--- old/usr/src/cmd/zfs/zfs_main.c	2012-10-26 17:09:22.901949000 +0200
+++ new/usr/src/cmd/zfs/zfs_main.c	2012-10-26 17:09:22.910935000 +0200
@@ -252,8 +252,8 @@
 	case HELP_ROLLBACK:
 		return (gettext("\trollback [-rRf] <snapshot>\n"));
 	case HELP_SEND:
-		return (gettext("\tsend [-DnPpRv] [-[iI] snapshot] "
-		    "<snapshot>\n"));
+		return (gettext("\tsend [-DnPpRv] [-[iI] snapshot] <snapshot>\n"
+		    "\tsend -F [-nPRv] [-[iI] snapshot] <snapshot>\n"));
 	case HELP_SET:
 		return (gettext("\tset <property=value> "
 		    "<filesystem|volume|snapshot> ...\n"));
@@ -3521,7 +3521,7 @@
 	boolean_t extraverbose = B_FALSE;
 
 	/* check options */
-	while ((c = getopt(argc, argv, ":i:I:RDpvnP")) != -1) {
+	while ((c = getopt(argc, argv, ":i:I:RDpvnPF")) != -1) {
 		switch (c) {
 		case 'i':
 			if (fromname)
@@ -3556,6 +3556,9 @@
 		case 'n':
 			flags.dryrun = B_TRUE;
 			break;
+		case 'F':
+			flags.far = B_TRUE;
+			break;
 		case ':':
 			(void) fprintf(stderr, gettext("missing argument for "
 			    "'%c' option\n"), optopt);
@@ -3581,6 +3584,11 @@
 		usage(B_FALSE);
 	}
 
+	if (flags.far && (flags.dedup || flags.props)) {
+		(void) fprintf(stderr, gettext("options -D and -p are not "
+		    "allowed with -F\n"));
+		usage(B_FALSE);
+	}
 	if (!flags.dryrun && isatty(STDOUT_FILENO)) {
 		(void) fprintf(stderr,
 		    gettext("Error: Stream can not be written to a terminal.\n"
--- old/usr/src/lib/libzfs/common/libzfs.h	2012-10-26 17:09:23.307921000 +0200
+++ new/usr/src/lib/libzfs/common/libzfs.h	2012-10-26 17:09:23.313924000 +0200
@@ -585,6 +585,9 @@
 
 	/* show progress (ie. -v) */
 	boolean_t progress;
+
+	/* send output as FAR-stream */
+	boolean_t far;
 } sendflags_t;
 
 typedef boolean_t (snapfilter_cb_t)(zfs_handle_t *, void *);
--- old/usr/src/lib/libzfs/common/libzfs_sendrecv.c	2012-10-26 17:09:23.548926000 +0200
+++ new/usr/src/lib/libzfs/common/libzfs_sendrecv.c	2012-10-26 17:09:23.556926000 +0200
@@ -789,7 +789,7 @@
 	char prevsnap[ZFS_MAXNAMELEN];
 	uint64_t prevsnap_obj;
 	boolean_t seenfrom, seento, replicate, doall, fromorigin;
-	boolean_t verbose, dryrun, parsable, progress;
+	boolean_t verbose, dryrun, parsable, progress, far;
 	int outfd;
 	boolean_t err;
 	nvlist_t *fss;
@@ -868,7 +868,7 @@
  */
 static int
 dump_ioctl(zfs_handle_t *zhp, const char *fromsnap, uint64_t fromsnap_obj,
-    boolean_t fromorigin, int outfd, nvlist_t *debugnv)
+    boolean_t fromorigin, int outfd, int far, nvlist_t *debugnv)
 {
 	zfs_cmd_t zc = { 0 };
 	libzfs_handle_t *hdl = zhp->zfs_hdl;
@@ -882,6 +882,7 @@
 	zc.zc_obj = fromorigin;
 	zc.zc_sendobj = zfs_prop_get_int(zhp, ZFS_PROP_OBJSETID);
 	zc.zc_fromobj = fromsnap_obj;
+	zc.zc_guid = far ? 2 : 0;
 
 	VERIFY(0 == nvlist_alloc(&thisdbg, NV_UNIQUE_NAME, 0));
 	if (fromsnap && fromsnap[0] != '\0') {
@@ -1169,7 +1170,7 @@
 		}
 
 		err = dump_ioctl(zhp, sdd->prevsnap, sdd->prevsnap_obj,
-		    fromorigin, sdd->outfd, sdd->debugnv);
+		    fromorigin, sdd->outfd, sdd->far, sdd->debugnv);
 
 		if (sdd->progress) {
 			(void) pthread_cancel(tid);
@@ -1461,7 +1462,7 @@
 			}
 		}
 
-		if (!flags->dryrun) {
+		if (!flags->dryrun && !flags->far) {
 			/* write first begin record */
 			drr.drr_type = DRR_BEGIN;
 			drr.drr_u.drr_begin.drr_magic = DMU_BACKUP_MAGIC;
@@ -1520,6 +1521,7 @@
 	sdd.parsable = flags->parsable;
 	sdd.progress = flags->progress;
 	sdd.dryrun = flags->dryrun;
+	sdd.far = flags->far;
 	sdd.filter_cb = filter_func;
 	sdd.filter_cb_arg = cb_arg;
 	if (debugnvp)
@@ -1581,8 +1583,8 @@
 		sdd.cleanup_fd = -1;
 	}
 
-	if (!flags->dryrun && (flags->replicate || flags->doall ||
-	    flags->props)) {
+	if (!flags->dryrun && !flags->far &&
+	    (flags->replicate || flags->doall || flags->props)) {
 		/*
 		 * write final end record.  NB: want to do this even if
 		 * there was some error, because it might not be totally
--- old/usr/src/uts/common/Makefile.files	2012-10-26 17:09:23.848881000 +0200
+++ new/usr/src/uts/common/Makefile.files	2012-10-26 17:09:23.855874000 +0200
@@ -1418,6 +1418,12 @@
 	rrwlock.o		\
 	zfs_vfsops.o		\
 	zfs_vnops.o		\
+	far.o			\
+	far_pass1.o		\
+	far_pass2.o		\
+	far_send.o		\
+	far_crc32c.o		\
+	far_count.o		\
 	zvol.o
 
 ZUT_OBJS +=			\
--- old/usr/src/uts/common/fs/zfs/dsl_dataset.c	2012-10-26 17:09:24.114808000 +0200
+++ new/usr/src/uts/common/fs/zfs/dsl_dataset.c	2012-10-26 17:09:24.122806000 +0200
@@ -680,7 +680,7 @@
 	}
 }
 
-static int
+int
 dsl_dataset_namelen(dsl_dataset_t *ds)
 {
 	int result;
--- /dev/null	2012-10-26 17:09:24.000000000 +0200
+++ new/usr/src/uts/common/fs/zfs/far.c	2012-10-26 17:09:24.478801000 +0200
@@ -0,0 +1,1285 @@
+/*
+ * 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/far.h>
+#include <sys/far_impl.h>
+
+/*
+ * far_send generates a stream of filesystem data analogous to dmu_send.
+ * The main difference is that the far-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 far_count
+ */
+static int
+far_dnode_changed(spa_t *spa, far_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
+far_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
+far_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
+far_release_sa_handle(sa_handle_t *hdl, dmu_buf_t *db, void *tag)
+{
+	sa_handle_destroy(hdl);
+	sa_buf_rele(db, tag);
+}
+
+static int
+far_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
+far_file_cb(spa_t *spa, far_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 = far_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 = far_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->far_file_data)
+			err = f->f_ops->far_file_data(ctx, tbuf->b_data,
+			    zb->zb_blkid * blksz, blksz);
+
+		(void) arc_buf_remove_ref(tbuf, &tbuf);
+	}
+	return (err);
+}
+
+static int
+far_enum_bp(spa_t *spa, far_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 = far_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 = far_dnode_changed(spa, da, dnobj, dnp, buf,
+			    NULL, NULL);
+			if (err)
+				goto out;
+		}
+	} else {
+		err = far_file_cb(spa, da, zb, bp, pbuf, ctx);
+	}
+out:
+	if (buf)
+		(void) arc_buf_remove_ref(buf, &buf);
+
+	return (err);
+}
+
+static int
+far_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;
+	far_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 = far_find_from_bp(spa, f->f_dnp, f->f_bl,
+		    &czb, &fbp, &fpbuf);
+		if (err)
+			return (EIO);
+		if (fbp) {
+			err = far_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 = far_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 = far_dnode_changed(spa, f, dnobj,
+				    fblk + i, fbuf, NULL, NULL);
+			} else if (fbuf) {
+				if (memcmp(tblk + i, fblk + i, sizeof (*tblk)))
+					err = far_dnode_changed(spa, f,
+					    dnobj, fblk + i, fbuf, tblk + i,
+					    tbuf);
+			} else {
+				if (tblk[i].dn_type != DMU_OT_NONE)
+					err = far_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
+far_diff_dir(far_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->far_dirent_del) {
+					err = f->f_ops->far_dirent_del(ctx,
+					    za->za_name,
+					    za->za_first_integer & mask);
+					if (err)
+						goto out;
+				}
+			} else {
+				if (f->f_ops->far_dirent_add) {
+					err = f->f_ops->far_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->far_dirent_mod) {
+					err = f->f_ops->far_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->far_dirent_unmod) {
+					err = f->f_ops->far_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
+far_enum_dir(far_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->far_dirent_del) {
+				err = f->f_ops->far_dirent_del(ctx,
+				    za->za_name, za->za_first_integer & mask);
+				if (err)
+					break;
+			}
+		} else {
+			if (f->f_ops->far_dirent_add) {
+				err = f->f_ops->far_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
+far_dnode_changed(spa_t *spa, far_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;
+	far_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 = far_get_info(f, dnobj, FAR_OLD, &si, FI_ATTR_LINKS);
+		if (err)
+			return (err);
+		if (si.si_nlinks == 0)
+			from = NULL;
+	}
+	if (to) {
+		err = far_get_info(f, dnobj, FAR_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->far_dir_mod)
+				err = f->f_ops->far_dir_mod(f, dnobj);
+		} else if (from) {
+			if (f->f_ops->far_dir_del)
+				err = f->f_ops->far_dir_del(f, dnobj);
+		} else if (to) {
+			if (f->f_ops->far_dir_add)
+				err = f->f_ops->far_dir_add(f, dnobj);
+		}
+	} else if (type == DMU_OT_PLAIN_FILE_CONTENTS) {
+		if (from && to) {
+			if (f->f_ops->far_file_mod)
+				err = f->f_ops->far_file_mod(f, dnobj);
+		} else if (from) {
+			if (f->f_ops->far_file_del)
+				err = f->f_ops->far_file_del(f, dnobj);
+		} else if (to) {
+			if (f->f_ops->far_file_add)
+				err = f->f_ops->far_file_add(f, dnobj);
+		}
+	} else {
+		/* TODO other types, symlinks? */
+		err = 0;
+	}
+	return (err);
+}
+
+typedef struct _far_search {
+	far_t *zs_f;
+	uint64_t zs_dnobj;
+	uint64_t zs_parent;
+	objset_t *zs_osp;
+} far_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)
+{
+	far_search_t *zs = arg;
+	far_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
+far_search_parent(far_t *f, uint64_t dnobj, far_which_t which,
+    uint64_t *parent)
+{
+	dsl_dataset_t *ds;
+	far_search_t zs;
+	int ret;
+
+	if (which == FAR_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
+far_get_info(far_t *f, uint64_t dnobj, far_which_t which,
+    far_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 == FAR_OLD) {
+		osp = f->f_fromsnap;
+		if (!osp)
+			return (ENOENT);
+		sa_table = f->f_from_sa_table;
+	} else if (which == FAR_NEW) {
+		osp = f->f_tosnap;
+		sa_table = f->f_to_sa_table;
+	} else {
+		return (EINVAL);
+	}
+
+	ret = far_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) {
+		far_info_t si;
+		int good = 0;
+		/*
+		 * verify parent. this is very expensive and only a workaround
+		 */
+		ret = far_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 = far_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 = far_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:
+	far_release_sa_handle(hdl, db, FTAG);
+	return (ret);
+}
+
+int
+far_file_contents(far_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 = far_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
+far_dir_contents(far_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 = far_diff_dir(f, dnobj, DIR_TO, ctx);
+		if (err)
+			goto out;
+		err = far_diff_dir(f, dnobj, DIR_FROM, ctx);
+	} else if (to) {
+		err = far_enum_dir(f, dnobj, DIR_TO, ctx);
+	} else if (from) {
+		err = far_enum_dir(f, dnobj, DIR_FROM, ctx);
+	}
+out:
+	if (from)
+		dnode_rele(from, FTAG);
+	if (to)
+		dnode_rele(to, FTAG);
+
+	return (err);
+}
+
+int
+far_find_entry(far_t *f, uint64_t dirobj, uint64_t dnobj,
+    far_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 == FAR_OLD) {
+		os = f->f_fromsnap;
+		if (!os)
+			return (ENOENT);
+	} else if (which == FAR_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
+far_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
+far_lookup_entry(far_t *f, uint64_t dirobj, char *name,
+    far_which_t which, uint64_t *dnobj)
+{
+	struct objset *osp;
+	int ret;
+
+	if (which == FAR_OLD) {
+		osp = f->f_fromsnap;
+		if (!osp)
+			return (ENOENT);
+	} else if (which == FAR_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
+far_write(far_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
+far_get_uuid(far_t *f, far_which_t which, uint8_t data[16])
+{
+	if (which == FAR_OLD && !f->f_fromds)
+		return (ENOENT);
+
+	LE_OUT64(data, f->f_tods->ds_dir->dd_pool->dp_spa->spa_config_guid);
+	if (which == FAR_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
+far_get_ctransid(far_t *f, far_which_t which, uint64_t *ctransid)
+{
+	if (which == FAR_OLD && !f->f_fromds)
+		return (ENOENT);
+
+	if (which == FAR_OLD)
+		*ctransid = f->f_fromds->ds_phys->ds_creation_txg;
+	else
+		*ctransid = f->f_tods->ds_phys->ds_creation_txg;
+	return (0);
+}
+
+int
+far_get_snapname(far_t *f, far_which_t which,
+    char **name, int *len)
+{
+	dsl_dataset_t *ds;
+
+	if (which == FAR_OLD && !f->f_fromds)
+		return (ENOENT);
+
+	if (which == FAR_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
+far_read_symlink(far_t *f, uint64_t dnobj, far_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 == FAR_OLD) {
+		osp = f->f_fromsnap;
+		if (!osp)
+			return (EINVAL);
+		sa_table = f->f_from_sa_table;
+	} else if (which == FAR_NEW) {
+		osp = f->f_tosnap;
+		sa_table = f->f_to_sa_table;
+	} else {
+		return (EINVAL);
+	}
+
+	err = far_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:
+	far_release_sa_handle(hdl, db, FTAG);
+
+	return (ret);
+}
+
+int
+far_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;
+	far_t *f;
+	arc_buf_t *buf = NULL;
+	uint32_t flags;
+	objset_phys_t *osp = NULL;
+	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 = kmem_zalloc(sizeof (far_t), KM_SLEEP);
+	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;
+	f->f_dmu_sendarg.dsa_off = off;
+	f->f_dmu_sendarg.dsa_outfd = outfd;
+	f->f_dmu_sendarg.dsa_proc = curproc;
+	f->f_dnp = osp ? &osp->os_meta_dnode : NULL;
+	mutex_enter(&ds->ds_sendstream_lock);
+	list_insert_head(&ds->ds_sendstreams, &f->f_dmu_sendarg);
+	mutex_exit(&ds->ds_sendstream_lock);
+
+	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 = far_sa_setup(fromsnap, &f->f_from_sa_table);
+		if (err)
+			goto out;
+	}
+	err = far_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 = far_start(f, &f->f_ops);
+	if (err)
+		goto out;
+
+
+
+	err = traverse_dataset(ds, f->f_fromtxg,
+	    TRAVERSE_PRE | TRAVERSE_PREFETCH_METADATA, far_cb, f);
+	if (err) {
+		far_abort(f);
+		goto out;
+	}
+	err = far_start2(f, &f->f_ops);
+	if (err) {
+		goto out;
+	}
+	err = traverse_dataset(ds, f->f_fromtxg,
+	    TRAVERSE_PRE | TRAVERSE_PREFETCH_METADATA, far_cb, f);
+	if (err) {
+		far_abort(f);
+		goto out;
+	}
+
+	err = far_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);
+
+	mutex_enter(&ds->ds_sendstream_lock);
+	list_remove(&ds->ds_sendstreams, &f->f_dmu_sendarg);
+	mutex_exit(&ds->ds_sendstream_lock);
+	kmem_free(f, sizeof (far_t));
+
+	return (err);
+}
--- /dev/null	2012-10-26 17:09:24.000000000 +0200
+++ new/usr/src/uts/common/fs/zfs/far_count.c	2012-10-26 17:09:24.594803000 +0200
@@ -0,0 +1,152 @@
+/*
+ * 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/far_impl.h>
+
+static void
+far_count_value_free(mod_hash_val_t val)
+{
+	far_count_elem_t *fce = val;
+	kmem_free(fce, sizeof (*fce));
+}
+
+static void
+far_count_key_free(mod_hash_key_t key)
+{
+	kmem_free(key, sizeof (uint64_t));
+}
+
+static int
+far_count_cmp(const void *aa, const void *bb)
+{
+	const far_count_elem_t *a = aa;
+	const far_count_elem_t *b = bb;
+
+	if (a->fce_ino < b->fce_ino)
+		return -1;
+	if (a->fce_ino > b->fce_ino)
+		return 1;
+	return 0;
+}
+
+int
+far_add_count(far_counter_t *fc, uint64_t ino, uint64_t inc,
+    uint64_t aux, uint64_t *new_count, uint64_t *old_aux)
+{
+	far_count_elem_t *fce;
+	far_count_elem_t e = { .fce_ino = ino };
+	avl_index_t where;
+
+	fce = avl_find(&fc->fc_avl, &e, &where);
+
+	if (!fce) {
+		fce = kmem_alloc(sizeof (*fce), KM_SLEEP);
+		fce->fce_count = 0;
+		fce->fce_aux = 0;
+		fce->fce_ino = ino;
+		avl_insert(&fc->fc_avl, fce, where);
+	}
+
+	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
+far_get_count(far_counter_t *fc, uint64_t ino, uint64_t *count, uint64_t *aux)
+{
+	far_count_elem_t *fce;
+	far_count_elem_t e = { .fce_ino = ino };
+
+	fce = avl_find(&fc->fc_avl, &e, NULL);
+	if (!fce) {
+		if (count)
+			*count = 0;
+		if (aux)
+			*aux = 0;
+		return (ENOENT);
+	} else {
+		if (count)
+			*count = fce->fce_count;
+		if (aux)
+			*aux = fce->fce_aux;
+		return (0);
+	}
+}
+
+void
+far_free_count(far_counter_t *fc, uint64_t ino)
+{
+	far_count_elem_t *fce;
+	far_count_elem_t e = { .fce_ino = ino };
+
+	fce = avl_find(&fc->fc_avl, &e, NULL);
+	if (!fce)
+		return;
+	avl_remove(&fc->fc_avl, fce);
+	kmem_free(fce, sizeof (*fce));
+}
+
+int
+far_count_init(far_counter_t *fc, char *name)
+{
+	avl_create(&fc->fc_avl, far_count_cmp, sizeof (far_count_elem_t),
+	    offsetof(far_count_elem_t, fce_avl_node));
+	fc->fc_name = name;
+
+	return 0;
+}
+
+int
+far_count_fini(far_counter_t *fc)
+{
+	far_count_elem_t *fce;
+	int ret = 0;
+
+	while ((fce = avl_first(&fc->fc_avl))) {
+		/*
+		 * a count of zero might be left over if a file had > 1 links
+		 * and be replaced by a file with > 1 link. see test 041.034
+		 */
+		if (fce->fce_count != 0) {
+			cmn_err(CE_NOTE, "far_assert_count_empty: %s ino %"
+			    PRIu64 " count %" PRIu64"\n", fc->fc_name,
+			    fce->fce_ino, fce->fce_count);
+			++ret;
+		}
+		avl_remove(&fc->fc_avl, fce);
+		kmem_free(fce, sizeof (*fce));
+	}
+	avl_destroy(&fc->fc_avl);
+
+	return (ret);
+}
--- /dev/null	2012-10-26 17:09:24.000000000 +0200
+++ new/usr/src/uts/common/fs/zfs/far_crc32c.c	2012-10-26 17:09:24.701803000 +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/far_crc32c.h>
+
+static void far_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
+far_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
+far_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
+far_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
+far_crc32c(uint32_t crc32, const uint8_t *buf, int len)
+{
+	int rem;
+
+	if (!initialized) {
+		far_crc32c_init();
+		initialized = 1;
+	}
+
+	rem = 4 - (((uintptr_t)buf) & 3);
+	if (rem != 0) {
+		if (len < rem) {
+			rem = len;
+		}
+		far_crc32c_byte(&crc32, buf, rem);
+		buf = buf + rem;
+		len = len - rem;
+	}
+	if (len > 3) {
+		far_crc32c_word(&crc32, (const uint32_t *) buf, len / 4);
+	}
+	rem = len & 3;
+	if (rem != 0) {
+		far_crc32c_byte(&crc32, buf + len - rem, rem);
+	}
+	return (crc32);
+}
--- /dev/null	2012-10-26 17:09:24.000000000 +0200
+++ new/usr/src/uts/common/fs/zfs/far_pass1.c	2012-10-26 17:09:24.805803000 +0200
@@ -0,0 +1,506 @@
+/*
+ * 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/far.h>
+#include <sys/far_impl.h>
+
+struct far_enum {
+	far_t *fe_far;
+	uint64_t fe_parent_ino;
+	far_dirent_t *fe_dirent_chain;
+};
+
+struct far_file {
+	far_t *ff_far;
+	uint64_t ff_len;
+	uint64_t ff_last_byte;
+	uint64_t ff_ino;
+	far_path_t *ff_path;
+	far_dirent_t *ff_dirent;
+};
+
+static int far_file_data_pass1(void *far_filep, void *data, uint64_t off,
+    uint64_t len);
+static int far_dirent_add_pass1(void *far_enump, char *name, uint64_t ino);
+static int far_dirent_mod_pass1(void *far_enump, char *name,
+    uint64_t ino_old, uint64_t ino_new);
+static int far_dir_add_pass1(far_t *f, uint64_t ino);
+static int far_mod_pass1(far_t *f, uint64_t ino);
+
+static far_ops_t _ops = {
+	.far_dir_add = far_dir_add_pass1,
+	.far_dir_mod = far_mod_pass1,
+	.far_dirent_add = far_dirent_add_pass1,
+	.far_dirent_mod = far_dirent_mod_pass1,
+	.far_file_mod = far_mod_pass1,
+	.far_file_data = far_file_data_pass1
+};
+
+static int far_file_add_genchange(far_t *f, uint64_t ino);
+
+int
+far_start(far_t *f, far_ops_t **ops)
+{
+	int ret;
+
+	f->f_pass = PASS_LINK;
+	far_count_init(&f->f_link_add_cnt, "link_add_cnt");
+	far_count_init(&f->f_del_dir_cnt, "del_dir_cnt");
+	far_count_init(&f->f_put_back_cnt, "put_back_cnt");
+	far_send_init(f);
+
+	*ops = &_ops;
+
+	ret = far_send_start(f);
+	if (ret) {
+		far_abort(f);
+		return (ret);
+	}
+
+	return (0);
+}
+
+static int
+enum_dir(far_t *f, uint64_t ino, far_dirent_t *chain)
+{
+	struct far_enum fe = {
+		.fe_far = f,
+		.fe_parent_ino = ino,
+		.fe_dirent_chain = chain
+	};
+
+	return (far_dir_contents(f, ino, &fe));
+}
+
+static int
+far_file_data_pass1(void *far_filep, void *data, uint64_t off, uint64_t len)
+{
+	struct far_file *ff = far_filep;
+
+	if (off + len > ff->ff_len)
+		len = ff->ff_len - off;
+
+	ff->ff_last_byte = off + len;
+
+	return far_send_file_data(ff->ff_far, &ff->ff_path,
+	    ff->ff_dirent, ff->ff_ino, off, len, data);
+}
+
+static int
+dirent_add_dir(far_t *f, far_dirent_t *dirent, uint64_t ino, int exists)
+{
+	far_info_t si_old;
+	far_info_t si_new;
+	int ret;
+
+	ret = far_get_info(f, ino, FAR_OLD, &si_old,
+			    FI_ATTR_PARENT | FI_ATTR_GEN | FI_ATTR_MODE);
+	if (ret && ret != ENOENT)
+		return (ret);
+
+	if (ret == 0) {
+		ret = far_get_info(f, ino, FAR_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 far_send_rename(f, dirent, ino,
+						si_old.si_parent, exists);
+		return (0);
+	}
+
+	if (ino > f->f_current_ino)
+		return (0);
+
+	/* dir is new */
+	ret = far_send_mkdir(f, dirent, ino, exists);
+	if (ret)
+		return (ret);
+
+	return (enum_dir(f, ino, dirent));
+}
+
+int
+far_dirent_add_file(far_t *f, far_dirent_t *dirent,
+    uint64_t ino, uint64_t mode, int exists)
+{
+	far_info_t si_old;
+	far_info_t si_new;
+	int ret;
+	far_path_t *far_path;
+	uint64_t new_count;
+	uint64_t old_aux;
+
+	ret = far_get_info(f, ino, FAR_OLD, &si_old,
+			    FI_ATTR_GEN | FI_ATTR_PARENT);
+	if (ret && ret != ENOENT)
+		return (ret);
+
+	if (ret == 0) {
+		ret = far_get_info(f, ino, FAR_NEW, &si_new,
+				    FI_ATTR_GEN);
+		if (ret)
+			return (ret);
+
+		if (si_old.si_gen == si_new.si_gen)
+			return far_send_link(f, dirent, ino, si_old.si_parent,
+			    FAR_OLD, exists);
+	}
+
+	/* file is new */
+	ret = far_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 = far_send_create_file(f, dirent, ino,
+					    exists, &far_path);
+	else
+		ret = far_send_link(f, dirent, ino, old_aux, FAR_NEW, exists);
+	if (ret)
+		return (ret);
+
+	ret = far_get_info(f, ino, FAR_NEW, &si_new,
+			    FI_ATTR_SIZE | FI_ATTR_LINKS);
+	ASSERT(ret == 0);
+	if (new_count == 1 && S_ISREG(mode)) {
+		struct far_file ff;
+
+		ff.ff_ino = ino;
+		ff.ff_len = si_new.si_size;
+		ff.ff_far = f;
+		ff.ff_path = far_path;
+		ff.ff_dirent = dirent;
+		ff.ff_last_byte = 0;
+		ret = far_file_contents(f, ino, &ff);
+		far_path_free(ff.ff_path);
+		if (ret)
+			return (ret);
+		if (ff.ff_last_byte != si_new.si_size) {
+			/* sparse end */
+			ret = far_send_truncate(f, NULL, ino, si_new.si_size);
+			if (ret)
+				return (ret);
+		}
+	}
+	if (new_count == si_new.si_nlinks)
+		far_free_count(&f->f_link_add_cnt, ino);
+
+	return (0);
+}
+
+static int
+dirent_add(far_t *f, far_dirent_t *dirent, uint64_t ino, int exists)
+{
+	far_info_t si;
+	int ret;
+
+	ret = far_get_info(f, ino, FAR_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 (far_dirent_add_file(f, dirent, ino, si.si_mode,
+		    exists));
+	}
+}
+
+static int
+far_dirent_add_pass1(void *far_enump, char *name, uint64_t ino)
+{
+	struct far_enum *fe = far_enump;
+	far_dirent_t dirent = {
+		.fd_name = name,
+		.fd_parent_ino = fe->fe_parent_ino,
+		.fd_prev = fe->fe_dirent_chain,
+	};
+
+	return (dirent_add(fe->fe_far, &dirent, ino, 0));
+}
+
+static int
+far_dirent_mod_pass1(void *far_enump, char *name,
+    uint64_t ino_old, uint64_t ino_new)
+{
+	struct far_enum *fe = far_enump;
+	far_dirent_t dirent = {
+		.fd_name = name,
+		.fd_parent_ino = fe->fe_parent_ino,
+		.fd_prev = fe->fe_dirent_chain,
+	};
+
+	return (dirent_add(fe->fe_far, &dirent, ino_new, 1));
+}
+
+static int
+far_file_add_genchange(far_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
+	 */
+	far_info_t si_old;
+	far_info_t si_new;
+	int same_name = 0;
+
+	ret = far_get_info(f, ino, FAR_OLD, &si_old, FI_ATTR_MODE |
+			    FI_ATTR_LINKS | FI_ATTR_PARENT);
+	if (ret)
+		return (ret);
+	ret = far_get_info(f, ino, FAR_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 far_add_count(&f->f_link_add_cnt, ino, 0, 0, NULL,
+		    NULL);
+
+	if (S_ISDIR(si_old.si_mode))
+		return (0);
+
+	far_which_t from;
+	far_which_t to;
+	uint64_t new_ino;
+	uint64_t parent = si_new.si_parent;
+
+	if (si_old.si_nlinks == 1) {
+		from = FAR_OLD;
+		to = FAR_NEW;
+		parent = si_old.si_parent;
+	} else if (si_old.si_nlinks > 1 && si_new.si_nlinks == 1) {
+		from = FAR_NEW;
+		to = FAR_OLD;
+		parent = si_new.si_parent;
+	} else {
+		return (EINVAL);
+	}
+
+	ret = far_find_entry(f, parent, ino, from, &name);
+	if (ret)
+		return (ret);
+
+	ret = far_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;
+	}
+
+	far_dirent_t dirent = {
+		.fd_parent_ino = parent,
+		.fd_name = name,
+		.fd_prev = NULL
+	};
+
+	ret = far_send_unlink(f, &dirent, ino);
+	if (ret)
+		goto out;
+	ret = far_dirent_add_file(f, &dirent, ino, si_new.si_mode, 0);
+
+out:
+	far_free_name(name);
+	return (ret);
+}
+
+static int
+far_dir_add_pass1(far_t *f, uint64_t ino)
+{
+	int ret;
+	uint64_t parent;
+	uint64_t first_parent = FAR_NO_INO;
+	far_info_t si;
+	far_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 = far_get_info(f, parent, FAR_NEW, &si,
+				    FI_ATTR_PARENT);
+		if (ret)
+			return (ret);
+		if (first_parent == FAR_NO_INO)
+			first_parent = si.si_parent;
+
+		ret = far_get_info(f, parent, FAR_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 = FAR_NO_INO;
+			break;
+		}
+		parent = si.si_parent;
+
+		if (parent > ino)
+			return (0);
+	}
+
+	/*
+	 * check for same-name
+	 */
+	if (first_parent != FAR_NO_INO) {
+		ret = far_get_info(f, first_parent, FAR_OLD, &si,
+		    FI_ATTR_MODE);
+		if (ret && ret != ENOENT)
+			return (ret);
+		if (ret == 0 && S_ISDIR(si.si_mode)) {
+			uint64_t old_ino;
+
+			ret = far_find_entry(f, first_parent, ino,
+			    FAR_NEW, &name);
+			if (ret)
+				return (ret);
+
+			ret = far_lookup_entry(f, first_parent, name,
+			    FAR_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 = far_add_count(&f->f_link_add_cnt,
+					    ino, 0, 0, NULL, NULL);
+					if (ret)
+						goto out;
+				}
+			}
+		}
+	}
+	/* dir is new */
+	ret = far_send_mkdir(f, same_name ? &dirent : NULL, ino, same_name);
+	if (ret)
+		goto out;
+
+	ret = enum_dir(f, ino, NULL);
+
+out:
+	far_free_name(name);
+	return (ret);
+}
+
+static int
+far_mod_pass1(far_t *f, uint64_t ino)
+{
+	far_info_t si_old;
+	far_info_t si_new;
+	int ret;
+
+	f->f_current_ino = ino;
+	f->f_current_path = NULL;
+
+	ret = far_get_info(f, ino, FAR_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 = far_get_info(f, ino, FAR_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 (far_dir_add_pass1(f, ino));
+		else
+			return (far_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 far_file ff;
+		ff.ff_ino = ino;
+		ff.ff_len = si_new.si_size;
+		ff.ff_far = f;
+		ff.ff_path = NULL;
+		ff.ff_dirent = NULL;
+		ff.ff_last_byte = 0;
+
+		ret = far_file_contents(f, ino, &ff);
+		far_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 = far_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 = far_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 = far_send_chmod(f, NULL, ino, si_new.si_mode);
+		if (ret)
+			return (ret);
+	}
+
+	return (ret);
+}
--- /dev/null	2012-10-26 17:09:24.000000000 +0200
+++ new/usr/src/uts/common/fs/zfs/far_pass2.c	2012-10-26 17:09:24.917693000 +0200
@@ -0,0 +1,528 @@
+/*
+ * 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/far.h>
+#include <sys/far_impl.h>
+
+struct far_enum {
+	far_t *fe_far;
+	uint64_t fe_parent_ino;
+	uint64_t fe_del_dir_cnt;
+	uint64_t fe_put_back_cnt;
+	far_dirent_t *fe_dirent_chain;
+};
+
+struct far_file {
+	far_t *ff_far;
+	uint64_t ff_len;
+	uint64_t ff_last_byte;
+	uint64_t ff_ino;
+	far_path_t *ff_path;
+	far_dirent_t *ff_dirent;
+};
+
+static int far_dirent_del_pass2(void *far_enump, char *name, uint64_t ino);
+static int far_dirent_mod_pass2(void *far_enump, char *name,
+    uint64_t ino_old, uint64_t ino_new);
+static int far_dirent_unmod_pass2(void *far_enump, char *name, uint64_t ino);
+static int far_dir_del_pass2(far_t *f, uint64_t ino);
+static int far_add_pass2(far_t *f, uint64_t ino);
+static int far_mod_pass2(far_t *f, uint64_t ino);
+
+far_ops_t _ops = {
+	.far_dirent_del = far_dirent_del_pass2,
+	.far_dirent_mod = far_dirent_mod_pass2,
+	.far_dirent_unmod = far_dirent_unmod_pass2,
+	.far_file_add = far_add_pass2,
+	.far_file_mod = far_mod_pass2,
+	.far_dir_add = far_add_pass2,
+	.far_dir_del = far_dir_del_pass2,
+	.far_dir_mod = far_mod_pass2
+};
+
+int
+far_start2(far_t *f, far_ops_t **ops)
+{
+	f->f_pass = PASS_UNLINK;
+	*ops = &_ops;
+
+	return (0);
+}
+
+int
+far_abort(far_t *f)
+{
+	far_send_fini(f);
+
+	return (0);
+}
+
+int
+far_end(far_t *f)
+{
+	int ret;
+	int ret2;
+
+	far_send_end(f);
+
+	ret = far_count_fini(&f->f_link_add_cnt);
+	ret += far_count_fini(&f->f_del_dir_cnt);
+	ret += far_count_fini(&f->f_put_back_cnt);
+
+	ret2 = far_abort(f);
+
+	return (ret ? EIO : ret2);
+}
+
+static int
+dir_del(far_t *f, uint64_t ino, uint64_t removed_entries)
+{
+	far_info_t si;
+	uint64_t parent;
+	int ret;
+	int exists;
+	uint64_t new_count;
+	uint64_t left;
+
+	while (1) {
+		ret = far_get_info(f, ino, FAR_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);
+
+		far_free_count(&f->f_del_dir_cnt, ino);
+		ret = far_send_rmdir(f, NULL, ino);
+		if (ret)
+			return (ret);
+
+		parent = si.si_parent;
+		exists = far_get_info(f, parent, FAR_NEW, &si,
+		    FI_ATTR_MODE);
+		if (exists && exists != ENOENT)
+			return (exists);
+
+		ret = far_get_count(&f->f_put_back_cnt, parent, &left, NULL);
+		if (ret && ret != ENOENT)
+			return (ret);
+		if (left > 0) {
+			ret = far_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;
+			far_info_t si_new;
+			int new;
+
+			new = far_get_info(f, ino, FAR_NEW, &si_new,
+					    FI_ATTR_MODE);
+			if (new && new != ENOENT)
+				return (new);
+			ret = far_find_entry(f, parent, ino, FAR_OLD, &name);
+			if (ret)
+				return (ret);
+
+			ret = far_lookup_entry(f, parent, name,
+						FAR_NEW, &new_ino);
+			if (ret && ret != ENOENT) {
+				far_free_name(name);
+				return (ret);
+			}
+			if (ret == 0 && new_ino != ino) {
+				far_dirent_t dirent = {
+					.fd_name = name,
+					.fd_parent_ino = parent
+				};
+
+				ret = far_send_rename_from_tempname(f, &dirent,
+				    ino, new_ino);
+				if (ret) {
+					far_free_name(name);
+					return (ret);
+				}
+			} else if (ret == 0 && new_ino == ino &&
+			    !S_ISDIR(si_new.si_mode)) {
+				far_dirent_t dirent = {
+					.fd_name = name,
+					.fd_parent_ino = parent
+				};
+
+				ret = far_dirent_add_file(f, &dirent, ino,
+				    si_new.si_mode, 0);
+				if (ret) {
+					far_free_name(name);
+					return (ret);
+				}
+			}
+			far_free_name(name);
+		}
+
+		if (left == 0) {
+			far_free_count(&f->f_put_back_cnt, parent);
+			if (exists == 0 && S_ISDIR(si.si_mode)) {
+				ret = far_send_mtime_update(f, NULL, parent);
+				if (ret)
+					return (ret);
+			}
+		}
+
+		if (exists == 0 && S_ISDIR(si.si_mode))
+			return (0);
+
+		/* propagate deletion */
+		ret = far_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(far_t *f, uint64_t ino, uint64_t *pput_back_cnt,
+    uint64_t *pdel_dir_cnt)
+{
+	int ret;
+	struct far_enum fe = {
+		.fe_far = f,
+		.fe_parent_ino = ino,
+		.fe_del_dir_cnt = 0,
+		.fe_put_back_cnt = 0
+	};
+
+	ret = far_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 = far_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 far_enum *fe, far_dirent_t *dirent,
+		uint64_t ino, uint64_t remains)
+{
+	int ret;
+
+	ret = far_send_unlink(fe->fe_far, dirent, ino);
+	if (ret)
+		return (ret);
+
+	if (remains != FAR_NO_INO) {
+		ret = far_send_rename_from_tempname(fe->fe_far, dirent,
+		    ino, remains);
+		if (ret)
+			return (ret);
+	}
+
+	fe->fe_del_dir_cnt++;
+
+	return (0);
+}
+
+static int
+dirent_del_dir(struct far_enum *fe, far_dirent_t *dirent, uint64_t ino,
+    uint64_t remains)
+{
+	int ret;
+	far_info_t si;
+	far_info_t si_old;
+	int new;
+	int old;
+	far_t *f = fe->fe_far;
+
+	new = far_get_info(f, ino, FAR_NEW, &si, FI_ATTR_GEN |
+			    FI_ATTR_MODE);
+	if (new && new != ENOENT)
+		return (new);
+	old = far_get_info(f, ino, FAR_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 = far_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);
+		}
+
+		far_free_count(&f->f_del_dir_cnt, ino);
+		ret = far_send_rmdir(f, dirent, ino);
+		if (ret)
+			return (ret);
+	}
+	if (remains != FAR_NO_INO) {
+		ret = far_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;
+		far_info_t sip;
+		char *name = NULL;
+
+		ret = far_get_info(f, parent, FAR_OLD, &sip,
+				    FI_ATTR_MODE);
+		if (ret && ret != ENOENT)
+			return (ret);
+		if (ret == 0 && S_ISDIR(sip.si_mode)) {
+			uint64_t old_ino;
+
+			ret = far_find_entry(f, parent, ino, FAR_OLD, &name);
+			if (ret)
+				return (ret);
+
+			ret = far_lookup_entry(f, parent, name,
+						FAR_NEW, &old_ino);
+			if (ret && ret != ENOENT) {
+				far_free_name(name);
+				return (ret);
+			}
+			if (ret == 0) {
+				ret = far_dirent_add_file(f, dirent, ino,
+				    si.si_mode, 0);
+				if (ret)
+					return (ret);
+			}
+		}
+	}
+
+	return (0);
+}
+
+static int
+dirent_del(struct far_enum *fe, far_dirent_t *dirent,
+    uint64_t ino, uint64_t remains)
+{
+	far_info_t si;
+	int ret;
+
+	ret = far_get_info(fe->fe_far, ino, FAR_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
+far_dirent_del_pass2(void *far_enump, char *name, uint64_t ino)
+{
+	struct far_enum *fe = far_enump;
+	far_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, FAR_NO_INO));
+}
+
+static int
+far_dirent_mod_pass2(void *far_enump, char *name,
+    uint64_t ino_old, uint64_t ino_new)
+{
+	struct far_enum *fe = far_enump;
+	far_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
+far_dirent_unmod_pass2(void *far_enump, char *name, uint64_t ino)
+{
+	struct far_enum *fe = far_enump;
+	far_t *f = fe->fe_far;
+	int ret;
+	uint64_t cnt;
+	far_info_t si_old;
+	far_info_t si_new;
+	far_dirent_t dirent = {
+		.fd_name = name,
+		.fd_parent_ino = fe->fe_parent_ino,
+		.fd_prev = fe->fe_dirent_chain,
+	};
+
+	ret = far_get_count(&f->f_link_add_cnt, ino, &cnt, NULL);
+	if (ret)
+		return (ret == ENOENT ? 0 : ret);
+
+	ret = far_get_info(f, ino, FAR_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 = far_get_info(f, ino, FAR_NEW, &si_new, FI_ATTR_MODE);
+	if (ret)
+		return (ret);
+	ret = far_send_unlink(f, &dirent, ino);
+	if (ret)
+		return (ret);
+	if (S_ISDIR(si_new.si_mode)) {
+		far_free_count(&f->f_link_add_cnt, ino);
+		ret = far_send_rename_from_tempname(f, &dirent, ino, ino);
+		if (ret)
+			return (ret);
+		ret = far_send_mtime_update(f, &dirent, ino);
+	} else {
+		ret = far_dirent_add_file(f, &dirent, ino, si_new.si_mode, 0);
+	}
+
+	return (ret);
+}
+
+static int
+far_add_pass2(far_t *f, uint64_t ino)
+{
+	f->f_current_ino = ino;
+	f->f_current_path = NULL;
+
+	return (far_send_mtime_update(f, NULL, ino));
+}
+
+static int
+far_dir_del_pass2(far_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 = far_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
+far_mod_pass2(far_t *f, uint64_t ino)
+{
+	far_info_t si_old;
+	far_info_t si_new;
+	int ret;
+	uint64_t put_back_cnt = 0;
+
+	f->f_current_ino = ino;
+	f->f_current_path = NULL;
+
+	ret = far_get_info(f, ino, FAR_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 = far_get_info(f, ino, FAR_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 = far_dir_del_pass2(f, ino);
+			if (ret)
+				return (ret);
+		}
+		return (far_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 (far_send_mtime_update(f, NULL, ino));
+}
--- /dev/null	2012-10-26 17:09:25.000000000 +0200
+++ new/usr/src/uts/common/fs/zfs/far_send.c	2012-10-26 17:09:25.027684000 +0200
@@ -0,0 +1,946 @@
+/*
+ * 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/far.h>
+#include <sys/far_impl.h>
+#include <sys/far_crc32c.h>
+
+#define	TEMPNAME_PREFIX "far-tempname-"
+/* 2^128 needs 39 digits in decimal */
+#define	TEMPNAME_SIZE (sizeof (TEMPNAME_PREFIX) + 39)
+
+void
+far_send_init(far_t *f)
+{
+	f->f_alloc_len = FAR_SEND_BUF_SIZE;
+	f->f_buf = kmem_alloc(f->f_alloc_len, KM_SLEEP);
+	f->f_size = 0;
+}
+
+void
+far_send_fini(far_t *f)
+{
+	kmem_free(f->f_buf, f->f_alloc_len);
+}
+
+static int
+far_send_reserve(far_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
+far_send_put(far_t *f, void *buf, int len)
+{
+	int ret;
+	void *p;
+
+	ret = far_send_reserve(f, &p, len);
+	if (ret)
+		return (ret);
+
+	memcpy(p, buf, len);
+
+	return (0);
+}
+
+static int
+far_send_put_attr(far_t *f, uint16_t attr, void *buf, int len)
+{
+	far_attr_header_t hdr;
+	int ret;
+
+	LE_OUT16(&hdr.fa_type, attr);
+	LE_OUT16(&hdr.fa_len, len);
+
+	ret = far_send_put(f, &hdr, sizeof (hdr));
+	if (ret)
+		return (ret);
+	return (far_send_put(f, buf, len));
+}
+
+static int
+far_send_reserve_attr(far_t *f, uint16_t attr, void **buf, int len)
+{
+	far_attr_header_t hdr;
+	int ret;
+
+	LE_OUT16(&hdr.fa_type, attr);
+	LE_OUT16(&hdr.fa_len, len);
+
+	ret = far_send_put(f, &hdr, sizeof (hdr));
+	if (ret)
+		return (ret);
+	return (far_send_reserve(f, buf, len));
+}
+
+static int
+far_send_put_u64(far_t *f, uint16_t attr, uint64_t val)
+{
+	uint64_t v;
+
+	LE_OUT64(&v, val);
+	return (far_send_put_attr(f, attr, &v, sizeof (v)));
+}
+
+static int
+far_send_put_time(far_t *f, uint16_t attr, far_time_t *t)
+{
+	char buf[12];
+
+	LE_OUT64(buf, t->st_sec);
+	LE_OUT32(buf + 8, t->st_nsec);
+
+	return (far_send_put_attr(f, attr, buf, sizeof (buf)));
+}
+
+static int
+far_cmd_start(far_t *f, uint16_t cmd)
+{
+	far_cmd_header_t ch;
+
+	memset(&ch, 0, sizeof (ch));
+	LE_OUT16(&ch.fc_cmd, cmd);
+	f->f_size = 0;
+	return (far_send_put(f, &ch, sizeof (ch)));
+}
+
+static int
+far_cmd_send(far_t *f)
+{
+	far_cmd_header_t *ch;
+	uint32_t crc;
+	int ret;
+
+	ch = (far_cmd_header_t *)f->f_buf;
+	LE_OUT32(&ch->fc_len, f->f_size - sizeof (*ch));
+	ch->fc_crc = 0;
+
+	crc = far_crc32c(0, f->f_buf, f->f_size);
+	LE_OUT32(&ch->fc_crc, crc);
+
+	ret = far_write(f, f->f_buf, f->f_size);
+	f->f_size = 0;
+
+	mutex_enter(&f->f_tods->ds_sendstream_lock);
+	*f->f_dmu_sendarg.dsa_off += f->f_size;
+	mutex_exit(&f->f_tods->ds_sendstream_lock);
+
+	return (ret);
+}
+
+static int
+far_send_stream_header(far_t *f)
+{
+	far_stream_header_t header;
+
+	strcpy(header.fs_magic, FAR_SEND_STREAM_MAGIC);
+	LE_OUT32(&header.fs_version, FAR_SEND_STREAM_VERSION);
+
+	return (far_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(far_path_t **fp, char *name, int namelen)
+{
+	far_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(far_path_t *fp, char *b)
+{
+	far_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(far_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(far_t *f, uint16_t attr, far_path_t *fp)
+{
+	int ret;
+	void *p;
+
+	ret = far_send_reserve_attr(f, attr, &p, fp->fp_total_len);
+	if (ret)
+		return (ret);
+	path_copy(fp, p);
+
+	return (0);
+}
+
+void
+far_path_free(far_path_t *fp)
+{
+	far_path_t *next;
+	while (fp) {
+		next = fp->fp_next;
+		kmem_free(fp, fp->fp_len + sizeof (*fp));
+		fp = next;
+	}
+}
+
+static int
+is_ino_run(far_t *f, uint64_t ino)
+{
+	int ret;
+	far_info_t si;
+
+	while (1) {
+		if (ino > f->f_current_ino)
+			return (0);
+		ret = far_get_info(f, ino, FAR_OLD, &si, 0);
+		if (ret && ret != ENOENT)
+			return (ret);
+		if (ret != ENOENT)
+			break;
+		ret = far_get_info(f, ino, FAR_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(far_t *f, far_dirent_t *dirent, uint64_t ino,
+    int devise_tempname, far_which_t which_in, far_path_t **fp)
+{
+	int ret = 0;
+	far_dirent_t *de;
+	far_info_t si;
+	far_which_t which;
+	far_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 = far_get_info(f, ino, FAR_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 = far_get_info(f, ino, FAR_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 == FAR_OLD) {
+				new_parent = 0;
+			} else if (which_in == FAR_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 = FAR_OLD;
+			} else if (!old_parent && new_parent) {
+				which = FAR_NEW;
+			} else if (is_ino_run(f, new_parent)) {
+				check_tempname = 1;
+				which = FAR_NEW;
+			} else {
+				which = FAR_OLD;
+			}
+		} else {
+			if (old_parent && !new_parent) {
+				which = FAR_OLD;
+			} else {
+				check_tempname = 1;
+				which = FAR_NEW;
+			}
+		}
+		if (which == FAR_OLD)
+			parent = old_parent;
+		else
+			parent = new_parent;
+		if (parent == ino)
+			break;
+		ret = far_find_entry(f, parent, ino, which, &t_name);
+		if (ret)
+			return (ret);
+		name = strdup(t_name);
+		far_free_name(t_name);
+		namebuflen = strlen(name) + 1;
+		if (check_tempname) {
+			far_info_t si_old;
+			far_info_t si_new;
+
+			ret = far_get_info(f, parent, FAR_OLD,
+					    &si_old, FI_ATTR_GEN);
+			if (ret && ret != ENOENT)
+				return (ret);
+			if (ret == 0) {
+				ret = far_get_info(f, parent, FAR_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 = far_lookup_entry(f, parent, name,
+						FAR_OLD, &old_ino);
+			if (ret && ret != ENOENT) {
+				far_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 = far_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
+far_send_start(far_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 = FAR_CMD_SUBVOL;
+
+	ret = far_send_stream_header(f);
+	if (ret) {
+		far_abort(f);
+		return (ret);
+	}
+
+	if ((ret = far_get_uuid(f, FAR_NEW, n_uuid)) ||
+	    (ret = far_get_ctransid(f, FAR_NEW, &n_ctrans)) ||
+	    (ret = far_get_snapname(f, FAR_NEW, &path, &len)))
+		goto out;
+	/* for now, strip the pool name */
+	if ((p = strchr(path, '/')))
+		++p;
+	else
+		p = path;
+	ret = far_get_uuid(f, FAR_OLD, o_uuid);
+	if (ret && ret != ENOENT)
+		goto out;
+	if (ret == 0) {
+		ret = far_get_ctransid(f, FAR_OLD, &o_ctrans);
+		if (ret)
+			goto out;
+		cmd = FAR_CMD_SNAPSHOT;
+	}
+	if ((ret = far_cmd_start(f, cmd)) ||
+	    (ret = far_send_put_attr(f, FAR_ATTR_PATH, p, strlen(p))) ||
+	    (ret = far_send_put_u64(f, FAR_ATTR_CTRANSID, n_ctrans)) ||
+	    (ret = far_send_put_attr(f, FAR_ATTR_UUID, n_uuid, 16)))
+		goto out;
+	if (cmd == FAR_CMD_SNAPSHOT) {
+		if ((ret = far_send_put_u64(f, FAR_ATTR_CLONE_CTRANSID,
+		    o_ctrans)) ||
+		    (ret = far_send_put_attr(f, FAR_ATTR_CLONE_UUID,
+		    o_uuid, 16)))
+			goto out;
+	}
+	ret = far_cmd_send(f);
+
+out:
+	kmem_free(path, len);
+
+	return (ret);
+}
+
+int
+far_send_create_file(far_t *f, far_dirent_t *dirent, uint64_t ino,
+    int devise_tempname, far_path_t **path_ret)
+{
+	far_path_t *path = NULL;
+	far_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, FAR_NEW, &path);
+	if (ret)
+		goto out;
+
+	ret = far_get_info(f, ino, FAR_NEW, &si,
+			    FI_ATTR_MODE | FI_ATTR_UID | FI_ATTR_GID);
+	if (ret)
+		goto out;
+
+	if (S_ISREG(si.si_mode)) {
+		cmd = FAR_CMD_MKFILE;
+	} else if (S_ISDIR(si.si_mode)) {
+		cmd = FAR_CMD_MKDIR;
+	} else if (S_ISLNK(si.si_mode)) {
+		cmd = FAR_CMD_SYMLINK;
+		ret = far_read_symlink(f, ino, FAR_NEW, &symlink, &symlen);
+		if (ret)
+			goto out;
+	} else if (S_ISCHR(si.si_mode) || S_ISBLK(si.si_mode)) {
+		cmd = FAR_CMD_MKNOD;
+		send_rdev = 1;
+	} else if (S_ISFIFO(si.si_mode)) {
+		cmd = FAR_CMD_MKFIFO;
+	} else if (S_ISSOCK(si.si_mode)) {
+		cmd = FAR_CMD_MKSOCK;
+	} else {
+		/* unknown file type, ignore for now */
+		return (0);
+	}
+
+	if (send_rdev) {
+		far_info_t sirdev;
+		uint64_t r_major;
+		uint64_t r_minor;
+		ret = far_get_info(f, ino, FAR_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 = far_cmd_start(f, cmd)) ||
+	    (ret = put_path(f, FAR_ATTR_PATH, path)) ||
+	    (ret = far_send_put_u64(f, FAR_ATTR_INO, ino)))
+		goto out;
+	if (send_rdev) {
+		ret = far_send_put_u64(f, FAR_ATTR_RDEV, rdev);
+		if (ret)
+			goto out;
+		ret = far_send_put_u64(f, FAR_ATTR_MODE, si.si_mode);
+		if (ret)
+			goto out;
+	}
+	if (S_ISLNK(si.si_mode)) {
+		ret = far_send_put_attr(f, FAR_ATTR_PATH_LINK,
+					symlink, strlen(symlink));
+		if (ret)
+			goto out;
+	}
+	if ((ret = far_cmd_send(f)))
+		goto out;
+
+	/* send CHOWN */
+	if ((ret = far_cmd_start(f, FAR_CMD_CHOWN)) ||
+	    (ret = put_path(f, FAR_ATTR_PATH, path)) ||
+	    (ret = far_send_put_u64(f, FAR_ATTR_UID, si.si_uid)) ||
+	    (ret = far_send_put_u64(f, FAR_ATTR_GID, si.si_gid)) ||
+	    (ret = far_cmd_send(f)))
+		goto out;
+
+	/* send CHMOD, but not for symlinks */
+	if (!S_ISLNK(si.si_mode)) {
+		if ((ret = far_cmd_start(f, FAR_CMD_CHMOD)) ||
+		    (ret = put_path(f, FAR_ATTR_PATH, path)) ||
+		    (ret = far_send_put_u64(f, FAR_ATTR_MODE,
+		    si.si_mode & 0xfff)) ||
+		    (ret = far_cmd_send(f)))
+			goto out;
+	}
+
+out:
+	if (ret == 0 && path_ret)
+		*path_ret = path;
+	else
+		far_path_free(path);
+	if (symlink)
+		kmem_free(symlink, symlen);
+	return (ret);
+}
+
+int
+far_send_link(far_t *f, far_dirent_t *new_dirent, uint64_t ino,
+    uint64_t old_parent_ino, far_which_t which, int devise_tempname)
+{
+	far_path_t *new_path = NULL;
+	far_path_t *old_path = NULL;
+	int ret;
+	far_dirent_t old_dirent = {
+		.fd_name = NULL,
+		.fd_parent_ino = old_parent_ino,
+		.fd_prev = NULL,
+	};
+
+	ret = far_find_entry(f, old_parent_ino, ino, which,
+	    &old_dirent.fd_name);
+	if (ret)
+		return (ret);
+	ret = build_path(f, &old_dirent, ino, 0, FAR_OLD, &old_path);
+	if (ret)
+		goto out;
+
+	ret = build_path(f, new_dirent, ino, devise_tempname, FAR_NEW,
+	    &new_path);
+	if (ret)
+		goto out;
+
+	if ((ret = far_cmd_start(f, FAR_CMD_LINK)) ||
+	    (ret = put_path(f, FAR_ATTR_PATH_LINK, old_path)) ||
+	    (ret = put_path(f, FAR_ATTR_PATH, new_path)) ||
+	    (ret = far_cmd_send(f)))
+		goto out;
+
+	if (f->f_pass == PASS_UNLINK)
+		ret = far_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);
+	far_path_free(old_path);
+	far_path_free(new_path);
+	return (ret);
+}
+
+int
+far_send_mkdir(far_t *f, far_dirent_t *dirent,
+		uint64_t ino, int devise_tempname)
+{
+	far_path_t *path = NULL;
+	far_info_t si;
+	int ret;
+
+	ret = build_path(f, dirent, ino, devise_tempname, FAR_NEW, &path);
+	if (ret)
+		goto out;
+
+	ret = far_get_info(f, ino, FAR_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 = far_cmd_start(f, FAR_CMD_MKDIR)) ||
+		    (ret = put_path(f, FAR_ATTR_PATH, path)) ||
+		    (ret = far_send_put_u64(f, FAR_ATTR_INO, ino)) ||
+		    (ret = far_cmd_send(f)))
+			goto out;
+	}
+
+	/* send CHOWN */
+	if ((ret = far_cmd_start(f, FAR_CMD_CHOWN)) ||
+	    (ret = put_path(f, FAR_ATTR_PATH, path)) ||
+	    (ret = far_send_put_u64(f, FAR_ATTR_UID, si.si_uid)) ||
+	    (ret = far_send_put_u64(f, FAR_ATTR_GID, si.si_gid)) ||
+	    (ret = far_cmd_send(f)))
+		goto out;
+
+	/* send CHMOD */
+	if ((ret = far_cmd_start(f, FAR_CMD_CHMOD)) ||
+	    (ret = put_path(f, FAR_ATTR_PATH, path)) ||
+	    (ret = far_send_put_u64(f, FAR_ATTR_MODE,
+	    si.si_mode & 0xfff)) ||
+	    (ret = far_cmd_send(f)))
+		goto out;
+
+out:
+	far_path_free(path);
+	return (ret);
+}
+
+/* this one is only used for directory renames */
+int
+far_send_rename(far_t *f, far_dirent_t *new_dirent, uint64_t ino,
+    uint64_t old_parent_ino, int devise_tempname)
+{
+	far_path_t *new_path = NULL;
+	far_path_t *old_path = NULL;
+	int ret;
+	far_dirent_t old_dirent = {
+		.fd_name = NULL,
+		.fd_parent_ino = old_parent_ino,
+		.fd_prev = NULL,
+	};
+
+	ret = far_find_entry(f, old_parent_ino, ino, FAR_OLD,
+	    &old_dirent.fd_name);
+	if (ret)
+		return (ret);
+	ret = build_path(f, &old_dirent, ino, 0, FAR_OLD, &old_path);
+	if (ret)
+		goto out;
+
+	ret = build_path(f, new_dirent, ino, devise_tempname, FAR_NEW,
+	    &new_path);
+	if (ret)
+		goto out;
+
+	if ((ret = far_cmd_start(f, FAR_CMD_RENAME)) ||
+	    (ret = put_path(f, FAR_ATTR_PATH, old_path)) ||
+	    (ret = put_path(f, FAR_ATTR_PATH_TO, new_path)) ||
+	    (ret = far_cmd_send(f)))
+		goto out;
+out:
+	far_path_free(old_path);
+	far_path_free(new_path);
+	return (ret);
+}
+
+int
+far_send_rename_from_tempname(far_t *f, far_dirent_t *dirent,
+    uint64_t ino, uint64_t old)
+{
+	char buf[TEMPNAME_SIZE];
+	far_path_t *new_path = NULL;
+	far_path_t *old_path = NULL;
+	int ret;
+	far_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, FAR_OLD, &old_path);
+	if (ret)
+		goto out;
+	ret = build_path(f, dirent, ino, 0, FAR_NEW, &new_path);
+	if (ret)
+		goto out;
+
+	if ((ret = far_cmd_start(f, FAR_CMD_RENAME)) ||
+	    (ret = put_path(f, FAR_ATTR_PATH, old_path)) ||
+	    (ret = put_path(f, FAR_ATTR_PATH_TO, new_path)) ||
+	    (ret = far_cmd_send(f)))
+		goto out;
+
+	ret = far_send_mtime_update(f, dirent, old);
+
+out:
+	far_path_free(old_path);
+	far_path_free(new_path);
+	return (ret);
+}
+
+int
+far_send_unlink(far_t *f, far_dirent_t *dirent, uint64_t ino)
+{
+	far_path_t *path = NULL;
+	int ret;
+
+	ret = build_path(f, dirent, ino, 0, FAR_OLD, &path);
+	if (ret)
+		goto out;
+
+	if ((ret = far_cmd_start(f, FAR_CMD_UNLINK)) ||
+	    (ret = put_path(f, FAR_ATTR_PATH, path)) ||
+	    (ret = far_cmd_send(f)))
+		goto out;
+
+out:
+	far_path_free(path);
+	return (ret);
+}
+
+int
+far_send_rmdir(far_t *f, far_dirent_t *dirent, uint64_t ino)
+{
+	far_path_t *path;
+	int ret;
+
+	ret = build_path(f, dirent, ino, 0, FAR_OLD, &path);
+	if (ret)
+		goto out;
+
+	if ((ret = far_cmd_start(f, FAR_CMD_RMDIR)) ||
+	    (ret = put_path(f, FAR_ATTR_PATH, path)) ||
+	    (ret = far_cmd_send(f)))
+		goto out;
+
+out:
+	far_path_free(path);
+	return (ret);
+}
+
+int
+far_send_file_data(far_t *f, far_path_t **path_p,
+		    far_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, FAR_NEW, path_p);
+		if (ret)
+			return (ret);
+	}
+
+	while (len) {
+		uint64_t l = MIN(len, FAR_SEND_READ_SIZE);
+
+		if ((ret = far_cmd_start(f, FAR_CMD_WRITE)) ||
+		    (ret = put_path(f, FAR_ATTR_PATH, *path_p)) ||
+		    (ret = far_send_put_u64(f, FAR_ATTR_FILE_OFFSET, off))||
+		    (ret = far_send_put_attr(f, FAR_ATTR_DATA, data, l)) ||
+		    (ret = far_cmd_send(f)))
+			goto out;
+		data += l;
+		off += l;
+		len -= l;
+	}
+
+out:
+	return (ret);
+}
+
+int
+far_send_mtime_update(far_t *f, far_dirent_t *dirent, uint64_t ino)
+{
+	far_path_t *path = NULL;
+	int ret;
+	far_info_t si;
+
+	ret = far_get_info(f, ino, FAR_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, FAR_NEW, &path);
+	if (ret)
+		goto out;
+
+	if ((ret = far_cmd_start(f, FAR_CMD_UTIMES)) ||
+	    (ret = put_path(f, FAR_ATTR_PATH, path)) ||
+	    (ret = far_send_put_time(f, FAR_ATTR_ATIME, &si.si_atime)) ||
+	    (ret = far_send_put_time(f, FAR_ATTR_MTIME, &si.si_mtime)) ||
+	    (ret = far_send_put_time(f, FAR_ATTR_CTIME, &si.si_ctime)) ||
+	    (ret = far_send_put_time(f, FAR_ATTR_OTIME, &si.si_otime)) ||
+	    (ret = far_cmd_send(f)))
+		goto out;
+
+out:
+	far_path_free(path);
+	return (ret);
+}
+
+int
+far_send_truncate(far_t *f, far_dirent_t *dirent, uint64_t ino,
+    uint64_t new_size)
+{
+	far_path_t *path = NULL;
+	int ret;
+
+	ret = build_path(f, dirent, ino, 0, FAR_NEW, &path);
+	if (ret)
+		return (ret);
+
+	if ((ret = far_cmd_start(f, FAR_CMD_TRUNCATE)) ||
+	    (ret = put_path(f, FAR_ATTR_PATH, path)) ||
+	    (ret = far_send_put_u64(f, FAR_ATTR_SIZE, new_size)) ||
+	    (ret = far_cmd_send(f)))
+		goto out;
+
+out:
+	far_path_free(path);
+	return (ret);
+}
+
+int
+far_send_chown(far_t *f, far_dirent_t *dirent, uint64_t ino,
+		    uint64_t new_uid, uint64_t new_gid)
+{
+	far_path_t *path = NULL;
+	int ret;
+
+	ret = build_path(f, dirent, ino, 0, FAR_NEW, &path);
+	if (ret)
+		return (ret);
+
+	if ((ret = far_cmd_start(f, FAR_CMD_CHOWN)) ||
+	    (ret = put_path(f, FAR_ATTR_PATH, path)) ||
+	    (ret = far_send_put_u64(f, FAR_ATTR_UID, new_uid)) ||
+	    (ret = far_send_put_u64(f, FAR_ATTR_GID, new_gid)) ||
+	    (ret = far_cmd_send(f)))
+		goto out;
+
+out:
+	far_path_free(path);
+	return (ret);
+}
+
+int
+far_send_chmod(far_t *f, far_dirent_t *dirent, uint64_t ino,
+		    uint64_t new_mode)
+{
+	far_path_t *path = NULL;
+	int ret;
+
+	ret = build_path(f, dirent, ino, 0, FAR_NEW, &path);
+	if (ret)
+		return (ret);
+
+	if ((ret = far_cmd_start(f, FAR_CMD_CHMOD)) ||
+	    (ret = put_path(f, FAR_ATTR_PATH, path)) ||
+	    (ret = far_send_put_u64(f, FAR_ATTR_MODE, new_mode)) ||
+	    (ret = far_cmd_send(f)))
+		goto out;
+
+out:
+	far_path_free(path);
+	return (ret);
+}
+
+int
+far_send_end(far_t *f)
+{
+	int ret;
+
+	if ((ret = far_cmd_start(f, FAR_CMD_END)) ||
+	    (ret = far_cmd_send(f)))
+		goto out;
+
+out:
+	return (ret);
+}
--- old/usr/src/uts/common/fs/zfs/sys/dsl_dataset.h	2012-10-26 17:09:25.144683000 +0200
+++ new/usr/src/uts/common/fs/zfs/sys/dsl_dataset.h	2012-10-26 17:09:25.150687000 +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-26 17:09:25.000000000 +0200
+++ new/usr/src/uts/common/fs/zfs/sys/far.h	2012-10-26 17:09:25.387681000 +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_FAR_H
+#define	_SYS_FAR_H
+
+#include <sys/inttypes.h>
+#include <sys/types.h>
+#include <sys/dmu.h>
+#include <sys/vnode.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+#define	FAR_SEND_STREAM_MAGIC "btrfs-stream"
+#define	FAR_SEND_STREAM_VERSION 1
+
+#define	FAR_SEND_BUF_SIZE 65536
+#define	FAR_SEND_READ_SIZE 49152
+
+typedef struct _far_stream_header {
+	char		fs_magic[sizeof (FAR_SEND_STREAM_MAGIC)];
+	uint32_t	fs_version;
+} __attribute__((__packed__)) far_stream_header_t;
+
+typedef struct _far_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__)) far_cmd_header_t;
+
+typedef struct _far_attr_header {
+	uint16_t	fa_type;
+	/* len of the payload, not including header */
+	uint16_t	fa_len;
+} __attribute__((__packed__)) far_attr_header_t;
+
+/* commands */
+#define	FAR_CMD_SUBVOL		1
+#define	FAR_CMD_SNAPSHOT	2
+#define	FAR_CMD_MKFILE		3
+#define	FAR_CMD_MKDIR		4
+#define	FAR_CMD_MKNOD		5
+#define	FAR_CMD_MKFIFO		6
+#define	FAR_CMD_MKSOCK		7
+#define	FAR_CMD_SYMLINK	8
+#define	FAR_CMD_RENAME		9
+#define	FAR_CMD_LINK		10
+#define	FAR_CMD_UNLINK		11
+#define	FAR_CMD_RMDIR		12
+#define	FAR_CMD_SET_XATTR	13
+#define	FAR_CMD_REMOVE_XATTR	14
+#define	FAR_CMD_WRITE		15
+#define	FAR_CMD_CLONE		16
+#define	FAR_CMD_TRUNCATE	17
+#define	FAR_CMD_CHMOD		18
+#define	FAR_CMD_CHOWN		19
+#define	FAR_CMD_UTIMES		20
+#define	FAR_CMD_END		21
+#define	FAR_CMD_MAX		21
+
+/* attributes */
+#define	FAR_ATTR_UUID			1
+#define	FAR_ATTR_CTRANSID		2
+#define	FAR_ATTR_INO			3
+#define	FAR_ATTR_SIZE			4
+#define	FAR_ATTR_MODE			5
+#define	FAR_ATTR_UID			6
+#define	FAR_ATTR_GID			7
+#define	FAR_ATTR_RDEV			8
+#define	FAR_ATTR_CTIME			9
+#define	FAR_ATTR_MTIME			10
+#define	FAR_ATTR_ATIME			11
+#define	FAR_ATTR_OTIME			12
+#define	FAR_ATTR_XATTR_NAME		13
+#define	FAR_ATTR_XATTR_DATA		14
+#define	FAR_ATTR_PATH			15
+#define	FAR_ATTR_PATH_TO		16
+#define	FAR_ATTR_PATH_LINK		17
+#define	FAR_ATTR_FILE_OFFSET		18
+#define	FAR_ATTR_DATA			19
+#define	FAR_ATTR_CLONE_UUID		20
+#define	FAR_ATTR_CLONE_CTRANSID	21
+#define	FAR_ATTR_CLONE_PATH		22
+#define	FAR_ATTR_CLONE_OFFSET		23
+#define	FAR_ATTR_CLONE_LEN		24
+#define	FAR_ATTR_MAX			24
+
+int far_send(objset_t *tosnap, objset_t *fromsnap, int outfd, vnode_t *vp,
+    offset_t *off);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* _SYS_FAR_H */
--- /dev/null	2012-10-26 17:09:25.000000000 +0200
+++ new/usr/src/uts/common/fs/zfs/sys/far_crc32c.h	2012-10-26 17:09:25.492684000 +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_FAR_CRC32C_H
+#define	_SYS_FAR_CRC32C_H
+
+#include <sys/inttypes.h>
+#include <sys/types.h>
+
+uint32_t far_crc32c(uint32_t seed, const uint8_t *data, int len);
+
+#endif /* _SYS_FAR_CRC32C_H */
--- /dev/null	2012-10-26 17:09:25.000000000 +0200
+++ new/usr/src/uts/common/fs/zfs/sys/far_impl.h	2012-10-26 17:09:25.598689000 +0200
@@ -0,0 +1,237 @@
+/*
+ * 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_FAR_IMPL_H
+#define	_SYS_FAR_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>
+#include <sys/avl.h>
+#include <sys/dmu_impl.h>
+
+#define	FAR_NO_INO	0
+
+enum pass {
+	PASS_LINK,
+	PASS_UNLINK
+};
+
+typedef struct _far_count_elem {
+	avl_node_t	fce_avl_node;
+	uint64_t	fce_ino;
+	uint64_t	fce_count;
+	uint64_t	fce_aux;
+} far_count_elem_t;
+
+typedef struct _far_counter {
+	avl_tree_t fc_avl;
+	char *fc_name;
+} far_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 _far {
+	enum pass f_pass;
+	struct _far_ops	*f_ops;
+	far_counter_t f_del_dir_cnt;
+	far_counter_t f_put_back_cnt;
+	far_counter_t f_link_add_cnt;
+	uint64_t f_current_ino;
+	struct _far_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;
+	dmu_sendarg_t	f_dmu_sendarg;
+} far_t;
+
+typedef struct _far_ops {
+	int (*far_dir_add)(far_t *f, uint64_t ino);
+	int (*far_dir_del)(far_t *f, uint64_t ino);
+	int (*far_dir_mod)(far_t *f, uint64_t ino);
+	int (*far_dirent_add)(void *far_enump, char *name, uint64_t ino);
+	int (*far_dirent_del)(void *far_enump, char *name, uint64_t ino);
+	int (*far_dirent_mod)(void *far_enump, char *name,
+	    uint64_t ino_old, uint64_t ino_new);
+	int (*far_dirent_unmod)(void *far_enump, char *name, uint64_t ino);
+	int (*far_file_add)(far_t *f, uint64_t ino);
+	int (*far_file_del)(far_t *f, uint64_t ino);
+	int (*far_file_mod)(far_t *f, uint64_t ino);
+	int (*far_file_data)(void *far_filep, void *data, uint64_t off,
+	    uint64_t len);
+} far_ops_t;
+
+typedef struct _far_path {
+	struct _far_path *fp_next;
+	int fp_len;
+	int fp_total_len;
+	char fp_buf[0];
+} far_path_t;
+
+typedef struct _far_dirent {
+	char *fd_name;
+	uint64_t fd_parent_ino;
+	struct _far_dirent *fd_prev;
+} far_dirent_t;
+
+typedef enum _far_which {
+	FAR_UNDEF,
+	FAR_OLD,
+	FAR_NEW
+} far_which_t;
+
+typedef struct _far_time {
+	uint64_t	st_sec;
+	uint64_t	st_nsec;
+} far_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 _far_info {
+	uint64_t	si_nlinks;
+	uint64_t	si_parent;
+	union {
+		uint64_t	si_nentries;
+		uint64_t	si_size;
+	};
+	far_time_t	si_atime;
+	far_time_t	si_mtime;
+	far_time_t	si_ctime;
+	far_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;
+} far_info_t;
+
+int far_start(far_t *f, far_ops_t **);
+int far_start2(far_t *f, far_ops_t **);
+int far_abort(far_t *f);
+int far_end(far_t *f);
+
+int far_dirent_add_file(far_t *f, far_dirent_t *dirent,
+    uint64_t ino, uint64_t mode, int exists);
+void far_path_free(far_path_t *fp);
+
+int far_get_info(far_t *f, uint64_t dnobj, far_which_t which,
+    far_info_t *sp, uint64_t flags);
+
+typedef int (*far_file_cb_t)(void *ctx, void *data, int len);
+int far_file_contents(far_t *f, uint64_t dnobj, void *ctx);
+int far_dir_contents(far_t *f, uint64_t dnobj, void *ctx);
+int far_find_entry(far_t *f, uint64_t dirobj, uint64_t dnobj,
+    far_which_t which, char **name);
+void far_free_name(char *name);
+int far_lookup_entry(far_t *f, uint64_t dirobj, char *name,
+    far_which_t which, uint64_t *dnobj);
+int far_write(far_t *f, const uint8_t *data, int len);
+int far_get_uuid(far_t *f, far_which_t which, uint8_t data[16]);
+int far_get_ctransid(far_t *f, far_which_t which,
+    uint64_t *ctransid);
+int far_get_snapname(far_t *f, far_which_t which,
+    char **name, int *len);
+int far_read_symlink(far_t *f, uint64_t dnobj, far_which_t which,
+    char **target, int *plen);
+
+int far_send_start(far_t *f);
+int far_send_create_file(far_t *f, far_dirent_t *dirent, uint64_t ino,
+int devise_tempname, far_path_t **path_ret);
+int far_send_link(far_t *f, far_dirent_t *new_dirent, uint64_t ino,
+    uint64_t old_parent_ino, far_which_t which,
+int devise_tempname);
+int far_send_mkdir(far_t *f, far_dirent_t *dirent, uint64_t ino,
+int devise_tempname);
+int far_send_rename(far_t *f, far_dirent_t *dirent, uint64_t ino,
+    uint64_t old_parent_ino, int devise_tempname);
+int far_send_rename_from_tempname(far_t *f, far_dirent_t *dirent,
+    uint64_t ino, uint64_t old);
+int far_send_unlink(far_t *f, far_dirent_t *dirent, uint64_t ino);
+int far_send_rmdir(far_t *f, far_dirent_t *dirent, uint64_t ino);
+/* TODO: make **path *path or do we really still alloc it? */
+int far_send_file_data(far_t *f,
+    far_path_t **path, far_dirent_t *dirent,
+    uint64_t ino, uint64_t off, uint64_t len, void *data);
+int far_send_mtime_update(far_t *f, far_dirent_t *dirent, uint64_t ino);
+int far_send_truncate(far_t *f, far_dirent_t *dirent, uint64_t ino,
+    uint64_t new_size);
+int far_send_chown(far_t *f, far_dirent_t *dirent, uint64_t ino,
+    uint64_t new_uid, uint64_t new_gid);
+int far_send_chmod(far_t *f, far_dirent_t *dirent, uint64_t ino,
+    uint64_t new_mode);
+int far_send_end(far_t *f);
+int far_count_init(far_counter_t *fc, char *name);
+void far_count_destroy(far_counter_t *fc);
+int far_add_count(far_counter_t *fc, uint64_t ino, uint64_t inc,
+    uint64_t aux, uint64_t *new_count, uint64_t *old_aux);
+void far_free_count(far_counter_t *fc, uint64_t ino);
+int far_get_count(far_counter_t *fc, uint64_t ino, uint64_t *new_count,
+    uint64_t *old_aux);
+int far_count_fini(far_counter_t *fc);
+
+void far_send_init(far_t *f);
+void far_send_fini(far_t *f);
+
+#endif /* _SYS_FAR_IMPL_H */
--- old/usr/src/uts/common/fs/zfs/zfs_ioctl.c	2012-10-26 17:09:25.705686000 +0200
+++ new/usr/src/uts/common/fs/zfs/zfs_ioctl.c	2012-10-26 17:09:25.713693000 +0200
@@ -172,6 +172,7 @@
 #include <sys/dsl_scan.h>
 #include <sharefs/share.h>
 #include <sys/dmu_objset.h>
+#include <sys/far.h>
 
 #include "zfs_namecheck.h"
 #include "zfs_prop.h"
@@ -4042,8 +4043,9 @@
  * 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.
+ * zc_guid	bit 0: if set, estimate size of stream only. zc_cookie is
+ *		ignored. output size in zc_objset_type.
+ * zc_guid	bit 1: if set, send output in FAR-format
  *
  * outputs: none
  */
@@ -4058,7 +4060,8 @@
 	dsl_dataset_t *dsfrom = NULL;
 	spa_t *spa;
 	dsl_pool_t *dp;
-	boolean_t estimate = (zc->zc_guid != 0);
+	boolean_t estimate = ((zc->zc_guid & 1) != 0);
+	boolean_t far = ((zc->zc_guid & 2) != 0);
 
 	error = spa_open(zc->zc_name, &spa, FTAG);
 	if (error)
@@ -4134,8 +4137,12 @@
 		}
 
 		off = fp->f_offset;
-		error = dmu_send(tosnap, fromsnap,
-		    zc->zc_cookie, fp->f_vnode, &off);
+		if (!far)
+			error = dmu_send(tosnap, fromsnap,
+			    zc->zc_cookie, fp->f_vnode, &off);
+		else
+			error = far_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;
@@ -5092,11 +5099,16 @@
 	offset_t off;
 	char *fromname;
 	int fd;
+	int far;
 
 	error = nvlist_lookup_int32(innvl, "fd", &fd);
 	if (error != 0)
 		return (EINVAL);
 
+	error = nvlist_lookup_int32(innvl, "far", &far);
+	if (error != 0 && error != ENOENT)
+		return (EINVAL);
+
 	error = dmu_objset_hold(snapname, FTAG, &tosnap);
 	if (error)
 		return (error);
@@ -5119,7 +5131,10 @@
 	}
 
 	off = fp->f_offset;
-	error = dmu_send(tosnap, fromsnap, fd, fp->f_vnode, &off);
+	if (!far)
+		error = dmu_send(tosnap, fromsnap, fd, fp->f_vnode, &off);
+	else
+		error = far_send(tosnap, fromsnap, fd, fp->f_vnode, &off);
 
 	if (VOP_SEEK(fp->f_vnode, fp->f_offset, &off, NULL) == 0)
 		fp->f_offset = off;
