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