*/
/*
- * The follow should be set to reflect the type of system you have:
+ * The following should be set to reflect the type of system you have:
* JOBS -> 1 if you have Berkeley job control, 0 otherwise.
* define SYSV if you are running under System V.
* define DEBUG=1 to compile in debugging ('set -o debug' to turn on)
* a quit signal will generate a core dump.
*/
#define DEBUG 0
+/* Tweak debug output verbosity here */
+#define DEBUG_TIME 0
+#define DEBUG_PID 1
+#define DEBUG_SIG 1
+
#define PROFILE 0
#define IFS_BROKEN
#define JOBS ENABLE_ASH_JOB_CONTROL
#if DEBUG
-#ifndef _GNU_SOURCE
-#define _GNU_SOURCE
-#endif
+# ifndef _GNU_SOURCE
+# define _GNU_SOURCE
+# endif
#endif
#include "busybox.h" /* for applet_names */
#include <setjmp.h>
#include <fnmatch.h>
#if JOBS || ENABLE_ASH_READ_NCHARS
-#include <termios.h>
+# include <termios.h>
#endif
#ifndef PIPE_BUF
-#define PIPE_BUF 4096 /* amount of buffering in a pipe */
+# define PIPE_BUF 4096 /* amount of buffering in a pipe */
#endif
#if defined(__uClinux__)
-#error "Do not even bother, ash will not run on uClinux"
+# error "Do not even bother, ash will not run on uClinux"
#endif
#define CMDTABLESIZE 31 /* should be prime */
-/* ============ Misc helpers */
-
-#define xbarrier() do { __asm__ __volatile__ ("": : :"memory"); } while (0)
-
-/* C99 say: "char" declaration may be signed or unsigned default */
-#define signed_char2int(sc) ((int)((signed char)sc))
-
-
/* ============ Shell options */
static const char *const optletters_optnames[] = {
} while (0)
+/* ============ DEBUG */
+#if DEBUG
+static void trace_printf(const char *fmt, ...);
+static void trace_vprintf(const char *fmt, va_list va);
+# define TRACE(param) trace_printf param
+# define TRACEV(param) trace_vprintf param
+# define close(f) do { \
+ int dfd = (f); \
+ if (close(dfd) < 0) \
+ bb_error_msg("bug on %d: closing %d(%x)", \
+ __LINE__, dfd, dfd); \
+} while (0)
+#else
+# define TRACE(param)
+# define TRACEV(param)
+#endif
+
+
/* ============ Utility functions */
+#define xbarrier() do { __asm__ __volatile__ ("": : :"memory"); } while (0)
+
+/* C99 say: "char" declaration may be signed or unsigned by default */
+#define signed_char2int(sc) ((int)(signed char)(sc))
+
static int isdigit_str9(const char *str)
{
int maxlen = 9 + 1; /* max 9 digits: 999999999 */
exception = e;
longjmp(exception_handler->loc, 1);
}
+#if DEBUG
+#define raise_exception(e) do { \
+ TRACE(("raising exception %d on line %d\n", (e), __LINE__)); \
+ raise_exception(e); \
+} while (0)
+#endif
/*
* Called from trap.c when a SIGINT is received. (If the user specifies
raise_exception(i);
/* NOTREACHED */
}
+#if DEBUG
+#define raise_interrupt() do { \
+ TRACE(("raising interrupt on line %d\n", __LINE__)); \
+ raise_interrupt(); \
+} while (0)
+#endif
#if ENABLE_ASH_OPTIMIZE_FOR_SIZE
static void
raise_interrupt();
}
#define FORCE_INT_ON force_int_on()
-#else
+
+#else /* !ASH_OPTIMIZE_FOR_SIZE */
+
#define INT_ON do { \
xbarrier(); \
if (--suppressint == 0 && intpending) \
if (intpending) \
raise_interrupt(); \
} while (0)
-#endif /* ASH_OPTIMIZE_FOR_SIZE */
+#endif /* !ASH_OPTIMIZE_FOR_SIZE */
#define SAVE_INT(v) ((v) = suppressint)
onsig(int signo)
{
gotsig[signo - 1] = 1;
- pendingsig = signo;
if (/* exsig || */ (signo == SIGINT && !trap[SIGINT])) {
if (!suppressint) {
raise_interrupt(); /* does not return */
}
intpending = 1;
+ } else {
+ pendingsig = signo;
}
}
if (debug != 1)
return;
+ if (DEBUG_TIME)
+ fprintf(tracefile, "%u ", (int) time(NULL));
+ if (DEBUG_PID)
+ fprintf(tracefile, "[%u] ", (int) getpid());
+ if (DEBUG_SIG)
+ fprintf(tracefile, "pending s:%d i:%d(supp:%d) ", pendingsig, intpending, suppressint);
va_start(va, fmt);
vfprintf(tracefile, fmt, va);
va_end(va);
{
if (debug != 1)
return;
+ if (DEBUG_TIME)
+ fprintf(tracefile, "%u ", (int) time(NULL));
+ if (DEBUG_PID)
+ fprintf(tracefile, "[%u] ", (int) getpid());
+ if (DEBUG_SIG)
+ fprintf(tracefile, "pending s:%d i:%d(supp:%d) ", pendingsig, intpending, suppressint);
vfprintf(tracefile, fmt, va);
}
shtree(n, 1, NULL, stdout);
}
-#define TRACE(param) trace_printf param
-#define TRACEV(param) trace_vprintf param
-
-#else
-
-#define TRACE(param)
-#define TRACEV(param)
-
#endif /* DEBUG */
* NB: _not_ safe_waitpid, we need to detect EINTR */
pid = waitpid(-1, &status,
(doing_jobctl ? (wait_flags | WUNTRACED) : wait_flags));
- TRACE(("wait returns pid=%d, status=0x%x\n", pid, status));
+ TRACE(("wait returns pid=%d, status=0x%x, errno=%d(%s)\n", pid, status, errno, strerror(errno)));
if (pid <= 0) {
/* If we were doing blocking wait and (probably) got EINTR,
if (newfd < 0) {
/* NTOFD/NFROMFD: copy redir->ndup.dupfd to fd */
if (redir->ndup.dupfd < 0) { /* "fd>&-" */
- close(fd);
+ /* Don't want to trigger debugging */
+ if (fd != -1)
+ close(fd);
} else {
copyfd(redir->ndup.dupfd, fd | COPYFD_EXACT);
}
/*close(fd);*/
copyfd(copy, fd | COPYFD_EXACT);
}
- close(copy);
+ close(copy & ~COPYFD_RESTORE);
}
}
redirlist = rp->next;
pendingsig = 0;
xbarrier();
+ TRACE(("dotrap entered\n"));
for (i = 1, q = gotsig; i < NSIG; i++, q++) {
if (!*q)
continue;
- *q = '\0';
p = trap[i];
+ /* non-trapped SIGINT is handled separately by raise_interrupt,
+ * don't upset it by resetting gotsig[SIGINT-1] */
+ if (i == SIGINT && !p)
+ continue;
+
+ TRACE(("sig %d is active, will run handler '%s'\n", i, p));
+ *q = '\0';
if (!p)
continue;
skip = evalstring(p, SKIPEVAL);
exitstatus = savestatus;
- if (skip)
+ if (skip) {
+ TRACE(("dotrap returns %d\n", skip));
return skip;
+ }
}
+ TRACE(("dotrap returns 0\n"));
return 0;
}
static void
evaltree(union node *n, int flags)
{
-
struct jmploc *volatile savehandler = exception_handler;
struct jmploc jmploc;
int checkexit = 0;
void (*evalfn)(union node *, int);
int status;
+ int int_level;
+
+ SAVE_INT(int_level);
if (n == NULL) {
TRACE(("evaltree(NULL) called\n"));
goto out1;
}
- TRACE(("pid %d, evaltree(%p: %d, %d) called\n",
- getpid(), n, n->type, flags));
+ TRACE(("evaltree(%p: %d, %d) called\n", n, n->type, flags));
exception_handler = &jmploc;
{
int err = setjmp(jmploc.loc);
if (err) {
/* if it was a signal, check for trap handlers */
- if (exception == EXSIG)
+ if (exception == EXSIG) {
+ TRACE(("exception %d (EXSIG) in evaltree, err=%d\n", exception, err));
goto out;
+ }
/* continue on the way out */
+ TRACE(("exception %d in evaltree, propagating err=%d\n", exception, err));
exception_handler = savehandler;
longjmp(exception_handler->loc, err);
}
if (exitstatus == 0) {
n = n->nif.ifpart;
goto evaln;
- } else if (n->nif.elsepart) {
+ }
+ if (n->nif.elsepart) {
n = n->nif.elsepart;
goto evaln;
}
exexit:
raise_exception(EXEXIT);
}
+
+ RESTORE_INT(int_level);
+ TRACE(("leaving evaltree (no interrupts)\n"));
}
#if !defined(__alpha__) || (defined(__GNUC__) && __GNUC__ >= 3)
if (prevfd >= 0)
close(prevfd);
prevfd = pip[0];
- close(pip[1]);
+ /* Don't want to trigger debugging */
+ if (pip[1] != -1)
+ close(pip[1]);
}
if (n->npipe.pipe_backgnd == 0) {
exitstatus = waitforjob(jp);
if (forkshell(jp, cmd, FORK_FG) != 0) {
exitstatus = waitforjob(jp);
INT_ON;
+ TRACE(("forked child exited with %d\n", exitstatus));
break;
}
FORCE_INT_ON;
#endif
status = 0;
- startword = 1;
+ startword = 2;
backslash = 0;
#if ENABLE_ASH_READ_TIMEOUT
if (timeout) /* NB: ensuring end_ms is nonzero */
#endif
STARTSTACKSTR(p);
do {
+ const char *is_ifs;
+
#if ENABLE_ASH_READ_TIMEOUT
if (end_ms) {
struct pollfd pfd[1];
continue;
}
if (!rflag && c == '\\') {
- backslash++;
+ backslash = 1;
continue;
}
if (c == '\n')
break;
- if (startword && *ifs == ' ' && strchr(ifs, c)) {
- continue;
+ is_ifs = strchr(ifs, c);
+ if (startword && is_ifs) {
+ if (isspace(c))
+ continue;
+ /* non-space ifs char */
+ startword--;
+ if (startword == 1) /* first one? */
+ continue;
}
startword = 0;
- if (ap[1] != NULL && strchr(ifs, c) != NULL) {
+ if (ap[1] != NULL && is_ifs) {
+ const char *beg;
STACKSTRNUL(p);
- setvar(*ap, stackblock(), 0);
+ beg = stackblock();
+ setvar(*ap, beg, 0);
ap++;
- startword = 1;
+ /* can we skip one non-space ifs? (2: yes) */
+ startword = isspace(c) ? 2 : 1;
STARTSTACKSTR(p);
- } else {
- put:
- STPUTC(c, p);
+ continue;
}
+ put:
+ STPUTC(c, p);
}
/* end of do {} while: */
#if ENABLE_ASH_READ_NCHARS
#endif
STACKSTRNUL(p);
- /* Remove trailing blanks */
- while ((char *)stackblock() <= --p && strchr(ifs, *p) != NULL)
+ /* Remove trailing space ifs chars */
+ while ((char *)stackblock() <= --p && isspace(*p) && strchr(ifs, *p) != NULL)
*p = '\0';
setvar(*ap, stackblock(), 0);
while (*++ap != NULL)
exception_handler = &jmploc;
#if DEBUG
opentrace();
- trace_puts("Shell args: ");
+ TRACE(("Shell args: "));
trace_puts_args(argv);
#endif
rootpid = getpid();
}
state3:
state = 4;
- if (minusc)
+ if (minusc) {
+ /* evalstring pushes parsefile stack.
+ * Ensure we don't falsely claim that 0 (stdin)
+ * is one of stacked source fds */
+ if (!sflag)
+ g_parsefile->fd = -1;
evalstring(minusc, 0);
+ }
if (sflag || minusc == NULL) {
#if ENABLE_FEATURE_EDITING_SAVEHISTORY
/* NOTREACHED */
}
-#if DEBUG
-const char *applet_name = "debug stuff usage";
-int main(int argc, char **argv)
-{
- return ash_main(argc, argv);
-}
-#endif
-
/*-
* Copyright (c) 1989, 1991, 1993, 1994
smallint fake_mode;
/* these three support $?, $#, and $1 */
smalluint last_return_code;
- char **global_argv;
+ /* is global_argv and global_argv[1..n] malloced? (note: not [0]) */
+ smalluint global_args_malloced;
+ /* how many non-NULL argv's we have. NB: $# + 1 */
int global_argc;
+ char **global_argv;
#if ENABLE_HUSH_LOOPS
unsigned depth_break_continue;
unsigned depth_of_loop;
return dst;
}
-static char **add_strings_to_strings(char **strings, char **add)
+static char **add_strings_to_strings(char **strings, char **add, int need_to_dup)
{
int i;
unsigned count1;
v[count1 + count2] = NULL;
i = count2;
while (--i >= 0)
- v[count1 + i] = add[i];
+ v[count1 + i] = (need_to_dup ? xstrdup(add[i]) : add[i]);
return v;
}
char *v[2];
v[0] = add;
v[1] = NULL;
- return add_strings_to_strings(strings, v);
+ return add_strings_to_strings(strings, v, /*dup:*/ 0);
}
static void putenv_all(char **strings)
* Otherwise, just finish current list[] and start new */
static int o_save_ptr(o_string *o, int n)
{
- if (o->o_glob)
- return o_glob(o, n); /* o_save_ptr_helper is inside */
+ if (o->o_glob) { /* if globbing is requested */
+ /* If o->has_empty_slot, list[n] was already globbed
+ * (if it was requested back then when it was filled)
+ * so don't do that again! */
+ if (!o->has_empty_slot)
+ return o_glob(o, n); /* o_save_ptr_helper is inside */
+ }
return o_save_ptr_helper(o, n);
}
switch (opt) {
case 'c':
G.global_argv = argv + optind;
+ if (!argv[optind]) {
+ /* -c 'script' (no params): prevent empty $0 */
+ *--G.global_argv = argv[0];
+ optind--;
+ } /* else -c 'script' PAR0 PAR1: $0 is PAR0 */
G.global_argc = argc - optind;
opt = parse_and_run_string(optarg, 0 /* parse_flag */);
goto final_return;
return set_local_var(string, 0);
}
-/* built-in 'set [VAR=value]' handler */
+/* built-in 'set' handler
+ * SUSv3 says:
+ * set [-abCefmnuvx] [-h] [-o option] [argument...]
+ * set [+abCefmnuvx] [+h] [+o option] [argument...]
+ * set -- [argument...]
+ * set -o
+ * set +o
+ * Implementations shall support the options in both their hyphen and
+ * plus-sign forms. These options can also be specified as options to sh.
+ * Examples:
+ * Write out all variables and their values: set
+ * Set $1, $2, and $3 and set "$#" to 3: set c a b
+ * Turn on the -x and -v options: set -xv
+ * Unset all positional parameters: set --
+ * Set $1 to the value of x, even if it begins with '-' or '+': set -- "$x"
+ * Set the positional parameters to the expansion of x, even if x expands
+ * with a leading '-' or '+': set -- $x
+ *
+ * So far, we only support "set -- [argument...]" by ignoring all options
+ * (also, "-o option" will be mishandled by taking "option" as parameter #1).
+ */
static int builtin_set(char **argv)
{
- char *temp = argv[1];
struct variable *e;
+ char **pp;
+ char *arg = *++argv;
- if (temp == NULL)
+ if (arg == NULL) {
for (e = G.top_var; e; e = e->next)
puts(e->varstr);
- else
- set_local_var(xstrdup(temp), 0);
+ } else {
+ /* NB: G.global_argv[0] ($0) is never freed/changed */
+
+ if (G.global_args_malloced) {
+ pp = G.global_argv;
+ while (*++pp)
+ free(*pp);
+ G.global_argv[1] = NULL;
+ } else {
+ G.global_args_malloced = 1;
+ pp = xzalloc(sizeof(pp[0]) * 2);
+ pp[0] = G.global_argv[0]; /* retain $0 */
+ G.global_argv = pp;
+ }
+ do {
+ if (arg[0] == '+')
+ continue;
+ if (arg[0] != '-')
+ break;
+ if (arg[1] == '-' && arg[2] == '\0') {
+ argv++;
+ break;
+ }
+ } while ((arg = *++argv) != NULL);
+ /* Now argv[0] is 1st argument */
+
+ /* This realloc's G.global_argv */
+ G.global_argv = pp = add_strings_to_strings(G.global_argv, argv, /*dup:*/ 1);
+ G.global_argc = 1;
+ while (*++pp)
+ G.global_argc++;
+ }
return EXIT_SUCCESS;
}
n = atoi(argv[1]);
}
if (n >= 0 && n < G.global_argc) {
- G.global_argv[n] = G.global_argv[0];
+ if (G.global_args_malloced) {
+ int m = 1;
+ while (m <= n)
+ free(G.global_argv[m++]);
+ }
G.global_argc -= n;
- G.global_argv += n;
+ memmove(&G.global_argv[1], &G.global_argv[n+1],
+ G.global_argc * sizeof(G.global_argv[0]));
return EXIT_SUCCESS;
}
return EXIT_FAILURE;