]> andersk Git - openssh.git/blobdiff - sftp.c
- (tim) [configure.ac] Use the C99-conforming functions snprintf() and
[openssh.git] / sftp.c
diff --git a/sftp.c b/sftp.c
index 0123fd72c0ac06549cbc50e8ff1fb7723dab038e..e01703ba90b7f7678ae0e170b8a8b17a29d85df9 100644 (file)
--- a/sftp.c
+++ b/sftp.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sftp.c,v 1.110 2009/08/13 13:39:54 jmc Exp $ */
+/* $OpenBSD: sftp.c,v 1.122 2010/01/15 00:05:22 guenther Exp $ */
 /*
  * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
  *
 /*
  * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
  *
@@ -35,6 +35,9 @@
 #ifdef HAVE_PATHS_H
 # include <paths.h>
 #endif
 #ifdef HAVE_PATHS_H
 # include <paths.h>
 #endif
+#ifdef HAVE_LIBGEN_H
+#include <libgen.h>
+#endif
 #ifdef USE_LIBEDIT
 #include <histedit.h>
 #else
 #ifdef USE_LIBEDIT
 #include <histedit.h>
 #else
@@ -65,30 +68,39 @@ typedef void EditLine;
 #include "sftp-common.h"
 #include "sftp-client.h"
 
 #include "sftp-common.h"
 #include "sftp-client.h"
 
+#define DEFAULT_COPY_BUFLEN    32768   /* Size of buffer for up/download */
+#define DEFAULT_NUM_REQUESTS   64      /* # concurrent outstanding requests */
+
 /* File to read commands from */
 FILE* infile;
 
 /* Are we in batchfile mode? */
 int batchmode = 0;
 
 /* File to read commands from */
 FILE* infile;
 
 /* Are we in batchfile mode? */
 int batchmode = 0;
 
-/* Size of buffer used when copying files */
-size_t copy_buffer_len = 32768;
-
-/* Number of concurrent outstanding requests */
-size_t num_requests = 64;
-
 /* PID of ssh transport process */
 static pid_t sshpid = -1;
 
 /* This is set to 0 if the progressmeter is not desired. */
 int showprogress = 1;
 
 /* PID of ssh transport process */
 static pid_t sshpid = -1;
 
 /* This is set to 0 if the progressmeter is not desired. */
 int showprogress = 1;
 
+/* When this option is set, we always recursively download/upload directories */
+int global_rflag = 0;
+
+/* When this option is set, the file transfers will always preserve times */
+int global_pflag = 0;
+
 /* 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;
 
 /* 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;
 
+/* Context used for commandline completion */
+struct complete_ctx {
+       struct sftp_conn *conn;
+       char **remote_pathp;
+};
+
 int remote_glob(struct sftp_conn *, const char *, int,
     int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
 
 int remote_glob(struct sftp_conn *, const char *, int,
     int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
 
@@ -98,16 +110,17 @@ extern char *__progname;
 #define WHITESPACE " \t\r\n"
 
 /* ls flags */
 #define WHITESPACE " \t\r\n"
 
 /* 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 LS_LONG_VIEW   0x0001  /* Full view ala ls -l */
+#define LS_SHORT_VIEW  0x0002  /* Single row view ala ls -1 */
+#define LS_NUMERIC_VIEW        0x0004  /* Long view with numeric uid/gid */
+#define LS_NAME_SORT   0x0008  /* Sort by name (default) */
+#define LS_TIME_SORT   0x0010  /* Sort by mtime */
+#define LS_SIZE_SORT   0x0020  /* Sort by file size */
+#define LS_REVERSE_SORT        0x0040  /* Reverse sort order */
+#define LS_SHOW_ALL    0x0080  /* Don't skip filenames starting with '.' */
+#define LS_SI_UNITS    0x0100  /* Display sizes as K, M, G, etc. */
+
+#define VIEW_FLAGS     (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS)
 #define SORT_FLAGS     (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
 
 /* Commands for interactive mode */
 #define SORT_FLAGS     (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
 
 /* Commands for interactive mode */
@@ -139,46 +152,50 @@ extern char *__progname;
 struct CMD {
        const char *c;
        const int n;
 struct CMD {
        const char *c;
        const int n;
+       const int t;
 };
 
 };
 
+/* Type of completion */
+#define NOARGS 0
+#define REMOTE 1
+#define LOCAL  2
+
 static const struct CMD cmds[] = {
 static const struct CMD cmds[] = {
-       { "bye",        I_QUIT },
-       { "cd",         I_CHDIR },
-       { "chdir",      I_CHDIR },
-       { "chgrp",      I_CHGRP },
-       { "chmod",      I_CHMOD },
-       { "chown",      I_CHOWN },
-       { "df",         I_DF },
-       { "dir",        I_LS },
-       { "exit",       I_QUIT },
-       { "get",        I_GET },
-       { "mget",       I_GET },
-       { "help",       I_HELP },
-       { "lcd",        I_LCHDIR },
-       { "lchdir",     I_LCHDIR },
-       { "lls",        I_LLS },
-       { "lmkdir",     I_LMKDIR },
-       { "ln",         I_SYMLINK },
-       { "lpwd",       I_LPWD },
-       { "ls",         I_LS },
-       { "lumask",     I_LUMASK },
-       { "mkdir",      I_MKDIR },
-       { "progress",   I_PROGRESS },
-       { "put",        I_PUT },
-       { "mput",       I_PUT },
-       { "pwd",        I_PWD },
-       { "quit",       I_QUIT },
-       { "rename",     I_RENAME },
-       { "rm",         I_RM },
-       { "rmdir",      I_RMDIR },
-       { "symlink",    I_SYMLINK },
-       { "version",    I_VERSION },
-       { "!",          I_SHELL },
-       { "?",          I_HELP },
-       { NULL,                 -1}
+       { "bye",        I_QUIT,         NOARGS  },
+       { "cd",         I_CHDIR,        REMOTE  },
+       { "chdir",      I_CHDIR,        REMOTE  },
+       { "chgrp",      I_CHGRP,        REMOTE  },
+       { "chmod",      I_CHMOD,        REMOTE  },
+       { "chown",      I_CHOWN,        REMOTE  },
+       { "df",         I_DF,           REMOTE  },
+       { "dir",        I_LS,           REMOTE  },
+       { "exit",       I_QUIT,         NOARGS  },
+       { "get",        I_GET,          REMOTE  },
+       { "help",       I_HELP,         NOARGS  },
+       { "lcd",        I_LCHDIR,       LOCAL   },
+       { "lchdir",     I_LCHDIR,       LOCAL   },
+       { "lls",        I_LLS,          LOCAL   },
+       { "lmkdir",     I_LMKDIR,       LOCAL   },
+       { "ln",         I_SYMLINK,      REMOTE  },
+       { "lpwd",       I_LPWD,         LOCAL   },
+       { "ls",         I_LS,           REMOTE  },
+       { "lumask",     I_LUMASK,       NOARGS  },
+       { "mkdir",      I_MKDIR,        REMOTE  },
+       { "progress",   I_PROGRESS,     NOARGS  },
+       { "put",        I_PUT,          LOCAL   },
+       { "pwd",        I_PWD,          REMOTE  },
+       { "quit",       I_QUIT,         NOARGS  },
+       { "rename",     I_RENAME,       REMOTE  },
+       { "rm",         I_RM,           REMOTE  },
+       { "rmdir",      I_RMDIR,        REMOTE  },
+       { "symlink",    I_SYMLINK,      REMOTE  },
+       { "version",    I_VERSION,      NOARGS  },
+       { "!",          I_SHELL,        NOARGS  },
+       { "?",          I_HELP,         NOARGS  },
+       { NULL,         -1,             -1      }
 };
 
 };
 
-int interactive_loop(int fd_in, int fd_out, char *file1, char *file2);
+int interactive_loop(struct sftp_conn *, char *file1, char *file2);
 
 /* ARGSUSED */
 static void
 
 /* ARGSUSED */
 static void
@@ -216,18 +233,18 @@ help(void)
            "df [-hi] [path]                    Display statistics for current directory or\n"
            "                                   filesystem containing 'path'\n"
            "exit                               Quit sftp\n"
            "df [-hi] [path]                    Display statistics for current directory or\n"
            "                                   filesystem containing 'path'\n"
            "exit                               Quit sftp\n"
-           "get [-P] remote-path [local-path]  Download file\n"
+           "get [-Ppr] remote [local]          Download file\n"
            "help                               Display this help text\n"
            "lcd path                           Change local directory to 'path'\n"
            "lls [ls-options [path]]            Display local directory listing\n"
            "lmkdir path                        Create local directory\n"
            "ln oldpath newpath                 Symlink remote file\n"
            "lpwd                               Print local working directory\n"
            "help                               Display this help text\n"
            "lcd path                           Change local directory to 'path'\n"
            "lls [ls-options [path]]            Display local directory listing\n"
            "lmkdir path                        Create local directory\n"
            "ln oldpath newpath                 Symlink remote file\n"
            "lpwd                               Print local working directory\n"
-           "ls [-1aflnrSt] [path]              Display remote directory listing\n"
+           "ls [-1afhlnrSt] [path]             Display remote directory listing\n"
            "lumask umask                       Set local umask to 'umask'\n"
            "mkdir path                         Create remote directory\n"
            "progress                           Toggle display of progress meter\n"
            "lumask umask                       Set local umask to 'umask'\n"
            "mkdir path                         Create remote directory\n"
            "progress                           Toggle display of progress meter\n"
-           "put [-P] local-path [remote-path]  Upload file\n"
+           "put [-Ppr] local [remote]          Upload file\n"
            "pwd                                Display remote working directory\n"
            "quit                               Quit sftp\n"
            "rename oldpath newpath             Rename remote file\n"
            "pwd                                Display remote working directory\n"
            "quit                               Quit sftp\n"
            "rename oldpath newpath             Rename remote file\n"
@@ -313,21 +330,6 @@ path_strip(char *path, char *strip)
        return (xstrdup(path));
 }
 
        return (xstrdup(path));
 }
 
-static char *
-path_append(char *p1, char *p2)
-{
-       char *ret;
-       size_t len = strlen(p1) + strlen(p2) + 2;
-
-       ret = xmalloc(len);
-       strlcpy(ret, p1, len);
-       if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/')
-               strlcat(ret, "/", len);
-       strlcat(ret, p2, len);
-
-       return(ret);
-}
-
 static char *
 make_absolute(char *p, char *pwd)
 {
 static char *
 make_absolute(char *p, char *pwd)
 {
@@ -343,27 +345,8 @@ make_absolute(char *p, char *pwd)
 }
 
 static int
 }
 
 static int
-infer_path(const char *p, char **ifp)
-{
-       char *cp;
-
-       cp = strrchr(p, '/');
-       if (cp == NULL) {
-               *ifp = xstrdup(p);
-               return(0);
-       }
-
-       if (!cp[1]) {
-               error("Invalid path");
-               return(-1);
-       }
-
-       *ifp = xstrdup(cp + 1);
-       return(0);
-}
-
-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 *pflag,
+    int *rflag)
 {
        extern int opterr, optind, optopt, optreset;
        int ch;
 {
        extern int opterr, optind, optopt, optreset;
        int ch;
@@ -371,13 +354,17 @@ parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag)
        optind = optreset = 1;
        opterr = 0;
 
        optind = optreset = 1;
        opterr = 0;
 
-       *pflag = 0;
-       while ((ch = getopt(argc, argv, "Pp")) != -1) {
+       *rflag = *pflag = 0;
+       while ((ch = getopt(argc, argv, "PpRr")) != -1) {
                switch (ch) {
                case 'p':
                case 'P':
                        *pflag = 1;
                        break;
                switch (ch) {
                case 'p':
                case 'P':
                        *pflag = 1;
                        break;
+               case 'r':
+               case 'R':
+                       *rflag = 1;
+                       break;
                default:
                        error("%s: Invalid flag -%c", cmd, optopt);
                        return -1;
                default:
                        error("%s: Invalid flag -%c", cmd, optopt);
                        return -1;
@@ -397,7 +384,7 @@ parse_ls_flags(char **argv, int argc, int *lflag)
        opterr = 0;
 
        *lflag = LS_NAME_SORT;
        opterr = 0;
 
        *lflag = LS_NAME_SORT;
-       while ((ch = getopt(argc, argv, "1Saflnrt")) != -1) {
+       while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
                switch (ch) {
                case '1':
                        *lflag &= ~VIEW_FLAGS;
                switch (ch) {
                case '1':
                        *lflag &= ~VIEW_FLAGS;
@@ -413,12 +400,15 @@ parse_ls_flags(char **argv, int argc, int *lflag)
                case 'f':
                        *lflag &= ~SORT_FLAGS;
                        break;
                case 'f':
                        *lflag &= ~SORT_FLAGS;
                        break;
+               case 'h':
+                       *lflag |= LS_SI_UNITS;
+                       break;
                case 'l':
                case 'l':
-                       *lflag &= ~VIEW_FLAGS;
+                       *lflag &= ~LS_SHORT_VIEW;
                        *lflag |= LS_LONG_VIEW;
                        break;
                case 'n':
                        *lflag |= LS_LONG_VIEW;
                        break;
                case 'n':
-                       *lflag &= ~VIEW_FLAGS;
+                       *lflag &= ~LS_SHORT_VIEW;
                        *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
                        break;
                case 'r':
                        *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
                        break;
                case 'r':
@@ -489,62 +479,79 @@ remote_is_dir(struct sftp_conn *conn, char *path)
        return(S_ISDIR(a->perm));
 }
 
        return(S_ISDIR(a->perm));
 }
 
+/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
+static int
+pathname_is_dir(char *pathname)
+{
+       size_t l = strlen(pathname);
+
+       return l > 0 && pathname[l - 1] == '/';
+}
+
 static int
 static int
-process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
+process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
+    int pflag, int rflag)
 {
        char *abs_src = NULL;
        char *abs_dst = NULL;
 {
        char *abs_src = NULL;
        char *abs_dst = NULL;
-       char *tmp;
        glob_t g;
        glob_t g;
-       int err = 0;
-       int i;
+       char *filename, *tmp=NULL;
+       int i, err = 0;
 
        abs_src = xstrdup(src);
        abs_src = make_absolute(abs_src, pwd);
 
        abs_src = xstrdup(src);
        abs_src = make_absolute(abs_src, pwd);
-
        memset(&g, 0, sizeof(g));
        memset(&g, 0, sizeof(g));
+
        debug3("Looking up %s", abs_src);
        debug3("Looking up %s", abs_src);
-       if (remote_glob(conn, abs_src, 0, NULL, &g)) {
+       if (remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) {
                error("File \"%s\" not found.", abs_src);
                err = -1;
                goto out;
        }
 
                error("File \"%s\" not found.", abs_src);
                err = -1;
                goto out;
        }
 
-       /* If multiple matches, dst must be a directory or unspecified */
-       if (g.gl_matchc > 1 && dst && !is_dir(dst)) {
-               error("Multiple files match, but \"%s\" is not a directory",
-                   dst);
+       /*
+        * If multiple matches then dst must be a directory or
+        * unspecified.
+        */
+       if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
+               error("Multiple source paths, but destination "
+                   "\"%s\" is not a directory", dst);
                err = -1;
                goto out;
        }
 
        for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
                err = -1;
                goto out;
        }
 
        for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
-               if (infer_path(g.gl_pathv[i], &tmp)) {
+               tmp = xstrdup(g.gl_pathv[i]);
+               if ((filename = basename(tmp)) == NULL) {
+                       error("basename %s: %s", tmp, strerror(errno));
+                       xfree(tmp);
                        err = -1;
                        goto out;
                }
 
                if (g.gl_matchc == 1 && dst) {
                        err = -1;
                        goto out;
                }
 
                if (g.gl_matchc == 1 && dst) {
-                       /* If directory specified, append filename */
-                       xfree(tmp);
                        if (is_dir(dst)) {
                        if (is_dir(dst)) {
-                               if (infer_path(g.gl_pathv[0], &tmp)) {
-                                       err = 1;
-                                       goto out;
-                               }
-                               abs_dst = path_append(dst, tmp);
-                               xfree(tmp);
-                       } else
+                               abs_dst = path_append(dst, filename);
+                       } else {
                                abs_dst = xstrdup(dst);
                                abs_dst = xstrdup(dst);
+                       }
                } else if (dst) {
                } else if (dst) {
-                       abs_dst = path_append(dst, tmp);
-                       xfree(tmp);
-               } else
-                       abs_dst = tmp;
+                       abs_dst = path_append(dst, filename);
+               } else {
+                       abs_dst = xstrdup(filename);
+               }
+               xfree(tmp);
 
                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 (do_download(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
-                       err = -1;
+               if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
+                       if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL, 
+                           pflag || global_pflag, 1) == -1)
+                               err = -1;
+               } else {
+                       if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
+                           pflag || global_pflag) == -1)
+                               err = -1;
+               }
                xfree(abs_dst);
                abs_dst = NULL;
        }
                xfree(abs_dst);
                abs_dst = NULL;
        }
@@ -556,14 +563,15 @@ out:
 }
 
 static int
 }
 
 static int
-process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
+process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
+    int pflag, int rflag)
 {
        char *tmp_dst = NULL;
        char *abs_dst = NULL;
 {
        char *tmp_dst = NULL;
        char *abs_dst = NULL;
-       char *tmp;
+       char *tmp = NULL, *filename = NULL;
        glob_t g;
        int err = 0;
        glob_t g;
        int err = 0;
-       int i;
+       int i, dst_is_dir = 1;
        struct stat sb;
 
        if (dst) {
        struct stat sb;
 
        if (dst) {
@@ -573,16 +581,20 @@ process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
 
        memset(&g, 0, sizeof(g));
        debug3("Looking up %s", src);
 
        memset(&g, 0, sizeof(g));
        debug3("Looking up %s", src);
-       if (glob(src, GLOB_NOCHECK, NULL, &g)) {
+       if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
                error("File \"%s\" not found.", src);
                err = -1;
                goto out;
        }
 
                error("File \"%s\" not found.", src);
                err = -1;
                goto out;
        }
 
+       /* If we aren't fetching to pwd then stash this status for later */
+       if (tmp_dst != NULL)
+               dst_is_dir = remote_is_dir(conn, tmp_dst);
+
        /* If multiple matches, dst may be directory or unspecified */
        /* If multiple matches, dst may be directory or unspecified */
-       if (g.gl_matchc > 1 && tmp_dst && !remote_is_dir(conn, tmp_dst)) {
-               error("Multiple files match, but \"%s\" is not a directory",
-                   tmp_dst);
+       if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
+               error("Multiple paths match, but destination "
+                   "\"%s\" is not a directory", tmp_dst);
                err = -1;
                goto out;
        }
                err = -1;
                goto out;
        }
@@ -593,38 +605,38 @@ process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
                        error("stat %s: %s", g.gl_pathv[i], strerror(errno));
                        continue;
                }
                        error("stat %s: %s", g.gl_pathv[i], strerror(errno));
                        continue;
                }
-
-               if (!S_ISREG(sb.st_mode)) {
-                       error("skipping non-regular file %s",
-                           g.gl_pathv[i]);
-                       continue;
-               }
-               if (infer_path(g.gl_pathv[i], &tmp)) {
+               
+               tmp = xstrdup(g.gl_pathv[i]);
+               if ((filename = basename(tmp)) == NULL) {
+                       error("basename %s: %s", tmp, strerror(errno));
+                       xfree(tmp);
                        err = -1;
                        goto out;
                }
 
                if (g.gl_matchc == 1 && tmp_dst) {
                        /* If directory specified, append filename */
                        err = -1;
                        goto out;
                }
 
                if (g.gl_matchc == 1 && tmp_dst) {
                        /* If directory specified, append filename */
-                       if (remote_is_dir(conn, tmp_dst)) {
-                               if (infer_path(g.gl_pathv[0], &tmp)) {
-                                       err = 1;
-                                       goto out;
-                               }
-                               abs_dst = path_append(tmp_dst, tmp);
-                               xfree(tmp);
-                       } else
+                       if (dst_is_dir)
+                               abs_dst = path_append(tmp_dst, filename);
+                       else
                                abs_dst = xstrdup(tmp_dst);
                                abs_dst = xstrdup(tmp_dst);
-
                } else if (tmp_dst) {
                } else if (tmp_dst) {
-                       abs_dst = path_append(tmp_dst, tmp);
-                       xfree(tmp);
-               } else
-                       abs_dst = make_absolute(tmp, pwd);
+                       abs_dst = path_append(tmp_dst, filename);
+               } else {
+                       abs_dst = make_absolute(xstrdup(filename), pwd);
+               }
+               xfree(tmp);
 
                printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
 
                printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
-               if (do_upload(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
-                       err = -1;
+               if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
+                       if (upload_dir(conn, g.gl_pathv[i], abs_dst,
+                           pflag || global_pflag, 1) == -1)
+                               err = -1;
+               } else {
+                       if (do_upload(conn, g.gl_pathv[i], abs_dst,
+                           pflag || global_pflag) == -1)
+                               err = -1;
+               }
        }
 
 out:
        }
 
 out:
@@ -708,13 +720,14 @@ do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
                xfree(tmp);
 
                if (lflag & LS_LONG_VIEW) {
                xfree(tmp);
 
                if (lflag & LS_LONG_VIEW) {
-                       if (lflag & LS_NUMERIC_VIEW) {
+                       if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
                                char *lname;
                                struct stat sb;
 
                                memset(&sb, 0, sizeof(sb));
                                attrib_to_stat(&d[n]->a, &sb);
                                char *lname;
                                struct stat sb;
 
                                memset(&sb, 0, sizeof(sb));
                                attrib_to_stat(&d[n]->a, &sb);
-                               lname = ls_file(fname, &sb, 1);
+                               lname = ls_file(fname, &sb, 1,
+                                   (lflag & LS_SI_UNITS));
                                printf("%s\n", lname);
                                xfree(lname);
                        } else
                                printf("%s\n", lname);
                                xfree(lname);
                        } else
@@ -816,7 +829,7 @@ do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
                                a = do_lstat(conn, g.gl_pathv[i], 1);
                        if (a != NULL)
                                attrib_to_stat(a, &sb);
                                a = do_lstat(conn, g.gl_pathv[i], 1);
                        if (a != NULL)
                                attrib_to_stat(a, &sb);
-                       lname = ls_file(fname, &sb, 1);
+                       lname = ls_file(fname, &sb, 1, (lflag & LS_SI_UNITS));
                        printf("%s\n", lname);
                        xfree(lname);
                } else {
                        printf("%s\n", lname);
                        xfree(lname);
                } else {
@@ -848,19 +861,19 @@ do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
        char s_avail[FMT_SCALED_STRSIZE];
        char s_root[FMT_SCALED_STRSIZE];
        char s_total[FMT_SCALED_STRSIZE];
        char s_avail[FMT_SCALED_STRSIZE];
        char s_root[FMT_SCALED_STRSIZE];
        char s_total[FMT_SCALED_STRSIZE];
+       unsigned long long ffree;
 
        if (do_statvfs(conn, path, &st, 1) == -1)
                return -1;
        if (iflag) {
 
        if (do_statvfs(conn, path, &st, 1) == -1)
                return -1;
        if (iflag) {
+               ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
                printf("     Inodes        Used       Avail      "
                    "(root)    %%Capacity\n");
                printf("%11llu %11llu %11llu %11llu         %3llu%%\n",
                    (unsigned long long)st.f_files,
                    (unsigned long long)(st.f_files - st.f_ffree),
                    (unsigned long long)st.f_favail,
                printf("     Inodes        Used       Avail      "
                    "(root)    %%Capacity\n");
                printf("%11llu %11llu %11llu %11llu         %3llu%%\n",
                    (unsigned long long)st.f_files,
                    (unsigned long long)(st.f_files - st.f_ffree),
                    (unsigned long long)st.f_favail,
-                   (unsigned long long)st.f_ffree,
-                   (unsigned long long)(100 * (st.f_files - st.f_ffree) /
-                   st.f_files));
+                   (unsigned long long)st.f_ffree, ffree);
        } else if (hflag) {
                strlcpy(s_used, "error", sizeof(s_used));
                strlcpy(s_avail, "error", sizeof(s_avail));
        } else if (hflag) {
                strlcpy(s_used, "error", sizeof(s_used));
                strlcpy(s_avail, "error", sizeof(s_avail));
@@ -934,12 +947,23 @@ undo_glob_escape(char *s)
  * Split a string into an argument vector using sh(1)-style quoting,
  * comment and escaping rules, but with some tweaks to handle glob(3)
  * wildcards.
  * Split a string into an argument vector using sh(1)-style quoting,
  * comment and escaping rules, but with some tweaks to handle glob(3)
  * wildcards.
+ * The "sloppy" flag allows for recovery from missing terminating quote, for
+ * use in parsing incomplete commandlines during tab autocompletion.
+ *
  * Returns NULL on error or a NULL-terminated array of arguments.
  * Returns NULL on error or a NULL-terminated array of arguments.
+ *
+ * If "lastquote" is not NULL, the quoting character used for the last
+ * argument is placed in *lastquote ("\0", "'" or "\"").
+ * 
+ * If "terminated" is not NULL, *terminated will be set to 1 when the
+ * last argument's quote has been properly terminated or 0 otherwise.
+ * This parameter is only of use if "sloppy" is set.
  */
 #define MAXARGS        128
 #define MAXARGLEN      8192
 static char **
  */
 #define MAXARGS        128
 #define MAXARGLEN      8192
 static char **
-makeargv(const char *arg, int *argcp)
+makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
+    u_int *terminated)
 {
        int argc, quot;
        size_t i, j;
 {
        int argc, quot;
        size_t i, j;
@@ -953,6 +977,10 @@ makeargv(const char *arg, int *argcp)
                error("string too long");
                return NULL;
        }
                error("string too long");
                return NULL;
        }
+       if (terminated != NULL)
+               *terminated = 1;
+       if (lastquote != NULL)
+               *lastquote = '\0';
        state = MA_START;
        i = j = 0;
        for (;;) {
        state = MA_START;
        i = j = 0;
        for (;;) {
@@ -969,6 +997,8 @@ makeargv(const char *arg, int *argcp)
                        if (state == MA_START) {
                                argv[argc] = argvs + j;
                                state = q;
                        if (state == MA_START) {
                                argv[argc] = argvs + j;
                                state = q;
+                               if (lastquote != NULL)
+                                       *lastquote = arg[i];
                        } else if (state == MA_UNQUOTED) 
                                state = q;
                        else if (state == q)
                        } else if (state == MA_UNQUOTED) 
                                state = q;
                        else if (state == q)
@@ -1005,6 +1035,8 @@ makeargv(const char *arg, int *argcp)
                                if (state == MA_START) {
                                        argv[argc] = argvs + j;
                                        state = MA_UNQUOTED;
                                if (state == MA_START) {
                                        argv[argc] = argvs + j;
                                        state = MA_UNQUOTED;
+                                       if (lastquote != NULL)
+                                               *lastquote = '\0';
                                }
                                if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
                                    arg[i + 1] == '*' || arg[i + 1] == '\\') {
                                }
                                if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
                                    arg[i + 1] == '*' || arg[i + 1] == '\\') {
@@ -1030,6 +1062,12 @@ makeargv(const char *arg, int *argcp)
                                goto string_done;
                } else if (arg[i] == '\0') {
                        if (state == MA_SQUOTE || state == MA_DQUOTE) {
                                goto string_done;
                } else if (arg[i] == '\0') {
                        if (state == MA_SQUOTE || state == MA_DQUOTE) {
+                               if (sloppy) {
+                                       state = MA_UNQUOTED;
+                                       if (terminated != NULL)
+                                               *terminated = 0;
+                                       goto string_done;
+                               }
                                error("Unterminated quoted argument");
                                return NULL;
                        }
                                error("Unterminated quoted argument");
                                return NULL;
                        }
@@ -1043,6 +1081,8 @@ makeargv(const char *arg, int *argcp)
                        if (state == MA_START) {
                                argv[argc] = argvs + j;
                                state = MA_UNQUOTED;
                        if (state == MA_START) {
                                argv[argc] = argvs + j;
                                state = MA_UNQUOTED;
+                               if (lastquote != NULL)
+                                       *lastquote = '\0';
                        }
                        if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
                            (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
                        }
                        if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
                            (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
@@ -1065,8 +1105,8 @@ makeargv(const char *arg, int *argcp)
 }
 
 static int
 }
 
 static int
-parse_args(const char **cpp, int *pflag, int *lflag, int *iflag, int *hflag,
-    unsigned long *n_arg, char **path1, char **path2)
+parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag,
+    int *hflag, unsigned long *n_arg, char **path1, char **path2)
 {
        const char *cmd, *cp = *cpp;
        char *cp2, **argv;
 {
        const char *cmd, *cp = *cpp;
        char *cp2, **argv;
@@ -1077,18 +1117,19 @@ parse_args(const char **cpp, int *pflag, int *lflag, int *iflag, int *hflag,
        /* Skip leading whitespace */
        cp = cp + strspn(cp, WHITESPACE);
 
        /* Skip leading whitespace */
        cp = cp + strspn(cp, WHITESPACE);
 
-       /* Ignore blank lines and lines which begin with comment '#' char */
-       if (*cp == '\0' || *cp == '#')
-               return (0);
-
        /* Check for leading '-' (disable error processing) */
        *iflag = 0;
        if (*cp == '-') {
                *iflag = 1;
                cp++;
        /* Check for leading '-' (disable error processing) */
        *iflag = 0;
        if (*cp == '-') {
                *iflag = 1;
                cp++;
+               cp = cp + strspn(cp, WHITESPACE);
        }
 
        }
 
-       if ((argv = makeargv(cp, &argc)) == NULL)
+       /* Ignore blank lines and lines which begin with comment '#' char */
+       if (*cp == '\0' || *cp == '#')
+               return (0);
+
+       if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
                return -1;
 
        /* Figure out which command we have */
                return -1;
 
        /* Figure out which command we have */
@@ -1109,13 +1150,13 @@ parse_args(const char **cpp, int *pflag, int *lflag, int *iflag, int *hflag,
        }
 
        /* Get arguments and parse flags */
        }
 
        /* Get arguments and parse flags */
-       *lflag = *pflag = *hflag = *n_arg = 0;
+       *lflag = *pflag = *rflag = *hflag = *n_arg = 0;
        *path1 = *path2 = NULL;
        optidx = 1;
        switch (cmdnum) {
        case I_GET:
        case I_PUT:
        *path1 = *path2 = NULL;
        optidx = 1;
        switch (cmdnum) {
        case I_GET:
        case I_PUT:
-               if ((optidx = parse_getput_flags(cmd, argv, argc, pflag)) == -1)
+               if ((optidx = parse_getput_flags(cmd, argv, argc, pflag, rflag)) == -1)
                        return -1;
                /* Get first pathname (mandatory) */
                if (argc - optidx < 1) {
                        return -1;
                /* Get first pathname (mandatory) */
                if (argc - optidx < 1) {
@@ -1235,7 +1276,7 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
     int err_abort)
 {
        char *path1, *path2, *tmp;
     int err_abort)
 {
        char *path1, *path2, *tmp;
-       int pflag = 0, lflag = 0, iflag = 0, hflag = 0, cmdnum, i;
+       int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, cmdnum, i;
        unsigned long n_arg = 0;
        Attrib a, *aa;
        char path_buf[MAXPATHLEN];
        unsigned long n_arg = 0;
        Attrib a, *aa;
        char path_buf[MAXPATHLEN];
@@ -1243,7 +1284,7 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
        glob_t g;
 
        path1 = path2 = NULL;
        glob_t g;
 
        path1 = path2 = NULL;
-       cmdnum = parse_args(&cmd, &pflag, &lflag, &iflag, &hflag, &n_arg,
+       cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag, &n_arg,
            &path1, &path2);
 
        if (iflag != 0)
            &path1, &path2);
 
        if (iflag != 0)
@@ -1261,10 +1302,10 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
                err = -1;
                break;
        case I_GET:
                err = -1;
                break;
        case I_GET:
-               err = process_get(conn, path1, path2, *pwd, pflag);
+               err = process_get(conn, path1, path2, *pwd, pflag, rflag);
                break;
        case I_PUT:
                break;
        case I_PUT:
-               err = process_put(conn, path1, path2, *pwd, pflag);
+               err = process_put(conn, path1, path2, *pwd, pflag, rflag);
                break;
        case I_RENAME:
                path1 = make_absolute(path1, *pwd);
                break;
        case I_RENAME:
                path1 = make_absolute(path1, *pwd);
@@ -1290,7 +1331,7 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
                attrib_clear(&a);
                a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
                a.perm = 0777;
                attrib_clear(&a);
                a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
                a.perm = 0777;
-               err = do_mkdir(conn, path1, &a);
+               err = do_mkdir(conn, path1, &a, 1);
                break;
        case I_RMDIR:
                path1 = make_absolute(path1, *pwd);
                break;
        case I_RMDIR:
                path1 = make_absolute(path1, *pwd);
@@ -1468,21 +1509,352 @@ prompt(EditLine *el)
 {
        return ("sftp> ");
 }
 {
        return ("sftp> ");
 }
-#endif
+
+/* Display entries in 'list' after skipping the first 'len' chars */
+static void
+complete_display(char **list, u_int len)
+{
+       u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
+       struct winsize ws;
+       char *tmp;
+
+       /* Count entries for sort and find longest */
+       for (y = 0; list[y]; y++) 
+               m = MAX(m, strlen(list[y]));
+
+       if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
+               width = ws.ws_col;
+
+       m = m > len ? m - len : 0;
+       columns = width / (m + 2);
+       columns = MAX(columns, 1);
+       colspace = width / columns;
+       colspace = MIN(colspace, width);
+
+       printf("\n");
+       m = 1;
+       for (y = 0; list[y]; y++) {
+               llen = strlen(list[y]);
+               tmp = llen > len ? list[y] + len : "";
+               printf("%-*s", colspace, tmp);
+               if (m >= columns) {
+                       printf("\n");
+                       m = 1;
+               } else
+                       m++;
+       }
+       printf("\n");
+}
+
+/*
+ * Given a "list" of words that begin with a common prefix of "word",
+ * attempt to find an autocompletion to extends "word" by the next
+ * characters common to all entries in "list".
+ */
+static char *
+complete_ambiguous(const char *word, char **list, size_t count)
+{
+       if (word == NULL)
+               return NULL;
+
+       if (count > 0) {
+               u_int y, matchlen = strlen(list[0]);
+
+               /* Find length of common stem */
+               for (y = 1; list[y]; y++) {
+                       u_int x;
+
+                       for (x = 0; x < matchlen; x++) 
+                               if (list[0][x] != list[y][x]) 
+                                       break;
+
+                       matchlen = x;
+               }
+
+               if (matchlen > strlen(word)) {
+                       char *tmp = xstrdup(list[0]);
+
+                       tmp[matchlen] = '\0';
+                       return tmp;
+               }
+       } 
+
+       return xstrdup(word);
+}
+
+/* Autocomplete a sftp command */
+static int
+complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
+    int terminated)
+{
+       u_int y, count = 0, cmdlen, tmplen;
+       char *tmp, **list, argterm[3];
+       const LineInfo *lf;
+
+       list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
+
+       /* No command specified: display all available commands */
+       if (cmd == NULL) {
+               for (y = 0; cmds[y].c; y++)
+                       list[count++] = xstrdup(cmds[y].c);
+               
+               list[count] = NULL;
+               complete_display(list, 0);
+
+               for (y = 0; list[y] != NULL; y++)  
+                       xfree(list[y]); 
+               xfree(list);
+               return count;
+       }
+
+       /* Prepare subset of commands that start with "cmd" */
+       cmdlen = strlen(cmd);
+       for (y = 0; cmds[y].c; y++)  {
+               if (!strncasecmp(cmd, cmds[y].c, cmdlen)) 
+                       list[count++] = xstrdup(cmds[y].c);
+       }
+       list[count] = NULL;
+
+       if (count == 0)
+               return 0;
+
+       /* Complete ambigious command */
+       tmp = complete_ambiguous(cmd, list, count);
+       if (count > 1)
+               complete_display(list, 0);
+
+       for (y = 0; list[y]; y++)  
+               xfree(list[y]); 
+       xfree(list);
+
+       if (tmp != NULL) {
+               tmplen = strlen(tmp);
+               cmdlen = strlen(cmd);
+               /* If cmd may be extended then do so */
+               if (tmplen > cmdlen)
+                       if (el_insertstr(el, tmp + cmdlen) == -1)
+                               fatal("el_insertstr failed.");
+               lf = el_line(el);
+               /* Terminate argument cleanly */
+               if (count == 1) {
+                       y = 0;
+                       if (!terminated)
+                               argterm[y++] = quote;
+                       if (lastarg || *(lf->cursor) != ' ')
+                               argterm[y++] = ' ';
+                       argterm[y] = '\0';
+                       if (y > 0 && el_insertstr(el, argterm) == -1)
+                               fatal("el_insertstr failed.");
+               }
+               xfree(tmp);
+       }
+
+       return count;
+}
+
+/*
+ * Determine whether a particular sftp command's arguments (if any)
+ * represent local or remote files.
+ */
+static int
+complete_is_remote(char *cmd) {
+       int i;
+
+       if (cmd == NULL)
+               return -1;
+
+       for (i = 0; cmds[i].c; i++) {
+               if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c))) 
+                       return cmds[i].t;
+       }
+
+       return -1;
+}
+
+/* Autocomplete a filename "file" */
+static int
+complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
+    char *file, int remote, int lastarg, char quote, int terminated)
+{
+       glob_t g;
+       char *tmp, *tmp2, ins[3];
+       u_int i, hadglob, pwdlen, len, tmplen, filelen;
+       const LineInfo *lf;
+       
+       /* Glob from "file" location */
+       if (file == NULL)
+               tmp = xstrdup("*");
+       else
+               xasprintf(&tmp, "%s*", file);
+
+       memset(&g, 0, sizeof(g));
+       if (remote != LOCAL) {
+               tmp = make_absolute(tmp, remote_path);
+               remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
+       } else 
+               glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
+       
+       /* Determine length of pwd so we can trim completion display */
+       for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
+               /* Terminate counting on first unescaped glob metacharacter */
+               if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
+                       if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
+                               hadglob = 1;
+                       break;
+               }
+               if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
+                       tmplen++;
+               if (tmp[tmplen] == '/')
+                       pwdlen = tmplen + 1;    /* track last seen '/' */
+       }
+       xfree(tmp);
+
+       if (g.gl_matchc == 0) 
+               goto out;
+
+       if (g.gl_matchc > 1)
+               complete_display(g.gl_pathv, pwdlen);
+
+       tmp = NULL;
+       /* Don't try to extend globs */
+       if (file == NULL || hadglob)
+               goto out;
+
+       tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
+       tmp = path_strip(tmp2, remote_path);
+       xfree(tmp2);
+
+       if (tmp == NULL)
+               goto out;
+
+       tmplen = strlen(tmp);
+       filelen = strlen(file);
+
+       if (tmplen > filelen)  {
+               tmp2 = tmp + filelen;
+               len = strlen(tmp2); 
+               /* quote argument on way out */
+               for (i = 0; i < len; i++) {
+                       ins[0] = '\\';
+                       ins[1] = tmp2[i];
+                       ins[2] = '\0';
+                       switch (tmp2[i]) {
+                       case '\'':
+                       case '"':
+                       case '\\':
+                       case '\t':
+                       case ' ':
+                               if (quote == '\0' || tmp2[i] == quote) {
+                                       if (el_insertstr(el, ins) == -1)
+                                               fatal("el_insertstr "
+                                                   "failed.");
+                                       break;
+                               }
+                               /* FALLTHROUGH */
+                       default:
+                               if (el_insertstr(el, ins + 1) == -1)
+                                       fatal("el_insertstr failed.");
+                               break;
+                       }
+               }
+       }
+
+       lf = el_line(el);
+       if (g.gl_matchc == 1) {
+               i = 0;
+               if (!terminated)
+                       ins[i++] = quote;
+               if (*(lf->cursor - 1) != '/' &&
+                   (lastarg || *(lf->cursor) != ' '))
+                       ins[i++] = ' ';
+               ins[i] = '\0';
+               if (i > 0 && el_insertstr(el, ins) == -1)
+                       fatal("el_insertstr failed.");
+       }
+       xfree(tmp);
+
+ out:
+       globfree(&g);
+       return g.gl_matchc;
+}
+
+/* tab-completion hook function, called via libedit */
+static unsigned char
+complete(EditLine *el, int ch)
+{
+       char **argv, *line, quote; 
+       u_int argc, carg, cursor, len, terminated, ret = CC_ERROR;
+       const LineInfo *lf;
+       struct complete_ctx *complete_ctx;
+
+       lf = el_line(el);
+       if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
+               fatal("%s: el_get failed", __func__);
+
+       /* Figure out which argument the cursor points to */
+       cursor = lf->cursor - lf->buffer;
+       line = (char *)xmalloc(cursor + 1);
+       memcpy(line, lf->buffer, cursor);
+       line[cursor] = '\0';
+       argv = makeargv(line, &carg, 1, &quote, &terminated);
+       xfree(line);
+
+       /* Get all the arguments on the line */
+       len = lf->lastchar - lf->buffer;
+       line = (char *)xmalloc(len + 1);
+       memcpy(line, lf->buffer, len);
+       line[len] = '\0';
+       argv = makeargv(line, &argc, 1, NULL, NULL);
+
+       /* Ensure cursor is at EOL or a argument boundary */
+       if (line[cursor] != ' ' && line[cursor] != '\0' &&
+           line[cursor] != '\n') {
+               xfree(line);
+               return ret;
+       }
+
+       if (carg == 0) {
+               /* Show all available commands */
+               complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
+               ret = CC_REDISPLAY;
+       } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ')  {
+               /* Handle the command parsing */
+               if (complete_cmd_parse(el, argv[0], argc == carg,
+                   quote, terminated) != 0) 
+                       ret = CC_REDISPLAY;
+       } else if (carg >= 1) {
+               /* Handle file parsing */
+               int remote = complete_is_remote(argv[0]);
+               char *filematch = NULL;
+
+               if (carg > 1 && line[cursor-1] != ' ')
+                       filematch = argv[carg - 1];
+
+               if (remote != 0 &&
+                   complete_match(el, complete_ctx->conn,
+                   *complete_ctx->remote_pathp, filematch,
+                   remote, carg == argc, quote, terminated) != 0) 
+                       ret = CC_REDISPLAY;
+       }
+
+       xfree(line);    
+       return ret;
+}
+#endif /* USE_LIBEDIT */
 
 int
 
 int
-interactive_loop(int fd_in, int fd_out, char *file1, char *file2)
+interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
 {
 {
-       char *pwd;
+       char *remote_path;
        char *dir = NULL;
        char cmd[2048];
        char *dir = NULL;
        char cmd[2048];
-       struct sftp_conn *conn;
        int err, interactive;
        EditLine *el = NULL;
 #ifdef USE_LIBEDIT
        History *hl = NULL;
        HistEvent hev;
        extern char *__progname;
        int err, interactive;
        EditLine *el = NULL;
 #ifdef USE_LIBEDIT
        History *hl = NULL;
        HistEvent hev;
        extern char *__progname;
+       struct complete_ctx complete_ctx;
 
        if (!batchmode && isatty(STDIN_FILENO)) {
                if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
 
        if (!batchmode && isatty(STDIN_FILENO)) {
                if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
@@ -1497,27 +1869,32 @@ interactive_loop(int fd_in, int fd_out, char *file1, char *file2)
                el_set(el, EL_TERMINAL, NULL);
                el_set(el, EL_SIGNAL, 1);
                el_source(el, NULL);
                el_set(el, EL_TERMINAL, NULL);
                el_set(el, EL_SIGNAL, 1);
                el_source(el, NULL);
+
+               /* Tab Completion */
+               el_set(el, EL_ADDFN, "ftp-complete", 
+                   "Context senstive argument completion", complete);
+               complete_ctx.conn = conn;
+               complete_ctx.remote_pathp = &remote_path;
+               el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
+               el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
        }
 #endif /* USE_LIBEDIT */
 
        }
 #endif /* USE_LIBEDIT */
 
-       conn = do_init(fd_in, fd_out, copy_buffer_len, num_requests);
-       if (conn == NULL)
-               fatal("Couldn't initialise connection to server");
-
-       pwd = do_realpath(conn, ".");
-       if (pwd == NULL)
+       remote_path = do_realpath(conn, ".");
+       if (remote_path == NULL)
                fatal("Need cwd");
 
        if (file1 != NULL) {
                dir = xstrdup(file1);
                fatal("Need cwd");
 
        if (file1 != NULL) {
                dir = xstrdup(file1);
-               dir = make_absolute(dir, pwd);
+               dir = make_absolute(dir, remote_path);
 
                if (remote_is_dir(conn, dir) && file2 == NULL) {
                        printf("Changing to: %s\n", dir);
                        snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
 
                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,
+                           &remote_path, 1) != 0) {
                                xfree(dir);
                                xfree(dir);
-                               xfree(pwd);
+                               xfree(remote_path);
                                xfree(conn);
                                return (-1);
                        }
                                xfree(conn);
                                return (-1);
                        }
@@ -1528,9 +1905,10 @@ interactive_loop(int fd_in, int fd_out, char *file1, char *file2)
                                snprintf(cmd, sizeof cmd, "get %s %s", dir,
                                    file2);
 
                                snprintf(cmd, sizeof cmd, "get %s %s", dir,
                                    file2);
 
-                       err = parse_dispatch_command(conn, cmd, &pwd, 1);
+                       err = parse_dispatch_command(conn, cmd,
+                           &remote_path, 1);
                        xfree(dir);
                        xfree(dir);
-                       xfree(pwd);
+                       xfree(remote_path);
                        xfree(conn);
                        return (err);
                }
                        xfree(conn);
                        return (err);
                }
@@ -1571,7 +1949,8 @@ interactive_loop(int fd_in, int fd_out, char *file1, char *file2)
                        const char *line;
                        int count = 0;
 
                        const char *line;
                        int count = 0;
 
-                       if ((line = el_gets(el, &count)) == NULL || count <= 0) {
+                       if ((line = el_gets(el, &count)) == NULL ||
+                           count <= 0) {
                                printf("\n");
                                break;
                        }
                                printf("\n");
                                break;
                        }
@@ -1591,11 +1970,12 @@ interactive_loop(int fd_in, int fd_out, char *file1, char *file2)
                interrupted = 0;
                signal(SIGINT, cmd_interrupt);
 
                interrupted = 0;
                signal(SIGINT, cmd_interrupt);
 
-               err = parse_dispatch_command(conn, cmd, &pwd, batchmode);
+               err = parse_dispatch_command(conn, cmd, &remote_path,
+                   batchmode);
                if (err != 0)
                        break;
        }
                if (err != 0)
                        break;
        }
-       xfree(pwd);
+       xfree(remote_path);
        xfree(conn);
 
 #ifdef USE_LIBEDIT
        xfree(conn);
 
 #ifdef USE_LIBEDIT
@@ -1647,9 +2027,11 @@ connect_to_server(char *path, char **args, int *in, int *out)
                 * 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
                 * 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
+                * kill it too.  Contrawise, since sftp sends SIGTERMs to the
+                * underlying ssh, it must *not* ignore that signal.
                 */
                signal(SIGINT, SIG_IGN);
                 */
                signal(SIGINT, SIG_IGN);
+               signal(SIGTERM, SIG_DFL);
                execvp(path, args);
                fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
                _exit(1);
                execvp(path, args);
                fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
                _exit(1);
@@ -1668,7 +2050,7 @@ usage(void)
        extern char *__progname;
 
        fprintf(stderr,
        extern char *__progname;
 
        fprintf(stderr,
-           "usage: %s [-1246Cqv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
+           "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
            "          [-D sftp_server_path] [-F ssh_config] "
            "[-i identity_file]\n"
            "          [-o ssh_option] [-P port] [-R num_requests] "
            "          [-D sftp_server_path] [-F ssh_config] "
            "[-i identity_file]\n"
            "          [-o ssh_option] [-P port] [-R num_requests] "
@@ -1685,7 +2067,7 @@ int
 main(int argc, char **argv)
 {
        int in, out, ch, err;
 main(int argc, char **argv)
 {
        int in, out, ch, err;
-       char *host, *userhost, *cp, *file2 = NULL;
+       char *host = NULL, *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;
        int debug_level = 0, sshver = 2;
        char *file1 = NULL, *sftp_server = NULL;
        char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
@@ -1693,6 +2075,9 @@ main(int argc, char **argv)
        arglist args;
        extern int optind;
        extern char *optarg;
        arglist args;
        extern int optind;
        extern char *optarg;
+       struct sftp_conn *conn;
+       size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
+       size_t num_requests = DEFAULT_NUM_REQUESTS;
 
        /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
        sanitise_stdfd();
 
        /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
        sanitise_stdfd();
@@ -1710,7 +2095,7 @@ main(int argc, char **argv)
        infile = stdin;
 
        while ((ch = getopt(argc, argv,
        infile = stdin;
 
        while ((ch = getopt(argc, argv,
-           "1246hqvCc:D:i:o:s:S:b:B:F:P:R:")) != -1) {
+           "1246hqrvCc:D:i:o:s:S:b:B:F:P:R:")) != -1) {
                switch (ch) {
                /* Passed through to ssh(1) */
                case '4':
                switch (ch) {
                /* Passed through to ssh(1) */
                case '4':
@@ -1723,7 +2108,8 @@ main(int argc, char **argv)
                case 'c':
                case 'i':
                case 'o':
                case 'c':
                case 'i':
                case 'o':
-                       addargs(&args, "-%c%s", ch, optarg);
+                       addargs(&args, "-%c", ch);
+                       addargs(&args, "%s", optarg);
                        break;
                case 'q':
                        showprogress = 0;
                        break;
                case 'q':
                        showprogress = 0;
@@ -1764,9 +2150,15 @@ main(int argc, char **argv)
                        batchmode = 1;
                        addargs(&args, "-obatchmode yes");
                        break;
                        batchmode = 1;
                        addargs(&args, "-obatchmode yes");
                        break;
+               case 'p':
+                       global_pflag = 1;
+                       break;
                case 'D':
                        sftp_direct = optarg;
                        break;
                case 'D':
                        sftp_direct = optarg;
                        break;
+               case 'r':
+                       global_rflag = 1;
+                       break;
                case 'R':
                        num_requests = strtol(optarg, &cp, 10);
                        if (num_requests == 0 || *cp != '\0')
                case 'R':
                        num_requests = strtol(optarg, &cp, 10);
                        if (num_requests == 0 || *cp != '\0')
@@ -1806,7 +2198,8 @@ main(int argc, char **argv)
                                fprintf(stderr, "Missing username\n");
                                usage();
                        }
                                fprintf(stderr, "Missing username\n");
                                usage();
                        }
-                       addargs(&args, "-l%s", userhost);
+                       addargs(&args, "-l");
+                       addargs(&args, "%s", userhost);
                }
 
                if ((cp = colon(host)) != NULL) {
                }
 
                if ((cp = colon(host)) != NULL) {
@@ -1826,24 +2219,32 @@ main(int argc, char **argv)
                if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
                        addargs(&args, "-s");
 
                if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
                        addargs(&args, "-s");
 
+               addargs(&args, "--");
                addargs(&args, "%s", host);
                addargs(&args, "%s", (sftp_server != NULL ?
                    sftp_server : "sftp"));
 
                addargs(&args, "%s", host);
                addargs(&args, "%s", (sftp_server != NULL ?
                    sftp_server : "sftp"));
 
-               if (!batchmode)
-                       fprintf(stderr, "Connecting to %s...\n", host);
                connect_to_server(ssh_program, args.list, &in, &out);
        } else {
                args.list = NULL;
                addargs(&args, "sftp-server");
 
                connect_to_server(ssh_program, args.list, &in, &out);
        } else {
                args.list = NULL;
                addargs(&args, "sftp-server");
 
-               if (!batchmode)
-                       fprintf(stderr, "Attaching to %s...\n", sftp_direct);
                connect_to_server(sftp_direct, args.list, &in, &out);
        }
        freeargs(&args);
 
                connect_to_server(sftp_direct, args.list, &in, &out);
        }
        freeargs(&args);
 
-       err = interactive_loop(in, out, file1, file2);
+       conn = do_init(in, out, copy_buffer_len, num_requests);
+       if (conn == NULL)
+               fatal("Couldn't initialise connection to server");
+
+       if (!batchmode) {
+               if (sftp_direct == NULL)
+                       fprintf(stderr, "Connected to %s.\n", host);
+               else
+                       fprintf(stderr, "Attached to %s.\n", sftp_direct);
+       }
+
+       err = interactive_loop(conn, file1, file2);
 
 #if !defined(USE_PIPES)
        shutdown(in, SHUT_RDWR);
 
 #if !defined(USE_PIPES)
        shutdown(in, SHUT_RDWR);
This page took 0.176083 seconds and 4 git commands to generate.