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