+void
+expandarg(arg, arglist, full)
+ union node *arg;
+ struct arglist *arglist;
+ {
+ struct strlist *sp;
+ char *p;
+
+#if UDIR
+ didudir = 0;
+#endif
+ argbackq = arg->narg.backquote;
+ STARTSTACKSTR(expdest);
+ ifsfirst.next = NULL;
+ ifslastp = NULL;
+ argstr(arg->narg.text, full);
+ if (arglist == NULL)
+ return; /* here document expanded */
+ STPUTC('\0', expdest);
+ p = grabstackstr(expdest);
+ exparg.lastp = &exparg.list;
+ if (full) {
+ ifsbreakup(p, &exparg);
+ *exparg.lastp = NULL;
+ exparg.lastp = &exparg.list;
+ expandmeta(exparg.list);
+ } else {
+ sp = (struct strlist *)stalloc(sizeof (struct strlist));
+ sp->text = p;
+ *exparg.lastp = sp;
+ exparg.lastp = &sp->next;
+ }
+ while (ifsfirst.next != NULL) {
+ struct ifsregion *ifsp;
+ INTOFF;
+ ifsp = ifsfirst.next->next;
+ ckfree(ifsfirst.next);
+ ifsfirst.next = ifsp;
+ INTON;
+ }
+ *exparg.lastp = NULL;
+ if (exparg.list) {
+ *arglist->lastp = exparg.list;
+ arglist->lastp = exparg.lastp;
+ }
+}
+
+
+
+/*
+ * Perform variable and command substitution. If full is set, output CTLESC
+ * characters to allow for further processing. If full is not set, treat
+ * $@ like $* since no splitting will be performed.
+ */
+
+STATIC void
+argstr(p, full)
+ register char *p;
+ {
+ char c;
+
+ for (;;) {
+ switch (c = *p++) {
+ case '\0':
+ case CTLENDVAR:
+ goto breakloop;
+ case CTLESC:
+ if (full)
+ STPUTC(c, expdest);
+ c = *p++;
+ STPUTC(c, expdest);
+ break;
+ case CTLVAR:
+ p = evalvar(p, full);
+ break;
+ case CTLBACKQ:
+ case CTLBACKQ|CTLQUOTE:
+ expbackq(argbackq->n, c & CTLQUOTE, full);
+ argbackq = argbackq->next;
+ break;
+ default:
+ STPUTC(c, expdest);
+ }
+ }
+breakloop:;
+}
+
+
+/*
+ * Expand stuff in backwards quotes.
+ */
+
+STATIC void
+expbackq(cmd, quoted, full)
+ union node *cmd;
+ {
+ struct backcmd in;
+ int i;
+ char buf[128];
+ char *p;
+ char *dest = expdest;
+ struct ifsregion saveifs, *savelastp;
+ struct nodelist *saveargbackq;
+ char lastc;
+ int startloc = dest - stackblock();
+ char const *syntax = quoted? DQSYNTAX : BASESYNTAX;
+ int saveherefd;
+
+ INTOFF;
+ saveifs = ifsfirst;
+ savelastp = ifslastp;
+ saveargbackq = argbackq;
+ saveherefd = herefd;
+ herefd = -1;
+ p = grabstackstr(dest);
+ evalbackcmd(cmd, &in);
+ ungrabstackstr(p, dest);
+ ifsfirst = saveifs;
+ ifslastp = savelastp;
+ argbackq = saveargbackq;
+ herefd = saveherefd;
+
+ p = in.buf;
+ lastc = '\0';
+ for (;;) {
+ if (--in.nleft < 0) {
+ if (in.fd < 0)
+ break;
+ while ((i = read(in.fd, buf, sizeof buf)) < 0 && errno == EINTR);
+ TRACE(("expbackq: read returns %d\n", i));
+ if (i <= 0)
+ break;
+ p = buf;
+ in.nleft = i - 1;
+ }
+ lastc = *p++;
+ if (lastc != '\0') {
+ if (full && syntax[lastc] == CCTL)
+ STPUTC(CTLESC, dest);
+ STPUTC(lastc, dest);
+ }
+ }
+ if (lastc == '\n') {
+ STUNPUTC(dest);
+ }
+ if (in.fd >= 0)
+ close(in.fd);
+ if (in.buf)
+ ckfree(in.buf);
+ if (in.jp)
+ waitforjob(in.jp);
+ if (quoted == 0)
+ recordregion(startloc, dest - stackblock(), 0);
+ TRACE(("evalbackq: size=%d: \"%.*s\"\n",
+ (dest - stackblock()) - startloc,
+ (dest - stackblock()) - startloc,
+ stackblock() + startloc));
+ expdest = dest;
+ INTON;
+}
+
+
+
+/*
+ * Expand a variable, and return a pointer to the next character in the
+ * input string.
+ */
+
+STATIC char *
+evalvar(p, full)
+ char *p;
+ {
+ int subtype;
+ int flags;
+ char *var;
+ char *val;
+ int c;
+ int set;
+ int special;
+ int startloc;
+
+ flags = *p++;
+ subtype = flags & VSTYPE;
+ var = p;
+ special = 0;
+ if (! is_name(*p))
+ special = 1;
+ p = strchr(p, '=') + 1;
+again: /* jump here after setting a variable with ${var=text} */
+ if (special) {
+ set = varisset(*var);
+ val = NULL;
+ } else {
+ val = lookupvar(var);
+ if (val == NULL || (flags & VSNUL) && val[0] == '\0') {
+ val = NULL;
+ set = 0;
+ } else
+ set = 1;
+ }
+ startloc = expdest - stackblock();
+ if (set && subtype != VSPLUS) {
+ /* insert the value of the variable */
+ if (special) {
+ varvalue(*var, flags & VSQUOTE, full);
+ } else {
+ char const *syntax = (flags & VSQUOTE)? DQSYNTAX : BASESYNTAX;
+
+ while (*val) {
+ if (full && syntax[*val] == CCTL)
+ STPUTC(CTLESC, expdest);
+ STPUTC(*val++, expdest);
+ }
+ }
+ }
+ if (subtype == VSPLUS)
+ set = ! set;
+ if (((flags & VSQUOTE) == 0 || (*var == '@' && shellparam.nparam != 1))
+ && (set || subtype == VSNORMAL))
+ recordregion(startloc, expdest - stackblock(), flags & VSQUOTE);
+ if (! set && subtype != VSNORMAL) {
+ if (subtype == VSPLUS || subtype == VSMINUS) {
+ argstr(p, full);
+ } else {
+ char *startp;
+ int saveherefd = herefd;
+ herefd = -1;
+ argstr(p, 0);
+ STACKSTRNUL(expdest);
+ herefd = saveherefd;
+ startp = stackblock() + startloc;
+ if (subtype == VSASSIGN) {
+ setvar(var, startp, 0);
+ STADJUST(startp - expdest, expdest);
+ flags &=~ VSNUL;
+ goto again;
+ }
+ /* subtype == VSQUESTION */
+ if (*p != CTLENDVAR) {
+ outfmt(&errout, "%s\n", startp);
+ error((char *)NULL);
+ }
+ error("%.*s: parameter %snot set", p - var - 1,
+ var, (flags & VSNUL)? "null or " : nullstr);
+ }
+ }
+ if (subtype != VSNORMAL) { /* skip to end of alternative */
+ int nesting = 1;
+ for (;;) {
+ if ((c = *p++) == CTLESC)
+ p++;
+ else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) {
+ if (set)
+ argbackq = argbackq->next;
+ } else if (c == CTLVAR) {
+ if ((*p++ & VSTYPE) != VSNORMAL)
+ nesting++;
+ } else if (c == CTLENDVAR) {
+ if (--nesting == 0)
+ break;
+ }
+ }
+ }
+ return p;
+}
+
+
+
+/*
+ * Test whether a specialized variable is set.
+ */
+
+STATIC int
+varisset(name)
+ char name;
+ {
+ char **ap;
+
+ if (name == '!') {
+ if (backgndpid == -1)
+ return 0;
+ } else if (name == '@' || name == '*') {
+ if (*shellparam.p == NULL)
+ return 0;
+ } else if ((unsigned)(name -= '1') <= '9' - '1') {
+ ap = shellparam.p;
+ do {
+ if (*ap++ == NULL)
+ return 0;
+ } while (--name >= 0);
+ }
+ return 1;
+}
+
+
+
+/*
+ * Add the value of a specialized variable to the stack string.
+ */
+
+STATIC void
+varvalue(name, quoted, allow_split)
+ char name;
+ {
+ int num;
+ char temp[32];
+ char *p;
+ int i;
+ extern int exitstatus;
+ char sep;
+ char **ap;
+ char const *syntax;
+
+ switch (name) {
+ case '$':
+ num = rootpid;
+ goto numvar;
+ case '?':
+ num = exitstatus;
+ goto numvar;
+ case '#':
+ num = shellparam.nparam;
+ goto numvar;
+ case '!':
+ num = backgndpid;
+numvar:
+ p = temp + 31;
+ temp[31] = '\0';
+ do {
+ *--p = num % 10 + '0';
+ } while ((num /= 10) != 0);
+ while (*p)
+ STPUTC(*p++, expdest);
+ break;
+ case '-':
+ for (i = 0 ; optchar[i] ; i++) {
+ if (optval[i])
+ STPUTC(optchar[i], expdest);
+ }
+ break;
+ case '@':
+ if (allow_split) {
+ sep = '\0';
+ goto allargs;
+ }
+ /* fall through */
+ case '*':
+ sep = ' ';
+allargs:
+ syntax = quoted? DQSYNTAX : BASESYNTAX;
+ for (ap = shellparam.p ; (p = *ap++) != NULL ; ) {
+ /* should insert CTLESC characters */
+ while (*p) {
+ if (syntax[*p] == CCTL)
+ STPUTC(CTLESC, expdest);
+ STPUTC(*p++, expdest);
+ }
+ if (*ap)
+ STPUTC(sep, expdest);
+ }
+ break;
+ case '0':
+ p = arg0;
+string:
+ syntax = quoted? DQSYNTAX : BASESYNTAX;
+ while (*p) {
+ if (syntax[*p] == CCTL)
+ STPUTC(CTLESC, expdest);
+ STPUTC(*p++, expdest);
+ }
+ break;
+ default:
+ if ((unsigned)(name -= '1') <= '9' - '1') {
+ p = shellparam.p[name];
+ goto string;
+ }
+ break;
+ }
+}
+
+
+
+/*
+ * Record the the fact that we have to scan this region of the
+ * string for IFS characters.
+ */
+
+STATIC void
+recordregion(start, end, nulonly) {
+ register struct ifsregion *ifsp;
+
+ if (ifslastp == NULL) {
+ ifsp = &ifsfirst;
+ } else {
+ ifsp = (struct ifsregion *)ckmalloc(sizeof (struct ifsregion));
+ ifslastp->next = ifsp;
+ }
+ ifslastp = ifsp;
+ ifslastp->next = NULL;
+ ifslastp->begoff = start;
+ ifslastp->endoff = end;
+ ifslastp->nulonly = nulonly;
+}
+
+
+
+/*
+ * Break the argument string into pieces based upon IFS and add the
+ * strings to the argument list. The regions of the string to be
+ * searched for IFS characters have been stored by recordregion.
+ */
+
+STATIC void
+ifsbreakup(string, arglist)
+ char *string;
+ struct arglist *arglist;
+ {
+ struct ifsregion *ifsp;
+ struct strlist *sp;
+ char *start;
+ register char *p;
+ char *q;
+ char *ifs;
+
+ start = string;
+ if (ifslastp != NULL) {
+ ifsp = &ifsfirst;
+ do {
+ p = string + ifsp->begoff;
+ ifs = ifsp->nulonly? nullstr : ifsval();
+ while (p < string + ifsp->endoff) {
+ q = p;
+ if (*p == CTLESC)
+ p++;
+ if (strchr(ifs, *p++)) {
+ if (q > start || *ifs != ' ') {
+ *q = '\0';
+ sp = (struct strlist *)stalloc(sizeof *sp);
+ sp->text = start;
+ *arglist->lastp = sp;
+ arglist->lastp = &sp->next;
+ }
+ if (*ifs == ' ') {
+ for (;;) {
+ if (p >= string + ifsp->endoff)
+ break;
+ q = p;
+ if (*p == CTLESC)
+ p++;
+ if (strchr(ifs, *p++) == NULL) {
+ p = q;
+ break;
+ }
+ }
+ }
+ start = p;
+ }
+ }
+ } while ((ifsp = ifsp->next) != NULL);
+ if (*start || (*ifs != ' ' && start > string)) {
+ sp = (struct strlist *)stalloc(sizeof *sp);
+ sp->text = start;
+ *arglist->lastp = sp;
+ arglist->lastp = &sp->next;
+ }
+ } else {
+ sp = (struct strlist *)stalloc(sizeof *sp);
+ sp->text = start;
+ *arglist->lastp = sp;
+ arglist->lastp = &sp->next;
+ }
+}
+
+
+
+/*
+ * Expand shell metacharacters. At this point, the only control characters
+ * should be escapes. The results are stored in the list exparg.
+ */
+
+char *expdir;
+
+
+STATIC void
+expandmeta(str)
+ struct strlist *str;
+ {
+ char *p;
+ struct strlist **savelastp;
+ struct strlist *sp;
+ char c;
+
+ while (str) {
+ if (fflag)
+ goto nometa;
+ p = str->text;
+#if UDIR
+ if (p[0] == '/' && p[1] == 'u' && p[2] == '/')
+ str->text = p = expudir(p);
+#endif
+ for (;;) { /* fast check for meta chars */
+ if ((c = *p++) == '\0')
+ goto nometa;
+ if (c == '*' || c == '?' || c == '[' || c == '!')
+ break;
+ }
+ savelastp = exparg.lastp;
+ INTOFF;
+ if (expdir == NULL)
+ expdir = ckmalloc(1024); /* I hope this is big enough */
+ expmeta(expdir, str->text);
+ ckfree(expdir);
+ expdir = NULL;
+ INTON;
+ if (exparg.lastp == savelastp) {
+ if (! zflag) {
+nometa:
+ *exparg.lastp = str;
+ rmescapes(str->text);
+ exparg.lastp = &str->next;
+ }
+ } else {
+ *exparg.lastp = NULL;
+ *savelastp = sp = expsort(*savelastp);
+ while (sp->next != NULL)
+ sp = sp->next;
+ exparg.lastp = &sp->next;
+ }
+ str = str->next;
+ }
+}
+
+
+#if UDIR
+/*
+ * Expand /u/username into the home directory for the specified user.
+ * We could use the getpw stuff here, but then we would have to load
+ * in stdio and who knows what else.
+ */
+
+#define MAXLOGNAME 32
+#define MAXPWLINE 128
+
+char *pfgets();
+
+
+STATIC char *
+expudir(path)
+ char *path;
+ {
+ register char *p, *q, *r;
+ char name[MAXLOGNAME];
+ char line[MAXPWLINE];
+ int i;
+
+ r = path; /* result on failure */
+ p = r + 3; /* the 3 skips "/u/" */
+ q = name;
+ while (*p && *p != '/') {
+ if (q >= name + MAXLOGNAME - 1)
+ return r; /* fail, name too long */
+ *q++ = *p++;
+ }
+ *q = '\0';
+ setinputfile("/etc/passwd", 1);
+ q = line + strlen(name);
+ while (pfgets(line, MAXPWLINE) != NULL) {
+ if (line[0] == name[0] && prefix(name, line) && *q == ':') {
+ /* skip to start of home directory */
+ i = 4;
+ do {
+ while (*++q && *q != ':');
+ } while (--i > 0);
+ if (*q == '\0')
+ break; /* fail, corrupted /etc/passwd */
+ q++;
+ for (r = q ; *r && *r != '\n' && *r != ':' ; r++);
+ *r = '\0'; /* nul terminate home directory */
+ i = r - q; /* i = strlen(q) */
+ r = stalloc(i + strlen(p) + 1);
+ scopy(q, r);
+ scopy(p, r + i);
+ TRACE(("expudir converts %s to %s\n", path, r));
+ didudir = 1;
+ path = r; /* succeed */
+ break;
+ }
+ }
+ popfile();
+ return r;
+}
+#endif
+
+
+/*
+ * Do metacharacter (i.e. *, ?, [...]) expansion.
+ */
+
+STATIC void
+expmeta(enddir, name)
+ char *enddir;
+ char *name;
+ {
+ register char *p;
+ char *q;
+ char *start;
+ char *endname;
+ int metaflag;
+ struct stat statb;
+ DIR *dirp;
+ struct dirent *dp;
+ int atend;
+ int matchdot;
+
+ metaflag = 0;
+ start = name;
+ for (p = name ; ; p++) {
+ if (*p == '*' || *p == '?')
+ metaflag = 1;
+ else if (*p == '[') {
+ q = p + 1;
+ if (*q == '!')
+ q++;
+ for (;;) {
+ if (*q == CTLESC)
+ q++;
+ if (*q == '/' || *q == '\0')
+ break;
+ if (*++q == ']') {
+ metaflag = 1;
+ break;
+ }
+ }
+ } else if (*p == '!' && p[1] == '!' && (p == name || p[-1] == '/')) {
+ metaflag = 1;
+ } else if (*p == '\0')
+ break;
+ else if (*p == CTLESC)
+ p++;
+ if (*p == '/') {
+ if (metaflag)
+ break;
+ start = p + 1;
+ }
+ }
+ if (metaflag == 0) { /* we've reached the end of the file name */
+ if (enddir != expdir)
+ metaflag++;
+ for (p = name ; ; p++) {
+ if (*p == CTLESC)
+ p++;
+ *enddir++ = *p;
+ if (*p == '\0')
+ break;
+ }
+ if (metaflag == 0 || stat(expdir, &statb) >= 0)
+ addfname(expdir);
+ return;
+ }
+ endname = p;
+ if (start != name) {
+ p = name;
+ while (p < start) {
+ if (*p == CTLESC)
+ p++;
+ *enddir++ = *p++;
+ }
+ }
+ if (enddir == expdir) {
+ p = ".";
+ } else if (enddir == expdir + 1 && *expdir == '/') {
+ p = "/";
+ } else {
+ p = expdir;
+ enddir[-1] = '\0';
+ }
+ if ((dirp = opendir(p)) == NULL)
+ return;
+ if (enddir != expdir)
+ enddir[-1] = '/';
+ if (*endname == 0) {
+ atend = 1;
+ } else {
+ atend = 0;
+ *endname++ = '\0';
+ }
+ matchdot = 0;
+ if (start[0] == '.' || start[0] == CTLESC && start[1] == '.')
+ matchdot++;
+ while (! int_pending() && (dp = readdir(dirp)) != NULL) {
+ if (dp->d_name[0] == '.' && ! matchdot)
+ continue;
+ if (patmatch(start, dp->d_name)) {
+ if (atend) {
+ scopy(dp->d_name, enddir);
+ addfname(expdir);
+ } else {
+ char *q;
+ for (p = enddir, q = dp->d_name ; *p++ = *q++ ;);
+ p[-1] = '/';
+ expmeta(p, endname);
+ }
+ }
+ }
+ closedir(dirp);
+ if (! atend)
+ endname[-1] = '/';
+}
+
+
+/*
+ * Add a file name to the list.