1 /* Minimal /bin/sh for in-container use.
2 Copyright (C) 2018-2024 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
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.
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.
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/>. */
23 #include <sys/syscall.h>
25 #include <sys/types.h>
29 #include <sys/fcntl.h>
33 #include <sys/sysmacros.h>
39 #include <support/support.h>
40 #include <support/timespec.h>
42 /* Design considerations
44 General rule: optimize for developer time, not run time.
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.
55 #define MAX_ARG_COUNT 100
56 #define MAX_LINE_LENGTH 1000
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
62 /* Emulate the "/bin/true" command. Arguments are ignored. */
64 true_func (char **argv
)
69 /* Emulate the "/bin/echo" command. Options are ignored, arguments
70 are printed to stdout. */
72 echo_func (char **argv
)
76 for (i
= 0; argv
[i
]; i
++)
80 fputs (argv
[i
], stdout
);
87 /* Emulate the "/bin/cp" command. Options are ignored. Only copies
88 one source file to one destination file. Directory destinations
91 copy_func (char **argv
)
93 char *sname
= argv
[0];
94 char *dname
= argv
[1];
95 int sfd
= -1, dfd
= -1;
99 sfd
= open (sname
, O_RDONLY
);
102 fprintf (stderr
, "cp: unable to open %s for reading: %s\n",
103 sname
, strerror (errno
));
107 if (fstat (sfd
, &st
) < 0)
109 fprintf (stderr
, "cp: unable to fstat %s: %s\n",
110 sname
, strerror (errno
));
114 dfd
= open (dname
, O_WRONLY
| O_TRUNC
| O_CREAT
, 0600);
117 fprintf (stderr
, "cp: unable to open %s for writing: %s\n",
118 dname
, strerror (errno
));
122 if (support_copy_file_range (sfd
, 0, dfd
, 0, st
.st_size
, 0) != st
.st_size
)
124 fprintf (stderr
, "cp: cannot copy file %s to %s: %s\n",
125 sname
, dname
, strerror (errno
));
130 chmod (dname
, st
.st_mode
& 0777);
142 /* Emulate the 'exit' builtin. The exit value is optional. */
144 exit_func (char **argv
)
149 exit_val
= atoi (argv
[0]) & 0xff;
154 /* Emulate the "/bin/kill" command. Options are ignored. */
156 kill_func (char **argv
)
158 int signum
= SIGTERM
;
161 for (i
= 0; argv
[i
]; i
++)
164 if (strcmp (argv
[i
], "$$") == 0)
167 pid
= atoi (argv
[i
]);
173 /* Emulate the "/bin/sleep" command. No suffix support. Options are
176 sleep_func (char **argv
)
180 fprintf (stderr
, "sleep: missing operand\n");
184 double sec
= strtod (argv
[0], &endptr
);
185 if (endptr
== argv
[0] || errno
== ERANGE
|| sec
< 0)
187 fprintf (stderr
, "sleep: invalid time interval '%s'\n", argv
[0]);
190 struct timespec ts
= dtotimespec (sec
);
191 if (nanosleep (&ts
, NULL
) < 0)
193 fprintf (stderr
, "sleep: failed to nanosleep: %s\n", strerror (errno
));
199 /* This is a list of all the built-in commands we understand. */
202 int (*func
) (char **argv
);
203 } builtin_funcs
[] = {
204 { "true", true_func
},
205 { "echo", echo_func
},
207 { "exit", exit_func
},
208 { "kill", kill_func
},
209 { "sleep", sleep_func
},
213 /* Run one tokenized command. argv[0] is the command. argv is
216 run_command_array (char **argv
)
221 int (*builtin_func
) (char **args
);
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
]);
236 for (j
= i
= 0; argv
[i
]; i
++)
238 if (strcmp (argv
[i
], "<") == 0 && argv
[i
+ 1])
240 new_stdin
= open (argv
[i
+ 1], O_WRONLY
|O_CREAT
|O_TRUNC
, 0777);
244 if (strcmp (argv
[i
], ">") == 0 && argv
[i
+ 1])
246 new_stdout
= open (argv
[i
+ 1], O_WRONLY
|O_CREAT
|O_TRUNC
, 0777);
250 if (strcmp (argv
[i
], ">>") == 0 && argv
[i
+ 1])
252 new_stdout
= open (argv
[i
+ 1], O_WRONLY
|O_CREAT
|O_APPEND
, 0777);
256 if (strcmp (argv
[i
], "2>") == 0 && argv
[i
+ 1])
258 new_stderr
= open (argv
[i
+ 1], O_WRONLY
|O_CREAT
|O_TRUNC
, 0777);
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
;
271 dprintf (stderr
, "builtin %p argv0 `%s'\n", builtin_func
, argv
[0]);
276 fprintf (stderr
, "sh: fork failed\n");
289 dup2 (new_stdout
, 1);
294 dup2 (new_stderr
, 2);
298 if (builtin_func
!= NULL
)
299 exit (builtin_func (argv
+ 1));
301 execvp (argv
[0], argv
);
303 fprintf (stderr
, "sh: execing %s failed: %s",
304 argv
[0], strerror (errno
));
308 waitpid (pid
, &status
, 0);
310 dprintf (stderr
, "exiting run_command_array\n");
312 if (WIFEXITED (status
))
314 int rv
= WEXITSTATUS (status
);
318 else if (WIFSIGNALED (status
))
320 int sig
= WTERMSIG (status
);
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". */
332 run_command_string (const char *cmdline
, const char **iargs
)
334 char *args
[MAX_ARG_COUNT
+1];
336 const char *start
, *end
;
339 for (nargs
= 0; iargs
[nargs
] != NULL
; ++nargs
)
342 dprintf (stderr
, "run_command_string starting: '%s'\n", cmdline
);
344 while (ap
< MAX_ARG_COUNT
)
346 /* If the argument is quoted, this is the quote character, else NUL. */
349 /* Skip whitespace up to the next token. */
350 while (*cmdline
&& isspace (*cmdline
))
356 /* Check for quoted argument. */
357 in_quote
= (*cmdline
== '\'' || *cmdline
== '"') ? *cmdline
: 0;
359 /* Skip to end of token; either by whitespace or matching quote. */
360 dprintf (stderr
, "in_quote %d\n", in_quote
);
362 && (!isspace (*cmdline
) || in_quote
))
364 if (*cmdline
== in_quote
367 dprintf (stderr
, "[%c]%d ", *cmdline
, in_quote
);
370 dprintf (stderr
, "\n");
372 /* Allocate space for this token and store it in args[]. */
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;
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] == '\'')
384 args
[ap
][strlen (args
[ap
])-1] = 0;
388 else if (args
[ap
][0] == '"'
389 && args
[ap
][strlen (args
[ap
])-1] == '"')
391 args
[ap
][strlen (args
[ap
])-1] = 0;
395 /* Replace positional parameters like $4. */
396 else if (args
[ap
][0] == '$'
397 && isdigit (args
[ap
][1])
400 int a
= args
[ap
][1] - '1';
401 if (0 <= a
&& a
< nargs
)
402 args
[ap
] = strdup (iargs
[a
]);
411 /* Lastly, NULL terminate the array and run it. */
413 run_command_array (args
);
416 /* Run a script by reading lines and passing them to the above
419 run_script (const char *filename
, const char **args
)
421 char line
[MAX_LINE_LENGTH
+ 1];
422 dprintf (stderr
, "run_script starting: '%s'\n", filename
);
423 FILE *f
= fopen (filename
, "r");
426 fprintf (stderr
, "sh: %s: %s\n", filename
, strerror (errno
));
429 while (fgets (line
, sizeof (line
), f
) != NULL
)
433 dprintf (stderr
, "comment: %s\n", line
);
436 run_command_string (line
, args
);
442 main (int argc
, const char **argv
)
446 if (strcmp (argv
[1], "--debug") == 0)
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
]);
457 if (strcmp (argv
[1], "-c") == 0)
459 if (strcmp (argv
[2], "--") == 0)
460 run_command_string (argv
[3], argv
+4);
462 run_command_string (argv
[2], argv
+3);
465 run_script (argv
[1], argv
+2);
467 dprintf (stderr
, "normal exit 0\n");