1 | /* Minimal /bin/sh for in-container use. |
2 | Copyright (C) 2018-2022 Free Software Foundation, Inc. |
3 | This file is part of the GNU C Library. |
4 | |
5 | The GNU C Library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Lesser General Public |
7 | License as published by the Free Software Foundation; either |
8 | version 2.1 of the License, or (at your option) any later version. |
9 | |
10 | The GNU C Library is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | Lesser General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU Lesser General Public |
16 | License along with the GNU C Library; if not, see |
17 | <https://www.gnu.org/licenses/>. */ |
18 | |
19 | #define _FILE_OFFSET_BITS 64 |
20 | |
21 | #include <stdio.h> |
22 | #include <stdlib.h> |
23 | #include <string.h> |
24 | #include <sched.h> |
25 | #include <sys/syscall.h> |
26 | #include <unistd.h> |
27 | #include <sys/types.h> |
28 | #include <dirent.h> |
29 | #include <string.h> |
30 | #include <sys/stat.h> |
31 | #include <sys/fcntl.h> |
32 | #include <sys/file.h> |
33 | #include <sys/wait.h> |
34 | #include <stdarg.h> |
35 | #include <sys/sysmacros.h> |
36 | #include <ctype.h> |
37 | #include <utime.h> |
38 | #include <errno.h> |
39 | #include <error.h> |
40 | |
41 | #include <support/support.h> |
42 | #include <support/timespec.h> |
43 | |
44 | /* Design considerations |
45 | |
46 | General rule: optimize for developer time, not run time. |
47 | |
48 | Specifically: |
49 | |
50 | * Don't worry about slow algorithms |
51 | * Don't worry about free'ing memory |
52 | * Don't implement anything the testsuite doesn't need. |
53 | * Line and argument counts are limited, see below. |
54 | |
55 | */ |
56 | |
57 | #define MAX_ARG_COUNT 100 |
58 | #define MAX_LINE_LENGTH 1000 |
59 | |
60 | /* Debugging is enabled via --debug, which must be the first argument. */ |
61 | static int debug_mode = 0; |
62 | #define dprintf if (debug_mode) fprintf |
63 | |
64 | /* Emulate the "/bin/true" command. Arguments are ignored. */ |
65 | static int |
66 | true_func (char **argv) |
67 | { |
68 | return 0; |
69 | } |
70 | |
71 | /* Emulate the "/bin/echo" command. Options are ignored, arguments |
72 | are printed to stdout. */ |
73 | static int |
74 | echo_func (char **argv) |
75 | { |
76 | int i; |
77 | |
78 | for (i = 0; argv[i]; i++) |
79 | { |
80 | if (i > 0) |
81 | putchar (c: ' '); |
82 | fputs (s: argv[i], stdout); |
83 | } |
84 | putchar (c: '\n'); |
85 | |
86 | return 0; |
87 | } |
88 | |
89 | /* Emulate the "/bin/cp" command. Options are ignored. Only copies |
90 | one source file to one destination file. Directory destinations |
91 | are not supported. */ |
92 | static int |
93 | copy_func (char **argv) |
94 | { |
95 | char *sname = argv[0]; |
96 | char *dname = argv[1]; |
97 | int sfd = -1, dfd = -1; |
98 | struct stat st; |
99 | int ret = 1; |
100 | |
101 | sfd = open (file: sname, O_RDONLY); |
102 | if (sfd < 0) |
103 | { |
104 | fprintf (stderr, format: "cp: unable to open %s for reading: %s\n" , |
105 | sname, strerror (errno)); |
106 | return 1; |
107 | } |
108 | |
109 | if (fstat (fd: sfd, buf: &st) < 0) |
110 | { |
111 | fprintf (stderr, format: "cp: unable to fstat %s: %s\n" , |
112 | sname, strerror (errno)); |
113 | goto out; |
114 | } |
115 | |
116 | dfd = open (file: dname, O_WRONLY | O_TRUNC | O_CREAT, 0600); |
117 | if (dfd < 0) |
118 | { |
119 | fprintf (stderr, format: "cp: unable to open %s for writing: %s\n" , |
120 | dname, strerror (errno)); |
121 | goto out; |
122 | } |
123 | |
124 | if (support_copy_file_range (sfd, 0, dfd, 0, st.st_size, 0) != st.st_size) |
125 | { |
126 | fprintf (stderr, format: "cp: cannot copy file %s to %s: %s\n" , |
127 | sname, dname, strerror (errno)); |
128 | goto out; |
129 | } |
130 | |
131 | ret = 0; |
132 | chmod (file: dname, mode: st.st_mode & 0777); |
133 | |
134 | out: |
135 | if (sfd >= 0) |
136 | close (fd: sfd); |
137 | if (dfd >= 0) |
138 | close (fd: dfd); |
139 | |
140 | return ret; |
141 | |
142 | } |
143 | |
144 | /* Emulate the 'exit' builtin. The exit value is optional. */ |
145 | static int |
146 | exit_func (char **argv) |
147 | { |
148 | int exit_val = 0; |
149 | |
150 | if (argv[0] != 0) |
151 | exit_val = atoi (nptr: argv[0]) & 0xff; |
152 | exit (status: exit_val); |
153 | return 0; |
154 | } |
155 | |
156 | /* Emulate the "/bin/kill" command. Options are ignored. */ |
157 | static int |
158 | kill_func (char **argv) |
159 | { |
160 | int signum = SIGTERM; |
161 | int i; |
162 | |
163 | for (i = 0; argv[i]; i++) |
164 | { |
165 | pid_t pid; |
166 | if (strcmp (s1: argv[i], s2: "$$" ) == 0) |
167 | pid = getpid (); |
168 | else |
169 | pid = atoi (nptr: argv[i]); |
170 | kill (pid: pid, sig: signum); |
171 | } |
172 | return 0; |
173 | } |
174 | |
175 | /* Emulate the "/bin/sleep" command. No suffix support. Options are |
176 | ignored. */ |
177 | static int |
178 | sleep_func (char **argv) |
179 | { |
180 | if (argv[0] == NULL) |
181 | { |
182 | fprintf (stderr, format: "sleep: missing operand\n" ); |
183 | return 1; |
184 | } |
185 | char *endptr = NULL; |
186 | double sec = strtod (nptr: argv[0], endptr: &endptr); |
187 | if (endptr == argv[0] || errno == ERANGE || sec < 0) |
188 | { |
189 | fprintf (stderr, format: "sleep: invalid time interval '%s'\n" , argv[0]); |
190 | return 1; |
191 | } |
192 | struct timespec ts = dtotimespec (sec); |
193 | if (nanosleep (requested_time: &ts, NULL) < 0) |
194 | { |
195 | fprintf (stderr, format: "sleep: failed to nanosleep: %s\n" , strerror (errno)); |
196 | return 1; |
197 | } |
198 | return 0; |
199 | } |
200 | |
201 | /* This is a list of all the built-in commands we understand. */ |
202 | static struct { |
203 | const char *name; |
204 | int (*func) (char **argv); |
205 | } builtin_funcs[] = { |
206 | { "true" , true_func }, |
207 | { "echo" , echo_func }, |
208 | { "cp" , copy_func }, |
209 | { "exit" , exit_func }, |
210 | { "kill" , kill_func }, |
211 | { "sleep" , sleep_func }, |
212 | { NULL, NULL } |
213 | }; |
214 | |
215 | /* Run one tokenized command. argv[0] is the command. argv is |
216 | NULL-terminated. */ |
217 | static void |
218 | run_command_array (char **argv) |
219 | { |
220 | int i, j; |
221 | pid_t pid; |
222 | int status; |
223 | int (*builtin_func) (char **args); |
224 | |
225 | if (argv[0] == NULL) |
226 | return; |
227 | |
228 | builtin_func = NULL; |
229 | |
230 | int new_stdin = 0; |
231 | int new_stdout = 1; |
232 | int new_stderr = 2; |
233 | |
234 | dprintf (stderr, format: "run_command_array starting\n" ); |
235 | for (i = 0; argv[i]; i++) |
236 | dprintf (stderr, format: " argv [%d] `%s'\n" , i, argv[i]); |
237 | |
238 | for (j = i = 0; argv[i]; i++) |
239 | { |
240 | if (strcmp (s1: argv[i], s2: "<" ) == 0 && argv[i + 1]) |
241 | { |
242 | new_stdin = open (file: argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777); |
243 | ++i; |
244 | continue; |
245 | } |
246 | if (strcmp (s1: argv[i], s2: ">" ) == 0 && argv[i + 1]) |
247 | { |
248 | new_stdout = open (file: argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777); |
249 | ++i; |
250 | continue; |
251 | } |
252 | if (strcmp (s1: argv[i], s2: ">>" ) == 0 && argv[i + 1]) |
253 | { |
254 | new_stdout = open (file: argv[i + 1], O_WRONLY|O_CREAT|O_APPEND, 0777); |
255 | ++i; |
256 | continue; |
257 | } |
258 | if (strcmp (s1: argv[i], s2: "2>" ) == 0 && argv[i + 1]) |
259 | { |
260 | new_stderr = open (file: argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777); |
261 | ++i; |
262 | continue; |
263 | } |
264 | argv[j++] = argv[i]; |
265 | } |
266 | argv[j] = NULL; |
267 | |
268 | |
269 | for (i = 0; builtin_funcs[i].name != NULL; i++) |
270 | if (strcmp (s1: argv[0], s2: builtin_funcs[i].name) == 0) |
271 | builtin_func = builtin_funcs[i].func; |
272 | |
273 | dprintf (stderr, format: "builtin %p argv0 `%s'\n" , builtin_func, argv[0]); |
274 | |
275 | pid = fork (); |
276 | if (pid < 0) |
277 | { |
278 | fprintf (stderr, format: "sh: fork failed\n" ); |
279 | exit (status: 1); |
280 | } |
281 | |
282 | if (pid == 0) |
283 | { |
284 | if (new_stdin != 0) |
285 | { |
286 | dup2 (fd: new_stdin, fd2: 0); |
287 | close (fd: new_stdin); |
288 | } |
289 | if (new_stdout != 1) |
290 | { |
291 | dup2 (fd: new_stdout, fd2: 1); |
292 | close (fd: new_stdout); |
293 | } |
294 | if (new_stderr != 2) |
295 | { |
296 | dup2 (fd: new_stderr, fd2: 2); |
297 | close (fd: new_stderr); |
298 | } |
299 | |
300 | if (builtin_func != NULL) |
301 | exit (status: builtin_func (argv + 1)); |
302 | |
303 | execvp (file: argv[0], argv: argv); |
304 | |
305 | fprintf (stderr, format: "sh: execing %s failed: %s" , |
306 | argv[0], strerror (errno)); |
307 | exit (status: 127); |
308 | } |
309 | |
310 | waitpid (pid: pid, stat_loc: &status, options: 0); |
311 | |
312 | dprintf (stderr, format: "exiting run_command_array\n" ); |
313 | |
314 | if (WIFEXITED (status)) |
315 | { |
316 | int rv = WEXITSTATUS (status); |
317 | if (rv) |
318 | exit (status: rv); |
319 | } |
320 | else if (WIFSIGNALED (status)) |
321 | { |
322 | int sig = WTERMSIG (status); |
323 | raise (sig: sig); |
324 | } |
325 | else |
326 | exit (status: 1); |
327 | } |
328 | |
329 | /* Run one command-as-a-string, by tokenizing it. Limited to |
330 | MAX_ARG_COUNT arguments. Simple substitution is done of $1 to $9 |
331 | (as whole separate tokens) from iargs[]. Quoted strings work if |
332 | the quotes wrap whole tokens; i.e. "foo bar" but not foo" bar". */ |
333 | static void |
334 | run_command_string (const char *cmdline, const char **iargs) |
335 | { |
336 | char *args[MAX_ARG_COUNT+1]; |
337 | int ap = 0; |
338 | const char *start, *end; |
339 | int nargs; |
340 | |
341 | for (nargs = 0; iargs[nargs] != NULL; ++nargs) |
342 | ; |
343 | |
344 | dprintf (stderr, format: "run_command_string starting: '%s'\n" , cmdline); |
345 | |
346 | while (ap < MAX_ARG_COUNT) |
347 | { |
348 | /* If the argument is quoted, this is the quote character, else NUL. */ |
349 | int in_quote = 0; |
350 | |
351 | /* Skip whitespace up to the next token. */ |
352 | while (*cmdline && isspace (*cmdline)) |
353 | cmdline ++; |
354 | if (*cmdline == 0) |
355 | break; |
356 | |
357 | start = cmdline; |
358 | /* Check for quoted argument. */ |
359 | in_quote = (*cmdline == '\'' || *cmdline == '"') ? *cmdline : 0; |
360 | |
361 | /* Skip to end of token; either by whitespace or matching quote. */ |
362 | dprintf (stderr, format: "in_quote %d\n" , in_quote); |
363 | while (*cmdline |
364 | && (!isspace (*cmdline) || in_quote)) |
365 | { |
366 | if (*cmdline == in_quote |
367 | && cmdline != start) |
368 | in_quote = 0; |
369 | dprintf (stderr, format: "[%c]%d " , *cmdline, in_quote); |
370 | cmdline ++; |
371 | } |
372 | dprintf (stderr, format: "\n" ); |
373 | |
374 | /* Allocate space for this token and store it in args[]. */ |
375 | end = cmdline; |
376 | dprintf (stderr, format: "start<%s> end<%s>\n" , start, end); |
377 | args[ap] = (char *) xmalloc (n: end - start + 1); |
378 | memcpy (dest: args[ap], src: start, n: end - start); |
379 | args[ap][end - start] = 0; |
380 | |
381 | /* Strip off quotes, if found. */ |
382 | dprintf (stderr, format: "args[%d] = <%s>\n" , ap, args[ap]); |
383 | if (args[ap][0] == '\'' |
384 | && args[ap][strlen (s: args[ap])-1] == '\'') |
385 | { |
386 | args[ap][strlen (s: args[ap])-1] = 0; |
387 | args[ap] ++; |
388 | } |
389 | |
390 | else if (args[ap][0] == '"' |
391 | && args[ap][strlen (s: args[ap])-1] == '"') |
392 | { |
393 | args[ap][strlen (s: args[ap])-1] = 0; |
394 | args[ap] ++; |
395 | } |
396 | |
397 | /* Replace positional parameters like $4. */ |
398 | else if (args[ap][0] == '$' |
399 | && isdigit (args[ap][1]) |
400 | && args[ap][2] == 0) |
401 | { |
402 | int a = args[ap][1] - '1'; |
403 | if (0 <= a && a < nargs) |
404 | args[ap] = strdup (s: iargs[a]); |
405 | } |
406 | |
407 | ap ++; |
408 | |
409 | if (*cmdline == 0) |
410 | break; |
411 | } |
412 | |
413 | /* Lastly, NULL terminate the array and run it. */ |
414 | args[ap] = NULL; |
415 | run_command_array (argv: args); |
416 | } |
417 | |
418 | /* Run a script by reading lines and passing them to the above |
419 | function. */ |
420 | static void |
421 | run_script (const char *filename, const char **args) |
422 | { |
423 | char line[MAX_LINE_LENGTH + 1]; |
424 | dprintf (stderr, format: "run_script starting: '%s'\n" , filename); |
425 | FILE *f = fopen (filename: filename, modes: "r" ); |
426 | if (f == NULL) |
427 | { |
428 | fprintf (stderr, format: "sh: %s: %s\n" , filename, strerror (errno)); |
429 | exit (status: 1); |
430 | } |
431 | while (fgets (s: line, n: sizeof (line), stream: f) != NULL) |
432 | { |
433 | if (line[0] == '#') |
434 | { |
435 | dprintf (stderr, format: "comment: %s\n" , line); |
436 | continue; |
437 | } |
438 | run_command_string (cmdline: line, iargs: args); |
439 | } |
440 | fclose (stream: f); |
441 | } |
442 | |
443 | int |
444 | main (int argc, const char **argv) |
445 | { |
446 | int i; |
447 | |
448 | if (strcmp (s1: argv[1], s2: "--debug" ) == 0) |
449 | { |
450 | debug_mode = 1; |
451 | --argc; |
452 | ++argv; |
453 | } |
454 | |
455 | dprintf (stderr, format: "container-sh starting:\n" ); |
456 | for (i = 0; i < argc; i++) |
457 | dprintf (stderr, format: " argv[%d] is `%s'\n" , i, argv[i]); |
458 | |
459 | if (strcmp (s1: argv[1], s2: "-c" ) == 0) |
460 | run_command_string (cmdline: argv[2], iargs: argv+3); |
461 | else |
462 | run_script (filename: argv[1], args: argv+2); |
463 | |
464 | dprintf (stderr, format: "normal exit 0\n" ); |
465 | return 0; |
466 | } |
467 | |