/* vi: set sw=4 ts=4: */
/*
* ash shell port for busybox
*
* Copyright (c) 1989, 1991, 1993, 1994
* The Regents of the University of California. All rights reserved.
*
* Copyright (c) 1997-2005 Herbert Xu <herbert@gondor.apana.org.au>
* was re-ported from NetBSD and debianized.
*
*
* This code is derived from software contributed to Berkeley by
* Kenneth Almquist.
*
* Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
*
* Original BSD copyright notice is retained at the end of this file.
*/
/*
* rewrite arith.y to micro stack based cryptic algorithm by
* Copyright (c) 2001 Aaron Lehmann <aaronl@vitelus.com>
*
* Modified by Paul Mundt <lethal@linux-sh.org> (c) 2004 to support
* dynamic variables.
*
* Modified by Vladimir Oleynik <dzo@simtreas.ru> (c) 2001-2005 to be
* used in busybox and size optimizations,
* rewrote arith (see notes to this), added locale support,
* rewrote dynamic variables.
*
*/
/*
* The follow 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)
* define DEBUG=2 to compile in and turn on debugging.
*
* When debugging is on, debugging info will be written to ./trace and
* a quit signal will generate a core dump.
*/
#define DEBUG 0
#define IFS_BROKEN
#define PROFILE 0
#if ENABLE_ASH_JOB_CONTROL
#define JOBS 1
#else
#define JOBS 0
#endif
#if DEBUG
#define _GNU_SOURCE
#endif
#include "busybox.h" /* for struct bb_applet */
#include <paths.h>
#include <setjmp.h>
#include <fnmatch.h>
#if JOBS || ENABLE_ASH_READ_NCHARS
#include <termios.h>
#endif
extern char **environ;
#if defined(__uClinux__)
#error "Do not even bother, ash will not run on uClinux"
#endif
/* ============ 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[] = {
"e" "errexit",
"f" "noglob",
"I" "ignoreeof",
"i" "interactive",
"m" "monitor",
"n" "noexec",
"s" "stdin",
"x" "xtrace",
"v" "verbose",
"C" "noclobber",
"a" "allexport",
"b" "notify",
"u" "nounset",
"\0" "vi"
#if DEBUG
,"\0" "nolog"
,"\0" "debug"
#endif
};
#define optletters(n) optletters_optnames[(n)][0]
#define optnames(n) (&optletters_optnames[(n)][1])
enum { NOPTS = ARRAY_SIZE(optletters_optnames) };
static char optlist[NOPTS] ALIGN1;
#define eflag optlist[0]
#define fflag optlist[1]
#define Iflag optlist[2]
#define iflag optlist[3]
#define mflag optlist[4]
#define nflag optlist[5]
#define sflag optlist[6]
#define xflag optlist[7]
#define vflag optlist[8]
#define Cflag optlist[9]
#define aflag optlist[10]
#define bflag optlist[11]
#define uflag optlist[12]
#define viflag optlist[13]
#if DEBUG
#define nolog optlist[14]
#define debug optlist[15]
#endif
/* ============ Misc data */
static char nullstr[1] ALIGN1; /* zero length string */
static const char homestr[] ALIGN1 = "HOME";
static const char snlfmt[] ALIGN1 = "%s\n";
static const char illnum[] ALIGN1 = "Illegal number: %s";
static char *minusc; /* argument to -c option */
/* pid of main shell */
static int rootpid;
/* shell level: 0 for the main shell, 1 for its children, and so on */
static int shlvl;
#define rootshell (!shlvl)
/* trap handler commands */
static char *trap[NSIG];
static smallint isloginsh;
/* current value of signal */
static char sigmode[NSIG - 1];
/* indicates specified signal received */
static char gotsig[NSIG - 1];
static char *arg0; /* value of $0 */
/* ============ Interrupts / exceptions */
/*
* We enclose jmp_buf in a structure so that we can declare pointers to
* jump locations. The global variable handler contains the location to
* jump to when an exception occurs, and the global variable exception
* contains a code identifying the exception. To implement nested
* exception handlers, the user should save the value of handler on entry
* to an inner scope, set handler to point to a jmploc structure for the
* inner scope, and restore handler on exit from the scope.
*/
struct jmploc {
jmp_buf loc;
};
static struct jmploc *exception_handler;
static int exception;
/* exceptions */
#define EXINT 0 /* SIGINT received */
#define EXERROR 1 /* a generic error */
#define EXSHELLPROC 2 /* execute a shell procedure */
#define EXEXEC 3 /* command execution failed */
#define EXEXIT 4 /* exit the shell */
#define EXSIG 5 /* trapped signal in wait(1) */
static volatile int suppressint;
static volatile sig_atomic_t intpending;
/* do we generate EXSIG events */
static int exsig;
/* last pending signal */
static volatile sig_atomic_t pendingsig;
/*
* Sigmode records the current value of the signal handlers for the various
* modes. A value of zero means that the current handler is not known.
* S_HARD_IGN indicates that the signal was ignored on entry to the shell,
*/
#define S_DFL 1 /* default signal handling (SIG_DFL) */
#define S_CATCH 2 /* signal is caught */
#define S_IGN 3 /* signal is ignored (SIG_IGN) */
#define S_HARD_IGN 4 /* signal is ignored permenantly */
#define S_RESET 5 /* temporary - to reset a hard ignored sig */
/*
* These macros allow the user to suspend the handling of interrupt signals
* over a period of time. This is similar to SIGHOLD to or sigblock, but
* much more efficient and portable. (But hacking the kernel is so much
* more fun than worrying about efficiency and portability. :-))
*/
#define INT_OFF \
do { \
suppressint++; \
xbarrier(); \
} while (0)
/*
* Called to raise an exception. Since C doesn't include exceptions, we
* just do a longjmp to the exception handler. The type of exception is
* stored in the global variable "exception".
*/
static void raise_exception(int) ATTRIBUTE_NORETURN;
static void
raise_exception(int e)
{
#if DEBUG
if (exception_handler == NULL)
abort();
#endif
INT_OFF;
exception = e;
longjmp(exception_handler->loc, 1);
}
/*
* Called from trap.c when a SIGINT is received. (If the user specifies
* that SIGINT is to be trapped or ignored using the trap builtin, then
* this routine is not called.) Suppressint is nonzero when interrupts
* are held using the INT_OFF macro. (The test for iflag is just
* defensive programming.)
*/
static void raise_interrupt(void) ATTRIBUTE_NORETURN;
static void
raise_interrupt(void)
{
int i;
sigset_t mask;
intpending = 0;
/* Signal is not automatically re-enabled after it is raised,
* do it ourself */
sigemptyset(&mask);
sigprocmask(SIG_SETMASK, &mask, 0);
/* pendingsig = 0; - now done in onsig() */
i = EXSIG;
if (gotsig[SIGINT - 1] && !trap[SIGINT]) {
if (!(rootshell && iflag)) {
signal(SIGINT, SIG_DFL);
raise(SIGINT);
}
i = EXINT;
}
raise_exception(i);
/* NOTREACHED */
}
#if ENABLE_ASH_OPTIMIZE_FOR_SIZE
static void
int_on(void)
{
if (--suppressint == 0 && intpending) {
raise_interrupt();
}
}
#define INT_ON int_on()
static void
force_int_on(void)
{
suppressint = 0;
if (intpending)
raise_interrupt();
}
#define FORCE_INT_ON force_int_on()
#else
#define INT_ON \
do { \
xbarrier(); \
if (--suppressint == 0 && intpending) \
raise_interrupt(); \
} while (0)
#define FORCE_INT_ON \
do { \
xbarrier(); \
suppressint = 0; \
if (intpending) \
raise_interrupt(); \
} while (0)
#endif /* ASH_OPTIMIZE_FOR_SIZE */
#define SAVE_INT(v) ((v) = suppressint)
#define RESTORE_INT(v) \
do { \
xbarrier(); \
suppressint = (v); \
if (suppressint == 0 && intpending) \
raise_interrupt(); \
} while (0)
#define EXSIGON \
do { \
exsig++; \
xbarrier(); \
if (pendingsig) \
raise_exception(EXSIG); \
} while (0)
/* EXSIG is turned off by evalbltin(). */
/*
* Ignore a signal. Only one usage site - in forkchild()
*/
static void
ignoresig(int signo)
{
if (sigmode[signo - 1] != S_IGN && sigmode[signo - 1] != S_HARD_IGN) {
signal(signo, SIG_IGN);
}
sigmode[signo - 1] = S_HARD_IGN;
}
/*
* Signal handler. Only one usage site - in setsignal()
*/
static void
onsig(int signo)
{
gotsig[signo - 1] = 1;
pendingsig = signo;
if (exsig || (signo == SIGINT && !trap[SIGINT])) {
if (!suppressint) {
pendingsig = 0;
raise_interrupt();
}
intpending = 1;
}
}
/* ============ Stdout/stderr output */
static void
outstr(const char *p, FILE *file)
{
INT_OFF;
fputs(p, file);
INT_ON;
}
static void
flush_stdout_stderr(void)
{
INT_OFF;
fflush(stdout);
fflush(stderr);
INT_ON;
}
static void
flush_stderr(void)
{
INT_OFF;
fflush(stderr);
INT_ON;
}
static void
outcslow(int c, FILE *dest)
{
INT_OFF;
putc(c, dest);
fflush(dest);
INT_ON;
}
static int out1fmt(const char *, ...) __attribute__((__format__(__printf__,1,2)));
static int
out1fmt(const char *fmt, ...)
{
va_list ap;
int r;
INT_OFF;
va_start(ap, fmt);
r = vprintf(fmt, ap);
va_end(ap);
INT_ON;
return r;
}
static int fmtstr(char *, size_t, const char *, ...) __attribute__((__format__(__printf__,3,4)));
static int
fmtstr(char *outbuf, size_t length, const char *fmt, ...)
{
va_list ap;
int ret;
va_start(ap, fmt);
INT_OFF;
ret = vsnprintf(outbuf, length, fmt, ap);
va_end(ap);
INT_ON;
return ret;
}
static void
out1str(const char *p)
{
outstr(p, stdout);
}
static void
out2str(const char *p)
{
outstr(p, stderr);
flush_stderr();
}
/* ============ Parser structures */
/* control characters in argument strings */
#define CTLESC '\201' /* escape next character */
#define CTLVAR '\202' /* variable defn */
#define CTLENDVAR '\203'
#define CTLBACKQ '\204'
#define CTLQUOTE 01 /* ored with CTLBACKQ code if in quotes */
/* CTLBACKQ | CTLQUOTE == '\205' */
#define CTLARI '\206' /* arithmetic expression */
#define CTLENDARI '\207'
#define CTLQUOTEMARK '\210'
/* variable substitution byte (follows CTLVAR) */
#define VSTYPE 0x0f /* type of variable substitution */
#define VSNUL 0x10 /* colon--treat the empty string as unset */
#define VSQUOTE 0x80 /* inside double quotes--suppress splitting */
/* values of VSTYPE field */
#define VSNORMAL 0x1 /* normal variable: $var or ${var} */
#define VSMINUS 0x2 /* ${var-text} */
#define VSPLUS 0x3 /* ${var+text} */
#define VSQUESTION 0x4 /* ${var?message} */
#define VSASSIGN 0x5 /* ${var=text} */
#define VSTRIMRIGHT 0x6 /* ${var%pattern} */
#define VSTRIMRIGHTMAX 0x7 /* ${var%%pattern} */
#define VSTRIMLEFT 0x8 /* ${var#pattern} */
#define VSTRIMLEFTMAX 0x9 /* ${var##pattern} */
#define VSLENGTH 0xa /* ${#var} */
static const char dolatstr[] ALIGN1 = {
CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0'
};
#define NCMD 0
#define NPIPE 1
#define NREDIR 2
#define NBACKGND 3
#define NSUBSHELL 4
#define NAND 5
#define NOR 6
#define NSEMI 7
#define NIF 8
#define NWHILE 9
#define NUNTIL 10
#define NFOR 11
#define NCASE 12
#define NCLIST 13
#define NDEFUN 14
#define NARG 15
#define NTO 16
#define NCLOBBER 17
#define NFROM 18
#define NFROMTO 19
#define NAPPEND 20
#define NTOFD 21
#define NFROMFD 22
#define NHERE 23
#define NXHERE 24
#define NNOT 25
union node;
struct ncmd {
int type;
union node *assign;
union node *args;
union node *redirect;
};
struct npipe {
int type;
int backgnd;
struct nodelist *cmdlist;
};
struct nredir {
int type;
union node *n;
union node *redirect;
};
struct nbinary {
int type;
union node *ch1;
union node *ch2;
};
struct nif {
int type;
union node *test;
union node *ifpart;
union node *elsepart;
};
struct nfor {
int type;
union node *args;
union node *body;
char *var;
};
struct ncase {
int type;
union node *expr;
union node *cases;
};
struct nclist {
int type;
union node *next;
union node *pattern;
union node *body;
};
struct narg {
int type;
union node *next;
char *text;
struct nodelist *backquote;
};
struct nfile {
int type;
union node *next;
int fd;
union node *fname;
char *expfname;
};
struct ndup {
int type;
union node *next;
int fd;
int dupfd;
union node *vname;
};
struct nhere {
int type;
union node *next;
int fd;
union node *doc;
};
struct nnot {
int type;
union node *com;
};
union node {
int type;
struct ncmd ncmd;
struct npipe npipe;
struct nredir nredir;
struct nbinary nbinary;
struct nif nif;
struct nfor nfor;
struct ncase ncase;
struct nclist nclist;
struct narg narg;
struct nfile nfile;
struct ndup ndup;
struct nhere nhere;
struct nnot nnot;
};
struct nodelist {
struct nodelist *next;
union node *n;
};
struct funcnode {
int count;
union node n;
};
/*
* Free a parse tree.
*/
static void
freefunc(struct funcnode *f)
{
if (f && --f->count < 0)
free(f);
}
/* ============ Debugging output */
#if DEBUG
static FILE *tracefile;
static void
trace_printf(const char *fmt, ...)
{
va_list va;
if (debug != 1)
return;
va_start(va, fmt);
vfprintf(tracefile, fmt, va);
va_end(va);
}
static void
trace_vprintf(const char *fmt, va_list va)
{
if (debug != 1)
return;
vfprintf(tracefile, fmt, va);
}
static void
trace_puts(const char *s)
{
if (debug != 1)
return;
fputs(s, tracefile);
}
static void
trace_puts_quoted(char *s)
{
char *p;
char c;
if (debug != 1)
return;
putc('"', tracefile);
for (p = s; *p; p++) {
switch (*p) {
case '\n': c = 'n'; goto backslash;
case '\t': c = 't'; goto backslash;
case '\r': c = 'r'; goto backslash;
case '"': c = '"'; goto backslash;
case '\\': c = '\\'; goto backslash;
case CTLESC: c = 'e'; goto backslash;
case CTLVAR: c = 'v'; goto backslash;
case CTLVAR+CTLQUOTE: c = 'V'; goto backslash;
case CTLBACKQ: c = 'q'; goto backslash;
case CTLBACKQ+CTLQUOTE: c = 'Q'; goto backslash;
backslash:
putc('\\', tracefile);
putc(c, tracefile);
break;
default:
if (*p >= ' ' && *p <= '~')
putc(*p, tracefile);
else {
putc('\\', tracefile);