blob: 3665e40f2afe067acc93673a3544952c8fae6ffa [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.
*
* TODOs:
* grep for "TODO" and fix (some of them are easy)
* special variables (done: PWD, PPID, RANDOM)
* tilde expansion
* aliases
* follow IFS rules more precisely, including update semantics
* builtins mandated by standards we don't support:
* [un]alias, command, fc, getopts, newgrp, readonly, times
* make complex ${var%...} constructs support optional
* make here documents optional
*
* Bash compat TODO:
* redirection of stdout+stderr: &> and >&
* subst operator: ${var/[/]expr/expr}
* brace expansion: one/{two,three,four}
* reserved words: function select
* advanced test: [[ ]]
* process substitution: <(list) and >(list)
* =~: regex operator
* let EXPR [EXPR...]
* Each EXPR is an arithmetic expression (ARITHMETIC EVALUATION)
* If the last arg evaluates to 0, let returns 1; 0 otherwise.
* NB: let `echo 'a=a + 1'` - error (IOW: multi-word expansion is used)
* ((EXPR))
* The EXPR is evaluated according to ARITHMETIC EVALUATION.
* This is exactly equivalent to let "EXPR".
* $[EXPR]: synonym for $((EXPR))
* export builtin should be special, its arguments are assignments
* and therefore expansion of them should be "one-word" expansion:
* $ export i=`echo 'a b'` # export has one arg: "i=a b"
* compare with:
* $ ls i=`echo 'a b'` # ls has two args: "i=a" and "b"
* ls: cannot access i=a: No such file or directory
* ls: cannot access b: No such file or directory
* Note1: same applies to local builtin.
* Note2: bash 3.2.33(1) does this only if export word itself
* is not quoted:
* $ export i=`echo 'aaa bbb'`; echo "$i"
* aaa bbb
* $ "export" i=`echo 'aaa bbb'`; echo "$i"
* aaa
*
* Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
*/
#include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */
#include <malloc.h> /* for malloc_trim */
#include <glob.h>
/* #include <dmalloc.h> */
#if ENABLE_HUSH_CASE
# include <fnmatch.h>
#endif
#include "shell_common.h"
#include "math.h"
#include "match.h"
#if ENABLE_HUSH_RANDOM_SUPPORT
# include "random.h"
#else
# define CLEAR_RANDOM_T(rnd) ((void)0)
#endif
#ifndef PIPE_BUF
# define PIPE_BUF 4096 /* amount of buffering in a pipe */
#endif
/* 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
/* Slightly bigger (+200 bytes), but faster hush.
* So far it only enables a trick with counting SIGCHLDs and forks,
* which allows us to do fewer waitpid's.
* (we can detect a case where neither forks were done nor SIGCHLDs happened
* and therefore waitpid will return the same result as last time)
*/
#define ENABLE_HUSH_FAST 0
/* TODO: implement simplified code for users which do not need ${var%...} ops
* So far ${var%...} ops are always enabled:
*/
#define ENABLE_HUSH_DOLLAR_OPS 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
#include "NUM_APPLETS.h"
#if NUM_APPLETS == 1
/* STANDALONE does not make sense, and won't compile */
# undef CONFIG_FEATURE_SH_STANDALONE
# undef ENABLE_FEATURE_SH_STANDALONE
# undef IF_FEATURE_SH_STANDALONE
# undef IF_NOT_FEATURE_SH_STANDALONE
# define ENABLE_FEATURE_SH_STANDALONE 0
# define IF_FEATURE_SH_STANDALONE(...)
# define IF_NOT_FEATURE_SH_STANDALONE(...) __VA_ARGS__
#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_varexp(...) 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_VARS_STR "_*@$!?#"
#define SPECIAL_VARS_STR ("_*@$!?#" + 1)
#define NUMERIC_SPECVARS_STR ("_*@$!?#" + 3)
#define SPECIAL_VAR_SYMBOL 3
struct variable;
static const char hush_version_str[] ALIGN1 = "HUSH_VERSION="BB_VER;
/* This supports saving pointers malloced in vfork child,
* to be freed in the parent.
*/
#if !BB_MMU
typedef struct nommu_save_t {
char **new_env;
struct variable *old_vars;
char **argv;
char **argv_from_re_execing;
} nommu_save_t;
#endif
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 ,
/* three pseudo-keywords support contrived "case" syntax: */
RES_CASE_IN, /* "case ... IN", turns into RES_MATCH when IN is observed */
RES_MATCH , /* "word)" */
RES_CASE_BODY, /* "this command is inside CASE" */
RES_ESAC ,
#endif
RES_XXXX ,
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 *) FAST_FUNC;
int (*peek) (struct in_str *) FAST_FUNC;
} in_str;
#define i_getch(input) ((input)->get(input))
#define i_peek(input) ((input)->peek(input))
/* The descrip member of this structure is only used to make
* debugging output pretty */
static const struct {
int mode;
signed char default_fd;
char descrip[3];
} redir_table[] = {
{ O_RDONLY, 0, "<" },
{ O_CREAT|O_TRUNC|O_WRONLY, 1, ">" },
{ O_CREAT|O_APPEND|O_WRONLY, 1, ">>" },
{ O_CREAT|O_RDWR, 1, "<>" },
{ O_RDONLY, 0, "<<" },
/* Should not be needed. Bogus default_fd helps in debugging */
/* { O_RDONLY, 77, "<<" }, */
};
struct redir_struct {
struct redir_struct *next;
char *rd_filename; /* filename */
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:
* bit 0: do we need to trim leading tabs?
* bit 1: is heredoc quoted (<<'delim' syntax) ?
*/
};
typedef enum redir_type {
REDIRECT_INPUT = 0,
REDIRECT_OVERWRITE = 1,
REDIRECT_APPEND = 2,
REDIRECT_IO = 3,
REDIRECT_HEREDOC = 4,
REDIRECT_HEREDOC2 = 5, /* 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 cmd_type; /* CMD_xxx */
#define CMD_NORMAL 0
#define CMD_SUBSHELL 1
/* used for "[[ EXPR ]]" */
#if ENABLE_HUSH_BASH_COMPAT
# define CMD_SINGLEWORD_NOGLOB 2
#endif
/* used for "export noglob=* glob* a=`echo a b`" */
//#define CMD_SINGLEWORD_NOGLOB_COND 3
// It is hard to implement correctly, it adds significant amounts of tricky code,
// and all this is only useful for really obscure export statements
// almost nobody would use anyway. #ifdef CMD_SINGLEWORD_NOGLOB_COND
// guards the code which implements it, but I have doubts it works
// in all cases (especially with mixed globbed/non-globbed arguments)
#if ENABLE_HUSH_FUNCTIONS
# define CMD_FUNCDEF 3
#endif
/* if non-NULL, this "command" is { list }, ( list ), or a compound statement */
struct pipe *group;
#if !BB_MMU
char *group_as_string;
#endif
#if ENABLE_HUSH_FUNCTIONS
struct function *child_func;
/* This field is used to prevent a bug here:
* while...do f1() {a;}; f1; f1() {b;}; f1; done
* 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 its "parent cmd" struct is still "alive",
* we put those fields back into cmd->xxx
* (struct function has ->parent_cmd ptr to facilitate that).
* When we loop back, we can execute "f1() {a;}" again and set f1 correctly.
* 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 */
};
/* Is there anything in this command at all? */
#define IS_NULL_CMD(cmd) \
(!(cmd)->group && !(cmd)->argv && !(cmd)->redirects)
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;
/* Is there anything in this pipe at all? */
#define IS_NULL_PIPE(pi) \
((pi)->num_cmds == 0 IF_HAS_KEYWORDS( && (pi)->res_word == RES_NONE))
/* 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 */
#if ENABLE_HUSH_LOCAL
unsigned func_nest_level;
#endif
int max_len; /* if > 0, name is part of initial env; else name is malloced */
smallint flg_export; /* putenv should be done on this var */
smallint flg_read_only;
};
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 {
/* interactive_fd != 0 means we are an interactive shell.
* If we are, then saved_tty_pgrp can also be != 0, meaning
* that controlling tty is available. With saved_tty_pgrp == 0,
* job control still works, but terminal signals
* (^C, ^Z, ^Y, ^\) won't work at all, and background
* process groups can only be created with "cmd &".
* With saved_tty_pgrp != 0, hush will use tcsetpgrp()
* to give tty to the foreground process group,
* and will take it back when the group is stopped (^Z)
* or killed (^C).
*/
#if ENABLE_HUSH_INTERACTIVE
/* 'interactive_fd' is a fd# open to ctty, if we have one
* _AND_ if we decided to act interactively */
int interactive_fd;
const char *PS1;
const char *PS2;
# define G_interactive_fd (G.interactive_fd)
#else
# define G_interactive_fd 0
#endif
#if ENABLE_FEATURE_EDITING
line_input_t *line_input_state;
#endif
pid_t root_pid;
pid_t root_ppid;
pid_t last_bg_pid;
#if ENABLE_HUSH_RANDOM_SUPPORT
random_t random_gen;
#endif
#if ENABLE_HUSH_JOB
int run_list_level;
int last_jobid;
pid_t saved_tty_pgrp;
struct pipe *job_list;
# define G_saved_tty_pgrp (G.saved_tty_pgrp)
#else
# define G_saved_tty_pgrp 0
#endif
smallint flag_SIGINT;
#if ENABLE_HUSH_LOOPS
smallint flag_break_continue;
#endif
#if ENABLE_HUSH_FUNCTIONS
/* 0: outside of a function (or sourced file)
* -1: inside of a function, ok to use return builtin
* 1: return is invoked, skip all till end of func
*/
smallint flag_return_in_progress;
#endif
smallint fake_mode;
smallint exiting; /* used to prevent EXIT trap recursion */
/* These four support $?, $#, and $1 */
smalluint last_exitcode;
/* are global_argv and global_argv[1..n] malloced? (note: not [0]) */
smalluint global_args_malloced;
smalluint inherited_set_is_saved;
/* how many non-NULL argv's we have. NB: $# + 1 */
int global_argc;
char **global_argv;
#if !BB_MMU
char *argv0_for_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;
# if ENABLE_HUSH_LOCAL
struct variable **shadowed_vars_pp;
unsigned func_nest_level;
# endif
#endif
/* Signal and trap handling */
#if ENABLE_HUSH_FAST
unsigned count_SIGCHLD;
unsigned handled_SIGCHLD;
smallint we_have_children;
#endif
/* which signals have non-DFL handler (even with no traps set)? */
unsigned non_DFL_mask;
char **traps; /* char *traps[NSIG] */
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 ? CONFIG_FEATURE_EDITING_MAX_LEN : 2];
};
#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) FAST_FUNC;
static int builtin_echo(char **argv) FAST_FUNC;
static int builtin_eval(char **argv) FAST_FUNC;
static int builtin_exec(char **argv) FAST_FUNC;
static int builtin_exit(char **argv) FAST_FUNC;
static int builtin_export(char **argv) FAST_FUNC;
#if ENABLE_HUSH_JOB
static int builtin_fg_bg(char **argv) FAST_FUNC;
static int builtin_jobs(char **argv) FAST_FUNC;
#endif
#if ENABLE_HUSH_HELP
static int builtin_help(char **argv) FAST_FUNC;
#endif
#if ENABLE_HUSH_LOCAL
static int builtin_local(char **argv) FAST_FUNC;
#endif
#if HUSH_DEBUG
static int builtin_memleak(char **argv) FAST_FUNC;
#endif
#if ENABLE_PRINTF
static int builtin_printf(char **argv) FAST_FUNC;
#endif
static int builtin_pwd(char **argv) FAST_FUNC;
static int builtin_read(char **argv) FAST_FUNC;
static int builtin_set(char **argv) FAST_FUNC;
static int builtin_shift(char **argv) FAST_FUNC;
static int builtin_source(char **argv) FAST_FUNC;
static int builtin_test(char **argv) FAST_FUNC;
static int builtin_trap(char **argv) FAST_FUNC;
static int builtin_type(char **argv) FAST_FUNC;
static int builtin_true(char **argv) FAST_FUNC;
static int builtin_umask(char **argv) FAST_FUNC;
static int builtin_unset(char **argv) FAST_FUNC;
static int builtin_wait(char **argv) FAST_FUNC;
#if ENABLE_HUSH_LOOPS
static int builtin_break(char **argv) FAST_FUNC;
static int builtin_continue(char **argv) FAST_FUNC;
#endif
#if ENABLE_HUSH_FUNCTIONS
static int builtin_return(char **argv) FAST_FUNC;
#endif
/* Table of built-in functions. They can be forked or not, depending on
* 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 *b_cmd;
int (*b_function)(char **argv) FAST_FUNC;
#if ENABLE_HUSH_HELP
const char *b_descr;
# define BLTIN(cmd, func, help) { cmd, func, help }
#else
# define BLTIN(cmd, func, help) { cmd, func }
#endif
};
static const struct built_in_command bltins1[] = {
BLTIN("." , builtin_source , "Run commands in a file"),
BLTIN(":" , builtin_true , NULL),
#if ENABLE_HUSH_JOB
BLTIN("bg" , builtin_fg_bg , "Resume a job in the background"),
#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("eval" , builtin_eval , "Construct and run shell command"),
BLTIN("exec" , builtin_exec , "Execute command, don't return to shell"),
BLTIN("exit" , builtin_exit , "Exit"),
BLTIN("export" , builtin_export , "Set environment variables"),
#if ENABLE_HUSH_JOB
BLTIN("fg" , builtin_fg_bg , "Bring job into the foreground"),
#endif
#if ENABLE_HUSH_HELP
BLTIN("help" , builtin_help , NULL),
#endif
#if ENABLE_HUSH_JOB
BLTIN("jobs" , builtin_jobs , "List jobs"),
#endif
#if ENABLE_HUSH_LOCAL
BLTIN("local" , builtin_local , "Set local variables"),
#endif
#if HUSH_DEBUG
BLTIN("memleak" , builtin_memleak , NULL),
#endif
BLTIN("read" , builtin_read , "Input into variable"),
#if ENABLE_HUSH_FUNCTIONS
BLTIN("return" , builtin_return , "Return from a function"),
#endif
BLTIN("set" , builtin_set , "Set/unset positional parameters"),
BLTIN("shift" , builtin_shift , "Shift positional parameters"),
#if ENABLE_HUSH_BASH_COMPAT
BLTIN("source" , builtin_source , "Run commands in a file"),
#endif
BLTIN("trap" , builtin_trap , "Trap signals"),
BLTIN("type" , builtin_type , "Show command type"),
BLTIN("ulimit" , shell_builtin_ulimit , "Control resource limits"),
BLTIN("umask" , builtin_umask , "Set file creation mask"),
BLTIN("unset" , builtin_unset , "Unset variables"),
BLTIN("wait" , builtin_wait , "Wait for process"),
};
/* For now, echo and test are unconditionally enabled.
* Maybe make it configurable? */
static const struct built_in_command bltins2[] = {
BLTIN("[" , builtin_test , NULL),
BLTIN("echo" , builtin_echo , NULL),
#if ENABLE_PRINTF
BLTIN("printf" , builtin_printf , NULL),
#endif
BLTIN("pwd" , builtin_pwd , NULL),
BLTIN("test" , builtin_test , NULL),
};
/* 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_varexp
# define debug_printf_varexp(...) (indent(), fprintf(stderr, __VA_ARGS__))
#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, ...) die_if_script(__VA_ARGS__)
# define syntax_error(lineno, msg) syntax_error(msg)
# define syntax_error_at(lineno, msg) syntax_error_at(msg)
# define syntax_error_unterm_ch(lineno, ch) syntax_error_unterm_ch(ch)
# 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);
}
static void syntax_error_unterm_str(unsigned lineno, const char *s)
{
die_if_script(lineno, "syntax error: unterminated %s", s);
}
/* It so happens that all such cases are totally fatal
* even if shell is interactive: EOF while looking for closing
* delimiter. There is nowhere to read stuff from after that,
* 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] = { ch, '\0' };
syntax_error_unterm_str(lineno, msg);
xfunc_die();
}
static void syntax_error_unexpected_ch(unsigned lineno, int ch)
{
char msg[2];
msg[0] = ch;
msg[1] = '\0';
die_if_script(lineno, "syntax error: unexpected %s", ch == EOF ? "EOF" : 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(...) die_if_script(__LINE__, __VA_ARGS__)
# define syntax_error(msg) syntax_error(__LINE__, msg)
# define syntax_error_at(msg) syntax_error_at(__LINE__, msg)
# define syntax_error_unterm_ch(ch) syntax_error_unterm_ch(__LINE__, ch)
# 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
#if ENABLE_HUSH_INTERACTIVE
static void cmdedit_update_prompt(void);
#else
# define cmdedit_update_prompt() ((void)0)
#endif
/* Utility functions
*/
/* Replace each \x with x in place, return ptr past NUL. */
static char *unbackslash(char *src)
{
char *dst = src = strchrnul(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
/* Note: takes ownership of "add" ptr (it is not strdup'ed) */
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 free_strings(char **strings)
{
char **v;
if (!strings)
return;
v = strings;
while (*v) {
free(*v);
v++;
}
free(strings);
}
/* Helpers for setting new $n and restoring them back
*/
typedef struct save_arg_t {
char *sv_argv0;
char **sv_g_argv;
int sv_g_argc;
smallint sv_g_malloced;
} save_arg_t;
static void save_and_replace_G_args(save_arg_t *sv, char **argv)
{
int n;
sv->sv_argv0 = argv[0];
sv->sv_g_argv = G.global_argv;
sv->sv_g_argc = G.global_argc;
sv->sv_g_malloced = G.global_args_malloced;
argv[0] = G.global_argv[0]; /* retain $0 */
G.global_argv = argv;
G.global_args_malloced = 0;
n = 1;
while (*++argv)
n++;
G.global_argc = n;
}
static void restore_G_args(save_arg_t *sv, char **argv)
{
char **pp;
if (G.global_args_malloced) {
/* someone ran "set -- arg1 arg2 ...", undo */
pp = G.global_argv;
while (*++pp) /* note: does not free $0 */
free(*pp);
free(G.global_argv);
}
argv[0] = sv->sv_argv0;
G.global_argv = sv->sv_g_argv;
G.global_argc = sv->sv_g_argc;
G.global_args_malloced = sv->sv_g_malloced;
}
/* 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 seems 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,
* except for handlers set to '' (empty string).
*
* If job control is off, backgrounded commands ("cmd &")
* have SIGINT, SIGQUIT set to SIG_IGN.
*
* Commands which are run in command substitution ("`cmd`")
* have SIGTTIN, SIGTTOU, SIGTSTP set to SIG_IGN.
*
* Ordinary commands have signals set to SIG_IGN/DFL as inherited
* by the shell from its parent.
*
* Signals 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. Try this in bash:
* while :; do :; done - ^Z does not background it
* (while :; do :; done) - ^Z backgrounds it
* 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).
* 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:
* unblock signals with special interactive handling
* (child shell is not interactive),
* unset all traps except '' (note: regardless of child shell's type - {}, (), etc)
* after [v]fork, if we plan to exec:
* POSIX says fork clears pending signal mask in child - no need to clear it.
* Restore blocked signal set to one inherited by shell just prior to exec.
*
* Note: as a result, we do not use signal handlers much. The only uses
* are to count SIGCHLDs
* and to restore tty pgrp on signal-induced exit.
*
* Note 2 (compat):
* Standard says "When a subshell is entered, traps that are not being ignored
* are set to the default actions". bash interprets it so that traps which
* are set to '' (ignore) are NOT reset to defaults. We do the same.
*/
enum {
SPECIAL_INTERACTIVE_SIGS = 0
| (1 << SIGTERM)
| (1 << SIGINT)
| (1 << SIGHUP)
,
SPECIAL_JOB_SIGS = 0
#if ENABLE_HUSH_JOB
| (1 << SIGTTIN)
| (1 << SIGTTOU)
| (1 << SIGTSTP)
#endif
};
#if ENABLE_HUSH_FAST
static void SIGCHLD_handler(int sig UNUSED_PARAM)
{
G.count_SIGCHLD++;
//bb_error_msg("[%d] SIGCHLD_handler: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
}
#endif
#if ENABLE_HUSH_JOB
/* 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 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_saved_tty_pgrp && 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_all();
sigexit(- (exitcode & 0xff));
#else
exit(exitcode);
#endif
}
static int check_and_run_traps(int sig)
{
static const struct timespec zero_timespec;
smalluint save_rcode;
int last_sig = 0;
if (sig)
goto jump_in;
while (1) {
sig = sigtimedwait(&G.blocked_set, NULL, &zero_timespec);
if (sig <= 0)
break;
jump_in:
last_sig = sig;
if (G.traps && G.traps[sig]) {
if (G.traps[sig][0]) {
/* We have user-defined handler */
char *argv[] = { NULL, xstrdup(G.traps[sig]), NULL };
save_rcode = G.last_exitcode;
builtin_eval(argv);
free(argv[1]);
G.last_exitcode = save_rcode;
} /* else: "" trap, ignoring signal */
continue;
}
/* not a trap: special action */
switch (sig) {
#if ENABLE_HUSH_FAST
case SIGCHLD:
G.count_SIGCHLD++;
//bb_error_msg("[%d] check_and_run_traps: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
break;
#endif
case SIGINT:
/* Builtin was ^C'ed, make it look prettier: */
bb_putchar('\n');
G.flag_SIGINT = 1;
break;
#if ENABLE_HUSH_JOB
case SIGHUP: {
struct pipe *job;
/* bash is observed to signal whole process groups,
* not individual processes */
for (job = G.job_list; job; job = job->next) {
if (job->pgrp <= 0)
continue;
debug_printf_exec("HUPing pgrp %d\n", job->pgrp);
if (kill(- job->pgrp, SIGHUP) == 0)
kill(- job->pgrp, SIGCONT);
}
sigexit(SIGHUP);
}
#endif
default: /* ignored: */
/* SIGTERM, SIGQUIT, SIGTTIN, SIGTTOU, SIGTSTP */
break;
}
}
return last_sig;
}
static const char *get_cwd(int force)
{
if (force || G.cwd == NULL) {
/* xrealloc_getcwd_or_warn(arg) calls free(arg),
* we must not try to free(bb_msg_unknown) */
if (G.cwd == bb_msg_unknown)
G.cwd = NULL;
G.cwd = xrealloc_getcwd_or_warn((char *)G.cwd);
if (!G.cwd)
G.cwd = bb_msg_unknown;
}
return G.cwd;
}
/*
* Shell and environment variable support
*/
static struct variable **get_ptr_to_local_var(const char *name)
{
struct variable **pp;
struct variable *cur;
int len;
len = strlen(name);
pp = &G.top_var;
while ((cur = *pp) != NULL) {
if (strncmp(cur->varstr, name, len) == 0 && cur->varstr[len] == '=')
return pp;
pp = &cur->next;
}
return NULL;
}
static struct variable *get_local_var(const char *name)
{
struct variable **pp = get_ptr_to_local_var(name);
if (pp)
return *pp;
return NULL;
}
static const char* FAST_FUNC get_local_var_value(const char *name)
{
struct variable **pp = get_ptr_to_local_var(name);
if (pp)
return strchr((*pp)->varstr, '=') + 1;
if (strcmp(name, "PPID") == 0)
return utoa(G.root_ppid);
// bash compat: UID? EUID?
#if ENABLE_HUSH_RANDOM_SUPPORT
if (strcmp(name, "RANDOM") == 0) {
return utoa(next_random(&G.random_gen));
}
#endif
return NULL;
}
/* str holds "NAME=VAL" and is expected to be malloced.
* We take ownership of it.
* flg_export:
* 0: do not change export flag
* (if creating new variable, flag will be 0)
* 1: set export flag and putenv the variable
* -1: clear export flag and unsetenv the variable
* flg_read_only is set only when we handle -R var=val
*/
#if !BB_MMU && ENABLE_HUSH_LOCAL
/* all params are used */
#elif BB_MMU && ENABLE_HUSH_LOCAL
#define set_local_var(str, flg_export, local_lvl, flg_read_only) \
set_local_var(str, flg_export, local_lvl)
#elif BB_MMU && !ENABLE_HUSH_LOCAL
#define set_local_var(str, flg_export, local_lvl, flg_read_only) \
set_local_var(str, flg_export)
#elif !BB_MMU && !ENABLE_HUSH_LOCAL
#define set_local_var(str, flg_export, local_lvl, flg_read_only) \
set_local_var(str, flg_export, flg_read_only)
#endif
static int set_local_var(char *str, int flg_export, int local_lvl, int flg_read_only)
{
struct variable **var_pp;
struct variable *cur;
char *eq_sign;
int name_len;
eq_sign = strchr(str, '=');
if (!eq_sign) { /* not expected to ever happen? */
free(str);
return -1;
}
name_len = eq_sign - str + 1; /* including '=' */
var_pp = &G.top_var;
while ((cur = *var_pp) != NULL) {
if (strncmp(cur->varstr, str, name_len) != 0) {
var_pp = &cur->next;
continue;
}
/* We found an existing var with this name */
if (cur->flg_read_only) {
#if !BB_MMU
if (!flg_read_only)
#endif
bb_error_msg("%s: readonly variable", str);
free(str);
return -1;
}
if (flg_export == -1) { // "&& cur->flg_export" ?
debug_printf_env("%s: unsetenv '%s'\n", __func__, str);
*eq_sign = '\0';
unsetenv(str);
*eq_sign = '=';
}
#if ENABLE_HUSH_LOCAL
if (cur->func_nest_level < local_lvl) {
/* New variable is declared as local,
* and existing one is global, or local
* from enclosing function.
* Remove and save old one: */
*var_pp = cur->next;
cur->next = *G.shadowed_vars_pp;
*G.shadowed_vars_pp = cur;
/* bash 3.2.33(1) and exported vars:
* # export z=z
* # f() { local z=a; env | grep ^z; }
* # f
* z=a
* # env | grep ^z
* z=z
*/
if (cur->flg_export)
flg_export = 1;
break;
}
#endif
if (strcmp(cur->varstr + name_len, eq_sign + 1) == 0) {
free_and_exp:
free(str);
goto exp;
}
if (cur->max_len != 0) {
if (cur->max_len >= strlen(str)) {
/* This one is from startup env, reuse space */
strcpy(cur->varstr, str);
goto free_and_exp;
}
} else {
/* max_len == 0 signifies "malloced" var, which we can
* (and has to) free */
free(cur->varstr);
}
cur->max_len = 0;
goto set_str_and_exp;
}
/* Not found - create new variable struct */
cur = xzalloc(sizeof(*cur));
#if ENABLE_HUSH_LOCAL
cur->func_nest_level = local_lvl;
#endif
cur->next = *var_pp;
*var_pp = cur;
set_str_and_exp:
cur->varstr = str;
#if !BB_MMU
cur->flg_read_only = flg_read_only;
#endif
exp:
if (flg_export == 1)
cur->flg_export = 1;
if (name_len == 4 && cur->varstr[0] == 'P' && cur->varstr[1] == 'S')
cmdedit_update_prompt();
if (cur->flg_export) {
if (flg_export == -1) {
cur->flg_export = 0;
/* unsetenv was already done */
} else {
debug_printf_env("%s: putenv '%s'\n", __func__, cur->varstr);
return putenv(cur->varstr);
}
}
return 0;
}
/* Used at startup and after each cd */
static void set_pwd_var(int exp)
{
set_local_var(xasprintf("PWD=%s", get_cwd(/*force:*/ 1)),
/*exp:*/ exp, /*lvl:*/ 0, /*ro:*/ 0);
}
static int unset_local_var_len(const char *name, int name_len)
{
struct variable *cur;
struct variable **var_pp;
if (!name)
return EXIT_SUCCESS;
var_pp = &G.top_var;
while ((cur = *var_pp) != NULL) {
if (strncmp(cur->varstr, name, name_len) == 0 && cur->varstr[name_len] == '=') {
if (cur->flg_read_only) {
bb_error_msg("%s: readonly variable", name);
return EXIT_FAILURE;
}
*var_pp = cur->next;
debug_printf_env("%s: unsetenv '%s'\n", __func__, cur->varstr);
bb_unsetenv(cur->varstr);
if (name_len == 3 && cur->varstr[0] == 'P' && cur->varstr[1] == 'S')
cmdedit_update_prompt();
if (!cur->max_len)
free(cur->varstr);
free(cur);
return EXIT_SUCCESS;
}
var_pp = &cur->next;
}
return EXIT_SUCCESS;
}
static int unset_local_var(const char *name)
{
return unset_local_var_len(name, strlen(name));
}
static void unset_vars(char **strings)
{
char **v;
if (!strings)
return;
v = strings;
while (*v) {
const char *eq = strchrnul(*v, '=');
unset_local_var_len(*v, (int)(eq - *v));
v++;
}
free(strings);
}
#if ENABLE_SH_MATH_SUPPORT
# define is_name(c) ((c) == '_' || isalpha((unsigned char)(c)))
# define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c)))
static char* FAST_FUNC 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;
}
#endif
static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val)
{
char *var = xasprintf("%s=%s", name, val);
set_local_var(var, /*flags:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
}
/*
* Helpers for "var1=val1 var2=val2 cmd" feature
*/
static void add_vars(struct variable *var)
{
struct variable *next;
while (var) {
next = var->next;
var->next = G.top_var;
G.top_var = var;
if (var->flg_export) {
debug_printf_env("%s: restoring exported '%s'\n", __func__, var->varstr);
putenv(var->varstr);
} else {
debug_printf_env("%s: restoring variable '%s'\n", __func__, var->varstr);
}
var = next;
}
}
static struct variable *set_vars_and_save_old(char **strings)
{
char **s;
struct variable *old = NULL;
if (!strings)
return old;
s = strings;
while (*s) {
struct variable *var_p;
struct variable **var_pp;
char *eq;
eq = strchr(*s, '=');
if (eq) {
*eq = '\0';
var_pp = get_ptr_to_local_var(*s);
*eq = '=';
if (var_pp) {
/* Remove variable from global linked list */
var_p = *var_pp;
debug_printf_env("%s: removing '%s'\n", __func__, var_p->varstr);
*var_pp = var_p->next;
/* Add it to returned list */
var_p->next = old;
old = var_p;
}
set_local_var(*s, /*exp:*/ 1, /*lvl:*/ 0, /*ro:*/ 0);
}
s++;
}
return old;
}
/*
* in_str support
*/
static int FAST_FUNC static_get(struct in_str *i)
{
int ch = *i->p;
if (ch != '\0') {
i->p++;
return ch;
}
return EOF;
}
static int FAST_FUNC static_peek(struct in_str *i)
{
return *i->p;
}
#if ENABLE_HUSH_INTERACTIVE
static void cmdedit_update_prompt(void)
{
if (ENABLE_FEATURE_EDITING_FANCY_PROMPT) {
G.PS1 = get_local_var_value("PS1");
if (G.PS1 == NULL)
G.PS1 = "\\w \\$ ";
G.PS2 = get_local_var_value("PS2");
} else {
G.PS1 = NULL;
}
if (G.PS2 == NULL)
G.PS2 = "> ";
}
static const char* setup_prompt_string(int promptmode)
{
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);
/* bash uses $PWD value, even if it is set by user.
* It uses current dir only if PWD is unset.
* We always use current dir. */
G.PS1 = xasprintf("%s %c ", get_cwd(0), (geteuid() != 0) ? '$' : '#');
prompt_str = G.PS1;
} else
prompt_str = G.PS2;
} 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, CONFIG_FEATURE_EDITING_MAX_LEN-1, G.line_input_state);
/* catch *SIGINT* etc (^C is handled by read_line_input) */
check_and_run_traps(0);
} while (r == 0 || G.flag_SIGINT); /* repeat if ^C or SIGINT */
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_all();
G.user_input_buf[0] = r = fgetc(i->file);
/*G.user_input_buf[1] = '\0'; - already is and never changed */
//do we need check_and_run_traps(0)? (maybe only if stdin)
} 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 FAST_FUNC 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 FAST_FUNC 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';
}
static void o_addstr(o_string *o, const char *str)
{
o_addblock(o, str, strlen(str));
}
#if !BB_MMU
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--;
}
}
#undef HUSH_BRACE_EXP
/*
* HUSH_BRACE_EXP code needs corresponding quoting on variable expansion side.
* Currently, "v='{q,w}'; echo $v" erroneously expands braces in $v.
* Apparently, on unquoted $v bash still does globbing
* ("v='*.txt'; echo $v" prints all .txt files),
* but NOT brace expansion! Thus, there should be TWO independent
* quoting mechanisms on $v expansion side: one protects
* $v from brace expansion, and other additionally protects "$v" against globbing.
* We have only second one.
*/
#ifdef HUSH_BRACE_EXP
# define MAYBE_BRACES "{}"
#else
# define MAYBE_BRACES ""
#endif
/* My analysis of quoting semantics tells me that state information
* is associated with a destination, not a source.
*/
static void o_addqchr(o_string *o, int ch)
{
int sz = 1;
char *found = strchr("*?[\\" MAYBE_BRACES, 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("*?[\\" MAYBE_BRACES, 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, "*?[\\" MAYBE_BRACES);
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 "*?[\\" MAYBE_BRACES */
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;
}
#ifdef HUSH_BRACE_EXP
/* There in a GNU extension, GLOB_BRACE, but it is not usable:
* first, it processes even {a} (no commas), second,
* I didn't manage to make it return strings when they don't match
* existing files. Need to re-implement it.
*/
/* Helper */
static int glob_needed(const char *s)
{
while (*s) {
if (*s == '\\') {
if (!s[1])
return 0;
s += 2;
continue;
}
if (*s == '*' || *s == '[' || *s == '?' || *s == '{')
return 1;
s++;
}
return 0;
}
/* Return pointer to next closing brace or to comma */
static const char *next_brace_sub(const char *cp)
{
unsigned depth = 0;
cp++;
while (*cp != '\0') {
if (*cp == '\\') {
if (*++cp == '\0')
break;
cp++;
continue;
}
/*{*/ if ((*cp == '}' && depth-- == 0) || (*cp == ',' && depth == 0))
break;
if (*cp++ == '{') /*}*/
depth++;
}
return *cp != '\0' ? cp : NULL;
}
/* Recursive brace globber. Note: may garble pattern[]. */
static int glob_brace(char *pattern, o_string *o, int n)
{
char *new_pattern_buf;
const char *begin;
const char *next;
const char *rest;
const char *p;
size_t rest_len;
debug_printf_glob("glob_brace('%s')\n", pattern);
begin = pattern;
while (1) {
if (*begin == '\0')
goto simple_glob;
if (*begin == '{') /*}*/ {
/* Find the first sub-pattern and at the same time
* find the rest after the closing brace */
next = next_brace_sub(begin);
if (next == NULL) {
/* An illegal expression */
goto simple_glob;
}
/*{*/ if (*next == '}') {
/* "{abc}" with no commas - illegal
* brace expr, disregard and skip it */
begin = next + 1;
continue;
}
break;
}
if (*begin == '\\' && begin[1] != '\0')
begin++;
begin++;
}
debug_printf_glob("begin:%s\n", begin);
debug_printf_glob("next:%s\n", next);
/* Now find the end of the whole brace expression */
rest = next;
/*{*/ while (*rest != '}') {
rest = next_brace_sub(rest);
if (rest == NULL) {
/* An illegal expression */
goto simple_glob;
}
debug_printf_glob("rest:%s\n", rest);
}
rest_len = strlen(++rest) + 1;
/* We are sure the brace expression is well-formed */
/* Allocate working buffer large enough for our work */
new_pattern_buf = xmalloc(strlen(pattern));
/* We have a brace expression. BEGIN points to the opening {,
* NEXT points past the terminator of the first element, and REST
* points past the final }. We will accumulate result names from
* recursive runs for each brace alternative in the buffer using
* GLOB_APPEND. */
p = begin + 1;
while (1) {
/* Construct the new glob expression */
memcpy(
mempcpy(
mempcpy(new_pattern_buf,
/* We know the prefix for all sub-patterns */
pattern, begin - pattern),
p, next - p),
rest, rest_len);
/* Note: glob_brace() may garble new_pattern_buf[].
* That's why we re-copy prefix every time (1st memcpy above).
*/
n = glob_brace(new_pattern_buf, o, n);
/*{*/ if (*next == '}') {
/* We saw the last entry */
break;
}
p = next + 1;
next = next_brace_sub(next);
}
free(new_pattern_buf);
return n;
simple_glob:
{
int gr;
glob_t globdata;
memset(&globdata, 0, sizeof(globdata));
gr = glob(pattern, 0, NULL, &globdata);
debug_printf_glob("glob('%s'):%d\n", pattern, gr);
if (gr != 0) {
if (gr == GLOB_NOMATCH) {
globfree(&globdata);
/* NB: garbles parameter */
unbackslash(pattern);
o_addstr_with_NUL(o, pattern);
debug_printf_glob("glob pattern '%s' is literal\n", pattern);
return o_save_ptr_helper(o, n);
}
if (gr == GLOB_NOSPACE)
bb_error_msg_and_die(bb_msg_memory_exhausted);
/* GLOB_ABORTED? Only happens with GLOB_ERR flag,
* but we didn't specify it. Paranoia again. */
bb_error_msg_and_die("glob error %d on '%s'", gr, pattern);
}
if (globdata.gl_pathv && globdata.gl_pathv[0]) {
char **argv = globdata.gl_pathv;
while (1) {
o_addstr_with_NUL(o, *argv);
n = o_save_ptr_helper(o, n);
argv++;
if (!*argv)
break;
}
}
globfree(&globdata);
}
return n;
}
/* Performs globbing on last list[],
* saving each result as a new list[].
*/
static int o_glob(o_string *o, int n)
{
char *pattern, *copy;
debug_printf_glob("start o_glob: n:%d o->data:%p\n", n, o->data);
if (!o->data)
return o_save_ptr_helper(o, n);
pattern = o->data + o_get_last_ptr(o, n);
debug_printf_glob("glob pattern '%s'\n", pattern);
if (!glob_needed(pattern)) {
/* unbackslash last string in o in place, fix length */
o->length = unbackslash(pattern) - o->data;
debug_printf_glob("glob pattern '%s' is literal\n", pattern);
return o_save_ptr_helper(o, n);
}
copy = xstrdup(pattern);
/* "forget" pattern in o */
o->length = pattern - o->data;
n = glob_brace(copy, o, n);
free(copy);
if (DEBUG_GLOB)
debug_print_list("o_glob returning", o, n);
return n;
}
#else /* !HUSH_BRACE_EXP */
/* Helper */
static int glob_needed(const char *s)
{
while (*s) {
if (*s == '\\') {
if (!s[1])
return 0;
s += 2;
continue;
}
if (*s == '*' || *s == '[' || *s == '?')
return 1;
s++;
}
return 0;
}
/* Performs globbing on last list[],
* saving each result as a new list[].
*/
static int o_glob(o_string *o, int n)
{
glob_t globdata;
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:
/* unbackslash last string in o in place, fix length */
o->length = unbackslash(pattern) - o->data;
debug_printf_glob("glob pattern '%s' is literal\n", pattern);
return o_save_ptr_helper(o, n);
}
memset(&globdata, 0, sizeof(globdata));
/* Can't use GLOB_NOCHECK: it does not unescape the string.
* If we glob "*.\*" and don't find anything, we need
* to fall back to using literal "*.*", but GLOB_NOCHECK
* will return "*.\*"!
*/
gr = glob(pattern, 0, NULL, &globdata);
debug_printf_glob("glob('%s'):%d\n", pattern, gr);
if (gr != 0) {
if (gr == GLOB_NOMATCH) {
globfree(&globdata);
goto literal;
}
if (gr == GLOB_NOSPACE)
bb_error_msg_and_die(bb_msg_memory_exhausted);
/* GLOB_ABORTED? Only happens with GLOB_ERR flag,
* but we didn't specify it. Paranoia again. */
bb_error_msg_and_die("glob error %d on '%s'", gr, pattern);
}
if (globdata.gl_pathv && globdata.gl_pathv[0]) {
char **argv = globdata.gl_pathv;
/* "forget" pattern in o */
o->length = pattern - o->data;
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;
}
#endif /* !HUSH_BRACE_EXP */
/* 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;
}
#if ENABLE_SH_MATH_SUPPORT
static arith_t expand_and_evaluate_arith(const char *arg, int *errcode_p)
{
arith_eval_hooks_t hooks;
arith_t res;
char *exp_str;
hooks.lookupvar = get_local_var_value;
hooks.setvar = set_local_var_from_halves;
hooks.endofname = endofname;
exp_str = expand_pseudo_dquoted(arg);
res = arith(exp_str ? exp_str : arg, errcode_p, &hooks);
free(exp_str);
return res;
}
#endif
/* Expand all variable references in given string, adding words to list[]
* at n, n+1,... positions. Return updated n (so that list[n] is next one
* to be filled). This routine is extremely tricky: has to deal with
* variables/parameters with whitespace, $* and $@, and constructs like
* 'echo -$*-'. If you play here, you must run testsuite afterwards! */
static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
{
/* or_mask is either 0 (normal case) or 0x80 -
* expansion of right-hand side of assignment == 1-element expand.
* It will also do no globbing, and thus we must not backslash-quote!
*/
char ored_ch;
char *p;
ored_ch = 0;
debug_printf_expand("expand_vars_to_list: arg:'%s' or_mask:%x\n", arg, or_mask);
debug_print_list("expand_vars_to_list", output, n);
n = o_save_ptr(output, n);
debug_print_list("expand_vars_to_list[0]", output, n);
while ((p = strchr(arg, SPECIAL_VAR_SYMBOL)) != NULL) {
char first_ch;
int i;
char *to_be_freed = NULL;
const char *val = 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;
switch (first_ch & 0x7f) {
/* Highest bit in first_ch indicates that var is double-quoted */
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++;
/* Can't just stuff it into output o_string,
* expanded result may need to be globbed
* and $IFS-splitted */
debug_printf_subst("SUBST '%s' first_ch %x\n", arg, first_ch);
G.last_exitcode = process_command_subs(&subst_result, arg);
debug_printf_subst("SUBST RES:%d '%s'\n", G.last_exitcode, subst_result.data);
val = subst_result.data;
goto store_val;
#endif
#if ENABLE_SH_MATH_SUPPORT
case '+': { /* <SPECIAL_VAR_SYMBOL>+cmd<SPECIAL_VAR_SYMBOL> */
arith_t res;
int errcode;
arg++; /* skip '+' */
*p = '\0'; /* replace trailing <SPECIAL_VAR_SYMBOL> */
debug_printf_subst("ARITH '%s' first_ch %x\n", arg, first_ch);
res = expand_and_evaluate_arith(arg, &errcode);
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> */
char *var;
char first_char;
char exp_op;
char exp_save = exp_save; /* for compiler */
char *exp_saveptr; /* points to expansion operator */
char *exp_word = exp_word; /* for compiler */
var = arg;
*p = '\0';
exp_saveptr = arg[1] ? strchr("%#:-=+?", arg[1]) : NULL;
first_char = arg[0] = first_ch & 0x7f;
exp_op = 0;
if (first_char == '#' && arg[1] && !exp_saveptr) {
/* handle length expansion ${#var} */
var++;
exp_op = 'L';
} else {
/* maybe handle parameter expansion */
if (exp_saveptr /* if 2nd char is one of expansion operators */
&& strchr(NUMERIC_SPECVARS_STR, first_char) /* 1st char is special variable */
) {
/* ${?:0}, ${#[:]%0} etc */
exp_saveptr = var + 1;
} else {
/* ${?}, ${var}, ${var:0}, ${var[:]%0} etc */
exp_saveptr = var+1 + strcspn(var+1, "%#:-=+?");
}
exp_op = exp_save = *exp_saveptr;
if (exp_op) {
exp_word = exp_saveptr + 1;
if (exp_op == ':') {
exp_op = *exp_word++;
if (ENABLE_HUSH_BASH_COMPAT
&& (exp_op == '\0' || !strchr("%#:-=+?"+3, exp_op))
) {
/* oops... it's ${var:N[:M]}, not ${var:?xxx} or some such */
exp_op = ':';
exp_word--;
}
}
*exp_saveptr = '\0';
} /* else: it's not an expansion op, but bare ${var} */
}
/* lookup the variable in question */
if (isdigit(var[0])) {
/* parse_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 {
switch (var[0]) {
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 */
val = utoa(G.global_argc ? G.global_argc-1 : 0);
break;
default:
val = get_local_var_value(var);
}
}
/* handle any expansions */
if (exp_op == 'L') {
debug_printf_expand("expand: length(%s)=", val);
val = utoa(val ? strlen(val) : 0);
debug_printf_expand("%s\n", val);
} else if (exp_op) {
if (exp_op == '%' || exp_op == '#') {
/* Standard-mandated substring removal ops:
* ${parameter%word} - remove smallest suffix pattern
* ${parameter%%word} - remove largest suffix pattern
* ${parameter#word} - remove smallest prefix pattern
* ${parameter##word} - remove largest prefix pattern
*
* Word is expanded to produce a glob pattern.
* Then var's value is matched to it and matching part removed.
*/
if (val) {
bool match_at_left;
char *loc;
scan_t scan = pick_scan(exp_op, *exp_word, &match_at_left);
if (exp_op == *exp_word) /* ## or %% */
exp_word++;
val = to_be_freed = xstrdup(val);
{
char *exp_exp_word = expand_pseudo_dquoted(exp_word);
if (exp_exp_word)
exp_word = exp_exp_word;
loc = scan(to_be_freed, exp_word, match_at_left);
//bb_error_msg("op:%c str:'%s' pat:'%s' res:'%s'",
// exp_op, to_be_freed, exp_word, loc);
free(exp_exp_word);
}
if (loc) { /* match was found */
if (match_at_left) /* # or ## */
val = loc;
else /* % or %% */
*loc = '\0';
}
}
} else if (exp_op == ':') {
#if ENABLE_HUSH_BASH_COMPAT && ENABLE_SH_MATH_SUPPORT
/* It's ${var:N[:M]} bashism.
* Note that in encoded form it has TWO parts:
* var:N<SPECIAL_VAR_SYMBOL>M<SPECIAL_VAR_SYMBOL>
*/
arith_t beg, len;
int errcode = 0;
beg = expand_and_evaluate_arith(exp_word, &errcode);
debug_printf_varexp("beg:'%s'=%lld\n", exp_word, (long long)beg);
*p++ = SPECIAL_VAR_SYMBOL;
exp_word = p;
p = strchr(p, SPECIAL_VAR_SYMBOL);
*p = '\0';
len = expand_and_evaluate_arith(exp_word, &errcode);
debug_printf_varexp("len:'%s'=%lld\n", exp_word, (long long)len);
if (errcode >= 0 && len >= 0) { /* bash compat: len < 0 is illegal */
if (beg < 0) /* bash compat */
beg = 0;
debug_printf_varexp("from val:'%s'\n", val);
if (len == 0 || !val || beg >= strlen(val))
val = "";
else {
/* Paranoia. What if user entered 9999999999999
* which fits in arith_t but not int? */
if (len >= INT_MAX)
len = INT_MAX;
val = to_be_freed = xstrndup(val + beg, len);
}
debug_printf_varexp("val:'%s'\n", val);
} else
#endif
{
die_if_script("malformed ${%s:...}", var);
val = "";
}
} else { /* one of "-=+?" */
/* Standard-mandated substitution ops:
* ${var?word} - indicate error if unset
* If var is unset, word (or a message indicating it is unset
* if word is null) is written to standard error
* and the shell exits with a non-zero exit status.
* Otherwise, the value of var is substituted.
* ${var-word} - use default value
* If var is unset, word is substituted.
* ${var=word} - assign and use default value
* If var is unset, word is assigned to var.
* In all cases, final value of var is substituted.
* ${var+word} - use alternative value
* If var is unset, null is substituted.
* Otherwise, word is substituted.
*
* Word is subjected to tilde expansion, parameter expansion,
* command substitution, and arithmetic expansion.
* If word is not needed, it is not expanded.
*
* Colon forms (${var:-word}, ${var:=word} etc) do the same,
* but also treat null var as if it is unset.
*/
int use_word = (!val || ((exp_save == ':') && !val[0]));
if (exp_op == '+')
use_word = !use_word;
debug_printf_expand("expand: op:%c (null:%s) test:%i\n", exp_op,
(exp_save == ':') ? "true" : "false", use_word);
if (use_word) {
to_be_freed = expand_pseudo_dquoted(exp_word);
if (to_be_freed)
exp_word = to_be_freed;
if (exp_op == '?') {
/* mimic bash message */
die_if_script("%s: %s",
var,
exp_word[0] ? exp_word : "parameter null or not set"
);
//TODO: how interactive bash aborts expansion mid-command?
} else {
val = exp_word;
}
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, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
}
}
}
} /* one of "-=+?" */
*exp_saveptr = exp_save;
} /* if (exp_op) */
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(to_be_freed);
/* 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;
}
enum {
EXPVAR_FLAG_GLOB = 0x200,
EXPVAR_FLAG_ESCAPE_VARS = 0x100,
EXPVAR_FLAG_SINGLEWORD = 0x80, /* must be 0x80 */
};
static char **expand_variables(char **argv, unsigned or_mask)
{
int n;
char **list;
char **v;
o_string output = NULL_O_STRING;
/* protect against globbing for "$var"? */
/* (unquoted $var will temporarily switch it off) */
output.o_escape = 1 & (or_mask / EXPVAR_FLAG_ESCAPE_VARS);
output.o_glob = 1 & (or_mask / EXPVAR_FLAG_GLOB);
n = 0;
v = argv;
while (*v) {
n = expand_vars_to_list(&output, n, *v, (unsigned 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, EXPVAR_FLAG_GLOB | EXPVAR_FLAG_ESCAPE_VARS);
}
#if ENABLE_HUSH_BASH_COMPAT
static char **expand_strvec_to_strvec_singleword_noglob(char **argv)
{
return expand_variables(argv, EXPVAR_FLAG_SINGLEWORD);
}
#endif
#ifdef CMD_SINGLEWORD_NOGLOB_COND
static char **expand_strvec_to_strvec_singleword_noglob_cond(char **argv)
{
int n;
char **list;
char **v;
o_string output = NULL_O_STRING;
n = 0;
v = argv;
while (*v) {
int is_var = is_well_formed_var_name(*v, '=');
/* is_var * 0x80: singleword expansion for vars */
n = expand_vars_to_list(&output, n, *v, is_var * 0x80);
/* Subtle! expand_vars_to_list did not glob last word yet.
* It does this only when fed with further data.
* Therefore we set globbing flags AFTER it, not before:
*/
/* if it is not recognizably abc=...; then: */
output.o_escape = !is_var; /* protect against globbing for "$var" */
/* (unquoted $var will temporarily switch it off) */
output.o_glob = !is_var; /* and indeed do globbing */
v++;
}
debug_print_list("expand_cond", &output, n);
/* output.data (malloced in one block) gets returned in "list" */
list = o_finalize_list(&output, n);
debug_print_strings("expand_cond[1]", list);
return list;
}
#endif
/* Used for expansion of right hand of assignments */
/* NB: should NOT do globbing!
* "export v=/bin/c*; env | grep ^v=" outputs "v=/bin/c*" */
static char *expand_string_to_string(const char *str)
{
char *argv[2], **list;
argv[0] = (char*)str;
argv[1] = NULL;
list = expand_variables(argv, EXPVAR_FLAG_ESCAPE_VARS | EXPVAR_FLAG_SINGLEWORD);
if (HUSH_DEBUG)
if (!list[0] || list[1])
bb_error_msg_and_die("BUG in varexp2");
/* actually, just move string 2*sizeof(char*) bytes back */
overlapping_strcpy((char*)list, list[0]);
unbackslash((char*)list);
debug_printf_expand("string_to_string='%s'\n", (char*)list);
return (char*)list;
}
/* Used for "eval" builtin */
static char* expand_strvec_to_string(char **argv)
{
char **list;
list = expand_variables(argv, EXPVAR_FLAG_SINGLEWORD);
/* 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");
/* bash uses ' ' regardless of $IFS contents */
list[n][-1] = ' ';
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(char ***to_free, const char *s,
char *g_argv0, char **g_argv,
char **builtin_argv) NORETURN;
static void reset_traps_to_defaults(void)
{
/* This function is always called in a child shell
* after fork (not vfork, NOMMU doesn't use this function).
*/
unsigned sig;
unsigned mask;
/* Child shells are not interactive.
* SIGTTIN/SIGTTOU/SIGTSTP should not have special handling.
* Testcase: (while :; do :; done) + ^Z should background.
* Same goes for SIGTERM, SIGHUP, SIGINT.
*/
if (!G.traps && !(G.non_DFL_mask & SPECIAL_INTERACTIVE_SIGS))
return; /* already no traps and no SPECIAL_INTERACTIVE_SIGS */
/* Switching off SPECIAL_INTERACTIVE_SIGS.
* Stupid. It can be done with *single* &= op, but we can't use
* the fact that G.blocked_set is implemented as a bitmask
* in libc... */
mask = (SPECIAL_INTERACTIVE_SIGS >> 1);
sig = 1;
while (1) {
if (mask & 1) {
/* Careful. Only if no trap or trap is not "" */
if (!G.traps || !G.traps[sig] || G.traps[sig][0])
sigdelset(&G.blocked_set, sig);
}
mask >>= 1;
if (!mask)
break;
sig++;
}
/* Our homegrown sig mask is saner to work with :) */
G.non_DFL_mask &= ~SPECIAL_INTERACTIVE_SIGS;
/* Resetting all traps to default except empty ones */
mask = G.non_DFL_mask;
if (G.traps) for (sig = 0; sig < NSIG; sig++, mask >>= 1) {
if (!G.traps[sig] || !G.traps[sig][0])
continue;
free(G.traps[sig]);
G.traps[sig] = NULL;
/* There is no signal for 0 (EXIT) */
if (sig == 0)
continue;
/* There was a trap handler, we just removed it.
* But if sig still has non-DFL handling,
* we should not unblock the sig. */
if (mask & 1)
continue;
sigdelset(&G.blocked_set, sig);
}
sigprocmask(SIG_SETMASK, &G.blocked_set, NULL);
}
#else /* !BB_MMU */
static void re_execute_shell(char ***to_free, const char *s,
char *g_argv0, char **g_argv,
char **builtin_argv) NORETURN;
static void re_execute_shell(char ***to_free, const char *s,
char *g_argv0, char **g_argv,
char **builtin_argv)
{
# define NOMMU_HACK_FMT ("-$%x:%x:%x:%x:%x:%llx" IF_HUSH_LOOPS(":%x"))
/* delims + 2 * (number of bytes in printed hex numbers) */
char param_buf[sizeof(NOMMU_HACK_FMT) + 2 * (sizeof(int)*6 + sizeof(long long)*1)];
char *heredoc_argv[4];
struct variable *cur;
# if ENABLE_HUSH_FUNCTIONS
struct function *funcp;
# endif
char **argv, **pp;
unsigned cnt;
unsigned long long empty_trap_mask;
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;
}
cnt = 0;
pp = builtin_argv;
if (pp) while (*pp++)
cnt++;
empty_trap_mask = 0;
if (G.traps) {
int sig;
for (sig = 1; sig < NSIG; sig++) {
if (G.traps[sig] && !G.traps[sig][0])
empty_trap_mask |= 1LL << sig;
}
}
sprintf(param_buf, NOMMU_HACK_FMT
, (unsigned) G.root_pid
, (unsigned) G.root_ppid
, (unsigned) G.last_bg_pid
, (unsigned) G.last_exitcode
, cnt
, empty_trap_mask
IF_HUSH_LOOPS(, G.depth_of_loop)
);
# undef NOMMU_HACK_FMT
/* 1:hush 2:-$<pid>:<pid>:<exitcode>:<etc...> <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++;
*to_free = 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
*
*