/*
* Copyright (c) 1998 Michael Smith <msmith@freebsd.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <limits.h>
#include <unistd.h>
#include <dirent.h>
#include <macros.h>
#include <sys/systeminfo.h>
#include "ficl.h"
/* Commands and return values; nonzero return sets command_errmsg != NULL */
#define CMD_OK 0
/*
* Support for commands
*/
struct bootblk_command
{
const char *c_name;
const char *c_desc;
};
struct moduledir {
int d_flags;
};
extern char **_environ;
char *command_errmsg;
extern void pager_open(void);
extern void pager_close(void);
extern int pager_output(const char *);
extern int pager_file(const char *);
static int page_file(char *);
static int include(const char *);
/* update when loader version will change */
/*
* BootForth Interface to Ficl Forth interpreter.
*/
/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
*
* Jordan K. Hubbard
* 29 August 1998
*
* The meat of the simple parser.
*/
static void clean(void);
#define DIGIT(x) \
/*
* backslash: Return malloc'd copy of str with all standard "backslash
* processing" done on it. Original can be free'd if desired.
*/
char *
{
/*
* Remove backslashes from the strings. Turn \040 etc. into a single
* character (we allow eight bit values). Currently NUL is not
* allowed.
*
* Turn "\n" and "\t" into '\n' and '\t' characters. Etc.
*/
char *new_str;
int seenbs = 0;
int i = 0;
return (NULL);
while (*str) {
if (seenbs) {
seenbs = 0;
switch (*str) {
case '\\':
new_str[i++] = '\\';
str++;
break;
/* preserve backslashed quotes, dollar signs */
case '\'':
case '"':
case '$':
new_str[i++] = '\\';
break;
case 'b':
new_str[i++] = '\b';
str++;
break;
case 'f':
new_str[i++] = '\f';
str++;
break;
case 'r':
new_str[i++] = '\r';
str++;
break;
case 'n':
new_str[i++] = '\n';
str++;
break;
case 's':
new_str[i++] = ' ';
str++;
break;
case 't':
new_str[i++] = '\t';
str++;
break;
case 'v':
new_str[i++] = '\13';
str++;
break;
case 'z':
str++;
break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9': {
char val;
/* Three digit octal constant? */
/*
* Allow null value if user really
* wants to shoot at feet, but beware!
*/
str += 3;
break;
}
/*
* One or two digit hex constant?
* If two are there they will both be taken.
* Use \z to split them up if this is not
* wanted.
*/
if (*str == '0' &&
str += 4;
} else
str += 3;
/* Yep, allow null value here too */
break;
}
}
break;
default:
break;
}
} else {
if (*str == '\\') {
seenbs = 1;
str++;
} else
}
}
if (seenbs) {
/*
* The final character was a '\'.
* Put it in as a single backslash.
*/
new_str[i++] = '\\';
}
new_str[i] = '\0';
return (new_str);
}
/*
* parse: accept a string of input and "parse" it for backslash
* substitutions and environment variable expansions (${var}),
* arguments. Returns 0 on success, 1 on failure (ok, ok, so I
* wimped-out on the error codes! :).
*
* Note that the argv array returned must be freed by the caller, but
* we own the space allocated for arguments and will free that on next
* invocation. This allows argv consumers to modify the array if
* required.
*
* NB: environment variables that expand to more than one whitespace
* separated token will be returned as a single argv[] element, not
* split in turn. Expanded text is also immune to further backslash
* elimination or expansion since this is a one-pass, non-recursive
* parser. You didn't specify more than this so if you want more, ask
* me. - jkh
*/
if (expr) { \
clean(); \
return (1); \
}
/* Accept the usual delimiters for a variable, returning counterpart */
static char
{
if (ch == '{')
return ('}');
else if (ch == '(')
return (')');
return ('\0');
}
static int
{
return (ch == '\'');
}
static int
{
return (ch == '"');
}
int
{
int ac;
size_t i = 0;
return (1);
/* Initialize vector and state */
clean();
token = 0;
/* And awaaaaaaaaay we go! */
while (*p) {
switch (state) {
case STR:
if ((*p == '\\') && p[1]) {
p++;
buf[i++] = *p++;
} else if (isquote(*p)) {
if (dquote) { /* keep quote */
buf[i++] = *p++;
} else
++p;
} else if (isdquote(*p)) {
if (quote) { /* keep dquote */
buf[i++] = *p++;
} else
++p;
if (i) {
buf[i] = '\0';
i = 0;
}
++p;
} else if (*p == '$' && !quote) {
if (token)
p += 2;
else
++p;
} else {
buf[i++] = *p++;
}
break;
case WHITE:
if (isspace(*p))
++p;
else
break;
case VAR:
if (token) {
} else {
q = p;
while (*q && !isspace(*q))
++q;
}
tmp = *q;
*q = '\0';
}
*q = tmp; /* restore value */
p = q + (token ? 1 : 0);
break;
}
}
/* missing terminating ' or " */
/* If at end of token, add it */
buf[i] = '\0';
}
return (0);
}
/* Clean vector space */
static void
clean(void)
{
int i;
for (i = 0; i < MAXARGS; i++) {
}
}
}
static int
{
return (1);
return (0);
}
static char *
isadir(void)
{
char *buf;
int ret;
return (NULL);
if (ret == -1) {
return (NULL);
}
return (buf);
}
/*
* Shim for taking commands from BF and passing them out to 'standard'
*/
static void
{
int nstrings, i;
char **argv;
/* Get the name of the current word */
/* Find our command structure */
}
/* Check whether we have been compiled or are being interpreted */
/*
* Get parameters from stack, in the format:
* an un ... a2 u2 a1 u1 n --
* Where n is the number of strings, a/u are pairs of
* in LIFO order.
*/
if (nstrings)
for (i = 0; i < nstrings; i++) {
}
} else {
/* Get remainder of invocation */
;
if (len > 0) {
}
}
command_errbuf[0] = 0;
} else {
}
/*
* If there was error during nested ficlExec(), we may no longer have
* valid environment to return. Throw all exceptions from here.
*/
if (result != 0)
/* This is going to be thrown!!! */
}
static char *
get_currdev(void)
{
int ret;
char *currdev;
/* do the best we can to return something... */
return (strdup(":"));
if (ret == 0)
else
return (strdup(":"));
return (currdev);
}
/*
* Replace a word definition (a builtin command) with another
* one that:
*
* - Throw error results instead of returning them on the stack
* - Pass a flag indicating whether the word was compiled or is
* being interpreted.
*
* There is one major problem with builtins that cannot be overcome
* in anyway, except by outlawing it. We want builtins to behave
* differently depending on whether they have been compiled or they
* are being interpreted. Notice that this is *not* the interpreter's
* current state. For example:
*
* : example ls ; immediate
* : problem example ; \ "ls" gets executed while compiling
* example \ "ls" gets executed while interpreting
*
* Notice that, though the current state is different in the two
* invocations of "example", in both cases "ls" has been
* *compiled in*, which is what we really want.
*
* The problem arises when you tick the builtin. For example:
*
* : example-1 ['] ls postpone literal ; immediate
* : example-2 example-1 execute ; immediate
* : problem example-2 ;
* example-2
*
* We have no way, when we get EXECUTEd, of knowing what our behavior
* should be. Thus, our only alternative is to "outlaw" this. See RFI
* 0007, and ANS Forth Standard's appendix D, item 6.7 for a related
* problem, concerning compile semantics.
*
* The problem is compounded by the fact that "' builtin CATCH" is valid
* and desirable. The only solution is to create an intermediary word.
* For example:
*
* : my-ls ls ;
* : example ['] my-ls catch ;
*
* So, with the below implementation, here is a summary of the behavior
* of builtins:
*
* ls -l \ "interpret" behavior, ie,
* \ takes parameters from TIB
* : ex-1 s" -l" 1 ls ; \ "compile" behavior, ie,
* \ takes parameters from the stack
* : ex-2 ['] ls catch ; immediate \ undefined behavior
* : ex-3 ['] ls catch ; \ undefined behavior
* ex-2 ex-3 \ "interpret" behavior,
* \ catch works
* : ex-4 ex-2 ; \ "compile" behavior,
* \ catch does not work
* : ex-5 ex-3 ; immediate \ same as ex-2
* : ex-6 ex-3 ; \ same as ex-3
* : ex-7 ['] ex-1 catch ; \ "compile" behavior,
* \ catch works
* : ex-8 postpone ls ; immediate \ same as ex-2
* : ex-9 postpone ls ; \ same as ex-3
*
* As the definition below is particularly tricky, and it's side effects
* must be well understood by those playing with it, I'll be heavy on
* the comments.
*
* (if you edit this definition, pay attention to trailing spaces after
* each word -- I warned you! :-) )
*/
#define BUILTIN_CONSTRUCTOR \
": builtin: " \
">in @ " /* save the tib index pointer */ \
"' " /* get next word's xt */ \
"swap >in ! " /* point again to next word */ \
"create " /* create a new definition of the next word */ \
", " /* save previous definition's xt */ \
"immediate " /* make the new definition an immediate word */ \
\
"does> " /* Now, the *new* definition will: */ \
"state @ if " /* if in compiling state: */ \
"1 postpone literal " /* pass 1 flag to indicate compile */ \
"@ compile, " /* compile in previous definition */ \
"postpone throw " /* throw stack-returned result */ \
"else " /* if in interpreting state: */ \
"0 swap " /* pass 0 flag to indicate interpret */ \
"@ execute " /* call previous definition */ \
"throw " /* throw stack-returned result */ \
"then ; "
extern int ficlExecFD(ficlVm *, int);
/*
* Initialise the Forth interpreter, create all our commands as words.
*/
ficlVm *
{
char *buf;
/* set up commands list */
} else {
}
buf = get_currdev();
/* Put all private definitions in a "builtins" vocabulary */
"vocabulary builtins also builtins definitions");
if (rv != FICL_VM_STATUS_OUT_OF_TEXT) {
exit(1);
}
/* Builtin constructor word */
if (rv != FICL_VM_STATUS_OUT_OF_TEXT) {
exit(1);
}
/* make all commands appear as Forth words */
if (rv != FICL_VM_STATUS_OUT_OF_TEXT) {
exit(1);
}
if (rv != FICL_VM_STATUS_OUT_OF_TEXT) {
exit(1);
}
if (rv != FICL_VM_STATUS_OUT_OF_TEXT) {
exit(1);
}
}
if (rv != FICL_VM_STATUS_OUT_OF_TEXT) {
exit(1);
}
/*
* Export some version numbers so that code can detect the
*/
/* try to load and run init file if present */
if (*rc != '\0') {
if (fd != -1) {
}
}
return (bf_vm);
}
void
bf_fini(void)
{
}
/*
* Feed a line of user input to the Forth interpreter
*/
int
{
int result;
ficlString s;
switch (result) {
case FICL_VM_STATUS_ABORTQ:
case FICL_VM_STATUS_QUIT:
break;
case FICL_VM_STATUS_USER_EXIT:
break;
case FICL_VM_STATUS_ABORT:
printf("Aborted!\n");
break;
case BF_PARSE:
printf("Parse error!\n");
break;
default:
if (command_errmsg != NULL) {
}
}
return (result);
}
char *
{
char *currdev;
int ret;
char *buf;
char *tmppath;
char *tmpdev;
/* do the best we can to return something... */
/*
* the path can have device provided, check for it
* and extract it.
*/
buf++;
*buf = '\0';
} else {
if (tmppath[0] != '/')
}
}
buf++;
*buf = '\0';
}
else {
tmppath);
}
return (buf);
}
static void
{
int c;
char *lp;
switch (c = getchar() & 0177) {
case '\n':
case '\r':
*lp = '\0';
putchar('\n');
return;
case '\b':
case '\177':
lp--;
putchar('\b');
putchar(' ');
putchar('\b');
}
break;
case 'r'&037: {
char *p;
putchar('\n');
putchar(*p);
break;
}
case 'u'&037:
case 'w'&037:
putchar('\n');
break;
default:
*lp++ = c;
putchar(c);
}
}
/*NOTREACHED*/
}
static int
{
char c;
size--; /* leave space for terminator */
len = 0;
while (size != 0) {
if (err < 0) /* read error */
return (-1);
if (err == 0) { /* EOF */
if (len == 0)
return (-1); /* nothing to read */
break;
}
if ((c == '\r') || (c == '\n')) /* line terminators */
break;
*buf++ = c; /* keep char */
size--;
len++;
}
*buf = 0;
return (len);
}
static char *
{
int i;
char *cp;
if (hlong == 0)
return (NULL);
cp[0] = 0;
for (i = 0; i < argc; i++) {
if (i < (argc - 1))
}
return (cp);
}
/*
* Help is read from a formatted text file.
*
* Entries in the file are formatted as:
* # Ttopic [Ssubtopic] Ddescription
* help
* text
* here
* #
*
* Note that for code simplicity's sake, the above format must be followed
* exactly.
*
* Subtopic entries must immediately follow the topic (this is used to
* produce the listing of subtopics).
*
* If no argument(s) are supplied by the user, the help for 'help' is displayed.
*/
static int
{
for (;;) {
return (0);
continue;
*ep++ = 0;
*ep++ = 0;
} else if (*cp == 'D') {
}
}
continue;
}
return (1);
}
}
static int
{
int i;
pager_output(" ");
pager_output(" ");
}
do {
pager_output(" ");
} while (i++ < 30);
}
return (pager_output("\n"));
}
static int
{
/* page the help text from our load path */
printf("Verbose help not available, "
"use '?' to list commands\n");
return (CMD_OK);
}
/* pick up request from arguments */
switch (argc) {
case 3:
case 2:
break;
case 1:
break;
default:
command_errmsg = "usage is 'help <topic> [<subtopic>]";
return (CMD_ERROR);
}
/* magic "index" keyword */
/* Scan the helpfile looking for help matching the request */
pager_open();
while (help_getnext(hfd, &t, &s, &d)) {
if (doindex) { /* dink around formatting */
if (help_emitsummary(t, s, d))
break;
/* topic mismatch */
/* nothing more on this topic, stop scanning */
if (matched)
break;
} else {
/* topic matched */
matched = 1;
/* exact match, print text */
(buf[0] != '#')) {
if (pager_output(buf))
break;
if (pager_output("\n"))
break;
}
/* topic match, list subtopics */
if (help_emitsummary(t, s, d))
break;
}
}
free(t);
free(s);
free(d);
}
pager_close();
if (!matched) {
"no help available for '%s'", topic);
if (subtopic)
return (CMD_ERROR);
}
if (subtopic)
return (CMD_OK);
}
static int
{
int res;
res = 0;
pager_open();
if (res)
break;
}
}
pager_close();
return (CMD_OK);
}
/*
* substitution happening.
*/
static int
{
char **ev;
char *cp;
if (argc < 2) {
/*
* With no arguments, print everything.
*/
pager_open();
pager_output(*ev);
pager_output("=");
}
if (pager_output("\n"))
break;
}
pager_close();
} else {
} else {
return (CMD_ERROR);
}
}
return (CMD_OK);
}
static int
{
int err;
if (argc != 2) {
command_errmsg = "wrong number of arguments";
return (CMD_ERROR);
} else {
return (CMD_ERROR);
}
*(value++) = 0;
else
value = "";
return (CMD_ERROR);
}
}
return (CMD_OK);
}
static int
{
int err;
if (argc != 3) {
command_errmsg = "wrong number of arguments";
return (CMD_ERROR);
} else {
return (CMD_ERROR);
}
}
return (CMD_OK);
}
static int
{
int err;
if (argc != 2) {
command_errmsg = "wrong number of arguments";
return (CMD_ERROR);
} else {
return (CMD_ERROR);
}
}
return (CMD_OK);
}
static int
{
char *s;
nl = 0;
optind = 1;
opterr = 1;
switch (ch) {
case 'n':
nl = 1;
break;
case '?':
default:
/* getopt has already reported an error */
return (CMD_OK);
}
}
if (s != NULL) {
printf("%s", s);
free(s);
}
if (!nl)
printf("\n");
return (CMD_OK);
}
/*
* A passable emulation of the sh(1) command of the same name.
*/
static int
ischar(void)
{
return (1);
}
static int
{
char *prompt;
int timeout;
char *cp;
char *name;
int c;
timeout = -1;
optind = 1;
opterr = 1;
switch (c) {
case 'p':
break;
case 't':
sizeof (command_errbuf),
"bad timeout '%s'", optarg);
return (CMD_ERROR);
}
break;
default:
return (CMD_OK);
}
}
if (timeout >= 0) {
while (!ischar())
return (CMD_OK); /* is timeout an error? */
}
return (CMD_OK);
}
/*
* File pager
*/
static int
{
int i;
int res;
char *name;
res = 0;
pager_open();
if (pager_output(line))
break;
if (!res) {
}
}
pager_close();
if (res == 0)
return (CMD_OK);
return (CMD_ERROR);
}
static int
{
int result;
if (result == -1) {
"error showing %s", filename);
}
return (result);
}
static int
{
int fd;
struct dirent *d;
int verbose;
fd = -1;
verbose = 0;
optind = 1;
opterr = 1;
switch (ch) {
case 'l':
verbose = 1;
break;
case '?':
default:
/* getopt has already reported an error */
return (CMD_OK);
}
}
if (argc < 2) {
path = "";
} else {
}
if (fd == -1) {
goto out;
}
pager_open();
pager_output("\n");
/* stat the file, if possible */
if (path[0] == '\0')
else
/* ignore return, could be symlink, etc. */
if (verbose) {
} else {
}
if (pager_output(lbuf))
goto out;
}
}
out:
pager_close();
if (fd != -1)
return (result);
}
/*
* Given (path) containing a vaguely reasonable path specification, return an fd
* on the directory, and an allocated copy of the path to the directory.
*/
static int
{
int fd;
fd = -1;
/* one extra byte for a possible trailing slash required */
/* Make sure the path is respectable to begin with */
"bad path '%s'", path);
goto out;
}
/* If there's no path on the device, assume '/' */
if (*cp == 0)
if (fd < 0) {
goto out;
}
goto out;
}
goto out;
}
return (fd);
out:
if (fd != -1)
return (-1);
}
static int
{
int i;
int res;
char **argvbuf;
/*
* Since argv is static, we need to save it here.
*/
for (i = 0; i < argc; i++)
for (i = 0; i < argc; i++)
return (res);
}
/*
* Header prepended to each line. The text immediately follows the header.
* We try to make this short in order to save memory -- the loader has
* limited memory available, and some of the forth files are very long.
*/
struct includeline
{
int line;
char text[];
};
int
include(const char *filename)
{
char *path;
"can't open '%s': %s", filename,
return (CMD_ERROR);
}
/*
* Read the script into memory.
*/
line = 0;
line++;
/* Allocate script line structure and copy line, flags */
if (*cp == '\0')
continue; /* ignore empty line, save memory */
continue; /* ignore comment */
/*
* On malloc failure (it happens!), free as much as possible
* and exit
*/
}
"file '%s' line %d: memory allocation "
return (CMD_ERROR);
}
} else {
}
}
/*
* Execute the script
*/
if (res != FICL_VM_STATUS_OUT_OF_TEXT) {
"Error while including %s, in the line %d:\n%s",
break;
} else
}
(void) bf_run("");
}
return (res);
}
static int
{
return (CMD_OK);
}
static int
{
return (CMD_OK);
}
static void
moduledir_rebuild(void)
{
int cplen;
/*
* Rebuild list of module directories if it changed
*/
;
/*
* Ignore trailing slashes
*/
cplen--)
;
continue;
break;
}
return;
}
if (*ep == 0)
break;
}
/*
* Delete unused directories if any
*/
while (mdp) {
} else {
}
}
}
static char *
{
int pathlen;
return (NULL);
*cp = '\0';
return (result);
/* also check for gz file */
if (res == 0)
return (result);
}
return (NULL);
}
static char *
{
char *result;
int namelen;
return (NULL);
if (*name == 0)
char *gz;
/* also check for gz file */
if (res == 0)
}
return (NULL);
}
if (result)
break;
}
return (result);
}
static int
{
char *filename;
dofile = 0;
optind = 1;
if (argc == 1) {
command_errmsg = "no filename specified";
return (CMD_ERROR);
}
switch (ch) {
case 'k':
break;
case 't':
dofile = 1;
break;
case '?':
default:
return (CMD_OK);
}
}
if (dofile) {
command_errmsg = "invalid load type";
return (CMD_ERROR);
}
#if 0
#endif
return (CMD_OK);
}
return (CMD_ERROR);
}
return (CMD_OK);
}
static int
{
unsetenv("kernelname");
return (CMD_OK);
}
static int
{
exit(0);
return (CMD_OK);
}