diff --git a/usr/src/cmd/zpool/zpool_main.c b/usr/src/cmd/zpool/zpool_main.c index 247865ec..9c19b819 100644 --- a/usr/src/cmd/zpool/zpool_main.c +++ b/usr/src/cmd/zpool/zpool_main.c @@ -196,8 +196,8 @@ get_usage(zpool_help_t idx) { case HELP_CLEAR: return (gettext("\tclear [device]\n")); case HELP_CREATE: - return (gettext("\tcreate [-fn] [-R root] [-m mountpoint] " - " ...\n")); + return (gettext("\tcreate [-fn] [-o property=value] ... \n" + "\t [-m mountpoint] [-R root] ...\n")); case HELP_DESTROY: return (gettext("\tdestroy [-f] \n")); case HELP_DETACH: @@ -445,6 +445,52 @@ print_vdev_tree(zpool_handle_t *zhp, const char *name, nvlist_t *nv, int indent, } } +/* + * Add a property pair (name, string-value) into a property nvlist. + */ +static int +add_prop_list(const char *propname, char *propval, nvlist_t **props, + boolean_t poolprop) +{ + zpool_prop_t prop = ZPOOL_PROP_INVAL; + zfs_prop_t fprop; + nvlist_t *proplist; + const char *normnm; + char *strval; + + if (*props == NULL && + nvlist_alloc(props, NV_UNIQUE_NAME, 0) != 0) { + (void) fprintf(stderr, + gettext("internal error: out of memory\n")); + return (1); + } + + proplist = *props; + + if (poolprop) { + if ((prop = zpool_name_to_prop(propname)) == ZPOOL_PROP_INVAL) { + (void) fprintf(stderr, gettext("property '%s' is " + "not a valid pool property\n"), propname); + return (2); + } + normnm = zpool_prop_to_name(prop); + } else { + if ((fprop = zfs_name_to_prop(propname)) != ZFS_PROP_INVAL) { + normnm = zfs_prop_to_name(fprop); + } else { + normnm = propname; + } + } + + if (nvlist_add_string(proplist, normnm, propval) != 0) { + (void) fprintf(stderr, gettext("internal " + "error: out of memory\n")); + return (1); + } + + return (0); +} + /* * zpool add [-fn] ... * @@ -513,7 +559,7 @@ zpool_do_add(int argc, char **argv) } /* pass off to get_vdev_spec for processing */ - nvroot = make_root_vdev(zhp, force, !force, B_FALSE, argc, argv); + nvroot = make_root_vdev(zhp, NULL, force, !force, B_FALSE, argc, argv); if (nvroot == NULL) { zpool_close(zhp); return (1); @@ -589,7 +635,7 @@ zpool_do_remove(int argc, char **argv) } /* - * zpool create [-fn] [-R root] [-m mountpoint] ... + * zpool create [-fn] [-o property=value] ... [-R root] [-m mountpoint] ... * * -f Force creation, even if devices appear in use * -n Do not create the pool, but display the resulting layout if it @@ -597,6 +643,7 @@ zpool_do_remove(int argc, char **argv) * -R Create a pool under an alternate root * -m Set default mountpoint for the root dataset. By default it's * '/' + * -o Set property=value. * * Creates the named pool according to the given vdev specification. The * bulk of the vdev processing is done in get_vdev_spec() in zpool_vdev.c. Once @@ -616,9 +663,11 @@ zpool_do_create(int argc, char **argv) char *mountpoint = NULL; nvlist_t **child; uint_t children; + nvlist_t *props = NULL; + char *propval = NULL; /* check options */ - while ((c = getopt(argc, argv, ":fnR:m:")) != -1) { + while ((c = getopt(argc, argv, ":fnR:m:o:")) != -1) { switch (c) { case 'f': force = B_TRUE; @@ -632,6 +681,17 @@ zpool_do_create(int argc, char **argv) case 'm': mountpoint = optarg; break; + case 'o': + if ((propval = strchr(optarg, '=')) == NULL) { + (void) fprintf(stderr, gettext("missing " + "'=' for -o option\n")); + usage(B_FALSE); + } + *propval = '\0'; + propval++; + if (add_prop_list(optarg, propval, &props, B_TRUE)) + usage(B_FALSE); + break; case ':': (void) fprintf(stderr, gettext("missing argument for " "'%c' option\n"), optopt); @@ -672,7 +732,7 @@ zpool_do_create(int argc, char **argv) } /* pass off to get_vdev_spec for bulk processing */ - nvroot = make_root_vdev(NULL, force, !force, B_FALSE, argc - 1, + nvroot = make_root_vdev(NULL, props, force, !force, B_FALSE, argc - 1, argv + 1); if (nvroot == NULL) return (1); @@ -766,6 +826,12 @@ zpool_do_create(int argc, char **argv) ret = 0; } else { ret = 1; + /* + * Add other properties not describing the vdev topology + */ + if (props) + nvlist_add_nvlist(nvroot, ZPOOL_CONFIG_PROPS, props); + /* * Hand off to libzfs. */ @@ -2322,7 +2388,7 @@ zpool_do_attach_or_replace(int argc, char **argv, int replacing) return (1); } - nvroot = make_root_vdev(zhp, force, B_FALSE, replacing, argc, argv); + nvroot = make_root_vdev(zhp, NULL, force, B_FALSE, replacing, argc, argv); if (nvroot == NULL) { zpool_close(zhp); return (1); diff --git a/usr/src/cmd/zpool/zpool_util.h b/usr/src/cmd/zpool/zpool_util.h index 2a1aca03..f4e2c154 100644 --- a/usr/src/cmd/zpool/zpool_util.h +++ b/usr/src/cmd/zpool/zpool_util.h @@ -47,7 +47,7 @@ uint_t num_logs(nvlist_t *nv); * Virtual device functions */ -nvlist_t *make_root_vdev(zpool_handle_t *zhp, int force, int check_rep, +nvlist_t *make_root_vdev(zpool_handle_t *zhp, nvlist_t *props, int force, int check_rep, boolean_t isreplace, int argc, char **argv); /* diff --git a/usr/src/cmd/zpool/zpool_vdev.c b/usr/src/cmd/zpool/zpool_vdev.c index 63b93a19..d8a8c5e7 100644 --- a/usr/src/cmd/zpool/zpool_vdev.c +++ b/usr/src/cmd/zpool/zpool_vdev.c @@ -413,7 +413,7 @@ is_whole_disk(const char *arg) * xxx Shorthand for /dev/dsk/xxx */ static nvlist_t * -make_leaf_vdev(const char *arg, uint64_t is_log) +make_leaf_vdev(nvlist_t *props, const char *arg, uint64_t is_log) { char path[MAXPATHLEN]; struct stat statbuf; @@ -538,6 +538,20 @@ make_leaf_vdev(const char *arg, uint64_t is_log) (void) close(fd); } + if (props != NULL) { + + uint64_t ashift = 0; + char *value = NULL; + + if (nvlist_lookup_string(props, + zpool_prop_to_name(ZPOOL_PROP_ASHIFT), &value) == 0) + zfs_nicestrtonum(NULL, value, &ashift); + + if (ashift > 0) + verify(nvlist_add_uint64(vdev, ZPOOL_CONFIG_ASHIFT, + ashift) == 0); + } + return (vdev); } @@ -1158,7 +1172,7 @@ is_grouping(const char *type, int *mindev) * because the program is just going to exit anyway. */ nvlist_t * -construct_spec(int argc, char **argv) +construct_spec(nvlist_t *props, int argc, char **argv) { nvlist_t *nvroot, *nv, **top, **spares; int t, toplevels, mindev, nspares, nlogs; @@ -1234,7 +1248,7 @@ construct_spec(int argc, char **argv) children * sizeof (nvlist_t *)); if (child == NULL) zpool_no_memory(); - if ((nv = make_leaf_vdev(argv[c], B_FALSE)) + if ((nv = make_leaf_vdev(props, argv[c], B_FALSE)) == NULL) return (NULL); child[children - 1] = nv; @@ -1279,7 +1293,7 @@ construct_spec(int argc, char **argv) * We have a device. Pass off to make_leaf_vdev() to * construct the appropriate nvlist describing the vdev. */ - if ((nv = make_leaf_vdev(argv[0], is_log)) == NULL) + if ((nv = make_leaf_vdev(props, argv[0], is_log)) == NULL) return (NULL); if (is_log) nlogs++; @@ -1342,7 +1356,7 @@ construct_spec(int argc, char **argv) * added, even if they appear in use. */ nvlist_t * -make_root_vdev(zpool_handle_t *zhp, int force, int check_rep, +make_root_vdev(zpool_handle_t *zhp, nvlist_t *props, int force, int check_rep, boolean_t isreplacing, int argc, char **argv) { nvlist_t *newroot; @@ -1354,7 +1368,7 @@ make_root_vdev(zpool_handle_t *zhp, int force, int check_rep, * that we have a valid specification, and that all devices can be * opened. */ - if ((newroot = construct_spec(argc, argv)) == NULL) + if ((newroot = construct_spec(props, argc, argv)) == NULL) return (NULL); if (zhp && ((poolconfig = zpool_get_config(zhp, NULL)) == NULL)) diff --git a/usr/src/common/zfs/zfs_prop.c b/usr/src/common/zfs/zfs_prop.c index 4bb2d3d7..3d30d64b 100644 --- a/usr/src/common/zfs/zfs_prop.c +++ b/usr/src/common/zfs/zfs_prop.c @@ -70,7 +70,14 @@ typedef enum { PROP_DEFAULT, PROP_READONLY, - PROP_INHERIT + PROP_INHERIT, + /* + * ONETIME properties are a sort of conglomeration of READONLY + * and INHERIT. They can be set only during object creation, + * after that they are READONLY. If not explicitly set during + * creation, they can be inherited. + */ + PROP_ONETIME } prop_attr_t; typedef struct zfs_index { @@ -326,10 +333,15 @@ zfs_prop_init(void) register_number(ZFS_PROP_COMPRESSRATIO, "compressratio", 0, PROP_READONLY, ZFS_TYPE_ANY, "<1.00x or higher if compressed>", "RATIO"); + + /* readonly onetime number properties */ + register_number(ZPOOL_PROP_ASHIFT, "ashift", 0, PROP_ONETIME, + ZFS_TYPE_POOL, "", "ASHIFT"); register_number(ZFS_PROP_VOLBLOCKSIZE, "volblocksize", 8192, - PROP_READONLY, + PROP_ONETIME, ZFS_TYPE_VOLUME, "512 to 128k, power of 2", "VOLBLOCK"); + /* default number properties */ register_number(ZFS_PROP_QUOTA, "quota", 0, PROP_DEFAULT, ZFS_TYPE_FILESYSTEM, " | none", "QUOTA"); @@ -616,7 +628,17 @@ zpool_prop_default_numeric(zpool_prop_t prop) int zfs_prop_readonly(zfs_prop_t prop) { - return (zfs_prop_table[prop].pd_attr == PROP_READONLY); + return (zfs_prop_table[prop].pd_attr == PROP_READONLY || + zfs_prop_table[prop].pd_attr == PROP_ONETIME); +} + +/* + * Returns TRUE if the property is only allowed to be set once. + */ +boolean_t +zfs_prop_setonce(zfs_prop_t prop) +{ + return (zfs_prop_table[prop].pd_attr == PROP_ONETIME); } /* @@ -645,7 +667,8 @@ zpool_prop_to_name(zpool_prop_t prop) int zfs_prop_inheritable(zfs_prop_t prop) { - return (zfs_prop_table[prop].pd_attr == PROP_INHERIT); + return (zfs_prop_table[prop].pd_attr == PROP_INHERIT || + zfs_prop_table[prop].pd_attr == PROP_ONETIME); } /* diff --git a/usr/src/lib/libzfs/common/libzfs_dataset.c b/usr/src/lib/libzfs/common/libzfs_dataset.c index 58abeb60..5f214d01 100644 --- a/usr/src/lib/libzfs/common/libzfs_dataset.c +++ b/usr/src/lib/libzfs/common/libzfs_dataset.c @@ -694,7 +694,7 @@ bootfs_poolname_valid(char *pool, char *bootfs) */ 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) + nvlist_t *nvl, uint64_t zoned, zfs_handle_t *zhp, const char *errbuf, boolean_t creating) { nvpair_t *elem; const char *propname; @@ -780,7 +780,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) || (creating == B_FALSE))) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "'%s' is readonly"), propname); @@ -1036,6 +1036,26 @@ zfs_validate_properties(libzfs_handle_t *hdl, zfs_type_t type, char *pool_name, break; #endif /* !__APPLE__ */ + + case ZPOOL_PROP_ASHIFT: + if (creating == B_FALSE) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "property '%s' can only be set at " + "creation time"), propname); + (void) zfs_error(hdl, EZFS_BADPROP, errbuf); + goto error; + } + + if (intval != 0 && (intval < 9 || intval > 17)) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "property '%s' number %d is invalid."), + propname, intval); + (void) zfs_error(hdl, EZFS_BADPROP, errbuf); + goto error; + } + + break; + case ZPOOL_PROP_BOOTFS: /* * bootfs property value has to be a dataset name and @@ -1848,7 +1868,7 @@ zfs_prop_set(zfs_handle_t *zhp, const char *propname, const char *propval) } if ((realprops = zfs_validate_properties(hdl, zhp->zfs_type, NULL, nvl, - zfs_prop_get_int(zhp, ZFS_PROP_ZONED), zhp, errbuf)) == NULL) + zfs_prop_get_int(zhp, ZFS_PROP_ZONED), zhp, errbuf, B_FALSE)) == NULL) goto error; nvlist_free(nvl); nvl = realprops; @@ -2835,7 +2855,7 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type, zc.zc_objset_type = DMU_OST_ZFS; if (props && (props = zfs_validate_properties(hdl, type, NULL, props, - zoned, NULL, errbuf)) == 0) + zoned, NULL, errbuf, B_TRUE)) == 0) return (-1); if (type == ZFS_TYPE_VOLUME) { @@ -3120,7 +3140,7 @@ 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) + zoned, zhp, errbuf, B_TRUE)) == NULL) return (-1); if (zcmd_write_src_nvlist(hdl, &zc, props, NULL) != 0) { diff --git a/usr/src/lib/libzfs/common/libzfs_impl.h b/usr/src/lib/libzfs/common/libzfs_impl.h index ed575c87..e401eda7 100644 --- a/usr/src/lib/libzfs/common/libzfs_impl.h +++ b/usr/src/lib/libzfs/common/libzfs_impl.h @@ -119,7 +119,7 @@ zfs_prop_t zfs_prop_iter_common(zfs_prop_f, void *, zfs_type_t, boolean_t, zfs_prop_t zfs_name_to_prop_common(const char *, zfs_type_t); nvlist_t *zfs_validate_properties(libzfs_handle_t *, zfs_type_t, char *, - nvlist_t *, uint64_t, zfs_handle_t *zhp, const char *errbuf); + nvlist_t *, uint64_t, zfs_handle_t *zhp, const char *errbuf, boolean_t creating); typedef struct prop_changelist prop_changelist_t; diff --git a/usr/src/lib/libzfs/common/libzfs_pool.c b/usr/src/lib/libzfs/common/libzfs_pool.c index 3a6471fc..b30c431f 100644 --- a/usr/src/lib/libzfs/common/libzfs_pool.c +++ b/usr/src/lib/libzfs/common/libzfs_pool.c @@ -399,6 +399,7 @@ zpool_create(libzfs_handle_t *hdl, const char *pool, nvlist_t *nvroot, { zfs_cmd_t zc = { 0 }; char msg[1024]; + nvlist_t *props = NULL; (void) snprintf(msg, sizeof (msg), dgettext(TEXT_DOMAIN, "cannot create '%s'"), pool); @@ -410,6 +411,24 @@ zpool_create(libzfs_handle_t *hdl, const char *pool, nvlist_t *nvroot, return (zfs_error_fmt(hdl, EZFS_BADPATH, dgettext(TEXT_DOMAIN, "bad alternate root '%s'"), altroot)); + /* + * Process additional properties not (directly) describing the + * vdev tree. + */ + + if (nvlist_lookup_nvlist(nvroot, ZPOOL_CONFIG_PROPS, &props) == 0) { + char errbuf[1024]; + nvlist_t *newprops = zfs_validate_properties(hdl, ZFS_TYPE_POOL, + pool, props, 0, NULL, errbuf, B_TRUE); + nvlist_remove(nvroot, ZPOOL_CONFIG_PROPS, DATA_TYPE_NVLIST); + if (newprops) { + nvlist_add_nvlist(nvroot, ZPOOL_CONFIG_PROPS, newprops); + } else { + return(zfs_error(hdl, EZFS_BADPROP, msg)); + } + nvlist_free(newprops); + } + if (zcmd_write_src_nvlist(hdl, &zc, nvroot, NULL) != 0) return (-1); @@ -2228,7 +2247,7 @@ zpool_set_prop(zpool_handle_t *zhp, const char *propname, const char *propval) } if ((realprops = zfs_validate_properties(zhp->zpool_hdl, ZFS_TYPE_POOL, - zhp->zpool_name, nvl, 0, NULL, errbuf)) == NULL) { + zhp->zpool_name, nvl, 0, NULL, errbuf, B_FALSE)) == NULL) { nvlist_free(nvl); return (-1); } @@ -2267,6 +2286,7 @@ zpool_get_prop_int(zpool_handle_t *zhp, zpool_prop_t prop) switch (prop) { case ZPOOL_PROP_AUTOREPLACE: + case ZPOOL_PROP_ASHIFT: if (nvlist_lookup_nvlist(zhp->zpool_props, zpool_prop_to_name(prop), &nvp) != 0) { value = zpool_prop_default_numeric(prop); @@ -2347,6 +2367,21 @@ zpool_get_prop(zpool_handle_t *zhp, zpool_prop_t prop, char *propbuf, (void) strlcpy(propbuf, value ? "on" : "off", proplen); break; + case ZPOOL_PROP_ASHIFT: + if (nvlist_lookup_nvlist(zhp->zpool_props, + zpool_prop_to_name(prop), &nvp) != 0) { + value = zpool_prop_default_numeric(prop); + src = ZFS_SRC_DEFAULT; + } else { + VERIFY(nvlist_lookup_uint64(nvp, + ZFS_PROP_SOURCE, &value) == 0); + src = value; + VERIFY(nvlist_lookup_uint64(nvp, ZFS_PROP_VALUE, + &value) == 0); + } + (void) snprintf(propbuf, proplen, "%llu", value); + break; + default: return (-1); } diff --git a/usr/src/uts/common/fs/zfs/dsl_prop.c b/usr/src/uts/common/fs/zfs/dsl_prop.c index e814fe27..3fd2fae9 100644 --- a/usr/src/uts/common/fs/zfs/dsl_prop.c +++ b/usr/src/uts/common/fs/zfs/dsl_prop.c @@ -44,8 +44,13 @@ dodefault(const char *propname, int intsz, int numint, void *buf) { zfs_prop_t prop; + /* + * The setonce properties are read-only, BUT they still + * have a default value that can be used as the initial + * value. + */ if ((prop = zfs_name_to_prop(propname)) == ZFS_PROP_INVAL || - zfs_prop_readonly(prop)) + (zfs_prop_readonly(prop) && !zfs_prop_setonce(prop))) return (ENOENT); if (zfs_prop_get_type(prop) == PROP_TYPE_STRING) { diff --git a/usr/src/uts/common/fs/zfs/spa.c b/usr/src/uts/common/fs/zfs/spa.c index 33e63251..7686b15b 100644 --- a/usr/src/uts/common/fs/zfs/spa.c +++ b/usr/src/uts/common/fs/zfs/spa.c @@ -1138,6 +1138,7 @@ spa_create(const char *pool, nvlist_t *nvroot, const char *altroot, uint64_t txg = TXG_INITIAL; nvlist_t **spares; uint_t nspares; + nvlist_t *props = 0; /* * If this pool already exists, return failure. @@ -1272,6 +1273,21 @@ spa_create(const char *pool, nvlist_t *nvroot, const char *altroot, mutex_exit(&spa_namespace_lock); + /* + * Process additional properties not set diectly in above call. + */ + + if (nvlist_lookup_nvlist(nvroot, ZPOOL_CONFIG_PROPS, &props) == 0) { + /* + * Remove properties already set above + */ + nvlist_remove(nvroot, zpool_prop_to_name(ZPOOL_PROP_DELEGATION), DATA_TYPE_UINT64); + nvlist_remove(nvroot, zpool_prop_to_name(ZPOOL_PROP_BOOTFS), DATA_TYPE_UINT64); + + /* apply porperties. */ + spa_set_props(spa, props); + } + return (0); } @@ -3014,6 +3030,15 @@ spa_sync_props(void *arg1, void *arg2, cred_t *cr, dmu_tx_t *tx) zpool_prop_to_name(ZPOOL_PROP_AUTOREPLACE), 8, 1, &intval, tx) == 0); break; + + case ZPOOL_PROP_ASHIFT: + VERIFY(nvlist_lookup_uint64(nvp, + nvpair_name(nvpair), &intval) == 0); + VERIFY(zap_update(mos, + spa->spa_pool_props_object, + zpool_prop_to_name(ZPOOL_PROP_ASHIFT), 8, 1, + &intval, tx) == 0); + break; } spa_history_internal_log(LOG_POOL_PROPSET, spa, tx, cr, "%s %lld %s", diff --git a/usr/src/uts/common/fs/zfs/zfs_ioctl.c b/usr/src/uts/common/fs/zfs/zfs_ioctl.c index b28bf952..a692e881 100644 --- a/usr/src/uts/common/fs/zfs/zfs_ioctl.c +++ b/usr/src/uts/common/fs/zfs/zfs_ioctl.c @@ -1535,6 +1535,15 @@ zfs_ioc_pool_set_props(zfs_cmd_t *zc) objnum = dmu_objset_id(os); dmu_objset_close(os); break; + + case ZPOOL_PROP_ASHIFT: + /* + * Property can only be set at pool create time, and + * that code path does not go through this function. + * So unconditionally fail here. + */ + error = EPERM; + break; } if (error) diff --git a/usr/src/uts/common/sys/fs/zfs.h b/usr/src/uts/common/sys/fs/zfs.h index 2dbb168f..4fed3e99 100644 --- a/usr/src/uts/common/sys/fs/zfs.h +++ b/usr/src/uts/common/sys/fs/zfs.h @@ -106,6 +106,7 @@ typedef enum { ZPOOL_PROP_DELEGATION, ZFS_PROP_VERSION, ZPOOL_PROP_NAME, + ZPOOL_PROP_ASHIFT, ZFS_NUM_PROPS } zfs_prop_t; @@ -283,6 +284,9 @@ extern zpool_prop_t zpool_prop_iter(zpool_prop_f, void *); #define ZPOOL_CONFIG_DEGRADED "degraded" #define ZPOOL_CONFIG_REMOVED "removed" +/* Extra properties passed to spa_create. */ +#define ZPOOL_CONFIG_PROPS "props" + #define VDEV_TYPE_ROOT "root" #define VDEV_TYPE_MIRROR "mirror" #define VDEV_TYPE_REPLACING "replacing"