diff options
Diffstat (limited to 'cddl/contrib/opensolaris/lib/libzfs/common/libzfs_dataset.c')
-rw-r--r-- | cddl/contrib/opensolaris/lib/libzfs/common/libzfs_dataset.c | 3026 |
1 files changed, 1739 insertions, 1287 deletions
diff --git a/cddl/contrib/opensolaris/lib/libzfs/common/libzfs_dataset.c b/cddl/contrib/opensolaris/lib/libzfs/common/libzfs_dataset.c index 4fc441a..58ce6c8 100644 --- a/cddl/contrib/opensolaris/lib/libzfs/common/libzfs_dataset.c +++ b/cddl/contrib/opensolaris/lib/libzfs/common/libzfs_dataset.c @@ -20,12 +20,10 @@ */ /* - * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ -#pragma ident "%Z%%M% %I% %E% SMI" - #include <assert.h> #include <ctype.h> #include <errno.h> @@ -35,20 +33,26 @@ #include <stdlib.h> #include <strings.h> #include <unistd.h> +#include <stddef.h> #include <zone.h> #include <fcntl.h> #include <sys/mntent.h> #include <sys/mnttab.h> #include <sys/mount.h> +#include <sys/avl.h> +#include <priv.h> +#include <pwd.h> +#include <grp.h> +#include <stddef.h> #include <sys/spa.h> -#include <sys/zio.h> #include <sys/zap.h> #include <libzfs.h> #include "zfs_namecheck.h" #include "zfs_prop.h" #include "libzfs_impl.h" +#include "zfs_deleg.h" static int zvol_create_link_common(libzfs_handle_t *, const char *, int); @@ -121,7 +125,8 @@ path_to_str(const char *path, int types) * 'buf' detailing exactly why the name was not valid. */ static int -zfs_validate_name(libzfs_handle_t *hdl, const char *path, int type) +zfs_validate_name(libzfs_handle_t *hdl, const char *path, int type, + boolean_t modifying) { namecheck_err_t why; char what; @@ -194,43 +199,118 @@ zfs_validate_name(libzfs_handle_t *hdl, const char *path, int type) return (0); } + if (modifying && strchr(path, '%') != NULL) { + if (hdl != NULL) + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "invalid character %c in name"), '%'); + return (0); + } + return (-1); } int zfs_name_valid(const char *name, zfs_type_t type) { - return (zfs_validate_name(NULL, name, type)); + if (type == ZFS_TYPE_POOL) + return (zpool_name_valid(NULL, B_FALSE, name)); + return (zfs_validate_name(NULL, name, type, B_FALSE)); } /* * This function takes the raw DSL properties, and filters out the user-defined * properties into a separate nvlist. */ -static int -process_user_props(zfs_handle_t *zhp) +static nvlist_t * +process_user_props(zfs_handle_t *zhp, nvlist_t *props) { libzfs_handle_t *hdl = zhp->zfs_hdl; nvpair_t *elem; nvlist_t *propval; + nvlist_t *nvl; - nvlist_free(zhp->zfs_user_props); - - if (nvlist_alloc(&zhp->zfs_user_props, NV_UNIQUE_NAME, 0) != 0) - return (no_memory(hdl)); + if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0) { + (void) no_memory(hdl); + return (NULL); + } elem = NULL; - while ((elem = nvlist_next_nvpair(zhp->zfs_props, elem)) != NULL) { + while ((elem = nvlist_next_nvpair(props, elem)) != NULL) { if (!zfs_prop_user(nvpair_name(elem))) continue; verify(nvpair_value_nvlist(elem, &propval) == 0); - if (nvlist_add_nvlist(zhp->zfs_user_props, - nvpair_name(elem), propval) != 0) - return (no_memory(hdl)); + if (nvlist_add_nvlist(nvl, nvpair_name(elem), propval) != 0) { + nvlist_free(nvl); + (void) no_memory(hdl); + return (NULL); + } } - return (0); + return (nvl); +} + +static zpool_handle_t * +zpool_add_handle(zfs_handle_t *zhp, const char *pool_name) +{ + libzfs_handle_t *hdl = zhp->zfs_hdl; + zpool_handle_t *zph; + + if ((zph = zpool_open_canfail(hdl, pool_name)) != NULL) { + if (hdl->libzfs_pool_handles != NULL) + zph->zpool_next = hdl->libzfs_pool_handles; + hdl->libzfs_pool_handles = zph; + } + return (zph); +} + +static zpool_handle_t * +zpool_find_handle(zfs_handle_t *zhp, const char *pool_name, int len) +{ + libzfs_handle_t *hdl = zhp->zfs_hdl; + zpool_handle_t *zph = hdl->libzfs_pool_handles; + + while ((zph != NULL) && + (strncmp(pool_name, zpool_get_name(zph), len) != 0)) + zph = zph->zpool_next; + return (zph); +} + +/* + * Returns a handle to the pool that contains the provided dataset. + * If a handle to that pool already exists then that handle is returned. + * Otherwise, a new handle is created and added to the list of handles. + */ +static zpool_handle_t * +zpool_handle(zfs_handle_t *zhp) +{ + char *pool_name; + int len; + zpool_handle_t *zph; + + len = strcspn(zhp->zfs_name, "/@") + 1; + pool_name = zfs_alloc(zhp->zfs_hdl, len); + (void) strlcpy(pool_name, zhp->zfs_name, len); + + zph = zpool_find_handle(zhp, pool_name, len); + if (zph == NULL) + zph = zpool_add_handle(zhp, pool_name); + + free(pool_name); + return (zph); +} + +void +zpool_free_handles(libzfs_handle_t *hdl) +{ + zpool_handle_t *next, *zph = hdl->libzfs_pool_handles; + + while (zph != NULL) { + next = zph->zpool_next; + zpool_close(zph); + zph = next; + } + hdl->libzfs_pool_handles = NULL; } /* @@ -241,6 +321,7 @@ get_stats(zfs_handle_t *zhp) { zfs_cmd_t zc = { 0 }; libzfs_handle_t *hdl = zhp->zfs_hdl; + nvlist_t *allprops, *userprops; (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); @@ -261,22 +342,23 @@ get_stats(zfs_handle_t *zhp) zhp->zfs_dmustats = zc.zc_objset_stats; /* structure assignment */ - (void) strlcpy(zhp->zfs_root, zc.zc_value, sizeof (zhp->zfs_root)); - - if (zhp->zfs_props) { - nvlist_free(zhp->zfs_props); - zhp->zfs_props = NULL; - } - - if (zcmd_read_dst_nvlist(hdl, &zc, &zhp->zfs_props) != 0) { + if (zcmd_read_dst_nvlist(hdl, &zc, &allprops) != 0) { zcmd_free_nvlists(&zc); return (-1); } zcmd_free_nvlists(&zc); - if (process_user_props(zhp) != 0) + if ((userprops = process_user_props(zhp, allprops)) == NULL) { + nvlist_free(allprops); return (-1); + } + + nvlist_free(zhp->zfs_props); + nvlist_free(zhp->zfs_user_props); + + zhp->zfs_props = allprops; + zhp->zfs_user_props = userprops; return (0); } @@ -298,16 +380,25 @@ zfs_handle_t * make_dataset_handle(libzfs_handle_t *hdl, const char *path) { zfs_handle_t *zhp = calloc(sizeof (zfs_handle_t), 1); + char *logstr; if (zhp == NULL) return (NULL); zhp->zfs_hdl = hdl; + /* + * Preserve history log string. + * any changes performed here will be + * logged as an internal event. + */ + logstr = zhp->zfs_hdl->libzfs_log_str; + zhp->zfs_hdl->libzfs_log_str = NULL; top: (void) strlcpy(zhp->zfs_name, path, sizeof (zhp->zfs_name)); if (get_stats(zhp) != 0) { + zhp->zfs_hdl->libzfs_log_str = logstr; free(zhp); return (NULL); } @@ -339,18 +430,19 @@ top: zc.zc_objset_type = DMU_OST_ZFS; } - /* If we can successfully roll it back, reget the stats */ - if (ioctl(hdl->libzfs_fd, ZFS_IOC_ROLLBACK, &zc) == 0) - goto top; /* - * If we can sucessfully destroy it, pretend that it + * If we can successfully destroy it, pretend that it * never existed. */ if (ioctl(hdl->libzfs_fd, ZFS_IOC_DESTROY, &zc) == 0) { + zhp->zfs_hdl->libzfs_log_str = logstr; free(zhp); errno = ENOENT; return (NULL); } + /* If we can successfully roll it back, reget the stats */ + if (ioctl(hdl->libzfs_fd, ZFS_IOC_ROLLBACK, &zc) == 0) + goto top; } /* @@ -373,6 +465,8 @@ top: else abort(); /* we should never see any other types */ + zhp->zfs_hdl->libzfs_log_str = logstr; + zhp->zpool_hdl = zpool_handle(zhp); return (zhp); } @@ -393,7 +487,7 @@ zfs_open(libzfs_handle_t *hdl, const char *path, int types) /* * Validate the name before we even try to open it. */ - if (!zfs_validate_name(hdl, path, ZFS_TYPE_ANY)) { + if (!zfs_validate_name(hdl, path, ZFS_TYPE_DATASET, B_FALSE)) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid dataset name")); (void) zfs_error(hdl, EZFS_INVALIDNAME, errbuf); @@ -431,350 +525,92 @@ zfs_close(zfs_handle_t *zhp) free(zhp); } -/* - * Given a numeric suffix, convert the value into a number of bits that the - * resulting value must be shifted. - */ -static int -str2shift(libzfs_handle_t *hdl, const char *buf) -{ - const char *ends = "BKMGTPEZ"; - int i; - - if (buf[0] == '\0') - return (0); - for (i = 0; i < strlen(ends); i++) { - if (toupper(buf[0]) == ends[i]) - break; - } - if (i == strlen(ends)) { - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "invalid numeric suffix '%s'"), buf); - return (-1); - } - - /* - * We want to allow trailing 'b' characters for 'GB' or 'Mb'. But don't - * allow 'BB' - that's just weird. - */ - if (buf[1] == '\0' || (toupper(buf[1]) == 'B' && buf[2] == '\0' && - toupper(buf[0]) != 'B')) - return (10*i); - - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "invalid numeric suffix '%s'"), buf); - return (-1); -} - -/* - * Convert a string of the form '100G' into a real number. Used when setting - * properties or creating a volume. 'buf' is used to place an extended error - * message for the caller to use. - */ -static int -nicestrtonum(libzfs_handle_t *hdl, const char *value, uint64_t *num) +int +zfs_spa_version(zfs_handle_t *zhp, int *spa_version) { - char *end; - int shift; - - *num = 0; - - /* Check to see if this looks like a number. */ - if ((value[0] < '0' || value[0] > '9') && value[0] != '.') { - if (hdl) - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "bad numeric value '%s'"), value); - return (-1); - } - - /* Rely on stroll() to process the numeric portion. */ - errno = 0; - *num = strtoll(value, &end, 10); + zpool_handle_t *zpool_handle = zhp->zpool_hdl; - /* - * Check for ERANGE, which indicates that the value is too large to fit - * in a 64-bit value. - */ - if (errno == ERANGE) { - if (hdl) - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "numeric value is too large")); + if (zpool_handle == NULL) return (-1); - } - - /* - * If we have a decimal value, then do the computation with floating - * point arithmetic. Otherwise, use standard arithmetic. - */ - if (*end == '.') { - double fval = strtod(value, &end); - - if ((shift = str2shift(hdl, end)) == -1) - return (-1); - - fval *= pow(2, shift); - - if (fval > UINT64_MAX) { - if (hdl) - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "numeric value is too large")); - return (-1); - } - - *num = (uint64_t)fval; - } else { - if ((shift = str2shift(hdl, end)) == -1) - return (-1); - - /* Check for overflow */ - if (shift >= 64 || (*num << shift) >> shift != *num) { - if (hdl) - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "numeric value is too large")); - return (-1); - } - - *num <<= shift; - } + *spa_version = zpool_get_prop_int(zpool_handle, + ZPOOL_PROP_VERSION, NULL); return (0); } -int -zfs_nicestrtonum(libzfs_handle_t *hdl, const char *str, uint64_t *val) -{ - return (nicestrtonum(hdl, str, val)); -} - /* - * The prop_parse_*() functions are designed to allow flexibility in callers - * when setting properties. At the DSL layer, all properties are either 64-bit - * numbers or strings. We want the user to be able to ignore this fact and - * specify properties as native values (boolean, for example) or as strings (to - * simplify command line utilities). This also handles converting index types - * (compression, checksum, etc) from strings to their on-disk index. + * The choice of reservation property depends on the SPA version. */ - static int -prop_parse_boolean(libzfs_handle_t *hdl, nvpair_t *elem, uint64_t *val) +zfs_which_resv_prop(zfs_handle_t *zhp, zfs_prop_t *resv_prop) { - uint64_t ret; + int spa_version; - switch (nvpair_type(elem)) { - case DATA_TYPE_STRING: - { - char *value; - verify(nvpair_value_string(elem, &value) == 0); - - if (strcmp(value, "on") == 0) { - ret = 1; - } else if (strcmp(value, "off") == 0) { - ret = 0; - } else { - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "property '%s' must be 'on' or 'off'"), - nvpair_name(elem)); - return (-1); - } - break; - } - - case DATA_TYPE_UINT64: - { - verify(nvpair_value_uint64(elem, &ret) == 0); - if (ret > 1) { - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "'%s' must be a boolean value"), - nvpair_name(elem)); - return (-1); - } - break; - } - - case DATA_TYPE_BOOLEAN_VALUE: - { - boolean_t value; - verify(nvpair_value_boolean_value(elem, &value) == 0); - ret = value; - break; - } - - default: - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "'%s' must be a boolean value"), - nvpair_name(elem)); + if (zfs_spa_version(zhp, &spa_version) < 0) return (-1); - } - - *val = ret; - return (0); -} - -static int -prop_parse_number(libzfs_handle_t *hdl, nvpair_t *elem, zfs_prop_t prop, - uint64_t *val) -{ - uint64_t ret; - boolean_t isnone = B_FALSE; - switch (nvpair_type(elem)) { - case DATA_TYPE_STRING: - { - char *value; - (void) nvpair_value_string(elem, &value); - if (strcmp(value, "none") == 0) { - isnone = B_TRUE; - ret = 0; - } else if (nicestrtonum(hdl, value, &ret) != 0) { - return (-1); - } - break; - } - - case DATA_TYPE_UINT64: - (void) nvpair_value_uint64(elem, &ret); - break; - - default: - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "'%s' must be a number"), - nvpair_name(elem)); - return (-1); - } - - /* - * Quota special: force 'none' and don't allow 0. - */ - if (ret == 0 && !isnone && prop == ZFS_PROP_QUOTA) { - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "use 'none' to disable quota")); - return (-1); - } - - *val = ret; - return (0); -} - -static int -prop_parse_index(libzfs_handle_t *hdl, nvpair_t *elem, zfs_prop_t prop, - uint64_t *val) -{ - char *propname = nvpair_name(elem); - char *value; - - if (nvpair_type(elem) != DATA_TYPE_STRING) { - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "'%s' must be a string"), propname); - return (-1); - } - - (void) nvpair_value_string(elem, &value); - - if (zfs_prop_string_to_index(prop, value, val) != 0) { - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "'%s' must be one of '%s'"), propname, - zfs_prop_values(prop)); - return (-1); - } + if (spa_version >= SPA_VERSION_REFRESERVATION) + *resv_prop = ZFS_PROP_REFRESERVATION; + else + *resv_prop = ZFS_PROP_RESERVATION; return (0); } /* - * Check if the bootfs name has the same pool name as it is set to. - * Assuming bootfs is a valid dataset name. - */ -static boolean_t -bootfs_poolname_valid(char *pool, char *bootfs) -{ - char ch, *pname; - - /* get the pool name from the bootfs name */ - pname = bootfs; - while (*bootfs && !isspace(*bootfs) && *bootfs != '/') - bootfs++; - - ch = *bootfs; - *bootfs = 0; - - if (strcmp(pool, pname) == 0) { - *bootfs = ch; - return (B_TRUE); - } - - *bootfs = ch; - return (B_FALSE); -} - -/* * Given an nvlist of properties to set, validates that they are correct, and * parses any numeric properties (index, boolean, etc) if they are specified as * strings. */ nvlist_t * -zfs_validate_properties(libzfs_handle_t *hdl, zfs_type_t type, char *pool_name, - nvlist_t *nvl, uint64_t zoned, zfs_handle_t *zhp, const char *errbuf) +zfs_valid_proplist(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl, + uint64_t zoned, zfs_handle_t *zhp, const char *errbuf) { nvpair_t *elem; - const char *propname; - zfs_prop_t prop; uint64_t intval; char *strval; + zfs_prop_t prop; nvlist_t *ret; - int isuser; + int chosen_normal = -1; + int chosen_utf = -1; if (nvlist_alloc(&ret, NV_UNIQUE_NAME, 0) != 0) { (void) no_memory(hdl); return (NULL); } - if (type == ZFS_TYPE_SNAPSHOT) { - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "snapshot properties cannot be modified")); - (void) zfs_error(hdl, EZFS_PROPTYPE, errbuf); - goto error; - } - elem = NULL; while ((elem = nvlist_next_nvpair(nvl, elem)) != NULL) { - propname = nvpair_name(elem); + const char *propname = nvpair_name(elem); /* * Make sure this property is valid and applies to this type. */ - if ((prop = zfs_name_to_prop_common(propname, type)) - == ZFS_PROP_INVAL) { - isuser = zfs_prop_user(propname); - if (!isuser || (isuser && (type & ZFS_TYPE_POOL))) { + if ((prop = zfs_name_to_prop(propname)) == ZPROP_INVAL) { + if (!zfs_prop_user(propname)) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "invalid property '%s'"), - propname); + "invalid property '%s'"), propname); (void) zfs_error(hdl, EZFS_BADPROP, errbuf); goto error; - } else { - /* - * If this is a user property, make sure it's a - * string, and that it's less than - * ZAP_MAXNAMELEN. - */ - if (nvpair_type(elem) != DATA_TYPE_STRING) { - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "'%s' must be a string"), - propname); - (void) zfs_error(hdl, EZFS_BADPROP, - errbuf); - goto error; - } + } - if (strlen(nvpair_name(elem)) >= - ZAP_MAXNAMELEN) { - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "property name '%s' is too long"), - propname); - (void) zfs_error(hdl, EZFS_BADPROP, - errbuf); - goto error; - } + /* + * If this is a user property, make sure it's a + * string, and that it's less than ZAP_MAXNAMELEN. + */ + if (nvpair_type(elem) != DATA_TYPE_STRING) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "'%s' must be a string"), propname); + (void) zfs_error(hdl, EZFS_BADPROP, errbuf); + goto error; + } + + if (strlen(nvpair_name(elem)) >= ZAP_MAXNAMELEN) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "property name '%s' is too long"), + propname); + (void) zfs_error(hdl, EZFS_BADPROP, errbuf); + goto error; } (void) nvpair_value_string(elem, &strval); @@ -785,10 +621,12 @@ zfs_validate_properties(libzfs_handle_t *hdl, zfs_type_t type, char *pool_name, continue; } - /* - * Normalize the name, to get rid of shorthand abbrevations. - */ - propname = zfs_prop_to_name(prop); + if (type == ZFS_TYPE_SNAPSHOT) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "this property can not be modified for snapshots")); + (void) zfs_error(hdl, EZFS_PROPTYPE, errbuf); + goto error; + } if (!zfs_prop_valid_for_type(prop, type)) { zfs_error_aux(hdl, @@ -799,7 +637,7 @@ zfs_validate_properties(libzfs_handle_t *hdl, zfs_type_t type, char *pool_name, } if (zfs_prop_readonly(prop) && - (prop != ZFS_PROP_VOLBLOCKSIZE || zhp != NULL)) { + (!zfs_prop_setonce(prop) || zhp != NULL)) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "'%s' is readonly"), propname); @@ -807,70 +645,31 @@ zfs_validate_properties(libzfs_handle_t *hdl, zfs_type_t type, char *pool_name, goto error; } + if (zprop_parse_value(hdl, elem, prop, type, ret, + &strval, &intval, errbuf) != 0) + goto error; + /* - * Convert any properties to the internal DSL value types. + * Perform some additional checks for specific properties. */ - strval = NULL; - switch (zfs_prop_get_type(prop)) { - case prop_type_boolean: - if (prop_parse_boolean(hdl, elem, &intval) != 0) { - (void) zfs_error(hdl, EZFS_BADPROP, errbuf); - goto error; - } - break; + switch (prop) { + case ZFS_PROP_VERSION: + { + int version; - case prop_type_string: - if (nvpair_type(elem) != DATA_TYPE_STRING) { - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "'%s' must be a string"), - propname); - (void) zfs_error(hdl, EZFS_BADPROP, errbuf); - goto error; - } - (void) nvpair_value_string(elem, &strval); - if (strlen(strval) >= ZFS_MAXPROPLEN) { + if (zhp == NULL) + break; + version = zfs_prop_get_int(zhp, ZFS_PROP_VERSION); + if (intval < version) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "'%s' is too long"), propname); + "Can not downgrade; already at version %u"), + version); (void) zfs_error(hdl, EZFS_BADPROP, errbuf); goto error; } break; - - case prop_type_number: - if (prop_parse_number(hdl, elem, prop, &intval) != 0) { - (void) zfs_error(hdl, EZFS_BADPROP, errbuf); - goto error; - } - break; - - case prop_type_index: - if (prop_parse_index(hdl, elem, prop, &intval) != 0) { - (void) zfs_error(hdl, EZFS_BADPROP, errbuf); - goto error; - } - break; - - default: - abort(); } - /* - * Add the result to our return set of properties. - */ - if (strval) { - if (nvlist_add_string(ret, propname, strval) != 0) { - (void) no_memory(hdl); - goto error; - } - } else if (nvlist_add_uint64(ret, propname, intval) != 0) { - (void) no_memory(hdl); - goto error; - } - - /* - * Perform some additional checks for specific properties. - */ - switch (prop) { case ZFS_PROP_RECORDSIZE: case ZFS_PROP_VOLBLOCKSIZE: /* must be power of two within SPA_{MIN,MAX}BLOCKSIZE */ @@ -900,32 +699,52 @@ zfs_validate_properties(libzfs_handle_t *hdl, zfs_type_t type, char *pool_name, break; case ZFS_PROP_MOUNTPOINT: + { + namecheck_err_t why; + if (strcmp(strval, ZFS_MOUNTPOINT_NONE) == 0 || strcmp(strval, ZFS_MOUNTPOINT_LEGACY) == 0) break; - if (strval[0] != '/') { - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "'%s' must be an absolute path, " - "'none', or 'legacy'"), propname); + if (mountpoint_namecheck(strval, &why)) { + switch (why) { + case NAME_ERR_LEADING_SLASH: + zfs_error_aux(hdl, + dgettext(TEXT_DOMAIN, + "'%s' must be an absolute path, " + "'none', or 'legacy'"), propname); + break; + case NAME_ERR_TOOLONG: + zfs_error_aux(hdl, + dgettext(TEXT_DOMAIN, + "component of '%s' is too long"), + propname); + break; + } (void) zfs_error(hdl, EZFS_BADPROP, errbuf); goto error; } + } + /*FALLTHRU*/ + case ZFS_PROP_SHARESMB: case ZFS_PROP_SHARENFS: /* - * For the mountpoint and sharenfs properties, check if - * it can be set in a global/non-global zone based on + * For the mountpoint and sharenfs or sharesmb + * properties, check if it can be set in a + * global/non-global zone based on * the zoned property value: * * global zone non-global zone * -------------------------------------------------- * zoned=on mountpoint (no) mountpoint (yes) * sharenfs (no) sharenfs (no) + * sharesmb (no) sharesmb (no) * * zoned=off mountpoint (yes) N/A * sharenfs (yes) + * sharesmb (yes) */ if (zoned) { if (getzoneid() == GLOBAL_ZONEID) { @@ -936,7 +755,8 @@ zfs_validate_properties(libzfs_handle_t *hdl, zfs_type_t type, char *pool_name, (void) zfs_error(hdl, EZFS_ZONED, errbuf); goto error; - } else if (prop == ZFS_PROP_SHARENFS) { + } else if (prop == ZFS_PROP_SHARENFS || + prop == ZFS_PROP_SHARESMB) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "'%s' cannot be set in " "a non-global zone"), propname); @@ -956,22 +776,73 @@ zfs_validate_properties(libzfs_handle_t *hdl, zfs_type_t type, char *pool_name, goto error; } - break; - - case ZFS_PROP_BOOTFS: /* - * bootfs property value has to be a dataset name and - * the dataset has to be in the same pool as it sets to. + * At this point, it is legitimate to set the + * property. Now we want to make sure that the + * property value is valid if it is sharenfs. */ - if (strval[0] != '\0' && (!zfs_name_valid(strval, - ZFS_TYPE_FILESYSTEM) || !bootfs_poolname_valid( - pool_name, strval))) { + if ((prop == ZFS_PROP_SHARENFS || + prop == ZFS_PROP_SHARESMB) && + strcmp(strval, "on") != 0 && + strcmp(strval, "off") != 0) { + zfs_share_proto_t proto; - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "'%s' " - "is an invalid name"), strval); - (void) zfs_error(hdl, EZFS_INVALIDNAME, errbuf); - goto error; + if (prop == ZFS_PROP_SHARESMB) + proto = PROTO_SMB; + else + proto = PROTO_NFS; + + /* + * Must be an valid sharing protocol + * option string so init the libshare + * in order to enable the parser and + * then parse the options. We use the + * control API since we don't care about + * the current configuration and don't + * want the overhead of loading it + * until we actually do something. + */ + + if (zfs_init_libshare(hdl, + SA_INIT_CONTROL_API) != SA_OK) { + /* + * An error occurred so we can't do + * anything + */ + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "'%s' cannot be set: problem " + "in share initialization"), + propname); + (void) zfs_error(hdl, EZFS_BADPROP, + errbuf); + goto error; + } + + if (zfs_parse_options(strval, proto) != SA_OK) { + /* + * There was an error in parsing so + * deal with it by issuing an error + * message and leaving after + * uninitializing the the libshare + * interface. + */ + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "'%s' cannot be set to invalid " + "options"), propname); + (void) zfs_error(hdl, EZFS_BADPROP, + errbuf); + zfs_uninit_libshare(hdl); + goto error; + } + zfs_uninit_libshare(hdl); } + + break; + case ZFS_PROP_UTF8ONLY: + chosen_utf = (int)intval; + break; + case ZFS_PROP_NORMALIZE: + chosen_normal = (int)intval; break; } @@ -988,6 +859,7 @@ zfs_validate_properties(libzfs_handle_t *hdl, zfs_type_t type, char *pool_name, switch (prop) { case ZFS_PROP_RESERVATION: + case ZFS_PROP_REFRESERVATION: if (intval > volsize) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "'%s' is greater than current " @@ -1025,6 +897,27 @@ zfs_validate_properties(libzfs_handle_t *hdl, zfs_type_t type, char *pool_name, } /* + * If normalization was chosen, but no UTF8 choice was made, + * enforce rejection of non-UTF8 names. + * + * If normalization was chosen, but rejecting non-UTF8 names + * was explicitly not chosen, it is an error. + */ + if (chosen_normal > 0 && chosen_utf < 0) { + if (nvlist_add_uint64(ret, + zfs_prop_to_name(ZFS_PROP_UTF8ONLY), 1) != 0) { + (void) no_memory(hdl); + goto error; + } + } else if (chosen_normal > 0 && chosen_utf == 0) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "'%s' must be set 'on' if normalization chosen"), + zfs_prop_to_name(ZFS_PROP_UTF8ONLY)); + (void) zfs_error(hdl, EZFS_BADPROP, errbuf); + goto error; + } + + /* * If this is an existing volume, and someone is setting the volsize, * make sure that it matches the reservation, or add it if necessary. */ @@ -1033,23 +926,24 @@ zfs_validate_properties(libzfs_handle_t *hdl, zfs_type_t type, char *pool_name, &intval) == 0) { uint64_t old_volsize = zfs_prop_get_int(zhp, ZFS_PROP_VOLSIZE); - uint64_t old_reservation = zfs_prop_get_int(zhp, - ZFS_PROP_RESERVATION); + uint64_t old_reservation; uint64_t new_reservation; + zfs_prop_t resv_prop; + + if (zfs_which_resv_prop(zhp, &resv_prop) < 0) + goto error; + old_reservation = zfs_prop_get_int(zhp, resv_prop); if (old_volsize == old_reservation && - nvlist_lookup_uint64(ret, - zfs_prop_to_name(ZFS_PROP_RESERVATION), + nvlist_lookup_uint64(ret, zfs_prop_to_name(resv_prop), &new_reservation) != 0) { if (nvlist_add_uint64(ret, - zfs_prop_to_name(ZFS_PROP_RESERVATION), - intval) != 0) { + zfs_prop_to_name(resv_prop), intval) != 0) { (void) no_memory(hdl); goto error; } } } - return (ret); error: @@ -1057,6 +951,808 @@ error: return (NULL); } +static int +zfs_get_perm_who(const char *who, zfs_deleg_who_type_t *who_type, + uint64_t *ret_who) +{ + struct passwd *pwd; + struct group *grp; + uid_t id; + + if (*who_type == ZFS_DELEG_EVERYONE || *who_type == ZFS_DELEG_CREATE || + *who_type == ZFS_DELEG_NAMED_SET) { + *ret_who = -1; + return (0); + } + if (who == NULL && !(*who_type == ZFS_DELEG_EVERYONE)) + return (EZFS_BADWHO); + + if (*who_type == ZFS_DELEG_WHO_UNKNOWN && + strcmp(who, "everyone") == 0) { + *ret_who = -1; + *who_type = ZFS_DELEG_EVERYONE; + return (0); + } + + pwd = getpwnam(who); + grp = getgrnam(who); + + if ((*who_type == ZFS_DELEG_USER) && pwd) { + *ret_who = pwd->pw_uid; + } else if ((*who_type == ZFS_DELEG_GROUP) && grp) { + *ret_who = grp->gr_gid; + } else if (pwd) { + *ret_who = pwd->pw_uid; + *who_type = ZFS_DELEG_USER; + } else if (grp) { + *ret_who = grp->gr_gid; + *who_type = ZFS_DELEG_GROUP; + } else { + char *end; + + id = strtol(who, &end, 10); + if (errno != 0 || *end != '\0') { + return (EZFS_BADWHO); + } else { + *ret_who = id; + if (*who_type == ZFS_DELEG_WHO_UNKNOWN) + *who_type = ZFS_DELEG_USER; + } + } + + return (0); +} + +static void +zfs_perms_add_to_nvlist(nvlist_t *who_nvp, char *name, nvlist_t *perms_nvp) +{ + if (perms_nvp != NULL) { + verify(nvlist_add_nvlist(who_nvp, + name, perms_nvp) == 0); + } else { + verify(nvlist_add_boolean(who_nvp, name) == 0); + } +} + +static void +helper(zfs_deleg_who_type_t who_type, uint64_t whoid, char *whostr, + zfs_deleg_inherit_t inherit, nvlist_t *who_nvp, nvlist_t *perms_nvp, + nvlist_t *sets_nvp) +{ + boolean_t do_perms, do_sets; + char name[ZFS_MAX_DELEG_NAME]; + + do_perms = (nvlist_next_nvpair(perms_nvp, NULL) != NULL); + do_sets = (nvlist_next_nvpair(sets_nvp, NULL) != NULL); + + if (!do_perms && !do_sets) + do_perms = do_sets = B_TRUE; + + if (do_perms) { + zfs_deleg_whokey(name, who_type, inherit, + (who_type == ZFS_DELEG_NAMED_SET) ? + whostr : (void *)&whoid); + zfs_perms_add_to_nvlist(who_nvp, name, perms_nvp); + } + if (do_sets) { + zfs_deleg_whokey(name, toupper(who_type), inherit, + (who_type == ZFS_DELEG_NAMED_SET) ? + whostr : (void *)&whoid); + zfs_perms_add_to_nvlist(who_nvp, name, sets_nvp); + } +} + +static void +zfs_perms_add_who_nvlist(nvlist_t *who_nvp, uint64_t whoid, void *whostr, + nvlist_t *perms_nvp, nvlist_t *sets_nvp, + zfs_deleg_who_type_t who_type, zfs_deleg_inherit_t inherit) +{ + if (who_type == ZFS_DELEG_NAMED_SET || who_type == ZFS_DELEG_CREATE) { + helper(who_type, whoid, whostr, 0, + who_nvp, perms_nvp, sets_nvp); + } else { + if (inherit & ZFS_DELEG_PERM_LOCAL) { + helper(who_type, whoid, whostr, ZFS_DELEG_LOCAL, + who_nvp, perms_nvp, sets_nvp); + } + if (inherit & ZFS_DELEG_PERM_DESCENDENT) { + helper(who_type, whoid, whostr, ZFS_DELEG_DESCENDENT, + who_nvp, perms_nvp, sets_nvp); + } + } +} + +/* + * Construct nvlist to pass down to kernel for setting/removing permissions. + * + * The nvlist is constructed as a series of nvpairs with an optional embedded + * nvlist of permissions to remove or set. The topmost nvpairs are the actual + * base attribute named stored in the dsl. + * Arguments: + * + * whostr: is a comma separated list of users, groups, or a single set name. + * whostr may be null for everyone or create perms. + * who_type: is the type of entry in whostr. Typically this will be + * ZFS_DELEG_WHO_UNKNOWN. + * perms: common separated list of permissions. May be null if user + * is requested to remove permissions by who. + * inherit: Specifies the inheritance of the permissions. Will be either + * ZFS_DELEG_PERM_LOCAL and/or ZFS_DELEG_PERM_DESCENDENT. + * nvp The constructed nvlist to pass to zfs_perm_set(). + * The output nvp will look something like this. + * ul$1234 -> {create ; destroy } + * Ul$1234 -> { @myset } + * s-$@myset - { snapshot; checksum; compression } + */ +int +zfs_build_perms(zfs_handle_t *zhp, char *whostr, char *perms, + zfs_deleg_who_type_t who_type, zfs_deleg_inherit_t inherit, nvlist_t **nvp) +{ + nvlist_t *who_nvp; + nvlist_t *perms_nvp = NULL; + nvlist_t *sets_nvp = NULL; + char errbuf[1024]; + char *who_tok, *perm; + int error; + + *nvp = NULL; + + if (perms) { + if ((error = nvlist_alloc(&perms_nvp, + NV_UNIQUE_NAME, 0)) != 0) { + return (1); + } + if ((error = nvlist_alloc(&sets_nvp, + NV_UNIQUE_NAME, 0)) != 0) { + nvlist_free(perms_nvp); + return (1); + } + } + + if ((error = nvlist_alloc(&who_nvp, NV_UNIQUE_NAME, 0)) != 0) { + if (perms_nvp) + nvlist_free(perms_nvp); + if (sets_nvp) + nvlist_free(sets_nvp); + return (1); + } + + if (who_type == ZFS_DELEG_NAMED_SET) { + namecheck_err_t why; + char what; + + if ((error = permset_namecheck(whostr, &why, &what)) != 0) { + nvlist_free(who_nvp); + if (perms_nvp) + nvlist_free(perms_nvp); + if (sets_nvp) + nvlist_free(sets_nvp); + + switch (why) { + case NAME_ERR_NO_AT: + zfs_error_aux(zhp->zfs_hdl, + dgettext(TEXT_DOMAIN, + "set definition must begin with an '@' " + "character")); + } + return (zfs_error(zhp->zfs_hdl, + EZFS_BADPERMSET, whostr)); + } + } + + /* + * Build up nvlist(s) of permissions. Two nvlists are maintained. + * The first nvlist perms_nvp will have normal permissions and the + * other sets_nvp will have only permssion set names in it. + */ + for (perm = strtok(perms, ","); perm; perm = strtok(NULL, ",")) { + const char *perm_canonical = zfs_deleg_canonicalize_perm(perm); + + if (perm_canonical) { + verify(nvlist_add_boolean(perms_nvp, + perm_canonical) == 0); + } else if (perm[0] == '@') { + verify(nvlist_add_boolean(sets_nvp, perm) == 0); + } else { + nvlist_free(who_nvp); + nvlist_free(perms_nvp); + nvlist_free(sets_nvp); + return (zfs_error(zhp->zfs_hdl, EZFS_BADPERM, perm)); + } + } + + if (whostr && who_type != ZFS_DELEG_CREATE) { + who_tok = strtok(whostr, ","); + if (who_tok == NULL) { + nvlist_free(who_nvp); + if (perms_nvp) + nvlist_free(perms_nvp); + if (sets_nvp) + nvlist_free(sets_nvp); + (void) snprintf(errbuf, sizeof (errbuf), + dgettext(TEXT_DOMAIN, "Who string is NULL"), + whostr); + return (zfs_error(zhp->zfs_hdl, EZFS_BADWHO, errbuf)); + } + } + + /* + * Now create the nvlist(s) + */ + do { + uint64_t who_id; + + error = zfs_get_perm_who(who_tok, &who_type, + &who_id); + if (error) { + nvlist_free(who_nvp); + if (perms_nvp) + nvlist_free(perms_nvp); + if (sets_nvp) + nvlist_free(sets_nvp); + (void) snprintf(errbuf, sizeof (errbuf), + dgettext(TEXT_DOMAIN, + "Unable to determine uid/gid for " + "%s "), who_tok); + return (zfs_error(zhp->zfs_hdl, EZFS_BADWHO, errbuf)); + } + + /* + * add entries for both local and descendent when required + */ + zfs_perms_add_who_nvlist(who_nvp, who_id, who_tok, + perms_nvp, sets_nvp, who_type, inherit); + + } while (who_tok = strtok(NULL, ",")); + *nvp = who_nvp; + return (0); +} + +static int +zfs_perm_set_common(zfs_handle_t *zhp, nvlist_t *nvp, boolean_t unset) +{ + zfs_cmd_t zc = { 0 }; + int error; + char errbuf[1024]; + + (void) snprintf(errbuf, sizeof (errbuf), + dgettext(TEXT_DOMAIN, "Cannot update 'allows' for '%s'"), + zhp->zfs_name); + + if (zcmd_write_src_nvlist(zhp->zfs_hdl, &zc, nvp)) + return (-1); + + (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); + zc.zc_perm_action = unset; + + error = zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_SET_FSACL, &zc); + if (error && errno == ENOTSUP) { + (void) snprintf(errbuf, sizeof (errbuf), + gettext("Pool must be upgraded to use 'allow/unallow'")); + zcmd_free_nvlists(&zc); + return (zfs_error(zhp->zfs_hdl, EZFS_BADVERSION, errbuf)); + } else if (error) { + return (zfs_standard_error(zhp->zfs_hdl, errno, errbuf)); + } + zcmd_free_nvlists(&zc); + + return (error); +} + +int +zfs_perm_set(zfs_handle_t *zhp, nvlist_t *nvp) +{ + return (zfs_perm_set_common(zhp, nvp, B_FALSE)); +} + +int +zfs_perm_remove(zfs_handle_t *zhp, nvlist_t *perms) +{ + return (zfs_perm_set_common(zhp, perms, B_TRUE)); +} + +static int +perm_compare(const void *arg1, const void *arg2) +{ + const zfs_perm_node_t *node1 = arg1; + const zfs_perm_node_t *node2 = arg2; + int ret; + + ret = strcmp(node1->z_pname, node2->z_pname); + + if (ret > 0) + return (1); + if (ret < 0) + return (-1); + else + return (0); +} + +static void +zfs_destroy_perm_tree(avl_tree_t *tree) +{ + zfs_perm_node_t *permnode; + void *cookie = NULL; + + while ((permnode = avl_destroy_nodes(tree, &cookie)) != NULL) + free(permnode); + avl_destroy(tree); +} + +static void +zfs_destroy_tree(avl_tree_t *tree) +{ + zfs_allow_node_t *allownode; + void *cookie = NULL; + + while ((allownode = avl_destroy_nodes(tree, &cookie)) != NULL) { + zfs_destroy_perm_tree(&allownode->z_localdescend); + zfs_destroy_perm_tree(&allownode->z_local); + zfs_destroy_perm_tree(&allownode->z_descend); + free(allownode); + } + avl_destroy(tree); +} + +void +zfs_free_allows(zfs_allow_t *allow) +{ + zfs_allow_t *allownext; + zfs_allow_t *freeallow; + + allownext = allow; + while (allownext) { + zfs_destroy_tree(&allownext->z_sets); + zfs_destroy_tree(&allownext->z_crperms); + zfs_destroy_tree(&allownext->z_user); + zfs_destroy_tree(&allownext->z_group); + zfs_destroy_tree(&allownext->z_everyone); + freeallow = allownext; + allownext = allownext->z_next; + free(freeallow); + } +} + +static zfs_allow_t * +zfs_alloc_perm_tree(zfs_handle_t *zhp, zfs_allow_t *prev, char *setpoint) +{ + zfs_allow_t *ptree; + + if ((ptree = zfs_alloc(zhp->zfs_hdl, + sizeof (zfs_allow_t))) == NULL) { + return (NULL); + } + + (void) strlcpy(ptree->z_setpoint, setpoint, sizeof (ptree->z_setpoint)); + avl_create(&ptree->z_sets, + perm_compare, sizeof (zfs_allow_node_t), + offsetof(zfs_allow_node_t, z_node)); + avl_create(&ptree->z_crperms, + perm_compare, sizeof (zfs_allow_node_t), + offsetof(zfs_allow_node_t, z_node)); + avl_create(&ptree->z_user, + perm_compare, sizeof (zfs_allow_node_t), + offsetof(zfs_allow_node_t, z_node)); + avl_create(&ptree->z_group, + perm_compare, sizeof (zfs_allow_node_t), + offsetof(zfs_allow_node_t, z_node)); + avl_create(&ptree->z_everyone, + perm_compare, sizeof (zfs_allow_node_t), + offsetof(zfs_allow_node_t, z_node)); + + if (prev) + prev->z_next = ptree; + ptree->z_next = NULL; + return (ptree); +} + +/* + * Add permissions to the appropriate AVL permission tree. + * The appropriate tree may not be the requested tree. + * For example if ld indicates a local permission, but + * same permission also exists as a descendent permission + * then the permission will be removed from the descendent + * tree and add the the local+descendent tree. + */ +static int +zfs_coalesce_perm(zfs_handle_t *zhp, zfs_allow_node_t *allownode, + char *perm, char ld) +{ + zfs_perm_node_t pnode, *permnode, *permnode2; + zfs_perm_node_t *newnode; + avl_index_t where, where2; + avl_tree_t *tree, *altree; + + (void) strlcpy(pnode.z_pname, perm, sizeof (pnode.z_pname)); + + if (ld == ZFS_DELEG_NA) { + tree = &allownode->z_localdescend; + altree = &allownode->z_descend; + } else if (ld == ZFS_DELEG_LOCAL) { + tree = &allownode->z_local; + altree = &allownode->z_descend; + } else { + tree = &allownode->z_descend; + altree = &allownode->z_local; + } + permnode = avl_find(tree, &pnode, &where); + permnode2 = avl_find(altree, &pnode, &where2); + + if (permnode2) { + avl_remove(altree, permnode2); + free(permnode2); + if (permnode == NULL) { + tree = &allownode->z_localdescend; + } + } + + /* + * Now insert new permission in either requested location + * local/descendent or into ld when perm will exist in both. + */ + if (permnode == NULL) { + if ((newnode = zfs_alloc(zhp->zfs_hdl, + sizeof (zfs_perm_node_t))) == NULL) { + return (-1); + } + *newnode = pnode; + avl_add(tree, newnode); + } + return (0); +} + +/* + * Uggh, this is going to be a bit complicated. + * we have an nvlist coming out of the kernel that + * will indicate where the permission is set and then + * it will contain allow of the various "who's", and what + * their permissions are. To further complicate this + * we will then have to coalesce the local,descendent + * and local+descendent permissions where appropriate. + * The kernel only knows about a permission as being local + * or descendent, but not both. + * + * In order to make this easier for zfs_main to deal with + * a series of AVL trees will be used to maintain + * all of this, primarily for sorting purposes as well + * as the ability to quickly locate a specific entry. + * + * What we end up with are tree's for sets, create perms, + * user, groups and everyone. With each of those trees + * we have subtrees for local, descendent and local+descendent + * permissions. + */ +int +zfs_perm_get(zfs_handle_t *zhp, zfs_allow_t **zfs_perms) +{ + zfs_cmd_t zc = { 0 }; + int error; + nvlist_t *nvlist; + nvlist_t *permnv, *sourcenv; + nvpair_t *who_pair, *source_pair; + nvpair_t *perm_pair; + char errbuf[1024]; + zfs_allow_t *zallowp, *newallowp; + char ld; + char *nvpname; + uid_t uid; + gid_t gid; + avl_tree_t *tree; + avl_index_t where; + + (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); + + if (zcmd_alloc_dst_nvlist(zhp->zfs_hdl, &zc, 0) != 0) + return (-1); + + while (ioctl(zhp->zfs_hdl->libzfs_fd, ZFS_IOC_GET_FSACL, &zc) != 0) { + if (errno == ENOMEM) { + if (zcmd_expand_dst_nvlist(zhp->zfs_hdl, &zc) != 0) { + zcmd_free_nvlists(&zc); + return (-1); + } + } else if (errno == ENOTSUP) { + zcmd_free_nvlists(&zc); + (void) snprintf(errbuf, sizeof (errbuf), + gettext("Pool must be upgraded to use 'allow'")); + return (zfs_error(zhp->zfs_hdl, + EZFS_BADVERSION, errbuf)); + } else { + zcmd_free_nvlists(&zc); + return (-1); + } + } + + if (zcmd_read_dst_nvlist(zhp->zfs_hdl, &zc, &nvlist) != 0) { + zcmd_free_nvlists(&zc); + return (-1); + } + + zcmd_free_nvlists(&zc); + + source_pair = nvlist_next_nvpair(nvlist, NULL); + + if (source_pair == NULL) { + *zfs_perms = NULL; + return (0); + } + + *zfs_perms = zfs_alloc_perm_tree(zhp, NULL, nvpair_name(source_pair)); + if (*zfs_perms == NULL) { + return (0); + } + + zallowp = *zfs_perms; + + for (;;) { + struct passwd *pwd; + struct group *grp; + zfs_allow_node_t *allownode; + zfs_allow_node_t findallownode; + zfs_allow_node_t *newallownode; + + (void) strlcpy(zallowp->z_setpoint, + nvpair_name(source_pair), + sizeof (zallowp->z_setpoint)); + + if ((error = nvpair_value_nvlist(source_pair, &sourcenv)) != 0) + goto abort; + + /* + * Make sure nvlist is composed correctly + */ + if (zfs_deleg_verify_nvlist(sourcenv)) { + goto abort; + } + + who_pair = nvlist_next_nvpair(sourcenv, NULL); + if (who_pair == NULL) { + goto abort; + } + + do { + error = nvpair_value_nvlist(who_pair, &permnv); + if (error) { + goto abort; + } + + /* + * First build up the key to use + * for looking up in the various + * who trees. + */ + ld = nvpair_name(who_pair)[1]; + nvpname = nvpair_name(who_pair); + switch (nvpair_name(who_pair)[0]) { + case ZFS_DELEG_USER: + case ZFS_DELEG_USER_SETS: + tree = &zallowp->z_user; + uid = atol(&nvpname[3]); + pwd = getpwuid(uid); + (void) snprintf(findallownode.z_key, + sizeof (findallownode.z_key), "user %s", + (pwd) ? pwd->pw_name : + &nvpair_name(who_pair)[3]); + break; + case ZFS_DELEG_GROUP: + case ZFS_DELEG_GROUP_SETS: + tree = &zallowp->z_group; + gid = atol(&nvpname[3]); + grp = getgrgid(gid); + (void) snprintf(findallownode.z_key, + sizeof (findallownode.z_key), "group %s", + (grp) ? grp->gr_name : + &nvpair_name(who_pair)[3]); + break; + case ZFS_DELEG_CREATE: + case ZFS_DELEG_CREATE_SETS: + tree = &zallowp->z_crperms; + (void) strlcpy(findallownode.z_key, "", + sizeof (findallownode.z_key)); + break; + case ZFS_DELEG_EVERYONE: + case ZFS_DELEG_EVERYONE_SETS: + (void) snprintf(findallownode.z_key, + sizeof (findallownode.z_key), "everyone"); + tree = &zallowp->z_everyone; + break; + case ZFS_DELEG_NAMED_SET: + case ZFS_DELEG_NAMED_SET_SETS: + (void) snprintf(findallownode.z_key, + sizeof (findallownode.z_key), "%s", + &nvpair_name(who_pair)[3]); + tree = &zallowp->z_sets; + break; + } + + /* + * Place who in tree + */ + allownode = avl_find(tree, &findallownode, &where); + if (allownode == NULL) { + if ((newallownode = zfs_alloc(zhp->zfs_hdl, + sizeof (zfs_allow_node_t))) == NULL) { + goto abort; + } + avl_create(&newallownode->z_localdescend, + perm_compare, + sizeof (zfs_perm_node_t), + offsetof(zfs_perm_node_t, z_node)); + avl_create(&newallownode->z_local, + perm_compare, + sizeof (zfs_perm_node_t), + offsetof(zfs_perm_node_t, z_node)); + avl_create(&newallownode->z_descend, + perm_compare, + sizeof (zfs_perm_node_t), + offsetof(zfs_perm_node_t, z_node)); + (void) strlcpy(newallownode->z_key, + findallownode.z_key, + sizeof (findallownode.z_key)); + avl_insert(tree, newallownode, where); + allownode = newallownode; + } + + /* + * Now iterate over the permissions and + * place them in the appropriate local, + * descendent or local+descendent tree. + * + * The permissions are added to the tree + * via zfs_coalesce_perm(). + */ + perm_pair = nvlist_next_nvpair(permnv, NULL); + if (perm_pair == NULL) + goto abort; + do { + if (zfs_coalesce_perm(zhp, allownode, + nvpair_name(perm_pair), ld) != 0) + goto abort; + } while (perm_pair = nvlist_next_nvpair(permnv, + perm_pair)); + } while (who_pair = nvlist_next_nvpair(sourcenv, who_pair)); + + source_pair = nvlist_next_nvpair(nvlist, source_pair); + if (source_pair == NULL) + break; + + /* + * allocate another node from the link list of + * zfs_allow_t structures + */ + newallowp = zfs_alloc_perm_tree(zhp, zallowp, + nvpair_name(source_pair)); + if (newallowp == NULL) { + goto abort; + } + zallowp = newallowp; + } + nvlist_free(nvlist); + return (0); +abort: + zfs_free_allows(*zfs_perms); + nvlist_free(nvlist); + return (-1); +} + +static char * +zfs_deleg_perm_note(zfs_deleg_note_t note) +{ + /* + * Don't put newlines on end of lines + */ + switch (note) { + case ZFS_DELEG_NOTE_CREATE: + return (dgettext(TEXT_DOMAIN, + "Must also have the 'mount' ability")); + case ZFS_DELEG_NOTE_DESTROY: + return (dgettext(TEXT_DOMAIN, + "Must also have the 'mount' ability")); + case ZFS_DELEG_NOTE_SNAPSHOT: + return (dgettext(TEXT_DOMAIN, + "Must also have the 'mount' ability")); + case ZFS_DELEG_NOTE_ROLLBACK: + return (dgettext(TEXT_DOMAIN, + "Must also have the 'mount' ability")); + case ZFS_DELEG_NOTE_CLONE: + return (dgettext(TEXT_DOMAIN, "Must also have the 'create' " + "ability and 'mount'\n" + "\t\t\t\tability in the origin file system")); + case ZFS_DELEG_NOTE_PROMOTE: + return (dgettext(TEXT_DOMAIN, "Must also have the 'mount'\n" + "\t\t\t\tand 'promote' ability in the origin file system")); + case ZFS_DELEG_NOTE_RENAME: + return (dgettext(TEXT_DOMAIN, "Must also have the 'mount' " + "and 'create' \n\t\t\t\tability in the new parent")); + case ZFS_DELEG_NOTE_RECEIVE: + return (dgettext(TEXT_DOMAIN, "Must also have the 'mount'" + " and 'create' ability")); + case ZFS_DELEG_NOTE_USERPROP: + return (dgettext(TEXT_DOMAIN, + "Allows changing any user property")); + case ZFS_DELEG_NOTE_ALLOW: + return (dgettext(TEXT_DOMAIN, + "Must also have the permission that is being\n" + "\t\t\t\tallowed")); + case ZFS_DELEG_NOTE_MOUNT: + return (dgettext(TEXT_DOMAIN, + "Allows mount/umount of ZFS datasets")); + case ZFS_DELEG_NOTE_SHARE: + return (dgettext(TEXT_DOMAIN, + "Allows sharing file systems over NFS or SMB\n" + "\t\t\t\tprotocols")); + case ZFS_DELEG_NOTE_NONE: + default: + return (dgettext(TEXT_DOMAIN, "")); + } +} + +typedef enum { + ZFS_DELEG_SUBCOMMAND, + ZFS_DELEG_PROP, + ZFS_DELEG_OTHER +} zfs_deleg_perm_type_t; + +/* + * is the permission a subcommand or other? + */ +zfs_deleg_perm_type_t +zfs_deleg_perm_type(const char *perm) +{ + if (strcmp(perm, "userprop") == 0) + return (ZFS_DELEG_OTHER); + else + return (ZFS_DELEG_SUBCOMMAND); +} + +static char * +zfs_deleg_perm_type_str(zfs_deleg_perm_type_t type) +{ + switch (type) { + case ZFS_DELEG_SUBCOMMAND: + return (dgettext(TEXT_DOMAIN, "subcommand")); + case ZFS_DELEG_PROP: + return (dgettext(TEXT_DOMAIN, "property")); + case ZFS_DELEG_OTHER: + return (dgettext(TEXT_DOMAIN, "other")); + } + return (""); +} + +/*ARGSUSED*/ +static int +zfs_deleg_prop_cb(int prop, void *cb) +{ + if (zfs_prop_delegatable(prop)) + (void) fprintf(stderr, "%-15s %-15s\n", zfs_prop_to_name(prop), + zfs_deleg_perm_type_str(ZFS_DELEG_PROP)); + + return (ZPROP_CONT); +} + +void +zfs_deleg_permissions(void) +{ + int i; + + (void) fprintf(stderr, "\n%-15s %-15s\t%s\n\n", "NAME", + "TYPE", "NOTES"); + + /* + * First print out the subcommands + */ + for (i = 0; zfs_deleg_perm_tab[i].z_perm != NULL; i++) { + (void) fprintf(stderr, "%-15s %-15s\t%s\n", + zfs_deleg_perm_tab[i].z_perm, + zfs_deleg_perm_type_str( + zfs_deleg_perm_type(zfs_deleg_perm_tab[i].z_perm)), + zfs_deleg_perm_note(zfs_deleg_perm_tab[i].z_note)); + } + + (void) zprop_iter(zfs_deleg_prop_cb, NULL, B_FALSE, B_TRUE, + ZFS_TYPE_DATASET|ZFS_TYPE_VOLUME); +} + /* * Given a property name and value, set the property for the given dataset. */ @@ -1070,6 +1766,8 @@ zfs_prop_set(zfs_handle_t *zhp, const char *propname, const char *propval) libzfs_handle_t *hdl = zhp->zfs_hdl; nvlist_t *nvl = NULL, *realprops; zfs_prop_t prop; + boolean_t do_prefix; + uint64_t idx; (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, "cannot set property for '%s'"), @@ -1081,9 +1779,10 @@ zfs_prop_set(zfs_handle_t *zhp, const char *propname, const char *propval) goto error; } - if ((realprops = zfs_validate_properties(hdl, zhp->zfs_type, NULL, nvl, + if ((realprops = zfs_valid_proplist(hdl, zhp->zfs_type, nvl, zfs_prop_get_int(zhp, ZFS_PROP_ZONED), zhp, errbuf)) == NULL) goto error; + nvlist_free(nvl); nvl = realprops; @@ -1102,7 +1801,7 @@ zfs_prop_set(zfs_handle_t *zhp, const char *propname, const char *propval) goto error; } - if ((cl = changelist_gather(zhp, prop, 0)) == NULL) + if ((cl = changelist_gather(zhp, prop, 0, 0)) == NULL) goto error; if (prop == ZFS_PROP_MOUNTPOINT && changelist_haszonedchild(cl)) { @@ -1113,7 +1812,15 @@ zfs_prop_set(zfs_handle_t *zhp, const char *propname, const char *propval) goto error; } - if ((ret = changelist_prefix(cl)) != 0) + /* + * If the dataset's canmount property is being set to noauto, + * then we want to prevent unmounting & remounting it. + */ + do_prefix = !((prop == ZFS_PROP_CANMOUNT) && + (zprop_string_to_index(prop, propval, &idx, + ZFS_TYPE_DATASET) == 0) && (idx == ZFS_CANMOUNT_NOAUTO)); + + if (do_prefix && (ret = changelist_prefix(cl)) != 0) goto error; /* @@ -1121,11 +1828,10 @@ zfs_prop_set(zfs_handle_t *zhp, const char *propname, const char *propval) */ (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); - if (zcmd_write_src_nvlist(hdl, &zc, nvl, NULL) != 0) + if (zcmd_write_src_nvlist(hdl, &zc, nvl) != 0) goto error; - ret = ioctl(hdl->libzfs_fd, ZFS_IOC_SET_PROP, &zc); - + ret = zfs_ioctl(hdl, ZFS_IOC_SET_PROP, &zc); if (ret != 0) { switch (errno) { @@ -1137,6 +1843,7 @@ zfs_prop_set(zfs_handle_t *zhp, const char *propname, const char *propval) */ switch (prop) { case ZFS_PROP_QUOTA: + case ZFS_PROP_REFQUOTA: zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "size is less than current used or " "reserved space")); @@ -1144,6 +1851,7 @@ zfs_prop_set(zfs_handle_t *zhp, const char *propname, const char *propval) break; case ZFS_PROP_RESERVATION: + case ZFS_PROP_REFRESERVATION: zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "size is greater than available space")); (void) zfs_error(hdl, EZFS_PROPSPACE, errbuf); @@ -1168,10 +1876,22 @@ zfs_prop_set(zfs_handle_t *zhp, const char *propname, const char *propval) case ENOTSUP: zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "pool must be upgraded to allow gzip compression")); + "pool and or dataset must be upgraded to set this " + "property or value")); (void) zfs_error(hdl, EZFS_BADVERSION, errbuf); break; + case ERANGE: + if (prop == ZFS_PROP_COMPRESSION) { + (void) zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "property setting is not allowed on " + "bootable datasets")); + (void) zfs_error(hdl, EZFS_NOTSUP, errbuf); + } else { + (void) zfs_standard_error(hdl, errno, errbuf); + } + break; + case EOVERFLOW: /* * This platform can't address a volume this big. @@ -1187,11 +1907,14 @@ zfs_prop_set(zfs_handle_t *zhp, const char *propname, const char *propval) (void) zfs_standard_error(hdl, errno, errbuf); } } else { + if (do_prefix) + ret = changelist_postfix(cl); + /* * Refresh the statistics so the new property value * is reflected. */ - if ((ret = changelist_postfix(cl)) == 0) + if (ret == 0) (void) get_stats(zhp); } @@ -1219,7 +1942,7 @@ zfs_prop_inherit(zfs_handle_t *zhp, const char *propname) (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, "cannot inherit %s for '%s'"), propname, zhp->zfs_name); - if ((prop = zfs_name_to_prop(propname)) == ZFS_PROP_INVAL) { + if ((prop = zfs_name_to_prop(propname)) == ZPROP_INVAL) { /* * For user properties, the amount of work we have to do is very * small, so just do it here. @@ -1233,8 +1956,7 @@ zfs_prop_inherit(zfs_handle_t *zhp, const char *propname) (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); (void) strlcpy(zc.zc_value, propname, sizeof (zc.zc_value)); - if (ioctl(zhp->zfs_hdl->libzfs_fd, - ZFS_IOC_SET_PROP, &zc) != 0) + if (zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_INHERIT_PROP, &zc) != 0) return (zfs_standard_error(hdl, errno, errbuf)); return (0); @@ -1272,7 +1994,7 @@ zfs_prop_inherit(zfs_handle_t *zhp, const char *propname) /* * Determine datasets which will be affected by this change, if any. */ - if ((cl = changelist_gather(zhp, prop, 0)) == NULL) + if ((cl = changelist_gather(zhp, prop, 0, 0)) == NULL) return (-1); if (prop == ZFS_PROP_MOUNTPOINT && changelist_haszonedchild(cl)) { @@ -1286,8 +2008,7 @@ zfs_prop_inherit(zfs_handle_t *zhp, const char *propname) if ((ret = changelist_prefix(cl)) != 0) goto error; - if ((ret = ioctl(zhp->zfs_hdl->libzfs_fd, - ZFS_IOC_SET_PROP, &zc)) != 0) { + if ((ret = zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_INHERIT_PROP, &zc)) != 0) { return (zfs_standard_error(hdl, errno, errbuf)); } else { @@ -1305,15 +2026,6 @@ error: return (ret); } -void -nicebool(int value, char *buf, size_t buflen) -{ - if (value) - (void) strlcpy(buf, "on", buflen); - else - (void) strlcpy(buf, "off", buflen); -} - /* * True DSL properties are stored in an nvlist. The following two functions * extract them appropriately. @@ -1327,8 +2039,8 @@ getprop_uint64(zfs_handle_t *zhp, zfs_prop_t prop, char **source) *source = NULL; if (nvlist_lookup_nvlist(zhp->zfs_props, zfs_prop_to_name(prop), &nv) == 0) { - verify(nvlist_lookup_uint64(nv, ZFS_PROP_VALUE, &value) == 0); - (void) nvlist_lookup_string(nv, ZFS_PROP_SOURCE, source); + verify(nvlist_lookup_uint64(nv, ZPROP_VALUE, &value) == 0); + (void) nvlist_lookup_string(nv, ZPROP_SOURCE, source); } else { value = zfs_prop_default_numeric(prop); *source = ""; @@ -1346,8 +2058,8 @@ getprop_string(zfs_handle_t *zhp, zfs_prop_t prop, char **source) *source = NULL; if (nvlist_lookup_nvlist(zhp->zfs_props, zfs_prop_to_name(prop), &nv) == 0) { - verify(nvlist_lookup_string(nv, ZFS_PROP_VALUE, &value) == 0); - (void) nvlist_lookup_string(nv, ZFS_PROP_SOURCE, source); + verify(nvlist_lookup_string(nv, ZPROP_VALUE, &value) == 0); + (void) nvlist_lookup_string(nv, ZPROP_SOURCE, source); } else { if ((value = (char *)zfs_prop_default_string(prop)) == NULL) value = ""; @@ -1367,9 +2079,11 @@ getprop_string(zfs_handle_t *zhp, zfs_prop_t prop, char **source) * the source "temporary". */ static int -get_numeric_property(zfs_handle_t *zhp, zfs_prop_t prop, zfs_source_t *src, +get_numeric_property(zfs_handle_t *zhp, zfs_prop_t prop, zprop_source_t *src, char **source, uint64_t *val) { + zfs_cmd_t zc = { 0 }; + nvlist_t *zplprops = NULL; struct mnttab mnt; char *mntopt_on = NULL; char *mntopt_off = NULL; @@ -1406,6 +2120,11 @@ get_numeric_property(zfs_handle_t *zhp, zfs_prop_t prop, zfs_source_t *src, mntopt_on = MNTOPT_XATTR; mntopt_off = MNTOPT_NOXATTR; break; + + case ZFS_PROP_NBMAND: + mntopt_on = MNTOPT_NBMAND; + mntopt_off = MNTOPT_NONBMAND; + break; } /* @@ -1444,43 +2163,32 @@ get_numeric_property(zfs_handle_t *zhp, zfs_prop_t prop, zfs_source_t *src, case ZFS_PROP_READONLY: case ZFS_PROP_SETUID: case ZFS_PROP_XATTR: + case ZFS_PROP_NBMAND: *val = getprop_uint64(zhp, prop, source); if (hasmntopt(&mnt, mntopt_on) && !*val) { *val = B_TRUE; if (src) - *src = ZFS_SRC_TEMPORARY; + *src = ZPROP_SRC_TEMPORARY; } else if (hasmntopt(&mnt, mntopt_off) && *val) { *val = B_FALSE; if (src) - *src = ZFS_SRC_TEMPORARY; + *src = ZPROP_SRC_TEMPORARY; } break; - case ZFS_PROP_RECORDSIZE: - case ZFS_PROP_COMPRESSION: - case ZFS_PROP_ZONED: - case ZFS_PROP_CREATION: - case ZFS_PROP_COMPRESSRATIO: - case ZFS_PROP_REFERENCED: - case ZFS_PROP_USED: - case ZFS_PROP_CREATETXG: - case ZFS_PROP_AVAILABLE: - case ZFS_PROP_VOLSIZE: - case ZFS_PROP_VOLBLOCKSIZE: - *val = getprop_uint64(zhp, prop, source); - break; - case ZFS_PROP_CANMOUNT: *val = getprop_uint64(zhp, prop, source); - if (*val == 0) + if (*val != ZFS_CANMOUNT_ON) *source = zhp->zfs_name; else *source = ""; /* default */ break; case ZFS_PROP_QUOTA: + case ZFS_PROP_REFQUOTA: case ZFS_PROP_RESERVATION: + case ZFS_PROP_REFRESERVATION: *val = getprop_uint64(zhp, prop, source); if (*val == 0) *source = ""; /* default */ @@ -1496,11 +2204,60 @@ get_numeric_property(zfs_handle_t *zhp, zfs_prop_t prop, zfs_source_t *src, *val = zhp->zfs_dmustats.dds_num_clones; break; + case ZFS_PROP_VERSION: + case ZFS_PROP_NORMALIZE: + case ZFS_PROP_UTF8ONLY: + case ZFS_PROP_CASE: + if (!zfs_prop_valid_for_type(prop, zhp->zfs_head_type) || + zcmd_alloc_dst_nvlist(zhp->zfs_hdl, &zc, 0) != 0) + return (-1); + (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); + if (zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_OBJSET_ZPLPROPS, &zc)) { + zcmd_free_nvlists(&zc); + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "unable to get %s property"), + zfs_prop_to_name(prop)); + return (zfs_error(zhp->zfs_hdl, EZFS_BADVERSION, + dgettext(TEXT_DOMAIN, "internal error"))); + } + if (zcmd_read_dst_nvlist(zhp->zfs_hdl, &zc, &zplprops) != 0 || + nvlist_lookup_uint64(zplprops, zfs_prop_to_name(prop), + val) != 0) { + zcmd_free_nvlists(&zc); + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "unable to get %s property"), + zfs_prop_to_name(prop)); + return (zfs_error(zhp->zfs_hdl, EZFS_NOMEM, + dgettext(TEXT_DOMAIN, "internal error"))); + } + if (zplprops) + nvlist_free(zplprops); + zcmd_free_nvlists(&zc); + break; + default: - zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, - "cannot get non-numeric property")); - return (zfs_error(zhp->zfs_hdl, EZFS_BADPROP, - dgettext(TEXT_DOMAIN, "internal error"))); + switch (zfs_prop_get_type(prop)) { + case PROP_TYPE_NUMBER: + case PROP_TYPE_INDEX: + *val = getprop_uint64(zhp, prop, source); + /* + * If we tried to use a defalut value for a + * readonly property, it means that it was not + * present; return an error. + */ + if (zfs_prop_readonly(prop) && + *source && (*source)[0] == '\0') { + return (-1); + } + break; + + case PROP_TYPE_STRING: + default: + zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, + "cannot get non-numeric property")); + return (zfs_error(zhp->zfs_hdl, EZFS_BADPROP, + dgettext(TEXT_DOMAIN, "internal error"))); + } } return (0); @@ -1510,22 +2267,22 @@ get_numeric_property(zfs_handle_t *zhp, zfs_prop_t prop, zfs_source_t *src, * Calculate the source type, given the raw source string. */ static void -get_source(zfs_handle_t *zhp, zfs_source_t *srctype, char *source, +get_source(zfs_handle_t *zhp, zprop_source_t *srctype, char *source, char *statbuf, size_t statlen) { - if (statbuf == NULL || *srctype == ZFS_SRC_TEMPORARY) + if (statbuf == NULL || *srctype == ZPROP_SRC_TEMPORARY) return; if (source == NULL) { - *srctype = ZFS_SRC_NONE; + *srctype = ZPROP_SRC_NONE; } else if (source[0] == '\0') { - *srctype = ZFS_SRC_DEFAULT; + *srctype = ZPROP_SRC_DEFAULT; } else { if (strcmp(source, zhp->zfs_name) == 0) { - *srctype = ZFS_SRC_LOCAL; + *srctype = ZPROP_SRC_LOCAL; } else { (void) strlcpy(statbuf, source, statlen); - *srctype = ZFS_SRC_INHERITED; + *srctype = ZPROP_SRC_INHERITED; } } @@ -1540,12 +2297,11 @@ get_source(zfs_handle_t *zhp, zfs_source_t *srctype, char *source, */ int zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen, - zfs_source_t *src, char *statbuf, size_t statlen, boolean_t literal) + zprop_source_t *src, char *statbuf, size_t statlen, boolean_t literal) { char *source = NULL; uint64_t val; char *str; - const char *root; const char *strval; /* @@ -1555,66 +2311,9 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen, return (-1); if (src) - *src = ZFS_SRC_NONE; + *src = ZPROP_SRC_NONE; switch (prop) { - case ZFS_PROP_ATIME: - case ZFS_PROP_READONLY: - case ZFS_PROP_SETUID: - case ZFS_PROP_ZONED: - case ZFS_PROP_DEVICES: - case ZFS_PROP_EXEC: - case ZFS_PROP_CANMOUNT: - case ZFS_PROP_XATTR: - /* - * Basic boolean values are built on top of - * get_numeric_property(). - */ - if (get_numeric_property(zhp, prop, src, &source, &val) != 0) - return (-1); - nicebool(val, propbuf, proplen); - - break; - - case ZFS_PROP_AVAILABLE: - case ZFS_PROP_RECORDSIZE: - case ZFS_PROP_CREATETXG: - case ZFS_PROP_REFERENCED: - case ZFS_PROP_USED: - case ZFS_PROP_VOLSIZE: - case ZFS_PROP_VOLBLOCKSIZE: - case ZFS_PROP_NUMCLONES: - /* - * Basic numeric values are built on top of - * get_numeric_property(). - */ - if (get_numeric_property(zhp, prop, src, &source, &val) != 0) - return (-1); - if (literal) - (void) snprintf(propbuf, proplen, "%llu", - (u_longlong_t)val); - else - zfs_nicenum(val, propbuf, proplen); - break; - - case ZFS_PROP_COMPRESSION: - case ZFS_PROP_CHECKSUM: - case ZFS_PROP_SNAPDIR: -#ifdef ZFS_NO_ACL - case ZFS_PROP_ACLMODE: - case ZFS_PROP_ACLINHERIT: - case ZFS_PROP_COPIES: - val = getprop_uint64(zhp, prop, &source); - verify(zfs_prop_index_to_string(prop, val, &strval) == 0); - (void) strlcpy(propbuf, strval, proplen); - break; -#else /* ZFS_NO_ACL */ - case ZFS_PROP_ACLMODE: - case ZFS_PROP_ACLINHERIT: - (void) strlcpy(propbuf, "<unsupported>", proplen); - break; -#endif /* ZFS_NO_ACL */ - case ZFS_PROP_CREATION: /* * 'creation' is a time_t stored in the statistics. We convert @@ -1638,25 +2337,42 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen, * Getting the precise mountpoint can be tricky. * * - for 'none' or 'legacy', return those values. - * - for default mountpoints, construct it as /zfs/<dataset> * - for inherited mountpoints, we want to take everything * after our ancestor and append it to the inherited value. * * If the pool has an alternate root, we want to prepend that * root to any values we return. */ - root = zhp->zfs_root; + str = getprop_string(zhp, prop, &source); - if (str[0] == '\0') { - (void) snprintf(propbuf, proplen, "%s/zfs/%s", - root, zhp->zfs_name); - } else if (str[0] == '/') { + if (str[0] == '/') { + char buf[MAXPATHLEN]; + char *root = buf; const char *relpath = zhp->zfs_name + strlen(source); if (relpath[0] == '/') relpath++; - if (str[1] == '\0') + + if ((zpool_get_prop(zhp->zpool_hdl, + ZPOOL_PROP_ALTROOT, buf, MAXPATHLEN, NULL)) || + (strcmp(root, "-") == 0)) + root[0] = '\0'; + /* + * Special case an alternate root of '/'. This will + * avoid having multiple leading slashes in the + * mountpoint path. + */ + if (strcmp(root, "/") == 0) + root++; + + /* + * If the mountpoint is '/' then skip over this + * if we are obtaining either an alternate root or + * an inherited mountpoint. + */ + if (str[1] == '\0' && (root[0] != '\0' || + relpath[0] != '\0')) str++; if (relpath[0] == '\0') @@ -1673,13 +2389,6 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen, break; - case ZFS_PROP_SHARENFS: - case ZFS_PROP_SHAREISCSI: - case ZFS_PROP_ISCSIOPTIONS: - (void) strlcpy(propbuf, getprop_string(zhp, prop, &source), - proplen); - break; - case ZFS_PROP_ORIGIN: (void) strlcpy(propbuf, getprop_string(zhp, prop, &source), proplen); @@ -1692,7 +2401,10 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen, break; case ZFS_PROP_QUOTA: + case ZFS_PROP_REFQUOTA: case ZFS_PROP_RESERVATION: + case ZFS_PROP_REFRESERVATION: + if (get_numeric_property(zhp, prop, src, &source, &val) != 0) return (-1); @@ -1766,7 +2478,35 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen, break; default: - abort(); + switch (zfs_prop_get_type(prop)) { + case PROP_TYPE_NUMBER: + if (get_numeric_property(zhp, prop, src, + &source, &val) != 0) + return (-1); + if (literal) + (void) snprintf(propbuf, proplen, "%llu", + (u_longlong_t)val); + else + zfs_nicenum(val, propbuf, proplen); + break; + + case PROP_TYPE_STRING: + (void) strlcpy(propbuf, + getprop_string(zhp, prop, &source), proplen); + break; + + case PROP_TYPE_INDEX: + if (get_numeric_property(zhp, prop, src, + &source, &val) != 0) + return (-1); + if (zfs_prop_index_to_string(prop, val, &strval) != 0) + return (-1); + (void) strlcpy(propbuf, strval, proplen); + break; + + default: + abort(); + } } get_source(zhp, src, source, statbuf, statlen); @@ -1783,33 +2523,42 @@ uint64_t zfs_prop_get_int(zfs_handle_t *zhp, zfs_prop_t prop) { char *source; - zfs_source_t sourcetype = ZFS_SRC_NONE; uint64_t val; - (void) get_numeric_property(zhp, prop, &sourcetype, &source, &val); + (void) get_numeric_property(zhp, prop, NULL, &source, &val); return (val); } +int +zfs_prop_set_int(zfs_handle_t *zhp, zfs_prop_t prop, uint64_t val) +{ + char buf[64]; + + zfs_nicenum(val, buf, sizeof (buf)); + return (zfs_prop_set(zhp, zfs_prop_to_name(prop), buf)); +} + /* * Similar to zfs_prop_get(), but returns the value as an integer. */ int zfs_prop_get_numeric(zfs_handle_t *zhp, zfs_prop_t prop, uint64_t *value, - zfs_source_t *src, char *statbuf, size_t statlen) + zprop_source_t *src, char *statbuf, size_t statlen) { char *source; /* * Check to see if this property applies to our object */ - if (!zfs_prop_valid_for_type(prop, zhp->zfs_type)) + if (!zfs_prop_valid_for_type(prop, zhp->zfs_type)) { return (zfs_error_fmt(zhp->zfs_hdl, EZFS_PROPTYPE, dgettext(TEXT_DOMAIN, "cannot get property '%s'"), zfs_prop_to_name(prop))); + } if (src) - *src = ZFS_SRC_NONE; + *src = ZPROP_SRC_NONE; if (get_numeric_property(zhp, prop, src, &source, value) != 0) return (-1); @@ -1847,6 +2596,9 @@ zfs_iter_filesystems(zfs_handle_t *zhp, zfs_iter_f func, void *data) zfs_handle_t *nzhp; int ret; + if (zhp->zfs_type != ZFS_TYPE_FILESYSTEM) + return (0); + for ((void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); ioctl(zhp->zfs_hdl->libzfs_fd, ZFS_IOC_DATASET_LIST_NEXT, &zc) == 0; (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name))) { @@ -1890,6 +2642,9 @@ zfs_iter_snapshots(zfs_handle_t *zhp, zfs_iter_f func, void *data) zfs_handle_t *nzhp; int ret; + if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT) + return (0); + for ((void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); ioctl(zhp->zfs_hdl->libzfs_fd, ZFS_IOC_SNAPSHOT_LIST_NEXT, &zc) == 0; @@ -1948,12 +2703,16 @@ parent_name(const char *path, char *buf, size_t buflen) } /* - * Checks to make sure that the given path has a parent, and that it exists. We - * also fetch the 'zoned' property, which is used to validate property settings - * when creating new datasets. + * If accept_ancestor is false, then check to make sure that the given path has + * a parent, and that it exists. If accept_ancestor is true, then find the + * closest existing ancestor for the given path. In prefixlen return the + * length of already existing prefix of the given path. We also fetch the + * 'zoned' property, which is used to validate property settings when creating + * new datasets. */ static int -check_parents(libzfs_handle_t *hdl, const char *path, uint64_t *zoned) +check_parents(libzfs_handle_t *hdl, const char *path, uint64_t *zoned, + boolean_t accept_ancestor, int *prefixlen) { zfs_cmd_t zc = { 0 }; char parent[ZFS_MAXNAMELEN]; @@ -1984,16 +2743,22 @@ check_parents(libzfs_handle_t *hdl, const char *path, uint64_t *zoned) } /* check to see if the parent dataset exists */ - if ((zhp = make_dataset_handle(hdl, parent)) == NULL) { - switch (errno) { - case ENOENT: + while ((zhp = make_dataset_handle(hdl, parent)) == NULL) { + if (errno == ENOENT && accept_ancestor) { + /* + * Go deeper to find an ancestor, give up on top level. + */ + if (parent_name(parent, parent, sizeof (parent)) != 0) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "no such pool '%s'"), zc.zc_name); + return (zfs_error(hdl, EZFS_NOENT, errbuf)); + } + } else if (errno == ENOENT) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "parent does not exist")); return (zfs_error(hdl, EZFS_NOENT, errbuf)); - - default: + } else return (zfs_standard_error(hdl, errno, errbuf)); - } } *zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED); @@ -2014,6 +2779,136 @@ check_parents(libzfs_handle_t *hdl, const char *path, uint64_t *zoned) } zfs_close(zhp); + if (prefixlen != NULL) + *prefixlen = strlen(parent); + return (0); +} + +/* + * Finds whether the dataset of the given type(s) exists. + */ +boolean_t +zfs_dataset_exists(libzfs_handle_t *hdl, const char *path, zfs_type_t types) +{ + zfs_handle_t *zhp; + + if (!zfs_validate_name(hdl, path, types, B_FALSE)) + return (B_FALSE); + + /* + * Try to get stats for the dataset, which will tell us if it exists. + */ + if ((zhp = make_dataset_handle(hdl, path)) != NULL) { + int ds_type = zhp->zfs_type; + + zfs_close(zhp); + if (types & ds_type) + return (B_TRUE); + } + return (B_FALSE); +} + +/* + * Given a path to 'target', create all the ancestors between + * the prefixlen portion of the path, and the target itself. + * Fail if the initial prefixlen-ancestor does not already exist. + */ +int +create_parents(libzfs_handle_t *hdl, char *target, int prefixlen) +{ + zfs_handle_t *h; + char *cp; + const char *opname; + + /* make sure prefix exists */ + cp = target + prefixlen; + if (*cp != '/') { + assert(strchr(cp, '/') == NULL); + h = zfs_open(hdl, target, ZFS_TYPE_FILESYSTEM); + } else { + *cp = '\0'; + h = zfs_open(hdl, target, ZFS_TYPE_FILESYSTEM); + *cp = '/'; + } + if (h == NULL) + return (-1); + zfs_close(h); + + /* + * Attempt to create, mount, and share any ancestor filesystems, + * up to the prefixlen-long one. + */ + for (cp = target + prefixlen + 1; + cp = strchr(cp, '/'); *cp = '/', cp++) { + char *logstr; + + *cp = '\0'; + + h = make_dataset_handle(hdl, target); + if (h) { + /* it already exists, nothing to do here */ + zfs_close(h); + continue; + } + + logstr = hdl->libzfs_log_str; + hdl->libzfs_log_str = NULL; + if (zfs_create(hdl, target, ZFS_TYPE_FILESYSTEM, + NULL) != 0) { + hdl->libzfs_log_str = logstr; + opname = dgettext(TEXT_DOMAIN, "create"); + goto ancestorerr; + } + + hdl->libzfs_log_str = logstr; + h = zfs_open(hdl, target, ZFS_TYPE_FILESYSTEM); + if (h == NULL) { + opname = dgettext(TEXT_DOMAIN, "open"); + goto ancestorerr; + } + + if (zfs_mount(h, NULL, 0) != 0) { + opname = dgettext(TEXT_DOMAIN, "mount"); + goto ancestorerr; + } + + if (zfs_share(h) != 0) { + opname = dgettext(TEXT_DOMAIN, "share"); + goto ancestorerr; + } + + zfs_close(h); + } + + return (0); + +ancestorerr: + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "failed to %s ancestor '%s'"), opname, target); + return (-1); +} + +/* + * Creates non-existing ancestors of the given path. + */ +int +zfs_create_ancestors(libzfs_handle_t *hdl, const char *path) +{ + int prefix; + uint64_t zoned; + char *path_copy; + int rc; + + if (check_parents(hdl, path, &zoned, B_TRUE, &prefix) != 0) + return (-1); + + if ((path_copy = strdup(path)) != NULL) { + rc = create_parents(hdl, path_copy, prefix); + free(path_copy); + } + if (path_copy == NULL || rc != 0) + return (-1); + return (0); } @@ -2035,11 +2930,11 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type, "cannot create '%s'"), path); /* validate the path, taking care to note the extended error message */ - if (!zfs_validate_name(hdl, path, type)) + if (!zfs_validate_name(hdl, path, type, B_TRUE)) return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf)); /* validate parents exist */ - if (check_parents(hdl, path, &zoned) != 0) + if (check_parents(hdl, path, &zoned, B_FALSE, NULL) != 0) return (-1); /* @@ -2050,7 +2945,7 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type, * first try to see if the dataset exists. */ (void) strlcpy(zc.zc_name, path, sizeof (zc.zc_name)); - if (ioctl(hdl->libzfs_fd, ZFS_IOC_OBJSET_STATS, &zc) == 0) { + if (zfs_dataset_exists(hdl, zc.zc_name, ZFS_TYPE_DATASET)) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "dataset already exists")); return (zfs_error(hdl, EZFS_EXISTS, errbuf)); @@ -2061,7 +2956,7 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type, else zc.zc_objset_type = DMU_OST_ZFS; - if (props && (props = zfs_validate_properties(hdl, type, NULL, props, + if (props && (props = zfs_valid_proplist(hdl, type, props, zoned, NULL, errbuf)) == 0) return (-1); @@ -2111,13 +3006,12 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type, } } - if (props && - zcmd_write_src_nvlist(hdl, &zc, props, NULL) != 0) + if (props && zcmd_write_src_nvlist(hdl, &zc, props) != 0) return (-1); nvlist_free(props); /* create the dataset */ - ret = ioctl(hdl->libzfs_fd, ZFS_IOC_CREATE, &zc); + ret = zfs_ioctl(hdl, ZFS_IOC_CREATE, &zc); if (ret == 0 && type == ZFS_TYPE_VOLUME) { ret = zvol_create_link(hdl, path); @@ -2158,6 +3052,11 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type, return (zfs_error(hdl, EZFS_BADPROP, errbuf)); + case ENOTSUP: + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "pool must be upgraded to set this " + "property or value")); + return (zfs_error(hdl, EZFS_BADVERSION, errbuf)); #ifdef _ILP32 case EOVERFLOW: /* @@ -2189,10 +3088,13 @@ zfs_destroy(zfs_handle_t *zhp) if (ZFS_IS_VOLUME(zhp)) { /* - * Unconditionally unshare this zvol ignoring failure as it - * indicates only that the volume wasn't shared initially. + * If user doesn't have permissions to unshare volume, then + * abort the request. This would only happen for a + * non-privileged user. */ - (void) zfs_unshare_iscsi(zhp); + if (zfs_unshare_iscsi(zhp) != 0) { + return (-1); + } if (zvol_remove_link(zhp->zfs_hdl, zhp->zfs_name) != 0) return (-1); @@ -2202,7 +3104,7 @@ zfs_destroy(zfs_handle_t *zhp) zc.zc_objset_type = DMU_OST_ZFS; } - if (ioctl(zhp->zfs_hdl->libzfs_fd, ZFS_IOC_DESTROY, &zc) != 0) { + if (zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_DESTROY, &zc) != 0) { return (zfs_standard_error_fmt(zhp->zfs_hdl, errno, dgettext(TEXT_DOMAIN, "cannot destroy '%s'"), zhp->zfs_name)); @@ -2276,7 +3178,7 @@ zfs_destroy_snaps(zfs_handle_t *zhp, char *snapname) (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); (void) strlcpy(zc.zc_value, snapname, sizeof (zc.zc_value)); - ret = ioctl(zhp->zfs_hdl->libzfs_fd, ZFS_IOC_DESTROY_SNAPS, &zc); + ret = zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_DESTROY_SNAPS, &zc); if (ret != 0) { char errbuf[1024]; @@ -2318,11 +3220,11 @@ zfs_clone(zfs_handle_t *zhp, const char *target, nvlist_t *props) "cannot create '%s'"), target); /* validate the target name */ - if (!zfs_validate_name(hdl, target, ZFS_TYPE_FILESYSTEM)) + if (!zfs_validate_name(hdl, target, ZFS_TYPE_FILESYSTEM, B_TRUE)) return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf)); /* validate parents exist */ - if (check_parents(hdl, target, &zoned) != 0) + if (check_parents(hdl, target, &zoned, B_FALSE, NULL) != 0) return (-1); (void) parent_name(target, parent, sizeof (parent)); @@ -2337,11 +3239,11 @@ zfs_clone(zfs_handle_t *zhp, const char *target, nvlist_t *props) } if (props) { - if ((props = zfs_validate_properties(hdl, type, NULL, props, - zoned, zhp, errbuf)) == NULL) + if ((props = zfs_valid_proplist(hdl, type, props, zoned, + zhp, errbuf)) == NULL) return (-1); - if (zcmd_write_src_nvlist(hdl, &zc, props, NULL) != 0) { + if (zcmd_write_src_nvlist(hdl, &zc, props) != 0) { nvlist_free(props); return (-1); } @@ -2351,7 +3253,7 @@ zfs_clone(zfs_handle_t *zhp, const char *target, nvlist_t *props) (void) strlcpy(zc.zc_name, target, sizeof (zc.zc_name)); (void) strlcpy(zc.zc_value, zhp->zfs_name, sizeof (zc.zc_value)); - ret = ioctl(zhp->zfs_hdl->libzfs_fd, ZFS_IOC_CREATE, &zc); + ret = zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_CREATE, &zc); zcmd_free_nvlists(&zc); @@ -2470,7 +3372,7 @@ zfs_promote(zfs_handle_t *zhp) return (zfs_error(hdl, EZFS_BADTYPE, errbuf)); } - (void) strlcpy(parent, zhp->zfs_dmustats.dds_clone_of, sizeof (parent)); + (void) strlcpy(parent, zhp->zfs_dmustats.dds_origin, sizeof (parent)); if (parent[0] == '\0') { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "not a cloned filesystem")); @@ -2480,14 +3382,14 @@ zfs_promote(zfs_handle_t *zhp) *cp = '\0'; /* Walk the snapshots we will be moving */ - pzhp = zfs_open(hdl, zhp->zfs_dmustats.dds_clone_of, ZFS_TYPE_SNAPSHOT); + pzhp = zfs_open(hdl, zhp->zfs_dmustats.dds_origin, ZFS_TYPE_SNAPSHOT); if (pzhp == NULL) return (-1); pd.cb_pivot_txg = zfs_prop_get_int(pzhp, ZFS_PROP_CREATETXG); zfs_close(pzhp); pd.cb_target = zhp->zfs_name; pd.cb_errbuf = errbuf; - pzhp = zfs_open(hdl, parent, ZFS_TYPE_ANY); + pzhp = zfs_open(hdl, parent, ZFS_TYPE_DATASET); if (pzhp == NULL) return (-1); (void) zfs_prop_get(pzhp, ZFS_PROP_MOUNTPOINT, pd.cb_mountpoint, @@ -2499,10 +3401,10 @@ zfs_promote(zfs_handle_t *zhp) } /* issue the ioctl */ - (void) strlcpy(zc.zc_value, zhp->zfs_dmustats.dds_clone_of, + (void) strlcpy(zc.zc_value, zhp->zfs_dmustats.dds_origin, sizeof (zc.zc_value)); (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); - ret = ioctl(hdl->libzfs_fd, ZFS_IOC_PROMOTE, &zc); + ret = zfs_ioctl(hdl, ZFS_IOC_PROMOTE, &zc); if (ret != 0) { int save_errno = errno; @@ -2570,10 +3472,11 @@ zfs_create_link_cb(zfs_handle_t *zhp, void *arg) * Takes a snapshot of the given dataset. */ int -zfs_snapshot(libzfs_handle_t *hdl, const char *path, boolean_t recursive) +zfs_snapshot(libzfs_handle_t *hdl, const char *path, boolean_t recursive, + nvlist_t *props) { const char *delim; - char *parent; + char parent[ZFS_MAXNAMELEN]; zfs_handle_t *zhp; zfs_cmd_t zc = { 0 }; int ret; @@ -2583,33 +3486,52 @@ zfs_snapshot(libzfs_handle_t *hdl, const char *path, boolean_t recursive) "cannot snapshot '%s'"), path); /* validate the target name */ - if (!zfs_validate_name(hdl, path, ZFS_TYPE_SNAPSHOT)) + if (!zfs_validate_name(hdl, path, ZFS_TYPE_SNAPSHOT, B_TRUE)) return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf)); + if (props) { + if ((props = zfs_valid_proplist(hdl, ZFS_TYPE_SNAPSHOT, + props, B_FALSE, NULL, errbuf)) == NULL) + return (-1); + + if (zcmd_write_src_nvlist(hdl, &zc, props) != 0) { + nvlist_free(props); + return (-1); + } + + nvlist_free(props); + } + /* make sure the parent exists and is of the appropriate type */ delim = strchr(path, '@'); - if ((parent = zfs_alloc(hdl, delim - path + 1)) == NULL) - return (-1); (void) strncpy(parent, path, delim - path); parent[delim - path] = '\0'; if ((zhp = zfs_open(hdl, parent, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME)) == NULL) { - free(parent); + zcmd_free_nvlists(&zc); return (-1); } (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); (void) strlcpy(zc.zc_value, delim+1, sizeof (zc.zc_value)); + if (ZFS_IS_VOLUME(zhp)) + zc.zc_objset_type = DMU_OST_ZVOL; + else + zc.zc_objset_type = DMU_OST_ZFS; zc.zc_cookie = recursive; - ret = ioctl(zhp->zfs_hdl->libzfs_fd, ZFS_IOC_SNAPSHOT, &zc); + ret = zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_SNAPSHOT, &zc); + + zcmd_free_nvlists(&zc); /* * if it was recursive, the one that actually failed will be in * zc.zc_name. */ - (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, - "cannot create snapshot '%s@%s'"), zc.zc_name, zc.zc_value); + if (ret != 0) + (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, + "cannot create snapshot '%s@%s'"), zc.zc_name, zc.zc_value); + if (ret == 0 && recursive) { struct createdata cd; @@ -2620,408 +3542,24 @@ zfs_snapshot(libzfs_handle_t *hdl, const char *path, boolean_t recursive) if (ret == 0 && zhp->zfs_type == ZFS_TYPE_VOLUME) { ret = zvol_create_link(zhp->zfs_hdl, path); if (ret != 0) { - (void) ioctl(zhp->zfs_hdl->libzfs_fd, ZFS_IOC_DESTROY, - &zc); + (void) zfs_standard_error(hdl, errno, + dgettext(TEXT_DOMAIN, + "Volume successfully snapshotted, but device links " + "were not created")); + zfs_close(zhp); + return (-1); } } if (ret != 0) (void) zfs_standard_error(hdl, errno, errbuf); - free(parent); zfs_close(zhp); return (ret); } /* - * Dumps a backup of the given snapshot (incremental from fromsnap if it's not - * NULL) to the file descriptor specified by outfd. - */ -int -zfs_send(zfs_handle_t *zhp, const char *fromsnap, int outfd) -{ - zfs_cmd_t zc = { 0 }; - char errbuf[1024]; - libzfs_handle_t *hdl = zhp->zfs_hdl; - - assert(zhp->zfs_type == ZFS_TYPE_SNAPSHOT); - - (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); - if (fromsnap) - (void) strlcpy(zc.zc_value, fromsnap, sizeof (zc.zc_name)); - zc.zc_cookie = outfd; - - if (ioctl(zhp->zfs_hdl->libzfs_fd, ZFS_IOC_SENDBACKUP, &zc) != 0) { - (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, - "cannot send '%s'"), zhp->zfs_name); - - switch (errno) { - - case EXDEV: - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "not an earlier snapshot from the same fs")); - return (zfs_error(hdl, EZFS_CROSSTARGET, errbuf)); - - case EDQUOT: - case EFBIG: - case EIO: - case ENOLINK: - case ENOSPC: - case ENXIO: - case EPIPE: - case ERANGE: - case EFAULT: - case EROFS: - zfs_error_aux(hdl, strerror(errno)); - return (zfs_error(hdl, EZFS_BADBACKUP, errbuf)); - - default: - return (zfs_standard_error(hdl, errno, errbuf)); - } - } - - return (0); -} - -/* - * Create ancestors of 'target', but not target itself, and not - * ancestors whose names are shorter than prefixlen. Die if - * prefixlen-ancestor does not exist. - */ -static int -create_parents(libzfs_handle_t *hdl, char *target, int prefixlen) -{ - zfs_handle_t *h; - char *cp; - - /* make sure prefix exists */ - cp = strchr(target + prefixlen, '/'); - *cp = '\0'; - h = zfs_open(hdl, target, ZFS_TYPE_FILESYSTEM); - *cp = '/'; - if (h == NULL) - return (-1); - zfs_close(h); - - /* - * Attempt to create, mount, and share any ancestor filesystems, - * up to the prefixlen-long one. - */ - for (cp = target + prefixlen + 1; - cp = strchr(cp, '/'); *cp = '/', cp++) { - const char *opname; - - *cp = '\0'; - - h = make_dataset_handle(hdl, target); - if (h) { - /* it already exists, nothing to do here */ - zfs_close(h); - continue; - } - - opname = dgettext(TEXT_DOMAIN, "create"); - if (zfs_create(hdl, target, ZFS_TYPE_FILESYSTEM, - NULL) != 0) - goto ancestorerr; - - opname = dgettext(TEXT_DOMAIN, "open"); - h = zfs_open(hdl, target, ZFS_TYPE_FILESYSTEM); - if (h == NULL) - goto ancestorerr; - - opname = dgettext(TEXT_DOMAIN, "mount"); - if (zfs_mount(h, NULL, 0) != 0) - goto ancestorerr; - - opname = dgettext(TEXT_DOMAIN, "share"); - if (zfs_share(h) != 0) - goto ancestorerr; - - zfs_close(h); - - continue; -ancestorerr: - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "failed to %s ancestor '%s'"), opname, target); - return (-1); - } - - return (0); -} - -/* - * Restores a backup of tosnap from the file descriptor specified by infd. - */ -int -zfs_receive(libzfs_handle_t *hdl, const char *tosnap, int isprefix, - int verbose, int dryrun, boolean_t force, int infd) -{ - zfs_cmd_t zc = { 0 }; - time_t begin_time; - int ioctl_err, err, bytes, size, choplen; - char *cp; - dmu_replay_record_t drr; - struct drr_begin *drrb = &zc.zc_begin_record; - char errbuf[1024]; - prop_changelist_t *clp; - char chopprefix[ZFS_MAXNAMELEN]; - - begin_time = time(NULL); - - (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, - "cannot receive")); - - /* read in the BEGIN record */ - cp = (char *)&drr; - bytes = 0; - do { - size = read(infd, cp, sizeof (drr) - bytes); - cp += size; - bytes += size; - } while (size > 0); - - if (size < 0 || bytes != sizeof (drr)) { - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid " - "stream (failed to read first record)")); - return (zfs_error(hdl, EZFS_BADSTREAM, errbuf)); - } - - zc.zc_begin_record = drr.drr_u.drr_begin; - - if (drrb->drr_magic != DMU_BACKUP_MAGIC && - drrb->drr_magic != BSWAP_64(DMU_BACKUP_MAGIC)) { - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid " - "stream (bad magic number)")); - return (zfs_error(hdl, EZFS_BADSTREAM, errbuf)); - } - - if (drrb->drr_version != DMU_BACKUP_VERSION && - drrb->drr_version != BSWAP_64(DMU_BACKUP_VERSION)) { - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "only version " - "0x%llx is supported (stream is version 0x%llx)"), - DMU_BACKUP_VERSION, drrb->drr_version); - return (zfs_error(hdl, EZFS_BADSTREAM, errbuf)); - } - - if (strchr(drr.drr_u.drr_begin.drr_toname, '@') == NULL) { - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid " - "stream (bad snapshot name)")); - return (zfs_error(hdl, EZFS_BADSTREAM, errbuf)); - } - /* - * Determine how much of the snapshot name stored in the stream - * we are going to tack on to the name they specified on the - * command line, and how much we are going to chop off. - * - * If they specified a snapshot, chop the entire name stored in - * the stream. - */ - (void) strcpy(chopprefix, drr.drr_u.drr_begin.drr_toname); - if (isprefix) { - /* - * They specified a fs with -d, we want to tack on - * everything but the pool name stored in the stream - */ - if (strchr(tosnap, '@')) { - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid " - "argument - snapshot not allowed with -d")); - return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf)); - } - cp = strchr(chopprefix, '/'); - if (cp == NULL) - cp = strchr(chopprefix, '@'); - *cp = '\0'; - } else if (strchr(tosnap, '@') == NULL) { - /* - * If they specified a filesystem without -d, we want to - * tack on everything after the fs specified in the - * first name from the stream. - */ - cp = strchr(chopprefix, '@'); - *cp = '\0'; - } - choplen = strlen(chopprefix); - - /* - * Determine name of destination snapshot, store in zc_value. - */ - (void) strcpy(zc.zc_value, tosnap); - (void) strncat(zc.zc_value, drr.drr_u.drr_begin.drr_toname+choplen, - sizeof (zc.zc_value)); - if (!zfs_validate_name(hdl, zc.zc_value, ZFS_TYPE_SNAPSHOT)) - return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf)); - - (void) strcpy(zc.zc_name, zc.zc_value); - if (drrb->drr_fromguid) { - /* incremental backup stream */ - zfs_handle_t *h; - - /* do the recvbackup ioctl to the containing fs */ - *strchr(zc.zc_name, '@') = '\0'; - - /* make sure destination fs exists */ - h = zfs_open(hdl, zc.zc_name, - ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME); - if (h == NULL) - return (-1); - if (!dryrun) { - /* - * We need to unmount all the dependents of the dataset - * and the dataset itself. If it's a volume - * then remove device link. - */ - if (h->zfs_type == ZFS_TYPE_FILESYSTEM) { - clp = changelist_gather(h, ZFS_PROP_NAME, 0); - if (clp == NULL) - return (-1); - if (changelist_prefix(clp) != 0) { - changelist_free(clp); - return (-1); - } - } else { - (void) zvol_remove_link(hdl, h->zfs_name); - } - } - zfs_close(h); - } else { - /* full backup stream */ - - /* Make sure destination fs does not exist */ - *strchr(zc.zc_name, '@') = '\0'; - if (ioctl(hdl->libzfs_fd, ZFS_IOC_OBJSET_STATS, &zc) == 0) { - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "destination '%s' exists"), zc.zc_name); - return (zfs_error(hdl, EZFS_EXISTS, errbuf)); - } - - if (strchr(zc.zc_name, '/') == NULL) { - /* - * they're trying to do a recv into a - * nonexistant topmost filesystem. - */ - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "destination does not exist"), zc.zc_name); - return (zfs_error(hdl, EZFS_EXISTS, errbuf)); - } - - /* Do the recvbackup ioctl to the fs's parent. */ - *strrchr(zc.zc_name, '/') = '\0'; - - if (isprefix && (err = create_parents(hdl, - zc.zc_value, strlen(tosnap))) != 0) { - return (zfs_error(hdl, EZFS_BADRESTORE, errbuf)); - } - - } - - zc.zc_cookie = infd; - zc.zc_guid = force; - if (verbose) { - (void) printf("%s %s stream of %s into %s\n", - dryrun ? "would receive" : "receiving", - drrb->drr_fromguid ? "incremental" : "full", - drr.drr_u.drr_begin.drr_toname, - zc.zc_value); - (void) fflush(stdout); - } - if (dryrun) - return (0); - err = ioctl_err = ioctl(hdl->libzfs_fd, ZFS_IOC_RECVBACKUP, &zc); - if (ioctl_err != 0) { - switch (errno) { - case ENODEV: - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "most recent snapshot does not match incremental " - "source")); - (void) zfs_error(hdl, EZFS_BADRESTORE, errbuf); - break; - case ETXTBSY: - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "destination has been modified since most recent " - "snapshot")); - (void) zfs_error(hdl, EZFS_BADRESTORE, errbuf); - break; - case EEXIST: - if (drrb->drr_fromguid == 0) { - /* it's the containing fs that exists */ - cp = strchr(zc.zc_value, '@'); - *cp = '\0'; - } - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "destination already exists")); - (void) zfs_error_fmt(hdl, EZFS_EXISTS, - dgettext(TEXT_DOMAIN, "cannot restore to %s"), - zc.zc_value); - break; - case EINVAL: - (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf); - break; - case ECKSUM: - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "invalid stream (checksum mismatch)")); - (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf); - break; - default: - (void) zfs_standard_error(hdl, errno, errbuf); - } - } - - /* - * Mount or recreate the /dev links for the target filesystem - * (if created, or if we tore them down to do an incremental - * restore), and the /dev links for the new snapshot (if - * created). Also mount any children of the target filesystem - * if we did an incremental receive. - */ - cp = strchr(zc.zc_value, '@'); - if (cp && (ioctl_err == 0 || drrb->drr_fromguid)) { - zfs_handle_t *h; - - *cp = '\0'; - h = zfs_open(hdl, zc.zc_value, - ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME); - *cp = '@'; - if (h) { - if (h->zfs_type == ZFS_TYPE_VOLUME) { - err = zvol_create_link(hdl, h->zfs_name); - if (err == 0 && ioctl_err == 0) - err = zvol_create_link(hdl, - zc.zc_value); - } else { - if (drrb->drr_fromguid) { - err = changelist_postfix(clp); - changelist_free(clp); - } else { - err = zfs_mount(h, NULL, 0); - } - } - zfs_close(h); - } - } - - if (err || ioctl_err) - return (-1); - - if (verbose) { - char buf1[64]; - char buf2[64]; - uint64_t bytes = zc.zc_cookie; - time_t delta = time(NULL) - begin_time; - if (delta == 0) - delta = 1; - zfs_nicenum(bytes, buf1, sizeof (buf1)); - zfs_nicenum(bytes/delta, buf2, sizeof (buf1)); - - (void) printf("received %sb stream in %lu seconds (%sb/sec)\n", - buf1, delta, buf2); - } - - return (0); -} - -/* * Destroy any more recent snapshots. We invoke this callback on any dependents * of the snapshot first. If the 'cb_dependent' member is non-zero, then this * is a dependent and we should just destroy it without checking the transaction @@ -3030,9 +3568,9 @@ zfs_receive(libzfs_handle_t *hdl, const char *tosnap, int isprefix, typedef struct rollback_data { const char *cb_target; /* the snapshot */ uint64_t cb_create; /* creation time reference */ - prop_changelist_t *cb_clp; /* changelist pointer */ - int cb_error; + boolean_t cb_error; boolean_t cb_dependent; + boolean_t cb_force; } rollback_data_t; static int @@ -3045,23 +3583,35 @@ rollback_destroy(zfs_handle_t *zhp, void *data) zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT && zfs_prop_get_int(zhp, ZFS_PROP_CREATETXG) > cbp->cb_create) { + char *logstr; cbp->cb_dependent = B_TRUE; - if (zfs_iter_dependents(zhp, B_FALSE, rollback_destroy, - cbp) != 0) - cbp->cb_error = 1; + cbp->cb_error |= zfs_iter_dependents(zhp, B_FALSE, + rollback_destroy, cbp); cbp->cb_dependent = B_FALSE; - if (zfs_destroy(zhp) != 0) - cbp->cb_error = 1; - else - changelist_remove(zhp, cbp->cb_clp); + logstr = zhp->zfs_hdl->libzfs_log_str; + zhp->zfs_hdl->libzfs_log_str = NULL; + cbp->cb_error |= zfs_destroy(zhp); + zhp->zfs_hdl->libzfs_log_str = logstr; } } else { + /* We must destroy this clone; first unmount it */ + prop_changelist_t *clp; + + clp = changelist_gather(zhp, ZFS_PROP_NAME, 0, + cbp->cb_force ? MS_FORCE: 0); + if (clp == NULL || changelist_prefix(clp) != 0) { + cbp->cb_error = B_TRUE; + zfs_close(zhp); + return (0); + } if (zfs_destroy(zhp) != 0) - cbp->cb_error = 1; + cbp->cb_error = B_TRUE; else - changelist_remove(zhp, cbp->cb_clp); + changelist_remove(clp, zhp->zfs_name); + (void) changelist_postfix(clp); + changelist_free(clp); } zfs_close(zhp); @@ -3069,48 +3619,6 @@ rollback_destroy(zfs_handle_t *zhp, void *data) } /* - * Rollback the dataset to its latest snapshot. - */ -static int -do_rollback(zfs_handle_t *zhp) -{ - int ret; - zfs_cmd_t zc = { 0 }; - - assert(zhp->zfs_type == ZFS_TYPE_FILESYSTEM || - zhp->zfs_type == ZFS_TYPE_VOLUME); - - if (zhp->zfs_type == ZFS_TYPE_VOLUME && - zvol_remove_link(zhp->zfs_hdl, zhp->zfs_name) != 0) - return (-1); - - (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); - - if (ZFS_IS_VOLUME(zhp)) - zc.zc_objset_type = DMU_OST_ZVOL; - else - zc.zc_objset_type = DMU_OST_ZFS; - - /* - * We rely on the consumer to verify that there are no newer snapshots - * for the given dataset. Given these constraints, we can simply pass - * the name on to the ioctl() call. There is still an unlikely race - * condition where the user has taken a snapshot since we verified that - * this was the most recent. - */ - if ((ret = ioctl(zhp->zfs_hdl->libzfs_fd, ZFS_IOC_ROLLBACK, - &zc)) != 0) { - (void) zfs_standard_error_fmt(zhp->zfs_hdl, errno, - dgettext(TEXT_DOMAIN, "cannot rollback '%s'"), - zhp->zfs_name); - } else if (zhp->zfs_type == ZFS_TYPE_VOLUME) { - ret = zvol_create_link(zhp->zfs_hdl, zhp->zfs_name); - } - - return (ret); -} - -/* * Given a dataset, rollback to a specific snapshot, discarding any * data changes since then and making it the active dataset. * @@ -3118,56 +3626,87 @@ do_rollback(zfs_handle_t *zhp) * their dependents. */ int -zfs_rollback(zfs_handle_t *zhp, zfs_handle_t *snap, int flag) +zfs_rollback(zfs_handle_t *zhp, zfs_handle_t *snap, boolean_t force) { - int ret; rollback_data_t cb = { 0 }; - prop_changelist_t *clp; - - /* - * Unmount all dependendents of the dataset and the dataset itself. - * The list we need to gather is the same as for doing rename - */ - clp = changelist_gather(zhp, ZFS_PROP_NAME, flag ? MS_FORCE: 0); - if (clp == NULL) - return (-1); + int err; + zfs_cmd_t zc = { 0 }; + boolean_t restore_resv = 0; + uint64_t old_volsize, new_volsize; + zfs_prop_t resv_prop; - if ((ret = changelist_prefix(clp)) != 0) - goto out; + assert(zhp->zfs_type == ZFS_TYPE_FILESYSTEM || + zhp->zfs_type == ZFS_TYPE_VOLUME); /* * Destroy all recent snapshots and its dependends. */ + cb.cb_force = force; cb.cb_target = snap->zfs_name; cb.cb_create = zfs_prop_get_int(snap, ZFS_PROP_CREATETXG); - cb.cb_clp = clp; (void) zfs_iter_children(zhp, rollback_destroy, &cb); - if ((ret = cb.cb_error) != 0) { - (void) changelist_postfix(clp); - goto out; - } + if (cb.cb_error) + return (-1); /* * Now that we have verified that the snapshot is the latest, * rollback to the given snapshot. */ - ret = do_rollback(zhp); - if (ret != 0) { - (void) changelist_postfix(clp); - goto out; + if (zhp->zfs_type == ZFS_TYPE_VOLUME) { + if (zvol_remove_link(zhp->zfs_hdl, zhp->zfs_name) != 0) + return (-1); + if (zfs_which_resv_prop(zhp, &resv_prop) < 0) + return (-1); + old_volsize = zfs_prop_get_int(zhp, ZFS_PROP_VOLSIZE); + restore_resv = + (old_volsize == zfs_prop_get_int(zhp, resv_prop)); } + (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); + + if (ZFS_IS_VOLUME(zhp)) + zc.zc_objset_type = DMU_OST_ZVOL; + else + zc.zc_objset_type = DMU_OST_ZFS; + /* - * We only want to re-mount the filesystem if it was mounted in the - * first place. + * We rely on zfs_iter_children() to verify that there are no + * newer snapshots for the given dataset. Therefore, we can + * simply pass the name on to the ioctl() call. There is still + * an unlikely race condition where the user has taken a + * snapshot since we verified that this was the most recent. + * */ - ret = changelist_postfix(clp); + if ((err = zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_ROLLBACK, &zc)) != 0) { + (void) zfs_standard_error_fmt(zhp->zfs_hdl, errno, + dgettext(TEXT_DOMAIN, "cannot rollback '%s'"), + zhp->zfs_name); + return (err); + } -out: - changelist_free(clp); - return (ret); + /* + * For volumes, if the pre-rollback volsize matched the pre- + * rollback reservation and the volsize has changed then set + * the reservation property to the post-rollback volsize. + * Make a new handle since the rollback closed the dataset. + */ + if ((zhp->zfs_type == ZFS_TYPE_VOLUME) && + (zhp = make_dataset_handle(zhp->zfs_hdl, zhp->zfs_name))) { + if (err = zvol_create_link(zhp->zfs_hdl, zhp->zfs_name)) { + zfs_close(zhp); + return (err); + } + if (restore_resv) { + new_volsize = zfs_prop_get_int(zhp, ZFS_PROP_VOLSIZE); + if (old_volsize != new_volsize) + err = zfs_prop_set_int(zhp, resv_prop, + new_volsize); + } + zfs_close(zhp); + } + return (err); } /* @@ -3210,7 +3749,7 @@ zfs_iter_dependents(zfs_handle_t *zhp, boolean_t allowrecursion, * Renames the given dataset. */ int -zfs_rename(zfs_handle_t *zhp, const char *target, int recursive) +zfs_rename(zfs_handle_t *zhp, const char *target, boolean_t recursive) { int ret; zfs_cmd_t zc = { 0 }; @@ -3262,7 +3801,7 @@ zfs_rename(zfs_handle_t *zhp, const char *target, int recursive) errbuf)); } } - if (!zfs_validate_name(hdl, target, zhp->zfs_type)) + if (!zfs_validate_name(hdl, target, zhp->zfs_type, B_TRUE)) return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf)); } else { if (recursive) { @@ -3271,12 +3810,12 @@ zfs_rename(zfs_handle_t *zhp, const char *target, int recursive) return (zfs_error(hdl, EZFS_BADTYPE, errbuf)); } - if (!zfs_validate_name(hdl, target, zhp->zfs_type)) + if (!zfs_validate_name(hdl, target, zhp->zfs_type, B_TRUE)) return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf)); uint64_t unused; /* validate parents */ - if (check_parents(hdl, target, &unused) != 0) + if (check_parents(hdl, target, &unused, B_FALSE, NULL) != 0) return (-1); (void) parent_name(target, parent, sizeof (parent)); @@ -3313,17 +3852,22 @@ zfs_rename(zfs_handle_t *zhp, const char *target, int recursive) if (recursive) { struct destroydata dd; - parentname = strdup(zhp->zfs_name); + parentname = zfs_strdup(zhp->zfs_hdl, zhp->zfs_name); + if (parentname == NULL) { + ret = -1; + goto error; + } delim = strchr(parentname, '@'); *delim = '\0'; - zhrp = zfs_open(zhp->zfs_hdl, parentname, ZFS_TYPE_ANY); + zhrp = zfs_open(zhp->zfs_hdl, parentname, ZFS_TYPE_DATASET); if (zhrp == NULL) { - return (-1); + ret = -1; + goto error; } dd.snapname = delim + 1; dd.gotone = B_FALSE; - dd.closezhp = B_FALSE; + dd.closezhp = B_TRUE; /* We remove any zvol links prior to renaming them */ ret = zfs_iter_filesystems(zhrp, zfs_remove_link_cb, &dd); @@ -3331,7 +3875,7 @@ zfs_rename(zfs_handle_t *zhp, const char *target, int recursive) goto error; } } else { - if ((cl = changelist_gather(zhp, ZFS_PROP_NAME, 0)) == NULL) + if ((cl = changelist_gather(zhp, ZFS_PROP_NAME, 0, 0)) == NULL) return (-1); if (changelist_haszonedchild(cl)) { @@ -3356,19 +3900,19 @@ zfs_rename(zfs_handle_t *zhp, const char *target, int recursive) zc.zc_cookie = recursive; - if ((ret = ioctl(zhp->zfs_hdl->libzfs_fd, ZFS_IOC_RENAME, &zc)) != 0) { + if ((ret = zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_RENAME, &zc)) != 0) { /* * if it was recursive, the one that actually failed will * be in zc.zc_name */ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, - "cannot rename to '%s'"), zc.zc_name); + "cannot rename '%s'"), zc.zc_name); if (recursive && errno == EEXIST) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "a child dataset already has a snapshot " "with the new name")); - (void) zfs_error(hdl, EZFS_CROSSTARGET, errbuf); + (void) zfs_error(hdl, EZFS_EXISTS, errbuf); } else { (void) zfs_standard_error(zhp->zfs_hdl, errno, errbuf); } @@ -3432,6 +3976,8 @@ zvol_create_link_common(libzfs_handle_t *hdl, const char *dataset, int ifexists) zfs_cmd_t zc = { 0 }; #if 0 di_devlink_handle_t dhdl; + priv_set_t *priv_effective; + int privileged; #endif (void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name)); @@ -3470,17 +4016,51 @@ zvol_create_link_common(libzfs_handle_t *hdl, const char *dataset, int ifexists) #if 0 /* - * Call devfsadm and wait for the links to magically appear. + * If privileged call devfsadm and wait for the links to + * magically appear. + * Otherwise, print out an informational message. */ - if ((dhdl = di_devlink_init(ZFS_DRIVER, DI_MAKE_LINK)) == NULL) { - zfs_error_aux(hdl, strerror(errno)); - (void) zfs_error_fmt(hdl, EZFS_DEVLINKS, - dgettext(TEXT_DOMAIN, "cannot create device links " - "for '%s'"), dataset); - (void) ioctl(hdl->libzfs_fd, ZFS_IOC_REMOVE_MINOR, &zc); - return (-1); + + priv_effective = priv_allocset(); + (void) getppriv(PRIV_EFFECTIVE, priv_effective); + privileged = (priv_isfullset(priv_effective) == B_TRUE); + priv_freeset(priv_effective); + + if (privileged) { + if ((dhdl = di_devlink_init(ZFS_DRIVER, + DI_MAKE_LINK)) == NULL) { + zfs_error_aux(hdl, strerror(errno)); + (void) zfs_error_fmt(hdl, errno, + dgettext(TEXT_DOMAIN, "cannot create device links " + "for '%s'"), dataset); + (void) ioctl(hdl->libzfs_fd, ZFS_IOC_REMOVE_MINOR, &zc); + return (-1); + } else { + (void) di_devlink_fini(&dhdl); + } } else { - (void) di_devlink_fini(&dhdl); + char pathname[MAXPATHLEN]; + struct stat64 statbuf; + int i; + +#define MAX_WAIT 10 + + /* + * This is the poor mans way of waiting for the link + * to show up. If after 10 seconds we still don't + * have it, then print out a message. + */ + (void) snprintf(pathname, sizeof (pathname), "/dev/zvol/dsk/%s", + dataset); + + for (i = 0; i != MAX_WAIT; i++) { + if (stat64(pathname, &statbuf) == 0) + break; + (void) sleep(1); + } + if (i == MAX_WAIT) + (void) printf(gettext("%s may not be immediately " + "available\n"), pathname); } #endif @@ -3524,200 +4104,6 @@ zfs_get_user_props(zfs_handle_t *zhp) } /* - * Given a comma-separated list of properties, contruct a property list - * containing both user-defined and native properties. This function will - * return a NULL list if 'all' is specified, which can later be expanded on a - * per-dataset basis by zfs_expand_proplist(). - */ -int -zfs_get_proplist_common(libzfs_handle_t *hdl, char *fields, - zfs_proplist_t **listp, zfs_type_t type) -{ - size_t len; - char *s, *p; - char c; - zfs_prop_t prop; - zfs_proplist_t *entry; - zfs_proplist_t **last; - - *listp = NULL; - last = listp; - - /* - * If 'all' is specified, return a NULL list. - */ - if (strcmp(fields, "all") == 0) - return (0); - - /* - * If no fields were specified, return an error. - */ - if (fields[0] == '\0') { - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "no properties specified")); - return (zfs_error(hdl, EZFS_BADPROP, dgettext(TEXT_DOMAIN, - "bad property list"))); - } - - /* - * It would be nice to use getsubopt() here, but the inclusion of column - * aliases makes this more effort than it's worth. - */ - s = fields; - while (*s != '\0') { - if ((p = strchr(s, ',')) == NULL) { - len = strlen(s); - p = s + len; - } else { - len = p - s; - } - - /* - * Check for empty options. - */ - if (len == 0) { - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "empty property name")); - return (zfs_error(hdl, EZFS_BADPROP, - dgettext(TEXT_DOMAIN, "bad property list"))); - } - - /* - * Check all regular property names. - */ - c = s[len]; - s[len] = '\0'; - prop = zfs_name_to_prop_common(s, type); - - if (prop != ZFS_PROP_INVAL && - !zfs_prop_valid_for_type(prop, type)) - prop = ZFS_PROP_INVAL; - - /* - * When no property table entry can be found, return failure if - * this is a pool property or if this isn't a user-defined - * dataset property, - */ - if (prop == ZFS_PROP_INVAL && - (type & ZFS_TYPE_POOL || !zfs_prop_user(s))) { - zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "invalid property '%s'"), s); - return (zfs_error(hdl, EZFS_BADPROP, - dgettext(TEXT_DOMAIN, "bad property list"))); - } - - if ((entry = zfs_alloc(hdl, sizeof (zfs_proplist_t))) == NULL) - return (-1); - - entry->pl_prop = prop; - if (prop == ZFS_PROP_INVAL) { - if ((entry->pl_user_prop = - zfs_strdup(hdl, s)) == NULL) { - free(entry); - return (-1); - } - entry->pl_width = strlen(s); - } else { - entry->pl_width = zfs_prop_width(prop, - &entry->pl_fixed); - } - - *last = entry; - last = &entry->pl_next; - - s = p; - if (c == ',') - s++; - } - - return (0); -} - -int -zfs_get_proplist(libzfs_handle_t *hdl, char *fields, zfs_proplist_t **listp) -{ - return (zfs_get_proplist_common(hdl, fields, listp, ZFS_TYPE_ANY)); -} - -void -zfs_free_proplist(zfs_proplist_t *pl) -{ - zfs_proplist_t *next; - - while (pl != NULL) { - next = pl->pl_next; - free(pl->pl_user_prop); - free(pl); - pl = next; - } -} - -typedef struct expand_data { - zfs_proplist_t **last; - libzfs_handle_t *hdl; -} expand_data_t; - -static zfs_prop_t -zfs_expand_proplist_cb(zfs_prop_t prop, void *cb) -{ - zfs_proplist_t *entry; - expand_data_t *edp = cb; - - if ((entry = zfs_alloc(edp->hdl, sizeof (zfs_proplist_t))) == NULL) - return (ZFS_PROP_INVAL); - - entry->pl_prop = prop; - entry->pl_width = zfs_prop_width(prop, &entry->pl_fixed); - entry->pl_all = B_TRUE; - - *(edp->last) = entry; - edp->last = &entry->pl_next; - - return (ZFS_PROP_CONT); -} - -int -zfs_expand_proplist_common(libzfs_handle_t *hdl, zfs_proplist_t **plp, - zfs_type_t type) -{ - zfs_proplist_t *entry; - zfs_proplist_t **last; - expand_data_t exp; - - if (*plp == NULL) { - /* - * If this is the very first time we've been called for an 'all' - * specification, expand the list to include all native - * properties. - */ - last = plp; - - exp.last = last; - exp.hdl = hdl; - - if (zfs_prop_iter_common(zfs_expand_proplist_cb, &exp, type, - B_FALSE) == ZFS_PROP_INVAL) - return (-1); - - /* - * Add 'name' to the beginning of the list, which is handled - * specially. - */ - if ((entry = zfs_alloc(hdl, - sizeof (zfs_proplist_t))) == NULL) - return (-1); - - entry->pl_prop = ZFS_PROP_NAME; - entry->pl_width = zfs_prop_width(ZFS_PROP_NAME, - &entry->pl_fixed); - entry->pl_all = B_TRUE; - entry->pl_next = *plp; - *plp = entry; - } - return (0); -} - -/* * This function is used by 'zfs list' to determine the exact set of columns to * display, and their maximum widths. This does two main things: * @@ -3729,17 +4115,17 @@ zfs_expand_proplist_common(libzfs_handle_t *hdl, zfs_proplist_t **plp, * so that we can size the column appropriately. */ int -zfs_expand_proplist(zfs_handle_t *zhp, zfs_proplist_t **plp) +zfs_expand_proplist(zfs_handle_t *zhp, zprop_list_t **plp) { libzfs_handle_t *hdl = zhp->zfs_hdl; - zfs_proplist_t *entry; - zfs_proplist_t **last, **start; + zprop_list_t *entry; + zprop_list_t **last, **start; nvlist_t *userprops, *propval; nvpair_t *elem; char *strval; char buf[ZFS_MAXPROPLEN]; - if (zfs_expand_proplist_common(hdl, plp, ZFS_TYPE_ANY) != 0) + if (zprop_expand_list(hdl, plp, ZFS_TYPE_DATASET) != 0) return (-1); userprops = zfs_get_user_props(zhp); @@ -3753,7 +4139,7 @@ zfs_expand_proplist(zfs_handle_t *zhp, zfs_proplist_t **plp) */ start = plp; while (*start != NULL) { - if ((*start)->pl_prop == ZFS_PROP_INVAL) + if ((*start)->pl_prop == ZPROP_INVAL) break; start = &(*start)->pl_next; } @@ -3772,14 +4158,14 @@ zfs_expand_proplist(zfs_handle_t *zhp, zfs_proplist_t **plp) if (*last == NULL) { if ((entry = zfs_alloc(hdl, - sizeof (zfs_proplist_t))) == NULL || + sizeof (zprop_list_t))) == NULL || ((entry->pl_user_prop = zfs_strdup(hdl, nvpair_name(elem)))) == NULL) { free(entry); return (-1); } - entry->pl_prop = ZFS_PROP_INVAL; + entry->pl_prop = ZPROP_INVAL; entry->pl_width = strlen(nvpair_name(elem)); entry->pl_all = B_TRUE; *last = entry; @@ -3794,7 +4180,7 @@ zfs_expand_proplist(zfs_handle_t *zhp, zfs_proplist_t **plp) if (entry->pl_fixed) continue; - if (entry->pl_prop != ZFS_PROP_INVAL) { + if (entry->pl_prop != ZPROP_INVAL) { if (zfs_prop_get(zhp, entry->pl_prop, buf, sizeof (buf), NULL, NULL, 0, B_FALSE) == 0) { if (strlen(buf) > entry->pl_width) @@ -3803,7 +4189,7 @@ zfs_expand_proplist(zfs_handle_t *zhp, zfs_proplist_t **plp) } else if (nvlist_lookup_nvlist(userprops, entry->pl_user_prop, &propval) == 0) { verify(nvlist_lookup_string(propval, - ZFS_PROP_VALUE, &strval) == 0); + ZPROP_VALUE, &strval) == 0); if (strlen(strval) > entry->pl_width) entry->pl_width = strlen(strval); } @@ -3812,6 +4198,72 @@ zfs_expand_proplist(zfs_handle_t *zhp, zfs_proplist_t **plp) return (0); } +#ifdef TODO +int +zfs_iscsi_perm_check(libzfs_handle_t *hdl, char *dataset, ucred_t *cred) +{ + zfs_cmd_t zc = { 0 }; + nvlist_t *nvp; + gid_t gid; + uid_t uid; + const gid_t *groups; + int group_cnt; + int error; + + if (nvlist_alloc(&nvp, NV_UNIQUE_NAME, 0) != 0) + return (no_memory(hdl)); + + uid = ucred_geteuid(cred); + gid = ucred_getegid(cred); + group_cnt = ucred_getgroups(cred, &groups); + + if (uid == (uid_t)-1 || gid == (uid_t)-1 || group_cnt == (uid_t)-1) + return (1); + + if (nvlist_add_uint32(nvp, ZFS_DELEG_PERM_UID, uid) != 0) { + nvlist_free(nvp); + return (1); + } + + if (nvlist_add_uint32(nvp, ZFS_DELEG_PERM_GID, gid) != 0) { + nvlist_free(nvp); + return (1); + } + + if (nvlist_add_uint32_array(nvp, + ZFS_DELEG_PERM_GROUPS, (uint32_t *)groups, group_cnt) != 0) { + nvlist_free(nvp); + return (1); + } + (void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name)); + + if (zcmd_write_src_nvlist(hdl, &zc, nvp)) + return (-1); + + error = ioctl(hdl->libzfs_fd, ZFS_IOC_ISCSI_PERM_CHECK, &zc); + nvlist_free(nvp); + return (error); +} +#endif + +int +zfs_deleg_share_nfs(libzfs_handle_t *hdl, char *dataset, char *path, + void *export, void *sharetab, int sharemax, zfs_share_op_t operation) +{ + zfs_cmd_t zc = { 0 }; + int error; + + (void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name)); + (void) strlcpy(zc.zc_value, path, sizeof (zc.zc_value)); + zc.zc_share.z_sharedata = (uint64_t)(uintptr_t)sharetab; + zc.zc_share.z_exportdata = (uint64_t)(uintptr_t)export; + zc.zc_share.z_sharetype = operation; + zc.zc_share.z_sharemax = sharemax; + + error = ioctl(hdl->libzfs_fd, ZFS_IOC_SHARE, &zc); + return (error); +} + /* * Attach/detach the given filesystem to/from the given jail. */ |