1 /* sh.c - toybox shell
2 *
3 * Copyright 2006 Rob Landley <rob@landley.net>
4 *
5 * The POSIX-2008/SUSv4 spec for this is at:
6 * http://opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
7 * and http://opengroup.org/onlinepubs/9699919799/utilities/sh.html
8 *
9 * The first link describes the following shell builtins:
10 *
11 * break colon continue dot eval exec exit export readonly return set shift
12 * times trap unset
13 *
14 * The second link (the utilities directory) also contains specs for the
15 * following shell builtins:
16 *
17 * alias bg cd command fc fg getopts hash jobs kill read type ulimit
18 * umask unalias wait
19 *
20 * Things like the bash man page are good to read too.
21 *
22 * TODO: // Handle embedded NUL bytes in the command line.
23
24 USE_SH(NEWTOY(cd, NULL, TOYFLAG_NOFORK))
25 USE_SH(NEWTOY(exit, NULL, TOYFLAG_NOFORK))
26
27 USE_SH(NEWTOY(sh, "c:i", TOYFLAG_BIN))
28 USE_SH(OLDTOY(toysh, sh, TOYFLAG_BIN))
29 // Login lies in argv[0], so add some aliases to catch that
30 USE_SH(OLDTOY(-sh, sh, 0))
31 USE_SH(OLDTOY(-toysh, sh, 0))
32
33 config SH
34 bool "sh (toysh)"
35 default n
36 help
37 usage: sh [-c command] [script]
38
39 Command shell. Runs a shell script, or reads input interactively
40 and responds to it.
41
42 -c command line to execute
43 -i interactive mode (default when STDIN is a tty)
44
45 config EXIT
46 bool
47 default n
48 depends on SH
49 help
50 usage: exit [status]
51
52 Exit shell. If no return value supplied on command line, use value
53 of most recent command, or 0 if none.
54
55 config CD
56 bool
57 default n
58 depends on SH
59 help
60 usage: cd [-PL] [path]
61
62 Change current directory. With no arguments, go $HOME.
63
64 -P Physical path: resolve symlinks in path.
65 -L Local path: .. trims directories off $PWD (default).
66 */
67
68 /*
69 This level of micromanagement is silly, it adds more complexity than it's
70 worth. (Not just to the code, but decision fatigue configuring it.)
71
72 That said, the following list is kept for the moment as a todo list of
73 features I need to implement.
74
75 config SH_PROFILE
76 bool "Profile support"
77 default n
78 depends on SH_TTY
79 help
80 Read /etc/profile and ~/.profile when running interactively.
81
82 Also enables the built-in command "source".
83
84 config SH_JOBCTL
85 bool "Job Control (fg, bg, jobs)"
86 default n
87 depends on SH_TTY
88 help
89 Add job control to toysh. This lets toysh handle CTRL-Z, and enables
90 the built-in commands "fg", "bg", and "jobs".
91
92 With pipe support, enable use of "&" to run background processes.
93
94 config SH_FLOWCTL
95 bool "Flow control (if, while, for, functions)"
96 default n
97 depends on SH
98 help
99 Add flow control to toysh. This enables the if/then/else/fi,
100 while/do/done, and for/do/done constructs.
101
102 With pipe support, this enables the ability to define functions
103 using the "function name" or "name()" syntax, plus curly brackets
104 "{ }" to group commands.
105
106 config SH_QUOTES
107 bool "Smarter argument parsing (quotes)"
108 default n
109 depends on SH
110 help
111 Add support for parsing "" and '' style quotes to the toysh command
112 parser, with lets arguments have spaces in them.
113
114 config SH_WILDCARDS
115 bool "Wildcards ( ?*{,} )"
116 default n
117 depends on SH_QUOTES
118 help
119 Expand wildcards in argument names, ala "ls -l *.t?z" and
120 "rm subdir/{one,two,three}.txt".
121
122 config SH_PROCARGS
123 bool "Executable arguments ( `` and $() )"
124 default n
125 depends on SH_QUOTES
126 help
127 Add support for executing arguments contianing $() and ``, using
128 the output of the command as the new argument value(s).
129
130 (Bash calls this "command substitution".)
131
132 config SH_ENVVARS
133 bool "Environment variable support"
134 default n
135 depends on SH_QUOTES
136 help
137 Substitute environment variable values for $VARNAME or ${VARNAME},
138 and enable the built-in command "export".
139
140 config SH_LOCALS
141 bool "Local variables"
142 default n
143 depends on SH_ENVVARS
144 help
145 Support for local variables, fancy prompts ($PS1), the "set" command,
146 and $?.
147
148 config SH_ARRAYS
149 bool "Array variables"
150 default n
151 depends on SH_LOCALS
152 help
153 Support for ${blah[blah]} style array variables.
154
155 config SH_PIPES
156 bool "Pipes and redirects ( | > >> < << & && | || () ; )"
157 default n
158 depends on SH
159 help
160 Support multiple commands on the same command line. This includes
161 | pipes, > >> < redirects, << here documents, || && conditional
162 execution, () subshells, ; sequential execution, and (with job
163 control) & background processes.
164
165 config SH_BUILTINS
166 bool "Builtin commands"
167 default n
168 depends on SH
169 help
170 Adds the commands exec, fg, bg, help, jobs, pwd, export, source, set,
171 unset, read, alias.
172 */
173
174 #define FOR_sh
175 #include "toys.h"
176
177 GLOBALS(
178 char *command;
179 )
180
181 // A single executable, its arguments, and other information we know about it.
182 #define SH_FLAG_EXIT 1
183 #define SH_FLAG_SUSPEND 2
184 #define SH_FLAG_PIPE 4
185 #define SH_FLAG_AND 8
186 #define SH_FLAG_OR 16
187 #define SH_FLAG_AMP 32
188 #define SH_FLAG_SEMI 64
189 #define SH_FLAG_PAREN 128
190
191 // What we know about a single process.
192 struct command {
193 struct command *next;
194 int flags; // exit, suspend, && ||
195 int pid; // pid (or exit code)
196 int argc;
197 char *argv[0];
198 };
199
200 // A collection of processes piped into/waiting on each other.
201 struct pipeline {
202 struct pipeline *next;
203 int job_id;
204 struct command *cmd;
205 char *cmdline; // Unparsed line for display purposes
206 int cmdlinelen; // How long is cmdline?
207 };
208
209 // Parse one word from the command line, appending one or more argv[] entries
210 // to struct command. Handles environment variable substitution and
211 // substrings. Returns pointer to next used byte, or NULL if it
212 // hit an ending token.
parse_word(char * start,struct command ** cmd)213 static char *parse_word(char *start, struct command **cmd)
214 {
215 char *end;
216
217 // Detect end of line (and truncate line at comment)
218 if (strchr("><&|(;", *start)) return 0;
219
220 // Grab next word. (Add dequote and envvar logic here)
221 end = start;
222 while (*end && !isspace(*end)) end++;
223 (*cmd)->argv[(*cmd)->argc++] = xstrndup(start, end-start);
224
225 // Allocate more space if there's no room for NULL terminator.
226
227 if (!((*cmd)->argc & 7))
228 *cmd=xrealloc(*cmd,
229 sizeof(struct command) + ((*cmd)->argc+8)*sizeof(char *));
230 (*cmd)->argv[(*cmd)->argc] = 0;
231 return end;
232 }
233
234 // Parse a line of text into a pipeline.
235 // Returns a pointer to the next line.
236
parse_pipeline(char * cmdline,struct pipeline * line)237 static char *parse_pipeline(char *cmdline, struct pipeline *line)
238 {
239 struct command **cmd = &(line->cmd);
240 char *start = line->cmdline = cmdline;
241
242 if (!cmdline) return 0;
243
244 line->cmdline = cmdline;
245
246 // Parse command into argv[]
247 for (;;) {
248 char *end;
249
250 // Skip leading whitespace and detect end of line.
251 while (isspace(*start)) start++;
252 if (!*start || *start=='#') {
253 line->cmdlinelen = start-cmdline;
254 return 0;
255 }
256
257 // Allocate next command structure if necessary
258 if (!*cmd) *cmd = xzalloc(sizeof(struct command)+8*sizeof(char *));
259
260 // Parse next argument and add the results to argv[]
261 end = parse_word(start, cmd);
262
263 // If we hit the end of this command, how did it end?
264 if (!end) {
265 if (*start) {
266 if (*start==';') {
267 start++;
268 break;
269 }
270 // handle | & < > >> << || &&
271 }
272 break;
273 }
274 start = end;
275 }
276
277 line->cmdlinelen = start-cmdline;
278
279 return start;
280 }
281
282 // Execute the commands in a pipeline
run_pipeline(struct pipeline * line)283 static void run_pipeline(struct pipeline *line)
284 {
285 struct toy_list *tl;
286 struct command *cmd = line->cmd;
287 if (!cmd || !cmd->argc) return;
288
289 tl = toy_find(cmd->argv[0]);
290 // Is this command a builtin that should run in this process?
291 if (tl && (tl->flags & TOYFLAG_NOFORK)) {
292 struct toy_context temp;
293 jmp_buf rebound;
294
295 // This fakes lots of what toybox_main() does.
296 memcpy(&temp, &toys, sizeof(struct toy_context));
297 memset(&toys, 0, sizeof(struct toy_context));
298
299 if (!setjmp(rebound)) {
300 toys.rebound = &rebound;
301 toy_init(tl, cmd->argv);
302 tl->toy_main();
303 }
304 cmd->pid = toys.exitval;
305 if (toys.optargs != toys.argv+1) free(toys.optargs);
306 if (toys.old_umask) umask(toys.old_umask);
307 memcpy(&toys, &temp, sizeof(struct toy_context));
308 } else {
309 int status;
310
311 cmd->pid = vfork();
312 if (!cmd->pid) xexec(cmd->argv);
313 else waitpid(cmd->pid, &status, 0);
314
315 if (WIFEXITED(status)) cmd->pid = WEXITSTATUS(status);
316 if (WIFSIGNALED(status)) cmd->pid = WTERMSIG(status);
317 }
318
319 return;
320 }
321
322 // Free the contents of a command structure
free_cmd(void * data)323 static void free_cmd(void *data)
324 {
325 struct command *cmd=(struct command *)data;
326
327 while(cmd->argc) free(cmd->argv[--cmd->argc]);
328 }
329
330
331 // Parse a command line and do what it says to do.
handle(char * command)332 static void handle(char *command)
333 {
334 struct pipeline line;
335 char *start = command;
336
337 // Loop through commands in this line
338
339 for (;;) {
340
341 // Parse a group of connected commands
342
343 memset(&line,0,sizeof(struct pipeline));
344 start = parse_pipeline(start, &line);
345 if (!line.cmd) break;
346
347 // Run those commands
348
349 run_pipeline(&line);
350 llist_traverse(line.cmd, free_cmd);
351 }
352 }
353
cd_main(void)354 void cd_main(void)
355 {
356 char *dest = *toys.optargs ? *toys.optargs : getenv("HOME");
357
358 xchdir(dest ? dest : "/");
359 }
360
exit_main(void)361 void exit_main(void)
362 {
363 exit(*toys.optargs ? atoi(*toys.optargs) : 0);
364 }
365
sh_main(void)366 void sh_main(void)
367 {
368 FILE *f;
369
370 // Set up signal handlers and grab control of this tty.
371 if (isatty(0)) toys.optflags |= FLAG_i;
372
373 f = *toys.optargs ? xfopen(*toys.optargs, "r") : NULL;
374 if (TT.command) handle(TT.command);
375 else {
376 size_t cmdlen = 0;
377 for (;;) {
378 char *prompt = getenv("PS1"), *command = 0;
379
380 // TODO: parse escapes in prompt
381 if (!f) printf("%s", prompt ? prompt : "$ ");
382 if (1 > getline(&command, &cmdlen, f ? f : stdin)) break;
383 handle(command);
384 free(command);
385 }
386 }
387
388 toys.exitval = 1;
389 }
390