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
24USE_SH(NEWTOY(cd, NULL, TOYFLAG_NOFORK))
25USE_SH(NEWTOY(exit, NULL, TOYFLAG_NOFORK))
26
27USE_SH(NEWTOY(sh, "c:i", TOYFLAG_BIN))
28USE_SH(OLDTOY(toysh, sh, TOYFLAG_BIN))
29
30config 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
42config 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
52config 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/*
66This level of micromanagement is silly, it adds more complexity than it's
67worth. (Not just to the code, but decision fatigue configuring it.)
68
69That said, the following list is kept for the moment as a todo list of
70features I need to implement.
71
72config 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
81config 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
91config 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
103config 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
111config 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
119config 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
129config 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
137config 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
145config 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
152config 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
162config 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
174GLOBALS(
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.
189struct 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.
198struct 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.
210static 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
234static 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
280static 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
320static 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.
329static 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
351void cd_main(void)
352{
353  char *dest = *toys.optargs ? *toys.optargs : getenv("HOME");
354
355  xchdir(dest ? dest : "/");
356}
357
358void exit_main(void)
359{
360  exit(*toys.optargs ? atoi(*toys.optargs) : 0);
361}
362
363void 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