From b5bca9f50c365ce8feced3fc20299796fe29c14d Mon Sep 17 00:00:00 2001 From: Steven Hartland Date: Thu, 14 Nov 2013 09:37:37 -0800 Subject: [PATCH] 1867: behlendorf/issue-1350 - Adds support for property overrides (WIP) Adds support for property overrides (-o property=value), property excludes (-x property) and dataset limits (-l ) to zfs receive. Both -o and -x options mirror the functionality already available in Oracle's ZFS implementation which is also mentioned in the upstream feature request #2745: The -l option allows receive to be limited to specific datasets within the stream effectively allowing partial restores from a multi dataset stream. References: https://www.illumos.org/issues/2745 Ported-by: Milan-Benes Issue #1350 --- cmd/zfs/zfs_main.c | 66 ++++++++- include/libzfs.h | 2 +- include/libzfs_impl.h | 2 + lib/libzfs/libzfs_dataset.c | 2 +- lib/libzfs/libzfs_sendrecv.c | 270 +++++++++++++++++++++++++++++++---- man/man8/zfs.8 | 86 +++++++++++ 6 files changed, 392 insertions(+), 36 deletions(-) diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c index 8eb209152b47..88f9e6022cf4 100644 --- a/cmd/zfs/zfs_main.c +++ b/cmd/zfs/zfs_main.c @@ -249,7 +249,10 @@ get_usage(zfs_help_t idx) case HELP_RECEIVE: return (gettext("\treceive [-vnFus] \n" - "\treceive [-vnFus] [-d | -e] \n")); + "\treceive [-vnFus] [-d | -e] \n" + "\treceive [-vnFus] [-d | -e] [-o ] " + "... [-x ] ... [-l ] ... " + " \n")); case HELP_RENAME: return (gettext("\trename [-f] " "\n" @@ -478,6 +481,21 @@ usage(boolean_t requested) exit(requested ? 0 : 2); } +static int +parsepropname(nvlist_t *props) +{ + char *propname = optarg; + + if (nvlist_lookup_string(props, propname, NULL) == 0) { + (void) fprintf(stderr, gettext("property '%s' " + "specified multiple times\n"), propname); + return (-1); + } + if (nvlist_add_boolean(props, propname)) + nomem(); + return (0); +} + static int parseprop(nvlist_t *props) { @@ -3857,18 +3875,24 @@ zfs_do_send(int argc, char **argv) } /* - * zfs receive [-vnFus] [-d | -e] + * zfs receive [-vnFus] [-d | -e] [-l ] ... + * [-o property=value] ... [-x property] ... * * Restore a backup stream from stdin. */ static int zfs_do_receive(int argc, char **argv) { + nvlist_t *props, *limitds; int c, err; recvflags_t flags = { 0 }; + if (nvlist_alloc(&props, NV_UNIQUE_NAME, 0) != 0) + nomem(); + if (nvlist_alloc(&limitds, NV_UNIQUE_NAME, 0) != 0) + nomem(); /* check options */ - while ((c = getopt(argc, argv, ":denuvFs")) != -1) { + while ((c = getopt(argc, argv, ":del:no:uvx:Fs")) != -1) { switch (c) { case 's': flags.skip = B_TRUE; @@ -3880,15 +3904,32 @@ zfs_do_receive(int argc, char **argv) flags.isprefix = B_TRUE; flags.istail = B_TRUE; break; + case 'l': + if (parsepropname(limitds)) { + err = 1; + goto recverror; + } + break; case 'n': flags.dryrun = B_TRUE; break; + case 'o': + if (parseprop(props)) { + err = 1; + goto recverror; + } + break; case 'u': flags.nomount = B_TRUE; break; case 'v': flags.verbose = B_TRUE; break; + case 'x': + if (parsepropname(props)) { + err = 1; + goto recverror; + } case 'F': flags.force = B_TRUE; break; @@ -3925,7 +3966,24 @@ zfs_do_receive(int argc, char **argv) return (1); } - err = zfs_receive(g_zfs, argv[0], &flags, STDIN_FILENO, NULL); + if (nvlist_empty(props)) { + nvlist_free(props); + props = NULL; + } + + if (nvlist_empty(limitds)) { + nvlist_free(limitds); + limitds = NULL; + } + + err = zfs_receive(g_zfs, argv[0], &flags, STDIN_FILENO, props, limitds, NULL); + +recverror: + if (props != NULL) + nvlist_free(props); + + if (limitds != NULL) + nvlist_free(limitds); return (err != 0); } diff --git a/include/libzfs.h b/include/libzfs.h index f2f41e821455..df91ba78c541 100644 --- a/include/libzfs.h +++ b/include/libzfs.h @@ -684,7 +684,7 @@ typedef struct recvflags { } recvflags_t; extern int zfs_receive(libzfs_handle_t *, const char *, recvflags_t *, - int, avl_tree_t *); + int, nvlist_t *, nvlist_t *, avl_tree_t *); typedef enum diff_flags { ZFS_DIFF_PARSEABLE = 0x1, diff --git a/include/libzfs_impl.h b/include/libzfs_impl.h index be132f1b905f..db351670e63f 100644 --- a/include/libzfs_impl.h +++ b/include/libzfs_impl.h @@ -189,6 +189,8 @@ int changelist_haszonedchild(prop_changelist_t *); void remove_mountpoint(zfs_handle_t *); int create_parents(libzfs_handle_t *, char *, int); +int check_parents(libzfs_handle_t *hdl, const char *path, uint64_t *zoned, + boolean_t accept_ancestor, int *prefixlen); boolean_t isa_child_of(const char *dataset, const char *parent); zfs_handle_t *make_dataset_handle(libzfs_handle_t *, const char *); diff --git a/lib/libzfs/libzfs_dataset.c b/lib/libzfs/libzfs_dataset.c index 4afa84e33e14..23baeed671c8 100644 --- a/lib/libzfs/libzfs_dataset.c +++ b/lib/libzfs/libzfs_dataset.c @@ -2915,7 +2915,7 @@ parent_name(const char *path, char *buf, size_t buflen) * 'zoned' property, which is used to validate property settings when creating * new datasets. */ -static int +int check_parents(libzfs_handle_t *hdl, const char *path, uint64_t *zoned, boolean_t accept_ancestor, int *prefixlen) { diff --git a/lib/libzfs/libzfs_sendrecv.c b/lib/libzfs/libzfs_sendrecv.c index 4dc3aa613bd1..4a9f887379a0 100644 --- a/lib/libzfs/libzfs_sendrecv.c +++ b/lib/libzfs/libzfs_sendrecv.c @@ -64,7 +64,8 @@ extern void zfs_setprop_error(libzfs_handle_t *, zfs_prop_t, int, char *); static int zfs_receive_impl(libzfs_handle_t *, const char *, recvflags_t *, - int, const char *, nvlist_t *, avl_tree_t *, char **, int, uint64_t *); + int, nvlist_t *, nvlist_t *, const char *, nvlist_t *, avl_tree_t *, + char **, int, uint64_t *); static const zio_cksum_t zero_cksum = { { 0 } }; @@ -2049,7 +2050,7 @@ created_before(libzfs_handle_t *hdl, avl_tree_t *avl, static int recv_incremental_replication(libzfs_handle_t *hdl, const char *tofs, recvflags_t *flags, nvlist_t *stream_nv, avl_tree_t *stream_avl, - nvlist_t *renamed) + nvlist_t *renamed, nvlist_t *limitds) { nvlist_t *local_nv, *deleted = NULL; avl_tree_t *local_avl; @@ -2101,6 +2102,12 @@ recv_incremental_replication(libzfs_handle_t *hdl, const char *tofs, &parent_fromsnap_guid)); (void) nvlist_lookup_uint64(nvfs, "origin", &originguid); + if (limitds && !nvlist_exists(limitds, fsname)) { + if (flags->verbose) + (void) printf("skipping replication of %s\n", fsname); + continue; + } + /* * First find the stream's fs, so we can check for * a different origin (due to "zfs promote") @@ -2360,8 +2367,9 @@ recv_incremental_replication(libzfs_handle_t *hdl, const char *tofs, static int zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname, - recvflags_t *flags, dmu_replay_record_t *drr, zio_cksum_t *zc, - char **top_zfs, int cleanup_fd, uint64_t *action_handlep) + recvflags_t *flags, nvlist_t *props, nvlist_t *limitds, + dmu_replay_record_t *drr, zio_cksum_t *zc, char **top_zfs, int cleanup_fd, + uint64_t *action_handlep) { nvlist_t *stream_nv = NULL; avl_tree_t *stream_avl = NULL; @@ -2481,7 +2489,7 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname, } softerr = recv_incremental_replication(hdl, tofs, flags, - stream_nv, stream_avl, renamed); + stream_nv, stream_avl, renamed, limitds); /* Unmount renamed filesystems before receiving. */ while ((pair = nvlist_next_nvpair(renamed, @@ -2527,8 +2535,8 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname, * recv_skip() and return 0). */ error = zfs_receive_impl(hdl, destname, flags, fd, - sendfs, stream_nv, stream_avl, top_zfs, cleanup_fd, - action_handlep); + props, limitds, sendfs, stream_nv, stream_avl, top_zfs, + cleanup_fd, action_handlep); if (error == ENODATA) { error = 0; break; @@ -2542,7 +2550,7 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname, * renames again. */ softerr = recv_incremental_replication(hdl, tofs, flags, - stream_nv, stream_avl, NULL); + stream_nv, stream_avl, NULL, limitds); } out: @@ -2654,21 +2662,182 @@ recv_skip(libzfs_handle_t *hdl, int fd, boolean_t byteswap) return (-1); } +/* + * Calculate a list of properties for the current dataset taking into account + * its current properties (props) and the external (exprops) + */ +static int +props_override(char *dsname, nvlist_t *props, nvlist_t *exprops, + nvlist_t **npropsp, recvflags_t *flags, libzfs_handle_t *hdl, + zfs_type_t type, uint64_t zoned, zfs_handle_t *zhp, + const char *errbuf) +{ + nvlist_t *doprops, *goprops, *dxprops, *gxprops, *nprops, *vprops; + nvpair_t *pair; + char *strval; + int ret = 0; + + if (nvlist_empty(props) || nvlist_empty(exprops)) + return (0); /* No properties */ + + if (nvlist_dup(props, &nprops, 0) != 0) + return (-1); + + VERIFY(nvlist_alloc(&doprops, NV_UNIQUE_NAME, 0) == 0); + VERIFY(nvlist_alloc(&goprops, NV_UNIQUE_NAME, 0) == 0); + VERIFY(nvlist_alloc(&dxprops, NV_UNIQUE_NAME, 0) == 0); + VERIFY(nvlist_alloc(&gxprops, NV_UNIQUE_NAME, 0) == 0); + + /* build lists to process in order */ + pair = nvlist_next_nvpair(exprops, NULL); + while (pair != NULL) { + const char *propname = nvpair_name(pair); + char *tods = strchr(propname, '@'); + if (tods == NULL) { + switch(nvpair_type(pair)) + { + case DATA_TYPE_BOOLEAN: + (void) nvlist_add_nvpair(gxprops, pair); + break; + case DATA_TYPE_STRING: + (void) nvlist_add_nvpair(goprops, pair); + break; + default: + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "'%s' must be a string or boolean"), + propname); + (void) zfs_error(hdl, EZFS_BADPROP, errbuf); + ret = -1; + goto error; + } + } else if (strcmp(dsname, tods+1) == 0) { + /* dataset specific property */ + *tods = '\0'; + switch(nvpair_type(pair)) + { + case DATA_TYPE_BOOLEAN: + (void) nvlist_add_boolean(dxprops, propname); + break; + case DATA_TYPE_STRING: + nvpair_value_string(pair, &strval); + (void) nvlist_add_string(doprops, propname, + strval); + break; + default: + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "'%s' must be a string or boolean"), + propname); + (void) zfs_error(hdl, EZFS_BADPROP, errbuf); + ret = -1; + goto error; + } + *tods = '@'; + } + pair = nvlist_next_nvpair(exprops, pair); + } + /* convert override properties e.g. strings to native */ + if ((vprops = zfs_valid_proplist(hdl, type, goprops, zoned, zhp, + errbuf)) == NULL) + goto error; + + nvlist_free(goprops); + goprops = vprops; + + if ((vprops = zfs_valid_proplist(hdl, type, doprops, zoned, zhp, + errbuf)) == NULL) + goto error; + + nvlist_free(doprops); + doprops = vprops; + + /* global - override / set properties */ + pair = nvlist_next_nvpair(goprops, NULL); + while (pair != NULL) { + const char *pname = nvpair_name(pair); + if (!nvlist_exists(gxprops, pname) && + !nvlist_exists(dxprops, pname)) { + if (flags->verbose) + (void) printf("%s %s property from %s\n", + nvlist_exists(nprops, pname) ? + "overriding" : "setting", pname, dsname); + (void) nvlist_add_nvpair(nprops, pair); + } + pair = nvlist_next_nvpair(goprops, pair); + } + + /* global - exclude properties */ + pair = nvlist_next_nvpair(gxprops, NULL); + while (pair != NULL) { + const char *pname = nvpair_name(pair); + if (!nvlist_exists(doprops, pname)) { + if (flags->verbose && nvlist_exists(nprops, pname)) + (void) printf("excluding %s property from %s\n", + pname, dsname); + + (void) nvlist_remove_all(nprops, pname); + } + pair = nvlist_next_nvpair(gxprops, pair); + } + + /* dataset - override / set properties */ + pair = nvlist_next_nvpair(doprops, NULL); + while (pair != NULL) { + const char *pname = nvpair_name(pair); + if (!nvlist_exists(dxprops, pname)) { + if (flags->verbose) + (void) printf("%s %s property from %s\n", + nvlist_exists(nprops, pname) ? + "overriding" : "setting", pname, dsname); + (void) nvlist_add_nvpair(nprops, pair); + } + + pair = nvlist_next_nvpair(doprops, pair); + + } + + /* dataset - exclude properties */ + pair = nvlist_next_nvpair(dxprops, NULL); + while (pair != NULL) { + const char *pname = nvpair_name(pair); + if (nvlist_exists(nprops, pname)) { + if (flags->verbose) + (void) printf("excluding %s property from %s\n", + pname, dsname); + + (void) nvlist_remove_all(nprops, pname); + } + + pair = nvlist_next_nvpair(dxprops, pair); + } + + *npropsp = nprops; + +error: + if (0 != ret) + nvlist_free(nprops); + nvlist_free(goprops); + nvlist_free(gxprops); + nvlist_free(doprops); + nvlist_free(dxprops); + return (ret); +} + /* * Restores a backup of tosnap from the file descriptor specified by infd. */ static int zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, - recvflags_t *flags, dmu_replay_record_t *drr, - dmu_replay_record_t *drr_noswap, const char *sendfs, - nvlist_t *stream_nv, avl_tree_t *stream_avl, char **top_zfs, int cleanup_fd, - uint64_t *action_handlep) + recvflags_t *flags, nvlist_t *exprops, nvlist_t *limitds, + dmu_replay_record_t *drr, dmu_replay_record_t *drr_noswap, + const char *sendfs, nvlist_t *stream_nv, avl_tree_t *stream_avl, + char **top_zfs, int cleanup_fd, uint64_t *action_handlep) { zfs_cmd_t zc = {"\0"}; time_t begin_time; - int ioctl_err, ioctl_errno, err; + int ioctl_err, ioctl_errno, err, skip; char *cp; struct drr_begin *drrb = &drr->drr_u.drr_begin; + char dsname[ZFS_MAXNAMELEN]; char errbuf[1024]; char prop_errbuf[1024]; const char *chopprefix; @@ -2677,8 +2846,10 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, uint64_t parent_snapguid = 0; prop_changelist_t *clp = NULL; nvlist_t *snapprops_nvlist = NULL; + nvlist_t *props = NULL; + nvlist_t *nprops = NULL; zprop_errflags_t prop_errflags; - boolean_t recursive; + boolean_t recursive, has_exprops; begin_time = time(NULL); @@ -2690,10 +2861,9 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, if (stream_avl != NULL) { char *snapname; + nvlist_t *snapprops; nvlist_t *fs = fsavl_find(stream_avl, drrb->drr_toguid, &snapname); - nvlist_t *props; - int ret; (void) nvlist_lookup_uint64(fs, "parentfromsnap", &parent_snapguid); @@ -2705,11 +2875,16 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, VERIFY(0 == nvlist_add_uint64(props, zfs_prop_to_name(ZFS_PROP_CANMOUNT), 0)); } - ret = zcmd_write_src_nvlist(hdl, &zc, props); - if (err) + + if (err) { nvlist_free(props); - if (ret != 0) - return (-1); + props = NULL; + } + + if (0 == nvlist_lookup_nvlist(fs, "snapprops", &snapprops)) { + VERIFY(0 == nvlist_lookup_nvlist(snapprops, + snapname, &snapprops_nvlist)); + } } cp = NULL; @@ -2870,6 +3045,11 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, (void) strcpy(zc.zc_name, zc.zc_value); *strchr(zc.zc_name, '@') = '\0'; + (void) strcpy(dsname, drrb->drr_toname); + *strchr(dsname, '@') = '\0'; + + has_exprops = !nvlist_empty(exprops); + if (zfs_dataset_exists(hdl, zc.zc_name, ZFS_TYPE_DATASET)) { zfs_handle_t *zhp; @@ -2964,6 +3144,23 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, } newfs = B_TRUE; + if (has_exprops) { + /* Create an override set of properties if needed */ + uint64_t zoned = 0; + if (flags->isprefix && !flags->istail && !flags->dryrun) { + /* Check if we're zoned or not */ + if (check_parents(hdl, zc.zc_value, &zoned, B_FALSE, NULL) != 0) { + zcmd_free_nvlists(&zc); + return (-1); + } + } + + if (props_override(dsname, props, exprops, &nprops, flags, + hdl, ZFS_TYPE_DATASET, zoned, NULL, errbuf) != 0) { + zcmd_free_nvlists(&zc); + return (-1); + } + } } zc.zc_begin_record = drr_noswap->drr_u.drr_begin; @@ -2980,15 +3177,17 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, return (recv_skip(hdl, infd, flags->byteswap)); } + skip = limitds && !nvlist_exists(limitds, dsname); if (flags->verbose) { (void) printf("%s %s stream of %s into %s\n", - flags->dryrun ? "would receive" : "receiving", + skip ? (flags->dryrun ? "would skip" : "skipping") : + (flags->dryrun ? "would receive" : "receiving"), drrb->drr_fromguid ? "incremental" : "full", drrb->drr_toname, zc.zc_value); (void) fflush(stdout); } - if (flags->dryrun) { + if (flags->dryrun || skip) { zcmd_free_nvlists(&zc); return (recv_skip(hdl, infd, flags->byteswap)); } @@ -2998,6 +3197,15 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, zc.zc_cleanup_fd = cleanup_fd; zc.zc_action_handle = *action_handlep; + if (nprops) { + if (zcmd_write_src_nvlist(hdl, &zc, nprops) != 0) { + nvlist_free(nprops); + return (-1); + } + nvlist_free(nprops); + } else if (props && zcmd_write_src_nvlist(hdl, &zc, props) != 0) + return (-1); + err = ioctl_err = zfs_ioctl(hdl, ZFS_IOC_RECV, &zc); ioctl_errno = errno; prop_errflags = (zprop_errflags_t)zc.zc_obj; @@ -3210,8 +3418,9 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, static int zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap, recvflags_t *flags, - int infd, const char *sendfs, nvlist_t *stream_nv, avl_tree_t *stream_avl, - char **top_zfs, int cleanup_fd, uint64_t *action_handlep) + int infd, nvlist_t *props, nvlist_t *limitds, const char *sendfs, + nvlist_t *stream_nv, avl_tree_t *stream_avl, char **top_zfs, int cleanup_fd, + uint64_t *action_handlep) { int err; dmu_replay_record_t drr, drr_noswap; @@ -3303,13 +3512,14 @@ zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap, recvflags_t *flags, sendfs = nonpackage_sendfs; } return (zfs_receive_one(hdl, infd, tosnap, flags, - &drr, &drr_noswap, sendfs, stream_nv, stream_avl, - top_zfs, cleanup_fd, action_handlep)); + props, limitds, &drr, &drr_noswap, sendfs, stream_nv, + stream_avl, top_zfs, cleanup_fd, action_handlep)); } else { assert(DMU_GET_STREAM_HDRTYPE(drrb->drr_versioninfo) == DMU_COMPOUNDSTREAM); return (zfs_receive_package(hdl, infd, tosnap, flags, - &drr, &zcksum, top_zfs, cleanup_fd, action_handlep)); + props, limitds, &drr, &zcksum, top_zfs, cleanup_fd, + action_handlep)); } } @@ -3321,7 +3531,7 @@ zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap, recvflags_t *flags, */ int zfs_receive(libzfs_handle_t *hdl, const char *tosnap, recvflags_t *flags, - int infd, avl_tree_t *stream_avl) + int infd, nvlist_t *props, nvlist_t *limitds, avl_tree_t *stream_avl) { char *top_zfs = NULL; int err; @@ -3373,8 +3583,8 @@ zfs_receive(libzfs_handle_t *hdl, const char *tosnap, recvflags_t *flags, cleanup_fd = open(ZFS_DEV, O_RDWR); VERIFY(cleanup_fd >= 0); - err = zfs_receive_impl(hdl, tosnap, flags, infd, NULL, NULL, - stream_avl, &top_zfs, cleanup_fd, &action_handle); + err = zfs_receive_impl(hdl, tosnap, flags, infd, props, limitds, NULL, + NULL, stream_avl, &top_zfs, cleanup_fd, &action_handle); VERIFY(0 == close(cleanup_fd)); diff --git a/man/man8/zfs.8 b/man/man8/zfs.8 index 9dc29c3a2f32..a8041909bf65 100644 --- a/man/man8/zfs.8 +++ b/man/man8/zfs.8 @@ -187,6 +187,11 @@ zfs \- configures ZFS file systems \fBzfs\fR \fBreceive | recv\fR [\fB-vnFus\fR] \fIfilesystem\fR|\fIvolume\fR|\fIsnapshot\fR .fi +.LP +.nf +\fBzfs\fR \fBreceive | recv\fR [\fB-vnFus\fR] [\fB-d\fR|\fB-e\fR] [\fB-o\fR \fIproperty\fR] [\fB-x\fR \fIproperty\fR] [\fB-l\fR \fIfilesystem\fR|\fIvolume\fR] \fIfilesystem\fR|\fIvolume\fR|\fIsnapshot\fR +.fi + .LP .nf \fBzfs\fR \fBreceive | recv\fR [\fB-vnFus\fR] [\fB-d\fR|\fB-e\fR] \fIfilesystem\fR @@ -3182,6 +3187,10 @@ then the receiving system must have that feature enabled as well. See .na \fB\fBzfs receive\fR [\fB-vnFus\fR] [\fB-d\fR|\fB-e\fR] \fIfilesystem\fR\fR .ad +.br +.na +\fBzfs\fR \fBreceive\fR [\fB-vnFus\fR] [\fB-d\fR|\fB-e\fR] [\fB-o\fR \fIproperty\fR] [\fB-x\fR \fIproperty\fR] [\fB-l\fR \fIfilesystem\fR|\fIvolume\fR] \fIfilesystem\fR|\fIvolume\fR|\fIsnapshot\fR +.ad .sp .6 .RS 4n Creates a snapshot whose contents are as specified in the stream provided on standard input. If a full stream is received, then a new file system is created as well. Streams are created using the \fBzfs send\fR subcommand, which by default creates a full stream. \fBzfs recv\fR can be used as an alias for \fBzfs receive\fR. @@ -3216,6 +3225,50 @@ Discard the first element of the sent snapshot's file system name, using the rem 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. .RE +.sp +.ne 2 +.na +\fB\fB-l\fR\fR \fIfilesystem\fR|\fIvolume\fR +.ad +.sp .6 +.RS 4n +Limits the the recieve to only the \fIfilesystem\fR or \fIvolume\fR specified. As multiple \fB-l\fR options may be specified, this can be used to restore specific filesystems or volumes from the received stream. +.RE + +.sp +.ne 2 +.mk +.na +\fB-o\fR \fIproperty\fR=\fIvalue\fR +.ad +.sp .6 +.RS 4n +Sets the specified property as if the command \fIzfs set property=value\fR is invoked at the same time the received dataset is created from the non-incremental send stream or updated from the incremental send stream.. +.sp +Any editable ZFS property can also be set at receive time. Set-once properties bound to the received data, such as normalization and casesensitivity, cannot be set at receive time even when the datasets are newly created by zfs receive. +.sp +Multiple \fB-o\fR options can be specified. +.sp +The \fIproperty\fR option may take one of two forms +.RS +4 +.TP +.ie t \(bu +.el o +\fIproperty\fR - Global property applied to all streams. +.RE +.RS +4 +.TP +.ie t \(bu +.el o +\fIproperty\fR @ \fIvolume\fR | \fIfilesystem\fR - Local property applied to the specified \fIvolume\fR or \fIfilesystem\fR streams only. +.RE +.sp +The most specific \fB-o\fR option takes precedence so in the case where both a global \fIproperty\fR and a \fIproperty\fR @ \fIfilesystem\fR | \fIvolume\fR are specified for the same \fIproperty\fR the value of said \fIproperty\fR will be the one which most closely matches the domain of the \fIproperty\fR. +.sp +both \fB-o\fR and \fB-x\fR are specified for the same \fIproperty\fR the \fB-x\fR option takes precedence unless the \fB-o\fR option is a better domain match than the \fB-x\fR option. +.br +.RE + .sp .ne 2 .mk @@ -3248,6 +3301,39 @@ Print verbose information about the stream and the time required to perform the Do not actually receive the stream. This can be useful in conjunction with the \fB-v\fR option to verify the name the receive operation would use. .RE +.sp +.ne 2 +.mk +.na +\fB\fB-x\fR\fR \fIproperty\fR=\fIvalue\fR +.ad +.sp .6 +.RS 4n +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. +.sp +If the specified property is not present in the send stream, this option does nothing. +.sp +If a received property needs to be overridden, the effective value can be set or inherited, depending on the property. +.sp +In the case of an incremental update, \fB-x\fR leaves any existing local setting or explicit inheritance unchanged (since the received property is already overridden). +.sp +The \fIproperty\fR option may take one of two forms +.RS +4 +.TP +.ie t \(bu +.el o +\fIproperty\fR - Global property excluded from all streams. +.RE +.RS +4 +.TP +.ie t \(bu +.el o +\fIproperty\fR @ \fIvolume\fR | \fIfilesystem\fR - Local property excluded from the specified \fIvolume\fR or \fIfilesystem\fR streams only. +.RE +.sp +All \fB-o\fR restrictions apply equally to \fB-x\fR. +.RE + .sp .ne 2 .mk