Revisiting sftp tab completion patch
Ben Lindstrom
mouring at eviladmin.org
Wed Dec 12 15:22:38 EST 2007
I've finally took the time to figure the last few bugs (that I know of).
This patch will be submit to be included in a few weeks. This patch
should be generic enough for portable without too much hassle.
This patch mimics OpenBSD's ftp behavior. I'm not sure like that (e.g. it
doesn't put / at the end of directories by default), but that is more a
question for the community at large.
Yes I'm back on the list. =)
- Ben
Index: sftp.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/sftp.c,v
retrieving revision 1.97
diff -u -r1.97 sftp.c
--- sftp.c 24 Oct 2007 03:30:02 -0000 1.97
+++ sftp.c 12 Dec 2007 03:19:32 -0000
@@ -71,6 +71,12 @@
int remote_glob(struct sftp_conn *, const char *, int,
int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
+/* sftp connection structure */
+struct sftp_conn *conn;
+
+/* sftp remote path */
+char *remote_path;
+
/* Separators for interactive commands */
#define WHITESPACE " \t\r\n"
@@ -115,42 +121,49 @@
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[] = {
- { "bye", I_QUIT },
- { "cd", I_CHDIR },
- { "chdir", I_CHDIR },
- { "chgrp", I_CHGRP },
- { "chmod", I_CHMOD },
- { "chown", I_CHOWN },
- { "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 },
+ { "dir", I_LS, REMOTE },
+ { "exit", I_QUIT, NOARGS },
+ { "get", I_GET, REMOTE },
+ { "mget", 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 },
+ { "mput", 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);
@@ -1344,13 +1357,237 @@
return ("sftp> ");
}
+void
+complete_display(char **list, u_int len)
+{
+ u_int y, m = 0, width = 80, columns = 1, colspace = 0;
+ struct winsize ws;
+
+ /* Count entries for sort and find longest filename */
+ for (y = 0; list[y]; y++)
+ m = MAX(m, strlen(list[y]));
+
+ if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
+ width = ws.ws_col;
+
+ m -= len;
+ 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++) {
+ char *tmp = list[y];
+
+ tmp += len;
+ printf("%-*s", colspace, tmp);
+ if (m >= columns) {
+ printf("\n");
+ m = 1;
+ } else
+ m++;
+ }
+ printf("\n");
+}
+
+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]);
+
+ for (y = 1; list[y]; y++) {
+ 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] = NULL;
+ return (tmp);
+ }
+ }
+
+ return (xstrdup(word));
+}
+
+
+int
+complete_cmd_parse(EditLine *el, char *cmd)
+{
+ u_int y, count = 0, cmdlen;
+ char **list;
+
+ if (cmd == NULL)
+ return (0);
+
+ list = xcalloc((sizeof(cmds) / sizeof(*cmds)), sizeof(char *));
+ 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) {
+ char *tmp = complete_ambiguous(cmd, list, count);
+
+ if (count > 1)
+ complete_display(list, 0);
+
+ for (y = 1; list[y]; y++)
+ xfree(list[y]);
+ xfree(list);
+
+ if (tmp != NULL) {
+ if (strlen(tmp) > strlen(cmd))
+ if (el_insertstr(el, tmp + strlen(cmd)) == -1)
+ fatal("el_insertstr failed.");
+
+ xfree(tmp);
+ }
+ }
+
+ return (count);
+}
+
+int
+complete_is_remote(char *cmd) {
+ int i;
+
+ if (cmd == NULL)
+ return (-1);
+
+ for (i = 0; cmds[i].c; i++) {
+ size_t cmdlen = strlen(cmds[i].c);
+
+ if (!strncasecmp(cmd, cmds[i].c, cmdlen))
+ return cmds[i].t;
+ }
+
+ return (-1);
+}
+
+int
+complete_match(EditLine *el, char *file, int remote)
+{
+ glob_t g;
+ char *tmp, *tmp2, *pwd;
+ u_int len;
+
+ if (file == NULL)
+ return (0);
+
+ len = strlen(file) + 2; /* NULL + Wildcard */
+ tmp = xmalloc(len);
+ snprintf(tmp, len, "%s*", file);
+
+ memset(&g, 0, sizeof(g));
+ if (remote != LOCAL) {
+ tmp = make_absolute(tmp, remote_path);
+ remote_glob(conn, tmp, 0, NULL, &g);
+ } else
+ glob(tmp, GLOB_DOOFFS, NULL, &g);
+
+ xfree(tmp);
+
+ if (g.gl_matchc == 0)
+ return (0);
+
+ tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
+ tmp = path_strip(tmp2, remote_path);
+ xfree(tmp2);
+
+ if (g.gl_matchc > 1) {
+ char *pwd = strrchr(g.gl_pathv[0], '/');
+ u_int len = 0;
+
+ if (pwd != NULL)
+ len = strlen(g.gl_pathv[0]) - strlen(pwd) + 1;
+
+ complete_display(g.gl_pathv, len);
+ }
+
+ globfree(&g);
+ if (tmp != NULL) {
+ if (strlen(tmp) > strlen(file)) {
+ char *ap, *tmp2 = tmp + strlen(file);
+ u_int len = strlen(tmp2);
+
+ while ((ap = strsep(&tmp2, " ")) != NULL) {
+ if (strlen(ap) > 0) {
+ if (el_insertstr(el, ap) == -1)
+ fatal("el_insertstr failed.");
+ len -= strlen(ap);
+ }
+ if (len > 0) {
+ len--;
+ if (el_insertstr(el, "\\ ") == -1)
+ fatal("el_insertstr failed.");
+ }
+ }
+ }
+
+ xfree(tmp);
+ }
+
+ return (g.gl_matchc);
+}
+
+unsigned char
+complete(EditLine *el, int ch)
+{
+ char **argv, *line;
+ u_int x, argc, carg, len, ret = CC_ERROR;
+ const LineInfo *lf;
+
+ lf = el_line(el);
+
+ /* Figure out which argument we are on */
+ len = lf->cursor - lf->buffer + 1;
+ line = (char *)xmalloc(len);
+ strlcpy(line, lf->buffer, len);
+ argv = makeargv(line, &carg);
+ xfree(line);
+
+ /* now get the real argument */
+ len = lf->lastchar - lf->buffer + 1;
+ line = (char *)xmalloc(len);
+ strlcpy(line, lf->buffer, len);
+ argv = makeargv(line, &argc);
+ xfree(line);
+
+ if (carg == 1) { /* Handle the command parsing */
+ if (complete_cmd_parse(el, argv[0]) != 0)
+ ret = CC_REDISPLAY;
+ } else if (carg > 1) { /* Handle file parsing */
+ int remote = complete_is_remote(argv[0]);
+
+ if (remote != 0 && complete_match(el, argv[carg - 1],
+ remote) != 0)
+ ret = CC_REDISPLAY;
+ }
+
+ return (ret);
+}
+
int
interactive_loop(int fd_in, int fd_out, char *file1, char *file2)
{
- char *pwd;
char *dir = NULL;
char cmd[2048];
- struct sftp_conn *conn;
int err, interactive;
EditLine *el = NULL;
History *hl = NULL;
@@ -1370,26 +1607,31 @@
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);
+ el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
}
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);
- 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 (parse_dispatch_command(conn, cmd, &pwd, 1) != 0) {
+ if (parse_dispatch_command(conn, cmd, &remote_path, 1) != 0) {
xfree(dir);
- xfree(pwd);
+ xfree(remote_path);
xfree(conn);
return (-1);
}
@@ -1400,9 +1642,9 @@
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(pwd);
+ xfree(remote_path);
xfree(conn);
return (err);
}
@@ -1455,11 +1697,11 @@
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;
}
- xfree(pwd);
+ xfree(remote_path);
xfree(conn);
if (el != NULL)
More information about the openssh-unix-dev
mailing list