Passing SFTP options when using SCP
Damien Miller
djm at mindrot.org
Tue Dec 13 16:15:43 AEDT 2022
On Mon, 12 Dec 2022, Chris Rapier wrote:
> The value tests should probably be 'val <= 0' as opposed to 'val == 0'. Also
> you might want to have the buffer size check for a maximum value. With
> overhead, 256K triggers an "Outbound buffer too long" error. 255K seems to
> work but I haven't done the exact math on that one yet. Lastly, if nrequests
> is set to a ridiculous size (on my system more than 150000) it ends up sucking
> up a lot of memory and stalls. I think that's a buyer beware sort of thing but
> I just wanted to let you know.
>
> Also, not defining the sftp_copy_buflen and sftp_nrequests means that in
> do_sftp_connect the line
>
> return do_init(*reminp, *remoutp,
> sftp_copy_buflen, sftp_nrequests, limit_kbps);
>
> Ends up with 0's in there for the buflen and requests. What sort of behaviour
> does that create on the sftp server? It seems to just pull out the stops.
No, if you look at do_init() then it uses default values of 32K buffer
size and 64 concurrent requests.
This updates the diff to use scan_scaled() and strtonum() and refuses
negative values.
diff --git a/scp.1 b/scp.1
index cd23f97..a98df59 100644
--- a/scp.1
+++ b/scp.1
@@ -28,6 +28,7 @@
.Op Fl o Ar ssh_option
.Op Fl P Ar port
.Op Fl S Ar program
+.Op Fl X Ar sftp_option
.Ar source ... target
.Sh DESCRIPTION
.Nm
@@ -278,6 +279,19 @@ and
to print debugging messages about their progress.
This is helpful in
debugging connection, authentication, and configuration problems.
+.It Fl X Ar sftp_option
+Specify an option that controls aspects of SFTP protocol behaviour.
+The valid options are:
+.Bl -tag -width Ds
+.It Cm nrequests Ns = Ns Ar value
+Controls how many concurrent SFTP read or write requests may be in progress
+at any point in time during a download or upload.
+By default 64 requests may be active concurrently.
+.It Cm buffer Ns = Ns Ar value
+Controls the maximum buffer size for a single SFTP read/write operation used
+during download or upload.
+By default a 32KB buffer is used.
+.El
.El
.Sh EXIT STATUS
.Ex -std scp
diff --git a/scp.c b/scp.c
index c7194c2..25c8d38 100644
--- a/scp.c
+++ b/scp.c
@@ -96,6 +96,7 @@
#include <time.h>
#include <unistd.h>
#include <limits.h>
+#include <util.h>
#include <vis.h>
#include "xmalloc.h"
@@ -150,6 +151,10 @@ char *ssh_program = _PATH_SSH_PROGRAM;
pid_t do_cmd_pid = -1;
pid_t do_cmd_pid2 = -1;
+/* SFTP copy parameters */
+size_t sftp_copy_buflen;
+size_t sftp_nrequests;
+
/* Needed for sftp */
volatile sig_atomic_t interrupted = 0;
@@ -418,13 +423,14 @@ void throughlocal_sftp(struct sftp_conn *, struct sftp_conn *,
int
main(int argc, char **argv)
{
- int ch, fflag, tflag, status, n;
+ int ch, fflag, tflag, status, r, n;
char **newargv, *argv0;
const char *errstr;
extern char *optarg;
extern int optind;
enum scp_mode_e mode = MODE_SFTP;
char *sftp_direct = NULL;
+ long long llv;
/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
sanitise_stdfd();
@@ -452,7 +458,7 @@ main(int argc, char **argv)
fflag = Tflag = tflag = 0;
while ((ch = getopt(argc, argv,
- "12346ABCTdfOpqRrstvD:F:J:M:P:S:c:i:l:o:")) != -1) {
+ "12346ABCTdfOpqRrstvD:F:J:M:P:S:c:i:l:o:X:")) != -1) {
switch (ch) {
/* User-visible flags. */
case '1':
@@ -533,6 +539,31 @@ main(int argc, char **argv)
addargs(&remote_remote_args, "-q");
showprogress = 0;
break;
+ case 'X':
+ /* Please keep in sync with sftp.c -X */
+ if (strncmp(optarg, "buffer=", 7) == 0) {
+ r = scan_scaled(optarg + 7, &llv);
+ if (r == 0 && (llv <= 0 || llv > 256 * 1024)) {
+ r = -1;
+ errno = EINVAL;
+ }
+ if (r == -1) {
+ fatal("Invalid buffer size \"%s\": %s",
+ optarg + 7, strerror(errno));
+ }
+ sftp_copy_buflen = (size_t)llv;
+ } else if (strncmp(optarg, "nrequests=", 10) == 0) {
+ llv = strtonum(optarg + 10, 1, 256 * 1024,
+ &errstr);
+ if (errstr != NULL) {
+ fatal("Invalid number of requests "
+ "\"%s\": %s", optarg + 10, errstr);
+ }
+ sftp_nrequests = (size_t)llv;
+ } else {
+ fatal("Invalid -X option");
+ }
+ break;
/* Server options. */
case 'd':
@@ -941,7 +972,8 @@ do_sftp_connect(char *host, char *user, int port, char *sftp_direct,
reminp, remoutp, pidp) < 0)
return NULL;
}
- return do_init(*reminp, *remoutp, 32768, 64, limit_kbps);
+ return do_init(*reminp, *remoutp,
+ sftp_copy_buflen, sftp_nrequests, limit_kbps);
}
void
diff --git a/sftp-client.c b/sftp-client.c
index a3e7a5d..1907753 100644
--- a/sftp-client.c
+++ b/sftp-client.c
@@ -55,10 +55,10 @@
extern volatile sig_atomic_t interrupted;
extern int showprogress;
-/* Default size of buffer for up/download */
+/* Default size of buffer for up/download (fix sftp.1 scp.1 if changed) */
#define DEFAULT_COPY_BUFLEN 32768
-/* Default number of concurrent outstanding requests */
+/* Default number of concurrent xfer requests (fix sftp.1 scp.1 if changed) */
#define DEFAULT_NUM_REQUESTS 64
/* Minimum amount of data to read at a time */
diff --git a/sftp.1 b/sftp.1
index 3b3f2c5..3b430c5 100644
--- a/sftp.1
+++ b/sftp.1
@@ -44,6 +44,7 @@
.Op Fl R Ar num_requests
.Op Fl S Ar program
.Op Fl s Ar subsystem | sftp_server
+.Op Fl X Ar sftp_option
.Ar destination
.Sh DESCRIPTION
.Nm
@@ -320,6 +321,19 @@ does not have an sftp subsystem configured.
.It Fl v
Raise logging level.
This option is also passed to ssh.
+.It Fl X Ar sftp_option
+Specify an option that controls aspects of SFTP protocol behaviour.
+The valid options are:
+.Bl -tag -width Ds
+.It Cm nrequests Ns = Ns Ar value
+Controls how many concurrent SFTP read or write requests may be in progress
+at any point in time during a download or upload.
+By default 64 requests may be active concurrently.
+.It Cm buffer Ns = Ns Ar value
+Controls the maximum buffer size for a single SFTP read/write operation used
+during download or upload.
+By default a 32KB buffer is used.
+.El
.El
.Sh INTERACTIVE COMMANDS
Once in interactive mode,
diff --git a/sftp.c b/sftp.c
index 02547f6..6dc52a3 100644
--- a/sftp.c
+++ b/sftp.c
@@ -2383,7 +2383,7 @@ main(int argc, char **argv)
struct sftp_conn *conn;
size_t copy_buffer_len = 0;
size_t num_requests = 0;
- long long limit_kbps = 0;
+ long long llv, limit_kbps = 0;
/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
sanitise_stdfd();
@@ -2400,7 +2400,7 @@ main(int argc, char **argv)
infile = stdin;
while ((ch = getopt(argc, argv,
- "1246AafhNpqrvCc:D:i:l:o:s:S:b:B:F:J:P:R:")) != -1) {
+ "1246AafhNpqrvCc:D:i:l:o:s:S:b:B:F:J:P:R:X:")) != -1) {
switch (ch) {
/* Passed through to ssh(1) */
case 'A':
@@ -2497,6 +2497,31 @@ main(int argc, char **argv)
ssh_program = optarg;
replacearg(&args, 0, "%s", ssh_program);
break;
+ case 'X':
+ /* Please keep in sync with ssh.c -X */
+ if (strncmp(optarg, "buffer=", 7) == 0) {
+ r = scan_scaled(optarg + 7, &llv);
+ if (r == 0 && (llv <= 0 || llv > 256 * 1024)) {
+ r = -1;
+ errno = EINVAL;
+ }
+ if (r == -1) {
+ fatal("Invalid buffer size \"%s\": %s",
+ optarg + 7, strerror(errno));
+ }
+ copy_buffer_len = (size_t)llv;
+ } else if (strncmp(optarg, "nrequests=", 10) == 0) {
+ llv = strtonum(optarg + 10, 1, 256 * 1024,
+ &errstr);
+ if (errstr != NULL) {
+ fatal("Invalid number of requests "
+ "\"%s\": %s", optarg + 10, errstr);
+ }
+ num_requests = (size_t)llv;
+ } else {
+ fatal("Invalid -X option");
+ }
+ break;
case 'h':
default:
usage();
More information about the openssh-unix-dev
mailing list