From 2f86f9c431a75d4f1ec8a645257d16db0fb843bc Mon Sep 17 00:00:00 2001 From: Saso Kiselkov Date: Thu, 9 Feb 2017 18:34:21 +0100 Subject: [PATCH] Attaching a vdev while resilver/scrub is running causes panic. --- usr/src/uts/common/fs/zfs/dsl_scan.c | 25 ++++++++++++++++++++++ usr/src/uts/common/fs/zfs/range_tree.c | 12 +++++++++++ usr/src/uts/common/fs/zfs/sys/dsl_scan.h | 1 + usr/src/uts/common/fs/zfs/sys/range_tree.h | 1 + usr/src/uts/common/fs/zfs/vdev.c | 3 +-- 5 files changed, 40 insertions(+), 2 deletions(-) diff --git a/usr/src/uts/common/fs/zfs/dsl_scan.c b/usr/src/uts/common/fs/zfs/dsl_scan.c index 79957438d486..2ed3d1792814 100644 --- a/usr/src/uts/common/fs/zfs/dsl_scan.c +++ b/usr/src/uts/common/fs/zfs/dsl_scan.c @@ -2799,6 +2799,30 @@ dsl_scan_io_queue_destroy(dsl_scan_io_queue_t *queue) cv_destroy(&queue->q_cv); } +/* + * Properly transfers a dsl_scan_queue_t from `svd' to `tvd'. This is + * called on behalf of vdev_top_transfer when creating or destroying + * a mirror vdev due to zpool attach/detach. + */ +void +dsl_scan_io_queue_vdev_xfer(vdev_t *svd, vdev_t *tvd) +{ + mutex_enter(&svd->vdev_scan_io_queue_lock); + mutex_enter(&tvd->vdev_scan_io_queue_lock); + + VERIFY3P(tvd->vdev_scan_io_queue, ==, NULL); + tvd->vdev_scan_io_queue = svd->vdev_scan_io_queue; + svd->vdev_scan_io_queue = NULL; + if (tvd->vdev_scan_io_queue != NULL) { + tvd->vdev_scan_io_queue->q_vd = tvd; + range_tree_set_lock(tvd->vdev_scan_io_queue->q_exts_by_addr, + &tvd->vdev_scan_io_queue_lock); + } + + mutex_exit(&tvd->vdev_scan_io_queue_lock); + mutex_exit(&svd->vdev_scan_io_queue_lock); +} + static void scan_io_queues_destroy(dsl_scan_t *scn) { @@ -3157,6 +3181,7 @@ scan_io_queues_run(dsl_scan_t *scn) spa->spa_root_vdev->vdev_children; ASSERT(scn->scn_is_sorted); + ASSERT(spa_config_held(spa, SCL_CONFIG, RW_READER)); if (scn->scn_taskq == NULL) { char *tq_name = kmem_zalloc(ZFS_MAX_DATASET_NAME_LEN + 16, diff --git a/usr/src/uts/common/fs/zfs/range_tree.c b/usr/src/uts/common/fs/zfs/range_tree.c index 718c93b0698a..abd29efb9ae4 100644 --- a/usr/src/uts/common/fs/zfs/range_tree.c +++ b/usr/src/uts/common/fs/zfs/range_tree.c @@ -131,6 +131,18 @@ range_tree_set_gap(range_tree_t *rt, uint64_t gap) rt->rt_gap = gap; } +/* + * Changes out the lock used by the range tree. Useful when you are moving + * the range tree between containing structures without having to recreate + * it. Both the old and new locks must be held by the caller. + */ +void +range_tree_set_lock(range_tree_t *rt, kmutex_t *lp) +{ + ASSERT(MUTEX_HELD(rt->rt_lock) && MUTEX_HELD(lp)); + rt->rt_lock = lp; +} + static void range_tree_stat_incr(range_tree_t *rt, range_seg_t *rs) { diff --git a/usr/src/uts/common/fs/zfs/sys/dsl_scan.h b/usr/src/uts/common/fs/zfs/sys/dsl_scan.h index 430baf7568c4..7cd3360f6019 100644 --- a/usr/src/uts/common/fs/zfs/sys/dsl_scan.h +++ b/usr/src/uts/common/fs/zfs/sys/dsl_scan.h @@ -179,6 +179,7 @@ void dsl_scan_ds_clone_swapped(struct dsl_dataset *ds1, struct dsl_dataset *ds2, boolean_t dsl_scan_active(dsl_scan_t *scn); void dsl_scan_freed(spa_t *spa, const blkptr_t *bp); void dsl_scan_io_queue_destroy(dsl_scan_io_queue_t *queue); +void dsl_scan_io_queue_vdev_xfer(vdev_t *svd, vdev_t *tvd); #ifdef __cplusplus } diff --git a/usr/src/uts/common/fs/zfs/sys/range_tree.h b/usr/src/uts/common/fs/zfs/sys/range_tree.h index bb44cc88b963..98062071187c 100644 --- a/usr/src/uts/common/fs/zfs/sys/range_tree.h +++ b/usr/src/uts/common/fs/zfs/sys/range_tree.h @@ -88,6 +88,7 @@ void range_tree_verify(range_tree_t *rt, uint64_t start, uint64_t size); void range_tree_swap(range_tree_t **rtsrc, range_tree_t **rtdst); void range_tree_stat_verify(range_tree_t *rt); void range_tree_set_gap(range_tree_t *rt, uint64_t gap); +void range_tree_set_lock(range_tree_t *rt, kmutex_t *lp); void range_tree_add(void *arg, uint64_t start, uint64_t size); void range_tree_remove(void *arg, uint64_t start, uint64_t size); diff --git a/usr/src/uts/common/fs/zfs/vdev.c b/usr/src/uts/common/fs/zfs/vdev.c index 338d1956f78c..e1b002ace49b 100644 --- a/usr/src/uts/common/fs/zfs/vdev.c +++ b/usr/src/uts/common/fs/zfs/vdev.c @@ -770,8 +770,7 @@ vdev_top_transfer(vdev_t *svd, vdev_t *tvd) tvd->vdev_islog = svd->vdev_islog; svd->vdev_islog = 0; - tvd->vdev_scan_io_queue = svd->vdev_scan_io_queue; - svd->vdev_scan_io_queue = NULL; + dsl_scan_io_queue_vdev_xfer(svd, tvd); } static void