From 6be8bf5552b16475629a15ab62759eb7a6d73e3b Mon Sep 17 00:00:00 2001 From: Mateusz Piotrowski <0mp@FreeBSD.org> Date: Mon, 26 Aug 2024 18:27:24 +0200 Subject: [PATCH] zpool: Provide GUID to zpool-reguid(8) with -g (#16239) This commit extends the zpool-reguid(8) command with a -g flag, which allows the user to specify the GUID to set. This change also adds some general tests for zpool-reguid(8). Sponsored-by: Wasabi Technology, Inc. Sponsored-by: Klara, Inc. Signed-off-by: Mateusz Piotrowski <0mp@FreeBSD.org> Reviewed-by: Rob Norris Reviewed-by: Brian Behlendorf Reviewed-by: Tony Hutter --- cmd/zpool/zpool_main.c | 23 ++++-- cmd/ztest.c | 2 +- include/libzfs.h | 1 + include/sys/fs/zfs.h | 5 ++ include/sys/spa.h | 2 +- lib/libzfs/libzfs.abi | 6 ++ lib/libzfs/libzfs_pool.c | 41 ++++++++++- man/man8/zpool-reguid.8 | 14 +++- module/zfs/spa.c | 25 ++++++- module/zfs/zfs_ioctl.c | 30 +++++++- tests/runfiles/common.run | 4 + .../cli_root/zpool_reguid/Makefile.am | 6 ++ .../cli_root/zpool_reguid/cleanup.ksh | 32 ++++++++ .../cli_root/zpool_reguid/setup.ksh | 34 +++++++++ .../zpool_reguid/zpool_reguid_001_pos.ksh | 73 +++++++++++++++++++ .../zpool_reguid/zpool_reguid_002_neg.ksh | 60 +++++++++++++++ 16 files changed, 342 insertions(+), 16 deletions(-) create mode 100644 tests/zfs-tests/tests/functional/cli_root/zpool_reguid/Makefile.am create mode 100755 tests/zfs-tests/tests/functional/cli_root/zpool_reguid/cleanup.ksh create mode 100755 tests/zfs-tests/tests/functional/cli_root/zpool_reguid/setup.ksh create mode 100755 tests/zfs-tests/tests/functional/cli_root/zpool_reguid/zpool_reguid_001_pos.ksh create mode 100755 tests/zfs-tests/tests/functional/cli_root/zpool_reguid/zpool_reguid_002_neg.ksh diff --git a/cmd/zpool/zpool_main.c b/cmd/zpool/zpool_main.c index 620746f8e7..9cd26a8650 100644 --- a/cmd/zpool/zpool_main.c +++ b/cmd/zpool/zpool_main.c @@ -537,7 +537,7 @@ get_usage(zpool_help_t idx) "\t [-o property=value] " "[ ...]\n")); case HELP_REGUID: - return (gettext("\treguid \n")); + return (gettext("\treguid [-g guid] \n")); case HELP_SYNC: return (gettext("\tsync [pool] ...\n")); case HELP_VERSION: @@ -2025,7 +2025,7 @@ zpool_do_create(int argc, char **argv) char *end; u_longlong_t ver; - ver = strtoull(propval, &end, 10); + ver = strtoull(propval, &end, 0); if (*end == '\0' && ver < SPA_VERSION_FEATURES) { enable_pool_features = B_FALSE; @@ -8232,19 +8232,32 @@ zpool_do_clear(int argc, char **argv) } /* - * zpool reguid + * zpool reguid [-g ] */ int zpool_do_reguid(int argc, char **argv) { + uint64_t guid; + uint64_t *guidp = NULL; int c; + char *endptr; char *poolname; zpool_handle_t *zhp; int ret = 0; /* check options */ - while ((c = getopt(argc, argv, "")) != -1) { + while ((c = getopt(argc, argv, "g:")) != -1) { switch (c) { + case 'g': + errno = 0; + guid = strtoull(optarg, &endptr, 10); + if (errno != 0 || *endptr != '\0') { + (void) fprintf(stderr, + gettext("invalid GUID: %s\n"), optarg); + usage(B_FALSE); + } + guidp = &guid; + break; case '?': (void) fprintf(stderr, gettext("invalid option '%c'\n"), optopt); @@ -8270,7 +8283,7 @@ zpool_do_reguid(int argc, char **argv) if ((zhp = zpool_open(g_zfs, poolname)) == NULL) return (1); - ret = zpool_reguid(zhp); + ret = zpool_set_guid(zhp, guidp); zpool_close(zhp); return (ret); diff --git a/cmd/ztest.c b/cmd/ztest.c index 6a9264ddcc..7c9db84d4e 100644 --- a/cmd/ztest.c +++ b/cmd/ztest.c @@ -6746,7 +6746,7 @@ ztest_reguid(ztest_ds_t *zd, uint64_t id) load = spa_load_guid(spa); (void) pthread_rwlock_wrlock(&ztest_name_lock); - error = spa_change_guid(spa); + error = spa_change_guid(spa, NULL); zs->zs_guid = spa_guid(spa); (void) pthread_rwlock_unlock(&ztest_name_lock); diff --git a/include/libzfs.h b/include/libzfs.h index bf5579f38f..2412797541 100644 --- a/include/libzfs.h +++ b/include/libzfs.h @@ -300,6 +300,7 @@ _LIBZFS_H int zpool_trim(zpool_handle_t *, pool_trim_func_t, nvlist_t *, _LIBZFS_H int zpool_clear(zpool_handle_t *, const char *, nvlist_t *); _LIBZFS_H int zpool_reguid(zpool_handle_t *); +_LIBZFS_H int zpool_set_guid(zpool_handle_t *, const uint64_t *); _LIBZFS_H int zpool_reopen_one(zpool_handle_t *, void *); _LIBZFS_H int zpool_sync_one(zpool_handle_t *, void *); diff --git a/include/sys/fs/zfs.h b/include/sys/fs/zfs.h index c7e48d1edc..73d686a002 100644 --- a/include/sys/fs/zfs.h +++ b/include/sys/fs/zfs.h @@ -1710,6 +1710,11 @@ typedef enum { #define ZPOOL_INITIALIZE_COMMAND "initialize_command" #define ZPOOL_INITIALIZE_VDEVS "initialize_vdevs" +/* + * The following are names used when invoking ZFS_IOC_POOL_REGUID. + */ +#define ZPOOL_REGUID_GUID "guid" + /* * The following are names used when invoking ZFS_IOC_POOL_TRIM. */ diff --git a/include/sys/spa.h b/include/sys/spa.h index a70912335b..93f381affd 100644 --- a/include/sys/spa.h +++ b/include/sys/spa.h @@ -1092,7 +1092,7 @@ extern void spa_strfree(char *); extern uint64_t spa_generate_guid(spa_t *spa); extern void snprintf_blkptr(char *buf, size_t buflen, const blkptr_t *bp); extern void spa_freeze(spa_t *spa); -extern int spa_change_guid(spa_t *spa); +extern int spa_change_guid(spa_t *spa, const uint64_t *guidp); extern void spa_upgrade(spa_t *spa, uint64_t version); extern void spa_evict_all(void); extern vdev_t *spa_lookup_by_guid(spa_t *spa, uint64_t guid, diff --git a/lib/libzfs/libzfs.abi b/lib/libzfs/libzfs.abi index 88baa4168c..87c5c4380b 100644 --- a/lib/libzfs/libzfs.abi +++ b/lib/libzfs/libzfs.abi @@ -556,6 +556,7 @@ + @@ -6639,6 +6640,11 @@ + + + + + diff --git a/lib/libzfs/libzfs_pool.c b/lib/libzfs/libzfs_pool.c index e493e8562a..dfa7c4db68 100644 --- a/lib/libzfs/libzfs_pool.c +++ b/lib/libzfs/libzfs_pool.c @@ -4310,22 +4310,55 @@ zpool_vdev_clear(zpool_handle_t *zhp, uint64_t guid) /* * Change the GUID for a pool. + * + * Similar to zpool_reguid(), but may take a GUID. + * + * If the guid argument is NULL, then no GUID is passed in the nvlist to the + * ioctl(). */ int -zpool_reguid(zpool_handle_t *zhp) +zpool_set_guid(zpool_handle_t *zhp, const uint64_t *guid) { char errbuf[ERRBUFLEN]; libzfs_handle_t *hdl = zhp->zpool_hdl; + nvlist_t *nvl = NULL; zfs_cmd_t zc = {"\0"}; + int error = -1; + + if (guid != NULL) { + if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0) + return (no_memory(hdl)); + + if (nvlist_add_uint64(nvl, ZPOOL_REGUID_GUID, *guid) != 0) { + nvlist_free(nvl); + return (no_memory(hdl)); + } + + zcmd_write_src_nvlist(hdl, &zc, nvl); + } (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); + error = zfs_ioctl(hdl, ZFS_IOC_POOL_REGUID, &zc); + if (error) { + return (zpool_standard_error(hdl, errno, errbuf)); + } + if (guid != NULL) { + zcmd_free_nvlists(&zc); + nvlist_free(nvl); + } + return (0); +} - return (zpool_standard_error(hdl, errno, errbuf)); +/* + * Change the GUID for a pool. + */ +int +zpool_reguid(zpool_handle_t *zhp) +{ + return (zpool_set_guid(zhp, NULL)); } /* diff --git a/man/man8/zpool-reguid.8 b/man/man8/zpool-reguid.8 index 1fd4ddd9a7..4fda3f316e 100644 --- a/man/man8/zpool-reguid.8 +++ b/man/man8/zpool-reguid.8 @@ -25,8 +25,10 @@ .\" Copyright (c) 2018 George Melikov. All Rights Reserved. .\" Copyright 2017 Nexenta Systems, Inc. .\" Copyright (c) 2017 Open-E, Inc. All Rights Reserved. +.\" Copyright (c) 2024, Klara Inc. +.\" Copyright (c) 2024, Mateusz Piotrowski .\" -.Dd May 31, 2021 +.Dd June 21, 2023 .Dt ZPOOL-REGUID 8 .Os . @@ -36,6 +38,7 @@ .Sh SYNOPSIS .Nm zpool .Cm reguid +.Op Fl g Ar guid .Ar pool . .Sh DESCRIPTION @@ -43,6 +46,15 @@ Generates a new unique identifier for the pool. You must ensure that all devices in this pool are online and healthy before performing this action. . +.Bl -tag -width Ds +.It Fl g Ar guid +Set the pool GUID to the provided value. +The GUID can be any 64-bit value accepted by +.Xr strtoull 3 +in base 10. +.Nm +will return an error if the provided GUID is already in use. +.El .Sh SEE ALSO .Xr zpool-export 8 , .Xr zpool-import 8 diff --git a/module/zfs/spa.c b/module/zfs/spa.c index 99a8d107ec..d51cc4fcd0 100644 --- a/module/zfs/spa.c +++ b/module/zfs/spa.c @@ -1040,16 +1040,34 @@ spa_change_guid_sync(void *arg, dmu_tx_t *tx) * online when we do this, or else any vdevs that weren't present * would be orphaned from our pool. We are also going to issue a * sysevent to update any watchers. + * + * The GUID of the pool will be changed to the value pointed to by guidp. + * The GUID may not be set to the reserverd value of 0. + * The new GUID will be generated if guidp is NULL. */ int -spa_change_guid(spa_t *spa) +spa_change_guid(spa_t *spa, const uint64_t *guidp) { - int error; uint64_t guid; + int error; mutex_enter(&spa->spa_vdev_top_lock); mutex_enter(&spa_namespace_lock); - guid = spa_generate_guid(NULL); + + if (guidp != NULL) { + guid = *guidp; + if (guid == 0) { + error = SET_ERROR(EINVAL); + goto out; + } + + if (spa_guid_exists(guid, 0)) { + error = SET_ERROR(EEXIST); + goto out; + } + } else { + guid = spa_generate_guid(NULL); + } error = dsl_sync_task(spa->spa_name, spa_change_guid_check, spa_change_guid_sync, &guid, 5, ZFS_SPACE_CHECK_RESERVED); @@ -1068,6 +1086,7 @@ spa_change_guid(spa_t *spa) spa_event_notify(spa, NULL, NULL, ESC_ZFS_POOL_REGUID); } +out: mutex_exit(&spa_namespace_lock); mutex_exit(&spa->spa_vdev_top_lock); diff --git a/module/zfs/zfs_ioctl.c b/module/zfs/zfs_ioctl.c index 897335dd4e..7ce2d91961 100644 --- a/module/zfs/zfs_ioctl.c +++ b/module/zfs/zfs_ioctl.c @@ -1794,17 +1794,45 @@ zfs_ioc_pool_get_history(zfs_cmd_t *zc) return (error); } +/* + * inputs: + * zc_nvlist_src nvlist optionally containing ZPOOL_REGUID_GUID + * zc_nvlist_src_size size of the nvlist + */ static int zfs_ioc_pool_reguid(zfs_cmd_t *zc) { + uint64_t *guidp = NULL; + nvlist_t *props = NULL; spa_t *spa; + uint64_t guid; int error; + if (zc->zc_nvlist_src_size != 0) { + error = get_nvlist(zc->zc_nvlist_src, zc->zc_nvlist_src_size, + zc->zc_iflags, &props); + if (error != 0) + return (error); + + error = nvlist_lookup_uint64(props, ZPOOL_REGUID_GUID, &guid); + if (error == 0) + guidp = &guid; + else if (error == ENOENT) + guidp = NULL; + else + goto out; + } + error = spa_open(zc->zc_name, &spa, FTAG); if (error == 0) { - error = spa_change_guid(spa); + error = spa_change_guid(spa, guidp); spa_close(spa, FTAG); } + +out: + if (props != NULL) + nvlist_free(props); + return (error); } diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index ad13166469..088e46ce57 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -514,6 +514,10 @@ tags = ['functional', 'cli_root', 'zpool_offline'] tests = ['zpool_online_001_pos', 'zpool_online_002_neg'] tags = ['functional', 'cli_root', 'zpool_online'] +[tests/functional/cli_root/zpool_reguid] +tests = ['zpool_reguid_001_pos', 'zpool_reguid_002_neg'] +tags = ['functional', 'cli_root', 'zpool_reguid'] + [tests/functional/cli_root/zpool_remove] tests = ['zpool_remove_001_neg', 'zpool_remove_002_pos', 'zpool_remove_003_pos'] diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_reguid/Makefile.am b/tests/zfs-tests/tests/functional/cli_root/zpool_reguid/Makefile.am new file mode 100644 index 0000000000..87d46b3940 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zpool_reguid/Makefile.am @@ -0,0 +1,6 @@ +pkgdatadir = $(datadir)/@PACKAGE@/zfs-tests/tests/functional/cli_root/zpool_reguid +dist_pkgdata_SCRIPTS = \ + setup.ksh \ + cleanup.ksh \ + zpool_reguid_001_pos.ksh \ + zpool_reguid_002_neg.ksh diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_reguid/cleanup.ksh b/tests/zfs-tests/tests/functional/cli_root/zpool_reguid/cleanup.ksh new file mode 100755 index 0000000000..3167a5097b --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zpool_reguid/cleanup.ksh @@ -0,0 +1,32 @@ +#!/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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +. $STF_SUITE/include/libtest.shlib + +verify_runnable "global" + +default_cleanup diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_reguid/setup.ksh b/tests/zfs-tests/tests/functional/cli_root/zpool_reguid/setup.ksh new file mode 100755 index 0000000000..3d866cfd9f --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zpool_reguid/setup.ksh @@ -0,0 +1,34 @@ +#!/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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +. $STF_SUITE/include/libtest.shlib + +verify_runnable "global" + +DISK=${DISKS%% *} + +default_setup $DISK diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_reguid/zpool_reguid_001_pos.ksh b/tests/zfs-tests/tests/functional/cli_root/zpool_reguid/zpool_reguid_001_pos.ksh new file mode 100755 index 0000000000..4e18abd988 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zpool_reguid/zpool_reguid_001_pos.ksh @@ -0,0 +1,73 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# +# CDDL HEADER END +# +# Copyright 2023 Mateusz Piotrowski +# + +. $STF_SUITE/include/libtest.shlib + +# +# DESCRIPTION: +# Verify 'zpool reguid' can change pool's GUID. +# +# STRATEGY: +# 1. Use zpool get to obtain the initial GUID of a pool. +# 2. Change pool's GUID with zpool reguid. +# 3. Verify the GUID has changed to a random GUID. +# +# 4. Change pool's GUID with zpool reguid -g. +# 5. Verify the GUID has changed to the specified GUID. +# + +# set_guid guid [expected_guid] +set_guid() { + gflag_guid="$1" + expected_guid="${2:-"$gflag_guid"}" + + initial_guid="$(zpool get -H -o value guid "$TESTPOOL")" + log_assert "Verify 'zpool reguid -g \"$gflag_guid\"' sets GUID as expected." + log_must zpool reguid -g "$gflag_guid" "$TESTPOOL" + retrieved_guid="$(zpool get -H -o value guid "$TESTPOOL")" + if [[ "$retrieved_guid" == "" ]]; then + log_fail "Unable to obtain the new GUID of pool $TESTPOOL" + fi + if [[ "$expected_guid" != "$retrieved_guid" ]]; then + log_fail "GUID set to '$retrieved_guid' instead of '$expected_guid'" + fi +} + +log_assert "Verify 'zpool reguid' picks a new random GUID for the pool." +initial_guid="$(zpool get -H -o value guid "$TESTPOOL")" +if [[ $initial_guid == "" ]]; then + log_fail "Unable to obtain the initial GUID of pool $TESTPOOL" +fi +log_must zpool reguid "$TESTPOOL" +new_guid="$(zpool get -H -o value guid "$TESTPOOL")" +if [[ "$new_guid" == "" ]]; then + log_fail "Unable to obtain the new GUID of pool $TESTPOOL" +fi +if [[ "$initial_guid" == "$new_guid" ]]; then + log_fail "GUID change failed; GUID has not changed: $initial_guid" +fi + +for g in "$(bc -e '2^64 - 1')" 0; do + set_guid "$g" +done +# zpool-reguid(8) will strip the leading 0. +set_guid 0123 "123" +# GUID "-1" is effectively 2^64 - 1 in value. +set_guid -1 "$(bc -e '2^64 - 1')" + +log_pass "'zpool reguid' changes GUID as expected." diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_reguid/zpool_reguid_002_neg.ksh b/tests/zfs-tests/tests/functional/cli_root/zpool_reguid/zpool_reguid_002_neg.ksh new file mode 100755 index 0000000000..599041e284 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zpool_reguid/zpool_reguid_002_neg.ksh @@ -0,0 +1,60 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# +# CDDL HEADER END +# +# Copyright 2023 Mateusz Piotrowski +# + +. $STF_SUITE/include/libtest.shlib + +# +# DESCRIPTION: +# Verify 'zpool reguid' does not accept invalid GUIDs. +# +# STRATEGY: +# 1. Call zpool reguid with an invalid GUID. +# 2. Verify that the call fails. +# 3. Verify that the pool GUID did not change. +# +# 4. Call zpool reguid with a GUID that is already in use. +# 5. Verify that the call fails. +# + +check_guid() { + invalid_guid="$1" + initial_guid="$(zpool get -H -o value guid "$TESTPOOL")" + log_assert "'zpool reguid' will not accept invalid GUID '$invalid_guid'" + if zpool reguid -g "$invalid_guid" "$TESTPOOL"; then + log_fail "'zpool reguid' accepted invalid GUID: $invalid_guid" + fi + final_guid="$(zpool get -H -o value guid "$TESTPOOL")" + if [[ "$initial_guid" != "$final_guid" ]]; then + log_fail "Invalid GUID change from '$initial_guid' to '$final_guid'" + fi +} + +log_assert "Verify 'zpool reguid' does not accept invalid GUIDs" + +for ig in "$(bc -e '2^64')" 0xA 0xa; do + check_guid "$ig" +done + +guid="42" +log_assert "Verify 'zpool reguid -g' does not accept GUID which are already in use" +log_must zpool reguid -g "$guid" "$TESTPOOL" +if zpool reguid -g "$guid" "$TESTPOOL"; then + log_fail "'zpool reguid' accepted GUID that was already in use: $invalid_guid" +fi + +log_pass "'zpool reguid' does not accept invalid GUIDs."