bin/87352: [PATCH] Add line edit and history support to ngctl(8) via libedit

Nuno Antunes nuno.antunes at gmail.com
Sat Nov 26 13:30:31 GMT 2005


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
-------------- next part --------------
--- 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>
-------------- next part --------------
--- 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;
 }
 
 /*
-------------- next part --------------
--- 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
-------------- next part --------------
--- 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 */


More information about the freebsd-bugs mailing list