Skip to content

Commit 7a8c4e5

Browse files
committed
btrfs-progs: offline filesystem resize feature
This patch introduces the ability to resize a btrfs filesystem while it is not mounted via a new `--offline` flag. Currently only increasing the size of the filesystem is supported, though I believe it would be possible to implement shrinking the filesystem to the end of the last device extent. This is a more general, and hopefully more useful, solution to the problem I was trying to solve with the ("btrfs-progs: add slack space for mkfs --shrink") patch. This patch should enable users to resize a filesystem without the higher capabilities needed for mounting a filesystem. Signed-off-by: Leo Martins <[email protected]> --- Changelog: v1->v2: - use chunk root instead of fs root - fix offline resize error handling - fix variable declarations to not have newlines - fix parameter list to have more important arguments first
1 parent 5378bb9 commit 7a8c4e5

File tree

1 file changed

+248
-1
lines changed

1 file changed

+248
-1
lines changed

cmds/filesystem.c

Lines changed: 248 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,12 @@
3838
#include "kernel-lib/sizes.h"
3939
#include "kernel-lib/list_sort.h"
4040
#include "kernel-lib/overflow.h"
41+
#include "kernel-shared/accessors.h"
4142
#include "kernel-shared/ctree.h"
4243
#include "kernel-shared/compression.h"
4344
#include "kernel-shared/volumes.h"
4445
#include "kernel-shared/disk-io.h"
46+
#include "kernel-shared/transaction.h"
4547
#include "common/defs.h"
4648
#include "common/internal.h"
4749
#include "common/messages.h"
@@ -1287,9 +1289,241 @@ static const char * const cmd_filesystem_resize_usage[] = {
12871289
"[kK] means KiB, which denotes 1KiB = 1024B, 1MiB = 1024KiB, etc.",
12881290
"",
12891291
OPTLINE("--enqueue", "wait if there's another exclusive operation running, otherwise continue"),
1292+
OPTLINE("--offline", "resize an offline filesystem, only increases are allowed"),
12901293
NULL
12911294
};
12921295

1296+
static int check_offline_resize_args(const char *path, const char *amount,
1297+
const struct btrfs_fs_info *fs_info,
1298+
struct btrfs_device **device_ret,
1299+
u64 *new_size_ret)
1300+
{
1301+
int ret = 0;
1302+
char amount_dup[BTRFS_VOL_NAME_MAX];
1303+
struct btrfs_device *device = NULL;
1304+
struct btrfs_device *mindev = NULL;
1305+
bool dev_found = false;
1306+
u64 devid = 1;
1307+
u64 mindevid = (u64)-1;
1308+
char *devstr = NULL;
1309+
struct stat stat_buf;
1310+
char *sizestr = NULL;
1311+
u64 new_size = 0, old_size = 0, diff = 0, device_size = 0;
1312+
int mod = 0;
1313+
1314+
if (check_mounted(path)) {
1315+
error("%s must not be mounted to use --offline", path);
1316+
return 1;
1317+
}
1318+
1319+
if (!fs_info->fs_devices->num_devices) {
1320+
error("no devices found");
1321+
return 1;
1322+
}
1323+
1324+
ret = snprintf(amount_dup, BTRFS_VOL_NAME_MAX, "%s", amount);
1325+
if (strlen(amount) != ret) {
1326+
error("newsize argument is too long");
1327+
return 1;
1328+
}
1329+
1330+
if (strcmp(amount, "cancel") == 0) {
1331+
error("cannot cancel offline resize since the operation is synchronous");
1332+
return 1;
1333+
}
1334+
1335+
/* Parse device id. */
1336+
sizestr = amount_dup;
1337+
devstr = strchr(sizestr, ':');
1338+
if (devstr) {
1339+
sizestr = devstr + 1;
1340+
*devstr = 0;
1341+
devstr = amount_dup;
1342+
1343+
errno = 0;
1344+
devid = strtoull(devstr, NULL, 10);
1345+
1346+
if (errno) {
1347+
error("failed to parse devid %s: %m", devstr);
1348+
return 1;
1349+
}
1350+
}
1351+
1352+
/* Find device matching device id */
1353+
list_for_each_entry(device, &fs_info->fs_devices->devices, dev_list) {
1354+
if (device->devid < mindevid) {
1355+
mindevid = device->devid;
1356+
mindev = device;
1357+
}
1358+
if (device->devid == devid) {
1359+
dev_found = true;
1360+
break;
1361+
}
1362+
}
1363+
1364+
if (devstr && !dev_found) {
1365+
/* Devid specified but not found. */
1366+
error("cannot find devid: %lld", devid);
1367+
return 1;
1368+
} else if (!devstr && devid == 1 && !dev_found) {
1369+
/*
1370+
* No device specified, assuming implicit 1 but it does not
1371+
* exist. Use minimum device as fallback.
1372+
*/
1373+
warning("no devid specified means devid 1 which does not exist, using\n"
1374+
"\t lowest devid %llu as a fallback",
1375+
mindevid);
1376+
devid = mindevid;
1377+
device = mindev;
1378+
}
1379+
if (!device) {
1380+
error("unable to find device");
1381+
return 1;
1382+
}
1383+
*device_ret = device;
1384+
old_size = device->total_bytes;
1385+
1386+
if (strcmp(sizestr, "max") == 0) {
1387+
if (path_is_block_device(device->name)) {
1388+
ret = device_get_partition_size(device->name, &new_size);
1389+
if (ret)
1390+
new_size = 0;
1391+
} else if (path_is_reg_file(device->name)) {
1392+
ret = stat(device->name, &stat_buf);
1393+
if (ret) {
1394+
new_size = 0;
1395+
} else {
1396+
new_size = stat_buf.st_size;
1397+
}
1398+
}
1399+
1400+
if (new_size == 0) {
1401+
error("unable to get size for device: %s",
1402+
device->name);
1403+
return 1;
1404+
}
1405+
} else {
1406+
if (sizestr[0] == '-') {
1407+
error("offline resize does not support shrinking");
1408+
return 1;
1409+
} else if (sizestr[0] == '+') {
1410+
mod = 1;
1411+
sizestr++;
1412+
}
1413+
ret = parse_u64_with_suffix(sizestr, &diff);
1414+
if (ret < 0) {
1415+
error("failed to parse size %s", sizestr);
1416+
return 1;
1417+
}
1418+
1419+
/* For target sizes without +/- sign prefix (e.g. 1:150g) */
1420+
if (mod == 0) {
1421+
new_size = diff;
1422+
} else if (mod > 0) {
1423+
if (diff > ULLONG_MAX - old_size) {
1424+
error("increasing %s is out of range",
1425+
pretty_size_mode(diff, UNITS_DEFAULT));
1426+
return 1;
1427+
}
1428+
new_size = old_size + diff;
1429+
}
1430+
}
1431+
new_size = round_down(new_size, fs_info->sectorsize);
1432+
if (new_size < old_size) {
1433+
error("offline resize does not support shrinking");
1434+
return 1;
1435+
}
1436+
*new_size_ret = new_size;
1437+
1438+
ret = device_get_partition_size(device->name, &device_size);
1439+
if (ret) {
1440+
error("unable to get size for device: %s", device->name);
1441+
return -1;
1442+
}
1443+
if (path_is_block_device(device->name) && new_size > device_size) {
1444+
error("unable to resize '%s': not enough free space",
1445+
device->name);
1446+
return 1;
1447+
}
1448+
1449+
if (new_size < 256 * SZ_1M)
1450+
warning("the new size %lld (%s) is < 256MiB, this may be rejected by kernel",
1451+
new_size, pretty_size_mode(new_size, UNITS_DEFAULT));
1452+
1453+
pr_verbose(LOG_DEFAULT, "Resize device id %lld from %s to %s\n", devid,
1454+
pretty_size_mode(old_size, UNITS_DEFAULT),
1455+
pretty_size_mode(new_size, UNITS_DEFAULT));
1456+
return 0;
1457+
}
1458+
1459+
static int offline_resize(const char *path, const char *amount)
1460+
{
1461+
int ret = 0;
1462+
struct btrfs_root *root;
1463+
struct btrfs_fs_info *fs_info;
1464+
struct btrfs_device *device;
1465+
struct btrfs_super_block *super;
1466+
struct btrfs_trans_handle *trans;
1467+
u64 new_size;
1468+
u64 old_total;
1469+
u64 diff;
1470+
char dev_name[BTRFS_VOL_NAME_MAX];
1471+
1472+
root = open_ctree(path, 0, OPEN_CTREE_WRITES | OPEN_CTREE_CHUNK_ROOT_ONLY);
1473+
if (!root) {
1474+
error("could not open file at %s\n"
1475+
"offline resize works on a file containing a btrfs image.",
1476+
path);
1477+
return 1;
1478+
}
1479+
fs_info = root->fs_info;
1480+
super = fs_info->super_copy;
1481+
1482+
ret = check_offline_resize_args(path, amount, fs_info, &device,
1483+
&new_size);
1484+
if (ret) {
1485+
ret = 1;
1486+
goto close;
1487+
}
1488+
1489+
ret = snprintf(dev_name, BTRFS_VOL_NAME_MAX, "%s", device->name);
1490+
if (strlen(device->name) != ret) {
1491+
error("device name too long %s", device->name);
1492+
ret = 1;
1493+
goto close;
1494+
}
1495+
ret = 0;
1496+
1497+
trans = btrfs_start_transaction(root, 1);
1498+
if (IS_ERR(trans)) {
1499+
ret = PTR_ERR(trans);
1500+
errno = -ret;
1501+
error_msg(ERROR_MSG_START_TRANS, "%m");
1502+
return ret;
1503+
}
1504+
old_total = btrfs_super_total_bytes(super);
1505+
diff = round_down(new_size - device->total_bytes, fs_info->sectorsize);
1506+
btrfs_set_super_total_bytes(super, round_down(old_total + diff,
1507+
fs_info->sectorsize));
1508+
device->total_bytes = new_size;
1509+
ret = btrfs_update_device(trans, device);
1510+
if (ret) {
1511+
btrfs_abort_transaction(trans, ret);
1512+
goto close;
1513+
}
1514+
ret |= btrfs_commit_transaction(trans, root);
1515+
close:
1516+
ret |= close_ctree(root);
1517+
if (ret)
1518+
return ret;
1519+
1520+
if (path_is_reg_file(dev_name))
1521+
ret = truncate(dev_name, new_size);
1522+
if (ret)
1523+
error("failed to truncate %s", device->name);
1524+
return ret;
1525+
}
1526+
12931527
static int check_resize_args(const char *amount, const char *path, u64 *devid_ret)
12941528
{
12951529
struct btrfs_ioctl_fs_info_args fi_args;
@@ -1449,6 +1683,7 @@ static int cmd_filesystem_resize(const struct cmd_struct *cmd,
14491683
u64 devid;
14501684
int ret;
14511685
bool enqueue = false;
1686+
bool offline = false;
14521687
bool cancel = false;
14531688

14541689
/*
@@ -1458,6 +1693,8 @@ static int cmd_filesystem_resize(const struct cmd_struct *cmd,
14581693
for (optind = 1; optind < argc; optind++) {
14591694
if (strcmp(argv[optind], "--enqueue") == 0) {
14601695
enqueue = true;
1696+
} else if (strcmp(argv[optind], "--offline") == 0) {
1697+
offline = true;
14611698
} else if (strcmp(argv[optind], "--") == 0) {
14621699
/* Separator: options -- non-options */
14631700
} else if (strncmp(argv[optind], "--", 2) == 0) {
@@ -1472,6 +1709,12 @@ static int cmd_filesystem_resize(const struct cmd_struct *cmd,
14721709
if (check_argc_exact(argc - optind, 2))
14731710
return 1;
14741711

1712+
if (offline && enqueue) {
1713+
error("--enqueue is not compatible with --offline\n"
1714+
"since offline resizing is synchronous");
1715+
return 1;
1716+
}
1717+
14751718
amount = argv[optind];
14761719
path = argv[optind + 1];
14771720

@@ -1481,6 +1724,9 @@ static int cmd_filesystem_resize(const struct cmd_struct *cmd,
14811724
return 1;
14821725
}
14831726

1727+
if (offline)
1728+
return offline_resize(path, amount);
1729+
14841730
cancel = (strcmp("cancel", amount) == 0);
14851731

14861732
fd = btrfs_open_dir(path);
@@ -1490,7 +1736,8 @@ static int cmd_filesystem_resize(const struct cmd_struct *cmd,
14901736
error(
14911737
"resize works on mounted filesystems and accepts only\n"
14921738
"directories as argument. Passing file containing a btrfs image\n"
1493-
"would resize the underlying filesystem instead of the image.\n");
1739+
"would resize the underlying filesystem instead of the image.\n"
1740+
"To resize a file containing a btrfs image please use the --offline flag.\n");
14941741
}
14951742
return 1;
14961743
}

0 commit comments

Comments
 (0)