diff --git a/cmd/zpool/zpool_main.c b/cmd/zpool/zpool_main.c index 01131be04fc8..8c55ea7a2a3c 100644 --- a/cmd/zpool/zpool_main.c +++ b/cmd/zpool/zpool_main.c @@ -525,8 +525,8 @@ get_usage(zpool_help_t idx) case HELP_REOPEN: return (gettext("\treopen [-n] \n")); case HELP_INITIALIZE: - return (gettext("\tinitialize [-c | -s | -u] [-w] <-a | " - "[ ...]>\n")); + return (gettext("\tinitialize [-c | -s | -u] [-w] [-z] " + "<-a | [ ...]>\n")); case HELP_SCRUB: return (gettext("\tscrub [-e | -s | -p | -t | -C [-t] | " "[-S date] [-E date] [-t]] [-w]\n" @@ -772,7 +772,7 @@ usage(boolean_t requested) } /* - * zpool initialize [-c | -s | -u] [-w] <-a | pool> [ ...] + * zpool initialize [-c | -s | -u] [-w] [-z] <-a | pool> [ ...] * Initialize all unused blocks in the specified vdevs, or all vdevs in the pool * if none specified. * @@ -791,6 +791,7 @@ zpool_do_initialize(int argc, char **argv) int err = 0; boolean_t wait = B_FALSE; boolean_t initialize_all = B_FALSE; + boolean_t zero = B_FALSE; struct option long_options[] = { {"cancel", no_argument, NULL, 'c'}, @@ -798,16 +799,20 @@ zpool_do_initialize(int argc, char **argv) {"uninit", no_argument, NULL, 'u'}, {"wait", no_argument, NULL, 'w'}, {"all", no_argument, NULL, 'a'}, + {"zero", no_argument, NULL, 'z'}, {0, 0, 0, 0} }; pool_initialize_func_t cmd_type = POOL_INITIALIZE_START; - while ((c = getopt_long(argc, argv, "acsuw", long_options, + while ((c = getopt_long(argc, argv, "acsuwz", long_options, NULL)) != -1) { switch (c) { case 'a': initialize_all = B_TRUE; break; + case 'z': + zero = B_TRUE; + break; case 'c': if (cmd_type != POOL_INITIALIZE_START && cmd_type != POOL_INITIALIZE_CANCEL) { @@ -856,7 +861,9 @@ zpool_do_initialize(int argc, char **argv) initialize_cbdata_t cbdata = { .wait = wait, - .cmd_type = cmd_type + .cmd_type = cmd_type, + .value = 0, + .value_provided = zero }; if (initialize_all && argc > 0) { @@ -876,6 +883,12 @@ zpool_do_initialize(int argc, char **argv) usage(B_FALSE); } + if (zero && (cmd_type != POOL_INITIALIZE_START)) { + (void) fprintf(stderr, gettext("-z cannot be used with -c, -s " + "or -u\n")); + usage(B_FALSE); + } + if (argc == 0 && initialize_all) { /* Initilize each pool recursively */ err = for_each_pool(argc, argv, B_TRUE, NULL, ZFS_TYPE_POOL, @@ -899,9 +912,11 @@ zpool_do_initialize(int argc, char **argv) fnvlist_add_boolean(vdevs, argv[i]); } if (wait) - err = zpool_initialize_wait(zhp, cmd_type, vdevs); + err = zpool_initialize_wait(zhp, cmd_type, vdevs, + cbdata.value, cbdata.value_provided); else - err = zpool_initialize(zhp, cmd_type, vdevs); + err = zpool_initialize(zhp, cmd_type, vdevs, + cbdata.value, cbdata.value_provided); fnvlist_free(vdevs); } diff --git a/cmd/ztest.c b/cmd/ztest.c index 57395a4e5d24..530ce1f65f06 100644 --- a/cmd/ztest.c +++ b/cmd/ztest.c @@ -7253,7 +7253,8 @@ ztest_initialize(ztest_ds_t *zd, uint64_t id) nvlist_t *vdev_guids = fnvlist_alloc(); nvlist_t *vdev_errlist = fnvlist_alloc(); fnvlist_add_uint64(vdev_guids, path, guid); - error = spa_vdev_initialize(spa, vdev_guids, cmd, vdev_errlist); + error = spa_vdev_initialize(spa, vdev_guids, cmd, 0, B_FALSE, + vdev_errlist); fnvlist_free(vdev_guids); fnvlist_free(vdev_errlist); diff --git a/include/libzfs.h b/include/libzfs.h index 718a58cadf1a..77506ecd7d72 100644 --- a/include/libzfs.h +++ b/include/libzfs.h @@ -299,6 +299,8 @@ typedef struct trim_cbdata { typedef struct initialize_cbdata { boolean_t wait; pool_initialize_func_t cmd_type; + uint64_t value; + boolean_t value_provided; } initialize_cbdata_t; /* * Functions to manipulate pool and vdev state @@ -308,9 +310,9 @@ _LIBZFS_H int zpool_scan_range(zpool_handle_t *, pool_scan_func_t, pool_scrub_cmd_t, pool_scrub_flags_t, time_t, time_t); _LIBZFS_H int zpool_initialize_one(zpool_handle_t *, void *); _LIBZFS_H int zpool_initialize(zpool_handle_t *, pool_initialize_func_t, - nvlist_t *); + nvlist_t *, uint64_t, boolean_t); _LIBZFS_H int zpool_initialize_wait(zpool_handle_t *, pool_initialize_func_t, - nvlist_t *); + nvlist_t *, uint64_t, boolean_t); _LIBZFS_H int zpool_trim(zpool_handle_t *, pool_trim_func_t, nvlist_t *, trimflags_t *); _LIBZFS_H int zpool_condense(zpool_handle_t *, const char *, const char *); diff --git a/include/libzfs_core.h b/include/libzfs_core.h index 7f850943ff83..b8fb629a7707 100644 --- a/include/libzfs_core.h +++ b/include/libzfs_core.h @@ -70,7 +70,7 @@ _LIBZFS_CORE_H int lzc_unload_key(const char *); _LIBZFS_CORE_H int lzc_change_key(const char *, uint64_t, nvlist_t *, uint8_t *, uint_t); _LIBZFS_CORE_H int lzc_initialize(const char *, pool_initialize_func_t, - nvlist_t *, nvlist_t **); + uint64_t, boolean_t, nvlist_t *, nvlist_t **); _LIBZFS_CORE_H int lzc_trim(const char *, pool_trim_func_t, uint64_t, boolean_t, nvlist_t *, nvlist_t **); _LIBZFS_CORE_H int lzc_redact(const char *, const char *, nvlist_t *); diff --git a/include/sys/fs/zfs.h b/include/sys/fs/zfs.h index 002f559fadd6..02d30985d1fe 100644 --- a/include/sys/fs/zfs.h +++ b/include/sys/fs/zfs.h @@ -1095,6 +1095,8 @@ typedef struct zpool_load_policy { "com.delphix:vdev_initialize_state" #define VDEV_LEAF_ZAP_INITIALIZE_ACTION_TIME \ "com.delphix:vdev_initialize_action_time" +#define VDEV_LEAF_ZAP_INITIALIZE_VALUE \ + "org.openzfs:vdev_initialize_value" /* vdev TRIM state */ #define VDEV_LEAF_ZAP_TRIM_LAST_OFFSET \ @@ -1928,6 +1930,7 @@ typedef enum { */ #define ZPOOL_INITIALIZE_COMMAND "initialize_command" #define ZPOOL_INITIALIZE_VDEVS "initialize_vdevs" +#define ZPOOL_INITIALIZE_VALUE "initialize_value" /* * The following are names used when invoking ZFS_IOC_POOL_REGUID. diff --git a/include/sys/spa.h b/include/sys/spa.h index 51a39754f09b..991885bd50e4 100644 --- a/include/sys/spa.h +++ b/include/sys/spa.h @@ -827,7 +827,7 @@ extern int spa_vdev_alloc(spa_t *spa, uint64_t guid); extern int spa_vdev_noalloc(spa_t *spa, uint64_t guid); extern boolean_t spa_vdev_remove_active(spa_t *spa); extern int spa_vdev_initialize(spa_t *spa, nvlist_t *nv, uint64_t cmd_type, - nvlist_t *vdev_errlist); + uint64_t value, boolean_t value_provided, nvlist_t *vdev_errlist); extern int spa_vdev_trim(spa_t *spa, nvlist_t *nv, uint64_t cmd_type, uint64_t rate, boolean_t partial, boolean_t secure, nvlist_t *vdev_errlist); extern int spa_vdev_setpath(spa_t *spa, uint64_t guid, const char *newpath); diff --git a/include/sys/vdev_impl.h b/include/sys/vdev_impl.h index 84e78f5dbc87..18847c7f2111 100644 --- a/include/sys/vdev_impl.h +++ b/include/sys/vdev_impl.h @@ -291,6 +291,8 @@ struct vdev { kcondvar_t vdev_initialize_cv; uint64_t vdev_initialize_offset[TXG_SIZE]; uint64_t vdev_initialize_last_offset; + /* value written to free space by the initializing thread */ + uint64_t vdev_initialize_value; /* valid while initializing */ zfs_range_tree_t *vdev_initialize_tree; uint64_t vdev_initialize_bytes_est; diff --git a/include/sys/vdev_initialize.h b/include/sys/vdev_initialize.h index f5e66b3a14a9..1021834344b4 100644 --- a/include/sys/vdev_initialize.h +++ b/include/sys/vdev_initialize.h @@ -33,7 +33,8 @@ extern "C" { #endif -extern void vdev_initialize(vdev_t *vd); +extern void vdev_initialize(vdev_t *vd, uint64_t value, + boolean_t value_provided); 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); diff --git a/lib/libzfs/libzfs.abi b/lib/libzfs/libzfs.abi index a627613c39ca..2b597435ecea 100644 --- a/lib/libzfs/libzfs.abi +++ b/lib/libzfs/libzfs.abi @@ -6767,6 +6767,8 @@ + + @@ -7237,12 +7239,16 @@ + + + + diff --git a/lib/libzfs/libzfs_pool.c b/lib/libzfs/libzfs_pool.c index 95aec70b905d..328b25736f39 100644 --- a/lib/libzfs/libzfs_pool.c +++ b/lib/libzfs/libzfs_pool.c @@ -2604,9 +2604,11 @@ zpool_initialize_one(zpool_handle_t *zhp, void *data) ZPOOL_CONFIG_VDEV_TREE); zpool_collect_leaves(zhp, nvroot, vdevs); if (cb->wait) - error = zpool_initialize_wait(zhp, cb->cmd_type, vdevs); + error = zpool_initialize_wait(zhp, cb->cmd_type, vdevs, + cb->value, cb->value_provided); else - error = zpool_initialize(zhp, cb->cmd_type, vdevs); + error = zpool_initialize(zhp, cb->cmd_type, vdevs, + cb->value, cb->value_provided); fnvlist_free(vdevs); return (error); @@ -2618,7 +2620,7 @@ zpool_initialize_one(zpool_handle_t *zhp, void *data) */ static int zpool_initialize_impl(zpool_handle_t *zhp, pool_initialize_func_t cmd_type, - nvlist_t *vds, boolean_t wait) + nvlist_t *vds, uint64_t value, boolean_t value_provided, boolean_t wait) { int err; @@ -2637,7 +2639,7 @@ zpool_initialize_impl(zpool_handle_t *zhp, pool_initialize_func_t cmd_type, } err = lzc_initialize(zhp->zpool_name, cmd_type, - vdev_guids, &errlist); + value, value_provided, vdev_guids, &errlist); if (err != 0) { if (errlist != NULL && nvlist_lookup_nvlist(errlist, @@ -2705,9 +2707,10 @@ out: */ int zpool_initialize(zpool_handle_t *zhp, pool_initialize_func_t cmd_type, - nvlist_t *vds) + nvlist_t *vds, uint64_t value, boolean_t value_provided) { - return (zpool_initialize_impl(zhp, cmd_type, vds, B_FALSE)); + return (zpool_initialize_impl(zhp, cmd_type, vds, value, value_provided, + B_FALSE)); } /* @@ -2715,9 +2718,10 @@ zpool_initialize(zpool_handle_t *zhp, pool_initialize_func_t cmd_type, */ int zpool_initialize_wait(zpool_handle_t *zhp, pool_initialize_func_t cmd_type, - nvlist_t *vds) + nvlist_t *vds, uint64_t value, boolean_t value_provided) { - return (zpool_initialize_impl(zhp, cmd_type, vds, B_TRUE)); + return (zpool_initialize_impl(zhp, cmd_type, vds, value, value_provided, + B_TRUE)); } static int diff --git a/lib/libzfs_core/libzfs_core.abi b/lib/libzfs_core/libzfs_core.abi index 5007942ff418..e88de56ae404 100644 --- a/lib/libzfs_core/libzfs_core.abi +++ b/lib/libzfs_core/libzfs_core.abi @@ -4282,6 +4282,8 @@ + + diff --git a/lib/libzfs_core/libzfs_core.c b/lib/libzfs_core/libzfs_core.c index 5cce5d8702b7..f1dbd3aec118 100644 --- a/lib/libzfs_core/libzfs_core.c +++ b/lib/libzfs_core/libzfs_core.c @@ -1906,13 +1906,16 @@ lzc_reopen(const char *pool_name, boolean_t scrub_restart) */ int lzc_initialize(const char *poolname, pool_initialize_func_t cmd_type, - nvlist_t *vdevs, nvlist_t **errlist) + uint64_t value, boolean_t value_provided, nvlist_t *vdevs, + nvlist_t **errlist) { int error; nvlist_t *args = fnvlist_alloc(); fnvlist_add_uint64(args, ZPOOL_INITIALIZE_COMMAND, (uint64_t)cmd_type); fnvlist_add_nvlist(args, ZPOOL_INITIALIZE_VDEVS, vdevs); + if (value_provided) + fnvlist_add_uint64(args, ZPOOL_INITIALIZE_VALUE, value); error = lzc_ioctl(ZFS_IOC_POOL_INITIALIZE, poolname, args, errlist); diff --git a/man/man4/zfs.4 b/man/man4/zfs.4 index 2877b5359f23..f55140182b8f 100644 --- a/man/man4/zfs.4 +++ b/man/man4/zfs.4 @@ -20,7 +20,7 @@ .\" own identifying information: .\" Portions Copyright [yyyy] [name of copyright owner] .\" -.Dd May 26, 2026 +.Dd July 12, 2026 .Dt ZFS 4 .Os . @@ -1918,8 +1918,13 @@ The default value of 5 transaction groups typically provides sufficient time for import and mount operations to complete on most systems. . .It Sy zfs_initialize_value Ns = Ns Sy 16045690984833335022 Po 0xDEADBEEFDEADBEEE Pc Pq u64 -Pattern written to vdev free space by -.Xr zpool-initialize 8 . +Default pattern written to vdev free space by +.Xr zpool-initialize 8 , +used unless +.Nm zpool Cm initialize Fl z +requests zeroes instead. +The value in effect when a device starts initializing is recorded per device +and reused if the run is suspended and later resumed. . .It Sy zfs_initialize_chunk_size Ns = Ns Sy 1048576 Ns B Po 1 MiB Pc Pq u64 Size of writes used by diff --git a/man/man8/zpool-initialize.8 b/man/man8/zpool-initialize.8 index 5299a897cb97..9fcfe2396649 100644 --- a/man/man8/zpool-initialize.8 +++ b/man/man8/zpool-initialize.8 @@ -28,7 +28,7 @@ .\" Copyright (c) 2017 Open-E, Inc. All Rights Reserved. .\" Copyright (c) 2025 Hewlett Packard Enterprise Development LP. .\" -.Dd July 30, 2025 +.Dd July 12, 2026 .Dt ZPOOL-INITIALIZE 8 .Os . @@ -40,6 +40,7 @@ .Cm initialize .Op Fl c Ns | Ns Fl s | Ns Fl u .Op Fl w +.Op Fl z .Fl a Ns | Ns Ar pool .Oo Ar device Oc Ns … . @@ -48,6 +49,9 @@ Begins initializing by writing to all unallocated regions on the specified devices, or all eligible devices in the pool if no individual devices are specified. Only leaf data or log devices may be initialized. +By default a non-zero pattern is written; see +.Fl z +to write zeroes instead. .Bl -tag -width Ds .It Fl a , -all Begin, cancel, suspend initializing on @@ -76,6 +80,16 @@ with no flags can be used to re-initialize all unallocated regions on the relevant target devices. .It Fl w , -wait Wait until the devices have finished initializing before returning. +.It Fl z , -zero +Write zeroes to the unallocated regions instead of the default non-zero +pattern. +This is useful for reclaiming space on thinly provisioned backing storage which +does not support TRIM. +If the backing storage supports TRIM, +.Xr zpool-trim 8 +is typically the more efficient way to return space to it. +The chosen value is recorded per device, so a run that is suspended and later +resumed keeps writing zeroes. .El . .Sh SEE ALSO diff --git a/module/zfs/spa.c b/module/zfs/spa.c index bdaa1c1a1b07..935b95634e84 100644 --- a/module/zfs/spa.c +++ b/module/zfs/spa.c @@ -8898,7 +8898,7 @@ spa_vdev_detach(spa_t *spa, uint64_t guid, uint64_t pguid, int replace_done) static int spa_vdev_initialize_impl(spa_t *spa, uint64_t guid, uint64_t cmd_type, - list_t *vd_list) + uint64_t value, boolean_t value_provided, list_t *vd_list) { ASSERT(spa_namespace_held()); @@ -8948,7 +8948,7 @@ spa_vdev_initialize_impl(spa_t *spa, uint64_t guid, uint64_t cmd_type, switch (cmd_type) { case POOL_INITIALIZE_START: - vdev_initialize(vd); + vdev_initialize(vd, value, value_provided); break; case POOL_INITIALIZE_CANCEL: vdev_initialize_stop(vd, VDEV_INITIALIZE_CANCELED, vd_list); @@ -8969,7 +8969,7 @@ spa_vdev_initialize_impl(spa_t *spa, uint64_t guid, uint64_t cmd_type, int spa_vdev_initialize(spa_t *spa, nvlist_t *nv, uint64_t cmd_type, - nvlist_t *vdev_errlist) + uint64_t value, boolean_t value_provided, nvlist_t *vdev_errlist) { int total_errors = 0; list_t vd_list; @@ -8991,7 +8991,7 @@ spa_vdev_initialize(spa_t *spa, nvlist_t *nv, uint64_t cmd_type, uint64_t vdev_guid = fnvpair_value_uint64(pair); int error = spa_vdev_initialize_impl(spa, vdev_guid, cmd_type, - &vd_list); + value, value_provided, &vd_list); if (error != 0) { char guid_as_str[MAXNAMELEN]; diff --git a/module/zfs/vdev.c b/module/zfs/vdev.c index 0be1ef457367..ba2266bb55e2 100644 --- a/module/zfs/vdev.c +++ b/module/zfs/vdev.c @@ -4579,7 +4579,8 @@ vdev_online(spa_t *spa, uint64_t guid, uint64_t flags, vdev_state_t *newstate) if (vdev_writeable(vd) && vd->vdev_initialize_thread == NULL && vd->vdev_initialize_state == VDEV_INITIALIZE_ACTIVE) { - (void) vdev_initialize(vd); + /* Preserve the fill value chosen when the run started. */ + vdev_initialize(vd, vd->vdev_initialize_value, B_TRUE); } mutex_exit(&vd->vdev_initialize_lock); diff --git a/module/zfs/vdev_initialize.c b/module/zfs/vdev_initialize.c index d13da1e5a663..b7709317df5e 100644 --- a/module/zfs/vdev_initialize.c +++ b/module/zfs/vdev_initialize.c @@ -97,6 +97,11 @@ vdev_initialize_zap_update_sync(void *arg, dmu_tx_t *tx) VERIFY0(zap_update(mos, vd->vdev_leaf_zap, VDEV_LEAF_ZAP_INITIALIZE_STATE, sizeof (initialize_state), 1, &initialize_state, tx)); + + uint64_t initialize_value = vd->vdev_initialize_value; + VERIFY0(zap_update(mos, vd->vdev_leaf_zap, + VDEV_LEAF_ZAP_INITIALIZE_VALUE, sizeof (initialize_value), 1, + &initialize_value, tx)); } static void @@ -130,6 +135,10 @@ vdev_initialize_zap_remove_sync(void *arg, dmu_tx_t *tx) error = zap_remove(mos, vd->vdev_leaf_zap, VDEV_LEAF_ZAP_INITIALIZE_ACTION_TIME, tx); VERIFY(error == 0 || error == ENOENT); + + error = zap_remove(mos, vd->vdev_leaf_zap, + VDEV_LEAF_ZAP_INITIALIZE_VALUE, tx); + VERIFY(error == 0 || error == ENOENT); } static void @@ -293,31 +302,31 @@ vdev_initialize_write(vdev_t *vd, uint64_t start, uint64_t size, abd_t *data) } /* - * Callback to fill each ABD chunk with zfs_initialize_value. len must be + * Callback to fill each ABD chunk with the requested fill value. len must be * divisible by sizeof (uint64_t), and buf must be 8-byte aligned. The ABD * allocation will guarantee these for us. */ static int -vdev_initialize_block_fill(void *buf, size_t len, void *unused) +vdev_initialize_block_fill(void *buf, size_t len, void *arg) { - (void) unused; + uint64_t value = *(uint64_t *)arg; ASSERT0(len % sizeof (uint64_t)); for (uint64_t i = 0; i < len; i += sizeof (uint64_t)) { - *(uint64_t *)((char *)(buf) + i) = zfs_initialize_value; + *(uint64_t *)((char *)(buf) + i) = value; } return (0); } static abd_t * -vdev_initialize_block_alloc(void) +vdev_initialize_block_alloc(uint64_t value) { /* Allocate ABD for filler data */ abd_t *data = abd_alloc_for_io(zfs_initialize_chunk_size, B_FALSE); ASSERT0(zfs_initialize_chunk_size % sizeof (uint64_t)); (void) abd_iterate_func(data, 0, zfs_initialize_chunk_size, - vdev_initialize_block_fill, NULL); + vdev_initialize_block_fill, &value); return (data); } @@ -539,7 +548,8 @@ vdev_initialize_thread(void *arg) vd->vdev_initialize_last_offset = 0; VERIFY0(vdev_initialize_load(vd)); - abd_t *deadbeef = vdev_initialize_block_alloc(); + abd_t *deadbeef = + vdev_initialize_block_alloc(vd->vdev_initialize_value); vd->vdev_initialize_tree = zfs_range_tree_create_flags( NULL, ZFS_RANGE_SEG64, NULL, 0, 0, @@ -627,7 +637,7 @@ vdev_initialize_thread(void *arg) * Device must be a leaf and not already be initializing. */ void -vdev_initialize(vdev_t *vd) +vdev_initialize(vdev_t *vd, uint64_t value, boolean_t value_provided) { ASSERT(MUTEX_HELD(&vd->vdev_initialize_lock)); ASSERT(vd->vdev_ops->vdev_op_leaf); @@ -638,6 +648,20 @@ vdev_initialize(vdev_t *vd) ASSERT(!vd->vdev_top->vdev_removing); ASSERT(!vd->vdev_top->vdev_rz_expanding); + /* + * Fix the fill value for the whole run so it is stable across a + * suspend/resume (it is persisted to the leaf ZAP). An explicit value + * always wins. Otherwise, when resuming an existing run keep the value + * chosen when it started (already loaded from the leaf ZAP); only a + * fresh run falls back to the module default. + */ + if (value_provided) { + vd->vdev_initialize_value = value; + } else if (vd->vdev_initialize_state != VDEV_INITIALIZE_ACTIVE && + vd->vdev_initialize_state != VDEV_INITIALIZE_SUSPENDED) { + vd->vdev_initialize_value = zfs_initialize_value; + } + vdev_initialize_change_state(vd, VDEV_INITIALIZE_ACTIVE); vd->vdev_initialize_thread = thread_create(NULL, 0, vdev_initialize_thread, vd, 0, &p0, TS_RUN, maxclsyspri); @@ -801,6 +825,18 @@ vdev_initialize_restart(vdev_t *vd) ASSERT(err == 0 || err == ENOENT); vd->vdev_initialize_action_time = timestamp; + /* + * Restore the fill value chosen when this run started. Pools + * initialized before this field existed fall back to the + * module default, preserving their previous behavior. + */ + uint64_t value = zfs_initialize_value; + err = zap_lookup(vd->vdev_spa->spa_meta_objset, + vd->vdev_leaf_zap, VDEV_LEAF_ZAP_INITIALIZE_VALUE, + sizeof (value), 1, &value); + ASSERT(err == 0 || err == ENOENT); + vd->vdev_initialize_value = value; + if ((vd->vdev_initialize_state == VDEV_INITIALIZE_SUSPENDED || vd->vdev_offline) && !vd->vdev_top->vdev_rz_expanding) { /* load progress for reporting, but don't resume */ @@ -810,7 +846,7 @@ vdev_initialize_restart(vdev_t *vd) !vd->vdev_top->vdev_removing && !vd->vdev_top->vdev_rz_expanding && vd->vdev_initialize_thread == NULL) { - vdev_initialize(vd); + vdev_initialize(vd, value, B_TRUE); } mutex_exit(&vd->vdev_initialize_lock); diff --git a/module/zfs/zfs_ioctl.c b/module/zfs/zfs_ioctl.c index fb70821f3308..2c68aa022a4c 100644 --- a/module/zfs/zfs_ioctl.c +++ b/module/zfs/zfs_ioctl.c @@ -4798,7 +4798,8 @@ zfs_ioc_destroy(zfs_cmd_t *zc) */ static const zfs_ioc_key_t zfs_keys_pool_initialize[] = { {ZPOOL_INITIALIZE_COMMAND, DATA_TYPE_UINT64, 0}, - {ZPOOL_INITIALIZE_VDEVS, DATA_TYPE_NVLIST, 0} + {ZPOOL_INITIALIZE_VDEVS, DATA_TYPE_NVLIST, 0}, + {ZPOOL_INITIALIZE_VALUE, DATA_TYPE_UINT64, ZK_OPTIONAL} }; static int @@ -4831,6 +4832,14 @@ zfs_ioc_pool_initialize(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl) } } + /* + * An explicit fill value is optional; when absent the initializing + * thread uses the zfs_initialize_value module default. + */ + uint64_t value = 0; + boolean_t value_provided = (nvlist_lookup_uint64(innvl, + ZPOOL_INITIALIZE_VALUE, &value) == 0); + spa_t *spa; int error = spa_open(poolname, &spa, FTAG); if (error != 0) @@ -4838,7 +4847,7 @@ zfs_ioc_pool_initialize(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl) nvlist_t *vdev_errlist = fnvlist_alloc(); int total_errors = spa_vdev_initialize(spa, vdev_guids, cmd_type, - vdev_errlist); + value, value_provided, vdev_errlist); if (fnvlist_size(vdev_errlist) > 0) { fnvlist_add_nvlist(outnvl, ZPOOL_INITIALIZE_VDEVS, diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index 21af672f93ea..3b06e5f1af5f 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -530,7 +530,8 @@ tests = ['zpool_initialize_attach_detach_add_remove', 'zpool_initialize_uninit', 'zpool_initialize_unsupported_vdevs', 'zpool_initialize_verify_checksums', - 'zpool_initialize_verify_initialized'] + 'zpool_initialize_verify_initialized', + 'zpool_initialize_zero'] pre = tags = ['functional', 'cli_root', 'zpool_initialize'] diff --git a/tests/zfs-tests/cmd/libzfs_input_check.c b/tests/zfs-tests/cmd/libzfs_input_check.c index 604478875611..3c9d369b1e54 100644 --- a/tests/zfs-tests/cmd/libzfs_input_check.c +++ b/tests/zfs-tests/cmd/libzfs_input_check.c @@ -812,15 +812,19 @@ static void test_vdev_initialize(const char *pool) { nvlist_t *required = fnvlist_alloc(); + nvlist_t *optional = fnvlist_alloc(); nvlist_t *vdev_guids = fnvlist_alloc(); fnvlist_add_uint64(vdev_guids, "path", 0xdeadbeefdeadbeef); fnvlist_add_uint64(required, ZPOOL_INITIALIZE_COMMAND, POOL_INITIALIZE_START); fnvlist_add_nvlist(required, ZPOOL_INITIALIZE_VDEVS, vdev_guids); + fnvlist_add_uint64(optional, ZPOOL_INITIALIZE_VALUE, 0); - IOC_INPUT_TEST(ZFS_IOC_POOL_INITIALIZE, pool, required, NULL, EINVAL); + IOC_INPUT_TEST(ZFS_IOC_POOL_INITIALIZE, pool, required, optional, + EINVAL); nvlist_free(vdev_guids); + nvlist_free(optional); nvlist_free(required); } diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am index 67e0bdbc3205..a3783402a577 100644 --- a/tests/zfs-tests/tests/Makefile.am +++ b/tests/zfs-tests/tests/Makefile.am @@ -1278,6 +1278,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \ functional/cli_root/zpool_initialize/zpool_initialize_unsupported_vdevs.ksh \ functional/cli_root/zpool_initialize/zpool_initialize_verify_checksums.ksh \ functional/cli_root/zpool_initialize/zpool_initialize_verify_initialized.ksh \ + functional/cli_root/zpool_initialize/zpool_initialize_zero.ksh \ functional/cli_root/zpool_labelclear/zpool_labelclear_active.ksh \ functional/cli_root/zpool_labelclear/zpool_labelclear_exported.ksh \ functional/cli_root/zpool_labelclear/zpool_labelclear_removed.ksh \ diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_initialize/zpool_initialize_zero.ksh b/tests/zfs-tests/tests/functional/cli_root/zpool_initialize/zpool_initialize_zero.ksh new file mode 100755 index 000000000000..f19e315bbaf5 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zpool_initialize/zpool_initialize_zero.ksh @@ -0,0 +1,94 @@ +#!/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 +# + +. $STF_SUITE/include/libtest.shlib + +# +# DESCRIPTION: +# "zpool initialize -z" writes zeroes instead of the default pattern, and the +# chosen value is persisted per device. +# +# STRATEGY: +# 1. Pre-fill the backing file with a non-zero byte pattern, so that finding +# zeroes afterwards actually proves -z did the work (rather than the check +# passing on space that happened to already be zero). +# 2. Create a one-disk pool and run "zpool initialize -w -z". +# 3. Verify the zero fill value is persisted in the leaf ZAP. +# 4. Verify the pre-existing non-zero pattern is gone from every metaslab. +# + +function cleanup +{ + if datasetexists $TESTPOOL; then + zpool destroy -f $TESTPOOL + fi + if [[ -d "$TESTDIR" ]]; then + rm -rf "$TESTDIR" + fi +} +log_onexit cleanup + +log_assert "'zpool initialize -z' writes zeroes and persists the choice" + +SMALLFILE="$TESTDIR/smallfile" + +log_must mkdir "$TESTDIR" +# Fill the whole device with 0xff so free space starts out non-zero. +log_must eval "tr '\0' '\377' < /dev/zero | head -c $MINVDEVSIZE > $SMALLFILE" + +log_must zpool create $TESTPOOL "$SMALLFILE" +log_must zpool initialize -w -z $TESTPOOL + +# The zero fill value must be recorded in the leaf ZAP. +log_must eval "zdb -dddd $TESTPOOL | \ + grep -qE 'org.openzfs:vdev_initialize_value = 0( |\$)'" + +log_must zpool export $TESTPOOL + +# The device started out entirely 0xff. After zeroing, its free space (the +# large majority of a freshly created pool) must actually contain zeroes: the +# 0xff pre-fill must be gone AND zero words must dominate. Requiring zeroes to +# dominate (rather than only "0xff is gone") also catches a regression that +# wrote some other non-zero value. Use "-t x4" (not x8, unsupported on +# FreeBSD) and count both patterns in a single pass. +set -A counts $(od -An -tx4 -v "$SMALLFILE" | awk ' + { for (i = 1; i <= NF; i++) { + n++ + if ($i == "ffffffff") f++ + else if ($i == "00000000") z++ + } } + END { print f + 0, z + 0, n + 0 }') +ffwords=${counts[0]} +zwords=${counts[1]} +nwords=${counts[2]} +log_note "after -z: 0xff words=$ffwords zero words=$zwords of $nwords" + +if [[ $nwords -eq 0 ]]; then + log_fail "Could not read back the device" +elif [[ $ffwords -ge $((nwords / 2)) ]]; then + log_fail "0xff pre-fill still present after 'initialize -z'" +elif [[ $zwords -lt $((nwords / 2)) ]]; then + log_fail "free space is not predominantly zero after 'initialize -z'" +else + log_pass "'zpool initialize -z' zeroed the pool's free space" +fi