| /* |
| * CDDL HEADER START |
| * |
| * The contents of this file are subject to the terms of the |
| * Common Development and Distribution License (the "License"). |
| * You may not use this file except in compliance with the License. |
| * |
| * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
| * or http://www.opensolaris.org/os/licensing. |
| * See the License for the specific language governing permissions |
| * and limitations under the License. |
| * |
| * When distributing Covered Code, include this CDDL HEADER in each |
| * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
| * If applicable, add the following below this CDDL HEADER, with the |
| * fields enclosed by brackets "[]" replaced with your own identifying |
| * information: Portions Copyright [yyyy] [name of copyright owner] |
| * |
| * CDDL HEADER END |
| */ |
| |
| /* |
| * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 2011, 2020 by Delphix. All rights reserved. |
| * Copyright (c) 2012, Joyent, Inc. All rights reserved. |
| * Copyright (c) 2012 Pawel Jakub Dawidek <pawel@dawidek.net>. |
| * All rights reserved |
| * Copyright (c) 2013 Steven Hartland. All rights reserved. |
| * Copyright 2015, OmniTI Computer Consulting, Inc. All rights reserved. |
| * Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com> |
| * Copyright (c) 2018, loli10K <ezomori.nozomu@gmail.com>. All rights reserved. |
| * Copyright (c) 2019 Datto Inc. |
| */ |
| |
| #include <assert.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <libintl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <strings.h> |
| #include <unistd.h> |
| #include <stddef.h> |
| #include <fcntl.h> |
| #include <sys/mount.h> |
| #include <sys/mntent.h> |
| #include <sys/mnttab.h> |
| #include <sys/avl.h> |
| #include <sys/debug.h> |
| #include <sys/stat.h> |
| #include <pthread.h> |
| #include <umem.h> |
| #include <time.h> |
| |
| #include <libzfs.h> |
| #include <libzfs_core.h> |
| #include <libzutil.h> |
| |
| #include "zfs_namecheck.h" |
| #include "zfs_prop.h" |
| #include "zfs_fletcher.h" |
| #include "libzfs_impl.h" |
| #include <cityhash.h> |
| #include <zlib.h> |
| #include <sys/zio_checksum.h> |
| #include <sys/dsl_crypt.h> |
| #include <sys/ddt.h> |
| #include <sys/socket.h> |
| #include <sys/sha2.h> |
| |
| static int zfs_receive_impl(libzfs_handle_t *, const char *, const char *, |
| recvflags_t *, int, const char *, nvlist_t *, avl_tree_t *, char **, |
| const char *, nvlist_t *); |
| static int guid_to_name_redact_snaps(libzfs_handle_t *hdl, const char *parent, |
| uint64_t guid, boolean_t bookmark_ok, uint64_t *redact_snap_guids, |
| uint64_t num_redact_snaps, char *name); |
| static int guid_to_name(libzfs_handle_t *, const char *, |
| uint64_t, boolean_t, char *); |
| |
| typedef struct progress_arg { |
| zfs_handle_t *pa_zhp; |
| int pa_fd; |
| boolean_t pa_parsable; |
| boolean_t pa_estimate; |
| int pa_verbosity; |
| boolean_t pa_astitle; |
| boolean_t pa_progress; |
| uint64_t pa_size; |
| } progress_arg_t; |
| |
| static int |
| dump_record(dmu_replay_record_t *drr, void *payload, int payload_len, |
| zio_cksum_t *zc, int outfd) |
| { |
| ASSERT3U(offsetof(dmu_replay_record_t, drr_u.drr_checksum.drr_checksum), |
| ==, sizeof (dmu_replay_record_t) - sizeof (zio_cksum_t)); |
| fletcher_4_incremental_native(drr, |
| offsetof(dmu_replay_record_t, drr_u.drr_checksum.drr_checksum), zc); |
| if (drr->drr_type != DRR_BEGIN) { |
| ASSERT(ZIO_CHECKSUM_IS_ZERO(&drr->drr_u. |
| drr_checksum.drr_checksum)); |
| drr->drr_u.drr_checksum.drr_checksum = *zc; |
| } |
| fletcher_4_incremental_native(&drr->drr_u.drr_checksum.drr_checksum, |
| sizeof (zio_cksum_t), zc); |
| if (write(outfd, drr, sizeof (*drr)) == -1) |
| return (errno); |
| if (payload_len != 0) { |
| fletcher_4_incremental_native(payload, payload_len, zc); |
| if (write(outfd, payload, payload_len) == -1) |
| return (errno); |
| } |
| return (0); |
| } |
| |
| /* |
| * Routines for dealing with the AVL tree of fs-nvlists |
| */ |
| typedef struct fsavl_node { |
| avl_node_t fn_node; |
| nvlist_t *fn_nvfs; |
| char *fn_snapname; |
| uint64_t fn_guid; |
| } fsavl_node_t; |
| |
| static int |
| fsavl_compare(const void *arg1, const void *arg2) |
| { |
| const fsavl_node_t *fn1 = (const fsavl_node_t *)arg1; |
| const fsavl_node_t *fn2 = (const fsavl_node_t *)arg2; |
| |
| return (TREE_CMP(fn1->fn_guid, fn2->fn_guid)); |
| } |
| |
| /* |
| * Given the GUID of a snapshot, find its containing filesystem and |
| * (optionally) name. |
| */ |
| static nvlist_t * |
| fsavl_find(avl_tree_t *avl, uint64_t snapguid, char **snapname) |
| { |
| fsavl_node_t fn_find; |
| fsavl_node_t *fn; |
| |
| fn_find.fn_guid = snapguid; |
| |
| fn = avl_find(avl, &fn_find, NULL); |
| if (fn) { |
| if (snapname) |
| *snapname = fn->fn_snapname; |
| return (fn->fn_nvfs); |
| } |
| return (NULL); |
| } |
| |
| static void |
| fsavl_destroy(avl_tree_t *avl) |
| { |
| fsavl_node_t *fn; |
| void *cookie; |
| |
| if (avl == NULL) |
| return; |
| |
| cookie = NULL; |
| while ((fn = avl_destroy_nodes(avl, &cookie)) != NULL) |
| free(fn); |
| avl_destroy(avl); |
| free(avl); |
| } |
| |
| /* |
| * Given an nvlist, produce an avl tree of snapshots, ordered by guid |
| */ |
| static avl_tree_t * |
| fsavl_create(nvlist_t *fss) |
| { |
| avl_tree_t *fsavl; |
| nvpair_t *fselem = NULL; |
| |
| if ((fsavl = malloc(sizeof (avl_tree_t))) == NULL) |
| return (NULL); |
| |
| avl_create(fsavl, fsavl_compare, sizeof (fsavl_node_t), |
| offsetof(fsavl_node_t, fn_node)); |
| |
| while ((fselem = nvlist_next_nvpair(fss, fselem)) != NULL) { |
| nvlist_t *nvfs, *snaps; |
| nvpair_t *snapelem = NULL; |
| |
| nvfs = fnvpair_value_nvlist(fselem); |
| snaps = fnvlist_lookup_nvlist(nvfs, "snaps"); |
| |
| while ((snapelem = |
| nvlist_next_nvpair(snaps, snapelem)) != NULL) { |
| fsavl_node_t *fn; |
| uint64_t guid; |
| |
| guid = fnvpair_value_uint64(snapelem); |
| if ((fn = malloc(sizeof (fsavl_node_t))) == NULL) { |
| fsavl_destroy(fsavl); |
| return (NULL); |
| } |
| fn->fn_nvfs = nvfs; |
| fn->fn_snapname = nvpair_name(snapelem); |
| fn->fn_guid = guid; |
| |
| /* |
| * Note: if there are multiple snaps with the |
| * same GUID, we ignore all but one. |
| */ |
| avl_index_t where = 0; |
| if (avl_find(fsavl, fn, &where) == NULL) |
| avl_insert(fsavl, fn, where); |
| else |
| free(fn); |
| } |
| } |
| |
| return (fsavl); |
| } |
| |
| /* |
| * Routines for dealing with the giant nvlist of fs-nvlists, etc. |
| */ |
| typedef struct send_data { |
| /* |
| * assigned inside every recursive call, |
| * restored from *_save on return: |
| * |
| * guid of fromsnap snapshot in parent dataset |
| * txg of fromsnap snapshot in current dataset |
| * txg of tosnap snapshot in current dataset |
| */ |
| |
| uint64_t parent_fromsnap_guid; |
| uint64_t fromsnap_txg; |
| uint64_t tosnap_txg; |
| |
| /* the nvlists get accumulated during depth-first traversal */ |
| nvlist_t *parent_snaps; |
| nvlist_t *fss; |
| nvlist_t *snapprops; |
| nvlist_t *snapholds; /* user holds */ |
| |
| /* send-receive configuration, does not change during traversal */ |
| const char *fsname; |
| const char *fromsnap; |
| const char *tosnap; |
| boolean_t recursive; |
| boolean_t raw; |
| boolean_t doall; |
| boolean_t replicate; |
| boolean_t skipmissing; |
| boolean_t verbose; |
| boolean_t backup; |
| boolean_t seenfrom; |
| boolean_t seento; |
| boolean_t holds; /* were holds requested with send -h */ |
| boolean_t props; |
| |
| /* |
| * The header nvlist is of the following format: |
| * { |
| * "tosnap" -> string |
| * "fromsnap" -> string (if incremental) |
| * "fss" -> { |
| * id -> { |
| * |
| * "name" -> string (full name; for debugging) |
| * "parentfromsnap" -> number (guid of fromsnap in parent) |
| * |
| * "props" -> { name -> value (only if set here) } |
| * "snaps" -> { name (lastname) -> number (guid) } |
| * "snapprops" -> { name (lastname) -> { name -> value } } |
| * "snapholds" -> { name (lastname) -> { holdname -> crtime } } |
| * |
| * "origin" -> number (guid) (if clone) |
| * "is_encroot" -> boolean |
| * "sent" -> boolean (not on-disk) |
| * } |
| * } |
| * } |
| * |
| */ |
| } send_data_t; |
| |
| static void |
| send_iterate_prop(zfs_handle_t *zhp, boolean_t received_only, nvlist_t *nv); |
| |
| static int |
| send_iterate_snap(zfs_handle_t *zhp, void *arg) |
| { |
| send_data_t *sd = arg; |
| uint64_t guid = zhp->zfs_dmustats.dds_guid; |
| uint64_t txg = zhp->zfs_dmustats.dds_creation_txg; |
| char *snapname; |
| nvlist_t *nv; |
| boolean_t isfromsnap, istosnap, istosnapwithnofrom; |
| |
| snapname = strrchr(zhp->zfs_name, '@')+1; |
| isfromsnap = (sd->fromsnap != NULL && |
| strcmp(sd->fromsnap, snapname) == 0); |
| istosnap = (sd->tosnap != NULL && (strcmp(sd->tosnap, snapname) == 0)); |
| istosnapwithnofrom = (istosnap && sd->fromsnap == NULL); |
| |
| if (sd->tosnap_txg != 0 && txg > sd->tosnap_txg) { |
| if (sd->verbose) { |
| (void) fprintf(stderr, dgettext(TEXT_DOMAIN, |
| "skipping snapshot %s because it was created " |
| "after the destination snapshot (%s)\n"), |
| zhp->zfs_name, sd->tosnap); |
| } |
| zfs_close(zhp); |
| return (0); |
| } |
| |
| fnvlist_add_uint64(sd->parent_snaps, snapname, guid); |
| /* |
| * NB: if there is no fromsnap here (it's a newly created fs in |
| * an incremental replication), we will substitute the tosnap. |
| */ |
| if (isfromsnap || (sd->parent_fromsnap_guid == 0 && istosnap)) { |
| sd->parent_fromsnap_guid = guid; |
| } |
| |
| if (!sd->recursive) { |
| |
| /* |
| * To allow a doall stream to work properly |
| * with a NULL fromsnap |
| */ |
| if (sd->doall && sd->fromsnap == NULL && !sd->seenfrom) { |
| sd->seenfrom = B_TRUE; |
| } |
| |
| if (!sd->seenfrom && isfromsnap) { |
| sd->seenfrom = B_TRUE; |
| zfs_close(zhp); |
| return (0); |
| } |
| |
| if ((sd->seento || !sd->seenfrom) && !istosnapwithnofrom) { |
| zfs_close(zhp); |
| return (0); |
| } |
| |
| if (istosnap) |
| sd->seento = B_TRUE; |
| } |
| |
| nv = fnvlist_alloc(); |
| send_iterate_prop(zhp, sd->backup, nv); |
| fnvlist_add_nvlist(sd->snapprops, snapname, nv); |
| fnvlist_free(nv); |
| if (sd->holds) { |
| nvlist_t *holds; |
| if (lzc_get_holds(zhp->zfs_name, &holds) == 0) { |
| fnvlist_add_nvlist(sd->snapholds, snapname, holds); |
| fnvlist_free(holds); |
| } |
| } |
| |
| zfs_close(zhp); |
| return (0); |
| } |
| |
| static void |
| send_iterate_prop(zfs_handle_t *zhp, boolean_t received_only, nvlist_t *nv) |
| { |
| nvlist_t *props = NULL; |
| nvpair_t *elem = NULL; |
| |
| if (received_only) |
| props = zfs_get_recvd_props(zhp); |
| else |
| props = zhp->zfs_props; |
| |
| while ((elem = nvlist_next_nvpair(props, elem)) != NULL) { |
| char *propname = nvpair_name(elem); |
| zfs_prop_t prop = zfs_name_to_prop(propname); |
| nvlist_t *propnv; |
| |
| if (!zfs_prop_user(propname)) { |
| /* |
| * Realistically, this should never happen. However, |
| * we want the ability to add DSL properties without |
| * needing to make incompatible version changes. We |
| * need to ignore unknown properties to allow older |
| * software to still send datasets containing these |
| * properties, with the unknown properties elided. |
| */ |
| if (prop == ZPROP_INVAL) |
| continue; |
| |
| if (zfs_prop_readonly(prop)) |
| continue; |
| } |
| |
| verify(nvpair_value_nvlist(elem, &propnv) == 0); |
| if (prop == ZFS_PROP_QUOTA || prop == ZFS_PROP_RESERVATION || |
| prop == ZFS_PROP_REFQUOTA || |
| prop == ZFS_PROP_REFRESERVATION) { |
| char *source; |
| uint64_t value; |
| verify(nvlist_lookup_uint64(propnv, |
| ZPROP_VALUE, &value) == 0); |
| if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT) |
| continue; |
| /* |
| * May have no source before SPA_VERSION_RECVD_PROPS, |
| * but is still modifiable. |
| */ |
| if (nvlist_lookup_string(propnv, |
| ZPROP_SOURCE, &source) == 0) { |
| if ((strcmp(source, zhp->zfs_name) != 0) && |
| (strcmp(source, |
| ZPROP_SOURCE_VAL_RECVD) != 0)) |
| continue; |
| } |
| } else { |
| char *source; |
| if (nvlist_lookup_string(propnv, |
| ZPROP_SOURCE, &source) != 0) |
| continue; |
| if ((strcmp(source, zhp->zfs_name) != 0) && |
| (strcmp(source, ZPROP_SOURCE_VAL_RECVD) != 0)) |
| continue; |
| } |
| |
| if (zfs_prop_user(propname) || |
| zfs_prop_get_type(prop) == PROP_TYPE_STRING) { |
| char *value; |
| value = fnvlist_lookup_string(propnv, ZPROP_VALUE); |
| fnvlist_add_string(nv, propname, value); |
| } else { |
| uint64_t value; |
| value = fnvlist_lookup_uint64(propnv, ZPROP_VALUE); |
| fnvlist_add_uint64(nv, propname, value); |
| } |
| } |
| } |
| |
| /* |
| * returns snapshot creation txg |
| * and returns 0 if the snapshot does not exist |
| */ |
| static uint64_t |
| get_snap_txg(libzfs_handle_t *hdl, const char *fs, const char *snap) |
| { |
| char name[ZFS_MAX_DATASET_NAME_LEN]; |
| uint64_t txg = 0; |
| |
| if (fs == NULL || fs[0] == '\0' || snap == NULL || snap[0] == '\0') |
| return (txg); |
| |
| (void) snprintf(name, sizeof (name), "%s@%s", fs, snap); |
| if (zfs_dataset_exists(hdl, name, ZFS_TYPE_SNAPSHOT)) { |
| zfs_handle_t *zhp = zfs_open(hdl, name, ZFS_TYPE_SNAPSHOT); |
| if (zhp != NULL) { |
| txg = zfs_prop_get_int(zhp, ZFS_PROP_CREATETXG); |
| zfs_close(zhp); |
| } |
| } |
| |
| return (txg); |
| } |
| |
| /* |
| * recursively generate nvlists describing datasets. See comment |
| * for the data structure send_data_t above for description of contents |
| * of the nvlist. |
| */ |
| static int |
| send_iterate_fs(zfs_handle_t *zhp, void *arg) |
| { |
| send_data_t *sd = arg; |
| nvlist_t *nvfs = NULL, *nv = NULL; |
| int rv = 0; |
| uint64_t min_txg = 0, max_txg = 0; |
| uint64_t parent_fromsnap_guid_save = sd->parent_fromsnap_guid; |
| uint64_t fromsnap_txg_save = sd->fromsnap_txg; |
| uint64_t tosnap_txg_save = sd->tosnap_txg; |
| uint64_t txg = zhp->zfs_dmustats.dds_creation_txg; |
| uint64_t guid = zhp->zfs_dmustats.dds_guid; |
| uint64_t fromsnap_txg, tosnap_txg; |
| char guidstring[64]; |
| |
| fromsnap_txg = get_snap_txg(zhp->zfs_hdl, zhp->zfs_name, sd->fromsnap); |
| if (fromsnap_txg != 0) |
| sd->fromsnap_txg = fromsnap_txg; |
| |
| tosnap_txg = get_snap_txg(zhp->zfs_hdl, zhp->zfs_name, sd->tosnap); |
| if (tosnap_txg != 0) |
| sd->tosnap_txg = tosnap_txg; |
| |
| /* |
| * on the send side, if the current dataset does not have tosnap, |
| * perform two additional checks: |
| * |
| * - skip sending the current dataset if it was created later than |
| * the parent tosnap |
| * - return error if the current dataset was created earlier than |
| * the parent tosnap, unless --skip-missing specified. Then |
| * just print a warning |
| */ |
| if (sd->tosnap != NULL && tosnap_txg == 0) { |
| if (sd->tosnap_txg != 0 && txg > sd->tosnap_txg) { |
| if (sd->verbose) { |
| (void) fprintf(stderr, dgettext(TEXT_DOMAIN, |
| "skipping dataset %s: snapshot %s does " |
| "not exist\n"), zhp->zfs_name, sd->tosnap); |
| } |
| } else if (sd->skipmissing) { |
| (void) fprintf(stderr, dgettext(TEXT_DOMAIN, |
| "WARNING: skipping dataset %s and its children:" |
| " snapshot %s does not exist\n"), |
| zhp->zfs_name, sd->tosnap); |
| } else { |
| (void) fprintf(stderr, dgettext(TEXT_DOMAIN, |
| "cannot send %s@%s%s: snapshot %s@%s does not " |
| "exist\n"), sd->fsname, sd->tosnap, sd->recursive ? |
| dgettext(TEXT_DOMAIN, " recursively") : "", |
| zhp->zfs_name, sd->tosnap); |
| rv = EZFS_NOENT; |
| } |
| goto out; |
| } |
| |
| nvfs = fnvlist_alloc(); |
| fnvlist_add_string(nvfs, "name", zhp->zfs_name); |
| fnvlist_add_uint64(nvfs, "parentfromsnap", |
| sd->parent_fromsnap_guid); |
| |
| if (zhp->zfs_dmustats.dds_origin[0]) { |
| zfs_handle_t *origin = zfs_open(zhp->zfs_hdl, |
| zhp->zfs_dmustats.dds_origin, ZFS_TYPE_SNAPSHOT); |
| if (origin == NULL) { |
| rv = -1; |
| goto out; |
| } |
| fnvlist_add_uint64(nvfs, "origin", |
| origin->zfs_dmustats.dds_guid); |
| |
| zfs_close(origin); |
| } |
| |
| /* iterate over props */ |
| if (sd->props || sd->backup || sd->recursive) { |
| nv = fnvlist_alloc(); |
| send_iterate_prop(zhp, sd->backup, nv); |
| } |
| if (zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) != ZIO_CRYPT_OFF) { |
| boolean_t encroot; |
| |
| /* determine if this dataset is an encryption root */ |
| if (zfs_crypto_get_encryption_root(zhp, &encroot, NULL) != 0) { |
| rv = -1; |
| goto out; |
| } |
| |
| if (encroot) |
| fnvlist_add_boolean(nvfs, "is_encroot"); |
| |
| /* |
| * Encrypted datasets can only be sent with properties if |
| * the raw flag is specified because the receive side doesn't |
| * currently have a mechanism for recursively asking the user |
| * for new encryption parameters. |
| */ |
| if (!sd->raw) { |
| (void) fprintf(stderr, dgettext(TEXT_DOMAIN, |
| "cannot send %s@%s: encrypted dataset %s may not " |
| "be sent with properties without the raw flag\n"), |
| sd->fsname, sd->tosnap, zhp->zfs_name); |
| rv = -1; |
| goto out; |
| } |
| |
| } |
| |
| if (nv != NULL) |
| fnvlist_add_nvlist(nvfs, "props", nv); |
| |
| /* iterate over snaps, and set sd->parent_fromsnap_guid */ |
| sd->parent_fromsnap_guid = 0; |
| sd->parent_snaps = fnvlist_alloc(); |
| sd->snapprops = fnvlist_alloc(); |
| if (sd->holds) |
| sd->snapholds = fnvlist_alloc(); |
| |
| /* |
| * If this is a "doall" send, a replicate send or we're just trying |
| * to gather a list of previous snapshots, iterate through all the |
| * snaps in the txg range. Otherwise just look at the one we're |
| * interested in. |
| */ |
| if (sd->doall || sd->replicate || sd->tosnap == NULL) { |
| if (!sd->replicate && fromsnap_txg != 0) |
| min_txg = fromsnap_txg; |
| if (!sd->replicate && tosnap_txg != 0) |
| max_txg = tosnap_txg; |
| (void) zfs_iter_snapshots_sorted(zhp, send_iterate_snap, sd, |
| min_txg, max_txg); |
| } else { |
| char snapname[MAXPATHLEN] = { 0 }; |
| zfs_handle_t *snap; |
| |
| (void) snprintf(snapname, sizeof (snapname), "%s@%s", |
| zhp->zfs_name, sd->tosnap); |
| if (sd->fromsnap != NULL) |
| sd->seenfrom = B_TRUE; |
| snap = zfs_open(zhp->zfs_hdl, snapname, |
| ZFS_TYPE_SNAPSHOT); |
| if (snap != NULL) |
| (void) send_iterate_snap(snap, sd); |
| } |
| |
| fnvlist_add_nvlist(nvfs, "snaps", sd->parent_snaps); |
| fnvlist_add_nvlist(nvfs, "snapprops", sd->snapprops); |
| if (sd->holds) |
| fnvlist_add_nvlist(nvfs, "snapholds", sd->snapholds); |
| fnvlist_free(sd->parent_snaps); |
| fnvlist_free(sd->snapprops); |
| fnvlist_free(sd->snapholds); |
| |
| /* Do not allow the size of the properties list to exceed the limit */ |
| if ((fnvlist_size(nvfs) + fnvlist_size(sd->fss)) > |
| zhp->zfs_hdl->libzfs_max_nvlist) { |
| (void) fprintf(stderr, dgettext(TEXT_DOMAIN, |
| "warning: cannot send %s@%s: the size of the list of " |
| "snapshots and properties is too large to be received " |
| "successfully.\n" |
| "Select a smaller number of snapshots to send.\n"), |
| zhp->zfs_name, sd->tosnap); |
| rv = EZFS_NOSPC; |
| goto out; |
| } |
| /* add this fs to nvlist */ |
| (void) snprintf(guidstring, sizeof (guidstring), |
| "0x%llx", (longlong_t)guid); |
| fnvlist_add_nvlist(sd->fss, guidstring, nvfs); |
| |
| /* iterate over children */ |
| if (sd->recursive) |
| rv = zfs_iter_filesystems(zhp, send_iterate_fs, sd); |
| |
| out: |
| sd->parent_fromsnap_guid = parent_fromsnap_guid_save; |
| sd->fromsnap_txg = fromsnap_txg_save; |
| sd->tosnap_txg = tosnap_txg_save; |
| fnvlist_free(nv); |
| fnvlist_free(nvfs); |
| |
| zfs_close(zhp); |
| return (rv); |
| } |
| |
| static int |
| gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap, |
| const char *tosnap, boolean_t recursive, boolean_t raw, boolean_t doall, |
| boolean_t replicate, boolean_t skipmissing, boolean_t verbose, |
| boolean_t backup, boolean_t holds, boolean_t props, nvlist_t **nvlp, |
| avl_tree_t **avlp) |
| { |
| zfs_handle_t *zhp; |
| send_data_t sd = { 0 }; |
| int error; |
| |
| zhp = zfs_open(hdl, fsname, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME); |
| if (zhp == NULL) |
| return (EZFS_BADTYPE); |
| |
| sd.fss = fnvlist_alloc(); |
| sd.fsname = fsname; |
| sd.fromsnap = fromsnap; |
| sd.tosnap = tosnap; |
| sd.recursive = recursive; |
| sd.raw = raw; |
| sd.doall = doall; |
| sd.replicate = replicate; |
| sd.skipmissing = skipmissing; |
| sd.verbose = verbose; |
| sd.backup = backup; |
| sd.holds = holds; |
| sd.props = props; |
| |
| if ((error = send_iterate_fs(zhp, &sd)) != 0) { |
| fnvlist_free(sd.fss); |
| if (avlp != NULL) |
| *avlp = NULL; |
| *nvlp = NULL; |
| return (error); |
| } |
| |
| if (avlp != NULL && (*avlp = fsavl_create(sd.fss)) == NULL) { |
| fnvlist_free(sd.fss); |
| *nvlp = NULL; |
| return (EZFS_NOMEM); |
| } |
| |
| *nvlp = sd.fss; |
| return (0); |
| } |
| |
| /* |
| * Routines specific to "zfs send" |
| */ |
| typedef struct send_dump_data { |
| /* these are all just the short snapname (the part after the @) */ |
| const char *fromsnap; |
| const char *tosnap; |
| char prevsnap[ZFS_MAX_DATASET_NAME_LEN]; |
| uint64_t prevsnap_obj; |
| boolean_t seenfrom, seento, replicate, doall, fromorigin; |
| boolean_t dryrun, parsable, progress, embed_data, block_diff, std_out; |
| boolean_t large_block, compress, raw, holds; |
| boolean_t progressastitle; |
| int outfd; |
| boolean_t err; |
| nvlist_t *fss; |
| nvlist_t *snapholds; |
| avl_tree_t *fsavl; |
| snapfilter_cb_t *filter_cb; |
| void *filter_cb_arg; |
| nvlist_t *debugnv; |
| char holdtag[ZFS_MAX_DATASET_NAME_LEN]; |
| int cleanup_fd; |
| int verbosity; |
| uint64_t size; |
| } send_dump_data_t; |
| |
| static int |
| zfs_send_space(zfs_handle_t *zhp, const char *snapname, const char *from, |
| enum lzc_send_flags flags, uint64_t *spacep) |
| { |
| libzfs_handle_t *hdl = zhp->zfs_hdl; |
| int error; |
| |
| assert(snapname != NULL); |
| error = lzc_send_space(snapname, from, flags, spacep); |
| |
| if (error != 0) { |
| char errbuf[1024]; |
| (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, |
| "warning: cannot estimate space for '%s'"), snapname); |
| |
| switch (error) { |
| case EXDEV: |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "not an earlier snapshot from the same fs")); |
| return (zfs_error(hdl, EZFS_CROSSTARGET, errbuf)); |
| |
| case ENOENT: |
| if (zfs_dataset_exists(hdl, snapname, |
| ZFS_TYPE_SNAPSHOT)) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "incremental source (%s) does not exist"), |
| snapname); |
| } |
| return (zfs_error(hdl, EZFS_NOENT, errbuf)); |
| |
| case EDQUOT: |
| case EFBIG: |
| case EIO: |
| case ENOLINK: |
| case ENOSPC: |
| case ENOSTR: |
| case ENXIO: |
| case EPIPE: |
| case ERANGE: |
| case EFAULT: |
| case EROFS: |
| case EINVAL: |
| zfs_error_aux(hdl, "%s", strerror(error)); |
| return (zfs_error(hdl, EZFS_BADBACKUP, errbuf)); |
| |
| default: |
| return (zfs_standard_error(hdl, error, errbuf)); |
| } |
| } |
| |
| return (0); |
| } |
| |
| /* |
| * Dumps a backup of the given snapshot (incremental from fromsnap if it's not |
| * NULL) to the file descriptor specified by outfd. |
| */ |
| static int |
| dump_ioctl(zfs_handle_t *zhp, const char *fromsnap, uint64_t fromsnap_obj, |
| boolean_t fromorigin, int outfd, enum lzc_send_flags flags, |
| nvlist_t *debugnv, boolean_t block_diff) |
| { |
| zfs_cmd_t zc = {"\0"}; |
| libzfs_handle_t *hdl = zhp->zfs_hdl; |
| nvlist_t *thisdbg; |
| |
| assert(zhp->zfs_type == ZFS_TYPE_SNAPSHOT); |
| assert(fromsnap_obj == 0 || !fromorigin); |
| |
| (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); |
| zc.zc_cookie = outfd; |
| zc.zc_obj = fromorigin; |
| zc.zc_sendobj = zfs_prop_get_int(zhp, ZFS_PROP_OBJSETID); |
| zc.zc_fromobj = fromsnap_obj; |
| zc.zc_flags = flags; |
| zc.zc_block_diff = block_diff ? BLOCK_DIFF_MAGIC : 0; |
| |
| thisdbg = fnvlist_alloc(); |
| if (fromsnap && fromsnap[0] != '\0') { |
| fnvlist_add_string(thisdbg, "fromsnap", fromsnap); |
| } |
| |
| if (zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_SEND, &zc) != 0) { |
| char errbuf[1024]; |
| (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, |
| "warning: cannot send '%s'"), zhp->zfs_name); |
| |
| fnvlist_add_uint64(thisdbg, "error", errno); |
| if (debugnv) { |
| fnvlist_add_nvlist(debugnv, zhp->zfs_name, thisdbg); |
| } |
| fnvlist_free(thisdbg); |
| |
| switch (errno) { |
| case EXDEV: |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "not an earlier snapshot from the same fs")); |
| return (zfs_error(hdl, EZFS_CROSSTARGET, errbuf)); |
| |
| case EACCES: |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "source key must be loaded")); |
| return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf)); |
| |
| case ENOENT: |
| if (zfs_dataset_exists(hdl, zc.zc_name, |
| ZFS_TYPE_SNAPSHOT)) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "incremental source (@%s) does not exist"), |
| zc.zc_value); |
| } |
| return (zfs_error(hdl, EZFS_NOENT, errbuf)); |
| |
| case EDQUOT: |
| case EFBIG: |
| case EIO: |
| case ENOLINK: |
| case ENOSPC: |
| case ENOSTR: |
| case ENXIO: |
| case EPIPE: |
| case ERANGE: |
| case EFAULT: |
| case EROFS: |
| case EINVAL: |
| zfs_error_aux(hdl, "%s", strerror(errno)); |
| return (zfs_error(hdl, EZFS_BADBACKUP, errbuf)); |
| |
| default: |
| return (zfs_standard_error(hdl, errno, errbuf)); |
| } |
| } |
| |
| if (debugnv) |
| fnvlist_add_nvlist(debugnv, zhp->zfs_name, thisdbg); |
| fnvlist_free(thisdbg); |
| |
| return (0); |
| } |
| |
| static void |
| gather_holds(zfs_handle_t *zhp, send_dump_data_t *sdd) |
| { |
| assert(zhp->zfs_type == ZFS_TYPE_SNAPSHOT); |
| |
| /* |
| * zfs_send() only sets snapholds for sends that need them, |
| * e.g. replication and doall. |
| */ |
| if (sdd->snapholds == NULL) |
| return; |
| |
| fnvlist_add_string(sdd->snapholds, zhp->zfs_name, sdd->holdtag); |
| } |
| |
| int |
| zfs_send_progress(zfs_handle_t *zhp, int fd, uint64_t *bytes_written, |
| uint64_t *blocks_visited) |
| { |
| zfs_cmd_t zc = {"\0"}; |
| |
| (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); |
| zc.zc_cookie = fd; |
| if (zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_SEND_PROGRESS, &zc) != 0) |
| return (errno); |
| if (bytes_written != NULL) |
| *bytes_written = zc.zc_cookie; |
| if (blocks_visited != NULL) |
| *blocks_visited = zc.zc_objset_type; |
| return (0); |
| } |
| |
| static void * |
| send_progress_thread(void *arg) |
| { |
| progress_arg_t *pa = arg; |
| zfs_handle_t *zhp = pa->pa_zhp; |
| uint64_t bytes; |
| uint64_t blocks; |
| uint64_t total = pa->pa_size / 100; |
| char buf[16]; |
| time_t t; |
| struct tm *tm; |
| boolean_t firstloop = B_TRUE; |
| |
| /* |
| * Print the progress from ZFS_IOC_SEND_PROGRESS every second. |
| */ |
| for (;;) { |
| int err; |
| (void) sleep(1); |
| if ((err = zfs_send_progress(zhp, pa->pa_fd, &bytes, |
| &blocks)) != 0) { |
| if (err == EINTR || err == ENOENT) |
| return ((void *)0); |
| return ((void *)(uintptr_t)err); |
| } |
| |
| if (firstloop && !pa->pa_parsable && pa->pa_progress) { |
| (void) fprintf(stderr, |
| "TIME %s %sSNAPSHOT %s\n", |
| pa->pa_estimate ? "BYTES" : " SENT", |
| pa->pa_verbosity >= 2 ? " BLOCKS " : "", |
| zhp->zfs_name); |
| firstloop = B_FALSE; |
| } |
| |
| (void) time(&t); |
| tm = localtime(&t); |
| |
| if (pa->pa_astitle) { |
| char buf_bytes[16]; |
| char buf_size[16]; |
| int pct; |
| zfs_nicenum(bytes, buf_bytes, sizeof (buf_bytes)); |
| zfs_nicenum(pa->pa_size, buf_size, sizeof (buf_size)); |
| pct = (total > 0) ? bytes / total : 100; |
| zfs_setproctitle("sending %s (%d%%: %s/%s)", |
| zhp->zfs_name, MIN(pct, 100), buf_bytes, buf_size); |
| } |
| |
| if (pa->pa_verbosity >= 2 && pa->pa_parsable) { |
| (void) fprintf(stderr, |
| "%02d:%02d:%02d\t%llu\t%llu\t%s\n", |
| tm->tm_hour, tm->tm_min, tm->tm_sec, |
| (u_longlong_t)bytes, (u_longlong_t)blocks, |
| zhp->zfs_name); |
| } else if (pa->pa_verbosity >= 2) { |
| zfs_nicenum(bytes, buf, sizeof (buf)); |
| (void) fprintf(stderr, |
| "%02d:%02d:%02d %5s %8llu %s\n", |
| tm->tm_hour, tm->tm_min, tm->tm_sec, |
| buf, (u_longlong_t)blocks, zhp->zfs_name); |
| } else if (pa->pa_parsable) { |
| (void) fprintf(stderr, "%02d:%02d:%02d\t%llu\t%s\n", |
| tm->tm_hour, tm->tm_min, tm->tm_sec, |
| (u_longlong_t)bytes, zhp->zfs_name); |
| } else if (pa->pa_progress) { |
| zfs_nicebytes(bytes, buf, sizeof (buf)); |
| (void) fprintf(stderr, "%02d:%02d:%02d %5s %s\n", |
| tm->tm_hour, tm->tm_min, tm->tm_sec, |
| buf, zhp->zfs_name); |
| } |
| } |
| } |
| |
| static void |
| send_print_verbose(FILE *fout, const char *tosnap, const char *fromsnap, |
| uint64_t size, boolean_t parsable) |
| { |
| if (parsable) { |
| if (fromsnap != NULL) { |
| (void) fprintf(fout, "incremental\t%s\t%s", |
| fromsnap, tosnap); |
| } else { |
| (void) fprintf(fout, "full\t%s", |
| tosnap); |
| } |
| } else { |
| if (fromsnap != NULL) { |
| if (strchr(fromsnap, '@') == NULL && |
| strchr(fromsnap, '#') == NULL) { |
| (void) fprintf(fout, dgettext(TEXT_DOMAIN, |
| "send from @%s to %s"), |
| fromsnap, tosnap); |
| } else { |
| (void) fprintf(fout, dgettext(TEXT_DOMAIN, |
| "send from %s to %s"), |
| fromsnap, tosnap); |
| } |
| } else { |
| (void) fprintf(fout, dgettext(TEXT_DOMAIN, |
| "full send of %s"), |
| tosnap); |
| } |
| } |
| |
| if (parsable) { |
| (void) fprintf(fout, "\t%llu", |
| (longlong_t)size); |
| } else if (size != 0) { |
| char buf[16]; |
| zfs_nicebytes(size, buf, sizeof (buf)); |
| (void) fprintf(fout, dgettext(TEXT_DOMAIN, |
| " estimated size is %s"), buf); |
| } |
| (void) fprintf(fout, "\n"); |
| } |
| |
| static int |
| dump_snapshot(zfs_handle_t *zhp, void *arg) |
| { |
| send_dump_data_t *sdd = arg; |
| progress_arg_t pa = { 0 }; |
| pthread_t tid; |
| char *thissnap; |
| enum lzc_send_flags flags = 0; |
| int err; |
| boolean_t isfromsnap, istosnap, fromorigin; |
| boolean_t exclude = B_FALSE; |
| FILE *fout = sdd->std_out ? stdout : stderr; |
| |
| err = 0; |
| thissnap = strchr(zhp->zfs_name, '@') + 1; |
| isfromsnap = (sdd->fromsnap != NULL && |
| strcmp(sdd->fromsnap, thissnap) == 0); |
| |
| if (!sdd->seenfrom && isfromsnap) { |
| gather_holds(zhp, sdd); |
| sdd->seenfrom = B_TRUE; |
| (void) strlcpy(sdd->prevsnap, thissnap, |
| sizeof (sdd->prevsnap)); |
| sdd->prevsnap_obj = zfs_prop_get_int(zhp, ZFS_PROP_OBJSETID); |
| zfs_close(zhp); |
| return (0); |
| } |
| |
| if (sdd->seento || !sdd->seenfrom) { |
| zfs_close(zhp); |
| return (0); |
| } |
| |
| istosnap = (strcmp(sdd->tosnap, thissnap) == 0); |
| if (istosnap) |
| sdd->seento = B_TRUE; |
| |
| if (sdd->large_block) |
| flags |= LZC_SEND_FLAG_LARGE_BLOCK; |
| if (sdd->embed_data) |
| flags |= LZC_SEND_FLAG_EMBED_DATA; |
| if (sdd->compress) |
| flags |= LZC_SEND_FLAG_COMPRESS; |
| if (sdd->raw) |
| flags |= LZC_SEND_FLAG_RAW; |
| if (sdd->block_diff) |
| flags |= LZC_SEND_FLAG_BLOCKDIFF; |
| |
| if (!sdd->doall && !isfromsnap && !istosnap) { |
| if (sdd->replicate) { |
| char *snapname; |
| nvlist_t *snapprops; |
| /* |
| * Filter out all intermediate snapshots except origin |
| * snapshots needed to replicate clones. |
| */ |
| nvlist_t *nvfs = fsavl_find(sdd->fsavl, |
| zhp->zfs_dmustats.dds_guid, &snapname); |
| |
| snapprops = fnvlist_lookup_nvlist(nvfs, "snapprops"); |
| snapprops = fnvlist_lookup_nvlist(snapprops, thissnap); |
| exclude = !nvlist_exists(snapprops, "is_clone_origin"); |
| } else { |
| exclude = B_TRUE; |
| } |
| } |
| |
| /* |
| * If a filter function exists, call it to determine whether |
| * this snapshot will be sent. |
| */ |
| if (exclude || (sdd->filter_cb != NULL && |
| sdd->filter_cb(zhp, sdd->filter_cb_arg) == B_FALSE)) { |
| /* |
| * This snapshot is filtered out. Don't send it, and don't |
| * set prevsnap_obj, so it will be as if this snapshot didn't |
| * exist, and the next accepted snapshot will be sent as |
| * an incremental from the last accepted one, or as the |
| * first (and full) snapshot in the case of a replication, |
| * non-incremental send. |
| */ |
| zfs_close(zhp); |
| return (0); |
| } |
| |
| gather_holds(zhp, sdd); |
| fromorigin = sdd->prevsnap[0] == '\0' && |
| (sdd->fromorigin || sdd->replicate); |
| |
| if (sdd->verbosity != 0) { |
| uint64_t size = 0; |
| char fromds[ZFS_MAX_DATASET_NAME_LEN]; |
| |
| if (sdd->prevsnap[0] != '\0') { |
| (void) strlcpy(fromds, zhp->zfs_name, sizeof (fromds)); |
| *(strchr(fromds, '@') + 1) = '\0'; |
| (void) strlcat(fromds, sdd->prevsnap, sizeof (fromds)); |
| } |
| if (zfs_send_space(zhp, zhp->zfs_name, |
| sdd->prevsnap[0] ? fromds : NULL, flags, &size) != 0) { |
| size = 0; /* cannot estimate send space */ |
| } else { |
| send_print_verbose(fout, zhp->zfs_name, |
| sdd->prevsnap[0] ? sdd->prevsnap : NULL, |
| size, sdd->parsable); |
| } |
| sdd->size += size; |
| } |
| |
| if (!sdd->dryrun) { |
| /* |
| * If progress reporting is requested, spawn a new thread to |
| * poll ZFS_IOC_SEND_PROGRESS at a regular interval. |
| */ |
| if (sdd->progress || sdd->progressastitle) { |
| pa.pa_zhp = zhp; |
| pa.pa_fd = sdd->outfd; |
| pa.pa_parsable = sdd->parsable; |
| pa.pa_estimate = B_FALSE; |
| pa.pa_verbosity = sdd->verbosity; |
| pa.pa_size = sdd->size; |
| pa.pa_astitle = sdd->progressastitle; |
| pa.pa_progress = sdd->progress; |
| |
| if ((err = pthread_create(&tid, NULL, |
| send_progress_thread, &pa)) != 0) { |
| zfs_close(zhp); |
| return (err); |
| } |
| } |
| |
| err = dump_ioctl(zhp, sdd->prevsnap, sdd->prevsnap_obj, |
| fromorigin, sdd->outfd, flags, sdd->debugnv, sdd->block_diff); |
| |
| if (sdd->progress || sdd->progressastitle) { |
| void *status = NULL; |
| (void) pthread_cancel(tid); |
| (void) pthread_join(tid, &status); |
| int error = (int)(uintptr_t)status; |
| if (error != 0 && status != PTHREAD_CANCELED) { |
| char errbuf[1024]; |
| (void) snprintf(errbuf, sizeof (errbuf), |
| dgettext(TEXT_DOMAIN, |
| "progress thread exited nonzero")); |
| return (zfs_standard_error(zhp->zfs_hdl, error, |
| errbuf)); |
| } |
| } |
| } |
| |
| (void) strcpy(sdd->prevsnap, thissnap); |
| sdd->prevsnap_obj = zfs_prop_get_int(zhp, ZFS_PROP_OBJSETID); |
| zfs_close(zhp); |
| return (err); |
| } |
| |
| static int |
| dump_filesystem(zfs_handle_t *zhp, void *arg) |
| { |
| int rv = 0; |
| send_dump_data_t *sdd = arg; |
| boolean_t missingfrom = B_FALSE; |
| zfs_cmd_t zc = {"\0"}; |
| uint64_t min_txg = 0, max_txg = 0; |
| |
| (void) snprintf(zc.zc_name, sizeof (zc.zc_name), "%s@%s", |
| zhp->zfs_name, sdd->tosnap); |
| if (zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_OBJSET_STATS, &zc) != 0) { |
| (void) fprintf(stderr, dgettext(TEXT_DOMAIN, |
| "WARNING: could not send %s@%s: does not exist\n"), |
| zhp->zfs_name, sdd->tosnap); |
| sdd->err = B_TRUE; |
| return (0); |
| } |
| |
| if (sdd->replicate && sdd->fromsnap) { |
| /* |
| * If this fs does not have fromsnap, and we're doing |
| * recursive, we need to send a full stream from the |
| * beginning (or an incremental from the origin if this |
| * is a clone). If we're doing non-recursive, then let |
| * them get the error. |
| */ |
| (void) snprintf(zc.zc_name, sizeof (zc.zc_name), "%s@%s", |
| zhp->zfs_name, sdd->fromsnap); |
| if (zfs_ioctl(zhp->zfs_hdl, |
| ZFS_IOC_OBJSET_STATS, &zc) != 0) { |
| missingfrom = B_TRUE; |
| } |
| } |
| |
| sdd->seenfrom = sdd->seento = sdd->prevsnap[0] = 0; |
| sdd->prevsnap_obj = 0; |
| if (sdd->fromsnap == NULL || missingfrom) |
| sdd->seenfrom = B_TRUE; |
| |
| |
| |
| /* |
| * Iterate through all snapshots and process the ones we will be |
| * sending. If we only have a "from" and "to" snapshot to deal |
| * with, we can avoid iterating through all the other snapshots. |
| */ |
| if (sdd->doall || sdd->replicate || sdd->tosnap == NULL) { |
| if (!sdd->replicate && sdd->fromsnap != NULL) |
| min_txg = get_snap_txg(zhp->zfs_hdl, zhp->zfs_name, |
| sdd->fromsnap); |
| if (!sdd->replicate && sdd->tosnap != NULL) |
| max_txg = get_snap_txg(zhp->zfs_hdl, zhp->zfs_name, |
| sdd->tosnap); |
| rv = zfs_iter_snapshots_sorted(zhp, dump_snapshot, arg, |
| min_txg, max_txg); |
| } else { |
| char snapname[MAXPATHLEN] = { 0 }; |
| zfs_handle_t *snap; |
| |
| if (!sdd->seenfrom) { |
| (void) snprintf(snapname, sizeof (snapname), |
| "%s@%s", zhp->zfs_name, sdd->fromsnap); |
| snap = zfs_open(zhp->zfs_hdl, snapname, |
| ZFS_TYPE_SNAPSHOT); |
| if (snap != NULL) |
| rv = dump_snapshot(snap, sdd); |
| else |
| rv = -1; |
| } |
| |
| if (rv == 0) { |
| (void) snprintf(snapname, sizeof (snapname), |
| "%s@%s", zhp->zfs_name, sdd->tosnap); |
| snap = zfs_open(zhp->zfs_hdl, snapname, |
| ZFS_TYPE_SNAPSHOT); |
| if (snap != NULL) |
| rv = dump_snapshot(snap, sdd); |
| else |
| rv = -1; |
| } |
| } |
| |
| if (!sdd->seenfrom) { |
| (void) fprintf(stderr, dgettext(TEXT_DOMAIN, |
| "WARNING: could not send %s@%s:\n" |
| "incremental source (%s@%s) does not exist\n"), |
| zhp->zfs_name, sdd->tosnap, |
| zhp->zfs_name, sdd->fromsnap); |
| sdd->err = B_TRUE; |
| } else if (!sdd->seento) { |
| if (sdd->fromsnap) { |
| (void) fprintf(stderr, dgettext(TEXT_DOMAIN, |
| "WARNING: could not send %s@%s:\n" |
| "incremental source (%s@%s) " |
| "is not earlier than it\n"), |
| zhp->zfs_name, sdd->tosnap, |
| zhp->zfs_name, sdd->fromsnap); |
| } else { |
| (void) fprintf(stderr, dgettext(TEXT_DOMAIN, |
| "WARNING: " |
| "could not send %s@%s: does not exist\n"), |
| zhp->zfs_name, sdd->tosnap); |
| } |
| sdd->err = B_TRUE; |
| } |
| |
| return (rv); |
| } |
| |
| static int |
| dump_filesystems(zfs_handle_t *rzhp, void *arg) |
| { |
| send_dump_data_t *sdd = arg; |
| nvpair_t *fspair; |
| boolean_t needagain, progress; |
| |
| if (!sdd->replicate) |
| return (dump_filesystem(rzhp, sdd)); |
| |
| /* Mark the clone origin snapshots. */ |
| for (fspair = nvlist_next_nvpair(sdd->fss, NULL); fspair; |
| fspair = nvlist_next_nvpair(sdd->fss, fspair)) { |
| nvlist_t *nvfs; |
| uint64_t origin_guid = 0; |
| |
| nvfs = fnvpair_value_nvlist(fspair); |
| (void) nvlist_lookup_uint64(nvfs, "origin", &origin_guid); |
| if (origin_guid != 0) { |
| char *snapname; |
| nvlist_t *origin_nv = fsavl_find(sdd->fsavl, |
| origin_guid, &snapname); |
| if (origin_nv != NULL) { |
| nvlist_t *snapprops; |
| snapprops = fnvlist_lookup_nvlist(origin_nv, |
| "snapprops"); |
| snapprops = fnvlist_lookup_nvlist(snapprops, |
| snapname); |
| fnvlist_add_boolean(snapprops, |
| "is_clone_origin"); |
| } |
| } |
| } |
| again: |
| needagain = progress = B_FALSE; |
| for (fspair = nvlist_next_nvpair(sdd->fss, NULL); fspair; |
| fspair = nvlist_next_nvpair(sdd->fss, fspair)) { |
| nvlist_t *fslist, *parent_nv; |
| char *fsname; |
| zfs_handle_t *zhp; |
| int err; |
| uint64_t origin_guid = 0; |
| uint64_t parent_guid = 0; |
| |
| fslist = fnvpair_value_nvlist(fspair); |
| if (nvlist_lookup_boolean(fslist, "sent") == 0) |
| continue; |
| |
| fsname = fnvlist_lookup_string(fslist, "name"); |
| (void) nvlist_lookup_uint64(fslist, "origin", &origin_guid); |
| (void) nvlist_lookup_uint64(fslist, "parentfromsnap", |
| &parent_guid); |
| |
| if (parent_guid != 0) { |
| parent_nv = fsavl_find(sdd->fsavl, parent_guid, NULL); |
| if (!nvlist_exists(parent_nv, "sent")) { |
| /* parent has not been sent; skip this one */ |
| needagain = B_TRUE; |
| continue; |
| } |
| } |
| |
| if (origin_guid != 0) { |
| nvlist_t *origin_nv = fsavl_find(sdd->fsavl, |
| origin_guid, NULL); |
| if (origin_nv != NULL && |
| !nvlist_exists(origin_nv, "sent")) { |
| /* |
| * origin has not been sent yet; |
| * skip this clone. |
| */ |
| needagain = B_TRUE; |
| continue; |
| } |
| } |
| |
| zhp = zfs_open(rzhp->zfs_hdl, fsname, ZFS_TYPE_DATASET); |
| if (zhp == NULL) |
| return (-1); |
| err = dump_filesystem(zhp, sdd); |
| fnvlist_add_boolean(fslist, "sent"); |
| progress = B_TRUE; |
| zfs_close(zhp); |
| if (err) |
| return (err); |
| } |
| if (needagain) { |
| assert(progress); |
| goto again; |
| } |
| |
| /* clean out the sent flags in case we reuse this fss */ |
| for (fspair = nvlist_next_nvpair(sdd->fss, NULL); fspair; |
| fspair = nvlist_next_nvpair(sdd->fss, fspair)) { |
| nvlist_t *fslist; |
| |
| fslist = fnvpair_value_nvlist(fspair); |
| (void) nvlist_remove_all(fslist, "sent"); |
| } |
| |
| return (0); |
| } |
| |
| nvlist_t * |
| zfs_send_resume_token_to_nvlist(libzfs_handle_t *hdl, const char *token) |
| { |
| unsigned int version; |
| int nread, i; |
| unsigned long long checksum, packed_len; |
| |
| /* |
| * Decode token header, which is: |
| * <token version>-<checksum of payload>-<uncompressed payload length> |
| * Note that the only supported token version is 1. |
| */ |
| nread = sscanf(token, "%u-%llx-%llx-", |
| &version, &checksum, &packed_len); |
| if (nread != 3) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "resume token is corrupt (invalid format)")); |
| return (NULL); |
| } |
| |
| if (version != ZFS_SEND_RESUME_TOKEN_VERSION) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "resume token is corrupt (invalid version %u)"), |
| version); |
| return (NULL); |
| } |
| |
| /* convert hexadecimal representation to binary */ |
| token = strrchr(token, '-') + 1; |
| int len = strlen(token) / 2; |
| unsigned char *compressed = zfs_alloc(hdl, len); |
| for (i = 0; i < len; i++) { |
| nread = sscanf(token + i * 2, "%2hhx", compressed + i); |
| if (nread != 1) { |
| free(compressed); |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "resume token is corrupt " |
| "(payload is not hex-encoded)")); |
| return (NULL); |
| } |
| } |
| |
| /* verify checksum */ |
| zio_cksum_t cksum; |
| fletcher_4_native_varsize(compressed, len, &cksum); |
| if (cksum.zc_word[0] != checksum) { |
| free(compressed); |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "resume token is corrupt (incorrect checksum)")); |
| return (NULL); |
| } |
| |
| /* uncompress */ |
| void *packed = zfs_alloc(hdl, packed_len); |
| uLongf packed_len_long = packed_len; |
| if (uncompress(packed, &packed_len_long, compressed, len) != Z_OK || |
| packed_len_long != packed_len) { |
| free(packed); |
| free(compressed); |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "resume token is corrupt (decompression failed)")); |
| return (NULL); |
| } |
| |
| /* unpack nvlist */ |
| nvlist_t *nv; |
| int error = nvlist_unpack(packed, packed_len, &nv, KM_SLEEP); |
| free(packed); |
| free(compressed); |
| if (error != 0) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "resume token is corrupt (nvlist_unpack failed)")); |
| return (NULL); |
| } |
| return (nv); |
| } |
| static enum lzc_send_flags |
| lzc_flags_from_sendflags(const sendflags_t *flags) |
| { |
| enum lzc_send_flags lzc_flags = 0; |
| if (flags->largeblock) |
| lzc_flags |= LZC_SEND_FLAG_LARGE_BLOCK; |
| if (flags->embed_data) |
| lzc_flags |= LZC_SEND_FLAG_EMBED_DATA; |
| if (flags->compress) |
| lzc_flags |= LZC_SEND_FLAG_COMPRESS; |
| if (flags->raw) |
| lzc_flags |= LZC_SEND_FLAG_RAW; |
| if (flags->saved) |
| lzc_flags |= LZC_SEND_FLAG_SAVED; |
| if (flags->block_diff) |
| lzc_flags |= LZC_SEND_FLAG_BLOCKDIFF; |
| return (lzc_flags); |
| } |
| |
| static int |
| estimate_size(zfs_handle_t *zhp, const char *from, int fd, sendflags_t *flags, |
| uint64_t resumeobj, uint64_t resumeoff, uint64_t bytes, |
| const char *redactbook, char *errbuf, uint64_t *sizep) |
| { |
| uint64_t size; |
| FILE *fout = flags->dryrun ? stdout : stderr; |
| progress_arg_t pa = { 0 }; |
| int err = 0; |
| pthread_t ptid; |
| |
| if (flags->progress || flags->progressastitle) { |
| pa.pa_zhp = zhp; |
| pa.pa_fd = fd; |
| pa.pa_parsable = flags->parsable; |
| pa.pa_estimate = B_TRUE; |
| pa.pa_verbosity = flags->verbosity; |
| |
| err = pthread_create(&ptid, NULL, |
| send_progress_thread, &pa); |
| if (err != 0) { |
| zfs_error_aux(zhp->zfs_hdl, "%s", strerror(errno)); |
| return (zfs_error(zhp->zfs_hdl, |
| EZFS_THREADCREATEFAILED, errbuf)); |
| } |
| } |
| |
| err = lzc_send_space_resume_redacted(zhp->zfs_name, from, |
| lzc_flags_from_sendflags(flags), resumeobj, resumeoff, bytes, |
| redactbook, fd, &size); |
| *sizep = size; |
| |
| if (flags->progress || flags->progressastitle) { |
| void *status = NULL; |
| (void) pthread_cancel(ptid); |
| (void) pthread_join(ptid, &status); |
| int error = (int)(uintptr_t)status; |
| if (error != 0 && status != PTHREAD_CANCELED) { |
| char errbuf[1024]; |
| (void) snprintf(errbuf, sizeof (errbuf), |
| dgettext(TEXT_DOMAIN, "progress thread exited " |
| "nonzero")); |
| return (zfs_standard_error(zhp->zfs_hdl, error, |
| errbuf)); |
| } |
| } |
| |
| if (!flags->progress && !flags->parsable) |
| return (err); |
| |
| if (err != 0) { |
| zfs_error_aux(zhp->zfs_hdl, "%s", strerror(err)); |
| return (zfs_error(zhp->zfs_hdl, EZFS_BADBACKUP, |
| errbuf)); |
| } |
| send_print_verbose(fout, zhp->zfs_name, from, size, |
| flags->parsable); |
| |
| if (flags->parsable) { |
| (void) fprintf(fout, "size\t%llu\n", (longlong_t)size); |
| } else { |
| char buf[16]; |
| zfs_nicenum(size, buf, sizeof (buf)); |
| (void) fprintf(fout, dgettext(TEXT_DOMAIN, |
| "total estimated size is %s\n"), buf); |
| } |
| return (0); |
| } |
| |
| static boolean_t |
| redact_snaps_contains(const uint64_t *snaps, uint64_t num_snaps, uint64_t guid) |
| { |
| for (int i = 0; i < num_snaps; i++) { |
| if (snaps[i] == guid) |
| return (B_TRUE); |
| } |
| return (B_FALSE); |
| } |
| |
| static boolean_t |
| redact_snaps_equal(const uint64_t *snaps1, uint64_t num_snaps1, |
| const uint64_t *snaps2, uint64_t num_snaps2) |
| { |
| if (num_snaps1 != num_snaps2) |
| return (B_FALSE); |
| for (int i = 0; i < num_snaps1; i++) { |
| if (!redact_snaps_contains(snaps2, num_snaps2, snaps1[i])) |
| return (B_FALSE); |
| } |
| return (B_TRUE); |
| } |
| |
| /* |
| * Check that the list of redaction snapshots in the bookmark matches the send |
| * we're resuming, and return whether or not it's complete. |
| * |
| * Note that the caller needs to free the contents of *bookname with free() if |
| * this function returns successfully. |
| */ |
| static int |
| find_redact_book(libzfs_handle_t *hdl, const char *path, |
| const uint64_t *redact_snap_guids, int num_redact_snaps, |
| char **bookname) |
| { |
| char errbuf[1024]; |
| int error = 0; |
| nvlist_t *props = fnvlist_alloc(); |
| nvlist_t *bmarks; |
| |
| (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, |
| "cannot resume send")); |
| |
| fnvlist_add_boolean(props, "redact_complete"); |
| fnvlist_add_boolean(props, zfs_prop_to_name(ZFS_PROP_REDACT_SNAPS)); |
| error = lzc_get_bookmarks(path, props, &bmarks); |
| fnvlist_free(props); |
| if (error != 0) { |
| if (error == ESRCH) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "nonexistent redaction bookmark provided")); |
| } else if (error == ENOENT) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "dataset to be sent no longer exists")); |
| } else { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "unknown error: %s"), strerror(error)); |
| } |
| return (zfs_error(hdl, EZFS_BADPROP, errbuf)); |
| } |
| nvpair_t *pair; |
| for (pair = nvlist_next_nvpair(bmarks, NULL); pair; |
| pair = nvlist_next_nvpair(bmarks, pair)) { |
| |
| nvlist_t *bmark = fnvpair_value_nvlist(pair); |
| nvlist_t *vallist = fnvlist_lookup_nvlist(bmark, |
| zfs_prop_to_name(ZFS_PROP_REDACT_SNAPS)); |
| uint_t len = 0; |
| uint64_t *bmarksnaps = fnvlist_lookup_uint64_array(vallist, |
| ZPROP_VALUE, &len); |
| if (redact_snaps_equal(redact_snap_guids, |
| num_redact_snaps, bmarksnaps, len)) { |
| break; |
| } |
| } |
| if (pair == NULL) { |
| fnvlist_free(bmarks); |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "no appropriate redaction bookmark exists")); |
| return (zfs_error(hdl, EZFS_BADPROP, errbuf)); |
| } |
| char *name = nvpair_name(pair); |
| nvlist_t *bmark = fnvpair_value_nvlist(pair); |
| nvlist_t *vallist = fnvlist_lookup_nvlist(bmark, "redact_complete"); |
| boolean_t complete = fnvlist_lookup_boolean_value(vallist, |
| ZPROP_VALUE); |
| if (!complete) { |
| fnvlist_free(bmarks); |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "incomplete redaction bookmark provided")); |
| return (zfs_error(hdl, EZFS_BADPROP, errbuf)); |
| } |
| *bookname = strndup(name, ZFS_MAX_DATASET_NAME_LEN); |
| ASSERT3P(*bookname, !=, NULL); |
| fnvlist_free(bmarks); |
| return (0); |
| } |
| |
| static int |
| zfs_send_resume_impl(libzfs_handle_t *hdl, sendflags_t *flags, int outfd, |
| nvlist_t *resume_nvl) |
| { |
| char errbuf[1024]; |
| char *toname; |
| char *fromname = NULL; |
| uint64_t resumeobj, resumeoff, toguid, fromguid, bytes; |
| zfs_handle_t *zhp; |
| int error = 0; |
| char name[ZFS_MAX_DATASET_NAME_LEN]; |
| enum lzc_send_flags lzc_flags = 0; |
| FILE *fout = (flags->verbosity > 0 && flags->dryrun) ? stdout : stderr; |
| uint64_t *redact_snap_guids = NULL; |
| int num_redact_snaps = 0; |
| char *redact_book = NULL; |
| uint64_t size = 0; |
| |
| (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, |
| "cannot resume send")); |
| |
| if (flags->verbosity != 0) { |
| (void) fprintf(fout, dgettext(TEXT_DOMAIN, |
| "resume token contents:\n")); |
| nvlist_print(fout, resume_nvl); |
| } |
| |
| if (nvlist_lookup_string(resume_nvl, "toname", &toname) != 0 || |
| nvlist_lookup_uint64(resume_nvl, "object", &resumeobj) != 0 || |
| nvlist_lookup_uint64(resume_nvl, "offset", &resumeoff) != 0 || |
| nvlist_lookup_uint64(resume_nvl, "bytes", &bytes) != 0 || |
| nvlist_lookup_uint64(resume_nvl, "toguid", &toguid) != 0) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "resume token is corrupt")); |
| return (zfs_error(hdl, EZFS_FAULT, errbuf)); |
| } |
| fromguid = 0; |
| (void) nvlist_lookup_uint64(resume_nvl, "fromguid", &fromguid); |
| |
| if (flags->largeblock || nvlist_exists(resume_nvl, "largeblockok")) |
| lzc_flags |= LZC_SEND_FLAG_LARGE_BLOCK; |
| if (flags->embed_data || nvlist_exists(resume_nvl, "embedok")) |
| lzc_flags |= LZC_SEND_FLAG_EMBED_DATA; |
| if (flags->compress || nvlist_exists(resume_nvl, "compressok")) |
| lzc_flags |= LZC_SEND_FLAG_COMPRESS; |
| if (flags->raw || nvlist_exists(resume_nvl, "rawok")) |
| lzc_flags |= LZC_SEND_FLAG_RAW; |
| if (flags->saved || nvlist_exists(resume_nvl, "savedok")) |
| lzc_flags |= LZC_SEND_FLAG_SAVED; |
| if (flags->saved || nvlist_exists(resume_nvl, "blockdiff")) |
| lzc_flags |= LZC_SEND_FLAG_BLOCKDIFF; |
| |
| if (flags->saved) { |
| (void) strcpy(name, toname); |
| } else { |
| error = guid_to_name(hdl, toname, toguid, B_FALSE, name); |
| if (error != 0) { |
| if (zfs_dataset_exists(hdl, toname, ZFS_TYPE_DATASET)) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "'%s' is no longer the same snapshot " |
| "used in the initial send"), toname); |
| } else { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "'%s' used in the initial send no " |
| "longer exists"), toname); |
| } |
| return (zfs_error(hdl, EZFS_BADPATH, errbuf)); |
| } |
| } |
| |
| zhp = zfs_open(hdl, name, ZFS_TYPE_DATASET); |
| if (zhp == NULL) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "unable to access '%s'"), name); |
| return (zfs_error(hdl, EZFS_BADPATH, errbuf)); |
| } |
| |
| if (nvlist_lookup_uint64_array(resume_nvl, "book_redact_snaps", |
| &redact_snap_guids, (uint_t *)&num_redact_snaps) != 0) { |
| num_redact_snaps = -1; |
| } |
| |
| if (fromguid != 0) { |
| if (guid_to_name_redact_snaps(hdl, toname, fromguid, B_TRUE, |
| redact_snap_guids, num_redact_snaps, name) != 0) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "incremental source %#llx no longer exists"), |
| (longlong_t)fromguid); |
| return (zfs_error(hdl, EZFS_BADPATH, errbuf)); |
| } |
| fromname = name; |
| } |
| |
| redact_snap_guids = NULL; |
| |
| if (nvlist_lookup_uint64_array(resume_nvl, |
| zfs_prop_to_name(ZFS_PROP_REDACT_SNAPS), &redact_snap_guids, |
| (uint_t *)&num_redact_snaps) == 0) { |
| char path[ZFS_MAX_DATASET_NAME_LEN]; |
| |
| (void) strlcpy(path, toname, sizeof (path)); |
| char *at = strchr(path, '@'); |
| ASSERT3P(at, !=, NULL); |
| |
| *at = '\0'; |
| |
| if ((error = find_redact_book(hdl, path, redact_snap_guids, |
| num_redact_snaps, &redact_book)) != 0) { |
| return (error); |
| } |
| } |
| |
| if (flags->verbosity != 0 || flags->progressastitle) { |
| /* |
| * Some of these may have come from the resume token, set them |
| * here for size estimate purposes. |
| */ |
| sendflags_t tmpflags = *flags; |
| if (lzc_flags & LZC_SEND_FLAG_LARGE_BLOCK) |
| tmpflags.largeblock = B_TRUE; |
| if (lzc_flags & LZC_SEND_FLAG_COMPRESS) |
| tmpflags.compress = B_TRUE; |
| if (lzc_flags & LZC_SEND_FLAG_EMBED_DATA) |
| tmpflags.embed_data = B_TRUE; |
| if (lzc_flags & LZC_SEND_FLAG_RAW) |
| tmpflags.raw = B_TRUE; |
| if (lzc_flags & LZC_SEND_FLAG_SAVED) |
| tmpflags.saved = B_TRUE; |
| if (lzc_flags & LZC_SEND_FLAG_BLOCKDIFF) |
| tmpflags.block_diff = B_TRUE; |
| error = estimate_size(zhp, fromname, outfd, &tmpflags, |
| resumeobj, resumeoff, bytes, redact_book, errbuf, &size); |
| } |
| |
| if (!flags->dryrun) { |
| progress_arg_t pa = { 0 }; |
| pthread_t tid; |
| /* |
| * If progress reporting is requested, spawn a new thread to |
| * poll ZFS_IOC_SEND_PROGRESS at a regular interval. |
| */ |
| if (flags->progress || flags->progressastitle) { |
| pa.pa_zhp = zhp; |
| pa.pa_fd = outfd; |
| pa.pa_parsable = flags->parsable; |
| pa.pa_estimate = B_FALSE; |
| pa.pa_verbosity = flags->verbosity; |
| pa.pa_size = size; |
| pa.pa_astitle = flags->progressastitle; |
| pa.pa_progress = flags->progress; |
| |
| error = pthread_create(&tid, NULL, |
| send_progress_thread, &pa); |
| if (error != 0) { |
| if (redact_book != NULL) |
| free(redact_book); |
| zfs_close(zhp); |
| return (error); |
| } |
| } |
| |
| error = lzc_send_resume_redacted(zhp->zfs_name, fromname, outfd, |
| lzc_flags, resumeobj, resumeoff, redact_book); |
| if (redact_book != NULL) |
| free(redact_book); |
| |
| if (flags->progress || flags->progress) { |
| void *status = NULL; |
| (void) pthread_cancel(tid); |
| (void) pthread_join(tid, &status); |
| int error = (int)(uintptr_t)status; |
| if (error != 0 && status != PTHREAD_CANCELED) { |
| char errbuf[1024]; |
| (void) snprintf(errbuf, sizeof (errbuf), |
| dgettext(TEXT_DOMAIN, |
| "progress thread exited nonzero")); |
| zfs_close(zhp); |
| return (zfs_standard_error(hdl, error, errbuf)); |
| } |
| } |
| |
| char errbuf[1024]; |
| (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, |
| "warning: cannot send '%s'"), zhp->zfs_name); |
| |
| zfs_close(zhp); |
| |
| switch (error) { |
| case 0: |
| return (0); |
| case EACCES: |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "source key must be loaded")); |
| return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf)); |
| case ESRCH: |
| if (lzc_exists(zhp->zfs_name)) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "incremental source could not be found")); |
| } |
| return (zfs_error(hdl, EZFS_NOENT, errbuf)); |
| |
| case EXDEV: |
| case ENOENT: |
| case EDQUOT: |
| case EFBIG: |
| case EIO: |
| case ENOLINK: |
| case ENOSPC: |
| case ENOSTR: |
| case ENXIO: |
| case EPIPE: |
| case ERANGE: |
| case EFAULT: |
| case EROFS: |
| zfs_error_aux(hdl, "%s", strerror(errno)); |
| return (zfs_error(hdl, EZFS_BADBACKUP, errbuf)); |
| |
| default: |
| return (zfs_standard_error(hdl, errno, errbuf)); |
| } |
| } else { |
| if (redact_book != NULL) |
| free(redact_book); |
| } |
| |
| zfs_close(zhp); |
| |
| return (error); |
| } |
| |
| int |
| zfs_send_resume(libzfs_handle_t *hdl, sendflags_t *flags, int outfd, |
| const char *resume_token) |
| { |
| int ret; |
| char errbuf[1024]; |
| nvlist_t *resume_nvl; |
| |
| (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, |
| "cannot resume send")); |
| |
| resume_nvl = zfs_send_resume_token_to_nvlist(hdl, resume_token); |
| if (resume_nvl == NULL) { |
| /* |
| * zfs_error_aux has already been set by |
| * zfs_send_resume_token_to_nvlist() |
| */ |
| return (zfs_error(hdl, EZFS_FAULT, errbuf)); |
| } |
| |
| ret = zfs_send_resume_impl(hdl, flags, outfd, resume_nvl); |
| fnvlist_free(resume_nvl); |
| |
| return (ret); |
| } |
| |
| int |
| zfs_send_saved(zfs_handle_t *zhp, sendflags_t *flags, int outfd, |
| const char *resume_token) |
| { |
| int ret; |
| libzfs_handle_t *hdl = zhp->zfs_hdl; |
| nvlist_t *saved_nvl = NULL, *resume_nvl = NULL; |
| uint64_t saved_guid = 0, resume_guid = 0; |
| uint64_t obj = 0, off = 0, bytes = 0; |
| char token_buf[ZFS_MAXPROPLEN]; |
| char errbuf[1024]; |
| |
| (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, |
| "saved send failed")); |
| |
| ret = zfs_prop_get(zhp, ZFS_PROP_RECEIVE_RESUME_TOKEN, |
| token_buf, sizeof (token_buf), NULL, NULL, 0, B_TRUE); |
| if (ret != 0) |
| goto out; |
| |
| saved_nvl = zfs_send_resume_token_to_nvlist(hdl, token_buf); |
| if (saved_nvl == NULL) { |
| /* |
| * zfs_error_aux has already been set by |
| * zfs_send_resume_token_to_nvlist() |
| */ |
| ret = zfs_error(hdl, EZFS_FAULT, errbuf); |
| goto out; |
| } |
| |
| /* |
| * If a resume token is provided we use the object and offset |
| * from that instead of the default, which starts from the |
| * beginning. |
| */ |
| if (resume_token != NULL) { |
| resume_nvl = zfs_send_resume_token_to_nvlist(hdl, |
| resume_token); |
| if (resume_nvl == NULL) { |
| ret = zfs_error(hdl, EZFS_FAULT, errbuf); |
| goto out; |
| } |
| |
| if (nvlist_lookup_uint64(resume_nvl, "object", &obj) != 0 || |
| nvlist_lookup_uint64(resume_nvl, "offset", &off) != 0 || |
| nvlist_lookup_uint64(resume_nvl, "bytes", &bytes) != 0 || |
| nvlist_lookup_uint64(resume_nvl, "toguid", |
| &resume_guid) != 0) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "provided resume token is corrupt")); |
| ret = zfs_error(hdl, EZFS_FAULT, errbuf); |
| goto out; |
| } |
| |
| if (nvlist_lookup_uint64(saved_nvl, "toguid", |
| &saved_guid)) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "dataset's resume token is corrupt")); |
| ret = zfs_error(hdl, EZFS_FAULT, errbuf); |
| goto out; |
| } |
| |
| if (resume_guid != saved_guid) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "provided resume token does not match dataset")); |
| ret = zfs_error(hdl, EZFS_BADBACKUP, errbuf); |
| goto out; |
| } |
| } |
| |
| (void) nvlist_remove_all(saved_nvl, "object"); |
| fnvlist_add_uint64(saved_nvl, "object", obj); |
| |
| (void) nvlist_remove_all(saved_nvl, "offset"); |
| fnvlist_add_uint64(saved_nvl, "offset", off); |
| |
| (void) nvlist_remove_all(saved_nvl, "bytes"); |
| fnvlist_add_uint64(saved_nvl, "bytes", bytes); |
| |
| (void) nvlist_remove_all(saved_nvl, "toname"); |
| fnvlist_add_string(saved_nvl, "toname", zhp->zfs_name); |
| |
| ret = zfs_send_resume_impl(hdl, flags, outfd, saved_nvl); |
| |
| out: |
| fnvlist_free(saved_nvl); |
| fnvlist_free(resume_nvl); |
| return (ret); |
| } |
| |
| /* |
| * This function informs the target system that the recursive send is complete. |
| * The record is also expected in the case of a send -p. |
| */ |
| static int |
| send_conclusion_record(int fd, zio_cksum_t *zc) |
| { |
| dmu_replay_record_t drr = { 0 }; |
| drr.drr_type = DRR_END; |
| if (zc != NULL) |
| drr.drr_u.drr_end.drr_checksum = *zc; |
| if (write(fd, &drr, sizeof (drr)) == -1) { |
| return (errno); |
| } |
| return (0); |
| } |
| |
| /* |
| * This function is responsible for sending the records that contain the |
| * necessary information for the target system's libzfs to be able to set the |
| * properties of the filesystem being received, or to be able to prepare for |
| * a recursive receive. |
| * |
| * The "zhp" argument is the handle of the snapshot we are sending |
| * (the "tosnap"). The "from" argument is the short snapshot name (the part |
| * after the @) of the incremental source. |
| */ |
| static int |
| send_prelim_records(zfs_handle_t *zhp, const char *from, int fd, |
| boolean_t gather_props, boolean_t recursive, boolean_t verbose, |
| boolean_t dryrun, boolean_t raw, boolean_t replicate, boolean_t skipmissing, |
| boolean_t backup, boolean_t holds, boolean_t props, boolean_t doall, |
| nvlist_t **fssp, avl_tree_t **fsavlp) |
| { |
| int err = 0; |
| char *packbuf = NULL; |
| size_t buflen = 0; |
| zio_cksum_t zc = { {0} }; |
| int featureflags = 0; |
| /* name of filesystem/volume that contains snapshot we are sending */ |
| char tofs[ZFS_MAX_DATASET_NAME_LEN]; |
| /* short name of snap we are sending */ |
| char *tosnap = ""; |
| |
| char errbuf[1024]; |
| (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, |
| "warning: cannot send '%s'"), zhp->zfs_name); |
| if (zhp->zfs_type == ZFS_TYPE_FILESYSTEM && zfs_prop_get_int(zhp, |
| ZFS_PROP_VERSION) >= ZPL_VERSION_SA) { |
| featureflags |= DMU_BACKUP_FEATURE_SA_SPILL; |
| } |
| |
| if (holds) |
| featureflags |= DMU_BACKUP_FEATURE_HOLDS; |
| |
| (void) strlcpy(tofs, zhp->zfs_name, ZFS_MAX_DATASET_NAME_LEN); |
| char *at = strchr(tofs, '@'); |
| if (at != NULL) { |
| *at = '\0'; |
| tosnap = at + 1; |
| } |
| |
| if (gather_props) { |
| nvlist_t *hdrnv = fnvlist_alloc(); |
| nvlist_t *fss = NULL; |
| |
| if (from != NULL) |
| fnvlist_add_string(hdrnv, "fromsnap", from); |
| fnvlist_add_string(hdrnv, "tosnap", tosnap); |
| if (!recursive) |
| fnvlist_add_boolean(hdrnv, "not_recursive"); |
| |
| if (raw) { |
| fnvlist_add_boolean(hdrnv, "raw"); |
| } |
| |
| if ((err = gather_nvlist(zhp->zfs_hdl, tofs, |
| from, tosnap, recursive, raw, doall, replicate, skipmissing, |
| verbose, backup, holds, props, &fss, fsavlp)) != 0) { |
| return (zfs_error(zhp->zfs_hdl, EZFS_BADBACKUP, |
| errbuf)); |
| } |
| /* |
| * Do not allow the size of the properties list to exceed |
| * the limit |
| */ |
| if ((fnvlist_size(fss) + fnvlist_size(hdrnv)) > |
| zhp->zfs_hdl->libzfs_max_nvlist) { |
| (void) snprintf(errbuf, sizeof (errbuf), |
| dgettext(TEXT_DOMAIN, "warning: cannot send '%s': " |
| "the size of the list of snapshots and properties " |
| "is too large to be received successfully.\n" |
| "Select a smaller number of snapshots to send.\n"), |
| zhp->zfs_name); |
| return (zfs_error(zhp->zfs_hdl, EZFS_NOSPC, |
| errbuf)); |
| } |
| fnvlist_add_nvlist(hdrnv, "fss", fss); |
| VERIFY0(nvlist_pack(hdrnv, &packbuf, &buflen, NV_ENCODE_XDR, |
| 0)); |
| if (fssp != NULL) { |
| *fssp = fss; |
| } else { |
| fnvlist_free(fss); |
| } |
| fnvlist_free(hdrnv); |
| } |
| |
| if (!dryrun) { |
| dmu_replay_record_t drr = { 0 }; |
| /* write first begin record */ |
| drr.drr_type = DRR_BEGIN; |
| drr.drr_u.drr_begin.drr_magic = DMU_BACKUP_MAGIC; |
| DMU_SET_STREAM_HDRTYPE(drr.drr_u.drr_begin. |
| drr_versioninfo, DMU_COMPOUNDSTREAM); |
| DMU_SET_FEATUREFLAGS(drr.drr_u.drr_begin. |
| drr_versioninfo, featureflags); |
| if (snprintf(drr.drr_u.drr_begin.drr_toname, |
| sizeof (drr.drr_u.drr_begin.drr_toname), "%s@%s", tofs, |
| tosnap) >= sizeof (drr.drr_u.drr_begin.drr_toname)) { |
| return (zfs_error(zhp->zfs_hdl, EZFS_BADBACKUP, |
| errbuf)); |
| } |
| drr.drr_payloadlen = buflen; |
| |
| err = dump_record(&drr, packbuf, buflen, &zc, fd); |
| free(packbuf); |
| if (err != 0) { |
| zfs_error_aux(zhp->zfs_hdl, "%s", strerror(err)); |
| return (zfs_error(zhp->zfs_hdl, EZFS_BADBACKUP, |
| errbuf)); |
| } |
| err = send_conclusion_record(fd, &zc); |
| if (err != 0) { |
| zfs_error_aux(zhp->zfs_hdl, "%s", strerror(err)); |
| return (zfs_error(zhp->zfs_hdl, EZFS_BADBACKUP, |
| errbuf)); |
| } |
| } |
| return (0); |
| } |
| |
| /* |
| * Generate a send stream. The "zhp" argument is the filesystem/volume |
| * that contains the snapshot to send. The "fromsnap" argument is the |
| * short name (the part after the '@') of the snapshot that is the |
| * incremental source to send from (if non-NULL). The "tosnap" argument |
| * is the short name of the snapshot to send. |
| * |
| * The content of the send stream is the snapshot identified by |
| * 'tosnap'. Incremental streams are requested in two ways: |
| * - from the snapshot identified by "fromsnap" (if non-null) or |
| * - from the origin of the dataset identified by zhp, which must |
| * be a clone. In this case, "fromsnap" is null and "fromorigin" |
| * is TRUE. |
| * |
| * The send stream is recursive (i.e. dumps a hierarchy of snapshots) and |
| * uses a special header (with a hdrtype field of DMU_COMPOUNDSTREAM) |
| * if "replicate" is set. If "doall" is set, dump all the intermediate |
| * snapshots. The DMU_COMPOUNDSTREAM header is used in the "doall" |
| * case too. If "props" is set, send properties. |
| */ |
| int |
| zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap, |
| sendflags_t *flags, int outfd, snapfilter_cb_t filter_func, |
| void *cb_arg, nvlist_t **debugnvp) |
| { |
| char errbuf[1024]; |
| send_dump_data_t sdd = { 0 }; |
| int err = 0; |
| nvlist_t *fss = NULL; |
| avl_tree_t *fsavl = NULL; |
| static uint64_t holdseq; |
| int spa_version; |
| FILE *fout; |
| |
| (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, |
| "cannot send '%s'"), zhp->zfs_name); |
| |
| if (fromsnap && fromsnap[0] == '\0') { |
| zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, |
| "zero-length incremental source")); |
| return (zfs_error(zhp->zfs_hdl, EZFS_NOENT, errbuf)); |
| } |
| |
| if (fromsnap) { |
| char full_fromsnap_name[ZFS_MAX_DATASET_NAME_LEN]; |
| if (snprintf(full_fromsnap_name, sizeof (full_fromsnap_name), |
| "%s@%s", zhp->zfs_name, fromsnap) >= |
| sizeof (full_fromsnap_name)) { |
| err = EINVAL; |
| goto stderr_out; |
| } |
| zfs_handle_t *fromsnapn = zfs_open(zhp->zfs_hdl, |
| full_fromsnap_name, ZFS_TYPE_SNAPSHOT); |
| if (fromsnapn == NULL) { |
| err = -1; |
| goto err_out; |
| } |
| zfs_close(fromsnapn); |
| } |
| |
| if (flags->replicate || flags->doall || flags->props || |
| flags->holds || flags->backup) { |
| char full_tosnap_name[ZFS_MAX_DATASET_NAME_LEN]; |
| if (snprintf(full_tosnap_name, sizeof (full_tosnap_name), |
| "%s@%s", zhp->zfs_name, tosnap) >= |
| sizeof (full_tosnap_name)) { |
| err = EINVAL; |
| goto stderr_out; |
| } |
| zfs_handle_t *tosnap = zfs_open(zhp->zfs_hdl, |
| full_tosnap_name, ZFS_TYPE_SNAPSHOT); |
| if (tosnap == NULL) { |
| err = -1; |
| goto err_out; |
| } |
| err = send_prelim_records(tosnap, fromsnap, outfd, |
| flags->replicate || flags->props || flags->holds, |
| flags->replicate, flags->verbosity > 0, flags->dryrun, |
| flags->raw, flags->replicate, flags->skipmissing, |
| flags->backup, flags->holds, flags->props, flags->doall, |
| &fss, &fsavl); |
| zfs_close(tosnap); |
| if (err != 0) |
| goto err_out; |
| } |
| |
| /* dump each stream */ |
| sdd.fromsnap = fromsnap; |
| sdd.tosnap = tosnap; |
| sdd.outfd = outfd; |
| sdd.replicate = flags->replicate; |
| sdd.doall = flags->doall; |
| sdd.fromorigin = flags->fromorigin; |
| sdd.fss = fss; |
| sdd.fsavl = fsavl; |
| sdd.verbosity = flags->verbosity; |
| sdd.parsable = flags->parsable; |
| sdd.progress = flags->progress; |
| sdd.progressastitle = flags->progressastitle; |
| sdd.dryrun = flags->dryrun; |
| sdd.large_block = flags->largeblock; |
| sdd.embed_data = flags->embed_data; |
| sdd.block_diff = flags->block_diff; |
| sdd.compress = flags->compress; |
| sdd.raw = flags->raw; |
| sdd.holds = flags->holds; |
| sdd.filter_cb = filter_func; |
| sdd.filter_cb_arg = cb_arg; |
| if (debugnvp) |
| sdd.debugnv = *debugnvp; |
| if (sdd.verbosity != 0 && sdd.dryrun) |
| sdd.std_out = B_TRUE; |
| fout = sdd.std_out ? stdout : stderr; |
| |
| /* |
| * Some flags require that we place user holds on the datasets that are |
| * being sent so they don't get destroyed during the send. We can skip |
| * this step if the pool is imported read-only since the datasets cannot |
| * be destroyed. |
| */ |
| if (!flags->dryrun && !zpool_get_prop_int(zfs_get_pool_handle(zhp), |
| ZPOOL_PROP_READONLY, NULL) && |
| zfs_spa_version(zhp, &spa_version) == 0 && |
| spa_version >= SPA_VERSION_USERREFS && |
| (flags->doall || flags->replicate)) { |
| ++holdseq; |
| (void) snprintf(sdd.holdtag, sizeof (sdd.holdtag), |
| ".send-%d-%llu", getpid(), (u_longlong_t)holdseq); |
| sdd.cleanup_fd = open(ZFS_DEV, O_RDWR | O_CLOEXEC); |
| if (sdd.cleanup_fd < 0) { |
| err = errno; |
| goto stderr_out; |
| } |
| sdd.snapholds = fnvlist_alloc(); |
| } else { |
| sdd.cleanup_fd = -1; |
| sdd.snapholds = NULL; |
| } |
| |
| if (flags->verbosity != 0 || sdd.snapholds != NULL) { |
| /* |
| * Do a verbose no-op dry run to get all the verbose output |
| * or to gather snapshot hold's before generating any data, |
| * then do a non-verbose real run to generate the streams. |
| */ |
| sdd.dryrun = B_TRUE; |
| err = dump_filesystems(zhp, &sdd); |
| |
| if (err != 0) |
| goto stderr_out; |
| |
| if (flags->verbosity != 0) { |
| if (flags->parsable) { |
| (void) fprintf(fout, "size\t%llu\n", |
| (longlong_t)sdd.size); |
| } else { |
| char buf[16]; |
| zfs_nicebytes(sdd.size, buf, sizeof (buf)); |
| (void) fprintf(fout, dgettext(TEXT_DOMAIN, |
| "total estimated size is %s\n"), buf); |
| } |
| } |
| |
| /* Ensure no snaps found is treated as an error. */ |
| if (!sdd.seento) { |
| err = ENOENT; |
| goto err_out; |
| } |
| |
| /* Skip the second run if dryrun was requested. */ |
| if (flags->dryrun) |
| goto err_out; |
| |
| if (sdd.snapholds != NULL) { |
| err = zfs_hold_nvl(zhp, sdd.cleanup_fd, sdd.snapholds); |
| if (err != 0) |
| goto stderr_out; |
| |
| fnvlist_free(sdd.snapholds); |
| sdd.snapholds = NULL; |
| } |
| |
| sdd.dryrun = B_FALSE; |
| sdd.verbosity = 0; |
| } |
| |
| err = dump_filesystems(zhp, &sdd); |
| fsavl_destroy(fsavl); |
| fnvlist_free(fss); |
| |
| /* Ensure no snaps found is treated as an error. */ |
| if (err == 0 && !sdd.seento) |
| err = ENOENT; |
| |
| if (sdd.cleanup_fd != -1) { |
| VERIFY(0 == close(sdd.cleanup_fd)); |
| sdd.cleanup_fd = -1; |
| } |
| |
| if (!flags->dryrun && (flags->replicate || flags->doall || |
| flags->props || flags->backup || flags->holds)) { |
| /* |
| * write final end record. NB: want to do this even if |
| * there was some error, because it might not be totally |
| * failed. |
| */ |
| err = send_conclusion_record(outfd, NULL); |
| if (err != 0) |
| return (zfs_standard_error(zhp->zfs_hdl, err, errbuf)); |
| } |
| |
| return (err || sdd.err); |
| |
| stderr_out: |
| err = zfs_standard_error(zhp->zfs_hdl, err, errbuf); |
| err_out: |
| fsavl_destroy(fsavl); |
| fnvlist_free(fss); |
| fnvlist_free(sdd.snapholds); |
| |
| if (sdd.cleanup_fd != -1) |
| VERIFY(0 == close(sdd.cleanup_fd)); |
| return (err); |
| } |
| |
| static zfs_handle_t * |
| name_to_dir_handle(libzfs_handle_t *hdl, const char *snapname) |
| { |
| char dirname[ZFS_MAX_DATASET_NAME_LEN]; |
| (void) strlcpy(dirname, snapname, ZFS_MAX_DATASET_NAME_LEN); |
| char *c = strchr(dirname, '@'); |
| if (c != NULL) |
| *c = '\0'; |
| return (zfs_open(hdl, dirname, ZFS_TYPE_DATASET)); |
| } |
| |
| /* |
| * Returns B_TRUE if earlier is an earlier snapshot in later's timeline; either |
| * an earlier snapshot in the same filesystem, or a snapshot before later's |
| * origin, or it's origin's origin, etc. |
| */ |
| static boolean_t |
| snapshot_is_before(zfs_handle_t *earlier, zfs_handle_t *later) |
| { |
| boolean_t ret; |
| uint64_t later_txg = |
| (later->zfs_type == ZFS_TYPE_FILESYSTEM || |
| later->zfs_type == ZFS_TYPE_VOLUME ? |
| UINT64_MAX : zfs_prop_get_int(later, ZFS_PROP_CREATETXG)); |
| uint64_t earlier_txg = zfs_prop_get_int(earlier, ZFS_PROP_CREATETXG); |
| |
| if (earlier_txg >= later_txg) |
| return (B_FALSE); |
| |
| zfs_handle_t *earlier_dir = name_to_dir_handle(earlier->zfs_hdl, |
| earlier->zfs_name); |
| zfs_handle_t *later_dir = name_to_dir_handle(later->zfs_hdl, |
| later->zfs_name); |
| |
| if (strcmp(earlier_dir->zfs_name, later_dir->zfs_name) == 0) { |
| zfs_close(earlier_dir); |
| zfs_close(later_dir); |
| return (B_TRUE); |
| } |
| |
| char clonename[ZFS_MAX_DATASET_NAME_LEN]; |
| if (zfs_prop_get(later_dir, ZFS_PROP_ORIGIN, clonename, |
| ZFS_MAX_DATASET_NAME_LEN, NULL, NULL, 0, B_TRUE) != 0) { |
| zfs_close(earlier_dir); |
| zfs_close(later_dir); |
| return (B_FALSE); |
| } |
| |
| zfs_handle_t *origin = zfs_open(earlier->zfs_hdl, clonename, |
| ZFS_TYPE_DATASET); |
| uint64_t origin_txg = zfs_prop_get_int(origin, ZFS_PROP_CREATETXG); |
| |
| /* |
| * If "earlier" is exactly the origin, then |
| * snapshot_is_before(earlier, origin) will return false (because |
| * they're the same). |
| */ |
| if (origin_txg == earlier_txg && |
| strcmp(origin->zfs_name, earlier->zfs_name) == 0) { |
| zfs_close(earlier_dir); |
| zfs_close(later_dir); |
| zfs_close(origin); |
| return (B_TRUE); |
| } |
| zfs_close(earlier_dir); |
| zfs_close(later_dir); |
| |
| ret = snapshot_is_before(earlier, origin); |
| zfs_close(origin); |
| return (ret); |
| } |
| |
| /* |
| * The "zhp" argument is the handle of the dataset to send (typically a |
| * snapshot). The "from" argument is the full name of the snapshot or |
| * bookmark that is the incremental source. |
| */ |
| int |
| zfs_send_one(zfs_handle_t *zhp, const char *from, int fd, sendflags_t *flags, |
| const char *redactbook) |
| { |
| int err; |
| libzfs_handle_t *hdl = zhp->zfs_hdl; |
| char *name = zhp->zfs_name; |
| pthread_t ptid; |
| progress_arg_t pa = { 0 }; |
| uint64_t size = 0; |
| |
| char errbuf[1024]; |
| (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, |
| "warning: cannot send '%s'"), name); |
| |
| if (from != NULL && strchr(from, '@')) { |
| zfs_handle_t *from_zhp = zfs_open(hdl, from, |
| ZFS_TYPE_DATASET); |
| if (from_zhp == NULL) |
| return (-1); |
| if (!snapshot_is_before(from_zhp, zhp)) { |
| zfs_close(from_zhp); |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "not an earlier snapshot from the same fs")); |
| return (zfs_error(hdl, EZFS_CROSSTARGET, errbuf)); |
| } |
| zfs_close(from_zhp); |
| } |
| |
| if (redactbook != NULL) { |
| char bookname[ZFS_MAX_DATASET_NAME_LEN]; |
| nvlist_t *redact_snaps; |
| zfs_handle_t *book_zhp; |
| char *at, *pound; |
| int dsnamelen; |
| |
| pound = strchr(redactbook, '#'); |
| if (pound != NULL) |
| redactbook = pound + 1; |
| at = strchr(name, '@'); |
| if (at == NULL) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "cannot do a redacted send to a filesystem")); |
| return (zfs_error(hdl, EZFS_BADTYPE, errbuf)); |
| } |
| dsnamelen = at - name; |
| if (snprintf(bookname, sizeof (bookname), "%.*s#%s", |
| dsnamelen, name, redactbook) |
| >= sizeof (bookname)) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "invalid bookmark name")); |
| return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf)); |
| } |
| book_zhp = zfs_open(hdl, bookname, ZFS_TYPE_BOOKMARK); |
| if (book_zhp == NULL) |
| return (-1); |
| if (nvlist_lookup_nvlist(book_zhp->zfs_props, |
| zfs_prop_to_name(ZFS_PROP_REDACT_SNAPS), |
| &redact_snaps) != 0 || redact_snaps == NULL) { |
| zfs_close(book_zhp); |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "not a redaction bookmark")); |
| return (zfs_error(hdl, EZFS_BADTYPE, errbuf)); |
| } |
| zfs_close(book_zhp); |
| } |
| |
| // MLW - Uncomment to test flag continuity. |
| // fprintf(stderr, "flags->block_diff) %d", flags->block_diff); |
| |
| /* |
| * Send fs properties |
| */ |
| if (flags->props || flags->holds || flags->backup ) { |
| /* |
| * Note: the header generated by send_prelim_records() |
| * assumes that the incremental source is in the same |
| * filesystem/volume as the target (which is a requirement |
| * when doing "zfs send -R"). But that isn't always the |
| * case here (e.g. send from snap in origin, or send from |
| * bookmark). We pass from=NULL, which will omit this |
| * information from the prelim records; it isn't used |
| * when receiving this type of stream. |
| */ |
| err = send_prelim_records(zhp, NULL, fd, B_TRUE, B_FALSE, |
| flags->verbosity > 0, flags->dryrun, flags->raw, |
| flags->replicate, B_FALSE, flags->backup, flags->holds, |
| flags->props, flags->doall, NULL, NULL); |
| if (err != 0) |
| return (err); |
| } |
| |
| /* |
| * Perform size estimate if verbose was specified. |
| */ |
| if (flags->verbosity != 0 || flags->progressastitle) { |
| err = estimate_size(zhp, from, fd, flags, 0, 0, 0, redactbook, |
| errbuf, &size); |
| if (err != 0) |
| return (err); |
| } |
| |
| if (flags->dryrun) |
| return (0); |
| |
| /* |
| * If progress reporting is requested, spawn a new thread to poll |
| * ZFS_IOC_SEND_PROGRESS at a regular interval. |
| */ |
| if (flags->progress || flags->progressastitle) { |
| pa.pa_zhp = zhp; |
| pa.pa_fd = fd; |
| pa.pa_parsable = flags->parsable; |
| pa.pa_estimate = B_FALSE; |
| pa.pa_verbosity = flags->verbosity; |
| pa.pa_size = size; |
| pa.pa_astitle = flags->progressastitle; |
| pa.pa_progress = flags->progress; |
| |
| err = pthread_create(&ptid, NULL, |
| send_progress_thread, &pa); |
| if (err != 0) { |
| zfs_error_aux(zhp->zfs_hdl, "%s", strerror(errno)); |
| return (zfs_error(zhp->zfs_hdl, |
| EZFS_THREADCREATEFAILED, errbuf)); |
| } |
| } |
| |
| err = lzc_send_redacted(name, from, fd, |
| lzc_flags_from_sendflags(flags), redactbook); |
| |
| if (flags->progress || flags->progressastitle) { |
| void *status = NULL; |
| (void) pthread_cancel(ptid); |
| (void) pthread_join(ptid, &status); |
| int error = (int)(uintptr_t)status; |
| if (error != 0 && status != PTHREAD_CANCELED) |
| return (zfs_standard_error_fmt(hdl, error, |
| dgettext(TEXT_DOMAIN, |
| "progress thread exited nonzero"))); |
| } |
| |
| if (err == 0 && (flags->props || flags->holds || flags->backup || flags->block_diff)) { |
| /* Write the final end record. */ |
| err = send_conclusion_record(fd, NULL); |
| if (err != 0) |
| return (zfs_standard_error(hdl, err, errbuf)); |
| } |
| if (err != 0) { |
| switch (errno) { |
| case EXDEV: |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "not an earlier snapshot from the same fs")); |
| return (zfs_error(hdl, EZFS_CROSSTARGET, errbuf)); |
| |
| case ENOENT: |
| case ESRCH: |
| if (lzc_exists(name)) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "incremental source (%s) does not exist"), |
| from); |
| } |
| return (zfs_error(hdl, EZFS_NOENT, errbuf)); |
| |
| case EACCES: |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "dataset key must be loaded")); |
| return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf)); |
| |
| case EBUSY: |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "target is busy; if a filesystem, " |
| "it must not be mounted")); |
| return (zfs_error(hdl, EZFS_BUSY, errbuf)); |
| |
| case EDQUOT: |
| case EFAULT: |
| case EFBIG: |
| case EINVAL: |
| case EIO: |
| case ENOLINK: |
| case ENOSPC: |
| case ENOSTR: |
| case ENXIO: |
| case EPIPE: |
| case ERANGE: |
| case EROFS: |
| zfs_error_aux(hdl, "%s", strerror(errno)); |
| return (zfs_error(hdl, EZFS_BADBACKUP, errbuf)); |
| |
| default: |
| return (zfs_standard_error(hdl, errno, errbuf)); |
| } |
| } |
| return (err != 0); |
| } |
| |
| /* |
| * Routines specific to "zfs recv" |
| */ |
| |
| static int |
| recv_read(libzfs_handle_t *hdl, int fd, void *buf, int ilen, |
| boolean_t byteswap, zio_cksum_t *zc) |
| { |
| char *cp = buf; |
| int rv; |
| int len = ilen; |
| |
| do { |
| rv = read(fd, cp, len); |
| cp += rv; |
| len -= rv; |
| } while (rv > 0); |
| |
| if (rv < 0 || len != 0) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "failed to read from stream")); |
| return (zfs_error(hdl, EZFS_BADSTREAM, dgettext(TEXT_DOMAIN, |
| "cannot receive"))); |
| } |
| |
| if (zc) { |
| if (byteswap) |
| fletcher_4_incremental_byteswap(buf, ilen, zc); |
| else |
| fletcher_4_incremental_native(buf, ilen, zc); |
| } |
| return (0); |
| } |
| |
| static int |
| recv_read_nvlist(libzfs_handle_t *hdl, int fd, int len, nvlist_t **nvp, |
| boolean_t byteswap, zio_cksum_t *zc) |
| { |
| char *buf; |
| int err; |
| |
| buf = zfs_alloc(hdl, len); |
| if (buf == NULL) |
| return (ENOMEM); |
| |
| if (len > hdl->libzfs_max_nvlist) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "nvlist too large")); |
| free(buf); |
| return (ENOMEM); |
| } |
| |
| err = recv_read(hdl, fd, buf, len, byteswap, zc); |
| if (err != 0) { |
| free(buf); |
| return (err); |
| } |
| |
| err = nvlist_unpack(buf, len, nvp, 0); |
| free(buf); |
| if (err != 0) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid " |
| "stream (malformed nvlist)")); |
| return (EINVAL); |
| } |
| return (0); |
| } |
| |
| /* |
| * Returns the grand origin (origin of origin of origin...) of a given handle. |
| * If this dataset is not a clone, it simply returns a copy of the original |
| * handle. |
| */ |
| static zfs_handle_t * |
| recv_open_grand_origin(zfs_handle_t *zhp) |
| { |
| char origin[ZFS_MAX_DATASET_NAME_LEN]; |
| zprop_source_t src; |
| zfs_handle_t *ozhp = zfs_handle_dup(zhp); |
| |
| while (ozhp != NULL) { |
| if (zfs_prop_get(ozhp, ZFS_PROP_ORIGIN, origin, |
| sizeof (origin), &src, NULL, 0, B_FALSE) != 0) |
| break; |
| |
| (void) zfs_close(ozhp); |
| ozhp = zfs_open(zhp->zfs_hdl, origin, ZFS_TYPE_FILESYSTEM); |
| } |
| |
| return (ozhp); |
| } |
| |
| static int |
| recv_rename_impl(zfs_handle_t *zhp, const char *name, const char *newname) |
| { |
| int err; |
| zfs_handle_t *ozhp = NULL; |
| |
| /* |
| * Attempt to rename the dataset. If it fails with EACCES we have |
| * attempted to rename the dataset outside of its encryption root. |
| * Force the dataset to become an encryption root and try again. |
| */ |
| err = lzc_rename(name, newname); |
| if (err == EACCES) { |
| ozhp = recv_open_grand_origin(zhp); |
| if (ozhp == NULL) { |
| err = ENOENT; |
| goto out; |
| } |
| |
| err = lzc_change_key(ozhp->zfs_name, DCP_CMD_FORCE_NEW_KEY, |
| NULL, NULL, 0); |
| if (err != 0) |
| goto out; |
| |
| err = lzc_rename(name, newname); |
| } |
| |
| out: |
| if (ozhp != NULL) |
| zfs_close(ozhp); |
| return (err); |
| } |
| |
| static int |
| recv_rename(libzfs_handle_t *hdl, const char *name, const char *tryname, |
| int baselen, char *newname, recvflags_t *flags) |
| { |
| static int seq; |
| int err; |
| prop_changelist_t *clp = NULL; |
| zfs_handle_t *zhp = NULL; |
| |
| zhp = zfs_open(hdl, name, ZFS_TYPE_DATASET); |
| if (zhp == NULL) { |
| err = -1; |
| goto out; |
| } |
| clp = changelist_gather(zhp, ZFS_PROP_NAME, 0, |
| flags->force ? MS_FORCE : 0); |
| if (clp == NULL) { |
| err = -1; |
| goto out; |
| } |
| err = changelist_prefix(clp); |
| if (err) |
| goto out; |
| |
| if (tryname) { |
| (void) strcpy(newname, tryname); |
| if (flags->verbose) { |
| (void) printf("attempting rename %s to %s\n", |
| name, newname); |
| } |
| err = recv_rename_impl(zhp, name, newname); |
| if (err == 0) |
| changelist_rename(clp, name, tryname); |
| } else { |
| err = ENOENT; |
| } |
| |
| if (err != 0 && strncmp(name + baselen, "recv-", 5) != 0) { |
| seq++; |
| |
| (void) snprintf(newname, ZFS_MAX_DATASET_NAME_LEN, |
| "%.*srecv-%u-%u", baselen, name, getpid(), seq); |
| |
| if (flags->verbose) { |
| (void) printf("failed - trying rename %s to %s\n", |
| name, newname); |
| } |
| err = recv_rename_impl(zhp, name, newname); |
| if (err == 0) |
| changelist_rename(clp, name, newname); |
| if (err && flags->verbose) { |
| (void) printf("failed (%u) - " |
| "will try again on next pass\n", errno); |
| } |
| err = EAGAIN; |
| } else if (flags->verbose) { |
| if (err == 0) |
| (void) printf("success\n"); |
| else |
| (void) printf("failed (%u)\n", errno); |
| } |
| |
| (void) changelist_postfix(clp); |
| |
| out: |
| if (clp != NULL) |
| changelist_free(clp); |
| if (zhp != NULL) |
| zfs_close(zhp); |
| |
| return (err); |
| } |
| |
| static int |
| recv_promote(libzfs_handle_t *hdl, const char *fsname, |
| const char *origin_fsname, recvflags_t *flags) |
| { |
| int err; |
| zfs_cmd_t zc = {"\0"}; |
| zfs_handle_t *zhp = NULL, *ozhp = NULL; |
| |
| if (flags->verbose) |
| (void) printf("promoting %s\n", fsname); |
| |
| (void) strlcpy(zc.zc_value, origin_fsname, sizeof (zc.zc_value)); |
| (void) strlcpy(zc.zc_name, fsname, sizeof (zc.zc_name)); |
| |
| /* |
| * Attempt to promote the dataset. If it fails with EACCES the |
| * promotion would cause this dataset to leave its encryption root. |
| * Force the origin to become an encryption root and try again. |
| */ |
| err = zfs_ioctl(hdl, ZFS_IOC_PROMOTE, &zc); |
| if (err == EACCES) { |
| zhp = zfs_open(hdl, fsname, ZFS_TYPE_DATASET); |
| if (zhp == NULL) { |
| err = -1; |
| goto out; |
| } |
| |
| ozhp = recv_open_grand_origin(zhp); |
| if (ozhp == NULL) { |
| err = -1; |
| goto out; |
| } |
| |
| err = lzc_change_key(ozhp->zfs_name, DCP_CMD_FORCE_NEW_KEY, |
| NULL, NULL, 0); |
| if (err != 0) |
| goto out; |
| |
| err = zfs_ioctl(hdl, ZFS_IOC_PROMOTE, &zc); |
| } |
| |
| out: |
| if (zhp != NULL) |
| zfs_close(zhp); |
| if (ozhp != NULL) |
| zfs_close(ozhp); |
| |
| return (err); |
| } |
| |
| static int |
| recv_destroy(libzfs_handle_t *hdl, const char *name, int baselen, |
| char *newname, recvflags_t *flags) |
| { |
| int err = 0; |
| prop_changelist_t *clp; |
| zfs_handle_t *zhp; |
| boolean_t defer = B_FALSE; |
| int spa_version; |
| |
| zhp = zfs_open(hdl, name, ZFS_TYPE_DATASET); |
| if (zhp == NULL) |
| return (-1); |
| clp = changelist_gather(zhp, ZFS_PROP_NAME, 0, |
| flags->force ? MS_FORCE : 0); |
| if (zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT && |
| zfs_spa_version(zhp, &spa_version) == 0 && |
| spa_version >= SPA_VERSION_USERREFS) |
| defer = B_TRUE; |
| zfs_close(zhp); |
| if (clp == NULL) |
| return (-1); |
| err = changelist_prefix(clp); |
| if (err) |
| return (err); |
| |
| if (flags->verbose) |
| (void) printf("attempting destroy %s\n", name); |
| if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT) { |
| nvlist_t *nv = fnvlist_alloc(); |
| fnvlist_add_boolean(nv, name); |
| err = lzc_destroy_snaps(nv, defer, NULL); |
| fnvlist_free(nv); |
| } else { |
| err = lzc_destroy(name); |
| } |
| if (err == 0) { |
| if (flags->verbose) |
| (void) printf("success\n"); |
| changelist_remove(clp, name); |
| } |
| |
| (void) changelist_postfix(clp); |
| changelist_free(clp); |
| |
| /* |
| * Deferred destroy might destroy the snapshot or only mark it to be |
| * destroyed later, and it returns success in either case. |
| */ |
| if (err != 0 || (defer && zfs_dataset_exists(hdl, name, |
| ZFS_TYPE_SNAPSHOT))) { |
| err = recv_rename(hdl, name, NULL, baselen, newname, flags); |
| } |
| |
| return (err); |
| } |
| |
| typedef struct guid_to_name_data { |
| uint64_t guid; |
| boolean_t bookmark_ok; |
| char *name; |
| char *skip; |
| uint64_t *redact_snap_guids; |
| uint64_t num_redact_snaps; |
| } guid_to_name_data_t; |
| |
| static boolean_t |
| redact_snaps_match(zfs_handle_t *zhp, guid_to_name_data_t *gtnd) |
| { |
| uint64_t *bmark_snaps; |
| uint_t bmark_num_snaps; |
| nvlist_t *nvl; |
| if (zhp->zfs_type != ZFS_TYPE_BOOKMARK) |
| return (B_FALSE); |
| |
| nvl = fnvlist_lookup_nvlist(zhp->zfs_props, |
| zfs_prop_to_name(ZFS_PROP_REDACT_SNAPS)); |
| bmark_snaps = fnvlist_lookup_uint64_array(nvl, ZPROP_VALUE, |
| &bmark_num_snaps); |
| if (bmark_num_snaps != gtnd->num_redact_snaps) |
| return (B_FALSE); |
| int i = 0; |
| for (; i < bmark_num_snaps; i++) { |
| int j = 0; |
| for (; j < bmark_num_snaps; j++) { |
| if (bmark_snaps[i] == gtnd->redact_snap_guids[j]) |
| break; |
| } |
| if (j == bmark_num_snaps) |
| break; |
| } |
| return (i == bmark_num_snaps); |
| } |
| |
| static int |
| guid_to_name_cb(zfs_handle_t *zhp, void *arg) |
| { |
| guid_to_name_data_t *gtnd = arg; |
| const char *slash; |
| int err; |
| |
| if (gtnd->skip != NULL && |
| (slash = strrchr(zhp->zfs_name, '/')) != NULL && |
| strcmp(slash + 1, gtnd->skip) == 0) { |
| zfs_close(zhp); |
| return (0); |
| } |
| |
| if (zfs_prop_get_int(zhp, ZFS_PROP_GUID) == gtnd->guid && |
| (gtnd->num_redact_snaps == -1 || redact_snaps_match(zhp, gtnd))) { |
| (void) strcpy(gtnd->name, zhp->zfs_name); |
| zfs_close(zhp); |
| return (EEXIST); |
| } |
| |
| err = zfs_iter_children(zhp, guid_to_name_cb, gtnd); |
| if (err != EEXIST && gtnd->bookmark_ok) |
| err = zfs_iter_bookmarks(zhp, guid_to_name_cb, gtnd); |
| zfs_close(zhp); |
| return (err); |
| } |
| |
| /* |
| * Attempt to find the local dataset associated with this guid. In the case of |
| * multiple matches, we attempt to find the "best" match by searching |
| * progressively larger portions of the hierarchy. This allows one to send a |
| * tree of datasets individually and guarantee that we will find the source |
| * guid within that hierarchy, even if there are multiple matches elsewhere. |
| * |
| * If num_redact_snaps is not -1, we attempt to find a redaction bookmark with |
| * the specified number of redaction snapshots. If num_redact_snaps isn't 0 or |
| * -1, then redact_snap_guids will be an array of the guids of the snapshots the |
| * redaction bookmark was created with. If num_redact_snaps is -1, then we will |
| * attempt to find a snapshot or bookmark (if bookmark_ok is passed) with the |
| * given guid. Note that a redaction bookmark can be returned if |
| * num_redact_snaps == -1. |
| */ |
| static int |
| guid_to_name_redact_snaps(libzfs_handle_t *hdl, const char *parent, |
| uint64_t guid, boolean_t bookmark_ok, uint64_t *redact_snap_guids, |
| uint64_t num_redact_snaps, char *name) |
| { |
| char pname[ZFS_MAX_DATASET_NAME_LEN]; |
| guid_to_name_data_t gtnd; |
| |
| gtnd.guid = guid; |
| gtnd.bookmark_ok = bookmark_ok; |
| gtnd.name = name; |
| gtnd.skip = NULL; |
| gtnd.redact_snap_guids = redact_snap_guids; |
| gtnd.num_redact_snaps = num_redact_snaps; |
| |
| /* |
| * Search progressively larger portions of the hierarchy, starting |
| * with the filesystem specified by 'parent'. This will |
| * select the "most local" version of the origin snapshot in the case |
| * that there are multiple matching snapshots in the system. |
| */ |
| (void) strlcpy(pname, parent, sizeof (pname)); |
| char *cp = strrchr(pname, '@'); |
| if (cp == NULL) |
| cp = strchr(pname, '\0'); |
| for (; cp != NULL; cp = strrchr(pname, '/')) { |
| /* Chop off the last component and open the parent */ |
| *cp = '\0'; |
| zfs_handle_t *zhp = make_dataset_handle(hdl, pname); |
| |
| if (zhp == NULL) |
| continue; |
| int err = guid_to_name_cb(zfs_handle_dup(zhp), >nd); |
| if (err != EEXIST) |
| err = zfs_iter_children(zhp, guid_to_name_cb, >nd); |
| if (err != EEXIST && bookmark_ok) |
| err = zfs_iter_bookmarks(zhp, guid_to_name_cb, >nd); |
| zfs_close(zhp); |
| if (err == EEXIST) |
| return (0); |
| |
| /* |
| * Remember the last portion of the dataset so we skip it next |
| * time through (as we've already searched that portion of the |
| * hierarchy). |
| */ |
| gtnd.skip = strrchr(pname, '/') + 1; |
| } |
| |
| return (ENOENT); |
| } |
| |
| static int |
| guid_to_name(libzfs_handle_t *hdl, const char *parent, uint64_t guid, |
| boolean_t bookmark_ok, char *name) |
| { |
| return (guid_to_name_redact_snaps(hdl, parent, guid, bookmark_ok, NULL, |
| -1, name)); |
| } |
| |
| /* |
| * Return +1 if guid1 is before guid2, 0 if they are the same, and -1 if |
| * guid1 is after guid2. |
| */ |
| static int |
| created_before(libzfs_handle_t *hdl, avl_tree_t *avl, |
| uint64_t guid1, uint64_t guid2) |
| { |
| nvlist_t *nvfs; |
| char *fsname = NULL, *snapname = NULL; |
| char buf[ZFS_MAX_DATASET_NAME_LEN]; |
| int rv; |
| zfs_handle_t *guid1hdl, *guid2hdl; |
| uint64_t create1, create2; |
| |
| if (guid2 == 0) |
| return (0); |
| if (guid1 == 0) |
| return (1); |
| |
| nvfs = fsavl_find(avl, guid1, &snapname); |
| fsname = fnvlist_lookup_string(nvfs, "name"); |
| (void) snprintf(buf, sizeof (buf), "%s@%s", fsname, snapname); |
| guid1hdl = zfs_open(hdl, buf, ZFS_TYPE_SNAPSHOT); |
| if (guid1hdl == NULL) |
| return (-1); |
| |
| nvfs = fsavl_find(avl, guid2, &snapname); |
| fsname = fnvlist_lookup_string(nvfs, "name"); |
| (void) snprintf(buf, sizeof (buf), "%s@%s", fsname, snapname); |
| guid2hdl = zfs_open(hdl, buf, ZFS_TYPE_SNAPSHOT); |
| if (guid2hdl == NULL) { |
| zfs_close(guid1hdl); |
| return (-1); |
| } |
| |
| create1 = zfs_prop_get_int(guid1hdl, ZFS_PROP_CREATETXG); |
| create2 = zfs_prop_get_int(guid2hdl, ZFS_PROP_CREATETXG); |
| |
| if (create1 < create2) |
| rv = -1; |
| else if (create1 > create2) |
| rv = +1; |
| else |
| rv = 0; |
| |
| zfs_close(guid1hdl); |
| zfs_close(guid2hdl); |
| |
| return (rv); |
| } |
| |
| /* |
| * This function reestablishes the hierarchy of encryption roots after a |
| * recursive incremental receive has completed. This must be done after the |
| * second call to recv_incremental_replication() has renamed and promoted all |
| * sent datasets to their final locations in the dataset hierarchy. |
| */ |
| static int |
| recv_fix_encryption_hierarchy(libzfs_handle_t *hdl, const char *top_zfs, |
| nvlist_t *stream_nv, avl_tree_t *stream_avl) |
| { |
| int err; |
| nvpair_t *fselem = NULL; |
| nvlist_t *stream_fss; |
| |
| stream_fss = fnvlist_lookup_nvlist(stream_nv, "fss"); |
| |
| while ((fselem = nvlist_next_nvpair(stream_fss, fselem)) != NULL) { |
| zfs_handle_t *zhp = NULL; |
| uint64_t crypt; |
| nvlist_t *snaps, *props, *stream_nvfs = NULL; |
| nvpair_t *snapel = NULL; |
| boolean_t is_encroot, is_clone, stream_encroot; |
| char *cp; |
| char *stream_keylocation = NULL; |
| char keylocation[MAXNAMELEN]; |
| char fsname[ZFS_MAX_DATASET_NAME_LEN]; |
| |
| keylocation[0] = '\0'; |
| stream_nvfs = fnvpair_value_nvlist(fselem); |
| snaps = fnvlist_lookup_nvlist(stream_nvfs, "snaps"); |
| props = fnvlist_lookup_nvlist(stream_nvfs, "props"); |
| stream_encroot = nvlist_exists(stream_nvfs, "is_encroot"); |
| |
| /* find a snapshot from the stream that exists locally */ |
| err = ENOENT; |
| while ((snapel = nvlist_next_nvpair(snaps, snapel)) != NULL) { |
| uint64_t guid; |
| |
| guid = fnvpair_value_uint64(snapel); |
| err = guid_to_name(hdl, top_zfs, guid, B_FALSE, |
| fsname); |
| if (err == 0) |
| break; |
| } |
| |
| if (err != 0) |
| continue; |
| |
| cp = strchr(fsname, '@'); |
| if (cp != NULL) |
| *cp = '\0'; |
| |
| zhp = zfs_open(hdl, fsname, ZFS_TYPE_DATASET); |
| if (zhp == NULL) { |
| err = ENOENT; |
| goto error; |
| } |
| |
| crypt = zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION); |
| is_clone = zhp->zfs_dmustats.dds_origin[0] != '\0'; |
| (void) zfs_crypto_get_encryption_root(zhp, &is_encroot, NULL); |
| |
| /* we don't need to do anything for unencrypted datasets */ |
| if (crypt == ZIO_CRYPT_OFF) { |
| zfs_close(zhp); |
| continue; |
| } |
| |
| /* |
| * If the dataset is flagged as an encryption root, was not |
| * received as a clone and is not currently an encryption root, |
| * force it to become one. Fixup the keylocation if necessary. |
| */ |
| if (stream_encroot) { |
| if (!is_clone && !is_encroot) { |
| err = lzc_change_key(fsname, |
| DCP_CMD_FORCE_NEW_KEY, NULL, NULL, 0); |
| if (err != 0) { |
| zfs_close(zhp); |
| goto error; |
| } |
| } |
| |
| stream_keylocation = fnvlist_lookup_string(props, |
| zfs_prop_to_name(ZFS_PROP_KEYLOCATION)); |
| |
| /* |
| * Refresh the properties in case the call to |
| * lzc_change_key() changed the value. |
| */ |
| zfs_refresh_properties(zhp); |
| err = zfs_prop_get(zhp, ZFS_PROP_KEYLOCATION, |
| keylocation, sizeof (keylocation), NULL, NULL, |
| 0, B_TRUE); |
| if (err != 0) { |
| zfs_close(zhp); |
| goto error; |
| } |
| |
| if (strcmp(keylocation, stream_keylocation) != 0) { |
| err = zfs_prop_set(zhp, |
| zfs_prop_to_name(ZFS_PROP_KEYLOCATION), |
| stream_keylocation); |
| if (err != 0) { |
| zfs_close(zhp); |
| goto error; |
| } |
| } |
| } |
| |
| /* |
| * If the dataset is not flagged as an encryption root and is |
| * currently an encryption root, force it to inherit from its |
| * parent. The root of a raw send should never be |
| * force-inherited. |
| */ |
| if (!stream_encroot && is_encroot && |
| strcmp(top_zfs, fsname) != 0) { |
| err = lzc_change_key(fsname, DCP_CMD_FORCE_INHERIT, |
| NULL, NULL, 0); |
| if (err != 0) { |
| zfs_close(zhp); |
| goto error; |
| } |
| } |
| |
| zfs_close(zhp); |
| } |
| |
| return (0); |
| |
| error: |
| return (err); |
| } |
| |
| static int |
| recv_incremental_replication(libzfs_handle_t *hdl, const char *tofs, |
| recvflags_t *flags, nvlist_t *stream_nv, avl_tree_t *stream_avl, |
| nvlist_t *renamed) |
| { |
| nvlist_t *local_nv, *deleted = NULL; |
| avl_tree_t *local_avl; |
| nvpair_t *fselem, *nextfselem; |
| char *fromsnap; |
| char newname[ZFS_MAX_DATASET_NAME_LEN]; |
| char guidname[32]; |
| int error; |
| boolean_t needagain, progress, recursive; |
| char *s1, *s2; |
| |
| fromsnap = fnvlist_lookup_string(stream_nv, "fromsnap"); |
| |
| recursive = (nvlist_lookup_boolean(stream_nv, "not_recursive") == |
| ENOENT); |
| |
| if (flags->dryrun) |
| return (0); |
| |
| again: |
| needagain = progress = B_FALSE; |
| |
| deleted = fnvlist_alloc(); |
| |
| if ((error = gather_nvlist(hdl, tofs, fromsnap, NULL, |
| recursive, B_TRUE, B_FALSE, recursive, B_FALSE, B_FALSE, B_FALSE, |
| B_FALSE, B_TRUE, &local_nv, &local_avl)) != 0) |
| return (error); |
| |
| /* |
| * Process deletes and renames |
| */ |
| for (fselem = nvlist_next_nvpair(local_nv, NULL); |
| fselem; fselem = nextfselem) { |
| nvlist_t *nvfs, *snaps; |
| nvlist_t *stream_nvfs = NULL; |
| nvpair_t *snapelem, *nextsnapelem; |
| uint64_t fromguid = 0; |
| uint64_t originguid = 0; |
| uint64_t stream_originguid = 0; |
| uint64_t parent_fromsnap_guid, stream_parent_fromsnap_guid; |
| char *fsname, *stream_fsname; |
| |
| nextfselem = nvlist_next_nvpair(local_nv, fselem); |
| |
| nvfs = fnvpair_value_nvlist(fselem); |
| snaps = fnvlist_lookup_nvlist(nvfs, "snaps"); |
| fsname = fnvlist_lookup_string(nvfs, "name"); |
| parent_fromsnap_guid = fnvlist_lookup_uint64(nvfs, |
| "parentfromsnap"); |
| (void) nvlist_lookup_uint64(nvfs, "origin", &originguid); |
| |
| /* |
| * First find the stream's fs, so we can check for |
| * a different origin (due to "zfs promote") |
| */ |
| for (snapelem = nvlist_next_nvpair(snaps, NULL); |
| snapelem; snapelem = nvlist_next_nvpair(snaps, snapelem)) { |
| uint64_t thisguid; |
| |
| thisguid = fnvpair_value_uint64(snapelem); |
| stream_nvfs = fsavl_find(stream_avl, thisguid, NULL); |
| |
| if (stream_nvfs != NULL) |
| break; |
| } |
| |
| /* check for promote */ |
| (void) nvlist_lookup_uint64(stream_nvfs, "origin", |
| &stream_originguid); |
| if (stream_nvfs && originguid != stream_originguid) { |
| switch (created_before(hdl, local_avl, |
| stream_originguid, originguid)) { |
| case 1: { |
| /* promote it! */ |
| nvlist_t *origin_nvfs; |
| char *origin_fsname; |
| |
| origin_nvfs = fsavl_find(local_avl, originguid, |
| NULL); |
| origin_fsname = fnvlist_lookup_string( |
| origin_nvfs, "name"); |
| error = recv_promote(hdl, fsname, origin_fsname, |
| flags); |
| if (error == 0) |
| progress = B_TRUE; |
| break; |
| } |
| default: |
| break; |
| case -1: |
| fsavl_destroy(local_avl); |
| fnvlist_free(local_nv); |
| return (-1); |
| } |
| /* |
| * We had/have the wrong origin, therefore our |
| * list of snapshots is wrong. Need to handle |
| * them on the next pass. |
| */ |
| needagain = B_TRUE; |
| continue; |
| } |
| |
| for (snapelem = nvlist_next_nvpair(snaps, NULL); |
| snapelem; snapelem = nextsnapelem) { |
| uint64_t thisguid; |
| char *stream_snapname; |
| nvlist_t *found, *props; |
| |
| nextsnapelem = nvlist_next_nvpair(snaps, snapelem); |
| |
| thisguid = fnvpair_value_uint64(snapelem); |
| found = fsavl_find(stream_avl, thisguid, |
| &stream_snapname); |
| |
| /* check for delete */ |
| if (found == NULL) { |
| char name[ZFS_MAX_DATASET_NAME_LEN]; |
| |
| if (!flags->force) |
| continue; |
| |
| (void) snprintf(name, sizeof (name), "%s@%s", |
| fsname, nvpair_name(snapelem)); |
| |
| error = recv_destroy(hdl, name, |
| strlen(fsname)+1, newname, flags); |
| if (error) |
| needagain = B_TRUE; |
| else |
| progress = B_TRUE; |
| sprintf(guidname, "%llu", |
| (u_longlong_t)thisguid); |
| nvlist_add_boolean(deleted, guidname); |
| continue; |
| } |
| |
| stream_nvfs = found; |
| |
| if (0 == nvlist_lookup_nvlist(stream_nvfs, "snapprops", |
| &props) && 0 == nvlist_lookup_nvlist(props, |
| stream_snapname, &props)) { |
| zfs_cmd_t zc = {"\0"}; |
| |
| zc.zc_cookie = B_TRUE; /* received */ |
| (void) snprintf(zc.zc_name, sizeof (zc.zc_name), |
| "%s@%s", fsname, nvpair_name(snapelem)); |
| if (zcmd_write_src_nvlist(hdl, &zc, |
| props) == 0) { |
| (void) zfs_ioctl(hdl, |
| ZFS_IOC_SET_PROP, &zc); |
| zcmd_free_nvlists(&zc); |
| } |
| } |
| |
| /* check for different snapname */ |
| if (strcmp(nvpair_name(snapelem), |
| stream_snapname) != 0) { |
| char name[ZFS_MAX_DATASET_NAME_LEN]; |
| char tryname[ZFS_MAX_DATASET_NAME_LEN]; |
| |
| (void) snprintf(name, sizeof (name), "%s@%s", |
| fsname, nvpair_name(snapelem)); |
| (void) snprintf(tryname, sizeof (name), "%s@%s", |
| fsname, stream_snapname); |
| |
| error = recv_rename(hdl, name, tryname, |
| strlen(fsname)+1, newname, flags); |
| if (error) |
| needagain = B_TRUE; |
| else |
| progress = B_TRUE; |
| } |
| |
| if (strcmp(stream_snapname, fromsnap) == 0) |
| fromguid = thisguid; |
| } |
| |
| /* check for delete */ |
| if (stream_nvfs == NULL) { |
| if (!flags->force) |
| continue; |
| |
| error = recv_destroy(hdl, fsname, strlen(tofs)+1, |
| newname, flags); |
| if (error) |
| needagain = B_TRUE; |
| else |
| progress = B_TRUE; |
| sprintf(guidname, "%llu", |
| (u_longlong_t)parent_fromsnap_guid); |
| nvlist_add_boolean(deleted, guidname); |
| continue; |
| } |
| |
| if (fromguid == 0) { |
| if (flags->verbose) { |
| (void) printf("local fs %s does not have " |
| "fromsnap (%s in stream); must have " |
| "been deleted locally; ignoring\n", |
| fsname, fromsnap); |
| } |
| continue; |
| } |
| |
| stream_fsname = fnvlist_lookup_string(stream_nvfs, "name"); |
| stream_parent_fromsnap_guid = fnvlist_lookup_uint64( |
| stream_nvfs, "parentfromsnap"); |
| |
| s1 = strrchr(fsname, '/'); |
| s2 = strrchr(stream_fsname, '/'); |
| |
| /* |
| * Check if we're going to rename based on parent guid change |
| * and the current parent guid was also deleted. If it was then |
| * rename will fail and is likely unneeded, so avoid this and |
| * force an early retry to determine the new |
| * parent_fromsnap_guid. |
| */ |
| if (stream_parent_fromsnap_guid != 0 && |
| parent_fromsnap_guid != 0 && |
| stream_parent_fromsnap_guid != parent_fromsnap_guid) { |
| sprintf(guidname, "%llu", |
| (u_longlong_t)parent_fromsnap_guid); |
| if (nvlist_exists(deleted, guidname)) { |
| progress = B_TRUE; |
| needagain = B_TRUE; |
| goto doagain; |
| } |
| } |
| |
| /* |
| * Check for rename. If the exact receive path is specified, it |
| * does not count as a rename, but we still need to check the |
| * datasets beneath it. |
| */ |
| if ((stream_parent_fromsnap_guid != 0 && |
| parent_fromsnap_guid != 0 && |
| stream_parent_fromsnap_guid != parent_fromsnap_guid) || |
| ((flags->isprefix || strcmp(tofs, fsname) != 0) && |
| (s1 != NULL) && (s2 != NULL) && strcmp(s1, s2) != 0)) { |
| nvlist_t *parent; |
| char tryname[ZFS_MAX_DATASET_NAME_LEN]; |
| |
| parent = fsavl_find(local_avl, |
| stream_parent_fromsnap_guid, NULL); |
| /* |
| * NB: parent might not be found if we used the |
| * tosnap for stream_parent_fromsnap_guid, |
| * because the parent is a newly-created fs; |
| * we'll be able to rename it after we recv the |
| * new fs. |
| */ |
| if (parent != NULL) { |
| char *pname; |
| |
| pname = fnvlist_lookup_string(parent, "name"); |
| (void) snprintf(tryname, sizeof (tryname), |
| "%s%s", pname, strrchr(stream_fsname, '/')); |
| } else { |
| tryname[0] = '\0'; |
| if (flags->verbose) { |
| (void) printf("local fs %s new parent " |
| "not found\n", fsname); |
| } |
| } |
| |
| newname[0] = '\0'; |
| |
| error = recv_rename(hdl, fsname, tryname, |
| strlen(tofs)+1, newname, flags); |
| |
| if (renamed != NULL && newname[0] != '\0') { |
| fnvlist_add_boolean(renamed, newname); |
| } |
| |
| if (error) |
| needagain = B_TRUE; |
| else |
| progress = B_TRUE; |
| } |
| } |
| |
| doagain: |
| fsavl_destroy(local_avl); |
| fnvlist_free(local_nv); |
| fnvlist_free(deleted); |
| |
| if (needagain && progress) { |
| /* do another pass to fix up temporary names */ |
| if (flags->verbose) |
| (void) printf("another pass:\n"); |
| goto again; |
| } |
| |
| return (needagain || error != 0); |
| } |
| |
| static int |
| zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname, |
| recvflags_t *flags, dmu_replay_record_t *drr, zio_cksum_t *zc, |
| char **top_zfs, nvlist_t *cmdprops) |
| { |
| nvlist_t *stream_nv = NULL; |
| avl_tree_t *stream_avl = NULL; |
| char *fromsnap = NULL; |
| char *sendsnap = NULL; |
| char *cp; |
| char tofs[ZFS_MAX_DATASET_NAME_LEN]; |
| char sendfs[ZFS_MAX_DATASET_NAME_LEN]; |
| char errbuf[1024]; |
| dmu_replay_record_t drre; |
| int error; |
| boolean_t anyerr = B_FALSE; |
| boolean_t softerr = B_FALSE; |
| boolean_t recursive, raw; |
| |
| (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, |
| "cannot receive")); |
| |
| assert(drr->drr_type == DRR_BEGIN); |
| assert(drr->drr_u.drr_begin.drr_magic == DMU_BACKUP_MAGIC); |
| assert(DMU_GET_STREAM_HDRTYPE(drr->drr_u.drr_begin.drr_versioninfo) == |
| DMU_COMPOUNDSTREAM); |
| |
| /* |
| * Read in the nvlist from the stream. |
| */ |
| if (drr->drr_payloadlen != 0) { |
| error = recv_read_nvlist(hdl, fd, drr->drr_payloadlen, |
| &stream_nv, flags->byteswap, zc); |
| if (error) { |
| error = zfs_error(hdl, EZFS_BADSTREAM, errbuf); |
| goto out; |
| } |
| } |
| |
| recursive = (nvlist_lookup_boolean(stream_nv, "not_recursive") == |
| ENOENT); |
| raw = (nvlist_lookup_boolean(stream_nv, "raw") == 0); |
| |
| if (recursive && strchr(destname, '@')) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "cannot specify snapshot name for multi-snapshot stream")); |
| error = zfs_error(hdl, EZFS_BADSTREAM, errbuf); |
| goto out; |
| } |
| |
| /* |
| * Read in the end record and verify checksum. |
| */ |
| if (0 != (error = recv_read(hdl, fd, &drre, sizeof (drre), |
| flags->byteswap, NULL))) |
| goto out; |
| if (flags->byteswap) { |
| drre.drr_type = BSWAP_32(drre.drr_type); |
| drre.drr_u.drr_end.drr_checksum.zc_word[0] = |
| BSWAP_64(drre.drr_u.drr_end.drr_checksum.zc_word[0]); |
| drre.drr_u.drr_end.drr_checksum.zc_word[1] = |
| BSWAP_64(drre.drr_u.drr_end.drr_checksum.zc_word[1]); |
| drre.drr_u.drr_end.drr_checksum.zc_word[2] = |
| BSWAP_64(drre.drr_u.drr_end.drr_checksum.zc_word[2]); |
| drre.drr_u.drr_end.drr_checksum.zc_word[3] = |
| BSWAP_64(drre.drr_u.drr_end.drr_checksum.zc_word[3]); |
| } |
| if (drre.drr_type != DRR_END) { |
| error = zfs_error(hdl, EZFS_BADSTREAM, errbuf); |
| goto out; |
| } |
| if (!ZIO_CHECKSUM_EQUAL(drre.drr_u.drr_end.drr_checksum, *zc)) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "incorrect header checksum")); |
| error = zfs_error(hdl, EZFS_BADSTREAM, errbuf); |
| goto out; |
| } |
| |
| (void) nvlist_lookup_string(stream_nv, "fromsnap", &fromsnap); |
| |
| if (drr->drr_payloadlen != 0) { |
| nvlist_t *stream_fss; |
| |
| stream_fss = fnvlist_lookup_nvlist(stream_nv, "fss"); |
| if ((stream_avl = fsavl_create(stream_fss)) == NULL) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "couldn't allocate avl tree")); |
| error = zfs_error(hdl, EZFS_NOMEM, errbuf); |
| goto out; |
| } |
| |
| if (fromsnap != NULL && recursive) { |
| nvlist_t *renamed = NULL; |
| nvpair_t *pair = NULL; |
| |
| (void) strlcpy(tofs, destname, sizeof (tofs)); |
| if (flags->isprefix) { |
| struct drr_begin *drrb = &drr->drr_u.drr_begin; |
| int i; |
| |
| if (flags->istail) { |
| cp = strrchr(drrb->drr_toname, '/'); |
| if (cp == NULL) { |
| (void) strlcat(tofs, "/", |
| sizeof (tofs)); |
| i = 0; |
| } else { |
| i = (cp - drrb->drr_toname); |
| } |
| } else { |
| i = strcspn(drrb->drr_toname, "/@"); |
| } |
| /* zfs_receive_one() will create_parents() */ |
| (void) strlcat(tofs, &drrb->drr_toname[i], |
| sizeof (tofs)); |
| *strchr(tofs, '@') = '\0'; |
| } |
| |
| if (!flags->dryrun && !flags->nomount) { |
| renamed = fnvlist_alloc(); |
| } |
| |
| softerr = recv_incremental_replication(hdl, tofs, flags, |
| stream_nv, stream_avl, renamed); |
| |
| /* Unmount renamed filesystems before receiving. */ |
| while ((pair = nvlist_next_nvpair(renamed, |
| pair)) != NULL) { |
| zfs_handle_t *zhp; |
| prop_changelist_t *clp = NULL; |
| |
| zhp = zfs_open(hdl, nvpair_name(pair), |
| ZFS_TYPE_FILESYSTEM); |
| if (zhp != NULL) { |
| clp = changelist_gather(zhp, |
| ZFS_PROP_MOUNTPOINT, 0, |
| flags->forceunmount ? MS_FORCE : 0); |
| zfs_close(zhp); |
| if (clp != NULL) { |
| softerr |= |
| changelist_prefix(clp); |
| changelist_free(clp); |
| } |
| } |
| } |
| |
| fnvlist_free(renamed); |
| } |
| } |
| |
| /* |
| * Get the fs specified by the first path in the stream (the top level |
| * specified by 'zfs send') and pass it to each invocation of |
| * zfs_receive_one(). |
| */ |
| (void) strlcpy(sendfs, drr->drr_u.drr_begin.drr_toname, |
| sizeof (sendfs)); |
| if ((cp = strchr(sendfs, '@')) != NULL) { |
| *cp = '\0'; |
| /* |
| * Find the "sendsnap", the final snapshot in a replication |
| * stream. zfs_receive_one() handles certain errors |
| * differently, depending on if the contained stream is the |
| * last one or not. |
| */ |
| sendsnap = (cp + 1); |
| } |
| |
| /* Finally, receive each contained stream */ |
| do { |
| /* |
| * we should figure out if it has a recoverable |
| * error, in which case do a recv_skip() and drive on. |
| * Note, if we fail due to already having this guid, |
| * zfs_receive_one() will take care of it (ie, |
| * recv_skip() and return 0). |
| */ |
| error = zfs_receive_impl(hdl, destname, NULL, flags, fd, |
| sendfs, stream_nv, stream_avl, top_zfs, sendsnap, cmdprops); |
| if (error == ENODATA) { |
| error = 0; |
| break; |
| } |
| anyerr |= error; |
| } while (error == 0); |
| |
| if (drr->drr_payloadlen != 0 && recursive && fromsnap != NULL) { |
| /* |
| * Now that we have the fs's they sent us, try the |
| * renames again. |
| */ |
| softerr = recv_incremental_replication(hdl, tofs, flags, |
| stream_nv, stream_avl, NULL); |
| } |
| |
| if (raw && softerr == 0 && *top_zfs != NULL) { |
| softerr = recv_fix_encryption_hierarchy(hdl, *top_zfs, |
| stream_nv, stream_avl); |
| } |
| |
| out: |
| fsavl_destroy(stream_avl); |
| fnvlist_free(stream_nv); |
| if (softerr) |
| error = -2; |
| if (anyerr) |
| error = -1; |
| return (error); |
| } |
| |
| static void |
| trunc_prop_errs(int truncated) |
| { |
| ASSERT(truncated != 0); |
| |
| if (truncated == 1) |
| (void) fprintf(stderr, dgettext(TEXT_DOMAIN, |
| "1 more property could not be set\n")); |
| else |
| (void) fprintf(stderr, dgettext(TEXT_DOMAIN, |
| "%d more properties could not be set\n"), truncated); |
| } |
| |
| static int |
| recv_skip(libzfs_handle_t *hdl, int fd, boolean_t byteswap) |
| { |
| dmu_replay_record_t *drr; |
| void *buf = zfs_alloc(hdl, SPA_MAXBLOCKSIZE); |
| uint64_t payload_size; |
| char errbuf[1024]; |
| |
| (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, |
| "cannot receive")); |
| |
| /* XXX would be great to use lseek if possible... */ |
| drr = buf; |
| |
| while (recv_read(hdl, fd, drr, sizeof (dmu_replay_record_t), |
| byteswap, NULL) == 0) { |
| if (byteswap) |
| drr->drr_type = BSWAP_32(drr->drr_type); |
| |
| switch (drr->drr_type) { |
| case DRR_BEGIN: |
| if (drr->drr_payloadlen != 0) { |
| (void) recv_read(hdl, fd, buf, |
| drr->drr_payloadlen, B_FALSE, NULL); |
| } |
| break; |
| |
| case DRR_END: |
| free(buf); |
| return (0); |
| |
| case DRR_OBJECT: |
| if (byteswap) { |
| drr->drr_u.drr_object.drr_bonuslen = |
| BSWAP_32(drr->drr_u.drr_object. |
| drr_bonuslen); |
| drr->drr_u.drr_object.drr_raw_bonuslen = |
| BSWAP_32(drr->drr_u.drr_object. |
| drr_raw_bonuslen); |
| } |
| |
| payload_size = |
| DRR_OBJECT_PAYLOAD_SIZE(&drr->drr_u.drr_object); |
| (void) recv_read(hdl, fd, buf, payload_size, |
| B_FALSE, NULL); |
| break; |
| |
| case DRR_WRITE: |
| if (byteswap) { |
| drr->drr_u.drr_write.drr_logical_size = |
| BSWAP_64( |
| drr->drr_u.drr_write.drr_logical_size); |
| drr->drr_u.drr_write.drr_compressed_size = |
| BSWAP_64( |
| drr->drr_u.drr_write.drr_compressed_size); |
| } |
| payload_size = |
| DRR_WRITE_PAYLOAD_SIZE(&drr->drr_u.drr_write); |
| assert(payload_size <= SPA_MAXBLOCKSIZE); |
| (void) recv_read(hdl, fd, buf, |
| payload_size, B_FALSE, NULL); |
| break; |
| case DRR_SPILL: |
| if (byteswap) { |
| drr->drr_u.drr_spill.drr_length = |
| BSWAP_64(drr->drr_u.drr_spill.drr_length); |
| drr->drr_u.drr_spill.drr_compressed_size = |
| BSWAP_64(drr->drr_u.drr_spill. |
| drr_compressed_size); |
| } |
| |
| payload_size = |
| DRR_SPILL_PAYLOAD_SIZE(&drr->drr_u.drr_spill); |
| (void) recv_read(hdl, fd, buf, payload_size, |
| B_FALSE, NULL); |
| break; |
| case DRR_WRITE_EMBEDDED: |
| if (byteswap) { |
| drr->drr_u.drr_write_embedded.drr_psize = |
| BSWAP_32(drr->drr_u.drr_write_embedded. |
| drr_psize); |
| } |
| (void) recv_read(hdl, fd, buf, |
| P2ROUNDUP(drr->drr_u.drr_write_embedded.drr_psize, |
| 8), B_FALSE, NULL); |
| break; |
| case DRR_OBJECT_RANGE: |
| case DRR_WRITE_BYREF: |
| case DRR_FREEOBJECTS: |
| case DRR_FREE: |
| break; |
| |
| default: |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "invalid record type")); |
| free(buf); |
| return (zfs_error(hdl, EZFS_BADSTREAM, errbuf)); |
| } |
| } |
| |
| free(buf); |
| return (-1); |
| } |
| |
| static void |
| recv_ecksum_set_aux(libzfs_handle_t *hdl, const char *target_snap, |
| boolean_t resumable, boolean_t checksum) |
| { |
| char target_fs[ZFS_MAX_DATASET_NAME_LEN]; |
| |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, (checksum ? |
| "checksum mismatch" : "incomplete stream"))); |
| |
| if (!resumable) |
| return; |
| (void) strlcpy(target_fs, target_snap, sizeof (target_fs)); |
| *strchr(target_fs, '@') = '\0'; |
| zfs_handle_t *zhp = zfs_open(hdl, target_fs, |
| ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME); |
| if (zhp == NULL) |
| return; |
| |
| char token_buf[ZFS_MAXPROPLEN]; |
| int error = zfs_prop_get(zhp, ZFS_PROP_RECEIVE_RESUME_TOKEN, |
| token_buf, sizeof (token_buf), |
| NULL, NULL, 0, B_TRUE); |
| if (error == 0) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "checksum mismatch or incomplete stream.\n" |
| "Partially received snapshot is saved.\n" |
| "A resuming stream can be generated on the sending " |
| "system by running:\n" |
| " zfs send -t %s"), |
| token_buf); |
| } |
| zfs_close(zhp); |
| } |
| |
| /* |
| * Prepare a new nvlist of properties that are to override (-o) or be excluded |
| * (-x) from the received dataset |
| * recvprops: received properties from the send stream |
| * cmdprops: raw input properties from command line |
| * origprops: properties, both locally-set and received, currently set on the |
| * target dataset if it exists, NULL otherwise. |
| * oxprops: valid output override (-o) and excluded (-x) properties |
| */ |
| static int |
| zfs_setup_cmdline_props(libzfs_handle_t *hdl, zfs_type_t type, |
| char *fsname, boolean_t zoned, boolean_t recursive, boolean_t newfs, |
| boolean_t raw, boolean_t toplevel, nvlist_t *recvprops, nvlist_t *cmdprops, |
| nvlist_t *origprops, nvlist_t **oxprops, uint8_t **wkeydata_out, |
| uint_t *wkeylen_out, const char *errbuf) |
| { |
| nvpair_t *nvp; |
| nvlist_t *oprops, *voprops; |
| zfs_handle_t *zhp = NULL; |
| zpool_handle_t *zpool_hdl = NULL; |
| char *cp; |
| int ret = 0; |
| char namebuf[ZFS_MAX_DATASET_NAME_LEN]; |
| |
| if (nvlist_empty(cmdprops)) |
| return (0); /* No properties to override or exclude */ |
| |
| *oxprops = fnvlist_alloc(); |
| oprops = fnvlist_alloc(); |
| |
| strlcpy(namebuf, fsname, ZFS_MAX_DATASET_NAME_LEN); |
| |
| /* |
| * Get our dataset handle. The target dataset may not exist yet. |
| */ |
| if (zfs_dataset_exists(hdl, namebuf, ZFS_TYPE_DATASET)) { |
| zhp = zfs_open(hdl, namebuf, ZFS_TYPE_DATASET); |
| if (zhp == NULL) { |
| ret = -1; |
| goto error; |
| } |
| } |
| |
| /* open the zpool handle */ |
| cp = strchr(namebuf, '/'); |
| if (cp != NULL) |
| *cp = '\0'; |
| zpool_hdl = zpool_open(hdl, namebuf); |
| if (zpool_hdl == NULL) { |
| ret = -1; |
| goto error; |
| } |
| |
| /* restore namebuf to match fsname for later use */ |
| if (cp != NULL) |
| *cp = '/'; |
| |
| /* |
| * first iteration: process excluded (-x) properties now and gather |
| * added (-o) properties to be later processed by zfs_valid_proplist() |
| */ |
| nvp = NULL; |
| while ((nvp = nvlist_next_nvpair(cmdprops, nvp)) != NULL) { |
| const char *name = nvpair_name(nvp); |
| zfs_prop_t prop = zfs_name_to_prop(name); |
| |
| /* "origin" is processed separately, don't handle it here */ |
| if (prop == ZFS_PROP_ORIGIN) |
| continue; |
| |
| /* raw streams can't override encryption properties */ |
| if ((zfs_prop_encryption_key_param(prop) || |
| prop == ZFS_PROP_ENCRYPTION) && raw) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "encryption property '%s' cannot " |
| "be set or excluded for raw streams."), name); |
| ret = zfs_error(hdl, EZFS_BADPROP, errbuf); |
| goto error; |
| } |
| |
| /* |
| * For plain replicated send, we can ignore encryption |
| * properties other than first stream |
| */ |
| if ((zfs_prop_encryption_key_param(prop) || prop == |
| ZFS_PROP_ENCRYPTION) && !newfs && recursive && !raw) { |
| continue; |
| } |
| |
| /* incremental streams can only exclude encryption properties */ |
| if ((zfs_prop_encryption_key_param(prop) || |
| prop == ZFS_PROP_ENCRYPTION) && !newfs && |
| nvpair_type(nvp) != DATA_TYPE_BOOLEAN) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "encryption property '%s' cannot " |
| "be set for incremental streams."), name); |
| ret = zfs_error(hdl, EZFS_BADPROP, errbuf); |
| goto error; |
| } |
| |
| switch (nvpair_type(nvp)) { |
| case DATA_TYPE_BOOLEAN: /* -x property */ |
| /* |
| * DATA_TYPE_BOOLEAN is the way we're asked to "exclude" |
| * a property: this is done by forcing an explicit |
| * inherit on the destination so the effective value is |
| * not the one we received from the send stream. |
| */ |
| if (!zfs_prop_valid_for_type(prop, type, B_FALSE) && |
| !zfs_prop_user(name)) { |
| (void) fprintf(stderr, dgettext(TEXT_DOMAIN, |
| "Warning: %s: property '%s' does not " |
| "apply to datasets of this type\n"), |
| fsname, name); |
| continue; |
| } |
| /* |
| * We do this only if the property is not already |
| * locally-set, in which case its value will take |
| * priority over the received anyway. |
| */ |
| if (nvlist_exists(origprops, name)) { |
| nvlist_t *attrs; |
| char *source = NULL; |
| |
| attrs = fnvlist_lookup_nvlist(origprops, name); |
| if (nvlist_lookup_string(attrs, |
| ZPROP_SOURCE, &source) == 0 && |
| strcmp(source, ZPROP_SOURCE_VAL_RECVD) != 0) |
| continue; |
| } |
| /* |
| * We can't force an explicit inherit on non-inheritable |
| * properties: if we're asked to exclude this kind of |
| * values we remove them from "recvprops" input nvlist. |
| */ |
| if (!zfs_prop_user(name) && /* can be inherited too */ |
| !zfs_prop_inheritable(prop) && |
| nvlist_exists(recvprops, name)) |
| fnvlist_remove(recvprops, name); |
| else |
| fnvlist_add_nvpair(*oxprops, nvp); |
| break; |
| case DATA_TYPE_STRING: /* -o property=value */ |
| /* |
| * we're trying to override a property that does not |
| * make sense for this type of dataset, but we don't |
| * want to fail if the receive is recursive: this comes |
| * in handy when the send stream contains, for |
| * instance, a child ZVOL and we're trying to receive |
| * it with "-o atime=on" |
| */ |
| if (!zfs_prop_valid_for_type(prop, type, B_FALSE) && |
| !zfs_prop_user(name)) { |
| if (recursive) |
| continue; |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "property '%s' does not apply to datasets " |
| "of this type"), name); |
| ret = zfs_error(hdl, EZFS_BADPROP, errbuf); |
| goto error; |
| } |
| fnvlist_add_nvpair(oprops, nvp); |
| break; |
| default: |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "property '%s' must be a string or boolean"), name); |
| ret = zfs_error(hdl, EZFS_BADPROP, errbuf); |
| goto error; |
| } |
| } |
| |
| if (toplevel) { |
| /* convert override strings properties to native */ |
| if ((voprops = zfs_valid_proplist(hdl, ZFS_TYPE_DATASET, |
| oprops, zoned, zhp, zpool_hdl, B_FALSE, errbuf)) == NULL) { |
| ret = zfs_error(hdl, EZFS_BADPROP, errbuf); |
| goto error; |
| } |
| |
| /* |
| * zfs_crypto_create() requires the parent name. Get it |
| * by truncating the fsname copy stored in namebuf. |
| */ |
| cp = strrchr(namebuf, '/'); |
| if (cp != NULL) |
| *cp = '\0'; |
| |
| if (!raw && !(!newfs && recursive) && |
| zfs_crypto_create(hdl, namebuf, voprops, NULL, |
| B_FALSE, wkeydata_out, wkeylen_out) != 0) { |
| fnvlist_free(voprops); |
| ret = zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf); |
| goto error; |
| } |
| |
| /* second pass: process "-o" properties */ |
| fnvlist_merge(*oxprops, voprops); |
| fnvlist_free(voprops); |
| } else { |
| /* override props on child dataset are inherited */ |
| nvp = NULL; |
| while ((nvp = nvlist_next_nvpair(oprops, nvp)) != NULL) { |
| const char *name = nvpair_name(nvp); |
| fnvlist_add_boolean(*oxprops, name); |
| } |
| } |
| |
| error: |
| if (zhp != NULL) |
| zfs_close(zhp); |
| if (zpool_hdl != NULL) |
| zpool_close(zpool_hdl); |
| fnvlist_free(oprops); |
| return (ret); |
| } |
| |
| /* |
| * Restores a backup of tosnap from the file descriptor specified by infd. |
| */ |
| static int |
| zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, |
| const char *originsnap, recvflags_t *flags, dmu_replay_record_t *drr, |
| dmu_replay_record_t *drr_noswap, const char *sendfs, nvlist_t *stream_nv, |
| avl_tree_t *stream_avl, char **top_zfs, |
| const char *finalsnap, nvlist_t *cmdprops) |
| { |
| time_t begin_time; |
| int ioctl_err, ioctl_errno, err; |
| char *cp; |
| struct drr_begin *drrb = &drr->drr_u.drr_begin; |
| char errbuf[1024]; |
| const char *chopprefix; |
| boolean_t newfs = B_FALSE; |
| boolean_t stream_wantsnewfs, stream_resumingnewfs; |
| boolean_t newprops = B_FALSE; |
| uint64_t read_bytes = 0; |
| uint64_t errflags = 0; |
| uint64_t parent_snapguid = 0; |
| prop_changelist_t *clp = NULL; |
| nvlist_t *snapprops_nvlist = NULL; |
| nvlist_t *snapholds_nvlist = NULL; |
| zprop_errflags_t prop_errflags; |
| nvlist_t *prop_errors = NULL; |
| boolean_t recursive; |
| char *snapname = NULL; |
| char destsnap[MAXPATHLEN * 2]; |
| char origin[MAXNAMELEN]; |
| char name[MAXPATHLEN]; |
| char tmp_keylocation[MAXNAMELEN]; |
| nvlist_t *rcvprops = NULL; /* props received from the send stream */ |
| nvlist_t *oxprops = NULL; /* override (-o) and exclude (-x) props */ |
| nvlist_t *origprops = NULL; /* original props (if destination exists) */ |
| zfs_type_t type; |
| boolean_t toplevel = B_FALSE; |
| boolean_t zoned = B_FALSE; |
| boolean_t hastoken = B_FALSE; |
| boolean_t redacted; |
| uint8_t *wkeydata = NULL; |
| uint_t wkeylen = 0; |
| |
| begin_time = time(NULL); |
| bzero(origin, MAXNAMELEN); |
| bzero(tmp_keylocation, MAXNAMELEN); |
| |
| (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, |
| "cannot receive")); |
| |
| recursive = (nvlist_lookup_boolean(stream_nv, "not_recursive") == |
| ENOENT); |
| |
| /* Did the user request holds be skipped via zfs recv -k? */ |
| boolean_t holds = flags->holds && !flags->skipholds; |
| |
| if (stream_avl != NULL) { |
| char *keylocation = NULL; |
| nvlist_t *lookup = NULL; |
| nvlist_t *fs = fsavl_find(stream_avl, drrb->drr_toguid, |
| &snapname); |
| |
| (void) nvlist_lookup_uint64(fs, "parentfromsnap", |
| &parent_snapguid); |
| err = nvlist_lookup_nvlist(fs, "props", &rcvprops); |
| if (err) { |
| rcvprops = fnvlist_alloc(); |
| newprops = B_TRUE; |
| } |
| |
| /* |
| * The keylocation property may only be set on encryption roots, |
| * but this dataset might not become an encryption root until |
| * recv_fix_encryption_hierarchy() is called. That function |
| * will fixup the keylocation anyway, so we temporarily unset |
| * the keylocation for now to avoid any errors from the receive |
| * ioctl. |
| */ |
| err = nvlist_lookup_string(rcvprops, |
| zfs_prop_to_name(ZFS_PROP_KEYLOCATION), &keylocation); |
| if (err == 0) { |
| strcpy(tmp_keylocation, keylocation); |
| (void) nvlist_remove_all(rcvprops, |
| zfs_prop_to_name(ZFS_PROP_KEYLOCATION)); |
| } |
| |
| if (flags->canmountoff) { |
| fnvlist_add_uint64(rcvprops, |
| zfs_prop_to_name(ZFS_PROP_CANMOUNT), 0); |
| } else if (newprops) { /* nothing in rcvprops, eliminate it */ |
| fnvlist_free(rcvprops); |
| rcvprops = NULL; |
| newprops = B_FALSE; |
| } |
| if (0 == nvlist_lookup_nvlist(fs, "snapprops", &lookup)) { |
| snapprops_nvlist = fnvlist_lookup_nvlist(lookup, |
| snapname); |
| } |
| if (holds) { |
| if (0 == nvlist_lookup_nvlist(fs, "snapholds", |
| &lookup)) { |
| snapholds_nvlist = fnvlist_lookup_nvlist( |
| lookup, snapname); |
| } |
| } |
| } |
| |
| cp = NULL; |
| |
| /* |
| * Determine how much of the snapshot name stored in the stream |
| * we are going to tack on to the name they specified on the |
| * command line, and how much we are going to chop off. |
| * |
| * If they specified a snapshot, chop the entire name stored in |
| * the stream. |
| */ |
| if (flags->istail) { |
| /* |
| * A filesystem was specified with -e. We want to tack on only |
| * the tail of the sent snapshot path. |
| */ |
| if (strchr(tosnap, '@')) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid " |
| "argument - snapshot not allowed with -e")); |
| err = zfs_error(hdl, EZFS_INVALIDNAME, errbuf); |
| goto out; |
| } |
| |
| chopprefix = strrchr(sendfs, '/'); |
| |
| if (chopprefix == NULL) { |
| /* |
| * The tail is the poolname, so we need to |
| * prepend a path separator. |
| */ |
| int len = strlen(drrb->drr_toname); |
| cp = malloc(len + 2); |
| cp[0] = '/'; |
| (void) strcpy(&cp[1], drrb->drr_toname); |
| chopprefix = cp; |
| } else { |
| chopprefix = drrb->drr_toname + (chopprefix - sendfs); |
| } |
| } else if (flags->isprefix) { |
| /* |
| * A filesystem was specified with -d. We want to tack on |
| * everything but the first element of the sent snapshot path |
| * (all but the pool name). |
| */ |
| if (strchr(tosnap, '@')) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid " |
| "argument - snapshot not allowed with -d")); |
| err = zfs_error(hdl, EZFS_INVALIDNAME, errbuf); |
| goto out; |
| } |
| |
| chopprefix = strchr(drrb->drr_toname, '/'); |
| if (chopprefix == NULL) |
| chopprefix = strchr(drrb->drr_toname, '@'); |
| } else if (strchr(tosnap, '@') == NULL) { |
| /* |
| * If a filesystem was specified without -d or -e, we want to |
| * tack on everything after the fs specified by 'zfs send'. |
| */ |
| chopprefix = drrb->drr_toname + strlen(sendfs); |
| } else { |
| /* A snapshot was specified as an exact path (no -d or -e). */ |
| if (recursive) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "cannot specify snapshot name for multi-snapshot " |
| "stream")); |
| err = zfs_error(hdl, EZFS_BADSTREAM, errbuf); |
| goto out; |
| } |
| chopprefix = drrb->drr_toname + strlen(drrb->drr_toname); |
| } |
| |
| ASSERT(strstr(drrb->drr_toname, sendfs) == drrb->drr_toname); |
| ASSERT(chopprefix > drrb->drr_toname || strchr(sendfs, '/') == NULL); |
| ASSERT(chopprefix <= drrb->drr_toname + strlen(drrb->drr_toname) || |
| strchr(sendfs, '/') == NULL); |
| ASSERT(chopprefix[0] == '/' || chopprefix[0] == '@' || |
| chopprefix[0] == '\0'); |
| |
| /* |
| * Determine name of destination snapshot. |
| */ |
| (void) strlcpy(destsnap, tosnap, sizeof (destsnap)); |
| (void) strlcat(destsnap, chopprefix, sizeof (destsnap)); |
| free(cp); |
| if (!zfs_name_valid(destsnap, ZFS_TYPE_SNAPSHOT)) { |
| err = zfs_error(hdl, EZFS_INVALIDNAME, errbuf); |
| goto out; |
| } |
| |
| /* |
| * Determine the name of the origin snapshot. |
| */ |
| if (originsnap) { |
| (void) strlcpy(origin, originsnap, sizeof (origin)); |
| if (flags->verbose) |
| (void) printf("using provided clone origin %s\n", |
| origin); |
| } else if (drrb->drr_flags & DRR_FLAG_CLONE) { |
| if (guid_to_name(hdl, destsnap, |
| drrb->drr_fromguid, B_FALSE, origin) != 0) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "local origin for clone %s does not exist"), |
| destsnap); |
| err = zfs_error(hdl, EZFS_NOENT, errbuf); |
| goto out; |
| } |
| if (flags->verbose) |
| (void) printf("found clone origin %s\n", origin); |
| } |
| |
| if ((DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo) & |
| DMU_BACKUP_FEATURE_DEDUP)) { |
| (void) fprintf(stderr, |
| gettext("ERROR: \"zfs receive\" no longer supports " |
| "deduplicated send streams. Use\n" |
| "the \"zstream redup\" command to convert this stream " |
| "to a regular,\n" |
| "non-deduplicated stream.\n")); |
| err = zfs_error(hdl, EZFS_NOTSUP, errbuf); |
| goto out; |
| } |
| |
| boolean_t resuming = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo) & |
| DMU_BACKUP_FEATURE_RESUMING; |
| boolean_t raw = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo) & |
| DMU_BACKUP_FEATURE_RAW; |
| boolean_t embedded = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo) & |
| DMU_BACKUP_FEATURE_EMBED_DATA; |
| stream_wantsnewfs = (drrb->drr_fromguid == 0 || |
| (drrb->drr_flags & DRR_FLAG_CLONE) || originsnap) && !resuming; |
| stream_resumingnewfs = (drrb->drr_fromguid == 0 || |
| (drrb->drr_flags & DRR_FLAG_CLONE) || originsnap) && resuming; |
| |
| if (stream_wantsnewfs) { |
| /* |
| * if the parent fs does not exist, look for it based on |
| * the parent snap GUID |
| */ |
| (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, |
| "cannot receive new filesystem stream")); |
| |
| (void) strcpy(name, destsnap); |
| cp = strrchr(name, '/'); |
| if (cp) |
| *cp = '\0'; |
| if (cp && |
| !zfs_dataset_exists(hdl, name, ZFS_TYPE_DATASET)) { |
| char suffix[ZFS_MAX_DATASET_NAME_LEN]; |
| (void) strcpy(suffix, strrchr(destsnap, '/')); |
| if (guid_to_name(hdl, name, parent_snapguid, |
| B_FALSE, destsnap) == 0) { |
| *strchr(destsnap, '@') = '\0'; |
| (void) strcat(destsnap, suffix); |
| } |
| } |
| } else { |
| /* |
| * If the fs does not exist, look for it based on the |
| * fromsnap GUID. |
| */ |
| if (resuming) { |
| (void) snprintf(errbuf, sizeof (errbuf), |
| dgettext(TEXT_DOMAIN, |
| "cannot receive resume stream")); |
| } else { |
| (void) snprintf(errbuf, sizeof (errbuf), |
| dgettext(TEXT_DOMAIN, |
| "cannot receive incremental stream")); |
| } |
| |
| (void) strcpy(name, destsnap); |
| *strchr(name, '@') = '\0'; |
| |
| /* |
| * If the exact receive path was specified and this is the |
| * topmost path in the stream, then if the fs does not exist we |
| * should look no further. |
| */ |
| if ((flags->isprefix || (*(chopprefix = drrb->drr_toname + |
| strlen(sendfs)) != '\0' && *chopprefix != '@')) && |
| !zfs_dataset_exists(hdl, name, ZFS_TYPE_DATASET)) { |
| char snap[ZFS_MAX_DATASET_NAME_LEN]; |
| (void) strcpy(snap, strchr(destsnap, '@')); |
| if (guid_to_name(hdl, name, drrb->drr_fromguid, |
| B_FALSE, destsnap) == 0) { |
| *strchr(destsnap, '@') = '\0'; |
| (void) strcat(destsnap, snap); |
| } |
| } |
| } |
| |
| (void) strcpy(name, destsnap); |
| *strchr(name, '@') = '\0'; |
| |
| redacted = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo) & |
| DMU_BACKUP_FEATURE_REDACTED; |
| |
| if (zfs_dataset_exists(hdl, name, ZFS_TYPE_DATASET)) { |
| zfs_cmd_t zc = {"\0"}; |
| zfs_handle_t *zhp; |
| boolean_t encrypted; |
| |
| (void) strcpy(zc.zc_name, name); |
| |
| /* |
| * Destination fs exists. It must be one of these cases: |
| * - an incremental send stream |
| * - the stream specifies a new fs (full stream or clone) |
| * and they want us to blow away the existing fs (and |
| * have therefore specified -F and removed any snapshots) |
| * - we are resuming a failed receive. |
| */ |
| if (stream_wantsnewfs) { |
| boolean_t is_volume = drrb->drr_type == DMU_OST_ZVOL; |
| if (!flags->force) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "destination '%s' exists\n" |
| "must specify -F to overwrite it"), name); |
| err = zfs_error(hdl, EZFS_EXISTS, errbuf); |
| goto out; |
| } |
| if (zfs_ioctl(hdl, ZFS_IOC_SNAPSHOT_LIST_NEXT, |
| &zc) == 0) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "destination has snapshots (eg. %s)\n" |
| "must destroy them to overwrite it"), |
| zc.zc_name); |
| err = zfs_error(hdl, EZFS_EXISTS, errbuf); |
| goto out; |
| } |
| if (is_volume && strrchr(name, '/') == NULL) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "destination %s is the root dataset\n" |
| "cannot overwrite with a ZVOL"), |
| name); |
| err = zfs_error(hdl, EZFS_EXISTS, errbuf); |
| goto out; |
| } |
| if (is_volume && |
| zfs_ioctl(hdl, ZFS_IOC_DATASET_LIST_NEXT, |
| &zc) == 0) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "destination has children (eg. %s)\n" |
| "cannot overwrite with a ZVOL"), |
| zc.zc_name); |
| err = zfs_error(hdl, EZFS_WRONG_PARENT, errbuf); |
| goto out; |
| } |
| } |
| |
| if ((zhp = zfs_open(hdl, name, |
| ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME)) == NULL) { |
| err = -1; |
| goto out; |
| } |
| |
| if (stream_wantsnewfs && |
| zhp->zfs_dmustats.dds_origin[0]) { |
| zfs_close(zhp); |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "destination '%s' is a clone\n" |
| "must destroy it to overwrite it"), name); |
| err = zfs_error(hdl, EZFS_EXISTS, errbuf); |
| goto out; |
| } |
| |
| /* |
| * Raw sends can not be performed as an incremental on top |
| * of existing unencrypted datasets. zfs recv -F can't be |
| * used to blow away an existing encrypted filesystem. This |
| * is because it would require the dsl dir to point to the |
| * new key (or lack of a key) and the old key at the same |
| * time. The -F flag may still be used for deleting |
| * intermediate snapshots that would otherwise prevent the |
| * receive from working. |
| */ |
| encrypted = zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) != |
| ZIO_CRYPT_OFF; |
| if (!stream_wantsnewfs && !encrypted && raw) { |
| zfs_close(zhp); |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "cannot perform raw receive on top of " |
| "existing unencrypted dataset")); |
| err = zfs_error(hdl, EZFS_BADRESTORE, errbuf); |
| goto out; |
| } |
| |
| if (stream_wantsnewfs && flags->force && |
| ((raw && !encrypted) || encrypted)) { |
| zfs_close(zhp); |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "zfs receive -F cannot be used to destroy an " |
| "encrypted filesystem or overwrite an " |
| "unencrypted one with an encrypted one")); |
| err = zfs_error(hdl, EZFS_BADRESTORE, errbuf); |
| goto out; |
| } |
| |
| if (!flags->dryrun && zhp->zfs_type == ZFS_TYPE_FILESYSTEM && |
| (stream_wantsnewfs || stream_resumingnewfs)) { |
| /* We can't do online recv in this case */ |
| clp = changelist_gather(zhp, ZFS_PROP_NAME, 0, |
| flags->forceunmount ? MS_FORCE : 0); |
| if (clp == NULL) { |
| zfs_close(zhp); |
| err = -1; |
| goto out; |
| } |
| if (changelist_prefix(clp) != 0) { |
| changelist_free(clp); |
| zfs_close(zhp); |
| err = -1; |
| goto out; |
| } |
| } |
| |
| /* |
| * If we are resuming a newfs, set newfs here so that we will |
| * mount it if the recv succeeds this time. We can tell |
| * that it was a newfs on the first recv because the fs |
| * itself will be inconsistent (if the fs existed when we |
| * did the first recv, we would have received it into |
| * .../%recv). |
| */ |
| if (resuming && zfs_prop_get_int(zhp, ZFS_PROP_INCONSISTENT)) |
| newfs = B_TRUE; |
| |
| /* we want to know if we're zoned when validating -o|-x props */ |
| zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED); |
| |
| /* may need this info later, get it now we have zhp around */ |
| if (zfs_prop_get(zhp, ZFS_PROP_RECEIVE_RESUME_TOKEN, NULL, 0, |
| NULL, NULL, 0, B_TRUE) == 0) |
| hastoken = B_TRUE; |
| |
| /* gather existing properties on destination */ |
| origprops = fnvlist_alloc(); |
| fnvlist_merge(origprops, zhp->zfs_props); |
| fnvlist_merge(origprops, zhp->zfs_user_props); |
| |
| zfs_close(zhp); |
| } else { |
| zfs_handle_t *zhp; |
| |
| /* |
| * Destination filesystem does not exist. Therefore we better |
| * be creating a new filesystem (either from a full backup, or |
| * a clone). It would therefore be invalid if the user |
| * specified only the pool name (i.e. if the destination name |
| * contained no slash character). |
| */ |
| cp = strrchr(name, '/'); |
| |
| if (!stream_wantsnewfs || cp == NULL) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "destination '%s' does not exist"), name); |
| err = zfs_error(hdl, EZFS_NOENT, errbuf); |
| goto out; |
| } |
| |
| /* |
| * Trim off the final dataset component so we perform the |
| * recvbackup ioctl to the filesystems's parent. |
| */ |
| *cp = '\0'; |
| |
| if (flags->isprefix && !flags->istail && !flags->dryrun && |
| create_parents(hdl, destsnap, strlen(tosnap)) != 0) { |
| err = zfs_error(hdl, EZFS_BADRESTORE, errbuf); |
| goto out; |
| } |
| |
| /* validate parent */ |
| zhp = zfs_open(hdl, name, ZFS_TYPE_DATASET); |
| if (zhp == NULL) { |
| err = zfs_error(hdl, EZFS_BADRESTORE, errbuf); |
| goto out; |
| } |
| if (zfs_get_type(zhp) != ZFS_TYPE_FILESYSTEM) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "parent '%s' is not a filesystem"), name); |
| err = zfs_error(hdl, EZFS_WRONG_PARENT, errbuf); |
| zfs_close(zhp); |
| goto out; |
| } |
| |
| zfs_close(zhp); |
| |
| newfs = B_TRUE; |
| *cp = '/'; |
| } |
| |
| if (flags->verbose) { |
| (void) printf("%s %s stream of %s into %s\n", |
| flags->dryrun ? "would receive" : "receiving", |
| drrb->drr_fromguid ? "incremental" : "full", |
| drrb->drr_toname, destsnap); |
| (void) fflush(stdout); |
| } |
| |
| /* |
| * If this is the top-level dataset, record it so we can use it |
| * for recursive operations later. |
| */ |
| if (top_zfs != NULL && |
| (*top_zfs == NULL || strcmp(*top_zfs, name) == 0)) { |
| toplevel = B_TRUE; |
| if (*top_zfs == NULL) |
| *top_zfs = zfs_strdup(hdl, name); |
| } |
| |
| if (drrb->drr_type == DMU_OST_ZVOL) { |
| type = ZFS_TYPE_VOLUME; |
| } else if (drrb->drr_type == DMU_OST_ZFS) { |
| type = ZFS_TYPE_FILESYSTEM; |
| } else { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "invalid record type: 0x%d"), drrb->drr_type); |
| err = zfs_error(hdl, EZFS_BADSTREAM, errbuf); |
| goto out; |
| } |
| if ((err = zfs_setup_cmdline_props(hdl, type, name, zoned, recursive, |
| stream_wantsnewfs, raw, toplevel, rcvprops, cmdprops, origprops, |
| &oxprops, &wkeydata, &wkeylen, errbuf)) != 0) |
| goto out; |
| |
| /* |
| * When sending with properties (zfs send -p), the encryption property |
| * is not included because it is a SETONCE property and therefore |
| * treated as read only. However, we are always able to determine its |
| * value because raw sends will include it in the DRR_BDEGIN payload |
| * and non-raw sends with properties are not allowed for encrypted |
| * datasets. Therefore, if this is a non-raw properties stream, we can |
| * infer that the value should be ZIO_CRYPT_OFF and manually add that |
| * to the received properties. |
| */ |
| if (stream_wantsnewfs && !raw && rcvprops != NULL && |
| !nvlist_exists(cmdprops, zfs_prop_to_name(ZFS_PROP_ENCRYPTION))) { |
| if (oxprops == NULL) |
| oxprops = fnvlist_alloc(); |
| fnvlist_add_uint64(oxprops, |
| zfs_prop_to_name(ZFS_PROP_ENCRYPTION), ZIO_CRYPT_OFF); |
| } |
| |
| if (flags->dryrun) { |
| void *buf = zfs_alloc(hdl, SPA_MAXBLOCKSIZE); |
| |
| /* |
| * We have read the DRR_BEGIN record, but we have |
| * not yet read the payload. For non-dryrun sends |
| * this will be done by the kernel, so we must |
| * emulate that here, before attempting to read |
| * more records. |
| */ |
| err = recv_read(hdl, infd, buf, drr->drr_payloadlen, |
| flags->byteswap, NULL); |
| free(buf); |
| if (err != 0) |
| goto out; |
| |
| err = recv_skip(hdl, infd, flags->byteswap); |
| goto out; |
| } |
| |
| err = ioctl_err = lzc_receive_with_cmdprops(destsnap, rcvprops, |
| oxprops, wkeydata, wkeylen, origin, flags->force, flags->resumable, |
| raw, infd, drr_noswap, -1, &read_bytes, &errflags, |
| NULL, &prop_errors); |
| ioctl_errno = ioctl_err; |
| prop_errflags = errflags; |
| |
| if (err == 0) { |
| nvpair_t *prop_err = NULL; |
| |
| while ((prop_err = nvlist_next_nvpair(prop_errors, |
| prop_err)) != NULL) { |
| char tbuf[1024]; |
| zfs_prop_t prop; |
| int intval; |
| |
| prop = zfs_name_to_prop(nvpair_name(prop_err)); |
| (void) nvpair_value_int32(prop_err, &intval); |
| if (strcmp(nvpair_name(prop_err), |
| ZPROP_N_MORE_ERRORS) == 0) { |
| trunc_prop_errs(intval); |
| break; |
| } else if (snapname == NULL || finalsnap == NULL || |
| strcmp(finalsnap, snapname) == 0 || |
| strcmp(nvpair_name(prop_err), |
| zfs_prop_to_name(ZFS_PROP_REFQUOTA)) != 0) { |
| /* |
| * Skip the special case of, for example, |
| * "refquota", errors on intermediate |
| * snapshots leading up to a final one. |
| * That's why we have all of the checks above. |
| * |
| * See zfs_ioctl.c's extract_delay_props() for |
| * a list of props which can fail on |
| * intermediate snapshots, but shouldn't |
| * affect the overall receive. |
| */ |
| (void) snprintf(tbuf, sizeof (tbuf), |
| dgettext(TEXT_DOMAIN, |
| "cannot receive %s property on %s"), |
| nvpair_name(prop_err), name); |
| zfs_setprop_error(hdl, prop, intval, tbuf); |
| } |
| } |
| } |
| |
| if (err == 0 && snapprops_nvlist) { |
| zfs_cmd_t zc = {"\0"}; |
| |
| (void) strcpy(zc.zc_name, destsnap); |
| zc.zc_cookie = B_TRUE; /* received */ |
| if (zcmd_write_src_nvlist(hdl, &zc, snapprops_nvlist) == 0) { |
| (void) zfs_ioctl(hdl, ZFS_IOC_SET_PROP, &zc); |
| zcmd_free_nvlists(&zc); |
| } |
| } |
| if (err == 0 && snapholds_nvlist) { |
| nvpair_t *pair; |
| nvlist_t *holds, *errors = NULL; |
| int cleanup_fd = -1; |
| |
| VERIFY(0 == nvlist_alloc(&holds, 0, KM_SLEEP)); |
| for (pair = nvlist_next_nvpair(snapholds_nvlist, NULL); |
| pair != NULL; |
| pair = nvlist_next_nvpair(snapholds_nvlist, pair)) { |
| fnvlist_add_string(holds, destsnap, nvpair_name(pair)); |
| } |
| (void) lzc_hold(holds, cleanup_fd, &errors); |
| fnvlist_free(snapholds_nvlist); |
| fnvlist_free(holds); |
| } |
| |
| if (err && (ioctl_errno == ENOENT || ioctl_errno == EEXIST)) { |
| /* |
| * It may be that this snapshot already exists, |
| * in which case we want to consume & ignore it |
| * rather than failing. |
| */ |
| avl_tree_t *local_avl; |
| nvlist_t *local_nv, *fs; |
| cp = strchr(destsnap, '@'); |
| |
| /* |
| * XXX Do this faster by just iterating over snaps in |
| * this fs. Also if zc_value does not exist, we will |
| * get a strange "does not exist" error message. |
| */ |
| *cp = '\0'; |
| if (gather_nvlist(hdl, destsnap, NULL, NULL, B_FALSE, B_TRUE, |
| B_FALSE, B_FALSE, B_FALSE, B_FALSE, B_FALSE, B_FALSE, |
| B_TRUE, &local_nv, &local_avl) == 0) { |
| *cp = '@'; |
| fs = fsavl_find(local_avl, drrb->drr_toguid, NULL); |
| fsavl_destroy(local_avl); |
| fnvlist_free(local_nv); |
| |
| if (fs != NULL) { |
| if (flags->verbose) { |
| (void) printf("snap %s already exists; " |
| "ignoring\n", destsnap); |
| } |
| err = ioctl_err = recv_skip(hdl, infd, |
| flags->byteswap); |
| } |
| } |
| *cp = '@'; |
| } |
| |
| if (ioctl_err != 0) { |
| switch (ioctl_errno) { |
| case ENODEV: |
| cp = strchr(destsnap, '@'); |
| *cp = '\0'; |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "most recent snapshot of %s does not\n" |
| "match incremental source"), destsnap); |
| (void) zfs_error(hdl, EZFS_BADRESTORE, errbuf); |
| *cp = '@'; |
| break; |
| case ETXTBSY: |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "destination %s has been modified\n" |
| "since most recent snapshot"), name); |
| (void) zfs_error(hdl, EZFS_BADRESTORE, errbuf); |
| break; |
| case EACCES: |
| if (raw && stream_wantsnewfs) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "failed to create encryption key")); |
| } else if (raw && !stream_wantsnewfs) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "encryption key does not match " |
| "existing key")); |
| } else { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "inherited key must be loaded")); |
| } |
| (void) zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf); |
| break; |
| case EEXIST: |
| cp = strchr(destsnap, '@'); |
| if (newfs) { |
| /* it's the containing fs that exists */ |
| *cp = '\0'; |
| } |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "destination already exists")); |
| (void) zfs_error_fmt(hdl, EZFS_EXISTS, |
| dgettext(TEXT_DOMAIN, "cannot restore to %s"), |
| destsnap); |
| *cp = '@'; |
| break; |
| case EINVAL: |
| if (flags->resumable) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "kernel modules must be upgraded to " |
| "receive this stream.")); |
| } else if (embedded && !raw) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "incompatible embedded data stream " |
| "feature with encrypted receive.")); |
| } |
| (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf); |
| break; |
| case ECKSUM: |
| case ZFS_ERR_STREAM_TRUNCATED: |
| recv_ecksum_set_aux(hdl, destsnap, flags->resumable, |
| ioctl_err == ECKSUM); |
| (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf); |
| break; |
| case ZFS_ERR_STREAM_LARGE_BLOCK_MISMATCH: |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "incremental send stream requires -L " |
| "(--large-block), to match previous receive.")); |
| (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf); |
| break; |
| case ENOTSUP: |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "pool must be upgraded to receive this stream.")); |
| (void) zfs_error(hdl, EZFS_BADVERSION, errbuf); |
| break; |
| case EDQUOT: |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "destination %s space quota exceeded."), name); |
| (void) zfs_error(hdl, EZFS_NOSPC, errbuf); |
| break; |
| case ZFS_ERR_FROM_IVSET_GUID_MISSING: |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "IV set guid missing. See errata %u at " |
| "https://openzfs.github.io/openzfs-docs/msg/" |
| "ZFS-8000-ER."), |
| ZPOOL_ERRATA_ZOL_8308_ENCRYPTION); |
| (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf); |
| break; |
| case ZFS_ERR_FROM_IVSET_GUID_MISMATCH: |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "IV set guid mismatch. See the 'zfs receive' " |
| "man page section\n discussing the limitations " |
| "of raw encrypted send streams.")); |
| (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf); |
| break; |
| case ZFS_ERR_SPILL_BLOCK_FLAG_MISSING: |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "Spill block flag missing for raw send.\n" |
| "The zfs software on the sending system must " |
| "be updated.")); |
| (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf); |
| break; |
| case EBUSY: |
| if (hastoken) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "destination %s contains " |
| "partially-complete state from " |
| "\"zfs receive -s\"."), name); |
| (void) zfs_error(hdl, EZFS_BUSY, errbuf); |
| break; |
| } |
| fallthrough; |
| default: |
| (void) zfs_standard_error(hdl, ioctl_errno, errbuf); |
| } |
| } |
| |
| /* |
| * Mount the target filesystem (if created). Also mount any |
| * children of the target filesystem if we did a replication |
| * receive (indicated by stream_avl being non-NULL). |
| */ |
| if (clp) { |
| if (!flags->nomount) |
| err |= changelist_postfix(clp); |
| changelist_free(clp); |
| } |
| |
| if ((newfs || stream_avl) && type == ZFS_TYPE_FILESYSTEM && !redacted) |
| flags->domount = B_TRUE; |
| |
| if (prop_errflags & ZPROP_ERR_NOCLEAR) { |
| (void) fprintf(stderr, dgettext(TEXT_DOMAIN, "Warning: " |
| "failed to clear unreceived properties on %s"), name); |
| (void) fprintf(stderr, "\n"); |
| } |
| if (prop_errflags & ZPROP_ERR_NORESTORE) { |
| (void) fprintf(stderr, dgettext(TEXT_DOMAIN, "Warning: " |
| "failed to restore original properties on %s"), name); |
| (void) fprintf(stderr, "\n"); |
| } |
| |
| if (err || ioctl_err) { |
| err = -1; |
| goto out; |
| } |
| |
| if (flags->verbose) { |
| char buf1[64]; |
| char buf2[64]; |
| uint64_t bytes = read_bytes; |
| time_t delta = time(NULL) - begin_time; |
| if (delta == 0) |
| delta = 1; |
| zfs_nicebytes(bytes, buf1, sizeof (buf1)); |
| zfs_nicebytes(bytes/delta, buf2, sizeof (buf1)); |
| |
| (void) printf("received %s stream in %lld seconds (%s/sec)\n", |
| buf1, (longlong_t)delta, buf2); |
| } |
| |
| err = 0; |
| out: |
| if (prop_errors != NULL) |
| fnvlist_free(prop_errors); |
| |
| if (tmp_keylocation[0] != '\0') { |
| fnvlist_add_string(rcvprops, |
| zfs_prop_to_name(ZFS_PROP_KEYLOCATION), tmp_keylocation); |
| } |
| |
| if (newprops) |
| fnvlist_free(rcvprops); |
| |
| fnvlist_free(oxprops); |
| fnvlist_free(origprops); |
| |
| return (err); |
| } |
| |
| /* |
| * Check properties we were asked to override (both -o|-x) |
| */ |
| static boolean_t |
| zfs_receive_checkprops(libzfs_handle_t *hdl, nvlist_t *props, |
| const char *errbuf) |
| { |
| nvpair_t *nvp; |
| zfs_prop_t prop; |
| const char *name; |
| |
| nvp = NULL; |
| while ((nvp = nvlist_next_nvpair(props, nvp)) != NULL) { |
| name = nvpair_name(nvp); |
| prop = zfs_name_to_prop(name); |
| |
| if (prop == ZPROP_INVAL) { |
| if (!zfs_prop_user(name)) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "invalid property '%s'"), name); |
| return (B_FALSE); |
| } |
| continue; |
| } |
| /* |
| * "origin" is readonly but is used to receive datasets as |
| * clones so we don't raise an error here |
| */ |
| if (prop == ZFS_PROP_ORIGIN) |
| continue; |
| |
| /* encryption params have their own verification later */ |
| if (prop == ZFS_PROP_ENCRYPTION || |
| zfs_prop_encryption_key_param(prop)) |
| continue; |
| |
| /* |
| * cannot override readonly, set-once and other specific |
| * settable properties |
| */ |
| if (zfs_prop_readonly(prop) || prop == ZFS_PROP_VERSION || |
| prop == ZFS_PROP_VOLSIZE) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "invalid property '%s'"), name); |
| return (B_FALSE); |
| } |
| } |
| |
| return (B_TRUE); |
| } |
| |
| static int |
| zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap, |
| const char *originsnap, recvflags_t *flags, int infd, const char *sendfs, |
| nvlist_t *stream_nv, avl_tree_t *stream_avl, char **top_zfs, |
| const char *finalsnap, nvlist_t *cmdprops) |
| { |
| int err; |
| dmu_replay_record_t drr, drr_noswap; |
| struct drr_begin *drrb = &drr.drr_u.drr_begin; |
| char errbuf[1024]; |
| zio_cksum_t zcksum = { { 0 } }; |
| uint64_t featureflags; |
| int hdrtype; |
| |
| (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, |
| "cannot receive")); |
| |
| /* check cmdline props, raise an error if they cannot be received */ |
| if (!zfs_receive_checkprops(hdl, cmdprops, errbuf)) { |
| return (zfs_error(hdl, EZFS_BADPROP, errbuf)); |
| } |
| |
| if (flags->isprefix && |
| !zfs_dataset_exists(hdl, tosnap, ZFS_TYPE_DATASET)) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "specified fs " |
| "(%s) does not exist"), tosnap); |
| return (zfs_error(hdl, EZFS_NOENT, errbuf)); |
| } |
| if (originsnap && |
| !zfs_dataset_exists(hdl, originsnap, ZFS_TYPE_DATASET)) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "specified origin fs " |
| "(%s) does not exist"), originsnap); |
| return (zfs_error(hdl, EZFS_NOENT, errbuf)); |
| } |
| |
| /* read in the BEGIN record */ |
| if (0 != (err = recv_read(hdl, infd, &drr, sizeof (drr), B_FALSE, |
| &zcksum))) |
| return (err); |
| |
| if (drr.drr_type == DRR_END || drr.drr_type == BSWAP_32(DRR_END)) { |
| /* It's the double end record at the end of a package */ |
| return (ENODATA); |
| } |
| |
| /* the kernel needs the non-byteswapped begin record */ |
| drr_noswap = drr; |
| |
| flags->byteswap = B_FALSE; |
| if (drrb->drr_magic == BSWAP_64(DMU_BACKUP_MAGIC)) { |
| /* |
| * We computed the checksum in the wrong byteorder in |
| * recv_read() above; do it again correctly. |
| */ |
| bzero(&zcksum, sizeof (zio_cksum_t)); |
| fletcher_4_incremental_byteswap(&drr, sizeof (drr), &zcksum); |
| flags->byteswap = B_TRUE; |
| |
| drr.drr_type = BSWAP_32(drr.drr_type); |
| drr.drr_payloadlen = BSWAP_32(drr.drr_payloadlen); |
| drrb->drr_magic = BSWAP_64(drrb->drr_magic); |
| drrb->drr_versioninfo = BSWAP_64(drrb->drr_versioninfo); |
| drrb->drr_creation_time = BSWAP_64(drrb->drr_creation_time); |
| drrb->drr_type = BSWAP_32(drrb->drr_type); |
| drrb->drr_flags = BSWAP_32(drrb->drr_flags); |
| drrb->drr_toguid = BSWAP_64(drrb->drr_toguid); |
| drrb->drr_fromguid = BSWAP_64(drrb->drr_fromguid); |
| } |
| |
| if (drrb->drr_magic != DMU_BACKUP_MAGIC || drr.drr_type != DRR_BEGIN) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid " |
| "stream (bad magic number)")); |
| return (zfs_error(hdl, EZFS_BADSTREAM, errbuf)); |
| } |
| |
| featureflags = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo); |
| hdrtype = DMU_GET_STREAM_HDRTYPE(drrb->drr_versioninfo); |
| |
| if (!DMU_STREAM_SUPPORTED(featureflags) || |
| (hdrtype != DMU_SUBSTREAM && hdrtype != DMU_COMPOUNDSTREAM)) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, |
| "stream has unsupported feature, feature flags = %llx"), |
| (unsigned long long)featureflags); |
| return (zfs_error(hdl, EZFS_BADSTREAM, errbuf)); |
| } |
| |
| /* Holds feature is set once in the compound stream header. */ |
| if (featureflags & DMU_BACKUP_FEATURE_HOLDS) |
| flags->holds = B_TRUE; |
| |
| if (strchr(drrb->drr_toname, '@') == NULL) { |
| zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid " |
| "stream (bad snapshot name)")); |
| return (zfs_error(hdl, EZFS_BADSTREAM, errbuf)); |
| } |
| |
| if (DMU_GET_STREAM_HDRTYPE(drrb->drr_versioninfo) == DMU_SUBSTREAM) { |
| char nonpackage_sendfs[ZFS_MAX_DATASET_NAME_LEN]; |
| if (sendfs == NULL) { |
| /* |
| * We were not called from zfs_receive_package(). Get |
| * the fs specified by 'zfs send'. |
| */ |
| char *cp; |
| (void) strlcpy(nonpackage_sendfs, |
| drr.drr_u.drr_begin.drr_toname, |
| sizeof (nonpackage_sendfs)); |
| if ((cp = strchr(nonpackage_sendfs, '@')) != NULL) |
| *cp = '\0'; |
| sendfs = nonpackage_sendfs; |
| VERIFY(finalsnap == NULL); |
| } |
| return (zfs_receive_one(hdl, infd, tosnap, originsnap, flags, |
| &drr, &drr_noswap, sendfs, stream_nv, stream_avl, top_zfs, |
| finalsnap, cmdprops)); |
| } else { |
| assert(DMU_GET_STREAM_HDRTYPE(drrb->drr_versioninfo) == |
| DMU_COMPOUNDSTREAM); |
| return (zfs_receive_package(hdl, infd, tosnap, flags, &drr, |
| &zcksum, top_zfs, cmdprops)); |
| } |
| } |
| |
| /* |
| * Restores a backup of tosnap from the file descriptor specified by infd. |
| * Return 0 on total success, -2 if some things couldn't be |
| * destroyed/renamed/promoted, -1 if some things couldn't be received. |
| * (-1 will override -2, if -1 and the resumable flag was specified the |
| * transfer can be resumed if the sending side supports it). |
| */ |
| int |
| zfs_receive(libzfs_handle_t *hdl, const char *tosnap, nvlist_t *props, |
| recvflags_t *flags, int infd, avl_tree_t *stream_avl) |
| { |
| char *top_zfs = NULL; |
| int err; |
| struct stat sb; |
| char *originsnap = NULL; |
| |
| /* |
| * The only way fstat can fail is if we do not have a valid file |
| * descriptor. |
| */ |
| if (fstat(infd, &sb) == -1) { |
| perror("fstat"); |
| return (-2); |
| } |
| |
| /* |
| * It is not uncommon for gigabytes to be processed in zfs receive. |
| * Speculatively increase the buffer size if supported by the platform. |
| */ |
| if (S_ISFIFO(sb.st_mode)) |
| libzfs_set_pipe_max(infd); |
| |
| if (props) { |
| err = nvlist_lookup_string(props, "origin", &originsnap); |
| if (err && err != ENOENT) |
| return (err); |
| } |
| |
| err = zfs_receive_impl(hdl, tosnap, originsnap, flags, infd, NULL, NULL, |
| stream_avl, &top_zfs, NULL, props); |
| |
| if (err == 0 && !flags->nomount && flags->domount && top_zfs) { |
| zfs_handle_t *zhp = NULL; |
| prop_changelist_t *clp = NULL; |
| |
| zhp = zfs_open(hdl, top_zfs, |
| ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME); |
| if (zhp == NULL) { |
| err = -1; |
| goto out; |
| } else { |
| if (zhp->zfs_type == ZFS_TYPE_VOLUME) { |
| zfs_close(zhp); |
| goto out; |
| } |
| |
| clp = changelist_gather(zhp, ZFS_PROP_MOUNTPOINT, |
| CL_GATHER_MOUNT_ALWAYS, |
| flags->forceunmount ? MS_FORCE : 0); |
| zfs_close(zhp); |
| if (clp == NULL) { |
| err = -1; |
| goto out; |
| } |
| |
| /* mount and share received datasets */ |
| err = changelist_postfix(clp); |
| changelist_free(clp); |
| if (err != 0) |
| err = -1; |
| } |
| } |
| |
| out: |
| if (top_zfs) |
| free(top_zfs); |
| |
| return (err); |
| } |