Date: Fri, 26 Feb 2016 12:11:13 +0000 From: Steven Hartland <killing@multiplay.co.uk> To: freebsd-fs@freebsd.org Subject: Re: ZFS receive and attributes, proposing some changes. was Re: ZFS full system backup hoses the backup host. Message-ID: <56D040E1.3090000@multiplay.co.uk> In-Reply-To: <C3EAA8F3-8EFA-450E-A4A3-A790EA2DA396@sarenet.es> References: <CACpH0Me58jK%2BOz3PCqH93NEn=5V1SKwPGdku62sAVLVh%2BWxEeA@mail.gmail.com> <0ae0e3ab-6ad1-4ae2-9ee9-a7d993088a01@email.android.com> <C3EAA8F3-8EFA-450E-A4A3-A790EA2DA396@sarenet.es>
index | next in thread | previous in thread | raw e-mail
[-- Attachment #1 --] I actually have a patch (see attached) which does this but never got round to finishing the RTI so its got some rough edges. On 26/02/2016 11:07, Borja Marcos wrote: >> On 26 Feb 2016, at 05:56, Karli Sjöberg <karli.sjoberg@slu.se> wrote: >> What have other people done to get around this and/or can we either put in >> an "ignore properties" on receive flag or a -R on send that doesn't send >> them? > (I removed freebsd-hackers as this discussion belongs in freebsd-fs) > > > The possibility of replacing options in the receive command would be most useful here. Ideally > it should be done atomically with the receive. > > For example: Imagine a replication system. It would be something like this: > > - Make the first snapshot > > - Send it | receive it with -u (you don’t want it mounted) > > - Set the destination dataset’s canmount property to “noauto" > > - And, periodically > > - Make new snapshot > > - Send it incrementally > > - etc etc > > > However, there is a race condition here. If your replication program/script crashes, or it stops for > whatever reason between the first send and the “set canmount” (or you reboot the destination server) > you end up with a time bomb. Despite being received with the -u flag, if you boot it will be mounted > automatically. If you are replicating the root filesystem (or anything mounted on an important system > directory) you are dead :) > > To avoid this kind of problems, two options come to my mind, from least desirable/useful/consistent > to better (in my opinion) > > 1) Have some kind of force-inherit attribute on a dataset which forces children’s mountpoint to go below it. > > Example: when replicating server1’s datasets, I send them below to pool/server1_copies. If I set > mountpoint=force_child (or something similar) for pool/server1_copies, anything received under it > would have the mountpoint changed, adding the mountpoint of pool/server1_copies to it. > > Imagine I am replicating the /usr filesystem of server1, which is pool_server1/usr, with the > mountpoint /usr. The destination would have the mountpoint /pool/server1_copies/usr. > > This at least avoids the unintended mountpoint overwrite problem, which is not bad. And this possibility > can be useful, but it’s not a very clean solution. > > 2) Adding an option to “zfs receive” so that properties can be changed for the received dataset *atomically*. > > The race condition is avoided if I can specify an option to zfs recv, something like -O canmount=noauto. > > > Actually, there are some changes I would make, not just for this particular case, which will not break > functionality and will make ZFS safer to use and easier to handle, especially when replicating datasets. > > > 1) Enhancing “zfs recv” with the possibility of changing options and holds atomically. > > - Changing options: -O option=value -O option=value > - Adding holds: -h holdname > > When using snapshots to replicate datasets you must be careful not to delete the last replicated snapshot > accidentally, or you can lose the ability to do an incremental send, forcing a full one. Holds help to prevent this. > And if using them it would be very useful to have the last snapshot properly protected in the destination dataset > as well. Again, it should be done ATOMICALLY. > > Same applies to dataset properties, if only for the outright dangerous combination of the canmount/mountpoint > properties. Once more, we want this to be done ATOMICALLY with the zfs recv. > > 2) Adding the possibility of specifying “default” or “inherit” for the mountpoint property. > > When a dataset is created, the default mountpoint is pool/dataset_name unless a different value is specified. > A value of “default” would set the dataset’s mountpoint back to pool/…/dataset_name, and “inherit” could > set the mountpoint to the "parent’s mountpoint”/dataset_name. > > These two options can be very useful when using zfs send/zfs recv to keep replicas as a backup, but I think > they can be useful in other situations. > > > What do you think? In my opinion these additions won’t break functionality and they are worthwhile if only for making > replication safer/more useful. > > Best regards, > > > > > > > > Borja. > > > > > _______________________________________________ > freebsd-fs@freebsd.org mailing list > https://lists.freebsd.org/mailman/listinfo/freebsd-fs > To unsubscribe, send any mail to "freebsd-fs-unsubscribe@freebsd.org" [-- Attachment #2 --] Adds support for property overrides (-o property=value), property excludes (-x property) and dataset limits (-l <volume|filesystem>) 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: https://www.illumos.org/issues/2745 The -l option allows receive to be limited to specific datasets within the stream effectively allowing partial restores from a multi dataset stream. --- cddl/contrib/opensolaris/lib/libzfs/common/libzfs_dataset.c.orig 2014-10-10 09:31:36.000000000 +0000 +++ cddl/contrib/opensolaris/lib/libzfs/common/libzfs_dataset.c 2014-11-13 14:31:10.452670131 +0000 @@ -2874,7 +2874,7 @@ parent_name(const char *path, char *buf, * '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) { --- cddl/contrib/opensolaris/lib/libzfs/common/libzfs_impl.h.orig 2014-10-10 09:31:36.000000000 +0000 +++ cddl/contrib/opensolaris/lib/libzfs/common/libzfs_impl.h 2014-11-13 14:31:10.453669806 +0000 @@ -188,6 +188,8 @@ int changelist_haszonedchild(prop_change 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 *); --- cddl/contrib/opensolaris/lib/libzfs/common/libzfs_sendrecv.c.orig 2014-10-10 09:31:36.000000000 +0000 +++ cddl/contrib/opensolaris/lib/libzfs/common/libzfs_sendrecv.c 2014-11-13 14:31:10.456669408 +0000 @@ -65,7 +65,8 @@ extern void zfs_setprop_error(libzfs_han #define ENODATA EIDRM 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 }; @@ -2024,7 +2025,7 @@ created_before(libzfs_handle_t *hdl, 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; @@ -2076,6 +2077,12 @@ again: &parent_fromsnap_guid)); (void) nvlist_lookup_uint64(nvfs, "origin", &originguid); + if (!nvlist_empty(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") @@ -2332,8 +2339,9 @@ doagain: 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 *exprops, 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; @@ -2453,7 +2461,7 @@ zfs_receive_package(libzfs_handle_t *hdl } 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, @@ -2499,8 +2507,8 @@ zfs_receive_package(libzfs_handle_t *hdl * recv_skip() and return 0). */ error = zfs_receive_impl(hdl, destname, flags, fd, - sendfs, stream_nv, stream_avl, top_zfs, cleanup_fd, - action_handlep); + exprops, limitds, sendfs, stream_nv, stream_avl, top_zfs, + cleanup_fd, action_handlep); if (error == ENODATA) { error = 0; break; @@ -2514,7 +2522,7 @@ zfs_receive_package(libzfs_handle_t *hdl * renames again. */ softerr = recv_incremental_replication(hdl, tofs, flags, - stream_nv, stream_avl, NULL); + stream_nv, stream_avl, NULL, limitds); } out: @@ -2627,30 +2635,198 @@ recv_skip(libzfs_handle_t *hdl, int fd, } /* + * Calculate a list of properties for the current dataset taking into account + * its current properties (props) and the external properties (exprops) + * + * This calculation: + * - Removes excluded properties (booleans) + * - Changes the values of overriden properties (strings) + * + * There are two types of external properties: + * - Global properties + * - Dataset specific properties (identified by # separator) + * + * An example of a dataset specific property would be 'pool/fs#quota' + * + * Dataset specific external properties take precidence over matching global + * properties. + */ +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; + const char sep = '#'; + + 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 */ + for (nvpair_t *pair = nvlist_next_nvpair(exprops, NULL); pair != NULL; + pair = nvlist_next_nvpair(exprops, pair)) { + const char *propname = nvpair_name(pair); + char *sepp = strchr(propname, sep); + if (sepp == NULL) { + switch(nvpair_type(pair)) + { + case DATA_TYPE_BOOLEAN: + VERIFY0(nvlist_add_nvpair(gxprops, pair)); + break; + case DATA_TYPE_STRING: + VERIFY0(nvlist_add_nvpair(goprops, pair)); + break; + default: + (void) fprintf(stderr, dgettext(TEXT_DOMAIN, + "prop '%s' must be a string or boolean"), + propname); + /* should never happen, so assert */ + assert(B_FALSE); + } + } else if (strcmp(dsname, sepp+1) == 0) { + /* dataset specific property */ + *sepp = '\0'; + switch(nvpair_type(pair)) + { + case DATA_TYPE_BOOLEAN: + VERIFY0(nvlist_add_boolean(dxprops, propname)); + break; + case DATA_TYPE_STRING: + nvpair_value_string(pair, &strval); + VERIFY0(nvlist_add_string(doprops, propname, + strval)); + break; + default: + (void) fprintf(stderr, dgettext(TEXT_DOMAIN, + "prop '%s' must be a string or boolean"), + propname); + /* should never happen, so assert */ + assert(B_FALSE); + } + *sepp = sep; + } + } + + /* 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 */ + for (nvpair_t *pair = nvlist_next_nvpair(goprops, NULL); pair != NULL; + pair = nvlist_next_nvpair(goprops, pair)) { + 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); + VERIFY0(nvlist_add_nvpair(nprops, pair)); + } + } + + /* global - exclude properties */ + for (nvpair_t *pair = nvlist_next_nvpair(gxprops, NULL); pair != NULL; + pair = nvlist_next_nvpair(gxprops, pair)) { + 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); + } + } + + /* dataset - override / set properties */ + for (nvpair_t *pair = nvlist_next_nvpair(doprops, NULL); pair != NULL; + pair = nvlist_next_nvpair(doprops, pair)) { + 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); + VERIFY0(nvlist_add_nvpair(nprops, pair)); + } + } + + /* dataset - exclude properties */ + for (nvpair_t *pair = nvlist_next_nvpair(dxprops, NULL); pair != NULL; + pair = nvlist_next_nvpair(dxprops, pair)) { + 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); + } + } + + *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; 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; boolean_t newfs = B_FALSE; - boolean_t stream_wantsnewfs; + boolean_t stream_wantsnewfs, skip; 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); @@ -2662,10 +2838,9 @@ zfs_receive_one(libzfs_handle_t *hdl, in 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); @@ -2677,17 +2852,16 @@ zfs_receive_one(libzfs_handle_t *hdl, in 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); + props = NULL; + } - if (0 == nvlist_lookup_nvlist(fs, "snapprops", &props)) { - VERIFY(0 == nvlist_lookup_nvlist(props, + if (0 == nvlist_lookup_nvlist(fs, "snapprops", &snapprops)) { + VERIFY(0 == nvlist_lookup_nvlist(snapprops, snapname, &snapprops_nvlist)); } - - if (ret != 0) - return (-1); } cp = NULL; @@ -2857,6 +3031,11 @@ zfs_receive_one(libzfs_handle_t *hdl, in (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; @@ -2920,6 +3099,16 @@ zfs_receive_one(libzfs_handle_t *hdl, in return (-1); } } + + /* convert override properties e.g. strings to native */ + if (has_exprops && props_override(dsname, props, exprops, + &nprops, flags, hdl, zhp->zfs_type, + zfs_prop_get_int(zhp, ZFS_PROP_ZONED), zhp, errbuf) != 0) { + zfs_close(zhp); + zcmd_free_nvlists(&zc); + return (-1); + } + zfs_close(zhp); } else { /* @@ -2950,20 +3139,39 @@ zfs_receive_one(libzfs_handle_t *hdl, in } 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; zc.zc_cookie = infd; zc.zc_guid = flags->force; + skip = !nvlist_empty(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)); } @@ -2973,6 +3181,15 @@ zfs_receive_one(libzfs_handle_t *hdl, in 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; @@ -3180,8 +3397,9 @@ zfs_receive_one(libzfs_handle_t *hdl, in 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 *exprops, 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; @@ -3273,13 +3491,14 @@ zfs_receive_impl(libzfs_handle_t *hdl, c 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)); + exprops, 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)); + exprops, limitds, &drr, &zcksum, top_zfs, cleanup_fd, + action_handlep)); } } @@ -3291,7 +3510,7 @@ zfs_receive_impl(libzfs_handle_t *hdl, c */ int zfs_receive(libzfs_handle_t *hdl, const char *tosnap, recvflags_t *flags, - int infd, avl_tree_t *stream_avl) + int infd, nvlist_t *exprops, nvlist_t *limitds, avl_tree_t *stream_avl) { char *top_zfs = NULL; int err; @@ -3301,8 +3520,8 @@ zfs_receive(libzfs_handle_t *hdl, const cleanup_fd = open(ZFS_DEV, O_RDWR|O_EXCL); 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, exprops, limitds, NULL, + NULL, stream_avl, &top_zfs, cleanup_fd, &action_handle); VERIFY(0 == close(cleanup_fd)); --- cddl/contrib/opensolaris/lib/libzfs/common/libzfs.h.orig 2014-10-10 09:31:36.000000000 +0000 +++ cddl/contrib/opensolaris/lib/libzfs/common/libzfs.h 2014-11-13 14:31:10.456669408 +0000 @@ -666,7 +666,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, --- cddl/contrib/opensolaris/cmd/zfs/zfs.8.orig 2014-10-10 09:31:35.000000000 +0000 +++ cddl/contrib/opensolaris/cmd/zfs/zfs.8 2014-11-13 14:34:05.050658083 +0000 @@ -190,10 +190,22 @@ .Nm .Cm receive Ns | Ns Cm recv .Op Fl vnFu +.Op Fl l Ar filesystem Ns | Ns Ar volume +.Ar ... +.Op Fl o Ar property Ns = Ns Ar value +.Ar ... +.Op Fl x Ar property +.Ar ... .Ar filesystem Ns | Ns Ar volume Ns | Ns Ar snapshot .Nm .Cm receive Ns | Ns Cm recv .Op Fl vnFu +.Op Fl l Ar filesystem Ns | Ns Ar volume +.Ar ... +.Op Fl o Ar property=value +.Ar ... +.Op Fl x Ar property +.Ar ... .Op Fl d | e .Ar filesystem .Nm @@ -2650,12 +2662,24 @@ feature. .Nm .Cm receive Ns | Ns Cm recv .Op Fl vnFu +.Op Fl l Ar filesystem Ns | Ns Ar volume +.Ar ... +.Op Fl o Ar property Ns = Ns Ar value +.Ar ... +.Op Fl x Ar property +.Ar ... .Ar filesystem Ns | Ns Ar volume Ns | Ns Ar snapshot .Xc .It Xo .Nm .Cm receive Ns | Ns Cm recv .Op Fl vnFu +.Op Fl l Ar filesystem Ns | Ns Ar volume +.Ar ... +.Op Fl o Ar property Ns = Ns Ar value +.Ar ... +.Op Fl x Ar property +.Ar ... .Op Fl d | e .Ar filesystem .Xc @@ -2747,6 +2771,109 @@ performing the receive operation. If rec stream (for example, one generated by .Qq Nm Cm send Fl R Bro Fl i | Fl I Brc ) , destroy snapshots and file systems that do not exist on the sending side. +.It Fl l +Limits the the receive to only the +.Ar filesystem +or +.Ar volume +specified. As multiple +.Fl l +options may be specified, this can be used to restore specific filesystems or +volumes from the received stream. +.It Fl o Ar property Ns = Ns Ar value +Sets the specified property as if the command zfs set +.Ar property=value +is invoked at the same time the received dataset is created from the +non-incremental send stream or updated from the incremental send stream. +.Pp +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. +.Pp +Multiple +.Fl o +options can be specified. +.Pp +The +.Ar property +option may take one of two forms +.Bl -bullet -compact +.It +.Ar property +- Global property applied to all streams. +.It +.Ar property Ns # Ns Op Ar volume Ns | Ns Ar filesystem +- Local property applied to the specified +.Ar volume +or +.Ar filesystem +streams only. +.El +.Pp +The most specific +.Fl o +option takes precedence so in the case where both a global +.Ar property +and a +.Ar property Ns # Ns Op Ar filesystem Ns | Ns Ar volume +are specified for the same +.Ar property +the value of said +.Ar property +will be the one which most closely matches the domain of the +.Ar property . +.Pp +If both +.Fl o +and +.Fl x +are specified for the same +.Ar property +the +.Fl x +option takes precedence unless the +.Fl o +option is a better domain match than the +.Fl x +option. +.It Xo +.Fl x Ar property +.Xc +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 the property. +.Pp +In the case of an incremental update, +.Fl x +leaves any existing local setting or explicit inheritance unchanged (since the +received property is already overridden). +.Pp +The +.Ar property +option may take one of two forms +.Bl -bullet -compact +.It +.Ar property +- Global property excluded from all streams. +.It +.Ar property Ns # Ns Op Ar volume Ns | Ns Ar filesystem +- Local property excluded from the specified +.Ar volume +or +.Ar filesystem +streams only. +.El +.Pp +All +.Fl o +restrictions apply equally to +.Fl x. .El .It Xo .Nm --- cddl/contrib/opensolaris/cmd/zfs/zfs_main.c.orig 2014-10-10 09:31:35.000000000 +0000 +++ cddl/contrib/opensolaris/cmd/zfs/zfs_main.c 2014-11-13 14:41:07.191629807 +0000 @@ -262,9 +262,10 @@ get_usage(zfs_help_t idx) case HELP_PROMOTE: return (gettext("\tpromote <clone-filesystem>\n")); case HELP_RECEIVE: - return (gettext("\treceive|recv [-vnFu] <filesystem|volume|" - "snapshot>\n" - "\treceive|recv [-vnFu] [-d | -e] <filesystem>\n")); + return (gettext("\treceive|recv [-vnFu] [-o <property>] ... " + "[-x <property>] ... <filesystem|volume|snapshot>\n" + "\treceive|recv [-vnFu] [-d | -e] [-o <property>] ... " + "[-x <property>] ... <filesystem>\n")); case HELP_RENAME: return (gettext("\trename [-f] <filesystem|volume|snapshot> " "<filesystem|volume|snapshot>\n" @@ -495,6 +496,19 @@ usage(boolean_t requested) } static int +add_unique_option(nvlist_t *opts, const char *type, char *name) +{ + if (nvlist_lookup_string(opts, name, NULL) == 0) { + (void) fprintf(stderr, gettext("%s option '%s' " + "specified multiple times\n"), type, name); + return (-1); + } + if (nvlist_add_boolean(opts, name)) + nomem(); + return (0); +} + +static int parseprop(nvlist_t *props, char *propname) { char *propval, *strval; @@ -3882,18 +3896,24 @@ zfs_do_send(int argc, char **argv) } /* - * zfs receive [-vnFu] [-d | -e] <fs@snap> + * zfs receive [-vnFu] [-d | -e] [-l <volume|filesystem>] ... + * [-o property=value] ... [-x property] ... <volume|filesytem|snapshot> * * Restore a backup stream from stdin. */ static int zfs_do_receive(int argc, char **argv) { + nvlist_t *exprops, *limitds; int c, err; recvflags_t flags = { 0 }; + if (nvlist_alloc(&exprops, NV_UNIQUE_NAME, 0) != 0) + nomem(); + if (nvlist_alloc(&limitds, NV_UNIQUE_NAME, 0) != 0) + nomem(); /* check options */ - while ((c = getopt(argc, argv, ":denuvF")) != -1) { + while ((c = getopt(argc, argv, ":del:no:uvx:F")) != -1) { switch (c) { case 'd': flags.isprefix = B_TRUE; @@ -3902,15 +3922,32 @@ zfs_do_receive(int argc, char **argv) flags.isprefix = B_TRUE; flags.istail = B_TRUE; break; + case 'l': + if (add_unique_option(limitds, "limit", optarg)) { + err = 1; + goto recverror; + } + break; case 'n': flags.dryrun = B_TRUE; break; + case 'o': + if (parseprop(exprops, optarg)) { + err = 1; + goto recverror; + } + break; case 'u': flags.nomount = B_TRUE; break; case 'v': flags.verbose = B_TRUE; break; + case 'x': + if (add_unique_option(exprops, "exclude", optarg)) { + err = 1; + goto recverror; + } case 'F': flags.force = B_TRUE; break; @@ -3947,7 +3984,11 @@ zfs_do_receive(int argc, char **argv) return (1); } - err = zfs_receive(g_zfs, argv[0], &flags, STDIN_FILENO, NULL); + err = zfs_receive(g_zfs, argv[0], &flags, STDIN_FILENO, exprops, limitds, NULL); + +recverror: + nvlist_free(exprops); + nvlist_free(limitds); return (err != 0); }home | help
Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?56D040E1.3090000>
