diff options
Diffstat (limited to 'sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zvol.c')
-rw-r--r-- | sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zvol.c | 715 |
1 files changed, 653 insertions, 62 deletions
diff --git a/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zvol.c b/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zvol.c index fedae03..db0ebf2 100644 --- a/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zvol.c +++ b/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zvol.c @@ -23,12 +23,10 @@ * All rights reserved. */ /* - * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ -#pragma ident "%Z%%M% %I% %E% SMI" - /* * ZFS volume emulation driver. * @@ -57,6 +55,9 @@ #include <sys/zap.h> #include <sys/spa.h> #include <sys/zio.h> +#include <sys/dmu_traverse.h> +#include <sys/dnode.h> +#include <sys/dsl_dataset.h> #include <sys/dsl_prop.h> #include <sys/dkio.h> #include <sys/byteorder.h> @@ -69,10 +70,14 @@ #include <sys/refcount.h> #include <sys/zfs_znode.h> #include <sys/zfs_rlock.h> +#include <sys/vdev_impl.h> +#include <sys/zvol.h> #include <geom/geom.h> #include "zfs_namecheck.h" +#define ZVOL_DUMPSIZE "dumpsize" + struct g_class zfs_zvol_class = { .name = "ZFS::ZVOL", .version = G_VERSION, @@ -80,11 +85,31 @@ struct g_class zfs_zvol_class = { DECLARE_GEOM_CLASS(zfs_zvol_class, zfs_zvol); -#define ZVOL_OBJ 1ULL -#define ZVOL_ZAP_OBJ 2ULL - +/* + * This lock protects the zvol_state structure from being modified + * while it's being used, e.g. an open that comes in before a create + * finishes. It also protects temporary opens of the dataset so that, + * e.g., an open doesn't get a spurious EBUSY. + */ +static kmutex_t zvol_state_lock; static uint32_t zvol_minors; +#define NUM_EXTENTS ((SPA_MAXBLOCKSIZE) / sizeof (zvol_extent_t)) + +typedef struct zvol_extent { + dva_t ze_dva; /* dva associated with this extent */ + uint64_t ze_stride; /* extent stride */ + uint64_t ze_size; /* number of blocks in extent */ +} zvol_extent_t; + +/* + * The list of extents associated with the dump device + */ +typedef struct zvol_ext_list { + zvol_extent_t zl_extents[NUM_EXTENTS]; + struct zvol_ext_list *zl_next; +} zvol_ext_list_t; + /* * The in-core state of each volume. */ @@ -94,11 +119,12 @@ typedef struct zvol_state { uint64_t zv_volblocksize; /* volume block size */ struct g_provider *zv_provider; /* GEOM provider */ uint8_t zv_min_bs; /* minimum addressable block shift */ - uint8_t zv_readonly; /* hard readonly; like write-protect */ + uint8_t zv_flags; /* readonly; dumpified */ objset_t *zv_objset; /* objset handle */ uint32_t zv_mode; /* DS_MODE_* flags at open time */ uint32_t zv_total_opens; /* total open count */ zilog_t *zv_zilog; /* ZIL handle */ + zvol_ext_list_t *zv_list; /* List of extents for dump */ uint64_t zv_txg_assign; /* txg to assign during ZIL replay */ znode_t zv_znode; /* for range locking */ int zv_state; @@ -107,11 +133,28 @@ typedef struct zvol_state { } zvol_state_t; /* + * zvol specific flags + */ +#define ZVOL_RDONLY 0x1 +#define ZVOL_DUMPIFIED 0x2 +#define ZVOL_EXCL 0x4 + +/* * zvol maximum transfer in one DMU tx. */ int zvol_maxphys = DMU_MAX_ACCESS/2; +extern int zfs_set_prop_nvlist(const char *, nvlist_t *); static int zvol_get_data(void *arg, lr_write_t *lr, char *buf, zio_t *zio); +static int zvol_dumpify(zvol_state_t *zv); +static int zvol_dump_fini(zvol_state_t *zv); +static int zvol_dump_init(zvol_state_t *zv, boolean_t resize); + +static void +zvol_size_changed(zvol_state_t *zv, major_t maj) +{ + +} int zvol_check_volsize(uint64_t volsize, uint64_t blocksize) @@ -145,7 +188,10 @@ zvol_readonly_changed_cb(void *arg, uint64_t newval) { zvol_state_t *zv = arg; - zv->zv_readonly = (uint8_t)newval; + if (newval) + zv->zv_flags |= ZVOL_RDONLY; + else + zv->zv_flags &= ~ZVOL_RDONLY; } int @@ -179,6 +225,7 @@ zvol_minor_lookup(const char *name) struct g_geom *gp; g_topology_assert(); + ASSERT(MUTEX_HELD(&zvol_state_lock)); LIST_FOREACH(gp, &zfs_zvol_class.geom, geom) { LIST_FOREACH(pp, &gp->provider, provider) { @@ -196,21 +243,29 @@ zvol_access(struct g_provider *pp, int acr, int acw, int ace) zvol_state_t *zv; g_topology_assert(); + mutex_enter(&zvol_state_lock); zv = pp->private; if (zv == NULL) { if (acr <= 0 && acw <= 0 && ace <= 0) return (0); + mutex_exit(&zvol_state_lock); return (pp->error); } ASSERT(zv->zv_objset != NULL); - if (acw > 0 && (zv->zv_readonly || (zv->zv_mode & DS_MODE_READONLY))) + if (acw > 0 && + ((zv->zv_flags & ZVOL_RDONLY) || + (zv->zv_mode & DS_MODE_READONLY))) { + mutex_exit(&zvol_state_lock); return (EROFS); + } zv->zv_total_opens += acr + acw + ace; + mutex_exit(&zvol_state_lock); + return (0); } @@ -324,8 +379,12 @@ zvol_serve_one(zvol_state_t *zv, struct bio *bp) dmu_tx_commit(tx); } } - if (error) + if (error) { + /* convert checksum errors into IO errors */ + if (error == ECKSUM) + error = EIO; break; + } off += size; addr += size; resid -= size; @@ -368,7 +427,7 @@ zvol_worker(void *arg) break; } - if (bp->bio_cmd != BIO_READ && !zil_disable) + if (bp->bio_cmd == BIO_FLUSH && !zil_disable) zil_commit(zv->zv_zilog, UINT64_MAX, ZVOL_OBJ); g_io_deliver(bp, bp->bio_error); @@ -376,25 +435,152 @@ zvol_worker(void *arg) } void -zvol_create_cb(objset_t *os, void *arg, dmu_tx_t *tx) +zvol_init_extent(zvol_extent_t *ze, blkptr_t *bp) { - zfs_create_data_t *zc = arg; + ze->ze_dva = bp->blk_dva[0]; /* structure assignment */ + ze->ze_stride = 0; + ze->ze_size = 1; +} + +/* extent mapping arg */ +struct maparg { + zvol_ext_list_t *ma_list; + zvol_extent_t *ma_extent; + int ma_gang; +}; + +/*ARGSUSED*/ +static int +zvol_map_block(traverse_blk_cache_t *bc, spa_t *spa, void *arg) +{ + zbookmark_t *zb = &bc->bc_bookmark; + blkptr_t *bp = &bc->bc_blkptr; + void *data = bc->bc_data; + dnode_phys_t *dnp = bc->bc_dnode; + struct maparg *ma = (struct maparg *)arg; + uint64_t stride; + + /* If there is an error, then keep trying to make progress */ + if (bc->bc_errno) + return (ERESTART); + +#ifdef ZFS_DEBUG + if (zb->zb_level == -1) { + ASSERT3U(BP_GET_TYPE(bp), ==, DMU_OT_OBJSET); + ASSERT3U(BP_GET_LEVEL(bp), ==, 0); + } else { + ASSERT3U(BP_GET_TYPE(bp), ==, dnp->dn_type); + ASSERT3U(BP_GET_LEVEL(bp), ==, zb->zb_level); + } + + if (zb->zb_level > 0) { + uint64_t fill = 0; + blkptr_t *bpx, *bpend; + + for (bpx = data, bpend = bpx + BP_GET_LSIZE(bp) / sizeof (*bpx); + bpx < bpend; bpx++) { + if (bpx->blk_birth != 0) { + fill += bpx->blk_fill; + } else { + ASSERT(bpx->blk_fill == 0); + } + } + ASSERT3U(fill, ==, bp->blk_fill); + } + + if (zb->zb_level == 0 && dnp->dn_type == DMU_OT_DNODE) { + uint64_t fill = 0; + dnode_phys_t *dnx, *dnend; + + for (dnx = data, dnend = dnx + (BP_GET_LSIZE(bp)>>DNODE_SHIFT); + dnx < dnend; dnx++) { + if (dnx->dn_type != DMU_OT_NONE) + fill++; + } + ASSERT3U(fill, ==, bp->blk_fill); + } +#endif + + if (zb->zb_level || dnp->dn_type == DMU_OT_DNODE) + return (0); + + /* Abort immediately if we have encountered gang blocks */ + if (BP_IS_GANG(bp)) { + ma->ma_gang++; + return (EINTR); + } + + /* first time? */ + if (ma->ma_extent->ze_size == 0) { + zvol_init_extent(ma->ma_extent, bp); + return (0); + } + + stride = (DVA_GET_OFFSET(&bp->blk_dva[0])) - + ((DVA_GET_OFFSET(&ma->ma_extent->ze_dva)) + + (ma->ma_extent->ze_size - 1) * (ma->ma_extent->ze_stride)); + if (DVA_GET_VDEV(BP_IDENTITY(bp)) == + DVA_GET_VDEV(&ma->ma_extent->ze_dva)) { + if (ma->ma_extent->ze_stride == 0) { + /* second block in this extent */ + ma->ma_extent->ze_stride = stride; + ma->ma_extent->ze_size++; + return (0); + } else if (ma->ma_extent->ze_stride == stride) { + /* + * the block we allocated has the same + * stride + */ + ma->ma_extent->ze_size++; + return (0); + } + } + + /* + * dtrace -n 'zfs-dprintf + * /stringof(arg0) == "zvol.c"/ + * { + * printf("%s: %s", stringof(arg1), stringof(arg3)) + * } ' + */ + dprintf("ma_extent 0x%lx mrstride 0x%lx stride %lx\n", + ma->ma_extent->ze_size, ma->ma_extent->ze_stride, stride); + dprintf_bp(bp, "%s", "next blkptr:"); + /* start a new extent */ + if (ma->ma_extent == &ma->ma_list->zl_extents[NUM_EXTENTS - 1]) { + ma->ma_list->zl_next = kmem_zalloc(sizeof (zvol_ext_list_t), + KM_SLEEP); + ma->ma_list = ma->ma_list->zl_next; + ma->ma_extent = &ma->ma_list->zl_extents[0]; + } else { + ma->ma_extent++; + } + zvol_init_extent(ma->ma_extent, bp); + return (0); +} + +/* ARGSUSED */ +void +zvol_create_cb(objset_t *os, void *arg, cred_t *cr, dmu_tx_t *tx) +{ + zfs_creat_t *zct = arg; + nvlist_t *nvprops = zct->zct_props; int error; uint64_t volblocksize, volsize; - VERIFY(nvlist_lookup_uint64(zc->zc_props, + VERIFY(nvlist_lookup_uint64(nvprops, zfs_prop_to_name(ZFS_PROP_VOLSIZE), &volsize) == 0); - if (nvlist_lookup_uint64(zc->zc_props, + if (nvlist_lookup_uint64(nvprops, zfs_prop_to_name(ZFS_PROP_VOLBLOCKSIZE), &volblocksize) != 0) volblocksize = zfs_prop_default_numeric(ZFS_PROP_VOLBLOCKSIZE); /* - * These properites must be removed from the list so the generic + * These properties must be removed from the list so the generic * property setting step won't apply to them. */ - VERIFY(nvlist_remove_all(zc->zc_props, + VERIFY(nvlist_remove_all(nvprops, zfs_prop_to_name(ZFS_PROP_VOLSIZE)) == 0); - (void) nvlist_remove_all(zc->zc_props, + (void) nvlist_remove_all(nvprops, zfs_prop_to_name(ZFS_PROP_VOLBLOCKSIZE)); error = dmu_object_claim(os, ZVOL_OBJ, DMU_OT_ZVOL, volblocksize, @@ -467,10 +653,110 @@ zil_replay_func_t *zvol_replay_vector[TX_MAX_TYPE] = { }; /* - * Create a minor node for the specified volume. + * reconstruct dva that gets us to the desired offset (offset + * is in bytes) */ int -zvol_create_minor(const char *name, dev_t dev) +zvol_get_dva(zvol_state_t *zv, uint64_t offset, dva_t *dva) +{ + zvol_ext_list_t *zl; + zvol_extent_t *ze; + int idx; + uint64_t tmp; + + if ((zl = zv->zv_list) == NULL) + return (EIO); + idx = 0; + ze = &zl->zl_extents[0]; + while (offset >= ze->ze_size * zv->zv_volblocksize) { + offset -= ze->ze_size * zv->zv_volblocksize; + + if (idx == NUM_EXTENTS - 1) { + /* we've reached the end of this array */ + ASSERT(zl->zl_next != NULL); + if (zl->zl_next == NULL) + return (-1); + zl = zl->zl_next; + ze = &zl->zl_extents[0]; + idx = 0; + } else { + ze++; + idx++; + } + } + DVA_SET_VDEV(dva, DVA_GET_VDEV(&ze->ze_dva)); + tmp = DVA_GET_OFFSET((&ze->ze_dva)); + tmp += (ze->ze_stride * (offset / zv->zv_volblocksize)); + DVA_SET_OFFSET(dva, tmp); + return (0); +} + +static void +zvol_free_extents(zvol_state_t *zv) +{ + zvol_ext_list_t *zl; + zvol_ext_list_t *tmp; + + if (zv->zv_list != NULL) { + zl = zv->zv_list; + while (zl != NULL) { + tmp = zl->zl_next; + kmem_free(zl, sizeof (zvol_ext_list_t)); + zl = tmp; + } + zv->zv_list = NULL; + } +} + +int +zvol_get_lbas(zvol_state_t *zv) +{ + struct maparg ma; + zvol_ext_list_t *zl; + zvol_extent_t *ze; + uint64_t blocks = 0; + int err; + + ma.ma_list = zl = kmem_zalloc(sizeof (zvol_ext_list_t), KM_SLEEP); + ma.ma_extent = &ma.ma_list->zl_extents[0]; + ma.ma_gang = 0; + zv->zv_list = ma.ma_list; + + err = traverse_zvol(zv->zv_objset, ADVANCE_PRE, zvol_map_block, &ma); + if (err == EINTR && ma.ma_gang) { + /* + * We currently don't support dump devices when the pool + * is so fragmented that our allocation has resulted in + * gang blocks. + */ + zvol_free_extents(zv); + return (EFRAGS); + } + ASSERT3U(err, ==, 0); + + ze = &zl->zl_extents[0]; + while (ze) { + blocks += ze->ze_size; + if (ze == &zl->zl_extents[NUM_EXTENTS - 1]) { + zl = zl->zl_next; + ze = &zl->zl_extents[0]; + } else { + ze++; + } + } + if (blocks != (zv->zv_volsize / zv->zv_volblocksize)) { + zvol_free_extents(zv); + return (EIO); + } + + return (0); +} + +/* + * Create a minor node (plus a whole lot more) for the specified volume. + */ +int +zvol_create_minor(const char *name, major_t maj) { struct g_provider *pp; struct g_geom *gp; @@ -478,11 +764,12 @@ zvol_create_minor(const char *name, dev_t dev) objset_t *os; dmu_object_info_t doi; uint64_t volsize; - int ds_mode = DS_MODE_PRIMARY; + int ds_mode = DS_MODE_OWNER; int error; DROP_GIANT(); g_topology_lock(); + mutex_enter(&zvol_state_lock); if ((zv = zvol_minor_lookup(name)) != NULL) { error = EEXIST; @@ -496,11 +783,7 @@ zvol_create_minor(const char *name, dev_t dev) if (error) goto end; - g_topology_unlock(); - PICKUP_GIANT(); error = zap_lookup(os, ZVOL_ZAP_OBJ, "size", 8, 1, &volsize); - DROP_GIANT(); - g_topology_lock(); if (error) { dmu_objset_close(os); goto end; @@ -524,14 +807,12 @@ zvol_create_minor(const char *name, dev_t dev) mutex_init(&zv->zv_znode.z_range_lock, NULL, MUTEX_DEFAULT, NULL); avl_create(&zv->zv_znode.z_range_avl, zfs_range_compare, sizeof (rl_t), offsetof(rl_t, r_node)); - - /* get and cache the blocksize */ error = dmu_object_info(os, ZVOL_OBJ, &doi); ASSERT(error == 0); zv->zv_volblocksize = doi.doi_data_block_size; - zil_replay(os, zv, &zv->zv_txg_assign, zvol_replay_vector); + zil_replay(os, zv, &zv->zv_txg_assign, zvol_replay_vector, NULL); /* XXX this should handle the possible i/o error */ VERIFY(dsl_prop_register(dmu_objset_ds(zv->zv_objset), @@ -547,6 +828,7 @@ zvol_create_minor(const char *name, dev_t dev) zvol_minors++; end: + mutex_exit(&zvol_state_lock); g_topology_unlock(); PICKUP_GIANT(); @@ -565,6 +847,7 @@ zvol_remove_minor(const char *name) DROP_GIANT(); g_topology_lock(); + mutex_enter(&zvol_state_lock); if ((zv = zvol_minor_lookup(name)) == NULL) { error = ENXIO; @@ -602,6 +885,7 @@ zvol_remove_minor(const char *name) zvol_minors--; end: + mutex_exit(&zvol_state_lock); g_topology_unlock(); PICKUP_GIANT(); @@ -609,55 +893,143 @@ end: } int -zvol_set_volsize(const char *name, dev_t dev, uint64_t volsize) +zvol_prealloc(zvol_state_t *zv) +{ + objset_t *os = zv->zv_objset; + dmu_tx_t *tx; + void *data; + uint64_t refd, avail, usedobjs, availobjs; + uint64_t resid = zv->zv_volsize; + uint64_t off = 0; + + /* Check the space usage before attempting to allocate the space */ + dmu_objset_space(os, &refd, &avail, &usedobjs, &availobjs); + if (avail < zv->zv_volsize) + return (ENOSPC); + + /* Free old extents if they exist */ + zvol_free_extents(zv); + + /* allocate the blocks by writing each one */ + data = kmem_zalloc(SPA_MAXBLOCKSIZE, KM_SLEEP); + + while (resid != 0) { + int error; + uint64_t bytes = MIN(resid, SPA_MAXBLOCKSIZE); + + tx = dmu_tx_create(os); + dmu_tx_hold_write(tx, ZVOL_OBJ, off, bytes); + error = dmu_tx_assign(tx, TXG_WAIT); + if (error) { + dmu_tx_abort(tx); + kmem_free(data, SPA_MAXBLOCKSIZE); + (void) dmu_free_long_range(os, ZVOL_OBJ, 0, off); + return (error); + } + dmu_write(os, ZVOL_OBJ, off, bytes, data, tx); + dmu_tx_commit(tx); + off += bytes; + resid -= bytes; + } + kmem_free(data, SPA_MAXBLOCKSIZE); + txg_wait_synced(dmu_objset_pool(os), 0); + + return (0); +} + +int +zvol_update_volsize(zvol_state_t *zv, major_t maj, uint64_t volsize) { - zvol_state_t *zv; dmu_tx_t *tx; int error; + + ASSERT(MUTEX_HELD(&zvol_state_lock)); + + tx = dmu_tx_create(zv->zv_objset); + dmu_tx_hold_zap(tx, ZVOL_ZAP_OBJ, TRUE, NULL); + error = dmu_tx_assign(tx, TXG_WAIT); + if (error) { + dmu_tx_abort(tx); + return (error); + } + + error = zap_update(zv->zv_objset, ZVOL_ZAP_OBJ, "size", 8, 1, + &volsize, tx); + dmu_tx_commit(tx); + + if (error == 0) + error = dmu_free_long_range(zv->zv_objset, + ZVOL_OBJ, volsize, DMU_OBJECT_END); + + /* + * If we are using a faked-up state (zv_provider == NULL) then don't + * try to update the in-core zvol state. + */ + if (error == 0 && zv->zv_provider) { + zv->zv_volsize = volsize; + zvol_size_changed(zv, maj); + } + return (error); +} + +int +zvol_set_volsize(const char *name, major_t maj, uint64_t volsize) +{ + zvol_state_t *zv; + int error; dmu_object_info_t doi; + uint64_t old_volsize = 0ULL; + zvol_state_t state = { 0 }; DROP_GIANT(); g_topology_lock(); + mutex_enter(&zvol_state_lock); if ((zv = zvol_minor_lookup(name)) == NULL) { - error = ENXIO; - goto end; + /* + * If we are doing a "zfs clone -o volsize=", then the + * minor node won't exist yet. + */ + error = dmu_objset_open(name, DMU_OST_ZVOL, DS_MODE_OWNER, + &state.zv_objset); + if (error != 0) + goto out; + zv = &state; } + old_volsize = zv->zv_volsize; if ((error = dmu_object_info(zv->zv_objset, ZVOL_OBJ, &doi)) != 0 || (error = zvol_check_volsize(volsize, - doi.doi_data_block_size)) != 0) { - goto end; - } + doi.doi_data_block_size)) != 0) + goto out; - if (zv->zv_readonly || (zv->zv_mode & DS_MODE_READONLY)) { + if (zv->zv_flags & ZVOL_RDONLY || (zv->zv_mode & DS_MODE_READONLY)) { error = EROFS; - goto end; + goto out; } - tx = dmu_tx_create(zv->zv_objset); - dmu_tx_hold_zap(tx, ZVOL_ZAP_OBJ, TRUE, NULL); - dmu_tx_hold_free(tx, ZVOL_OBJ, volsize, DMU_OBJECT_END); - error = dmu_tx_assign(tx, TXG_WAIT); - if (error) { - dmu_tx_abort(tx); - goto end; - } + error = zvol_update_volsize(zv, maj, volsize); - error = zap_update(zv->zv_objset, ZVOL_ZAP_OBJ, "size", 8, 1, - &volsize, tx); - if (error == 0) { - error = dmu_free_range(zv->zv_objset, ZVOL_OBJ, volsize, - DMU_OBJECT_END, tx); +#if 0 + /* + * Reinitialize the dump area to the new size. If we + * failed to resize the dump area then restore the it back to + * it's original size. + */ + if (error == 0 && zv->zv_flags & ZVOL_DUMPIFIED) { + if ((error = zvol_dumpify(zv)) != 0 || + (error = dumpvp_resize()) != 0) { + (void) zvol_update_volsize(zv, maj, old_volsize); + error = zvol_dumpify(zv); + } } +#endif - dmu_tx_commit(tx); +out: + if (state.zv_objset) + dmu_objset_close(state.zv_objset); - if (error == 0) { - zv->zv_volsize = volsize; - zv->zv_provider->mediasize = volsize; /* XXX: Not supported. */ - } -end: + mutex_exit(&zvol_state_lock); g_topology_unlock(); PICKUP_GIANT(); @@ -673,13 +1045,13 @@ zvol_set_volblocksize(const char *name, uint64_t volblocksize) DROP_GIANT(); g_topology_lock(); + mutex_enter(&zvol_state_lock); if ((zv = zvol_minor_lookup(name)) == NULL) { error = ENXIO; goto end; } - - if (zv->zv_readonly || (zv->zv_mode & DS_MODE_READONLY)) { + if (zv->zv_flags & ZVOL_RDONLY || (zv->zv_mode & DS_MODE_READONLY)) { error = EROFS; goto end; } @@ -702,6 +1074,7 @@ zvol_set_volblocksize(const char *name, uint64_t volblocksize) #endif } end: + mutex_exit(&zvol_state_lock); g_topology_unlock(); PICKUP_GIANT(); @@ -716,7 +1089,7 @@ zvol_get_done(dmu_buf_t *db, void *vzgd) dmu_buf_rele(db, vzgd); zfs_range_unlock(rl); - zil_add_vdev(zgd->zgd_zilog, DVA_GET_VDEV(BP_IDENTITY(zgd->zgd_bp))); + zil_add_block(zgd->zgd_zilog, zgd->zgd_bp); kmem_free(zgd, sizeof (zgd_t)); } @@ -754,7 +1127,7 @@ zvol_get_data(void *arg, lr_write_t *lr, char *buf, zio_t *zio) /* * Lock the range of the block to ensure that when the data is - * written out and it's checksum is being calculated that no other + * written out and its checksum is being calculated that no other * thread can change the block. */ boff = P2ALIGN_TYPED(lr->lr_offset, zv->zv_volblocksize, uint64_t); @@ -766,8 +1139,7 @@ zvol_get_data(void *arg, lr_write_t *lr, char *buf, zio_t *zio) error = dmu_sync(zio, db, &lr->lr_blkptr, lr->lr_common.lrc_txg, zvol_get_done, zgd); if (error == 0) - zil_add_vdev(zv->zv_zilog, - DVA_GET_VDEV(BP_IDENTITY(&lr->lr_blkptr))); + zil_add_block(zv->zv_zilog, &lr->lr_blkptr); /* * If we get EINPROGRESS, then we need to wait for a * write IO initiated by dmu_sync() to complete before @@ -791,11 +1163,230 @@ zvol_busy(void) void zvol_init(void) { + mutex_init(&zvol_state_lock, NULL, MUTEX_DEFAULT, NULL); ZFS_LOG(1, "ZVOL Initialized."); } void zvol_fini(void) { + mutex_destroy(&zvol_state_lock); ZFS_LOG(1, "ZVOL Deinitialized."); } + +static boolean_t +zvol_is_swap(zvol_state_t *zv) +{ + vnode_t *vp; + boolean_t ret = B_FALSE; + char *devpath; + size_t devpathlen; + int error; + +#if 0 + devpathlen = strlen(ZVOL_FULL_DEV_DIR) + strlen(zv->zv_name) + 1; + devpath = kmem_alloc(devpathlen, KM_SLEEP); + (void) sprintf(devpath, "%s%s", ZVOL_FULL_DEV_DIR, zv->zv_name); + error = lookupname(devpath, UIO_SYSSPACE, FOLLOW, NULLVPP, &vp); + kmem_free(devpath, devpathlen); + + ret = !error && IS_SWAPVP(common_specvp(vp)); + + if (vp != NULL) + VN_RELE(vp); +#endif + + return (ret); +} + +static int +zvol_dump_init(zvol_state_t *zv, boolean_t resize) +{ + dmu_tx_t *tx; + int error = 0; + objset_t *os = zv->zv_objset; + nvlist_t *nv = NULL; + uint64_t checksum, compress, refresrv; + + ASSERT(MUTEX_HELD(&zvol_state_lock)); + + tx = dmu_tx_create(os); + dmu_tx_hold_zap(tx, ZVOL_ZAP_OBJ, TRUE, NULL); + error = dmu_tx_assign(tx, TXG_WAIT); + if (error) { + dmu_tx_abort(tx); + return (error); + } + + /* + * If we are resizing the dump device then we only need to + * update the refreservation to match the newly updated + * zvolsize. Otherwise, we save off the original state of the + * zvol so that we can restore them if the zvol is ever undumpified. + */ + if (resize) { + error = zap_update(os, ZVOL_ZAP_OBJ, + zfs_prop_to_name(ZFS_PROP_REFRESERVATION), 8, 1, + &zv->zv_volsize, tx); + } else { + error = dsl_prop_get_integer(zv->zv_name, + zfs_prop_to_name(ZFS_PROP_COMPRESSION), &compress, NULL); + error = error ? error : dsl_prop_get_integer(zv->zv_name, + zfs_prop_to_name(ZFS_PROP_CHECKSUM), &checksum, NULL); + error = error ? error : dsl_prop_get_integer(zv->zv_name, + zfs_prop_to_name(ZFS_PROP_REFRESERVATION), &refresrv, NULL); + + error = error ? error : zap_update(os, ZVOL_ZAP_OBJ, + zfs_prop_to_name(ZFS_PROP_COMPRESSION), 8, 1, + &compress, tx); + error = error ? error : zap_update(os, ZVOL_ZAP_OBJ, + zfs_prop_to_name(ZFS_PROP_CHECKSUM), 8, 1, &checksum, tx); + error = error ? error : zap_update(os, ZVOL_ZAP_OBJ, + zfs_prop_to_name(ZFS_PROP_REFRESERVATION), 8, 1, + &refresrv, tx); + } + dmu_tx_commit(tx); + + /* Truncate the file */ + if (!error) + error = dmu_free_long_range(zv->zv_objset, + ZVOL_OBJ, 0, DMU_OBJECT_END); + + if (error) + return (error); + + /* + * We only need update the zvol's property if we are initializing + * the dump area for the first time. + */ + if (!resize) { + VERIFY(nvlist_alloc(&nv, NV_UNIQUE_NAME, KM_SLEEP) == 0); + VERIFY(nvlist_add_uint64(nv, + zfs_prop_to_name(ZFS_PROP_REFRESERVATION), 0) == 0); + VERIFY(nvlist_add_uint64(nv, + zfs_prop_to_name(ZFS_PROP_COMPRESSION), + ZIO_COMPRESS_OFF) == 0); + VERIFY(nvlist_add_uint64(nv, + zfs_prop_to_name(ZFS_PROP_CHECKSUM), + ZIO_CHECKSUM_OFF) == 0); + + error = zfs_set_prop_nvlist(zv->zv_name, nv); + nvlist_free(nv); + + if (error) + return (error); + } + + /* Allocate the space for the dump */ + error = zvol_prealloc(zv); + return (error); +} + +static int +zvol_dumpify(zvol_state_t *zv) +{ + int error = 0; + uint64_t dumpsize = 0; + dmu_tx_t *tx; + objset_t *os = zv->zv_objset; + + if (zv->zv_flags & ZVOL_RDONLY || (zv->zv_mode & DS_MODE_READONLY)) + return (EROFS); + + /* + * We do not support swap devices acting as dump devices. + */ + if (zvol_is_swap(zv)) + return (ENOTSUP); + + if (zap_lookup(zv->zv_objset, ZVOL_ZAP_OBJ, ZVOL_DUMPSIZE, + 8, 1, &dumpsize) != 0 || dumpsize != zv->zv_volsize) { + boolean_t resize = (dumpsize > 0) ? B_TRUE : B_FALSE; + + if ((error = zvol_dump_init(zv, resize)) != 0) { + (void) zvol_dump_fini(zv); + return (error); + } + } + + /* + * Build up our lba mapping. + */ + error = zvol_get_lbas(zv); + if (error) { + (void) zvol_dump_fini(zv); + return (error); + } + + tx = dmu_tx_create(os); + dmu_tx_hold_zap(tx, ZVOL_ZAP_OBJ, TRUE, NULL); + error = dmu_tx_assign(tx, TXG_WAIT); + if (error) { + dmu_tx_abort(tx); + (void) zvol_dump_fini(zv); + return (error); + } + + zv->zv_flags |= ZVOL_DUMPIFIED; + error = zap_update(os, ZVOL_ZAP_OBJ, ZVOL_DUMPSIZE, 8, 1, + &zv->zv_volsize, tx); + dmu_tx_commit(tx); + + if (error) { + (void) zvol_dump_fini(zv); + return (error); + } + + txg_wait_synced(dmu_objset_pool(os), 0); + return (0); +} + +static int +zvol_dump_fini(zvol_state_t *zv) +{ + dmu_tx_t *tx; + objset_t *os = zv->zv_objset; + nvlist_t *nv; + int error = 0; + uint64_t checksum, compress, refresrv; + + /* + * Attempt to restore the zvol back to its pre-dumpified state. + * This is a best-effort attempt as it's possible that not all + * of these properties were initialized during the dumpify process + * (i.e. error during zvol_dump_init). + */ + + tx = dmu_tx_create(os); + dmu_tx_hold_zap(tx, ZVOL_ZAP_OBJ, TRUE, NULL); + error = dmu_tx_assign(tx, TXG_WAIT); + if (error) { + dmu_tx_abort(tx); + return (error); + } + (void) zap_remove(os, ZVOL_ZAP_OBJ, ZVOL_DUMPSIZE, tx); + dmu_tx_commit(tx); + + (void) zap_lookup(zv->zv_objset, ZVOL_ZAP_OBJ, + zfs_prop_to_name(ZFS_PROP_CHECKSUM), 8, 1, &checksum); + (void) zap_lookup(zv->zv_objset, ZVOL_ZAP_OBJ, + zfs_prop_to_name(ZFS_PROP_COMPRESSION), 8, 1, &compress); + (void) zap_lookup(zv->zv_objset, ZVOL_ZAP_OBJ, + zfs_prop_to_name(ZFS_PROP_REFRESERVATION), 8, 1, &refresrv); + + VERIFY(nvlist_alloc(&nv, NV_UNIQUE_NAME, KM_SLEEP) == 0); + (void) nvlist_add_uint64(nv, + zfs_prop_to_name(ZFS_PROP_CHECKSUM), checksum); + (void) nvlist_add_uint64(nv, + zfs_prop_to_name(ZFS_PROP_COMPRESSION), compress); + (void) nvlist_add_uint64(nv, + zfs_prop_to_name(ZFS_PROP_REFRESERVATION), refresrv); + (void) zfs_set_prop_nvlist(zv->zv_name, nv); + nvlist_free(nv); + + zvol_free_extents(zv); + zv->zv_flags &= ~ZVOL_DUMPIFIED; + (void) dmu_free_long_range(os, ZVOL_OBJ, 0, DMU_OBJECT_END); + + return (0); +} |