+#ifdef HAVE_READLINE
+static char *complete_command(const char *text, int state) {
+ static int i;
+
+ if(!state)
+ i = 0;
+ else
+ i++;
+
+ while(commands[i].command) {
+ if(!commands[i].hidden && !strncasecmp(commands[i].command, text, strlen(text)))
+ return xstrdup(commands[i].command);
+ i++;
+ }
+
+ return NULL;
+}
+
+static char *complete_dump(const char *text, int state) {
+ const char *matches[] = {"reachable", "nodes", "edges", "subnets", "connections", "graph", NULL};
+ static int i;
+
+ if(!state)
+ i = 0;
+ else
+ i++;
+
+ while(matches[i]) {
+ if(!strncasecmp(matches[i], text, strlen(text)))
+ return xstrdup(matches[i]);
+ i++;
+ }
+
+ return NULL;
+}
+
+static char *complete_config(const char *text, int state) {
+ static int i;
+
+ if(!state)
+ i = 0;
+ else
+ i++;
+
+ while(variables[i].name) {
+ char *dot = strchr(text, '.');
+ if(dot) {
+ if((variables[i].type & VAR_HOST) && !strncasecmp(variables[i].name, dot + 1, strlen(dot + 1))) {
+ char *match;
+ xasprintf(&match, "%.*s.%s", (int)(dot - text), text, variables[i].name);
+ return match;
+ }
+ } else {
+ if(!strncasecmp(variables[i].name, text, strlen(text)))
+ return xstrdup(variables[i].name);
+ }
+ i++;
+ }
+
+ return NULL;
+}
+
+static char *complete_info(const char *text, int state) {
+ static int i;
+ if(!state) {
+ i = 0;
+ if(!connect_tincd(false))
+ return NULL;
+ // Check the list of nodes
+ sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES);
+ sendline(fd, "%d %d", CONTROL, REQ_DUMP_SUBNETS);
+ }
+
+ while(recvline(fd, line, sizeof line)) {
+ char item[4096];
+ int n = sscanf(line, "%d %d %s", &code, &req, item);
+ if(n == 2) {
+ i++;
+ if(i >= 2)
+ break;
+ else
+ continue;
+ }
+
+ if(n != 3) {
+ fprintf(stderr, "Unable to parse dump from tincd, n = %d, i = %d.\n", n, i);
+ break;
+ }
+
+ if(!strncmp(item, text, strlen(text)))
+ return xstrdup(strip_weight(item));
+ }
+
+ return NULL;
+}
+
+static char *complete_nothing(const char *text, int state) {
+ return NULL;
+}
+
+static char **completion (const char *text, int start, int end) {
+ char **matches = NULL;
+
+ if(!start)
+ matches = rl_completion_matches(text, complete_command);
+ else if(!strncasecmp(rl_line_buffer, "dump ", 5))
+ matches = rl_completion_matches(text, complete_dump);
+ else if(!strncasecmp(rl_line_buffer, "add ", 4))
+ matches = rl_completion_matches(text, complete_config);
+ else if(!strncasecmp(rl_line_buffer, "del ", 4))
+ matches = rl_completion_matches(text, complete_config);
+ else if(!strncasecmp(rl_line_buffer, "get ", 4))
+ matches = rl_completion_matches(text, complete_config);
+ else if(!strncasecmp(rl_line_buffer, "set ", 4))
+ matches = rl_completion_matches(text, complete_config);
+ else if(!strncasecmp(rl_line_buffer, "info ", 5))
+ matches = rl_completion_matches(text, complete_info);
+
+ return matches;
+}
+#endif
+
+static int cmd_shell(int argc, char *argv[]) {
+ xasprintf(&prompt, "%s> ", identname);
+ int result = 0;
+ char buf[4096];
+ char *line = NULL;
+ int maxargs = argc + 16;
+ char **nargv = xmalloc(maxargs * sizeof *nargv);
+
+ for(int i = 0; i < argc; i++)
+ nargv[i] = argv[i];
+
+#ifdef HAVE_READLINE
+ rl_readline_name = "tinc";
+ rl_completion_entry_function = complete_nothing;
+ rl_attempted_completion_function = completion;
+ rl_filename_completion_desired = 0;
+ char *copy = NULL;
+#endif
+
+ while(true) {
+#ifdef HAVE_READLINE
+ if(tty) {
+ free(copy);
+ free(line);
+ rl_basic_word_break_characters = "\t\n ";
+ line = readline(prompt);
+ if(line)
+ copy = xstrdup(line);
+ } else {
+ line = fgets(buf, sizeof buf, stdin);
+ }
+#else
+ if(tty)
+ fputs(prompt, stdout);
+
+ line = fgets(buf, sizeof buf, stdin);
+#endif
+
+ if(!line)
+ break;
+
+ /* Ignore comments */
+
+ if(*line == '#')
+ continue;
+
+ /* Split */
+
+ int nargc = argc;
+ char *p = line + strspn(line, " \t\n");
+ char *next = strtok(p, " \t\n");
+
+ while(p && *p) {
+ if(nargc >= maxargs) {
+ fprintf(stderr, "next %p '%s', p %p '%s'\n", next, next, p, p);
+ abort();
+ maxargs *= 2;
+ nargv = xrealloc(nargv, maxargs * sizeof *nargv);
+ }
+
+ nargv[nargc++] = p;
+ p = next;
+ next = strtok(NULL, " \t\n");
+ }
+
+ if(nargc == argc)
+ continue;
+
+ if(!strcasecmp(nargv[argc], "exit") || !strcasecmp(nargv[argc], "quit"))
+ return result;
+
+ bool found = false;
+
+ for(int i = 0; commands[i].command; i++) {
+ if(!strcasecmp(nargv[argc], commands[i].command)) {
+ result |= commands[i].function(nargc - argc - 1, nargv + argc + 1);
+ found = true;
+ break;
+ }
+ }
+
+#ifdef HAVE_READLINE
+ if(tty && found)
+ add_history(copy);
+#endif
+
+ if(!found) {
+ fprintf(stderr, "Unknown command `%s'.\n", nargv[argc]);
+ result |= 1;
+ }
+ }
+
+ free(nargv);
+
+ if(tty)
+ printf("\n");
+ return result;
+}
+
+