Mailing List Archive

[PATCH 1/2] sftp-server: support copy-data request
From: Mike Frysinger <vapier@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@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@openssh.com", 0, process_extended_fstatvfs, 0 },
{ "hardlink", "hardlink@openssh.com", 0, process_extended_hardlink, 1 },
{ "fsync", "fsync@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@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

_______________________________________________
openssh-unix-dev mailing list
openssh-unix-dev@mindrot.org
https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev