mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-10-28 11:25:21 +01:00
zpool export: return EBUSY when zvol minors are in use
When a zvol block device is open (e.g., mounted by the OS), exporting the pool can hang indefinitely in zvol_remove_minors_impl() which calls cv_wait(&zv->zv_removing_cv) with no timeout. This is because the open holder never closes the device, so the condition variable is never signaled. Fix this by taking an additional spa reference in zvol_first_open() and releasing it in zvol_last_close(). With this reference, spa_export_common() will return EBUSY via the existing spa_refcount_zero() check, matching the behavior already provided for mounted ZFS datasets. Additionally, move the zvol_remove_minors() call in spa_export_common() to after the spa_refcount_zero() check. Previously it was called before the check, which could still hang if a zvol was open. Reviewed-by: Brian Behlendorf <[email protected]> Signed-off-by: Heonje LEE <[email protected]> Closes #18841
This commit is contained in:
1 parent
67bb391670
commit
0bb41751f2
5 files changed
+108
-5
No files matched your search
+6
-4
@@ -7761,10 +7761,7 @@ spa_export_common(const char *pool, int new_state, nvlist_t **oldconfig,
|
||||
spa_condense_debug_cancel(spa);
|
||||
#endif
|
||||
spa_async_suspend(spa);
|
||||
if (spa->spa_zvol_taskq) {
|
||||
zvol_remove_minors(spa, spa_name(spa), B_TRUE);
|
||||
taskq_wait(spa->spa_zvol_taskq);
|
||||
}
|
||||
|
||||
spa_namespace_enter(FTAG);
|
||||
spa->spa_export_thread = curthread;
|
||||
spa_close(spa, FTAG);
|
||||
@@ -7802,6 +7799,11 @@ spa_export_common(const char *pool, int new_state, nvlist_t **oldconfig,
|
||||
* that we are finshed.
|
||||
*/
|
||||
|
||||
if (spa->spa_zvol_taskq) {
|
||||
zvol_remove_minors(spa, spa_name(spa), B_TRUE);
|
||||
taskq_wait(spa->spa_zvol_taskq);
|
||||
}
|
||||
|
||||
if (spa->spa_sync_on) {
|
||||
vdev_t *rvd = spa->spa_root_vdev;
|
||||
/*
|
||||
|
||||
@@ -1309,6 +1309,13 @@ zvol_first_open(zvol_state_t *zv, boolean_t readonly)
|
||||
if (error) {
|
||||
dmu_objset_disown(os, 1, zv);
|
||||
zv->zv_objset = NULL;
|
||||
} else {
|
||||
/*
|
||||
* Take a hold on the spa so that spa_export_common() will
|
||||
* return EBUSY while the zvol block device is open, just
|
||||
* as it does for mounted datasets.
|
||||
*/
|
||||
spa_open_ref(dmu_objset_spa(os), zv);
|
||||
}
|
||||
|
||||
return (error);
|
||||
@@ -1323,10 +1330,14 @@ zvol_last_close(zvol_state_t *zv)
|
||||
if (zv->zv_flags & ZVOL_REMOVING)
|
||||
cv_broadcast(&zv->zv_removing_cv);
|
||||
|
||||
spa_t *spa = dmu_objset_spa(zv->zv_objset);
|
||||
|
||||
zvol_shutdown_zv(zv);
|
||||
|
||||
dmu_objset_disown(zv->zv_objset, 1, zv);
|
||||
zv->zv_objset = NULL;
|
||||
|
||||
spa_close(spa, zv);
|
||||
}
|
||||
|
||||
typedef struct minors_job {
|
||||
|
||||
@@ -463,7 +463,7 @@ tags = ['functional', 'cli_root', 'zpool_events']
|
||||
[tests/functional/cli_root/zpool_export]
|
||||
tests = ['zpool_export_001_pos', 'zpool_export_002_pos',
|
||||
'zpool_export_003_neg', 'zpool_export_004_pos',
|
||||
'zpool_export_parallel_pos', 'zpool_export_parallel_admin']
|
||||
'zpool_export_005_neg', 'zpool_export_parallel_pos', 'zpool_export_parallel_admin']
|
||||
tags = ['functional', 'cli_root', 'zpool_export']
|
||||
|
||||
[tests/functional/cli_root/zpool_get]
|
||||
|
||||
@@ -1191,6 +1191,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
|
||||
functional/cli_root/zpool_export/zpool_export_002_pos.ksh \
|
||||
functional/cli_root/zpool_export/zpool_export_003_neg.ksh \
|
||||
functional/cli_root/zpool_export/zpool_export_004_pos.ksh \
|
||||
functional/cli_root/zpool_export/zpool_export_005_neg.ksh \
|
||||
functional/cli_root/zpool_export/zpool_export_parallel_admin.ksh \
|
||||
functional/cli_root/zpool_export/zpool_export_parallel_pos.ksh \
|
||||
functional/cli_root/zpool_get/cleanup.ksh \
|
||||
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
#!/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) 2025 by Heonje LEE. All rights reserved.
|
||||
#
|
||||
|
||||
. $STF_SUITE/include/libtest.shlib
|
||||
. $STF_SUITE/tests/functional/cli_root/zpool_export/zpool_export.kshlib
|
||||
|
||||
#
|
||||
# DESCRIPTION:
|
||||
# Exporting a pool with a mounted zvol block device must fail with
|
||||
# EBUSY rather than hanging the system in an uninterruptible sleep.
|
||||
# This mirrors the behavior already provided for mounted ZFS
|
||||
# filesystems (see zpool_export_002_pos.ksh).
|
||||
#
|
||||
# STRATEGY:
|
||||
# 1. Create a pool with a zvol.
|
||||
# 2. Format and mount the zvol block device.
|
||||
# 3. Attempt to export the pool -- must fail (EBUSY).
|
||||
# 4. Unmount the zvol.
|
||||
# 5. Export should now succeed.
|
||||
#
|
||||
|
||||
verify_runnable "global"
|
||||
|
||||
function cleanup
|
||||
{
|
||||
ismounted $mntpnt $NEWFS_DEFAULT_FS && log_must umount $mntpnt
|
||||
[[ -d $mntpnt ]] && log_must rmdir $mntpnt
|
||||
datasetexists $TESTPOOL1/vol && log_must zfs destroy $TESTPOOL1/vol
|
||||
poolexists $TESTPOOL1 && destroy_pool $TESTPOOL1
|
||||
[[ -f $vdev ]] && log_must rm -f $vdev
|
||||
zpool_export_cleanup
|
||||
}
|
||||
|
||||
mntpnt=$TESTDIR0/zvol_export_mnt
|
||||
vdev=$TESTDIR0/zvol_export_vdev
|
||||
|
||||
log_onexit cleanup
|
||||
|
||||
log_assert "Exporting a pool with a mounted zvol returns EBUSY"
|
||||
|
||||
log_must mkdir -p $TESTDIR0
|
||||
log_must truncate -s $MINVDEVSIZE $vdev
|
||||
|
||||
# 1. Create pool + zvol
|
||||
log_must zpool create $TESTPOOL1 $vdev
|
||||
log_must zfs create -V 100M $TESTPOOL1/vol
|
||||
block_device_wait ${ZVOL_DEVDIR}/$TESTPOOL1/vol
|
||||
|
||||
# 2. Format and mount the zvol block device
|
||||
log_must eval "new_fs ${ZVOL_RDEVDIR}/$TESTPOOL1/vol >/dev/null 2>&1"
|
||||
log_must mkdir -p $mntpnt
|
||||
log_must mount ${ZVOL_DEVDIR}/$TESTPOOL1/vol $mntpnt
|
||||
|
||||
# 3. Export must fail with EBUSY (not hang)
|
||||
log_mustnot zpool export $TESTPOOL1
|
||||
log_must poolexists $TESTPOOL1
|
||||
|
||||
# 4. Unmount the zvol
|
||||
log_must umount $mntpnt
|
||||
|
||||
# 5. Export should now succeed
|
||||
log_must zpool export $TESTPOOL1
|
||||
log_mustnot poolexists $TESTPOOL1
|
||||
|
||||
log_pass "Export with mounted zvol correctly returned EBUSY"
|
||||
Reference in new issue
Block a user