shell-container.c 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. /* Minimal /bin/sh for in-container use.
  2. Copyright (C) 2018-2019 Free Software Foundation, Inc.
  3. This file is part of the GNU C Library.
  4. The GNU C Library is free software; you can redistribute it and/or
  5. modify it under the terms of the GNU Lesser General Public
  6. License as published by the Free Software Foundation; either
  7. version 2.1 of the License, or (at your option) any later version.
  8. The GNU C Library is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. Lesser General Public License for more details.
  12. You should have received a copy of the GNU Lesser General Public
  13. License along with the GNU C Library; if not, see
  14. <http://www.gnu.org/licenses/>. */
  15. #define _FILE_OFFSET_BITS 64
  16. #include <stdio.h>
  17. #include <stdlib.h>
  18. #include <string.h>
  19. #include <sched.h>
  20. #include <sys/syscall.h>
  21. #include <unistd.h>
  22. #include <sys/types.h>
  23. #include <dirent.h>
  24. #include <string.h>
  25. #include <sys/stat.h>
  26. #include <sys/fcntl.h>
  27. #include <sys/file.h>
  28. #include <sys/wait.h>
  29. #include <stdarg.h>
  30. #include <sys/sysmacros.h>
  31. #include <ctype.h>
  32. #include <utime.h>
  33. #include <errno.h>
  34. #include <error.h>
  35. #include <support/support.h>
  36. /* Design considerations
  37. General rule: optimize for developer time, not run time.
  38. Specifically:
  39. * Don't worry about slow algorithms
  40. * Don't worry about free'ing memory
  41. * Don't implement anything the testsuite doesn't need.
  42. * Line and argument counts are limited, see below.
  43. */
  44. #define MAX_ARG_COUNT 100
  45. #define MAX_LINE_LENGTH 1000
  46. /* Debugging is enabled via --debug, which must be the first argument. */
  47. static int debug_mode = 0;
  48. #define dprintf if (debug_mode) fprintf
  49. /* Emulate the "/bin/true" command. Arguments are ignored. */
  50. static int
  51. true_func (char **argv)
  52. {
  53. return 0;
  54. }
  55. /* Emulate the "/bin/echo" command. Options are ignored, arguments
  56. are printed to stdout. */
  57. static int
  58. echo_func (char **argv)
  59. {
  60. int i;
  61. for (i = 0; argv[i]; i++)
  62. {
  63. if (i > 0)
  64. putchar (' ');
  65. fputs (argv[i], stdout);
  66. }
  67. putchar ('\n');
  68. return 0;
  69. }
  70. /* Emulate the "/bin/cp" command. Options are ignored. Only copies
  71. one source file to one destination file. Directory destinations
  72. are not supported. */
  73. static int
  74. copy_func (char **argv)
  75. {
  76. char *sname = argv[0];
  77. char *dname = argv[1];
  78. int sfd, dfd;
  79. struct stat st;
  80. sfd = open (sname, O_RDONLY);
  81. if (sfd < 0)
  82. {
  83. fprintf (stderr, "cp: unable to open %s for reading: %s\n",
  84. sname, strerror (errno));
  85. return 1;
  86. }
  87. if (fstat (sfd, &st) < 0)
  88. {
  89. fprintf (stderr, "cp: unable to fstat %s: %s\n",
  90. sname, strerror (errno));
  91. return 1;
  92. }
  93. dfd = open (dname, O_WRONLY | O_TRUNC | O_CREAT, 0600);
  94. if (dfd < 0)
  95. {
  96. fprintf (stderr, "cp: unable to open %s for writing: %s\n",
  97. dname, strerror (errno));
  98. return 1;
  99. }
  100. if (support_copy_file_range (sfd, 0, dfd, 0, st.st_size, 0) != st.st_size)
  101. {
  102. fprintf (stderr, "cp: cannot copy file %s to %s: %s\n",
  103. sname, dname, strerror (errno));
  104. return 1;
  105. }
  106. close (sfd);
  107. close (dfd);
  108. chmod (dname, st.st_mode & 0777);
  109. return 0;
  110. }
  111. /* This is a list of all the built-in commands we understand. */
  112. static struct {
  113. const char *name;
  114. int (*func) (char **argv);
  115. } builtin_funcs[] = {
  116. { "true", true_func },
  117. { "echo", echo_func },
  118. { "cp", copy_func },
  119. { NULL, NULL }
  120. };
  121. /* Run one tokenized command. argv[0] is the command. argv is
  122. NULL-terminated. */
  123. static void
  124. run_command_array (char **argv)
  125. {
  126. int i, j;
  127. pid_t pid;
  128. int status;
  129. int (*builtin_func) (char **args);
  130. if (argv[0] == NULL)
  131. return;
  132. builtin_func = NULL;
  133. int new_stdin = 0;
  134. int new_stdout = 1;
  135. int new_stderr = 2;
  136. dprintf (stderr, "run_command_array starting\n");
  137. for (i = 0; argv[i]; i++)
  138. dprintf (stderr, " argv [%d] `%s'\n", i, argv[i]);
  139. for (j = i = 0; argv[i]; i++)
  140. {
  141. if (strcmp (argv[i], "<") == 0 && argv[i + 1])
  142. {
  143. new_stdin = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777);
  144. ++i;
  145. continue;
  146. }
  147. if (strcmp (argv[i], ">") == 0 && argv[i + 1])
  148. {
  149. new_stdout = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777);
  150. ++i;
  151. continue;
  152. }
  153. if (strcmp (argv[i], ">>") == 0 && argv[i + 1])
  154. {
  155. new_stdout = open (argv[i + 1], O_WRONLY|O_CREAT|O_APPEND, 0777);
  156. ++i;
  157. continue;
  158. }
  159. if (strcmp (argv[i], "2>") == 0 && argv[i + 1])
  160. {
  161. new_stderr = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777);
  162. ++i;
  163. continue;
  164. }
  165. argv[j++] = argv[i];
  166. }
  167. argv[j] = NULL;
  168. for (i = 0; builtin_funcs[i].name != NULL; i++)
  169. if (strcmp (argv[0], builtin_funcs[i].name) == 0)
  170. builtin_func = builtin_funcs[i].func;
  171. dprintf (stderr, "builtin %p argv0 `%s'\n", builtin_func, argv[0]);
  172. pid = fork ();
  173. if (pid < 0)
  174. {
  175. fprintf (stderr, "sh: fork failed\n");
  176. exit (1);
  177. }
  178. if (pid == 0)
  179. {
  180. if (new_stdin != 0)
  181. {
  182. dup2 (new_stdin, 0);
  183. close (new_stdin);
  184. }
  185. if (new_stdout != 1)
  186. {
  187. dup2 (new_stdout, 1);
  188. close (new_stdout);
  189. }
  190. if (new_stderr != 2)
  191. {
  192. dup2 (new_stderr, 2);
  193. close (new_stdout);
  194. }
  195. if (builtin_func != NULL)
  196. exit (builtin_func (argv + 1));
  197. execvp (argv[0], argv);
  198. fprintf (stderr, "sh: execing %s failed: %s",
  199. argv[0], strerror (errno));
  200. exit (1);
  201. }
  202. waitpid (pid, &status, 0);
  203. dprintf (stderr, "exiting run_command_array\n");
  204. if (WIFEXITED (status))
  205. {
  206. int rv = WEXITSTATUS (status);
  207. if (rv)
  208. exit (rv);
  209. }
  210. else
  211. exit (1);
  212. }
  213. /* Run one command-as-a-string, by tokenizing it. Limited to
  214. MAX_ARG_COUNT arguments. Simple substitution is done of $1 to $9
  215. (as whole separate tokens) from iargs[]. Quoted strings work if
  216. the quotes wrap whole tokens; i.e. "foo bar" but not foo" bar". */
  217. static void
  218. run_command_string (const char *cmdline, const char **iargs)
  219. {
  220. char *args[MAX_ARG_COUNT+1];
  221. int ap = 0;
  222. const char *start, *end;
  223. int nargs;
  224. for (nargs = 0; iargs[nargs] != NULL; ++nargs)
  225. ;
  226. dprintf (stderr, "run_command_string starting: '%s'\n", cmdline);
  227. while (ap < MAX_ARG_COUNT)
  228. {
  229. /* If the argument is quoted, this is the quote character, else NUL. */
  230. int in_quote = 0;
  231. /* Skip whitespace up to the next token. */
  232. while (*cmdline && isspace (*cmdline))
  233. cmdline ++;
  234. if (*cmdline == 0)
  235. break;
  236. start = cmdline;
  237. /* Check for quoted argument. */
  238. in_quote = (*cmdline == '\'' || *cmdline == '"') ? *cmdline : 0;
  239. /* Skip to end of token; either by whitespace or matching quote. */
  240. dprintf (stderr, "in_quote %d\n", in_quote);
  241. while (*cmdline
  242. && (!isspace (*cmdline) || in_quote))
  243. {
  244. if (*cmdline == in_quote
  245. && cmdline != start)
  246. in_quote = 0;
  247. dprintf (stderr, "[%c]%d ", *cmdline, in_quote);
  248. cmdline ++;
  249. }
  250. dprintf (stderr, "\n");
  251. /* Allocate space for this token and store it in args[]. */
  252. end = cmdline;
  253. dprintf (stderr, "start<%s> end<%s>\n", start, end);
  254. args[ap] = (char *) xmalloc (end - start + 1);
  255. memcpy (args[ap], start, end - start);
  256. args[ap][end - start] = 0;
  257. /* Strip off quotes, if found. */
  258. dprintf (stderr, "args[%d] = <%s>\n", ap, args[ap]);
  259. if (args[ap][0] == '\''
  260. && args[ap][strlen (args[ap])-1] == '\'')
  261. {
  262. args[ap][strlen (args[ap])-1] = 0;
  263. args[ap] ++;
  264. }
  265. else if (args[ap][0] == '"'
  266. && args[ap][strlen (args[ap])-1] == '"')
  267. {
  268. args[ap][strlen (args[ap])-1] = 0;
  269. args[ap] ++;
  270. }
  271. /* Replace positional parameters like $4. */
  272. else if (args[ap][0] == '$'
  273. && isdigit (args[ap][1])
  274. && args[ap][2] == 0)
  275. {
  276. int a = args[ap][1] - '1';
  277. if (0 <= a && a < nargs)
  278. args[ap] = strdup (iargs[a]);
  279. }
  280. ap ++;
  281. if (*cmdline == 0)
  282. break;
  283. }
  284. /* Lastly, NULL terminate the array and run it. */
  285. args[ap] = NULL;
  286. run_command_array (args);
  287. }
  288. /* Run a script by reading lines and passing them to the above
  289. function. */
  290. static void
  291. run_script (const char *filename, const char **args)
  292. {
  293. char line[MAX_LINE_LENGTH + 1];
  294. dprintf (stderr, "run_script starting: '%s'\n", filename);
  295. FILE *f = fopen (filename, "r");
  296. if (f == NULL)
  297. {
  298. fprintf (stderr, "sh: %s: %s\n", filename, strerror (errno));
  299. exit (1);
  300. }
  301. while (fgets (line, sizeof (line), f) != NULL)
  302. {
  303. if (line[0] == '#')
  304. {
  305. dprintf (stderr, "comment: %s\n", line);
  306. continue;
  307. }
  308. run_command_string (line, args);
  309. }
  310. fclose (f);
  311. }
  312. int
  313. main (int argc, const char **argv)
  314. {
  315. int i;
  316. if (strcmp (argv[1], "--debug") == 0)
  317. {
  318. debug_mode = 1;
  319. --argc;
  320. ++argv;
  321. }
  322. dprintf (stderr, "container-sh starting:\n");
  323. for (i = 0; i < argc; i++)
  324. dprintf (stderr, " argv[%d] is `%s'\n", i, argv[i]);
  325. if (strcmp (argv[1], "-c") == 0)
  326. run_command_string (argv[2], argv+3);
  327. else
  328. run_script (argv[1], argv+2);
  329. dprintf (stderr, "normal exit 0\n");
  330. return 0;
  331. }