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. */
61static int debug_mode = 0;
62#define dprintf if (debug_mode) fprintf
63
64/* Emulate the "/bin/true" command. Arguments are ignored. */
65static int
66true_func (char **argv)
67{
68 return 0;
69}
70
71/* Emulate the "/bin/echo" command. Options are ignored, arguments
72 are printed to stdout. */
73static int
74echo_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. */
92static int
93copy_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
134out:
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. */
145static int
146exit_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. */
157static int
158kill_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. */
177static int
178sleep_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. */
202static 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. */
217static void
218run_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". */
333static void
334run_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. */
420static void
421run_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
443int
444main (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

source code of glibc/support/shell-container.c