summaryrefslogtreecommitdiff
path: root/shell/hush.c
diff options
context:
space:
mode:
authorDenys Vlasenko2009-10-08 03:06:04 +0200
committerDenys Vlasenko2009-10-08 03:06:04 +0200
commit4ac9819263114edb9b5b638ffa6d2e41a4bb46e7 (patch)
treef0c5bc9c7a2bf3a384b85350bfe4c9ca5ec4858f /shell/hush.c
parent5b807cd5acd1f27b3e7aa36aac2728be27c5907c (diff)
downloadbusybox-4ac9819263114edb9b5b638ffa6d2e41a4bb46e7.zip
busybox-4ac9819263114edb9b5b638ffa6d2e41a4bb46e7.tar.gz
apply post-1.15.1 fixes
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
Diffstat (limited to 'shell/hush.c')
-rw-r--r--shell/hush.c188
1 files changed, 125 insertions, 63 deletions
diff --git a/shell/hush.c b/shell/hush.c
index 5794b1d..b515eab 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -78,6 +78,7 @@
* 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
@@ -786,7 +787,7 @@ static void xxfree(void *ptr)
* HUSH_DEBUG >= 2 prints line number in this file where it was detected.
*/
#if HUSH_DEBUG < 2
-# define die_if_script(lineno, fmt...) die_if_script(fmt)
+# define die_if_script(lineno, ...) die_if_script(__VA_ARGS__)
# define syntax_error(lineno, msg) syntax_error(msg)
# define syntax_error_at(lineno, msg) syntax_error_at(msg)
# define syntax_error_unterm_ch(lineno, ch) syntax_error_unterm_ch(ch)
@@ -855,7 +856,7 @@ static void syntax_error_unexpected_ch(unsigned lineno, int ch)
# undef syntax_error_unterm_str
# undef syntax_error_unexpected_ch
#else
-# define die_if_script(fmt...) die_if_script(__LINE__, fmt)
+# define die_if_script(...) die_if_script(__LINE__, __VA_ARGS__)
# define syntax_error(msg) syntax_error(__LINE__, msg)
# define syntax_error_at(msg) syntax_error_at(__LINE__, msg)
# define syntax_error_unterm_ch(ch) syntax_error_unterm_ch(__LINE__, ch)
@@ -898,7 +899,7 @@ static int is_well_formed_var_name(const char *s, char terminator)
/* Replace each \x with x in place, return ptr past NUL. */
static char *unbackslash(char *src)
{
- char *dst = src;
+ char *dst = src = strchrnul(src, '\\');
while (1) {
if (*src == '\\')
src++;
@@ -1037,7 +1038,7 @@ static void restore_G_args(save_arg_t *sv, char **argv)
* is finished or backgrounded. It is the same in interactive and
* non-interactive shells, and is the same regardless of whether
* a user trap handler is installed or a shell special one is in effect.
- * ^C or ^Z from keyboard seem to execute "at once" because it usually
+ * ^C or ^Z from keyboard seems to execute "at once" because it usually
* backgrounds (i.e. stops) or kills all members of currently running
* pipe.
*
@@ -1104,12 +1105,17 @@ static void restore_G_args(save_arg_t *sv, char **argv)
* (child shell is not interactive),
* unset all traps (note: regardless of child shell's type - {}, (), etc)
* after [v]fork, if we plan to exec:
- * POSIX says pending signal mask is cleared in child - no need to clear it.
+ * POSIX says fork clears pending signal mask in child - no need to clear it.
* Restore blocked signal set to one inherited by shell just prior to exec.
*
* Note: as a result, we do not use signal handlers much. The only uses
* are to count SIGCHLDs
* 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
@@ -2596,43 +2602,51 @@ static void reset_traps_to_defaults(void)
{
/* This function is always called in a child shell
* after fork (not vfork, NOMMU doesn't use this function).
- * Child shells are not interactive.
- * SIGTTIN/SIGTTOU/SIGTSTP should not have special handling.
- * Testcase: (while :; do :; done) + ^Z should background.
- * Same goes for SIGTERM, SIGHUP, SIGINT.
*/
unsigned sig;
unsigned mask;
+ /* Child shells are not interactive.
+ * SIGTTIN/SIGTTOU/SIGTSTP should not have special handling.
+ * Testcase: (while :; do :; done) + ^Z should background.
+ * Same goes for SIGTERM, SIGHUP, SIGINT.
+ */
if (!G.traps && !(G.non_DFL_mask & SPECIAL_INTERACTIVE_SIGS))
- return;
+ return; /* already no traps and no SPECIAL_INTERACTIVE_SIGS */
- /* Stupid. It can be done with *single* &= op, but we can't use
- * the fact that G.blocked_set is implemented as a bitmask... */
+ /* Switching off SPECIAL_INTERACTIVE_SIGS.
+ * Stupid. It can be done with *single* &= op, but we can't use
+ * the fact that G.blocked_set is implemented as a bitmask
+ * in libc... */
mask = (SPECIAL_INTERACTIVE_SIGS >> 1);
sig = 1;
while (1) {
- if (mask & 1)
- sigdelset(&G.blocked_set, sig);
+ if (mask & 1) {
+ /* Careful. Only if no trap or trap is not "" */
+ if (!G.traps || !G.traps[sig] || G.traps[sig][0])
+ sigdelset(&G.blocked_set, sig);
+ }
mask >>= 1;
if (!mask)
break;
sig++;
}
-
+ /* Our homegrown sig mask is saner to work with :) */
G.non_DFL_mask &= ~SPECIAL_INTERACTIVE_SIGS;
+
+ /* Resetting all traps to default except empty ones */
mask = G.non_DFL_mask;
if (G.traps) for (sig = 0; sig < NSIG; sig++, mask >>= 1) {
- if (!G.traps[sig])
+ if (!G.traps[sig] || !G.traps[sig][0])
continue;
free(G.traps[sig]);
G.traps[sig] = NULL;
/* There is no signal for 0 (EXIT) */
if (sig == 0)
continue;
- /* There was a trap handler, we are removing it.
+ /* There was a trap handler, we just removed it.
* But if sig still has non-DFL handling,
- * we should not unblock it. */
+ * we should not unblock the sig. */
if (mask & 1)
continue;
sigdelset(&G.blocked_set, sig);
@@ -3079,15 +3093,21 @@ static const struct built_in_command* find_builtin(const char *name)
}
#if ENABLE_HUSH_FUNCTIONS
-static const struct function *find_function(const char *name)
+static struct function **find_function_slot(const char *name)
{
- const struct function *funcp = G.top_func;
- while (funcp) {
- if (strcmp(name, funcp->name) == 0) {
+ struct function **funcpp = &G.top_func;
+ while (*funcpp) {
+ if (strcmp(name, (*funcpp)->name) == 0) {
break;
}
- funcp = funcp->next;
+ funcpp = &(*funcpp)->next;
}
+ return funcpp;
+}
+
+static const struct function *find_function(const char *name)
+{
+ const struct function *funcp = *find_function_slot(name);
if (funcp)
debug_printf_exec("found function '%s'\n", name);
return funcp;
@@ -3096,18 +3116,11 @@ static const struct function *find_function(const char *name)
/* Note: takes ownership on name ptr */
static struct function *new_function(char *name)
{
- struct function *funcp;
- struct function **funcpp = &G.top_func;
+ struct function **funcpp = find_function_slot(name);
+ struct function *funcp = *funcpp;
- while ((funcp = *funcpp) != NULL) {
- struct command *cmd;
-
- if (strcmp(funcp->name, name) != 0) {
- funcpp = &funcp->next;
- continue;
- }
-
- cmd = funcp->parent_cmd;
+ if (funcp != NULL) {
+ struct command *cmd = funcp->parent_cmd;
debug_printf_exec("func %p parent_cmd %p\n", funcp, cmd);
if (!cmd) {
debug_printf_exec("freeing & replacing function '%s'\n", funcp->name);
@@ -3129,39 +3142,36 @@ static struct function *new_function(char *name)
cmd->group_as_string = funcp->body_as_string;
# endif
}
- goto skip;
+ } else {
+ debug_printf_exec("remembering new function '%s'\n", name);
+ funcp = *funcpp = xzalloc(sizeof(*funcp));
+ /*funcp->next = NULL;*/
}
- debug_printf_exec("remembering new function '%s'\n", name);
- funcp = *funcpp = xzalloc(sizeof(*funcp));
- /*funcp->next = NULL;*/
- skip:
+
funcp->name = name;
return funcp;
}
static void unset_func(const char *name)
{
- struct function *funcp;
- struct function **funcpp = &G.top_func;
-
- while ((funcp = *funcpp) != NULL) {
- if (strcmp(funcp->name, name) == 0) {
- *funcpp = funcp->next;
- /* funcp is unlinked now, deleting it.
- * Note: if !funcp->body, the function was created by
- * "-F name body", do not free ->body_as_string
- * and ->name as they were not malloced. */
- if (funcp->body) {
- free_pipe_list(funcp->body);
- free(funcp->name);
+ struct function **funcpp = find_function_slot(name);
+ struct function *funcp = *funcpp;
+
+ if (funcp != NULL) {
+ debug_printf_exec("freeing function '%s'\n", funcp->name);
+ *funcpp = funcp->next;
+ /* funcp is unlinked now, deleting it.
+ * Note: if !funcp->body, the function was created by
+ * "-F name body", do not free ->body_as_string
+ * and ->name as they were not malloced. */
+ if (funcp->body) {
+ free_pipe_list(funcp->body);
+ free(funcp->name);
# if !BB_MMU
- free(funcp->body_as_string);
+ free(funcp->body_as_string);
# endif
- }
- free(funcp);
- break;
}
- funcpp = &funcp->next;
+ free(funcp);
}
}
@@ -3628,9 +3638,9 @@ static int checkjobs(struct pipe* fg_pipe)
/* Note: is WIFSIGNALED, WEXITSTATUS = sig + 128 */
rcode = WEXITSTATUS(status);
IF_HAS_KEYWORDS(if (fg_pipe->pi_inverted) rcode = !rcode;)
- /* bash prints killing signal's name for *last*
+ /* bash prints killer signal's name for *last*
* process in pipe (prints just newline for SIGINT).
- * Mimic this. Example: "sleep 5" + ^\
+ * Mimic this. Example: "sleep 5" + (^\ or kill -QUIT)
*/
if (WIFSIGNALED(status)) {
int sig = WTERMSIG(status);
@@ -5183,6 +5193,47 @@ static FILE *generate_stream_from_string(const char *s)
xmove_fd(channel[1], 1);
/* Prevent it from trying to handle ctrl-z etc */
IF_HUSH_JOB(G.run_list_level = 1;)
+ /* Awful hack for `trap` or $(trap).
+ *
+ * http://www.opengroup.org/onlinepubs/009695399/utilities/trap.html
+ * contains an example where "trap" is executed in a subshell:
+ *
+ * save_traps=$(trap)
+ * ...
+ * eval "$save_traps"
+ *
+ * Standard does not say that "trap" in subshell shall print
+ * parent shell's traps. It only says that its output
+ * must have suitable form, but then, in the above example
+ * (which is not supposed to be normative), it implies that.
+ *
+ * bash (and probably other shell) does implement it
+ * (traps are reset to defaults, but "trap" still shows them),
+ * but as a result, "trap" logic is hopelessly messed up:
+ *
+ * # trap
+ * trap -- 'echo Ho' SIGWINCH <--- we have a handler
+ * # (trap) <--- trap is in subshell - no output (correct, traps are reset)
+ * # true | trap <--- trap is in subshell - no output (ditto)
+ * # echo `true | trap` <--- in subshell - output (but traps are reset!)
+ * trap -- 'echo Ho' SIGWINCH
+ * # echo `(trap)` <--- in subshell in subshell - output
+ * trap -- 'echo Ho' SIGWINCH
+ * # echo `true | (trap)` <--- in subshell in subshell in subshell - output!
+ * trap -- 'echo Ho' SIGWINCH
+ *
+ * The rules when to forget and when to not forget traps
+ * get really complex and nonsensical.
+ *
+ * Our solution: ONLY bare $(trap) or `trap` is special.
+ */
+ s = skip_whitespace(s);
+ if (strncmp(s, "trap", 4) == 0 && (*skip_whitespace(s + 4) == '\0'))
+ {
+ static const char *const argv[] = { NULL, NULL };
+ builtin_trap((char**)argv);
+ exit(0); /* not _exit() - we need to fflush */
+ }
#if BB_MMU
reset_traps_to_defaults();
parse_and_run_string(s);
@@ -5676,8 +5727,10 @@ static int handle_dollar(o_string *as_string,
goto make_var;
}
/* else: it's $_ */
- /* TODO: */
- /* $_ Shell or shell script name; or last cmd name */
+ /* TODO: $_ and $-: */
+ /* $_ Shell or shell script name; or last argument of last command
+ * (if last command wasn't a pipe; if it was, bash sets $_ to "");
+ * but in command's env, set to full pathname used to invoke it */
/* $- Option flags set by set builtin or shell options (-i etc) */
default:
o_addQchr(dest, '$');
@@ -5794,7 +5847,7 @@ static struct pipe *parse_stream(char **pstring,
* found. When recursing, quote state is passed in via dest->o_escape.
*/
debug_printf_parse("parse_stream entered, end_trigger='%c'\n",
- end_trigger ? : 'X');
+ end_trigger ? end_trigger : 'X');
debug_enter();
G.ifs = get_local_var_value("IFS");
@@ -6860,7 +6913,8 @@ static int FAST_FUNC builtin_cd(char **argv)
* bash says "bash: cd: HOME not set" and does nothing
* (exitcode 1)
*/
- newdir = get_local_var_value("HOME") ? : "/";
+ const char *home = get_local_var_value("HOME");
+ newdir = home ? home : "/";
}
if (chdir(newdir)) {
/* Mimic bash message exactly */
@@ -7057,6 +7111,10 @@ static int FAST_FUNC builtin_trap(char **argv)
if (G.traps[i]) {
printf("trap -- ");
print_escaped(G.traps[i]);
+ /* note: bash adds "SIG", but only if invoked
+ * as "bash". If called as "sh", or if set -o posix,
+ * then it prints short signal names.
+ * We are printing short names: */
printf(" %s\n", get_signame(i));
}
}
@@ -7268,6 +7326,10 @@ static int FAST_FUNC builtin_memleak(char **argv UNUSED_PARAM)
void *p;
unsigned long l;
+#ifdef M_TRIM_THRESHOLD
+ /* Optional. Reduces probability of false positives */
+ malloc_trim(0);
+#endif
/* Crude attempt to find where "free memory" starts,
* sans fragmentation. */
p = malloc(240);