blob: 4acc30110b99ba1765813ddf7c6316a760712536 [file] [log] [blame]
/* 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.
*/
/*
* 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)
* 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
/* 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
#endif
#include "busybox.h" /* for applet_names */
//TODO: pull in some .h and find out do we have SINGLE_APPLET_MAIN?
//#include "applet_tables.h" doesn't work
#include <paths.h>
#include <setjmp.h>
#include <fnmatch.h>
#include "math.h"
#if defined SINGLE_APPLET_MAIN
/* STANDALONE does not make sense, and won't compile */
#undef CONFIG_FEATURE_SH_STANDALONE
#undef ENABLE_FEATURE_SH_STANDALONE
#undef USE_FEATURE_SH_STANDALONE
#undef SKIP_FEATURE_SH_STANDALONE(...)
#define ENABLE_FEATURE_SH_STANDALONE 0
#define USE_FEATURE_SH_STANDALONE(...)
#define SKIP_FEATURE_SH_STANDALONE(...) __VA_ARGS__
#endif
#ifndef PIPE_BUF
# define PIPE_BUF 4096 /* amount of buffering in a pipe */
#endif
#if defined(__uClinux__)
# error "Do not even bother, ash will not run on NOMMU machine"
#endif
/* ============ Hash table sizes. Configurable. */
#define VTABSIZE 39
#define ATABSIZE 39
#define CMDTABLESIZE 31 /* should be prime */
/* ============ 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) };
/* ============ Misc data */
static const char homestr[] ALIGN1 = "HOME";
static const char snlfmt[] ALIGN1 = "%s\n";
static const char illnum[] ALIGN1 = "Illegal number: %s";
/*
* 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_type
* 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;
};
struct globals_misc {
/* pid of main shell */
int rootpid;
/* shell level: 0 for the main shell, 1 for its children, and so on */
int shlvl;
#define rootshell (!shlvl)
char *minusc; /* argument to -c option */
char *curdir; // = nullstr; /* current working directory */
char *physdir; // = nullstr; /* physical working directory */
char *arg0; /* value of $0 */
struct jmploc *exception_handler;
// disabled by vda: cannot understand how it was supposed to work -
// cannot fix bugs. That's why you have to explain your non-trivial designs!
// /* do we generate EXSIG events */
// int exsig; /* counter */
volatile int suppressint; /* counter */
// TODO: rename
// pendingsig -> pending_sig
// intpending -> pending_int
volatile /*sig_atomic_t*/ smallint intpending; /* 1 = got SIGINT */
/* last pending signal */
volatile /*sig_atomic_t*/ smallint pendingsig;
smallint exception_type; /* kind of exception (0..5) */
/* 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) */
smallint isloginsh;
char nullstr[1]; /* zero length string */
char optlist[NOPTS];
#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
/* trap handler commands */
/*
* 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.
*/
char sigmode[NSIG - 1];
#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 */
/* indicates specified signal received */
uint8_t gotsig[NSIG - 1]; /* offset by 1: "signal" 0 is meaningless */
char *trap[NSIG];
/* Rarely referenced stuff */
#if ENABLE_ASH_RANDOM_SUPPORT
/* Random number generators */
int32_t random_galois_LFSR; /* Galois LFSR (fast but weak). signed! */
uint32_t random_LCG; /* LCG (fast but weak) */
#endif
pid_t backgndpid; /* pid of last background process */
smallint job_warning; /* user was warned about stopped jobs (can be 2, 1 or 0). */
};
extern struct globals_misc *const ash_ptr_to_globals_misc;
#define G_misc (*ash_ptr_to_globals_misc)
#define rootpid (G_misc.rootpid )
#define shlvl (G_misc.shlvl )
#define minusc (G_misc.minusc )
#define curdir (G_misc.curdir )
#define physdir (G_misc.physdir )
#define arg0 (G_misc.arg0 )
#define exception_handler (G_misc.exception_handler)
#define exception_type (G_misc.exception_type )
#define suppressint (G_misc.suppressint )
#define intpending (G_misc.intpending )
//#define exsig (G_misc.exsig )
#define pendingsig (G_misc.pendingsig )
#define isloginsh (G_misc.isloginsh )
#define nullstr (G_misc.nullstr )
#define optlist (G_misc.optlist )
#define sigmode (G_misc.sigmode )
#define gotsig (G_misc.gotsig )
#define trap (G_misc.trap )
#define random_galois_LFSR (G_misc.random_galois_LFSR)
#define random_LCG (G_misc.random_LCG )
#define backgndpid (G_misc.backgndpid )
#define job_warning (G_misc.job_warning)
#define INIT_G_misc() do { \
(*(struct globals_misc**)&ash_ptr_to_globals_misc) = xzalloc(sizeof(G_misc)); \
barrier(); \
curdir = nullstr; \
physdir = nullstr; \
} 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(fd) do { \
int dfd = (fd); \
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 */
while (--maxlen && isdigit(*str))
str++;
return (*str == '\0');
}
/* ============ Interrupts / exceptions */
/*
* These macros allow the user to suspend the handling of interrupt signals
* over a period of time. This is similar to SIGHOLD or to 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_type".
*/
static void raise_exception(int) NORETURN;
static void
raise_exception(int e)
{
#if DEBUG
if (exception_handler == NULL)
abort();
#endif
INT_OFF;
exception_type = 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
* 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) NORETURN;
static void
raise_interrupt(void)
{
int ex_type;
intpending = 0;
/* Signal is not automatically unmasked after it is raised,
* do it ourself - unmask all signals */
sigprocmask_allsigs(SIG_UNBLOCK);
/* pendingsig = 0; - now done in onsig() */
ex_type = EXSIG;
if (gotsig[SIGINT - 1] && !trap[SIGINT]) {
if (!(rootshell && iflag)) {
/* Kill ourself with SIGINT */
signal(SIGINT, SIG_DFL);
raise(SIGINT);
}
ex_type = EXINT;
}
raise_exception(ex_type);
/* NOTREACHED */
}
#if DEBUG
#define raise_interrupt() do { \
TRACE(("raising interrupt on line %d\n", __LINE__)); \
raise_interrupt(); \
} while (0)
#endif
static USE_ASH_OPTIMIZE_FOR_SIZE(inline) void
int_on(void)
{
xbarrier();
if (--suppressint == 0 && intpending) {
raise_interrupt();
}
}
#define INT_ON int_on()
static USE_ASH_OPTIMIZE_FOR_SIZE(inline) void
force_int_on(void)
{
xbarrier();
suppressint = 0;
if (intpending)
raise_interrupt();
}
#define FORCE_INT_ON force_int_on()
#define SAVE_INT(v) ((v) = suppressint)
#define RESTORE_INT(v) do { \
xbarrier(); \
suppressint = (v); \
if (suppressint == 0 && intpending) \
raise_interrupt(); \
} while (0)
/* ============ 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} */
#if ENABLE_ASH_BASH_COMPAT
#define VSSUBSTR 0xc /* ${var:position:length} */
#define VSREPLACE 0xd /* ${var/pattern/replacement} */
#define VSREPLACEALL 0xe /* ${var//pattern/replacement} */
#endif
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
#if ENABLE_ASH_BASH_COMPAT
#define NTO2 17
#endif
#define NCLOBBER 18
#define NFROM 19
#define NFROMTO 20
#define NAPPEND 21
#define NTOFD 22
#define NFROMFD 23
#define NHERE 24
#define NXHERE 25
#define NNOT 26
#define N_NUMBER 27
union node;
struct ncmd {
smallint type; /* Nxxxx */
union node *assign;
union node *args;
union node *redirect;
};
struct npipe {
smallint type;
smallint pipe_backgnd;
struct nodelist *cmdlist;
};
struct nredir {
smallint type;
union node *n;
union node *redirect;
};
struct nbinary {
smallint type;
union node *ch1;
union node *ch2;
};
struct nif {
smallint type;
union node *test;
union node *ifpart;
union node *elsepart;
};
struct nfor {
smallint type;
union node *args;
union node *body;
char *var;
};
struct ncase {
smallint type;
union node *expr;
union node *cases;
};
struct nclist {
smallint type;
union node *next;
union node *pattern;
union node *body;
};
struct narg {
smallint type;
union node *next;
char *text;
struct nodelist *backquote;
};
/* nfile and ndup layout must match!
* NTOFD (>&fdnum) uses ndup structure, but we may discover mid-flight
* that it is actually NTO2 (>&file), and change its type.
*/
struct nfile {
smallint type;
union node *next;
int fd;
int _unused_dupfd;
union node *fname;
char *expfname;
};
struct ndup {
smallint type;
union node *next;
int fd;
int dupfd;
union node *vname;
char *_unused_expfname;
};
struct nhere {
smallint type;
union node *next;
int fd;
union node *doc;
};
struct nnot {
smallint type;
union node *com;
};
union node {
smallint 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;
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);
}
static void
trace_vprintf(const char *fmt, va_list 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);
}
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);
putc(*p >> 6 & 03, tracefile);
putc(*p >> 3 & 07, tracefile);
putc(*p & 07, tracefile);
}
break;
}
}
putc('"', tracefile);
}
static void
trace_puts_args(char **ap)
{
if (debug != 1)
return;
if (!*ap)
return;
while (1) {
trace_puts_quoted(*ap);
if (!*++ap) {
putc('\n', tracefile);
break;
}
putc(' ', tracefile);
}
}
static void
opentrace(void)
{
char s[100];
#ifdef O_APPEND
int flags;
#endif
if (debug != 1) {
if (tracefile)
fflush(tracefile);
/* leave open because libedit might be using it */
return;
}
strcpy(s, "./trace");
if (tracefile) {
if (!freopen(s, "a", tracefile)) {
fprintf(stderr, "Can't re-open %s\n", s);
debug = 0;
return;
}
} else {
tracefile = fopen(s, "a");
if (tracefile == NULL) {
fprintf(stderr, "Can't open %s\n", s);
debug = 0;
return;
}
}
#ifdef O_APPEND
flags = fcntl(fileno(tracefile), F_GETFL);
if (flags >= 0)
fcntl(fileno(tracefile), F_SETFL, flags | O_APPEND);
#endif
setlinebuf(tracefile);
fputs("\nTracing started.\n", tracefile);
}
static void
indent(int amount, char *pfx, FILE *fp)
{
int i;
for (i = 0; i < amount; i++) {
if (pfx && i == amount - 1)
fputs(pfx, fp);
putc('\t', fp);
}
}
/* little circular references here... */
static void shtree(union node *n, int ind, char *pfx, FILE *fp);
static void
sharg(union node *arg, FILE *fp)
{
char *p;
struct nodelist *bqlist;
int subtype;
if (arg->type != NARG) {
out1fmt("<node type %d>\n", arg->type);
abort();
}
bqlist = arg->narg.backquote;
for (p = arg->narg.text; *p; p++) {
switch (*p) {
case CTLESC:
putc(*++p, fp);
break;
case CTLVAR:
putc('$', fp);
putc('{', fp);
subtype = *++p;
if (subtype == VSLENGTH)
putc('#', fp);
while (*p != '=')
putc(*p++, fp);
if (subtype & VSNUL)
putc(':', fp);
switch (subtype & VSTYPE) {
case VSNORMAL:
putc('}', fp);
break;
case VSMINUS:
putc('-', fp);
break;
case VSPLUS:
putc('+', fp);
break;
case VSQUESTION:
putc('?', fp);
break;
case VSASSIGN:
putc('=', fp);
break;
case VSTRIMLEFT:
putc('#', fp);
break;
case VSTRIMLEFTMAX:
putc('#', fp);
putc('#', fp);
break;
case VSTRIMRIGHT:
putc('%', fp);
break;
case VSTRIMRIGHTMAX:
putc('%', fp);
putc('%', fp);
break;
case VSLENGTH:
break;
default:
out1fmt("<subtype %d>", subtype);
}
break;
case CTLENDVAR:
putc('}', fp);
break;
case CTLBACKQ:
case CTLBACKQ|CTLQUOTE:
putc('$', fp);
putc('(', fp);
shtree(bqlist->n, -1, NULL, fp);
putc(')', fp);
break;
default:
putc(*p, fp);
break;
}
}
}
static void
shcmd(union node *cmd, FILE *fp)
{
union node *np;
int first;
const char *s;
int dftfd;
first = 1;
for (np = cmd->ncmd.args; np; np = np->narg.next) {
if (!first)
putc(' ', fp);
sharg(np, fp);
first = 0;
}
for (np = cmd->ncmd.redirect; np; np = np->nfile.next) {
if (!first)
putc(' ', fp);
dftfd = 0;
switch (np->nfile.type) {
case NTO: s = ">>"+1; dftfd = 1; break;
case NCLOBBER: s = ">|"; dftfd = 1; break;
case NAPPEND: s = ">>"; dftfd = 1; break;
#if ENABLE_ASH_BASH_COMPAT
case NTO2:
#endif
case NTOFD: s = ">&"; dftfd = 1; break;
case NFROM: s = "<"; break;
case NFROMFD: s = "<&"; break;
case NFROMTO: s = "<>"; break;
default: s = "*error*"; break;
}
if (np->nfile.fd != dftfd)
fprintf(fp, "%d", np->nfile.fd);
fputs(s, fp);
if (np->nfile.type == NTOFD || np->nfile.type == NFROMFD) {
fprintf(fp, "%d", np->ndup.dupfd);
} else {
sharg(np->nfile.fname, fp);
}
first = 0;
}
}
static void
shtree(union node *n, int ind, char *pfx, FILE *fp)
{
struct nodelist *lp;
const char *s;
if (n == NULL)
return;
indent(ind, pfx, fp);
switch (n->type) {
case NSEMI:
s = "; ";
goto binop;
case NAND:
s = " && ";
goto binop;
case NOR:
s = " || ";
binop:
shtree(n->nbinary.ch1, ind, NULL, fp);
/* if (ind < 0) */
fputs(s, fp);
shtree(n->nbinary.ch2, ind, NULL, fp);
break;
case NCMD:
shcmd(n, fp);
if (ind >= 0)
putc('\n', fp);
break;
case NPIPE:
for (lp = n->npipe.cmdlist; lp; lp = lp->next) {
shcmd(lp->n, fp);
if (lp->next)
fputs(" | ", fp);
}
if (n->npipe.pipe_backgnd)
fputs(" &", fp);
if (ind >= 0)
putc('\n', fp);
break;
default:
fprintf(fp, "<node type %d>", n->type);
if (ind >= 0)
putc('\n', fp);
break;
}
}
static void
showtree(union node *n)
{
trace_puts("showtree called\n");
shtree(n, 1, NULL, stdout);
}
#endif /* DEBUG */
/* ============ Parser data */
/*
* ash_vmsg() needs parsefile->fd, hence parsefile definition is moved up.
*/
struct strlist {
struct strlist *next;
char *text;
};
struct alias;
struct strpush {
struct strpush *prev; /* preceding string on stack */
char *prev_string;
int prev_left_in_line;
#if ENABLE_ASH_ALIAS
struct alias *ap; /* if push was associated with an alias */
#endif
char *string; /* remember the string since it may change */
};
struct parsefile {
struct parsefile *prev; /* preceding file on stack */
int linno; /* current line */
int fd; /* file descriptor (or -1 if string) */
int left_in_line; /* number of chars left in this line */
int left_in_buffer; /* number of chars left in this buffer past the line */
char *next_to_pgetc; /* next char in buffer */
char *buf; /* input buffer */
struct strpush *strpush; /* for pushing strings at this level */
struct strpush basestrpush; /* so pushing one is fast */
};
static struct parsefile basepf; /* top level input file */
static struct parsefile *g_parsefile = &basepf; /* current input file */
static int startlinno; /* line # where last token started */
static char *commandname; /* currently executing command */
static struct strlist *cmdenviron; /* environment for builtin command */
static uint8_t exitstatus; /* exit status of last command */
/* ============ Message printing */
static void
ash_vmsg(const char *msg, va_list ap)
{
fprintf(stderr, "%s: ", arg0);
if (commandname) {
if (strcmp(arg0, commandname))
fprintf(stderr, "%s: ", commandname);
if (!iflag || g_parsefile->fd)
fprintf(stderr, "line %d: ", startlinno);
}
vfprintf(stderr, msg, ap);
outcslow('\n', stderr);
}
/*
* Exverror is called to raise the error exception. If the second argument
* is not NULL then error prints an error message using printf style
* formatting. It then raises the error exception.
*/
static void ash_vmsg_and_raise(int, const char *, va_list) NORETURN;
static void
ash_vmsg_and_raise(int cond, const char *msg, va_list ap)
{
#if DEBUG
if (msg) {
TRACE(("ash_vmsg_and_raise(%d, \"", cond));
TRACEV((msg, ap));
TRACE(("\") pid=%d\n", getpid()));
} else
TRACE(("ash_vmsg_and_raise(%d, NULL) pid=%d\n", cond, getpid()));
if (msg)
#endif
ash_vmsg(msg, ap);
flush_stdout_stderr();
raise_exception(cond);
/* NOTREACHED */
}
static void ash_msg_and_raise_error(const char *, ...) NORETURN;
static void
ash_msg_and_raise_error(const char *msg, ...)
{
va_list ap;
va_start(ap, msg);
ash_vmsg_and_raise(EXERROR, msg, ap);
/* NOTREACHED */
va_end(ap);
}
static void raise_error_syntax(const char *) NORETURN;
static void
raise_error_syntax(const char *msg)
{
ash_msg_and_raise_error("syntax error: %s", msg);
/* NOTREACHED */
}
static void ash_msg_and_raise(int, const char *, ...) NORETURN;
static void
ash_msg_and_raise(int cond, const char *msg, ...)
{
va_list ap;
va_start(ap, msg);
ash_vmsg_and_raise(cond, msg, ap);
/* NOTREACHED */
va_end(ap);
}
/*
* error/warning routines for external builtins
*/
static void
ash_msg(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
ash_vmsg(fmt, ap);
va_end(ap);
}
/*
* Return a string describing an error. The returned string may be a
* pointer to a static buffer that will be overwritten on the next call.
* Action describes the operation that got the error.
*/
static const char *
errmsg(int e, const char *em)
{
if (e == ENOENT || e == ENOTDIR) {
return em;
}
return strerror(e);
}
/* ============ Memory allocation */
/*
* It appears that grabstackstr() will barf with such alignments
* because stalloc() will return a string allocated in a new stackblock.
*/
#define SHELL_ALIGN(nbytes) (((nbytes) + SHELL_SIZE) & ~SHELL_SIZE)
enum {
/* Most machines require the value returned from malloc to be aligned
* in some way. The following macro will get this right
* on many machines. */
SHELL_SIZE = sizeof(union {int i; char *cp; double d; }) - 1,
/* Minimum size of a block */
MINSIZE = SHELL_ALIGN(504),
};
struct stack_block {
struct stack_block *prev;
char space[MINSIZE];
};
struct stackmark {
struct stack_block *stackp;
char *stacknxt;
size_t stacknleft;
struct stackmark *marknext;
};
struct globals_memstack {
struct stack_block *g_stackp; // = &stackbase;
struct stackmark *markp;
char *g_stacknxt; // = stackbase.space;
char *sstrend; // = stackbase.space + MINSIZE;
size_t g_stacknleft; // = MINSIZE;
int herefd; // = -1;
struct stack_block stackbase;
};
extern struct globals_memstack *const ash_ptr_to_globals_memstack;
#define G_memstack (*ash_ptr_to_globals_memstack)
#define g_stackp (G_memstack.g_stackp )
#define markp (G_memstack.markp )
#define g_stacknxt (G_memstack.g_stacknxt )
#define sstrend (G_memstack.sstrend )
#define g_stacknleft (G_memstack.g_stacknleft)
#define herefd (G_memstack.herefd )
#define stackbase (G_memstack.stackbase )
#define INIT_G_memstack() do { \
(*(struct globals_memstack**)&ash_ptr_to_globals_memstack) = xzalloc(sizeof(G_memstack)); \
barrier(); \
g_stackp = &stackbase; \
g_stacknxt = stackbase.space; \
g_stacknleft = MINSIZE; \
sstrend = stackbase.space + MINSIZE; \
herefd = -1; \
} while (0)
#define stackblock() ((void *)g_stacknxt)
#define stackblocksize() g_stacknleft
static void *
ckrealloc(void * p, size_t nbytes)
{
p = realloc(p, nbytes);
if (!p)
ash_msg_and_raise_error(bb_msg_memory_exhausted);
return p;
}
static void *
ckmalloc(size_t nbytes)
{
return ckrealloc(NULL, nbytes);
}
static void *
ckzalloc(size_t nbytes)
{
return memset(ckmalloc(nbytes), 0, nbytes);
}
/*
* Make a copy of a string in safe storage.
*/
static char *
ckstrdup(const char *s)
{
char *p = strdup(s);
if (!p)
ash_msg_and_raise_error(bb_msg_memory_exhausted);
return p;
}
/*
* Parse trees for commands are allocated in lifo order, so we use a stack
* to make this more efficient, and also to avoid all sorts of exception
* handling code to handle interrupts in the middle of a parse.
*
* The size 504 was chosen because the Ultrix malloc handles that size
* well.
*/
static void *
stalloc(size_t nbytes)
{
char *p;
size_t aligned;
aligned = SHELL_ALIGN(nbytes);
if (aligned > g_stacknleft) {
size_t len;
size_t blocksize;
struct stack_block *sp;
blocksize = aligned;
if (blocksize < MINSIZE)
blocksize = MINSIZE;
len = sizeof(struct stack_block) - MINSIZE + blocksize;
if (len < blocksize)
ash_msg_and_raise_error(bb_msg_memory_exhausted);
INT_OFF;
sp = ckmalloc(len);
sp->prev = g_stackp;
g_stacknxt = sp->space;
g_stacknleft = blocksize;
sstrend = g_stacknxt + blocksize;
g_stackp = sp;
INT_ON;
}
p = g_stacknxt;
g_stacknxt += aligned;
g_stacknleft -= aligned;
return p;
}
static void *
stzalloc(size_t nbytes)
{
return memset(stalloc(nbytes), 0, nbytes);
}
static void
stunalloc(void *p)
{
#if DEBUG
if (!p || (g_stacknxt < (char *)p) || ((char *)p < g_stackp->space)) {
write(STDERR_FILENO, "stunalloc\n", 10);
abort();
}
#endif
g_stacknleft += g_stacknxt - (char *)p;
g_stacknxt = p;
}
/*
* Like strdup but works with the ash stack.
*/
static char *
ststrdup(const char *p)
{
size_t len = strlen(p) + 1;
return memcpy(stalloc(len), p, len);
}
static void
setstackmark(struct stackmark *mark)
{
mark->stackp = g_stackp;
mark->stacknxt = g_stacknxt;
mark->stacknleft = g_stacknleft;
mark->marknext = markp;
markp = mark;
}
static void
popstackmark(struct stackmark *mark)
{
struct stack_block *sp;
if (!mark->stackp)
return;
INT_OFF;
markp = mark->marknext;
while (g_stackp != mark->stackp) {
sp = g_stackp;
g_stackp = sp->prev;
free(sp);
}
g_stacknxt = mark->stacknxt;
g_stacknleft = mark->stacknleft;
sstrend = mark->stacknxt + mark->stacknleft;
INT_ON;
}
/*
* When the parser reads in a string, it wants to stick the string on the
* stack and only adjust the stack pointer when it knows how big the
* string is. Stackblock (defined in stack.h) returns a pointer to a block
* of space on top of the stack and stackblocklen returns the length of
* this block. Growstackblock will grow this space by at least one byte,
* possibly moving it (like realloc). Grabstackblock actually allocates the
* part of the block that has been used.
*/
static void
growstackblock(void)
{
size_t newlen;
newlen = g_stacknleft * 2;
if (newlen < g_stacknleft)
ash_msg_and_raise_error(bb_msg_memory_exhausted);
if (newlen < 128)
newlen += 128;
if (g_stacknxt == g_stackp->space && g_stackp != &stackbase) {
struct stack_block *oldstackp;
struct stackmark *xmark;
struct stack_block *sp;
struct stack_block *prevstackp;
size_t grosslen;
INT_OFF;
oldstackp = g_stackp;
sp = g_stackp;
prevstackp = sp->prev;
grosslen = newlen + sizeof(struct stack_block) - MINSIZE;
sp = ckrealloc(sp, grosslen);
sp->prev = prevstackp;
g_stackp = sp;
g_stacknxt = sp->space;
g_stacknleft = newlen;
sstrend = sp->space + newlen;
/*
* Stack marks pointing to the start of the old block
* must be relocated to point to the new block
*/
xmark = markp;
while (xmark != NULL && xmark->stackp == oldstackp) {
xmark->stackp = g_stackp;
xmark->stacknxt = g_stacknxt;
xmark->stacknleft = g_stacknleft;
xmark = xmark->marknext;
}
INT_ON;
} else {
char *oldspace = g_stacknxt;
size_t oldlen = g_stacknleft;
char *p = stalloc(newlen);
/* free the space we just allocated */
g_stacknxt = memcpy(p, oldspace, oldlen);
g_stacknleft += newlen;
}
}
static void
grabstackblock(size_t len)
{
len = SHELL_ALIGN(len);
g_stacknxt += len;
g_stacknleft -= len;
}
/*
* The following routines are somewhat easier to use than the above.
* The user declares a variable of type STACKSTR, which may be declared
* to be a register. The macro STARTSTACKSTR initializes things. Then
* the user uses the macro STPUTC to add characters to the string. In
* effect, STPUTC(c, p) is the same as *p++ = c except that the stack is
* grown as necessary. When the user is done, she can just leave the
* string there and refer to it using stackblock(). Or she can allocate
* the space for it using grabstackstr(). If it is necessary to allow
* someone else to use the stack temporarily and then continue to grow
* the string, the user should use grabstack to allocate the space, and
* then call ungrabstr(p) to return to the previous mode of operation.
*
* USTPUTC is like STPUTC except that it doesn't check for overflow.
* CHECKSTACKSPACE can be called before USTPUTC to ensure that there
* is space for at least one character.
*/
static void *
growstackstr(void)
{
size_t len = stackblocksize();
if (herefd >= 0 && len >= 1024) {
full_write(herefd, stackblock(), len);
return stackblock();
}
growstackblock();
return (char *)stackblock() + len;
}
/*
* Called from CHECKSTRSPACE.
*/
static char *
makestrspace(size_t newlen, char *p)
{
size_t len = p - g_stacknxt;
size_t size = stackblocksize();
for (;;) {
size_t nleft;
size = stackblocksize();
nleft = size - len;
if (nleft >= newlen)
break;
growstackblock();
}
return (char *)stackblock() + len;
}
static char *
stack_nputstr(const char *s, size_t n, char *p)
{
p = makestrspace(n, p);
p = (char *)memcpy(p, s, n) + n;
return p;
}
static char *
stack_putstr(const char *s, char *p)
{
return stack_nputstr(s, strlen(s), p);
}
static char *
_STPUTC(int c, char *p)
{
if (p == sstrend)
p = growstackstr();
*p++ = c;
return p;
}
#define STARTSTACKSTR(p) ((p) = stackblock())
#define STPUTC(c, p) ((p) = _STPUTC((c), (p)))
#define CHECKSTRSPACE(n, p) do { \
char *q = (p); \
size_t l = (n); \
size_t m = sstrend - q; \
if (l > m) \
(p) = makestrspace(l, q); \
} while (0)
#define USTPUTC(c, p) (*(p)++ = (c))
#define STACKSTRNUL(p) do { \
if ((p) == sstrend) \
(p) = growstackstr(); \
*(p) = '\0'; \
} while (0)
#define STUNPUTC(p) (--(p))
#define STTOPC(p) ((p)[-1])
#define STADJUST(amount, p) ((p) += (amount))
#define grabstackstr(p) stalloc((char *)(p) - (char *)stackblock())
#define ungrabstackstr(s, p) stunalloc(s)
#define stackstrend() ((void *)sstrend)
/* ============ String helpers */
/*
* prefix -- see if pfx is a prefix of string.
*/
static char *
prefix(const char *string, const char *pfx)
{
while (*pfx) {
if (*pfx++ != *string++)
return NULL;
}
return (char *) string;
}
/*
* Check for a valid number. This should be elsewhere.
*/
static int
is_number(const char *p)
{
do {
if (!isdigit(*p))
return 0;
} while (*++p != '\0');
return 1;
}
/*
* Convert a string of digits to an integer, printing an error message on
* failure.
*/
static int
number(const char *s)
{
if (!is_number(s))
ash_msg_and_raise_error(illnum, s);
return atoi(s);
}
/*
* Produce a possibly single quoted string suitable as input to the shell.
* The return string is allocated on the stack.
*/
static char *
single_quote(const char *s)
{
char *p;
STARTSTACKSTR(p);
do {
char *q;
size_t len;
len = strchrnul(s, '\'') - s;
q = p = makestrspace(len + 3, p);
*q++ = '\'';
q = (char *)memcpy(q, s, len) + len;
*q++ = '\'';
s += len;
STADJUST(q - p, p);
len = strspn(s, "'");
if (!len)
break;
q = p = makestrspace(len + 3, p);
*q++ = '"';
q = (char *)memcpy(q, s, len) + len;
*q++ = '"';
s += len;
STADJUST(q - p, p);
} while (*s);
USTPUTC(0, p);
return stackblock();
}
/* ============ nextopt */
static char **argptr; /* argument list for builtin commands */
static char *optionarg; /* set by nextopt (like getopt) */
static char *optptr; /* used by nextopt */
/*
* XXX - should get rid of. Have all builtins use getopt(3).
* The library getopt must have the BSD extension static variable
* "optreset", otherwise it can't be used within the shell safely.
*
* Standard option processing (a la getopt) for builtin routines.
* The only argument that is passed to nextopt is the option string;
* the other arguments are unnecessary. It returns the character,
* or '\0' on end of input.
*/
static int
nextopt(const char *optstring)
{
char *p;
const char *q;
char c;
p = optptr;
if (p == NULL || *p == '\0') {
/* We ate entire "-param", take next one */
p = *argptr;
if (p == NULL)
return '\0';
if (*p != '-')
return '\0';
if (*++p == '\0') /* just "-" ? */
return '\0';
argptr++;
if (LONE_DASH(p)) /* "--" ? */
return '\0';
/* p => next "-param" */
}
/* p => some option char in the middle of a "-param" */
c = *p++;
for (q = optstring; *q != c;) {
if (*q == '\0')
ash_msg_and_raise_error("illegal option -%c", c);
if (*++q == ':')
q++;
}
if (*++q == ':') {
if (*p == '\0') {
p = *argptr++;
if (p == NULL)
ash_msg_and_raise_error("no arg for -%c option", c);
}
optionarg = p;
p = NULL;
}
optptr = p;
return c;
}
/* ============ Shell variables */
/*
* The parsefile structure pointed to by the global variable parsefile
* contains information about the current file being read.
*/
struct shparam {
int nparam; /* # of positional parameters (without $0) */
#if ENABLE_ASH_GETOPTS
int optind; /* next parameter to be processed by getopts */
int optoff; /* used by getopts */
#endif
unsigned char malloced; /* if parameter list dynamically allocated */
char **p; /* parameter list */
};
/*
* Free the list of positional parameters.
*/
static void
freeparam(volatile struct shparam *param)
{
if (param->malloced) {
char **ap, **ap1;
ap = ap1 = param->p;
while (*ap)
free(*ap++);
free(ap1);
}
}
#if ENABLE_ASH_GETOPTS
static void getoptsreset(const char *value);
#endif
struct var {
struct var *next; /* next entry in hash list */
int flags; /* flags are defined above */
const char *text; /* name=value */
void (*func)(const char *); /* function to be called when */
/* the variable gets set/unset */
};
struct localvar {
struct localvar *next; /* next local variable in list */
struct var *vp; /* the variable that was made local */
int flags; /* saved flags */
const char *text; /* saved text */
};
/* flags */
#define VEXPORT 0x01 /* variable is exported */
#define VREADONLY 0x02 /* variable cannot be modified */
#define VSTRFIXED 0x04 /* variable struct is statically allocated */
#define VTEXTFIXED 0x08 /* text is statically allocated */
#define VSTACK 0x10 /* text is allocated on the stack */
#define VUNSET 0x20 /* the variable is not set */
#define VNOFUNC 0x40 /* don't call the callback function */
#define VNOSET 0x80 /* do not set variable - just readonly test */
#define VNOSAVE 0x100 /* when text is on the heap before setvareq */
#if ENABLE_ASH_RANDOM_SUPPORT
# define VDYNAMIC 0x200 /* dynamic variable */
#else
# define VDYNAMIC 0
#endif
#ifdef IFS_BROKEN
static const char defifsvar[] ALIGN1 = "IFS= \t\n";
#define defifs (defifsvar + 4)
#else
static const char defifs[] ALIGN1 = " \t\n";
#endif
/* Need to be before varinit_data[] */
#if ENABLE_LOCALE_SUPPORT
static void
change_lc_all(const char *value)
{
if (value && *value != '\0')
setlocale(LC_ALL, value);
}
static void
change_lc_ctype(const char *value)
{
if (value && *value != '\0')
setlocale(LC_CTYPE, value);
}
#endif
#if ENABLE_ASH_MAIL
static void chkmail(void);
static void changemail(const char *);
#endif
static void changepath(const char *);
#if ENABLE_ASH_RANDOM_SUPPORT
static void change_random(const char *);
#endif
static const struct {
int flags;
const char *text;
void (*func)(const char *);
} varinit_data[] = {
#ifdef IFS_BROKEN
{ VSTRFIXED|VTEXTFIXED , defifsvar , NULL },
#else
{ VSTRFIXED|VTEXTFIXED|VUNSET, "IFS\0" , NULL },
#endif
#if ENABLE_ASH_MAIL
{ VSTRFIXED|VTEXTFIXED|VUNSET, "MAIL\0" , changemail },
{ VSTRFIXED|VTEXTFIXED|VUNSET, "MAILPATH\0", changemail },
#endif
{ VSTRFIXED|VTEXTFIXED , bb_PATH_root_path, changepath },
{ VSTRFIXED|VTEXTFIXED , "PS1=$ " , NULL },
{ VSTRFIXED|VTEXTFIXED , "PS2=> " , NULL },
{ VSTRFIXED|VTEXTFIXED , "PS4=+ " , NULL },
#if ENABLE_ASH_GETOPTS
{ VSTRFIXED|VTEXTFIXED , "OPTIND=1" , getoptsreset },
#endif
#if ENABLE_ASH_RANDOM_SUPPORT
{ VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM\0", change_random },
#endif
#if ENABLE_LOCALE_SUPPORT
{ VSTRFIXED|VTEXTFIXED|VUNSET, "LC_ALL\0" , change_lc_all },
{ VSTRFIXED|VTEXTFIXED|VUNSET, "LC_CTYPE\0", change_lc_ctype },
#endif
#if ENABLE_FEATURE_EDITING_SAVEHISTORY
{ VSTRFIXED|VTEXTFIXED|VUNSET, "HISTFILE\0", NULL },
#endif
};
struct redirtab;
struct globals_var {
struct shparam shellparam; /* $@ current positional parameters */
struct redirtab *redirlist;
int g_nullredirs;
int preverrout_fd; /* save fd2 before print debug if xflag is set. */
struct var *vartab[VTABSIZE];
struct var varinit[ARRAY_SIZE(varinit_data)];
};
extern struct globals_var *const ash_ptr_to_globals_var;
#define G_var (*ash_ptr_to_globals_var)
#define shellparam (G_var.shellparam )
//#define redirlist (G_var.redirlist )
#define g_nullredirs (G_var.g_nullredirs )
#define preverrout_fd (G_var.preverrout_fd)
#define vartab (G_var.vartab )
#define varinit (G_var.varinit )
#define INIT_G_var() do { \
unsigned i; \
(*(struct globals_var**)&ash_ptr_to_globals_var) = xzalloc(sizeof(G_var)); \
barrier(); \
for (i = 0; i < ARRAY_SIZE(varinit_data); i++) { \
varinit[i].flags = varinit_data[i].flags; \
varinit[i].text = varinit_data[i].text; \
varinit[i].func = varinit_data[i].func; \
} \
} while (0)
#define vifs varinit[0]
#if ENABLE_ASH_MAIL
# define vmail (&vifs)[1]
# define vmpath (&vmail)[1]
# define vpath (&vmpath)[1]
#else
# define vpath (&vifs)[1]
#endif
#define vps1 (&vpath)[1]
#define vps2 (&vps1)[1]
#define vps4 (&vps2)[1]
#if ENABLE_ASH_GETOPTS
# define voptind (&vps4)[1]
# if ENABLE_ASH_RANDOM_SUPPORT
# define vrandom (&voptind)[1]
# endif
#else
# if ENABLE_ASH_RANDOM_SUPPORT
# define vrandom (&vps4)[1]
# endif
#endif
/*
* The following macros access the values of the above variables.
* They have to skip over the name. They return the null string
* for unset variables.
*/
#define ifsval() (vifs.text + 4)
#define ifsset() ((vifs.flags & VUNSET) == 0)
#if ENABLE_ASH_MAIL
# define mailval() (vmail.text + 5)
# define mpathval() (vmpath.text + 9)
# define mpathset() ((vmpath.flags & VUNSET) == 0)
#endif
#define pathval() (vpath.text + 5)
#define ps1val() (vps1.text + 4)
#define ps2val() (vps2.text + 4)
#define ps4val() (vps4.text + 4)
#if ENABLE_ASH_GETOPTS
# define optindval() (voptind.text + 7)
#endif
#define is_name(c) ((c) == '_' || isalpha((unsigned char)(c)))
#define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c)))
#if ENABLE_ASH_GETOPTS
static void
getoptsreset(const char *value)
{
shellparam.optind = number(value);
shellparam.optoff = -1;
}
#endif
/*
* Return of a legal variable name (a letter or underscore followed by zero or
* more letters, underscores, and digits).
*/
static char *
endofname(const char *name)
{
char *p;
p = (char *) name;
if (!is_name(*p))
return p;
while (*++p) {
if (!is_in_name(*p))
break;
}
return p;
}
/*
* Compares two strings up to the first = or '\0'. The first
* string must be terminated by '='; the second may be terminated by
* either '=' or '\0'.
*/
static int
varcmp(const char *p, const char *q)
{
int c, d;
while ((c = *p) == (d = *q)) {
if (!c || c == '=')
goto out;
p++;
q++;
}
if (c == '=')
c = '\0';
if (d == '=')
d = '\0';
out:
return c - d;
}
static int
varequal(const char *a, const char *b)
{
return !varcmp(a, b);
}
/*
* Find the appropriate entry in the hash table from the name.
*/
static struct var **
hashvar(const char *p)
{
unsigned hashval;
hashval = ((unsigned char) *p) << 4;
while (*p && *p != '=')
hashval += (unsigned char) *p++;
return &vartab[hashval % VTABSIZE];
}
static int
vpcmp(const void *a, const void *b)
{
return varcmp(*(const char **)a, *(const char **)b);
}
/*
* This routine initializes the builtin variables.
*/
static void
initvar(void)
{
struct var *vp;
struct var *end;
struct var **vpp;
/*
* PS1 depends on uid
*/
#if ENABLE_FEATURE_EDITING && ENABLE_FEATURE_EDITING_FANCY_PROMPT
vps1.text = "PS1=\\w \\$ ";
#else
if (!geteuid())
vps1.text = "PS1=# ";
#endif
vp = varinit;
end = vp + ARRAY_SIZE(varinit);
do {
vpp = hashvar(vp->text);
vp->next = *vpp;
*vpp = vp;
} while (++vp < end);
}
static struct var **
findvar(struct var **vpp, const char *name)
{
for (; *vpp; vpp = &(*vpp)->next) {
if (varequal((*vpp)->text, name)) {
break;
}
}
return vpp;
}
/*
* Find the value of a variable. Returns NULL if not set.
*/
static const char *
lookupvar(const char *name)
{
struct var *v;
v = *findvar(hashvar(name), name);
if (v) {
#if ENABLE_ASH_RANDOM_SUPPORT
/*
* Dynamic variables are implemented roughly the same way they are
* in bash. Namely, they're "special" so long as they aren't unset.
* As soon as they're unset, they're no longer dynamic, and dynamic
* lookup will no longer happen at that point. -- PFM.
*/
if ((v->flags & VDYNAMIC))
(*v->func)(NULL);
#endif
if (!(v->flags & VUNSET))
return strchrnul(v->text, '=') + 1;
}
return NULL;
}
/*
* Search the environment of a builtin command.
*/
static const char *
bltinlookup(const char *name)
{
struct strlist *sp;
for (sp = cmdenviron; sp; sp = sp->next) {
if (varequal(sp->text, name))
return strchrnul(sp->text, '=') + 1;
}
return lookupvar(name);
}
/*
* Same as setvar except that the variable and value are passed in
* the first argument as name=value. Since the first argument will
* be actually stored in the table, it should not be a string that
* will go away.
* Called with interrupts off.
*/
static void
setvareq(char *s, int flags)
{
struct var *vp, **vpp;
vpp = hashvar(s);
flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1));
vp = *findvar(vpp, s);
if (vp) {
if ((vp->flags & (VREADONLY|VDYNAMIC)) == VREADONLY) {
const char *n;
if (flags & VNOSAVE)
free(s);
n = vp->text;
ash_msg_and_raise_error("%.*s: is read only", strchrnul(n, '=') - n, n);
}
if (flags & VNOSET)
return;
if (vp->func && (flags & VNOFUNC) == 0)
(*vp->func)(strchrnul(s, '=') + 1);
if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
free((char*)vp->text);
flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET);
} else {
if (flags & VNOSET)
return;
/* not found */
vp = ckzalloc(sizeof(*vp));
vp->next = *vpp;
/*vp->func = NULL; - ckzalloc did it */
*vpp = vp;
}
if (!(flags & (VTEXTFIXED|VSTACK|VNOSAVE)))
s = ckstrdup(s);
vp->text = s;
vp->flags = flags;
}
/*
* Set the value of a variable. The flags argument is ored with the
* flags of the variable. If val is NULL, the variable is unset.
*/
static void
setvar(const char *name, const char *val, int flags)
{
char *p, *q;
size_t namelen;
char *nameeq;
size_t vallen;
q = endofname(name);
p = strchrnul(q, '=');
namelen = p - name;
if (!namelen || p != q)
ash_msg_and_raise_error("%.*s: bad variable name", namelen, name);
vallen = 0;
if (val == NULL) {
flags |= VUNSET;
} else {
vallen = strlen(val);
}
INT_OFF;
nameeq = ckmalloc(namelen + vallen + 2);
p = (char *)memcpy(nameeq, name, namelen) + namelen;
if (val) {
*p++ = '=';
p = (char *)memcpy(p, val, vallen) + vallen;
}
*p = '\0';
setvareq(nameeq, flags | VNOSAVE);
INT_ON;
}
#if ENABLE_ASH_GETOPTS
/*
* Safe version of setvar, returns 1 on success 0 on failure.
*/
static int
setvarsafe(const char *name, const char *val, int flags)
{
int err;
volatile int saveint;
struct jmploc *volatile savehandler = exception_handler;
struct jmploc jmploc;
SAVE_INT(saveint);
if (setjmp(jmploc.loc))
err = 1;
else {
exception_handler = &jmploc;
setvar(name, val, flags);
err = 0;
}
exception_handler = savehandler;
RESTORE_INT(saveint);
return err;
}
#endif
/*
* Unset the specified variable.
*/
static int
unsetvar(const char *s)
{
struct var **vpp;
struct var *vp;
int retval;
vpp = findvar(hashvar(s), s);
vp = *vpp;
retval = 2;
if (vp) {
int flags = vp->flags;
retval = 1;
if (flags & VREADONLY)
goto out;
#if ENABLE_ASH_RANDOM_SUPPORT
vp->flags &= ~VDYNAMIC;
#endif
if (flags & VUNSET)
goto ok;
if ((flags & VSTRFIXED) == 0) {
INT_OFF;
if ((flags & (VTEXTFIXED|VSTACK)) == 0)
free((char*)vp->text);
*vpp = vp->next;
free(vp);
INT_ON;
} else {
setvar(s, 0, 0);
vp->flags &= ~VEXPORT;
}
ok:
retval = 0;
}
out:
return retval;
}
/*
* Process a linked list of variable assignments.
*/
static void
listsetvar(struct strlist *list_set_var, int flags)
{
struct strlist *lp = list_set_var;
if (!lp)
return;
INT_OFF;
do {
setvareq(lp->text, flags);
lp = lp->next;
} while (lp);
INT_ON;
}
/*
* Generate a list of variables satisfying the given conditions.
*/
static char **
listvars(int on, int off, char ***end)
{
struct var **vpp;
struct var *vp;
char **ep;
int mask;
STARTSTACKSTR(ep);
vpp = vartab;
mask = on | off;
do {
for (vp = *vpp; vp; vp = vp->next) {
if ((vp->flags & mask) == on) {
if (ep == stackstrend())
ep = growstackstr();
*ep++ = (char *) vp->text;
}
}
} while (++vpp < vartab + VTABSIZE);
if (ep == stackstrend())
ep = growstackstr();
if (end)
*end = ep;
*ep++ = NULL;
return grabstackstr(ep);
}
/* ============ Path search helper
*
* The variable path (passed by reference) should be set to the start
* of the path before the first call; padvance will update
* this value as it proceeds. Successive calls to padvance will return
* the possible path expansions in sequence. If an option (indicated by
* a percent sign) appears in the path entry then the global variable
* pathopt will be set to point to it; otherwise pathopt will be set to
* NULL.
*/
static const char *pathopt; /* set by padvance */
static char *
padvance(const char **path, const char *name)
{
const char *p;
char *q;
const char *start;
size_t len;
if (*path == NULL)
return NULL;
start = *path;
for (p = start; *p && *p != ':' && *p != '%'; p++)
continue;
len = p - start + strlen(name) + 2; /* "2" is for '/' and '\0' */
while (stackblocksize() < len)
growstackblock();
q = stackblock();
if (p != start) {
memcpy(q, start, p - start);
q += p - start;
*q++ = '/';
}
strcpy(q, name);
pathopt = NULL;
if (*p == '%') {
pathopt = ++p;
while (*p && *p != ':')
p++;
}
if (*p == ':')
*path = p + 1;
else
*path = NULL;
return stalloc(len);
}
/* ============ Prompt */
static smallint doprompt; /* if set, prompt the user */
static smallint needprompt; /* true if interactive and at start of line */
#if ENABLE_FEATURE_EDITING
static line_input_t *line_input_state;
static const char *cmdedit_prompt;
static void
putprompt(const char *s)
{
if (ENABLE_ASH_EXPAND_PRMT) {
free((char*)cmdedit_prompt);
cmdedit_prompt = ckstrdup(s);
return;
}
cmdedit_prompt = s;
}
#else
static void
putprompt(const char *s)
{
out2str(s);
}
#endif
#if ENABLE_ASH_EXPAND_PRMT
/* expandstr() needs parsing machinery, so it is far away ahead... */
static const char *expandstr(const char *ps);
#else
#define expandstr(s) s
#endif
static void
setprompt(int whichprompt)
{
const char *prompt;
#if ENABLE_ASH_EXPAND_PRMT
struct stackmark smark;
#endif
needprompt = 0;
switch (whichprompt) {
case 1:
prompt = ps1val();
break;
case 2:
prompt = ps2val();
break;
default: /* 0 */
prompt = nullstr;
}
#if ENABLE_ASH_EXPAND_PRMT
setstackmark(&smark);
stalloc(stackblocksize());
#endif
putprompt(expandstr(prompt));
#if ENABLE_ASH_EXPAND_PRMT
popstackmark(&smark);
#endif
}
/* ============ The cd and pwd commands */
#define CD_PHYSICAL 1
#define CD_PRINT 2
static int docd(const char *, int);
static int
cdopt(void)
{
int flags = 0;
int i, j;
j = 'L';
while ((i = nextopt("LP"))) {
if (i != j) {
flags ^= CD_PHYSICAL;
j = i;
}
}
return flags;
}
/*
* Update curdir (the name of the current directory) in response to a
* cd command.
*/
static const char *
updatepwd(const char *dir)
{
char *new;
char *p;
char *cdcomppath;
const char *lim;
cdcomppath = ststrdup(dir);
STARTSTACKSTR(new);
if (*dir != '/') {
if (curdir == nullstr)
return 0;
new = stack_putstr(curdir, new);
}
new = makestrspace(strlen(dir) + 2, new);
lim = (char *)stackblock() + 1;
if (*dir != '/') {
if (new[-1] != '/')
USTPUTC('/', new);
if (new > lim && *lim == '/')
lim++;
} else {
USTPUTC('/', new);
cdcomppath++;
if (dir[1] == '/' && dir[2] != '/') {
USTPUTC('/', new);
cdcomppath++;
lim++;
}
}
p = strtok(cdcomppath, "/");
while (p) {
switch (*p) {
case '.':
if (p[1] == '.' && p[2] == '\0') {
while (new > lim) {
STUNPUTC(new);
if (new[-1] == '/')
break;
}
break;
}
if (p[1] == '\0')
break;
/* fall through */
default:
new = stack_putstr(p, new);
USTPUTC('/', new);
}
p = strtok(0, "/");
}
if (new > lim)
STUNPUTC(new);
*new = 0;
return stackblock();
}
/*
* Find out what the current directory is. If we already know the current
* directory, this routine returns immediately.
*/
static char *
getpwd(void)
{
char *dir = getcwd(NULL, 0); /* huh, using glibc extension? */
return dir ? dir : nullstr;
}
static void
setpwd(const char *val, int setold)
{
char *oldcur, *dir;
oldcur = dir = curdir;
if (setold) {
setvar("OLDPWD", oldcur, VEXPORT);
}
INT_OFF;
if (physdir != nullstr) {
if (physdir != oldcur)
free(physdir);
physdir = nullstr;
}
if (oldcur == val || !val) {
char *s = getpwd();
physdir = s;
if (!val)
dir = s;
} else
dir = ckstrdup(val);
if (oldcur != dir && oldcur != nullstr) {
free(oldcur);
}
curdir = dir;
INT_ON;
setvar("PWD", dir, VEXPORT);
}
static void hashcd(void);
/*
* Actually do the chdir. We also call hashcd to let the routines in exec.c
* know that the current directory has changed.
*/
static int
docd(const char *dest, int flags)
{
const char *dir = 0;
int err;
TRACE(("docd(\"%s\", %d) called\n", dest, flags));
INT_OFF;
if (!(flags & CD_PHYSICAL)) {
dir = updatepwd(dest);
if (dir)
dest = dir;
}
err = chdir(dest);
if (err)
goto out;
setpwd(dir, 1);
hashcd();
out:
INT_ON;
return err;
}
static int
cdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
{
const char *dest;
const char *path;
const char *p;
char c;
struct stat statb;
int flags;
flags = cdopt();
dest = *argptr;
if (!dest)
dest = bltinlookup(homestr);
else if (LONE_DASH(dest)) {
dest = bltinlookup("OLDPWD");
flags |= CD_PRINT;
}
if (!dest)
dest = nullstr;
if (*dest == '/')
goto step7;
if (*dest == '.') {
c = dest[1];
dotdot:
switch (c) {
case '\0':
case '/':
goto step6;
case '.':
c = dest[2];
if (c != '.')
goto dotdot;
}
}
if (!*dest)
dest = ".";
path = bltinlookup("CDPATH");
if (!path) {
step6:
step7:
p = dest;
goto docd;
}
do {
c = *path;
p = padvance(&path, dest);
if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) {
if (c && c != ':')
flags |= CD_PRINT;
docd:
if (!docd(p, flags))
goto out;
break;
}
} while (path);
ash_msg_and_raise_error("can't cd to %s", dest);
/* NOTREACHED */
out:
if (flags & CD_PRINT)
out1fmt(snlfmt, curdir);
return 0;
}
static int
pwdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
{
int flags;
const char *dir = curdir;
flags = cdopt();
if (flags) {
if (physdir == nullstr)
setpwd(dir, 0);
dir = physdir;
}
out1fmt(snlfmt, dir);
return 0;
}
/* ============ ... */
#define IBUFSIZ COMMON_BUFSIZE
/* buffer for top level input file */
#define basebuf bb_common_bufsiz1
/* Syntax classes */
#define CWORD 0 /* character is nothing special */
#define CNL 1 /* newline character */
#define CBACK 2 /* a backslash character */
#define CSQUOTE 3 /* single quote */
#define CDQUOTE 4 /* double quote */
#define CENDQUOTE 5 /* a terminating quote */
#define CBQUOTE 6 /* backwards single quote */
#define CVAR 7 /* a dollar sign */
#define CENDVAR 8 /* a '}' character */
#define CLP 9 /* a left paren in arithmetic */
#define CRP 10 /* a right paren in arithmetic */
#define CENDFILE 11 /* end of file */
#define CCTL 12 /* like CWORD, except it must be escaped */
#define CSPCL 13 /* these terminate a word */
#define CIGN 14 /* character should be ignored */
#if ENABLE_ASH_ALIAS
#define SYNBASE 130
#define PEOF -130
#define PEOA -129
#define PEOA_OR_PEOF PEOA
#else
#define SYNBASE 129
#define PEOF -129
#define PEOA_OR_PEOF PEOF
#endif
/* number syntax index */
#define BASESYNTAX 0 /* not in quotes */
#define DQSYNTAX 1 /* in double quotes */
#define SQSYNTAX 2 /* in single quotes */
#define ARISYNTAX 3 /* in arithmetic */
#define PSSYNTAX 4 /* prompt */
#if ENABLE_ASH_OPTIMIZE_FOR_SIZE
#define USE_SIT_FUNCTION
#endif
#if ENABLE_SH_MATH_SUPPORT
static const char S_I_T[][4] = {
#if ENABLE_ASH_ALIAS
{ CSPCL, CIGN, CIGN, CIGN }, /* 0, PEOA */
#endif
{ CSPCL, CWORD, CWORD, CWORD }, /* 1, ' ' */
{ CNL, CNL, CNL, CNL }, /* 2, \n */
{ CWORD, CCTL, CCTL, CWORD }, /* 3, !*-/:=?[]~ */
{ CDQUOTE, CENDQUOTE, CWORD, CWORD }, /* 4, '"' */
{ CVAR, CVAR, CWORD, CVAR }, /* 5, $ */
{ CSQUOTE, CWORD, CENDQUOTE, CWORD }, /* 6, "'" */
{ CSPCL, CWORD, CWORD, CLP }, /* 7, ( */
{ CSPCL, CWORD, CWORD, CRP }, /* 8, ) */
{ CBACK, CBACK, CCTL, CBACK }, /* 9, \ */
{ CBQUOTE, CBQUOTE, CWORD, CBQUOTE }, /* 10, ` */
{ CENDVAR, CENDVAR, CWORD, CENDVAR }, /* 11, } */
#ifndef USE_SIT_FUNCTION
{ CENDFILE, CENDFILE, CENDFILE, CENDFILE }, /* 12, PEOF */
{ CWORD, CWORD, CWORD, CWORD }, /* 13, 0-9A-Za-z */
{ CCTL, CCTL, CCTL, CCTL } /* 14, CTLESC ... */
#endif
};
#else
static const char S_I_T[][3] = {
#if ENABLE_ASH_ALIAS
{ CSPCL, CIGN, CIGN }, /* 0, PEOA */
#endif
{ CSPCL, CWORD, CWORD }, /* 1, ' ' */
{ CNL, CNL, CNL }, /* 2, \n */
{ CWORD, CCTL, CCTL }, /* 3, !*-/:=?[]~ */
{ CDQUOTE, CENDQUOTE, CWORD }, /* 4, '"' */
{ CVAR, CVAR, CWORD }, /* 5, $ */
{ CSQUOTE, CWORD, CENDQUOTE }, /* 6, "'" */
{ CSPCL, CWORD, CWORD }, /* 7, ( */
{ CSPCL, CWORD, CWORD }, /* 8, ) */
{ CBACK, CBACK, CCTL }, /* 9, \ */
{ CBQUOTE, CBQUOTE, CWORD }, /* 10, ` */
{ CENDVAR, CENDVAR, CWORD }, /* 11, } */
#ifndef USE_SIT_FUNCTION
{ CENDFILE, CENDFILE, CENDFILE }, /* 12, PEOF */
{ CWORD, CWORD, CWORD }, /* 13, 0-9A-Za-z */
{ CCTL, CCTL, CCTL } /* 14, CTLESC ... */
#endif
};
#endif /* SH_MATH_SUPPORT */
#ifdef USE_SIT_FUNCTION
static int
SIT(int c, int syntax)
{
static const char spec_symbls[] ALIGN1 = "\t\n !\"$&'()*-/:;<=>?[\\]`|}~";
#if ENABLE_ASH_ALIAS
static const char syntax_index_table[] ALIGN1 = {
1, 2, 1, 3, 4, 5, 1, 6, /* "\t\n !\"$&'" */
7, 8, 3, 3, 3, 3, 1, 1, /* "()*-/:;<" */
3, 1, 3, 3, 9, 3, 10, 1, /* "=>?[\\]`|" */
11, 3 /* "}~" */
};
#else
static const char syntax_index_table[] ALIGN1 = {
0, 1, 0, 2, 3, 4, 0, 5, /* "\t\n !\"$&'" */
6, 7, 2, 2, 2, 2, 0, 0, /* "()*-/:;<" */
2, 0, 2, 2, 8, 2, 9, 0, /* "=>?[\\]`|" */
10, 2 /* "}~" */
};
#endif
const char *s;
int indx;
if (c == PEOF) { /* 2^8+2 */
return CENDFILE;
}
#if ENABLE_ASH_ALIAS
if (c == PEOA) { /* 2^8+1 */
indx = 0;
} else
#endif
{
if ((unsigned char)c >= (unsigned char)(CTLESC)
&& (unsigned char)c <= (unsigned char)(CTLQUOTEMARK)
) {
return CCTL;
}
s = strchrnul(spec_symbls, c);
if (*s == '\0') {
return CWORD;
}
indx = syntax_index_table[s - spec_symbls];
}
return S_I_T[indx][syntax];
}
#else /* !USE_SIT_FUNCTION */
#if ENABLE_ASH_ALIAS
#define CSPCL_CIGN_CIGN_CIGN 0
#define CSPCL_CWORD_CWORD_CWORD 1
#define CNL_CNL_CNL_CNL 2
#define CWORD_CCTL_CCTL_CWORD 3
#define CDQUOTE_CENDQUOTE_CWORD_CWORD 4
#define CVAR_CVAR_CWORD_CVAR 5
#define CSQUOTE_CWORD_CENDQUOTE_CWORD 6
#define CSPCL_CWORD_CWORD_CLP 7
#define CSPCL_CWORD_CWORD_CRP 8
#define CBACK_CBACK_CCTL_CBACK 9
#define CBQUOTE_CBQUOTE_CWORD_CBQUOTE 10
#define CENDVAR_CENDVAR_CWORD_CENDVAR 11
#define CENDFILE_CENDFILE_CENDFILE_CENDFILE 12
#define CWORD_CWORD_CWORD_CWORD 13
#define CCTL_CCTL_CCTL_CCTL 14
#else
#define CSPCL_CWORD_CWORD_CWORD 0
#define CNL_CNL_CNL_CNL 1
#define CWORD_CCTL_CCTL_CWORD 2
#define CDQUOTE_CENDQUOTE_CWORD_CWORD 3
#define CVAR_CVAR_CWORD_CVAR 4
#define CSQUOTE_CWORD_CENDQUOTE_CWORD 5
#define CSPCL_CWORD_CWORD_CLP 6
#define CSPCL_CWORD_CWORD_CRP 7
#define CBACK_CBACK_CCTL_CBACK 8
#define CBQUOTE_CBQUOTE_CWORD_CBQUOTE 9
#define CENDVAR_CENDVAR_CWORD_CENDVAR 10
#define CENDFILE_CENDFILE_CENDFILE_CENDFILE 11
#define CWORD_CWORD_CWORD_CWORD 12
#define CCTL_CCTL_CCTL_CCTL 13
#endif
static const char syntax_index_table[258] = {
/* BASESYNTAX_DQSYNTAX_SQSYNTAX_ARISYNTAX */
/* 0 PEOF */ CENDFILE_CENDFILE_CENDFILE_CENDFILE,
#if ENABLE_ASH_ALIAS
/* 1 PEOA */ CSPCL_CIGN_CIGN_CIGN,
#endif
/* 2 -128 0x80 */ CWORD_CWORD_CWORD_CWORD,
/* 3 -127 CTLESC */ CCTL_CCTL_CCTL_CCTL,
/* 4 -126 CTLVAR */ CCTL_CCTL_CCTL_CCTL,
/* 5 -125 CTLENDVAR */ CCTL_CCTL_CCTL_CCTL,
/* 6 -124 CTLBACKQ */ CCTL_CCTL_CCTL_CCTL,
/* 7 -123 CTLQUOTE */ CCTL_CCTL_CCTL_CCTL,
/* 8 -122 CTLARI */ CCTL_CCTL_CCTL_CCTL,
/* 9 -121 CTLENDARI */ CCTL_CCTL_CCTL_CCTL,
/* 10 -120 CTLQUOTEMARK */ CCTL_CCTL_CCTL_CCTL,
/* 11 -119 */ CWORD_CWORD_CWORD_CWORD,
/* 12 -118 */ CWORD_CWORD_CWORD_CWORD,
/* 13 -117 */ CWORD_CWORD_CWORD_CWORD,
/* 14 -116 */ CWORD_CWORD_CWORD_CWORD,
/* 15 -115 */ CWORD_CWORD_CWORD_CWORD,
/* 16 -114 */ CWORD_CWORD_CWORD_CWORD,
/* 17 -113 */ CWORD_CWORD_CWORD_CWORD,
/* 18 -112 */ CWORD_CWORD_CWORD_CWORD,
/* 19 -111 */ CWORD_CWORD_CWORD_CWORD,
/* 20 -110 */ CWORD_CWORD_CWORD_CWORD,
/* 21 -109 */ CWORD_CWORD_CWORD_CWORD,
/* 22 -108 */ CWORD_CWORD_CWORD_CWORD,
/* 23 -107 */ CWORD_CWORD_CWORD_CWORD,
/* 24 -106 */ CWORD_CWORD_CWORD_CWORD,
/* 25 -105 */ CWORD_CWORD_CWORD_CWORD,
/* 26 -104 */ CWORD_CWORD_CWORD_CWORD,
/* 27 -103 */ CWORD_CWORD_CWORD_CWORD,
/* 28 -102 */ CWORD_CWORD_CWORD_CWORD,
/* 29 -101 */ CWORD_CWORD_CWORD_CWORD,
/* 30 -100 */ CWORD_CWORD_CWORD_CWORD,
/* 31 -99 */ CWORD_CWORD_CWORD_CWORD,
/* 32 -98 */ CWORD_CWORD_CWORD_CWORD,
/* 33 -97 */ CWORD_CWORD_CWORD_CWORD,
/* 34 -96 */ CWORD_CWORD_CWORD_CWORD,
/* 35 -95 */ CWORD_CWORD_CWORD_CWORD,
/* 36 -94 */ CWORD_CWORD_CWORD_CWORD,
/* 37 -93 */ CWORD_CWORD_CWORD_CWORD,
/* 38 -92 */ CWORD_CWORD_CWORD_CWORD,
/* 39 -91 */ CWORD_CWORD_CWORD_CWORD,
/* 40 -90 */ CWORD_CWORD_CWORD_CWORD,
/* 41 -89 */ CWORD_CWORD_CWORD_CWORD,
/* 42 -88 */ CWORD_CWORD_CWORD_CWORD,
/* 43 -87 */ CWORD_CWORD_CWORD_CWORD,
/* 44 -86 */ CWORD_CWORD_CWORD_CWORD,
/* 45 -85 */ CWORD_CWORD_CWORD_CWORD,
/* 46 -84 */ CWORD_CWORD_CWORD_CWORD,
/* 47 -83 */ CWORD_CWORD_CWORD_CWORD,
/* 48 -82 */ CWORD_CWORD_CWORD_CWORD,
/* 49 -81 */ CWORD_CWORD_CWORD_CWORD,
/* 50 -80 */ CWORD_CWORD_CWORD_CWORD,
/* 51 -79 */ CWORD_CWORD_CWORD_CWORD,
/* 52 -78 */ CWORD_CWORD_CWORD_CWORD,
/* 53 -77 */ CWORD_CWORD_CWORD_CWORD,
/* 54 -76 */ CWORD_CWORD_CWORD_CWORD,
/* 55 -75 */ CWORD_CWORD_CWORD_CWORD,
/* 56 -74 */ CWORD_CWORD_CWORD_CWORD,
/* 57 -73 */ CWORD_CWORD_CWORD_CWORD,
/* 58 -72 */ CWORD_CWORD_CWORD_CWORD,
/* 59 -71 */ CWORD_CWORD_CWORD_CWORD,
/* 60 -70 */ CWORD_CWORD_CWORD_CWORD,
/* 61 -69 */ CWORD_CWORD_CWORD_CWORD,
/* 62 -68 */ CWORD_CWORD_CWORD_CWORD,
/* 63 -67 */ CWORD_CWORD_CWORD_CWORD,
/* 64 -66 */ CWORD_CWORD_CWORD_CWORD,
/* 65 -65 */ CWORD_CWORD_CWORD_CWORD,
/* 66 -64 */ CWORD_CWORD_CWORD_CWORD,
/* 67 -63 */ CWORD_CWORD_CWORD_CWORD,
/* 68 -62 */ CWORD_CWORD_CWORD_CWORD,
/* 69 -61 */ CWORD_CWORD_CWORD_CWORD,
/* 70 -60 */ CWORD_CWORD_CWORD_CWORD,
/* 71 -59 */ CWORD_CWORD_CWORD_CWORD,
/* 72 -58 */ CWORD_CWORD_CWORD_CWORD,
/* 73 -57 */ CWORD_CWORD_CWORD_CWORD,
/* 74 -56 */ CWORD_CWORD_CWORD_CWORD,
/* 75 -55 */ CWORD_CWORD_CWORD_CWORD,
/* 76 -54 */ CWORD_CWORD_CWORD_CWORD,
/* 77 -53 */ CWORD_CWORD_CWORD_CWORD,
/* 78 -52 */ CWORD_CWORD_CWORD_CWORD,
/* 79 -51 */ CWORD_CWORD_CWORD_CWORD,
/* 80 -50 */ CWORD_CWORD_CWORD_CWORD,
/* 81 -49 */ CWORD_CWORD_CWORD_CWORD,
/* 82 -48 */ CWORD_CWORD_CWORD_CWORD,
/* 83 -47 */ CWORD_CWORD_CWORD_CWORD,
/* 84 -46 */ CWORD_CWORD_CWORD_CWORD,
/* 85 -45 */ CWORD_CWORD_CWORD_CWORD,
/* 86 -44 */ CWORD_CWORD_CWORD_CWORD,
/* 87 -43 */ CWORD_CWORD_CWORD_CWORD,
/* 88 -42 */ CWORD_CWORD_CWORD_CWORD,
/* 89 -41 */ CWORD_CWORD_CWORD_CWORD,
/* 90 -40 */ CWORD_CWORD_CWORD_CWORD,
/* 91 -39 */ CWORD_CWORD_CWORD_CWORD,
/* 92 -38 */ CWORD_CWORD_CWORD_CWORD,
/* 93 -37 */ CWORD_CWORD_CWORD_CWORD,
/* 94 -36 */ CWORD_CWORD_CWORD_CWORD,
/* 95 -35 */ CWORD_CWORD_CWORD_CWORD,
/* 96 -34 */ CWORD_CWORD_CWORD_CWORD,
/* 97 -33 */ CWORD_CWORD_CWORD_CWORD,
/* 98 -32 */ CWORD_CWORD_CWORD_CWORD,
/* 99 -31 */ CWORD_CWORD_CWORD_CWORD,
/* 100 -30 */ CWORD_CWORD_CWORD_CWORD,
/* 101 -29 */ CWORD_CWORD_CWORD_CWORD,
/* 102 -28 */ CWORD_CWORD_CWORD_CWORD,
/* 103 -27 */ CWORD_CWORD_CWORD_CWORD,
/* 104 -26 */ CWORD_CWORD_CWORD_CWORD,
/* 105 -25 */ CWORD_CWORD_CWORD_CWORD,
/* 106 -24 */ CWORD_CWORD_CWORD_CWORD,