xref: /freebsd/stand/common/commands.c (revision f374ba41)
1 /*-
2  * Copyright (c) 1998 Michael Smith <msmith@freebsd.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 
27 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
29 
30 #include <stand.h>
31 #include <string.h>
32 
33 #include "bootstrap.h"
34 
35 const char	*command_errmsg;
36 /* XXX should have procedural interface for setting, size limit? */
37 char		command_errbuf[COMMAND_ERRBUFSZ];
38 
39 static int page_file(char *filename);
40 
41 /*
42  * Help is read from a formatted text file.
43  *
44  * Entries in the file are formatted as
45 
46 # Ttopic [Ssubtopic] Ddescription
47 help
48 text
49 here
50 #
51 
52  *
53  * Note that for code simplicity's sake, the above format must be followed
54  * exactly.
55  *
56  * Subtopic entries must immediately follow the topic (this is used to
57  * produce the listing of subtopics).
58  *
59  * If no argument(s) are supplied by the user, the help for 'help' is displayed.
60  */
61 COMMAND_SET(help, "help", "detailed help", command_help);
62 
63 static int
64 help_getnext(int fd, char **topic, char **subtopic, char **desc)
65 {
66 	char	line[81], *cp, *ep;
67 
68 	/* Make sure we provide sane values. */
69 	*topic = *subtopic = *desc = NULL;
70 	for (;;) {
71 		if (fgetstr(line, 80, fd) < 0)
72 			return (0);
73 
74 		if (strlen(line) < 3 || line[0] != '#' || line[1] != ' ')
75 			continue;
76 
77 		cp = line + 2;
78 		while (cp != NULL && *cp != 0) {
79 			ep = strchr(cp, ' ');
80 			if (*cp == 'T' && *topic == NULL) {
81 				if (ep != NULL)
82 					*ep++ = 0;
83 				*topic = strdup(cp + 1);
84 			} else if (*cp == 'S' && *subtopic == NULL) {
85 				if (ep != NULL)
86 					*ep++ = 0;
87 				*subtopic = strdup(cp + 1);
88 			} else if (*cp == 'D') {
89 				*desc = strdup(cp + 1);
90 				ep = NULL;
91 			}
92 			cp = ep;
93 		}
94 		if (*topic == NULL) {
95 			free(*subtopic);
96 			free(*desc);
97 			*subtopic = *desc = NULL;
98 			continue;
99 		}
100 		return (1);
101 	}
102 }
103 
104 static int
105 help_emitsummary(char *topic, char *subtopic, char *desc)
106 {
107 	int	i;
108 
109 	pager_output("    ");
110 	pager_output(topic);
111 	i = strlen(topic);
112 	if (subtopic != NULL) {
113 		pager_output(" ");
114 		pager_output(subtopic);
115 		i += strlen(subtopic) + 1;
116 	}
117 	if (desc != NULL) {
118 		do {
119 			pager_output(" ");
120 		} while (i++ < 30);
121 		pager_output(desc);
122 	}
123 	return (pager_output("\n"));
124 }
125 
126 static int
127 command_help(int argc, char *argv[])
128 {
129 	char	buf[81];	/* XXX buffer size? */
130 	int	hfd, matched, doindex;
131 	char	*topic, *subtopic, *t, *s, *d;
132 
133 	/* page the help text from our load path */
134 	snprintf(buf, sizeof(buf), "%s/boot/%s", getenv("loaddev"),
135 	    HELP_FILENAME);
136 	if ((hfd = open(buf, O_RDONLY)) < 0) {
137 		printf("Verbose help not available, "
138 		    "use '?' to list commands\n");
139 		return (CMD_OK);
140 	}
141 
142 	/* pick up request from arguments */
143 	topic = subtopic = NULL;
144 	switch (argc) {
145 	case 3:
146 		subtopic = strdup(argv[2]);
147 		/* FALLTHROUGH */
148 	case 2:
149 		topic = strdup(argv[1]);
150 		break;
151 	case 1:
152 		topic = strdup("help");
153 		break;
154 	default:
155 		command_errmsg = "usage is 'help <topic> [<subtopic>]";
156 		close(hfd);
157 		return(CMD_ERROR);
158 	}
159 
160 	/* magic "index" keyword */
161 	doindex = strcmp(topic, "index") == 0? 1 : 0;
162 	matched = doindex;
163 
164 	/* Scan the helpfile looking for help matching the request */
165 	pager_open();
166 	while (help_getnext(hfd, &t, &s, &d)) {
167 
168 		if (doindex) {		/* dink around formatting */
169 			if (help_emitsummary(t, s, d))
170 				break;
171 
172 		} else if (strcmp(topic, t)) {
173 			/* topic mismatch */
174 			if (matched) {
175 				/* nothing more on this topic, stop scanning */
176 				break;
177 			}
178 		} else {
179 			/* topic matched */
180 			matched = 1;
181 			if ((subtopic == NULL && s == NULL) ||
182 			    (subtopic != NULL && s != NULL &&
183 			    strcmp(subtopic, s) == 0)) {
184 				/* exact match, print text */
185 				while (fgetstr(buf, 80, hfd) >= 0 &&
186 				    buf[0] != '#') {
187 					if (pager_output(buf))
188 						break;
189 					if (pager_output("\n"))
190 						break;
191 				}
192 			} else if (subtopic == NULL && s != NULL) {
193 				/* topic match, list subtopics */
194 				if (help_emitsummary(t, s, d))
195 					break;
196 			}
197 		}
198 		free(t);
199 		free(s);
200 		free(d);
201 		t = s = d = NULL;
202 	}
203 	free(t);
204 	free(s);
205 	free(d);
206 	pager_close();
207 	close(hfd);
208 	if (!matched) {
209 		snprintf(command_errbuf, sizeof(command_errbuf),
210 		    "no help available for '%s'", topic);
211 		free(topic);
212 		free(subtopic);
213 		return (CMD_ERROR);
214 	}
215 	free(topic);
216 	free(subtopic);
217 	return (CMD_OK);
218 }
219 
220 COMMAND_SET(commandlist, "?", "list commands", command_commandlist);
221 
222 /*
223  * Please note: although we use the pager for the list of commands,
224  * this routine is called from the ? FORTH function which then
225  * unconditionally prints some commands. This will lead to anomalous
226  * behavior. There's no 'pager_output' binding to FORTH to allow
227  * things to work right, so I'm documenting the bug rather than
228  * fixing it.
229  */
230 static int
231 command_commandlist(int argc __unused, char *argv[] __unused)
232 {
233 	struct bootblk_command	**cmdp;
234 	int	res;
235 	char	name[20];
236 
237 	res = 0;
238 	pager_open();
239 	res = pager_output("Available commands:\n");
240 	SET_FOREACH(cmdp, Xcommand_set) {
241 		if (res)
242 			break;
243 		if ((*cmdp)->c_name != NULL && (*cmdp)->c_desc != NULL) {
244 			snprintf(name, sizeof(name), "  %-15s  ",
245 			    (*cmdp)->c_name);
246 			pager_output(name);
247 			pager_output((*cmdp)->c_desc);
248 			res = pager_output("\n");
249 		}
250 	}
251 	pager_close();
252 	return (CMD_OK);
253 }
254 
255 /*
256  * XXX set/show should become set/echo if we have variable
257  * substitution happening.
258  */
259 
260 COMMAND_SET(show, "show", "show variable(s)", command_show);
261 
262 static int
263 command_show(int argc, char *argv[])
264 {
265 	struct env_var	*ev;
266 	char		*cp;
267 
268 	if (argc < 2) {
269 		/*
270 		 * With no arguments, print everything.
271 		 */
272 		pager_open();
273 		for (ev = environ; ev != NULL; ev = ev->ev_next) {
274 			pager_output(ev->ev_name);
275 			cp = getenv(ev->ev_name);
276 			if (cp != NULL) {
277 				pager_output("=");
278 				pager_output(cp);
279 			}
280 			if (pager_output("\n"))
281 				break;
282 		}
283 		pager_close();
284 	} else {
285 		if ((cp = getenv(argv[1])) != NULL) {
286 			printf("%s\n", cp);
287 		} else {
288 			snprintf(command_errbuf, sizeof(command_errbuf),
289 			    "variable '%s' not found", argv[1]);
290 			return (CMD_ERROR);
291 		}
292 	}
293 	return (CMD_OK);
294 }
295 
296 COMMAND_SET(set, "set", "set a variable", command_set);
297 
298 static int
299 command_set(int argc, char *argv[])
300 {
301 	int	err;
302 
303 	if (argc != 2) {
304 		command_errmsg = "wrong number of arguments";
305 		return (CMD_ERROR);
306 	} else {
307 #ifdef LOADER_VERIEXEC
308 		/*
309 		 * Impose restrictions if input is not verified
310 		 */
311 		const char *restricted[] = {
312 			"boot",
313 			"init",
314 			"loader.ve.",
315 			"rootfs",
316 			"secur",
317 			"vfs.",
318 			NULL,
319 		};
320 		const char **cp;
321 		int ves;
322 
323 		ves = ve_status_get(-1);
324 		if (ves == VE_UNVERIFIED_OK) {
325 #ifdef LOADER_VERIEXEC_TESTING
326 			printf("Checking: %s\n", argv[1]);
327 #endif
328 			for (cp = restricted; *cp; cp++) {
329 				if (strncmp(argv[1], *cp, strlen(*cp)) == 0) {
330 					printf("Ignoring restricted variable: %s\n",
331 					    argv[1]);
332 					return (CMD_OK);
333 				}
334 			}
335 		}
336 #endif
337 		if ((err = putenv(argv[1])) != 0) {
338 			command_errmsg = strerror(err);
339 			return (CMD_ERROR);
340 		}
341 	}
342 	return (CMD_OK);
343 }
344 
345 COMMAND_SET(unset, "unset", "unset a variable", command_unset);
346 
347 static int
348 command_unset(int argc, char *argv[])
349 {
350 	int	err;
351 
352 	if (argc != 2) {
353 		command_errmsg = "wrong number of arguments";
354 		return (CMD_ERROR);
355 	} else {
356 		if ((err = unsetenv(argv[1])) != 0) {
357 			command_errmsg = strerror(err);
358 			return (CMD_ERROR);
359 		}
360 	}
361 	return (CMD_OK);
362 }
363 
364 COMMAND_SET(echo, "echo", "echo arguments", command_echo);
365 
366 static int
367 command_echo(int argc, char *argv[])
368 {
369 	char	*s;
370 	int	nl, ch;
371 
372 	nl = 0;
373 	optind = 1;
374 	optreset = 1;
375 	while ((ch = getopt(argc, argv, "n")) != -1) {
376 		switch (ch) {
377 		case 'n':
378 			nl = 1;
379 			break;
380 		case '?':
381 		default:
382 			/* getopt has already reported an error */
383 			return (CMD_OK);
384 		}
385 	}
386 	argv += (optind);
387 	argc -= (optind);
388 
389 	s = unargv(argc, argv);
390 	if (s != NULL) {
391 		printf("%s", s);
392 		free(s);
393 	}
394 	if (!nl)
395 		printf("\n");
396 	return (CMD_OK);
397 }
398 
399 /*
400  * A passable emulation of the sh(1) command of the same name.
401  */
402 
403 COMMAND_SET(read, "read", "read input from the terminal", command_read);
404 
405 static int
406 command_read(int argc, char *argv[])
407 {
408 	char	*prompt;
409 	int	timeout;
410 	time_t	when;
411 	char	*cp;
412 	char	*name;
413 	char	buf[256];		/* XXX size? */
414 	int	c;
415 
416 	timeout = -1;
417 	prompt = NULL;
418 	optind = 1;
419 	optreset = 1;
420 	while ((c = getopt(argc, argv, "p:t:")) != -1) {
421 		switch (c) {
422 		case 'p':
423 			prompt = optarg;
424 			break;
425 		case 't':
426 			timeout = strtol(optarg, &cp, 0);
427 			if (cp == optarg) {
428 				snprintf(command_errbuf,
429 				    sizeof(command_errbuf),
430 				    "bad timeout '%s'", optarg);
431 				return (CMD_ERROR);
432 			}
433 			break;
434 		default:
435 			return (CMD_OK);
436 		}
437 	}
438 
439 	argv += (optind);
440 	argc -= (optind);
441 	name = (argc > 0) ? argv[0]: NULL;
442 
443 	if (prompt != NULL)
444 		printf("%s", prompt);
445 	if (timeout >= 0) {
446 		when = time(NULL) + timeout;
447 		while (!ischar())
448 			if (time(NULL) >= when)
449 				return (CMD_OK); /* is timeout an error? */
450 	}
451 
452 	ngets(buf, sizeof(buf));
453 
454 	if (name != NULL)
455 		setenv(name, buf, 1);
456 	return (CMD_OK);
457 }
458 
459 /*
460  * File pager
461  */
462 COMMAND_SET(more, "more", "show contents of a file", command_more);
463 
464 static int
465 command_more(int argc, char *argv[])
466 {
467 	int	i;
468 	int	res;
469 	char	line[80];
470 
471 	res = 0;
472 	pager_open();
473 	for (i = 1; (i < argc) && (res == 0); i++) {
474 		snprintf(line, sizeof(line), "*** FILE %s BEGIN ***\n",
475 		    argv[i]);
476 		if (pager_output(line))
477 			break;
478 		res = page_file(argv[i]);
479 		if (!res) {
480 			snprintf(line, sizeof(line), "*** FILE %s END ***\n",
481 			    argv[i]);
482 			res = pager_output(line);
483 		}
484 	}
485 	pager_close();
486 
487 	return (CMD_OK);
488 }
489 
490 static int
491 page_file(char *filename)
492 {
493 	int result;
494 
495 	result = pager_file(filename);
496 
497 	if (result == -1) {
498 		snprintf(command_errbuf, sizeof(command_errbuf),
499 		    "error showing %s", filename);
500 	}
501 
502 	return (result);
503 }
504 
505 /*
506  * List all disk-like devices
507  */
508 COMMAND_SET(lsdev, "lsdev", "list all devices", command_lsdev);
509 
510 static int
511 command_lsdev(int argc, char *argv[])
512 {
513 	int	verbose, ch, i;
514 	char	line[80];
515 
516 	verbose = 0;
517 	optind = 1;
518 	optreset = 1;
519 	while ((ch = getopt(argc, argv, "v")) != -1) {
520 		switch (ch) {
521 		case 'v':
522 			verbose = 1;
523 			break;
524 		case '?':
525 		default:
526 			/* getopt has already reported an error */
527 			return (CMD_OK);
528 		}
529 	}
530 	argv += (optind);
531 	argc -= (optind);
532 
533 	pager_open();
534 	for (i = 0; devsw[i] != NULL; i++) {
535 		if (devsw[i]->dv_print != NULL) {
536 			if (devsw[i]->dv_print(verbose))
537 				break;
538 		} else {
539 			snprintf(line, sizeof(line), "%s: (unknown)\n",
540 			    devsw[i]->dv_name);
541 			if (pager_output(line))
542 				break;
543 		}
544 	}
545 	pager_close();
546 	return (CMD_OK);
547 }
548 
549 static int
550 command_readtest(int argc, char *argv[])
551 {
552 	int fd;
553 	time_t start, end;
554 	char buf[512];
555 	ssize_t rv, count = 0;
556 
557 	if (argc != 2) {
558 		snprintf(command_errbuf, sizeof(command_errbuf),
559 		  "Usage: readtest <filename>");
560 		return (CMD_ERROR);
561 	}
562 
563 	start = getsecs();
564 	if ((fd = open(argv[1], O_RDONLY)) < 0) {
565 		snprintf(command_errbuf, sizeof(command_errbuf),
566 		  "can't open '%s'", argv[1]);
567 		return (CMD_ERROR);
568 	}
569 	while ((rv = read(fd, buf, sizeof(buf))) > 0)
570 		count += rv;
571 	end = getsecs();
572 
573 	printf("Received %zd bytes during %jd seconds\n", count, (intmax_t)end - start);
574 	close(fd);
575 	return (CMD_OK);
576 }
577 
578 COMMAND_SET(readtest, "readtest", "Time a file read", command_readtest);
579 
580 static int
581 command_quit(int argc, char *argv[])
582 {
583 	exit(0);
584 	return (CMD_OK);
585 }
586 
587 COMMAND_SET(quit, "quit", "exit the loader", command_quit);
588