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