123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395 |
- /* Minimal /bin/sh for in-container use.
- Copyright (C) 2018-2019 Free Software Foundation, Inc.
- This file is part of the GNU C Library.
- The GNU C Library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
- The GNU C Library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
- You should have received a copy of the GNU Lesser General Public
- License along with the GNU C Library; if not, see
- <http://www.gnu.org/licenses/>. */
- #define _FILE_OFFSET_BITS 64
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <sched.h>
- #include <sys/syscall.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <dirent.h>
- #include <string.h>
- #include <sys/stat.h>
- #include <sys/fcntl.h>
- #include <sys/file.h>
- #include <sys/wait.h>
- #include <stdarg.h>
- #include <sys/sysmacros.h>
- #include <ctype.h>
- #include <utime.h>
- #include <errno.h>
- #include <error.h>
- #include <support/support.h>
- /* Design considerations
- General rule: optimize for developer time, not run time.
- Specifically:
- * Don't worry about slow algorithms
- * Don't worry about free'ing memory
- * Don't implement anything the testsuite doesn't need.
- * Line and argument counts are limited, see below.
- */
- #define MAX_ARG_COUNT 100
- #define MAX_LINE_LENGTH 1000
- /* Debugging is enabled via --debug, which must be the first argument. */
- static int debug_mode = 0;
- #define dprintf if (debug_mode) fprintf
- /* Emulate the "/bin/true" command. Arguments are ignored. */
- static int
- true_func (char **argv)
- {
- return 0;
- }
- /* Emulate the "/bin/echo" command. Options are ignored, arguments
- are printed to stdout. */
- static int
- echo_func (char **argv)
- {
- int i;
- for (i = 0; argv[i]; i++)
- {
- if (i > 0)
- putchar (' ');
- fputs (argv[i], stdout);
- }
- putchar ('\n');
- return 0;
- }
- /* Emulate the "/bin/cp" command. Options are ignored. Only copies
- one source file to one destination file. Directory destinations
- are not supported. */
- static int
- copy_func (char **argv)
- {
- char *sname = argv[0];
- char *dname = argv[1];
- int sfd, dfd;
- struct stat st;
- sfd = open (sname, O_RDONLY);
- if (sfd < 0)
- {
- fprintf (stderr, "cp: unable to open %s for reading: %s\n",
- sname, strerror (errno));
- return 1;
- }
- if (fstat (sfd, &st) < 0)
- {
- fprintf (stderr, "cp: unable to fstat %s: %s\n",
- sname, strerror (errno));
- return 1;
- }
- dfd = open (dname, O_WRONLY | O_TRUNC | O_CREAT, 0600);
- if (dfd < 0)
- {
- fprintf (stderr, "cp: unable to open %s for writing: %s\n",
- dname, strerror (errno));
- return 1;
- }
- if (support_copy_file_range (sfd, 0, dfd, 0, st.st_size, 0) != st.st_size)
- {
- fprintf (stderr, "cp: cannot copy file %s to %s: %s\n",
- sname, dname, strerror (errno));
- return 1;
- }
- close (sfd);
- close (dfd);
- chmod (dname, st.st_mode & 0777);
- return 0;
- }
- /* This is a list of all the built-in commands we understand. */
- static struct {
- const char *name;
- int (*func) (char **argv);
- } builtin_funcs[] = {
- { "true", true_func },
- { "echo", echo_func },
- { "cp", copy_func },
- { NULL, NULL }
- };
- /* Run one tokenized command. argv[0] is the command. argv is
- NULL-terminated. */
- static void
- run_command_array (char **argv)
- {
- int i, j;
- pid_t pid;
- int status;
- int (*builtin_func) (char **args);
- if (argv[0] == NULL)
- return;
- builtin_func = NULL;
- int new_stdin = 0;
- int new_stdout = 1;
- int new_stderr = 2;
- dprintf (stderr, "run_command_array starting\n");
- for (i = 0; argv[i]; i++)
- dprintf (stderr, " argv [%d] `%s'\n", i, argv[i]);
- for (j = i = 0; argv[i]; i++)
- {
- if (strcmp (argv[i], "<") == 0 && argv[i + 1])
- {
- new_stdin = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777);
- ++i;
- continue;
- }
- if (strcmp (argv[i], ">") == 0 && argv[i + 1])
- {
- new_stdout = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777);
- ++i;
- continue;
- }
- if (strcmp (argv[i], ">>") == 0 && argv[i + 1])
- {
- new_stdout = open (argv[i + 1], O_WRONLY|O_CREAT|O_APPEND, 0777);
- ++i;
- continue;
- }
- if (strcmp (argv[i], "2>") == 0 && argv[i + 1])
- {
- new_stderr = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777);
- ++i;
- continue;
- }
- argv[j++] = argv[i];
- }
- argv[j] = NULL;
- for (i = 0; builtin_funcs[i].name != NULL; i++)
- if (strcmp (argv[0], builtin_funcs[i].name) == 0)
- builtin_func = builtin_funcs[i].func;
- dprintf (stderr, "builtin %p argv0 `%s'\n", builtin_func, argv[0]);
- pid = fork ();
- if (pid < 0)
- {
- fprintf (stderr, "sh: fork failed\n");
- exit (1);
- }
- if (pid == 0)
- {
- if (new_stdin != 0)
- {
- dup2 (new_stdin, 0);
- close (new_stdin);
- }
- if (new_stdout != 1)
- {
- dup2 (new_stdout, 1);
- close (new_stdout);
- }
- if (new_stderr != 2)
- {
- dup2 (new_stderr, 2);
- close (new_stdout);
- }
- if (builtin_func != NULL)
- exit (builtin_func (argv + 1));
- execvp (argv[0], argv);
- fprintf (stderr, "sh: execing %s failed: %s",
- argv[0], strerror (errno));
- exit (1);
- }
- waitpid (pid, &status, 0);
- dprintf (stderr, "exiting run_command_array\n");
- if (WIFEXITED (status))
- {
- int rv = WEXITSTATUS (status);
- if (rv)
- exit (rv);
- }
- else
- exit (1);
- }
- /* Run one command-as-a-string, by tokenizing it. Limited to
- MAX_ARG_COUNT arguments. Simple substitution is done of $1 to $9
- (as whole separate tokens) from iargs[]. Quoted strings work if
- the quotes wrap whole tokens; i.e. "foo bar" but not foo" bar". */
- static void
- run_command_string (const char *cmdline, const char **iargs)
- {
- char *args[MAX_ARG_COUNT+1];
- int ap = 0;
- const char *start, *end;
- int nargs;
- for (nargs = 0; iargs[nargs] != NULL; ++nargs)
- ;
- dprintf (stderr, "run_command_string starting: '%s'\n", cmdline);
- while (ap < MAX_ARG_COUNT)
- {
- /* If the argument is quoted, this is the quote character, else NUL. */
- int in_quote = 0;
- /* Skip whitespace up to the next token. */
- while (*cmdline && isspace (*cmdline))
- cmdline ++;
- if (*cmdline == 0)
- break;
- start = cmdline;
- /* Check for quoted argument. */
- in_quote = (*cmdline == '\'' || *cmdline == '"') ? *cmdline : 0;
- /* Skip to end of token; either by whitespace or matching quote. */
- dprintf (stderr, "in_quote %d\n", in_quote);
- while (*cmdline
- && (!isspace (*cmdline) || in_quote))
- {
- if (*cmdline == in_quote
- && cmdline != start)
- in_quote = 0;
- dprintf (stderr, "[%c]%d ", *cmdline, in_quote);
- cmdline ++;
- }
- dprintf (stderr, "\n");
- /* Allocate space for this token and store it in args[]. */
- end = cmdline;
- dprintf (stderr, "start<%s> end<%s>\n", start, end);
- args[ap] = (char *) xmalloc (end - start + 1);
- memcpy (args[ap], start, end - start);
- args[ap][end - start] = 0;
- /* Strip off quotes, if found. */
- dprintf (stderr, "args[%d] = <%s>\n", ap, args[ap]);
- if (args[ap][0] == '\''
- && args[ap][strlen (args[ap])-1] == '\'')
- {
- args[ap][strlen (args[ap])-1] = 0;
- args[ap] ++;
- }
- else if (args[ap][0] == '"'
- && args[ap][strlen (args[ap])-1] == '"')
- {
- args[ap][strlen (args[ap])-1] = 0;
- args[ap] ++;
- }
- /* Replace positional parameters like $4. */
- else if (args[ap][0] == '$'
- && isdigit (args[ap][1])
- && args[ap][2] == 0)
- {
- int a = args[ap][1] - '1';
- if (0 <= a && a < nargs)
- args[ap] = strdup (iargs[a]);
- }
- ap ++;
- if (*cmdline == 0)
- break;
- }
- /* Lastly, NULL terminate the array and run it. */
- args[ap] = NULL;
- run_command_array (args);
- }
- /* Run a script by reading lines and passing them to the above
- function. */
- static void
- run_script (const char *filename, const char **args)
- {
- char line[MAX_LINE_LENGTH + 1];
- dprintf (stderr, "run_script starting: '%s'\n", filename);
- FILE *f = fopen (filename, "r");
- if (f == NULL)
- {
- fprintf (stderr, "sh: %s: %s\n", filename, strerror (errno));
- exit (1);
- }
- while (fgets (line, sizeof (line), f) != NULL)
- {
- if (line[0] == '#')
- {
- dprintf (stderr, "comment: %s\n", line);
- continue;
- }
- run_command_string (line, args);
- }
- fclose (f);
- }
- int
- main (int argc, const char **argv)
- {
- int i;
- if (strcmp (argv[1], "--debug") == 0)
- {
- debug_mode = 1;
- --argc;
- ++argv;
- }
- dprintf (stderr, "container-sh starting:\n");
- for (i = 0; i < argc; i++)
- dprintf (stderr, " argv[%d] is `%s'\n", i, argv[i]);
- if (strcmp (argv[1], "-c") == 0)
- run_command_string (argv[2], argv+3);
- else
- run_script (argv[1], argv+2);
- dprintf (stderr, "normal exit 0\n");
- return 0;
- }
|