zpool create: report which device caused failure

When zpool create fails because a vdev is already in use, the
error message now identifies the problematic device and the pool
it belongs to, e.g.:

  cannot create 'tank': device '/dev/sdb1' is part of
  active pool 'rpool'

Implementation follows the ZPOOL_CONFIG_LOAD_INFO pattern used
by zpool import:

  - Add spa_create_info to spa_t to capture error info during
    vdev_label_init(), before vdev_close() resets vdev state
  - When vdev_inuse() detects a conflict, read the on-disk
    label to extract the pool name and store it with the
    device path
  - Return the info wrapped under ZPOOL_CONFIG_CREATE_INFO
    through the ioctl zc_nvlist_dst to userspace
  - In libzfs, zpool_create_info() unwraps the nvlist and
    formats the device-specific error message

Restructure zpool_create() error handling so all switch cases
use break instead of return, eliminating duplicated cleanup
code and using the single create_failed exit path.

Reviewed-by: Tony Hutter <[email protected]>
Reviewed-by: Brian Behlendorf <[email protected]>
Signed-off-by: Christos Longros <[email protected]>
Closes #18213
This commit is contained in:
Christos Longros authored and GitHub committed 2026-04-03 14:18:24 -07:00
1 parent cfae167754
commit 33ed68fc24
11 files changed
+233 -34

No files matched your search

+6 -5
View File
@@ -3038,7 +3038,7 @@ ztest_spa_create_destroy(ztest_ds_t *zd, uint64_t id)
*/
nvroot = make_vdev_root("/dev/bogus", NULL, NULL, 0, 0, NULL, 0, 0, 1);
VERIFY3U(ENOENT, ==,
spa_create("ztest_bad_file", nvroot, NULL, NULL, NULL));
spa_create("ztest_bad_file", nvroot, NULL, NULL, NULL, NULL));
fnvlist_free(nvroot);
/*
@@ -3046,7 +3046,7 @@ ztest_spa_create_destroy(ztest_ds_t *zd, uint64_t id)
*/
nvroot = make_vdev_root("/dev/bogus", NULL, NULL, 0, 0, NULL, 0, 2, 1);
VERIFY3U(ENOENT, ==,
spa_create("ztest_bad_mirror", nvroot, NULL, NULL, NULL));
spa_create("ztest_bad_mirror", nvroot, NULL, NULL, NULL, NULL));
fnvlist_free(nvroot);
/*
@@ -3056,7 +3056,7 @@ ztest_spa_create_destroy(ztest_ds_t *zd, uint64_t id)
(void) pthread_rwlock_rdlock(&ztest_name_lock);
nvroot = make_vdev_root("/dev/bogus", NULL, NULL, 0, 0, NULL, 0, 0, 1);
VERIFY3U(EEXIST, ==,
spa_create(zo->zo_pool, nvroot, NULL, NULL, NULL));
spa_create(zo->zo_pool, nvroot, NULL, NULL, NULL, NULL));
fnvlist_free(nvroot);
/*
@@ -3208,7 +3208,7 @@ ztest_spa_upgrade(ztest_ds_t *zd, uint64_t id)
props = fnvlist_alloc();
fnvlist_add_uint64(props,
zpool_prop_to_name(ZPOOL_PROP_VERSION), version);
VERIFY0(spa_create(name, nvroot, props, NULL, NULL));
VERIFY0(spa_create(name, nvroot, props, NULL, NULL, NULL));
fnvlist_free(nvroot);
fnvlist_free(props);
@@ -8686,7 +8686,8 @@ ztest_init(ztest_shared_t *zs)
free(buf);
}
VERIFY0(spa_create(ztest_opts.zo_pool, nvroot, props, NULL, NULL));
VERIFY0(spa_create(ztest_opts.zo_pool, nvroot, props,
NULL, NULL, NULL));
fnvlist_free(nvroot);
fnvlist_free(props);
+3
View File
@@ -952,6 +952,9 @@ typedef struct zpool_load_policy {
#define ZPOOL_CONFIG_BOOTFS "bootfs" /* not stored on disk */
#define ZPOOL_CONFIG_MISSING_DEVICES "missing_vdevs" /* not stored on disk */
#define ZPOOL_CONFIG_LOAD_INFO "load_info" /* not stored on disk */
#define ZPOOL_CONFIG_CREATE_INFO "create_info" /* not stored on disk */
#define ZPOOL_CREATE_INFO_VDEV "create_err_vdev"
#define ZPOOL_CREATE_INFO_POOL "create_err_pool"
#define ZPOOL_CONFIG_REWIND_INFO "rewind_info" /* not stored on disk */
#define ZPOOL_CONFIG_UNSUP_FEAT "unsup_feat" /* not stored on disk */
#define ZPOOL_CONFIG_ENABLED_FEAT "enabled_feat" /* not stored on disk */
+1 -1
View File
@@ -777,7 +777,7 @@ extern int spa_open_rewind(const char *pool, spa_t **, const void *tag,
extern int spa_get_stats(const char *pool, nvlist_t **config, char *altroot,
size_t buflen);
extern int spa_create(const char *pool, nvlist_t *nvroot, nvlist_t *props,
nvlist_t *zplprops, struct dsl_crypto_params *dcp);
nvlist_t *zplprops, struct dsl_crypto_params *dcp, nvlist_t **errinfo);
extern int spa_import(char *pool, nvlist_t *config, nvlist_t *props,
uint64_t flags);
extern nvlist_t *spa_tryimport(nvlist_t *tryconfig);
+1
View File
@@ -229,6 +229,7 @@ struct spa {
nvlist_t *spa_config_syncing; /* currently syncing config */
nvlist_t *spa_config_splitting; /* config for splitting */
nvlist_t *spa_load_info; /* info and errors from load */
nvlist_t *spa_create_info; /* info from create */
uint64_t spa_config_txg; /* txg of last config change */
uint32_t spa_sync_pass; /* iterate-to-convergence */
pool_state_t spa_state; /* pool state */
+75 -24
View File
@@ -1536,6 +1536,50 @@ zpool_is_draid_spare(const char *name)
return (B_FALSE);
}
/*
* Extract device-specific error information from a failed pool creation.
* If the kernel returned ZPOOL_CONFIG_CREATE_INFO in the ioctl output,
* set an appropriate error aux message identifying the problematic device.
*/
static int
zpool_create_info(libzfs_handle_t *hdl, zfs_cmd_t *zc)
{
nvlist_t *outnv = NULL;
nvlist_t *info = NULL;
const char *vdev = NULL;
const char *pname = NULL;
if (zc->zc_nvlist_dst_size == 0)
return (ENOENT);
if (nvlist_unpack((void *)(uintptr_t)zc->zc_nvlist_dst,
zc->zc_nvlist_dst_size, &outnv, 0) != 0 || outnv == NULL)
return (EINVAL);
if (nvlist_lookup_nvlist(outnv, ZPOOL_CONFIG_CREATE_INFO, &info) != 0) {
nvlist_free(outnv);
return (EINVAL);
}
if (nvlist_lookup_string(info, ZPOOL_CREATE_INFO_VDEV, &vdev) != 0) {
nvlist_free(outnv);
return (EINVAL);
}
if (nvlist_lookup_string(info, ZPOOL_CREATE_INFO_POOL, &pname) == 0) {
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"device '%s' is part of active pool '%s'"),
vdev, pname);
} else {
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"device '%s' is in use"), vdev);
}
nvlist_free(outnv);
return (0);
}
/*
* Create the named pool, using the provided vdev list. It is assumed
* that the consumer has already validated the contents of the nvlist, so we
@@ -1615,16 +1659,9 @@ zpool_create(libzfs_handle_t *hdl, const char *pool, nvlist_t *nvroot,
zcmd_write_src_nvlist(hdl, &zc, zc_props);
(void) strlcpy(zc.zc_name, pool, sizeof (zc.zc_name));
zcmd_alloc_dst_nvlist(hdl, &zc, 4096);
if ((ret = zfs_ioctl(hdl, ZFS_IOC_POOL_CREATE, &zc)) != 0) {
zcmd_free_nvlists(&zc);
nvlist_free(zc_props);
nvlist_free(zc_fsprops);
nvlist_free(hidden_args);
if (wkeydata != NULL)
free(wkeydata);
switch (errno) {
case EBUSY:
/*
@@ -1634,11 +1671,14 @@ zpool_create(libzfs_handle_t *hdl, const char *pool, nvlist_t *nvroot,
* label. This can also happen under if the device is
* part of an active md or lvm device.
*/
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"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, errbuf));
if (zpool_create_info(hdl, &zc) != 0) {
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"one or more vdevs refer to the same "
"device, or one of\nthe devices is "
"part of an active md or lvm device"));
}
ret = zfs_error(hdl, EZFS_BADDEV, errbuf);
break;
case ERANGE:
/*
@@ -1653,7 +1693,8 @@ 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, errbuf));
ret = zfs_error(hdl, EZFS_BADPROP, errbuf);
break;
case EOVERFLOW:
/*
@@ -1672,12 +1713,14 @@ 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, errbuf));
ret = zfs_error(hdl, EZFS_BADDEV, errbuf);
break;
case ENOSPC:
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"one or more devices is out of space"));
return (zfs_error(hdl, EZFS_BADDEV, errbuf));
ret = zfs_error(hdl, EZFS_BADDEV, errbuf);
break;
case EINVAL:
if (zpool_has_draid_vdev(nvroot) &&
@@ -1685,24 +1728,32 @@ 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, errbuf));
ret = zfs_error(hdl, EZFS_BADDEV, errbuf);
} else {
return (zpool_standard_error(hdl, errno,
errbuf));
ret = zpool_standard_error(hdl, errno, errbuf);
}
break;
case ENXIO:
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"one or more devices could not be opened"));
return (zfs_error(hdl, EZFS_BADDEV, errbuf));
if (zpool_create_info(hdl, &zc) == 0) {
ret = zfs_error(hdl, EZFS_BADDEV, errbuf);
} else {
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"one or more devices could not be "
"opened"));
ret = zfs_error(hdl, EZFS_BADDEV, errbuf);
}
break;
case EDOM:
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"block size out of range or does not match"));
return (zfs_error(hdl, EZFS_BADDEV, errbuf));
ret = zfs_error(hdl, EZFS_BADDEV, errbuf);
break;
default:
return (zpool_standard_error(hdl, errno, errbuf));
ret = zpool_standard_error(hdl, errno, errbuf);
break;
}
}
+9 -1
View File
@@ -1947,6 +1947,10 @@ spa_activate(spa_t *spa, spa_mode_t mode)
static void
spa_deactivate(spa_t *spa)
{
if (spa->spa_create_info != NULL) {
nvlist_free(spa->spa_create_info);
spa->spa_create_info = NULL;
}
ASSERT(spa->spa_sync_on == B_FALSE);
ASSERT0P(spa->spa_dsl_pool);
ASSERT0P(spa->spa_root_vdev);
@@ -7060,7 +7064,7 @@ spa_create_check_encryption_params(dsl_crypto_params_t *dcp,
*/
int
spa_create(const char *pool, nvlist_t *nvroot, nvlist_t *props,
nvlist_t *zplprops, dsl_crypto_params_t *dcp)
nvlist_t *zplprops, dsl_crypto_params_t *dcp, nvlist_t **errinfo)
{
spa_t *spa;
const char *altroot = NULL;
@@ -7212,6 +7216,10 @@ spa_create(const char *pool, nvlist_t *nvroot, nvlist_t *props,
spa_config_exit(spa, SCL_ALL, FTAG);
if (error != 0) {
if (errinfo != NULL) {
*errinfo = spa->spa_create_info;
spa->spa_create_info = NULL;
}
spa_unload(spa);
spa_deactivate(spa);
spa_remove(spa);
+22 -1
View File
@@ -1109,8 +1109,29 @@ vdev_label_init(vdev_t *vd, uint64_t crtxg, vdev_labeltype_t reason)
* Determine if the vdev is in use.
*/
if (reason != VDEV_LABEL_REMOVE && reason != VDEV_LABEL_SPLIT &&
vdev_inuse(vd, crtxg, reason, &spare_guid, &l2cache_guid))
vdev_inuse(vd, crtxg, reason, &spare_guid, &l2cache_guid)) {
if (spa->spa_create_info == NULL) {
nvlist_t *nv = fnvlist_alloc();
nvlist_t *cfg;
if (vd->vdev_path != NULL)
fnvlist_add_string(nv,
ZPOOL_CREATE_INFO_VDEV, vd->vdev_path);
cfg = vdev_label_read_config(vd, -1ULL);
if (cfg != NULL) {
const char *pname;
if (nvlist_lookup_string(cfg,
ZPOOL_CONFIG_POOL_NAME, &pname) == 0)
fnvlist_add_string(nv,
ZPOOL_CREATE_INFO_POOL, pname);
nvlist_free(cfg);
}
spa->spa_create_info = nv;
}
return (SET_ERROR(EBUSY));
}
/*
* If this is a request to add or replace a spare or l2cache device
+11 -1
View File
@@ -1660,6 +1660,7 @@ zfs_ioc_pool_create(zfs_cmd_t *zc)
dsl_crypto_params_t *dcp = NULL;
const char *spa_name = zc->zc_name;
boolean_t unload_wkey = B_TRUE;
nvlist_t *errinfo = NULL;
if ((error = get_nvlist(zc->zc_nvlist_conf, zc->zc_nvlist_conf_size,
zc->zc_iflags, &config)))
@@ -1711,7 +1712,16 @@ zfs_ioc_pool_create(zfs_cmd_t *zc)
spa_name = tname;
}
error = spa_create(zc->zc_name, config, props, zplprops, dcp);
error = spa_create(zc->zc_name, config, props, zplprops, dcp,
&errinfo);
if (errinfo != NULL) {
nvlist_t *outnv = fnvlist_alloc();
fnvlist_add_nvlist(outnv,
ZPOOL_CONFIG_CREATE_INFO, errinfo);
(void) put_nvlist(zc, outnv);
nvlist_free(outnv);
nvlist_free(errinfo);
}
/*
* Set the remaining root properties
+1 -1
View File
@@ -430,7 +430,7 @@ tests = ['zpool_create_001_pos', 'zpool_create_002_pos',
'zpool_create_features_005_pos', 'zpool_create_features_006_pos',
'zpool_create_features_007_pos', 'zpool_create_features_008_pos',
'zpool_create_features_009_pos', 'create-o_ashift',
'zpool_create_tempname', 'zpool_create_dryrun_output']
'zpool_create_tempname', 'zpool_create_errinfo_001_neg', 'zpool_create_dryrun_output']
tags = ['functional', 'cli_root', 'zpool_create']
[tests/functional/cli_root/zpool_destroy]
+1
View File
@@ -1098,6 +1098,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
functional/cli_root/zpool_create/zpool_create_features_008_pos.ksh \
functional/cli_root/zpool_create/zpool_create_features_009_pos.ksh \
functional/cli_root/zpool_create/zpool_create_tempname.ksh \
functional/cli_root/zpool_create/zpool_create_errinfo_001_neg.ksh \
functional/cli_root/zpool_destroy/zpool_destroy_001_pos.ksh \
functional/cli_root/zpool_destroy/zpool_destroy_002_pos.ksh \
functional/cli_root/zpool_destroy/zpool_destroy_003_neg.ksh \
@@ -0,0 +1,103 @@
#!/bin/ksh -p
# SPDX-License-Identifier: CDDL-1.0
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# 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 https://opensource.org/licenses/CDDL-1.0.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright (c) 2026, Christos Longros. All rights reserved.
#
. $STF_SUITE/include/libtest.shlib
#
# DESCRIPTION:
# 'zpool create' should report which device is in use when it fails
# because a vdev belongs to an active pool.
#
# STRATEGY:
# 1. Create a backing file for two block devices.
# 2. Attach two block devices to the same file.
# 3. Attempt to create a mirror pool using both devices.
# 4. Verify the error message identifies the specific device.
# 5. Verify the error message names the active pool.
#
verify_runnable "global"
TESTFILE="$TEST_BASE_DIR/vdev_errinfo"
TESTPOOL2="testpool_errinfo"
BLKDEV1=""
BLKDEV2=""
function cleanup
{
destroy_pool $TESTPOOL2
destroy_pool $TESTPOOL
if is_linux; then
[[ -n "$BLKDEV1" ]] && losetup -d "$BLKDEV1" 2>/dev/null
[[ -n "$BLKDEV2" ]] && losetup -d "$BLKDEV2" 2>/dev/null
elif is_freebsd; then
[[ -n "$BLKDEV1" ]] && mdconfig -d -u "$BLKDEV1" 2>/dev/null
[[ -n "$BLKDEV2" ]] && mdconfig -d -u "$BLKDEV2" 2>/dev/null
fi
rm -f "$TESTFILE"
}
log_assert "'zpool create' reports device-specific errors for in-use vdevs."
log_onexit cleanup
# Create a file to back the block devices
log_must truncate -s $MINVDEVSIZE "$TESTFILE"
# Attach two block devices to the same file (platform-specific)
if is_linux; then
BLKDEV1=$(losetup -f --show "$TESTFILE")
BLKDEV2=$(losetup -f --show "$TESTFILE")
elif is_freebsd; then
BLKDEV1=/dev/$(mdconfig -a -t vnode -f "$TESTFILE")
BLKDEV2=/dev/$(mdconfig -a -t vnode -f "$TESTFILE")
else
log_unsupported "Platform not supported for this test"
fi
log_note "Using devices: $BLKDEV1 $BLKDEV2"
# Attempt to create a mirror pool; this should fail because both
# devices refer to the same underlying file.
log_mustnot zpool create $TESTPOOL2 mirror $BLKDEV1 $BLKDEV2
# Re-run to capture the error message for content verification
errmsg=$(zpool create $TESTPOOL2 mirror $BLKDEV1 $BLKDEV2 2>&1)
log_note "zpool create output: $errmsg"
# Error message should name one of the devices
log_must eval "echo '$errmsg' | grep -qE '$BLKDEV1|$BLKDEV2'"
# Error message should name the active pool
if echo "$errmsg" | grep -q "active pool"; then
log_note "Error message correctly identifies the active pool"
else
log_fail "Error message does not mention the active pool: $errmsg"
fi
log_pass "'zpool create' reports device-specific errors for in-use vdevs."