Skip to content

Commit 0ab4172

Browse files
committed
import: require force when cachefile hostid doesn't match on-disk
Previously, if a cachefile is passed to zpool import, the cached config is mostly offered as-is to ZFS_IOC_POOL_TRYIMPORT->spa_tryimport(), and the results are taken as the canonical pool config and handed back to ZFS_IOC_POOL_IMPORT. In the course of its operation, spa_load() will inspect the pool and build a new config from what it finds on disk. However, it then regenerates a new config ready to import, and so rightly sets the hostid and hostname for the local host in the config it returns. Because of this, the "require force" checks always decide the pool is exported and last touched by the local host, even if this is not true, which is possible in a HA environment when MMP is not enabled. The pool may be imported on another head, but the import checks still pass here, so the pool ends up imported on both. (This doesn't happen when a cachefile isn't used, because the pool config is discovered in userspace in zpool_find_import(), and that does find the on-disk hostid and hostname correctly). Since the systemd zfs-import-cache.service unit uses cachefile imports, this can lead to a system returning after a crash with a "valid" cachefile on disk and automatically, quietly, importing a pool that has already been taken up by a secondary head. This commit causes the on-disk hostid and hostname to be included in the ZPOOL_CONFIG_LOAD_INFO item in the returned config, and then changes the "force" checks for zpool import to use them if present. This method should give no change in behaviour for old userspace on new kernels (they won't know to look for the new config items) and for new userspace on old kernels (the won't find the new config items). Reviewed-by: Brian Behlendorf <[email protected]> Signed-off-by: Rob Norris <[email protected]> Sponsored-by: Klara, Inc. Sponsored-by: Wasabi Technology, Inc. Closes openzfs#15290 (cherry picked from commit 54b1b1d)
1 parent bef9815 commit 0ab4172

File tree

3 files changed

+49
-12
lines changed

3 files changed

+49
-12
lines changed

cmd/zpool/zpool_main.c

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3068,12 +3068,21 @@ zfs_force_import_required(nvlist_t *config)
30683068
nvlist_t *nvinfo;
30693069

30703070
state = fnvlist_lookup_uint64(config, ZPOOL_CONFIG_POOL_STATE);
3071-
(void) nvlist_lookup_uint64(config, ZPOOL_CONFIG_HOSTID, &hostid);
3071+
nvinfo = fnvlist_lookup_nvlist(config, ZPOOL_CONFIG_LOAD_INFO);
3072+
3073+
/*
3074+
* The hostid on LOAD_INFO comes from the MOS label via
3075+
* spa_tryimport(). If its not there then we're likely talking to an
3076+
* older kernel, so use the top one, which will be from the label
3077+
* discovered in zpool_find_import(), or if a cachefile is in use, the
3078+
* local hostid.
3079+
*/
3080+
if (nvlist_lookup_uint64(nvinfo, ZPOOL_CONFIG_HOSTID, &hostid) != 0)
3081+
nvlist_lookup_uint64(config, ZPOOL_CONFIG_HOSTID, &hostid);
30723082

30733083
if (state != POOL_STATE_EXPORTED && hostid != get_system_hostid())
30743084
return (B_TRUE);
30753085

3076-
nvinfo = fnvlist_lookup_nvlist(config, ZPOOL_CONFIG_LOAD_INFO);
30773086
if (nvlist_exists(nvinfo, ZPOOL_CONFIG_MMP_STATE)) {
30783087
mmp_state_t mmp_state = fnvlist_lookup_uint64(nvinfo,
30793088
ZPOOL_CONFIG_MMP_STATE);
@@ -3143,15 +3152,21 @@ do_import(nvlist_t *config, const char *newname, const char *mntopts,
31433152
uint64_t timestamp = 0;
31443153
uint64_t hostid = 0;
31453154

3146-
if (nvlist_exists(config, ZPOOL_CONFIG_HOSTNAME))
3155+
if (nvlist_exists(nvinfo, ZPOOL_CONFIG_HOSTNAME))
3156+
hostname = fnvlist_lookup_string(nvinfo,
3157+
ZPOOL_CONFIG_HOSTNAME);
3158+
else if (nvlist_exists(config, ZPOOL_CONFIG_HOSTNAME))
31473159
hostname = fnvlist_lookup_string(config,
31483160
ZPOOL_CONFIG_HOSTNAME);
31493161

31503162
if (nvlist_exists(config, ZPOOL_CONFIG_TIMESTAMP))
31513163
timestamp = fnvlist_lookup_uint64(config,
31523164
ZPOOL_CONFIG_TIMESTAMP);
31533165

3154-
if (nvlist_exists(config, ZPOOL_CONFIG_HOSTID))
3166+
if (nvlist_exists(nvinfo, ZPOOL_CONFIG_HOSTID))
3167+
hostid = fnvlist_lookup_uint64(nvinfo,
3168+
ZPOOL_CONFIG_HOSTID);
3169+
else if (nvlist_exists(config, ZPOOL_CONFIG_HOSTID))
31553170
hostid = fnvlist_lookup_uint64(config,
31563171
ZPOOL_CONFIG_HOSTID);
31573172

module/zfs/spa.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4200,6 +4200,24 @@ spa_ld_trusted_config(spa_t *spa, spa_import_type_t type,
42004200
rvd = mrvd;
42014201
spa_config_exit(spa, SCL_ALL, FTAG);
42024202

4203+
/*
4204+
* If 'zpool import' used a cached config, then the on-disk hostid and
4205+
* hostname may be different to the cached config in ways that should
4206+
* prevent import. Userspace can't discover this without a scan, but
4207+
* we know, so we add these values to LOAD_INFO so the caller can know
4208+
* the difference.
4209+
*
4210+
* Note that we have to do this before the config is regenerated,
4211+
* because the new config will have the hostid and hostname for this
4212+
* host, in readiness for import.
4213+
*/
4214+
if (nvlist_exists(mos_config, ZPOOL_CONFIG_HOSTID))
4215+
fnvlist_add_uint64(spa->spa_load_info, ZPOOL_CONFIG_HOSTID,
4216+
fnvlist_lookup_uint64(mos_config, ZPOOL_CONFIG_HOSTID));
4217+
if (nvlist_exists(mos_config, ZPOOL_CONFIG_HOSTNAME))
4218+
fnvlist_add_string(spa->spa_load_info, ZPOOL_CONFIG_HOSTNAME,
4219+
fnvlist_lookup_string(mos_config, ZPOOL_CONFIG_HOSTNAME));
4220+
42034221
/*
42044222
* We will use spa_config if we decide to reload the spa or if spa_load
42054223
* fails and we rewind. We must thus regenerate the config using the

tests/zfs-tests/tests/functional/cli_root/zpool_import/zpool_import_hostid_changed_cachefile_unclean_export.ksh

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020

2121
#
2222
# DESCRIPTION:
23-
# A pool that wasn't cleanly exported should be importable from a cachefile
24-
# without force even if the local hostid doesn't match the on-disk hostid.
23+
# A pool that wasn't cleanly exported should not be importable from a cachefile
24+
# without force if the local hostid doesn't match the on-disk hostid.
2525
#
2626
# STRATEGY:
2727
# 1. Set a hostid.
@@ -32,8 +32,9 @@
3232
# 4.2. Export the pool.
3333
# 4.3. Restore the device state from the copy.
3434
# 5. Change the hostid.
35-
# 6. Verify that importing the pool from the cachefile succeeds
36-
# without force.
35+
# 6. Verify that importing the pool from the cachefile fails.
36+
# 7. Verify that importing the pool from the cachefile with force
37+
# succeeds.
3738
#
3839

3940
verify_runnable "global"
@@ -64,8 +65,11 @@ log_must rm -f $VDEV0.bak
6465
# 5. Change the hostid.
6566
log_must zgenhostid -f $HOSTID2
6667

67-
# 6. Verify that importing the pool from the cachefile succeeds without force.
68-
log_must zpool import -c $CPATHBKP $TESTPOOL1
68+
# 6. Verify that importing the pool from the cachefile fails.
69+
log_mustnot zpool import -c $CPATHBKP $TESTPOOL1
6970

70-
log_pass "zpool import can import pool from cachefile if not cleanly " \
71-
"exported when hostid changes."
71+
# 7. Verify that importing the pool from the cachefile with force succeeds.
72+
log_must zpool import -f -c $CPATHBKP $TESTPOOL1
73+
74+
log_pass "zpool import from cachefile requires force if not cleanly " \
75+
"exported and hostid changes."

0 commit comments

Comments
 (0)