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: "make sh" doesn't work (nofork builtins need to be included)
23 * TODO: test that $PS1 color changes work without stupid \[ \] hack
24 * TODO: make fake pty wrapper for test infrastructure
25 * TODO: // Handle embedded NUL bytes in the command line.
26 * TODO: var=val command
27 * existing but considered builtins: false kill pwd true
28 * buitins: alias bg command fc fg getopts jobs newgrp read umask unalias wait
29 * "special" builtins: break continue : . eval exec export readonly return set
30 * shift times trap unset
31 * | & ; < > ( ) $ ` \ " ' <space> <tab> <newline>
32 * * ? [ # ~ = %
33 * ! { } case do done elif else esac fi for if in then until while
34 * [[ ]] function select
35 * $@ $* $# $? $- $$ $! $0
36 * ENV HOME IFS LANG LC_ALL LINENO PATH PPID PS1 PS2 PS4 PWD
37 * label:
38 * TODO: test exit from "trap EXIT" doesn't recurse
39
40 USE_SH(NEWTOY(cd, NULL, TOYFLAG_NOFORK))
41 USE_SH(NEWTOY(exit, NULL, TOYFLAG_NOFORK))
42
43 USE_SH(NEWTOY(sh, "c:i", TOYFLAG_BIN))
44 USE_SH(OLDTOY(toysh, sh, TOYFLAG_BIN))
45 // Login lies in argv[0], so add some aliases to catch that
46 USE_SH(OLDTOY(-sh, sh, 0))
47 USE_SH(OLDTOY(-toysh, sh, 0))
48
49 config SH
50 bool "sh (toysh)"
51 default n
52 help
53 usage: sh [-c command] [script]
54
55 Command shell. Runs a shell script, or reads input interactively
56 and responds to it.
57
58 -c command line to execute
59 -i interactive mode (default when STDIN is a tty)
60
61 config CD
62 bool
63 default n
64 depends on SH
65 help
66 usage: cd [-PL] [path]
67
68 Change current directory. With no arguments, go $HOME.
69
70 -P Physical path: resolve symlinks in path.
71 -L Local path: .. trims directories off $PWD (default).
72
73 config EXIT
74 bool
75 default n
76 depends on SH
77 help
78 usage: exit [status]
79
80 Exit shell. If no return value supplied on command line, use value
81 of most recent command, or 0 if none.
82 */
83
84 #define FOR_sh
85 #include "toys.h"
86
87 GLOBALS(
88 char *command;
89
90 long lineno;
91 )
92
93 // What we know about a single process.
94 struct command {
95 struct command *next;
96 int flags; // exit, suspend, && ||
97 int pid; // pid (or exit code)
98 int argc;
99 char *argv[0];
100 };
101
102 // A collection of processes piped into/waiting on each other.
103 struct pipeline {
104 struct pipeline *next;
105 int job_id;
106 struct command *cmd;
107 char *cmdline; // Unparsed line for display purposes
108 int cmdlinelen; // How long is cmdline?
109 };
110
cd_main(void)111 void cd_main(void)
112 {
113 char *dest = *toys.optargs ? *toys.optargs : getenv("HOME");
114
115 xchdir(dest ? dest : "/");
116 }
117
exit_main(void)118 void exit_main(void)
119 {
120 exit(*toys.optargs ? atoi(*toys.optargs) : 0);
121 }
122
123 // Parse one word from the command line, appending one or more argv[] entries
124 // to struct command. Handles environment variable substitution and
125 // substrings. Returns pointer to next used byte, or NULL if it
126 // hit an ending token.
parse_word(char * start,struct command ** cmd)127 static char *parse_word(char *start, struct command **cmd)
128 {
129 char *end;
130
131 // Detect end of line (and truncate line at comment)
132 if (strchr("><&|(;", *start)) return 0;
133
134 // Grab next word. (Add dequote and envvar logic here)
135 end = start;
136 while (*end && !isspace(*end)) end++;
137 (*cmd)->argv[(*cmd)->argc++] = xstrndup(start, end-start);
138
139 // Allocate more space if there's no room for NULL terminator.
140
141 if (!((*cmd)->argc & 7))
142 *cmd=xrealloc(*cmd,
143 sizeof(struct command) + ((*cmd)->argc+8)*sizeof(char *));
144 (*cmd)->argv[(*cmd)->argc] = 0;
145 return end;
146 }
147
148 // Parse a line of text into a pipeline.
149 // Returns a pointer to the next line.
150
parse_pipeline(char * cmdline,struct pipeline * line)151 static char *parse_pipeline(char *cmdline, struct pipeline *line)
152 {
153 struct command **cmd = &(line->cmd);
154 char *start = line->cmdline = cmdline;
155
156 if (!cmdline) return 0;
157
158 line->cmdline = cmdline;
159
160 // Parse command into argv[]
161 for (;;) {
162 char *end;
163
164 // Skip leading whitespace and detect end of line.
165 while (isspace(*start)) start++;
166 if (!*start || *start=='#') {
167 line->cmdlinelen = start-cmdline;
168 return 0;
169 }
170
171 // Allocate next command structure if necessary
172 if (!*cmd) *cmd = xzalloc(sizeof(struct command)+8*sizeof(char *));
173
174 // Parse next argument and add the results to argv[]
175 end = parse_word(start, cmd);
176
177 // If we hit the end of this command, how did it end?
178 if (!end) {
179 if (*start) {
180 if (*start==';') {
181 start++;
182 break;
183 }
184 // handle | & < > >> << || &&
185 }
186 break;
187 }
188 start = end;
189 }
190
191 line->cmdlinelen = start-cmdline;
192
193 return start;
194 }
195
196 // Execute the commands in a pipeline
run_pipeline(struct pipeline * line)197 static void run_pipeline(struct pipeline *line)
198 {
199 struct toy_list *tl;
200 struct command *cmd = line->cmd;
201 if (!cmd || !cmd->argc) return;
202
203 tl = toy_find(cmd->argv[0]);
204
205 // Is this command a builtin that should run in this process?
206 if (tl && (tl->flags & TOYFLAG_NOFORK)) {
207 struct toy_context temp;
208 jmp_buf rebound;
209
210 // This fakes lots of what toybox_main() does.
211 memcpy(&temp, &toys, sizeof(struct toy_context));
212 memset(&toys, 0, sizeof(struct toy_context));
213
214 if (!setjmp(rebound)) {
215 toys.rebound = &rebound;
216 toy_init(tl, cmd->argv);
217 tl->toy_main();
218 }
219 cmd->pid = toys.exitval;
220 if (toys.optargs != toys.argv+1) free(toys.optargs);
221 if (toys.old_umask) umask(toys.old_umask);
222 memcpy(&toys, &temp, sizeof(struct toy_context));
223 } else {
224 int status;
225
226 cmd->pid = vfork();
227 if (!cmd->pid) xexec(cmd->argv);
228 else waitpid(cmd->pid, &status, 0);
229
230 if (WIFEXITED(status)) cmd->pid = WEXITSTATUS(status);
231 if (WIFSIGNALED(status)) cmd->pid = WTERMSIG(status);
232 }
233
234 return;
235 }
236
237 // Free the contents of a command structure
free_cmd(void * data)238 static void free_cmd(void *data)
239 {
240 struct command *cmd=(struct command *)data;
241
242 while(cmd->argc) free(cmd->argv[--cmd->argc]);
243 }
244
245
246 // Parse a command line and do what it says to do.
handle(char * command)247 static void handle(char *command)
248 {
249 struct pipeline line;
250 char *start = command;
251
252 // Loop through commands in this line
253
254 for (;;) {
255
256 // Parse a group of connected commands
257
258 memset(&line,0,sizeof(struct pipeline));
259 start = parse_pipeline(start, &line);
260 if (!line.cmd) break;
261
262 // Run those commands
263
264 run_pipeline(&line);
265 llist_traverse(line.cmd, free_cmd);
266 }
267 }
268
do_prompt(void)269 static void do_prompt(void)
270 {
271 char *prompt = getenv("PS1"), *s, c, cc;
272
273 if (!prompt) prompt = "\\$ ";
274 while (*prompt) {
275 c = *(prompt++);
276
277 if (c=='!') {
278 if (*prompt=='!') prompt++;
279 else {
280 printf("%ld", TT.lineno);
281 continue;
282 }
283 } else if (c=='\\') {
284 cc = *(prompt++);
285 if (!cc) goto down;
286
287 // \nnn \dD{}hHjlstT@AuvVwW!#$
288 // Ignore bash's "nonprintable" hack; query our cursor position instead.
289 if (cc=='[' || cc==']') continue;
290 else if (cc=='$') putchar(getuid() ? '$' : '#');
291 else if (cc=='h' || cc=='H') {
292 *toybuf = 0;
293 gethostname(toybuf, sizeof(toybuf)-1);
294 if (cc=='h' && (s = strchr(toybuf, '.'))) *s = 0;
295 fputs(toybuf, stdout);
296 } else if (cc=='s') fputs(getbasename(*toys.argv), stdout);
297 else {
298 if (!(c = unescape(cc))) {
299 c = '\\';
300 prompt--;
301 }
302
303 goto down;
304 }
305 continue;
306 }
307 down:
308 putchar(c);
309 }
310 }
311
sh_main(void)312 void sh_main(void)
313 {
314 FILE *f = 0;
315
316 // Set up signal handlers and grab control of this tty.
317 if (isatty(0)) toys.optflags |= FLAG_i;
318
319 if (*toys.optargs) f = xfopen(*toys.optargs, "r");
320 if (TT.command) handle(xstrdup(TT.command));
321 else {
322 size_t cmdlen = 0;
323 for (;;) {
324 char *command = 0;
325
326 // TODO: parse escapes in prompt
327 if (!f) do_prompt();
328 if (1 > getline(&command, &cmdlen, f ? f : stdin)) break;
329 handle(command);
330 free(command);
331 }
332 }
333
334 toys.exitval = 1;
335 }
336