upstream: add a sftp client "cp" command that supports server-side
copying of files. Useful for this task and for testing the copy-data extension. Patch from Mike Frysinger; ok dtucker@ OpenBSD-Commit-ID: 1bb1b950af0d49f0d5425b1f267e197aa1b57444
This commit is contained in:
parent
7988bfc4b7
commit
3fa539c3ff
122
sftp-client.c
122
sftp-client.c
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: sftp-client.c,v 1.161 2022/01/17 21:41:04 djm Exp $ */
|
||||
/* $OpenBSD: sftp-client.c,v 1.162 2022/03/31 03:07:03 djm Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
|
||||
*
|
||||
|
@ -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",
|
||||
|
@ -1078,6 +1083,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)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: sftp-client.h,v 1.35 2022/01/01 01:55:30 jsg Exp $ */
|
||||
/* $OpenBSD: sftp-client.h,v 1.36 2022/03/31 03:07:03 djm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
|
||||
|
@ -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 *);
|
||||
|
||||
|
|
18
sftp.1
18
sftp.1
|
@ -1,4 +1,4 @@
|
|||
.\" $OpenBSD: sftp.1,v 1.138 2021/07/02 05:11:21 dtucker Exp $
|
||||
.\" $OpenBSD: sftp.1,v 1.139 2022/03/31 03:07:03 djm Exp $
|
||||
.\"
|
||||
.\" Copyright (c) 2001 Damien Miller. All rights reserved.
|
||||
.\"
|
||||
|
@ -22,7 +22,7 @@
|
|||
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
.\"
|
||||
.Dd $Mdocdate: July 2 2021 $
|
||||
.Dd $Mdocdate: March 31 2022 $
|
||||
.Dt SFTP 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
|
@ -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@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
|
||||
|
|
16
sftp.c
16
sftp.c
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: sftp.c,v 1.213 2022/03/18 02:50:21 djm Exp $ */
|
||||
/* $OpenBSD: sftp.c,v 1.214 2022/03/31 03:07:03 djm Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
|
||||
*
|
||||
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue