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/cdefs.h> 29 30 /* 31 * Simple commandline interpreter, toplevel and misc. 32 * 33 * XXX may be obsoleted by BootFORTH or some other, better, interpreter. 34 */ 35 36 #include <stand.h> 37 #include <string.h> 38 #include "bootstrap.h" 39 40 #ifdef BOOT_FORTH 41 #include "ficl.h" 42 #define RETURN(x) \ 43 ficlStackPushInteger(ficlVmGetDataStack(bf_vm), !x); return (x) 44 45 extern ficlVm *bf_vm; 46 #else 47 #define RETURN(x) return (x) 48 #endif 49 50 #include "linenoise/linenoise.h" 51 52 #define MAXARGS 20 /* maximum number of arguments allowed */ 53 54 static char *prompt(void); 55 56 #ifndef BOOT_FORTH 57 static int perform(int argc, char *argv[]); 58 59 /* 60 * Perform the command 61 */ 62 int 63 perform(int argc, char *argv[]) 64 { 65 int result; 66 struct bootblk_command **cmdp; 67 bootblk_cmd_t *cmd; 68 69 if (argc < 1) 70 return (CMD_OK); 71 72 /* set return defaults; a successful command will override these */ 73 command_errmsg = command_errbuf; 74 strcpy(command_errbuf, "no error message"); 75 cmd = NULL; 76 result = CMD_ERROR; 77 78 /* search the command set for the command */ 79 SET_FOREACH(cmdp, Xcommand_set) { 80 if (((*cmdp)->c_name != NULL) && 81 strcmp(argv[0], (*cmdp)->c_name) == 0) 82 cmd = (*cmdp)->c_fn; 83 } 84 if (cmd != NULL) { 85 result = (cmd)(argc, argv); 86 } else { 87 command_errmsg = "unknown command"; 88 } 89 RETURN(result); 90 } 91 #endif /* ! BOOT_FORTH */ 92 93 /* 94 * Interactive mode 95 */ 96 void 97 interact(const char *rc) 98 { 99 char *input = NULL; 100 101 bf_init((rc) ? "" : NULL); 102 103 if (rc == NULL) { 104 /* Read our default configuration. */ 105 (void) include("/boot/loader.rc"); 106 } else if (*rc != '\0') 107 (void) include(rc); 108 109 printf("\n"); 110 111 /* 112 * Before interacting, we might want to autoboot. 113 */ 114 autoboot_maybe(); 115 116 /* 117 * Not autobooting, go manual 118 */ 119 printf("\nType '?' for a list of commands, " 120 "'help' for more detailed help.\n"); 121 if (getenv("prompt") == NULL) 122 (void) setenv("prompt", "${interpret}", 1); 123 if (getenv("interpret") == NULL) 124 (void) setenv("interpret", "ok", 1); 125 126 while ((input = linenoise(prompt())) != NULL) { 127 bf_vm->sourceId.i = 0; 128 (void) bf_run(input); 129 (void) linenoiseHistoryAdd(input); 130 free(input); 131 } 132 } 133 134 /* 135 * Read commands from a file, then execute them. 136 * 137 * We store the commands in memory and close the source file so that the media 138 * holding it can safely go away while we are executing. 139 * 140 * Commands may be prefixed with '@' (so they aren't displayed) or '-' (so 141 * that the script won't stop if they fail). 142 */ 143 COMMAND_SET(include, "include", "read commands from a file", command_include); 144 145 static int 146 command_include(int argc, char *argv[]) 147 { 148 int i; 149 int res; 150 char **argvbuf; 151 152 /* 153 * Since argv is static, we need to save it here. 154 */ 155 argvbuf = calloc((uint_t)argc, sizeof (char *)); 156 for (i = 0; i < argc; i++) 157 argvbuf[i] = strdup(argv[i]); 158 159 res = CMD_OK; 160 for (i = 1; (i < argc) && (res == CMD_OK); i++) 161 res = include(argvbuf[i]); 162 163 for (i = 0; i < argc; i++) 164 free(argvbuf[i]); 165 free(argvbuf); 166 167 return (res); 168 } 169 170 COMMAND_SET(sifting, "sifting", "find words", command_sifting); 171 172 static int 173 command_sifting(int argc, char *argv[]) 174 { 175 if (argc != 2) { 176 command_errmsg = "wrong number of arguments"; 177 return (CMD_ERROR); 178 } 179 ficlPrimitiveSiftingImpl(bf_vm, argv[1]); 180 return (CMD_OK); 181 } 182 183 /* 184 * Header prepended to each line. The text immediately follows the header. 185 * We try to make this short in order to save memory -- the loader has 186 * limited memory available, and some of the forth files are very long. 187 */ 188 struct includeline 189 { 190 struct includeline *next; 191 int line; 192 char text[0]; 193 }; 194 195 /* 196 * The PXE TFTP service allows opening exactly one connection at the time, 197 * so we need to read included file into memory, then process line by line 198 * as it may contain embedded include commands. 199 */ 200 int 201 include(const char *filename) 202 { 203 struct includeline *script, *se, *sp; 204 int res = CMD_OK; 205 int prevsrcid, fd, line; 206 char *cp, input[256]; /* big enough? */ 207 208 if (((fd = open(filename, O_RDONLY)) == -1)) { 209 (void) snprintf(command_errbuf, sizeof (command_errbuf), 210 "can't open '%s': %s", filename, strerror(errno)); 211 return (CMD_ERROR); 212 } 213 /* 214 * Read the script into memory. 215 */ 216 script = se = NULL; 217 line = 0; 218 219 while (fgetstr(input, sizeof (input), fd) >= 0) { 220 line++; 221 cp = input; 222 /* Allocate script line structure and copy line, flags */ 223 if (*cp == '\0') 224 continue; /* ignore empty line, save memory */ 225 if (cp[0] == '\\' && cp[1] == ' ') 226 continue; /* ignore comment */ 227 228 sp = malloc(sizeof (struct includeline) + strlen(cp) + 1); 229 /* 230 * On malloc failure (it happens!), free as much as possible 231 * and exit 232 */ 233 if (sp == NULL) { 234 while (script != NULL) { 235 se = script; 236 script = script->next; 237 free(se); 238 } 239 (void) snprintf(command_errbuf, sizeof (command_errbuf), 240 "file '%s' line %d: memory allocation failure - " 241 "aborting", 242 filename, line); 243 (void) close(fd); 244 return (CMD_ERROR); 245 } 246 (void) strcpy(sp->text, cp); 247 sp->line = line; 248 sp->next = NULL; 249 if (script == NULL) { 250 script = sp; 251 } else { 252 se->next = sp; 253 } 254 se = sp; 255 } 256 (void) close(fd); 257 258 /* 259 * Execute the script 260 */ 261 262 prevsrcid = bf_vm->sourceId.i; 263 bf_vm->sourceId.i = fd+1; /* 0 is user input device */ 264 265 res = CMD_OK; 266 267 for (sp = script; sp != NULL; sp = sp->next) { 268 res = bf_run(sp->text); 269 if (res != FICL_VM_STATUS_OUT_OF_TEXT) { 270 (void) snprintf(command_errbuf, sizeof (command_errbuf), 271 "Error while including %s, in the line %d:\n%s", 272 filename, sp->line, sp->text); 273 res = CMD_ERROR; 274 break; 275 } else { 276 res = CMD_OK; 277 } 278 } 279 280 bf_vm->sourceId.i = -1; 281 (void) bf_run(""); 282 bf_vm->sourceId.i = prevsrcid; 283 284 while (script != NULL) { 285 se = script; 286 script = script->next; 287 free(se); 288 } 289 290 return (res); 291 } 292 293 /* 294 * Emit the current prompt; use the same syntax as the parser 295 * for embedding environment variables. 296 */ 297 static char * 298 prompt(void) 299 { 300 static char promptbuf[20]; /* probably too large, but well... */ 301 char *pr, *p, *cp, *ev; 302 int n = 0; 303 304 if ((cp = getenv("prompt")) == NULL) 305 cp = (char *)(uintptr_t)">"; 306 pr = p = strdup(cp); 307 308 while (*p != 0) { 309 if ((*p == '$') && (*(p+1) == '{')) { 310 for (cp = p + 2; (*cp != 0) && (*cp != '}'); cp++) 311 ; 312 *cp = 0; 313 ev = getenv(p + 2); 314 315 if (ev != NULL) 316 n = sprintf(promptbuf+n, "%s", ev); 317 p = cp + 1; 318 continue; 319 } 320 promptbuf[n++] = *p; 321 p++; 322 } 323 if (promptbuf[n - 1] != ' ') 324 promptbuf[n++] = ' '; 325 promptbuf[n] = '\0'; 326 free(pr); 327 return (promptbuf); 328 } 329