xref: /f-stack/tools/ngctl/main.c (revision 3b2bd0f6)
1 
2 /*
3  * main.c
4  *
5  * Copyright (c) 1996-1999 Whistle Communications, Inc.
6  * All rights reserved.
7  *
8  * Subject to the following obligations and disclaimer of warranty, use and
9  * redistribution of this software, in source or object code forms, with or
10  * without modifications are expressly permitted by Whistle Communications;
11  * provided, however, that:
12  * 1. Any and all reproductions of the source or object code must include the
13  *    copyright notice above and the following disclaimer of warranties; and
14  * 2. No rights are granted, in any manner or form, to use Whistle
15  *    Communications, Inc. trademarks, including the mark "WHISTLE
16  *    COMMUNICATIONS" on advertising, endorsements, or otherwise except as
17  *    such appears in the above copyright notice or in the software.
18  *
19  * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
20  * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
21  * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
22  * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
23  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
24  * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
25  * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
26  * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
27  * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
28  * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
29  * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
30  * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
31  * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
32  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
34  * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
35  * OF SUCH DAMAGE.
36  *
37  * $Whistle: main.c,v 1.12 1999/11/29 19:17:46 archie Exp $
38  */
39 
40 #include <sys/cdefs.h>
41 __FBSDID("$FreeBSD$");
42 
43 #include <sys/param.h>
44 #include <sys/socket.h>
45 #include <sys/select.h>
46 
47 #include <ctype.h>
48 #include <err.h>
49 #include <errno.h>
50 #include <limits.h>
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <string.h>
54 #include <sysexits.h>
55 #include <unistd.h>
56 #ifdef EDITLINE
57 #include <signal.h>
58 #include <histedit.h>
59 #include <pthread.h>
60 #endif
61 
62 #include <netgraph.h>
63 
64 #include "ngctl.h"
65 
66 #ifdef FSTACK
67 #include "ff_ipc.h"
68 #endif
69 
70 #define PROMPT			"+ "
71 #define MAX_ARGS		512
72 #define WHITESPACE		" \t\r\n\v\f"
73 #define DUMP_BYTES_PER_LINE	16
74 
75 /* Internal functions */
76 static int	ReadFile(FILE *fp);
77 #ifndef FSTACK
78 static void	ReadSockets(fd_set *);
79 #endif
80 static int	DoParseCommand(const char *line);
81 static int	DoCommand(int ac, char **av);
82 #ifndef FSTACK
83 static int	DoInteractive(void);
84 #endif
85 static const	struct ngcmd *FindCommand(const char *string);
86 static int	MatchCommand(const struct ngcmd *cmd, const char *s);
87 static void	Usage(const char *msg);
88 static int	ReadCmd(int ac, char **av);
89 static int	HelpCmd(int ac, char **av);
90 static int	QuitCmd(int ac, char **av);
91 #ifdef EDITLINE
92 static volatile sig_atomic_t unblock;
93 static pthread_mutex_t	mutex = PTHREAD_MUTEX_INITIALIZER;
94 static pthread_cond_t	cond = PTHREAD_COND_INITIALIZER;
95 #endif
96 
97 /* List of commands */
98 static const struct ngcmd *const cmds[] = {
99 	&config_cmd,
100 	&connect_cmd,
101 	&debug_cmd,
102 	&dot_cmd,
103 	&help_cmd,
104 	&list_cmd,
105 	&mkpeer_cmd,
106 	&msg_cmd,
107 	&name_cmd,
108 	&read_cmd,
109 	&rmhook_cmd,
110 	&show_cmd,
111 	&shutdown_cmd,
112 	&status_cmd,
113 	&types_cmd,
114 	&write_cmd,
115 	&quit_cmd,
116 	NULL
117 };
118 
119 /* Commands defined in this file */
120 const struct ngcmd read_cmd = {
121 	ReadCmd,
122 	"read <filename>",
123 	"Read and execute commands from a file",
124 	NULL,
125 	{ "source", "." }
126 };
127 const struct ngcmd help_cmd = {
128 	HelpCmd,
129 	"help [command]",
130 	"Show command summary or get more help on a specific command",
131 	NULL,
132 	{ "?" }
133 };
134 const struct ngcmd quit_cmd = {
135 	QuitCmd,
136 	"quit",
137 	"Exit program",
138 	NULL,
139 	{ "exit" }
140 };
141 
142 /* Our control and data sockets */
143 #ifndef FSTACK
144 int	csock, dsock;
145 #else
146 int csock = -1, dsock = -1;
147 
148 void
149 __attribute__((destructor)) close_ng_socks()
150 {
151         if (csock >= 0) {
152                 ng_close(csock);
153         }
154 
155         if (dsock >= 0) {
156                 ng_close(dsock);
157         }
158 }
159 #endif
160 
161 /*
162  * main()
163  */
164 int
165 main(int ac, char *av[])
166 {
167 	char	name[NG_NODESIZ];
168 #ifndef FSTACK
169 	int	interactive = isatty(0) && isatty(1);
170 #endif
171 	FILE	*fp = NULL;
172 	int	ch, rtn = 0;
173 
174 	/* Set default node name */
175 	snprintf(name, sizeof(name), "ngctl%d", getpid());
176 
177 	/* Parse command line */
178 #ifndef FSTACK
179 	while ((ch = getopt(ac, av, "df:n:")) != -1) {
180 #else
181 	ff_ipc_init();
182 	while ((ch = getopt(ac, av, "df:n:p:")) != -1) {
183 #endif
184 		switch (ch) {
185 		case 'd':
186 			NgSetDebug(NgSetDebug(-1) + 1);
187 			break;
188 		case 'f':
189 			if (strcmp(optarg, "-") == 0)
190 				fp = stdin;
191 			else if ((fp = fopen(optarg, "r")) == NULL)
192 				err(EX_NOINPUT, "%s", optarg);
193 			break;
194 		case 'n':
195 			snprintf(name, sizeof(name), "%s", optarg);
196 			break;
197 #ifdef FSTACK
198 		case 'p':
199 			ff_set_proc_id(atoi(optarg));
200 			break;
201 #endif
202 		case '?':
203 		default:
204 			Usage((char *)NULL);
205 			break;
206 		}
207 	}
208 	ac -= optind;
209 	av += optind;
210 
211 	/* Create a new socket node */
212 	if (NgMkSockNode(name, &csock, &dsock) < 0)
213 		err(EX_OSERR, "can't create node");
214 
215 	/* Do commands as requested */
216 	if (ac == 0) {
217 		if (fp != NULL) {
218 			rtn = ReadFile(fp);
219 #ifndef FSTACK
220 		} else if (interactive) {
221 			rtn = DoInteractive();
222 #endif
223 		} else
224 			Usage("no command specified");
225 	} else {
226 		rtn = DoCommand(ac, av);
227 	}
228 
229 	/* Convert command return code into system exit code */
230 	switch (rtn) {
231 	case CMDRTN_OK:
232 	case CMDRTN_QUIT:
233 		rtn = 0;
234 		break;
235 	case CMDRTN_USAGE:
236 		rtn = EX_USAGE;
237 		break;
238 	case CMDRTN_ERROR:
239 		rtn = EX_OSERR;
240 		break;
241 	}
242 	return (rtn);
243 }
244 
245 /*
246  * Process commands from a file
247  */
248 static int
249 ReadFile(FILE *fp)
250 {
251 	char line[LINE_MAX];
252 	int num, rtn;
253 
254 	for (num = 1; fgets(line, sizeof(line), fp) != NULL; num++) {
255 		if (*line == '#')
256 			continue;
257 		if ((rtn = DoParseCommand(line)) != 0) {
258 			warnx("line %d: error in file", num);
259 			return (rtn);
260 		}
261 	}
262 	return (CMDRTN_OK);
263 }
264 
265 #ifndef FSTACK
266 #ifdef EDITLINE
267 /* Signal handler for Monitor() thread. */
268 static void
269 Unblock(int signal __unused)
270 {
271 
272 	unblock = 1;
273 }
274 
275 /*
276  * Thread that monitors csock and dsock while main thread
277  * can be blocked in el_gets().
278  */
279 static void *
280 Monitor(void *v __unused)
281 {
282 	struct sigaction act;
283 	const int maxfd = MAX(csock, dsock) + 1;
284 
285 	act.sa_handler = Unblock;
286 	sigemptyset(&act.sa_mask);
287 	act.sa_flags = 0;
288 	sigaction(SIGUSR1, &act, NULL);
289 
290 	pthread_mutex_lock(&mutex);
291 	for (;;) {
292 		fd_set rfds;
293 
294 		/* See if any data or control messages are arriving. */
295 		FD_ZERO(&rfds);
296 		FD_SET(csock, &rfds);
297 		FD_SET(dsock, &rfds);
298 		unblock = 0;
299 		if (select(maxfd, &rfds, NULL, NULL, NULL) <= 0) {
300 			if (errno == EINTR) {
301 				if (unblock == 1)
302 					pthread_cond_wait(&cond, &mutex);
303 				continue;
304 			}
305 			err(EX_OSERR, "select");
306 		}
307 		ReadSockets(&rfds);
308 	}
309 
310 	return (NULL);
311 }
312 
313 static char *
314 Prompt(EditLine *el __unused)
315 {
316 
317 	return (PROMPT);
318 }
319 
320 /*
321  * Here we start a thread, that will monitor the netgraph
322  * sockets and catch any unexpected messages or data on them,
323  * that can arrive while user edits his/her commands.
324  *
325  * Whenever we expect data on netgraph sockets, we send signal
326  * to monitoring thread. The signal forces it to exit select()
327  * system call and sleep on condvar until we wake it. While
328  * monitoring thread sleeps, we can do our work with netgraph
329  * sockets.
330  */
331 static int
332 DoInteractive(void)
333 {
334 	pthread_t monitor;
335 	EditLine *el;
336 	History *hist;
337 	HistEvent hev = { 0, "" };
338 
339 	(*help_cmd.func)(0, NULL);
340 	pthread_create(&monitor, NULL, Monitor, NULL);
341 	el = el_init(getprogname(), stdin, stdout, stderr);
342 	if (el == NULL)
343 		return (CMDRTN_ERROR);
344 	el_set(el, EL_PROMPT, Prompt);
345 	el_set(el, EL_SIGNAL, 1);
346 	el_set(el, EL_EDITOR, "emacs");
347 	hist = history_init();
348 	if (hist == NULL)
349 		return (CMDRTN_ERROR);
350 	history(hist, &hev, H_SETSIZE, 100);
351 	history(hist, &hev, H_SETUNIQUE, 1);
352 	el_set(el, EL_HIST, history, (const char *)hist);
353 	el_source(el, NULL);
354 
355 	for (;;) {
356 		const char *buf;
357 		int count;
358 
359 		if ((buf = el_gets(el, &count)) == NULL) {
360 			printf("\n");
361 			break;
362 		}
363 		history(hist, &hev, H_ENTER, buf);
364 		pthread_kill(monitor, SIGUSR1);
365 		pthread_mutex_lock(&mutex);
366 		if (DoParseCommand(buf) == CMDRTN_QUIT) {
367 			pthread_mutex_unlock(&mutex);
368 			break;
369 		}
370 		pthread_cond_signal(&cond);
371 		pthread_mutex_unlock(&mutex);
372 	}
373 
374 	history_end(hist);
375 	el_end(el);
376 	pthread_cancel(monitor);
377 
378 	return (CMDRTN_QUIT);
379 }
380 
381 #else /* !EDITLINE */
382 
383 /*
384  * Interactive mode w/o libedit functionality.
385  */
386 static int
387 DoInteractive(void)
388 {
389 	const int maxfd = MAX(csock, dsock) + 1;
390 
391 	(*help_cmd.func)(0, NULL);
392 	while (1) {
393 		struct timeval tv;
394 		fd_set rfds;
395 
396 		/* See if any data or control messages are arriving */
397 		FD_ZERO(&rfds);
398 		FD_SET(csock, &rfds);
399 		FD_SET(dsock, &rfds);
400 		memset(&tv, 0, sizeof(tv));
401 		if (select(maxfd, &rfds, NULL, NULL, &tv) <= 0) {
402 
403 			/* Issue prompt and wait for anything to happen */
404 			printf("%s", PROMPT);
405 			fflush(stdout);
406 			FD_ZERO(&rfds);
407 			FD_SET(0, &rfds);
408 			FD_SET(csock, &rfds);
409 			FD_SET(dsock, &rfds);
410 			if (select(maxfd, &rfds, NULL, NULL, NULL) < 0)
411 				err(EX_OSERR, "select");
412 
413 			/* If not user input, print a newline first */
414 			if (!FD_ISSET(0, &rfds))
415 				printf("\n");
416 		}
417 
418 		ReadSockets(&rfds);
419 
420 		/* Get any user input */
421 		if (FD_ISSET(0, &rfds)) {
422 			char buf[LINE_MAX];
423 
424 			if (fgets(buf, sizeof(buf), stdin) == NULL) {
425 				printf("\n");
426 				break;
427 			}
428 			if (DoParseCommand(buf) == CMDRTN_QUIT)
429 				break;
430 		}
431 	}
432 	return (CMDRTN_QUIT);
433 }
434 #endif /* !EDITLINE */
435 
436 /*
437  * Read and process data on netgraph control and data sockets.
438  */
439 static void
440 ReadSockets(fd_set *rfds)
441 {
442 	/* Display any incoming control message. */
443 	if (FD_ISSET(csock, rfds))
444 		MsgRead();
445 
446 	/* Display any incoming data packet. */
447 	if (FD_ISSET(dsock, rfds)) {
448 		char hook[NG_HOOKSIZ];
449 		u_char *buf;
450 		int rl;
451 
452 		/* Read packet from socket. */
453 		if ((rl = NgAllocRecvData(dsock, &buf, hook)) < 0)
454 			err(EX_OSERR, "reading hook \"%s\"", hook);
455 		if (rl == 0)
456 			errx(EX_OSERR, "EOF from hook \"%s\"?", hook);
457 
458 		/* Write packet to stdout. */
459 		printf("Rec'd data packet on hook \"%s\":\n", hook);
460 		DumpAscii(buf, rl);
461 		free(buf);
462 	}
463 }
464 #endif
465 
466 /*
467  * Parse a command line and execute the command
468  */
469 static int
470 DoParseCommand(const char *line)
471 {
472 	char *av[MAX_ARGS];
473 	int ac;
474 
475 	/* Parse line */
476 	for (ac = 0, av[0] = strtok((char *)line, WHITESPACE);
477 	    ac < MAX_ARGS - 1 && av[ac];
478 	    av[++ac] = strtok(NULL, WHITESPACE));
479 
480 	/* Do command */
481 	return (DoCommand(ac, av));
482 }
483 
484 /*
485  * Execute the command
486  */
487 static int
488 DoCommand(int ac, char **av)
489 {
490 	const struct ngcmd *cmd;
491 	int rtn;
492 
493 	if (ac == 0 || *av[0] == 0)
494 		return (CMDRTN_OK);
495 	if ((cmd = FindCommand(av[0])) == NULL)
496 		return (CMDRTN_ERROR);
497 	if ((rtn = (*cmd->func)(ac, av)) == CMDRTN_USAGE)
498 		warnx("usage: %s", cmd->cmd);
499 	return (rtn);
500 }
501 
502 /*
503  * Find a command
504  */
505 static const struct ngcmd *
506 FindCommand(const char *string)
507 {
508 	int k, found = -1;
509 
510 	for (k = 0; cmds[k] != NULL; k++) {
511 		if (MatchCommand(cmds[k], string)) {
512 			if (found != -1) {
513 				warnx("\"%s\": ambiguous command", string);
514 				return (NULL);
515 			}
516 			found = k;
517 		}
518 	}
519 	if (found == -1) {
520 		warnx("\"%s\": unknown command", string);
521 		return (NULL);
522 	}
523 	return (cmds[found]);
524 }
525 
526 /*
527  * See if string matches a prefix of "cmd" (or an alias) case insensitively
528  */
529 static int
530 MatchCommand(const struct ngcmd *cmd, const char *s)
531 {
532 	int a;
533 
534 	/* Try to match command, ignoring the usage stuff */
535 	if (strlen(s) <= strcspn(cmd->cmd, WHITESPACE)) {
536 		if (strncasecmp(s, cmd->cmd, strlen(s)) == 0)
537 			return (1);
538 	}
539 
540 	/* Try to match aliases */
541 	for (a = 0; a < MAX_CMD_ALIAS && cmd->aliases[a] != NULL; a++) {
542 		if (strlen(cmd->aliases[a]) >= strlen(s)) {
543 			if (strncasecmp(s, cmd->aliases[a], strlen(s)) == 0)
544 				return (1);
545 		}
546 	}
547 
548 	/* No match */
549 	return (0);
550 }
551 
552 /*
553  * ReadCmd()
554  */
555 static int
556 ReadCmd(int ac, char **av)
557 {
558 	FILE *fp;
559 	int rtn;
560 
561 	/* Open file */
562 	switch (ac) {
563 	case 2:
564 		if ((fp = fopen(av[1], "r")) == NULL) {
565 			warn("%s", av[1]);
566 			return (CMDRTN_ERROR);
567 		}
568 		break;
569 	default:
570 		return (CMDRTN_USAGE);
571 	}
572 
573 	/* Process it */
574 	rtn = ReadFile(fp);
575 	fclose(fp);
576 	return (rtn);
577 }
578 
579 /*
580  * HelpCmd()
581  */
582 static int
583 HelpCmd(int ac, char **av)
584 {
585 	const struct ngcmd *cmd;
586 	int k;
587 
588 	switch (ac) {
589 	case 0:
590 	case 1:
591 		/* Show all commands */
592 		printf("Available commands:\n");
593 		for (k = 0; cmds[k] != NULL; k++) {
594 			char *s, buf[100];
595 
596 			cmd = cmds[k];
597 			snprintf(buf, sizeof(buf), "%s", cmd->cmd);
598 			for (s = buf; *s != '\0' && !isspace(*s); s++);
599 			*s = '\0';
600 			printf("  %-10s %s\n", buf, cmd->desc);
601 		}
602 		return (CMDRTN_OK);
603 	default:
604 		/* Show help on a specific command */
605 		if ((cmd = FindCommand(av[1])) != NULL) {
606 			printf("usage:    %s\n", cmd->cmd);
607 			if (cmd->aliases[0] != NULL) {
608 				int a = 0;
609 
610 				printf("Aliases:  ");
611 				while (1) {
612 					printf("%s", cmd->aliases[a++]);
613 					if (a == MAX_CMD_ALIAS
614 					    || cmd->aliases[a] == NULL) {
615 						printf("\n");
616 						break;
617 					}
618 					printf(", ");
619 				}
620 			}
621 			printf("Summary:  %s\n", cmd->desc);
622 			if (cmd->help != NULL) {
623 				const char *s;
624 				char buf[65];
625 				int tot, len, done;
626 
627 				printf("Description:\n");
628 				for (s = cmd->help; *s != '\0'; s += len) {
629 					while (isspace(*s))
630 						s++;
631 					tot = snprintf(buf,
632 					    sizeof(buf), "%s", s);
633 					len = strlen(buf);
634 					done = len == tot;
635 					if (!done) {
636 						while (len > 0
637 						    && !isspace(buf[len-1]))
638 							buf[--len] = '\0';
639 					}
640 					printf("  %s\n", buf);
641 				}
642 			}
643 		}
644 	}
645 	return (CMDRTN_OK);
646 }
647 
648 /*
649  * QuitCmd()
650  */
651 static int
652 QuitCmd(int ac __unused, char **av __unused)
653 {
654 	return (CMDRTN_QUIT);
655 }
656 
657 /*
658  * Dump data in hex and ASCII form
659  */
660 void
661 DumpAscii(const u_char *buf, int len)
662 {
663 	char ch, sbuf[100];
664 	int k, count;
665 
666 	for (count = 0; count < len; count += DUMP_BYTES_PER_LINE) {
667 		snprintf(sbuf, sizeof(sbuf), "%04x:  ", count);
668 		for (k = 0; k < DUMP_BYTES_PER_LINE; k++) {
669 			if (count + k < len) {
670 				snprintf(sbuf + strlen(sbuf),
671 				    sizeof(sbuf) - strlen(sbuf),
672 				    "%02x ", buf[count + k]);
673 			} else {
674 				snprintf(sbuf + strlen(sbuf),
675 				    sizeof(sbuf) - strlen(sbuf), "   ");
676 			}
677 		}
678 		snprintf(sbuf + strlen(sbuf), sizeof(sbuf) - strlen(sbuf), " ");
679 		for (k = 0; k < DUMP_BYTES_PER_LINE; k++) {
680 			if (count + k < len) {
681 				ch = isprint(buf[count + k]) ?
682 				    buf[count + k] : '.';
683 				snprintf(sbuf + strlen(sbuf),
684 				    sizeof(sbuf) - strlen(sbuf), "%c", ch);
685 			} else {
686 				snprintf(sbuf + strlen(sbuf),
687 				    sizeof(sbuf) - strlen(sbuf), " ");
688 			}
689 		}
690 		printf("%s\n", sbuf);
691 	}
692 }
693 
694 /*
695  * Usage()
696  */
697 static void
698 Usage(const char *msg)
699 {
700 	if (msg)
701 		warnx("%s", msg);
702 	fprintf(stderr,
703 #ifndef FSTACK
704 		"usage: ngctl [-d] [-f file] [-n name] [command ...]\n");
705 #else
706 		"usage: ngctl -p <f-stack proc_id>  [-d] [-f file] [-n name] [command ...]\n");
707 #endif
708 	exit(EX_USAGE);
709 }
710