summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-05-23 16:54:48 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-05-23 16:54:48 -0700
commit400544639d2a11a9c1e276a912a9dff8fe4107dc (patch)
tree587237ffcddde9bdde29ec58d8e182d12046fb7e
parentf83ef5bca211a1bdc1ea661c11543712f96a6cea (diff)
parentf13342e15deafb7538a7a8577ed5f4c33c56f64e (diff)
downloadlinux-next-400544639d2a11a9c1e276a912a9dff8fe4107dc.tar.gz
linux-next-400544639d2a11a9c1e276a912a9dff8fe4107dc.zip
Merge tag 'for-7.1-rc4-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux
Pull btrfs fixes from David Sterba: "A batch of fixes to simple quotas: - add conditional rescheduling point not dependent on the lock during inode iterations to avoid delays with PREEMPT_NONE enabled - fix subvolume deletion so it does not break the squota invariants - properly handle enabling squota, tracking extents in the initial transaction - catch and warn about underflows, clamp to zero to avoid further problems And one fix to inode size handling: - fix handling of preallocated extents beyond i_size when not using the no-holes feature" * tag 'for-7.1-rc4-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux: btrfs: swallow btrfs_record_squota_delta() ENOENT btrfs: clamp to avoid squota underflow btrfs: fix squota accounting during enable generation btrfs: check for subvolume before deleting squota qgroup btrfs: always drop root->inodes lock before cond_resched() btrfs: mark file extent range dirty after converting prealloc extents
-rw-r--r--fs/btrfs/extent_map.c4
-rw-r--r--fs/btrfs/file.c11
-rw-r--r--fs/btrfs/fs.h1
-rw-r--r--fs/btrfs/inode.c4
-rw-r--r--fs/btrfs/qgroup.c101
5 files changed, 83 insertions, 38 deletions
diff --git a/fs/btrfs/extent_map.c b/fs/btrfs/extent_map.c
index 9284c0a81bef..6b79bff241f2 100644
--- a/fs/btrfs/extent_map.c
+++ b/fs/btrfs/extent_map.c
@@ -1246,7 +1246,9 @@ static struct btrfs_inode *find_first_inode_to_shrink(struct btrfs_root *root,
write_unlock(&tree->lock);
next:
from = btrfs_ino(inode) + 1;
- cond_resched_lock(&root->inodes.xa_lock);
+ xa_unlock(&root->inodes);
+ cond_resched();
+ xa_lock(&root->inodes);
}
xa_unlock(&root->inodes);
diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index cf1cb5c4db75..8c171ed07008 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -633,7 +633,7 @@ again:
trans->transid);
btrfs_set_file_extent_num_bytes(leaf, fi,
end - other_start);
- return 0;
+ goto mark_dirty;
}
}
@@ -661,7 +661,7 @@ again:
other_end - start);
btrfs_set_file_extent_offset(leaf, fi,
start - orig_offset);
- return 0;
+ goto mark_dirty;
}
}
@@ -788,7 +788,12 @@ again:
}
}
- return 0;
+mark_dirty:
+ ret = btrfs_inode_set_file_extent_range(inode, start, end - start);
+ if (ret)
+ btrfs_abort_transaction(trans, ret);
+
+ return ret;
}
/*
diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h
index a4758d94b32e..a8aa086a4df8 100644
--- a/fs/btrfs/fs.h
+++ b/fs/btrfs/fs.h
@@ -155,6 +155,7 @@ enum {
BTRFS_FS_LOG_RECOVERING,
BTRFS_FS_OPEN,
BTRFS_FS_QUOTA_ENABLED,
+ BTRFS_FS_SQUOTA_ENABLING,
BTRFS_FS_UPDATE_UUID_TREE_GEN,
BTRFS_FS_CREATING_FREE_SPACE_TREE,
BTRFS_FS_BTREE_ERR,
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 75136a172710..1ca1cbdf25bc 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -10699,7 +10699,9 @@ struct btrfs_inode *btrfs_find_first_inode(struct btrfs_root *root, u64 min_ino)
break;
from = btrfs_ino(inode) + 1;
- cond_resched_lock(&root->inodes.xa_lock);
+ xa_unlock(&root->inodes);
+ cond_resched();
+ xa_lock(&root->inodes);
}
xa_unlock(&root->inodes);
diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c
index cdf736d3a4e5..6838faceb6d5 100644
--- a/fs/btrfs/qgroup.c
+++ b/fs/btrfs/qgroup.c
@@ -1107,7 +1107,13 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info,
if (simple) {
fs_info->qgroup_flags |= BTRFS_QGROUP_STATUS_FLAG_SIMPLE_MODE;
btrfs_set_fs_incompat(fs_info, SIMPLE_QUOTA);
- btrfs_set_qgroup_status_enable_gen(leaf, ptr, trans->transid);
+ /*
+ * Set the enable generation to the next transaction, as we cannot
+ * ensure that extents written during this transaction will see any
+ * state we have set here. So we should treat all extents of the
+ * transaction as coming in before squotas was enabled.
+ */
+ btrfs_set_qgroup_status_enable_gen(leaf, ptr, trans->transid + 1);
} else {
fs_info->qgroup_flags |= BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT;
}
@@ -1210,7 +1216,15 @@ out_add_root:
goto out_free_path;
}
- fs_info->qgroup_enable_gen = trans->transid;
+ /*
+ * Set fs_info->qgroup_enable_gen and BTRFS_FS_SQUOTA_ENABLING
+ * under the transaction handle. We want to ensure that all extents in
+ * the next transaction definitely see them.
+ */
+ if (simple) {
+ fs_info->qgroup_enable_gen = trans->transid + 1;
+ set_bit(BTRFS_FS_SQUOTA_ENABLING, &fs_info->flags);
+ }
mutex_unlock(&fs_info->qgroup_ioctl_lock);
/*
@@ -1224,9 +1238,15 @@ out_add_root:
*/
ret = btrfs_commit_transaction(trans);
trans = NULL;
+
mutex_lock(&fs_info->qgroup_ioctl_lock);
- if (ret)
+ if (ret) {
+ if (simple) {
+ clear_bit(BTRFS_FS_SQUOTA_ENABLING, &fs_info->flags);
+ fs_info->qgroup_enable_gen = 0;
+ }
goto out_free_path;
+ }
/*
* Set quota enabled flag after committing the transaction, to avoid
@@ -1236,6 +1256,8 @@ out_add_root:
spin_lock(&fs_info->qgroup_lock);
fs_info->quota_root = quota_root;
set_bit(BTRFS_FS_QUOTA_ENABLED, &fs_info->flags);
+ if (simple)
+ clear_bit(BTRFS_FS_SQUOTA_ENABLING, &fs_info->flags);
spin_unlock(&fs_info->qgroup_lock);
/* Skip rescan for simple qgroups. */
@@ -1715,32 +1737,24 @@ out:
return ret;
}
-static bool can_delete_parent_qgroup(struct btrfs_qgroup *qgroup)
-
+static bool can_delete_parent_qgroup(struct btrfs_fs_info *fs_info, struct btrfs_qgroup *qgroup)
{
ASSERT(btrfs_qgroup_level(qgroup->qgroupid));
+ if (btrfs_qgroup_mode(fs_info) == BTRFS_QGROUP_MODE_SIMPLE)
+ squota_check_parent_usage(fs_info, qgroup);
return list_empty(&qgroup->members);
}
/*
- * Return true if we can delete the squota qgroup and false otherwise.
- *
- * Rules for whether we can delete:
- *
- * A subvolume qgroup can be removed iff the subvolume is fully deleted, which
- * is iff there is 0 usage in the qgroup.
- *
- * A higher level qgroup can be removed iff it has no members.
- * Note: We audit its usage to warn on inconsitencies without blocking deletion.
+ * Because a shared extent can outlive its owning subvolume, we cannot delete a
+ * subvol squota qgroup until all of the extents it owns are gone, even if the
+ * subvolume itself has been deleted.
*/
-static bool can_delete_squota_qgroup(struct btrfs_fs_info *fs_info, struct btrfs_qgroup *qgroup)
+static bool can_delete_squota_subvol_qgroup(struct btrfs_fs_info *fs_info,
+ struct btrfs_qgroup *qgroup)
{
ASSERT(btrfs_qgroup_mode(fs_info) == BTRFS_QGROUP_MODE_SIMPLE);
-
- if (btrfs_qgroup_level(qgroup->qgroupid) > 0) {
- squota_check_parent_usage(fs_info, qgroup);
- return can_delete_parent_qgroup(qgroup);
- }
+ ASSERT(btrfs_qgroup_level(qgroup->qgroupid) == 0);
return !(qgroup->rfer || qgroup->excl || qgroup->rfer_cmpr || qgroup->excl_cmpr);
}
@@ -1754,14 +1768,11 @@ static int can_delete_qgroup(struct btrfs_fs_info *fs_info, struct btrfs_qgroup
{
struct btrfs_key key;
BTRFS_PATH_AUTO_FREE(path);
-
- /* Since squotas cannot be inconsistent, they have special rules for deletion. */
- if (btrfs_qgroup_mode(fs_info) == BTRFS_QGROUP_MODE_SIMPLE)
- return can_delete_squota_qgroup(fs_info, qgroup);
+ int ret;
/* For higher level qgroup, we can only delete it if it has no child. */
if (btrfs_qgroup_level(qgroup->qgroupid))
- return can_delete_parent_qgroup(qgroup);
+ return can_delete_parent_qgroup(fs_info, qgroup);
/*
* For level-0 qgroups, we can only delete it if it has no subvolume
@@ -1777,10 +1788,21 @@ static int can_delete_qgroup(struct btrfs_fs_info *fs_info, struct btrfs_qgroup
return -ENOMEM;
/*
- * The @ret from btrfs_find_root() exactly matches our definition for
- * the return value, thus can be returned directly.
+ * Any subvol qgroup, regardless of mode, cannot be deleted if the
+ * subvol still exists.
*/
- return btrfs_find_root(fs_info->tree_root, &key, path, NULL, NULL);
+ ret = btrfs_find_root(fs_info->tree_root, &key, path, NULL, NULL);
+ /*
+ * btrfs_find_root returns <0 on error, 0 if found, and >0 if not,
+ * so the "found" and "error" cases match our desired return values.
+ */
+ if (ret <= 0)
+ return ret;
+
+ /* Squotas require additional checks, even if the subvol is deleted. */
+ if (btrfs_qgroup_mode(fs_info) == BTRFS_QGROUP_MODE_SIMPLE)
+ return can_delete_squota_subvol_qgroup(fs_info, qgroup);
+ return 1;
}
int btrfs_remove_qgroup(struct btrfs_trans_handle *trans, u64 qgroupid)
@@ -4922,7 +4944,8 @@ int btrfs_record_squota_delta(struct btrfs_fs_info *fs_info,
u64 num_bytes = delta->num_bytes;
const int sign = (delta->is_inc ? 1 : -1);
- if (btrfs_qgroup_mode(fs_info) != BTRFS_QGROUP_MODE_SIMPLE)
+ if (btrfs_qgroup_mode(fs_info) != BTRFS_QGROUP_MODE_SIMPLE &&
+ !test_bit(BTRFS_FS_SQUOTA_ENABLING, &fs_info->flags))
return 0;
if (!btrfs_is_fstree(root))
@@ -4934,8 +4957,9 @@ int btrfs_record_squota_delta(struct btrfs_fs_info *fs_info,
spin_lock(&fs_info->qgroup_lock);
qgroup = find_qgroup_rb(fs_info, root);
- if (!qgroup) {
- ret = -ENOENT;
+ if (WARN_ON_ONCE(!qgroup)) {
+ btrfs_warn(fs_info, "squota failed to find qgroup for root %llu", root);
+ ret = 0;
goto out;
}
@@ -4944,8 +4968,19 @@ int btrfs_record_squota_delta(struct btrfs_fs_info *fs_info,
list_for_each_entry(qg, &qgroup_list, iterator) {
struct btrfs_qgroup_list *glist;
- qg->excl += num_bytes * sign;
- qg->rfer += num_bytes * sign;
+ ASSERT(qg->excl == qg->rfer);
+ if (WARN_ON_ONCE(sign < 0 && qg->excl < num_bytes)) {
+ btrfs_warn(fs_info,
+ "squota underflow qg %hu/%llu excl %llu num_bytes %llu",
+ btrfs_qgroup_level(qg->qgroupid),
+ btrfs_qgroup_subvolid(qg->qgroupid),
+ qg->excl, num_bytes);
+ qg->excl = 0;
+ qg->rfer = 0;
+ } else {
+ qg->excl += num_bytes * sign;
+ qg->rfer += num_bytes * sign;
+ }
qgroup_dirty(fs_info, qg);
list_for_each_entry(glist, &qg->groups, next_group)