blob: cab7ea5b02a0d50c6e98dceecd82a8b808f57aa8 [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>
*
* Licensed under GPLv2 or later, see file LICENSE in this source tree.
*
* 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)
* make complex ${var%...} constructs support optional
* make here documents optional
* special variables (done: PWD, PPID, RANDOM)
* follow IFS rules more precisely, including update semantics
* tilde expansion
* aliases
* "command" missing features:
* command -p CMD: run CMD using default $PATH
* (can use this to override standalone shell as well?)
* command BLTIN: disables special-ness (e.g. errors do not abort)
* command -V CMD1 CMD2 CMD3 (multiple args) (not in standard)
* builtins mandated by standards we don't support:
* [un]alias, fc:
* fc -l[nr] [BEG] [END]: list range of commands in history
* fc [-e EDITOR] [BEG] [END]: edit/rerun range of commands
* fc -s [PAT=REP] [CMD]: rerun CMD, replacing PAT with REP
*
* Bash compat TODO:
* redirection of stdout+stderr: &> and >&
* 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))
* indirect expansion: ${!VAR}
* substring op on @: ${@:n:m}
*
* Won't do:
* Some builtins mandated by standards:
* newgrp [GRP]: not a builtin in bash but a suid binary
* which spawns a new shell with new group ID
*
* Status of [[ support:
* [[ args ]] are CMD_SINGLEWORD_NOGLOB:
* v='a b'; [[ $v = 'a b' ]]; echo 0:$?
* [[ /bin/n* ]]; echo 0:$?
* TODO:
* &&/|| are AND/OR ops, -a/-o are not
* quoting needs to be considered (-f is an operator, "-f" and ""-f are not; etc)
* = is glob match operator, not equality operator: STR = GLOB
* (in GLOB, quoting is significant on char-by-char basis: a*cd"*")
* == same as =
* add =~ regex match operator: STR =~ REGEX
*/
//config:config HUSH
//config: bool "hush (68 kb)"
//config: default y
//config: help
//config: hush is a small shell. It handles the normal flow control
//config: constructs such as if/then/elif/else/fi, for/in/do/done, while loops,
//config: case/esac. Redirections, here documents, $((arithmetic))
//config: and functions are supported.
//config:
//config: It will compile and work on no-mmu systems.
//config:
//config: It does not handle select, aliases, tilde expansion,
//config: &>file and >&file redirection of stdout+stderr.
//config:
//config:config HUSH_BASH_COMPAT
//config: bool "bash-compatible extensions"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_BRACE_EXPANSION
//config: bool "Brace expansion"
//config: default y
//config: depends on HUSH_BASH_COMPAT
//config: help
//config: Enable {abc,def} extension.
//config:
//config:config HUSH_LINENO_VAR
//config: bool "$LINENO variable"
//config: default y
//config: depends on HUSH_BASH_COMPAT
//config:
//config:config HUSH_BASH_SOURCE_CURDIR
//config: bool "'source' and '.' builtins search current directory after $PATH"
//config: default n # do not encourage non-standard behavior
//config: depends on HUSH_BASH_COMPAT
//config: help
//config: This is not compliant with standards. Avoid if possible.
//config:
//config:config HUSH_INTERACTIVE
//config: bool "Interactive mode"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config: help
//config: Enable interactive mode (prompt and command editing).
//config: Without this, hush simply reads and executes commands
//config: from stdin just like a shell script from a file.
//config: No prompt, no PS1/PS2 magic shell variables.
//config:
//config:config HUSH_SAVEHISTORY
//config: bool "Save command history to .hush_history"
//config: default y
//config: depends on HUSH_INTERACTIVE && FEATURE_EDITING_SAVEHISTORY
//config:
//config:config HUSH_JOB
//config: bool "Job control"
//config: default y
//config: depends on HUSH_INTERACTIVE
//config: help
//config: Enable job control: Ctrl-Z backgrounds, Ctrl-C interrupts current
//config: command (not entire shell), fg/bg builtins work. Without this option,
//config: "cmd &" still works by simply spawning a process and immediately
//config: prompting for next command (or executing next command in a script),
//config: but no separate process group is formed.
//config:
//config:config HUSH_TICK
//config: bool "Support command substitution"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config: help
//config: Enable `command` and $(command).
//config:
//config:config HUSH_IF
//config: bool "Support if/then/elif/else/fi"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_LOOPS
//config: bool "Support for, while and until loops"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_CASE
//config: bool "Support case ... esac statement"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config: help
//config: Enable case ... esac statement. +400 bytes.
//config:
//config:config HUSH_FUNCTIONS
//config: bool "Support funcname() { commands; } syntax"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config: help
//config: Enable support for shell functions. +800 bytes.
//config:
//config:config HUSH_LOCAL
//config: bool "local builtin"
//config: default y
//config: depends on HUSH_FUNCTIONS
//config: help
//config: Enable support for local variables in functions.
//config:
//config:config HUSH_RANDOM_SUPPORT
//config: bool "Pseudorandom generator and $RANDOM variable"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config: help
//config: Enable pseudorandom generator and dynamic variable "$RANDOM".
//config: Each read of "$RANDOM" will generate a new pseudorandom value.
//config:
//config:config HUSH_MODE_X
//config: bool "Support 'hush -x' option and 'set -x' command"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config: help
//config: This instructs hush to print commands before execution.
//config: Adds ~300 bytes.
//config:
//config:config HUSH_ECHO
//config: bool "echo builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_PRINTF
//config: bool "printf builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_TEST
//config: bool "test builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_HELP
//config: bool "help builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_EXPORT
//config: bool "export builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_EXPORT_N
//config: bool "Support 'export -n' option"
//config: default y
//config: depends on HUSH_EXPORT
//config: help
//config: export -n unexports variables. It is a bash extension.
//config:
//config:config HUSH_READONLY
//config: bool "readonly builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config: help
//config: Enable support for read-only variables.
//config:
//config:config HUSH_KILL
//config: bool "kill builtin (supports kill %jobspec)"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_WAIT
//config: bool "wait builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_COMMAND
//config: bool "command builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_TRAP
//config: bool "trap builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_TYPE
//config: bool "type builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_TIMES
//config: bool "times builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_READ
//config: bool "read builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_SET
//config: bool "set builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_UNSET
//config: bool "unset builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_ULIMIT
//config: bool "ulimit builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_UMASK
//config: bool "umask builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_GETOPTS
//config: bool "getopts builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_MEMLEAK
//config: bool "memleak builtin (debugging)"
//config: default n
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//applet:IF_HUSH(APPLET(hush, BB_DIR_BIN, BB_SUID_DROP))
// APPLET_ODDNAME:name main location suid_type help
//applet:IF_SH_IS_HUSH( APPLET_ODDNAME(sh, hush, BB_DIR_BIN, BB_SUID_DROP, hush))
//applet:IF_BASH_IS_HUSH(APPLET_ODDNAME(bash, hush, BB_DIR_BIN, BB_SUID_DROP, hush))
//kbuild:lib-$(CONFIG_HUSH) += hush.o match.o shell_common.o
//kbuild:lib-$(CONFIG_SH_IS_HUSH) += hush.o match.o shell_common.o
//kbuild:lib-$(CONFIG_BASH_IS_HUSH) += hush.o match.o shell_common.o
//kbuild:lib-$(CONFIG_HUSH_RANDOM_SUPPORT) += random.o
/* -i (interactive) is also accepted,
* but does nothing, therefore not shown in help.
* NOMMU-specific options are not meant to be used by users,
* therefore we don't show them either.
*/
//usage:#define hush_trivial_usage
//usage: "[-enxl] [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS] / -s [ARGS]]"
//usage:#define hush_full_usage "\n\n"
//usage: "Unix shell interpreter"
#if !(defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) \
|| defined(__APPLE__) \
)
# include <malloc.h> /* for malloc_trim */
#endif
#include <glob.h>
/* #include <dmalloc.h> */
#if ENABLE_HUSH_CASE
# include <fnmatch.h>
#endif
#include <sys/times.h>
#include <sys/utsname.h> /* for setting $HOSTNAME */
#include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */
#include "unicode.h"
#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 O_CLOEXEC
# define O_CLOEXEC 0
#endif
#ifndef F_DUPFD_CLOEXEC
# define F_DUPFD_CLOEXEC F_DUPFD
#endif
#if ENABLE_FEATURE_SH_EMBEDDED_SCRIPTS && !(ENABLE_ASH || ENABLE_SH_IS_ASH || ENABLE_BASH_IS_ASH)
# include "embedded_scripts.h"
#else
# define NUM_SCRIPTS 0
#endif
/* So far, all bash compat is controlled by one config option */
/* Separate defines document which part of code implements what */
#define BASH_PATTERN_SUBST ENABLE_HUSH_BASH_COMPAT
#define BASH_SUBSTR ENABLE_HUSH_BASH_COMPAT
#define BASH_SOURCE ENABLE_HUSH_BASH_COMPAT
#define BASH_HOSTNAME_VAR ENABLE_HUSH_BASH_COMPAT
#define BASH_EPOCH_VARS ENABLE_HUSH_BASH_COMPAT
#define BASH_TEST2 (ENABLE_HUSH_BASH_COMPAT && ENABLE_HUSH_TEST)
#define BASH_READ_D ENABLE_HUSH_BASH_COMPAT
/* 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
# undef ENABLE_FEATURE_EDITING_SAVE_ON_EXIT
# define ENABLE_FEATURE_EDITING_SAVE_ON_EXIT 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_printf_heredoc(...) 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_redir(...) do {} while (0)
#define debug_printf_list(...) do {} while (0)
#define debug_printf_subst(...) do {} while (0)
#define debug_printf_prompt(...) do {} while (0)
#define debug_printf_clean(...) do {} while (0)
#define ERR_PTR ((void*)(long)1)
#define JOB_STATUS_FORMAT "[%u] %-22s %.40s\n"
#define _SPECIAL_VARS_STR "_*@$!?#-"
#define SPECIAL_VARS_STR ("_*@$!?#-" + 1)
#define NUMERIC_SPECVARS_STR ("_*@$!?#-" + 3)
#if BASH_PATTERN_SUBST
/* Support / and // replace ops */
/* Note that // is stored as \ in "encoded" string representation */
# define VAR_ENCODED_SUBST_OPS "\\/%#:-=+?"
# define VAR_SUBST_OPS ("\\/%#:-=+?" + 1)
# define MINUS_PLUS_EQUAL_QUESTION ("\\/%#:-=+?" + 5)
#else
# define VAR_ENCODED_SUBST_OPS "%#:-=+?"
# define VAR_SUBST_OPS "%#:-=+?"
# define MINUS_PLUS_EQUAL_QUESTION ("%#:-=+?" + 3)
#endif
#define SPECIAL_VAR_SYMBOL_STR "\3"
#define SPECIAL_VAR_SYMBOL 3
/* The "variable" with name "\1" emits string "\3". Testcase: "echo ^C" */
#define SPECIAL_VAR_QUOTED_SVS 1
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 {
struct variable *old_vars;
char **argv;
char **argv_from_re_execing;
} nommu_save_t;
#endif
enum {
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
};
typedef struct o_string {
char *data;
int length; /* position where data is appended */
int maxlen;
int o_expflags;
/* At least some part of the string was inside '' or "",
* possibly empty one: word"", wo''rd etc. */
smallint has_quoted_part;
smallint has_empty_slot;
smallint ended_in_ifs;
} o_string;
enum {
EXP_FLAG_SINGLEWORD = 0x80, /* must be 0x80 */
EXP_FLAG_GLOB = 0x2,
/* Protect newly added chars against globbing
* by prepending \ to *, ?, [, \ */
EXP_FLAG_ESC_GLOB_CHARS = 0x1,
};
/* Used for initialization: o_string foo = NULL_O_STRING; */
#define NULL_O_STRING { NULL }
#ifndef debug_printf_parse
static const char *const assignment_flag[] = {
"MAYBE_ASSIGNMENT",
"DEFINITELY_ASSIGNMENT",
"NOT_ASSIGNMENT",
"WORD_IS_KEYWORD",
};
#endif
/* We almost can use standard FILE api, but we need an ability to move
* its fd when redirects coincide with it. No api exists for that
* (RFE for it at https://sourceware.org/bugzilla/show_bug.cgi?id=21902).
* HFILE is our internal alternative. Only supports reading.
* Since we now can, we incorporate linked list of all opened HFILEs
* into the struct (used to be a separate mini-list).
*/
typedef struct HFILE {
char *cur;
char *end;
struct HFILE *next_hfile;
int fd;
char buf[1024];
} HFILE;
typedef struct in_str {
const char *p;
int peek_buf[2];
int last_char;
HFILE *file;
} in_str;
/* 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 */
unsigned assignment_cnt; /* how many argv[i] are assignments? */
#if ENABLE_HUSH_LINENO_VAR
unsigned lineno;
#endif
smallint cmd_type; /* CMD_xxx */
#define CMD_NORMAL 0
#define CMD_SUBSHELL 1
#if BASH_TEST2 || ENABLE_HUSH_LOCAL || ENABLE_HUSH_EXPORT || ENABLE_HUSH_READONLY
/* used for "[[ EXPR ]]", and to prevent word splitting and globbing in
* "export v=t*"
*/
# define CMD_SINGLEWORD_NOGLOB 2
#endif
#if ENABLE_HUSH_FUNCTIONS
# define CMD_FUNCDEF 3
#endif
smalluint cmd_exitcode;
/* 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
unsigned 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 = 0,
PIPE_AND = 1,
PIPE_OR = 2,
PIPE_BG = 3,
} 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;
o_string word;
#if !BB_MMU
o_string as_string;
#endif
smallint is_assignment; /* 0:maybe, 1:yes, 2:no, 3:keyword */
#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
};
enum {
MAYBE_ASSIGNMENT = 0,
DEFINITELY_ASSIGNMENT = 1,
NOT_ASSIGNMENT = 2,
/* Not an assignment, but next word may be: "if v=xyz cmd;" */
WORD_IS_KEYWORD = 3,
};
/* 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 */
uint16_t var_nest_level;
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
/* set -/+o OPT support. (TODO: make it optional)
* bash supports the following opts:
* allexport off
* braceexpand on
* emacs on
* errexit off
* errtrace off
* functrace off
* hashall on
* histexpand off
* history on
* ignoreeof off
* interactive-comments on
* keyword off
* monitor on
* noclobber off
* noexec off
* noglob off
* nolog off
* notify off
* nounset off
* onecmd off
* physical off
* pipefail off
* posix off
* privileged off
* verbose off
* vi off
* xtrace off
*/
static const char o_opt_strings[] ALIGN1 =
"pipefail\0"
"noexec\0"
"errexit\0"
#if ENABLE_HUSH_MODE_X
"xtrace\0"
#endif
;
enum {
OPT_O_PIPEFAIL,
OPT_O_NOEXEC,
OPT_O_ERREXIT,
#if ENABLE_HUSH_MODE_X
OPT_O_XTRACE,
#endif
NUM_OPT_O
};
/* "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;
IF_NOT_FEATURE_EDITING_FANCY_PROMPT(char *PS1;)
# 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;
unsigned 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
/* How deeply are we in context where "set -e" is ignored */
int errexit_depth;
/* "set -e" rules (do we follow them correctly?):
* Exit if pipe, list, or compound command exits with a non-zero status.
* Shell does not exit if failed command is part of condition in
* if/while, part of && or || list except the last command, any command
* in a pipe but the last, or if the command's return value is being
* inverted with !. If a compound command other than a subshell returns a
* non-zero status because a command failed while -e was being ignored, the
* shell does not exit. A trap on ERR, if set, is executed before the shell
* exits [ERR is a bashism].
*
* If a compound command or function executes in a context where -e is
* ignored, none of the commands executed within are affected by the -e
* setting. If a compound command or function sets -e while executing in a
* context where -e is ignored, that setting does not have any effect until
* the compound command or the command containing the function call completes.
*/
char o_opt[NUM_OPT_O];
#if ENABLE_HUSH_MODE_X
# define G_x_mode (G.o_opt[OPT_O_XTRACE])
#else
# define G_x_mode 0
#endif
char opt_s;
char opt_c;
#if ENABLE_HUSH_INTERACTIVE
smallint promptmode; /* 0: PS1, 1: PS2 */
#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;
# define G_flag_return_in_progress (G.flag_return_in_progress)
#else
# define G_flag_return_in_progress 0
#endif
smallint exiting; /* used to prevent EXIT trap recursion */
/* These support $? */
smalluint last_exitcode;
smalluint expand_exitcode;
smalluint last_bg_pid_exitcode;
#if ENABLE_HUSH_SET
/* are global_argv and global_argv[1..n] malloced? (note: not [0]) */
smalluint global_args_malloced;
# define G_global_args_malloced (G.global_args_malloced)
#else
# define G_global_args_malloced 0
#endif
#if ENABLE_HUSH_BASH_COMPAT
int dead_job_exitcode; /* for "wait -n" */
#endif
/* 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
#if ENABLE_HUSH_GETOPTS
unsigned getopt_count;
#endif
const char *ifs;
char *ifs_whitespace; /* = G.ifs or malloced */
const char *cwd;
struct variable *top_var;
char **expanded_assignments;
struct variable **shadowed_vars_pp;
unsigned var_nest_level;
#if ENABLE_HUSH_FUNCTIONS
# if ENABLE_HUSH_LOCAL
unsigned func_nest_level; /* solely to prevent "local v" in non-functions */
# endif
struct function *top_func;
#endif
/* Signal and trap handling */
#if ENABLE_HUSH_FAST
unsigned count_SIGCHLD;
unsigned handled_SIGCHLD;
smallint we_have_children;
#endif
#if ENABLE_HUSH_LINENO_VAR
unsigned parse_lineno;
unsigned execute_lineno;
#endif
HFILE *HFILE_list;
HFILE *HFILE_stdin;
/* Which signals have non-DFL handler (even with no traps set)?
* Set at the start to:
* (SIGQUIT + maybe SPECIAL_INTERACTIVE_SIGS + maybe SPECIAL_JOBSTOP_SIGS)
* SPECIAL_INTERACTIVE_SIGS are cleared after fork.
* The rest is cleared right before execv syscalls.
* Other than these two times, never modified.
*/
unsigned special_sig_mask;
#if ENABLE_HUSH_JOB
unsigned fatal_sig_mask;
# define G_fatal_sig_mask (G.fatal_sig_mask)
#else
# define G_fatal_sig_mask 0
#endif
#if ENABLE_HUSH_TRAP
int pre_trap_exitcode;
# if ENABLE_HUSH_FUNCTIONS
int return_exitcode;
# endif
char **traps; /* char *traps[NSIG] */
# define G_traps G.traps
#else
# define G_traps ((char**)NULL)
#endif
sigset_t pending_set;
#if ENABLE_HUSH_MEMLEAK
unsigned long memleak_value;
#endif
#if ENABLE_HUSH_MODE_X
unsigned x_mode_depth;
/* "set -x" output should not be redirectable with subsequent 2>FILE.
* We dup fd#2 to x_mode_fd when "set -x" is executed, and use it
* for all subsequent output.
*/
int x_mode_fd;
o_string x_mode_buf;
#endif
#if HUSH_DEBUG >= 2
int debug_indent;
#endif
struct sigaction sa;
char optstring_buf[sizeof("eixcs")];
#if BASH_EPOCH_VARS
char epoch_buf[sizeof("%lu.nnnnnn") + sizeof(long)*3];
#endif
#if ENABLE_FEATURE_EDITING
char user_input_buf[CONFIG_FEATURE_EDITING_MAX_LEN];
#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))); \
/* memset(&G.sa, 0, sizeof(G.sa)); */ \
sigfillset(&G.sa.sa_mask); \
G.sa.sa_flags = SA_RESTART; \
} while (0)
/* Function prototypes for builtins */
static int builtin_cd(char **argv) FAST_FUNC;
#if ENABLE_HUSH_ECHO
static int builtin_echo(char **argv) FAST_FUNC;
#endif
static int builtin_eval(char **argv) FAST_FUNC;
static int builtin_exec(char **argv) FAST_FUNC;
static int builtin_exit(char **argv) FAST_FUNC;
#if ENABLE_HUSH_EXPORT
static int builtin_export(char **argv) FAST_FUNC;
#endif
#if ENABLE_HUSH_READONLY
static int builtin_readonly(char **argv) FAST_FUNC;
#endif
#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_GETOPTS
static int builtin_getopts(char **argv) FAST_FUNC;
#endif
#if ENABLE_HUSH_HELP
static int builtin_help(char **argv) FAST_FUNC;
#endif
#if MAX_HISTORY && ENABLE_FEATURE_EDITING
static int builtin_history(char **argv) FAST_FUNC;
#endif
#if ENABLE_HUSH_LOCAL
static int builtin_local(char **argv) FAST_FUNC;
#endif
#if ENABLE_HUSH_MEMLEAK
static int builtin_memleak(char **argv) FAST_FUNC;
#endif
#if ENABLE_HUSH_PRINTF
static int builtin_printf(char **argv) FAST_FUNC;
#endif
static int builtin_pwd(char **argv) FAST_FUNC;
#if ENABLE_HUSH_READ
static int builtin_read(char **argv) FAST_FUNC;
#endif
#if ENABLE_HUSH_SET
static int builtin_set(char **argv) FAST_FUNC;
#endif
static int builtin_shift(char **argv) FAST_FUNC;
static int builtin_source(char **argv) FAST_FUNC;
#if ENABLE_HUSH_TEST || BASH_TEST2
static int builtin_test(char **argv) FAST_FUNC;
#endif
#if ENABLE_HUSH_TRAP
static int builtin_trap(char **argv) FAST_FUNC;
#endif
#if ENABLE_HUSH_TYPE
static int builtin_type(char **argv) FAST_FUNC;
#endif
#if ENABLE_HUSH_TIMES
static int builtin_times(char **argv) FAST_FUNC;
#endif
static int builtin_true(char **argv) FAST_FUNC;
#if ENABLE_HUSH_UMASK
static int builtin_umask(char **argv) FAST_FUNC;
#endif
#if ENABLE_HUSH_UNSET
static int builtin_unset(char **argv) FAST_FUNC;
#endif
#if ENABLE_HUSH_KILL
static int builtin_kill(char **argv) FAST_FUNC;
#endif
#if ENABLE_HUSH_WAIT
static int builtin_wait(char **argv) FAST_FUNC;
#endif
#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 file"),
BLTIN(":" , builtin_true , NULL),
#if ENABLE_HUSH_JOB
BLTIN("bg" , builtin_fg_bg , "Resume job in background"),
#endif
#if ENABLE_HUSH_LOOPS
BLTIN("break" , builtin_break , "Exit 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 , NULL),
#if ENABLE_HUSH_EXPORT
BLTIN("export" , builtin_export , "Set environment variables"),
#endif
#if ENABLE_HUSH_JOB
BLTIN("fg" , builtin_fg_bg , "Bring job to foreground"),
#endif
#if ENABLE_HUSH_GETOPTS
BLTIN("getopts" , builtin_getopts , NULL),
#endif
#if ENABLE_HUSH_HELP
BLTIN("help" , builtin_help , NULL),
#endif
#if MAX_HISTORY && ENABLE_FEATURE_EDITING
BLTIN("history" , builtin_history , "Show history"),
#endif
#if ENABLE_HUSH_JOB
BLTIN("jobs" , builtin_jobs , "List jobs"),
#endif
#if ENABLE_HUSH_KILL
BLTIN("kill" , builtin_kill , "Send signals to processes"),
#endif
#if ENABLE_HUSH_LOCAL
BLTIN("local" , builtin_local , "Set local variables"),
#endif
#if ENABLE_HUSH_MEMLEAK
BLTIN("memleak" , builtin_memleak , NULL),
#endif
#if ENABLE_HUSH_READ
BLTIN("read" , builtin_read , "Input into variable"),
#endif
#if ENABLE_HUSH_READONLY
BLTIN("readonly" , builtin_readonly, "Make variables read-only"),
#endif
#if ENABLE_HUSH_FUNCTIONS
BLTIN("return" , builtin_return , "Return from function"),
#endif
#if ENABLE_HUSH_SET
BLTIN("set" , builtin_set , "Set positional parameters"),
#endif
BLTIN("shift" , builtin_shift , "Shift positional parameters"),
#if BASH_SOURCE
BLTIN("source" , builtin_source , NULL),
#endif
#if ENABLE_HUSH_TIMES
BLTIN("times" , builtin_times , NULL),
#endif
#if ENABLE_HUSH_TRAP
BLTIN("trap" , builtin_trap , "Trap signals"),
#endif
BLTIN("true" , builtin_true , NULL),
#if ENABLE_HUSH_TYPE
BLTIN("type" , builtin_type , "Show command type"),
#endif
#if ENABLE_HUSH_ULIMIT
BLTIN("ulimit" , shell_builtin_ulimit, "Control resource limits"),
#endif
#if ENABLE_HUSH_UMASK
BLTIN("umask" , builtin_umask , "Set file creation mask"),
#endif
#if ENABLE_HUSH_UNSET
BLTIN("unset" , builtin_unset , "Unset variables"),
#endif
#if ENABLE_HUSH_WAIT
BLTIN("wait" , builtin_wait , "Wait for process to finish"),
#endif
};
/* These builtins won't be used if we are on NOMMU and need to re-exec
* (it's cheaper to run an external program in this case):
*/
static const struct built_in_command bltins2[] = {
#if ENABLE_HUSH_TEST
BLTIN("[" , builtin_test , NULL),
#endif
#if BASH_TEST2
BLTIN("[[" , builtin_test , NULL),
#endif
#if ENABLE_HUSH_ECHO
BLTIN("echo" , builtin_echo , NULL),
#endif
#if ENABLE_HUSH_PRINTF
BLTIN("printf" , builtin_printf , NULL),
#endif
BLTIN("pwd" , builtin_pwd , NULL),
#if ENABLE_HUSH_TEST
BLTIN("test" , builtin_test , NULL),
#endif
};
/* Debug printouts.
*/
#if HUSH_DEBUG >= 2
/* prevent disasters with G.debug_indent < 0 */
# define indent() fdprintf(2, "%*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(), fdprintf(2, __VA_ARGS__))
#endif
#ifndef debug_printf_parse
# define debug_printf_parse(...) (indent(), fdprintf(2, __VA_ARGS__))
#endif
#ifndef debug_printf_heredoc
# define debug_printf_heredoc(...) (indent(), fdprintf(2, __VA_ARGS__))
#endif
#ifndef debug_printf_exec
#define debug_printf_exec(...) (indent(), fdprintf(2, __VA_ARGS__))
#endif
#ifndef debug_printf_env
# define debug_printf_env(...) (indent(), fdprintf(2, __VA_ARGS__))
#endif
#ifndef debug_printf_jobs
# define debug_printf_jobs(...) (indent(), fdprintf(2, __VA_ARGS__))
# define DEBUG_JOBS 1
#else
# define DEBUG_JOBS 0
#endif
#ifndef debug_printf_expand
# define debug_printf_expand(...) (indent(), fdprintf(2, __VA_ARGS__))
# define DEBUG_EXPAND 1
#else
# define DEBUG_EXPAND 0
#endif
#ifndef debug_printf_varexp
# define debug_printf_varexp(...) (indent(), fdprintf(2, __VA_ARGS__))
#endif
#ifndef debug_printf_glob
# define debug_printf_glob(...) (indent(), fdprintf(2, __VA_ARGS__))
# define DEBUG_GLOB 1
#else
# define DEBUG_GLOB 0
#endif
#ifndef debug_printf_redir
# define debug_printf_redir(...) (indent(), fdprintf(2, __VA_ARGS__))
#endif
#ifndef debug_printf_list
# define debug_printf_list(...) (indent(), fdprintf(2, __VA_ARGS__))
#endif
#ifndef debug_printf_subst
# define debug_printf_subst(...) (indent(), fdprintf(2, __VA_ARGS__))
#endif
#ifndef debug_printf_prompt
# define debug_printf_prompt(...) (indent(), fdprintf(2, __VA_ARGS__))
#endif
#ifndef debug_printf_clean
# define debug_printf_clean(...) (indent(), fdprintf(2, __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();
fdprintf(2, "%s:\n", prefix);
while (*vv)
fdprintf(2, " '%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 msg_and_die_if_script(lineno, ...) msg_and_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(void)
{
if (!G_interactive_fd) {
if (G.last_exitcode) /* sometines it's 2, not 1 (bash compat) */
xfunc_error_retval = G.last_exitcode;
xfunc_die();
}
}
static void msg_and_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);
die_if_script();
}
static void syntax_error(unsigned lineno UNUSED_PARAM, const char *msg)
{
if (msg)
bb_error_msg("syntax error: %s", msg);
else
bb_simple_error_msg("syntax error");
die_if_script();
}
static void syntax_error_at(unsigned lineno UNUSED_PARAM, const char *msg)
{
bb_error_msg("syntax error at '%s'", msg);
die_if_script();
}
static void syntax_error_unterm_str(unsigned lineno UNUSED_PARAM, const char *s)
{
bb_error_msg("syntax error: unterminated %s", s);
//? source4.tests fails: in bash, echo ${^} in script does not terminate the script
// die_if_script();
}
static void syntax_error_unterm_ch(unsigned lineno, char ch)
{
char msg[2] = { ch, '\0' };
syntax_error_unterm_str(lineno, msg);
}
static void syntax_error_unexpected_ch(unsigned lineno UNUSED_PARAM, int ch)
{
char msg[2];
msg[0] = ch;
msg[1] = '\0';
#if HUSH_DEBUG >= 2
bb_error_msg("hush.c:%u", lineno);
#endif
bb_error_msg("syntax error: unexpected %s", ch == EOF ? "EOF" : msg);
die_if_script();
}
#if HUSH_DEBUG < 2
# undef msg_and_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 msg_and_die_if_script(...) msg_and_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
/* 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 (*src != '\0') {
/* \x -> x */
*dst++ = *src++;
continue;
}
/* else: "\<nul>". Do not delete this backslash.
* Testcase: eval 'echo ok\'
*/
*dst++ = '\\';
/* fallthrough */
}
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);
}
static int dup_CLOEXEC(int fd, int avoid_fd)
{
int newfd;
repeat:
newfd = fcntl(fd, F_DUPFD_CLOEXEC, avoid_fd + 1);
if (newfd >= 0) {
if (F_DUPFD_CLOEXEC == F_DUPFD) /* if old libc (w/o F_DUPFD_CLOEXEC) */
fcntl(newfd, F_SETFD, FD_CLOEXEC);
} else { /* newfd < 0 */
if (errno == EBUSY)
goto repeat;
if (errno == EINTR)
goto repeat;
}
return newfd;
}
static int xdup_CLOEXEC_and_close(int fd, int avoid_fd)
{
int newfd;
repeat:
newfd = fcntl(fd, F_DUPFD_CLOEXEC, avoid_fd + 1);
if (newfd < 0) {
if (errno == EBUSY)
goto repeat;
if (errno == EINTR)
goto repeat;
/* fd was not open? */
if (errno == EBADF)
return fd;
xfunc_die();
}
if (F_DUPFD_CLOEXEC == F_DUPFD) /* if old libc (w/o F_DUPFD_CLOEXEC) */
fcntl(newfd, F_SETFD, FD_CLOEXEC);
close(fd);
return newfd;
}
/* Manipulating HFILEs */
static HFILE *hfopen(const char *name)
{
HFILE *fp;
int fd;
fd = STDIN_FILENO;
if (name) {
fd = open(name, O_RDONLY | O_CLOEXEC);
if (fd < 0)
return NULL;
if (O_CLOEXEC == 0) /* ancient libc */
close_on_exec_on(fd);
}
fp = xmalloc(sizeof(*fp));
if (name == NULL)
G.HFILE_stdin = fp;
fp->fd = fd;
fp->cur = fp->end = fp->buf;
fp->next_hfile = G.HFILE_list;
G.HFILE_list = fp;
return fp;
}
static void hfclose(HFILE *fp)
{
HFILE **pp = &G.HFILE_list;
while (*pp) {
HFILE *cur = *pp;
if (cur == fp) {
*pp = cur->next_hfile;
break;
}
pp = &cur->next_hfile;
}
if (fp->fd >= 0)
close(fp->fd);
free(fp);
}
static int refill_HFILE_and_getc(HFILE *fp)
{
int n;
if (fp->fd < 0) {
/* Already saw EOF */
return EOF;
}
/* Try to buffer more input */
fp->cur = fp->buf;
n = safe_read(fp->fd, fp->buf, sizeof(fp->buf));
if (n < 0) {
bb_simple_perror_msg("read error");
n = 0;
}
fp->end = fp->buf + n;
if (n == 0) {
/* EOF/error */
close(fp->fd);
fp->fd = -1;
return EOF;
}
return (unsigned char)(*fp->cur++);
}
/* Inlined for common case of non-empty buffer.
*/
static ALWAYS_INLINE int hfgetc(HFILE *fp)
{
if (fp->cur < fp->end)
return (unsigned char)(*fp->cur++);
/* Buffer empty */
return refill_HFILE_and_getc(fp);
}
static int move_HFILEs_on_redirect(int fd, int avoid_fd)
{
HFILE *fl = G.HFILE_list;
while (fl) {
if (fd == fl->fd) {
/* We use it only on script files, they are all CLOEXEC */
fl->fd = xdup_CLOEXEC_and_close(fd, avoid_fd);
debug_printf_redir("redirect_fd %d: matches a script fd, moving it to %d\n", fd, fl->fd);
return 1; /* "found and moved" */
}
fl = fl->next_hfile;
}
#if ENABLE_HUSH_MODE_X
if (G.x_mode_fd > 0 && fd == G.x_mode_fd) {
G.x_mode_fd = xdup_CLOEXEC_and_close(fd, avoid_fd);
return 1; /* "found and moved" */
}
#endif
return 0; /* "not in the list" */
}
#if ENABLE_FEATURE_SH_STANDALONE && BB_MMU
static void close_all_HFILE_list(void)
{
HFILE *fl = G.HFILE_list;
while (fl) {
/* hfclose would also free HFILE object.
* It is disastrous if we share memory with a vforked parent.
* I'm not sure we never come here after vfork.
* Therefore just close fd, nothing more.
*
* ">" instead of ">=": we don't close fd#0,
* interactive shell uses hfopen(NULL) as stdin input
* which has fl->fd == 0, but fd#0 gets redirected in pipes.
* If we'd close it here, then e.g. interactive "set | sort"
* with NOFORKed sort, would have sort's input fd closed.
*/
if (fl->fd > 0)
/*hfclose(fl); - unsafe */
close(fl->fd);
fl = fl->next_hfile;
}
}
#endif
static int fd_in_HFILEs(int fd)
{
HFILE *fl = G.HFILE_list;
while (fl) {
if (fl->fd == fd)
return 1;
fl = fl->next_hfile;
}
return 0;
}
/* 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;
IF_HUSH_SET(smallint sv_g_malloced;)
} save_arg_t;
static void save_and_replace_G_args(save_arg_t *sv, char **argv)
{
sv->sv_argv0 = argv[0];
sv->sv_g_argv = G.global_argv;
sv->sv_g_argc = G.global_argc;
IF_HUSH_SET(sv->sv_g_malloced = G.global_args_malloced;)
argv[0] = G.global_argv[0]; /* retain $0 */
G.global_argv = argv;
IF_HUSH_SET(G.global_args_malloced = 0;)
G.global_argc = 1 + string_array_len(argv + 1);
}
static void restore_G_args(save_arg_t *sv, char **argv)
{
#if ENABLE_HUSH_SET
if (G.global_args_malloced) {
/* someone ran "set -- arg1 arg2 ...", undo */
char **pp = G.global_argv;
while (*++pp) /* note: does not free $0 */
free(*pp);
free(G.global_argv);
}
#endif
argv[0] = sv->sv_argv0;
G.global_argv = sv->sv_g_argv;
G.global_argc = sv->sv_g_argc;
IF_HUSH_SET(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 is 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
* Example 4: this does not wait and does not execute ls:
* "sleep 5 & wait; ls -l" + press ^C
*
* (What happens to signals which are IGN on shell start?)
* (What happens with signal mask on shell start?)
*
* Old implementation
* ==================
* 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 special_sig_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 special_sig_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.
*
* Problem: the above approach makes it unwieldy to catch signals while
* we are in read builtin, or while we read commands from stdin:
* masked signals are not visible!
*
* New implementation
* ==================
* We record each signal we are interested in by installing signal handler
* for them - a bit like emulating kernel pending signal mask in userspace.
* We are interested in: signals which need to have special handling
* as described above, and all signals which have traps set.
* Signals are recorded in pending_set.
* After each pipe execution, we extract any pending signals
* and act on them.
*
* unsigned special_sig_mask: a mask of shell-special signals.
* unsigned fatal_sig_mask: a mask of signals on which we restore tty pgrp.
* char *traps[sig] if trap for sig is set (even if it's '').
* sigset_t pending_set: set of sigs we received.
*
* "trap - SIGxxx":
* if sig is in special_sig_mask, set handler back to:
* record_pending_signo, or to IGN if it's a tty stop signal
* if sig is in fatal_sig_mask, set handler back to sigexit.
* else: set handler back to SIG_DFL
* "trap 'cmd' SIGxxx":
* set handler to record_pending_signo.
* "trap '' SIGxxx":
* set handler to SIG_IGN.
* after [v]fork, if we plan to be a shell:
* set signals with special interactive handling to SIG_DFL
* (because 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.
*
* To make wait builtin interruptible, we handle SIGCHLD as special signal,
* otherwise (if we leave it SIG_DFL) sigsuspend in wait builtin will not wake up on it.
*
* Note (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_JOBSTOP_SIGS = 0
#if ENABLE_HUSH_JOB
| (1 << SIGTTIN)
| (1 << SIGTTOU)
| (1 << SIGTSTP)
#endif
,
};
static void record_pending_signo(int sig)
{
sigaddset(&G.pending_set, sig);
#if ENABLE_HUSH_FAST
if (sig == SIGCHLD) {
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
}
static sighandler_t install_sighandler(int sig, sighandler_t handler)
{
struct sigaction old_sa;
/* We could use signal() to install handlers... almost:
* except that we need to mask ALL signals while handlers run.
* I saw signal nesting in strace, race window isn't small.
* SA_RESTART is also needed, but in Linux, signal()
* sets SA_RESTART too.
*/
/* memset(&G.sa, 0, sizeof(G.sa)); - already done */
/* sigfillset(&G.sa.sa_mask); - already done */
/* G.sa.sa_flags = SA_RESTART; - already done */
G.sa.sa_handler = handler;
sigaction(sig, &G.sa, &old_sa);
return old_sa.sa_handler;
}
static void hush_exit(int exitcode) NORETURN;
static void restore_ttypgrp_and__exit(void) NORETURN;
static void restore_ttypgrp_and__exit(void)
{
/* xfunc has failed! die die die */
/* no EXIT traps, this is an escape hatch! */
G.exiting = 1;
hush_exit(xfunc_error_retval);
}
#if ENABLE_HUSH_JOB
/* Needed only on some libc:
* It was observed that on exit(), fgetc'ed buffered data
* gets "unwound" via lseek(fd, -NUM, SEEK_CUR).
* With the net effect that even after fork(), not vfork(),
* exit() in NOEXECed applet in "sh SCRIPT":
* noexec_applet_here
* echo END_OF_SCRIPT
* lseeks fd in input FILE object from EOF to "e" in "echo END_OF_SCRIPT".
* This makes "echo END_OF_SCRIPT" executed twice.
* Similar problems can be seen with msg_and_die_if_script() -> xfunc_die()
* and in `cmd` handling.
* If set as die_func(), this makes xfunc_die() exit via _exit(), not exit():
*/
static void fflush_and__exit(void) NORETURN;
static void fflush_and__exit(void)
{
fflush_all();
_exit(xfunc_error_retval);
}
/* After [v]fork, in child: do not restore tty pgrp on xfunc death */
# define disable_restore_tty_pgrp_on_exit() (die_func = fflush_and__exit)
/* After [v]fork, in parent: restore tty pgrp on xfunc death */
# define enable_restore_tty_pgrp_on_exit() (die_func = restore_ttypgrp_and__exit)
/* 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)
{
/* 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) {
/* Disable all signals: job control, SIGPIPE, etc.
* Mostly paranoid measure, to prevent infinite SIGTTOU.
*/
sigprocmask_allsigs(SIG_BLOCK);
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
static sighandler_t pick_sighandler(unsigned sig)
{
sighandler_t handler = SIG_DFL;
if (sig < sizeof(unsigned)*8) {
unsigned sigmask = (1 << sig);
#if ENABLE_HUSH_JOB
/* is sig fatal? */
if (G_fatal_sig_mask & sigmask)
handler = sigexit;
else
#endif
/* sig has special handling? */
if (G.special_sig_mask & sigmask) {
handler = record_pending_signo;
/* TTIN/TTOU/TSTP can't be set to record_pending_signo
* in order to ignore them: they will be raised
* in an endless loop when we try to do some
* terminal ioctls! We do have to _ignore_ these.
*/
if (SPECIAL_JOBSTOP_SIGS & sigmask)
handler = SIG_IGN;
}
}
return handler;
}
/* Restores tty foreground process group, and exits. */
static void hush_exit(int exitcode)
{
#if ENABLE_FEATURE_EDITING_SAVE_ON_EXIT
if (G.line_input_state)
save_history(G.line_input_state);
#endif
fflush_all();
if (G.exiting <= 0 && G_traps && G_traps[0] && G_traps[0][0]) {
char *argv[3];
/* argv[0] is unused */
argv[1] = xstrdup(G_traps[0]); /* copy, since EXIT trap handler may modify G_traps[0] */
argv[2] = NULL;
G.exiting = 1; /* prevent EXIT trap recursion */
/* Note: G_traps[0] is not cleared!
* "trap" will still show it, if executed
* in the handler */
builtin_eval(argv);
}
#if ENABLE_FEATURE_CLEAN_UP
{
struct variable *cur_var;
if (G.cwd != bb_msg_unknown)
free((char*)G.cwd);
cur_var = G.top_var;
while (cur_var) {
struct variable *tmp = cur_var;
if (!cur_var->max_len)
free(cur_var->varstr);
cur_var = cur_var->next;
free(tmp);
}
}
#endif
fflush_all();
#if ENABLE_HUSH_JOB
sigexit(- (exitcode & 0xff));
#else
_exit(exitcode);
#endif
}
//TODO: return a mask of ALL handled sigs?
static int check_and_run_traps(void)
{
int last_sig = 0;
while (1) {
int sig;
if (sigisemptyset(&G.pending_set))
break;
sig = 0;
do {
sig++;
if (sigismember(&G.pending_set, sig)) {
sigdelset(&G.pending_set, sig);
goto got_sig;
}
} while (sig < NSIG);
break;
got_sig:
#if ENABLE_HUSH_TRAP
if (G_traps && G_traps[sig]) {
debug_printf_exec("%s: sig:%d handler:'%s'\n", __func__, sig, G.traps[sig]);
if (G_traps[sig][0]) {
/* We have user-defined handler */
smalluint save_rcode;
int save_pre;
char *argv[3];
/* argv[0] is unused */
argv[1] = xstrdup(G_traps[sig]);
/* why strdup? trap can modify itself: trap 'trap "echo oops" INT' INT */
argv[2] = NULL;
save_pre = G.pre_trap_exitcode;
G.pre_trap_exitcode = save_rcode = G.last_exitcode;
builtin_eval(argv);
free(argv[1]);
G.pre_trap_exitcode = save_pre;
G.last_exitcode = save_rcode;
# if ENABLE_HUSH_FUNCTIONS
if (G.return_exitcode >= 0) {
debug_printf_exec("trap exitcode:%d\n", G.return_exitcode);
G.last_exitcode = G.return_exitcode;
}
# endif
last_sig = sig;
} /* else: "" trap, ignoring signal */
continue;
}
#endif
/* not a trap: special action */
switch (sig) {
case SIGINT:
debug_printf_exec("%s: sig:%d default SIGINT handler\n", __func__, sig);
G.flag_SIGINT = 1;
last_sig = sig;
break;
#if ENABLE_HUSH_JOB
case SIGHUP: {
//TODO: why are we doing this? ash and dash don't do this,
//they have no handler for SIGHUP at all,
//they rely on kernel to send SIGHUP+SIGCONT to orphaned process groups
struct pipe *job;
debug_printf_exec("%s: sig:%d default SIGHUP handler\n", __func__, sig);
/* 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
#if ENABLE_HUSH_FAST
case SIGCHLD:
debug_printf_exec("%s: sig:%d default SIGCHLD handler\n", __func__, sig);
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);
/* Note:
* We don't do 'last_sig = sig' here -> NOT returning this sig.
* This simplifies wait builtin a bit.
*/
break;
#endif
default: /* ignored: */
debug_printf_exec("%s: sig:%d default handling is to ignore\n", __func__, sig);
/* SIGTERM, SIGQUIT, SIGTTIN, SIGTTOU, SIGTSTP */
/* Note:
* We don't do 'last_sig = sig' here -> NOT returning this sig.
* Example: wait is not interrupted by TERM
* in interactive shell, because TERM is ignored.
*/
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, unsigned len)
{
struct variable **pp;
struct variable *cur;
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 const char* FAST_FUNC get_local_var_value(const char *name)
{
struct variable **vpp;
unsigned len = strlen(name);
if (G.expanded_assignments) {
char **cpp = G.expanded_assignments;
while (*cpp) {
char *cp = *cpp;
if (strncmp(cp, name, len) == 0 && cp[len] == '=')
return cp + len + 1;
cpp++;
}
}
vpp = get_ptr_to_local_var(name, len);
if (vpp)
return (*vpp)->varstr + len + 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
#if ENABLE_HUSH_LINENO_VAR
if (strcmp(name, "LINENO") == 0)
return utoa(G.execute_lineno);
#endif
#if BASH_EPOCH_VARS
{
const char *fmt = NULL;
if (strcmp(name, "EPOCHSECONDS") == 0)
fmt = "%lu";
else if (strcmp(name, "EPOCHREALTIME") == 0)
fmt = "%lu.%06u";
if (fmt) {
struct timeval tv;
gettimeofday(&tv, NULL);
sprintf(G.epoch_buf, fmt, (unsigned long)tv.tv_sec,
(unsigned)tv.tv_usec);
return G.epoch_buf;
}
}
#endif
return NULL;
}
#if ENABLE_HUSH_GETOPTS
static void handle_changed_special_names(const char *name, unsigned name_len)
{
if (name_len == 6) {
if (strncmp(name, "OPTIND", 6) == 0) {
G.getopt_count = 0;
return;
}
}
}
#else
/* Do not even bother evaluating arguments */
# define handle_changed_special_names(...) ((void)0)
#endif
/* str holds "NAME=VAL" and is expected to be malloced.
* We take ownership of it.
*/
#define SETFLAG_EXPORT (1 << 0)
#define SETFLAG_UNEXPORT (1 << 1)
#define SETFLAG_MAKE_RO (1 << 2)
#define SETFLAG_VARLVL_SHIFT 3
static int set_local_var(char *str, unsigned flags)
{
struct variable **cur_pp;
struct variable *cur;
char *free_me = NULL;
char *eq_sign;
int name_len;
int retval;
unsigned local_lvl = (flags >> SETFLAG_VARLVL_SHIFT);
eq_sign = strchr(str, '=');
if (HUSH_DEBUG && !eq_sign)
bb_simple_error_msg_and_die("BUG in setvar");
name_len = eq_sign - str + 1; /* including '=' */
cur_pp = &G.top_var;
while ((cur = *cur_pp) != NULL) {
if (strncmp(cur->varstr, str, name_len) != 0) {
cur_pp = &cur->next;
continue;
}
/* We found an existing var with this name */
if (cur->flg_read_only) {
bb_error_msg("%s: readonly variable", str);
free(str);
//NOTE: in bash, assignment in "export READONLY_VAR=Z" fails, and sets $?=1,
//but export per se succeeds (does put the var in env). We don't mimic that.
return -1;
}
if (flags & SETFLAG_UNEXPORT) { // && cur->flg_export ?
debug_printf_env("%s: unsetenv '%s'\n", __func__, str);
*eq_sign = '\0';
unsetenv(str);
*eq_sign = '=';
}
if (cur->var_nest_level < local_lvl) {
/* 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)
flags |= SETFLAG_EXPORT;
/* New variable is local ("local VAR=VAL" or
* "VAR=VAL cmd")
* and existing one is global, or local
* on a lower level that new one.
* Remove it from global variable list:
*/
*cur_pp = cur->next;
if (G.shadowed_vars_pp) {
/* Save in "shadowed" list */
debug_printf_env("shadowing %s'%s'/%u by '%s'/%u\n",
cur->flg_export ? "exported " : "",
cur->varstr, cur->var_nest_level, str, local_lvl
);
cur->next = *G.shadowed_vars_pp;
*G.shadowed_vars_pp = cur;
} else {
/* Came from pseudo_exec_argv(), no need to save: delete it */
debug_printf_env("shadow-deleting %s'%s'/%u by '%s'/%u\n",
cur->flg_export ? "exported " : "",
cur->varstr, cur->var_nest_level, str, local_lvl
);
if (cur->max_len == 0) /* allocated "VAR=VAL"? */
free_me = cur->varstr; /* then free it later */
free(cur);
}
break;
}
if (strcmp(cur->varstr + name_len, eq_sign + 1) == 0) {
debug_printf_env("assignement '%s' does not change anything\n", str);
free_and_exp:
free(str);
goto exp;
}
/* Replace the value in the found "struct variable" */
if (cur->max_len != 0) {
if (cur->max_len >= strnlen(str, cur->max_len + 1)) {
/* This one is from startup env, reuse space */
debug_printf_env("reusing startup env for '%s'\n", str);
strcpy(cur->varstr, str);
goto free_and_exp;
}
/* Can't reuse */
cur->max_len = 0;
goto set_str_and_exp;
}
/* max_len == 0 signifies "malloced" var, which we can
* (and have to) free. But we can't free(cur->varstr) here:
* if cur->flg_export is 1, it is in the environment.
* We should either unsetenv+free, or wait until putenv,
* then putenv(new)+free(old).
*/
free_me = cur->varstr;
goto set_str_and_exp;
}
/* Not found or shadowed - create new variable struct */
debug_printf_env("%s: alloc new var '%s'/%u\n", __func__, str, local_lvl);
cur = xzalloc(sizeof(*cur));
cur->var_nest_level = local_lvl;
cur->next = *cur_pp;
*cur_pp = cur;
set_str_and_exp:
cur->varstr = str;
exp:
#if !BB_MMU || ENABLE_HUSH_READONLY
if (flags & SETFLAG_MAKE_RO) {
cur->flg_read_only = 1;
}
#endif
if (flags & SETFLAG_EXPORT)
cur->flg_export = 1;
retval = 0;
if (cur->flg_export) {
if (flags & SETFLAG_UNEXPORT) {
cur->flg_export = 0;
/* unsetenv was already done */
} else {
debug_printf_env("%s: putenv '%s'/%u\n", __func__, cur->varstr, cur->var_nest_level);
retval = putenv(cur->varstr);
/* fall through to "free(free_me)" -
* only now we can free old exported malloced string
*/
}
}
free(free_me);
handle_changed_special_names(cur->varstr, name_len - 1);
return retval;
}
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, /*flag:*/ 0);
}
/* Used at startup and after each cd */
static void set_pwd_var(unsigned flag)
{
set_local_var(xasprintf("PWD=%s", get_cwd(/*force:*/ 1)), flag);
}
#if ENABLE_HUSH_UNSET || ENABLE_HUSH_GETOPTS
static int unset_local_var_len(const char *name, int name_len)
{
struct variable *cur;
struct variable **cur_pp;
cur_pp = &G.top_var;
while ((cur = *cur_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;
}
*cur_pp = 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);
break;
}
cur_pp = &cur->next;
}
/* Handle "unset LINENO" et al even if did not find the variable to unset */
handle_changed_special_names(name, name_len);
return EXIT_SUCCESS;
}
static int unset_local_var(const char *name)
{
return unset_local_var_len(name, strlen(name));
}
#endif
/*
* 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'/%u\n", __func__, var->varstr, var->var_nest_level);
putenv(var->varstr);
} else {
debug_printf_env("%s: restoring variable '%s'/%u\n", __func__, var->varstr, var->var_nest_level);
}
var = next;
}
}
/* We put strings[i] into variable table and possibly putenv them.
* If variable is read only, we can free the strings[i]
* which attempts to overwrite it.
* The strings[] vector itself is freed.
*/
static void set_vars_and_save_old(char **strings)
{
char **s;
if (!strings)
return;
s = strings;
while (*s) {
struct variable *var_p;
struct variable **var_pp;
char *eq;
eq = strchr(*s, '=');
if (HUSH_DEBUG && !eq)
bb_simple_error_msg_and_die("BUG in varexp4");
var_pp = get_ptr_to_local_var(*s, eq - *s);
if (var_pp) {
var_p = *var_pp;
if (var_p->flg_read_only) {
char **p;
bb_error_msg("%s: readonly variable", *s);
/*
* "VAR=V BLTIN" unsets VARs after BLTIN completes.
* If VAR is readonly, leaving it in the list
* after asssignment error (msg above)
* causes doubled error message later, on unset.
*/
debug_printf_env("removing/freeing '%s' element\n", *s);
free(*s);
p = s;
do { *p = p[1]; p++; } while (*p);
goto next;
}
/* below, set_local_var() with nest level will
* "shadow" (remove) this variable from
* global linked list.
*/
}
debug_printf_env("%s: env override '%s'/%u\n", __func__, *s, G.var_nest_level);
set_local_var(*s, (G.var_nest_level << SETFLAG_VARLVL_SHIFT) | SETFLAG_EXPORT);
s++;
next: ;
}
free(strings);
}
/*
* Unicode helper
*/
static void reinit_unicode_for_hush(void)
{
/* Unicode support should be activated even if LANG is set
* _during_ shell execution, not only if it was set when
* shell was started. Therefore, re-check LANG every time:
*/
if (ENABLE_FEATURE_CHECK_UNICODE_IN_ENV
|| ENABLE_UNICODE_USING_LOCALE
) {
const char *s = get_local_var_value("LC_ALL");
if (!s) s = get_local_var_value("LC_CTYPE");
if (!s) s = get_local_var_value("LANG");
reinit_unicode(s);
}
}
/*
* in_str support (strings, and "strings" read from files).
*/
#if ENABLE_HUSH_INTERACTIVE
/* To test correct lineedit/interactive behavior, type from command line:
* echo $P\
* \
* AT\
* H\
* \
* It exercises a lot of corner cases.
*/
static const char *setup_prompt_string(void)
{
const char *prompt_str;
debug_printf_prompt("%s promptmode:%d\n", __func__, G.promptmode);
# if ENABLE_FEATURE_EDITING_FANCY_PROMPT
prompt_str = get_local_var_value(G.promptmode == 0 ? "PS1" : "PS2");
if (!prompt_str)
prompt_str = "";
# else
prompt_str = "> "; /* if PS2, else... */
if (G.promptmode == 0) { /* PS1 */
/* No fancy prompts supported, (re)generate "CURDIR $ " by hand */
free(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) ? '$' : '#');
}
# endif
debug_printf("prompt_str '%s'\n", prompt_str);
return prompt_str;
}
static int get_user_input(struct in_str *i)
{
int r;
const char *prompt_str;
prompt_str = setup_prompt_string();
# if ENABLE_FEATURE_EDITING
for (;;) {
reinit_unicode_for_hush();
if (G.flag_SIGINT) {
/* There was ^C'ed, make it look prettier: */
bb_putchar('\n');
G.flag_SIGINT = 0;
}
/* buglet: SIGINT will not make new prompt to appear _at once_,
* only after <Enter>. (^C works immediately) */
r = read_line_input(G.line_input_state, prompt_str,
G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1
);
/* read_line_input intercepts ^C, "convert" it to SIGINT */
if (r == 0)
raise(SIGINT);
check_and_run_traps();
if (r != 0 && !G.flag_SIGINT)
break;
/* ^C or SIGINT: repeat */
/* bash prints ^C even on real SIGINT (non-kbd generated) */
write(STDOUT_FILENO, "^C", 2);
G.last_exitcode = 128 + SIGINT;
}
if (r < 0) {
/* EOF/error detected */
i->p = NULL;
i->peek_buf[0] = r = EOF;
return r;
}
i->p = G.user_input_buf;
return (unsigned char)*i->p++;
# else
for (;;) {
G.flag_SIGINT = 0;
if (i->last_char == '\0' || i->last_char == '\n') {
/* Why check_and_run_traps here? Try this interactively:
* $ trap 'echo INT' INT; (sleep 2; kill -INT $$) &
* $ <[enter], repeatedly...>
* Without check_and_run_traps, handler never runs.
*/
check_and_run_traps();
fputs(prompt_str, stdout);
}
fflush_all();
//FIXME: here ^C or SIGINT will have effect only after <Enter>
r = hfgetc(i->file);
/* In !ENABLE_FEATURE_EDITING we don't use read_line_input,
* no ^C masking happens during fgetc, no special code for ^C:
* it generates SIGINT as usual.
*/
check_and_run_traps();
if (G.flag_SIGINT)
G.last_exitcode = 128 + SIGINT;
if (r != '\0')
break;
}
return r;
# endif
}
/* This is the magic location that prints prompts
* and gets data back from the user */
static int fgetc_interactive(struct in_str *i)
{
int ch;
/* If it's interactive stdin, get new line. */
if (G_interactive_fd && i->file == G.HFILE_stdin) {
/* Returns first char (or EOF), the rest is in i->p[] */
ch = get_user_input(i);
G.promptmode = 1; /* PS2 */
debug_printf_prompt("%s promptmode=%d\n", __func__, G.promptmode);
} else {
/* Not stdin: script file, sourced file, etc */
do ch = hfgetc(i->file); while (ch == '\0');
}
return ch;
}
#else
static ALWAYS_INLINE int fgetc_interactive(struct in_str *i)
{
int ch;
do ch = hfgetc(i->file); while (ch == '\0');
return ch;
}
#endif /* INTERACTIVE */
static int i_getch(struct in_str *i)
{
int ch;
if (!i->file) {
/* string-based in_str */
ch = (unsigned char)*i->p;
if (ch != '\0') {
i->p++;
i->last_char = ch;
return ch;
}
return EOF;
}
/* FILE-based in_str */
#if ENABLE_FEATURE_EDITING
/* This can be stdin, check line editing char[] buffer */
if (i->p && *i->p != '\0') {
ch = (unsigned char)*i->p++;
goto out;
}
#endif
/* peek_buf[] is an int array, not char. Can contain EOF. */
ch = i->peek_buf[0];
if (ch != 0) {
int ch2 = i->peek_buf[1];
i->peek_buf[0] = ch2;
if (ch2 == 0) /* very likely, avoid redundant write */
goto out;
i->peek_buf[1] = 0;
goto out;
}
ch = fgetc_interactive(i);
out:
debug_printf("file_get: got '%c' %d\n", ch, ch);
i->last_char = ch;
#if ENABLE_HUSH_LINENO_VAR
if (ch == '\n') {
G.parse_lineno++;
debug_printf_parse("G.parse_lineno++ = %u\n", G.parse_lineno);
}
#endif
return ch;
}
static int i_peek(struct in_str *i)
{
int ch;
if (!i->file) {
/* string-based in_str */
/* Doesn't report EOF on NUL. None of the callers care. */
return (unsigned char)*i->p;
}
/* FILE-based in_str */
#if ENABLE_FEATURE_EDITING && ENABLE_HUSH_INTERACTIVE
/* This can be stdin, check line editing char[] buffer */
if (i->p && *i->p != '\0')
return (unsigned char)*i->p;
#endif
/* peek_buf[] is an int array, not char. Can contain EOF. */
ch = i->peek_buf[0];
if (ch != 0)
return ch;
/* Need to get a new char */
ch = fgetc_interactive(i);
debug_printf("file_peek: got '%c' %d\n", ch, ch);
/* Save it by either rolling back line editing buffer, or in i->peek_buf[0] */
#if ENABLE_FEATURE_EDITING && ENABLE_HUSH_INTERACTIVE
if (i->p) {
i->p -= 1;
return ch;
}
#endif
i->peek_buf[0] = ch;
/*i->peek_buf[1] = 0; - already is */
return ch;
}
/* Only ever called if i_peek() was called, and did not return EOF.
* IOW: we know the previous peek saw an ordinary char, not EOF, not NUL,
* not end-of-line. Therefore we never need to read a new editing line here.
*/
static int i_peek2(struct in_str *i)
{
int ch;
/* There are two cases when i->p[] buffer exists.
* (1) it's a string in_str.
* (2) It's a file, and we have a saved line editing buffer.
* In both cases, we know that i->p[0] exists and not NUL, and
* the peek2 result is in i->p[1].
*/
if (i->p)
return (unsigned char)i->p[1];
/* Now we know it is a file-based in_str. */
/* peek_buf[] is an int array, not char. Can contain EOF. */
/* Is there 2nd char? */
ch = i->peek_buf[1];
if (ch == 0) {
/* We did not read it yet, get it now */
do ch = hfgetc(i->file); while (ch == '\0');
i->peek_buf[1] = ch;
}
debug_printf("file_peek2: got '%c' %d\n", ch, ch);
return ch;
}
static int i_getch_and_eat_bkslash_nl(struct in_str *input)
{
for (;;) {
int ch, ch2;
ch = i_getch(input);
if (ch != '\\')
return ch;
ch2 = i_peek(input);
if (ch2 != '\n')
return ch;
/* backslash+newline, skip it */
i_getch(input);
}
}
/* Note: this function _eats_ \<newline> pairs, safe to use plain
* i_getch() after it instead of i_getch_and_eat_bkslash_nl().
*/
static int i_peek_and_eat_bkslash_nl(struct in_str *input)
{
for (;;) {
int ch, ch2;
ch = i_peek(input);
if (ch != '\\')
return ch;
ch2 = i_peek2(input);
if (ch2 != '\n')
return ch;
/* backslash+newline, skip it */
i_getch(input);
i_getch(input);
}
}
static void setup_file_in_str(struct in_str *i, HFILE *fp)
{
memset(i, 0, sizeof(*i));
i->file = fp;
/* i->p = NULL; */
}
static void setup_string_in_str(struct in_str *i, const char *s)
{
memset(i, 0, sizeof(*i));
/*i->file = NULL */;
i->p = s;
}
/*
* o_string support
*/
#define B_CHUNK (32 * sizeof(char*))
static void o_reset_to_empty_unquoted(o_string *o)
{
o->length = 0;
o->has_quoted_part = 0;
if (o->data)
o->data[0] = '\0';
}
static void o_free_and_set_NULL(o_string *o)
{
free(o->data);
memset(o, 0, sizeof(*o));
}
static ALWAYS_INLINE void o_free(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-1);
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);
if (o->length < o->maxlen) {
/* likely. avoid o_grow_by() call */
add:
o->data[o->length] = ch;
o->length++;
o->data[o->length] = '\0';
return;
}
o_grow_by(o, 1);
goto add;
}
#if 0
/* Valid only if we know o_string is not empty */
static void o_delchr(o_string *o)
{
o->length--;
o->data[o->length] = '\0';
}
#endif
static void o_addblock(o_string *o, const char *str, int len)
{
o_grow_by(o, len);
((char*)mempcpy(&o->data[o->length], str, len))[0] = '\0';
o->length += len;
}
static void o_addstr(o_string *o, const char *str)
{
o_addblock(o, str, strlen(str));
}
static void o_addstr_with_NUL(o_string *o, const char *str)
{
o_addblock(o, str, strlen(str) + 1);
}
#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
#if ENABLE_HUSH_MODE_X
static void x_mode_addchr(int ch)
{
o_addchr(&G.x_mode_buf, ch);
}
static void x_mode_addstr(const char *str)
{
o_addstr(&G.x_mode_buf, str);
}
static void x_mode_addblock(const char *str, int len)
{
o_addblock(&G.x_mode_buf, str, len);
}
static void x_mode_prefix(void)
{
int n = G.x_mode_depth;
do x_mode_addchr('+'); while (--n >= 0);
}
static void x_mode_flush(void)
{
int len = G.x_mode_buf.length;
if (len <= 0)
return;
if (G.x_mode_fd > 0) {
G.x_mode_buf.data[len] = '\n';
full_write(G.x_mode_fd, G.x_mode_buf.data, len + 1);
}
G.x_mode_buf.length = 0;
}
#endif
/*
* HUSH_BRACE_EXPANSION 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.
*/
#if ENABLE_HUSH_BRACE_EXPANSION
# 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;
/* '-' is included because of this case:
* >filename0 >filename1 >filename9; v='-'; echo filename[0"$v"9]
*/
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)