zfs and device name changes
Takashi YAMAMOTO <[email protected]> Tue, 24 Mar 2026 09:14:37 +0900
| Newsgroups | gmane.os.netbsd.devel.kernel |
|---|---|
| Message-ID | <CAKDaYtAV3yzdzBPAXt21dOVWAFQmcoYWKdxs=Tkkn2j1Gyvo5w@mail.gmail.com> |
hi, the attached patch is my attempt to make zfs a bit more robust against device name changes. the identical patch is available at github too: https://github.com/yamt/netbsd-src/commit/32283c2e362034301c3da218a05849c04ee20c2a while it seems working as far as i tested, i'd be happy if someone can review it as my knowledge of zfs (well, and recent netbsd in general) is weak.
a.diff
(application/octet-stream, 7.1 KB)
commit 32283c2e362034301c3da218a05849c04ee20c2a Author: YAMAMOTO Takashi <[email protected]> Date: Sun Mar 22 15:38:57 2026 +0000 zfs vdev_disk.c: be robust against device name changes currently, when device names are changed on a reboot, zfs fails to open vdevs, marking them UNAVAIL. while it can be repaired by performing zpool export/import, it isn't convenient in some situations. this commit is an attempt to make it a bit more robust. if open by path (ZPOOL_CONFIG_PATH) is failed, try to find the vdev by scanning all disks on the system, looking at the vdev guid (ZPOOL_CONFIG_GUID) in their vdev labels. eg. ``` [ 9.815914] ZFS: trying to find a vdev (/dev/dk2) by guid 62707ba3e8a27c0c [ 9.948753] ZFS: vdev 62707ba3e8a27c0c found: dk4 (bdev 168:4) ``` note: this doesn't update the vdev path in vdev label or /etc/zfs/zpool.cache. you may still want to perform zpool export/import to update the cached path to avoid possibly expensive scans. inspired by raidframe and FreeBSD vdev_geom.c. possible alternatives: - illumos seems to have other means to open disks. (ZPOOL_CONFIG_PHYS_PATH, devid) it might or might not be possible to implement them for NetBSD. - record NetBSD-specific disk IDs. (eg. dkwedge wname) while it might work, it requires on-disk changes to zfs. diff --git a/external/cddl/osnet/dist/uts/common/fs/zfs/vdev_disk.c b/external/cddl/osnet/dist/uts/common/fs/zfs/vdev_disk.c index 2fb42e54a819..5012dca10652 100644 --- a/external/cddl/osnet/dist/uts/common/fs/zfs/vdev_disk.c +++ b/external/cddl/osnet/dist/uts/common/fs/zfs/vdev_disk.c @@ -27,6 +27,9 @@ #include <sys/zfs_context.h> #include <sys/spa.h> +#ifdef __NetBSD__ +#include <sys/spa_impl.h> +#endif #include <sys/refcount.h> #include <sys/vdev_disk.h> #include <sys/vdev_impl.h> @@ -35,6 +38,9 @@ #include <sys/sunldi.h> #include <sys/fm/fs/zfs.h> #include <sys/disk.h> +#ifdef __NetBSD__ +#include <sys/disklabel.h> +#endif #include <sys/dkio.h> #include <sys/workqueue.h> @@ -137,6 +143,160 @@ vdev_disk_flush(struct work *work, void *cookie) vdev_disk_io_intr(bp); } +#ifdef __NetBSD__ + +/* ported from zpool_read_label in libzfs */ +static int +disk_read_config(vnode_t *vp, nvlist_t **config) +{ + vdev_label_t *label; + uint64_t state, txg; + int l; + uint64_t nsectors; + unsigned int sector_size; + uint64_t size; + int error; + + error = getdisksize(vp, &nsectors, §or_size); + if (error != 0) + return error; + + if (UINT64_MAX / nsectors < sector_size) + return EOVERFLOW; + + size = P2ALIGN_TYPED(nsectors * sector_size, sizeof (vdev_label_t), + uint64_t); + ASSERT3U(size / nsectors, ==, sector_size); + + if ((label = kmem_alloc(sizeof (vdev_label_t), KM_SLEEP)) == NULL) + return ENOMEM; + + for (l = 0; l < VDEV_LABELS; l++) { + error = vn_rdwr(UIO_READ, vp, (caddr_t)label, + sizeof (vdev_label_t), vdev_label_offset(size, l, 0), + UIO_SYSSPACE, 0, RLIM64_INFINITY, kcred, NULL); + if (error != 0) + continue; + + if (nvlist_unpack(label->vl_vdev_phys.vp_nvlist, + sizeof (label->vl_vdev_phys.vp_nvlist), config, 0) != 0) + continue; + + if (nvlist_lookup_uint64(*config, ZPOOL_CONFIG_POOL_STATE, + &state) != 0 || state > POOL_STATE_L2CACHE) { + nvlist_free(*config); + continue; + } + + if (state != POOL_STATE_SPARE && state != POOL_STATE_L2CACHE && + (nvlist_lookup_uint64(*config, ZPOOL_CONFIG_POOL_TXG, + &txg) != 0 || txg == 0)) { + nvlist_free(*config); + continue; + } + + kmem_free(label, sizeof(*label)); + return (0); + } + + kmem_free(label, sizeof(*label)); + *config = NULL; + return (0); +} + +static int +disk_read_vdev_guid(vnode_t *vp, uint64_t *resultp) +{ + nvlist_t *config; + int error; + + *resultp = 0; + + error = disk_read_config(vp, &config); + if (error != 0) + return error; + if (config == NULL) + return ENOENT; + + (void) nvlist_lookup_uint64(config, ZPOOL_CONFIG_GUID, resultp); + nvlist_free(config); + + return 0; +} + +static boolean_t +device_is_eligible_for_vdev(device_t device) +{ + if (device_class(device) != DV_DISK) + return B_FALSE; + + /* XXX better check? */ + if (device_is_a(device, "dk") || + device_is_a(device, "ld") || + device_is_a(device, "wd") || + device_is_a(device, "sd")) { + return B_TRUE; + } + + return B_FALSE; +} + +static int +open_disk_by_vdev_guid(uint64_t vdev_guid, vnode_t **vpp) +{ + device_t device; + deviter_t iter; + + for (device = deviter_first(&iter, DEVITER_F_ROOT_FIRST); + device != NULL; device = deviter_next(&iter)) { + vnode_t *vp; + uint64_t guid; + devmajor_t major; + devminor_t minor; + dev_t dev; + int error; + + if (!device_is_eligible_for_vdev(device)) + continue; + + major = devsw_name2blk(device_xname(device), NULL, 0); + minor = minor(device_unit(device)); + if (device_is_a(device, "dk")) + dev = makedev(major, minor); + else + dev = MAKEDISKDEV(major, minor, RAW_PART); + error = vn_bdev_open(dev, &vp, curlwp); + if (error != 0) + continue; + + if (disk_read_vdev_guid(vp, &guid) == 0 && + guid == vdev_guid) { + printf("ZFS: vdev %" PRIx64 " found: %s (bdev %" PRIu32 + ":%" PRIu32 ")\n", vdev_guid, + device_xname(device), (uint32_t)major, + (uint32_t)minor); + *vpp = vp; + return 0; + } + + vn_close(vp, FREAD|FWRITE, kcred); + } + + return ENOENT; +} + +static boolean_t +check_guid(vdev_t *vd) +{ + /* this condition is a copy-and-paste from vdev_geom.c */ + return !(vd->vdev_spa->spa_splitting_newspa || + (vd->vdev_prevstate == VDEV_STATE_UNKNOWN && + vd->vdev_spa->spa_load_state == SPA_LOAD_NONE || + vd->vdev_spa->spa_load_state == SPA_LOAD_CREATE)); +} + +#endif /* __NetBSD__ */ + static int vdev_disk_open(vdev_t *vd, uint64_t *psize, uint64_t *max_psize, uint64_t *ashift, uint64_t *pashift) @@ -208,6 +368,40 @@ vdev_disk_open(vdev_t *vd, uint64_t *psize, uint64_t *max_psize, error = vn_open(vd->vdev_path, UIO_SYSSPACE, FREAD|FWRITE, 0, &vp, CRCREAT, 0); +#ifdef __NetBSD__ + if (check_guid(vd)) { + if (error == 0) { + /* + * check guid in the vdev label on the disk + * to be robust with device name changes. + * eg. recabling + */ + uint64_t guid; + error = disk_read_vdev_guid(vp, &guid); + if (error != 0) { + vn_close(vp, FREAD|FWRITE, kcred); + printf("ZFS WARNING: failed to read guid " + "for %s\n", vd->vdev_path); + } else if (guid != vd->vdev_guid) { + vn_close(vp, FREAD|FWRITE, kcred); + printf("ZFS WARNING: vdev guid mismatch " + "for %s, actual %" PRIx64 + " expected %" PRIx64 "\n", + vd->vdev_path, guid, vd->vdev_guid); + error = ENOENT; + } + } + if (error != 0) { + /* + * try to find the vdev by scanning all disks on + * the system. this can be slow. + */ + printf("ZFS: trying to find a vdev (%s) by guid %" + PRIx64 "\n", vd->vdev_path, vd->vdev_guid); + error = open_disk_by_vdev_guid(vd->vdev_guid, &vp); + } + } +#endif if (error != 0) { vd->vdev_stat.vs_aux = VDEV_AUX_OPEN_FAILED; return (SET_ERROR(error));