diff options
-rw-r--r-- | ChangeLog | 9 | ||||
-rw-r--r-- | sftp.c | 484 |
2 files changed, 443 insertions, 50 deletions
@@ -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 | ||
145 | 20091226 | 154 | 20091226 |
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 |
@@ -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...*/ |
96 | int sort_flag; | 96 | int sort_flag; |
97 | 97 | ||
98 | /* Context used for commandline completion */ | ||
99 | struct complete_ctx { | ||
100 | struct sftp_conn *conn; | ||
101 | char **remote_pathp; | ||
102 | }; | ||
103 | |||
98 | int remote_glob(struct sftp_conn *, const char *, int, | 104 | int 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; | |||
145 | struct CMD { | 151 | struct 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 | |||
150 | static const struct CMD cmds[] = { | 162 | static 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 | ||
187 | int interactive_loop(struct sftp_conn *, char *file1, char *file2); | 197 | int 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 |
939 | static char ** | 959 | static char ** |
940 | makeargv(const char *arg, int *argcp) | 960 | makeargv(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 | ||
1065 | static int | 1102 | static int |
1066 | parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag, int *hflag, | 1103 | parse_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 */ | ||
1509 | static void | ||
1510 | complete_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 | */ | ||
1549 | static char * | ||
1550 | complete_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 */ | ||
1581 | static int | ||
1582 | complete_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 | */ | ||
1654 | static int | ||
1655 | complete_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" */ | ||
1670 | static int | ||
1671 | complete_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 */ | ||
1780 | static unsigned char | ||
1781 | complete(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, "e, &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 | |||
1471 | int | 1842 | int |
1472 | interactive_loop(struct sftp_conn *conn, char *file1, char *file2) | 1843 | interactive_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 |