* <(list) and >(list) Process Substitution
* Tilde Expansion
*
- * Bash stuff (maybe optionally enable?):
+ * Bash stuff (optionally enabled):
* &> and >& redirection of stdout+stderr
- * Brace expansion
+ * Brace Expansion
* reserved words: [[ ]] function select
* substrings ${var:1:5}
+ * 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)
+ * ((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 (PWD etc)
* follow IFS rules more precisely, including update semantics
+ * 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"
+ * compare with:
+ * $ ls i=`echo 'a b'` # ls has two args: "i=a" and "b"
+ * ls: cannot access i=a: No such file or directory
+ * ls: cannot access b: No such file or directory
+ * Note1: same applies to local builtin.
+ * Note2: bash 3.2.33(1) does this only if export word itself
+ * is not quoted:
+ * $ export i=`echo 'aaa bbb'`; echo "$i"
+ * aaa bbb
+ * $ "export" i=`echo 'aaa bbb'`; echo "$i"
+ * aaa
*
* Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
*/
} nommu_save_t;
#endif
-/* The descrip member of this structure is only used to make
- * debugging output pretty */
-static const struct {
- int mode;
- signed char default_fd;
- char descrip[3];
-} redir_table[] = {
- { 0, 0, "??" },
- { O_RDONLY, 0, "<" },
- { O_CREAT|O_TRUNC|O_WRONLY, 1, ">" },
- { O_CREAT|O_APPEND|O_WRONLY, 1, ">>" },
- { O_RDONLY, 0, "<<" },
- { O_CREAT|O_RDWR, 1, "<>" },
-/* Should not be needed. Bogus default_fd helps in debugging */
-/* { O_RDONLY, 77, "<<" }, */
-};
-
typedef enum reserved_style {
RES_NONE = 0,
#if ENABLE_HUSH_IF
smallint promptmode; /* 0: PS1, 1: PS2 */
#endif
FILE *file;
- int (*get) (struct in_str *);
- int (*peek) (struct in_str *);
+ int (*get) (struct in_str *) FAST_FUNC;
+ int (*peek) (struct in_str *) FAST_FUNC;
} in_str;
#define i_getch(input) ((input)->get(input))
#define i_peek(input) ((input)->peek(input))
+/* The descrip member of this structure is only used to make
+ * debugging output pretty */
+static const struct {
+ int mode;
+ signed char default_fd;
+ char descrip[3];
+} redir_table[] = {
+ { O_RDONLY, 0, "<" },
+ { O_CREAT|O_TRUNC|O_WRONLY, 1, ">" },
+ { O_CREAT|O_APPEND|O_WRONLY, 1, ">>" },
+ { O_CREAT|O_RDWR, 1, "<>" },
+ { O_RDONLY, 0, "<<" },
+/* Should not be needed. Bogus default_fd helps in debugging */
+/* { O_RDONLY, 77, "<<" }, */
+};
+
struct redir_struct {
struct redir_struct *next;
char *rd_filename; /* filename */
smallint rd_type; /* (enum redir_type) */
/* note: for heredocs, rd_filename contains heredoc delimiter,
* and subsequently heredoc itself; and rd_dup is a bitmask:
- * 1: do we need to trim leading tabs?
- * 2: is heredoc quoted (<<'delim' syntax) ?
+ * bit 0: do we need to trim leading tabs?
+ * bit 1: is heredoc quoted (<<'delim' syntax) ?
*/
};
typedef enum redir_type {
- REDIRECT_INVALID = 0,
- REDIRECT_INPUT = 1,
- REDIRECT_OVERWRITE = 2,
- REDIRECT_APPEND = 3,
+ REDIRECT_INPUT = 0,
+ REDIRECT_OVERWRITE = 1,
+ REDIRECT_APPEND = 2,
+ REDIRECT_IO = 3,
REDIRECT_HEREDOC = 4,
- REDIRECT_IO = 5,
- REDIRECT_HEREDOC2 = 6, /* REDIRECT_HEREDOC after heredoc is loaded */
+ REDIRECT_HEREDOC2 = 5, /* REDIRECT_HEREDOC after heredoc is loaded */
REDIRFD_CLOSE = -3,
REDIRFD_SYNTAX_ERR = -2,
pid_t pid; /* 0 if exited */
int assignment_cnt; /* how many argv[i] are assignments? */
smallint is_stopped; /* is the command currently running? */
- smallint grp_type; /* GRP_xxx */
-#define GRP_NORMAL 0
-#define GRP_SUBSHELL 1
+ smallint cmd_type; /* CMD_xxx */
+#define CMD_NORMAL 0
+#define CMD_SUBSHELL 1
+
+/* used for "[[ EXPR ]]" */
+#if ENABLE_HUSH_BASH_COMPAT
+# define CMD_SINGLEWORD_NOGLOB 2
+#endif
+
+/* used for "export noglob=* glob* a=`echo a b`" */
+//#define CMD_SINGLEWORD_NOGLOB_COND 3
+// It is hard to implement correctly, it adds significant amounts of tricky code,
+// and all this is only useful for really obscure export statements
+// almost nobody would use anyway. #ifdef CMD_SINGLEWORD_NOGLOB_COND
+// guards the code which implements it, but I have doubts it works
+// in all cases (especially with mixed globbed/non-globbed arguments)
+
#if ENABLE_HUSH_FUNCTIONS
-# define GRP_FUNCTION 2
+# define CMD_FUNCDEF 3
#endif
+
/* if non-NULL, this "command" is { list }, ( list ), or a compound statement */
struct pipe *group;
#if !BB_MMU
#if ENABLE_HUSH_FUNCTIONS
struct function *child_func;
/* This field is used to prevent a bug here:
- * while...do f1() {a;}; f1; f1 {b;}; f1; done
+ * while...do f1() {a;}; f1; f1() {b;}; f1; done
* When we execute "f1() {a;}" cmd, we create new function and clear
* cmd->group, cmd->group_as_string, cmd->argv[0].
- * when we execute "f1 {b;}", we notice that f1 exists,
- * and that it's "parent cmd" struct is still "alive",
+ * When we execute "f1() {b;}", we notice that f1 exists,
+ * and that its "parent cmd" struct is still "alive",
* we put those fields back into cmd->xxx
* (struct function has ->parent_cmd ptr to facilitate that).
* When we loop back, we can execute "f1() {a;}" again and set f1 correctly.
struct variable {
struct variable *next;
char *varstr; /* points to "name=" portion */
+#if ENABLE_HUSH_LOCAL
+ unsigned func_nest_level;
+#endif
int max_len; /* if > 0, name is part of initial env; else name is malloced */
smallint flg_export; /* putenv should be done on this var */
smallint flg_read_only;
struct variable shell_ver;
#if ENABLE_HUSH_FUNCTIONS
struct function *top_func;
+# if ENABLE_HUSH_LOCAL
+ struct variable **shadowed_vars_pp;
+ unsigned func_nest_level;
+# endif
#endif
/* Signal and trap handling */
#if ENABLE_HUSH_FAST
/* Function prototypes for builtins */
-static int builtin_cd(char **argv);
-static int builtin_echo(char **argv);
-static int builtin_eval(char **argv);
-static int builtin_exec(char **argv);
-static int builtin_exit(char **argv);
-static int builtin_export(char **argv);
+static int builtin_cd(char **argv) FAST_FUNC;
+static int builtin_echo(char **argv) FAST_FUNC;
+static int builtin_eval(char **argv) FAST_FUNC;
+static int builtin_exec(char **argv) FAST_FUNC;
+static int builtin_exit(char **argv) FAST_FUNC;
+static int builtin_export(char **argv) FAST_FUNC;
#if ENABLE_HUSH_JOB
-static int builtin_fg_bg(char **argv);
-static int builtin_jobs(char **argv);
+static int builtin_fg_bg(char **argv) FAST_FUNC;
+static int builtin_jobs(char **argv) FAST_FUNC;
#endif
#if ENABLE_HUSH_HELP
-static int builtin_help(char **argv);
+static int builtin_help(char **argv) FAST_FUNC;
+#endif
+#if ENABLE_HUSH_LOCAL
+static int builtin_local(char **argv) FAST_FUNC;
#endif
#if HUSH_DEBUG
-static int builtin_memleak(char **argv);
-#endif
-static int builtin_pwd(char **argv);
-static int builtin_read(char **argv);
-static int builtin_set(char **argv);
-static int builtin_shift(char **argv);
-static int builtin_source(char **argv);
-static int builtin_test(char **argv);
-static int builtin_trap(char **argv);
-static int builtin_true(char **argv);
-static int builtin_umask(char **argv);
-static int builtin_unset(char **argv);
-static int builtin_wait(char **argv);
+static int builtin_memleak(char **argv) FAST_FUNC;
+#endif
+static int builtin_pwd(char **argv) FAST_FUNC;
+static int builtin_read(char **argv) FAST_FUNC;
+static int builtin_set(char **argv) FAST_FUNC;
+static int builtin_shift(char **argv) FAST_FUNC;
+static int builtin_source(char **argv) FAST_FUNC;
+static int builtin_test(char **argv) FAST_FUNC;
+static int builtin_trap(char **argv) FAST_FUNC;
+static int builtin_type(char **argv) FAST_FUNC;
+static int builtin_true(char **argv) FAST_FUNC;
+static int builtin_umask(char **argv) FAST_FUNC;
+static int builtin_unset(char **argv) FAST_FUNC;
+static int builtin_wait(char **argv) FAST_FUNC;
#if ENABLE_HUSH_LOOPS
-static int builtin_break(char **argv);
-static int builtin_continue(char **argv);
+static int builtin_break(char **argv) FAST_FUNC;
+static int builtin_continue(char **argv) FAST_FUNC;
#endif
#if ENABLE_HUSH_FUNCTIONS
-static int builtin_return(char **argv);
+static int builtin_return(char **argv) FAST_FUNC;
#endif
/* Table of built-in functions. They can be forked or not, depending on
* still be set at the end. */
struct built_in_command {
const char *cmd;
- int (*function)(char **argv);
+ int (*function)(char **argv) FAST_FUNC;
#if ENABLE_HUSH_HELP
const char *descr;
-#define BLTIN(cmd, func, help) { cmd, func, help }
+# define BLTIN(cmd, func, help) { cmd, func, help }
#else
-#define BLTIN(cmd, func, help) { cmd, func }
+# define BLTIN(cmd, func, help) { cmd, func }
#endif
};
-/* For now, echo and test are unconditionally enabled.
- * Maybe make it configurable? */
-static const struct built_in_command bltins[] = {
- BLTIN("." , builtin_source , "Run commands in a file"),
- BLTIN(":" , builtin_true , "No-op"),
- BLTIN("[" , builtin_test , "Test condition"),
+static const struct built_in_command bltins1[] = {
+ BLTIN("." , builtin_source , "Run commands in a file"),
+ BLTIN(":" , builtin_true , NULL),
#if ENABLE_HUSH_JOB
- BLTIN("bg" , builtin_fg_bg , "Resume a job in the background"),
+ BLTIN("bg" , builtin_fg_bg , "Resume a job in the background"),
#endif
#if ENABLE_HUSH_LOOPS
- BLTIN("break" , builtin_break , "Exit from a loop"),
+ BLTIN("break" , builtin_break , "Exit from a loop"),
#endif
- BLTIN("cd" , builtin_cd , "Change directory"),
+ BLTIN("cd" , builtin_cd , "Change directory"),
#if ENABLE_HUSH_LOOPS
- BLTIN("continue", builtin_continue, "Start new loop iteration"),
+ BLTIN("continue" , builtin_continue, "Start new loop iteration"),
#endif
- BLTIN("echo" , builtin_echo , "Write to stdout"),
- BLTIN("eval" , builtin_eval , "Construct and run shell command"),
- BLTIN("exec" , builtin_exec , "Execute command, don't return to shell"),
- BLTIN("exit" , builtin_exit , "Exit"),
- BLTIN("export" , builtin_export , "Set environment variable"),
+ BLTIN("eval" , builtin_eval , "Construct and run shell command"),
+ BLTIN("exec" , builtin_exec , "Execute command, don't return to shell"),
+ BLTIN("exit" , builtin_exit , "Exit"),
+ BLTIN("export" , builtin_export , "Set environment variables"),
#if ENABLE_HUSH_JOB
- BLTIN("fg" , builtin_fg_bg , "Bring job into the foreground"),
+ BLTIN("fg" , builtin_fg_bg , "Bring job into the foreground"),
#endif
#if ENABLE_HUSH_HELP
- BLTIN("help" , builtin_help , "List shell built-in commands"),
+ BLTIN("help" , builtin_help , NULL),
#endif
#if ENABLE_HUSH_JOB
- BLTIN("jobs" , builtin_jobs , "List active jobs"),
+ BLTIN("jobs" , builtin_jobs , "List jobs"),
+#endif
+#if ENABLE_HUSH_LOCAL
+ BLTIN("local" , builtin_local , "Set local variables"),
#endif
#if HUSH_DEBUG
- BLTIN("memleak" , builtin_memleak , "Debug tool"),
+ BLTIN("memleak" , builtin_memleak , NULL),
#endif
- BLTIN("pwd" , builtin_pwd , "Print current directory"),
- BLTIN("read" , builtin_read , "Input environment variable"),
+ BLTIN("read" , builtin_read , "Input into variable"),
#if ENABLE_HUSH_FUNCTIONS
- BLTIN("return" , builtin_return , "Return from a function"),
-#endif
- BLTIN("set" , builtin_set , "Set/unset shell local variables"),
- BLTIN("shift" , builtin_shift , "Shift positional parameters"),
- BLTIN("test" , builtin_test , "Test condition"),
- BLTIN("trap" , builtin_trap , "Trap signals"),
-// BLTIN("ulimit" , builtin_return , "Control resource limits"),
- BLTIN("umask" , builtin_umask , "Set file creation mask"),
- BLTIN("unset" , builtin_unset , "Unset environment variable"),
- BLTIN("wait" , builtin_wait , "Wait for process"),
+ BLTIN("return" , builtin_return , "Return from a function"),
+#endif
+ BLTIN("set" , builtin_set , "Set/unset positional parameters"),
+ BLTIN("shift" , builtin_shift , "Shift positional parameters"),
+ BLTIN("trap" , builtin_trap , "Trap signals"),
+ BLTIN("type" , builtin_type , "Write a description of command type"),
+// BLTIN("ulimit" , 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"),
+};
+/* For now, echo and test are unconditionally enabled.
+ * Maybe make it configurable? */
+static const struct built_in_command bltins2[] = {
+ BLTIN("[" , builtin_test , NULL),
+ BLTIN("echo" , builtin_echo , NULL),
+ BLTIN("pwd" , builtin_pwd , NULL),
+ BLTIN("test" , builtin_test , NULL),
};
# define debug_enter() (G.debug_indent++)
# define debug_leave() (G.debug_indent--)
#else
-# define indent() ((void)0)
+# define indent() ((void)0)
# define debug_enter() ((void)0)
# define debug_leave() ((void)0)
#endif
fprintf(stderr, " '%s'\n", *vv++);
}
#else
-#define debug_print_strings(prefix, vv) ((void)0)
+# define debug_print_strings(prefix, vv) ((void)0)
#endif
die_if_script(lineno, "syntax error at '%s'", msg);
}
+static void syntax_error_unterm_str(unsigned lineno, const char *s)
+{
+ die_if_script(lineno, "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,
static void syntax_error_unterm_ch(unsigned lineno, char ch) NORETURN;
static void syntax_error_unterm_ch(unsigned lineno, char ch)
{
- char msg[2];
- msg[0] = ch;
- msg[1] = '\0';
- die_if_script(lineno, "syntax error: unterminated %s", msg);
+ char msg[2] = { ch, '\0' };
+ syntax_error_unterm_str(lineno, msg);
xfunc_die();
}
-static void syntax_error_unterm_str(unsigned lineno, const char *s)
-{
- die_if_script(lineno, "syntax error: unterminated %s", s);
-}
-
static void syntax_error_unexpected_ch(unsigned lineno, int ch)
{
char msg[2];
#if ENABLE_HUSH_INTERACTIVE
static void cmdedit_update_prompt(void);
#else
-# define cmdedit_update_prompt()
+# define cmdedit_update_prompt() ((void)0)
#endif
* If job control is off, backgrounded commands ("cmd &")
* have SIGINT, SIGQUIT set to SIG_IGN.
*
- * Commands run in command substitution ("`cmd`")
+ * Commands which are run in command substitution ("`cmd`")
* have SIGTTIN, SIGTTOU, SIGTSTP set to SIG_IGN.
*
- * Ordinary commands have signals set to SIG_IGN/DFL set as inherited
+ * Ordinary commands have signals set to SIG_IGN/DFL as inherited
* by the shell from its parent.
*
- * Siganls which differ from SIG_DFL action
+ * Signals which differ from SIG_DFL action
* (note: child (i.e., [v]forked) shell is not an interactive shell):
*
* SIGQUIT: ignore
}
-static const char *set_cwd(void)
+static const char *get_cwd(int force)
{
- /* xrealloc_getcwd_or_warn(arg) calls free(arg),
- * we must not try to free(bb_msg_unknown) */
- if (G.cwd == bb_msg_unknown)
- G.cwd = NULL;
- G.cwd = xrealloc_getcwd_or_warn((char *)G.cwd);
- if (!G.cwd)
- G.cwd = bb_msg_unknown;
+ if (force || G.cwd == NULL) {
+ /* xrealloc_getcwd_or_warn(arg) calls free(arg),
+ * we must not try to free(bb_msg_unknown) */
+ if (G.cwd == bb_msg_unknown)
+ G.cwd = NULL;
+ G.cwd = xrealloc_getcwd_or_warn((char *)G.cwd);
+ if (!G.cwd)
+ G.cwd = bb_msg_unknown;
+ }
return G.cwd;
}
* -1: clear export flag and unsetenv the variable
* flg_read_only is set only when we handle -R var=val
*/
-#if BB_MMU
-#define set_local_var(str, flg_export, flg_read_only) \
+#if !BB_MMU && ENABLE_HUSH_LOCAL
+/* all params are used */
+#elif BB_MMU && ENABLE_HUSH_LOCAL
+#define set_local_var(str, flg_export, local_lvl, flg_read_only) \
+ set_local_var(str, flg_export, local_lvl)
+#elif BB_MMU && !ENABLE_HUSH_LOCAL
+#define set_local_var(str, flg_export, local_lvl, flg_read_only) \
set_local_var(str, flg_export)
+#elif !BB_MMU && !ENABLE_HUSH_LOCAL
+#define set_local_var(str, flg_export, local_lvl, flg_read_only) \
+ set_local_var(str, flg_export, flg_read_only)
#endif
-static int set_local_var(char *str, int flg_export, int flg_read_only)
+static int set_local_var(char *str, int flg_export, int local_lvl, int flg_read_only)
{
+ struct variable **var_pp;
struct variable *cur;
char *eq_sign;
int name_len;
}
name_len = eq_sign - str + 1; /* including '=' */
- cur = G.top_var; /* cannot be NULL (we have HUSH_VERSION and it's RO) */
- while (1) {
+ var_pp = &G.top_var;
+ while ((cur = *var_pp) != NULL) {
if (strncmp(cur->varstr, str, name_len) != 0) {
- if (!cur->next) {
- /* Bail out. Note that now cur points
- * to last var in linked list */
- break;
- }
- cur = cur->next;
+ var_pp = &cur->next;
continue;
}
/* We found an existing var with this name */
free(str);
return -1;
}
- if (flg_export == -1) {
+ if (flg_export == -1) { // "&& cur->flg_export" ?
debug_printf_env("%s: unsetenv '%s'\n", __func__, str);
*eq_sign = '\0';
unsetenv(str);
*eq_sign = '=';
}
+#if ENABLE_HUSH_LOCAL
+ if (cur->func_nest_level < local_lvl) {
+ /* New variable is declared as local,
+ * and existing one is global, or local
+ * from enclosing function.
+ * Remove and save old one: */
+ *var_pp = cur->next;
+ cur->next = *G.shadowed_vars_pp;
+ *G.shadowed_vars_pp = cur;
+ /* bash 3.2.33(1) and exported vars:
+ * # export z=z
+ * # f() { local z=a; env | grep ^z; }
+ * # f
+ * z=a
+ * # env | grep ^z
+ * z=z
+ */
+ if (cur->flg_export)
+ flg_export = 1;
+ break;
+ }
+#endif
if (strcmp(cur->varstr + name_len, eq_sign + 1) == 0) {
free_and_exp:
free(str);
goto exp;
}
- if (cur->max_len >= strlen(str)) {
- /* This one is from startup env, reuse space */
- strcpy(cur->varstr, str);
- goto free_and_exp;
- }
- /* max_len == 0 signifies "malloced" var, which we can
- * (and has to) free */
- if (!cur->max_len)
+ if (cur->max_len != 0) {
+ if (cur->max_len >= strlen(str)) {
+ /* This one is from startup env, reuse space */
+ strcpy(cur->varstr, str);
+ goto free_and_exp;
+ }
+ } else {
+ /* max_len == 0 signifies "malloced" var, which we can
+ * (and has to) free */
free(cur->varstr);
+ }
cur->max_len = 0;
goto set_str_and_exp;
}
- /* Not found - create next variable struct */
- cur->next = xzalloc(sizeof(*cur));
- cur = cur->next;
+ /* Not found - create new variable struct */
+ cur = xzalloc(sizeof(*cur));
+#if ENABLE_HUSH_LOCAL
+ cur->func_nest_level = local_lvl;
+#endif
+ cur->next = *var_pp;
+ *var_pp = cur;
set_str_and_exp:
cur->varstr = str;
{
/* arith code doesnt malloc space, so do it for it */
char *var = xasprintf("%s=%s", name, val);
- set_local_var(var, flags, 0);
+ set_local_var(var, flags, /*lvl:*/ 0, /*ro:*/ 0);
}
#endif
debug_printf_env("%s: restoring exported '%s'\n", __func__, var->varstr);
putenv(var->varstr);
} else {
- debug_printf_env("%s: restoring local '%s'\n", __func__, var->varstr);
+ debug_printf_env("%s: restoring variable '%s'\n", __func__, var->varstr);
}
var = next;
}
var_p->next = old;
old = var_p;
}
- set_local_var(*s, 1, 0);
+ set_local_var(*s, /*exp:*/ 1, /*lvl:*/ 0, /*ro:*/ 0);
}
s++;
}
/*
* in_str support
*/
-static int static_get(struct in_str *i)
+static int FAST_FUNC static_get(struct in_str *i)
{
int ch = *i->p++;
if (ch != '\0')
return EOF;
}
-static int static_peek(struct in_str *i)
+static int FAST_FUNC static_peek(struct in_str *i)
{
return *i->p;
}
/* Set up the prompt */
if (promptmode == 0) { /* PS1 */
free((char*)G.PS1);
- G.PS1 = xasprintf("%s %c ", G.cwd, (geteuid() != 0) ? '$' : '#');
+ G.PS1 = xasprintf("%s %c ", get_cwd(0), (geteuid() != 0) ? '$' : '#');
prompt_str = G.PS1;
} else
prompt_str = G.PS2;
/* This is the magic location that prints prompts
* and gets data back from the user */
-static int file_get(struct in_str *i)
+static int FAST_FUNC file_get(struct in_str *i)
{
int ch;
/* All callers guarantee this routine will never
* be used right after a newline, so prompting is not needed.
*/
-static int file_peek(struct in_str *i)
+static int FAST_FUNC file_peek(struct in_str *i)
{
int ch;
if (i->p && *i->p) {
o_addchr(o, ch);
}
#else
-#define nommu_addchr(o, str) ((void)0)
+# define nommu_addchr(o, str) ((void)0)
#endif
static void o_addstr_with_NUL(o_string *o, const char *str)
}
}
#else
-#define debug_print_list(prefix, o, n) ((void)0)
+# define debug_print_list(prefix, o, n) ((void)0)
#endif
/* n = o_save_ptr_helper(str, n) "starts new string" by storing an index value
* 'echo -$*-'. If you play here, you must run testsuite afterwards! */
static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
{
- /* or_mask is either 0 (normal case) or 0x80
- * (expansion of right-hand side of assignment == 1-element expand.
- * It will also do no globbing, and thus we must not backslash-quote!) */
-
+ /* or_mask is either 0 (normal case) or 0x80 -
+ * expansion of right-hand side of assignment == 1-element expand.
+ * It will also do no globbing, and thus we must not backslash-quote!
+ */
char ored_ch;
char *p;
val = NULL;
} else {
char *new_var = xasprintf("%s=%s", var, val);
- set_local_var(new_var, 0, 0);
+ set_local_var(new_var, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
}
}
}
n = 0;
v = argv;
while (*v) {
- n = expand_vars_to_list(&output, n, *v, (char)or_mask);
+ n = expand_vars_to_list(&output, n, *v, (unsigned char)or_mask);
v++;
}
debug_print_list("expand_variables", &output, n);
return expand_variables(argv, 0x100);
}
+#if ENABLE_HUSH_BASH_COMPAT
+static char **expand_strvec_to_strvec_singleword_noglob(char **argv)
+{
+ return expand_variables(argv, 0x80);
+}
+#endif
+
+#ifdef CMD_SINGLEWORD_NOGLOB_COND
+static char **expand_strvec_to_strvec_singleword_noglob_cond(char **argv)
+{
+ int n;
+ char **list;
+ char **v;
+ o_string output = NULL_O_STRING;
+
+ n = 0;
+ v = argv;
+ while (*v) {
+ int is_var = is_well_formed_var_name(*v, '=');
+ /* is_var * 0x80: singleword expansion for vars */
+ n = expand_vars_to_list(&output, n, *v, is_var * 0x80);
+
+ /* Subtle! expand_vars_to_list did not glob last word yet.
+ * It does this only when fed with further data.
+ * Therefore we set globbing flags AFTER it, not before:
+ */
+
+ /* if it is not recognizably abc=...; then: */
+ output.o_escape = !is_var; /* protect against globbing for "$var" */
+ /* (unquoted $var will temporarily switch it off) */
+ output.o_glob = !is_var; /* and indeed do globbing */
+ v++;
+ }
+ debug_print_list("expand_cond", &output, n);
+
+ /* output.data (malloced in one block) gets returned in "list" */
+ list = o_finalize_list(&output, n);
+ debug_print_strings("expand_cond[1]", list);
+ return list;
+}
+#endif
+
/* Used for expansion of right hand of assignments */
/* NB: should NOT do globbing! "export v=/bin/c*; env | grep ^v=" outputs
* "v=/bin/c*" */
argv[0] = (char*)str;
argv[1] = NULL;
- list = expand_variables(argv, 0x80); /* 0x80: make one-element expansion */
+ list = expand_variables(argv, 0x80); /* 0x80: singleword expansion */
if (HUSH_DEBUG)
if (!list[0] || list[1])
bb_error_msg_and_die("BUG in varexp2");
/* actually, just move string 2*sizeof(char*) bytes back */
overlapping_strcpy((char*)list, list[0]);
+ unbackslash((char*)list);
debug_printf_expand("string_to_string='%s'\n", (char*)list);
return (char*)list;
}
#if BB_MMU
/* never called */
-void re_execute_shell(char ***to_free, const char *s, char *argv0, char **argv);
+void re_execute_shell(char ***to_free, const char *s,
+ char *g_argv0, char **g_argv,
+ char **builtin_argv) NORETURN;
static void reset_traps_to_defaults(void)
{
#else /* !BB_MMU */
-static void re_execute_shell(char ***to_free, const char *s, char *g_argv0, char **g_argv) NORETURN;
-static void re_execute_shell(char ***to_free, const char *s, char *g_argv0, char **g_argv)
+static void re_execute_shell(char ***to_free, const char *s,
+ char *g_argv0, char **g_argv,
+ char **builtin_argv) NORETURN;
+static void re_execute_shell(char ***to_free, const char *s,
+ char *g_argv0, char **g_argv,
+ char **builtin_argv)
{
- char param_buf[sizeof("-$%x:%x:%x:%x") + sizeof(unsigned) * 4];
+ char param_buf[sizeof("-$%x:%x:%x:%x:%x") + sizeof(unsigned) * 2];
char *heredoc_argv[4];
struct variable *cur;
#if ENABLE_HUSH_FUNCTIONS
goto do_exec;
}
- sprintf(param_buf, "-$%x:%x:%x" IF_HUSH_LOOPS(":%x")
+ cnt = 0;
+ pp = builtin_argv;
+ if (pp) while (*pp++)
+ cnt++;
+
+ sprintf(param_buf, "-$%x:%x:%x:%x" IF_HUSH_LOOPS(":%x")
, (unsigned) G.root_pid
, (unsigned) G.last_bg_pid
, (unsigned) G.last_exitcode
+ , cnt
IF_HUSH_LOOPS(, G.depth_of_loop)
);
/* 1:hush 2:-$<pid>:<pid>:<exitcode>:<depth> <vars...> <funcs...>
* 3:-c 4:<cmd> 5:<arg0> <argN...> 6:NULL
*/
- cnt = 6;
+ cnt += 6;
for (cur = G.top_var; cur; cur = cur->next) {
if (!cur->flg_export || cur->flg_read_only)
cnt += 2;
*/
*pp++ = (char *) "-c";
*pp++ = (char *) s;
+ if (builtin_argv) {
+ while (*++builtin_argv)
+ *pp++ = *builtin_argv;
+ *pp++ = (char *) "";
+ }
*pp++ = g_argv0;
while (*g_argv)
*pp++ = *g_argv++;
#else
/* Delegate blocking writes to another process */
xmove_fd(pair.wr, STDOUT_FILENO);
- re_execute_shell(&to_free, heredoc, NULL, NULL);
+ re_execute_shell(&to_free, heredoc, NULL, NULL, NULL);
#endif
}
/* parent */
}
/* not "else if": on syntax error, we may have both! */
if (command->group) {
- debug_printf_clean(" begin group (grp_type:%d)\n",
- command->grp_type);
+ debug_printf_clean(" begin group (cmd_type:%d)\n",
+ command->cmd_type);
free_pipe_list(command->group);
debug_printf_clean(" end group\n");
command->group = NULL;
static void parse_and_run_string(const char *s);
-static const struct built_in_command* find_builtin(const char *name)
+static char *find_in_path(const char *arg)
{
- const struct built_in_command *x;
- for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
- if (strcmp(name, x->cmd) != 0)
+ char *ret = NULL;
+ const char *PATH = get_local_var_value("PATH");
+
+ if (!PATH)
+ return NULL;
+
+ while (1) {
+ const char *end = strchrnul(PATH, ':');
+ int sz = end - PATH; /* must be int! */
+
+ free(ret);
+ if (sz != 0) {
+ ret = xasprintf("%.*s/%s", sz, PATH, arg);
+ } else {
+ /* We have xxx::yyyy in $PATH,
+ * it means "use current dir" */
+ ret = xstrdup(arg);
+ }
+ if (access(ret, F_OK) == 0)
+ break;
+
+ if (*end == '\0') {
+ free(ret);
+ return NULL;
+ }
+ PATH = end + 1;
+ }
+
+ return ret;
+}
+
+static const struct built_in_command* find_builtin_helper(const char *name,
+ const struct built_in_command *x,
+ const struct built_in_command *end)
+{
+ while (x != end) {
+ if (strcmp(name, x->cmd) != 0) {
+ x++;
continue;
+ }
debug_printf_exec("found builtin '%s'\n", name);
return x;
}
return NULL;
}
+static const struct built_in_command* find_builtin1(const char *name)
+{
+ return find_builtin_helper(name, bltins1, &bltins1[ARRAY_SIZE(bltins1)]);
+}
+static const struct built_in_command* find_builtin(const char *name)
+{
+ const struct built_in_command *x = find_builtin1(name);
+ if (x)
+ return x;
+ return find_builtin_helper(name, bltins2, &bltins2[ARRAY_SIZE(bltins2)]);
+}
#if ENABLE_HUSH_FUNCTIONS
static const struct function *find_function(const char *name)
}
# if BB_MMU
-#define exec_function(nommu_save, funcp, argv) \
+#define exec_function(to_free, funcp, argv) \
exec_function(funcp, argv)
# endif
-static void exec_function(nommu_save_t *nommu_save,
+static void exec_function(char ***to_free,
const struct function *funcp,
char **argv) NORETURN;
-static void exec_function(nommu_save_t *nommu_save,
+static void exec_function(char ***to_free,
const struct function *funcp,
char **argv)
{
fflush(NULL);
_exit(n);
# else
- re_execute_shell(&nommu_save->argv_from_re_execing,
+ re_execute_shell(to_free,
funcp->body_as_string,
G.global_argv[0],
- argv + 1);
+ argv + 1,
+ NULL);
# endif
}
smallint sv_flg;
save_and_replace_G_args(&sv, argv);
+
/* "we are in function, ok to use return" */
sv_flg = G.flag_return_in_progress;
G.flag_return_in_progress = -1;
+#if ENABLE_HUSH_LOCAL
+ G.func_nest_level++;
+#endif
/* On MMU, funcp->body is always non-NULL */
# if !BB_MMU
rc = run_list(funcp->body);
}
+#if ENABLE_HUSH_LOCAL
+ {
+ struct variable *var;
+ struct variable **var_pp;
+
+ var_pp = &G.top_var;
+ while ((var = *var_pp) != NULL) {
+ if (var->func_nest_level < G.func_nest_level) {
+ var_pp = &var->next;
+ continue;
+ }
+ /* Unexport */
+ if (var->flg_export)
+ bb_unsetenv(var->varstr);
+ /* Remove from global list */
+ *var_pp = var->next;
+ /* Free */
+ if (!var->max_len)
+ free(var->varstr);
+ free(var);
+ }
+ G.func_nest_level--;
+ }
+#endif
G.flag_return_in_progress = sv_flg;
+
restore_G_args(&sv, argv);
return rc;
#endif /* ENABLE_HUSH_FUNCTIONS */
+# if BB_MMU
+#define exec_builtin(to_free, x, argv) \
+ exec_builtin(x, argv)
+# else
+#define exec_builtin(to_free, x, argv) \
+ exec_builtin(to_free, argv)
+# endif
+static void exec_builtin(char ***to_free,
+ const struct built_in_command *x,
+ char **argv) NORETURN;
+static void exec_builtin(char ***to_free,
+ const struct built_in_command *x,
+ char **argv)
+{
+# if BB_MMU
+ int rcode = x->function(argv);
+ fflush(NULL);
+ _exit(rcode);
+# else
+ /* On NOMMU, we must never block!
+ * Example: { sleep 99 | read line; } & echo Ok
+ */
+ re_execute_shell(to_free,
+ argv[0],
+ G.global_argv[0],
+ G.global_argv + 1,
+ argv);
+# endif
+}
+
+
#if BB_MMU
#define pseudo_exec_argv(nommu_save, argv, assignment_cnt, argv_expanded) \
pseudo_exec_argv(argv, assignment_cnt, argv_expanded)
goto skip;
#endif
- /* On NOMMU, we must never block!
- * Example: { sleep 99999 | read line } & echo Ok
- * read builtin will block on read syscall, leaving parent blocked
- * in vfork. Therefore we can't do this:
- */
-#if BB_MMU
/* Check if the command matches any of the builtins.
* Depending on context, this might be redundant. But it's
* easier to waste a few CPU cycles than it is to figure out
* if this is one of those cases.
*/
{
- int rcode;
- const struct built_in_command *x = find_builtin(argv[0]);
+ /* On NOMMU, it is more expensive to re-execute shell
+ * just in order to run echo or test builtin.
+ * It's better to skip it here and run corresponding
+ * non-builtin later. */
+ const struct built_in_command *x;
+ x = BB_MMU ? find_builtin(argv[0]) : find_builtin1(argv[0]);
if (x) {
- rcode = x->function(argv);
- fflush(NULL);
- _exit(rcode);
+ exec_builtin(&nommu_save->argv_from_re_execing, x, argv);
}
}
-#endif
#if ENABLE_HUSH_FUNCTIONS
/* Check if the command matches any functions */
{
const struct function *funcp = find_function(argv[0]);
if (funcp) {
- exec_function(nommu_save, funcp, argv);
+ exec_function(&nommu_save->argv_from_re_execing, funcp, argv);
}
}
#endif
re_execute_shell(&nommu_save->argv_from_re_execing,
command->group_as_string,
G.global_argv[0],
- G.global_argv + 1);
+ G.global_argv + 1,
+ NULL);
#endif
}
#endif
return rcode;
}
+ if (!fg_pipe->alive_cmds)
+ return rcode;
}
/* There are still running processes in the fg pipe */
goto wait_more; /* do waitpid again */
if (pi->num_cmds != 1
|| pi->followup == PIPE_BG
- || command->grp_type == GRP_SUBSHELL
+ || command->cmd_type == CMD_SUBSHELL
) {
goto must_fork;
}
if (command->group) {
#if ENABLE_HUSH_FUNCTIONS
- if (command->grp_type == GRP_FUNCTION) {
+ if (command->cmd_type == CMD_FUNCDEF) {
/* "executing" func () { list } */
struct function *funcp;
p = expand_string_to_string(*argv);
debug_printf_exec("set shell var:'%s'->'%s'\n",
*argv, p);
- set_local_var(p, 0, 0);
+ set_local_var(p, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
argv++;
}
/* Do we need to flag set_local_var() errors?
}
/* Expand the rest into (possibly) many strings each */
- argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt);
+ if (0) {}
+#if ENABLE_HUSH_BASH_COMPAT
+ else if (command->cmd_type == CMD_SINGLEWORD_NOGLOB) {
+ argv_expanded = expand_strvec_to_strvec_singleword_noglob(argv + command->assignment_cnt);
+ }
+#endif
+#ifdef CMD_SINGLEWORD_NOGLOB_COND
+ else if (command->cmd_type == CMD_SINGLEWORD_NOGLOB_COND) {
+ argv_expanded = expand_strvec_to_strvec_singleword_noglob_cond(argv + command->assignment_cnt);
+
+ }
+#endif
+ else {
+ argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt);
+ }
x = find_builtin(argv_expanded[0]);
#if ENABLE_HUSH_FUNCTIONS
}
#if ENABLE_HUSH_FUNCTIONS
else {
+# if ENABLE_HUSH_LOCAL
+ struct variable **sv;
+ sv = G.shadowed_vars_pp;
+ G.shadowed_vars_pp = &old_vars;
+# endif
debug_printf_exec(": function '%s' '%s'...\n",
funcp->name, argv_expanded[1]);
rcode = run_function(funcp, argv_expanded) & 0xff;
+# if ENABLE_HUSH_LOCAL
+ G.shadowed_vars_pp = sv;
+# endif
}
#endif
}
[RES_XXXX ] = "XXXX" ,
[RES_SNTX ] = "SNTX" ,
};
- static const char *const GRPTYPE[] = {
+ static const char *const CMDTYPE[] = {
"{}",
"()",
+ "[noglob]",
#if ENABLE_HUSH_FUNCTIONS
"func()",
#endif
command->assignment_cnt);
if (command->group) {
fprintf(stderr, " group %s: (argv=%p)\n",
- GRPTYPE[command->grp_type],
+ CMDTYPE[command->cmd_type],
argv);
debug_print_tree(command->group, lvl+1);
prn++;
#endif
#if ENABLE_HUSH_LOOPS
struct pipe *loop_top = NULL;
- char *for_varname = NULL;
char **for_lcur = NULL;
char **for_list = NULL;
#endif
for_list = expand_strvec_to_strvec(vals);
for_lcur = for_list;
debug_print_strings("for_list", for_list);
- for_varname = pi->cmds[0].argv[0];
- pi->cmds[0].argv[0] = NULL;
}
- free(pi->cmds[0].argv[0]);
if (!*for_lcur) {
/* "for" loop is over, clean up */
free(for_list);
for_list = NULL;
for_lcur = NULL;
- pi->cmds[0].argv[0] = for_varname;
break;
}
/* Insert next value from for_lcur */
/* note: *for_lcur already has quotes removed, $var expanded, etc */
- pi->cmds[0].argv[0] = xasprintf("%s=%s", for_varname, *for_lcur++);
- pi->cmds[0].assignment_cnt = 1;
+ set_local_var(xasprintf("%s=%s", pi->cmds[0].argv[0], *for_lcur++), /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
+ continue;
}
if (rword == RES_IN) {
continue; /* "for v IN list;..." - "in" has no cmds anyway */
debug_printf_parse("pop stack %p\n", ctx->stack);
old = ctx->stack;
old->command->group = ctx->list_head;
- old->command->grp_type = GRP_NORMAL;
+ old->command->cmd_type = CMD_NORMAL;
#if !BB_MMU
o_addstr(&old->as_string, ctx->as_string.data);
o_free_unsafe(&ctx->as_string);
* Same with heredocs:
* for <<\H delim is H; <<\\H, <<"\H", <<"\\H" - \H
*/
- unbackslash(ctx->pending_redirect->rd_filename);
- /* Is it <<"HEREDOC"? */
- if (ctx->pending_redirect->rd_type == REDIRECT_HEREDOC
- && word->o_quoted
- ) {
- ctx->pending_redirect->rd_dup |= HEREDOC_QUOTED;
+ if (ctx->pending_redirect->rd_type == REDIRECT_HEREDOC) {
+ unbackslash(ctx->pending_redirect->rd_filename);
+ /* Is it <<"HEREDOC"? */
+ if (word->o_quoted) {
+ ctx->pending_redirect->rd_dup |= HEREDOC_QUOTED;
+ }
}
debug_printf_parse("word stored in rd_filename: '%s'\n", word->data);
ctx->pending_redirect = NULL;
(ctx->ctx_res_w == RES_SNTX));
return (ctx->ctx_res_w == RES_SNTX);
}
+# ifdef CMD_SINGLEWORD_NOGLOB_COND
+ if (strcmp(word->data, "export") == 0
+# if ENABLE_HUSH_LOCAL
+ || strcmp(word->data, "local") == 0
+# endif
+ ) {
+ command->cmd_type = CMD_SINGLEWORD_NOGLOB_COND;
+ } else
+# endif
+# if ENABLE_HUSH_BASH_COMPAT
+ if (strcmp(word->data, "[[") == 0) {
+ command->cmd_type = CMD_SINGLEWORD_NOGLOB;
+ }
+ /* fall through */
+# endif
}
#endif
if (command->group) {
char *p;
redir->rd_type = REDIRECT_HEREDOC2;
- /* redir->dup is (ab)used to indicate <<- */
+ /* redir->rd_dup is (ab)used to indicate <<- */
p = fetch_till_str(&ctx->as_string, input,
redir->rd_filename, redir->rd_dup & HEREDOC_SKIPTABS);
if (!p) {
re_execute_shell(&to_free,
s,
G.global_argv[0],
- G.global_argv + 1);
+ G.global_argv + 1,
+ NULL);
#endif
}
return 1;
}
nommu_addchr(&ctx->as_string, ch);
- command->grp_type = GRP_FUNCTION;
+ command->cmd_type = CMD_FUNCDEF;
goto skip;
}
#endif
endch = '}';
if (ch == '(') {
endch = ')';
- command->grp_type = GRP_SUBSHELL;
+ command->cmd_type = CMD_SUBSHELL;
} else {
/* bash does not allow "{echo...", requires whitespace */
ch = i_getch(input);
o_string *dest,
struct in_str *input)
{
- int expansion;
int ch = i_peek(input); /* first character after the $ */
unsigned char quote_mask = dest->o_escape ? 0x80 : 0;
goto make_one_char_var;
case '{': {
bool first_char, all_digits;
+ int expansion;
- o_addchr(dest, SPECIAL_VAR_SYMBOL);
ch = i_getch(input);
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 = true;
goto char_ok;
}
+ /* They're being verbose and doing ${?} */
+ if (i_peek(input) == '}' && strchr("$!?#*@_", ch))
+ goto char_ok;
}
if (expansion < 2
* "The backslash retains its special meaning [in "..."]
* only when followed by one of the following characters:
* $, `, ", \, or <newline>. A double quote may be quoted
- * within double quotes by preceding it with a backslash.
+ * within double quotes by preceding it with a backslash."
*/
if (strchr("$`\"\\\n", next) != NULL) {
ch = i_getch(input);
dest.o_assignment = NOT_ASSIGNMENT;
}
+ /* Note: nommu_addchr(&ctx.as_string, ch) is already done */
+
switch (ch) {
case '#':
if (dest.length == 0) {
ch = i_getch(input);
if (ch != '\n') {
o_addchr(&dest, '\\');
- nommu_addchr(&ctx.as_string, '\\');
+ /*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.o_quoted = 1;
+ } else {
+#if !BB_MMU
+ /* It's "\<newline>". Remove trailing '\' from ctx.as_string */
+ ctx.as_string.data[--ctx.as_string.length] = '\0';
+#endif
}
break;
case '$':
nommu_addchr(&ctx.as_string, ch);
if (ch == '\'')
break;
- if (dest.o_assignment == NOT_ASSIGNMENT)
- o_addqchr(&dest, ch);
- else
- o_addchr(&dest, ch);
+ o_addqchr(&dest, ch);
}
break;
case '"':
IF_HAS_KEYWORDS(struct parse_context *p2;)
/* Clean up allocated tree.
- * Samples for finding leaks on syntax error recovery path.
- * Run them from interactive shell, watch pmap `pidof hush`.
- * while if false; then false; fi do break; done
- * (bash accepts it)
+ * Sample for finding leaks on syntax error recovery path.
+ * Run it from interactive shell, watch pmap `pidof hush`.
* while if false; then false; fi; do break; fi
* Samples to catch leaks at execution:
* while if (true | {true;}); then echo ok; fi; do break; done
};
int signal_mask_is_inited = 0;
int opt;
+ unsigned builtin_argc;
char **e;
struct variable *cur_var;
G.global_argc = argc;
G.global_argv = argv;
/* Initialize some more globals to non-zero values */
- set_cwd();
cmdedit_update_prompt();
if (setjmp(die_jmp)) {
/* Parse options */
/* http://www.opengroup.org/onlinepubs/9699919799/utilities/sh.html */
+ builtin_argc = 0;
while (1) {
opt = getopt(argc, argv, "c:xins"
#if !BB_MMU
break;
switch (opt) {
case 'c':
+ /* Possibilities:
+ * sh ... -c 'script'
+ * sh ... -c 'script' ARG0 [ARG1...]
+ * On NOMMU, if builtin_argc != 0,
+ * 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...] ""
+ */
if (!G.root_pid)
G.root_pid = getpid();
G.global_argv = argv + optind;
- if (!argv[optind]) {
- /* -c 'script' (no params): prevent empty $0 */
- *--G.global_argv = argv[0];
- optind--;
- } /* else -c 'script' PAR0 PAR1: $0 is PAR0 */
G.global_argc = argc - optind;
+ if (builtin_argc) {
+ /* -c 'builtin' [BARGV...] "" ARG0 [ARG1...] */
+ const struct built_in_command *x;
+
+ block_signals(0); /* 0: called 1st time */
+ 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);
+ }
+ goto final_return;
+ }
+ if (!G.global_argv[0]) {
+ /* -c 'script' (no params): prevent empty $0 */
+ G.global_argv--; /* points to argv[i] of 'script' */
+ G.global_argv[0] = argv[0];
+ G.global_argc--;
+ } /* else -c 'script' ARG0 [ARG1...]: $0 is ARG0 */
block_signals(0); /* 0: called 1st time */
parse_and_run_string(optarg);
goto final_return;
G.last_bg_pid = bb_strtou(optarg, &optarg, 16);
optarg++;
G.last_exitcode = bb_strtou(optarg, &optarg, 16);
+ optarg++;
+ builtin_argc = bb_strtou(optarg, &optarg, 16);
# if ENABLE_HUSH_LOOPS
optarg++;
G.depth_of_loop = bb_strtou(optarg, &optarg, 16);
break;
case 'R':
case 'V':
- set_local_var(xstrdup(optarg), 0, opt == 'R');
+ set_local_var(xstrdup(optarg), /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ opt == 'R');
break;
# if ENABLE_HUSH_FUNCTIONS
case 'F': {
/* bash: after sourcing /etc/profile,
* tries to source (in the given order):
* ~/.bash_profile, ~/.bash_login, ~/.profile,
- * stopping of first found. --noprofile turns this off.
+ * stopping on first found. --noprofile turns this off.
* bash also sources ~/.bash_logout on exit.
* If called as sh, skips .bash_XXX files.
*/
* NB: don't forget to (re)run block_signals(0/1) as needed.
*/
- /* A shell is interactive if the '-i' flag was given, or if all of
- * the following conditions are met:
+ /* A shell is interactive if the '-i' flag was given,
+ * or if all of the following conditions are met:
* no -c command
* no arguments remaining or the -s flag given
* standard input is a terminal
/*
* Built-ins
*/
-static int builtin_true(char **argv UNUSED_PARAM)
+static int FAST_FUNC builtin_true(char **argv UNUSED_PARAM)
{
return 0;
}
-static int builtin_test(char **argv)
+static int FAST_FUNC builtin_test(char **argv)
{
int argc = 0;
while (*argv) {
return test_main(argc, argv - argc);
}
-static int builtin_echo(char **argv)
+static int FAST_FUNC builtin_echo(char **argv)
{
int argc = 0;
while (*argv) {
return echo_main(argc, argv - argc);
}
-static int builtin_eval(char **argv)
+static int FAST_FUNC builtin_eval(char **argv)
{
int rcode = EXIT_SUCCESS;
return rcode;
}
-static int builtin_cd(char **argv)
+static int FAST_FUNC builtin_cd(char **argv)
{
const char *newdir = argv[1];
if (newdir == NULL) {
bb_perror_msg("cd: %s", newdir);
return EXIT_FAILURE;
}
- set_cwd();
+ get_cwd(1);
return EXIT_SUCCESS;
}
-static int builtin_exec(char **argv)
+static int FAST_FUNC builtin_exec(char **argv)
{
if (*++argv == NULL)
return EXIT_SUCCESS; /* bash does this */
}
}
-static int builtin_exit(char **argv)
+static int FAST_FUNC builtin_exit(char **argv)
{
debug_printf_exec("%s()\n", __func__);
static void print_escaped(const char *s)
{
+ if (*s == '\'')
+ goto squote;
do {
- if (*s != '\'') {
- const char *p;
-
- p = strchrnul(s, '\'');
- /* print 'xxxx', possibly just '' */
- printf("'%.*s'", (int)(p - s), s);
- if (*p == '\0')
- break;
- s = p;
- }
+ const char *p = strchrnul(s, '\'');
+ /* print 'xxxx', possibly just '' */
+ printf("'%.*s'", (int)(p - s), s);
+ if (*p == '\0')
+ break;
+ s = p;
+ squote:
/* s points to '; print "'''...'''" */
putchar('"');
do putchar('\''); while (*++s == '\'');
} while (*s);
}
-static int builtin_export(char **argv)
-{
- unsigned opt_unexport;
-
- if (argv[1] == NULL) {
- char **e = environ;
- if (e) {
- while (*e) {
-#if 0
- puts(*e++);
-#else
- /* ash emits: export VAR='VAL'
- * bash: declare -x VAR="VAL"
- * we follow ash example */
- const char *s = *e++;
- const char *p = strchr(s, '=');
-
- if (!p) /* wtf? take next variable */
- continue;
- /* export var= */
- printf("export %.*s", (int)(p - s) + 1, s);
- print_escaped(p + 1);
- putchar('\n');
-#endif
- }
- /*fflush(stdout); - done after each builtin anyway */
- }
- return EXIT_SUCCESS;
- }
-
-#if ENABLE_HUSH_EXPORT_N
- /* "!": do not abort on errors */
- /* "+": stop at 1st non-option */
- opt_unexport = getopt32(argv, "!+n");
- if (opt_unexport == (unsigned)-1)
- return EXIT_FAILURE;
- argv += optind;
-#else
- opt_unexport = 0;
- argv++;
+#if !ENABLE_HUSH_LOCAL
+#define helper_export_local(argv, exp, lvl) \
+ helper_export_local(argv, exp)
#endif
-
+static void helper_export_local(char **argv, int exp, int lvl)
+{
do {
char *name = *argv;
struct variable *var;
var = get_local_var(name);
- if (opt_unexport) {
+ if (exp == -1) { /* unexporting? */
/* export -n NAME (without =VALUE) */
if (var) {
var->flg_export = 0;
} /* else: export -n NOT_EXISTING_VAR: no-op */
continue;
}
- /* export NAME (without =VALUE) */
- if (var) {
- var->flg_export = 1;
- debug_printf_env("%s: putenv '%s'\n", __func__, var->varstr);
- putenv(var->varstr);
- continue;
+ if (exp == 1) { /* exporting? */
+ /* export NAME (without =VALUE) */
+ if (var) {
+ var->flg_export = 1;
+ debug_printf_env("%s: putenv '%s'\n", __func__, var->varstr);
+ putenv(var->varstr);
+ continue;
+ }
}
/* Exporting non-existing variable.
* bash does not put it in environment,
* but remembers that it is exported,
* and does put it in env when it is set later.
* We just set it to "" and export. */
+ /* Or, it's "local NAME" (without =VALUE).
+ * bash sets the value to "". */
name = xasprintf("%s=", name);
} else {
- /* (Un)exporting NAME=VALUE */
+ /* (Un)exporting/making local NAME=VALUE */
name = xstrdup(name);
}
- set_local_var(name,
- /*export:*/ (opt_unexport ? -1 : 1),
- /*readonly:*/ 0
- );
+ set_local_var(name, /*exp:*/ exp, /*lvl:*/ lvl, /*ro:*/ 0);
} while (*++argv);
+}
+
+static int FAST_FUNC builtin_export(char **argv)
+{
+ unsigned opt_unexport;
+
+#if ENABLE_HUSH_EXPORT_N
+ /* "!": do not abort on errors */
+ opt_unexport = getopt32(argv, "!n");
+ if (opt_unexport == (uint32_t)-1)
+ return EXIT_FAILURE;
+ argv += optind;
+#else
+ opt_unexport = 0;
+ argv++;
+#endif
+
+ if (argv[0] == NULL) {
+ char **e = environ;
+ if (e) {
+ while (*e) {
+#if 0
+ puts(*e++);
+#else
+ /* ash emits: export VAR='VAL'
+ * bash: declare -x VAR="VAL"
+ * we follow ash example */
+ const char *s = *e++;
+ const char *p = strchr(s, '=');
+
+ if (!p) /* wtf? take next variable */
+ continue;
+ /* export var= */
+ printf("export %.*s", (int)(p - s) + 1, s);
+ print_escaped(p + 1);
+ putchar('\n');
+#endif
+ }
+ /*fflush(stdout); - done after each builtin anyway */
+ }
+ return EXIT_SUCCESS;
+ }
+
+ helper_export_local(argv, (opt_unexport ? -1 : 1), 0);
+
+ return EXIT_SUCCESS;
+}
+#if ENABLE_HUSH_LOCAL
+static int FAST_FUNC builtin_local(char **argv)
+{
+ if (G.func_nest_level == 0) {
+ bb_error_msg("%s: not in a function", argv[0]);
+ return EXIT_FAILURE; /* bash compat */
+ }
+ helper_export_local(argv, 0, G.func_nest_level);
return EXIT_SUCCESS;
}
+#endif
-static int builtin_trap(char **argv)
+static int FAST_FUNC builtin_trap(char **argv)
{
int sig;
char *new_cmd;
goto process_sig_list;
}
+/* http://www.opengroup.org/onlinepubs/9699919799/utilities/type.html */
+static int FAST_FUNC builtin_type(char **argv)
+{
+ int ret = EXIT_SUCCESS;
+
+ while (*++argv) {
+ const char *type;
+ char *path = NULL;
+
+ if (0) {} /* make conditional compile easier below */
+ /*else if (find_alias(*argv))
+ type = "an alias";*/
+#if ENABLE_HUSH_FUNCTIONS
+ else if (find_function(*argv))
+ type = "a function";
+#endif
+ else if (find_builtin(*argv))
+ type = "a shell builtin";
+ else if ((path = find_in_path(*argv)) != NULL)
+ type = path;
+ else {
+ bb_error_msg("type: %s: not found", *argv);
+ ret = EXIT_FAILURE;
+ continue;
+ }
+
+ printf("%s is %s\n", *argv, type);
+ free(path);
+ }
+
+ return ret;
+}
+
#if ENABLE_HUSH_JOB
/* built-in 'fg' and 'bg' handler */
-static int builtin_fg_bg(char **argv)
+static int FAST_FUNC builtin_fg_bg(char **argv)
{
int i, jobnum;
struct pipe *pi;
#endif
#if ENABLE_HUSH_HELP
-static int builtin_help(char **argv UNUSED_PARAM)
+static int FAST_FUNC builtin_help(char **argv UNUSED_PARAM)
{
const struct built_in_command *x;
- printf("\n"
+ printf(
"Built-in commands:\n"
"------------------\n");
- for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
- printf("%s\t%s\n", x->cmd, x->descr);
+ for (x = bltins1; x != &bltins1[ARRAY_SIZE(bltins1)]; x++) {
+ if (x->descr)
+ printf("%s\t%s\n", x->cmd, x->descr);
}
- printf("\n\n");
+ bb_putchar('\n');
return EXIT_SUCCESS;
}
#endif
#if ENABLE_HUSH_JOB
-static int builtin_jobs(char **argv UNUSED_PARAM)
+static int FAST_FUNC builtin_jobs(char **argv UNUSED_PARAM)
{
struct pipe *job;
const char *status_string;
#endif
#if HUSH_DEBUG
-static int builtin_memleak(char **argv UNUSED_PARAM)
+static int FAST_FUNC builtin_memleak(char **argv UNUSED_PARAM)
{
void *p;
unsigned long l;
if (!G.memleak_value)
G.memleak_value = l;
-
+
l -= G.memleak_value;
if ((long)l < 0)
l = 0;
}
#endif
-static int builtin_pwd(char **argv UNUSED_PARAM)
+static int FAST_FUNC builtin_pwd(char **argv UNUSED_PARAM)
{
- puts(set_cwd());
+ puts(get_cwd(0));
return EXIT_SUCCESS;
}
-static int builtin_read(char **argv)
+static int FAST_FUNC builtin_read(char **argv)
{
char *string;
const char *name = "REPLY";
//TODO: bash unbackslashes input, splits words and puts them in argv[i]
string = xmalloc_reads(STDIN_FILENO, xasprintf("%s=", name), NULL);
- return set_local_var(string, 0, 0);
+ return set_local_var(string, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
}
/* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#set
*
* So far, we only support "set -- [argument...]" and some of the short names.
*/
-static int builtin_set(char **argv)
+static int FAST_FUNC builtin_set(char **argv)
{
int n;
char **pp, **g_argv;
return EXIT_FAILURE;
}
-static int builtin_shift(char **argv)
+static int FAST_FUNC builtin_shift(char **argv)
{
int n = 1;
if (argv[1]) {
return EXIT_FAILURE;
}
-static int builtin_source(char **argv)
+static int FAST_FUNC builtin_source(char **argv)
{
- const char *PATH;
+ char *arg_path;
FILE *input;
save_arg_t sv;
#if ENABLE_HUSH_FUNCTIONS
if (*++argv == NULL)
return EXIT_FAILURE;
- if (strchr(*argv, '/') == NULL
- && (PATH = get_local_var_value("PATH")) != NULL
- ) {
- /* Search through $PATH */
- while (1) {
- const char *end = strchrnul(PATH, ':');
- int sz = end - PATH; /* must be int! */
-
- if (sz != 0) {
- char *tmp = xasprintf("%.*s/%s", sz, PATH, *argv);
- input = fopen_for_read(tmp);
- free(tmp);
- } else {
- /* We have xxx::yyyy in $PATH,
- * it means "use current dir" */
- input = fopen_for_read(*argv);
- }
- if (input)
- goto opened_ok;
- if (*end == '\0')
- break;
- PATH = end + 1;
- }
- }
- input = fopen_or_warn(*argv, "r");
+ 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");
if (!input) {
/* bb_perror_msg("%s", *argv); - done by fopen_or_warn */
return EXIT_FAILURE;
}
- opened_ok:
close_on_exec_on(fileno(input));
#if ENABLE_HUSH_FUNCTIONS
return G.last_exitcode;
}
-static int builtin_umask(char **argv)
+static int FAST_FUNC builtin_umask(char **argv)
{
int rc;
mode_t mask;
}
/* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#unset */
-static int builtin_unset(char **argv)
+static int FAST_FUNC builtin_unset(char **argv)
{
int ret;
unsigned opts;
}
/* http://www.opengroup.org/onlinepubs/9699919799/utilities/wait.html */
-static int builtin_wait(char **argv)
+static int FAST_FUNC builtin_wait(char **argv)
{
int ret = EXIT_SUCCESS;
int status, sig;
#endif
#if ENABLE_HUSH_LOOPS
-static int builtin_break(char **argv)
+static int FAST_FUNC builtin_break(char **argv)
{
unsigned depth;
if (G.depth_of_loop == 0) {
return EXIT_SUCCESS;
}
-static int builtin_continue(char **argv)
+static int FAST_FUNC builtin_continue(char **argv)
{
G.flag_break_continue = 1; /* BC_CONTINUE = 2 = 1+1 */
return builtin_break(argv);
#endif
#if ENABLE_HUSH_FUNCTIONS
-static int builtin_return(char **argv)
+static int FAST_FUNC builtin_return(char **argv)
{
int rc;