//config: you to run the specified command or builtin,
//config: even when there is a function with the same name.
//config:
+//config:config ASH_EMBEDDED_SCRIPTS
+//config: bool "Embed scripts in the binary"
+//config: default y
+//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
+//config: help
+//config: Allow scripts to be compressed and embedded in the busybox
+//config: binary. The scripts should be placed in the 'embed' directory
+//config: at build time. Like applets, scripts can be run as
+//config: 'busybox SCRIPT ...' or by linking their name to the binary.
+//config:
+//config: This also allows applets to be implemented as scripts: place
+//config: the script in 'applets_sh' and a stub C file containing
+//config: configuration in the appropriate subsystem directory.
+//config:
//config:endif # ash options
//applet:IF_ASH(APPLET(ash, BB_DIR_BIN, BB_SUID_DROP))
#define JOBS ENABLE_ASH_JOB_CONTROL
-#include <setjmp.h>
#include <fnmatch.h>
#include <sys/times.h>
#include <sys/utsname.h> /* for setting $HOSTNAME */
#include "busybox.h" /* for applet_names */
+#if ENABLE_ASH_EMBEDDED_SCRIPTS
+# include "embedded_scripts.h"
+#else
+# define NUM_SCRIPTS 0
+#endif
/* So far, all bash compat is controlled by one config option */
/* Separate defines document which part of code implements what */
#define IF_BASH_PATTERN_SUBST IF_ASH_BASH_COMPAT
#define BASH_SUBSTR ENABLE_ASH_BASH_COMPAT
#define IF_BASH_SUBSTR IF_ASH_BASH_COMPAT
-/* [[ EXPR ]] */
+/* BASH_TEST2: [[ EXPR ]]
+ * Status of [[ support:
+ * We replace && and || with -a and -o
+ * TODO:
+ * singleword+noglob expansion:
+ * v='a b'; [[ $v = 'a b' ]]; echo 0:$?
+ * [[ /bin/n* ]]; echo 0:$?
+ * -a/-o are not AND/OR ops! (they are just strings)
+ * quoting needs to be considered (-f is an operator, "-f" and ""-f are not; etc)
+ * = is glob match operator, not equality operator: STR = GLOB
+ * (in GLOB, quoting is significant on char-by-char basis: a*cd"*")
+ * == same as =
+ * add =~ regex match operator: STR =~ REGEX
+ */
#define BASH_TEST2 (ENABLE_ASH_BASH_COMPAT * ENABLE_ASH_TEST)
#define BASH_SOURCE ENABLE_ASH_BASH_COMPAT
#define BASH_PIPEFAIL ENABLE_ASH_BASH_COMPAT
}
INT_OFF;
- nameeq = ckmalloc(namelen + vallen + 2);
+ nameeq = ckzalloc(namelen + vallen + 2);
p = mempcpy(nameeq, name, namelen);
if (val) {
*p++ = '=';
- p = mempcpy(p, val, vallen);
+ memcpy(p, val, vallen);
}
- *p = '\0';
vp = setvareq(nameeq, flags | VNOSAVE);
INT_ON;
f = open(fname, O_WRONLY, 0666);
if (f < 0)
goto ecreate;
- if (fstat(f, &sb) < 0 && S_ISREG(sb.st_mode)) {
+ if (!fstat(f, &sb) && S_ISREG(sb.st_mode)) {
close(f);
errno = EEXIST;
goto ecreate;
* performs globbing, and thus diverges from what we do).
*/
#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */
-#define EXP_QPAT 0x20 /* pattern in quoted parameter expansion */
-#define EXP_VARTILDE2 0x40 /* expand tildes after colons only */
-#define EXP_WORD 0x80 /* expand word in parameter expansion */
+#define EXP_VARTILDE2 0x20 /* expand tildes after colons only */
+#define EXP_WORD 0x40 /* expand word in parameter expansion */
#define EXP_QUOTED 0x100 /* expand word in double quotes */
/*
* rmescape() flags
#define RMESCAPE_HEAP 0x10 /* Malloc strings instead of stalloc */
/* Add CTLESC when necessary. */
-#define QUOTES_ESC (EXP_FULL | EXP_CASE | EXP_QPAT)
+#define QUOTES_ESC (EXP_FULL | EXP_CASE)
/* Do not skip NUL characters. */
#define QUOTES_KEEPNUL EXP_TILDE
realifs = ifsset() ? ifsval() : defifs;
ifsp = &ifsfirst;
do {
+ int afternul;
+
p = string + ifsp->begoff;
+ afternul = nulonly;
nulonly = ifsp->nulonly;
ifs = nulonly ? nullstr : realifs;
ifsspc = 0;
p++;
continue;
}
- if (!nulonly)
+ if (!(afternul || nulonly))
ifsspc = (strchr(defifs, *p) != NULL);
/* Ignore IFS whitespace at start */
if (q == start && ifsspc) {
IF_BASH_PATTERN_SUBST('/',) CTLESC, CTLQUOTEMARK, '\0' };
char *p, *q, *r;
- unsigned inquotes;
unsigned protect_against_glob;
unsigned globbing;
}
}
- inquotes = 0;
globbing = flag & RMESCAPE_GLOB;
protect_against_glob = globbing;
while (*p) {
if ((unsigned char)*p == CTLQUOTEMARK) {
-// Note: both inquotes and protect_against_glob only affect whether
+// Note: protect_against_glob only affect whether
// CTLESC,<ch> gets converted to <ch> or to \<ch>
- inquotes = ~inquotes;
p++;
protect_against_glob = globbing;
continue;
}
+ if (*p == '\\') {
+ /* naked back slash */
+ protect_against_glob = 0;
+ goto copy;
+ }
if ((unsigned char)*p == CTLESC) {
p++;
#if DEBUG
*q++ = '\\';
}
}
- } else if (*p == '\\' && !inquotes) {
- /* naked back slash */
- protect_against_glob = 0;
- goto copy;
}
#if BASH_PATTERN_SUBST
else if (slash_position && p == str + *slash_position) {
if (quotes & QUOTES_ESC) {
int n = SIT(c, syntax);
if (n == CCTL
- || (((quotes & EXP_FULL) || syntax != BASESYNTAX)
- && n == CBACK
- )
+ || (syntax != BASESYNTAX && n == CBACK)
) {
USTPUTC(CTLESC, q);
}
case CTLENDVAR: /* ??? */
goto breakloop;
case CTLQUOTEMARK:
- inquotes ^= EXP_QUOTED;
/* "$@" syntax adherence hack */
- if (inquotes && !memcmp(p, dolatstr + 1, DOLATSTRLEN - 1)) {
- p = evalvar(p + 1, flags | inquotes) + 1;
+ if (!inquotes && !memcmp(p, dolatstr + 1, DOLATSTRLEN - 1)) {
+ p = evalvar(p + 1, flags | EXP_QUOTED) + 1;
goto start;
}
+ inquotes ^= EXP_QUOTED;
addquote:
if (flags & QUOTES_ESC) {
p--;
case CTLESC:
startloc++;
length++;
-
- /*
- * Quoted parameter expansion pattern: remove quote
- * unless inside inner quotes or we have a literal
- * backslash.
- */
- if (((flags | inquotes) & (EXP_QPAT | EXP_QUOTED)) ==
- EXP_QPAT && *p != '\\')
- break;
-
goto addquote;
case CTLVAR:
TRACE(("argstr: evalvar('%s')\n", p));
if (subtype == VSREPLACE || subtype == VSREPLACEALL) {
/* Find '/' and replace with NUL */
repl = p;
+ /* The pattern can't be empty.
+ * IOW: if the first char after "${v//" is a slash,
+ * it does not terminate the pattern - it's the first char of the pattern:
+ * v=/dev/ram; echo ${v////-} prints -dev-ram (pattern is "/")
+ * v=/dev/ram; echo ${v///r/-} prints /dev-am (pattern is "/r")
+ */
+ if (*repl == '/')
+ repl++;
for (;;) {
- /* Handle escaped slashes, e.g. "${v/\//_}" (they are CTLESC'ed by this point) */
if (*repl == '\0') {
repl = NULL;
break;
*repl = '\0';
break;
}
+ /* Handle escaped slashes, e.g. "${v/\//_}" (they are CTLESC'ed by this point) */
if ((unsigned char)*repl == CTLESC && repl[1])
repl++;
repl++;
}
#endif
argstr_flags = EXP_TILDE;
- if (subtype != VSASSIGN && subtype != VSQUESTION)
- argstr_flags |= (flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE);
+ if (subtype != VSASSIGN
+ && subtype != VSQUESTION
+#if BASH_SUBSTR
+ && subtype != VSSUBSTR
+#endif
+ ) {
+ /* EXP_CASE keeps CTLESC's */
+ argstr_flags = EXP_TILDE | EXP_CASE;
+ }
argstr(p, argstr_flags);
+ //bb_error_msg("str0:'%s'", (char *)stackblock() + strloc);
#if BASH_PATTERN_SUBST
slash_pos = -1;
if (repl) {
slash_pos = expdest - ((char *)stackblock() + strloc);
STPUTC('/', expdest);
- argstr(repl + 1, argstr_flags);
+ //bb_error_msg("repl+1:'%s'", repl + 1);
+ argstr(repl + 1, EXP_TILDE); /* EXP_TILDE: echo "${v/x/~}" expands ~ ! */
*repl = '/';
}
#endif
* ash -c 'echo ${#1#}' name:'1=#'
*/
static NOINLINE ssize_t
-varvalue(char *name, int varflags, int flags, int *quotedp)
+varvalue(char *name, int varflags, int flags, int quoted)
{
const char *p;
int num;
int i;
ssize_t len = 0;
int sep;
- int quoted = *quotedp;
int subtype = varflags & VSTYPE;
int discard = subtype == VSPLUS || subtype == VSLENGTH;
int quotes = (discard ? 0 : (flags & QUOTES_ESC)) | QUOTES_KEEPNUL;
case '-':
expdest = makestrspace(NOPTS, expdest);
for (i = NOPTS - 1; i >= 0; i--) {
- if (optlist[i]) {
+ if (optlist[i] && optletters(i)) {
USTPUTC(optletters(i), expdest);
len++;
}
case '*': {
char **ap;
char sepc;
+ char c;
- if (quoted)
- sep = 0;
- sep |= ifsset() ? ifsval()[0] : ' ';
+ /* We will set c to 0 or ~0 depending on whether
+ * we're doing field splitting. We won't do field
+ * splitting if either we're quoted or sep is zero.
+ *
+ * Instead of testing (quoted || !sep) the following
+ * trick optimises away any branches by using the
+ * fact that EXP_QUOTED (which is the only bit that
+ * can be set in quoted) is the same as EXP_FULL <<
+ * CHAR_BIT (which is the only bit that can be set
+ * in sep).
+ */
+#if EXP_QUOTED >> CHAR_BIT != EXP_FULL
+#error The following two lines expect EXP_QUOTED == EXP_FULL << CHAR_BIT
+#endif
+ c = !((quoted | ~sep) & EXP_QUOTED) - 1;
+ sep &= ~quoted;
+ sep |= ifsset() ? (unsigned char)(c & ifsval()[0]) : ' ';
param:
sepc = sep;
- *quotedp = !sepc;
ap = shellparam.p;
if (!ap)
return -1;
char varflags;
char subtype;
int quoted;
- char easy;
char *var;
int patloc;
int startloc;
quoted = flag & EXP_QUOTED;
var = p;
- easy = (!quoted || (*var == '@' && shellparam.nparam));
startloc = expdest - (char *)stackblock();
p = strchr(p, '=') + 1; //TODO: use var_end(p)?
again:
- varlen = varvalue(var, varflags, flag, "ed);
+ varlen = varvalue(var, varflags, flag, quoted);
if (varflags & VSNUL)
varlen--;
if (subtype == VSNORMAL) {
record:
- if (!easy)
- goto end;
+ if (quoted) {
+ quoted = *var == '@' && shellparam.nparam;
+ if (!quoted)
+ goto end;
+ }
recordregion(startloc, expdest - (char *)stackblock(), quoted);
goto end;
}
/*
* Do metacharacter (i.e. *, ?, [...]) expansion.
*/
+typedef struct exp_t {
+ char *dir;
+ unsigned dir_max;
+} exp_t;
static void
-expmeta(char *expdir, char *enddir, char *name)
+expmeta(exp_t *exp, char *name, unsigned name_len, unsigned expdir_len)
{
+#define expdir exp->dir
+#define expdir_max exp->dir_max
+ char *enddir = expdir + expdir_len;
char *p;
const char *cp;
char *start;
}
}
} else {
- if (*p == '\\')
+ if (*p == '\\' && p[1])
esc++;
if (p[esc] == '/') {
if (metaflag)
}
}
if (metaflag == 0) { /* we've reached the end of the file name */
- if (enddir != expdir)
- metaflag++;
+ if (!expdir_len)
+ return;
p = name;
do {
- if (*p == '\\')
+ if (*p == '\\' && p[1])
p++;
*enddir++ = *p;
} while (*p++);
- if (metaflag == 0 || lstat(expdir, &statb) >= 0)
+ if (lstat(expdir, &statb) == 0)
addfname(expdir);
return;
}
if (name < start) {
p = name;
do {
- if (*p == '\\')
+ if (*p == '\\' && p[1])
p++;
*enddir++ = *p++;
} while (p < start);
}
- if (enddir == expdir) {
+ *enddir = '\0';
+ cp = expdir;
+ expdir_len = enddir - cp;
+ if (!expdir_len)
cp = ".";
- } else if (enddir == expdir + 1 && *expdir == '/') {
- cp = "/";
- } else {
- cp = expdir;
- enddir[-1] = '\0';
- }
dirp = opendir(cp);
if (dirp == NULL)
return;
- if (enddir != expdir)
- enddir[-1] = '/';
if (*endname == 0) {
atend = 1;
} else {
*endname = '\0';
endname += esc + 1;
}
+ name_len -= endname - name;
matchdot = 0;
p = start;
if (*p == '\\')
strcpy(enddir, dp->d_name);
addfname(expdir);
} else {
- for (p = enddir, cp = dp->d_name; (*p++ = *cp++) != '\0';)
- continue;
- p[-1] = '/';
- expmeta(expdir, p, endname);
+ unsigned offset;
+ unsigned len;
+
+ p = stpcpy(enddir, dp->d_name);
+ *p = '/';
+
+ offset = p - expdir + 1;
+ len = offset + name_len + NAME_MAX;
+ if (len > expdir_max) {
+ len += PATH_MAX;
+ expdir = ckrealloc(expdir, len);
+ expdir_max = len;
+ }
+
+ expmeta(exp, endname, name_len, offset);
+ enddir = expdir + expdir_len;
}
}
}
closedir(dirp);
if (!atend)
endname[-esc - 1] = esc ? '\\' : '/';
+#undef expdir
+#undef expdir_max
}
static struct strlist *
/* TODO - EXP_REDIR */
while (str) {
- char *expdir;
+ exp_t exp;
struct strlist **savelastp;
struct strlist *sp;
char *p;
+ unsigned len;
if (fflag)
goto nometa;
INT_OFF;
p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP);
- {
- int i = strlen(str->text);
-//BUGGY estimation of how long expanded name can be
- expdir = ckmalloc(i < 2048 ? 2048 : i+1);
- }
- expmeta(expdir, expdir, p);
- free(expdir);
+ len = strlen(p);
+ exp.dir_max = len + PATH_MAX;
+ exp.dir = ckmalloc(exp.dir_max);
+
+ expmeta(&exp, p, len, 0);
+ free(exp.dir);
if (p != str->text)
free(p);
INT_ON;
#else
execve(cmd, argv, envp);
#endif
+
if (cmd != bb_busybox_exec_path && errno == ENOEXEC) {
/* Run "cmd" as a shell script:
* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
/* Map to POSIX errors */
switch (e) {
- case EACCES:
+ default:
exerrno = 126;
break;
+ case ELOOP:
+ case ENAMETOOLONG:
case ENOENT:
+ case ENOTDIR:
exerrno = 127;
break;
- default:
- exerrno = 2;
- break;
}
exitstatus = exerrno;
TRACE(("shellexec failed for %s, errno %d, suppress_int %d\n",
case CMDFUNCTION:
if (describe_command_verbose) {
- out1str(" is a shell function");
+ /*out1str(" is a shell function");*/
+ out1str(" is a function"); /* bash says this */
} else {
out1str(command);
}
shellparam.optind = 1;
shellparam.optoff = -1;
#endif
- pushlocalvars();
evaltree(func->n.ndefun.body, flags & EV_TESTED);
- poplocalvars(0);
funcdone:
INT_OFF;
funcline = savefuncline;
/*
* Execute a simple command.
*/
+static void unwindfiles(struct parsefile *stop);
static int
isassignment(const char *p)
{
"\0\0", bltincmd /* why three NULs? */
};
struct localvar_list *localvar_stop;
+ struct parsefile *file_stop;
struct redirtab *redir_stop;
struct stackmark smark;
union node *argp;
TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags));
setstackmark(&smark);
localvar_stop = pushlocalvars();
+ file_stop = g_parsefile;
back_exitstatus = 0;
cmdentry.cmdtype = CMDBUILTIN;
goto readstatus;
case CMDFUNCTION:
- poplocalvars(1);
/* See above for the rationale */
dowait(DOWAIT_NONBLOCK, NULL);
if (evalfun(cmdentry.u.func, argc, argv, flags))
if (cmd->ncmd.redirect)
popredir(/*drop:*/ cmd_is_exec);
unwindredir(redir_stop);
+ unwindfiles(file_stop);
unwindlocalvars(localvar_stop);
if (lastarg) {
/* dsl: I think this is intended to be used to support
return c;
}
+struct synstack {
+ smalluint syntax;
+ uint8_t innerdq :1;
+ uint8_t varpushed :1;
+ uint8_t dblquote :1;
+ int varnest; /* levels of variables expansion */
+ int dqvarnest; /* levels of variables expansion within double quotes */
+ int parenlevel; /* levels of parens in arithmetic */
+ struct synstack *prev;
+ struct synstack *next;
+};
+
+static void
+synstack_push(struct synstack **stack, struct synstack *next, int syntax)
+{
+ memset(next, 0, sizeof(*next));
+ next->syntax = syntax;
+ next->next = *stack;
+ (*stack)->prev = next;
+ *stack = next;
+}
+
+static ALWAYS_INLINE void
+synstack_pop(struct synstack **stack)
+{
+ *stack = (*stack)->next;
+}
+
/*
* To handle the "." command, a stack of input files is used. Pushfile
* adds a new entry to the stack and popfile restores the previous level.
INT_ON;
}
+static void
+unwindfiles(struct parsefile *stop)
+{
+ while (g_parsefile != stop)
+ popfile();
+}
+
/*
* Return to top level.
*/
static void
popallfiles(void)
{
- while (g_parsefile != &basepf)
- popfile();
+ unwindfiles(&basepf);
}
/*
case TLP:
function_flag = 0;
break;
+# if BASH_TEST2
case TWORD:
if (strcmp("[[", wordtext) == 0)
goto do_func;
/* fall through */
+# endif
default:
raise_error_unexpected_syntax(-1);
}
*vpp = NULL;
*rpp = NULL;
n = stzalloc(sizeof(struct ncmd));
- n->type = NCMD;
+ if (NCMD != 0)
+ n->type = NCMD;
n->ncmd.linno = savelinno;
n->ncmd.args = args;
n->ncmd.assign = vars;
size_t len;
struct nodelist *bqlist;
smallint quotef;
- smallint dblquote;
smallint oldstyle;
- IF_FEATURE_SH_MATH(smallint prevsyntax;) /* syntax before arithmetic */
smallint pssyntax; /* we are expanding a prompt string */
- int varnest; /* levels of variables expansion */
- IF_FEATURE_SH_MATH(int arinest;) /* levels of arithmetic expansion */
- IF_FEATURE_SH_MATH(int parenlevel;) /* levels of parens in arithmetic */
- int dqvarnest; /* levels of variables expansion within double quotes */
IF_BASH_DOLLAR_SQUOTE(smallint bash_dollar_squote = 0;)
+ /* syntax stack */
+ struct synstack synbase = { };
+ struct synstack *synstack = &synbase;
- bqlist = NULL;
- quotef = 0;
- IF_FEATURE_SH_MATH(prevsyntax = 0;)
#if ENABLE_ASH_EXPAND_PRMT
pssyntax = (syntax == PSSYNTAX);
if (pssyntax)
#else
pssyntax = 0; /* constant */
#endif
- dblquote = (syntax == DQSYNTAX);
- varnest = 0;
- IF_FEATURE_SH_MATH(arinest = 0;)
- IF_FEATURE_SH_MATH(parenlevel = 0;)
- dqvarnest = 0;
+ synstack->syntax = syntax;
+
+ if (syntax == DQSYNTAX)
+ synstack->dblquote = 1;
+ quotef = 0;
+ bqlist = NULL;
STARTSTACKSTR(out);
loop:
CHECKEND(); /* set c to PEOF if at end of here document */
for (;;) { /* until end of line or end of word */
CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */
- switch (SIT(c, syntax)) {
+ switch (SIT(c, synstack->syntax)) {
case CNL: /* '\n' */
- if (syntax == BASESYNTAX)
+ if (synstack->syntax == BASESYNTAX
+ && !synstack->varnest
+ ) {
goto endword; /* exit outer loop */
+ }
USTPUTC(c, out);
nlprompt();
c = pgetc();
if (c & 0x100) {
/* Unknown escape. Encode as '\z' */
c = (unsigned char)c;
- if (eofmark == NULL || dblquote)
+ if (eofmark == NULL || synstack->dblquote)
USTPUTC(CTLESC, out);
USTPUTC('\\', out);
}
}
#endif
- if (eofmark == NULL || dblquote)
+ if (!eofmark || synstack->dblquote || synstack->varnest)
USTPUTC(CTLESC, out);
USTPUTC(c, out);
break;
/* Backslash is retained if we are in "str"
* and next char isn't dquote-special.
*/
- if (dblquote
+ if (synstack->dblquote
&& c != '\\'
&& c != '`'
&& c != '$'
- && (c != '"' || eofmark != NULL)
+ && (c != '"' || (eofmark != NULL && !synstack->varnest))
+ && (c != '}' || !synstack->varnest)
) {
-//dash survives not doing USTPUTC(CTLESC), but merely by chance:
-//Example: "\z" gets encoded as "\<CTLESC>z".
-//rmescapes() then emits "\", "\z", protecting z from globbing.
-//But it's wrong, should protect _both_ from globbing:
-//everything in double quotes is not globbed.
-//Unlike dash, we have a fix in rmescapes() which emits bare "z"
-//for "<CTLESC>z" since "z" is not glob-special (else unicode may break),
-//and glob would see "\z" and eat "\". Thus:
USTPUTC(CTLESC, out); /* protect '\' from glob */
USTPUTC('\\', out);
}
}
break;
case CSQUOTE:
- syntax = SQSYNTAX;
+ synstack->syntax = SQSYNTAX;
quotemark:
if (eofmark == NULL) {
USTPUTC(CTLQUOTEMARK, out);
}
break;
case CDQUOTE:
- syntax = DQSYNTAX;
- dblquote = 1;
+ synstack->syntax = DQSYNTAX;
+ synstack->dblquote = 1;
+ toggledq:
+ if (synstack->varnest)
+ synstack->innerdq ^= 1;
goto quotemark;
case CENDQUOTE:
IF_BASH_DOLLAR_SQUOTE(bash_dollar_squote = 0;)
- if (eofmark != NULL && varnest == 0) {
+ if (eofmark != NULL && synstack->varnest == 0) {
USTPUTC(c, out);
- } else {
- if (dqvarnest == 0) {
- syntax = BASESYNTAX;
- dblquote = 0;
- }
- quotef = 1;
- goto quotemark;
+ break;
}
- break;
+
+ if (synstack->dqvarnest == 0) {
+ synstack->syntax = BASESYNTAX;
+ synstack->dblquote = 0;
+ }
+
+ quotef = 1;
+
+ if (c == '"')
+ goto toggledq;
+
+ goto quotemark;
case CVAR: /* '$' */
PARSESUB(); /* parse substitution */
break;
case CENDVAR: /* '}' */
- if (varnest > 0) {
- varnest--;
- if (dqvarnest > 0) {
- dqvarnest--;
- }
+ if (!synstack->innerdq && synstack->varnest > 0) {
+ if (!--synstack->varnest && synstack->varpushed)
+ synstack_pop(&synstack);
+ else if (synstack->dqvarnest > 0)
+ synstack->dqvarnest--;
c = CTLENDVAR;
}
USTPUTC(c, out);
break;
#if ENABLE_FEATURE_SH_MATH
case CLP: /* '(' in arithmetic */
- parenlevel++;
+ synstack->parenlevel++;
USTPUTC(c, out);
break;
case CRP: /* ')' in arithmetic */
- if (parenlevel > 0) {
- parenlevel--;
+ if (synstack->parenlevel > 0) {
+ synstack->parenlevel--;
} else {
if (pgetc_eatbnl() == ')') {
c = CTLENDARI;
- if (--arinest == 0) {
- syntax = prevsyntax;
- }
+ synstack_pop(&synstack);
} else {
/*
* unbalanced parens
break;
#endif
case CBQUOTE: /* '`' */
+ if (checkkwd & CHKEOFMARK) {
+ quotef = 1;
+ USTPUTC('`', out);
+ break;
+ }
+
PARSEBACKQOLD();
break;
case CENDFILE:
case CIGN:
break;
default:
- if (varnest == 0) {
+ if (synstack->varnest == 0) {
#if BASH_REDIR_OUTPUT
if (c == '&') {
//Can't call pgetc_eatbnl() here, this requires three-deep pungetc()
endword:
#if ENABLE_FEATURE_SH_MATH
- if (syntax == ARISYNTAX)
+ if (synstack->syntax == ARISYNTAX)
raise_error_syntax("missing '))'");
#endif
- if (syntax != BASESYNTAX && eofmark == NULL)
+ if (synstack->syntax != BASESYNTAX && eofmark == NULL)
raise_error_syntax("unterminated quoted string");
- if (varnest != 0) {
+ if (synstack->varnest != 0) {
/* { */
raise_error_syntax("missing '}'");
}
np = stzalloc(sizeof(struct nfile));
if (c == '>') {
np->nfile.fd = 1;
- c = pgetc();
+ c = pgetc_eatbnl();
if (c == '>')
np->type = NAPPEND;
else if (c == '|')
#endif
else { /* c == '<' */
/*np->nfile.fd = 0; - stzalloc did it */
- c = pgetc();
+ c = pgetc_eatbnl();
switch (c) {
case '<':
if (sizeof(struct nfile) != sizeof(struct nhere)) {
np->type = NHERE;
heredoc = stzalloc(sizeof(struct heredoc));
heredoc->here = np;
- c = pgetc();
+ c = pgetc_eatbnl();
if (c == '-') {
heredoc->striptabs = 1;
} else {
|| (c != '(' && c != '{' && !is_name(c) && !is_special(c))
) {
#if BASH_DOLLAR_SQUOTE
- if (syntax != DQSYNTAX && c == '\'')
+ if (synstack->syntax != DQSYNTAX && c == '\'')
bash_dollar_squote = 1;
else
#endif
}
} else {
/* $VAR, $<specialchar>, ${...}, or PEOA/PEOF */
+ smalluint newsyn = synstack->syntax;
+
USTPUTC(CTLVAR, out);
typeloc = out - (char *)stackblock();
STADJUST(1, out);
STPUTC(c, out);
c = pgetc_eatbnl();
} while (isdigit(c));
- } else {
+ } else if (c != '}') {
/* $[{[#]]<specialchar>[}] */
int cc = c;
}
USTPUTC(cc, out);
- }
+ } else
+ goto badsub;
if (c != '}' && subtype == VSLENGTH) {
/* ${#VAR didn't end with } */
static const char types[] ALIGN1 = "}-+?=";
/* ${VAR...} but not $VAR or ${#VAR} */
/* c == first char after VAR */
+ int cc = c;
+
switch (c) {
case ':':
c = pgetc_eatbnl();
break;
}
case '%':
- case '#': {
- int cc = c;
+ case '#':
subtype = (c == '#' ? VSTRIMLEFT : VSTRIMRIGHT);
c = pgetc_eatbnl();
- if (c != cc)
- goto badsub;
- subtype++;
+ if (c == cc)
+ subtype++;
+ else
+ pungetc();
+
+ newsyn = BASESYNTAX;
break;
- }
#if BASH_PATTERN_SUBST
case '/':
/* ${v/[/]pattern/repl} */
//TODO: encode pattern and repl separately.
-// Currently ${v/$var_with_slash/repl} is horribly broken
+// Currently cases like: v=1;echo ${v/$((1/1))/ONE}
+// are broken (should print "ONE")
subtype = VSREPLACE;
+ newsyn = BASESYNTAX;
c = pgetc_eatbnl();
if (c != '/')
goto badsub;
badsub:
pungetc();
}
+
+ if (newsyn == ARISYNTAX)
+ newsyn = DQSYNTAX;
+
+ if ((newsyn != synstack->syntax || synstack->innerdq)
+ && subtype != VSNORMAL
+ ) {
+ synstack_push(&synstack,
+ synstack->prev ?: alloca(sizeof(*synstack)),
+ newsyn);
+
+ synstack->varpushed = 1;
+ synstack->dblquote = newsyn != BASESYNTAX;
+ }
+
((unsigned char *)stackblock())[typeloc] = subtype;
if (subtype != VSNORMAL) {
- varnest++;
- if (dblquote)
- dqvarnest++;
+ synstack->varnest++;
+ if (synstack->dblquote)
+ synstack->dqvarnest++;
}
STPUTC('=', out);
}
int pc;
setprompt_if(needprompt, 2);
- pc = pgetc();
+ pc = pgetc_eatbnl();
switch (pc) {
case '`':
goto done;
case '\\':
- pc = pgetc();
- if (pc == '\n') {
- nlprompt();
- /*
- * If eating a newline, avoid putting
- * the newline into the new character
- * stream (via the STPUTC after the
- * switch).
- */
- continue;
- }
+ pc = pgetc(); /* or pgetc_eatbnl()? why (example)? */
if (pc != '\\' && pc != '`' && pc != '$'
- && (!dblquote || pc != '"')
+ && (!synstack->dblquote || pc != '"')
) {
STPUTC('\\', pout);
}
* Parse an arithmetic expansion (indicate start of one and set state)
*/
parsearith: {
- if (++arinest == 1) {
- prevsyntax = syntax;
- syntax = ARISYNTAX;
- }
+
+ synstack_push(&synstack,
+ synstack->prev ?: alloca(sizeof(*synstack)),
+ ARISYNTAX);
+ synstack->dblquote = 1;
USTPUTC(CTLARI, out);
goto parsearith_return;
}
}
setprompt_if(needprompt, 2);
for (;;) { /* until token or start of word found */
- c = pgetc();
+ c = pgetc_eatbnl();
if (c == ' ' || c == '\t' IF_ASH_ALIAS( || c == PEOA))
continue;
continue;
pungetc();
} else if (c == '\\') {
- if (pgetc() != '\n') {
- pungetc();
- break; /* return readtoken1(...) */
- }
- nlprompt();
+ break; /* return readtoken1(...) */
} else {
const char *p;
break; /* return readtoken1(...) */
if ((int)(p - xxreadtoken_chars) >= xxreadtoken_singles) {
- int cc = pgetc();
+ int cc = pgetc_eatbnl();
if (cc == c) { /* double occurrence? */
p += xxreadtoken_doubles + 1;
} else {
}
setprompt_if(needprompt, 2);
for (;;) { /* until token or start of word found */
- c = pgetc();
+ c = pgetc_eatbnl();
switch (c) {
case ' ': case '\t':
IF_ASH_ALIAS(case PEOA:)
continue;
pungetc();
continue;
- case '\\':
- if (pgetc() == '\n') {
- nlprompt();
- continue;
- }
- pungetc();
- goto breakloop;
case '\n':
nlnoprompt();
RETURN(TNL);
case PEOF:
RETURN(TEOF);
case '&':
- if (pgetc() == '&')
+ if (pgetc_eatbnl() == '&')
RETURN(TAND);
pungetc();
RETURN(TBACKGND);
case '|':
- if (pgetc() == '|')
+ if (pgetc_eatbnl() == '|')
RETURN(TOR);
pungetc();
RETURN(TPIPE);
case ';':
- if (pgetc() == ';')
+ if (pgetc_eatbnl() == ';')
RETURN(TENDCASE);
pungetc();
RETURN(TSEMI);
RETURN(TLP);
case ')':
RETURN(TRP);
- default:
- goto breakloop;
}
+ break;
}
- breakloop:
return readtoken1(c, BASESYNTAX, (char *)NULL, 0);
#undef RETURN
}
heredoclist = NULL;
while (here) {
+ tokpushback = 0;
setprompt_if(needprompt, 2);
readtoken1(pgetc(), here->here->type == NHERE ? SQSYNTAX : DQSYNTAX,
here->eofmark, here->striptabs);
static int FAST_FUNC
readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
{
- char *opt_n = NULL;
- char *opt_p = NULL;
- char *opt_t = NULL;
- char *opt_u = NULL;
- char *opt_d = NULL; /* optimized out if !BASH */
- int read_flags = 0;
+ struct builtin_read_params params;
const char *r;
int i;
+ memset(¶ms, 0, sizeof(params));
+
while ((i = nextopt("p:u:rt:n:sd:")) != '\0') {
switch (i) {
case 'p':
- opt_p = optionarg;
+ params.opt_p = optionarg;
break;
case 'n':
- opt_n = optionarg;
+ params.opt_n = optionarg;
break;
case 's':
- read_flags |= BUILTIN_READ_SILENT;
+ params.read_flags |= BUILTIN_READ_SILENT;
break;
case 't':
- opt_t = optionarg;
+ params.opt_t = optionarg;
break;
case 'r':
- read_flags |= BUILTIN_READ_RAW;
+ params.read_flags |= BUILTIN_READ_RAW;
break;
case 'u':
- opt_u = optionarg;
+ params.opt_u = optionarg;
break;
#if BASH_READ_D
case 'd':
- opt_d = optionarg;
+ params.opt_d = optionarg;
break;
#endif
default:
}
}
+ params.argv = argptr;
+ params.setvar = setvar0;
+ params.ifs = bltinlookup("IFS"); /* can be NULL */
+
/* "read -s" needs to save/restore termios, can't allow ^C
* to jump out of it.
*/
again:
INT_OFF;
- r = shell_builtin_read(setvar0,
- argptr,
- bltinlookup("IFS"), /* can be NULL */
- read_flags,
- opt_n,
- opt_p,
- opt_t,
- opt_u,
- opt_d
- );
+ r = shell_builtin_read(¶ms);
INT_ON;
if ((uintptr_t)r == 1 && errno == EINTR) {
}
}
+ setvareq((char*)defifsvar, VTEXTFIXED);
setvareq((char*)defoptindvar, VTEXTFIXED);
setvar0("PPID", utoa(getppid()));
//usage:#define ash_trivial_usage
-//usage: "[-/+OPTIONS] [-/+o OPT]... [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]"
+//usage: "[-/+OPTIONS] [-/+o OPT]... [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS] / -s [ARGS]]"
//usage:#define ash_full_usage "\n\n"
//usage: "Unix shell interpreter"
xargv = argv;
login_sh = xargv[0] && xargv[0][0] == '-';
+#if NUM_SCRIPTS > 0
+ if (minusc)
+ goto setarg0;
+#endif
arg0 = xargv[0];
/* if (xargv[0]) - mmm, this is always true! */
xargv++;
+ argptr = xargv;
for (i = 0; i < NOPTS; i++)
optlist[i] = 2;
- argptr = xargv;
if (options(/*cmdline:*/ 1, &login_sh)) {
/* it already printed err message */
raise_exception(EXERROR);
* is used to figure out how far we had gotten.
*/
int ash_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+#if NUM_SCRIPTS > 0
+int ash_main(int argc, char **argv)
+#else
int ash_main(int argc UNUSED_PARAM, char **argv)
+#endif
+/* note: 'argc' is used only if embedded scripts are enabled */
{
volatile smallint state;
struct jmploc jmploc;
init();
setstackmark(&smark);
+
+#if NUM_SCRIPTS > 0
+ if (argc < 0)
+ /* Non-NULL minusc tells procargs that an embedded script is being run */
+ minusc = get_script_content(-argc - 1);
+#endif
login_sh = procargs(argv);
#if DEBUG
TRACE(("Shell args: "));