]> git.hungrycats.org Git - linux/commitdiff
btrfs: account stripe_alloc trapped free space for honest statfs
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Mon, 27 Jul 2026 16:31:14 +0000 (12:31 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Fri, 18 Sep 2026 21:36:21 +0000 (17:36 -0400)
Free space in the partially filled stripes of a stripe_alloc (raid56
write-hole-safe) block group is real free space -- it is in the free
space tree and cache -- but the stripe-exclusive allocator cannot hand it
out until the whole stripe frees.  statfs therefore over-reports available
space, promising free space that a later allocation refuses with ENOSPC.

Track this trapped space per block group as stripe_unusable, summed into
space_info->bytes_stripe_unusable.  It is derived, so there is no on-disk
format change and the free space tree and cache are left untouched
(preserving the extent-tree/free-space-tree consistency btrfs check
verifies).

stripe_unusable is defined by a scan that accumulates every free byte of
the block group into a per-stripe array and then sums the stripes that are
partially filled.  A per-stripe accumulator, rather than a streaming sweep
or a per-stripe cache search, is what makes the result correct regardless
of the order the free space cache yields its ranges -- an offset-sorted
bitmap entry can emit runs that lie past a following extent entry, so the
ranges are not globally monotonic -- and regardless of whether free space
is stored as extents or bitmaps.  A debug-build assertion checks that the
scan distributes exactly the cache's free space.  Because trapped space
only settles at commit -- when the retire path returns partially filled
stripes and deleted extents are unpinned -- the scan is recomputed at
commit for groups whose free space changed (flagged cheaply on the
allocation and free paths), which suits the timescale of the reclaim it
feeds far better than a running per-extent tally.  The space_info total is
recomputed as the sum of the armed groups after those rescans, so it
cannot drift.

statfs subtracts bytes_stripe_unusable from the data f_bavail so df
reports what can actually be allocated, and the counter is exposed at
/sys/fs/btrfs/<uuid>/allocation/data/bytes_stripe_unusable.  It is
deliberately not part of btrfs_space_info_used(), so the reservation layer
is unchanged and this carries no ENOSPC-behaviour risk.

The counter is armed once a group's free space cache is loaded, so a group
contributes zero until then and statfs starts optimistic and settles to
honest as groups cache and commit.  It is disarmed when a group turns
read-only (its free space is then accounted as read-only and already
excluded from statfs) and re-armed by a rescan on the way back to
read-write, so a balance both recovers trapped space and updates the
counter.  A CONFIG_BTRFS_DEBUG-only sysfs trigger, stripe_unusable_rescan,
forces a full recompute for auditing the accounting.

Two deliberate imprecisions, matching existing behaviour rather than
bettering it: statfs subtracts bytes_stripe_unusable but not
bytes_zone_unusable, so stripe_alloc df reports availability before reclaim
while zoned df reports it after; and superblock stripes, permanently
unusable in every profile, are left to the existing bytes_super accounting
rather than separately reported here.  Both await a maintainer decision on
a common convention.

Assisted-by: Claude:claude-opus-4-8
fs/btrfs/block-group.c
fs/btrfs/block-group.h
fs/btrfs/free-space-cache.c
fs/btrfs/free-space-cache.h
fs/btrfs/space-info.h
fs/btrfs/super.c
fs/btrfs/sysfs.c
fs/btrfs/transaction.c
include/trace/events/btrfs.h

index c28b6ff8b96a39445da1d9e269d2fcd795cf9260..36655268bfaf5371253e5ed87b21a1eacdf35b96 100644 (file)
@@ -887,6 +887,66 @@ void btrfs_retire_open_stripes(struct btrfs_fs_info *fs_info,
        }
 }
 
+/*
+ * Commit-time recompute of stripe_unusable for every stripe_alloc block group
+ * whose free space changed this transaction.  Trapped free space only settles
+ * at commit -- partially filled stripes are returned by the retire above and
+ * deleted extents are unpinned into the free space cache -- so this is where
+ * the counter can be made authoritative.  Called after btrfs_finish_extent_commit
+ * so the free space cache reflects this transaction's frees.
+ */
+void btrfs_scan_stripe_unusable(struct btrfs_fs_info *fs_info, bool force)
+{
+       struct btrfs_space_info *sinfo;
+
+       if (!btrfs_test_opt(fs_info, STRIPE_ALLOC))
+               return;
+
+       list_for_each_entry(sinfo, &fs_info->space_info, list) {
+               u64 total = 0;
+               int raid;
+
+               if (!(sinfo->flags & BTRFS_BLOCK_GROUP_DATA))
+                       continue;
+               down_read(&sinfo->groups_sem);
+               for (raid = 0; raid < BTRFS_NR_RAID_TYPES; raid++) {
+                       struct btrfs_block_group *bg;
+
+                       if (!(btrfs_raid_array[raid].bg_flag &
+                             BTRFS_BLOCK_GROUP_RAID56_MASK))
+                               continue;
+                       list_for_each_entry(bg, &sinfo->block_groups[raid],
+                                           list) {
+                               bool dirty = test_and_clear_bit(
+                                       BLOCK_GROUP_FLAG_STRIPE_UNUSABLE_DIRTY,
+                                       &bg->runtime_flags);
+
+                               /*
+                                * @force rescans every armed group regardless of
+                                * the dirty flag (debug: isolates a missed
+                                * dirty-marking from a genuine cache difference).
+                                */
+                               if (force || dirty)
+                                       btrfs_block_group_rescan_stripe_unusable(bg);
+                               /*
+                                * Sum the authoritative per-group values into the
+                                * space_info total so it cannot drift from repeated
+                                * arm/disarm/rescan deltas.  Disarmed (read-only)
+                                * groups are excluded; their free space is already
+                                * accounted as read-only.
+                                */
+                               if (READ_ONCE(bg->stripe_unusable_ready))
+                                       total += READ_ONCE(bg->stripe_unusable);
+                       }
+               }
+               up_read(&sinfo->groups_sem);
+
+               spin_lock(&sinfo->lock);
+               sinfo->bytes_stripe_unusable = total;
+               spin_unlock(&sinfo->lock);
+       }
+}
+
 /*
  * With stripe-exclusive allocation, no in-place (nocow/prealloc) write may
  * land in a raid56 data block group: it could tear a stripe containing
@@ -1482,6 +1542,15 @@ static noinline void caching_thread(struct btrfs_work *work)
        block_group->cached = ret ? BTRFS_CACHE_ERROR : BTRFS_CACHE_FINISHED;
        spin_unlock(&block_group->lock);
 
+       /*
+        * The free space cache is now populated; compute the initial trapped
+        * free space of a stripe_alloc block group and arm incremental
+        * accounting.  Nothing until this point counted against
+        * stripe_unusable, so the scan captures the full loaded state.
+        */
+       if (!ret)
+               btrfs_block_group_init_stripe_unusable(block_group);
+
 #ifdef CONFIG_BTRFS_DEBUG
        if (btrfs_should_fragment_free_space(block_group)) {
                u64 bytes_used;
@@ -1822,6 +1891,13 @@ int btrfs_remove_block_group(struct btrfs_trans_handle *trans,
        WARN_ON(!list_empty(&block_group->dirty_list));
        spin_unlock(&trans->transaction->dirty_bgs_lock);
 
+       /*
+        * A removed group has already been marked read-only (which disarmed
+        * stripe_unusable), but disarm defensively before the free space cache
+        * is torn down so the space_info total never keeps a stale contribution.
+        */
+       btrfs_block_group_disarm_stripe_unusable(block_group);
+
        btrfs_remove_free_space_cache(block_group);
 
        spin_lock(&block_group->space_info->lock);
@@ -2028,6 +2104,14 @@ static int inc_block_group_ro(struct btrfs_block_group *cache, bool force)
 out:
        spin_unlock(&cache->lock);
        spin_unlock(&sinfo->lock);
+       /*
+        * A read-only group's free space (trapped included) is now accounted
+        * as read-only and excluded from statfs; drop its stripe_unusable
+        * contribution so it is not subtracted twice.  Idempotent, so repeated
+        * ro increments are harmless.
+        */
+       if (!ret)
+               btrfs_block_group_disarm_stripe_unusable(cache);
        if (ret == -ENOSPC && btrfs_test_opt(cache->fs_info, ENOSPC_DEBUG)) {
                btrfs_info(cache->fs_info,
                        "unable to make block group %llu ro", cache->start);
@@ -3118,6 +3202,15 @@ static int read_one_block_group(struct btrfs_fs_info *info,
        trace_btrfs_add_block_group(info, cache, 0);
        btrfs_add_bg_to_space_info(info, cache);
 
+       /*
+        * Full or empty groups are marked cached above without going through
+        * the caching thread, so arm their stripe_unusable accounting here
+        * (both cases scan to zero trapped space).  Groups that still need
+        * caching are armed when caching_thread finishes.
+        */
+       if (cache->cached == BTRFS_CACHE_FINISHED)
+               btrfs_block_group_init_stripe_unusable(cache);
+
        set_avail_alloc_bits(info, cache->flags);
        if (btrfs_chunk_writeable(info, cache->start)) {
                if (cache->used == 0 && cache->remap_bytes == 0) {
@@ -3634,6 +3727,9 @@ struct btrfs_block_group *btrfs_make_block_group(struct btrfs_trans_handle *tran
        btrfs_link_bg_list(cache, &trans->new_bgs);
        btrfs_inc_delayed_refs_rsv_bg_inserts(fs_info);
 
+       /* A freshly created stripe_alloc group starts fully free (0 trapped). */
+       btrfs_block_group_init_stripe_unusable(cache);
+
        set_avail_alloc_bits(fs_info, type);
        return cache;
 }
@@ -3777,6 +3873,7 @@ unlock_out:
 void btrfs_dec_block_group_ro(struct btrfs_block_group *cache)
 {
        struct btrfs_space_info *sinfo = cache->space_info;
+       bool became_rw = false;
 
        BUG_ON(!cache->ro);
 
@@ -3794,9 +3891,18 @@ void btrfs_dec_block_group_ro(struct btrfs_block_group *cache)
                }
                sinfo->bytes_readonly -= btrfs_block_group_available_space(cache);
                list_del_init(&cache->ro_list);
+               became_rw = true;
        }
        spin_unlock(&cache->lock);
        spin_unlock(&sinfo->lock);
+
+       /*
+        * Back to read-write: re-arm stripe_unusable accounting, rescanning
+        * the current free space (it may have changed while read-only).  The
+        * scan re-adds this group's trapped free space to the space_info total.
+        */
+       if (became_rw)
+               btrfs_block_group_init_stripe_unusable(cache);
 }
 
 static int update_block_group_item(struct btrfs_trans_handle *trans,
index b2feb6ff254bde620dcba3c9352d8acb541f85e6..af18ad4209cb6eba90abfacaba813f384ba18f66 100644 (file)
@@ -107,6 +107,11 @@ enum btrfs_block_group_flags {
         * from violations and skips such groups (sticky, debug only).
         */
        BLOCK_GROUP_FLAG_STRIPE_RELOC_USED,
+       /*
+        * Free space changed this transaction, so the group's stripe_unusable
+        * counter must be rescanned at commit (stripe_alloc only).
+        */
+       BLOCK_GROUP_FLAG_STRIPE_UNUSABLE_DIRTY,
 };
 
 enum btrfs_caching_type {
@@ -233,6 +238,19 @@ struct btrfs_block_group {
        /* Private to the commit-time retire walk. */
        struct list_head open_stripe_retire_list;
 
+       /*
+        * Free bytes trapped in partially filled stripes of a stripe_alloc
+        * block group: they are genuine free space (present in the free space
+        * cache and the free space tree, so no on-disk accounting changes) but
+        * the stripe-exclusive allocator cannot hand them out until the whole
+        * stripe frees.  Computed once at cache-in and maintained incrementally
+        * as free space is added; both this counter and the ready flag are
+        * protected by free_space_ctl->tree_lock.  Only statfs subtracts it
+        * (honest f_bavail); the reservation layer is deliberately unchanged.
+        */
+       u64 stripe_unusable;
+       bool stripe_unusable_ready;
+
        /*
         * Used for several lists:
         *
@@ -379,6 +397,7 @@ void btrfs_clear_data_reloc_bg(struct btrfs_block_group *bg);
 void btrfs_release_data_reloc_bg(struct btrfs_fs_info *fs_info);
 bool btrfs_stripe_alloc_forces_cow(struct btrfs_fs_info *fs_info, u64 bytenr);
 bool btrfs_is_stripe_alloc_bg(const struct btrfs_block_group *bg);
+void btrfs_scan_stripe_unusable(struct btrfs_fs_info *fs_info, bool force);
 #ifdef CONFIG_BTRFS_DEBUG
 void btrfs_stripe_check_write(struct btrfs_fs_info *fs_info,
                              u64 full_stripe_start, bool sub_stripe);
index 3f0f25dff0c8168a113c9dcdc0fbfb7163465fab..b85084f8c869800b2723eed3bc30393c069b7338 100644 (file)
@@ -1481,6 +1481,8 @@ static int __btrfs_add_free_space_zoned(struct btrfs_block_group *block_group,
        return 0;
 }
 
+static void stripe_unusable_mark_dirty(struct btrfs_block_group *bg);
+
 int btrfs_add_free_space(struct btrfs_block_group *block_group,
                         u64 bytenr, u64 size)
 {
@@ -1496,6 +1498,7 @@ int btrfs_add_free_space(struct btrfs_block_group *block_group,
        if (btrfs_test_opt(block_group->fs_info, DISCARD_SYNC))
                trim_state = BTRFS_TRIM_STATE_TRIMMED;
 
+       stripe_unusable_mark_dirty(block_group);
        return __btrfs_add_free_space(block_group, bytenr, size, trim_state);
 }
 
@@ -1874,6 +1877,218 @@ out:
        return ret;
 }
 
+/*
+ * Distribute a free range [a, end) into the per-stripe free-byte accumulator,
+ * splitting at full stripe boundaries.  Indexing by stripe makes the result
+ * independent of the order ranges arrive in -- the free space cache can visit
+ * them out of offset order (a bitmap entry spans past a later extent entry) --
+ * and of the extent-vs-bitmap representation.
+ */
+static void stripe_dist(u32 *freep, u64 bg_start, u64 fsl, u64 a, u64 end,
+                       u64 *counted)
+{
+       while (a < end) {
+               u64 idx = div64_u64(a - bg_start, fsl);
+               u64 stripe_end = bg_start + (idx + 1) * fsl;
+               u64 c = min(end, stripe_end);
+
+               freep[idx] += c - a;
+               *counted += c - a;
+               a = c;
+       }
+}
+
+/* Number of full stripes in a block group; the size of the freep array. */
+static u64 stripe_unusable_nstripes(const struct btrfs_block_group *bg)
+{
+       u64 fsl = bg->full_stripe_len;
+
+       return div64_u64(bg->length + fsl - 1, fsl);
+}
+
+/*
+ * Total trapped free bytes (free space in partially filled stripes) over the
+ * whole block group -- the authoritative definition of stripe_unusable,
+ * recomputed at cache-in and after each commit that changed the group's free
+ * space.  Every free byte is accumulated into a per-stripe array (so a stripe
+ * whose total free is neither zero nor a whole stripe is partially filled, and
+ * its free bytes are trapped).  @freep is a zeroed array of stripe_unusable_
+ * nstripes() u32 entries the caller allocates before taking the tree lock (it
+ * can be large); this runs with ctl->tree_lock held.
+ */
+static u64 stripe_unusable_scan(struct btrfs_block_group *bg, u32 *freep,
+                               u64 nstripes)
+{
+       struct btrfs_free_space_ctl *ctl = bg->free_space_ctl;
+       const u32 unit = bg->fs_info->sectorsize;
+       const u64 fsl = bg->full_stripe_len;
+       const u64 bg_start = bg->start;
+       u64 counted = 0;
+       u64 trapped = 0;
+       struct rb_node *n;
+       u64 i;
+
+       lockdep_assert_held(&ctl->tree_lock);
+
+       for (n = rb_first(&ctl->free_space_offset); n; n = rb_next(n)) {
+               struct btrfs_free_space *e =
+                       rb_entry(n, struct btrfs_free_space, offset_index);
+
+               if (e->bitmap) {
+                       unsigned long b = 0;
+
+                       while (b < BITS_PER_BITMAP) {
+                               unsigned long z;
+
+                               b = find_next_bit(e->bitmap, BITS_PER_BITMAP, b);
+                               if (b >= BITS_PER_BITMAP)
+                                       break;
+                               z = find_next_zero_bit(e->bitmap,
+                                                      BITS_PER_BITMAP, b);
+                               stripe_dist(freep, bg_start, fsl,
+                                           e->offset + (u64)b * unit,
+                                           e->offset + (u64)z * unit, &counted);
+                               b = z;
+                       }
+               } else {
+                       stripe_dist(freep, bg_start, fsl, e->offset,
+                                   e->offset + e->bytes, &counted);
+               }
+       }
+
+       /* Every free byte was distributed to exactly one stripe (debug check). */
+       ASSERT(counted == ctl->free_space,
+              "stripe_unusable_scan counted=%llu != free_space=%llu",
+              counted, ctl->free_space);
+
+       for (i = 0; i < nstripes; i++)
+               if (freep[i] && freep[i] < fsl)
+                       trapped += freep[i];
+       return trapped;
+}
+
+/*
+ * Compute a stripe_alloc block group's initial trapped free space once its
+ * free space cache is loaded, and enable incremental accounting from here on.
+ * Idempotent-safe to call on a not-yet-populated (new, empty) block group,
+ * where it simply arms accounting with a zero baseline.
+ */
+void btrfs_block_group_init_stripe_unusable(struct btrfs_block_group *bg)
+{
+       struct btrfs_free_space_ctl *ctl = bg->free_space_ctl;
+       u64 trapped, nstripes;
+       u32 *freep;
+
+       if (!btrfs_is_stripe_alloc_bg(bg))
+               return;
+       if (READ_ONCE(bg->stripe_unusable_ready))
+               return;
+
+       nstripes = stripe_unusable_nstripes(bg);
+       freep = kvcalloc(nstripes, sizeof(*freep), GFP_NOFS);
+       if (!freep)
+               return;
+
+       spin_lock(&ctl->tree_lock);
+       if (bg->stripe_unusable_ready) {
+               spin_unlock(&ctl->tree_lock);
+               kvfree(freep);
+               return;
+       }
+       trapped = stripe_unusable_scan(bg, freep, nstripes);
+       bg->stripe_unusable = trapped;
+       bg->stripe_unusable_ready = true;
+       spin_unlock(&ctl->tree_lock);
+       kvfree(freep);
+
+       if (trapped) {
+               spin_lock(&bg->space_info->lock);
+               btrfs_space_info_update_bytes_stripe_unusable(bg->space_info,
+                                                             trapped);
+               spin_unlock(&bg->space_info->lock);
+       }
+}
+
+/*
+ * Remove a stripe_alloc block group's trapped-space contribution from the
+ * space_info total and stop incremental accounting.  Used when the group turns
+ * read-only or is removed: from that point its free space is accounted
+ * elsewhere (bytes_readonly / removal) and statfs already excludes it, so
+ * leaving stripe_unusable armed would double count.  Re-arm on the way back to
+ * read-write with btrfs_block_group_init_stripe_unusable().  Idempotent.
+ */
+void btrfs_block_group_disarm_stripe_unusable(struct btrfs_block_group *bg)
+{
+       struct btrfs_free_space_ctl *ctl = bg->free_space_ctl;
+       u64 trapped;
+
+       if (!btrfs_is_stripe_alloc_bg(bg))
+               return;
+
+       spin_lock(&ctl->tree_lock);
+       if (!bg->stripe_unusable_ready) {
+               spin_unlock(&ctl->tree_lock);
+               return;
+       }
+       trapped = bg->stripe_unusable;
+       bg->stripe_unusable_ready = false;
+       spin_unlock(&ctl->tree_lock);
+
+       if (trapped) {
+               spin_lock(&bg->space_info->lock);
+               btrfs_space_info_update_bytes_stripe_unusable(bg->space_info,
+                                                             -(s64)trapped);
+               spin_unlock(&bg->space_info->lock);
+       }
+}
+
+
+/*
+ * Recompute a stripe_alloc block group's trapped free space from a fresh scan
+ * and fold the change into the space_info total.  Called at commit for groups
+ * whose free space changed this transaction; trapped space only settles at the
+ * commit that returns partially filled stripes and unpins deleted extents, so
+ * a per-commit rescan is both correct and far cheaper than a running tally.
+ */
+void btrfs_block_group_rescan_stripe_unusable(struct btrfs_block_group *bg)
+{
+       struct btrfs_free_space_ctl *ctl = bg->free_space_ctl;
+       u64 nstripes;
+       u32 *freep;
+
+       if (!btrfs_is_stripe_alloc_bg(bg))
+               return;
+
+       nstripes = stripe_unusable_nstripes(bg);
+       freep = kvcalloc(nstripes, sizeof(*freep), GFP_NOFS);
+       if (!freep)
+               return;
+
+       spin_lock(&ctl->tree_lock);
+       if (bg->stripe_unusable_ready)          /* skip if disarmed (read-only) */
+               bg->stripe_unusable = stripe_unusable_scan(bg, freep, nstripes);
+       spin_unlock(&ctl->tree_lock);
+       kvfree(freep);
+       /*
+        * The space_info total is recomputed authoritatively from the sum of
+        * armed groups by btrfs_scan_stripe_unusable() after all rescans, so it
+        * cannot drift; no per-group delta is applied here.
+        */
+}
+
+/*
+ * Flag a stripe_alloc block group's stripe_unusable counter for recomputation
+ * at the next commit, because its free space just changed.  Cheap (one bit) so
+ * it can sit on the hot allocation and free paths; the actual scan is deferred
+ * to commit.  Reading stripe_unusable_ready without the tree lock is a benign
+ * race: a missed or spurious flag only delays or repeats a rescan.
+ */
+static void stripe_unusable_mark_dirty(struct btrfs_block_group *bg)
+{
+       if (READ_ONCE(bg->stripe_unusable_ready))
+               set_bit(BLOCK_GROUP_FLAG_STRIPE_UNUSABLE_DIRTY, &bg->runtime_flags);
+}
+
 /*
  * Align an offset up to the next full stripe boundary.  Full stripe geometry
  * is relative to the start of the block group, which is not necessarily
@@ -2033,6 +2248,8 @@ out:
        btrfs_discard_update_discardable(block_group);
        spin_unlock(&ctl->tree_lock);
 
+       if (!ret)
+               stripe_unusable_mark_dirty(block_group);
        if (head_len)
                __btrfs_add_free_space(block_group, head_start, head_len,
                                       head_trim_state);
index 06a6bc70d9ac4d6486f6c6921443801a0b48ed8e..6bee1e7a7913c954c855ddc4c662806651fc9865 100644 (file)
@@ -113,6 +113,9 @@ u64 btrfs_find_space_for_alloc(struct btrfs_block_group *block_group,
                               u64 *max_extent_size);
 int btrfs_claim_free_stripe_run(struct btrfs_block_group *block_group,
                                u64 want_bytes, u64 *start, u64 *len);
+void btrfs_block_group_init_stripe_unusable(struct btrfs_block_group *block_group);
+void btrfs_block_group_disarm_stripe_unusable(struct btrfs_block_group *block_group);
+void btrfs_block_group_rescan_stripe_unusable(struct btrfs_block_group *block_group);
 void btrfs_dump_free_space(struct btrfs_block_group *block_group,
                           u64 bytes);
 int btrfs_find_space_cluster(struct btrfs_block_group *block_group,
index d0130c8ba3ddafc9cd62678845e004c189e1dabb..ef5a0ea9039db74cc554980a174b60f194014d25 100644 (file)
@@ -150,6 +150,12 @@ struct btrfs_space_info {
        u64 bytes_readonly;     /* total bytes that are read only */
        u64 bytes_zone_unusable;        /* total bytes that are unusable until
                                           resetting the device zone */
+       u64 bytes_stripe_unusable;      /* total free bytes trapped in partially
+                                          filled stripes of stripe_alloc block
+                                          groups; free but not allocatable until
+                                          the whole stripe frees.  Informational
+                                          only (subtracted in statfs); NOT part
+                                          of btrfs_space_info_used(). */
 
        u64 max_extent_size;    /* This will hold the maximum extent size of
                                   the space info if we had an ENOSPC in the
@@ -282,6 +288,7 @@ btrfs_space_info_update_##name(struct btrfs_space_info *sinfo,              \
 DECLARE_SPACE_INFO_UPDATE(bytes_may_use, "space_info");
 DECLARE_SPACE_INFO_UPDATE(bytes_pinned, "pinned");
 DECLARE_SPACE_INFO_UPDATE(bytes_zone_unusable, "zone_unusable");
+DECLARE_SPACE_INFO_UPDATE(bytes_stripe_unusable, "stripe_unusable");
 
 static inline u64 btrfs_space_info_used(const struct btrfs_space_info *s_info,
                                        bool may_use_included)
@@ -291,6 +298,7 @@ static inline u64 btrfs_space_info_used(const struct btrfs_space_info *s_info,
        return s_info->bytes_used + s_info->bytes_reserved +
                s_info->bytes_pinned + s_info->bytes_readonly +
                s_info->bytes_zone_unusable +
+               s_info->bytes_stripe_unusable +
                (may_use_included ? s_info->bytes_may_use : 0);
 }
 
index 90bf49dfb76625b92441fc15aacd6ca7e2f5e729..86d6a755bee106465ce51043373ec1971a8d706c 100644 (file)
@@ -1743,6 +1743,24 @@ static int btrfs_statfs(struct dentry *dentry, struct kstatfs *buf)
                                        factor = btrfs_bg_type_to_factor(
                                                btrfs_raid_array[i].bg_flag);
                        }
+
+                       /*
+                        * Free space trapped in partially filled stripes of
+                        * stripe_alloc (raid56 write-hole-safe) block groups is
+                        * present in the free space tree but cannot be allocated
+                        * until the whole stripe frees.  Subtract it so f_bavail
+                        * reflects what can actually be allocated.  Only armed,
+                        * non-read-only groups contribute (read-only groups are
+                        * already removed above); the counter is logical, so
+                        * scale it to the disk units of total_free_data.
+                        */
+                       if (found->bytes_stripe_unusable) {
+                               u64 su = found->bytes_stripe_unusable * factor;
+
+                               if (su > total_free_data)
+                                       su = total_free_data;
+                               total_free_data -= su;
+                       }
                }
 
                /*
index c5bb1c7eac6afeed148210973c5c5f2ee4b69f56..92f4d87d72afa548f90225a6a4f5e682c9be1fec 100644 (file)
@@ -778,6 +778,35 @@ static ssize_t btrfs_force_chunk_alloc_store(struct kobject *kobj,
 }
 BTRFS_ATTR_W(space_info, force_chunk_alloc, btrfs_force_chunk_alloc_store);
 
+/*
+ * Force a full recompute of stripe_unusable across every armed stripe_alloc
+ * block group, ignoring the per-group dirty flag.  If the reported value rises
+ * to match a fresh umount/mount scan, groups were being missed by dirty
+ * marking; if it stays put, the live cache genuinely differs from a reload.
+ */
+static ssize_t btrfs_stripe_unusable_rescan_store(struct kobject *kobj,
+                                                 struct kobj_attribute *a,
+                                                 const char *buf, size_t len)
+{
+       struct btrfs_fs_info *fs_info = to_fs_info(get_btrfs_kobj(kobj));
+       bool val;
+       int ret;
+
+       if (!capable(CAP_SYS_ADMIN))
+               return -EPERM;
+
+       ret = kstrtobool(buf, &val);
+       if (ret)
+               return ret;
+       if (!val)
+               return -EINVAL;
+
+       btrfs_scan_stripe_unusable(fs_info, true);
+       return len;
+}
+BTRFS_ATTR_W(space_info, stripe_unusable_rescan,
+            btrfs_stripe_unusable_rescan_store);
+
 #endif
 
 SPACE_INFO_ATTR(flags);
@@ -788,6 +817,7 @@ SPACE_INFO_ATTR(bytes_reserved);
 SPACE_INFO_ATTR(bytes_may_use);
 SPACE_INFO_ATTR(bytes_readonly);
 SPACE_INFO_ATTR(bytes_zone_unusable);
+SPACE_INFO_ATTR(bytes_stripe_unusable);
 SPACE_INFO_ATTR(disk_used);
 SPACE_INFO_ATTR(disk_total);
 SPACE_INFO_ATTR(reclaim_count);
@@ -916,6 +946,7 @@ static struct attribute *space_info_attrs[] = {
        BTRFS_ATTR_PTR(space_info, bytes_may_use),
        BTRFS_ATTR_PTR(space_info, bytes_readonly),
        BTRFS_ATTR_PTR(space_info, bytes_zone_unusable),
+       BTRFS_ATTR_PTR(space_info, bytes_stripe_unusable),
        BTRFS_ATTR_PTR(space_info, disk_used),
        BTRFS_ATTR_PTR(space_info, disk_total),
        BTRFS_ATTR_PTR(space_info, bg_reclaim_threshold),
@@ -928,6 +959,7 @@ static struct attribute *space_info_attrs[] = {
        BTRFS_ATTR_PTR(space_info, periodic_reclaim),
 #ifdef CONFIG_BTRFS_DEBUG
        BTRFS_ATTR_PTR(space_info, force_chunk_alloc),
+       BTRFS_ATTR_PTR(space_info, stripe_unusable_rescan),
 #endif
        NULL,
 };
index d8f11d8e26a811a2212a89a508de1c8b1cf40eb6..0b3961bcba31373f44f3ba4724b6c93db26bc34c 100644 (file)
@@ -2638,6 +2638,12 @@ int btrfs_commit_transaction(struct btrfs_trans_handle *trans)
        if (unlikely(ret))
                goto scrub_continue;
 
+       /*
+        * The free space cache now reflects this transaction's frees; recompute
+        * stripe_unusable for stripe_alloc groups whose free space changed.
+        */
+       btrfs_scan_stripe_unusable(fs_info, false);
+
        if (test_bit(BTRFS_TRANS_HAVE_FREE_BGS, &cur_trans->flags))
                btrfs_clear_space_info_full(fs_info);
 
index 6ecfab97c1a9006bdc0456db05bdc1d19b50d2c9..e1af4dd8287cec1423da5889e4bb0aca84627400 100644 (file)
@@ -3291,6 +3291,14 @@ DEFINE_EVENT(btrfs__space_info_update, update_bytes_zone_unusable,
        TP_ARGS(fs_info, sinfo, old, diff)
 );
 
+DEFINE_EVENT(btrfs__space_info_update, update_bytes_stripe_unusable,
+
+       TP_PROTO(const struct btrfs_fs_info *fs_info,
+                const struct btrfs_space_info *sinfo, u64 old, s64 diff),
+
+       TP_ARGS(fs_info, sinfo, old, diff)
+);
+
 DECLARE_EVENT_CLASS(btrfs_raid56_bio,
 
        TP_PROTO(const struct btrfs_raid_bio *rbio,