(root)/
glibc-2.38/
support/
shell-container.c
       1  /* Minimal /bin/sh for in-container use.
       2     Copyright (C) 2018-2023 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  #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>
      40  #include <support/timespec.h>
      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.  */
      59  static int debug_mode = 0;
      60  #define dprintf if (debug_mode) fprintf
      61  
      62  /* Emulate the "/bin/true" command.  Arguments are ignored.  */
      63  static int
      64  true_func (char **argv)
      65  {
      66    return 0;
      67  }
      68  
      69  /* Emulate the "/bin/echo" command.  Options are ignored, arguments
      70     are printed to stdout.  */
      71  static int
      72  echo_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.  */
      90  static int
      91  copy_func (char **argv)
      92  {
      93    char *sname = argv[0];
      94    char *dname = argv[1];
      95    int sfd = -1, dfd = -1;
      96    struct stat st;
      97    int ret = 1;
      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        goto out;
     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        goto out;
     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        goto out;
     127      }
     128  
     129    ret = 0;
     130    chmod (dname, st.st_mode & 0777);
     131  
     132  out:
     133    if (sfd >= 0)
     134      close (sfd);
     135    if (dfd >= 0)
     136      close (dfd);
     137  
     138    return ret;
     139  
     140  }
     141  
     142  /* Emulate the 'exit' builtin.  The exit value is optional.  */
     143  static int
     144  exit_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  
     154  /* Emulate the "/bin/kill" command.  Options are ignored.  */
     155  static int
     156  kill_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  
     173  /* Emulate the "/bin/sleep" command.  No suffix support.  Options are
     174     ignored.  */
     175  static int
     176  sleep_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  
     199  /* This is a list of all the built-in commands we understand.  */
     200  static 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 },
     207    { "exit", exit_func },
     208    { "kill", kill_func },
     209    { "sleep", sleep_func },
     210    { NULL, NULL }
     211  };
     212  
     213  /* Run one tokenized command.  argv[0] is the command.  argv is
     214     NULL-terminated.  */
     215  static void
     216  run_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);
     295  	  close (new_stderr);
     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));
     305        exit (127);
     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      }
     318    else if (WIFSIGNALED (status))
     319      {
     320        int sig = WTERMSIG (status);
     321        raise (sig);
     322      }
     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".  */
     331  static void
     332  run_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.  */
     418  static void
     419  run_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  
     441  int
     442  main (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)
     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      }
     464    else
     465      run_script (argv[1], argv+2);
     466  
     467    dprintf (stderr, "normal exit 0\n");
     468    return 0;
     469  }