diff options
author | LOLi <[email protected]> | 2017-05-10 01:21:09 +0200 |
---|---|---|
committer | Brian Behlendorf <[email protected]> | 2017-05-09 16:21:09 -0700 |
commit | a3eeab2de68670a4481eab3d086982aff23b6906 (patch) | |
tree | 5e3da58bca04309596df84dd84b591e8631eae0f /lib/libzfs | |
parent | 305bc4b370b20de81eaf10a1cf724374258b74d1 (diff) |
Add property overriding (-o|-x) to 'zfs receive'
This allows users to specify "-o property=value" to override and
"-x property" to exclude properties when receiving a zfs send stream.
Both native and user properties can be specified.
This is useful when using zfs send/receive for periodic
backup/replication because it lets users change properties such as
canmount, mountpoint, or compression without modifying the source.
References:
https://www.illumos.org/issues/2745
https://www.illumos.org/issues/3753
Reviewed by: Matthew Ahrens <[email protected]>
Reviewed-by: Alek Pinchuk <[email protected]>
Reviewed-by: Brian Behlendorf <[email protected]>
Signed-off-by: loli10K <[email protected]>
Closes #1350
Closes #5349
Diffstat (limited to 'lib/libzfs')
-rw-r--r-- | lib/libzfs/libzfs_sendrecv.c | 241 |
1 files changed, 225 insertions, 16 deletions
diff --git a/lib/libzfs/libzfs_sendrecv.c b/lib/libzfs/libzfs_sendrecv.c index 5e1896a3f..71ee8faae 100644 --- a/lib/libzfs/libzfs_sendrecv.c +++ b/lib/libzfs/libzfs_sendrecv.c @@ -28,6 +28,7 @@ * Copyright (c) 2013 Steven Hartland. All rights reserved. * Copyright 2015, OmniTI Computer Consulting, Inc. All rights reserved. * Copyright 2016 Igor Kozhukhov <[email protected]> + * Copyright (c) 2017, loli10K <[email protected]>. All rights reserved. */ #include <assert.h> @@ -69,7 +70,7 @@ extern void zfs_setprop_error(libzfs_handle_t *, zfs_prop_t, int, char *); static int zfs_receive_impl(libzfs_handle_t *, const char *, const char *, recvflags_t *, int, const char *, nvlist_t *, avl_tree_t *, char **, int, - uint64_t *, const char *); + uint64_t *, const char *, nvlist_t *); static int guid_to_name(libzfs_handle_t *, const char *, uint64_t, boolean_t, char *); @@ -2745,7 +2746,8 @@ 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) + char **top_zfs, int cleanup_fd, uint64_t *action_handlep, + nvlist_t *cmdprops) { nvlist_t *stream_nv = NULL; avl_tree_t *stream_avl = NULL; @@ -2921,7 +2923,7 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname, */ error = zfs_receive_impl(hdl, destname, NULL, flags, fd, sendfs, stream_nv, stream_avl, top_zfs, cleanup_fd, - action_handlep, sendsnap); + action_handlep, sendsnap, cmdprops); if (error == ENODATA) { error = 0; break; @@ -3086,6 +3088,131 @@ recv_ecksum_set_aux(libzfs_handle_t *hdl, const char *target_snap, } /* + * Prepare a new nvlist of properties that are to override (-o) or be excluded + * (-x) from the received dataset + * recvprops: received properties from the send stream + * cmdprops: raw input properties from command line + * origprops: properties, both locally-set and received, currently set on the + * target dataset if it exists, NULL otherwise. + * oxprops: valid output override (-o) and excluded (-x) properties + */ +static int +zfs_setup_cmdline_props(libzfs_handle_t *hdl, zfs_type_t type, boolean_t zoned, + boolean_t recursive, boolean_t toplevel, nvlist_t *recvprops, + nvlist_t *cmdprops, nvlist_t *origprops, nvlist_t **oxprops, + const char *errbuf) +{ + nvpair_t *nvp; + nvlist_t *oprops, *voprops; + zfs_handle_t *zhp = NULL; + zpool_handle_t *zpool_hdl = NULL; + int ret = 0; + + if (nvlist_empty(cmdprops)) + return (0); /* No properties to override or exclude */ + + *oxprops = fnvlist_alloc(); + oprops = fnvlist_alloc(); + + /* + * first iteration: process excluded (-x) properties now and gather + * added (-o) properties to be later processed by zfs_valid_proplist() + */ + nvp = NULL; + while ((nvp = nvlist_next_nvpair(cmdprops, nvp)) != NULL) { + const char *name = nvpair_name(nvp); + zfs_prop_t prop = zfs_name_to_prop(name); + + /* "origin" is processed separately, don't handle it here */ + if (prop == ZFS_PROP_ORIGIN) + continue; + + /* + * we're trying to override or exclude a property that does not + * make sense for this type of dataset, but we don't want to + * fail if the receive is recursive: this comes in handy when + * the send stream contains, for instance, a child ZVOL and + * we're trying to receive it with "-o atime=on" + */ + if (!zfs_prop_valid_for_type(prop, type, B_FALSE) && + !zfs_prop_user(name)) { + if (recursive) + continue; + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "property '%s' does not apply to datasets of this " + "type"), name); + ret = zfs_error(hdl, EZFS_BADPROP, errbuf); + goto error; + } + + switch (nvpair_type(nvp)) { + case DATA_TYPE_BOOLEAN: /* -x property */ + /* + * DATA_TYPE_BOOLEAN is the way we're asked to "exclude" + * a property: this is done by forcing an explicit + * inherit on the destination so the effective value is + * not the one we received from the send stream. + * We do this only if the property is not already + * locally-set, in which case its value will take + * priority over the received anyway. + */ + if (nvlist_exists(origprops, name)) { + nvlist_t *attrs; + + attrs = fnvlist_lookup_nvlist(origprops, name); + if (strcmp(fnvlist_lookup_string(attrs, + ZPROP_SOURCE), ZPROP_SOURCE_VAL_RECVD) != 0) + continue; + } + /* + * We can't force an explicit inherit on non-inheritable + * properties: if we're asked to exclude this kind of + * values we remove them from "recvprops" input nvlist. + */ + if (!zfs_prop_inheritable(prop) && + !zfs_prop_user(name) && /* can be inherited too */ + nvlist_exists(recvprops, name)) + fnvlist_remove(recvprops, name); + else + fnvlist_add_nvpair(*oxprops, nvp); + break; + case DATA_TYPE_STRING: /* -o property=value */ + fnvlist_add_nvpair(oprops, nvp); + break; + default: + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "property '%s' must be a string or boolean"), name); + ret = zfs_error(hdl, EZFS_BADPROP, errbuf); + goto error; + } + } + + if (toplevel) { + /* convert override strings properties to native */ + if ((voprops = zfs_valid_proplist(hdl, ZFS_TYPE_DATASET, + oprops, zoned, zhp, zpool_hdl, errbuf)) == NULL) { + ret = zfs_error(hdl, EZFS_BADPROP, errbuf); + goto error; + } + + /* second pass: process "-o" properties */ + fnvlist_merge(*oxprops, voprops); + fnvlist_free(voprops); + } else { + /* override props on child dataset are inherited */ + nvp = NULL; + while ((nvp = nvlist_next_nvpair(oprops, nvp)) != NULL) { + const char *name = nvpair_name(nvp); + fnvlist_add_boolean(*oxprops, name); + } + } + +error: + fnvlist_free(oprops); + return (ret); +} + +/* * Restores a backup of tosnap from the file descriptor specified by infd. */ static int @@ -3093,7 +3220,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, const char *originsnap, 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, const char *finalsnap) + uint64_t *action_handlep, const char *finalsnap, nvlist_t *cmdprops) { time_t begin_time; int ioctl_err, ioctl_errno, err; @@ -3116,7 +3243,12 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, char destsnap[MAXPATHLEN * 2]; char origin[MAXNAMELEN]; char name[MAXPATHLEN]; - nvlist_t *props = NULL; + nvlist_t *rcvprops = NULL; /* props received from the send stream */ + nvlist_t *oxprops = NULL; /* override (-o) and exclude (-x) props */ + nvlist_t *origprops = NULL; /* original props (if destination exists) */ + zfs_type_t type; + boolean_t toplevel; + boolean_t zoned = B_FALSE; begin_time = time(NULL); bzero(origin, MAXNAMELEN); @@ -3134,14 +3266,14 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, (void) nvlist_lookup_uint64(fs, "parentfromsnap", &parent_snapguid); - err = nvlist_lookup_nvlist(fs, "props", &props); + err = nvlist_lookup_nvlist(fs, "props", &rcvprops); if (err) { - VERIFY(0 == nvlist_alloc(&props, NV_UNIQUE_NAME, 0)); + VERIFY(0 == nvlist_alloc(&rcvprops, NV_UNIQUE_NAME, 0)); newprops = B_TRUE; } if (flags->canmountoff) { - VERIFY(0 == nvlist_add_uint64(props, + VERIFY(0 == nvlist_add_uint64(rcvprops, zfs_prop_to_name(ZFS_PROP_CANMOUNT), 0)); } if (0 == nvlist_lookup_nvlist(fs, "snapprops", &lookup)) { @@ -3395,6 +3527,14 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, if (resuming && zfs_prop_get_int(zhp, ZFS_PROP_INCONSISTENT)) newfs = B_TRUE; + /* we want to know if we're zoned when validating -o|-x props */ + zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED); + + /* gather existing properties on destination */ + origprops = fnvlist_alloc(); + fnvlist_merge(origprops, zhp->zfs_props); + fnvlist_merge(origprops, zhp->zfs_user_props); + zfs_close(zhp); } else { /* @@ -3441,9 +3581,24 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, goto out; } - err = ioctl_err = lzc_receive_one(destsnap, props, origin, - flags->force, flags->resumable, infd, drr_noswap, cleanup_fd, - &read_bytes, &errflags, action_handlep, &prop_errors); + toplevel = chopprefix[0] != '/'; + if (drrb->drr_type == DMU_OST_ZVOL) { + type = ZFS_TYPE_VOLUME; + } else if (drrb->drr_type == DMU_OST_ZFS) { + type = ZFS_TYPE_FILESYSTEM; + } else { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "invalid record type: 0x%d"), drrb->drr_type); + err = zfs_error(hdl, EZFS_BADSTREAM, errbuf); + goto out; + } + if ((err = zfs_setup_cmdline_props(hdl, type, zoned, recursive, + toplevel, rcvprops, cmdprops, origprops, &oxprops, errbuf)) != 0) + goto out; + + err = ioctl_err = lzc_receive_with_cmdprops(destsnap, rcvprops, oxprops, + origin, flags->force, flags->resumable, infd, drr_noswap, + cleanup_fd, &read_bytes, &errflags, action_handlep, &prop_errors); ioctl_errno = ioctl_err; prop_errflags = errflags; @@ -3658,16 +3813,65 @@ out: nvlist_free(prop_errors); if (newprops) - nvlist_free(props); + nvlist_free(rcvprops); + + nvlist_free(oxprops); + nvlist_free(origprops); return (err); } +/* + * Check properties we were asked to override (both -o|-x) + */ +static boolean_t +zfs_receive_checkprops(libzfs_handle_t *hdl, nvlist_t *props, + const char *errbuf) +{ + nvpair_t *nvp; + zfs_prop_t prop; + const char *name; + + nvp = NULL; + while ((nvp = nvlist_next_nvpair(props, nvp)) != NULL) { + name = nvpair_name(nvp); + prop = zfs_name_to_prop(name); + + if (prop == ZPROP_INVAL) { + if (!zfs_prop_user(name)) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "invalid property '%s'"), name); + return (B_FALSE); + } + continue; + } + /* + * "origin" is readonly but is used to receive datasets as + * clones so we don't raise an error here + */ + if (prop == ZFS_PROP_ORIGIN) + continue; + + /* + * cannot override readonly, set-once and other specific + * settable properties + */ + if (zfs_prop_readonly(prop) || prop == ZFS_PROP_VERSION || + prop == ZFS_PROP_VOLSIZE) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "invalid property '%s'"), name); + return (B_FALSE); + } + } + + return (B_TRUE); +} + static int zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap, const char *originsnap, recvflags_t *flags, int infd, const char *sendfs, nvlist_t *stream_nv, avl_tree_t *stream_avl, char **top_zfs, int cleanup_fd, - uint64_t *action_handlep, const char *finalsnap) + uint64_t *action_handlep, const char *finalsnap, nvlist_t *cmdprops) { int err; dmu_replay_record_t drr, drr_noswap; @@ -3680,6 +3884,11 @@ zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap, (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, "cannot receive")); + /* check cmdline props, raise an error if they cannot be received */ + if (!zfs_receive_checkprops(hdl, cmdprops, errbuf)) { + return (zfs_error(hdl, EZFS_BADPROP, errbuf)); + } + if (flags->isprefix && !zfs_dataset_exists(hdl, tosnap, ZFS_TYPE_DATASET)) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "specified fs " @@ -3768,12 +3977,12 @@ zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap, } return (zfs_receive_one(hdl, infd, tosnap, originsnap, flags, &drr, &drr_noswap, sendfs, stream_nv, stream_avl, top_zfs, - cleanup_fd, action_handlep, finalsnap)); + cleanup_fd, action_handlep, finalsnap, cmdprops)); } 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)); + &zcksum, top_zfs, cleanup_fd, action_handlep, cmdprops)); } } @@ -3846,7 +4055,7 @@ zfs_receive(libzfs_handle_t *hdl, const char *tosnap, nvlist_t *props, VERIFY(cleanup_fd >= 0); err = zfs_receive_impl(hdl, tosnap, originsnap, flags, infd, NULL, NULL, - stream_avl, &top_zfs, cleanup_fd, &action_handle, NULL); + stream_avl, &top_zfs, cleanup_fd, &action_handle, NULL, props); VERIFY(0 == close(cleanup_fd)); |