Skip to content

Commit 5136c0b

Browse files
loemrawkdave
authored andcommitted
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 <[email protected]>
1 parent 7919d8f commit 5136c0b

File tree

2 files changed

+165
-8
lines changed

2 files changed

+165
-8
lines changed

cmds/filesystem.c

Lines changed: 164 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include "kernel-shared/compression.h"
4343
#include "kernel-shared/volumes.h"
4444
#include "kernel-shared/disk-io.h"
45+
#include "kernel-shared/transaction.h"
4546
#include "common/defs.h"
4647
#include "common/internal.h"
4748
#include "common/messages.h"
@@ -1287,6 +1288,7 @@ static const char * const cmd_filesystem_resize_usage[] = {
12871288
"[kK] means KiB, which denotes 1KiB = 1024B, 1MiB = 1024KiB, etc.",
12881289
"",
12891290
OPTLINE("--enqueue", "wait if there's another exclusive operation running, otherwise continue"),
1291+
OPTLINE("--offline", "resize an offline/unmounted filesystem (limitations: shrinking and multi-device not supported)"),
12901292
NULL
12911293
};
12921294

@@ -1358,6 +1360,154 @@ static bool parse_resize_args(const char *amount, struct resize_args *ret)
13581360
return true;
13591361
}
13601362

1363+
static bool check_offline_resize_args(const char *path, const char *amount,
1364+
const struct btrfs_fs_info *fs_info,
1365+
struct btrfs_device **device_ret,
1366+
u64 *new_size_ret)
1367+
{
1368+
struct btrfs_device *device = NULL;
1369+
struct resize_args args;
1370+
struct stat stat_buf;
1371+
u64 new_size = 0, old_size = 0, device_size = 0;
1372+
1373+
if (check_mounted(path)) {
1374+
error("%s must not be mounted to use --offline", path);
1375+
return false;
1376+
}
1377+
1378+
if (fs_info->fs_devices->num_devices > 1) {
1379+
error("multi-device not supported with --offline");
1380+
return false;
1381+
}
1382+
device = list_first_entry_or_null(&fs_info->fs_devices->devices,
1383+
struct btrfs_device, dev_list);
1384+
if (!device) {
1385+
error("no device found");
1386+
return false;
1387+
}
1388+
*device_ret = device;
1389+
old_size = device->total_bytes;
1390+
1391+
fstat(device->fd, &stat_buf);
1392+
if (device_get_partition_size_fd_stat(device->fd, &stat_buf, &device_size))
1393+
device_size = 0;
1394+
if (!device_size) {
1395+
error("unable to get size at path %s", device->name);
1396+
return false;
1397+
}
1398+
1399+
if (!parse_resize_args(amount, &args))
1400+
return false;
1401+
1402+
if (args.is_cancel) {
1403+
error("can not cancel --offline resize");
1404+
return false;
1405+
}
1406+
if (args.specified_dev_id && args.devid != device->devid) {
1407+
error("invalid device id %llu", args.devid);
1408+
return false;
1409+
}
1410+
if (args.is_max) {
1411+
new_size = device_size;
1412+
} else {
1413+
if (args.mod == 0) {
1414+
new_size = args.size;
1415+
} else if (args.mod < 0) {
1416+
error("offline resize does not support shrinking");
1417+
return false;
1418+
} else {
1419+
if (args.size > ULLONG_MAX - old_size) {
1420+
error("increasing (%llu) %s is out of range",
1421+
args.size, pretty_size_mode(args.size, UNITS_DEFAULT));
1422+
return false;
1423+
}
1424+
new_size = old_size + args.size;
1425+
}
1426+
}
1427+
new_size = round_down(new_size, fs_info->sectorsize);
1428+
if (new_size < old_size) {
1429+
error("offline resize does not support shrinking");
1430+
return false;
1431+
}
1432+
*new_size_ret = new_size;
1433+
1434+
if (path_is_block_device(device->name) && new_size > device_size) {
1435+
error("unable to resize '%s': not enough free space", device->name);
1436+
return false;
1437+
}
1438+
1439+
if (new_size < 256 * SZ_1M)
1440+
warning("the new size %lld (%s) is < 256MiB, this may be rejected by kernel",
1441+
new_size, pretty_size_mode(new_size, UNITS_DEFAULT));
1442+
1443+
pr_verbose(LOG_DEFAULT, "Resize from %s to %s\n",
1444+
pretty_size_mode(old_size, UNITS_DEFAULT),
1445+
pretty_size_mode(new_size, UNITS_DEFAULT));
1446+
return true;
1447+
}
1448+
1449+
static bool offline_resize(const char *path, const char *amount)
1450+
{
1451+
int ret = false;
1452+
struct btrfs_root *root;
1453+
struct btrfs_fs_info *fs_info;
1454+
struct btrfs_device *device;
1455+
struct btrfs_super_block *super;
1456+
struct btrfs_trans_handle *trans;
1457+
u64 new_size;
1458+
u64 old_total;
1459+
u64 diff;
1460+
1461+
root = open_ctree(path, 0, OPEN_CTREE_WRITES | OPEN_CTREE_CHUNK_ROOT_ONLY);
1462+
if (!root)
1463+
return false;
1464+
fs_info = root->fs_info;
1465+
super = fs_info->super_copy;
1466+
1467+
if (!check_offline_resize_args(path, amount, fs_info, &device, &new_size)) {
1468+
ret = false;
1469+
goto close;
1470+
}
1471+
1472+
trans = btrfs_start_transaction(root, 1);
1473+
if (IS_ERR(trans)) {
1474+
errno = -PTR_ERR(trans);
1475+
error_msg(ERROR_MSG_START_TRANS, "%m");
1476+
ret = false;
1477+
goto close;
1478+
}
1479+
1480+
old_total = btrfs_super_total_bytes(super);
1481+
diff = round_down(new_size - device->total_bytes, fs_info->sectorsize);
1482+
btrfs_set_super_total_bytes(super, round_down(old_total + diff, fs_info->sectorsize));
1483+
device->total_bytes = new_size;
1484+
ret = btrfs_update_device(trans, device);
1485+
if (ret) {
1486+
btrfs_abort_transaction(trans, ret);
1487+
ret = false;
1488+
goto close;
1489+
}
1490+
1491+
if (path_is_reg_file(device->name)) {
1492+
if (truncate(device->name, new_size)) {
1493+
error("unable to truncate %s to new size %llu", device->name, new_size);
1494+
btrfs_abort_transaction(trans, ret);
1495+
ret = false;
1496+
goto close;
1497+
}
1498+
}
1499+
1500+
if (btrfs_commit_transaction(trans, root)) {
1501+
ret = false;
1502+
goto close;
1503+
}
1504+
1505+
ret = true;
1506+
close:
1507+
close_ctree(root);
1508+
return ret;
1509+
}
1510+
13611511
static int check_resize_args(const char *amount, const char *path, u64 *devid_ret)
13621512
{
13631513
struct btrfs_ioctl_fs_info_args fi_args;
@@ -1484,6 +1634,7 @@ static int cmd_filesystem_resize(const struct cmd_struct *cmd,
14841634
u64 devid;
14851635
int ret;
14861636
bool enqueue = false;
1637+
bool offline = false;
14871638
bool cancel = false;
14881639

14891640
/*
@@ -1493,6 +1644,8 @@ static int cmd_filesystem_resize(const struct cmd_struct *cmd,
14931644
for (optind = 1; optind < argc; optind++) {
14941645
if (strcmp(argv[optind], "--enqueue") == 0) {
14951646
enqueue = true;
1647+
} else if (strcmp(argv[optind], "--offline") == 0) {
1648+
offline = true;
14961649
} else if (strcmp(argv[optind], "--") == 0) {
14971650
/* Separator: options -- non-options */
14981651
} else if (strncmp(argv[optind], "--", 2) == 0) {
@@ -1507,6 +1660,11 @@ static int cmd_filesystem_resize(const struct cmd_struct *cmd,
15071660
if (check_argc_exact(argc - optind, 2))
15081661
return 1;
15091662

1663+
if (offline && enqueue) {
1664+
error("--enqueue is not compatible with --offline");
1665+
return 1;
1666+
}
1667+
15101668
amount = argv[optind];
15111669
path = argv[optind + 1];
15121670

@@ -1516,17 +1674,16 @@ static int cmd_filesystem_resize(const struct cmd_struct *cmd,
15161674
return 1;
15171675
}
15181676

1677+
if (offline)
1678+
return !offline_resize(path, amount);
1679+
15191680
cancel = (strcmp("cancel", amount) == 0);
15201681

15211682
fd = btrfs_open_dir(path);
15221683
if (fd < 0) {
1523-
/* The path is a directory */
1524-
if (fd == -ENOTDIR) {
1525-
error(
1526-
"resize works on mounted filesystems and accepts only\n"
1527-
"directories as argument. Passing file containing a btrfs image\n"
1528-
"would resize the underlying filesystem instead of the image.\n");
1529-
}
1684+
/* The path is not a directory. */
1685+
if (fd == -ENOTDIR)
1686+
error("to resize a file containing a BTRFS image use the --offline flag");
15301687
return 1;
15311688
}
15321689

tests/cli-tests/003-fi-resize-args/test.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ done
5858
run_mustfail_stdout "should fail for image" \
5959
"$TOP/btrfs" filesystem resize 1:-128M "$TEST_DEV" |
6060
_log_stdout |
61-
grep -q "ERROR: resize works on mounted filesystems and accepts only" ||
61+
grep -q "ERROR: to resize a file containing a BTRFS image use the --offline flag" ||
6262
_fail "no expected error message in the output 2"
6363

6464
run_check_umount_test_dev

0 commit comments

Comments
 (0)