[PATCH 2/2] sftp-client: add a "cp" helper to copy files

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


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



More information about the openssh-unix-dev mailing list