btrfs: add support for recursive btrfs snapshotting
This commit is contained in:
parent
cbf21ecc02
commit
f70a17f8d4
|
@ -294,7 +294,7 @@ int tar_export_start(TarExport *e, const char *path, int fd, ImportCompressType
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
/* Let's try to make a snapshot, if we can, so that the export is atomic */
|
/* Let's try to make a snapshot, if we can, so that the export is atomic */
|
||||||
r = btrfs_subvol_snapshot_fd(sfd, e->temp_path, BTRFS_SNAPSHOT_READ_ONLY);
|
r = btrfs_subvol_snapshot_fd(sfd, e->temp_path, BTRFS_SNAPSHOT_READ_ONLY|BTRFS_SNAPSHOT_RECURSIVE);
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
log_debug_errno(r, "Couldn't create snapshot %s of %s, not exporting atomically: %m", e->temp_path, path);
|
log_debug_errno(r, "Couldn't create snapshot %s of %s, not exporting atomically: %m", e->temp_path, path);
|
||||||
free(e->temp_path);
|
free(e->temp_path);
|
||||||
|
|
|
@ -3761,7 +3761,7 @@ int main(int argc, char *argv[]) {
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
r = btrfs_subvol_snapshot(arg_directory, np, (arg_read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY);
|
r = btrfs_subvol_snapshot(arg_directory, np, (arg_read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE);
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
log_error_errno(r, "Failed to create snapshot %s from %s: %m", np, arg_directory);
|
log_error_errno(r, "Failed to create snapshot %s from %s: %m", np, arg_directory);
|
||||||
goto finish;
|
goto finish;
|
||||||
|
@ -3785,7 +3785,7 @@ int main(int argc, char *argv[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg_template) {
|
if (arg_template) {
|
||||||
r = btrfs_subvol_snapshot(arg_template, arg_directory, (arg_read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY);
|
r = btrfs_subvol_snapshot(arg_template, arg_directory, (arg_read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE);
|
||||||
if (r == -EEXIST) {
|
if (r == -EEXIST) {
|
||||||
if (!arg_quiet)
|
if (!arg_quiet)
|
||||||
log_info("Directory %s already exists, not populating from template %s.", arg_directory, arg_template);
|
log_info("Directory %s already exists, not populating from template %s.", arg_directory, arg_template);
|
||||||
|
|
|
@ -101,74 +101,6 @@ int btrfs_is_snapshot(int fd) {
|
||||||
return F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC);
|
return F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC);
|
||||||
}
|
}
|
||||||
|
|
||||||
int btrfs_subvol_snapshot_fd(int old_fd, const char *new_path, BtrfsSnapshotFlags flags) {
|
|
||||||
struct btrfs_ioctl_vol_args_v2 args = {
|
|
||||||
.flags = flags & BTRFS_SNAPSHOT_READ_ONLY ? BTRFS_SUBVOL_RDONLY : 0,
|
|
||||||
};
|
|
||||||
_cleanup_close_ int new_fd = -1;
|
|
||||||
const char *subvolume;
|
|
||||||
int r;
|
|
||||||
|
|
||||||
assert(new_path);
|
|
||||||
|
|
||||||
r = btrfs_is_snapshot(old_fd);
|
|
||||||
if (r < 0)
|
|
||||||
return r;
|
|
||||||
if (r == 0) {
|
|
||||||
if (!(flags & BTRFS_SNAPSHOT_FALLBACK_COPY))
|
|
||||||
return -EISDIR;
|
|
||||||
|
|
||||||
r = btrfs_subvol_make(new_path);
|
|
||||||
if (r < 0)
|
|
||||||
return r;
|
|
||||||
|
|
||||||
r = copy_directory_fd(old_fd, new_path, true);
|
|
||||||
if (r < 0) {
|
|
||||||
btrfs_subvol_remove(new_path, false);
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flags & BTRFS_SNAPSHOT_READ_ONLY) {
|
|
||||||
r = btrfs_subvol_set_read_only(new_path, true);
|
|
||||||
if (r < 0) {
|
|
||||||
btrfs_subvol_remove(new_path, false);
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
r = extract_subvolume_name(new_path, &subvolume);
|
|
||||||
if (r < 0)
|
|
||||||
return r;
|
|
||||||
|
|
||||||
new_fd = open_parent(new_path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
|
|
||||||
if (new_fd < 0)
|
|
||||||
return new_fd;
|
|
||||||
|
|
||||||
strncpy(args.name, subvolume, sizeof(args.name)-1);
|
|
||||||
args.fd = old_fd;
|
|
||||||
|
|
||||||
if (ioctl(new_fd, BTRFS_IOC_SNAP_CREATE_V2, &args) < 0)
|
|
||||||
return -errno;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int btrfs_subvol_snapshot(const char *old_path, const char *new_path, BtrfsSnapshotFlags flags) {
|
|
||||||
_cleanup_close_ int old_fd = -1;
|
|
||||||
|
|
||||||
assert(old_path);
|
|
||||||
assert(new_path);
|
|
||||||
|
|
||||||
old_fd = open(old_path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
|
|
||||||
if (old_fd < 0)
|
|
||||||
return -errno;
|
|
||||||
|
|
||||||
return btrfs_subvol_snapshot_fd(old_fd, new_path, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
int btrfs_subvol_make(const char *path) {
|
int btrfs_subvol_make(const char *path) {
|
||||||
struct btrfs_ioctl_vol_args args = {};
|
struct btrfs_ioctl_vol_args args = {};
|
||||||
_cleanup_close_ int fd = -1;
|
_cleanup_close_ int fd = -1;
|
||||||
|
@ -909,3 +841,183 @@ int btrfs_subvol_remove(const char *path, bool recursive) {
|
||||||
int btrfs_subvol_remove_fd(int fd, const char *subvolume, bool recursive) {
|
int btrfs_subvol_remove_fd(int fd, const char *subvolume, bool recursive) {
|
||||||
return subvol_remove_children(fd, subvolume, 0, recursive);
|
return subvol_remove_children(fd, subvolume, 0, recursive);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int subvol_snapshot_children(int old_fd, int new_fd, const char *subvolume, uint64_t subvol_id, BtrfsSnapshotFlags flags) {
|
||||||
|
|
||||||
|
struct btrfs_ioctl_search_args args = {
|
||||||
|
.key.tree_id = BTRFS_ROOT_TREE_OBJECTID,
|
||||||
|
|
||||||
|
.key.min_objectid = BTRFS_FIRST_FREE_OBJECTID,
|
||||||
|
.key.max_objectid = BTRFS_LAST_FREE_OBJECTID,
|
||||||
|
|
||||||
|
.key.min_type = BTRFS_ROOT_BACKREF_KEY,
|
||||||
|
.key.max_type = BTRFS_ROOT_BACKREF_KEY,
|
||||||
|
|
||||||
|
.key.min_transid = 0,
|
||||||
|
.key.max_transid = (uint64_t) -1,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct btrfs_ioctl_vol_args_v2 vol_args = {
|
||||||
|
.flags = flags & BTRFS_SNAPSHOT_READ_ONLY ? BTRFS_SUBVOL_RDONLY : 0,
|
||||||
|
.fd = old_fd,
|
||||||
|
};
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(old_fd >= 0);
|
||||||
|
assert(new_fd >= 0);
|
||||||
|
assert(subvolume);
|
||||||
|
|
||||||
|
strncpy(vol_args.name, subvolume, sizeof(vol_args.name)-1);
|
||||||
|
vol_args.fd = old_fd;
|
||||||
|
|
||||||
|
if (ioctl(new_fd, BTRFS_IOC_SNAP_CREATE_V2, &vol_args) < 0)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
if (!(flags & BTRFS_SNAPSHOT_RECURSIVE))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (subvol_id == 0) {
|
||||||
|
r = btrfs_subvol_get_id_fd(old_fd, &subvol_id);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
args.key.min_offset = args.key.max_offset = subvol_id;
|
||||||
|
|
||||||
|
while (btrfs_ioctl_search_args_compare(&args) <= 0) {
|
||||||
|
const struct btrfs_ioctl_search_header *sh;
|
||||||
|
unsigned i;
|
||||||
|
|
||||||
|
args.key.nr_items = 256;
|
||||||
|
if (ioctl(old_fd, BTRFS_IOC_TREE_SEARCH, &args) < 0)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
if (args.key.nr_items <= 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) {
|
||||||
|
_cleanup_free_ char *p = NULL, *c = NULL, *np = NULL;
|
||||||
|
struct btrfs_ioctl_ino_lookup_args ino_args;
|
||||||
|
const struct btrfs_root_ref *ref;
|
||||||
|
_cleanup_close_ int old_child_fd = -1, new_child_fd = -1;
|
||||||
|
|
||||||
|
btrfs_ioctl_search_args_set(&args, sh);
|
||||||
|
|
||||||
|
if (sh->type != BTRFS_ROOT_BACKREF_KEY)
|
||||||
|
continue;
|
||||||
|
if (sh->offset != subvol_id)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ref = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh);
|
||||||
|
|
||||||
|
p = strndup((char*) ref + sizeof(struct btrfs_root_ref), le64toh(ref->name_len));
|
||||||
|
if (!p)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
zero(ino_args);
|
||||||
|
ino_args.treeid = subvol_id;
|
||||||
|
ino_args.objectid = htole64(ref->dirid);
|
||||||
|
|
||||||
|
if (ioctl(old_fd, BTRFS_IOC_INO_LOOKUP, &ino_args) < 0)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
/* The kernel returns an empty name if the
|
||||||
|
* subvolume is in the top-level directory,
|
||||||
|
* and otherwise appends a slash, so that we
|
||||||
|
* can just concatenate easily here, without
|
||||||
|
* adding a slash. */
|
||||||
|
c = strappend(ino_args.name, p);
|
||||||
|
if (!c)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
old_child_fd = openat(old_fd, c, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
|
||||||
|
if (old_child_fd < 0)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
np = strjoin(subvolume, "/", ino_args.name, NULL);
|
||||||
|
if (!np)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
new_child_fd = openat(new_fd, np, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
|
||||||
|
if (new_child_fd < 0)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
/* When btrfs clones the subvolumes, child
|
||||||
|
* subvolumes appear as directories. Remove
|
||||||
|
* them, so that we can create a new snapshot
|
||||||
|
* in their place */
|
||||||
|
if (unlinkat(new_child_fd, p, AT_REMOVEDIR) < 0)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
r = subvol_snapshot_children(old_child_fd, new_child_fd, p, sh->objectid, flags & ~BTRFS_SNAPSHOT_FALLBACK_COPY);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Increase search key by one, to read the next item, if we can. */
|
||||||
|
if (!btrfs_ioctl_search_args_inc(&args))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int btrfs_subvol_snapshot_fd(int old_fd, const char *new_path, BtrfsSnapshotFlags flags) {
|
||||||
|
_cleanup_close_ int new_fd = -1;
|
||||||
|
const char *subvolume;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(old_fd >= 0);
|
||||||
|
assert(new_path);
|
||||||
|
|
||||||
|
r = btrfs_is_snapshot(old_fd);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
if (r == 0) {
|
||||||
|
if (!(flags & BTRFS_SNAPSHOT_FALLBACK_COPY))
|
||||||
|
return -EISDIR;
|
||||||
|
|
||||||
|
r = btrfs_subvol_make(new_path);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = copy_directory_fd(old_fd, new_path, true);
|
||||||
|
if (r < 0) {
|
||||||
|
btrfs_subvol_remove(new_path, false);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags & BTRFS_SNAPSHOT_READ_ONLY) {
|
||||||
|
r = btrfs_subvol_set_read_only(new_path, true);
|
||||||
|
if (r < 0) {
|
||||||
|
btrfs_subvol_remove(new_path, false);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = extract_subvolume_name(new_path, &subvolume);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
new_fd = open_parent(new_path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
|
||||||
|
if (new_fd < 0)
|
||||||
|
return new_fd;
|
||||||
|
|
||||||
|
return subvol_snapshot_children(old_fd, new_fd, subvolume, 0, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
int btrfs_subvol_snapshot(const char *old_path, const char *new_path, BtrfsSnapshotFlags flags) {
|
||||||
|
_cleanup_close_ int old_fd = -1;
|
||||||
|
|
||||||
|
assert(old_path);
|
||||||
|
assert(new_path);
|
||||||
|
|
||||||
|
old_fd = open(old_path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
|
||||||
|
if (old_fd < 0)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
return btrfs_subvol_snapshot_fd(old_fd, new_path, flags);
|
||||||
|
}
|
||||||
|
|
|
@ -46,6 +46,7 @@ typedef struct BtrfsQuotaInfo {
|
||||||
typedef enum BtrfsSnapshotFlags {
|
typedef enum BtrfsSnapshotFlags {
|
||||||
BTRFS_SNAPSHOT_FALLBACK_COPY = 1,
|
BTRFS_SNAPSHOT_FALLBACK_COPY = 1,
|
||||||
BTRFS_SNAPSHOT_READ_ONLY = 2,
|
BTRFS_SNAPSHOT_READ_ONLY = 2,
|
||||||
|
BTRFS_SNAPSHOT_RECURSIVE = 4,
|
||||||
} BtrfsSnapshotFlags;
|
} BtrfsSnapshotFlags;
|
||||||
|
|
||||||
int btrfs_is_snapshot(int fd);
|
int btrfs_is_snapshot(int fd);
|
||||||
|
|
|
@ -491,7 +491,7 @@ int image_clone(Image *i, const char *new_name, bool read_only) {
|
||||||
case IMAGE_DIRECTORY:
|
case IMAGE_DIRECTORY:
|
||||||
new_path = strjoina("/var/lib/machines/", new_name);
|
new_path = strjoina("/var/lib/machines/", new_name);
|
||||||
|
|
||||||
r = btrfs_subvol_snapshot(i->path, new_path, (read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY);
|
r = btrfs_subvol_snapshot(i->path, new_path, (read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case IMAGE_RAW:
|
case IMAGE_RAW:
|
||||||
|
|
|
@ -133,9 +133,17 @@ int main(int argc, char *argv[]) {
|
||||||
if (mkdir("/xxxrectest/mnt", 0755) < 0)
|
if (mkdir("/xxxrectest/mnt", 0755) < 0)
|
||||||
log_error_errno(errno, "Failed to make directory: %m");
|
log_error_errno(errno, "Failed to make directory: %m");
|
||||||
|
|
||||||
|
r = btrfs_subvol_snapshot("/xxxrectest", "/xxxrectest2", BTRFS_SNAPSHOT_RECURSIVE);
|
||||||
|
if (r < 0)
|
||||||
|
log_error_errno(r, "Failed to snapshot subvolume: %m");
|
||||||
|
|
||||||
r = btrfs_subvol_remove("/xxxrectest", true);
|
r = btrfs_subvol_remove("/xxxrectest", true);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
log_error_errno(r, "Failed to recursively remove subvolume: %m");
|
log_error_errno(r, "Failed to recursively remove subvolume: %m");
|
||||||
|
|
||||||
|
r = btrfs_subvol_remove("/xxxrectest2", true);
|
||||||
|
if (r < 0)
|
||||||
|
log_error_errno(r, "Failed to recursively remove subvolume: %m");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue