blob: b6e49db99f9b5a13989359ac11af27966e24dcfe [file] [log] [blame]
/* vi: set sw=4 ts=4: */
/*
* A prototype Bourne shell grammar parser.
* Intended to follow the original Thompson and Ritchie
* "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) 2008,2009 Denys Vlasenko <vda.linux@googlemail.com>
*
* Credits:
* The parser routines proper are all original material, first
* written Dec 2000 and Jan 2001 by Larry Doolittle. The
* execution engine, the builtins, and much of the underlying
* support has been adapted from busybox-0.49pre's lash, which is
* Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
* written by Erik Andersen <andersen@codepoet.org>. That, in turn,
* is based in part on ladsh.c, by Michael K. Johnson and Erik W.
* Troan, which they placed in the public domain. I don't know
* how much of the Johnson/Troan code has survived the repeated
* rewrites.
*
* Other credits:
* 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.
*
* There are two big (and related) architecture differences between
* this parser and the lash parser. One is that this version is
* actually designed from the ground up to understand nearly all
* of the Bourne grammar. The second, consequential change is that
* the parser and input reader have been turned inside out. Now,
* the parser is in control, and asks for input as needed. The old
* way had the input reader in control, and it asked for parsing to
* take place as needed. The new way makes it much easier to properly
* handle the recursion implicit in the various substitutions, especially
* across continuation lines.
*
* POSIX syntax not implemented:
* aliases
* <(list) and >(list) Process Substitution
* Tilde Expansion
*
* Bash stuff (maybe optionally enable?):
* &> and >& redirection of stdout+stderr
* Brace expansion
* reserved words: [[ ]] function select
* substrings ${var:1:5}
*
* TODOs:
* grep for "TODO" and fix (some of them are easy)
* change { and } from special chars to reserved words
* builtins: return, ulimit
* follow IFS rules more precisely, including update semantics
* figure out what to do with backslash-newline
* continuation lines, both explicit and implicit - done?
* SIGHUP handling
* ^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))
*
* Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
*/
#include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */
#include <glob.h>
/* #include <dmalloc.h> */
#if ENABLE_HUSH_CASE
# 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
#if BUILD_AS_NOMMU
# undef BB_MMU
# undef USE_FOR_NOMMU
# undef USE_FOR_MMU
# define BB_MMU 0
# define USE_FOR_NOMMU(...) __VA_ARGS__
# define USE_FOR_MMU(...)
#endif
#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__
#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
#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(...)
#else
# define HAS_KEYWORDS 0
# define IF_HAS_KEYWORDS(...)
# define IF_HAS_NO_KEYWORDS(...) __VA_ARGS__
#endif
/* 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)
/* Finer-grained debug switches */
#define debug_printf_parse(...) do {} while (0)
#define debug_print_tree(a, b) do {} while (0)
#define debug_printf_exec(...) do {} while (0)
#define debug_printf_env(...) do {} while (0)
#define debug_printf_jobs(...) do {} while (0)
#define debug_printf_expand(...) do {} while (0)
#define debug_printf_glob(...) do {} while (0)
#define debug_printf_list(...) do {} while (0)
#define debug_printf_subst(...) do {} while (0)
#define debug_printf_clean(...) do {} while (0)
#define ERR_PTR ((void*)(long)1)
#define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n"
#define SPECIAL_VAR_SYMBOL 3
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 */
static const struct {
int mode;
signed char default_fd;
char descrip[3];
} redir_table[] = {
{ 0, 0, "??" },
{ O_RDONLY, 0, "<" },
{ O_CREAT|O_TRUNC|O_WRONLY, 1, ">" },
{ O_CREAT|O_APPEND|O_WRONLY, 1, ">>" },
{ O_RDONLY, 0, "<<" },
{ O_CREAT|O_RDWR, 1, "<>" },
/* Should not be needed. Bogus default_fd helps in debugging */
/* { O_RDONLY, 77, "<<" }, */
};
typedef enum reserved_style {
RES_NONE = 0,
#if ENABLE_HUSH_IF
RES_IF ,
RES_THEN ,
RES_ELIF ,
RES_ELSE ,
RES_FI ,
#endif
#if ENABLE_HUSH_LOOPS
RES_FOR ,
RES_WHILE ,
RES_UNTIL ,
RES_DO ,
RES_DONE ,
#endif
#if ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE
RES_IN ,
#endif
#if ENABLE_HUSH_CASE
RES_CASE ,
/* two pseudo-keywords support contrived "case" syntax: */
RES_MATCH , /* "word)" */
RES_CASEI , /* "this command is inside CASE" */
RES_ESAC ,
#endif
RES_XXXX ,
RES_SNTX
} reserved_style;
typedef struct o_string {
char *data;
int length; /* position where data is appended */
int maxlen;
/* Protect newly added chars against globbing
* (by prepending \ to *, ?, [, \) */
smallint o_escape;
smallint o_glob;
/* 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;
enum {
MAYBE_ASSIGNMENT = 0,
DEFINITELY_ASSIGNMENT = 1,
NOT_ASSIGNMENT = 2,
WORD_IS_KEYWORD = 3, /* not assigment, but next word may be: "if v=xyz cmd;" */
};
/* Used for initialization: o_string foo = NULL_O_STRING; */
#define NULL_O_STRING { NULL }
/* I can almost use ordinary FILE*. Is open_memstream() universally
* available? Where is it documented? */
typedef struct in_str {
const char *p;
/* eof_flag=1: last char in ->p is really an EOF */
char eof_flag; /* meaningless if ->p == NULL */
char peek_buf[2];
#if ENABLE_HUSH_INTERACTIVE
smallint promptme;
smallint promptmode; /* 0: PS1, 1: PS2 */
#endif
FILE *file;
int (*get) (struct in_str *);
int (*peek) (struct in_str *);
} in_str;
#define i_getch(input) ((input)->get(input))
#define i_peek(input) ((input)->peek(input))
struct redir_struct {
struct redir_struct *next;
char *rd_filename; /* filename */
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 */
/* 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.
*/
struct redir_struct *redirects; /* I/O redirections */
};
struct pipe {
struct pipe *next;
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
int jobid; /* job number */
pid_t pgrp; /* process group ID for the job */
char *cmdtext; /* name of job */
#endif
struct command *cmds; /* array of commands in pipe */
smallint followup; /* PIPE_BG, PIPE_SEQ, PIPE_OR, PIPE_AND */
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 {
/* linked list of pipes */
struct pipe *list_head;
/* last pipe (being constructed right now) */
struct pipe *pipe;
/* last command in pipe (being constructed right now) */
struct command *command;
/* last redirect in command->redirects list */
struct redir_struct *pending_redirect;
#if !BB_MMU
o_string as_string;
#endif
#if HAS_KEYWORDS
smallint ctx_res_w;
smallint ctx_inverted; /* "! cmd | cmd" */
#if ENABLE_HUSH_CASE
smallint ctx_dsemicolon; /* ";;" seen */
#endif
/* bitmask of FLAG_xxx, for figuring out valid reserved words */
int old_flag;
/* group we are enclosed in:
* example: "if pipe1; pipe2; then pipe3; fi"
* when we see "if" or "then", we malloc and copy current context,
* and make ->stack point to it. then we parse pipeN.
* when closing "then" / fi" / whatever is found,
* we move list_head into ->stack->command->group,
* copy ->stack into current context, and delete ->stack.
* (parsing of { list } and ( list ) doesn't use this method)
*/
struct parse_context *stack;
#endif
};
/* On program start, environ points to initial environment.
* putenv adds new pointers into it, unsetenv removes them.
* Neither of these (de)allocates the strings.
* setenv allocates new strings in malloc space and does putenv,
* and thus setenv is unusable (leaky) for shell's purposes */
#define setenv(...) setenv_is_leaky_dont_use()
struct variable {
struct variable *next;
char *varstr; /* points to "name=" portion */
int max_len; /* if > 0, name is part of initial env; else name is malloced */
smallint flg_export; /* putenv should be done on this var */
smallint flg_read_only;
};
enum {
BC_BREAK = 1,
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) */
struct globals {
#if ENABLE_HUSH_INTERACTIVE
/* 'interactive_fd' is a fd# open to ctty, if we have one
* _AND_ if we decided to act interactively */
int interactive_fd;
const char *PS1;
const char *PS2;
#define G_interactive_fd (G.interactive_fd)
#else
#define G_interactive_fd 0
#endif
#if ENABLE_FEATURE_EDITING
line_input_t *line_input_state;
#endif
pid_t root_pid;
pid_t last_bg_pid;
#if ENABLE_HUSH_JOB
int run_list_level;
pid_t saved_tty_pgrp;
int last_jobid;
struct pipe *job_list;
struct pipe *toplevel_list;
//// smallint ctrl_z_flag;
#endif
smallint flag_SIGINT;
#if ENABLE_HUSH_LOOPS
smallint flag_break_continue;
#endif
smallint fake_mode;
smallint exiting; /* used to prevent EXIT trap recursion */
/* These four support $?, $#, and $1 */
smalluint last_exitcode;
/* are global_argv and global_argv[1..n] malloced? (note: not [0]) */
smalluint global_args_malloced;
/* how many non-NULL argv's we have. NB: $# + 1 */
int global_argc;
char **global_argv;
#if !BB_MMU
char *argv0_for_re_execing;
char **argv_from_re_execing;
#endif
#if ENABLE_HUSH_LOOPS
unsigned depth_break_continue;
unsigned depth_of_loop;
#endif
const char *ifs;
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;
/* which signals have non-DFL handler (even with no traps set)? */
unsigned non_DFL_mask;
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;
#endif
#if ENABLE_HUSH_JOB
sigjmp_buf toplevel_jb;
#endif
};
#define G (*ptr_to_globals)
/* Not #defining name to G.name - this quickly gets unwieldy
* (too many defines). Also, I actually prefer to see when a variable
* is global, thus "G." prefix is a useful hint */
#define INIT_G() do { \
SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
} while (0)
/* Function prototypes for builtins */
static int builtin_cd(char **argv);
static int builtin_echo(char **argv);
static int builtin_eval(char **argv);
static int builtin_exec(char **argv);
static int builtin_exit(char **argv);
static int builtin_export(char **argv);
#if ENABLE_HUSH_JOB
static int builtin_fg_bg(char **argv);
static int builtin_jobs(char **argv);
#endif
#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_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);
#if ENABLE_HUSH_LOOPS
static int builtin_break(char **argv);
static int builtin_continue(char **argv);
#endif
/* Table of built-in functions. They can be forked or not, depending on
* context: within pipes, they fork. As simple commands, they do not.
* When used in non-forking context, they can change global variables
* in the parent shell process. If forked, of course they cannot.
* For example, 'unset foo | whatever' will parse and run, but foo will
* still be set at the end. */
struct built_in_command {
const char *cmd;
int (*function)(char **argv);
#if ENABLE_HUSH_HELP
const char *descr;
#define BLTIN(cmd, func, help) { cmd, func, help }
#else
#define BLTIN(cmd, func, help) { cmd, func }
#endif
};
/* For now, echo and test are unconditionally enabled.
* Maybe make it configurable? */
static const struct built_in_command bltins[] = {
BLTIN("." , builtin_source , "Run commands in a file"),
BLTIN(":" , builtin_true , "No-op"),
BLTIN("[" , builtin_test , "Test condition"),
#if ENABLE_HUSH_JOB
BLTIN("bg" , builtin_fg_bg , "Resume a job in the background"),
#endif
#if ENABLE_HUSH_LOOPS
BLTIN("break" , builtin_break , "Exit from a loop"),
#endif
BLTIN("cd" , builtin_cd , "Change directory"),
#if ENABLE_HUSH_LOOPS
BLTIN("continue", builtin_continue, "Start new loop iteration"),
#endif
BLTIN("echo" , builtin_echo , "Write to stdout"),
BLTIN("eval" , builtin_eval , "Construct and run shell command"),
BLTIN("exec" , builtin_exec , "Execute command, don't return to shell"),
BLTIN("exit" , builtin_exit , "Exit"),
BLTIN("export" , builtin_export , "Set environment variable"),
#if ENABLE_HUSH_JOB
BLTIN("fg" , builtin_fg_bg , "Bring job into the foreground"),
#endif
#if ENABLE_HUSH_HELP
BLTIN("help" , builtin_help , "List shell built-in commands"),
#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"),
// BLTIN("return" , builtin_return , "Return from a function"),
BLTIN("set" , builtin_set , "Set/unset shell local variables"),
BLTIN("shift" , builtin_shift , "Shift positional parameters"),
BLTIN("test" , builtin_test , "Test condition"),
BLTIN("trap" , builtin_trap , "Trap signals"),
// BLTIN("ulimit" , builtin_return , "Control resource limits"),
BLTIN("umask" , builtin_umask , "Set file creation mask"),
BLTIN("unset" , builtin_unset , "Unset environment variable"),
BLTIN("wait" , builtin_wait , "Wait for process"),
};
/* 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
#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
# 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
#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)
{
indent();
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.
*/
#if LEAK_HUNTING
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
/* 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) {
count2++;
v++;
}
v = xrealloc(strings, (count1 + count2 + 1) * sizeof(char*));
v[count1 + count2] = NULL;
i = count2;
while (--i >= 0)
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)
{
char *v[2];
v[0] = 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)
{
if (!strings)
return;
while (*strings) {
debug_printf_env("putenv '%s'\n", *strings);
putenv(*strings++);
}
}
static char **putenv_all_and_save_old(char **strings)
{
char **old = NULL;
char **s = strings;
if (!strings)
return old;
while (*strings) {
char *v, *eq;
eq = strchr(*strings, '=');
if (eq) {
*eq = '\0';
v = getenv(*strings);
*eq = '=';
if (v) {
/* v points to VAL in VAR=VAL, go back to VAR */
v -= (eq - *strings) + 1;
old = add_string_to_strings(old, v);
}
}
strings++;
}
putenv_all(s);
return old;
}
static void free_strings_and_unsetenv(char **strings, int unset)
{
char **v;
if (!strings)
return;
v = strings;
while (*v) {
if (unset) {
debug_printf_env("unsetenv '%s'\n", *v);
bb_unsetenv(*v);
}
free(*v++);
}
free(strings);
}
static void free_strings(char **strings)
{
free_strings_and_unsetenv(strings, 0);
}
/* Basic theory of signal handling in shell
* ========================================
* This does not describe what hush does, rather, it is current understanding
* what it _should_ do. If it doesn't, it's a bug.
* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#trap
*
* Signals are handled only after each pipe ("cmd | cmd | cmd" thing)
* is finished or backgrounded. It is the same in interactive and
* non-interactive shells, and is the same regardless of whether
* a user trap handler is installed or a shell special one is in effect.
* ^C or ^Z from keyboard seem to execute "at once" because it usually
* backgrounds (i.e. stops) or kills all members of currently running
* pipe.
*
* Wait builtin in interruptible by signals for which user trap is set
* or by SIGINT in interactive shell.
*
* Trap handlers will execute even within trap handlers. (right?)
*
* 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.
*
* Commands run in command substitution ("`cmd`")
* have SIGTTIN, SIGTTOU, SIGTSTP set to SIG_IGN.
*
* Ordinary commands have signals set to SIG_IGN/DFL set as inherited
* by the shell from its parent.
*
* Siganls which differ from SIG_DFL action
* (note: child (i.e., [v]forked) shell is not an interactive shell):
*
* SIGQUIT: ignore
* SIGTERM (interactive): ignore
* SIGHUP (interactive):
* send SIGCONT to stopped jobs, send SIGHUP to all jobs and exit
* SIGTTIN, SIGTTOU, SIGTSTP (if job control is on): ignore
* (note that ^Z is handled not by trapping SIGTSTP, but by seeing
* that all pipe members are stopped) (right?)
* SIGINT (interactive): wait for last pipe, ignore the rest
* of the command line, show prompt. NB: ^C does not send SIGINT
* to interactive shell while shell is waiting for a pipe,
* since shell is bg'ed (is not in foreground process group).
* (check/expand this)
* Example 1: this waits 5 sec, but does not execute ls:
* "echo $$; sleep 5; ls -l" + "kill -INT <pid>"
* Example 2: this does not wait and does not execute ls:
* "echo $$; sleep 5 & wait; ls -l" + "kill -INT <pid>"
* Example 3: this does not wait 5 sec, but executes ls:
* "sleep 5; ls -l" + press ^C
*
* (What happens to signals which are IGN on shell start?)
* (What happens with signal mask on shell start?)
*
* Implementation in hush
* ======================
* We use in-kernel pending signal mask to determine which signals were sent.
* We block all signals which we don't want to take action immediately,
* i.e. we block all signals which need to have special handling as described
* above, and all signals which have traps set.
* After each pipe execution, we extract any pending signals via sigtimedwait()
* and act on them.
*
* unsigned non_DFL_mask: a mask of such "special" signals
* sigset_t blocked_set: current blocked signal set
*
* "trap - SIGxxx":
* clear bit in blocked_set unless it is also in non_DFL_mask
* "trap 'cmd' SIGxxx":
* set bit in blocked_set (even if 'cmd' is '')
* after [v]fork, if we plan to be a shell:
* nothing for {} child shell (say, "true | { true; true; } | true")
* unset all traps if () shell.
* 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.
*
* Note: as a result, we do not use signal handlers much. The only uses
* are to count SIGCHLDs [disabled - bug somewhere, + bloat]
* and to restore tty pgrp on signal-induced exit.
*/
//static void SIGCHLD_handler(int sig UNUSED_PARAM)
//{
// G.count_SIGCHLD++;
//}
static int check_and_run_traps(int sig)
{
static const struct timespec zero_timespec = { 0, 0 };
smalluint save_rcode;
int last_sig = 0;
if (sig)
goto jump_in;
while (1) {
sig = sigtimedwait(&G.blocked_set, NULL, &zero_timespec);
if (sig <= 0)
break;
jump_in:
last_sig = sig;
if (G.traps && G.traps[sig]) {
if (G.traps[sig][0]) {
/* We have user-defined handler */
char *argv[] = { NULL, xstrdup(G.traps[sig]), NULL };
save_rcode = G.last_exitcode;
builtin_eval(argv);
free(argv[1]);
G.last_exitcode = save_rcode;
} /* else: "" trap, ignoring signal */
continue;
}
/* not a trap: special action */
switch (sig) {
// case SIGCHLD:
// G.count_SIGCHLD++;
// break;
case SIGINT:
bb_putchar('\n');
G.flag_SIGINT = 1;
break;
//TODO
// case SIGHUP: ...
// break;
default: /* ignored: */
/* SIGTERM, SIGQUIT, SIGTTIN, SIGTTOU, SIGTSTP */
break;
}
}
return last_sig;
}
#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)
* or called directly with -EXITCODE.
* We also call it if xfunc is exiting. */
static void sigexit(int sig) NORETURN;
static void sigexit(int sig)
{
/* Disable all signals: job control, SIGPIPE, etc. */
sigprocmask_allsigs(SIG_BLOCK);
/* Careful: we can end up here after [v]fork. Do not restore
* tty pgrp then, only top-level shell process does that */
if (G_interactive_fd && getpid() == G.root_pid)
tcsetpgrp(G_interactive_fd, G.saved_tty_pgrp);
/* Not a signal, just exit */
if (sig <= 0)
_exit(- sig);
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.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]);
}
#if ENABLE_HUSH_JOB
fflush(NULL); /* flush all streams */
sigexit(- (exitcode & 0xff));
#else
exit(exitcode);
#endif
}
static const char *set_cwd(void)
{
/* xrealloc_getcwd_or_warn(arg) calls free(arg),
* we must not try to free(bb_msg_unknown) */
if (G.cwd == bb_msg_unknown)
G.cwd = NULL;
G.cwd = xrealloc_getcwd_or_warn((char *)G.cwd);
if (!G.cwd)
G.cwd = bb_msg_unknown;
return G.cwd;
}
/* Get/check local shell variables */
static struct variable *get_local_var(const char *name)
{
struct variable *cur;
int len;
if (!name)
return NULL;
len = strlen(name);
for (cur = G.top_var; cur; cur = cur->next) {
if (strncmp(cur->varstr, name, len) == 0 && cur->varstr[len] == '=')
return cur;
}
return NULL;
}
static const char *get_local_var_value(const char *src)
{
struct variable *var = get_local_var(src);
if (var)
return strchr(var->varstr, '=') + 1;
return NULL;
}
/* str holds "NAME=VAL" and is expected to be malloced.
* We take ownership of it.
* flg_export:
* 0: do not export
* 1: export
* -1: if NAME is set, leave export status alone
* if NAME is not set, do not export
* flg_read_only is set only when we handle -R var=val
*/
#if BB_MMU
#define set_local_var(str, flg_export, flg_read_only) \
set_local_var(str, flg_export)
#endif
static int set_local_var(char *str, int flg_export, int flg_read_only)
{
struct variable *cur;
char *value;
int name_len;
value = strchr(str, '=');
if (!value) { /* not expected to ever happen? */
free(str);
return -1;
}
name_len = value - str + 1; /* including '=' */
cur = G.top_var; /* cannot be NULL (we have HUSH_VERSION and it's RO) */
while (1) {
if (strncmp(cur->varstr, str, name_len) != 0) {
if (!cur->next) {
/* Bail out. Note that now cur points
* to last var in linked list */
break;
}
cur = cur->next;
continue;
}
/* We found an existing var with this name */
*value = '\0';
if (cur->flg_read_only) {
#if !BB_MMU
if (!flg_read_only)
#endif
bb_error_msg("%s: readonly variable", str);
free(str);
return -1;
}
debug_printf_env("%s: unsetenv '%s'\n", __func__, str);
unsetenv(str); /* just in case */
*value = '=';
if (strcmp(cur->varstr, str) == 0) {
free_and_exp:
free(str);
goto exp;
}
if (cur->max_len >= strlen(str)) {
/* This one is from startup env, reuse space */
strcpy(cur->varstr, str);
goto free_and_exp;
}
/* max_len == 0 signifies "malloced" var, which we can
* (and has to) free */
if (!cur->max_len)
free(cur->varstr);
cur->max_len = 0;
goto set_str_and_exp;
}
/* Not found - create next variable struct */
cur->next = xzalloc(sizeof(*cur));
cur = cur->next;
set_str_and_exp:
cur->varstr = str;
#if !BB_MMU
cur->flg_read_only = flg_read_only;
#endif
exp:
if (flg_export == 1)
cur->flg_export = 1;
if (cur->flg_export) {
debug_printf_env("%s: putenv '%s'\n", __func__, cur->varstr);
return putenv(cur->varstr);
}
return 0;
}
static int unset_local_var(const char *name)
{
struct variable *cur;
struct variable *prev = prev; /* for gcc */
int name_len;
if (!name)
return EXIT_SUCCESS;
name_len = strlen(name);
cur = G.top_var;
while (cur) {
if (strncmp(cur->varstr, name, name_len) == 0 && cur->varstr[name_len] == '=') {
if (cur->flg_read_only) {
bb_error_msg("%s: readonly variable", name);
return EXIT_FAILURE;
}
/* prev is ok to use here because 1st variable, HUSH_VERSION,
* is ro, and we cannot reach this code on the 1st pass */
prev->next = cur->next;
debug_printf_env("%s: unsetenv '%s'\n", __func__, cur->varstr);
bb_unsetenv(cur->varstr);
if (!cur->max_len)
free(cur->varstr);
free(cur);
return EXIT_SUCCESS;
}
prev = cur;
cur = cur->next;
}
return EXIT_SUCCESS;
}
#if ENABLE_SH_MATH_SUPPORT
#define is_name(c) ((c) == '_' || isalpha((unsigned char)(c)))
#define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c)))
static char *endofname(const char *name)
{
char *p;
p = (char *) name;
if (!is_name(*p))
return p;
while (*++p) {
if (!is_in_name(*p))
break;
}
return p;
}
static void arith_set_local_var(const char *name, const char *val, int flags)
{
/* arith code doesnt malloc space, so do it for it */
char *var = xasprintf("%s=%s", name, val);
set_local_var(var, flags, 0);
}
#endif
/*
* in_str support
*/
static int static_get(struct in_str *i)
{
int ch = *i->p++;
if (ch != '\0')
return ch;
i->p--;
return EOF;
}
static int static_peek(struct in_str *i)
{
return *i->p;
}
#if ENABLE_HUSH_INTERACTIVE
static void cmdedit_set_initial_prompt(void)
{
if (ENABLE_FEATURE_EDITING_FANCY_PROMPT) {
G.PS1 = getenv("PS1");
if (G.PS1 == NULL)
G.PS1 = "\\w \\$ ";
} else
G.PS1 = NULL;
}
static const char* setup_prompt_string(int promptmode)
{
const char *prompt_str;
debug_printf("setup_prompt_string %d ", promptmode);
if (!ENABLE_FEATURE_EDITING_FANCY_PROMPT) {
/* Set up the prompt */
if (promptmode == 0) { /* PS1 */
free((char*)G.PS1);
G.PS1 = xasprintf("%s %c ", G.cwd, (geteuid() != 0) ? '$' : '#');
prompt_str = G.PS1;
} else
prompt_str = G.PS2;
} else
prompt_str = (promptmode == 0) ? G.PS1 : G.PS2;
debug_printf("result '%s'\n", prompt_str);
return prompt_str;
}
static void get_user_input(struct in_str *i)
{
int r;
const char *prompt_str;
prompt_str = setup_prompt_string(i->promptmode);
#if ENABLE_FEATURE_EDITING
/* Enable command line editing only while a command line
* is actually being read */
do {
G.flag_SIGINT = 0;
/* buglet: SIGINT will not make new prompt to appear _at once_,
* only after <Enter>. (^C will work) */
r = read_line_input(prompt_str, G.user_input_buf, BUFSIZ-1, G.line_input_state);
/* catch *SIGINT* etc (^C is handled by read_line_input) */
check_and_run_traps(0);
} while (r == 0 || G.flag_SIGINT); /* repeat if ^C or SIGINT */
i->eof_flag = (r < 0);
if (i->eof_flag) { /* EOF/error detected */
G.user_input_buf[0] = EOF; /* yes, it will be truncated, it's ok */
G.user_input_buf[1] = '\0';
}
#else
do {
G.flag_SIGINT = 0;
fputs(prompt_str, stdout);
fflush(stdout);
G.user_input_buf[0] = r = fgetc(i->file);
/*G.user_input_buf[1] = '\0'; - already is and never changed */
//do we need check_and_run_traps(0)? (maybe only if stdin)
} while (G.flag_SIGINT);
i->eof_flag = (r == EOF);
#endif
i->p = G.user_input_buf;
}
#endif /* INTERACTIVE */
/* This is the magic location that prints prompts
* and gets data back from the user */
static int file_get(struct in_str *i)
{
int ch;
/* If there is data waiting, eat it up */
if (i->p && *i->p) {
#if ENABLE_HUSH_INTERACTIVE
take_cached:
#endif
ch = *i->p++;
if (i->eof_flag && !*i->p)
ch = EOF;
/* note: ch is never NUL */
} else {
/* need to double check i->file because we might be doing something
* more complicated by now, like sourcing or substituting. */
#if ENABLE_HUSH_INTERACTIVE
if (G_interactive_fd && i->promptme && i->file == stdin) {
do {
get_user_input(i);
} while (!*i->p); /* need non-empty line */
i->promptmode = 1; /* PS2 */
i->promptme = 0;
goto take_cached;
}
#endif
do ch = fgetc(i->file); while (ch == '\0');
}
debug_printf("file_get: got '%c' %d\n", ch, ch);
#if ENABLE_HUSH_INTERACTIVE
if (ch == '\n')
i->promptme = 1;
#endif
return ch;
}
/* All callers guarantee this routine will never
* be used right after a newline, so prompting is not needed.
*/
static int file_peek(struct in_str *i)
{
int ch;
if (i->p && *i->p) {
if (i->eof_flag && !i->p[1])
return EOF;
return *i->p;
/* note: ch is never NUL */
}
do ch = fgetc(i->file); while (ch == '\0');
i->eof_flag = (ch == EOF);
i->peek_buf[0] = ch;
i->peek_buf[1] = '\0';
i->p = i->peek_buf;
debug_printf("file_peek: got '%c' %d\n", ch, ch);
return ch;
}
static void setup_file_in_str(struct in_str *i, FILE *f)
{
i->peek = file_peek;
i->get = file_get;
#if ENABLE_HUSH_INTERACTIVE
i->promptme = 1;
i->promptmode = 0; /* PS1 */
#endif
i->file = f;
i->p = NULL;
}
static void setup_string_in_str(struct in_str *i, const char *s)
{
i->peek = static_peek;
i->get = static_get;
#if ENABLE_HUSH_INTERACTIVE
i->promptme = 1;
i->promptmode = 0; /* PS1 */
#endif
i->p = s;
i->eof_flag = 0;
}
/*
* o_string support
*/
#define B_CHUNK (32 * sizeof(char*))
static void o_reset_to_empty_unquoted(o_string *o)
{
o->length = 0;
o->o_quoted = 0;
if (o->data)
o->data[0] = '\0';
}
static void o_free(o_string *o)
{
free(o->data);
memset(o, 0, sizeof(*o));
}
static ALWAYS_INLINE void o_free_unsafe(o_string *o)
{
free(o->data);
}
static void o_grow_by(o_string *o, int len)
{
if (o->length + len > o->maxlen) {
o->maxlen += (2*len > B_CHUNK ? 2*len : B_CHUNK);
o->data = xrealloc(o->data, 1 + o->maxlen);
}
}
static void o_addchr(o_string *o, int ch)
{
debug_printf("o_addchr: '%c' o->length=%d o=%p\n", ch, o->length, o);
o_grow_by(o, 1);
o->data[o->length] = ch;
o->length++;
o->data[o->length] = '\0';
}
static void o_addblock(o_string *o, const char *str, int len)
{
o_grow_by(o, len);
memcpy(&o->data[o->length], str, len);
o->length += len;
o->data[o->length] = '\0';
}
#if !BB_MMU
static void o_addstr(o_string *o, const char *str)
{
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)
{
o_addblock(o, str, strlen(str) + 1);
}
static void o_addblock_duplicate_backslash(o_string *o, const char *str, int len)
{
while (len) {
o_addchr(o, *str);
if (*str++ == '\\'
&& (*str != '*' && *str != '?' && *str != '[')
) {
o_addchr(o, '\\');
}
len--;
}
}
/* My analysis of quoting semantics tells me that state information
* is associated with a destination, not a source.
*/
static void o_addqchr(o_string *o, int ch)
{
int sz = 1;
char *found = strchr("*?[\\", ch);
if (found)
sz++;
o_grow_by(o, sz);
if (found) {
o->data[o->length] = '\\';
o->length++;
}
o->data[o->length] = ch;
o->length++;
o->data[o->length] = '\0';
}
static void o_addQchr(o_string *o, int ch)
{
int sz = 1;
if (o->o_escape && strchr("*?[\\", ch)) {
sz++;
o->data[o->length] = '\\';
o->length++;
}
o_grow_by(o, sz);
o->data[o->length] = ch;
o->length++;
o->data[o->length] = '\0';
}
static void o_addQstr(o_string *o, const char *str, int len)
{
if (!o->o_escape) {
o_addblock(o, str, len);
return;
}
while (len) {
char ch;
int sz;
int ordinary_cnt = strcspn(str, "*?[\\");
if (ordinary_cnt > len) /* paranoia */
ordinary_cnt = len;
o_addblock(o, str, ordinary_cnt);
if (ordinary_cnt == len)
return;
str += ordinary_cnt;
len -= ordinary_cnt + 1; /* we are processing + 1 char below */
ch = *str++;
sz = 1;
if (ch) { /* it is necessarily one of "*?[\\" */
sz++;
o->data[o->length] = '\\';
o->length++;
}
o_grow_by(o, sz);
o->data[o->length] = ch;
o->length++;
o->data[o->length] = '\0';
}
}
/* A special kind of o_string for $VAR and `cmd` expansion.
* It contains char* list[] at the beginning, which is grown in 16 element
* increments. Actual string data starts at the next multiple of 16 * (char*).
* list[i] contains an INDEX (int!) into this string data.
* It means that if list[] needs to grow, data needs to be moved higher up
* but list[i]'s need not be modified.
* NB: remembering how many list[i]'s you have there is crucial.
* o_finalize_list() operation post-processes this structure - calculates
* and stores actual char* ptrs in list[]. Oh, it NULL terminates it as well.
*/
#if DEBUG_EXPAND || DEBUG_GLOB
static void debug_print_list(const char *prefix, o_string *o, int n)
{
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);
i++;
}
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));
}
}
#else
#define debug_print_list(prefix, o, n) ((void)0)
#endif
/* n = o_save_ptr_helper(str, n) "starts new string" by storing an index value
* in list[n] so that it points past last stored byte so far.
* It returns n+1. */
static int o_save_ptr_helper(o_string *o, int n)
{
char **list = (char**)o->data;
int string_start;
int string_len;
if (!o->has_empty_slot) {
string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
string_len = o->length - string_start;
if (!(n & 0xf)) { /* 0, 0x10, 0x20...? */
debug_printf_list("list[%d]=%d string_start=%d (growing)\n", n, string_len, string_start);
/* list[n] points to string_start, make space for 16 more pointers */
o->maxlen += 0x10 * sizeof(list[0]);
o->data = xrealloc(o->data, o->maxlen + 1);
list = (char**)o->data;
memmove(list + n + 0x10, list + n, string_len);
o->length += 0x10 * sizeof(list[0]);
} else {
debug_printf_list("list[%d]=%d string_start=%d\n",
n, string_len, string_start);
}
} else {
/* We have empty slot at list[n], reuse without growth */
string_start = ((n+1 + 0xf) & ~0xf) * sizeof(list[0]); /* NB: n+1! */
string_len = o->length - string_start;
debug_printf_list("list[%d]=%d string_start=%d (empty slot)\n",
n, string_len, string_start);
o->has_empty_slot = 0;
}
list[n] = (char*)(ptrdiff_t)string_len;
return n + 1;
}
/* "What was our last o_save_ptr'ed position (byte offset relative o->data)?" */
static int o_get_last_ptr(o_string *o, int n)
{
char **list = (char**)o->data;
int string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
return ((int)(ptrdiff_t)list[n-1]) + string_start;
}
/* o_glob performs globbing on last list[], saving each result
* as a new list[]. */
static int o_glob(o_string *o, int n)
{
glob_t globdata;
int gr;
char *pattern;
debug_printf_glob("start o_glob: n:%d o->data:%p\n", n, o->data);
if (!o->data)
return o_save_ptr_helper(o, n);
pattern = o->data + o_get_last_ptr(o, n);
debug_printf_glob("glob pattern '%s'\n", pattern);
if (!glob_needed(pattern)) {
literal:
o->length = unbackslash(pattern) - o->data;
debug_printf_glob("glob pattern '%s' is literal\n", pattern);
return o_save_ptr_helper(o, n);
}
memset(&globdata, 0, sizeof(globdata));
gr = glob(pattern, 0, NULL, &globdata);
debug_printf_glob("glob('%s'):%d\n", pattern, gr);
if (gr == GLOB_NOSPACE)
bb_error_msg_and_die("out of memory during glob");
if (gr == GLOB_NOMATCH) {
globfree(&globdata);
goto literal;
}
if (gr != 0) { /* GLOB_ABORTED ? */
//TODO: testcase for bad glob pattern behavior
bb_error_msg("glob(3) error %d on '%s'", gr, pattern);
}
if (globdata.gl_pathv && globdata.gl_pathv[0]) {
char **argv = globdata.gl_pathv;
o->length = pattern - o->data; /* "forget" pattern */
while (1) {
o_addstr_with_NUL(o, *argv);
n = o_save_ptr_helper(o, n);
argv++;
if (!*argv)
break;
}
}
globfree(&globdata);
if (DEBUG_GLOB)
debug_print_list("o_glob returning", o, n);
return n;
}
/* If o->o_glob == 1, glob the string so far remembered.
* Otherwise, just finish current list[] and start new */
static int o_save_ptr(o_string *o, int n)
{
if (o->o_glob) { /* if globbing is requested */
/* If o->has_empty_slot, list[n] was already globbed
* (if it was requested back then when it was filled)
* so don't do that again! */
if (!o->has_empty_slot)
return o_glob(o, n); /* o_save_ptr_helper is inside */
}
return o_save_ptr_helper(o, n);
}
/* "Please convert list[n] to real char* ptrs, and NULL terminate it." */
static char **o_finalize_list(o_string *o, int n)
{
char **list;
int string_start;
n = o_save_ptr(o, n); /* force growth for list[n] if necessary */
if (DEBUG_EXPAND)
debug_print_list("finalized", o, n);
debug_printf_expand("finalized n:%d\n", n);
list = (char**)o->data;
string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
list[--n] = NULL;
while (n) {
n--;
list[n] = o->data + (int)(ptrdiff_t)list[n] + string_start;
}
return list;
}
/* Expansion can recurse */
#if ENABLE_HUSH_TICK
static int process_command_subs(o_string *dest, const char *s);
#endif
static char *expand_string_to_string(const char *str);
#if BB_MMU
#define parse_stream_dquoted(as_string, dest, input, dquote_end) \
parse_stream_dquoted(dest, input, dquote_end)
#endif
static int parse_stream_dquoted(o_string *as_string,
o_string *dest,
struct in_str *input,
int dquote_end);
/* expand_strvec_to_strvec() takes a list of strings, expands
* all variable references within and returns a pointer to
* a list of expanded strings, possibly with larger number
* of strings. (Think VAR="a b"; echo $VAR).
* This new list is allocated as a single malloc block.
* NULL-terminated list of char* pointers is at the beginning of it,
* followed by strings themself.
* Caller can deallocate entire list by single free(list). */
/* Store given string, finalizing the word and starting new one whenever
* we encounter IFS char(s). This is used for expanding variable values.
* End-of-string does NOT finalize word: think about 'echo -$VAR-' */
static int expand_on_ifs(o_string *output, int n, const char *str)
{
while (1) {
int word_len = strcspn(str, G.ifs);
if (word_len) {
if (output->o_escape || !output->o_glob)
o_addQstr(output, str, word_len);
else /* protect backslashes against globbing up :) */
o_addblock_duplicate_backslash(output, str, word_len);
str += word_len;
}
if (!*str) /* EOL - do not finalize word */
break;
o_addchr(output, '\0');
debug_print_list("expand_on_ifs", output, n);
n = o_save_ptr(output, n);
str += strspn(str, G.ifs); /* skip ifs chars */
}
debug_print_list("expand_on_ifs[1]", output, n);
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
* variables/parameters with whitespace, $* and $@, and constructs like
* 'echo -$*-'. If you play here, you must run testsuite afterwards! */
static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
{
/* or_mask is either 0 (normal case) or 0x80
* (expansion of right-hand side of assignment == 1-element expand.
* It will also do no globbing, and thus we must not backslash-quote!) */
char first_ch, ored_ch;
int i;
const char *val;
char *dyn_val, *p;
dyn_val = NULL;
ored_ch = 0;
debug_printf_expand("expand_vars_to_list: arg '%s'\n", arg);
debug_print_list("expand_vars_to_list", output, n);
n = o_save_ptr(output, n);
debug_print_list("expand_vars_to_list[0]", output, n);
while ((p = strchr(arg, SPECIAL_VAR_SYMBOL)) != NULL) {
#if ENABLE_HUSH_TICK
o_string subst_result = NULL_O_STRING;
#endif
#if ENABLE_SH_MATH_SUPPORT
char arith_buf[sizeof(arith_t)*3 + 2];
#endif
o_addblock(output, arg, p - arg);
debug_print_list("expand_vars_to_list[1]", output, n);
arg = ++p;
p = strchr(p, SPECIAL_VAR_SYMBOL);
first_ch = arg[0] | or_mask; /* forced to "quoted" if or_mask = 0x80 */
/* "$@" is special. Even if quoted, it can still
* expand to nothing (not even an empty string) */
if ((first_ch & 0x7f) != '@')
ored_ch |= first_ch;
val = NULL;
switch (first_ch & 0x7f) {
/* Highest bit in first_ch indicates that var is double-quoted */
case '$': /* pid */
val = utoa(G.root_pid);
break;
case '!': /* bg pid */
val = G.last_bg_pid ? utoa(G.last_bg_pid) : (char*)"";
break;
case '?': /* exitcode */
val = utoa(G.last_exitcode);
break;
case '#': /* argc */
if (arg[1] != SPECIAL_VAR_SYMBOL)
/* actually, it's a ${#var} */
goto case_default;
val = utoa(G.global_argc ? G.global_argc-1 : 0);
break;
case '*':
case '@':
i = 1;
if (!G.global_argv[i])
break;
ored_ch |= first_ch; /* do it for "$@" _now_, when we know it's not empty */
if (!(first_ch & 0x80)) { /* unquoted $* or $@ */
smallint sv = output->o_escape;
/* unquoted var's contents should be globbed, so don't escape */
output->o_escape = 0;
while (G.global_argv[i]) {
n = expand_on_ifs(output, n, G.global_argv[i]);
debug_printf_expand("expand_vars_to_list: argv %d (last %d)\n", i, G.global_argc - 1);
if (G.global_argv[i++][0] && G.global_argv[i]) {
/* this argv[] is not empty and not last:
* put terminating NUL, start new word */
o_addchr(output, '\0');
debug_print_list("expand_vars_to_list[2]", output, n);
n = o_save_ptr(output, n);
debug_print_list("expand_vars_to_list[3]", output, n);
}
}
output->o_escape = sv;
} else
/* If or_mask is nonzero, we handle assignment 'a=....$@.....'
* and in this case should treat it like '$*' - see 'else...' below */
if (first_ch == ('@'|0x80) && !or_mask) { /* quoted $@ */
while (1) {
o_addQstr(output, G.global_argv[i], strlen(G.global_argv[i]));
if (++i >= G.global_argc)
break;
o_addchr(output, '\0');
debug_print_list("expand_vars_to_list[4]", output, n);
n = o_save_ptr(output, n);
}
} else { /* quoted $*: add as one word */
while (1) {
o_addQstr(output, G.global_argv[i], strlen(G.global_argv[i]));
if (!G.global_argv[++i])
break;
if (G.ifs[0])
o_addchr(output, G.ifs[0]);
}
}
break;
case SPECIAL_VAR_SYMBOL: /* <SPECIAL_VAR_SYMBOL><SPECIAL_VAR_SYMBOL> */
/* "Empty variable", used to make "" etc to not disappear */
arg++;
ored_ch = 0x80;
break;
#if ENABLE_HUSH_TICK
case '`': /* <SPECIAL_VAR_SYMBOL>`cmd<SPECIAL_VAR_SYMBOL> */
*p = '\0';
arg++;
//TODO: can we just stuff it into "output" directly?
debug_printf_subst("SUBST '%s' first_ch %x\n", arg, first_ch);
process_command_subs(&subst_result, arg);
debug_printf_subst("SUBST RES '%s'\n", subst_result.data);
val = subst_result.data;
goto store_val;
#endif
#if ENABLE_SH_MATH_SUPPORT
case '+': { /* <SPECIAL_VAR_SYMBOL>+cmd<SPECIAL_VAR_SYMBOL> */
arith_eval_hooks_t hooks;
arith_t res;
int errcode;
char *exp_str;
arg++; /* skip '+' */
*p = '\0'; /* replace trailing <SPECIAL_VAR_SYMBOL> */
debug_printf_subst("ARITH '%s' first_ch %x\n", arg, first_ch);
exp_str = expand_pseudo_dquoted(arg);
hooks.lookupvar = get_local_var_value;
hooks.setvar = arith_set_local_var;
hooks.endofname = endofname;
res = arith(exp_str ? exp_str : arg, &errcode, &hooks);
free(exp_str);
if (errcode < 0) {
const char *msg = "error in arithmetic";
switch (errcode) {
case -3:
msg = "exponent less than 0";
break;
case -2:
msg = "divide by 0";
break;
case -5:
msg = "expression recursion loop detected";
break;
}
die_if_script(msg);
}
debug_printf_subst("ARITH RES '"arith_t_fmt"'\n", res);
sprintf(arith_buf, arith_t_fmt, res);
val = arith_buf;
break;
}
#endif
default: /* <SPECIAL_VAR_SYMBOL>varname<SPECIAL_VAR_SYMBOL> */
case_default: {
bool exp_len = false;
bool exp_null = false;
char *var = arg;
char exp_save = exp_save; /* for compiler */
char exp_op = exp_op; /* for compiler */
char *exp_word = exp_word; /* for compiler */
size_t exp_off = 0;
*p = '\0';
arg[0] = first_ch & 0x7f;
/* prepare for expansions */
if (var[0] == '#') {
/* handle length expansion ${#var} */
exp_len = true;
++var;
} else {
/* maybe handle parameter expansion */
exp_off = strcspn(var, ":-=+?%#");
if (!var[exp_off])
exp_off = 0;
if (exp_off) {
exp_save = var[exp_off];
exp_null = exp_save == ':';
exp_word = var + exp_off;
if (exp_null)
++exp_word;
exp_op = *exp_word++;
var[exp_off] = '\0';
}
}
/* lookup the variable in question */
if (isdigit(var[0])) {
/* handle_dollar() should have vetted var for us */
i = xatoi_u(var);
if (i < G.global_argc)
val = G.global_argv[i];
/* else val remains NULL: $N with too big N */
} else
val = get_local_var_value(var);
/* handle any expansions */
if (exp_len) {
debug_printf_expand("expand: length of '%s' = ", val);
val = utoa(val ? strlen(val) : 0);
debug_printf_expand("%s\n", val);
} else if (exp_off) {
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 {
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;
}
arg[0] = first_ch;
#if ENABLE_HUSH_TICK
store_val:
#endif
if (!(first_ch & 0x80)) { /* unquoted $VAR */
debug_printf_expand("unquoted '%s', output->o_escape:%d\n", val, output->o_escape);
if (val) {
/* unquoted var's contents should be globbed, so don't escape */
smallint sv = output->o_escape;
output->o_escape = 0;
n = expand_on_ifs(output, n, val);
val = NULL;
output->o_escape = sv;
}
} else { /* quoted $VAR, val will be appended below */
debug_printf_expand("quoted '%s', output->o_escape:%d\n", val, output->o_escape);
}
} /* default: */
} /* switch (char after <SPECIAL_VAR_SYMBOL>) */
if (val) {
o_addQstr(output, val, strlen(val));
}
free(dyn_val);
dyn_val = NULL;
/* Do the check to avoid writing to a const string */
if (*p != SPECIAL_VAR_SYMBOL)
*p = SPECIAL_VAR_SYMBOL;
#if ENABLE_HUSH_TICK
o_free(&subst_result);
#endif
arg = ++p;
} /* end of "while (SPECIAL_VAR_SYMBOL is found) ..." */
if (arg[0]) {
debug_print_list("expand_vars_to_list[a]", output, n);
/* this part is literal, and it was already pre-quoted
* if needed (much earlier), do not use o_addQstr here! */
o_addstr_with_NUL(output, arg);
debug_print_list("expand_vars_to_list[b]", output, n);
} else if (output->length == o_get_last_ptr(output, n) /* expansion is empty */
&& !(ored_ch & 0x80) /* and all vars were not quoted. */
) {
n--;
/* allow to reuse list[n] later without re-growth */
output->has_empty_slot = 1;
} else {
o_addchr(output, '\0');
}
return n;
}
static char **expand_variables(char **argv, int or_mask)
{
int n;
char **list;
char **v;
o_string output = NULL_O_STRING;
if (or_mask & 0x100) {
output.o_escape = 1; /* protect against globbing for "$var" */
/* (unquoted $var will temporarily switch it off) */
output.o_glob = 1;
}
n = 0;
v = argv;
while (*v) {
n = expand_vars_to_list(&output, n, *v, (char)or_mask);
v++;
}
debug_print_list("expand_variables", &output, n);
/* output.data (malloced in one block) gets returned in "list" */
list = o_finalize_list(&output, n);
debug_print_strings("expand_variables[1]", list);
return list;
}
static char **expand_strvec_to_strvec(char **argv)
{
return expand_variables(argv, 0x100);
}
/* Used for expansion of right hand of assignments */
/* NB: should NOT do globbing! "export v=/bin/c*; env | grep ^v=" outputs
* "v=/bin/c*" */
static char *expand_string_to_string(const char *str)
{
char *argv[2], **list;
argv[0] = (char*)str;
argv[1] = NULL;
list = expand_variables(argv, 0x80); /* 0x80: make one-element expansion */
if (HUSH_DEBUG)
if (!list[0] || list[1])
bb_error_msg_and_die("BUG in varexp2");
/* actually, just move string 2*sizeof(char*) bytes back */
overlapping_strcpy((char*)list, list[0]);
debug_printf_expand("string_to_string='%s'\n", (char*)list);
return (char*)list;
}
/* Used for "eval" builtin */
static char* expand_strvec_to_string(char **argv)
{
char **list;
list = expand_variables(argv, 0x80);
/* Convert all NULs to spaces */
if (list[0]) {
int n = 1;
while (list[n]) {
if (HUSH_DEBUG)
if (list[n-1] + strlen(list[n-1]) + 1 != list[n])
bb_error_msg_and_die("BUG in varexp3");
list[n][-1] = ' '; /* TODO: or to G.ifs[0]? */
n++;
}
}
overlapping_strcpy((char*)list, list[0]);
debug_printf_expand("strvec_to_string='%s'\n", (char*)list);
return (char*)list;
}
static char **expand_assignments(char **argv, int count)
{
int i;
char **p = NULL;
/* Expand assignments into one string each */
for (i = 0; i < count; i++) {
p = add_string_to_strings(p, expand_string_to_string(argv[i]));
}
return p;
}
#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[])
{
int openfd, mode;
struct redir_struct *redir;
for (redir = prog->redirects; redir; redir = redir->next) {
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->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;
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!) */
//what the above comment tries to say?
return 1;
}
} else {
/* rd_fd<*>rd_dup or rd_fd<*>- cases */
openfd = redir->rd_dup;
}
if (openfd != redir->rd_fd) {
if (squirrel && redir->rd_fd < 3) {
squirrel[redir->rd_fd] = dup(redir->rd_fd);
}
if (openfd == REDIRFD_CLOSE) {
/* "n>-" means "close me" */
close(redir->rd_fd);
} else {
xdup2(openfd, redir->rd_fd);
if (redir->rd_dup == REDIRFD_TO_FILE)
close(openfd);
}
}
}
return 0;
}
static void restore_redirects(int squirrel[])
{
int i, fd;
for (i = 0; i < 3; i++) {
fd = squirrel[i];
if (fd != -1) {
/* We simply die on error */
xmove_fd(fd, i);
}
}
}
static void free_pipe_list(struct pipe *head);
/* Return code is the exit status of the pipe */
static void free_pipe(struct pipe *pi)
{
char **p;
struct command *command;
struct redir_struct *r, *rnext;
int a, i;
if (pi->stopped_cmds > 0) /* why? */
return;
debug_printf_clean("run pipe: (pid %d)\n", getpid());
for (i = 0; i < pi->num_cmds; i++) {
command = &pi->cmds[i];
debug_printf_clean(" command %d:\n", i);
if (command->argv) {
for (a = 0, p = command->argv; *p; 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(" 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(" 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);
}
command->redirects = NULL;
}
free(pi->cmds); /* children are an array, they get freed all at once */
pi->cmds = NULL;
#if ENABLE_HUSH_JOB
free(pi->cmdtext);
pi->cmdtext = NULL;
#endif
}
static void free_pipe_list(struct pipe *head)
{
struct pipe *pi, *next;
for (pi = head; pi; pi = next) {
#if HAS_KEYWORDS
debug_printf_clean(" pipe reserved word %d\n", pi->res_word);
#endif
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
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</