Skip to content

Commit c6e70ee

Browse files
committed
btrfs-progs: offline filesystem resize feature
Add support for resizing btrfs filesystems while unmounted using a new --offline flag. This enables filesystem resizing without requiring mount privileges or dealing with mounted filesystem constraints. The offline resize functionality currently only supports increasing the size of 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 a safer 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. Signed-off-by: Leo Martins <[email protected]>
1 parent 944909d commit c6e70ee

File tree

2 files changed

+167
-9
lines changed

2 files changed

+167
-9
lines changed

cmds/filesystem.c

Lines changed: 166 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
#include "kerncompat.h"
18+
#include "kernel-shared/transaction.h"
1819
#include <sys/ioctl.h>
1920
#include <sys/stat.h>
2021
#include <linux/version.h>
@@ -1287,14 +1288,15 @@ 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 filesystem, shrinking and multi-device not supported"),
12901292
NULL
12911293
};
12921294

12931295
struct resize_args {
12941296
bool is_cancel;
12951297
bool specified_dev_id;
1296-
u64 devid;
12971298
bool is_max;
1299+
u64 devid;
12981300
int mod;
12991301
u64 size;
13001302
};
@@ -1356,6 +1358,154 @@ static bool parse_resize_args(const char *amount, struct resize_args *ret) {
13561358
return true;
13571359
}
13581360

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

14871638
/*
@@ -1491,6 +1642,8 @@ static int cmd_filesystem_resize(const struct cmd_struct *cmd,
14911642
for (optind = 1; optind < argc; optind++) {
14921643
if (strcmp(argv[optind], "--enqueue") == 0) {
14931644
enqueue = true;
1645+
} else if (strcmp(argv[optind], "--offline") == 0) {
1646+
offline = true;
14941647
} else if (strcmp(argv[optind], "--") == 0) {
14951648
/* Separator: options -- non-options */
14961649
} else if (strncmp(argv[optind], "--", 2) == 0) {
@@ -1505,6 +1658,12 @@ static int cmd_filesystem_resize(const struct cmd_struct *cmd,
15051658
if (check_argc_exact(argc - optind, 2))
15061659
return 1;
15071660

1661+
if (offline && enqueue) {
1662+
error("--enqueue is not compatible with --offline\n"
1663+
"since offline resizing is synchronous");
1664+
return 1;
1665+
}
1666+
15081667
amount = argv[optind];
15091668
path = argv[optind + 1];
15101669

@@ -1514,17 +1673,16 @@ static int cmd_filesystem_resize(const struct cmd_struct *cmd,
15141673
return 1;
15151674
}
15161675

1676+
if (offline)
1677+
return !offline_resize(path, amount);
1678+
15171679
cancel = (strcmp("cancel", amount) == 0);
15181680

15191681
fd = btrfs_open_dir(path);
15201682
if (fd < 0) {
1521-
/* The path is a directory */
1522-
if (fd == -ENOTDIR) {
1523-
error(
1524-
"resize works on mounted filesystems and accepts only\n"
1525-
"directories as argument. Passing file containing a btrfs image\n"
1526-
"would resize the underlying filesystem instead of the image.\n");
1527-
}
1683+
/* The path is not a directory */
1684+
if (fd == -ENOTDIR)
1685+
error("to resize a file containing a BTRFS image use the --offline flag");
15281686
return 1;
15291687
}
15301688

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)