usr/src/uts/common/fs/zfs/zvol.c
changeset 6423 437422a29d3a
parent 5688 c0b02c8fd2c0
child 6689 47572a2f5e73
--- a/usr/src/uts/common/fs/zfs/zvol.c	Fri Apr 11 16:15:33 2008 -0700
+++ b/usr/src/uts/common/fs/zfs/zvol.c	Fri Apr 11 18:36:28 2008 -0700
@@ -19,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -53,6 +53,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/efi_partition.h>
@@ -70,13 +73,16 @@
 #include <sys/refcount.h>
 #include <sys/zfs_znode.h>
 #include <sys/zfs_rlock.h>
+#include <sys/vdev_disk.h>
+#include <sys/vdev_impl.h>
+#include <sys/zvol.h>
+#include <sys/dumphdr.h>
 
 #include "zfs_namecheck.h"
 
-#define	ZVOL_OBJ		1ULL
-#define	ZVOL_ZAP_OBJ		2ULL
+static void *zvol_state;
 
-static void *zvol_state;
+#define	ZVOL_DUMPSIZE		"dumpsize"
 
 /*
  * This lock protects the zvol_state structure from being modified
@@ -87,6 +93,22 @@
 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.
  */
@@ -96,22 +118,33 @@
 	uint64_t	zv_volblocksize; /* volume block size */
 	minor_t		zv_minor;	/* minor number */
 	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_open_count[OTYPCNT];	/* open counts */
 	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 */
 } zvol_state_t;
 
 /*
+ * zvol specific flags
+ */
+#define	ZVOL_RDONLY	0x1
+#define	ZVOL_DUMPIFIED	0x2
+
+/*
  * 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)
@@ -122,6 +155,10 @@
 	    "Size", zv->zv_volsize) == DDI_SUCCESS);
 	VERIFY(ddi_prop_update_int64(dev, zfs_dip,
 	    "Nblocks", lbtodb(zv->zv_volsize)) == DDI_SUCCESS);
+
+	/* Notify specfs to invalidate the cached size */
+	spec_size_invalidate(dev, VBLK);
+	spec_size_invalidate(dev, VCHR);
 }
 
 int
@@ -156,7 +193,10 @@
 {
 	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
@@ -219,6 +259,131 @@
 	return (zv);
 }
 
+void
+zvol_init_extent(zvol_extent_t *ze, blkptr_t *bp)
+{
+	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)
@@ -235,7 +400,7 @@
 		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(nvprops,
@@ -313,7 +478,107 @@
 };
 
 /*
- * Create a minor node for the specified volume.
+ * reconstruct dva that gets us to the desired offset (offset
+ * is in bytes)
+ */
+int
+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)
@@ -327,7 +592,7 @@
 	int ds_mode = DS_MODE_PRIMARY;
 	vnode_t *vp = NULL;
 	char *devpath;
-	size_t devpathlen = strlen(ZVOL_FULL_DEV_DIR) + 1 + strlen(name) + 1;
+	size_t devpathlen = strlen(ZVOL_FULL_DEV_DIR) + strlen(name) + 1;
 	char chrbuf[30], blkbuf[30];
 	int error;
 
@@ -362,7 +627,7 @@
 	 */
 	devpath = kmem_alloc(devpathlen, KM_SLEEP);
 
-	(void) sprintf(devpath, "%s/%s", ZVOL_FULL_DEV_DIR, name);
+	(void) sprintf(devpath, "%s%s", ZVOL_FULL_DEV_DIR, name);
 
 	error = lookupname(devpath, UIO_SYSSPACE, NO_FOLLOW, NULL, &vp);
 
@@ -444,15 +709,12 @@
 	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);
-
 	zvol_size_changed(zv, maj);
 
 	/* XXX this should handle the possible i/o error */
@@ -512,13 +774,107 @@
 	return (0);
 }
 
+static int
+zvol_truncate(zvol_state_t *zv, uint64_t offset, uint64_t size)
+{
+	dmu_tx_t *tx;
+	int error;
+
+	tx = dmu_tx_create(zv->zv_objset);
+	dmu_tx_hold_free(tx, ZVOL_OBJ, offset, size);
+	error = dmu_tx_assign(tx, TXG_WAIT);
+	if (error) {
+		dmu_tx_abort(tx);
+		return (error);
+	}
+	error = dmu_free_range(zv->zv_objset, ZVOL_OBJ, offset, size, tx);
+	dmu_tx_commit(tx);
+	return (0);
+}
+
+int
+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) zvol_truncate(zv, 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)
+{
+	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);
+	dmu_tx_hold_free(tx, ZVOL_OBJ, volsize, DMU_OBJECT_END);
+	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 = zvol_truncate(zv, volsize, DMU_OBJECT_END);
+
+	if (error == 0) {
+		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;
-	dmu_tx_t *tx;
 	int error;
 	dmu_object_info_t doi;
+	uint64_t old_volsize = 0ULL;
 
 	mutex_enter(&zvol_state_lock);
 
@@ -526,6 +882,7 @@
 		mutex_exit(&zvol_state_lock);
 		return (ENXIO);
 	}
+	old_volsize = zv->zv_volsize;
 
 	if ((error = dmu_object_info(zv->zv_objset, ZVOL_OBJ, &doi)) != 0 ||
 	    (error = zvol_check_volsize(volsize,
@@ -534,33 +891,24 @@
 		return (error);
 	}
 
-	if (zv->zv_readonly || (zv->zv_mode & DS_MODE_READONLY)) {
+	if (zv->zv_flags & ZVOL_RDONLY || (zv->zv_mode & DS_MODE_READONLY)) {
 		mutex_exit(&zvol_state_lock);
 		return (EROFS);
 	}
 
-	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);
-		mutex_exit(&zvol_state_lock);
-		return (error);
-	}
+	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);
-	}
-
-	dmu_tx_commit(tx);
-
-	if (error == 0) {
-		zv->zv_volsize = volsize;
-		zvol_size_changed(zv, maj);
+	/*
+	 * 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);
+		}
 	}
 
 	mutex_exit(&zvol_state_lock);
@@ -581,8 +929,7 @@
 		mutex_exit(&zvol_state_lock);
 		return (ENXIO);
 	}
-
-	if (zv->zv_readonly || (zv->zv_mode & DS_MODE_READONLY)) {
+	if (zv->zv_flags & ZVOL_RDONLY || (zv->zv_mode & DS_MODE_READONLY)) {
 		mutex_exit(&zvol_state_lock);
 		return (EROFS);
 	}
@@ -626,7 +973,7 @@
 	ASSERT(zv->zv_objset != NULL);
 
 	if ((flag & FWRITE) &&
-	    (zv->zv_readonly || (zv->zv_mode & DS_MODE_READONLY))) {
+	    (zv->zv_flags & ZVOL_RDONLY || (zv->zv_mode & DS_MODE_READONLY))) {
 		mutex_exit(&zvol_state_lock);
 		return (EROFS);
 	}
@@ -732,7 +1079,7 @@
 
 	/*
 	 * 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);
@@ -794,6 +1141,76 @@
 }
 
 int
+zvol_dumpio(vdev_t *vd, uint64_t size, uint64_t offset, void *addr,
+    int bflags, int isdump)
+{
+	vdev_disk_t *dvd;
+	int direction;
+	int c;
+	int numerrors = 0;
+
+	for (c = 0; c < vd->vdev_children; c++) {
+		if (zvol_dumpio(vd->vdev_child[c], size, offset,
+		    addr, bflags, isdump) != 0) {
+			numerrors++;
+		} else if (bflags & B_READ) {
+			break;
+		}
+	}
+
+	if (!vd->vdev_ops->vdev_op_leaf)
+		return (numerrors < vd->vdev_children ? 0 : EIO);
+
+	if (!vdev_writeable(vd))
+		return (EIO);
+
+	dvd = vd->vdev_tsd;
+	ASSERT3P(dvd, !=, NULL);
+	direction = bflags & (B_WRITE | B_READ);
+	ASSERT(ISP2(direction));
+	offset += VDEV_LABEL_START_SIZE;
+
+	if (ddi_in_panic() || isdump) {
+		if (direction & B_READ)
+			return (EIO);
+		return (ldi_dump(dvd->vd_lh, addr, lbtodb(offset),
+		    lbtodb(size)));
+	} else {
+		return (vdev_disk_physio(dvd->vd_lh, addr, size, offset,
+		    direction));
+	}
+}
+
+int
+zvol_physio(zvol_state_t *zv, int bflags, uint64_t off,
+    uint64_t size, void *addr, int isdump)
+{
+	dva_t dva;
+	vdev_t *vd;
+	int error;
+	spa_t *spa = dmu_objset_spa(zv->zv_objset);
+
+	ASSERT(size <= zv->zv_volblocksize);
+
+	/* restrict requests to multiples of the system block size */
+	if (P2PHASE(off, DEV_BSIZE) || P2PHASE(size, DEV_BSIZE))
+		return (EINVAL);
+
+	if (zvol_get_dva(zv, off, &dva) != 0)
+		return (EIO);
+
+	spa_config_enter(spa, RW_READER, FTAG);
+	vd = vdev_lookup_top(spa, DVA_GET_VDEV(&dva));
+
+	error = zvol_dumpio(vd, size,
+	    DVA_GET_OFFSET(&dva) + (off % zv->zv_volblocksize),
+	    addr, bflags & (B_READ | B_WRITE | B_PHYS), isdump);
+
+	spa_config_exit(spa, FTAG);
+	return (error);
+}
+
+int
 zvol_strategy(buf_t *bp)
 {
 	zvol_state_t *zv = ddi_get_soft_state(zvol_state, getminor(bp->b_edev));
@@ -803,7 +1220,7 @@
 	objset_t *os;
 	rl_t *rl;
 	int error = 0;
-	boolean_t reading;
+	boolean_t reading, is_dump = zv->zv_flags & ZVOL_DUMPIFIED;
 
 	if (zv == NULL) {
 		bioerror(bp, ENXIO);
@@ -817,8 +1234,9 @@
 		return (0);
 	}
 
-	if ((zv->zv_readonly || (zv->zv_mode & DS_MODE_READONLY)) &&
-	    !(bp->b_flags & B_READ)) {
+	if (!(bp->b_flags & B_READ) &&
+	    (zv->zv_flags & ZVOL_RDONLY ||
+	    zv->zv_mode & DS_MODE_READONLY)) {
 		bioerror(bp, EROFS);
 		biodone(bp);
 		return (0);
@@ -842,14 +1260,18 @@
 	rl = zfs_range_lock(&zv->zv_znode, off, resid,
 	    reading ? RL_READER : RL_WRITER);
 
+	if (resid > volsize - off)	/* don't write past the end */
+		resid = volsize - off;
+
 	while (resid != 0 && off < volsize) {
 
-		size = MIN(resid, zvol_maxphys); /* zvol_maxphys per tx */
-
-		if (size > volsize - off)	/* don't write past the end */
-			size = volsize - off;
-
-		if (reading) {
+		size = MIN(resid, zvol_maxphys);
+		if (is_dump) {
+			/* can't straddle a block boundary */
+			size = MIN(size, P2END(off, zv->zv_volblocksize) - off);
+			error = zvol_physio(zv, bp->b_flags, off, size,
+			    addr, 0);
+		} else if (reading) {
 			error = dmu_read(os, ZVOL_OBJ, off, size, addr);
 		} else {
 			dmu_tx_t *tx = dmu_tx_create(os);
@@ -874,9 +1296,8 @@
 	if ((bp->b_resid = resid) == bp->b_bcount)
 		bioerror(bp, off > volsize ? EINVAL : error);
 
-	if (!(bp->b_flags & B_ASYNC) && !reading && !zil_disable)
+	if (!(bp->b_flags & B_ASYNC) && !reading && !zil_disable && !is_dump)
 		zil_commit(zv->zv_zilog, UINT64_MAX, ZVOL_OBJ);
-
 	biodone(bp);
 
 	return (0);
@@ -897,6 +1318,45 @@
 		bp->b_bcount = zvol_maxphys;
 }
 
+int
+zvol_dump(dev_t dev, caddr_t addr, daddr_t blkno, int nblocks)
+{
+	minor_t minor = getminor(dev);
+	zvol_state_t *zv;
+	int error = 0;
+	uint64_t size;
+	uint64_t boff;
+	uint64_t resid;
+
+	if (minor == 0)			/* This is the control device */
+		return (ENXIO);
+
+	zv = ddi_get_soft_state(zvol_state, minor);
+	if (zv == NULL)
+		return (ENXIO);
+
+	boff = ldbtob(blkno);
+	resid = ldbtob(nblocks);
+	if (boff + resid > zv->zv_volsize) {
+		/* dump should know better than to write here */
+		ASSERT(blkno + resid <= zv->zv_volsize);
+		return (EIO);
+	}
+	while (resid) {
+		/* can't straddle a block boundary */
+		size = MIN(resid, P2END(boff, zv->zv_volblocksize) - boff);
+
+		error = zvol_physio(zv, B_WRITE, boff, size, addr, 1);
+		if (error)
+			break;
+		boff += size;
+		addr += size;
+		resid -= size;
+	}
+
+	return (error);
+}
+
 /*ARGSUSED*/
 int
 zvol_read(dev_t dev, uio_t *uio, cred_t *cr)
@@ -942,6 +1402,12 @@
 	if (zv == NULL)
 		return (ENXIO);
 
+	if (zv->zv_flags & ZVOL_DUMPIFIED) {
+		error = physio(zvol_strategy, NULL, dev, B_WRITE,
+		    zvol_minphys, uio);
+		return (error);
+	}
+
 	rl = zfs_range_lock(&zv->zv_znode, uio->uio_loffset, uio->uio_resid,
 	    RL_WRITER);
 	while (uio->uio_resid > 0) {
@@ -982,6 +1448,7 @@
 	struct uuid uuid = EFI_RESERVED;
 	uint32_t crc;
 	int error = 0;
+	rl_t *rl;
 
 	mutex_enter(&zvol_state_lock);
 
@@ -1027,7 +1494,7 @@
 		 * zvol.  Currently this interface will return ENOTTY to
 		 * such requests.  These requests could be supported by
 		 * adding a check for lba == 0 and consing up an appropriate
-		 * RMBR.
+		 * PMBR.
 		 */
 		if (efi.dki_lba == 1) {
 			efi_gpt_t gpt;
@@ -1099,10 +1566,27 @@
 
 	case DKIOCGGEOM:
 	case DKIOCGVTOC:
-		/* commands using these (like prtvtoc) expect ENOTSUP */
+		/*
+		 * commands using these (like prtvtoc) expect ENOTSUP
+		 * since we're emulating an EFI label
+		 */
 		error = ENOTSUP;
 		break;
 
+	case DKIOCDUMPINIT:
+		rl = zfs_range_lock(&zv->zv_znode, 0, zv->zv_volsize,
+		    RL_WRITER);
+		error = zvol_dumpify(zv);
+		zfs_range_unlock(rl);
+		break;
+
+	case DKIOCDUMPFINI:
+		rl = zfs_range_lock(&zv->zv_znode, 0, zv->zv_volsize,
+		    RL_WRITER);
+		error = zvol_dump_fini(zv);
+		zfs_range_unlock(rl);
+		break;
+
 	default:
 		error = ENOTTY;
 		break;
@@ -1131,3 +1615,216 @@
 	mutex_destroy(&zvol_state_lock);
 	ddi_soft_state_fini(&zvol_state);
 }
+
+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;
+
+	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);
+
+	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_free(tx, ZVOL_OBJ, 0, DMU_OBJECT_END);
+	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 = zvol_truncate(zv, 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;
+
+	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);
+	}
+
+	/*
+	 * 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).
+	 */
+	(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);
+
+	(void) zap_remove(os, ZVOL_ZAP_OBJ, ZVOL_DUMPSIZE, tx);
+	zvol_free_extents(zv);
+	zv->zv_flags &= ~ZVOL_DUMPIFIED;
+	dmu_tx_commit(tx);
+
+	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);
+
+	return (0);
+}