2 * Copyright (c) 1998 Michael Smith <msmith@freebsd.org>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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
27 #include <sys/types.h>
39 #include <sys/systeminfo.h>
40 #include <sys/queue.h>
41 #include <sys/mnttab.h>
44 /* Commands and return values; nonzero return sets command_errmsg != NULL */
45 typedef int (bootblk_cmd_t
)(int argc
, char *argv
[]);
50 * Support for commands
52 struct bootblk_command
57 STAILQ_ENTRY(bootblk_command
) next
;
60 #define MDIR_REMOVED 0x0001
61 #define MDIR_NOHINTS 0x0002
64 char *d_path
; /* path of modules directory */
65 uchar_t
*d_hints
; /* content of linker.hints file */
66 int d_hintsz
; /* size of hints data */
68 STAILQ_ENTRY(moduledir
) d_link
;
70 static STAILQ_HEAD(, moduledir
) moduledir_list
=
71 STAILQ_HEAD_INITIALIZER(moduledir_list
);
73 static const char *default_searchpath
= "/platform/i86pc";
75 static char typestr
[] = "?fc?d?b? ?l?s?w";
76 static int ls_getdir(char **pathp
);
77 extern char **_environ
;
80 char command_errbuf
[256];
82 extern void pager_open(void);
83 extern void pager_close(void);
84 extern int pager_output(const char *);
85 extern int pager_file(const char *);
86 static int page_file(char *);
87 static int include(const char *);
89 static int command_help(int argc
, char *argv
[]);
90 static int command_commandlist(int argc
, char *argv
[]);
91 static int command_show(int argc
, char *argv
[]);
92 static int command_set(int argc
, char *argv
[]);
93 static int command_setprop(int argc
, char *argv
[]);
94 static int command_unset(int argc
, char *argv
[]);
95 static int command_echo(int argc
, char *argv
[]);
96 static int command_read(int argc
, char *argv
[]);
97 static int command_more(int argc
, char *argv
[]);
98 static int command_ls(int argc
, char *argv
[]);
99 static int command_include(int argc
, char *argv
[]);
100 static int command_autoboot(int argc
, char *argv
[]);
101 static int command_boot(int argc
, char *argv
[]);
102 static int command_unload(int argc
, char *argv
[]);
103 static int command_load(int argc
, char *argv
[]);
104 static int command_reboot(int argc
, char *argv
[]);
107 #define BF_DICTSIZE 30000
109 /* update when loader version will change */
110 static const char bootprog_rev
[] = "1.1";
111 STAILQ_HEAD(cmdh
, bootblk_command
) commands
;
114 * BootForth Interface to Ficl Forth interpreter.
121 * Redistribution and use in source and binary forms, with or without
122 * modification, are permitted provided that the following conditions
124 * 1. Redistributions of source code must retain the above copyright
125 * notice, this list of conditions and the following disclaimer.
126 * 2. Redistributions in binary form must reproduce the above copyright
127 * notice, this list of conditions and the following disclaimer in the
128 * documentation and/or other materials provided with the distribution.
133 * The meat of the simple parser.
136 static void clean(void);
137 static int insert(int *argcp
, char *buf
);
139 #define PARSE_BUFSIZE 1024 /* maximum size of one element */
140 #define MAXARGS 20 /* maximum number of elements */
141 static char *args
[MAXARGS
];
144 (isdigit(x) ? (x) - '0' : islower(x) ? (x) + 10 - 'a' : (x) + 10 - 'A')
147 * backslash: Return malloc'd copy of str with all standard "backslash
148 * processing" done on it. Original can be free'd if desired.
154 * Remove backslashes from the strings. Turn \040 etc. into a single
155 * character (we allow eight bit values). Currently NUL is not
158 * Turn "\n" and "\t" into '\n' and '\t' characters. Etc.
164 if ((new_str
= strdup(str
)) == NULL
)
176 /* preserve backslashed quotes, dollar signs */
181 new_str
[i
++] = *str
++;
215 new_str
[i
++] = '\13';
223 case '0': case '1': case '2': case '3': case '4':
224 case '5': case '6': case '7': case '8': case '9': {
227 /* Three digit octal constant? */
228 if (*str
>= '0' && *str
<= '3' &&
229 *(str
+ 1) >= '0' && *(str
+ 1) <= '7' &&
230 *(str
+ 2) >= '0' && *(str
+ 2) <= '7') {
232 val
= (DIGIT(*str
) << 6) +
233 (DIGIT(*(str
+ 1)) << 3) +
237 * Allow null value if user really
238 * wants to shoot at feet, but beware!
246 * One or two digit hex constant?
247 * If two are there they will both be taken.
248 * Use \z to split them up if this is not
252 (*(str
+ 1) == 'x' || *(str
+ 1) == 'X') &&
253 isxdigit(*(str
+ 2))) {
254 val
= DIGIT(*(str
+ 2));
255 if (isxdigit(*(str
+ 3))) {
261 /* Yep, allow null value here too */
269 new_str
[i
++] = *str
++;
277 new_str
[i
++] = *str
++;
283 * The final character was a '\'.
284 * Put it in as a single backslash.
293 * parse: accept a string of input and "parse" it for backslash
294 * substitutions and environment variable expansions (${var}),
295 * returning an argc/argv style vector of whitespace separated
296 * arguments. Returns 0 on success, 1 on failure (ok, ok, so I
297 * wimped-out on the error codes! :).
299 * Note that the argv array returned must be freed by the caller, but
300 * we own the space allocated for arguments and will free that on next
301 * invocation. This allows argv consumers to modify the array if
304 * NB: environment variables that expand to more than one whitespace
305 * separated token will be returned as a single argv[] element, not
306 * split in turn. Expanded text is also immune to further backslash
307 * elimination or expansion since this is a one-pass, non-recursive
308 * parser. You didn't specify more than this so if you want more, ask
312 #define PARSE_FAIL(expr) \
314 printf("fail at line %d\n", __LINE__); \
321 /* Accept the usual delimiters for a variable, returning counterpart */
345 parse(int *argc
, char ***argv
, char *str
)
348 char *val
, *p
, *q
, *copy
= NULL
;
350 char token
, tmp
, quote
, dquote
, *buf
;
351 enum { STR
, VAR
, WHITE
} state
;
355 if (!str
|| (p
= copy
= backslash(str
)) == NULL
)
358 /* Initialize vector and state */
361 buf
= (char *)malloc(PARSE_BUFSIZE
);
364 /* And awaaaaaaaaay we go! */
368 if ((*p
== '\\') && p
[1]) {
370 PARSE_FAIL(i
== (PARSE_BUFSIZE
- 1));
372 } else if (isquote(*p
)) {
373 quote
= quote
? 0 : *p
;
374 if (dquote
) { /* keep quote */
375 PARSE_FAIL(i
== (PARSE_BUFSIZE
- 1));
379 } else if (isdquote(*p
)) {
380 dquote
= dquote
? 0 : *p
;
381 if (quote
) { /* keep dquote */
382 PARSE_FAIL(i
== (PARSE_BUFSIZE
- 1));
386 } else if (isspace(*p
) && !quote
&& !dquote
) {
390 PARSE_FAIL(insert(&ac
, buf
));
394 } else if (*p
== '$' && !quote
) {
395 token
= isdelim(*(p
+ 1));
402 PARSE_FAIL(i
== (PARSE_BUFSIZE
- 1));
416 PARSE_FAIL((q
= strchr(p
, token
)) == NULL
);
419 while (*q
&& !isspace(*q
))
424 if ((val
= getenv(p
)) != NULL
) {
425 size_t len
= strlen(val
);
427 strncpy(buf
+ i
, val
, PARSE_BUFSIZE
- (i
+ 1));
428 i
+= min(len
, PARSE_BUFSIZE
- 1);
430 *q
= tmp
; /* restore value */
431 p
= q
+ (token
? 1 : 0);
436 /* missing terminating ' or " */
437 PARSE_FAIL(quote
|| dquote
);
438 /* If at end of token, add it */
439 if (i
&& state
== STR
) {
441 PARSE_FAIL(insert(&ac
, buf
));
445 *argv
= (char **)malloc((sizeof (char *) * ac
+ 1));
446 bcopy(args
, *argv
, sizeof (char *) * ac
+ 1);
454 /* Clean vector space */
460 for (i
= 0; i
< MAXARGS
; i
++) {
461 if (args
[i
] != NULL
) {
469 insert(int *argcp
, char *buf
)
471 if (*argcp
>= MAXARGS
)
473 args
[(*argcp
)++] = strdup(buf
);
484 if ((buf
= malloc(bufsize
)) == NULL
)
486 ret
= sysinfo(SI_ARCHITECTURE_K
, buf
, bufsize
);
495 * Shim for taking commands from BF and passing them out to 'standard'
496 * argv/argc command functions.
499 bf_command(ficlVm
*vm
)
501 char *name
, *line
, *tail
, *cp
;
503 struct bootblk_command
*cmdp
;
509 /* Get the name of the current word */
510 name
= vm
->runningWord
->name
;
512 /* Find our command structure */
514 STAILQ_FOREACH(cmdp
, &commands
, next
) {
515 if ((cmdp
->c_name
!= NULL
) && strcmp(name
, cmdp
->c_name
) == 0)
519 printf("callout for unknown command '%s'\n", name
);
521 /* Check whether we have been compiled or are being interpreted */
522 if (ficlStackPopInteger(ficlVmGetDataStack(vm
))) {
524 * Get parameters from stack, in the format:
525 * an un ... a2 u2 a1 u1 n --
526 * Where n is the number of strings, a/u are pairs of
527 * address/size for strings, and they will be concatenated
530 nstrings
= ficlStackPopInteger(ficlVmGetDataStack(vm
));
531 for (i
= 0, len
= 0; i
< nstrings
; i
++)
532 len
+= ficlStackFetch(ficlVmGetDataStack(vm
), i
* 2).i
+ 1;
533 line
= malloc(strlen(name
) + len
+ 1);
537 for (i
= 0; i
< nstrings
; i
++) {
538 len
= ficlStackPopInteger(
539 ficlVmGetDataStack(vm
));
540 cp
= ficlStackPopPointer(
541 ficlVmGetDataStack(vm
));
543 strncat(line
, cp
, len
);
546 /* Get remainder of invocation */
547 tail
= ficlVmGetInBuf(vm
);
548 for (cp
= tail
, len
= 0;
549 cp
!= vm
->tib
.end
&& *cp
!= 0 && *cp
!= '\n'; cp
++, len
++)
552 line
= malloc(strlen(name
) + len
+ 2);
556 strncat(line
, tail
, len
);
557 ficlVmUpdateTib(vm
, tail
+ len
);
561 command_errmsg
= command_errbuf
;
562 command_errbuf
[0] = 0;
563 if (!parse(&argc
, &argv
, line
)) {
564 result
= (cmd
)(argc
, argv
);
571 * If there was error during nested ficlExec(), we may no longer have
572 * valid environment to return. Throw all exceptions from here.
575 ficlVmThrow(vm
, result
);
576 /* This is going to be thrown!!! */
577 ficlStackPushInteger(ficlVmGetDataStack(vm
), result
);
586 struct mnttab mpref
= {0};
587 struct mnttab mp
= {0};
589 mpref
.mnt_mountp
= "/";
590 fp
= fopen(MNTTAB
, "r");
592 /* do the best we can to return something... */
594 return (strdup(":"));
596 ret
= getmntany(fp
, &mp
, &mpref
);
599 (void) asprintf(&currdev
, "zfs:%s:", mp
.mnt_special
);
601 return (strdup(":"));
607 * Replace a word definition (a builtin command) with another
610 * - Throw error results instead of returning them on the stack
611 * - Pass a flag indicating whether the word was compiled or is
614 * There is one major problem with builtins that cannot be overcome
615 * in anyway, except by outlawing it. We want builtins to behave
616 * differently depending on whether they have been compiled or they
617 * are being interpreted. Notice that this is *not* the interpreter's
618 * current state. For example:
620 * : example ls ; immediate
621 * : problem example ; \ "ls" gets executed while compiling
622 * example \ "ls" gets executed while interpreting
624 * Notice that, though the current state is different in the two
625 * invocations of "example", in both cases "ls" has been
626 * *compiled in*, which is what we really want.
628 * The problem arises when you tick the builtin. For example:
630 * : example-1 ['] ls postpone literal ; immediate
631 * : example-2 example-1 execute ; immediate
632 * : problem example-2 ;
635 * We have no way, when we get EXECUTEd, of knowing what our behavior
636 * should be. Thus, our only alternative is to "outlaw" this. See RFI
637 * 0007, and ANS Forth Standard's appendix D, item 6.7 for a related
638 * problem, concerning compile semantics.
640 * The problem is compounded by the fact that "' builtin CATCH" is valid
641 * and desirable. The only solution is to create an intermediary word.
645 * : example ['] my-ls catch ;
647 * So, with the below implementation, here is a summary of the behavior
650 * ls -l \ "interpret" behavior, ie,
651 * \ takes parameters from TIB
652 * : ex-1 s" -l" 1 ls ; \ "compile" behavior, ie,
653 * \ takes parameters from the stack
654 * : ex-2 ['] ls catch ; immediate \ undefined behavior
655 * : ex-3 ['] ls catch ; \ undefined behavior
656 * ex-2 ex-3 \ "interpret" behavior,
658 * : ex-4 ex-2 ; \ "compile" behavior,
659 * \ catch does not work
660 * : ex-5 ex-3 ; immediate \ same as ex-2
661 * : ex-6 ex-3 ; \ same as ex-3
662 * : ex-7 ['] ex-1 catch ; \ "compile" behavior,
664 * : ex-8 postpone ls ; immediate \ same as ex-2
665 * : ex-9 postpone ls ; \ same as ex-3
667 * As the definition below is particularly tricky, and it's side effects
668 * must be well understood by those playing with it, I'll be heavy on
671 * (if you edit this definition, pay attention to trailing spaces after
672 * each word -- I warned you! :-) )
674 #define BUILTIN_CONSTRUCTOR \
676 ">in @ " /* save the tib index pointer */ \
677 "' " /* get next word's xt */ \
678 "swap >in ! " /* point again to next word */ \
679 "create " /* create a new definition of the next word */ \
680 ", " /* save previous definition's xt */ \
681 "immediate " /* make the new definition an immediate word */ \
683 "does> " /* Now, the *new* definition will: */ \
684 "state @ if " /* if in compiling state: */ \
685 "1 postpone literal " /* pass 1 flag to indicate compile */ \
686 "@ compile, " /* compile in previous definition */ \
687 "postpone throw " /* throw stack-returned result */ \
688 "else " /* if in interpreting state: */ \
689 "0 swap " /* pass 0 flag to indicate interpret */ \
690 "@ execute " /* call previous definition */ \
691 "throw " /* throw stack-returned result */ \
694 extern int ficlExecFD(ficlVm
*, int);
695 #define COMMAND_SET(ptr, name, desc, fn) \
696 ptr = malloc(sizeof (struct bootblk_command)); \
697 ptr->c_name = (name); \
698 ptr->c_desc = (desc); \
702 * Initialise the Forth interpreter, create all our commands as words.
705 bf_init(const char *rc
, ficlOutputFunction out
)
707 struct bootblk_command
*cmdp
;
708 char create_buf
[41]; /* 31 characters-long builtins */
711 ficlSystemInformation
*fsi
;
712 ficlDictionary
*dict
;
715 /* set up commands list */
716 STAILQ_INIT(&commands
);
717 COMMAND_SET(cmdp
, "help", "detailed help", command_help
);
718 STAILQ_INSERT_TAIL(&commands
, cmdp
, next
);
719 COMMAND_SET(cmdp
, "?", "list commands", command_commandlist
);
720 STAILQ_INSERT_TAIL(&commands
, cmdp
, next
);
721 COMMAND_SET(cmdp
, "show", "show variable(s)", command_show
);
722 STAILQ_INSERT_TAIL(&commands
, cmdp
, next
);
723 COMMAND_SET(cmdp
, "printenv", "show variable(s)", command_show
);
724 STAILQ_INSERT_TAIL(&commands
, cmdp
, next
);
725 COMMAND_SET(cmdp
, "set", "set a variable", command_set
);
726 STAILQ_INSERT_TAIL(&commands
, cmdp
, next
);
727 COMMAND_SET(cmdp
, "setprop", "set a variable", command_setprop
);
728 STAILQ_INSERT_TAIL(&commands
, cmdp
, next
);
729 COMMAND_SET(cmdp
, "unset", "unset a variable", command_unset
);
730 STAILQ_INSERT_TAIL(&commands
, cmdp
, next
);
731 COMMAND_SET(cmdp
, "echo", "echo arguments", command_echo
);
732 STAILQ_INSERT_TAIL(&commands
, cmdp
, next
);
733 COMMAND_SET(cmdp
, "read", "read input from the terminal", command_read
);
734 STAILQ_INSERT_TAIL(&commands
, cmdp
, next
);
735 COMMAND_SET(cmdp
, "more", "show contents of a file", command_more
);
736 STAILQ_INSERT_TAIL(&commands
, cmdp
, next
);
737 COMMAND_SET(cmdp
, "ls", "list files", command_ls
);
738 STAILQ_INSERT_TAIL(&commands
, cmdp
, next
);
739 COMMAND_SET(cmdp
, "include", "read commands from a file",
741 STAILQ_INSERT_TAIL(&commands
, cmdp
, next
);
742 COMMAND_SET(cmdp
, "boot", "boot a file or loaded kernel", command_boot
);
743 STAILQ_INSERT_TAIL(&commands
, cmdp
, next
);
744 COMMAND_SET(cmdp
, "autoboot", "boot automatically after a delay",
746 STAILQ_INSERT_TAIL(&commands
, cmdp
, next
);
747 COMMAND_SET(cmdp
, "load", "load a kernel or module", command_load
);
748 STAILQ_INSERT_TAIL(&commands
, cmdp
, next
);
749 COMMAND_SET(cmdp
, "unload", "unload all modules", command_unload
);
750 STAILQ_INSERT_TAIL(&commands
, cmdp
, next
);
751 COMMAND_SET(cmdp
, "reboot", "reboot the system", command_reboot
);
752 STAILQ_INSERT_TAIL(&commands
, cmdp
, next
);
754 fsi
= malloc(sizeof (ficlSystemInformation
));
755 ficlSystemInformationInitialize(fsi
);
757 fsi
->dictionarySize
= BF_DICTSIZE
;
759 bf_sys
= ficlSystemCreate(fsi
);
761 ficlSystemCompileExtras(bf_sys
);
762 bf_vm
= ficlSystemCreateVm(bf_sys
);
765 if (buf
== NULL
|| strcmp(buf
, "amd64") != 0) {
766 (void) setenv("ISADIR", "", 1);
768 (void) setenv("ISADIR", buf
, 1);
773 (void) setenv("currdev", buf
, 1);
776 /* Put all private definitions in a "builtins" vocabulary */
777 rv
= ficlVmEvaluate(bf_vm
,
778 "vocabulary builtins also builtins definitions");
779 if (rv
!= FICL_VM_STATUS_OUT_OF_TEXT
) {
780 printf("error interpreting forth: %d\n", rv
);
784 /* Builtin constructor word */
785 rv
= ficlVmEvaluate(bf_vm
, BUILTIN_CONSTRUCTOR
);
786 if (rv
!= FICL_VM_STATUS_OUT_OF_TEXT
) {
787 printf("error interpreting forth: %d\n", rv
);
791 /* make all commands appear as Forth words */
792 dict
= ficlSystemGetDictionary(bf_sys
);
794 STAILQ_FOREACH(cmdp
, &commands
, next
) {
795 ficlDictionaryAppendPrimitive(dict
, (char *)cmdp
->c_name
,
796 bf_command
, FICL_WORD_DEFAULT
);
797 rv
= ficlVmEvaluate(bf_vm
, "forth definitions builtins");
798 if (rv
!= FICL_VM_STATUS_OUT_OF_TEXT
) {
799 printf("error interpreting forth: %d\n", rv
);
802 sprintf(create_buf
, "builtin: %s", cmdp
->c_name
);
803 rv
= ficlVmEvaluate(bf_vm
, create_buf
);
804 if (rv
!= FICL_VM_STATUS_OUT_OF_TEXT
) {
805 printf("error interpreting forth: %d\n", rv
);
808 rv
= ficlVmEvaluate(bf_vm
, "builtins definitions");
809 if (rv
!= FICL_VM_STATUS_OUT_OF_TEXT
) {
810 printf("error interpreting forth: %d\n", rv
);
814 rv
= ficlVmEvaluate(bf_vm
, "only forth definitions");
815 if (rv
!= FICL_VM_STATUS_OUT_OF_TEXT
) {
816 printf("error interpreting forth: %d\n", rv
);
821 * Export some version numbers so that code can detect the
822 * loader/host version
824 env
= ficlSystemGetEnvironment(bf_sys
);
825 ficlDictionarySetConstant(env
, "loader_version",
826 (bootprog_rev
[0] - '0') * 10 + (bootprog_rev
[2] - '0'));
828 /* try to load and run init file if present */
830 rc
= "/boot/forth/boot.4th";
832 fd
= open(rc
, O_RDONLY
);
834 (void) ficlExecFD(bf_vm
, fd
);
845 ficlSystemDestroy(bf_sys
);
849 * Feed a line of user input to the Forth interpreter
857 FICL_STRING_SET_FROM_CSTRING(s
, line
);
858 result
= ficlVmExecuteString(bf_vm
, s
);
861 case FICL_VM_STATUS_OUT_OF_TEXT
:
862 case FICL_VM_STATUS_ABORTQ
:
863 case FICL_VM_STATUS_QUIT
:
864 case FICL_VM_STATUS_ERROR_EXIT
:
866 case FICL_VM_STATUS_USER_EXIT
:
868 case FICL_VM_STATUS_ABORT
:
869 printf("Aborted!\n");
872 printf("Parse error!\n");
875 if (command_errmsg
!= NULL
) {
876 printf("%s\n", command_errmsg
);
877 command_errmsg
= NULL
;
881 setenv("interpret", bf_vm
->state
? "" : "ok", 1);
887 get_dev(const char *path
)
890 struct mnttab mpref
= {0};
891 struct mnttab mp
= {0};
899 fp
= fopen(MNTTAB
, "r");
901 /* do the best we can to return something... */
903 return (strdup(path
));
906 * the path can have device provided, check for it
909 buf
= strrchr(path
, ':');
911 tmppath
= buf
+1; /* real path */
912 buf
= strchr(path
, ':'); /* skip zfs: */
914 tmpdev
= strdup(buf
);
915 buf
= strchr(tmpdev
, ':'); /* get ending : */
918 tmppath
= (char *)path
;
919 if (tmppath
[0] != '/')
920 if ((cwd
= getcwd(NULL
, PATH_MAX
)) == NULL
) {
922 return (strdup(path
));
925 currdev
= getenv("currdev");
926 buf
= strchr(currdev
, ':'); /* skip zfs: */
929 return (strdup(path
));
932 tmpdev
= strdup(buf
);
933 buf
= strchr(tmpdev
, ':'); /* get ending : */
937 mpref
.mnt_special
= tmpdev
;
938 ret
= getmntany(fp
, &mp
, &mpref
);
943 (void) asprintf(&buf
, "%s/%s", ret
? "":mp
.mnt_mountp
, tmppath
);
945 (void) asprintf(&buf
, "%s/%s/%s", ret
? "":mp
.mnt_mountp
, cwd
,
953 ngets(char *buf
, int n
)
959 switch (c
= getchar() & 0177) {
978 for (p
= buf
; p
< lp
; ++p
)
988 if ((n
< 1) || ((lp
- buf
) < n
- 1)) {
997 fgetstr(char *buf
, int size
, int fd
)
1002 size
--; /* leave space for terminator */
1005 err
= read(fd
, &c
, sizeof (c
));
1006 if (err
< 0) /* read error */
1009 if (err
== 0) { /* EOF */
1011 return (-1); /* nothing to read */
1014 if ((c
== '\r') || (c
== '\n')) /* line terminators */
1016 *buf
++ = c
; /* keep char */
1025 unargv(int argc
, char *argv
[])
1031 for (i
= 0, hlong
= 0; i
< argc
; i
++)
1032 hlong
+= strlen(argv
[i
]) + 2;
1039 for (i
= 0; i
< argc
; i
++) {
1040 strcat(cp
, argv
[i
]);
1049 * Help is read from a formatted text file.
1051 * Entries in the file are formatted as:
1052 * # Ttopic [Ssubtopic] Ddescription
1058 * Note that for code simplicity's sake, the above format must be followed
1061 * Subtopic entries must immediately follow the topic (this is used to
1062 * produce the listing of subtopics).
1064 * If no argument(s) are supplied by the user, the help for 'help' is displayed.
1067 help_getnext(int fd
, char **topic
, char **subtopic
, char **desc
)
1069 char line
[81], *cp
, *ep
;
1072 if (fgetstr(line
, 80, fd
) < 0)
1075 if ((strlen(line
) < 3) || (line
[0] != '#') || (line
[1] != ' '))
1078 *topic
= *subtopic
= *desc
= NULL
;
1080 while ((cp
!= NULL
) && (*cp
!= 0)) {
1081 ep
= strchr(cp
, ' ');
1082 if ((*cp
== 'T') && (*topic
== NULL
)) {
1085 *topic
= strdup(cp
+ 1);
1086 } else if ((*cp
== 'S') && (*subtopic
== NULL
)) {
1089 *subtopic
= strdup(cp
+ 1);
1090 } else if (*cp
== 'D') {
1091 *desc
= strdup(cp
+ 1);
1096 if (*topic
== NULL
) {
1097 if (*subtopic
!= NULL
)
1108 help_emitsummary(char *topic
, char *subtopic
, char *desc
)
1113 pager_output(topic
);
1115 if (subtopic
!= NULL
) {
1117 pager_output(subtopic
);
1118 i
+= strlen(subtopic
) + 1;
1126 return (pager_output("\n"));
1130 command_help(int argc
, char *argv
[])
1132 char buf
[81]; /* XXX buffer size? */
1133 int hfd
, matched
, doindex
;
1134 char *topic
, *subtopic
, *t
, *s
, *d
;
1136 /* page the help text from our load path */
1137 sprintf(buf
, "/boot/loader.help");
1138 if ((hfd
= open(buf
, O_RDONLY
)) < 0) {
1139 printf("Verbose help not available, "
1140 "use '?' to list commands\n");
1144 /* pick up request from arguments */
1145 topic
= subtopic
= NULL
;
1148 subtopic
= strdup(argv
[2]);
1150 topic
= strdup(argv
[1]);
1153 topic
= strdup("help");
1156 command_errmsg
= "usage is 'help <topic> [<subtopic>]";
1161 /* magic "index" keyword */
1162 doindex
= strcmp(topic
, "index") == 0;
1165 /* Scan the helpfile looking for help matching the request */
1167 while (help_getnext(hfd
, &t
, &s
, &d
)) {
1168 if (doindex
) { /* dink around formatting */
1169 if (help_emitsummary(t
, s
, d
))
1172 } else if (strcmp(topic
, t
)) {
1173 /* topic mismatch */
1174 /* nothing more on this topic, stop scanning */
1180 if (((subtopic
== NULL
) && (s
== NULL
)) ||
1181 ((subtopic
!= NULL
) && (s
!= NULL
) &&
1182 strcmp(subtopic
, s
) == 0)) {
1183 /* exact match, print text */
1184 while ((fgetstr(buf
, 80, hfd
) >= 0) &&
1186 if (pager_output(buf
))
1188 if (pager_output("\n"))
1191 } else if ((subtopic
== NULL
) && (s
!= NULL
)) {
1192 /* topic match, list subtopics */
1193 if (help_emitsummary(t
, s
, d
))
1204 snprintf(command_errbuf
, sizeof (command_errbuf
),
1205 "no help available for '%s'", topic
);
1218 command_commandlist(int argc
, char *argv
[])
1220 struct bootblk_command
*cmdp
;
1226 res
= pager_output("Available commands:\n");
1228 STAILQ_FOREACH(cmdp
, &commands
, next
) {
1231 if ((cmdp
->c_name
!= NULL
) && (cmdp
->c_desc
!= NULL
)) {
1232 sprintf(name
, " %-15s ", cmdp
->c_name
);
1234 pager_output(cmdp
->c_desc
);
1235 res
= pager_output("\n");
1243 * XXX set/show should become set/echo if we have variable
1244 * substitution happening.
1247 command_show(int argc
, char *argv
[])
1254 * With no arguments, print everything.
1257 for (ev
= _environ
; *ev
!= NULL
; ev
++) {
1264 if (pager_output("\n"))
1269 if ((cp
= getenv(argv
[1])) != NULL
) {
1272 snprintf(command_errbuf
, sizeof (command_errbuf
),
1273 "variable '%s' not found", argv
[1]);
1281 command_set(int argc
, char *argv
[])
1287 command_errmsg
= "wrong number of arguments";
1290 copy
= strdup(argv
[1]);
1292 command_errmsg
= strerror(errno
);
1295 if ((value
= strchr(copy
, '=')) != NULL
)
1299 if ((err
= setenv(copy
, value
, 1)) != 0) {
1301 command_errmsg
= strerror(errno
);
1310 command_setprop(int argc
, char *argv
[])
1315 command_errmsg
= "wrong number of arguments";
1318 if ((err
= setenv(argv
[1], argv
[2], 1)) != 0) {
1319 command_errmsg
= strerror(err
);
1327 command_unset(int argc
, char *argv
[])
1332 command_errmsg
= "wrong number of arguments";
1335 if ((err
= unsetenv(argv
[1])) != 0) {
1336 command_errmsg
= strerror(err
);
1344 command_echo(int argc
, char *argv
[])
1352 while ((ch
= getopt(argc
, argv
, "n")) != -1) {
1359 /* getopt has already reported an error */
1366 s
= unargv(argc
, argv
);
1377 * A passable emulation of the sh(1) command of the same name.
1386 command_read(int argc
, char *argv
[])
1393 char buf
[256]; /* XXX size? */
1400 while ((c
= getopt(argc
, argv
, "p:t:")) != -1) {
1406 timeout
= strtol(optarg
, &cp
, 0);
1408 snprintf(command_errbuf
,
1409 sizeof (command_errbuf
),
1410 "bad timeout '%s'", optarg
);
1421 name
= (argc
> 0) ? argv
[0]: NULL
;
1424 printf("%s", prompt
);
1426 when
= time(NULL
) + timeout
;
1428 if (time(NULL
) >= when
)
1429 return (CMD_OK
); /* is timeout an error? */
1432 ngets(buf
, sizeof (buf
));
1435 setenv(name
, buf
, 1);
1443 command_more(int argc
, char *argv
[])
1452 for (i
= 1; (i
< argc
) && (res
== 0); i
++) {
1453 sprintf(line
, "*** FILE %s BEGIN ***\n", argv
[i
]);
1454 if (pager_output(line
))
1456 name
= get_dev(argv
[i
]);
1457 res
= page_file(name
);
1460 sprintf(line
, "*** FILE %s END ***\n", argv
[i
]);
1461 res
= pager_output(line
);
1472 page_file(char *filename
)
1476 result
= pager_file(filename
);
1479 snprintf(command_errbuf
, sizeof (command_errbuf
),
1480 "error showing %s", filename
);
1487 command_ls(int argc
, char *argv
[])
1494 char lbuf
[128]; /* one line */
1503 while ((ch
= getopt(argc
, argv
, "l")) != -1) {
1510 /* getopt has already reported an error */
1514 argv
+= (optind
- 1);
1515 argc
-= (optind
- 1);
1523 fd
= ls_getdir(&path
);
1528 dir
= fdopendir(fd
);
1533 while ((d
= readdir(dir
)) != NULL
) {
1534 if (strcmp(d
->d_name
, ".") && strcmp(d
->d_name
, "..")) {
1535 /* stat the file, if possible */
1538 buf
= malloc(strlen(path
) + strlen(d
->d_name
) + 2);
1539 if (path
[0] == '\0')
1540 sprintf(buf
, "%s", d
->d_name
);
1542 sprintf(buf
, "%s/%s", path
, d
->d_name
);
1543 /* ignore return, could be symlink, etc. */
1548 sprintf(lbuf
, " %c %8d %s\n",
1549 typestr
[sb
.st_mode
>> 12],
1550 (int)sb
.st_size
, d
->d_name
);
1552 sprintf(lbuf
, " %c %s\n",
1553 typestr
[sb
.st_mode
>> 12], d
->d_name
);
1555 if (pager_output(lbuf
))
1569 * Given (path) containing a vaguely reasonable path specification, return an fd
1570 * on the directory, and an allocated copy of the path to the directory.
1573 ls_getdir(char **pathp
)
1581 /* one extra byte for a possible trailing slash required */
1582 path
= malloc(strlen(*pathp
) + 2);
1583 strcpy(path
, *pathp
);
1585 /* Make sure the path is respectable to begin with */
1586 if ((cp
= get_dev(path
)) == NULL
) {
1587 snprintf(command_errbuf
, sizeof (command_errbuf
),
1588 "bad path '%s'", path
);
1592 /* If there's no path on the device, assume '/' */
1596 fd
= open(cp
, O_RDONLY
);
1598 snprintf(command_errbuf
, sizeof (command_errbuf
),
1599 "open '%s' failed: %s", path
, strerror(errno
));
1602 if (fstat(fd
, &sb
) < 0) {
1603 snprintf(command_errbuf
, sizeof (command_errbuf
),
1604 "stat failed: %s", strerror(errno
));
1607 if (!S_ISDIR(sb
.st_mode
)) {
1608 snprintf(command_errbuf
, sizeof (command_errbuf
),
1609 "%s: %s", path
, strerror(ENOTDIR
));
1627 command_include(int argc
, char *argv
[])
1634 * Since argv is static, we need to save it here.
1636 argvbuf
= (char **)calloc(argc
, sizeof (char *));
1637 for (i
= 0; i
< argc
; i
++)
1638 argvbuf
[i
] = strdup(argv
[i
]);
1641 for (i
= 1; (i
< argc
) && (res
== CMD_OK
); i
++)
1642 res
= include(argvbuf
[i
]);
1644 for (i
= 0; i
< argc
; i
++)
1652 * Header prepended to each line. The text immediately follows the header.
1653 * We try to make this short in order to save memory -- the loader has
1654 * limited memory available, and some of the forth files are very long.
1658 struct includeline
*next
;
1664 include(const char *filename
)
1666 struct includeline
*script
, *se
, *sp
;
1668 int prevsrcid
, fd
, line
;
1669 char *cp
, input
[256]; /* big enough? */
1672 path
= get_dev(filename
);
1673 if (((fd
= open(path
, O_RDONLY
)) == -1)) {
1674 snprintf(command_errbuf
, sizeof (command_errbuf
),
1675 "can't open '%s': %s", filename
,
1683 * Read the script into memory.
1688 while (fgetstr(input
, sizeof (input
), fd
) >= 0) {
1691 /* Allocate script line structure and copy line, flags */
1693 continue; /* ignore empty line, save memory */
1694 if (cp
[0] == '\\' && cp
[1] == ' ')
1695 continue; /* ignore comment */
1697 sp
= malloc(sizeof (struct includeline
) + strlen(cp
) + 1);
1699 * On malloc failure (it happens!), free as much as possible
1703 while (script
!= NULL
) {
1705 script
= script
->next
;
1708 snprintf(command_errbuf
, sizeof (command_errbuf
),
1709 "file '%s' line %d: memory allocation "
1710 "failure - aborting", filename
, line
);
1713 strcpy(sp
->text
, cp
);
1717 if (script
== NULL
) {
1727 * Execute the script
1730 prevsrcid
= bf_vm
->sourceId
.i
;
1731 bf_vm
->sourceId
.i
= fd
+1; /* 0 is user input device */
1735 for (sp
= script
; sp
!= NULL
; sp
= sp
->next
) {
1736 res
= bf_run(sp
->text
);
1737 if (res
!= FICL_VM_STATUS_OUT_OF_TEXT
) {
1738 snprintf(command_errbuf
, sizeof (command_errbuf
),
1739 "Error while including %s, in the line %d:\n%s",
1740 filename
, sp
->line
, sp
->text
);
1747 bf_vm
->sourceId
.i
= -1;
1749 bf_vm
->sourceId
.i
= prevsrcid
;
1751 while (script
!= NULL
) {
1753 script
= script
->next
;
1761 command_boot(int argc
, char *argv
[])
1767 command_autoboot(int argc
, char *argv
[])
1773 moduledir_rebuild(void)
1775 struct moduledir
*mdp
, *mtmp
;
1776 const char *path
, *cp
, *ep
;
1779 path
= getenv("module_path");
1781 path
= default_searchpath
;
1783 * Rebuild list of module directories if it changed
1785 STAILQ_FOREACH(mdp
, &moduledir_list
, d_link
)
1786 mdp
->d_flags
|= MDIR_REMOVED
;
1788 for (ep
= path
; *ep
!= 0; ep
++) {
1790 for (; *ep
!= 0 && *ep
!= ';'; ep
++)
1793 * Ignore trailing slashes
1795 for (cplen
= ep
- cp
; cplen
> 1 && cp
[cplen
- 1] == '/';
1798 STAILQ_FOREACH(mdp
, &moduledir_list
, d_link
) {
1799 if (strlen(mdp
->d_path
) != cplen
||
1800 bcmp(cp
, mdp
->d_path
, cplen
) != 0)
1802 mdp
->d_flags
&= ~MDIR_REMOVED
;
1806 mdp
= malloc(sizeof (*mdp
) + cplen
+ 1);
1809 mdp
->d_path
= (char *)(mdp
+ 1);
1810 bcopy(cp
, mdp
->d_path
, cplen
);
1811 mdp
->d_path
[cplen
] = 0;
1812 mdp
->d_hints
= NULL
;
1814 STAILQ_INSERT_TAIL(&moduledir_list
, mdp
, d_link
);
1820 * Delete unused directories if any
1822 mdp
= STAILQ_FIRST(&moduledir_list
);
1824 if ((mdp
->d_flags
& MDIR_REMOVED
) == 0) {
1825 mdp
= STAILQ_NEXT(mdp
, d_link
);
1830 mdp
= STAILQ_NEXT(mdp
, d_link
);
1831 STAILQ_REMOVE(&moduledir_list
, mtmp
, moduledir
, d_link
);
1838 file_lookup(const char *path
, const char *name
, int namelen
)
1841 char *result
, *cp
, *gz
;
1844 pathlen
= strlen(path
);
1845 result
= malloc(pathlen
+ namelen
+ 2);
1848 bcopy(path
, result
, pathlen
);
1849 if (pathlen
> 0 && result
[pathlen
- 1] != '/')
1850 result
[pathlen
++] = '/';
1851 cp
= result
+ pathlen
;
1852 bcopy(name
, cp
, namelen
);
1855 if (stat(result
, &st
) == 0 && S_ISREG(st
.st_mode
))
1857 /* also check for gz file */
1858 (void) asprintf(&gz
, "%s.gz", result
);
1860 int res
= stat(gz
, &st
);
1870 file_search(const char *name
)
1872 struct moduledir
*mdp
;
1880 return (strdup(name
));
1882 if (strchr(name
, '/') != NULL
) {
1884 if (stat(name
, &sb
) == 0)
1885 return (strdup(name
));
1886 /* also check for gz file */
1887 (void) asprintf(&gz
, "%s.gz", name
);
1889 int res
= stat(gz
, &sb
);
1892 return (strdup(name
));
1897 moduledir_rebuild();
1899 namelen
= strlen(name
);
1900 STAILQ_FOREACH(mdp
, &moduledir_list
, d_link
) {
1901 result
= file_lookup(mdp
->d_path
, name
, namelen
);
1909 command_load(int argc
, char *argv
[])
1912 char *typestr
= NULL
;
1918 command_errmsg
= "no filename specified";
1922 while ((ch
= getopt(argc
, argv
, "kt:")) != -1) {
1935 argv
+= (optind
- 1);
1936 argc
-= (optind
- 1);
1938 if ((typestr
== NULL
) || (*typestr
== 0)) {
1939 command_errmsg
= "invalid load type";
1943 return (file_loadraw(argv
[1], typestr
, argc
- 2, argv
+ 2, 1)
1944 ? CMD_OK
: CMD_ERROR
);
1949 filename
= file_search(argv
[1]);
1950 if (filename
== NULL
) {
1951 snprintf(command_errbuf
, sizeof (command_errbuf
),
1952 "can't find '%s'", argv
[1]);
1955 setenv("kernelname", filename
, 1);
1961 command_unload(int argc
, char *argv
[])
1963 unsetenv("kernelname");
1968 command_reboot(int argc
, char *argv
[])