ZFS receive and attributes, proposing some changes. was Re: ZFS full system backup hoses the backup host.

Steven Hartland killing at multiplay.co.uk
Fri Feb 26 12:11:09 UTC 2016


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 at 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 at freebsd.org mailing list
> https://lists.freebsd.org/mailman/listinfo/freebsd-fs
> To unsubscribe, send any mail to "freebsd-fs-unsubscribe at freebsd.org"

-------------- next part --------------
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 at 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);
 }


More information about the freebsd-fs mailing list