From 5534b70d8b522e756076f49ea2c5f7e0a7c781aa Mon Sep 17 00:00:00 2001 From: Etienne Dechamps Date: Fri, 6 Jul 2012 15:44:14 +0200 Subject: [PATCH 1/5] Fix error handling for "zpool online -e". The error handling code around zpool_relabel_disk() is either inexistent or wrong. The function call itself is not checked, and zpool_relabel_disk() is generating error messages from an unitialized buffer. Before: # zpool online -e homez sdb; echo $? `: cannot relabel 'sdb1': unable to open device: 2 0 After: # zpool online -e homez sdb; echo $? cannot expand sdb: cannot relabel 'sdb1': unable to open device: 2 1 --- lib/libzfs/libzfs_pool.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/libzfs/libzfs_pool.c b/lib/libzfs/libzfs_pool.c index 7b78f25f0007..730460218d37 100644 --- a/lib/libzfs/libzfs_pool.c +++ b/lib/libzfs/libzfs_pool.c @@ -2081,15 +2081,14 @@ zpool_get_physpath(zpool_handle_t *zhp, char *physpath, size_t phypath_size) * the disk to use the new unallocated space. */ static int -zpool_relabel_disk(libzfs_handle_t *hdl, const char *path) +zpool_relabel_disk(libzfs_handle_t *hdl, const char *path, const char *msg) { - char errbuf[1024]; int fd, error; if ((fd = open(path, O_RDWR|O_DIRECT)) < 0) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "cannot " "relabel '%s': unable to open device: %d"), path, errno); - return (zfs_error(hdl, EZFS_OPENFAILED, errbuf)); + return (zfs_error(hdl, EZFS_OPENFAILED, msg)); } /* @@ -2102,7 +2101,7 @@ zpool_relabel_disk(libzfs_handle_t *hdl, const char *path) if (error && error != VT_ENOSPC) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "cannot " "relabel '%s': unable to read disk capacity"), path); - return (zfs_error(hdl, EZFS_NOCAP, errbuf)); + return (zfs_error(hdl, EZFS_NOCAP, msg)); } return (0); } @@ -2160,7 +2159,9 @@ zpool_vdev_online(zpool_handle_t *zhp, const char *path, int flags, if (wholedisk) { pathname += strlen(DISK_ROOT) + 1; - (void) zpool_relabel_disk(hdl, pathname); + int result = zpool_relabel_disk(hdl, pathname, msg); + if (result != 0) + return (result); } } From 5d49b12aeb63542723ecc5b6faffcc03bff26cb8 Mon Sep 17 00:00:00 2001 From: Etienne Dechamps Date: Fri, 6 Jul 2012 16:22:03 +0200 Subject: [PATCH 2/5] Use the right device path when relabelling. Currently, zpool_vdev_online() calls zpool_relabel_disk() with a short partition device name, which is obviously wrong because (1) zpool_relabel_disk() expects a full, absolute path to use with open() and (2) efi_write() must be called on an opened disk device, not a partition device. With this patch, zpool_relabel_disk() gets called with a full disk device path. The path is determined using the same algorithm as zpool_find_vdev(). --- lib/libzfs/libzfs_pool.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/libzfs/libzfs_pool.c b/lib/libzfs/libzfs_pool.c index 730460218d37..8247dab0c932 100644 --- a/lib/libzfs/libzfs_pool.c +++ b/lib/libzfs/libzfs_pool.c @@ -2140,13 +2140,10 @@ zpool_vdev_online(zpool_handle_t *zhp, const char *path, int flags, if (flags & ZFS_ONLINE_EXPAND || zpool_get_prop_int(zhp, ZPOOL_PROP_AUTOEXPAND, NULL)) { - char *pathname = NULL; uint64_t wholedisk = 0; (void) nvlist_lookup_uint64(tgt, ZPOOL_CONFIG_WHOLE_DISK, &wholedisk); - verify(nvlist_lookup_string(tgt, ZPOOL_CONFIG_PATH, - &pathname) == 0); /* * XXX - L2ARC 1.0 devices can't support expansion. @@ -2158,8 +2155,15 @@ zpool_vdev_online(zpool_handle_t *zhp, const char *path, int flags, } if (wholedisk) { - pathname += strlen(DISK_ROOT) + 1; - int result = zpool_relabel_disk(hdl, pathname, msg); + const char *fullpath = path; + char buf[MAXPATHLEN]; + if (path[0] != '/') { + if (zfs_resolve_shortname(path, buf, sizeof(buf))) + return (zfs_error(hdl, EZFS_NODEVICE, msg)); + fullpath = buf; + } + + int result = zpool_relabel_disk(hdl, fullpath, msg); if (result != 0) return (result); } From 2f301406b3b25a3d761ee1e08b0806f90b9c017f Mon Sep 17 00:00:00 2001 From: Etienne Dechamps Date: Wed, 11 Jul 2012 15:06:32 +0200 Subject: [PATCH 3/5] Move partition scanning from userspace to module. Currently, zpool online -e (dynamic vdev expansion) doesn't work on whole disks because we're invoking ioctl(BLKRRPART) from userspace while ZFS still has a partition open on the disk, which results in EBUSY. This patch moves the BLKRRPART invocation from the zpool utility to the module. Specifically, this is done just before opening the device in vdev_disk_open() which is called inside vdev_reopen(). This requires jumping through some hoops to get to the disk device from the partition device, and to make sure we can still open the partition after the BLKRRPART call. Note that this new code path is triggered on dynamic vdev expansion only; other actions, like creating a new pool, are unchanged and still call BLKRRPART from userspace. --- include/linux/blkdev_compat.h | 18 ++++++--- include/sys/efi_partition.h | 1 + lib/libefi/rdwr_efi.c | 13 ++----- lib/libzfs/libzfs_pool.c | 5 ++- module/zfs/vdev_disk.c | 73 ++++++++++++++++++++++++++++++++--- 5 files changed, 87 insertions(+), 23 deletions(-) diff --git a/include/linux/blkdev_compat.h b/include/linux/blkdev_compat.h index bd1b2bf54003..11de7e60fd33 100644 --- a/include/linux/blkdev_compat.h +++ b/include/linux/blkdev_compat.h @@ -370,15 +370,21 @@ bio_set_flags_failfast(struct block_device *bdev, int *flags) * Used to exclusively open a block device from within the kernel. */ #if defined(HAVE_BLKDEV_GET_BY_PATH) -# define vdev_bdev_open(path, md, hld) blkdev_get_by_path(path, \ +# define vdev_bdev_open(path, md, hld) blkdev_get_by_path(path, md, hld) +# define vdev_bdev_open_exclusive(path, md, hld) blkdev_get_by_path(path, \ (md) | FMODE_EXCL, hld) -# define vdev_bdev_close(bdev, md) blkdev_put(bdev, (md) | FMODE_EXCL) +# define vdev_bdev_close(bdev, md) blkdev_put(bdev, (md)) +# define vdev_bdev_close_exclusive(bdev, md) blkdev_put(bdev, (md) | FMODE_EXCL) #elif defined(HAVE_OPEN_BDEV_EXCLUSIVE) -# define vdev_bdev_open(path, md, hld) open_bdev_exclusive(path, md, hld) -# define vdev_bdev_close(bdev, md) close_bdev_exclusive(bdev, md) +# define vdev_bdev_open(path, md, hld) open_bdev(path, md, hld) +# define vdev_bdev_open_exclusive(path, md, hld) open_bdev_exclusive(path, md, hld) +# define vdev_bdev_close(bdev, md) close_bdev(bdev, md) +# define vdev_bdev_close_exclusive(bdev, md) close_bdev_exclusive(bdev, md) #else -# define vdev_bdev_open(path, md, hld) open_bdev_excl(path, md, hld) -# define vdev_bdev_close(bdev, md) close_bdev_excl(bdev) +# define vdev_bdev_open(path, md, hld) open_bdev(path, md, hld) +# define vdev_bdev_open_exclusive(path, md, hld) open_bdev_excl(path, md, hld) +# define vdev_bdev_close(bdev, md) close_bdev(bdev) +# define vdev_bdev_close_exclusive(bdev, md) close_bdev_excl(bdev) #endif /* HAVE_BLKDEV_GET_BY_PATH | HAVE_OPEN_BDEV_EXCLUSIVE */ /* diff --git a/include/sys/efi_partition.h b/include/sys/efi_partition.h index e75e45a6bc0d..ee367a574e46 100644 --- a/include/sys/efi_partition.h +++ b/include/sys/efi_partition.h @@ -229,6 +229,7 @@ struct partition64 { extern int efi_alloc_and_init(int, uint32_t, struct dk_gpt **); extern int efi_alloc_and_read(int, struct dk_gpt **); extern int efi_write(int, struct dk_gpt *); +extern int efi_rescan(int); extern void efi_free(struct dk_gpt *); extern int efi_type(int); extern void efi_err_check(struct dk_gpt *); diff --git a/lib/libefi/rdwr_efi.c b/lib/libefi/rdwr_efi.c index 0600a9523ebc..3e51bbf05200 100644 --- a/lib/libefi/rdwr_efi.c +++ b/lib/libefi/rdwr_efi.c @@ -497,10 +497,9 @@ efi_ioctl(int fd, int cmd, dk_efi_t *dk_ioc) return (error); } -#if defined(__linux__) -static int -efi_rescan(int fd) +int efi_rescan(int fd) { +#if defined(__linux__) int retry = 5; int error; @@ -512,10 +511,10 @@ efi_rescan(int fd) return (-1); } } +#endif return (0); } -#endif static int check_label(int fd, dk_efi_t *dk_ioc) @@ -1302,12 +1301,6 @@ efi_write(int fd, struct dk_gpt *vtoc) (void) write_pmbr(fd, vtoc); free(dk_ioc.dki_data); -#if defined(__linux__) - rval = efi_rescan(fd); - if (rval) - return (VT_ERROR); -#endif - return (0); } diff --git a/lib/libzfs/libzfs_pool.c b/lib/libzfs/libzfs_pool.c index 8247dab0c932..2fa8c81281dc 100644 --- a/lib/libzfs/libzfs_pool.c +++ b/lib/libzfs/libzfs_pool.c @@ -2095,6 +2095,9 @@ zpool_relabel_disk(libzfs_handle_t *hdl, const char *path, const char *msg) * It's possible that we might encounter an error if the device * does not have any unallocated space left. If so, we simply * ignore that error and continue on. + * + * Also, we don't call efi_rescan() - that would just return EBUSY. + * The module will do it for us in vdev_disk_open(). */ error = efi_use_whole_disk(fd); (void) close(fd); @@ -3819,7 +3822,7 @@ zpool_label_disk(libzfs_handle_t *hdl, zpool_handle_t *zhp, char *name) vtoc->efi_parts[8].p_size = resv; vtoc->efi_parts[8].p_tag = V_RESERVED; - if ((rval = efi_write(fd, vtoc)) != 0) { + if ((rval = efi_write(fd, vtoc)) != 0 || (rval = efi_rescan(fd)) != 0) { /* * Some block drivers (like pcata) may not support EFI * GPT labels. Print out a helpful error message dir- diff --git a/module/zfs/vdev_disk.c b/module/zfs/vdev_disk.c index 28a4861abd72..b14caeb12fe6 100644 --- a/module/zfs/vdev_disk.c +++ b/module/zfs/vdev_disk.c @@ -158,10 +158,68 @@ vdev_elevator_switch(vdev_t *v, char *elevator) return (error); } +/* + * Expanding a whole disk vdev involves invoking BLKRRPART on the + * whole disk device. This poses a problem, because BLKRRPART will + * return EBUSY if one of the disk's partitions is open. That's why + * we have to do it here, just before opening the data partition. + * Unfortunately, BLKRRPART works by dropping all partitions and + * recreating them, which means that for a short time window, all + * /dev/sdxN device files disappear (until udev recreates them). + * This means two things: + * - When we open the data partition just after a BLKRRPART, we + * can't do it using the normal device file path because of the + * obvious race condition with udev. Instead, we use reliable + * kernel APIs to get a handle to the new partition device from + * the whole disk device. + * - Because vdev_disk_open() initially needs to find the device + * using its path, multiple vdev_disk_open() invocations in + * short succession on the same disk with BLKRRPARTs in the + * middle have a high probability of failure (because of the + * race condition with udev). A typical situation where this + * might happen is when the zpool userspace tool does a + * TRYIMPORT immediately followed by an IMPORT. For this + * reason, we only invoke BLKRRPART in the module when strictly + * necessary (zpool online -e case), and rely on userspace to + * do it when possible. + */ +static struct block_device * vdev_disk_rrpart(const char *path, int mode, vdev_disk_t *vd) +{ + struct block_device *result = NULL, *bdev; + struct gendisk *disk; + int error, partno; + + bdev = vdev_bdev_open(path, vdev_bdev_mode(mode), vd); + if (bdev) { + disk = get_gendisk(bdev->bd_dev, &partno); + vdev_bdev_close(bdev, vdev_bdev_mode(mode)); + + if (disk) { + bdev = bdget(disk_devt(disk)); + if (bdev) { + error = blkdev_get(bdev, vdev_bdev_mode(mode), vd); + if (error == 0) + error = ioctl_by_bdev(bdev, BLKRRPART, 0); + vdev_bdev_close(bdev, vdev_bdev_mode(mode)); + } + + bdev = bdget_disk(disk, partno); + if (bdev) { + error = blkdev_get(bdev, vdev_bdev_mode(mode) | FMODE_EXCL, vd); + if (error == 0) + result = bdev; + } + put_disk(disk); + } + } + + return result; +} + static int vdev_disk_open(vdev_t *v, uint64_t *psize, uint64_t *ashift) { - struct block_device *bdev; + struct block_device *bdev = NULL; vdev_disk_t *vd; int mode, block_size; @@ -190,7 +248,10 @@ vdev_disk_open(vdev_t *v, uint64_t *psize, uint64_t *ashift) * level vdev validation. */ mode = spa_mode(v->vdev_spa); - bdev = vdev_bdev_open(v->vdev_path, vdev_bdev_mode(mode), vd); + if (v->vdev_wholedisk && v->vdev_expanding) + bdev = vdev_disk_rrpart(v->vdev_path, mode, vd); + if (!bdev) + bdev = vdev_bdev_open_exclusive(v->vdev_path, vdev_bdev_mode(mode), vd); if (IS_ERR(bdev)) { kmem_free(vd, sizeof(vdev_disk_t)); return -PTR_ERR(bdev); @@ -238,7 +299,7 @@ vdev_disk_close(vdev_t *v) return; if (vd->vd_bdev != NULL) - vdev_bdev_close(vd->vd_bdev, + vdev_bdev_close_exclusive(vd->vd_bdev, vdev_bdev_mode(spa_mode(v->vdev_spa))); kmem_free(vd, sizeof(vdev_disk_t)); @@ -710,13 +771,13 @@ vdev_disk_read_rootlabel(char *devpath, char *devid, nvlist_t **config) uint64_t s, size; int i; - bdev = vdev_bdev_open(devpath, vdev_bdev_mode(FREAD), NULL); + bdev = vdev_bdev_open_exclusive(devpath, vdev_bdev_mode(FREAD), NULL); if (IS_ERR(bdev)) return -PTR_ERR(bdev); s = bdev_capacity(bdev); if (s == 0) { - vdev_bdev_close(bdev, vdev_bdev_mode(FREAD)); + vdev_bdev_close_exclusive(bdev, vdev_bdev_mode(FREAD)); return EIO; } @@ -756,7 +817,7 @@ vdev_disk_read_rootlabel(char *devpath, char *devid, nvlist_t **config) } vmem_free(label, sizeof(vdev_label_t)); - vdev_bdev_close(bdev, vdev_bdev_mode(FREAD)); + vdev_bdev_close_exclusive(bdev, vdev_bdev_mode(FREAD)); return 0; } From 8d45c2d99cb4fb3464b421abccd108803129add5 Mon Sep 17 00:00:00 2001 From: Etienne Dechamps Date: Wed, 11 Jul 2012 17:47:10 +0200 Subject: [PATCH 4/5] Fix efi_use_whole_disk() when efi_nparts == 128. Commit e5dc681a changed EFI_NUMPAR from 9 to 128. This means that the on-disk EFI label has efi_nparts = 128 instead of 9. The index of the reserved partition, however, is still 8. This breaks efi_use_whole_disk(), which uses efi_nparts-1 as the index of the reserved partition. This commit fixes efi_use_whole_disk() when the index of the reserved partition is not efi_nparts-1. It rewrites the algorithm and makes it more robust by using the order of the partitions instead of their numbering. It assumes that the last non-empty partition is the reserved partition, and that the non-empty partition before that is the data partition. --- lib/libefi/rdwr_efi.c | 50 ++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/lib/libefi/rdwr_efi.c b/lib/libefi/rdwr_efi.c index 3e51bbf05200..037d0464d25d 100644 --- a/lib/libefi/rdwr_efi.c +++ b/lib/libefi/rdwr_efi.c @@ -1027,24 +1027,15 @@ efi_use_whole_disk(int fd) struct dk_gpt *efi_label; int rval; int i; - uint_t phy_last_slice = 0; - diskaddr_t pl_start = 0; - diskaddr_t pl_size; + uint_t resv_index = 0, data_index = 0; + diskaddr_t resv_start = 0, data_start = 0; + diskaddr_t difference; rval = efi_alloc_and_read(fd, &efi_label); if (rval < 0) { return (rval); } - /* find the last physically non-zero partition */ - for (i = 0; i < efi_label->efi_nparts - 2; i ++) { - if (pl_start < efi_label->efi_parts[i].p_start) { - pl_start = efi_label->efi_parts[i].p_start; - phy_last_slice = i; - } - } - pl_size = efi_label->efi_parts[phy_last_slice].p_size; - /* * If alter_lba is 1, we are using the backup label. * Since we can locate the backup label by disk capacity, @@ -1060,16 +1051,28 @@ efi_use_whole_disk(int fd) return (VT_ENOSPC); } + difference = efi_label->efi_last_lba - efi_label->efi_altern_lba; + /* - * If there is space between the last physically non-zero partition - * and the reserved partition, just add the unallocated space to this - * area. Otherwise, the unallocated space is added to the last - * physically non-zero partition. + * Find the last physically non-zero partition. + * This is the reserved partition. */ - if (pl_start + pl_size - 1 == efi_label->efi_last_u_lba - - EFI_MIN_RESV_SIZE) { - efi_label->efi_parts[phy_last_slice].p_size += - efi_label->efi_last_lba - efi_label->efi_altern_lba; + for (i = 0; i < efi_label->efi_nparts; i ++) { + if (resv_start < efi_label->efi_parts[i].p_start) { + resv_start = efi_label->efi_parts[i].p_start; + resv_index = i; + } + } + + /* + * Find the last physically non-zero partition before that. + * This is the data partition. + */ + for (i = 0; i < resv_index; i ++) { + if (data_start < efi_label->efi_parts[i].p_start) { + data_start = efi_label->efi_parts[i].p_start; + data_index = i; + } } /* @@ -1077,10 +1080,9 @@ efi_use_whole_disk(int fd) * here except fabricated devids (which get generated via * efi_write()). So there is no need to copy data. */ - efi_label->efi_parts[efi_label->efi_nparts - 1].p_start += - efi_label->efi_last_lba - efi_label->efi_altern_lba; - efi_label->efi_last_u_lba += efi_label->efi_last_lba - - efi_label->efi_altern_lba; + efi_label->efi_parts[data_index].p_size += difference; + efi_label->efi_parts[resv_index].p_start += difference; + efi_label->efi_last_u_lba += difference; rval = efi_write(fd, efi_label); if (rval < 0) { From be99500d39c272f6a78be29aeaf79a8a21a5a0f1 Mon Sep 17 00:00:00 2001 From: Etienne Dechamps Date: Thu, 12 Jul 2012 09:54:28 +0200 Subject: [PATCH 5/5] Don't use non-exclusive versions of vdev_bdev_open. This reverts parts of 2f30140 which added non-exclusive versions of vdev_bdev_open and vdev_bdev_close. These calls were needed in the first versions of the 2f30140 patch, but it turns out they're not needed anymore and I just forgot to remove them. --- include/linux/blkdev_compat.h | 18 ++++++------------ module/zfs/vdev_disk.c | 10 +++++----- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/include/linux/blkdev_compat.h b/include/linux/blkdev_compat.h index 11de7e60fd33..bd1b2bf54003 100644 --- a/include/linux/blkdev_compat.h +++ b/include/linux/blkdev_compat.h @@ -370,21 +370,15 @@ bio_set_flags_failfast(struct block_device *bdev, int *flags) * Used to exclusively open a block device from within the kernel. */ #if defined(HAVE_BLKDEV_GET_BY_PATH) -# define vdev_bdev_open(path, md, hld) blkdev_get_by_path(path, md, hld) -# define vdev_bdev_open_exclusive(path, md, hld) blkdev_get_by_path(path, \ +# define vdev_bdev_open(path, md, hld) blkdev_get_by_path(path, \ (md) | FMODE_EXCL, hld) -# define vdev_bdev_close(bdev, md) blkdev_put(bdev, (md)) -# define vdev_bdev_close_exclusive(bdev, md) blkdev_put(bdev, (md) | FMODE_EXCL) +# define vdev_bdev_close(bdev, md) blkdev_put(bdev, (md) | FMODE_EXCL) #elif defined(HAVE_OPEN_BDEV_EXCLUSIVE) -# define vdev_bdev_open(path, md, hld) open_bdev(path, md, hld) -# define vdev_bdev_open_exclusive(path, md, hld) open_bdev_exclusive(path, md, hld) -# define vdev_bdev_close(bdev, md) close_bdev(bdev, md) -# define vdev_bdev_close_exclusive(bdev, md) close_bdev_exclusive(bdev, md) +# define vdev_bdev_open(path, md, hld) open_bdev_exclusive(path, md, hld) +# define vdev_bdev_close(bdev, md) close_bdev_exclusive(bdev, md) #else -# define vdev_bdev_open(path, md, hld) open_bdev(path, md, hld) -# define vdev_bdev_open_exclusive(path, md, hld) open_bdev_excl(path, md, hld) -# define vdev_bdev_close(bdev, md) close_bdev(bdev) -# define vdev_bdev_close_exclusive(bdev, md) close_bdev_excl(bdev) +# define vdev_bdev_open(path, md, hld) open_bdev_excl(path, md, hld) +# define vdev_bdev_close(bdev, md) close_bdev_excl(bdev) #endif /* HAVE_BLKDEV_GET_BY_PATH | HAVE_OPEN_BDEV_EXCLUSIVE */ /* diff --git a/module/zfs/vdev_disk.c b/module/zfs/vdev_disk.c index b14caeb12fe6..a22846552552 100644 --- a/module/zfs/vdev_disk.c +++ b/module/zfs/vdev_disk.c @@ -251,7 +251,7 @@ vdev_disk_open(vdev_t *v, uint64_t *psize, uint64_t *ashift) if (v->vdev_wholedisk && v->vdev_expanding) bdev = vdev_disk_rrpart(v->vdev_path, mode, vd); if (!bdev) - bdev = vdev_bdev_open_exclusive(v->vdev_path, vdev_bdev_mode(mode), vd); + bdev = vdev_bdev_open(v->vdev_path, vdev_bdev_mode(mode), vd); if (IS_ERR(bdev)) { kmem_free(vd, sizeof(vdev_disk_t)); return -PTR_ERR(bdev); @@ -299,7 +299,7 @@ vdev_disk_close(vdev_t *v) return; if (vd->vd_bdev != NULL) - vdev_bdev_close_exclusive(vd->vd_bdev, + vdev_bdev_close(vd->vd_bdev, vdev_bdev_mode(spa_mode(v->vdev_spa))); kmem_free(vd, sizeof(vdev_disk_t)); @@ -771,13 +771,13 @@ vdev_disk_read_rootlabel(char *devpath, char *devid, nvlist_t **config) uint64_t s, size; int i; - bdev = vdev_bdev_open_exclusive(devpath, vdev_bdev_mode(FREAD), NULL); + bdev = vdev_bdev_open(devpath, vdev_bdev_mode(FREAD), NULL); if (IS_ERR(bdev)) return -PTR_ERR(bdev); s = bdev_capacity(bdev); if (s == 0) { - vdev_bdev_close_exclusive(bdev, vdev_bdev_mode(FREAD)); + vdev_bdev_close(bdev, vdev_bdev_mode(FREAD)); return EIO; } @@ -817,7 +817,7 @@ vdev_disk_read_rootlabel(char *devpath, char *devid, nvlist_t **config) } vmem_free(label, sizeof(vdev_label_t)); - vdev_bdev_close_exclusive(bdev, vdev_bdev_mode(FREAD)); + vdev_bdev_close(bdev, vdev_bdev_mode(FREAD)); return 0; }