summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fs/btrfs/bio.c4
-rw-r--r--fs/btrfs/block-group.c2
-rw-r--r--fs/btrfs/disk-io.c53
-rw-r--r--fs/btrfs/ioctl.c8
-rw-r--r--fs/btrfs/tree-checker.c6
-rw-r--r--fs/btrfs/volumes.h5
-rw-r--r--fs/btrfs/zoned.c78
-rw-r--r--fs/btrfs/zstd.c3
8 files changed, 120 insertions, 39 deletions
diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c
index 2c6234f6182f..2e12a6d7e16f 100644
--- a/fs/btrfs/bio.c
+++ b/fs/btrfs/bio.c
@@ -624,6 +624,8 @@ static void run_one_async_done(struct btrfs_work *work, bool do_free)
/* If an error occurred we just want to clean up the bio and move on. */
if (bio->bi_status) {
+ btrfs_put_bioc(async->bioc);
+ btrfs_bio_counter_dec(async->bbio->inode->root->fs_info);
btrfs_bio_end_io(async->bbio, bio->bi_status);
return;
}
@@ -762,6 +764,7 @@ static bool btrfs_submit_chunk(struct btrfs_bio *bbio, int mirror_num)
split = btrfs_split_bio(fs_info, bbio, map_length);
if (IS_ERR(split)) {
status = errno_to_blk_status(PTR_ERR(split));
+ btrfs_put_bioc(bioc);
btrfs_bio_counter_dec(fs_info);
goto end_bbio;
}
@@ -822,6 +825,7 @@ done:
return map_length == length;
fail:
+ btrfs_put_bioc(bioc);
btrfs_bio_counter_dec(fs_info);
/*
* We have split the original bbio, now we have to end both the current
diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c
index 9a98e291769c..f923d27f1bf2 100644
--- a/fs/btrfs/block-group.c
+++ b/fs/btrfs/block-group.c
@@ -3727,7 +3727,7 @@ void btrfs_free_reserved_bytes(struct btrfs_block_group *cache, u64 num_bytes,
if (bg_ro)
space_info->bytes_readonly += num_bytes;
else if (btrfs_is_zoned(cache->fs_info))
- space_info->bytes_zone_unusable += num_bytes;
+ btrfs_space_info_update_bytes_zone_unusable(space_info, num_bytes);
space_info->bytes_reserved -= num_bytes;
space_info->max_extent_size = 0;
diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
index c628c5081d92..910087409346 100644
--- a/fs/btrfs/disk-io.c
+++ b/fs/btrfs/disk-io.c
@@ -1169,6 +1169,8 @@ static struct btrfs_root *btrfs_get_global_root(struct btrfs_fs_info *fs_info,
return btrfs_grab_root(fs_info->stripe_root);
case BTRFS_REMAP_TREE_OBJECTID:
return btrfs_grab_root(fs_info->remap_root);
+ case BTRFS_DATA_RELOC_TREE_OBJECTID:
+ return btrfs_grab_root(fs_info->data_reloc_root);
default:
return NULL;
}
@@ -1304,8 +1306,15 @@ static struct btrfs_root *btrfs_get_root_ref(struct btrfs_fs_info *fs_info,
*
* This is namely for free-space-tree and quota tree, which can change
* at runtime and should only be grabbed from fs_info.
+ *
+ * The data reloc tree is also such a tree. It is loaded at mount time
+ * by read_data_reloc_root() and never inserted into the radix tree, as
+ * radix tree keys are of type `unsigned long`, which is 32 bits wide
+ * on 32-bit platforms.
+ * Casting the objectid to `unsigned long` can therefore result in a
+ * conflict with a subvolume.
*/
- if (!btrfs_is_fstree(objectid) && objectid != BTRFS_DATA_RELOC_TREE_OBJECTID)
+ if (!btrfs_is_fstree(objectid))
return ERR_PTR(-ENOENT);
again:
root = btrfs_lookup_fs_root(fs_info, objectid);
@@ -1409,6 +1418,39 @@ struct btrfs_root *btrfs_get_new_fs_root(struct btrfs_fs_info *fs_info,
return btrfs_get_root_ref(fs_info, objectid, anon_dev, true);
}
+/* Read and initialize the data reloc tree. */
+static struct btrfs_root *read_data_reloc_root(struct btrfs_fs_info *fs_info)
+{
+ struct btrfs_key key = {
+ .objectid = BTRFS_DATA_RELOC_TREE_OBJECTID,
+ .type = BTRFS_ROOT_ITEM_KEY,
+ .offset = (u64)-1,
+ };
+ struct btrfs_root *root;
+ int ret;
+
+ root = btrfs_read_tree_root(fs_info->tree_root, &key);
+ if (IS_ERR(root))
+ return root;
+
+ if (btrfs_root_refs(&root->root_item) == 0) {
+ ret = -ENOENT;
+ goto fail;
+ }
+
+ /*
+ * This tree can share blocks with some other fs tree during
+ * relocation and we need a proper setup by btrfs_init_fs_root().
+ */
+ ret = btrfs_init_fs_root(root, 0);
+ if (ret)
+ goto fail;
+ return root;
+fail:
+ btrfs_put_root(root);
+ return ERR_PTR(ret);
+}
+
/*
* Return a root for the given objectid.
*
@@ -2275,12 +2317,7 @@ static int btrfs_read_roots(struct btrfs_fs_info *fs_info)
PTR_ERR(root));
}
} else {
- /*
- * This tree can share blocks with some other fs tree during
- * relocation and we need a proper setup by btrfs_get_fs_root().
- */
- root = btrfs_get_fs_root(tree_root->fs_info,
- BTRFS_DATA_RELOC_TREE_OBJECTID, true);
+ root = read_data_reloc_root(fs_info);
if (IS_ERR(root)) {
if (!btrfs_test_opt(fs_info, IGNOREBADROOTS)) {
location.objectid = BTRFS_DATA_RELOC_TREE_OBJECTID;
@@ -3815,8 +3852,6 @@ fail_block_groups:
btrfs_put_block_group_cache(fs_info);
fail_tree_roots:
- if (fs_info->data_reloc_root)
- btrfs_drop_and_free_fs_root(fs_info, fs_info->data_reloc_root);
free_root_pointers(fs_info, true);
invalidate_and_check_btree_folios(fs_info);
diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c
index f94aa4939a86..6ba883db107b 100644
--- a/fs/btrfs/ioctl.c
+++ b/fs/btrfs/ioctl.c
@@ -5161,8 +5161,12 @@ static int btrfs_ioctl_subvol_sync(struct btrfs_fs_info *fs_info, void __user *a
return -ENOENT;
wait_for_deletion = true;
- ASSERT(root_flags & BTRFS_ROOT_SUBVOL_DEAD, "root_flags=0x%llx",
- root_flags);
+ if (unlikely(!(root_flags & BTRFS_ROOT_SUBVOL_DEAD))) {
+ btrfs_err(fs_info,
+ "subvol %llu has refs 0 without DEAD flag",
+ args.subvolid);
+ return -EUCLEAN;
+ }
sched_ret = schedule_timeout_interruptible(HZ);
/* Early wake up or error. */
if (sched_ret != 0)
diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
index 9cd79d97b9b5..a2aa4013e082 100644
--- a/fs/btrfs/tree-checker.c
+++ b/fs/btrfs/tree-checker.c
@@ -1766,7 +1766,11 @@ static int check_extent_item(struct extent_buffer *leaf,
inline_refs += btrfs_shared_data_ref_count(leaf, sref);
break;
case BTRFS_EXTENT_OWNER_REF_KEY:
- WARN_ON(!btrfs_fs_incompat(fs_info, SIMPLE_QUOTA));
+ if (unlikely(!btrfs_fs_incompat(fs_info, SIMPLE_QUOTA))) {
+ extent_err(leaf, slot,
+ "extent owner ref without simple quota");
+ return -EUCLEAN;
+ }
break;
default:
extent_err(leaf, slot, "unknown inline ref type: %u",
diff --git a/fs/btrfs/volumes.h b/fs/btrfs/volumes.h
index 6eddda239221..1c4cfb21ad12 100644
--- a/fs/btrfs/volumes.h
+++ b/fs/btrfs/volumes.h
@@ -708,9 +708,8 @@ static inline enum btrfs_map_op btrfs_op(const struct bio *bio)
static inline unsigned long btrfs_chunk_item_size(int num_stripes)
{
- ASSERT(num_stripes);
- return sizeof(struct btrfs_chunk) +
- sizeof(struct btrfs_stripe) * (num_stripes - 1);
+ return offsetof(struct btrfs_chunk, stripe) +
+ sizeof(struct btrfs_stripe) * num_stripes;
}
/*
diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c
index 63a3aaca97b8..bdadc704c8cb 100644
--- a/fs/btrfs/zoned.c
+++ b/fs/btrfs/zoned.c
@@ -1122,11 +1122,13 @@ u64 btrfs_find_allocatable_zones(struct btrfs_device *device, u64 hole_start,
return pos;
}
-static bool btrfs_dev_set_active_zone(struct btrfs_device *device, u64 pos)
+static bool btrfs_dev_set_active_zone(struct btrfs_device *device, u64 pos, bool *new)
{
struct btrfs_zoned_device_info *zone_info = device->zone_info;
unsigned int zno = (pos >> zone_info->zone_size_shift);
+ *new = false;
+
/* We can use any number of zones */
if (zone_info->max_active_zones == 0)
return true;
@@ -1138,23 +1140,29 @@ static bool btrfs_dev_set_active_zone(struct btrfs_device *device, u64 pos)
if (test_and_set_bit(zno, zone_info->active_zones)) {
/* Someone already set the bit */
atomic_inc(&zone_info->active_zones_left);
+ } else {
+ *new = true;
}
}
return true;
}
-static void btrfs_dev_clear_active_zone(struct btrfs_device *device, u64 pos)
+static bool btrfs_dev_clear_active_zone(struct btrfs_device *device, u64 pos)
{
struct btrfs_zoned_device_info *zone_info = device->zone_info;
unsigned int zno = (pos >> zone_info->zone_size_shift);
/* We can use any number of zones */
if (zone_info->max_active_zones == 0)
- return;
+ return false;
- if (test_and_clear_bit(zno, zone_info->active_zones))
+ if (test_and_clear_bit(zno, zone_info->active_zones)) {
atomic_inc(&zone_info->active_zones_left);
+ return true;
+ }
+
+ return false;
}
int btrfs_reset_device_zone(struct btrfs_device *device, u64 physical,
@@ -1376,11 +1384,13 @@ static int btrfs_load_zone_info(struct btrfs_fs_info *fs_info, int zone_idx,
return 0;
}
+ ASSERT(!test_bit(BTRFS_FS_OPEN, &fs_info->flags));
+ up_read(&dev_replace->rwsem);
+
nofs_flag = memalloc_nofs_save();
ret = btrfs_get_dev_zone(device, info->physical, &zone);
memalloc_nofs_restore(nofs_flag);
if (ret) {
- up_read(&dev_replace->rwsem);
if (ret != -EIO && ret != -EOPNOTSUPP)
return ret;
info->alloc_offset = WP_MISSING_DEV;
@@ -1392,7 +1402,6 @@ static int btrfs_load_zone_info(struct btrfs_fs_info *fs_info, int zone_idx,
"zoned: unexpected conventional zone %llu on device %s (devid %llu)",
zone.start << SECTOR_SHIFT, rcu_dereference(device->name),
device->devid);
- up_read(&dev_replace->rwsem);
return -EIO;
}
@@ -1420,8 +1429,6 @@ static int btrfs_load_zone_info(struct btrfs_fs_info *fs_info, int zone_idx,
break;
}
- up_read(&dev_replace->rwsem);
-
return 0;
}
@@ -2407,6 +2414,7 @@ bool btrfs_zone_activate(struct btrfs_block_group *block_group)
u64 physical;
const bool is_data = (block_group->flags & BTRFS_BLOCK_GROUP_DATA);
bool ret;
+ bool new;
int i;
if (!btrfs_is_zoned(block_group->fs_info))
@@ -2460,12 +2468,12 @@ bool btrfs_zone_activate(struct btrfs_block_group *block_group)
goto out_unlock;
}
- if (!btrfs_dev_set_active_zone(device, physical)) {
+ if (!btrfs_dev_set_active_zone(device, physical, &new)) {
/* Cannot activate the zone */
ret = false;
goto out_unlock;
}
- if (!is_data)
+ if (!is_data && new)
zinfo->reserved_active_zones--;
}
@@ -2499,8 +2507,16 @@ static void wait_eb_writebacks(struct btrfs_block_group *block_group)
continue;
if (eb->start >= end)
break;
+ /*
+ * Skip if we can't get the refcount for the extent buffer,
+ * i.e. because it was already written back. Otherwise an eb
+ * disappering underneath us can cause a use-after-free.
+ */
+ if (!refcount_inc_not_zero(&eb->refs))
+ continue;
rcu_read_unlock();
wait_on_extent_buffer_writeback(eb);
+ free_extent_buffer(eb);
rcu_read_lock();
}
rcu_read_unlock();
@@ -2512,6 +2528,7 @@ static int call_zone_finish(struct btrfs_block_group *block_group,
struct btrfs_device *device = stripe->dev;
const u64 physical = stripe->physical;
struct btrfs_zoned_device_info *zinfo = device->zone_info;
+ bool cleared;
int ret;
if (!device->bdev)
@@ -2533,9 +2550,9 @@ static int call_zone_finish(struct btrfs_block_group *block_group,
return ret;
}
- if (!(block_group->flags & BTRFS_BLOCK_GROUP_DATA))
+ cleared = btrfs_dev_clear_active_zone(device, physical);
+ if (!(block_group->flags & BTRFS_BLOCK_GROUP_DATA) && cleared)
zinfo->reserved_active_zones++;
- btrfs_dev_clear_active_zone(device, physical);
return 0;
}
@@ -2583,9 +2600,10 @@ static int do_zone_finish(struct btrfs_block_group *block_group, bool fully_writ
/* Ensure all writes in this block group finish */
btrfs_wait_block_group_reservations(block_group);
- /* No need to wait for NOCOW writers. Zoned mode does not allow that */
- btrfs_wait_ordered_roots(fs_info, U64_MAX, block_group);
- /* Wait for extent buffers to be written. */
+ /*
+ * No need to wait for NOCOW writers, as zoned mode does not
+ * allow that, only wait for extent buffers to be written.
+ */
if (is_metadata)
wait_eb_writebacks(block_group);
@@ -3114,6 +3132,8 @@ int btrfs_reset_unused_block_groups(struct btrfs_space_info *space_info, u64 num
{
struct btrfs_fs_info *fs_info = space_info->fs_info;
const sector_t zone_size_sectors = fs_info->zone_size >> SECTOR_SHIFT;
+ LIST_HEAD(retry_list);
+ int ret = 0;
if (!btrfs_is_zoned(fs_info))
return 0;
@@ -3155,11 +3175,10 @@ int btrfs_reset_unused_block_groups(struct btrfs_space_info *space_info, u64 num
}
if (!found) {
spin_unlock(&fs_info->unused_bgs_lock);
- return 0;
+ goto out;
}
list_del_init(&bg->bg_list);
- btrfs_put_block_group(bg);
spin_unlock(&fs_info->unused_bgs_lock);
/*
@@ -3173,7 +3192,6 @@ int btrfs_reset_unused_block_groups(struct btrfs_space_info *space_info, u64 num
for (int i = 0; i < map->num_stripes; i++) {
struct btrfs_io_stripe *stripe = &map->stripes[i];
unsigned int nofs_flags;
- int ret;
nofs_flags = memalloc_nofs_save();
ret = blkdev_zone_mgmt(stripe->dev->bdev, REQ_OP_ZONE_RESET,
@@ -3183,7 +3201,7 @@ int btrfs_reset_unused_block_groups(struct btrfs_space_info *space_info, u64 num
if (ret) {
up_read(&fs_info->dev_replace.rwsem);
- return ret;
+ goto requeue;
}
}
up_read(&fs_info->dev_replace.rwsem);
@@ -3194,7 +3212,7 @@ int btrfs_reset_unused_block_groups(struct btrfs_space_info *space_info, u64 num
if (bg->ro) {
spin_unlock(&bg->lock);
spin_unlock(&space_info->lock);
- continue;
+ goto requeue;
}
reclaimed = bg->alloc_offset;
@@ -3218,17 +3236,33 @@ int btrfs_reset_unused_block_groups(struct btrfs_space_info *space_info, u64 num
ASSERT(reclaimed == bg->zone_capacity,
"reclaimed=%llu bg->zone_capacity=%llu", reclaimed, bg->zone_capacity);
bg->free_space_ctl->free_space += reclaimed;
- space_info->bytes_zone_unusable -= reclaimed;
+ btrfs_space_info_update_bytes_zone_unusable(space_info, -reclaimed);
spin_unlock(&bg->lock);
btrfs_return_free_space(space_info, reclaimed);
spin_unlock(&space_info->lock);
+ btrfs_put_block_group(bg);
+
if (num_bytes <= reclaimed)
break;
num_bytes -= reclaimed;
+ continue;
+
+requeue:
+ list_add_tail(&bg->bg_list, &retry_list);
+
+ if (ret)
+ goto out;
}
- return 0;
+out:
+ if (!list_empty(&retry_list)) {
+ spin_lock(&fs_info->unused_bgs_lock);
+ list_splice_tail(&retry_list, &fs_info->unused_bgs);
+ spin_unlock(&fs_info->unused_bgs_lock);
+ }
+
+ return ret;
}
void btrfs_show_zoned_stats(struct btrfs_fs_info *fs_info, struct seq_file *seq)
diff --git a/fs/btrfs/zstd.c b/fs/btrfs/zstd.c
index cc92d0b1b948..9b7c7f43527f 100644
--- a/fs/btrfs/zstd.c
+++ b/fs/btrfs/zstd.c
@@ -347,7 +347,8 @@ void zstd_put_workspace(struct btrfs_fs_info *fs_info, struct list_head *ws)
/* A node is only taken off the lru if we are the corresponding level */
if (clip_level(workspace->req_level) == workspace->level) {
/* Hide a max level workspace from reclaim */
- if (list_empty(&zwsm->idle_ws[ZSTD_BTRFS_MAX_LEVEL - 1])) {
+ if (workspace->level == ZSTD_BTRFS_MAX_LEVEL - 1 &&
+ list_empty(&zwsm->idle_ws[ZSTD_BTRFS_MAX_LEVEL - 1])) {
INIT_LIST_HEAD(&workspace->lru_list);
} else {
workspace->last_used = jiffies;