X-Git-Url: http://andersk.mit.edu/gitweb/openssh.git/blobdiff_plain/e1c5bfafcaa11bd09a0325e046b09425e74901d5..HEAD:/scp.c diff --git a/scp.c b/scp.c index e603646b..09efb82a 100644 --- a/scp.c +++ b/scp.c @@ -1,3 +1,4 @@ +/* $OpenBSD: scp.c,v 1.165 2009/12/20 07:28:36 guenther Exp $ */ /* * scp - secure remote copy. This is basically patched BSD rcp which * uses ssh to do the data transfer (instead of using rcmd). @@ -52,11 +53,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by the University of - * California, Berkeley and its contributors. - * 4. Neither the name of the University nor the names of its contributors + * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * @@ -75,51 +72,61 @@ */ #include "includes.h" -RCSID("$OpenBSD: scp.c,v 1.84 2001/09/19 19:24:19 stevesk Exp $"); + +#include +#include +#ifdef HAVE_SYS_STAT_H +# include +#endif +#ifdef HAVE_POLL_H +#include +#else +# ifdef HAVE_SYS_POLL_H +# include +# endif +#endif +#ifdef HAVE_SYS_TIME_H +# include +#endif +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(HAVE_STRNVIS) && defined(HAVE_VIS_H) +#include +#endif #include "xmalloc.h" #include "atomicio.h" #include "pathnames.h" #include "log.h" #include "misc.h" +#include "progressmeter.h" -#ifdef HAVE___PROGNAME extern char *__progname; -#else -char *__progname; -#endif - -/* For progressmeter() -- number of seconds before xfer considered "stalled" */ -#define STALLTIME 5 -/* alarm() interval for updating progress meter */ -#define PROGRESSTIME 1 -/* Progress meter bar */ -#define BAR \ - "************************************************************"\ - "************************************************************"\ - "************************************************************"\ - "************************************************************" -#define MAX_BARLENGTH (sizeof(BAR) - 1) +#define COPY_BUFLEN 16384 -/* Visual statistics about files as they are transferred. */ -void progressmeter(int); +int do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout); -/* Returns width of the terminal (for progress meter calculations). */ -int getttywidth(void); -int do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout, int argc); +void bwlimit(int); /* Struct for addargs */ arglist args; -/* Time a transfer started. */ -static struct timeval start; - -/* Number of bytes of current file transferred so far. */ -volatile off_t statbytes; - -/* Total size of current file. */ -off_t totalbytes = 0; +/* Bandwidth limit */ +off_t limit_rate = 0; /* Name of current file being transferred. */ char *curfile; @@ -133,6 +140,64 @@ int showprogress = 1; /* This is the program to execute for the secured connection. ("ssh" or -S) */ char *ssh_program = _PATH_SSH_PROGRAM; +/* This is used to store the pid of ssh_program */ +pid_t do_cmd_pid = -1; + +static void +killchild(int signo) +{ + if (do_cmd_pid > 1) { + kill(do_cmd_pid, signo ? signo : SIGTERM); + waitpid(do_cmd_pid, NULL, 0); + } + + if (signo) + _exit(1); + exit(1); +} + +static int +do_local_cmd(arglist *a) +{ + u_int i; + int status; + pid_t pid; + + if (a->num == 0) + fatal("do_local_cmd: no arguments"); + + if (verbose_mode) { + fprintf(stderr, "Executing:"); + for (i = 0; i < a->num; i++) + fprintf(stderr, " %s", a->list[i]); + fprintf(stderr, "\n"); + } + if ((pid = fork()) == -1) + fatal("do_local_cmd: fork: %s", strerror(errno)); + + if (pid == 0) { + execvp(a->list[0], a->list); + perror(a->list[0]); + exit(1); + } + + do_cmd_pid = pid; + signal(SIGTERM, killchild); + signal(SIGINT, killchild); + signal(SIGHUP, killchild); + + while (waitpid(pid, &status, 0) == -1) + if (errno != EINTR) + fatal("do_local_cmd: waitpid: %s", strerror(errno)); + + do_cmd_pid = -1; + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + return (-1); + + return (0); +} + /* * This function executes the given command as the specified user on the * given host. This returns < 0 if execution fails, and >= 0 otherwise. This @@ -140,7 +205,7 @@ char *ssh_program = _PATH_SSH_PROGRAM; */ int -do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout, int argc) +do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout) { int pin[2], pout[2], reserved[2]; @@ -154,7 +219,8 @@ do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout, int argc) * Reserve two descriptors so that the real pipes won't get * descriptors 0 and 1 because that will screw up dup2 below. */ - pipe(reserved); + if (pipe(reserved) < 0) + fatal("pipe: %s", strerror(errno)); /* Create a socket pair for communicating with ssh. */ if (pipe(pin) < 0) @@ -166,8 +232,9 @@ do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout, int argc) close(reserved[0]); close(reserved[1]); - /* For a child to execute the command on the remote host using ssh. */ - if (fork() == 0) { + /* Fork a child to execute the command on the remote host using ssh. */ + do_cmd_pid = fork(); + if (do_cmd_pid == 0) { /* Child. */ close(pin[1]); close(pout[0]); @@ -176,32 +243,39 @@ do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout, int argc) close(pin[0]); close(pout[1]); - args.list[0] = ssh_program; - if (remuser != NULL) - addargs(&args, "-l%s", remuser); + replacearg(&args, 0, "%s", ssh_program); + if (remuser != NULL) { + addargs(&args, "-l"); + addargs(&args, "%s", remuser); + } + addargs(&args, "--"); addargs(&args, "%s", host); addargs(&args, "%s", cmd); execvp(ssh_program, args.list); perror(ssh_program); exit(1); + } else if (do_cmd_pid == -1) { + fatal("fork: %s", strerror(errno)); } /* Parent. Close the other side, and return the local side. */ close(pin[0]); *fdout = pin[1]; close(pout[1]); *fdin = pout[0]; + signal(SIGTERM, killchild); + signal(SIGINT, killchild); + signal(SIGHUP, killchild); return 0; } typedef struct { - int cnt; + size_t cnt; char *buf; } BUF; BUF *allocbuf(BUF *, int, int); void lostconn(int); -void nospace(void); int okname(char *); void run_err(const char *,...); void verifydir(char *); @@ -220,31 +294,43 @@ void sink(int, char *[]); void source(int, char *[]); void tolocal(int, char *[]); void toremote(char *, int, char *[]); +size_t scpio(ssize_t (*)(int, void *, size_t), int, void *, size_t, off_t *); void usage(void); int -main(argc, argv) - int argc; - char *argv[]; +main(int argc, char **argv) { - int ch, fflag, tflag; - char *targ; + int ch, fflag, tflag, status, n; + double speed; + char *targ, *endp, **newargv; extern char *optarg; extern int optind; - __progname = get_progname(argv[0]); + /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ + sanitise_stdfd(); + + /* Copy argv, because we modify it */ + newargv = xcalloc(MAX(argc + 1, 1), sizeof(*newargv)); + for (n = 0; n < argc; n++) + newargv[n] = xstrdup(argv[n]); + argv = newargv; + + __progname = ssh_get_progname(argv[0]); + memset(&args, '\0', sizeof(args)); args.list = NULL; - addargs(&args, "ssh"); /* overwritten with ssh_program */ + addargs(&args, "%s", ssh_program); addargs(&args, "-x"); addargs(&args, "-oForwardAgent no"); - addargs(&args, "-oFallBackToRsh no"); + addargs(&args, "-oPermitLocalCommand no"); addargs(&args, "-oClearAllForwardings yes"); fflag = tflag = 0; - while ((ch = getopt(argc, argv, "dfprtvBCc:i:P:q46S:o:F:")) != -1) + while ((ch = getopt(argc, argv, "dfl:prtvBCc:i:P:q1246S:o:F:")) != -1) switch (ch) { /* User-visible flags. */ + case '1': + case '2': case '4': case '6': case 'C': @@ -254,14 +340,22 @@ main(argc, argv) case 'c': case 'i': case 'F': - addargs(&args, "-%c%s", ch, optarg); + addargs(&args, "-%c", ch); + addargs(&args, "%s", optarg); break; case 'P': - addargs(&args, "-p%s", optarg); + addargs(&args, "-p"); + addargs(&args, "%s", optarg); break; case 'B': addargs(&args, "-oBatchmode yes"); break; + case 'l': + speed = strtod(optarg, &endp); + if (speed <= 0 || *endp != '\0') + usage(); + limit_rate = speed * 1024; + break; case 'p': pflag = 1; break; @@ -276,6 +370,7 @@ main(argc, argv) verbose_mode = 1; break; case 'q': + addargs(&args, "-q"); showprogress = 0; break; @@ -301,9 +396,9 @@ main(argc, argv) argv += optind; if ((pwd = getpwuid(userid = getuid())) == NULL) - fatal("unknown user %d", (int) userid); + fatal("unknown user %u", (u_int) userid); - if (!isatty(STDERR_FILENO)) + if (!isatty(STDOUT_FILENO)) showprogress = 0; remin = STDIN_FILENO; @@ -326,6 +421,7 @@ main(argc, argv) targetshouldbedirectory = 1; remin = remout = -1; + do_cmd_pid = -1; /* Command to be executed on remote system using "ssh". */ (void) snprintf(cmd, sizeof cmd, "scp%s%s%s%s", verbose_mode ? " -v" : "", @@ -337,53 +433,113 @@ main(argc, argv) if ((targ = colon(argv[argc - 1]))) /* Dest is remote host. */ toremote(targ, argc, argv); else { - tolocal(argc, argv); /* Dest is local host. */ if (targetshouldbedirectory) verifydir(argv[argc - 1]); + tolocal(argc, argv); /* Dest is local host. */ + } + /* + * Finally check the exit status of the ssh process, if one was forked + * and no error has occurred yet + */ + if (do_cmd_pid != -1 && errs == 0) { + if (remin != -1) + (void) close(remin); + if (remout != -1) + (void) close(remout); + if (waitpid(do_cmd_pid, &status, 0) == -1) + errs = 1; + else { + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + errs = 1; + } } exit(errs != 0); } +/* + * atomicio-like wrapper that also applies bandwidth limits and updates + * the progressmeter counter. + */ +size_t +scpio(ssize_t (*f)(int, void *, size_t), int fd, void *_p, size_t l, off_t *c) +{ + u_char *p = (u_char *)_p; + size_t offset; + ssize_t r; + struct pollfd pfd; + + pfd.fd = fd; + pfd.events = f == read ? POLLIN : POLLOUT; + for (offset = 0; offset < l;) { + r = f(fd, p + offset, l - offset); + if (r == 0) { + errno = EPIPE; + return offset; + } + if (r < 0) { + if (errno == EINTR) + continue; + if (errno == EAGAIN || errno == EWOULDBLOCK) { + (void)poll(&pfd, 1, -1); /* Ignore errors */ + continue; + } + return offset; + } + offset += (size_t)r; + *c += (off_t)r; + if (limit_rate) + bwlimit(r); + } + return offset; +} + void -toremote(targ, argc, argv) - char *targ, *argv[]; - int argc; +toremote(char *targ, int argc, char **argv) { - int i, len; - char *bp, *host, *src, *suser, *thost, *tuser; + char *bp, *host, *src, *suser, *thost, *tuser, *arg; + arglist alist; + int i; + + memset(&alist, '\0', sizeof(alist)); + alist.list = NULL; *targ++ = 0; if (*targ == 0) targ = "."; - if ((thost = strchr(argv[argc - 1], '@'))) { + arg = xstrdup(argv[argc - 1]); + if ((thost = strrchr(arg, '@'))) { /* user@host */ *thost++ = 0; - tuser = argv[argc - 1]; + tuser = arg; if (*tuser == '\0') tuser = NULL; - else if (!okname(tuser)) - exit(1); } else { - thost = argv[argc - 1]; + thost = arg; tuser = NULL; } + if (tuser != NULL && !okname(tuser)) { + xfree(arg); + return; + } + for (i = 0; i < argc - 1; i++) { src = colon(argv[i]); if (src) { /* remote to remote */ - static char *ssh_options = - "-x -o'FallBackToRsh no' " - "-o'ClearAllForwardings yes'"; + freeargs(&alist); + addargs(&alist, "%s", ssh_program); + if (verbose_mode) + addargs(&alist, "-v"); + addargs(&alist, "-x"); + addargs(&alist, "-oClearAllForwardings yes"); + addargs(&alist, "-n"); + *src++ = 0; if (*src == 0) src = "."; - host = strchr(argv[i], '@'); - len = strlen(ssh_program) + strlen(argv[i]) + - strlen(src) + (tuser ? strlen(tuser) : 0) + - strlen(thost) + strlen(targ) + - strlen(ssh_options) + CMDNEEDS + 20; - bp = xmalloc(len); + host = strrchr(argv[i], '@'); + if (host) { *host++ = 0; host = cleanhostname(host); @@ -392,35 +548,26 @@ toremote(targ, argc, argv) suser = pwd->pw_name; else if (!okname(suser)) continue; - snprintf(bp, len, - "%s%s %s -n " - "-l %s %s %s %s '%s%s%s:%s'", - ssh_program, verbose_mode ? " -v" : "", - ssh_options, suser, host, cmd, src, - tuser ? tuser : "", tuser ? "@" : "", - thost, targ); + addargs(&alist, "-l"); + addargs(&alist, "%s", suser); } else { host = cleanhostname(argv[i]); - snprintf(bp, len, - "exec %s%s %s -n %s " - "%s %s '%s%s%s:%s'", - ssh_program, verbose_mode ? " -v" : "", - ssh_options, host, cmd, src, - tuser ? tuser : "", tuser ? "@" : "", - thost, targ); } - if (verbose_mode) - fprintf(stderr, "Executing: %s\n", bp); - (void) system(bp); - (void) xfree(bp); + addargs(&alist, "--"); + addargs(&alist, "%s", host); + addargs(&alist, "%s", cmd); + addargs(&alist, "%s", src); + addargs(&alist, "%s%s%s:%s", + tuser ? tuser : "", tuser ? "@" : "", + thost, targ); + if (do_local_cmd(&alist) != 0) + errs = 1; } else { /* local to remote */ if (remin == -1) { - len = strlen(targ) + CMDNEEDS + 20; - bp = xmalloc(len); - (void) snprintf(bp, len, "%s -t %s", cmd, targ); + xasprintf(&bp, "%s -t -- %s", cmd, targ); host = cleanhostname(thost); if (do_cmd(host, tuser, bp, &remin, - &remout, argc) < 0) + &remout) < 0) exit(1); if (response() < 0) exit(1); @@ -429,35 +576,38 @@ toremote(targ, argc, argv) source(1, argv + i); } } + xfree(arg); } void -tolocal(argc, argv) - int argc; - char *argv[]; +tolocal(int argc, char **argv) { - int i, len; char *bp, *host, *src, *suser; + arglist alist; + int i; + + memset(&alist, '\0', sizeof(alist)); + alist.list = NULL; for (i = 0; i < argc - 1; i++) { if (!(src = colon(argv[i]))) { /* Local to local. */ - len = strlen(_PATH_CP) + strlen(argv[i]) + - strlen(argv[argc - 1]) + 20; - bp = xmalloc(len); - (void) snprintf(bp, len, "exec %s%s%s %s %s", _PATH_CP, - iamrecursive ? " -r" : "", pflag ? " -p" : "", - argv[i], argv[argc - 1]); - if (verbose_mode) - fprintf(stderr, "Executing: %s\n", bp); - if (system(bp)) + freeargs(&alist); + addargs(&alist, "%s", _PATH_CP); + if (iamrecursive) + addargs(&alist, "-r"); + if (pflag) + addargs(&alist, "-p"); + addargs(&alist, "--"); + addargs(&alist, "%s", argv[i]); + addargs(&alist, "%s", argv[argc-1]); + if (do_local_cmd(&alist)) ++errs; - (void) xfree(bp); continue; } *src++ = 0; if (*src == 0) src = "."; - if ((host = strchr(argv[i], '@')) == NULL) { + if ((host = strrchr(argv[i], '@')) == NULL) { host = argv[i]; suser = NULL; } else { @@ -465,14 +615,10 @@ tolocal(argc, argv) suser = argv[i]; if (*suser == '\0') suser = pwd->pw_name; - else if (!okname(suser)) - continue; } host = cleanhostname(host); - len = strlen(src) + CMDNEEDS + 20; - bp = xmalloc(len); - (void) snprintf(bp, len, "%s -f %s", cmd, src); - if (do_cmd(host, suser, bp, &remin, &remout, argc) < 0) { + xasprintf(&bp, "%s -f -- %s", cmd, src); + if (do_cmd(host, suser, bp, &remin, &remout) < 0) { (void) xfree(bp); ++errs; continue; @@ -485,16 +631,15 @@ tolocal(argc, argv) } void -source(argc, argv) - int argc; - char *argv[]; +source(int argc, char **argv) { struct stat stb; static BUF buffer; BUF *bp; - off_t i, amt, result; - int fd, haderr, indx; - char *last, *name, buf[2048]; + off_t i, statbytes; + size_t amt; + int fd = -1, haderr, indx; + char *last, *name, buf[2048], encname[MAXPATHLEN]; int len; for (indx = 0; indx < argc; ++indx) { @@ -503,12 +648,21 @@ source(argc, argv) len = strlen(name); while (len > 1 && name[len-1] == '/') name[--len] = '\0'; - if ((fd = open(name, O_RDONLY, 0)) < 0) + if ((fd = open(name, O_RDONLY|O_NONBLOCK, 0)) < 0) goto syserr; + if (strchr(name, '\n') != NULL) { + strnvis(encname, name, sizeof(encname), VIS_NL); + name = encname; + } if (fstat(fd, &stb) < 0) { syserr: run_err("%s: %s", name, strerror(errno)); goto next; } + if (stb.st_size < 0) { + run_err("%s: %s", name, "Negative file size"); + goto next; + } + unset_nonblock(fd); switch (stb.st_mode & S_IFMT) { case S_IFREG: break; @@ -533,65 +687,66 @@ syserr: run_err("%s: %s", name, strerror(errno)); * versions expecting microseconds. */ (void) snprintf(buf, sizeof buf, "T%lu 0 %lu 0\n", - (u_long) stb.st_mtime, - (u_long) stb.st_atime); - (void) atomicio(write, remout, buf, strlen(buf)); + (u_long) (stb.st_mtime < 0 ? 0 : stb.st_mtime), + (u_long) (stb.st_atime < 0 ? 0 : stb.st_atime)); + if (verbose_mode) { + fprintf(stderr, "File mtime %ld atime %ld\n", + (long)stb.st_mtime, (long)stb.st_atime); + fprintf(stderr, "Sending file timestamps: %s", + buf); + } + (void) atomicio(vwrite, remout, buf, strlen(buf)); if (response() < 0) goto next; } #define FILEMODEMASK (S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO) -#ifdef HAVE_LONG_LONG_INT snprintf(buf, sizeof buf, "C%04o %lld %s\n", (u_int) (stb.st_mode & FILEMODEMASK), - (long long) stb.st_size, last); -#else - /* XXX: Handle integer overflow? */ - snprintf(buf, sizeof buf, "C%04o %lu %s\n", - (u_int) (stb.st_mode & FILEMODEMASK), - (u_long) stb.st_size, last); -#endif - + (long long)stb.st_size, last); if (verbose_mode) { fprintf(stderr, "Sending file modes: %s", buf); - fflush(stderr); } - (void) atomicio(write, remout, buf, strlen(buf)); + (void) atomicio(vwrite, remout, buf, strlen(buf)); if (response() < 0) goto next; - if ((bp = allocbuf(&buffer, fd, 2048)) == NULL) { -next: (void) close(fd); + if ((bp = allocbuf(&buffer, fd, COPY_BUFLEN)) == NULL) { +next: if (fd != -1) { + (void) close(fd); + fd = -1; + } continue; } - if (showprogress) { - totalbytes = stb.st_size; - progressmeter(-1); - } - /* Keep writing after an error so that we stay sync'd up. */ + if (showprogress) + start_progress_meter(curfile, stb.st_size, &statbytes); + set_nonblock(remout); for (haderr = i = 0; i < stb.st_size; i += bp->cnt) { amt = bp->cnt; - if (i + amt > stb.st_size) + if (i + (off_t)amt > stb.st_size) amt = stb.st_size - i; if (!haderr) { - result = atomicio(read, fd, bp->buf, amt); - if (result != amt) - haderr = result >= 0 ? EIO : errno; + if (atomicio(read, fd, bp->buf, amt) != amt) + haderr = errno; } - if (haderr) - (void) atomicio(write, remout, bp->buf, amt); - else { - result = atomicio(write, remout, bp->buf, amt); - if (result != amt) - haderr = result >= 0 ? EIO : errno; - statbytes += result; + /* Keep writing after error to retain sync */ + if (haderr) { + (void)atomicio(vwrite, remout, bp->buf, amt); + continue; } + if (scpio(vwrite, remout, bp->buf, amt, + &statbytes) != amt) + haderr = errno; } + unset_nonblock(remout); if (showprogress) - progressmeter(1); + stop_progress_meter(); - if (close(fd) < 0 && !haderr) - haderr = errno; + if (fd != -1) { + if (close(fd) < 0 && !haderr) + haderr = errno; + fd = -1; + } if (!haderr) - (void) atomicio(write, remout, "", 1); + (void) atomicio(vwrite, remout, "", 1); else run_err("%s: %s", name, strerror(haderr)); (void) response(); @@ -599,9 +754,7 @@ next: (void) close(fd); } void -rsource(name, statp) - char *name; - struct stat *statp; +rsource(char *name, struct stat *statp) { DIR *dirp; struct dirent *dp; @@ -620,7 +773,7 @@ rsource(name, statp) (void) snprintf(path, sizeof(path), "T%lu 0 %lu 0\n", (u_long) statp->st_mtime, (u_long) statp->st_atime); - (void) atomicio(write, remout, path, strlen(path)); + (void) atomicio(vwrite, remout, path, strlen(path)); if (response() < 0) { closedir(dirp); return; @@ -630,7 +783,7 @@ rsource(name, statp) (u_int) (statp->st_mode & FILEMODEMASK), 0, last); if (verbose_mode) fprintf(stderr, "Entering directory: %s", path); - (void) atomicio(write, remout, path, strlen(path)); + (void) atomicio(vwrite, remout, path, strlen(path)); if (response() < 0) { closedir(dirp); return; @@ -649,14 +802,66 @@ rsource(name, statp) source(1, vect); } (void) closedir(dirp); - (void) atomicio(write, remout, "E\n", 2); + (void) atomicio(vwrite, remout, "E\n", 2); (void) response(); } void -sink(argc, argv) - int argc; - char *argv[]; +bwlimit(int amount) +{ + static struct timeval bwstart, bwend; + static int lamt, thresh = 16384; + u_int64_t waitlen; + struct timespec ts, rm; + + if (!timerisset(&bwstart)) { + gettimeofday(&bwstart, NULL); + return; + } + + lamt += amount; + if (lamt < thresh) + return; + + gettimeofday(&bwend, NULL); + timersub(&bwend, &bwstart, &bwend); + if (!timerisset(&bwend)) + return; + + lamt *= 8; + waitlen = (double)1000000L * lamt / limit_rate; + + bwstart.tv_sec = waitlen / 1000000L; + bwstart.tv_usec = waitlen % 1000000L; + + if (timercmp(&bwstart, &bwend, >)) { + timersub(&bwstart, &bwend, &bwend); + + /* Adjust the wait time */ + if (bwend.tv_sec) { + thresh /= 2; + if (thresh < 2048) + thresh = 2048; + } else if (bwend.tv_usec < 10000) { + thresh *= 2; + if (thresh > COPY_BUFLEN * 4) + thresh = COPY_BUFLEN * 4; + } + + TIMEVAL_TO_TIMESPEC(&bwend, &ts); + while (nanosleep(&ts, &rm) == -1) { + if (errno != EINTR) + break; + ts = rm; + } + } + + lamt = 0; + gettimeofday(&bwstart, NULL); +} + +void +sink(int argc, char **argv) { static BUF buffer; struct stat stb; @@ -664,16 +869,18 @@ sink(argc, argv) YES, NO, DISPLAYED } wrerr; BUF *bp; - off_t i, j; - int amt, count, exists, first, mask, mode, ofd, omode; - off_t size; + off_t i; + size_t j, count; + int amt, exists, first, ofd; + mode_t mode, omode, mask; + off_t size, statbytes; int setimes, targisdir, wrerrno = 0; char ch, *cp, *np, *targ, *why, *vect[1], buf[2048]; struct timeval tv[2]; #define atime tv[0] #define mtime tv[1] -#define SCREWUP(str) do { why = str; goto screwup; } while (0) +#define SCREWUP(str) { why = str; goto screwup; } setimes = targisdir = 0; mask = umask(0); @@ -687,12 +894,12 @@ sink(argc, argv) if (targetshouldbedirectory) verifydir(targ); - (void) atomicio(write, remout, "", 1); + (void) atomicio(vwrite, remout, "", 1); if (stat(targ, &stb) == 0 && S_ISDIR(stb.st_mode)) targisdir = 1; for (first = 1;; first = 0) { cp = buf; - if (atomicio(read, remin, cp, 1) <= 0) + if (atomicio(read, remin, cp, 1) != 1) return; if (*cp++ == '\n') SCREWUP("unexpected "); @@ -702,10 +909,12 @@ sink(argc, argv) *cp++ = ch; } while (cp < &buf[sizeof(buf) - 1] && ch != '\n'); *cp = 0; + if (verbose_mode) + fprintf(stderr, "Sink: %s", buf); if (buf[0] == '\01' || buf[0] == '\02') { if (iamremote == 0) - (void) atomicio(write, STDERR_FILENO, + (void) atomicio(vwrite, STDERR_FILENO, buf + 1, strlen(buf + 1)); if (buf[0] == '\02') exit(1); @@ -713,7 +922,7 @@ sink(argc, argv) continue; } if (buf[0] == 'E') { - (void) atomicio(write, remout, "", 1); + (void) atomicio(vwrite, remout, "", 1); return; } if (ch == '\n') @@ -735,7 +944,7 @@ sink(argc, argv) atime.tv_usec = strtol(cp, &cp, 10); if (!cp || *cp++ != '\0') SCREWUP("atime.usec not delimited"); - (void) atomicio(write, remout, "", 1); + (void) atomicio(vwrite, remout, "", 1); continue; } if (*cp != 'C' && *cp != 'D') { @@ -765,9 +974,13 @@ sink(argc, argv) size = size * 10 + (*cp++ - '0'); if (*cp++ != ' ') SCREWUP("size not delimited"); + if ((strchr(cp, '/') != NULL) || (strcmp(cp, "..") == 0)) { + run_err("error: unexpected filename: %s", cp); + exit(1); + } if (targisdir) { static char *namebuf; - static int cursize; + static size_t cursize; size_t need; need = strlen(targ) + strlen(cp) + 250; @@ -778,7 +991,7 @@ sink(argc, argv) cursize = need; } (void) snprintf(namebuf, need, "%s%s%s", targ, - *targ ? "/" : "", cp); + strcmp(targ, "/") ? "/" : "", cp); np = namebuf; } else np = targ; @@ -786,6 +999,8 @@ sink(argc, argv) exists = stat(np, &stb) == 0; if (buf[0] == 'D') { int mod_flag = pflag; + if (!iamrecursive) + SCREWUP("received directory without -r"); if (exists) { if (!S_ISDIR(stb.st_mode)) { errno = ENOTDIR; @@ -820,80 +1035,83 @@ sink(argc, argv) bad: run_err("%s: %s", np, strerror(errno)); continue; } - (void) atomicio(write, remout, "", 1); - if ((bp = allocbuf(&buffer, ofd, 4096)) == NULL) { + (void) atomicio(vwrite, remout, "", 1); + if ((bp = allocbuf(&buffer, ofd, COPY_BUFLEN)) == NULL) { (void) close(ofd); continue; } cp = bp->buf; wrerr = NO; - if (showprogress) { - totalbytes = size; - progressmeter(-1); - } statbytes = 0; - for (count = i = 0; i < size; i += 4096) { - amt = 4096; + if (showprogress) + start_progress_meter(curfile, size, &statbytes); + set_nonblock(remin); + for (count = i = 0; i < size; i += bp->cnt) { + amt = bp->cnt; if (i + amt > size) amt = size - i; count += amt; do { - j = read(remin, cp, amt); - if (j == -1 && (errno == EINTR || - errno == EAGAIN)) { - continue; - } else if (j <= 0) { - run_err("%s", j ? strerror(errno) : + j = scpio(read, remin, cp, amt, &statbytes); + if (j == 0) { + run_err("%s", j != EPIPE ? + strerror(errno) : "dropped connection"); exit(1); } amt -= j; cp += j; - statbytes += j; } while (amt > 0); + if (count == bp->cnt) { /* Keep reading so we stay sync'd up. */ if (wrerr == NO) { - j = atomicio(write, ofd, bp->buf, count); - if (j != count) { + if (atomicio(vwrite, ofd, bp->buf, + count) != count) { wrerr = YES; - wrerrno = j >= 0 ? EIO : errno; + wrerrno = errno; } } count = 0; cp = bp->buf; } } + unset_nonblock(remin); if (showprogress) - progressmeter(1); + stop_progress_meter(); if (count != 0 && wrerr == NO && - (j = atomicio(write, ofd, bp->buf, count)) != count) { + atomicio(vwrite, ofd, bp->buf, count) != count) { wrerr = YES; - wrerrno = j >= 0 ? EIO : errno; + wrerrno = errno; } - if (ftruncate(ofd, size)) { + if (wrerr == NO && (!exists || S_ISREG(stb.st_mode)) && + ftruncate(ofd, size) != 0) { run_err("%s: truncate: %s", np, strerror(errno)); wrerr = DISPLAYED; } if (pflag) { if (exists || omode != mode) #ifdef HAVE_FCHMOD - if (fchmod(ofd, omode)) + if (fchmod(ofd, omode)) { #else /* HAVE_FCHMOD */ - if (chmod(np, omode)) + if (chmod(np, omode)) { #endif /* HAVE_FCHMOD */ run_err("%s: set mode: %s", np, strerror(errno)); + wrerr = DISPLAYED; + } } else { if (!exists && omode != mode) #ifdef HAVE_FCHMOD - if (fchmod(ofd, omode & ~mask)) + if (fchmod(ofd, omode & ~mask)) { #else /* HAVE_FCHMOD */ - if (chmod(np, omode & ~mask)) + if (chmod(np, omode & ~mask)) { #endif /* HAVE_FCHMOD */ run_err("%s: set mode: %s", np, strerror(errno)); + wrerr = DISPLAYED; + } } if (close(ofd) == -1) { wrerr = YES; @@ -913,7 +1131,7 @@ bad: run_err("%s: %s", np, strerror(errno)); run_err("%s: %s", np, strerror(wrerrno)); break; case NO: - (void) atomicio(write, remout, "", 1); + (void) atomicio(vwrite, remout, "", 1); break; case DISPLAYED: break; @@ -925,7 +1143,7 @@ screwup: } int -response() +response(void) { char ch, *cp, resp, rbuf[2048]; @@ -948,7 +1166,7 @@ response() } while (cp < &rbuf[sizeof(rbuf) - 1] && ch != '\n'); if (!iamremote) - (void) atomicio(write, STDERR_FILENO, rbuf, cp - rbuf); + (void) atomicio(vwrite, STDERR_FILENO, rbuf, cp - rbuf); ++errs; if (resp == 1) return (-1); @@ -958,12 +1176,12 @@ response() } void -usage() +usage(void) { (void) fprintf(stderr, - "usage: scp [-pqrvBC46] [-F config] [-S ssh] [-P port] [-c cipher] [-i identity]\n" - " [-o option] f1 f2\n" - " or: scp [options] f1 ... fn directory\n"); + "usage: scp [-1246BCpqrv] [-c cipher] [-F ssh_config] [-i identity_file]\n" + " [-l limit] [-o ssh_option] [-P port] [-S program]\n" + " [[user@]host1:]file1 ... [[user@]host2:]file2\n"); exit(1); } @@ -974,15 +1192,15 @@ run_err(const char *fmt,...) va_list ap; ++errs; - if (fp == NULL && !(fp = fdopen(remout, "w"))) - return; - (void) fprintf(fp, "%c", 0x01); - (void) fprintf(fp, "scp: "); - va_start(ap, fmt); - (void) vfprintf(fp, fmt, ap); - va_end(ap); - (void) fprintf(fp, "\n"); - (void) fflush(fp); + if (fp != NULL || (remout != -1 && (fp = fdopen(remout, "w")))) { + (void) fprintf(fp, "%c", 0x01); + (void) fprintf(fp, "scp: "); + va_start(ap, fmt); + (void) vfprintf(fp, fmt, ap); + va_end(ap); + (void) fprintf(fp, "\n"); + (void) fflush(fp); + } if (!iamremote) { va_start(ap, fmt); @@ -993,8 +1211,7 @@ run_err(const char *fmt,...) } void -verifydir(cp) - char *cp; +verifydir(char *cp) { struct stat stb; @@ -1004,12 +1221,11 @@ verifydir(cp) errno = ENOTDIR; } run_err("%s: %s", cp, strerror(errno)); - exit(1); + killchild(0); } int -okname(cp0) - char *cp0; +okname(char *cp0) { int c; char *cp; @@ -1019,9 +1235,18 @@ okname(cp0) c = (int)*cp; if (c & 0200) goto bad; - if (!isalpha(c) && !isdigit(c) && - c != '_' && c != '-' && c != '.' && c != '+') - goto bad; + if (!isalpha(c) && !isdigit(c)) { + switch (c) { + case '\'': + case '"': + case '`': + case ' ': + case '#': + goto bad; + default: + break; + } + } } while (*++cp); return (1); @@ -1030,40 +1255,35 @@ bad: fprintf(stderr, "%s: invalid user name\n", cp0); } BUF * -allocbuf(bp, fd, blksize) - BUF *bp; - int fd, blksize; +allocbuf(BUF *bp, int fd, int blksize) { size_t size; -#ifdef HAVE_ST_BLKSIZE +#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE struct stat stb; if (fstat(fd, &stb) < 0) { run_err("fstat: %s", strerror(errno)); return (0); } - if (stb.st_blksize == 0) + size = roundup(stb.st_blksize, blksize); + if (size == 0) size = blksize; - else - size = blksize + (stb.st_blksize - blksize % stb.st_blksize) % - stb.st_blksize; -#else /* HAVE_ST_BLKSIZE */ +#else /* HAVE_STRUCT_STAT_ST_BLKSIZE */ size = blksize; -#endif /* HAVE_ST_BLKSIZE */ +#endif /* HAVE_STRUCT_STAT_ST_BLKSIZE */ if (bp->cnt >= size) return (bp); if (bp->buf == NULL) bp->buf = xmalloc(size); else - bp->buf = xrealloc(bp->buf, size); + bp->buf = xrealloc(bp->buf, 1, size); memset(bp->buf, 0, size); bp->cnt = size; return (bp); } void -lostconn(signo) - int signo; +lostconn(int signo) { if (!iamremote) write(STDERR_FILENO, "lost connection\n", 16); @@ -1072,142 +1292,3 @@ lostconn(signo) else exit(1); } - -static void -updateprogressmeter(int ignore) -{ - int save_errno = errno; - - progressmeter(0); - signal(SIGALRM, updateprogressmeter); - alarm(PROGRESSTIME); - errno = save_errno; -} - -static int -foregroundproc(void) -{ - static pid_t pgrp = -1; - int ctty_pgrp; - - if (pgrp == -1) - pgrp = getpgrp(); - -#ifdef HAVE_TCGETPGRP - return ((ctty_pgrp = tcgetpgrp(STDOUT_FILENO)) != -1 && - ctty_pgrp == pgrp); -#else - return ((ioctl(STDOUT_FILENO, TIOCGPGRP, &ctty_pgrp) != -1 && - ctty_pgrp == pgrp)); -#endif -} - -void -progressmeter(int flag) -{ - static const char prefixes[] = " KMGTP"; - static struct timeval lastupdate; - static off_t lastsize; - struct timeval now, td, wait; - off_t cursize, abbrevsize; - double elapsed; - int ratio, barlength, i, remaining; - char buf[256]; - - if (flag == -1) { - (void) gettimeofday(&start, (struct timezone *) 0); - lastupdate = start; - lastsize = 0; - } - if (foregroundproc() == 0) - return; - - (void) gettimeofday(&now, (struct timezone *) 0); - cursize = statbytes; - if (totalbytes != 0) { - ratio = 100.0 * cursize / totalbytes; - ratio = MAX(ratio, 0); - ratio = MIN(ratio, 100); - } else - ratio = 100; - - snprintf(buf, sizeof(buf), "\r%-20.20s %3d%% ", curfile, ratio); - - barlength = getttywidth() - 51; - barlength = (barlength <= MAX_BARLENGTH)?barlength:MAX_BARLENGTH; - if (barlength > 0) { - i = barlength * ratio / 100; - snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), - "|%.*s%*s|", i, BAR, barlength - i, ""); - } - i = 0; - abbrevsize = cursize; - while (abbrevsize >= 100000 && i < sizeof(prefixes)) { - i++; - abbrevsize >>= 10; - } - snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), " %5lu %c%c ", - (unsigned long) abbrevsize, prefixes[i], - prefixes[i] == ' ' ? ' ' : 'B'); - - timersub(&now, &lastupdate, &wait); - if (cursize > lastsize) { - lastupdate = now; - lastsize = cursize; - if (wait.tv_sec >= STALLTIME) { - start.tv_sec += wait.tv_sec; - start.tv_usec += wait.tv_usec; - } - wait.tv_sec = 0; - } - timersub(&now, &start, &td); - elapsed = td.tv_sec + (td.tv_usec / 1000000.0); - - if (flag != 1 && - (statbytes <= 0 || elapsed <= 0.0 || cursize > totalbytes)) { - snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), - " --:-- ETA"); - } else if (wait.tv_sec >= STALLTIME) { - snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), - " - stalled -"); - } else { - if (flag != 1) - remaining = (int)(totalbytes / (statbytes / elapsed) - - elapsed); - else - remaining = elapsed; - - i = remaining / 3600; - if (i) - snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), - "%2d:", i); - else - snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), - " "); - i = remaining % 3600; - snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), - "%02d:%02d%s", i / 60, i % 60, - (flag != 1) ? " ETA" : " "); - } - atomicio(write, fileno(stdout), buf, strlen(buf)); - - if (flag == -1) { - mysignal(SIGALRM, updateprogressmeter); - alarm(PROGRESSTIME); - } else if (flag == 1) { - alarm(0); - atomicio(write, fileno(stdout), "\n", 1); - statbytes = 0; - } -} - -int -getttywidth(void) -{ - struct winsize winsize; - - if (ioctl(fileno(stdout), TIOCGWINSZ, &winsize) != -1) - return (winsize.ws_col ? winsize.ws_col : 80); - else - return (80); -}