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