diff options
Diffstat (limited to 'sys/cddl/contrib/opensolaris/uts/common/fs/zfs/vdev_geom.c')
-rw-r--r-- | sys/cddl/contrib/opensolaris/uts/common/fs/zfs/vdev_geom.c | 445 |
1 files changed, 352 insertions, 93 deletions
diff --git a/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/vdev_geom.c b/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/vdev_geom.c index eebc911..f151f83 100644 --- a/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/vdev_geom.c +++ b/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/vdev_geom.c @@ -96,13 +96,9 @@ vdev_geom_orphan(struct g_consumer *cp) g_wither_geom(gp, error); } vdev_geom_release(vd); - /* Both methods below work, but in a bit different way. */ -#if 0 - vd->vdev_reopen_wanted = 1; -#else - vd->vdev_stat.vs_aux = VDEV_AUX_OPEN_FAILED; - vdev_set_state(vd, B_TRUE, VDEV_STATE_CANT_OPEN, vd->vdev_stat.vs_aux); -#endif + + vd->vdev_remove_wanted = B_TRUE; + spa_async_request(vd->vdev_spa, SPA_ASYNC_REMOVE); } static struct g_consumer * @@ -229,7 +225,7 @@ vdev_geom_worker(void *arg) vd->vdev_nowritecache = B_TRUE; } g_destroy_bio(bp); - zio_next_stage_async(zio); + zio_interrupt(zio); } } @@ -249,6 +245,194 @@ vdev_geom_get_id(struct g_consumer *cp) return (id); } +static uint64_t +nvlist_get_guid(nvlist_t *list) +{ + nvpair_t *elem = NULL; + uint64_t value; + + while ((elem = nvlist_next_nvpair(list, elem)) != NULL) { + if (nvpair_type(elem) == DATA_TYPE_UINT64 && + strcmp(nvpair_name(elem), "guid") == 0) { + VERIFY(nvpair_value_uint64(elem, &value) == 0); + return (value); + } + } + return (0); +} + +static char * +nvlist_get_devid(nvlist_t *list, uint64_t guid) +{ + nvpair_t *elem = NULL; + int progress; + char *id; + + progress = 0; + id = NULL; + + while ((elem = nvlist_next_nvpair(list, elem)) != NULL) { + switch (nvpair_type(elem)) { + case DATA_TYPE_STRING: + { + char *value; + + VERIFY(nvpair_value_string(elem, &value) == 0); + if (strcmp(nvpair_name(elem), "type") == 0 && + strcmp(value, "disk") == 0) { + progress |= 0x01; + } else if (strcmp(nvpair_name(elem), "devid") == 0) { + progress |= 0x02; + id = value; + } + break; + } + case DATA_TYPE_UINT64: + { + uint64_t value; + + VERIFY(nvpair_value_uint64(elem, &value) == 0); + if (strcmp(nvpair_name(elem), "guid") == 0 && + value == guid) { + progress |= 0x04; + } + break; + } + case DATA_TYPE_NVLIST: + { + nvlist_t *value; + char *lid; + + VERIFY(nvpair_value_nvlist(elem, &value) == 0); + lid = nvlist_get_devid(value, guid); + if (lid != NULL) + return (lid); + break; + } + case DATA_TYPE_NVLIST_ARRAY: + { + nvlist_t **value; + u_int c, count; + char *lid; + + VERIFY(nvpair_value_nvlist_array(elem, &value, + &count) == 0); + + for (c = 0; c < count; c++) { + lid = nvlist_get_devid(value[c], guid); + if (lid != NULL) + return (lid); + } + break; + } + } + if (progress == 0x07) + break; + } + if (progress != 0x07) + id = NULL; + return (id); +} + +static int +vdev_geom_io(struct g_consumer *cp, int cmd, void *data, off_t offset, off_t size) +{ + struct bio *bp; + u_char *p; + off_t off; + int error; + + ASSERT((offset % cp->provider->sectorsize) == 0); + ASSERT((size % cp->provider->sectorsize) == 0); + + bp = g_alloc_bio(); + off = offset; + offset += size; + p = data; + error = 0; + + for (; off < offset; off += MAXPHYS, p += MAXPHYS, size -= MAXPHYS) { + bzero(bp, sizeof(*bp)); + bp->bio_cmd = cmd; + bp->bio_done = NULL; + bp->bio_offset = off; + bp->bio_length = MIN(size, MAXPHYS); + bp->bio_data = p; + g_io_request(bp, cp); + error = biowait(bp, "vdev_geom_io"); + if (error != 0) + break; + } + + g_destroy_bio(bp); + return (error); +} + +static char * +vdev_geom_read_id(struct g_consumer *cp) +{ + struct g_provider *pp; + vdev_label_t *label; + char *p, *buf; + size_t buflen; + uint64_t psize; + off_t offset, size; + char *id; + int error, l, len; + + g_topology_assert_not(); + + pp = cp->provider; + + psize = pp->mediasize; + psize = P2ALIGN(psize, (uint64_t)sizeof(vdev_label_t)); + + size = sizeof(*label) + pp->sectorsize - + ((sizeof(*label) - 1) % pp->sectorsize) - 1; + + id = NULL; + label = kmem_alloc(size, KM_SLEEP); + buflen = sizeof(label->vl_vdev_phys.vp_nvlist); + + for (l = 0; l < VDEV_LABELS && id == NULL; l++) { + nvlist_t *config = NULL; + uint64_t guid; + + offset = vdev_label_offset(psize, l, 0); + if ((offset % pp->sectorsize) != 0) + continue; + + error = vdev_geom_io(cp, BIO_READ, label, offset, size); + if (error != 0) + continue; + buf = label->vl_vdev_phys.vp_nvlist; + + if (nvlist_unpack(buf, buflen, &config, 0) != 0) + continue; + + guid = nvlist_get_guid(config); + if (guid == 0) { + nvlist_free(config); + continue; + } + id = nvlist_get_devid(config, guid); + if (id != NULL) { + char *tmp; + + tmp = kmem_zalloc(DISK_IDENT_SIZE, KM_SLEEP); + strlcpy(tmp, id, DISK_IDENT_SIZE); + id = tmp; + } + + nvlist_free(config); + } + + kmem_free(label, size); + if (id != NULL) + ZFS_LOG(1, "ID of %s: %s", pp->name, id); + return (id); +} + static void vdev_geom_free_id(char *id) { @@ -290,6 +474,7 @@ vdev_geom_attach_by_id_event(void *arg, int flags __unused) zgp->orphan = vdev_geom_taste_orphan; zcp = g_new_consumer(zgp); + /* First round tries to get provider's ID without reading metadata. */ LIST_FOREACH(mp, &g_classes, class) { if (mp == &zfs_vdev_class) continue; @@ -324,6 +509,41 @@ vdev_geom_attach_by_id_event(void *arg, int flags __unused) } } } + /* Second round looks for ID by reading ZFS metadata. */ + LIST_FOREACH(mp, &g_classes, class) { + if (mp == &zfs_vdev_class) + continue; + LIST_FOREACH(gp, &mp->geom, geom) { + if (gp->flags & G_GEOM_WITHER) + continue; + LIST_FOREACH(pp, &gp->provider, provider) { + if (pp->flags & G_PF_WITHER) + continue; + g_attach(zcp, pp); + if (g_access(zcp, 1, 0, 0) != 0) { + g_detach(zcp); + continue; + } + g_topology_unlock(); + id = vdev_geom_read_id(zcp); + g_topology_lock(); + g_access(zcp, -1, 0, 0); + g_detach(zcp); + if (id == NULL || strcmp(id, ap->id) != 0) { + vdev_geom_free_id(id); + continue; + } + vdev_geom_free_id(id); + ap->cp = vdev_geom_attach(pp, ap->write); + if (ap->cp == NULL) { + printf("ZFS WARNING: Cannot open %s " + "for writting.\n", pp->name); + continue; + } + goto end; + } + } + } ap->cp = NULL; end: g_destroy_consumer(zcp); @@ -345,25 +565,13 @@ vdev_geom_attach_by_id(const char *id, int write) return (cp); } -static int -vdev_geom_open(vdev_t *vd, uint64_t *psize, uint64_t *ashift) +static struct g_consumer * +vdev_geom_open_by_path_and_devid(vdev_t *vd) { - vdev_geom_ctx_t *ctx; struct g_provider *pp; struct g_consumer *cp; - char *id = NULL; - int owned; - - /* - * We must have a pathname, and it must be absolute. - */ - if (vd->vdev_path == NULL || vd->vdev_path[0] != '/') { - vd->vdev_stat.vs_aux = VDEV_AUX_BAD_LABEL; - return (EINVAL); - } + char *id; - if ((owned = mtx_owned(&Giant))) - mtx_unlock(&Giant); cp = NULL; g_topology_lock(); pp = g_provider_by_name(vd->vdev_path + sizeof("/dev/") - 1); @@ -380,40 +588,101 @@ vdev_geom_open(vdev_t *vd, uint64_t *psize, uint64_t *ashift) ZFS_LOG(1, "ID mismatch for provider %s: " "[%s]!=[%s].", vd->vdev_path, vd->vdev_devid, id); - goto next; - } - ZFS_LOG(1, "ID match for provider %s.", vd->vdev_path); + } else + ZFS_LOG(1, "ID match for provider %s.", + vd->vdev_path); + vdev_geom_free_id(id); } } -next: g_topology_unlock(); - vdev_geom_free_id(id); - if (cp == NULL && vd->vdev_devid != NULL) { - ZFS_LOG(1, "Searching by ID [%s].", vd->vdev_devid); - cp = vdev_geom_attach_by_id(vd->vdev_devid, - !!(spa_mode & FWRITE)); - if (cp != NULL) { - size_t len = strlen(cp->provider->name) + 6; /* 6 == strlen("/dev/") + 1 */ - char *buf = kmem_alloc(len, KM_SLEEP); - - snprintf(buf, len, "/dev/%s", cp->provider->name); - spa_strfree(vd->vdev_path); - vd->vdev_path = buf; - - ZFS_LOG(1, "Attach by ID [%s] succeeded, provider %s.", - vd->vdev_devid, vd->vdev_path); + + return (cp); +} + +static struct g_consumer * +vdev_geom_open_by_devid(vdev_t *vd) +{ + struct g_consumer *cp; + char *buf; + size_t len; + + /* + * We can't search by devid if it's missing. + */ + if (vd->vdev_devid == NULL) + return (NULL); + + ZFS_LOG(1, "Searching by ID [%s].", vd->vdev_devid); + cp = vdev_geom_attach_by_id(vd->vdev_devid, !!(spa_mode & FWRITE)); + if (cp != NULL) { + len = strlen(cp->provider->name) + strlen("/dev/") + 1; + buf = kmem_alloc(len, KM_SLEEP); + + snprintf(buf, len, "/dev/%s", cp->provider->name); + spa_strfree(vd->vdev_path); + vd->vdev_path = buf; + + ZFS_LOG(1, "Attach by ID [%s] succeeded, provider %s.", + vd->vdev_devid, vd->vdev_path); + } else + ZFS_LOG(1, "Search by ID [%s] failed.", vd->vdev_devid); + + return (cp); +} + +static int +vdev_geom_open(vdev_t *vd, uint64_t *psize, uint64_t *ashift) +{ + vdev_geom_ctx_t *ctx; + struct g_provider *pp; + struct g_consumer *cp; + int owned; + + /* + * We must have a pathname, and it must be absolute. + */ + if (vd->vdev_path == NULL || vd->vdev_path[0] != '/') { + vd->vdev_stat.vs_aux = VDEV_AUX_BAD_LABEL; + return (EINVAL); + } + + vd->vdev_tsd = NULL; + + if ((owned = mtx_owned(&Giant))) + mtx_unlock(&Giant); + cp = vdev_geom_open_by_path_and_devid(vd); + if (cp == NULL) { + /* + * The device at vd->vdev_path doesn't have the right devid. + * The disks might have merely moved around so try all other + * geom providers to find one with the right devid. + */ + cp = vdev_geom_open_by_devid(vd); + if (cp == NULL) { + ZFS_LOG(1, "Provider %s not found.", vd->vdev_path); + vd->vdev_stat.vs_aux = VDEV_AUX_OPEN_FAILED; + if (owned) + mtx_lock(&Giant); + return (EACCES); } } if (owned) mtx_lock(&Giant); - if (cp == NULL) { - ZFS_LOG(1, "Provider %s (id=[%s]) not found.", vd->vdev_path, - vd->vdev_devid); - vd->vdev_stat.vs_aux = VDEV_AUX_OPEN_FAILED; - return (EACCES); - } + + cp->private = vd; + + ctx = kmem_zalloc(sizeof(*ctx), KM_SLEEP); + bioq_init(&ctx->gc_queue); + mtx_init(&ctx->gc_queue_mtx, "zfs:vdev:geom:queue", NULL, MTX_DEF); + ctx->gc_consumer = cp; + ctx->gc_state = 0; + + vd->vdev_tsd = ctx; pp = cp->provider; + kproc_create(vdev_geom_worker, ctx, NULL, 0, 0, "vdev:worker %s", + pp->name); + /* * Determine the actual size of the device. */ @@ -430,19 +699,6 @@ next: */ vd->vdev_nowritecache = B_FALSE; - cp->private = vd; - - ctx = kmem_zalloc(sizeof(*ctx), KM_SLEEP); - bioq_init(&ctx->gc_queue); - mtx_init(&ctx->gc_queue_mtx, "zfs:vdev:geom:queue", NULL, MTX_DEF); - ctx->gc_consumer = cp; - ctx->gc_state = 0; - - vd->vdev_tsd = ctx; - - kproc_create(vdev_geom_worker, ctx, NULL, 0, 0, "vdev:worker %s", - pp->name); - return (0); } @@ -469,13 +725,16 @@ vdev_geom_io_intr(struct bio *bp) zio = bp->bio_caller1; ctx = zio->io_vd->vdev_tsd; + if ((zio->io_error = bp->bio_error) == 0 && bp->bio_resid != 0) + zio->io_error = EIO; + mtx_lock(&ctx->gc_queue_mtx); bioq_insert_tail(&ctx->gc_queue, bp); wakeup_one(&ctx->gc_queue); mtx_unlock(&ctx->gc_queue_mtx); } -static void +static int vdev_geom_io_start(zio_t *zio) { vdev_t *vd; @@ -492,18 +751,19 @@ vdev_geom_io_start(zio_t *zio) cp = ctx->gc_consumer; if (zio->io_type == ZIO_TYPE_IOCTL) { - zio_vdev_io_bypass(zio); - /* XXPOLICY */ - if (vdev_is_dead(vd)) { + if (!vdev_readable(vd)) { zio->io_error = ENXIO; - zio_next_stage_async(zio); - return; + return (ZIO_PIPELINE_CONTINUE); } switch (zio->io_cmd) { case DKIOCFLUSHWRITECACHE: + + if (zfs_nocacheflush) + break; + if (vd->vdev_nowritecache) { zio->io_error = ENOTSUP; break; @@ -514,27 +774,13 @@ vdev_geom_io_start(zio_t *zio) zio->io_error = ENOTSUP; } - zio_next_stage_async(zio); - return; + return (ZIO_PIPELINE_CONTINUE); } - - if (zio->io_type == ZIO_TYPE_READ && vdev_cache_read(zio) == 0) - return; - - if ((zio = vdev_queue_io(zio)) == NULL) - return; - sendreq: - - error = vdev_is_dead(vd) ? ENXIO : vdev_error_inject(vd, zio); - if (error == 0 && cp == NULL) - error = ENXIO; - if (error) { - zio->io_error = error; - zio_next_stage_async(zio); - return; + if (cp == NULL) { + zio->io_error = ENXIO; + return (ZIO_PIPELINE_CONTINUE); } - bp = g_alloc_bio(); bp->bio_caller1 = zio; switch (zio->io_type) { @@ -555,20 +801,33 @@ sendreq: bp->bio_done = vdev_geom_io_intr; g_io_request(bp, cp); + + return (ZIO_PIPELINE_STOP); } static void vdev_geom_io_done(zio_t *zio) { - vdev_queue_io_done(zio); - - if (zio->io_type == ZIO_TYPE_WRITE) - vdev_cache_write(zio); - if (zio_injection_enabled && zio->io_error == 0) - zio->io_error = zio_handle_device_injection(zio->io_vd, EIO); - - zio_next_stage(zio); + /* + * If the device returned ENXIO, then attempt we should verify if GEOM + * provider has been removed. If this is the case, then we trigger an + * asynchronous removal of the device. + */ + if (zio->io_error == ENXIO) { + vdev_t *vd = zio->io_vd; + vdev_geom_ctx_t *ctx; + struct g_provider *pp = NULL; + + ctx = vd->vdev_tsd; + if (ctx != NULL && ctx->gc_consumer != NULL) + pp = ctx->gc_consumer->provider; + + if (pp == NULL || (pp->flags & G_PF_ORPHAN)) { + vd->vdev_remove_wanted = B_TRUE; + spa_async_request(zio->io_spa, SPA_ASYNC_REMOVE); + } + } } vdev_ops_t vdev_geom_ops = { |