]> git.ipfire.org Git - thirdparty/glibc.git/blame - support/shell-container.c
support/shell-container.c: Add builtin exit
[thirdparty/glibc.git] / support / shell-container.c
CommitLineData
561b0bec 1/* Minimal /bin/sh for in-container use.
d614a753 2 Copyright (C) 2018-2020 Free Software Foundation, Inc.
561b0bec
DD
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
5a82c748 17 <https://www.gnu.org/licenses/>. */
561b0bec
DD
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
6e36266c 122 if (support_copy_file_range (sfd, 0, dfd, 0, st.st_size, 0) != st.st_size)
561b0bec
DD
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
5a5a3a32
AZ
138/* Emulate the 'exit' builtin. The exit value is optional. */
139static int
140exit_func (char **argv)
141{
142 int exit_val = 0;
143
144 if (argv[0] != 0)
145 exit_val = atoi (argv[0]) & 0xff;
146 exit (exit_val);
147 return 0;
148}
149
561b0bec
DD
150/* This is a list of all the built-in commands we understand. */
151static struct {
152 const char *name;
153 int (*func) (char **argv);
154} builtin_funcs[] = {
155 { "true", true_func },
156 { "echo", echo_func },
157 { "cp", copy_func },
5a5a3a32 158 { "exit", exit_func },
561b0bec
DD
159 { NULL, NULL }
160};
161
162/* Run one tokenized command. argv[0] is the command. argv is
163 NULL-terminated. */
164static void
165run_command_array (char **argv)
166{
167 int i, j;
168 pid_t pid;
169 int status;
170 int (*builtin_func) (char **args);
171
172 if (argv[0] == NULL)
173 return;
174
175 builtin_func = NULL;
176
177 int new_stdin = 0;
178 int new_stdout = 1;
179 int new_stderr = 2;
180
181 dprintf (stderr, "run_command_array starting\n");
182 for (i = 0; argv[i]; i++)
183 dprintf (stderr, " argv [%d] `%s'\n", i, argv[i]);
184
185 for (j = i = 0; argv[i]; i++)
186 {
187 if (strcmp (argv[i], "<") == 0 && argv[i + 1])
188 {
189 new_stdin = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777);
190 ++i;
191 continue;
192 }
193 if (strcmp (argv[i], ">") == 0 && argv[i + 1])
194 {
195 new_stdout = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777);
196 ++i;
197 continue;
198 }
199 if (strcmp (argv[i], ">>") == 0 && argv[i + 1])
200 {
201 new_stdout = open (argv[i + 1], O_WRONLY|O_CREAT|O_APPEND, 0777);
202 ++i;
203 continue;
204 }
205 if (strcmp (argv[i], "2>") == 0 && argv[i + 1])
206 {
207 new_stderr = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777);
208 ++i;
209 continue;
210 }
211 argv[j++] = argv[i];
212 }
213 argv[j] = NULL;
214
215
216 for (i = 0; builtin_funcs[i].name != NULL; i++)
217 if (strcmp (argv[0], builtin_funcs[i].name) == 0)
218 builtin_func = builtin_funcs[i].func;
219
220 dprintf (stderr, "builtin %p argv0 `%s'\n", builtin_func, argv[0]);
221
222 pid = fork ();
223 if (pid < 0)
224 {
225 fprintf (stderr, "sh: fork failed\n");
226 exit (1);
227 }
228
229 if (pid == 0)
230 {
231 if (new_stdin != 0)
232 {
233 dup2 (new_stdin, 0);
234 close (new_stdin);
235 }
236 if (new_stdout != 1)
237 {
238 dup2 (new_stdout, 1);
239 close (new_stdout);
240 }
241 if (new_stderr != 2)
242 {
243 dup2 (new_stderr, 2);
542160f0 244 close (new_stderr);
561b0bec
DD
245 }
246
247 if (builtin_func != NULL)
248 exit (builtin_func (argv + 1));
249
250 execvp (argv[0], argv);
251
252 fprintf (stderr, "sh: execing %s failed: %s",
253 argv[0], strerror (errno));
5fce0e09 254 exit (127);
561b0bec
DD
255 }
256
257 waitpid (pid, &status, 0);
258
259 dprintf (stderr, "exiting run_command_array\n");
260
261 if (WIFEXITED (status))
262 {
263 int rv = WEXITSTATUS (status);
264 if (rv)
265 exit (rv);
266 }
267 else
268 exit (1);
269}
270
271/* Run one command-as-a-string, by tokenizing it. Limited to
272 MAX_ARG_COUNT arguments. Simple substitution is done of $1 to $9
273 (as whole separate tokens) from iargs[]. Quoted strings work if
274 the quotes wrap whole tokens; i.e. "foo bar" but not foo" bar". */
275static void
276run_command_string (const char *cmdline, const char **iargs)
277{
278 char *args[MAX_ARG_COUNT+1];
279 int ap = 0;
280 const char *start, *end;
281 int nargs;
282
283 for (nargs = 0; iargs[nargs] != NULL; ++nargs)
284 ;
285
286 dprintf (stderr, "run_command_string starting: '%s'\n", cmdline);
287
288 while (ap < MAX_ARG_COUNT)
289 {
290 /* If the argument is quoted, this is the quote character, else NUL. */
291 int in_quote = 0;
292
293 /* Skip whitespace up to the next token. */
294 while (*cmdline && isspace (*cmdline))
295 cmdline ++;
296 if (*cmdline == 0)
297 break;
298
299 start = cmdline;
300 /* Check for quoted argument. */
301 in_quote = (*cmdline == '\'' || *cmdline == '"') ? *cmdline : 0;
302
303 /* Skip to end of token; either by whitespace or matching quote. */
304 dprintf (stderr, "in_quote %d\n", in_quote);
305 while (*cmdline
306 && (!isspace (*cmdline) || in_quote))
307 {
308 if (*cmdline == in_quote
309 && cmdline != start)
310 in_quote = 0;
311 dprintf (stderr, "[%c]%d ", *cmdline, in_quote);
312 cmdline ++;
313 }
314 dprintf (stderr, "\n");
315
316 /* Allocate space for this token and store it in args[]. */
317 end = cmdline;
318 dprintf (stderr, "start<%s> end<%s>\n", start, end);
319 args[ap] = (char *) xmalloc (end - start + 1);
320 memcpy (args[ap], start, end - start);
321 args[ap][end - start] = 0;
322
323 /* Strip off quotes, if found. */
324 dprintf (stderr, "args[%d] = <%s>\n", ap, args[ap]);
325 if (args[ap][0] == '\''
326 && args[ap][strlen (args[ap])-1] == '\'')
327 {
328 args[ap][strlen (args[ap])-1] = 0;
329 args[ap] ++;
330 }
331
332 else if (args[ap][0] == '"'
333 && args[ap][strlen (args[ap])-1] == '"')
334 {
335 args[ap][strlen (args[ap])-1] = 0;
336 args[ap] ++;
337 }
338
339 /* Replace positional parameters like $4. */
340 else if (args[ap][0] == '$'
341 && isdigit (args[ap][1])
342 && args[ap][2] == 0)
343 {
344 int a = args[ap][1] - '1';
345 if (0 <= a && a < nargs)
346 args[ap] = strdup (iargs[a]);
347 }
348
349 ap ++;
350
351 if (*cmdline == 0)
352 break;
353 }
354
355 /* Lastly, NULL terminate the array and run it. */
356 args[ap] = NULL;
357 run_command_array (args);
358}
359
360/* Run a script by reading lines and passing them to the above
361 function. */
362static void
363run_script (const char *filename, const char **args)
364{
365 char line[MAX_LINE_LENGTH + 1];
366 dprintf (stderr, "run_script starting: '%s'\n", filename);
367 FILE *f = fopen (filename, "r");
368 if (f == NULL)
369 {
370 fprintf (stderr, "sh: %s: %s\n", filename, strerror (errno));
371 exit (1);
372 }
373 while (fgets (line, sizeof (line), f) != NULL)
374 {
375 if (line[0] == '#')
376 {
377 dprintf (stderr, "comment: %s\n", line);
378 continue;
379 }
380 run_command_string (line, args);
381 }
382 fclose (f);
383}
384
385int
386main (int argc, const char **argv)
387{
388 int i;
389
390 if (strcmp (argv[1], "--debug") == 0)
391 {
392 debug_mode = 1;
393 --argc;
394 ++argv;
395 }
396
397 dprintf (stderr, "container-sh starting:\n");
398 for (i = 0; i < argc; i++)
399 dprintf (stderr, " argv[%d] is `%s'\n", i, argv[i]);
400
401 if (strcmp (argv[1], "-c") == 0)
402 run_command_string (argv[2], argv+3);
403 else
404 run_script (argv[1], argv+2);
405
406 dprintf (stderr, "normal exit 0\n");
407 return 0;
408}