From 2e01e0c2d55b39e3b1c87a396e062223ae3274a4 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Sat, 2 Aug 2025 09:56:19 +0930 Subject: [PATCH 01/53] btrfs-progs: fsck-tests: make the warning check more specific for 057 [BUG] On older kernels without the fix ae4477f93756 ("btrfs: update superblock's device bytes_used when dropping chunk"), the test case 057 will result super block device item to mismatch with the chunk one. Normally this is not a big deal, but newer btrfs-progs will check for such mismatch. Although newer btrfs-progs won't report this as an error, the test case fsck/057 will manually check for any warnings, and fail the test case: ====== RUN CHECK /home/runner/work/btrfs-progs/btrfs-progs/btrfs check /dev/loop1 [1/8] checking log skipped (none written) [2/8] checking root items [3/8] checking extents WARNING: device 2's bytes_used was 503316480 in tree but 570425344 in superblock [4/8] checking free space tree [5/8] checking fs roots [6/8] checking only csums items (without verifying data) [7/8] checking root refs [8/8] checking quota groups skipped (not enabled on this FS) Opening filesystem to check... Checking filesystem on /dev/loop1 UUID: efd3e3b9-2fac-4a6f-b378-34694dc2d446 found 147456 bytes used, no error found total csum bytes: 0 total tree bytes: 147456 total fs tree bytes: 32768 total extent tree bytes: 16384 btree space waste bytes: 139992 file data blocks allocated: 0 referenced 0 That WARNING line will fail the test case, even if we didn't error out. [CAUSE] The test case itself is a test case for btrfs-progs commit 0dc8b8b6a414 ("btrfs-progs: check: fix wrong total bytes check for seed device"), which will report minor warning like the following: [2/7] checking extents WARNING: minor unaligned/mismatch device size detected WARNING: recommended to use 'btrfs rescue fix-device-size' to fix it But in this particular case, 057 should only check for the related wanrings, not something caused by the kernel. [FIX] Make the warning check in fsck/057 more specific, instead of "WARNING" use "fix-device-size" as the keyword. This is an unfortunate workaround for the CI kernels, which doesn't have fast enough backport of upstream kernel fixes. Reviewed-by: Boris Burkov Signed-off-by: Qu Wenruo --- tests/fsck-tests/057-seed-false-alerts/test.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/fsck-tests/057-seed-false-alerts/test.sh b/tests/fsck-tests/057-seed-false-alerts/test.sh index edf160fab3..2d491e1fba 100755 --- a/tests/fsck-tests/057-seed-false-alerts/test.sh +++ b/tests/fsck-tests/057-seed-false-alerts/test.sh @@ -35,13 +35,13 @@ sprouted_output=$(_mktemp btrfs-progs-sprouted-check-stdout.XXXXXX) run_check_stdout $SUDO_HELPER "$TOP/btrfs" check "$dev1" >> "$seed_output" run_check_stdout $SUDO_HELPER "$TOP/btrfs" check "$dev2" >> "$sprouted_output" -# There should be no warning for both seed and sprouted fs -if grep -q "WARNING" "$seed_output"; then +# There should be no warning about the device size for both seed and sprouted fs +if grep -q "fix-device-size" "$seed_output"; then cleanup_loopdevs rm -f -- "$seed_output" "$sprouted_output" _fail "false alerts detected for seed fs" fi -if grep -q "WARNING" "$sprouted_output"; then +if grep -q "fix-device-size" "$sprouted_output"; then cleanup_loopdevs rm -f -- "$seed_output" "$sprouted_output" _fail "false alerts detected for sprouted fs" From 69eb0710a60b1fd0e2031e90573efa0596715d74 Mon Sep 17 00:00:00 2001 From: Mark Harmstone Date: Sat, 2 Aug 2025 09:56:20 +0930 Subject: [PATCH 02/53] btrfs-progs: check that device byte values in superblock match those in chunk root The superblock of each device contains a copy of the corresponding struct btrfs_dev_item that lives in the chunk root. Add a check that the total_bytes and bytes_used values of these two copies match. Signed-off-by: Mark Harmstone [ Change the error to warning ] Reviewed-by: Boris Burkov Signed-off-by: Qu Wenruo --- check/main.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/check/main.c b/check/main.c index 258c97b2e8..0f0600edae 100644 --- a/check/main.c +++ b/check/main.c @@ -8645,6 +8645,7 @@ static int check_device_used(struct device_record *dev_rec, if (opt_check_repair) { ret = repair_dev_item_bytes_used(gfs_info, dev_rec->devid, total_byte); + dev_rec->byte_used = total_byte; } return ret; } else { @@ -8702,6 +8703,28 @@ static bool is_super_size_valid(void) return true; } +static int check_super_dev_item(struct device_record *dev_rec) +{ + struct btrfs_dev_item *super_di = &gfs_info->super_copy->dev_item; + int ret = 0; + + if (btrfs_stack_device_total_bytes(super_di) != dev_rec->total_byte) { + warning("device %llu's total_bytes was %llu in tree but %llu in superblock", + dev_rec->devid, dev_rec->total_byte, + btrfs_stack_device_total_bytes(super_di)); + ret = 1; + } + + if (btrfs_stack_device_bytes_used(super_di) != dev_rec->byte_used) { + warning("device %llu's bytes_used was %llu in tree but %llu in superblock", + dev_rec->devid, dev_rec->byte_used, + btrfs_stack_device_bytes_used(super_di)); + ret = 1; + } + + return ret; +} + /* check btrfs_dev_item -> btrfs_dev_extent */ static int check_devices(struct rb_root *dev_cache, struct device_extent_tree *dev_extent_cache) @@ -8723,6 +8746,18 @@ static int check_devices(struct rb_root *dev_cache, gfs_info->sectorsize); if (dev_rec->bad_block_dev_size && !ret) ret = 1; + + if (dev_rec->devid == gfs_info->super_copy->dev_item.devid) { + /* + * This dev item mismatch between super and chunk tree + * is not a criticl problem, and CI kernels do not receive + * needed backport so they will cause mismatch during RW mounts. + * + * SO here we didn't record the mismatch as an error. + */ + check_super_dev_item(dev_rec); + } + dev_node = rb_next(dev_node); } list_for_each_entry(dext_rec, &dev_extent_cache->no_device_orphans, From 7919d8f945ef494f4236f35c4b728abebce3e394 Mon Sep 17 00:00:00 2001 From: Leo Martins Date: Thu, 28 Aug 2025 15:05:02 -0700 Subject: [PATCH 03/53] btrfs-progs: refactor resize argument checking Extract resize argument parsing logic from check_resize_args() into a separate parse_resize_args() function and makes the parsing logic reusable for future features like offline resize. Signed-off-by: Leo Martins --- cmds/filesystem.c | 147 ++++++++++++++++++++++++++++------------------ 1 file changed, 91 insertions(+), 56 deletions(-) diff --git a/cmds/filesystem.c b/cmds/filesystem.c index 40c4ee9d92..d5f0b5ace8 100644 --- a/cmds/filesystem.c +++ b/cmds/filesystem.c @@ -1290,19 +1290,84 @@ static const char * const cmd_filesystem_resize_usage[] = { NULL }; +struct resize_args { + bool is_cancel; + bool specified_dev_id; + bool is_max; + u64 devid; + int mod; + u64 size; +}; + +static bool parse_resize_args(const char *amount, struct resize_args *ret) +{ + char amount_dup[BTRFS_VOL_NAME_MAX]; + char *devstr; + char *sizestr; + + ret->is_cancel = false; + if (strcmp("cancel", amount) == 0) { + ret->is_cancel = true; + return true; + } + + if (strlen(amount) >= BTRFS_VOL_NAME_MAX) { + error("newsize argument is too long %zu >= %d", strlen(amount), + BTRFS_VOL_NAME_MAX); + return false; + } + strncpy(amount_dup, amount, BTRFS_VOL_NAME_MAX); + + sizestr = amount_dup; + devstr = strchr(sizestr, ':'); + ret->specified_dev_id = false; + if (devstr) { + sizestr = devstr + 1; + *devstr = 0; + devstr = amount_dup; + + errno = 0; + ret->specified_dev_id = true; + ret->devid = strtoull(devstr, NULL, 10); + + if (errno) { + error("failed to parse devid %s: %m", devstr); + return false; + } + } + + if (strcmp(sizestr, "max") == 0) { + ret->is_max = true; + } else { + ret->is_max = false; + + ret->mod = 0; + if (sizestr[0] == '-') { + ret->mod = -1; + sizestr++; + } else if (sizestr[0] == '+') { + ret->mod = 1; + sizestr++; + } + if (parse_u64_with_suffix(sizestr, &ret->size) < 0) { + error("failed to parse size %s", sizestr); + return false; + } + } + + return true; +} + static int check_resize_args(const char *amount, const char *path, u64 *devid_ret) { struct btrfs_ioctl_fs_info_args fi_args; struct btrfs_ioctl_dev_info_args *di_args = NULL; + struct resize_args args; int ret, i, dev_idx = -1; - u64 devid = 1; u64 mindev = (u64)-1; int mindev_idx = 0; const char *res_str = NULL; - char *devstr = NULL, *sizestr = NULL; - u64 new_size = 0, old_size = 0, diff = 0; - int mod = 0; - char amount_dup[BTRFS_VOL_NAME_MAX]; + u64 new_size = 0, old_size = 0; *devid_ret = (u64)-1; ret = get_fs_info(path, &fi_args, &di_args); @@ -1317,37 +1382,20 @@ static int check_resize_args(const char *amount, const char *path, u64 *devid_re goto out; } - ret = snprintf(amount_dup, BTRFS_VOL_NAME_MAX, "%s", amount); - if (strlen(amount) != ret) { - error("newsize argument is too long"); + if (!parse_resize_args(amount, &args)) { ret = 1; goto out; } - ret = 0; /* Cancel does not need to determine the device number. */ - if (strcmp(amount, "cancel") == 0) { + if (args.is_cancel) { /* Different format, print and exit */ pr_verbose(LOG_DEFAULT, "Request to cancel resize\n"); goto out; } - sizestr = amount_dup; - devstr = strchr(sizestr, ':'); - if (devstr) { - sizestr = devstr + 1; - *devstr = 0; - devstr = amount_dup; - - errno = 0; - devid = strtoull(devstr, NULL, 10); - - if (errno) { - error("failed to parse devid %s: %m", devstr); - ret = 1; - goto out; - } - } + if (!args.specified_dev_id) + args.devid = 1; dev_idx = -1; for(i = 0; i < fi_args.num_devices; i++) { @@ -1355,18 +1403,18 @@ static int check_resize_args(const char *amount, const char *path, u64 *devid_re mindev = di_args[i].devid; mindev_idx = i; } - if (di_args[i].devid == devid) { + if (di_args[i].devid == args.devid) { dev_idx = i; break; } } - if (devstr && dev_idx < 0) { + if (args.specified_dev_id && dev_idx < 0) { /* Devid specified but not found. */ - error("cannot find devid: %lld", devid); + error("cannot find devid: %llu", args.devid); ret = 1; goto out; - } else if (!devstr && devid == 1 && dev_idx < 0) { + } else if (!args.specified_dev_id && dev_idx < 0) { /* * No device specified, assuming implicit 1 but it does not * exist. Use minimum device as fallback. @@ -1374,7 +1422,7 @@ static int check_resize_args(const char *amount, const char *path, u64 *devid_re warning("no devid specified means devid 1 which does not exist, using\n" "\t lowest devid %llu as a fallback", mindev); *devid_ret = mindev; - devid = mindev; + args.devid = mindev; dev_idx = mindev_idx; } else { /* @@ -1383,44 +1431,31 @@ static int check_resize_args(const char *amount, const char *path, u64 *devid_re */ } - if (strcmp(sizestr, "max") == 0) { + if (args.is_max) { res_str = "max"; } else { - if (sizestr[0] == '-') { - mod = -1; - sizestr++; - } else if (sizestr[0] == '+') { - mod = 1; - sizestr++; - } - ret = parse_u64_with_suffix(sizestr, &diff); - if (ret < 0) { - error("failed to parse size %s", sizestr); - ret = 1; - goto out; - } old_size = di_args[dev_idx].total_bytes; /* For target sizes without +/- sign prefix (e.g. 1:150g) */ - if (mod == 0) { - new_size = diff; - } else if (mod < 0) { - if (diff > old_size) { + if (args.mod == 0) { + new_size = args.size; + } else if (args.mod < 0) { + if (args.size > old_size) { error("current size is %s which is smaller than %s", pretty_size_mode(old_size, UNITS_DEFAULT), - pretty_size_mode(diff, UNITS_DEFAULT)); + pretty_size_mode(args.size, UNITS_DEFAULT)); ret = 1; goto out; } - new_size = old_size - diff; - } else if (mod > 0) { - if (diff > ULLONG_MAX - old_size) { + new_size = old_size - args.size; + } else if (args.mod > 0) { + if (args.size > ULLONG_MAX - old_size) { error("increasing %s is out of range", - pretty_size_mode(diff, UNITS_DEFAULT)); + pretty_size_mode(args.size, UNITS_DEFAULT)); ret = 1; goto out; } - new_size = old_size + diff; + new_size = old_size + args.size; } new_size = round_down(new_size, fi_args.sectorsize); res_str = pretty_size_mode(new_size, UNITS_DEFAULT); @@ -1430,7 +1465,7 @@ static int check_resize_args(const char *amount, const char *path, u64 *devid_re new_size, pretty_size_mode(new_size, UNITS_DEFAULT)); } - pr_verbose(LOG_DEFAULT, "Resize device id %lld (%s) from %s to %s\n", devid, + pr_verbose(LOG_DEFAULT, "Resize device id %llu (%s) from %s to %s\n", args.devid, di_args[dev_idx].path, pretty_size_mode(di_args[dev_idx].total_bytes, UNITS_DEFAULT), res_str); From 5136c0b94a5d9ac7b8bddbc5b64c7b741c9d9642 Mon Sep 17 00:00:00 2001 From: Leo Martins Date: Thu, 21 Aug 2025 15:59:31 -0700 Subject: [PATCH 04/53] btrfs-progs: fi resize: support offline/unmounted resize Add support for resizing btrfs filesystems while unmounted using a new option --offline, without requiring mount privileges or dealing with mounted filesystem constraints. Current limitations: - increase size - single-device filesystem It works on both block devices and regular files. For regular files it also truncates the file to the new size. This provides an alternative for users who need to resize filesystems in environments where mounting may not be possible or desirable, such as in containers or during system recovery. Pull-request: #1007 Signed-off-by: Leo Martins --- cmds/filesystem.c | 171 ++++++++++++++++++++- tests/cli-tests/003-fi-resize-args/test.sh | 2 +- 2 files changed, 165 insertions(+), 8 deletions(-) diff --git a/cmds/filesystem.c b/cmds/filesystem.c index d5f0b5ace8..1e477bc21e 100644 --- a/cmds/filesystem.c +++ b/cmds/filesystem.c @@ -42,6 +42,7 @@ #include "kernel-shared/compression.h" #include "kernel-shared/volumes.h" #include "kernel-shared/disk-io.h" +#include "kernel-shared/transaction.h" #include "common/defs.h" #include "common/internal.h" #include "common/messages.h" @@ -1287,6 +1288,7 @@ static const char * const cmd_filesystem_resize_usage[] = { "[kK] means KiB, which denotes 1KiB = 1024B, 1MiB = 1024KiB, etc.", "", OPTLINE("--enqueue", "wait if there's another exclusive operation running, otherwise continue"), + OPTLINE("--offline", "resize an offline/unmounted filesystem (limitations: shrinking and multi-device not supported)"), NULL }; @@ -1358,6 +1360,154 @@ static bool parse_resize_args(const char *amount, struct resize_args *ret) return true; } +static bool check_offline_resize_args(const char *path, const char *amount, + const struct btrfs_fs_info *fs_info, + struct btrfs_device **device_ret, + u64 *new_size_ret) +{ + struct btrfs_device *device = NULL; + struct resize_args args; + struct stat stat_buf; + u64 new_size = 0, old_size = 0, device_size = 0; + + if (check_mounted(path)) { + error("%s must not be mounted to use --offline", path); + return false; + } + + if (fs_info->fs_devices->num_devices > 1) { + error("multi-device not supported with --offline"); + return false; + } + device = list_first_entry_or_null(&fs_info->fs_devices->devices, + struct btrfs_device, dev_list); + if (!device) { + error("no device found"); + return false; + } + *device_ret = device; + old_size = device->total_bytes; + + fstat(device->fd, &stat_buf); + if (device_get_partition_size_fd_stat(device->fd, &stat_buf, &device_size)) + device_size = 0; + if (!device_size) { + error("unable to get size at path %s", device->name); + return false; + } + + if (!parse_resize_args(amount, &args)) + return false; + + if (args.is_cancel) { + error("can not cancel --offline resize"); + return false; + } + if (args.specified_dev_id && args.devid != device->devid) { + error("invalid device id %llu", args.devid); + return false; + } + if (args.is_max) { + new_size = device_size; + } else { + if (args.mod == 0) { + new_size = args.size; + } else if (args.mod < 0) { + error("offline resize does not support shrinking"); + return false; + } else { + if (args.size > ULLONG_MAX - old_size) { + error("increasing (%llu) %s is out of range", + args.size, pretty_size_mode(args.size, UNITS_DEFAULT)); + return false; + } + new_size = old_size + args.size; + } + } + new_size = round_down(new_size, fs_info->sectorsize); + if (new_size < old_size) { + error("offline resize does not support shrinking"); + return false; + } + *new_size_ret = new_size; + + if (path_is_block_device(device->name) && new_size > device_size) { + error("unable to resize '%s': not enough free space", device->name); + return false; + } + + if (new_size < 256 * SZ_1M) + warning("the new size %lld (%s) is < 256MiB, this may be rejected by kernel", + new_size, pretty_size_mode(new_size, UNITS_DEFAULT)); + + pr_verbose(LOG_DEFAULT, "Resize from %s to %s\n", + pretty_size_mode(old_size, UNITS_DEFAULT), + pretty_size_mode(new_size, UNITS_DEFAULT)); + return true; +} + +static bool offline_resize(const char *path, const char *amount) +{ + int ret = false; + struct btrfs_root *root; + struct btrfs_fs_info *fs_info; + struct btrfs_device *device; + struct btrfs_super_block *super; + struct btrfs_trans_handle *trans; + u64 new_size; + u64 old_total; + u64 diff; + + root = open_ctree(path, 0, OPEN_CTREE_WRITES | OPEN_CTREE_CHUNK_ROOT_ONLY); + if (!root) + return false; + fs_info = root->fs_info; + super = fs_info->super_copy; + + if (!check_offline_resize_args(path, amount, fs_info, &device, &new_size)) { + ret = false; + goto close; + } + + trans = btrfs_start_transaction(root, 1); + if (IS_ERR(trans)) { + errno = -PTR_ERR(trans); + error_msg(ERROR_MSG_START_TRANS, "%m"); + ret = false; + goto close; + } + + old_total = btrfs_super_total_bytes(super); + diff = round_down(new_size - device->total_bytes, fs_info->sectorsize); + btrfs_set_super_total_bytes(super, round_down(old_total + diff, fs_info->sectorsize)); + device->total_bytes = new_size; + ret = btrfs_update_device(trans, device); + if (ret) { + btrfs_abort_transaction(trans, ret); + ret = false; + goto close; + } + + if (path_is_reg_file(device->name)) { + if (truncate(device->name, new_size)) { + error("unable to truncate %s to new size %llu", device->name, new_size); + btrfs_abort_transaction(trans, ret); + ret = false; + goto close; + } + } + + if (btrfs_commit_transaction(trans, root)) { + ret = false; + goto close; + } + + ret = true; +close: + close_ctree(root); + return ret; +} + static int check_resize_args(const char *amount, const char *path, u64 *devid_ret) { struct btrfs_ioctl_fs_info_args fi_args; @@ -1484,6 +1634,7 @@ static int cmd_filesystem_resize(const struct cmd_struct *cmd, u64 devid; int ret; bool enqueue = false; + bool offline = false; bool cancel = false; /* @@ -1493,6 +1644,8 @@ static int cmd_filesystem_resize(const struct cmd_struct *cmd, for (optind = 1; optind < argc; optind++) { if (strcmp(argv[optind], "--enqueue") == 0) { enqueue = true; + } else if (strcmp(argv[optind], "--offline") == 0) { + offline = true; } else if (strcmp(argv[optind], "--") == 0) { /* Separator: options -- non-options */ } else if (strncmp(argv[optind], "--", 2) == 0) { @@ -1507,6 +1660,11 @@ static int cmd_filesystem_resize(const struct cmd_struct *cmd, if (check_argc_exact(argc - optind, 2)) return 1; + if (offline && enqueue) { + error("--enqueue is not compatible with --offline"); + return 1; + } + amount = argv[optind]; path = argv[optind + 1]; @@ -1516,17 +1674,16 @@ static int cmd_filesystem_resize(const struct cmd_struct *cmd, return 1; } + if (offline) + return !offline_resize(path, amount); + cancel = (strcmp("cancel", amount) == 0); fd = btrfs_open_dir(path); if (fd < 0) { - /* The path is a directory */ - if (fd == -ENOTDIR) { - error( - "resize works on mounted filesystems and accepts only\n" - "directories as argument. Passing file containing a btrfs image\n" - "would resize the underlying filesystem instead of the image.\n"); - } + /* The path is not a directory. */ + if (fd == -ENOTDIR) + error("to resize a file containing a BTRFS image use the --offline flag"); return 1; } diff --git a/tests/cli-tests/003-fi-resize-args/test.sh b/tests/cli-tests/003-fi-resize-args/test.sh index 66e09fa7fd..2f9d75b430 100755 --- a/tests/cli-tests/003-fi-resize-args/test.sh +++ b/tests/cli-tests/003-fi-resize-args/test.sh @@ -58,7 +58,7 @@ done run_mustfail_stdout "should fail for image" \ "$TOP/btrfs" filesystem resize 1:-128M "$TEST_DEV" | _log_stdout | - grep -q "ERROR: resize works on mounted filesystems and accepts only" || + grep -q "ERROR: to resize a file containing a BTRFS image use the --offline flag" || _fail "no expected error message in the output 2" run_check_umount_test_dev From a77a30c7ce5f8a77ac2a5fddcf31c59bd951ac9e Mon Sep 17 00:00:00 2001 From: Leo Martins Date: Thu, 28 Aug 2025 16:42:21 -0700 Subject: [PATCH 05/53] btrfs-progs: tests: add case for offline filesystem resize Add comprehensive test coverage for the new --offline resize feature. The test case covers: - Valid resize operations: incremental (+1G, 1:+1G) and absolute (2G) - Invalid operations that should fail: shrinking, multi-device, cancel - Error conditions: invalid device IDs, malformed size arguments - Option compatibility: --offline with --enqueue should be rejected - Mount state validation: offline resize should fail on mounted filesystems Each test creates temporary filesystem images, performs resize operations, and verifies the resulting filesystem size matches expectations. Error cases use run_mustfail to ensure proper failure handling. Signed-off-by: Leo Martins --- tests/misc-tests/070-offline-resize/test.sh | 168 ++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100755 tests/misc-tests/070-offline-resize/test.sh diff --git a/tests/misc-tests/070-offline-resize/test.sh b/tests/misc-tests/070-offline-resize/test.sh new file mode 100755 index 0000000000..7788c4d84d --- /dev/null +++ b/tests/misc-tests/070-offline-resize/test.sh @@ -0,0 +1,168 @@ +#!/bin/bash +# +# Test filesystem resize --offline functionality +# +# Tests various resize operations on offline: +# +# - Incremental resize (+1G, 1:+1G) +# - Absolute resize (2G) +# - Invalid operations (multi-device, invalid syntax, shrinking) + +source "$TEST_TOP/common" || exit + +check_prereq mkfs.btrfs +check_prereq btrfs + +setup_root_helper + +get_filesystem_size() +{ + local mount="$1" + + size=$(run_check_stdout $SUDO_HELPER "$TOP/btrfs" filesystem usage -m "$mount" | \ + grep "Device size:" | awk '{print $3}' | sed 's/\..*//') + echo "$size" +} + +create_backing_file() +{ + local path="$1" + local size_mb="$2" + + run_check truncate -s "${size_mb}M" "$path" +} + +# Test offline resize with incremental size (+1G) +_log "Testing offline resize +1G" +backing="$(_mktemp backing)" +create_backing_file "$backing" 1024 + +run_check "$TOP/mkfs.btrfs" -f "$backing" +run_check "$TOP/btrfs" filesystem resize --offline "+1G" "$backing" + +TEST_DEV="$backing" +run_check_mount_test_dev +actual_size=$(get_filesystem_size "$TEST_MNT") +if [ "$actual_size" != "2048" ]; then + _fail "Size mismatch: expected 2048MiB, got ${actual_size}MiB" +fi +run_check_umount_test_dev +rm -f "$backing" + +# Test offline resize with device-specific incremental size (1:+1G) +_log "Testing offline resize 1:+1G" +backing="$(_mktemp backing)" +create_backing_file "$backing" 1024 + +run_check "$TOP/mkfs.btrfs" -f "$backing" +run_check "$TOP/btrfs" filesystem resize --offline "1:+1G" "$backing" + +TEST_DEV="$backing" +run_check_mount_test_dev +actual_size=$(get_filesystem_size "$TEST_MNT") +if [ "$actual_size" != "2048" ]; then + _fail "Size mismatch: expected 2048MiB, got ${actual_size}MiB" +fi +run_check_umount_test_dev +rm -f "$backing" + +# Test offline resize with absolute size (2G) +_log "Testing offline resize 2G" +backing="$(_mktemp backing)" +create_backing_file "$backing" 1024 + +run_check "$TOP/mkfs.btrfs" -f "$backing" +run_check "$TOP/btrfs" filesystem resize --offline "2G" "$backing" + +TEST_DEV="$backing" +run_check_mount_test_dev +actual_size=$(get_filesystem_size "$TEST_MNT") +if [ "$actual_size" != "2048" ]; then + _fail "Size mismatch: expected 2048MiB, got ${actual_size}MiB" +fi +run_check_umount_test_dev +rm -f "$backing" + +# Test offline resize with invalid device id (2:+1G) +_log "Testing offline resize with invalid device id 2:+1G" +backing="$(_mktemp backing)" +create_backing_file "$backing" 1024 + +run_check "$TOP/mkfs.btrfs" -f "$backing" +run_mustfail "offline resize should fail with invalid device id" \ + "$TOP/btrfs" filesystem resize --offline "2:+1G" "$backing" +rm -f "$backing" + +# Test offline resize with shrinking (not supported) +_log "Testing offline resize with shrinking -10M" +backing="$(_mktemp backing)" +create_backing_file "$backing" 1024 + +run_check "$TOP/mkfs.btrfs" -f "$backing" +run_mustfail "offline resize should not support shrinking" \ + "$TOP/btrfs" filesystem resize --offline "-10M" "$backing" +rm -f "$backing" + +# Test offline resize with cancel (not supported) +_log "Testing offline resize with cancel" +backing="$(_mktemp backing)" +create_backing_file "$backing" 1024 + +run_check "$TOP/mkfs.btrfs" -f "$backing" +run_mustfail "offline resize should not support cancel" \ + "$TOP/btrfs" filesystem resize --offline "cancel" "$backing" +rm -f "$backing" + +# Test offline resize with invalid size format +_log "Testing offline resize with invalid size format 1:+1a" +backing="$(_mktemp backing)" +create_backing_file "$backing" 1024 + +run_check "$TOP/mkfs.btrfs" -f "$backing" +run_mustfail "offline resize should fail with invalid size format" \ + "$TOP/btrfs" filesystem resize --offline "1:+1a" "$backing" +rm -f "$backing" + +# Test offline resize on multi-device filesystem (should fail) +_log "Testing offline resize on multi-device filesystem" +backing1="$(_mktemp backing1)" +backing2="$(_mktemp backing2)" + +create_backing_file "$backing1" 1024 +create_backing_file "$backing2" 1024 + +run_check "$TOP/mkfs.btrfs" -f "$backing1" "$backing2" +run_mustfail "offline resize should fail on multi-device filesystem" \ + "$TOP/btrfs" filesystem resize --offline "+1G" "$backing1" + +rm -f "$backing1" "$backing2" + +# Test that --offline and --enqueue are incompatible +_log "Testing that --offline and --enqueue are incompatible" +backing="$(_mktemp backing)" +create_backing_file "$backing" 1024 + +run_check "$TOP/mkfs.btrfs" -f "$backing" +run_mustfail "--offline and --enqueue should be incompatible" \ + "$TOP/btrfs" filesystem resize --offline --enqueue "+1G" "$backing" + +rm -f "$backing" + +# Test offline resize on mounted filesystem (should fail) +_log "Testing offline resize on mounted filesystem" +setup_loopdevs 1 +prepare_loopdevs + +dev="${loopdevs[1]}" +run_check "$TOP/mkfs.btrfs" -f "$dev" + +TEST_DEV="$dev" +run_check_mount_test_dev + +# Get the backing file path from the loop device +backing=$(losetup -l "$dev" | tail -n1 | awk '{print $6}') +run_mustfail "offline resize should fail on mounted filesystem" \ + "$TOP/btrfs" filesystem resize --offline "+1G" "$backing" + +run_check_umount_test_dev +cleanup_loopdevs From 5ddccad451494fda8def357d8f0b909816078ca1 Mon Sep 17 00:00:00 2001 From: Leo Martins Date: Tue, 2 Sep 2025 11:10:53 -0700 Subject: [PATCH 06/53] btrfs-progs: docs: document --offline resize feature Add documentation for the new --offline. Update the warning to reflect that it is now possible to resize a btrfs image, but you need to specify the --offline flag. Highlighted that --offline flag only supports increasing the size of single device filesystems. Signed-off-by: Leo Martins --- Documentation/btrfs-filesystem.rst | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Documentation/btrfs-filesystem.rst b/Documentation/btrfs-filesystem.rst index 146d88b65b..c76b8f45c0 100644 --- a/Documentation/btrfs-filesystem.rst +++ b/Documentation/btrfs-filesystem.rst @@ -240,10 +240,8 @@ resize [options] [:][+/-][kKmMgGtTpPeE]|[:]max Resize a mounted filesystem identified by *path*. A particular device can be resized by specifying a *devid*. - .. warning:: - If *path* is a file containing a BTRFS image then resize does not work - as expected and does not resize the image. This would resize the underlying - filesystem instead. + .. note:: + To resize a file containing a BTRFS image, please use the --offline flag. The *devid* can be found in the output of :command:`btrfs filesystem show` and defaults to 1 if not specified. @@ -290,6 +288,18 @@ resize [options] [:][+/-][kKmMgGtTpPeE]|[:]max --enqueue wait if there's another exclusive operation running, otherwise continue + --offline + resize an unmounted (offline) filesystem + + .. warning:: The offline resize functionality currently + supports **only increasing** the size of **single-device** + filesystems. IOW, shrinking and multi-device filesystems are + **not supported** with this option. + + For filesystems stored in regular files, the file will be + truncated to the new size as part of the resize operation. + This flag is cannot be used together with with --enqueue since + offline resizing is synchronous. show [options] [|||