diff --git a/cmd/zpool/zpool_main.c b/cmd/zpool/zpool_main.c index 474f3bbb1096..ed500415ee4c 100644 --- a/cmd/zpool/zpool_main.c +++ b/cmd/zpool/zpool_main.c @@ -392,7 +392,7 @@ get_usage(zpool_help_t idx) case HELP_REOPEN: return (gettext("\treopen [-n] \n")); case HELP_INITIALIZE: - return (gettext("\tinitialize [-c | -s] [-w] " + return (gettext("\tinitialize [-c | -s | -u] [-w] " "[ ...]\n")); case HELP_SCRUB: return (gettext("\tscrub [-s | -p] [-w] ...\n")); @@ -548,12 +548,13 @@ usage(boolean_t requested) } /* - * zpool initialize [-c | -s] [-w] [ ...] + * zpool initialize [-c | -s | -u] [-w] [ ...] * Initialize all unused blocks in the specified vdevs, or all vdevs in the pool * if none specified. * * -c Cancel. Ends active initializing. * -s Suspend. Initializing can then be restarted with no flags. + * -u Uninitialize. Clears initialization state. * -w Wait. Blocks until initializing has completed. */ int @@ -569,12 +570,14 @@ zpool_do_initialize(int argc, char **argv) struct option long_options[] = { {"cancel", no_argument, NULL, 'c'}, {"suspend", no_argument, NULL, 's'}, + {"uninit", no_argument, NULL, 'u'}, {"wait", no_argument, NULL, 'w'}, {0, 0, 0, 0} }; pool_initialize_func_t cmd_type = POOL_INITIALIZE_START; - while ((c = getopt_long(argc, argv, "csw", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "csuw", long_options, + NULL)) != -1) { switch (c) { case 'c': if (cmd_type != POOL_INITIALIZE_START && @@ -594,6 +597,15 @@ zpool_do_initialize(int argc, char **argv) } cmd_type = POOL_INITIALIZE_SUSPEND; break; + case 'u': + if (cmd_type != POOL_INITIALIZE_START && + cmd_type != POOL_INITIALIZE_UNINIT) { + (void) fprintf(stderr, gettext("-u cannot be " + "combined with other options\n")); + usage(B_FALSE); + } + cmd_type = POOL_INITIALIZE_UNINIT; + break; case 'w': wait = B_TRUE; break; @@ -620,8 +632,8 @@ zpool_do_initialize(int argc, char **argv) } if (wait && (cmd_type != POOL_INITIALIZE_START)) { - (void) fprintf(stderr, gettext("-w cannot be used with -c or " - "-s\n")); + (void) fprintf(stderr, gettext("-w cannot be used with -c, -s" + "or -u\n")); usage(B_FALSE); } diff --git a/include/sys/fs/zfs.h b/include/sys/fs/zfs.h index 111e70ece151..84f5aee59d81 100644 --- a/include/sys/fs/zfs.h +++ b/include/sys/fs/zfs.h @@ -1173,6 +1173,7 @@ typedef enum pool_initialize_func { POOL_INITIALIZE_START, POOL_INITIALIZE_CANCEL, POOL_INITIALIZE_SUSPEND, + POOL_INITIALIZE_UNINIT, POOL_INITIALIZE_FUNCS } pool_initialize_func_t; diff --git a/include/sys/vdev_initialize.h b/include/sys/vdev_initialize.h index 81d39ebebcb2..942fc71c52ae 100644 --- a/include/sys/vdev_initialize.h +++ b/include/sys/vdev_initialize.h @@ -33,6 +33,7 @@ extern "C" { #endif extern void vdev_initialize(vdev_t *vd); +extern void vdev_uninitialize(vdev_t *vd); extern void vdev_initialize_stop(vdev_t *vd, vdev_initializing_state_t tgt_state, list_t *vd_list); extern void vdev_initialize_stop_all(vdev_t *vd, diff --git a/lib/libzfs/libzfs.abi b/lib/libzfs/libzfs.abi index 13ce19df9b34..58c2d7635f11 100644 --- a/lib/libzfs/libzfs.abi +++ b/lib/libzfs/libzfs.abi @@ -5410,7 +5410,8 @@ - + + diff --git a/lib/libzfs/libzfs_pool.c b/lib/libzfs/libzfs_pool.c index 29f077841da0..fc6c6e8e2cf6 100644 --- a/lib/libzfs/libzfs_pool.c +++ b/lib/libzfs/libzfs_pool.c @@ -2224,8 +2224,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, @@ -2251,11 +2251,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; diff --git a/lib/libzfs_core/libzfs_core.abi b/lib/libzfs_core/libzfs_core.abi index 1b03a5c42ef4..7ede3e0972d5 100644 --- a/lib/libzfs_core/libzfs_core.abi +++ b/lib/libzfs_core/libzfs_core.abi @@ -1726,7 +1726,8 @@ - + + diff --git a/man/man8/zpool-initialize.8 b/man/man8/zpool-initialize.8 index 0a108180dbbe..ada00bb1be90 100644 --- a/man/man8/zpool-initialize.8 +++ b/man/man8/zpool-initialize.8 @@ -36,7 +36,7 @@ .Sh SYNOPSIS .Nm zpool .Cm initialize -.Op Fl c Ns | Ns Fl s +.Op Fl c Ns | Ns Fl s | Ns Fl u .Op Fl w .Ar pool .Oo Ar device Oc Ns … @@ -60,6 +60,14 @@ initialized, the command will fail and no suspension will occur on any device. Initializing can then be resumed by running .Nm zpool Cm initialize with no flags on the relevant target devices. +.It Fl u , -uninit +Clears the initialization state on the specified devices, or all eligible +devices if none are specified. +If the devices are being actively initialized the command will fail. +After being cleared +.Nm zpool Cm initialize +with no flags can be used to re-initialize all unallocoated regions on +the relevant target devices. .It Fl w , -wait Wait until the devices have finished initializing before returning. .El diff --git a/module/zfs/spa.c b/module/zfs/spa.c index 790dd3efb992..b281dc854ba1 100644 --- a/module/zfs/spa.c +++ b/module/zfs/spa.c @@ -7304,6 +7304,10 @@ spa_vdev_initialize_impl(spa_t *spa, uint64_t guid, uint64_t cmd_type, vd->vdev_initialize_state != VDEV_INITIALIZE_ACTIVE) { mutex_exit(&vd->vdev_initialize_lock); return (SET_ERROR(ESRCH)); + } else if (cmd_type == POOL_INITIALIZE_UNINIT && + vd->vdev_initialize_thread != NULL) { + mutex_exit(&vd->vdev_initialize_lock); + return (SET_ERROR(EBUSY)); } switch (cmd_type) { @@ -7316,6 +7320,9 @@ spa_vdev_initialize_impl(spa_t *spa, uint64_t guid, uint64_t cmd_type, case POOL_INITIALIZE_SUSPEND: vdev_initialize_stop(vd, VDEV_INITIALIZE_SUSPENDED, vd_list); break; + case POOL_INITIALIZE_UNINIT: + vdev_uninitialize(vd); + break; default: panic("invalid cmd_type %llu", (unsigned long long)cmd_type); } diff --git a/module/zfs/vdev_initialize.c b/module/zfs/vdev_initialize.c index 6ffd0d618fdd..5d90fd67cc2f 100644 --- a/module/zfs/vdev_initialize.c +++ b/module/zfs/vdev_initialize.c @@ -100,6 +100,39 @@ vdev_initialize_zap_update_sync(void *arg, dmu_tx_t *tx) &initialize_state, tx)); } +static void +vdev_initialize_zap_remove_sync(void *arg, dmu_tx_t *tx) +{ + uint64_t guid = *(uint64_t *)arg; + + kmem_free(arg, sizeof (uint64_t)); + + vdev_t *vd = spa_lookup_by_guid(tx->tx_pool->dp_spa, guid, B_FALSE); + if (vd == NULL || vd->vdev_top->vdev_removing || !vdev_is_concrete(vd)) + return; + + ASSERT3S(vd->vdev_initialize_state, ==, VDEV_INITIALIZE_NONE); + ASSERT3U(vd->vdev_leaf_zap, !=, 0); + + vd->vdev_initialize_last_offset = 0; + vd->vdev_initialize_action_time = 0; + + objset_t *mos = vd->vdev_spa->spa_meta_objset; + int error; + + error = zap_remove(mos, vd->vdev_leaf_zap, + VDEV_LEAF_ZAP_INITIALIZE_LAST_OFFSET, tx); + VERIFY(error == 0 || error == ENOENT); + + error = zap_remove(mos, vd->vdev_leaf_zap, + VDEV_LEAF_ZAP_INITIALIZE_STATE, tx); + VERIFY(error == 0 || error == ENOENT); + + error = zap_remove(mos, vd->vdev_leaf_zap, + VDEV_LEAF_ZAP_INITIALIZE_ACTION_TIME, tx); + VERIFY(error == 0 || error == ENOENT); +} + static void vdev_initialize_change_state(vdev_t *vd, vdev_initializing_state_t new_state) { @@ -127,8 +160,14 @@ vdev_initialize_change_state(vdev_t *vd, vdev_initializing_state_t new_state) dmu_tx_t *tx = dmu_tx_create_dd(spa_get_dsl(spa)->dp_mos_dir); VERIFY0(dmu_tx_assign(tx, TXG_WAIT)); - dsl_sync_task_nowait(spa_get_dsl(spa), vdev_initialize_zap_update_sync, - guid, tx); + + if (new_state != VDEV_INITIALIZE_NONE) { + dsl_sync_task_nowait(spa_get_dsl(spa), + vdev_initialize_zap_update_sync, guid, tx); + } else { + dsl_sync_task_nowait(spa_get_dsl(spa), + vdev_initialize_zap_remove_sync, guid, tx); + } switch (new_state) { case VDEV_INITIALIZE_ACTIVE: @@ -149,6 +188,10 @@ vdev_initialize_change_state(vdev_t *vd, vdev_initializing_state_t new_state) spa_history_log_internal(spa, "initialize", tx, "vdev=%s complete", vd->vdev_path); break; + case VDEV_INITIALIZE_NONE: + spa_history_log_internal(spa, "uninitialize", tx, + "vdev=%s", vd->vdev_path); + break; default: panic("invalid state %llu", (unsigned long long)new_state); } @@ -604,6 +647,24 @@ vdev_initialize(vdev_t *vd) vdev_initialize_thread, vd, 0, &p0, TS_RUN, maxclsyspri); } +/* + * Uninitializes a device. Caller must hold vdev_initialize_lock. + * Device must be a leaf and not already be initializing. + */ +void +vdev_uninitialize(vdev_t *vd) +{ + ASSERT(MUTEX_HELD(&vd->vdev_initialize_lock)); + ASSERT(vd->vdev_ops->vdev_op_leaf); + ASSERT(vdev_is_concrete(vd)); + ASSERT3P(vd->vdev_initialize_thread, ==, NULL); + ASSERT(!vd->vdev_detached); + ASSERT(!vd->vdev_initialize_exit_wanted); + ASSERT(!vd->vdev_top->vdev_removing); + + vdev_initialize_change_state(vd, VDEV_INITIALIZE_NONE); +} + /* * Wait for the initialize thread to be terminated (cancelled or stopped). */ @@ -760,6 +821,7 @@ vdev_initialize_restart(vdev_t *vd) } EXPORT_SYMBOL(vdev_initialize); +EXPORT_SYMBOL(vdev_uninitialize); EXPORT_SYMBOL(vdev_initialize_stop); EXPORT_SYMBOL(vdev_initialize_stop_all); EXPORT_SYMBOL(vdev_initialize_stop_wait); diff --git a/module/zfs/zfs_ioctl.c b/module/zfs/zfs_ioctl.c index a4b391cbea12..f441328f3018 100644 --- a/module/zfs/zfs_ioctl.c +++ b/module/zfs/zfs_ioctl.c @@ -3985,7 +3985,8 @@ zfs_ioc_pool_initialize(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl) if (!(cmd_type == POOL_INITIALIZE_CANCEL || cmd_type == POOL_INITIALIZE_START || - cmd_type == POOL_INITIALIZE_SUSPEND)) { + cmd_type == POOL_INITIALIZE_SUSPEND || + cmd_type == POOL_INITIALIZE_UNINIT)) { return (SET_ERROR(EINVAL)); } diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index 502c06494012..6a7d976e7494 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -431,6 +431,7 @@ tests = ['zpool_initialize_attach_detach_add_remove', 'zpool_initialize_start_and_cancel_neg', 'zpool_initialize_start_and_cancel_pos', 'zpool_initialize_suspend_resume', + 'zpool_initialize_uninit', 'zpool_initialize_unsupported_vdevs', 'zpool_initialize_verify_checksums', 'zpool_initialize_verify_initialized'] diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_initialize/Makefile.am b/tests/zfs-tests/tests/functional/cli_root/zpool_initialize/Makefile.am index 3968902ec36d..483c1c2f5914 100644 --- a/tests/zfs-tests/tests/functional/cli_root/zpool_initialize/Makefile.am +++ b/tests/zfs-tests/tests/functional/cli_root/zpool_initialize/Makefile.am @@ -10,6 +10,7 @@ dist_pkgdata_SCRIPTS = \ zpool_initialize_start_and_cancel_neg.ksh \ zpool_initialize_start_and_cancel_pos.ksh \ zpool_initialize_suspend_resume.ksh \ + zpool_initialize_uninit.ksh \ zpool_initialize_unsupported_vdevs.ksh \ zpool_initialize_verify_checksums.ksh \ zpool_initialize_verify_initialized.ksh diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_initialize/zpool_initialize_uninit.ksh b/tests/zfs-tests/tests/functional/cli_root/zpool_initialize/zpool_initialize_uninit.ksh new file mode 100755 index 000000000000..17f776cfbc20 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zpool_initialize/zpool_initialize_uninit.ksh @@ -0,0 +1,141 @@ +#!/bin/ksh -p +# +# 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) 2016 by Delphix. All rights reserved. +# Copyright (C) 2023 Lawrence Livermore National Security, LLC. +# +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/cli_root/zpool_initialize/zpool_initialize.kshlib + +# +# DESCRIPTION: +# Starting, stopping, uninitializing, and restart an initialize works. +# +# STRATEGY: +# 1. Create a one-disk pool. +# 2. Verify uninitialize succeeds for uninitialized pool. +# 3. Verify pool wide cancel|suspend + uninit +# a. Start initializing and verify that initializing is active. +# b. Verify uninitialize fails when actively initializing. +# c. Cancel or suspend initializing and verify that initializing is not active. +# d. Verify uninitialize succeeds after being cancelled. +# 4. Verify per-disk cancel|suspend + uninit +# + +DISK1="$(echo $DISKS | cut -d' ' -f1)" +DISK2="$(echo $DISKS | cut -d' ' -f2)" +DISK3="$(echo $DISKS | cut -d' ' -f3)" + +function status_check # pool disk1-state disk2-state disk3-state +{ + typeset pool="$1" + typeset disk1_state="$2" + typeset disk2_state="$3" + typeset disk3_state="$4" + + state=$(zpool status -i "$pool" | grep "$DISK1" | grep "$disk1_state") + if [[ -z "$state" ]]; then + log_fail "DISK1 state; expected='$disk1_state' got '$state'" + fi + + state=$(zpool status -i "$pool" | grep "$DISK2" | grep "$disk2_state") + if [[ -z "$state" ]]; then + log_fail "DISK2 state; expected='$disk2_state' got '$state'" + fi + + state=$(zpool status -i "$pool" | grep "$DISK3" | grep "$disk3_state") + if [[ -z "$state" ]]; then + log_fail "DISK3 state; expected='$disk3_state' got '$state'" + fi +} + +function status_check_all # pool disk-state +{ + typeset pool="$1" + typeset disk_state="$2" + + status_check "$pool" "$disk_state" "$disk_state" "$disk_state" +} + +# 1. Create a one-disk pool. +log_must zpool create -f $TESTPOOL $DISK1 $DISK2 $DISK3 +status_check_all $TESTPOOL "uninitialized" + +# 2. Verify uninitialize succeeds for uninitialized pool. +log_must zpool initialize -u $TESTPOOL +status_check_all $TESTPOOL "uninitialized" + +# 3. Verify pool wide cancel + uninit +log_must zpool initialize $TESTPOOL +status_check_all $TESTPOOL "[[:digit:]]* initialized" + +log_mustnot zpool initialize -u $TESTPOOL +status_check_all $TESTPOOL "[[:digit:]]* initialized" + +log_must zpool initialize -c $TESTPOOL +status_check_all $TESTPOOL "uninitialized" + +log_must zpool initialize -u $TESTPOOL +status_check_all $TESTPOOL "uninitialized" + +# 3. Verify pool wide suspend + uninit +log_must zpool initialize $TESTPOOL +status_check_all $TESTPOOL "[[:digit:]]* initialized" + +log_mustnot zpool initialize -u $TESTPOOL +status_check_all $TESTPOOL "[[:digit:]]* initialized" + +log_must zpool initialize -s $TESTPOOL +status_check_all $TESTPOOL "suspended" + +log_must zpool initialize -u $TESTPOOL +status_check_all $TESTPOOL "uninitialized" + +# 4. Verify per-disk cancel|suspend + uninit +log_must zpool initialize $TESTPOOL +status_check_all $TESTPOOL "[[:digit:]]* initialized" + +log_must zpool initialize -c $TESTPOOL $DISK1 +log_must zpool initialize -s $TESTPOOL $DISK2 +log_mustnot zpool initialize -u $TESTPOOL $DISK3 +status_check $TESTPOOL "uninitialized" "suspended" "[[:digit:]]* initialized" + +log_must zpool initialize -u $TESTPOOL $DISK1 +status_check $TESTPOOL "uninitialized" "suspended" "[[:digit:]]* initialized" + +log_must zpool initialize -u $TESTPOOL $DISK2 +status_check $TESTPOOL "uninitialized" "uninitialized" "[[:digit:]]* initialized" + +log_must zpool initialize $TESTPOOL $DISK1 +status_check $TESTPOOL "[[:digit:]]* initialized" "uninitialized" "[[:digit:]]* initialized" + +log_must zpool initialize $TESTPOOL $DISK2 +status_check_all $TESTPOOL "[[:digit:]]* initialized" + +log_must zpool initialize -s $TESTPOOL +status_check_all $TESTPOOL "suspended" + +log_must zpool initialize -u $TESTPOOL $DISK1 $DISK2 $DISK3 +status_check_all $TESTPOOL "uninitialized" + +log_pass "Initialize start + cancel/suspend + uninit + start works"