]> git.proxmox.com Git - mirror_zfs.git/blobdiff - lib/libzfs/libzfs_pool.c
Add slow disk diagnosis to ZED
[mirror_zfs.git] / lib / libzfs / libzfs_pool.c
index 83fab043366a6bec24172ec87389d9d80a07c194..402c14a6baee85b7a7a6d4e8ca5f9c1eec374b57 100644 (file)
@@ -6,7 +6,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.
  *
@@ -29,7 +29,7 @@
  * Copyright (c) 2017, Intel Corporation.
  * Copyright (c) 2018, loli10K <ezomori.nozomu@gmail.com>
  * Copyright (c) 2021, Colm Buckley <colm@tuatha.org>
- * Copyright (c) 2021, Klara Inc.
+ * Copyright (c) 2021, 2023, Klara Inc.
  */
 
 #include <errno.h>
@@ -60,9 +60,9 @@
 static boolean_t zpool_vdev_is_interior(const char *name);
 
 typedef struct prop_flags {
-       int create:1;   /* Validate property on creation */
-       int import:1;   /* Validate property on import */
-       int vdevprop:1; /* Validate property as a VDEV property */
+       unsigned int create:1;  /* Validate property on creation */
+       unsigned int import:1;  /* Validate property on import */
+       unsigned int vdevprop:1; /* Validate property as a VDEV property */
 } prop_flags_t;
 
 /*
@@ -119,7 +119,7 @@ zpool_get_prop_string(zpool_handle_t *zhp, zpool_prop_t prop,
     zprop_source_t *src)
 {
        nvlist_t *nv, *nvl;
-       char *value;
+       const char *value;
        zprop_source_t source;
 
        nvl = zhp->zpool_props;
@@ -128,7 +128,7 @@ zpool_get_prop_string(zpool_handle_t *zhp, zpool_prop_t prop,
                value = fnvlist_lookup_string(nv, ZPROP_VALUE);
        } else {
                source = ZPROP_SRC_DEFAULT;
-               if ((value = (char *)zpool_prop_default_string(prop)) == NULL)
+               if ((value = zpool_prop_default_string(prop)) == NULL)
                        value = "-";
        }
 
@@ -255,6 +255,7 @@ zpool_get_state_str(zpool_handle_t *zhp)
        if (zpool_get_state(zhp) == POOL_STATE_UNAVAIL) {
                str = gettext("FAULTED");
        } else if (status == ZPOOL_STATUS_IO_FAILURE_WAIT ||
+           status == ZPOOL_STATUS_IO_FAILURE_CONTINUE ||
            status == ZPOOL_STATUS_IO_FAILURE_MMP) {
                str = gettext("SUSPENDED");
        } else {
@@ -339,6 +340,8 @@ zpool_get_prop(zpool_handle_t *zhp, zpool_prop_t prop, char *buf,
                case ZPOOL_PROP_ASHIFT:
                case ZPOOL_PROP_MAXBLOCKSIZE:
                case ZPOOL_PROP_MAXDNODESIZE:
+               case ZPOOL_PROP_BCLONESAVED:
+               case ZPOOL_PROP_BCLONEUSED:
                        if (literal)
                                (void) snprintf(buf, len, "%llu",
                                    (u_longlong_t)intval);
@@ -380,6 +383,7 @@ zpool_get_prop(zpool_handle_t *zhp, zpool_prop_t prop, char *buf,
                        }
                        break;
 
+               case ZPOOL_PROP_BCLONERATIO:
                case ZPOOL_PROP_DEDUPRATIO:
                        if (literal)
                                (void) snprintf(buf, len, "%llu.%02llu",
@@ -423,6 +427,37 @@ zpool_get_prop(zpool_handle_t *zhp, zpool_prop_t prop, char *buf,
        return (0);
 }
 
+/*
+ * Get a zpool property value for 'propname' and return the value in
+ * a pre-allocated buffer.
+ */
+int
+zpool_get_userprop(zpool_handle_t *zhp, const char *propname, char *buf,
+    size_t len, zprop_source_t *srctype)
+{
+       nvlist_t *nv, *nvl;
+       uint64_t ival;
+       const char *value;
+       zprop_source_t source = ZPROP_SRC_LOCAL;
+
+       nvl = zhp->zpool_props;
+       if (nvlist_lookup_nvlist(nvl, propname, &nv) == 0) {
+               if (nvlist_lookup_uint64(nv, ZPROP_SOURCE, &ival) == 0)
+                       source = ival;
+               verify(nvlist_lookup_string(nv, ZPROP_VALUE, &value) == 0);
+       } else {
+               source = ZPROP_SRC_DEFAULT;
+               value = "-";
+       }
+
+       if (srctype)
+               *srctype = source;
+
+       (void) strlcpy(buf, value, len);
+
+       return (0);
+}
+
 /*
  * Check if the bootfs name has the same pool name as it is set to.
  * Assuming bootfs is a valid dataset name.
@@ -456,9 +491,9 @@ zpool_valid_proplist(libzfs_handle_t *hdl, const char *poolname,
        nvpair_t *elem;
        nvlist_t *retprops;
        zpool_prop_t prop;
-       char *strval;
+       const char *strval;
        uint64_t intval;
-       char *slash, *check;
+       const char *slash, *check;
        struct stat64 statbuf;
        zpool_handle_t *zhp;
        char report[1024];
@@ -546,6 +581,44 @@ zpool_valid_proplist(libzfs_handle_t *hdl, const char *poolname,
                                (void) no_memory(hdl);
                                goto error;
                        }
+                       continue;
+               } else if (prop == ZPOOL_PROP_INVAL &&
+                   zfs_prop_user(propname)) {
+                       /*
+                        * 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);
+
+                       if (strlen(strval) >= ZFS_MAXPROPLEN) {
+                               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                                   "property value '%s' is too long"),
+                                   strval);
+                               (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+                               goto error;
+                       }
+
+                       if (nvlist_add_string(retprops, propname,
+                           strval) != 0) {
+                               (void) no_memory(hdl);
+                               goto error;
+                       }
+
                        continue;
                }
 
@@ -686,11 +759,12 @@ zpool_valid_proplist(libzfs_handle_t *hdl, const char *poolname,
                                goto error;
                        }
 
-                       *slash = '\0';
+                       *(char *)slash = '\0';
 
                        if (strval[0] != '\0' &&
                            (stat64(strval, &statbuf) != 0 ||
                            !S_ISDIR(statbuf.st_mode))) {
+                               *(char *)slash = '/';
                                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                                    "'%s' is not a valid directory"),
                                    strval);
@@ -698,7 +772,7 @@ zpool_valid_proplist(libzfs_handle_t *hdl, const char *poolname,
                                goto error;
                        }
 
-                       *slash = '/';
+                       *(char *)slash = '/';
                        break;
 
                case ZPOOL_PROP_COMPATIBILITY:
@@ -776,7 +850,7 @@ zpool_set_prop(zpool_handle_t *zhp, const char *propname, const char *propval)
 {
        zfs_cmd_t zc = {"\0"};
        int ret = -1;
-       char errbuf[1024];
+       char errbuf[ERRBUFLEN];
        nvlist_t *nvl = NULL;
        nvlist_t *realprops;
        uint64_t version;
@@ -851,10 +925,31 @@ zpool_expand_proplist(zpool_handle_t *zhp, zprop_list_t **plp,
                features = zpool_get_features(zhp);
 
        if ((*plp)->pl_all && firstexpand) {
+               /* Handle userprops in the all properties case */
+               if (zhp->zpool_props == NULL && zpool_props_refresh(zhp))
+                       return (-1);
+
+               nvp = NULL;
+               while ((nvp = nvlist_next_nvpair(zhp->zpool_props, nvp)) !=
+                   NULL) {
+                       const char *propname = nvpair_name(nvp);
+
+                       if (!zfs_prop_user(propname))
+                               continue;
+
+                       entry = zfs_alloc(hdl, sizeof (zprop_list_t));
+                       entry->pl_prop = ZPROP_USERPROP;
+                       entry->pl_user_prop = zfs_strdup(hdl, propname);
+                       entry->pl_width = strlen(entry->pl_user_prop);
+                       entry->pl_all = B_TRUE;
+
+                       *last = entry;
+                       last = &entry->pl_next;
+               }
+
                for (i = 0; i < SPA_FEATURES; i++) {
-                       zprop_list_t *entry = zfs_alloc(hdl,
-                           sizeof (zprop_list_t));
-                       entry->pl_prop = ZPROP_INVAL;
+                       entry = zfs_alloc(hdl, sizeof (zprop_list_t));
+                       entry->pl_prop = ZPROP_USERPROP;
                        entry->pl_user_prop = zfs_asprintf(hdl, "feature@%s",
                            spa_feature_table[i].fi_uname);
                        entry->pl_width = strlen(entry->pl_user_prop);
@@ -870,7 +965,6 @@ zpool_expand_proplist(zpool_handle_t *zhp, zprop_list_t **plp,
            nvp != NULL; nvp = nvlist_next_nvpair(features, nvp)) {
                char *propname;
                boolean_t found;
-               zprop_list_t *entry;
 
                if (zfeature_is_supported(nvpair_name(nvp)))
                        continue;
@@ -898,7 +992,7 @@ zpool_expand_proplist(zpool_handle_t *zhp, zprop_list_t **plp,
                }
 
                entry = zfs_alloc(hdl, sizeof (zprop_list_t));
-               entry->pl_prop = ZPROP_INVAL;
+               entry->pl_prop = ZPROP_USERPROP;
                entry->pl_user_prop = propname;
                entry->pl_width = strlen(entry->pl_user_prop);
                entry->pl_all = B_TRUE;
@@ -911,11 +1005,17 @@ zpool_expand_proplist(zpool_handle_t *zhp, zprop_list_t **plp,
                if (entry->pl_fixed && !literal)
                        continue;
 
-               if (entry->pl_prop != ZPROP_INVAL &&
+               if (entry->pl_prop != ZPROP_USERPROP &&
                    zpool_get_prop(zhp, entry->pl_prop, buf, sizeof (buf),
                    NULL, literal) == 0) {
                        if (strlen(buf) > entry->pl_width)
                                entry->pl_width = strlen(buf);
+               } else if (entry->pl_prop == ZPROP_INVAL &&
+                   zfs_prop_user(entry->pl_user_prop) &&
+                   zpool_get_userprop(zhp, entry->pl_user_prop, buf,
+                   sizeof (buf), NULL) == 0) {
+                       if (strlen(buf) > entry->pl_width)
+                               entry->pl_width = strlen(buf);
                }
        }
 
@@ -928,7 +1028,7 @@ vdev_expand_proplist(zpool_handle_t *zhp, const char *vdevname,
 {
        zprop_list_t *entry;
        char buf[ZFS_MAXPROPLEN];
-       char *strval = NULL;
+       const char *strval = NULL;
        int err = 0;
        nvpair_t *elem = NULL;
        nvlist_t *vprops = NULL;
@@ -967,7 +1067,7 @@ vdev_expand_proplist(zpool_handle_t *zhp, const char *vdevname,
 
                        /* Skip properties that are not user defined */
                        if ((prop = vdev_name_to_prop(propname)) !=
-                           VDEV_PROP_USER)
+                           VDEV_PROP_USERPROP)
                                continue;
 
                        if (nvpair_value_nvlist(elem, &propval) != 0)
@@ -1286,7 +1386,7 @@ zpool_has_special_vdev(nvlist_t *nvroot)
        if (nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_CHILDREN, &child,
            &children) == 0) {
                for (uint_t c = 0; c < children; c++) {
-                       char *bias;
+                       const char *bias;
 
                        if (nvlist_lookup_string(child[c],
                            ZPOOL_CONFIG_ALLOCATION_BIAS, &bias) == 0 &&
@@ -1310,7 +1410,7 @@ zpool_has_draid_vdev(nvlist_t *nvroot)
        if (nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_CHILDREN,
            &child, &children) == 0) {
                for (uint_t c = 0; c < children; c++) {
-                       char *type;
+                       const char *type;
 
                        if (nvlist_lookup_string(child[c],
                            ZPOOL_CONFIG_TYPE, &type) == 0 &&
@@ -1368,14 +1468,14 @@ zpool_create(libzfs_handle_t *hdl, const char *pool, nvlist_t *nvroot,
        nvlist_t *hidden_args = NULL;
        uint8_t *wkeydata = NULL;
        uint_t wkeylen = 0;
-       char msg[1024];
+       char errbuf[ERRBUFLEN];
        int ret = -1;
 
-       (void) snprintf(msg, sizeof (msg), dgettext(TEXT_DOMAIN,
+       (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
            "cannot create '%s'"), pool);
 
        if (!zpool_name_valid(hdl, B_FALSE, pool))
-               return (zfs_error(hdl, EZFS_INVALIDNAME, msg));
+               return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf));
 
        zcmd_write_conf_nvlist(hdl, &zc, nvroot);
 
@@ -1383,21 +1483,21 @@ zpool_create(libzfs_handle_t *hdl, const char *pool, nvlist_t *nvroot,
                prop_flags_t flags = { .create = B_TRUE, .import = B_FALSE };
 
                if ((zc_props = zpool_valid_proplist(hdl, pool, props,
-                   SPA_VERSION_1, flags, msg)) == NULL) {
+                   SPA_VERSION_1, flags, errbuf)) == NULL) {
                        goto create_failed;
                }
        }
 
        if (fsprops) {
                uint64_t zoned;
-               char *zonestr;
+               const char *zonestr;
 
                zoned = ((nvlist_lookup_string(fsprops,
                    zfs_prop_to_name(ZFS_PROP_ZONED), &zonestr) == 0) &&
                    strcmp(zonestr, "on") == 0);
 
                if ((zc_fsprops = zfs_valid_proplist(hdl, ZFS_TYPE_FILESYSTEM,
-                   fsprops, zoned, NULL, NULL, B_TRUE, msg)) == NULL) {
+                   fsprops, zoned, NULL, NULL, B_TRUE, errbuf)) == NULL) {
                        goto create_failed;
                }
 
@@ -1407,7 +1507,7 @@ zpool_create(libzfs_handle_t *hdl, const char *pool, nvlist_t *nvroot,
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                            "%s property requires a special vdev"),
                            zfs_prop_to_name(ZFS_PROP_SPECIAL_SMALL_BLOCKS));
-                       (void) zfs_error(hdl, EZFS_BADPROP, msg);
+                       (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
                        goto create_failed;
                }
 
@@ -1417,7 +1517,7 @@ zpool_create(libzfs_handle_t *hdl, const char *pool, nvlist_t *nvroot,
                }
                if (zfs_crypto_create(hdl, NULL, zc_fsprops, props, B_TRUE,
                    &wkeydata, &wkeylen) != 0) {
-                       zfs_error(hdl, EZFS_CRYPTOFAILED, msg);
+                       zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf);
                        goto create_failed;
                }
                if (nvlist_add_nvlist(zc_props,
@@ -1465,7 +1565,7 @@ zpool_create(libzfs_handle_t *hdl, const char *pool, nvlist_t *nvroot,
                            "one or more vdevs refer to the same device, or "
                            "one of\nthe devices is part of an active md or "
                            "lvm device"));
-                       return (zfs_error(hdl, EZFS_BADDEV, msg));
+                       return (zfs_error(hdl, EZFS_BADDEV, errbuf));
 
                case ERANGE:
                        /*
@@ -1480,7 +1580,7 @@ zpool_create(libzfs_handle_t *hdl, const char *pool, nvlist_t *nvroot,
                         */
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                            "record size invalid"));
-                       return (zfs_error(hdl, EZFS_BADPROP, msg));
+                       return (zfs_error(hdl, EZFS_BADPROP, errbuf));
 
                case EOVERFLOW:
                        /*
@@ -1499,12 +1599,12 @@ zpool_create(libzfs_handle_t *hdl, const char *pool, nvlist_t *nvroot,
                                    "one or more devices is less than the "
                                    "minimum size (%s)"), buf);
                        }
-                       return (zfs_error(hdl, EZFS_BADDEV, msg));
+                       return (zfs_error(hdl, EZFS_BADDEV, errbuf));
 
                case ENOSPC:
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                            "one or more devices is out of space"));
-                       return (zfs_error(hdl, EZFS_BADDEV, msg));
+                       return (zfs_error(hdl, EZFS_BADDEV, errbuf));
 
                case EINVAL:
                        if (zpool_has_draid_vdev(nvroot) &&
@@ -1512,13 +1612,14 @@ zpool_create(libzfs_handle_t *hdl, const char *pool, nvlist_t *nvroot,
                                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                                    "dRAID vdevs are unsupported by the "
                                    "kernel"));
-                               return (zfs_error(hdl, EZFS_BADDEV, msg));
+                               return (zfs_error(hdl, EZFS_BADDEV, errbuf));
                        } else {
-                               return (zpool_standard_error(hdl, errno, msg));
+                               return (zpool_standard_error(hdl, errno,
+                                   errbuf));
                        }
 
                default:
-                       return (zpool_standard_error(hdl, errno, msg));
+                       return (zpool_standard_error(hdl, errno, errbuf));
                }
        }
 
@@ -1542,7 +1643,7 @@ zpool_destroy(zpool_handle_t *zhp, const char *log_str)
        zfs_cmd_t zc = {"\0"};
        zfs_handle_t *zfp = NULL;
        libzfs_handle_t *hdl = zhp->zpool_hdl;
-       char msg[1024];
+       char errbuf[ERRBUFLEN];
 
        if (zhp->zpool_state == POOL_STATE_ACTIVE &&
            (zfp = zfs_open(hdl, zhp->zpool_name, ZFS_TYPE_FILESYSTEM)) == NULL)
@@ -1552,15 +1653,15 @@ zpool_destroy(zpool_handle_t *zhp, const char *log_str)
        zc.zc_history = (uint64_t)(uintptr_t)log_str;
 
        if (zfs_ioctl(hdl, ZFS_IOC_POOL_DESTROY, &zc) != 0) {
-               (void) snprintf(msg, sizeof (msg), dgettext(TEXT_DOMAIN,
+               (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
                    "cannot destroy '%s'"), zhp->zpool_name);
 
                if (errno == EROFS) {
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                            "one or more devices is read only"));
-                       (void) zfs_error(hdl, EZFS_BADDEV, msg);
+                       (void) zfs_error(hdl, EZFS_BADDEV, errbuf);
                } else {
-                       (void) zpool_standard_error(hdl, errno, msg);
+                       (void) zpool_standard_error(hdl, errno, errbuf);
                }
 
                if (zfp)
@@ -1583,14 +1684,14 @@ int
 zpool_checkpoint(zpool_handle_t *zhp)
 {
        libzfs_handle_t *hdl = zhp->zpool_hdl;
-       char msg[1024];
+       char errbuf[ERRBUFLEN];
        int error;
 
        error = lzc_pool_checkpoint(zhp->zpool_name);
        if (error != 0) {
-               (void) snprintf(msg, sizeof (msg), dgettext(TEXT_DOMAIN,
+               (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
                    "cannot checkpoint '%s'"), zhp->zpool_name);
-               (void) zpool_standard_error(hdl, error, msg);
+               (void) zpool_standard_error(hdl, error, errbuf);
                return (-1);
        }
 
@@ -1604,14 +1705,14 @@ int
 zpool_discard_checkpoint(zpool_handle_t *zhp)
 {
        libzfs_handle_t *hdl = zhp->zpool_hdl;
-       char msg[1024];
+       char errbuf[ERRBUFLEN];
        int error;
 
        error = lzc_pool_checkpoint_discard(zhp->zpool_name);
        if (error != 0) {
-               (void) snprintf(msg, sizeof (msg), dgettext(TEXT_DOMAIN,
+               (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
                    "cannot discard checkpoint in '%s'"), zhp->zpool_name);
-               (void) zpool_standard_error(hdl, error, msg);
+               (void) zpool_standard_error(hdl, error, errbuf);
                return (-1);
        }
 
@@ -1628,11 +1729,11 @@ zpool_add(zpool_handle_t *zhp, nvlist_t *nvroot)
        zfs_cmd_t zc = {"\0"};
        int ret;
        libzfs_handle_t *hdl = zhp->zpool_hdl;
-       char msg[1024];
+       char errbuf[ERRBUFLEN];
        nvlist_t **spares, **l2cache;
        uint_t nspares, nl2cache;
 
-       (void) snprintf(msg, sizeof (msg), dgettext(TEXT_DOMAIN,
+       (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
            "cannot add to '%s'"), zhp->zpool_name);
 
        if (zpool_get_prop_int(zhp, ZPOOL_PROP_VERSION, NULL) <
@@ -1641,7 +1742,7 @@ zpool_add(zpool_handle_t *zhp, nvlist_t *nvroot)
            &spares, &nspares) == 0) {
                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "pool must be "
                    "upgraded to add hot spares"));
-               return (zfs_error(hdl, EZFS_BADVERSION, msg));
+               return (zfs_error(hdl, EZFS_BADVERSION, errbuf));
        }
 
        if (zpool_get_prop_int(zhp, ZPOOL_PROP_VERSION, NULL) <
@@ -1650,7 +1751,7 @@ zpool_add(zpool_handle_t *zhp, nvlist_t *nvroot)
            &l2cache, &nl2cache) == 0) {
                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "pool must be "
                    "upgraded to add cache devices"));
-               return (zfs_error(hdl, EZFS_BADVERSION, msg));
+               return (zfs_error(hdl, EZFS_BADVERSION, errbuf));
        }
 
        zcmd_write_conf_nvlist(hdl, &zc, nvroot);
@@ -1667,7 +1768,7 @@ zpool_add(zpool_handle_t *zhp, nvlist_t *nvroot)
                         */
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                            "one or more vdevs refer to the same device"));
-                       (void) zfs_error(hdl, EZFS_BADDEV, msg);
+                       (void) zfs_error(hdl, EZFS_BADDEV, errbuf);
                        break;
 
                case EINVAL:
@@ -1684,7 +1785,7 @@ zpool_add(zpool_handle_t *zhp, nvlist_t *nvroot)
                                    "raidz or dRAID vdevs"));
                        }
 
-                       (void) zfs_error(hdl, EZFS_BADDEV, msg);
+                       (void) zfs_error(hdl, EZFS_BADDEV, errbuf);
                        break;
 
                case EOVERFLOW:
@@ -1704,17 +1805,17 @@ zpool_add(zpool_handle_t *zhp, nvlist_t *nvroot)
                                    "device is less than the minimum "
                                    "size (%s)"), buf);
                        }
-                       (void) zfs_error(hdl, EZFS_BADDEV, msg);
+                       (void) zfs_error(hdl, EZFS_BADDEV, errbuf);
                        break;
 
                case ENOTSUP:
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                            "pool must be upgraded to add these vdevs"));
-                       (void) zfs_error(hdl, EZFS_BADVERSION, msg);
+                       (void) zfs_error(hdl, EZFS_BADVERSION, errbuf);
                        break;
 
                default:
-                       (void) zpool_standard_error(hdl, errno, msg);
+                       (void) zpool_standard_error(hdl, errno, errbuf);
                }
 
                ret = -1;
@@ -1982,7 +2083,7 @@ zpool_print_unsup_feat(nvlist_t *config)
 
        for (nvpair_t *nvp = nvlist_next_nvpair(unsup_feat, NULL);
            nvp != NULL; nvp = nvlist_next_nvpair(unsup_feat, nvp)) {
-               char *desc = fnvpair_value_string(nvp);
+               const char *desc = fnvpair_value_string(nvp);
                if (strlen(desc) > 0)
                        (void) printf("\t%s (%s)\n", nvpair_name(nvp), desc);
                else
@@ -2006,10 +2107,10 @@ zpool_import_props(libzfs_handle_t *hdl, nvlist_t *config, const char *newname,
        nvlist_t *nvinfo = NULL;
        nvlist_t *missing = NULL;
        const char *thename;
-       char *origname;
+       const char *origname;
        int ret;
        int error = 0;
-       char errbuf[1024];
+       char errbuf[ERRBUFLEN];
 
        origname = fnvlist_lookup_string(config, ZPOOL_CONFIG_POOL_NAME);
 
@@ -2110,7 +2211,7 @@ zpool_import_props(libzfs_handle_t *hdl, nvlist_t *config, const char *newname,
                case EREMOTEIO:
                        if (nv != NULL && nvlist_lookup_nvlist(nv,
                            ZPOOL_CONFIG_LOAD_INFO, &nvinfo) == 0) {
-                               char *hostname = "<unknown>";
+                               const char *hostname = "<unknown>";
                                uint64_t hostid = 0;
                                mmp_state_t mmp_state;
 
@@ -2213,7 +2314,6 @@ zpool_import_props(libzfs_handle_t *hdl, nvlist_t *config, const char *newname,
                            ((policy.zlp_rewind & ZPOOL_TRY_REWIND) != 0), nv);
                }
                nvlist_free(nv);
-               return (0);
        }
 
        return (ret);
@@ -2235,7 +2335,7 @@ zpool_translate_vdev_guids(zpool_handle_t *zhp, nvlist_t *vds,
            elem = nvlist_next_nvpair(vds, elem)) {
                boolean_t spare, cache;
 
-               char *vd_path = nvpair_name(elem);
+               const char *vd_path = nvpair_name(elem);
                nvlist_t *tgt = zpool_find_vdev(zhp, vd_path, &spare, &cache,
                    NULL);
 
@@ -2288,8 +2388,8 @@ xlate_init_err(int err)
 }
 
 /*
- * Begin, suspend, or cancel the initialization (initializing of all free
- * blocks) for the given vdevs in the given pool.
+ * Begin, suspend, cancel, or uninit (clear) the initialization (initializing
+ * of all free blocks) for the given vdevs in the given pool.
  */
 static int
 zpool_initialize_impl(zpool_handle_t *zhp, pool_initialize_func_t cmd_type,
@@ -2315,11 +2415,16 @@ zpool_initialize_impl(zpool_handle_t *zhp, pool_initialize_func_t cmd_type,
            vdev_guids, &errlist);
 
        if (err != 0) {
-               if (errlist != NULL) {
-                       vd_errlist = fnvlist_lookup_nvlist(errlist,
-                           ZPOOL_INITIALIZE_VDEVS);
+               if (errlist != NULL && nvlist_lookup_nvlist(errlist,
+                   ZPOOL_INITIALIZE_VDEVS, &vd_errlist) == 0) {
                        goto list_errors;
                }
+
+               if (err == EINVAL && cmd_type == POOL_INITIALIZE_UNINIT) {
+                       zfs_error_aux(zhp->zpool_hdl, dgettext(TEXT_DOMAIN,
+                           "uninitialize is not supported by kernel"));
+               }
+
                (void) zpool_standard_error(zhp->zpool_hdl, err,
                    dgettext(TEXT_DOMAIN, "operation failed"));
                goto out;
@@ -2349,7 +2454,7 @@ list_errors:
        for (elem = nvlist_next_nvpair(vd_errlist, NULL); elem != NULL;
            elem = nvlist_next_nvpair(vd_errlist, elem)) {
                int64_t vd_error = xlate_init_err(fnvpair_value_int64(elem));
-               char *path;
+               const char *path;
 
                if (nvlist_lookup_string(guids_to_paths, nvpair_name(elem),
                    &path) != 0)
@@ -2447,7 +2552,7 @@ check_trim_errs(zpool_handle_t *zhp, trimflags_t *trim_flags,
        for (elem = nvlist_next_nvpair(errlist, NULL);
            elem != NULL; elem = nvlist_next_nvpair(errlist, elem)) {
                int64_t vd_error = xlate_trim_err(fnvpair_value_int64(elem));
-               char *path;
+               const char *path;
 
                /*
                 * If only the pool was specified, and it was not a secure
@@ -2516,11 +2621,11 @@ zpool_trim(zpool_handle_t *zhp, pool_trim_func_t cmd_type, nvlist_t *vds,
                                goto out;
                        }
                } else {
-                       char msg[1024];
+                       char errbuf[ERRBUFLEN];
 
-                       (void) snprintf(msg, sizeof (msg),
+                       (void) snprintf(errbuf, sizeof (errbuf),
                            dgettext(TEXT_DOMAIN, "operation failed"));
-                       zpool_standard_error(zhp->zpool_hdl, err, msg);
+                       zpool_standard_error(zhp->zpool_hdl, err, errbuf);
                        retval = -1;
                        goto out;
                }
@@ -2544,49 +2649,84 @@ out:
 int
 zpool_scan(zpool_handle_t *zhp, pool_scan_func_t func, pool_scrub_cmd_t cmd)
 {
-       zfs_cmd_t zc = {"\0"};
-       char msg[1024];
+       char errbuf[ERRBUFLEN];
        int err;
        libzfs_handle_t *hdl = zhp->zpool_hdl;
 
-       (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
-       zc.zc_cookie = func;
-       zc.zc_flags = cmd;
+       nvlist_t *args = fnvlist_alloc();
+       fnvlist_add_uint64(args, "scan_type", (uint64_t)func);
+       fnvlist_add_uint64(args, "scan_command", (uint64_t)cmd);
+
+       err = lzc_scrub(ZFS_IOC_POOL_SCRUB, zhp->zpool_name, args, NULL);
+       fnvlist_free(args);
 
-       if (zfs_ioctl(hdl, ZFS_IOC_POOL_SCAN, &zc) == 0)
+       if (err == 0) {
                return (0);
+       } else if (err == ZFS_ERR_IOC_CMD_UNAVAIL) {
+               zfs_cmd_t zc = {"\0"};
+               (void) strlcpy(zc.zc_name, zhp->zpool_name,
+                   sizeof (zc.zc_name));
+               zc.zc_cookie = func;
+               zc.zc_flags = cmd;
 
-       err = errno;
+               if (zfs_ioctl(hdl, ZFS_IOC_POOL_SCAN, &zc) == 0)
+                       return (0);
+       }
 
-       /* ECANCELED on a scrub means we resumed a paused scrub */
-       if (err == ECANCELED && func == POOL_SCAN_SCRUB &&
-           cmd == POOL_SCRUB_NORMAL)
+       /*
+        * An ECANCELED on a scrub means one of the following:
+        * 1. we resumed a paused scrub.
+        * 2. we resumed a paused error scrub.
+        * 3. Error scrub is not run because of no error log.
+        */
+       if (err == ECANCELED && (func == POOL_SCAN_SCRUB ||
+           func == POOL_SCAN_ERRORSCRUB) && cmd == POOL_SCRUB_NORMAL)
                return (0);
-
-       if (err == ENOENT && func != POOL_SCAN_NONE && cmd == POOL_SCRUB_NORMAL)
+       /*
+        * The following cases have been handled here:
+        * 1. Paused a scrub/error scrub if there is none in progress.
+        */
+       if (err == ENOENT && func != POOL_SCAN_NONE && cmd ==
+           POOL_SCRUB_PAUSE) {
                return (0);
+       }
+
+       ASSERT3U(func, >=, POOL_SCAN_NONE);
+       ASSERT3U(func, <, POOL_SCAN_FUNCS);
 
-       if (func == POOL_SCAN_SCRUB) {
+       if (func == POOL_SCAN_SCRUB || func == POOL_SCAN_ERRORSCRUB) {
                if (cmd == POOL_SCRUB_PAUSE) {
-                       (void) snprintf(msg, sizeof (msg), dgettext(TEXT_DOMAIN,
-                           "cannot pause scrubbing %s"), zc.zc_name);
+                       (void) snprintf(errbuf, sizeof (errbuf),
+                           dgettext(TEXT_DOMAIN, "cannot pause scrubbing %s"),
+                           zhp->zpool_name);
                } else {
                        assert(cmd == POOL_SCRUB_NORMAL);
-                       (void) snprintf(msg, sizeof (msg), dgettext(TEXT_DOMAIN,
-                           "cannot scrub %s"), zc.zc_name);
+                       (void) snprintf(errbuf, sizeof (errbuf),
+                           dgettext(TEXT_DOMAIN, "cannot scrub %s"),
+                           zhp->zpool_name);
                }
        } else if (func == POOL_SCAN_RESILVER) {
                assert(cmd == POOL_SCRUB_NORMAL);
-               (void) snprintf(msg, sizeof (msg), dgettext(TEXT_DOMAIN,
-                   "cannot restart resilver on %s"), zc.zc_name);
+               (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+                   "cannot restart resilver on %s"), zhp->zpool_name);
        } else if (func == POOL_SCAN_NONE) {
-               (void) snprintf(msg, sizeof (msg),
-                   dgettext(TEXT_DOMAIN, "cannot cancel scrubbing %s"),
-                   zc.zc_name);
+               (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+                   "cannot cancel scrubbing %s"), zhp->zpool_name);
        } else {
                assert(!"unexpected result");
        }
 
+       /*
+        * With EBUSY, five cases are possible:
+        *
+        * Current state                Requested
+        * 1. Normal Scrub Running      Normal Scrub or Error Scrub
+        * 2. Normal Scrub Paused       Error Scrub
+        * 3. Normal Scrub Paused       Pause Normal Scrub
+        * 4. Error Scrub Running       Normal Scrub or Error Scrub
+        * 5. Error Scrub Paused        Pause Error Scrub
+        * 6. Resilvering               Anything else
+        */
        if (err == EBUSY) {
                nvlist_t *nvroot;
                pool_scan_stat_t *ps = NULL;
@@ -2598,19 +2738,51 @@ zpool_scan(zpool_handle_t *zhp, pool_scan_func_t func, pool_scrub_cmd_t cmd)
                    ZPOOL_CONFIG_SCAN_STATS, (uint64_t **)&ps, &psc);
                if (ps && ps->pss_func == POOL_SCAN_SCRUB &&
                    ps->pss_state == DSS_SCANNING) {
-                       if (cmd == POOL_SCRUB_PAUSE)
-                               return (zfs_error(hdl, EZFS_SCRUB_PAUSED, msg));
-                       else
-                               return (zfs_error(hdl, EZFS_SCRUBBING, msg));
+                       if (ps->pss_pass_scrub_pause == 0) {
+                               /* handles case 1 */
+                               assert(cmd == POOL_SCRUB_NORMAL);
+                               return (zfs_error(hdl, EZFS_SCRUBBING,
+                                   errbuf));
+                       } else {
+                               if (func == POOL_SCAN_ERRORSCRUB) {
+                                       /* handles case 2 */
+                                       ASSERT3U(cmd, ==, POOL_SCRUB_NORMAL);
+                                       return (zfs_error(hdl,
+                                           EZFS_SCRUB_PAUSED_TO_CANCEL,
+                                           errbuf));
+                               } else {
+                                       /* handles case 3 */
+                                       ASSERT3U(func, ==, POOL_SCAN_SCRUB);
+                                       ASSERT3U(cmd, ==, POOL_SCRUB_PAUSE);
+                                       return (zfs_error(hdl,
+                                           EZFS_SCRUB_PAUSED, errbuf));
+                               }
+                       }
+               } else if (ps &&
+                   ps->pss_error_scrub_func == POOL_SCAN_ERRORSCRUB &&
+                   ps->pss_error_scrub_state == DSS_ERRORSCRUBBING) {
+                       if (ps->pss_pass_error_scrub_pause == 0) {
+                               /* handles case 4 */
+                               ASSERT3U(cmd, ==, POOL_SCRUB_NORMAL);
+                               return (zfs_error(hdl, EZFS_ERRORSCRUBBING,
+                                   errbuf));
+                       } else {
+                               /* handles case 5 */
+                               ASSERT3U(func, ==, POOL_SCAN_ERRORSCRUB);
+                               ASSERT3U(cmd, ==, POOL_SCRUB_PAUSE);
+                               return (zfs_error(hdl, EZFS_ERRORSCRUB_PAUSED,
+                                   errbuf));
+                       }
                } else {
-                       return (zfs_error(hdl, EZFS_RESILVERING, msg));
+                       /* handles case 6 */
+                       return (zfs_error(hdl, EZFS_RESILVERING, errbuf));
                }
        } else if (err == ENOENT) {
-               return (zfs_error(hdl, EZFS_NO_SCRUB, msg));
+               return (zfs_error(hdl, EZFS_NO_SCRUB, errbuf));
        } else if (err == ENOTSUP && func == POOL_SCAN_RESILVER) {
-               return (zfs_error(hdl, EZFS_NO_RESILVER_DEFER, msg));
+               return (zfs_error(hdl, EZFS_NO_RESILVER_DEFER, errbuf));
        } else {
-               return (zpool_standard_error(hdl, err, msg));
+               return (zpool_standard_error(hdl, err, errbuf));
        }
 }
 
@@ -2628,7 +2800,7 @@ vdev_to_nvlist_iter(nvlist_t *nv, nvlist_t *search, boolean_t *avail_spare,
        nvlist_t **child;
        nvlist_t *ret;
        uint64_t is_log;
-       char *srchkey;
+       const char *srchkey;
        nvpair_t *pair = nvlist_next_nvpair(search, NULL);
 
        /* Nothing to look for */
@@ -2650,7 +2822,7 @@ vdev_to_nvlist_iter(nvlist_t *nv, nvlist_t *search, boolean_t *avail_spare,
                break;
 
        case DATA_TYPE_STRING: {
-               char *srchval, *val;
+               const char *srchval, *val;
 
                srchval = fnvpair_value_string(pair);
                if (nvlist_lookup_string(nv, srchkey, &val) != 0)
@@ -2676,7 +2848,7 @@ vdev_to_nvlist_iter(nvlist_t *nv, nvlist_t *search, boolean_t *avail_spare,
                        if (zfs_strcmp_pathname(srchval, val, wholedisk) == 0)
                                return (nv);
 
-               } else if (strcmp(srchkey, ZPOOL_CONFIG_TYPE) == 0 && val) {
+               } else if (strcmp(srchkey, ZPOOL_CONFIG_TYPE) == 0) {
                        char *type, *idx, *end, *p;
                        uint64_t id, vdev_id;
 
@@ -2853,6 +3025,7 @@ zpool_vdev_is_interior(const char *name)
            strncmp(name, VDEV_TYPE_SPARE, strlen(VDEV_TYPE_SPARE)) == 0 ||
            strncmp(name,
            VDEV_TYPE_REPLACING, strlen(VDEV_TYPE_REPLACING)) == 0 ||
+           strncmp(name, VDEV_TYPE_ROOT, strlen(VDEV_TYPE_ROOT)) == 0 ||
            strncmp(name, VDEV_TYPE_MIRROR, strlen(VDEV_TYPE_MIRROR)) == 0)
                return (B_TRUE);
 
@@ -2863,6 +3036,9 @@ zpool_vdev_is_interior(const char *name)
        return (B_FALSE);
 }
 
+/*
+ * Lookup the nvlist for a given vdev.
+ */
 nvlist_t *
 zpool_find_vdev(zpool_handle_t *zhp, const char *path, boolean_t *avail_spare,
     boolean_t *l2cache, boolean_t *log)
@@ -2870,6 +3046,7 @@ zpool_find_vdev(zpool_handle_t *zhp, const char *path, boolean_t *avail_spare,
        char *end;
        nvlist_t *nvroot, *search, *ret;
        uint64_t guid;
+       boolean_t __avail_spare, __l2cache, __log;
 
        search = fnvlist_alloc();
 
@@ -2885,6 +3062,18 @@ zpool_find_vdev(zpool_handle_t *zhp, const char *path, boolean_t *avail_spare,
        nvroot = fnvlist_lookup_nvlist(zhp->zpool_config,
            ZPOOL_CONFIG_VDEV_TREE);
 
+       /*
+        * User can pass NULL for avail_spare, l2cache, and log, but
+        * we still need to provide variables to vdev_to_nvlist_iter(), so
+        * just point them to junk variables here.
+        */
+       if (!avail_spare)
+               avail_spare = &__avail_spare;
+       if (!l2cache)
+               l2cache = &__l2cache;
+       if (!log)
+               log = &__log;
+
        *avail_spare = B_FALSE;
        *l2cache = B_FALSE;
        if (log != NULL)
@@ -2895,154 +3084,6 @@ zpool_find_vdev(zpool_handle_t *zhp, const char *path, boolean_t *avail_spare,
        return (ret);
 }
 
-static int
-vdev_is_online(nvlist_t *nv)
-{
-       uint64_t ival;
-
-       if (nvlist_lookup_uint64(nv, ZPOOL_CONFIG_OFFLINE, &ival) == 0 ||
-           nvlist_lookup_uint64(nv, ZPOOL_CONFIG_FAULTED, &ival) == 0 ||
-           nvlist_lookup_uint64(nv, ZPOOL_CONFIG_REMOVED, &ival) == 0)
-               return (0);
-
-       return (1);
-}
-
-/*
- * Helper function for zpool_get_physpaths().
- */
-static int
-vdev_get_one_physpath(nvlist_t *config, char *physpath, size_t physpath_size,
-    size_t *bytes_written)
-{
-       size_t bytes_left, pos, rsz;
-       char *tmppath;
-       const char *format;
-
-       if (nvlist_lookup_string(config, ZPOOL_CONFIG_PHYS_PATH,
-           &tmppath) != 0)
-               return (EZFS_NODEVICE);
-
-       pos = *bytes_written;
-       bytes_left = physpath_size - pos;
-       format = (pos == 0) ? "%s" : " %s";
-
-       rsz = snprintf(physpath + pos, bytes_left, format, tmppath);
-       *bytes_written += rsz;
-
-       if (rsz >= bytes_left) {
-               /* if physpath was not copied properly, clear it */
-               if (bytes_left != 0) {
-                       physpath[pos] = 0;
-               }
-               return (EZFS_NOSPC);
-       }
-       return (0);
-}
-
-static int
-vdev_get_physpaths(nvlist_t *nv, char *physpath, size_t phypath_size,
-    size_t *rsz, boolean_t is_spare)
-{
-       char *type;
-       int ret;
-
-       if (nvlist_lookup_string(nv, ZPOOL_CONFIG_TYPE, &type) != 0)
-               return (EZFS_INVALCONFIG);
-
-       if (strcmp(type, VDEV_TYPE_DISK) == 0) {
-               /*
-                * An active spare device has ZPOOL_CONFIG_IS_SPARE set.
-                * For a spare vdev, we only want to boot from the active
-                * spare device.
-                */
-               if (is_spare) {
-                       uint64_t spare = 0;
-                       (void) nvlist_lookup_uint64(nv, ZPOOL_CONFIG_IS_SPARE,
-                           &spare);
-                       if (!spare)
-                               return (EZFS_INVALCONFIG);
-               }
-
-               if (vdev_is_online(nv)) {
-                       if ((ret = vdev_get_one_physpath(nv, physpath,
-                           phypath_size, rsz)) != 0)
-                               return (ret);
-               }
-       } else if (strcmp(type, VDEV_TYPE_MIRROR) == 0 ||
-           strcmp(type, VDEV_TYPE_RAIDZ) == 0 ||
-           strcmp(type, VDEV_TYPE_REPLACING) == 0 ||
-           (is_spare = (strcmp(type, VDEV_TYPE_SPARE) == 0))) {
-               nvlist_t **child;
-               uint_t count;
-               int i, ret;
-
-               if (nvlist_lookup_nvlist_array(nv,
-                   ZPOOL_CONFIG_CHILDREN, &child, &count) != 0)
-                       return (EZFS_INVALCONFIG);
-
-               for (i = 0; i < count; i++) {
-                       ret = vdev_get_physpaths(child[i], physpath,
-                           phypath_size, rsz, is_spare);
-                       if (ret == EZFS_NOSPC)
-                               return (ret);
-               }
-       }
-
-       return (EZFS_POOL_INVALARG);
-}
-
-/*
- * Get phys_path for a root pool config.
- * Return 0 on success; non-zero on failure.
- */
-static int
-zpool_get_config_physpath(nvlist_t *config, char *physpath, size_t phypath_size)
-{
-       size_t rsz;
-       nvlist_t *vdev_root;
-       nvlist_t **child;
-       uint_t count;
-       char *type;
-
-       rsz = 0;
-
-       if (nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE,
-           &vdev_root) != 0)
-               return (EZFS_INVALCONFIG);
-
-       if (nvlist_lookup_string(vdev_root, ZPOOL_CONFIG_TYPE, &type) != 0 ||
-           nvlist_lookup_nvlist_array(vdev_root, ZPOOL_CONFIG_CHILDREN,
-           &child, &count) != 0)
-               return (EZFS_INVALCONFIG);
-
-       /*
-        * root pool can only have a single top-level vdev.
-        */
-       if (strcmp(type, VDEV_TYPE_ROOT) != 0 || count != 1)
-               return (EZFS_POOL_INVALARG);
-
-       (void) vdev_get_physpaths(child[0], physpath, phypath_size, &rsz,
-           B_FALSE);
-
-       /* No online devices */
-       if (rsz == 0)
-               return (EZFS_NODEVICE);
-
-       return (0);
-}
-
-/*
- * Get phys_path for a root pool
- * Return 0 on success; non-zero on failure.
- */
-int
-zpool_get_physpath(zpool_handle_t *zhp, char *physpath, size_t phypath_size)
-{
-       return (zpool_get_config_physpath(zhp->zpool_config, physpath,
-           phypath_size));
-}
-
 /*
  * Convert a vdev path to a GUID.  Returns GUID or 0 on error.
  *
@@ -3087,31 +3128,31 @@ zpool_vdev_online(zpool_handle_t *zhp, const char *path, int flags,
     vdev_state_t *newstate)
 {
        zfs_cmd_t zc = {"\0"};
-       char msg[1024];
+       char errbuf[ERRBUFLEN];
        nvlist_t *tgt;
        boolean_t avail_spare, l2cache, islog;
        libzfs_handle_t *hdl = zhp->zpool_hdl;
 
        if (flags & ZFS_ONLINE_EXPAND) {
-               (void) snprintf(msg, sizeof (msg),
+               (void) snprintf(errbuf, sizeof (errbuf),
                    dgettext(TEXT_DOMAIN, "cannot expand %s"), path);
        } else {
-               (void) snprintf(msg, sizeof (msg),
+               (void) snprintf(errbuf, sizeof (errbuf),
                    dgettext(TEXT_DOMAIN, "cannot online %s"), path);
        }
 
        (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
        if ((tgt = zpool_find_vdev(zhp, path, &avail_spare, &l2cache,
            &islog)) == NULL)
-               return (zfs_error(hdl, EZFS_NODEVICE, msg));
+               return (zfs_error(hdl, EZFS_NODEVICE, errbuf));
 
        zc.zc_guid = fnvlist_lookup_uint64(tgt, ZPOOL_CONFIG_GUID);
 
-       if (avail_spare)
-               return (zfs_error(hdl, EZFS_ISSPARE, msg));
+       if (!(flags & ZFS_ONLINE_SPARE) && avail_spare)
+               return (zfs_error(hdl, EZFS_ISSPARE, errbuf));
 
 #ifndef __FreeBSD__
-       char *pathname;
+       const char *pathname;
        if ((flags & ZFS_ONLINE_EXPAND ||
            zpool_get_prop_int(zhp, ZPOOL_PROP_AUTOEXPAND, NULL)) &&
            nvlist_lookup_string(tgt, ZPOOL_CONFIG_PATH, &pathname) == 0) {
@@ -3126,7 +3167,7 @@ zpool_vdev_online(zpool_handle_t *zhp, const char *path, int flags,
                if (l2cache) {
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                            "cannot expand cache devices"));
-                       return (zfs_error(hdl, EZFS_VDEVNOTSUP, msg));
+                       return (zfs_error(hdl, EZFS_VDEVNOTSUP, errbuf));
                }
 
                if (wholedisk) {
@@ -3139,12 +3180,12 @@ zpool_vdev_online(zpool_handle_t *zhp, const char *path, int flags,
                                    sizeof (buf));
                                if (error != 0)
                                        return (zfs_error(hdl, EZFS_NODEVICE,
-                                           msg));
+                                           errbuf));
 
                                fullpath = buf;
                        }
 
-                       error = zpool_relabel_disk(hdl, fullpath, msg);
+                       error = zpool_relabel_disk(hdl, fullpath, errbuf);
                        if (error != 0)
                                return (error);
                }
@@ -3159,9 +3200,9 @@ zpool_vdev_online(zpool_handle_t *zhp, const char *path, int flags,
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "was split "
                            "from this pool into a new one.  Use '%s' "
                            "instead"), "zpool detach");
-                       return (zfs_error(hdl, EZFS_POSTSPLIT_ONLINE, msg));
+                       return (zfs_error(hdl, EZFS_POSTSPLIT_ONLINE, errbuf));
                }
-               return (zpool_standard_error(hdl, errno, msg));
+               return (zpool_standard_error(hdl, errno, errbuf));
        }
 
        *newstate = zc.zc_cookie;
@@ -3175,23 +3216,23 @@ int
 zpool_vdev_offline(zpool_handle_t *zhp, const char *path, boolean_t istmp)
 {
        zfs_cmd_t zc = {"\0"};
-       char msg[1024];
+       char errbuf[ERRBUFLEN];
        nvlist_t *tgt;
        boolean_t avail_spare, l2cache;
        libzfs_handle_t *hdl = zhp->zpool_hdl;
 
-       (void) snprintf(msg, sizeof (msg),
+       (void) snprintf(errbuf, sizeof (errbuf),
            dgettext(TEXT_DOMAIN, "cannot offline %s"), path);
 
        (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
        if ((tgt = zpool_find_vdev(zhp, path, &avail_spare, &l2cache,
            NULL)) == NULL)
-               return (zfs_error(hdl, EZFS_NODEVICE, msg));
+               return (zfs_error(hdl, EZFS_NODEVICE, errbuf));
 
        zc.zc_guid = fnvlist_lookup_uint64(tgt, ZPOOL_CONFIG_GUID);
 
        if (avail_spare)
-               return (zfs_error(hdl, EZFS_ISSPARE, msg));
+               return (zfs_error(hdl, EZFS_ISSPARE, errbuf));
 
        zc.zc_cookie = VDEV_STATE_OFFLINE;
        zc.zc_obj = istmp ? ZFS_OFFLINE_TEMPORARY : 0;
@@ -3205,19 +3246,53 @@ zpool_vdev_offline(zpool_handle_t *zhp, const char *path, boolean_t istmp)
                /*
                 * There are no other replicas of this device.
                 */
-               return (zfs_error(hdl, EZFS_NOREPLICAS, msg));
+               return (zfs_error(hdl, EZFS_NOREPLICAS, errbuf));
 
        case EEXIST:
                /*
                 * The log device has unplayed logs
                 */
-               return (zfs_error(hdl, EZFS_UNPLAYED_LOGS, msg));
+               return (zfs_error(hdl, EZFS_UNPLAYED_LOGS, errbuf));
 
        default:
-               return (zpool_standard_error(hdl, errno, msg));
+               return (zpool_standard_error(hdl, errno, errbuf));
        }
 }
 
+/*
+ * Remove the specified vdev asynchronously from the configuration, so
+ * that it may come ONLINE if reinserted. This is called from zed on
+ * Udev remove event.
+ * Note: We also have a similar function zpool_vdev_remove() that
+ * removes the vdev from the pool.
+ */
+int
+zpool_vdev_remove_wanted(zpool_handle_t *zhp, const char *path)
+{
+       zfs_cmd_t zc = {"\0"};
+       char errbuf[ERRBUFLEN];
+       nvlist_t *tgt;
+       boolean_t avail_spare, l2cache;
+       libzfs_handle_t *hdl = zhp->zpool_hdl;
+
+       (void) snprintf(errbuf, sizeof (errbuf),
+           dgettext(TEXT_DOMAIN, "cannot remove %s"), path);
+
+       (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
+       if ((tgt = zpool_find_vdev(zhp, path, &avail_spare, &l2cache,
+           NULL)) == NULL)
+               return (zfs_error(hdl, EZFS_NODEVICE, errbuf));
+
+       zc.zc_guid = fnvlist_lookup_uint64(tgt, ZPOOL_CONFIG_GUID);
+
+       zc.zc_cookie = VDEV_STATE_REMOVED;
+
+       if (zfs_ioctl(hdl, ZFS_IOC_VDEV_SET_STATE, &zc) == 0)
+               return (0);
+
+       return (zpool_standard_error(hdl, errno, errbuf));
+}
+
 /*
  * Mark the given vdev faulted.
  */
@@ -3225,10 +3300,10 @@ int
 zpool_vdev_fault(zpool_handle_t *zhp, uint64_t guid, vdev_aux_t aux)
 {
        zfs_cmd_t zc = {"\0"};
-       char msg[1024];
+       char errbuf[ERRBUFLEN];
        libzfs_handle_t *hdl = zhp->zpool_hdl;
 
-       (void) snprintf(msg, sizeof (msg),
+       (void) snprintf(errbuf, sizeof (errbuf),
            dgettext(TEXT_DOMAIN, "cannot fault %llu"), (u_longlong_t)guid);
 
        (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
@@ -3245,36 +3320,59 @@ zpool_vdev_fault(zpool_handle_t *zhp, uint64_t guid, vdev_aux_t aux)
                /*
                 * There are no other replicas of this device.
                 */
-               return (zfs_error(hdl, EZFS_NOREPLICAS, msg));
+               return (zfs_error(hdl, EZFS_NOREPLICAS, errbuf));
 
        default:
-               return (zpool_standard_error(hdl, errno, msg));
+               return (zpool_standard_error(hdl, errno, errbuf));
        }
 
 }
 
 /*
- * Mark the given vdev degraded.
+ * Generic set vdev state function
  */
-int
-zpool_vdev_degrade(zpool_handle_t *zhp, uint64_t guid, vdev_aux_t aux)
+static int
+zpool_vdev_set_state(zpool_handle_t *zhp, uint64_t guid, vdev_aux_t aux,
+    vdev_state_t state)
 {
        zfs_cmd_t zc = {"\0"};
-       char msg[1024];
+       char errbuf[ERRBUFLEN];
        libzfs_handle_t *hdl = zhp->zpool_hdl;
 
-       (void) snprintf(msg, sizeof (msg),
-           dgettext(TEXT_DOMAIN, "cannot degrade %llu"), (u_longlong_t)guid);
+       (void) snprintf(errbuf, sizeof (errbuf),
+           dgettext(TEXT_DOMAIN, "cannot set %s %llu"),
+           zpool_state_to_name(state, aux), (u_longlong_t)guid);
 
        (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
        zc.zc_guid = guid;
-       zc.zc_cookie = VDEV_STATE_DEGRADED;
+       zc.zc_cookie = state;
        zc.zc_obj = aux;
 
        if (zfs_ioctl(hdl, ZFS_IOC_VDEV_SET_STATE, &zc) == 0)
                return (0);
 
-       return (zpool_standard_error(hdl, errno, msg));
+       return (zpool_standard_error(hdl, errno, errbuf));
+}
+
+/*
+ * Mark the given vdev degraded.
+ */
+int
+zpool_vdev_degrade(zpool_handle_t *zhp, uint64_t guid, vdev_aux_t aux)
+{
+       return (zpool_vdev_set_state(zhp, guid, aux, VDEV_STATE_DEGRADED));
+}
+
+/*
+ * Mark the given vdev as in a removed state (as if the device does not exist).
+ *
+ * This is different than zpool_vdev_remove() which does a removal of a device
+ * from the pool (but the device does exist).
+ */
+int
+zpool_vdev_set_removed_state(zpool_handle_t *zhp, uint64_t guid, vdev_aux_t aux)
+{
+       return (zpool_vdev_set_state(zhp, guid, aux, VDEV_STATE_REMOVED));
 }
 
 /*
@@ -3289,7 +3387,8 @@ is_replacing_spare(nvlist_t *search, nvlist_t *tgt, int which)
 
        if (nvlist_lookup_nvlist_array(search, ZPOOL_CONFIG_CHILDREN, &child,
            &children) == 0) {
-               char *type = fnvlist_lookup_string(search, ZPOOL_CONFIG_TYPE);
+               const char *type = fnvlist_lookup_string(search,
+                   ZPOOL_CONFIG_TYPE);
                if ((strcmp(type, VDEV_TYPE_SPARE) == 0 ||
                    strcmp(type, VDEV_TYPE_DRAID_SPARE) == 0) &&
                    children == 2 && child[which] == tgt)
@@ -3312,34 +3411,35 @@ zpool_vdev_attach(zpool_handle_t *zhp, const char *old_disk,
     const char *new_disk, nvlist_t *nvroot, int replacing, boolean_t rebuild)
 {
        zfs_cmd_t zc = {"\0"};
-       char msg[1024];
+       char errbuf[ERRBUFLEN];
        int ret;
        nvlist_t *tgt;
        boolean_t avail_spare, l2cache, islog;
        uint64_t val;
        char *newname;
+       const char *type;
        nvlist_t **child;
        uint_t children;
        nvlist_t *config_root;
        libzfs_handle_t *hdl = zhp->zpool_hdl;
 
        if (replacing)
-               (void) snprintf(msg, sizeof (msg), dgettext(TEXT_DOMAIN,
+               (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
                    "cannot replace %s with %s"), old_disk, new_disk);
        else
-               (void) snprintf(msg, sizeof (msg), dgettext(TEXT_DOMAIN,
+               (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
                    "cannot attach %s to %s"), new_disk, old_disk);
 
        (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
        if ((tgt = zpool_find_vdev(zhp, old_disk, &avail_spare, &l2cache,
            &islog)) == NULL)
-               return (zfs_error(hdl, EZFS_NODEVICE, msg));
+               return (zfs_error(hdl, EZFS_NODEVICE, errbuf));
 
        if (avail_spare)
-               return (zfs_error(hdl, EZFS_ISSPARE, msg));
+               return (zfs_error(hdl, EZFS_ISSPARE, errbuf));
 
        if (l2cache)
-               return (zfs_error(hdl, EZFS_ISL2CACHE, msg));
+               return (zfs_error(hdl, EZFS_ISL2CACHE, errbuf));
 
        zc.zc_guid = fnvlist_lookup_uint64(tgt, ZPOOL_CONFIG_GUID);
        zc.zc_cookie = replacing;
@@ -3349,14 +3449,22 @@ zpool_vdev_attach(zpool_handle_t *zhp, const char *old_disk,
            zfeature_lookup_guid("org.openzfs:device_rebuild", NULL) != 0) {
                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                    "the loaded zfs module doesn't support device rebuilds"));
-               return (zfs_error(hdl, EZFS_POOL_NOTSUP, msg));
+               return (zfs_error(hdl, EZFS_POOL_NOTSUP, errbuf));
+       }
+
+       type = fnvlist_lookup_string(tgt, ZPOOL_CONFIG_TYPE);
+       if (strcmp(type, VDEV_TYPE_RAIDZ) == 0 &&
+           zfeature_lookup_guid("org.openzfs:raidz_expansion", NULL) != 0) {
+               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                   "the loaded zfs module doesn't support raidz expansion"));
+               return (zfs_error(hdl, EZFS_POOL_NOTSUP, errbuf));
        }
 
        if (nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_CHILDREN,
            &child, &children) != 0 || children != 1) {
                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                    "new device must be a single disk"));
-               return (zfs_error(hdl, EZFS_INVALCONFIG, msg));
+               return (zfs_error(hdl, EZFS_INVALCONFIG, errbuf));
        }
 
        config_root = fnvlist_lookup_nvlist(zpool_get_config(zhp, NULL),
@@ -3377,7 +3485,7 @@ zpool_vdev_attach(zpool_handle_t *zhp, const char *old_disk,
                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                    "can only be replaced by another hot spare"));
                free(newname);
-               return (zfs_error(hdl, EZFS_BADTARGET, msg));
+               return (zfs_error(hdl, EZFS_BADTARGET, errbuf));
        }
 
        free(newname);
@@ -3419,6 +3527,10 @@ zpool_vdev_attach(zpool_handle_t *zhp, const char *old_disk,
                                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                                    "cannot replace a replacing device"));
                        }
+               } else if (strcmp(type, VDEV_TYPE_RAIDZ) == 0) {
+                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                           "raidz_expansion feature must be enabled "
+                           "in order to attach a device to raidz"));
                } else {
                        char status[64] = {0};
                        zpool_prop_get_feature(zhp,
@@ -3435,7 +3547,7 @@ zpool_vdev_attach(zpool_handle_t *zhp, const char *old_disk,
                                    "disks"));
                        }
                }
-               (void) zfs_error(hdl, EZFS_BADTARGET, msg);
+               (void) zfs_error(hdl, EZFS_BADTARGET, errbuf);
                break;
 
        case EINVAL:
@@ -3444,14 +3556,13 @@ zpool_vdev_attach(zpool_handle_t *zhp, const char *old_disk,
                 */
                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                    "new device must be a single disk"));
-               (void) zfs_error(hdl, EZFS_INVALCONFIG, msg);
+               (void) zfs_error(hdl, EZFS_INVALCONFIG, errbuf);
                break;
 
        case EBUSY:
-               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "%s is busy, "
-                   "or device removal is in progress"),
+               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "%s is busy"),
                    new_disk);
-               (void) zfs_error(hdl, EZFS_BADDEV, msg);
+               (void) zfs_error(hdl, EZFS_BADDEV, errbuf);
                break;
 
        case EOVERFLOW:
@@ -3460,7 +3571,7 @@ zpool_vdev_attach(zpool_handle_t *zhp, const char *old_disk,
                 */
                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                    "device is too small"));
-               (void) zfs_error(hdl, EZFS_BADDEV, msg);
+               (void) zfs_error(hdl, EZFS_BADDEV, errbuf);
                break;
 
        case EDOM:
@@ -3470,18 +3581,46 @@ zpool_vdev_attach(zpool_handle_t *zhp, const char *old_disk,
                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                    "new device has a different optimal sector size; use the "
                    "option '-o ashift=N' to override the optimal size"));
-               (void) zfs_error(hdl, EZFS_BADDEV, msg);
+               (void) zfs_error(hdl, EZFS_BADDEV, errbuf);
                break;
 
        case ENAMETOOLONG:
                /*
                 * The resulting top-level vdev spec won't fit in the label.
                 */
-               (void) zfs_error(hdl, EZFS_DEVOVERFLOW, msg);
+               (void) zfs_error(hdl, EZFS_DEVOVERFLOW, errbuf);
+               break;
+
+       case ENXIO:
+               /*
+                * The existing raidz vdev has offline children
+                */
+               if (strcmp(type, VDEV_TYPE_RAIDZ) == 0) {
+                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                           "raidz vdev has devices that are are offline or "
+                           "being replaced"));
+                       (void) zfs_error(hdl, EZFS_BADDEV, errbuf);
+                       break;
+               } else {
+                       (void) zpool_standard_error(hdl, errno, errbuf);
+               }
                break;
 
+       case EADDRINUSE:
+               /*
+                * The boot reserved area is already being used (FreeBSD)
+                */
+               if (strcmp(type, VDEV_TYPE_RAIDZ) == 0) {
+                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                           "the reserved boot area needed for the expansion "
+                           "is already being used by a boot loader"));
+                       (void) zfs_error(hdl, EZFS_BADDEV, errbuf);
+               } else {
+                       (void) zpool_standard_error(hdl, errno, errbuf);
+               }
+               break;
        default:
-               (void) zpool_standard_error(hdl, errno, msg);
+               (void) zpool_standard_error(hdl, errno, errbuf);
        }
 
        return (-1);
@@ -3494,24 +3633,24 @@ int
 zpool_vdev_detach(zpool_handle_t *zhp, const char *path)
 {
        zfs_cmd_t zc = {"\0"};
-       char msg[1024];
+       char errbuf[ERRBUFLEN];
        nvlist_t *tgt;
        boolean_t avail_spare, l2cache;
        libzfs_handle_t *hdl = zhp->zpool_hdl;
 
-       (void) snprintf(msg, sizeof (msg),
+       (void) snprintf(errbuf, sizeof (errbuf),
            dgettext(TEXT_DOMAIN, "cannot detach %s"), path);
 
        (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
        if ((tgt = zpool_find_vdev(zhp, path, &avail_spare, &l2cache,
            NULL)) == NULL)
-               return (zfs_error(hdl, EZFS_NODEVICE, msg));
+               return (zfs_error(hdl, EZFS_NODEVICE, errbuf));
 
        if (avail_spare)
-               return (zfs_error(hdl, EZFS_ISSPARE, msg));
+               return (zfs_error(hdl, EZFS_ISSPARE, errbuf));
 
        if (l2cache)
-               return (zfs_error(hdl, EZFS_ISL2CACHE, msg));
+               return (zfs_error(hdl, EZFS_ISL2CACHE, errbuf));
 
        zc.zc_guid = fnvlist_lookup_uint64(tgt, ZPOOL_CONFIG_GUID);
 
@@ -3526,18 +3665,18 @@ zpool_vdev_detach(zpool_handle_t *zhp, const char *path)
                 */
                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "only "
                    "applicable to mirror and replacing vdevs"));
-               (void) zfs_error(hdl, EZFS_BADTARGET, msg);
+               (void) zfs_error(hdl, EZFS_BADTARGET, errbuf);
                break;
 
        case EBUSY:
                /*
                 * There are no other replicas of this device.
                 */
-               (void) zfs_error(hdl, EZFS_NOREPLICAS, msg);
+               (void) zfs_error(hdl, EZFS_NOREPLICAS, errbuf);
                break;
 
        default:
-               (void) zpool_standard_error(hdl, errno, msg);
+               (void) zpool_standard_error(hdl, errno, errbuf);
        }
 
        return (-1);
@@ -3592,7 +3731,8 @@ zpool_vdev_split(zpool_handle_t *zhp, char *newname, nvlist_t **newroot,
     nvlist_t *props, splitflags_t flags)
 {
        zfs_cmd_t zc = {"\0"};
-       char msg[1024], *bias;
+       char errbuf[ERRBUFLEN];
+       const char *bias;
        nvlist_t *tree, *config, **child, **newchild, *newconfig = NULL;
        nvlist_t **varray = NULL, *zc_props = NULL;
        uint_t c, children, newchildren, lastlog = 0, vcount, found = 0;
@@ -3601,11 +3741,11 @@ zpool_vdev_split(zpool_handle_t *zhp, char *newname, nvlist_t **newroot,
        boolean_t freelist = B_FALSE, memory_err = B_TRUE;
        int retval = 0;
 
-       (void) snprintf(msg, sizeof (msg),
+       (void) snprintf(errbuf, sizeof (errbuf),
            dgettext(TEXT_DOMAIN, "Unable to split %s"), zhp->zpool_name);
 
        if (!zpool_name_valid(hdl, B_FALSE, newname))
-               return (zfs_error(hdl, EZFS_INVALIDNAME, msg));
+               return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf));
 
        if ((config = zpool_get_config(zhp, NULL)) == NULL) {
                (void) fprintf(stderr, gettext("Internal error: unable to "
@@ -3619,7 +3759,7 @@ zpool_vdev_split(zpool_handle_t *zhp, char *newname, nvlist_t **newroot,
        if (props) {
                prop_flags_t flags = { .create = B_FALSE, .import = B_TRUE };
                if ((zc_props = zpool_valid_proplist(hdl, zhp->zpool_name,
-                   props, vers, flags, msg)) == NULL)
+                   props, vers, flags, errbuf)) == NULL)
                        return (-1);
                (void) nvlist_lookup_uint64(zc_props,
                    zpool_prop_to_name(ZPOOL_PROP_READONLY), &readonly);
@@ -3650,7 +3790,7 @@ zpool_vdev_split(zpool_handle_t *zhp, char *newname, nvlist_t **newroot,
        for (c = 0; c < children; c++) {
                uint64_t is_log = B_FALSE, is_hole = B_FALSE;
                boolean_t is_special = B_FALSE, is_dedup = B_FALSE;
-               char *type;
+               const char *type;
                nvlist_t **mchild, *vdev;
                uint_t mchildren;
                int entry;
@@ -3691,7 +3831,7 @@ zpool_vdev_split(zpool_handle_t *zhp, char *newname, nvlist_t **newroot,
                } else if (strcmp(type, VDEV_TYPE_MIRROR) != 0) {
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                            "Source pool must be composed only of mirrors\n"));
-                       retval = zfs_error(hdl, EZFS_INVALCONFIG, msg);
+                       retval = zfs_error(hdl, EZFS_INVALCONFIG, errbuf);
                        goto out;
                }
 
@@ -3739,7 +3879,7 @@ zpool_vdev_split(zpool_handle_t *zhp, char *newname, nvlist_t **newroot,
        if (found != newchildren) {
                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "Device list must "
                    "include at most one disk from each mirror"));
-               retval = zfs_error(hdl, EZFS_INVALCONFIG, msg);
+               retval = zfs_error(hdl, EZFS_INVALCONFIG, errbuf);
                goto out;
        }
 
@@ -3793,7 +3933,7 @@ zpool_vdev_split(zpool_handle_t *zhp, char *newname, nvlist_t **newroot,
                zcmd_write_src_nvlist(hdl, &zc, zc_props);
 
        if (zfs_ioctl(hdl, ZFS_IOC_VDEV_SPLIT, &zc) != 0) {
-               retval = zpool_standard_error(hdl, errno, msg);
+               retval = zpool_standard_error(hdl, errno, errbuf);
                goto out;
        }
 
@@ -3832,31 +3972,31 @@ int
 zpool_vdev_remove(zpool_handle_t *zhp, const char *path)
 {
        zfs_cmd_t zc = {"\0"};
-       char msg[1024];
+       char errbuf[ERRBUFLEN];
        nvlist_t *tgt;
        boolean_t avail_spare, l2cache, islog;
        libzfs_handle_t *hdl = zhp->zpool_hdl;
        uint64_t version;
 
-       (void) snprintf(msg, sizeof (msg),
+       (void) snprintf(errbuf, sizeof (errbuf),
            dgettext(TEXT_DOMAIN, "cannot remove %s"), path);
 
        if (zpool_is_draid_spare(path)) {
                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                    "dRAID spares cannot be removed"));
-               return (zfs_error(hdl, EZFS_NODEVICE, msg));
+               return (zfs_error(hdl, EZFS_NODEVICE, errbuf));
        }
 
        (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
        if ((tgt = zpool_find_vdev(zhp, path, &avail_spare, &l2cache,
            &islog)) == NULL)
-               return (zfs_error(hdl, EZFS_NODEVICE, msg));
+               return (zfs_error(hdl, EZFS_NODEVICE, errbuf));
 
        version = zpool_get_prop_int(zhp, ZPOOL_PROP_VERSION, NULL);
        if (islog && version < SPA_VERSION_HOLES) {
                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                    "pool must be upgraded to support log removal"));
-               return (zfs_error(hdl, EZFS_BADVERSION, msg));
+               return (zfs_error(hdl, EZFS_BADVERSION, errbuf));
        }
 
        zc.zc_guid = fnvlist_lookup_uint64(tgt, ZPOOL_CONFIG_GUID);
@@ -3866,11 +4006,17 @@ zpool_vdev_remove(zpool_handle_t *zhp, const char *path)
 
        switch (errno) {
 
+       case EALREADY:
+               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                   "removal for this vdev is already in progress."));
+               (void) zfs_error(hdl, EZFS_BUSY, errbuf);
+               break;
+
        case EINVAL:
                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                    "invalid config; all top-level vdevs must "
                    "have the same sector size and not be raidz."));
-               (void) zfs_error(hdl, EZFS_INVALCONFIG, msg);
+               (void) zfs_error(hdl, EZFS_INVALCONFIG, errbuf);
                break;
 
        case EBUSY:
@@ -3881,21 +4027,21 @@ zpool_vdev_remove(zpool_handle_t *zhp, const char *path)
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                            "Pool busy; removal may already be in progress"));
                }
-               (void) zfs_error(hdl, EZFS_BUSY, msg);
+               (void) zfs_error(hdl, EZFS_BUSY, errbuf);
                break;
 
        case EACCES:
                if (islog) {
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                            "Mount encrypted datasets to replay logs."));
-                       (void) zfs_error(hdl, EZFS_BUSY, msg);
+                       (void) zfs_error(hdl, EZFS_BUSY, errbuf);
                } else {
-                       (void) zpool_standard_error(hdl, errno, msg);
+                       (void) zpool_standard_error(hdl, errno, errbuf);
                }
                break;
 
        default:
-               (void) zpool_standard_error(hdl, errno, msg);
+               (void) zpool_standard_error(hdl, errno, errbuf);
        }
        return (-1);
 }
@@ -3904,10 +4050,10 @@ int
 zpool_vdev_remove_cancel(zpool_handle_t *zhp)
 {
        zfs_cmd_t zc = {{0}};
-       char msg[1024];
+       char errbuf[ERRBUFLEN];
        libzfs_handle_t *hdl = zhp->zpool_hdl;
 
-       (void) snprintf(msg, sizeof (msg),
+       (void) snprintf(errbuf, sizeof (errbuf),
            dgettext(TEXT_DOMAIN, "cannot cancel removal"));
 
        (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
@@ -3916,25 +4062,25 @@ zpool_vdev_remove_cancel(zpool_handle_t *zhp)
        if (zfs_ioctl(hdl, ZFS_IOC_VDEV_REMOVE, &zc) == 0)
                return (0);
 
-       return (zpool_standard_error(hdl, errno, msg));
+       return (zpool_standard_error(hdl, errno, errbuf));
 }
 
 int
 zpool_vdev_indirect_size(zpool_handle_t *zhp, const char *path,
     uint64_t *sizep)
 {
-       char msg[1024];
+       char errbuf[ERRBUFLEN];
        nvlist_t *tgt;
        boolean_t avail_spare, l2cache, islog;
        libzfs_handle_t *hdl = zhp->zpool_hdl;
 
-       (void) snprintf(msg, sizeof (msg),
+       (void) snprintf(errbuf, sizeof (errbuf),
            dgettext(TEXT_DOMAIN, "cannot determine indirect size of %s"),
            path);
 
        if ((tgt = zpool_find_vdev(zhp, path, &avail_spare, &l2cache,
            &islog)) == NULL)
-               return (zfs_error(hdl, EZFS_NODEVICE, msg));
+               return (zfs_error(hdl, EZFS_NODEVICE, errbuf));
 
        if (avail_spare || l2cache || islog) {
                *sizep = 0;
@@ -3944,7 +4090,7 @@ zpool_vdev_indirect_size(zpool_handle_t *zhp, const char *path,
        if (nvlist_lookup_uint64(tgt, ZPOOL_CONFIG_INDIRECT_SIZE, sizep) != 0) {
                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                    "indirect size not available"));
-               return (zfs_error(hdl, EINVAL, msg));
+               return (zfs_error(hdl, EINVAL, errbuf));
        }
        return (0);
 }
@@ -3956,7 +4102,7 @@ int
 zpool_clear(zpool_handle_t *zhp, const char *path, nvlist_t *rewindnvl)
 {
        zfs_cmd_t zc = {"\0"};
-       char msg[1024];
+       char errbuf[ERRBUFLEN];
        nvlist_t *tgt;
        zpool_load_policy_t policy;
        boolean_t avail_spare, l2cache;
@@ -3965,11 +4111,11 @@ zpool_clear(zpool_handle_t *zhp, const char *path, nvlist_t *rewindnvl)
        int error;
 
        if (path)
-               (void) snprintf(msg, sizeof (msg),
+               (void) snprintf(errbuf, sizeof (errbuf),
                    dgettext(TEXT_DOMAIN, "cannot clear errors for %s"),
                    path);
        else
-               (void) snprintf(msg, sizeof (msg),
+               (void) snprintf(errbuf, sizeof (errbuf),
                    dgettext(TEXT_DOMAIN, "cannot clear errors for %s"),
                    zhp->zpool_name);
 
@@ -3977,14 +4123,14 @@ zpool_clear(zpool_handle_t *zhp, const char *path, nvlist_t *rewindnvl)
        if (path) {
                if ((tgt = zpool_find_vdev(zhp, path, &avail_spare,
                    &l2cache, NULL)) == NULL)
-                       return (zfs_error(hdl, EZFS_NODEVICE, msg));
+                       return (zfs_error(hdl, EZFS_NODEVICE, errbuf));
 
                /*
                 * Don't allow error clearing for hot spares.  Do allow
                 * error clearing for l2cache devices.
                 */
                if (avail_spare)
-                       return (zfs_error(hdl, EZFS_ISSPARE, msg));
+                       return (zfs_error(hdl, EZFS_ISSPARE, errbuf));
 
                zc.zc_guid = fnvlist_lookup_uint64(tgt, ZPOOL_CONFIG_GUID);
        }
@@ -4014,7 +4160,7 @@ zpool_clear(zpool_handle_t *zhp, const char *path, nvlist_t *rewindnvl)
        }
 
        zcmd_free_nvlists(&zc);
-       return (zpool_standard_error(hdl, errno, msg));
+       return (zpool_standard_error(hdl, errno, errbuf));
 }
 
 /*
@@ -4024,10 +4170,10 @@ int
 zpool_vdev_clear(zpool_handle_t *zhp, uint64_t guid)
 {
        zfs_cmd_t zc = {"\0"};
-       char msg[1024];
+       char errbuf[ERRBUFLEN];
        libzfs_handle_t *hdl = zhp->zpool_hdl;
 
-       (void) snprintf(msg, sizeof (msg),
+       (void) snprintf(errbuf, sizeof (errbuf),
            dgettext(TEXT_DOMAIN, "cannot clear errors for %llx"),
            (u_longlong_t)guid);
 
@@ -4038,7 +4184,7 @@ zpool_vdev_clear(zpool_handle_t *zhp, uint64_t guid)
        if (zfs_ioctl(hdl, ZFS_IOC_CLEAR, &zc) == 0)
                return (0);
 
-       return (zpool_standard_error(hdl, errno, msg));
+       return (zpool_standard_error(hdl, errno, errbuf));
 }
 
 /*
@@ -4047,18 +4193,18 @@ zpool_vdev_clear(zpool_handle_t *zhp, uint64_t guid)
 int
 zpool_reguid(zpool_handle_t *zhp)
 {
-       char msg[1024];
+       char errbuf[ERRBUFLEN];
        libzfs_handle_t *hdl = zhp->zpool_hdl;
        zfs_cmd_t zc = {"\0"};
 
-       (void) snprintf(msg, sizeof (msg),
+       (void) snprintf(errbuf, sizeof (errbuf),
            dgettext(TEXT_DOMAIN, "cannot reguid '%s'"), zhp->zpool_name);
 
        (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
        if (zfs_ioctl(hdl, ZFS_IOC_POOL_REGUID, &zc) == 0)
                return (0);
 
-       return (zpool_standard_error(hdl, errno, msg));
+       return (zpool_standard_error(hdl, errno, errbuf));
 }
 
 /*
@@ -4123,10 +4269,11 @@ char *
 zpool_vdev_name(libzfs_handle_t *hdl, zpool_handle_t *zhp, nvlist_t *nv,
     int name_flags)
 {
-       char *path, *type, *env;
+       const char *type, *tpath;
+       const char *path;
        uint64_t value;
        char buf[PATH_BUF_LEN];
-       char tmpbuf[PATH_BUF_LEN];
+       char tmpbuf[PATH_BUF_LEN * 2];
 
        /*
         * vdev_name will be "root"/"root-0" for the root vdev, but it is the
@@ -4136,19 +4283,11 @@ zpool_vdev_name(libzfs_handle_t *hdl, zpool_handle_t *zhp, nvlist_t *nv,
        if (zhp != NULL && strcmp(type, "root") == 0)
                return (zfs_strdup(hdl, zpool_get_name(zhp)));
 
-       env = getenv("ZPOOL_VDEV_NAME_PATH");
-       if (env && (strtoul(env, NULL, 0) > 0 ||
-           !strncasecmp(env, "YES", 3) || !strncasecmp(env, "ON", 2)))
+       if (libzfs_envvar_is_set("ZPOOL_VDEV_NAME_PATH"))
                name_flags |= VDEV_NAME_PATH;
-
-       env = getenv("ZPOOL_VDEV_NAME_GUID");
-       if (env && (strtoul(env, NULL, 0) > 0 ||
-           !strncasecmp(env, "YES", 3) || !strncasecmp(env, "ON", 2)))
+       if (libzfs_envvar_is_set("ZPOOL_VDEV_NAME_GUID"))
                name_flags |= VDEV_NAME_GUID;
-
-       env = getenv("ZPOOL_VDEV_NAME_FOLLOW_LINKS");
-       if (env && (strtoul(env, NULL, 0) > 0 ||
-           !strncasecmp(env, "YES", 3) || !strncasecmp(env, "ON", 2)))
+       if (libzfs_envvar_is_set("ZPOOL_VDEV_NAME_FOLLOW_LINKS"))
                name_flags |= VDEV_NAME_FOLLOW_LINKS;
 
        if (nvlist_lookup_uint64(nv, ZPOOL_CONFIG_NOT_PRESENT, &value) == 0 ||
@@ -4156,7 +4295,9 @@ zpool_vdev_name(libzfs_handle_t *hdl, zpool_handle_t *zhp, nvlist_t *nv,
                (void) nvlist_lookup_uint64(nv, ZPOOL_CONFIG_GUID, &value);
                (void) snprintf(buf, sizeof (buf), "%llu", (u_longlong_t)value);
                path = buf;
-       } else if (nvlist_lookup_string(nv, ZPOOL_CONFIG_PATH, &path) == 0) {
+       } else if (nvlist_lookup_string(nv, ZPOOL_CONFIG_PATH, &tpath) == 0) {
+               path = tpath;
+
                if (name_flags & VDEV_NAME_FOLLOW_LINKS) {
                        char *rp = realpath(path, NULL);
                        if (rp) {
@@ -4247,33 +4388,28 @@ zpool_get_errlog(zpool_handle_t *zhp, nvlist_t **nverrlistp)
 {
        zfs_cmd_t zc = {"\0"};
        libzfs_handle_t *hdl = zhp->zpool_hdl;
-       uint64_t count;
-       zbookmark_phys_t *zb = NULL;
-       int i;
+       zbookmark_phys_t *buf;
+       uint64_t buflen = 10000; /* approx. 1MB of RAM */
+
+       if (fnvlist_lookup_uint64(zhp->zpool_config,
+           ZPOOL_CONFIG_ERRCOUNT) == 0)
+               return (0);
 
        /*
-        * Retrieve the raw error list from the kernel.  If the number of errors
-        * has increased, allocate more space and continue until we get the
-        * entire list.
+        * Retrieve the raw error list from the kernel.  If it doesn't fit,
+        * allocate a larger buffer and retry.
         */
-       count = fnvlist_lookup_uint64(zhp->zpool_config, ZPOOL_CONFIG_ERRCOUNT);
-       if (count == 0)
-               return (0);
-       zc.zc_nvlist_dst = (uintptr_t)zfs_alloc(zhp->zpool_hdl,
-           count * sizeof (zbookmark_phys_t));
-       zc.zc_nvlist_dst_size = count;
        (void) strcpy(zc.zc_name, zhp->zpool_name);
        for (;;) {
+               buf = zfs_alloc(zhp->zpool_hdl,
+                   buflen * sizeof (zbookmark_phys_t));
+               zc.zc_nvlist_dst = (uintptr_t)buf;
+               zc.zc_nvlist_dst_size = buflen;
                if (zfs_ioctl(zhp->zpool_hdl, ZFS_IOC_ERROR_LOG,
                    &zc) != 0) {
-                       free((void *)(uintptr_t)zc.zc_nvlist_dst);
+                       free(buf);
                        if (errno == ENOMEM) {
-                               void *dst;
-
-                               count = zc.zc_nvlist_dst_size;
-                               dst = zfs_alloc(zhp->zpool_hdl, count *
-                                   sizeof (zbookmark_phys_t));
-                               zc.zc_nvlist_dst = (uintptr_t)dst;
+                               buflen *= 2;
                        } else {
                                return (zpool_standard_error_fmt(hdl, errno,
                                    dgettext(TEXT_DOMAIN, "errors: List of "
@@ -4291,18 +4427,17 @@ zpool_get_errlog(zpool_handle_t *zhp, nvlist_t **nverrlistp)
         * _not_ copied as part of the process.  So we point the start of our
         * array appropriate and decrement the total number of elements.
         */
-       zb = ((zbookmark_phys_t *)(uintptr_t)zc.zc_nvlist_dst) +
-           zc.zc_nvlist_dst_size;
-       count -= zc.zc_nvlist_dst_size;
+       zbookmark_phys_t *zb = buf + zc.zc_nvlist_dst_size;
+       uint64_t zblen = buflen - zc.zc_nvlist_dst_size;
 
-       qsort(zb, count, sizeof (zbookmark_phys_t), zbookmark_mem_compare);
+       qsort(zb, zblen, sizeof (zbookmark_phys_t), zbookmark_mem_compare);
 
        verify(nvlist_alloc(nverrlistp, 0, KM_SLEEP) == 0);
 
        /*
         * Fill in the nverrlistp with nvlist's of dataset and object numbers.
         */
-       for (i = 0; i < count; i++) {
+       for (uint64_t i = 0; i < zblen; i++) {
                nvlist_t *nv;
 
                /* ignoring zb_blkid and zb_level for now */
@@ -4329,11 +4464,11 @@ zpool_get_errlog(zpool_handle_t *zhp, nvlist_t **nverrlistp)
                nvlist_free(nv);
        }
 
-       free((void *)(uintptr_t)zc.zc_nvlist_dst);
+       free(buf);
        return (0);
 
 nomem:
-       free((void *)(uintptr_t)zc.zc_nvlist_dst);
+       free(buf);
        return (no_memory(zhp->zpool_hdl));
 }
 
@@ -4834,8 +4969,8 @@ zpool_load_compat(const char *compat, boolean_t *features, char *report,
                for (uint_t i = 0; i < SPA_FEATURES; i++)
                        features[i] = B_TRUE;
 
-       char err_badfile[1024] = "";
-       char err_badtoken[1024] = "";
+       char err_badfile[ZFS_MAXPROPLEN] = "";
+       char err_badtoken[ZFS_MAXPROPLEN] = "";
 
        /*
         * We ignore errors from the directory open()
@@ -5003,7 +5138,7 @@ zpool_vdev_guid(zpool_handle_t *zhp, const char *vdevname, uint64_t *vdev_guid)
 
        verify(zhp != NULL);
        if (zpool_get_state(zhp) == POOL_STATE_UNAVAIL) {
-               char errbuf[1024];
+               char errbuf[ERRBUFLEN];
                (void) snprintf(errbuf, sizeof (errbuf),
                    dgettext(TEXT_DOMAIN, "pool is in an unavailable state"));
                return (zfs_error(zhp->zpool_hdl, EZFS_POOLUNAVAIL, errbuf));
@@ -5011,7 +5146,7 @@ zpool_vdev_guid(zpool_handle_t *zhp, const char *vdevname, uint64_t *vdev_guid)
 
        if ((tgt = zpool_find_vdev(zhp, vdevname, &avail_spare, &l2cache,
            NULL)) == NULL) {
-               char errbuf[1024];
+               char errbuf[ERRBUFLEN];
                (void) snprintf(errbuf, sizeof (errbuf),
                    dgettext(TEXT_DOMAIN, "can not find %s in %s"),
                    vdevname, zhp->zpool_name);
@@ -5031,11 +5166,11 @@ zpool_get_vdev_prop_value(nvlist_t *nvprop, vdev_prop_t prop, char *prop_name,
     char *buf, size_t len, zprop_source_t *srctype, boolean_t literal)
 {
        nvlist_t *nv;
-       char *strval;
+       const char *strval;
        uint64_t intval;
        zprop_source_t src = ZPROP_SRC_NONE;
 
-       if (prop == VDEV_PROP_USER) {
+       if (prop == VDEV_PROP_USERPROP) {
                /* user property, prop_name must contain the property name */
                assert(prop_name != NULL);
                if (nvlist_lookup_nvlist(nvprop, prop_name, &nv) == 0) {
@@ -5061,8 +5196,7 @@ zpool_get_vdev_prop_value(nvlist_t *nvprop, vdev_prop_t prop, char *prop_name,
                        strval = fnvlist_lookup_string(nv, ZPROP_VALUE);
                } else {
                        src = ZPROP_SRC_DEFAULT;
-                       if ((strval = (char *)vdev_prop_default_string(prop))
-                           == NULL)
+                       if ((strval = vdev_prop_default_string(prop)) == NULL)
                                strval = "-";
                }
                (void) strlcpy(buf, strval, len);
@@ -5126,6 +5260,19 @@ zpool_get_vdev_prop_value(nvlist_t *nvprop, vdev_prop_t prop, char *prop_name,
                                    (u_longlong_t)intval);
                        }
                        break;
+               case VDEV_PROP_CHECKSUM_N:
+               case VDEV_PROP_CHECKSUM_T:
+               case VDEV_PROP_IO_N:
+               case VDEV_PROP_IO_T:
+               case VDEV_PROP_SLOW_IO_N:
+               case VDEV_PROP_SLOW_IO_T:
+                       if (intval == UINT64_MAX) {
+                               (void) strlcpy(buf, "-", len);
+                       } else {
+                               (void) snprintf(buf, len, "%llu",
+                                   (u_longlong_t)intval);
+                       }
+                       break;
                case VDEV_PROP_FRAGMENTATION:
                        if (intval == UINT64_MAX) {
                                (void) strlcpy(buf, "-", len);
@@ -5156,6 +5303,9 @@ zpool_get_vdev_prop_value(nvlist_t *nvprop, vdev_prop_t prop, char *prop_name,
                } else {
                        src = ZPROP_SRC_DEFAULT;
                        intval = vdev_prop_default_numeric(prop);
+                       /* Only use if provided by the RAIDZ VDEV above */
+                       if (prop == VDEV_PROP_RAIDZ_EXPANDING)
+                               return (ENOENT);
                }
                if (vdev_prop_index_to_string(prop, intval,
                    (const char **)&strval) != 0)
@@ -5197,7 +5347,7 @@ zpool_get_vdev_prop(zpool_handle_t *zhp, const char *vdevname, vdev_prop_t prop,
 
        fnvlist_add_uint64(reqnvl, ZPOOL_VDEV_PROPS_GET_VDEV, vdev_guid);
 
-       if (prop != VDEV_PROP_USER) {
+       if (prop != VDEV_PROP_USERPROP) {
                /* prop_name overrides prop value */
                if (prop_name != NULL)
                        prop = vdev_name_to_prop(prop_name);
@@ -5221,7 +5371,7 @@ zpool_get_vdev_prop(zpool_handle_t *zhp, const char *vdevname, vdev_prop_t prop,
                ret = zpool_get_vdev_prop_value(retprops, prop, prop_name, buf,
                    len, srctype, literal);
        } else {
-               char errbuf[1024];
+               char errbuf[ERRBUFLEN];
                (void) snprintf(errbuf, sizeof (errbuf),
                    dgettext(TEXT_DOMAIN, "cannot get vdev property %s from"
                    " %s in %s"), prop_name, vdevname, zhp->zpool_name);
@@ -5259,7 +5409,7 @@ zpool_get_all_vdev_props(zpool_handle_t *zhp, const char *vdevname,
        nvlist_free(nvl);
 
        if (ret) {
-               char errbuf[1024];
+               char errbuf[ERRBUFLEN];
                (void) snprintf(errbuf, sizeof (errbuf),
                    dgettext(TEXT_DOMAIN, "cannot get vdev properties for"
                    " %s in %s"), vdevname, zhp->zpool_name);
@@ -5300,7 +5450,7 @@ zpool_set_vdev_prop(zpool_handle_t *zhp, const char *vdevname,
                return (no_memory(zhp->zpool_hdl));
        }
 
-       char errbuf[1024];
+       char errbuf[ERRBUFLEN];
        (void) snprintf(errbuf, sizeof (errbuf),
            dgettext(TEXT_DOMAIN, "cannot set property %s for %s on %s"),
            propname, vdevname, zhp->zpool_name);