| /* |
| * Copyright (C) 2012 Alexander Block. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public |
| * License v2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public |
| * License along with this program; if not, write to the |
| * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
| * Boston, MA 021110-1307, USA. |
| */ |
| |
| #include "kerncompat.h" |
| #include "androidcompat.h" |
| |
| #include <unistd.h> |
| #include <stdint.h> |
| #include <dirent.h> |
| #include <fcntl.h> |
| #include <pthread.h> |
| #include <math.h> |
| #include <ftw.h> |
| #include <sys/wait.h> |
| #include <assert.h> |
| #include <getopt.h> |
| #include <limits.h> |
| |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/ioctl.h> |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <sys/xattr.h> |
| #include <uuid/uuid.h> |
| |
| #include "ctree.h" |
| #include "ioctl.h" |
| #include "commands.h" |
| #include "utils.h" |
| #include "list.h" |
| #include "btrfs-list.h" |
| |
| #include "send.h" |
| #include "send-stream.h" |
| #include "send-utils.h" |
| |
| static int g_verbose = 0; |
| |
| struct btrfs_receive |
| { |
| int mnt_fd; |
| int dest_dir_fd; |
| |
| int write_fd; |
| char write_path[PATH_MAX]; |
| |
| char *root_path; |
| char *dest_dir_path; /* relative to root_path */ |
| char full_subvol_path[PATH_MAX]; |
| char *full_root_path; |
| int dest_dir_chroot; |
| |
| 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) |
| { |
| int ret; |
| int subvol_fd = -1; |
| struct btrfs_ioctl_received_subvol_args rs_args; |
| char uuid_str[BTRFS_UUID_UNPARSED_SIZE]; |
| u64 flags; |
| |
| if (r->cur_subvol_path[0] == 0) |
| return 0; |
| |
| subvol_fd = openat(r->mnt_fd, r->cur_subvol_path, |
| O_RDONLY | O_NOATIME); |
| if (subvol_fd < 0) { |
| ret = -errno; |
| error("cannot open %s: %s\n", |
| r->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; |
| |
| if (g_verbose >= 1) { |
| uuid_unparse((u8*)rs_args.uuid, uuid_str); |
| fprintf(stderr, "BTRFS_IOC_SET_RECEIVED_SUBVOL uuid=%s, " |
| "stransid=%llu\n", uuid_str, rs_args.stransid); |
| } |
| |
| ret = ioctl(subvol_fd, BTRFS_IOC_SET_RECEIVED_SUBVOL, &rs_args); |
| if (ret < 0) { |
| ret = -errno; |
| error("ioctl BTRFS_IOC_SET_RECEIVED_SUBVOL failed: %s", |
| strerror(-ret)); |
| goto out; |
| } |
| r->cur_subvol.rtransid = rs_args.rtransid; |
| |
| ret = ioctl(subvol_fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags); |
| if (ret < 0) { |
| ret = -errno; |
| error("ioctl BTRFS_IOC_SUBVOL_GETFLAGS failed: %s", |
| strerror(-ret)); |
| goto out; |
| } |
| |
| flags |= BTRFS_SUBVOL_RDONLY; |
| |
| ret = ioctl(subvol_fd, BTRFS_IOC_SUBVOL_SETFLAGS, &flags); |
| if (ret < 0) { |
| ret = -errno; |
| error("failed to make subvolume read only: %s", |
| strerror(-ret)); |
| goto out; |
| } |
| |
| ret = 0; |
| |
| out: |
| if (r->cur_subvol_path[0]) { |
| r->cur_subvol_path[0] = 0; |
| } |
| if (subvol_fd != -1) |
| close(subvol_fd); |
| return ret; |
| } |
| |
| static int process_subvol(const char *path, const u8 *uuid, u64 ctransid, |
| void *user) |
| { |
| int ret; |
| struct btrfs_receive *r = user; |
| struct btrfs_ioctl_vol_args args_v1; |
| char uuid_str[BTRFS_UUID_UNPARSED_SIZE]; |
| |
| ret = finish_subvol(r); |
| if (ret < 0) |
| goto out; |
| |
| BUG_ON(r->cur_subvol.path); |
| BUG_ON(r->cur_subvol_path[0]); |
| |
| if (*r->dest_dir_path == 0) { |
| strncpy_null(r->cur_subvol_path, path); |
| } else { |
| ret = path_cat_out(r->cur_subvol_path, r->dest_dir_path, path); |
| if (ret < 0) { |
| error("subvol: path invalid: %s\n", path); |
| goto out; |
| } |
| } |
| ret = path_cat3_out(r->full_subvol_path, r->root_path, |
| r->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; |
| |
| if (g_verbose) { |
| uuid_unparse((u8*)r->cur_subvol.received_uuid, uuid_str); |
| fprintf(stderr, "receiving subvol %s uuid=%s, stransid=%llu\n", |
| path, uuid_str, |
| r->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); |
| if (ret < 0) { |
| ret = -errno; |
| error("creating subvolume %s failed: %s", path, strerror(-ret)); |
| goto out; |
| } |
| |
| out: |
| return ret; |
| } |
| |
| static int process_snapshot(const char *path, const u8 *uuid, u64 ctransid, |
| const u8 *parent_uuid, u64 parent_ctransid, |
| void *user) |
| { |
| int ret; |
| struct btrfs_receive *r = 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); |
| if (ret < 0) |
| goto out; |
| |
| BUG_ON(r->cur_subvol.path); |
| BUG_ON(r->cur_subvol_path[0]); |
| |
| if (*r->dest_dir_path == 0) { |
| strncpy_null(r->cur_subvol_path, path); |
| } else { |
| ret = path_cat_out(r->cur_subvol_path, r->dest_dir_path, path); |
| if (ret < 0) { |
| error("snapshot: path invalid: %s", path); |
| goto out; |
| } |
| } |
| ret = path_cat3_out(r->full_subvol_path, r->root_path, |
| r->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; |
| |
| if (g_verbose) { |
| uuid_unparse((u8*)r->cur_subvol.received_uuid, uuid_str); |
| fprintf(stderr, "receiving snapshot %s uuid=%s, " |
| "ctransid=%llu ", path, uuid_str, |
| r->cur_subvol.stransid); |
| uuid_unparse(parent_uuid, uuid_str); |
| fprintf(stderr, "parent_uuid=%s, parent_ctransid=%llu\n", |
| uuid_str, parent_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); |
| } |
| if (!parent_subvol) { |
| ret = -ENOENT; |
| 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 (r->full_root_path) { |
| size_t root_len; |
| size_t sub_len; |
| |
| root_len = strlen(r->full_root_path); |
| sub_len = strlen(parent_subvol->path); |
| |
| /* First make sure the parent subvol is actually in our path */ |
| if (sub_len < root_len || |
| strstr(parent_subvol->path, r->full_root_path) == NULL) { |
| 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; |
| fprintf(stderr, "ERROR: subvolume %s was modified after it was received.\n", r->subvol_parent_name); |
| goto out; |
| } else { |
| fprintf(stderr, "WARNING: subvolume %s was modified after it was received.\n", r->subvol_parent_name); |
| } |
| }*/ |
| |
| if (*parent_subvol->path == 0) |
| args_v2.fd = dup(r->mnt_fd); |
| else |
| args_v2.fd = openat(r->mnt_fd, parent_subvol->path, |
| O_RDONLY | O_NOATIME); |
| if (args_v2.fd < 0) { |
| ret = -errno; |
| if (errno != ENOENT) |
| error("cannot open %s: %s", |
| parent_subvol->path, strerror(-ret)); |
| else |
| fprintf(stderr, |
| "It seems that you have changed your default " |
| "subvolume or you specify other subvolume to\n" |
| "mount btrfs, try to remount this btrfs filesystem " |
| "with fs tree, and run btrfs receive again!\n"); |
| goto out; |
| } |
| |
| ret = ioctl(r->dest_dir_fd, BTRFS_IOC_SNAP_CREATE_V2, &args_v2); |
| close(args_v2.fd); |
| if (ret < 0) { |
| ret = -errno; |
| error("creating snapshot %s -> %s failed: %s", |
| parent_subvol->path, path, strerror(-ret)); |
| goto out; |
| } |
| |
| out: |
| if (parent_subvol) { |
| free(parent_subvol->path); |
| free(parent_subvol); |
| } |
| return ret; |
| } |
| |
| static int process_mkfile(const char *path, void *user) |
| { |
| int ret; |
| struct btrfs_receive *r = user; |
| char full_path[PATH_MAX]; |
| |
| ret = path_cat_out(full_path, r->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); |
| |
| ret = creat(full_path, 0600); |
| if (ret < 0) { |
| ret = -errno; |
| error("mkfile %s failed: %s", path, strerror(-ret)); |
| goto out; |
| } |
| close(ret); |
| ret = 0; |
| |
| out: |
| return ret; |
| } |
| |
| static int process_mkdir(const char *path, void *user) |
| { |
| int ret; |
| struct btrfs_receive *r = user; |
| char full_path[PATH_MAX]; |
| |
| ret = path_cat_out(full_path, r->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); |
| |
| ret = mkdir(full_path, 0700); |
| if (ret < 0) { |
| ret = -errno; |
| error("mkdir %s failed: %s", path, strerror(-ret)); |
| } |
| |
| 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_MAX]; |
| |
| ret = path_cat_out(full_path, r->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", |
| path, mode, dev); |
| |
| ret = mknod(full_path, mode & S_IFMT, dev); |
| if (ret < 0) { |
| ret = -errno; |
| error("mknod %s failed: %s", path, strerror(-ret)); |
| } |
| |
| out: |
| return ret; |
| } |
| |
| static int process_mkfifo(const char *path, void *user) |
| { |
| int ret; |
| struct btrfs_receive *r = user; |
| char full_path[PATH_MAX]; |
| |
| ret = path_cat_out(full_path, r->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); |
| |
| ret = mkfifo(full_path, 0600); |
| if (ret < 0) { |
| ret = -errno; |
| error("mkfifo %s failed: %s", path, strerror(-ret)); |
| } |
| |
| out: |
| return ret; |
| } |
| |
| static int process_mksock(const char *path, void *user) |
| { |
| int ret; |
| struct btrfs_receive *r = user; |
| char full_path[PATH_MAX]; |
| |
| ret = path_cat_out(full_path, r->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); |
| |
| ret = mknod(full_path, 0600 | S_IFSOCK, 0); |
| if (ret < 0) { |
| ret = -errno; |
| error("mknod %s failed: %s", path, strerror(-ret)); |
| } |
| |
| 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_MAX]; |
| |
| ret = path_cat_out(full_path, r->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); |
| |
| ret = symlink(lnk, full_path); |
| if (ret < 0) { |
| ret = -errno; |
| error("symlink %s -> %s failed: %s", path, |
| lnk, strerror(-ret)); |
| } |
| |
| 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_MAX]; |
| char full_to[PATH_MAX]; |
| |
| ret = path_cat_out(full_from, r->full_subvol_path, from); |
| if (ret < 0) { |
| error("rename: source path invalid: %s", from); |
| goto out; |
| } |
| |
| ret = path_cat_out(full_to, r->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); |
| |
| ret = rename(full_from, full_to); |
| if (ret < 0) { |
| ret = -errno; |
| error("rename %s -> %s failed: %s", from, |
| to, strerror(-ret)); |
| } |
| |
| 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_MAX]; |
| char full_link_path[PATH_MAX]; |
| |
| ret = path_cat_out(full_path, r->full_subvol_path, path); |
| if (ret < 0) { |
| error("link: source path invalid: %s", full_path); |
| goto out; |
| } |
| |
| ret = path_cat_out(full_link_path, r->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); |
| |
| ret = link(full_link_path, full_path); |
| if (ret < 0) { |
| ret = -errno; |
| error("link %s -> %s failed: %s", path, lnk, strerror(-ret)); |
| } |
| |
| out: |
| return ret; |
| } |
| |
| |
| static int process_unlink(const char *path, void *user) |
| { |
| int ret; |
| struct btrfs_receive *r = user; |
| char full_path[PATH_MAX]; |
| |
| ret = path_cat_out(full_path, r->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); |
| |
| ret = unlink(full_path); |
| if (ret < 0) { |
| ret = -errno; |
| error("unlink %s failed. %s", path, strerror(-ret)); |
| } |
| |
| out: |
| return ret; |
| } |
| |
| static int process_rmdir(const char *path, void *user) |
| { |
| int ret; |
| struct btrfs_receive *r = user; |
| char full_path[PATH_MAX]; |
| |
| ret = path_cat_out(full_path, r->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); |
| |
| ret = rmdir(full_path); |
| if (ret < 0) { |
| ret = -errno; |
| error("rmdir %s failed: %s", path, strerror(-ret)); |
| } |
| |
| out: |
| return ret; |
| } |
| |
| static int open_inode_for_write(struct btrfs_receive *r, const char *path) |
| { |
| int ret = 0; |
| |
| if (r->write_fd != -1) { |
| if (strcmp(r->write_path, path) == 0) |
| goto out; |
| close(r->write_fd); |
| r->write_fd = -1; |
| } |
| |
| r->write_fd = open(path, O_RDWR); |
| if (r->write_fd < 0) { |
| ret = -errno; |
| error("cannont open %s: %s", path, strerror(-ret)); |
| goto out; |
| } |
| strncpy_null(r->write_path, path); |
| |
| out: |
| return ret; |
| } |
| |
| static void close_inode_for_write(struct btrfs_receive *r) |
| { |
| if(r->write_fd == -1) |
| return; |
| |
| close(r->write_fd); |
| r->write_fd = -1; |
| r->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_MAX]; |
| u64 pos = 0; |
| int w; |
| |
| ret = path_cat_out(full_path, r->full_subvol_path, path); |
| if (ret < 0) { |
| error("write: path invalid: %s", path); |
| goto out; |
| } |
| |
| ret = open_inode_for_write(r, full_path); |
| if (ret < 0) |
| goto out; |
| |
| while (pos < len) { |
| w = pwrite(r->write_fd, (char*)data + pos, len - pos, |
| offset + pos); |
| if (w < 0) { |
| ret = -errno; |
| error("writing to %s failed: %s\n", |
| path, strerror(-ret)); |
| goto out; |
| } |
| pos += w; |
| } |
| |
| out: |
| return ret; |
| } |
| |
| static int process_clone(const char *path, u64 offset, u64 len, |
| const u8 *clone_uuid, u64 clone_ctransid, |
| const char *clone_path, u64 clone_offset, |
| void *user) |
| { |
| int ret; |
| struct btrfs_receive *r = user; |
| struct btrfs_ioctl_clone_range_args clone_args; |
| struct subvol_info *si = NULL; |
| char full_path[PATH_MAX]; |
| char *subvol_path = NULL; |
| char full_clone_path[PATH_MAX]; |
| int clone_fd = -1; |
| |
| ret = path_cat_out(full_path, r->full_subvol_path, path); |
| if (ret < 0) { |
| error("clone: source path invalid: %s", path); |
| goto out; |
| } |
| |
| ret = open_inode_for_write(r, 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, |
| BTRFS_UUID_SIZE) == 0) { |
| /* TODO check generation of extent */ |
| subvol_path = strdup(r->cur_subvol_path); |
| } else { |
| ret = -ENOENT; |
| error("clone: did not find source subvol"); |
| goto out; |
| } |
| } else { |
| /*if (rs_args.ctransid > rs_args.rtransid) { |
| if (!r->force) { |
| ret = -EINVAL; |
| fprintf(stderr, "ERROR: subvolume %s was " |
| "modified after it was " |
| "received.\n", |
| r->subvol_parent_name); |
| goto out; |
| } else { |
| fprintf(stderr, "WARNING: subvolume %s was " |
| "modified after it was " |
| "received.\n", |
| r->subvol_parent_name); |
| } |
| }*/ |
| subvol_path = strdup(si->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 = openat(r->mnt_fd, full_clone_path, O_RDONLY | O_NOATIME); |
| if (clone_fd < 0) { |
| ret = -errno; |
| error("cannot open %s: %s", full_clone_path, strerror(-ret)); |
| goto out; |
| } |
| |
| clone_args.src_fd = clone_fd; |
| 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 < 0) { |
| ret = -errno; |
| error("failed to clone extents to %s\n%s\n", |
| path, strerror(-ret)); |
| goto out; |
| } |
| |
| out: |
| if (si) { |
| free(si->path); |
| free(si); |
| } |
| free(subvol_path); |
| if (clone_fd != -1) |
| close(clone_fd); |
| return ret; |
| } |
| |
| |
| 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_MAX]; |
| |
| ret = path_cat_out(full_path, r->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 (r->cached_capabilities_len) |
| warning("capabilities set multiple times per file: %s", |
| full_path); |
| if (len > sizeof(r->cached_capabilities)) { |
| error("capabilities encoded to %d bytes, buffer too small", |
| len); |
| ret = -E2BIG; |
| goto out; |
| } |
| r->cached_capabilities_len = len; |
| memcpy(r->cached_capabilities, data, len); |
| } |
| |
| if (g_verbose >= 2) { |
| fprintf(stderr, "set_xattr %s - name=%s data_len=%d " |
| "data=%.*s\n", path, name, len, |
| len, (char*)data); |
| } |
| |
| ret = lsetxattr(full_path, name, data, len, 0); |
| if (ret < 0) { |
| ret = -errno; |
| error("lsetxattr %s %s=%.*s failed: %s", |
| path, name, len, (char*)data, strerror(-ret)); |
| goto out; |
| } |
| |
| out: |
| 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_MAX]; |
| |
| ret = path_cat_out(full_path, r->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", |
| path, name); |
| } |
| |
| ret = lremovexattr(full_path, name); |
| if (ret < 0) { |
| ret = -errno; |
| error("lremovexattr %s %s failed: %s", |
| path, name, strerror(-ret)); |
| goto out; |
| } |
| |
| out: |
| 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_MAX]; |
| |
| ret = path_cat_out(full_path, r->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); |
| |
| ret = truncate(full_path, size); |
| if (ret < 0) { |
| ret = -errno; |
| error("truncate %s failed: %s", path, strerror(-ret)); |
| goto out; |
| } |
| |
| out: |
| 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_MAX]; |
| |
| ret = path_cat_out(full_path, r->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); |
| |
| ret = chmod(full_path, mode); |
| if (ret < 0) { |
| ret = -errno; |
| error("chmod %s failed: %s", path, strerror(-ret)); |
| goto out; |
| } |
| |
| out: |
| 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_MAX]; |
| |
| ret = path_cat_out(full_path, r->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, |
| uid, gid); |
| |
| ret = lchown(full_path, uid, gid); |
| if (ret < 0) { |
| ret = -errno; |
| error("chown %s failed: %s", path, strerror(-ret)); |
| goto out; |
| } |
| |
| if (r->cached_capabilities_len) { |
| if (g_verbose >= 2) |
| fprintf(stderr, "chown: restore capabilities\n"); |
| ret = lsetxattr(full_path, "security.capability", |
| r->cached_capabilities, |
| r->cached_capabilities_len, 0); |
| memset(r->cached_capabilities, 0, |
| sizeof(r->cached_capabilities)); |
| r->cached_capabilities_len = 0; |
| if (ret < 0) { |
| ret = -errno; |
| error("restoring capabilities %s: %s", |
| path, strerror(-ret)); |
| goto out; |
| } |
| } |
| |
| out: |
| return ret; |
| } |
| |
| static int process_utimes(const char *path, struct timespec *at, |
| struct timespec *mt, struct timespec *ct, |
| void *user) |
| { |
| int ret = 0; |
| struct btrfs_receive *r = user; |
| char full_path[PATH_MAX]; |
| struct timespec tv[2]; |
| |
| ret = path_cat_out(full_path, r->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); |
| |
| tv[0] = *at; |
| tv[1] = *mt; |
| ret = utimensat(AT_FDCWD, full_path, tv, AT_SYMLINK_NOFOLLOW); |
| if (ret < 0) { |
| ret = -errno; |
| error("utimes %s failed: %s", |
| path, strerror(-ret)); |
| goto out; |
| } |
| |
| out: |
| 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, |
| .snapshot = process_snapshot, |
| .mkfile = process_mkfile, |
| .mkdir = process_mkdir, |
| .mknod = process_mknod, |
| .mkfifo = process_mkfifo, |
| .mksock = process_mksock, |
| .symlink = process_symlink, |
| .rename = process_rename, |
| .link = process_link, |
| .unlink = process_unlink, |
| .rmdir = process_rmdir, |
| .write = process_write, |
| .clone = process_clone, |
| .set_xattr = process_set_xattr, |
| .remove_xattr = process_remove_xattr, |
| .truncate = process_truncate, |
| .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, |
| 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; |
| |
| dest_dir_full_path = realpath(tomnt, NULL); |
| if (!dest_dir_full_path) { |
| ret = -errno; |
| 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) { |
| ret = -errno; |
| error("cannot open destination directory %s: %s", |
| dest_dir_full_path, strerror(-ret)); |
| goto out; |
| } |
| |
| if (realmnt[0]) { |
| r->root_path = realmnt; |
| } else { |
| ret = find_mount_root(dest_dir_full_path, &r->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; |
| } |
| } |
| r->mnt_fd = open(r->root_path, O_RDONLY | O_NOATIME); |
| if (r->mnt_fd < 0) { |
| ret = -errno; |
| error("cannot open %s: %s", r->root_path, strerror(-ret)); |
| goto out; |
| } |
| |
| /* |
| * 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(r->mnt_fd, &subvol_id); |
| if (ret) { |
| error("cannot resolve our subvolid: %d", |
| ret); |
| goto out; |
| } |
| |
| root_subvol_path[0] = 0; |
| ret = btrfs_subvolid_resolve(r->mnt_fd, root_subvol_path, |
| PATH_MAX, subvol_id); |
| if (ret) { |
| error("cannot resolve our subvol path"); |
| goto out; |
| } |
| |
| /* |
| * Ok we're inside of a subvol off of the root subvol, we need to |
| * actually set full_root_path. |
| */ |
| if (*root_subvol_path) |
| r->full_root_path = root_subvol_path; |
| |
| if (r->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); |
| r->root_path = strdup("/"); |
| r->dest_dir_path = r->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. |
| */ |
| r->dest_dir_path = dest_dir_full_path + strlen(r->root_path); |
| while (r->dest_dir_path[0] == '/') |
| r->dest_dir_path++; |
| } |
| |
| ret = subvol_uuid_search_init(r->mnt_fd, &r->sus); |
| if (ret < 0) |
| goto out; |
| |
| while (!end) { |
| if (r->cached_capabilities_len) { |
| if (g_verbose >= 3) |
| fprintf(stderr, "clear cached capabilities\n"); |
| memset(r->cached_capabilities, 0, |
| sizeof(r->cached_capabilities)); |
| r->cached_capabilities_len = 0; |
| } |
| |
| ret = btrfs_read_and_process_send_stream(r_fd, &send_ops, r, |
| r->honor_end_cmd, |
| max_errors); |
| if (ret < 0) |
| goto out; |
| if (ret) |
| end = 1; |
| |
| close_inode_for_write(r); |
| ret = finish_subvol(r); |
| if (ret < 0) |
| goto out; |
| } |
| ret = 0; |
| |
| out: |
| if (r->write_fd != -1) { |
| close(r->write_fd); |
| r->write_fd = -1; |
| } |
| |
| if (r->root_path != realmnt) |
| free(r->root_path); |
| r->root_path = NULL; |
| r->dest_dir_path = NULL; |
| free(dest_dir_full_path); |
| subvol_uuid_search_finit(&r->sus); |
| if (r->mnt_fd != -1) { |
| close(r->mnt_fd); |
| r->mnt_fd = -1; |
| } |
| if (r->dest_dir_fd != -1) { |
| close(r->dest_dir_fd); |
| r->dest_dir_fd = -1; |
| } |
| |
| return ret; |
| } |
| |
| int cmd_receive(int argc, char **argv) |
| { |
| char *tomnt = NULL; |
| char fromfile[PATH_MAX]; |
| char realmnt[PATH_MAX]; |
| struct btrfs_receive r; |
| int receive_fd = fileno(stdin); |
| u64 max_errors = 1; |
| int ret = 0; |
| |
| memset(&r, 0, sizeof(r)); |
| r.mnt_fd = -1; |
| r.write_fd = -1; |
| r.dest_dir_fd = -1; |
| r.dest_dir_chroot = 0; |
| realmnt[0] = 0; |
| fromfile[0] = 0; |
| |
| while (1) { |
| int c; |
| static const struct option long_opts[] = { |
| { "max-errors", required_argument, NULL, 'E' }, |
| { "chroot", no_argument, NULL, 'C' }, |
| { NULL, 0, NULL, 0 } |
| }; |
| |
| c = getopt_long(argc, argv, "Cevf:m:", long_opts, NULL); |
| if (c < 0) |
| break; |
| |
| switch (c) { |
| case 'v': |
| g_verbose++; |
| break; |
| case 'f': |
| 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; |
| break; |
| case 'C': |
| r.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 '?': |
| default: |
| error("receive args invalid"); |
| return 1; |
| } |
| } |
| |
| if (check_argc_exact(argc - optind, 1)) |
| usage(cmd_receive_usage); |
| |
| tomnt = argv[optind]; |
| |
| if (fromfile[0]) { |
| receive_fd = open(fromfile, O_RDONLY | O_NOATIME); |
| if (receive_fd < 0) { |
| error("cannot open %s: %s", fromfile, strerror(errno)); |
| goto out; |
| } |
| } |
| |
| ret = do_receive(&r, 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 <infile>] [--max-errors <N>] <mount>", |
| "Receive subvolumes from stdin.", |
| "Receives one or more subvolumes that were previously", |
| "sent with btrfs send. The received subvolumes are stored", |
| "into <mount>.", |
| "btrfs receive will fail in case a receiving subvolume", |
| "already exists. It will also fail in case a previously", |
| "received subvolume was 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 <infile> 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 <end cmd>", |
| " in the data stream. Without this option,", |
| " the receiver terminates only if an error", |
| " is recognized or on EOF.", |
| "-C|--chroot confine the process to <mount> using chroot", |
| "--max-errors <N> Terminate as soon as N errors happened while", |
| " processing commands from the send stream.", |
| " Default value is 1. A value of 0 means no limit.", |
| "-m <mountpoint> The root mount point of the destination fs.", |
| " If you do not have /proc use this to tell us where ", |
| " this file system is mounted.", |
| NULL |
| }; |