• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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