* "small and simple is beautiful" philosophy, which
* incidentally is a good match to today's BusyBox.
*
- * Copyright (C) 2000,2001 Larry Doolittle <larry@doolittle.boa.org>
+ * Copyright (C) 2000,2001 Larry Doolittle <larry@doolittle.boa.org>
* Copyright (C) 2008,2009 Denys Vlasenko <vda.linux@googlemail.com>
*
* Credits:
* rewrites.
*
* Other credits:
- * o_addchr() derived from similar w_addchar function in glibc-2.2.
- * setup_redirect(), redirect_opt_num(), and big chunks of main()
+ * o_addchr derived from similar w_addchar function in glibc-2.2.
+ * parse_redirect, redirect_opt_num, and big chunks of main
* and many builtins derived from contributions by Erik Andersen.
* Miscellaneous bugfixes from Matt Kraai.
*
* POSIX syntax not implemented:
* aliases
* <(list) and >(list) Process Substitution
- * Here Documents ( << word )
- * Functions
* Tilde Expansion
- * Parameter Expansion for substring processing ${var#word} ${var%word}
*
* Bash stuff (maybe optionally enable?):
* &> and >& redirection of stdout+stderr
* ^Z handling (and explain it in comments for mere humans)
* separate job control from interactiveness
* (testcase: booting with init=/bin/hush does not show prompt (2009-04))
- * functions
*
* Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
*/
-
#include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */
-//TODO: pull in some .h and find out whether we have SINGLE_APPLET_MAIN?
-//#include "applet_tables.h" doesn't work
#include <glob.h>
/* #include <dmalloc.h> */
#if ENABLE_HUSH_CASE
-#include <fnmatch.h>
+# include <fnmatch.h>
#endif
#include "math.h"
+#include "match.h"
+#ifndef PIPE_BUF
+# define PIPE_BUF 4096 /* amount of buffering in a pipe */
+#endif
+
+
+/* Debug build knobs */
+#define LEAK_HUNTING 0
+#define BUILD_AS_NOMMU 0
+/* Enable/disable sanity checks. Ok to enable in production,
+ * only adds a bit of bloat. Set to >1 to get non-production level verbosity.
+ * Keeping 1 for now even in released versions.
+ */
+#define HUSH_DEBUG 1
-#ifdef WANT_TO_TEST_NOMMU
+
+#if BUILD_AS_NOMMU
# undef BB_MMU
# undef USE_FOR_NOMMU
# undef USE_FOR_MMU
#if defined SINGLE_APPLET_MAIN
/* 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__
-#define ENABLE_FEATURE_SH_STANDALONE 0
-#define USE_FEATURE_SH_STANDALONE(...)
-#define SKIP_FEATURE_SH_STANDALONE(...) __VA_ARGS__
+# undef CONFIG_FEATURE_SH_STANDALONE
+# undef ENABLE_FEATURE_SH_STANDALONE
+# undef USE_FEATURE_SH_STANDALONE
+# define SKIP_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
-#undef ENABLE_FEATURE_EDITING
-#define ENABLE_FEATURE_EDITING 0
-#undef ENABLE_FEATURE_EDITING_FANCY_PROMPT
-#define ENABLE_FEATURE_EDITING_FANCY_PROMPT 0
+# undef ENABLE_FEATURE_EDITING
+# define ENABLE_FEATURE_EDITING 0
+# undef ENABLE_FEATURE_EDITING_FANCY_PROMPT
+# define ENABLE_FEATURE_EDITING_FANCY_PROMPT 0
#endif
/* Do we support ANY keywords? */
#if ENABLE_HUSH_IF || ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE
-#define HAS_KEYWORDS 1
-#define IF_HAS_KEYWORDS(...) __VA_ARGS__
-#define IF_HAS_NO_KEYWORDS(...)
+# define HAS_KEYWORDS 1
+# define IF_HAS_KEYWORDS(...) __VA_ARGS__
+# define IF_HAS_NO_KEYWORDS(...)
#else
-#define HAS_KEYWORDS 0
-#define IF_HAS_KEYWORDS(...)
-#define IF_HAS_NO_KEYWORDS(...) __VA_ARGS__
+# define HAS_KEYWORDS 0
+# define IF_HAS_KEYWORDS(...)
+# define IF_HAS_NO_KEYWORDS(...) __VA_ARGS__
#endif
-/* Keep unconditionally on for now */
-#define HUSH_DEBUG 1
-/* In progress... */
-#define ENABLE_HUSH_FUNCTIONS 0
-
-
/* If you comment out one of these below, it will be #defined later
* to perform debug printfs to stderr: */
#define debug_printf(...) do {} while (0)
#define debug_printf_subst(...) do {} while (0)
#define debug_printf_clean(...) do {} while (0)
-#ifndef debug_printf
-#define debug_printf(...) fprintf(stderr, __VA_ARGS__)
-#endif
-
-#ifndef debug_printf_parse
-#define debug_printf_parse(...) fprintf(stderr, __VA_ARGS__)
-#endif
-
-#ifndef debug_printf_exec
-#define debug_printf_exec(...) fprintf(stderr, __VA_ARGS__)
-#endif
-
-#ifndef debug_printf_env
-#define debug_printf_env(...) fprintf(stderr, __VA_ARGS__)
-#endif
-
-#ifndef debug_printf_jobs
-#define debug_printf_jobs(...) fprintf(stderr, __VA_ARGS__)
-#define DEBUG_JOBS 1
-#else
-#define DEBUG_JOBS 0
-#endif
-
-#ifndef debug_printf_expand
-#define debug_printf_expand(...) fprintf(stderr, __VA_ARGS__)
-#define DEBUG_EXPAND 1
-#else
-#define DEBUG_EXPAND 0
-#endif
-
-#ifndef debug_printf_glob
-#define debug_printf_glob(...) fprintf(stderr, __VA_ARGS__)
-#define DEBUG_GLOB 1
-#else
-#define DEBUG_GLOB 0
-#endif
-
-#ifndef debug_printf_list
-#define debug_printf_list(...) fprintf(stderr, __VA_ARGS__)
-#endif
-
-#ifndef debug_printf_subst
-#define debug_printf_subst(...) fprintf(stderr, __VA_ARGS__)
-#endif
-
-#ifndef debug_printf_clean
-/* broken, of course, but OK for testing */
-static const char *indenter(int i)
-{
- static const char blanks[] ALIGN1 =
- " ";
- return &blanks[sizeof(blanks) - i - 1];
-}
-#define debug_printf_clean(...) fprintf(stderr, __VA_ARGS__)
-#define DEBUG_CLEAN 1
-#else
-#define DEBUG_CLEAN 0
-#endif
-
-#if DEBUG_EXPAND
-static void debug_print_strings(const char *prefix, char **vv)
-{
- fprintf(stderr, "%s:\n", prefix);
- while (*vv)
- fprintf(stderr, " '%s'\n", *vv++);
-}
-#else
-#define debug_print_strings(prefix, vv) ((void)0)
-#endif
-
-/*
- * Leak hunting. Use hush_leaktool.sh for post-processing.
- */
-#ifdef FOR_HUSH_LEAKTOOL
-static void *xxmalloc(int lineno, size_t size)
-{
- void *ptr = xmalloc((size + 0xff) & ~0xff);
- fdprintf(2, "line %d: malloc %p\n", lineno, ptr);
- return ptr;
-}
-static void *xxrealloc(int lineno, void *ptr, size_t size)
-{
- ptr = xrealloc(ptr, (size + 0xff) & ~0xff);
- fdprintf(2, "line %d: realloc %p\n", lineno, ptr);
- return ptr;
-}
-static char *xxstrdup(int lineno, const char *str)
-{
- char *ptr = xstrdup(str);
- fdprintf(2, "line %d: strdup %p\n", lineno, ptr);
- return ptr;
-}
-static void xxfree(void *ptr)
-{
- fdprintf(2, "free %p\n", ptr);
- free(ptr);
-}
-#define xmalloc(s) xxmalloc(__LINE__, s)
-#define xrealloc(p, s) xxrealloc(__LINE__, p, s)
-#define xstrdup(s) xxstrdup(__LINE__, s)
-#define free(p) xxfree(p)
-#endif
-
-
#define ERR_PTR ((void*)(long)1)
-static const char hush_version_str[] ALIGN1 = "HUSH_VERSION="BB_VER;
-
#define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n"
#define SPECIAL_VAR_SYMBOL 3
-typedef enum redir_type {
- REDIRECT_INPUT = 1,
- REDIRECT_OVERWRITE = 2,
- REDIRECT_APPEND = 3,
- REDIRECT_HEREIS = 4,
- REDIRECT_IO = 5
-} redir_type;
+static const char hush_version_str[] ALIGN1 = "HUSH_VERSION="BB_VER;
+
+/* This supports saving pointers malloced in vfork child,
+ * to be freed in the parent. One pointer is saved in
+ * G.argv_from_re_execing global var instead. TODO: unify.
+ */
+#if !BB_MMU
+typedef struct nommu_save_t {
+ char **new_env;
+ char **old_env;
+ char **argv;
+} nommu_save_t;
+#endif
/* The descrip member of this structure is only used to make
* debugging output pretty */
signed char default_fd;
char descrip[3];
} redir_table[] = {
- { 0, 0, "()" },
+ { 0, 0, "??" },
{ O_RDONLY, 0, "<" },
{ O_CREAT|O_TRUNC|O_WRONLY, 1, ">" },
{ O_CREAT|O_APPEND|O_WRONLY, 1, ">>" },
- { O_RDONLY, -1, "<<" },
- { O_RDWR, 1, "<>" }
+ { O_RDONLY, 0, "<<" },
+ { O_CREAT|O_RDWR, 1, "<>" },
+/* Should not be needed. Bogus default_fd helps in debugging */
+/* { O_RDONLY, 77, "<<" }, */
};
-typedef enum pipe_style {
- PIPE_SEQ = 1,
- PIPE_AND = 2,
- PIPE_OR = 3,
- PIPE_BG = 4,
-} pipe_style;
-
typedef enum reserved_style {
RES_NONE = 0,
#if ENABLE_HUSH_IF
* (by prepending \ to *, ?, [, \) */
smallint o_escape;
smallint o_glob;
- smallint nonnull;
+ /* At least some part of the string was inside '' or "",
+ * possibly empty one: word"", wo''rd etc. */
+ smallint o_quoted;
smallint has_empty_slot;
smallint o_assignment; /* 0:maybe, 1:yes, 2:no */
} o_string;
struct redir_struct {
struct redir_struct *next;
char *rd_filename; /* filename */
- int fd; /* file descriptor being redirected */
- int dup; /* -1, or file descriptor being duplicated */
- smallint /*enum redir_type*/ rd_type;
+ int rd_fd; /* fd to redirect */
+ /* fd to redirect to, or -3 if rd_fd is to be closed (n>&-) */
+ int rd_dup;
+ 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) ?
+ */
};
+typedef enum redir_type {
+ REDIRECT_INVALID = 0,
+ REDIRECT_INPUT = 1,
+ REDIRECT_OVERWRITE = 2,
+ REDIRECT_APPEND = 3,
+ REDIRECT_HEREDOC = 4,
+ REDIRECT_IO = 5,
+ REDIRECT_HEREDOC2 = 6, /* REDIRECT_HEREDOC after heredoc is loaded */
+
+ REDIRFD_CLOSE = -3,
+ REDIRFD_SYNTAX_ERR = -2,
+ REDIRFD_TO_FILE = -1,
+ /* otherwise, rd_fd is redirected to rd_dup */
+
+ HEREDOC_SKIPTABS = 1,
+ HEREDOC_QUOTED = 2,
+} redir_type;
+
struct command {
pid_t pid; /* 0 if exited */
int assignment_cnt; /* how many argv[i] are assignments? */
smallint is_stopped; /* is the command currently running? */
smallint grp_type; /* GRP_xxx */
+#define GRP_NORMAL 0
+#define GRP_SUBSHELL 1
+#if ENABLE_HUSH_FUNCTIONS
+# define GRP_FUNCTION 2
+#endif
struct pipe *group; /* if non-NULL, this "command" is { list },
* ( list ), or a compound statement */
#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
+ * 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",
+ * 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.
+ * Without this trick, loop would execute a;b;b;b;...
+ * instead of correct sequence a;b;a;b;...
+ * When command is freed, it severs the link
+ * (sets ->child_func->parent_cmd to NULL).
+ */
#endif
char **argv; /* command name and arguments */
- struct redir_struct *redirects; /* I/O redirections */
-};
/* argv vector may contain variable references (^Cvar^C, ^C0^C etc)
* and on execution these are substituted with their values.
* Substitution can make _several_ words out of one argv[n]!
* Example: argv[0]=='.^C*^C.' here: echo .$*.
* References of the form ^C`cmd arg^C are `cmd arg` substitutions.
*/
-#define GRP_NORMAL 0
-#define GRP_SUBSHELL 1
-#if ENABLE_HUSH_FUNCTIONS
-#define GRP_FUNCTION 2
-#endif
+ struct redir_struct *redirects; /* I/O redirections */
+};
struct pipe {
struct pipe *next;
- int num_cmds; /* total number of commands in job */
+ int num_cmds; /* total number of commands in pipe */
int alive_cmds; /* number of commands running (not exited) */
int stopped_cmds; /* number of commands alive, but stopped */
#if ENABLE_HUSH_JOB
IF_HAS_KEYWORDS(smallint pi_inverted;) /* "! cmd | cmd" */
IF_HAS_KEYWORDS(smallint res_word;) /* needed for if, for, while, until... */
};
+typedef enum pipe_style {
+ PIPE_SEQ = 1,
+ PIPE_AND = 2,
+ PIPE_OR = 3,
+ PIPE_BG = 4,
+} pipe_style;
/* This holds pointers to the various results of parsing */
struct parse_context {
BC_CONTINUE = 2,
};
+#if ENABLE_HUSH_FUNCTIONS
+struct function {
+ struct function *next;
+ char *name;
+ struct command *parent_cmd;
+ struct pipe *body;
+#if !BB_MMU
+ char *body_as_string;
+#endif
+};
+#endif
+
/* "Globals" within this file */
/* Sorted roughly by size (smaller offsets == smaller code) */
smallint flag_break_continue;
#endif
smallint fake_mode;
+ smallint exiting; /* used to prevent EXIT trap recursion */
/* These four support $?, $#, and $1 */
- smalluint last_return_code;
+ smalluint last_exitcode;
/* are global_argv and global_argv[1..n] malloced? (note: not [0]) */
smalluint global_args_malloced;
/* how many non-NULL argv's we have. NB: $# + 1 */
const char *cwd;
struct variable *top_var; /* = &G.shell_ver (set in main()) */
struct variable shell_ver;
+#if ENABLE_HUSH_FUNCTIONS
+ struct function *top_func;
+#endif
/* Signal and trap handling */
// unsigned count_SIGCHLD;
// unsigned handled_SIGCHLD;
char **traps; /* char *traps[NSIG] */
sigset_t blocked_set;
sigset_t inherited_set;
+#if HUSH_DEBUG
+ unsigned long memleak_value;
+ int debug_indent;
+#endif
char user_input_buf[ENABLE_FEATURE_EDITING ? BUFSIZ : 2];
#if ENABLE_FEATURE_SH_STANDALONE
struct nofork_save_area nofork_save;
#if ENABLE_HUSH_HELP
static int builtin_help(char **argv);
#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_test(char **argv);
-static int builtin_trap(char **argv);
-static int builtin_true(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);
#endif
#if ENABLE_HUSH_JOB
BLTIN("jobs" , builtin_jobs , "List active jobs"),
+#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"),
};
-static void maybe_die(const char *notice, const char *msg)
-{
- /* Was using fancy stuff:
- * (G_interactive_fd ? bb_error_msg : bb_error_msg_and_die)(...params...)
- * but it SEGVs. ?! Oh well... explicit temp ptr works around that */
- void FAST_FUNC (*fp)(const char *s, ...) = bb_error_msg_and_die;
-#if ENABLE_HUSH_INTERACTIVE
- if (G_interactive_fd)
- fp = bb_error_msg;
+/* Debug printouts.
+ */
+#if HUSH_DEBUG
+/* prevent disasters with G.debug_indent < 0 */
+# define indent() fprintf(stderr, "%*s", (G.debug_indent * 2) & 0xff, "")
+# define debug_enter() (G.debug_indent++)
+# define debug_leave() (G.debug_indent--)
+#else
+# define indent() ((void)0)
+# define debug_enter() ((void)0)
+# define debug_leave() ((void)0)
#endif
- fp(msg ? "%s: %s" : notice, notice, msg);
-}
-#if 1
-#define syntax(msg) maybe_die("syntax error", msg);
+
+#ifndef debug_printf
+# define debug_printf(...) (indent(), fprintf(stderr, __VA_ARGS__))
+#endif
+
+#ifndef debug_printf_parse
+# define debug_printf_parse(...) (indent(), fprintf(stderr, __VA_ARGS__))
+#endif
+
+#ifndef debug_printf_exec
+#define debug_printf_exec(...) (indent(), fprintf(stderr, __VA_ARGS__))
+#endif
+
+#ifndef debug_printf_env
+# define debug_printf_env(...) (indent(), fprintf(stderr, __VA_ARGS__))
+#endif
+
+#ifndef debug_printf_jobs
+# define debug_printf_jobs(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define DEBUG_JOBS 1
#else
-/* Debug -- trick gcc to expand __LINE__ and convert to string */
-#define __syntax(msg, line) maybe_die("syntax error hush.c:" # line, msg)
-#define _syntax(msg, line) __syntax(msg, line)
-#define syntax(msg) _syntax(msg, __LINE__)
+# define DEBUG_JOBS 0
#endif
+#ifndef debug_printf_expand
+# define debug_printf_expand(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define DEBUG_EXPAND 1
+#else
+# define DEBUG_EXPAND 0
+#endif
-static int glob_needed(const char *s)
+#ifndef debug_printf_glob
+# define debug_printf_glob(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define DEBUG_GLOB 1
+#else
+# define DEBUG_GLOB 0
+#endif
+
+#ifndef debug_printf_list
+# define debug_printf_list(...) (indent(), fprintf(stderr, __VA_ARGS__))
+#endif
+
+#ifndef debug_printf_subst
+# define debug_printf_subst(...) (indent(), fprintf(stderr, __VA_ARGS__))
+#endif
+
+#ifndef debug_printf_clean
+# define debug_printf_clean(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define DEBUG_CLEAN 1
+#else
+# define DEBUG_CLEAN 0
+#endif
+
+#if DEBUG_EXPAND
+static void debug_print_strings(const char *prefix, char **vv)
{
- while (*s) {
- if (*s == '\\')
- s++;
- if (*s == '*' || *s == '[' || *s == '?')
- return 1;
- s++;
- }
- return 0;
+ indent();
+ fprintf(stderr, "%s:\n", prefix);
+ while (*vv)
+ fprintf(stderr, " '%s'\n", *vv++);
}
+#else
+#define debug_print_strings(prefix, vv) ((void)0)
+#endif
-static int is_assignment(const char *s)
+
+/* Leak hunting. Use hush_leaktool.sh for post-processing.
+ */
+#if LEAK_HUNTING
+static void *xxmalloc(int lineno, size_t size)
{
- if (!s || !(isalpha(*s) || *s == '_'))
- return 0;
- s++;
- while (isalnum(*s) || *s == '_')
- s++;
- return *s == '=';
+ void *ptr = xmalloc((size + 0xff) & ~0xff);
+ fdprintf(2, "line %d: malloc %p\n", lineno, ptr);
+ return ptr;
}
-
-/* Replace each \x with x in place, return ptr past NUL. */
-static char *unbackslash(char *src)
+static void *xxrealloc(int lineno, void *ptr, size_t size)
{
- char *dst = src;
- while (1) {
- if (*src == '\\')
- src++;
- if ((*dst++ = *src++) == '\0')
- break;
- }
- return dst;
+ ptr = xrealloc(ptr, (size + 0xff) & ~0xff);
+ fdprintf(2, "line %d: realloc %p\n", lineno, ptr);
+ return ptr;
}
-
-static char **add_strings_to_strings(char **strings, char **add, int need_to_dup)
+static char *xxstrdup(int lineno, const char *str)
{
- int i;
- unsigned count1;
- unsigned count2;
- char **v;
-
- v = strings;
- count1 = 0;
- if (v) {
- while (*v) {
- count1++;
- v++;
- }
- }
+ char *ptr = xstrdup(str);
+ fdprintf(2, "line %d: strdup %p\n", lineno, ptr);
+ return ptr;
+}
+static void xxfree(void *ptr)
+{
+ fdprintf(2, "free %p\n", ptr);
+ free(ptr);
+}
+#define xmalloc(s) xxmalloc(__LINE__, s)
+#define xrealloc(p, s) xxrealloc(__LINE__, p, s)
+#define xstrdup(s) xxstrdup(__LINE__, s)
+#define free(p) xxfree(p)
+#endif
+
+
+/* Syntax and runtime errors. They always abort scripts.
+ * In interactive use they usually discard unparsed and/or unexecuted commands
+ * and return to the prompt.
+ * 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 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)
+# define syntax_error_unterm_str(lineno, s) syntax_error_unterm_str(s)
+# define syntax_error_unexpected_ch(lineno, ch) syntax_error_unexpected_ch(ch)
+#endif
+
+static void die_if_script(unsigned lineno, const char *fmt, ...)
+{
+ va_list p;
+
+#if HUSH_DEBUG >= 2
+ bb_error_msg("hush.c:%u", lineno);
+#endif
+ va_start(p, fmt);
+ bb_verror_msg(fmt, p, NULL);
+ va_end(p);
+ if (!G_interactive_fd)
+ xfunc_die();
+}
+
+static void syntax_error(unsigned lineno, const char *msg)
+{
+ if (msg)
+ die_if_script(lineno, "syntax error: %s", msg);
+ else
+ die_if_script(lineno, "syntax error", NULL);
+}
+
+static void syntax_error_at(unsigned lineno, const char *msg)
+{
+ die_if_script(lineno, "syntax error at '%s'", msg);
+}
+
+/* It so happens that all such cases are totally fatal
+ * even if shell is interactive: EOF while looking for closing
+ * delimiter. There is nowhere to read stuff from after that,
+ * it's EOF! The only choice is to terminate.
+ */
+static void syntax_error_unterm_ch(unsigned lineno, char ch) NORETURN;
+static void syntax_error_unterm_ch(unsigned lineno, char ch)
+{
+ char msg[2];
+ msg[0] = ch;
+ msg[1] = '\0';
+ die_if_script(lineno, "syntax error: unterminated %s", 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)
+{
+ char msg[2];
+ msg[0] = ch;
+ msg[1] = '\0';
+ die_if_script(lineno, "syntax error: unexpected %s", msg);
+}
+
+#if HUSH_DEBUG < 2
+# undef die_if_script
+# undef syntax_error
+# undef syntax_error_at
+# undef syntax_error_unterm_ch
+# undef syntax_error_unterm_str
+# undef syntax_error_unexpected_ch
+#else
+# define die_if_script(fmt...) die_if_script(__LINE__, fmt)
+# 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)
+# define syntax_error_unterm_str(s) syntax_error_unterm_str(__LINE__, s)
+# define syntax_error_unexpected_ch(ch) syntax_error_unexpected_ch(__LINE__, ch)
+#endif
+
+
+/* Utility functions
+ */
+static int glob_needed(const char *s)
+{
+ while (*s) {
+ if (*s == '\\')
+ s++;
+ if (*s == '*' || *s == '[' || *s == '?')
+ return 1;
+ s++;
+ }
+ return 0;
+}
+
+static int is_well_formed_var_name(const char *s, char terminator)
+{
+ if (!s || !(isalpha(*s) || *s == '_'))
+ return 0;
+ s++;
+ while (isalnum(*s) || *s == '_')
+ s++;
+ return *s == terminator;
+}
+
+/* Replace each \x with x in place, return ptr past NUL. */
+static char *unbackslash(char *src)
+{
+ char *dst = src;
+ while (1) {
+ if (*src == '\\')
+ src++;
+ if ((*dst++ = *src++) == '\0')
+ break;
+ }
+ return dst;
+}
+
+static char **add_strings_to_strings(char **strings, char **add, int need_to_dup)
+{
+ int i;
+ unsigned count1;
+ unsigned count2;
+ char **v;
+
+ v = strings;
+ count1 = 0;
+ if (v) {
+ while (*v) {
+ count1++;
+ v++;
+ }
+ }
count2 = 0;
v = add;
while (*v) {
v[count1 + i] = (need_to_dup ? xstrdup(add[i]) : add[i]);
return v;
}
+#if LEAK_HUNTING
+static char **xx_add_strings_to_strings(int lineno, char **strings, char **add, int need_to_dup)
+{
+ char **ptr = add_strings_to_strings(strings, add, need_to_dup);
+ fdprintf(2, "line %d: add_strings_to_strings %p\n", lineno, ptr);
+ return ptr;
+}
+#define add_strings_to_strings(strings, add, need_to_dup) \
+ xx_add_strings_to_strings(__LINE__, strings, add, need_to_dup)
+#endif
static char **add_string_to_strings(char **strings, char *add)
{
v[1] = NULL;
return add_strings_to_strings(strings, v, /*dup:*/ 0);
}
+#if LEAK_HUNTING
+static char **xx_add_string_to_strings(int lineno, char **strings, char *add)
+{
+ char **ptr = add_string_to_strings(strings, add);
+ fdprintf(2, "line %d: add_string_to_strings %p\n", lineno, ptr);
+ return ptr;
+}
+#define add_string_to_strings(strings, add) \
+ xx_add_string_to_strings(__LINE__, strings, add)
+#endif
static void putenv_all(char **strings)
{
*
* Trap handlers will execute even within trap handlers. (right?)
*
- * User trap handlers are forgotten when subshell ("(cmd)") is entered. [TODO]
+ * User trap handlers are forgotten when subshell ("(cmd)") is entered.
*
* If job control is off, backgrounded commands ("cmd &")
* have SIGINT, SIGQUIT set to SIG_IGN.
* 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. [TODO]
+ * unset all traps if () shell.
* after [v]fork, if we plan to exec:
* POSIX says pending signal mask is cleared in child - no need to clear it.
* Restore blocked signal set to one inherited by shell just prior to exec.
if (G.traps[sig][0]) {
/* We have user-defined handler */
char *argv[] = { NULL, xstrdup(G.traps[sig]), NULL };
- save_rcode = G.last_return_code;
+ save_rcode = G.last_exitcode;
builtin_eval(argv);
free(argv[1]);
- G.last_return_code = save_rcode;
+ G.last_exitcode = save_rcode;
} /* else: "" trap, ignoring signal */
continue;
}
}
#if ENABLE_HUSH_JOB
+
+/* After [v]fork, in child: do not restore tty pgrp on xfunc death */
+#define disable_restore_tty_pgrp_on_exit() (die_sleep = 0)
+/* After [v]fork, in parent: restore tty pgrp on xfunc death */
+#define enable_restore_tty_pgrp_on_exit() (die_sleep = -1)
+
/* 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)
kill_myself_with_sig(sig); /* does not return */
}
+#else
+
+#define disable_restore_tty_pgrp_on_exit() ((void)0)
+#define enable_restore_tty_pgrp_on_exit() ((void)0)
+
#endif
/* Restores tty foreground process group, and exits. */
static void hush_exit(int exitcode) NORETURN;
static void hush_exit(int exitcode)
{
- if (G.traps && G.traps[0] && G.traps[0][0]) {
- char *argv[] = { NULL, xstrdup(G.traps[0]), NULL };
-//TODO: do we need to prevent recursion?
+ if (G.exiting <= 0 && G.traps && G.traps[0] && G.traps[0][0]) {
+ /* Prevent recursion:
+ * trap "echo Hi; exit" EXIT; exit
+ */
+ char *argv[] = { NULL, G.traps[0], NULL };
+ G.traps[0] = NULL;
+ G.exiting = 1;
builtin_eval(argv);
free(argv[1]);
}
*/
#define B_CHUNK (32 * sizeof(char*))
-static void o_reset(o_string *o)
+static void o_reset_to_empty_unquoted(o_string *o)
{
o->length = 0;
- o->nonnull = 0;
+ o->o_quoted = 0;
if (o->data)
o->data[0] = '\0';
}
{
o_addblock(o, str, strlen(str));
}
+static void nommu_addchr(o_string *o, int ch)
+{
+ if (o)
+ o_addchr(o, ch);
+}
+#else
+#define nommu_addchr(o, str) ((void)0)
#endif
static void o_addstr_with_NUL(o_string *o, const char *str)
char **list = (char**)o->data;
int string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
int i = 0;
+
+ indent();
fprintf(stderr, "%s: list:%p n:%d string_start:%d length:%d maxlen:%d\n",
prefix, list, n, string_start, o->length, o->maxlen);
while (i < n) {
+ indent();
fprintf(stderr, " list[%d]=%d '%s' %p\n", i, (int)list[i],
o->data + (int)list[i] + string_start,
o->data + (int)list[i] + string_start);
}
if (n) {
const char *p = o->data + (int)list[n - 1] + string_start;
+ indent();
fprintf(stderr, " total_sz:%ld\n", (long)((p + strlen(p) + 1) - o->data));
}
}
return n;
}
+/* Helper to expand $((...)) and heredoc body. These act as if
+ * they are in double quotes, with the exception that they are not :).
+ * Just the rules are similar: "expand only $var and `cmd`"
+ *
+ * Returns malloced string.
+ * As an optimization, we return NULL if expansion is not needed.
+ */
+static char *expand_pseudo_dquoted(const char *str)
+{
+ char *exp_str;
+ struct in_str input;
+ o_string dest = NULL_O_STRING;
+
+ if (strchr(str, '$') == NULL
+#if ENABLE_HUSH_TICK
+ && strchr(str, '`') == NULL
+#endif
+ ) {
+ return NULL;
+ }
+
+ /* We need to expand. Example:
+ * echo $(($a + `echo 1`)) $((1 + $((2)) ))
+ */
+ setup_string_in_str(&input, str);
+ parse_stream_dquoted(NULL, &dest, &input, EOF);
+ //bb_error_msg("'%s' -> '%s'", str, dest.data);
+ exp_str = expand_string_to_string(dest.data);
+ //bb_error_msg("'%s' -> '%s'", dest.data, exp_str);
+ o_free_unsafe(&dest);
+ return exp_str;
+}
+
/* Expand all variable references in given string, adding words to list[]
* at n, n+1,... positions. Return updated n (so that list[n] is next one
* to be filled). This routine is extremely tricky: has to deal with
char first_ch, ored_ch;
int i;
const char *val;
- char *p;
+ char *dyn_val, *p;
+ dyn_val = NULL;
ored_ch = 0;
debug_printf_expand("expand_vars_to_list: arg '%s'\n", arg);
val = G.last_bg_pid ? utoa(G.last_bg_pid) : (char*)"";
break;
case '?': /* exitcode */
- val = utoa(G.last_return_code);
+ val = utoa(G.last_exitcode);
break;
case '#': /* argc */
if (arg[1] != SPECIAL_VAR_SYMBOL)
*p = '\0'; /* replace trailing <SPECIAL_VAR_SYMBOL> */
debug_printf_subst("ARITH '%s' first_ch %x\n", arg, first_ch);
- /* Optional: skip expansion if expr is simple ("a + 3", "i++" etc) */
- exp_str = NULL;
- if (strchr(arg, '$') != NULL
-#if ENABLE_HUSH_TICK
- || strchr(arg, '`') != NULL
-#endif
- ) {
- /* We need to expand. Example:
- * echo $(($a + `echo 1`)) $((1 + $((2)) ))
- */
- struct in_str input;
- o_string dest = NULL_O_STRING;
-
- setup_string_in_str(&input, arg);
- parse_stream_dquoted(NULL, &dest, &input, EOF);
- //bb_error_msg("'%s' -> '%s'", arg, dest.data);
- exp_str = expand_string_to_string(dest.data);
- //bb_error_msg("'%s' -> '%s'", dest.data, exp_str);
- o_free_unsafe(&dest);
- }
+ exp_str = expand_pseudo_dquoted(arg);
hooks.lookupvar = get_local_var_value;
hooks.setvar = arith_set_local_var;
hooks.endofname = endofname;
free(exp_str);
if (errcode < 0) {
+ const char *msg = "error in arithmetic";
switch (errcode) {
- case -3: maybe_die("arith", "exponent less than 0"); break;
- case -2: maybe_die("arith", "divide by zero"); break;
- case -5: maybe_die("arith", "expression recursion loop detected"); break;
- default: maybe_die("arith", "syntax error"); break;
+ case -3:
+ msg = "exponent less than 0";
+ break;
+ case -2:
+ msg = "divide by 0";
+ break;
+ case -5:
+ msg = "expression recursion loop detected";
+ break;
}
+ die_if_script(msg);
}
debug_printf_subst("ARITH RES '"arith_t_fmt"'\n", res);
sprintf(arith_buf, arith_t_fmt, res);
++var;
} else {
/* maybe handle parameter expansion */
- exp_off = strcspn(var, ":-=+?");
+ exp_off = strcspn(var, ":-=+?%#");
if (!var[exp_off])
exp_off = 0;
if (exp_off) {
exp_save = var[exp_off];
exp_null = exp_save == ':';
exp_word = var + exp_off;
- if (exp_null) ++exp_word;
+ if (exp_null)
+ ++exp_word;
exp_op = *exp_word++;
var[exp_off] = '\0';
}
val = utoa(val ? strlen(val) : 0);
debug_printf_expand("%s\n", val);
} else if (exp_off) {
- /* we need to do an expansion */
- int exp_test = (!val || (exp_null && !val[0]));
- if (exp_op == '+')
- exp_test = !exp_test;
- debug_printf_expand("expand: op:%c (null:%s) test:%i\n", exp_op,
- exp_null ? "true" : "false", exp_test);
- if (exp_test) {
- if (exp_op == '?')
- maybe_die(var, *exp_word ? exp_word : "parameter null or not set");
- else
- val = exp_word;
-
- if (exp_op == '=') {
- if (isdigit(var[0]) || var[0] == '#') {
- maybe_die(var, "special vars cannot assign in this way");
- val = NULL;
+ if (exp_op == '%' || exp_op == '#') {
+ if (val) {
+ /* we need to do a pattern match */
+ bool zero;
+ char *loc;
+ scan_t scan = pick_scan(exp_op, *exp_word, &zero);
+ if (exp_op == *exp_word) /* ## or %% */
+ ++exp_word;
+ val = dyn_val = xstrdup(val);
+ loc = scan(dyn_val, exp_word, zero);
+ if (zero)
+ val = loc;
+ else
+ *loc = '\0';
+ }
+ } else {
+ /* we need to do an expansion */
+ int exp_test = (!val || (exp_null && !val[0]));
+ if (exp_op == '+')
+ exp_test = !exp_test;
+ debug_printf_expand("expand: op:%c (null:%s) test:%i\n", exp_op,
+ exp_null ? "true" : "false", exp_test);
+ if (exp_test) {
+ if (exp_op == '?') {
+//TODO: how interactive bash aborts expansion mid-command?
+ /* ${var?[error_msg_if_unset]} */
+ /* ${var:?[error_msg_if_unset_or_null]} */
+ /* mimic bash message */
+ die_if_script("%s: %s",
+ var,
+ exp_word[0] ? exp_word : "parameter null or not set"
+ );
} else {
- char *new_var = xmalloc(strlen(var) + strlen(val) + 2);
- sprintf(new_var, "%s=%s", var, val);
- set_local_var(new_var, -1, 0);
+ val = exp_word;
+ }
+
+ if (exp_op == '=') {
+ /* ${var=[word]} or ${var:=[word]} */
+ if (isdigit(var[0]) || var[0] == '#') {
+ /* mimic bash message */
+ die_if_script("$%s: cannot assign in this way", var);
+ val = NULL;
+ } else {
+ char *new_var = xasprintf("%s=%s", var, val);
+ set_local_var(new_var, -1, 0);
+ }
}
}
}
+
var[exp_off] = exp_save;
}
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;
}
+#if BB_MMU
+/* never called */
+void re_execute_shell(const char *s, char *argv0, char **argv);
+
+#define clean_up_after_re_execute() ((void)0)
+
+static void reset_traps_to_defaults(void)
+{
+ unsigned sig;
+ int dirty;
+
+ if (!G.traps)
+ return;
+ dirty = 0;
+ for (sig = 0; sig < NSIG; sig++) {
+ if (!G.traps[sig])
+ 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
+ * (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);
+ dirty = 1;
+ }
+ if (dirty)
+ sigprocmask(SIG_SETMASK, &G.blocked_set, NULL);
+}
+
+#else /* !BB_MMU */
+
+static void re_execute_shell(const char *s, char *g_argv0, char **g_argv) NORETURN;
+static void re_execute_shell(const char *s, char *g_argv0, char **g_argv)
+{
+ char param_buf[sizeof("-$%x:%x:%x:%x") + sizeof(unsigned) * 4];
+ char *heredoc_argv[4];
+ struct variable *cur;
+#if ENABLE_HUSH_FUNCTIONS
+ struct function *funcp;
+#endif
+ char **argv, **pp;
+ unsigned cnt;
+
+ if (!g_argv0) { /* heredoc */
+ argv = heredoc_argv;
+ argv[0] = (char *) G.argv0_for_re_execing;
+ argv[1] = (char *) "-<";
+ argv[2] = (char *) s;
+ argv[3] = NULL;
+ pp = &argv[3]; /* used as pointer to empty environment */
+ goto do_exec;
+ }
+
+ sprintf(param_buf, "-$%x:%x:%x" USE_HUSH_LOOPS(":%x")
+ , (unsigned) G.root_pid
+ , (unsigned) G.last_bg_pid
+ , (unsigned) G.last_exitcode
+ USE_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;
+ for (cur = G.top_var; cur; cur = cur->next) {
+ if (!cur->flg_export || cur->flg_read_only)
+ cnt += 2;
+ }
+#if ENABLE_HUSH_FUNCTIONS
+ for (funcp = G.top_func; funcp; funcp = funcp->next)
+ cnt += 3;
+#endif
+ pp = g_argv;
+ while (*pp++)
+ cnt++;
+ G.argv_from_re_execing = argv = pp = xzalloc(sizeof(argv[0]) * cnt);
+ *pp++ = (char *) G.argv0_for_re_execing;
+ *pp++ = param_buf;
+ for (cur = G.top_var; cur; cur = cur->next) {
+ if (cur->varstr == hush_version_str)
+ continue;
+ if (cur->flg_read_only) {
+ *pp++ = (char *) "-R";
+ *pp++ = cur->varstr;
+ } else if (!cur->flg_export) {
+ *pp++ = (char *) "-V";
+ *pp++ = cur->varstr;
+ }
+ }
+#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
+ /* We can pass activated traps here. Say, -Tnn:trap_string
+ *
+ * However, POSIX says that subshells reset signals with traps
+ * to SIG_DFL.
+ * I tested bash-3.2 and it not only does that with true subshells
+ * of the form ( list ), but with any forked children shells.
+ * I set trap "echo W" WINCH; and then tried:
+ *
+ * { echo 1; sleep 20; echo 2; } &
+ * while true; do echo 1; sleep 20; echo 2; break; done &
+ * true | { echo 1; sleep 20; echo 2; } | cat
+ *
+ * In all these cases sending SIGWINCH to the child shell
+ * did not run the trap. If I add trap "echo V" WINCH;
+ * _inside_ group (just before echo 1), it works.
+ *
+ * I conclude it means we don't need to pass active traps here.
+ * exec syscall below resets them to SIG_DFL for us.
+ */
+ *pp++ = (char *) "-c";
+ *pp++ = (char *) s;
+ *pp++ = g_argv0;
+ while (*g_argv)
+ *pp++ = *g_argv++;
+ /* *pp = NULL; - is already there */
+ pp = environ;
+
+ do_exec:
+ debug_printf_exec("re_execute_shell pid:%d cmd:'%s'\n", getpid(), s);
+ sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
+ execve(bb_busybox_exec_path, argv, pp);
+ /* Fallback. Useful for init=/bin/hush usage etc */
+ if (argv[0][0] == '/')
+ execve(argv[0], argv, pp);
+ xfunc_error_retval = 127;
+ bb_error_msg_and_die("can't re-execute the shell");
+}
+
+static void clean_up_after_re_execute(void)
+{
+ char **pp = G.argv_from_re_execing;
+ if (pp) {
+ /* Must match re_execute_shell's allocations (if any) */
+ free(pp);
+ G.argv_from_re_execing = NULL;
+ }
+}
+#endif /* !BB_MMU */
+
+
+static void setup_heredoc(struct redir_struct *redir)
+{
+ struct fd_pair pair;
+ pid_t pid;
+ int len, written;
+ /* the _body_ of heredoc (misleading field name) */
+ const char *heredoc = redir->rd_filename;
+ char *expanded;
+
+ expanded = NULL;
+ if (!(redir->rd_dup & HEREDOC_QUOTED)) {
+ expanded = expand_pseudo_dquoted(heredoc);
+ if (expanded)
+ heredoc = expanded;
+ }
+ len = strlen(heredoc);
+
+ close(redir->rd_fd); /* often saves dup2+close in xmove_fd */
+ xpiped_pair(pair);
+ xmove_fd(pair.rd, redir->rd_fd);
+
+ /* Try writing without forking. Newer kernels have
+ * dynamically growing pipes. Must use non-blocking write! */
+ ndelay_on(pair.wr);
+ while (1) {
+ written = write(pair.wr, heredoc, len);
+ if (written <= 0)
+ break;
+ len -= written;
+ if (len == 0) {
+ close(pair.wr);
+ free(expanded);
+ return;
+ }
+ heredoc += written;
+ }
+ ndelay_off(pair.wr);
+
+ /* Okay, pipe buffer was not big enough */
+ /* Note: we must not create a stray child (bastard? :)
+ * for the unsuspecting parent process. Child creates a grandchild
+ * and exits before parent execs the process which consumes heredoc
+ * (that exec happens after we return from this function) */
+ pid = vfork();
+ if (pid < 0)
+ bb_perror_msg_and_die("vfork");
+ if (pid == 0) {
+ /* child */
+ pid = BB_MMU ? fork() : vfork();
+ if (pid < 0)
+ bb_perror_msg_and_die(BB_MMU ? "fork" : "vfork");
+ if (pid != 0)
+ _exit(0);
+ /* grandchild */
+ close(redir->rd_fd); /* read side of the pipe */
+#if BB_MMU
+ full_write(pair.wr, heredoc, len); /* may loop or block */
+ _exit(0);
+#else
+ /* Delegate blocking writes to another process */
+ disable_restore_tty_pgrp_on_exit();
+ xmove_fd(pair.wr, STDOUT_FILENO);
+ re_execute_shell(heredoc, NULL, NULL);
+#endif
+ }
+ /* parent */
+ enable_restore_tty_pgrp_on_exit();
+ clean_up_after_re_execute();
+ close(pair.wr);
+ free(expanded);
+ wait(NULL); /* wait till child has died */
+}
+
/* squirrel != NULL means we squirrel away copies of stdin, stdout,
* and stderr if they are redirected. */
static int setup_redirects(struct command *prog, int squirrel[])
struct redir_struct *redir;
for (redir = prog->redirects; redir; redir = redir->next) {
- if (redir->dup == -1 && redir->rd_filename == NULL) {
- /* something went wrong in the parse. Pretend it didn't happen */
+ if (redir->rd_type == REDIRECT_HEREDOC2) {
+ /* rd_fd<<HERE case */
+ if (squirrel && redir->rd_fd < 3) {
+ squirrel[redir->rd_fd] = dup(redir->rd_fd);
+ }
+ /* for REDIRECT_HEREDOC2, rd_filename holds _contents_
+ * of the heredoc */
+ debug_printf_parse("set heredoc '%s'\n",
+ redir->rd_filename);
+ setup_heredoc(redir);
continue;
}
- if (redir->dup == -1) {
+
+ if (redir->rd_dup == REDIRFD_TO_FILE) {
+ /* rd_fd<*>file case (<*> is <,>,>>,<>) */
char *p;
+ if (redir->rd_filename == NULL) {
+ /* Something went wrong in the parse.
+ * Pretend it didn't happen */
+ bb_error_msg("bug in redirect parse");
+ continue;
+ }
mode = redir_table[redir->rd_type].mode;
-//TODO: check redir for names like '\\'
p = expand_string_to_string(redir->rd_filename);
openfd = open_or_warn(p, mode);
free(p);
if (openfd < 0) {
/* this could get lost if stderr has been redirected, but
- bash and ash both lose it as well (though zsh doesn't!) */
+ * bash and ash both lose it as well (though zsh doesn't!) */
+//what the above comment tries to say?
return 1;
}
} else {
- openfd = redir->dup;
+ /* rd_fd<*>rd_dup or rd_fd<*>- cases */
+ openfd = redir->rd_dup;
}
- if (openfd != redir->fd) {
- if (squirrel && redir->fd < 3) {
- squirrel[redir->fd] = dup(redir->fd);
+ if (openfd != redir->rd_fd) {
+ if (squirrel && redir->rd_fd < 3) {
+ squirrel[redir->rd_fd] = dup(redir->rd_fd);
}
- if (openfd == -3) {
- /* "-" means "close me" and we use -3 for that */
- close(redir->fd);
+ if (openfd == REDIRFD_CLOSE) {
+ /* "n>-" means "close me" */
+ close(redir->rd_fd);
} else {
- dup2(openfd, redir->fd);
- if (redir->dup == -1)
+ xdup2(openfd, redir->rd_fd);
+ if (redir->rd_dup == REDIRFD_TO_FILE)
close(openfd);
}
}
}
-#if !DEBUG_CLEAN
-#define free_pipe_list(head, indent) free_pipe_list(head)
-#define free_pipe(pi, indent) free_pipe(pi)
-#endif
-static void free_pipe_list(struct pipe *head, int indent);
+static void free_pipe_list(struct pipe *head);
/* Return code is the exit status of the pipe */
-static void free_pipe(struct pipe *pi, int indent)
+static void free_pipe(struct pipe *pi)
{
char **p;
struct command *command;
if (pi->stopped_cmds > 0) /* why? */
return;
- debug_printf_clean("%s run pipe: (pid %d)\n", indenter(indent), getpid());
+ debug_printf_clean("run pipe: (pid %d)\n", getpid());
for (i = 0; i < pi->num_cmds; i++) {
command = &pi->cmds[i];
- debug_printf_clean("%s command %d:\n", indenter(indent), i);
+ debug_printf_clean(" command %d:\n", i);
if (command->argv) {
for (a = 0, p = command->argv; *p; a++, p++) {
- debug_printf_clean("%s argv[%d] = %s\n", indenter(indent), a, *p);
+ debug_printf_clean(" argv[%d] = %s\n", a, *p);
}
free_strings(command->argv);
command->argv = NULL;
}
/* not "else if": on syntax error, we may have both! */
if (command->group) {
- debug_printf_clean("%s begin group (grp_type:%d)\n", indenter(indent), command->grp_type);
- free_pipe_list(command->group, indent+3);
- debug_printf_clean("%s end group\n", indenter(indent));
+ debug_printf_clean(" begin group (grp_type:%d)\n",
+ command->grp_type);
+ free_pipe_list(command->group);
+ debug_printf_clean(" end group\n");
command->group = NULL;
}
+ /* else is crucial here.
+ * If group != NULL, child_func is meaningless */
+#if ENABLE_HUSH_FUNCTIONS
+ else if (command->child_func) {
+ debug_printf_exec("cmd %p releases child func at %p\n", command, command->child_func);
+ command->child_func->parent_cmd = NULL;
+ }
+#endif
#if !BB_MMU
free(command->group_as_string);
command->group_as_string = NULL;
#endif
for (r = command->redirects; r; r = rnext) {
- debug_printf_clean("%s redirect %d%s", indenter(indent), r->fd, redir_table[r->rd_type].descrip);
- if (r->dup == -1) {
- /* guard against the case >$FOO, where foo is unset or blank */
- if (r->rd_filename) {
- debug_printf_clean(" %s\n", r->rd_filename);
- free(r->rd_filename);
- r->rd_filename = NULL;
- }
- } else {
- debug_printf_clean("&%d\n", r->dup);
+ debug_printf_clean(" redirect %d%s",
+ r->rd_fd, redir_table[r->rd_type].descrip);
+ /* guard against the case >$FOO, where foo is unset or blank */
+ if (r->rd_filename) {
+ debug_printf_clean(" fname:'%s'\n", r->rd_filename);
+ free(r->rd_filename);
+ r->rd_filename = NULL;
}
+ debug_printf_clean(" rd_dup:%d\n", r->rd_dup);
rnext = r->next;
free(r);
}
#endif
}
-static void free_pipe_list(struct pipe *head, int indent)
+static void free_pipe_list(struct pipe *head)
{
struct pipe *pi, *next;
for (pi = head; pi; pi = next) {
#if HAS_KEYWORDS
- debug_printf_clean("%s pipe reserved mode %d\n", indenter(indent), pi->res_word);
+ debug_printf_clean(" pipe reserved word %d\n", pi->res_word);
#endif
- free_pipe(pi, indent);
- debug_printf_clean("%s pipe followup code %d\n", indenter(indent), pi->followup);
+ free_pipe(pi);
+ debug_printf_clean("pipe followup code %d\n", pi->followup);
next = pi->next;
/*pi->next = NULL;*/
free(pi);
}
+static int run_list(struct pipe *pi);
+#if BB_MMU
+#define parse_stream(pstring, input, end_trigger) \
+ parse_stream(input, end_trigger)
+#endif
+static struct pipe *parse_stream(char **pstring,
+ struct in_str *input,
+ int end_trigger);
+static void parse_and_run_string(const char *s);
+
+
+static const struct built_in_command* find_builtin(const char *name)
+{
+ const struct built_in_command *x;
+ for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
+ if (strcmp(name, x->cmd) != 0)
+ continue;
+ debug_printf_exec("found builtin '%s'\n", name);
+ return x;
+ }
+ return NULL;
+}
+
+#if ENABLE_HUSH_FUNCTIONS
+static const struct function *find_function(const char *name)
+{
+ const struct function *funcp = G.top_func;
+ while (funcp) {
+ if (strcmp(name, funcp->name) == 0) {
+ break;
+ }
+ funcp = funcp->next;
+ }
+ 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;
+ }
+
+ 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);
+ free(funcp->name);
+ /* Note: if !funcp->body, do not free body_as_string!
+ * This is a special case of "-F name body" function:
+ * body_as_string was not malloced! */
+ if (funcp->body) {
+ free_pipe_list(funcp->body);
#if !BB_MMU
-typedef struct nommu_save_t {
- char **new_env;
- char **old_env;
- char **argv;
-} nommu_save_t;
-#else
+ free(funcp->body_as_string);
+#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
+ cmd->group_as_string = funcp->body_as_string;
+#endif
+ }
+ goto skip;
+ }
+ 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;
+}
+
+static void exec_function(const struct function *funcp, char **argv) NORETURN;
+static void exec_function(const struct function *funcp, char **argv)
+{
+# if BB_MMU
+ int n = 1;
+
+ argv[0] = G.global_argv[0];
+ G.global_argv = argv;
+ while (*++argv)
+ n++;
+ G.global_argc = n;
+ /* On MMU, funcp->body is always non-NULL */
+ n = run_list(funcp->body);
+ fflush(NULL);
+ _exit(n);
+# else
+ re_execute_shell(funcp->body_as_string, G.global_argv[0], argv + 1);
+# endif
+}
+
+static int run_function(const struct function *funcp, char **argv)
+{
+ int n;
+ char **pp;
+ char *sv_argv0;
+ smallint sv_g_malloced;
+ int sv_g_argc;
+ char **sv_g_argv;
+
+ sv_argv0 = argv[0];
+ sv_g_malloced = G.global_args_malloced;
+ sv_g_argc = G.global_argc;
+ sv_g_argv = G.global_argv;
+
+ pp = argv;
+ n = 1;
+ while (*++pp)
+ n++;
+
+ argv[0] = G.global_argv[0]; /* retain $0 */
+ G.global_args_malloced = 0;
+ G.global_argc = n;
+ G.global_argv = argv;
+
+ /* On MMU, funcp->body is always non-NULL */
+#if !BB_MMU
+ if (!funcp->body) {
+ /* Function defined by -F */
+ parse_and_run_string(funcp->body_as_string);
+ n = G.last_exitcode;
+ } else
+#endif
+ {
+ n = run_list(funcp->body);
+ }
+
+ if (G.global_args_malloced) {
+ /* function ran "set -- arg1 arg2 ..." */
+ pp = G.global_argv;
+ while (*++pp)
+ free(*pp);
+ free(G.global_argv);
+ }
+
+ argv[0] = sv_argv0;
+ G.global_args_malloced = sv_g_malloced;
+ G.global_argc = sv_g_argc;
+ G.global_argv = sv_g_argv;
+
+ return n;
+}
+#endif
+
+
+#if BB_MMU
#define pseudo_exec_argv(nommu_save, argv, assignment_cnt, argv_expanded) \
pseudo_exec_argv(argv, assignment_cnt, argv_expanded)
#define pseudo_exec(nommu_save, command, argv_expanded) \
pseudo_exec(command, argv_expanded)
#endif
-/* Called after [v]fork() in run_pipe(), or from builtin_exec().
+/* Called after [v]fork() in run_pipe, or from builtin_exec.
* Never returns.
* XXX no exit() here. If you don't exec, use _exit instead.
* The at_exit handlers apparently confuse the calling process,
#endif
}
+#if ENABLE_FEATURE_SH_STANDALONE || BB_MMU
+ if (strchr(argv[0], '/') != NULL)
+ 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
*/
{
int rcode;
- const struct built_in_command *x;
- for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
- if (strcmp(argv[0], x->cmd) == 0) {
- debug_printf_exec("running builtin '%s'\n",
- argv[0]);
- rcode = x->function(argv);
- fflush(NULL);
- _exit(rcode);
- }
+ const struct built_in_command *x = find_builtin(argv[0]);
+ if (x) {
+ rcode = x->function(argv);
+ fflush(NULL);
+ _exit(rcode);
}
}
#endif
-
-#if ENABLE_FEATURE_SH_STANDALONE
- /* Check if the command matches any busybox applets */
- if (strchr(argv[0], '/') == NULL) {
- int a = find_applet_by_name(argv[0]);
- if (a >= 0) {
-#if BB_MMU /* see above why on NOMMU it is not allowed */
- if (APPLET_IS_NOEXEC(a)) {
- debug_printf_exec("running applet '%s'\n", argv[0]);
- run_applet_no_and_exit(a, argv);
- }
-#endif
- /* Re-exec ourselves */
- debug_printf_exec("re-execing applet '%s'\n", argv[0]);
- sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
- execv(bb_busybox_exec_path, argv);
- /* If they called chroot or otherwise made the binary no longer
- * executable, fall through */
- }
- }
-#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);
-}
-
-#if BB_MMU
-static void reset_traps_to_defaults(void)
-{
- unsigned sig;
- int dirty;
-
- if (!G.traps)
- return;
- dirty = 0;
- for (sig = 0; sig < NSIG; sig++) {
- if (!G.traps[sig])
- 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
- * (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);
- dirty = 1;
- }
- if (dirty)
- sigprocmask(SIG_SETMASK, &G.blocked_set, NULL);
-}
-#define clean_up_after_re_execute() ((void)0)
-
-#else /* !BB_MMU */
-
-static void re_execute_shell(const char *s) NORETURN;
-static void re_execute_shell(const char *s)
-{
- char param_buf[sizeof("-$%x:%x:%x:%x") + sizeof(unsigned) * 4];
- struct variable *cur;
- char **argv, **pp, **pp2;
- unsigned cnt;
-
- sprintf(param_buf, "-$%x:%x:%x" USE_HUSH_LOOPS(":%x")
- , (unsigned) G.root_pid
- , (unsigned) G.last_bg_pid
- , (unsigned) G.last_return_code
- USE_HUSH_LOOPS(, G.depth_of_loop)
- );
- /* 1:hush 2:-$<pid>:<pid>:<exitcode>:<depth> <vars...>
- * 3:-c 4:<cmd> <argN...> 5:NULL
- */
- cnt = 5 + G.global_argc;
- for (cur = G.top_var; cur; cur = cur->next) {
- if (!cur->flg_export || cur->flg_read_only)
- cnt += 2;
- }
- G.argv_from_re_execing = pp = xzalloc(sizeof(argv[0]) * cnt);
- *pp++ = (char *) G.argv0_for_re_execing;
- *pp++ = param_buf;
- for (cur = G.top_var; cur; cur = cur->next) {
- if (cur->varstr == hush_version_str)
- continue;
- if (cur->flg_read_only) {
- *pp++ = (char *) "-R";
- *pp++ = cur->varstr;
- } else if (!cur->flg_export) {
- *pp++ = (char *) "-V";
- *pp++ = cur->varstr;
+#if ENABLE_HUSH_FUNCTIONS
+ /* Check if the command matches any functions */
+ {
+ const struct function *funcp = find_function(argv[0]);
+ if (funcp) {
+ exec_function(funcp, argv);
}
}
-//TODO: pass functions
- /* We can pass activated traps here. Say, -Tnn:trap_string
- *
- * However, POSIX says that subshells reset signals with traps
- * to SIG_DFL.
- * I tested bash-3.2 and it not only does that with true subshells
- * of the form ( list ), but with any forked children shells.
- * I set trap "echo W" WINCH; and then tried:
- *
- * { echo 1; sleep 20; echo 2; } &
- * while true; do echo 1; sleep 20; echo 2; break; done &
- * true | { echo 1; sleep 20; echo 2; } | cat
- *
- * In all these cases sending SIGWINCH to the child shell
- * did not run the trap. If I add trap "echo V" WINCH;
- * _inside_ group (just before echo 1), it works.
- *
- * I conclude it means we don't need to pass active traps here.
- * exec syscall below resets them to SIG_DFL for us.
- */
- *pp++ = (char *) "-c";
- *pp++ = (char *) s;
- pp2 = G.global_argv;
- while (*pp2)
- *pp++ = *pp2++;
- /* *pp = NULL; - is already there */
-
- debug_printf_exec("re_execute_shell pid:%d cmd:'%s'\n", getpid(), s);
- sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
- execv(bb_busybox_exec_path, G.argv_from_re_execing);
- /* Fallback. Useful for init=/bin/hush usage etc */
- if (G.argv0_for_re_execing[0] == '/')
- execv(G.argv0_for_re_execing, G.argv_from_re_execing);
- xfunc_error_retval = 127;
- bb_error_msg_and_die("can't re-execute the shell");
-}
+#endif
-static void clean_up_after_re_execute(void)
-{
- char **pp = G.argv_from_re_execing;
- if (pp) {
- /* Must match re_execute_shell's allocations (if any) */
- free(pp);
- G.argv_from_re_execing = NULL;
+#if ENABLE_FEATURE_SH_STANDALONE
+ /* Check if the command matches any busybox applets */
+ {
+ int a = find_applet_by_name(argv[0]);
+ if (a >= 0) {
+# if BB_MMU /* see above why on NOMMU it is not allowed */
+ if (APPLET_IS_NOEXEC(a)) {
+ debug_printf_exec("running applet '%s'\n", argv[0]);
+ run_applet_no_and_exit(a, argv);
+ }
+# endif
+ /* Re-exec ourselves */
+ debug_printf_exec("re-execing applet '%s'\n", argv[0]);
+ sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
+ execv(bb_busybox_exec_path, argv);
+ /* If they called chroot or otherwise made the binary no longer
+ * executable, fall through */
+ }
}
-}
#endif
-static int run_list(struct pipe *pi);
+#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);
+}
-/* Called after [v]fork() in run_pipe()
+/* Called after [v]fork() in run_pipe
*/
static void pseudo_exec(nommu_save_t *nommu_save,
struct command *command,
* since this process is about to exit */
_exit(rcode);
#else
- re_execute_shell(command->group_as_string);
+ re_execute_shell(command->group_as_string,
+ G.global_argv[0],
+ G.global_argv + 1);
#endif
}
{
remove_bg_job(pi);
pi->stopped_cmds = 0;
- free_pipe(pi, 0);
+ free_pipe(pi);
free(pi);
}
#endif /* JOB */
int rcode;
debug_printf_exec("run_pipe start: members:%d\n", pi->num_cmds);
+ debug_enter();
USE_HUSH_JOB(pi->pgrp = -1;)
pi->stopped_cmds = 0;
if (command->group) {
#if ENABLE_HUSH_FUNCTIONS
if (command->grp_type == GRP_FUNCTION) {
- /* func () { list } */
- bb_error_msg("here we ought to remember function definition, and go on");
+ /* "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
+ funcp->body_as_string = command->group_as_string;
+ command->group_as_string = NULL;
+#endif
+ command->group = NULL;
+ command->argv[0] = NULL;
+ debug_printf_exec("cmd %p has child func at %p\n", command, funcp);
+ funcp->parent_cmd = command;
+ command->child_func = funcp;
+
+ debug_printf_exec("run_pipe: return EXIT_SUCCESS\n");
+ debug_leave();
return EXIT_SUCCESS;
}
#endif
/* { list } */
debug_printf("non-subshell group\n");
- setup_redirects(command, squirrel);
- debug_printf_exec(": run_list\n");
- rcode = run_list(command->group) & 0xff;
+ rcode = 1; /* exitcode if redir failed */
+ if (setup_redirects(command, squirrel) == 0) {
+ debug_printf_exec(": run_list\n");
+ rcode = run_list(command->group) & 0xff;
+ }
restore_redirects(squirrel);
- debug_printf_exec("run_pipe return %d\n", rcode);
IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;)
+ debug_leave();
+ debug_printf_exec("run_pipe: return %d\n", rcode);
return rcode;
}
argv = command->argv ? command->argv : (char **) &null_ptr;
{
const struct built_in_command *x;
+#if ENABLE_HUSH_FUNCTIONS
+ const struct function *funcp;
+#else
+ enum { funcp = 0 };
+#endif
char **new_env = NULL;
char **old_env = NULL;
if (argv[command->assignment_cnt] == NULL) {
/* Assignments, but no command */
/* Ensure redirects take effect. Try "a=t >file" */
- setup_redirects(command, squirrel);
+ rcode = setup_redirects(command, squirrel);
restore_redirects(squirrel);
/* Set shell variables */
while (*argv) {
/* Do we need to flag set_local_var() errors?
* "assignment to readonly var" and "putenv error"
*/
- return EXIT_SUCCESS;
+ IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;)
+ debug_leave();
+ debug_printf_exec("run_pipe: return %d\n", rcode);
+ return rcode;
}
/* Expand the rest into (possibly) many strings each */
argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt);
- for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
- if (strcmp(argv_expanded[0], x->cmd) != 0)
- continue;
- if (x->function == builtin_exec && argv_expanded[1] == NULL) {
- debug_printf("exec with redirects only\n");
- setup_redirects(command, NULL);
- rcode = EXIT_SUCCESS;
- goto clean_up_and_ret1;
+ x = find_builtin(argv_expanded[0]);
+#if ENABLE_HUSH_FUNCTIONS
+ funcp = NULL;
+ if (!x)
+ funcp = find_function(argv_expanded[0]);
+#endif
+ if (x || funcp) {
+ if (!funcp) {
+ if (x->function == builtin_exec && argv_expanded[1] == NULL) {
+ debug_printf("exec with redirects only\n");
+ rcode = setup_redirects(command, NULL);
+ goto clean_up_and_ret1;
+ }
}
- debug_printf("builtin inline %s\n", argv_expanded[0]);
/* XXX setup_redirects acts on file descriptors, not FILEs.
* This is perfect for work that comes after exec().
* Is it really safe for inline use? Experimentally,
* things seem to work with glibc. */
- setup_redirects(command, squirrel);
- new_env = expand_assignments(argv, command->assignment_cnt);
- old_env = putenv_all_and_save_old(new_env);
- debug_printf_exec(": builtin '%s' '%s'...\n",
- x->cmd, argv_expanded[1]);
- rcode = x->function(argv_expanded) & 0xff;
+ 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);
+ if (!funcp) {
+ debug_printf_exec(": builtin '%s' '%s'...\n",
+ x->cmd, argv_expanded[1]);
+ rcode = x->function(argv_expanded) & 0xff;
+ }
+#if ENABLE_HUSH_FUNCTIONS
+ else {
+ debug_printf_exec(": function '%s' '%s'...\n",
+ funcp->name, argv_expanded[1]);
+ rcode = run_function(funcp, argv_expanded) & 0xff;
+ }
+#endif
+ }
#if ENABLE_FEATURE_SH_STANDALONE
clean_up_and_ret:
#endif
restore_redirects(squirrel);
free_strings_and_unsetenv(new_env, 1);
putenv_all(old_env);
- free(old_env); /* not free_strings()! */
+ /* Free the pointers, but the strings themselves
+ * are in environ now, don't use free_strings! */
+ free(old_env);
clean_up_and_ret1:
free(argv_expanded);
IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;)
+ debug_leave();
debug_printf_exec("run_pipe return %d\n", rcode);
return rcode;
}
+
#if ENABLE_FEATURE_SH_STANDALONE
i = find_applet_by_name(argv_expanded[0]);
if (i >= 0 && APPLET_IS_NOFORK(i)) {
- setup_redirects(command, squirrel);
- save_nofork_data(&G.nofork_save);
- new_env = expand_assignments(argv, command->assignment_cnt);
- old_env = putenv_all_and_save_old(new_env);
- debug_printf_exec(": run_nofork_applet '%s' '%s'...\n",
+ rcode = setup_redirects(command, squirrel);
+ if (rcode == 0) {
+ save_nofork_data(&G.nofork_save);
+ new_env = expand_assignments(argv, command->assignment_cnt);
+ old_env = putenv_all_and_save_old(new_env);
+ debug_printf_exec(": run_nofork_applet '%s' '%s'...\n",
argv_expanded[0], argv_expanded[1]);
- rcode = run_nofork_applet_prime(&G.nofork_save, i, argv_expanded);
+ rcode = run_nofork_applet_prime(&G.nofork_save, i, argv_expanded);
+ }
goto clean_up_and_ret;
}
#endif
command->pid = BB_MMU ? fork() : vfork();
if (!command->pid) { /* child */
#if ENABLE_HUSH_JOB
- die_sleep = 0; /* do not restore tty pgrp on xfunc death */
+ disable_restore_tty_pgrp_on_exit();
/* Every child adds itself to new process group
* with pgid == pid_of_first_child_in_pipe */
close(pipefds[0]); /* read end */
/* Like bash, explicit redirects override pipes,
* and the pipe fd is available for dup'ing. */
- setup_redirects(command, NULL);
+ if (setup_redirects(command, NULL))
+ _exit(1);
/* Restore default handlers just prior to exec */
/*signal(SIGCHLD, SIG_DFL); - so far we don't have any handlers */
}
/* parent or error */
-#if ENABLE_HUSH_JOB
- die_sleep = -1; /* restore tty pgrp on xfunc death */
-#endif
+ enable_restore_tty_pgrp_on_exit();
#if !BB_MMU
/* Clean up after vforked child */
clean_up_after_re_execute();
free(nommu_save.argv);
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);
#endif
free(argv_expanded);
argv_expanded = NULL;
}
if (!pi->alive_cmds) {
+ debug_leave();
debug_printf_exec("run_pipe return 1 (all forks failed, no children)\n");
return 1;
}
+ debug_leave();
debug_printf_exec("run_pipe return -1 (%u children started)\n", pi->alive_cmds);
return -1;
}
struct command *command = &pi->cmds[prn];
char **argv = command->argv;
- fprintf(stderr, "%*s prog %d assignment_cnt:%d",
+ fprintf(stderr, "%*s cmd %d assignment_cnt:%d",
lvl*2, "", prn,
command->assignment_cnt);
if (command->group) {
char **for_lcur = NULL;
char **for_list = NULL;
#endif
- smallint flag_skip = 1;
- smalluint rcode = 0; /* probably just for compiler */
+ smallint last_followup;
+ smalluint rcode;
#if ENABLE_HUSH_IF || ENABLE_HUSH_CASE
smalluint cond_code = 0;
#else
- enum { cond_code = 0, };
+ enum { cond_code = 0 };
+#endif
+#if HAS_KEYWORDS
+ smallint rword; /* enum reserved_style */
+ smallint last_rword; /* ditto */
#endif
- /*enum reserved_style*/ smallint rword = RES_NONE;
- /*enum reserved_style*/ smallint skip_more_for_this_rword = RES_XXXX;
debug_printf_exec("run_list start lvl %d\n", G.run_list_level + 1);
+ debug_enter();
#if ENABLE_HUSH_LOOPS
/* Check syntax for "for" */
continue;
/* current word is FOR or IN (BOLD in comments below) */
if (cpipe->next == NULL) {
- syntax("malformed for");
+ syntax_error("malformed for");
+ debug_leave();
debug_printf_exec("run_list lvl %d return 1\n", G.run_list_level);
return 1;
}
if (cpipe->res_word == RES_IN /* "for v IN a b; not_do..."? */
|| cpipe->next->res_word != RES_IN /* FOR v not_do_and_not_in..."? */
) {
- syntax("malformed for");
+ syntax_error("malformed for");
+ debug_leave();
debug_printf_exec("run_list lvl %d return 1\n", G.run_list_level);
return 1;
}
//// /* ctrl-Z handler will store pid etc in pi */
//// G.toplevel_list = pi;
//// G.ctrl_z_flag = 0;
-////#if ENABLE_FEATURE_SH_STANDALONE
-//// G.nofork_save.saved = 0; /* in case we will run a nofork later */
-////#endif
+#if ENABLE_FEATURE_SH_STANDALONE
+ G.nofork_save.saved = 0; /* in case we will run a nofork later */
+#endif
//// signal_SA_RESTART_empty_mask(SIGTSTP, handler_ctrl_z);
//// signal(SIGINT, handler_ctrl_c);
}
#endif /* JOB */
+#if HAS_KEYWORDS
+ rword = RES_NONE;
+ last_rword = RES_XXXX;
+#endif
+ last_followup = PIPE_SEQ;
+ 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) {
if (G.flag_SIGINT)
break;
IF_HAS_KEYWORDS(rword = pi->res_word;)
- IF_HAS_NO_KEYWORDS(rword = RES_NONE;)
- debug_printf_exec(": rword=%d cond_code=%d skip_more=%d\n",
- rword, cond_code, skip_more_for_this_rword);
+ debug_printf_exec(": rword=%d cond_code=%d last_rword=%d\n",
+ rword, cond_code, last_rword);
#if ENABLE_HUSH_LOOPS
if ((rword == RES_WHILE || rword == RES_UNTIL || rword == RES_FOR)
&& loop_top == NULL /* avoid bumping G.depth_of_loop twice */
G.depth_of_loop++;
}
#endif
- if (rword == skip_more_for_this_rword && flag_skip) {
- if (pi->followup == PIPE_SEQ)
- flag_skip = 0;
- /* it is "<false> && CMD" or "<true> || CMD"
- * and we should not execute CMD */
- continue;
+ /* Still in the same "if...", "then..." or "do..." branch? */
+ if (IF_HAS_KEYWORDS(rword == last_rword &&) 1) {
+ if ((rcode == 0 && last_followup == PIPE_OR)
+ || (rcode != 0 && last_followup == PIPE_AND)
+ ) {
+ /* It is "<true> || CMD" or "<false> && CMD"
+ * and we should not execute CMD */
+ debug_printf_exec("skipped cmd because of || or &&\n");
+ last_followup = pi->followup;
+ continue;
+ }
}
- flag_skip = 1;
- skip_more_for_this_rword = RES_XXXX;
+ last_followup = pi->followup;
+ IF_HAS_KEYWORDS(last_rword = rword;)
#if ENABLE_HUSH_IF
if (cond_code) {
if (rword == RES_THEN) {
+ /* if false; then ... fi has exitcode 0! */
+ G.last_exitcode = rcode = EXIT_SUCCESS;
/* "if <false> THEN cmd": skip cmd */
continue;
}
vals = (char**)encoded_dollar_at_argv;
if (pi->next->res_word == RES_IN) {
/* if no variable values after "in" we skip "for" */
- if (!pi->next->cmds[0].argv)
+ if (!pi->next->cmds[0].argv) {
+ G.last_exitcode = rcode = EXIT_SUCCESS;
+ debug_printf_exec(": null FOR: exitcode EXIT_SUCCESS\n");
break;
+ }
vals = pi->next->cmds[0].argv;
} /* else: "for var; do..." -> assume "$@" list */
/* create list of variable values */
pi->cmds[0].argv[0] = for_varname;
break;
}
- /* insert next value from for_lcur */
-//TODO: does it need escaping?
+ /* 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;
}
/* After analyzing all keywords and conditions, we decided
* to execute this pipe. NB: have to do checkjobs(NULL)
- * after run_pipe() to collect any background children,
+ * after run_pipe to collect any background children,
* even if list execution is to be stopped. */
debug_printf_exec(": run_pipe with %d members\n", pi->num_cmds);
{
#endif
rcode = r = run_pipe(pi); /* NB: rcode is a smallint */
if (r != -1) {
- /* we only ran a builtin: rcode is already known
+ /* We only ran a builtin: 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);
check_and_run_traps(0);
#if ENABLE_HUSH_LOOPS
- /* was it "break" or "continue"? */
+ /* Was it "break" or "continue"? */
if (G.flag_break_continue) {
smallint fbc = G.flag_break_continue;
- /* we might fall into outer *loop*,
+ /* We might fall into outer *loop*,
* don't want to break it too */
if (loop_top) {
G.depth_break_continue--;
}
#endif
} else if (pi->followup == PIPE_BG) {
- /* what does bash do with attempts to background builtins? */
+ /* What does bash do with attempts to background builtins? */
/* even bash 3.2 doesn't do that well with nested bg:
* try "{ { sleep 10; echo DEEP; } & echo HERE; } &".
* I'm NOT treating inner &'s as jobs */
if (G.run_list_level == 1)
insert_bg_job(pi);
#endif
- rcode = 0; /* EXIT_SUCCESS */
+ G.last_exitcode = rcode = EXIT_SUCCESS;
+ debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n");
} else {
#if ENABLE_HUSH_JOB
if (G.run_list_level == 1 && G_interactive_fd) {
- /* waits for completion, then fg's main shell */
+ /* Waits for completion, then fg's main shell */
rcode = checkjobs_and_fg_shell(pi);
+ debug_printf_exec(": checkjobs_and_fg_shell exitcode %d\n", rcode);
check_and_run_traps(0);
- debug_printf_exec(": checkjobs_and_fg_shell returned %d\n", rcode);
} else
#endif
- { /* this one just waits for completion */
+ { /* This one just waits for completion */
rcode = checkjobs(pi);
+ debug_printf_exec(": checkjobs exitcode %d\n", rcode);
check_and_run_traps(0);
- debug_printf_exec(": checkjobs returned %d\n", rcode);
}
+ G.last_exitcode = rcode;
}
}
- debug_printf_exec(": setting last_return_code=%d\n", rcode);
- G.last_return_code = rcode;
/* Analyze how result affects subsequent commands */
#if ENABLE_HUSH_IF
cond_code = rcode;
#endif
#if ENABLE_HUSH_LOOPS
- if (rword == RES_WHILE) {
- if (rcode) {
- rcode = 0; /* "while false; do...done" - exitcode 0 */
- goto check_jobs_and_break;
+ /* Beware of "while false; true; do ..."! */
+ if (pi->next && pi->next->res_word == RES_DO) {
+ if (rword == RES_WHILE) {
+ if (rcode) {
+ /* "while false; do...done" - exitcode 0 */
+ G.last_exitcode = rcode = EXIT_SUCCESS;
+ debug_printf_exec(": while expr is false: breaking (exitcode:EXIT_SUCCESS)\n");
+ goto check_jobs_and_break;
+ }
}
- }
- if (rword == RES_UNTIL) {
- if (!rcode) {
+ if (rword == RES_UNTIL) {
+ if (!rcode) {
+ debug_printf_exec(": until expr is true: breaking\n");
check_jobs_and_break:
- checkjobs(NULL);
- break;
+ checkjobs(NULL);
+ break;
+ }
}
}
#endif
- if ((rcode == 0 && pi->followup == PIPE_OR)
- || (rcode != 0 && pi->followup == PIPE_AND)
- ) {
- skip_more_for_this_rword = rword;
- }
check_jobs_and_continue:
checkjobs(NULL);
//// signal(SIGINT, SIG_IGN);
//// }
#endif
- debug_printf_exec("run_list lvl %d return %d\n", G.run_list_level + 1, rcode);
#if ENABLE_HUSH_LOOPS
if (loop_top)
G.depth_of_loop--;
#if ENABLE_HUSH_CASE
free(case_word);
#endif
+ debug_leave();
+ debug_printf_exec("run_list lvl %d return %d\n", G.run_list_level + 1, rcode);
return rcode;
}
int rcode = 0;
debug_printf_exec("run_and_free_list entered\n");
if (!G.fake_mode) {
- debug_printf_exec(": run_list with %d members\n", pi->num_cmds);
+ debug_printf_exec(": run_list: 1st pipe with %d cmds\n", pi->num_cmds);
rcode = run_list(pi);
}
/* free_pipe_list has the side effect of clearing memory.
* In the long run that function can be merged with run_list,
* but doing that now would hobble the debugging effort. */
- free_pipe_list(pi, /* indent: */ 0);
+ free_pipe_list(pi);
debug_printf_exec("run_and_free_list return %d\n", rcode);
return rcode;
}
-/* Peek ahead in the in_str to find out if we have a "&n" construct,
- * as in "2>&1", that represents duplicating a file descriptor.
- * Return either -2 (syntax error), -1 (no &), or the number found.
- */
-static int redirect_dup_num(struct in_str *input)
-{
- int ch, d = 0, ok = 0;
- ch = i_peek(input);
- if (ch != '&') return -1;
-
- i_getch(input); /* get the & */
- ch = i_peek(input);
- if (ch == '-') {
- i_getch(input);
- return -3; /* "-" represents "close me" */
- }
- while (isdigit(ch)) {
- d = d*10 + (ch-'0');
- ok = 1;
- i_getch(input);
- ch = i_peek(input);
- }
- if (ok) return d;
-
- bb_error_msg("ambiguous redirect");
- return -2;
-}
-
-/* The src parameter allows us to peek forward to a possible &n syntax
- * for file descriptor duplication, e.g., "2>&1".
- * Return code is 0 normally, 1 if a syntax error is detected in src.
- * Resource errors (in xmalloc) cause the process to exit */
-static int setup_redirect(struct parse_context *ctx,
- int fd,
- redir_type style,
- struct in_str *input)
-{
- struct command *command = ctx->command;
- struct redir_struct *redir;
- struct redir_struct **redirp;
- int dup_num;
-
- /* Check for a '2>&1' type redirect */
- dup_num = redirect_dup_num(input);
- if (dup_num == -2)
- return 1; /* syntax error */
-
- /* Create a new redir_struct and drop it onto the end of the linked list */
- redirp = &command->redirects;
- while ((redir = *redirp) != NULL) {
- redirp = &(redir->next);
- }
- *redirp = redir = xzalloc(sizeof(*redir));
- /* redir->next = NULL; */
- /* redir->rd_filename = NULL; */
- redir->rd_type = style;
- redir->fd = (fd == -1) ? redir_table[style].default_fd : fd;
-
- debug_printf("Redirect type %d%s\n", redir->fd, redir_table[style].descrip);
-
- redir->dup = dup_num;
- if (dup_num != -1) {
- /* Erik had a check here that the file descriptor in question
- * is legit; I postpone that to "run time"
- * A "-" representation of "close me" shows up as a -3 here */
- debug_printf("Duplicating redirect '%d>&%d'\n", redir->fd, redir->dup);
- } else {
- /* We do _not_ try to open the file that src points to,
- * since we need to return and let src be expanded first.
- * Set ctx->pending_redirect, so we know what to do at the
- * end of the next parsed word. */
- ctx->pending_redirect = redir;
- }
- return 0;
-}
-
-
static struct pipe *new_pipe(void)
{
struct pipe *pi;
&& command->redirects == NULL
) {
debug_printf_parse("done_command: skipping null cmd, num_cmds=%d\n", pi->num_cmds);
+ memset(command, 0, sizeof(*command)); /* paranoia */
return pi->num_cmds;
}
pi->num_cmds++;
debug_printf_parse("done_command: ++num_cmds=%d\n", pi->num_cmds);
+ //debug_print_tree(ctx->list_head, 20);
} else {
debug_printf_parse("done_command: initializing, num_cmds=%d\n", pi->num_cmds);
}
/* Close previous command */
not_null = done_command(ctx);
ctx->pipe->followup = type;
- IF_HAS_KEYWORDS(ctx->pipe->pi_inverted = ctx->ctx_inverted;)
- IF_HAS_KEYWORDS(ctx->ctx_inverted = 0;)
- IF_HAS_KEYWORDS(ctx->pipe->res_word = ctx->ctx_res_w;)
+#if HAS_KEYWORDS
+ ctx->pipe->pi_inverted = ctx->ctx_inverted;
+ ctx->ctx_inverted = 0;
+ ctx->pipe->res_word = ctx->ctx_res_w;
+#endif
/* Without this check, even just <enter> on command line generates
* tree of three NOPs (!). Which is harmless but annoying.
* IOW: it is safe to do it unconditionally.
* RES_NONE case is for "for a in; do ..." (empty IN set)
- * to work, possibly other cases too. */
- if (not_null IF_HAS_KEYWORDS(|| ctx->ctx_res_w != RES_NONE)) {
+ * and other cases to work. */
+ if (not_null
+#if ENABLE_HUSH_IF
+ || ctx->ctx_res_w == RES_FI
+#endif
+#if ENABLE_HUSH_LOOPS
+ || ctx->ctx_res_w == RES_DONE
+ || ctx->ctx_res_w == RES_FOR
+ || ctx->ctx_res_w == RES_IN
+#endif
+#if ENABLE_HUSH_CASE
+ || ctx->ctx_res_w == RES_ESAC
+#endif
+ ) {
struct pipe *new_p;
debug_printf_parse("done_pipe: adding new pipe: "
"not_null:%d ctx->ctx_res_w:%d\n",
* ctx->command = &ctx->pipe->cmds[0];
*/
done_command(ctx);
+ //debug_print_tree(ctx->list_head, 10);
}
debug_printf_parse("done_pipe return\n");
}
done_command(ctx);
}
-
/* If a reserved word is found and processed, parse context is modified
* and 1 is returned.
*/
#endif
if (r->flag == 0) { /* '!' */
if (ctx->ctx_inverted) { /* bash doesn't accept '! ! true' */
- syntax("! ! command");
+ syntax_error("! ! command");
IF_HAS_KEYWORDS(ctx->ctx_res_w = RES_SNTX;)
}
ctx->ctx_inverted = 1;
initialize_context(ctx);
ctx->stack = old;
} else if (/*ctx->ctx_res_w == RES_NONE ||*/ !(ctx->old_flag & (1 << r->res))) {
- syntax(word->data);
+ syntax_error_at(word->data);
ctx->ctx_res_w = RES_SNTX;
return 1;
}
struct command *command = ctx->command;
debug_printf_parse("done_word entered: '%s' %p\n", word->data, command);
- if (word->length == 0 && word->nonnull == 0) {
+ if (word->length == 0 && word->o_quoted == 0) {
debug_printf_parse("done_word return 0: true null, ignored\n");
return 0;
}
- /* If this word wasn't an assignment, next ones definitely
- * can't be assignments. Even if they look like ones. */
- if (word->o_assignment != DEFINITELY_ASSIGNMENT
- && word->o_assignment != WORD_IS_KEYWORD
- ) {
- word->o_assignment = NOT_ASSIGNMENT;
- } else {
- if (word->o_assignment == DEFINITELY_ASSIGNMENT)
- command->assignment_cnt++;
- word->o_assignment = MAYBE_ASSIGNMENT;
- }
if (ctx->pending_redirect) {
/* We do not glob in e.g. >*.tmp case. bash seems to glob here
* only if run as "bash", not "sh" */
+ /* http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html
+ * "2.7 Redirection
+ * ...the word that follows the redirection operator
+ * shall be subjected to tilde expansion, parameter expansion,
+ * command substitution, arithmetic expansion, and quote
+ * removal. Pathname expansion shall not be performed
+ * on the word by a non-interactive shell; an interactive
+ * shell may perform it, but shall do so only when
+ * the expansion would result in one word."
+ */
ctx->pending_redirect->rd_filename = xstrdup(word->data);
- word->o_assignment = NOT_ASSIGNMENT;
- debug_printf("word stored in rd_filename: '%s'\n", word->data);
+ /* Cater for >\file case:
+ * >\a creates file a; >\\a, >"\a", >"\\a" create file \a
+ * 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;
+ }
+ debug_printf_parse("word stored in rd_filename: '%s'\n", word->data);
+ ctx->pending_redirect = NULL;
} else {
- /* "{ echo foo; } echo bar" - bad */
- /* NB: bash allows e.g.:
- * if true; then { echo foo; } fi
- * while if false; then false; fi do break; done
- * TODO? */
+ /* If this word wasn't an assignment, next ones definitely
+ * can't be assignments. Even if they look like ones. */
+ if (word->o_assignment != DEFINITELY_ASSIGNMENT
+ && word->o_assignment != WORD_IS_KEYWORD
+ ) {
+ word->o_assignment = NOT_ASSIGNMENT;
+ } else {
+ if (word->o_assignment == DEFINITELY_ASSIGNMENT)
+ command->assignment_cnt++;
+ word->o_assignment = MAYBE_ASSIGNMENT;
+ }
+
if (command->group) {
- syntax(word->data);
+ /* "{ echo foo; } echo bar" - bad */
+ /* NB: bash allows e.g.:
+ * if true; then { echo foo; } fi
+ * while if false; then false; fi do break; done
+ * and disallows:
+ * while if false; then false; fi; do; break; done
+ * TODO? */
+ syntax_error_at(word->data);
debug_printf_parse("done_word return 1: syntax error, "
"groups and arglists don't mix\n");
return 1;
}
#if HAS_KEYWORDS
-#if ENABLE_HUSH_CASE
+# if ENABLE_HUSH_CASE
if (ctx->ctx_dsemicolon
&& strcmp(word->data, "esac") != 0 /* not "... pattern) cmd;; esac" */
) {
/* ctx->ctx_res_w = RES_MATCH; */
ctx->ctx_dsemicolon = 0;
} else
-#endif
+# endif
if (!command->argv /* if it's the first word... */
-#if ENABLE_HUSH_LOOPS
+# if ENABLE_HUSH_LOOPS
&& ctx->ctx_res_w != RES_FOR /* ...not after FOR or IN */
&& ctx->ctx_res_w != RES_IN
-#endif
+# endif
) {
debug_printf_parse(": checking '%s' for reserved-ness\n", word->data);
if (reserved_word(word, ctx)) {
- o_reset(word);
+ o_reset_to_empty_unquoted(word);
debug_printf_parse("done_word return %d\n",
(ctx->ctx_res_w == RES_SNTX));
return (ctx->ctx_res_w == RES_SNTX);
}
}
#endif
- if (word->nonnull /* word had "xx" or 'xx' at least as part of it. */
+ if (word->o_quoted /* word had "xx" or 'xx' at least as part of it. */
/* optimization: and if it's ("" or '') or ($v... or `cmd`...): */
&& (word->data[0] == '\0' || word->data[0] == SPECIAL_VAR_SYMBOL)
/* (otherwise it's known to be not empty and is already safe) */
}
}
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);
}
- o_reset(word);
- ctx->pending_redirect = NULL;
-
#if ENABLE_HUSH_LOOPS
- /* Force FOR to have just one word (variable name) */
- /* NB: basically, this makes hush see "for v in ..." syntax as if
- * as it is "for v; in ...". FOR and IN become two pipe structs
- * in parse tree. */
if (ctx->ctx_res_w == RES_FOR) {
-//TODO: check that command->argv[0] is a valid variable name!
+ if (word->o_quoted
+ || !is_well_formed_var_name(command->argv[0], '\0')
+ ) {
+ /* bash says just "not a valid identifier" */
+ syntax_error("not a valid identifier in for");
+ return 1;
+ }
+ /* Force FOR to have just one word (variable name) */
+ /* NB: basically, this makes hush see "for v in ..."
+ * syntax as if it is "for v; in ...". FOR and IN become
+ * two pipe structs in parse tree. */
done_pipe(ctx, PIPE_SEQ);
}
#endif
done_pipe(ctx, PIPE_SEQ);
}
#endif
+
+ o_reset_to_empty_unquoted(word);
+
debug_printf_parse("done_word return 0\n");
return 0;
}
+
+/* Peek ahead in the input to find out if we have a "&n" construct,
+ * as in "2>&1", that represents duplicating a file descriptor.
+ * Return:
+ * REDIRFD_CLOSE if >&- "close fd" construct is seen,
+ * REDIRFD_SYNTAX_ERR if syntax error,
+ * REDIRFD_TO_FILE if no & was seen,
+ * or the number found.
+ */
+#if BB_MMU
+#define parse_redir_right_fd(as_string, input) \
+ parse_redir_right_fd(input)
+#endif
+static int parse_redir_right_fd(o_string *as_string, struct in_str *input)
+{
+ int ch, d, ok;
+
+ ch = i_peek(input);
+ if (ch != '&')
+ return REDIRFD_TO_FILE;
+
+ ch = i_getch(input); /* get the & */
+ nommu_addchr(as_string, ch);
+ ch = i_peek(input);
+ if (ch == '-') {
+ ch = i_getch(input);
+ nommu_addchr(as_string, ch);
+ return REDIRFD_CLOSE;
+ }
+ d = 0;
+ ok = 0;
+ while (ch != EOF && isdigit(ch)) {
+ d = d*10 + (ch-'0');
+ ok = 1;
+ ch = i_getch(input);
+ nommu_addchr(as_string, ch);
+ ch = i_peek(input);
+ }
+ if (ok) return d;
+
+//TODO: this is the place to catch ">&file" bashism (redirect both fd 1 and 2)
+
+ bb_error_msg("ambiguous redirect");
+ return REDIRFD_SYNTAX_ERR;
+}
+
+/* Return code is 0 normal, 1 if a syntax error is detected
+ */
+static int parse_redirect(struct parse_context *ctx,
+ int fd,
+ redir_type style,
+ struct in_str *input)
+{
+ struct command *command = ctx->command;
+ struct redir_struct *redir;
+ struct redir_struct **redirp;
+ int dup_num;
+
+ dup_num = REDIRFD_TO_FILE;
+ if (style != REDIRECT_HEREDOC) {
+ /* Check for a '>&1' type redirect */
+ dup_num = parse_redir_right_fd(&ctx->as_string, input);
+ if (dup_num == REDIRFD_SYNTAX_ERR)
+ return 1;
+ } else {
+ int ch = i_peek(input);
+ dup_num = (ch == '-'); /* HEREDOC_SKIPTABS bit is 1 */
+ if (dup_num) { /* <<-... */
+ ch = i_getch(input);
+ nommu_addchr(&ctx->as_string, ch);
+ ch = i_peek(input);
+ }
+ }
+
+ if (style == REDIRECT_OVERWRITE && dup_num == REDIRFD_TO_FILE) {
+ int ch = i_peek(input);
+ if (ch == '|') {
+ /* >|FILE redirect ("clobbering" >).
+ * Since we do not support "set -o noclobber" yet,
+ * >| and > are the same for now. Just eat |.
+ */
+ ch = i_getch(input);
+ nommu_addchr(&ctx->as_string, ch);
+ }
+ }
+
+ /* Create a new redir_struct and append it to the linked list */
+ redirp = &command->redirects;
+ while ((redir = *redirp) != NULL) {
+ redirp = &(redir->next);
+ }
+ *redirp = redir = xzalloc(sizeof(*redir));
+ /* redir->next = NULL; */
+ /* redir->rd_filename = NULL; */
+ redir->rd_type = style;
+ redir->rd_fd = (fd == -1) ? redir_table[style].default_fd : fd;
+
+ debug_printf_parse("redirect type %d %s\n", redir->rd_fd,
+ redir_table[style].descrip);
+
+ redir->rd_dup = dup_num;
+ if (style != REDIRECT_HEREDOC && dup_num != REDIRFD_TO_FILE) {
+ /* Erik had a check here that the file descriptor in question
+ * is legit; I postpone that to "run time"
+ * A "-" representation of "close me" shows up as a -3 here */
+ debug_printf_parse("duplicating redirect '%d>&%d'\n",
+ redir->rd_fd, redir->rd_dup);
+ } else {
+ /* Set ctx->pending_redirect, so we know what to do at the
+ * end of the next parsed word. */
+ ctx->pending_redirect = redir;
+ }
+ return 0;
+}
+
/* If a redirect is immediately preceded by a number, that number is
* supposed to tell which file descriptor to redirect. This routine
* looks for such preceding numbers. In an ideal world this routine
* echo 49>foo # redirects fd 49 to file "foo", nothing passed to echo
* echo -2>foo # redirects fd 1 to file "foo", "-2" passed to echo
* echo 49x>foo # redirects fd 1 to file "foo", "49x" passed to echo
- * A -1 output from this program means no valid number was found, so the
- * caller should use the appropriate default for this redirection.
+ *
+ * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html
+ * "2.7 Redirection
+ * ... If n is quoted, the number shall not be recognized as part of
+ * the redirection expression. For example:
+ * echo \2>a
+ * writes the character 2 into file a"
+ * We are getting it right by setting ->o_quoted on any \<char>
+ *
+ * A -1 return means no valid number was found,
+ * the caller should use the appropriate default for this redirection.
+ */
+static int redirect_opt_num(o_string *o)
+{
+ int num;
+
+ if (o->data == NULL)
+ return -1;
+ num = bb_strtou(o->data, NULL, 10);
+ if (errno || num < 0)
+ return -1;
+ o_reset_to_empty_unquoted(o);
+ return num;
+}
+
+#if BB_MMU
+#define fetch_till_str(as_string, input, word, skip_tabs) \
+ fetch_till_str(input, word, skip_tabs)
+#endif
+static char *fetch_till_str(o_string *as_string,
+ struct in_str *input,
+ const char *word,
+ int skip_tabs)
+{
+ o_string heredoc = NULL_O_STRING;
+ int past_EOL = 0;
+ int ch;
+
+ goto jump_in;
+ while (1) {
+ ch = i_getch(input);
+ nommu_addchr(as_string, ch);
+ if (ch == '\n') {
+ if (strcmp(heredoc.data + past_EOL, word) == 0) {
+ heredoc.data[past_EOL] = '\0';
+ debug_printf_parse("parsed heredoc '%s'\n", heredoc.data);
+ return heredoc.data;
+ }
+ do {
+ o_addchr(&heredoc, ch);
+ past_EOL = heredoc.length;
+ jump_in:
+ do {
+ ch = i_getch(input);
+ nommu_addchr(as_string, ch);
+ } while (skip_tabs && ch == '\t');
+ } while (ch == '\n');
+ }
+ if (ch == EOF) {
+ o_free_unsafe(&heredoc);
+ return NULL;
+ }
+ o_addchr(&heredoc, ch);
+ nommu_addchr(as_string, ch);
+ }
+}
+
+/* Look at entire parse tree for not-yet-loaded REDIRECT_HEREDOCs
+ * and load them all. There should be exactly heredoc_cnt of them.
*/
-static int redirect_opt_num(o_string *o)
+static int fetch_heredocs(int heredoc_cnt, struct parse_context *ctx, struct in_str *input)
{
- int num;
+ struct pipe *pi = ctx->list_head;
- if (o->length == 0)
- return -1;
- for (num = 0; num < o->length; num++) {
- if (!isdigit(o->data[num])) {
- return -1;
+ while (pi && heredoc_cnt) {
+ int i;
+ struct command *cmd = pi->cmds;
+
+ debug_printf_parse("fetch_heredocs: num_cmds:%d cmd argv0:'%s'\n",
+ pi->num_cmds,
+ cmd->argv ? cmd->argv[0] : "NONE");
+ for (i = 0; i < pi->num_cmds; i++) {
+ struct redir_struct *redir = cmd->redirects;
+
+ debug_printf_parse("fetch_heredocs: %d cmd argv0:'%s'\n",
+ i, cmd->argv ? cmd->argv[0] : "NONE");
+ while (redir) {
+ if (redir->rd_type == REDIRECT_HEREDOC) {
+ char *p;
+
+ redir->rd_type = REDIRECT_HEREDOC2;
+ /* redir->dup is (ab)used to indicate <<- */
+ p = fetch_till_str(&ctx->as_string, input,
+ redir->rd_filename, redir->rd_dup & HEREDOC_SKIPTABS);
+ if (!p) {
+ syntax_error("unexpected EOF in here document");
+ return 1;
+ }
+ free(redir->rd_filename);
+ redir->rd_filename = p;
+ heredoc_cnt--;
+ }
+ redir = redir->next;
+ }
+ cmd++;
}
+ pi = pi->next;
}
- num = atoi(o->data);
- o_reset(o);
- return num;
+#if 0
+ /* Should be 0. If it isn't, it's a parse error */
+ if (heredoc_cnt)
+ bb_error_msg_and_die("heredoc BUG 2");
+#endif
+ return 0;
}
-#if BB_MMU
-#define parse_stream(pstring, input, end_trigger) \
- parse_stream(input, end_trigger)
-#endif
-static struct pipe *parse_stream(char **pstring,
- struct in_str *input,
- int end_trigger);
-static void parse_and_run_string(const char *s);
#if ENABLE_HUSH_TICK
static FILE *generate_stream_from_string(const char *s)
bb_perror_msg_and_die(BB_MMU ? "fork" : "vfork");
if (pid == 0) { /* child */
-#if ENABLE_HUSH_JOB
- die_sleep = 0; /* do not restore tty pgrp on xfunc death */
-#endif
+ disable_restore_tty_pgrp_on_exit();
/* Process substitution is not considered to be usual
* 'command execution'.
* SUSv3 says ctrl-Z should be ignored, ctrl-C should not.
#if BB_MMU
reset_traps_to_defaults();
parse_and_run_string(s);
- _exit(G.last_return_code);
+ _exit(G.last_exitcode);
#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
* echo OK
*/
- re_execute_shell(s);
+ re_execute_shell(s,
+ G.global_argv[0],
+ G.global_argv + 1);
#endif
}
/* parent */
-#if ENABLE_HUSH_JOB
- die_sleep = -1; /* restore tty pgrp on xfunc death */
-#endif
+ enable_restore_tty_pgrp_on_exit();
clean_up_after_re_execute();
close(channel[1]);
pf = fdopen(channel[0], "r");
debug_printf_parse("parse_group entered\n");
#if ENABLE_HUSH_FUNCTIONS
- if (ch == 'F') { /* function definition? */
- bb_error_msg("aha '%s' is a function, parsing it...", dest->data);
- //command->fname = dest->data;
+ if (ch == '(' && !dest->o_quoted) {
+ if (dest->length)
+ done_word(dest, ctx);
+ if (!command->argv)
+ goto skip; /* (... */
+ if (command->argv[1]) { /* word word ... (... */
+ syntax_error_unexpected_ch('(');
+ return 1;
+ }
+ /* it is "word(..." or "word (..." */
+ do
+ ch = i_getch(input);
+ while (ch == ' ' || ch == '\t');
+ if (ch != ')') {
+ syntax_error_unexpected_ch(ch);
+ return 1;
+ }
+ nommu_addchr(&ctx->as_string, ch);
+ do
+ ch = i_getch(input);
+ while (ch == ' ' || ch == '\t' || ch == '\n');
+ if (ch != '{') {
+ syntax_error_unexpected_ch(ch);
+ return 1;
+ }
+ nommu_addchr(&ctx->as_string, ch);
command->grp_type = GRP_FUNCTION;
-//TODO: review every o_reset() location... do they handle all o_string fields correctly?
- memset(dest, 0, sizeof(*dest));
+ goto skip;
}
#endif
- if (command->argv /* word [word](... */
- || dest->length /* word(... */
- || dest->nonnull /* ""(... */
+ if (command->argv /* word [word]{... */
+ || dest->length /* word{... */
+ || dest->o_quoted /* ""{... */
) {
- syntax(NULL);
+ syntax_error(NULL);
debug_printf_parse("parse_group return 1: "
"syntax error, groups and arglists don't mix\n");
return 1;
}
+
+#if ENABLE_HUSH_FUNCTIONS
+ skip:
+#endif
endch = '}';
if (ch == '(') {
endch = ')';
command->grp_type = GRP_SUBSHELL;
}
+
{
#if !BB_MMU
char *as_string = NULL;
#if !BB_MMU
free(as_string);
#endif
- syntax(NULL);
+ syntax_error(NULL);
debug_printf_parse("parse_group return 1: "
"parse_stream returned %p\n", pipe_list);
return 1;
{
while (1) {
int ch = i_getch(input);
- if (ch == EOF)
- break;
+ if (ch == EOF) {
+ syntax_error_unterm_ch('\'');
+ /*xfunc_die(); - redundant */
+ }
if (ch == '\'')
- break;
+ return;
o_addchr(dest, ch);
}
}
{
while (1) {
int ch = i_getch(input);
+ if (ch == EOF) {
+ syntax_error_unterm_ch('"');
+ /*xfunc_die(); - redundant */
+ }
if (ch == '"')
- break;
+ return;
if (ch == '\\') { /* \x. Copy both chars. */
o_addchr(dest, ch);
ch = i_getch(input);
}
- if (ch == EOF)
- break;
o_addchr(dest, ch);
if (ch == '`') {
add_till_backquote(dest, input);
{
while (1) {
int ch = i_getch(input);
+ if (ch == EOF) {
+ syntax_error_unterm_ch('`');
+ /*xfunc_die(); - redundant */
+ }
if (ch == '`')
- break;
- if (ch == '\\') { /* \x. Copy both chars unless it is \` */
+ return;
+ if (ch == '\\') {
+ /* \x. Copy both chars unless it is \` */
int ch2 = i_getch(input);
+ if (ch2 == EOF) {
+ syntax_error_unterm_ch('`');
+ /*xfunc_die(); - redundant */
+ }
if (ch2 != '`' && ch2 != '$' && ch2 != '\\')
o_addchr(dest, ch);
ch = ch2;
}
- if (ch == EOF)
- break;
o_addchr(dest, ch);
}
}
int count = 0;
while (1) {
int ch = i_getch(input);
- if (ch == EOF)
- break;
+ if (ch == EOF) {
+ syntax_error_unterm_ch(')');
+ /*xfunc_die(); - redundant */
+ }
if (ch == '(')
count++;
if (ch == ')') {
o_addchr(dest, ch);
continue;
}
- if (ch == '\\') { /* \x. Copy verbatim. Important for \(, \) */
+ if (ch == '\\') {
+ /* \x. Copy verbatim. Important for \(, \) */
ch = i_getch(input);
- if (ch == EOF)
- break;
+ if (ch == EOF) {
+ syntax_error_unterm_ch(')');
+ /*xfunc_die(); - redundant */
+ }
o_addchr(dest, ch);
continue;
}
debug_printf_parse("handle_dollar entered: ch='%c'\n", ch);
if (isalpha(ch)) {
ch = i_getch(input);
-#if !BB_MMU
- if (as_string) o_addchr(as_string, ch);
-#endif
+ nommu_addchr(as_string, ch);
make_var:
o_addchr(dest, SPECIAL_VAR_SYMBOL);
while (1) {
if (!isalnum(ch) && ch != '_')
break;
ch = i_getch(input);
-#if !BB_MMU
- if (as_string) o_addchr(as_string, ch);
-#endif
+ nommu_addchr(as_string, ch);
}
o_addchr(dest, SPECIAL_VAR_SYMBOL);
} else if (isdigit(ch)) {
make_one_char_var:
ch = i_getch(input);
-#if !BB_MMU
- if (as_string) o_addchr(as_string, ch);
-#endif
+ nommu_addchr(as_string, ch);
o_addchr(dest, SPECIAL_VAR_SYMBOL);
debug_printf_parse(": '%c'\n", ch);
o_addchr(dest, ch | quote_mask);
o_addchr(dest, SPECIAL_VAR_SYMBOL);
ch = i_getch(input);
-#if !BB_MMU
- if (as_string) o_addchr(as_string, ch);
-#endif
+ nommu_addchr(as_string, ch);
/* XXX maybe someone will try to escape the '}' */
expansion = 0;
first_char = true;
all_digits = false;
while (1) {
ch = i_getch(input);
-#if !BB_MMU
- if (as_string) o_addchr(as_string, ch);
-#endif
+ nommu_addchr(as_string, ch);
if (ch == '}')
break;
break;
}
goto case_default;
-#if 0 /* not implemented yet :( */
case '#': /* remove prefix */
case '%': /* remove suffix */
if (expansion == 0) {
break;
}
goto case_default;
-#endif
case '-': /* default value */
case '=': /* assign default */
case '+': /* alternative */
break;
default:
case_default:
- syntax("unterminated ${name}");
+ syntax_error_unterm_str("${name}");
debug_printf_parse("handle_dollar return 1: unterminated ${name}\n");
return 1;
}
int pos;
# endif
ch = i_getch(input);
-# if !BB_MMU
- if (as_string) o_addchr(as_string, ch);
-# endif
+ nommu_addchr(as_string, ch);
# if ENABLE_SH_MATH_SUPPORT
if (i_peek(input) == '(') {
ch = i_getch(input);
-# if !BB_MMU
- if (as_string) o_addchr(as_string, ch);
-# endif
+ nommu_addchr(as_string, ch);
o_addchr(dest, SPECIAL_VAR_SYMBOL);
o_addchr(dest, /*quote_mask |*/ '+');
# if !BB_MMU
}
# endif
# if ENABLE_HUSH_TICK
- //int pos = dest->length;
o_addchr(dest, SPECIAL_VAR_SYMBOL);
o_addchr(dest, quote_mask | '`');
# if !BB_MMU
o_addchr(as_string, '`');
}
# endif
- //debug_printf_subst("SUBST RES2 '%s'\n", dest->data + pos);
o_addchr(dest, SPECIAL_VAR_SYMBOL);
# endif
break;
#endif
case '_':
ch = i_getch(input);
-#if !BB_MMU
- if (as_string) o_addchr(as_string, ch);
-#endif
+ nommu_addchr(as_string, ch);
ch = i_peek(input);
if (isalnum(ch)) { /* it's $_name or $_123 */
ch = '_';
again:
ch = i_getch(input);
-#if !BB_MMU
- if (as_string && ch != EOF)
- o_addchr(as_string, ch);
-#endif
+ if (ch != EOF)
+ nommu_addchr(as_string, ch);
if (ch == dquote_end) { /* may be only '"' or EOF */
- dest->nonnull = 1;
if (dest->o_assignment == NOT_ASSIGNMENT)
dest->o_escape ^= 1;
debug_printf_parse("parse_stream_dquoted return 0\n");
return 0;
}
+ /* note: can't move it above ch == dquote_end check! */
if (ch == EOF) {
- syntax("unterminated \"");
- debug_printf_parse("parse_stream_dquoted return 1: unterminated \"\n");
- return 1;
+ syntax_error_unterm_ch('"');
+ /*xfunc_die(); - redundant */
}
next = '\0';
if (ch != '\n') {
ch, ch, dest->o_escape);
if (ch == '\\') {
if (next == EOF) {
- syntax("\\<eof>");
- debug_printf_parse("parse_stream_dquoted return 1: \\<eof>\n");
- return 1;
+ syntax_error("\\<eof>");
+ xfunc_die();
}
/* bash:
* "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.
- * If enabled, history expansion will be performed unless
- * an ! appearing in double quotes is escaped using
- * a backslash. The backslash preceding the ! is not removed."
*/
if (strchr("$`\"\\", next) != NULL) {
o_addqchr(dest, i_getch(input));
if (ch == '='
&& (dest->o_assignment == MAYBE_ASSIGNMENT
|| dest->o_assignment == WORD_IS_KEYWORD)
- && is_assignment(dest->data)
+ && is_well_formed_var_name(dest->data, '=')
) {
dest->o_assignment = DEFINITELY_ASSIGNMENT;
}
struct parse_context ctx;
o_string dest = NULL_O_STRING;
int is_in_dquote;
+ int heredoc_cnt;
/* Double-quote state is handled in the state variable is_in_dquote.
* A single-quote triggers a bypass of the main loop until its mate is
*/
debug_printf_parse("parse_stream entered, end_trigger='%c'\n",
end_trigger ? : 'X');
+ debug_enter();
G.ifs = get_local_var_value("IFS");
if (G.ifs == NULL)
/* dest.o_assignment = MAYBE_ASSIGNMENT; - already is */
initialize_context(&ctx);
is_in_dquote = 0;
+ heredoc_cnt = 0;
while (1) {
const char *is_ifs;
const char *is_special;
redir_type redir_style;
if (is_in_dquote) {
+ /* dest.o_quoted = 1; - already is (see below) */
if (parse_stream_dquoted(&ctx.as_string, &dest, input, '"')) {
goto parse_error;
}
ch, ch, dest.o_escape);
if (ch == EOF) {
struct pipe *pi;
+
+ if (heredoc_cnt) {
+ syntax_error_unterm_str("here document");
+ xfunc_die();
+ }
if (done_word(&dest, &ctx)) {
- goto parse_error;
+ xfunc_die();
}
o_free(&dest);
done_pipe(&ctx, PIPE_SEQ);
pi = ctx.list_head;
/* If we got nothing... */
-// TODO: test script consisting of just "&"
+ /* (this makes bare "&" cmd a no-op.
+ * bash says: "syntax error near unexpected token '&'") */
if (pi->num_cmds == 0
IF_HAS_KEYWORDS( && pi->res_word == RES_NONE)
) {
- free_pipe_list(pi, 0);
+ free_pipe_list(pi);
pi = NULL;
}
- debug_printf_parse("parse_stream return %p\n", pi);
#if !BB_MMU
debug_printf_parse("as_string '%s'\n", ctx.as_string.data);
if (pstring)
else
o_free_unsafe(&ctx.as_string);
#endif
+ debug_leave();
+ debug_printf_parse("parse_stream return %p\n", pi);
return pi;
}
-#if !BB_MMU
- o_addchr(&ctx.as_string, ch);
-#endif
+ 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 ((dest.o_assignment == MAYBE_ASSIGNMENT
|| dest.o_assignment == WORD_IS_KEYWORD)
&& ch == '='
- && is_assignment(dest.data)
+ && is_well_formed_var_name(dest.data, '=')
) {
dest.o_assignment = DEFINITELY_ASSIGNMENT;
}
#endif
/* Treat newline as a command separator. */
done_pipe(&ctx, PIPE_SEQ);
+ debug_printf_parse("heredoc_cnt:%d\n", heredoc_cnt);
+ if (heredoc_cnt) {
+ if (fetch_heredocs(heredoc_cnt, &ctx, input)) {
+ goto parse_error;
+ }
+ heredoc_cnt = 0;
+ }
dest.o_assignment = MAYBE_ASSIGNMENT;
ch = ';';
/* note: if (is_ifs) continue;
* will still trigger for us */
}
}
- if (end_trigger && end_trigger == ch) {
+ if (end_trigger && end_trigger == ch
+ && (heredoc_cnt == 0 || end_trigger != ';')
+ ) {
//TODO: disallow "{ cmd }" without semicolon
+ if (heredoc_cnt) {
+ /* This is technically valid:
+ * { cat <<HERE; }; echo Ok
+ * heredoc
+ * heredoc
+ * heredoc
+ * HERE
+ * but we don't support this.
+ * We require heredoc to be in enclosing {}/(),
+ * if any.
+ */
+ syntax_error_unterm_str("here document");
+ goto parse_error;
+ }
if (done_word(&dest, &ctx)) {
goto parse_error;
}
if (!HAS_KEYWORDS
IF_HAS_KEYWORDS(|| (ctx.ctx_res_w == RES_NONE && ctx.old_flag == 0))
) {
- debug_printf_parse("parse_stream return %p: "
- "end_trigger char found\n",
- ctx.list_head);
o_free(&dest);
#if !BB_MMU
debug_printf_parse("as_string '%s'\n", ctx.as_string.data);
else
o_free_unsafe(&ctx.as_string);
#endif
+ debug_leave();
+ debug_printf_parse("parse_stream return %p: "
+ "end_trigger char found\n",
+ ctx.list_head);
return ctx.list_head;
}
}
if (is_ifs)
continue;
- if (dest.o_assignment == MAYBE_ASSIGNMENT) {
- /* ch is a special char and thus this word
- * cannot be an assignment */
- dest.o_assignment = NOT_ASSIGNMENT;
- }
-
next = '\0';
if (ch != '\n') {
next = i_peek(input);
}
+ /* Catch <, > before deciding whether this word is
+ * an assignment. a=1 2>z b=2: b=2 is still assignment */
+ switch (ch) {
+ case '>':
+ redir_fd = redirect_opt_num(&dest);
+ if (done_word(&dest, &ctx)) {
+ goto parse_error;
+ }
+ redir_style = REDIRECT_OVERWRITE;
+ if (next == '>') {
+ redir_style = REDIRECT_APPEND;
+ ch = i_getch(input);
+ nommu_addchr(&ctx.as_string, ch);
+ }
+#if 0
+ else if (next == '(') {
+ syntax_error(">(process) not supported");
+ goto parse_error;
+ }
+#endif
+ if (parse_redirect(&ctx, redir_fd, redir_style, input))
+ goto parse_error;
+ continue; /* back to top of while (1) */
+ case '<':
+ redir_fd = redirect_opt_num(&dest);
+ if (done_word(&dest, &ctx)) {
+ goto parse_error;
+ }
+ redir_style = REDIRECT_INPUT;
+ if (next == '<') {
+ redir_style = REDIRECT_HEREDOC;
+ heredoc_cnt++;
+ debug_printf_parse("++heredoc_cnt=%d\n", heredoc_cnt);
+ ch = i_getch(input);
+ nommu_addchr(&ctx.as_string, ch);
+ } else if (next == '>') {
+ redir_style = REDIRECT_IO;
+ ch = i_getch(input);
+ nommu_addchr(&ctx.as_string, ch);
+ }
+#if 0
+ else if (next == '(') {
+ syntax_error("<(process) not supported");
+ goto parse_error;
+ }
+#endif
+ if (parse_redirect(&ctx, redir_fd, redir_style, input))
+ goto parse_error;
+ continue; /* back to top of while (1) */
+ }
+
+ if (dest.o_assignment == MAYBE_ASSIGNMENT
+ /* check that we are not in word in "a=1 2>word b=1": */
+ && !ctx.pending_redirect
+ ) {
+ /* ch is a special char and thus this word
+ * cannot be an assignment */
+ dest.o_assignment = NOT_ASSIGNMENT;
+ }
+
switch (ch) {
case '#':
if (dest.length == 0) {
i_getch(input);
/* note: we do not add it to &ctx.as_string */
}
-#if !BB_MMU
-//TODO: go back one char?
- o_addchr(&ctx.as_string, '\n');
-#endif
+ nommu_addchr(&ctx.as_string, '\n');
} else {
o_addQchr(&dest, ch);
}
break;
case '\\':
if (next == EOF) {
- syntax("\\<eof>");
- goto parse_error;
+ syntax_error("\\<eof>");
+ xfunc_die();
}
o_addchr(&dest, '\\');
ch = i_getch(input);
+ nommu_addchr(&ctx.as_string, ch);
o_addchr(&dest, ch);
-#if !BB_MMU
- o_addchr(&ctx.as_string, ch);
-#endif
+ /* Example: echo Hello \2>file
+ * we need to know that word 2 is quoted */
+ dest.o_quoted = 1;
break;
case '$':
if (handle_dollar(&ctx.as_string, &dest, input) != 0) {
}
break;
case '\'':
- dest.nonnull = 1;
+ dest.o_quoted = 1;
while (1) {
ch = i_getch(input);
if (ch == EOF) {
- syntax("unterminated '");
- goto parse_error;
+ syntax_error_unterm_ch('\'');
+ /*xfunc_die(); - redundant */
}
-#if !BB_MMU
- o_addchr(&ctx.as_string, ch);
-#endif
+ nommu_addchr(&ctx.as_string, ch);
if (ch == '\'')
break;
if (dest.o_assignment == NOT_ASSIGNMENT)
}
break;
case '"':
- dest.nonnull = 1;
+ dest.o_quoted = 1;
is_in_dquote ^= 1; /* invert */
if (dest.o_assignment == NOT_ASSIGNMENT)
dest.o_escape ^= 1;
break;
#if ENABLE_HUSH_TICK
case '`': {
- //int pos = dest.length;
+#if !BB_MMU
+ int pos;
+#endif
o_addchr(&dest, SPECIAL_VAR_SYMBOL);
o_addchr(&dest, '`');
+#if !BB_MMU
+ pos = dest.length;
+#endif
add_till_backquote(&dest, input);
+#if !BB_MMU
+ o_addstr(&ctx.as_string, dest.data + pos);
+ o_addchr(&ctx.as_string, '`');
+#endif
o_addchr(&dest, SPECIAL_VAR_SYMBOL);
//debug_printf_subst("SUBST RES3 '%s'\n", dest.data + pos);
break;
}
#endif
- case '>':
- redir_fd = redirect_opt_num(&dest);
- if (done_word(&dest, &ctx)) {
- goto parse_error;
- }
- redir_style = REDIRECT_OVERWRITE;
- if (next == '>') {
- redir_style = REDIRECT_APPEND;
- ch = i_getch(input);
-#if !BB_MMU
- o_addchr(&ctx.as_string, ch);
-#endif
- }
-#if 0
- else if (next == '(') {
- syntax(">(process) not supported");
- goto parse_error;
- }
-#endif
- setup_redirect(&ctx, redir_fd, redir_style, input);
- break;
- case '<':
- redir_fd = redirect_opt_num(&dest);
- if (done_word(&dest, &ctx)) {
- goto parse_error;
- }
- redir_style = REDIRECT_INPUT;
- if (next == '<') {
- redir_style = REDIRECT_HEREIS;
- ch = i_getch(input);
-#if !BB_MMU
- o_addchr(&ctx.as_string, ch);
-#endif
- } else if (next == '>') {
- redir_style = REDIRECT_IO;
- ch = i_getch(input);
-#if !BB_MMU
- o_addchr(&ctx.as_string, ch);
-#endif
- }
-#if 0
- else if (next == '(') {
- syntax("<(process) not supported");
- goto parse_error;
- }
-#endif
- setup_redirect(&ctx, redir_fd, redir_style, input);
- break;
case ';':
#if ENABLE_HUSH_CASE
case_semi:
if (ch != ';')
break;
ch = i_getch(input);
-#if !BB_MMU
- o_addchr(&ctx.as_string, ch);
-#endif
+ nommu_addchr(&ctx.as_string, ch);
if (ctx.ctx_res_w == RES_CASEI) {
ctx.ctx_dsemicolon = 1;
ctx.ctx_res_w = RES_MATCH;
}
if (next == '&') {
ch = i_getch(input);
-#if !BB_MMU
- o_addchr(&ctx.as_string, ch);
-#endif
+ nommu_addchr(&ctx.as_string, ch);
done_pipe(&ctx, PIPE_AND);
} else {
done_pipe(&ctx, PIPE_BG);
#endif
if (next == '|') { /* || */
ch = i_getch(input);
-#if !BB_MMU
- o_addchr(&ctx.as_string, ch);
-#endif
+ nommu_addchr(&ctx.as_string, ch);
done_pipe(&ctx, PIPE_OR);
} else {
/* we could pick up a file descriptor choice here
if (ctx.ctx_res_w == RES_MATCH
&& ctx.command->argv == NULL /* not (word|(... */
&& dest.length == 0 /* not word(... */
- && dest.nonnull == 0 /* not ""(... */
+ && dest.o_quoted == 0 /* not ""(... */
) {
continue;
}
-#endif
-#if ENABLE_HUSH_FUNCTIONS
- if (dest.length != 0 /* not just () but word() */
- && dest.nonnull == 0 /* not a"b"c() */
- && ctx.command->argv == NULL /* it's the first word */
-//TODO: "func ( ) {...}" - note spaces - is valid format too in bash
- && i_peek(input) == ')'
- && !match_reserved_word(&dest)
- ) {
- bb_error_msg("seems like a function definition");
- i_getch(input);
-//if !BB_MMU o_addchr(&ctx.as_string...
- do {
-//TODO: do it properly.
- ch = i_getch(input);
- } while (ch == ' ' || ch == '\n');
- if (ch != '{') {
- syntax("was expecting {");
- goto parse_error;
- }
- ch = 'F'; /* magic value */
- }
#endif
case '{':
if (parse_group(&dest, &ctx, input, ch) != 0) {
/* proper use of this character is caught by end_trigger:
* if we see {, we call parse_group(..., end_trigger='}')
* and it will match } earlier (not here). */
- syntax("unexpected } or )");
+ syntax_error_unexpected_ch(ch);
goto parse_error;
default:
if (HUSH_DEBUG)
debug_printf_clean("freeing list %p from ctx %p\n",
pctx->list_head, pctx);
debug_print_tree(pctx->list_head, 0);
- free_pipe_list(pctx->list_head, 0);
+ free_pipe_list(pctx->list_head);
debug_printf_clean("freed list %p\n", pctx->list_head);
#if !BB_MMU
o_free_unsafe(&pctx->as_string);
if (pstring)
*pstring = NULL;
#endif
+ debug_leave();
return ERR_PTR;
}
/* Discard cached input, force prompt */
INIT_G();
if (EXIT_SUCCESS) /* if EXIT_SUCCESS == 0, is already done */
- G.last_return_code = EXIT_SUCCESS;
+ G.last_exitcode = EXIT_SUCCESS;
#if !BB_MMU
G.argv0_for_re_execing = argv[0];
#endif
G.PS2 = "> ";
#endif
+ if (setjmp(die_jmp)) {
+ /* xfunc has failed! die die die */
+ /* no EXIT traps, this is an escape hatch! */
+ G.exiting = 1;
+ hush_exit(xfunc_error_retval);
+ }
+
/* Shell is non-interactive at first. We need to call
* block_signals(0) if we are going to execute "sh <script>",
* "sh -c <cmds>" or login shell's /etc/profile and friends.
while (1) {
opt = getopt(argc, argv, "c:xins"
#if !BB_MMU
- "$:!:?:D:R:V:"
+ "<:$:R:V:"
+# if ENABLE_HUSH_FUNCTIONS
+ "F:"
+# endif
#endif
);
if (opt <= 0)
* operate, so simply do nothing here. */
break;
#if !BB_MMU
+ case '<': /* "big heredoc" support */
+ full_write(STDOUT_FILENO, optarg, strlen(optarg));
+ _exit(0);
case '$':
G.root_pid = bb_strtou(optarg, &optarg, 16);
optarg++;
G.last_bg_pid = bb_strtou(optarg, &optarg, 16);
optarg++;
- G.last_return_code = bb_strtou(optarg, &optarg, 16);
+ G.last_exitcode = bb_strtou(optarg, &optarg, 16);
# if ENABLE_HUSH_LOOPS
optarg++;
G.depth_of_loop = bb_strtou(optarg, &optarg, 16);
case 'V':
set_local_var(xstrdup(optarg), 0, opt == 'R');
break;
+# if ENABLE_HUSH_FUNCTIONS
+ case 'F': {
+ struct function *funcp = new_function(optarg);
+ /* funcp->name is already set to optarg */
+ /* funcp->body is set to NULL. It's a special case. */
+ funcp->body_as_string = argv[optind];
+ optind++;
+ break;
+ }
+# endif
#endif
case 'n':
case 'x':
tcsetpgrp(G_interactive_fd, getpid());
/* -1 is special - makes xfuncs longjmp, not exit
* (we reset die_sleep = 0 whereever we [v]fork) */
- die_sleep = -1;
- if (setjmp(die_jmp)) {
- /* xfunc has failed! die die die */
- hush_exit(xfunc_error_retval);
- }
+ enable_restore_tty_pgrp_on_exit(); /* sets die_sleep = -1 */
} else if (!signal_mask_is_inited) {
block_signals(0); /* 0: called 1st time */
} /* else: block_signals(0) was done before */
free(tmp);
}
#endif
- hush_exit(G.last_return_code);
+ hush_exit(G.last_exitcode);
}
*/
parse_and_run_string(str);
free(str);
- rcode = G.last_return_code;
+ rcode = G.last_exitcode;
}
return rcode;
}
const char *newdir = argv[1];
if (newdir == NULL) {
/* bash does nothing (exitcode 0) if HOME is ""; if it's unset,
- * bash says "bash: cd: HOME not set" and does nothing (exitcode 1)
+ * bash says "bash: cd: HOME not set" and does nothing
+ * (exitcode 1)
*/
newdir = getenv("HOME") ? : "/";
}
static int 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"
+
+ /* note: EXIT trap is run by hush_exit */
if (*++argv == NULL)
- hush_exit(G.last_return_code);
+ hush_exit(G.last_exitcode);
/* mimic bash: exit 123abc == exit 255 + error msg */
xfunc_error_retval = 255;
/* bash: exit -2 == exit 254, no error msg */
static int builtin_export(char **argv)
{
if (*++argv == NULL) {
- // TODO:
- // ash emits: export VAR='VAL'
- // bash: declare -x VAR="VAL"
- // (both also escape as needed (quotes, $, etc))
char **e = environ;
- if (e)
- while (*e)
+ if (e) {
+ while (*e) {
+#if 0
puts(*e++);
+#else
+ /* ash emits: export VAR='VAL'
+ * bash: declare -x VAR="VAL"
+ * we follow ash example */
+ const char *s = *e++;
+ const char *p = strchr(s, '=');
+
+ if (!p) /* wtf? take next variable */
+ continue;
+ /* export var= */
+ printf("export %.*s", (int)(p - s) + 1, s);
+ 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('"');
+ }
+ putchar('\n');
+#endif
+ }
+ fflush(stdout);
+ }
return EXIT_SUCCESS;
}
}
#endif
+#if HUSH_DEBUG
+static int builtin_memleak(char **argv UNUSED_PARAM)
+{
+ void *p;
+ unsigned long l;
+
+ /* Crude attempt to find where "free memory" starts,
+ * sans fragmentation. */
+ p = malloc(240);
+ l = (unsigned long)p;
+ free(p);
+ p = malloc(3400);
+ if (l < (unsigned long)p) l = (unsigned long)p;
+ free(p);
+
+ if (!G.memleak_value)
+ G.memleak_value = l;
+
+ l -= G.memleak_value;
+ if ((long)l < 0)
+ l = 0;
+ l /= 1024;
+ if (l > 127)
+ l = 127;
+
+ /* Exitcode is "how many kilobytes we leaked since 1st call" */
+ return l;
+}
+#endif
+
static int builtin_pwd(char **argv UNUSED_PARAM)
{
puts(set_cwd());
static int builtin_read(char **argv)
{
char *string;
- const char *name = argv[1] ? argv[1] : "REPLY";
-//TODO: check that argv[1] is a valid variable name
+ const char *name = "REPLY";
+
+ if (argv[1]) {
+ name = argv[1];
+ /* bash (3.2.33(1)) bug: "read 0abcd" will execute,
+ * and _after_ that_ it will complain */
+ if (!is_well_formed_var_name(name, '\0')) {
+ /* Mimic bash message */
+ bb_error_msg("read: '%s': not a valid identifier", name);
+ return 1;
+ }
+ }
+
+//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);
++argv;
goto set_argv;
}
-
- if (arg[0] == '+' || arg[0] == '-') {
- for (n = 1; arg[n]; ++n)
- if (set_mode(arg[0], arg[n]))
- goto error;
- continue;
- }
-
- break;
+ if (arg[0] != '+' && arg[0] != '-')
+ break;
+ for (n = 1; arg[n]; ++n)
+ if (set_mode(arg[0], arg[n]))
+ goto error;
} while ((arg = *++argv) != NULL);
/* Now argv[0] is 1st argument */
- /* Only reset global_argv if we didn't process anything */
if (arg == NULL)
return EXIT_SUCCESS;
set_argv:
* set G.global_argv=argv+1, recurse, and restore. */
parse_and_run_file(input);
fclose(input);
- return G.last_return_code;
+ return G.last_exitcode;
}
static int builtin_umask(char **argv)
ret = EXIT_FAILURE;
}
}
-#if ENABLE_HUSH_FUNCTIONS
- else {
- unset_local_func(*argv);
- }
-#endif
+//#if ENABLE_HUSH_FUNCTIONS
+// else {
+// unset_local_func(*argv);
+// }
+//#endif
argv++;
}
return ret;