1/* Minimal /bin/sh for in-container use.
2 Copyright (C) 2018-2020 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. */
60static int debug_mode = 0;
61#define dprintf if (debug_mode) fprintf
62
63/* Emulate the "/bin/true" command. Arguments are ignored. */
64static int
65true_func (char **argv)
66{
67 return 0;
68}
69
70/* Emulate the "/bin/echo" command. Options are ignored, arguments
71 are printed to stdout. */
72static int
73echo_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. */
91static int
92copy_func (char **argv)
93{
94 char *sname = argv[0];
95 char *dname = argv[1];
96 int sfd, dfd;
97 struct stat st;
98
99 sfd = open (sname, O_RDONLY);
100 if (sfd < 0)
101 {
102 fprintf (stderr, "cp: unable to open %s for reading: %s\n",
103 sname, strerror (errno));
104 return 1;
105 }
106
107 if (fstat (sfd, &st) < 0)
108 {
109 fprintf (stderr, "cp: unable to fstat %s: %s\n",
110 sname, strerror (errno));
111 return 1;
112 }
113
114 dfd = open (dname, O_WRONLY | O_TRUNC | O_CREAT, 0600);
115 if (dfd < 0)
116 {
117 fprintf (stderr, "cp: unable to open %s for writing: %s\n",
118 dname, strerror (errno));
119 return 1;
120 }
121
122 if (support_copy_file_range (sfd, 0, dfd, 0, st.st_size, 0) != st.st_size)
123 {
124 fprintf (stderr, "cp: cannot copy file %s to %s: %s\n",
125 sname, dname, strerror (errno));
126 return 1;
127 }
128
129 close (sfd);
130 close (dfd);
131
132 chmod (dname, st.st_mode & 0777);
133
134 return 0;
135
136}
137
138/* This is a list of all the built-in commands we understand. */
139static struct {
140 const char *name;
141 int (*func) (char **argv);
142} builtin_funcs[] = {
143 { "true", true_func },
144 { "echo", echo_func },
145 { "cp", copy_func },
146 { NULL, NULL }
147};
148
149/* Run one tokenized command. argv[0] is the command. argv is
150 NULL-terminated. */
151static void
152run_command_array (char **argv)
153{
154 int i, j;
155 pid_t pid;
156 int status;
157 int (*builtin_func) (char **args);
158
159 if (argv[0] == NULL)
160 return;
161
162 builtin_func = NULL;
163
164 int new_stdin = 0;
165 int new_stdout = 1;
166 int new_stderr = 2;
167
168 dprintf (stderr, "run_command_array starting\n");
169 for (i = 0; argv[i]; i++)
170 dprintf (stderr, " argv [%d] `%s'\n", i, argv[i]);
171
172 for (j = i = 0; argv[i]; i++)
173 {
174 if (strcmp (argv[i], "<") == 0 && argv[i + 1])
175 {
176 new_stdin = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777);
177 ++i;
178 continue;
179 }
180 if (strcmp (argv[i], ">") == 0 && argv[i + 1])
181 {
182 new_stdout = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777);
183 ++i;
184 continue;
185 }
186 if (strcmp (argv[i], ">>") == 0 && argv[i + 1])
187 {
188 new_stdout = open (argv[i + 1], O_WRONLY|O_CREAT|O_APPEND, 0777);
189 ++i;
190 continue;
191 }
192 if (strcmp (argv[i], "2>") == 0 && argv[i + 1])
193 {
194 new_stderr = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777);
195 ++i;
196 continue;
197 }
198 argv[j++] = argv[i];
199 }
200 argv[j] = NULL;
201
202
203 for (i = 0; builtin_funcs[i].name != NULL; i++)
204 if (strcmp (argv[0], builtin_funcs[i].name) == 0)
205 builtin_func = builtin_funcs[i].func;
206
207 dprintf (stderr, "builtin %p argv0 `%s'\n", builtin_func, argv[0]);
208
209 pid = fork ();
210 if (pid < 0)
211 {
212 fprintf (stderr, "sh: fork failed\n");
213 exit (1);
214 }
215
216 if (pid == 0)
217 {
218 if (new_stdin != 0)
219 {
220 dup2 (new_stdin, 0);
221 close (new_stdin);
222 }
223 if (new_stdout != 1)
224 {
225 dup2 (new_stdout, 1);
226 close (new_stdout);
227 }
228 if (new_stderr != 2)
229 {
230 dup2 (new_stderr, 2);
231 close (new_stdout);
232 }
233
234 if (builtin_func != NULL)
235 exit (builtin_func (argv + 1));
236
237 execvp (argv[0], argv);
238
239 fprintf (stderr, "sh: execing %s failed: %s",
240 argv[0], strerror (errno));
241 exit (1);
242 }
243
244 waitpid (pid, &status, 0);
245
246 dprintf (stderr, "exiting run_command_array\n");
247
248 if (WIFEXITED (status))
249 {
250 int rv = WEXITSTATUS (status);
251 if (rv)
252 exit (rv);
253 }
254 else
255 exit (1);
256}
257
258/* Run one command-as-a-string, by tokenizing it. Limited to
259 MAX_ARG_COUNT arguments. Simple substitution is done of $1 to $9
260 (as whole separate tokens) from iargs[]. Quoted strings work if
261 the quotes wrap whole tokens; i.e. "foo bar" but not foo" bar". */
262static void
263run_command_string (const char *cmdline, const char **iargs)
264{
265 char *args[MAX_ARG_COUNT+1];
266 int ap = 0;
267 const char *start, *end;
268 int nargs;
269
270 for (nargs = 0; iargs[nargs] != NULL; ++nargs)
271 ;
272
273 dprintf (stderr, "run_command_string starting: '%s'\n", cmdline);
274
275 while (ap < MAX_ARG_COUNT)
276 {
277 /* If the argument is quoted, this is the quote character, else NUL. */
278 int in_quote = 0;
279
280 /* Skip whitespace up to the next token. */
281 while (*cmdline && isspace (*cmdline))
282 cmdline ++;
283 if (*cmdline == 0)
284 break;
285
286 start = cmdline;
287 /* Check for quoted argument. */
288 in_quote = (*cmdline == '\'' || *cmdline == '"') ? *cmdline : 0;
289
290 /* Skip to end of token; either by whitespace or matching quote. */
291 dprintf (stderr, "in_quote %d\n", in_quote);
292 while (*cmdline
293 && (!isspace (*cmdline) || in_quote))
294 {
295 if (*cmdline == in_quote
296 && cmdline != start)
297 in_quote = 0;
298 dprintf (stderr, "[%c]%d ", *cmdline, in_quote);
299 cmdline ++;
300 }
301 dprintf (stderr, "\n");
302
303 /* Allocate space for this token and store it in args[]. */
304 end = cmdline;
305 dprintf (stderr, "start<%s> end<%s>\n", start, end);
306 args[ap] = (char *) xmalloc (end - start + 1);
307 memcpy (args[ap], start, end - start);
308 args[ap][end - start] = 0;
309
310 /* Strip off quotes, if found. */
311 dprintf (stderr, "args[%d] = <%s>\n", ap, args[ap]);
312 if (args[ap][0] == '\''
313 && args[ap][strlen (args[ap])-1] == '\'')
314 {
315 args[ap][strlen (args[ap])-1] = 0;
316 args[ap] ++;
317 }
318
319 else if (args[ap][0] == '"'
320 && args[ap][strlen (args[ap])-1] == '"')
321 {
322 args[ap][strlen (args[ap])-1] = 0;
323 args[ap] ++;
324 }
325
326 /* Replace positional parameters like $4. */
327 else if (args[ap][0] == '$'
328 && isdigit (args[ap][1])
329 && args[ap][2] == 0)
330 {
331 int a = args[ap][1] - '1';
332 if (0 <= a && a < nargs)
333 args[ap] = strdup (iargs[a]);
334 }
335
336 ap ++;
337
338 if (*cmdline == 0)
339 break;
340 }
341
342 /* Lastly, NULL terminate the array and run it. */
343 args[ap] = NULL;
344 run_command_array (args);
345}
346
347/* Run a script by reading lines and passing them to the above
348 function. */
349static void
350run_script (const char *filename, const char **args)
351{
352 char line[MAX_LINE_LENGTH + 1];
353 dprintf (stderr, "run_script starting: '%s'\n", filename);
354 FILE *f = fopen (filename, "r");
355 if (f == NULL)
356 {
357 fprintf (stderr, "sh: %s: %s\n", filename, strerror (errno));
358 exit (1);
359 }
360 while (fgets (line, sizeof (line), f) != NULL)
361 {
362 if (line[0] == '#')
363 {
364 dprintf (stderr, "comment: %s\n", line);
365 continue;
366 }
367 run_command_string (line, args);
368 }
369 fclose (f);
370}
371
372int
373main (int argc, const char **argv)
374{
375 int i;
376
377 if (strcmp (argv[1], "--debug") == 0)
378 {
379 debug_mode = 1;
380 --argc;
381 ++argv;
382 }
383
384 dprintf (stderr, "container-sh starting:\n");
385 for (i = 0; i < argc; i++)
386 dprintf (stderr, " argv[%d] is `%s'\n", i, argv[i]);
387
388 if (strcmp (argv[1], "-c") == 0)
389 run_command_string (argv[2], argv+3);
390 else
391 run_script (argv[1], argv+2);
392
393 dprintf (stderr, "normal exit 0\n");
394 return 0;
395}
396