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