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