* Copyright (C) 2000,2001 Larry Doolittle <larry@doolittle.boa.org>
* Copyright (C) 2008,2009 Denys Vlasenko <vda.linux@googlemail.com>
*
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ *
* Credits:
* The parser routines proper are all original material, first
* written Dec 2000 and Jan 2001 by Larry Doolittle. The
*
* Bash compat TODO:
* redirection of stdout+stderr: &> and >&
- * brace expansion: one/{two,three,four}
* reserved words: function select
* advanced test: [[ ]]
* process substitution: <(list) and >(list)
* The EXPR is evaluated according to ARITHMETIC EVALUATION.
* This is exactly equivalent to let "EXPR".
* $[EXPR]: synonym for $((EXPR))
- * export builtin should be special, its arguments are assignments
+ *
+ * Won't do:
+ * In bash, export builtin is special, its arguments are assignments
* and therefore expansion of them should be "one-word" expansion:
* $ export i=`echo 'a b'` # export has one arg: "i=a b"
* compare with:
* aaa bbb
* $ "export" i=`echo 'aaa bbb'`; echo "$i"
* aaa
- *
- * Licensed under GPLv2 or later, see file LICENSE in this source tree.
*/
-#include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */
-#include <malloc.h> /* for malloc_trim */
+#if !(defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) \
+ || defined(__APPLE__) \
+ )
+# include <malloc.h> /* for malloc_trim */
+#endif
#include <glob.h>
/* #include <dmalloc.h> */
#if ENABLE_HUSH_CASE
# include <fnmatch.h>
#endif
+#include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */
+#include "unicode.h"
#include "shell_common.h"
#include "math.h"
#include "match.h"
# define PIPE_BUF 4096 /* amount of buffering in a pipe */
#endif
-//applet:IF_HUSH(APPLET(hush, _BB_DIR_BIN, _BB_SUID_DROP))
-//applet:IF_MSH(APPLET(msh, _BB_DIR_BIN, _BB_SUID_DROP))
-//applet:IF_FEATURE_SH_IS_HUSH(APPLET_ODDNAME(sh, hush, _BB_DIR_BIN, _BB_SUID_DROP, sh))
-//applet:IF_FEATURE_BASH_IS_HUSH(APPLET_ODDNAME(bash, hush, _BB_DIR_BIN, _BB_SUID_DROP, bash))
-
-//kbuild:lib-$(CONFIG_HUSH) += hush.o match.o shell_common.o
-//kbuild:lib-$(CONFIG_HUSH_RANDOM_SUPPORT) += random.o
-
//config:config HUSH
//config: bool "hush"
//config: default y
//config:
//config: It will compile and work on no-mmu systems.
//config:
-//config: It does not handle select, aliases, brace expansion,
-//config: tilde expansion, &>file and >&file redirection of stdout+stderr.
+//config: It does not handle select, aliases, tilde expansion,
+//config: &>file and >&file redirection of stdout+stderr.
//config:
//config:config HUSH_BASH_COMPAT
//config: bool "bash-compatible extensions"
//config: help
//config: Enable bash-compatible extensions.
//config:
+//config:config HUSH_BRACE_EXPANSION
+//config: bool "Brace expansion"
+//config: default y
+//config: depends on HUSH_BASH_COMPAT
+//config: help
+//config: Enable {abc,def} extension.
+//config:
//config:config HUSH_HELP
//config: bool "help builtin"
//config: default y
//config: msh is deprecated and will be removed, please migrate to hush.
//config:
-//usage:#define hush_trivial_usage NOUSAGE_STR
-//usage:#define hush_full_usage ""
-//usage:#define msh_trivial_usage NOUSAGE_STR
-//usage:#define msh_full_usage ""
-//usage:#define sh_trivial_usage NOUSAGE_STR
-//usage:#define sh_full_usage ""
-//usage:#define bash_trivial_usage NOUSAGE_STR
-//usage:#define bash_full_usage ""
+//applet:IF_HUSH(APPLET(hush, BB_DIR_BIN, BB_SUID_DROP))
+//applet:IF_MSH(APPLET(msh, BB_DIR_BIN, BB_SUID_DROP))
+//applet:IF_FEATURE_SH_IS_HUSH(APPLET_ODDNAME(sh, hush, BB_DIR_BIN, BB_SUID_DROP, sh))
+//applet:IF_FEATURE_BASH_IS_HUSH(APPLET_ODDNAME(bash, hush, BB_DIR_BIN, BB_SUID_DROP, bash))
+
+//kbuild:lib-$(CONFIG_HUSH) += hush.o match.o shell_common.o
+//kbuild:lib-$(CONFIG_HUSH_RANDOM_SUPPORT) += random.o
+
+/* -i (interactive) and -s (read stdin) are also accepted,
+ * but currently do nothing, therefore aren't shown in help.
+ * NOMMU-specific options are not meant to be used by users,
+ * therefore we don't show them either.
+ */
+//usage:#define hush_trivial_usage
+//usage: "[-nxl] [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]"
+//usage:#define hush_full_usage "\n\n"
+//usage: "Unix shell interpreter"
+
+//usage:#define msh_trivial_usage hush_trivial_usage
+//usage:#define msh_full_usage hush_full_usage
+
+//usage:#if ENABLE_FEATURE_SH_IS_HUSH
+//usage:# define sh_trivial_usage hush_trivial_usage
+//usage:# define sh_full_usage hush_full_usage
+//usage:#endif
+//usage:#if ENABLE_FEATURE_BASH_IS_HUSH
+//usage:# define bash_trivial_usage hush_trivial_usage
+//usage:# define bash_full_usage hush_full_usage
+//usage:#endif
/* Build knobs */
# define ENABLE_FEATURE_EDITING 0
# undef ENABLE_FEATURE_EDITING_FANCY_PROMPT
# define ENABLE_FEATURE_EDITING_FANCY_PROMPT 0
+# undef ENABLE_FEATURE_EDITING_SAVE_ON_EXIT
+# define ENABLE_FEATURE_EDITING_SAVE_ON_EXIT 0
#endif
/* Do we support ANY keywords? */
RES_SNTX
};
-enum {
- EXP_FLAG_GLOB = 0x200,
- EXP_FLAG_ESC_GLOB_CHARS = 0x100,
- EXP_FLAG_SINGLEWORD = 0x80, /* must be 0x80 */
-};
-
typedef struct o_string {
char *data;
int length; /* position where data is appended */
int maxlen;
- /* Protect newly added chars against globbing
- * (by prepending \ to *, ?, [, \) */
int o_expflags;
/* At least some part of the string was inside '' or "",
* possibly empty one: word"", wo''rd etc. */
smallint o_assignment; /* 0:maybe, 1:yes, 2:no */
} o_string;
enum {
- MAYBE_ASSIGNMENT = 0,
+ EXP_FLAG_SINGLEWORD = 0x80, /* must be 0x80 */
+ EXP_FLAG_GLOB = 0x2,
+ /* Protect newly added chars against globbing
+ * by prepending \ to *, ?, [, \ */
+ EXP_FLAG_ESC_GLOB_CHARS = 0x1,
+};
+enum {
+ MAYBE_ASSIGNMENT = 0,
DEFINITELY_ASSIGNMENT = 1,
- NOT_ASSIGNMENT = 2,
- WORD_IS_KEYWORD = 3, /* not assigment, but next word may be: "if v=xyz cmd;" */
+ NOT_ASSIGNMENT = 2,
+ /* Not an assigment, but next word may be: "if v=xyz cmd;" */
+ WORD_IS_KEYWORD = 3,
};
/* Used for initialization: o_string foo = NULL_O_STRING; */
#define NULL_O_STRING { NULL }
-/* I can almost use ordinary FILE*. Is open_memstream() universally
- * available? Where is it documented? */
+#ifndef debug_printf_parse
+static const char *const assignment_flag[] = {
+ "MAYBE_ASSIGNMENT",
+ "DEFINITELY_ASSIGNMENT",
+ "NOT_ASSIGNMENT",
+ "WORD_IS_KEYWORD",
+};
+#endif
+
typedef struct in_str {
const char *p;
/* eof_flag=1: last char in ->p is really an EOF */
char eof_flag; /* meaningless if ->p == NULL */
char peek_buf[2];
#if ENABLE_HUSH_INTERACTIVE
- smallint promptme;
smallint promptmode; /* 0: PS1, 1: PS2 */
#endif
+ int last_char;
FILE *file;
int (*get) (struct in_str *) FAST_FUNC;
int (*peek) (struct in_str *) FAST_FUNC;
struct command {
pid_t pid; /* 0 if exited */
int assignment_cnt; /* how many argv[i] are assignments? */
- smallint is_stopped; /* is the command currently running? */
smallint cmd_type; /* CMD_xxx */
#define CMD_NORMAL 0
#define CMD_SUBSHELL 1
# define CMD_FUNCDEF 3
#endif
+ smalluint cmd_exitcode;
/* if non-NULL, this "command" is { list }, ( list ), or a compound statement */
struct pipe *group;
#if !BB_MMU
#define IS_NULL_CMD(cmd) \
(!(cmd)->group && !(cmd)->argv && !(cmd)->redirects)
-
struct pipe {
struct pipe *next;
int num_cmds; /* total number of commands in pipe */
#endif
+/* set -/+o OPT support. (TODO: make it optional)
+ * bash supports the following opts:
+ * allexport off
+ * braceexpand on
+ * emacs on
+ * errexit off
+ * errtrace off
+ * functrace off
+ * hashall on
+ * histexpand off
+ * history on
+ * ignoreeof off
+ * interactive-comments on
+ * keyword off
+ * monitor on
+ * noclobber off
+ * noexec off
+ * noglob off
+ * nolog off
+ * notify off
+ * nounset off
+ * onecmd off
+ * physical off
+ * pipefail off
+ * posix off
+ * privileged off
+ * verbose off
+ * vi off
+ * xtrace off
+ */
+static const char o_opt_strings[] ALIGN1 =
+ "pipefail\0"
+ "noexec\0"
+#if ENABLE_HUSH_MODE_X
+ "xtrace\0"
+#endif
+ ;
+enum {
+ OPT_O_PIPEFAIL,
+ OPT_O_NOEXEC,
+#if ENABLE_HUSH_MODE_X
+ OPT_O_XTRACE,
+#endif
+ NUM_OPT_O
+};
+
+
/* "Globals" within this file */
/* Sorted roughly by size (smaller offsets == smaller code) */
struct globals {
# define G_saved_tty_pgrp (G.saved_tty_pgrp)
#else
# define G_saved_tty_pgrp 0
+#endif
+ char o_opt[NUM_OPT_O];
+#if ENABLE_HUSH_MODE_X
+# define G_x_mode (G.o_opt[OPT_O_XTRACE])
+#else
+# define G_x_mode 0
#endif
smallint flag_SIGINT;
#if ENABLE_HUSH_LOOPS
* 1: return is invoked, skip all till end of func
*/
smallint flag_return_in_progress;
-#endif
- smallint n_mode;
-#if ENABLE_HUSH_MODE_X
- smallint x_mode;
-# define G_x_mode (G.x_mode)
-#else
-# define G_x_mode 0
#endif
smallint exiting; /* used to prevent EXIT trap recursion */
/* These four support $?, $#, and $1 */
smalluint last_exitcode;
/* are global_argv and global_argv[1..n] malloced? (note: not [0]) */
smalluint global_args_malloced;
- smalluint inherited_set_is_saved;
/* how many non-NULL argv's we have. NB: $# + 1 */
int global_argc;
char **global_argv;
#endif
const char *ifs;
const char *cwd;
- struct variable *top_var; /* = &G.shell_ver (set in main()) */
- struct variable shell_ver;
+ struct variable *top_var;
char **expanded_assignments;
#if ENABLE_HUSH_FUNCTIONS
struct function *top_func;
unsigned handled_SIGCHLD;
smallint we_have_children;
#endif
- /* which signals have non-DFL handler (even with no traps set)? */
- unsigned non_DFL_mask;
+ /* Which signals have non-DFL handler (even with no traps set)?
+ * Set at the start to:
+ * (SIGQUIT + maybe SPECIAL_INTERACTIVE_SIGS + maybe SPECIAL_JOBSTOP_SIGS)
+ * SPECIAL_INTERACTIVE_SIGS are cleared after fork.
+ * The rest is cleared right before execv syscalls.
+ * Other than these two times, never modified.
+ */
+ unsigned special_sig_mask;
+#if ENABLE_HUSH_JOB
+ unsigned fatal_sig_mask;
+# define G_fatal_sig_mask G.fatal_sig_mask
+#else
+# define G_fatal_sig_mask 0
+#endif
char **traps; /* char *traps[NSIG] */
- sigset_t blocked_set;
- sigset_t inherited_set;
+ sigset_t pending_set;
#if HUSH_DEBUG
unsigned long memleak_value;
int debug_indent;
#endif
+ struct sigaction sa;
char user_input_buf[ENABLE_FEATURE_EDITING ? CONFIG_FEATURE_EDITING_MAX_LEN : 2];
};
#define G (*ptr_to_globals)
* is global, thus "G." prefix is a useful hint */
#define INIT_G() do { \
SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+ /* memset(&G.sa, 0, sizeof(G.sa)); */ \
+ sigfillset(&G.sa.sa_mask); \
+ G.sa.sa_flags = SA_RESTART; \
} while (0)
#if ENABLE_HUSH_HELP
static int builtin_help(char **argv) FAST_FUNC;
#endif
+#if MAX_HISTORY && ENABLE_FEATURE_EDITING
+static int builtin_history(char **argv) FAST_FUNC;
+#endif
#if ENABLE_HUSH_LOCAL
static int builtin_local(char **argv) FAST_FUNC;
#endif
#if ENABLE_HUSH_HELP
BLTIN("help" , builtin_help , NULL),
#endif
+#if MAX_HISTORY && ENABLE_FEATURE_EDITING
+ BLTIN("history" , builtin_history , "Show command history"),
+#endif
#if ENABLE_HUSH_JOB
BLTIN("jobs" , builtin_jobs , "List jobs"),
#endif
*/
#if HUSH_DEBUG
/* prevent disasters with G.debug_indent < 0 */
-# define indent() fprintf(stderr, "%*s", (G.debug_indent * 2) & 0xff, "")
+# define indent() fdprintf(2, "%*s", (G.debug_indent * 2) & 0xff, "")
# define debug_enter() (G.debug_indent++)
# define debug_leave() (G.debug_indent--)
#else
#endif
#ifndef debug_printf
-# define debug_printf(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define debug_printf(...) (indent(), fdprintf(2, __VA_ARGS__))
#endif
#ifndef debug_printf_parse
-# define debug_printf_parse(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define debug_printf_parse(...) (indent(), fdprintf(2, __VA_ARGS__))
#endif
#ifndef debug_printf_exec
-#define debug_printf_exec(...) (indent(), fprintf(stderr, __VA_ARGS__))
+#define debug_printf_exec(...) (indent(), fdprintf(2, __VA_ARGS__))
#endif
#ifndef debug_printf_env
-# define debug_printf_env(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define debug_printf_env(...) (indent(), fdprintf(2, __VA_ARGS__))
#endif
#ifndef debug_printf_jobs
-# define debug_printf_jobs(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define debug_printf_jobs(...) (indent(), fdprintf(2, __VA_ARGS__))
# define DEBUG_JOBS 1
#else
# define DEBUG_JOBS 0
#endif
#ifndef debug_printf_expand
-# define debug_printf_expand(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define debug_printf_expand(...) (indent(), fdprintf(2, __VA_ARGS__))
# define DEBUG_EXPAND 1
#else
# define DEBUG_EXPAND 0
#endif
#ifndef debug_printf_varexp
-# define debug_printf_varexp(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define debug_printf_varexp(...) (indent(), fdprintf(2, __VA_ARGS__))
#endif
#ifndef debug_printf_glob
-# define debug_printf_glob(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define debug_printf_glob(...) (indent(), fdprintf(2, __VA_ARGS__))
# define DEBUG_GLOB 1
#else
# define DEBUG_GLOB 0
#endif
#ifndef debug_printf_list
-# define debug_printf_list(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define debug_printf_list(...) (indent(), fdprintf(2, __VA_ARGS__))
#endif
#ifndef debug_printf_subst
-# define debug_printf_subst(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define debug_printf_subst(...) (indent(), fdprintf(2, __VA_ARGS__))
#endif
#ifndef debug_printf_clean
-# define debug_printf_clean(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define debug_printf_clean(...) (indent(), fdprintf(2, __VA_ARGS__))
# define DEBUG_CLEAN 1
#else
# define DEBUG_CLEAN 0
static void debug_print_strings(const char *prefix, char **vv)
{
indent();
- fprintf(stderr, "%s:\n", prefix);
+ fdprintf(2, "%s:\n", prefix);
while (*vv)
- fprintf(stderr, " '%s'\n", *vv++);
+ fdprintf(2, " '%s'\n", *vv++);
}
#else
# define debug_print_strings(prefix, vv) ((void)0)
xfunc_die();
}
-static void syntax_error(unsigned lineno, const char *msg)
+static void syntax_error(unsigned lineno UNUSED_PARAM, const char *msg)
{
if (msg)
- die_if_script(lineno, "syntax error: %s", msg);
+ bb_error_msg("syntax error: %s", msg);
else
- die_if_script(lineno, "syntax error", NULL);
+ bb_error_msg("syntax error");
}
-static void syntax_error_at(unsigned lineno, const char *msg)
+static void syntax_error_at(unsigned lineno UNUSED_PARAM, const char *msg)
{
- die_if_script(lineno, "syntax error at '%s'", msg);
+ bb_error_msg("syntax error at '%s'", msg);
}
-static void syntax_error_unterm_str(unsigned lineno, const char *s)
+static void syntax_error_unterm_str(unsigned lineno UNUSED_PARAM, const char *s)
{
- die_if_script(lineno, "syntax error: unterminated %s", s);
+ bb_error_msg("syntax error: unterminated %s", s);
}
-/* It so happens that all such cases are totally fatal
- * even if shell is interactive: EOF while looking for closing
- * delimiter. There is nowhere to read stuff from after that,
- * it's EOF! The only choice is to terminate.
- */
-static void syntax_error_unterm_ch(unsigned lineno, char ch) NORETURN;
static void syntax_error_unterm_ch(unsigned lineno, char ch)
{
char msg[2] = { ch, '\0' };
syntax_error_unterm_str(lineno, msg);
- xfunc_die();
}
-static void syntax_error_unexpected_ch(unsigned lineno, int ch)
+static void syntax_error_unexpected_ch(unsigned lineno UNUSED_PARAM, int ch)
{
char msg[2];
msg[0] = ch;
msg[1] = '\0';
- die_if_script(lineno, "syntax error: unexpected %s", ch == EOF ? "EOF" : msg);
+ bb_error_msg("syntax error: unexpected %s", ch == EOF ? "EOF" : msg);
}
#if HUSH_DEBUG < 2
* backgrounds (i.e. stops) or kills all members of currently running
* pipe.
*
- * Wait builtin in interruptible by signals for which user trap is set
+ * Wait builtin is interruptible by signals for which user trap is set
* or by SIGINT in interactive shell.
*
* Trap handlers will execute even within trap handlers. (right?)
* "echo $$; sleep 5 & wait; ls -l" + "kill -INT <pid>"
* Example 3: this does not wait 5 sec, but executes ls:
* "sleep 5; ls -l" + press ^C
+ * Example 4: this does not wait and does not execute ls:
+ * "sleep 5 & wait; ls -l" + press ^C
*
* (What happens to signals which are IGN on shell start?)
* (What happens with signal mask on shell start?)
*
- * Implementation in hush
- * ======================
+ * Old implementation
+ * ==================
* We use in-kernel pending signal mask to determine which signals were sent.
* We block all signals which we don't want to take action immediately,
* i.e. we block all signals which need to have special handling as described
* After each pipe execution, we extract any pending signals via sigtimedwait()
* and act on them.
*
- * unsigned non_DFL_mask: a mask of such "special" signals
+ * unsigned special_sig_mask: a mask of such "special" signals
* sigset_t blocked_set: current blocked signal set
*
* "trap - SIGxxx":
- * clear bit in blocked_set unless it is also in non_DFL_mask
+ * clear bit in blocked_set unless it is also in special_sig_mask
* "trap 'cmd' SIGxxx":
* set bit in blocked_set (even if 'cmd' is '')
* after [v]fork, if we plan to be a shell:
* 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.
+ *
+ * Problem: the above approach makes it unwieldy to catch signals while
+ * we are in read builtin, or while we read commands from stdin:
+ * masked signals are not visible!
+ *
+ * New implementation
+ * ==================
+ * We record each signal we are interested in by installing signal handler
+ * for them - a bit like emulating kernel pending signal mask in userspace.
+ * We are interested in: signals which need to have special handling
+ * as described above, and all signals which have traps set.
+ * Signals are recorded in pending_set.
+ * After each pipe execution, we extract any pending signals
+ * and act on them.
+ *
+ * unsigned special_sig_mask: a mask of shell-special signals.
+ * unsigned fatal_sig_mask: a mask of signals on which we restore tty pgrp.
+ * char *traps[sig] if trap for sig is set (even if it's '').
+ * sigset_t pending_set: set of sigs we received.
+ *
+ * "trap - SIGxxx":
+ * if sig is in special_sig_mask, set handler back to:
+ * record_pending_signo, or to IGN if it's a tty stop signal
+ * if sig is in fatal_sig_mask, set handler back to sigexit.
+ * else: set handler back to SIG_DFL
+ * "trap 'cmd' SIGxxx":
+ * set handler to record_pending_signo.
+ * "trap '' SIGxxx":
+ * set handler to SIG_IGN.
+ * after [v]fork, if we plan to be a shell:
+ * set signals with special interactive handling to SIG_DFL
+ * (because child shell is not interactive),
+ * unset all traps except '' (note: regardless of child shell's type - {}, (), etc)
+ * after [v]fork, if we plan to exec:
+ * POSIX says fork clears pending signal mask in child - no need to clear it.
+ *
+ * To make wait builtin interruptible, we handle SIGCHLD as special signal,
+ * otherwise (if we leave it SIG_DFL) sigsuspend in wait builtin will not wake up on it.
+ *
+ * Note (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
| (1 << SIGINT)
| (1 << SIGHUP)
,
- SPECIAL_JOB_SIGS = 0
+ SPECIAL_JOBSTOP_SIGS = 0
#if ENABLE_HUSH_JOB
| (1 << SIGTTIN)
| (1 << SIGTTOU)
| (1 << SIGTSTP)
#endif
+ ,
};
-#if ENABLE_HUSH_FAST
-static void SIGCHLD_handler(int sig UNUSED_PARAM)
+static void record_pending_signo(int sig)
{
- G.count_SIGCHLD++;
+ sigaddset(&G.pending_set, sig);
+#if ENABLE_HUSH_FAST
+ if (sig == SIGCHLD) {
+ G.count_SIGCHLD++;
//bb_error_msg("[%d] SIGCHLD_handler: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
-}
+ }
#endif
+}
+
+static sighandler_t install_sighandler(int sig, sighandler_t handler)
+{
+ struct sigaction old_sa;
+
+ /* We could use signal() to install handlers... almost:
+ * except that we need to mask ALL signals while handlers run.
+ * I saw signal nesting in strace, race window isn't small.
+ * SA_RESTART is also needed, but in Linux, signal()
+ * sets SA_RESTART too.
+ */
+ /* memset(&G.sa, 0, sizeof(G.sa)); - already done */
+ /* sigfillset(&G.sa.sa_mask); - already done */
+ /* G.sa.sa_flags = SA_RESTART; - already done */
+ G.sa.sa_handler = handler;
+ sigaction(sig, &G.sa, &old_sa);
+ return old_sa.sa_handler;
+}
#if ENABLE_HUSH_JOB
static void sigexit(int sig) NORETURN;
static void sigexit(int sig)
{
- /* Disable all signals: job control, SIGPIPE, etc. */
- sigprocmask_allsigs(SIG_BLOCK);
-
/* 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)
+ if (G_saved_tty_pgrp && getpid() == G.root_pid) {
+ /* Disable all signals: job control, SIGPIPE, etc.
+ * Mostly paranoid measure, to prevent infinite SIGTTOU.
+ */
+ sigprocmask_allsigs(SIG_BLOCK);
tcsetpgrp(G_interactive_fd, G_saved_tty_pgrp);
+ }
/* Not a signal, just exit */
if (sig <= 0)
#endif
+static sighandler_t pick_sighandler(unsigned sig)
+{
+ sighandler_t handler = SIG_DFL;
+ if (sig < sizeof(unsigned)*8) {
+ unsigned sigmask = (1 << sig);
+
+#if ENABLE_HUSH_JOB
+ /* is sig fatal? */
+ if (G_fatal_sig_mask & sigmask)
+ handler = sigexit;
+ else
+#endif
+ /* sig has special handling? */
+ if (G.special_sig_mask & sigmask) {
+ handler = record_pending_signo;
+ /* TTIN/TTOU/TSTP can't be set to record_pending_signo
+ * in order to ignore them: they will be raised
+ * in an endless loop when we try to do some
+ * terminal ioctls! We do have to _ignore_ these.
+ */
+ if (SPECIAL_JOBSTOP_SIGS & sigmask)
+ handler = SIG_IGN;
+ }
+ }
+ return handler;
+}
+
/* Restores tty foreground process group, and exits. */
static void hush_exit(int exitcode) NORETURN;
static void hush_exit(int exitcode)
{
+#if ENABLE_FEATURE_EDITING_SAVE_ON_EXIT
+ save_history(G.line_input_state);
+#endif
+
+ fflush_all();
if (G.exiting <= 0 && G.traps && G.traps[0] && G.traps[0][0]) {
- /* Prevent recursion:
- * trap "echo Hi; exit" EXIT; exit
- */
char *argv[3];
/* argv[0] is unused */
argv[1] = G.traps[0];
builtin_eval(argv);
}
+#if ENABLE_FEATURE_CLEAN_UP
+ {
+ struct variable *cur_var;
+ if (G.cwd != bb_msg_unknown)
+ free((char*)G.cwd);
+ cur_var = G.top_var;
+ while (cur_var) {
+ struct variable *tmp = cur_var;
+ if (!cur_var->max_len)
+ free(cur_var->varstr);
+ cur_var = cur_var->next;
+ free(tmp);
+ }
+ }
+#endif
+
#if ENABLE_HUSH_JOB
fflush_all();
sigexit(- (exitcode & 0xff));
}
-static int check_and_run_traps(int sig)
+//TODO: return a mask of ALL handled sigs?
+static int check_and_run_traps(void)
{
- /* I want it in rodata, not in bss.
- * gcc 4.2.1 puts it in rodata only if it has { 0, 0 }
- * initializer. But other compilers may still use bss.
- * TODO: find more portable solution.
- */
- static const struct timespec zero_timespec = { 0, 0 };
- smalluint save_rcode;
int last_sig = 0;
- if (sig)
- goto jump_in;
while (1) {
- sig = sigtimedwait(&G.blocked_set, NULL, &zero_timespec);
- if (sig <= 0)
+ int sig;
+
+ if (sigisemptyset(&G.pending_set))
break;
- jump_in:
- last_sig = sig;
+ sig = 0;
+ do {
+ sig++;
+ if (sigismember(&G.pending_set, sig)) {
+ sigdelset(&G.pending_set, sig);
+ goto got_sig;
+ }
+ } while (sig < NSIG);
+ break;
+ got_sig:
if (G.traps && G.traps[sig]) {
if (G.traps[sig][0]) {
/* We have user-defined handler */
+ smalluint save_rcode;
char *argv[3];
/* argv[0] is unused */
argv[1] = G.traps[sig];
save_rcode = G.last_exitcode;
builtin_eval(argv);
G.last_exitcode = save_rcode;
+ last_sig = sig;
} /* else: "" trap, ignoring signal */
continue;
}
/* not a trap: special action */
switch (sig) {
-#if ENABLE_HUSH_FAST
- case SIGCHLD:
- G.count_SIGCHLD++;
-//bb_error_msg("[%d] check_and_run_traps: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
- break;
-#endif
case SIGINT:
/* Builtin was ^C'ed, make it look prettier: */
bb_putchar('\n');
G.flag_SIGINT = 1;
+ last_sig = sig;
break;
#if ENABLE_HUSH_JOB
case SIGHUP: {
}
sigexit(SIGHUP);
}
+#endif
+#if ENABLE_HUSH_FAST
+ case SIGCHLD:
+ G.count_SIGCHLD++;
+//bb_error_msg("[%d] check_and_run_traps: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
+ /* Note:
+ * We dont do 'last_sig = sig' here -> NOT returning this sig.
+ * This simplifies wait builtin a bit.
+ */
+ break;
#endif
default: /* ignored: */
/* SIGTERM, SIGQUIT, SIGTTIN, SIGTTOU, SIGTSTP */
+ /* Note:
+ * We dont do 'last_sig = sig' here -> NOT returning this sig.
+ * Example: wait is not interrupted by TERM
+ * in interactive shell, because TERM is ignored.
+ */
break;
}
}
int ch = *i->p;
if (ch != '\0') {
i->p++;
+ i->last_char = ch;
return ch;
}
return EOF;
/* Enable command line editing only while a command line
* is actually being read */
do {
+ /* Unicode support should be activated even if LANG is set
+ * _during_ shell execution, not only if it was set when
+ * shell was started. Therefore, re-check LANG every time:
+ */
+ const char *s = get_local_var_value("LC_ALL");
+ if (!s) s = get_local_var_value("LC_CTYPE");
+ if (!s) s = get_local_var_value("LANG");
+ reinit_unicode(s);
+
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, CONFIG_FEATURE_EDITING_MAX_LEN-1, G.line_input_state);
+ r = read_line_input(G.line_input_state, prompt_str, G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1, /*timeout*/ -1);
/* catch *SIGINT* etc (^C is handled by read_line_input) */
- check_and_run_traps(0);
+ check_and_run_traps();
} while (r == 0 || G.flag_SIGINT); /* repeat if ^C or SIGINT */
i->eof_flag = (r < 0);
if (i->eof_flag) { /* EOF/error detected */
# else
do {
G.flag_SIGINT = 0;
- fputs(prompt_str, stdout);
+ if (i->last_char == '\0' || i->last_char == '\n') {
+ /* Why check_and_run_traps here? Try this interactively:
+ * $ trap 'echo INT' INT; (sleep 2; kill -INT $$) &
+ * $ <[enter], repeatedly...>
+ * Without check_and_run_traps, handler never runs.
+ */
+ check_and_run_traps();
+ fputs(prompt_str, stdout);
+ }
fflush_all();
G.user_input_buf[0] = r = fgetc(i->file);
/*G.user_input_buf[1] = '\0'; - already is and never changed */
-//do we need check_and_run_traps(0)? (maybe only if stdin)
} while (G.flag_SIGINT);
i->eof_flag = (r == EOF);
# endif
/* need to double check i->file because we might be doing something
* more complicated by now, like sourcing or substituting. */
#if ENABLE_HUSH_INTERACTIVE
- if (G_interactive_fd && i->promptme && i->file == stdin) {
+ if (G_interactive_fd && i->file == stdin) {
do {
get_user_input(i);
} while (!*i->p); /* need non-empty line */
i->promptmode = 1; /* PS2 */
- i->promptme = 0;
goto take_cached;
}
#endif
do ch = fgetc(i->file); while (ch == '\0');
}
debug_printf("file_get: got '%c' %d\n", ch, ch);
-#if ENABLE_HUSH_INTERACTIVE
- if (ch == '\n')
- i->promptme = 1;
-#endif
+ i->last_char = ch;
return ch;
}
static void setup_file_in_str(struct in_str *i, FILE *f)
{
+ memset(i, 0, sizeof(*i));
i->peek = file_peek;
i->get = file_get;
-#if ENABLE_HUSH_INTERACTIVE
- i->promptme = 1;
- i->promptmode = 0; /* PS1 */
-#endif
+ /* i->promptmode = 0; - PS1 (memset did it) */
i->file = f;
- i->p = NULL;
+ /* i->p = NULL; */
}
static void setup_string_in_str(struct in_str *i, const char *s)
{
+ memset(i, 0, sizeof(*i));
i->peek = static_peek;
i->get = static_get;
-#if ENABLE_HUSH_INTERACTIVE
- i->promptme = 1;
- i->promptmode = 0; /* PS1 */
-#endif
+ /* i->promptmode = 0; - PS1 (memset did it) */
i->p = s;
- i->eof_flag = 0;
+ /* i->eof_flag = 0; */
}
o_addblock(o, str, strlen(str) + 1);
}
-static void o_addblock_duplicate_backslash(o_string *o, const char *str, int len)
-{
- while (len) {
- len--;
- o_addchr(o, *str);
- if (*str++ == '\\') {
- /* \z -> \\\z; \<eol> -> \\<eol> */
- o_addchr(o, '\\');
- if (len) {
- len--;
- o_addchr(o, '\\');
- o_addchr(o, *str++);
- }
- }
- }
-}
-
-#undef HUSH_BRACE_EXP
/*
- * HUSH_BRACE_EXP code needs corresponding quoting on variable expansion side.
+ * HUSH_BRACE_EXPANSION code needs corresponding quoting on variable expansion side.
* Currently, "v='{q,w}'; echo $v" erroneously expands braces in $v.
* Apparently, on unquoted $v bash still does globbing
* ("v='*.txt'; echo $v" prints all .txt files),
* We have only second one.
*/
-#ifdef HUSH_BRACE_EXP
+#if ENABLE_HUSH_BRACE_EXPANSION
# define MAYBE_BRACES "{}"
#else
# define MAYBE_BRACES ""
ordinary_cnt = len;
o_addblock(o, str, ordinary_cnt);
if (ordinary_cnt == len)
- return;
+ return; /* NUL is already added by o_addblock */
str += ordinary_cnt;
len -= ordinary_cnt + 1; /* we are processing + 1 char below */
o_grow_by(o, sz);
o->data[o->length] = ch;
o->length++;
- o->data[o->length] = '\0';
}
+ o->data[o->length] = '\0';
}
static void o_addQblock(o_string *o, const char *str, int len)
int i = 0;
indent();
- fprintf(stderr, "%s: list:%p n:%d string_start:%d length:%d maxlen:%d glob:%d quoted:%d escape:%d\n",
+ fdprintf(2, "%s: list:%p n:%d string_start:%d length:%d maxlen:%d glob:%d quoted:%d escape:%d\n",
prefix, list, n, string_start, o->length, o->maxlen,
!!(o->o_expflags & EXP_FLAG_GLOB),
o->has_quoted_part,
!!(o->o_expflags & EXP_FLAG_ESC_GLOB_CHARS));
while (i < n) {
indent();
- fprintf(stderr, " list[%d]=%d '%s' %p\n", i, (int)list[i],
- o->data + (int)list[i] + string_start,
- o->data + (int)list[i] + string_start);
+ fdprintf(2, " list[%d]=%d '%s' %p\n", i, (int)(uintptr_t)list[i],
+ o->data + (int)(uintptr_t)list[i] + string_start,
+ o->data + (int)(uintptr_t)list[i] + string_start);
i++;
}
if (n) {
- const char *p = o->data + (int)list[n - 1] + string_start;
+ const char *p = o->data + (int)(uintptr_t)list[n - 1] + string_start;
indent();
- fprintf(stderr, " total_sz:%ld\n", (long)((p + strlen(p) + 1) - o->data));
+ fdprintf(2, " total_sz:%ld\n", (long)((p + strlen(p) + 1) - o->data));
}
}
#else
n, string_len, string_start);
o->has_empty_slot = 0;
}
+ o->has_quoted_part = 0;
list[n] = (char*)(uintptr_t)string_len;
return n + 1;
}
return ((int)(uintptr_t)list[n-1]) + string_start;
}
-#ifdef HUSH_BRACE_EXP
+#if ENABLE_HUSH_BRACE_EXPANSION
/* There in a GNU extension, GLOB_BRACE, but it is not usable:
* first, it processes even {a} (no commas), second,
* I didn't manage to make it return strings when they don't match
return n;
}
-#else /* !HUSH_BRACE_EXP */
+#else /* !HUSH_BRACE_EXPANSION */
/* Helper */
static int glob_needed(const char *s)
return n;
}
-#endif /* !HUSH_BRACE_EXP */
+#endif /* !HUSH_BRACE_EXPANSION */
/* If o->o_expflags & EXP_FLAG_GLOB, glob the string so far remembered.
* Otherwise, just finish current list[] and start new */
/*** Parsing routines ***/
+#ifndef debug_print_tree
+static void debug_print_tree(struct pipe *pi, int lvl)
+{
+ static const char *const PIPE[] = {
+ [PIPE_SEQ] = "SEQ",
+ [PIPE_AND] = "AND",
+ [PIPE_OR ] = "OR" ,
+ [PIPE_BG ] = "BG" ,
+ };
+ static const char *RES[] = {
+ [RES_NONE ] = "NONE" ,
+# if ENABLE_HUSH_IF
+ [RES_IF ] = "IF" ,
+ [RES_THEN ] = "THEN" ,
+ [RES_ELIF ] = "ELIF" ,
+ [RES_ELSE ] = "ELSE" ,
+ [RES_FI ] = "FI" ,
+# 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
+ [RES_IN ] = "IN" ,
+# 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
+ [RES_XXXX ] = "XXXX" ,
+ [RES_SNTX ] = "SNTX" ,
+ };
+ static const char *const CMDTYPE[] = {
+ "{}",
+ "()",
+ "[noglob]",
+# if ENABLE_HUSH_FUNCTIONS
+ "func()",
+# endif
+ };
+
+ int pin, prn;
+
+ pin = 0;
+ while (pi) {
+ fdprintf(2, "%*spipe %d res_word=%s followup=%d %s\n", lvl*2, "",
+ pin, RES[pi->res_word], pi->followup, PIPE[pi->followup]);
+ prn = 0;
+ while (prn < pi->num_cmds) {
+ struct command *command = &pi->cmds[prn];
+ char **argv = command->argv;
+
+ fdprintf(2, "%*s cmd %d assignment_cnt:%d",
+ lvl*2, "", prn,
+ command->assignment_cnt);
+ if (command->group) {
+ fdprintf(2, " group %s: (argv=%p)%s%s\n",
+ CMDTYPE[command->cmd_type],
+ argv
+# if !BB_MMU
+ , " group_as_string:", command->group_as_string
+# else
+ , "", ""
+# endif
+ );
+ debug_print_tree(command->group, lvl+1);
+ prn++;
+ continue;
+ }
+ if (argv) while (*argv) {
+ fdprintf(2, " '%s'", *argv);
+ argv++;
+ }
+ fdprintf(2, "\n");
+ prn++;
+ }
+ pi = pi->next;
+ pin++;
+ }
+}
+#endif /* debug_print_tree */
+
static struct pipe *new_pipe(void)
{
struct pipe *pi;
*/
static const struct reserved_combo reserved_list[] = {
# 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 },
+ { "!", RES_NONE, NOT_ASSIGNMENT , 0 },
+ { "if", RES_IF, MAYBE_ASSIGNMENT, FLAG_THEN | FLAG_START },
+ { "then", RES_THEN, MAYBE_ASSIGNMENT, FLAG_ELIF | FLAG_ELSE | FLAG_FI },
+ { "elif", RES_ELIF, MAYBE_ASSIGNMENT, FLAG_THEN },
+ { "else", RES_ELSE, MAYBE_ASSIGNMENT, FLAG_FI },
+ { "fi", RES_FI, NOT_ASSIGNMENT , FLAG_END },
# 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 },
+ { "for", RES_FOR, NOT_ASSIGNMENT , FLAG_IN | FLAG_DO | FLAG_START },
+ { "while", RES_WHILE, MAYBE_ASSIGNMENT, FLAG_DO | FLAG_START },
+ { "until", RES_UNTIL, MAYBE_ASSIGNMENT, FLAG_DO | FLAG_START },
+ { "in", RES_IN, NOT_ASSIGNMENT , FLAG_DO },
+ { "do", RES_DO, MAYBE_ASSIGNMENT, FLAG_DONE },
+ { "done", RES_DONE, NOT_ASSIGNMENT , FLAG_END },
# endif
# if ENABLE_HUSH_CASE
- { "case", RES_CASE, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_START },
- { "esac", RES_ESAC, NOT_ASSIGNMENT , FLAG_END },
+ { "case", RES_CASE, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_START },
+ { "esac", RES_ESAC, NOT_ASSIGNMENT , FLAG_END },
# endif
};
const struct reserved_combo *r;
- for (r = reserved_list; r < reserved_list + ARRAY_SIZE(reserved_list); r++) {
+ for (r = reserved_list; r < reserved_list + ARRAY_SIZE(reserved_list); r++) {
if (strcmp(word->data, r->literal) == 0)
return r;
}
ctx->ctx_res_w = r->res;
ctx->old_flag = r->flag;
word->o_assignment = r->assignment_flag;
+ debug_printf_parse("word->o_assignment='%s'\n", assignment_flag[word->o_assignment]);
if (ctx->old_flag & FLAG_END) {
struct parse_context *old;
debug_printf_parse("word stored in rd_filename: '%s'\n", word->data);
ctx->pending_redirect = NULL;
} else {
- /* If this word wasn't an assignment, next ones definitely
- * can't be assignments. Even if they look like ones. */
- if (word->o_assignment != DEFINITELY_ASSIGNMENT
- && word->o_assignment != WORD_IS_KEYWORD
- ) {
- word->o_assignment = NOT_ASSIGNMENT;
- } else {
- if (word->o_assignment == DEFINITELY_ASSIGNMENT)
- command->assignment_cnt++;
- word->o_assignment = MAYBE_ASSIGNMENT;
- }
-
#if HAS_KEYWORDS
# if ENABLE_HUSH_CASE
if (ctx->ctx_dsemicolon
&& ctx->ctx_res_w != RES_CASE
# endif
) {
- debug_printf_parse("checking '%s' for reserved-ness\n", word->data);
- if (reserved_word(word, ctx)) {
+ int reserved = reserved_word(word, ctx);
+ debug_printf_parse("checking for reserved-ness: %d\n", reserved);
+ if (reserved) {
o_reset_to_empty_unquoted(word);
debug_printf_parse("done_word return %d\n",
(ctx->ctx_res_w == RES_SNTX));
"groups and arglists don't mix\n");
return 1;
}
+
+ /* If this word wasn't an assignment, next ones definitely
+ * can't be assignments. Even if they look like ones. */
+ if (word->o_assignment != DEFINITELY_ASSIGNMENT
+ && word->o_assignment != WORD_IS_KEYWORD
+ ) {
+ word->o_assignment = NOT_ASSIGNMENT;
+ } else {
+ if (word->o_assignment == DEFINITELY_ASSIGNMENT) {
+ command->assignment_cnt++;
+ debug_printf_parse("++assignment_cnt=%d\n", command->assignment_cnt);
+ }
+ debug_printf_parse("word->o_assignment was:'%s'\n", assignment_flag[word->o_assignment]);
+ word->o_assignment = MAYBE_ASSIGNMENT;
+ }
+ debug_printf_parse("word->o_assignment='%s'\n", assignment_flag[word->o_assignment]);
+
if (word->has_quoted_part
/* optimization: and if it's ("" or '') or ($v... or `cmd`...): */
&& (word->data[0] == '\0' || word->data[0] == SPECIAL_VAR_SYMBOL)
) {
p += 3;
}
- if (p == word->data || p[0] != '\0') {
- /* saw no "$@", or not only "$@" but some
- * real text is there too */
- /* insert "empty variable" reference, this makes
- * e.g. "", $empty"" etc to not disappear */
- o_addchr(word, SPECIAL_VAR_SYMBOL);
- o_addchr(word, SPECIAL_VAR_SYMBOL);
- }
}
command->argv = add_string_to_strings(command->argv, xstrdup(word->data));
debug_print_strings("word appended to argv", command->argv);
int ch;
goto jump_in;
+
while (1) {
ch = i_getch(input);
if (ch != EOF)
#if ENABLE_HUSH_TICK || ENABLE_SH_MATH_SUPPORT || ENABLE_HUSH_DOLLAR_OPS
/* Subroutines for copying $(...) and `...` things */
-static void add_till_backquote(o_string *dest, struct in_str *input, int in_dquote);
+static int add_till_backquote(o_string *dest, struct in_str *input, int in_dquote);
/* '...' */
-static void add_till_single_quote(o_string *dest, struct in_str *input)
+static int add_till_single_quote(o_string *dest, struct in_str *input)
{
while (1) {
int ch = i_getch(input);
if (ch == EOF) {
syntax_error_unterm_ch('\'');
- /*xfunc_die(); - redundant */
+ return 0;
}
if (ch == '\'')
- return;
+ return 1;
o_addchr(dest, ch);
}
}
/* "...\"...`..`...." - do we need to handle "...$(..)..." too? */
-static void add_till_double_quote(o_string *dest, struct in_str *input)
+static int add_till_double_quote(o_string *dest, struct in_str *input)
{
while (1) {
int ch = i_getch(input);
if (ch == EOF) {
syntax_error_unterm_ch('"');
- /*xfunc_die(); - redundant */
+ return 0;
}
if (ch == '"')
- return;
+ return 1;
if (ch == '\\') { /* \x. Copy both chars. */
o_addchr(dest, ch);
ch = i_getch(input);
}
o_addchr(dest, ch);
if (ch == '`') {
- add_till_backquote(dest, input, /*in_dquote:*/ 1);
+ if (!add_till_backquote(dest, input, /*in_dquote:*/ 1))
+ return 0;
o_addchr(dest, ch);
continue;
}
* Example Output
* echo `echo '\'TEST\`echo ZZ\`BEST` \TESTZZBEST
*/
-static void add_till_backquote(o_string *dest, struct in_str *input, int in_dquote)
+static int add_till_backquote(o_string *dest, struct in_str *input, int in_dquote)
{
while (1) {
int ch = i_getch(input);
if (ch == '`')
- return;
+ return 1;
if (ch == '\\') {
/* \x. Copy both unless it is \`, \$, \\ and maybe \" */
ch = i_getch(input);
}
if (ch == EOF) {
syntax_error_unterm_ch('`');
- /*xfunc_die(); - redundant */
+ return 0;
}
o_addchr(dest, ch);
}
ch = i_getch(input);
if (ch == EOF) {
syntax_error_unterm_ch(end_ch);
- /*xfunc_die(); - redundant */
+ return 0;
}
if (ch == end_ch IF_HUSH_BASH_COMPAT( || ch == end_char2)) {
if (!dbl)
o_addchr(dest, ch);
if (ch == '(' || ch == '{') {
ch = (ch == '(' ? ')' : '}');
- add_till_closing_bracket(dest, input, ch);
+ if (!add_till_closing_bracket(dest, input, ch))
+ return 0;
o_addchr(dest, ch);
continue;
}
if (ch == '\'') {
- add_till_single_quote(dest, input);
+ if (!add_till_single_quote(dest, input))
+ return 0;
o_addchr(dest, ch);
continue;
}
if (ch == '"') {
- add_till_double_quote(dest, input);
+ if (!add_till_double_quote(dest, input))
+ return 0;
o_addchr(dest, ch);
continue;
}
if (ch == '`') {
- add_till_backquote(dest, input, /*in_dquote:*/ 0);
+ if (!add_till_backquote(dest, input, /*in_dquote:*/ 0))
+ return 0;
o_addchr(dest, ch);
continue;
}
ch = i_getch(input);
if (ch == EOF) {
syntax_error_unterm_ch(')');
- /*xfunc_die(); - redundant */
+ return 0;
}
o_addchr(dest, ch);
continue;
) {
bad_dollar_syntax:
syntax_error_unterm_str("${name}");
- debug_printf_parse("parse_dollar return 1: unterminated ${name}\n");
- return 1;
+ debug_printf_parse("parse_dollar return 0: unterminated ${name}\n");
+ return 0;
}
nommu_addchr(as_string, ch);
ch |= quote_mask;
pos = dest->length;
#if ENABLE_HUSH_DOLLAR_OPS
last_ch = add_till_closing_bracket(dest, input, end_ch);
+ if (last_ch == 0) /* error? */
+ return 0;
#else
#error Simple code to only allow ${var} is not implemented
#endif
o_addchr(dest, /*quote_mask |*/ '+');
if (!BB_MMU)
pos = dest->length;
- add_till_closing_bracket(dest, input, ')' | DOUBLE_CLOSE_CHAR_FLAG);
+ if (!add_till_closing_bracket(dest, input, ')' | DOUBLE_CLOSE_CHAR_FLAG))
+ return 0; /* error */
if (as_string) {
o_addstr(as_string, dest->data + pos);
o_addchr(as_string, ')');
o_addchr(dest, quote_mask | '`');
if (!BB_MMU)
pos = dest->length;
- add_till_closing_bracket(dest, input, ')');
+ if (!add_till_closing_bracket(dest, input, ')'))
+ return 0; /* error */
if (as_string) {
o_addstr(as_string, dest->data + pos);
o_addchr(as_string, ')');
default:
o_addQchr(dest, '$');
}
- debug_printf_parse("parse_dollar return 0\n");
- return 0;
+ debug_printf_parse("parse_dollar return 1 (ok)\n");
+ return 1;
#undef as_string
}
if (ch != EOF)
nommu_addchr(as_string, ch);
if (ch == dquote_end) { /* may be only '"' or EOF */
- debug_printf_parse("encode_string return 0\n");
- return 0;
+ debug_printf_parse("encode_string return 1 (ok)\n");
+ return 1;
}
/* note: can't move it above ch == dquote_end check! */
if (ch == EOF) {
syntax_error_unterm_ch('"');
- /*xfunc_die(); - redundant */
+ return 0; /* error */
}
next = '\0';
if (ch != '\n') {
goto again;
}
if (ch == '$') {
- if (parse_dollar(as_string, dest, input, /*quote_mask:*/ 0x80) != 0) {
- debug_printf_parse("encode_string return 1: "
- "parse_dollar returned non-0\n");
- return 1;
+ if (!parse_dollar(as_string, dest, input, /*quote_mask:*/ 0x80)) {
+ debug_printf_parse("encode_string return 0: "
+ "parse_dollar returned 0 (error)\n");
+ return 0;
}
goto again;
}
//unsigned pos = dest->length;
o_addchr(dest, SPECIAL_VAR_SYMBOL);
o_addchr(dest, 0x80 | '`');
- add_till_backquote(dest, input, /*in_dquote:*/ dquote_end == '"');
+ if (!add_till_backquote(dest, input, /*in_dquote:*/ dquote_end == '"'))
+ return 0; /* error */
o_addchr(dest, SPECIAL_VAR_SYMBOL);
//debug_printf_subst("SUBST RES3 '%s'\n", dest->data + pos);
goto again;
* Scan input until EOF or end_trigger char.
* Return a list of pipes to execute, or NULL on EOF
* or if end_trigger character is met.
- * On syntax error, exit is shell is not interactive,
+ * On syntax error, exit if shell is not interactive,
* reset parsing machinery and start parsing anew,
* or return ERR_PTR.
*/
* here we should use blank chars as separators, not $IFS
*/
- reset: /* we come back here only on syntax errors in interactive shell */
-
-#if ENABLE_HUSH_INTERACTIVE
- input->promptmode = 0; /* PS1 */
-#endif
if (MAYBE_ASSIGNMENT != 0)
dest.o_assignment = MAYBE_ASSIGNMENT;
initialize_context(&ctx);
/* end_trigger == '}' case errors out earlier,
* checking only ')' */
if (end_trigger == ')') {
- syntax_error_unterm_ch('('); /* exits */
- /* goto parse_error; */
+ syntax_error_unterm_ch('(');
+ goto parse_error;
}
if (done_word(&dest, &ctx)) {
/* (this makes bare "&" cmd a no-op.
* bash says: "syntax error near unexpected token '&'") */
if (pi->num_cmds == 0
- IF_HAS_KEYWORDS( && pi->res_word == RES_NONE)
+ IF_HAS_KEYWORDS(&& pi->res_word == RES_NONE)
) {
free_pipe_list(pi);
pi = NULL;
&& is_well_formed_var_name(dest.data, '=')
) {
dest.o_assignment = DEFINITELY_ASSIGNMENT;
+ debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]);
}
continue;
}
goto parse_error;
}
if (ch == '\n') {
-#if ENABLE_HUSH_CASE
- /* "case ... in <newline> word) ..." -
- * newlines are ignored (but ';' wouldn't be) */
- if (ctx.command->argv == NULL
- && ctx.ctx_res_w == RES_MATCH
+ /* Is this a case when newline is simply ignored?
+ * Some examples:
+ * "cmd | <newline> cmd ..."
+ * "case ... in <newline> word) ..."
+ */
+ if (IS_NULL_CMD(ctx.command)
+ && dest.length == 0 && !dest.has_quoted_part
) {
- continue;
+ /* This newline can be ignored. But...
+ * Without check #1, interactive shell
+ * ignores even bare <newline>,
+ * and shows the continuation prompt:
+ * ps1_prompt$ <enter>
+ * ps2> _ <=== wrong, should be ps1
+ * Without check #2, "cmd & <newline>"
+ * is similarly mistreated.
+ * (BTW, this makes "cmd & cmd"
+ * and "cmd && cmd" non-orthogonal.
+ * Really, ask yourself, why
+ * "cmd && <newline>" doesn't start
+ * cmd but waits for more input?
+ * No reason...)
+ */
+ struct pipe *pi = ctx.list_head;
+ if (pi->num_cmds != 0 /* check #1 */
+ && pi->followup != PIPE_BG /* check #2 */
+ ) {
+ continue;
+ }
}
-#endif
/* Treat newline as a command separator. */
done_pipe(&ctx, PIPE_SEQ);
debug_printf_parse("heredoc_cnt:%d\n", heredoc_cnt);
heredoc_cnt = 0;
}
dest.o_assignment = MAYBE_ASSIGNMENT;
+ debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]);
ch = ';';
/* note: if (is_blank) continue;
* will still trigger for us */
}
done_pipe(&ctx, PIPE_SEQ);
dest.o_assignment = MAYBE_ASSIGNMENT;
+ debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]);
/* Do we sit outside of any if's, loops or case's? */
if (!HAS_KEYWORDS
- IF_HAS_KEYWORDS(|| (ctx.ctx_res_w == RES_NONE && ctx.old_flag == 0))
+ IF_HAS_KEYWORDS(|| (ctx.ctx_res_w == RES_NONE && ctx.old_flag == 0))
) {
o_free(&dest);
#if !BB_MMU
if (parse_redirect(&ctx, redir_fd, redir_style, input))
goto parse_error;
continue; /* back to top of while (1) */
+ case '#':
+ if (dest.length == 0 && !dest.has_quoted_part) {
+ /* skip "#comment" */
+ while (1) {
+ ch = i_peek(input);
+ if (ch == EOF || ch == '\n')
+ break;
+ i_getch(input);
+ /* note: we do not add it to &ctx.as_string */
+ }
+ nommu_addchr(&ctx.as_string, '\n');
+ continue; /* back to top of while (1) */
+ }
+ break;
+ case '\\':
+ if (next == '\n') {
+ /* It's "\<newline>" */
+#if !BB_MMU
+ /* Remove trailing '\' from ctx.as_string */
+ ctx.as_string.data[--ctx.as_string.length] = '\0';
+#endif
+ ch = i_getch(input); /* eat it */
+ continue; /* back to top of while (1) */
+ }
+ break;
}
if (dest.o_assignment == MAYBE_ASSIGNMENT
/* ch is a special char and thus this word
* cannot be an assignment */
dest.o_assignment = NOT_ASSIGNMENT;
+ debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]);
}
/* Note: nommu_addchr(&ctx.as_string, ch) is already done */
switch (ch) {
- case '#':
- if (dest.length == 0) {
- while (1) {
- ch = i_peek(input);
- if (ch == EOF || ch == '\n')
- break;
- i_getch(input);
- /* note: we do not add it to &ctx.as_string */
- }
- nommu_addchr(&ctx.as_string, '\n');
- } else {
- o_addQchr(&dest, ch);
- }
+ case '#': /* non-comment #: "echo a#b" etc */
+ o_addQchr(&dest, ch);
break;
case '\\':
if (next == EOF) {
xfunc_die();
}
ch = i_getch(input);
- if (ch != '\n') {
- o_addchr(&dest, '\\');
- /*nommu_addchr(&ctx.as_string, '\\'); - already done */
- o_addchr(&dest, ch);
- nommu_addchr(&ctx.as_string, ch);
- /* Example: echo Hello \2>file
- * we need to know that word 2 is quoted */
- dest.has_quoted_part = 1;
- }
-#if !BB_MMU
- else {
- /* It's "\<newline>". Remove trailing '\' from ctx.as_string */
- ctx.as_string.data[--ctx.as_string.length] = '\0';
- }
-#endif
+ /* note: ch != '\n' (that case does not reach this place) */
+ o_addchr(&dest, '\\');
+ /*nommu_addchr(&ctx.as_string, '\\'); - already done */
+ o_addchr(&dest, ch);
+ nommu_addchr(&ctx.as_string, ch);
+ /* Example: echo Hello \2>file
+ * we need to know that word 2 is quoted */
+ dest.has_quoted_part = 1;
break;
case '$':
- if (parse_dollar(&ctx.as_string, &dest, input, /*quote_mask:*/ 0) != 0) {
+ if (!parse_dollar(&ctx.as_string, &dest, input, /*quote_mask:*/ 0)) {
debug_printf_parse("parse_stream parse error: "
- "parse_dollar returned non-0\n");
+ "parse_dollar returned 0 (error)\n");
goto parse_error;
}
break;
case '\'':
dest.has_quoted_part = 1;
- while (1) {
- ch = i_getch(input);
- if (ch == EOF) {
- syntax_error_unterm_ch('\'');
- /*xfunc_die(); - redundant */
+ if (next == '\'' && !ctx.pending_redirect) {
+ insert_empty_quoted_str_marker:
+ nommu_addchr(&ctx.as_string, next);
+ i_getch(input); /* eat second ' */
+ o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+ o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+ } else {
+ while (1) {
+ ch = i_getch(input);
+ if (ch == EOF) {
+ syntax_error_unterm_ch('\'');
+ goto parse_error;
+ }
+ nommu_addchr(&ctx.as_string, ch);
+ if (ch == '\'')
+ break;
+ o_addqchr(&dest, ch);
}
- nommu_addchr(&ctx.as_string, ch);
- if (ch == '\'')
- break;
- o_addqchr(&dest, ch);
}
break;
case '"':
dest.has_quoted_part = 1;
+ if (next == '"' && !ctx.pending_redirect)
+ goto insert_empty_quoted_str_marker;
if (dest.o_assignment == NOT_ASSIGNMENT)
dest.o_expflags |= EXP_FLAG_ESC_GLOB_CHARS;
- if (encode_string(&ctx.as_string, &dest, input, '"', /*process_bkslash:*/ 1))
+ if (!encode_string(&ctx.as_string, &dest, input, '"', /*process_bkslash:*/ 1))
goto parse_error;
dest.o_expflags &= ~EXP_FLAG_ESC_GLOB_CHARS;
break;
#if ENABLE_HUSH_TICK
case '`': {
- unsigned pos;
+ USE_FOR_NOMMU(unsigned pos;)
o_addchr(&dest, SPECIAL_VAR_SYMBOL);
o_addchr(&dest, '`');
- pos = dest.length;
- add_till_backquote(&dest, input, /*in_dquote:*/ 0);
+ USE_FOR_NOMMU(pos = dest.length;)
+ if (!add_till_backquote(&dest, input, /*in_dquote:*/ 0))
+ goto parse_error;
# if !BB_MMU
o_addstr(&ctx.as_string, dest.data + pos);
o_addchr(&ctx.as_string, '`');
/* We just finished a cmd. New one may start
* with an assignment */
dest.o_assignment = MAYBE_ASSIGNMENT;
+ debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]);
break;
case '&':
if (done_word(&dest, &ctx)) {
}
IF_HAS_KEYWORDS(pctx = p2;)
} while (HAS_KEYWORDS && pctx);
- /* Free text, clear all dest fields */
+
o_free(&dest);
- /* If we are not in top-level parse, we return,
- * our caller will propagate error.
- */
- if (end_trigger != ';') {
+ G.last_exitcode = 1;
#if !BB_MMU
- if (pstring)
- *pstring = NULL;
+ if (pstring)
+ *pstring = NULL;
#endif
- debug_leave();
- return ERR_PTR;
- }
- /* Discard cached input, force prompt */
- input->p = NULL;
- IF_HUSH_INTERACTIVE(input->promptme = 1;)
- goto reset;
+ debug_leave();
+ return ERR_PTR;
}
}
expand_string_to_string(str)
#endif
static char *expand_string_to_string(const char *str, int do_unbackslash);
+#if ENABLE_HUSH_TICK
static int process_command_subs(o_string *dest, const char *s);
+#endif
/* expand_strvec_to_strvec() takes a list of strings, expands
* all variable references within and returns a pointer to
* followed by strings themselves.
* Caller can deallocate entire list by single free(list). */
+/* A horde of its helpers come first: */
+
+static void o_addblock_duplicate_backslash(o_string *o, const char *str, int len)
+{
+ while (--len >= 0) {
+ char c = *str++;
+
+#if ENABLE_HUSH_BRACE_EXPANSION
+ if (c == '{' || c == '}') {
+ /* { -> \{, } -> \} */
+ o_addchr(o, '\\');
+ /* And now we want to add { or } and continue:
+ * o_addchr(o, c);
+ * continue;
+ * luckily, just falling throught achieves this.
+ */
+ }
+#endif
+ o_addchr(o, c);
+ if (c == '\\') {
+ /* \z -> \\\z; \<eol> -> \\<eol> */
+ o_addchr(o, '\\');
+ if (len) {
+ len--;
+ o_addchr(o, '\\');
+ o_addchr(o, *str++);
+ }
+ }
+ }
+}
+
/* Store given string, finalizing the word and starting new one whenever
* we encounter IFS char(s). This is used for expanding variable values.
- * End-of-string does NOT finalize word: think about 'echo -$VAR-' */
-static int expand_on_ifs(o_string *output, int n, const char *str)
+ * End-of-string does NOT finalize word: think about 'echo -$VAR-'.
+ * Return in *ended_with_ifs:
+ * 1 - ended with IFS char, else 0 (this includes case of empty str).
+ */
+static int expand_on_ifs(int *ended_with_ifs, o_string *output, int n, const char *str)
{
+ int last_is_ifs = 0;
+
while (1) {
- int word_len = strcspn(str, G.ifs);
+ int word_len;
+
+ if (!*str) /* EOL - do not finalize word */
+ break;
+ word_len = strcspn(str, G.ifs);
if (word_len) {
- if (!(output->o_expflags & EXP_FLAG_GLOB))
+ /* We have WORD_LEN leading non-IFS chars */
+ if (!(output->o_expflags & EXP_FLAG_GLOB)) {
o_addblock(output, str, word_len);
- else {
+ } else {
/* Protect backslashes against globbing up :)
* Example: "v='\*'; echo b$v" prints "b\*"
* (and does not try to glob on "*")
/*o_addblock(output, str, word_len); - WRONG: "v='\*'; echo Z$v" prints "Z*" instead of "Z\*" */
/*o_addqblock(output, str, word_len); - WRONG: "v='*'; echo Z$v" prints "Z*" instead of Z* files */
}
+ last_is_ifs = 0;
str += word_len;
+ if (!*str) /* EOL - do not finalize word */
+ break;
}
+
+ /* We know str here points to at least one IFS char */
+ last_is_ifs = 1;
+ str += strspn(str, G.ifs); /* skip IFS chars */
if (!*str) /* EOL - do not finalize word */
break;
- o_addchr(output, '\0');
- debug_print_list("expand_on_ifs", output, n);
- n = o_save_ptr(output, n);
- str += strspn(str, G.ifs); /* skip ifs chars */
+
+ /* Start new word... but not always! */
+ /* Case "v=' a'; echo ''$v": we do need to finalize empty word: */
+ if (output->has_quoted_part
+ /* Case "v=' a'; echo $v":
+ * here nothing precedes the space in $v expansion,
+ * therefore we should not finish the word
+ * (IOW: if there *is* word to finalize, only then do it):
+ */
+ || (n > 0 && output->data[output->length - 1])
+ ) {
+ o_addchr(output, '\0');
+ debug_print_list("expand_on_ifs", output, n);
+ n = o_save_ptr(output, n);
+ }
}
+
+ if (ended_with_ifs)
+ *ended_with_ifs = last_is_ifs;
debug_print_list("expand_on_ifs[1]", output, n);
return n;
}
*/
setup_string_in_str(&input, str);
encode_string(NULL, &dest, &input, EOF, process_bkslash);
+//TODO: error check (encode_string returns 0 on error)?
//bb_error_msg("'%s' -> '%s'", str, dest.data);
exp_str = expand_string_to_string(dest.data, /*unbackslash:*/ do_unbackslash);
//bb_error_msg("'%s' -> '%s'", dest.data, exp_str);
}
#if ENABLE_SH_MATH_SUPPORT
-static arith_t expand_and_evaluate_arith(const char *arg, int *errcode_p)
+static arith_t expand_and_evaluate_arith(const char *arg, const char **errmsg_p)
{
- arith_eval_hooks_t hooks;
+ arith_state_t math_state;
arith_t res;
char *exp_str;
- hooks.lookupvar = get_local_var_value;
- hooks.setvar = set_local_var_from_halves;
- //hooks.endofname = endofname;
+ math_state.lookupvar = get_local_var_value;
+ math_state.setvar = set_local_var_from_halves;
+ //math_state.endofname = endofname;
exp_str = encode_then_expand_string(arg, /*process_bkslash:*/ 1, /*unbackslash:*/ 1);
- res = arith(exp_str ? exp_str : arg, errcode_p, &hooks);
+ res = arith(&math_state, exp_str ? exp_str : arg);
free(exp_str);
+ if (errmsg_p)
+ *errmsg_p = math_state.errmsg;
+ if (math_state.errmsg)
+ die_if_script(math_state.errmsg);
return res;
}
#endif
char *exp_exp_word;
char *loc;
unsigned scan_flags = pick_scan(exp_op, *exp_word);
- if (exp_op == *exp_word) /* ## or %% */
+ if (exp_op == *exp_word) /* ## or %% */
exp_word++;
exp_exp_word = encode_then_expand_string(exp_word, /*process_bkslash:*/ 1, /*unbackslash:*/ 1);
if (exp_exp_word)
* var:N<SPECIAL_VAR_SYMBOL>M<SPECIAL_VAR_SYMBOL>
*/
arith_t beg, len;
- int errcode = 0;
+ const char *errmsg;
- beg = expand_and_evaluate_arith(exp_word, &errcode);
+ beg = expand_and_evaluate_arith(exp_word, &errmsg);
+ if (errmsg)
+ goto arith_err;
debug_printf_varexp("beg:'%s'=%lld\n", exp_word, (long long)beg);
*p++ = SPECIAL_VAR_SYMBOL;
exp_word = p;
p = strchr(p, SPECIAL_VAR_SYMBOL);
*p = '\0';
- len = expand_and_evaluate_arith(exp_word, &errcode);
+ len = expand_and_evaluate_arith(exp_word, &errmsg);
+ if (errmsg)
+ goto arith_err;
debug_printf_varexp("len:'%s'=%lld\n", exp_word, (long long)len);
-
- if (errcode >= 0 && len >= 0) { /* bash compat: len < 0 is illegal */
+ if (len >= 0) { /* bash compat: len < 0 is illegal */
if (beg < 0) /* bash compat */
beg = 0;
debug_printf_varexp("from val:'%s'\n", val);
- if (len == 0 || !val || beg >= strlen(val))
- val = "";
- else {
+ if (len == 0 || !val || beg >= strlen(val)) {
+ arith_err:
+ val = NULL;
+ } else {
/* Paranoia. What if user entered 9999999999999
* which fits in arith_t but not int? */
if (len >= INT_MAX)
#endif
{
die_if_script("malformed ${%s:...}", var);
- val = "";
+ val = NULL;
}
} else { /* one of "-=+?" */
/* Standard-mandated substitution ops:
* expansion of right-hand side of assignment == 1-element expand.
*/
char cant_be_null = 0; /* only bit 0x80 matters */
+ int ended_in_ifs = 0; /* did last unquoted expansion end with IFS chars? */
char *p;
debug_printf_expand("expand_vars_to_list: arg:'%s' singleword:%x\n", arg,
#if ENABLE_SH_MATH_SUPPORT
char arith_buf[sizeof(arith_t)*3 + 2];
#endif
+
+ if (ended_in_ifs) {
+ o_addchr(output, '\0');
+ n = o_save_ptr(output, n);
+ ended_in_ifs = 0;
+ }
+
o_addblock(output, arg, p - arg);
debug_print_list("expand_vars_to_list[1]", output, n);
arg = ++p;
cant_be_null |= first_ch; /* do it for "$@" _now_, when we know it's not empty */
if (!(first_ch & 0x80)) { /* unquoted $* or $@ */
while (G.global_argv[i]) {
- n = expand_on_ifs(output, n, G.global_argv[i]);
+ n = expand_on_ifs(NULL, output, n, G.global_argv[i]);
debug_printf_expand("expand_vars_to_list: argv %d (last %d)\n", i, G.global_argc - 1);
if (G.global_argv[i++][0] && G.global_argv[i]) {
/* this argv[] is not empty and not last:
if (G.ifs[0])
o_addchr(output, G.ifs[0]);
}
+ output->has_quoted_part = 1;
}
break;
}
case SPECIAL_VAR_SYMBOL: /* <SPECIAL_VAR_SYMBOL><SPECIAL_VAR_SYMBOL> */
/* "Empty variable", used to make "" etc to not disappear */
+ output->has_quoted_part = 1;
arg++;
cant_be_null = 0x80;
break;
#if ENABLE_SH_MATH_SUPPORT
case '+': { /* <SPECIAL_VAR_SYMBOL>+cmd<SPECIAL_VAR_SYMBOL> */
arith_t res;
- int errcode;
arg++; /* skip '+' */
*p = '\0'; /* replace trailing <SPECIAL_VAR_SYMBOL> */
debug_printf_subst("ARITH '%s' first_ch %x\n", arg, first_ch);
- res = expand_and_evaluate_arith(arg, &errcode);
-
- if (errcode < 0) {
- const char *msg = "error in arithmetic";
- switch (errcode) {
- case -3:
- msg = "exponent less than 0";
- break;
- case -2:
- msg = "divide by 0";
- break;
- case -5:
- msg = "expression recursion loop detected";
- break;
- }
- die_if_script(msg);
- }
- debug_printf_subst("ARITH RES '"arith_t_fmt"'\n", res);
- sprintf(arith_buf, arith_t_fmt, res);
+ res = expand_and_evaluate_arith(arg, NULL);
+ debug_printf_subst("ARITH RES '"ARITH_FMT"'\n", res);
+ sprintf(arith_buf, ARITH_FMT, res);
val = arith_buf;
break;
}
debug_printf_expand("unquoted '%s', output->o_escape:%d\n", val,
!!(output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS));
if (val && val[0]) {
- n = expand_on_ifs(output, n, val);
+ n = expand_on_ifs(&ended_in_ifs, output, n, val);
val = NULL;
}
} else { /* quoted $VAR, val will be appended below */
+ output->has_quoted_part = 1;
debug_printf_expand("quoted '%s', output->o_escape:%d\n", val,
!!(output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS));
}
} /* end of "while (SPECIAL_VAR_SYMBOL is found) ..." */
if (arg[0]) {
+ if (ended_in_ifs) {
+ o_addchr(output, '\0');
+ n = o_save_ptr(output, n);
+ }
debug_print_list("expand_vars_to_list[a]", output, n);
/* this part is literal, and it was already pre-quoted
* if needed (much earlier), do not use o_addQstr here! */
for (i = 0; i < count; i++) {
G.expanded_assignments = p = add_string_to_strings(p, expand_string_to_string(argv[i], /*unbackslash:*/ 1));
}
- G.expanded_assignments = NULL;
- return p;
+ G.expanded_assignments = NULL;
+ return p;
+}
+
+
+static void switch_off_special_sigs(unsigned mask)
+{
+ unsigned sig = 0;
+ while ((mask >>= 1) != 0) {
+ sig++;
+ if (!(mask & 1))
+ continue;
+ if (G.traps) {
+ if (G.traps[sig] && !G.traps[sig][0])
+ /* trap is '', has to remain SIG_IGN */
+ continue;
+ free(G.traps[sig]);
+ G.traps[sig] = NULL;
+ }
+ /* We are here only if no trap or trap was not '' */
+ install_sighandler(sig, SIG_DFL);
+ }
}
-
#if BB_MMU
/* never called */
void re_execute_shell(char ***to_free, const char *s,
* Testcase: (while :; do :; done) + ^Z should background.
* Same goes for SIGTERM, SIGHUP, SIGINT.
*/
- if (!G.traps && !(G.non_DFL_mask & SPECIAL_INTERACTIVE_SIGS))
- return; /* already no traps and no SPECIAL_INTERACTIVE_SIGS */
-
- /* 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) {
- /* 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;
+ mask = (G.special_sig_mask & SPECIAL_INTERACTIVE_SIGS) | G_fatal_sig_mask;
+ if (!G.traps && !mask)
+ return; /* already no traps and no special 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] || !G.traps[sig][0])
- continue;
+ /* Switch off special sigs */
+ switch_off_special_sigs(mask);
+#if ENABLE_HUSH_JOB
+ G_fatal_sig_mask = 0;
+#endif
+ G.special_sig_mask &= ~SPECIAL_INTERACTIVE_SIGS;
+ /* SIGQUIT,SIGCHLD and maybe SPECIAL_JOBSTOP_SIGS
+ * remain set in G.special_sig_mask */
+
+ if (!G.traps)
+ return;
+
+ /* Reset all sigs to default except ones with empty traps */
+ for (sig = 0; sig < NSIG; sig++) {
+ if (!G.traps[sig])
+ continue; /* no trap: nothing to do */
+ if (!G.traps[sig][0])
+ continue; /* empty trap: has to remain SIG_IGN */
+ /* sig has non-empty trap, reset it: */
free(G.traps[sig]);
G.traps[sig] = NULL;
- /* There is no signal for 0 (EXIT) */
+ /* There is no signal for trap 0 (EXIT) */
if (sig == 0)
continue;
- /* There was a trap handler, we just removed it.
- * But if sig still has non-DFL handling,
- * we should not unblock the sig. */
- if (mask & 1)
- continue;
- sigdelset(&G.blocked_set, sig);
+ install_sighandler(sig, pick_sighandler(sig));
}
- sigprocmask(SIG_SETMASK, &G.blocked_set, NULL);
}
#else /* !BB_MMU */
* _inside_ group (just before echo 1), it works.
*
* I conclude it means we don't need to pass active traps here.
- * Even if we would use signal handlers instead of signal masking
- * in order to implement trap handling,
- * exec syscall below resets signals to SIG_DFL for us.
*/
*pp++ = (char *) "-c";
*pp++ = (char *) s;
do_exec:
debug_printf_exec("re_execute_shell pid:%d cmd:'%s'\n", getpid(), s);
- sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
+ /* Don't propagate SIG_IGN to the child */
+ if (SPECIAL_JOBSTOP_SIGS != 0)
+ switch_off_special_sigs(G.special_sig_mask & SPECIAL_JOBSTOP_SIGS);
execve(bb_busybox_exec_path, argv, pp);
/* Fallback. Useful for init=/bin/hush usage etc */
if (argv[0][0] == '/')
while (1) {
struct pipe *pipe_list;
+#if ENABLE_HUSH_INTERACTIVE
+ if (end_trigger == ';')
+ inp->promptmode = 0; /* PS1 */
+#endif
pipe_list = parse_stream(NULL, inp, end_trigger);
- if (!pipe_list) { /* EOF */
- if (empty)
+ if (!pipe_list || pipe_list == ERR_PTR) { /* EOF/error */
+ /* If we are in "big" script
+ * (not in `cmd` or something similar)...
+ */
+ if (pipe_list == ERR_PTR && end_trigger == ';') {
+ /* Discard cached input (rest of line) */
+ int ch = inp->last_char;
+ while (ch != EOF && ch != '\n') {
+ //bb_error_msg("Discarded:'%c'", ch);
+ ch = i_getch(inp);
+ }
+ /* Force prompt */
+ inp->p = NULL;
+ /* This stream isn't empty */
+ empty = 0;
+ continue;
+ }
+ if (!pipe_list && empty)
G.last_exitcode = 0;
break;
}
debug_printf_exec("parse_and_run_stream: run_and_free_list\n");
run_and_free_list(pipe_list);
empty = 0;
+#if ENABLE_HUSH_FUNCTIONS
+ if (G.flag_return_in_progress == 1)
+ break;
+#endif
}
}
char **argv)
{
#if BB_MMU
- int rcode = x->b_function(argv);
+ int rcode;
+ fflush_all();
+ rcode = x->b_function(argv);
fflush_all();
_exit(rcode);
#else
+ fflush_all();
/* On NOMMU, we must never block!
* Example: { sleep 99 | read line; } & echo Ok
*/
static void execvp_or_die(char **argv)
{
debug_printf_exec("execing '%s'\n", argv[0]);
- sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
+ /* Don't propagate SIG_IGN to the child */
+ if (SPECIAL_JOBSTOP_SIGS != 0)
+ switch_off_special_sigs(G.special_sig_mask & SPECIAL_JOBSTOP_SIGS);
execvp(argv[0], argv);
bb_perror_msg("can't execute '%s'", argv[0]);
_exit(127); /* bash compat */
# endif
/* Re-exec ourselves */
debug_printf_exec("re-execing applet '%s'\n", argv[0]);
- sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
+ /* Don't propagate SIG_IGN to the child */
+ if (SPECIAL_JOBSTOP_SIGS != 0)
+ switch_off_special_sigs(G.special_sig_mask & SPECIAL_JOBSTOP_SIGS);
execv(bb_busybox_exec_path, argv);
/* If they called chroot or otherwise made the binary no longer
* executable, fall through */
#endif
/* Were we asked to wait for fg pipe? */
if (fg_pipe) {
- for (i = 0; i < fg_pipe->num_cmds; i++) {
+ i = fg_pipe->num_cmds;
+ while (--i >= 0) {
debug_printf_jobs("check pid %d\n", fg_pipe->cmds[i].pid);
if (fg_pipe->cmds[i].pid != childpid)
continue;
if (dead) {
+ int ex;
fg_pipe->cmds[i].pid = 0;
fg_pipe->alive_cmds--;
- if (i == fg_pipe->num_cmds - 1) {
- /* last process gives overall exitstatus */
- rcode = WEXITSTATUS(status);
- /* bash prints killer signal's name for *last*
- * process in pipe (prints just newline for SIGINT).
- * Mimic this. Example: "sleep 5" + (^\ or kill -QUIT)
- */
- if (WIFSIGNALED(status)) {
- int sig = WTERMSIG(status);
- printf("%s\n", sig == SIGINT ? "" : get_signame(sig));
- /* TODO: MIPS has 128 sigs (1..128), what if sig==128 here?
- * Maybe we need to use sig | 128? */
- rcode = sig + 128;
- }
- IF_HAS_KEYWORDS(if (fg_pipe->pi_inverted) rcode = !rcode;)
+ ex = WEXITSTATUS(status);
+ /* bash prints killer signal's name for *last*
+ * process in pipe (prints just newline for SIGINT/SIGPIPE).
+ * Mimic this. Example: "sleep 5" + (^\ or kill -QUIT)
+ */
+ if (WIFSIGNALED(status)) {
+ int sig = WTERMSIG(status);
+ if (i == fg_pipe->num_cmds-1)
+ /* TODO: use strsignal() instead for bash compat? but that's bloat... */
+ printf("%s\n", sig == SIGINT || sig == SIGPIPE ? "" : get_signame(sig));
+ /* TODO: if (WCOREDUMP(status)) + " (core dumped)"; */
+ /* TODO: MIPS has 128 sigs (1..128), what if sig==128 here?
+ * Maybe we need to use sig | 128? */
+ ex = sig + 128;
}
+ fg_pipe->cmds[i].cmd_exitcode = ex;
} else {
- fg_pipe->cmds[i].is_stopped = 1;
fg_pipe->stopped_cmds++;
}
debug_printf_jobs("fg_pipe: alive_cmds %d stopped_cmds %d\n",
fg_pipe->alive_cmds, fg_pipe->stopped_cmds);
- if (fg_pipe->alive_cmds - fg_pipe->stopped_cmds <= 0) {
+ if (fg_pipe->alive_cmds == fg_pipe->stopped_cmds) {
/* All processes in fg pipe have exited or stopped */
+ i = fg_pipe->num_cmds;
+ while (--i >= 0) {
+ rcode = fg_pipe->cmds[i].cmd_exitcode;
+ /* usually last process gives overall exitstatus,
+ * but with "set -o pipefail", last *failed* process does */
+ if (G.o_opt[OPT_O_PIPEFAIL] == 0 || rcode != 0)
+ break;
+ }
+ IF_HAS_KEYWORDS(if (fg_pipe->pi_inverted) rcode = !rcode;)
/* Note: *non-interactive* bash does not continue if all processes in fg pipe
* are stopped. Testcase: "cat | cat" in a script (not on command line!)
* and "killall -STOP cat" */
if (G_interactive_fd) {
#if ENABLE_HUSH_JOB
- if (fg_pipe->alive_cmds)
+ if (fg_pipe->alive_cmds != 0)
insert_bg_job(fg_pipe);
#endif
return rcode;
}
- if (!fg_pipe->alive_cmds)
+ if (fg_pipe->alive_cmds == 0)
return rcode;
}
/* There are still running processes in the fg pipe */
}
} else {
/* child stopped */
- pi->cmds[i].is_stopped = 1;
pi->stopped_cmds++;
}
#endif
* cmd ; ... { list } ; ...
* cmd && ... { list } && ...
* cmd || ... { list } || ...
- * If it is, then we can run cmd as a builtin, NOFORK [do we do this?],
+ * If it is, then we can run cmd as a builtin, NOFORK,
* or (if SH_STANDALONE) an applet, and we can run the { list }
* with run_list. If it isn't one of these, we fork and exec cmd.
*
* subshell: ( list ) [&]
*/
#if !ENABLE_HUSH_MODE_X
-#define redirect_and_varexp_helper(new_env_p, old_vars_p, command, squirrel, char argv_expanded) \
+#define redirect_and_varexp_helper(new_env_p, old_vars_p, command, squirrel, argv_expanded) \
redirect_and_varexp_helper(new_env_p, old_vars_p, command, squirrel)
#endif
static int redirect_and_varexp_helper(char ***new_env_p,
}
/* Expand the rest into (possibly) many strings each */
- if (0) {}
#if ENABLE_HUSH_BASH_COMPAT
- else if (command->cmd_type == CMD_SINGLEWORD_NOGLOB) {
+ if (command->cmd_type == CMD_SINGLEWORD_NOGLOB) {
argv_expanded = expand_strvec_to_strvec_singleword_noglob(argv + command->assignment_cnt);
- }
+ } else
#endif
- else {
+ {
argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt);
}
if (!funcp) {
debug_printf_exec(": builtin '%s' '%s'...\n",
x->b_cmd, argv_expanded[1]);
+ fflush_all();
rcode = x->b_function(argv_expanded) & 0xff;
fflush_all();
}
return rcode;
}
- if (ENABLE_FEATURE_SH_STANDALONE) {
+ if (ENABLE_FEATURE_SH_NOFORK) {
int n = find_applet_by_name(argv_expanded[0]);
if (n >= 0 && APPLET_IS_NOFORK(n)) {
rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, squirrel, argv_expanded);
if (setup_redirects(command, NULL))
_exit(1);
- /* Restore default handlers just prior to exec */
- /*signal(SIGCHLD, SIG_DFL); - so far we don't have any handlers */
-
/* Stores to nommu_save list of env vars putenv'ed
* (NOMMU, on MMU we don't need that) */
/* cast away volatility... */
return -1;
}
-#ifndef debug_print_tree
-static void debug_print_tree(struct pipe *pi, int lvl)
-{
- static const char *const PIPE[] = {
- [PIPE_SEQ] = "SEQ",
- [PIPE_AND] = "AND",
- [PIPE_OR ] = "OR" ,
- [PIPE_BG ] = "BG" ,
- };
- static const char *RES[] = {
- [RES_NONE ] = "NONE" ,
-# if ENABLE_HUSH_IF
- [RES_IF ] = "IF" ,
- [RES_THEN ] = "THEN" ,
- [RES_ELIF ] = "ELIF" ,
- [RES_ELSE ] = "ELSE" ,
- [RES_FI ] = "FI" ,
-# 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
- [RES_IN ] = "IN" ,
-# 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
- [RES_XXXX ] = "XXXX" ,
- [RES_SNTX ] = "SNTX" ,
- };
- static const char *const CMDTYPE[] = {
- "{}",
- "()",
- "[noglob]",
-# if ENABLE_HUSH_FUNCTIONS
- "func()",
-# endif
- };
-
- int pin, prn;
-
- pin = 0;
- while (pi) {
- fprintf(stderr, "%*spipe %d res_word=%s followup=%d %s\n", lvl*2, "",
- pin, RES[pi->res_word], pi->followup, PIPE[pi->followup]);
- prn = 0;
- while (prn < pi->num_cmds) {
- struct command *command = &pi->cmds[prn];
- char **argv = command->argv;
-
- fprintf(stderr, "%*s cmd %d assignment_cnt:%d",
- lvl*2, "", prn,
- command->assignment_cnt);
- if (command->group) {
- fprintf(stderr, " group %s: (argv=%p)%s%s\n",
- CMDTYPE[command->cmd_type],
- argv
-# if !BB_MMU
- , " group_as_string:", command->group_as_string
-# else
- , "", ""
-# endif
- );
- debug_print_tree(command->group, lvl+1);
- prn++;
- continue;
- }
- if (argv) while (*argv) {
- fprintf(stderr, " '%s'", *argv);
- argv++;
- }
- fprintf(stderr, "\n");
- prn++;
- }
- pi = pi->next;
- pin++;
- }
-}
-#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!) */
static int run_list(struct pipe *pi)
#if ENABLE_HUSH_LOOPS
/* Check syntax for "for" */
- for (struct pipe *cpipe = pi; cpipe; cpipe = cpipe->next) {
- if (cpipe->res_word != RES_FOR && cpipe->res_word != RES_IN)
- continue;
- /* current word is FOR or IN (BOLD in comments below) */
- if (cpipe->next == NULL) {
- syntax_error("malformed for");
- debug_leave();
- debug_printf_exec("run_list lvl %d return 1\n", G.run_list_level);
- return 1;
- }
- /* "FOR v; do ..." and "for v IN a b; do..." are ok */
- if (cpipe->next->res_word == RES_DO)
- continue;
- /* next word is not "do". It must be "in" then ("FOR v in ...") */
- if (cpipe->res_word == RES_IN /* "for v IN a b; not_do..."? */
- || cpipe->next->res_word != RES_IN /* FOR v not_do_and_not_in..."? */
- ) {
- syntax_error("malformed for");
- debug_leave();
- debug_printf_exec("run_list lvl %d return 1\n", G.run_list_level);
- return 1;
+ {
+ struct pipe *cpipe;
+ for (cpipe = pi; cpipe; cpipe = cpipe->next) {
+ if (cpipe->res_word != RES_FOR && cpipe->res_word != RES_IN)
+ continue;
+ /* current word is FOR or IN (BOLD in comments below) */
+ if (cpipe->next == NULL) {
+ syntax_error("malformed for");
+ debug_leave();
+ debug_printf_exec("run_list lvl %d return 1\n", G.run_list_level);
+ return 1;
+ }
+ /* "FOR v; do ..." and "for v IN a b; do..." are ok */
+ if (cpipe->next->res_word == RES_DO)
+ continue;
+ /* next word is not "do". It must be "in" then ("FOR v in ...") */
+ if (cpipe->res_word == RES_IN /* "for v IN a b; not_do..."? */
+ || cpipe->next->res_word != RES_IN /* FOR v not_do_and_not_in..."? */
+ ) {
+ syntax_error("malformed for");
+ debug_leave();
+ debug_printf_exec("run_list lvl %d return 1\n", G.run_list_level);
+ return 1;
+ }
}
}
#endif
* and we should not execute CMD */
debug_printf_exec("skipped cmd because of || or &&\n");
last_followup = pi->followup;
- continue;
+ goto dont_check_jobs_but_continue;
}
}
last_followup = pi->followup;
* and we don't need to wait for anything. */
G.last_exitcode = rcode;
debug_printf_exec(": builtin/func exitcode %d\n", rcode);
- check_and_run_traps(0);
+ check_and_run_traps();
#if ENABLE_HUSH_LOOPS
/* Was it "break" or "continue"? */
if (G.flag_break_continue) {
G.flag_break_continue = 0;
/* else: e.g. "continue 2" should *break* once, *then* continue */
} /* else: "while... do... { we are here (innermost list is not a loop!) };...done" */
- if (G.depth_break_continue != 0 || fbc == BC_BREAK)
- goto check_jobs_and_break;
+ if (G.depth_break_continue != 0 || fbc == BC_BREAK) {
+ checkjobs(NULL);
+ break;
+ }
/* "continue": simulate end of loop */
rword = RES_DONE;
continue;
#endif
#if ENABLE_HUSH_FUNCTIONS
if (G.flag_return_in_progress == 1) {
- /* same as "goto check_jobs_and_break" */
checkjobs(NULL);
break;
}
/* even bash 3.2 doesn't do that well with nested bg:
* try "{ { sleep 10; echo DEEP; } & echo HERE; } &".
* I'm NOT treating inner &'s as jobs */
- check_and_run_traps(0);
+ check_and_run_traps();
#if ENABLE_HUSH_JOB
if (G.run_list_level == 1)
insert_bg_job(pi);
/* Waits for completion, then fg's main shell */
rcode = checkjobs_and_fg_shell(pi);
debug_printf_exec(": checkjobs_and_fg_shell exitcode %d\n", rcode);
- check_and_run_traps(0);
+ check_and_run_traps();
} else
#endif
{ /* This one just waits for completion */
rcode = checkjobs(pi);
debug_printf_exec(": checkjobs exitcode %d\n", rcode);
- check_and_run_traps(0);
+ check_and_run_traps();
}
G.last_exitcode = rcode;
}
if (rword == RES_IF || rword == RES_ELIF)
cond_code = rcode;
#endif
+ check_jobs_and_continue:
+ checkjobs(NULL);
+ dont_check_jobs_but_continue: ;
#if ENABLE_HUSH_LOOPS
/* Beware of "while false; true; do ..."! */
- if (pi->next && pi->next->res_word == RES_DO) {
+ if (pi->next
+ && (pi->next->res_word == RES_DO || pi->next->res_word == RES_DONE)
+ /* check for RES_DONE is needed for "while ...; do \n done" case */
+ ) {
if (rword == RES_WHILE) {
if (rcode) {
/* "while false; do...done" - exitcode 0 */
G.last_exitcode = rcode = EXIT_SUCCESS;
debug_printf_exec(": while expr is false: breaking (exitcode:EXIT_SUCCESS)\n");
- goto check_jobs_and_break;
+ break;
}
}
if (rword == RES_UNTIL) {
if (!rcode) {
debug_printf_exec(": until expr is true: breaking\n");
- check_jobs_and_break:
- checkjobs(NULL);
break;
}
}
}
#endif
-
- check_jobs_and_continue:
- checkjobs(NULL);
} /* for (pi) */
#if ENABLE_HUSH_JOB
{
int rcode = 0;
debug_printf_exec("run_and_free_list entered\n");
- if (!G.n_mode) {
+ if (!G.o_opt[OPT_O_NOEXEC]) {
debug_printf_exec(": run_list: 1st pipe with %d cmds\n", pi->num_cmds);
rcode = run_list(pi);
}
}
+static void install_sighandlers(unsigned mask)
+{
+ sighandler_t old_handler;
+ unsigned sig = 0;
+ while ((mask >>= 1) != 0) {
+ sig++;
+ if (!(mask & 1))
+ continue;
+ old_handler = install_sighandler(sig, pick_sighandler(sig));
+ /* POSIX allows shell to re-enable SIGCHLD
+ * even if it was SIG_IGN on entry.
+ * Therefore we skip IGN check for it:
+ */
+ if (sig == SIGCHLD)
+ continue;
+ if (old_handler == SIG_IGN) {
+ /* oops... restore back to IGN, and record this fact */
+ install_sighandler(sig, old_handler);
+ if (!G.traps)
+ G.traps = xzalloc(sizeof(G.traps[0]) * NSIG);
+ free(G.traps[sig]);
+ G.traps[sig] = xzalloc(1); /* == xstrdup(""); */
+ }
+ }
+}
+
/* Called a few times only (or even once if "sh -c") */
-static void init_sigmasks(void)
+static void install_special_sighandlers(void)
{
- unsigned sig;
unsigned mask;
- sigset_t old_blocked_set;
- if (!G.inherited_set_is_saved) {
- sigprocmask(SIG_SETMASK, NULL, &G.blocked_set);
- G.inherited_set = G.blocked_set;
- }
- old_blocked_set = G.blocked_set;
-
- mask = (1 << SIGQUIT);
+ /* Which signals are shell-special? */
+ mask = (1 << SIGQUIT) | (1 << SIGCHLD);
if (G_interactive_fd) {
- mask = (1 << SIGQUIT) | SPECIAL_INTERACTIVE_SIGS;
+ mask |= SPECIAL_INTERACTIVE_SIGS;
if (G_saved_tty_pgrp) /* we have ctty, job control sigs work */
- mask |= SPECIAL_JOB_SIGS;
+ mask |= SPECIAL_JOBSTOP_SIGS;
}
- G.non_DFL_mask = mask;
-
- sig = 0;
- while (mask) {
- if (mask & 1)
- sigaddset(&G.blocked_set, sig);
- mask >>= 1;
- sig++;
+ /* Careful, do not re-install handlers we already installed */
+ if (G.special_sig_mask != mask) {
+ unsigned diff = mask & ~G.special_sig_mask;
+ G.special_sig_mask = mask;
+ install_sighandlers(diff);
}
- sigdelset(&G.blocked_set, SIGCHLD);
-
- if (memcmp(&old_blocked_set, &G.blocked_set, sizeof(old_blocked_set)) != 0)
- sigprocmask(SIG_SETMASK, &G.blocked_set, NULL);
-
- /* POSIX allows shell to re-enable SIGCHLD
- * even if it was SIG_IGN on entry */
-#if ENABLE_HUSH_FAST
- G.count_SIGCHLD++; /* ensure it is != G.handled_SIGCHLD */
- if (!G.inherited_set_is_saved)
- signal(SIGCHLD, SIGCHLD_handler);
-#else
- if (!G.inherited_set_is_saved)
- signal(SIGCHLD, SIG_DFL);
-#endif
-
- G.inherited_set_is_saved = 1;
}
#if ENABLE_HUSH_JOB
/* helper */
-static void maybe_set_to_sigexit(int sig)
-{
- void (*handler)(int);
- /* non_DFL_mask'ed signals are, well, masked,
- * no need to set handler for them.
- */
- if (!((G.non_DFL_mask >> sig) & 1)) {
- handler = signal(sig, sigexit);
- if (handler == SIG_IGN) /* oops... restore back to IGN! */
- signal(sig, handler);
- }
-}
/* Set handlers to restore tty pgrp and exit */
-static void set_fatal_handlers(void)
-{
- /* We _must_ restore tty pgrp on fatal signals */
- if (HUSH_DEBUG) {
- maybe_set_to_sigexit(SIGILL );
- maybe_set_to_sigexit(SIGFPE );
- maybe_set_to_sigexit(SIGBUS );
- maybe_set_to_sigexit(SIGSEGV);
- maybe_set_to_sigexit(SIGTRAP);
- } /* else: hush is perfect. what SEGV? */
- maybe_set_to_sigexit(SIGABRT);
+static void install_fatal_sighandlers(void)
+{
+ unsigned mask;
+
+ /* We will restore tty pgrp on these signals */
+ mask = 0
+ + (1 << SIGILL ) * HUSH_DEBUG
+ + (1 << SIGFPE ) * HUSH_DEBUG
+ + (1 << SIGBUS ) * HUSH_DEBUG
+ + (1 << SIGSEGV) * HUSH_DEBUG
+ + (1 << SIGTRAP) * HUSH_DEBUG
+ + (1 << SIGABRT)
/* bash 3.2 seems to handle these just like 'fatal' ones */
- maybe_set_to_sigexit(SIGPIPE);
- maybe_set_to_sigexit(SIGALRM);
- /* if we are interactive, SIGHUP, SIGTERM and SIGINT are masked.
+ + (1 << SIGPIPE)
+ + (1 << SIGALRM)
+ /* if we are interactive, SIGHUP, SIGTERM and SIGINT are special sigs.
* if we aren't interactive... but in this case
- * we never want to restore pgrp on exit, and this fn is not called */
- /*maybe_set_to_sigexit(SIGHUP );*/
- /*maybe_set_to_sigexit(SIGTERM);*/
- /*maybe_set_to_sigexit(SIGINT );*/
+ * we never want to restore pgrp on exit, and this fn is not called
+ */
+ /*+ (1 << SIGHUP )*/
+ /*+ (1 << SIGTERM)*/
+ /*+ (1 << SIGINT )*/
+ ;
+ G_fatal_sig_mask = mask;
+
+ install_sighandlers(mask);
}
#endif
-static int set_mode(const char cstate, const char mode)
+static int set_mode(int state, char mode, const char *o_opt)
{
- int state = (cstate == '-' ? 1 : 0);
+ int idx;
switch (mode) {
- case 'n': G.n_mode = state; break;
- case 'x': IF_HUSH_MODE_X(G_x_mode = state;) break;
- default: return EXIT_FAILURE;
+ case 'n':
+ G.o_opt[OPT_O_NOEXEC] = state;
+ break;
+ case 'x':
+ IF_HUSH_MODE_X(G_x_mode = state;)
+ break;
+ case 'o':
+ if (!o_opt) {
+ /* "set -+o" without parameter.
+ * in bash, set -o produces this output:
+ * pipefail off
+ * and set +o:
+ * set +o pipefail
+ * We always use the second form.
+ */
+ const char *p = o_opt_strings;
+ idx = 0;
+ while (*p) {
+ printf("set %co %s\n", (G.o_opt[idx] ? '-' : '+'), p);
+ idx++;
+ p += strlen(p) + 1;
+ }
+ break;
+ }
+ idx = index_in_strings(o_opt_strings, o_opt);
+ if (idx >= 0) {
+ G.o_opt[idx] = state;
+ break;
+ }
+ default:
+ return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
int hush_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int hush_main(int argc, char **argv)
{
+ enum {
+ OPT_login = (1 << 0),
+ };
+ unsigned flags;
int opt;
unsigned builtin_argc;
char **e;
struct variable *cur_var;
+ struct variable *shell_ver;
INIT_G();
- if (EXIT_SUCCESS) /* if EXIT_SUCCESS == 0, it is already done */
+ if (EXIT_SUCCESS != 0) /* if EXIT_SUCCESS == 0, it is already done */
G.last_exitcode = EXIT_SUCCESS;
+#if ENABLE_HUSH_FAST
+ G.count_SIGCHLD++; /* ensure it is != G.handled_SIGCHLD */
+#endif
#if !BB_MMU
G.argv0_for_re_execing = argv[0];
#endif
/* Deal with HUSH_VERSION */
- G.shell_ver.flg_export = 1;
- G.shell_ver.flg_read_only = 1;
+ shell_ver = xzalloc(sizeof(*shell_ver));
+ shell_ver->flg_export = 1;
+ shell_ver->flg_read_only = 1;
/* Code which handles ${var<op>...} needs writable values for all variables,
* therefore we xstrdup: */
- G.shell_ver.varstr = xstrdup(hush_version_str),
- G.top_var = &G.shell_ver;
+ shell_ver->varstr = xstrdup(hush_version_str);
/* Create shell local variables from the values
* currently living in the environment */
debug_printf_env("unsetenv '%s'\n", "HUSH_VERSION");
unsetenv("HUSH_VERSION"); /* in case it exists in initial env */
+ G.top_var = shell_ver;
cur_var = G.top_var;
e = environ;
if (e) while (*e) {
e++;
}
/* (Re)insert HUSH_VERSION into env (AFTER we scanned the env!) */
- debug_printf_env("putenv '%s'\n", G.shell_ver.varstr);
- putenv(G.shell_ver.varstr);
+ debug_printf_env("putenv '%s'\n", shell_ver->varstr);
+ putenv(shell_ver->varstr);
/* Export PWD */
set_pwd_var(/*exp:*/ 1);
#if ENABLE_FEATURE_EDITING
G.line_input_state = new_line_input_t(FOR_SHELL);
-# if defined MAX_HISTORY && MAX_HISTORY > 0 && ENABLE_HUSH_SAVEHISTORY
- {
- const char *hp = get_local_var_value("HISTFILE");
- if (!hp) {
- hp = get_local_var_value("HOME");
- if (hp) {
- G.line_input_state->hist_file = concat_path_file(hp, ".hush_history");
- //set_local_var(xasprintf("HISTFILE=%s", ...));
- }
- }
- }
-# endif
#endif
- G.global_argc = argc;
- G.global_argv = argv;
/* Initialize some more globals to non-zero values */
cmdedit_update_prompt();
}
/* Shell is non-interactive at first. We need to call
- * init_sigmasks() if we are going to execute "sh <script>",
+ * install_special_sighandlers() if we are going to execute "sh <script>",
* "sh -c <cmds>" or login shell's /etc/profile and friends.
- * If we later decide that we are interactive, we run init_sigmasks()
+ * If we later decide that we are interactive, we run install_special_sighandlers()
* in order to intercept (more) signals.
*/
/* Parse options */
/* http://www.opengroup.org/onlinepubs/9699919799/utilities/sh.html */
+ flags = (argv[0] && argv[0][0] == '-') ? OPT_login : 0;
builtin_argc = 0;
while (1) {
- opt = getopt(argc, argv, "+c:xins"
+ opt = getopt(argc, argv, "+c:xinsl"
#if !BB_MMU
"<:$:R:V:"
# if ENABLE_HUSH_FUNCTIONS
/* -c 'builtin' [BARGV...] "" ARG0 [ARG1...] */
const struct built_in_command *x;
- init_sigmasks();
+ install_special_sighandlers();
x = find_builtin(optarg);
if (x) { /* paranoia */
G.global_argc -= builtin_argc; /* skip [BARGV...] "" */
G.global_argv += builtin_argc;
G.global_argv[-1] = NULL; /* replace "" */
+ fflush_all();
G.last_exitcode = x->b_function(argv + optind - 1);
}
goto final_return;
G.global_argv[0] = argv[0];
G.global_argc++;
} /* else -c 'script' ARG0 [ARG1...]: $0 is ARG0 */
- init_sigmasks();
+ install_special_sighandlers();
parse_and_run_string(optarg);
goto final_return;
case 'i':
/* "-s" means "read from stdin", but this is how we always
* operate, so simply do nothing here. */
break;
+ case 'l':
+ flags |= OPT_login;
+ break;
#if !BB_MMU
case '<': /* "big heredoc" support */
full_write1_str(optarg);
empty_trap_mask = bb_strtoull(optarg, &optarg, 16);
if (empty_trap_mask != 0) {
int sig;
- init_sigmasks();
+ install_special_sighandlers();
G.traps = xzalloc(sizeof(G.traps[0]) * NSIG);
for (sig = 1; sig < NSIG; sig++) {
if (empty_trap_mask & (1LL << sig)) {
G.traps[sig] = xzalloc(1); /* == xstrdup(""); */
- sigaddset(&G.blocked_set, sig);
+ install_sighandler(sig, SIG_IGN);
}
}
- sigprocmask(SIG_SETMASK, &G.blocked_set, NULL);
}
# if ENABLE_HUSH_LOOPS
optarg++;
#endif
case 'n':
case 'x':
- if (set_mode('-', opt) == 0) /* no error */
+ if (set_mode(1, opt, NULL) == 0) /* no error */
break;
default:
#ifndef BB_VER
}
} /* option parsing loop */
+ /* Skip options. Try "hush -l": $1 should not be "-l"! */
+ G.global_argc = argc - (optind - 1);
+ G.global_argv = argv + (optind - 1);
+ G.global_argv[0] = argv[0];
+
if (!G.root_pid) {
G.root_pid = getpid();
G.root_ppid = getppid();
}
/* If we are login shell... */
- if (argv[0] && argv[0][0] == '-') {
+ if (flags & OPT_login) {
FILE *input;
debug_printf("sourcing /etc/profile\n");
input = fopen_for_read("/etc/profile");
if (input != NULL) {
close_on_exec_on(fileno(input));
- init_sigmasks();
+ install_special_sighandlers();
parse_and_run_file(input);
fclose(input);
}
*/
}
- if (argv[optind]) {
+ if (G.global_argv[1]) {
FILE *input;
/*
* "bash <script>" (which is never interactive (unless -i?))
* sources $BASH_ENV here (without scanning $PATH).
* If called as sh, does the same but with $ENV.
*/
- debug_printf("running script '%s'\n", argv[optind]);
- G.global_argv = argv + optind;
- G.global_argc = argc - optind;
- input = xfopen_for_read(argv[optind]);
+ G.global_argc--;
+ G.global_argv++;
+ debug_printf("running script '%s'\n", G.global_argv[0]);
+ input = xfopen_for_read(G.global_argv[0]);
close_on_exec_on(fileno(input));
- init_sigmasks();
+ install_special_sighandlers();
parse_and_run_file(input);
#if ENABLE_FEATURE_CLEAN_UP
fclose(input);
}
/* Up to here, shell was non-interactive. Now it may become one.
- * NB: don't forget to (re)run init_sigmasks() as needed.
+ * NB: don't forget to (re)run install_special_sighandlers() as needed.
*/
/* A shell is interactive if the '-i' flag was given,
}
}
- /* Block some signals */
- init_sigmasks();
+ /* Install more signal handlers */
+ install_special_sighandlers();
if (G_saved_tty_pgrp) {
/* Set other signals to restore saved_tty_pgrp */
- set_fatal_handlers();
+ install_fatal_sighandlers();
/* Put ourselves in our own process group
* (bash, too, does this only if ctty is available) */
bb_setpgrp(); /* is the same as setpgid(our_pid, our_pid); */
/* -1 is special - makes xfuncs longjmp, not exit
* (we reset die_sleep = 0 whereever we [v]fork) */
enable_restore_tty_pgrp_on_exit(); /* sets die_sleep = -1 */
+
+# if ENABLE_HUSH_SAVEHISTORY && MAX_HISTORY > 0
+ {
+ const char *hp = get_local_var_value("HISTFILE");
+ if (!hp) {
+ hp = get_local_var_value("HOME");
+ if (hp)
+ hp = concat_path_file(hp, ".hush_history");
+ } else {
+ hp = xstrdup(hp);
+ }
+ if (hp) {
+ G.line_input_state->hist_file = hp;
+ //set_local_var(xasprintf("HISTFILE=%s", ...));
+ }
+# if ENABLE_FEATURE_SH_HISTFILESIZE
+ hp = get_local_var_value("HISTFILESIZE");
+ G.line_input_state->max_history = size_from_HISTFILESIZE(hp);
+# endif
+ }
+# endif
} else {
- init_sigmasks();
+ install_special_sighandlers();
}
#elif ENABLE_HUSH_INTERACTIVE
/* No job control compiled in, only prompt/line editing */
if (G_interactive_fd) {
close_on_exec_on(G_interactive_fd);
}
- init_sigmasks();
+ install_special_sighandlers();
#else
/* We have interactiveness code disabled */
- init_sigmasks();
+ install_special_sighandlers();
#endif
/* bash:
* if interactive but not a login shell, sources ~/.bashrc
parse_and_run_file(stdin);
final_return:
-#if ENABLE_FEATURE_CLEAN_UP
- if (G.cwd != bb_msg_unknown)
- free((char*)G.cwd);
- cur_var = G.top_var->next;
- while (cur_var) {
- struct variable *tmp = cur_var;
- if (!cur_var->max_len)
- free(cur_var->varstr);
- cur_var = cur_var->next;
- free(tmp);
- }
-#endif
hush_exit(G.last_exitcode);
}
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)
+ * We'll need to undo trap cleanup (it's inside execvp_or_die)
* and tcsetpgrp, and this is inherently racy.
*/
execvp_or_die(argv);
* (if there are _stopped_ jobs, running ones don't count)
* # exit
* exit
- # EEE (then bash exits)
+ * EEE (then bash exits)
*
* TODO: we can use G.exiting = -1 as indicator "last cmd was exit"
*/
process_sig_list:
ret = EXIT_SUCCESS;
while (*argv) {
+ sighandler_t handler;
+
sig = get_signum(*argv++);
if (sig < 0 || sig >= NSIG) {
ret = EXIT_FAILURE;
if (sig == 0)
continue;
- if (new_cmd) {
- sigaddset(&G.blocked_set, sig);
- } else {
- /* There was a trap handler, we are removing it
- * (if sig has non-DFL handling,
- * we don't need to do anything) */
- if (sig < 32 && (G.non_DFL_mask & (1 << sig)))
- continue;
- sigdelset(&G.blocked_set, sig);
- }
+ if (new_cmd)
+ handler = (new_cmd[0] ? record_pending_signo : SIG_IGN);
+ else
+ /* We are removing trap handler */
+ handler = pick_sighandler(sig);
+ install_sighandler(sig, handler);
}
- sigprocmask(SIG_SETMASK, &G.blocked_set, NULL);
return ret;
}
debug_printf_jobs("reviving %d procs, pgrp %d\n", pi->num_cmds, pi->pgrp);
for (i = 0; i < pi->num_cmds; i++) {
debug_printf_jobs("reviving pid %d\n", pi->cmds[i].pid);
- pi->cmds[i].is_stopped = 0;
}
pi->stopped_cmds = 0;
}
#endif
+#if MAX_HISTORY && ENABLE_FEATURE_EDITING
+static int FAST_FUNC builtin_history(char **argv UNUSED_PARAM)
+{
+ show_history(G.line_input_state);
+ return EXIT_SUCCESS;
+}
+#endif
+
#if ENABLE_HUSH_JOB
static int FAST_FUNC builtin_jobs(char **argv UNUSED_PARAM)
{
return EXIT_SUCCESS;
}
+/* Interruptibility of read builtin in bash
+ * (tested on bash-4.2.8 by sending signals (not by ^C)):
+ *
+ * Empty trap makes read ignore corresponding signal, for any signal.
+ *
+ * SIGINT:
+ * - terminates non-interactive shell;
+ * - interrupts read in interactive shell;
+ * if it has non-empty trap:
+ * - executes trap and returns to command prompt in interactive shell;
+ * - executes trap and returns to read in non-interactive shell;
+ * SIGTERM:
+ * - is ignored (does not interrupt) read in interactive shell;
+ * - terminates non-interactive shell;
+ * if it has non-empty trap:
+ * - executes trap and returns to read;
+ * SIGHUP:
+ * - terminates shell (regardless of interactivity);
+ * if it has non-empty trap:
+ * - executes trap and returns to read;
+ */
static int FAST_FUNC builtin_read(char **argv)
{
const char *r;
char *opt_p = NULL;
char *opt_t = NULL;
char *opt_u = NULL;
+ const char *ifs;
int read_flags;
/* "!": do not abort on errors.
if (read_flags == (uint32_t)-1)
return EXIT_FAILURE;
argv += optind;
+ ifs = get_local_var_value("IFS"); /* can be NULL */
+ again:
r = shell_builtin_read(set_local_var_from_halves,
argv,
- get_local_var_value("IFS"), /* can be NULL */
+ ifs,
read_flags,
opt_n,
opt_p,
opt_u
);
+ if ((uintptr_t)r == 1 && errno == EINTR) {
+ unsigned sig = check_and_run_traps();
+ if (sig && sig != SIGINT)
+ goto again;
+ }
+
if ((uintptr_t)r > 1) {
bb_error_msg("%s", r);
r = (char*)(uintptr_t)1;
}
do {
- if (!strcmp(arg, "--")) {
+ if (strcmp(arg, "--") == 0) {
++argv;
goto set_argv;
}
if (arg[0] != '+' && arg[0] != '-')
break;
- for (n = 1; arg[n]; ++n)
- if (set_mode(arg[0], arg[n]))
+ for (n = 1; arg[n]; ++n) {
+ if (set_mode((arg[0] == '-'), arg[n], argv[1]))
goto error;
+ if (arg[n] == 'o' && argv[1])
+ argv++;
+ }
} while ((arg = *++argv) != NULL);
/* Now argv[0] is 1st argument */
free(arg_path);
if (!input) {
/* bb_perror_msg("%s", *argv); - done by fopen_or_warn */
+ /* POSIX: non-interactive shell should abort here,
+ * not merely fail. So far no one complained :)
+ */
return EXIT_FAILURE;
}
close_on_exec_on(fileno(input));
/* "we are inside sourced file, ok to use return" */
G.flag_return_in_progress = -1;
#endif
- save_and_replace_G_args(&sv, argv);
+ if (argv[1])
+ save_and_replace_G_args(&sv, argv);
parse_and_run_file(input);
fclose(input);
- restore_G_args(&sv, argv);
+ if (argv[1])
+ restore_G_args(&sv, argv);
#if ENABLE_HUSH_FUNCTIONS
G.flag_return_in_progress = sv_flg;
#endif
static int FAST_FUNC builtin_wait(char **argv)
{
int ret = EXIT_SUCCESS;
- int status, sig;
+ int status;
argv = skip_dash_dash(argv);
if (argv[0] == NULL) {
* ^C <-- after ~4 sec from keyboard
* $
*/
- sigaddset(&G.blocked_set, SIGCHLD);
- sigprocmask(SIG_SETMASK, &G.blocked_set, NULL);
while (1) {
- checkjobs(NULL);
- if (errno == ECHILD)
+ int sig;
+ sigset_t oldset, allsigs;
+
+ /* waitpid is not interruptible by SA_RESTARTed
+ * signals which we use. Thus, this ugly dance:
+ */
+
+ /* Make sure possible SIGCHLD is stored in kernel's
+ * pending signal mask before we call waitpid.
+ * Or else we may race with SIGCHLD, lose it,
+ * and get stuck in sigwaitinfo...
+ */
+ sigfillset(&allsigs);
+ sigprocmask(SIG_SETMASK, &allsigs, &oldset);
+
+ if (!sigisemptyset(&G.pending_set)) {
+ /* Crap! we raced with some signal! */
+ // sig = 0;
+ goto restore;
+ }
+
+ checkjobs(NULL); /* waitpid(WNOHANG) inside */
+ if (errno == ECHILD) {
+ sigprocmask(SIG_SETMASK, &oldset, NULL);
+ break;
+ }
+
+ /* Wait for SIGCHLD or any other signal */
+ //sig = sigwaitinfo(&allsigs, NULL);
+ /* It is vitally important for sigsuspend that SIGCHLD has non-DFL handler! */
+ /* Note: sigsuspend invokes signal handler */
+ sigsuspend(&oldset);
+ restore:
+ sigprocmask(SIG_SETMASK, &oldset, NULL);
+
+ /* So, did we get a signal? */
+ //if (sig > 0)
+ // raise(sig); /* run handler */
+ sig = check_and_run_traps();
+ if (sig /*&& sig != SIGCHLD - always true */) {
+ /* see note 2 */
+ ret = 128 + sig;
break;
- /* Wait for SIGCHLD or any other signal of interest */
- /* sigtimedwait with infinite timeout: */
- sig = sigwaitinfo(&G.blocked_set, NULL);
- if (sig > 0) {
- sig = check_and_run_traps(sig);
- if (sig && sig != SIGCHLD) { /* see note 2 */
- ret = 128 + sig;
- break;
- }
}
+ /* SIGCHLD, or no signal, or ignored one, such as SIGQUIT. Repeat */
}
- sigdelset(&G.blocked_set, SIGCHLD);
- sigprocmask(SIG_SETMASK, &G.blocked_set, NULL);
return ret;
}