* [PATCH 0/2] squota delete fstests @ 2026-05-13 0:43 Boris Burkov 2026-05-13 0:43 ` [PATCH 1/2] btrfs: inline enable_quota helper in test 301 Boris Burkov 2026-05-13 0:43 ` [PATCH 2/2] btrfs: test qgroup deletion races with squota writes Boris Burkov 0 siblings, 2 replies; 10+ messages in thread From: Boris Burkov @ 2026-05-13 0:43 UTC (permalink / raw) To: linux-btrfs, kernel-team, fstests Resurrect a failed previous attempt to land tests exercising edge cases of squota qgroup deletion. Previous attempt: https://lore.kernel.org/fstests/ce4a79cafb6790ef6d1e141d65195f72f469ae4d.1706035378.git.boris@bur.io/ Since then, there have been some more bugs fixed so include some additional testing reflecting that, as well as incorporating the last round of feedback. Boris Burkov (2): btrfs: inline enable_quota helper in test 301 btrfs: test qgroup deletion races with squota writes tests/btrfs/301 | 14 +---- tests/btrfs/348 | 136 ++++++++++++++++++++++++++++++++++++++++++++ tests/btrfs/348.out | 10 ++++ 3 files changed, 148 insertions(+), 12 deletions(-) create mode 100755 tests/btrfs/348 create mode 100644 tests/btrfs/348.out -- 2.54.0 ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 1/2] btrfs: inline enable_quota helper in test 301 2026-05-13 0:43 [PATCH 0/2] squota delete fstests Boris Burkov @ 2026-05-13 0:43 ` Boris Burkov 2026-05-14 3:34 ` Zorro Lang 2026-05-19 6:18 ` Anand Jain 2026-05-13 0:43 ` [PATCH 2/2] btrfs: test qgroup deletion races with squota writes Boris Burkov 1 sibling, 2 replies; 10+ messages in thread From: Boris Burkov @ 2026-05-13 0:43 UTC (permalink / raw) To: linux-btrfs, kernel-team, fstests The enable_quota helper in btrfs/301 takes a mode argument ('n', 's', or anything else) to switch between "no quota", "simple quota", and "full quota" enable. Only the simple-quota path is ever exercised by this test, so the dispatch is dead weight and the indirection just obscures the actual btrfs command being run. Inline the two call sites to call 'btrfs quota enable --simple' directly and drop the helper. Signed-off-by: Boris Burkov <boris@bur.io> --- tests/btrfs/301 | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/tests/btrfs/301 b/tests/btrfs/301 index 1f72a97b..31243aab 100755 --- a/tests/btrfs/301 +++ b/tests/btrfs/301 @@ -169,16 +169,6 @@ do_enospc_falloc() do_falloc $file $sz } -enable_quota() -{ - local mode=$1 - - [ $mode == "n" ] && return - arg=$([ $mode == "s" ] && echo "--simple") - - $BTRFS_UTIL_PROG quota enable $arg $SCRATCH_MNT -} - get_subvid() { _btrfs_get_subvolid $SCRATCH_MNT subv @@ -198,7 +188,7 @@ prepare() { _scratch_mkfs >> $seqres.full _scratch_mount - enable_quota "s" + $BTRFS_UTIL_PROG quota enable --simple $SCRATCH_MNT $BTRFS_UTIL_PROG subvolume create $subv >> $seqres.full local subvid=$(get_subvid) set_subvol_limit $subvid $limit @@ -421,7 +411,7 @@ enable_mature() # Sync before enabling squotas to reliably *not* count the writes # we did before enabling. sync - enable_quota "s" + $BTRFS_UTIL_PROG quota enable --simple $SCRATCH_MNT set_subvol_limit $subvid $limit _scratch_cycle_mount usage=$(get_subvol_usage $subvid) -- 2.54.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH 1/2] btrfs: inline enable_quota helper in test 301 2026-05-13 0:43 ` [PATCH 1/2] btrfs: inline enable_quota helper in test 301 Boris Burkov @ 2026-05-14 3:34 ` Zorro Lang 2026-05-15 21:15 ` Boris Burkov 2026-05-19 6:18 ` Anand Jain 1 sibling, 1 reply; 10+ messages in thread From: Zorro Lang @ 2026-05-14 3:34 UTC (permalink / raw) To: Boris Burkov; +Cc: linux-btrfs, kernel-team, fstests On Tue, May 12, 2026 at 05:43:33PM -0700, Boris Burkov wrote: > The enable_quota helper in btrfs/301 takes a mode argument ('n', 's', > or anything else) to switch between "no quota", "simple quota", and > "full quota" enable. Only the simple-quota path is ever exercised by > this test, so the dispatch is dead weight and the indirection just > obscures the actual btrfs command being run. > > Inline the two call sites to call 'btrfs quota enable --simple' > directly and drop the helper. > > Signed-off-by: Boris Burkov <boris@bur.io> > --- Hahah, to be honest, the title '[PATCH 0/2] squota delete fstests' gave me a minor heart attack. I thought it was a critical bug report about fstests accidentally deleting itself while running :-D > tests/btrfs/301 | 14 ++------------ > 1 file changed, 2 insertions(+), 12 deletions(-) > > diff --git a/tests/btrfs/301 b/tests/btrfs/301 > index 1f72a97b..31243aab 100755 > --- a/tests/btrfs/301 > +++ b/tests/btrfs/301 > @@ -169,16 +169,6 @@ do_enospc_falloc() > do_falloc $file $sz > } > > -enable_quota() > -{ > - local mode=$1 > - > - [ $mode == "n" ] && return > - arg=$([ $mode == "s" ] && echo "--simple") > - > - $BTRFS_UTIL_PROG quota enable $arg $SCRATCH_MNT > -} > - > get_subvid() > { > _btrfs_get_subvolid $SCRATCH_MNT subv > @@ -198,7 +188,7 @@ prepare() > { > _scratch_mkfs >> $seqres.full > _scratch_mount > - enable_quota "s" > + $BTRFS_UTIL_PROG quota enable --simple $SCRATCH_MNT > $BTRFS_UTIL_PROG subvolume create $subv >> $seqres.full > local subvid=$(get_subvid) > set_subvol_limit $subvid $limit > @@ -421,7 +411,7 @@ enable_mature() > # Sync before enabling squotas to reliably *not* count the writes > # we did before enabling. > sync > - enable_quota "s" > + $BTRFS_UTIL_PROG quota enable --simple $SCRATCH_MNT This change doesn't seem related to patch 2/2. I think we can review these 2 patches separately. For this patch, I'm fine with or without it. If the btrfs list prefers to have it, or move it to be a common helper, I'm good with that. Thanks, Zorro > set_subvol_limit $subvid $limit > _scratch_cycle_mount > usage=$(get_subvol_usage $subvid) > -- > 2.54.0 > > ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 1/2] btrfs: inline enable_quota helper in test 301 2026-05-14 3:34 ` Zorro Lang @ 2026-05-15 21:15 ` Boris Burkov 2026-05-16 13:55 ` Zorro Lang 0 siblings, 1 reply; 10+ messages in thread From: Boris Burkov @ 2026-05-15 21:15 UTC (permalink / raw) To: linux-btrfs, kernel-team, fstests On Thu, May 14, 2026 at 11:34:00AM +0800, Zorro Lang wrote: > On Tue, May 12, 2026 at 05:43:33PM -0700, Boris Burkov wrote: > > The enable_quota helper in btrfs/301 takes a mode argument ('n', 's', > > or anything else) to switch between "no quota", "simple quota", and > > "full quota" enable. Only the simple-quota path is ever exercised by > > this test, so the dispatch is dead weight and the indirection just > > obscures the actual btrfs command being run. > > > > Inline the two call sites to call 'btrfs quota enable --simple' > > directly and drop the helper. > > > > Signed-off-by: Boris Burkov <boris@bur.io> > > --- > > Hahah, to be honest, the title '[PATCH 0/2] squota delete fstests' gave me > a minor heart attack. I thought it was a critical bug report about fstests > accidentally deleting itself while running :-D > Oops, sorry! > > tests/btrfs/301 | 14 ++------------ > > 1 file changed, 2 insertions(+), 12 deletions(-) > > > > diff --git a/tests/btrfs/301 b/tests/btrfs/301 > > index 1f72a97b..31243aab 100755 > > --- a/tests/btrfs/301 > > +++ b/tests/btrfs/301 > > @@ -169,16 +169,6 @@ do_enospc_falloc() > > do_falloc $file $sz > > } > > > > -enable_quota() > > -{ > > - local mode=$1 > > - > > - [ $mode == "n" ] && return > > - arg=$([ $mode == "s" ] && echo "--simple") > > - > > - $BTRFS_UTIL_PROG quota enable $arg $SCRATCH_MNT > > -} > > - > > get_subvid() > > { > > _btrfs_get_subvolid $SCRATCH_MNT subv > > @@ -198,7 +188,7 @@ prepare() > > { > > _scratch_mkfs >> $seqres.full > > _scratch_mount > > - enable_quota "s" > > + $BTRFS_UTIL_PROG quota enable --simple $SCRATCH_MNT > > $BTRFS_UTIL_PROG subvolume create $subv >> $seqres.full > > local subvid=$(get_subvid) > > set_subvol_limit $subvid $limit > > @@ -421,7 +411,7 @@ enable_mature() > > # Sync before enabling squotas to reliably *not* count the writes > > # we did before enabling. > > sync > > - enable_quota "s" > > + $BTRFS_UTIL_PROG quota enable --simple $SCRATCH_MNT > > This change doesn't seem related to patch 2/2. I think we can review > these 2 patches separately. > > For this patch, I'm fine with or without it. If the btrfs list prefers > to have it, or move it to be a common helper, I'm good with that. > > Thanks, > Zorro > I pulled this into a separate patch based on Filipe's feedback on the old version I failed to get in. Agreed it doesn't need to be a series, just felt related enough to send in a batch, due to the context. Thanks for taking a look, Boris > > set_subvol_limit $subvid $limit > > _scratch_cycle_mount > > usage=$(get_subvol_usage $subvid) > > -- > > 2.54.0 > > > > ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 1/2] btrfs: inline enable_quota helper in test 301 2026-05-15 21:15 ` Boris Burkov @ 2026-05-16 13:55 ` Zorro Lang 0 siblings, 0 replies; 10+ messages in thread From: Zorro Lang @ 2026-05-16 13:55 UTC (permalink / raw) To: Boris Burkov; +Cc: linux-btrfs, kernel-team, fstests On Fri, May 15, 2026 at 02:15:49PM -0700, Boris Burkov wrote: > On Thu, May 14, 2026 at 11:34:00AM +0800, Zorro Lang wrote: > > On Tue, May 12, 2026 at 05:43:33PM -0700, Boris Burkov wrote: > > > The enable_quota helper in btrfs/301 takes a mode argument ('n', 's', > > > or anything else) to switch between "no quota", "simple quota", and > > > "full quota" enable. Only the simple-quota path is ever exercised by > > > this test, so the dispatch is dead weight and the indirection just > > > obscures the actual btrfs command being run. > > > > > > Inline the two call sites to call 'btrfs quota enable --simple' > > > directly and drop the helper. > > > > > > Signed-off-by: Boris Burkov <boris@bur.io> > > > --- > > > > Hahah, to be honest, the title '[PATCH 0/2] squota delete fstests' gave me > > a minor heart attack. I thought it was a critical bug report about fstests > > accidentally deleting itself while running :-D > > > > Oops, sorry! > > > > tests/btrfs/301 | 14 ++------------ > > > 1 file changed, 2 insertions(+), 12 deletions(-) > > > > > > diff --git a/tests/btrfs/301 b/tests/btrfs/301 > > > index 1f72a97b..31243aab 100755 > > > --- a/tests/btrfs/301 > > > +++ b/tests/btrfs/301 > > > @@ -169,16 +169,6 @@ do_enospc_falloc() > > > do_falloc $file $sz > > > } > > > > > > -enable_quota() > > > -{ > > > - local mode=$1 > > > - > > > - [ $mode == "n" ] && return > > > - arg=$([ $mode == "s" ] && echo "--simple") > > > - > > > - $BTRFS_UTIL_PROG quota enable $arg $SCRATCH_MNT > > > -} > > > - > > > get_subvid() > > { > > > _btrfs_get_subvolid $SCRATCH_MNT subv > > > @@ -198,7 +188,7 @@ prepare() > > > { > > > _scratch_mkfs >> $seqres.full > > > _scratch_mount > > > - enable_quota "s" > > > + $BTRFS_UTIL_PROG quota enable --simple $SCRATCH_MNT > > > $BTRFS_UTIL_PROG subvolume create $subv >> $seqres.full > > > local subvid=$(get_subvid) > > > set_subvol_limit $subvid $limit > > > @@ -421,7 +411,7 @@ enable_mature() > > > # Sync before enabling squotas to reliably *not* count the writes > > > # we did before enabling. > > > sync > > > - enable_quota "s" > > > + $BTRFS_UTIL_PROG quota enable --simple $SCRATCH_MNT > > > > This change doesn't seem related to patch 2/2. I think we can review > > these 2 patches separately. Don't worry. What I mean is that as long as one of these two patches gets an Ack first, even if the other is still being questioned, we can merge it first. After all, they are independent of each other. > > > > For this patch, I'm fine with or without it. If the btrfs list prefers > > to have it, or move it to be a common helper, I'm good with that. Sure. > > > > Thanks, > > Zorro > > > > I pulled this into a separate patch based on Filipe's feedback on the > old version I failed to get in. Agreed it doesn't need to be a series, > just felt related enough to send in a batch, due to the context. > > Thanks for taking a look, > Boris > > > > set_subvol_limit $subvid $limit > > > _scratch_cycle_mount > > > usage=$(get_subvol_usage $subvid) > > > -- > > > 2.54.0 > > > > > > > ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 1/2] btrfs: inline enable_quota helper in test 301 2026-05-13 0:43 ` [PATCH 1/2] btrfs: inline enable_quota helper in test 301 Boris Burkov 2026-05-14 3:34 ` Zorro Lang @ 2026-05-19 6:18 ` Anand Jain 1 sibling, 0 replies; 10+ messages in thread From: Anand Jain @ 2026-05-19 6:18 UTC (permalink / raw) To: Boris Burkov, linux-btrfs, kernel-team, fstests looks good. Reviewed-by: Anand Jain <asj@kernel.org> Thanks. ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 2/2] btrfs: test qgroup deletion races with squota writes 2026-05-13 0:43 [PATCH 0/2] squota delete fstests Boris Burkov 2026-05-13 0:43 ` [PATCH 1/2] btrfs: inline enable_quota helper in test 301 Boris Burkov @ 2026-05-13 0:43 ` Boris Burkov 2026-05-18 9:18 ` Anand Jain 1 sibling, 1 reply; 10+ messages in thread From: Boris Burkov @ 2026-05-13 0:43 UTC (permalink / raw) To: linux-btrfs, kernel-team, fstests When using simple quotas, an extent's EXTENT_OWNER_REF can long outlive the subvolume that created it, since the extent can pick up additional references that keep it alive after the owning subvolume is deleted. Several lifecycle bugs around the owning qgroup arise from this: 1. Freeing an extent whose owner qgroup is gone: must not cause an underflow nor a transaction abort. 2. Creating an extent in the same transaction that the owner subvolume is deleted: the qgroup may already be gone by the time the squota delta is recorded, leaving behind an EXTENT_OWNER_REF that points at a qgroup that never accumulated a usage delta. Manually re-creating that qgroup and then freeing the extent would underflow it. 3. Destroying a live subvolume's qgroup while delayed refs for newly allocated tree blocks have not yet flushed: rfer/excl read as 0, so the destroy looked safe, but the alloc delta was silently lost. 4. Destroying the qgroup of a live subvolume whose data predates 'btrfs quota enable --simple': pre-existing extents are not accounted (generation < qgroup_enable_gen), so rfer/excl stay 0 permanently and a usage-only check happily destroys a qgroup belonging to a still-mounted, still-writeable subvolume. Add four cases covering these scenarios. On a fixed kernel all four 'qgroup destroy' (and the re-create in case 2) operations are refused because the subvol check rejects the request; on an unfixed kernel they would succeed and leave the filesystem with broken accounting or trigger a transaction abort. Signed-off-by: Boris Burkov <boris@bur.io> --- tests/btrfs/348 | 136 ++++++++++++++++++++++++++++++++++++++++++++ tests/btrfs/348.out | 10 ++++ 2 files changed, 146 insertions(+) create mode 100755 tests/btrfs/348 create mode 100644 tests/btrfs/348.out diff --git a/tests/btrfs/348 b/tests/btrfs/348 new file mode 100755 index 00000000..ab816301 --- /dev/null +++ b/tests/btrfs/348 @@ -0,0 +1,136 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2024 Meta Platforms, Inc. All Rights Reserved. +# +# FS QA Test 348 +# +# Test various race conditions between qgroup deletion and squota writes +# +. ./common/preamble +_begin_fstest auto qgroup subvol clone + +# Import common functions. +. ./common/reflink + +# real QA test starts here + +_require_scratch_reflink +_require_cp_reflink +_require_scratch_enable_simple_quota + +_fixed_by_kernel_commit XXXXXXXXXXXX \ + "btrfs: check for subvolume before deleting squota qgroup" +_fixed_by_kernel_commit 0c309d66dacd \ + "btrfs: forbid creating subvol qgroups" + +subv1=$SCRATCH_MNT/subv1 +subv2=$SCRATCH_MNT/subv2 + +prepare() +{ + _scratch_mkfs >> $seqres.full + _scratch_mount + $BTRFS_UTIL_PROG quota enable --simple $SCRATCH_MNT + $BTRFS_UTIL_PROG subvolume create $subv1 >> $seqres.full + $BTRFS_UTIL_PROG subvolume create $subv2 >> $seqres.full + $XFS_IO_PROG -fc "pwrite -q 0 128K" $subv1/f + _cp_reflink $subv1/f $subv2/f +} + +# An extent can long outlive its owner. Test this by deleting the owning +# subvolume, committing the transaction, then deleting the reflinked copy. +free_from_deleted_owner() +{ + echo "free from deleted owner" + prepare + local subvid1=$(_btrfs_get_subvolid $SCRATCH_MNT subv1) + + $BTRFS_UTIL_PROG filesystem sync $SCRATCH_MNT + $BTRFS_UTIL_PROG subvolume delete $subv1 >> $seqres.full + $BTRFS_UTIL_PROG qgroup destroy 0/$subvid1 $SCRATCH_MNT >> $seqres.full + $BTRFS_UTIL_PROG filesystem sync $SCRATCH_MNT + rm $subv2/f + _scratch_unmount +} + +# A race where we delete the owner in the same transaction as writing the +# extent leads to incrementing the squota usage of the missing qgroup. +# This leaves behind an owner ref with an owner id that cannot exist, so +# freeing the extent now frees from that qgroup, but there has never +# been a corresponding usage to free. +add_to_deleted_owner() +{ + echo "add to deleted owner" + prepare + local subvid1=$(_btrfs_get_subvolid $SCRATCH_MNT subv1) + + $BTRFS_UTIL_PROG filesystem sync $SCRATCH_MNT + $BTRFS_UTIL_PROG subvolume delete $subv1 >> $seqres.full + $BTRFS_UTIL_PROG qgroup destroy 0/$subvid1 $SCRATCH_MNT >> $seqres.full + $BTRFS_UTIL_PROG filesystem sync $SCRATCH_MNT + $BTRFS_UTIL_PROG qgroup create 0/$subvid1 $SCRATCH_MNT >> $seqres.full + rm $subv2/f + _scratch_unmount +} + +# Create a subvol, destroy its qgroup before delayed refs update rfer/excl. +# On an unfixed kernel the qgroup destroy succeeds (rfer == 0), silently +# losing the alloc delta. On a fixed kernel the destroy is refused because +# the ROOT_ITEM still exists. +same_txn_destroy_race() +{ + echo "same txn destroy race" + prepare + + # Commit so the next subvol create starts a fresh transaction. + $BTRFS_UTIL_PROG filesystem sync $SCRATCH_MNT + + # New subvol -- tree block alloc delayed ref is pending, rfer/excl=0. + local subv3=$SCRATCH_MNT/subv3 + $BTRFS_UTIL_PROG subvolume create $subv3 >> $seqres.full + local subvid3=$(_btrfs_get_subvolid $SCRATCH_MNT subv3) + + # rfer/excl still 0 (delayed refs not flushed). Destroy should fail + # because the ROOT_ITEM exists. + $BTRFS_UTIL_PROG qgroup destroy 0/$subvid3 $SCRATCH_MNT 2>&1 + + _scratch_unmount +} + +# Enable squotas on a filesystem that already has a subvolume with data. +# Pre-existing extents have generation < qgroup_enable_gen, so the qgroup's +# rfer/excl stay 0 permanently. On an unfixed kernel can_delete_squota_qgroup +# only checks rfer/excl, so the qgroup can be destroyed for a live subvol. +# On a fixed kernel, the subvol check prevents deletion. +pre_existing_data_destroy() +{ + echo "pre-existing data destroy" + _scratch_mkfs >> $seqres.full + _scratch_mount + + # Create subvol and write data BEFORE enabling squotas. + $BTRFS_UTIL_PROG subvolume create $subv1 >> $seqres.full + $XFS_IO_PROG -fc "pwrite -q 0 128K" $subv1/f + sync + + # Enable squotas. Pre-existing data is not accounted (gen < enable_gen). + $BTRFS_UTIL_PROG quota enable --simple $SCRATCH_MNT + $BTRFS_UTIL_PROG filesystem sync $SCRATCH_MNT + + local subvid1=$(_btrfs_get_subvolid $SCRATCH_MNT subv1) + + # Destroy the qgroup. rfer/excl = 0 because data predates squotas. + # Should fail because the ROOT_ITEM still exists. + $BTRFS_UTIL_PROG qgroup destroy 0/$subvid1 $SCRATCH_MNT 2>&1 + + _scratch_unmount +} + +free_from_deleted_owner +add_to_deleted_owner +same_txn_destroy_race +pre_existing_data_destroy + +# success, all done +status=0 +exit diff --git a/tests/btrfs/348.out b/tests/btrfs/348.out new file mode 100644 index 00000000..0a1cf11f --- /dev/null +++ b/tests/btrfs/348.out @@ -0,0 +1,10 @@ +QA output created by 348 +free from deleted owner +ERROR: unable to destroy quota group: Device or resource busy +add to deleted owner +ERROR: unable to destroy quota group: Device or resource busy +ERROR: unable to create quota group: Invalid argument +same txn destroy race +ERROR: unable to destroy quota group: Device or resource busy +pre-existing data destroy +ERROR: unable to destroy quota group: Device or resource busy -- 2.54.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH 2/2] btrfs: test qgroup deletion races with squota writes 2026-05-13 0:43 ` [PATCH 2/2] btrfs: test qgroup deletion races with squota writes Boris Burkov @ 2026-05-18 9:18 ` Anand Jain 2026-05-18 16:28 ` Boris Burkov 0 siblings, 1 reply; 10+ messages in thread From: Anand Jain @ 2026-05-18 9:18 UTC (permalink / raw) To: Boris Burkov, linux-btrfs, kernel-team, fstests It appears that transaction commit could race with the testcase. -------- SECTION -- gen FSTYP -- btrfs PLATFORM -- Linux/x86_64 vm1 7.1.0-rc3-gf5d9f327e4b2 #14 SMP PREEMPT_DYNAMIC Sun May 17 21:25:06 +08 2026 MKFS_OPTIONS -- -f /dev/vde MOUNT_OPTIONS -- -o context=system_u:object_r:root_t:s0 /dev/vde /mnt/scratch btrfs/348 10s ... - output mismatch (see /Volumes/work/ws/fstests/results//gen/btrfs/348.out.bad) --- tests/btrfs/348.out 2026-05-18 14:04:31.427635000 +0800 +++ /Volumes/work/ws/fstests/results//gen/btrfs/348.out.bad 2026-05-18 17:13:33.000000000 +0800 @@ -5,6 +5,4 @@ ERROR: unable to destroy quota group: Device or resource busy ERROR: unable to create quota group: Invalid argument same txn destroy race -ERROR: unable to destroy quota group: Device or resource busy pre-existing data destroy -ERROR: unable to destroy quota group: Device or resource busy ... (Run 'diff -u /Volumes/work/ws/fstests/tests/btrfs/348.out /Volumes/work/ws/fstests/results//gen/btrfs/348.out.bad' to see the entire di) HINT: You _MAY_ be missing kernel fix: XXXXXXXXXXXX btrfs: check for subvolume before deleting squota qgroup HINT: You _MAY_ be missing kernel fix: 0c309d66dacd btrfs: forbid creating subvol qgroups Ran: btrfs/348 Failures: btrfs/348 Failed 1 of 1 tests Thanks, Anand On 13/5/26 08:43, Boris Burkov wrote: > When using simple quotas, an extent's EXTENT_OWNER_REF can long outlive > the subvolume that created it, since the extent can pick up additional > references that keep it alive after the owning subvolume is deleted. > > Several lifecycle bugs around the owning qgroup arise from this: > > 1. Freeing an extent whose owner qgroup is gone: must not cause an > underflow nor a transaction abort. > > 2. Creating an extent in the same transaction that the owner subvolume > is deleted: the qgroup may already be gone by the time the squota > delta is recorded, leaving behind an EXTENT_OWNER_REF that points > at a qgroup that never accumulated a usage delta. Manually > re-creating that qgroup and then freeing the extent would underflow > it. > > 3. Destroying a live subvolume's qgroup while delayed refs for newly > allocated tree blocks have not yet flushed: rfer/excl read as 0, > so the destroy looked safe, but the alloc delta was silently lost. > > 4. Destroying the qgroup of a live subvolume whose data predates > 'btrfs quota enable --simple': pre-existing extents are not > accounted (generation < qgroup_enable_gen), so rfer/excl stay 0 > permanently and a usage-only check happily destroys a qgroup > belonging to a still-mounted, still-writeable subvolume. > > Add four cases covering these scenarios. On a fixed kernel all four > 'qgroup destroy' (and the re-create in case 2) operations are refused > because the subvol check rejects the request; on an unfixed kernel > they would succeed and leave the filesystem with broken accounting or > trigger a transaction abort. > > Signed-off-by: Boris Burkov <boris@bur.io> > --- > tests/btrfs/348 | 136 ++++++++++++++++++++++++++++++++++++++++++++ > tests/btrfs/348.out | 10 ++++ > 2 files changed, 146 insertions(+) > create mode 100755 tests/btrfs/348 > create mode 100644 tests/btrfs/348.out > > diff --git a/tests/btrfs/348 b/tests/btrfs/348 > new file mode 100755 > index 00000000..ab816301 > --- /dev/null > +++ b/tests/btrfs/348 > @@ -0,0 +1,136 @@ > +#! /bin/bash > +# SPDX-License-Identifier: GPL-2.0 > +# Copyright (c) 2024 Meta Platforms, Inc. All Rights Reserved. > +# > +# FS QA Test 348 > +# > +# Test various race conditions between qgroup deletion and squota writes > +# > +. ./common/preamble > +_begin_fstest auto qgroup subvol clone > + > +# Import common functions. > +. ./common/reflink > + > +# real QA test starts here > + > +_require_scratch_reflink > +_require_cp_reflink > +_require_scratch_enable_simple_quota > + > +_fixed_by_kernel_commit XXXXXXXXXXXX \ > + "btrfs: check for subvolume before deleting squota qgroup" > +_fixed_by_kernel_commit 0c309d66dacd \ > + "btrfs: forbid creating subvol qgroups" > + > +subv1=$SCRATCH_MNT/subv1 > +subv2=$SCRATCH_MNT/subv2 > + > +prepare() > +{ > + _scratch_mkfs >> $seqres.full > + _scratch_mount > + $BTRFS_UTIL_PROG quota enable --simple $SCRATCH_MNT > + $BTRFS_UTIL_PROG subvolume create $subv1 >> $seqres.full > + $BTRFS_UTIL_PROG subvolume create $subv2 >> $seqres.full > + $XFS_IO_PROG -fc "pwrite -q 0 128K" $subv1/f > + _cp_reflink $subv1/f $subv2/f > +} > + > +# An extent can long outlive its owner. Test this by deleting the owning > +# subvolume, committing the transaction, then deleting the reflinked copy. > +free_from_deleted_owner() > +{ > + echo "free from deleted owner" > + prepare > + local subvid1=$(_btrfs_get_subvolid $SCRATCH_MNT subv1) > + > + $BTRFS_UTIL_PROG filesystem sync $SCRATCH_MNT > + $BTRFS_UTIL_PROG subvolume delete $subv1 >> $seqres.full > + $BTRFS_UTIL_PROG qgroup destroy 0/$subvid1 $SCRATCH_MNT >> $seqres.full > + $BTRFS_UTIL_PROG filesystem sync $SCRATCH_MNT > + rm $subv2/f > + _scratch_unmount > +} > + > +# A race where we delete the owner in the same transaction as writing the > +# extent leads to incrementing the squota usage of the missing qgroup. > +# This leaves behind an owner ref with an owner id that cannot exist, so > +# freeing the extent now frees from that qgroup, but there has never > +# been a corresponding usage to free. > +add_to_deleted_owner() > +{ > + echo "add to deleted owner" > + prepare > + local subvid1=$(_btrfs_get_subvolid $SCRATCH_MNT subv1) > + > + $BTRFS_UTIL_PROG filesystem sync $SCRATCH_MNT > + $BTRFS_UTIL_PROG subvolume delete $subv1 >> $seqres.full > + $BTRFS_UTIL_PROG qgroup destroy 0/$subvid1 $SCRATCH_MNT >> $seqres.full > + $BTRFS_UTIL_PROG filesystem sync $SCRATCH_MNT > + $BTRFS_UTIL_PROG qgroup create 0/$subvid1 $SCRATCH_MNT >> $seqres.full > + rm $subv2/f > + _scratch_unmount > +} > + > +# Create a subvol, destroy its qgroup before delayed refs update rfer/excl. > +# On an unfixed kernel the qgroup destroy succeeds (rfer == 0), silently > +# losing the alloc delta. On a fixed kernel the destroy is refused because > +# the ROOT_ITEM still exists. > +same_txn_destroy_race() > +{ > + echo "same txn destroy race" > + prepare > + > + # Commit so the next subvol create starts a fresh transaction. > + $BTRFS_UTIL_PROG filesystem sync $SCRATCH_MNT > + > + # New subvol -- tree block alloc delayed ref is pending, rfer/excl=0. > + local subv3=$SCRATCH_MNT/subv3 > + $BTRFS_UTIL_PROG subvolume create $subv3 >> $seqres.full > + local subvid3=$(_btrfs_get_subvolid $SCRATCH_MNT subv3) > + > + # rfer/excl still 0 (delayed refs not flushed). Destroy should fail > + # because the ROOT_ITEM exists. > + $BTRFS_UTIL_PROG qgroup destroy 0/$subvid3 $SCRATCH_MNT 2>&1 > + > + _scratch_unmount > +} > + > +# Enable squotas on a filesystem that already has a subvolume with data. > +# Pre-existing extents have generation < qgroup_enable_gen, so the qgroup's > +# rfer/excl stay 0 permanently. On an unfixed kernel can_delete_squota_qgroup > +# only checks rfer/excl, so the qgroup can be destroyed for a live subvol. > +# On a fixed kernel, the subvol check prevents deletion. > +pre_existing_data_destroy() > +{ > + echo "pre-existing data destroy" > + _scratch_mkfs >> $seqres.full > + _scratch_mount > + > + # Create subvol and write data BEFORE enabling squotas. > + $BTRFS_UTIL_PROG subvolume create $subv1 >> $seqres.full > + $XFS_IO_PROG -fc "pwrite -q 0 128K" $subv1/f > + sync > + > + # Enable squotas. Pre-existing data is not accounted (gen < enable_gen). > + $BTRFS_UTIL_PROG quota enable --simple $SCRATCH_MNT > + $BTRFS_UTIL_PROG filesystem sync $SCRATCH_MNT > + > + local subvid1=$(_btrfs_get_subvolid $SCRATCH_MNT subv1) > + > + # Destroy the qgroup. rfer/excl = 0 because data predates squotas. > + # Should fail because the ROOT_ITEM still exists. > + $BTRFS_UTIL_PROG qgroup destroy 0/$subvid1 $SCRATCH_MNT 2>&1 > + > + _scratch_unmount > +} > + > +free_from_deleted_owner > +add_to_deleted_owner > +same_txn_destroy_race > +pre_existing_data_destroy > + > +# success, all done > +status=0 > +exit > diff --git a/tests/btrfs/348.out b/tests/btrfs/348.out > new file mode 100644 > index 00000000..0a1cf11f > --- /dev/null > +++ b/tests/btrfs/348.out > @@ -0,0 +1,10 @@ > +QA output created by 348 > +free from deleted owner > +ERROR: unable to destroy quota group: Device or resource busy > +add to deleted owner > +ERROR: unable to destroy quota group: Device or resource busy > +ERROR: unable to create quota group: Invalid argument > +same txn destroy race > +ERROR: unable to destroy quota group: Device or resource busy > +pre-existing data destroy > +ERROR: unable to destroy quota group: Device or resource busy ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 2/2] btrfs: test qgroup deletion races with squota writes 2026-05-18 9:18 ` Anand Jain @ 2026-05-18 16:28 ` Boris Burkov 2026-05-19 6:15 ` Anand Jain 0 siblings, 1 reply; 10+ messages in thread From: Boris Burkov @ 2026-05-18 16:28 UTC (permalink / raw) To: Anand Jain; +Cc: linux-btrfs, kernel-team, fstests On Mon, May 18, 2026 at 05:18:18PM +0800, Anand Jain wrote: > > > It appears that transaction commit could race with the testcase. > Thank you for running the test and reporting the failure, sorry for the inconvenience. > > -------- > SECTION -- gen > FSTYP -- btrfs > PLATFORM -- Linux/x86_64 vm1 7.1.0-rc3-gf5d9f327e4b2 #14 SMP What kernel branch are you running this on? I couldn't find the sha f5d9f327e4b2, but I took a look at linus's 7.1-rc3 tag and the fixes aren't there: $ git ql v7.1-rc3 fs/btrfs/qgroup.c e0a85137a882 btrfs: avoid GFP_ATOMIC allocations in qgroup free paths 9a0448847310 btrfs: unexport btrfs_qgroup_reserve_meta() 534c0adacdeb btrfs: collapse __btrfs_qgroup_reserve_meta() into btrfs_qgroup_reserve_meta_prealloc() 5adf3f32c0b3 btrfs: collapse __btrfs_qgroup_free_meta() into btrfs_qgroup_free_meta_prealloc() vs. $ git ql btrfs/for-next fs/btrfs/qgroup.c 5c3eb7a1bce0 btrfs: swallow btrfs_record_squota_delta() ENOENT 5e4ccd2a4e70 btrfs: clamp to avoid squota underflow ed2bb38054d9 btrfs: fix squota accounting during enable generation 404bf5a24f2e btrfs: check for subvolume before deleting squota qgroup 452a1a22e98b btrfs: add missing unlikely to if branches leading to a DEBUG_WARN() e0a85137a882 btrfs: avoid GFP_ATOMIC allocations in qgroup free paths 9a0448847310 btrfs: unexport btrfs_qgroup_reserve_meta() I believe that: 404bf5a24f2e btrfs: check for subvolume before deleting squota qgroup is the critical one, as mentioned in the test's "You _MAY_ be missing kernel fix" section. Do you have that patch? If not, I think that is the most likely explanation. Thanks, Boris > PREEMPT_DYNAMIC Sun May 17 21:25:06 +08 2026 > MKFS_OPTIONS -- -f /dev/vde > MOUNT_OPTIONS -- -o context=system_u:object_r:root_t:s0 /dev/vde > /mnt/scratch > > btrfs/348 10s ... - output mismatch (see > /Volumes/work/ws/fstests/results//gen/btrfs/348.out.bad) > --- tests/btrfs/348.out 2026-05-18 14:04:31.427635000 +0800 > +++ /Volumes/work/ws/fstests/results//gen/btrfs/348.out.bad > 2026-05-18 17:13:33.000000000 +0800 > @@ -5,6 +5,4 @@ > ERROR: unable to destroy quota group: Device or resource busy > ERROR: unable to create quota group: Invalid argument > same txn destroy race > -ERROR: unable to destroy quota group: Device or resource busy > pre-existing data destroy > -ERROR: unable to destroy quota group: Device or resource busy > ... > (Run 'diff -u /Volumes/work/ws/fstests/tests/btrfs/348.out > /Volumes/work/ws/fstests/results//gen/btrfs/348.out.bad' to see the > entire di) > > HINT: You _MAY_ be missing kernel fix: > XXXXXXXXXXXX btrfs: check for subvolume before deleting squota qgroup > > HINT: You _MAY_ be missing kernel fix: > 0c309d66dacd btrfs: forbid creating subvol qgroups > > Ran: btrfs/348 > Failures: btrfs/348 > Failed 1 of 1 tests > > > Thanks, Anand > > > > > > > > On 13/5/26 08:43, Boris Burkov wrote: > > When using simple quotas, an extent's EXTENT_OWNER_REF can long outlive > > the subvolume that created it, since the extent can pick up additional > > references that keep it alive after the owning subvolume is deleted. > > > > Several lifecycle bugs around the owning qgroup arise from this: > > > > 1. Freeing an extent whose owner qgroup is gone: must not cause an > > underflow nor a transaction abort. > > > > 2. Creating an extent in the same transaction that the owner subvolume > > is deleted: the qgroup may already be gone by the time the squota > > delta is recorded, leaving behind an EXTENT_OWNER_REF that points > > at a qgroup that never accumulated a usage delta. Manually > > re-creating that qgroup and then freeing the extent would underflow > > it. > > > > 3. Destroying a live subvolume's qgroup while delayed refs for newly > > allocated tree blocks have not yet flushed: rfer/excl read as 0, > > so the destroy looked safe, but the alloc delta was silently lost. > > > > 4. Destroying the qgroup of a live subvolume whose data predates > > 'btrfs quota enable --simple': pre-existing extents are not > > accounted (generation < qgroup_enable_gen), so rfer/excl stay 0 > > permanently and a usage-only check happily destroys a qgroup > > belonging to a still-mounted, still-writeable subvolume. > > > > Add four cases covering these scenarios. On a fixed kernel all four > > 'qgroup destroy' (and the re-create in case 2) operations are refused > > because the subvol check rejects the request; on an unfixed kernel > > they would succeed and leave the filesystem with broken accounting or > > trigger a transaction abort. > > > > Signed-off-by: Boris Burkov <boris@bur.io> > > --- > > tests/btrfs/348 | 136 ++++++++++++++++++++++++++++++++++++++++++++ > > tests/btrfs/348.out | 10 ++++ > > 2 files changed, 146 insertions(+) > > create mode 100755 tests/btrfs/348 > > create mode 100644 tests/btrfs/348.out > > > > diff --git a/tests/btrfs/348 b/tests/btrfs/348 > > new file mode 100755 > > index 00000000..ab816301 > > --- /dev/null > > +++ b/tests/btrfs/348 > > @@ -0,0 +1,136 @@ > > +#! /bin/bash > > +# SPDX-License-Identifier: GPL-2.0 > > +# Copyright (c) 2024 Meta Platforms, Inc. All Rights Reserved. > > +# > > +# FS QA Test 348 > > +# > > +# Test various race conditions between qgroup deletion and squota writes > > +# > > +. ./common/preamble > > +_begin_fstest auto qgroup subvol clone > > + > > +# Import common functions. > > +. ./common/reflink > > + > > +# real QA test starts here > > + > > +_require_scratch_reflink > > +_require_cp_reflink > > +_require_scratch_enable_simple_quota > > + > > +_fixed_by_kernel_commit XXXXXXXXXXXX \ > > + "btrfs: check for subvolume before deleting squota qgroup" > > +_fixed_by_kernel_commit 0c309d66dacd \ > > + "btrfs: forbid creating subvol qgroups" > > + > > +subv1=$SCRATCH_MNT/subv1 > > +subv2=$SCRATCH_MNT/subv2 > > + > > +prepare() > > +{ > > + _scratch_mkfs >> $seqres.full > > + _scratch_mount > > + $BTRFS_UTIL_PROG quota enable --simple $SCRATCH_MNT > > + $BTRFS_UTIL_PROG subvolume create $subv1 >> $seqres.full > > + $BTRFS_UTIL_PROG subvolume create $subv2 >> $seqres.full > > + $XFS_IO_PROG -fc "pwrite -q 0 128K" $subv1/f > > + _cp_reflink $subv1/f $subv2/f > > +} > > + > > +# An extent can long outlive its owner. Test this by deleting the owning > > +# subvolume, committing the transaction, then deleting the reflinked copy. > > +free_from_deleted_owner() > > +{ > > + echo "free from deleted owner" > > + prepare > > + local subvid1=$(_btrfs_get_subvolid $SCRATCH_MNT subv1) > > + > > + $BTRFS_UTIL_PROG filesystem sync $SCRATCH_MNT > > + $BTRFS_UTIL_PROG subvolume delete $subv1 >> $seqres.full > > + $BTRFS_UTIL_PROG qgroup destroy 0/$subvid1 $SCRATCH_MNT >> $seqres.full > > + $BTRFS_UTIL_PROG filesystem sync $SCRATCH_MNT > > + rm $subv2/f > > + _scratch_unmount > > +} > > + > > +# A race where we delete the owner in the same transaction as writing the > > +# extent leads to incrementing the squota usage of the missing qgroup. > > +# This leaves behind an owner ref with an owner id that cannot exist, so > > +# freeing the extent now frees from that qgroup, but there has never > > +# been a corresponding usage to free. > > +add_to_deleted_owner() > > +{ > > + echo "add to deleted owner" > > + prepare > > + local subvid1=$(_btrfs_get_subvolid $SCRATCH_MNT subv1) > > + > > + $BTRFS_UTIL_PROG filesystem sync $SCRATCH_MNT > > + $BTRFS_UTIL_PROG subvolume delete $subv1 >> $seqres.full > > + $BTRFS_UTIL_PROG qgroup destroy 0/$subvid1 $SCRATCH_MNT >> $seqres.full > > + $BTRFS_UTIL_PROG filesystem sync $SCRATCH_MNT > > + $BTRFS_UTIL_PROG qgroup create 0/$subvid1 $SCRATCH_MNT >> $seqres.full > > + rm $subv2/f > > + _scratch_unmount > > +} > > + > > +# Create a subvol, destroy its qgroup before delayed refs update rfer/excl. > > +# On an unfixed kernel the qgroup destroy succeeds (rfer == 0), silently > > +# losing the alloc delta. On a fixed kernel the destroy is refused because > > +# the ROOT_ITEM still exists. > > +same_txn_destroy_race() > > +{ > > + echo "same txn destroy race" > > + prepare > > + > > + # Commit so the next subvol create starts a fresh transaction. > > + $BTRFS_UTIL_PROG filesystem sync $SCRATCH_MNT > > + > > + # New subvol -- tree block alloc delayed ref is pending, rfer/excl=0. > > + local subv3=$SCRATCH_MNT/subv3 > > + $BTRFS_UTIL_PROG subvolume create $subv3 >> $seqres.full > > + local subvid3=$(_btrfs_get_subvolid $SCRATCH_MNT subv3) > > + > > + # rfer/excl still 0 (delayed refs not flushed). Destroy should fail > > + # because the ROOT_ITEM exists. > > + $BTRFS_UTIL_PROG qgroup destroy 0/$subvid3 $SCRATCH_MNT 2>&1 > > + > > + _scratch_unmount > > +} > > + > > +# Enable squotas on a filesystem that already has a subvolume with data. > > +# Pre-existing extents have generation < qgroup_enable_gen, so the qgroup's > > +# rfer/excl stay 0 permanently. On an unfixed kernel can_delete_squota_qgroup > > +# only checks rfer/excl, so the qgroup can be destroyed for a live subvol. > > +# On a fixed kernel, the subvol check prevents deletion. > > +pre_existing_data_destroy() > > +{ > > + echo "pre-existing data destroy" > > + _scratch_mkfs >> $seqres.full > > + _scratch_mount > > + > > + # Create subvol and write data BEFORE enabling squotas. > > + $BTRFS_UTIL_PROG subvolume create $subv1 >> $seqres.full > > + $XFS_IO_PROG -fc "pwrite -q 0 128K" $subv1/f > > + sync > > + > > + # Enable squotas. Pre-existing data is not accounted (gen < enable_gen). > > + $BTRFS_UTIL_PROG quota enable --simple $SCRATCH_MNT > > + $BTRFS_UTIL_PROG filesystem sync $SCRATCH_MNT > > + > > + local subvid1=$(_btrfs_get_subvolid $SCRATCH_MNT subv1) > > + > > + # Destroy the qgroup. rfer/excl = 0 because data predates squotas. > > + # Should fail because the ROOT_ITEM still exists. > > + $BTRFS_UTIL_PROG qgroup destroy 0/$subvid1 $SCRATCH_MNT 2>&1 > > + > > + _scratch_unmount > > +} > > + > > +free_from_deleted_owner > > +add_to_deleted_owner > > +same_txn_destroy_race > > +pre_existing_data_destroy > > + > > +# success, all done > > +status=0 > > +exit > > diff --git a/tests/btrfs/348.out b/tests/btrfs/348.out > > new file mode 100644 > > index 00000000..0a1cf11f > > --- /dev/null > > +++ b/tests/btrfs/348.out > > @@ -0,0 +1,10 @@ > > +QA output created by 348 > > +free from deleted owner > > +ERROR: unable to destroy quota group: Device or resource busy > > +add to deleted owner > > +ERROR: unable to destroy quota group: Device or resource busy > > +ERROR: unable to create quota group: Invalid argument > > +same txn destroy race > > +ERROR: unable to destroy quota group: Device or resource busy > > +pre-existing data destroy > > +ERROR: unable to destroy quota group: Device or resource busy > ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 2/2] btrfs: test qgroup deletion races with squota writes 2026-05-18 16:28 ` Boris Burkov @ 2026-05-19 6:15 ` Anand Jain 0 siblings, 0 replies; 10+ messages in thread From: Anand Jain @ 2026-05-19 6:15 UTC (permalink / raw) To: Boris Burkov; +Cc: linux-btrfs, kernel-team, fstests Boris, The 7.1.0-rc3-gf5d9f327e4b2 kernel includes the for-next tree plus my latest f_fsid kernel patches. I double-checked and the required btrfs squota patches were indeed present: $ git log --oneline fs/btrfs | grep "check for subvolume before" 404bf5a24f2e btrfs: check for subvolume before deleting squota qgroup $ git log --oneline fs/btrfs | grep "forbid creating subvol qgroups" 0c309d66dacd btrfs: forbid creating subvol qgroups Possibly the issue was likely my messed-up VM image from SELinux + fanotifywait troubleshooting last evening. After a fresh restart, btrfs/348 passes: $ ./check -s gen btrfs/348 ... Ran: btrfs/348 Passed all 1 tests Sorry for the noise!. Looks good. Reviewed-by: Anand Jain <asj@kernel.org> A small nit (which can be fixed during merge). >>> diff --git a/tests/btrfs/348 b/tests/btrfs/348 >>> new file mode 100755 >>> index 00000000..ab816301 >>> --- /dev/null >>> +++ b/tests/btrfs/348 >>> @@ -0,0 +1,136 @@ >>> +#! /bin/bash >>> +# SPDX-License-Identifier: GPL-2.0 >>> +# Copyright (c) 2024 Meta Platforms, Inc. All Rights Reserved. Do you need this to be `s/2024/2026/` ? Thanks. ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-05-19 6:18 UTC | newest] Thread overview: 10+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-05-13 0:43 [PATCH 0/2] squota delete fstests Boris Burkov 2026-05-13 0:43 ` [PATCH 1/2] btrfs: inline enable_quota helper in test 301 Boris Burkov 2026-05-14 3:34 ` Zorro Lang 2026-05-15 21:15 ` Boris Burkov 2026-05-16 13:55 ` Zorro Lang 2026-05-19 6:18 ` Anand Jain 2026-05-13 0:43 ` [PATCH 2/2] btrfs: test qgroup deletion races with squota writes Boris Burkov 2026-05-18 9:18 ` Anand Jain 2026-05-18 16:28 ` Boris Burkov 2026-05-19 6:15 ` Anand Jain
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox