Skip to content

Commit d7e49b7

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]>
1 parent b299793 commit d7e49b7

File tree

1 file changed

+258
-2
lines changed

1 file changed

+258
-2
lines changed

cmds/filesystem.c

Lines changed: 258 additions & 2 deletions
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"
43-
#include "kernel-shared/volumes.h"
4444
#include "kernel-shared/disk-io.h"
45+
#include "kernel-shared/transaction.h"
46+
#include "kernel-shared/volumes.h"
4547
#include "common/defs.h"
4648
#include "common/internal.h"
4749
#include "common/messages.h"
@@ -1287,6 +1289,7 @@ 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

@@ -1440,6 +1443,246 @@ static int check_resize_args(const char *amount, const char *path, u64 *devid_re
14401443
return ret;
14411444
}
14421445

1446+
static int check_offline_resize_args(const struct btrfs_fs_info *fs_info,
1447+
const char *amount, const char *path,
1448+
struct btrfs_device **device_ret,
1449+
u64 *new_size_ret)
1450+
{
1451+
int ret = 0;
1452+
1453+
char amount_dup[BTRFS_VOL_NAME_MAX];
1454+
1455+
struct btrfs_device *device = NULL;
1456+
struct btrfs_device *mindev = NULL;
1457+
bool dev_found = false;
1458+
u64 devid = 1;
1459+
u64 mindevid = (u64)-1;
1460+
char *devstr = NULL;
1461+
1462+
struct stat stat_buf;
1463+
char *sizestr = NULL;
1464+
u64 new_size = 0, old_size = 0, diff = 0;
1465+
int mod = 0;
1466+
1467+
if (check_mounted(path)) {
1468+
error("%s must not be mounted to use --offline", path);
1469+
ret = 1;
1470+
goto out;
1471+
}
1472+
1473+
if (!fs_info->fs_devices->num_devices) {
1474+
error("no devices found");
1475+
ret = 1;
1476+
goto out;
1477+
}
1478+
1479+
ret = snprintf(amount_dup, BTRFS_VOL_NAME_MAX, "%s", amount);
1480+
if (strlen(amount) != ret) {
1481+
error("newsize argument is too long");
1482+
ret = 1;
1483+
goto out;
1484+
}
1485+
ret = 0;
1486+
1487+
if (strcmp(amount, "cancel") == 0) {
1488+
error("cannot cancel offline resize since the operation is synchronous");
1489+
ret = 1;
1490+
goto out;
1491+
}
1492+
1493+
/* Parse device id. */
1494+
sizestr = amount_dup;
1495+
devstr = strchr(sizestr, ':');
1496+
if (devstr) {
1497+
sizestr = devstr + 1;
1498+
*devstr = 0;
1499+
devstr = amount_dup;
1500+
1501+
errno = 0;
1502+
devid = strtoull(devstr, NULL, 10);
1503+
1504+
if (errno) {
1505+
error("failed to parse devid %s: %m", devstr);
1506+
ret = 1;
1507+
goto out;
1508+
}
1509+
}
1510+
1511+
/* Find device matching device id */
1512+
list_for_each_entry(device, &fs_info->fs_devices->devices, dev_list) {
1513+
if (device->devid < mindevid) {
1514+
mindevid = device->devid;
1515+
mindev = device;
1516+
}
1517+
if (device->devid == devid) {
1518+
dev_found = true;
1519+
break;
1520+
}
1521+
}
1522+
1523+
if (devstr && !dev_found) {
1524+
/* Devid specified but not found. */
1525+
error("cannot find devid: %lld", devid);
1526+
ret = 1;
1527+
goto out;
1528+
} else if (!devstr && devid == 1 && !dev_found) {
1529+
/*
1530+
* No device specified, assuming implicit 1 but it does not
1531+
* exist. Use minimum device as fallback.
1532+
*/
1533+
warning("no devid specified means devid 1 which does not exist, using\n"
1534+
"\t lowest devid %llu as a fallback",
1535+
mindevid);
1536+
devid = mindevid;
1537+
device = mindev;
1538+
}
1539+
if (!device) {
1540+
error("unable to find device");
1541+
ret = 1;
1542+
goto out;
1543+
}
1544+
*device_ret = device;
1545+
old_size = device->total_bytes;
1546+
1547+
if (strcmp(sizestr, "max") == 0) {
1548+
if (path_is_block_device(device->name)) {
1549+
new_size = device_get_partition_size(device->name);
1550+
} else if (path_is_reg_file(device->name)) {
1551+
stat(device->name, &stat_buf);
1552+
new_size = stat_buf.st_size;
1553+
}
1554+
1555+
if (new_size == 0) {
1556+
error("unable to get size for device: %s",
1557+
device->name);
1558+
ret = 1;
1559+
goto out;
1560+
}
1561+
} else {
1562+
if (sizestr[0] == '-') {
1563+
error("offline resize does not support shrinking");
1564+
ret = 1;
1565+
goto out;
1566+
} else if (sizestr[0] == '+') {
1567+
mod = 1;
1568+
sizestr++;
1569+
}
1570+
ret = parse_u64_with_suffix(sizestr, &diff);
1571+
if (ret < 0) {
1572+
error("failed to parse size %s", sizestr);
1573+
ret = 1;
1574+
goto out;
1575+
}
1576+
1577+
/* For target sizes without +/- sign prefix (e.g. 1:150g) */
1578+
if (mod == 0) {
1579+
new_size = diff;
1580+
} else if (mod > 0) {
1581+
if (diff > ULLONG_MAX - old_size) {
1582+
error("increasing %s is out of range",
1583+
pretty_size_mode(diff, UNITS_DEFAULT));
1584+
ret = 1;
1585+
goto out;
1586+
}
1587+
new_size = old_size + diff;
1588+
}
1589+
}
1590+
new_size = round_down(new_size, fs_info->sectorsize);
1591+
if (new_size < old_size) {
1592+
error("offline resize does not support shrinking");
1593+
ret = 1;
1594+
goto out;
1595+
}
1596+
*new_size_ret = new_size;
1597+
1598+
if (path_is_block_device(device->name) &&
1599+
new_size > device_get_partition_size(device->name)) {
1600+
error("unable to resize '%s': not enough free space",
1601+
device->name);
1602+
ret = 1;
1603+
goto out;
1604+
}
1605+
1606+
if (new_size < 256 * SZ_1M)
1607+
warning("the new size %lld (%s) is < 256MiB, this may be rejected by kernel",
1608+
new_size, pretty_size_mode(new_size, UNITS_DEFAULT));
1609+
1610+
pr_verbose(LOG_DEFAULT, "Resize device id %lld from %s to %s\n", devid,
1611+
pretty_size_mode(old_size, UNITS_DEFAULT),
1612+
pretty_size_mode(new_size, UNITS_DEFAULT));
1613+
out:
1614+
return ret;
1615+
}
1616+
1617+
static int offline_resize(const char *amount, const char *path)
1618+
{
1619+
int ret = 0, ret2 = 0;
1620+
struct btrfs_root *root;
1621+
struct btrfs_fs_info *fs_info;
1622+
struct btrfs_device *device;
1623+
struct btrfs_super_block *super_copy;
1624+
struct btrfs_trans_handle *trans;
1625+
u64 new_size;
1626+
1627+
u64 old_total;
1628+
u64 diff;
1629+
1630+
char dev_name[BTRFS_VOL_NAME_MAX];
1631+
1632+
root = open_ctree(path, 0, OPEN_CTREE_WRITES);
1633+
if (!root) {
1634+
error("could not open file at %s\n"
1635+
"offline resize works on a file containing a btrfs image.",
1636+
path);
1637+
return 1;
1638+
}
1639+
fs_info = root->fs_info;
1640+
super_copy = fs_info->super_copy;
1641+
1642+
ret = check_offline_resize_args(fs_info, amount, path, &device,
1643+
&new_size);
1644+
if (ret) {
1645+
ret = 1;
1646+
goto close;
1647+
}
1648+
1649+
ret = snprintf(dev_name, BTRFS_VOL_NAME_MAX, "%s", device->name);
1650+
if (strlen(device->name) != ret) {
1651+
error("device name too long %s", device->name);
1652+
ret = 1;
1653+
goto close;
1654+
}
1655+
ret = 0;
1656+
1657+
trans = btrfs_start_transaction(root, 0);
1658+
old_total = btrfs_super_total_bytes(super_copy);
1659+
diff = round_down(new_size - device->total_bytes, fs_info->sectorsize);
1660+
btrfs_set_super_total_bytes(
1661+
super_copy, round_down(old_total + diff, fs_info->sectorsize));
1662+
device->total_bytes = new_size;
1663+
1664+
ret = btrfs_update_device(trans, device);
1665+
ret2 = btrfs_commit_transaction(trans, root);
1666+
if (ret2)
1667+
ret = 1;
1668+
close:
1669+
ret2 = close_ctree(root);
1670+
if (ret2)
1671+
ret = 1;
1672+
1673+
if (ret)
1674+
return ret;
1675+
1676+
if (path_is_reg_file(dev_name)) {
1677+
ret = truncate(dev_name, new_size);
1678+
if (ret) {
1679+
error("failed to truncate %s", device->name);
1680+
ret = 1;
1681+
}
1682+
}
1683+
return ret;
1684+
}
1685+
14431686
static int cmd_filesystem_resize(const struct cmd_struct *cmd,
14441687
int argc, char **argv)
14451688
{
@@ -1449,6 +1692,7 @@ static int cmd_filesystem_resize(const struct cmd_struct *cmd,
14491692
u64 devid;
14501693
int ret;
14511694
bool enqueue = false;
1695+
bool offline = false;
14521696
bool cancel = false;
14531697

14541698
/*
@@ -1458,6 +1702,8 @@ static int cmd_filesystem_resize(const struct cmd_struct *cmd,
14581702
for (optind = 1; optind < argc; optind++) {
14591703
if (strcmp(argv[optind], "--enqueue") == 0) {
14601704
enqueue = true;
1705+
} else if (strcmp(argv[optind], "--offline") == 0) {
1706+
offline = true;
14611707
} else if (strcmp(argv[optind], "--") == 0) {
14621708
/* Separator: options -- non-options */
14631709
} else if (strncmp(argv[optind], "--", 2) == 0) {
@@ -1472,6 +1718,12 @@ static int cmd_filesystem_resize(const struct cmd_struct *cmd,
14721718
if (check_argc_exact(argc - optind, 2))
14731719
return 1;
14741720

1721+
if (offline && enqueue) {
1722+
error("--enqueue is not compatible with --offline\n"
1723+
"since offline resizing is synchronous");
1724+
return 1;
1725+
}
1726+
14751727
amount = argv[optind];
14761728
path = argv[optind + 1];
14771729

@@ -1481,6 +1733,9 @@ static int cmd_filesystem_resize(const struct cmd_struct *cmd,
14811733
return 1;
14821734
}
14831735

1736+
if (offline)
1737+
return offline_resize(amount, path);
1738+
14841739
cancel = (strcmp("cancel", amount) == 0);
14851740

14861741
fd = btrfs_open_dir(path);
@@ -1490,7 +1745,8 @@ static int cmd_filesystem_resize(const struct cmd_struct *cmd,
14901745
error(
14911746
"resize works on mounted filesystems and accepts only\n"
14921747
"directories as argument. Passing file containing a btrfs image\n"
1493-
"would resize the underlying filesystem instead of the image.\n");
1748+
"would resize the underlying filesystem instead of the image.\n"
1749+
"To resize a file containing a btrfs image please use the --offline flag.\n");
14941750
}
14951751
return 1;
14961752
}

0 commit comments

Comments
 (0)