| /* |
| * Copyright (C) 2012 Alexander Block. |
| * Copyright (C) 2012, 2013 STRATO AG. |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy |
| * of this software and associated documentation files (the "Software"), to |
| * deal in the Software without restriction, including without limitation the |
| * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
| * sell copies of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
| * DEALINGS IN THE SOFTWARE. |
| */ |
| |
| /* |
| * This file contains a receiver for the FAR stream for the target |
| * filesystem Btrfs on Linux. |
| */ |
| |
| #define _GNU_SOURCE |
| #define _POSIX_C_SOURCE 200809 |
| #define _XOPEN_SOURCE 700 |
| #define _BSD_SOURCE |
| |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <libgen.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <inttypes.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <uuid/uuid.h> |
| #include <sys/ioctl.h> |
| #include <mntent.h> |
| #include <btrfs/ctree.h> |
| #include <btrfs/ioctl.h> |
| #include <btrfs/send-utils.h> |
| #include <btrfs/btrfs-list.h> |
| #include "far-rcv/far-rcv.h" |
| |
| #if BTRFS_UUID_SIZE != FAR_UUID_SIZE |
| # error "This won't work, BTRFS_UUID_SIZE != FAR_UUID_SIZE" |
| #endif /* if BTRFS_UUID_SIZE != FAR_UUID_SIZE */ |
| |
| |
| struct btrfs_rcv_ctx { |
| struct far_rcv_ctx frctx; /* MUST be the first member */ |
| |
| int mnt_fd; |
| int dest_dir_fd; |
| struct subvol_uuid_search subvol_uuid_search_ctx; |
| |
| char *mnt_path; |
| char *dest_dir_path; /* relative to mnt_path */ |
| char *full_subvol_path; |
| |
| char *explicit_parent; |
| int free_explicit_parent; |
| char *explicit_dest_subvol; |
| |
| struct subvol_info *cur_subvol; |
| }; |
| |
| |
| static void usage(const char *progname); |
| int main(int argc, char **argv); |
| static int btrfs_rcv_finish_subvol(struct far_rcv_ctx *frctx); |
| static int btrfs_rcv_process_subvol(struct far_rcv_ctx *frctx, const char *path, |
| const unsigned char *uuid, |
| uint64_t ctransid); |
| static int btrfs_rcv_process_snapshot(struct far_rcv_ctx *frctx, |
| const char *path, |
| const unsigned char *uuid, |
| uint64_t ctransid, |
| const unsigned char *parent_uuid, |
| uint64_t parent_ctransid); |
| static int btrfs_rcv_process_clone(struct far_rcv_ctx *frctx, const char *path, |
| uint64_t offset, uint64_t len, |
| const unsigned char *clone_uuid, |
| uint64_t clone_ctransid, |
| const char *clone_path, |
| uint64_t clone_offset); |
| static int btrfs_rcv_find_mount_root(const char *path, char **mount_root); |
| |
| |
| static void usage(const char *progname) |
| { |
| fprintf(stderr, |
| "%s [-ve] [-f <infile>] [-d <dest>] [-p <parent>] <mount>\n", |
| progname); |
| char *more[] = { |
| "Receive subvolumes from stdin.", |
| "Receives one or more subvolumes that were previously sent with btrfs send or", |
| "a similar sender for the FAR stream format.", |
| "The received subvolumes are stored into <mount>.", |
| "This tool will fail in case a received 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.", |
| "-v Enable verbose debug output. Each occurrency of this option", |
| " increases the verbose level more.", |
| "-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.", |
| "-f <infile> By default, stdin is used to receive the subvolumes. Use this", |
| " option to specify a file to use instead.", |
| "-p <parent> Disables the automatic searching for parents if incremental", |
| " streams are received. In case of multiple received snapshots,", |
| " each received snapshot or subvolume becomes the new parent for", |
| " the following received snapshot.", |
| "-d <dest> Overrides the name of received subvolumes and snapshots to", |
| " <dest>. The <mount> parameter can be omited if the -d option is", |
| " used and <dest> is not specified relative to a <mount>", |
| " parameter.", |
| " Note that the -d option causes failures if multiple snapshots", |
| " are received in one stream, since the subvolume is already", |
| " existent in this case and already set to read-only.", |
| NULL |
| }; |
| char **pp = more; |
| |
| while (*pp) { |
| fprintf(stderr, "%s\n", *pp); |
| pp++; |
| } |
| } |
| |
| int main(int argc, char **argv) |
| { |
| int c; |
| char *tomnt = NULL; |
| char *fromfile = NULL; |
| int stream_fd = -1; |
| int ret = 0; |
| int verbose = 0; |
| const char *progname; |
| int did_far_rcv_init = 0; |
| struct btrfs_rcv_ctx *brctx = NULL; |
| char *dest_dir_full_path = NULL; |
| char *name1 = NULL; |
| char *name2 = NULL; |
| int support_xattrs = 1; |
| int honor_end_cmd = 0; |
| |
| progname = basename(argv[0]); |
| brctx = calloc(1, sizeof(*brctx)); |
| if (!brctx) { |
| ret = -ENOMEM; |
| fprintf(stderr, "%s: ERROR: malloc() failed.\n", progname); |
| goto out; |
| } |
| |
| brctx->mnt_fd = -1; |
| brctx->dest_dir_fd = -1; |
| brctx->mnt_path = NULL; |
| brctx->dest_dir_path = NULL; |
| brctx->full_subvol_path = NULL; |
| brctx->explicit_parent = NULL; |
| brctx->free_explicit_parent = 0; |
| brctx->explicit_dest_subvol = NULL; |
| brctx->cur_subvol = NULL; |
| |
| while ((c = getopt(argc, argv, "vexXsSf:d:p:")) != -1) { |
| switch (c) { |
| case 'v': |
| verbose++; |
| break; |
| case 'e': |
| honor_end_cmd = 1; |
| break; |
| case 'x': |
| support_xattrs = 0; |
| break; |
| case 'X': |
| support_xattrs = 1; |
| break; |
| case 's': |
| case 'S': |
| /* ignored */ |
| break; |
| case 'f': |
| fromfile = optarg; |
| break; |
| case 'd': |
| brctx->explicit_dest_subvol = optarg; |
| break; |
| case 'p': |
| brctx->explicit_parent = optarg; |
| break; |
| case '?': |
| default: |
| fprintf(stderr, "%s: ERROR: args invalid.\n", progname); |
| usage(progname); |
| ret = -EINVAL; |
| goto out; |
| } |
| } |
| |
| if (optind == argc && brctx->explicit_dest_subvol) { |
| name1 = strdup(brctx->explicit_dest_subvol); |
| name2 = strdup(brctx->explicit_dest_subvol); |
| tomnt = dirname(name1); |
| brctx->explicit_dest_subvol = basename(name2); |
| } else if (optind + 1 < argc) { |
| ret = -EINVAL; |
| fprintf(stderr, "%s: ERROR: too many arguments.\n", progname); |
| usage(progname); |
| goto out; |
| } else if (optind + 1 != argc) { |
| ret = -EINVAL; |
| fprintf(stderr, "%s: ERROR: need path to subvolume.\n", |
| progname); |
| usage(progname); |
| goto out; |
| } else { |
| tomnt = argv[optind]; |
| } |
| |
| if (brctx->explicit_dest_subvol && |
| !strncmp(tomnt, brctx->explicit_dest_subvol, strlen(tomnt))) { |
| brctx->explicit_dest_subvol += strlen(tomnt); |
| if (tomnt[0] != '\0') |
| while (*brctx->explicit_dest_subvol == '/') |
| brctx->explicit_dest_subvol++; |
| } |
| |
| if (fromfile) { |
| stream_fd = open(fromfile, O_RDONLY); |
| if (stream_fd < 0) { |
| ret = -errno; |
| fprintf(stderr, |
| "%s: ERROR: failed to open \"%s\". %s.\n", |
| progname, fromfile, strerror(errno)); |
| goto out; |
| } |
| } else { |
| stream_fd = 0; /* stdin */ |
| } |
| |
| did_far_rcv_init = 1; |
| ret = far_rcv_init(&brctx->frctx); |
| if (ret) { |
| fprintf(stderr, "%s: ERROR: far_rcv_init() failed. %s.\n", |
| progname, strerror(-ret)); |
| goto out; |
| } |
| |
| brctx->frctx.verbose = verbose; |
| brctx->frctx.support_xattrs = support_xattrs; |
| brctx->frctx.honor_end_cmd = honor_end_cmd; |
| brctx->frctx.ops.finish_subvol = btrfs_rcv_finish_subvol; |
| brctx->frctx.ops.subvol = btrfs_rcv_process_subvol; |
| brctx->frctx.ops.snapshot = btrfs_rcv_process_snapshot; |
| brctx->frctx.ops.clone = btrfs_rcv_process_clone; |
| |
| dest_dir_full_path = realpath(tomnt, NULL); |
| if (!dest_dir_full_path) { |
| ret = -errno; |
| fprintf(stderr, "%s: ERROR: realpath(%s) failed. %s.\n", |
| progname, tomnt, strerror(errno)); |
| goto out; |
| } |
| brctx->dest_dir_fd = open(dest_dir_full_path, O_RDONLY | O_NOATIME); |
| if (brctx->dest_dir_fd < 0) { |
| ret = -errno; |
| fprintf(stderr, |
| "%s: ERROR: failed to open destination directory %s. %s.\n", |
| progname, dest_dir_full_path, strerror(errno)); |
| goto out; |
| } |
| |
| ret = btrfs_rcv_find_mount_root(dest_dir_full_path, &brctx->mnt_path); |
| if (ret < 0) { |
| ret = -EINVAL; |
| fprintf(stderr, |
| "%s: ERROR: failed to determine mount point for %s.\n", |
| progname, dest_dir_full_path); |
| goto out; |
| } |
| brctx->mnt_fd = open(brctx->mnt_path, O_RDONLY | O_NOATIME); |
| if (brctx->mnt_fd < 0) { |
| ret = -errno; |
| fprintf(stderr, "%s: ERROR: failed to open %s. %s.\n", |
| progname, brctx->mnt_path, strerror(errno)); |
| goto out; |
| } |
| |
| /* |
| * btrfs_rcv_find_mount_root returns a mnt_path that is a subpath |
| * of dest_dir_full_path. Now get the other part of mnt_path, |
| * which is the destination dir relative to mnt_path. |
| */ |
| brctx->dest_dir_path = dest_dir_full_path + strlen(brctx->mnt_path); |
| while (*brctx->dest_dir_path == '/') |
| brctx->dest_dir_path++; |
| if (brctx->explicit_parent && |
| !strncmp(brctx->mnt_path, brctx->explicit_parent, |
| strlen(brctx->mnt_path))) { |
| brctx->explicit_parent += strlen(brctx->mnt_path); |
| while (*brctx->explicit_parent == '/') |
| brctx->explicit_parent++; |
| } |
| |
| ret = subvol_uuid_search_init(brctx->mnt_fd, |
| &brctx->subvol_uuid_search_ctx); |
| if (ret < 0) { |
| fprintf(stderr, |
| "%s: ERROR, subvol_uuid_search_init() failed with %d.\n", |
| progname, ret); |
| goto out; |
| } |
| |
| ret = far_rcv_mainloop(&brctx->frctx, stream_fd, "./"); |
| subvol_uuid_search_finit(&brctx->subvol_uuid_search_ctx); |
| if (ret) { |
| fprintf(stderr, |
| "%s: ERROR, far_rcv_mainloop() failed with %s.\n", |
| progname, strerror(-ret)); |
| goto out; |
| } |
| |
| out: |
| if (did_far_rcv_init) |
| far_rcv_deinit(&brctx->frctx); |
| if (stream_fd >= 0) |
| close(stream_fd); |
| free(dest_dir_full_path); |
| free(name1); |
| free(name2); |
| if (brctx) { |
| if (brctx->mnt_fd >= 0) |
| close(brctx->mnt_fd); |
| if (brctx->dest_dir_fd >= 0) |
| close(brctx->dest_dir_fd); |
| free(brctx->mnt_path); |
| free(brctx->full_subvol_path); |
| if (brctx->cur_subvol) { |
| free(brctx->cur_subvol->path); |
| free(brctx->cur_subvol); |
| } |
| if (brctx->free_explicit_parent) |
| free(brctx->explicit_parent); |
| } |
| free(brctx); |
| exit(-ret); |
| } |
| |
| static int btrfs_rcv_finish_subvol(struct far_rcv_ctx *frctx) |
| { |
| struct btrfs_rcv_ctx *brctx = (struct btrfs_rcv_ctx *)frctx; |
| int ret; |
| int subvol_fd = -1; |
| int info_fd = -1; |
| struct btrfs_ioctl_received_subvol_args rs_args; |
| char uuid_str[128]; |
| uint64_t flags; |
| |
| if (brctx->cur_subvol == NULL) |
| return 0; |
| |
| subvol_fd = openat(brctx->mnt_fd, brctx->cur_subvol->path, |
| O_RDONLY | O_NOATIME); |
| if (subvol_fd < 0) { |
| ret = -errno; |
| fprintf(stderr, "ERROR: open %s failed. %s.\n", |
| brctx->cur_subvol->path, strerror(-ret)); |
| goto out; |
| } |
| |
| memset(&rs_args, 0, sizeof(rs_args)); |
| memcpy(rs_args.uuid, brctx->cur_subvol->received_uuid, BTRFS_UUID_SIZE); |
| rs_args.stransid = brctx->cur_subvol->stransid; |
| |
| if (brctx->frctx.verbose >= 1) { |
| uuid_unparse((u8*)rs_args.uuid, uuid_str); |
| fprintf(stderr, |
| "BTRFS_IOC_SET_RECEIVED_SUBVOL uuid=%s, stransid=%" PRIu64 ".\n", |
| uuid_str, (uint64_t)rs_args.stransid); |
| } |
| |
| 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", |
| strerror(-ret)); |
| goto out; |
| } |
| brctx->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", |
| strerror(-ret)); |
| goto out; |
| } |
| |
| flags |= BTRFS_SUBVOL_RDONLY; |
| |
| 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)); |
| goto out; |
| } |
| |
| ret = btrfs_list_get_path_rootid(subvol_fd, |
| &brctx->cur_subvol->root_id); |
| if (ret < 0) |
| goto out; |
| subvol_uuid_search_add(&brctx->subvol_uuid_search_ctx, |
| brctx->cur_subvol); |
| brctx->cur_subvol = NULL; |
| ret = 0; |
| |
| out: |
| if (brctx->cur_subvol) { |
| free(brctx->cur_subvol->path); |
| free(brctx->cur_subvol); |
| brctx->cur_subvol = NULL; |
| } |
| |
| if (subvol_fd != -1) |
| close(subvol_fd); |
| if (info_fd != -1) |
| close(info_fd); |
| return ret; |
| } |
| |
| static int btrfs_rcv_process_subvol(struct far_rcv_ctx *frctx, const char *path, |
| const unsigned char *uuid, |
| uint64_t ctransid) |
| { |
| struct btrfs_rcv_ctx *brctx = (struct btrfs_rcv_ctx *)frctx; |
| int ret; |
| struct btrfs_ioctl_vol_args args_v1; |
| char uuid_str[128]; |
| const char *const orig_path = path; |
| |
| ret = btrfs_rcv_finish_subvol(frctx); |
| if (ret < 0) |
| goto out; |
| |
| brctx->cur_subvol = calloc(1, sizeof(*brctx->cur_subvol)); |
| if (!brctx->cur_subvol) { |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| if (brctx->explicit_dest_subvol) { |
| if (brctx->frctx.verbose) |
| fprintf(stderr, "Override destination, use \"%s\".\n", |
| brctx->explicit_dest_subvol); |
| path = brctx->explicit_dest_subvol; |
| } |
| if (strlen(brctx->dest_dir_path) == 0) |
| brctx->cur_subvol->path = strdup(path); |
| else |
| brctx->cur_subvol->path = path_cat(brctx->dest_dir_path, path); |
| free(brctx->full_subvol_path); |
| brctx->full_subvol_path = path_cat3(brctx->mnt_path, |
| brctx->dest_dir_path, path); |
| if (frctx->free_current_base_path) |
| free((void *)frctx->current_base_path); |
| frctx->current_base_path = brctx->full_subvol_path; |
| frctx->free_current_base_path = 0; |
| |
| if (brctx->explicit_dest_subvol) |
| fprintf(stderr, "At subvol %s (orig name was %s).\n", path, |
| orig_path); |
| else |
| fprintf(stderr, "At subvol %s.\n", path); |
| |
| memcpy(brctx->cur_subvol->received_uuid, uuid, BTRFS_UUID_SIZE); |
| brctx->cur_subvol->stransid = ctransid; |
| |
| if (brctx->frctx.verbose) { |
| uuid_unparse((u8*)brctx->cur_subvol->received_uuid, uuid_str); |
| fprintf(stderr, |
| "receiving subvol %s uuid=%s, stransid=%" PRIu64 ".\n", |
| path, uuid_str, (uint64_t)brctx->cur_subvol->stransid); |
| } |
| |
| memset(&args_v1, 0, sizeof(args_v1)); |
| assert(strlen(path) < sizeof(args_v1.name)); |
| strcpy(args_v1.name, path); |
| ret = ioctl(brctx->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)); |
| goto out; |
| } |
| |
| out: |
| return ret; |
| } |
| |
| static int btrfs_rcv_process_snapshot(struct far_rcv_ctx *frctx, |
| const char *path, |
| const unsigned char *uuid, |
| uint64_t ctransid, |
| const unsigned char *parent_uuid, |
| uint64_t parent_ctransid) |
| { |
| struct btrfs_rcv_ctx *brctx = (struct btrfs_rcv_ctx *)frctx; |
| int ret; |
| char uuid_str[128]; |
| struct btrfs_ioctl_vol_args_v2 args_v2; |
| struct subvol_info *parent_subvol = NULL; |
| const char *const orig_path = path; |
| |
| ret = btrfs_rcv_finish_subvol(frctx); |
| if (ret < 0) |
| goto out; |
| |
| brctx->cur_subvol = calloc(1, sizeof(*brctx->cur_subvol)); |
| if (!brctx->cur_subvol) { |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| if (brctx->explicit_dest_subvol) { |
| if (brctx->frctx.verbose) |
| fprintf(stderr, "Override destination, use \"%s\".\n", |
| brctx->explicit_dest_subvol); |
| path = brctx->explicit_dest_subvol; |
| } |
| if (strlen(brctx->dest_dir_path) == 0) |
| brctx->cur_subvol->path = strdup(path); |
| else |
| brctx->cur_subvol->path = path_cat(brctx->dest_dir_path, path); |
| free(brctx->full_subvol_path); |
| brctx->full_subvol_path = path_cat3(brctx->mnt_path, |
| brctx->dest_dir_path, path); |
| if (frctx->free_current_base_path) |
| free((void *)frctx->current_base_path); |
| frctx->current_base_path = brctx->full_subvol_path; |
| frctx->free_current_base_path = 0; |
| |
| if (brctx->explicit_dest_subvol) |
| fprintf(stderr, "At snapshot %s (orig name was %s).\n", path, |
| orig_path); |
| else |
| fprintf(stderr, "At snapshot %s.\n", path); |
| |
| memcpy(brctx->cur_subvol->received_uuid, uuid, BTRFS_UUID_SIZE); |
| brctx->cur_subvol->stransid = ctransid; |
| |
| if (brctx->frctx.verbose) { |
| uuid_unparse((unsigned char*)brctx->cur_subvol->received_uuid, |
| uuid_str); |
| fprintf(stderr, |
| "receiving snapshot %s uuid=%s, ctransid=%" PRIu64 ", ", |
| path, uuid_str, (uint64_t)brctx->cur_subvol->stransid); |
| uuid_unparse(parent_uuid, uuid_str); |
| fprintf(stderr, |
| "parent_uuid=%s, parent_ctransid=%" PRIu64 ".\n", |
| uuid_str, (uint64_t)parent_ctransid); |
| } |
| |
| memset(&args_v2, 0, sizeof(args_v2)); |
| assert(strlen(path) < sizeof(args_v2.name)); |
| strcpy(args_v2.name, path); |
| |
| if (brctx->explicit_parent) { |
| if (brctx->frctx.verbose) |
| fprintf(stderr, "Override parent, use \"%s\".\n", |
| brctx->explicit_parent); |
| parent_subvol = subvol_uuid_search( |
| &brctx->subvol_uuid_search_ctx, 0, |
| NULL, 0, brctx->explicit_parent, |
| subvol_search_by_path); |
| } else { |
| parent_subvol = subvol_uuid_search( |
| &brctx->subvol_uuid_search_ctx, 0, |
| parent_uuid, parent_ctransid, NULL, |
| subvol_search_by_received_uuid); |
| } |
| if (!parent_subvol) { |
| fprintf(stderr, "ERROR: could not find parent subvolume.\n"); |
| goto out; |
| } |
| |
| /*if (rs_args.ctransid > rs_args.rtransid) { |
| if (!brctx->force) { |
| ret = -EINVAL; |
| fprintf(stderr, |
| "ERROR: subvolume %s was modified after it was received.\n", |
| brctx->subvol_parent_name); |
| goto out; |
| } else { |
| fprintf(stderr, |
| "WARNING: subvolume %s was modified after it was received.\n", |
| brctx->subvol_parent_name); |
| } |
| }*/ |
| |
| args_v2.fd = openat(brctx->mnt_fd, parent_subvol->path, |
| O_RDONLY | O_NOATIME); |
| if (args_v2.fd < 0) { |
| ret = -errno; |
| fprintf(stderr, "ERROR: open %s failed. %s.\n", |
| parent_subvol->path, strerror(-ret)); |
| goto out; |
| } |
| |
| ret = ioctl(brctx->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)); |
| goto out; |
| } |
| |
| if (brctx->explicit_parent) { |
| if (brctx->free_explicit_parent) |
| free(brctx->explicit_parent); |
| brctx->explicit_parent = strdup(brctx->cur_subvol->path); |
| brctx->free_explicit_parent = 1; |
| } |
| |
| out: |
| #if defined (BTRFS_UUID_TREE_OBJECTID) /* hacky way to detect the new |
| * interface */ |
| if (parent_subvol) { |
| free(parent_subvol->path); |
| free(parent_subvol); |
| } |
| #endif /* defined (BTRFS_UUID_TREE_OBJECTID) */ |
| return ret; |
| } |
| |
| static int btrfs_rcv_process_clone(struct far_rcv_ctx *frctx, const char *path, |
| uint64_t offset, uint64_t len, |
| const unsigned char *clone_uuid, |
| uint64_t clone_ctransid, |
| const char *clone_path, |
| uint64_t clone_offset) |
| { |
| struct btrfs_rcv_ctx *brctx = (struct btrfs_rcv_ctx *)frctx; |
| int ret; |
| struct btrfs_ioctl_clone_range_args clone_args; |
| struct subvol_info *si = NULL; |
| char *full_path = path_cat(brctx->full_subvol_path, path); |
| char *subvol_path = NULL; |
| char *full_clone_path = NULL; |
| int clone_fd = -1; |
| |
| ret = frctx->ops.open_inode_for_write(frctx, full_path); |
| if (ret < 0) |
| goto out; |
| |
| si = subvol_uuid_search(&brctx->subvol_uuid_search_ctx, 0, clone_uuid, |
| clone_ctransid, NULL, |
| subvol_search_by_received_uuid); |
| if (!si) { |
| if (memcmp(clone_uuid, brctx->cur_subvol->received_uuid, |
| BTRFS_UUID_SIZE) == 0) { |
| /* TODO check generation of extent */ |
| subvol_path = strdup(brctx->cur_subvol->path); |
| } else { |
| ret = -ENOENT; |
| fprintf(stderr, "ERROR: did not find source subvol.\n"); |
| goto out; |
| } |
| } else { |
| /*if (rs_args.ctransid > rs_args.rtransid) { |
| if (!brctx->force) { |
| ret = -EINVAL; |
| fprintf(stderr, |
| "ERROR: subvolume %s was modified after it was received.\n", |
| brctx->subvol_parent_name); |
| goto out; |
| } else { |
| fprintf(stderr, |
| "WARNING: subvolume %s was modified after it was received.\n", |
| brctx->subvol_parent_name); |
| } |
| }*/ |
| subvol_path = strdup(si->path); |
| } |
| |
| full_clone_path = path_cat3(brctx->mnt_path, subvol_path, clone_path); |
| |
| clone_fd = open(full_clone_path, O_RDONLY | O_NOATIME); |
| if (clone_fd < 0) { |
| ret = -errno; |
| fprintf(stderr, "ERROR: failed to open clone src %s. %s.\n", |
| 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(brctx->frctx.write_fd, BTRFS_IOC_CLONE_RANGE, &clone_args); |
| if (ret) { |
| ret = -errno; |
| fprintf(stderr, "ERROR: failed to clone extents to %s.\n%s.\n", |
| path, strerror(-ret)); |
| goto out; |
| } |
| |
| out: |
| #if defined (BTRFS_UUID_TREE_OBJECTID) /* hacky way to detect the new |
| * interface */ |
| if (si) { |
| free(si->path); |
| free(si); |
| } |
| #endif /* defined (BTRFS_UUID_TREE_OBJECTID) */ |
| free(full_path); |
| free(full_clone_path); |
| free(subvol_path); |
| if (clone_fd != -1) |
| close(clone_fd); |
| return ret; |
| } |
| |
| /* |
| * TODO: this function is a copy of btrfs-progs/cmds-send.c find_mount_root() |
| * since it is not exported there and since adding it to the list of exported |
| * functions in libbtrfs either exported too much (util.c) or didn't look |
| * proper (send-utils.c which is then also used by cmds-subvolume.c). |
| */ |
| static int btrfs_rcv_find_mount_root(const char *path, char **mount_root) |
| { |
| FILE *mnttab; |
| int fd; |
| struct mntent *ent; |
| int len; |
| int longest_matchlen = 0; |
| char *longest_match = NULL; |
| |
| fd = open(path, O_RDONLY | O_NOATIME); |
| if (fd < 0) |
| return -errno; |
| close(fd); |
| |
| mnttab = fopen("/proc/mounts", "r"); |
| while ((ent = getmntent(mnttab))) { |
| len = strlen(ent->mnt_dir); |
| if (strncmp(ent->mnt_dir, path, len) == 0) { |
| /* match found */ |
| if (longest_matchlen < len) { |
| free(longest_match); |
| longest_matchlen = len; |
| longest_match = strdup(ent->mnt_dir); |
| } |
| } |
| } |
| fclose(mnttab); |
| |
| *mount_root = realpath(longest_match, NULL); |
| free(longest_match); |
| |
| return 0; |
| } |