X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=cmds-receive.c;h=68123a31cc5cf5afe2eed69f5fad956c2ffb6253;hb=82a1442e9cd025436be8ae65e684fd6b2701efbb;hp=03d21e90488d058be8c5572f11e5a2bbde8cd6c0;hpb=de22c28ef31d9721606ba05965a093a8044be0de;p=platform%2Fupstream%2Fbtrfs-progs.git diff --git a/cmds-receive.c b/cmds-receive.c index 03d21e9..68123a3 100644 --- a/cmds-receive.c +++ b/cmds-receive.c @@ -16,12 +16,8 @@ * Boston, MA 021110-1307, USA. */ -#define _GNU_SOURCE -#define _POSIX_C_SOURCE 200809 -#define _XOPEN_SOURCE 700 -#define _BSD_SOURCE - #include "kerncompat.h" +#include "androidcompat.h" #include #include @@ -30,9 +26,10 @@ #include #include #include -#include +#include #include #include +#include #include #include @@ -52,6 +49,8 @@ #include "send.h" #include "send-stream.h" #include "send-utils.h" +#include "send-dump.h" +#include "help.h" static int g_verbose = 0; @@ -61,20 +60,35 @@ struct btrfs_receive int dest_dir_fd; int write_fd; - char *write_path; + char write_path[PATH_MAX]; char *root_path; char *dest_dir_path; /* relative to root_path */ - char *full_subvol_path; + char full_subvol_path[PATH_MAX]; + char *full_root_path; + int dest_dir_chroot; - struct subvol_info *cur_subvol; + struct subvol_info cur_subvol; + /* + * Substitute for cur_subvol::path which is a pointer and we cannot + * change it to an array as it's a public API. + */ + char cur_subvol_path[PATH_MAX]; struct subvol_uuid_search sus; int honor_end_cmd; + + /* + * Buffer to store capabilities from security.capabilities xattr, + * usually 20 bytes, but make same room for potentially larger + * encodings. Must be set only once per file, denoted by length > 0. + */ + char cached_capabilities[64]; + int cached_capabilities_len; }; -static int finish_subvol(struct btrfs_receive *r) +static int finish_subvol(struct btrfs_receive *rctx) { int ret; int subvol_fd = -1; @@ -82,21 +96,21 @@ static int finish_subvol(struct btrfs_receive *r) char uuid_str[BTRFS_UUID_UNPARSED_SIZE]; u64 flags; - if (r->cur_subvol == NULL) + if (rctx->cur_subvol_path[0] == 0) return 0; - subvol_fd = openat(r->mnt_fd, r->cur_subvol->path, - O_RDONLY | O_NOATIME); + subvol_fd = openat(rctx->mnt_fd, rctx->cur_subvol_path, + O_RDONLY | O_NOATIME); if (subvol_fd < 0) { ret = -errno; - fprintf(stderr, "ERROR: open %s failed. %s\n", - r->cur_subvol->path, strerror(-ret)); + error("cannot open %s: %s", + rctx->cur_subvol_path, strerror(-ret)); goto out; } memset(&rs_args, 0, sizeof(rs_args)); - memcpy(rs_args.uuid, r->cur_subvol->received_uuid, BTRFS_UUID_SIZE); - rs_args.stransid = r->cur_subvol->stransid; + memcpy(rs_args.uuid, rctx->cur_subvol.received_uuid, BTRFS_UUID_SIZE); + rs_args.stransid = rctx->cur_subvol.stransid; if (g_verbose >= 1) { uuid_unparse((u8*)rs_args.uuid, uuid_str); @@ -107,16 +121,16 @@ static int finish_subvol(struct btrfs_receive *r) ret = ioctl(subvol_fd, BTRFS_IOC_SET_RECEIVED_SUBVOL, &rs_args); if (ret < 0) { ret = -errno; - fprintf(stderr, "ERROR: BTRFS_IOC_SET_RECEIVED_SUBVOL failed. %s\n", + error("ioctl BTRFS_IOC_SET_RECEIVED_SUBVOL failed: %s", strerror(-ret)); goto out; } - r->cur_subvol->rtransid = rs_args.rtransid; + rctx->cur_subvol.rtransid = rs_args.rtransid; ret = ioctl(subvol_fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags); if (ret < 0) { ret = -errno; - fprintf(stderr, "ERROR: BTRFS_IOC_SUBVOL_GETFLAGS failed. %s\n", + error("ioctl BTRFS_IOC_SUBVOL_GETFLAGS failed: %s", strerror(-ret)); goto out; } @@ -126,18 +140,16 @@ static int finish_subvol(struct btrfs_receive *r) ret = ioctl(subvol_fd, BTRFS_IOC_SUBVOL_SETFLAGS, &flags); if (ret < 0) { ret = -errno; - fprintf(stderr, "ERROR: failed to make subvolume read only. " - "%s\n", strerror(-ret)); + error("failed to make subvolume read only: %s", + strerror(-ret)); goto out; } ret = 0; out: - if (r->cur_subvol) { - free(r->cur_subvol->path); - free(r->cur_subvol); - r->cur_subvol = NULL; + if (rctx->cur_subvol_path[0]) { + rctx->cur_subvol_path[0] = 0; } if (subvol_fd != -1) close(subvol_fd); @@ -148,42 +160,62 @@ static int process_subvol(const char *path, const u8 *uuid, u64 ctransid, void *user) { int ret; - struct btrfs_receive *r = user; + struct btrfs_receive *rctx = user; struct btrfs_ioctl_vol_args args_v1; char uuid_str[BTRFS_UUID_UNPARSED_SIZE]; - ret = finish_subvol(r); + ret = finish_subvol(rctx); if (ret < 0) goto out; - r->cur_subvol = calloc(1, sizeof(*r->cur_subvol)); + if (rctx->cur_subvol.path) { + error("subvol: another one already started, path ptr: %s", + rctx->cur_subvol.path); + ret = -EINVAL; + goto out; + } + if (rctx->cur_subvol_path[0]) { + error("subvol: another one already started, path buf: %s", + rctx->cur_subvol.path); + ret = -EINVAL; + goto out; + } - if (strlen(r->dest_dir_path) == 0) - r->cur_subvol->path = strdup(path); - else - r->cur_subvol->path = path_cat(r->dest_dir_path, path); - free(r->full_subvol_path); - r->full_subvol_path = path_cat3(r->root_path, r->dest_dir_path, path); + if (*rctx->dest_dir_path == 0) { + strncpy_null(rctx->cur_subvol_path, path); + } else { + ret = path_cat_out(rctx->cur_subvol_path, rctx->dest_dir_path, + path); + if (ret < 0) { + error("subvol: path invalid: %s", path); + goto out; + } + } + ret = path_cat3_out(rctx->full_subvol_path, rctx->root_path, + rctx->dest_dir_path, path); + if (ret < 0) { + error("subvol: path invalid: %s", path); + goto out; + } fprintf(stderr, "At subvol %s\n", path); - memcpy(r->cur_subvol->received_uuid, uuid, BTRFS_UUID_SIZE); - r->cur_subvol->stransid = ctransid; + memcpy(rctx->cur_subvol.received_uuid, uuid, BTRFS_UUID_SIZE); + rctx->cur_subvol.stransid = ctransid; if (g_verbose) { - uuid_unparse((u8*)r->cur_subvol->received_uuid, uuid_str); + uuid_unparse((u8*)rctx->cur_subvol.received_uuid, uuid_str); fprintf(stderr, "receiving subvol %s uuid=%s, stransid=%llu\n", path, uuid_str, - r->cur_subvol->stransid); + rctx->cur_subvol.stransid); } memset(&args_v1, 0, sizeof(args_v1)); strncpy_null(args_v1.name, path); - ret = ioctl(r->dest_dir_fd, BTRFS_IOC_SUBVOL_CREATE, &args_v1); + ret = ioctl(rctx->dest_dir_fd, BTRFS_IOC_SUBVOL_CREATE, &args_v1); if (ret < 0) { ret = -errno; - fprintf(stderr, "ERROR: creating subvolume %s failed. " - "%s\n", path, strerror(-ret)); + error("creating subvolume %s failed: %s", path, strerror(-ret)); goto out; } @@ -196,34 +228,55 @@ static int process_snapshot(const char *path, const u8 *uuid, u64 ctransid, void *user) { int ret; - struct btrfs_receive *r = user; + struct btrfs_receive *rctx = user; char uuid_str[BTRFS_UUID_UNPARSED_SIZE]; struct btrfs_ioctl_vol_args_v2 args_v2; struct subvol_info *parent_subvol = NULL; - ret = finish_subvol(r); + ret = finish_subvol(rctx); if (ret < 0) goto out; - r->cur_subvol = calloc(1, sizeof(*r->cur_subvol)); + if (rctx->cur_subvol.path) { + error("snapshot: another one already started, path ptr: %s", + rctx->cur_subvol.path); + ret = -EINVAL; + goto out; + } + if (rctx->cur_subvol_path[0]) { + error("snapshot: another one already started, path buf: %s", + rctx->cur_subvol.path); + ret = -EINVAL; + goto out; + } - if (strlen(r->dest_dir_path) == 0) - r->cur_subvol->path = strdup(path); - else - r->cur_subvol->path = path_cat(r->dest_dir_path, path); - free(r->full_subvol_path); - r->full_subvol_path = path_cat3(r->root_path, r->dest_dir_path, path); + if (*rctx->dest_dir_path == 0) { + strncpy_null(rctx->cur_subvol_path, path); + } else { + ret = path_cat_out(rctx->cur_subvol_path, rctx->dest_dir_path, + path); + if (ret < 0) { + error("snapshot: path invalid: %s", path); + goto out; + } + } + ret = path_cat3_out(rctx->full_subvol_path, rctx->root_path, + rctx->dest_dir_path, path); + if (ret < 0) { + error("snapshot: path invalid: %s", path); + goto out; + } fprintf(stdout, "At snapshot %s\n", path); - memcpy(r->cur_subvol->received_uuid, uuid, BTRFS_UUID_SIZE); - r->cur_subvol->stransid = ctransid; + memcpy(rctx->cur_subvol.received_uuid, uuid, BTRFS_UUID_SIZE); + rctx->cur_subvol.stransid = ctransid; if (g_verbose) { - uuid_unparse((u8*)r->cur_subvol->received_uuid, uuid_str); + uuid_unparse((u8*)rctx->cur_subvol.received_uuid, uuid_str); fprintf(stderr, "receiving snapshot %s uuid=%s, " "ctransid=%llu ", path, uuid_str, - r->cur_subvol->stransid); + rctx->cur_subvol.stransid); uuid_unparse(parent_uuid, uuid_str); fprintf(stderr, "parent_uuid=%s, parent_ctransid=%llu\n", uuid_str, parent_ctransid); @@ -232,18 +285,63 @@ static int process_snapshot(const char *path, const u8 *uuid, u64 ctransid, memset(&args_v2, 0, sizeof(args_v2)); strncpy_null(args_v2.name, path); - parent_subvol = subvol_uuid_search(&r->sus, 0, parent_uuid, - parent_ctransid, NULL, subvol_search_by_received_uuid); - if (!parent_subvol) { - parent_subvol = subvol_uuid_search(&r->sus, 0, parent_uuid, - parent_ctransid, NULL, subvol_search_by_uuid); + parent_subvol = subvol_uuid_search(&rctx->sus, 0, parent_uuid, + parent_ctransid, NULL, + subvol_search_by_received_uuid); + if (IS_ERR_OR_NULL(parent_subvol)) { + parent_subvol = subvol_uuid_search(&rctx->sus, 0, parent_uuid, + parent_ctransid, NULL, + subvol_search_by_uuid); } - if (!parent_subvol) { - ret = -ENOENT; - fprintf(stderr, "ERROR: could not find parent subvolume\n"); + if (IS_ERR_OR_NULL(parent_subvol)) { + if (!parent_subvol) + ret = -ENOENT; + else + ret = PTR_ERR(parent_subvol); + error("cannot find parent subvolume"); goto out; } + /* + * The path is resolved from the root subvol, but we could be in some + * subvolume under the root subvolume, so try and adjust the path to be + * relative to our root path. + */ + if (rctx->full_root_path) { + size_t root_len; + size_t sub_len; + + root_len = strlen(rctx->full_root_path); + sub_len = strlen(parent_subvol->path); + + /* First make sure the parent subvol is actually in our path */ + if (strstr(parent_subvol->path, rctx->full_root_path) != parent_subvol->path || + (sub_len > root_len && parent_subvol->path[root_len] != '/')) { + error( + "parent subvol is not reachable from inside the root subvol"); + ret = -ENOENT; + goto out; + } + + if (sub_len == root_len) { + parent_subvol->path[0] = '.'; + parent_subvol->path[1] = '\0'; + } else { + /* + * root path is foo/bar + * subvol path is foo/bar/baz + * + * we need to have baz be the path, so we need to move + * the bit after foo/bar/, so path + root_len + 1, and + * move the part we care about, so sub_len - root_len - + * 1. + */ + memmove(parent_subvol->path, + parent_subvol->path + root_len + 1, + sub_len - root_len - 1); + parent_subvol->path[sub_len - root_len - 1] = '\0'; + } + } /*if (rs_args.ctransid > rs_args.rtransid) { if (!r->force) { ret = -EINVAL; @@ -254,12 +352,15 @@ static int process_snapshot(const char *path, const u8 *uuid, u64 ctransid, } }*/ - args_v2.fd = openat(r->mnt_fd, parent_subvol->path, - O_RDONLY | O_NOATIME); + if (*parent_subvol->path == 0) + args_v2.fd = dup(rctx->mnt_fd); + else + args_v2.fd = openat(rctx->mnt_fd, parent_subvol->path, + O_RDONLY | O_NOATIME); if (args_v2.fd < 0) { ret = -errno; if (errno != ENOENT) - fprintf(stderr, "ERROR: open %s failed. %s\n", + error("cannot open %s: %s", parent_subvol->path, strerror(-ret)); else fprintf(stderr, @@ -270,13 +371,12 @@ static int process_snapshot(const char *path, const u8 *uuid, u64 ctransid, goto out; } - ret = ioctl(r->dest_dir_fd, BTRFS_IOC_SNAP_CREATE_V2, &args_v2); + ret = ioctl(rctx->dest_dir_fd, BTRFS_IOC_SNAP_CREATE_V2, &args_v2); close(args_v2.fd); if (ret < 0) { ret = -errno; - fprintf(stderr, "ERROR: creating snapshot %s -> %s " - "failed. %s\n", parent_subvol->path, - path, strerror(-ret)); + error("creating snapshot %s -> %s failed: %s", + parent_subvol->path, path, strerror(-ret)); goto out; } @@ -291,8 +391,14 @@ out: static int process_mkfile(const char *path, void *user) { int ret; - struct btrfs_receive *r = user; - char *full_path = path_cat(r->full_subvol_path, path); + struct btrfs_receive *rctx = user; + char full_path[PATH_MAX]; + + ret = path_cat_out(full_path, rctx->full_subvol_path, path); + if (ret < 0) { + error("mkfile: path invalid: %s", path); + goto out; + } if (g_verbose >= 2) fprintf(stderr, "mkfile %s\n", path); @@ -300,23 +406,27 @@ static int process_mkfile(const char *path, void *user) ret = creat(full_path, 0600); if (ret < 0) { ret = -errno; - fprintf(stderr, "ERROR: mkfile %s failed. %s\n", path, - strerror(-ret)); + error("mkfile %s failed: %s", path, strerror(-ret)); goto out; } close(ret); ret = 0; out: - free(full_path); return ret; } static int process_mkdir(const char *path, void *user) { int ret; - struct btrfs_receive *r = user; - char *full_path = path_cat(r->full_subvol_path, path); + struct btrfs_receive *rctx = user; + char full_path[PATH_MAX]; + + ret = path_cat_out(full_path, rctx->full_subvol_path, path); + if (ret < 0) { + error("mkdir: path invalid: %s", path); + goto out; + } if (g_verbose >= 2) fprintf(stderr, "mkdir %s\n", path); @@ -324,19 +434,24 @@ static int process_mkdir(const char *path, void *user) ret = mkdir(full_path, 0700); if (ret < 0) { ret = -errno; - fprintf(stderr, "ERROR: mkdir %s failed. %s\n", path, - strerror(-ret)); + error("mkdir %s failed: %s", path, strerror(-ret)); } - free(full_path); +out: return ret; } static int process_mknod(const char *path, u64 mode, u64 dev, void *user) { int ret; - struct btrfs_receive *r = user; - char *full_path = path_cat(r->full_subvol_path, path); + struct btrfs_receive *rctx = user; + char full_path[PATH_MAX]; + + ret = path_cat_out(full_path, rctx->full_subvol_path, path); + if (ret < 0) { + error("mknod: path invalid: %s", path); + goto out; + } if (g_verbose >= 2) fprintf(stderr, "mknod %s mode=%llu, dev=%llu\n", @@ -345,19 +460,24 @@ static int process_mknod(const char *path, u64 mode, u64 dev, void *user) ret = mknod(full_path, mode & S_IFMT, dev); if (ret < 0) { ret = -errno; - fprintf(stderr, "ERROR: mknod %s failed. %s\n", path, - strerror(-ret)); + error("mknod %s failed: %s", path, strerror(-ret)); } - free(full_path); +out: return ret; } static int process_mkfifo(const char *path, void *user) { int ret; - struct btrfs_receive *r = user; - char *full_path = path_cat(r->full_subvol_path, path); + struct btrfs_receive *rctx = user; + char full_path[PATH_MAX]; + + ret = path_cat_out(full_path, rctx->full_subvol_path, path); + if (ret < 0) { + error("mkfifo: path invalid: %s", path); + goto out; + } if (g_verbose >= 2) fprintf(stderr, "mkfifo %s\n", path); @@ -365,19 +485,24 @@ static int process_mkfifo(const char *path, void *user) ret = mkfifo(full_path, 0600); if (ret < 0) { ret = -errno; - fprintf(stderr, "ERROR: mkfifo %s failed. %s\n", path, - strerror(-ret)); + error("mkfifo %s failed: %s", path, strerror(-ret)); } - free(full_path); +out: return ret; } static int process_mksock(const char *path, void *user) { int ret; - struct btrfs_receive *r = user; - char *full_path = path_cat(r->full_subvol_path, path); + struct btrfs_receive *rctx = user; + char full_path[PATH_MAX]; + + ret = path_cat_out(full_path, rctx->full_subvol_path, path); + if (ret < 0) { + error("mksock: path invalid: %s", path); + goto out; + } if (g_verbose >= 2) fprintf(stderr, "mksock %s\n", path); @@ -385,19 +510,24 @@ static int process_mksock(const char *path, void *user) ret = mknod(full_path, 0600 | S_IFSOCK, 0); if (ret < 0) { ret = -errno; - fprintf(stderr, "ERROR: mknod %s failed. %s\n", path, - strerror(-ret)); + error("mknod %s failed: %s", path, strerror(-ret)); } - free(full_path); +out: return ret; } static int process_symlink(const char *path, const char *lnk, void *user) { int ret; - struct btrfs_receive *r = user; - char *full_path = path_cat(r->full_subvol_path, path); + struct btrfs_receive *rctx = user; + char full_path[PATH_MAX]; + + ret = path_cat_out(full_path, rctx->full_subvol_path, path); + if (ret < 0) { + error("symlink: path invalid: %s", path); + goto out; + } if (g_verbose >= 2) fprintf(stderr, "symlink %s -> %s\n", path, lnk); @@ -405,20 +535,32 @@ static int process_symlink(const char *path, const char *lnk, void *user) ret = symlink(lnk, full_path); if (ret < 0) { ret = -errno; - fprintf(stderr, "ERROR: symlink %s -> %s failed. %s\n", path, + error("symlink %s -> %s failed: %s", path, lnk, strerror(-ret)); } - free(full_path); +out: return ret; } static int process_rename(const char *from, const char *to, void *user) { int ret; - struct btrfs_receive *r = user; - char *full_from = path_cat(r->full_subvol_path, from); - char *full_to = path_cat(r->full_subvol_path, to); + struct btrfs_receive *rctx = user; + char full_from[PATH_MAX]; + char full_to[PATH_MAX]; + + ret = path_cat_out(full_from, rctx->full_subvol_path, from); + if (ret < 0) { + error("rename: source path invalid: %s", from); + goto out; + } + + ret = path_cat_out(full_to, rctx->full_subvol_path, to); + if (ret < 0) { + error("rename: target path invalid: %s", to); + goto out; + } if (g_verbose >= 2) fprintf(stderr, "rename %s -> %s\n", from, to); @@ -426,21 +568,32 @@ static int process_rename(const char *from, const char *to, void *user) ret = rename(full_from, full_to); if (ret < 0) { ret = -errno; - fprintf(stderr, "ERROR: rename %s -> %s failed. %s\n", from, + error("rename %s -> %s failed: %s", from, to, strerror(-ret)); } - free(full_from); - free(full_to); +out: return ret; } static int process_link(const char *path, const char *lnk, void *user) { int ret; - struct btrfs_receive *r = user; - char *full_path = path_cat(r->full_subvol_path, path); - char *full_link_path = path_cat(r->full_subvol_path, lnk); + struct btrfs_receive *rctx = user; + char full_path[PATH_MAX]; + char full_link_path[PATH_MAX]; + + ret = path_cat_out(full_path, rctx->full_subvol_path, path); + if (ret < 0) { + error("link: source path invalid: %s", full_path); + goto out; + } + + ret = path_cat_out(full_link_path, rctx->full_subvol_path, lnk); + if (ret < 0) { + error("link: target path invalid: %s", full_link_path); + goto out; + } if (g_verbose >= 2) fprintf(stderr, "link %s -> %s\n", path, lnk); @@ -448,12 +601,10 @@ static int process_link(const char *path, const char *lnk, void *user) ret = link(full_link_path, full_path); if (ret < 0) { ret = -errno; - fprintf(stderr, "ERROR: link %s -> %s failed. %s\n", path, - lnk, strerror(-ret)); + error("link %s -> %s failed: %s", path, lnk, strerror(-ret)); } - free(full_path); - free(full_link_path); +out: return ret; } @@ -461,8 +612,14 @@ static int process_link(const char *path, const char *lnk, void *user) static int process_unlink(const char *path, void *user) { int ret; - struct btrfs_receive *r = user; - char *full_path = path_cat(r->full_subvol_path, path); + struct btrfs_receive *rctx = user; + char full_path[PATH_MAX]; + + ret = path_cat_out(full_path, rctx->full_subvol_path, path); + if (ret < 0) { + error("unlink: path invalid: %s", path); + goto out; + } if (g_verbose >= 2) fprintf(stderr, "unlink %s\n", path); @@ -470,19 +627,24 @@ static int process_unlink(const char *path, void *user) ret = unlink(full_path); if (ret < 0) { ret = -errno; - fprintf(stderr, "ERROR: unlink %s failed. %s\n", path, - strerror(-ret)); + error("unlink %s failed. %s", path, strerror(-ret)); } - free(full_path); +out: return ret; } static int process_rmdir(const char *path, void *user) { int ret; - struct btrfs_receive *r = user; - char *full_path = path_cat(r->full_subvol_path, path); + struct btrfs_receive *rctx = user; + char full_path[PATH_MAX]; + + ret = path_cat_out(full_path, rctx->full_subvol_path, path); + if (ret < 0) { + error("rmdir: path invalid: %s", path); + goto out; + } if (g_verbose >= 2) fprintf(stderr, "rmdir %s\n", path); @@ -490,69 +652,71 @@ static int process_rmdir(const char *path, void *user) ret = rmdir(full_path); if (ret < 0) { ret = -errno; - fprintf(stderr, "ERROR: rmdir %s failed. %s\n", path, - strerror(-ret)); + error("rmdir %s failed: %s", path, strerror(-ret)); } - free(full_path); +out: return ret; } - -static int open_inode_for_write(struct btrfs_receive *r, const char *path) +static int open_inode_for_write(struct btrfs_receive *rctx, const char *path) { int ret = 0; - if (r->write_fd != -1) { - if (strcmp(r->write_path, path) == 0) + if (rctx->write_fd != -1) { + if (strcmp(rctx->write_path, path) == 0) goto out; - close(r->write_fd); - r->write_fd = -1; + close(rctx->write_fd); + rctx->write_fd = -1; } - r->write_fd = open(path, O_RDWR); - if (r->write_fd < 0) { + rctx->write_fd = open(path, O_RDWR); + if (rctx->write_fd < 0) { ret = -errno; - fprintf(stderr, "ERROR: open %s failed. %s\n", path, - strerror(-ret)); + error("cannot open %s: %s", path, strerror(-ret)); goto out; } - free(r->write_path); - r->write_path = strdup(path); + strncpy_null(rctx->write_path, path); out: return ret; } -static void close_inode_for_write(struct btrfs_receive *r) +static void close_inode_for_write(struct btrfs_receive *rctx) { - if(r->write_fd == -1) + if(rctx->write_fd == -1) return; - close(r->write_fd); - r->write_fd = -1; - r->write_path[0] = 0; + close(rctx->write_fd); + rctx->write_fd = -1; + rctx->write_path[0] = 0; } static int process_write(const char *path, const void *data, u64 offset, u64 len, void *user) { int ret = 0; - struct btrfs_receive *r = user; - char *full_path = path_cat(r->full_subvol_path, path); + struct btrfs_receive *rctx = user; + char full_path[PATH_MAX]; u64 pos = 0; int w; - ret = open_inode_for_write(r, full_path); + ret = path_cat_out(full_path, rctx->full_subvol_path, path); + if (ret < 0) { + error("write: path invalid: %s", path); + goto out; + } + + ret = open_inode_for_write(rctx, full_path); if (ret < 0) goto out; while (pos < len) { - w = pwrite(r->write_fd, (char*)data + pos, len - pos, + w = pwrite(rctx->write_fd, (char*)data + pos, len - pos, offset + pos); if (w < 0) { ret = -errno; - fprintf(stderr, "ERROR: writing to %s failed. %s\n", + error("writing to %s failed: %s", path, strerror(-ret)); goto out; } @@ -560,7 +724,6 @@ static int process_write(const char *path, const void *data, u64 offset, } out: - free(full_path); return ret; } @@ -570,28 +733,38 @@ static int process_clone(const char *path, u64 offset, u64 len, void *user) { int ret; - struct btrfs_receive *r = user; + struct btrfs_receive *rctx = user; struct btrfs_ioctl_clone_range_args clone_args; struct subvol_info *si = NULL; - char *full_path = path_cat(r->full_subvol_path, path); + char full_path[PATH_MAX]; char *subvol_path = NULL; - char *full_clone_path = NULL; + char full_clone_path[PATH_MAX]; int clone_fd = -1; - ret = open_inode_for_write(r, full_path); + ret = path_cat_out(full_path, rctx->full_subvol_path, path); + if (ret < 0) { + error("clone: source path invalid: %s", path); + goto out; + } + + ret = open_inode_for_write(rctx, full_path); if (ret < 0) goto out; - si = subvol_uuid_search(&r->sus, 0, clone_uuid, clone_ctransid, NULL, - subvol_search_by_received_uuid); - if (!si) { - if (memcmp(clone_uuid, r->cur_subvol->received_uuid, + si = subvol_uuid_search(&rctx->sus, 0, clone_uuid, clone_ctransid, + NULL, + subvol_search_by_received_uuid); + if (IS_ERR_OR_NULL(si)) { + if (memcmp(clone_uuid, rctx->cur_subvol.received_uuid, BTRFS_UUID_SIZE) == 0) { /* TODO check generation of extent */ - subvol_path = strdup(r->cur_subvol->path); + subvol_path = strdup(rctx->cur_subvol_path); } else { - ret = -ENOENT; - fprintf(stderr, "ERROR: did not find source subvol.\n"); + if (!si) + ret = -ENOENT; + else + ret = PTR_ERR(si); + error("clone: did not find source subvol"); goto out; } } else { @@ -610,16 +783,36 @@ static int process_clone(const char *path, u64 offset, u64 len, r->subvol_parent_name); } }*/ - subvol_path = strdup(si->path); + + /* strip the subvolume that we are receiving to from the start of subvol_path */ + if (rctx->full_root_path) { + size_t root_len = strlen(rctx->full_root_path); + size_t sub_len = strlen(si->path); + + if (sub_len > root_len && + strstr(si->path, rctx->full_root_path) == si->path && + si->path[root_len] == '/') { + subvol_path = strdup(si->path + root_len + 1); + } else { + error("clone: source subvol path %s unreachable from %s", + si->path, rctx->full_root_path); + goto out; + } + } else { + subvol_path = strdup(si->path); + } } - full_clone_path = path_cat3(r->root_path, subvol_path, clone_path); + ret = path_cat_out(full_clone_path, subvol_path, clone_path); + if (ret < 0) { + error("clone: target path invalid: %s", clone_path); + goto out; + } - clone_fd = open(full_clone_path, O_RDONLY | O_NOATIME); + clone_fd = openat(rctx->mnt_fd, full_clone_path, O_RDONLY | O_NOATIME); if (clone_fd < 0) { ret = -errno; - fprintf(stderr, "ERROR: failed to open %s. %s\n", - full_clone_path, strerror(-ret)); + error("cannot open %s: %s", full_clone_path, strerror(-ret)); goto out; } @@ -627,10 +820,10 @@ static int process_clone(const char *path, u64 offset, u64 len, clone_args.src_offset = clone_offset; clone_args.src_length = len; clone_args.dest_offset = offset; - ret = ioctl(r->write_fd, BTRFS_IOC_CLONE_RANGE, &clone_args); - if (ret) { + ret = ioctl(rctx->write_fd, BTRFS_IOC_CLONE_RANGE, &clone_args); + if (ret < 0) { ret = -errno; - fprintf(stderr, "ERROR: failed to clone extents to %s\n%s\n", + error("failed to clone extents to %s\n%s", path, strerror(-ret)); goto out; } @@ -640,8 +833,6 @@ out: free(si->path); free(si); } - free(full_path); - free(full_clone_path); free(subvol_path); if (clone_fd != -1) close(clone_fd); @@ -653,8 +844,30 @@ static int process_set_xattr(const char *path, const char *name, const void *data, int len, void *user) { int ret = 0; - struct btrfs_receive *r = user; - char *full_path = path_cat(r->full_subvol_path, path); + struct btrfs_receive *rctx = user; + char full_path[PATH_MAX]; + + ret = path_cat_out(full_path, rctx->full_subvol_path, path); + if (ret < 0) { + error("set_xattr: path invalid: %s", path); + goto out; + } + + if (strcmp("security.capability", name) == 0) { + if (g_verbose >= 3) + fprintf(stderr, "set_xattr: cache capabilities\n"); + if (rctx->cached_capabilities_len) + warning("capabilities set multiple times per file: %s", + full_path); + if (len > sizeof(rctx->cached_capabilities)) { + error("capabilities encoded to %d bytes, buffer too small", + len); + ret = -E2BIG; + goto out; + } + rctx->cached_capabilities_len = len; + memcpy(rctx->cached_capabilities, data, len); + } if (g_verbose >= 2) { fprintf(stderr, "set_xattr %s - name=%s data_len=%d " @@ -665,21 +878,26 @@ static int process_set_xattr(const char *path, const char *name, ret = lsetxattr(full_path, name, data, len, 0); if (ret < 0) { ret = -errno; - fprintf(stderr, "ERROR: lsetxattr %s %s=%.*s failed. %s\n", + error("lsetxattr %s %s=%.*s failed: %s", path, name, len, (char*)data, strerror(-ret)); goto out; } out: - free(full_path); return ret; } static int process_remove_xattr(const char *path, const char *name, void *user) { int ret = 0; - struct btrfs_receive *r = user; - char *full_path = path_cat(r->full_subvol_path, path); + struct btrfs_receive *rctx = user; + char full_path[PATH_MAX]; + + ret = path_cat_out(full_path, rctx->full_subvol_path, path); + if (ret < 0) { + error("remove_xattr: path invalid: %s", path); + goto out; + } if (g_verbose >= 2) { fprintf(stderr, "remove_xattr %s - name=%s\n", @@ -689,21 +907,26 @@ static int process_remove_xattr(const char *path, const char *name, void *user) ret = lremovexattr(full_path, name); if (ret < 0) { ret = -errno; - fprintf(stderr, "ERROR: lremovexattr %s %s failed. %s\n", + error("lremovexattr %s %s failed: %s", path, name, strerror(-ret)); goto out; } out: - free(full_path); return ret; } static int process_truncate(const char *path, u64 size, void *user) { int ret = 0; - struct btrfs_receive *r = user; - char *full_path = path_cat(r->full_subvol_path, path); + struct btrfs_receive *rctx = user; + char full_path[PATH_MAX]; + + ret = path_cat_out(full_path, rctx->full_subvol_path, path); + if (ret < 0) { + error("truncate: path invalid: %s", path); + goto out; + } if (g_verbose >= 2) fprintf(stderr, "truncate %s size=%llu\n", path, size); @@ -711,21 +934,25 @@ static int process_truncate(const char *path, u64 size, void *user) ret = truncate(full_path, size); if (ret < 0) { ret = -errno; - fprintf(stderr, "ERROR: truncate %s failed. %s\n", - path, strerror(-ret)); + error("truncate %s failed: %s", path, strerror(-ret)); goto out; } out: - free(full_path); return ret; } static int process_chmod(const char *path, u64 mode, void *user) { int ret = 0; - struct btrfs_receive *r = user; - char *full_path = path_cat(r->full_subvol_path, path); + struct btrfs_receive *rctx = user; + char full_path[PATH_MAX]; + + ret = path_cat_out(full_path, rctx->full_subvol_path, path); + if (ret < 0) { + error("chmod: path invalid: %s", path); + goto out; + } if (g_verbose >= 2) fprintf(stderr, "chmod %s - mode=0%o\n", path, (int)mode); @@ -733,21 +960,25 @@ static int process_chmod(const char *path, u64 mode, void *user) ret = chmod(full_path, mode); if (ret < 0) { ret = -errno; - fprintf(stderr, "ERROR: chmod %s failed. %s\n", - path, strerror(-ret)); + error("chmod %s failed: %s", path, strerror(-ret)); goto out; } out: - free(full_path); return ret; } static int process_chown(const char *path, u64 uid, u64 gid, void *user) { int ret = 0; - struct btrfs_receive *r = user; - char *full_path = path_cat(r->full_subvol_path, path); + struct btrfs_receive *rctx = user; + char full_path[PATH_MAX]; + + ret = path_cat_out(full_path, rctx->full_subvol_path, path); + if (ret < 0) { + error("chown: path invalid: %s", path); + goto out; + } if (g_verbose >= 2) fprintf(stderr, "chown %s - uid=%llu, gid=%llu\n", path, @@ -756,13 +987,28 @@ static int process_chown(const char *path, u64 uid, u64 gid, void *user) ret = lchown(full_path, uid, gid); if (ret < 0) { ret = -errno; - fprintf(stderr, "ERROR: chown %s failed. %s\n", - path, strerror(-ret)); + error("chown %s failed: %s", path, strerror(-ret)); goto out; } + if (rctx->cached_capabilities_len) { + if (g_verbose >= 2) + fprintf(stderr, "chown: restore capabilities\n"); + ret = lsetxattr(full_path, "security.capability", + rctx->cached_capabilities, + rctx->cached_capabilities_len, 0); + memset(rctx->cached_capabilities, 0, + sizeof(rctx->cached_capabilities)); + rctx->cached_capabilities_len = 0; + if (ret < 0) { + ret = -errno; + error("restoring capabilities %s: %s", + path, strerror(-ret)); + goto out; + } + } + out: - free(full_path); return ret; } @@ -771,10 +1017,16 @@ static int process_utimes(const char *path, struct timespec *at, void *user) { int ret = 0; - struct btrfs_receive *r = user; - char *full_path = path_cat(r->full_subvol_path, path); + struct btrfs_receive *rctx = user; + char full_path[PATH_MAX]; struct timespec tv[2]; + ret = path_cat_out(full_path, rctx->full_subvol_path, path); + if (ret < 0) { + error("utimes: path invalid: %s", path); + goto out; + } + if (g_verbose >= 2) fprintf(stderr, "utimes %s\n", path); @@ -783,16 +1035,29 @@ static int process_utimes(const char *path, struct timespec *at, ret = utimensat(AT_FDCWD, full_path, tv, AT_SYMLINK_NOFOLLOW); if (ret < 0) { ret = -errno; - fprintf(stderr, "ERROR: utimes %s failed. %s\n", + error("utimes %s failed: %s", path, strerror(-ret)); goto out; } out: - free(full_path); return ret; } +static int process_update_extent(const char *path, u64 offset, u64 len, + void *user) +{ + if (g_verbose >= 2) + fprintf(stderr, "update_extent %s: offset=%llu, len=%llu\n", + path, (unsigned long long)offset, + (unsigned long long)len); + + /* + * Sent with BTRFS_SEND_FLAG_NO_FILE_DATA, nothing to do. + */ + + return 0; +} static struct btrfs_send_ops send_ops = { .subvol = process_subvol, @@ -815,195 +1080,311 @@ static struct btrfs_send_ops send_ops = { .chmod = process_chmod, .chown = process_chown, .utimes = process_utimes, + .update_extent = process_update_extent, }; -static int do_receive(struct btrfs_receive *r, const char *tomnt, int r_fd, - u64 max_errors) +static int do_receive(struct btrfs_receive *rctx, const char *tomnt, + char *realmnt, int r_fd, u64 max_errors) { + u64 subvol_id; int ret; char *dest_dir_full_path; + char root_subvol_path[PATH_MAX]; int end = 0; + int iterations = 0; dest_dir_full_path = realpath(tomnt, NULL); if (!dest_dir_full_path) { ret = -errno; - fprintf(stderr, "ERROR: realpath(%s) failed. %s\n", tomnt, - strerror(-ret)); + error("realpath(%s) failed: %s", tomnt, strerror(-ret)); goto out; } - r->dest_dir_fd = open(dest_dir_full_path, O_RDONLY | O_NOATIME); - if (r->dest_dir_fd < 0) { + rctx->dest_dir_fd = open(dest_dir_full_path, O_RDONLY | O_NOATIME); + if (rctx->dest_dir_fd < 0) { ret = -errno; - fprintf(stderr, - "ERROR: failed to open destination directory %s. %s\n", + error("cannot open destination directory %s: %s", dest_dir_full_path, strerror(-ret)); goto out; } - ret = find_mount_root(dest_dir_full_path, &r->root_path); - if (ret < 0) { - fprintf(stderr, - "ERROR: failed to determine mount point for %s: %s\n", - dest_dir_full_path, strerror(-ret)); - ret = -EINVAL; - goto out; + if (realmnt[0]) { + rctx->root_path = realmnt; + } else { + ret = find_mount_root(dest_dir_full_path, &rctx->root_path); + if (ret < 0) { + error("failed to determine mount point for %s: %s", + dest_dir_full_path, strerror(-ret)); + ret = -EINVAL; + goto out; + } + if (ret > 0) { + error("%s doesn't belong to btrfs mount point", + dest_dir_full_path); + ret = -EINVAL; + goto out; + } } - if (ret > 0) { - fprintf(stderr, - "ERROR: %s doesn't belong to btrfs mount point\n", - dest_dir_full_path); - ret = -EINVAL; + rctx->mnt_fd = open(rctx->root_path, O_RDONLY | O_NOATIME); + if (rctx->mnt_fd < 0) { + ret = -errno; + error("cannot open %s: %s", rctx->root_path, strerror(-ret)); goto out; } - r->mnt_fd = open(r->root_path, O_RDONLY | O_NOATIME); - if (r->mnt_fd < 0) { - ret = -errno; - fprintf(stderr, "ERROR: failed to open %s. %s\n", r->root_path, - strerror(-ret)); + + /* + * If we use -m or a default subvol we want to resolve the path to the + * subvolume we're sitting in so that we can adjust the paths of any + * subvols we want to receive in. + */ + ret = btrfs_list_get_path_rootid(rctx->mnt_fd, &subvol_id); + if (ret) + goto out; + + root_subvol_path[0] = 0; + ret = btrfs_subvolid_resolve(rctx->mnt_fd, root_subvol_path, + PATH_MAX, subvol_id); + if (ret) { + error("cannot resolve our subvol path"); goto out; } /* - * find_mount_root returns a root_path that is a subpath of - * dest_dir_full_path. Now get the other part of root_path, - * which is the destination dir relative to root_path. + * Ok we're inside of a subvol off of the root subvol, we need to + * actually set full_root_path. */ - r->dest_dir_path = dest_dir_full_path + strlen(r->root_path); - while (r->dest_dir_path[0] == '/') - r->dest_dir_path++; + if (*root_subvol_path) + rctx->full_root_path = root_subvol_path; - ret = subvol_uuid_search_init(r->mnt_fd, &r->sus); + if (rctx->dest_dir_chroot) { + if (chroot(dest_dir_full_path)) { + ret = -errno; + error("failed to chroot to %s: %s", + dest_dir_full_path, strerror(-ret)); + goto out; + } + if (chdir("/")) { + ret = -errno; + error("failed to chdir to / after chroot: %s", + strerror(-ret)); + goto out; + } + fprintf(stderr, "Chroot to %s\n", dest_dir_full_path); + rctx->root_path = strdup("/"); + rctx->dest_dir_path = rctx->root_path; + } else { + /* + * find_mount_root returns a root_path that is a subpath of + * dest_dir_full_path. Now get the other part of root_path, + * which is the destination dir relative to root_path. + */ + rctx->dest_dir_path = dest_dir_full_path + strlen(rctx->root_path); + while (rctx->dest_dir_path[0] == '/') + rctx->dest_dir_path++; + } + + ret = subvol_uuid_search_init(rctx->mnt_fd, &rctx->sus); if (ret < 0) goto out; while (!end) { - ret = btrfs_read_and_process_send_stream(r_fd, &send_ops, r, - r->honor_end_cmd, + if (rctx->cached_capabilities_len) { + if (g_verbose >= 3) + fprintf(stderr, "clear cached capabilities\n"); + memset(rctx->cached_capabilities, 0, + sizeof(rctx->cached_capabilities)); + rctx->cached_capabilities_len = 0; + } + + ret = btrfs_read_and_process_send_stream(r_fd, &send_ops, + rctx, + rctx->honor_end_cmd, max_errors); - if (ret < 0) - goto out; - if (ret) + if (ret < 0) { + if (ret != -ENODATA) + goto out; + + /* Empty stream is invalid */ + if (iterations == 0) { + error("empty stream is not considered valid"); + ret = -EINVAL; + goto out; + } + + ret = 1; + } + if (ret > 0) end = 1; - close_inode_for_write(r); - ret = finish_subvol(r); + close_inode_for_write(rctx); + ret = finish_subvol(rctx); if (ret < 0) goto out; + + iterations++; } ret = 0; out: - if (r->write_fd != -1) { - close(r->write_fd); - r->write_fd = -1; - } - free(r->root_path); - r->root_path = NULL; - free(r->write_path); - r->write_path = NULL; - free(r->full_subvol_path); - r->full_subvol_path = NULL; - r->dest_dir_path = NULL; - free(dest_dir_full_path); - if (r->cur_subvol) { - free(r->cur_subvol->path); - free(r->cur_subvol); - r->cur_subvol = NULL; + if (rctx->write_fd != -1) { + close(rctx->write_fd); + rctx->write_fd = -1; } - subvol_uuid_search_finit(&r->sus); - if (r->mnt_fd != -1) { - close(r->mnt_fd); - r->mnt_fd = -1; + + if (rctx->root_path != realmnt) + free(rctx->root_path); + rctx->root_path = NULL; + rctx->dest_dir_path = NULL; + free(dest_dir_full_path); + subvol_uuid_search_finit(&rctx->sus); + if (rctx->mnt_fd != -1) { + close(rctx->mnt_fd); + rctx->mnt_fd = -1; } - if (r->dest_dir_fd != -1) { - close(r->dest_dir_fd); - r->dest_dir_fd = -1; + if (rctx->dest_dir_fd != -1) { + close(rctx->dest_dir_fd); + rctx->dest_dir_fd = -1; } + return ret; } -static const struct option long_opts[] = { - { "max-errors", 1, NULL, 'E' }, - { NULL, 0, NULL, 0 } -}; - int cmd_receive(int argc, char **argv) { - int c; char *tomnt = NULL; - char *fromfile = NULL; - struct btrfs_receive r; + char fromfile[PATH_MAX]; + char realmnt[PATH_MAX]; + struct btrfs_receive rctx; int receive_fd = fileno(stdin); u64 max_errors = 1; - int ret; + int dump = 0; + int ret = 0; - memset(&r, 0, sizeof(r)); - r.mnt_fd = -1; - r.write_fd = -1; - r.dest_dir_fd = -1; + memset(&rctx, 0, sizeof(rctx)); + rctx.mnt_fd = -1; + rctx.write_fd = -1; + rctx.dest_dir_fd = -1; + rctx.dest_dir_chroot = 0; + realmnt[0] = 0; + fromfile[0] = 0; + + while (1) { + int c; + enum { GETOPT_VAL_DUMP = 257 }; + static const struct option long_opts[] = { + { "max-errors", required_argument, NULL, 'E' }, + { "chroot", no_argument, NULL, 'C' }, + { "dump", no_argument, NULL, GETOPT_VAL_DUMP }, + { NULL, 0, NULL, 0 } + }; + + c = getopt_long(argc, argv, "Cevf:m:E:", long_opts, NULL); + if (c < 0) + break; - while ((c = getopt_long(argc, argv, "evf:", long_opts, NULL)) != -1) { switch (c) { case 'v': g_verbose++; break; case 'f': - fromfile = optarg; + if (arg_copy_path(fromfile, optarg, sizeof(fromfile))) { + error("input file path too long (%zu)", + strlen(optarg)); + ret = 1; + goto out; + } break; case 'e': - r.honor_end_cmd = 1; + rctx.honor_end_cmd = 1; + break; + case 'C': + rctx.dest_dir_chroot = 1; break; case 'E': max_errors = arg_strtou64(optarg); break; + case 'm': + if (arg_copy_path(realmnt, optarg, sizeof(realmnt))) { + error("mount point path too long (%zu)", + strlen(optarg)); + ret = 1; + goto out; + } + break; + case GETOPT_VAL_DUMP: + dump = 1; + break; case '?': default: - fprintf(stderr, "ERROR: receive args invalid.\n"); + error("receive args invalid"); return 1; } } - if (check_argc_exact(argc - optind, 1)) + if (dump && check_argc_exact(argc - optind, 0)) + usage(cmd_receive_usage); + if (!dump && check_argc_exact(argc - optind, 1)) usage(cmd_receive_usage); tomnt = argv[optind]; - if (fromfile) { + if (fromfile[0]) { receive_fd = open(fromfile, O_RDONLY | O_NOATIME); if (receive_fd < 0) { - fprintf(stderr, "ERROR: failed to open %s\n", fromfile); - return 1; + error("cannot open %s: %m", fromfile); + goto out; } } - ret = do_receive(&r, tomnt, receive_fd, max_errors); + if (dump) { + struct btrfs_dump_send_args dump_args; + + dump_args.root_path[0] = '.'; + dump_args.root_path[1] = '\0'; + dump_args.full_subvol_path[0] = '.'; + dump_args.full_subvol_path[1] = '\0'; + ret = btrfs_read_and_process_send_stream(receive_fd, + &btrfs_print_send_ops, &dump_args, 0, 0); + if (ret < 0) + error("failed to dump the send stream: %s", + strerror(-ret)); + } else { + ret = do_receive(&rctx, tomnt, realmnt, receive_fd, max_errors); + } + + if (receive_fd != fileno(stdin)) + close(receive_fd); +out: return !!ret; } const char * const cmd_receive_usage[] = { - "btrfs receive [-ve] [-f ] ", - "Receive subvolumes from stdin.", - "Receives one or more subvolumes that were previously ", + "btrfs receive [options] \n" + "btrfs receive --dump [options]", + "Receive subvolumes from a stream", + "Receives one or more subvolumes that were previously", "sent with btrfs send. The received subvolumes are stored", - "into .", - "btrfs receive will fail in case a receiving subvolume", + "into MOUNT.", + "The receive will fail in case the receiving subvolume", "already exists. It will also fail in case a previously", - "received subvolume was changed after it was received.", + "received subvolume has been changed after it was received.", "After receiving a subvolume, it is immediately set to", - "read only.\n", - "-v Enable verbose debug output. Each", - " occurrence of this option increases the", - " verbose level more.", - "-f By default, btrfs receive uses stdin", - " to receive the subvolumes. Use this", - " option to specify a file to use instead.", - "-e Terminate after receiving an ", - " in the data stream. Without this option,", - " the receiver terminates only if an error", - " is recognized or on EOF.", - "--max-errors Terminate as soon as N errors happened while", - " processing commands from the send stream.", + "read-only.", + "", + "-v increase verbosity about performed actions", + "-f FILE read the stream from FILE instead of stdin", + "-e terminate after receiving an marker in the stream.", + " Without this option the receiver side terminates only in case", + " of an error on end of file.", + "-C|--chroot confine the process to using chroot", + "-E|--max-errors NERR", + " terminate as soon as NERR errors occur while", + " stream processing commands from the stream.", " Default value is 1. A value of 0 means no limit.", + "-m ROOTMOUNT the root mount point of the destination filesystem.", + " If /proc is not accessible, use this to tell us where", + " this file system is mounted.", + "--dump dump stream metadata, one line per operation,", + " does not require the MOUNT parameter", NULL };