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 
29 #include <stand.h>
30 #include <string.h>
31 
32 #include "bootstrap.h"
33 
34 const char	*command_errmsg;
35 /* XXX should have procedural interface for setting, size limit? */
36 char		command_errbuf[COMMAND_ERRBUFSZ];
37 
38 static int page_file(char *filename);
39 
40 /* BEGIN CSTYLED */
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 /* END CSTYLED */
62 COMMAND_SET(help, "help", "detailed help", command_help);
63 
64 static int
help_getnext(int fd,char ** topic,char ** subtopic,char ** desc)65 help_getnext(int fd, char **topic, char **subtopic, char **desc)
66 {
67 	char	line[81], *cp, *ep;
68 
69 	/* Make sure we provide sane values. */
70 	*topic = *subtopic = *desc = NULL;
71 	for (;;) {
72 		if (fgetstr(line, 80, fd) < 0)
73 			return (0);
74 
75 		if (strlen(line) < 3 || line[0] != '#' || line[1] != ' ')
76 			continue;
77 
78 		*topic = *subtopic = *desc = NULL;
79 		cp = line + 2;
80 		while (cp != NULL && *cp != 0) {
81 			ep = strchr(cp, ' ');
82 			if (*cp == 'T' && *topic == NULL) {
83 				if (ep != NULL)
84 					*ep++ = 0;
85 				*topic = strdup(cp + 1);
86 			} else if (*cp == 'S' && *subtopic == NULL) {
87 				if (ep != NULL)
88 					*ep++ = 0;
89 				*subtopic = strdup(cp + 1);
90 			} else if (*cp == 'D') {
91 				*desc = strdup(cp + 1);
92 				ep = NULL;
93 			}
94 			cp = ep;
95 		}
96 		if (*topic == NULL) {
97 			free(*subtopic);
98 			free(*desc);
99 			*subtopic = *desc = NULL;
100 			continue;
101 		}
102 		return (1);
103 	}
104 }
105 
106 static int
help_emitsummary(char * topic,char * subtopic,char * desc)107 help_emitsummary(char *topic, char *subtopic, char *desc)
108 {
109 	int	i;
110 
111 	pager_output("    ");
112 	pager_output(topic);
113 	i = strlen(topic);
114 	if (subtopic != NULL) {
115 		pager_output(" ");
116 		pager_output(subtopic);
117 		i += strlen(subtopic) + 1;
118 	}
119 	if (desc != NULL) {
120 		do {
121 			pager_output(" ");
122 		} while (i++ < 30);
123 		pager_output(desc);
124 	}
125 	return (pager_output("\n"));
126 }
127 
128 
129 static int
command_help(int argc,char * argv[])130 command_help(int argc, char *argv[])
131 {
132 	char	buf[81];	/* XXX buffer size? */
133 	int	hfd, matched, doindex;
134 	char	*topic, *subtopic, *t, *s, *d;
135 
136 	/* page the help text from our load path */
137 	snprintf(buf, sizeof (buf), "%s/boot/loader.help", getenv("loaddev"));
138 	if ((hfd = open(buf, O_RDONLY)) < 0) {
139 		printf("Verbose help not available, "
140 		    "use '?' to list commands\n");
141 		return (CMD_OK);
142 	}
143 
144 	/* pick up request from arguments */
145 	topic = subtopic = NULL;
146 	switch (argc) {
147 	case 3:
148 		subtopic = strdup(argv[2]);
149 		/* FALLTHROUGH */
150 	case 2:
151 		topic = strdup(argv[1]);
152 		break;
153 	case 1:
154 		topic = strdup("help");
155 		break;
156 	default:
157 		command_errmsg = "usage is 'help <topic> [<subtopic>]";
158 		close(hfd);
159 		return (CMD_ERROR);
160 	}
161 
162 	/* magic "index" keyword */
163 	doindex = strcmp(topic, "index") == 0? 1 : 0;
164 	matched = doindex;
165 
166 	/* Scan the helpfile looking for help matching the request */
167 	pager_open();
168 	while (help_getnext(hfd, &t, &s, &d)) {
169 
170 		if (doindex) {		/* dink around formatting */
171 			if (help_emitsummary(t, s, d))
172 				break;
173 
174 		} else if (strcmp(topic, t)) {
175 			/* topic mismatch */
176 			if (matched) {
177 				/* nothing more on this topic, stop scanning */
178 				break;
179 			}
180 		} else {
181 			/* topic matched */
182 			matched = 1;
183 			if ((subtopic == NULL && s == NULL) ||
184 			    (subtopic != NULL && s != NULL &&
185 			    strcmp(subtopic, s) == 0)) {
186 				/* exact match, print text */
187 				while (fgetstr(buf, 80, hfd) >= 0 &&
188 				    buf[0] != '#') {
189 					if (pager_output(buf))
190 						break;
191 					if (pager_output("\n"))
192 						break;
193 				}
194 			} else if (subtopic == NULL && s != NULL) {
195 				/* topic match, list subtopics */
196 				if (help_emitsummary(t, s, d))
197 					break;
198 			}
199 		}
200 		free(t);
201 		free(s);
202 		free(d);
203 		t = s = d = NULL;
204 	}
205 	free(t);
206 	free(s);
207 	free(d);
208 	pager_close();
209 	close(hfd);
210 	if (!matched) {
211 		snprintf(command_errbuf, sizeof (command_errbuf),
212 		    "no help available for '%s'", topic);
213 		free(topic);
214 		free(subtopic);
215 		return (CMD_ERROR);
216 	}
217 	free(topic);
218 	free(subtopic);
219 	return (CMD_OK);
220 }
221 
222 COMMAND_SET(commandlist, "?", "list commands", command_commandlist);
223 
224 static int
command_commandlist(int argc __unused,char * argv[]__unused)225 command_commandlist(int argc __unused, char *argv[] __unused)
226 {
227 	struct bootblk_command	**cmdp;
228 	int	res;
229 	char	name[20];
230 
231 	res = 0;
232 	pager_open();
233 	res = pager_output("Available commands:\n");
234 	SET_FOREACH(cmdp, Xcommand_set) {
235 		if (res)
236 			break;
237 		if ((*cmdp)->c_name != NULL && (*cmdp)->c_desc != NULL) {
238 			snprintf(name, sizeof (name),"  %-15s  ",
239 			    (*cmdp)->c_name);
240 			pager_output(name);
241 			pager_output((*cmdp)->c_desc);
242 			res = pager_output("\n");
243 		}
244 	}
245 	pager_close();
246 	return (CMD_OK);
247 }
248 
249 /*
250  * XXX set/show should become set/echo if we have variable
251  * substitution happening.
252  */
253 
254 COMMAND_SET(show, "show", "show variable(s)", command_show);
255 
256 static int
command_show(int argc,char * argv[])257 command_show(int argc, char *argv[])
258 {
259 	struct env_var	*ev;
260 	char		*cp;
261 
262 	if (argc < 2) {
263 		/*
264 		 * With no arguments, print everything.
265 		 */
266 		pager_open();
267 		for (ev = environ; ev != NULL; ev = ev->ev_next) {
268 			pager_output(ev->ev_name);
269 			cp = getenv(ev->ev_name);
270 			if (cp != NULL) {
271 				pager_output("=");
272 				pager_output(cp);
273 			}
274 			if (pager_output("\n"))
275 				break;
276 		}
277 		pager_close();
278 	} else {
279 		if ((cp = getenv(argv[1])) != NULL) {
280 			printf("%s\n", cp);
281 		} else {
282 			snprintf(command_errbuf, sizeof (command_errbuf),
283 			    "variable '%s' not found", argv[1]);
284 			return (CMD_ERROR);
285 		}
286 	}
287 	return (CMD_OK);
288 }
289 
290 COMMAND_SET(set, "set", "set a variable", command_set);
291 
292 static int
command_set(int argc,char * argv[])293 command_set(int argc, char *argv[])
294 {
295 	int	err;
296 
297 	if (argc != 2) {
298 		command_errmsg = "wrong number of arguments";
299 		return (CMD_ERROR);
300 	} else {
301 		if ((err = putenv(argv[1])) != 0) {
302 			command_errmsg = strerror(err);
303 			return (CMD_ERROR);
304 		}
305 	}
306 	return (CMD_OK);
307 }
308 
309 COMMAND_SET(setprop, "setprop", "set a variable", command_setprop);
310 
311 static int
command_setprop(int argc,char * argv[])312 command_setprop(int argc, char *argv[])
313 {
314 	int	err;
315 
316 	if (argc != 3) {
317 		command_errmsg = "wrong number of arguments";
318 		return (CMD_ERROR);
319 	} else {
320 		if ((err = setenv(argv[1], argv[2], 1)) != 0) {
321 			command_errmsg = strerror(err);
322 			return (CMD_ERROR);
323 		}
324 	}
325 	return (CMD_OK);
326 }
327 
328 COMMAND_SET(unset, "unset", "unset a variable", command_unset);
329 
330 static int
command_unset(int argc,char * argv[])331 command_unset(int argc, char *argv[])
332 {
333 	int	err;
334 
335 	if (argc != 2) {
336 		command_errmsg = "wrong number of arguments";
337 		return (CMD_ERROR);
338 	} else {
339 		if ((err = unsetenv(argv[1])) != 0) {
340 			command_errmsg = strerror(err);
341 			return (CMD_ERROR);
342 		}
343 	}
344 	return (CMD_OK);
345 }
346 
347 COMMAND_SET(echo, "echo", "echo arguments", command_echo);
348 
349 static int
command_echo(int argc,char * argv[])350 command_echo(int argc, char *argv[])
351 {
352 	char	*s;
353 	int	nl, ch;
354 
355 	nl = 0;
356 	optind = 1;
357 	optreset = 1;
358 	while ((ch = getopt(argc, argv, "n")) != -1) {
359 		switch (ch) {
360 		case 'n':
361 			nl = 1;
362 			break;
363 		case '?':
364 		default:
365 			/* getopt has already reported an error */
366 			return (CMD_OK);
367 		}
368 	}
369 	argv += (optind);
370 	argc -= (optind);
371 
372 	s = unargv(argc, argv);
373 	if (s != NULL) {
374 		printf("%s", s);
375 		free(s);
376 	}
377 	if (!nl)
378 		printf("\n");
379 	return (CMD_OK);
380 }
381 
382 /*
383  * A passable emulation of the sh(1) command of the same name.
384  */
385 
386 COMMAND_SET(read, "read", "read input from the terminal", command_read);
387 
388 static int
command_read(int argc,char * argv[])389 command_read(int argc, char *argv[])
390 {
391 	char	*prompt;
392 	int	timeout;
393 	time_t	when;
394 	char	*cp;
395 	char	*name;
396 	char	buf[256];		/* XXX size? */
397 	int	c;
398 
399 	timeout = -1;
400 	prompt = NULL;
401 	optind = 1;
402 	optreset = 1;
403 	while ((c = getopt(argc, argv, "p:t:")) != -1) {
404 		switch (c) {
405 		case 'p':
406 			prompt = optarg;
407 			break;
408 		case 't':
409 			timeout = strtol(optarg, &cp, 0);
410 			if (cp == optarg) {
411 				snprintf(command_errbuf,
412 				    sizeof (command_errbuf),
413 				    "bad timeout '%s'", optarg);
414 				return (CMD_ERROR);
415 			}
416 			break;
417 		default:
418 			return (CMD_OK);
419 		}
420 	}
421 
422 	argv += (optind);
423 	argc -= (optind);
424 	name = (argc > 0) ? argv[0]: NULL;
425 
426 	if (prompt != NULL)
427 		printf("%s", prompt);
428 	if (timeout >= 0) {
429 		when = time(NULL) + timeout;
430 		while (!ischar())
431 			if (time(NULL) >= when)
432 				return (CMD_OK); /* is timeout an error? */
433 	}
434 
435 	ngets(buf, sizeof (buf));
436 
437 	if (name != NULL)
438 		setenv(name, buf, 1);
439 	return (CMD_OK);
440 }
441 
442 /*
443  * File pager
444  */
445 COMMAND_SET(more, "more", "show contents of a file", command_more);
446 
447 static int
command_more(int argc,char * argv[])448 command_more(int argc, char *argv[])
449 {
450 	int	i;
451 	int	res;
452 	char	line[80];
453 
454 	res = 0;
455 	pager_open();
456 	for (i = 1; (i < argc) && (res == 0); i++) {
457 		snprintf(line, sizeof (line), "*** FILE %s BEGIN ***\n",
458 		    argv[i]);
459 		if (pager_output(line))
460 			break;
461 		res = page_file(argv[i]);
462 		if (!res) {
463 			snprintf(line, sizeof (line), "*** FILE %s END ***\n",
464 			    argv[i]);
465 			res = pager_output(line);
466 		}
467 	}
468 	pager_close();
469 
470 	if (res == 0)
471 		return (CMD_OK);
472 	else
473 		return (CMD_ERROR);
474 }
475 
476 static int
page_file(char * filename)477 page_file(char *filename)
478 {
479 	int result;
480 
481 	result = pager_file(filename);
482 
483 	if (result == -1) {
484 		snprintf(command_errbuf, sizeof (command_errbuf),
485 		    "error showing %s", filename);
486 	}
487 
488 	return (result);
489 }
490 
491 /*
492  * List all disk-like devices
493  */
494 COMMAND_SET(lsdev, "lsdev", "list all devices", command_lsdev);
495 
496 static int
command_lsdev(int argc,char * argv[])497 command_lsdev(int argc, char *argv[])
498 {
499 	int	verbose, ch, i;
500 	char	line[80];
501 
502 	verbose = 0;
503 	optind = 1;
504 	optreset = 1;
505 	while ((ch = getopt(argc, argv, "v")) != -1) {
506 		switch (ch) {
507 		case 'v':
508 			verbose = 1;
509 			break;
510 		case '?':
511 		default:
512 			/* getopt has already reported an error */
513 			return (CMD_OK);
514 		}
515 	}
516 	argv += (optind);
517 	argc -= (optind);
518 
519 	pager_open();
520 	for (i = 0; devsw[i] != NULL; i++) {
521 		if (devsw[i]->dv_print != NULL) {
522 			if (devsw[i]->dv_print(verbose))
523 				break;
524 		} else {
525 			snprintf(line, sizeof (line), "%s: (unknown)\n",
526 			    devsw[i]->dv_name);
527 			if (pager_output(line))
528 				break;
529 		}
530 	}
531 	pager_close();
532 	return (CMD_OK);
533 }
534