summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDarren Tucker <dtucker@zip.com.au>2010-01-08 19:02:40 +1100
committerDarren Tucker <dtucker@zip.com.au>2010-01-08 19:02:40 +1100
commit909d858d6b86c789003b809a636c2c228d3f30c5 (patch)
tree16e97da15727ad115afc97bba02f636f81d37915
parent0c348f5b9eb4ac9f763cb3f55a252d86e2cfc57b (diff)
- djm@cvs.openbsd.org 2010/01/04 02:03:57
[sftp.c] Implement tab-completion of commands, local and remote filenames for sftp. Hacked on and off for some time by myself, mouring, Carlos Silva (via 2009 Google Summer of Code) and polished to a fine sheen by myself again. It should deal more-or-less correctly with the ikky corner-cases presented by quoted filenames, but the UI could still be slightly improved. In particular, it is quite slow for remote completion on large directories. bz#200; ok markus@
-rw-r--r--ChangeLog9
-rw-r--r--sftp.c484
2 files changed, 443 insertions, 50 deletions
diff --git a/ChangeLog b/ChangeLog
index ac3ca7b02..70e3c15e9 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -141,6 +141,15 @@
141 [sshconnect2.c] 141 [sshconnect2.c]
142 Don't escape backslashes in the SSH2 banner. bz#1533, patch from 142 Don't escape backslashes in the SSH2 banner. bz#1533, patch from
143 Michal Gorny via Gentoo. 143 Michal Gorny via Gentoo.
144 - djm@cvs.openbsd.org 2010/01/04 02:03:57
145 [sftp.c]
146 Implement tab-completion of commands, local and remote filenames for sftp.
147 Hacked on and off for some time by myself, mouring, Carlos Silva (via 2009
148 Google Summer of Code) and polished to a fine sheen by myself again.
149 It should deal more-or-less correctly with the ikky corner-cases presented
150 by quoted filenames, but the UI could still be slightly improved.
151 In particular, it is quite slow for remote completion on large directories.
152 bz#200; ok markus@
144 153
14520091226 15420091226
146 - (tim) [contrib/cygwin/Makefile] Install ssh-copy-id and ssh-copy-id.1 155 - (tim) [contrib/cygwin/Makefile] Install ssh-copy-id and ssh-copy-id.1
diff --git a/sftp.c b/sftp.c
index d8728cc25..6a5ccc49d 100644
--- a/sftp.c
+++ b/sftp.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: sftp.c,v 1.115 2009/12/20 07:28:36 guenther Exp $ */ 1/* $OpenBSD: sftp.c,v 1.116 2010/01/04 02:03:57 djm Exp $ */
2/* 2/*
3 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> 3 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
4 * 4 *
@@ -95,6 +95,12 @@ volatile sig_atomic_t interrupted = 0;
95/* I wish qsort() took a separate ctx for the comparison function...*/ 95/* I wish qsort() took a separate ctx for the comparison function...*/
96int sort_flag; 96int sort_flag;
97 97
98/* Context used for commandline completion */
99struct complete_ctx {
100 struct sftp_conn *conn;
101 char **remote_pathp;
102};
103
98int remote_glob(struct sftp_conn *, const char *, int, 104int remote_glob(struct sftp_conn *, const char *, int,
99 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */ 105 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
100 106
@@ -145,43 +151,47 @@ extern char *__progname;
145struct CMD { 151struct CMD {
146 const char *c; 152 const char *c;
147 const int n; 153 const int n;
154 const int t;
148}; 155};
149 156
157/* Type of completion */
158#define NOARGS 0
159#define REMOTE 1
160#define LOCAL 2
161
150static const struct CMD cmds[] = { 162static const struct CMD cmds[] = {
151 { "bye", I_QUIT }, 163 { "bye", I_QUIT, NOARGS },
152 { "cd", I_CHDIR }, 164 { "cd", I_CHDIR, REMOTE },
153 { "chdir", I_CHDIR }, 165 { "chdir", I_CHDIR, REMOTE },
154 { "chgrp", I_CHGRP }, 166 { "chgrp", I_CHGRP, REMOTE },
155 { "chmod", I_CHMOD }, 167 { "chmod", I_CHMOD, REMOTE },
156 { "chown", I_CHOWN }, 168 { "chown", I_CHOWN, REMOTE },
157 { "df", I_DF }, 169 { "df", I_DF, REMOTE },
158 { "dir", I_LS }, 170 { "dir", I_LS, REMOTE },
159 { "exit", I_QUIT }, 171 { "exit", I_QUIT, NOARGS },
160 { "get", I_GET }, 172 { "get", I_GET, REMOTE },
161 { "mget", I_GET }, 173 { "help", I_HELP, NOARGS },
162 { "help", I_HELP }, 174 { "lcd", I_LCHDIR, LOCAL },
163 { "lcd", I_LCHDIR }, 175 { "lchdir", I_LCHDIR, LOCAL },
164 { "lchdir", I_LCHDIR }, 176 { "lls", I_LLS, LOCAL },
165 { "lls", I_LLS }, 177 { "lmkdir", I_LMKDIR, LOCAL },
166 { "lmkdir", I_LMKDIR }, 178 { "ln", I_SYMLINK, REMOTE },
167 { "ln", I_SYMLINK }, 179 { "lpwd", I_LPWD, LOCAL },
168 { "lpwd", I_LPWD }, 180 { "ls", I_LS, REMOTE },
169 { "ls", I_LS }, 181 { "lumask", I_LUMASK, NOARGS },
170 { "lumask", I_LUMASK }, 182 { "mkdir", I_MKDIR, REMOTE },
171 { "mkdir", I_MKDIR }, 183 { "progress", I_PROGRESS, NOARGS },
172 { "progress", I_PROGRESS }, 184 { "put", I_PUT, LOCAL },
173 { "put", I_PUT }, 185 { "pwd", I_PWD, REMOTE },
174 { "mput", I_PUT }, 186 { "quit", I_QUIT, NOARGS },
175 { "pwd", I_PWD }, 187 { "rename", I_RENAME, REMOTE },
176 { "quit", I_QUIT }, 188 { "rm", I_RM, REMOTE },
177 { "rename", I_RENAME }, 189 { "rmdir", I_RMDIR, REMOTE },
178 { "rm", I_RM }, 190 { "symlink", I_SYMLINK, REMOTE },
179 { "rmdir", I_RMDIR }, 191 { "version", I_VERSION, NOARGS },
180 { "symlink", I_SYMLINK }, 192 { "!", I_SHELL, NOARGS },
181 { "version", I_VERSION }, 193 { "?", I_HELP, NOARGS },
182 { "!", I_SHELL }, 194 { NULL, -1, -1 }
183 { "?", I_HELP },
184 { NULL, -1}
185}; 195};
186 196
187int interactive_loop(struct sftp_conn *, char *file1, char *file2); 197int interactive_loop(struct sftp_conn *, char *file1, char *file2);
@@ -932,12 +942,23 @@ undo_glob_escape(char *s)
932 * Split a string into an argument vector using sh(1)-style quoting, 942 * Split a string into an argument vector using sh(1)-style quoting,
933 * comment and escaping rules, but with some tweaks to handle glob(3) 943 * comment and escaping rules, but with some tweaks to handle glob(3)
934 * wildcards. 944 * wildcards.
945 * The "sloppy" flag allows for recovery from missing terminating quote, for
946 * use in parsing incomplete commandlines during tab autocompletion.
947 *
935 * Returns NULL on error or a NULL-terminated array of arguments. 948 * Returns NULL on error or a NULL-terminated array of arguments.
949 *
950 * If "lastquote" is not NULL, the quoting character used for the last
951 * argument is placed in *lastquote ("\0", "'" or "\"").
952 *
953 * If "terminated" is not NULL, *terminated will be set to 1 when the
954 * last argument's quote has been properly terminated or 0 otherwise.
955 * This parameter is only of use if "sloppy" is set.
936 */ 956 */
937#define MAXARGS 128 957#define MAXARGS 128
938#define MAXARGLEN 8192 958#define MAXARGLEN 8192
939static char ** 959static char **
940makeargv(const char *arg, int *argcp) 960makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
961 u_int *terminated)
941{ 962{
942 int argc, quot; 963 int argc, quot;
943 size_t i, j; 964 size_t i, j;
@@ -951,6 +972,10 @@ makeargv(const char *arg, int *argcp)
951 error("string too long"); 972 error("string too long");
952 return NULL; 973 return NULL;
953 } 974 }
975 if (terminated != NULL)
976 *terminated = 1;
977 if (lastquote != NULL)
978 *lastquote = '\0';
954 state = MA_START; 979 state = MA_START;
955 i = j = 0; 980 i = j = 0;
956 for (;;) { 981 for (;;) {
@@ -967,6 +992,8 @@ makeargv(const char *arg, int *argcp)
967 if (state == MA_START) { 992 if (state == MA_START) {
968 argv[argc] = argvs + j; 993 argv[argc] = argvs + j;
969 state = q; 994 state = q;
995 if (lastquote != NULL)
996 *lastquote = arg[i];
970 } else if (state == MA_UNQUOTED) 997 } else if (state == MA_UNQUOTED)
971 state = q; 998 state = q;
972 else if (state == q) 999 else if (state == q)
@@ -1003,6 +1030,8 @@ makeargv(const char *arg, int *argcp)
1003 if (state == MA_START) { 1030 if (state == MA_START) {
1004 argv[argc] = argvs + j; 1031 argv[argc] = argvs + j;
1005 state = MA_UNQUOTED; 1032 state = MA_UNQUOTED;
1033 if (lastquote != NULL)
1034 *lastquote = '\0';
1006 } 1035 }
1007 if (arg[i + 1] == '?' || arg[i + 1] == '[' || 1036 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1008 arg[i + 1] == '*' || arg[i + 1] == '\\') { 1037 arg[i + 1] == '*' || arg[i + 1] == '\\') {
@@ -1028,6 +1057,12 @@ makeargv(const char *arg, int *argcp)
1028 goto string_done; 1057 goto string_done;
1029 } else if (arg[i] == '\0') { 1058 } else if (arg[i] == '\0') {
1030 if (state == MA_SQUOTE || state == MA_DQUOTE) { 1059 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1060 if (sloppy) {
1061 state = MA_UNQUOTED;
1062 if (terminated != NULL)
1063 *terminated = 0;
1064 goto string_done;
1065 }
1031 error("Unterminated quoted argument"); 1066 error("Unterminated quoted argument");
1032 return NULL; 1067 return NULL;
1033 } 1068 }
@@ -1041,6 +1076,8 @@ makeargv(const char *arg, int *argcp)
1041 if (state == MA_START) { 1076 if (state == MA_START) {
1042 argv[argc] = argvs + j; 1077 argv[argc] = argvs + j;
1043 state = MA_UNQUOTED; 1078 state = MA_UNQUOTED;
1079 if (lastquote != NULL)
1080 *lastquote = '\0';
1044 } 1081 }
1045 if ((state == MA_SQUOTE || state == MA_DQUOTE) && 1082 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1046 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) { 1083 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
@@ -1063,8 +1100,8 @@ makeargv(const char *arg, int *argcp)
1063} 1100}
1064 1101
1065static int 1102static int
1066parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag, int *hflag, 1103parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag,
1067 unsigned long *n_arg, char **path1, char **path2) 1104 int *hflag, unsigned long *n_arg, char **path1, char **path2)
1068{ 1105{
1069 const char *cmd, *cp = *cpp; 1106 const char *cmd, *cp = *cpp;
1070 char *cp2, **argv; 1107 char *cp2, **argv;
@@ -1086,7 +1123,7 @@ parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag, int
1086 cp++; 1123 cp++;
1087 } 1124 }
1088 1125
1089 if ((argv = makeargv(cp, &argc)) == NULL) 1126 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
1090 return -1; 1127 return -1;
1091 1128
1092 /* Figure out which command we have */ 1129 /* Figure out which command we have */
@@ -1468,10 +1505,344 @@ prompt(EditLine *el)
1468} 1505}
1469#endif 1506#endif
1470 1507
1508/* Display entries in 'list' after skipping the first 'len' chars */
1509static void
1510complete_display(char **list, u_int len)
1511{
1512 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1513 struct winsize ws;
1514 char *tmp;
1515
1516 /* Count entries for sort and find longest */
1517 for (y = 0; list[y]; y++)
1518 m = MAX(m, strlen(list[y]));
1519
1520 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1521 width = ws.ws_col;
1522
1523 m = m > len ? m - len : 0;
1524 columns = width / (m + 2);
1525 columns = MAX(columns, 1);
1526 colspace = width / columns;
1527 colspace = MIN(colspace, width);
1528
1529 printf("\n");
1530 m = 1;
1531 for (y = 0; list[y]; y++) {
1532 llen = strlen(list[y]);
1533 tmp = llen > len ? list[y] + len : "";
1534 printf("%-*s", colspace, tmp);
1535 if (m >= columns) {
1536 printf("\n");
1537 m = 1;
1538 } else
1539 m++;
1540 }
1541 printf("\n");
1542}
1543
1544/*
1545 * Given a "list" of words that begin with a common prefix of "word",
1546 * attempt to find an autocompletion to extends "word" by the next
1547 * characters common to all entries in "list".
1548 */
1549static char *
1550complete_ambiguous(const char *word, char **list, size_t count)
1551{
1552 if (word == NULL)
1553 return NULL;
1554
1555 if (count > 0) {
1556 u_int y, matchlen = strlen(list[0]);
1557
1558 /* Find length of common stem */
1559 for (y = 1; list[y]; y++) {
1560 u_int x;
1561
1562 for (x = 0; x < matchlen; x++)
1563 if (list[0][x] != list[y][x])
1564 break;
1565
1566 matchlen = x;
1567 }
1568
1569 if (matchlen > strlen(word)) {
1570 char *tmp = xstrdup(list[0]);
1571
1572 tmp[matchlen] = NULL;
1573 return tmp;
1574 }
1575 }
1576
1577 return xstrdup(word);
1578}
1579
1580/* Autocomplete a sftp command */
1581static int
1582complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1583 int terminated)
1584{
1585 u_int y, count = 0, cmdlen, tmplen;
1586 char *tmp, **list, argterm[3];
1587 const LineInfo *lf;
1588
1589 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1590
1591 /* No command specified: display all available commands */
1592 if (cmd == NULL) {
1593 for (y = 0; cmds[y].c; y++)
1594 list[count++] = xstrdup(cmds[y].c);
1595
1596 list[count] = NULL;
1597 complete_display(list, 0);
1598
1599 for (y = 0; list[y] != NULL; y++)
1600 xfree(list[y]);
1601 xfree(list);
1602 return count;
1603 }
1604
1605 /* Prepare subset of commands that start with "cmd" */
1606 cmdlen = strlen(cmd);
1607 for (y = 0; cmds[y].c; y++) {
1608 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
1609 list[count++] = xstrdup(cmds[y].c);
1610 }
1611 list[count] = NULL;
1612
1613 if (count == 0)
1614 return 0;
1615
1616 /* Complete ambigious command */
1617 tmp = complete_ambiguous(cmd, list, count);
1618 if (count > 1)
1619 complete_display(list, 0);
1620
1621 for (y = 0; list[y]; y++)
1622 xfree(list[y]);
1623 xfree(list);
1624
1625 if (tmp != NULL) {
1626 tmplen = strlen(tmp);
1627 cmdlen = strlen(cmd);
1628 /* If cmd may be extended then do so */
1629 if (tmplen > cmdlen)
1630 if (el_insertstr(el, tmp + cmdlen) == -1)
1631 fatal("el_insertstr failed.");
1632 lf = el_line(el);
1633 /* Terminate argument cleanly */
1634 if (count == 1) {
1635 y = 0;
1636 if (!terminated)
1637 argterm[y++] = quote;
1638 if (lastarg || *(lf->cursor) != ' ')
1639 argterm[y++] = ' ';
1640 argterm[y] = '\0';
1641 if (y > 0 && el_insertstr(el, argterm) == -1)
1642 fatal("el_insertstr failed.");
1643 }
1644 xfree(tmp);
1645 }
1646
1647 return count;
1648}
1649
1650/*
1651 * Determine whether a particular sftp command's arguments (if any)
1652 * represent local or remote files.
1653 */
1654static int
1655complete_is_remote(char *cmd) {
1656 int i;
1657
1658 if (cmd == NULL)
1659 return -1;
1660
1661 for (i = 0; cmds[i].c; i++) {
1662 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
1663 return cmds[i].t;
1664 }
1665
1666 return -1;
1667}
1668
1669/* Autocomplete a filename "file" */
1670static int
1671complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1672 char *file, int remote, int lastarg, char quote, int terminated)
1673{
1674 glob_t g;
1675 char *tmp, *tmp2, ins[3];
1676 u_int i, hadglob, pwdlen, len, tmplen, filelen;
1677 const LineInfo *lf;
1678
1679 /* Glob from "file" location */
1680 if (file == NULL)
1681 tmp = xstrdup("*");
1682 else
1683 xasprintf(&tmp, "%s*", file);
1684
1685 memset(&g, 0, sizeof(g));
1686 if (remote != LOCAL) {
1687 tmp = make_absolute(tmp, remote_path);
1688 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1689 } else
1690 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1691
1692 /* Determine length of pwd so we can trim completion display */
1693 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1694 /* Terminate counting on first unescaped glob metacharacter */
1695 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1696 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1697 hadglob = 1;
1698 break;
1699 }
1700 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1701 tmplen++;
1702 if (tmp[tmplen] == '/')
1703 pwdlen = tmplen + 1; /* track last seen '/' */
1704 }
1705 xfree(tmp);
1706
1707 if (g.gl_matchc == 0)
1708 goto out;
1709
1710 if (g.gl_matchc > 1)
1711 complete_display(g.gl_pathv, pwdlen);
1712
1713 tmp = NULL;
1714 /* Don't try to extend globs */
1715 if (file == NULL || hadglob)
1716 goto out;
1717
1718 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
1719 tmp = path_strip(tmp2, remote_path);
1720 xfree(tmp2);
1721
1722 if (tmp == NULL)
1723 goto out;
1724
1725 tmplen = strlen(tmp);
1726 filelen = strlen(file);
1727
1728 if (tmplen > filelen) {
1729 tmp2 = tmp + filelen;
1730 len = strlen(tmp2);
1731 /* quote argument on way out */
1732 for (i = 0; i < len; i++) {
1733 ins[0] = '\\';
1734 ins[1] = tmp2[i];
1735 ins[2] = '\0';
1736 switch (tmp2[i]) {
1737 case '\'':
1738 case '"':
1739 case '\\':
1740 case '\t':
1741 case ' ':
1742 if (quote == '\0' || tmp2[i] == quote) {
1743 if (el_insertstr(el, ins) == -1)
1744 fatal("el_insertstr "
1745 "failed.");
1746 break;
1747 }
1748 /* FALLTHROUGH */
1749 default:
1750 if (el_insertstr(el, ins + 1) == -1)
1751 fatal("el_insertstr failed.");
1752 break;
1753 }
1754 }
1755 }
1756
1757 lf = el_line(el);
1758 /*
1759 * XXX should we really extend here? the user may not be done if
1760 * the filename is a directory.
1761 */
1762 if (g.gl_matchc == 1) {
1763 i = 0;
1764 if (!terminated)
1765 ins[i++] = quote;
1766 if (lastarg || *(lf->cursor) != ' ')
1767 ins[i++] = ' ';
1768 ins[i] = '\0';
1769 if (i > 0 && el_insertstr(el, ins) == -1)
1770 fatal("el_insertstr failed.");
1771 }
1772 xfree(tmp);
1773
1774 out:
1775 globfree(&g);
1776 return g.gl_matchc;
1777}
1778
1779/* tab-completion hook function, called via libedit */
1780static unsigned char
1781complete(EditLine *el, int ch)
1782{
1783 char **argv, *line, quote;
1784 u_int argc, carg, cursor, len, terminated, ret = CC_ERROR;
1785 const LineInfo *lf;
1786 struct complete_ctx *complete_ctx;
1787
1788 lf = el_line(el);
1789 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1790 fatal("%s: el_get failed", __func__);
1791
1792 /* Figure out which argument the cursor points to */
1793 cursor = lf->cursor - lf->buffer;
1794 line = (char *)xmalloc(cursor + 1);
1795 memcpy(line, lf->buffer, cursor);
1796 line[cursor] = '\0';
1797 argv = makeargv(line, &carg, 1, &quote, &terminated);
1798 xfree(line);
1799
1800 /* Get all the arguments on the line */
1801 len = lf->lastchar - lf->buffer;
1802 line = (char *)xmalloc(len + 1);
1803 memcpy(line, lf->buffer, len);
1804 line[len] = '\0';
1805 argv = makeargv(line, &argc, 1, NULL, NULL);
1806
1807 /* Ensure cursor is at EOL or a argument boundary */
1808 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1809 line[cursor] != '\n') {
1810 xfree(line);
1811 return ret;
1812 }
1813
1814 if (carg == 0) {
1815 /* Show all available commands */
1816 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1817 ret = CC_REDISPLAY;
1818 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1819 /* Handle the command parsing */
1820 if (complete_cmd_parse(el, argv[0], argc == carg,
1821 quote, terminated) != 0)
1822 ret = CC_REDISPLAY;
1823 } else if (carg >= 1) {
1824 /* Handle file parsing */
1825 int remote = complete_is_remote(argv[0]);
1826 char *filematch = NULL;
1827
1828 if (carg > 1 && line[cursor-1] != ' ')
1829 filematch = argv[carg - 1];
1830
1831 if (remote != 0 &&
1832 complete_match(el, complete_ctx->conn,
1833 *complete_ctx->remote_pathp, filematch,
1834 remote, carg == argc, quote, terminated) != 0)
1835 ret = CC_REDISPLAY;
1836 }
1837
1838 xfree(line);
1839 return ret;
1840}
1841
1471int 1842int
1472interactive_loop(struct sftp_conn *conn, char *file1, char *file2) 1843interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
1473{ 1844{
1474 char *pwd; 1845 char *remote_path;
1475 char *dir = NULL; 1846 char *dir = NULL;
1476 char cmd[2048]; 1847 char cmd[2048];
1477 int err, interactive; 1848 int err, interactive;
@@ -1480,6 +1851,7 @@ interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
1480 History *hl = NULL; 1851 History *hl = NULL;
1481 HistEvent hev; 1852 HistEvent hev;
1482 extern char *__progname; 1853 extern char *__progname;
1854 struct complete_ctx complete_ctx;
1483 1855
1484 if (!batchmode && isatty(STDIN_FILENO)) { 1856 if (!batchmode && isatty(STDIN_FILENO)) {
1485 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL) 1857 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
@@ -1494,23 +1866,32 @@ interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
1494 el_set(el, EL_TERMINAL, NULL); 1866 el_set(el, EL_TERMINAL, NULL);
1495 el_set(el, EL_SIGNAL, 1); 1867 el_set(el, EL_SIGNAL, 1);
1496 el_source(el, NULL); 1868 el_source(el, NULL);
1869
1870 /* Tab Completion */
1871 el_set(el, EL_ADDFN, "ftp-complete",
1872 "Context senstive argument completion", complete);
1873 complete_ctx.conn = conn;
1874 complete_ctx.remote_pathp = &remote_path;
1875 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
1876 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
1497 } 1877 }
1498#endif /* USE_LIBEDIT */ 1878#endif /* USE_LIBEDIT */
1499 1879
1500 pwd = do_realpath(conn, "."); 1880 remote_path = do_realpath(conn, ".");
1501 if (pwd == NULL) 1881 if (remote_path == NULL)
1502 fatal("Need cwd"); 1882 fatal("Need cwd");
1503 1883
1504 if (file1 != NULL) { 1884 if (file1 != NULL) {
1505 dir = xstrdup(file1); 1885 dir = xstrdup(file1);
1506 dir = make_absolute(dir, pwd); 1886 dir = make_absolute(dir, remote_path);
1507 1887
1508 if (remote_is_dir(conn, dir) && file2 == NULL) { 1888 if (remote_is_dir(conn, dir) && file2 == NULL) {
1509 printf("Changing to: %s\n", dir); 1889 printf("Changing to: %s\n", dir);
1510 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir); 1890 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
1511 if (parse_dispatch_command(conn, cmd, &pwd, 1) != 0) { 1891 if (parse_dispatch_command(conn, cmd,
1892 &remote_path, 1) != 0) {
1512 xfree(dir); 1893 xfree(dir);
1513 xfree(pwd); 1894 xfree(remote_path);
1514 xfree(conn); 1895 xfree(conn);
1515 return (-1); 1896 return (-1);
1516 } 1897 }
@@ -1521,9 +1902,10 @@ interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
1521 snprintf(cmd, sizeof cmd, "get %s %s", dir, 1902 snprintf(cmd, sizeof cmd, "get %s %s", dir,
1522 file2); 1903 file2);
1523 1904
1524 err = parse_dispatch_command(conn, cmd, &pwd, 1); 1905 err = parse_dispatch_command(conn, cmd,
1906 &remote_path, 1);
1525 xfree(dir); 1907 xfree(dir);
1526 xfree(pwd); 1908 xfree(remote_path);
1527 xfree(conn); 1909 xfree(conn);
1528 return (err); 1910 return (err);
1529 } 1911 }
@@ -1564,7 +1946,8 @@ interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
1564 const char *line; 1946 const char *line;
1565 int count = 0; 1947 int count = 0;
1566 1948
1567 if ((line = el_gets(el, &count)) == NULL || count <= 0) { 1949 if ((line = el_gets(el, &count)) == NULL ||
1950 count <= 0) {
1568 printf("\n"); 1951 printf("\n");
1569 break; 1952 break;
1570 } 1953 }
@@ -1584,11 +1967,12 @@ interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
1584 interrupted = 0; 1967 interrupted = 0;
1585 signal(SIGINT, cmd_interrupt); 1968 signal(SIGINT, cmd_interrupt);
1586 1969
1587 err = parse_dispatch_command(conn, cmd, &pwd, batchmode); 1970 err = parse_dispatch_command(conn, cmd, &remote_path,
1971 batchmode);
1588 if (err != 0) 1972 if (err != 0)
1589 break; 1973 break;
1590 } 1974 }
1591 xfree(pwd); 1975 xfree(remote_path);
1592 xfree(conn); 1976 xfree(conn);
1593 1977
1594#ifdef USE_LIBEDIT 1978#ifdef USE_LIBEDIT