- djm@cvs.openbsd.org 2013/07/25 00:56:52

[sftp-client.c sftp-client.h sftp.1 sftp.c]
     sftp support for resuming partial downloads; patch mostly by Loganaden
     Velvindron/AfriNIC with some tweaks by me; feedback and ok dtucker@
This commit is contained in:
Damien Miller 2013-07-25 11:56:52 +10:00
parent 98e27dcf58
commit 0d032419ee
5 changed files with 133 additions and 56 deletions

View File

@ -15,6 +15,10 @@
[ssh.c] [ssh.c]
daemonise backgrounded (ControlPersist'ed) multiplexing master to ensure daemonise backgrounded (ControlPersist'ed) multiplexing master to ensure
it is fully detached from its controlling terminal. based on debugging it is fully detached from its controlling terminal. based on debugging
- djm@cvs.openbsd.org 2013/07/25 00:56:52
[sftp-client.c sftp-client.h sftp.1 sftp.c]
sftp support for resuming partial downloads; patch mostly by Loganaden
Velvindron/AfriNIC with some tweaks by me; feedback and ok dtucker@
20130720 20130720
- (djm) OpenBSD CVS Sync - (djm) OpenBSD CVS Sync

View File

@ -1,4 +1,4 @@
/* $OpenBSD: sftp-client.c,v 1.100 2013/06/01 22:34:50 dtucker Exp $ */ /* $OpenBSD: sftp-client.c,v 1.101 2013/07/25 00:56:51 djm Exp $ */
/* /*
* Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
* *
@ -112,7 +112,7 @@ send_msg(struct sftp_conn *conn, Buffer *m)
iov[1].iov_len = buffer_len(m); iov[1].iov_len = buffer_len(m);
if (atomiciov6(writev, conn->fd_out, iov, 2, if (atomiciov6(writev, conn->fd_out, iov, 2,
conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_out) != conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_out) !=
buffer_len(m) + sizeof(mlen)) buffer_len(m) + sizeof(mlen))
fatal("Couldn't send packet: %s", strerror(errno)); fatal("Couldn't send packet: %s", strerror(errno));
@ -988,16 +988,17 @@ send_read_request(struct sftp_conn *conn, u_int id, u_int64_t offset,
int int
do_download(struct sftp_conn *conn, char *remote_path, char *local_path, do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
Attrib *a, int pflag) Attrib *a, int pflag, int resume)
{ {
Attrib junk; Attrib junk;
Buffer msg; Buffer msg;
char *handle; char *handle;
int local_fd, status = 0, write_error; int local_fd = -1, status = 0, write_error;
int read_error, write_errno; int read_error, write_errno, reordered = 0;
u_int64_t offset, size; u_int64_t offset = 0, size, highwater;
u_int handle_len, mode, type, id, buflen, num_req, max_req; u_int handle_len, mode, type, id, buflen, num_req, max_req;
off_t progress_counter; off_t progress_counter;
struct stat st;
struct request { struct request {
u_int id; u_int id;
u_int len; u_int len;
@ -1050,21 +1051,36 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
return(-1); return(-1);
} }
local_fd = open(local_path, O_WRONLY | O_CREAT | O_TRUNC, local_fd = open(local_path, O_WRONLY | O_CREAT | (resume ? : O_TRUNC),
mode | S_IWUSR); mode | S_IWUSR);
if (local_fd == -1) { if (local_fd == -1) {
error("Couldn't open local file \"%s\" for writing: %s", error("Couldn't open local file \"%s\" for writing: %s",
local_path, strerror(errno)); local_path, strerror(errno));
do_close(conn, handle, handle_len); goto fail;
buffer_free(&msg); }
free(handle); offset = highwater = 0;
return(-1); if (resume) {
if (fstat(local_fd, &st) == -1) {
error("Unable to stat local file \"%s\": %s",
local_path, strerror(errno));
goto fail;
}
if ((size_t)st.st_size > size) {
error("Unable to resume download of \"%s\": "
"local file is larger than remote", local_path);
fail:
do_close(conn, handle, handle_len);
buffer_free(&msg);
free(handle);
return -1;
}
offset = highwater = st.st_size;
} }
/* Read from remote and write to local */ /* Read from remote and write to local */
write_error = read_error = write_errno = num_req = offset = 0; write_error = read_error = write_errno = num_req = 0;
max_req = 1; max_req = 1;
progress_counter = 0; progress_counter = offset;
if (showprogress && size != 0) if (showprogress && size != 0)
start_progress_meter(remote_path, size, &progress_counter); start_progress_meter(remote_path, size, &progress_counter);
@ -1139,6 +1155,10 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
write_error = 1; write_error = 1;
max_req = 0; max_req = 0;
} }
else if (!reordered && req->offset <= highwater)
highwater = req->offset + len;
else if (!reordered && req->offset > highwater)
reordered = 1;
progress_counter += len; progress_counter += len;
free(data); free(data);
@ -1187,7 +1207,15 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
/* Sanity check */ /* Sanity check */
if (TAILQ_FIRST(&requests) != NULL) if (TAILQ_FIRST(&requests) != NULL)
fatal("Transfer complete, but requests still in queue"); fatal("Transfer complete, but requests still in queue");
/* Truncate at highest contiguous point to avoid holes on interrupt */
if (read_error || write_error || interrupted) {
if (reordered && resume) {
error("Unable to resume download of \"%s\": "
"server reordered requests", local_path);
}
debug("truncating at %llu", (unsigned long long)highwater);
ftruncate(local_fd, highwater);
}
if (read_error) { if (read_error) {
error("Couldn't read from remote file \"%s\" : %s", error("Couldn't read from remote file \"%s\" : %s",
remote_path, fx2txt(status)); remote_path, fx2txt(status));
@ -1199,7 +1227,8 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
do_close(conn, handle, handle_len); do_close(conn, handle, handle_len);
} else { } else {
status = do_close(conn, handle, handle_len); status = do_close(conn, handle, handle_len);
if (interrupted)
status = -1;
/* Override umask and utimes if asked */ /* Override umask and utimes if asked */
#ifdef HAVE_FCHMOD #ifdef HAVE_FCHMOD
if (pflag && fchmod(local_fd, mode) == -1) if (pflag && fchmod(local_fd, mode) == -1)
@ -1227,7 +1256,7 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
static int static int
download_dir_internal(struct sftp_conn *conn, char *src, char *dst, download_dir_internal(struct sftp_conn *conn, char *src, char *dst,
Attrib *dirattrib, int pflag, int printflag, int depth) Attrib *dirattrib, int pflag, int printflag, int depth, int resume)
{ {
int i, ret = 0; int i, ret = 0;
SFTP_DIRENT **dir_entries; SFTP_DIRENT **dir_entries;
@ -1280,11 +1309,11 @@ download_dir_internal(struct sftp_conn *conn, char *src, char *dst,
continue; continue;
if (download_dir_internal(conn, new_src, new_dst, if (download_dir_internal(conn, new_src, new_dst,
&(dir_entries[i]->a), pflag, printflag, &(dir_entries[i]->a), pflag, printflag,
depth + 1) == -1) depth + 1, resume) == -1)
ret = -1; ret = -1;
} else if (S_ISREG(dir_entries[i]->a.perm) ) { } else if (S_ISREG(dir_entries[i]->a.perm) ) {
if (do_download(conn, new_src, new_dst, if (do_download(conn, new_src, new_dst,
&(dir_entries[i]->a), pflag) == -1) { &(dir_entries[i]->a), pflag, resume) == -1) {
error("Download of file %s to %s failed", error("Download of file %s to %s failed",
new_src, new_dst); new_src, new_dst);
ret = -1; ret = -1;
@ -1317,7 +1346,7 @@ download_dir_internal(struct sftp_conn *conn, char *src, char *dst,
int int
download_dir(struct sftp_conn *conn, char *src, char *dst, download_dir(struct sftp_conn *conn, char *src, char *dst,
Attrib *dirattrib, int pflag, int printflag) Attrib *dirattrib, int pflag, int printflag, int resume)
{ {
char *src_canon; char *src_canon;
int ret; int ret;
@ -1328,7 +1357,7 @@ download_dir(struct sftp_conn *conn, char *src, char *dst,
} }
ret = download_dir_internal(conn, src_canon, dst, ret = download_dir_internal(conn, src_canon, dst,
dirattrib, pflag, printflag, 0); dirattrib, pflag, printflag, 0, resume);
free(src_canon); free(src_canon);
return ret; return ret;
} }
@ -1553,7 +1582,7 @@ upload_dir_internal(struct sftp_conn *conn, char *src, char *dst,
a.perm &= 01777; a.perm &= 01777;
if (!pflag) if (!pflag)
a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME; a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
status = do_mkdir(conn, dst, &a, 0); status = do_mkdir(conn, dst, &a, 0);
/* /*
* we lack a portable status for errno EEXIST, * we lack a portable status for errno EEXIST,
@ -1563,7 +1592,7 @@ upload_dir_internal(struct sftp_conn *conn, char *src, char *dst,
if (status != SSH2_FX_OK) { if (status != SSH2_FX_OK) {
if (status != SSH2_FX_FAILURE) if (status != SSH2_FX_FAILURE)
return -1; return -1;
if (do_stat(conn, dst, 0) == NULL) if (do_stat(conn, dst, 0) == NULL)
return -1; return -1;
} }
@ -1571,7 +1600,7 @@ upload_dir_internal(struct sftp_conn *conn, char *src, char *dst,
error("Failed to open dir \"%s\": %s", src, strerror(errno)); error("Failed to open dir \"%s\": %s", src, strerror(errno));
return -1; return -1;
} }
while (((dp = readdir(dirp)) != NULL) && !interrupted) { while (((dp = readdir(dirp)) != NULL) && !interrupted) {
if (dp->d_ino == 0) if (dp->d_ino == 0)
continue; continue;

View File

@ -1,4 +1,4 @@
/* $OpenBSD: sftp-client.h,v 1.20 2010/12/04 00:18:01 djm Exp $ */ /* $OpenBSD: sftp-client.h,v 1.21 2013/07/25 00:56:51 djm Exp $ */
/* /*
* Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
@ -106,13 +106,13 @@ int do_symlink(struct sftp_conn *, char *, char *);
* Download 'remote_path' to 'local_path'. Preserve permissions and times * Download 'remote_path' to 'local_path'. Preserve permissions and times
* if 'pflag' is set * if 'pflag' is set
*/ */
int do_download(struct sftp_conn *, char *, char *, Attrib *, int); int do_download(struct sftp_conn *, char *, char *, Attrib *, int, int);
/* /*
* Recursively download 'remote_directory' to 'local_directory'. Preserve * Recursively download 'remote_directory' to 'local_directory'. Preserve
* times if 'pflag' is set * times if 'pflag' is set
*/ */
int download_dir(struct sftp_conn *, char *, char *, Attrib *, int, int); int download_dir(struct sftp_conn *, char *, char *, Attrib *, int, int, int);
/* /*
* Upload 'local_path' to 'remote_path'. Preserve permissions and times * Upload 'local_path' to 'remote_path'. Preserve permissions and times

28
sftp.1
View File

@ -1,4 +1,4 @@
.\" $OpenBSD: sftp.1,v 1.91 2011/09/05 05:56:13 djm Exp $ .\" $OpenBSD: sftp.1,v 1.92 2013/07/25 00:56:51 djm Exp $
.\" .\"
.\" Copyright (c) 2001 Damien Miller. All rights reserved. .\" 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 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\" .\"
.Dd $Mdocdate: September 5 2011 $ .Dd $Mdocdate: July 25 2013 $
.Dt SFTP 1 .Dt SFTP 1
.Os .Os
.Sh NAME .Sh NAME
@ -129,7 +129,7 @@ may be used to indicate standard input.
.Nm .Nm
will abort if any of the following will abort if any of the following
commands fail: commands fail:
.Ic get , put , rename , ln , .Ic get , put , reget , rename , ln ,
.Ic rm , mkdir , chdir , ls , .Ic rm , mkdir , chdir , ls ,
.Ic lchdir , chmod , chown , .Ic lchdir , chmod , chown ,
.Ic chgrp , lpwd , df , symlink , .Ic chgrp , lpwd , df , symlink ,
@ -343,7 +343,7 @@ extension.
Quit Quit
.Nm sftp . .Nm sftp .
.It Xo Ic get .It Xo Ic get
.Op Fl Ppr .Op Fl aPpr
.Ar remote-path .Ar remote-path
.Op Ar local-path .Op Ar local-path
.Xc .Xc
@ -363,6 +363,14 @@ is specified, then
.Ar local-path .Ar local-path
must specify a directory. must specify a directory.
.Pp .Pp
If the
.Fl a
flag is specified, then attempt to resume partial transfers of existing files.
Note that resumption assumes that any partial copy of the local file matches
the remote copy.
If the remote file differs from the partial local copy then the resultant file
is likely to be corrupt.
.Pp
If either the If either the
.Fl P .Fl P
or or
@ -503,6 +511,18 @@ Display remote working directory.
.It Ic quit .It Ic quit
Quit Quit
.Nm sftp . .Nm sftp .
.It Xo Ic reget
.Op Fl Ppr
.Ar remote-path
.Op Ar local-path
.Xc
Resume download of
.Ar remote-path .
Equivalent to
.Ic get
with the
.Fl a
flag set.
.It Ic rename Ar oldpath Ar newpath .It Ic rename Ar oldpath Ar newpath
Rename remote file from Rename remote file from
.Ar oldpath .Ar oldpath

76
sftp.c
View File

@ -1,4 +1,4 @@
/* $OpenBSD: sftp.c,v 1.147 2013/07/12 00:20:00 djm Exp $ */ /* $OpenBSD: sftp.c,v 1.148 2013/07/25 00:56:52 djm Exp $ */
/* /*
* Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
* *
@ -88,6 +88,9 @@ int showprogress = 1;
/* When this option is set, we always recursively download/upload directories */ /* When this option is set, we always recursively download/upload directories */
int global_rflag = 0; int global_rflag = 0;
/* When this option is set, we resume download if possible */
int global_aflag = 0;
/* When this option is set, the file transfers will always preserve times */ /* When this option is set, the file transfers will always preserve times */
int global_pflag = 0; int global_pflag = 0;
@ -151,6 +154,7 @@ extern char *__progname;
#define I_SYMLINK 21 #define I_SYMLINK 21
#define I_VERSION 22 #define I_VERSION 22
#define I_PROGRESS 23 #define I_PROGRESS 23
#define I_REGET 26
struct CMD { struct CMD {
const char *c; const char *c;
@ -190,6 +194,7 @@ static const struct CMD cmds[] = {
{ "put", I_PUT, LOCAL }, { "put", I_PUT, LOCAL },
{ "pwd", I_PWD, REMOTE }, { "pwd", I_PWD, REMOTE },
{ "quit", I_QUIT, NOARGS }, { "quit", I_QUIT, NOARGS },
{ "reget", I_REGET, REMOTE },
{ "rename", I_RENAME, REMOTE }, { "rename", I_RENAME, REMOTE },
{ "rm", I_RM, REMOTE }, { "rm", I_RM, REMOTE },
{ "rmdir", I_RMDIR, REMOTE }, { "rmdir", I_RMDIR, REMOTE },
@ -239,6 +244,7 @@ help(void)
" filesystem containing 'path'\n" " filesystem containing 'path'\n"
"exit Quit sftp\n" "exit Quit sftp\n"
"get [-Ppr] remote [local] Download file\n" "get [-Ppr] remote [local] Download file\n"
"reget remote [local] Resume download file\n"
"help Display this help text\n" "help Display this help text\n"
"lcd path Change local directory to 'path'\n" "lcd path Change local directory to 'path'\n"
"lls [ls-options [path]] Display local directory listing\n" "lls [ls-options [path]] Display local directory listing\n"
@ -350,8 +356,8 @@ make_absolute(char *p, char *pwd)
} }
static int static int
parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag, parse_getput_flags(const char *cmd, char **argv, int argc,
int *rflag) int *aflag, int *pflag, int *rflag)
{ {
extern int opterr, optind, optopt, optreset; extern int opterr, optind, optopt, optreset;
int ch; int ch;
@ -359,9 +365,12 @@ parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag,
optind = optreset = 1; optind = optreset = 1;
opterr = 0; opterr = 0;
*rflag = *pflag = 0; *aflag = *rflag = *pflag = 0;
while ((ch = getopt(argc, argv, "PpRr")) != -1) { while ((ch = getopt(argc, argv, "aPpRr")) != -1) {
switch (ch) { switch (ch) {
case 'a':
*aflag = 1;
break;
case 'p': case 'p':
case 'P': case 'P':
*pflag = 1; *pflag = 1;
@ -519,7 +528,7 @@ pathname_is_dir(char *pathname)
static int static int
process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
int pflag, int rflag) int pflag, int rflag, int resume)
{ {
char *abs_src = NULL; char *abs_src = NULL;
char *abs_dst = NULL; char *abs_dst = NULL;
@ -571,15 +580,18 @@ process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
} }
free(tmp); free(tmp);
if (!quiet) resume |= global_aflag;
if (!quiet && resume)
printf("Resuming %s to %s\n", g.gl_pathv[i], abs_dst);
else if (!quiet && !resume)
printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst); printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) { if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL, if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
pflag || global_pflag, 1) == -1) pflag || global_pflag, 1, resume) == -1)
err = -1; err = -1;
} else { } else {
if (do_download(conn, g.gl_pathv[i], abs_dst, NULL, if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
pflag || global_pflag) == -1) pflag || global_pflag, resume) == -1)
err = -1; err = -1;
} }
free(abs_dst); free(abs_dst);
@ -1118,8 +1130,9 @@ makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
} }
static int static int
parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag, parse_args(const char **cpp, int *aflag, int *hflag, int *iflag, int *lflag,
int *hflag, int *sflag, unsigned long *n_arg, char **path1, char **path2) int *pflag, int *rflag, int *sflag, unsigned long *n_arg,
char **path1, char **path2)
{ {
const char *cmd, *cp = *cpp; const char *cmd, *cp = *cpp;
char *cp2, **argv; char *cp2, **argv;
@ -1163,14 +1176,15 @@ parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag,
} }
/* Get arguments and parse flags */ /* Get arguments and parse flags */
*lflag = *pflag = *rflag = *hflag = *n_arg = 0; *aflag = *lflag = *pflag = *rflag = *hflag = *n_arg = 0;
*path1 = *path2 = NULL; *path1 = *path2 = NULL;
optidx = 1; optidx = 1;
switch (cmdnum) { switch (cmdnum) {
case I_GET: case I_GET:
case I_REGET:
case I_PUT: case I_PUT:
if ((optidx = parse_getput_flags(cmd, argv, argc, if ((optidx = parse_getput_flags(cmd, argv, argc,
pflag, rflag)) == -1) aflag, pflag, rflag)) == -1)
return -1; return -1;
/* Get first pathname (mandatory) */ /* Get first pathname (mandatory) */
if (argc - optidx < 1) { if (argc - optidx < 1) {
@ -1185,6 +1199,11 @@ parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag,
/* Destination is not globbed */ /* Destination is not globbed */
undo_glob_escape(*path2); undo_glob_escape(*path2);
} }
if (*aflag && cmdnum == I_PUT) {
/* XXX implement resume for uploads */
error("Resume is not supported for uploads");
return -1;
}
break; break;
case I_LINK: case I_LINK:
if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1) if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
@ -1293,7 +1312,8 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
int err_abort) int err_abort)
{ {
char *path1, *path2, *tmp; char *path1, *path2, *tmp;
int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, sflag = 0; int aflag = 0, hflag = 0, iflag = 0, lflag = 0, pflag = 0;
int rflag = 0, sflag = 0;
int cmdnum, i; int cmdnum, i;
unsigned long n_arg = 0; unsigned long n_arg = 0;
Attrib a, *aa; Attrib a, *aa;
@ -1302,9 +1322,8 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
glob_t g; glob_t g;
path1 = path2 = NULL; path1 = path2 = NULL;
cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag, cmdnum = parse_args(&cmd, &aflag, &hflag, &iflag, &lflag, &pflag,
&sflag, &n_arg, &path1, &path2); &rflag, &sflag, &n_arg, &path1, &path2);
if (iflag != 0) if (iflag != 0)
err_abort = 0; err_abort = 0;
@ -1319,8 +1338,12 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
/* Unrecognized command */ /* Unrecognized command */
err = -1; err = -1;
break; break;
case I_REGET:
aflag = 1;
/* FALLTHROUGH */
case I_GET: case I_GET:
err = process_get(conn, path1, path2, *pwd, pflag, rflag); err = process_get(conn, path1, path2, *pwd, pflag,
rflag, aflag);
break; break;
case I_PUT: case I_PUT:
err = process_put(conn, path1, path2, *pwd, pflag, rflag); err = process_put(conn, path1, path2, *pwd, pflag, rflag);
@ -1949,12 +1972,10 @@ interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
} }
} else { } else {
/* XXX this is wrong wrt quoting */ /* XXX this is wrong wrt quoting */
if (file2 == NULL) snprintf(cmd, sizeof cmd, "get%s %s%s%s",
snprintf(cmd, sizeof cmd, "get %s", dir); global_aflag ? " -a" : "", dir,
else file2 == NULL ? "" : " ",
snprintf(cmd, sizeof cmd, "get %s %s", dir, file2 == NULL ? "" : file2);
file2);
err = parse_dispatch_command(conn, cmd, err = parse_dispatch_command(conn, cmd,
&remote_path, 1); &remote_path, 1);
free(dir); free(dir);
@ -2143,7 +2164,7 @@ main(int argc, char **argv)
infile = stdin; infile = stdin;
while ((ch = getopt(argc, argv, while ((ch = getopt(argc, argv,
"1246hpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) { "1246ahpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
switch (ch) { switch (ch) {
/* Passed through to ssh(1) */ /* Passed through to ssh(1) */
case '4': case '4':
@ -2183,6 +2204,9 @@ main(int argc, char **argv)
case '2': case '2':
sshver = 2; sshver = 2;
break; break;
case 'a':
global_aflag = 1;
break;
case 'B': case 'B':
copy_buffer_len = strtol(optarg, &cp, 10); copy_buffer_len = strtol(optarg, &cp, 10);
if (copy_buffer_len == 0 || *cp != '\0') if (copy_buffer_len == 0 || *cp != '\0')