xref: /illumos-gate/usr/src/common/ficl/emu/loader_emu.c (revision bde334a8dbd66dfa70ce4d7fc9dcad6e1ae45fe4)
1 /*
2  * Copyright (c) 1998 Michael Smith <msmith@freebsd.org>
3  * Copyright 2019 OmniOS Community Edition (OmniOSce) Association.
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <fcntl.h>
31 #include <errno.h>
32 #include <stdlib.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <strings.h>
36 #include <limits.h>
37 #include <unistd.h>
38 #include <dirent.h>
39 #include <macros.h>
40 #include <sys/systeminfo.h>
41 #include <sys/queue.h>
42 #include <sys/mnttab.h>
43 #include "gfx_fb.h"
44 #include "ficl.h"
45 
46 /* Commands and return values; nonzero return sets command_errmsg != NULL */
47 typedef int (bootblk_cmd_t)(int argc, char *argv[]);
48 #define	CMD_OK		0
49 #define	CMD_ERROR	1
50 
51 /*
52  * Support for commands
53  */
54 struct bootblk_command
55 {
56 	const char *c_name;
57 	const char *c_desc;
58 	bootblk_cmd_t *c_fn;
59 	STAILQ_ENTRY(bootblk_command) next;
60 };
61 
62 #define	MDIR_REMOVED	0x0001
63 #define	MDIR_NOHINTS	0x0002
64 
65 struct moduledir {
66 	char	*d_path;	/* path of modules directory */
67 	uchar_t	*d_hints;	/* content of linker.hints file */
68 	int	d_hintsz;	/* size of hints data */
69 	int	d_flags;
70 	STAILQ_ENTRY(moduledir) d_link;
71 };
72 static STAILQ_HEAD(, moduledir) moduledir_list =
73     STAILQ_HEAD_INITIALIZER(moduledir_list);
74 
75 static const char *default_searchpath = "/platform/i86pc";
76 
77 static char typestr[] = "?fc?d?b? ?l?s?w";
78 static int	ls_getdir(char **pathp);
79 extern char **_environ;
80 
81 char	*command_errmsg;
82 char	command_errbuf[256];
83 
84 extern void pager_open(void);
85 extern void pager_close(void);
86 extern int pager_output(const char *);
87 extern int pager_file(const char *);
88 static int page_file(char *);
89 static int include(const char *);
90 
91 static int command_help(int argc, char *argv[]);
92 static int command_commandlist(int argc, char *argv[]);
93 static int command_show(int argc, char *argv[]);
94 static int command_set(int argc, char *argv[]);
95 static int command_setprop(int argc, char *argv[]);
96 static int command_unset(int argc, char *argv[]);
97 static int command_echo(int argc, char *argv[]);
98 static int command_read(int argc, char *argv[]);
99 static int command_more(int argc, char *argv[]);
100 static int command_ls(int argc, char *argv[]);
101 static int command_include(int argc, char *argv[]);
102 static int command_autoboot(int argc, char *argv[]);
103 static int command_boot(int argc, char *argv[]);
104 static int command_unload(int argc, char *argv[]);
105 static int command_load(int argc, char *argv[]);
106 static int command_reboot(int argc, char *argv[]);
107 static int command_sifting(int argc, char *argv[]);
108 static int command_framebuffer(int argc, char *argv[]);
109 
110 #define	BF_PARSE	100
111 #define	BF_DICTSIZE	30000
112 
113 /* update when loader version will change */
114 static const char bootprog_rev[] = "1.1";
115 STAILQ_HEAD(cmdh, bootblk_command) commands;
116 
117 /*
118  * BootForth   Interface to Ficl Forth interpreter.
119  */
120 
121 ficlSystem *bf_sys;
122 ficlVm	*bf_vm;
123 
124 /*
125  * Redistribution and use in source and binary forms, with or without
126  * modification, are permitted provided that the following conditions
127  * are met:
128  * 1. Redistributions of source code must retain the above copyright
129  *    notice, this list of conditions and the following disclaimer.
130  * 2. Redistributions in binary form must reproduce the above copyright
131  *    notice, this list of conditions and the following disclaimer in the
132  *    documentation and/or other materials provided with the distribution.
133  *
134  * Jordan K. Hubbard
135  * 29 August 1998
136  *
137  * The meat of the simple parser.
138  */
139 
140 static void	 clean(void);
141 static int	 insert(int *argcp, char *buf);
142 
143 #define	PARSE_BUFSIZE	1024	/* maximum size of one element */
144 #define	MAXARGS		20	/* maximum number of elements */
145 static	char		*args[MAXARGS];
146 
147 #define	DIGIT(x)	\
148 	(isdigit(x) ? (x) - '0' : islower(x) ? (x) + 10 - 'a' : (x) + 10 - 'A')
149 
150 /*
151  * backslash: Return malloc'd copy of str with all standard "backslash
152  * processing" done on it.  Original can be free'd if desired.
153  */
154 char *
155 backslash(char *str)
156 {
157 	/*
158 	 * Remove backslashes from the strings. Turn \040 etc. into a single
159 	 * character (we allow eight bit values). Currently NUL is not
160 	 * allowed.
161 	 *
162 	 * Turn "\n" and "\t" into '\n' and '\t' characters. Etc.
163 	 */
164 	char *new_str;
165 	int seenbs = 0;
166 	int i = 0;
167 
168 	if ((new_str = strdup(str)) == NULL)
169 		return (NULL);
170 
171 	while (*str) {
172 		if (seenbs) {
173 			seenbs = 0;
174 			switch (*str) {
175 			case '\\':
176 				new_str[i++] = '\\';
177 				str++;
178 			break;
179 
180 			/* preserve backslashed quotes, dollar signs */
181 			case '\'':
182 			case '"':
183 			case '$':
184 				new_str[i++] = '\\';
185 				new_str[i++] = *str++;
186 			break;
187 
188 			case 'b':
189 				new_str[i++] = '\b';
190 				str++;
191 			break;
192 
193 			case 'f':
194 				new_str[i++] = '\f';
195 				str++;
196 			break;
197 
198 			case 'r':
199 				new_str[i++] = '\r';
200 				str++;
201 			break;
202 
203 			case 'n':
204 				new_str[i++] = '\n';
205 				str++;
206 			break;
207 
208 			case 's':
209 				new_str[i++] = ' ';
210 				str++;
211 			break;
212 
213 			case 't':
214 				new_str[i++] = '\t';
215 				str++;
216 			break;
217 
218 			case 'v':
219 				new_str[i++] = '\13';
220 				str++;
221 			break;
222 
223 			case 'z':
224 				str++;
225 			break;
226 
227 			case '0': case '1': case '2': case '3': case '4':
228 			case '5': case '6': case '7': case '8': case '9': {
229 				char val;
230 
231 				/* Three digit octal constant? */
232 				if (*str >= '0' && *str <= '3' &&
233 				    *(str + 1) >= '0' && *(str + 1) <= '7' &&
234 				    *(str + 2) >= '0' && *(str + 2) <= '7') {
235 
236 					val = (DIGIT(*str) << 6) +
237 					    (DIGIT(*(str + 1)) << 3) +
238 					    DIGIT(*(str + 2));
239 
240 					/*
241 					 * Allow null value if user really
242 					 * wants to shoot at feet, but beware!
243 					 */
244 					new_str[i++] = val;
245 					str += 3;
246 					break;
247 				}
248 
249 				/*
250 				 * One or two digit hex constant?
251 				 * If two are there they will both be taken.
252 				 * Use \z to split them up if this is not
253 				 * wanted.
254 				 */
255 				if (*str == '0' &&
256 				    (*(str + 1) == 'x' || *(str + 1) == 'X') &&
257 				    isxdigit(*(str + 2))) {
258 					val = DIGIT(*(str + 2));
259 					if (isxdigit(*(str + 3))) {
260 						val = (val << 4) +
261 						    DIGIT(*(str + 3));
262 						str += 4;
263 					} else
264 						str += 3;
265 					/* Yep, allow null value here too */
266 					new_str[i++] = val;
267 					break;
268 				}
269 			}
270 			break;
271 
272 			default:
273 				new_str[i++] = *str++;
274 			break;
275 			}
276 		} else {
277 			if (*str == '\\') {
278 				seenbs = 1;
279 				str++;
280 			} else
281 				new_str[i++] = *str++;
282 		}
283 	}
284 
285 	if (seenbs) {
286 		/*
287 		 * The final character was a '\'.
288 		 * Put it in as a single backslash.
289 		 */
290 		new_str[i++] = '\\';
291 	}
292 	new_str[i] = '\0';
293 	return (new_str);
294 }
295 
296 /*
297  * parse: accept a string of input and "parse" it for backslash
298  * substitutions and environment variable expansions (${var}),
299  * returning an argc/argv style vector of whitespace separated
300  * arguments.  Returns 0 on success, 1 on failure (ok, ok, so I
301  * wimped-out on the error codes! :).
302  *
303  * Note that the argv array returned must be freed by the caller, but
304  * we own the space allocated for arguments and will free that on next
305  * invocation.  This allows argv consumers to modify the array if
306  * required.
307  *
308  * NB: environment variables that expand to more than one whitespace
309  * separated token will be returned as a single argv[] element, not
310  * split in turn.  Expanded text is also immune to further backslash
311  * elimination or expansion since this is a one-pass, non-recursive
312  * parser.  You didn't specify more than this so if you want more, ask
313  * me. - jkh
314  */
315 
316 #define	PARSE_FAIL(expr)	\
317 if (expr) { \
318     printf("fail at line %d\n", __LINE__); \
319     clean(); \
320     free(copy); \
321     free(buf); \
322     return (1); \
323 }
324 
325 /* Accept the usual delimiters for a variable, returning counterpart */
326 static char
327 isdelim(int ch)
328 {
329 	if (ch == '{')
330 		return ('}');
331 	else if (ch == '(')
332 		return (')');
333 	return ('\0');
334 }
335 
336 static int
337 isquote(int ch)
338 {
339 	return (ch == '\'');
340 }
341 
342 static int
343 isdquote(int ch)
344 {
345 	return (ch == '"');
346 }
347 
348 int
349 parse(int *argc, char ***argv, char *str)
350 {
351 	int ac;
352 	char *val, *p, *q, *copy = NULL;
353 	size_t i = 0;
354 	char token, tmp, quote, dquote, *buf;
355 	enum { STR, VAR, WHITE } state;
356 
357 	ac = *argc = 0;
358 	dquote = quote = 0;
359 	if (!str || (p = copy = backslash(str)) == NULL)
360 		return (1);
361 
362 	/* Initialize vector and state */
363 	clean();
364 	state = STR;
365 	buf = (char *)malloc(PARSE_BUFSIZE);
366 	token = 0;
367 
368 	/* And awaaaaaaaaay we go! */
369 	while (*p) {
370 		switch (state) {
371 		case STR:
372 			if ((*p == '\\') && p[1]) {
373 				p++;
374 				PARSE_FAIL(i == (PARSE_BUFSIZE - 1));
375 				buf[i++] = *p++;
376 			} else if (isquote(*p)) {
377 				quote = quote ? 0 : *p;
378 				if (dquote) { /* keep quote */
379 					PARSE_FAIL(i == (PARSE_BUFSIZE - 1));
380 					buf[i++] = *p++;
381 				} else
382 					++p;
383 			} else if (isdquote(*p)) {
384 				dquote = dquote ? 0 : *p;
385 				if (quote) { /* keep dquote */
386 					PARSE_FAIL(i == (PARSE_BUFSIZE - 1));
387 					buf[i++] = *p++;
388 				} else
389 					++p;
390 			} else if (isspace(*p) && !quote && !dquote) {
391 				state = WHITE;
392 				if (i) {
393 					buf[i] = '\0';
394 					PARSE_FAIL(insert(&ac, buf));
395 					i = 0;
396 				}
397 				++p;
398 			} else if (*p == '$' && !quote) {
399 				token = isdelim(*(p + 1));
400 				if (token)
401 					p += 2;
402 				else
403 					++p;
404 				state = VAR;
405 			} else {
406 				PARSE_FAIL(i == (PARSE_BUFSIZE - 1));
407 				buf[i++] = *p++;
408 			}
409 		break;
410 
411 		case WHITE:
412 			if (isspace(*p))
413 				++p;
414 			else
415 				state = STR;
416 		break;
417 
418 		case VAR:
419 			if (token) {
420 				PARSE_FAIL((q = strchr(p, token)) == NULL);
421 			} else {
422 				q = p;
423 				while (*q && !isspace(*q))
424 					++q;
425 			}
426 			tmp = *q;
427 			*q = '\0';
428 			if ((val = getenv(p)) != NULL) {
429 				size_t len = strlen(val);
430 
431 				(void) strncpy(buf + i, val,
432 				    PARSE_BUFSIZE - (i + 1));
433 				i += min(len, PARSE_BUFSIZE - 1);
434 			}
435 			*q = tmp;	/* restore value */
436 			p = q + (token ? 1 : 0);
437 			state = STR;
438 		break;
439 		}
440 	}
441 	/* missing terminating ' or " */
442 	PARSE_FAIL(quote || dquote);
443 	/* If at end of token, add it */
444 	if (i && state == STR) {
445 		buf[i] = '\0';
446 		PARSE_FAIL(insert(&ac, buf));
447 	}
448 	args[ac] = NULL;
449 	*argc = ac;
450 	*argv = (char **)malloc((sizeof (char *) * ac + 1));
451 	bcopy(args, *argv, sizeof (char *) * ac + 1);
452 	free(buf);
453 	free(copy);
454 	return (0);
455 }
456 
457 #define	MAXARGS	20
458 
459 /* Clean vector space */
460 static void
461 clean(void)
462 {
463 	int i;
464 
465 	for (i = 0; i < MAXARGS; i++) {
466 		if (args[i] != NULL) {
467 			free(args[i]);
468 			args[i] = NULL;
469 		}
470 	}
471 }
472 
473 static int
474 insert(int *argcp, char *buf)
475 {
476 	if (*argcp >= MAXARGS)
477 		return (1);
478 	args[(*argcp)++] = strdup(buf);
479 	return (0);
480 }
481 
482 static char *
483 isadir(void)
484 {
485 	char *buf;
486 	size_t bufsize = 20;
487 	int ret;
488 
489 	if ((buf = malloc(bufsize)) == NULL)
490 		return (NULL);
491 	ret = sysinfo(SI_ARCHITECTURE_K, buf, bufsize);
492 	if (ret == -1) {
493 		free(buf);
494 		return (NULL);
495 	}
496 	return (buf);
497 }
498 
499 /*
500  * Shim for taking commands from BF and passing them out to 'standard'
501  * argv/argc command functions.
502  */
503 static void
504 bf_command(ficlVm *vm)
505 {
506 	char *name, *line, *tail, *cp;
507 	size_t len;
508 	struct bootblk_command *cmdp;
509 	bootblk_cmd_t *cmd;
510 	int nstrings, i;
511 	int argc, result;
512 	char **argv;
513 
514 	/* Get the name of the current word */
515 	name = vm->runningWord->name;
516 
517 	/* Find our command structure */
518 	cmd = NULL;
519 	STAILQ_FOREACH(cmdp, &commands, next) {
520 		if ((cmdp->c_name != NULL) && strcmp(name, cmdp->c_name) == 0)
521 			cmd = cmdp->c_fn;
522 	}
523 	if (cmd == NULL)
524 		printf("callout for unknown command '%s'\n", name);
525 
526 	/* Check whether we have been compiled or are being interpreted */
527 	if (ficlStackPopInteger(ficlVmGetDataStack(vm))) {
528 		/*
529 		 * Get parameters from stack, in the format:
530 		 * an un ... a2 u2 a1 u1 n --
531 		 * Where n is the number of strings, a/u are pairs of
532 		 * address/size for strings, and they will be concatenated
533 		 * in LIFO order.
534 		 */
535 		nstrings = ficlStackPopInteger(ficlVmGetDataStack(vm));
536 		for (i = 0, len = 0; i < nstrings; i++)
537 			len += ficlStackFetch(ficlVmGetDataStack(vm),
538 			    i * 2).i + 1;
539 		line = malloc(strlen(name) + len + 1);
540 		(void) strcpy(line, name);
541 
542 		if (nstrings)
543 			for (i = 0; i < nstrings; i++) {
544 				len = ficlStackPopInteger(
545 				    ficlVmGetDataStack(vm));
546 				cp = ficlStackPopPointer(
547 				    ficlVmGetDataStack(vm));
548 				(void) strcat(line, " ");
549 				(void) strncat(line, cp, len);
550 			}
551 	} else {
552 		/* Get remainder of invocation */
553 		tail = ficlVmGetInBuf(vm);
554 		for (cp = tail, len = 0;
555 		    cp != vm->tib.end && *cp != 0 && *cp != '\n'; cp++, len++)
556 			;
557 
558 		line = malloc(strlen(name) + len + 2);
559 		(void) strcpy(line, name);
560 		if (len > 0) {
561 			(void) strcat(line, " ");
562 			(void) strncat(line, tail, len);
563 			ficlVmUpdateTib(vm, tail + len);
564 		}
565 	}
566 
567 	command_errmsg = command_errbuf;
568 	command_errbuf[0] = 0;
569 	if (!parse(&argc, &argv, line)) {
570 		result = (cmd)(argc, argv);
571 		free(argv);
572 	} else {
573 		result = BF_PARSE;
574 	}
575 	free(line);
576 	/*
577 	 * If there was error during nested ficlExec(), we may no longer have
578 	 * valid environment to return.  Throw all exceptions from here.
579 	 */
580 	if (result != 0)
581 		ficlVmThrow(vm, result);
582 	/* This is going to be thrown!!! */
583 	ficlStackPushInteger(ficlVmGetDataStack(vm), result);
584 }
585 
586 static char *
587 get_currdev(void)
588 {
589 	int ret;
590 	char *currdev;
591 	FILE *fp;
592 	struct mnttab mpref = {0};
593 	struct mnttab mp = {0};
594 
595 	mpref.mnt_mountp = "/";
596 	fp = fopen(MNTTAB, "r");
597 
598 	/* do the best we can to return something... */
599 	if (fp == NULL)
600 		return (strdup(":"));
601 
602 	ret = getmntany(fp, &mp, &mpref);
603 	(void) fclose(fp);
604 	if (ret == 0)
605 		(void) asprintf(&currdev, "zfs:%s:", mp.mnt_special);
606 	else
607 		return (strdup(":"));
608 
609 	return (currdev);
610 }
611 
612 /*
613  * Replace a word definition (a builtin command) with another
614  * one that:
615  *
616  *        - Throw error results instead of returning them on the stack
617  *        - Pass a flag indicating whether the word was compiled or is
618  *          being interpreted.
619  *
620  * There is one major problem with builtins that cannot be overcome
621  * in anyway, except by outlawing it. We want builtins to behave
622  * differently depending on whether they have been compiled or they
623  * are being interpreted. Notice that this is *not* the interpreter's
624  * current state. For example:
625  *
626  * : example ls ; immediate
627  * : problem example ;		\ "ls" gets executed while compiling
628  * example			\ "ls" gets executed while interpreting
629  *
630  * Notice that, though the current state is different in the two
631  * invocations of "example", in both cases "ls" has been
632  * *compiled in*, which is what we really want.
633  *
634  * The problem arises when you tick the builtin. For example:
635  *
636  * : example-1 ['] ls postpone literal ; immediate
637  * : example-2 example-1 execute ; immediate
638  * : problem example-2 ;
639  * example-2
640  *
641  * We have no way, when we get EXECUTEd, of knowing what our behavior
642  * should be. Thus, our only alternative is to "outlaw" this. See RFI
643  * 0007, and ANS Forth Standard's appendix D, item 6.7 for a related
644  * problem, concerning compile semantics.
645  *
646  * The problem is compounded by the fact that "' builtin CATCH" is valid
647  * and desirable. The only solution is to create an intermediary word.
648  * For example:
649  *
650  * : my-ls ls ;
651  * : example ['] my-ls catch ;
652  *
653  * So, with the below implementation, here is a summary of the behavior
654  * of builtins:
655  *
656  * ls -l				\ "interpret" behavior, ie,
657  *					\ takes parameters from TIB
658  * : ex-1 s" -l" 1 ls ;			\ "compile" behavior, ie,
659  *					\ takes parameters from the stack
660  * : ex-2 ['] ls catch ; immediate	\ undefined behavior
661  * : ex-3 ['] ls catch ;		\ undefined behavior
662  * ex-2 ex-3				\ "interpret" behavior,
663  *					\ catch works
664  * : ex-4 ex-2 ;			\ "compile" behavior,
665  *					\ catch does not work
666  * : ex-5 ex-3 ; immediate		\ same as ex-2
667  * : ex-6 ex-3 ;			\ same as ex-3
668  * : ex-7 ['] ex-1 catch ;		\ "compile" behavior,
669  *					\ catch works
670  * : ex-8 postpone ls ;	immediate	\ same as ex-2
671  * : ex-9 postpone ls ;			\ same as ex-3
672  *
673  * As the definition below is particularly tricky, and it's side effects
674  * must be well understood by those playing with it, I'll be heavy on
675  * the comments.
676  *
677  * (if you edit this definition, pay attention to trailing spaces after
678  *  each word -- I warned you! :-) )
679  */
680 #define	BUILTIN_CONSTRUCTOR \
681 ": builtin: "		\
682 ">in @ "		/* save the tib index pointer */ \
683 "' "			/* get next word's xt */ \
684 "swap >in ! "		/* point again to next word */ \
685 "create "		/* create a new definition of the next word */ \
686 ", "			/* save previous definition's xt */ \
687 "immediate "		/* make the new definition an immediate word */ \
688 			\
689 "does> "		/* Now, the *new* definition will: */ \
690 "state @ if "		/* if in compiling state: */ \
691 "1 postpone literal "	/* pass 1 flag to indicate compile */ \
692 "@ compile, "		/* compile in previous definition */ \
693 "postpone throw "		/* throw stack-returned result */ \
694 "else "		/* if in interpreting state: */ \
695 "0 swap "			/* pass 0 flag to indicate interpret */ \
696 "@ execute "		/* call previous definition */ \
697 "throw "			/* throw stack-returned result */ \
698 "then ; "
699 
700 extern int ficlExecFD(ficlVm *, int);
701 #define	COMMAND_SET(ptr, name, desc, fn)		\
702 	ptr = malloc(sizeof (struct bootblk_command));	\
703 	ptr->c_name = (name);				\
704 	ptr->c_desc = (desc);				\
705 	ptr->c_fn = (fn);
706 
707 /*
708  * Initialise the Forth interpreter, create all our commands as words.
709  */
710 ficlVm *
711 bf_init(const char *rc, ficlOutputFunction out)
712 {
713 	struct bootblk_command *cmdp;
714 	char create_buf[41];	/* 31 characters-long builtins */
715 	char *buf;
716 	int fd, rv;
717 	ficlSystemInformation *fsi;
718 	ficlDictionary *dict;
719 	ficlDictionary *env;
720 
721 	/* set up commands list */
722 	STAILQ_INIT(&commands);
723 	COMMAND_SET(cmdp, "help", "detailed help", command_help);
724 	STAILQ_INSERT_TAIL(&commands, cmdp, next);
725 	COMMAND_SET(cmdp, "?", "list commands", command_commandlist);
726 	STAILQ_INSERT_TAIL(&commands, cmdp, next);
727 	COMMAND_SET(cmdp, "show", "show variable(s)", command_show);
728 	STAILQ_INSERT_TAIL(&commands, cmdp, next);
729 	COMMAND_SET(cmdp, "printenv", "show variable(s)", command_show);
730 	STAILQ_INSERT_TAIL(&commands, cmdp, next);
731 	COMMAND_SET(cmdp, "set", "set a variable", command_set);
732 	STAILQ_INSERT_TAIL(&commands, cmdp, next);
733 	COMMAND_SET(cmdp, "setprop", "set a variable", command_setprop);
734 	STAILQ_INSERT_TAIL(&commands, cmdp, next);
735 	COMMAND_SET(cmdp, "unset", "unset a variable", command_unset);
736 	STAILQ_INSERT_TAIL(&commands, cmdp, next);
737 	COMMAND_SET(cmdp, "echo", "echo arguments", command_echo);
738 	STAILQ_INSERT_TAIL(&commands, cmdp, next);
739 	COMMAND_SET(cmdp, "read", "read input from the terminal", command_read);
740 	STAILQ_INSERT_TAIL(&commands, cmdp, next);
741 	COMMAND_SET(cmdp, "more", "show contents of a file", command_more);
742 	STAILQ_INSERT_TAIL(&commands, cmdp, next);
743 	COMMAND_SET(cmdp, "ls", "list files", command_ls);
744 	STAILQ_INSERT_TAIL(&commands, cmdp, next);
745 	COMMAND_SET(cmdp, "include", "read commands from a file",
746 	    command_include);
747 	STAILQ_INSERT_TAIL(&commands, cmdp, next);
748 	COMMAND_SET(cmdp, "boot", "boot a file or loaded kernel", command_boot);
749 	STAILQ_INSERT_TAIL(&commands, cmdp, next);
750 	COMMAND_SET(cmdp, "autoboot", "boot automatically after a delay",
751 	    command_autoboot);
752 	STAILQ_INSERT_TAIL(&commands, cmdp, next);
753 	COMMAND_SET(cmdp, "load", "load a kernel or module", command_load);
754 	STAILQ_INSERT_TAIL(&commands, cmdp, next);
755 	COMMAND_SET(cmdp, "unload", "unload all modules", command_unload);
756 	STAILQ_INSERT_TAIL(&commands, cmdp, next);
757 	COMMAND_SET(cmdp, "reboot", "reboot the system", command_reboot);
758 	STAILQ_INSERT_TAIL(&commands, cmdp, next);
759 	COMMAND_SET(cmdp, "sifting", "find words", command_sifting);
760 	STAILQ_INSERT_TAIL(&commands, cmdp, next);
761 	COMMAND_SET(cmdp, "framebuffer", "framebuffer mode management",
762 	    command_framebuffer);
763 	STAILQ_INSERT_TAIL(&commands, cmdp, next);
764 
765 	fsi = malloc(sizeof (ficlSystemInformation));
766 	ficlSystemInformationInitialize(fsi);
767 	fsi->textOut = out;
768 	fsi->dictionarySize = BF_DICTSIZE;
769 
770 	bf_sys = ficlSystemCreate(fsi);
771 	free(fsi);
772 	ficlSystemCompileExtras(bf_sys);
773 	bf_vm = ficlSystemCreateVm(bf_sys);
774 
775 	buf = isadir();
776 	if (buf == NULL || strcmp(buf, "amd64") != 0) {
777 		(void) setenv("ISADIR", "", 1);
778 	} else {
779 		(void) setenv("ISADIR", buf, 1);
780 	}
781 	if (buf != NULL)
782 		free(buf);
783 	buf = get_currdev();
784 	(void) setenv("currdev", buf, 1);
785 	free(buf);
786 
787 	/* Put all private definitions in a "builtins" vocabulary */
788 	rv = ficlVmEvaluate(bf_vm,
789 	    "vocabulary builtins also builtins definitions");
790 	if (rv != FICL_VM_STATUS_OUT_OF_TEXT) {
791 		printf("error interpreting forth: %d\n", rv);
792 		exit(1);
793 	}
794 
795 	/* Builtin constructor word  */
796 	rv = ficlVmEvaluate(bf_vm, BUILTIN_CONSTRUCTOR);
797 	if (rv != FICL_VM_STATUS_OUT_OF_TEXT) {
798 		printf("error interpreting forth: %d\n", rv);
799 		exit(1);
800 	}
801 
802 	/* make all commands appear as Forth words */
803 	dict = ficlSystemGetDictionary(bf_sys);
804 	cmdp = NULL;
805 	STAILQ_FOREACH(cmdp, &commands, next) {
806 		(void) ficlDictionaryAppendPrimitive(dict, (char *)cmdp->c_name,
807 		    bf_command, FICL_WORD_DEFAULT);
808 		rv = ficlVmEvaluate(bf_vm, "forth definitions builtins");
809 		if (rv != FICL_VM_STATUS_OUT_OF_TEXT) {
810 			printf("error interpreting forth: %d\n", rv);
811 			exit(1);
812 		}
813 		(void) snprintf(create_buf, sizeof (create_buf), "builtin: %s",
814 		    cmdp->c_name);
815 		rv = ficlVmEvaluate(bf_vm, create_buf);
816 		if (rv != FICL_VM_STATUS_OUT_OF_TEXT) {
817 			printf("error interpreting forth: %d\n", rv);
818 			exit(1);
819 		}
820 		rv = ficlVmEvaluate(bf_vm, "builtins definitions");
821 		if (rv != FICL_VM_STATUS_OUT_OF_TEXT) {
822 			printf("error interpreting forth: %d\n", rv);
823 			exit(1);
824 		}
825 	}
826 	rv = ficlVmEvaluate(bf_vm, "only forth definitions");
827 	if (rv != FICL_VM_STATUS_OUT_OF_TEXT) {
828 		printf("error interpreting forth: %d\n", rv);
829 		exit(1);
830 	}
831 
832 	/*
833 	 * Export some version numbers so that code can detect the
834 	 * loader/host version
835 	 */
836 	env = ficlSystemGetEnvironment(bf_sys);
837 	(void) ficlDictionarySetConstant(env, "loader_version",
838 	    (bootprog_rev[0] - '0') * 10 + (bootprog_rev[2] - '0'));
839 
840 	/* try to load and run init file if present */
841 	if (rc == NULL)
842 		rc = "/boot/forth/boot.4th";
843 	if (*rc != '\0') {
844 		fd = open(rc, O_RDONLY);
845 		if (fd != -1) {
846 			(void) ficlExecFD(bf_vm, fd);
847 			(void) close(fd);
848 		}
849 	}
850 
851 	gfx_framework_init();
852 	return (bf_vm);
853 }
854 
855 void
856 bf_fini(void)
857 {
858 	ficlSystemDestroy(bf_sys);
859 	gfx_framework_fini();
860 }
861 
862 /*
863  * Feed a line of user input to the Forth interpreter
864  */
865 int
866 bf_run(char *line)
867 {
868 	int result;
869 	ficlString s;
870 
871 	FICL_STRING_SET_FROM_CSTRING(s, line);
872 	result = ficlVmExecuteString(bf_vm, s);
873 
874 	switch (result) {
875 	case FICL_VM_STATUS_OUT_OF_TEXT:
876 	case FICL_VM_STATUS_ABORTQ:
877 	case FICL_VM_STATUS_QUIT:
878 	case FICL_VM_STATUS_ERROR_EXIT:
879 	break;
880 	case FICL_VM_STATUS_USER_EXIT:
881 	break;
882 	case FICL_VM_STATUS_ABORT:
883 		printf("Aborted!\n");
884 	break;
885 	case BF_PARSE:
886 		printf("Parse error!\n");
887 	break;
888 	default:
889 		if (command_errmsg != NULL) {
890 			printf("%s\n", command_errmsg);
891 			command_errmsg = NULL;
892 		}
893 	}
894 
895 	(void) setenv("interpret", bf_vm->state ? "" : "ok", 1);
896 
897 	return (result);
898 }
899 
900 char *
901 get_dev(const char *path)
902 {
903 	FILE *fp;
904 	struct mnttab mpref = {0};
905 	struct mnttab mp = {0};
906 	char *currdev;
907 	int ret;
908 	char *buf;
909 	char *tmppath;
910 	char *tmpdev;
911 	char *cwd = NULL;
912 
913 	fp = fopen(MNTTAB, "r");
914 
915 	/* do the best we can to return something... */
916 	if (fp == NULL)
917 		return (strdup(path));
918 
919 	/*
920 	 * the path can have device provided, check for it
921 	 * and extract it.
922 	 */
923 	buf = strrchr(path, ':');
924 	if (buf != NULL) {
925 		tmppath = buf+1;		/* real path */
926 		buf = strchr(path, ':');	/* skip zfs: */
927 		buf++;
928 		tmpdev = strdup(buf);
929 		buf = strchr(tmpdev, ':');	/* get ending : */
930 		*buf = '\0';
931 	} else {
932 		tmppath = (char *)path;
933 		if (tmppath[0] != '/')
934 			if ((cwd = getcwd(NULL, PATH_MAX)) == NULL) {
935 				(void) fclose(fp);
936 				return (strdup(path));
937 			}
938 
939 		currdev = getenv("currdev");
940 		buf = strchr(currdev, ':');	/* skip zfs: */
941 		if (buf == NULL) {
942 			(void) fclose(fp);
943 			return (strdup(path));
944 		}
945 		buf++;
946 		tmpdev = strdup(buf);
947 		buf = strchr(tmpdev, ':');	/* get ending : */
948 		*buf = '\0';
949 	}
950 
951 	mpref.mnt_special = tmpdev;
952 	ret = getmntany(fp, &mp, &mpref);
953 	(void) fclose(fp);
954 	free(tmpdev);
955 
956 	if (cwd == NULL)
957 		(void) asprintf(&buf, "%s/%s", ret? "":mp.mnt_mountp, tmppath);
958 	else {
959 		(void) asprintf(&buf, "%s/%s/%s", ret? "":mp.mnt_mountp, cwd,
960 		    tmppath);
961 		free(cwd);
962 	}
963 	return (buf);
964 }
965 
966 static void
967 ngets(char *buf, int n)
968 {
969 	int c;
970 	char *lp;
971 
972 	for (lp = buf; ; )
973 		switch (c = getchar() & 0177) {
974 		case '\n':
975 		case '\r':
976 			*lp = '\0';
977 			(void) putchar('\n');
978 			return;
979 		case '\b':
980 		case '\177':
981 			if (lp > buf) {
982 				lp--;
983 				(void) putchar('\b');
984 				(void) putchar(' ');
985 				(void) putchar('\b');
986 			}
987 			break;
988 		case 'r'&037: {
989 			char *p;
990 
991 			(void) putchar('\n');
992 			for (p = buf; p < lp; ++p)
993 				(void) putchar(*p);
994 			break;
995 		}
996 		case 'u'&037:
997 		case 'w'&037:
998 			lp = buf;
999 			(void) putchar('\n');
1000 			break;
1001 		default:
1002 			if ((n < 1) || ((lp - buf) < n - 1)) {
1003 				*lp++ = c;
1004 				(void) putchar(c);
1005 			}
1006 		}
1007 	/*NOTREACHED*/
1008 }
1009 
1010 static int
1011 fgetstr(char *buf, int size, int fd)
1012 {
1013 	char c;
1014 	int err, len;
1015 
1016 	size--;			/* leave space for terminator */
1017 	len = 0;
1018 	while (size != 0) {
1019 		err = read(fd, &c, sizeof (c));
1020 		if (err < 0)			/* read error */
1021 			return (-1);
1022 
1023 		if (err == 0) {	/* EOF */
1024 			if (len == 0)
1025 				return (-1);	/* nothing to read */
1026 			break;
1027 		}
1028 		if ((c == '\r') || (c == '\n'))	/* line terminators */
1029 			break;
1030 		*buf++ = c;			/* keep char */
1031 		size--;
1032 		len++;
1033 	}
1034 	*buf = 0;
1035 	return (len);
1036 }
1037 
1038 static char *
1039 unargv(int argc, char *argv[])
1040 {
1041 	size_t hlong;
1042 	int i;
1043 	char *cp;
1044 
1045 	for (i = 0, hlong = 0; i < argc; i++)
1046 		hlong += strlen(argv[i]) + 2;
1047 
1048 	if (hlong == 0)
1049 		return (NULL);
1050 
1051 	cp = malloc(hlong);
1052 	cp[0] = 0;
1053 	for (i = 0; i < argc; i++) {
1054 		(void) strcat(cp, argv[i]);
1055 		if (i < (argc - 1))
1056 			(void) strcat(cp, " ");
1057 	}
1058 
1059 	return (cp);
1060 }
1061 
1062 /*
1063  * Help is read from a formatted text file.
1064  *
1065  * Entries in the file are formatted as:
1066  * # Ttopic [Ssubtopic] Ddescription
1067  * help
1068  * text
1069  * here
1070  * #
1071  *
1072  * Note that for code simplicity's sake, the above format must be followed
1073  * exactly.
1074  *
1075  * Subtopic entries must immediately follow the topic (this is used to
1076  * produce the listing of subtopics).
1077  *
1078  * If no argument(s) are supplied by the user, the help for 'help' is displayed.
1079  */
1080 static int
1081 help_getnext(int fd, char **topic, char **subtopic, char **desc)
1082 {
1083 	char line[81], *cp, *ep;
1084 
1085 	*topic = *subtopic = *desc = NULL;
1086 	for (;;) {
1087 		if (fgetstr(line, 80, fd) < 0)
1088 			return (0);
1089 
1090 		if (strlen(line) < 3 || line[0] != '#' || line[1] != ' ')
1091 			continue;
1092 
1093 		*topic = *subtopic = *desc = NULL;
1094 		cp = line + 2;
1095 		while (cp != NULL && *cp != 0) {
1096 			ep = strchr(cp, ' ');
1097 			if (*cp == 'T' && *topic == NULL) {
1098 				if (ep != NULL)
1099 					*ep++ = 0;
1100 				*topic = strdup(cp + 1);
1101 			} else if (*cp == 'S' && *subtopic == NULL) {
1102 				if (ep != NULL)
1103 					*ep++ = 0;
1104 				*subtopic = strdup(cp + 1);
1105 			} else if (*cp == 'D') {
1106 				*desc = strdup(cp + 1);
1107 				ep = NULL;
1108 			}
1109 			cp = ep;
1110 		}
1111 		if (*topic == NULL) {
1112 			free(*subtopic);
1113 			free(*desc);
1114 			continue;
1115 		}
1116 		return (1);
1117 	}
1118 }
1119 
1120 static int
1121 help_emitsummary(char *topic, char *subtopic, char *desc)
1122 {
1123 	int i;
1124 
1125 	(void) pager_output("    ");
1126 	(void) pager_output(topic);
1127 	i = strlen(topic);
1128 	if (subtopic != NULL) {
1129 		(void) pager_output(" ");
1130 		(void) pager_output(subtopic);
1131 		i += strlen(subtopic) + 1;
1132 	}
1133 	if (desc != NULL) {
1134 		do {
1135 			(void) pager_output(" ");
1136 		} while (i++ < 30);
1137 		(void) pager_output(desc);
1138 	}
1139 	return (pager_output("\n"));
1140 }
1141 
1142 static int
1143 command_help(int argc, char *argv[])
1144 {
1145 	char buf[81];	/* XXX buffer size? */
1146 	int hfd, matched, doindex;
1147 	char *topic, *subtopic, *t, *s, *d;
1148 
1149 	/* page the help text from our load path */
1150 	(void) snprintf(buf, sizeof (buf), "/boot/loader.help");
1151 	if ((hfd = open(buf, O_RDONLY)) < 0) {
1152 		printf("Verbose help not available, "
1153 		    "use '?' to list commands\n");
1154 		return (CMD_OK);
1155 	}
1156 
1157 	/* pick up request from arguments */
1158 	topic = subtopic = NULL;
1159 	switch (argc) {
1160 	case 3:
1161 		subtopic = strdup(argv[2]);
1162 		/* FALLTHROUGH */
1163 	case 2:
1164 		topic = strdup(argv[1]);
1165 	break;
1166 	case 1:
1167 		topic = strdup("help");
1168 	break;
1169 	default:
1170 		command_errmsg = "usage is 'help <topic> [<subtopic>]";
1171 		(void) close(hfd);
1172 		return (CMD_ERROR);
1173 	}
1174 
1175 	/* magic "index" keyword */
1176 	doindex = strcmp(topic, "index") == 0;
1177 	matched = doindex;
1178 
1179 	/* Scan the helpfile looking for help matching the request */
1180 	pager_open();
1181 	while (help_getnext(hfd, &t, &s, &d)) {
1182 		if (doindex) {		/* dink around formatting */
1183 			if (help_emitsummary(t, s, d))
1184 				break;
1185 
1186 		} else if (strcmp(topic, t)) {
1187 			/* topic mismatch */
1188 			/* nothing more on this topic, stop scanning */
1189 			if (matched)
1190 				break;
1191 		} else {
1192 			/* topic matched */
1193 			matched = 1;
1194 			if ((subtopic == NULL && s == NULL) ||
1195 			    (subtopic != NULL && s != NULL &&
1196 			    strcmp(subtopic, s) == 0)) {
1197 				/* exact match, print text */
1198 				while (fgetstr(buf, 80, hfd) >= 0 &&
1199 				    buf[0] != '#') {
1200 					if (pager_output(buf))
1201 						break;
1202 					if (pager_output("\n"))
1203 						break;
1204 				}
1205 			} else if (subtopic == NULL && s != NULL) {
1206 				/* topic match, list subtopics */
1207 				if (help_emitsummary(t, s, d))
1208 					break;
1209 			}
1210 		}
1211 		free(t);
1212 		free(s);
1213 		free(d);
1214 		t = s = d = NULL;
1215 	}
1216 	free(t);
1217 	free(s);
1218 	free(d);
1219 	pager_close();
1220 	(void) close(hfd);
1221 	if (!matched) {
1222 		(void) snprintf(command_errbuf, sizeof (command_errbuf),
1223 		    "no help available for '%s'", topic);
1224 		free(topic);
1225 		free(subtopic);
1226 		return (CMD_ERROR);
1227 	}
1228 	free(topic);
1229 	free(subtopic);
1230 	return (CMD_OK);
1231 }
1232 
1233 static int
1234 command_commandlist(int argc __unused, char *argv[] __unused)
1235 {
1236 	struct bootblk_command *cmdp;
1237 	int res;
1238 	char name[20];
1239 
1240 	res = 0;
1241 	pager_open();
1242 	res = pager_output("Available commands:\n");
1243 	cmdp = NULL;
1244 	STAILQ_FOREACH(cmdp, &commands, next) {
1245 		if (res)
1246 			break;
1247 		if (cmdp->c_name != NULL && cmdp->c_desc != NULL) {
1248 			(void) snprintf(name, sizeof (name), "  %-15s  ",
1249 			    cmdp->c_name);
1250 			(void) pager_output(name);
1251 			(void) pager_output(cmdp->c_desc);
1252 			res = pager_output("\n");
1253 		}
1254 	}
1255 	pager_close();
1256 	return (CMD_OK);
1257 }
1258 
1259 /*
1260  * XXX set/show should become set/echo if we have variable
1261  * substitution happening.
1262  */
1263 static int
1264 command_show(int argc, char *argv[])
1265 {
1266 	char **ev;
1267 	char *cp;
1268 
1269 	if (argc < 2) {
1270 		/*
1271 		 * With no arguments, print everything.
1272 		 */
1273 		pager_open();
1274 		for (ev = _environ; *ev != NULL; ev++) {
1275 			(void) pager_output(*ev);
1276 			cp = getenv(*ev);
1277 			if (cp != NULL) {
1278 				(void) pager_output("=");
1279 				(void) pager_output(cp);
1280 			}
1281 			if (pager_output("\n"))
1282 				break;
1283 		}
1284 		pager_close();
1285 	} else {
1286 		if ((cp = getenv(argv[1])) != NULL) {
1287 			printf("%s\n", cp);
1288 		} else {
1289 			(void) snprintf(command_errbuf, sizeof (command_errbuf),
1290 			    "variable '%s' not found", argv[1]);
1291 			return (CMD_ERROR);
1292 		}
1293 	}
1294 	return (CMD_OK);
1295 }
1296 
1297 static int
1298 command_set(int argc, char *argv[])
1299 {
1300 	int	err;
1301 	char	*value, *copy;
1302 
1303 	if (argc != 2) {
1304 		command_errmsg = "wrong number of arguments";
1305 		return (CMD_ERROR);
1306 	} else {
1307 		copy = strdup(argv[1]);
1308 		if (copy == NULL) {
1309 			command_errmsg = strerror(errno);
1310 			return (CMD_ERROR);
1311 		}
1312 		if ((value = strchr(copy, '=')) != NULL)
1313 			*(value++) = 0;
1314 		else
1315 			value = "";
1316 		if ((err = setenv(copy, value, 1)) != 0) {
1317 			free(copy);
1318 			command_errmsg = strerror(errno);
1319 			return (CMD_ERROR);
1320 		}
1321 		free(copy);
1322 	}
1323 	return (CMD_OK);
1324 }
1325 
1326 static int
1327 command_setprop(int argc, char *argv[])
1328 {
1329 	int err;
1330 
1331 	if (argc != 3) {
1332 		command_errmsg = "wrong number of arguments";
1333 		return (CMD_ERROR);
1334 	} else {
1335 		if ((err = setenv(argv[1], argv[2], 1)) != 0) {
1336 			command_errmsg = strerror(err);
1337 			return (CMD_ERROR);
1338 		}
1339 	}
1340 	return (CMD_OK);
1341 }
1342 
1343 static int
1344 command_unset(int argc, char *argv[])
1345 {
1346 	int err;
1347 
1348 	if (argc != 2) {
1349 		command_errmsg = "wrong number of arguments";
1350 		return (CMD_ERROR);
1351 	} else {
1352 		if ((err = unsetenv(argv[1])) != 0) {
1353 			command_errmsg = strerror(err);
1354 			return (CMD_ERROR);
1355 		}
1356 	}
1357 	return (CMD_OK);
1358 }
1359 
1360 static int
1361 command_echo(int argc, char *argv[])
1362 {
1363 	char *s;
1364 	int nl, ch;
1365 
1366 	nl = 0;
1367 	optind = 1;
1368 	opterr = 1;
1369 	while ((ch = getopt(argc, argv, "n")) != -1) {
1370 		switch (ch) {
1371 		case 'n':
1372 			nl = 1;
1373 		break;
1374 		case '?':
1375 		default:
1376 			/* getopt has already reported an error */
1377 		return (CMD_OK);
1378 		}
1379 	}
1380 	argv += (optind);
1381 	argc -= (optind);
1382 
1383 	s = unargv(argc, argv);
1384 	if (s != NULL) {
1385 		printf("%s", s);
1386 		free(s);
1387 	}
1388 	if (!nl)
1389 		printf("\n");
1390 	return (CMD_OK);
1391 }
1392 
1393 /*
1394  * A passable emulation of the sh(1) command of the same name.
1395  */
1396 static int
1397 ischar(void)
1398 {
1399 	return (1);
1400 }
1401 
1402 static int
1403 command_read(int argc, char *argv[])
1404 {
1405 	char *prompt;
1406 	int timeout;
1407 	time_t when;
1408 	char *cp;
1409 	char *name;
1410 	char buf[256];		/* XXX size? */
1411 	int c;
1412 
1413 	timeout = -1;
1414 	prompt = NULL;
1415 	optind = 1;
1416 	opterr = 1;
1417 	while ((c = getopt(argc, argv, "p:t:")) != -1) {
1418 		switch (c) {
1419 		case 'p':
1420 			prompt = optarg;
1421 		break;
1422 		case 't':
1423 			timeout = strtol(optarg, &cp, 0);
1424 			if (cp == optarg) {
1425 				(void) snprintf(command_errbuf,
1426 				    sizeof (command_errbuf),
1427 				    "bad timeout '%s'", optarg);
1428 				return (CMD_ERROR);
1429 			}
1430 		break;
1431 		default:
1432 		return (CMD_OK);
1433 		}
1434 	}
1435 
1436 	argv += (optind);
1437 	argc -= (optind);
1438 	name = (argc > 0) ? argv[0]: NULL;
1439 
1440 	if (prompt != NULL)
1441 		printf("%s", prompt);
1442 	if (timeout >= 0) {
1443 		when = time(NULL) + timeout;
1444 		while (!ischar())
1445 			if (time(NULL) >= when)
1446 				return (CMD_OK); /* is timeout an error? */
1447 	}
1448 
1449 	ngets(buf, sizeof (buf));
1450 
1451 	if (name != NULL)
1452 		(void) setenv(name, buf, 1);
1453 	return (CMD_OK);
1454 }
1455 
1456 /*
1457  * File pager
1458  */
1459 static int
1460 command_more(int argc, char *argv[])
1461 {
1462 	int i;
1463 	int res;
1464 	char line[80];
1465 	char *name;
1466 
1467 	res = 0;
1468 	pager_open();
1469 	for (i = 1; (i < argc) && (res == 0); i++) {
1470 		(void) snprintf(line, sizeof (line), "*** FILE %s BEGIN ***\n",
1471 		    argv[i]);
1472 		if (pager_output(line))
1473 			break;
1474 		name = get_dev(argv[i]);
1475 		res = page_file(name);
1476 		free(name);
1477 		if (!res) {
1478 			(void) snprintf(line, sizeof (line),
1479 			    "*** FILE %s END ***\n", argv[i]);
1480 			res = pager_output(line);
1481 		}
1482 	}
1483 	pager_close();
1484 
1485 	if (res == 0)
1486 		return (CMD_OK);
1487 	return (CMD_ERROR);
1488 }
1489 
1490 static int
1491 page_file(char *filename)
1492 {
1493 	int result;
1494 
1495 	result = pager_file(filename);
1496 
1497 	if (result == -1) {
1498 		(void) snprintf(command_errbuf, sizeof (command_errbuf),
1499 		    "error showing %s", filename);
1500 	}
1501 
1502 	return (result);
1503 }
1504 
1505 static int
1506 command_ls(int argc, char *argv[])
1507 {
1508 	DIR *dir;
1509 	int fd;
1510 	struct stat sb;
1511 	struct dirent *d;
1512 	char *buf, *path;
1513 	char lbuf[128];	/* one line */
1514 	int result, ch;
1515 	int verbose;
1516 
1517 	result = CMD_OK;
1518 	fd = -1;
1519 	verbose = 0;
1520 	optind = 1;
1521 	opterr = 1;
1522 	while ((ch = getopt(argc, argv, "l")) != -1) {
1523 		switch (ch) {
1524 		case 'l':
1525 			verbose = 1;
1526 		break;
1527 		case '?':
1528 		default:
1529 			/* getopt has already reported an error */
1530 		return (CMD_OK);
1531 		}
1532 	}
1533 	argv += (optind - 1);
1534 	argc -= (optind - 1);
1535 
1536 	if (argc < 2) {
1537 		path = "";
1538 	} else {
1539 		path = argv[1];
1540 	}
1541 
1542 	fd = ls_getdir(&path);
1543 	if (fd == -1) {
1544 		result = CMD_ERROR;
1545 		goto out;
1546 	}
1547 	dir = fdopendir(fd);
1548 	pager_open();
1549 	(void) pager_output(path);
1550 	(void) pager_output("\n");
1551 
1552 	while ((d = readdir(dir)) != NULL) {
1553 		if (strcmp(d->d_name, ".") && strcmp(d->d_name, "..")) {
1554 			/* stat the file, if possible */
1555 			if (path[0] == '\0') {
1556 				(void) asprintf(&buf, "%s", d->d_name);
1557 			} else {
1558 				(void) asprintf(&buf, "%s/%s", path, d->d_name);
1559 			}
1560 			if (buf != NULL) {
1561 				/* ignore return, could be symlink, etc. */
1562 				if (stat(buf, &sb)) {
1563 					sb.st_size = 0;
1564 					sb.st_mode = 0;
1565 				}
1566 				free(buf);
1567 			}
1568 			if (verbose) {
1569 				(void) snprintf(lbuf, sizeof (lbuf),
1570 				    " %c %8d %s\n",
1571 				    typestr[sb.st_mode >> 12],
1572 				    (int)sb.st_size, d->d_name);
1573 			} else {
1574 				(void) snprintf(lbuf, sizeof (lbuf),
1575 				    " %c  %s\n",
1576 				    typestr[sb.st_mode >> 12], d->d_name);
1577 			}
1578 			if (pager_output(lbuf))
1579 				goto out;
1580 		}
1581 	}
1582 out:
1583 	pager_close();
1584 	if (fd != -1)
1585 		(void) closedir(dir);
1586 	if (path != NULL)
1587 		free(path);
1588 	return (result);
1589 }
1590 
1591 /*
1592  * Given (path) containing a vaguely reasonable path specification, return an fd
1593  * on the directory, and an allocated copy of the path to the directory.
1594  */
1595 static int
1596 ls_getdir(char **pathp)
1597 {
1598 	struct stat sb;
1599 	int fd;
1600 	char *cp, *path;
1601 
1602 	fd = -1;
1603 
1604 	/* one extra byte for a possible trailing slash required */
1605 	path = malloc(strlen(*pathp) + 2);
1606 	(void) strcpy(path, *pathp);
1607 
1608 	/* Make sure the path is respectable to begin with */
1609 	if ((cp = get_dev(path)) == NULL) {
1610 		(void) snprintf(command_errbuf, sizeof (command_errbuf),
1611 		    "bad path '%s'", path);
1612 		goto out;
1613 	}
1614 
1615 	/* If there's no path on the device, assume '/' */
1616 	if (*cp == 0)
1617 		(void) strcat(path, "/");
1618 
1619 	fd = open(cp, O_RDONLY);
1620 	if (fd < 0) {
1621 		(void) snprintf(command_errbuf, sizeof (command_errbuf),
1622 		    "open '%s' failed: %s", path, strerror(errno));
1623 		goto out;
1624 	}
1625 	if (fstat(fd, &sb) < 0) {
1626 		(void) snprintf(command_errbuf, sizeof (command_errbuf),
1627 		    "stat failed: %s", strerror(errno));
1628 		goto out;
1629 	}
1630 	if (!S_ISDIR(sb.st_mode)) {
1631 		(void) snprintf(command_errbuf, sizeof (command_errbuf),
1632 		    "%s: %s", path, strerror(ENOTDIR));
1633 		goto out;
1634 	}
1635 
1636 	free(cp);
1637 	*pathp = path;
1638 	return (fd);
1639 
1640 out:
1641 	free(cp);
1642 	free(path);
1643 	*pathp = NULL;
1644 	if (fd != -1)
1645 		(void) close(fd);
1646 	return (-1);
1647 }
1648 
1649 static int
1650 command_include(int argc, char *argv[])
1651 {
1652 	int i;
1653 	int res;
1654 	char **argvbuf;
1655 
1656 	/*
1657 	 * Since argv is static, we need to save it here.
1658 	 */
1659 	argvbuf = (char **)calloc(argc, sizeof (char *));
1660 	for (i = 0; i < argc; i++)
1661 		argvbuf[i] = strdup(argv[i]);
1662 
1663 	res = CMD_OK;
1664 	for (i = 1; (i < argc) && (res == CMD_OK); i++)
1665 		res = include(argvbuf[i]);
1666 
1667 	for (i = 0; i < argc; i++)
1668 		free(argvbuf[i]);
1669 	free(argvbuf);
1670 
1671 	return (res);
1672 }
1673 
1674 /*
1675  * Header prepended to each line. The text immediately follows the header.
1676  * We try to make this short in order to save memory -- the loader has
1677  * limited memory available, and some of the forth files are very long.
1678  */
1679 struct includeline
1680 {
1681 	struct includeline *next;
1682 	int line;
1683 	char text[];
1684 };
1685 
1686 int
1687 include(const char *filename)
1688 {
1689 	struct includeline *script, *se, *sp;
1690 	int res = CMD_OK;
1691 	int prevsrcid, fd, line;
1692 	char *cp, input[256]; /* big enough? */
1693 	char *path;
1694 
1695 	path = get_dev(filename);
1696 	if (((fd = open(path, O_RDONLY)) == -1)) {
1697 		(void) snprintf(command_errbuf, sizeof (command_errbuf),
1698 		    "can't open '%s': %s", filename,
1699 		    strerror(errno));
1700 		free(path);
1701 		return (CMD_ERROR);
1702 	}
1703 
1704 	free(path);
1705 	/*
1706 	 * Read the script into memory.
1707 	 */
1708 	script = se = NULL;
1709 	line = 0;
1710 
1711 	while (fgetstr(input, sizeof (input), fd) >= 0) {
1712 		line++;
1713 		cp = input;
1714 		/* Allocate script line structure and copy line, flags */
1715 		if (*cp == '\0')
1716 			continue;	/* ignore empty line, save memory */
1717 		if (cp[0] == '\\' && cp[1] == ' ')
1718 			continue;	/* ignore comment */
1719 
1720 		sp = malloc(sizeof (struct includeline) + strlen(cp) + 1);
1721 		/*
1722 		 * On malloc failure (it happens!), free as much as possible
1723 		 * and exit
1724 		 */
1725 		if (sp == NULL) {
1726 			while (script != NULL) {
1727 				se = script;
1728 				script = script->next;
1729 				free(se);
1730 			}
1731 			(void) snprintf(command_errbuf, sizeof (command_errbuf),
1732 			    "file '%s' line %d: memory allocation "
1733 			    "failure - aborting", filename, line);
1734 			return (CMD_ERROR);
1735 		}
1736 		(void) strcpy(sp->text, cp);
1737 		sp->line = line;
1738 		sp->next = NULL;
1739 
1740 		if (script == NULL) {
1741 			script = sp;
1742 		} else {
1743 			se->next = sp;
1744 		}
1745 		se = sp;
1746 	}
1747 	(void) close(fd);
1748 
1749 	/*
1750 	 * Execute the script
1751 	 */
1752 
1753 	prevsrcid = bf_vm->sourceId.i;
1754 	bf_vm->sourceId.i = fd+1;	/* 0 is user input device */
1755 
1756 	res = CMD_OK;
1757 
1758 	for (sp = script; sp != NULL; sp = sp->next) {
1759 		res = bf_run(sp->text);
1760 		if (res != FICL_VM_STATUS_OUT_OF_TEXT) {
1761 			(void) snprintf(command_errbuf, sizeof (command_errbuf),
1762 			    "Error while including %s, in the line %d:\n%s",
1763 			    filename, sp->line, sp->text);
1764 			res = CMD_ERROR;
1765 			break;
1766 		} else
1767 			res = CMD_OK;
1768 	}
1769 
1770 	bf_vm->sourceId.i = -1;
1771 	(void) bf_run("");
1772 	bf_vm->sourceId.i = prevsrcid;
1773 
1774 	while (script != NULL) {
1775 		se = script;
1776 		script = script->next;
1777 		free(se);
1778 	}
1779 
1780 	return (res);
1781 }
1782 
1783 static int
1784 command_boot(int argc, char *argv[])
1785 {
1786 	return (CMD_OK);
1787 }
1788 
1789 static int
1790 command_autoboot(int argc, char *argv[])
1791 {
1792 	return (CMD_OK);
1793 }
1794 
1795 static void
1796 moduledir_rebuild(void)
1797 {
1798 	struct moduledir *mdp, *mtmp;
1799 	const char *path, *cp, *ep;
1800 	int cplen;
1801 
1802 	path = getenv("module_path");
1803 	if (path == NULL)
1804 		path = default_searchpath;
1805 	/*
1806 	 * Rebuild list of module directories if it changed
1807 	 */
1808 	STAILQ_FOREACH(mdp, &moduledir_list, d_link)
1809 		mdp->d_flags |= MDIR_REMOVED;
1810 
1811 	for (ep = path; *ep != 0;  ep++) {
1812 		cp = ep;
1813 		for (; *ep != 0 && *ep != ';'; ep++)
1814 			;
1815 		/*
1816 		 * Ignore trailing slashes
1817 		 */
1818 		for (cplen = ep - cp; cplen > 1 && cp[cplen - 1] == '/';
1819 		    cplen--)
1820 			;
1821 		STAILQ_FOREACH(mdp, &moduledir_list, d_link) {
1822 			if (strlen(mdp->d_path) != cplen ||
1823 			    bcmp(cp, mdp->d_path, cplen) != 0)
1824 				continue;
1825 			mdp->d_flags &= ~MDIR_REMOVED;
1826 			break;
1827 		}
1828 		if (mdp == NULL) {
1829 			mdp = malloc(sizeof (*mdp) + cplen + 1);
1830 			if (mdp == NULL)
1831 				return;
1832 			mdp->d_path = (char *)(mdp + 1);
1833 			bcopy(cp, mdp->d_path, cplen);
1834 			mdp->d_path[cplen] = 0;
1835 			mdp->d_hints = NULL;
1836 			mdp->d_flags = 0;
1837 			STAILQ_INSERT_TAIL(&moduledir_list, mdp, d_link);
1838 		}
1839 		if (*ep == 0)
1840 			break;
1841 	}
1842 	/*
1843 	 * Delete unused directories if any
1844 	 */
1845 	mdp = STAILQ_FIRST(&moduledir_list);
1846 	while (mdp) {
1847 		if ((mdp->d_flags & MDIR_REMOVED) == 0) {
1848 			mdp = STAILQ_NEXT(mdp, d_link);
1849 		} else {
1850 			if (mdp->d_hints)
1851 				free(mdp->d_hints);
1852 			mtmp = mdp;
1853 			mdp = STAILQ_NEXT(mdp, d_link);
1854 			STAILQ_REMOVE(&moduledir_list, mtmp, moduledir, d_link);
1855 			free(mtmp);
1856 		}
1857 	}
1858 }
1859 
1860 static char *
1861 file_lookup(const char *path, const char *name, int namelen)
1862 {
1863 	struct stat st;
1864 	char *result, *cp, *gz;
1865 	int pathlen;
1866 
1867 	pathlen = strlen(path);
1868 	result = malloc(pathlen + namelen + 2);
1869 	if (result == NULL)
1870 		return (NULL);
1871 	bcopy(path, result, pathlen);
1872 	if (pathlen > 0 && result[pathlen - 1] != '/')
1873 		result[pathlen++] = '/';
1874 	cp = result + pathlen;
1875 	bcopy(name, cp, namelen);
1876 	cp += namelen;
1877 	*cp = '\0';
1878 	if (stat(result, &st) == 0 && S_ISREG(st.st_mode))
1879 		return (result);
1880 	/* also check for gz file */
1881 	(void) asprintf(&gz, "%s.gz", result);
1882 	if (gz != NULL) {
1883 		int res = stat(gz, &st);
1884 		free(gz);
1885 		if (res == 0)
1886 			return (result);
1887 	}
1888 	free(result);
1889 	return (NULL);
1890 }
1891 
1892 static char *
1893 file_search(const char *name)
1894 {
1895 	struct moduledir *mdp;
1896 	struct stat sb;
1897 	char *result;
1898 	int namelen;
1899 
1900 	if (name == NULL)
1901 		return (NULL);
1902 	if (*name == 0)
1903 		return (strdup(name));
1904 
1905 	if (strchr(name, '/') != NULL) {
1906 		char *gz;
1907 		if (stat(name, &sb) == 0)
1908 			return (strdup(name));
1909 		/* also check for gz file */
1910 		(void) asprintf(&gz, "%s.gz", name);
1911 		if (gz != NULL) {
1912 			int res = stat(gz, &sb);
1913 			free(gz);
1914 			if (res == 0)
1915 				return (strdup(name));
1916 		}
1917 		return (NULL);
1918 	}
1919 
1920 	moduledir_rebuild();
1921 	result = NULL;
1922 	namelen = strlen(name);
1923 	STAILQ_FOREACH(mdp, &moduledir_list, d_link) {
1924 		result = file_lookup(mdp->d_path, name, namelen);
1925 		if (result)
1926 			break;
1927 	}
1928 	return (result);
1929 }
1930 
1931 static int
1932 command_load(int argc, char *argv[])
1933 {
1934 	int dofile, ch;
1935 	char *typestr = NULL;
1936 	char *filename;
1937 	dofile = 0;
1938 	optind = 1;
1939 
1940 	if (argc == 1) {
1941 		command_errmsg = "no filename specified";
1942 		return (CMD_ERROR);
1943 	}
1944 
1945 	while ((ch = getopt(argc, argv, "kt:")) != -1) {
1946 		switch (ch) {
1947 		case 'k':
1948 			break;
1949 		case 't':
1950 			typestr = optarg;
1951 			dofile = 1;
1952 			break;
1953 		case '?':
1954 		default:
1955 			return (CMD_OK);
1956 		}
1957 	}
1958 	argv += (optind - 1);
1959 	argc -= (optind - 1);
1960 	if (dofile) {
1961 		if ((typestr == NULL) || (*typestr == 0)) {
1962 			command_errmsg = "invalid load type";
1963 			return (CMD_ERROR);
1964 		}
1965 #if 0
1966 		return (file_loadraw(argv[1], typestr, argc - 2, argv + 2, 1)
1967 		    ? CMD_OK : CMD_ERROR);
1968 #endif
1969 		return (CMD_OK);
1970 	}
1971 
1972 	filename = file_search(argv[1]);
1973 	if (filename == NULL) {
1974 		(void) snprintf(command_errbuf, sizeof (command_errbuf),
1975 		    "can't find '%s'", argv[1]);
1976 		return (CMD_ERROR);
1977 	}
1978 	(void) setenv("kernelname", filename, 1);
1979 
1980 	return (CMD_OK);
1981 }
1982 
1983 static int
1984 command_unload(int argc, char *argv[])
1985 {
1986 	(void) unsetenv("kernelname");
1987 	return (CMD_OK);
1988 }
1989 
1990 static int
1991 command_reboot(int argc, char *argv[])
1992 {
1993 	exit(0);
1994 	return (CMD_OK);
1995 }
1996 
1997 static int
1998 command_sifting(int argc, char *argv[])
1999 {
2000 	if (argc != 2) {
2001 		command_errmsg = "wrong number of arguments";
2002 		return (CMD_ERROR);
2003 	}
2004 	ficlPrimitiveSiftingImpl(bf_vm, argv[1]);
2005 	return (CMD_OK);
2006 }
2007 
2008 /* Only implement get and list. Ignore arguments on, off and set. */
2009 static int
2010 command_framebuffer(int argc, char *argv[])
2011 {
2012 	if (fb.fd < 0) {
2013 		printf("Framebuffer is not available.\n");
2014 		return (CMD_OK);
2015 	}
2016 
2017 	if (argc == 2 && strcmp(argv[1], "get") == 0) {
2018 		printf("\nSystem frame buffer: %s\n", fb.ident.name);
2019 		printf("%dx%dx%d, stride=%d\n", fb.fb_width, fb.fb_height,
2020 		    fb.fb_depth, (fb.fb_pitch << 3) / fb.fb_depth);
2021 		return (CMD_OK);
2022 	}
2023 	if (argc == 2 && strcmp(argv[1], "list") == 0) {
2024 		printf("0: %dx%dx%d\n", fb.fb_width, fb.fb_height, fb.fb_depth);
2025 		return (CMD_OK);
2026 	}
2027 	if (argc == 3 && strcmp(argv[1], "set") == 0)
2028 		return (CMD_OK);
2029 	if (argc == 2 && strcmp(argv[1], "on") == 0)
2030 		return (CMD_OK);
2031 	if (argc == 2 && strcmp(argv[1], "off") == 0)
2032 		return (CMD_OK);
2033 
2034 	(void) snprintf(command_errbuf, sizeof (command_errbuf),
2035 	    "usage: %s get | list", argv[0]);
2036 	return (CMD_ERROR);
2037 }
2038