cmd_image_update

New tool: 'bcachefs image update', for updating images produced by
'bcachefs image create': this tool will update images while making the
minimal changes to the on disk image, to make tools like casync more
efficient.

Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
Kent Overstreet 2025-04-28 13:38:04 -04:00
parent c1a25673e9
commit 3321afc8b6
2 changed files with 181 additions and 1 deletions

View File

@ -40,6 +40,7 @@ void bcachefs_usage(void)
"\n"
"Commands for managing images:\n"
" image create Create a new compact disk image\n"
" image update Sync an image with a directory tree\n"
"\n"
"Mount:\n"
" mount Mount a filesystem\n"

View File

@ -26,6 +26,7 @@
#include "crypto.h"
#include "libbcachefs/alloc_background.h"
#include "libbcachefs/alloc_foreground.h"
#include "libbcachefs/btree_update.h"
#include "libbcachefs/data_update.h"
#include "libbcachefs/disk_accounting.h"
#include "libbcachefs/errcode.h"
@ -66,6 +67,22 @@ static u64 count_input_size(int dirfd)
return bytes;
}
static void set_data_allowed_for_image_update(struct bch_fs *c)
{
mutex_lock(&c->sb_lock);
struct bch_member *m = bch2_members_v2_get_mut(c->disk_sb.sb, 0);
SET_BCH_MEMBER_DATA_ALLOWED(m, BIT(BCH_DATA_user));
m = bch2_members_v2_get_mut(c->disk_sb.sb, 1);
SET_BCH_MEMBER_DATA_ALLOWED(m, BIT(BCH_DATA_journal)|BIT(BCH_DATA_btree));
bch2_write_super(c);
mutex_unlock(&c->sb_lock);
bch2_dev_allocator_set_rw(c, c->devs[0], true);
bch2_dev_allocator_set_rw(c, c->devs[1], true);
}
struct move_btree_args {
bool move_alloc;
unsigned target;
@ -78,7 +95,6 @@ static bool move_btree_pred(struct bch_fs *c, void *_arg,
{
struct move_btree_args *args = _arg;
data_opts->target = dev_to_target(0);
data_opts->target = args->target;
if (k.k->type != KEY_TYPE_btree_ptr_v2)
@ -608,6 +624,166 @@ static int cmd_image_create(int argc, char *argv[])
return 0;
}
static int image_update(const char *src_path, const char *dst_image,
bool keep_alloc,
unsigned verbosity)
{
int src_fd = xopen(src_path, O_RDONLY);
if (!S_ISDIR(xfstat(src_fd).st_mode))
die("%s is not a directory", src_path);
u64 input_bytes = count_input_size(src_fd);
if (truncate(dst_image, input_bytes * 2))
die("truncate error: %m");
darray_const_str device_paths = {};
darray_push(&device_paths, dst_image);
struct bch_opts opts = bch2_opts_empty();
opt_set(opts, copygc_enabled, false);
opt_set(opts, rebalance_enabled, false);
opt_set(opts, nostart, true);
struct bch_fs *c = bch2_fs_open(&device_paths, &opts);
int ret = PTR_ERR_OR_ZERO(c);
if (ret)
die("error opening image: %s", bch2_err_str(ret));
c->loglevel = 5 + max_t(int, 0, verbosity - 1);
struct dev_opts dev_opts = dev_opts_default();
dev_opts.path = mprintf("%s.metadata", dst_image);
if (1) {
/* factor this out into a helper */
ret = open_for_format(&dev_opts, BLK_OPEN_CREAT, false);
if (ret) {
fprintf(stderr, "error opening %s: %m", dev_opts.path);
goto err;
}
if (ftruncate(dev_opts.bdev->bd_fd, input_bytes)) {
fprintf(stderr, "ftruncate error: %m");
goto err;
}
ret = bch2_format_for_device_add(&dev_opts,
c->opts.block_size,
c->opts.btree_node_size);
bch_err_msg(c, ret, "opening %s", dev_opts.path);
if (ret)
goto err;
ret = bch2_dev_add(c, dev_opts.path);
bch_err_msg(c, ret, "adding metadata device");
if (ret)
goto err;
}
set_data_allowed_for_image_update(c);
ret = bch2_fs_start(c);
bch_err_msg(c, ret, "starting fs");
if (ret)
goto err;
bch_verbose(c, "Moving btree to temp device");
ret = move_btree(c, true, 1);
bch_err_msg(c, ret, "migrating btree to temp device");
if (ret)
goto err_stop;
/* xattrs will be recreated */
bch_verbose(c, "Deleting xattrs");
ret = bch2_btree_delete_range(c, BTREE_ID_xattrs, POS_MIN, SPOS_MAX,
BTREE_ITER_all_snapshots, NULL);
bch_err_msg(c, ret, "deleting xattrs");
if (ret)
goto err_stop;
bch_verbose(c, "Syncing data");
struct copy_fs_state s = {};
ret = copy_fs(c, &s, src_fd, src_path) ?:
finish_image(c, keep_alloc, verbosity);
if (ret)
goto err;
bch2_fs_stop(c);
unlink(dev_opts.path);
xclose(src_fd);
return 0;
err_stop:
bch2_fs_stop(c);
err:
unlink(dev_opts.path);
exit(EXIT_FAILURE);
}
static void image_update_usage(void)
{
puts("bcachefs image update - update an image file, minimizing changes\n"
"Usage: bcachefs image update [OPTION]... <file>\n"
"\n"
"Options:\n"
" --source=path Source directory to be used as content for the new image\n"
" -a, --keep-alloc Include allocation info in the filesystem\n"
" 6.16+ regenerates alloc info on first rw mount\n"
" -q, --quiet Only print errors\n"
" -v, --verbose Verbose filesystem initialization\n"
" -h, --help Display this help and exit\n"
"\n"
"Report bugs to <linux-bcachefs@vger.kernel.org>");
}
static int cmd_image_update(int argc, char *argv[])
{
static const struct option longopts[] = {
{ "source", required_argument, NULL, 's' },
{ "keep-alloc", no_argument, NULL, 'a' },
{ "quiet", no_argument, NULL, 'q' },
{ "verbose", no_argument, NULL, 'v' },
{ "help", no_argument, NULL, 'h' },
{ NULL }
};
const char *source = NULL;
bool keep_alloc = false;
unsigned verbosity = 1;
int opt;
while ((opt = getopt_long(argc, argv, "s:aqvh",
longopts, NULL)) != -1)
switch (opt) {
case 's':
source = optarg;
break;
case 'a':
keep_alloc = true;
break;
case 'q':
verbosity = 0;
break;
case 'v':
verbosity++;
break;
case 'h':
image_update_usage();
exit(EXIT_SUCCESS);
default:
die("getopt ret %i %c", opt, opt);
}
args_shift(optind);
if (argc != 1)
die("Please supply a filename");
return image_update(source, argv[0],
keep_alloc, verbosity);
}
static int image_usage(void)
{
puts("bcachefs image - commands for creating and updating image files\n"
@ -615,6 +791,7 @@ static int image_usage(void)
"\n"
"Commands:\n"
" create Create a minimally-sized disk image\n"
" update Update a disk image, minimizing changes\n"
"\n"
"Report bugs to <linux-bcachefs@vger.kernel.org>");
return 0;
@ -628,6 +805,8 @@ int image_cmds(int argc, char *argv[])
return image_usage();
if (!strcmp(cmd, "create"))
return cmd_image_create(argc, argv);
if (!strcmp(cmd, "update"))
return cmd_image_update(argc, argv);
image_usage();
return -EINVAL;