* <(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)
- * change { and } from special chars to reserved words
- * $var refs in function do not pick up values set by "var=val func"
- * builtins: return, ulimit
+ * builtins: ulimit
+ * special variables (done: PWD)
* follow IFS rules more precisely, including update semantics
- * figure out what to do with backslash-newline
- * continuation lines, both explicit and implicit - done?
- * SIGHUP handling
- * separate job control from interactiveness
- * (testcase: booting with init=/bin/hush does not show prompt (2009-04))
+ * 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.
*/
-#include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */
+#include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */
+#include <malloc.h> /* for malloc_trim */
#include <glob.h>
/* #include <dmalloc.h> */
#if ENABLE_HUSH_CASE
#endif
#include "math.h"
#include "match.h"
+#if ENABLE_HUSH_RANDOM_SUPPORT
+# include "random.h"
+#else
+# define CLEAR_RANDOM_T(rnd) ((void)0)
+#endif
#ifndef PIPE_BUF
-# define PIPE_BUF 4096 /* amount of buffering in a pipe */
+# define PIPE_BUF 4096 /* amount of buffering in a pipe */
#endif
-/* Debug build knobs */
+/* Build knobs */
#define LEAK_HUNTING 0
#define BUILD_AS_NOMMU 0
/* Enable/disable sanity checks. Ok to enable in production,
* Keeping 1 for now even in released versions.
*/
#define HUSH_DEBUG 1
+/* Slightly bigger (+200 bytes), but faster hush.
+ * So far it only enables a trick with counting SIGCHLDs and forks,
+ * which allows us to do fewer waitpid's.
+ * (we can detect a case where neither forks were done nor SIGCHLDs happened
+ * and therefore waitpid will return the same result as last time)
+ */
+#define ENABLE_HUSH_FAST 0
#if BUILD_AS_NOMMU
/* STANDALONE does not make sense, and won't compile */
# undef CONFIG_FEATURE_SH_STANDALONE
# undef ENABLE_FEATURE_SH_STANDALONE
-# undef USE_FEATURE_SH_STANDALONE
-# define SKIP_FEATURE_SH_STANDALONE(...) __VA_ARGS__
+# undef IF_FEATURE_SH_STANDALONE
+# define IF_FEATURE_SH_STANDALONE(...)
+# define IF_NOT_FEATURE_SH_STANDALONE(...) __VA_ARGS__
# define ENABLE_FEATURE_SH_STANDALONE 0
-# define USE_FEATURE_SH_STANDALONE(...)
-# define SKIP_FEATURE_SH_STANDALONE(...) __VA_ARGS__
#endif
#if !ENABLE_HUSH_INTERACTIVE
#define SPECIAL_VAR_SYMBOL 3
+struct variable;
+
static const char hush_version_str[] ALIGN1 = "HUSH_VERSION="BB_VER;
/* This supports saving pointers malloced in vfork child,
#if !BB_MMU
typedef struct nommu_save_t {
char **new_env;
- char **old_env;
+ struct variable *old_vars;
char **argv;
char **argv_from_re_execing;
} 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
#endif
#if ENABLE_HUSH_CASE
RES_CASE ,
- /* two pseudo-keywords support contrived "case" syntax: */
- RES_MATCH , /* "word)" */
- RES_CASEI , /* "this command is inside CASE" */
+ /* three pseudo-keywords support contrived "case" syntax: */
+ RES_CASE_IN, /* "case ... IN", turns into RES_MATCH when IN is observed */
+ RES_MATCH , /* "word)" */
+ RES_CASE_BODY, /* "this command is inside CASE" */
RES_ESAC ,
#endif
RES_XXXX ,
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
- struct pipe *group; /* if non-NULL, this "command" is { list },
- * ( list ), or a compound statement */
+
+ /* if non-NULL, this "command" is { list }, ( list ), or a compound statement */
+ struct pipe *group;
#if !BB_MMU
char *group_as_string;
#endif
#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;
char *name;
struct command *parent_cmd;
struct pipe *body;
-#if !BB_MMU
+# if !BB_MMU
char *body_as_string;
-#endif
+# endif
};
#endif
/* "Globals" within this file */
/* Sorted roughly by size (smaller offsets == smaller code) */
struct globals {
+ /* interactive_fd != 0 means we are an interactive shell.
+ * If we are, then saved_tty_pgrp can also be != 0, meaning
+ * that controlling tty is available. With saved_tty_pgrp == 0,
+ * job control still works, but terminal signals
+ * (^C, ^Z, ^Y, ^\) won't work at all, and background
+ * process groups can only be created with "cmd &".
+ * With saved_tty_pgrp != 0, hush will use tcsetpgrp()
+ * to give tty to the foreground process group,
+ * and will take it back when the group is stopped (^Z)
+ * or killed (^C).
+ */
#if ENABLE_HUSH_INTERACTIVE
/* 'interactive_fd' is a fd# open to ctty, if we have one
* _AND_ if we decided to act interactively */
int interactive_fd;
const char *PS1;
const char *PS2;
-#define G_interactive_fd (G.interactive_fd)
+# define G_interactive_fd (G.interactive_fd)
#else
-#define G_interactive_fd 0
+# define G_interactive_fd 0
#endif
#if ENABLE_FEATURE_EDITING
line_input_t *line_input_state;
#endif
pid_t root_pid;
+ pid_t root_ppid;
pid_t last_bg_pid;
+#if ENABLE_HUSH_RANDOM_SUPPORT
+ random_t random_gen;
+#endif
#if ENABLE_HUSH_JOB
int run_list_level;
- pid_t saved_tty_pgrp;
int last_jobid;
+ pid_t saved_tty_pgrp;
struct pipe *job_list;
- struct pipe *toplevel_list;
+# define G_saved_tty_pgrp (G.saved_tty_pgrp)
+#else
+# define G_saved_tty_pgrp 0
#endif
smallint flag_SIGINT;
#if ENABLE_HUSH_LOOPS
smallint flag_break_continue;
+#endif
+#if ENABLE_HUSH_FUNCTIONS
+ /* 0: outside of a function (or sourced file)
+ * -1: inside of a function, ok to use return builtin
+ * 1: return is invoked, skip all till end of func
+ */
+ smallint flag_return_in_progress;
#endif
smallint fake_mode;
smallint exiting; /* used to prevent EXIT trap recursion */
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 */
-// unsigned count_SIGCHLD;
-// unsigned handled_SIGCHLD;
+#if ENABLE_HUSH_FAST
+ unsigned count_SIGCHLD;
+ unsigned handled_SIGCHLD;
+ smallint we_have_children;
+#endif
/* which signals have non-DFL handler (even with no traps set)? */
unsigned non_DFL_mask;
char **traps; /* char *traps[NSIG] */
unsigned long memleak_value;
int debug_indent;
#endif
- char user_input_buf[ENABLE_FEATURE_EDITING ? BUFSIZ : 2];
+ char user_input_buf[ENABLE_FEATURE_EDITING ? CONFIG_FEATURE_EDITING_MAX_LEN : 2];
};
#define G (*ptr_to_globals)
/* Not #defining name to G.name - this quickly gets unwieldy
/* 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
+#if ENABLE_PRINTF
+static int builtin_printf(char **argv) FAST_FUNC;
+#endif
+static int builtin_pwd(char **argv) FAST_FUNC;
+static int builtin_read(char **argv) FAST_FUNC;
+static int builtin_set(char **argv) FAST_FUNC;
+static 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) 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"),
-#endif
- BLTIN("pwd" , builtin_pwd , "Print current directory"),
- BLTIN("read" , builtin_read , "Input environment variable"),
-// BLTIN("return" , builtin_return , "Return from a function"),
- 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("memleak" , builtin_memleak , NULL),
+#endif
+ 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 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),
+#if ENABLE_PRINTF
+ BLTIN("printf" , builtin_printf , NULL),
+#endif
+ 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
* HUSH_DEBUG >= 2 prints line number in this file where it was detected.
*/
#if HUSH_DEBUG < 2
-# define die_if_script(lineno, fmt...) die_if_script(fmt)
+# define die_if_script(lineno, ...) die_if_script(__VA_ARGS__)
# define syntax_error(lineno, msg) syntax_error(msg)
# define syntax_error_at(lineno, msg) syntax_error_at(msg)
# define syntax_error_unterm_ch(lineno, ch) syntax_error_unterm_ch(ch)
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, char ch)
+static void syntax_error_unexpected_ch(unsigned lineno, int ch)
{
char msg[2];
msg[0] = ch;
msg[1] = '\0';
- die_if_script(lineno, "syntax error: unexpected %s", msg);
+ die_if_script(lineno, "syntax error: unexpected %s", ch == EOF ? "EOF" : msg);
}
#if HUSH_DEBUG < 2
# undef syntax_error_unterm_str
# undef syntax_error_unexpected_ch
#else
-# define die_if_script(fmt...) die_if_script(__LINE__, fmt)
+# define die_if_script(...) die_if_script(__LINE__, __VA_ARGS__)
# define syntax_error(msg) syntax_error(__LINE__, msg)
# define syntax_error_at(msg) syntax_error_at(__LINE__, msg)
# define syntax_error_unterm_ch(ch) syntax_error_unterm_ch(__LINE__, ch)
#endif
+#if ENABLE_HUSH_INTERACTIVE
+static void cmdedit_update_prompt(void);
+#else
+# define cmdedit_update_prompt() ((void)0)
+#endif
+
+
/* Utility functions
*/
static int glob_needed(const char *s)
/* Replace each \x with x in place, return ptr past NUL. */
static char *unbackslash(char *src)
{
- char *dst = src;
+ char *dst = src = strchrnul(src, '\\');
while (1) {
if (*src == '\\')
src++;
xx_add_strings_to_strings(__LINE__, strings, add, need_to_dup)
#endif
+/* Note: takes ownership of "add" ptr (it is not strdup'ed) */
static char **add_string_to_strings(char **strings, char *add)
{
char *v[2];
xx_add_string_to_strings(__LINE__, strings, add)
#endif
-static void putenv_all(char **strings)
-{
- if (!strings)
- return;
- while (*strings) {
- debug_printf_env("putenv '%s'\n", *strings);
- putenv(*strings++);
- }
-}
-
-static char **putenv_all_and_save_old(char **strings)
-{
- char **old = NULL;
- char **s = strings;
-
- if (!strings)
- return old;
- while (*strings) {
- char *v, *eq;
-
- eq = strchr(*strings, '=');
- if (eq) {
- *eq = '\0';
- v = getenv(*strings);
- *eq = '=';
- if (v) {
- /* v points to VAL in VAR=VAL, go back to VAR */
- v -= (eq - *strings) + 1;
- old = add_string_to_strings(old, v);
- }
- }
- strings++;
- }
- putenv_all(s);
- return old;
-}
-
-static void free_strings_and_unsetenv(char **strings, int unset)
+static void free_strings(char **strings)
{
char **v;
if (!strings)
return;
-
v = strings;
while (*v) {
- if (unset) {
- debug_printf_env("unsetenv '%s'\n", *v);
- bb_unsetenv(*v);
- }
- free(*v++);
+ free(*v);
+ v++;
}
free(strings);
}
-static void free_strings(char **strings)
-{
- free_strings_and_unsetenv(strings, 0);
-}
-
/* Helpers for setting new $n and restoring them back
*/
* is finished or backgrounded. It is the same in interactive and
* non-interactive shells, and is the same regardless of whether
* a user trap handler is installed or a shell special one is in effect.
- * ^C or ^Z from keyboard seem to execute "at once" because it usually
+ * ^C or ^Z from keyboard seems to execute "at once" because it usually
* backgrounds (i.e. stops) or kills all members of currently running
* pipe.
*
* 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
* "trap 'cmd' SIGxxx":
* set bit in blocked_set (even if 'cmd' is '')
* after [v]fork, if we plan to be a shell:
- * nothing for {} child shell (say, "true | { true; true; } | true")
- * unset all traps if () shell.
+ * unblock signals with special interactive handling
+ * (child shell is not interactive),
+ * unset all traps (note: regardless of child shell's type - {}, (), etc)
* after [v]fork, if we plan to exec:
- * POSIX says pending signal mask is cleared in child - no need to clear it.
+ * 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: as a result, we do not use signal handlers much. The only uses
- * are to count SIGCHLDs [disabled - bug somewhere, + bloat]
+ * are to count SIGCHLDs
* and to restore tty pgrp on signal-induced exit.
+ *
+ * Note 2 (compat):
+ * Standard says "When a subshell is entered, traps that are not being ignored
+ * are set to the default actions". bash interprets it so that traps which
+ * are set to "" (ignore) are NOT reset to defaults. We do the same.
*/
enum {
SPECIAL_INTERACTIVE_SIGS = 0
-#if ENABLE_HUSH_JOB
- | (1 << SIGTTIN) | (1 << SIGTTOU) | (1 << SIGTSTP)
-#endif
| (1 << SIGTERM)
-//TODO | (1 << SIGHUP)
| (1 << SIGINT)
+ | (1 << SIGHUP)
+ ,
+ SPECIAL_JOB_SIGS = 0
+#if ENABLE_HUSH_JOB
+ | (1 << SIGTTIN)
+ | (1 << SIGTTOU)
+ | (1 << SIGTSTP)
+#endif
};
-//static void SIGCHLD_handler(int sig UNUSED_PARAM)
-//{
-// G.count_SIGCHLD++;
-//}
-
-static int check_and_run_traps(int sig)
+#if ENABLE_HUSH_FAST
+static void SIGCHLD_handler(int sig UNUSED_PARAM)
{
- static const struct timespec zero_timespec = { 0, 0 };
- smalluint save_rcode;
- int last_sig = 0;
-
- if (sig)
- goto jump_in;
- while (1) {
- sig = sigtimedwait(&G.blocked_set, NULL, &zero_timespec);
- if (sig <= 0)
- break;
- jump_in:
- last_sig = sig;
- if (G.traps && G.traps[sig]) {
- if (G.traps[sig][0]) {
- /* We have user-defined handler */
- char *argv[] = { NULL, xstrdup(G.traps[sig]), NULL };
- save_rcode = G.last_exitcode;
- builtin_eval(argv);
- free(argv[1]);
- G.last_exitcode = save_rcode;
- } /* else: "" trap, ignoring signal */
- continue;
- }
- /* not a trap: special action */
- switch (sig) {
-// case SIGCHLD:
-// G.count_SIGCHLD++;
-// break;
- case SIGINT:
-//TODO: add putchar('\n') also when we detect that child was killed (sleep 5 + ^C)
- /* Builtin was ^C'ed, make it look prettier: */
- bb_putchar('\n');
- G.flag_SIGINT = 1;
- break;
-//TODO
-// case SIGHUP: ...
-// break;
- default: /* ignored: */
- /* SIGTERM, SIGQUIT, SIGTTIN, SIGTTOU, SIGTSTP */
- break;
- }
- }
- return last_sig;
+ G.count_SIGCHLD++;
+//bb_error_msg("[%d] SIGCHLD_handler: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
}
+#endif
#if ENABLE_HUSH_JOB
/* Restores tty foreground process group, and exits.
* May be called as signal handler for fatal signal
- * (will faithfully resend signal to itself, producing correct exit state)
+ * (will resend signal to itself, producing correct exit state)
* or called directly with -EXITCODE.
* We also call it if xfunc is exiting. */
static void sigexit(int sig) NORETURN;
/* Careful: we can end up here after [v]fork. Do not restore
* tty pgrp then, only top-level shell process does that */
- if (G_interactive_fd && getpid() == G.root_pid)
- tcsetpgrp(G_interactive_fd, G.saved_tty_pgrp);
+ if (G_saved_tty_pgrp && getpid() == G.root_pid)
+ tcsetpgrp(G_interactive_fd, G_saved_tty_pgrp);
/* Not a signal, just exit */
if (sig <= 0)
#endif
}
+static int check_and_run_traps(int sig)
+{
+ static const struct timespec zero_timespec = { 0, 0 };
+ smalluint save_rcode;
+ int last_sig = 0;
+
+ if (sig)
+ goto jump_in;
+ while (1) {
+ sig = sigtimedwait(&G.blocked_set, NULL, &zero_timespec);
+ if (sig <= 0)
+ break;
+ jump_in:
+ last_sig = sig;
+ if (G.traps && G.traps[sig]) {
+ if (G.traps[sig][0]) {
+ /* We have user-defined handler */
+ char *argv[] = { NULL, xstrdup(G.traps[sig]), NULL };
+ save_rcode = G.last_exitcode;
+ builtin_eval(argv);
+ free(argv[1]);
+ G.last_exitcode = save_rcode;
+ } /* else: "" trap, ignoring signal */
+ continue;
+ }
+ /* not a trap: special action */
+ switch (sig) {
+#if ENABLE_HUSH_FAST
+ case SIGCHLD:
+ G.count_SIGCHLD++;
+//bb_error_msg("[%d] check_and_run_traps: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
+ break;
+#endif
+ case SIGINT:
+ /* Builtin was ^C'ed, make it look prettier: */
+ bb_putchar('\n');
+ G.flag_SIGINT = 1;
+ break;
+#if ENABLE_HUSH_JOB
+ case SIGHUP: {
+ struct pipe *job;
+ /* bash is observed to signal whole process groups,
+ * not individual processes */
+ for (job = G.job_list; job; job = job->next) {
+ if (job->pgrp <= 0)
+ continue;
+ debug_printf_exec("HUPing pgrp %d\n", job->pgrp);
+ if (kill(- job->pgrp, SIGHUP) == 0)
+ kill(- job->pgrp, SIGCONT);
+ }
+ sigexit(SIGHUP);
+ }
+#endif
+ default: /* ignored: */
+ /* SIGTERM, SIGQUIT, SIGTTIN, SIGTTOU, SIGTSTP */
+ break;
+ }
+ }
+ return last_sig;
+}
+
-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;
}
-/* Get/check local shell variables */
-static struct variable *get_local_var(const char *name)
+/*
+ * Shell and environment variable support
+ */
+static struct variable **get_ptr_to_local_var(const char *name)
{
+ struct variable **pp;
struct variable *cur;
int len;
- if (!name)
- return NULL;
len = strlen(name);
- for (cur = G.top_var; cur; cur = cur->next) {
+ pp = &G.top_var;
+ while ((cur = *pp) != NULL) {
if (strncmp(cur->varstr, name, len) == 0 && cur->varstr[len] == '=')
- return cur;
+ return pp;
+ pp = &cur->next;
}
return NULL;
}
-static const char *get_local_var_value(const char *src)
+static struct variable *get_local_var(const char *name)
+{
+ struct variable **pp = get_ptr_to_local_var(name);
+ if (pp)
+ return *pp;
+ return NULL;
+}
+
+static const char *get_local_var_value(const char *name)
{
- struct variable *var = get_local_var(src);
- if (var)
- return strchr(var->varstr, '=') + 1;
+ struct variable **pp = get_ptr_to_local_var(name);
+ if (pp)
+ return strchr((*pp)->varstr, '=') + 1;
+ if (strcmp(name, "PPID") == 0)
+ return utoa(G.root_ppid);
+ // bash compat: UID? EUID?
+#if ENABLE_HUSH_RANDOM_SUPPORT
+ if (strcmp(name, "RANDOM") == 0) {
+ return utoa(next_random(&G.random_gen));
+ }
+#endif
return NULL;
}
/* str holds "NAME=VAL" and is expected to be malloced.
* We take ownership of it.
* flg_export:
- * 0: do not export
- * 1: export
- * -1: if NAME is set, leave export status alone
- * if NAME is not set, do not export
+ * 0: do not change export flag
+ * (if creating new variable, flag will be 0)
+ * 1: set export flag and putenv the variable
+ * -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 *value;
+ char *eq_sign;
int name_len;
- value = strchr(str, '=');
- if (!value) { /* not expected to ever happen? */
+ eq_sign = strchr(str, '=');
+ if (!eq_sign) { /* not expected to ever happen? */
free(str);
return -1;
}
- name_len = value - str + 1; /* including '=' */
- cur = G.top_var; /* cannot be NULL (we have HUSH_VERSION and it's RO) */
- while (1) {
+ name_len = eq_sign - str + 1; /* including '=' */
+ 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 */
- *value = '\0';
if (cur->flg_read_only) {
#if !BB_MMU
if (!flg_read_only)
free(str);
return -1;
}
- debug_printf_env("%s: unsetenv '%s'\n", __func__, str);
- unsetenv(str); /* just in case */
- *value = '=';
- if (strcmp(cur->varstr, str) == 0) {
+ 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;
exp:
if (flg_export == 1)
cur->flg_export = 1;
+ if (name_len == 4 && cur->varstr[0] == 'P' && cur->varstr[1] == 'S')
+ cmdedit_update_prompt();
if (cur->flg_export) {
- debug_printf_env("%s: putenv '%s'\n", __func__, cur->varstr);
- return putenv(cur->varstr);
+ if (flg_export == -1) {
+ cur->flg_export = 0;
+ /* unsetenv was already done */
+ } else {
+ debug_printf_env("%s: putenv '%s'\n", __func__, cur->varstr);
+ return putenv(cur->varstr);
+ }
}
return 0;
}
-static int unset_local_var(const char *name)
+/* Used at startup and after each cd */
+static void set_pwd_var(int exp)
+{
+ set_local_var(xasprintf("PWD=%s", get_cwd(/*force:*/ 1)),
+ /*exp:*/ exp, /*lvl:*/ 0, /*ro:*/ 0);
+}
+
+static int unset_local_var_len(const char *name, int name_len)
{
struct variable *cur;
- struct variable *prev = prev; /* for gcc */
- int name_len;
+ struct variable **var_pp;
if (!name)
return EXIT_SUCCESS;
- name_len = strlen(name);
- cur = G.top_var;
- while (cur) {
+ var_pp = &G.top_var;
+ while ((cur = *var_pp) != NULL) {
if (strncmp(cur->varstr, name, name_len) == 0 && cur->varstr[name_len] == '=') {
if (cur->flg_read_only) {
bb_error_msg("%s: readonly variable", name);
return EXIT_FAILURE;
}
- /* prev is ok to use here because 1st variable, HUSH_VERSION,
- * is ro, and we cannot reach this code on the 1st pass */
- prev->next = cur->next;
+ *var_pp = cur->next;
debug_printf_env("%s: unsetenv '%s'\n", __func__, cur->varstr);
bb_unsetenv(cur->varstr);
+ if (name_len == 3 && cur->varstr[0] == 'P' && cur->varstr[1] == 'S')
+ cmdedit_update_prompt();
if (!cur->max_len)
free(cur->varstr);
free(cur);
return EXIT_SUCCESS;
}
- prev = cur;
- cur = cur->next;
+ var_pp = &cur->next;
}
return EXIT_SUCCESS;
}
+static int unset_local_var(const char *name)
+{
+ return unset_local_var_len(name, strlen(name));
+}
+
+static void unset_vars(char **strings)
+{
+ char **v;
+
+ if (!strings)
+ return;
+ v = strings;
+ while (*v) {
+ const char *eq = strchrnul(*v, '=');
+ unset_local_var_len(*v, (int)(eq - *v));
+ v++;
+ }
+ free(strings);
+}
+
#if ENABLE_SH_MATH_SUPPORT
#define is_name(c) ((c) == '_' || isalpha((unsigned char)(c)))
#define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c)))
{
/* 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
/*
- * in_str support
+ * Helpers for "var1=val1 var2=val2 cmd" feature
*/
-static int static_get(struct in_str *i)
+static void add_vars(struct variable *var)
{
- int ch = *i->p++;
+ struct variable *next;
+
+ while (var) {
+ next = var->next;
+ var->next = G.top_var;
+ G.top_var = var;
+ if (var->flg_export) {
+ debug_printf_env("%s: restoring exported '%s'\n", __func__, var->varstr);
+ putenv(var->varstr);
+ } else {
+ debug_printf_env("%s: restoring variable '%s'\n", __func__, var->varstr);
+ }
+ var = next;
+ }
+}
+
+static struct variable *set_vars_and_save_old(char **strings)
+{
+ char **s;
+ struct variable *old = NULL;
+
+ if (!strings)
+ return old;
+ s = strings;
+ while (*s) {
+ struct variable *var_p;
+ struct variable **var_pp;
+ char *eq;
+
+ eq = strchr(*s, '=');
+ if (eq) {
+ *eq = '\0';
+ var_pp = get_ptr_to_local_var(*s);
+ *eq = '=';
+ if (var_pp) {
+ /* Remove variable from global linked list */
+ var_p = *var_pp;
+ debug_printf_env("%s: removing '%s'\n", __func__, var_p->varstr);
+ *var_pp = var_p->next;
+ /* Add it to returned list */
+ var_p->next = old;
+ old = var_p;
+ }
+ set_local_var(*s, /*exp:*/ 1, /*lvl:*/ 0, /*ro:*/ 0);
+ }
+ s++;
+ }
+ return old;
+}
+
+
+/*
+ * in_str support
+ */
+static int FAST_FUNC static_get(struct in_str *i)
+{
+ int ch = *i->p++;
if (ch != '\0')
return ch;
i->p--;
return EOF;
}
-static int static_peek(struct in_str *i)
+static int FAST_FUNC static_peek(struct in_str *i)
{
return *i->p;
}
#if ENABLE_HUSH_INTERACTIVE
-static void cmdedit_set_initial_prompt(void)
+static void cmdedit_update_prompt(void)
{
if (ENABLE_FEATURE_EDITING_FANCY_PROMPT) {
- G.PS1 = getenv("PS1");
+ G.PS1 = get_local_var_value("PS1");
if (G.PS1 == NULL)
G.PS1 = "\\w \\$ ";
- } else
+ G.PS2 = get_local_var_value("PS2");
+ } else {
G.PS1 = NULL;
+ }
+ if (G.PS2 == NULL)
+ G.PS2 = "> ";
}
static const char* setup_prompt_string(int promptmode)
/* Set up the prompt */
if (promptmode == 0) { /* PS1 */
free((char*)G.PS1);
- G.PS1 = xasprintf("%s %c ", G.cwd, (geteuid() != 0) ? '$' : '#');
+ /* bash uses $PWD value, even if it is set by user.
+ * It uses current dir only if PWD is unset.
+ * We always use current dir. */
+ G.PS1 = xasprintf("%s %c ", get_cwd(0), (geteuid() != 0) ? '$' : '#');
prompt_str = G.PS1;
} else
prompt_str = G.PS2;
G.flag_SIGINT = 0;
/* buglet: SIGINT will not make new prompt to appear _at once_,
* only after <Enter>. (^C will work) */
- r = read_line_input(prompt_str, G.user_input_buf, BUFSIZ-1, G.line_input_state);
+ r = read_line_input(prompt_str, G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1, G.line_input_state);
/* catch *SIGINT* etc (^C is handled by read_line_input) */
check_and_run_traps(0);
} while (r == 0 || G.flag_SIGINT); /* repeat if ^C or SIGINT */
/* 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
goto literal;
}
if (gr != 0) { /* GLOB_ABORTED ? */
-//TODO: testcase for bad glob pattern behavior
+ /* TODO: testcase for bad glob pattern behavior */
bb_error_msg("glob(3) error %d on '%s'", gr, pattern);
}
if (globdata.gl_pathv && globdata.gl_pathv[0]) {
* to be filled). This routine is extremely tricky: has to deal with
* variables/parameters with whitespace, $* and $@, and constructs like
* 'echo -$*-'. If you play here, you must run testsuite afterwards! */
-static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
+static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
{
- /* or_mask is either 0 (normal case) or 0x80
- * (expansion of right-hand side of assignment == 1-element expand.
- * It will also do no globbing, and thus we must not backslash-quote!) */
-
- char first_ch, ored_ch;
- int i;
- const char *val;
- char *dyn_val, *p;
+ /* 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;
- dyn_val = NULL;
ored_ch = 0;
- debug_printf_expand("expand_vars_to_list: arg '%s'\n", arg);
+ debug_printf_expand("expand_vars_to_list: arg:'%s' or_mask:%x\n", arg, or_mask);
debug_print_list("expand_vars_to_list", output, n);
n = o_save_ptr(output, n);
debug_print_list("expand_vars_to_list[0]", output, n);
while ((p = strchr(arg, SPECIAL_VAR_SYMBOL)) != NULL) {
+ char first_ch;
+ int i;
+ char *dyn_val = NULL;
+ const char *val = NULL;
#if ENABLE_HUSH_TICK
o_string subst_result = NULL_O_STRING;
#endif
if ((first_ch & 0x7f) != '@')
ored_ch |= first_ch;
- val = NULL;
switch (first_ch & 0x7f) {
/* Highest bit in first_ch indicates that var is double-quoted */
case '$': /* pid */
case '`': /* <SPECIAL_VAR_SYMBOL>`cmd<SPECIAL_VAR_SYMBOL> */
*p = '\0';
arg++;
-//TODO: can we just stuff it into "output" directly?
+ /* Can't just stuff it into output 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);
if (exp_op == '%' || exp_op == '#') {
if (val) {
/* we need to do a pattern match */
- bool zero;
+ bool match_at_left;
char *loc;
- scan_t scan = pick_scan(exp_op, *exp_word, &zero);
+ 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, zero);
- if (zero)
+ loc = scan(dyn_val, exp_word, match_at_left);
+ if (match_at_left) /* # or ## */
val = loc;
- else
+ else if (loc) /* % or %% and match was found */
*loc = '\0';
}
} else {
val = NULL;
} else {
char *new_var = xasprintf("%s=%s", var, val);
- set_local_var(new_var, -1, 0);
+ set_local_var(new_var, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
}
}
}
}
} /* default: */
} /* switch (char after <SPECIAL_VAR_SYMBOL>) */
+
if (val) {
o_addQstr(output, val, strlen(val));
}
free(dyn_val);
- dyn_val = NULL;
/* Do the check to avoid writing to a const string */
if (*p != SPECIAL_VAR_SYMBOL)
*p = SPECIAL_VAR_SYMBOL;
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 (HUSH_DEBUG)
if (list[n-1] + strlen(list[n-1]) + 1 != list[n])
bb_error_msg_and_die("BUG in varexp3");
- list[n][-1] = ' '; /* TODO: or to G.ifs[0]? */
+ /* bash uses ' ' regardless of $IFS contents */
+ list[n][-1] = ' ';
n++;
}
}
#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)
{
/* This function is always called in a child shell
* after fork (not vfork, NOMMU doesn't use this function).
- * Child shells are not interactive.
- * SIGTTIN/SIGTTOU/SIGTSTP should not have special handling.
- * Testcase: (while :; do :; done) + ^Z should background.
- * Same goes for SIGTERM, SIGHUP, SIGINT.
*/
unsigned sig;
unsigned mask;
+ /* Child shells are not interactive.
+ * SIGTTIN/SIGTTOU/SIGTSTP should not have special handling.
+ * Testcase: (while :; do :; done) + ^Z should background.
+ * Same goes for SIGTERM, SIGHUP, SIGINT.
+ */
if (!G.traps && !(G.non_DFL_mask & SPECIAL_INTERACTIVE_SIGS))
- return;
+ return; /* already no traps and no SPECIAL_INTERACTIVE_SIGS */
- /* Stupid. It can be done with *single* &= op, but we can't use
- * the fact that G.blocked_set is implemented as a bitmask... */
+ /* Switching off SPECIAL_INTERACTIVE_SIGS.
+ * Stupid. It can be done with *single* &= op, but we can't use
+ * the fact that G.blocked_set is implemented as a bitmask
+ * in libc... */
mask = (SPECIAL_INTERACTIVE_SIGS >> 1);
sig = 1;
while (1) {
- if (mask & 1)
- sigdelset(&G.blocked_set, sig);
+ if (mask & 1) {
+ /* Careful. Only if no trap or trap is not "" */
+ if (!G.traps || !G.traps[sig] || G.traps[sig][0])
+ sigdelset(&G.blocked_set, sig);
+ }
mask >>= 1;
if (!mask)
break;
sig++;
}
-
+ /* Our homegrown sig mask is saner to work with :) */
G.non_DFL_mask &= ~SPECIAL_INTERACTIVE_SIGS;
+
+ /* Resetting all traps to default except empty ones */
mask = G.non_DFL_mask;
if (G.traps) for (sig = 0; sig < NSIG; sig++, mask >>= 1) {
- if (!G.traps[sig])
+ if (!G.traps[sig] || !G.traps[sig][0])
continue;
free(G.traps[sig]);
G.traps[sig] = NULL;
/* There is no signal for 0 (EXIT) */
if (sig == 0)
continue;
- /* There was a trap handler, we are removing it.
+ /* There was a trap handler, we just removed it.
* But if sig still has non-DFL handling,
- * we should not unblock it. */
+ * we should not unblock the sig. */
if (mask & 1)
continue;
sigdelset(&G.blocked_set, sig);
#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
+# if ENABLE_HUSH_FUNCTIONS
struct function *funcp;
-#endif
+# endif
char **argv, **pp;
unsigned cnt;
goto do_exec;
}
- sprintf(param_buf, "-$%x:%x:%x" USE_HUSH_LOOPS(":%x")
+ cnt = 0;
+ pp = builtin_argv;
+ if (pp) while (*pp++)
+ cnt++;
+
+ sprintf(param_buf, "-$%x:%x:%x:%x:%x" IF_HUSH_LOOPS(":%x")
, (unsigned) G.root_pid
+ , (unsigned) G.root_ppid
, (unsigned) G.last_bg_pid
, (unsigned) G.last_exitcode
- USE_HUSH_LOOPS(, G.depth_of_loop)
+ , 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;
}
-#if ENABLE_HUSH_FUNCTIONS
+# if ENABLE_HUSH_FUNCTIONS
for (funcp = G.top_func; funcp; funcp = funcp->next)
cnt += 3;
-#endif
+# endif
pp = g_argv;
while (*pp++)
cnt++;
*pp++ = cur->varstr;
}
}
-#if ENABLE_HUSH_FUNCTIONS
+# if ENABLE_HUSH_FUNCTIONS
for (funcp = G.top_func; funcp; funcp = funcp->next) {
*pp++ = (char *) "-F";
*pp++ = funcp->name;
*pp++ = funcp->body_as_string;
}
-#endif
+# endif
/* We can pass activated traps here. Say, -Tnn:trap_string
*
* However, POSIX says that subshells reset signals with traps
*/
*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 */
+#if ENABLE_HUSH_FAST
+ G.count_SIGCHLD++;
+//bb_error_msg("[%d] fork in setup_heredoc: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
+#endif
enable_restore_tty_pgrp_on_exit();
#if !BB_MMU
free(to_free);
for (redir = prog->redirects; redir; redir = redir->next) {
if (redir->rd_type == REDIRECT_HEREDOC2) {
/* rd_fd<<HERE case */
- if (squirrel && redir->rd_fd < 3) {
+ if (squirrel && redir->rd_fd < 3
+ && squirrel[redir->rd_fd] < 0
+ ) {
squirrel[redir->rd_fd] = dup(redir->rd_fd);
}
/* for REDIRECT_HEREDOC2, rd_filename holds _contents_
}
if (openfd != redir->rd_fd) {
- if (squirrel && redir->rd_fd < 3) {
+ if (squirrel && redir->rd_fd < 3
+ && squirrel[redir->rd_fd] < 0
+ ) {
squirrel[redir->rd_fd] = dup(redir->rd_fd);
}
if (openfd == REDIRFD_CLOSE) {
}
/* 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)
+static struct function **find_function_slot(const char *name)
{
- const struct function *funcp = G.top_func;
- while (funcp) {
- if (strcmp(name, funcp->name) == 0) {
+ struct function **funcpp = &G.top_func;
+ while (*funcpp) {
+ if (strcmp(name, (*funcpp)->name) == 0) {
break;
}
- funcp = funcp->next;
+ funcpp = &(*funcpp)->next;
}
- debug_printf_exec("found function '%s'\n", name);
+ return funcpp;
+}
+
+static const struct function *find_function(const char *name)
+{
+ const struct function *funcp = *find_function_slot(name);
+ if (funcp)
+ debug_printf_exec("found function '%s'\n", name);
return funcp;
}
/* Note: takes ownership on name ptr */
static struct function *new_function(char *name)
{
- struct function *funcp;
- struct function **funcpp = &G.top_func;
-
- while ((funcp = *funcpp) != NULL) {
- struct command *cmd;
-
- if (strcmp(funcp->name, name) != 0) {
- funcpp = &funcp->next;
- continue;
- }
+ struct function **funcpp = find_function_slot(name);
+ struct function *funcp = *funcpp;
- cmd = funcp->parent_cmd;
+ if (funcp != NULL) {
+ struct command *cmd = funcp->parent_cmd;
debug_printf_exec("func %p parent_cmd %p\n", funcp, cmd);
if (!cmd) {
debug_printf_exec("freeing & replacing function '%s'\n", funcp->name);
* body_as_string was not malloced! */
if (funcp->body) {
free_pipe_list(funcp->body);
-#if !BB_MMU
+# if !BB_MMU
free(funcp->body_as_string);
-#endif
+# endif
}
} else {
debug_printf_exec("reinserting in tree & replacing function '%s'\n", funcp->name);
cmd->argv[0] = funcp->name;
cmd->group = funcp->body;
-#if !BB_MMU
+# if !BB_MMU
cmd->group_as_string = funcp->body_as_string;
-#endif
+# endif
}
- goto skip;
+ } else {
+ debug_printf_exec("remembering new function '%s'\n", name);
+ funcp = *funcpp = xzalloc(sizeof(*funcp));
+ /*funcp->next = NULL;*/
}
- debug_printf_exec("remembering new function '%s'\n", command->argv[0]);
- funcp = *funcpp = xzalloc(sizeof(*funcp));
- /*funcp->next = NULL;*/
- skip:
+
funcp->name = name;
return funcp;
}
-#if BB_MMU
-#define exec_function(nommu_save, funcp, argv) \
+static void unset_func(const char *name)
+{
+ struct function **funcpp = find_function_slot(name);
+ struct function *funcp = *funcpp;
+
+ if (funcp != NULL) {
+ debug_printf_exec("freeing function '%s'\n", funcp->name);
+ *funcpp = funcp->next;
+ /* funcp is unlinked now, deleting it.
+ * Note: if !funcp->body, the function was created by
+ * "-F name body", do not free ->body_as_string
+ * and ->name as they were not malloced. */
+ if (funcp->body) {
+ free_pipe_list(funcp->body);
+ free(funcp->name);
+# if !BB_MMU
+ free(funcp->body_as_string);
+# endif
+ }
+ free(funcp);
+ }
+}
+
+# if BB_MMU
+#define exec_function(to_free, funcp, argv) \
exec_function(funcp, argv)
-#endif
-static void exec_function(nommu_save_t *nommu_save,
+# endif
+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
}
{
int rc;
save_arg_t sv;
+ 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
+# if !BB_MMU
if (!funcp->body) {
/* Function defined by -F */
parse_and_run_string(funcp->body_as_string);
rc = G.last_exitcode;
} else
-#endif
+# endif
{
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
+}
+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)
{
new_env = expand_assignments(argv, assignment_cnt);
#if BB_MMU
- putenv_all(new_env);
+ set_vars_and_save_old(new_env);
free(new_env); /* optional */
+ /* we can also destroy set_vars_and_save_old's return value,
+ * to save memory */
#else
nommu_save->new_env = new_env;
- nommu_save->old_env = putenv_all_and_save_old(new_env);
+ nommu_save->old_vars = set_vars_and_save_old(new_env);
#endif
if (argv_expanded) {
argv = 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
#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 exec '%s'", argv[0]);
- _exit(EXIT_FAILURE);
+ execvp_or_die(argv);
}
/* Called after [v]fork() in run_pipe
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
}
}
len = 0;
- do len += strlen(*argv) + 1; while (*++argv);
- pi->cmdtext = p = xmalloc(len);
+ do {
+ len += strlen(*argv) + 1;
+ } while (*++argv);
+ p = xmalloc(len);
+ pi->cmdtext = p;
argv = pi->cmds[0].argv;
do {
len = strlen(*argv);
static void insert_bg_job(struct pipe *pi)
{
- struct pipe *thejob;
+ struct pipe *job, **jobp;
int i;
/* Linear search for the ID of the job to use */
pi->jobid = 1;
- for (thejob = G.job_list; thejob; thejob = thejob->next)
- if (thejob->jobid >= pi->jobid)
- pi->jobid = thejob->jobid + 1;
-
- /* Add thejob to the list of running jobs */
- if (!G.job_list) {
- thejob = G.job_list = xmalloc(sizeof(*thejob));
- } else {
- for (thejob = G.job_list; thejob->next; thejob = thejob->next)
- continue;
- thejob->next = xmalloc(sizeof(*thejob));
- thejob = thejob->next;
- }
-
- /* Physically copy the struct job */
- memcpy(thejob, pi, sizeof(struct pipe));
- thejob->cmds = xzalloc(sizeof(pi->cmds[0]) * pi->num_cmds);
- /* We cannot copy entire pi->cmds[] vector! Double free()s will happen */
+ for (job = G.job_list; job; job = job->next)
+ if (job->jobid >= pi->jobid)
+ pi->jobid = job->jobid + 1;
+
+ /* Add job to the list of running jobs */
+ jobp = &G.job_list;
+ while ((job = *jobp) != NULL)
+ jobp = &job->next;
+ job = *jobp = xmalloc(sizeof(*job));
+
+ *job = *pi; /* physical copy */
+ job->next = NULL;
+ job->cmds = xzalloc(sizeof(pi->cmds[0]) * pi->num_cmds);
+ /* Cannot copy entire pi->cmds[] vector! This causes double frees */
for (i = 0; i < pi->num_cmds; i++) {
-// TODO: do we really need to have so many fields which are just dead weight
-// at execution stage?
- thejob->cmds[i].pid = pi->cmds[i].pid;
+ job->cmds[i].pid = pi->cmds[i].pid;
/* all other fields are not used and stay zero */
}
- thejob->next = NULL;
- thejob->cmdtext = xstrdup(get_cmdtext(pi));
+ job->cmdtext = xstrdup(get_cmdtext(pi));
- /* We don't wait for background thejobs to return -- append it
- to the list of backgrounded thejobs and leave it alone */
if (G_interactive_fd)
- printf("[%d] %d %s\n", thejob->jobid, thejob->cmds[0].pid, thejob->cmdtext);
- G.last_bg_pid = thejob->cmds[0].pid;
- G.last_jobid = thejob->jobid;
+ printf("[%d] %d %s\n", job->jobid, job->cmds[0].pid, job->cmdtext);
+ /* Last command's pid goes to $! */
+ G.last_bg_pid = job->cmds[job->num_cmds - 1].pid;
+ G.last_jobid = job->jobid;
}
static void remove_bg_job(struct pipe *pi)
debug_printf_jobs("checkjobs %p\n", fg_pipe);
- errno = 0;
-// if (G.handled_SIGCHLD == G.count_SIGCHLD)
-// /* avoid doing syscall, nothing there anyway */
-// return rcode;
-
attributes = WUNTRACED;
if (fg_pipe == NULL)
attributes |= WNOHANG;
+ errno = 0;
+#if ENABLE_HUSH_FAST
+ if (G.handled_SIGCHLD == G.count_SIGCHLD) {
+//bb_error_msg("[%d] checkjobs: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d children?:%d fg_pipe:%p",
+//getpid(), G.count_SIGCHLD, G.handled_SIGCHLD, G.we_have_children, fg_pipe);
+ /* There was neither fork nor SIGCHLD since last waitpid */
+ /* Avoid doing waitpid syscall if possible */
+ if (!G.we_have_children) {
+ errno = ECHILD;
+ return -1;
+ }
+ if (fg_pipe == NULL) { /* is WNOHANG set? */
+ /* We have children, but they did not exit
+ * or stop yet (we saw no SIGCHLD) */
+ return 0;
+ }
+ /* else: !WNOHANG, waitpid will block, can't short-circuit */
+ }
+#endif
+
/* Do we do this right?
* bash-3.00# sleep 20 | false
* <ctrl-Z pressed>
int i;
int dead;
-// i = G.count_SIGCHLD;
+#if ENABLE_HUSH_FAST
+ i = G.count_SIGCHLD;
+#endif
childpid = waitpid(-1, &status, attributes);
if (childpid <= 0) {
if (childpid && errno != ECHILD)
bb_perror_msg("waitpid");
-// else /* Until next SIGCHLD, waitpid's are useless */
-// G.handled_SIGCHLD = i;
+#if ENABLE_HUSH_FAST
+ else { /* Until next SIGCHLD, waitpid's are useless */
+ G.we_have_children = (childpid == 0);
+ G.handled_SIGCHLD = i;
+//bb_error_msg("[%d] checkjobs: waitpid returned <= 0, G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
+ }
+#endif
break;
}
dead = WIFEXITED(status) || WIFSIGNALED(status);
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));
+ }
}
} else {
fg_pipe->cmds[i].is_stopped = 1;
#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 */
{
pid_t p;
int rcode = checkjobs(fg_pipe);
- /* Job finished, move the shell to the foreground */
- p = getpgid(0); /* pgid of our process */
- debug_printf_jobs("fg'ing ourself: getpgid(0)=%d\n", (int)p);
- tcsetpgrp(G_interactive_fd, p);
+ if (G_saved_tty_pgrp) {
+ /* Job finished, move the shell to the foreground */
+ p = getpgrp(); /* our process group id */
+ debug_printf_jobs("fg'ing ourself: getpgrp()=%d\n", (int)p);
+ tcsetpgrp(G_interactive_fd, p);
+ }
return rcode;
}
#endif
* cmd || ... { list } || ...
* If it is, then we can run cmd as a builtin, NOFORK [do we do this?],
* or (if SH_STANDALONE) an applet, and we can run the { list }
- * with run_list(). If it isn't one of these, we fork and exec cmd.
+ * with run_list. If it isn't one of these, we fork and exec cmd.
*
* Cases when we must fork:
* non-single: cmd | cmd
* backgrounded: cmd & { list } &
* subshell: ( list ) [&]
*/
-static int run_pipe(struct pipe *pi)
+static NOINLINE int run_pipe(struct pipe *pi)
{
static const char *const null_ptr = NULL;
int i;
int nextin;
- int pipefds[2]; /* pipefds[0] is for reading */
struct command *command;
char **argv_expanded;
char **argv;
debug_printf_exec("run_pipe start: members:%d\n", pi->num_cmds);
debug_enter();
- USE_HUSH_JOB(pi->pgrp = -1;)
+ IF_HUSH_JOB(pi->pgrp = -1;)
pi->stopped_cmds = 0;
command = &(pi->cmds[0]);
argv_expanded = NULL;
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;
funcp = new_function(command->argv[0]);
/* funcp->name is already set to argv[0] */
funcp->body = command->group;
-#if !BB_MMU
+# if !BB_MMU
funcp->body_as_string = command->group_as_string;
command->group_as_string = NULL;
-#endif
+# endif
command->group = NULL;
command->argv[0] = NULL;
debug_printf_exec("cmd %p has child func at %p\n", command, funcp);
enum { funcp = 0 };
#endif
char **new_env = NULL;
- char **old_env = NULL;
+ struct variable *old_vars = NULL;
if (argv[command->assignment_cnt] == NULL) {
/* Assignments, but no command */
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);
+ }
+
+ /* if someone gives us an empty string: `cmd with empty output` */
+ if (!argv_expanded[0]) {
+ debug_leave();
+ return 0;
+ }
x = find_builtin(argv_expanded[0]);
#if ENABLE_HUSH_FUNCTIONS
rcode = setup_redirects(command, squirrel);
if (rcode == 0) {
new_env = expand_assignments(argv, command->assignment_cnt);
- old_env = putenv_all_and_save_old(new_env);
+ 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);
}
#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
}
clean_up_and_ret:
#endif
restore_redirects(squirrel);
- free_strings_and_unsetenv(new_env, 1);
- putenv_all(old_env);
- /* Free the pointers, but the strings themselves
- * are in environ now, don't use free_strings! */
- free(old_env);
+ unset_vars(new_env);
+ add_vars(old_vars);
clean_up_and_ret1:
free(argv_expanded);
IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;)
rcode = setup_redirects(command, squirrel);
if (rcode == 0) {
new_env = expand_assignments(argv, command->assignment_cnt);
- old_env = putenv_all_and_save_old(new_env);
+ old_vars = set_vars_and_save_old(new_env);
debug_printf_exec(": run_nofork_applet '%s' '%s'...\n",
argv_expanded[0], argv_expanded[1]);
rcode = run_nofork_applet(i, argv_expanded);
nextin = 0;
for (i = 0; i < pi->num_cmds; i++) {
+ struct fd_pair pipefds;
#if !BB_MMU
volatile nommu_save_t nommu_save;
nommu_save.new_env = NULL;
- nommu_save.old_env = NULL;
+ nommu_save.old_vars = NULL;
nommu_save.argv = NULL;
nommu_save.argv_from_re_execing = NULL;
#endif
}
/* pipes are inserted between pairs of commands */
- pipefds[0] = 0;
- pipefds[1] = 1;
+ pipefds.rd = 0;
+ pipefds.wr = 1;
if ((i + 1) < pi->num_cmds)
- xpipe(pipefds);
+ xpiped_pair(pipefds);
command->pid = BB_MMU ? fork() : vfork();
if (!command->pid) { /* child */
#if ENABLE_HUSH_JOB
disable_restore_tty_pgrp_on_exit();
+ CLEAR_RANDOM_T(&G.random_gen); /* or else $RANDOM repeats in child */
/* Every child adds itself to new process group
* with pgid == pid_of_first_child_in_pipe */
pgrp = pi->pgrp;
if (pgrp < 0) /* true for 1st process only */
pgrp = getpid();
- if (setpgid(0, pgrp) == 0 && pi->followup != PIPE_BG) {
+ if (setpgid(0, pgrp) == 0
+ && pi->followup != PIPE_BG
+ && G_saved_tty_pgrp /* we have ctty */
+ ) {
/* We do it in *every* child, not just first,
* to avoid races */
tcsetpgrp(G_interactive_fd, pgrp);
}
}
#endif
- xmove_fd(nextin, 0);
- xmove_fd(pipefds[1], 1); /* write end */
- if (pipefds[0] > 1)
- close(pipefds[0]); /* read end */
+ if (pi->alive_cmds == 0 && pi->followup == PIPE_BG) {
+ /* 1st cmd in backgrounded pipe
+ * should have its stdin /dev/null'ed */
+ close(0);
+ if (open(bb_dev_null, O_RDONLY))
+ xopen("/", O_RDONLY);
+ } else {
+ xmove_fd(nextin, 0);
+ }
+ xmove_fd(pipefds.wr, 1);
+ if (pipefds.rd > 1)
+ close(pipefds.rd);
/* Like bash, explicit redirects override pipes,
* and the pipe fd is available for dup'ing. */
if (setup_redirects(command, NULL))
}
/* parent or error */
+#if ENABLE_HUSH_FAST
+ G.count_SIGCHLD++;
+//bb_error_msg("[%d] fork in run_pipe: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
+#endif
enable_restore_tty_pgrp_on_exit();
#if !BB_MMU
/* Clean up after vforked child */
free(nommu_save.argv);
free(nommu_save.argv_from_re_execing);
- free_strings_and_unsetenv(nommu_save.new_env, 1);
- putenv_all(nommu_save.old_env);
- /* Free the pointers, but the strings themselves
- * are in environ now, don't use free_strings! */
- free(nommu_save.old_env);
+ unset_vars(nommu_save.new_env);
+ add_vars(nommu_save.old_vars);
#endif
free(argv_expanded);
argv_expanded = NULL;
if (i)
close(nextin);
if ((i + 1) < pi->num_cmds)
- close(pipefds[1]); /* write end */
+ close(pipefds.wr);
/* Pass read (output) pipe end to next iteration */
- nextin = pipefds[0];
+ nextin = pipefds.rd;
}
if (!pi->alive_cmds) {
};
static const char *RES[] = {
[RES_NONE ] = "NONE" ,
-#if ENABLE_HUSH_IF
+# if ENABLE_HUSH_IF
[RES_IF ] = "IF" ,
[RES_THEN ] = "THEN" ,
[RES_ELIF ] = "ELIF" ,
[RES_ELSE ] = "ELSE" ,
[RES_FI ] = "FI" ,
-#endif
-#if ENABLE_HUSH_LOOPS
+# endif
+# if ENABLE_HUSH_LOOPS
[RES_FOR ] = "FOR" ,
[RES_WHILE] = "WHILE",
[RES_UNTIL] = "UNTIL",
[RES_DO ] = "DO" ,
[RES_DONE ] = "DONE" ,
-#endif
-#if ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE
+# endif
+# if ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE
[RES_IN ] = "IN" ,
-#endif
-#if ENABLE_HUSH_CASE
- [RES_CASE ] = "CASE" ,
+# endif
+# if ENABLE_HUSH_CASE
+ [RES_CASE ] = "CASE" ,
+ [RES_CASE_IN ] = "CASE_IN" ,
[RES_MATCH] = "MATCH",
- [RES_CASEI] = "CASEI",
+ [RES_CASE_BODY] = "CASE_BODY",
[RES_ESAC ] = "ESAC" ,
-#endif
+# endif
[RES_XXXX ] = "XXXX" ,
[RES_SNTX ] = "SNTX" ,
};
- static const char *const GRPTYPE[] = {
+ static const char *const CMDTYPE[] = {
"{}",
"()",
-#if ENABLE_HUSH_FUNCTIONS
+ "[noglob]",
+# if ENABLE_HUSH_FUNCTIONS
"func()",
-#endif
+# endif
};
int pin, prn;
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++;
pin++;
}
}
-#endif
+#endif /* debug_print_tree */
/* NB: called by pseudo_exec, and therefore must not modify any
* global data until exec/_exit (we can be a child after vfork!) */
#endif
#if ENABLE_HUSH_LOOPS
struct pipe *loop_top = NULL;
- char *for_varname = NULL;
char **for_lcur = NULL;
char **for_list = NULL;
#endif
rcode = G.last_exitcode;
/* Go through list of pipes, (maybe) executing them. */
- for (; pi; pi = USE_HUSH_LOOPS(rword == RES_DONE ? loop_top : ) pi->next) {
+ for (; pi; pi = IF_HUSH_LOOPS(rword == RES_DONE ? loop_top : ) pi->next) {
if (G.flag_SIGINT)
break;
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 */
}
continue;
}
- if (rword == RES_CASEI) { /* inside of a case branch */
+ if (rword == RES_CASE_BODY) { /* inside of a case branch */
if (cond_code != 0)
continue; /* not matched yet, skip this pipe */
}
#endif
rcode = r = run_pipe(pi); /* NB: rcode is a smallint */
if (r != -1) {
- /* We only ran a builtin: rcode is already known
+ /* We ran a builtin, function, or group.
+ * rcode is already known
* and we don't need to wait for anything. */
G.last_exitcode = rcode;
debug_printf_exec(": builtin/func exitcode %d\n", rcode);
rword = RES_DONE;
continue;
}
+#endif
+#if ENABLE_HUSH_FUNCTIONS
+ if (G.flag_return_in_progress == 1) {
+ /* same as "goto check_jobs_and_break" */
+ checkjobs(NULL);
+ break;
+ }
#endif
} else if (pi->followup == PIPE_BG) {
/* What does bash do with attempts to background builtins? */
#endif
#if ENABLE_HUSH_CASE
if (ctx->ctx_res_w == RES_MATCH)
- ctx->ctx_res_w = RES_CASEI;
+ ctx->ctx_res_w = RES_CASE_BODY;
+ if (ctx->ctx_res_w == RES_CASE)
+ ctx->ctx_res_w = RES_CASE_IN;
#endif
ctx->command = NULL; /* trick done_command below */
/* Create the memory for command, roughly:
};
enum {
FLAG_END = (1 << RES_NONE ),
-#if ENABLE_HUSH_IF
+# if ENABLE_HUSH_IF
FLAG_IF = (1 << RES_IF ),
FLAG_THEN = (1 << RES_THEN ),
FLAG_ELIF = (1 << RES_ELIF ),
FLAG_ELSE = (1 << RES_ELSE ),
FLAG_FI = (1 << RES_FI ),
-#endif
-#if ENABLE_HUSH_LOOPS
+# endif
+# if ENABLE_HUSH_LOOPS
FLAG_FOR = (1 << RES_FOR ),
FLAG_WHILE = (1 << RES_WHILE),
FLAG_UNTIL = (1 << RES_UNTIL),
FLAG_DO = (1 << RES_DO ),
FLAG_DONE = (1 << RES_DONE ),
FLAG_IN = (1 << RES_IN ),
-#endif
-#if ENABLE_HUSH_CASE
+# endif
+# if ENABLE_HUSH_CASE
FLAG_MATCH = (1 << RES_MATCH),
FLAG_ESAC = (1 << RES_ESAC ),
-#endif
+# endif
FLAG_START = (1 << RES_XXXX ),
};
* FLAG_START means the word must start a new compound list.
*/
static const struct reserved_combo reserved_list[] = {
-#if ENABLE_HUSH_IF
+# if ENABLE_HUSH_IF
{ "!", RES_NONE, NOT_ASSIGNMENT , 0 },
{ "if", RES_IF, WORD_IS_KEYWORD, FLAG_THEN | FLAG_START },
{ "then", RES_THEN, WORD_IS_KEYWORD, FLAG_ELIF | FLAG_ELSE | FLAG_FI },
{ "elif", RES_ELIF, WORD_IS_KEYWORD, FLAG_THEN },
{ "else", RES_ELSE, WORD_IS_KEYWORD, FLAG_FI },
{ "fi", RES_FI, NOT_ASSIGNMENT , FLAG_END },
-#endif
-#if ENABLE_HUSH_LOOPS
+# endif
+# if ENABLE_HUSH_LOOPS
{ "for", RES_FOR, NOT_ASSIGNMENT , FLAG_IN | FLAG_DO | FLAG_START },
{ "while", RES_WHILE, WORD_IS_KEYWORD, FLAG_DO | FLAG_START },
{ "until", RES_UNTIL, WORD_IS_KEYWORD, FLAG_DO | FLAG_START },
{ "in", RES_IN, NOT_ASSIGNMENT , FLAG_DO },
{ "do", RES_DO, WORD_IS_KEYWORD, FLAG_DONE },
{ "done", RES_DONE, NOT_ASSIGNMENT , FLAG_END },
-#endif
-#if ENABLE_HUSH_CASE
+# endif
+# if ENABLE_HUSH_CASE
{ "case", RES_CASE, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_START },
{ "esac", RES_ESAC, NOT_ASSIGNMENT , FLAG_END },
-#endif
+# endif
};
const struct reserved_combo *r;
*/
static int reserved_word(o_string *word, struct parse_context *ctx)
{
-#if ENABLE_HUSH_CASE
+# if ENABLE_HUSH_CASE
static const struct reserved_combo reserved_match = {
"", RES_MATCH, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_ESAC
};
-#endif
+# endif
const struct reserved_combo *r;
if (word->o_quoted)
return 0;
debug_printf("found reserved word %s, res %d\n", r->literal, r->res);
-#if ENABLE_HUSH_CASE
- if (r->res == RES_IN && ctx->ctx_res_w == RES_CASE)
- /* "case word IN ..." - IN part starts first match part */
+# if ENABLE_HUSH_CASE
+ if (r->res == RES_IN && ctx->ctx_res_w == RES_CASE_IN) {
+ /* "case word IN ..." - IN part starts first MATCH part */
r = &reserved_match;
- else
-#endif
+ } else
+# endif
if (r->flag == 0) { /* '!' */
if (ctx->ctx_inverted) { /* bash doesn't accept '! ! true' */
syntax_error("! ! command");
debug_printf_parse("pop stack %p\n", ctx->stack);
old = ctx->stack;
old->command->group = ctx->list_head;
- old->command->grp_type = GRP_NORMAL;
-#if !BB_MMU
+ old->command->cmd_type = CMD_NORMAL;
+# if !BB_MMU
o_addstr(&old->as_string, ctx->as_string.data);
o_free_unsafe(&ctx->as_string);
old->command->group_as_string = xstrdup(old->as_string.data);
debug_printf_parse("pop, remembering as:'%s'\n",
old->command->group_as_string);
-#endif
+# endif
*ctx = *old; /* physical copy */
free(old);
}
return 1;
}
-#endif
+#endif /* HAS_KEYWORDS */
/* Word is complete, look at it and update parsing context.
* Normal return is 0. Syntax errors return 1.
* 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;
# if ENABLE_HUSH_LOOPS
&& ctx->ctx_res_w != RES_FOR /* ...not after FOR or IN */
&& ctx->ctx_res_w != RES_IN
+# endif
+# if ENABLE_HUSH_CASE
+ && ctx->ctx_res_w != RES_CASE
# endif
) {
debug_printf_parse("checking '%s' for reserved-ness\n", word->data);
(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) {
}
}
command->argv = add_string_to_strings(command->argv, xstrdup(word->data));
-//SEGV, but good idea.
-// command->argv = add_string_to_strings(command->argv, word->data);
-// word->data = NULL;
-// word->length = 0;
debug_print_strings("word appended to argv", command->argv);
}
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) {
{
FILE *pf;
int pid, channel[2];
-#if !BB_MMU
+# if !BB_MMU
char **to_free;
-#endif
+# endif
xpipe(channel);
pid = BB_MMU ? fork() : vfork();
+ (1 << SIGTTIN)
+ (1 << SIGTTOU)
, SIG_IGN);
+ CLEAR_RANDOM_T(&G.random_gen); /* or else $RANDOM repeats in child */
close(channel[0]); /* NB: close _first_, then move fd! */
xmove_fd(channel[1], 1);
/* Prevent it from trying to handle ctrl-z etc */
- USE_HUSH_JOB(G.run_list_level = 1;)
-#if BB_MMU
+ IF_HUSH_JOB(G.run_list_level = 1;)
+ /* Awful hack for `trap` or $(trap).
+ *
+ * http://www.opengroup.org/onlinepubs/009695399/utilities/trap.html
+ * contains an example where "trap" is executed in a subshell:
+ *
+ * save_traps=$(trap)
+ * ...
+ * eval "$save_traps"
+ *
+ * Standard does not say that "trap" in subshell shall print
+ * parent shell's traps. It only says that its output
+ * must have suitable form, but then, in the above example
+ * (which is not supposed to be normative), it implies that.
+ *
+ * bash (and probably other shell) does implement it
+ * (traps are reset to defaults, but "trap" still shows them),
+ * but as a result, "trap" logic is hopelessly messed up:
+ *
+ * # trap
+ * trap -- 'echo Ho' SIGWINCH <--- we have a handler
+ * # (trap) <--- trap is in subshell - no output (correct, traps are reset)
+ * # true | trap <--- trap is in subshell - no output (ditto)
+ * # echo `true | trap` <--- in subshell - output (but traps are reset!)
+ * trap -- 'echo Ho' SIGWINCH
+ * # echo `(trap)` <--- in subshell in subshell - output
+ * trap -- 'echo Ho' SIGWINCH
+ * # echo `true | (trap)` <--- in subshell in subshell in subshell - output!
+ * trap -- 'echo Ho' SIGWINCH
+ *
+ * The rules when to forget and when to not forget traps
+ * get really complex and nonsensical.
+ *
+ * Our solution: ONLY bare $(trap) or `trap` is special.
+ */
+ s = skip_whitespace(s);
+ if (strncmp(s, "trap", 4) == 0 && (*skip_whitespace(s + 4) == '\0'))
+ {
+ static const char *const argv[] = { NULL, NULL };
+ builtin_trap((char**)argv);
+ exit(0); /* not _exit() - we need to fflush */
+ }
+# if BB_MMU
reset_traps_to_defaults();
parse_and_run_string(s);
_exit(G.last_exitcode);
-#else
+# else
/* We re-execute after vfork on NOMMU. This makes this script safe:
* yes "0123456789012345678901234567890" | dd bs=32 count=64k >BIG
* huge=`cat BIG` # was blocking here forever
re_execute_shell(&to_free,
s,
G.global_argv[0],
- G.global_argv + 1);
-#endif
+ G.global_argv + 1,
+ NULL);
+# endif
}
/* parent */
+# if ENABLE_HUSH_FAST
+ G.count_SIGCHLD++;
+//bb_error_msg("[%d] fork in generate_stream_from_string: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
+# endif
enable_restore_tty_pgrp_on_exit();
-#if !BB_MMU
+# if !BB_MMU
free(to_free);
-#endif
+# endif
close(channel[1]);
pf = fdopen(channel[0], "r");
return pf;
debug_printf("closed FILE from child. return 0\n");
return 0;
}
-#endif
+#endif /* ENABLE_HUSH_TICK */
static int parse_group(o_string *dest, struct parse_context *ctx,
struct in_str *input, int ch)
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;
while (1) {
ch = i_getch(input);
nommu_addchr(as_string, ch);
- if (ch == '}')
+ if (ch == '}') {
break;
+ }
if (first_char) {
- if (ch == '#')
+ if (ch == '#') {
/* ${#var}: length of var contents */
goto char_ok;
- else if (isdigit(ch)) {
+ }
+ 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
o_addchr(dest, ch | quote_mask);
quote_mask = 0;
first_char = false;
- }
+ } /* while (1) */
o_addchr(dest, SPECIAL_VAR_SYMBOL);
break;
}
-#if (ENABLE_SH_MATH_SUPPORT || ENABLE_HUSH_TICK)
+#if ENABLE_SH_MATH_SUPPORT || ENABLE_HUSH_TICK
case '(': {
# if !BB_MMU
int pos;
goto make_var;
}
/* else: it's $_ */
- /* TODO: */
- /* $_ Shell or shell script name; or last cmd name */
+ /* TODO: $_ and $-: */
+ /* $_ Shell or shell script name; or last argument of last command
+ * (if last command wasn't a pipe; if it was, bash sets $_ to "");
+ * but in command's env, set to full pathname used to invoke it */
/* $- Option flags set by set builtin or shell options (-i etc) */
default:
o_addQchr(dest, '$');
if (ch != '\n') {
next = i_peek(input);
}
- debug_printf_parse(": ch=%c (%d) escape=%d\n",
+ debug_printf_parse("\" ch=%c (%d) escape=%d\n",
ch, ch, dest->o_escape);
if (ch == '\\') {
if (next == EOF) {
* "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("$`\"\\", next) != NULL) {
- o_addqchr(dest, i_getch(input));
+ if (strchr("$`\"\\\n", next) != NULL) {
+ ch = i_getch(input);
+ if (ch != '\n') {
+ o_addqchr(dest, ch);
+ nommu_addchr(as_string, ch);
+ }
} else {
o_addqchr(dest, '\\');
+ nommu_addchr(as_string, '\\');
}
goto again;
}
* found. When recursing, quote state is passed in via dest->o_escape.
*/
debug_printf_parse("parse_stream entered, end_trigger='%c'\n",
- end_trigger ? : 'X');
+ end_trigger ? end_trigger : 'X');
debug_enter();
+ /* If very first arg is "" or '', dest.data may end up NULL.
+ * Preventing this: */
+ o_addchr(&dest, '\0');
+ dest.length = 0;
+
G.ifs = get_local_var_value("IFS");
if (G.ifs == NULL)
G.ifs = " \t\n";
if (heredoc_cnt) {
syntax_error_unterm_str("here document");
- xfunc_die();
+ goto parse_error;
+ }
+ /* end_trigger == '}' case errors out earlier,
+ * checking only ')' */
+ if (end_trigger == ')') {
+ syntax_error_unterm_ch('('); /* exits */
+ /* goto parse_error; */
}
+
if (done_word(&dest, &ctx)) {
- xfunc_die();
+ goto parse_error;
}
o_free(&dest);
done_pipe(&ctx, PIPE_SEQ);
nommu_addchr(&ctx.as_string, ch);
is_ifs = strchr(G.ifs, ch);
is_special = strchr("<>;&|(){}#'" /* special outside of "str" */
- "\\$\"" USE_HUSH_TICK("`") /* always special */
+ "\\$\"" IF_HUSH_TICK("`") /* always special */
, ch);
if (!is_special && !is_ifs) { /* ordinary char */
+ ordinary_char:
o_addQchr(&dest, ch);
if ((dest.o_assignment == MAYBE_ASSIGNMENT
|| dest.o_assignment == WORD_IS_KEYWORD)
* will still trigger for us */
}
}
+
+ /* "cmd}" or "cmd }..." without semicolon or &:
+ * } is an ordinary char in this case, even inside { cmd; }
+ * Pathological example: { ""}; } should exec "}" cmd
+ */
+ if (ch == '}') {
+ if (!IS_NULL_CMD(ctx.command) /* cmd } */
+ || dest.length != 0 /* word} */
+ || dest.o_quoted /* ""} */
+ ) {
+ goto ordinary_char;
+ }
+ if (!IS_NULL_PIPE(ctx.pipe)) /* cmd | } */
+ goto skip_end_trigger;
+ /* else: } does terminate a group */
+ }
+
if (end_trigger && end_trigger == ch
- && (heredoc_cnt == 0 || end_trigger != ';')
+ && (ch != ';' || heredoc_cnt == 0)
+#if ENABLE_HUSH_CASE
+ && (ch != ')'
+ || ctx.ctx_res_w != RES_MATCH
+ || (!dest.o_quoted && strcmp(dest.data, "esac") == 0)
+ )
+#endif
) {
if (heredoc_cnt) {
/* This is technically valid:
if (done_word(&dest, &ctx)) {
goto parse_error;
}
- /* Disallow "{ cmd }" without semicolon or & */
- //debug_printf_parse("null pi %d\n", IS_NULL_PIPE(ctx.pipe))
- //debug_printf_parse("null cmd %d\n", IS_NULL_CMD(ctx.command))
- if (ch == '}'
- && !(IS_NULL_PIPE(ctx.pipe) && IS_NULL_CMD(ctx.command))
- ) {
- syntax_error_unexpected_ch(ch);
- goto parse_error;
- }
done_pipe(&ctx, PIPE_SEQ);
dest.o_assignment = MAYBE_ASSIGNMENT;
/* Do we sit outside of any if's, loops or case's? */
return ctx.list_head;
}
}
+ skip_end_trigger:
if (is_ifs)
continue;
dest.o_assignment = NOT_ASSIGNMENT;
}
+ /* Note: nommu_addchr(&ctx.as_string, ch) is already done */
+
switch (ch) {
case '#':
if (dest.length == 0) {
syntax_error("\\<eof>");
xfunc_die();
}
- o_addchr(&dest, '\\');
ch = i_getch(input);
- nommu_addchr(&ctx.as_string, ch);
- o_addchr(&dest, ch);
- /* Example: echo Hello \2>file
- * we need to know that word 2 is quoted */
- dest.o_quoted = 1;
+ if (ch != '\n') {
+ o_addchr(&dest, '\\');
+ /*nommu_addchr(&ctx.as_string, '\\'); - already done */
+ o_addchr(&dest, ch);
+ nommu_addchr(&ctx.as_string, ch);
+ /* Example: echo Hello \2>file
+ * we need to know that word 2 is quoted */
+ dest.o_quoted = 1;
+ }
+#if !BB_MMU
+ else {
+ /* It's "\<newline>". Remove trailing '\' from ctx.as_string */
+ ctx.as_string.data[--ctx.as_string.length] = '\0';
+ }
+#endif
break;
case '$':
if (handle_dollar(&ctx.as_string, &dest, input) != 0) {
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 '"':
break;
ch = i_getch(input);
nommu_addchr(&ctx.as_string, ch);
- if (ctx.ctx_res_w == RES_CASEI) {
+ if (ctx.ctx_res_w == RES_CASE_BODY) {
ctx.ctx_dsemicolon = 1;
ctx.ctx_res_w = RES_MATCH;
break;
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
}
/* Discard cached input, force prompt */
input->p = NULL;
- USE_HUSH_INTERACTIVE(input->promptme = 1;)
+ IF_HUSH_INTERACTIVE(input->promptme = 1;)
goto reset;
}
}
unsigned mask;
mask = (1 << SIGQUIT);
- if (G_interactive_fd)
+ if (G_interactive_fd) {
mask = (1 << SIGQUIT) | SPECIAL_INTERACTIVE_SIGS;
+ if (G_saved_tty_pgrp) /* we have ctty, job control sigs work */
+ mask |= SPECIAL_JOB_SIGS;
+ }
G.non_DFL_mask = mask;
if (!second_time)
second_time ? NULL : &G.inherited_set);
/* POSIX allows shell to re-enable SIGCHLD
* even if it was SIG_IGN on entry */
-// G.count_SIGCHLD++; /* ensure it is != G.handled_SIGCHLD */
+#if ENABLE_HUSH_FAST
+ G.count_SIGCHLD++; /* ensure it is != G.handled_SIGCHLD */
+ if (!second_time)
+ signal(SIGCHLD, SIGCHLD_handler);
+#else
if (!second_time)
- signal(SIGCHLD, SIG_DFL); // SIGCHLD_handler);
+ signal(SIGCHLD, SIG_DFL);
+#endif
}
#if ENABLE_HUSH_JOB
/* bash 3.2 seems to handle these just like 'fatal' ones */
maybe_set_to_sigexit(SIGPIPE);
maybe_set_to_sigexit(SIGALRM);
-//TODO: disable and move down when proper SIGHUP handling is added
- maybe_set_to_sigexit(SIGHUP );
- /* if we are interactive, [SIGHUP,] SIGTERM and SIGINT are masked.
+ /* if we are interactive, SIGHUP, SIGTERM and SIGINT are masked.
* if we aren't interactive... but in this case
* we never want to restore pgrp on exit, and this fn is not called */
+ /*maybe_set_to_sigexit(SIGHUP );*/
/*maybe_set_to_sigexit(SIGTERM);*/
/*maybe_set_to_sigexit(SIGINT );*/
}
};
int signal_mask_is_inited = 0;
int opt;
+ unsigned builtin_argc;
char **e;
struct variable *cur_var;
}
e++;
}
+ /* reinstate HUSH_VERSION */
debug_printf_env("putenv '%s'\n", hush_version_str);
- putenv((char *)hush_version_str); /* reinstate HUSH_VERSION */
+ putenv((char *)hush_version_str);
+
+ /* Export PWD */
+ set_pwd_var(/*exp:*/ 1);
+ /* bash also exports SHLVL and _,
+ * and sets (but doesn't export) the following variables:
+ * BASH=/bin/bash
+ * BASH_VERSINFO=([0]="3" [1]="2" [2]="0" [3]="1" [4]="release" [5]="i386-pc-linux-gnu")
+ * BASH_VERSION='3.2.0(1)-release'
+ * HOSTTYPE=i386
+ * MACHTYPE=i386-pc-linux-gnu
+ * OSTYPE=linux-gnu
+ * HOSTNAME=<xxxxxxxxxx>
+ * PPID=<NNNNN> - we also do it elsewhere
+ * EUID=<NNNNN>
+ * UID=<NNNNN>
+ * GROUPS=()
+ * LINES=<NNN>
+ * COLUMNS=<NNN>
+ * BASH_ARGC=()
+ * BASH_ARGV=()
+ * BASH_LINENO=()
+ * BASH_SOURCE=()
+ * DIRSTACK=()
+ * PIPESTATUS=([0]="0")
+ * HISTFILE=/<xxx>/.bash_history
+ * HISTFILESIZE=500
+ * HISTSIZE=500
+ * MAILCHECK=60
+ * PATH=/usr/gnu/bin:/usr/local/bin:/bin:/usr/bin:.
+ * SHELL=/bin/bash
+ * SHELLOPTS=braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitor
+ * TERM=dumb
+ * OPTERR=1
+ * OPTIND=1
+ * IFS=$' \t\n'
+ * PS1='\s-\v\$ '
+ * PS2='> '
+ * PS4='+ '
+ */
+
#if ENABLE_FEATURE_EDITING
G.line_input_state = new_line_input_t(FOR_SHELL);
#endif
G.global_argc = argc;
G.global_argv = argv;
/* Initialize some more globals to non-zero values */
- set_cwd();
-#if ENABLE_HUSH_INTERACTIVE
- if (ENABLE_FEATURE_EDITING)
- cmdedit_set_initial_prompt();
- G.PS2 = "> ";
-#endif
+ cmdedit_update_prompt();
if (setjmp(die_jmp)) {
/* xfunc has failed! die die die */
/* Parse options */
/* http://www.opengroup.org/onlinepubs/9699919799/utilities/sh.html */
+ builtin_argc = 0;
while (1) {
- opt = getopt(argc, argv, "c:xins"
+ opt = getopt(argc, argv, "+c:xins"
#if !BB_MMU
"<:$:R:V:"
# if ENABLE_HUSH_FUNCTIONS
break;
switch (opt) {
case 'c':
- if (!G.root_pid)
+ /* 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.root_ppid = getppid();
+ }
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;
case '$':
G.root_pid = bb_strtou(optarg, &optarg, 16);
optarg++;
+ G.root_ppid = bb_strtou(optarg, &optarg, 16);
+ optarg++;
G.last_bg_pid = bb_strtou(optarg, &optarg, 16);
optarg++;
G.last_exitcode = bb_strtou(optarg, &optarg, 16);
+ 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': {
}
} /* option parsing loop */
- if (!G.root_pid)
+ if (!G.root_pid) {
G.root_pid = getpid();
+ G.root_ppid = getppid();
+ }
/* If we are login shell... */
if (argv[0] && argv[0][0] == '-') {
FILE *input;
- /* TODO: what should argv be while sourcing /etc/profile? */
debug_printf("sourcing /etc/profile\n");
input = fopen_for_read("/etc/profile");
if (input != NULL) {
/* 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
*/
#if ENABLE_HUSH_JOB
if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) {
- G.saved_tty_pgrp = tcgetpgrp(STDIN_FILENO);
- debug_printf("saved_tty_pgrp:%d\n", G.saved_tty_pgrp);
-//TODO: "interactive" and "have job control" are two different things.
-//If tcgetpgrp fails here, "have job control" is false, but "interactive"
-//should stay on! Currently, we mix these into one.
- if (G.saved_tty_pgrp >= 0) {
- /* try to dup stdin to high fd#, >= 255 */
- G_interactive_fd = fcntl(STDIN_FILENO, F_DUPFD, 255);
+ G_saved_tty_pgrp = tcgetpgrp(STDIN_FILENO);
+ debug_printf("saved_tty_pgrp:%d\n", G_saved_tty_pgrp);
+ if (G_saved_tty_pgrp < 0)
+ G_saved_tty_pgrp = 0;
+
+ /* try to dup stdin to high fd#, >= 255 */
+ G_interactive_fd = fcntl(STDIN_FILENO, F_DUPFD, 255);
+ if (G_interactive_fd < 0) {
+ /* try to dup to any fd */
+ G_interactive_fd = dup(STDIN_FILENO);
if (G_interactive_fd < 0) {
- /* try to dup to any fd */
- G_interactive_fd = dup(STDIN_FILENO);
- if (G_interactive_fd < 0)
- /* give up */
- G_interactive_fd = 0;
+ /* give up */
+ G_interactive_fd = 0;
+ G_saved_tty_pgrp = 0;
}
-// TODO: track & disallow any attempts of user
-// to (inadvertently) close/redirect it
}
+// TODO: track & disallow any attempts of user
+// to (inadvertently) close/redirect G_interactive_fd
}
debug_printf("interactive_fd:%d\n", G_interactive_fd);
if (G_interactive_fd) {
- pid_t shell_pgrp;
-
- /* We are indeed interactive shell, and we will perform
- * job control. Setting up for that. */
-
close_on_exec_on(G_interactive_fd);
- /* If we were run as 'hush &', sleep until we are
- * in the foreground (tty pgrp == our pgrp).
- * If we get started under a job aware app (like bash),
- * make sure we are now in charge so we don't fight over
- * who gets the foreground */
- while (1) {
- shell_pgrp = getpgrp();
- G.saved_tty_pgrp = tcgetpgrp(G_interactive_fd);
- if (G.saved_tty_pgrp == shell_pgrp)
- break;
- /* send TTIN to ourself (should stop us) */
- kill(- shell_pgrp, SIGTTIN);
+
+ if (G_saved_tty_pgrp) {
+ /* If we were run as 'hush &', sleep until we are
+ * in the foreground (tty pgrp == our pgrp).
+ * If we get started under a job aware app (like bash),
+ * make sure we are now in charge so we don't fight over
+ * who gets the foreground */
+ while (1) {
+ pid_t shell_pgrp = getpgrp();
+ G_saved_tty_pgrp = tcgetpgrp(G_interactive_fd);
+ if (G_saved_tty_pgrp == shell_pgrp)
+ break;
+ /* send TTIN to ourself (should stop us) */
+ kill(- shell_pgrp, SIGTTIN);
+ }
}
+
/* Block some signals */
block_signals(signal_mask_is_inited);
- /* Set other signals to restore saved_tty_pgrp */
- set_fatal_handlers();
- /* Put ourselves in our own process group */
- bb_setpgrp(); /* is the same as setpgid(our_pid, our_pid); */
- /* Grab control of the terminal */
- tcsetpgrp(G_interactive_fd, getpid());
+
+ if (G_saved_tty_pgrp) {
+ /* Set other signals to restore saved_tty_pgrp */
+ set_fatal_handlers();
+ /* Put ourselves in our own process group
+ * (bash, too, does this only if ctty is available) */
+ bb_setpgrp(); /* is the same as setpgid(our_pid, our_pid); */
+ /* Grab control of the terminal */
+ tcsetpgrp(G_interactive_fd, getpid());
+ }
/* -1 is special - makes xfuncs longjmp, not exit
* (we reset die_sleep = 0 whereever we [v]fork) */
enable_restore_tty_pgrp_on_exit(); /* sets die_sleep = -1 */
*/
if (!ENABLE_FEATURE_SH_EXTRA_QUIET && G_interactive_fd) {
- printf("\n\n%s hush - the humble shell\n", bb_banner);
- printf("Enter 'help' for a list of built-in commands.\n\n");
+ /* note: ash and hush share this string */
+ printf("\n\n%s %s\n"
+ IF_HUSH_HELP("Enter 'help' for a list of built-in commands.\n")
+ "\n",
+ bb_banner,
+ "hush - the humble shell"
+ );
}
parse_and_run_file(stdin);
int lash_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int lash_main(int argc, char **argv)
{
- //bb_error_msg("lash is deprecated, please use hush instead");
+ bb_error_msg("lash is deprecated, please use hush instead");
+ return hush_main(argc, argv);
+}
+#endif
+
+#if ENABLE_MSH
+int msh_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int msh_main(int argc, char **argv)
+{
+ //bb_error_msg("msh is deprecated, please use hush instead");
return hush_main(argc, argv);
}
#endif
/*
* Built-ins
*/
-static int builtin_trap(char **argv)
-{
- int i;
- int sig;
- char *new_cmd;
-
- if (!G.traps)
- G.traps = xzalloc(sizeof(G.traps[0]) * NSIG);
-
- argv++;
- if (!*argv) {
- /* No args: print all trapped. This isn't 100% correct as we
- * should be escaping the cmd so that it can be pasted back in
- */
- for (i = 0; i < NSIG; ++i)
- if (G.traps[i])
- printf("trap -- '%s' %s\n", G.traps[i], get_signame(i));
- return EXIT_SUCCESS;
- }
-
- new_cmd = NULL;
- i = 0;
- /* If first arg is decimal: reset all specified signals */
- sig = bb_strtou(*argv, NULL, 10);
- if (errno == 0) {
- int ret;
- set_all:
- ret = EXIT_SUCCESS;
- while (*argv) {
- sig = get_signum(*argv++);
- if (sig < 0 || sig >= NSIG) {
- ret = EXIT_FAILURE;
- /* Mimic bash message exactly */
- bb_perror_msg("trap: %s: invalid signal specification", argv[i]);
- continue;
- }
-
- free(G.traps[sig]);
- G.traps[sig] = xstrdup(new_cmd);
-
- debug_printf("trap: setting SIG%s (%i) to '%s'",
- get_signame(sig), sig, G.traps[sig]);
-
- /* There is no signal for 0 (EXIT) */
- if (sig == 0)
- continue;
-
- if (new_cmd) {
- sigaddset(&G.blocked_set, sig);
- } else {
- /* There was a trap handler, we are removing it
- * (if sig has non-DFL handling,
- * we don't need to do anything) */
- if (sig < 32 && (G.non_DFL_mask & (1 << sig)))
- continue;
- sigdelset(&G.blocked_set, sig);
- }
- sigprocmask(SIG_SETMASK, &G.blocked_set, NULL);
- }
- return ret;
- }
-
- /* First arg is "-": reset all specified to default */
- /* First arg is "": ignore all specified */
- /* Everything else: execute first arg upon signal */
- if (!argv[1]) {
- bb_error_msg("trap: invalid arguments");
- return EXIT_FAILURE;
- }
- if (NOT_LONE_DASH(*argv))
- new_cmd = *argv;
- argv++;
- goto set_all;
-}
-
-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 run_applet_main(char **argv, int (*applet_main_func)(int argc, char **argv))
{
int argc = 0;
while (*argv) {
argc++;
argv++;
}
- return test_main(argc, argv - argc);
+ return applet_main_func(argc, argv - argc);
}
-static int builtin_echo(char **argv)
+static int FAST_FUNC builtin_test(char **argv)
{
- int argc = 0;
- while (*argv) {
- argc++;
- argv++;
- }
- return echo_main(argc, argv - argc);
+ return run_applet_main(argv, test_main);
+}
+
+static int FAST_FUNC builtin_echo(char **argv)
+{
+ return run_applet_main(argv, echo_main);
}
-static int builtin_eval(char **argv)
+#if ENABLE_PRINTF
+static int FAST_FUNC builtin_printf(char **argv)
+{
+ return run_applet_main(argv, printf_main);
+}
+#endif
+
+static int FAST_FUNC builtin_eval(char **argv)
{
int rcode = EXIT_SUCCESS;
return rcode;
}
-static int builtin_cd(char **argv)
+static int FAST_FUNC builtin_cd(char **argv)
{
const char *newdir = argv[1];
if (newdir == NULL) {
* bash says "bash: cd: HOME not set" and does nothing
* (exitcode 1)
*/
- newdir = getenv("HOME") ? : "/";
+ const char *home = get_local_var_value("HOME");
+ newdir = home ? home : "/";
}
if (chdir(newdir)) {
/* Mimic bash message exactly */
bb_perror_msg("cd: %s", newdir);
return EXIT_FAILURE;
}
- set_cwd();
+ /* Read current dir (get_cwd(1) is inside) and set PWD.
+ * Note: do not enforce exporting. If PWD was unset or unexported,
+ * set it again, but do not export. bash does the same.
+ */
+ set_pwd_var(/*exp:*/ 0);
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 */
- {
-#if !BB_MMU
- nommu_save_t dummy;
-#endif
-// TODO: if exec fails, bash does NOT exit! We do...
- pseudo_exec_argv(&dummy, argv, 0, NULL);
- /* never returns */
- }
+
+ /* Careful: we can end up here after [v]fork. Do not restore
+ * tty pgrp then, only top-level shell process does that */
+ if (G_saved_tty_pgrp && getpid() == G.root_pid)
+ tcsetpgrp(G_interactive_fd, G_saved_tty_pgrp);
+
+ /* TODO: if exec fails, bash does NOT exit! We do.
+ * We'll need to undo sigprocmask (it's inside execvp_or_die)
+ * and tcsetpgrp, and this is inherently racy.
+ */
+ execvp_or_die(argv);
}
-static int builtin_exit(char **argv)
+static int FAST_FUNC builtin_exit(char **argv)
{
debug_printf_exec("%s()\n", __func__);
-// TODO: bash does it ONLY on top-level sh exit (+interacive only?)
- //puts("exit"); /* bash does it */
-// TODO: warn if we have background jobs: "There are stopped jobs"
-// On second consecutive 'exit', exit anyway.
-// perhaps use G.exiting = -1 as indicator "last cmd was exit"
+
+ /* interactive bash:
+ * # trap "echo EEE" EXIT
+ * # exit
+ * exit
+ * There are stopped jobs.
+ * (if there are _stopped_ jobs, running ones don't count)
+ * # exit
+ * exit
+ # EEE (then bash exits)
+ *
+ * we can use G.exiting = -1 as indicator "last cmd was exit"
+ */
/* note: EXIT trap is run by hush_exit */
if (*++argv == NULL)
hush_exit(xatoi(*argv) & 0xff);
}
-static int builtin_export(char **argv)
+static void print_escaped(const char *s)
{
- if (*++argv == NULL) {
+ if (*s == '\'')
+ goto squote;
+ do {
+ 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 == '\'');
+ putchar('"');
+ } while (*s);
+}
+
+#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;
+
+ /* So far we do not check that name is valid (TODO?) */
+
+ if (strchr(name, '=') == NULL) {
+ struct variable *var;
+
+ var = get_local_var(name);
+ if (exp == -1) { /* unexporting? */
+ /* export -n NAME (without =VALUE) */
+ if (var) {
+ var->flg_export = 0;
+ debug_printf_env("%s: unsetenv '%s'\n", __func__, name);
+ unsetenv(name);
+ } /* else: export -n NOT_EXISTING_VAR: no-op */
+ 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/making local NAME=VALUE */
+ name = xstrdup(name);
+ }
+ 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) {
continue;
/* export var= */
printf("export %.*s", (int)(p - s) + 1, s);
- s = p + 1;
- while (*s) {
- if (*s != '\'') {
- p = strchrnul(s, '\'');
- /* print 'xxxx' */
- printf("'%.*s'", (int)(p - s), s);
- if (*p == '\0')
- break;
- s = p;
- }
- /* s points to '; print ''...'''" */
- putchar('"');
- do putchar('\''); while (*++s == '\'');
- putchar('"');
- }
+ print_escaped(p + 1);
putchar('\n');
#endif
}
- fflush(stdout);
+ /*fflush(stdout); - done after each builtin anyway */
}
return EXIT_SUCCESS;
}
- do {
- const char *value;
- char *name = *argv;
+ helper_export_local(argv, (opt_unexport ? -1 : 1), 0);
- value = strchr(name, '=');
- if (!value) {
- /* They are exporting something without a =VALUE */
- struct variable *var;
+ return EXIT_SUCCESS;
+}
- var = get_local_var(name);
- if (var) {
- var->flg_export = 1;
- debug_printf_env("%s: putenv '%s'\n", __func__, var->varstr);
- putenv(var->varstr);
+#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 FAST_FUNC builtin_trap(char **argv)
+{
+ int sig;
+ char *new_cmd;
+
+ if (!G.traps)
+ G.traps = xzalloc(sizeof(G.traps[0]) * NSIG);
+
+ argv++;
+ if (!*argv) {
+ int i;
+ /* No args: print all trapped */
+ for (i = 0; i < NSIG; ++i) {
+ if (G.traps[i]) {
+ printf("trap -- ");
+ print_escaped(G.traps[i]);
+ /* note: bash adds "SIG", but only if invoked
+ * as "bash". If called as "sh", or if set -o posix,
+ * then it prints short signal names.
+ * We are printing short names: */
+ printf(" %s\n", get_signame(i));
+ }
+ }
+ /*fflush(stdout); - done after each builtin anyway */
+ return EXIT_SUCCESS;
+ }
+
+ new_cmd = NULL;
+ /* If first arg is a number: reset all specified signals */
+ sig = bb_strtou(*argv, NULL, 10);
+ if (errno == 0) {
+ int ret;
+ process_sig_list:
+ ret = EXIT_SUCCESS;
+ while (*argv) {
+ sig = get_signum(*argv++);
+ if (sig < 0 || sig >= NSIG) {
+ ret = EXIT_FAILURE;
+ /* Mimic bash message exactly */
+ bb_perror_msg("trap: %s: invalid signal specification", argv[-1]);
+ continue;
+ }
+
+ free(G.traps[sig]);
+ G.traps[sig] = xstrdup(new_cmd);
+
+ debug_printf("trap: setting SIG%s (%i) to '%s'",
+ get_signame(sig), sig, G.traps[sig]);
+
+ /* There is no signal for 0 (EXIT) */
+ if (sig == 0)
+ continue;
+
+ if (new_cmd) {
+ sigaddset(&G.blocked_set, sig);
+ } else {
+ /* There was a trap handler, we are removing it
+ * (if sig has non-DFL handling,
+ * we don't need to do anything) */
+ if (sig < 32 && (G.non_DFL_mask & (1 << sig)))
+ continue;
+ sigdelset(&G.blocked_set, sig);
}
- /* bash does not return an error when trying to export
- * an undefined variable. Do likewise. */
+ }
+ sigprocmask(SIG_SETMASK, &G.blocked_set, NULL);
+ return ret;
+ }
+
+ if (!argv[1]) { /* no second arg */
+ bb_error_msg("trap: invalid arguments");
+ return EXIT_FAILURE;
+ }
+
+ /* First arg is "-": reset all specified to default */
+ /* First arg is "--": skip it, the rest is "handler SIGs..." */
+ /* Everything else: set arg as signal handler
+ * (includes "" case, which ignores signal) */
+ if (argv[0][0] == '-') {
+ if (argv[0][1] == '\0') { /* "-" */
+ /* new_cmd remains NULL: "reset these sigs" */
+ goto reset_traps;
+ }
+ if (argv[0][1] == '-' && argv[0][2] == '\0') { /* "--" */
+ argv++;
+ }
+ /* else: "-something", no special meaning */
+ }
+ new_cmd = *argv;
+ reset_traps:
+ argv++;
+ 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;
}
- set_local_var(xstrdup(name), 1, 0);
- } while (*++argv);
- return EXIT_SUCCESS;
+ 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;
if (!G_interactive_fd)
return EXIT_FAILURE;
+
/* If they gave us no args, assume they want the last backgrounded task */
if (!argv[1]) {
for (pi = G.job_list; pi; pi = pi->next) {
bb_error_msg("%s: %d: no such job", argv[0], jobnum);
return EXIT_FAILURE;
found:
- // TODO: bash prints a string representation
- // of job being foregrounded (like "sleep 1 | cat")
- if (argv[0][0] == 'f') {
+ /* TODO: bash prints a string representation
+ * of job being foregrounded (like "sleep 1 | cat") */
+ if (argv[0][0] == 'f' && G_saved_tty_pgrp) {
/* Put the job into the foreground. */
tcsetpgrp(G_interactive_fd, pi->pgrp);
}
#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;
+# ifdef M_TRIM_THRESHOLD
+ /* Optional. Reduces probability of false positives */
+ malloc_trim(0);
+# endif
/* Crude attempt to find where "free memory" starts,
* sans fragmentation. */
p = malloc(240);
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)
{
+ char *arg_path;
FILE *input;
save_arg_t sv;
+#if ENABLE_HUSH_FUNCTIONS
+ smallint sv_flg;
+#endif
if (*++argv == NULL)
return EXIT_FAILURE;
- /* TODO: search through $PATH is missing */
- 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;
}
close_on_exec_on(fileno(input));
- /* Now run the file */
+#if ENABLE_HUSH_FUNCTIONS
+ sv_flg = G.flag_return_in_progress;
+ /* "we are inside sourced file, ok to use return" */
+ G.flag_return_in_progress = -1;
+#endif
save_and_replace_G_args(&sv, argv);
+
parse_and_run_file(input);
- restore_G_args(&sv, argv);
fclose(input);
+ restore_G_args(&sv, argv);
+#if ENABLE_HUSH_FUNCTIONS
+ G.flag_return_in_progress = sv_flg;
+#endif
+
return G.last_exitcode;
}
-static int builtin_umask(char **argv)
+static int FAST_FUNC builtin_umask(char **argv)
{
- mode_t new_umask;
- const char *arg = argv[1];
- if (arg) {
-//TODO: umask may take chmod-like symbolic masks
- new_umask = bb_strtou(arg, NULL, 8);
- if (errno) {
- //Message? bash examples:
- //bash: umask: 'q': invalid symbolic mode operator
- //bash: umask: 999: octal number out of range
- return EXIT_FAILURE;
+ int rc;
+ mode_t mask;
+
+ mask = umask(0);
+ if (argv[1]) {
+ mode_t old_mask = mask;
+
+ mask ^= 0777;
+ rc = bb_parse_mode(argv[1], &mask);
+ mask ^= 0777;
+ if (rc == 0) {
+ mask = old_mask;
+ /* bash messages:
+ * 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]);
}
} else {
- new_umask = umask(0);
- printf("%.3o\n", (unsigned) new_umask);
- /* fall through and restore new_umask which we set to 0 */
+ rc = 1;
+ /* Mimic bash */
+ printf("%04o\n", (unsigned) mask);
+ /* fall through and restore mask which we set to 0 */
}
- umask(new_umask);
- return EXIT_SUCCESS;
+ umask(mask);
+
+ return !rc; /* rc != 0 - success */
}
/* 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;
- char var;
+ unsigned opts;
- if (!*++argv)
- return EXIT_SUCCESS;
-
- var = 'v';
- if (argv[0][0] == '-') {
- switch (argv[0][1]) {
- case 'v':
- case 'f':
- var = argv[0][1];
- break;
- default:
- bb_error_msg("unset: %s: invalid option", *argv);
- return EXIT_FAILURE;
- }
-//TODO: disallow "unset -vf ..." too
- argv++;
+ /* "!": do not abort on errors */
+ /* "+": stop at 1st non-option */
+ opts = getopt32(argv, "!+vf");
+ if (opts == (unsigned)-1)
+ return EXIT_FAILURE;
+ if (opts == 3) {
+ bb_error_msg("unset: -v and -f are exclusive");
+ return EXIT_FAILURE;
}
+ argv += optind;
ret = EXIT_SUCCESS;
while (*argv) {
- if (var == 'v') {
+ if (!(opts & 2)) { /* not -f */
if (unset_local_var(*argv)) {
/* unset <nonexistent_var> doesn't fail.
* Error is when one tries to unset RO var.
ret = EXIT_FAILURE;
}
}
-//#if ENABLE_HUSH_FUNCTIONS
-// else {
-// unset_local_func(*argv);
-// }
-//#endif
+#if ENABLE_HUSH_FUNCTIONS
+ else {
+ unset_func(*argv);
+ }
+#endif
argv++;
}
return ret;
}
/* 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;
return ret;
}
+#if ENABLE_HUSH_LOOPS || ENABLE_HUSH_FUNCTIONS
+static unsigned parse_numeric_argv1(char **argv, unsigned def, unsigned def_min)
+{
+ if (argv[1]) {
+ def = bb_strtou(argv[1], NULL, 10);
+ if (errno || def < def_min || argv[2]) {
+ bb_error_msg("%s: bad arguments", argv[0]);
+ def = UINT_MAX;
+ }
+ }
+ return def;
+}
+#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) {
bb_error_msg("%s: only meaningful in a loop", argv[0]);
return EXIT_SUCCESS; /* bash compat */
}
G.flag_break_continue++; /* BC_BREAK = 1 */
- G.depth_break_continue = 1;
- if (argv[1]) {
- G.depth_break_continue = bb_strtou(argv[1], NULL, 10);
- if (errno || !G.depth_break_continue || argv[2]) {
- bb_error_msg("%s: bad arguments", argv[0]);
- G.flag_break_continue = BC_BREAK;
- G.depth_break_continue = UINT_MAX;
- }
- }
- if (G.depth_of_loop < G.depth_break_continue)
+
+ G.depth_break_continue = depth = parse_numeric_argv1(argv, 1, 1);
+ if (depth == UINT_MAX)
+ G.flag_break_continue = BC_BREAK;
+ if (G.depth_of_loop < depth)
G.depth_break_continue = G.depth_of_loop;
+
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 FAST_FUNC builtin_return(char **argv)
+{
+ int rc;
+
+ if (G.flag_return_in_progress != -1) {
+ bb_error_msg("%s: not in a function or sourced script", argv[0]);
+ return EXIT_FAILURE; /* bash compat */
+ }
+
+ G.flag_return_in_progress = 1;
+
+ /* bash:
+ * out of range: wraps around at 256, does not error out
+ * non-numeric param:
+ * f() { false; return qwe; }; f; echo $?
+ * bash: return: qwe: numeric argument required <== we do this
+ * 255 <== we also do this
+ */
+ rc = parse_numeric_argv1(argv, G.last_exitcode, 0);
+ return rc;
+}
+#endif