From owner-freebsd-bugs@FreeBSD.ORG Sat Nov 26 13:30:31 2005 Return-Path: X-Original-To: freebsd-bugs@freebsd.org Delivered-To: freebsd-bugs@freebsd.org Received: from mx1.FreeBSD.org (mx1.freebsd.org [216.136.204.125]) by hub.freebsd.org (Postfix) with ESMTP id 560D916A44B for ; Sat, 26 Nov 2005 13:30:31 +0000 (GMT) (envelope-from nuno.antunes@gmail.com) Received: from zproxy.gmail.com (zproxy.gmail.com [64.233.162.206]) by mx1.FreeBSD.org (Postfix) with ESMTP id 3CA7243D69 for ; Sat, 26 Nov 2005 13:30:14 +0000 (GMT) (envelope-from nuno.antunes@gmail.com) Received: by zproxy.gmail.com with SMTP id z31so661735nzd for ; Sat, 26 Nov 2005 05:30:13 -0800 (PST) DomainKey-Signature: a=rsa-sha1; q=dns; c=nofws; s=beta; d=gmail.com; h=received:message-id:date:from:user-agent:x-accept-language:mime-version:to:cc:subject:references:in-reply-to:content-type; b=Hgy9hXNKgfroC+WCoSHNLVdLA8d3EqtHjvJWLY/h6E489EGjr3Wx91v3US0A6/bLgcfPL9KX5v3jb4XX0qUSyE9ibriRlbE0FhPWq5xIW12uilvMhCwupN8sZOVZFczLqNTB0hrRLD4MibRTYbjBx7PnQTr1pcPToBKmKKKBoI8= Received: by 10.65.61.16 with SMTP id o16mr8090518qbk; Sat, 26 Nov 2005 05:30:12 -0800 (PST) Received: from ?192.168.0.12? ( [85.240.162.102]) by mx.gmail.com with ESMTP id f15sm1056550qba.2005.11.26.05.30.10; Sat, 26 Nov 2005 05:30:12 -0800 (PST) Message-ID: <43886358.7090009@gmail.com> Date: Sat, 26 Nov 2005 13:30:00 +0000 From: Nuno Antunes User-Agent: Mozilla Thunderbird 1.0.7 (X11/20051108) X-Accept-Language: en-us, en MIME-Version: 1.0 To: Gleb Smirnoff References: <200511151459.jAFEx6OY047069@freefall.freebsd.org> In-Reply-To: <200511151459.jAFEx6OY047069@freefall.freebsd.org> Content-Type: multipart/mixed; boundary="------------090005030307040400050300" Cc: freebsd-bugs@FreeBSD.org Subject: Re: bin/87352: [PATCH] Add line edit and history support to ngctl(8) via libedit X-BeenThere: freebsd-bugs@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: Bug reports List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sat, 26 Nov 2005 13:30:31 -0000 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 --------------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 #include #include +#include +#include #include #include @@ -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--