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
43 /* Design considerations
44
45 General rule: optimize for developer time, not run time.
46
47 Specifically:
48
49 * Don't worry about slow algorithms
50 * Don't worry about free'ing memory
51 * Don't implement anything the testsuite doesn't need.
52 * Line and argument counts are limited, see below.
53
54 */
55
56 #define MAX_ARG_COUNT 100
57 #define MAX_LINE_LENGTH 1000
58
59 /* Debugging is enabled via --debug, which must be the first argument. */
60 static int debug_mode = 0;
61 #define dprintf if (debug_mode) fprintf
62
63 /* Emulate the "/bin/true" command. Arguments are ignored. */
64 static int
true_func(char ** argv)65 true_func (char **argv)
66 {
67 return 0;
68 }
69
70 /* Emulate the "/bin/echo" command. Options are ignored, arguments
71 are printed to stdout. */
72 static int
echo_func(char ** argv)73 echo_func (char **argv)
74 {
75 int i;
76
77 for (i = 0; argv[i]; i++)
78 {
79 if (i > 0)
80 putchar (' ');
81 fputs (argv[i], stdout);
82 }
83 putchar ('\n');
84
85 return 0;
86 }
87
88 /* Emulate the "/bin/cp" command. Options are ignored. Only copies
89 one source file to one destination file. Directory destinations
90 are not supported. */
91 static int
copy_func(char ** argv)92 copy_func (char **argv)
93 {
94 char *sname = argv[0];
95 char *dname = argv[1];
96 int sfd = -1, dfd = -1;
97 struct stat st;
98 int ret = 1;
99
100 sfd = open (sname, O_RDONLY);
101 if (sfd < 0)
102 {
103 fprintf (stderr, "cp: unable to open %s for reading: %s\n",
104 sname, strerror (errno));
105 return 1;
106 }
107
108 if (fstat (sfd, &st) < 0)
109 {
110 fprintf (stderr, "cp: unable to fstat %s: %s\n",
111 sname, strerror (errno));
112 goto out;
113 }
114
115 dfd = open (dname, O_WRONLY | O_TRUNC | O_CREAT, 0600);
116 if (dfd < 0)
117 {
118 fprintf (stderr, "cp: unable to open %s for writing: %s\n",
119 dname, strerror (errno));
120 goto out;
121 }
122
123 if (support_copy_file_range (sfd, 0, dfd, 0, st.st_size, 0) != st.st_size)
124 {
125 fprintf (stderr, "cp: cannot copy file %s to %s: %s\n",
126 sname, dname, strerror (errno));
127 goto out;
128 }
129
130 ret = 0;
131 chmod (dname, st.st_mode & 0777);
132
133 out:
134 if (sfd >= 0)
135 close (sfd);
136 if (dfd >= 0)
137 close (dfd);
138
139 return ret;
140
141 }
142
143 /* Emulate the 'exit' builtin. The exit value is optional. */
144 static int
exit_func(char ** argv)145 exit_func (char **argv)
146 {
147 int exit_val = 0;
148
149 if (argv[0] != 0)
150 exit_val = atoi (argv[0]) & 0xff;
151 exit (exit_val);
152 return 0;
153 }
154
155 /* Emulate the "/bin/kill" command. Options are ignored. */
156 static int
kill_func(char ** argv)157 kill_func (char **argv)
158 {
159 int signum = SIGTERM;
160 int i;
161
162 for (i = 0; argv[i]; i++)
163 {
164 pid_t pid;
165 if (strcmp (argv[i], "$$") == 0)
166 pid = getpid ();
167 else
168 pid = atoi (argv[i]);
169 kill (pid, signum);
170 }
171 return 0;
172 }
173
174 /* This is a list of all the built-in commands we understand. */
175 static struct {
176 const char *name;
177 int (*func) (char **argv);
178 } builtin_funcs[] = {
179 { "true", true_func },
180 { "echo", echo_func },
181 { "cp", copy_func },
182 { "exit", exit_func },
183 { "kill", kill_func },
184 { NULL, NULL }
185 };
186
187 /* Run one tokenized command. argv[0] is the command. argv is
188 NULL-terminated. */
189 static void
run_command_array(char ** argv)190 run_command_array (char **argv)
191 {
192 int i, j;
193 pid_t pid;
194 int status;
195 int (*builtin_func) (char **args);
196
197 if (argv[0] == NULL)
198 return;
199
200 builtin_func = NULL;
201
202 int new_stdin = 0;
203 int new_stdout = 1;
204 int new_stderr = 2;
205
206 dprintf (stderr, "run_command_array starting\n");
207 for (i = 0; argv[i]; i++)
208 dprintf (stderr, " argv [%d] `%s'\n", i, argv[i]);
209
210 for (j = i = 0; argv[i]; i++)
211 {
212 if (strcmp (argv[i], "<") == 0 && argv[i + 1])
213 {
214 new_stdin = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777);
215 ++i;
216 continue;
217 }
218 if (strcmp (argv[i], ">") == 0 && argv[i + 1])
219 {
220 new_stdout = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777);
221 ++i;
222 continue;
223 }
224 if (strcmp (argv[i], ">>") == 0 && argv[i + 1])
225 {
226 new_stdout = open (argv[i + 1], O_WRONLY|O_CREAT|O_APPEND, 0777);
227 ++i;
228 continue;
229 }
230 if (strcmp (argv[i], "2>") == 0 && argv[i + 1])
231 {
232 new_stderr = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777);
233 ++i;
234 continue;
235 }
236 argv[j++] = argv[i];
237 }
238 argv[j] = NULL;
239
240
241 for (i = 0; builtin_funcs[i].name != NULL; i++)
242 if (strcmp (argv[0], builtin_funcs[i].name) == 0)
243 builtin_func = builtin_funcs[i].func;
244
245 dprintf (stderr, "builtin %p argv0 `%s'\n", builtin_func, argv[0]);
246
247 pid = fork ();
248 if (pid < 0)
249 {
250 fprintf (stderr, "sh: fork failed\n");
251 exit (1);
252 }
253
254 if (pid == 0)
255 {
256 if (new_stdin != 0)
257 {
258 dup2 (new_stdin, 0);
259 close (new_stdin);
260 }
261 if (new_stdout != 1)
262 {
263 dup2 (new_stdout, 1);
264 close (new_stdout);
265 }
266 if (new_stderr != 2)
267 {
268 dup2 (new_stderr, 2);
269 close (new_stderr);
270 }
271
272 if (builtin_func != NULL)
273 exit (builtin_func (argv + 1));
274
275 execvp (argv[0], argv);
276
277 fprintf (stderr, "sh: execing %s failed: %s",
278 argv[0], strerror (errno));
279 exit (127);
280 }
281
282 waitpid (pid, &status, 0);
283
284 dprintf (stderr, "exiting run_command_array\n");
285
286 if (WIFEXITED (status))
287 {
288 int rv = WEXITSTATUS (status);
289 if (rv)
290 exit (rv);
291 }
292 else if (WIFSIGNALED (status))
293 {
294 int sig = WTERMSIG (status);
295 raise (sig);
296 }
297 else
298 exit (1);
299 }
300
301 /* Run one command-as-a-string, by tokenizing it. Limited to
302 MAX_ARG_COUNT arguments. Simple substitution is done of $1 to $9
303 (as whole separate tokens) from iargs[]. Quoted strings work if
304 the quotes wrap whole tokens; i.e. "foo bar" but not foo" bar". */
305 static void
run_command_string(const char * cmdline,const char ** iargs)306 run_command_string (const char *cmdline, const char **iargs)
307 {
308 char *args[MAX_ARG_COUNT+1];
309 int ap = 0;
310 const char *start, *end;
311 int nargs;
312
313 for (nargs = 0; iargs[nargs] != NULL; ++nargs)
314 ;
315
316 dprintf (stderr, "run_command_string starting: '%s'\n", cmdline);
317
318 while (ap < MAX_ARG_COUNT)
319 {
320 /* If the argument is quoted, this is the quote character, else NUL. */
321 int in_quote = 0;
322
323 /* Skip whitespace up to the next token. */
324 while (*cmdline && isspace (*cmdline))
325 cmdline ++;
326 if (*cmdline == 0)
327 break;
328
329 start = cmdline;
330 /* Check for quoted argument. */
331 in_quote = (*cmdline == '\'' || *cmdline == '"') ? *cmdline : 0;
332
333 /* Skip to end of token; either by whitespace or matching quote. */
334 dprintf (stderr, "in_quote %d\n", in_quote);
335 while (*cmdline
336 && (!isspace (*cmdline) || in_quote))
337 {
338 if (*cmdline == in_quote
339 && cmdline != start)
340 in_quote = 0;
341 dprintf (stderr, "[%c]%d ", *cmdline, in_quote);
342 cmdline ++;
343 }
344 dprintf (stderr, "\n");
345
346 /* Allocate space for this token and store it in args[]. */
347 end = cmdline;
348 dprintf (stderr, "start<%s> end<%s>\n", start, end);
349 args[ap] = (char *) xmalloc (end - start + 1);
350 memcpy (args[ap], start, end - start);
351 args[ap][end - start] = 0;
352
353 /* Strip off quotes, if found. */
354 dprintf (stderr, "args[%d] = <%s>\n", ap, args[ap]);
355 if (args[ap][0] == '\''
356 && args[ap][strlen (args[ap])-1] == '\'')
357 {
358 args[ap][strlen (args[ap])-1] = 0;
359 args[ap] ++;
360 }
361
362 else if (args[ap][0] == '"'
363 && args[ap][strlen (args[ap])-1] == '"')
364 {
365 args[ap][strlen (args[ap])-1] = 0;
366 args[ap] ++;
367 }
368
369 /* Replace positional parameters like $4. */
370 else if (args[ap][0] == '$'
371 && isdigit (args[ap][1])
372 && args[ap][2] == 0)
373 {
374 int a = args[ap][1] - '1';
375 if (0 <= a && a < nargs)
376 args[ap] = strdup (iargs[a]);
377 }
378
379 ap ++;
380
381 if (*cmdline == 0)
382 break;
383 }
384
385 /* Lastly, NULL terminate the array and run it. */
386 args[ap] = NULL;
387 run_command_array (args);
388 }
389
390 /* Run a script by reading lines and passing them to the above
391 function. */
392 static void
run_script(const char * filename,const char ** args)393 run_script (const char *filename, const char **args)
394 {
395 char line[MAX_LINE_LENGTH + 1];
396 dprintf (stderr, "run_script starting: '%s'\n", filename);
397 FILE *f = fopen (filename, "r");
398 if (f == NULL)
399 {
400 fprintf (stderr, "sh: %s: %s\n", filename, strerror (errno));
401 exit (1);
402 }
403 while (fgets (line, sizeof (line), f) != NULL)
404 {
405 if (line[0] == '#')
406 {
407 dprintf (stderr, "comment: %s\n", line);
408 continue;
409 }
410 run_command_string (line, args);
411 }
412 fclose (f);
413 }
414
415 int
main(int argc,const char ** argv)416 main (int argc, const char **argv)
417 {
418 int i;
419
420 if (strcmp (argv[1], "--debug") == 0)
421 {
422 debug_mode = 1;
423 --argc;
424 ++argv;
425 }
426
427 dprintf (stderr, "container-sh starting:\n");
428 for (i = 0; i < argc; i++)
429 dprintf (stderr, " argv[%d] is `%s'\n", i, argv[i]);
430
431 if (strcmp (argv[1], "-c") == 0)
432 run_command_string (argv[2], argv+3);
433 else
434 run_script (argv[1], argv+2);
435
436 dprintf (stderr, "normal exit 0\n");
437 return 0;
438 }
439