/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 ] [-d ] \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 .", "-v Enable verbose debug output. Each occurrency of this option", " increases the verbose level more.", "-e Terminate after receiving an in the data stream.", " Without this option, the receiver terminates only if an error", " is recognized or on EOF.", "-f By default, stdin is used to receive the subvolumes. Use this", " option to specify a file to use instead.", "-d With 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 .\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; }