diff options
author | Jerry Jelinek <jerry.jelinek@joyent.com> | 2019-08-01 11:34:42 +0000 |
---|---|---|
committer | Jerry Jelinek <jerry.jelinek@joyent.com> | 2019-08-01 11:34:42 +0000 |
commit | 7f874eb65f07035e3f808d58a70ad712248fde0a (patch) | |
tree | cf9ef0b9e1cc37c48c49f4cafe18d7ad105d5ce6 | |
parent | ec6335ff2d187135730376f34f2fe1cae4189572 (diff) | |
parent | 6ccda740e007c01cb5d1436fe337851ff8c5d422 (diff) | |
download | illumos-joyent-7f874eb65f07035e3f808d58a70ad712248fde0a.tar.gz |
[illumos-gate merge]
commit 6ccda740e007c01cb5d1436fe337851ff8c5d422
11282 port ZoL send/recv fixes
39 files changed, 2684 insertions, 433 deletions
diff --git a/usr/src/cmd/zfs/zfs_main.c b/usr/src/cmd/zfs/zfs_main.c index dc14a2c609..9ca6c03be7 100644 --- a/usr/src/cmd/zfs/zfs_main.c +++ b/usr/src/cmd/zfs/zfs_main.c @@ -269,10 +269,12 @@ get_usage(zfs_help_t idx) case HELP_PROMOTE: return (gettext("\tpromote <clone-filesystem>\n")); case HELP_RECEIVE: - return (gettext("\treceive [-vnsFu] <filesystem|volume|" - "snapshot>\n" - "\treceive [-vnsFu] [-o origin=<snapshot>] [-d | -e] " - "<filesystem>\n" + return (gettext("\treceive [-vnsFhu] " + "[-o <property>=<value>] ... [-x <property>] ...\n" + "\t <filesystem|volume|snapshot>\n" + "\treceive [-vnsFhu] [-o <property>=<value>] ... " + "[-x <property>] ... \n" + "\t [-d | -e] <filesystem>\n" "\treceive -A <filesystem|volume>\n")); case HELP_RENAME: return (gettext("\trename [-f] <filesystem|volume|snapshot> " @@ -282,9 +284,9 @@ get_usage(zfs_help_t idx) case HELP_ROLLBACK: return (gettext("\trollback [-rRf] <snapshot>\n")); case HELP_SEND: - return (gettext("\tsend [-DnPpRvLecr] [-[iI] snapshot] " + return (gettext("\tsend [-DnPpRvLecwhb] [-[iI] snapshot] " "<snapshot>\n" - "\tsend [-Lecr] [-i snapshot|bookmark] " + "\tsend [-nvPLecw] [-i snapshot|bookmark] " "<filesystem|volume|snapshot>\n" "\tsend [-nvPe] -t <receive_resume_token>\n")); case HELP_SET: @@ -536,26 +538,48 @@ usage(boolean_t requested) * Take a property=value argument string and add it to the given nvlist. * Modifies the argument inplace. */ -static int +static boolean_t parseprop(nvlist_t *props, char *propname) { - char *propval, *strval; + char *propval; if ((propval = strchr(propname, '=')) == NULL) { (void) fprintf(stderr, gettext("missing " "'=' for property=value argument\n")); - return (-1); + return (B_FALSE); } *propval = '\0'; propval++; - if (nvlist_lookup_string(props, propname, &strval) == 0) { + if (nvlist_exists(props, propname)) { (void) fprintf(stderr, gettext("property '%s' " "specified multiple times\n"), propname); - return (-1); + return (B_FALSE); } if (nvlist_add_string(props, propname, propval) != 0) nomem(); - return (0); + return (B_TRUE); +} + +/* + * Take a property name argument and add it to the given nvlist. + * Modifies the argument inplace. + */ +static boolean_t +parsepropname(nvlist_t *props, char *propname) +{ + if (strchr(propname, '=') != NULL) { + (void) fprintf(stderr, gettext("invalid character " + "'=' in property argument\n")); + return (B_FALSE); + } + if (nvlist_exists(props, propname)) { + (void) fprintf(stderr, gettext("property '%s' " + "specified multiple times\n"), propname); + return (B_FALSE); + } + if (nvlist_add_boolean(props, propname) != 0) + nomem(); + return (B_TRUE); } static int @@ -679,8 +703,10 @@ zfs_do_clone(int argc, char **argv) keeptrying = B_TRUE; break; case 'o': - if (parseprop(props, optarg) != 0) + if (!parseprop(props, optarg)) { + nvlist_free(props); return (1); + } break; case 'p': parents = B_TRUE; @@ -863,7 +889,7 @@ zfs_do_create(int argc, char **argv) dryrun = B_TRUE; break; case 'o': - if (parseprop(props, optarg) != 0) + if (!parseprop(props, optarg)) goto error; break; case 's': @@ -3764,8 +3790,10 @@ zfs_do_set(int argc, char **argv) if (nvlist_alloc(&props, NV_UNIQUE_NAME, 0) != 0) nomem(); for (int i = 1; i < ds_start; i++) { - if ((ret = parseprop(props, argv[i])) != 0) + if (!parseprop(props, argv[i])) { + ret = -1; goto error; + } } ret = zfs_for_each(argc - ds_start, argv + ds_start, 0, @@ -3832,8 +3860,11 @@ zfs_do_snapshot(int argc, char **argv) while ((c = getopt(argc, argv, "ro:")) != -1) { switch (c) { case 'o': - if (parseprop(props, optarg) != 0) + if (!parseprop(props, optarg)) { + nvlist_free(sd.sd_nvl); + nvlist_free(props); return (1); + } break; case 'r': sd.sd_recursive = B_TRUE; @@ -3916,11 +3947,13 @@ zfs_do_send(int argc, char **argv) {"resume", required_argument, NULL, 't'}, {"compressed", no_argument, NULL, 'c'}, {"raw", no_argument, NULL, 'w'}, + {"backup", no_argument, NULL, 'b'}, + {"holds", no_argument, NULL, 'h'}, {0, 0, 0, 0} }; /* check options */ - while ((c = getopt_long(argc, argv, ":i:I:RbDpvnPLet:cw", long_options, + while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLeht:cwb", long_options, NULL)) != -1) { switch (c) { case 'i': @@ -3940,6 +3973,12 @@ zfs_do_send(int argc, char **argv) case 'p': flags.props = B_TRUE; break; + case 'b': + flags.backup = B_TRUE; + break; + case 'h': + flags.holds = B_TRUE; + break; case 'P': flags.parsable = B_TRUE; flags.verbose = B_TRUE; @@ -4020,7 +4059,7 @@ zfs_do_send(int argc, char **argv) if (resume_token != NULL) { if (fromname != NULL || flags.replicate || flags.props || - flags.dedup) { + flags.backup || flags.dedup) { (void) fprintf(stderr, gettext("invalid flags combined with -t\n")); usage(B_FALSE); @@ -4063,8 +4102,8 @@ zfs_do_send(int argc, char **argv) enum lzc_send_flags lzc_flags = 0; if (flags.replicate || flags.doall || flags.props || - flags.dedup || flags.dryrun || flags.verbose || - flags.progress) { + flags.backup || flags.dedup || flags.holds || + flags.dryrun || flags.verbose || flags.progress) { (void) fprintf(stderr, gettext("Error: " "Unsupported flag with filesystem or bookmark.\n")); @@ -4171,19 +4210,25 @@ zfs_do_receive(int argc, char **argv) int c, err = 0; recvflags_t flags = { 0 }; boolean_t abort_resumable = B_FALSE; - nvlist_t *props; - nvpair_t *nvp = NULL; if (nvlist_alloc(&props, NV_UNIQUE_NAME, 0) != 0) nomem(); /* check options */ - while ((c = getopt(argc, argv, ":o:denuvFsA")) != -1) { + while ((c = getopt(argc, argv, ":o:x:dehnuvFsA")) != -1) { switch (c) { case 'o': - if (parseprop(props, optarg) != 0) - return (1); + if (!parseprop(props, optarg)) { + nvlist_free(props); + usage(B_FALSE); + } + break; + case 'x': + if (!parsepropname(props, optarg)) { + nvlist_free(props); + usage(B_FALSE); + } break; case 'd': flags.isprefix = B_TRUE; @@ -4192,6 +4237,9 @@ zfs_do_receive(int argc, char **argv) flags.isprefix = B_TRUE; flags.istail = B_TRUE; break; + case 'h': + flags.skipholds = B_TRUE; + break; case 'n': flags.dryrun = B_TRUE; break; @@ -4235,13 +4283,6 @@ zfs_do_receive(int argc, char **argv) usage(B_FALSE); } - while ((nvp = nvlist_next_nvpair(props, nvp))) { - if (strcmp(nvpair_name(nvp), "origin") != 0) { - (void) fprintf(stderr, gettext("invalid option")); - usage(B_FALSE); - } - } - if (abort_resumable) { if (flags.isprefix || flags.istail || flags.dryrun || flags.resumable || flags.nomount) { @@ -6179,7 +6220,7 @@ share_mount_one(zfs_handle_t *zhp, int op, int flags, char *protocol, (void) fprintf(stderr, gettext("cannot %s '%s': " "Contains partially-completed state from " - "\"zfs receive -r\", which can be resumed with " + "\"zfs receive -s\", which can be resumed with " "\"zfs send -t\"\n"), cmdname, zfs_get_name(zhp)); return (1); @@ -7657,7 +7698,7 @@ zfs_do_change_key(int argc, char **argv) inheritkey = B_TRUE; break; case 'o': - if (parseprop(props, optarg) != 0) { + if (!parseprop(props, optarg)) { nvlist_free(props); return (1); } diff --git a/usr/src/lib/libzfs/common/libzfs.h b/usr/src/lib/libzfs/common/libzfs.h index 378a303677..49008ad38d 100644 --- a/usr/src/lib/libzfs/common/libzfs.h +++ b/usr/src/lib/libzfs/common/libzfs.h @@ -141,6 +141,7 @@ typedef enum zfs_error { EZFS_TOOMANY, /* argument list too long */ EZFS_INITIALIZING, /* currently initializing */ EZFS_NO_INITIALIZE, /* no active initialize */ + EZFS_WRONG_PARENT, /* invalid parent dataset (e.g ZVOL) */ EZFS_NO_RESILVER_DEFER, /* pool doesn't support resilver_defer */ EZFS_CRYPTOFAILED, /* failed to setup encryption */ EZFS_UNKNOWN @@ -516,7 +517,7 @@ extern nvlist_t *zfs_get_clones_nvl(zfs_handle_t *); */ extern int zfs_crypto_get_encryption_root(zfs_handle_t *, boolean_t *, char *); extern int zfs_crypto_create(libzfs_handle_t *, char *, nvlist_t *, nvlist_t *, - uint8_t **, uint_t *); + boolean_t stdin_available, uint8_t **, uint_t *); extern int zfs_crypto_clone_check(libzfs_handle_t *, zfs_handle_t *, char *, nvlist_t *); extern int zfs_crypto_attempt_load_keys(libzfs_handle_t *, char *); @@ -675,6 +676,12 @@ typedef struct sendflags { /* raw encrypted records are permitted */ boolean_t raw; + + /* only send received properties (ie. -b) */ + boolean_t backup; + + /* include snapshot holds in send stream */ + boolean_t holds; } sendflags_t; typedef boolean_t (snapfilter_cb_t)(zfs_handle_t *, void *); @@ -738,6 +745,12 @@ typedef struct recvflags { /* do not mount file systems as they are extracted (private) */ boolean_t nomount; + + /* Was holds flag set in the compound header? */ + boolean_t holds; + + /* skip receive of snapshot holds */ + boolean_t skipholds; } recvflags_t; extern int zfs_receive(libzfs_handle_t *, const char *, nvlist_t *, diff --git a/usr/src/lib/libzfs/common/libzfs_crypto.c b/usr/src/lib/libzfs/common/libzfs_crypto.c index 7c16207a58..c7233e5348 100644 --- a/usr/src/lib/libzfs/common/libzfs_crypto.c +++ b/usr/src/lib/libzfs/common/libzfs_crypto.c @@ -719,7 +719,8 @@ zfs_crypto_get_encryption_root(zfs_handle_t *zhp, boolean_t *is_encroot, int zfs_crypto_create(libzfs_handle_t *hdl, char *parent_name, nvlist_t *props, - nvlist_t *pool_props, uint8_t **wkeydata_out, uint_t *wkeylen_out) + nvlist_t *pool_props, boolean_t stdin_available, uint8_t **wkeydata_out, + uint_t *wkeylen_out) { int ret; uint64_t crypt = ZIO_CRYPT_INHERIT, pcrypt = ZIO_CRYPT_INHERIT; @@ -848,6 +849,17 @@ zfs_crypto_create(libzfs_handle_t *hdl, char *parent_name, nvlist_t *props, * encryption root. Populate the encryption params. */ if (keylocation != NULL) { + /* + * 'zfs recv -o keylocation=prompt' won't work because stdin + * is being used by the send stream, so we disallow it. + */ + if (!stdin_available && strcmp(keylocation, "prompt") == 0) { + ret = EINVAL; + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "Cannot use " + "'prompt' keylocation because stdin is in use.")); + goto out; + } + ret = populate_create_encryption_params_nvlists(hdl, NULL, B_FALSE, keyformat, keylocation, props, &wkeydata, &wkeylen); diff --git a/usr/src/lib/libzfs/common/libzfs_dataset.c b/usr/src/lib/libzfs/common/libzfs_dataset.c index 9caae294b0..1cfe32dd67 100644 --- a/usr/src/lib/libzfs/common/libzfs_dataset.c +++ b/usr/src/lib/libzfs/common/libzfs_dataset.c @@ -3696,8 +3696,8 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type, } (void) parent_name(path, parent, sizeof (parent)); - if (zfs_crypto_create(hdl, parent, props, NULL, &wkeydata, - &wkeylen) != 0) { + if (zfs_crypto_create(hdl, parent, props, NULL, B_TRUE, + &wkeydata, &wkeylen) != 0) { nvlist_free(props); return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf)); } diff --git a/usr/src/lib/libzfs/common/libzfs_pool.c b/usr/src/lib/libzfs/common/libzfs_pool.c index fd33fb8e1a..c18f565354 100644 --- a/usr/src/lib/libzfs/common/libzfs_pool.c +++ b/usr/src/lib/libzfs/common/libzfs_pool.c @@ -1214,7 +1214,7 @@ zpool_create(libzfs_handle_t *hdl, const char *pool, nvlist_t *nvroot, (nvlist_alloc(&zc_props, NV_UNIQUE_NAME, 0) != 0)) { goto create_failed; } - if (zfs_crypto_create(hdl, NULL, zc_fsprops, props, + if (zfs_crypto_create(hdl, NULL, zc_fsprops, props, B_TRUE, &wkeydata, &wkeylen) != 0) { (void) zfs_error(hdl, EZFS_CRYPTOFAILED, msg); goto create_failed; diff --git a/usr/src/lib/libzfs/common/libzfs_sendrecv.c b/usr/src/lib/libzfs/common/libzfs_sendrecv.c index 56583244f4..13e1bafa5d 100644 --- a/usr/src/lib/libzfs/common/libzfs_sendrecv.c +++ b/usr/src/lib/libzfs/common/libzfs_sendrecv.c @@ -28,6 +28,8 @@ * Copyright 2015, OmniTI Computer Consulting, Inc. All rights reserved. * Copyright (c) 2014 Integros [integros.com] * Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com> + * Copyright (c) 2017, loli10K <ezomori.nozomu@gmail.com>. All rights reserved. + * Copyright (c) 2018 Datto Inc. */ #include <assert.h> @@ -602,14 +604,18 @@ typedef struct send_data { 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 raw; boolean_t recursive; + boolean_t raw; boolean_t verbose; + boolean_t backup; + boolean_t holds; /* were holds requested with send -h */ + boolean_t props; /* * The header nvlist is of the following format: @@ -625,6 +631,7 @@ typedef struct send_data { * "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 @@ -636,7 +643,8 @@ typedef struct send_data { */ } send_data_t; -static void send_iterate_prop(zfs_handle_t *zhp, nvlist_t *nv); +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) @@ -672,20 +680,35 @@ send_iterate_snap(zfs_handle_t *zhp, void *arg) } VERIFY(0 == nvlist_alloc(&nv, NV_UNIQUE_NAME, 0)); - send_iterate_prop(zhp, nv); + send_iterate_prop(zhp, sd->backup, nv); VERIFY(0 == nvlist_add_nvlist(sd->snapprops, snapname, nv)); nvlist_free(nv); + if (sd->holds) { + nvlist_t *holds = fnvlist_alloc(); + int err = lzc_get_holds(zhp->zfs_name, &holds); + if (err == 0) { + VERIFY(0 == nvlist_add_nvlist(sd->snapholds, + snapname, holds)); + } + fnvlist_free(holds); + } zfs_close(zhp); return (0); } static void -send_iterate_prop(zfs_handle_t *zhp, nvlist_t *nv) +send_iterate_prop(zfs_handle_t *zhp, boolean_t received_only, nvlist_t *nv) { + nvlist_t *props = NULL; nvpair_t *elem = NULL; - while ((elem = nvlist_next_nvpair(zhp->zfs_props, 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; @@ -848,8 +871,10 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg) } /* iterate over props */ - VERIFY(0 == nvlist_alloc(&nv, NV_UNIQUE_NAME, 0)); - send_iterate_prop(zhp, nv); + if (sd->props || sd->backup || sd->recursive) { + VERIFY(0 == nvlist_alloc(&nv, NV_UNIQUE_NAME, 0)); + send_iterate_prop(zhp, sd->backup, nv); + } if (zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) != ZIO_CRYPT_OFF) { boolean_t encroot; @@ -880,17 +905,24 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg) } - VERIFY(0 == nvlist_add_nvlist(nvfs, "props", nv)); + if (nv != NULL) + VERIFY(0 == nvlist_add_nvlist(nvfs, "props", nv)); /* iterate over snaps, and set sd->parent_fromsnap_guid */ sd->parent_fromsnap_guid = 0; VERIFY(0 == nvlist_alloc(&sd->parent_snaps, NV_UNIQUE_NAME, 0)); VERIFY(0 == nvlist_alloc(&sd->snapprops, NV_UNIQUE_NAME, 0)); + if (sd->holds) + VERIFY(0 == nvlist_alloc(&sd->snapholds, NV_UNIQUE_NAME, 0)); (void) zfs_iter_snapshots(zhp, B_FALSE, send_iterate_snap, sd); VERIFY(0 == nvlist_add_nvlist(nvfs, "snaps", sd->parent_snaps)); VERIFY(0 == nvlist_add_nvlist(nvfs, "snapprops", sd->snapprops)); + if (sd->holds) + VERIFY(0 == nvlist_add_nvlist(nvfs, "snapholds", + sd->snapholds)); nvlist_free(sd->parent_snaps); nvlist_free(sd->snapprops); + nvlist_free(sd->snapholds); /* add this fs to nvlist */ (void) snprintf(guidstring, sizeof (guidstring), @@ -914,8 +946,9 @@ out: 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 verbose, - nvlist_t **nvlp, avl_tree_t **avlp) + const char *tosnap, boolean_t recursive, boolean_t raw, + 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 }; @@ -932,6 +965,9 @@ gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap, sd.recursive = recursive; sd.raw = raw; sd.verbose = verbose; + sd.backup = backup; + sd.holds = holds; + sd.props = props; if ((error = send_iterate_fs(zhp, &sd)) != 0) { nvlist_free(sd.fss); @@ -962,7 +998,7 @@ typedef struct send_dump_data { uint64_t prevsnap_obj; boolean_t seenfrom, seento, replicate, doall, fromorigin; boolean_t verbose, dryrun, parsable, progress, embed_data, std_out; - boolean_t large_block, compress, raw; + boolean_t large_block, compress, raw, holds; int outfd; boolean_t err; nvlist_t *fss; @@ -1823,6 +1859,9 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap, } } + if (flags->holds) + featureflags |= DMU_BACKUP_FEATURE_HOLDS; + /* * Start the dedup thread if this is a dedup stream. We do not bother * doing this if this a raw send of an encrypted dataset with dedup off @@ -1850,13 +1889,17 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap, } } - if (flags->replicate || flags->doall || flags->props) { + if (flags->replicate || flags->doall || flags->props || + flags->holds || flags->backup) { dmu_replay_record_t drr = { 0 }; char *packbuf = NULL; size_t buflen = 0; - zio_cksum_t zc = { 0 }; + zio_cksum_t zc; - if (flags->replicate || flags->props) { + ZIO_SET_CHECKSUM(&zc, 0, 0, 0, 0); + + if (flags->replicate || flags->props || flags->backup || + flags->holds) { nvlist_t *hdrnv; VERIFY(0 == nvlist_alloc(&hdrnv, NV_UNIQUE_NAME, 0)); @@ -1875,7 +1918,9 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap, err = gather_nvlist(zhp->zfs_hdl, zhp->zfs_name, fromsnap, tosnap, flags->replicate, flags->raw, - flags->verbose, &fss, &fsavl); + flags->verbose, flags->backup, + flags->holds, flags->props, &fss, + &fsavl); if (err) goto err_out; VERIFY(0 == nvlist_add_nvlist(hdrnv, "fss", fss)); @@ -1941,6 +1986,7 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap, sdd.embed_data = flags->embed_data; 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) @@ -1973,6 +2019,7 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap, sdd.cleanup_fd = -1; sdd.snapholds = NULL; } + if (flags->verbose || sdd.snapholds != NULL) { /* * Do a verbose no-op dry run to get all the verbose output @@ -2041,7 +2088,7 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap, } if (!flags->dryrun && (flags->replicate || flags->doall || - flags->props)) { + 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 @@ -2745,7 +2792,8 @@ again: needagain = progress = B_FALSE; if ((error = gather_nvlist(hdl, tofs, fromsnap, NULL, - recursive, B_TRUE, B_FALSE, &local_nv, &local_avl)) != 0) + recursive, B_TRUE, B_FALSE, + B_FALSE, B_FALSE, B_TRUE, &local_nv, &local_avl)) != 0) return (error); /* @@ -3344,6 +3392,191 @@ recv_ecksum_set_aux(libzfs_handle_t *hdl, const char *target_snap, } /* + * 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; + + /* + * we're trying to override or exclude 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) && + !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; + } + + /* raw streams can't override encryption properties */ + if ((zfs_prop_encryption_key_param(prop) || + prop == ZFS_PROP_ENCRYPTION) && (raw || !newfs)) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "encryption property '%s' cannot " + "be set or excluded for raw or 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. + * 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; + + attrs = fnvlist_lookup_nvlist(origprops, name); + if (strcmp(fnvlist_lookup_string(attrs, + ZPROP_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_inheritable(prop) && + !zfs_prop_user(name) && /* can be inherited too */ + nvlist_exists(recvprops, name)) + fnvlist_remove(recvprops, name); + else + fnvlist_add_nvpair(*oxprops, nvp); + break; + case DATA_TYPE_STRING: /* -o property=value */ + 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 && 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 @@ -3353,29 +3586,41 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, avl_tree_t *stream_avl, char **top_zfs, int cleanup_fd, uint64_t *action_handlep, const char *finalsnap, nvlist_t *cmdprops) { - zfs_cmd_t zc = { 0 }; time_t begin_time; int ioctl_err, ioctl_errno, err; char *cp; struct drr_begin *drrb = &drr->drr_u.drr_begin; char errbuf[1024]; - char prop_errbuf[1024]; const char *chopprefix; boolean_t newfs = B_FALSE; boolean_t stream_wantsnewfs; 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; - nvlist_t *props = 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; + 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, @@ -3384,18 +3629,20 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, 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); - int ret; (void) nvlist_lookup_uint64(fs, "parentfromsnap", &parent_snapguid); - err = nvlist_lookup_nvlist(fs, "props", &props); + err = nvlist_lookup_nvlist(fs, "props", &rcvprops); if (err) { - VERIFY(0 == nvlist_alloc(&props, NV_UNIQUE_NAME, 0)); + VERIFY(0 == nvlist_alloc(&rcvprops, NV_UNIQUE_NAME, 0)); newprops = B_TRUE; } /* @@ -3406,28 +3653,32 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, * the keylocation for now to avoid any errors from the receive * ioctl. */ - err = nvlist_lookup_string(props, + err = nvlist_lookup_string(rcvprops, zfs_prop_to_name(ZFS_PROP_KEYLOCATION), &keylocation); if (err == 0) { (void) strcpy(tmp_keylocation, keylocation); - (void) nvlist_remove_all(props, + (void) nvlist_remove_all(rcvprops, zfs_prop_to_name(ZFS_PROP_KEYLOCATION)); } if (flags->canmountoff) { - VERIFY(0 == nvlist_add_uint64(props, + VERIFY(0 == nvlist_add_uint64(rcvprops, zfs_prop_to_name(ZFS_PROP_CANMOUNT), 0)); + } else if (newprops) { /* nothing in rcvprops, eliminate it */ + nvlist_free(rcvprops); + rcvprops = NULL; + newprops = B_FALSE; } - ret = zcmd_write_src_nvlist(hdl, &zc, props); - if (0 == nvlist_lookup_nvlist(fs, "snapprops", &lookup)) { VERIFY(0 == nvlist_lookup_nvlist(lookup, snapname, &snapprops_nvlist)); } - - if (ret != 0) { - err = -1; - goto out; + if (holds) { + if (0 == nvlist_lookup_nvlist(fs, "snapholds", + &lookup)) { + VERIFY(0 == nvlist_lookup_nvlist(lookup, + snapname, &snapholds_nvlist)); + } } } @@ -3511,11 +3762,10 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, /* * Determine name of destination snapshot, store in zc_value. */ - (void) strcpy(zc.zc_value, tosnap); - (void) strncat(zc.zc_value, chopprefix, sizeof (zc.zc_value)); + (void) strlcpy(destsnap, tosnap, sizeof (destsnap)); + (void) strlcat(destsnap, chopprefix, sizeof (destsnap)); free(cp); - if (!zfs_name_valid(zc.zc_value, ZFS_TYPE_SNAPSHOT)) { - zcmd_free_nvlists(&zc); + if (!zfs_name_valid(destsnap, ZFS_TYPE_SNAPSHOT)) { err = zfs_error(hdl, EZFS_INVALIDNAME, errbuf); goto out; } @@ -3524,22 +3774,21 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, * Determine the name of the origin snapshot, store in zc_string. */ if (originsnap) { - (void) strncpy(zc.zc_string, originsnap, sizeof (zc.zc_string)); + (void) strlcpy(origin, originsnap, sizeof (origin)); if (flags->verbose) (void) printf("using provided clone origin %s\n", - zc.zc_string); + origin); } else if (drrb->drr_flags & DRR_FLAG_CLONE) { - if (guid_to_name(hdl, zc.zc_value, - drrb->drr_fromguid, B_FALSE, zc.zc_string) != 0) { - zcmd_free_nvlists(&zc); + 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"), - zc.zc_value); + destsnap); err = zfs_error(hdl, EZFS_NOENT, errbuf); goto out; } if (flags->verbose) - (void) printf("found clone origin %s\n", zc.zc_string); + (void) printf("found clone origin %s\n", origin); } boolean_t resuming = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo) & @@ -3559,18 +3808,18 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, "cannot receive new filesystem stream")); - (void) strcpy(zc.zc_name, zc.zc_value); - cp = strrchr(zc.zc_name, '/'); + (void) strcpy(name, destsnap); + cp = strrchr(name, '/'); if (cp) *cp = '\0'; if (cp && - !zfs_dataset_exists(hdl, zc.zc_name, ZFS_TYPE_DATASET)) { + !zfs_dataset_exists(hdl, name, ZFS_TYPE_DATASET)) { char suffix[ZFS_MAX_DATASET_NAME_LEN]; - (void) strcpy(suffix, strrchr(zc.zc_value, '/')); - if (guid_to_name(hdl, zc.zc_name, parent_snapguid, - B_FALSE, zc.zc_value) == 0) { - *strchr(zc.zc_value, '@') = '\0'; - (void) strcat(zc.zc_value, suffix); + (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 { @@ -3581,8 +3830,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, "cannot receive incremental stream")); - (void) strcpy(zc.zc_name, zc.zc_value); - *strchr(zc.zc_name, '@') = '\0'; + (void) strcpy(name, destsnap); + *strchr(name, '@') = '\0'; /* * If the exact receive path was specified and this is the @@ -3591,24 +3840,27 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, */ if ((flags->isprefix || (*(chopprefix = drrb->drr_toname + strlen(sendfs)) != '\0' && *chopprefix != '@')) && - !zfs_dataset_exists(hdl, zc.zc_name, ZFS_TYPE_DATASET)) { + !zfs_dataset_exists(hdl, name, ZFS_TYPE_DATASET)) { char snap[ZFS_MAX_DATASET_NAME_LEN]; - (void) strcpy(snap, strchr(zc.zc_value, '@')); - if (guid_to_name(hdl, zc.zc_name, drrb->drr_fromguid, - B_FALSE, zc.zc_value) == 0) { - *strchr(zc.zc_value, '@') = '\0'; - (void) strcat(zc.zc_value, snap); + (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(zc.zc_name, zc.zc_value); - *strchr(zc.zc_name, '@') = '\0'; + (void) strcpy(name, destsnap); + *strchr(name, '@') = '\0'; - if (zfs_dataset_exists(hdl, zc.zc_name, ZFS_TYPE_DATASET)) { + 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 @@ -3622,8 +3874,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, zcmd_free_nvlists(&zc); zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "destination '%s' exists\n" - "must specify -F to overwrite it"), - zc.zc_name); + "must specify -F to overwrite it"), name); err = zfs_error(hdl, EZFS_EXISTS, errbuf); goto out; } @@ -3639,7 +3890,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, } } - if ((zhp = zfs_open(hdl, zc.zc_name, + if ((zhp = zfs_open(hdl, name, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME)) == NULL) { zcmd_free_nvlists(&zc); err = -1; @@ -3652,8 +3903,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, zfs_close(zhp); zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "destination '%s' is a clone\n" - "must destroy it to overwrite it"), - zc.zc_name); + "must destroy it to overwrite it"), name); err = zfs_error(hdl, EZFS_EXISTS, errbuf); goto out; } @@ -3698,14 +3948,12 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, clp = changelist_gather(zhp, ZFS_PROP_NAME, 0, 0); if (clp == NULL) { zfs_close(zhp); - zcmd_free_nvlists(&zc); err = -1; goto out; } if (changelist_prefix(clp) != 0) { changelist_free(clp); zfs_close(zhp); - zcmd_free_nvlists(&zc); err = -1; goto out; } @@ -3722,8 +3970,24 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, 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); + cp = NULL; } else { + zfs_handle_t *zhp; + /* * Destination filesystem does not exist. Therefore we better * be creating a new filesystem (either from a full backup, or @@ -3731,11 +3995,11 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, * specified only the pool name (i.e. if the destination name * contained no slash character). */ - if (!stream_wantsnewfs || - (cp = strrchr(zc.zc_name, '/')) == NULL) { - zcmd_free_nvlists(&zc); + cp = strrchr(name, '/'); + + if (!stream_wantsnewfs || cp == NULL) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "destination '%s' does not exist"), zc.zc_name); + "destination '%s' does not exist"), name); err = zfs_error(hdl, EZFS_NOENT, errbuf); goto out; } @@ -3747,65 +4011,110 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, *cp = '\0'; if (flags->isprefix && !flags->istail && !flags->dryrun && - create_parents(hdl, zc.zc_value, strlen(tosnap)) != 0) { - zcmd_free_nvlists(&zc); + 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; + } + + /* + * It is invalid to receive a properties stream that was + * unencrypted on the send side as a child of an encrypted + * parent. Technically there is nothing preventing this, but + * it would mean that the encryption=off property which is + * locally set on the send side would not be received correctly. + * We can infer encryption=off if the stream is not raw and + * properties were included since the send side will only ever + * send the encryption property in a raw nvlist header. This + * check will be avoided if the user specifically overrides + * the encryption property on the command line. + */ + if (!raw && rcvprops != NULL && + !nvlist_exists(cmdprops, + zfs_prop_to_name(ZFS_PROP_ENCRYPTION))) { + uint64_t crypt; + + crypt = zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION); + + if (crypt != ZIO_CRYPT_OFF) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "parent '%s' must not be encrypted to " + "receive unenecrypted property"), name); + err = zfs_error(hdl, EZFS_BADPROP, errbuf); + zfs_close(zhp); + goto out; + } + } + zfs_close(zhp); newfs = B_TRUE; + *cp = '/'; } - zc.zc_begin_record = *drr_noswap; - zc.zc_cookie = infd; - zc.zc_guid = flags->force; - zc.zc_resumable = flags->resumable; 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, zc.zc_value); + drrb->drr_toname, destsnap); (void) fflush(stdout); } if (flags->dryrun) { - zcmd_free_nvlists(&zc); err = recv_skip(hdl, infd, flags->byteswap); 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 (top_zfs && (*top_zfs == NULL || strcmp(*top_zfs, name) == 0)) + toplevel = B_TRUE; + 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; - zc.zc_nvlist_dst = (uint64_t)(uintptr_t)prop_errbuf; - zc.zc_nvlist_dst_size = sizeof (prop_errbuf); - zc.zc_cleanup_fd = cleanup_fd; - zc.zc_action_handle = *action_handlep; + /* + * The following is a difference between ZoL and illumos. + * + * On illumos, we must trim the last component of the dataset name + * that is passed via the ioctl so that we can properly validate + * zfs_secpolicy_recv() when receiving to a delegated dataset within + * zone. This matches the historical behavior of the receive ioctl. + * However, we can't do this until after zfs_setup_cmdline_props() + * has finished with the full name. + */ + if (cp != NULL) + *cp = '\0'; - err = ioctl_err = zfs_ioctl(hdl, ZFS_IOC_RECV, &zc); + err = ioctl_err = lzc_receive_with_cmdprops(destsnap, rcvprops, + oxprops, wkeydata, wkeylen, origin, flags->force, flags->resumable, + raw, infd, drr_noswap, cleanup_fd, &read_bytes, &errflags, + action_handlep, &prop_errors); ioctl_errno = errno; - prop_errflags = (zprop_errflags_t)zc.zc_obj; + prop_errflags = errflags; if (err == 0) { - nvlist_t *prop_errors; - VERIFY(0 == nvlist_unpack((void *)(uintptr_t)zc.zc_nvlist_dst, - zc.zc_nvlist_dst_size, &prop_errors, 0)); - nvpair_t *prop_err = NULL; while ((prop_err = nvlist_next_nvpair(prop_errors, @@ -3838,26 +4147,38 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, (void) snprintf(tbuf, sizeof (tbuf), dgettext(TEXT_DOMAIN, "cannot receive %s property on %s"), - nvpair_name(prop_err), zc.zc_name); + nvpair_name(prop_err), name); zfs_setprop_error(hdl, prop, intval, tbuf); } } nvlist_free(prop_errors); } - zc.zc_nvlist_dst = 0; - zc.zc_nvlist_dst_size = 0; - zcmd_free_nvlists(&zc); - if (err == 0 && snapprops_nvlist) { - zfs_cmd_t zc2 = { 0 }; + zfs_cmd_t zc = { 0 }; - (void) strcpy(zc2.zc_name, zc.zc_value); - zc2.zc_cookie = B_TRUE; /* received */ - if (zcmd_write_src_nvlist(hdl, &zc2, snapprops_nvlist) == 0) { - (void) zfs_ioctl(hdl, ZFS_IOC_SET_PROP, &zc2); - zcmd_free_nvlists(&zc2); + (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)) { + VERIFY(0 == nvlist_add_string(holds, destsnap, + nvpair_name(pair))); } + (void) lzc_hold(holds, cleanup_fd, &errors); + nvlist_free(snapholds_nvlist); + nvlist_free(holds); } if (err && (ioctl_errno == ENOENT || ioctl_errno == EEXIST)) { @@ -3868,7 +4189,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, */ avl_tree_t *local_avl; nvlist_t *local_nv, *fs; - cp = strchr(zc.zc_value, '@'); + cp = strchr(destsnap, '@'); /* * XXX Do this faster by just iterating over snaps in @@ -3876,8 +4197,9 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, * get a strange "does not exist" error message. */ *cp = '\0'; - if (gather_nvlist(hdl, zc.zc_value, NULL, NULL, B_FALSE, B_TRUE, - B_FALSE, &local_nv, &local_avl) == 0) { + if (gather_nvlist(hdl, destsnap, NULL, NULL, B_FALSE, B_TRUE, + 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); @@ -3886,7 +4208,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, if (fs != NULL) { if (flags->verbose) { (void) printf("snap %s already exists; " - "ignoring\n", zc.zc_value); + "ignoring\n", destsnap); } err = ioctl_err = recv_skip(hdl, infd, flags->byteswap); @@ -3898,18 +4220,18 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, if (ioctl_err != 0) { switch (ioctl_errno) { case ENODEV: - cp = strchr(zc.zc_value, '@'); + cp = strchr(destsnap, '@'); *cp = '\0'; zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "most recent snapshot of %s does not\n" - "match incremental source"), zc.zc_value); + "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"), zc.zc_name); + "since most recent snapshot"), name); (void) zfs_error(hdl, EZFS_BADRESTORE, errbuf); break; case EACCES: @@ -3927,7 +4249,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, (void) zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf); break; case EEXIST: - cp = strchr(zc.zc_value, '@'); + cp = strchr(destsnap, '@'); if (newfs) { /* it's the containing fs that exists */ *cp = '\0'; @@ -3936,7 +4258,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, "destination already exists")); (void) zfs_error_fmt(hdl, EZFS_EXISTS, dgettext(TEXT_DOMAIN, "cannot restore to %s"), - zc.zc_value); + destsnap); *cp = '@'; break; case EINVAL: @@ -3947,7 +4269,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf); break; case ECKSUM: - recv_ecksum_set_aux(hdl, zc.zc_value, flags->resumable); + recv_ecksum_set_aux(hdl, destsnap, flags->resumable); (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf); break; case ENOTSUP: @@ -3957,7 +4279,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, break; case EDQUOT: zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "destination %s space quota exceeded"), zc.zc_name); + "destination %s space quota exceeded."), name); (void) zfs_error(hdl, EZFS_NOSPC, errbuf); break; case ZFS_ERR_FROM_IVSET_GUID_MISSING: @@ -3974,6 +4296,23 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, "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; + } + /* fallthru */ default: (void) zfs_standard_error(hdl, ioctl_errno, errbuf); } @@ -3984,12 +4323,12 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, * children of the target filesystem if we did a replication * receive (indicated by stream_avl being non-NULL). */ - cp = strchr(zc.zc_value, '@'); + cp = strchr(destsnap, '@'); if (cp && (ioctl_err == 0 || !newfs)) { zfs_handle_t *h; *cp = '\0'; - h = zfs_open(hdl, zc.zc_value, + h = zfs_open(hdl, destsnap, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME); if (h != NULL) { if (h->zfs_type == ZFS_TYPE_VOLUME) { @@ -4000,7 +4339,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, * for mounting and sharing later. */ if (top_zfs && *top_zfs == NULL) - *top_zfs = zfs_strdup(hdl, zc.zc_value); + *top_zfs = zfs_strdup(hdl, destsnap); } zfs_close(h); } @@ -4015,14 +4354,12 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, if (prop_errflags & ZPROP_ERR_NOCLEAR) { (void) fprintf(stderr, dgettext(TEXT_DOMAIN, "Warning: " - "failed to clear unreceived properties on %s"), - zc.zc_name); + "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"), - zc.zc_name); + "failed to restore original properties on %s"), name); (void) fprintf(stderr, "\n"); } @@ -4031,12 +4368,10 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, goto out; } - *action_handlep = zc.zc_action_handle; - if (flags->verbose) { char buf1[64]; char buf2[64]; - uint64_t bytes = zc.zc_cookie; + uint64_t bytes = read_bytes; time_t delta = time(NULL) - begin_time; if (delta == 0) delta = 1; @@ -4051,16 +4386,70 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, out: if (tmp_keylocation[0] != '\0') { - VERIFY(0 == nvlist_add_string(props, + VERIFY(0 == nvlist_add_string(rcvprops, zfs_prop_to_name(ZFS_PROP_KEYLOCATION), tmp_keylocation)); } if (newprops) - nvlist_free(props); + nvlist_free(rcvprops); + + nvlist_free(oxprops); + nvlist_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, @@ -4078,6 +4467,11 @@ zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap, (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 " @@ -4143,6 +4537,12 @@ zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap, return (zfs_error(hdl, EZFS_BADSTREAM, errbuf)); } + /* Holds feature is set once in the compound stream header. */ + boolean_t holds = (DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo) & + DMU_BACKUP_FEATURE_HOLDS); + if (holds) + flags->holds = B_TRUE; + if (strchr(drrb->drr_toname, '@') == NULL) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid " "stream (bad snapshot name)")); diff --git a/usr/src/lib/libzfs/common/libzfs_util.c b/usr/src/lib/libzfs/common/libzfs_util.c index cad82d0c9a..f755a43715 100644 --- a/usr/src/lib/libzfs/common/libzfs_util.c +++ b/usr/src/lib/libzfs/common/libzfs_util.c @@ -263,6 +263,8 @@ libzfs_error_description(libzfs_handle_t *hdl) case EZFS_NO_INITIALIZE: return (dgettext(TEXT_DOMAIN, "there is no active " "initialization")); + case EZFS_WRONG_PARENT: + return (dgettext(TEXT_DOMAIN, "invalid parent dataset")); case EZFS_NO_RESILVER_DEFER: return (dgettext(TEXT_DOMAIN, "this action requires the " "resilver_defer feature")); diff --git a/usr/src/lib/libzfs_core/common/libzfs_core.c b/usr/src/lib/libzfs_core/common/libzfs_core.c index df973b532d..ac8ec6d11a 100644 --- a/usr/src/lib/libzfs_core/common/libzfs_core.c +++ b/usr/src/lib/libzfs_core/common/libzfs_core.c @@ -670,82 +670,126 @@ recv_read(int fd, void *buf, int ilen) } static int -recv_impl(const char *snapname, nvlist_t *props, const char *origin, - boolean_t force, boolean_t resumable, boolean_t raw, int fd, - const dmu_replay_record_t *begin_record) +recv_impl(const char *snapname, nvlist_t *recvdprops, nvlist_t *localprops, + uint8_t *wkeydata, uint_t wkeylen, const char *origin, boolean_t force, + boolean_t resumable, boolean_t raw, int input_fd, + const dmu_replay_record_t *begin_record, int cleanup_fd, + uint64_t *read_bytes, uint64_t *errflags, uint64_t *action_handle, + nvlist_t **errors) { + /* * The receive ioctl is still legacy, so we need to construct our own * zfs_cmd_t rather than using zfsc_ioctl(). */ zfs_cmd_t zc = { 0 }; - char *atp; char *packed = NULL; size_t size; + + dmu_replay_record_t drr; + char fsname[MAXPATHLEN]; + char *atp; int error; ASSERT3S(g_refcount, >, 0); VERIFY3S(g_fd, !=, -1); - /* zc_name is name of containing filesystem */ - (void) strlcpy(zc.zc_name, snapname, sizeof (zc.zc_name)); - atp = strchr(zc.zc_name, '@'); + /* Set 'fsname' to the name of containing filesystem */ + (void) strlcpy(fsname, snapname, sizeof (fsname)); + atp = strchr(fsname, '@'); if (atp == NULL) return (EINVAL); *atp = '\0'; /* if the fs does not exist, try its parent. */ - if (!lzc_exists(zc.zc_name)) { - char *slashp = strrchr(zc.zc_name, '/'); + if (!lzc_exists(fsname)) { + char *slashp = strrchr(fsname, '/'); if (slashp == NULL) return (ENOENT); *slashp = '\0'; + } + /* + * The begin_record is normally a non-byteswapped BEGIN record. + * For resumable streams it may be set to any non-byteswapped + * dmu_replay_record_t. + */ + if (begin_record == NULL) { + error = recv_read(input_fd, &drr, sizeof (drr)); + if (error != 0) + return (error); + } else { + drr = *begin_record; } - /* zc_value is full name of the snapshot to create */ + (void) strlcpy(zc.zc_name, fsname, sizeof (zc.zc_name)); (void) strlcpy(zc.zc_value, snapname, sizeof (zc.zc_value)); - if (props != NULL) { - /* zc_nvlist_src is props to set */ - packed = fnvlist_pack(props, &size); + if (recvdprops != NULL) { + packed = fnvlist_pack(recvdprops, &size); zc.zc_nvlist_src = (uint64_t)(uintptr_t)packed; zc.zc_nvlist_src_size = size; } - /* zc_string is name of clone origin (if DRR_FLAG_CLONE) */ - if (origin != NULL) - (void) strlcpy(zc.zc_string, origin, sizeof (zc.zc_string)); + if (localprops != NULL) { + packed = fnvlist_pack(localprops, &size); + zc.zc_nvlist_conf = (uint64_t)(uintptr_t)packed; + zc.zc_nvlist_conf_size = size; + } - /* zc_begin_record is non-byteswapped BEGIN record */ - if (begin_record == NULL) { - error = recv_read(fd, &zc.zc_begin_record, - sizeof (zc.zc_begin_record)); - if (error != 0) - goto out; - } else { - zc.zc_begin_record = *begin_record; + /* Use zc_history_ members for hidden args */ + if (wkeydata != NULL) { + nvlist_t *hidden_args = fnvlist_alloc(); + fnvlist_add_uint8_array(hidden_args, "wkeydata", wkeydata, + wkeylen); + packed = fnvlist_pack(hidden_args, &size); + zc.zc_history_offset = (uint64_t)(uintptr_t)packed; + zc.zc_history_len = size; } - /* zc_cookie is fd to read from */ - zc.zc_cookie = fd; + if (origin != NULL) + (void) strlcpy(zc.zc_string, origin, sizeof (zc.zc_string)); - /* zc guid is force flag */ + ASSERT3S(drr.drr_type, ==, DRR_BEGIN); + zc.zc_begin_record = drr; zc.zc_guid = force; - + zc.zc_cookie = input_fd; + zc.zc_cleanup_fd = -1; + zc.zc_action_handle = 0; zc.zc_resumable = resumable; - /* zc_cleanup_fd is unused */ - zc.zc_cleanup_fd = -1; + if (cleanup_fd >= 0) + zc.zc_cleanup_fd = cleanup_fd; + + if (action_handle != NULL) + zc.zc_action_handle = *action_handle; + + zc.zc_nvlist_dst_size = 128 * 1024; + zc.zc_nvlist_dst = (uint64_t)(uintptr_t)malloc(zc.zc_nvlist_dst_size); error = ioctl(g_fd, ZFS_IOC_RECV, &zc); - if (error != 0) + if (error != 0) { error = errno; + } else { + if (read_bytes != NULL) + *read_bytes = zc.zc_cookie; + + if (errflags != NULL) + *errflags = zc.zc_obj; + + if (action_handle != NULL) + *action_handle = zc.zc_action_handle; + + if (errors != NULL) + VERIFY0(nvlist_unpack( + (void *)(uintptr_t)zc.zc_nvlist_dst, + zc.zc_nvlist_dst_size, errors, KM_SLEEP)); + } -out: if (packed != NULL) fnvlist_pack_free(packed, size); free((void*)(uintptr_t)zc.zc_nvlist_dst); + return (error); } @@ -766,8 +810,8 @@ int lzc_receive(const char *snapname, nvlist_t *props, const char *origin, boolean_t raw, boolean_t force, int fd) { - return (recv_impl(snapname, props, origin, force, B_FALSE, raw, fd, - NULL)); + return (recv_impl(snapname, props, NULL, NULL, 0, origin, force, + B_FALSE, raw, fd, NULL, -1, NULL, NULL, NULL, NULL)); } /* @@ -780,8 +824,8 @@ int lzc_receive_resumable(const char *snapname, nvlist_t *props, const char *origin, boolean_t force, boolean_t raw, int fd) { - return (recv_impl(snapname, props, origin, force, B_TRUE, raw, fd, - NULL)); + return (recv_impl(snapname, props, NULL, NULL, 0, origin, force, + B_TRUE, raw, fd, NULL, -1, NULL, NULL, NULL, NULL)); } /* @@ -802,8 +846,28 @@ lzc_receive_with_header(const char *snapname, nvlist_t *props, { if (begin_record == NULL) return (EINVAL); - return (recv_impl(snapname, props, origin, force, resumable, raw, fd, - begin_record)); + + return (recv_impl(snapname, props, NULL, NULL, 0, origin, force, + resumable, raw, fd, begin_record, -1, NULL, NULL, NULL, NULL)); +} + +/* + * Allows the caller to pass an additional 'cmdprops' argument. + * + * The 'cmdprops' nvlist contains both override ('zfs receive -o') and + * exclude ('zfs receive -x') properties. Callers are responsible for freeing + * this nvlist + */ +int lzc_receive_with_cmdprops(const char *snapname, nvlist_t *props, + nvlist_t *cmdprops, uint8_t *wkeydata, uint_t wkeylen, const char *origin, + boolean_t force, boolean_t resumable, boolean_t raw, int input_fd, + const dmu_replay_record_t *begin_record, int cleanup_fd, + uint64_t *read_bytes, uint64_t *errflags, uint64_t *action_handle, + nvlist_t **errors) +{ + return (recv_impl(snapname, props, cmdprops, wkeydata, wkeylen, origin, + force, resumable, raw, input_fd, begin_record, cleanup_fd, + read_bytes, errflags, action_handle, errors)); } /* diff --git a/usr/src/lib/libzfs_core/common/libzfs_core.h b/usr/src/lib/libzfs_core/common/libzfs_core.h index f1905f9968..e1574feff6 100644 --- a/usr/src/lib/libzfs_core/common/libzfs_core.h +++ b/usr/src/lib/libzfs_core/common/libzfs_core.h @@ -92,6 +92,10 @@ int lzc_receive_resumable(const char *, nvlist_t *, const char *, boolean_t, boolean_t, int); int lzc_receive_with_header(const char *, nvlist_t *, const char *, boolean_t, boolean_t, boolean_t, int, const struct dmu_replay_record *); +int lzc_receive_with_cmdprops(const char *, nvlist_t *, nvlist_t *, + uint8_t *, uint_t, const char *, boolean_t, boolean_t, boolean_t, int, + const struct dmu_replay_record *, int, uint64_t *, uint64_t *, + uint64_t *, nvlist_t **); boolean_t lzc_exists(const char *); diff --git a/usr/src/lib/libzfs_core/common/mapfile-vers b/usr/src/lib/libzfs_core/common/mapfile-vers index c14ec17a43..5bcb42c726 100644 --- a/usr/src/lib/libzfs_core/common/mapfile-vers +++ b/usr/src/lib/libzfs_core/common/mapfile-vers @@ -38,6 +38,12 @@ $mapfile_version 2 +SYMBOL_VERSION ILLUMOS_0.6 { + global: + + lzc_receive_with_cmdprops; +} ILLUMOS_0.5; + SYMBOL_VERSION ILLUMOS_0.5 { global: diff --git a/usr/src/man/man1m/zfs.1m b/usr/src/man/man1m/zfs.1m index b4b09d0b1b..b1ba39dd4c 100644 --- a/usr/src/man/man1m/zfs.1m +++ b/usr/src/man/man1m/zfs.1m @@ -169,12 +169,12 @@ .Ar snapshot bookmark .Nm .Cm send -.Op Fl DLPRcenpvw +.Op Fl DLPRbcehnpvw .Op Oo Fl I Ns | Ns Fl i Oc Ar snapshot .Ar snapshot .Nm .Cm send -.Op Fl Lce +.Op Fl LPcenvw .Op Fl i Ar snapshot Ns | Ns Ar bookmark .Ar filesystem Ns | Ns Ar volume Ns | Ns Ar snapshot .Nm @@ -183,14 +183,18 @@ .Fl t Ar receive_resume_token .Nm .Cm receive -.Op Fl Fnsuv +.Op Fl Fhnsuv .Op Fl o Sy origin Ns = Ns Ar snapshot +.Op Fl o Ar property Ns = Ns Ar value +.Op Fl x Ar property .Ar filesystem Ns | Ns Ar volume Ns | Ns Ar snapshot .Nm .Cm receive -.Op Fl Fnsuv +.Op Fl Fhnsuv .Op Fl d Ns | Ns Fl e .Op Fl o Sy origin Ns = Ns Ar snapshot +.Op Fl o Ar property Ns = Ns Ar value +.Op Fl x Ar property .Ar filesystem .Nm .Cm receive @@ -3026,7 +3030,7 @@ feature. .It Xo .Nm .Cm send -.Op Fl DLPRcenpvw +.Op Fl DLPRbcehnpvw .Op Oo Fl I Ns | Ns Fl i Oc Ar snapshot .Ar snapshot .Xc @@ -3128,6 +3132,14 @@ See for details on ZFS feature flags and the .Sy embedded_data feature. +.It Fl b, -backup +Sends only received property values whether or not they are overridden by local +settings, but only if the dataset has ever been received. +Use this option when you want +.Nm zfs Cm receive +to restore received properties backed up on the sent dataset and to avoid +sending local settings that may have nothing to do with the source dataset, +but only with how the data is backed up. .It Fl c , -compressed Generate a more compact stream by using compressed WRITE records for blocks which are compressed on disk and in memory @@ -3147,6 +3159,12 @@ option is not supplied in conjunction with .Fl c , then the data will be decompressed before sending so it can be split into smaller block sizes. +.It Fl h, -holds +Generate a stream package that includes any snapshot holds (created with the +.Sy zfs hold +command), and indicating to +.Sy zfs receive +that the holds be applied to the dataset on the receiving system. .It Fl i Ar snapshot Generate an incremental stream from the first .Ar snapshot @@ -3345,16 +3363,20 @@ for more details. .It Xo .Nm .Cm receive -.Op Fl Fnsuv +.Op Fl Fhnsuv .Op Fl o Sy origin Ns = Ns Ar snapshot +.Op Fl o Ar property Ns = Ns Ar value +.Op Fl x Ar property .Ar filesystem Ns | Ns Ar volume Ns | Ns Ar snapshot .Xc .It Xo .Nm .Cm receive -.Op Fl Fnsuv +.Op Fl Fhnsuv .Op Fl d Ns | Ns Fl e .Op Fl o Sy origin Ns = Ns Ar snapshot +.Op Fl o Ar property Ns = Ns Ar value +.Op Fl x Ar property .Ar filesystem .Xc Creates a snapshot whose contents are as specified in the stream provided on @@ -3385,6 +3407,31 @@ destroyed by using the .Nm zfs Cm destroy Fl d command. .Pp +If +.Fl o Em property Ns = Ns Ar value +or +.Fl x Em property +is specified, it applies to the effective value of the property throughout +the entire subtree of replicated datasets. +Effective property values will be +set ( +.Fl o +) or inherited ( +.Fl x +) on the topmost in the replicated subtree. +In descendant datasets, if the property is set by the send stream, it will be +overridden by forcing the property to be inherited from the top‐most file +system. +Received properties are retained in spite of being overridden and may be +restored with +.Nm zfs Cm inherit Fl S . +Specifying +.Fl o Sy origin Ns = Ns Em snapshot +is a special case because, even if +.Sy origin +is a read-only property and cannot be set, it's allowed to receive the send +stream as a clone of the given snapshot. +.Pp Raw encrypted send streams (created with .Nm zfs Cm send Fl w ) may only be received as is, and cannot be re-encrypted, decrypted, or @@ -3433,7 +3480,6 @@ When an incremental raw receive is performed on top of an existing snapshot, ZFS will check to confirm that the "from" snapshot on both the source and destination were using the same IV set, ensuring the new IV set is consistent. .Pp -ds The name of the snapshot .Pq and file system, if a full stream is received that this subcommand creates depends on the argument type and the use of the @@ -3494,6 +3540,9 @@ snapshot as described in the paragraph above. Discard all but the last element of the sent snapshot's file system name, using that element to determine the name of the target file system for the new snapshot as described in the paragraph above. +.It Fl h +Skip the receive of holds. +There is no effect if holds are not sent. .It Fl n Do not actually receive the stream. This can be useful in conjunction with the @@ -3507,11 +3556,68 @@ Which snapshot was specified will not affect the success or failure of the receive, as long as the snapshot does exist. If the stream is an incremental send stream, all the normal verification will be performed. -.It Fl u -File system that is associated with the received stream is not mounted. -.It Fl v -Print verbose information about the stream and the time required to perform the -receive operation. +.It Fl o Em property Ns = Ns Ar value +Sets the specified property as if the command +.Nm zfs Cm set Em property Ns = Ns Ar value +was invoked immediately before the receive. +When receiving a stream from +.Nm zfs Cm send Fl R , +causes the property to be inherited by all descendant datasets, as though +.Nm zfs Cm inherit Em property +was run on any descendant datasets that have this property set on the +sending system. +.Pp +Any editable property can be set at receive time. +Set-once properties bound to the received data, such as +.Sy normalization +and +.Sy casesensitivity , +cannot be set at receive time even when the datasets are newly created by +.Nm zfs Cm receive . +Additionally both settable properties +.Sy version +and +.Sy volsize +cannot be set at receive time. +.Pp +The +.Fl o +option may be specified multiple times, for different properties. +An error results if the same property is specified in multiple +.Fl o +or +.Fl x +options. +.Pp +The +.Fl o +option may also be used to override encryption properties upon initial +receive. +This allows unencrypted streams to be received as encrypted datasets. +To cause the received dataset (or root dataset of a recursive stream) to be +received as an encryption root, specify encryption properties in the same +manner as is required for +.Nm +.Cm create . +For instance: +.Bd -literal +# zfs send tank/test@snap1 | zfs recv -o encryption=on -o keyformat=passphrase -o keylocation=file:///path/to/keyfile +.Ed +.Pp +Note that +.Op Fl o Ar keylocation Ns = Ns Ar prompt +may not be specified here, since stdin is already being utilized for the send +stream. +Once the receive has completed, you can use +.Nm +.Cm set +to change this setting after the fact. +Similarly, you can receive a dataset as an encrypted child by specifying +.Op Fl x Ar encryption +to force the property to be inherited. +Overriding encryption properties (except for +.Sy keylocation ) +is not possible with raw send streams. .It Fl s If the receive is interrupted, save the partially received state, rather than deleting it. @@ -3537,6 +3643,30 @@ feature enabled. See .Xr zpool-features 5 for details on ZFS feature flags. +.It Fl u +File system that is associated with the received stream is not mounted. +.It Fl v +Print verbose information about the stream and the time required to perform the +receive operation. +.It Fl x Em property +Ensures that the effective value of the specified property after the +receive is unaffected by the value of that property in the send stream (if any), +as if the property had been excluded from the send stream. +.Pp +If the specified property is not present in the send stream, this option does +nothing. +.Pp +If a received property needs to be overridden, the effective value will be +set or inherited, depending on whether the property is inheritable or not. +.Pp +In the case of an incremental update, +.Fl x +leaves any existing local setting or explicit inheritance unchanged. +.Pp +All +.Fl o +restrictions (e.g. set-once) apply equally to +.Fl x . .El .It Xo .Nm diff --git a/usr/src/pkg/manifests/system-test-zfstest.mf b/usr/src/pkg/manifests/system-test-zfstest.mf index d2f1f7e94f..53f9b77f35 100644 --- a/usr/src/pkg/manifests/system-test-zfstest.mf +++ b/usr/src/pkg/manifests/system-test-zfstest.mf @@ -1042,6 +1042,9 @@ file \ mode=0555 file path=opt/zfs-tests/tests/functional/cli_root/zfs_receive/cleanup \ mode=0555 +file \ + path=opt/zfs-tests/tests/functional/cli_root/zfs_receive/receive-o-x_props_override \ + mode=0555 file path=opt/zfs-tests/tests/functional/cli_root/zfs_receive/setup mode=0555 file \ path=opt/zfs-tests/tests/functional/cli_root/zfs_receive/zfs_receive_001_pos \ @@ -1086,6 +1089,9 @@ file \ path=opt/zfs-tests/tests/functional/cli_root/zfs_receive/zfs_receive_014_pos \ mode=0555 file \ + path=opt/zfs-tests/tests/functional/cli_root/zfs_receive/zfs_receive_015_pos \ + mode=0555 +file \ path=opt/zfs-tests/tests/functional/cli_root/zfs_receive/zfs_receive_from_encrypted \ mode=0555 file path=opt/zfs-tests/tests/functional/cli_root/zfs_receive/zfs_receive_raw \ @@ -1190,6 +1196,8 @@ file \ mode=0444 file path=opt/zfs-tests/tests/functional/cli_root/zfs_send/cleanup mode=0555 file path=opt/zfs-tests/tests/functional/cli_root/zfs_send/setup mode=0555 +file path=opt/zfs-tests/tests/functional/cli_root/zfs_send/zfs_send-b \ + mode=0555 file path=opt/zfs-tests/tests/functional/cli_root/zfs_send/zfs_send.cfg \ mode=0444 file path=opt/zfs-tests/tests/functional/cli_root/zfs_send/zfs_send_001_pos \ @@ -2819,14 +2827,18 @@ file path=opt/zfs-tests/tests/functional/rsend/send-wDR_encrypted_zvol \ file path=opt/zfs-tests/tests/functional/rsend/send_encrypted_files mode=0555 file path=opt/zfs-tests/tests/functional/rsend/send_encrypted_hierarchy \ mode=0555 +file path=opt/zfs-tests/tests/functional/rsend/send_encrypted_props mode=0555 file path=opt/zfs-tests/tests/functional/rsend/send_encrypted_truncated_files \ mode=0555 file path=opt/zfs-tests/tests/functional/rsend/send_freeobjects mode=0555 +file path=opt/zfs-tests/tests/functional/rsend/send_holds mode=0555 file path=opt/zfs-tests/tests/functional/rsend/send_mixed_raw mode=0555 file path=opt/zfs-tests/tests/functional/rsend/send_realloc_dnode_size \ mode=0555 file path=opt/zfs-tests/tests/functional/rsend/send_realloc_encrypted_files \ mode=0555 +file path=opt/zfs-tests/tests/functional/rsend/send_realloc_files mode=0555 +file path=opt/zfs-tests/tests/functional/rsend/send_spill_block mode=0555 file path=opt/zfs-tests/tests/functional/rsend/setup mode=0555 file path=opt/zfs-tests/tests/functional/scrub_mirror/cleanup mode=0555 file path=opt/zfs-tests/tests/functional/scrub_mirror/default.cfg mode=0444 diff --git a/usr/src/test/test-runner/stf/contrib/include/logapi.shlib b/usr/src/test/test-runner/stf/contrib/include/logapi.shlib index 32d27b10ce..80a744937a 100644 --- a/usr/src/test/test-runner/stf/contrib/include/logapi.shlib +++ b/usr/src/test/test-runner/stf/contrib/include/logapi.shlib @@ -78,6 +78,81 @@ function log_mustnot (( $? != 0 )) && log_fail } +# Execute a positive test but retry the command on failure if the output +# matches an expected pattern. Otherwise behave like log_must and exit +# $STF_FAIL is test fails. +# +# $1 - retry keyword +# $2 - retry attempts +# $3-$@ - command to execute +# +function log_must_retry +{ + typeset out="" + typeset logfile="/tmp/log.$$" + typeset status=1 + typeset expect=$1 + typeset retry=$2 + typeset delay=1 + shift 2 + + while [[ -e $logfile ]]; do + logfile="$logfile.$$" + done + + while (( $retry > 0 )); do + "$@" 2>$logfile + status=$? + out="cat $logfile" + + if (( $status == 0 )); then + $out | egrep -i "internal error|assertion failed" \ + > /dev/null 2>&1 + # internal error or assertion failed + if [[ $? -eq 0 ]]; then + print -u2 $($out) + _printerror "$@" "internal error or" \ + " assertion failure exited $status" + status=1 + else + [[ -n $LOGAPI_DEBUG ]] && print $($out) + _printsuccess "$@" + fi + break + else + $out | grep -i "$expect" > /dev/null 2>&1 + if (( $? == 0 )); then + print -u2 $($out) + _printerror "$@" "Retry in $delay seconds" + sleep $delay + + (( retry=retry - 1 )) + (( delay=delay * 2 )) + else + break; + fi + fi + done + + if (( $status != 0 )) ; then + print -u2 $($out) + _printerror "$@" "exited $status" + fi + + _recursive_output $logfile "false" + return $status +} + +# Execute a positive test and exit $STF_FAIL is test fails after being +# retried up to 5 times when the command returns the keyword "busy". +# +# $@ - command to execute +function log_must_busy +{ + log_must_retry "busy" 5 "$@" + (( $? != 0 )) && log_fail +} + # Execute a negative test with keyword expected, and exit # $STF_FAIL if test passes # diff --git a/usr/src/test/zfs-tests/include/commands.cfg b/usr/src/test/zfs-tests/include/commands.cfg index e5d8dbe403..4e5fade06f 100644 --- a/usr/src/test/zfs-tests/include/commands.cfg +++ b/usr/src/test/zfs-tests/include/commands.cfg @@ -95,6 +95,7 @@ export USR_BIN_FILES='awk runat sed seq + sha256sum shuf sleep sort diff --git a/usr/src/test/zfs-tests/runfiles/delphix.run b/usr/src/test/zfs-tests/runfiles/delphix.run index c22141d4c6..c81678d933 100644 --- a/usr/src/test/zfs-tests/runfiles/delphix.run +++ b/usr/src/test/zfs-tests/runfiles/delphix.run @@ -180,9 +180,10 @@ tests = ['zfs_receive_001_pos', 'zfs_receive_002_pos', 'zfs_receive_003_pos', 'zfs_receive_005_neg', 'zfs_receive_006_pos', 'zfs_receive_007_neg', 'zfs_receive_008_pos', 'zfs_receive_009_neg', 'zfs_receive_010_pos', 'zfs_receive_011_pos', 'zfs_receive_012_pos', - 'zfs_receive_013_pos', 'zfs_receive_014_pos', - 'zfs_receive_from_encrypted', 'zfs_receive_raw', - 'zfs_receive_raw_incremental', 'zfs_receive_to_encrypted'] + 'zfs_receive_013_pos', 'zfs_receive_014_pos', 'zfs_receive_015_pos', + 'receive-o-x_props_override', 'zfs_receive_from_encrypted', + 'zfs_receive_to_encrypted' 'zfs_receive_raw', + 'zfs_receive_raw_incremental'] [/opt/zfs-tests/tests/functional/cli_root/zfs_remap] tests = ['zfs_remap_cliargs', 'zfs_remap_obsolete_counts'] @@ -205,7 +206,8 @@ tests = ['zfs_rollback_001_pos', 'zfs_rollback_002_pos', [/opt/zfs-tests/tests/functional/cli_root/zfs_send] tests = ['zfs_send_001_pos', 'zfs_send_002_pos', 'zfs_send_003_pos', 'zfs_send_004_neg', 'zfs_send_005_pos', 'zfs_send_006_pos', - 'zfs_send_007_pos', 'zfs_send_encrypted', 'zfs_send_raw'] + 'zfs_send_007_pos', 'zfs_send_encrypted', 'zfs_send_raw', + 'zfs_send-b'] [/opt/zfs-tests/tests/functional/cli_root/zfs_set] tests = ['cache_001_pos', 'cache_002_neg', 'canmount_001_pos', @@ -605,8 +607,9 @@ tests = ['rsend_001_pos', 'rsend_002_pos', 'rsend_003_pos', 'rsend_004_pos', 'send-c_mixed_compression', 'send-c_stream_size_estimate', 'send-cD', 'send-c_embedded_blocks', 'send-c_resume', 'send-cpL_varied_recsize', 'send-c_recv_dedup', 'send_encrypted_files', 'send_encrypted_hierarchy', - 'send_encrypted_truncated_files', - 'send_realloc_encrypted_files', + 'send_encrypted_props', 'send_encrypted_truncated_files', + 'send_realloc_files', + 'send_realloc_encrypted_files', 'send_holds', 'send_mixed_raw', 'send-wDR_encrypted_zvol'] [/opt/zfs-tests/tests/functional/scrub_mirror] diff --git a/usr/src/test/zfs-tests/runfiles/omnios.run b/usr/src/test/zfs-tests/runfiles/omnios.run index 01d40f3628..2df5cd612a 100644 --- a/usr/src/test/zfs-tests/runfiles/omnios.run +++ b/usr/src/test/zfs-tests/runfiles/omnios.run @@ -181,9 +181,10 @@ tests = ['zfs_receive_001_pos', 'zfs_receive_002_pos', 'zfs_receive_003_pos', 'zfs_receive_005_neg', 'zfs_receive_006_pos', 'zfs_receive_007_neg', 'zfs_receive_008_pos', 'zfs_receive_009_neg', 'zfs_receive_010_pos', 'zfs_receive_011_pos', 'zfs_receive_012_pos', - 'zfs_receive_013_pos', 'zfs_receive_014_pos', - 'zfs_receive_from_encrypted', 'zfs_receive_raw', - 'zfs_receive_raw_incremental', 'zfs_receive_to_encrypted'] + 'zfs_receive_013_pos', 'zfs_receive_014_pos', 'zfs_receive_015_pos', + 'receive-o-x_props_override', 'zfs_receive_from_encrypted', + 'zfs_receive_to_encrypted' 'zfs_receive_raw', + 'zfs_receive_raw_incremental'] [/opt/zfs-tests/tests/functional/cli_root/zfs_remap] tests = ['zfs_remap_cliargs', 'zfs_remap_obsolete_counts'] @@ -206,7 +207,8 @@ tests = ['zfs_rollback_001_pos', 'zfs_rollback_002_pos', [/opt/zfs-tests/tests/functional/cli_root/zfs_send] tests = ['zfs_send_001_pos', 'zfs_send_002_pos', 'zfs_send_003_pos', 'zfs_send_004_neg', 'zfs_send_005_pos', 'zfs_send_006_pos', - 'zfs_send_007_pos', 'zfs_send_encrypted', 'zfs_send_raw'] + 'zfs_send_007_pos', 'zfs_send_encrypted', 'zfs_send_raw', + 'zfs_send-b'] [/opt/zfs-tests/tests/functional/cli_root/zfs_set] tests = ['cache_001_pos', 'cache_002_neg', 'canmount_001_pos', @@ -605,8 +607,9 @@ tests = ['rsend_001_pos', 'rsend_002_pos', 'rsend_003_pos', 'rsend_004_pos', 'send-c_mixed_compression', 'send-c_stream_size_estimate', 'send-cD', 'send-c_embedded_blocks', 'send-c_resume', 'send-cpL_varied_recsize', 'send-c_recv_dedup', 'send_encrypted_files', 'send_encrypted_hierarchy', - 'send_encrypted_truncated_files', - 'send_realloc_encrypted_files', + 'send_encrypted_props', 'send_encrypted_truncated_files', + 'send_realloc_files', + 'send_realloc_encrypted_files', 'send_holds', 'send_mixed_raw', 'send-wDR_encrypted_zvol'] [/opt/zfs-tests/tests/functional/scrub_mirror] diff --git a/usr/src/test/zfs-tests/runfiles/openindiana.run b/usr/src/test/zfs-tests/runfiles/openindiana.run index f84ca9fbc4..ce1a96c5e0 100644 --- a/usr/src/test/zfs-tests/runfiles/openindiana.run +++ b/usr/src/test/zfs-tests/runfiles/openindiana.run @@ -181,9 +181,10 @@ tests = ['zfs_receive_001_pos', 'zfs_receive_002_pos', 'zfs_receive_003_pos', 'zfs_receive_005_neg', 'zfs_receive_006_pos', 'zfs_receive_007_neg', 'zfs_receive_008_pos', 'zfs_receive_009_neg', 'zfs_receive_010_pos', 'zfs_receive_011_pos', 'zfs_receive_012_pos', - 'zfs_receive_013_pos', 'zfs_receive_014_pos', - 'zfs_receive_from_encrypted', 'zfs_receive_raw', - 'zfs_receive_raw_incremental', 'zfs_receive_to_encrypted'] + 'zfs_receive_013_pos', 'zfs_receive_014_pos', 'zfs_receive_015_pos', + 'receive-o-x_props_override', 'zfs_receive_from_encrypted', + 'zfs_receive_to_encrypted' 'zfs_receive_raw', + 'zfs_receive_raw_incremental'] [/opt/zfs-tests/tests/functional/cli_root/zfs_remap] tests = ['zfs_remap_cliargs', 'zfs_remap_obsolete_counts'] @@ -206,7 +207,8 @@ tests = ['zfs_rollback_001_pos', 'zfs_rollback_002_pos', [/opt/zfs-tests/tests/functional/cli_root/zfs_send] tests = ['zfs_send_001_pos', 'zfs_send_002_pos', 'zfs_send_003_pos', 'zfs_send_004_neg', 'zfs_send_005_pos', 'zfs_send_006_pos', - 'zfs_send_007_pos', 'zfs_send_encrypted', 'zfs_send_raw'] + 'zfs_send_007_pos', 'zfs_send_encrypted', 'zfs_send_raw', + 'zfs_send-b'] [/opt/zfs-tests/tests/functional/cli_root/zfs_set] tests = ['cache_001_pos', 'cache_002_neg', 'canmount_001_pos', @@ -605,8 +607,9 @@ tests = ['rsend_001_pos', 'rsend_002_pos', 'rsend_003_pos', 'rsend_004_pos', 'send-c_mixed_compression', 'send-c_stream_size_estimate', 'send-cD', 'send-c_embedded_blocks', 'send-c_resume', 'send-cpL_varied_recsize', 'send-c_recv_dedup', 'send_encrypted_files', 'send_encrypted_hierarchy', - 'send_encrypted_truncated_files', - 'send_realloc_encrypted_files', + 'send_encrypted_props', 'send_encrypted_truncated_files', + 'send_realloc_files', + 'send_realloc_encrypted_files', 'send_holds', 'send_mixed_raw', 'send-wDR_encrypted_zvol'] [/opt/zfs-tests/tests/functional/scrub_mirror] diff --git a/usr/src/test/zfs-tests/runfiles/smartos.run b/usr/src/test/zfs-tests/runfiles/smartos.run index ec42157057..45af16b2ad 100644 --- a/usr/src/test/zfs-tests/runfiles/smartos.run +++ b/usr/src/test/zfs-tests/runfiles/smartos.run @@ -140,9 +140,10 @@ tests = ['zfs_receive_001_pos', 'zfs_receive_002_pos', 'zfs_receive_003_pos', 'zfs_receive_005_neg', 'zfs_receive_006_pos', 'zfs_receive_007_neg', 'zfs_receive_008_pos', 'zfs_receive_009_neg', 'zfs_receive_010_pos', 'zfs_receive_011_pos', 'zfs_receive_012_pos', - 'zfs_receive_013_pos', 'zfs_receive_014_pos', - 'zfs_receive_from_encrypted', 'zfs_receive_raw', - 'zfs_receive_raw_incremental', 'zfs_receive_to_encrypted'] + 'zfs_receive_013_pos', 'zfs_receive_014_pos', 'zfs_receive_015_pos', + 'receive-o-x_props_override', 'zfs_receive_from_encrypted', + 'zfs_receive_to_encrypted' 'zfs_receive_raw', + 'zfs_receive_raw_incremental'] [/opt/zfs-tests/tests/functional/cli_root/zfs_remap] tests = ['zfs_remap_cliargs', 'zfs_remap_obsolete_counts'] @@ -165,7 +166,8 @@ tests = ['zfs_rollback_001_pos', 'zfs_rollback_002_pos', [/opt/zfs-tests/tests/functional/cli_root/zfs_send] tests = ['zfs_send_001_pos', 'zfs_send_002_pos', 'zfs_send_003_pos', 'zfs_send_004_neg', 'zfs_send_005_pos', 'zfs_send_006_pos', - 'zfs_send_007_pos', 'zfs_send_encrypted', 'zfs_send_raw'] + 'zfs_send_007_pos', 'zfs_send_encrypted', 'zfs_send_raw', + 'zfs_send-b'] [/opt/zfs-tests/tests/functional/cli_root/zfs_set] tests = ['cache_001_pos', 'cache_002_neg', 'canmount_001_pos', @@ -514,8 +516,9 @@ tests = ['rsend_001_pos', 'rsend_002_pos', 'rsend_003_pos', 'rsend_004_pos', 'send-c_incremental', 'send-cD', 'send-c_resume', 'send-c_recv_dedup', 'send_encrypted_files', 'send_encrypted_hierarchy', - 'send_encrypted_truncated_files', - 'send_realloc_encrypted_files', + 'send_encrypted_props', 'send_encrypted_truncated_files', + 'send_realloc_files', + 'send_realloc_encrypted_files', 'send_holds', 'send_mixed_raw', 'send-wDR_encrypted_zvol'] [/opt/zfs-tests/tests/functional/scrub_mirror] diff --git a/usr/src/test/zfs-tests/tests/functional/cli_root/zfs_receive/receive-o-x_props_override.ksh b/usr/src/test/zfs-tests/tests/functional/cli_root/zfs_receive/receive-o-x_props_override.ksh new file mode 100755 index 0000000000..7b369e1a38 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/cli_root/zfs_receive/receive-o-x_props_override.ksh @@ -0,0 +1,318 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# +# CDDL HEADER END +# + +# +# Copyright 2017, loli10K <ezomori.nozomu@gmail.com>. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/cli_root/zfs_set/zfs_set_common.kshlib + +# +# DESCRIPTION: +# Verify ZFS property override (-o) and exclude (-x) options work when +# receiving a send stream +# +# STRATEGY: +# 1. Create a filesystem with children. +# 2. Snapshot the filesystems. +# 3. Create various send streams (full, incremental, replication) and verify +# we can both override and exclude native and user properties. +# + +verify_runnable "both" + +function cleanup +{ + log_must rm -f $streamfile_full + log_must rm -f $streamfile_incr + log_must rm -f $streamfile_repl + log_must rm -f $streamfile_trun + destroy_dataset "$orig" "-rf" + destroy_dataset "$dest" "-rf" +} + +log_assert "ZFS receive property override and exclude options work as expected." +log_onexit cleanup + +orig=$TESTPOOL/$TESTFS1 +origsub=$orig/sub +dest=$TESTPOOL/$TESTFS2 +destsub=$dest/sub +typeset userprop=$(valid_user_property 8) +typeset userval=$(user_property_value 8) +typeset streamfile_full=$TESTDIR/streamfile_full.$$ +typeset streamfile_incr=$TESTDIR/streamfile_incr.$$ +typeset streamfile_repl=$TESTDIR/streamfile_repl.$$ +typeset streamfile_trun=$TESTDIR/streamfile_trun.$$ + +# +# 3.1 Verify we can't specify the same property in multiple -o or -x options +# or an invalid value was specified. +# +# Create a full send stream +log_must zfs create $orig +log_must zfs snapshot $orig@snap1 +log_must eval "zfs send $orig@snap1 > $streamfile_full" +# Verify we reject invalid options +log_mustnot eval "zfs recv $dest -o atime < $streamfile_full" +log_mustnot eval "zfs recv $dest -x atime=off < $streamfile_full" +log_mustnot eval "zfs recv $dest -o atime=off -x atime < $streamfile_full" +log_mustnot eval "zfs recv $dest -o atime=off -o atime=on < $streamfile_full" +log_mustnot eval "zfs recv $dest -x atime -x atime < $streamfile_full" +log_mustnot eval "zfs recv $dest -o version=1 < $streamfile_full" +log_mustnot eval "zfs recv $dest -x version < $streamfile_full" +log_mustnot eval "zfs recv $dest -x normalization < $streamfile_full" +# Verify we also reject invalid ZVOL options +log_must zfs create -V 32K -s $orig/zvol +log_must eval "zfs send $orig@snap1 > $streamfile_full" +log_mustnot eval "zfs recv $dest -x volsize < $streamfile_full" +log_mustnot eval "zfs recv $dest -o volsize=32K < $streamfile_full" +# Cleanup +block_device_wait +log_must_busy zfs destroy -r -f $orig + +# +# 3.2 Verify -o property=value works on streams without properties. +# +# Create a full send stream +log_must zfs create $orig +log_must zfs snapshot $orig@snap1 +log_must eval "zfs send $orig@snap1 > $streamfile_full" +# Receive the full stream, override some properties +log_must eval "zfs recv -o compression=on -o '$userprop:dest'='$userval' "\ + "$dest < $streamfile_full" +log_must eval "check_prop_source $dest compression on local" +log_must eval "check_prop_source $dest '$userprop:dest' '$userval' local" +# Cleanup +log_must zfs destroy -r -f $orig +log_must zfs destroy -r -f $dest + +# +# 3.3 Verify -o property=value and -x work on both native and user properties +# for an incremental replication send stream. +# +# Create a dataset tree and receive it +log_must zfs create $orig +log_must zfs create $origsub +log_must zfs snapshot -r $orig@snap1 +log_must eval "zfs send -R $orig@snap1 > $streamfile_repl" +log_must eval "zfs recv $dest < $streamfile_repl" +# Fill the datasets with properties and create an incremental replication stream +log_must zfs snapshot -r $orig@snap2 +log_must zfs snapshot -r $orig@snap3 +log_must eval "zfs set copies=2 $orig" +log_must eval "zfs set '$userprop:orig'='$userval' $orig" +log_must eval "zfs set '$userprop:orig'='$userval' $origsub" +log_must eval "zfs set '$userprop:snap'='$userval' $orig@snap1" +log_must eval "zfs set '$userprop:snap'='$userval' $origsub@snap3" +log_must eval "zfs send -R -I $orig@snap1 $orig@snap3 > $streamfile_incr" +# Sets various combination of override and exclude options +log_must eval "zfs recv -F -o atime=off -o '$userprop:dest2'='$userval' "\ + "-o quota=123456789 -x compression "\ + "-x '$userprop:orig' -x '$userprop:snap3' $dest < $streamfile_incr" +# Verify we can correctly override and exclude properties +log_must eval "check_prop_source $dest copies 2 received" +log_must eval "check_prop_source $dest atime off local" +log_must eval "check_prop_source $dest '$userprop:dest2' '$userval' local" +log_must eval "check_prop_source $dest quota 123456789 local" +log_must eval "check_prop_inherit $destsub copies $dest" +log_must eval "check_prop_inherit $destsub atime $dest" +log_must eval "check_prop_inherit $destsub '$userprop:dest2' $dest" +log_must eval "check_prop_source $destsub quota 0 default" +log_must eval "check_prop_source $destsub compression off default" +log_must eval "check_prop_missing $dest '$userprop:orig'" +log_must eval "check_prop_missing $destsub '$userprop:orig'" +log_must eval "check_prop_source " \ + "$dest@snap1 '$userprop:snap' '$userval' received" +log_must eval "check_prop_source " \ + "$destsub@snap3 '$userprop:snap' '$userval' received" +log_must eval "check_prop_missing $dest@snap3 '$userprop:snap3'" +log_must eval "check_prop_missing $destsub@snap3 '$userprop:snap3'" +# Cleanup +log_must zfs destroy -r -f $orig +log_must zfs destroy -r -f $dest + +# +# 3.4 Verify '-x property' does not remove existing local properties and a +# modified sent property is received and updated to the new value but can +# still be excluded. +# +# Create a dataset tree +log_must zfs create $orig +log_must zfs create $origsub +log_must zfs snapshot -r $orig@snap1 +log_must eval "zfs set copies=2 $orig" +log_must eval "zfs set '$userprop:orig'='oldval' $orig" +log_must eval "zfs set '$userprop:orig'='oldsubval' $origsub" +log_must eval "zfs send -R $orig@snap1 > $streamfile_repl" +log_must eval "zfs receive $dest < $streamfile_repl" +log_must eval "check_prop_source $dest copies 2 received" +log_must eval "check_prop_inherit $destsub copies $dest" +log_must eval "check_prop_source $dest '$userprop:orig' 'oldval' received" +log_must eval "check_prop_source $destsub '$userprop:orig' 'oldsubval' received" +# Set new custom properties on both source and destination +log_must eval "zfs set copies=3 $orig" +log_must eval "zfs set '$userprop:orig'='newval' $orig" +log_must eval "zfs set '$userprop:orig'='newsubval' $origsub" +log_must eval "zfs set compression=gzip $dest" +log_must eval "zfs set '$userprop:dest'='localval' $dest" +# Receive the new stream, verify we preserve locally set properties +log_must zfs snapshot -r $orig@snap2 +log_must zfs snapshot -r $orig@snap3 +log_must eval "zfs send -R -I $orig@snap1 $orig@snap3 > $streamfile_incr" +log_must eval "zfs recv -F -x copies -x compression -x '$userprop:orig' " \ + "-x '$userprop:dest' $dest < $streamfile_incr" +log_must eval "check_prop_source $dest '$userprop:dest' 'localval' local" +log_must eval "check_prop_received $dest '$userprop:orig' 'newval'" +log_must eval "check_prop_received $destsub '$userprop:orig' 'newsubval'" +log_must eval "check_prop_missing $dest '$userprop:orig'" +log_must eval "check_prop_missing $destsub '$userprop:orig'" +log_must eval "check_prop_source $dest copies 1 default" +log_must eval "check_prop_received $dest copies 3" +log_must eval "check_prop_source $destsub copies 1 default" +log_must eval "check_prop_received $destsub copies '-'" +log_must eval "check_prop_source $dest compression gzip local" +log_must eval "check_prop_inherit $destsub compression $dest" +# Cleanup +log_must zfs destroy -r -f $orig +log_must zfs destroy -r -f $dest + +# +# 3.5 Verify we can exclude non-inheritable properties from a send stream +# +# Create a dataset tree and replication stream +log_must zfs create $orig +log_must zfs create $origsub +log_must zfs snapshot -r $orig@snap1 +log_must eval "zfs set quota=123456789 $orig" +log_must eval "zfs send -R $orig@snap1 > $streamfile_repl" +# Receive the stream excluding non-inheritable properties +log_must eval "zfs recv -F -x quota $dest < $streamfile_repl" +log_must eval "check_prop_source $dest quota 0 default" +log_must eval "check_prop_source $destsub quota 0 default" +# Set some non-inheritable properties on the destination, verify we keep them +log_must eval "zfs set quota=123456789 $dest" +log_must eval "zfs set canmount=off $destsub" +log_must zfs snapshot -r $orig@snap2 +log_must zfs snapshot -r $orig@snap3 +log_must eval "zfs send -R -I $orig@snap1 $orig@snap3 > $streamfile_incr" +log_must eval "zfs recv -F -x quota -x canmount $dest < $streamfile_incr" +log_must eval "check_prop_source $dest quota 123456789 local" +log_must eval "check_prop_source $destsub quota 0 default" +log_must eval "check_prop_source $destsub canmount off local" +# Cleanup +log_must zfs destroy -r -f $orig +log_must zfs destroy -r -f $dest + +# +# 3.6 Verify we correctly restore existing properties on a failed receive +# +# Receive a "clean" dataset tree +log_must zfs create $orig +log_must zfs create $origsub +log_must zfs snapshot -r $orig@snap1 +log_must eval "zfs send -R $orig@snap1 > $streamfile_repl" +log_must eval "zfs receive $dest < $streamfile_repl" +# Set custom properties on the destination +log_must eval "zfs set atime=off $dest" +log_must eval "zfs set quota=123456789 $dest" +log_must eval "zfs set '$userprop:orig'='$userval' $dest" +log_must eval "zfs set '$userprop:origsub'='$userval' $destsub" +# Create a truncated incremental replication stream +mntpnt=$(get_prop mountpoint $orig) +log_must eval "dd if=/dev/urandom of=$mntpnt/file bs=1024k count=10" +log_must zfs snapshot -r $orig@snap2 +log_must zfs snapshot -r $orig@snap3 +log_must eval "zfs send -R -I $orig@snap1 $orig@snap3 > $streamfile_incr" +log_must eval "dd if=$streamfile_incr of=$streamfile_trun bs=1024k count=9" +# Receive the truncated stream, verify original properties are kept +log_mustnot eval "zfs recv -F -o copies=3 -o quota=987654321 "\ + "-o '$userprop:new'='badval' $dest < $streamfile_trun" +log_must eval "check_prop_source $dest copies 1 default" +log_must eval "check_prop_source $destsub copies 1 default" +log_must eval "check_prop_source $dest atime off local" +log_must eval "check_prop_inherit $destsub atime $dest" +log_must eval "check_prop_source $dest quota 123456789 local" +log_must eval "check_prop_source $destsub quota 0 default" +log_must eval "check_prop_source $dest '$userprop:orig' '$userval' local" +log_must eval "check_prop_inherit $destsub '$userprop:orig' $dest" +log_must eval "check_prop_source $destsub '$userprop:origsub' '$userval' local" +log_must eval "check_prop_missing $dest '$userprop:new'" +# Cleanup +log_must zfs destroy -r -f $orig +log_must zfs destroy -r -f $dest + +# +# 3.7 Verify we can't receive a send stream overriding or excluding properties +# invalid for the dataset type unless the stream it's recursive, in which +# case only the appropriate properties are set on the destination. +# +log_must zfs create -V 128K -s $orig +log_must zfs snapshot $orig@snap1 +log_must eval "zfs send $orig@snap1 > $streamfile_full" +log_mustnot eval "zfs receive -x atime $dest < $streamfile_full" +log_mustnot eval "zfs receive -o atime=off $dest < $streamfile_full" +log_must_busy zfs destroy -r -f $orig +log_must zfs create $orig +log_must zfs create -V 128K -s $origsub +log_must zfs snapshot -r $orig@snap1 +log_must eval "zfs send -R $orig@snap1 > $streamfile_repl" +log_must eval "zfs receive -o atime=off $dest < $streamfile_repl" +log_must eval "check_prop_source $dest type filesystem -" +log_must eval "check_prop_source $dest atime off local" +log_must eval "check_prop_source $destsub type volume -" +log_must eval "check_prop_source $destsub atime - -" +# Cleanup +block_device_wait +log_must_busy zfs destroy -r -f $orig +log_must_busy zfs destroy -r -f $dest + +# +# 3.8 Verify 'zfs recv -x|-o' works correctly when used in conjunction with -d +# and -e options. +# +log_must zfs create -p $orig/1/2/3/4 +log_must eval "zfs set copies=2 $orig" +log_must eval "zfs set atime=on $orig" +log_must eval "zfs set '$userprop:orig'='oldval' $orig" +log_must zfs snapshot -r $orig@snap1 +log_must eval "zfs send -R $orig/1/2@snap1 > $streamfile_repl" +# Verify 'zfs recv -e' +log_must zfs create $dest +log_must eval "zfs receive -e -o copies=3 -x atime "\ + "-o '$userprop:orig'='newval' $dest < $streamfile_repl" +log_must datasetexists $dest/2/3/4 +log_must eval "check_prop_source $dest/2 copies 3 local" +log_must eval "check_prop_inherit $dest/2/3/4 copies $dest/2" +log_must eval "check_prop_source $dest/2/3/4 atime on default" +log_must eval "check_prop_source $dest/2 '$userprop:orig' 'newval' local" +log_must eval "check_prop_inherit $dest/2/3/4 '$userprop:orig' $dest/2" +log_must zfs destroy -r -f $dest +# Verify 'zfs recv -d' +log_must zfs create $dest +typeset fs="$(echo $orig | awk -F'/' '{print $NF}')" +log_must eval "zfs receive -d -o copies=3 -x atime "\ + "-o '$userprop:orig'='newval' $dest < $streamfile_repl" +log_must datasetexists $dest/$fs/1/2/3/4 +log_must eval "check_prop_source $dest/$fs/1/2 copies 3 local" +log_must eval "check_prop_inherit $dest/$fs/1/2/3/4 copies $dest/$fs/1/2" +log_must eval "check_prop_source $dest/$fs/1/2/3/4 atime on default" +log_must eval "check_prop_source $dest/$fs/1/2 '$userprop:orig' 'newval' local" +log_must eval "check_prop_inherit $dest/$fs/1/2/3/4 '$userprop:orig' $dest/$fs/1/2" +# We don't need to cleanup here + +log_pass "ZFS receive property override and exclude options passed." diff --git a/usr/src/test/zfs-tests/tests/functional/cli_root/zfs_receive/zfs_receive_015_pos.ksh b/usr/src/test/zfs-tests/tests/functional/cli_root/zfs_receive/zfs_receive_015_pos.ksh new file mode 100755 index 0000000000..ead3c43117 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/cli_root/zfs_receive/zfs_receive_015_pos.ksh @@ -0,0 +1,83 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# +# CDDL HEADER END +# + +# +# Copyright 2016, loli10K. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/cli_root/zfs_set/zfs_set_common.kshlib + +# +# DESCRIPTION: +# Verify ZFS can receive custom properties on both filesystems and +# snapshots from full and incremental streams. +# +# STRATEGY: +# 1. Create a filesystem. +# 2. Snapshot the filesystem. +# 3. Set custom properties on both the fs and snapshots. +# 4. Create different send streams with the properties. +# 5. Receive the send streams and verify the properties. +# + +verify_runnable "both" + +typeset streamfile_full=$TEST_BASE_DIR/streamfile_full.$$ +typeset streamfile_incr=$TEST_BASE_DIR/streamfile_incr.$$ +orig=$TESTPOOL/$TESTFS1 +dest=$TESTPOOL/$TESTFS2 +typeset user_prop=$(valid_user_property 8) +typeset value=$(user_property_value 8) + +function cleanup +{ + log_must rm $streamfile_full + log_must rm $streamfile_incr + log_must zfs destroy -rf $TESTPOOL/$TESTFS1 + log_must zfs destroy -rf $TESTPOOL/$TESTFS2 +} + +log_assert "ZFS can receive custom properties." +log_onexit cleanup + +# 1. Create a filesystem. +log_must zfs create $orig + +# 2. Snapshot the filesystem. +log_must zfs snapshot $orig@snap1 +log_must zfs snapshot $orig@snap2 +log_must zfs snapshot $orig@snap3 + +# 3. Set custom properties on both the fs and snapshots. +log_must eval "zfs set '$user_prop'='$value' $orig" +log_must eval "zfs set '$user_prop:snap1'='$value:snap1' $orig@snap1" +log_must eval "zfs set '$user_prop:snap2'='$value:snap2' $orig@snap2" +log_must eval "zfs set '$user_prop:snap3'='$value:snap3' $orig@snap3" + +# 4. Create different send streams with the properties. +log_must eval "zfs send -p $orig@snap1 > $streamfile_full" +log_must eval "zfs send -p -I $orig@snap1 $orig@snap3 > $streamfile_incr" + +# 5. Receive the send streams and verify the properties. +log_must eval "zfs recv $dest < $streamfile_full" +log_must eval "check_user_prop $dest $user_prop '$value'" +log_must eval "check_user_prop $dest@snap1 '$user_prop:snap1' '$value:snap1'" +log_must eval "zfs recv $dest < $streamfile_incr" +log_must eval "check_user_prop $dest@snap2 '$user_prop:snap2' '$value:snap2'" +log_must eval "check_user_prop $dest@snap3 '$user_prop:snap3' '$value:snap3'" + +log_pass "ZFS can receive custom properties passed." diff --git a/usr/src/test/zfs-tests/tests/functional/cli_root/zfs_receive/zfs_receive_to_encrypted.ksh b/usr/src/test/zfs-tests/tests/functional/cli_root/zfs_receive/zfs_receive_to_encrypted.ksh index 3ce5ddc36c..57896c6fd3 100644 --- a/usr/src/test/zfs-tests/tests/functional/cli_root/zfs_receive/zfs_receive_to_encrypted.ksh +++ b/usr/src/test/zfs-tests/tests/functional/cli_root/zfs_receive/zfs_receive_to_encrypted.ksh @@ -46,7 +46,7 @@ function cleanup log_onexit cleanup -log_assert "ZFS should receive encrypted filesystems into child dataset" +log_assert "ZFS should receive to an encrypted child dataset" typeset passphrase="password" typeset snap="$TESTPOOL/$TESTFS@snap" @@ -57,21 +57,14 @@ log_must zfs snapshot $snap log_must eval "echo $passphrase | zfs create -o encryption=on" \ "-o keyformat=passphrase $TESTPOOL/$TESTFS1" -# XXX - on illumos we're currently conflicting on mount prop -log_must zfs set mountpoint=none $TESTPOOL/$TESTFS - log_note "Verifying ZFS will receive to an encrypted child" log_must eval "zfs send $snap | zfs receive $TESTPOOL/$TESTFS1/c1" -log_note "Verifying 'send -p' will receive to an encrypted child" -log_must eval "zfs send -p $snap | zfs receive $TESTPOOL/$TESTFS1/c2" -# XXX - on illumos, not sending encryption prop yet -# log_must test "$(get_prop 'encryption' $TESTPOOL/$TESTFS1/c2)" == "off" +log_note "Verifying 'send -p' will not receive to an encrypted child" +log_mustnot eval "zfs send -p $snap | zfs receive $TESTPOOL/$TESTFS1/c2" -log_note "Verifying 'send -R' will receive to an encrypted child" -log_must eval "zfs send -R $snap | zfs receive $TESTPOOL/$TESTFS1/c3" -# XXX - on illumos, not sending encryption prop yet -# log_must test "$(get_prop 'encryption' $TESTPOOL/$TESTFS1/c3)" == "off" +log_note "Verifying 'send -R' will not receive to an encrypted child" +log_mustnot eval "zfs send -R $snap | zfs receive $TESTPOOL/$TESTFS1/c3" log_note "Verifying ZFS will not receive to an encrypted child when the" \ "parent key is unloaded" @@ -79,4 +72,4 @@ log_must zfs unmount $TESTPOOL/$TESTFS1 log_must zfs unload-key $TESTPOOL/$TESTFS1 log_mustnot eval "zfs send $snap | zfs receive $TESTPOOL/$TESTFS1/c4" -log_pass "ZFS can receive encrypted filesystems into child dataset" +log_pass "ZFS can receive to an encrypted child dataset" diff --git a/usr/src/test/zfs-tests/tests/functional/cli_root/zfs_send/zfs_send-b.ksh b/usr/src/test/zfs-tests/tests/functional/cli_root/zfs_send/zfs_send-b.ksh new file mode 100755 index 0000000000..cd879846ce --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/cli_root/zfs_send/zfs_send-b.ksh @@ -0,0 +1,103 @@ +#!/bin/ksh -p +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright 2018, loli10K <ezomori.nozomu@gmail.com>. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/cli_root/zfs_set/zfs_set_common.kshlib + +# +# DESCRIPTION: +# 'zfs send -b' should works as expected. +# +# STRATEGY: +# 1. Create a source dataset and set some properties +# 2. Verify command line options interact with '-b' correctly +# 3. Send the dataset and its properties to a new "backup" destination +# 4. Set some properties on the new "backup" dataset +# 5. Restore the "backup" dataset to a new destination +# 6. Verify only original (received) properties are sent from "backup" +# + +verify_runnable "both" + +function cleanup +{ + for ds in "$SENDFS" "$BACKUP" "$RESTORE"; do + datasetexists $ds && log_must zfs destroy -r $ds + done +} + +log_assert "'zfs send -b' should work as expected." +log_onexit cleanup + +SENDFS="$TESTPOOL/sendfs" +BACKUP="$TESTPOOL/backup" +RESTORE="$TESTPOOL/restore" + +# 1. Create a source dataset and set some properties +log_must zfs create $SENDFS +log_must zfs snapshot "$SENDFS@s1" +log_must zfs bookmark "$SENDFS@s1" "$SENDFS#bm" +log_must zfs snapshot "$SENDFS@s2" +log_must zfs set "compression=gzip" $SENDFS +log_must zfs set "org.zfsonlinux:prop=val" $SENDFS +log_must zfs set "org.zfsonlinux:snapprop=val" "$SENDFS@s1" + +# 2. Verify command line options interact with '-b' correctly +typeset opts=("" "p" "Rp" "cew" "nv" "D" "DLPRcenpvw") +for opt in ${opts[@]}; do + log_must eval "zfs send -b$opt $SENDFS@s1 > /dev/null" + log_must eval "zfs send -b$opt -i $SENDFS@s1 $SENDFS@s2 > /dev/null" + log_must eval "zfs send -b$opt -I $SENDFS@s1 $SENDFS@s2 > /dev/null" +done +for opt in ${opts[@]}; do + log_mustnot eval "zfs send -b$opt $SENDFS > /dev/null" + log_mustnot eval "zfs send -b$opt $SENDFS#bm > /dev/null" + log_mustnot eval "zfs send -b$opt -i $SENDFS#bm $SENDFS@s2 > /dev/null" +done + +# Do 3..6 in a loop to verify various combination of "zfs send" options +typeset opts=("" "p" "R" "pR" "cew") +for opt in ${opts[@]}; do + # 3. Send the dataset and its properties to a new "backup" destination + # NOTE: only need to send properties (-p) here + log_must eval "zfs send -p $SENDFS@s1 | zfs recv $BACKUP" + + # 4. Set some properties on the new "backup" dataset + # NOTE: override "received" values and set some new properties as well + log_must zfs set "compression=lz4" $BACKUP + log_must zfs set "exec=off" $BACKUP + log_must zfs set "org.zfsonlinux:prop=newval" $BACKUP + log_must zfs set "org.zfsonlinux:newprop=newval" $BACKUP + log_must zfs set "org.zfsonlinux:snapprop=newval" "$BACKUP@s1" + log_must zfs set "org.zfsonlinux:newsnapprop=newval" "$BACKUP@s1" + + # 5. Restore the "backup" dataset to a new destination + log_must eval "zfs send -b$opt $BACKUP@s1 | zfs recv $RESTORE" + + # 6. Verify only original (received) properties are sent from "backup" + log_must eval "check_prop_source $RESTORE compression gzip received" + log_must eval "check_prop_source $RESTORE org.zfsonlinux:prop val received" + log_must eval "check_prop_source $RESTORE@s1 org.zfsonlinux:snapprop val received" + log_must eval "check_prop_source $RESTORE exec on default" + log_must eval "check_prop_missing $RESTORE org.zfsonlinux:newprop" + log_must eval "check_prop_missing $RESTORE@s1 org.zfsonlinux:newsnapprop" + + # cleanup + log_must zfs destroy -r $BACKUP + log_must zfs destroy -r $RESTORE +done + +log_pass "'zfs send -b' works as expected." diff --git a/usr/src/test/zfs-tests/tests/functional/cli_root/zfs_set/zfs_set_common.kshlib b/usr/src/test/zfs-tests/tests/functional/cli_root/zfs_set/zfs_set_common.kshlib index a5aafd46cb..b9c3ea0f7e 100644 --- a/usr/src/test/zfs-tests/tests/functional/cli_root/zfs_set/zfs_set_common.kshlib +++ b/usr/src/test/zfs-tests/tests/functional/cli_root/zfs_set/zfs_set_common.kshlib @@ -265,3 +265,110 @@ function get_source echo "$source" } + +# +# Verify property $2 is set from source $4 on dataset $1 and has value $3. +# +# $1 checked dataset +# $2 user property +# $3 property value +# $4 source +# +# Returns: 0 if both expected source and value match, 1 otherwise +# +function check_prop_source +{ + typeset dataset="$1" + typeset prop="$2" + typeset value="$3" + typeset source="$4" + typeset chk_value=$(get_prop "$prop" "$dataset") + typeset chk_source=$(get_source "$prop" "$dataset") + + if [[ "$chk_value" != "$value" || "$chk_source" != "$4" ]] + then + return 1 + else + return 0 + fi +} + +# +# Verify target dataset $1 inherit property $2 from dataset $3. +# +# $1 checked dataset +# $2 property +# $3 inherited dataset +# +# Returns: 0 if property has expected value and is inherited, 1 otherwise +# +function check_prop_inherit +{ + typeset checked_dtst="$1" + typeset prop="$2" + typeset inherited_dtst="$3" + typeset inherited_value=$(get_prop "$prop" "$inherited_dtst") + typeset value=$(get_prop "$prop" "$checked_dtst") + typeset source=$(get_source "$prop" "$checked_dtst") + + if [[ "$value" != "$inherited_value" || \ + "$source" != "inherited from $inherited_dtst" ]] + then + return 1 + else + return 0 + fi +} + +# +# Verify property $2 received value on dataset $1 has value $3 +# +# $1 checked dataset +# $2 property name +# $3 checked value +# +# Returns: 0 if property has expected value and is received, 1 otherwise +# +function check_prop_received +{ + typeset dataset="$1" + typeset prop="$2" + typeset value="$3" + + received=$(zfs get -H -o received "$prop" "$dataset") + if (($? != 0)); then + log_fail "Unable to get $prop received value for dataset " \ + "$dataset" + fi + if [[ "$received" == "$value" ]] + then + return 0 + else + return 1 + fi +} + +# +# Verify user property $2 is not set on dataset $1 +# +# $1 checked dataset +# $2 property name +# +# Returns: 0 if property is missing (not set), 1 otherwise +# +function check_prop_missing +{ + typeset dataset="$1" + typeset prop="$2" + + value=$(zfs get -H -o value "$prop" "$dataset") + if (($? != 0)); then + log_fail "Unable to get $prop value for dataset $dataset" + fi + if [[ "-" == "$value" ]] + then + return 0 + else + return 1 + fi +} diff --git a/usr/src/test/zfs-tests/tests/functional/rsend/rsend.kshlib b/usr/src/test/zfs-tests/tests/functional/rsend/rsend.kshlib index 6b22cb709e..5adf2bca42 100644 --- a/usr/src/test/zfs-tests/tests/functional/rsend/rsend.kshlib +++ b/usr/src/test/zfs-tests/tests/functional/rsend/rsend.kshlib @@ -30,6 +30,7 @@ . $STF_SUITE/include/libtest.shlib . $STF_SUITE/include/math.shlib +. $STF_SUITE/tests/functional/cli_root/zfs_set/zfs_set_common.kshlib . $STF_SUITE/tests/functional/rsend/rsend.cfg # @@ -488,9 +489,13 @@ function churn_files value=$((RANDOM % 5)) if [ $value -eq 0 -a $xattrs -ne 0 ]; then attrname="testattr$((RANDOM % 3))" + attrlen="$(((RANDOM % 1000) + 1))" + attrvalue="$(random_string VALID_NAME_CHAR \ + $attrlen)" attr -qr $attrname $file_name || \ log_fail "Failed to remove $attrname" - attr -qs $attrname -V TestValue $file_name || \ + attr -qs $attrname \ + -V "$attrvalue" $file_name || \ log_fail "Failed to set $attrname" elif [ $value -eq 1 ]; then dd if=/dev/urandom of=$file_name \ @@ -518,9 +523,12 @@ function churn_files if [ $xattrs -ne 0 ]; then for j in {0..2}; do attrname="testattr$j" - attr -qs $attrname -V TestValue \ - $file_name || log_fail \ - "Failed to set $attrname" + attrlen="$(((RANDOM % 1000) + 1))" + attrvalue="$(random_string \ + VALID_NAME_CHAR $attrlen)" + attr -qs $attrname \ + -V "$attrvalue" $file_name || \ + log_fail "Failed to set $attrname" done fi fi @@ -769,3 +777,22 @@ function resume_cleanup cleanup_pool $POOL2 rm -f /$sendpool/initial.zsend /$sendpool/incremental.zsend } + +# Randomly set the property to one of the enumerated values. +function rand_set_prop +{ + typeset dtst=$1 + typeset prop=$2 + shift 2 + typeset value=$(random_get $@) + + log_must eval "zfs set $prop='$value' $dtst" +} + +# Generate a recursive checksum of a filesystems contents. Only file +# data is included in the checksum (no meta data, or xattrs). +function recursive_cksum +{ + find $1 -type f -exec sha256sum {} \; | \ + sort -k 2 | awk '{ print $1 }' | sha256sum +} diff --git a/usr/src/test/zfs-tests/tests/functional/rsend/rsend_012_pos.ksh b/usr/src/test/zfs-tests/tests/functional/rsend/rsend_012_pos.ksh index d257ed8e4c..45f885d6ff 100644 --- a/usr/src/test/zfs-tests/tests/functional/rsend/rsend_012_pos.ksh +++ b/usr/src/test/zfs-tests/tests/functional/rsend/rsend_012_pos.ksh @@ -44,16 +44,6 @@ verify_runnable "global" -function rand_set_prop -{ - typeset dtst=$1 - typeset prop=$2 - shift 2 - typeset value=$(random_get $@) - - log_must eval "zfs set $prop='$value' $dtst" -} - function edited_prop { typeset behaviour=$1 diff --git a/usr/src/test/zfs-tests/tests/functional/rsend/send_encrypted_files.ksh b/usr/src/test/zfs-tests/tests/functional/rsend/send_encrypted_files.ksh index c85dd40965..14bb39a86b 100644 --- a/usr/src/test/zfs-tests/tests/functional/rsend/send_encrypted_files.ksh +++ b/usr/src/test/zfs-tests/tests/functional/rsend/send_encrypted_files.ksh @@ -54,12 +54,6 @@ function cleanup } log_onexit cleanup -function recursive_cksum -{ - find $1 -type f -exec sha256sum {} \; | \ - sort -k 2 | awk '{ print $1 }' | sha256sum -} - log_assert "Verify 'zfs send -w' works with many different file layouts" typeset keyfile=/$TESTPOOL/pkey diff --git a/usr/src/test/zfs-tests/tests/functional/rsend/send_encrypted_props.ksh b/usr/src/test/zfs-tests/tests/functional/rsend/send_encrypted_props.ksh new file mode 100755 index 0000000000..a216f1c5ff --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/rsend/send_encrypted_props.ksh @@ -0,0 +1,199 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2018 by Datto Inc. All rights reserved. +# + +. $STF_SUITE/tests/functional/rsend/rsend.kshlib + +# +# DESCRIPTION: +# Verify that zfs properly handles encryption properties when receiving +# send streams. +# +# STRATEGY: +# 1. Create a few unencrypted and encrypted test datasets with some data +# 2. Take snapshots of these datasets in preparation for sending +# 3. Verify that 'zfs recv -o keylocation=prompt' fails +# 4. Verify that 'zfs recv -x <encryption prop>' fails on a raw send stream +# 5. Verify that encryption properties cannot be changed on incrementals +# 6. Verify that a simple send can be received as an encryption root +# 7. Verify that an unencrypted props send can be received as an +# encryption root +# 8. Verify that an unencrypted recursive send can be received as an +# encryption root +# 9. Verify that an unencrypted props send can be received as an +# encryption child +# 10. Verify that an unencrypted recursive send can be received as an +# encryption child +# + +verify_runnable "both" + +function cleanup +{ + destroy_dataset $TESTPOOL/recv "-r" + destroy_dataset $TESTPOOL/crypt "-r" + destroy_dataset $TESTPOOL/ds "-r" + [[ -f $sendfile ]] && log_must rm $sendfile + [[ -f $keyfile ]] && log_must rm $keyfile +} +log_onexit cleanup + +log_assert "'zfs recv' must properly handle encryption properties" + +typeset keyfile=/$TESTPOOL/pkey +typeset sendfile=/$TESTPOOL/sendfile +typeset snap=$TESTPOOL/ds@snap +typeset esnap=$TESTPOOL/crypt@snap1 +typeset esnap2=$TESTPOOL/crypt@snap2 + +log_must eval "echo 'password' > $keyfile" + +log_must zfs create $TESTPOOL/ds +log_must zfs create $TESTPOOL/ds/ds1 + +log_must zfs create -o encryption=on -o keyformat=passphrase \ + -o keylocation=file://$keyfile $TESTPOOL/crypt +log_must zfs create $TESTPOOL/crypt/ds1 +log_must zfs create -o keyformat=passphrase -o keylocation=file://$keyfile \ + $TESTPOOL/crypt/ds2 + +log_must mkfile 1M /$TESTPOOL/ds/$TESTFILE0 +log_must cp /$TESTPOOL/ds/$TESTFILE0 /$TESTPOOL/crypt/$TESTFILE0 +typeset cksum=$(md5sum /$TESTPOOL/ds/$TESTFILE0 | awk '{ print $1 }') + +log_must zfs snap -r $snap +log_must zfs snap -r $esnap +log_must zfs snap -r $esnap2 + +# Embedded data is incompatible with encrypted datasets, so we cannot +# allow embedded streams to be received. +log_note "Must not be able to receive an embedded stream as encrypted" +log_mustnot eval "zfs send -e $TESTPOOL/crypt/ds1 | zfs recv $TESTPOOL/recv" + +# We currently don't have an elegant and secure way to pass the passphrase +# in via prompt because the send stream itself is using stdin. +log_note "Must not be able to use 'keylocation=prompt' on receive" +log_must eval "zfs send $snap > $sendfile" +log_mustnot eval "zfs recv -o encryption=on -o keyformat=passphrase" \ + "$TESTPOOL/recv < $sendfile" +log_mustnot eval "zfs recv -o encryption=on -o keyformat=passphrase" \ + "-o keylocation=prompt $TESTPOOL/recv < $sendfile" + +# Raw sends do not have access to the decrypted data so we cannot override +# the encryption settings without losing the data. +log_note "Must not be able to disable encryption properties on raw send" +log_must eval "zfs send -w $esnap > $sendfile" +log_mustnot eval "zfs recv -x encryption $TESTPOOL/recv < $sendfile" +log_mustnot eval "zfs recv -x keyformat $TESTPOOL/recv < $sendfile" +log_mustnot eval "zfs recv -x pbkdf2iters $TESTPOOL/recv < $sendfile" + +# Encryption properties are set upon creating the dataset. Changing them +# afterwards requires using 'zfs change-key' or an update from a raw send. +log_note "Must not be able to change encryption properties on incrementals" +log_must eval "zfs send $esnap | zfs recv -o encryption=on" \ + "-o keyformat=passphrase -o keylocation=file://$keyfile $TESTPOOL/recv" +log_mustnot eval "zfs send -i $esnap $esnap2 |" \ + "zfs recv -o encryption=aes-128-ccm $TESTPOOL/recv" +log_mustnot eval "zfs send -i $esnap $esnap2 |" \ + "zfs recv -o keyformat=hex $TESTPOOL/recv" +log_mustnot eval "zfs send -i $esnap $esnap2 |" \ + "zfs recv -o pbkdf2iters=100k $TESTPOOL/recv" +log_must zfs destroy -r $TESTPOOL/recv + +# Test that we can receive a simple stream as an encryption root. +log_note "Must be able to receive stream as encryption root" +ds=$TESTPOOL/recv +log_must eval "zfs send $snap > $sendfile" +log_must eval "zfs recv -o encryption=on -o keyformat=passphrase" \ + "-o keylocation=file://$keyfile $ds < $sendfile" +log_must test "$(get_prop 'encryption' $ds)" == "aes-256-ccm" +log_must test "$(get_prop 'encryptionroot' $ds)" == "$ds" +log_must test "$(get_prop 'keyformat' $ds)" == "passphrase" +log_must test "$(get_prop 'keylocation' $ds)" == "file://$keyfile" +log_must test "$(get_prop 'mounted' $ds)" == "yes" +recv_cksum=$(md5sum /$ds/$TESTFILE0 | awk '{ print $1 }') +log_must test "$recv_cksum" == "$cksum" +log_must zfs destroy -r $ds + +# Test that we can override encryption properties on a properties stream +# of an unencrypted dataset, turning it into an encryption root. +log_note "Must be able to receive stream with props as encryption root" +ds=$TESTPOOL/recv +log_must eval "zfs send -p $snap > $sendfile" +log_must eval "zfs recv -o encryption=on -o keyformat=passphrase" \ + "-o keylocation=file://$keyfile $ds < $sendfile" +log_must test "$(get_prop 'encryption' $ds)" == "aes-256-ccm" +log_must test "$(get_prop 'encryptionroot' $ds)" == "$ds" +log_must test "$(get_prop 'keyformat' $ds)" == "passphrase" +log_must test "$(get_prop 'keylocation' $ds)" == "file://$keyfile" +log_must test "$(get_prop 'mounted' $ds)" == "yes" +recv_cksum=$(md5sum /$ds/$TESTFILE0 | awk '{ print $1 }') +log_must test "$recv_cksum" == "$cksum" +log_must zfs destroy -r $ds + +# Test that we can override encryption properties on a recursive stream +# of an unencrypted dataset, turning it into an encryption root. The root +# dataset of the stream should become an encryption root with all children +# inheriting from it. +log_note "Must be able to receive recursive stream as encryption root" +ds=$TESTPOOL/recv +log_must eval "zfs send -R $snap > $sendfile" +log_must eval "zfs recv -o encryption=on -o keyformat=passphrase" \ + "-o keylocation=file://$keyfile $ds < $sendfile" +log_must test "$(get_prop 'encryption' $ds)" == "aes-256-ccm" +log_must test "$(get_prop 'encryptionroot' $ds)" == "$ds" +log_must test "$(get_prop 'keyformat' $ds)" == "passphrase" +log_must test "$(get_prop 'keylocation' $ds)" == "file://$keyfile" +log_must test "$(get_prop 'mounted' $ds)" == "yes" +recv_cksum=$(md5sum /$ds/$TESTFILE0 | awk '{ print $1 }') +log_must test "$recv_cksum" == "$cksum" +log_must zfs destroy -r $ds + +# Test that we can override an unencrypted properties stream's encryption +# settings, receiving it as an encrypted child. +log_note "Must be able to receive stream with props to encrypted child" +ds=$TESTPOOL/crypt/recv +log_must eval "zfs send -p $snap > $sendfile" +log_must eval "zfs recv -x encryption $ds < $sendfile" +log_must test "$(get_prop 'encryptionroot' $ds)" == "$TESTPOOL/crypt" +log_must test "$(get_prop 'encryption' $ds)" == "aes-256-ccm" +log_must test "$(get_prop 'keyformat' $ds)" == "passphrase" +log_must test "$(get_prop 'mounted' $ds)" == "yes" +recv_cksum=$(md5sum /$ds/$TESTFILE0 | awk '{ print $1 }') +log_must test "$recv_cksum" == "$cksum" +log_must zfs destroy -r $ds + +# Test that we can override an unencrypted recursive stream's encryption +# settings, receiving all datasets as encrypted children. +log_note "Must be able to receive recursive stream to encrypted child" +ds=$TESTPOOL/crypt/recv +log_must eval "zfs send -R $snap > $sendfile" +log_must eval "zfs recv -x encryption $ds < $sendfile" +log_must test "$(get_prop 'encryptionroot' $ds)" == "$TESTPOOL/crypt" +log_must test "$(get_prop 'encryption' $ds)" == "aes-256-ccm" +log_must test "$(get_prop 'keyformat' $ds)" == "passphrase" +log_must test "$(get_prop 'mounted' $ds)" == "yes" +recv_cksum=$(md5sum /$ds/$TESTFILE0 | awk '{ print $1 }') +log_must test "$recv_cksum" == "$cksum" +log_must zfs destroy -r $ds + +# Check that we haven't printed the key to the zpool history log +log_mustnot eval "zpool history -i | grep -q 'wkeydata'" + +log_pass "'zfs recv' properly handles encryption properties" diff --git a/usr/src/test/zfs-tests/tests/functional/rsend/send_encrypted_truncated_files.ksh b/usr/src/test/zfs-tests/tests/functional/rsend/send_encrypted_truncated_files.ksh index d701bcecb9..b55b631127 100644 --- a/usr/src/test/zfs-tests/tests/functional/rsend/send_encrypted_truncated_files.ksh +++ b/usr/src/test/zfs-tests/tests/functional/rsend/send_encrypted_truncated_files.ksh @@ -50,12 +50,6 @@ function cleanup } log_onexit cleanup -function recursive_cksum -{ - find $1 -type f -exec sha256sum {} \; | \ - sort -k 2 | awk '{ print $1 }' | sha256sum -} - log_assert "Verify 'zfs send -w' works with many different file layouts" typeset keyfile=/$TESTPOOL/pkey diff --git a/usr/src/test/zfs-tests/tests/functional/rsend/send_holds.ksh b/usr/src/test/zfs-tests/tests/functional/rsend/send_holds.ksh new file mode 100755 index 0000000000..5dcf0e2a0a --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/rsend/send_holds.ksh @@ -0,0 +1,177 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# +# CDDL HEADER END +# + +# +# Copyright (c) 2012, 2016 by Delphix. All rights reserved. +# Copyright (c) 2018 Datto, Inc. All rights reserved. +# + +. $STF_SUITE/tests/functional/rsend/rsend.kshlib +. $STF_SUITE/tests/functional/cli_root/cli_common.kshlib + +# +# DESCRIPTION: +# Verify 'zfs send' can send dataset holds. +# Verify 'zfs receive' can receive dataset holds. +# +# STRATEGY: +# 1. Create a snapshot. +# 2. Create a full send stream with the fs, including holds. +# 3. Receive the send stream and verify the data integrity. +# 4. Fill in fs with some new data. +# 5. Create an incremental send stream with the fs, including holds. +# 6. Receive the incremental send stream and verify the data integrity. +# 7. Verify the holds have been received as expected. +# 8. Create an incremental snap with no holds, and send that with -h. +# 9. Confirm the snapshot was received as expected. +# 10. Create an incremental snapshot and place a hold on it. +# 11. Receive the incremental stream with -h and verify holds skipped. +# + +verify_runnable "both" + +function cleanup +{ + eval "zfs holds $init_snap |grep -q hold1-1" && + log_must zfs release hold1-1 $init_snap + eval "zfs holds $init_snap |grep -q hold1-2" && + log_must zfs release hold1-2 $init_snap + eval "zfs holds $recv_snap |grep -q hold1-1" && + log_must zfs release hold1-1 $recv_snap + eval "zfs holds $recv_snap |grep -q hold1-2" && + log_must zfs release hold1-2 $recv_snap + eval "zfs holds $inc_snap |grep -q hold2-1" && + log_must zfs release hold2-1 $inc_snap + eval "zfs holds $recv_inc_snap |grep -q hold2-1" && + log_must zfs release hold2-1 $recv_inc_snap + eval "zfs holds $inc_snap3 |grep -q hold3-1" && + log_must zfs release hold3-1 $inc_snap3 + + # destroy datasets + datasetexists $recv_root/$TESTFS1 && + log_must destroy_dataset "$recv_root/$TESTFS1" "-Rf" + datasetexists $recv_root && log_must destroy_dataset "$recv_root" "-Rf" + datasetexists $TESTPOOL/$TESTFS1 && log_must destroy_dataset "$TESTPOOL/$TESTFS1" "-Rf" + + [[ -e $full_bkup ]] && log_must rm -f $full_bkup + [[ -e $inc_bkup ]] && log_must rm -f $inc_bkup + [[ -e $inc_data2 ]] && log_must rm -f $inc_data2 + [[ -d $TESTDIR1 ]] && log_must rm -rf $TESTDIR1 + +} + +# +# Check if hold exists on the specified dataset. +# +function check_hold #<snapshot> <hold> +{ + typeset dataset=$1 + typeset hold=$2 + + log_note "checking $dataset for $hold" + eval "zfs holds $dataset |grep -q $hold" +} + +log_assert "Verify 'zfs send/recv' can send and receive dataset holds." +log_onexit cleanup + +init_snap=$TESTPOOL/$TESTFS1@init_snap +inc_snap=$TESTPOOL/$TESTFS1@inc_snap +inc_snap2=$TESTPOOL/$TESTFS1@inc_snap2 +inc_snap3=$TESTPOOL/$TESTFS1@inc_snap3 +full_bkup=$TEST_BASE_DIR/fullbkup.$$ +inc_bkup=$TEST_BASE_DIR/incbkup.$$ +init_data=$TESTDIR/$TESTFILE1 +inc_data=$TESTDIR/$TESTFILE2 +inc_data2=$TESTDIR/testfile3 +recv_root=$TESTPOOL/rst_ctr +recv_snap=$recv_root/$TESTFS1@init_snap +recv_inc_snap=$recv_root/$TESTFS1@inc_snap +recv_inc_snap2=$recv_root/$TESTFS1@inc_snap2 +recv_inc_snap3=$recv_root/$TESTFS1@inc_snap3 +recv_data=$TESTDIR1/$TESTFS1/$TESTFILE1 +recv_inc_data=$TESTDIR1/$TESTFS1/$TESTFILE2 +recv_inc_data2=$TESTDIR1/$TESTFS1/testfile3 + +log_note "Verify 'zfs send' can create full send stream." + +# Preparation +if ! datasetexists $TESTPOOL/$TESTFS1; then + log_must zfs create $TESTPOOL/$TESTFS1 +fi +[[ -e $init_data ]] && log_must rm -f $init_data +log_must zfs create $recv_root +[[ ! -d $TESTDIR1 ]] && log_must mkdir -p $TESTDIR1 +[[ ! -d $TESTDIR ]] && log_must mkdir -p $TESTDIR +log_must zfs set mountpoint=$TESTDIR $TESTPOOL/$TESTFS1 +log_must zfs set mountpoint=$TESTDIR1 $recv_root + +file_write -o create -f $init_data -b $BLOCK_SIZE -c $WRITE_COUNT + +log_must zfs snapshot $init_snap +log_must zfs hold hold1-1 $init_snap +log_must zfs hold hold1-2 $init_snap +log_must eval "zfs send -h $init_snap > $full_bkup" + +log_note "Verify the send stream is valid to receive." + +log_must zfs recv -F $recv_snap <$full_bkup +log_must datasetexists $recv_snap +receive_check $recv_snap ${recv_snap%%@*} + +log_note "Verify the holds were received." +log_must check_hold $recv_snap hold1-1 +log_must check_hold $recv_snap hold1-2 +compare_cksum $init_data $recv_data + +log_note "Verify 'zfs send -i' can create incremental send stream." + +file_write -o create -f $inc_data -b $BLOCK_SIZE -c $WRITE_COUNT -d 0 + +log_must zfs snapshot $inc_snap +log_must zfs hold hold2-1 $inc_snap +log_must eval "zfs send -h -i $init_snap $inc_snap > $inc_bkup" + +log_note "Verify the incremental send stream is valid to receive." + +log_must zfs recv -F $recv_inc_snap <$inc_bkup +log_must datasetexists $recv_inc_snap +log_note "Verify the hold was received from the incremental send." + +log_must check_hold $recv_inc_snap hold2-1 + +compare_cksum $inc_data $recv_inc_data + +log_note "Verify send -h works when there are no holds." +file_write -o create -f $inc_data2 -b $BLOCK_SIZE -c $WRITE_COUNT -d 0 +log_must zfs snapshot $inc_snap2 +log_must eval "zfs send -h -i $inc_snap $inc_snap2 > $inc_bkup" +log_must zfs recv -F $recv_inc_snap2 <$inc_bkup +log_must datasetexists $recv_inc_snap2 +compare_cksum $inc_data2 $recv_inc_data2 + +log_note "Verify send -h fails properly when receive dataset already exists" +log_must zfs recv -F $recv_inc_snap2 <$inc_bkup + +log_note "Verify recv -h skips the receive of holds" +log_must zfs snapshot $inc_snap3 +log_must zfs hold hold3-1 $inc_snap3 +log_must eval "zfs send -h -i $inc_snap2 $inc_snap3 > $inc_bkup" +log_must zfs recv -F -h $recv_inc_snap3 <$inc_bkup +log_must datasetexists $recv_inc_snap3 +log_mustnot check_hold $recv_inc_snap3 hold3-1 + +log_pass "'zfs send/recv' can send and receive dataset holds." diff --git a/usr/src/test/zfs-tests/tests/functional/rsend/send_realloc_encrypted_files.ksh b/usr/src/test/zfs-tests/tests/functional/rsend/send_realloc_encrypted_files.ksh index 0649beaa35..e2f9132129 100644 --- a/usr/src/test/zfs-tests/tests/functional/rsend/send_realloc_encrypted_files.ksh +++ b/usr/src/test/zfs-tests/tests/functional/rsend/send_realloc_encrypted_files.ksh @@ -72,7 +72,6 @@ for i in {1..5}; do rand_set_prop $POOL/fs compression "off" "lzjb" "gzip" "lz4" rand_set_prop $POOL/fs recordsize "32K" "128K" rand_set_prop $POOL/fs dnodesize "legacy" "auto" "4k" - rand_set_prop $POOL/fs xattr "on" "sa" # Churn the filesystem in such a way that we're likely to be both # allocating and reallocating objects in the incremental stream. diff --git a/usr/src/test/zfs-tests/tests/functional/rsend/send_realloc_files.ksh b/usr/src/test/zfs-tests/tests/functional/rsend/send_realloc_files.ksh new file mode 100755 index 0000000000..7add469df3 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/rsend/send_realloc_files.ksh @@ -0,0 +1,93 @@ +#!/bin/ksh + +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright (c) 2019 by Lawrence Livermore National Security, LLC. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/rsend/rsend.kshlib + +# +# Description: +# Verify incremental receive properly handles reallocation. +# +# Strategy: +# 1. Create a pool containing an encrypted filesystem. +# 2. Use 'zfs send -wp' to perform a raw send of the initial filesystem. +# 3. Repeat the followings steps N times to verify raw incremental receives. +# a) Randomly change several key dataset properties. +# b) Modify the contents of the filesystem such that dnode reallocation +# is likely during the 'zfs receive', and receive_object() exercises +# as much of its functionality as possible. +# c) Create a new snapshot and generate an raw incremental stream. +# d) Receive the raw incremental stream and verify the received contents. +# e) Destroy the incremental stream and old snapshot. +# + +log_assert "Verify incremental receive handles reallocation" + +function cleanup +{ + rm -f $BACKDIR/fs@* + destroy_dataset $POOL/fs "-rR" + destroy_dataset $POOL/newfs "-rR" +} + +log_onexit cleanup + +log_must zfs create $POOL/fs + +last_snap=1 +log_must zfs snapshot $POOL/fs@snap${last_snap} +log_must eval "zfs send $POOL/fs@snap${last_snap} >$BACKDIR/fs@snap${last_snap}" +log_must eval "zfs recv $POOL/newfs < $BACKDIR/fs@snap${last_snap}" + +# Set atime=off to prevent the recursive_cksum from modifying newfs. +log_must zfs set atime=off $POOL/newfs + +for i in {1..5}; do + # Randomly modify several dataset properties in order to generate + # more interesting incremental send streams. + rand_set_prop $POOL/fs checksum "off" "fletcher4" "sha256" + rand_set_prop $POOL/fs compression "off" "lzjb" "gzip" "lz4" + rand_set_prop $POOL/fs recordsize "32K" "128K" + rand_set_prop $POOL/fs dnodesize "legacy" "auto" "4k" + + # Churn the filesystem in such a way that we're likely to be both + # allocating and reallocating objects in the incremental stream. + log_must churn_files 1000 524288 $POOL/fs 0 + expected_cksum=$(recursive_cksum /$fs) + + # Create a snapshot and use it to send an incremental stream. + this_snap=$((last_snap + 1)) + log_must zfs snapshot $POOL/fs@snap${this_snap} + log_must eval "zfs send -i $POOL/fs@snap${last_snap} \ + $POOL/fs@snap${this_snap} > $BACKDIR/fs@snap${this_snap}" + + # Receive the incremental stream and verify the received contents. + log_must eval "zfs recv $POOL/newfs < $BACKDIR/fs@snap${this_snap}" + actual_cksum=$(recursive_cksum /$POOL/newfs) + + if [[ "$expected_cksum" != "$actual_cksum" ]]; then + log_fail "Checksums differ ($expected_cksum != $actual_cksum)" + fi + + # Destroy the incremental stream and old snapshot. + rm -f $BACKDIR/fs@snap${last_snap} + log_must zfs destroy $POOL/fs@snap${last_snap} + log_must zfs destroy $POOL/newfs@snap${last_snap} + last_snap=$this_snap +done + +log_pass "Verify incremental receive handles dnode reallocation" diff --git a/usr/src/test/zfs-tests/tests/functional/rsend/send_spill_block.ksh b/usr/src/test/zfs-tests/tests/functional/rsend/send_spill_block.ksh new file mode 100755 index 0000000000..9de732e223 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/rsend/send_spill_block.ksh @@ -0,0 +1,155 @@ +#!/bin/ksh + +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright (c) 2019 by Lawrence Livermore National Security, LLC. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/rsend/rsend.kshlib + +# +# Description: +# Verify spill blocks are correctly preserved. +# +# Strategy: +# 1) Create a set of files each containing some file data. +# 2) Add enough xattrs to the file to require a spill block. +# 3) Snapshot and send these files to a new dataset. +# 4) Modify the files and spill blocks in a variety of ways. +# 5) Send the changes using an incremental send stream. +# 6) Verify that all the xattrs (and thus the spill block) were +# preserved when receiving the incremental stream. +# + +verify_runnable "both" + +log_assert "Verify spill blocks are correctly preserved" + +function cleanup +{ + rm -f $BACKDIR/fs@* + destroy_dataset $POOL/fs "-rR" + destroy_dataset $POOL/newfs "-rR" +} + +attrvalue="abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" + +log_onexit cleanup + +log_must zfs create $POOL/fs +log_must zfs set xattr=sa $POOL/fs +log_must zfs set dnodesize=legacy $POOL/fs +log_must zfs set recordsize=128k $POOL/fs + +# Create 40 files each with a spill block containing xattrs. Each file +# will be modified in a different way to validate the incremental receive. +for i in {1..40}; do + file="/$POOL/fs/file$i" + + log_must mkfile 16384 $file + for j in {1..20}; do + log_must attr -qs "testattr$j" -V "$attrvalue" $file + done +done + +# Snapshot the pool and send it to the new dataset. +log_must zfs snapshot $POOL/fs@snap1 +log_must eval "zfs send -e $POOL/fs@snap1 >$BACKDIR/fs@snap1" +log_must eval "zfs recv $POOL/newfs < $BACKDIR/fs@snap1" + +# +# Modify file[1-6]'s contents but not the spill blocks. +# +# file1 - Increase record size; single block +# file2 - Increase record size; multiple blocks +# file3 - Truncate file to zero size; single block +# file4 - Truncate file to smaller size; single block +# file5 - Truncate file to much larger size; add holes +# file6 - Truncate file to embedded size; embedded data +# +log_must mkfile 32768 /$POOL/fs/file1 +log_must mkfile 1048576 /$POOL/fs/file2 +log_must truncate -s 0 /$POOL/fs/file3 +log_must truncate -s 8192 /$POOL/fs/file4 +log_must truncate -s 1073741824 /$POOL/fs/file5 +log_must truncate -s 50 /$POOL/fs/file6 + +# +# Modify file[11-16]'s contents and their spill blocks. +# +# file11 - Increase record size; single block +# file12 - Increase record size; multiple blocks +# file13 - Truncate file to zero size; single block +# file14 - Truncate file to smaller size; single block +# file15 - Truncate file to much larger size; add holes +# file16 - Truncate file to embedded size; embedded data +# +log_must mkfile 32768 /$POOL/fs/file11 +log_must mkfile 1048576 /$POOL/fs/file12 +log_must truncate -s 0 /$POOL/fs/file13 +log_must truncate -s 8192 /$POOL/fs/file14 +log_must truncate -s 1073741824 /$POOL/fs/file15 +log_must truncate -s 50 /$POOL/fs/file16 + +for i in {11..20}; do + log_must attr -qr testattr1 /$POOL/fs/file$i +done + +# +# Modify file[21-26]'s contents and remove their spill blocks. +# +# file21 - Increase record size; single block +# file22 - Increase record size; multiple blocks +# file23 - Truncate file to zero size; single block +# file24 - Truncate file to smaller size; single block +# file25 - Truncate file to much larger size; add holes +# file26 - Truncate file to embedded size; embedded data +# +log_must mkfile 32768 /$POOL/fs/file21 +log_must mkfile 1048576 /$POOL/fs/file22 +log_must truncate -s 0 /$POOL/fs/file23 +log_must truncate -s 8192 /$POOL/fs/file24 +log_must truncate -s 1073741824 /$POOL/fs/file25 +log_must truncate -s 50 /$POOL/fs/file26 + +for i in {21..30}; do + for j in {1..20}; do + log_must attr -qr testattr$j /$POOL/fs/file$i + done +done + +# +# Modify file[31-40]'s spill blocks but not the file contents. +# +for i in {31..40}; do + file="/$POOL/fs/file$i" + log_must attr -qr testattr$(((RANDOM % 20) + 1)) $file + log_must attr -qs testattr$(((RANDOM % 20) + 1)) -V "$attrvalue" $file +done + +# Calculate the expected recursive checksum for the source. +expected_cksum=$(recursive_cksum /$POOL/fs) + +# Snapshot the pool and send the incremental snapshot. +log_must zfs snapshot $POOL/fs@snap2 +log_must eval "zfs send -e -i $POOL/fs@snap1 $POOL/fs@snap2 >$BACKDIR/fs@snap2" +log_must eval "zfs recv -F $POOL/newfs < $BACKDIR/fs@snap2" + +# Validate the received copy using the received recursive checksum. +actual_cksum=$(recursive_cksum /$POOL/newfs) +if [[ "$expected_cksum" != "$actual_cksum" ]]; then + log_fail "Checksums differ ($expected_cksum != $actual_cksum)" +fi + +log_pass "Verify spill blocks are correctly preserved" diff --git a/usr/src/uts/common/fs/zfs/dbuf.c b/usr/src/uts/common/fs/zfs/dbuf.c index e96cd84928..a7d191e7dd 100644 --- a/usr/src/uts/common/fs/zfs/dbuf.c +++ b/usr/src/uts/common/fs/zfs/dbuf.c @@ -2209,7 +2209,7 @@ dbuf_assign_arcbuf(dmu_buf_impl_t *db, arc_buf_t *buf, dmu_tx_t *tx) ASSERT(db->db_level == 0); ASSERT3U(dbuf_is_metadata(db), ==, arc_is_metadata(buf)); ASSERT(buf != NULL); - ASSERT(arc_buf_lsize(buf) == db->db.db_size); + ASSERT3U(arc_buf_lsize(buf), ==, db->db.db_size); ASSERT(tx->tx_txg != 0); arc_return_buf(buf, db); diff --git a/usr/src/uts/common/fs/zfs/dmu_recv.c b/usr/src/uts/common/fs/zfs/dmu_recv.c index b72aa21922..268c3af586 100644 --- a/usr/src/uts/common/fs/zfs/dmu_recv.c +++ b/usr/src/uts/common/fs/zfs/dmu_recv.c @@ -72,13 +72,11 @@ typedef struct dmu_recv_begin_arg { static int recv_begin_check_existing_impl(dmu_recv_begin_arg_t *drba, dsl_dataset_t *ds, - uint64_t fromguid) + uint64_t fromguid, uint64_t featureflags) { uint64_t val; int error; dsl_pool_t *dp = ds->ds_dir->dd_pool; - struct drr_begin *drrb = drba->drba_cookie->drc_drrb; - uint64_t featureflags = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo); boolean_t encrypted = ds->ds_dir->dd_crypto_obj != 0; boolean_t raw = (featureflags & DMU_BACKUP_FEATURE_RAW) != 0; boolean_t embed = (featureflags & DMU_BACKUP_FEATURE_EMBED_DATA) != 0; @@ -254,7 +252,7 @@ dmu_recv_begin_check(void *arg, dmu_tx_t *tx) !spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_LARGE_DNODE)) return (SET_ERROR(ENOTSUP)); - if ((featureflags & DMU_BACKUP_FEATURE_RAW)) { + if (featureflags & DMU_BACKUP_FEATURE_RAW) { /* raw receives require the encryption feature */ if (!spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_ENCRYPTION)) return (SET_ERROR(ENOTSUP)); @@ -280,7 +278,8 @@ dmu_recv_begin_check(void *arg, dmu_tx_t *tx) return (SET_ERROR(EINVAL)); } - error = recv_begin_check_existing_impl(drba, ds, fromguid); + error = recv_begin_check_existing_impl(drba, ds, fromguid, + featureflags); dsl_dataset_rele_flags(ds, dsflags, FTAG); } else if (error == ENOENT) { /* target fs does not exist; must be a full backup or clone */ @@ -408,6 +407,7 @@ dmu_recv_begin_sync(void *arg, dmu_tx_t *tx) if (drrb->drr_flags & DRR_FLAG_CI_DATA) crflags |= DS_FLAG_CI_DATASET; + if ((featureflags & DMU_BACKUP_FEATURE_RAW) == 0) dsflags |= DS_HOLD_FLAG_DECRYPT; @@ -719,7 +719,8 @@ dmu_recv_resume_begin_sync(void *arg, dmu_tx_t *tx) */ int dmu_recv_begin(char *tofs, char *tosnap, dmu_replay_record_t *drr_begin, - boolean_t force, boolean_t resumable, char *origin, dmu_recv_cookie_t *drc) + boolean_t force, boolean_t resumable, nvlist_t *localprops, + nvlist_t *hidden_args, char *origin, dmu_recv_cookie_t *drc) { dmu_recv_begin_arg_t drba = { 0 }; @@ -758,9 +759,33 @@ dmu_recv_begin(char *tofs, char *tosnap, dmu_replay_record_t *drr_begin, dmu_recv_resume_begin_check, dmu_recv_resume_begin_sync, &drba, 5, ZFS_SPACE_CHECK_NORMAL)); } else { - return (dsl_sync_task(tofs, + int err; + + /* + * For non-raw, non-incremental, non-resuming receives the + * user can specify encryption parameters on the command line + * with "zfs recv -o". For these receives we create a dcp and + * pass it to the sync task. Creating the dcp will implicitly + * remove the encryption params from the localprops nvlist, + * which avoids errors when trying to set these normally + * read-only properties. Any other kind of receive that + * attempts to set these properties will fail as a result. + */ + if ((DMU_GET_FEATUREFLAGS(drc->drc_drrb->drr_versioninfo) & + DMU_BACKUP_FEATURE_RAW) == 0 && + origin == NULL && drc->drc_drrb->drr_fromguid == 0) { + err = dsl_crypto_params_create_nvlist(DCP_CMD_NONE, + localprops, hidden_args, &drba.drba_dcp); + if (err != 0) + return (err); + } + + err = dsl_sync_task(tofs, dmu_recv_begin_check, dmu_recv_begin_sync, - &drba, 5, ZFS_SPACE_CHECK_NORMAL)); + &drba, 5, ZFS_SPACE_CHECK_NORMAL); + dsl_crypto_params_free(drba.drba_dcp, !!err); + + return (err); } } @@ -1240,6 +1265,7 @@ receive_object(struct receive_writer_arg *rwa, struct drr_object *drro, * earlier in the stream. */ txg_wait_synced(dmu_objset_pool(rwa->os), 0); + if (dmu_object_info(rwa->os, drro->drr_object, NULL) != ENOENT) return (SET_ERROR(EINVAL)); @@ -2454,7 +2480,8 @@ dmu_recv_stream(dmu_recv_cookie_t *drc, vnode_t *vp, offset_t *voffp, goto out; } - (void) bqueue_init(&rwa.q, zfs_recv_queue_length, + (void) bqueue_init(&rwa.q, + MAX(zfs_recv_queue_length, 2 * zfs_max_recordsize), offsetof(struct receive_record_arg, node)); cv_init(&rwa.cv, NULL, CV_DEFAULT, NULL); mutex_init(&rwa.mutex, NULL, MUTEX_DEFAULT, NULL); diff --git a/usr/src/uts/common/fs/zfs/dmu_send.c b/usr/src/uts/common/fs/zfs/dmu_send.c index 27da9b68b2..34cfa2c011 100644 --- a/usr/src/uts/common/fs/zfs/dmu_send.c +++ b/usr/src/uts/common/fs/zfs/dmu_send.c @@ -58,7 +58,7 @@ /* Set this tunable to TRUE to replace corrupt data with 0x2f5baddb10c */ int zfs_send_corrupt_data = B_FALSE; -int zfs_send_queue_length = 16 * 1024 * 1024; +int zfs_send_queue_length = SPA_MAXBLOCKSIZE; /* Set this tunable to FALSE to disable setting of DRR_FLAG_FREERECORDS */ int zfs_send_set_freerecords_bit = B_TRUE; /* Set this tunable to FALSE is disable sending unmodified spill blocks. */ @@ -1143,7 +1143,8 @@ dmu_send_impl(void *tag, dsl_pool_t *dp, dsl_dataset_t *to_ds, goto out; } - err = bqueue_init(&to_arg.q, zfs_send_queue_length, + err = bqueue_init(&to_arg.q, + MAX(zfs_send_queue_length, 2 * zfs_max_recordsize), offsetof(struct send_block_record, ln)); to_arg.error_code = 0; to_arg.cancel = B_FALSE; @@ -1295,7 +1296,6 @@ dmu_send(const char *tosnap, const char *fromsnap, boolean_t embedok, err = dsl_pool_hold(tosnap, FTAG, &dp); if (err != 0) return (err); - if (strchr(tosnap, '@') == NULL && spa_writeable(dp->dp_spa)) { /* * We are sending a filesystem or volume. Ensure diff --git a/usr/src/uts/common/fs/zfs/dsl_userhold.c b/usr/src/uts/common/fs/zfs/dsl_userhold.c index d0274dc4ce..446a19b804 100644 --- a/usr/src/uts/common/fs/zfs/dsl_userhold.c +++ b/usr/src/uts/common/fs/zfs/dsl_userhold.c @@ -83,6 +83,7 @@ dsl_dataset_user_hold_check(void *arg, dmu_tx_t *tx) { dsl_dataset_user_hold_arg_t *dduha = arg; dsl_pool_t *dp = dmu_tx_pool(tx); + nvlist_t *tmp_holds; if (spa_version(dp->dp_spa) < SPA_VERSION_USERREFS) return (SET_ERROR(ENOTSUP)); @@ -90,6 +91,26 @@ dsl_dataset_user_hold_check(void *arg, dmu_tx_t *tx) if (!dmu_tx_is_syncing(tx)) return (0); + /* + * Ensure the list has no duplicates by copying name/values from + * non-unique dduha_holds to unique tmp_holds, and comparing counts. + */ + tmp_holds = fnvlist_alloc(); + for (nvpair_t *pair = nvlist_next_nvpair(dduha->dduha_holds, NULL); + pair != NULL; pair = nvlist_next_nvpair(dduha->dduha_holds, pair)) { + size_t len = strlen(nvpair_name(pair)) + + strlen(fnvpair_value_string(pair)); + char *nameval = kmem_zalloc(len + 2, KM_SLEEP); + (void) strcpy(nameval, nvpair_name(pair)); + (void) strcat(nameval, "@"); + (void) strcat(nameval, fnvpair_value_string(pair)); + fnvlist_add_string(tmp_holds, nameval, ""); + kmem_free(nameval, len + 2); + } + size_t tmp_count = fnvlist_num_pairs(tmp_holds); + fnvlist_free(tmp_holds); + if (tmp_count != fnvlist_num_pairs(dduha->dduha_holds)) + return (SET_ERROR(EEXIST)); for (nvpair_t *pair = nvlist_next_nvpair(dduha->dduha_holds, NULL); pair != NULL; pair = nvlist_next_nvpair(dduha->dduha_holds, pair)) { dsl_dataset_t *ds; @@ -312,7 +333,8 @@ dsl_dataset_user_hold(nvlist_t *holds, minor_t cleanup_minor, nvlist_t *errlist) return (0); dduha.dduha_holds = holds; - dduha.dduha_chkholds = fnvlist_alloc(); + /* chkholds can have non-unique name */ + VERIFY(0 == nvlist_alloc(&dduha.dduha_chkholds, 0, KM_SLEEP)); dduha.dduha_errlist = errlist; dduha.dduha_minor = cleanup_minor; diff --git a/usr/src/uts/common/fs/zfs/sys/dmu_recv.h b/usr/src/uts/common/fs/zfs/sys/dmu_recv.h index e2b595e77b..fa2bcd14e9 100644 --- a/usr/src/uts/common/fs/zfs/sys/dmu_recv.h +++ b/usr/src/uts/common/fs/zfs/sys/dmu_recv.h @@ -31,6 +31,7 @@ #define _DMU_RECV_H #include <sys/inttypes.h> +#include <sys/dsl_crypt.h> #include <sys/spa.h> extern const char *recv_clone_name; @@ -59,8 +60,9 @@ typedef struct dmu_recv_cookie { } dmu_recv_cookie_t; int dmu_recv_begin(char *tofs, char *tosnap, - struct dmu_replay_record *drr_begin, - boolean_t force, boolean_t resumable, char *origin, dmu_recv_cookie_t *drc); + struct dmu_replay_record *drr_begin, boolean_t force, boolean_t resumable, + nvlist_t *localprops, nvlist_t *hidden_args, char *origin, + dmu_recv_cookie_t *drc); int dmu_recv_stream(dmu_recv_cookie_t *drc, struct vnode *vp, offset_t *voffp, int cleanup_fd, uint64_t *action_handlep); int dmu_recv_end(dmu_recv_cookie_t *drc, void *owner); diff --git a/usr/src/uts/common/fs/zfs/sys/zfs_ioctl.h b/usr/src/uts/common/fs/zfs/sys/zfs_ioctl.h index 1457200dd8..2ad4e77398 100644 --- a/usr/src/uts/common/fs/zfs/sys/zfs_ioctl.h +++ b/usr/src/uts/common/fs/zfs/sys/zfs_ioctl.h @@ -91,11 +91,12 @@ typedef enum drr_headertype { /* flag #18 is reserved for a Delphix feature */ #define DMU_BACKUP_FEATURE_LARGE_BLOCKS (1 << 19) #define DMU_BACKUP_FEATURE_RESUMING (1 << 20) -/* flag #21 is reserved for a Delphix feature */ +/* flag #21 is reserved for the redacted send/receive feature */ #define DMU_BACKUP_FEATURE_COMPRESSED (1 << 22) #define DMU_BACKUP_FEATURE_LARGE_DNODE (1 << 23) #define DMU_BACKUP_FEATURE_RAW (1 << 24) /* flag #25 is reserved for the ZSTD compression feature */ +#define DMU_BACKUP_FEATURE_HOLDS (1 << 26) /* * Mask of all supported backup features @@ -103,10 +104,9 @@ typedef enum drr_headertype { #define DMU_BACKUP_FEATURE_MASK (DMU_BACKUP_FEATURE_DEDUP | \ DMU_BACKUP_FEATURE_DEDUPPROPS | DMU_BACKUP_FEATURE_SA_SPILL | \ DMU_BACKUP_FEATURE_EMBED_DATA | DMU_BACKUP_FEATURE_LZ4 | \ - DMU_BACKUP_FEATURE_RESUMING | \ - DMU_BACKUP_FEATURE_LARGE_BLOCKS | DMU_BACKUP_FEATURE_LARGE_DNODE | \ - DMU_BACKUP_FEATURE_COMPRESSED | \ - DMU_BACKUP_FEATURE_RAW) + DMU_BACKUP_FEATURE_RESUMING | DMU_BACKUP_FEATURE_LARGE_BLOCKS | \ + DMU_BACKUP_FEATURE_COMPRESSED | DMU_BACKUP_FEATURE_LARGE_DNODE | \ + DMU_BACKUP_FEATURE_RAW | DMU_BACKUP_FEATURE_HOLDS) /* Are all features in the given flag word currently supported? */ #define DMU_STREAM_SUPPORTED(x) (!((x) & ~DMU_BACKUP_FEATURE_MASK)) @@ -173,7 +173,7 @@ typedef enum dmu_send_resume_token_version { * flags in the drr_flags field in the DRR_WRITE, DRR_SPILL, DRR_OBJECT, * DRR_WRITE_BYREF, and DRR_OBJECT_RANGE blocks */ -#define DRR_CHECKSUM_DEDUP (1<<0) /* not used for DRR_SPILL blocks */ +#define DRR_CHECKSUM_DEDUP (1<<0) /* not used for SPILL records */ #define DRR_RAW_BYTESWAP (1<<1) #define DRR_OBJECT_SPILL (1<<2) /* OBJECT record has a spill block */ #define DRR_SPILL_UNMODIFIED (1<<2) /* SPILL record for unmodified block */ diff --git a/usr/src/uts/common/fs/zfs/zfs_ioctl.c b/usr/src/uts/common/fs/zfs/zfs_ioctl.c index 0039810543..b28f847b10 100644 --- a/usr/src/uts/common/fs/zfs/zfs_ioctl.c +++ b/usr/src/uts/common/fs/zfs/zfs_ioctl.c @@ -24,13 +24,14 @@ * Copyright (c) 2011-2012 Pawel Jakub Dawidek. All rights reserved. * Portions Copyright 2011 Martin Matuska * Copyright 2015, OmniTI Computer Consulting, Inc. All rights reserved. - * Copyright 2015 Nexenta Systems, Inc. All rights reserved. - * Copyright (c) 2014, 2019 Joyent, Inc. All rights reserved. + * Copyright 2016 Nexenta Systems, Inc. All rights reserved. + * Copyright (c) 2014, 2016 Joyent, Inc. All rights reserved. * Copyright (c) 2011, 2017 by Delphix. All rights reserved. * Copyright (c) 2013 by Saso Kiselkov. All rights reserved. * Copyright (c) 2013 Steven Hartland. All rights reserved. * Copyright (c) 2014 Integros [integros.com] * Copyright 2016 Toomas Soome <tsoome@me.com> + * Copyright (c) 2017, loli10K <ezomori.nozomu@gmail.com>. All rights reserved. * Copyright 2017 RackTop Systems. * Copyright (c) 2017, Datto, Inc. All rights reserved. */ @@ -2637,7 +2638,11 @@ retry: } /* Validate value type */ - if (err == 0 && prop == ZPROP_INVAL) { + if (err == 0 && source == ZPROP_SRC_INHERITED) { + /* inherited properties are expected to be booleans */ + if (nvpair_type(propval) != DATA_TYPE_BOOLEAN) + err = SET_ERROR(EINVAL); + } else if (err == 0 && prop == ZPROP_INVAL) { if (zfs_prop_user(propname)) { if (nvpair_type(propval) != DATA_TYPE_STRING) err = SET_ERROR(EINVAL); @@ -2682,7 +2687,11 @@ retry: err = zfs_check_settable(dsname, pair, CRED()); if (err == 0) { - err = zfs_prop_set_special(dsname, source, pair); + if (source == ZPROP_SRC_INHERITED) + err = -1; /* does not need special handling */ + else + err = zfs_prop_set_special(dsname, source, + pair); if (err == -1) { /* * For better performance we build up a list of @@ -2734,6 +2743,9 @@ retry: strval = fnvpair_value_string(propval); err = dsl_prop_set_string(dsname, propname, source, strval); + } else if (nvpair_type(propval) == DATA_TYPE_BOOLEAN) { + err = dsl_prop_inherit(dsname, propname, + source); } else { intval = fnvpair_value_uint64(propval); err = dsl_prop_set_int(dsname, propname, source, @@ -3813,10 +3825,39 @@ zfs_ioc_destroy(zfs_cmd_t *zc) if (ost == DMU_OST_ZFS) zfs_unmount_snap(zc->zc_name); - if (strchr(zc->zc_name, '@')) + if (strchr(zc->zc_name, '@')) { err = dsl_destroy_snapshot(zc->zc_name, zc->zc_defer_destroy); - else + } else { err = dsl_destroy_head(zc->zc_name); + if (err == EEXIST) { + /* + * It is possible that the given DS may have + * hidden child (%recv) datasets - "leftovers" + * resulting from the previously interrupted + * 'zfs receive'. + * + * 6 extra bytes for /%recv + */ + char namebuf[ZFS_MAX_DATASET_NAME_LEN + 6]; + + if (snprintf(namebuf, sizeof (namebuf), "%s/%s", + zc->zc_name, recv_clone_name) >= + sizeof (namebuf)) + return (SET_ERROR(EINVAL)); + + /* + * Try to remove the hidden child (%recv) and after + * that try to remove the target dataset. + * If the hidden child (%recv) does not exist + * the original error (EEXIST) will be returned + */ + err = dsl_destroy_head(namebuf); + if (err == 0) + err = dsl_destroy_head(zc->zc_name); + else if (err == ENOENT) + err = SET_ERROR(EEXIST); + } + } if (ost == DMU_OST_ZVOL && err == 0) (void) zvol_remove_minor(zc->zc_name); return (err); @@ -4469,71 +4510,37 @@ static boolean_t zfs_ioc_recv_inject_err; #endif /* - * inputs: - * zc_name name of containing filesystem - * zc_nvlist_src{_size} nvlist of properties to apply - * zc_value name of snapshot to create - * zc_string name of clone origin (if DRR_FLAG_CLONE) - * zc_cookie file descriptor to recv from - * zc_begin_record the BEGIN record of the stream (not byteswapped) - * zc_guid force flag - * zc_cleanup_fd cleanup-on-exit file descriptor - * zc_action_handle handle for this guid/ds mapping (or zero on first call) - * zc_resumable if data is incomplete assume sender will resume - * - * outputs: - * zc_cookie number of bytes read - * zc_nvlist_dst{_size} error for each unapplied received property - * zc_obj zprop_errflags_t - * zc_action_handle handle for this guid/ds mapping + * nvlist 'errors' is always allocated. It will contain descriptions of + * encountered errors, if any. It's the callers responsibility to free. */ static int -zfs_ioc_recv(zfs_cmd_t *zc) +zfs_ioc_recv_impl(char *tofs, char *tosnap, char *origin, nvlist_t *recvprops, + nvlist_t *localprops, nvlist_t *hidden_args, boolean_t force, + boolean_t resumable, int input_fd, dmu_replay_record_t *begin_record, + int cleanup_fd, uint64_t *read_bytes, uint64_t *errflags, + uint64_t *action_handle, nvlist_t **errors) { - file_t *fp; dmu_recv_cookie_t drc; - boolean_t force = (boolean_t)zc->zc_guid; - int fd; int error = 0; int props_error = 0; - nvlist_t *errors; offset_t off; - nvlist_t *props = NULL; /* sent properties */ + nvlist_t *local_delayprops = NULL; + nvlist_t *recv_delayprops = NULL; nvlist_t *origprops = NULL; /* existing properties */ - nvlist_t *delayprops = NULL; /* sent properties applied post-receive */ - char *origin = NULL; - char *tosnap; - char tofs[ZFS_MAX_DATASET_NAME_LEN]; + nvlist_t *origrecvd = NULL; /* existing received properties */ boolean_t first_recvd_props = B_FALSE; + file_t *input_fp; - if (dataset_namecheck(zc->zc_value, NULL, NULL) != 0 || - strchr(zc->zc_value, '@') == NULL || - strchr(zc->zc_value, '%')) - return (SET_ERROR(EINVAL)); + *read_bytes = 0; + *errflags = 0; + *errors = fnvlist_alloc(); - (void) strcpy(tofs, zc->zc_value); - tosnap = strchr(tofs, '@'); - *tosnap++ = '\0'; - - if (zc->zc_nvlist_src != 0 && - (error = get_nvlist(zc->zc_nvlist_src, zc->zc_nvlist_src_size, - zc->zc_iflags, &props)) != 0) - return (error); - - fd = zc->zc_cookie; - fp = getf(fd); - if (fp == NULL) { - nvlist_free(props); + input_fp = getf(input_fd); + if (input_fp == NULL) return (SET_ERROR(EBADF)); - } - errors = fnvlist_alloc(); - - if (zc->zc_string[0]) - origin = zc->zc_string; - - error = dmu_recv_begin(tofs, tosnap, - &zc->zc_begin_record, force, zc->zc_resumable, origin, &drc); + error = dmu_recv_begin(tofs, tosnap, begin_record, force, + resumable, localprops, hidden_args, origin, &drc); if (error != 0) goto out; @@ -4542,7 +4549,7 @@ zfs_ioc_recv(zfs_cmd_t *zc) * to the new data. Note that we must call dmu_recv_stream() if * dmu_recv_begin() succeeds. */ - if (props != NULL && !drc.drc_newfs) { + if (recvprops != NULL && !drc.drc_newfs) { if (spa_version(dsl_dataset_get_spa(drc.drc_ds)) >= SPA_VERSION_RECVD_PROPS && !dsl_prop_get_hasrecvd(tofs)) @@ -4550,10 +4557,10 @@ zfs_ioc_recv(zfs_cmd_t *zc) /* * If new received properties are supplied, they are to - * completely replace the existing received properties, so stash - * away the existing ones. + * completely replace the existing received properties, + * so stash away the existing ones. */ - if (dsl_prop_get_received(tofs, &origprops) == 0) { + if (dsl_prop_get_received(tofs, &origrecvd) == 0) { nvlist_t *errlist = NULL; /* * Don't bother writing a property if its value won't @@ -4564,32 +4571,81 @@ zfs_ioc_recv(zfs_cmd_t *zc) * regardless. */ if (!first_recvd_props) - props_reduce(props, origprops); - if (zfs_check_clearable(tofs, origprops, &errlist) != 0) - (void) nvlist_merge(errors, errlist, 0); + props_reduce(recvprops, origrecvd); + if (zfs_check_clearable(tofs, origrecvd, &errlist) != 0) + (void) nvlist_merge(*errors, errlist, 0); nvlist_free(errlist); - if (clear_received_props(tofs, origprops, - first_recvd_props ? NULL : props) != 0) - zc->zc_obj |= ZPROP_ERR_NOCLEAR; + if (clear_received_props(tofs, origrecvd, + first_recvd_props ? NULL : recvprops) != 0) + *errflags |= ZPROP_ERR_NOCLEAR; + } else { + *errflags |= ZPROP_ERR_NOCLEAR; + } + } + + /* + * Stash away existing properties so we can restore them on error unless + * we're doing the first receive after SPA_VERSION_RECVD_PROPS, in which + * case "origrecvd" will take care of that. + */ + if (localprops != NULL && !drc.drc_newfs && !first_recvd_props) { + objset_t *os; + if (dmu_objset_hold(tofs, FTAG, &os) == 0) { + if (dsl_prop_get_all(os, &origprops) != 0) { + *errflags |= ZPROP_ERR_NOCLEAR; + } + dmu_objset_rele(os, FTAG); } else { - zc->zc_obj |= ZPROP_ERR_NOCLEAR; + *errflags |= ZPROP_ERR_NOCLEAR; } } - if (props != NULL) { + if (recvprops != NULL) { props_error = dsl_prop_set_hasrecvd(tofs); if (props_error == 0) { - delayprops = extract_delay_props(props); + recv_delayprops = extract_delay_props(recvprops); (void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_RECEIVED, - props, errors); + recvprops, *errors); } } - off = fp->f_offset; - error = dmu_recv_stream(&drc, fp->f_vnode, &off, zc->zc_cleanup_fd, - &zc->zc_action_handle); + if (localprops != NULL) { + nvlist_t *oprops = fnvlist_alloc(); + nvlist_t *xprops = fnvlist_alloc(); + nvpair_t *nvp = NULL; + + while ((nvp = nvlist_next_nvpair(localprops, nvp)) != NULL) { + if (nvpair_type(nvp) == DATA_TYPE_BOOLEAN) { + /* -x property */ + const char *name = nvpair_name(nvp); + zfs_prop_t prop = zfs_name_to_prop(name); + if (prop != ZPROP_INVAL) { + if (!zfs_prop_inheritable(prop)) + continue; + } else if (!zfs_prop_user(name)) + continue; + fnvlist_add_boolean(xprops, name); + } else { + /* -o property=value */ + fnvlist_add_nvpair(oprops, nvp); + } + } + + local_delayprops = extract_delay_props(oprops); + (void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_LOCAL, + oprops, *errors); + (void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_INHERITED, + xprops, *errors); + + nvlist_free(oprops); + nvlist_free(xprops); + } + + off = input_fp->f_offset; + error = dmu_recv_stream(&drc, input_fp->f_vnode, &off, cleanup_fd, + action_handle); if (error == 0) { zfsvfs_t *zfsvfs = NULL; @@ -4615,43 +4671,37 @@ zfs_ioc_recv(zfs_cmd_t *zc) } /* Set delayed properties now, after we're done receiving. */ - if (delayprops != NULL && error == 0) { + if (recv_delayprops != NULL && error == 0) { (void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_RECEIVED, - delayprops, errors); + recv_delayprops, *errors); + } + if (local_delayprops != NULL && error == 0) { + (void) zfs_set_prop_nvlist(tofs, ZPROP_SRC_LOCAL, + local_delayprops, *errors); } - } - - if (delayprops != NULL && props != NULL) { - /* - * Merge delayed props back in with initial props, in case - * we're DEBUG and zfs_ioc_recv_inject_err is set (which means - * we have to make sure clear_received_props() includes - * the delayed properties). - * - * Since zfs_ioc_recv_inject_err is only in DEBUG kernels, - * using ASSERT() will be just like a VERIFY. - */ - ASSERT(nvlist_merge(props, delayprops, 0) == 0); - nvlist_free(delayprops); } /* - * Now that all props, initial and delayed, are set, report the prop - * errors to the caller. + * Merge delayed props back in with initial props, in case + * we're DEBUG and zfs_ioc_recv_inject_err is set (which means + * we have to make sure clear_received_props() includes + * the delayed properties). + * + * Since zfs_ioc_recv_inject_err is only in DEBUG kernels, + * using ASSERT() will be just like a VERIFY. */ - if (zc->zc_nvlist_dst_size != 0 && - (nvlist_smush(errors, zc->zc_nvlist_dst_size) != 0 || - put_nvlist(zc, errors) != 0)) { - /* - * Caller made zc->zc_nvlist_dst less than the minimum expected - * size or supplied an invalid address. - */ - props_error = SET_ERROR(EINVAL); + if (recv_delayprops != NULL) { + ASSERT(nvlist_merge(recvprops, recv_delayprops, 0) == 0); + nvlist_free(recv_delayprops); + } + if (local_delayprops != NULL) { + ASSERT(nvlist_merge(localprops, local_delayprops, 0) == 0); + nvlist_free(local_delayprops); } - zc->zc_cookie = off - fp->f_offset; - if (VOP_SEEK(fp->f_vnode, fp->f_offset, &off, NULL) == 0) - fp->f_offset = off; + *read_bytes = off - input_fp->f_offset; + if (VOP_SEEK(input_fp->f_vnode, input_fp->f_offset, &off, NULL) == 0) + input_fp->f_offset = off; #ifdef DEBUG if (zfs_ioc_recv_inject_err) { @@ -4659,48 +4709,103 @@ zfs_ioc_recv(zfs_cmd_t *zc) error = 1; } #endif + /* * On error, restore the original props. */ - if (error != 0 && props != NULL && !drc.drc_newfs) { - if (clear_received_props(tofs, props, NULL) != 0) { + if (error != 0 && recvprops != NULL && !drc.drc_newfs) { + if (clear_received_props(tofs, recvprops, NULL) != 0) { /* * We failed to clear the received properties. * Since we may have left a $recvd value on the * system, we can't clear the $hasrecvd flag. */ - zc->zc_obj |= ZPROP_ERR_NORESTORE; + *errflags |= ZPROP_ERR_NORESTORE; } else if (first_recvd_props) { dsl_prop_unset_hasrecvd(tofs); } - if (origprops == NULL && !drc.drc_newfs) { + if (origrecvd == NULL && !drc.drc_newfs) { /* We failed to stash the original properties. */ - zc->zc_obj |= ZPROP_ERR_NORESTORE; + *errflags |= ZPROP_ERR_NORESTORE; } /* * dsl_props_set() will not convert RECEIVED to LOCAL on or * after SPA_VERSION_RECVD_PROPS, so we need to specify LOCAL - * explictly if we're restoring local properties cleared in the + * explicitly if we're restoring local properties cleared in the * first new-style receive. */ - if (origprops != NULL && + if (origrecvd != NULL && zfs_set_prop_nvlist(tofs, (first_recvd_props ? ZPROP_SRC_LOCAL : ZPROP_SRC_RECEIVED), - origprops, NULL) != 0) { + origrecvd, NULL) != 0) { /* * We stashed the original properties but failed to * restore them. */ - zc->zc_obj |= ZPROP_ERR_NORESTORE; + *errflags |= ZPROP_ERR_NORESTORE; } } + if (error != 0 && localprops != NULL && !drc.drc_newfs && + !first_recvd_props) { + nvlist_t *setprops; + nvlist_t *inheritprops; + nvpair_t *nvp; + + if (origprops == NULL) { + /* We failed to stash the original properties. */ + *errflags |= ZPROP_ERR_NORESTORE; + goto out; + } + + /* Restore original props */ + setprops = fnvlist_alloc(); + inheritprops = fnvlist_alloc(); + nvp = NULL; + while ((nvp = nvlist_next_nvpair(localprops, nvp)) != NULL) { + const char *name = nvpair_name(nvp); + const char *source; + nvlist_t *attrs; + + if (!nvlist_exists(origprops, name)) { + /* + * Property was not present or was explicitly + * inherited before the receive, restore this. + */ + fnvlist_add_boolean(inheritprops, name); + continue; + } + attrs = fnvlist_lookup_nvlist(origprops, name); + source = fnvlist_lookup_string(attrs, ZPROP_SOURCE); + + /* Skip received properties */ + if (strcmp(source, ZPROP_SOURCE_VAL_RECVD) == 0) + continue; + + if (strcmp(source, tofs) == 0) { + /* Property was locally set */ + fnvlist_add_nvlist(setprops, name, attrs); + } else { + /* Property was implicitly inherited */ + fnvlist_add_boolean(inheritprops, name); + } + } + + if (zfs_set_prop_nvlist(tofs, ZPROP_SRC_LOCAL, setprops, + NULL) != 0) + *errflags |= ZPROP_ERR_NORESTORE; + if (zfs_set_prop_nvlist(tofs, ZPROP_SRC_INHERITED, inheritprops, + NULL) != 0) + *errflags |= ZPROP_ERR_NORESTORE; + + nvlist_free(setprops); + nvlist_free(inheritprops); + } out: - nvlist_free(props); + releasef(input_fd); + nvlist_free(origrecvd); nvlist_free(origprops); - nvlist_free(errors); - releasef(fd); if (error == 0) error = props_error; @@ -4710,6 +4815,97 @@ out: /* * inputs: + * zc_name name of containing filesystem + * zc_nvlist_src{_size} nvlist of received properties to apply + * zc_nvlist_conf{_size} nvlist of local properties to apply + * zc_history_offset{_len} nvlist of hidden args { "wkeydata" -> value } + * zc_value name of snapshot to create + * zc_string name of clone origin (if DRR_FLAG_CLONE) + * zc_cookie file descriptor to recv from + * zc_begin_record the BEGIN record of the stream (not byteswapped) + * zc_guid force flag + * zc_cleanup_fd cleanup-on-exit file descriptor + * zc_action_handle handle for this guid/ds mapping (or zero on first call) + * zc_resumable if data is incomplete assume sender will resume + * + * outputs: + * zc_cookie number of bytes read + * zc_nvlist_dst{_size} error for each unapplied received property + * zc_obj zprop_errflags_t + * zc_action_handle handle for this guid/ds mapping + */ +static int +zfs_ioc_recv(zfs_cmd_t *zc) +{ + dmu_replay_record_t begin_record; + nvlist_t *errors = NULL; + nvlist_t *recvdprops = NULL; + nvlist_t *localprops = NULL; + nvlist_t *hidden_args = NULL; + char *origin = NULL; + char *tosnap; + char tofs[ZFS_MAX_DATASET_NAME_LEN]; + int error = 0; + + if (dataset_namecheck(zc->zc_value, NULL, NULL) != 0 || + strchr(zc->zc_value, '@') == NULL || + strchr(zc->zc_value, '%')) + return (SET_ERROR(EINVAL)); + + (void) strlcpy(tofs, zc->zc_value, sizeof (tofs)); + tosnap = strchr(tofs, '@'); + *tosnap++ = '\0'; + + if (zc->zc_nvlist_src != 0 && + (error = get_nvlist(zc->zc_nvlist_src, zc->zc_nvlist_src_size, + zc->zc_iflags, &recvdprops)) != 0) + return (error); + + if (zc->zc_nvlist_conf != 0 && + (error = get_nvlist(zc->zc_nvlist_conf, zc->zc_nvlist_conf_size, + zc->zc_iflags, &localprops)) != 0) + return (error); + + if (zc->zc_history_offset != 0 && + (error = get_nvlist(zc->zc_history_offset, zc->zc_history_len, + zc->zc_iflags, &hidden_args)) != 0) + return (error); + + if (zc->zc_string[0]) + origin = zc->zc_string; + + begin_record.drr_type = DRR_BEGIN; + begin_record.drr_payloadlen = zc->zc_begin_record.drr_payloadlen; + begin_record.drr_u.drr_begin = zc->zc_begin_record.drr_u.drr_begin; + + error = zfs_ioc_recv_impl(tofs, tosnap, origin, recvdprops, localprops, + hidden_args, zc->zc_guid, zc->zc_resumable, zc->zc_cookie, + &begin_record, zc->zc_cleanup_fd, &zc->zc_cookie, &zc->zc_obj, + &zc->zc_action_handle, &errors); + nvlist_free(recvdprops); + nvlist_free(localprops); + + /* + * Now that all props, initial and delayed, are set, report the prop + * errors to the caller. + */ + if (zc->zc_nvlist_dst_size != 0 && errors != NULL && + (nvlist_smush(errors, zc->zc_nvlist_dst_size) != 0 || + put_nvlist(zc, errors) != 0)) { + /* + * Caller made zc->zc_nvlist_dst less than the minimum expected + * size or supplied an invalid address. + */ + error = SET_ERROR(EINVAL); + } + + nvlist_free(errors); + + return (error); +} + +/* + * inputs: * zc_name name of snapshot to send * zc_cookie file descriptor to send stream to * zc_obj fromorigin flag (mutually exclusive with zc_fromobj) |