--- old/usr/src/cmd/truss/codes.c	2012-10-17 21:48:36.545465000 +0200
+++ new/usr/src/cmd/truss/codes.c	2012-10-17 21:48:36.554453000 +0200
@@ -1193,6 +1193,8 @@
 		"zfs_cmd_t" },
 	{ (uint_t)ZFS_IOC_SEND,			"ZFS_IOC_SEND",
 		"zfs_cmd_t" },
+	{ (uint_t)ZFS_IOC_FITS_SEND,		"ZFS_IOC_FITS_SEND",
+		"zfs_cmd_t" },
 	{ (uint_t)ZFS_IOC_INJECT_FAULT,		"ZFS_IOC_INJECT_FAULT",
 		"zfs_cmd_t" },
 	{ (uint_t)ZFS_IOC_CLEAR_FAULT,		"ZFS_IOC_CLEAR_FAULT",
--- old/usr/src/cmd/zfs/zfs_main.c	2012-10-17 21:48:36.859380000 +0200
+++ new/usr/src/cmd/zfs/zfs_main.c	2012-10-17 21:48:36.867380000 +0200
@@ -89,6 +89,7 @@
 static int zfs_do_share(int argc, char **argv);
 static int zfs_do_unshare(int argc, char **argv);
 static int zfs_do_send(int argc, char **argv);
+static int zfs_do_fits_send(int argc, char **argv);
 static int zfs_do_receive(int argc, char **argv);
 static int zfs_do_promote(int argc, char **argv);
 static int zfs_do_userspace(int argc, char **argv);
@@ -131,6 +132,7 @@
 	HELP_RENAME,
 	HELP_ROLLBACK,
 	HELP_SEND,
+	HELP_FITS_SEND,
 	HELP_SET,
 	HELP_SHARE,
 	HELP_SNAPSHOT,
@@ -188,6 +190,8 @@
 	{ "send",	zfs_do_send,		HELP_SEND		},
 	{ "receive",	zfs_do_receive,		HELP_RECEIVE		},
 	{ NULL },
+	{ "fits-send",	zfs_do_fits_send,	HELP_FITS_SEND		},
+	{ NULL },
 	{ "allow",	zfs_do_allow,		HELP_ALLOW		},
 	{ NULL },
 	{ "unallow",	zfs_do_unallow,		HELP_UNALLOW		},
@@ -254,6 +258,9 @@
 	case HELP_SEND:
 		return (gettext("\tsend [-DnPpRv] [-[iI] snapshot] "
 		    "<snapshot>\n"));
+	case HELP_FITS_SEND:
+		return (gettext("\tfits-send [-v] [-i snapshot] "
+		    "<snapshot>\n"));
 	case HELP_SET:
 		return (gettext("\tset <property=value> "
 		    "<filesystem|volume|snapshot> ...\n"));
@@ -3725,6 +3732,114 @@
 }
 
 /*
+ * Send a backup stream to stdout in fits format.
+ */
+static int
+zfs_do_fits_send(int argc, char **argv)
+{
+	char *fromname = NULL;
+	char *toname = NULL;
+	char *cp;
+	zfs_handle_t *zhp;
+	sendflags_t flags = { 0 };
+	int c, err;
+
+	/* check options */
+	while ((c = getopt(argc, argv, ":i:v")) != -1) {
+		switch (c) {
+		case 'i':
+			if (fromname)
+				usage(B_FALSE);
+			fromname = optarg;
+			break;
+		case 'v':
+			flags.verbose = B_TRUE;
+			break;
+		case ':':
+			(void) fprintf(stderr, gettext("missing argument for "
+			    "'%c' option\n"), optopt);
+			usage(B_FALSE);
+			break;
+		case '?':
+			(void) fprintf(stderr, gettext("invalid option '%c'\n"),
+			    optopt);
+			usage(B_FALSE);
+		}
+	}
+
+	argc -= optind;
+	argv += optind;
+
+	/* check number of arguments */
+	if (argc < 1) {
+		(void) fprintf(stderr, gettext("missing snapshot argument\n"));
+		usage(B_FALSE);
+	}
+	if (argc > 1) {
+		(void) fprintf(stderr, gettext("too many arguments\n"));
+		usage(B_FALSE);
+	}
+
+	if (isatty(STDOUT_FILENO)) {
+		(void) fprintf(stderr,
+		    gettext("Error: Stream can not be written to a terminal.\n"
+		    "You must redirect standard output.\n"));
+		return (1);
+	}
+
+	cp = strchr(argv[0], '@');
+	if (cp == NULL) {
+		(void) fprintf(stderr,
+		    gettext("argument must be a snapshot\n"));
+		usage(B_FALSE);
+	}
+	*cp = '\0';
+	toname = cp + 1;
+	zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_FILESYSTEM);
+	if (zhp == NULL)
+		return (1);
+
+	/*
+	 * If they specified the full path to the snapshot, chop off
+	 * everything except the short name of the snapshot, but special
+	 * case if they specify the origin.
+	 */
+	if (fromname && (cp = strchr(fromname, '@')) != NULL) {
+		char origin[ZFS_MAXNAMELEN];
+		zprop_source_t src;
+
+		(void) zfs_prop_get(zhp, ZFS_PROP_ORIGIN,
+		    origin, sizeof (origin), &src, NULL, 0, B_FALSE);
+
+		if (strcmp(origin, fromname) == 0) {
+			fromname = NULL;
+			flags.fromorigin = B_TRUE;
+		} else {
+			*cp = '\0';
+			if (cp != fromname && strcmp(argv[0], fromname)) {
+				(void) fprintf(stderr,
+				    gettext("incremental source must be "
+				    "in same filesystem\n"));
+				usage(B_FALSE);
+			}
+			fromname = cp + 1;
+			if (strchr(fromname, '@') || strchr(fromname, '/')) {
+				(void) fprintf(stderr,
+				    gettext("invalid incremental source\n"));
+				usage(B_FALSE);
+			}
+		}
+	}
+
+	err = zfs_fits_send(zhp, fromname, toname, &flags, STDOUT_FILENO,
+	    NULL, 0);
+
+	zfs_close(zhp);
+
+	return (err != 0);
+}
+
+/*
  * allow/unallow stuff
  */
 /* copied from zfs/sys/dsl_deleg.h */
--- old/usr/src/lib/libzfs/common/libzfs.h	2012-10-17 21:48:37.278376000 +0200
+++ new/usr/src/lib/libzfs/common/libzfs.h	2012-10-17 21:48:37.284379000 +0200
@@ -591,6 +591,8 @@
 
 extern int zfs_send(zfs_handle_t *, const char *, const char *,
     sendflags_t *, int, snapfilter_cb_t, void *, nvlist_t **);
+extern int zfs_fits_send(zfs_handle_t *, const char *, const char *,
+    sendflags_t *, int, snapfilter_cb_t, void *);
 
 extern int zfs_promote(zfs_handle_t *);
 extern int zfs_hold(zfs_handle_t *, const char *, const char *, boolean_t,
--- old/usr/src/lib/libzfs/common/libzfs_sendrecv.c	2012-10-17 21:48:37.526356000 +0200
+++ new/usr/src/lib/libzfs/common/libzfs_sendrecv.c	2012-10-17 21:48:37.534350000 +0200
@@ -3197,3 +3197,102 @@
 
 	return (err);
 }
+
+/*
+ * Generate a fits stream for the dataset identified by the argument zhp.
+ *
+ * The content of the send stream is the snapshot identified by
+ * 'tosnap'.  Incremental streams are requested from the snapshot identified
+ * by "fromsnap" (if non-null)
+ * Currently no recursive send is implemented
+ */
+int
+zfs_fits_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
+    sendflags_t *flags, int outfd, snapfilter_cb_t filter_func,
+    void *cb_arg)
+{
+	char errbuf[1024];
+	char name[MAXPATHLEN];
+	zfs_cmd_t zc = { 0 };
+	libzfs_handle_t *hdl = zhp->zfs_hdl;
+	zfs_handle_t *thdl;
+
+	(void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+	    "cannot send '%s'"), zhp->zfs_name);
+
+	if (fromsnap && fromsnap[0] == '\0') {
+		zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+		    "zero-length incremental source"));
+		return (zfs_error(zhp->zfs_hdl, EZFS_NOENT, errbuf));
+	}
+
+	(void) snprintf(name, sizeof (name), "%s@%s", zhp->zfs_name, tosnap);
+	if ((thdl = zfs_open(hdl, name, ZFS_TYPE_SNAPSHOT)) == NULL) {
+		(void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+		    "WARNING: could not send %s@%s: does not exist\n"),
+		    zhp->zfs_name, tosnap);
+		return (B_TRUE);
+	}
+	zc.zc_sendobj = zfs_prop_get_int(thdl, ZFS_PROP_OBJSETID);
+	zfs_close(thdl);
+
+	if (fromsnap) {
+		(void) snprintf(name, sizeof (name), "%s@%s",
+		    zhp->zfs_name, fromsnap);
+		if ((thdl = zfs_open(hdl, name, ZFS_TYPE_SNAPSHOT)) == NULL) {
+			(void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+			    "WARNING: could not send %s@%s:\n"
+			    "incremental source (%s@%s) does not exist\n"),
+			    zhp->zfs_name, tosnap,
+			    zhp->zfs_name, fromsnap);
+			return (B_TRUE);
+		}
+		zc.zc_fromobj = zfs_prop_get_int(thdl, ZFS_PROP_OBJSETID);
+		zfs_close(thdl);
+	}
+
+	assert(zhp->zfs_type == ZFS_TYPE_FILESYSTEM);
+
+	(void) snprintf(zc.zc_name, sizeof (zc.zc_name), "%s@%s",
+	    zhp->zfs_name, tosnap);
+	zc.zc_cookie = outfd;
+	zc.zc_obj = 0;
+
+	if (zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_FITS_SEND, &zc) != 0) {
+		char errbuf[1024];
+		(void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+		    "warning: cannot send '%s'"), zhp->zfs_name);
+
+		switch (errno) {
+		case EXDEV:
+			zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+			    "not an earlier snapshot from the same fs"));
+			return (zfs_error(hdl, EZFS_CROSSTARGET, errbuf));
+		case ENOENT:
+			if (zfs_dataset_exists(hdl, zc.zc_name,
+			    ZFS_TYPE_SNAPSHOT)) {
+				zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+				    "incremental source (@%s) does not exist"),
+				    zc.zc_value);
+			}
+			return (zfs_error(hdl, EZFS_NOENT, errbuf));
+		case EDQUOT:
+		case EFBIG:
+		case EIO:
+		case ENOLINK:
+		case ENOSPC:
+		case ENOSTR:
+		case ENXIO:
+		case EPIPE:
+		case ERANGE:
+		case EFAULT:
+		case EROFS:
+			zfs_error_aux(hdl, strerror(errno));
+			return (zfs_error(hdl, EZFS_BADBACKUP, errbuf));
+		default:
+			return (zfs_standard_error(hdl, errno, errbuf));
+		}
+	}
+
+	return (0);
+}
--- old/usr/src/lib/libzfs/common/mapfile-vers	2012-10-17 21:48:37.832264000 +0200
+++ new/usr/src/lib/libzfs/common/mapfile-vers	2012-10-17 21:48:37.839258000 +0200
@@ -72,6 +72,7 @@
 	zfs_destroy_snaps;
 	zfs_destroy_snaps_nvl;
 	zfs_expand_proplist;
+	zfs_fits_send;
 	zfs_get_handle;
 	zfs_get_holds;
 	zfs_get_name;
--- old/usr/src/uts/common/Makefile.files	2012-10-17 21:48:38.109262000 +0200
+++ new/usr/src/uts/common/Makefile.files	2012-10-17 21:48:38.117261000 +0200
@@ -1340,6 +1340,12 @@
 	dmu_objset.o		\
 	dmu_traverse.o		\
 	dmu_tx.o		\
+	fits.o			\
+	fits_pass1.o		\
+	fits_pass2.o		\
+	fits_send.o		\
+	fits_crc32c.o		\
+	fits_count.o		\
 	dnode.o			\
 	dnode_sync.o		\
 	dsl_dir.o		\
--- old/usr/src/uts/common/fs/zfs/dsl_dataset.c	2012-10-17 21:48:38.368263000 +0200
+++ new/usr/src/uts/common/fs/zfs/dsl_dataset.c	2012-10-17 21:48:38.375267000 +0200
@@ -680,7 +680,7 @@
 	}
 }
 
-static int
+int
 dsl_dataset_namelen(dsl_dataset_t *ds)
 {
 	int result;
--- /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);
+}
--- /dev/null	2012-10-17 21:48:38.000000000 +0200
+++ new/usr/src/uts/common/fs/zfs/fits_count.c	2012-10-17 21:48:38.822140000 +0200
@@ -0,0 +1,124 @@
+/*
+ * 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/errno.h>
+#include <sys/fits_impl.h>
+
+int
+fits_add_count(fits_counter_t *fc, uint64_t ino, uint64_t inc,
+	       uint64_t aux, uint64_t *new_count, uint64_t *old_aux)
+{
+	fits_count_elem_t *fce = fc->fc_head;
+
+	while (fce && fce->fce_ino != ino)
+		fce = fce->fce_next;
+
+	if (!fce) {
+		fce = kmem_alloc(sizeof(*fce), KM_SLEEP);
+		fce->fce_ino = ino;
+		fce->fce_count = 0;
+		fce->fce_next = fc->fc_head;
+		fc->fc_head = fce;
+	}
+
+	if (old_aux) {
+		*old_aux = fce->fce_aux;
+		fce->fce_aux = aux;
+	}
+	fce->fce_count += inc;
+
+	if (new_count)
+		*new_count = fce->fce_count;
+
+	return 0;
+}
+
+int
+fits_get_count(fits_counter_t *fc, uint64_t ino, uint64_t *new_count,
+	       uint64_t *old_aux)
+{
+	fits_count_elem_t *fce = fc->fc_head;
+
+	while (fce && fce->fce_ino != ino)
+		fce = fce->fce_next;
+
+	if (!fce) {
+		if (new_count)
+			*new_count = 0;
+		if (old_aux)
+			*old_aux = 0;
+		return ENOENT;
+	} else {
+		if (new_count)
+			*new_count = fce->fce_count;
+		if (old_aux)
+			*old_aux = fce->fce_aux;
+	}
+
+	return 0;
+}
+
+void
+fits_free_count(fits_counter_t *fc, uint64_t ino)
+{
+	fits_count_elem_t *fce = fc->fc_head;
+	fits_count_elem_t *prev = NULL;
+
+	while (fce && fce->fce_ino != ino) {
+		prev = fce;
+		fce = fce->fce_next;
+	}
+
+	if (!fce)
+		return;
+
+	if (prev)
+		prev->fce_next = fce->fce_next;
+	else
+		fc->fc_head = fce->fce_next;
+
+	kmem_free(fce, sizeof(*fce));
+}
+
+int
+fits_assert_count_empty(fits_counter_t *fc)
+{
+	fits_count_elem_t *fce = fc->fc_head;
+	int ret = 0;
+
+	while (fce) {
+		printf("!fits_assert_count_empty: %s ino %"PRIu64" count %"PRIu64"\n",
+		       fc->fc_name, fce->fce_ino, fce->fce_count);
+		if (fce->fce_count != 0)
+			++ret;
+		fce = fce->fce_next;
+		/* XXX TODO free count */
+		/* XXX known leftover: if a file had > 1 links and be replaced
+		 * by a file with > 1 link, but no same_name replacements, the
+		 * link_add_cnt leaks with being 0
+		 */
+	}
+
+	return ret;
+}
--- /dev/null	2012-10-17 21:48:38.000000000 +0200
+++ new/usr/src/uts/common/fs/zfs/fits_crc32c.c	2012-10-17 21:48:38.927136000 +0200
@@ -0,0 +1,174 @@
+/*
+ * 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
+ */
+/*
+ * The crc32c algorithms are taken from sctp_crc32 implementation
+ * common/inet/sctp_crc32.{c,h}, which in turn were taken from nxge_fflp_hash.c
+ */
+
+#include <sys/zfs_context.h>
+#include <sys/fits_crc32c.h>
+
+static void fits_crc32c_word(uint32_t *crcptr, const uint32_t *buf, int len);
+
+/*
+ * Fast CRC32C calculation algorithm.  The basic idea is to look at it
+ * four bytes (one word) at a time, using four tables.  The
+ * standard algorithm in RFC 3309 uses one table.
+ */
+
+#define	CRC_32C_POLY 0x1EDC6F41L
+
+/* The four CRC32c tables. */
+static uint32_t crc32c_tab[4][256];
+static int initialized;
+
+
+static uint32_t
+reflect_32(uint32_t b)
+{
+	int i;
+	uint32_t rw = 0;
+
+	for (i = 0; i < 32; i++) {
+		if (b & 1) {
+			rw |= 1 << (31 - i);
+		}
+		b >>= 1;
+	}
+	return (rw);
+}
+
+#ifdef _BIG_ENDIAN
+static uint32_t
+flip32(uint32_t w)
+{
+	return (((w >> 24) | ((w >> 8) & 0xff00) |
+	    ((w << 8) & 0xff0000) | (w << 24)));
+}
+#endif
+
+/*
+ * Initialize the crc32c tables.
+ */
+
+void
+fits_crc32c_init(void)
+{
+	uint32_t index, bit, byte, crc;
+
+	for (index = 0; index < 256; index++) {
+		crc = reflect_32(index);
+		for (byte = 0; byte < 4; byte++) {
+			for (bit = 0; bit < 8; bit++) {
+				crc = (crc & 0x80000000) ?
+				    (crc << 1) ^ CRC_32C_POLY : crc << 1;
+			}
+#ifdef _BIG_ENDIAN
+			crc32c_tab[3 - byte][index] = flip32(reflect_32(crc));
+#else
+			crc32c_tab[byte][index] = reflect_32(crc);
+#endif
+		}
+	}
+}
+
+/*
+ * Lookup  the crc32c for a byte stream
+ */
+static void
+fits_crc32c_byte(uint32_t *crcptr, const uint8_t *buf, int len)
+{
+	uint32_t crc;
+	int i;
+
+	crc = *crcptr;
+	for (i = 0; i < len; i++) {
+#ifdef _BIG_ENDIAN
+		crc = (crc << 8) ^ crc32c_tab[3][buf[i] ^ (crc >> 24)];
+#else
+		crc = (crc >> 8) ^ crc32c_tab[0][buf[i] ^ (crc & 0xff)];
+#endif
+	}
+	*crcptr = crc;
+}
+
+/*
+ * Lookup  the crc32c for a 32 bit word stream
+ * Lookup is done fro the 4 bytes in parallel
+ * from the tables computed earlier
+ *
+ */
+static void
+fits_crc32c_word(uint32_t *crcptr, const uint32_t *buf, int len)
+{
+	uint32_t w, crc;
+	int i;
+
+	crc = *crcptr;
+	for (i = 0; i < len; i++) {
+		w = crc ^ buf[i];
+		crc = crc32c_tab[0][w >> 24] ^
+		    crc32c_tab[1][(w >> 16) & 0xff] ^
+		    crc32c_tab[2][(w >> 8) & 0xff] ^
+		    crc32c_tab[3][w & 0xff];
+	}
+	*crcptr = crc;
+}
+
+/*
+ * Lookup  the crc32c for a stream of bytes
+ *
+ * Tries to lookup the CRC on 4 byte words
+ * If the buffer is not 4 byte aligned, first compute
+ * with byte lookup until aligned. Then compute crc
+ * for each 4 bytes. If there are bytes left at the end of
+ * the buffer, then perform a byte lookup for the remaining bytes
+ *
+ *
+ */
+uint32_t
+fits_crc32c(uint32_t crc32, const uint8_t *buf, int len)
+{
+	int rem;
+
+	if (!initialized) {
+		fits_crc32c_init();
+		initialized = 1;
+	}
+
+	rem = 4 - (((uintptr_t)buf) & 3);
+	if (rem != 0) {
+		if (len < rem) {
+			rem = len;
+		}
+		fits_crc32c_byte(&crc32, buf, rem);
+		buf = buf + rem;
+		len = len - rem;
+	}
+	if (len > 3) {
+		fits_crc32c_word(&crc32, (const uint32_t *) buf, len / 4);
+	}
+	rem = len & 3;
+	if (rem != 0) {
+		fits_crc32c_byte(&crc32, buf + len - rem, rem);
+	}
+	return (crc32);
+}
--- /dev/null	2012-10-17 21:48:39.000000000 +0200
+++ new/usr/src/uts/common/fs/zfs/fits_pass1.c	2012-10-17 21:48:39.031136000 +0200
@@ -0,0 +1,509 @@
+/*
+ * 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/errno.h>
+#include <sys/stat.h>
+#include <sys/fits.h>
+#include <sys/fits_impl.h>
+
+struct fits_enum {
+	fits_t *fe_fits;
+	uint64_t fe_parent_ino;
+	fits_dirent_t *fe_dirent_chain;
+};
+
+struct fits_file {
+	fits_t *ff_fits;
+	uint64_t ff_len;
+	uint64_t ff_last_byte;
+	uint64_t ff_ino;
+	fits_path_t *ff_path;
+	fits_dirent_t *ff_dirent;
+};
+
+static int fits_file_data_pass1(void *fits_filep, void *data, uint64_t off,
+    uint64_t len);
+static int fits_dirent_add_pass1(void *fits_enump, char *name, uint64_t ino);
+static int fits_dirent_mod_pass1(void *fits_enump, char *name,
+    uint64_t ino_old, uint64_t ino_new);
+static int fits_dir_add_pass1(fits_t *f, uint64_t ino);
+static int fits_mod_pass1(fits_t *f, uint64_t ino);
+
+static fits_ops_t _ops = {
+	.fits_dir_add = fits_dir_add_pass1,
+	.fits_dir_mod = fits_mod_pass1,
+	.fits_dirent_add = fits_dirent_add_pass1,
+	.fits_dirent_mod = fits_dirent_mod_pass1,
+	.fits_file_mod = fits_mod_pass1,
+	.fits_file_data = fits_file_data_pass1
+};
+
+static int fits_file_add_genchange(fits_t *f, uint64_t ino);
+
+int
+fits_start(fits_t *f, fits_ops_t **ops)
+{
+	int ret;
+
+	f->f_pass = PASS_LINK;
+	f->f_link_add_cnt.fc_head = NULL;
+	f->f_link_add_cnt.fc_name = "link_add_cnt";
+	f->f_del_dir_cnt.fc_head = NULL;
+	f->f_del_dir_cnt.fc_name = "del_dir_cnt";
+	f->f_put_back_cnt.fc_head = NULL;
+	f->f_put_back_cnt.fc_name = "put_back_cnt";
+	fits_send_init(f);
+
+	*ops = &_ops;
+
+	ret = fits_send_start(f);
+	if (ret) {
+		fits_abort(f);
+		return (ret);
+	}
+
+	return (0);
+}
+
+static int
+enum_dir(fits_t *f, uint64_t ino, fits_dirent_t *chain)
+{
+	struct fits_enum fe = {
+		.fe_fits = f,
+		.fe_parent_ino = ino,
+		.fe_dirent_chain = chain
+	};
+
+	return (fits_dir_contents(f, ino, &fe));
+}
+
+static int
+fits_file_data_pass1(void *fits_filep, void *data, uint64_t off, uint64_t len)
+{
+	struct fits_file *ff = fits_filep;
+
+	if (off + len > ff->ff_len)
+		len = ff->ff_len - off;
+
+	ff->ff_last_byte = off + len;
+
+	return fits_send_file_data(ff->ff_fits, &ff->ff_path,
+	    ff->ff_dirent, ff->ff_ino, off, len, data);
+}
+
+static int
+dirent_add_dir(fits_t *f, fits_dirent_t *dirent, uint64_t ino, int exists)
+{
+	fits_info_t si_old;
+	fits_info_t si_new;
+	int ret;
+
+	ret = fits_get_info(f, ino, FITS_OLD, &si_old,
+			    FI_ATTR_PARENT | FI_ATTR_GEN | FI_ATTR_MODE);
+	if (ret && ret != ENOENT)
+		return (ret);
+
+	if (ret == 0) {
+		ret = fits_get_info(f, ino, FITS_NEW, &si_new,
+				    FI_ATTR_GEN | FI_ATTR_MODE);
+		if (ret)
+			return (ret);
+
+		if (si_old.si_gen == si_new.si_gen ||
+		    (S_ISDIR(si_new.si_mode) && S_ISDIR(si_old.si_mode)))
+			return fits_send_rename(f, dirent, ino,
+						si_old.si_parent, exists);
+		return (0);
+	}
+
+	if (ino > f->f_current_ino)
+		return (0);
+
+	/* dir is new */
+	ret = fits_send_mkdir(f, dirent, ino, exists);
+	if (ret)
+		return (ret);
+
+	return (enum_dir(f, ino, dirent));
+}
+
+int
+fits_dirent_add_file(fits_t *f, fits_dirent_t *dirent,
+    uint64_t ino, uint64_t mode, int exists)
+{
+	fits_info_t si_old;
+	fits_info_t si_new;
+	int ret;
+	fits_path_t *fits_path;
+	uint64_t new_count;
+	uint64_t old_aux;
+
+	ret = fits_get_info(f, ino, FITS_OLD, &si_old,
+			    FI_ATTR_GEN | FI_ATTR_PARENT);
+	if (ret && ret != ENOENT)
+		return (ret);
+
+	if (ret == 0) {
+		ret = fits_get_info(f, ino, FITS_NEW, &si_new,
+				    FI_ATTR_GEN);
+		if (ret)
+			return (ret);
+
+		if (si_old.si_gen == si_new.si_gen)
+			return fits_send_link(f, dirent, ino, si_old.si_parent,
+			    FITS_OLD, exists);
+	}
+
+	/* file is new */
+	ret = fits_add_count(&f->f_link_add_cnt, ino, 1, dirent->fd_parent_ino,
+	    &new_count, &old_aux);
+	if (ret)
+		return (ret);
+
+	if (new_count == 1)
+		ret = fits_send_create_file(f, dirent, ino,
+					    exists, &fits_path);
+	else
+		ret = fits_send_link(f, dirent, ino, old_aux, FITS_NEW, exists);
+	if (ret)
+		return (ret);
+
+	ret = fits_get_info(f, ino, FITS_NEW, &si_new,
+			    FI_ATTR_SIZE | FI_ATTR_LINKS);
+	ASSERT(ret == 0);
+	if (new_count == 1 && S_ISREG(mode)) {
+		struct fits_file ff;
+
+		ff.ff_ino = ino;
+		ff.ff_len = si_new.si_size;
+		ff.ff_fits = f;
+		ff.ff_path = fits_path;
+		ff.ff_dirent = dirent;
+		ff.ff_last_byte = 0;
+		ret = fits_file_contents(f, ino, &ff);
+		fits_path_free(ff.ff_path);
+		if (ret)
+			return (ret);
+		if (ff.ff_last_byte != si_new.si_size) {
+			/* sparse end */
+			ret = fits_send_truncate(f, NULL, ino, si_new.si_size);
+			if (ret)
+				return (ret);
+		}
+	}
+	if (new_count == si_new.si_nlinks)
+		fits_free_count(&f->f_link_add_cnt, ino);
+
+	return (0);
+}
+
+static int
+dirent_add(fits_t *f, fits_dirent_t *dirent, uint64_t ino, int exists)
+{
+	fits_info_t si;
+	int ret;
+
+	ret = fits_get_info(f, ino, FITS_NEW, &si, FI_ATTR_MODE);
+	if (ret)
+		return (ret);
+
+	if (S_ISDIR(si.si_mode)) {
+		return (dirent_add_dir(f, dirent, ino, exists));
+	} else {
+		return (fits_dirent_add_file(f, dirent, ino, si.si_mode,
+		    exists));
+	}
+}
+
+static int
+fits_dirent_add_pass1(void *fits_enump, char *name, uint64_t ino)
+{
+	struct fits_enum *fe = fits_enump;
+	fits_dirent_t dirent = {
+		.fd_name = name,
+		.fd_parent_ino = fe->fe_parent_ino,
+		.fd_prev = fe->fe_dirent_chain,
+	};
+
+	return (dirent_add(fe->fe_fits, &dirent, ino, 0));
+}
+
+static int
+fits_dirent_mod_pass1(void *fits_enump, char *name,
+    uint64_t ino_old, uint64_t ino_new)
+{
+	struct fits_enum *fe = fits_enump;
+	fits_dirent_t dirent = {
+		.fd_name = name,
+		.fd_parent_ino = fe->fe_parent_ino,
+		.fd_prev = fe->fe_dirent_chain,
+	};
+
+	return (dirent_add(fe->fe_fits, &dirent, ino_new, 1));
+}
+
+static int
+fits_file_add_genchange(fits_t *f, uint64_t ino)
+{
+	int ret;
+	char *name = NULL;
+
+	f->f_current_ino = ino;
+	f->f_current_path = NULL;
+
+	/*
+	 * only called when generation has changed. TODO: move to own
+	 * function
+	 */
+	fits_info_t si_old;
+	fits_info_t si_new;
+	int same_name = 0;
+
+	ret = fits_get_info(f, ino, FITS_OLD, &si_old, FI_ATTR_MODE |
+			    FI_ATTR_LINKS | FI_ATTR_PARENT);
+	if (ret)
+		return (ret);
+	ret = fits_get_info(f, ino, FITS_NEW, &si_new, FI_ATTR_MODE |
+			    FI_ATTR_LINKS | FI_ATTR_PARENT);
+	if (ret)
+		return (ret);
+
+	if (si_old.si_nlinks > 1 && si_new.si_nlinks > 1)
+		return fits_add_count(&f->f_link_add_cnt, ino, 0, 0, NULL,
+		    NULL);
+
+	if (S_ISDIR(si_old.si_mode))
+		return (0);
+
+	fits_which_t from;
+	fits_which_t to;
+	uint64_t new_ino;
+	uint64_t parent = si_new.si_parent;
+
+	if (si_old.si_nlinks == 1) {
+		from = FITS_OLD;
+		to = FITS_NEW;
+		parent = si_old.si_parent;
+	} else if (si_old.si_nlinks > 1 && si_new.si_nlinks == 1) {
+		from = FITS_NEW;
+		to = FITS_OLD;
+		parent = si_new.si_parent;
+	} else {
+		return (EINVAL);
+	}
+
+	ret = fits_find_entry(f, parent, ino, from, &name);
+	if (ret)
+		return (ret);
+
+	ret = fits_lookup_entry(f, parent, name, to, &new_ino);
+	if (ret && ret != ENOENT)
+		goto out;
+	if (ret == 0 && new_ino == ino)
+		same_name = 1;
+
+	if ((si_old.si_nlinks == 1 || si_new.si_nlinks == 1) && !same_name) {
+		ret = 0;
+		goto out;
+	}
+
+	fits_dirent_t dirent = {
+		.fd_parent_ino = parent,
+		.fd_name = name,
+		.fd_prev = NULL
+	};
+
+	ret = fits_send_unlink(f, &dirent, ino);
+	if (ret)
+		goto out;
+	ret = fits_dirent_add_file(f, &dirent, ino, si_new.si_mode, 0);
+
+out:
+	fits_free_name(name);
+	return (ret);
+}
+
+static int
+fits_dir_add_pass1(fits_t *f, uint64_t ino)
+{
+	int ret;
+	uint64_t parent;
+	uint64_t first_parent = FITS_NO_INO;
+	fits_info_t si;
+	fits_dirent_t dirent;
+	int same_name = 0;
+	char *name = NULL;
+
+	f->f_current_ino = ino;
+	f->f_current_path = NULL;
+
+	parent = ino;
+	while (1) {
+		/* the new parent must exist, otherwise the fs is wrong */
+		ret = fits_get_info(f, parent, FITS_NEW, &si,
+				    FI_ATTR_PARENT);
+		if (ret)
+			return (ret);
+		if (first_parent == FITS_NO_INO)
+			first_parent = si.si_parent;
+
+		ret = fits_get_info(f, parent, FITS_OLD, &si, 0);
+		if (ret && ret != ENOENT)
+			return (ret);
+		if (ret != ENOENT)
+			break;
+
+		/*
+		 * this check is only needed for a full send, on all
+		 * incrementals the parent already exists and it breaks out
+		 * above
+		 */
+		if (parent == si.si_parent) {
+			first_parent = FITS_NO_INO;
+			break;
+		}
+		parent = si.si_parent;
+
+		if (parent > ino)
+			return (0);
+	}
+
+	/*
+	 * check for same-name
+	 */
+	if (first_parent != FITS_NO_INO) {
+		ret = fits_get_info(f, first_parent, FITS_OLD, &si,
+		    FI_ATTR_MODE);
+		if (ret && ret != ENOENT)
+			return (ret);
+		if (ret == 0 && S_ISDIR(si.si_mode)) {
+			uint64_t old_ino;
+
+			ret = fits_find_entry(f, first_parent, ino,
+			    FITS_NEW, &name);
+			if (ret)
+				return (ret);
+
+			ret = fits_lookup_entry(f, first_parent, name,
+			    FITS_OLD, &old_ino);
+			if (ret && ret != ENOENT) {
+				goto out;
+			}
+			if (ret == 0) {
+				same_name = 1;
+				dirent.fd_name = name;
+				dirent.fd_parent_ino = first_parent;
+				dirent.fd_prev = NULL;
+				if (old_ino == ino) {
+					ret = fits_add_count(&f->f_link_add_cnt,
+					    ino, 0, 0, NULL, NULL);
+					if (ret)
+						goto out;
+				}
+			}
+		}
+	}
+	/* dir is new */
+	ret = fits_send_mkdir(f, same_name ? &dirent : NULL, ino, same_name);
+	if (ret)
+		goto out;
+
+	ret = enum_dir(f, ino, NULL);
+
+out:
+	fits_free_name(name);
+	return (ret);
+}
+
+static int
+fits_mod_pass1(fits_t *f, uint64_t ino)
+{
+	fits_info_t si_old;
+	fits_info_t si_new;
+	int ret;
+
+	f->f_current_ino = ino;
+	f->f_current_path = NULL;
+
+	ret = fits_get_info(f, ino, FITS_NEW, &si_new, FI_ATTR_SIZE |
+			    FI_ATTR_MODE | FI_ATTR_GEN | FI_ATTR_UID |
+			    FI_ATTR_GID | FI_ATTR_SIZE);
+	if (ret)
+		return (ret);
+	ret = fits_get_info(f, ino, FITS_OLD, &si_old, FI_ATTR_GEN |
+			    FI_ATTR_MODE | FI_ATTR_UID |
+			    FI_ATTR_GID | FI_ATTR_SIZE);
+	if (ret)
+		return (ret);
+
+	if (!(S_ISDIR(si_old.si_mode) && S_ISDIR(si_new.si_mode)) &&
+	    si_new.si_gen != si_old.si_gen) {
+		if (S_ISDIR(si_new.si_mode))
+			return (fits_dir_add_pass1(f, ino));
+		else
+			return (fits_file_add_genchange(f, ino));
+	}
+
+	if (S_ISDIR(si_new.si_mode)) {
+		ret = enum_dir(f, ino, NULL);
+		if (ret)
+			return (ret);
+	}
+
+	if (S_ISREG(si_new.si_mode)) {
+		struct fits_file ff;
+		ff.ff_ino = ino;
+		ff.ff_len = si_new.si_size;
+		ff.ff_fits = f;
+		ff.ff_path = NULL;
+		ff.ff_dirent = NULL;
+		ff.ff_last_byte = 0;
+
+		ret = fits_file_contents(f, ino, &ff);
+		fits_path_free(ff.ff_path);
+		if (ret)
+			return (ret);
+		if (si_new.si_size < si_old.si_size ||
+		    (si_new.si_size != si_old.si_size &&
+		    si_new.si_size != ff.ff_last_byte)) {
+			ret = fits_send_truncate(f, NULL, ino, si_new.si_size);
+			if (ret)
+				return (ret);
+		}
+	}
+
+	if (si_old.si_uid != si_new.si_uid || si_old.si_gid != si_new.si_gid) {
+		ret = fits_send_chown(f, NULL, ino, si_new.si_uid,
+		    si_new.si_gid);
+		if (ret)
+			return (ret);
+	}
+	if (si_old.si_mode != si_new.si_mode) {
+		ret = fits_send_chmod(f, NULL, ino, si_new.si_mode);
+		if (ret)
+			return (ret);
+	}
+
+	return (ret);
+}
--- /dev/null	2012-10-17 21:48:39.000000000 +0200
+++ new/usr/src/uts/common/fs/zfs/fits_pass2.c	2012-10-17 21:48:39.139138000 +0200
@@ -0,0 +1,529 @@
+/*
+ * 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/errno.h>
+#include <sys/stat.h>
+#include <sys/fits.h>
+#include <sys/fits_impl.h>
+
+struct fits_enum {
+	fits_t *fe_fits;
+	uint64_t fe_parent_ino;
+	uint64_t fe_del_dir_cnt;
+	uint64_t fe_put_back_cnt;
+	fits_dirent_t *fe_dirent_chain;
+};
+
+struct fits_file {
+	fits_t *ff_fits;
+	uint64_t ff_len;
+	uint64_t ff_last_byte;
+	uint64_t ff_ino;
+	fits_path_t *ff_path;
+	fits_dirent_t *ff_dirent;
+};
+
+static int fits_dirent_del_pass2(void *fits_enump, char *name, uint64_t ino);
+static int fits_dirent_mod_pass2(void *fits_enump, char *name,
+    uint64_t ino_old, uint64_t ino_new);
+static int fits_dirent_unmod_pass2(void *fits_enump, char *name, uint64_t ino);
+static int fits_dir_del_pass2(fits_t *f, uint64_t ino);
+static int fits_add_pass2(fits_t *f, uint64_t ino);
+static int fits_mod_pass2(fits_t *f, uint64_t ino);
+
+fits_ops_t _ops = {
+	.fits_dirent_del = fits_dirent_del_pass2,
+	.fits_dirent_mod = fits_dirent_mod_pass2,
+	.fits_dirent_unmod = fits_dirent_unmod_pass2,
+	.fits_file_add = fits_add_pass2,
+	.fits_file_mod = fits_mod_pass2,
+	.fits_dir_add = fits_add_pass2,
+	.fits_dir_del = fits_dir_del_pass2,
+	.fits_dir_mod = fits_mod_pass2
+};
+
+int
+fits_start2(fits_t *f, fits_ops_t **ops)
+{
+	f->f_pass = PASS_UNLINK;
+	*ops = &_ops;
+
+	return (0);
+}
+
+int
+fits_abort(fits_t *f)
+{
+	fits_send_fini(f);
+
+	return (0);
+}
+
+int
+fits_end(fits_t *f)
+{
+	int ret;
+	int ret2;
+
+	fits_send_end(f);
+
+	ret = fits_assert_count_empty(&f->f_link_add_cnt);
+	ret += fits_assert_count_empty(&f->f_del_dir_cnt);
+	ret += fits_assert_count_empty(&f->f_put_back_cnt);
+
+	printf("!fits: fits_end calling fits_abort\n");
+	ret2 = fits_abort(f);
+
+	return (ret ? ret : ret2);
+}
+
+static int
+dir_del(fits_t *f, uint64_t ino, uint64_t removed_entries)
+{
+	fits_info_t si;
+	uint64_t parent;
+	int ret;
+	int exists;
+	uint64_t new_count;
+	uint64_t left;
+
+	while (1) {
+		ret = fits_get_info(f, ino, FITS_OLD, &si, FI_ATTR_GEN |
+				    FI_ATTR_PARENT | FI_ATTR_NENTRIES);
+		if (ret)
+			return (ret);
+
+		if (si.si_parent > f->f_current_ino)
+			return (0);
+
+		if (removed_entries + 2 < si.si_nentries) /* '.' and '..' */
+			return (0);
+
+		fits_free_count(&f->f_del_dir_cnt, ino);
+		ret = fits_send_rmdir(f, NULL, ino);
+		if (ret)
+			return (ret);
+
+		parent = si.si_parent;
+		exists = fits_get_info(f, parent, FITS_NEW, &si,
+		    FI_ATTR_MODE);
+		if (exists && exists != ENOENT)
+			return (exists);
+
+		ret = fits_get_count(&f->f_put_back_cnt, parent, &left, NULL);
+		if (ret && ret != ENOENT)
+			return (ret);
+		if (left > 0) {
+			ret = fits_add_count(&f->f_put_back_cnt, parent, -1, 0,
+			    &left, NULL);
+			if (ret)
+				return (ret);
+		}
+		if ((int64_t)left < 0)
+			return (EINVAL);
+
+		if (exists == 0 && S_ISDIR(si.si_mode)) {
+			char *name;
+			uint64_t new_ino;
+			fits_info_t si_new;
+			int new;
+
+			new = fits_get_info(f, ino, FITS_NEW, &si_new,
+					    FI_ATTR_MODE);
+			if (new && new != ENOENT)
+				return (new);
+			ret = fits_find_entry(f, parent, ino, FITS_OLD, &name);
+			if (ret)
+				return (ret);
+
+			ret = fits_lookup_entry(f, parent, name,
+						FITS_NEW, &new_ino);
+			if (ret && ret != ENOENT) {
+				fits_free_name(name);
+				return (ret);
+			}
+			if (ret == 0 && new_ino != ino) {
+				fits_dirent_t dirent = {
+					.fd_name = name,
+					.fd_parent_ino = parent
+				};
+
+				ret = fits_send_rename_from_tempname(f, &dirent,
+				    ino, new_ino);
+				if (ret) {
+					fits_free_name(name);
+					return (ret);
+				}
+			} else if (ret == 0 && new_ino == ino &&
+			    !S_ISDIR(si_new.si_mode)) {
+				fits_dirent_t dirent = {
+					.fd_name = name,
+					.fd_parent_ino = parent
+				};
+
+				ret = fits_dirent_add_file(f, &dirent, ino,
+				    si_new.si_mode, 0);
+				if (ret) {
+					fits_free_name(name);
+					return (ret);
+				}
+			}
+			fits_free_name(name);
+		}
+
+		if (left == 0) {
+			fits_free_count(&f->f_put_back_cnt, parent);
+			if (exists == 0 && S_ISDIR(si.si_mode)) {
+				ret = fits_send_mtime_update(f, NULL, parent);
+				if (ret)
+					return (ret);
+			}
+		}
+
+		if (exists == 0 && S_ISDIR(si.si_mode))
+			return (0);
+
+		/* propagate deletion */
+		ret = fits_add_count(&f->f_del_dir_cnt, parent, 1, 0,
+		    &new_count, NULL);
+		if (ret)
+			return (ret);
+
+		ino = parent;
+		removed_entries = new_count;
+	}
+}
+
+static int
+enum_dir(fits_t *f, uint64_t ino, uint64_t *pput_back_cnt,
+    uint64_t *pdel_dir_cnt)
+{
+	int ret;
+	struct fits_enum fe = {
+		.fe_fits = f,
+		.fe_parent_ino = ino,
+		.fe_del_dir_cnt = 0,
+		.fe_put_back_cnt = 0
+	};
+
+	ret = fits_dir_contents(f, ino, &fe);
+	if (ret)
+		return (ret);
+
+	if (pput_back_cnt)
+		*pput_back_cnt = fe.fe_put_back_cnt;
+	if (pdel_dir_cnt)
+		*pdel_dir_cnt = fe.fe_del_dir_cnt;
+
+	if (fe.fe_put_back_cnt) {
+		ret = fits_add_count(&f->f_put_back_cnt, ino,
+		    fe.fe_put_back_cnt, 0, NULL, NULL);
+		if (ret)
+			return (ret);
+	}
+
+	return (0);
+}
+
+static int
+dirent_del_file(struct fits_enum *fe, fits_dirent_t *dirent,
+		uint64_t ino, uint64_t remains)
+{
+	int ret;
+
+	ret = fits_send_unlink(fe->fe_fits, dirent, ino);
+	if (ret)
+		return (ret);
+
+	if (remains != FITS_NO_INO) {
+		ret = fits_send_rename_from_tempname(fe->fe_fits, dirent,
+		    ino, remains);
+		if (ret)
+			return (ret);
+	}
+
+	fe->fe_del_dir_cnt++;
+
+	return (0);
+}
+
+static int
+dirent_del_dir(struct fits_enum *fe, fits_dirent_t *dirent, uint64_t ino,
+    uint64_t remains)
+{
+	int ret;
+	fits_info_t si;
+	fits_info_t si_old;
+	int new;
+	int old;
+	fits_t *f = fe->fe_fits;
+
+	new = fits_get_info(f, ino, FITS_NEW, &si, FI_ATTR_GEN |
+			    FI_ATTR_MODE);
+	if (new && new != ENOENT)
+		return (new);
+	old = fits_get_info(f, ino, FITS_OLD, &si_old,
+			    FI_ATTR_NENTRIES | FI_ATTR_GEN |
+			    FI_ATTR_PARENT);
+	if (old)
+		return (old);
+
+	/* new == 0 means the dir was renamed, which happened during pass 1 */
+	if (new == ENOENT ||
+	    (si.si_gen != si_old.si_gen && !S_ISDIR(si.si_mode))) {
+		uint64_t cnt;
+
+		if (ino > f->f_current_ino) {
+			++fe->fe_put_back_cnt;
+			return (0);
+		}
+
+		ret = fits_get_count(&f->f_del_dir_cnt, ino, &cnt,
+				    NULL);
+		if (ret && ret != ENOENT)
+			return (ret);
+		/* 2 for '.' and '..' */
+		if (cnt + 2 < si_old.si_nentries) {
+			++fe->fe_put_back_cnt;
+			return (0);
+		}
+
+		fits_free_count(&f->f_del_dir_cnt, ino);
+		ret = fits_send_rmdir(f, dirent, ino);
+		if (ret)
+			return (ret);
+	}
+	if (remains != FITS_NO_INO) {
+		ret = fits_send_rename_from_tempname(f, dirent, ino, remains);
+		if (ret)
+			return (ret);
+	}
+	fe->fe_del_dir_cnt++;
+	if (new == 0 && si.si_gen != si_old.si_gen && !S_ISDIR(si.si_mode) &&
+	    si_old.si_parent == dirent->fd_parent_ino) {
+		uint64_t parent = si_old.si_parent;
+		fits_info_t sip;
+		char *name = NULL;
+
+		ret = fits_get_info(f, parent, FITS_OLD, &sip,
+				    FI_ATTR_MODE);
+		if (ret && ret != ENOENT)
+			return (ret);
+		if (ret == 0 && S_ISDIR(sip.si_mode)) {
+			uint64_t old_ino;
+
+			ret = fits_find_entry(f, parent, ino, FITS_OLD, &name);
+			if (ret)
+				return (ret);
+
+			ret = fits_lookup_entry(f, parent, name,
+						FITS_NEW, &old_ino);
+			if (ret && ret != ENOENT) {
+				fits_free_name(name);
+				return (ret);
+			}
+			if (ret == 0) {
+				ret = fits_dirent_add_file(f, dirent, ino,
+				    si.si_mode, 0);
+				if (ret)
+					return (ret);
+			}
+		}
+	}
+
+	return (0);
+}
+
+static int
+dirent_del(struct fits_enum *fe, fits_dirent_t *dirent,
+    uint64_t ino, uint64_t remains)
+{
+	fits_info_t si;
+	int ret;
+
+	ret = fits_get_info(fe->fe_fits, ino, FITS_OLD, &si, FI_ATTR_MODE);
+	if (ret)
+		return (ret);
+
+	if (S_ISDIR(si.si_mode)) {
+		return (dirent_del_dir(fe, dirent, ino, remains));
+	} else {
+		return (dirent_del_file(fe, dirent, ino, remains));
+	}
+}
+
+static int
+fits_dirent_del_pass2(void *fits_enump, char *name, uint64_t ino)
+{
+	struct fits_enum *fe = fits_enump;
+	fits_dirent_t dirent = {
+		.fd_name = name,
+		.fd_parent_ino = fe->fe_parent_ino,
+		.fd_prev = fe->fe_dirent_chain,
+	};
+
+	return (dirent_del(fe, &dirent, ino, FITS_NO_INO));
+}
+
+static int
+fits_dirent_mod_pass2(void *fits_enump, char *name,
+    uint64_t ino_old, uint64_t ino_new)
+{
+	struct fits_enum *fe = fits_enump;
+	fits_dirent_t dirent = {
+		.fd_name = name,
+		.fd_parent_ino = fe->fe_parent_ino,
+		.fd_prev = fe->fe_dirent_chain,
+	};
+
+	return (dirent_del(fe, &dirent, ino_old, ino_new));
+}
+
+static int
+fits_dirent_unmod_pass2(void *fits_enump, char *name, uint64_t ino)
+{
+	struct fits_enum *fe = fits_enump;
+	fits_t *f = fe->fe_fits;
+	int ret;
+	uint64_t cnt;
+	fits_info_t si_old;
+	fits_info_t si_new;
+	fits_dirent_t dirent = {
+		.fd_name = name,
+		.fd_parent_ino = fe->fe_parent_ino,
+		.fd_prev = fe->fe_dirent_chain,
+	};
+
+	ret = fits_get_count(&f->f_link_add_cnt, ino, &cnt, NULL);
+	if (ret)
+		return (ret == ENOENT ? 0 : ret);
+
+	ret = fits_get_info(f, ino, FITS_OLD, &si_old, FI_ATTR_MODE);
+	if (ret)
+		return (ret);
+
+	if (S_ISDIR(si_old.si_mode)) {
+		return (dirent_del_dir(fe, &dirent, ino, 0));
+	}
+
+	ret = fits_get_info(f, ino, FITS_NEW, &si_new, FI_ATTR_MODE);
+	if (ret)
+		return (ret);
+	ret = fits_send_unlink(f, &dirent, ino);
+	if (ret)
+		return (ret);
+	if (S_ISDIR(si_new.si_mode)) {
+		fits_free_count(&f->f_link_add_cnt, ino);
+		ret = fits_send_rename_from_tempname(f, &dirent, ino, ino);
+		if (ret)
+			return (ret);
+		ret = fits_send_mtime_update(f, &dirent, ino);
+	} else {
+		ret = fits_dirent_add_file(f, &dirent, ino, si_new.si_mode, 0);
+	}
+
+	return (ret);
+}
+
+static int
+fits_add_pass2(fits_t *f, uint64_t ino)
+{
+	f->f_current_ino = ino;
+	f->f_current_path = NULL;
+
+	return (fits_send_mtime_update(f, NULL, ino));
+}
+
+static int
+fits_dir_del_pass2(fits_t *f, uint64_t ino)
+{
+	uint64_t put_back_cnt;
+	uint64_t del_dir_cnt;
+	uint64_t new_count;
+	int ret;
+
+	f->f_current_ino = ino;
+	f->f_current_path = NULL;
+
+	ret = enum_dir(f, ino, &put_back_cnt, &del_dir_cnt);
+	if (ret)
+		return (ret);
+
+	ret = fits_add_count(&f->f_del_dir_cnt, ino, del_dir_cnt,
+	    0, &new_count, NULL);
+	if (ret)
+		return (ret);
+
+	if (put_back_cnt == 0) {
+		ret = dir_del(f, ino, new_count);
+		if (ret)
+			return (ret);
+	}
+
+	return (0);
+}
+
+static int
+fits_mod_pass2(fits_t *f, uint64_t ino)
+{
+	fits_info_t si_old;
+	fits_info_t si_new;
+	int ret;
+	uint64_t put_back_cnt = 0;
+
+	f->f_current_ino = ino;
+	f->f_current_path = NULL;
+
+	ret = fits_get_info(f, ino, FITS_NEW, &si_new, FI_ATTR_SIZE |
+			    FI_ATTR_MODE | FI_ATTR_GEN | FI_ATTR_UID |
+			    FI_ATTR_GID | FI_ATTR_SIZE);
+	if (ret)
+		return (ret);
+	ret = fits_get_info(f, ino, FITS_OLD, &si_old, FI_ATTR_GEN |
+			    FI_ATTR_MODE | FI_ATTR_UID |
+			    FI_ATTR_GID | FI_ATTR_SIZE);
+	if (ret)
+		return (ret);
+
+	if (!(S_ISDIR(si_old.si_mode) && S_ISDIR(si_new.si_mode)) &&
+	    si_new.si_gen != si_old.si_gen) {
+		if (S_ISDIR(si_old.si_mode)) {
+			ret = fits_dir_del_pass2(f, ino);
+			if (ret)
+				return (ret);
+		}
+		return (fits_add_pass2(f, ino));
+	}
+
+	if (S_ISDIR(si_new.si_mode)) {
+		ret = enum_dir(f, ino, &put_back_cnt, NULL);
+		if (ret)
+			return (ret);
+	}
+
+	if (put_back_cnt)
+		return (0);
+
+	return (fits_send_mtime_update(f, NULL, ino));
+}
--- /dev/null	2012-10-17 21:48:39.000000000 +0200
+++ new/usr/src/uts/common/fs/zfs/fits_send.c	2012-10-17 21:48:39.246138000 +0200
@@ -0,0 +1,942 @@
+/*
+ * 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/mkdev.h>
+#include <sys/errno.h>
+#include <sys/types.h>
+#include <sys/fits.h>
+#include <sys/fits_impl.h>
+#include <sys/fits_crc32c.h>
+
+#define	TEMPNAME_PREFIX "fits-tempname-"
+/* 2^128 needs 39 digits in decimal */
+#define	TEMPNAME_SIZE (sizeof (TEMPNAME_PREFIX) + 39)
+
+void
+fits_send_init(fits_t *f)
+{
+	f->f_alloc_len = FITS_SEND_BUF_SIZE;
+	f->f_buf = kmem_alloc(f->f_alloc_len, KM_SLEEP);
+	f->f_size = 0;
+}
+
+void
+fits_send_fini(fits_t *f)
+{
+	kmem_free(f->f_buf, f->f_alloc_len);
+}
+
+static int
+fits_send_reserve(fits_t *f, void **buf, int len)
+{
+	int res = f->f_alloc_len - f->f_size;
+	if (len > res)
+		return (-E2BIG);
+	*buf = f->f_buf + f->f_size;
+	f->f_size += len;
+
+	return (0);
+}
+
+static int
+fits_send_put(fits_t *f, void *buf, int len)
+{
+	int ret;
+	void *p;
+
+	ret = fits_send_reserve(f, &p, len);
+	if (ret)
+		return (ret);
+
+	memcpy(p, buf, len);
+
+	return (0);
+}
+
+static int
+fits_send_put_attr(fits_t *f, uint16_t attr, void *buf, int len)
+{
+	fits_attr_header_t hdr;
+	int ret;
+
+	LE_OUT16(&hdr.fa_type, attr);
+	LE_OUT16(&hdr.fa_len, len);
+
+	ret = fits_send_put(f, &hdr, sizeof (hdr));
+	if (ret)
+		return (ret);
+	return (fits_send_put(f, buf, len));
+}
+
+static int
+fits_send_reserve_attr(fits_t *f, uint16_t attr, void **buf, int len)
+{
+	fits_attr_header_t hdr;
+	int ret;
+
+	LE_OUT16(&hdr.fa_type, attr);
+	LE_OUT16(&hdr.fa_len, len);
+
+	ret = fits_send_put(f, &hdr, sizeof (hdr));
+	if (ret)
+		return (ret);
+	return (fits_send_reserve(f, buf, len));
+}
+
+static int
+fits_send_put_u64(fits_t *f, uint16_t attr, uint64_t val)
+{
+	uint64_t v;
+
+	LE_OUT64(&v, val);
+	return (fits_send_put_attr(f, attr, &v, sizeof (v)));
+}
+
+static int
+fits_send_put_time(fits_t *f, uint16_t attr, fits_time_t *t)
+{
+	char buf[12];
+
+	LE_OUT64(buf, t->st_sec);
+	LE_OUT32(buf + 8, t->st_nsec);
+
+	return (fits_send_put_attr(f, attr, buf, sizeof (buf)));
+}
+
+static int
+fits_cmd_start(fits_t *f, uint16_t cmd)
+{
+	fits_cmd_header_t ch;
+
+	memset(&ch, 0, sizeof (ch));
+	LE_OUT16(&ch.fc_cmd, cmd);
+	f->f_size = 0;
+	return (fits_send_put(f, &ch, sizeof (ch)));
+}
+
+static int
+fits_cmd_send(fits_t *f)
+{
+	fits_cmd_header_t *ch;
+	uint32_t crc;
+	int ret;
+
+	ch = (fits_cmd_header_t *)f->f_buf;
+	LE_OUT32(&ch->fc_len, f->f_size - sizeof (*ch));
+	ch->fc_crc = 0;
+
+	crc = fits_crc32c(0, f->f_buf, f->f_size);
+	LE_OUT32(&ch->fc_crc, crc);
+
+	ret = fits_write(f, f->f_buf, f->f_size);
+	f->f_size = 0;
+
+	return (ret);
+}
+
+static int
+fits_send_stream_header(fits_t *f)
+{
+	fits_stream_header_t header;
+
+	strcpy(header.fs_magic, FITS_SEND_STREAM_MAGIC);
+	LE_OUT32(&header.fs_version, FITS_SEND_STREAM_VERSION);
+
+	return (fits_write(f, (uint8_t *)&header, sizeof (header)));
+}
+
+static void
+tempname(uint64_t ino, char *buf, int maxlen)
+{
+	int l = sizeof (TEMPNAME_PREFIX) - 1;
+	memcpy(buf, TEMPNAME_PREFIX, MIN(maxlen, l));
+	snprintf(buf + l, maxlen - l, "%llu", (long long)ino);
+}
+
+static void
+path_add_name(fits_path_t **fp, char *name, int namelen)
+{
+	fits_path_t *new;
+
+	new = kmem_alloc(sizeof (*new) + namelen + 1, KM_SLEEP);
+	new->fp_next = *fp;
+	new->fp_len = namelen + 1;
+	new->fp_total_len = namelen + 1;
+	if (*fp)
+		new->fp_total_len += (*fp)->fp_total_len;
+	memcpy(new->fp_buf, name, namelen);
+	new->fp_buf[namelen] = '\0';
+	*fp = new;
+}
+
+static void
+path_copy(fits_path_t *fp, char *b)
+{
+	fits_path_t *cur;
+
+	for (cur = fp; cur; cur = cur->fp_next) {
+		*b = '/';
+		memcpy(b + 1, cur->fp_buf, cur->fp_len - 1);
+		b += cur->fp_len;
+	}
+}
+
+static void
+path2buf(fits_path_t *fp, char **buf, int *buf_len)
+{
+	char *b;
+
+	*buf_len = fp->fp_total_len + 1; /* one for the trailing 0-byte */
+	*buf = b = kmem_alloc(*buf_len, KM_SLEEP);
+
+	path_copy(fp, b);
+
+	b[*buf_len - 1] = '\0';
+}
+
+static int
+put_path(fits_t *f, uint16_t attr, fits_path_t *fp)
+{
+	int ret;
+	void *p;
+
+	ret = fits_send_reserve_attr(f, attr, &p, fp->fp_total_len);
+	if (ret)
+		return (ret);
+	path_copy(fp, p);
+
+	return (0);
+}
+
+void
+fits_path_free(fits_path_t *fp)
+{
+	fits_path_t *next;
+	while (fp) {
+		next = fp->fp_next;
+		kmem_free(fp, fp->fp_len + sizeof (*fp));
+		fp = next;
+	}
+}
+
+static int
+is_ino_run(fits_t *f, uint64_t ino)
+{
+	int ret;
+	fits_info_t si;
+
+	while (1) {
+		if (ino > f->f_current_ino)
+			return (0);
+		ret = fits_get_info(f, ino, FITS_OLD, &si, 0);
+		if (ret && ret != ENOENT)
+			return (ret);
+		if (ret != ENOENT)
+			break;
+		ret = fits_get_info(f, ino, FITS_NEW, &si, FI_ATTR_PARENT);
+		if (ret && ret != ENOENT)
+			return (ret);
+		if (ret)
+			return (0);	/* ignore for now */
+		ino = si.si_parent;
+	}
+	return (1);
+}
+
+static int
+build_path(fits_t *f, fits_dirent_t *dirent, uint64_t ino,
+    int devise_tempname, fits_which_t which_in, fits_path_t **fp)
+{
+	int ret = 0;
+	fits_dirent_t *de;
+	fits_info_t si;
+	fits_which_t which;
+	fits_dirent_t temp_dirent;
+	char temp_buf[TEMPNAME_SIZE];
+
+	if (devise_tempname) {
+		if (!dirent)
+			return (EINVAL);
+		temp_dirent = *dirent;
+		tempname(ino, temp_buf, sizeof (temp_buf));
+		temp_dirent.fd_name = temp_buf;
+		dirent = &temp_dirent;
+	}
+
+	*fp = NULL;
+
+	for (de = dirent; de; de = de->fd_prev) {
+		path_add_name(fp, de->fd_name, strlen(de->fd_name));
+		ino = de->fd_parent_ino;
+	}
+
+	/*
+	 * XXX TODO check if f->f_current_path is set. if yes, use it instead.
+	 * otherwise save result of loop below to f_current_path
+	 */
+	while (1) {
+		int namebuflen;
+		char *name;
+		char *t_name;
+		uint64_t old_parent;
+		uint64_t new_parent;
+		uint64_t old_gen = 0;
+		uint64_t new_gen = 0;
+		uint64_t parent;
+		int check_tempname;
+		uint64_t old_mode = 0;
+
+		old_parent = 0;
+		new_parent = 0;
+		check_tempname = 0;
+		ret = fits_get_info(f, ino, FITS_OLD, &si, FI_ATTR_PARENT |
+		    FI_ATTR_GEN | FI_ATTR_MODE);
+		if (ret && ret != ENOENT)
+			return (ret);
+		if (ret == 0) {
+			old_parent = si.si_parent;
+			old_gen = si.si_gen;
+			old_mode = si.si_mode;
+		}
+		ret = fits_get_info(f, ino, FITS_NEW, &si, FI_ATTR_PARENT |
+		    FI_ATTR_GEN | FI_ATTR_MODE);
+		if (ret && ret != ENOENT)
+			return (ret);
+		if (ret == 0) {
+			new_parent = si.si_parent;
+			new_gen = si.si_gen;
+		}
+		if (old_parent && new_parent && old_gen != new_gen &&
+		    !(S_ISDIR(old_mode) && S_ISDIR(si.si_mode))) {
+			if (which_in == FITS_OLD) {
+				new_parent = 0;
+			} else if (which_in == FITS_NEW) {
+				old_parent = 0;
+				if (S_ISDIR(si.si_mode))
+					check_tempname = 1;
+			}
+		}
+
+		if (f->f_pass == PASS_LINK) {
+			if (old_parent && !new_parent) {
+				which = FITS_OLD;
+			} else if (!old_parent && new_parent) {
+				which = FITS_NEW;
+			} else if (is_ino_run(f, new_parent)) {
+				check_tempname = 1;
+				which = FITS_NEW;
+			} else {
+				which = FITS_OLD;
+			}
+		} else {
+			if (old_parent && !new_parent) {
+				which = FITS_OLD;
+			} else {
+				check_tempname = 1;
+				which = FITS_NEW;
+			}
+		}
+		if (which == FITS_OLD)
+			parent = old_parent;
+		else
+			parent = new_parent;
+		if (parent == ino)
+			break;
+		ret = fits_find_entry(f, parent, ino, which, &t_name);
+		if (ret)
+			return (ret);
+		name = strdup(t_name);
+		fits_free_name(t_name);
+		namebuflen = strlen(name) + 1;
+		if (check_tempname) {
+			fits_info_t si_old;
+			fits_info_t si_new;
+
+			ret = fits_get_info(f, parent, FITS_OLD,
+					    &si_old, FI_ATTR_GEN);
+			if (ret && ret != ENOENT)
+				return (ret);
+			if (ret == 0) {
+				ret = fits_get_info(f, parent, FITS_NEW,
+						    &si_new, FI_ATTR_GEN);
+				if (ret)
+					return (ret);
+				if (si_old.si_gen != si_new.si_gen)
+					check_tempname = 0;
+			} else {
+				check_tempname = 0;
+			}
+		}
+		if (check_tempname) {
+			uint64_t old_ino;
+
+			ret = fits_lookup_entry(f, parent, name,
+						FITS_OLD, &old_ino);
+			if (ret && ret != ENOENT) {
+				fits_free_name(name);
+				return (ret);
+			}
+			if ((ret == 0 && old_ino != ino) ||
+			    (ret == 0 && S_ISDIR(si.si_mode) &&
+			    !S_ISDIR(old_mode) && old_ino == ino)) {
+				int ret;
+				uint64_t cnt = 1;
+
+				if (f->f_pass == PASS_UNLINK &&
+				    new_parent < f->f_current_ino) {
+					ret = fits_get_count(&f->f_put_back_cnt,
+					    old_ino, &cnt, NULL);
+					if (ret && ret != ENOENT)
+						return (ret);
+				}
+				if (cnt) {
+					kmem_free(name, namebuflen);
+					namebuflen = TEMPNAME_SIZE;
+					name = kmem_alloc(namebuflen, KM_SLEEP);
+					tempname(ino, name, namebuflen);
+				}
+			}
+		}
+		ino = parent;
+		path_add_name(fp, name, strlen(name));
+		kmem_free(name, namebuflen);
+	}
+	if (*fp == NULL)
+		path_add_name(fp, "", 0);
+
+	return (0);
+}
+
+int
+fits_send_start(fits_t *f)
+{
+	int ret;
+	uint8_t o_uuid[16];
+	uint8_t n_uuid[16];
+	uint64_t o_ctrans;
+	uint64_t n_ctrans;
+	char *path = NULL;
+	int len;
+	char *p;
+	int cmd = FITS_CMD_SUBVOL;
+
+	ret = fits_send_stream_header(f);
+	if (ret) {
+		fits_abort(f);
+		return (ret);
+	}
+
+	if ((ret = fits_get_uuid(f, FITS_NEW, n_uuid)) ||
+	    (ret = fits_get_ctransid(f, FITS_NEW, &n_ctrans)) ||
+	    (ret = fits_get_snapname(f, FITS_NEW, &path, &len)))
+		goto out;
+	/* for now, strip the pool name */
+	if ((p = strchr(path, '/')))
+		++p;
+	else
+		p = path;
+	ret = fits_get_uuid(f, FITS_OLD, o_uuid);
+	if (ret && ret != ENOENT)
+		goto out;
+	if (ret == 0) {
+		ret = fits_get_ctransid(f, FITS_OLD, &o_ctrans);
+		if (ret)
+			goto out;
+		cmd = FITS_CMD_SNAPSHOT;
+	}
+	if ((ret = fits_cmd_start(f, cmd)) ||
+	    (ret = fits_send_put_attr(f, FITS_ATTR_PATH, p, strlen(p))) ||
+	    (ret = fits_send_put_u64(f, FITS_ATTR_CTRANSID, n_ctrans)) ||
+	    (ret = fits_send_put_attr(f, FITS_ATTR_UUID, n_uuid, 16)))
+		goto out;
+	if (cmd == FITS_CMD_SNAPSHOT) {
+		if ((ret = fits_send_put_u64(f, FITS_ATTR_CLONE_CTRANSID,
+		    o_ctrans)) ||
+		    (ret = fits_send_put_attr(f, FITS_ATTR_CLONE_UUID,
+		    o_uuid, 16)))
+			goto out;
+	}
+	ret = fits_cmd_send(f);
+
+out:
+	kmem_free(path, len);
+
+	return (ret);
+}
+
+int
+fits_send_create_file(fits_t *f, fits_dirent_t *dirent, uint64_t ino,
+    int devise_tempname, fits_path_t **path_ret)
+{
+	fits_path_t *path = NULL;
+	fits_info_t si;
+	int ret;
+	int send_rdev = 0;
+	int cmd;
+	uint64_t rdev = 0;
+	char *symlink = NULL;
+	int symlen = 0;
+
+	ret = build_path(f, dirent, ino, devise_tempname, FITS_NEW, &path);
+	if (ret)
+		goto out;
+
+	ret = fits_get_info(f, ino, FITS_NEW, &si,
+			    FI_ATTR_MODE | FI_ATTR_UID | FI_ATTR_GID);
+	if (ret)
+		goto out;
+
+	if (S_ISREG(si.si_mode)) {
+		cmd = FITS_CMD_MKFILE;
+	} else if (S_ISDIR(si.si_mode)) {
+		cmd = FITS_CMD_MKDIR;
+	} else if (S_ISLNK(si.si_mode)) {
+		cmd = FITS_CMD_SYMLINK;
+		ret = fits_read_symlink(f, ino, FITS_NEW, &symlink, &symlen);
+		if (ret)
+			goto out;
+	} else if (S_ISCHR(si.si_mode) || S_ISBLK(si.si_mode)) {
+		cmd = FITS_CMD_MKNOD;
+		send_rdev = 1;
+	} else if (S_ISFIFO(si.si_mode)) {
+		cmd = FITS_CMD_MKFIFO;
+	} else if (S_ISSOCK(si.si_mode)) {
+		cmd = FITS_CMD_MKSOCK;
+	} else {
+		/* unknown file type, ignore for now */
+		printf("!send_create_file: ignore file with mode 0%lo\n",
+			(unsigned long)si.si_mode);
+		return (0);
+	}
+
+	if (send_rdev) {
+		fits_info_t sirdev;
+		uint64_t r_major;
+		uint64_t r_minor;
+		ret = fits_get_info(f, ino, FITS_NEW, &sirdev, FI_ATTR_RDEV);
+		if (ret)
+			goto out;
+		rdev = sirdev.si_rdev;
+
+		/* XXX hardcodedly transform rdev to linux form */
+		r_major = rdev >> 32;
+		r_minor = rdev & 0xffffffful;
+		rdev = ((r_minor & 0xff) | ((r_major & 0xfff) << 8) |
+		    ((r_minor >> 8) << 20) | ((r_major >> 12) << 44));
+	}
+	/* send MKFILE */
+	if ((ret = fits_cmd_start(f, cmd)) ||
+	    (ret = put_path(f, FITS_ATTR_PATH, path)) ||
+	    (ret = fits_send_put_u64(f, FITS_ATTR_INO, ino)))
+		goto out;
+	if (send_rdev) {
+		ret = fits_send_put_u64(f, FITS_ATTR_RDEV, rdev);
+		if (ret)
+			goto out;
+		ret = fits_send_put_u64(f, FITS_ATTR_MODE, si.si_mode);
+		if (ret)
+			goto out;
+	}
+	if (S_ISLNK(si.si_mode)) {
+		ret = fits_send_put_attr(f, FITS_ATTR_PATH_LINK,
+					symlink, strlen(symlink));
+		if (ret)
+			goto out;
+	}
+	if ((ret = fits_cmd_send(f)))
+		goto out;
+
+	/* send CHOWN */
+	if ((ret = fits_cmd_start(f, FITS_CMD_CHOWN)) ||
+	    (ret = put_path(f, FITS_ATTR_PATH, path)) ||
+	    (ret = fits_send_put_u64(f, FITS_ATTR_UID, si.si_uid)) ||
+	    (ret = fits_send_put_u64(f, FITS_ATTR_GID, si.si_gid)) ||
+	    (ret = fits_cmd_send(f)))
+		goto out;
+
+	/* send CHMOD */
+	if ((ret = fits_cmd_start(f, FITS_CMD_CHMOD)) ||
+	    (ret = put_path(f, FITS_ATTR_PATH, path)) ||
+	    (ret = fits_send_put_u64(f, FITS_ATTR_MODE,
+	    si.si_mode & 0xfff)) ||
+	    (ret = fits_cmd_send(f)))
+		goto out;
+
+out:
+	if (ret == 0 && path_ret)
+		*path_ret = path;
+	else
+		fits_path_free(path);
+	if (symlink)
+		kmem_free(symlink, symlen);
+	return (ret);
+}
+
+int
+fits_send_link(fits_t *f, fits_dirent_t *new_dirent, uint64_t ino,
+    uint64_t old_parent_ino, fits_which_t which, int devise_tempname)
+{
+	fits_path_t *new_path = NULL;
+	fits_path_t *old_path = NULL;
+	int ret;
+	fits_dirent_t old_dirent = {
+		.fd_name = NULL,
+		.fd_parent_ino = old_parent_ino,
+		.fd_prev = NULL,
+	};
+
+	ret = fits_find_entry(f, old_parent_ino, ino, which,
+	    &old_dirent.fd_name);
+	if (ret)
+		return (ret);
+	ret = build_path(f, &old_dirent, ino, 0, FITS_OLD, &old_path);
+	if (ret)
+		goto out;
+
+	ret = build_path(f, new_dirent, ino, devise_tempname, FITS_NEW,
+	    &new_path);
+	if (ret)
+		goto out;
+
+	if ((ret = fits_cmd_start(f, FITS_CMD_LINK)) ||
+	    (ret = put_path(f, FITS_ATTR_PATH_LINK, old_path)) ||
+	    (ret = put_path(f, FITS_ATTR_PATH, new_path)) ||
+	    (ret = fits_cmd_send(f)))
+		goto out;
+
+	if (f->f_pass == PASS_UNLINK)
+		ret = fits_send_mtime_update(f, new_dirent, ino);
+out:
+	if (old_dirent.fd_name)
+		kmem_free(old_dirent.fd_name, strlen(old_dirent.fd_name) + 1);
+	fits_path_free(old_path);
+	fits_path_free(new_path);
+	return (ret);
+}
+
+int
+fits_send_mkdir(fits_t *f, fits_dirent_t *dirent,
+		uint64_t ino, int devise_tempname)
+{
+	fits_path_t *path = NULL;
+	fits_info_t si;
+	int ret;
+
+	ret = build_path(f, dirent, ino, devise_tempname, FITS_NEW, &path);
+	if (ret)
+		goto out;
+
+	ret = fits_get_info(f, ino, FITS_NEW, &si,
+			    FI_ATTR_UID | FI_ATTR_GID | FI_ATTR_MODE);
+	if (ret)
+		goto out;
+
+	if (path->fp_total_len != 1) {
+		/* don't send an mkdir for the root, but send chown/chmod */
+		if ((ret = fits_cmd_start(f, FITS_CMD_MKDIR)) ||
+		    (ret = put_path(f, FITS_ATTR_PATH, path)) ||
+		    (ret = fits_send_put_u64(f, FITS_ATTR_INO, ino)) ||
+		    (ret = fits_cmd_send(f)))
+			goto out;
+	}
+
+	/* send CHOWN */
+	if ((ret = fits_cmd_start(f, FITS_CMD_CHOWN)) ||
+	    (ret = put_path(f, FITS_ATTR_PATH, path)) ||
+	    (ret = fits_send_put_u64(f, FITS_ATTR_UID, si.si_uid)) ||
+	    (ret = fits_send_put_u64(f, FITS_ATTR_GID, si.si_gid)) ||
+	    (ret = fits_cmd_send(f)))
+		goto out;
+
+	/* send CHMOD */
+	if ((ret = fits_cmd_start(f, FITS_CMD_CHMOD)) ||
+	    (ret = put_path(f, FITS_ATTR_PATH, path)) ||
+	    (ret = fits_send_put_u64(f, FITS_ATTR_MODE,
+	    si.si_mode & 0xfff)) ||
+	    (ret = fits_cmd_send(f)))
+		goto out;
+
+out:
+	fits_path_free(path);
+	return (ret);
+}
+
+/* this one is only used for directory renames */
+int
+fits_send_rename(fits_t *f, fits_dirent_t *new_dirent, uint64_t ino,
+    uint64_t old_parent_ino, int devise_tempname)
+{
+	fits_path_t *new_path = NULL;
+	fits_path_t *old_path = NULL;
+	int ret;
+	fits_dirent_t old_dirent = {
+		.fd_name = NULL,
+		.fd_parent_ino = old_parent_ino,
+		.fd_prev = NULL,
+	};
+
+	ret = fits_find_entry(f, old_parent_ino, ino, FITS_OLD,
+	    &old_dirent.fd_name);
+	if (ret)
+		return (ret);
+	ret = build_path(f, &old_dirent, ino, 0, FITS_OLD, &old_path);
+	if (ret)
+		goto out;
+
+	ret = build_path(f, new_dirent, ino, devise_tempname, FITS_NEW,
+	    &new_path);
+	if (ret)
+		goto out;
+
+	if ((ret = fits_cmd_start(f, FITS_CMD_RENAME)) ||
+	    (ret = put_path(f, FITS_ATTR_PATH, old_path)) ||
+	    (ret = put_path(f, FITS_ATTR_PATH_TO, new_path)) ||
+	    (ret = fits_cmd_send(f)))
+		goto out;
+out:
+	fits_path_free(old_path);
+	fits_path_free(new_path);
+	return (ret);
+}
+
+int
+fits_send_rename_from_tempname(fits_t *f, fits_dirent_t *dirent,
+    uint64_t ino, uint64_t old)
+{
+	char buf[TEMPNAME_SIZE];
+	fits_path_t *new_path = NULL;
+	fits_path_t *old_path = NULL;
+	int ret;
+	fits_dirent_t old_dirent;
+
+	tempname(old, buf, sizeof (buf));
+	old_dirent = *dirent;
+	old_dirent.fd_name = buf;
+
+	ret = build_path(f, &old_dirent, old, 0, FITS_OLD, &old_path);
+	if (ret)
+		goto out;
+	ret = build_path(f, dirent, ino, 0, FITS_NEW, &new_path);
+	if (ret)
+		goto out;
+
+	if ((ret = fits_cmd_start(f, FITS_CMD_RENAME)) ||
+	    (ret = put_path(f, FITS_ATTR_PATH, old_path)) ||
+	    (ret = put_path(f, FITS_ATTR_PATH_TO, new_path)) ||
+	    (ret = fits_cmd_send(f)))
+		goto out;
+
+	ret = fits_send_mtime_update(f, dirent, old);
+
+out:
+	fits_path_free(old_path);
+	fits_path_free(new_path);
+	return (ret);
+}
+
+int
+fits_send_unlink(fits_t *f, fits_dirent_t *dirent, uint64_t ino)
+{
+	fits_path_t *path = NULL;
+	int ret;
+
+	ret = build_path(f, dirent, ino, 0, FITS_OLD, &path);
+	if (ret)
+		goto out;
+
+	if ((ret = fits_cmd_start(f, FITS_CMD_UNLINK)) ||
+	    (ret = put_path(f, FITS_ATTR_PATH, path)) ||
+	    (ret = fits_cmd_send(f)))
+		goto out;
+
+out:
+	fits_path_free(path);
+	return (ret);
+}
+
+int
+fits_send_rmdir(fits_t *f, fits_dirent_t *dirent, uint64_t ino)
+{
+	fits_path_t *path;
+	int ret;
+
+	ret = build_path(f, dirent, ino, 0, FITS_OLD, &path);
+	if (ret)
+		goto out;
+
+	if ((ret = fits_cmd_start(f, FITS_CMD_RMDIR)) ||
+	    (ret = put_path(f, FITS_ATTR_PATH, path)) ||
+	    (ret = fits_cmd_send(f)))
+		goto out;
+
+out:
+	fits_path_free(path);
+	return (ret);
+}
+
+int
+fits_send_file_data(fits_t *f, fits_path_t **path_p,
+		    fits_dirent_t *dirent, uint64_t ino,
+		    uint64_t off, uint64_t len, void *data)
+{
+	int ret = 0;
+
+	if (!*path_p) {
+		ret = build_path(f, dirent, ino, 0, FITS_NEW, path_p);
+		if (ret)
+			return (ret);
+	}
+
+	while (len) {
+		uint64_t l = MIN(len, FITS_SEND_READ_SIZE);
+
+		if ((ret = fits_cmd_start(f, FITS_CMD_WRITE)) ||
+		    (ret = put_path(f, FITS_ATTR_PATH, *path_p)) ||
+		    (ret = fits_send_put_u64(f, FITS_ATTR_FILE_OFFSET, off))||
+		    (ret = fits_send_put_attr(f, FITS_ATTR_DATA, data, l)) ||
+		    (ret = fits_cmd_send(f)))
+			goto out;
+		data += l;
+		off += l;
+		len -= l;
+	}
+
+out:
+	return (ret);
+}
+
+int
+fits_send_mtime_update(fits_t *f, fits_dirent_t *dirent, uint64_t ino)
+{
+	fits_path_t *path = NULL;
+	int ret;
+	fits_info_t si;
+
+	ret = fits_get_info(f, ino, FITS_NEW, &si,
+			    FI_ATTR_ATIME | FI_ATTR_MTIME |
+			    FI_ATTR_CTIME | FI_ATTR_OTIME);
+	if (ret) {
+		if (ret == ENOENT)
+			ret = 0;
+		goto out;
+	}
+
+	ret = build_path(f, dirent, ino, 0, FITS_NEW, &path);
+	if (ret)
+		goto out;
+
+	if ((ret = fits_cmd_start(f, FITS_CMD_UTIMES)) ||
+	    (ret = put_path(f, FITS_ATTR_PATH, path)) ||
+	    (ret = fits_send_put_time(f, FITS_ATTR_ATIME, &si.si_atime)) ||
+	    (ret = fits_send_put_time(f, FITS_ATTR_MTIME, &si.si_mtime)) ||
+	    (ret = fits_send_put_time(f, FITS_ATTR_CTIME, &si.si_ctime)) ||
+	    (ret = fits_send_put_time(f, FITS_ATTR_OTIME, &si.si_otime)) ||
+	    (ret = fits_cmd_send(f)))
+		goto out;
+
+out:
+	fits_path_free(path);
+	return (ret);
+}
+
+int
+fits_send_truncate(fits_t *f, fits_dirent_t *dirent, uint64_t ino,
+    uint64_t new_size)
+{
+	fits_path_t *path = NULL;
+	int ret;
+
+	ret = build_path(f, dirent, ino, 0, FITS_NEW, &path);
+	if (ret)
+		return (ret);
+
+	if ((ret = fits_cmd_start(f, FITS_CMD_TRUNCATE)) ||
+	    (ret = put_path(f, FITS_ATTR_PATH, path)) ||
+	    (ret = fits_send_put_u64(f, FITS_ATTR_SIZE, new_size)) ||
+	    (ret = fits_cmd_send(f)))
+		goto out;
+
+out:
+	fits_path_free(path);
+	return (ret);
+}
+
+int
+fits_send_chown(fits_t *f, fits_dirent_t *dirent, uint64_t ino,
+		    uint64_t new_uid, uint64_t new_gid)
+{
+	fits_path_t *path = NULL;
+	int ret;
+
+	ret = build_path(f, dirent, ino, 0, FITS_NEW, &path);
+	if (ret)
+		return (ret);
+
+	if ((ret = fits_cmd_start(f, FITS_CMD_CHOWN)) ||
+	    (ret = put_path(f, FITS_ATTR_PATH, path)) ||
+	    (ret = fits_send_put_u64(f, FITS_ATTR_UID, new_uid)) ||
+	    (ret = fits_send_put_u64(f, FITS_ATTR_GID, new_gid)) ||
+	    (ret = fits_cmd_send(f)))
+		goto out;
+
+out:
+	fits_path_free(path);
+	return (ret);
+}
+
+int
+fits_send_chmod(fits_t *f, fits_dirent_t *dirent, uint64_t ino,
+		    uint64_t new_mode)
+{
+	fits_path_t *path = NULL;
+	int ret;
+
+	ret = build_path(f, dirent, ino, 0, FITS_NEW, &path);
+	if (ret)
+		return (ret);
+
+	if ((ret = fits_cmd_start(f, FITS_CMD_CHMOD)) ||
+	    (ret = put_path(f, FITS_ATTR_PATH, path)) ||
+	    (ret = fits_send_put_u64(f, FITS_ATTR_MODE, new_mode)) ||
+	    (ret = fits_cmd_send(f)))
+		goto out;
+
+out:
+	fits_path_free(path);
+	return (ret);
+}
+
+int
+fits_send_end(fits_t *f)
+{
+	int ret;
+
+	if ((ret = fits_cmd_start(f, FITS_CMD_END)) ||
+	    (ret = fits_cmd_send(f)))
+		goto out;
+
+out:
+	return (ret);
+}
--- old/usr/src/uts/common/fs/zfs/sys/dsl_dataset.h	2012-10-17 21:48:39.355135000 +0200
+++ new/usr/src/uts/common/fs/zfs/sys/dsl_dataset.h	2012-10-17 21:48:39.361138000 +0200
@@ -202,6 +202,7 @@
 int dsl_dataset_own_obj(struct dsl_pool *dp, uint64_t dsobj,
     boolean_t inconsistentok, void *tag, dsl_dataset_t **dsp);
 void dsl_dataset_name(dsl_dataset_t *ds, char *name);
+int dsl_dataset_namelen(dsl_dataset_t *ds);
 void dsl_dataset_rele(dsl_dataset_t *ds, void *tag);
 void dsl_dataset_disown(dsl_dataset_t *ds, void *tag);
 void dsl_dataset_drop_ref(dsl_dataset_t *ds, void *tag);
--- /dev/null	2012-10-17 21:48:39.000000000 +0200
+++ new/usr/src/uts/common/fs/zfs/sys/fits.h	2012-10-17 21:48:39.628019000 +0200
@@ -0,0 +1,122 @@
+/*
+ * 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 Alexander Block. All rights reserved.
+ * Copyright (c) 2012 STRATO AG. All rights reserved.
+ */
+
+#ifndef	_SYS_FITS_H
+#define	_SYS_FITS_H
+
+#include <sys/inttypes.h>
+#include <sys/types.h>
+#include <sys/dmu.h>
+#include <sys/vnode.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+#define	FITS_SEND_STREAM_MAGIC "btrfs-stream"
+#define	FITS_SEND_STREAM_VERSION 1
+
+#define	FITS_SEND_BUF_SIZE 65536
+#define	FITS_SEND_READ_SIZE 49152
+
+typedef struct _fits_stream_header {
+	char		fs_magic[sizeof (FITS_SEND_STREAM_MAGIC)];
+	uint32_t	fs_version;
+} __attribute__((__packed__)) fits_stream_header_t;
+
+typedef struct _fits_cmd_header {
+	/* len of the payload, not including header */
+	uint32_t	fc_len;
+	uint16_t	fc_cmd;
+	/* the crc includes the header, but with fc_crc assumed as 0 */
+	uint32_t	fc_crc;
+} __attribute__((__packed__)) fits_cmd_header_t;
+
+typedef struct _fits_attr_header {
+	uint16_t	fa_type;
+	/* len of the payload, not including header */
+	uint16_t	fa_len;
+} __attribute__((__packed__)) fits_attr_header_t;
+
+/* commands */
+#define	FITS_CMD_SUBVOL		1
+#define	FITS_CMD_SNAPSHOT	2
+#define	FITS_CMD_MKFILE		3
+#define	FITS_CMD_MKDIR		4
+#define	FITS_CMD_MKNOD		5
+#define	FITS_CMD_MKFIFO		6
+#define	FITS_CMD_MKSOCK		7
+#define	FITS_CMD_SYMLINK	8
+#define	FITS_CMD_RENAME		9
+#define	FITS_CMD_LINK		10
+#define	FITS_CMD_UNLINK		11
+#define	FITS_CMD_RMDIR		12
+#define	FITS_CMD_SET_XATTR	13
+#define	FITS_CMD_REMOVE_XATTR	14
+#define	FITS_CMD_WRITE		15
+#define	FITS_CMD_CLONE		16
+#define	FITS_CMD_TRUNCATE	17
+#define	FITS_CMD_CHMOD		18
+#define	FITS_CMD_CHOWN		19
+#define	FITS_CMD_UTIMES		20
+#define	FITS_CMD_END		21
+#define	FITS_CMD_MAX		21
+
+/* attributes */
+#define	FITS_ATTR_UUID			1
+#define	FITS_ATTR_CTRANSID		2
+#define	FITS_ATTR_INO			3
+#define	FITS_ATTR_SIZE			4
+#define	FITS_ATTR_MODE			5
+#define	FITS_ATTR_UID			6
+#define	FITS_ATTR_GID			7
+#define	FITS_ATTR_RDEV			8
+#define	FITS_ATTR_CTIME			9
+#define	FITS_ATTR_MTIME			10
+#define	FITS_ATTR_ATIME			11
+#define	FITS_ATTR_OTIME			12
+#define	FITS_ATTR_XATTR_NAME		13
+#define	FITS_ATTR_XATTR_DATA		14
+#define	FITS_ATTR_PATH			15
+#define	FITS_ATTR_PATH_TO		16
+#define	FITS_ATTR_PATH_LINK		17
+#define	FITS_ATTR_FILE_OFFSET		18
+#define	FITS_ATTR_DATA			19
+#define	FITS_ATTR_CLONE_UUID		20
+#define	FITS_ATTR_CLONE_CTRANSID	21
+#define	FITS_ATTR_CLONE_PATH		22
+#define	FITS_ATTR_CLONE_OFFSET		23
+#define	FITS_ATTR_CLONE_LEN		24
+#define	FITS_ATTR_MAX			24
+
+int fits_send(objset_t *tosnap, objset_t *fromsnap, int outfd, vnode_t *vp,
+    offset_t *off);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* _SYS_FITS_H */
--- /dev/null	2012-10-17 21:48:39.000000000 +0200
+++ new/usr/src/uts/common/fs/zfs/sys/fits_crc32c.h	2012-10-17 21:48:39.752024000 +0200
@@ -0,0 +1,30 @@
+/*
+ * 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
+ */
+
+#ifndef	_SYS_FITS_CRC32C_H
+#define	_SYS_FITS_CRC32C_H
+
+#include <sys/inttypes.h>
+#include <sys/types.h>
+
+uint32_t fits_crc32c(uint32_t seed, const uint8_t *data, int len);
+
+#endif /* _SYS_FITS_CRC32C_H */
--- /dev/null	2012-10-17 21:48:39.000000000 +0200
+++ new/usr/src/uts/common/fs/zfs/sys/fits_impl.h	2012-10-17 21:48:39.881016000 +0200
@@ -0,0 +1,232 @@
+/*
+ * 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.
+ */
+
+#ifndef	_SYS_FITS_IMPL_H
+#define	_SYS_FITS_IMPL_H
+
+#include <sys/inttypes.h>
+#include <sys/types.h>
+#include <sys/cmn_err.h>
+#include <sys/spa.h>
+#include <sys/arc.h>
+#include <sys/dsl_dataset.h>
+#include <sys/dnode.h>
+#include <sys/sa.h>
+
+#define	FITS_NO_INO	0
+
+enum pass {
+	PASS_LINK,
+	PASS_UNLINK
+};
+
+typedef struct _fits_count_elem {
+	struct _fits_count_elem *fce_next;
+	uint64_t fce_ino;
+	uint64_t fce_count;
+	uint64_t fce_aux;
+} fits_count_elem_t;
+
+typedef struct _fits_counter {
+	fits_count_elem_t *fc_head;
+	const char *fc_name;
+} fits_counter_t;
+
+typedef struct blklevel {
+	uint64_t	bl_blk;
+	int		bl_nslots;
+	blkptr_t	*bl_bp;
+	arc_buf_t	*bl_buf;
+} blklevel_t;
+
+typedef struct _fits {
+	enum pass f_pass;
+	struct _fits_ops	*f_ops;
+	fits_counter_t f_del_dir_cnt;
+	fits_counter_t f_put_back_cnt;
+	fits_counter_t f_link_add_cnt;
+	uint64_t f_current_ino;
+	struct _fits_path *f_current_path;
+	int		f_alloc_len;
+	uint8_t		*f_buf;
+	int		f_size;
+	struct vnode	*f_vp;		/* file to which we are reporting */
+	offset_t	*f_offp;
+	int		f_err;		/* error that stopped diff search */
+	dsl_dataset_t	*f_fromds;
+	dsl_dataset_t	*f_tods;
+	objset_t	*f_fromsnap;
+	objset_t	*f_tosnap;
+	dnode_phys_t	*f_dnp;
+	blklevel_t	*f_bl;
+	blklevel_t	*f_filebl;
+	uint64_t	f_fromtxg;
+	sa_attr_type_t	*f_from_sa_table;
+	sa_attr_type_t	*f_to_sa_table;
+	uint64_t	f_shares_dir;
+} fits_t;
+
+typedef struct _fits_ops {
+	int (*fits_dir_add)(fits_t *f, uint64_t ino);
+	int (*fits_dir_del)(fits_t *f, uint64_t ino);
+	int (*fits_dir_mod)(fits_t *f, uint64_t ino);
+	int (*fits_dirent_add)(void *fits_enump, char *name, uint64_t ino);
+	int (*fits_dirent_del)(void *fits_enump, char *name, uint64_t ino);
+	int (*fits_dirent_mod)(void *fits_enump, char *name,
+	    uint64_t ino_old, uint64_t ino_new);
+	int (*fits_dirent_unmod)(void *fits_enump, char *name, uint64_t ino);
+	int (*fits_file_add)(fits_t *f, uint64_t ino);
+	int (*fits_file_del)(fits_t *f, uint64_t ino);
+	int (*fits_file_mod)(fits_t *f, uint64_t ino);
+	int (*fits_file_data)(void *fits_filep, void *data, uint64_t off,
+	    uint64_t len);
+} fits_ops_t;
+
+typedef struct _fits_path {
+	struct _fits_path *fp_next;
+	int fp_len;
+	int fp_total_len;
+	char fp_buf[0];
+} fits_path_t;
+
+typedef struct _fits_dirent {
+	char *fd_name;
+	uint64_t fd_parent_ino;
+	struct _fits_dirent *fd_prev;
+} fits_dirent_t;
+
+typedef enum _fits_which {
+	FITS_UNDEF,
+	FITS_OLD,
+	FITS_NEW
+} fits_which_t;
+
+typedef struct _fits_time {
+	uint64_t	st_sec;
+	uint64_t	st_nsec;
+} fits_time_t;
+
+#define	FI_ATTR_ATIME		(1 <<  0)
+#define	FI_ATTR_MTIME		(1 <<  1)
+#define	FI_ATTR_CTIME		(1 <<  2)
+#define	FI_ATTR_OTIME		(1 <<  3)
+#define	FI_ATTR_MODE		(1 <<  4)
+#define	FI_ATTR_SIZE		(1 <<  5)
+#define	FI_ATTR_NENTRIES	(1 <<  5)
+#define	FI_ATTR_PARENT		(1 <<  6)
+#define	FI_ATTR_LINKS		(1 <<  7)
+#define	FI_ATTR_RDEV		(1 <<  8)
+#define	FI_ATTR_UID		(1 <<  9)
+#define	FI_ATTR_GID		(1 << 10)
+#define	FI_ATTR_GEN		(1 << 11)
+/* XXX TODO xattr, acl, dacl */
+
+#undef si_uid	/* XXX defined in siginfo.h */
+#undef si_gid	/* XXX defined in siginfo.h */
+typedef struct _fits_info {
+	uint64_t	si_nlinks;
+	uint64_t	si_parent;
+	union {
+		uint64_t	si_nentries;
+		uint64_t	si_size;
+	};
+	fits_time_t	si_atime;
+	fits_time_t	si_mtime;
+	fits_time_t	si_ctime;
+	fits_time_t	si_otime;
+	uint64_t	si_mode;
+	/* XXX TODO xattr */
+	uint64_t	si_rdev;
+	uint64_t	si_uid;
+	uint64_t	si_gid;
+	uint64_t	si_gen;
+} fits_info_t;
+
+int fits_start(fits_t *f, fits_ops_t **);
+int fits_start2(fits_t *f, fits_ops_t **);
+int fits_abort(fits_t *f);
+int fits_end(fits_t *f);
+
+int fits_dirent_add_file(fits_t *f, fits_dirent_t *dirent,
+    uint64_t ino, uint64_t mode, int exists);
+void fits_path_free(fits_path_t *fp);
+
+int fits_get_info(fits_t *f, uint64_t dnobj, fits_which_t which,
+    fits_info_t *sp, uint64_t flags);
+
+typedef int (*fits_file_cb_t)(void *ctx, void *data, int len);
+int fits_file_contents(fits_t *f, uint64_t dnobj, void *ctx);
+int fits_dir_contents(fits_t *f, uint64_t dnobj, void *ctx);
+int fits_find_entry(fits_t *f, uint64_t dirobj, uint64_t dnobj,
+    fits_which_t which, char **name);
+void fits_free_name(char *name);
+int fits_lookup_entry(fits_t *f, uint64_t dirobj, char *name,
+    fits_which_t which, uint64_t *dnobj);
+int fits_write(fits_t *f, const uint8_t *data, int len);
+int fits_get_uuid(fits_t *f, fits_which_t which, uint8_t data[16]);
+int fits_get_ctransid(fits_t *f, fits_which_t which,
+    uint64_t *ctransid);
+int fits_get_snapname(fits_t *f, fits_which_t which,
+    char **name, int *len);
+int fits_read_symlink(fits_t *f, uint64_t dnobj, fits_which_t which,
+    char **target, int *plen);
+
+int fits_send_start(fits_t *f);
+int fits_send_create_file(fits_t *f, fits_dirent_t *dirent, uint64_t ino,
+int devise_tempname, fits_path_t **path_ret);
+int fits_send_link(fits_t *f, fits_dirent_t *new_dirent, uint64_t ino,
+    uint64_t old_parent_ino, fits_which_t which,
+int devise_tempname);
+int fits_send_mkdir(fits_t *f, fits_dirent_t *dirent, uint64_t ino,
+int devise_tempname);
+int fits_send_rename(fits_t *f, fits_dirent_t *dirent, uint64_t ino,
+    uint64_t old_parent_ino, int devise_tempname);
+int fits_send_rename_from_tempname(fits_t *f, fits_dirent_t *dirent,
+    uint64_t ino, uint64_t old);
+int fits_send_unlink(fits_t *f, fits_dirent_t *dirent, uint64_t ino);
+int fits_send_rmdir(fits_t *f, fits_dirent_t *dirent, uint64_t ino);
+/* TODO: make **path *path or do we really still alloc it? */
+int fits_send_file_data(fits_t *f,
+    fits_path_t **path, fits_dirent_t *dirent,
+    uint64_t ino, uint64_t off, uint64_t len, void *data);
+int fits_send_mtime_update(fits_t *f, fits_dirent_t *dirent, uint64_t ino);
+int fits_send_truncate(fits_t *f, fits_dirent_t *dirent, uint64_t ino,
+    uint64_t new_size);
+int fits_send_chown(fits_t *f, fits_dirent_t *dirent, uint64_t ino,
+    uint64_t new_uid, uint64_t new_gid);
+int fits_send_chmod(fits_t *f, fits_dirent_t *dirent, uint64_t ino,
+    uint64_t new_mode);
+int fits_send_end(fits_t *f);
+int fits_add_count(fits_counter_t *fc, uint64_t ino, uint64_t inc,
+    uint64_t aux, uint64_t *new_count, uint64_t *old_aux);
+void fits_free_count(fits_counter_t *fc, uint64_t ino);
+int fits_get_count(fits_counter_t *fc, uint64_t ino, uint64_t *new_count,
+    uint64_t *old_aux);
+int fits_assert_count_empty(fits_counter_t *fc);
+
+void fits_send_init(fits_t *f);
+void fits_send_fini(fits_t *f);
+
+#endif /* _SYS_FITS_IMPL_H */
--- old/usr/src/uts/common/fs/zfs/zfs_ioctl.c	2012-10-17 21:48:39.986024000 +0200
+++ new/usr/src/uts/common/fs/zfs/zfs_ioctl.c	2012-10-17 21:48:39.995020000 +0200
@@ -172,6 +172,7 @@
 #include <sys/dsl_scan.h>
 #include <sharefs/share.h>
 #include <sys/dmu_objset.h>
+#include <sys/fits.h>
 
 #include "zfs_namecheck.h"
 #include "zfs_prop.h"
@@ -5131,6 +5132,114 @@
 }
 
 /*
+ * inputs:
+ * zc_name	name of snapshot to send
+ * zc_cookie	file descriptor to send stream to
+ * zc_obj	fromorigin flag (mutually exclusive with zc_fromobj)
+ * zc_sendobj	objsetid of snapshot to send
+ * zc_fromobj	objsetid of incremental fromsnap (may be zero)
+ * zc_guid	if set, estimate size of stream only.  zc_cookie is ignored.
+ *		output size in zc_objset_type.
+ *
+ * outputs: none
+ */
+static int
+zfs_ioc_fits_send(zfs_cmd_t *zc)
+{
+	objset_t *fromsnap = NULL;
+	objset_t *tosnap;
+	int error;
+	offset_t off;
+	dsl_dataset_t *ds;
+	dsl_dataset_t *dsfrom = NULL;
+	spa_t *spa;
+	file_t *fp;
+	dsl_pool_t *dp;
+	boolean_t estimate = (zc->zc_guid != 0);
+
+	error = spa_open(zc->zc_name, &spa, FTAG);
+	if (error)
+		return (error);
+
+	dp = spa_get_dsl(spa);
+	rw_enter(&dp->dp_config_rwlock, RW_READER);
+	error = dsl_dataset_hold_obj(dp, zc->zc_sendobj, FTAG, &ds);
+	rw_exit(&dp->dp_config_rwlock);
+	spa_close(spa, FTAG);
+	if (error)
+		return (error);
+
+	error = dmu_objset_from_ds(ds, &tosnap);
+	if (error) {
+		dsl_dataset_rele(ds, FTAG);
+		return (error);
+	}
+
+	if (zc->zc_fromobj != 0) {
+		rw_enter(&dp->dp_config_rwlock, RW_READER);
+		error = dsl_dataset_hold_obj(dp, zc->zc_fromobj, FTAG, &dsfrom);
+		rw_exit(&dp->dp_config_rwlock);
+		if (error) {
+			dsl_dataset_rele(ds, FTAG);
+			return (error);
+		}
+		error = dmu_objset_from_ds(dsfrom, &fromsnap);
+		if (error) {
+			dsl_dataset_rele(dsfrom, FTAG);
+			dsl_dataset_rele(ds, FTAG);
+			return (error);
+		}
+	}
+
+	if (zc->zc_obj) {
+		dsl_pool_t *dp = ds->ds_dir->dd_pool;
+
+		if (fromsnap != NULL) {
+			dsl_dataset_rele(dsfrom, FTAG);
+			dsl_dataset_rele(ds, FTAG);
+			return (EINVAL);
+		}
+
+		if (dsl_dir_is_clone(ds->ds_dir)) {
+			rw_enter(&dp->dp_config_rwlock, RW_READER);
+			error = dsl_dataset_hold_obj(dp,
+			    ds->ds_dir->dd_phys->dd_origin_obj, FTAG, &dsfrom);
+			rw_exit(&dp->dp_config_rwlock);
+			if (error) {
+				dsl_dataset_rele(ds, FTAG);
+				return (error);
+			}
+			error = dmu_objset_from_ds(dsfrom, &fromsnap);
+			if (error) {
+				dsl_dataset_rele(dsfrom, FTAG);
+				dsl_dataset_rele(ds, FTAG);
+				return (error);
+			}
+		}
+	}
+
+	fp = getf(zc->zc_cookie);
+	if (fp == NULL) {
+		dsl_dataset_rele(ds, FTAG);
+		if (dsfrom)
+			dsl_dataset_rele(dsfrom, FTAG);
+		return (EBADF);
+	}
+
+	off = fp->f_offset;
+	error = fits_send(tosnap, fromsnap, zc->zc_cookie, fp->f_vnode, &off);
+
+	if (VOP_SEEK(fp->f_vnode, fp->f_offset, &off, NULL) == 0)
+		fp->f_offset = off;
+	releasef(zc->zc_cookie);
+
+	if (dsfrom)
+		dsl_dataset_rele(dsfrom, FTAG);
+	dsl_dataset_rele(ds, FTAG);
+	return (error);
+}
+
+/*
  * Determine approximately how large a zfs send stream will be -- the number
  * of bytes that will be written to the fd supplied to zfs_ioc_send_new().
  *
@@ -5422,6 +5531,8 @@
 	    zfs_ioc_userspace_many, zfs_secpolicy_userspace_many);
 	zfs_ioctl_register_dataset_read_secpolicy(ZFS_IOC_SEND,
 	    zfs_ioc_send, zfs_secpolicy_send);
+	zfs_ioctl_register_dataset_read_secpolicy(ZFS_IOC_FITS_SEND,
+	    zfs_ioc_fits_send, zfs_secpolicy_send);
 
 	zfs_ioctl_register_dataset_modify(ZFS_IOC_SET_PROP, zfs_ioc_set_prop,
 	    zfs_secpolicy_none);
--- old/usr/src/uts/common/sys/fs/zfs.h	2012-10-17 21:48:40.403016000 +0200
+++ new/usr/src/uts/common/sys/fs/zfs.h	2012-10-17 21:48:40.409016000 +0200
@@ -825,6 +825,7 @@
 	ZFS_IOC_SEND_NEW,
 	ZFS_IOC_SEND_SPACE,
 	ZFS_IOC_CLONE,
+	ZFS_IOC_FITS_SEND,
 	ZFS_IOC_LAST
 } zfs_ioc_t;
 
