=================================================================== RCS file: /cvsrepo/anoncvs/cvs/src/usr.bin/ssh/sftp.c,v retrieving revision 1.45 retrieving revision 1.45.2.2 diff -u -r1.45 -r1.45.2.2 --- src/usr.bin/ssh/sftp.c 2004/03/03 09:31:20 1.45 +++ src/usr.bin/ssh/sftp.c 2005/03/10 17:15:04 1.45.2.2 @@ -16,9 +16,10 @@ #include "includes.h" -RCSID("$OpenBSD: sftp.c,v 1.45 2004/03/03 09:31:20 djm Exp $"); +RCSID("$OpenBSD: sftp.c,v 1.45.2.2 2005/03/10 17:15:04 brad Exp $"); #include +#include #include "buffer.h" #include "xmalloc.h" @@ -48,16 +49,31 @@ /* This is set to 0 if the progressmeter is not desired. */ int showprogress = 1; +/* SIGINT received during command processing */ +volatile sig_atomic_t interrupted = 0; + +/* I wish qsort() took a separate ctx for the comparison function...*/ +int sort_flag; + int remote_glob(struct sftp_conn *, const char *, int, int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */ /* Separators for interactive commands */ #define WHITESPACE " \t\r\n" -/* Define what type of ls view (0 - multi-column) */ -#define LONG_VIEW 1 /* Full view ala ls -l */ -#define SHORT_VIEW 2 /* Single row view ala ls -1 */ +/* ls flags */ +#define LS_LONG_VIEW 0x01 /* Full view ala ls -l */ +#define LS_SHORT_VIEW 0x02 /* Single row view ala ls -1 */ +#define LS_NUMERIC_VIEW 0x04 /* Long view with numeric uid/gid */ +#define LS_NAME_SORT 0x08 /* Sort by name (default) */ +#define LS_TIME_SORT 0x10 /* Sort by mtime */ +#define LS_SIZE_SORT 0x20 /* Sort by file size */ +#define LS_REVERSE_SORT 0x40 /* Reverse sort order */ +#define LS_SHOW_ALL 0x80 /* Don't skip filenames starting with '.' */ +#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW) +#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT) + /* Commands for interactive mode */ #define I_CHDIR 1 #define I_CHGRP 2 @@ -127,6 +143,28 @@ int interactive_loop(int fd_in, int fd_out, char *file1, char *file2); static void +killchild(int signo) +{ + if (sshpid > 1) { + kill(sshpid, SIGTERM); + waitpid(sshpid, NULL, 0); + } + + _exit(1); +} + +static void +cmd_interrupt(int signo) +{ + const char msg[] = "\rInterrupt \n"; + int olderrno = errno; + + write(STDERR_FILENO, msg, sizeof(msg) - 1); + interrupted = 1; + errno = olderrno; +} + +static void help(void) { printf("Available commands:\n"); @@ -223,7 +261,7 @@ return (xstrdup(path)); len = strlen(strip); - if (strip != NULL && strncmp(path, strip, len) == 0) { + if (strncmp(path, strip, len) == 0) { if (strip[len - 1] != '/' && path[len] == '/') len++; return (xstrdup(path + len)); @@ -250,13 +288,13 @@ static char * make_absolute(char *p, char *pwd) { - char *abs; + char *abs_str; /* Derelativise */ if (p && p[0] != '/') { - abs = path_append(pwd, p); + abs_str = path_append(pwd, p); xfree(p); - return(abs); + return(abs_str); } else return(p); } @@ -309,16 +347,42 @@ { const char *cp = *cpp; + /* Defaults */ + *lflag = LS_NAME_SORT; + /* Check for flags */ if (cp++[0] == '-') { for(; strchr(WHITESPACE, *cp) == NULL; cp++) { switch (*cp) { case 'l': - *lflag = LONG_VIEW; + *lflag &= ~VIEW_FLAGS; + *lflag |= LS_LONG_VIEW; break; case '1': - *lflag = SHORT_VIEW; + *lflag &= ~VIEW_FLAGS; + *lflag |= LS_SHORT_VIEW; break; + case 'n': + *lflag &= ~VIEW_FLAGS; + *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW; + break; + case 'S': + *lflag &= ~SORT_FLAGS; + *lflag |= LS_SIZE_SORT; + break; + case 't': + *lflag &= ~SORT_FLAGS; + *lflag |= LS_TIME_SORT; + break; + case 'r': + *lflag |= LS_REVERSE_SORT; + break; + case 'f': + *lflag &= ~SORT_FLAGS; + break; + case 'a': + *lflag |= LS_SHOW_ALL; + break; default: error("Invalid flag -%c", *cp); return(-1); @@ -365,7 +429,7 @@ i++; if (cp[i] != '\'' && cp[i] != '\"' && cp[i] != '\\') { - error("Bad escaped character '\%c'", + error("Bad escaped character '\\%c'", cp[i]); goto fail; } @@ -461,7 +525,7 @@ goto out; } - for (i = 0; g.gl_pathv[i]; i++) { + for (i = 0; g.gl_pathv[i] && !interrupted; i++) { if (infer_path(g.gl_pathv[i], &tmp)) { err = -1; goto out; @@ -530,7 +594,7 @@ goto out; } - for (i = 0; g.gl_pathv[i]; i++) { + for (i = 0; g.gl_pathv[i] && !interrupted; i++) { if (!is_reg(g.gl_pathv[i])) { error("skipping non-regular file %s", g.gl_pathv[i]); @@ -578,8 +642,17 @@ { SFTP_DIRENT *a = *(SFTP_DIRENT **)aa; SFTP_DIRENT *b = *(SFTP_DIRENT **)bb; + int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1; - return (strcmp(a->filename, b->filename)); +#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1)) + if (sort_flag & LS_NAME_SORT) + return (rmul * strcmp(a->filename, b->filename)); + else if (sort_flag & LS_TIME_SORT) + return (rmul * NCMP(a->a.mtime, b->a.mtime)); + else if (sort_flag & LS_SIZE_SORT) + return (rmul * NCMP(a->a.size, b->a.size)); + + fatal("Unknown ls sort type"); } /* sftp ls.1 replacement for directories */ @@ -592,14 +665,16 @@ if ((n = do_readdir(conn, path, &d)) != 0) return (n); - if (!(lflag & SHORT_VIEW)) { + if (!(lflag & LS_SHORT_VIEW)) { int m = 0, width = 80; struct winsize ws; char *tmp; /* Count entries for sort and find longest filename */ - for (n = 0; d[n] != NULL; n++) - m = MAX(m, strlen(d[n]->filename)); + for (n = 0; d[n] != NULL; n++) { + if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL)) + m = MAX(m, strlen(d[n]->filename)); + } /* Add any subpath that also needs to be counted */ tmp = path_strip(path, strip_path); @@ -615,24 +690,33 @@ colspace = MIN(colspace, width); } - qsort(d, n, sizeof(*d), sdirent_comp); + if (lflag & SORT_FLAGS) { + sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT); + qsort(d, n, sizeof(*d), sdirent_comp); + } - for (n = 0; d[n] != NULL; n++) { + for (n = 0; d[n] != NULL && !interrupted; n++) { char *tmp, *fname; + if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL)) + continue; + tmp = path_append(path, d[n]->filename); fname = path_strip(tmp, strip_path); xfree(tmp); - if (lflag & LONG_VIEW) { - char *lname; - struct stat sb; + if (lflag & LS_LONG_VIEW) { + if (lflag & LS_NUMERIC_VIEW) { + char *lname; + struct stat sb; - memset(&sb, 0, sizeof(sb)); - attrib_to_stat(&d[n]->a, &sb); - lname = ls_file(fname, &sb, 1); - printf("%s\n", lname); - xfree(lname); + memset(&sb, 0, sizeof(sb)); + attrib_to_stat(&d[n]->a, &sb); + lname = ls_file(fname, &sb, 1); + printf("%s\n", lname); + xfree(lname); + } else + printf("%s\n", d[n]->longname); } else { printf("%-*s", colspace, fname); if (c >= columns) { @@ -645,7 +729,7 @@ xfree(fname); } - if (!(lflag & LONG_VIEW) && (c != 1)) + if (!(lflag & LS_LONG_VIEW) && (c != 1)) printf("\n"); free_sftp_dirents(d); @@ -659,34 +743,41 @@ { glob_t g; int i, c = 1, colspace = 0, columns = 1; - Attrib *a; + Attrib *a = NULL; memset(&g, 0, sizeof(g)); if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE, - NULL, &g)) { + NULL, &g) || (g.gl_pathc && !g.gl_matchc)) { + if (g.gl_pathc) + globfree(&g); error("Can't ls: \"%s\" not found", path); return (-1); } + if (interrupted) + goto out; + /* - * If the glob returns a single match, which is the same as the - * input glob, and it is a directory, then just list its contents + * If the glob returns a single match and it is a directory, + * then just list its contents. */ - if (g.gl_pathc == 1 && - strncmp(path, g.gl_pathv[0], strlen(g.gl_pathv[0]) - 1) == 0) { - if ((a = do_lstat(conn, path, 1)) == NULL) { + if (g.gl_matchc == 1) { + if ((a = do_lstat(conn, g.gl_pathv[0], 1)) == NULL) { globfree(&g); return (-1); } if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) && S_ISDIR(a->perm)) { + int err; + + err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag); globfree(&g); - return (do_ls_dir(conn, path, strip_path, lflag)); + return (err); } } - if (!(lflag & SHORT_VIEW)) { + if (!(lflag & LS_SHORT_VIEW)) { int m = 0, width = 80; struct winsize ws; @@ -702,12 +793,12 @@ colspace = width / columns; } - for (i = 0; g.gl_pathv[i]; i++) { + for (i = 0; g.gl_pathv[i] && !interrupted; i++, a = NULL) { char *fname; fname = path_strip(g.gl_pathv[i], strip_path); - if (lflag & LONG_VIEW) { + if (lflag & LS_LONG_VIEW) { char *lname; struct stat sb; @@ -719,7 +810,8 @@ * that the server returns as well as the filenames. */ memset(&sb, 0, sizeof(sb)); - a = do_lstat(conn, g.gl_pathv[i], 1); + if (a == NULL) + a = do_lstat(conn, g.gl_pathv[i], 1); if (a != NULL) attrib_to_stat(a, &sb); lname = ls_file(fname, &sb, 1); @@ -736,9 +828,10 @@ xfree(fname); } - if (!(lflag & LONG_VIEW) && (c != 1)) + if (!(lflag & LS_LONG_VIEW) && (c != 1)) printf("\n"); + out: if (g.gl_pathc) globfree(&g); @@ -948,7 +1041,7 @@ case I_RM: path1 = make_absolute(path1, *pwd); remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); - for (i = 0; g.gl_pathv[i]; i++) { + for (i = 0; g.gl_pathv[i] && !interrupted; i++) { printf("Removing %s\n", g.gl_pathv[i]); err = do_rm(conn, g.gl_pathv[i]); if (err != 0 && err_abort) @@ -1037,7 +1130,7 @@ a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; a.perm = n_arg; remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); - for (i = 0; g.gl_pathv[i]; i++) { + for (i = 0; g.gl_pathv[i] && !interrupted; i++) { printf("Changing mode on %s\n", g.gl_pathv[i]); err = do_setstat(conn, g.gl_pathv[i], &a); if (err != 0 && err_abort) @@ -1048,7 +1141,7 @@ case I_CHGRP: path1 = make_absolute(path1, *pwd); remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); - for (i = 0; g.gl_pathv[i]; i++) { + for (i = 0; g.gl_pathv[i] && !interrupted; i++) { if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) { if (err != 0 && err_abort) break; @@ -1123,6 +1216,12 @@ return (0); } +static char * +prompt(EditLine *el) +{ + return ("sftp> "); +} + int interactive_loop(int fd_in, int fd_out, char *file1, char *file2) { @@ -1131,7 +1230,26 @@ char cmd[2048]; struct sftp_conn *conn; int err; + EditLine *el = NULL; + History *hl = NULL; + HistEvent hev; + extern char *__progname; + if (!batchmode && isatty(STDIN_FILENO)) { + if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL) + fatal("Couldn't initialise editline"); + if ((hl = history_init()) == NULL) + fatal("Couldn't initialise editline history"); + history(hl, &hev, H_SETSIZE, 100); + el_set(el, EL_HIST, history, hl); + + el_set(el, EL_PROMPT, prompt); + el_set(el, EL_EDITOR, "emacs"); + el_set(el, EL_TERMINAL, NULL); + el_set(el, EL_SIGNAL, 1); + el_source(el, NULL); + } + conn = do_init(fd_in, fd_out, copy_buffer_len, num_requests); if (conn == NULL) fatal("Couldn't initialise connection to server"); @@ -1147,8 +1265,11 @@ if (remote_is_dir(conn, dir) && file2 == NULL) { printf("Changing to: %s\n", dir); snprintf(cmd, sizeof cmd, "cd \"%s\"", dir); - if (parse_dispatch_command(conn, cmd, &pwd, 1) != 0) + if (parse_dispatch_command(conn, cmd, &pwd, 1) != 0) { + xfree(dir); + xfree(pwd); return (-1); + } } else { if (file2 == NULL) snprintf(cmd, sizeof cmd, "get %s", dir); @@ -1170,22 +1291,37 @@ err = 0; for (;;) { char *cp; + const char *line; + int count = 0; - printf("sftp> "); + signal(SIGINT, SIG_IGN); - /* XXX: use libedit */ - if (fgets(cmd, sizeof(cmd), infile) == NULL) { - printf("\n"); - break; + if (el == NULL) { + printf("sftp> "); + if (fgets(cmd, sizeof(cmd), infile) == NULL) { + printf("\n"); + break; + } + if (batchmode) /* Echo command */ + printf("%s", cmd); + } else { + if ((line = el_gets(el, &count)) == NULL || count <= 0) + break; + history(hl, &hev, H_ENTER, line); + if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) { + fprintf(stderr, "Error: input line too long\n"); + continue; + } } - if (batchmode) /* Echo command */ - printf("%s", cmd); - cp = strrchr(cmd, '\n'); if (cp) *cp = '\0'; + /* Handle user interrupts gracefully during commands */ + interrupted = 0; + signal(SIGINT, cmd_interrupt); + err = parse_dispatch_command(conn, cmd, &pwd, batchmode); if (err != 0) break; @@ -1197,15 +1333,6 @@ } static void -killchild(int signo) -{ - if (sshpid > 1) - kill(sshpid, signo); - - _exit(1); -} - -static void connect_to_server(char *path, char **args, int *in, int *out) { int c_in, c_out; @@ -1234,15 +1361,23 @@ if ((dup2(c_in, STDIN_FILENO) == -1) || (dup2(c_out, STDOUT_FILENO) == -1)) { fprintf(stderr, "dup2: %s\n", strerror(errno)); - exit(1); + _exit(1); } close(*in); close(*out); close(c_in); close(c_out); - execv(path, args); + + /* + * The underlying ssh is in the same process group, so we must + * ignore SIGINT if we want to gracefully abort commands, + * otherwise the signal will make it to the ssh process and + * kill it too + */ + signal(SIGINT, SIG_IGN); + execvp(path, args); fprintf(stderr, "exec: %s: %s\n", path, strerror(errno)); - exit(1); + _exit(1); } signal(SIGTERM, killchild); @@ -1271,7 +1406,7 @@ main(int argc, char **argv) { int in, out, ch, err; - char *host, *userhost, *cp, *file2; + char *host, *userhost, *cp, *file2 = NULL; int debug_level = 0, sshver = 2; char *file1 = NULL, *sftp_server = NULL; char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL; @@ -1321,11 +1456,12 @@ fatal("Batch file already specified."); /* Allow "-" as stdin */ - if (strcmp(optarg, "-") != 0 && + if (strcmp(optarg, "-") != 0 && (infile = fopen(optarg, "r")) == NULL) fatal("%s (%s).", strerror(errno), optarg); showprogress = 0; batchmode = 1; + addargs(&args, "-obatchmode yes"); break; case 'P': sftp_direct = optarg;