--- /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);
+}
