Skip to content

Commit 44e653a

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 b299793 commit 44e653a

File tree

1 file changed

+238
-1
lines changed

1 file changed

+238
-1
lines changed

cmds/filesystem.c

Lines changed: 238 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,231 @@ 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;
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+
new_size = device_get_partition_size(device->name);
1389+
} else if (path_is_reg_file(device->name)) {
1390+
stat(device->name, &stat_buf);
1391+
new_size = stat_buf.st_size;
1392+
}
1393+
1394+
if (new_size == 0) {
1395+
error("unable to get size for device: %s",
1396+
device->name);
1397+
return 1;
1398+
}
1399+
} else {
1400+
if (sizestr[0] == '-') {
1401+
error("offline resize does not support shrinking");
1402+
return 1;
1403+
} else if (sizestr[0] == '+') {
1404+
mod = 1;
1405+
sizestr++;
1406+
}
1407+
ret = parse_u64_with_suffix(sizestr, &diff);
1408+
if (ret < 0) {
1409+
error("failed to parse size %s", sizestr);
1410+
return 1;
1411+
}
1412+
1413+
/* For target sizes without +/- sign prefix (e.g. 1:150g) */
1414+
if (mod == 0) {
1415+
new_size = diff;
1416+
} else if (mod > 0) {
1417+
if (diff > ULLONG_MAX - old_size) {
1418+
error("increasing %s is out of range",
1419+
pretty_size_mode(diff, UNITS_DEFAULT));
1420+
return 1;
1421+
}
1422+
new_size = old_size + diff;
1423+
}
1424+
}
1425+
new_size = round_down(new_size, fs_info->sectorsize);
1426+
if (new_size < old_size) {
1427+
error("offline resize does not support shrinking");
1428+
return 1;
1429+
}
1430+
*new_size_ret = new_size;
1431+
1432+
if (path_is_block_device(device->name) &&
1433+
new_size > device_get_partition_size(device->name)) {
1434+
error("unable to resize '%s': not enough free space",
1435+
device->name);
1436+
return 1;
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 device id %lld from %s to %s\n", devid,
1444+
pretty_size_mode(old_size, UNITS_DEFAULT),
1445+
pretty_size_mode(new_size, UNITS_DEFAULT));
1446+
return 0;
1447+
}
1448+
1449+
static int offline_resize(const char *path, const char *amount)
1450+
{
1451+
int ret = 0;
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+
char dev_name[BTRFS_VOL_NAME_MAX];
1461+
1462+
root = open_ctree(path, 0, OPEN_CTREE_WRITES | OPEN_CTREE_CHUNK_ROOT_ONLY);
1463+
if (!root) {
1464+
error("could not open file at %s\n"
1465+
"offline resize works on a file containing a btrfs image.",
1466+
path);
1467+
return 1;
1468+
}
1469+
fs_info = root->fs_info;
1470+
super = fs_info->super_copy;
1471+
1472+
ret = check_offline_resize_args(path, amount, fs_info, &device,
1473+
&new_size);
1474+
if (ret) {
1475+
ret = 1;
1476+
goto close;
1477+
}
1478+
1479+
ret = snprintf(dev_name, BTRFS_VOL_NAME_MAX, "%s", device->name);
1480+
if (strlen(device->name) != ret) {
1481+
error("device name too long %s", device->name);
1482+
ret = 1;
1483+
goto close;
1484+
}
1485+
ret = 0;
1486+
1487+
trans = btrfs_start_transaction(root, 1);
1488+
if (IS_ERR(trans)) {
1489+
ret = PTR_ERR(trans);
1490+
errno = -ret;
1491+
error_msg(ERROR_MSG_START_TRANS, "%m");
1492+
return ret;
1493+
}
1494+
old_total = btrfs_super_total_bytes(super);
1495+
diff = round_down(new_size - device->total_bytes, fs_info->sectorsize);
1496+
btrfs_set_super_total_bytes(super, round_down(old_total + diff,
1497+
fs_info->sectorsize));
1498+
device->total_bytes = new_size;
1499+
ret = btrfs_update_device(trans, device);
1500+
if (ret) {
1501+
btrfs_abort_transaction(trans, ret);
1502+
goto close;
1503+
}
1504+
ret |= btrfs_commit_transaction(trans, root);
1505+
close:
1506+
ret |= close_ctree(root);
1507+
if (ret)
1508+
return ret;
1509+
1510+
if (path_is_reg_file(dev_name))
1511+
ret = truncate(dev_name, new_size);
1512+
if (ret)
1513+
error("failed to truncate %s", device->name);
1514+
return ret;
1515+
}
1516+
12931517
static int check_resize_args(const char *amount, const char *path, u64 *devid_ret)
12941518
{
12951519
struct btrfs_ioctl_fs_info_args fi_args;
@@ -1449,6 +1673,7 @@ static int cmd_filesystem_resize(const struct cmd_struct *cmd,
14491673
u64 devid;
14501674
int ret;
14511675
bool enqueue = false;
1676+
bool offline = false;
14521677
bool cancel = false;
14531678

14541679
/*
@@ -1458,6 +1683,8 @@ static int cmd_filesystem_resize(const struct cmd_struct *cmd,
14581683
for (optind = 1; optind < argc; optind++) {
14591684
if (strcmp(argv[optind], "--enqueue") == 0) {
14601685
enqueue = true;
1686+
} else if (strcmp(argv[optind], "--offline") == 0) {
1687+
offline = true;
14611688
} else if (strcmp(argv[optind], "--") == 0) {
14621689
/* Separator: options -- non-options */
14631690
} else if (strncmp(argv[optind], "--", 2) == 0) {
@@ -1472,6 +1699,12 @@ static int cmd_filesystem_resize(const struct cmd_struct *cmd,
14721699
if (check_argc_exact(argc - optind, 2))
14731700
return 1;
14741701

1702+
if (offline && enqueue) {
1703+
error("--enqueue is not compatible with --offline\n"
1704+
"since offline resizing is synchronous");
1705+
return 1;
1706+
}
1707+
14751708
amount = argv[optind];
14761709
path = argv[optind + 1];
14771710

@@ -1481,6 +1714,9 @@ static int cmd_filesystem_resize(const struct cmd_struct *cmd,
14811714
return 1;
14821715
}
14831716

1717+
if (offline)
1718+
return offline_resize(path, amount);
1719+
14841720
cancel = (strcmp("cancel", amount) == 0);
14851721

14861722
fd = btrfs_open_dir(path);
@@ -1490,7 +1726,8 @@ static int cmd_filesystem_resize(const struct cmd_struct *cmd,
14901726
error(
14911727
"resize works on mounted filesystems and accepts only\n"
14921728
"directories as argument. Passing file containing a btrfs image\n"
1493-
"would resize the underlying filesystem instead of the image.\n");
1729+
"would resize the underlying filesystem instead of the image.\n"
1730+
"To resize a file containing a btrfs image please use the --offline flag.\n");
14941731
}
14951732
return 1;
14961733
}

0 commit comments

Comments
 (0)