diff options
Diffstat (limited to 'sys/contrib/openzfs/lib/libzfs/libzfs_util.c')
-rw-r--r-- | sys/contrib/openzfs/lib/libzfs/libzfs_util.c | 679 |
1 files changed, 529 insertions, 150 deletions
diff --git a/sys/contrib/openzfs/lib/libzfs/libzfs_util.c b/sys/contrib/openzfs/lib/libzfs/libzfs_util.c index c3c009ae3a10..26f5135dff62 100644 --- a/sys/contrib/openzfs/lib/libzfs/libzfs_util.c +++ b/sys/contrib/openzfs/lib/libzfs/libzfs_util.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: CDDL-1.0 /* * CDDL HEADER START * @@ -6,7 +7,7 @@ * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE - * or http://www.opensolaris.org/os/licensing. + * or https://opensource.org/licenses/CDDL-1.0. * See the License for the specific language governing permissions * and limitations under the License. * @@ -22,7 +23,7 @@ /* * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2020 Joyent, Inc. All rights reserved. - * Copyright (c) 2011, 2020 by Delphix. All rights reserved. + * Copyright (c) 2011, 2024 by Delphix. All rights reserved. * Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com> * Copyright (c) 2017 Datto Inc. * Copyright (c) 2020 The FreeBSD Foundation @@ -68,6 +69,7 @@ * as necessary. */ #define URI_REGEX "^\\([A-Za-z][A-Za-z0-9+.\\-]*\\):" +#define STR_NUMS "0123456789" int libzfs_errno(libzfs_handle_t *hdl) @@ -170,6 +172,8 @@ libzfs_error_description(libzfs_handle_t *hdl) return (dgettext(TEXT_DOMAIN, "I/O error")); case EZFS_INTR: return (dgettext(TEXT_DOMAIN, "signal received")); + case EZFS_CKSUM: + return (dgettext(TEXT_DOMAIN, "insufficient replicas")); case EZFS_ISSPARE: return (dgettext(TEXT_DOMAIN, "device is reserved as a hot " "spare")); @@ -241,10 +245,20 @@ libzfs_error_description(libzfs_handle_t *hdl) "into a new one")); case EZFS_SCRUB_PAUSED: return (dgettext(TEXT_DOMAIN, "scrub is paused; " - "use 'zpool scrub' to resume")); + "use 'zpool scrub' to resume scrub")); + case EZFS_SCRUB_PAUSED_TO_CANCEL: + return (dgettext(TEXT_DOMAIN, "scrub is paused; " + "use 'zpool scrub' to resume or 'zpool scrub -s' to " + "cancel scrub")); case EZFS_SCRUBBING: return (dgettext(TEXT_DOMAIN, "currently scrubbing; " - "use 'zpool scrub -s' to cancel current scrub")); + "use 'zpool scrub -s' to cancel scrub")); + case EZFS_ERRORSCRUBBING: + return (dgettext(TEXT_DOMAIN, "currently error scrubbing; " + "use 'zpool scrub -s' to cancel error scrub")); + case EZFS_ERRORSCRUB_PAUSED: + return (dgettext(TEXT_DOMAIN, "error scrub is paused; " + "use 'zpool scrub -e' to resume error scrub")); case EZFS_NO_SCRUB: return (dgettext(TEXT_DOMAIN, "there is no active scrub")); case EZFS_DIFF: @@ -296,6 +310,20 @@ libzfs_error_description(libzfs_handle_t *hdl) case EZFS_REBUILDING: return (dgettext(TEXT_DOMAIN, "currently sequentially " "resilvering")); + case EZFS_VDEV_NOTSUP: + return (dgettext(TEXT_DOMAIN, "operation not supported " + "on this type of vdev")); + case EZFS_NOT_USER_NAMESPACE: + return (dgettext(TEXT_DOMAIN, "the provided file " + "was not a user namespace file")); + case EZFS_RESUME_EXISTS: + return (dgettext(TEXT_DOMAIN, "Resuming recv on existing " + "dataset without force")); + case EZFS_RAIDZ_EXPAND_IN_PROGRESS: + return (dgettext(TEXT_DOMAIN, "raidz expansion in progress")); + case EZFS_ASHIFT_MISMATCH: + return (dgettext(TEXT_DOMAIN, "adding devices with " + "different physical sector sizes is not allowed")); case EZFS_UNKNOWN: return (dgettext(TEXT_DOMAIN, "unknown error")); default: @@ -390,6 +418,10 @@ zfs_common_error(libzfs_handle_t *hdl, int error, const char *fmt, case EINTR: zfs_verror(hdl, EZFS_INTR, fmt, ap); return (-1); + + case ECKSUM: + zfs_verror(hdl, EZFS_CKSUM, fmt, ap); + return (-1); } return (0); @@ -482,8 +514,11 @@ zfs_standard_error_fmt(libzfs_handle_t *hdl, int error, const char *fmt, ...) case ZFS_ERR_BADPROP: zfs_verror(hdl, EZFS_BADPROP, fmt, ap); break; + case ZFS_ERR_NOT_USER_NAMESPACE: + zfs_verror(hdl, EZFS_NOT_USER_NAMESPACE, fmt, ap); + break; default: - zfs_error_aux(hdl, "%s", strerror(error)); + zfs_error_aux(hdl, "%s", zfs_strerror(error)); zfs_verror(hdl, EZFS_UNKNOWN, fmt, ap); break; } @@ -597,8 +632,8 @@ zfs_setprop_error(libzfs_handle_t *hdl, zfs_prop_t prop, int err, (void) zfs_error(hdl, EZFS_VOLTOOBIG, errbuf); break; } + zfs_fallthrough; #endif - fallthrough; default: (void) zfs_standard_error(hdl, err, errbuf); } @@ -670,7 +705,7 @@ zpool_standard_error_fmt(libzfs_handle_t *hdl, int error, const char *fmt, ...) case ENOSPC: case EDQUOT: zfs_verror(hdl, EZFS_NOSPC, fmt, ap); - return (-1); + break; case EAGAIN: zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, @@ -716,6 +751,9 @@ zpool_standard_error_fmt(libzfs_handle_t *hdl, int error, const char *fmt, ...) case ZFS_ERR_BADPROP: zfs_verror(hdl, EZFS_BADPROP, fmt, ap); break; + case ZFS_ERR_VDEV_NOTSUP: + zfs_verror(hdl, EZFS_VDEV_NOTSUP, fmt, ap); + break; case ZFS_ERR_IOC_CMD_UNAVAIL: zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "the loaded zfs " "module does not support this operation. A reboot may " @@ -732,8 +770,19 @@ zpool_standard_error_fmt(libzfs_handle_t *hdl, int error, const char *fmt, ...) case ZFS_ERR_IOC_ARG_BADTYPE: zfs_verror(hdl, EZFS_IOC_NOTSUPPORTED, fmt, ap); break; + case ZFS_ERR_RAIDZ_EXPAND_IN_PROGRESS: + zfs_verror(hdl, EZFS_RAIDZ_EXPAND_IN_PROGRESS, fmt, ap); + break; + case ZFS_ERR_ASHIFT_MISMATCH: + zfs_verror(hdl, EZFS_ASHIFT_MISMATCH, fmt, ap); + break; + case ZFS_ERR_TOO_MANY_SITOUTS: + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "too many disks " + "already sitting out")); + zfs_verror(hdl, EZFS_BUSY, fmt, ap); + break; default: - zfs_error_aux(hdl, "%s", strerror(error)); + zfs_error_aux(hdl, "%s", zfs_strerror(error)); zfs_verror(hdl, EZFS_UNKNOWN, fmt, ap); } @@ -741,6 +790,12 @@ zpool_standard_error_fmt(libzfs_handle_t *hdl, int error, const char *fmt, ...) return (-1); } +int +zfs_ioctl(libzfs_handle_t *hdl, int request, zfs_cmd_t *zc) +{ + return (lzc_ioctl_fd(hdl->libzfs_fd, request, zc)); +} + /* * Display an out of memory error message and abort the current program. */ @@ -801,7 +856,7 @@ zfs_realloc(libzfs_handle_t *hdl, void *ptr, size_t oldsize, size_t newsize) return (NULL); } - bzero((char *)ret + oldsize, (newsize - oldsize)); + memset((char *)ret + oldsize, 0, newsize - oldsize); return (ret); } @@ -889,6 +944,7 @@ libzfs_run_process_impl(const char *path, char *argv[], char *env[], int flags, pid = fork(); if (pid == 0) { /* Child process */ + setpgid(0, 0); devnull_fd = open("/dev/null", O_WRONLY | O_CLOEXEC); if (devnull_fd < 0) @@ -987,16 +1043,13 @@ libzfs_free_str_array(char **strs, int count) * * Returns 0 otherwise. */ -int -libzfs_envvar_is_set(char *envvar) +boolean_t +libzfs_envvar_is_set(const char *envvar) { char *env = getenv(envvar); - if (env && (strtoul(env, NULL, 0) > 0 || + return (env && (strtoul(env, NULL, 0) > 0 || (!strncasecmp(env, "YES", 3) && strnlen(env, 4) == 3) || - (!strncasecmp(env, "ON", 2) && strnlen(env, 3) == 2))) - return (1); - - return (0); + (!strncasecmp(env, "ON", 2) && strnlen(env, 3) == 2))); } libzfs_handle_t * @@ -1034,6 +1087,7 @@ libzfs_init(void) zfs_prop_init(); zpool_prop_init(); zpool_feature_init(); + vdev_prop_init(); libzfs_mnttab_init(hdl); fletcher_4_init(); @@ -1142,7 +1196,7 @@ zfs_path_to_zhandle(libzfs_handle_t *hdl, const char *path, zfs_type_t argtype) * Initialize the zc_nvlist_dst member to prepare for receiving an nvlist from * an ioctl(). */ -int +void zcmd_alloc_dst_nvlist(libzfs_handle_t *hdl, zfs_cmd_t *zc, size_t len) { if (len == 0) @@ -1150,10 +1204,6 @@ zcmd_alloc_dst_nvlist(libzfs_handle_t *hdl, zfs_cmd_t *zc, size_t len) zc->zc_nvlist_dst_size = len; zc->zc_nvlist_dst = (uint64_t)(uintptr_t)zfs_alloc(hdl, zc->zc_nvlist_dst_size); - if (zc->zc_nvlist_dst == 0) - return (-1); - - return (0); } /* @@ -1161,16 +1211,12 @@ zcmd_alloc_dst_nvlist(libzfs_handle_t *hdl, zfs_cmd_t *zc, size_t len) * expand the nvlist to the size specified in 'zc_nvlist_dst_size', which was * filled in by the kernel to indicate the actual required size. */ -int +void zcmd_expand_dst_nvlist(libzfs_handle_t *hdl, zfs_cmd_t *zc) { free((void *)(uintptr_t)zc->zc_nvlist_dst); zc->zc_nvlist_dst = (uint64_t)(uintptr_t)zfs_alloc(hdl, zc->zc_nvlist_dst_size); - if (zc->zc_nvlist_dst == 0) - return (-1); - - return (0); } /* @@ -1187,38 +1233,33 @@ zcmd_free_nvlists(zfs_cmd_t *zc) zc->zc_nvlist_dst = 0; } -static int +static void zcmd_write_nvlist_com(libzfs_handle_t *hdl, uint64_t *outnv, uint64_t *outlen, nvlist_t *nvl) { char *packed; - size_t len; - - verify(nvlist_size(nvl, &len, NV_ENCODE_NATIVE) == 0); - if ((packed = zfs_alloc(hdl, len)) == NULL) - return (-1); + size_t len = fnvlist_size(nvl); + packed = zfs_alloc(hdl, len); verify(nvlist_pack(nvl, &packed, &len, NV_ENCODE_NATIVE, 0) == 0); *outnv = (uint64_t)(uintptr_t)packed; *outlen = len; - - return (0); } -int +void zcmd_write_conf_nvlist(libzfs_handle_t *hdl, zfs_cmd_t *zc, nvlist_t *nvl) { - return (zcmd_write_nvlist_com(hdl, &zc->zc_nvlist_conf, - &zc->zc_nvlist_conf_size, nvl)); + zcmd_write_nvlist_com(hdl, &zc->zc_nvlist_conf, + &zc->zc_nvlist_conf_size, nvl); } -int +void zcmd_write_src_nvlist(libzfs_handle_t *hdl, zfs_cmd_t *zc, nvlist_t *nvl) { - return (zcmd_write_nvlist_com(hdl, &zc->zc_nvlist_src, - &zc->zc_nvlist_src_size, nvl)); + zcmd_write_nvlist_com(hdl, &zc->zc_nvlist_src, + &zc->zc_nvlist_src_size, nvl); } /* @@ -1240,10 +1281,18 @@ zcmd_read_dst_nvlist(libzfs_handle_t *hdl, zfs_cmd_t *zc, nvlist_t **nvlp) * ================================================================ */ +void +zcmd_print_json(nvlist_t *nvl) +{ + nvlist_print_json(stdout, nvl); + (void) putchar('\n'); + nvlist_free(nvl); +} + static void zprop_print_headers(zprop_get_cbdata_t *cbp, zfs_type_t type) { - zprop_list_t *pl = cbp->cb_proplist; + zprop_list_t *pl; int i; char *title; size_t len; @@ -1267,7 +1316,8 @@ zprop_print_headers(zprop_get_cbdata_t *cbp, zfs_type_t type) /* first property is always NAME */ assert(cbp->cb_proplist->pl_prop == - ((type == ZFS_TYPE_POOL) ? ZPOOL_PROP_NAME : ZFS_PROP_NAME)); + ((type == ZFS_TYPE_POOL) ? ZPOOL_PROP_NAME : + ((type == ZFS_TYPE_VDEV) ? VDEV_PROP_NAME : ZFS_PROP_NAME))); /* * Go through and calculate the widths for each column. For the @@ -1281,15 +1331,19 @@ zprop_print_headers(zprop_get_cbdata_t *cbp, zfs_type_t type) /* * 'PROPERTY' column */ - if (pl->pl_prop != ZPROP_INVAL) { + if (pl->pl_prop != ZPROP_USERPROP) { const char *propname = (type == ZFS_TYPE_POOL) ? zpool_prop_to_name(pl->pl_prop) : - zfs_prop_to_name(pl->pl_prop); + ((type == ZFS_TYPE_VDEV) ? + vdev_prop_to_name(pl->pl_prop) : + zfs_prop_to_name(pl->pl_prop)); + assert(propname != NULL); len = strlen(propname); if (len > cbp->cb_colwidths[GET_COL_PROPERTY]) cbp->cb_colwidths[GET_COL_PROPERTY] = len; } else { + assert(pl->pl_user_prop != NULL); len = strlen(pl->pl_user_prop); if (len > cbp->cb_colwidths[GET_COL_PROPERTY]) cbp->cb_colwidths[GET_COL_PROPERTY] = len; @@ -1314,9 +1368,10 @@ zprop_print_headers(zprop_get_cbdata_t *cbp, zfs_type_t type) /* * 'NAME' and 'SOURCE' columns */ - if (pl->pl_prop == (type == ZFS_TYPE_POOL ? ZPOOL_PROP_NAME : - ZFS_PROP_NAME) && - pl->pl_width > cbp->cb_colwidths[GET_COL_NAME]) { + if (pl->pl_prop == ((type == ZFS_TYPE_POOL) ? ZPOOL_PROP_NAME : + ((type == ZFS_TYPE_VDEV) ? VDEV_PROP_NAME : + ZFS_PROP_NAME)) && pl->pl_width > + cbp->cb_colwidths[GET_COL_NAME]) { cbp->cb_colwidths[GET_COL_NAME] = pl->pl_width; cbp->cb_colwidths[GET_COL_SOURCE] = pl->pl_width + strlen(dgettext(TEXT_DOMAIN, "inherited from")); @@ -1361,6 +1416,103 @@ zprop_print_headers(zprop_get_cbdata_t *cbp, zfs_type_t type) } /* + * Add property value and source to provided nvlist, according to + * settings in cb structure. Later to be printed in JSON format. + */ +int +zprop_nvlist_one_property(const char *propname, + const char *value, zprop_source_t sourcetype, const char *source, + const char *recvd_value, nvlist_t *nvl, boolean_t as_int) +{ + int ret = 0; + nvlist_t *src_nv, *prop; + boolean_t all_numeric = strspn(value, STR_NUMS) == strlen(value); + src_nv = prop = NULL; + + if ((nvlist_alloc(&prop, NV_UNIQUE_NAME, 0) != 0) || + (nvlist_alloc(&src_nv, NV_UNIQUE_NAME, 0) != 0)) { + ret = -1; + goto err; + } + + if (as_int && all_numeric) { + uint64_t val; + sscanf(value, "%lld", (u_longlong_t *)&val); + if (nvlist_add_uint64(prop, "value", val) != 0) { + ret = -1; + goto err; + } + } else { + if (nvlist_add_string(prop, "value", value) != 0) { + ret = -1; + goto err; + } + } + + switch (sourcetype) { + case ZPROP_SRC_NONE: + if (nvlist_add_string(src_nv, "type", "NONE") != 0 || + (nvlist_add_string(src_nv, "data", "-") != 0)) { + ret = -1; + goto err; + } + break; + case ZPROP_SRC_DEFAULT: + if (nvlist_add_string(src_nv, "type", "DEFAULT") != 0 || + (nvlist_add_string(src_nv, "data", "-") != 0)) { + ret = -1; + goto err; + } + break; + case ZPROP_SRC_LOCAL: + if (nvlist_add_string(src_nv, "type", "LOCAL") != 0 || + (nvlist_add_string(src_nv, "data", "-") != 0)) { + ret = -1; + goto err; + } + break; + case ZPROP_SRC_TEMPORARY: + if (nvlist_add_string(src_nv, "type", "TEMPORARY") != 0 || + (nvlist_add_string(src_nv, "data", "-") != 0)) { + ret = -1; + goto err; + } + break; + case ZPROP_SRC_INHERITED: + if (nvlist_add_string(src_nv, "type", "INHERITED") != 0 || + (nvlist_add_string(src_nv, "data", source) != 0)) { + ret = -1; + goto err; + } + break; + case ZPROP_SRC_RECEIVED: + if (nvlist_add_string(src_nv, "type", "RECEIVED") != 0 || + (nvlist_add_string(src_nv, "data", + (recvd_value == NULL ? "-" : recvd_value)) != 0)) { + ret = -1; + goto err; + } + break; + default: + assert(!"unhandled zprop_source_t"); + if (nvlist_add_string(src_nv, "type", + "unhandled zprop_source_t") != 0) { + ret = -1; + goto err; + } + } + if ((nvlist_add_nvlist(prop, "source", src_nv) != 0) || + (nvlist_add_nvlist(nvl, propname, prop)) != 0) { + ret = -1; + goto err; + } +err: + nvlist_free(src_nv); + nvlist_free(prop); + return (ret); +} + +/* * Display a single line of output, according to the settings in the callback * structure. */ @@ -1451,6 +1603,26 @@ zprop_print_one_property(const char *name, zprop_get_cbdata_t *cbp, (void) printf("\n"); } +int +zprop_collect_property(const char *name, zprop_get_cbdata_t *cbp, + const char *propname, const char *value, zprop_source_t sourcetype, + const char *source, const char *recvd_value, nvlist_t *nvl) +{ + if (cbp->cb_json) { + if ((sourcetype & cbp->cb_sources) == 0) + return (0); + else { + return (zprop_nvlist_one_property(propname, value, + sourcetype, source, recvd_value, nvl, + cbp->cb_json_as_int)); + } + } else { + zprop_print_one_property(name, cbp, + propname, value, sourcetype, source, recvd_value); + return (0); + } +} + /* * Given a numeric suffix, convert the value into a number of bits that the * resulting value must be shifted. @@ -1459,15 +1631,17 @@ static int str2shift(libzfs_handle_t *hdl, const char *buf) { const char *ends = "BKMGTPEZ"; - int i; + int i, len; if (buf[0] == '\0') return (0); - for (i = 0; i < strlen(ends); i++) { + + len = strlen(ends); + for (i = 0; i < len; i++) { if (toupper(buf[0]) == ends[i]) break; } - if (i == strlen(ends)) { + if (i == len) { if (hdl) zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid numeric suffix '%s'"), buf); @@ -1583,13 +1757,13 @@ zfs_nicestrtonum(libzfs_handle_t *hdl, const char *value, uint64_t *num) */ int zprop_parse_value(libzfs_handle_t *hdl, nvpair_t *elem, int prop, - zfs_type_t type, nvlist_t *ret, char **svalp, uint64_t *ivalp, + zfs_type_t type, nvlist_t *ret, const char **svalp, uint64_t *ivalp, const char *errbuf) { data_type_t datatype = nvpair_type(elem); zprop_type_t proptype; const char *propname; - char *value; + const char *value; boolean_t isnone = B_FALSE; boolean_t isauto = B_FALSE; int err = 0; @@ -1597,6 +1771,9 @@ zprop_parse_value(libzfs_handle_t *hdl, nvpair_t *elem, int prop, if (type == ZFS_TYPE_POOL) { proptype = zpool_prop_get_type(prop); propname = zpool_prop_to_name(prop); + } else if (type == ZFS_TYPE_VDEV) { + proptype = vdev_prop_get_type(prop); + propname = vdev_prop_to_name(prop); } else { proptype = zfs_prop_get_type(prop); propname = zfs_prop_to_name(prop); @@ -1655,6 +1832,16 @@ zprop_parse_value(libzfs_handle_t *hdl, nvpair_t *elem, int prop, "use 'none' to disable quota/refquota")); goto error; } + /* + * Pool dedup table quota; force use of 'none' instead of 0 + */ + if ((type & ZFS_TYPE_POOL) && *ivalp == 0 && + (!isnone && !isauto) && + prop == ZPOOL_PROP_DEDUP_TABLE_QUOTA) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "use 'none' to disable ddt table quota")); + goto error; + } /* * Special handling for "*_limit=none". In this case it's not @@ -1667,6 +1854,20 @@ zprop_parse_value(libzfs_handle_t *hdl, nvpair_t *elem, int prop, } /* + * Special handling for "checksum_*=none". In this case it's not + * 0 but UINT64_MAX. + */ + if ((type & ZFS_TYPE_VDEV) && isnone && + (prop == VDEV_PROP_CHECKSUM_N || + prop == VDEV_PROP_CHECKSUM_T || + prop == VDEV_PROP_IO_N || + prop == VDEV_PROP_IO_T || + prop == VDEV_PROP_SLOW_IO_N || + prop == VDEV_PROP_SLOW_IO_T)) { + *ivalp = UINT64_MAX; + } + + /* * Special handling for setting 'refreservation' to 'auto'. Use * UINT64_MAX to tell the caller to use zfs_fix_auto_resv(). * 'auto' is only allowed on volumes. @@ -1682,6 +1883,10 @@ zprop_parse_value(libzfs_handle_t *hdl, nvpair_t *elem, int prop, } *ivalp = UINT64_MAX; break; + case ZPOOL_PROP_DEDUP_TABLE_QUOTA: + ASSERT(type & ZFS_TYPE_POOL); + *ivalp = UINT64_MAX; + break; default: zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "'auto' is invalid value for '%s'"), @@ -1735,43 +1940,35 @@ error: } static int -addlist(libzfs_handle_t *hdl, char *propname, zprop_list_t **listp, +addlist(libzfs_handle_t *hdl, const char *propname, zprop_list_t **listp, zfs_type_t type) { - int prop; - zprop_list_t *entry; - - prop = zprop_name_to_prop(propname, type); - + int prop = zprop_name_to_prop(propname, type); if (prop != ZPROP_INVAL && !zprop_valid_for_type(prop, type, B_FALSE)) prop = ZPROP_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, + * Return failure if no property table entry was found and this isn't + * a user-defined property. */ - if (prop == ZPROP_INVAL && ((type == ZFS_TYPE_POOL && + if (prop == ZPROP_USERPROP && ((type == ZFS_TYPE_POOL && + !zfs_prop_user(propname) && !zpool_prop_feature(propname) && !zpool_prop_unsupported(propname)) || - (type == ZFS_TYPE_DATASET && !zfs_prop_user(propname) && - !zfs_prop_userquota(propname) && !zfs_prop_written(propname)))) { + ((type == ZFS_TYPE_DATASET) && !zfs_prop_user(propname) && + !zfs_prop_userquota(propname) && !zfs_prop_written(propname)) || + ((type == ZFS_TYPE_VDEV) && !vdev_prop_user(propname)))) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid property '%s'"), propname); return (zfs_error(hdl, EZFS_BADPROP, dgettext(TEXT_DOMAIN, "bad property list"))); } - if ((entry = zfs_alloc(hdl, sizeof (zprop_list_t))) == NULL) - return (-1); + zprop_list_t *entry = zfs_alloc(hdl, sizeof (*entry)); entry->pl_prop = prop; - if (prop == ZPROP_INVAL) { - if ((entry->pl_user_prop = zfs_strdup(hdl, propname)) == - NULL) { - free(entry); - return (-1); - } + if (prop == ZPROP_USERPROP) { + entry->pl_user_prop = zfs_strdup(hdl, propname); entry->pl_width = strlen(propname); } else { entry->pl_width = zprop_width(prop, &entry->pl_fixed, @@ -1811,62 +2008,25 @@ zprop_get_list(libzfs_handle_t *hdl, char *props, zprop_list_t **listp, "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. - */ - while (*props != '\0') { - size_t len; - char *p; - char c; - - if ((p = strchr(props, ',')) == NULL) { - len = strlen(props); - p = props + len; - } else { - len = p - props; - } - - /* - * 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 = props[len]; - props[len] = '\0'; - - if (strcmp(props, "space") == 0) { - static char *spaceprops[] = { + for (char *p; (p = strsep(&props, ",")); ) + if (strcmp(p, "space") == 0) { + static const char *const spaceprops[] = { "name", "avail", "used", "usedbysnapshots", "usedbydataset", "usedbyrefreservation", - "usedbychildren", NULL + "usedbychildren" }; - int i; - for (i = 0; spaceprops[i]; i++) { + for (int i = 0; i < ARRAY_SIZE(spaceprops); i++) { if (addlist(hdl, spaceprops[i], listp, type)) return (-1); listp = &(*listp)->pl_next; } } else { - if (addlist(hdl, props, listp, type)) + if (addlist(hdl, p, listp, type)) return (-1); listp = &(*listp)->pl_next; } - props = p; - if (c == ',') - props++; - } - return (0); } @@ -1895,8 +2055,7 @@ zprop_expand_list_cb(int prop, void *cb) zprop_list_t *entry; expand_data_t *edp = cb; - if ((entry = zfs_alloc(edp->hdl, sizeof (zprop_list_t))) == NULL) - return (ZPROP_INVAL); + entry = zfs_alloc(edp->hdl, sizeof (zprop_list_t)); entry->pl_prop = prop; entry->pl_width = zprop_width(prop, &entry->pl_fixed, edp->type); @@ -1935,11 +2094,9 @@ zprop_expand_list(libzfs_handle_t *hdl, zprop_list_t **plp, zfs_type_t type) * Add 'name' to the beginning of the list, which is handled * specially. */ - if ((entry = zfs_alloc(hdl, sizeof (zprop_list_t))) == NULL) - return (-1); - - entry->pl_prop = (type == ZFS_TYPE_POOL) ? ZPOOL_PROP_NAME : - ZFS_PROP_NAME; + entry = zfs_alloc(hdl, sizeof (zprop_list_t)); + entry->pl_prop = ((type == ZFS_TYPE_POOL) ? ZPOOL_PROP_NAME : + ((type == ZFS_TYPE_VDEV) ? VDEV_PROP_NAME : ZFS_PROP_NAME)); entry->pl_width = zprop_width(entry->pl_prop, &entry->pl_fixed, type); entry->pl_all = B_TRUE; @@ -1956,45 +2113,66 @@ zprop_iter(zprop_func func, void *cb, boolean_t show_all, boolean_t ordered, return (zprop_iter_common(func, cb, show_all, ordered, type)); } -/* - * Fill given version buffer with zfs userland version - */ -void -zfs_version_userland(char *version, int len) +const char * +zfs_version_userland(void) { - (void) strlcpy(version, ZFS_META_ALIAS, len); + return (ZFS_META_ALIAS); } /* * Prints both zfs userland and kernel versions - * Returns 0 on success, and -1 on error (with errno set) + * Returns 0 on success, and -1 on error */ int zfs_version_print(void) { - char zver_userland[128]; - char zver_kernel[128]; - - zfs_version_userland(zver_userland, sizeof (zver_userland)); + (void) puts(ZFS_META_ALIAS); - (void) printf("%s\n", zver_userland); - - if (zfs_version_kernel(zver_kernel, sizeof (zver_kernel)) == -1) { + char *kver = zfs_version_kernel(); + if (kver == NULL) { fprintf(stderr, "zfs_version_kernel() failed: %s\n", - strerror(errno)); + zfs_strerror(errno)); return (-1); } - (void) printf("zfs-kmod-%s\n", zver_kernel); - + (void) printf("zfs-kmod-%s\n", kver); + free(kver); return (0); } /* + * Returns an nvlist with both zfs userland and kernel versions. + * Returns NULL on error. + */ +nvlist_t * +zfs_version_nvlist(void) +{ + nvlist_t *nvl; + char kmod_ver[64]; + if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0) + return (NULL); + if (nvlist_add_string(nvl, "userland", ZFS_META_ALIAS) != 0) + goto err; + char *kver = zfs_version_kernel(); + if (kver == NULL) { + fprintf(stderr, "zfs_version_kernel() failed: %s\n", + zfs_strerror(errno)); + goto err; + } + (void) snprintf(kmod_ver, 64, "zfs-kmod-%s", kver); + if (nvlist_add_string(nvl, "kernel", kmod_ver) != 0) + goto err; + return (nvl); +err: + nvlist_free(nvl); + return (NULL); +} + +/* * Return 1 if the user requested ANSI color output, and our terminal supports * it. Return 0 for no color. */ -static int +int use_color(void) { static int use_color = -1; @@ -2040,31 +2218,39 @@ use_color(void) } /* - * color_start() and color_end() are used for when you want to colorize a block - * of text. For example: + * The functions color_start() and color_end() are used for when you want + * to colorize a block of text. * - * color_start(ANSI_RED_FG) + * For example: + * color_start(ANSI_RED) * printf("hello"); * printf("world"); * color_end(); */ void -color_start(char *color) +color_start(const char *color) { - if (use_color()) - printf("%s", color); + if (color && use_color()) { + fputs(color, stdout); + fflush(stdout); + } } void color_end(void) { - if (use_color()) - printf(ANSI_RESET); + if (use_color()) { + fputs(ANSI_RESET, stdout); + fflush(stdout); + } + } -/* printf() with a color. If color is NULL, then do a normal printf. */ +/* + * printf() with a color. If color is NULL, then do a normal printf. + */ int -printf_color(char *color, char *format, ...) +printf_color(const char *color, const char *format, ...) { va_list aptr; int rc; @@ -2081,3 +2267,196 @@ printf_color(char *color, char *format, ...) return (rc); } + +/* PATH + 5 env vars + a NULL entry = 7 */ +#define ZPOOL_VDEV_SCRIPT_ENV_COUNT 7 + +/* + * There's a few places where ZFS will call external scripts (like the script + * in zpool.d/ and `zfs_prepare_disk`). These scripts are called with a + * reduced $PATH, and some vdev specific environment vars set. This function + * will allocate an populate the environment variable array that is passed to + * these scripts. The user must free the arrays with zpool_vdev_free_env() when + * they are done. + * + * The following env vars will be set (but value could be blank): + * + * POOL_NAME + * VDEV_PATH + * VDEV_UPATH + * VDEV_ENC_SYSFS_PATH + * + * In addition, you can set an optional environment variable named 'opt_key' + * to 'opt_val' if you want. + * + * Returns allocated env[] array on success, NULL otherwise. + */ +char ** +zpool_vdev_script_alloc_env(const char *pool_name, + const char *vdev_path, const char *vdev_upath, + const char *vdev_enc_sysfs_path, const char *opt_key, const char *opt_val) +{ + char **env = NULL; + int rc; + + env = calloc(ZPOOL_VDEV_SCRIPT_ENV_COUNT, sizeof (*env)); + if (!env) + return (NULL); + + env[0] = strdup("PATH=/bin:/sbin:/usr/bin:/usr/sbin"); + if (!env[0]) + goto error; + + /* Setup our custom environment variables */ + rc = asprintf(&env[1], "POOL_NAME=%s", pool_name ? pool_name : ""); + if (rc == -1) { + env[1] = NULL; + goto error; + } + + rc = asprintf(&env[2], "VDEV_PATH=%s", vdev_path ? vdev_path : ""); + if (rc == -1) { + env[2] = NULL; + goto error; + } + + rc = asprintf(&env[3], "VDEV_UPATH=%s", vdev_upath ? vdev_upath : ""); + if (rc == -1) { + env[3] = NULL; + goto error; + } + + rc = asprintf(&env[4], "VDEV_ENC_SYSFS_PATH=%s", + vdev_enc_sysfs_path ? vdev_enc_sysfs_path : ""); + if (rc == -1) { + env[4] = NULL; + goto error; + } + + if (opt_key != NULL) { + rc = asprintf(&env[5], "%s=%s", opt_key, + opt_val ? opt_val : ""); + if (rc == -1) { + env[5] = NULL; + goto error; + } + } + + return (env); + +error: + for (int i = 0; i < ZPOOL_VDEV_SCRIPT_ENV_COUNT; i++) + free(env[i]); + + free(env); + + return (NULL); +} + +/* + * Free the env[] array that was allocated by zpool_vdev_script_alloc_env(). + */ +void +zpool_vdev_script_free_env(char **env) +{ + for (int i = 0; i < ZPOOL_VDEV_SCRIPT_ENV_COUNT; i++) + free(env[i]); + + free(env); +} + +/* + * Prepare a disk by (optionally) running a program before labeling the disk. + * This can be useful for installing disk firmware or doing some pre-flight + * checks on the disk before it becomes part of the pool. The program run is + * located at ZFSEXECDIR/zfs_prepare_disk + * (E.x: /usr/local/libexec/zfs/zfs_prepare_disk). + * + * Return 0 on success, non-zero on failure. + */ +int +zpool_prepare_disk(zpool_handle_t *zhp, nvlist_t *vdev_nv, + const char *prepare_str, char **lines[], int *lines_cnt) +{ + const char *script_path = ZFSEXECDIR "/zfs_prepare_disk"; + const char *pool_name; + int rc = 0; + + /* Path to script and a NULL entry */ + char *argv[2] = {(char *)script_path}; + char **env = NULL; + const char *path = NULL, *enc_sysfs_path = NULL; + char *upath; + *lines_cnt = 0; + + if (access(script_path, X_OK) != 0) { + /* No script, nothing to do */ + return (0); + } + + (void) nvlist_lookup_string(vdev_nv, ZPOOL_CONFIG_PATH, &path); + (void) nvlist_lookup_string(vdev_nv, ZPOOL_CONFIG_VDEV_ENC_SYSFS_PATH, + &enc_sysfs_path); + + upath = zfs_get_underlying_path(path); + pool_name = zhp ? zpool_get_name(zhp) : NULL; + + env = zpool_vdev_script_alloc_env(pool_name, path, upath, + enc_sysfs_path, "VDEV_PREPARE", prepare_str); + + free(upath); + + if (env == NULL) { + return (ENOMEM); + } + + rc = libzfs_run_process_get_stdout(script_path, argv, env, lines, + lines_cnt); + + zpool_vdev_script_free_env(env); + + return (rc); +} + +/* + * Optionally run a script and then label a disk. The script can be used to + * prepare a disk for inclusion into the pool. For example, it might update + * the disk's firmware or check its health. + * + * The 'name' provided is the short name, stripped of any leading + * /dev path, and is passed to zpool_label_disk. vdev_nv is the nvlist for + * the vdev. prepare_str is a string that gets passed as the VDEV_PREPARE + * env variable to the script. + * + * The following env vars are passed to the script: + * + * POOL_NAME: The pool name (blank during zpool create) + * VDEV_PREPARE: Reason why the disk is being prepared for inclusion: + * "create", "add", "replace", or "autoreplace" + * VDEV_PATH: Path to the disk + * VDEV_UPATH: One of the 'underlying paths' to the disk. This is + * useful for DM devices. + * VDEV_ENC_SYSFS_PATH: Path to the disk's enclosure sysfs path, if available. + * + * Note, some of these values can be blank. + * + * Return 0 on success, non-zero otherwise. + */ +int +zpool_prepare_and_label_disk(libzfs_handle_t *hdl, zpool_handle_t *zhp, + const char *name, nvlist_t *vdev_nv, const char *prepare_str, + char **lines[], int *lines_cnt) +{ + int rc; + char vdev_path[MAXPATHLEN]; + (void) snprintf(vdev_path, sizeof (vdev_path), "%s/%s", DISK_ROOT, + name); + + /* zhp will be NULL when creating a pool */ + rc = zpool_prepare_disk(zhp, vdev_nv, prepare_str, lines, lines_cnt); + if (rc != 0) + return (rc); + + rc = zpool_label_disk(hdl, zhp, name); + return (rc); +} |