| /* |
| * 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. |
| */ |
| |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <libgen.h> |
| #include <string.h> |
| #include <strings.h> |
| #include <assert.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <inttypes.h> |
| #include <limits.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <uuid/uuid.h> |
| #include <sys/ioctl.h> |
| #include <sys/mnttab.h> |
| #include <libzfs.h> |
| |
| #include "far-rcv/far-rcv.h" |
| |
| |
| struct zfs_rcv_ctx { |
| struct far_rcv_ctx frctx; /* MUST be the first member */ |
| |
| libzfs_handle_t *zfs; |
| |
| char *base_dataset; /* command line option -b */ |
| char *cur_dataset; /* current dataset w/o @... */ |
| char *cur_full_dataset; /* base_dataset + cur_dataset */ |
| char *cur_snapshot; /* @..., including the @ in the front */ |
| |
| char *explicit_dest_subvol; |
| }; |
| |
| |
| static void usage(const char *progname); |
| int main(int argc, char **argv); |
| static int zfs_rcv_finish_subvol(struct far_rcv_ctx *frctx); |
| static int zfs_rcv_process_subvol(struct far_rcv_ctx *frctx, const char *path, |
| const unsigned char *uuid, |
| uint64_t ctransid); |
| static int zfs_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 zfs_rcv_process_subvol_snapshot(struct far_rcv_ctx *frctx, |
| const char *path, int is_snapshot); |
| |
| |
| static void usage(const char *progname) |
| { |
| fprintf(stderr, "%s [-ve] [-f <infile>] [-d <dest>] <dataset_base>\n", |
| progname); |
| char *more[] = { |
| "Receive snapshots from stdin.", |
| "Receives one or more snapshots that were previously sent with btrfs send,", |
| "zfs send -F or a similar sender for the FAR stream format.", |
| "ZFS filesystems are created and mounted as required. The received data is", |
| "stored below the ZFS pool or dataset <dataset_base>.", |
| "-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.", |
| "-d <dest> With <dest> either being filesystem@snapshot or only the name", |
| " of a filesystem, this option overrides the name of received", |
| " filesystems and optionally also the name of the first received", |
| " snapshot.", |
| NULL |
| }; |
| char **pp = more; |
| |
| while (*pp) { |
| fprintf(stderr, "%s\n", *pp); |
| pp++; |
| } |
| } |
| |
| int main(int argc, char **argv) |
| { |
| int c; |
| char *fromfile = NULL; |
| int stream_fd = -1; |
| int ret = 0; |
| int verbose = 0; |
| const char *progname; |
| int did_far_rcv_init = 0; |
| struct zfs_rcv_ctx *zrctx = NULL; |
| int support_xattrs = 1; |
| int honor_end_cmd = 0; |
| |
| progname = basename(argv[0]); |
| zrctx = calloc(1, sizeof(*zrctx)); |
| if (!zrctx) { |
| ret = -ENOMEM; |
| fprintf(stderr, "%s: ERROR: malloc() failed.\n", progname); |
| goto out; |
| } |
| |
| zrctx->zfs = NULL; |
| zrctx->base_dataset = NULL; |
| zrctx->cur_dataset = NULL; |
| zrctx->cur_full_dataset = NULL; |
| zrctx->cur_snapshot = NULL; |
| zrctx->explicit_dest_subvol = NULL; |
| |
| while ((c = getopt(argc, argv, "vexXsSf:d:")) != -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': |
| zrctx->explicit_dest_subvol = optarg; |
| break; |
| case '?': |
| default: |
| fprintf(stderr, "%s: ERROR: args invalid.\n", progname); |
| usage(progname); |
| ret = -EINVAL; |
| goto out; |
| } |
| } |
| |
| 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 <dataset_base>.\n", progname); |
| usage(progname); |
| goto out; |
| } |
| zrctx->base_dataset = argv[optind]; |
| |
| 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 */ |
| } |
| |
| zrctx->zfs = libzfs_init(); |
| |
| did_far_rcv_init = 1; |
| ret = far_rcv_init(&zrctx->frctx); |
| if (ret) { |
| fprintf(stderr, "%s: ERROR: far_rcv_init() failed. %s.\n", |
| progname, strerror(-ret)); |
| goto out; |
| } |
| |
| zrctx->frctx.verbose = verbose; |
| zrctx->frctx.support_xattrs = support_xattrs; |
| zrctx->frctx.honor_end_cmd = honor_end_cmd; |
| zrctx->frctx.ops.finish_subvol = zfs_rcv_finish_subvol; |
| zrctx->frctx.ops.subvol = zfs_rcv_process_subvol; |
| zrctx->frctx.ops.snapshot = zfs_rcv_process_snapshot; |
| |
| ret = far_rcv_mainloop(&zrctx->frctx, stream_fd, "./"); |
| 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(&zrctx->frctx); |
| if (stream_fd >= 0) |
| close(stream_fd); |
| if (zrctx) { |
| free(zrctx->cur_dataset); |
| free(zrctx->cur_full_dataset); |
| free(zrctx->cur_snapshot); |
| } |
| if (zrctx->zfs) |
| libzfs_fini(zrctx->zfs); |
| free(zrctx); |
| exit(-ret); |
| } |
| |
| static int zfs_rcv_finish_subvol(struct far_rcv_ctx *frctx) |
| { |
| struct zfs_rcv_ctx *zrctx = (struct zfs_rcv_ctx *)frctx; |
| int ret = 0; |
| |
| if (zrctx->cur_dataset == NULL) |
| return 0; |
| |
| if (zrctx->cur_snapshot) { |
| int l1 = strlen(zrctx->cur_full_dataset); |
| int l2 = strlen(zrctx->cur_snapshot); |
| char *path = malloc(l1 + l2 + 1); |
| |
| strcpy(path, zrctx->cur_full_dataset); |
| strcat(path, zrctx->cur_snapshot); |
| ret = zfs_snapshot(zrctx->zfs, path, 1, NULL); |
| free(zrctx->cur_snapshot); |
| zrctx->cur_snapshot = NULL; |
| } |
| |
| free(zrctx->cur_dataset); |
| zrctx->cur_dataset = NULL; |
| free(zrctx->cur_full_dataset); |
| zrctx->cur_full_dataset = NULL; |
| |
| return ret; |
| } |
| |
| static int zfs_rcv_process_subvol(struct far_rcv_ctx *frctx, const char *path, |
| const unsigned char *uuid, uint64_t ctransid) |
| { |
| return zfs_rcv_process_subvol_snapshot(frctx, path, 0); |
| } |
| |
| static int zfs_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) |
| { |
| return zfs_rcv_process_subvol_snapshot(frctx, path, 1); |
| } |
| |
| static int zfs_rcv_process_subvol_snapshot(struct far_rcv_ctx *frctx, |
| const char *path, int is_snapshot) |
| { |
| struct zfs_rcv_ctx *zrctx = (struct zfs_rcv_ctx *)frctx; |
| int ret; |
| char *p; |
| zfs_handle_t *zfh = NULL; |
| char *mounted_where = NULL; |
| |
| ret = zfs_rcv_finish_subvol(frctx); |
| if (ret < 0) |
| goto out; |
| |
| assert(zrctx->cur_dataset == NULL); |
| assert(zrctx->cur_full_dataset == NULL); |
| assert(zrctx->cur_snapshot == NULL); |
| if (zrctx->explicit_dest_subvol) { |
| if (zrctx->frctx.verbose) |
| fprintf(stderr, "Override destination, use \"%s\".\n", |
| zrctx->explicit_dest_subvol); |
| zrctx->cur_dataset = strdup(zrctx->explicit_dest_subvol); |
| p = strrchr(zrctx->explicit_dest_subvol, '@'); |
| /* |
| * use (optional) snapshot name only for the first one, |
| * all following ones take the snapshot name out of the |
| * transmitted name, and use the explicit name only for |
| * the name of the filesystem |
| */ |
| if (p) { |
| *p = '\0'; |
| } else { |
| p = strrchr(path, '@'); |
| if (p) |
| zrctx->cur_snapshot = strdup(p); |
| } |
| } else { |
| zrctx->cur_dataset = strdup(path); |
| } |
| |
| p = strrchr(zrctx->cur_dataset, '@'); |
| if (p) { |
| zrctx->cur_snapshot = strdup(p); |
| *p = '\0'; |
| } |
| |
| if (frctx->free_current_base_path) |
| free((void *)frctx->current_base_path); |
| frctx->free_current_base_path = 0; |
| |
| zrctx->cur_full_dataset = far_rcv_path_cat(zrctx->base_dataset, |
| zrctx->cur_dataset); |
| if (is_snapshot) { |
| if (zrctx->cur_snapshot) |
| fprintf(stderr, "At snapshot %s%s.\n", |
| zrctx->cur_dataset, zrctx->cur_snapshot); |
| else /* this will leave with an error message later */ |
| fprintf(stderr, "At snapshot %s.\n", |
| zrctx->cur_dataset); |
| |
| } else { |
| fprintf(stderr, "At subvol %s.\n", zrctx->cur_dataset); |
| } |
| |
| if (is_snapshot) { |
| /* |
| * This means nothing more than that this is an incremental |
| * transfer. |
| * Note that the term "snapshot" in the FAR stream format |
| * stands for incremental transfers while "subvolume" stands |
| * for full transfers. This is not related to ZFS filesystems |
| * and snapshots. |
| * |
| * For incremental transfers, it is enforced that the |
| * target is a snapshot, i.e. that the information is |
| * there which name shall be used for the snapshot. |
| */ |
| if (!zrctx->cur_snapshot) { |
| ret = -EINVAL; |
| fprintf(stderr, |
| "ERROR: no snapshot name \"@...\" for %s.\n", |
| zrctx->cur_dataset); |
| goto out; |
| } |
| |
| /* check that filesystem is existent */ |
| zfh = zfs_open(zrctx->zfs, zrctx->cur_full_dataset, |
| ZFS_TYPE_FILESYSTEM); |
| if (!zfh) { |
| ret = -libzfs_errno(zrctx->zfs); |
| fprintf(stderr, |
| "ERROR: filesystem %s cannot be opened, libzfs_errno = %d, %s.\n", |
| zrctx->cur_full_dataset, -ret, |
| libzfs_error_description(zrctx->zfs)); |
| goto out; |
| } |
| } else { |
| ret = zfs_create(zrctx->zfs, zrctx->cur_full_dataset, |
| ZFS_TYPE_FILESYSTEM, NULL); |
| if (ret && libzfs_errno(zrctx->zfs) != EZFS_EXISTS) { |
| ret = -libzfs_errno(zrctx->zfs); |
| fprintf(stderr, |
| "ERROR: create filesystem %s fails with libzfs_errno %d, %s.\n", |
| zrctx->cur_full_dataset, -ret, |
| libzfs_error_description(zrctx->zfs)); |
| goto out; |
| } else if (ret && libzfs_errno(zrctx->zfs) == EZFS_EXISTS) { |
| /* |
| * This won't work as expected in most cases unless |
| * the existent filesystem is empty. Enforce that |
| * only incrementally sent data can be applied to |
| * existent filesystems. What should we do otherwise, |
| * remove everything in the filesystem at the |
| * beginning, then apply the received data, than |
| * create a snapshot, afterwards do what? |
| */ |
| fprintf(stderr, |
| "ERROR: filesystem %s already exists, only incrementally sent data is supported!\n", |
| zrctx->cur_full_dataset); |
| goto out; |
| } |
| |
| zfh = zfs_open(zrctx->zfs, zrctx->cur_full_dataset, |
| ZFS_TYPE_FILESYSTEM); |
| if (!zfh) { |
| ret = -libzfs_errno(zrctx->zfs); |
| fprintf(stderr, |
| "ERROR: filesystem %s cannot be opened, libzfs_errno = %d, %s.\n", |
| zrctx->cur_full_dataset, -ret, |
| libzfs_error_description(zrctx->zfs)); |
| goto out; |
| } |
| } |
| |
| /* need to mount filesystem if it not there as expected */ |
| if (!zfs_is_mounted(zfh, &mounted_where)) { |
| ret = zfs_mount(zfh, NULL, 0); |
| if (ret) { |
| ret = -libzfs_errno(zrctx->zfs); |
| fprintf(stderr, |
| "ERROR: filesystem %s cannot be mounted, libzfs_errno = %d, %s.\n", |
| zrctx->cur_full_dataset, -ret, |
| libzfs_error_description(zrctx->zfs)); |
| goto out; |
| } |
| if (!zfs_is_mounted(zfh, &mounted_where)) { |
| fprintf(stderr, |
| "ERROR: failed to mount filesystem %s.\n", |
| zrctx->cur_full_dataset); |
| ret = EZFS_MOUNTFAILED; |
| goto out; |
| } |
| } |
| |
| frctx->current_base_path = mounted_where; |
| mounted_where = NULL; |
| frctx->free_current_base_path = 1; |
| |
| out: |
| free(mounted_where); |
| if (zfh != NULL) |
| zfs_close(zfh); |
| return ret; |
| } |