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