diff options
| -rw-r--r-- | fs/btrfs/bio.c | 4 | ||||
| -rw-r--r-- | fs/btrfs/block-group.c | 2 | ||||
| -rw-r--r-- | fs/btrfs/disk-io.c | 53 | ||||
| -rw-r--r-- | fs/btrfs/ioctl.c | 8 | ||||
| -rw-r--r-- | fs/btrfs/tree-checker.c | 6 | ||||
| -rw-r--r-- | fs/btrfs/volumes.h | 5 | ||||
| -rw-r--r-- | fs/btrfs/zoned.c | 78 | ||||
| -rw-r--r-- | fs/btrfs/zstd.c | 3 |
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; |
