* handle the recursion implicit in the various substitutions, especially
* across continuation lines.
*
- * POSIX syntax not implemented:
+ * TODOs:
+ * grep for "TODO" and fix (some of them are easy)
+ * special variables (done: PWD, PPID, RANDOM)
+ * tilde expansion
* aliases
- * <(list) and >(list) Process Substitution
- * Tilde Expansion
+ * follow IFS rules more precisely, including update semantics
+ * builtins mandated by standards we don't support:
+ * [un]alias, command, fc, getopts, newgrp, readonly, times
+ * make complex ${var%...} constructs support optional
+ * make here documents optional
*
- * Bash stuff (optionally enabled):
- * &> and >& redirection of stdout+stderr
- * Brace Expansion
- * reserved words: [[ ]] function select
- * substrings ${var:1:5}
+ * 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)
+ * =~: regex operator
* let EXPR [EXPR...]
- * Each EXPR is an arithmetic expression (ARITHMETIC EVALUATION)
- * If the last arg evaluates to 0, let returns 1; 0 otherwise.
- * NB: let `echo 'a=a + 1'` - error (IOW: multi-word expansion is used)
+ * Each EXPR is an arithmetic expression (ARITHMETIC EVALUATION)
+ * If the last arg evaluates to 0, let returns 1; 0 otherwise.
+ * NB: let `echo 'a=a + 1'` - error (IOW: multi-word expansion is used)
* ((EXPR))
- * The EXPR is evaluated according to ARITHMETIC EVALUATION.
- * This is exactly equivalent to let "expression".
- *
- * TODOs:
- * grep for "TODO" and fix (some of them are easy)
- * builtins: ulimit
- * special variables (done: PWD)
- * follow IFS rules more precisely, including update semantics
+ * 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
* and therefore expansion of them should be "one-word" expansion:
* $ export i=`echo 'a b'` # export has one arg: "i=a b"
#if ENABLE_HUSH_CASE
# include <fnmatch.h>
#endif
+
+#include "shell_common.h"
+#include "builtin_read.h"
+#include "builtin_ulimit.h"
#include "math.h"
#include "match.h"
#if ENABLE_HUSH_RANDOM_SUPPORT
# define USE_FOR_MMU(...)
#endif
-#if defined SINGLE_APPLET_MAIN
+#define SKIP_definitions 1
+#include "applet_tables.h"
+#undef SKIP_definitions
+#if NUM_APPLETS == 1
/* STANDALONE does not make sense, and won't compile */
# undef CONFIG_FEATURE_SH_STANDALONE
# undef ENABLE_FEATURE_SH_STANDALONE
# undef IF_FEATURE_SH_STANDALONE
+# undef IF_NOT_FEATURE_SH_STANDALONE
+# define ENABLE_FEATURE_SH_STANDALONE 0
# define IF_FEATURE_SH_STANDALONE(...)
# define IF_NOT_FEATURE_SH_STANDALONE(...) __VA_ARGS__
-# define ENABLE_FEATURE_SH_STANDALONE 0
#endif
#if !ENABLE_HUSH_INTERACTIVE
char *name;
struct command *parent_cmd;
struct pipe *body;
-#if !BB_MMU
+# if !BB_MMU
char *body_as_string;
-#endif
+# endif
};
#endif
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;
* For example, 'unset foo | whatever' will parse and run, but foo will
* still be set at the end. */
struct built_in_command {
- const char *cmd;
- int (*function)(char **argv) FAST_FUNC;
+ const char *b_cmd;
+ int (*b_function)(char **argv) FAST_FUNC;
#if ENABLE_HUSH_HELP
- const char *descr;
+ const char *b_descr;
# define BLTIN(cmd, func, help) { cmd, func, help }
#else
# define BLTIN(cmd, func, help) { cmd, func }
#endif
BLTIN("set" , builtin_set , "Set/unset positional parameters"),
BLTIN("shift" , builtin_shift , "Shift positional parameters"),
+#if ENABLE_HUSH_BASH_COMPAT
+ BLTIN("source" , builtin_source , "Run commands in a file"),
+#endif
BLTIN("trap" , builtin_trap , "Trap signals"),
- BLTIN("type" , builtin_type , "Write a description of command type"),
-// BLTIN("ulimit" , builtin_ulimit , "Control resource limits"),
+ BLTIN("type" , builtin_type , "Show command type"),
+ BLTIN("ulimit" , shell_builtin_ulimit , "Control resource limits"),
BLTIN("umask" , builtin_umask , "Set file creation mask"),
BLTIN("unset" , builtin_unset , "Unset variables"),
BLTIN("wait" , builtin_wait , "Wait for process"),
/* Utility functions
*/
-static int glob_needed(const char *s)
-{
- while (*s) {
- if (*s == '\\')
- s++;
- if (*s == '*' || *s == '[' || *s == '?')
- return 1;
- s++;
- }
- return 0;
-}
-
-static int is_well_formed_var_name(const char *s, char terminator)
-{
- if (!s || !(isalpha(*s) || *s == '_'))
- return 0;
- s++;
- while (isalnum(*s) || *s == '_')
- s++;
- return *s == terminator;
-}
-
/* Replace each \x with x in place, return ptr past NUL. */
static char *unbackslash(char *src)
{
*
* Trap handlers will execute even within trap handlers. (right?)
*
- * User trap handlers are forgotten when subshell ("(cmd)") is entered.
+ * User trap handlers are forgotten when subshell ("(cmd)") is entered,
+ * except for handlers set to '' (empty string).
*
* If job control is off, backgrounded commands ("cmd &")
* have SIGINT, SIGQUIT set to SIG_IGN.
* after [v]fork, if we plan to be a shell:
* unblock signals with special interactive handling
* (child shell is not interactive),
- * unset all traps (note: regardless of child shell's type - {}, (), etc)
+ * 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.
* Restore blocked signal set to one inherited by shell just prior to exec.
* Note 2 (compat):
* Standard says "When a subshell is entered, traps that are not being ignored
* are set to the default actions". bash interprets it so that traps which
- * are set to "" (ignore) are NOT reset to defaults. We do the same.
+ * are set to '' (ignore) are NOT reset to defaults. We do the same.
*/
enum {
SPECIAL_INTERACTIVE_SIGS = 0
}
#if ENABLE_HUSH_JOB
- fflush(NULL); /* flush all streams */
+ fflush_all();
sigexit(- (exitcode & 0xff));
#else
exit(exitcode);
return NULL;
}
-static const char *get_local_var_value(const char *name)
+static const char* FAST_FUNC get_local_var_value(const char *name)
{
struct variable **pp = get_ptr_to_local_var(name);
if (pp)
#if ENABLE_SH_MATH_SUPPORT
#define is_name(c) ((c) == '_' || isalpha((unsigned char)(c)))
#define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c)))
-static char *endofname(const char *name)
+static char* FAST_FUNC endofname(const char *name)
{
char *p;
}
return p;
}
+#endif
-static void arith_set_local_var(const char *name, const char *val, int flags)
+static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val)
{
- /* arith code doesnt malloc space, so do it for it */
char *var = xasprintf("%s=%s", name, val);
- set_local_var(var, flags, /*lvl:*/ 0, /*ro:*/ 0);
+ set_local_var(var, /*flags:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
}
-#endif
/*
do {
G.flag_SIGINT = 0;
fputs(prompt_str, stdout);
- fflush(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)
}
}
+#undef HUSH_BRACE_EXP
+/*
+ * HUSH_BRACE_EXP 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),
+ * but NOT brace expansion! Thus, there should be TWO independent
+ * quoting mechanisms on $v expansion side: one protects
+ * $v from brace expansion, and other additionally protects "$v" against globbing.
+ * We have only second one.
+ */
+
+#ifdef HUSH_BRACE_EXP
+# define MAYBE_BRACES "{}"
+#else
+# define MAYBE_BRACES ""
+#endif
+
/* My analysis of quoting semantics tells me that state information
* is associated with a destination, not a source.
*/
static void o_addqchr(o_string *o, int ch)
{
int sz = 1;
- char *found = strchr("*?[\\", ch);
+ char *found = strchr("*?[\\" MAYBE_BRACES, ch);
if (found)
sz++;
o_grow_by(o, sz);
static void o_addQchr(o_string *o, int ch)
{
int sz = 1;
- if (o->o_escape && strchr("*?[\\", ch)) {
+ if (o->o_escape && strchr("*?[\\" MAYBE_BRACES, ch)) {
sz++;
o->data[o->length] = '\\';
o->length++;
while (len) {
char ch;
int sz;
- int ordinary_cnt = strcspn(str, "*?[\\");
+ int ordinary_cnt = strcspn(str, "*?[\\" MAYBE_BRACES);
if (ordinary_cnt > len) /* paranoia */
ordinary_cnt = len;
o_addblock(o, str, ordinary_cnt);
ch = *str++;
sz = 1;
- if (ch) { /* it is necessarily one of "*?[\\" */
+ if (ch) { /* it is necessarily one of "*?[\\" MAYBE_BRACES */
sz++;
o->data[o->length] = '\\';
o->length++;
return ((int)(ptrdiff_t)list[n-1]) + string_start;
}
-/* o_glob performs globbing on last list[], saving each result
- * as a new list[]. */
+#ifdef HUSH_BRACE_EXP
+/* 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
+ * existing files. Need to re-implement it.
+ */
+
+/* Helper */
+static int glob_needed(const char *s)
+{
+ while (*s) {
+ if (*s == '\\') {
+ if (!s[1])
+ return 0;
+ s += 2;
+ continue;
+ }
+ if (*s == '*' || *s == '[' || *s == '?' || *s == '{')
+ return 1;
+ s++;
+ }
+ return 0;
+}
+/* Return pointer to next closing brace or to comma */
+static const char *next_brace_sub(const char *cp)
+{
+ unsigned depth = 0;
+ cp++;
+ while (*cp != '\0') {
+ if (*cp == '\\') {
+ if (*++cp == '\0')
+ break;
+ cp++;
+ continue;
+ }
+ /*{*/ if ((*cp == '}' && depth-- == 0) || (*cp == ',' && depth == 0))
+ break;
+ if (*cp++ == '{') /*}*/
+ depth++;
+ }
+
+ return *cp != '\0' ? cp : NULL;
+}
+/* Recursive brace globber. Note: may garble pattern[]. */
+static int glob_brace(char *pattern, o_string *o, int n)
+{
+ char *new_pattern_buf;
+ const char *begin;
+ const char *next;
+ const char *rest;
+ const char *p;
+ size_t rest_len;
+
+ debug_printf_glob("glob_brace('%s')\n", pattern);
+
+ begin = pattern;
+ while (1) {
+ if (*begin == '\0')
+ goto simple_glob;
+ if (*begin == '{') /*}*/ {
+ /* Find the first sub-pattern and at the same time
+ * find the rest after the closing brace */
+ next = next_brace_sub(begin);
+ if (next == NULL) {
+ /* An illegal expression */
+ goto simple_glob;
+ }
+ /*{*/ if (*next == '}') {
+ /* "{abc}" with no commas - illegal
+ * brace expr, disregard and skip it */
+ begin = next + 1;
+ continue;
+ }
+ break;
+ }
+ if (*begin == '\\' && begin[1] != '\0')
+ begin++;
+ begin++;
+ }
+ debug_printf_glob("begin:%s\n", begin);
+ debug_printf_glob("next:%s\n", next);
+
+ /* Now find the end of the whole brace expression */
+ rest = next;
+ /*{*/ while (*rest != '}') {
+ rest = next_brace_sub(rest);
+ if (rest == NULL) {
+ /* An illegal expression */
+ goto simple_glob;
+ }
+ debug_printf_glob("rest:%s\n", rest);
+ }
+ rest_len = strlen(++rest) + 1;
+
+ /* We are sure the brace expression is well-formed */
+
+ /* Allocate working buffer large enough for our work */
+ new_pattern_buf = xmalloc(strlen(pattern));
+
+ /* We have a brace expression. BEGIN points to the opening {,
+ * NEXT points past the terminator of the first element, and REST
+ * points past the final }. We will accumulate result names from
+ * recursive runs for each brace alternative in the buffer using
+ * GLOB_APPEND. */
+
+ p = begin + 1;
+ while (1) {
+ /* Construct the new glob expression */
+ memcpy(
+ mempcpy(
+ mempcpy(new_pattern_buf,
+ /* We know the prefix for all sub-patterns */
+ pattern, begin - pattern),
+ p, next - p),
+ rest, rest_len);
+
+ /* Note: glob_brace() may garble new_pattern_buf[].
+ * That's why we re-copy prefix every time (1st memcpy above).
+ */
+ n = glob_brace(new_pattern_buf, o, n);
+ /*{*/ if (*next == '}') {
+ /* We saw the last entry */
+ break;
+ }
+ p = next + 1;
+ next = next_brace_sub(next);
+ }
+ free(new_pattern_buf);
+ return n;
+
+ simple_glob:
+ {
+ int gr;
+ glob_t globdata;
+
+ memset(&globdata, 0, sizeof(globdata));
+ gr = glob(pattern, 0, NULL, &globdata);
+ debug_printf_glob("glob('%s'):%d\n", pattern, gr);
+ if (gr != 0) {
+ if (gr == GLOB_NOMATCH) {
+ globfree(&globdata);
+ /* NB: garbles parameter */
+ unbackslash(pattern);
+ o_addstr_with_NUL(o, pattern);
+ debug_printf_glob("glob pattern '%s' is literal\n", pattern);
+ return o_save_ptr_helper(o, n);
+ }
+ if (gr == GLOB_NOSPACE)
+ bb_error_msg_and_die(bb_msg_memory_exhausted);
+ /* GLOB_ABORTED? Only happens with GLOB_ERR flag,
+ * but we didn't specify it. Paranoia again. */
+ bb_error_msg_and_die("glob error %d on '%s'", gr, pattern);
+ }
+ if (globdata.gl_pathv && globdata.gl_pathv[0]) {
+ char **argv = globdata.gl_pathv;
+ while (1) {
+ o_addstr_with_NUL(o, *argv);
+ n = o_save_ptr_helper(o, n);
+ argv++;
+ if (!*argv)
+ break;
+ }
+ }
+ globfree(&globdata);
+ }
+ return n;
+}
+/* Performs globbing on last list[],
+ * saving each result as a new list[].
+ */
+static int o_glob(o_string *o, int n)
+{
+ char *pattern, *copy;
+
+ debug_printf_glob("start o_glob: n:%d o->data:%p\n", n, o->data);
+ if (!o->data)
+ return o_save_ptr_helper(o, n);
+ pattern = o->data + o_get_last_ptr(o, n);
+ debug_printf_glob("glob pattern '%s'\n", pattern);
+ if (!glob_needed(pattern)) {
+ /* unbackslash last string in o in place, fix length */
+ o->length = unbackslash(pattern) - o->data;
+ debug_printf_glob("glob pattern '%s' is literal\n", pattern);
+ return o_save_ptr_helper(o, n);
+ }
+
+ copy = xstrdup(pattern);
+ /* "forget" pattern in o */
+ o->length = pattern - o->data;
+ n = glob_brace(copy, o, n);
+ free(copy);
+ if (DEBUG_GLOB)
+ debug_print_list("o_glob returning", o, n);
+ return n;
+}
+
+#else
+
+/* Helper */
+static int glob_needed(const char *s)
+{
+ while (*s) {
+ if (*s == '\\') {
+ if (!s[1])
+ return 0;
+ s += 2;
+ continue;
+ }
+ if (*s == '*' || *s == '[' || *s == '?')
+ return 1;
+ s++;
+ }
+ return 0;
+}
+/* Performs globbing on last list[],
+ * saving each result as a new list[].
+ */
static int o_glob(o_string *o, int n)
{
glob_t globdata;
debug_printf_glob("glob pattern '%s'\n", pattern);
if (!glob_needed(pattern)) {
literal:
+ /* unbackslash last string in o in place, fix length */
o->length = unbackslash(pattern) - o->data;
debug_printf_glob("glob pattern '%s' is literal\n", pattern);
return o_save_ptr_helper(o, n);
}
memset(&globdata, 0, sizeof(globdata));
+ /* Can't use GLOB_NOCHECK: it does not unescape the string.
+ * If we glob "*.\*" and don't find anything, we need
+ * to fall back to using literal "*.*", but GLOB_NOCHECK
+ * will return "*.\*"!
+ */
gr = glob(pattern, 0, NULL, &globdata);
debug_printf_glob("glob('%s'):%d\n", pattern, gr);
- if (gr == GLOB_NOSPACE)
- bb_error_msg_and_die("out of memory during glob");
- if (gr == GLOB_NOMATCH) {
- globfree(&globdata);
- goto literal;
- }
- if (gr != 0) { /* GLOB_ABORTED ? */
- /* TODO: testcase for bad glob pattern behavior */
- bb_error_msg("glob(3) error %d on '%s'", gr, pattern);
+ if (gr != 0) {
+ if (gr == GLOB_NOMATCH) {
+ globfree(&globdata);
+ goto literal;
+ }
+ if (gr == GLOB_NOSPACE)
+ bb_error_msg_and_die(bb_msg_memory_exhausted);
+ /* GLOB_ABORTED? Only happens with GLOB_ERR flag,
+ * but we didn't specify it. Paranoia again. */
+ bb_error_msg_and_die("glob error %d on '%s'", gr, pattern);
}
if (globdata.gl_pathv && globdata.gl_pathv[0]) {
char **argv = globdata.gl_pathv;
- o->length = pattern - o->data; /* "forget" pattern */
+ /* "forget" pattern in o */
+ o->length = pattern - o->data;
while (1) {
o_addstr_with_NUL(o, *argv);
n = o_save_ptr_helper(o, n);
return n;
}
+#endif
+
/* If o->o_glob == 1, glob the string so far remembered.
* Otherwise, just finish current list[] and start new */
static int o_save_ptr(o_string *o, int n)
return exp_str;
}
+#if ENABLE_SH_MATH_SUPPORT
+static arith_t expand_and_evaluate_arith(const char *arg, int *errcode_p)
+{
+ arith_eval_hooks_t hooks;
+ arith_t res;
+ char *exp_str;
+
+ hooks.lookupvar = get_local_var_value;
+ hooks.setvar = set_local_var_from_halves;
+ hooks.endofname = endofname;
+ exp_str = expand_pseudo_dquoted(arg);
+ res = arith(exp_str ? exp_str : arg, errcode_p, &hooks);
+ free(exp_str);
+ return res;
+}
+#endif
+
/* Expand all variable references in given string, adding words to list[]
* at n, n+1,... positions. Return updated n (so that list[n] is next one
* to be filled). This routine is extremely tricky: has to deal with
while ((p = strchr(arg, SPECIAL_VAR_SYMBOL)) != NULL) {
char first_ch;
int i;
- char *dyn_val = NULL;
+ char *to_be_freed = NULL;
const char *val = NULL;
#if ENABLE_HUSH_TICK
o_string subst_result = NULL_O_STRING;
* expanded result may need to be globbed
* and $IFS-splitted */
debug_printf_subst("SUBST '%s' first_ch %x\n", arg, first_ch);
- process_command_subs(&subst_result, arg);
- debug_printf_subst("SUBST RES '%s'\n", subst_result.data);
+ G.last_exitcode = process_command_subs(&subst_result, arg);
+ debug_printf_subst("SUBST RES:%d '%s'\n", G.last_exitcode, subst_result.data);
val = subst_result.data;
goto store_val;
#endif
#if ENABLE_SH_MATH_SUPPORT
case '+': { /* <SPECIAL_VAR_SYMBOL>+cmd<SPECIAL_VAR_SYMBOL> */
- arith_eval_hooks_t hooks;
arith_t res;
int errcode;
- char *exp_str;
arg++; /* skip '+' */
*p = '\0'; /* replace trailing <SPECIAL_VAR_SYMBOL> */
debug_printf_subst("ARITH '%s' first_ch %x\n", arg, first_ch);
-
- exp_str = expand_pseudo_dquoted(arg);
- hooks.lookupvar = get_local_var_value;
- hooks.setvar = arith_set_local_var;
- hooks.endofname = endofname;
- res = arith(exp_str ? exp_str : arg, &errcode, &hooks);
- free(exp_str);
+ res = expand_and_evaluate_arith(arg, &errcode);
if (errcode < 0) {
const char *msg = "error in arithmetic";
#endif
default: /* <SPECIAL_VAR_SYMBOL>varname<SPECIAL_VAR_SYMBOL> */
case_default: {
- bool exp_len = false;
- bool exp_null = false;
char *var = arg;
+ char exp_len; /* '#' if it's ${#var} */
+ char exp_op;
char exp_save = exp_save; /* for compiler */
- char exp_op = exp_op; /* for compiler */
+ char *exp_saveptr = exp_saveptr; /* points to expansion operator */
char *exp_word = exp_word; /* for compiler */
- size_t exp_off = 0;
*p = '\0';
arg[0] = first_ch & 0x7f;
/* prepare for expansions */
- if (var[0] == '#') {
+ exp_op = 0;
+ exp_len = var[0];
+ if (exp_len == '#') {
/* handle length expansion ${#var} */
- exp_len = true;
- ++var;
+ var++;
} else {
/* maybe handle parameter expansion */
- exp_off = strcspn(var, ":-=+?%#");
- if (!var[exp_off])
- exp_off = 0;
- if (exp_off) {
- exp_save = var[exp_off];
- exp_null = exp_save == ':';
- exp_word = var + exp_off;
- if (exp_null)
- ++exp_word;
+ exp_saveptr = var + strcspn(var, "%#:-=+?");
+ exp_save = *exp_saveptr;
+ if (exp_save) {
+ exp_word = exp_saveptr;
+ if (exp_save == ':')
+ exp_word++;
exp_op = *exp_word++;
- var[exp_off] = '\0';
+ *exp_saveptr = '\0';
}
}
val = get_local_var_value(var);
/* handle any expansions */
- if (exp_len) {
- debug_printf_expand("expand: length of '%s' = ", val);
+ if (exp_len == '#') {
+ debug_printf_expand("expand: length(%s)=", val);
val = utoa(val ? strlen(val) : 0);
debug_printf_expand("%s\n", val);
- } else if (exp_off) {
+ } else if (exp_op) {
if (exp_op == '%' || exp_op == '#') {
+ /* Standard-mandated substring removal ops:
+ * ${parameter%word} - remove smallest suffix pattern
+ * ${parameter%%word} - remove largest suffix pattern
+ * ${parameter#word} - remove smallest prefix pattern
+ * ${parameter##word} - remove largest prefix pattern
+ *
+ * Word is expanded to produce a glob pattern.
+ * Then var's value is matched to it and matching part removed.
+ */
if (val) {
- /* we need to do a pattern match */
bool match_at_left;
char *loc;
scan_t scan = pick_scan(exp_op, *exp_word, &match_at_left);
if (exp_op == *exp_word) /* ## or %% */
- ++exp_word;
- val = dyn_val = xstrdup(val);
- loc = scan(dyn_val, exp_word, match_at_left);
- if (match_at_left) /* # or ## */
- val = loc;
- else if (loc) /* % or %% and match was found */
- *loc = '\0';
+ exp_word++;
+ val = to_be_freed = xstrdup(val);
+ {
+ char *exp_exp_word = expand_pseudo_dquoted(exp_word);
+ if (exp_exp_word)
+ exp_word = exp_exp_word;
+ loc = scan(to_be_freed, exp_word, match_at_left);
+ //bb_error_msg("op:%c str:'%s' pat:'%s' res:'%s'",
+ // exp_op, to_be_freed, exp_word, loc);
+ free(exp_exp_word);
+ }
+ if (loc) { /* match was found */
+ if (match_at_left) /* # or ## */
+ val = loc;
+ else /* % or %% */
+ *loc = '\0';
+ }
}
- } else {
- /* we need to do an expansion */
- int exp_test = (!val || (exp_null && !val[0]));
+ } else if (!strchr("%#:-=+?"+3, exp_op)) {
+#if ENABLE_HUSH_BASH_COMPAT
+ /* exp_op is ':' and next char isn't a subst operator.
+ * Assuming it's ${var:[N][:M]} bashism.
+ * TODO: N, M can be expressions similar to $((EXPR)): 2+2, 2+var etc
+ */
+ char *end;
+ unsigned len = INT_MAX;
+ unsigned beg = 0;
+ end = --exp_word;
+ if (*exp_word != ':') /* not ${var::...} */
+ beg = bb_strtou(exp_word, &end, 0);
+ //bb_error_msg("beg:'%s'=%u end:'%s'", exp_word, beg, end);
+ if (*end == ':') {
+ if (end[1] != '\0') /* not ${var:NUM:} */
+ len = bb_strtou(end + 1, &end, 0);
+ else {
+ len = 0;
+ end++;
+ }
+ //bb_error_msg("len:%u end:'%s'", len, end);
+ }
+ if (*end == '\0') {
+ //bb_error_msg("from val:'%s'", val);
+ if (len == 0 || !val || beg >= strlen(val))
+ val = "";
+ else
+ val = to_be_freed = xstrndup(val + beg, len);
+ //bb_error_msg("val:'%s'", val);
+ } else
+#endif
+ {
+ die_if_script("malformed ${%s...}", var);
+ val = "";
+ }
+ } else { /* one of "-=+?" */
+ /* Standard-mandated substitution ops:
+ * ${var?word} - indicate error if unset
+ * If var is unset, word (or a message indicating it is unset
+ * if word is null) is written to standard error
+ * and the shell exits with a non-zero exit status.
+ * Otherwise, the value of var is substituted.
+ * ${var-word} - use default value
+ * If var is unset, word is substituted.
+ * ${var=word} - assign and use default value
+ * If var is unset, word is assigned to var.
+ * In all cases, final value of var is substituted.
+ * ${var+word} - use alternative value
+ * If var is unset, null is substituted.
+ * Otherwise, word is substituted.
+ *
+ * Word is subjected to tilde expansion, parameter expansion,
+ * command substitution, and arithmetic expansion.
+ * If word is not needed, it is not expanded.
+ *
+ * Colon forms (${var:-word}, ${var:=word} etc) do the same,
+ * but also treat null var as if it is unset.
+ */
+ int use_word = (!val || ((exp_save == ':') && !val[0]));
if (exp_op == '+')
- exp_test = !exp_test;
+ use_word = !use_word;
debug_printf_expand("expand: op:%c (null:%s) test:%i\n", exp_op,
- exp_null ? "true" : "false", exp_test);
- if (exp_test) {
+ (exp_save == ':') ? "true" : "false", use_word);
+ if (use_word) {
+ to_be_freed = expand_pseudo_dquoted(exp_word);
+ if (to_be_freed)
+ exp_word = to_be_freed;
if (exp_op == '?') {
-//TODO: how interactive bash aborts expansion mid-command?
- /* ${var?[error_msg_if_unset]} */
- /* ${var:?[error_msg_if_unset_or_null]} */
/* mimic bash message */
die_if_script("%s: %s",
var,
exp_word[0] ? exp_word : "parameter null or not set"
);
+//TODO: how interactive bash aborts expansion mid-command?
} else {
val = exp_word;
}
}
}
- var[exp_off] = exp_save;
- }
+ *exp_saveptr = exp_save;
+ } /* if (exp_op) */
arg[0] = first_ch;
#if ENABLE_HUSH_TICK
if (val) {
o_addQstr(output, val, strlen(val));
}
- free(dyn_val);
+ free(to_be_freed);
/* Do the check to avoid writing to a const string */
if (*p != SPECIAL_VAR_SYMBOL)
*p = SPECIAL_VAR_SYMBOL;
char *g_argv0, char **g_argv,
char **builtin_argv)
{
- char param_buf[sizeof("-$%x:%x:%x:%x:%x") + sizeof(unsigned) * 2];
+#define NOMMU_HACK_FMT ("-$%x:%x:%x:%x:%x:%llx" IF_HUSH_LOOPS(":%x"))
+ /* delims + 2 * (number of bytes in printed hex numbers) */
+ char param_buf[sizeof(NOMMU_HACK_FMT) + 2 * (sizeof(int)*6 + sizeof(long long)*1)];
char *heredoc_argv[4];
struct variable *cur;
# if ENABLE_HUSH_FUNCTIONS
# endif
char **argv, **pp;
unsigned cnt;
+ unsigned long long empty_trap_mask;
if (!g_argv0) { /* heredoc */
argv = heredoc_argv;
if (pp) while (*pp++)
cnt++;
- sprintf(param_buf, "-$%x:%x:%x:%x:%x" IF_HUSH_LOOPS(":%x")
+ empty_trap_mask = 0;
+ if (G.traps) {
+ int sig;
+ for (sig = 1; sig < NSIG; sig++) {
+ if (G.traps[sig] && !G.traps[sig][0])
+ empty_trap_mask |= 1LL << sig;
+ }
+ }
+
+ sprintf(param_buf, NOMMU_HACK_FMT
, (unsigned) G.root_pid
, (unsigned) G.root_ppid
, (unsigned) G.last_bg_pid
, (unsigned) G.last_exitcode
, cnt
+ , empty_trap_mask
IF_HUSH_LOOPS(, G.depth_of_loop)
);
- /* 1:hush 2:-$<pid>:<pid>:<exitcode>:<depth> <vars...> <funcs...>
+#undef NOMMU_HACK_FMT
+ /* 1:hush 2:-$<pid>:<pid>:<exitcode>:<etc...> <vars...> <funcs...>
* 3:-c 4:<cmd> 5:<arg0> <argN...> 6:NULL
*/
cnt += 6;
* _inside_ group (just before echo 1), it works.
*
* I conclude it means we don't need to pass active traps here.
- * exec syscall below resets them to SIG_DFL for us.
+ * 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;
const struct built_in_command *end)
{
while (x != end) {
- if (strcmp(name, x->cmd) != 0) {
+ if (strcmp(name, x->b_cmd) != 0) {
x++;
continue;
}
G.global_argc = n;
/* On MMU, funcp->body is always non-NULL */
n = run_list(funcp->body);
- fflush(NULL);
+ fflush_all();
_exit(n);
# else
re_execute_shell(to_free,
char **argv)
{
#if BB_MMU
- int rcode = x->function(argv);
- fflush(NULL);
+ int rcode = x->b_function(argv);
+ fflush_all();
_exit(rcode);
#else
/* On NOMMU, we must never block!
}
+static void execvp_or_die(char **argv) NORETURN;
+static void execvp_or_die(char **argv)
+{
+ debug_printf_exec("execing '%s'\n", argv[0]);
+ sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
+ execvp(argv[0], argv);
+ bb_perror_msg("can't execute '%s'", argv[0]);
+ _exit(127); /* bash compat */
+}
+
#if BB_MMU
#define pseudo_exec_argv(nommu_save, argv, assignment_cnt, argv_expanded) \
pseudo_exec_argv(argv, assignment_cnt, argv_expanded)
static void pseudo_exec_argv(nommu_save_t *nommu_save,
char **argv, int assignment_cnt,
char **argv_expanded) NORETURN;
-static void pseudo_exec_argv(nommu_save_t *nommu_save,
+static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
char **argv, int assignment_cnt,
char **argv_expanded)
{
#if ENABLE_FEATURE_SH_STANDALONE || BB_MMU
skip:
#endif
- debug_printf_exec("execing '%s'\n", argv[0]);
- sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
- execvp(argv[0], argv);
- bb_perror_msg("can't execute '%s'", argv[0]);
- _exit(127); /* bash compat */
+ execvp_or_die(argv);
}
/* Called after [v]fork() in run_pipe
fg_pipe->alive_cmds--;
if (i == fg_pipe->num_cmds - 1) {
/* last process gives overall exitstatus */
- /* Note: is WIFSIGNALED, WEXITSTATUS = sig + 128 */
rcode = WEXITSTATUS(status);
- IF_HAS_KEYWORDS(if (fg_pipe->pi_inverted) rcode = !rcode;)
/* 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;)
}
} else {
fg_pipe->cmds[i].is_stopped = 1;
if (argv[command->assignment_cnt] == NULL) {
/* Assignments, but no command */
- /* Ensure redirects take effect. Try "a=t >file" */
+ /* Ensure redirects take effect (that is, create files).
+ * Try "a=t >file": */
rcode = setup_redirects(command, squirrel);
restore_redirects(squirrel);
/* Set shell variables */
set_local_var(p, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
argv++;
}
+ /* Redirect error sets $? to 1. Othervise,
+ * if evaluating assignment value set $?, retain it.
+ * Try "false; q=`exit 2`; echo $?" - should print 2: */
+ if (rcode == 0)
+ rcode = G.last_exitcode;
/* Do we need to flag set_local_var() errors?
* "assignment to readonly var" and "putenv error"
*/
/* if someone gives us an empty string: `cmd with empty output` */
if (!argv_expanded[0]) {
+ free(argv_expanded);
debug_leave();
- return 0;
+ return G.last_exitcode;
}
x = find_builtin(argv_expanded[0]);
#endif
if (x || funcp) {
if (!funcp) {
- if (x->function == builtin_exec && argv_expanded[1] == NULL) {
+ if (x->b_function == builtin_exec && argv_expanded[1] == NULL) {
debug_printf("exec with redirects only\n");
rcode = setup_redirects(command, NULL);
goto clean_up_and_ret1;
old_vars = set_vars_and_save_old(new_env);
if (!funcp) {
debug_printf_exec(": builtin '%s' '%s'...\n",
- x->cmd, argv_expanded[1]);
- rcode = x->function(argv_expanded) & 0xff;
- fflush(NULL);
+ x->b_cmd, argv_expanded[1]);
+ rcode = x->b_function(argv_expanded) & 0xff;
+ fflush_all();
}
#if ENABLE_HUSH_FUNCTIONS
else {
lvl*2, "", prn,
command->assignment_cnt);
if (command->group) {
- fprintf(stderr, " group %s: (argv=%p)\n",
+ fprintf(stderr, " group %s: (argv=%p)%s%s\n",
CMDTYPE[command->cmd_type],
- argv);
+ argv
+#if !BB_MMU
+ , " group_as_string:", command->group_as_string
+#else
+ , "", ""
+#endif
+ );
debug_print_tree(command->group, lvl+1);
prn++;
continue;
#if ENABLE_HUSH_TICK
-static FILE *generate_stream_from_string(const char *s)
+static FILE *generate_stream_from_string(const char *s, pid_t *pid_p)
{
- FILE *pf;
- int pid, channel[2];
+ pid_t pid;
+ int channel[2];
# if !BB_MMU
- char **to_free;
+ char **to_free = NULL;
# endif
xpipe(channel);
}
/* parent */
+ *pid_p = pid;
# if ENABLE_HUSH_FAST
G.count_SIGCHLD++;
//bb_error_msg("[%d] fork in generate_stream_from_string: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
free(to_free);
# endif
close(channel[1]);
- pf = fdopen(channel[0], "r");
- return pf;
+ close_on_exec_on(channel[0]);
+ return xfdopen_for_read(channel[0]);
}
/* Return code is exit status of the process that is run. */
static int process_command_subs(o_string *dest, const char *s)
{
- FILE *pf;
+ FILE *fp;
struct in_str pipe_str;
- int ch, eol_cnt;
+ pid_t pid;
+ int status, ch, eol_cnt;
- pf = generate_stream_from_string(s);
- if (pf == NULL)
- return 1;
- close_on_exec_on(fileno(pf));
+ fp = generate_stream_from_string(s, &pid);
/* Now send results of command back into original context */
- setup_file_in_str(&pipe_str, pf);
+ setup_file_in_str(&pipe_str, fp);
eol_cnt = 0;
while ((ch = i_getch(&pipe_str)) != EOF) {
if (ch == '\n') {
o_addQchr(dest, ch);
}
- debug_printf("done reading from pipe, pclose()ing\n");
- /* Note: we got EOF, and we just close the read end of the pipe.
- * We do not wait for the `cmd` child to terminate. bash and ash do.
- * Try these:
- * echo `echo Hi; exec 1>&-; sleep 2` - bash waits 2 sec
- * `false`; echo $? - bash outputs "1"
- */
- fclose(pf);
- debug_printf("closed FILE from child. return 0\n");
- return 0;
+ debug_printf("done reading from `cmd` pipe, closing it\n");
+ fclose(fp);
+ /* We need to extract exitcode. Test case
+ * "true; echo `sleep 1; false` $?"
+ * should print 1 */
+ safe_waitpid(pid, &status, 0);
+ debug_printf("child exited. returning its exitcode:%d\n", WEXITSTATUS(status));
+ return WEXITSTATUS(status);
}
#endif /* ENABLE_HUSH_TICK */
+#if !ENABLE_HUSH_FUNCTIONS
+#define parse_group(dest, ctx, input, ch) \
+ parse_group(ctx, input, ch)
+#endif
static int parse_group(o_string *dest, struct parse_context *ctx,
struct in_str *input, int ch)
{
goto skip;
}
#endif
+
+#if 0 /* Prevented by caller */
if (command->argv /* word [word]{... */
|| dest->length /* word{... */
|| dest->o_quoted /* ""{... */
"syntax error, groups and arglists don't mix\n");
return 1;
}
+#endif
#if ENABLE_HUSH_FUNCTIONS
skip:
* echo $(echo '(TEST)' BEST) (TEST) BEST
* echo $(echo 'TEST)' BEST) TEST) BEST
* echo $(echo \(\(TEST\) BEST) ((TEST) BEST
+ *
+ * Also adapted to eat ${var%...} constructs, since ... part
+ * can contain arbitrary constructs, just like $(cmd).
*/
-static void add_till_closing_paren(o_string *dest, struct in_str *input, bool dbl)
+#define DOUBLE_CLOSE_CHAR_FLAG 0x80
+static void add_till_closing_paren(o_string *dest, struct in_str *input, char end_ch)
{
- int count = 0;
+ char dbl = end_ch & DOUBLE_CLOSE_CHAR_FLAG;
+ end_ch &= (DOUBLE_CLOSE_CHAR_FLAG-1);
while (1) {
int ch = i_getch(input);
if (ch == EOF) {
- syntax_error_unterm_ch(')');
+ syntax_error_unterm_ch(end_ch);
/*xfunc_die(); - redundant */
}
- if (ch == '(')
- count++;
- if (ch == ')') {
- if (--count < 0) {
- if (!dbl)
- break;
- if (i_peek(input) == ')') {
- i_getch(input);
- break;
- }
+ if (ch == end_ch) {
+ if (!dbl)
+ break;
+ /* we look for closing )) of $((EXPR)) */
+ if (i_peek(input) == end_ch) {
+ i_getch(input); /* eat second ')' */
+ break;
}
}
o_addchr(dest, ch);
+ if (ch == '(' || ch == '{') {
+ ch = (ch == '(' ? ')' : '}');
+ add_till_closing_paren(dest, input, ch);
+ o_addchr(dest, ch);
+ continue;
+ }
if (ch == '\'') {
add_till_single_quote(dest, input);
o_addchr(dest, ch);
o_addchr(dest, ch);
continue;
}
+ if (ch == '`') {
+ add_till_backquote(dest, input);
+ o_addchr(dest, ch);
+ continue;
+ }
if (ch == '\\') {
/* \x. Copy verbatim. Important for \(, \) */
ch = i_getch(input);
case '@': /* args */
goto make_one_char_var;
case '{': {
- bool first_char, all_digits;
- int expansion;
+ o_addchr(dest, SPECIAL_VAR_SYMBOL);
- ch = i_getch(input);
+ ch = i_getch(input); /* eat '{' */
nommu_addchr(as_string, ch);
- o_addchr(dest, SPECIAL_VAR_SYMBOL);
- /* TODO: maybe someone will try to escape the '}' */
- expansion = 0;
- first_char = true;
- all_digits = false;
+ ch = i_getch(input); /* first char after '{' */
+ nommu_addchr(as_string, ch);
+ /* It should be ${?}, or ${#var},
+ * or even ${?+subst} - operator acting on a special variable,
+ * or the beginning of variable name.
+ */
+ if (!strchr("$!?#*@_", ch) && !isalnum(ch)) { /* not one of those */
+ bad_dollar_syntax:
+ syntax_error_unterm_str("${name}");
+ debug_printf_parse("handle_dollar return 1: unterminated ${name}\n");
+ return 1;
+ }
+ ch |= quote_mask;
+
+ /* It's possible to just call add_till_closing_paren() at this point.
+ * However, this regresses some of our testsuite cases
+ * which check invalid constructs like ${%}.
+ * Oh well... let's check that the var name part is fine... */
+
while (1) {
+ o_addchr(dest, ch);
+ debug_printf_parse(": '%c'\n", ch);
+
ch = i_getch(input);
nommu_addchr(as_string, ch);
- if (ch == '}') {
+ if (ch == '}')
break;
- }
- if (first_char) {
- if (ch == '#') {
- /* ${#var}: length of var contents */
- goto char_ok;
- }
- if (isdigit(ch)) {
- all_digits = true;
- goto char_ok;
- }
- /* They're being verbose and doing ${?} */
- if (i_peek(input) == '}' && strchr("$!?#*@_", ch))
- goto char_ok;
- }
-
- if (expansion < 2
- && ( (all_digits && !isdigit(ch))
- || (!all_digits && !isalnum(ch) && ch != '_')
- )
- ) {
+ if (!isalnum(ch) && ch != '_') {
/* handle parameter expansions
* http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02
*/
- if (first_char)
- goto case_default;
- switch (ch) {
- case ':': /* null modifier */
- if (expansion == 0) {
- debug_printf_parse(": null modifier\n");
- ++expansion;
- break;
- }
- goto case_default;
- case '#': /* remove prefix */
- case '%': /* remove suffix */
- if (expansion == 0) {
- debug_printf_parse(": remove suffix/prefix\n");
- expansion = 2;
- break;
- }
- goto case_default;
- case '-': /* default value */
- case '=': /* assign default */
- case '+': /* alternative */
- case '?': /* error indicate */
- debug_printf_parse(": parameter expansion\n");
- expansion = 2;
- break;
- default:
- case_default:
- syntax_error_unterm_str("${name}");
- debug_printf_parse("handle_dollar return 1: unterminated ${name}\n");
- return 1;
- }
+ if (!strchr("%#:-=+?", ch)) /* ${var<bad_char>... */
+ goto bad_dollar_syntax;
+ /* Eat everything until closing '}' */
+ o_addchr(dest, ch);
+//TODO: add nommu_addchr hack here
+ add_till_closing_paren(dest, input, '}');
+ break;
}
- char_ok:
- debug_printf_parse(": '%c'\n", ch);
- o_addchr(dest, ch | quote_mask);
- quote_mask = 0;
- first_char = false;
- } /* while (1) */
+ }
o_addchr(dest, SPECIAL_VAR_SYMBOL);
break;
}
# if !BB_MMU
pos = dest->length;
# endif
- add_till_closing_paren(dest, input, true);
+ add_till_closing_paren(dest, input, ')' | DOUBLE_CLOSE_CHAR_FLAG);
# if !BB_MMU
if (as_string) {
o_addstr(as_string, dest->data + pos);
# if !BB_MMU
pos = dest->length;
# endif
- add_till_closing_paren(dest, input, false);
+ add_till_closing_paren(dest, input, ')');
# if !BB_MMU
if (as_string) {
o_addstr(as_string, dest->data + pos);
- o_addchr(as_string, '`');
+ o_addchr(as_string, ')');
}
# endif
o_addchr(dest, SPECIAL_VAR_SYMBOL);
G.ifs = get_local_var_value("IFS");
if (G.ifs == NULL)
- G.ifs = " \t\n";
+ G.ifs = defifs;
reset:
#if ENABLE_HUSH_INTERACTIVE
return pi;
}
nommu_addchr(&ctx.as_string, ch);
+
+ next = '\0';
+ if (ch != '\n')
+ next = i_peek(input);
+
+ is_special = "{}<>;&|()#'" /* special outside of "str" */
+ "\\$\"" IF_HUSH_TICK("`"); /* always special */
+ /* Are { and } special here? */
+ if (ctx.command->argv /* word [word]{... - non-special */
+ || dest.length /* word{... - non-special */
+ || dest.o_quoted /* ""{... - non-special */
+ || (next != ';' /* }; - special */
+ && next != ')' /* }) - special */
+ && next != '&' /* }& and }&& ... - special */
+ && next != '|' /* }|| ... - special */
+ && !strchr(G.ifs, next) /* {word - non-special */
+ )
+ ) {
+ /* They are not special, skip "{}" */
+ is_special += 2;
+ }
+ is_special = strchr(is_special, ch);
is_ifs = strchr(G.ifs, ch);
- is_special = strchr("<>;&|(){}#'" /* special outside of "str" */
- "\\$\"" IF_HUSH_TICK("`") /* always special */
- , ch);
if (!is_special && !is_ifs) { /* ordinary char */
ordinary_char:
if (is_ifs)
continue;
- next = '\0';
- if (ch != '\n') {
- next = i_peek(input);
- }
-
/* Catch <, > before deciding whether this word is
* an assignment. a=1 2>z b=2: b=2 is still assignment */
switch (ch) {
* with redirect_opt_num(), but bash doesn't do it.
* "echo foo 2| cat" yields "foo 2". */
done_command(&ctx);
+#if !BB_MMU
+ o_reset_to_empty_unquoted(&ctx.as_string);
+#endif
}
goto new_cmd;
case '(':
*/
static void parse_and_run_stream(struct in_str *inp, int end_trigger)
{
+ /* Why we need empty flag?
+ * An obscure corner case "false; ``; echo $?":
+ * empty command in `` should still set $? to 0.
+ * But we can't just set $? to 0 at the start,
+ * this breaks "false; echo `echo $?`" case.
+ */
+ bool empty = 1;
while (1) {
struct pipe *pipe_list;
pipe_list = parse_stream(NULL, inp, end_trigger);
- if (!pipe_list) /* EOF */
+ if (!pipe_list) { /* EOF */
+ if (empty)
+ G.last_exitcode = 0;
break;
+ }
debug_print_tree(pipe_list, 0);
debug_printf_exec("parse_and_run_stream: run_and_free_list\n");
run_and_free_list(pipe_list);
+ empty = 0;
}
}
}
/* Called a few times only (or even once if "sh -c") */
-static void block_signals(int second_time)
+static void init_sigmasks(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);
if (G_interactive_fd) {
}
G.non_DFL_mask = mask;
- if (!second_time)
- sigprocmask(SIG_SETMASK, NULL, &G.blocked_set);
sig = 0;
while (mask) {
if (mask & 1)
}
sigdelset(&G.blocked_set, SIGCHLD);
- sigprocmask(SIG_SETMASK, &G.blocked_set,
- second_time ? NULL : &G.inherited_set);
+ 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 (!second_time)
+ if (!G.inherited_set_is_saved)
signal(SIGCHLD, SIGCHLD_handler);
#else
- if (!second_time)
+ if (!G.inherited_set_is_saved)
signal(SIGCHLD, SIG_DFL);
#endif
+
+ G.inherited_set_is_saved = 1;
}
#if ENABLE_HUSH_JOB
.flg_export = 1,
.flg_read_only = 1,
};
- int signal_mask_is_inited = 0;
int opt;
unsigned builtin_argc;
char **e;
struct variable *cur_var;
INIT_G();
- if (EXIT_SUCCESS) /* if EXIT_SUCCESS == 0, is already done */
+ if (EXIT_SUCCESS) /* if EXIT_SUCCESS == 0, it is already done */
G.last_exitcode = EXIT_SUCCESS;
#if !BB_MMU
G.argv0_for_re_execing = argv[0];
}
/* Shell is non-interactive at first. We need to call
- * block_signals(0) if we are going to execute "sh <script>",
+ * init_sigmasks() 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 block_signals(0)
- * (or re-run block_signals(1) if we ran block_signals(0) before)
+ * If we later decide that we are interactive, we run init_sigmasks()
* in order to intercept (more) signals.
*/
* sh ... -c 'script'
* sh ... -c 'script' ARG0 [ARG1...]
* On NOMMU, if builtin_argc != 0,
- * sh ... -c 'builtin' [BARGV...] "" ARG0 [ARG1...]
+ * sh ... -c 'builtin' BARGV... "" ARG0 [ARG1...]
* "" needs to be replaced with NULL
* and BARGV vector fed to builtin function.
- * Note: this form never happens:
- * sh ... -c 'builtin' [BARGV...] ""
+ * Note: the form without ARG0 never happens:
+ * sh ... -c 'builtin' BARGV... ""
*/
if (!G.root_pid) {
G.root_pid = getpid();
/* -c 'builtin' [BARGV...] "" ARG0 [ARG1...] */
const struct built_in_command *x;
- block_signals(0); /* 0: called 1st time */
+ init_sigmasks();
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 "" */
- G.last_exitcode = x->function(argv + optind - 1);
+ 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 */
- block_signals(0); /* 0: called 1st time */
+ init_sigmasks();
parse_and_run_string(optarg);
goto final_return;
case 'i':
case '<': /* "big heredoc" support */
full_write(STDOUT_FILENO, optarg, strlen(optarg));
_exit(0);
- case '$':
+ case '$': {
+ unsigned long long empty_trap_mask;
+
G.root_pid = bb_strtou(optarg, &optarg, 16);
optarg++;
G.root_ppid = bb_strtou(optarg, &optarg, 16);
G.last_exitcode = bb_strtou(optarg, &optarg, 16);
optarg++;
builtin_argc = bb_strtou(optarg, &optarg, 16);
+ optarg++;
+ empty_trap_mask = bb_strtoull(optarg, &optarg, 16);
+ if (empty_trap_mask != 0) {
+ int sig;
+ init_sigmasks();
+ 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);
+ }
+ }
+ sigprocmask(SIG_SETMASK, &G.blocked_set, NULL);
+ }
# if ENABLE_HUSH_LOOPS
optarg++;
G.depth_of_loop = bb_strtou(optarg, &optarg, 16);
# endif
break;
+ }
case 'R':
case 'V':
set_local_var(xstrdup(optarg), /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ opt == 'R');
input = fopen_for_read("/etc/profile");
if (input != NULL) {
close_on_exec_on(fileno(input));
- block_signals(0); /* 0: called 1st time */
- signal_mask_is_inited = 1;
+ init_sigmasks();
parse_and_run_file(input);
fclose(input);
}
G.global_argc = argc - optind;
input = xfopen_for_read(argv[optind]);
close_on_exec_on(fileno(input));
- if (!signal_mask_is_inited)
- block_signals(0); /* 0: called 1st time */
+ init_sigmasks();
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 block_signals(0/1) as needed.
+ * NB: don't forget to (re)run init_sigmasks() as needed.
*/
/* A shell is interactive if the '-i' flag was given,
}
/* Block some signals */
- block_signals(signal_mask_is_inited);
+ init_sigmasks();
if (G_saved_tty_pgrp) {
/* Set other signals to restore saved_tty_pgrp */
/* -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 */
- } else if (!signal_mask_is_inited) {
- block_signals(0); /* 0: called 1st time */
- } /* else: block_signals(0) was done before */
+ } else {
+ init_sigmasks();
+ }
#elif ENABLE_HUSH_INTERACTIVE
/* No job control compiled in, only prompt/line editing */
if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) {
}
if (G_interactive_fd) {
close_on_exec_on(G_interactive_fd);
- block_signals(signal_mask_is_inited);
- } else if (!signal_mask_is_inited) {
- block_signals(0);
}
+ init_sigmasks();
#else
/* We have interactiveness code disabled */
- if (!signal_mask_is_inited) {
- block_signals(0);
- }
+ init_sigmasks();
#endif
/* bash:
* if interactive but not a login shell, sources ~/.bashrc
}
#endif
+static char **skip_dash_dash(char **argv)
+{
+ argv++;
+ if (argv[0] && argv[0][0] == '-' && argv[0][1] == '-' && argv[0][2] == '\0')
+ argv++;
+ return argv;
+}
+
static int FAST_FUNC builtin_eval(char **argv)
{
int rcode = EXIT_SUCCESS;
- if (*++argv) {
+ argv = skip_dash_dash(argv);
+ if (*argv) {
char *str = expand_strvec_to_string(argv);
/* bash:
* eval "echo Hi; done" ("done" is syntax error):
static int FAST_FUNC builtin_cd(char **argv)
{
- const char *newdir = argv[1];
+ const char *newdir;
+
+ argv = skip_dash_dash(argv);
+ newdir = argv[0];
if (newdir == NULL) {
/* bash does nothing (exitcode 0) if HOME is ""; if it's unset,
* bash says "bash: cd: HOME not set" and does nothing
static int FAST_FUNC builtin_exec(char **argv)
{
- static const char pseudo_null_str[] = { SPECIAL_VAR_SYMBOL, SPECIAL_VAR_SYMBOL, '\0' };
- char **pp;
-#if !BB_MMU
- nommu_save_t dummy;
-#endif
-
- if (*++argv == NULL)
+ argv = skip_dash_dash(argv);
+ if (argv[0] == NULL)
return EXIT_SUCCESS; /* bash does this */
- /* Make sure empty arguments aren't ignored */
- /* Example: exec ls '' */
- pp = argv;
- while (*pp) {
- if ((*pp)[0] == '\0')
- *pp = (char*)pseudo_null_str;
- pp++;
- }
-
/* Careful: we can end up here after [v]fork. Do not restore
* tty pgrp then, only top-level shell process does that */
if (G_saved_tty_pgrp && getpid() == G.root_pid)
tcsetpgrp(G_interactive_fd, G_saved_tty_pgrp);
- /* TODO: if exec fails, bash does NOT exit! We do... */
- pseudo_exec_argv(&dummy, argv, 0, NULL);
- /* never returns */
+ /* TODO: if exec fails, bash does NOT exit! We do.
+ * We'll need to undo sigprocmask (it's inside execvp_or_die)
+ * and tcsetpgrp, and this is inherently racy.
+ */
+ execvp_or_die(argv);
}
static int FAST_FUNC builtin_exit(char **argv)
*/
/* note: EXIT trap is run by hush_exit */
- if (*++argv == NULL)
+ argv = skip_dash_dash(argv);
+ if (argv[0] == NULL)
hush_exit(G.last_exitcode);
/* mimic bash: exit 123abc == exit 255 + error msg */
xfunc_error_retval = 255;
/* bash: exit -2 == exit 254, no error msg */
- hush_exit(xatoi(*argv) & 0xff);
+ hush_exit(xatoi(argv[0]) & 0xff);
}
static void print_escaped(const char *s)
putchar('\n');
#endif
}
- /*fflush(stdout); - done after each builtin anyway */
+ /*fflush_all(); - done after each builtin anyway */
}
return EXIT_SUCCESS;
}
printf(" %s\n", get_signame(i));
}
}
- /*fflush(stdout); - done after each builtin anyway */
+ /*fflush_all(); - done after each builtin anyway */
return EXIT_SUCCESS;
}
free(G.traps[sig]);
G.traps[sig] = xstrdup(new_cmd);
- debug_printf("trap: setting SIG%s (%i) to '%s'",
+ debug_printf("trap: setting SIG%s (%i) to '%s'\n",
get_signame(sig), sig, G.traps[sig]);
/* There is no signal for 0 (EXIT) */
"Built-in commands:\n"
"------------------\n");
for (x = bltins1; x != &bltins1[ARRAY_SIZE(bltins1)]; x++) {
- if (x->descr)
- printf("%s\t%s\n", x->cmd, x->descr);
+ if (x->b_descr)
+ printf("%-10s%s\n", x->b_cmd, x->b_descr);
}
bb_putchar('\n');
return EXIT_SUCCESS;
static int FAST_FUNC builtin_read(char **argv)
{
- char *string;
- const char *name = "REPLY";
+ const char *r;
+ char *opt_n = NULL;
+ char *opt_p = NULL;
+ char *opt_t = NULL;
+ char *opt_u = NULL;
+ int read_flags;
- if (argv[1]) {
- name = argv[1];
- /* bash (3.2.33(1)) bug: "read 0abcd" will execute,
- * and _after_ that_ it will complain */
- if (!is_well_formed_var_name(name, '\0')) {
- /* Mimic bash message */
- bb_error_msg("read: '%s': not a valid identifier", name);
- return 1;
- }
- }
+ /* "!": do not abort on errors.
+ * Option string must start with "sr" to match BUILTIN_READ_xxx
+ */
+ read_flags = getopt32(argv, "!srn:p:t:u:", &opt_n, &opt_p, &opt_t, &opt_u);
+ if (read_flags == (uint32_t)-1)
+ return EXIT_FAILURE;
+ argv += optind;
+
+ r = shell_builtin_read(set_local_var_from_halves,
+ argv,
+ get_local_var_value("IFS"), /* can be NULL */
+ read_flags,
+ opt_n,
+ opt_p,
+ opt_t,
+ opt_u
+ );
-//TODO: bash unbackslashes input, splits words and puts them in argv[i]
+ if ((uintptr_t)r > 1) {
+ bb_error_msg("%s", r);
+ r = (char*)(uintptr_t)1;
+ }
- string = xmalloc_reads(STDIN_FILENO, xasprintf("%s=", name), NULL);
- return set_local_var(string, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
+ return (uintptr_t)r;
}
/* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#set
static int FAST_FUNC builtin_shift(char **argv)
{
int n = 1;
- if (argv[1]) {
- n = atoi(argv[1]);
+ argv = skip_dash_dash(argv);
+ if (argv[0]) {
+ n = atoi(argv[0]);
}
if (n >= 0 && n < G.global_argc) {
if (G.global_args_malloced) {
static int FAST_FUNC builtin_source(char **argv)
{
- char *arg_path;
+ char *arg_path, *filename;
FILE *input;
save_arg_t sv;
#if ENABLE_HUSH_FUNCTIONS
smallint sv_flg;
#endif
- if (*++argv == NULL)
- return EXIT_FAILURE;
-
- if (strchr(*argv, '/') == NULL && (arg_path = find_in_path(*argv)) != NULL) {
- input = fopen_for_read(arg_path);
- free(arg_path);
- } else
- input = fopen_or_warn(*argv, "r");
+ argv = skip_dash_dash(argv);
+ filename = argv[0];
+ if (!filename) {
+ /* bash says: "bash: .: filename argument required" */
+ return 2; /* bash compat */
+ }
+ arg_path = NULL;
+ if (!strchr(filename, '/')) {
+ arg_path = find_in_path(filename);
+ if (arg_path)
+ filename = arg_path;
+ }
+ input = fopen_or_warn(filename, "r");
+ free(arg_path);
if (!input) {
/* bb_perror_msg("%s", *argv); - done by fopen_or_warn */
return EXIT_FAILURE;
mode_t mask;
mask = umask(0);
- if (argv[1]) {
+ argv = skip_dash_dash(argv);
+ if (argv[0]) {
mode_t old_mask = mask;
mask ^= 0777;
- rc = bb_parse_mode(argv[1], &mask);
+ rc = bb_parse_mode(argv[0], &mask);
mask ^= 0777;
if (rc == 0) {
mask = old_mask;
* bash: umask: 'q': invalid symbolic mode operator
* bash: umask: 999: octal number out of range
*/
- bb_error_msg("%s: '%s' invalid mode", argv[0], argv[1]);
+ bb_error_msg("%s: invalid mode '%s'", "umask", argv[0]);
}
} else {
rc = 1;
int ret = EXIT_SUCCESS;
int status, sig;
- if (*++argv == NULL) {
+ argv = skip_dash_dash(argv);
+ if (argv[0] == NULL) {
/* Don't care about wait results */
/* Note 1: must wait until there are no more children */
/* Note 2: must be interruptible */