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