Date: Sat, 26 Nov 2005 13:30:00 +0000 From: Nuno Antunes <nuno.antunes@gmail.com> To: Gleb Smirnoff <glebius@FreeBSD.org> Cc: freebsd-bugs@FreeBSD.org Subject: Re: bin/87352: [PATCH] Add line edit and history support to ngctl(8) via libedit Message-ID: <43886358.7090009@gmail.com> In-Reply-To: <200511151459.jAFEx6OY047069@freefall.freebsd.org> References: <200511151459.jAFEx6OY047069@freefall.freebsd.org>
next in thread | previous in thread | raw e-mail | index | archive | help
This is a multi-part message in MIME format. --------------090005030307040400050300 Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit Gleb Smirnoff wrote: > Synopsis: [PATCH] Add line edit and history support to ngctl(8) via libedit > > State-Changed-From-To: open->suspended > State-Changed-By: glebius > State-Changed-When: Tue Nov 15 14:58:26 GMT 2005 > State-Changed-Why: > Submitter promises to resubmit an updated version of patch. > > > Responsible-Changed-From-To: freebsd-bugs->glebius > Responsible-Changed-By: glebius > Responsible-Changed-When: Tue Nov 15 14:58:26 GMT 2005 > Responsible-Changed-Why: > I like this plan, so I will handle the PR. > > http://www.freebsd.org/cgi/query-pr.cgi?pr=87352 > Hi Gleb, This is what I have. Please find the patches attached. I would prefer that the command line could be redisplayed automatically after data arrived on the data socket, but I did not find a way to do that. Instead, I am relying on the predefined libedit ^R emacs binding that redisplays the complete command line. I also include a patch for the manpage documenting this. Please let me know what you think. Thanks, Nuno Antunes --------------090005030307040400050300 Content-Type: text/plain; name="Makefile.patch.4" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="Makefile.patch.4" --- usr.sbin/ngctl/Makefile.orig Mon Jan 26 10:27:18 2004 +++ usr.sbin/ngctl/Makefile Sun Nov 20 14:58:39 2005 @@ -7,6 +7,6 @@ msg.c debug.c shutdown.c rmhook.c status.c types.c write.c WARNS?= 3 DPADD= ${LIBNETGRAPH} -LDADD= -lnetgraph +LDADD= -lnetgraph -ledit -ltermcap -lpthread .include <bsd.prog.mk> --------------090005030307040400050300 Content-Type: text/plain; name="main.c.patch.4" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="main.c.patch.4" --- usr.sbin/ngctl/main.c.orig Fri Feb 4 20:09:11 2005 +++ usr.sbin/ngctl/main.c Tue Nov 22 22:49:10 2005 @@ -49,13 +49,15 @@ static int ReadFile(FILE *fp); static int DoParseCommand(char *line); static int DoCommand(int ac, char **av); -static int DoInteractive(void); +static void * DoInteractive(void *); +static void * DoMonitor(void *); static const struct ngcmd *FindCommand(const char *string); static int MatchCommand(const struct ngcmd *cmd, const char *s); static void Usage(const char *msg); static int ReadCmd(int ac, char **av); static int HelpCmd(int ac, char **av); static int QuitCmd(int ac, char **av); +static const char *GetPrompt(EditLine *); /* List of commands */ static const struct ngcmd *const cmds[] = { @@ -152,7 +154,46 @@ if (fp != NULL) { rtn = ReadFile(fp); } else if (interactive) { - rtn = DoInteractive(); + pthread_t interact; + pthread_t monitor; + struct thread_data td; + void *thread_rtn; + + /* Initialize libedit stuff */ + td.el = el_init(getprogname(), stdin, stdout, stderr); + if (td.el == NULL) + return CMDRTN_ERROR; + td.hist = history_init(); + if (td.hist != NULL) { + history(td.hist, &td.he, H_SETSIZE, 100); + history(td.hist, &td.he, H_SETUNIQUE, 1); + el_set(td.el, EL_HIST, history, td.hist); + } + + el_set(td.el, EL_PROMPT, GetPrompt); + el_set(td.el, EL_SIGNAL, 1); + el_set(td.el, EL_EDITOR, "emacs"); + el_source(td.el, NULL); + + /* + * Now we create two threads. The Interactive thread to + * handle the interactive stuff and the Monitor thread + * to print data as it arrives on the socket. + */ + pthread_create(&interact, NULL, DoInteractive, &td); + pthread_create(&monitor, NULL, DoMonitor, &td); + + /* Wait for the Interactive thread to finish */ + pthread_join(interact, &thread_rtn); + rtn = *(int *)thread_rtn; + + /* Terminate monitor thread */ + pthread_cancel(monitor); + + /* Destroy libedit stuff */ + el_end(td.el); + if (td.hist != NULL) + history_end(td.hist); } else Usage("no command specified"); } else { @@ -196,40 +237,65 @@ } /* - * Interactive mode + * Interactive mode thread */ -static int -DoInteractive(void) +static void * +DoInteractive(void *vp) { - const int maxfd = MAX(csock, dsock) + 1; + struct thread_data *td = (struct thread_data *)vp; + static int rtn; (*help_cmd.func)(0, NULL); while (1) { - struct timeval tv; - fd_set rfds; + char buf[LINE_MAX]; + const char *line = NULL; + int count = 0; + + if ((line = el_gets(td->el, &count)) != NULL) { + int len; + + strncpy(buf, line, LINE_MAX); + /* Truncate buf in case len(line) >= LINE_MAX */ + buf[LINE_MAX - 1] = '\0'; + len = strlen(buf); + if (buf[--len] == '\n') + buf[len] = '\0'; + if (len > 0) { + history(td->hist, &td->he, H_ENTER, buf); + if ((rtn = DoParseCommand(buf)) == CMDRTN_QUIT) + break; + } + } + } + + return(&rtn); +} + +/* + * Monitor thread + */ +static void * +DoMonitor(void *vp) +{ + const int maxfd = MAX(csock, dsock) + 1; + fd_set rfds; + while (1) { /* See if any data or control messages are arriving */ FD_ZERO(&rfds); FD_SET(csock, &rfds); FD_SET(dsock, &rfds); - memset(&tv, 0, sizeof(tv)); - if (select(maxfd, &rfds, NULL, NULL, &tv) <= 0) { - - /* Issue prompt and wait for anything to happen */ - printf("%s", PROMPT); - fflush(stdout); - FD_ZERO(&rfds); - FD_SET(0, &rfds); - FD_SET(csock, &rfds); - FD_SET(dsock, &rfds); - if (select(maxfd, &rfds, NULL, NULL, NULL) < 0) - err(EX_OSERR, "select"); - - /* If not user input, print a newline first */ - if (!FD_ISSET(0, &rfds)) - printf("\n"); +again: + if (select(maxfd, &rfds, NULL, NULL, NULL) < 0) { + if (errno == EINTR) /* Allow signals */ + goto again; + err(EX_OSERR, "select"); } + /* Print a newline before echoing anything */ + printf("\n"); + fflush(stdout); + /* Display any incoming control message */ if (FD_ISSET(csock, &rfds)) MsgRead(); @@ -251,20 +317,9 @@ DumpAscii(buf, rl); free(buf); } + } /* while */ - /* Get any user input */ - if (FD_ISSET(0, &rfds)) { - char buf[LINE_MAX]; - - if (fgets(buf, sizeof(buf), stdin) == NULL) { - printf("\n"); - break; - } - if (DoParseCommand(buf) == CMDRTN_QUIT) - break; - } - } - return(CMDRTN_QUIT); + return NULL; } /* @@ -493,6 +548,13 @@ } printf("%s\n", sbuf); } +} + +static const char * +GetPrompt(EditLine *el) +{ + (void) el; + return PROMPT; } /* --------------090005030307040400050300 Content-Type: text/plain; name="ngctl.8.patch.4" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="ngctl.8.patch.4" --- usr.sbin/ngctl/ngctl.8.orig Tue Nov 22 22:20:56 2005 +++ usr.sbin/ngctl/ngctl.8 Sat Nov 26 13:09:58 2005 @@ -124,6 +124,10 @@ .Dq help command displays the available commands, their usage and aliases, and a brief description. +.Pp +When in interactive mode, if a control message or new data is received and +displayed while you were typing a command, you can press ^R to redisplay your +command line. .Sh EXIT STATUS .Ex -std .Sh SEE ALSO --------------090005030307040400050300 Content-Type: text/plain; name="ngctl.h.patch.4" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="ngctl.h.patch.4" --- usr.sbin/ngctl/ngctl.h.orig Tue Nov 22 22:37:21 2005 +++ usr.sbin/ngctl/ngctl.h Tue Nov 22 22:38:31 2005 @@ -52,6 +52,8 @@ #include <ctype.h> #include <errno.h> #include <err.h> +#include <histedit.h> +#include <pthread.h> #include <netgraph.h> #include <netgraph/ng_socket.h> @@ -66,6 +68,13 @@ const char *desc; /* description */ const char *help; /* help text */ const char *aliases[MAX_CMD_ALIAS]; /* command aliases */ +}; + +/* Data to be passed to the threads. */ +struct thread_data { + EditLine *el; /* EditLine cookie */ + History *hist; /* History cookie */ + HistEvent he; /* History event */ }; /* Command return values */ --------------090005030307040400050300--
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?43886358.7090009>