root/usr/src/boot/common/interp.c
/*
 * Copyright (c) 1998 Michael Smith <msmith@freebsd.org>
 * Copyright 2019 OmniOS Community Edition (OmniOSce) Association.
 * 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
 *    documentation and/or other materials provided with the distribution.
 *
 * 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 <sys/cdefs.h>

/*
 * Simple commandline interpreter, toplevel and misc.
 *
 * XXX may be obsoleted by BootFORTH or some other, better, interpreter.
 */

#include <stand.h>
#include <string.h>
#include "bootstrap.h"

#ifdef BOOT_FORTH
#include "ficl.h"
#define RETURN(x)       \
        ficlStackPushInteger(ficlVmGetDataStack(bf_vm), !x); return (x)

extern ficlVm *bf_vm;
#else
#define RETURN(x)       return (x)
#endif

#include "linenoise/linenoise.h"

#define MAXARGS 20              /* maximum number of arguments allowed */

static char *prompt(void);

#ifndef BOOT_FORTH
static int      perform(int argc, char *argv[]);

/*
 * Perform the command
 */
int
perform(int argc, char *argv[])
{
        int                     result;
        struct bootblk_command  **cmdp;
        bootblk_cmd_t           *cmd;

        if (argc < 1)
                return (CMD_OK);

        /* set return defaults; a successful command will override these */
        command_errmsg = command_errbuf;
        strcpy(command_errbuf, "no error message");
        cmd = NULL;
        result = CMD_ERROR;

        /* search the command set for the command */
        SET_FOREACH(cmdp, Xcommand_set) {
                if (((*cmdp)->c_name != NULL) &&
                    strcmp(argv[0], (*cmdp)->c_name) == 0)
                        cmd = (*cmdp)->c_fn;
        }
        if (cmd != NULL) {
                result = (cmd)(argc, argv);
        } else {
                command_errmsg = "unknown command";
        }
        RETURN(result);
}
#endif  /* ! BOOT_FORTH */

/*
 * Interactive mode
 */
void
interact(const char *rc)
{
        char *input = NULL;

        bf_init((rc) ? "" : NULL);

        if (rc == NULL) {
                /* Read our default configuration. */
                (void) include("/boot/loader.rc");
        } else if (*rc != '\0')
                (void) include(rc);

        printf("\n");

        /*
         * Before interacting, we might want to autoboot.
         */
        autoboot_maybe();

        /*
         * Not autobooting, go manual
         */
        printf("\nType '?' for a list of commands, "
            "'help' for more detailed help.\n");
        if (getenv("prompt") == NULL)
                (void) setenv("prompt", "${interpret}", 1);
        if (getenv("interpret") == NULL)
                (void) setenv("interpret", "ok", 1);

        while ((input = linenoise(prompt())) != NULL) {
                bf_vm->sourceId.i = 0;
                (void) bf_run(input);
                (void) linenoiseHistoryAdd(input);
                free(input);
        }
}

/*
 * Read commands from a file, then execute them.
 *
 * We store the commands in memory and close the source file so that the media
 * holding it can safely go away while we are executing.
 *
 * Commands may be prefixed with '@' (so they aren't displayed) or '-' (so
 * that the script won't stop if they fail).
 */
COMMAND_SET(include, "include", "read commands from a file", command_include);

static int
command_include(int argc, char *argv[])
{
        int     i;
        int     res;
        char    **argvbuf;

        /*
         * Since argv is static, we need to save it here.
         */
        argvbuf = calloc((uint_t)argc, sizeof (char *));
        for (i = 0; i < argc; i++)
                argvbuf[i] = strdup(argv[i]);

        res = CMD_OK;
        for (i = 1; (i < argc) && (res == CMD_OK); i++)
                res = include(argvbuf[i]);

        for (i = 0; i < argc; i++)
                free(argvbuf[i]);
        free(argvbuf);

        return (res);
}

COMMAND_SET(sifting, "sifting", "find words", command_sifting);

static int
command_sifting(int argc, char *argv[])
{
        if (argc != 2) {
                command_errmsg = "wrong number of arguments";
                return (CMD_ERROR);
        }
        ficlPrimitiveSiftingImpl(bf_vm, argv[1]);
        return (CMD_OK);
}

/*
 * 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
{
        struct includeline      *next;
        int                     line;
        char                    text[0];
};

/*
 * The PXE TFTP service allows opening exactly one connection at the time,
 * so we need to read included file into memory, then process line by line
 * as it may contain embedded include commands.
 */
int
include(const char *filename)
{
        struct includeline  *script, *se, *sp;
        int res = CMD_OK;
        int     prevsrcid, fd, line;
        char *cp, input[256];           /* big enough? */

        if (((fd = open(filename, O_RDONLY)) == -1)) {
                (void) snprintf(command_errbuf, sizeof (command_errbuf),
                    "can't open '%s': %s", filename, strerror(errno));
                return (CMD_ERROR);
        }
        /*
         * Read the script into memory.
         */
        script = se = NULL;
        line = 0;

        while (fgetstr(input, sizeof (input), fd) >= 0) {
                line++;
                cp = input;
                /* Allocate script line structure and copy line, flags */
                if (*cp == '\0')
                        continue;       /* ignore empty line, save memory */
                if (cp[0] == '\\' && cp[1] == ' ')
                        continue;       /* ignore comment */

                sp = malloc(sizeof (struct includeline) + strlen(cp) + 1);
                /*
                 * On malloc failure (it happens!), free as much as possible
                 * and exit
                 */
                if (sp == NULL) {
                        while (script != NULL) {
                                se = script;
                                script = script->next;
                                free(se);
                        }
                        (void) snprintf(command_errbuf, sizeof (command_errbuf),
                            "file '%s' line %d: memory allocation failure - "
                            "aborting",
                            filename, line);
                        (void) close(fd);
                        return (CMD_ERROR);
                }
                (void) strcpy(sp->text, cp);
                sp->line = line;
                sp->next = NULL;
                if (script == NULL) {
                        script = sp;
                } else {
                        se->next = sp;
                }
                se = sp;
        }
        (void) close(fd);

        /*
         * Execute the script
         */

        prevsrcid = bf_vm->sourceId.i;
        bf_vm->sourceId.i = fd+1;       /* 0 is user input device */

        res = CMD_OK;

        for (sp = script; sp != NULL; sp = sp->next) {
                res = bf_run(sp->text);
                if (res != FICL_VM_STATUS_OUT_OF_TEXT) {
                        (void) snprintf(command_errbuf, sizeof (command_errbuf),
                            "Error while including %s, in the line %d:\n%s",
                            filename, sp->line, sp->text);
                        res = CMD_ERROR;
                        break;
                } else {
                        res = CMD_OK;
                }
        }

        bf_vm->sourceId.i = -1;
        (void) bf_run("");
        bf_vm->sourceId.i = prevsrcid;

        while (script != NULL) {
                se = script;
                script = script->next;
                free(se);
        }

        return (res);
}

/*
 * Emit the current prompt; use the same syntax as the parser
 * for embedding environment variables.
 */
static char *
prompt(void)
{
        static char promptbuf[20];      /* probably too large, but well... */
        char    *pr, *p, *cp, *ev;
        int n = 0;

        if ((cp = getenv("prompt")) == NULL)
                cp = (char *)(uintptr_t)">";
        pr = p = strdup(cp);

        while (*p != 0) {
                if ((*p == '$') && (*(p+1) == '{')) {
                        for (cp = p + 2; (*cp != 0) && (*cp != '}'); cp++)
                                ;
                        *cp = 0;
                        ev = getenv(p + 2);

                        if (ev != NULL)
                                n = sprintf(promptbuf+n, "%s", ev);
                        p = cp + 1;
                        continue;
                }
                promptbuf[n++] = *p;
                p++;
        }
        if (promptbuf[n - 1] != ' ')
                promptbuf[n++] = ' ';
        promptbuf[n] = '\0';
        free(pr);
        return (promptbuf);
}