[PATCH 1/2] sftp-server: support copy-data request

Mike Frysinger vapier at gentoo.org
Tue Nov 13 13:22:15 AEDT 2018


From: Mike Frysinger <vapier at chromium.org>

Add support to the sftp-server for the copy-data extension defined in
[1].  This provides for efficient copying on the server side otherwise
clients have to emulate it by downloading the file before uploading it
to the new path.  For large files or slow networks, this can be
prohibitively slow. Now doing a file copy is limited only by the server
I/O.

For clients that provide virtual file systems on top of SFTP, this can
provide significant performance gains.

[1] https://tools.ietf.org/html/draft-ietf-secsh-filexfer-extensions-00#section-7
---
 PROTOCOL      | 39 ++++++++++++++++++++++-
 sftp-server.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 125 insertions(+), 1 deletion(-)

diff --git a/PROTOCOL b/PROTOCOL
index f75c1c0ae5b0..04a392db33be 100644
--- a/PROTOCOL
+++ b/PROTOCOL
@@ -459,12 +459,49 @@ This request asks the server to call fsync(2) on an open file handle.
 	string		"fsync at openssh.com"
 	string		handle
 
-One receiving this request, a server will call fsync(handle_fd) and will
+On receiving this request, a server will call fsync(handle_fd) and will
 respond with a SSH_FXP_STATUS message.
 
 This extension is advertised in the SSH_FXP_VERSION hello with version
 "1".
 
+3.7. sftp: Extension request "copy-data"
+
+This request asks the server to copy data from one open file handle and
+write it to a different open file handle.  This avoids needing to
+download the data before uploading it across the network twice.
+
+	byte		SSH_FXP_EXTENDED
+	uint32		id
+	string		"copy-data"
+	string		read-from-handle
+	uint64		read-from-offset
+	uint64		read-data-length
+	string		write-to-handle
+	uint64		write-to-offset
+
+The server will copy read-data-length bytes starting from
+read-from-offset from the read-from-handle and write them to
+write-to-handle starting from write-to-offset, and then respond with a
+SSH_FXP_STATUS message.
+
+It's equivalent to issuing a series of SSH_FXP_READ requests on
+read-from-handle and a series of requests of SSH_FXP_WRITE on
+write-to-handle.
+
+If read-from-handle and write-to-handle are the same, the server will
+fail the request and respond with a SSH_FX_INVALID_PARAMETER message.
+
+If read-data-length is 0, then the server will read data from the
+read-from-handle until EOF is reached.
+
+This extension is advertised in the SSH_FXP_VERSION hello with version
+"1".
+
+This request is identical to the "copy-data" request documented in:
+
+https://tools.ietf.org/html/draft-ietf-secsh-filexfer-extensions-00#section-7
+
 4. Miscellaneous changes
 
 4.1 Public key format
diff --git a/sftp-server.c b/sftp-server.c
index beac78ed0324..2d7c1c87e83f 100644
--- a/sftp-server.c
+++ b/sftp-server.c
@@ -107,6 +107,7 @@ static void process_extended_statvfs(u_int32_t id);
 static void process_extended_fstatvfs(u_int32_t id);
 static void process_extended_hardlink(u_int32_t id);
 static void process_extended_fsync(u_int32_t id);
+static void process_extended_copy_data(u_int32_t id);
 static void process_extended(u_int32_t id);
 
 struct sftp_handler {
@@ -148,6 +149,7 @@ static const struct sftp_handler extended_handlers[] = {
 	{ "fstatvfs", "fstatvfs at openssh.com", 0, process_extended_fstatvfs, 0 },
 	{ "hardlink", "hardlink at openssh.com", 0, process_extended_hardlink, 1 },
 	{ "fsync", "fsync at openssh.com", 0, process_extended_fsync, 1 },
+	{ "copy-data", "copy-data", 0, process_extended_copy_data, 1 },
 	{ NULL, NULL, 0, NULL, 0 }
 };
 
@@ -666,6 +668,9 @@ process_init(void)
 	    (r = sshbuf_put_cstring(msg, "1")) != 0 || /* version */
 	    /* fsync extension */
 	    (r = sshbuf_put_cstring(msg, "fsync at openssh.com")) != 0 ||
+	    (r = sshbuf_put_cstring(msg, "1")) != 0 || /* version */
+	    /* copy-data extension */
+	    (r = sshbuf_put_cstring(msg, "copy-data")) != 0 ||
 	    (r = sshbuf_put_cstring(msg, "1")) != 0) /* version */
 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
 	send_msg(msg);
@@ -1369,6 +1374,88 @@ process_extended_fsync(u_int32_t id)
 	send_status(id, status);
 }
 
+static void
+process_extended_copy_data(u_int32_t id)
+{
+	u_char buf[64*1024];
+	int read_handle, read_fd, write_handle, write_fd;
+	u_int64_t len, read_off, read_len, write_off;
+	int r, ret, copy_until_eof, status = SSH2_FX_OP_UNSUPPORTED;
+
+	if ((r = get_handle(iqueue, &read_handle)) != 0 ||
+	    (r = sshbuf_get_u64(iqueue, &read_off)) != 0 ||
+	    (r = sshbuf_get_u64(iqueue, &read_len)) != 0 ||
+	    (r = get_handle(iqueue, &write_handle)) != 0 ||
+	    (r = sshbuf_get_u64(iqueue, &write_off)) != 0)
+		fatal("%s: buffer error: %s", __func__, ssh_err(r));
+
+	debug("request %u: copy-data from \"%s\" (handle %d) off %llu len %llu "
+	    "to \"%s\" (handle %d) off %llu",
+	    id, handle_to_name(read_handle), read_handle,
+	    (unsigned long long)read_off, (unsigned long long)read_len,
+	    handle_to_name(write_handle), write_handle,
+	    (unsigned long long)write_off);
+
+	/* For read length of 0, we read until EOF. */
+	if (read_len == 0) {
+		read_len = (u_int64_t)-1 - read_off;
+		copy_until_eof = 1;
+	} else
+		copy_until_eof = 0;
+
+	read_fd = handle_to_fd(read_handle);
+	write_fd = handle_to_fd(write_handle);
+
+	/* Disallow reading & writing to the same handle */
+	if (read_handle == write_handle || read_fd < 0 || write_fd < 0) {
+		status = SSH2_FX_FAILURE;
+	} else {
+		if (lseek(read_fd, read_off, SEEK_SET) < 0) {
+			status = errno_to_portable(errno);
+			error("process_extended_copy_data: read_seek failed");
+		} else if (!(handle_to_flags(write_handle) & O_APPEND) &&
+				lseek(write_fd, write_off, SEEK_SET) < 0) {
+			status = errno_to_portable(errno);
+			error("process_extended_copy_data: write_seek failed");
+		} else {
+			/* Process the request in chunks. */
+			while (read_len || copy_until_eof) {
+				len = MINIMUM(sizeof(buf), read_len);
+				read_len -= len;
+
+				ret = read(read_fd, buf, len);
+				if (ret < 0) {
+					status = errno_to_portable(errno);
+					break;
+				} else if (ret == 0) {
+					status = copy_until_eof ? SSH2_FX_OK : SSH2_FX_EOF;
+					break;
+				}
+				len = ret;
+				handle_update_read(read_handle, len);
+
+				ret = write(write_fd, buf, len);
+				if (ret < 0) {
+					status = errno_to_portable(errno);
+					error("process_extended_copy_data: write failed");
+					break;
+				}
+				handle_update_write(write_handle, len);
+				if ((size_t)ret != len) {
+					debug2("nothing at all written");
+					status = SSH2_FX_FAILURE;
+					break;
+				}
+			}
+
+			if (read_len == 0)
+				status = SSH2_FX_OK;
+		}
+	}
+
+	send_status(id, status);
+}
+
 static void
 process_extended(u_int32_t id)
 {
-- 
2.17.1



More information about the openssh-unix-dev mailing list