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