-/* $OpenBSD: eval.c,v 1.35 2010/03/24 08:27:26 fgsch Exp $ */
+/* $OpenBSD: eval.c,v 1.39 2013/07/01 17:25:27 jca Exp $ */
/*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+ * 2011, 2012, 2013
* Thorsten Glaser <tg@mirbsd.org>
*
* Provided that these terms and disclaimer and all copyright notices
#include "sh.h"
-__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.90 2010/07/17 22:09:33 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.142 2013/07/24 18:03:57 tg Exp $");
/*
* string expansion
*/
/* expansion generator state */
-typedef struct Expand {
- /* int type; */ /* see expand() */
- const char *str; /* string */
+typedef struct {
+ /* not including an "int type;" member, see expand() */
+ /* string */
+ const char *str;
+ /* source */
union {
- const char **strv; /* string[] */
- struct shf *shf; /* file */
- } u; /* source */
- struct tbl *var; /* variable in ${var..} */
- short split; /* split "$@" / call waitlast $() */
+ /* string[] */
+ const char **strv;
+ /* file */
+ struct shf *shf;
+ } u;
+ /* variable in ${var...} */
+ struct tbl *var;
+ /* split "$@" / call waitlast in $() */
+ bool split;
} Expand;
#define XBASE 0 /* scanning original */
#define IFS_NWS 2 /* have seen IFS non-white-space */
static int varsub(Expand *, const char *, const char *, int *, int *);
-static int comsub(Expand *, const char *);
+static int comsub(Expand *, const char *, int);
+static char *valsub(struct op *, Area *);
static char *trimsub(char *, char *, int);
-static void glob(char *, XPtrV *, int);
+static void glob(char *, XPtrV *, bool);
static void globit(XString *, char **, char *, XPtrV *, int);
static const char *maybe_expand_tilde(const char *, XString *, char **, int);
-static char *tilde(char *);
#ifndef MKSH_NOPWNAM
static char *homedir(char *);
#endif
static void alt_expand(XPtrV *, char *, char *, char *, int);
-static size_t utflen(const char *);
+static int utflen(const char *);
static void utfincptr(const char *, mksh_ari_t *);
/* UTFMODE functions */
-static size_t
+static int
utflen(const char *s)
{
size_t n;
}
} else
n = strlen(s);
- return (n);
+
+ if (n > 2147483647)
+ n = 2147483647;
+ return ((int)n);
}
static void
s->start = s->str = cp;
source = s;
if (yylex(ONEWORD) != LWORD)
- internal_errorf("substitute");
+ internal_errorf("bad substitution");
source = sold;
afree(s, ATEMP);
return (evalstr(yylval.cp, f));
return (vap.rw);
}
XPinit(w, 32);
- XPput(w, NULL); /* space for shell name */
+ /* space for shell name */
+ XPput(w, NULL);
while (*ap != NULL)
expand(*ap++, &w, f);
XPput(w, NULL);
struct tbl *var; /* variable for ${var..} */
struct SubType *prev; /* old type */
struct SubType *next; /* poped type (to avoid re-allocating) */
+ size_t base; /* begin position of expanded word */
short stype; /* [=+-?%#] action after expanded word */
- short base; /* begin position of expanded word */
short f; /* saved value of f (DOPAT, etc) */
uint8_t quotep; /* saved value of quote (for ${..[%#]..}) */
uint8_t quotew; /* saved value of quote (for ${..[+-=]..}) */
} SubType;
void
-expand(const char *cp, /* input word */
- XPtrV *wp, /* output words */
- int f) /* DO* flags */
+expand(
+ /* input word */
+ const char *ccp,
+ /* output words */
+ XPtrV *wp,
+ /* DO* flags */
+ int f)
{
int c = 0;
- int type; /* expansion type */
- int quote = 0; /* quoted */
- XString ds; /* destination string */
- char *dp; /* destination */
- const char *sp; /* source */
- int fdo, word; /* second pass flags; have word */
- int doblank; /* field splitting of parameter/command subst */
- Expand x = { /* expansion variables */
+ /* expansion type */
+ int type;
+ /* quoted */
+ int quote = 0;
+ /* destination string and live pointer */
+ XString ds;
+ char *dp;
+ /* source */
+ const char *sp;
+ /* second pass flags */
+ int fdo;
+ /* have word */
+ int word;
+ /* field splitting of parameter/command substitution */
+ int doblank;
+ /* expansion variables */
+ Expand x = {
NULL, { NULL }, NULL, 0
};
SubType st_head, *st;
- int newlines = 0; /* For trailing newlines in COMSUB */
- int saw_eq, tilde_ok;
- int make_magic;
+ /* record number of trailing newlines in COMSUB */
+ int newlines = 0;
+ bool saw_eq, make_magic;
+ int tilde_ok;
size_t len;
+ char *cp;
- if (cp == NULL)
+ if (ccp == NULL)
internal_errorf("expand(NULL)");
/* for alias, readonly, set, typeset commands */
- if ((f & DOVACHECK) && is_wdvarassign(cp)) {
+ if ((f & DOVACHECK) && is_wdvarassign(ccp)) {
f &= ~(DOVACHECK|DOBLANK|DOGLOB|DOTILDE);
f |= DOASNTILDE;
}
if (Flag(FMARKDIRS))
f |= DOMARKDIRS;
if (Flag(FBRACEEXPAND) && (f & DOGLOB))
- f |= DOBRACE_;
+ f |= DOBRACE;
- Xinit(ds, dp, 128, ATEMP); /* init dest. string */
+ /* init destination string */
+ Xinit(ds, dp, 128, ATEMP);
type = XBASE;
- sp = cp;
+ sp = ccp;
fdo = 0;
- saw_eq = 0;
- tilde_ok = (f & (DOTILDE|DOASNTILDE)) ? 1 : 0; /* must be 1/0 */
+ saw_eq = false;
+ /* must be 1/0 */
+ tilde_ok = (f & (DOTILDE|DOASNTILDE)) ? 1 : 0;
doblank = 0;
- make_magic = 0;
+ make_magic = false;
word = (f&DOBLANK) ? IFS_WS : IFS_WORD;
/* clang doesn't know OSUBST comes before CSUBST */
memset(&st_head, 0, sizeof(st_head));
st = &st_head;
- while (1) {
+ while (/* CONSTCOND */ 1) {
Xcheck(ds, dp);
switch (type) {
- case XBASE: /* original prefixed string */
+ case XBASE:
+ /* original prefixed string */
c = *sp++;
switch (c) {
case EOS:
c = *sp++;
break;
case QCHAR:
- quote |= 2; /* temporary quote */
+ /* temporary quote */
+ quote |= 2;
c = *sp++;
break;
case OQUOTE:
quote = st->quotew;
continue;
case COMSUB:
+ case FUNSUB:
+ case VALSUB:
tilde_ok = 0;
if (f & DONTRUNCOMMAND) {
word = IFS_WORD;
- *dp++ = '$'; *dp++ = '(';
+ *dp++ = '$';
+ *dp++ = c == COMSUB ? '(' : '{';
+ if (c != COMSUB)
+ *dp++ = c == FUNSUB ? ' ' : '|';
while (*sp != '\0') {
Xcheck(ds, dp);
*dp++ = *sp++;
}
- *dp++ = ')';
+ if (c != COMSUB) {
+ *dp++ = ';';
+ *dp++ = '}';
+ } else
+ *dp++ = ')';
} else {
- type = comsub(&x, sp);
- if (type == XCOM && (f&DOBLANK))
+ type = comsub(&x, sp, c);
+ if (type != XBASE && (f & DOBLANK))
doblank++;
sp = strnul(sp) + 1;
newlines = 0;
*dp++ = ')'; *dp++ = ')';
} else {
struct tbl v;
- char *p;
v.flag = DEFINED|ISSET|INTEGER;
- v.type = 10; /* not default */
+ /* not default */
+ v.type = 10;
v.name[0] = '\0';
v_evaluate(&v, substitute(sp, 0),
KSH_UNWIND_ERROR, true);
sp = strnul(sp) + 1;
- for (p = str_val(&v); *p; ) {
+ cp = str_val(&v);
+ while (*cp) {
Xcheck(ds, dp);
- *dp++ = *p++;
+ *dp++ = *cp++;
}
}
continue;
- case OSUBST: { /* ${{#}var{:}[=+-?#%]word} */
- /* format is:
+ case OSUBST: {
+ /* ${{#}var{:}[=+-?#%]word} */
+ /*-
+ * format is:
* OSUBST [{x] plain-variable-part \0
* compiled-word-part CSUBST [}x]
* This is where all syntax checking gets done...
*/
- const char *varname = ++sp; /* skip the { or x (}) */
+ /* skip the { or x (}) */
+ const char *varname = ++sp;
int stype;
int slen = 0;
- sp = cstrchr(sp, '\0') + 1; /* skip variable */
+ /* skip variable */
+ sp = cstrchr(sp, '\0') + 1;
type = varsub(&x, varname, sp, &stype, &slen);
if (type < 0) {
char *beg, *end, *str;
-
unwind_substsyn:
- sp = varname - 2; /* restore sp */
+ /* restore sp */
+ sp = varname - 2;
end = (beg = wdcopy(sp, ATEMP)) +
(wdscan(sp, CSUBST) - sp);
/* ({) the } or x is already skipped */
*end = EOS;
str = snptreef(NULL, 64, "%S", beg);
afree(beg, ATEMP);
- errorf("%s: bad substitution", str);
+ errorf("%s: %s", str, "bad substitution");
}
if (f & DOBLANK)
doblank++;
tilde_ok = 0;
- if (type == XBASE) { /* expand? */
+ if (type == XBASE) {
+ /* expand? */
if (!st->next) {
SubType *newst;
st->stype = stype;
st->base = Xsavepos(ds, dp);
st->f = f;
- st->var = x.var;
+ if (x.var == &vtemp) {
+ st->var = tempvar();
+ st->var->flag &= ~INTEGER;
+ /* can't fail here */
+ setstr(st->var,
+ str_val(x.var),
+ KSH_RETURN_ERROR | 0x4);
+ } else
+ st->var = x.var;
+
st->quotew = st->quotep = quote;
/* skip qualifier(s) */
if (stype)
sp += slen;
- switch (stype & 0x7f) {
+ switch (stype & 0x17F) {
+ case 0x100 | '#': {
+ char *beg, *end;
+ mksh_ari_t seed;
+ register uint32_t h;
+
+ beg = wdcopy(sp, ATEMP);
+ end = beg + (wdscan(sp, CSUBST) - sp);
+ end[-2] = EOS;
+ end = wdstrip(beg, 0);
+ afree(beg, ATEMP);
+ evaluate(substitute(end, 0),
+ &seed, KSH_UNWIND_ERROR, true);
+ /* hash with seed, for now */
+ h = seed;
+ NZATUpdateString(h,
+ str_val(st->var));
+ NZAATFinish(h);
+ x.str = shf_smprintf("%08X",
+ (unsigned int)h);
+ break;
+ }
+ case 0x100 | 'Q': {
+ struct shf shf;
+
+ shf_sopen(NULL, 0, SHF_WR|SHF_DYNAMIC, &shf);
+ print_value_quoted(&shf, str_val(st->var));
+ x.str = shf_sclose(&shf);
+ break;
+ }
case '0': {
char *beg, *mid, *end, *stg;
mksh_ari_t from = 0, num = -1, flen, finc = 0;
} else {
end = mid +
(wdscan(mid, ADELIM) - mid);
- if (end >= stg)
+ if (end >= stg ||
+ /* more than max delimiters */
+ end[-1] != /*{*/ '}')
goto unwind_substsyn;
end[-2] = EOS;
sp += end - beg - 1;
}
- evaluate(substitute(stg = wdstrip(beg, false, false), 0),
+ evaluate(substitute(stg = wdstrip(beg, 0), 0),
&from, KSH_UNWIND_ERROR, true);
afree(stg, ATEMP);
if (end) {
- evaluate(substitute(stg = wdstrip(mid, false, false), 0),
+ evaluate(substitute(stg = wdstrip(mid, 0), 0),
&num, KSH_UNWIND_ERROR, true);
afree(stg, ATEMP);
}
else
d[-2] = EOS;
sp += (d ? d : p) - s - 1;
- tpat0 = wdstrip(s, true, true);
+ tpat0 = wdstrip(s,
+ WDS_KEEPQ | WDS_MAGIC);
pat = substitute(tpat0, 0);
if (d) {
- d = wdstrip(p, true, false);
+ d = wdstrip(p, WDS_KEEPQ);
rrep = substitute(d, 0);
afree(d, ATEMP);
} else
*d = '\0';
afree(tpat0, ATEMP);
- /* reject empty pattern */
- if (!*pat || gmatchx("", pat, false))
+ /* check for special cases */
+ d = str_val(st->var);
+ mkssert(d != NULL);
+ switch (*pat) {
+ case '#':
+ /* anchor at begin */
+ tpat0 = pat + 1;
+ tpat1 = rrep;
+ tpat2 = d;
+ break;
+ case '%':
+ /* anchor at end */
+ tpat0 = pat + 1;
+ tpat1 = d;
+ tpat2 = rrep;
+ break;
+ case '\0':
+ /* empty pattern */
goto no_repl;
+ default:
+ tpat0 = pat;
+ /* silence gcc */
+ tpat1 = tpat2 = NULL;
+ }
+ if (gmatchx(null, tpat0, false)) {
+ /*
+ * pattern matches
+ * the empty string
+ */
+ if (tpat0 == pat)
+ goto no_repl;
+ /* but is anchored */
+ s = shf_smprintf("%s%s",
+ tpat1, tpat2);
+ goto do_repl;
+ }
/* prepare string on which to work */
- strdupx(s, str_val(st->var), ATEMP);
+ strdupx(s, d, ATEMP);
sbeg = s;
/* first see if we have any match at all */
tpat2 = tpat1 + 2;
}
again_repl:
- /* this would not be necessary if gmatchx would return
+ /*
+ * this would not be necessary if gmatchx would return
* the start and end values of a match found, like re*
*/
if (!gmatchx(sbeg, tpat1, false))
while (p >= sbeg) {
bool gotmatch;
- c = *p; *p = '\0';
- gotmatch = gmatchx(sbeg, tpat0, false);
+ c = *p;
+ *p = '\0';
+ gotmatch = tobool(gmatchx(sbeg, tpat0, false));
*p = c;
if (gotmatch)
break;
goto again_repl;
end_repl:
afree(tpat1, ATEMP);
+ do_repl:
x.str = s;
no_repl:
afree(pat, ATEMP);
}
case '#':
case '%':
- /* ! DOBLANK,DOBRACE_,DOTILDE */
- f = DOPAT | (f&DONTRUNCOMMAND) |
- DOTEMP_;
+ /* ! DOBLANK,DOBRACE,DOTILDE */
+ f = (f & DONTRUNCOMMAND) |
+ DOPAT | DOTEMP;
st->quotew = quote = 0;
- /* Prepend open pattern (so |
+ /*
+ * Prepend open pattern (so |
* in a trim will work as
* expected)
*/
- *dp++ = MAGIC;
- *dp++ = (char)('@' | 0x80);
+ if (!Flag(FSH)) {
+ *dp++ = MAGIC;
+ *dp++ = '@' | 0x80;
+ }
break;
case '=':
- /* Enabling tilde expansion
+ /*
+ * Enabling tilde expansion
* after :s here is
* non-standard ksh, but is
* consistent with rules for
*/
if (!(x.var->flag & INTEGER))
f |= DOASNTILDE|DOTILDE;
- f |= DOTEMP_;
- /* These will be done after the
+ f |= DOTEMP;
+ /*
+ * These will be done after the
* value has been assigned.
*/
- f &= ~(DOBLANK|DOGLOB|DOBRACE_);
+ f &= ~(DOBLANK|DOGLOB|DOBRACE);
tilde_ok = 1;
break;
case '?':
f &= ~DOBLANK;
- f |= DOTEMP_;
+ f |= DOTEMP;
/* FALLTHROUGH */
default:
/* Enable tilde expansion */
sp += wdscan(sp, CSUBST) - sp;
continue;
}
- case CSUBST: /* only get here if expanding word */
+ case CSUBST:
+ /* only get here if expanding word */
do_CSUBST:
- sp++; /* ({) skip the } or x */
- tilde_ok = 0; /* in case of ${unset:-} */
+ /* ({) skip the } or x */
+ sp++;
+ /* in case of ${unset:-} */
+ tilde_ok = 0;
*dp = '\0';
quote = st->quotep;
f = st->f;
if (f&DOBLANK)
doblank--;
- switch (st->stype&0x7f) {
+ switch (st->stype & 0x17F) {
case '#':
case '%':
- /* Append end-pattern */
- *dp++ = MAGIC; *dp++ = ')'; *dp = '\0';
+ if (!Flag(FSH)) {
+ /* Append end-pattern */
+ *dp++ = MAGIC;
+ *dp++ = ')';
+ }
+ *dp = '\0';
dp = Xrestpos(ds, dp, st->base);
- /* Must use st->var since calling
+ /*
+ * Must use st->var since calling
* global would break things
* like x[i+=1].
*/
st = st->prev;
continue;
case '=':
- /* Restore our position and substitute
+ /*
+ * Restore our position and substitute
* the value of st->var (may not be
* the assigned value in the presence
* of integer/right-adj/etc attributes).
*/
dp = Xrestpos(ds, dp, st->base);
- /* Must use st->var since calling
+ /*
+ * Must use st->var since calling
* global would cause with things
* like x[i+=1] to be evaluated twice.
*/
- /* Note: not exported by FEXPORT
+ /*
+ * Note: not exported by FEXPORT
* in AT&T ksh.
*/
- /* XXX POSIX says readonly is only
+ /*
+ * XXX POSIX says readonly is only
* fatal for special builtins (setstr
* does readonly check).
*/
}
case '0':
case '/':
+ case 0x100 | '#':
+ case 0x100 | 'Q':
dp = Xrestpos(ds, dp, st->base);
type = XSUB;
if (f&DOBLANK)
type = XBASE;
continue;
- case OPAT: /* open pattern: *(foo|bar) */
+ case OPAT:
+ /* open pattern: *(foo|bar) */
/* Next char is the type of pattern */
- make_magic = 1;
- c = *sp++ + 0x80;
+ make_magic = true;
+ c = *sp++ | 0x80;
break;
- case SPAT: /* pattern separator (|) */
- make_magic = 1;
+ case SPAT:
+ /* pattern separator (|) */
+ make_magic = true;
c = '|';
break;
- case CPAT: /* close pattern */
- make_magic = 1;
+ case CPAT:
+ /* close pattern */
+ make_magic = true;
c = /*(*/ ')';
break;
}
break;
case XNULLSUB:
- /* Special case for "$@" (and "${foo[@]}") - no
+ /*
+ * Special case for "$@" (and "${foo[@]}") - no
* word is generated if $# is 0 (unless there is
* other stuff inside the quotes).
*/
type = XBASE;
if (f&DOBLANK) {
doblank--;
- /* not really correct: x=; "$x$@" should
+ /*
+ * not really correct: x=; "$x$@" should
* generate a null argument and
* set A; "${@:+}" shouldn't.
*/
case XARGSEP:
type = XARG;
quote = 1;
+ /* FALLTHROUGH */
case XARG:
if ((c = *x.str++) == '\0') {
- /* force null words to be created so
+ /*
+ * force null words to be created so
* set -- '' 2 ''; foo "$@" will do
* the right thing
*/
if (c == 0) {
if (quote && !x.split)
continue;
+ /* this is so we don't terminate */
c = ' ';
+ /* now force-emit a word */
+ goto emit_word;
}
if (quote && x.split) {
/* terminate word for "$@" */
break;
case XCOM:
- if (newlines) { /* Spit out saved NLs */
+ if (x.u.shf == NULL) {
+ /* $(<...) failed */
+ subst_exstat = 1;
+ /* fake EOF */
+ c = EOF;
+ } else if (newlines) {
+ /* spit out saved NLs */
c = '\n';
--newlines;
} else {
while ((c = shf_getc(x.u.shf)) == 0 || c == '\n')
if (c == '\n')
- /* Save newlines */
+ /* save newlines */
newlines++;
if (newlines && c != EOF) {
shf_ungetc(c, x.u.shf);
}
if (c == EOF) {
newlines = 0;
- shf_close(x.u.shf);
+ if (x.u.shf)
+ shf_close(x.u.shf);
if (x.split)
subst_exstat = waitlast();
type = XBASE;
/* check for end of word or IFS separation */
if (c == 0 || (!quote && (f & DOBLANK) && doblank &&
!make_magic && ctype(c, C_IFS))) {
- /* How words are broken up:
+ /*-
+ * How words are broken up:
* | value of c
* word | ws nws 0
* -----------------------------------
*/
if (word == IFS_WORD ||
(!ctype(c, C_IFSWS) && c && word == IFS_NWS)) {
- char *p;
-
+ emit_word:
*dp++ = '\0';
- p = Xclose(ds, dp);
- if (fdo & DOBRACE_)
+ cp = Xclose(ds, dp);
+ if (fdo & DOBRACE)
/* also does globbing */
- alt_expand(wp, p, p,
- p + Xlength(ds, (dp - 1)),
+ alt_expand(wp, cp, cp,
+ cp + Xlength(ds, (dp - 1)),
fdo | (f & DOMARKDIRS));
else if (fdo & DOGLOB)
- glob(p, wp, f & DOMARKDIRS);
- else if ((f & DOPAT) || !(fdo & DOMAGIC_))
- XPput(*wp, p);
+ glob(cp, wp, tobool(f & DOMARKDIRS));
+ else if ((f & DOPAT) || !(fdo & DOMAGIC))
+ XPput(*wp, cp);
else
- XPput(*wp, debunk(p, p, strlen(p) + 1));
+ XPput(*wp, debunk(cp, cp,
+ strlen(cp) + 1));
fdo = 0;
- saw_eq = 0;
+ saw_eq = false;
tilde_ok = (f & (DOTILDE|DOASNTILDE)) ? 1 : 0;
- if (c != 0)
- Xinit(ds, dp, 128, ATEMP);
- }
- if (c == 0)
+ if (c == 0)
+ return;
+ Xinit(ds, dp, 128, ATEMP);
+ } else if (c == 0) {
return;
+ } else if (type == XSUB && ctype(c, C_IFS) &&
+ !ctype(c, C_IFSWS) && Xlength(ds, dp) == 0) {
+ *(cp = alloc(1, ATEMP)) = '\0';
+ XPput(*wp, cp);
+ type = XSUBMID;
+ }
if (word != IFS_NWS)
word = ctype(c, C_IFSWS) ? IFS_WS : IFS_NWS;
} else {
if (type == XSUB) {
if (word == IFS_NWS &&
Xlength(ds, dp) == 0) {
- char *p;
-
- *(p = alloc(1, ATEMP)) = '\0';
- XPput(*wp, p);
+ *(cp = alloc(1, ATEMP)) = '\0';
+ XPput(*wp, cp);
}
type = XSUBMID;
}
if (!quote)
switch (c) {
case '[':
- case NOT:
+ case '!':
case '-':
case ']':
- /* For character classes - doesn't hurt
+ /*
+ * For character classes - doesn't hurt
* to have magic !,-,]s outside of
* [...] expressions.
*/
if (f & (DOPAT | DOGLOB)) {
- fdo |= DOMAGIC_;
+ fdo |= DOMAGIC;
if (c == '[')
fdo |= f & DOGLOB;
*dp++ = MAGIC;
case '*':
case '?':
if (f & (DOPAT | DOGLOB)) {
- fdo |= DOMAGIC_ | (f & DOGLOB);
+ fdo |= DOMAGIC | (f & DOGLOB);
*dp++ = MAGIC;
}
break;
- case OBRACE:
+ case '{':
+ case '}':
case ',':
- case CBRACE:
- if ((f & DOBRACE_) && (c == OBRACE ||
- (fdo & DOBRACE_))) {
- fdo |= DOBRACE_|DOMAGIC_;
+ if ((f & DOBRACE) && (c == '{' /*}*/ ||
+ (fdo & DOBRACE))) {
+ fdo |= DOBRACE|DOMAGIC;
*dp++ = MAGIC;
}
break;
case '=':
/* Note first unquoted = for ~ */
- if (!(f & DOTEMP_) && !saw_eq &&
+ if (!(f & DOTEMP) && !saw_eq &&
(Flag(FBRACEEXPAND) ||
(f & DOASNTILDE))) {
- saw_eq = 1;
+ saw_eq = true;
tilde_ok = 1;
}
break;
- case ':': /* : */
+ case ':':
+ /* : */
/* Note unquoted : for ~ */
- if (!(f & DOTEMP_) && (f & DOASNTILDE))
+ if (!(f & DOTEMP) && (f & DOASNTILDE))
tilde_ok = 1;
break;
case '~':
- /* tilde_ok is reset whenever
+ /*
+ * tilde_ok is reset whenever
* any of ' " $( $(( ${ } are seen.
* Note that tilde_ok must be preserved
* through the sequence ${A=a=}~
if (type == XBASE &&
(f & (DOTILDE|DOASNTILDE)) &&
(tilde_ok & 2)) {
- const char *p;
- char *dp_x;
+ const char *tcp;
+ char *tdp = dp;
- dp_x = dp;
- p = maybe_expand_tilde(sp,
- &ds, &dp_x,
+ tcp = maybe_expand_tilde(sp,
+ &ds, &tdp,
f & DOASNTILDE);
- if (p) {
- if (dp != dp_x)
+ if (tcp) {
+ if (dp != tdp)
word = IFS_WORD;
- dp = dp_x;
- sp = p;
+ dp = tdp;
+ sp = tcp;
continue;
}
}
break;
}
else
- quote &= ~2; /* undo temporary */
+ /* undo temporary */
+ quote &= ~2;
if (make_magic) {
- make_magic = 0;
- fdo |= DOMAGIC_ | (f & DOGLOB);
+ make_magic = false;
+ fdo |= DOMAGIC | (f & DOGLOB);
*dp++ = MAGIC;
} else if (ISMAGIC(c)) {
- fdo |= DOMAGIC_;
+ fdo |= DOMAGIC;
*dp++ = MAGIC;
}
- *dp++ = c; /* save output char */
+ /* save output char */
+ *dp++ = c;
word = IFS_WORD;
}
}
struct tbl *vp;
bool zero_ok = false;
- if ((stype = sp[0]) == '\0') /* Bad variable name */
+ if ((stype = sp[0]) == '\0')
+ /* Bad variable name */
return (-1);
xp->var = NULL;
}
}
if (Flag(FNOUNSET) && c == 0 && !zero_ok)
- errorf("%s: parameter not set", sp);
- *stypep = 0; /* unqualified variable/string substitution */
+ errorf("%s: %s", sp, "parameter not set");
+ /* unqualified variable/string substitution */
+ *stypep = 0;
xp->str = shf_smprintf("%d", c);
return (XSUB);
}
} else if (ctype(c, C_SUBOP1)) {
slen += 2;
stype |= c;
- } else if (ctype(c, C_SUBOP2)) { /* Note: ksh88 allows :%, :%%, etc */
+ } else if (ctype(c, C_SUBOP2)) {
+ /* Note: ksh88 allows :%, :%%, etc */
slen += 2;
stype = c;
if (word[slen + 0] == CHAR && c == word[slen + 1]) {
stype |= 0x80;
slen += 2;
}
- } else if (stype) /* : is not ok */
+ } else if (c == '@') {
+ /* @x where x is command char */
+ slen += 2;
+ stype |= 0x100;
+ if (word[slen] == CHAR) {
+ stype |= word[slen + 1];
+ slen += 2;
+ }
+ } else if (stype)
+ /* : is not ok */
return (-1);
if (!stype && *word != CSUBST)
return (-1);
c = sp[0];
if (c == '*' || c == '@') {
- switch (stype & 0x7f) {
- case '=': /* can't assign to a vector */
- case '%': /* can't trim a vector (yet) */
+ switch (stype & 0x17F) {
+ /* can't assign to a vector */
+ case '=':
+ /* can't trim a vector (yet) */
+ case '%':
case '#':
case '0':
case '/':
+ case 0x100 | '#':
+ case 0x100 | 'Q':
return (-1);
}
if (e->loc->argc == 0) {
} else {
xp->u.strv = (const char **)e->loc->argv + 1;
xp->str = *xp->u.strv++;
- xp->split = c == '@'; /* $@ */
+ /* $@ */
+ xp->split = tobool(c == '@');
state = XARG;
}
- zero_ok = true; /* POSIX 2009? */
+ /* POSIX 2009? */
+ zero_ok = true;
} else {
if ((p = cstrchr(sp, '[')) && (p[1] == '*' || p[1] == '@') &&
p[2] == ']') {
XPtrV wv;
- switch (stype & 0x7f) {
- case '=': /* can't assign to a vector */
- case '%': /* can't trim a vector (yet) */
+ switch (stype & 0x17F) {
+ /* can't assign to a vector */
+ case '=':
+ /* can't trim a vector (yet) */
+ case '%':
case '#':
case '?':
case '0':
case '/':
+ case 0x100 | '#':
+ case 0x100 | 'Q':
return (-1);
}
XPinit(wv, 32);
XPput(wv, 0);
xp->u.strv = (const char **)XPptrv(wv);
xp->str = *xp->u.strv++;
- xp->split = p[1] == '@'; /* ${foo[@]} */
+ /* ${foo[@]} */
+ xp->split = tobool(p[1] == '@');
state = XARG;
}
} else {
/* Can't assign things like $! or $1 */
- if ((stype & 0x7f) == '=' &&
+ if ((stype & 0x17F) == '=' &&
ctype(*sp, C_VAR1 | C_DIGIT))
return (-1);
if (*sp == '!' && sp[1]) {
++sp;
xp->var = global(sp);
- if (cstrchr(sp, '[')) {
+ if (vstrchr(sp, '[')) {
if (xp->var->flag & ISSET)
xp->str = shf_smprintf("%lu",
arrayindex(xp->var));
} else if (xp->var->flag & ISSET)
xp->str = xp->var->name;
else
- xp->str = "0"; /* ksh93 compat */
+ /* ksh93 compat */
+ xp->str = "0";
} else {
xp->var = global(sp);
xp->str = str_val(xp->var);
}
}
- c = stype&0x7f;
+ c = stype & 0x7F;
/* test the compiler's code generator */
- if (ctype(c, C_SUBOP2) || stype == (0x80 | '0') || c == '/' ||
+ if (((stype < 0x100) && (ctype(c, C_SUBOP2) || c == '/' ||
(((stype&0x80) ? *xp->str=='\0' : xp->str==null) ? /* undef? */
- c == '=' || c == '-' || c == '?' : c == '+'))
- state = XBASE; /* expand word instead of variable value */
+ c == '=' || c == '-' || c == '?' : c == '+'))) ||
+ stype == (0x80 | '0') || stype == (0x100 | '#') ||
+ stype == (0x100 | 'Q'))
+ /* expand word instead of variable value */
+ state = XBASE;
if (Flag(FNOUNSET) && xp->str == null && !zero_ok &&
(ctype(c, C_SUBOP2) || (state != XBASE && c != '+')))
- errorf("%s: parameter not set", sp);
+ errorf("%s: %s", sp, "parameter not set");
return (state);
}
* Run the command in $(...) and read its output.
*/
static int
-comsub(Expand *xp, const char *cp)
+comsub(Expand *xp, const char *cp, int fn MKSH_A_UNUSED)
{
Source *s, *sold;
struct op *t;
struct shf *shf;
+ uint8_t old_utfmode = UTFMODE;
s = pushs(SSTRING, ATEMP);
s->start = s->str = cp;
sold = source;
- t = compile(s);
+ t = compile(s, true);
afree(s, ATEMP);
source = sold;
+ UTFMODE = old_utfmode;
+
if (t == NULL)
return (XBASE);
- if (t != NULL && t->type == TCOM && /* $(<file) */
+ /* no waitlast() unless specifically enabled later */
+ xp->split = false;
+
+ if (t->type == TCOM &&
*t->args == NULL && *t->vars == NULL && t->ioact != NULL) {
+ /* $(<file) */
struct ioword *io = *t->ioact;
char *name;
- if ((io->flag&IOTYPE) != IOREAD)
- errorf("funny $() command: %s",
+ if ((io->flag & IOTYPE) != IOREAD)
+ errorf("%s: %s", "funny $() command",
snptreef(NULL, 32, "%R", io));
shf = shf_open(name = evalstr(io->name, DOTILDE), O_RDONLY, 0,
SHF_MAPHI|SHF_CLEXEC);
if (shf == NULL)
- errorf("%s: cannot open $() input", name);
- xp->split = 0; /* no waitlast() */
+ warningf(!Flag(FTALKING), "%s: %s %s: %s", name,
+ "can't open", "$(<...) input", cstrerror(errno));
+ } else if (fn == FUNSUB) {
+ int ofd1;
+ struct temp *tf = NULL;
+
+ /*
+ * create a temporary file, open for reading and writing,
+ * with an shf open for reading (buffered) but yet unused
+ */
+ maketemp(ATEMP, TT_FUNSUB, &tf);
+ if (!tf->shf) {
+ errorf("can't %s temporary file %s: %s",
+ "create", tf->tffn, cstrerror(errno));
+ }
+ /* extract shf from temporary file, unlink and free it */
+ shf = tf->shf;
+ unlink(tf->tffn);
+ afree(tf, ATEMP);
+ /* save stdout and let it point to the tempfile */
+ ofd1 = savefd(1);
+ ksh_dup2(shf_fileno(shf), 1, false);
+ /*
+ * run tree, with output thrown into the tempfile,
+ * in a new function block
+ */
+ valsub(t, NULL);
+ subst_exstat = exstat & 0xFF;
+ /* rewind the tempfile and restore regular stdout */
+ lseek(shf_fileno(shf), (off_t)0, SEEK_SET);
+ restfd(1, ofd1);
+ } else if (fn == VALSUB) {
+ xp->str = valsub(t, ATEMP);
+ subst_exstat = exstat & 0xFF;
+ return (XSUB);
} else {
int ofd1, pv[2];
+
openpipe(pv);
shf = shf_fdopen(pv[0], SHF_RD, NULL);
ofd1 = savefd(1);
ksh_dup2(pv[1], 1, false);
close(pv[1]);
}
- execute(t, XFORK|XXCOM|XPIPEO, NULL);
+ execute(t, XXCOM | XPIPEO | XFORK, NULL);
restfd(1, ofd1);
startlast();
- xp->split = 1; /* waitlast() */
+ /* waitlast() */
+ xp->split = true;
}
xp->u.shf = shf;
/*
* perform #pattern and %pattern substitution in ${}
*/
-
static char *
trimsub(char *str, char *pat, int how)
{
char *p, c;
switch (how & 0xFF) {
- case '#': /* shortest at beginning */
+ case '#':
+ /* shortest match at beginning */
for (p = str; p <= end; p += utf_ptradj(p)) {
c = *p; *p = '\0';
if (gmatchx(str, pat, false)) {
*p = c;
}
break;
- case '#'|0x80: /* longest match at beginning */
+ case '#'|0x80:
+ /* longest match at beginning */
for (p = end; p >= str; p--) {
c = *p; *p = '\0';
if (gmatchx(str, pat, false)) {
*p = c;
}
break;
- case '%': /* shortest match at end */
+ case '%':
+ /* shortest match at end */
p = end;
while (p >= str) {
if (gmatchx(p, pat, false))
--p;
}
break;
- case '%'|0x80: /* longest match at end */
+ case '%'|0x80:
+ /* longest match at end */
for (p = str; p <= end; p++)
if (gmatchx(p, pat, false)) {
trimsub_match:
break;
}
- return (str); /* no match, return string */
+ /* no match, return string */
+ return (str);
}
/*
/* XXX cp not const 'cause slashes are temporarily replaced with NULs... */
static void
-glob(char *cp, XPtrV *wp, int markdirs)
+glob(char *cp, XPtrV *wp, bool markdirs)
{
int oldsize = XPsize(*wp);
#define GF_GLOBBED BIT(1) /* some globbing has been done */
#define GF_MARKDIR BIT(2) /* add trailing / to directories */
-/* Apply file globbing to cp and store the matching files in wp. Returns
+/*
+ * Apply file globbing to cp and store the matching files in wp. Returns
* the number of matches found.
*/
int
-glob_str(char *cp, XPtrV *wp, int markdirs)
+glob_str(char *cp, XPtrV *wp, bool markdirs)
{
int oldsize = XPsize(*wp);
XString xs;
/* This to allow long expansions to be interrupted */
intrcheck();
- if (sp == NULL) { /* end of source path */
- /* We only need to check if the file exists if a pattern
+ if (sp == NULL) {
+ /* end of source path */
+ /*
+ * We only need to check if the file exists if a pattern
* is followed by a non-pattern (eg, foo*x/bar; no check
* is needed for foo* since the match must exist) or if
* any patterns were expanded and the markdirs option is set.
*/
if ((check & GF_EXCHECK) ||
((check & GF_MARKDIR) && (check & GF_GLOBBED))) {
-#define stat_check() (stat_done ? stat_done : \
- (stat_done = stat(Xstring(*xs, xp), &statb) < 0 \
- ? -1 : 1))
+#define stat_check() (stat_done ? stat_done : (stat_done = \
+ stat(Xstring(*xs, xp), &statb) < 0 ? -1 : 1))
struct stat lstatb, statb;
- int stat_done = 0; /* -1: failed, 1 ok */
+ /* -1: failed, 1 ok, 0 not yet done */
+ int stat_done = 0;
- if (lstat(Xstring(*xs, xp), &lstatb) < 0)
+ if (mksh_lstat(Xstring(*xs, xp), &lstatb) < 0)
return;
- /* special case for systems which strip trailing
+ /*
+ * special case for systems which strip trailing
* slashes from regular files (eg, /etc/passwd/).
* SunOS 4.1.3 does this...
*/
(!S_ISLNK(lstatb.st_mode) ||
stat_check() < 0 || !S_ISDIR(statb.st_mode)))
return;
- /* Possibly tack on a trailing / if there isn't already
+ /*
+ * Possibly tack on a trailing / if there isn't already
* one and if the file is a directory or a symlink to a
* directory
*/
np = strchr(sp, '/');
if (np != NULL) {
se = np;
- odirsep = *np; /* don't assume '/', can be multiple kinds */
+ /* don't assume '/', can be multiple kinds */
+ odirsep = *np;
*np++ = '\0';
} else {
odirsep = '\0'; /* keep gcc quiet */
}
- /* Check if sp needs globbing - done to avoid pattern checks for strings
+ /*
+ * Check if sp needs globbing - done to avoid pattern checks for strings
* containing MAGIC characters, open [s without the matching close ],
* etc. (otherwise opendir() will be called which may fail because the
* directory isn't readable - if no globbing is needed, only execute
DIR *dirp;
struct dirent *d;
char *name;
- int len;
- int prefix_len;
+ size_t len, prefix_len;
/* xp = *xpp; copy_non_glob() may have re-alloc'd xs */
*xp = '\0';
name = d->d_name;
if (name[0] == '.' &&
(name[1] == 0 || (name[1] == '.' && name[2] == 0)))
- continue; /* always ignore . and .. */
+ /* always ignore . and .. */
+ continue;
if ((*name == '.' && *sp != '.') ||
!gmatchx(name, sp, true))
continue;
return (dp);
}
-/* Check if p is an unquoted name, possibly followed by a / or :. If so
+/*
+ * Check if p is an unquoted name, possibly followed by a / or :. If so
* puts the expanded version in *dcp,dp and returns a pointer in p just
* past the name, otherwise returns 0.
*/
* based on a version by Arnold Robbins
*/
-static char *
+char *
tilde(char *cp)
{
char *dp = null;
char *p;
/* search for open brace */
- for (p = exp_start; (p = strchr(p, MAGIC)) && p[1] != OBRACE; p += 2)
+ for (p = exp_start; (p = strchr(p, MAGIC)) && p[1] != '{' /*}*/; p += 2)
;
brace_start = p;
count = 1;
for (p += 2; *p && count; p++) {
if (ISMAGIC(*p)) {
- if (*++p == OBRACE)
+ if (*++p == '{' /*}*/)
count++;
- else if (*p == CBRACE)
+ else if (*p == /*{*/ '}')
--count;
else if (*p == ',' && count == 1)
comma = p;
}
/* no valid expansions... */
if (!p || count != 0) {
- /* Note that given a{{b,c} we do not expand anything (this is
+ /*
+ * Note that given a{{b,c} we do not expand anything (this is
* what AT&T ksh does. This may be changed to do the {b,c}
* expansion. }
*/
if (fdo & DOGLOB)
- glob(start, wp, fdo & DOMARKDIRS);
+ glob(start, wp, tobool(fdo & DOMARKDIRS));
else
XPput(*wp, debunk(start, start, end - start));
return;
count = 1;
for (p = brace_start + 2; p != brace_end; p++) {
if (ISMAGIC(*p)) {
- if (*++p == OBRACE)
+ if (*++p == '{' /*}*/)
count++;
- else if ((*p == CBRACE && --count == 0) ||
+ else if ((*p == /*{*/ '}' && --count == 0) ||
(*p == ',' && count == 1)) {
char *news;
int l1, l2, l3;
+ /*
+ * addition safe since these operate on
+ * one string (separate substrings)
+ */
l1 = brace_start - start;
l2 = (p - 1) - field_start;
l3 = end - brace_end;
}
return;
}
+
+/* helper function due to setjmp/longjmp woes */
+static char *
+valsub(struct op *t, Area *ap)
+{
+ char * volatile cp = NULL;
+ struct tbl * volatile vp = NULL;
+
+ newenv(E_FUNC);
+ newblock();
+ if (ap)
+ vp = local("REPLY", false);
+ if (!kshsetjmp(e->jbuf))
+ execute(t, XXCOM | XERROK, NULL);
+ if (vp)
+ strdupx(cp, str_val(vp), ap);
+ quitenv(NULL);
+
+ return (cp);
+}