/* Free space calculations: */
 
+static unsigned journal_space_from(struct journal_device *ja,
+                                  enum journal_space_from from)
+{
+       switch (from) {
+       case journal_space_discarded:
+               return ja->discard_idx;
+       case journal_space_clean_ondisk:
+               return ja->dirty_idx_ondisk;
+       case journal_space_clean:
+               return ja->dirty_idx;
+       default:
+               BUG();
+       }
+}
+
 unsigned bch2_journal_dev_buckets_available(struct journal *j,
-                                           struct journal_device *ja)
+                                           struct journal_device *ja,
+                                           enum journal_space_from from)
 {
        struct bch_fs *c = container_of(j, struct bch_fs, journal);
-       unsigned next = (ja->cur_idx + 1) % ja->nr;
-       unsigned available = (ja->discard_idx + ja->nr - next) % ja->nr;
+       unsigned available = (journal_space_from(ja, from) -
+                             ja->cur_idx - 1 + ja->nr) % ja->nr;
 
        /*
         * Allocator startup needs some journal space before we can do journal
        return available;
 }
 
-void bch2_journal_space_available(struct journal *j)
+static struct journal_space {
+       unsigned        next_entry;
+       unsigned        remaining;
+} __journal_space_available(struct journal *j, unsigned nr_devs_want,
+                           enum journal_space_from from)
 {
        struct bch_fs *c = container_of(j, struct bch_fs, journal);
        struct bch_dev *ca;
        unsigned sectors_next_entry     = UINT_MAX;
        unsigned sectors_total          = UINT_MAX;
-       unsigned max_entry_size         = min(j->buf[0].buf_size >> 9,
-                                             j->buf[1].buf_size >> 9);
-       unsigned i, nr_online = 0, nr_devs = 0;
+       unsigned i, nr_devs = 0;
        unsigned unwritten_sectors = j->reservations.prev_buf_unwritten
                ? journal_prev_buf(j)->sectors
                : 0;
-       bool can_discard = false;
-       int ret = 0;
-
-       lockdep_assert_held(&j->lock);
 
        rcu_read_lock();
-       for_each_member_device_rcu(ca, c, i,
-                                  &c->rw_devs[BCH_DATA_JOURNAL]) {
-               struct journal_device *ja = &ca->journal;
-
-               if (!ja->nr)
-                       continue;
-
-               while (ja->dirty_idx != ja->cur_idx &&
-                      ja->bucket_seq[ja->dirty_idx] < journal_last_seq(j))
-                       ja->dirty_idx = (ja->dirty_idx + 1) % ja->nr;
-
-               while (ja->dirty_idx_ondisk != ja->dirty_idx &&
-                      ja->bucket_seq[ja->dirty_idx_ondisk] < j->last_seq_ondisk)
-                       ja->dirty_idx_ondisk = (ja->dirty_idx_ondisk + 1) % ja->nr;
-
-               if (ja->discard_idx != ja->dirty_idx_ondisk)
-                       can_discard = true;
-
-               nr_online++;
-       }
-
-       j->can_discard = can_discard;
-
-       if (nr_online < c->opts.metadata_replicas_required) {
-               ret = -EROFS;
-               sectors_next_entry = 0;
-               goto out;
-       }
-
        for_each_member_device_rcu(ca, c, i,
                                   &c->rw_devs[BCH_DATA_JOURNAL]) {
                struct journal_device *ja = &ca->journal;
                if (!ja->nr)
                        continue;
 
-               buckets_this_device = bch2_journal_dev_buckets_available(j, ja);
+               buckets_this_device = bch2_journal_dev_buckets_available(j, ja, from);
                sectors_this_device = ja->sectors_free;
 
                /*
                        buckets_this_device * ca->mi.bucket_size +
                        sectors_this_device);
 
-               max_entry_size = min_t(unsigned, max_entry_size,
-                                      ca->mi.bucket_size);
-
                nr_devs++;
        }
+       rcu_read_unlock();
 
-       if (!sectors_next_entry ||
-           nr_devs < min_t(unsigned, nr_online, c->opts.metadata_replicas)) {
-               ret = -ENOSPC;
-               sectors_next_entry = 0;
-       } else if (!fifo_free(&j->pin)) {
-               ret = -ENOSPC;
-               sectors_next_entry = 0;
+       if (nr_devs < nr_devs_want)
+               return (struct journal_space) { 0, 0 };
+
+       return (struct journal_space) {
+               .next_entry     = sectors_next_entry,
+               .remaining      = max_t(int, 0, sectors_total - sectors_next_entry),
+       };
+}
+
+void bch2_journal_space_available(struct journal *j)
+{
+       struct bch_fs *c = container_of(j, struct bch_fs, journal);
+       struct bch_dev *ca;
+       struct journal_space discarded, clean_ondisk, clean;
+       unsigned max_entry_size         = min(j->buf[0].buf_size >> 9,
+                                             j->buf[1].buf_size >> 9);
+       unsigned i, nr_online = 0, nr_devs_want;
+       bool can_discard = false;
+       int ret = 0;
+
+       lockdep_assert_held(&j->lock);
+
+       rcu_read_lock();
+       for_each_member_device_rcu(ca, c, i,
+                                  &c->rw_devs[BCH_DATA_JOURNAL]) {
+               struct journal_device *ja = &ca->journal;
+
+               if (!ja->nr)
+                       continue;
+
+               while (ja->dirty_idx != ja->cur_idx &&
+                      ja->bucket_seq[ja->dirty_idx] < journal_last_seq(j))
+                       ja->dirty_idx = (ja->dirty_idx + 1) % ja->nr;
+
+               while (ja->dirty_idx_ondisk != ja->dirty_idx &&
+                      ja->bucket_seq[ja->dirty_idx_ondisk] < j->last_seq_ondisk)
+                       ja->dirty_idx_ondisk = (ja->dirty_idx_ondisk + 1) % ja->nr;
+
+               if (ja->discard_idx != ja->dirty_idx_ondisk)
+                       can_discard = true;
+
+               max_entry_size = min_t(unsigned, max_entry_size, ca->mi.bucket_size);
+               nr_online++;
        }
-out:
        rcu_read_unlock();
 
-       j->cur_entry_sectors    = sectors_next_entry;
+       j->can_discard = can_discard;
+
+       if (nr_online < c->opts.metadata_replicas_required) {
+               ret = -EROFS;
+               goto out;
+       }
+
+       if (!fifo_free(&j->pin)) {
+               ret = -ENOSPC;
+               goto out;
+       }
+
+       nr_devs_want = min_t(unsigned, nr_online, c->opts.metadata_replicas);
+
+       discarded       = __journal_space_available(j, nr_devs_want, journal_space_discarded);
+       clean_ondisk    = __journal_space_available(j, nr_devs_want, journal_space_clean_ondisk);
+       clean           = __journal_space_available(j, nr_devs_want, journal_space_clean);
+
+       if (!discarded.next_entry)
+               ret = -ENOSPC;
+out:
+       j->cur_entry_sectors    = !ret ? discarded.next_entry : 0;
        j->cur_entry_error      = ret;
 
        if (!ret)