1 /* sh.c - toybox shell 2 * 3 * Copyright 2006 Rob Landley <rob (at) 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. 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 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 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 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. 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 354 void cd_main(void) 355 { 356 char *dest = *toys.optargs ? *toys.optargs : getenv("HOME"); 357 358 xchdir(dest ? dest : "/"); 359 } 360 361 void exit_main(void) 362 { 363 exit(*toys.optargs ? atoi(*toys.optargs) : 0); 364 } 365 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