[PATCH 2/2] sftp-client: add a "cp" helper to copy files
Mike Frysinger
vapier at gentoo.org
Fri Dec 24 15:17:11 AEDT 2021
Use the new copy-data extension for efficient remote file copies.
At the very least, this provides a quick method for testing.
---
Rebased onto the latest tree.
sftp-client.c | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++
sftp-client.h | 3 ++
sftp.1 | 14 +++++-
sftp.c | 14 ++++++
4 files changed, 150 insertions(+), 1 deletion(-)
diff --git a/sftp-client.c b/sftp-client.c
index 6c33a836d6b9..ebb2125544a5 100644
--- a/sftp-client.c
+++ b/sftp-client.c
@@ -103,6 +103,7 @@ struct sftp_conn {
#define SFTP_EXT_LSETSTAT 0x00000020
#define SFTP_EXT_LIMITS 0x00000040
#define SFTP_EXT_PATH_EXPAND 0x00000080
+#define SFTP_EXT_COPY_DATA 0x00000100
u_int exts;
u_int64_t limit_kbps;
struct bwlimit bwlimit_in, bwlimit_out;
@@ -534,6 +535,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_PATH_EXPAND;
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",
@@ -1060,6 +1065,121 @@ do_expand_path(struct sftp_conn *conn, const char *path)
return do_realpath_expand(conn, path, 1);
}
+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;
+
+ /* 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 {
+ /* NB: The user's umask will apply to this */
+ 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);
+ free(old_handle);
+ 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 7d0bd12ae5a3..f9911d35c949 100644
--- a/sftp-client.h
+++ b/sftp-client.h
@@ -125,6 +125,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);
+/* 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 7eebeeacbf3f..72b61b14a4bc 100644
--- a/sftp.1
+++ b/sftp.1
@@ -144,7 +144,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 , cp , chmod , chown ,
.Ic chgrp , lpwd , df , symlink ,
and
.Ic lmkdir .
@@ -400,6 +400,18 @@ If the
flag is specified, then symlinks will not be followed.
Note that this is only supported by servers that implement
the "lsetstat at openssh.com" extension.
+.It Ic copy Ar oldpath Ar newpath
+Copy remote file from
+.Ar oldpath
+to
+.Ar newpath .
+.Pp
+Note that this is only supported by servers that implement the "copy-data"
+extension.
+.It Ic cp Ar oldpath Ar newpath
+Alias to
+.Ic copy
+command.
.It Xo Ic df
.Op Fl hi
.Op Ar path
diff --git a/sftp.c b/sftp.c
index 8cb5917a91f1..416d57fcb302 100644
--- a/sftp.c
+++ b/sftp.c
@@ -137,6 +137,7 @@ enum sftp_command {
I_CHGRP,
I_CHMOD,
I_CHOWN,
+ I_COPY,
I_DF,
I_GET,
I_HELP,
@@ -180,6 +181,8 @@ static const struct CMD cmds[] = {
{ "chgrp", I_CHGRP, REMOTE },
{ "chmod", I_CHMOD, REMOTE },
{ "chown", I_CHOWN, REMOTE },
+ { "copy", I_COPY, REMOTE },
+ { "cp", I_COPY, REMOTE },
{ "df", I_DF, REMOTE },
{ "dir", I_LS, REMOTE },
{ "exit", I_QUIT, NOARGS },
@@ -286,6 +289,8 @@ help(void)
"chgrp [-h] grp path Change group of file 'path' to 'grp'\n"
"chmod [-h] mode path Change permissions of file 'path' to 'mode'\n"
"chown [-h] own path Change owner of file 'path' to 'own'\n"
+ "copy oldpath newpath Copy remote file\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"
@@ -1369,6 +1374,10 @@ parse_args(const char **cpp, int *ignore_errors, int *disable_echo, 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;
@@ -1536,6 +1545,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.33.0
More information about the openssh-unix-dev
mailing list