Use the new copy-data extension for efficient remote file copies. At the very least, this provides a quick method for testing. --- sftp-client.c | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++ sftp-client.h | 3 ++ sftp.1 | 7 ++- sftp.c | 12 +++++ 4 files changed, 139 insertions(+), 1 deletion(-) diff --git a/sftp-client.c b/sftp-client.c index 4986d6d8d291..cd2844a8585e 100644 --- a/sftp-client.c +++ b/sftp-client.c @@ -86,6 +86,7 @@ struct sftp_conn { #define SFTP_EXT_FSTATVFS 0x00000004 #define SFTP_EXT_HARDLINK 0x00000008 #define SFTP_EXT_FSYNC 0x00000010 +#define SFTP_EXT_COPY_DATA 0x00000020 u_int exts; u_int64_t limit_kbps; struct bwlimit bwlimit_in, bwlimit_out; @@ -463,6 +464,10 @@ do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests, strcmp((char *)value, "1") == 0) { ret->exts |= SFTP_EXT_FSYNC; known = 1; + } else if (strcmp(name, "copy-data") == 0 && + strcmp((char *)value, "1") == 0) { + ret->exts |= SFTP_EXT_COPY_DATA; + known = 1; } if (known) { debug2("Server supports extension \"%s\" revision %s", @@ -879,6 +884,119 @@ do_realpath(struct sftp_conn *conn, const char *path) return(filename); } +int +do_copy(struct sftp_conn *conn, const char *oldpath, const char *newpath) +{ + Attrib junk, *a; + struct sshbuf *msg; + u_char *old_handle, *new_handle; + u_int mode, status, id; + size_t old_handle_len, new_handle_len; + int r; + + /* Silently return if the extension is not supported */ + if ((conn->exts & SFTP_EXT_COPY_DATA) == 0) { + error("Server does not support copy-data extension"); + return -1; + } + + /* Make sure the file exists, and we can copy its perms */ + if ((a = do_stat(conn, oldpath, 0)) == NULL) + return -1; + + /* Do not preserve set[ug]id here, as we do not preserve ownership */ + if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) { + mode = a->perm & 0777; + + if (!S_ISREG(a->perm)) { + error("Cannot copy non-regular file: %s", oldpath); + return -1; + } + } else { + mode = 0666; + } + + /* Set up the new perms for the new file */ + attrib_clear(a); + a->perm = mode; + a->flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; + + if ((msg = sshbuf_new()) == NULL) + fatal("%s: sshbuf_new failed", __func__); + + attrib_clear(&junk); /* Send empty attributes */ + + /* Open the old file for reading */ + id = conn->msg_id++; + if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 || + (r = sshbuf_put_u32(msg, id)) != 0 || + (r = sshbuf_put_cstring(msg, oldpath)) != 0 || + (r = sshbuf_put_u32(msg, SSH2_FXF_READ)) != 0 || + (r = encode_attrib(msg, &junk)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + send_msg(conn, msg); + debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, oldpath); + + sshbuf_reset(msg); + + old_handle = get_handle(conn, id, &old_handle_len, + "remote open(\"%s\")", oldpath); + if (old_handle == NULL) { + sshbuf_free(msg); + return -1; + } + + /* Open the new file for writing */ + id = conn->msg_id++; + if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 || + (r = sshbuf_put_u32(msg, id)) != 0 || + (r = sshbuf_put_cstring(msg, newpath)) != 0 || + (r = sshbuf_put_u32(msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT| + SSH2_FXF_TRUNC)) != 0 || + (r = encode_attrib(msg, a)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + send_msg(conn, msg); + debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, newpath); + + sshbuf_reset(msg); + + new_handle = get_handle(conn, id, &new_handle_len, + "remote open(\"%s\")", newpath); + if (new_handle == NULL) { + sshbuf_free(msg); + return -1; + } + + /* Copy the file data */ + id = conn->msg_id++; + if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || + (r = sshbuf_put_u32(msg, id)) != 0 || + (r = sshbuf_put_cstring(msg, "copy-data")) != 0 || + (r = sshbuf_put_string(msg, old_handle, old_handle_len)) != 0 || + (r = sshbuf_put_u64(msg, 0)) != 0 || + (r = sshbuf_put_u64(msg, 0)) != 0 || + (r = sshbuf_put_string(msg, new_handle, new_handle_len)) != 0 || + (r = sshbuf_put_u64(msg, 0)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + send_msg(conn, msg); + debug3("Sent message copy-data \"%s\" 0 0 -> \"%s\" 0", + oldpath, newpath); + + status = get_status(conn, id); + if (status != SSH2_FX_OK) + error("Couldn't copy file \"%s\" to \"%s\": %s", oldpath, + newpath, fx2txt(status)); + + /* Clean up everything */ + sshbuf_free(msg); + do_close(conn, old_handle, old_handle_len); + do_close(conn, new_handle, new_handle_len); + free(old_handle); + free(new_handle); + + return status == SSH2_FX_OK ? 0 : -1; +} + int do_rename(struct sftp_conn *conn, const char *oldpath, const char *newpath, int force_legacy) diff --git a/sftp-client.h b/sftp-client.h index 14a3b8182c49..13b638ef006f 100644 --- a/sftp-client.h +++ b/sftp-client.h @@ -100,6 +100,9 @@ int do_statvfs(struct sftp_conn *, const char *, struct sftp_statvfs *, int); /* Rename 'oldpath' to 'newpath' */ int do_rename(struct sftp_conn *, const char *, const char *, int force_legacy); +/* Copy 'oldpath' to 'newpath' */ +int do_copy(struct sftp_conn *, const char *, const char *); + /* Link 'oldpath' to 'newpath' */ int do_hardlink(struct sftp_conn *, const char *, const char *); diff --git a/sftp.1 b/sftp.1 index 0fd54cae090e..f2eae7f32790 100644 --- a/sftp.1 +++ b/sftp.1 @@ -137,7 +137,7 @@ will abort if any of the following commands fail: .Ic get , put , reget , reput, rename , ln , .Ic rm , mkdir , chdir , ls , -.Ic lchdir , chmod , chown , +.Ic lchdir , copy , chmod , chown , .Ic chgrp , lpwd , df , symlink , and .Ic lmkdir . @@ -340,6 +340,11 @@ may contain characters and may match multiple files. .Ar own must be a numeric UID. +.It Ic copy Ar oldpath Ar newpath +Copy remote file from +.Ar oldpath +to +.Ar newpath . .It Xo Ic df .Op Fl hi .Op Ar path diff --git a/sftp.c b/sftp.c index 7db86c2d3cf0..3288279172a9 100644 --- a/sftp.c +++ b/sftp.c @@ -142,6 +142,7 @@ enum sftp_command { I_CHGRP, I_CHMOD, I_CHOWN, + I_COPY, I_DF, I_GET, I_HELP, @@ -185,6 +186,7 @@ static const struct CMD cmds[] = { { "chgrp", I_CHGRP, REMOTE }, { "chmod", I_CHMOD, REMOTE }, { "chown", I_CHOWN, REMOTE }, + { "cp", I_COPY, REMOTE }, { "df", I_DF, REMOTE }, { "dir", I_LS, REMOTE }, { "exit", I_QUIT, NOARGS }, @@ -281,6 +283,7 @@ help(void) "chgrp grp path Change group of file 'path' to 'grp'\n" "chmod mode path Change permissions of file 'path' to 'mode'\n" "chown own path Change owner of file 'path' to 'own'\n" + "cp oldpath newpath Copy remote file\n" "df [-hi] [path] Display statistics for current directory or\n" " filesystem containing 'path'\n" "exit Quit sftp\n" @@ -1373,6 +1376,10 @@ parse_args(const char **cpp, int *ignore_errors, int *aflag, if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1) return -1; goto parse_two_paths; + case I_COPY: + if ((optidx = parse_no_flags(cmd, argv, argc)) == -1) + return -1; + goto parse_two_paths; case I_RENAME: if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1) return -1; @@ -1535,6 +1542,11 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, err = process_put(conn, path1, path2, *pwd, pflag, rflag, aflag, fflag); break; + case I_COPY: + path1 = make_absolute(path1, *pwd); + path2 = make_absolute(path2, *pwd); + err = do_copy(conn, path1, path2); + break; case I_RENAME: path1 = make_absolute(path1, *pwd); path2 = make_absolute(path2, *pwd); -- 2.17.1 _______________________________________________ openssh-unix-dev mailing list openssh-unix-dev@xxxxxxxxxxx https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev