From nuno.antunes@gmail.com  Thu Oct 13 00:24:55 2005
Return-Path: <nuno.antunes@gmail.com>
Received: from mx1.FreeBSD.org (mx1.freebsd.org [216.136.204.125])
	by hub.freebsd.org (Postfix) with ESMTP id C8C2416A41F
	for <FreeBSD-gnats-submit@freebsd.org>; Thu, 13 Oct 2005 00:24:55 +0000 (GMT)
	(envelope-from nuno.antunes@gmail.com)
Received: from www.nantunes.org (bl7-161-98.dsl.telepac.pt [85.240.161.98])
	by mx1.FreeBSD.org (Postfix) with ESMTP id 01C5043D45
	for <FreeBSD-gnats-submit@freebsd.org>; Thu, 13 Oct 2005 00:24:54 +0000 (GMT)
	(envelope-from nuno.antunes@gmail.com)
Received: from localhost (localhost [172.16.0.2])
	by www.nantunes.org (Postfix) with ESMTP id DB87E2E4DD
	for <FreeBSD-gnats-submit@freebsd.org>; Thu, 13 Oct 2005 01:28:32 +0100 (WEST)
Received: from www.nantunes.org ([172.16.0.2])
 by localhost (www.nantunes.org [172.16.0.2]) (amavisd-new, port 10024)
 with LMTP id 81119-09 for <FreeBSD-gnats-submit@freebsd.org>;
 Thu, 13 Oct 2005 01:28:24 +0100 (WEST)
Received: from pt1wgfdc.ip.lab (localhost [172.16.0.2])
	by www.nantunes.org (Postfix) with ESMTP id F02E32E42A
	for <FreeBSD-gnats-submit@freebsd.org>; Thu, 13 Oct 2005 01:28:23 +0100 (WEST)
Message-Id: <1129163082.0@pt1wgfdc.ip.lab>
Date: Thu, 13 Oct 2005 01:24:42 +0100
From: "Nuno Antunes" <nuno.antunes@gmail.com>
To: "FreeBSD gnats submit" <FreeBSD-gnats-submit@freebsd.org>
Subject: [PATCH] Add line edit and history support to ngctl via libedit
X-Send-Pr-Version: gtk-send-pr 0.4.6 
X-GNATS-Notify:

>Number:         87352
>Category:       bin
>Synopsis:       [PATCH] Add line edit and history support to ngctl(8) via libedit
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    glebius
>State:          closed
>Quarter:        
>Keywords:       
>Date-Required:  
>Class:          change-request
>Submitter-Id:   current-users
>Arrival-Date:   Thu Oct 13 00:30:19 GMT 2005
>Closed-Date:    Thu Aug 10 11:12:10 GMT 2006
>Last-Modified:  Thu Aug 10 11:12:10 GMT 2006
>Originator:     Nuno Antunes
>Release:        FreeBSD 6.0-BETA4 i386
>Organization:
>Environment:


System: FreeBSD 6.0-BETA4 #2: Sat Sep 17 10:56:38 WEST 2005
    nant@pt1wgfdc.ip.lab:/usr/obj/usr/src/sys/LIFEBOOK8010



>Description:


This patch group adds line editing and history facilities to ngctl via libedit
while on the interactive mode tool. It now uses two threads, one to
handle user interactivity and the other to handle the data/control socket
operation.
 
I'm not sure if any special syncronization is required. Only minimal
syncronization was implemented (via a signal).


>How-To-Repeat:





>Fix:


--- Makefile.diff begins here ---
--- usr.sbin/ngctl/Makefile.orig	Wed Oct 12 23:18:02 2005
+++ usr.sbin/ngctl/Makefile	Tue Oct 11 23:13:21 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>
--- Makefile.diff ends here ---


--- ngctl.h.diff begins here ---
--- usr.sbin/ngctl/ngctl.h.orig	Mon Oct 10 22:51:59 2005
+++ usr.sbin/ngctl/ngctl.h	Wed Oct 12 23:26:24 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,14 @@
 	  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 */
+	pthread_t interact;
 };
 
 /* Command return values */
--- ngctl.h.diff ends here ---


--- main.c.diff begins here ---
--- usr.sbin/ngctl/main.c.orig	Fri Feb  4 20:09:11 2005
+++ usr.sbin/ngctl/main.c	Wed Oct 12 23:26:09 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,45 @@
 		if (fp != NULL) {
 			rtn = ReadFile(fp);
 		} else if (interactive) {
-			rtn = DoInteractive();
+			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);
+				el_set(td.el, EL_HIST, history, td.hist);
+			}
+			else printf("no history!!!\n");
+
+			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(&td.interact, NULL, DoInteractive, &td);
+			pthread_create(&monitor, NULL, DoMonitor, &td);
+
+
+			/* Wait for the Interactive thread to finish */
+			pthread_join(td.interact, &thread_rtn);
+			rtn = *(int *)thread_rtn;
+
+			pthread_cancel(monitor);
+
+			/* Destroy libedit stuff */
+			el_end(td.el);
+			if (td.hist != NULL)
+				history_end(td.hist);
 		} else
 			Usage("no command specified");
 	} else {
@@ -198,38 +238,66 @@
 /*
  * Interactive mode
  */
-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 - 1);
+			buf[LINE_MAX] = '\0';/* In case len(line) >= LINE_MAX */
+			len = strlen(buf);
+			if (buf[--len] == '\n')
+				buf[len] = '\0';
+			if ((rtn = DoParseCommand(buf)) == CMDRTN_QUIT)
+				break;
+			history(td->hist, &td->he, H_ENTER, buf);
+		}
+	}
+
+	return(&rtn);
+}
+
+/*
+ * Monitor thread
+ */
+static void *
+DoMonitor(void *vp)
+{
+	const int maxfd = MAX(csock, dsock) + 1;
+	struct thread_data *td = (struct thread_data *)vp;
+	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");
 		}
 
+		/* Save current line */
+
+		
+
+		/* Print a newline before echoing anything */
+		printf("\n");
+		fflush(stdout);
+
 		/* Display any incoming control message */
 		if (FD_ISSET(csock, &rfds))
 			MsgRead();
@@ -252,19 +320,14 @@
 			free(buf);
 		}
 
-		/* Get any user input */
-		if (FD_ISSET(0, &rfds)) {
-			char buf[LINE_MAX];
+		/* Restore the prompt to a known state */
+		el_reset(td->el);
+		printf("%s", GetPrompt(td->el));
+		fflush(stdout);
+		pthread_kill(td->interact, SIGCONT);
+	} /* while */
 
-			if (fgets(buf, sizeof(buf), stdin) == NULL) {
-				printf("\n");
-				break;
-			}
-			if (DoParseCommand(buf) == CMDRTN_QUIT)
-				break;
-		}
-	}
-	return(CMDRTN_QUIT);
+	return NULL;
 }
 
 /*
@@ -493,6 +556,13 @@
 		}
 		printf("%s\n", sbuf);
 	}
+}
+
+static const char *
+GetPrompt(EditLine *el)
+{
+	(void) el;
+	return PROMPT;
 }
 
 /*
--- main.c.diff ends here ---



>Release-Note:
>Audit-Trail:

From: Nuno Antunes <nuno.antunes@gmail.com>
To: FreeBSD-gnats-submit@freebsd.org
Cc:  
Subject: Re: bin/87352: [PATCH] Add line edit and history support to ngctl via libedit
Date: Wed, 26 Oct 2005 12:16:08 +0100

 Hi,
 
 I've been using this patch for some time now and detected some
 problems with it. Nothing
 serious but it's not perfect yet. I will post back another revised pacth so=
 on.
 
 Thanks,
 Nuno Antunes
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 

From: Gleb Smirnoff <glebius@FreeBSD.org>
To: Nuno Antunes <nuno.antunes@gmail.com>
Cc: Ganbold <ganbold@micom.mng.net>, ru@FreeBSD.org,
        freebsd-gnats-submit@FreeBSD.org
Subject: Re: bin/87352:
Date: Wed, 12 Jul 2006 15:28:56 +0400

 --ieNMXl1Fr3cevapt
 Content-Type: text/plain; charset=koi8-r
 Content-Disposition: inline
 
   Colleagues,
 
   I have pondered some time on Nuno's patch and on Ganbold's
 one. Finally I have decided that we should use libedit with
 its wide functionality instead of inventing bicycle ourselves.
 
 Since Nuno said that he sees some problems in his patch, I have
 written a patch from scratch. I failed to find any problems
 in it, yet.
 
 The patch is attached, please review it.
 
 -- 
 Totus tuus, Glebius.
 GLEBIUS-RIPN GLEB-RIPE
 
 --ieNMXl1Fr3cevapt
 Content-Type: text/plain; charset=koi8-r
 Content-Disposition: attachment; filename="ngctl.libedit"
 
 Index: Makefile
 ===================================================================
 RCS file: /home/ncvs/src/usr.sbin/ngctl/Makefile,v
 retrieving revision 1.14
 diff -u -p -r1.14 Makefile
 --- Makefile	26 Jan 2004 10:27:18 -0000	1.14
 +++ Makefile	12 Jul 2006 10:25:22 -0000
 @@ -6,7 +6,19 @@ MAN=	ngctl.8
  SRCS=	main.c mkpeer.c config.c connect.c dot.c name.c show.c list.c \
  	msg.c debug.c shutdown.c rmhook.c status.c types.c write.c
  WARNS?=	3
 +
 +.if defined(RELEASE_CRUNCH)
 +NGCTL_NO_LIBEDIT=
 +.endif
 +
  DPADD=	${LIBNETGRAPH}
  LDADD=	-lnetgraph
  
 +.if defined(NGCTL_NO_LIBEDIT)
 +CFLAGS+=-DNOLIBEDIT
 +.else
 +DPADD+=	${LIBPTHREAD} ${LIBEDIT} ${LIBTERMCAP}
 +LDADD+=	-pthread -ledit -ltermcap
 +.endif
 +
  .include <bsd.prog.mk>
 Index: main.c
 ===================================================================
 RCS file: /home/ncvs/src/usr.sbin/ngctl/main.c,v
 retrieving revision 1.20
 diff -u -p -r1.20 main.c
 --- main.c	28 Jun 2006 10:38:38 -0000	1.20
 +++ main.c	12 Jul 2006 10:44:09 -0000
 @@ -51,6 +51,11 @@
  #include <string.h>
  #include <sysexits.h>
  #include <unistd.h>
 +#ifndef NOLIBEDIT
 +#include <signal.h>
 +#include <histedit.h>
 +#include <pthread.h>
 +#endif
  
  #include <netgraph.h>
  
 @@ -63,7 +68,7 @@
  
  /* Internal functions */
  static int	ReadFile(FILE *fp);
 -static int	DoParseCommand(char *line);
 +static int	DoParseCommand(const char *line);
  static int	DoCommand(int ac, char **av);
  static int	DoInteractive(void);
  static const	struct ngcmd *FindCommand(const char *string);
 @@ -72,6 +77,11 @@ 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);
 +#ifndef NOLIBEDIT
 +static sig_atomic_t	unblock;
 +static pthread_mutex_t	mutex = PTHREAD_MUTEX_INITIALIZER;
 +static pthread_cond_t	cond = PTHREAD_COND_INITIALIZER;
 +#endif
  
  /* List of commands */
  static const struct ngcmd *const cmds[] = {
 @@ -211,8 +221,9 @@ ReadFile(FILE *fp)
  	return (CMDRTN_OK);
  }
  
 +#ifdef NOLIBEDIT
  /*
 - * Interactive mode
 + * Interactive mode w/o libedit functionality.
   */
  static int
  DoInteractive(void)
 @@ -283,17 +294,148 @@ DoInteractive(void)
  	return (CMDRTN_QUIT);
  }
  
 +#else /* ifndef NOLIBEDIT */
 +
 +/* Signal handler for Monitor() thread. */
 +static void
 +Unblock(int signal)
 +{
 +	unblock = 1;
 +}
 +
 +/*
 + * Thread that monitors csock and dsock, while main thread
 + * can be blocked in el_gets().
 + */
 +static void *
 +Monitor(void *v)
 +{
 +	struct sigaction act;
 +	const int maxfd = MAX(csock, dsock) + 1;
 +
 +	act.sa_handler = Unblock;
 +	sigemptyset(&act.sa_mask);
 +	act.sa_flags = 0;
 +	sigaction(SIGUSR1, &act, NULL);
 +
 +	pthread_mutex_lock(&mutex);
 +	for (;;) {
 +		fd_set rfds;
 +
 +		/* See if any data or control messages are arriving. */
 +		FD_ZERO(&rfds);
 +		FD_SET(csock, &rfds);
 +		FD_SET(dsock, &rfds);
 +		unblock = 0;
 +		if (select(maxfd, &rfds, NULL, NULL, NULL) <= 0) {
 +			if (errno == EINTR) {
 +				if (unblock == 1)
 +					pthread_cond_wait(&cond, &mutex);
 +				continue;
 +			}
 +			err(EX_OSERR, "select");
 +		}
 +
 +		/* Display any incoming control message. */
 +		if (FD_ISSET(csock, &rfds))
 +			MsgRead();
 +
 +		/* Display any incoming data packet. */
 +		if (FD_ISSET(dsock, &rfds)) {
 +			char hook[NG_HOOKSIZ];
 +			u_char *buf;
 +			int rl;
 +
 +			/* Read packet from socket. */
 +			if ((rl = NgAllocRecvData(dsock, &buf, hook)) < 0)
 +				err(EX_OSERR, "reading hook \"%s\"", hook);
 +			if (rl == 0)
 +				errx(EX_OSERR, "EOF from hook \"%s\"", hook);
 +
 +			/* Write packet to stdout. */
 +			printf("Rec'd data packet on hook \"%s\":\n", hook);
 +			DumpAscii(buf, rl);
 +			free(buf);
 +		}
 +	}
 +
 +	return (NULL);
 +}
 +
 +static char *
 +Prompt(EditLine *el)
 +{
 +	return PROMPT;
 +}
 +
 +/*
 + * Here we start a thread, that will monitor the netgraph
 + * sockets and catch any unexpected messages or data on them,
 + * that can arrive while user edits his/her commands.
 + *
 + * Whenever we expect data on netgraph sockets, we send signal
 + * to monitoring thread. The signal forces it to exit select()
 + * system call and sleep on condvar until we wake it. While
 + * monitoring thread sleeps, we can do our work with netgraph
 + * sockets.
 + */
 +static int
 +DoInteractive(void)
 +{
 +	pthread_t tr_monitor;
 +	EditLine *el;
 +	History *hist;
 +	HistEvent hev = { 0, "" };
 +
 +	(*help_cmd.func)(0, NULL);
 +	pthread_create(&tr_monitor, NULL, Monitor, NULL);
 +	el = el_init(getprogname(), stdin, stdout, stderr);
 +	if (el == NULL)
 +		return (CMDRTN_ERROR);
 +	el_set(el, EL_PROMPT, Prompt);
 +	el_set(el, EL_SIGNAL, 1);
 +	hist = history_init();
 +	if (hist == NULL)
 +		return (CMDRTN_ERROR);
 +	history(hist, &hev, H_SETSIZE, 100);
 +	el_set(el, EL_HIST, history, (const char *)hist);
 +	el_source(el, NULL);
 +
 +	for (;;) {
 +		const char *buf;
 +		int count;
 +
 +		if ((buf = el_gets(el, &count)) == NULL) {
 +			printf("\n");
 +			break;
 +		}
 +		history(hist, &hev, H_ENTER, buf);
 +		pthread_kill(tr_monitor, SIGUSR1);
 +		pthread_mutex_lock(&mutex);
 +		if (DoParseCommand(buf) == CMDRTN_QUIT)
 +			break;
 +		pthread_cond_signal(&cond);
 +		pthread_mutex_unlock(&mutex);
 +	}
 +
 +	el_end(el);
 +	history_end(hist);
 +
 +	return (CMDRTN_QUIT);
 +}
 +#endif /* ifndef NOLIBEDIT */
 +
  /*
   * Parse a command line and execute the command
   */
  static int
 -DoParseCommand(char *line)
 +DoParseCommand(const char *line)
  {
  	char *av[MAX_ARGS];
  	int ac;
  
  	/* Parse line */
 -	for (ac = 0, av[0] = strtok(line, WHITESPACE);
 +	for (ac = 0, av[0] = strtok((char *)line, WHITESPACE);
  	    ac < MAX_ARGS - 1 && av[ac];
  	    av[++ac] = strtok(NULL, WHITESPACE));
  
 
 --ieNMXl1Fr3cevapt--

From: "Nuno Antunes" <nuno.antunes@gmail.com>
To: "Gleb Smirnoff" <glebius@freebsd.org>
Cc: Ganbold <ganbold@micom.mng.net>, ru@freebsd.org, 
	freebsd-gnats-submit@freebsd.org
Subject: Re: bin/87352:
Date: Wed, 12 Jul 2006 17:18:09 +0100

 On 7/12/06, Gleb Smirnoff <glebius@freebsd.org> wrote:
 >   Colleagues,
 >
 >   I have pondered some time on Nuno's patch and on Ganbold's
 > one. Finally I have decided that we should use libedit with
 > its wide functionality instead of inventing bicycle ourselves.
 >
 > Since Nuno said that he sees some problems in his patch, I have
 > written a patch from scratch. I failed to find any problems
 > in it, yet.
 >
 > The patch is attached, please review it.
 >
 > --
 > Totus tuus, Glebius.
 > GLEBIUS-RIPN GLEB-RIPE
 >
 >
 >
 
 Hi Gleb,
 
 Your patch works great here, thanks! The problems I was seeing were
 related to thread synchonization but you solved that very nicely.
 
 I can only coment on the bikeshed. :-)
 
 I would just prefer that empty lines were not inserted into the
 history. I would also prefer that only unique lines were inserted into
 the history [ history(hist, &hev, H_SETUNIQUE, 1) ] and emacs style of
 editing [ el_set(el, EL_EDITOR, "emacs") ] by default but for that I
 guess I can use editrc. :-)
 
 Best regards,
 Nuno Antunes

From: Gleb Smirnoff <glebius@FreeBSD.org>
To: Nuno Antunes <nuno.antunes@gmail.com>
Cc: freebsd-gnats-submit@FreeBSD.org, Ganbold <ganbold@micom.mng.net>,
        ru@FreeBSD.org
Subject: Re: bin/87352:
Date: Thu, 13 Jul 2006 00:35:16 +0400

 N>  Your patch works great here, thanks! The problems I was seeing were
 N>  related to thread synchonization but you solved that very nicely.
 N>  
 N>  I can only coment on the bikeshed. :-)
 N>  
 N>  I would just prefer that empty lines were not inserted into the
 N>  history. I would also prefer that only unique lines were inserted into
 N>  the history [ history(hist, &hev, H_SETUNIQUE, 1) ] and emacs style of
 N>  editing [ el_set(el, EL_EDITOR, "emacs") ] by default but for that I
 N>  guess I can use editrc. :-)
 
 Yeah, I entirely agree about H_SETUNIQUE. But the editor mode should be
 set via .editrc.
 
 -- 
 Totus tuus, Glebius.
 GLEBIUS-RIPN GLEB-RIPE
State-Changed-From-To: suspended->patched 
State-Changed-By: glebius 
State-Changed-When: Mon Aug 7 14:08:44 UTC 2006 
State-Changed-Why:  
New functionality committed to HEAD. 

http://www.freebsd.org/cgi/query-pr.cgi?pr=87352 
State-Changed-From-To: patched->closed 
State-Changed-By: glebius 
State-Changed-When: Thu Aug 10 11:11:52 UTC 2006 
State-Changed-Why:  
Merged to RELENG_6. 

http://www.freebsd.org/cgi/query-pr.cgi?pr=87352 
>Unformatted:
