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