--- /dev/null	2012-10-17 21:48:38.000000000 +0200
+++ new/usr/src/uts/common/fs/zfs/fits.c	2012-10-17 21:48:38.706138000 +0200
@@ -0,0 +1,1271 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright (c) 2012 STRATO AG. All rights reserved.
+ */
+#include <sys/zfs_context.h>
+#include <sys/stat.h>
+#include <sys/errno.h>
+#include <sys/mkdev.h>
+#include <sys/debug.h>
+#include <sys/open.h>
+#include <sys/zfs_ioctl.h>
+#include <zfs_namecheck.h>
+#include <sys/policy.h>
+#include <sys/dmu_objset.h>
+#include <sys/dsl_prop.h>
+#include <sys/zvol.h>
+#include <sys/zap.h>
+#include <sys/dsl_dataset.h>
+#include <sys/dmu_traverse.h>
+#include <sys/dsl_dir.h>
+#include <sys/arc.h>
+#include <sys/spa.h>
+#include <sys/spa_impl.h>
+#include <sys/sa.h>
+#include <sys/sa_impl.h>
+#include <sys/zfs_acl.h>
+#include <sys/zfs_sa.h>
+#include <sys/zfs_znode.h>
+#include <sys/dbuf.h>
+#include <sys/fits.h>
+#include <sys/fits_impl.h>
+
+/*
+ * fits_send generates a stream of filesystem data analogous to dmu_send.
+ * The main difference is that the fits-stream does not contain zfs-specific
+ * data and can be replayed on any filesystem. It just contains commands like
+ * MKDIR, CHMOD, RENAME etc.
+ * The stream is generated in two passes. The first pass, PASS_LINK basically
+ * creates all new files/directories and links, while the second pass,
+ * PASS_UNLINK, does all the removal of old stuff.
+ * Each pass enumerates all objects in inode order.
+ * There are some corner cases:
+ *   Files / directories can only be created if the parent already exists or
+ * already has been created. If an object is encountered which parent does not
+ * satisfy this condition, it is put back and its creation will be trigger
+ * by the creation of the parent.
+ *   A similar case applies on deletion. A directory can only be removed after
+ * the last contained object has been removed. If a directory is not empty,
+ * it is put back and the deletion of the last object in it triggers the
+ * deletion.
+ *   If an objects gets deleted, and a new object is created under the same
+ * name, pass1 cannot create the object directly. So it is created under a
+ * temporary name and gets renamed in pass2.
+ *   If an object is deleted and a new object (of possibly diffent type)
+ * created under the same inode and the same name, this change cannot be
+ * detected by enumerating the containing directory (as name + inode are
+ * unchanged). It is detected by a change of the inode generation number and
+ * a flag is set for pass2. Creation is postponed. In pass2, all enumerated
+ * directories are checked for this inode (although the entry is unchanged,
+ * the directory has a bumped txg). If it is encountered, delete + create
+ * happen both in pass2.
+ *
+ * There are lots of TODOs left:
+ *  - add XATTR support
+ *  - add path-caching
+ *  - add a cache for brute-force parent search
+ *  - add a cache for inode-search in a directory
+ *  - use a hash instead of the linear list in fits_count
+ */
+static int
+fits_dnode_changed(spa_t *spa, fits_t *f, uint64_t dnobj,
+    dnode_phys_t *from, arc_buf_t *frombuf, dnode_phys_t *to, arc_buf_t *tobuf);
+
+/* copied from zfs_znode.c */
+static int
+fits_sa_setup(objset_t *osp, sa_attr_type_t **sa_table)
+{
+	uint64_t sa_obj = 0;
+	int error;
+
+	error = zap_lookup(osp, MASTER_NODE_OBJ, ZFS_SA_ATTRS, 8, 1, &sa_obj);
+	if (error != 0 && error != ENOENT)
+		return (error);
+
+	error = sa_setup(osp, sa_obj, zfs_attr_table, ZPL_END, sa_table);
+	return (error);
+}
+
+static int
+fits_grab_sa_handle(objset_t *osp, uint64_t obj, sa_handle_t **hdlp,
+    dmu_buf_t **db, void *tag)
+{
+	dmu_object_info_t doi;
+	int error;
+
+	if ((error = sa_buf_hold(osp, obj, tag, db)) != 0)
+		return (error);
+
+	dmu_object_info_from_db(*db, &doi);
+	if ((doi.doi_bonus_type != DMU_OT_SA &&
+	    doi.doi_bonus_type != DMU_OT_ZNODE) ||
+	    (doi.doi_bonus_type == DMU_OT_ZNODE &&
+	    doi.doi_bonus_size < sizeof (znode_phys_t))) {
+		sa_buf_rele(*db, tag);
+		return (ENOTSUP);
+	}
+
+	error = sa_handle_get(osp, obj, NULL, SA_HDL_PRIVATE, hdlp);
+	if (error != 0) {
+		sa_buf_rele(*db, tag);
+		return (error);
+	}
+
+	return (0);
+}
+
+static void
+fits_release_sa_handle(sa_handle_t *hdl, dmu_buf_t *db, void *tag)
+{
+	sa_handle_destroy(hdl);
+	sa_buf_rele(db, tag);
+}
+
+static int
+fits_find_from_bp(spa_t *spa, dnode_phys_t *dnp, blklevel_t *bl,
+    const zbookmark_t *zb, blkptr_t **bpp, arc_buf_t **pbuf)
+{
+	uint32_t flags;
+	int epbs = dnp->dn_indblkshift - SPA_BLKPTRSHIFT;
+	int epbmask = (1 << epbs) - 1;
+	int level;
+	int slot;
+	uint64_t blkid;
+	uint64_t blk;
+	blklevel_t *blp;
+	zbookmark_t czb;
+	int i;
+
+	*bpp = NULL;
+	for (level = dnp->dn_nlevels - 1; level >= zb->zb_level; --level) {
+		blkid = zb->zb_blkid >> (epbs * (level - zb->zb_level));
+		blk = blkid >> epbs;
+		slot = blk & epbmask;
+		blp = bl + level;
+
+		if (blp->bl_blk == blk)
+			continue;
+
+		for (i = 0; i <= level; ++i) {
+			blklevel_t *b = bl + i;
+
+			if (b->bl_buf)
+				arc_buf_remove_ref(b->bl_buf, &b->bl_buf);
+			b->bl_bp = NULL;
+			b->bl_buf = NULL;
+			b->bl_blk = -1;
+		}
+		ASSERT(slot < blp[1].bl_nslots);
+		if (BP_IS_HOLE(blp[1].bl_bp + slot)) {
+			*bpp = NULL;
+			return (0);
+		}
+		/*
+		 * load indblk
+		 */
+		flags = ARC_WAIT;
+		SET_BOOKMARK(&czb, zb->zb_objset, zb->zb_object, level, blkid);
+		if (dsl_read(NULL, spa, blp[1].bl_bp + slot, blp[1].bl_buf,
+		    arc_getbuf_func, &blp->bl_buf, ZIO_PRIORITY_ASYNC_READ,
+		    ZIO_FLAG_CANFAIL, &flags, &czb) != 0)
+			return (EIO);
+		blp->bl_bp = blp->bl_buf->b_data;
+		blp->bl_nslots = 1 << epbs;
+		blp->bl_blk = blk;
+	}
+	slot = zb->zb_blkid & epbmask;
+	blp = bl + zb->zb_level;
+	ASSERT(slot < blp->bl_nslots);
+	*bpp = blp->bl_bp + slot;
+	*pbuf = blp->bl_buf;
+	if (BP_IS_HOLE(*bpp))
+		*bpp = NULL;
+
+	return (0);
+}
+
+static int
+fits_file_cb(spa_t *spa, fits_t *f, zbookmark_t *zb,
+    blkptr_t *bp, arc_buf_t *pbuf, void *ctx)
+{
+	int err = 0;
+	blkptr_t *fbp;
+
+	if (issig(JUSTLOOKING) && issig(FORREAL))
+		return (EINTR);
+
+	if (f->f_fromds && zb->zb_objset == f->f_fromds->ds_object)
+		return (0);
+
+	if (bp == NULL) {
+		arc_buf_t *fpbuf = NULL;
+		zbookmark_t czb;
+
+		ASSERT(f->f_fromds);
+		SET_BOOKMARK(&czb, f->f_fromds->ds_object, zb->zb_object,
+		    zb->zb_level, zb->zb_blkid);
+		err = fits_find_from_bp(spa, f->f_dnp, f->f_filebl,
+		    &czb, &fbp, &fpbuf);
+		if (err)
+			return (err);
+		if (fbp) {
+#if 0
+			/* XXX TODO callback for newly created hole */
+			err = fits_enum_bp(spa, da, &czb, fbp, fpbuf);
+			if (err)
+				return (err);
+#endif
+		}
+	} else if (zb->zb_level == 0) {
+		arc_buf_t *tbuf;
+		uint32_t tflags = ARC_WAIT;
+		int blksz = BP_GET_LSIZE(bp);
+
+		if (dsl_read(NULL, spa, bp, pbuf,
+		    arc_getbuf_func, &tbuf, ZIO_PRIORITY_ASYNC_READ,
+		    ZIO_FLAG_CANFAIL, &tflags, zb) != 0)
+			return (EIO);
+
+		if (f->f_ops->fits_file_data)
+			err = f->f_ops->fits_file_data(ctx, tbuf->b_data,
+			    zb->zb_blkid * blksz, blksz);
+
+		(void) arc_buf_remove_ref(tbuf, &tbuf);
+	}
+	return (err);
+}
+
+static int
+fits_enum_bp(spa_t *spa, fits_t *da, zbookmark_t *zb,
+    blkptr_t *bp, arc_buf_t *pbuf, uint64_t min_txg, void *ctx)
+{
+	int err = 0;
+	arc_buf_t *buf = NULL;
+	uint32_t flags = ARC_WAIT;
+
+	if (BP_IS_HOLE(bp))
+		return (0);
+
+	if (bp->blk_birth <= min_txg)
+		return (0);
+
+	if (BP_GET_LEVEL(bp) > 0) {
+		int i;
+		int epb = BP_GET_LSIZE(bp) >> SPA_BLKPTRSHIFT;
+		blkptr_t *cbp;
+		zbookmark_t czb;
+
+		if (dsl_read(NULL, spa, bp, pbuf, arc_getbuf_func, &buf,
+		    ZIO_PRIORITY_ASYNC_READ, ZIO_FLAG_CANFAIL, &flags, zb) != 0)
+			return (EIO);
+		cbp = buf->b_data;
+		for (i = 0; i < epb; ++i, ++cbp) {
+			SET_BOOKMARK(&czb, zb->zb_objset, zb->zb_object,
+			    zb->zb_level - 1, zb->zb_blkid * epb + i);
+			err = fits_enum_bp(spa, da, &czb, cbp, buf, min_txg,
+			    ctx);
+			if (err)
+				goto out;
+		}
+	} else if (BP_GET_TYPE(bp) == DMU_OT_DNODE) {
+		int i;
+		int epb = BP_GET_LSIZE(bp) >> DNODE_SHIFT;
+		dnode_phys_t *dnp;
+
+		if (dsl_read(NULL, spa, bp, pbuf, arc_getbuf_func, &buf,
+		    ZIO_PRIORITY_ASYNC_READ, ZIO_FLAG_CANFAIL,
+		    &flags, zb) != 0) {
+			err = EIO;
+			goto out;
+		}
+		dnp = buf->b_data;
+		for (i = 0; i < epb; ++i, ++dnp) {
+			uint64_t dnobj = zb->zb_blkid * epb + i;
+			if (dnp->dn_type == DMU_OT_NONE)
+				continue;
+			err = fits_dnode_changed(spa, da, dnobj, dnp, buf,
+			    NULL, NULL);
+			if (err)
+				goto out;
+		}
+	} else {
+		err = fits_file_cb(spa, da, zb, bp, pbuf, ctx);
+	}
+out:
+	if (buf)
+		(void) arc_buf_remove_ref(buf, &buf);
+
+	return (err);
+}
+
+static int
+fits_cb(spa_t *spa, zilog_t *zilog, const blkptr_t *bp, arc_buf_t *pbuf,
+    const zbookmark_t *zb, const dnode_phys_t *dnp, void *arg)
+{
+	int err = 0;
+	fits_t *f = arg;
+	blkptr_t *fbp = NULL;
+	zbookmark_t czb;
+
+	if (issig(JUSTLOOKING) && issig(FORREAL))
+		return (EINTR);
+
+	if (f->f_fromds)
+		SET_BOOKMARK(&czb, f->f_fromds->ds_object, zb->zb_object,
+		    zb->zb_level, zb->zb_blkid);
+
+	if (zb->zb_object != DMU_META_DNODE_OBJECT)
+		return (0);
+
+	if (bp == NULL) {
+		arc_buf_t *fpbuf = NULL;
+
+		if (!f->f_fromds)
+			return (0);
+
+		err = fits_find_from_bp(spa, f->f_dnp, f->f_bl,
+		    &czb, &fbp, &fpbuf);
+		if (err)
+			return (EIO);
+		if (fbp) {
+			err = fits_enum_bp(spa, f, &czb, fbp, fpbuf, 0, NULL);
+			if (err)
+				return (EIO);
+		}
+		return (0);
+	} else if (zb->zb_level == 0) {
+		dnode_phys_t *tblk;
+		dnode_phys_t *fblk = NULL;
+		arc_buf_t *tbuf;
+		arc_buf_t *fbuf = NULL;
+		arc_buf_t *fpbuf = NULL;
+		uint32_t fflags = ARC_WAIT;
+		uint32_t tflags = ARC_WAIT;
+		int blksz = BP_GET_LSIZE(bp);
+		int i;
+
+		if (dsl_read(NULL, spa, bp, pbuf,
+		    arc_getbuf_func, &tbuf, ZIO_PRIORITY_ASYNC_READ,
+		    ZIO_FLAG_CANFAIL, &tflags, zb) != 0)
+			return (EIO);
+		tblk = tbuf->b_data;
+
+		if (f->f_fromds) {
+			err = fits_find_from_bp(spa, f->f_dnp, f->f_bl, zb,
+			    &fbp, &fpbuf);
+			if (err)
+				return (EIO);
+		}
+		if (fbp) {
+			if (dsl_read(NULL, spa, fbp, fpbuf,
+			    arc_getbuf_func, &fbuf, ZIO_PRIORITY_ASYNC_READ,
+			    ZIO_FLAG_CANFAIL, &fflags, &czb) != 0) {
+				(void) arc_buf_remove_ref(tbuf, &tbuf);
+				return (EIO);
+			}
+			fblk = fbuf->b_data;
+			if (blksz != BP_GET_LSIZE(fbp))
+				return (EIO);
+		}
+		for (i = 0; i < blksz >> DNODE_SHIFT; i++) {
+			uint64_t dnobj = (zb->zb_blkid <<
+				(DNODE_BLOCK_SHIFT - DNODE_SHIFT)) + i;
+			err = 0;
+			if (fbuf && (tblk[i].dn_type == DMU_OT_NONE) &&
+			    fblk[i].dn_type != DMU_OT_NONE) {
+				err = fits_dnode_changed(spa, f, dnobj,
+				    fblk + i, fbuf, NULL, NULL);
+			} else if (fbuf) {
+				if (memcmp(tblk + i, fblk + i, sizeof (*tblk)))
+					err = fits_dnode_changed(spa, f,
+					    dnobj, fblk + i, fbuf, tblk + i,
+					    tbuf);
+			} else {
+				if (tblk[i].dn_type != DMU_OT_NONE)
+					err = fits_dnode_changed(spa, f,
+					    dnobj, NULL, NULL, tblk + i, tbuf);
+			}
+			if (err)
+				break;
+		}
+		(void) arc_buf_remove_ref(tbuf, &tbuf);
+		if (fbuf)
+			(void) arc_buf_remove_ref(fbuf, &fbuf);
+
+		if (err)
+			return (EIO);
+		/* Don't care about the data blocks */
+		return (TRAVERSE_VISIT_NO_CHILDREN);
+	}
+	return (0);
+}
+
+#define	DIR_FROM	1
+#define	DIR_TO		2
+static int
+fits_diff_dir(fits_t *f, uint64_t dnobj, int dir, void *ctx)
+{
+	zap_cursor_t zc;
+	zap_attribute_t *za;
+	int err;
+	objset_t *os1;
+	objset_t *os2;
+	uint64_t mask =  ZFS_DIRENT_OBJ(-1ULL);
+	uint64_t num;
+	uint64_t ix = 0;
+
+	if (dir == DIR_FROM) {
+		os1 = f->f_fromsnap;
+		os2 = f->f_tosnap;
+	} else {
+		os1 = f->f_tosnap;
+		os2 = f->f_fromsnap;
+	}
+
+	za = kmem_alloc(sizeof (zap_attribute_t), KM_SLEEP);
+	for (zap_cursor_init(&zc, os1, dnobj);
+	    (err = zap_cursor_retrieve(&zc, za)) == 0;
+	    zap_cursor_advance(&zc), ++ix) {
+		err = zap_lookup(os2, dnobj, za->za_name, sizeof (num), 1,
+		    &num);
+		if (err && err != ENOENT)
+			break;
+		if (err == ENOENT) {
+			if (dir == DIR_FROM) {
+				if (f->f_ops->fits_dirent_del) {
+					err = f->f_ops->fits_dirent_del(ctx,
+					    za->za_name,
+					    za->za_first_integer & mask);
+					if (err)
+						goto out;
+				}
+			} else {
+				if (f->f_ops->fits_dirent_add) {
+					err = f->f_ops->fits_dirent_add(ctx,
+					    za->za_name,
+					    za->za_first_integer & mask);
+					if (err)
+						goto out;
+				}
+			}
+		} else if ((za->za_first_integer & mask) != (num & mask)) {
+			if (dir == DIR_TO) {
+				/* report only once */
+				if (f->f_ops->fits_dirent_mod) {
+					err = f->f_ops->fits_dirent_mod(ctx,
+					    za->za_name, num & mask,
+					    za->za_first_integer & mask);
+					if (err)
+						goto out;
+				}
+			}
+		} else {
+			if (dir == DIR_TO) {
+				/* report only once */
+				if (f->f_ops->fits_dirent_unmod) {
+					err = f->f_ops->fits_dirent_unmod(ctx,
+					    za->za_name, num & mask);
+					if (err)
+						goto out;
+				}
+			}
+		}
+	}
+	err = 0;
+out:
+	zap_cursor_fini(&zc);
+	kmem_free(za, sizeof (zap_attribute_t));
+
+	return (err);
+}
+static int
+fits_enum_dir(fits_t *f, uint64_t dnobj, int dir, void *ctx)
+{
+	zap_cursor_t zc;
+	zap_attribute_t *za;
+	int err;
+	objset_t *os;
+	uint64_t mask =  ZFS_DIRENT_OBJ(-1ULL);
+
+	if (dir == DIR_FROM)
+		os = f->f_fromsnap;
+	else
+		os = f->f_tosnap;
+
+	za = kmem_alloc(sizeof (zap_attribute_t), KM_SLEEP);
+	for (zap_cursor_init(&zc, os, dnobj);
+	    (err = zap_cursor_retrieve(&zc, za)) == 0;
+	    zap_cursor_advance(&zc)) {
+		if (dir == DIR_FROM) {
+			if (f->f_ops->fits_dirent_del) {
+				err = f->f_ops->fits_dirent_del(ctx,
+				    za->za_name, za->za_first_integer & mask);
+				if (err)
+					break;
+			}
+		} else {
+			if (f->f_ops->fits_dirent_add) {
+				err = f->f_ops->fits_dirent_add(ctx,
+				    za->za_name, za->za_first_integer & mask);
+				if (err)
+					break;
+			}
+		}
+	}
+	if (err == ENOENT)
+		err = 0;
+
+	zap_cursor_fini(&zc);
+	kmem_free(za, sizeof (zap_attribute_t));
+
+	return (err);
+}
+
+static int
+fits_dnode_changed(spa_t *spa, fits_t *f, uint64_t dnobj,
+    dnode_phys_t *from, arc_buf_t *frombuf, dnode_phys_t *to, arc_buf_t *tobuf)
+{
+	int err = 0;
+	int type = 0;
+	fits_info_t si;
+
+	if (dnobj == f->f_shares_dir)
+		return (0);
+
+	if (to && to->dn_type != DMU_OT_PLAIN_FILE_CONTENTS &&
+	    to->dn_type != DMU_OT_DIRECTORY_CONTENTS) {
+		to = NULL;
+	}
+	if (from && from->dn_type != DMU_OT_PLAIN_FILE_CONTENTS &&
+	    from->dn_type != DMU_OT_DIRECTORY_CONTENTS) {
+		from = NULL;
+	}
+
+	if (from) {
+		err = fits_get_info(f, dnobj, FITS_OLD, &si, FI_ATTR_LINKS);
+		if (err)
+			return (err);
+		if (si.si_nlinks == 0)
+			from = NULL;
+	}
+	if (to) {
+		err = fits_get_info(f, dnobj, FITS_NEW, &si, FI_ATTR_LINKS);
+		if (err)
+			return (err);
+		if (si.si_nlinks == 0)
+			to = NULL;
+	}
+
+	if (!to && !from)
+		return (0);
+
+	if (from) {
+		if (from->dn_bonustype != DMU_OT_SA &&
+		    from->dn_bonustype != DMU_OT_ZNODE)
+			return (EINVAL);
+	}
+	if (to) {
+		if (to->dn_bonustype != DMU_OT_SA &&
+		    to->dn_bonustype != DMU_OT_ZNODE)
+			return (EINVAL);
+	}
+
+	if (from)
+		type = from->dn_type;
+	else if (to)
+		type = to->dn_type;
+
+	err = 0;
+	if (type == DMU_OT_DIRECTORY_CONTENTS) {
+		if (from && to) {
+			if (f->f_ops->fits_dir_mod)
+				err = f->f_ops->fits_dir_mod(f, dnobj);
+		} else if (from) {
+			if (f->f_ops->fits_dir_del)
+				err = f->f_ops->fits_dir_del(f, dnobj);
+		} else if (to) {
+			if (f->f_ops->fits_dir_add)
+				err = f->f_ops->fits_dir_add(f, dnobj);
+		}
+	} else if (type == DMU_OT_PLAIN_FILE_CONTENTS) {
+		if (from && to) {
+			if (f->f_ops->fits_file_mod)
+				err = f->f_ops->fits_file_mod(f, dnobj);
+		} else if (from) {
+			if (f->f_ops->fits_file_del)
+				err = f->f_ops->fits_file_del(f, dnobj);
+		} else if (to) {
+			if (f->f_ops->fits_file_add)
+				err = f->f_ops->fits_file_add(f, dnobj);
+		}
+	} else {
+		/* TODO other types, symlinks? */
+		err = 0;
+	}
+	return (err);
+}
+
+typedef struct _fits_search {
+	fits_t *zs_f;
+	uint64_t zs_dnobj;
+	uint64_t zs_parent;
+	objset_t *zs_osp;
+} fits_search_t;
+
+static int
+search_cb(spa_t *spa, zilog_t *zilog, const blkptr_t *bp, arc_buf_t *pbuf,
+    const zbookmark_t *zb, const dnode_phys_t *dnp, void *arg)
+{
+	fits_search_t *zs = arg;
+	fits_t *f = zs->zs_f;
+	arc_buf_t *buf;
+	uint32_t flags = ARC_WAIT;
+	int ebp;
+	int i;
+	int ret;
+
+	if (issig(JUSTLOOKING) && issig(FORREAL))
+		return (EINTR);
+
+	if (zb->zb_object != DMU_META_DNODE_OBJECT)
+		return (0);
+
+	if (zb->zb_level != 0)
+		return (0);
+
+	if (!bp || BP_IS_HOLE(bp))
+		return (0);
+
+	if (BP_GET_TYPE(bp) != DMU_OT_DNODE)
+		return (0);
+
+	ebp = BP_GET_LSIZE(bp) >> DNODE_SHIFT;
+
+	if (dsl_read(NULL, spa, bp, pbuf,
+	    arc_getbuf_func, &buf, ZIO_PRIORITY_ASYNC_READ,
+	    ZIO_FLAG_CANFAIL, &flags, zb) != 0)
+		return (EIO);
+	dnp = buf->b_data;
+
+	for (i = 0; i < ebp; ++i) {
+		zap_cursor_t zc;
+		zap_attribute_t *za;
+		uint64_t mask = ZFS_DIRENT_OBJ(-1ULL);
+		uint64_t ix = 0;
+		uint64_t dnobj = (zb->zb_blkid <<
+			(DNODE_BLOCK_SHIFT - DNODE_SHIFT)) + i;
+
+		if (dnp[i].dn_type != DMU_OT_DIRECTORY_CONTENTS)
+			continue;
+		if (dnobj == f->f_shares_dir)
+			continue;
+
+		za = kmem_alloc(sizeof (zap_attribute_t), KM_SLEEP);
+		for (zap_cursor_init(&zc, zs->zs_osp, dnobj);
+		    (ret = zap_cursor_retrieve(&zc, za)) == 0;
+		    zap_cursor_advance(&zc), ++ix) {
+			if ((za->za_first_integer & mask) ==
+			    (zs->zs_dnobj & mask)) {
+				zs->zs_parent = dnobj;
+				break;
+			}
+		}
+		zap_cursor_fini(&zc);
+		kmem_free(za, sizeof (zap_attribute_t));
+	}
+
+	(void) arc_buf_remove_ref(buf, &buf);
+
+	if (zs->zs_parent)
+		return (EIO);	/* abort search */
+
+	return (TRAVERSE_VISIT_NO_CHILDREN);
+}
+
+static int
+fits_search_parent(fits_t *f, uint64_t dnobj, fits_which_t which,
+    uint64_t *parent)
+{
+	dsl_dataset_t *ds;
+	fits_search_t zs;
+	int ret;
+
+	if (which == FITS_OLD) {
+		ds = f->f_fromds;
+		zs.zs_osp = f->f_fromsnap;
+	} else {
+		ds = f->f_tods;
+		zs.zs_osp = f->f_tosnap;
+	}
+
+	zs.zs_f = f;
+	zs.zs_dnobj = dnobj;
+	zs.zs_parent = 0;
+	ret = traverse_dataset(ds, 0, TRAVERSE_PRE, search_cb, &zs);
+	if (zs.zs_parent) {
+		*parent = zs.zs_parent;
+		return (0);
+	}
+
+	return (ret ? ret : ENOENT);
+}
+
+int
+fits_get_info(fits_t *f, uint64_t dnobj, fits_which_t which,
+    fits_info_t *sp, uint64_t flags)
+{
+	int ret;
+	sa_handle_t *hdl = NULL;
+	dmu_buf_t *db;
+	objset_t *osp;
+	sa_bulk_attr_t bulk[13];
+	int count = 0;
+	sa_attr_type_t *sa_table;
+
+	if (which == FITS_OLD) {
+		osp = f->f_fromsnap;
+		if (!osp)
+			return (ENOENT);
+		sa_table = f->f_from_sa_table;
+	} else if (which == FITS_NEW) {
+		osp = f->f_tosnap;
+		sa_table = f->f_to_sa_table;
+	} else {
+		return (EINVAL);
+	}
+
+	ret = fits_grab_sa_handle(osp, dnobj, &hdl, &db, FTAG);
+	if (ret)
+		return (ret);
+
+	if (flags & FI_ATTR_ATIME) {
+		SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_ATIME], NULL,
+		    &sp->si_atime, sizeof (sp->si_atime));
+	}
+	if (flags & FI_ATTR_MTIME) {
+		SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_MTIME], NULL,
+		    &sp->si_mtime, sizeof (sp->si_mtime));
+	}
+	if (flags & FI_ATTR_CTIME) {
+		SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_CTIME], NULL,
+		    &sp->si_ctime, sizeof (sp->si_ctime));
+	}
+	if (flags & FI_ATTR_OTIME) {
+		SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_CRTIME], NULL,
+		    &sp->si_otime, sizeof (sp->si_otime));
+	}
+	if (flags & FI_ATTR_MODE) {
+		SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_MODE], NULL,
+		    &sp->si_mode, sizeof (sp->si_mode));
+	}
+	if (flags & FI_ATTR_SIZE) {
+		SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_SIZE], NULL,
+		    &sp->si_size, sizeof (sp->si_size));
+	}
+	if (flags & FI_ATTR_PARENT) {
+		SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_PARENT], NULL,
+		    &sp->si_parent, sizeof (sp->si_parent));
+	}
+	if (flags & FI_ATTR_LINKS) {
+		SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_LINKS], NULL,
+		    &sp->si_nlinks, sizeof (sp->si_nlinks));
+	}
+	if (flags & FI_ATTR_RDEV) {
+		SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_RDEV], NULL,
+		    &sp->si_rdev, sizeof (sp->si_rdev));
+	}
+	if (flags & FI_ATTR_UID) {
+		SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_UID], NULL,
+		    &sp->si_uid, sizeof (sp->si_uid));
+	}
+	if (flags & FI_ATTR_GID) {
+		SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_GID], NULL,
+		    &sp->si_gid, sizeof (sp->si_gid));
+	}
+	if (flags & FI_ATTR_GEN) {
+		SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_GEN], NULL,
+		    &sp->si_gen, sizeof (sp->si_gen));
+	}
+	/* XXX if you add things, also bump the size of bulk */
+	/* XXX XATTR */
+
+	/* XXX TODO get flags to check for xattrdir */
+	if (count) {
+		ret = sa_bulk_lookup(hdl, bulk, count);
+		if (ret)
+			goto out;
+	}
+
+	if ((flags & FI_ATTR_PARENT) && sp->si_parent != dnobj) {
+		fits_info_t si;
+		int good = 0;
+		/*
+		 * verify parent. this is very expensive and only a workaround
+		 */
+		ret = fits_get_info(f, sp->si_parent, which, &si, FI_ATTR_MODE);
+		if (ret && ret != ENOENT)
+			goto out;
+		if (ret == 0 && S_ISDIR(si.si_mode)) {
+			ret = fits_find_entry(f, sp->si_parent, dnobj, which,
+			    NULL);
+			if (ret && ret != ENOENT)
+				goto out;
+			if (ret == 0)
+				good = 1;
+		}
+		if (!good) {
+			uint64_t parent;
+
+			cmn_err(CE_NOTE, "parent wrong, do a brute force "
+			    "search for ino %"PRIu64"\n", dnobj);
+			ret = fits_search_parent(f, dnobj, which, &parent);
+			if (ret == ENOENT) {
+				cmn_err(CE_NOTE, "no parent found\n");
+				ret = EINVAL;
+				goto out;
+			}
+			if (ret)
+				goto out;
+			sp->si_parent = parent;
+			cmn_err(CE_NOTE, "parent found, use %"PRIu64"\n",
+			    parent);
+			/*
+			 * TODO add a bad parent cache to prevent additional
+			 * lookup in pass 2
+			 */
+		}
+	}
+
+out:
+	fits_release_sa_handle(hdl, db, FTAG);
+	return (ret);
+}
+
+int
+fits_file_contents(fits_t *f, uint64_t dnobj, void *ctx)
+{
+	dnode_t *from = NULL;
+	dnode_t *to = NULL;
+	int err;
+	int i;
+	zbookmark_t czb;
+	spa_t *spa = f->f_tods->ds_dir->dd_pool->dp_spa;
+
+	if (f->f_fromds) {
+		err = dnode_hold(f->f_fromsnap, dnobj, FTAG, &from);
+		if (err && err != ENOENT)
+			return (err);
+	}
+	if (from && from->dn_type != DMU_OT_PLAIN_FILE_CONTENTS) {
+		dnode_rele(from, FTAG);
+		from = NULL;
+	}
+	err = dnode_hold(f->f_tosnap, dnobj, FTAG, &to);
+	if (err)
+		goto out;
+	if (to->dn_type != DMU_OT_PLAIN_FILE_CONTENTS) {
+		err = EINVAL;
+		goto out;
+	}
+	if (from) {
+		f->f_filebl = kmem_zalloc(sizeof (blklevel_t)*from->dn_nlevels,
+					KM_SLEEP);
+		for (i = 0; i < from->dn_nlevels; ++i)
+			f->f_filebl[i].bl_blk = -1;
+		i = from->dn_nlevels - 1;
+		f->f_filebl[i].bl_nslots = from->dn_nblkptr;
+		f->f_filebl[i].bl_bp = &from->dn_phys->dn_blkptr[0];
+		f->f_filebl[i].bl_blk = 0;
+		f->f_filebl[i].bl_buf = from->dn_dbuf->db_parent->db_buf;
+	}
+	for (i = 0; i < to->dn_nblkptr; ++i) {
+		SET_BOOKMARK(&czb, f->f_tods->ds_object, dnobj,
+		    to->dn_nlevels - 1, i);
+		err = fits_enum_bp(spa, f, &czb, to->dn_phys->dn_blkptr + i,
+		    NULL, f->f_fromtxg, ctx);
+		if (err)
+			goto out;
+	}
+out:
+	if (f->f_filebl) {
+		kmem_free(f->f_filebl, sizeof (blklevel_t) * from->dn_nlevels);
+		f->f_filebl = NULL;
+	}
+	if (from)
+		dnode_rele(from, FTAG);
+	if (to)
+		dnode_rele(to, FTAG);
+
+	return (err);
+}
+
+int
+fits_dir_contents(fits_t *f, uint64_t dnobj, void *ctx)
+{
+	dnode_t *from = NULL;
+	dnode_t *to = NULL;
+	int err;
+
+	if (f->f_fromds) {
+		err = dnode_hold(f->f_fromsnap, dnobj, FTAG, &from);
+		if (err && err != ENOENT)
+			return (err);
+	}
+	if (from && from->dn_type != DMU_OT_DIRECTORY_CONTENTS) {
+		dnode_rele(from, FTAG);
+		from = NULL;
+	}
+	err = dnode_hold(f->f_tosnap, dnobj, FTAG, &to);
+	if (err && err != ENOENT)
+		return (err);
+	if (to && to->dn_type != DMU_OT_DIRECTORY_CONTENTS) {
+		dnode_rele(to, FTAG);
+		to = NULL;
+	}
+
+	if (to && from) {
+		err = fits_diff_dir(f, dnobj, DIR_TO, ctx);
+		if (err)
+			goto out;
+		err = fits_diff_dir(f, dnobj, DIR_FROM, ctx);
+	} else if (to) {
+		err = fits_enum_dir(f, dnobj, DIR_TO, ctx);
+	} else if (from) {
+		err = fits_enum_dir(f, dnobj, DIR_FROM, ctx);
+	}
+out:
+	if (from)
+		dnode_rele(from, FTAG);
+	if (to)
+		dnode_rele(to, FTAG);
+
+	return (err);
+}
+
+int
+fits_find_entry(fits_t *f, uint64_t dirobj, uint64_t dnobj,
+    fits_which_t which, char **name)
+{
+	zap_cursor_t zc;
+	zap_attribute_t *za;
+	int err;
+	uint64_t mask = ZFS_DIRENT_OBJ(-1ULL);
+	struct objset *os;
+
+	if (which == FITS_OLD) {
+		os = f->f_fromsnap;
+		if (!os)
+			return (ENOENT);
+	} else if (which == FITS_NEW) {
+		os = f->f_tosnap;
+	} else {
+		return (EINVAL);
+	}
+
+
+	if (name)
+		*name = NULL;
+	za = kmem_alloc(sizeof (zap_attribute_t), KM_SLEEP);
+	for (zap_cursor_init(&zc, os, dirobj);
+	    (err = zap_cursor_retrieve(&zc, za)) == 0;
+	    zap_cursor_advance(&zc)) {
+		if ((za->za_first_integer & mask) == (dnobj & mask)) {
+			if (name)
+				*name = za->za_name;
+			break;
+		}
+	}
+	zap_cursor_fini(&zc);
+	return (err);
+}
+
+void
+fits_free_name(char *name)
+{
+	zap_attribute_t *za;
+
+	if (!name)
+		return;
+
+	za = (zap_attribute_t *)(name - offsetof(zap_attribute_t, za_name));
+	kmem_free(za, sizeof (*za));
+}
+
+int
+fits_lookup_entry(fits_t *f, uint64_t dirobj, char *name,
+    fits_which_t which, uint64_t *dnobj)
+{
+	struct objset *osp;
+	int ret;
+
+	if (which == FITS_OLD) {
+		osp = f->f_fromsnap;
+		if (!osp)
+			return (ENOENT);
+	} else if (which == FITS_NEW) {
+		osp = f->f_tosnap;
+	} else {
+		return (EINVAL);
+	}
+
+	ret =  zap_lookup(osp, dirobj, name, sizeof (*dnobj), 1, dnobj);
+	if (ret)
+		return (ret);
+	*dnobj = ZFS_DIRENT_OBJ(*dnobj);
+
+	return (0);
+}
+
+int
+fits_write(fits_t *f, const uint8_t *data, int len)
+{
+	ssize_t resid; /* have to get resid to get detailed errno */
+	int err;
+
+	err = vn_rdwr(UIO_WRITE, f->f_vp, (caddr_t)data,
+	    len, 0, UIO_SYSSPACE, FAPPEND, RLIM64_INFINITY, CRED(), &resid);
+	*f->f_offp += len;
+
+	return (err);
+}
+
+int
+fits_get_uuid(fits_t *f, fits_which_t which, uint8_t data[16])
+{
+	if (which == FITS_OLD && !f->f_fromds)
+		return (ENOENT);
+
+	LE_OUT64(data, f->f_tods->ds_dir->dd_pool->dp_spa->spa_config_guid);
+	if (which == FITS_OLD) {
+		LE_OUT64(data + 8, f->f_fromds->ds_phys->ds_guid);
+	} else {
+		LE_OUT64(data + 8, f->f_tods->ds_phys->ds_guid);
+	}
+	return (0);
+}
+
+int
+fits_get_ctransid(fits_t *f, fits_which_t which, uint64_t *ctransid)
+{
+	if (which == FITS_OLD && !f->f_fromds)
+		return (ENOENT);
+
+	if (which == FITS_OLD)
+		*ctransid = f->f_fromds->ds_phys->ds_creation_txg;
+	else
+		*ctransid = f->f_tods->ds_phys->ds_creation_txg;
+	return (0);
+}
+
+int
+fits_get_snapname(fits_t *f, fits_which_t which,
+    char **name, int *len)
+{
+	dsl_dataset_t *ds;
+
+	if (which == FITS_OLD && !f->f_fromds)
+		return (ENOENT);
+
+	if (which == FITS_OLD)
+		ds = f->f_fromds;
+	else
+		ds = f->f_tods;
+
+	*len = dsl_dataset_namelen(ds) + 1;
+	*name = kmem_alloc(*len, KM_SLEEP);
+	dsl_dataset_name(ds, *name);
+	return (0);
+}
+
+int
+fits_read_symlink(fits_t *f, uint64_t dnobj, fits_which_t which,
+    char **target, int *plen)
+{
+	int err;
+	int ret;
+	sa_handle_t *hdl = NULL;
+	dmu_buf_t *db;
+	objset_t *osp;
+	dmu_object_info_t doi;
+	sa_attr_type_t *sa_table;
+
+	if (which == FITS_OLD) {
+		osp = f->f_fromsnap;
+		if (!osp)
+			return (EINVAL);
+		sa_table = f->f_from_sa_table;
+	} else if (which == FITS_NEW) {
+		osp = f->f_tosnap;
+		sa_table = f->f_to_sa_table;
+	} else {
+		return (EINVAL);
+	}
+
+	err = fits_grab_sa_handle(osp, dnobj, &hdl, &db, FTAG);
+	if (err)
+		return (err);
+
+	dmu_object_info_from_db(db, &doi);
+	if (doi.doi_bonus_type == DMU_OT_SA) {
+		int len;
+
+		ret = sa_size(hdl, sa_table[ZPL_SYMLINK], &len);
+		if (ret)
+			goto out;
+		*target = kmem_alloc(len + 1, KM_SLEEP);
+		*plen = len;
+		(*target)[len] = 0;
+		ret = sa_lookup(hdl, sa_table[ZPL_SYMLINK], *target, len + 1);
+		if (ret)
+			kmem_free(*target, len + 1);
+	} else {
+		/*
+		 * TODO read target from file data, the old way
+		 * see zfs_readlink
+		 */
+		ret = EINVAL;
+	}
+
+out:
+	fits_release_sa_handle(hdl, db, FTAG);
+
+	return (ret);
+}
+
+int
+fits_send(objset_t *tosnap, objset_t *fromsnap, int outfd, vnode_t *vp,
+    offset_t *off)
+{
+	dsl_dataset_t *ds;
+	dsl_dataset_t *fromds = NULL;
+	int err = 0;
+	fits_t f;
+	arc_buf_t *buf = NULL;
+	uint32_t flags;
+	objset_phys_t *osp;
+	int i;
+	zbookmark_t zb;
+
+	memset(&f, 0, sizeof (f));
+	ds = tosnap->os_dsl_dataset;
+	if (fromsnap)
+		fromds = fromsnap->os_dsl_dataset;
+
+	/* make certain we are looking at snapshots */
+	if (!dsl_dataset_is_snapshot(ds) ||
+	    (fromds && !dsl_dataset_is_snapshot(fromds)))
+		return (EINVAL);
+
+	/* fromsnap must be earlier and from the same lineage as tosnap */
+	if (fromds) {
+		if (fromds->ds_phys->ds_creation_txg >=
+		    ds->ds_phys->ds_creation_txg)
+			return (EXDEV);
+
+		if (fromds->ds_dir != ds->ds_dir)
+			return (EXDEV);
+
+		/*
+		 * read root dnode from from-dataset
+		 */
+		flags = ARC_WAIT;
+		SET_BOOKMARK(&zb, fromds->ds_object, ZB_ROOT_OBJECT,
+		    ZB_ROOT_LEVEL, ZB_ROOT_BLKID);
+		err = dsl_read_nolock(NULL, fromds->ds_dir->dd_pool->dp_spa,
+		    &fromds->ds_phys->ds_bp, arc_getbuf_func, &buf,
+		    ZIO_PRIORITY_ASYNC_READ, ZIO_FLAG_CANFAIL, &flags, &zb);
+		if (err)
+			return (err);
+		osp = buf->b_data;
+		f.f_dnp = &osp->os_meta_dnode;
+	}
+	f.f_vp = vp;
+	f.f_offp = off;
+	f.f_err = 0;
+	f.f_fromds = fromds;
+	f.f_tods = ds;
+	f.f_fromsnap = fromsnap;
+	f.f_tosnap = tosnap;
+	if (fromds) {
+		f.f_fromtxg = fromds->ds_phys->ds_creation_txg;
+		f.f_bl = kmem_zalloc(sizeof (blklevel_t) *
+		    f.f_dnp->dn_nlevels, KM_SLEEP);
+		for (i = 0; i < f.f_dnp->dn_nlevels; ++i)
+			f.f_bl[i].bl_blk = -1;
+		i = f.f_dnp->dn_nlevels - 1;
+		f.f_bl[i].bl_nslots = f.f_dnp->dn_nblkptr;
+		f.f_bl[i].bl_bp = &f.f_dnp->dn_blkptr[0];
+		f.f_bl[i].bl_blk = 0;
+
+		err = fits_sa_setup(fromsnap, &f.f_from_sa_table);
+		if (err)
+			goto out;
+	}
+	err = fits_sa_setup(tosnap, &f.f_to_sa_table);
+	if (err)
+		goto out;
+
+	err = zap_lookup(tosnap, MASTER_NODE_OBJ, ZFS_SHARES_DIR, 8, 1,
+	    &f.f_shares_dir);
+	if (err && err != ENOENT)
+		goto out;
+
+	err = fits_start(&f, &f.f_ops);
+	if (err)
+		goto out;
+
+	err = traverse_dataset(ds, f.f_fromtxg,
+	    TRAVERSE_PRE | TRAVERSE_PREFETCH_METADATA, fits_cb, &f);
+	if (err) {
+		fits_abort(&f);
+		goto out;
+	}
+	err = fits_start2(&f, &f.f_ops);
+	if (err) {
+		goto out;
+	}
+	err = traverse_dataset(ds, f.f_fromtxg,
+	    TRAVERSE_PRE | TRAVERSE_PREFETCH_METADATA, fits_cb, &f);
+	if (err) {
+		fits_abort(&f);
+		goto out;
+	}
+
+	err = fits_end(&f);
+	if (err)
+		goto out;
+
+out:
+	if (fromds) {
+		for (i = 0; i < f.f_dnp->dn_nlevels - 1; ++i) {
+			blklevel_t *b = f.f_bl + i;
+			if (b->bl_buf)
+				arc_buf_remove_ref(b->bl_buf, &b->bl_buf);
+		}
+		kmem_free(f.f_bl, sizeof (blklevel_t) * f.f_dnp->dn_nlevels);
+	}
+
+	if (buf)
+		arc_buf_remove_ref(buf, &buf);
+
+	return (err);
+}
