#endif
#include "math.h"
#include "match.h"
+#if ENABLE_HUSH_RANDOM_SUPPORT
+# include "random.h"
+#else
+# define CLEAR_RANDOM_T(rnd) ((void)0)
+#endif
#ifndef PIPE_BUF
# define PIPE_BUF 4096 /* amount of buffering in a pipe */
#endif
char *name;
struct command *parent_cmd;
struct pipe *body;
-#if !BB_MMU
+# if !BB_MMU
char *body_as_string;
-#endif
+# endif
};
#endif
line_input_t *line_input_state;
#endif
pid_t root_pid;
+ pid_t root_ppid;
pid_t last_bg_pid;
+#if ENABLE_HUSH_RANDOM_SUPPORT
+ random_t random_gen;
+#endif
#if ENABLE_HUSH_JOB
int run_list_level;
int last_jobid;
unsigned long memleak_value;
int debug_indent;
#endif
- char user_input_buf[ENABLE_FEATURE_EDITING ? BUFSIZ : 2];
+ char user_input_buf[ENABLE_FEATURE_EDITING ? CONFIG_FEATURE_EDITING_MAX_LEN : 2];
};
#define G (*ptr_to_globals)
/* Not #defining name to G.name - this quickly gets unwieldy
#if HUSH_DEBUG
static int builtin_memleak(char **argv) FAST_FUNC;
#endif
+#if ENABLE_PRINTF
+static int builtin_printf(char **argv) FAST_FUNC;
+#endif
static int builtin_pwd(char **argv) FAST_FUNC;
static int builtin_read(char **argv) FAST_FUNC;
static int builtin_set(char **argv) FAST_FUNC;
static const struct built_in_command bltins2[] = {
BLTIN("[" , builtin_test , NULL),
BLTIN("echo" , builtin_echo , NULL),
+#if ENABLE_PRINTF
+ BLTIN("printf" , builtin_printf , NULL),
+#endif
BLTIN("pwd" , builtin_pwd , NULL),
BLTIN("test" , builtin_test , NULL),
};
/* Replace each \x with x in place, return ptr past NUL. */
static char *unbackslash(char *src)
{
- char *dst = src;
+ char *dst = src = strchrnul(src, '\\');
while (1) {
if (*src == '\\')
src++;
* Note: as a result, we do not use signal handlers much. The only uses
* are to count SIGCHLDs
* and to restore tty pgrp on signal-induced exit.
+ *
+ * Note 2 (compat):
+ * Standard says "When a subshell is entered, traps that are not being ignored
+ * are set to the default actions". bash interprets it so that traps which
+ * are set to "" (ignore) are NOT reset to defaults. We do the same.
*/
enum {
SPECIAL_INTERACTIVE_SIGS = 0
struct variable **pp = get_ptr_to_local_var(name);
if (pp)
return strchr((*pp)->varstr, '=') + 1;
+ if (strcmp(name, "PPID") == 0)
+ return utoa(G.root_ppid);
+ // bash compat: UID? EUID?
+#if ENABLE_HUSH_RANDOM_SUPPORT
+ if (strcmp(name, "RANDOM") == 0) {
+ return utoa(next_random(&G.random_gen));
+ }
+#endif
return NULL;
}
G.flag_SIGINT = 0;
/* buglet: SIGINT will not make new prompt to appear _at once_,
* only after <Enter>. (^C will work) */
- r = read_line_input(prompt_str, G.user_input_buf, BUFSIZ-1, G.line_input_state);
+ r = read_line_input(prompt_str, G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1, G.line_input_state);
/* catch *SIGINT* etc (^C is handled by read_line_input) */
check_and_run_traps(0);
} while (r == 0 || G.flag_SIGINT); /* repeat if ^C or SIGINT */
* to be filled). This routine is extremely tricky: has to deal with
* variables/parameters with whitespace, $* and $@, and constructs like
* 'echo -$*-'. If you play here, you must run testsuite afterwards! */
-static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
+static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
{
/* or_mask is either 0 (normal case) or 0x80 -
* expansion of right-hand side of assignment == 1-element expand.
ored_ch = 0;
- debug_printf_expand("expand_vars_to_list: arg '%s'\n", arg);
+ debug_printf_expand("expand_vars_to_list: arg:'%s' or_mask:%x\n", arg, or_mask);
debug_print_list("expand_vars_to_list", output, n);
n = o_save_ptr(output, n);
debug_print_list("expand_vars_to_list[0]", output, n);
{
/* This function is always called in a child shell
* after fork (not vfork, NOMMU doesn't use this function).
- * Child shells are not interactive.
- * SIGTTIN/SIGTTOU/SIGTSTP should not have special handling.
- * Testcase: (while :; do :; done) + ^Z should background.
- * Same goes for SIGTERM, SIGHUP, SIGINT.
*/
unsigned sig;
unsigned mask;
+ /* Child shells are not interactive.
+ * SIGTTIN/SIGTTOU/SIGTSTP should not have special handling.
+ * Testcase: (while :; do :; done) + ^Z should background.
+ * Same goes for SIGTERM, SIGHUP, SIGINT.
+ */
if (!G.traps && !(G.non_DFL_mask & SPECIAL_INTERACTIVE_SIGS))
- return;
+ return; /* already no traps and no SPECIAL_INTERACTIVE_SIGS */
- /* Stupid. It can be done with *single* &= op, but we can't use
- * the fact that G.blocked_set is implemented as a bitmask... */
+ /* Switching off SPECIAL_INTERACTIVE_SIGS.
+ * Stupid. It can be done with *single* &= op, but we can't use
+ * the fact that G.blocked_set is implemented as a bitmask
+ * in libc... */
mask = (SPECIAL_INTERACTIVE_SIGS >> 1);
sig = 1;
while (1) {
- if (mask & 1)
- sigdelset(&G.blocked_set, sig);
+ if (mask & 1) {
+ /* Careful. Only if no trap or trap is not "" */
+ if (!G.traps || !G.traps[sig] || G.traps[sig][0])
+ sigdelset(&G.blocked_set, sig);
+ }
mask >>= 1;
if (!mask)
break;
sig++;
}
-
+ /* Our homegrown sig mask is saner to work with :) */
G.non_DFL_mask &= ~SPECIAL_INTERACTIVE_SIGS;
+
+ /* Resetting all traps to default except empty ones */
mask = G.non_DFL_mask;
if (G.traps) for (sig = 0; sig < NSIG; sig++, mask >>= 1) {
- if (!G.traps[sig])
+ if (!G.traps[sig] || !G.traps[sig][0])
continue;
free(G.traps[sig]);
G.traps[sig] = NULL;
/* There is no signal for 0 (EXIT) */
if (sig == 0)
continue;
- /* There was a trap handler, we are removing it.
+ /* There was a trap handler, we just removed it.
* But if sig still has non-DFL handling,
- * we should not unblock it. */
+ * we should not unblock the sig. */
if (mask & 1)
continue;
sigdelset(&G.blocked_set, sig);
char param_buf[sizeof("-$%x:%x:%x:%x:%x") + sizeof(unsigned) * 2];
char *heredoc_argv[4];
struct variable *cur;
-#if ENABLE_HUSH_FUNCTIONS
+# if ENABLE_HUSH_FUNCTIONS
struct function *funcp;
-#endif
+# endif
char **argv, **pp;
unsigned cnt;
if (pp) while (*pp++)
cnt++;
- sprintf(param_buf, "-$%x:%x:%x:%x" IF_HUSH_LOOPS(":%x")
+ sprintf(param_buf, "-$%x:%x:%x:%x:%x" IF_HUSH_LOOPS(":%x")
, (unsigned) G.root_pid
+ , (unsigned) G.root_ppid
, (unsigned) G.last_bg_pid
, (unsigned) G.last_exitcode
, cnt
if (!cur->flg_export || cur->flg_read_only)
cnt += 2;
}
-#if ENABLE_HUSH_FUNCTIONS
+# if ENABLE_HUSH_FUNCTIONS
for (funcp = G.top_func; funcp; funcp = funcp->next)
cnt += 3;
-#endif
+# endif
pp = g_argv;
while (*pp++)
cnt++;
*pp++ = cur->varstr;
}
}
-#if ENABLE_HUSH_FUNCTIONS
+# if ENABLE_HUSH_FUNCTIONS
for (funcp = G.top_func; funcp; funcp = funcp->next) {
*pp++ = (char *) "-F";
*pp++ = funcp->name;
*pp++ = funcp->body_as_string;
}
-#endif
+# endif
/* We can pass activated traps here. Say, -Tnn:trap_string
*
* However, POSIX says that subshells reset signals with traps
/* "we are in function, ok to use return" */
sv_flg = G.flag_return_in_progress;
G.flag_return_in_progress = -1;
-#if ENABLE_HUSH_LOCAL
+# if ENABLE_HUSH_LOCAL
G.func_nest_level++;
-#endif
+# endif
/* On MMU, funcp->body is always non-NULL */
# if !BB_MMU
rc = run_list(funcp->body);
}
-#if ENABLE_HUSH_LOCAL
+# if ENABLE_HUSH_LOCAL
{
struct variable *var;
struct variable **var_pp;
}
G.func_nest_level--;
}
-#endif
+# endif
G.flag_return_in_progress = sv_flg;
restore_G_args(&sv, argv);
#endif /* ENABLE_HUSH_FUNCTIONS */
-# if BB_MMU
+#if BB_MMU
#define exec_builtin(to_free, x, argv) \
exec_builtin(x, argv)
-# else
+#else
#define exec_builtin(to_free, x, argv) \
exec_builtin(to_free, argv)
-# endif
+#endif
static void exec_builtin(char ***to_free,
const struct built_in_command *x,
char **argv) NORETURN;
const struct built_in_command *x,
char **argv)
{
-# if BB_MMU
+#if BB_MMU
int rcode = x->function(argv);
fflush(NULL);
_exit(rcode);
-# else
+#else
/* On NOMMU, we must never block!
* Example: { sleep 99 | read line; } & echo Ok
*/
G.global_argv[0],
G.global_argv + 1,
argv);
-# endif
+#endif
}
+static void execvp_or_die(char **argv) NORETURN;
+static void execvp_or_die(char **argv)
+{
+ debug_printf_exec("execing '%s'\n", argv[0]);
+ sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
+ execvp(argv[0], argv);
+ bb_perror_msg("can't execute '%s'", argv[0]);
+ _exit(127); /* bash compat */
+}
+
#if BB_MMU
#define pseudo_exec_argv(nommu_save, argv, assignment_cnt, argv_expanded) \
pseudo_exec_argv(argv, assignment_cnt, argv_expanded)
static void pseudo_exec_argv(nommu_save_t *nommu_save,
char **argv, int assignment_cnt,
char **argv_expanded) NORETURN;
-static void pseudo_exec_argv(nommu_save_t *nommu_save,
+static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
char **argv, int assignment_cnt,
char **argv_expanded)
{
#if ENABLE_FEATURE_SH_STANDALONE || BB_MMU
skip:
#endif
- debug_printf_exec("execing '%s'\n", argv[0]);
- sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
- execvp(argv[0], argv);
- bb_perror_msg("can't execute '%s'", argv[0]);
- _exit(EXIT_FAILURE);
+ execvp_or_die(argv);
}
/* Called after [v]fork() in run_pipe
/* Note: is WIFSIGNALED, WEXITSTATUS = sig + 128 */
rcode = WEXITSTATUS(status);
IF_HAS_KEYWORDS(if (fg_pipe->pi_inverted) rcode = !rcode;)
- /* bash prints killing signal's name for *last*
+ /* bash prints killer signal's name for *last*
* process in pipe (prints just newline for SIGINT).
- * Mimic this. Example: "sleep 5" + ^\
+ * Mimic this. Example: "sleep 5" + (^\ or kill -QUIT)
*/
if (WIFSIGNALED(status)) {
int sig = WTERMSIG(status);
* backgrounded: cmd & { list } &
* subshell: ( list ) [&]
*/
-static int run_pipe(struct pipe *pi)
+static NOINLINE int run_pipe(struct pipe *pi)
{
static const char *const null_ptr = NULL;
int i;
argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt);
}
+ /* if someone gives us an empty string: `cmd with empty output` */
+ if (!argv_expanded[0]) {
+ debug_leave();
+ return 0;
+ }
+
x = find_builtin(argv_expanded[0]);
#if ENABLE_HUSH_FUNCTIONS
funcp = NULL;
if (!command->pid) { /* child */
#if ENABLE_HUSH_JOB
disable_restore_tty_pgrp_on_exit();
+ CLEAR_RANDOM_T(&G.random_gen); /* or else $RANDOM repeats in child */
/* Every child adds itself to new process group
* with pgid == pid_of_first_child_in_pipe */
};
static const char *RES[] = {
[RES_NONE ] = "NONE" ,
-#if ENABLE_HUSH_IF
+# if ENABLE_HUSH_IF
[RES_IF ] = "IF" ,
[RES_THEN ] = "THEN" ,
[RES_ELIF ] = "ELIF" ,
[RES_ELSE ] = "ELSE" ,
[RES_FI ] = "FI" ,
-#endif
-#if ENABLE_HUSH_LOOPS
+# endif
+# if ENABLE_HUSH_LOOPS
[RES_FOR ] = "FOR" ,
[RES_WHILE] = "WHILE",
[RES_UNTIL] = "UNTIL",
[RES_DO ] = "DO" ,
[RES_DONE ] = "DONE" ,
-#endif
-#if ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE
+# endif
+# if ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE
[RES_IN ] = "IN" ,
-#endif
-#if ENABLE_HUSH_CASE
+# endif
+# if ENABLE_HUSH_CASE
[RES_CASE ] = "CASE" ,
[RES_CASE_IN ] = "CASE_IN" ,
[RES_MATCH] = "MATCH",
[RES_CASE_BODY] = "CASE_BODY",
[RES_ESAC ] = "ESAC" ,
-#endif
+# endif
[RES_XXXX ] = "XXXX" ,
[RES_SNTX ] = "SNTX" ,
};
"{}",
"()",
"[noglob]",
-#if ENABLE_HUSH_FUNCTIONS
+# if ENABLE_HUSH_FUNCTIONS
"func()",
-#endif
+# endif
};
int pin, prn;
pin++;
}
}
-#endif
+#endif /* debug_print_tree */
/* NB: called by pseudo_exec, and therefore must not modify any
* global data until exec/_exit (we can be a child after vfork!) */
};
enum {
FLAG_END = (1 << RES_NONE ),
-#if ENABLE_HUSH_IF
+# if ENABLE_HUSH_IF
FLAG_IF = (1 << RES_IF ),
FLAG_THEN = (1 << RES_THEN ),
FLAG_ELIF = (1 << RES_ELIF ),
FLAG_ELSE = (1 << RES_ELSE ),
FLAG_FI = (1 << RES_FI ),
-#endif
-#if ENABLE_HUSH_LOOPS
+# endif
+# if ENABLE_HUSH_LOOPS
FLAG_FOR = (1 << RES_FOR ),
FLAG_WHILE = (1 << RES_WHILE),
FLAG_UNTIL = (1 << RES_UNTIL),
FLAG_DO = (1 << RES_DO ),
FLAG_DONE = (1 << RES_DONE ),
FLAG_IN = (1 << RES_IN ),
-#endif
-#if ENABLE_HUSH_CASE
+# endif
+# if ENABLE_HUSH_CASE
FLAG_MATCH = (1 << RES_MATCH),
FLAG_ESAC = (1 << RES_ESAC ),
-#endif
+# endif
FLAG_START = (1 << RES_XXXX ),
};
* FLAG_START means the word must start a new compound list.
*/
static const struct reserved_combo reserved_list[] = {
-#if ENABLE_HUSH_IF
+# if ENABLE_HUSH_IF
{ "!", RES_NONE, NOT_ASSIGNMENT , 0 },
{ "if", RES_IF, WORD_IS_KEYWORD, FLAG_THEN | FLAG_START },
{ "then", RES_THEN, WORD_IS_KEYWORD, FLAG_ELIF | FLAG_ELSE | FLAG_FI },
{ "elif", RES_ELIF, WORD_IS_KEYWORD, FLAG_THEN },
{ "else", RES_ELSE, WORD_IS_KEYWORD, FLAG_FI },
{ "fi", RES_FI, NOT_ASSIGNMENT , FLAG_END },
-#endif
-#if ENABLE_HUSH_LOOPS
+# endif
+# if ENABLE_HUSH_LOOPS
{ "for", RES_FOR, NOT_ASSIGNMENT , FLAG_IN | FLAG_DO | FLAG_START },
{ "while", RES_WHILE, WORD_IS_KEYWORD, FLAG_DO | FLAG_START },
{ "until", RES_UNTIL, WORD_IS_KEYWORD, FLAG_DO | FLAG_START },
{ "in", RES_IN, NOT_ASSIGNMENT , FLAG_DO },
{ "do", RES_DO, WORD_IS_KEYWORD, FLAG_DONE },
{ "done", RES_DONE, NOT_ASSIGNMENT , FLAG_END },
-#endif
-#if ENABLE_HUSH_CASE
+# endif
+# if ENABLE_HUSH_CASE
{ "case", RES_CASE, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_START },
{ "esac", RES_ESAC, NOT_ASSIGNMENT , FLAG_END },
-#endif
+# endif
};
const struct reserved_combo *r;
*/
static int reserved_word(o_string *word, struct parse_context *ctx)
{
-#if ENABLE_HUSH_CASE
+# if ENABLE_HUSH_CASE
static const struct reserved_combo reserved_match = {
"", RES_MATCH, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_ESAC
};
-#endif
+# endif
const struct reserved_combo *r;
if (word->o_quoted)
return 0;
debug_printf("found reserved word %s, res %d\n", r->literal, r->res);
-#if ENABLE_HUSH_CASE
+# if ENABLE_HUSH_CASE
if (r->res == RES_IN && ctx->ctx_res_w == RES_CASE_IN) {
/* "case word IN ..." - IN part starts first MATCH part */
r = &reserved_match;
} else
-#endif
+# endif
if (r->flag == 0) { /* '!' */
if (ctx->ctx_inverted) { /* bash doesn't accept '! ! true' */
syntax_error("! ! command");
old = ctx->stack;
old->command->group = ctx->list_head;
old->command->cmd_type = CMD_NORMAL;
-#if !BB_MMU
+# if !BB_MMU
o_addstr(&old->as_string, ctx->as_string.data);
o_free_unsafe(&ctx->as_string);
old->command->group_as_string = xstrdup(old->as_string.data);
debug_printf_parse("pop, remembering as:'%s'\n",
old->command->group_as_string);
-#endif
+# endif
*ctx = *old; /* physical copy */
free(old);
}
return 1;
}
-#endif
+#endif /* HAS_KEYWORDS */
/* Word is complete, look at it and update parsing context.
* Normal return is 0. Syntax errors return 1.
{
FILE *pf;
int pid, channel[2];
-#if !BB_MMU
+# if !BB_MMU
char **to_free;
-#endif
+# endif
xpipe(channel);
pid = BB_MMU ? fork() : vfork();
+ (1 << SIGTTIN)
+ (1 << SIGTTOU)
, SIG_IGN);
+ CLEAR_RANDOM_T(&G.random_gen); /* or else $RANDOM repeats in child */
close(channel[0]); /* NB: close _first_, then move fd! */
xmove_fd(channel[1], 1);
/* Prevent it from trying to handle ctrl-z etc */
builtin_trap((char**)argv);
exit(0); /* not _exit() - we need to fflush */
}
-#if BB_MMU
+# if BB_MMU
reset_traps_to_defaults();
parse_and_run_string(s);
_exit(G.last_exitcode);
-#else
+# else
/* We re-execute after vfork on NOMMU. This makes this script safe:
* yes "0123456789012345678901234567890" | dd bs=32 count=64k >BIG
* huge=`cat BIG` # was blocking here forever
G.global_argv[0],
G.global_argv + 1,
NULL);
-#endif
+# endif
}
/* parent */
-#if ENABLE_HUSH_FAST
+# if ENABLE_HUSH_FAST
G.count_SIGCHLD++;
//bb_error_msg("[%d] fork in generate_stream_from_string: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
-#endif
+# endif
enable_restore_tty_pgrp_on_exit();
-#if !BB_MMU
+# if !BB_MMU
free(to_free);
-#endif
+# endif
close(channel[1]);
pf = fdopen(channel[0], "r");
return pf;
debug_printf("closed FILE from child. return 0\n");
return 0;
}
-#endif
+#endif /* ENABLE_HUSH_TICK */
static int parse_group(o_string *dest, struct parse_context *ctx,
struct in_str *input, int ch)
o_addchr(dest, SPECIAL_VAR_SYMBOL);
break;
}
-#if (ENABLE_SH_MATH_SUPPORT || ENABLE_HUSH_TICK)
+#if ENABLE_SH_MATH_SUPPORT || ENABLE_HUSH_TICK
case '(': {
# if !BB_MMU
int pos;
if (ch != '\n') {
next = i_peek(input);
}
- debug_printf_parse(": ch=%c (%d) escape=%d\n",
+ debug_printf_parse("\" ch=%c (%d) escape=%d\n",
ch, ch, dest->o_escape);
if (ch == '\\') {
if (next == EOF) {
end_trigger ? end_trigger : 'X');
debug_enter();
+ /* If very first arg is "" or '', dest.data may end up NULL.
+ * Preventing this: */
+ o_addchr(&dest, '\0');
+ dest.length = 0;
+
G.ifs = get_local_var_value("IFS");
if (G.ifs == NULL)
G.ifs = " \t\n";
/* Example: echo Hello \2>file
* we need to know that word 2 is quoted */
dest.o_quoted = 1;
- } else {
+ }
#if !BB_MMU
+ else {
/* It's "\<newline>". Remove trailing '\' from ctx.as_string */
ctx.as_string.data[--ctx.as_string.length] = '\0';
-#endif
}
+#endif
break;
case '$':
if (handle_dollar(&ctx.as_string, &dest, input) != 0) {
* MACHTYPE=i386-pc-linux-gnu
* OSTYPE=linux-gnu
* HOSTNAME=<xxxxxxxxxx>
- * PPID=<NNNNN>
+ * PPID=<NNNNN> - we also do it elsewhere
* EUID=<NNNNN>
* UID=<NNNNN>
* GROUPS=()
* Note: this form never happens:
* sh ... -c 'builtin' [BARGV...] ""
*/
- if (!G.root_pid)
+ if (!G.root_pid) {
G.root_pid = getpid();
+ G.root_ppid = getppid();
+ }
G.global_argv = argv + optind;
G.global_argc = argc - optind;
if (builtin_argc) {
case '$':
G.root_pid = bb_strtou(optarg, &optarg, 16);
optarg++;
+ G.root_ppid = bb_strtou(optarg, &optarg, 16);
+ optarg++;
G.last_bg_pid = bb_strtou(optarg, &optarg, 16);
optarg++;
G.last_exitcode = bb_strtou(optarg, &optarg, 16);
}
} /* option parsing loop */
- if (!G.root_pid)
+ if (!G.root_pid) {
G.root_pid = getpid();
+ G.root_ppid = getppid();
+ }
/* If we are login shell... */
if (argv[0] && argv[0][0] == '-') {
*/
if (!ENABLE_FEATURE_SH_EXTRA_QUIET && G_interactive_fd) {
- printf("\n\n%s hush - the humble shell\n", bb_banner);
- if (ENABLE_HUSH_HELP)
- puts("Enter 'help' for a list of built-in commands.");
- puts("");
+ /* note: ash and hush share this string */
+ printf("\n\n%s %s\n"
+ IF_HUSH_HELP("Enter 'help' for a list of built-in commands.\n")
+ "\n",
+ bb_banner,
+ "hush - the humble shell"
+ );
}
parse_and_run_file(stdin);
return 0;
}
-static int FAST_FUNC builtin_test(char **argv)
+static int run_applet_main(char **argv, int (*applet_main_func)(int argc, char **argv))
{
int argc = 0;
while (*argv) {
argc++;
argv++;
}
- return test_main(argc, argv - argc);
+ return applet_main_func(argc, argv - argc);
+}
+
+static int FAST_FUNC builtin_test(char **argv)
+{
+ return run_applet_main(argv, test_main);
}
static int FAST_FUNC builtin_echo(char **argv)
{
- int argc = 0;
- while (*argv) {
- argc++;
- argv++;
- }
- return echo_main(argc, argv - argc);
+ return run_applet_main(argv, echo_main);
}
+#if ENABLE_PRINTF
+static int FAST_FUNC builtin_printf(char **argv)
+{
+ return run_applet_main(argv, printf_main);
+}
+#endif
+
static int FAST_FUNC builtin_eval(char **argv)
{
int rcode = EXIT_SUCCESS;
{
if (*++argv == NULL)
return EXIT_SUCCESS; /* bash does this */
- {
-#if !BB_MMU
- nommu_save_t dummy;
-#endif
- /* TODO: if exec fails, bash does NOT exit! We do... */
- pseudo_exec_argv(&dummy, argv, 0, NULL);
- /* never returns */
- }
+
+ /* Careful: we can end up here after [v]fork. Do not restore
+ * tty pgrp then, only top-level shell process does that */
+ if (G_saved_tty_pgrp && getpid() == G.root_pid)
+ tcsetpgrp(G_interactive_fd, G_saved_tty_pgrp);
+
+ /* TODO: if exec fails, bash does NOT exit! We do.
+ * We'll need to undo sigprocmask (it's inside execvp_or_die)
+ * and tcsetpgrp, and this is inherently racy.
+ */
+ execvp_or_die(argv);
}
static int FAST_FUNC builtin_exit(char **argv)
if (G.traps[i]) {
printf("trap -- ");
print_escaped(G.traps[i]);
- /* bash compat: it says SIGxxx, not just xxx */
- printf(" %s%s\n", i == 0 ? "" : "SIG", get_signame(i));
+ /* note: bash adds "SIG", but only if invoked
+ * as "bash". If called as "sh", or if set -o posix,
+ * then it prints short signal names.
+ * We are printing short names: */
+ printf(" %s\n", get_signame(i));
}
}
/*fflush(stdout); - done after each builtin anyway */
void *p;
unsigned long l;
-#ifdef M_TRIM_THRESHOLD
+# ifdef M_TRIM_THRESHOLD
/* Optional. Reduces probability of false positives */
malloc_trim(0);
-#endif
+# endif
/* Crude attempt to find where "free memory" starts,
* sans fragmentation. */
p = malloc(240);