Index: stable/8/bin/sh/expand.c =================================================================== --- stable/8/bin/sh/expand.c (revision 215310) +++ stable/8/bin/sh/expand.c (revision 215311) @@ -1,1589 +1,1590 @@ /*- * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 1997-2005 * Herbert Xu . All rights reserved. * * This code is derived from software contributed to Berkeley by * Kenneth Almquist. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint #if 0 static char sccsid[] = "@(#)expand.c 8.5 (Berkeley) 5/15/95"; #endif #endif /* not lint */ #include __FBSDID("$FreeBSD$"); #include #include #include -#include #include -#include -#include -#include +#include +#include #include +#include #include +#include #include +#include /* * Routines to expand arguments to commands. We have to deal with * backquotes, shell variables, and file metacharacters. */ #include "shell.h" #include "main.h" #include "nodes.h" #include "eval.h" #include "expand.h" #include "syntax.h" #include "parser.h" #include "jobs.h" #include "options.h" #include "var.h" #include "input.h" #include "output.h" #include "memalloc.h" #include "error.h" #include "mystring.h" #include "arith.h" #include "show.h" /* * Structure specifying which parts of the string should be searched * for IFS characters. */ struct ifsregion { struct ifsregion *next; /* next region in list */ int begoff; /* offset of start of region */ int endoff; /* offset of end of region */ int inquotes; /* search for nul bytes only */ }; static char *expdest; /* output of current string */ static struct nodelist *argbackq; /* list of back quote expressions */ static struct ifsregion ifsfirst; /* first struct in list of ifs regions */ static struct ifsregion *ifslastp; /* last struct in list */ static struct arglist exparg; /* holds expanded arg list */ static void argstr(char *, int); static char *exptilde(char *, int); static void expbackq(union node *, int, int); static int subevalvar(char *, char *, int, int, int, int); static char *evalvar(char *, int); static int varisset(char *, int); static void varvalue(char *, int, int, int); static void recordregion(int, int, int); static void removerecordregions(int); static void ifsbreakup(char *, struct arglist *); static void expandmeta(struct strlist *, int); static void expmeta(char *, char *); static void addfname(char *); static struct strlist *expsort(struct strlist *); static struct strlist *msort(struct strlist *, int); static int pmatch(const char *, const char *, int); static char *cvtnum(int, char *); static int collate_range_cmp(int, int); static int collate_range_cmp(int c1, int c2) { static char s1[2], s2[2]; s1[0] = c1; s2[0] = c2; return (strcoll(s1, s2)); } /* * Expand shell variables and backquotes inside a here document. * union node *arg the document * int fd; where to write the expanded version */ void expandhere(union node *arg, int fd) { herefd = fd; expandarg(arg, (struct arglist *)NULL, 0); xwrite(fd, stackblock(), expdest - stackblock()); } /* * Perform expansions on an argument, placing the resulting list of arguments * in arglist. Parameter expansion, command substitution and arithmetic * expansion are always performed; additional expansions can be requested * via flag (EXP_*). * The result is left in the stack string. * When arglist is NULL, perform here document expansion. A partial result * may be written to herefd, which is then not included in the stack string. * * Caution: this function uses global state and is not reentrant. * However, a new invocation after an interrupted invocation is safe * and will reset the global state for the new call. */ void expandarg(union node *arg, struct arglist *arglist, int flag) { struct strlist *sp; char *p; argbackq = arg->narg.backquote; STARTSTACKSTR(expdest); ifsfirst.next = NULL; ifslastp = NULL; argstr(arg->narg.text, flag); if (arglist == NULL) { return; /* here document expanded */ } STPUTC('\0', expdest); p = grabstackstr(expdest); exparg.lastp = &exparg.list; /* * TODO - EXP_REDIR */ if (flag & EXP_FULL) { ifsbreakup(p, &exparg); *exparg.lastp = NULL; exparg.lastp = &exparg.list; expandmeta(exparg.list, flag); } else { if (flag & EXP_REDIR) /*XXX - for now, just remove escapes */ rmescapes(p); 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 parameter expansion, command substitution and arithmetic * expansion, and tilde expansion if requested via EXP_TILDE/EXP_VARTILDE. * Processing ends at a CTLENDVAR character as well as '\0'. * This is used to expand word in ${var+word} etc. * If EXP_FULL, EXP_CASE or EXP_REDIR are set, keep and/or generate CTLESC * characters to allow for further processing. * If EXP_FULL is set, also preserve CTLQUOTEMARK characters. */ static void argstr(char *p, int flag) { char c; int quotes = flag & (EXP_FULL | EXP_CASE | EXP_REDIR); /* do CTLESC */ int firsteq = 1; if (*p == '~' && (flag & (EXP_TILDE | EXP_VARTILDE))) p = exptilde(p, flag); for (;;) { switch (c = *p++) { case '\0': case CTLENDVAR: goto breakloop; case CTLQUOTEMARK: /* "$@" syntax adherence hack */ if (p[0] == CTLVAR && p[2] == '@' && p[3] == '=') break; if ((flag & EXP_FULL) != 0) STPUTC(c, expdest); break; case CTLESC: if (quotes) STPUTC(c, expdest); c = *p++; STPUTC(c, expdest); break; case CTLVAR: p = evalvar(p, flag); break; case CTLBACKQ: case CTLBACKQ|CTLQUOTE: expbackq(argbackq->n, c & CTLQUOTE, flag); argbackq = argbackq->next; break; case CTLENDARI: expari(flag); break; case ':': case '=': /* * sort of a hack - expand tildes in variable * assignments (after the first '=' and after ':'s). */ STPUTC(c, expdest); if (flag & EXP_VARTILDE && *p == '~') { if (c == '=') { if (firsteq) firsteq = 0; else break; } p = exptilde(p, flag); } break; default: STPUTC(c, expdest); } } breakloop:; } /* * Perform tilde expansion, placing the result in the stack string and * returning the next position in the input string to process. */ static char * exptilde(char *p, int flag) { char c, *startp = p; struct passwd *pw; char *home; int quotes = flag & (EXP_FULL | EXP_CASE | EXP_REDIR); while ((c = *p) != '\0') { switch(c) { case CTLESC: return (startp); case CTLQUOTEMARK: return (startp); case ':': if (flag & EXP_VARTILDE) goto done; break; case '/': goto done; } p++; } done: *p = '\0'; if (*(startp+1) == '\0') { if ((home = lookupvar("HOME")) == NULL) goto lose; } else { if ((pw = getpwnam(startp+1)) == NULL) goto lose; home = pw->pw_dir; } if (*home == '\0') goto lose; *p = c; while ((c = *home++) != '\0') { if (quotes && SQSYNTAX[(int)c] == CCTL) STPUTC(CTLESC, expdest); STPUTC(c, expdest); } return (p); lose: *p = c; return (startp); } static void removerecordregions(int endoff) { if (ifslastp == NULL) return; if (ifsfirst.endoff > endoff) { while (ifsfirst.next != NULL) { struct ifsregion *ifsp; INTOFF; ifsp = ifsfirst.next->next; ckfree(ifsfirst.next); ifsfirst.next = ifsp; INTON; } if (ifsfirst.begoff > endoff) ifslastp = NULL; else { ifslastp = &ifsfirst; ifsfirst.endoff = endoff; } return; } ifslastp = &ifsfirst; while (ifslastp->next && ifslastp->next->begoff < endoff) ifslastp=ifslastp->next; while (ifslastp->next != NULL) { struct ifsregion *ifsp; INTOFF; ifsp = ifslastp->next->next; ckfree(ifslastp->next); ifslastp->next = ifsp; INTON; } if (ifslastp->endoff > endoff) ifslastp->endoff = endoff; } /* * Expand arithmetic expression. Backup to start of expression, * evaluate, place result in (backed up) result, adjust string position. */ void expari(int flag) { char *p, *start; arith_t result; int begoff; int quotes = flag & (EXP_FULL | EXP_CASE | EXP_REDIR); int quoted; /* * This routine is slightly over-complicated for * efficiency. First we make sure there is * enough space for the result, which may be bigger * than the expression. Next we * scan backwards looking for the start of arithmetic. If the * next previous character is a CTLESC character, then we * have to rescan starting from the beginning since CTLESC * characters have to be processed left to right. */ CHECKSTRSPACE(DIGITS(result) - 2, expdest); USTPUTC('\0', expdest); start = stackblock(); p = expdest - 2; while (p >= start && *p != CTLARI) --p; if (p < start || *p != CTLARI) error("missing CTLARI (shouldn't happen)"); if (p > start && *(p - 1) == CTLESC) for (p = start; *p != CTLARI; p++) if (*p == CTLESC) p++; if (p[1] == '"') quoted=1; else quoted=0; begoff = p - start; removerecordregions(begoff); if (quotes) rmescapes(p+2); result = arith(p+2); fmtstr(p, DIGITS(result), ARITH_FORMAT_STR, result); while (*p++) ; if (quoted == 0) recordregion(begoff, p - 1 - start, 0); result = expdest - p + 1; STADJUST(-result, expdest); } /* * Perform command substitution. */ static void expbackq(union node *cmd, int quoted, int flag) { 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; int quotes = flag & (EXP_FULL | EXP_CASE | EXP_REDIR); int nnl; 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'; nnl = 0; /* Don't copy trailing newlines */ 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 (quotes && syntax[(int)lastc] == CCTL) STPUTC(CTLESC, dest); if (lastc == '\n') { nnl++; } else { while (nnl > 0) { nnl--; STPUTC('\n', dest); } STPUTC(lastc, dest); } } } if (in.fd >= 0) close(in.fd); if (in.buf) ckfree(in.buf); if (in.jp) exitstatus = waitforjob(in.jp, (int *)NULL); if (quoted == 0) recordregion(startloc, dest - stackblock(), 0); - TRACE(("evalbackq: size=%d: \"%.*s\"\n", - (dest - stackblock()) - startloc, - (dest - stackblock()) - startloc, + TRACE(("expbackq: size=%td: \"%.*s\"\n", + ((dest - stackblock()) - startloc), + (int)((dest - stackblock()) - startloc), stackblock() + startloc)); expdest = dest; INTON; } static int subevalvar(char *p, char *str, int strloc, int subtype, int startloc, int varflags) { char *startp; char *loc = NULL; char *q; int c = 0; int saveherefd = herefd; struct nodelist *saveargbackq = argbackq; int amount; herefd = -1; argstr(p, 0); STACKSTRNUL(expdest); herefd = saveherefd; argbackq = saveargbackq; startp = stackblock() + startloc; if (str == NULL) str = stackblock() + strloc; switch (subtype) { case VSASSIGN: setvar(str, startp, 0); amount = startp - expdest; STADJUST(amount, expdest); varflags &= ~VSNUL; if (c != 0) *loc = c; return 1; case VSQUESTION: if (*p != CTLENDVAR) { outfmt(&errout, "%s\n", startp); error((char *)NULL); } error("%.*s: parameter %snot set", (int)(p - str - 1), str, (varflags & VSNUL) ? "null or " : nullstr); return 0; case VSTRIMLEFT: for (loc = startp; loc < str; loc++) { c = *loc; *loc = '\0'; if (patmatch(str, startp, varflags & VSQUOTE)) { *loc = c; goto recordleft; } *loc = c; if ((varflags & VSQUOTE) && *loc == CTLESC) loc++; } return 0; case VSTRIMLEFTMAX: for (loc = str - 1; loc >= startp;) { c = *loc; *loc = '\0'; if (patmatch(str, startp, varflags & VSQUOTE)) { *loc = c; goto recordleft; } *loc = c; loc--; if ((varflags & VSQUOTE) && loc > startp && *(loc - 1) == CTLESC) { for (q = startp; q < loc; q++) if (*q == CTLESC) q++; if (q > loc) loc--; } } return 0; case VSTRIMRIGHT: for (loc = str - 1; loc >= startp;) { if (patmatch(str, loc, varflags & VSQUOTE)) { amount = loc - expdest; STADJUST(amount, expdest); return 1; } loc--; if ((varflags & VSQUOTE) && loc > startp && *(loc - 1) == CTLESC) { for (q = startp; q < loc; q++) if (*q == CTLESC) q++; if (q > loc) loc--; } } return 0; case VSTRIMRIGHTMAX: for (loc = startp; loc < str - 1; loc++) { if (patmatch(str, loc, varflags & VSQUOTE)) { amount = loc - expdest; STADJUST(amount, expdest); return 1; } if ((varflags & VSQUOTE) && *loc == CTLESC) loc++; } return 0; default: abort(); } recordleft: amount = ((str - 1) - (loc - startp)) - expdest; STADJUST(amount, expdest); while (loc != str - 1) *startp++ = *loc++; return 1; } /* * Expand a variable, and return a pointer to the next character in the * input string. */ static char * evalvar(char *p, int flag) { int subtype; int varflags; char *var; char *val; int patloc; int c; int set; int special; int startloc; int varlen; int easy; int quotes = flag & (EXP_FULL | EXP_CASE | EXP_REDIR); varflags = (unsigned char)*p++; subtype = varflags & 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 (varflags & VSLINENO) { set = 1; special = 0; val = var; p[-1] = '\0'; /* temporarily overwrite '=' to have \0 terminated string */ } else if (special) { set = varisset(var, varflags & VSNUL); val = NULL; } else { val = bltinlookup(var, 1); if (val == NULL || ((varflags & VSNUL) && val[0] == '\0')) { val = NULL; set = 0; } else set = 1; } varlen = 0; startloc = expdest - stackblock(); if (!set && uflag && *var != '@' && *var != '*') { switch (subtype) { case VSNORMAL: case VSTRIMLEFT: case VSTRIMLEFTMAX: case VSTRIMRIGHT: case VSTRIMRIGHTMAX: case VSLENGTH: error("%.*s: parameter not set", (int)(p - var - 1), var); } } if (set && subtype != VSPLUS) { /* insert the value of the variable */ if (special) { varvalue(var, varflags & VSQUOTE, subtype, flag); if (subtype == VSLENGTH) { varlen = expdest - stackblock() - startloc; STADJUST(-varlen, expdest); } } else { char const *syntax = (varflags & VSQUOTE) ? DQSYNTAX : BASESYNTAX; if (subtype == VSLENGTH) { for (;*val; val++) varlen++; } else { while (*val) { if (quotes && syntax[(int)*val] == CCTL) STPUTC(CTLESC, expdest); STPUTC(*val++, expdest); } } } } if (subtype == VSPLUS) set = ! set; easy = ((varflags & VSQUOTE) == 0 || (*var == '@' && shellparam.nparam != 1)); switch (subtype) { case VSLENGTH: expdest = cvtnum(varlen, expdest); goto record; case VSNORMAL: if (!easy) break; record: recordregion(startloc, expdest - stackblock(), varflags & VSQUOTE); break; case VSPLUS: case VSMINUS: if (!set) { argstr(p, flag); break; } if (easy) goto record; break; case VSTRIMLEFT: case VSTRIMLEFTMAX: case VSTRIMRIGHT: case VSTRIMRIGHTMAX: if (!set) break; /* * Terminate the string and start recording the pattern * right after it */ STPUTC('\0', expdest); patloc = expdest - stackblock(); if (subevalvar(p, NULL, patloc, subtype, startloc, varflags) == 0) { int amount = (expdest - stackblock() - patloc) + 1; STADJUST(-amount, expdest); } /* Remove any recorded regions beyond start of variable */ removerecordregions(startloc); goto record; case VSASSIGN: case VSQUESTION: if (!set) { if (subevalvar(p, var, 0, subtype, startloc, varflags)) { varflags &= ~VSNUL; /* * Remove any recorded regions beyond * start of variable */ removerecordregions(startloc); goto again; } break; } if (easy) goto record; break; case VSERROR: c = p - var - 1; error("${%.*s%s}: Bad substitution", c, var, (c > 0 && *p != CTLENDVAR) ? "..." : ""); default: abort(); } p[-1] = '='; /* recover overwritten '=' */ 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(char *name, int nulok) { if (*name == '!') return backgndpid != -1; else if (*name == '@' || *name == '*') { if (*shellparam.p == NULL) return 0; if (nulok) { char **av; for (av = shellparam.p; *av; av++) if (**av != '\0') return 1; return 0; } } else if (is_digit(*name)) { char *ap; int num = atoi(name); if (num > shellparam.nparam) return 0; if (num == 0) ap = arg0; else ap = shellparam.p[num - 1]; if (nulok && (ap == NULL || *ap == '\0')) return 0; } return 1; } /* * Add the value of a specialized variable to the stack string. */ static void varvalue(char *name, int quoted, int subtype, int flag) { int num; char *p; int i; char sep; char **ap; char const *syntax; #define STRTODEST(p) \ do {\ if (flag & (EXP_FULL | EXP_CASE) && subtype != VSLENGTH) { \ syntax = quoted? DQSYNTAX : BASESYNTAX; \ while (*p) { \ if (syntax[(int)*p] == CCTL) \ STPUTC(CTLESC, expdest); \ STPUTC(*p++, expdest); \ } \ } else \ while (*p) \ STPUTC(*p++, expdest); \ } while (0) switch (*name) { case '$': num = rootpid; goto numvar; case '?': num = oexitstatus; goto numvar; case '#': num = shellparam.nparam; goto numvar; case '!': num = backgndpid; numvar: expdest = cvtnum(num, expdest); break; case '-': for (i = 0 ; i < NOPTS ; i++) { if (optlist[i].val) STPUTC(optlist[i].letter, expdest); } break; case '@': if (flag & EXP_FULL && quoted) { for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { STRTODEST(p); if (*ap) STPUTC('\0', expdest); } break; } /* FALLTHROUGH */ case '*': if (ifsset()) sep = ifsval()[0]; else sep = ' '; for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { STRTODEST(p); if (*ap && sep) STPUTC(sep, expdest); } break; case '0': p = arg0; STRTODEST(p); break; default: if (is_digit(*name)) { num = atoi(name); if (num > 0 && num <= shellparam.nparam) { p = shellparam.p[num - 1]; STRTODEST(p); } } break; } } /* * Record the the fact that we have to scan this region of the * string for IFS characters. */ static void recordregion(int start, int end, int inquotes) { struct ifsregion *ifsp; if (ifslastp == NULL) { ifsp = &ifsfirst; } else { if (ifslastp->endoff == start && ifslastp->inquotes == inquotes) { /* extend previous area */ ifslastp->endoff = end; return; } ifsp = (struct ifsregion *)ckmalloc(sizeof (struct ifsregion)); ifslastp->next = ifsp; } ifslastp = ifsp; ifslastp->next = NULL; ifslastp->begoff = start; ifslastp->endoff = end; ifslastp->inquotes = inquotes; } /* * 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. * CTLESC characters are preserved but have little effect in this pass * other than escaping CTL* characters. In particular, they do not escape * IFS characters: that should be done with the ifsregion mechanism. * CTLQUOTEMARK characters are used to preserve empty quoted strings. * This pass treats them as a regular character, making the string non-empty. * Later, they are removed along with the other CTL* characters. */ static void ifsbreakup(char *string, struct arglist *arglist) { struct ifsregion *ifsp; struct strlist *sp; char *start; char *p; char *q; const char *ifs; const char *ifsspc; int had_param_ch = 0; start = string; if (ifslastp == NULL) { /* Return entire argument, IFS doesn't apply to any of it */ sp = (struct strlist *)stalloc(sizeof *sp); sp->text = start; *arglist->lastp = sp; arglist->lastp = &sp->next; return; } ifs = ifsset() ? ifsval() : " \t\n"; for (ifsp = &ifsfirst; ifsp != NULL; ifsp = ifsp->next) { p = string + ifsp->begoff; while (p < string + ifsp->endoff) { q = p; if (*p == CTLESC) p++; if (ifsp->inquotes) { /* Only NULs (should be from "$@") end args */ had_param_ch = 1; if (*p != 0) { p++; continue; } ifsspc = NULL; } else { if (!strchr(ifs, *p)) { had_param_ch = 1; p++; continue; } ifsspc = strchr(" \t\n", *p); /* Ignore IFS whitespace at start */ if (q == start && ifsspc != NULL) { p++; start = p; continue; } had_param_ch = 0; } /* Save this argument... */ *q = '\0'; sp = (struct strlist *)stalloc(sizeof *sp); sp->text = start; *arglist->lastp = sp; arglist->lastp = &sp->next; p++; if (ifsspc != NULL) { /* Ignore further trailing IFS whitespace */ for (; p < string + ifsp->endoff; p++) { q = p; if (*p == CTLESC) p++; if (strchr(ifs, *p) == NULL) { p = q; break; } if (strchr(" \t\n", *p) == NULL) { p++; break; } } } start = p; } } /* * Save anything left as an argument. * Traditionally we have treated 'IFS=':'; set -- x$IFS' as * generating 2 arguments, the second of which is empty. * Some recent clarification of the Posix spec say that it * should only generate one.... */ if (had_param_ch || *start != 0) { sp = (struct strlist *)stalloc(sizeof *sp); sp->text = start; *arglist->lastp = sp; arglist->lastp = &sp->next; } } static char expdir[PATH_MAX]; #define expdir_end (expdir + sizeof(expdir)) /* * Perform pathname generation and remove control characters. * At this point, the only control characters should be CTLESC and CTLQUOTEMARK. * The results are stored in the list exparg. */ static void expandmeta(struct strlist *str, int flag __unused) { char *p; struct strlist **savelastp; struct strlist *sp; char c; /* TODO - EXP_REDIR */ while (str) { if (fflag) goto nometa; p = str->text; for (;;) { /* fast check for meta chars */ if ((c = *p++) == '\0') goto nometa; if (c == '*' || c == '?' || c == '[' || c == '!') break; } savelastp = exparg.lastp; INTOFF; expmeta(expdir, str->text); INTON; if (exparg.lastp == savelastp) { /* * no matches */ 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; } } /* * Do metacharacter (i.e. *, ?, [...]) expansion. */ static void expmeta(char *enddir, char *name) { char *p; char *q; char *start; char *endname; int metaflag; struct stat statb; DIR *dirp; struct dirent *dp; int atend; int matchdot; int esc; metaflag = 0; start = name; for (p = name; esc = 0, *p; p += esc + 1) { if (*p == '*' || *p == '?') metaflag = 1; else if (*p == '[') { q = p + 1; if (*q == '!' || *q == '^') q++; for (;;) { while (*q == CTLQUOTEMARK) q++; 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 == CTLQUOTEMARK) continue; else { if (*p == CTLESC) esc++; if (p[esc] == '/') { if (metaflag) break; start = p + esc + 1; } } } if (metaflag == 0) { /* we've reached the end of the file name */ if (enddir != expdir) metaflag++; for (p = name ; ; p++) { if (*p == CTLQUOTEMARK) continue; if (*p == CTLESC) p++; *enddir++ = *p; if (*p == '\0') break; if (enddir == expdir_end) return; } if (metaflag == 0 || lstat(expdir, &statb) >= 0) addfname(expdir); return; } endname = p; if (start != name) { p = name; while (p < start) { while (*p == CTLQUOTEMARK) p++; if (*p == CTLESC) p++; *enddir++ = *p++; if (enddir == expdir_end) return; } } 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'; endname += esc + 1; } matchdot = 0; p = start; while (*p == CTLQUOTEMARK) p++; if (*p == CTLESC) p++; if (*p == '.') matchdot++; while (! int_pending() && (dp = readdir(dirp)) != NULL) { if (dp->d_name[0] == '.' && ! matchdot) continue; if (patmatch(start, dp->d_name, 0)) { if (enddir + dp->d_namlen + 1 > expdir_end) continue; memcpy(enddir, dp->d_name, dp->d_namlen + 1); if (atend) addfname(expdir); else { if (enddir + dp->d_namlen + 2 > expdir_end) continue; enddir[dp->d_namlen] = '/'; enddir[dp->d_namlen + 1] = '\0'; expmeta(enddir + dp->d_namlen + 1, endname); } } } closedir(dirp); if (! atend) endname[-esc - 1] = esc ? CTLESC : '/'; } /* * Add a file name to the list. */ static void addfname(char *name) { char *p; struct strlist *sp; p = stalloc(strlen(name) + 1); scopy(name, p); sp = (struct strlist *)stalloc(sizeof *sp); sp->text = p; *exparg.lastp = sp; exparg.lastp = &sp->next; } /* * Sort the results of file name expansion. It calculates the number of * strings to sort and then calls msort (short for merge sort) to do the * work. */ static struct strlist * expsort(struct strlist *str) { int len; struct strlist *sp; len = 0; for (sp = str ; sp ; sp = sp->next) len++; return msort(str, len); } static struct strlist * msort(struct strlist *list, int len) { struct strlist *p, *q = NULL; struct strlist **lpp; int half; int n; if (len <= 1) return list; half = len >> 1; p = list; for (n = half ; --n >= 0 ; ) { q = p; p = p->next; } q->next = NULL; /* terminate first half of list */ q = msort(list, half); /* sort first half of list */ p = msort(p, len - half); /* sort second half */ lpp = &list; for (;;) { if (strcmp(p->text, q->text) < 0) { *lpp = p; lpp = &p->next; if ((p = *lpp) == NULL) { *lpp = q; break; } } else { *lpp = q; lpp = &q->next; if ((q = *lpp) == NULL) { *lpp = p; break; } } } return list; } /* * Returns true if the pattern matches the string. */ int patmatch(const char *pattern, const char *string, int squoted) { #ifdef notdef if (pattern[0] == '!' && pattern[1] == '!') return 1 - pmatch(pattern + 2, string); else #endif return pmatch(pattern, string, squoted); } static int pmatch(const char *pattern, const char *string, int squoted) { const char *p, *q; char c; p = pattern; q = string; for (;;) { switch (c = *p++) { case '\0': goto breakloop; case CTLESC: if (squoted && *q == CTLESC) q++; if (*q++ != *p++) return 0; break; case CTLQUOTEMARK: continue; case '?': if (squoted && *q == CTLESC) q++; if (*q++ == '\0') return 0; break; case '*': c = *p; while (c == CTLQUOTEMARK || c == '*') c = *++p; if (c != CTLESC && c != CTLQUOTEMARK && c != '?' && c != '*' && c != '[') { while (*q != c) { if (squoted && *q == CTLESC && q[1] == c) break; if (*q == '\0') return 0; if (squoted && *q == CTLESC) q++; q++; } } do { if (pmatch(p, q, squoted)) return 1; if (squoted && *q == CTLESC) q++; } while (*q++ != '\0'); return 0; case '[': { const char *endp; int invert, found; char chr; endp = p; if (*endp == '!' || *endp == '^') endp++; for (;;) { while (*endp == CTLQUOTEMARK) endp++; if (*endp == '\0') goto dft; /* no matching ] */ if (*endp == CTLESC) endp++; if (*++endp == ']') break; } invert = 0; if (*p == '!' || *p == '^') { invert++; p++; } found = 0; chr = *q++; if (squoted && chr == CTLESC) chr = *q++; if (chr == '\0') return 0; c = *p++; do { if (c == CTLQUOTEMARK) continue; if (c == CTLESC) c = *p++; if (*p == '-' && p[1] != ']') { p++; while (*p == CTLQUOTEMARK) p++; if (*p == CTLESC) p++; if ( collate_range_cmp(chr, c) >= 0 && collate_range_cmp(chr, *p) <= 0 ) found = 1; p++; } else { if (chr == c) found = 1; } } while ((c = *p++) != ']'); if (found == invert) return 0; break; } dft: default: if (squoted && *q == CTLESC) q++; if (*q++ != c) return 0; break; } } breakloop: if (*q != '\0') return 0; return 1; } /* * Remove any CTLESC and CTLQUOTEMARK characters from a string. */ void rmescapes(char *str) { char *p, *q; p = str; while (*p != CTLESC && *p != CTLQUOTEMARK) { if (*p++ == '\0') return; } q = p; while (*p) { if (*p == CTLQUOTEMARK) { p++; continue; } if (*p == CTLESC) p++; *q++ = *p++; } *q = '\0'; } /* * See if a pattern matches in a case statement. */ int casematch(union node *pattern, const char *val) { struct stackmark smark; int result; char *p; setstackmark(&smark); argbackq = pattern->narg.backquote; STARTSTACKSTR(expdest); ifslastp = NULL; argstr(pattern->narg.text, EXP_TILDE | EXP_CASE); STPUTC('\0', expdest); p = grabstackstr(expdest); result = patmatch(p, val, 0); popstackmark(&smark); return result; } /* * Our own itoa(). */ static char * cvtnum(int num, char *buf) { char temp[32]; int neg = num < 0; char *p = temp + 31; temp[31] = '\0'; do { *--p = num % 10 + '0'; } while ((num /= 10) != 0); if (neg) *--p = '-'; while (*p) STPUTC(*p++, buf); return buf; } /* * Do most of the work for wordexp(3). */ int wordexpcmd(int argc, char **argv) { size_t len; int i; out1fmt("%08x", argc - 1); for (i = 1, len = 0; i < argc; i++) len += strlen(argv[i]); out1fmt("%08x", (int)len); for (i = 1; i < argc; i++) { out1str(argv[i]); out1c('\0'); } return (0); } Index: stable/8/bin/sh/jobs.c =================================================================== --- stable/8/bin/sh/jobs.c (revision 215310) +++ stable/8/bin/sh/jobs.c (revision 215311) @@ -1,1256 +1,1256 @@ /*- * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Kenneth Almquist. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint #if 0 static char sccsid[] = "@(#)jobs.c 8.5 (Berkeley) 5/4/95"; #endif #endif /* not lint */ #include __FBSDID("$FreeBSD$"); -#include -#include -#include -#include -#include -#include +#include #include -#include -#include #include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include #include "shell.h" #if JOBS #include #undef CEOF /* syntax.h redefines this */ #endif #include "redir.h" #include "show.h" #include "main.h" #include "parser.h" #include "nodes.h" #include "jobs.h" #include "options.h" #include "trap.h" #include "syntax.h" #include "input.h" #include "output.h" #include "memalloc.h" #include "error.h" #include "mystring.h" static struct job *jobtab; /* array of jobs */ static int njobs; /* size of array */ MKINIT pid_t backgndpid = -1; /* pid of last background process */ #if JOBS static struct job *jobmru; /* most recently used job list */ static pid_t initialpgrp; /* pgrp of shell on invocation */ #endif int in_waitcmd = 0; /* are we in waitcmd()? */ int in_dowait = 0; /* are we in dowait()? */ volatile sig_atomic_t breakwaitcmd = 0; /* should wait be terminated? */ static int ttyfd = -1; #if JOBS static void restartjob(struct job *); #endif static void freejob(struct job *); static struct job *getjob(char *); static pid_t dowait(int, struct job *); static pid_t waitproc(int, int *); static void cmdtxt(union node *); static void cmdputs(const char *); #if JOBS static void setcurjob(struct job *); static void deljob(struct job *); static struct job *getcurjob(struct job *); #endif static void showjob(struct job *, pid_t, int); /* * Turn job control on and off. */ MKINIT int jobctl; #if JOBS void setjobctl(int on) { int i; if (on == jobctl || rootshell == 0) return; if (on) { if (ttyfd != -1) close(ttyfd); if ((ttyfd = open(_PATH_TTY, O_RDWR)) < 0) { i = 0; while (i <= 2 && !isatty(i)) i++; if (i > 2 || (ttyfd = fcntl(i, F_DUPFD, 10)) < 0) goto out; } if (ttyfd < 10) { /* * Keep our TTY file descriptor out of the way of * the user's redirections. */ if ((i = fcntl(ttyfd, F_DUPFD, 10)) < 0) { close(ttyfd); ttyfd = -1; goto out; } close(ttyfd); ttyfd = i; } if (fcntl(ttyfd, F_SETFD, FD_CLOEXEC) < 0) { close(ttyfd); ttyfd = -1; goto out; } do { /* while we are in the background */ initialpgrp = tcgetpgrp(ttyfd); if (initialpgrp < 0) { out: out2str("sh: can't access tty; job control turned off\n"); mflag = 0; return; } if (initialpgrp == -1) initialpgrp = getpgrp(); else if (initialpgrp != getpgrp()) { killpg(0, SIGTTIN); continue; } } while (0); setsignal(SIGTSTP); setsignal(SIGTTOU); setsignal(SIGTTIN); setpgid(0, rootpid); tcsetpgrp(ttyfd, rootpid); } else { /* turning job control off */ setpgid(0, initialpgrp); tcsetpgrp(ttyfd, initialpgrp); close(ttyfd); ttyfd = -1; setsignal(SIGTSTP); setsignal(SIGTTOU); setsignal(SIGTTIN); } jobctl = on; } #endif #ifdef mkinit INCLUDE INCLUDE SHELLPROC { backgndpid = -1; #if JOBS jobctl = 0; #endif } #endif #if JOBS int fgcmd(int argc __unused, char **argv) { struct job *jp; pid_t pgrp; int status; jp = getjob(argv[1]); if (jp->jobctl == 0) error("job not created under job control"); out1str(jp->ps[0].cmd); out1c('\n'); flushout(&output); pgrp = jp->ps[0].pid; tcsetpgrp(ttyfd, pgrp); restartjob(jp); jp->foreground = 1; INTOFF; status = waitforjob(jp, (int *)NULL); INTON; return status; } int bgcmd(int argc, char **argv) { char s[64]; struct job *jp; do { jp = getjob(*++argv); if (jp->jobctl == 0) error("job not created under job control"); if (jp->state == JOBDONE) continue; restartjob(jp); jp->foreground = 0; fmtstr(s, 64, "[%td] ", jp - jobtab + 1); out1str(s); out1str(jp->ps[0].cmd); out1c('\n'); } while (--argc > 1); return 0; } static void restartjob(struct job *jp) { struct procstat *ps; int i; if (jp->state == JOBDONE) return; setcurjob(jp); INTOFF; killpg(jp->ps[0].pid, SIGCONT); for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) { if (WIFSTOPPED(ps->status)) { ps->status = -1; jp->state = 0; } } INTON; } #endif int jobscmd(int argc, char *argv[]) { char *id; int ch, mode; optind = optreset = 1; opterr = 0; mode = SHOWJOBS_DEFAULT; while ((ch = getopt(argc, argv, "lps")) != -1) { switch (ch) { case 'l': mode = SHOWJOBS_VERBOSE; break; case 'p': mode = SHOWJOBS_PGIDS; break; case 's': mode = SHOWJOBS_PIDS; break; case '?': default: error("unknown option: -%c", optopt); } } argc -= optind; argv += optind; if (argc == 0) showjobs(0, mode); else while ((id = *argv++) != NULL) showjob(getjob(id), 0, mode); return (0); } static void showjob(struct job *jp, pid_t pid, int mode) { char s[64]; struct procstat *ps; struct job *j; int col, curr, i, jobno, prev, procno; pid_t ppid; char c; procno = (mode == SHOWJOBS_PGIDS) ? 1 : jp->nprocs; jobno = jp - jobtab + 1; curr = prev = 0; #if JOBS if ((j = getcurjob(NULL)) != NULL) { curr = j - jobtab + 1; if ((j = getcurjob(j)) != NULL) prev = j - jobtab + 1; } #endif for (ps = jp->ps ; ; ps++) { /* for each process */ if (mode == SHOWJOBS_PIDS || mode == SHOWJOBS_PGIDS) { ppid = (mode == SHOWJOBS_PIDS) ? ps->pid : getpgid(ps->pid); out1fmt("%d\n", (int)ppid); goto skip; } if (mode != SHOWJOBS_VERBOSE && ps != jp->ps && pid == 0) goto skip; if (pid != 0 && pid != ps->pid) goto skip; if (jobno == curr && ps == jp->ps) c = '+'; else if (jobno == prev && ps == jp->ps) c = '-'; else c = ' '; if (ps == jp->ps) fmtstr(s, 64, "[%d] %c ", jobno, c); else fmtstr(s, 64, " %c ", c); out1str(s); col = strlen(s); if (mode == SHOWJOBS_VERBOSE) { fmtstr(s, 64, "%d ", (int)ps->pid); out1str(s); col += strlen(s); } s[0] = '\0'; if (ps != jp->ps) { *s = '\0'; } else if (ps->status == -1) { strcpy(s, "Running"); } else if (WIFEXITED(ps->status)) { if (WEXITSTATUS(ps->status) == 0) strcpy(s, "Done"); else fmtstr(s, 64, "Done (%d)", WEXITSTATUS(ps->status)); } else { #if JOBS if (WIFSTOPPED(ps->status)) i = WSTOPSIG(ps->status); else #endif i = WTERMSIG(ps->status); if ((i & 0x7F) < sys_nsig && sys_siglist[i & 0x7F]) scopy(sys_siglist[i & 0x7F], s); else fmtstr(s, 64, "Signal %d", i & 0x7F); if (WCOREDUMP(ps->status)) strcat(s, " (core dumped)"); } out1str(s); col += strlen(s); do { out1c(' '); col++; } while (col < 30); out1str(ps->cmd); out1c('\n'); skip: if (--procno <= 0) break; } } /* * Print a list of jobs. If "change" is nonzero, only print jobs whose * statuses have changed since the last call to showjobs. * * If the shell is interrupted in the process of creating a job, the * result may be a job structure containing zero processes. Such structures * will be freed here. */ void showjobs(int change, int mode) { int jobno; struct job *jp; TRACE(("showjobs(%d) called\n", change)); while (dowait(0, (struct job *)NULL) > 0); for (jobno = 1, jp = jobtab ; jobno <= njobs ; jobno++, jp++) { if (! jp->used) continue; if (jp->nprocs == 0) { freejob(jp); continue; } if (change && ! jp->changed) continue; showjob(jp, 0, mode); jp->changed = 0; if (jp->state == JOBDONE) { freejob(jp); } } } /* * Mark a job structure as unused. */ static void freejob(struct job *jp) { struct procstat *ps; int i; INTOFF; for (i = jp->nprocs, ps = jp->ps ; --i >= 0 ; ps++) { if (ps->cmd != nullstr) ckfree(ps->cmd); } if (jp->ps != &jp->ps0) ckfree(jp->ps); jp->used = 0; #if JOBS deljob(jp); #endif INTON; } int waitcmd(int argc, char **argv) { struct job *job; int status, retval; struct job *jp; if (argc > 1) { job = getjob(argv[1]); } else { job = NULL; } /* * Loop until a process is terminated or stopped, or a SIGINT is * received. */ in_waitcmd++; do { if (job != NULL) { if (job->state) { status = job->ps[job->nprocs - 1].status; if (WIFEXITED(status)) retval = WEXITSTATUS(status); #if JOBS else if (WIFSTOPPED(status)) retval = WSTOPSIG(status) + 128; #endif else retval = WTERMSIG(status) + 128; if (! iflag) freejob(job); in_waitcmd--; return retval; } } else { for (jp = jobtab ; ; jp++) { if (jp >= jobtab + njobs) { /* no running procs */ in_waitcmd--; return 0; } if (jp->used && jp->state == 0) break; } } } while (dowait(1, (struct job *)NULL) != -1); in_waitcmd--; return 0; } int jobidcmd(int argc __unused, char **argv) { struct job *jp; int i; jp = getjob(argv[1]); for (i = 0 ; i < jp->nprocs ; ) { out1fmt("%d", (int)jp->ps[i].pid); out1c(++i < jp->nprocs? ' ' : '\n'); } return 0; } /* * Convert a job name to a job structure. */ static struct job * getjob(char *name) { int jobno; struct job *found, *jp; pid_t pid; int i; if (name == NULL) { #if JOBS currentjob: if ((jp = getcurjob(NULL)) == NULL) error("No current job"); return (jp); #else error("No current job"); #endif } else if (name[0] == '%') { if (is_digit(name[1])) { jobno = number(name + 1); if (jobno > 0 && jobno <= njobs && jobtab[jobno - 1].used != 0) return &jobtab[jobno - 1]; #if JOBS } else if (name[1] == '%' && name[2] == '\0') { goto currentjob; } else if (name[1] == '+' && name[2] == '\0') { goto currentjob; } else if (name[1] == '-' && name[2] == '\0') { if ((jp = getcurjob(NULL)) == NULL || (jp = getcurjob(jp)) == NULL) error("No previous job"); return (jp); #endif } else if (name[1] == '?') { found = NULL; for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { if (jp->used && jp->nprocs > 0 && strstr(jp->ps[0].cmd, name + 2) != NULL) { if (found) error("%s: ambiguous", name); found = jp; } } if (found != NULL) return (found); } else { found = NULL; for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { if (jp->used && jp->nprocs > 0 && prefix(name + 1, jp->ps[0].cmd)) { if (found) error("%s: ambiguous", name); found = jp; } } if (found) return found; } } else if (is_number(name)) { pid = (pid_t)number(name); for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { if (jp->used && jp->nprocs > 0 && jp->ps[jp->nprocs - 1].pid == pid) return jp; } } error("No such job: %s", name); /*NOTREACHED*/ return NULL; } /* * Return a new job structure, */ struct job * makejob(union node *node __unused, int nprocs) { int i; struct job *jp; for (i = njobs, jp = jobtab ; ; jp++) { if (--i < 0) { INTOFF; if (njobs == 0) { jobtab = ckmalloc(4 * sizeof jobtab[0]); #if JOBS jobmru = NULL; #endif } else { jp = ckmalloc((njobs + 4) * sizeof jobtab[0]); memcpy(jp, jobtab, njobs * sizeof jp[0]); #if JOBS /* Relocate `next' pointers and list head */ if (jobmru != NULL) jobmru = &jp[jobmru - jobtab]; for (i = 0; i < njobs; i++) if (jp[i].next != NULL) jp[i].next = &jp[jp[i].next - jobtab]; #endif /* Relocate `ps' pointers */ for (i = 0; i < njobs; i++) if (jp[i].ps == &jobtab[i].ps0) jp[i].ps = &jp[i].ps0; ckfree(jobtab); jobtab = jp; } jp = jobtab + njobs; for (i = 4 ; --i >= 0 ; jobtab[njobs++].used = 0); INTON; break; } if (jp->used == 0) break; } INTOFF; jp->state = 0; jp->used = 1; jp->changed = 0; jp->nprocs = 0; jp->foreground = 0; #if JOBS jp->jobctl = jobctl; jp->next = NULL; #endif if (nprocs > 1) { jp->ps = ckmalloc(nprocs * sizeof (struct procstat)); } else { jp->ps = &jp->ps0; } INTON; - TRACE(("makejob(%p, %d) returns %%%d\n", (void *)node, nprocs, + TRACE(("makejob(%p, %d) returns %%%td\n", (void *)node, nprocs, jp - jobtab + 1)); return jp; } #if JOBS static void setcurjob(struct job *cj) { struct job *jp, *prev; for (prev = NULL, jp = jobmru; jp != NULL; prev = jp, jp = jp->next) { if (jp == cj) { if (prev != NULL) prev->next = jp->next; else jobmru = jp->next; jp->next = jobmru; jobmru = cj; return; } } cj->next = jobmru; jobmru = cj; } static void deljob(struct job *j) { struct job *jp, *prev; for (prev = NULL, jp = jobmru; jp != NULL; prev = jp, jp = jp->next) { if (jp == j) { if (prev != NULL) prev->next = jp->next; else jobmru = jp->next; return; } } } /* * Return the most recently used job that isn't `nj', and preferably one * that is stopped. */ static struct job * getcurjob(struct job *nj) { struct job *jp; /* Try to find a stopped one.. */ for (jp = jobmru; jp != NULL; jp = jp->next) if (jp->used && jp != nj && jp->state == JOBSTOPPED) return (jp); /* Otherwise the most recently used job that isn't `nj' */ for (jp = jobmru; jp != NULL; jp = jp->next) if (jp->used && jp != nj) return (jp); return (NULL); } #endif /* * Fork of a subshell. If we are doing job control, give the subshell its * own process group. Jp is a job structure that the job is to be added to. * N is the command that will be evaluated by the child. Both jp and n may * be NULL. The mode parameter can be one of the following: * FORK_FG - Fork off a foreground process. * FORK_BG - Fork off a background process. * FORK_NOJOB - Like FORK_FG, but don't give the process its own * process group even if job control is on. * * When job control is turned off, background processes have their standard * input redirected to /dev/null (except for the second and later processes * in a pipeline). */ pid_t forkshell(struct job *jp, union node *n, int mode) { pid_t pid; pid_t pgrp; - TRACE(("forkshell(%%%d, %p, %d) called\n", jp - jobtab, (void *)n, + TRACE(("forkshell(%%%td, %p, %d) called\n", jp - jobtab, (void *)n, mode)); INTOFF; flushall(); pid = fork(); if (pid == -1) { TRACE(("Fork failed, errno=%d\n", errno)); INTON; error("Cannot fork: %s", strerror(errno)); } if (pid == 0) { struct job *p; int wasroot; int i; TRACE(("Child shell %d\n", (int)getpid())); wasroot = rootshell; rootshell = 0; closescript(); INTON; clear_traps(); #if JOBS jobctl = 0; /* do job control only in root shell */ if (wasroot && mode != FORK_NOJOB && mflag) { if (jp == NULL || jp->nprocs == 0) pgrp = getpid(); else pgrp = jp->ps[0].pid; if (setpgid(0, pgrp) == 0 && mode == FORK_FG) { /*** this causes superfluous TIOCSPGRPS ***/ if (tcsetpgrp(ttyfd, pgrp) < 0) error("tcsetpgrp failed, errno=%d", errno); } setsignal(SIGTSTP); setsignal(SIGTTOU); } else if (mode == FORK_BG) { ignoresig(SIGINT); ignoresig(SIGQUIT); if ((jp == NULL || jp->nprocs == 0) && ! fd0_redirected_p ()) { close(0); if (open(_PATH_DEVNULL, O_RDONLY) != 0) error("Can't open %s: %s", _PATH_DEVNULL, strerror(errno)); } } #else if (mode == FORK_BG) { ignoresig(SIGINT); ignoresig(SIGQUIT); if ((jp == NULL || jp->nprocs == 0) && ! fd0_redirected_p ()) { close(0); if (open(_PATH_DEVNULL, O_RDONLY) != 0) error("Can't open %s: %s", _PATH_DEVNULL, strerror(errno)); } } #endif INTOFF; for (i = njobs, p = jobtab ; --i >= 0 ; p++) if (p->used) freejob(p); INTON; if (wasroot && iflag) { setsignal(SIGINT); setsignal(SIGQUIT); setsignal(SIGTERM); } return pid; } if (rootshell && mode != FORK_NOJOB && mflag) { if (jp == NULL || jp->nprocs == 0) pgrp = pid; else pgrp = jp->ps[0].pid; setpgid(pid, pgrp); } if (mode == FORK_BG) backgndpid = pid; /* set $! */ if (jp) { struct procstat *ps = &jp->ps[jp->nprocs++]; ps->pid = pid; ps->status = -1; ps->cmd = nullstr; if (iflag && rootshell && n) ps->cmd = commandtext(n); jp->foreground = mode == FORK_FG; #if JOBS setcurjob(jp); #endif } INTON; TRACE(("In parent shell: child = %d\n", (int)pid)); return pid; } /* * Wait for job to finish. * * Under job control we have the problem that while a child process is * running interrupts generated by the user are sent to the child but not * to the shell. This means that an infinite loop started by an inter- * active user may be hard to kill. With job control turned off, an * interactive user may place an interactive program inside a loop. If * the interactive program catches interrupts, the user doesn't want * these interrupts to also abort the loop. The approach we take here * is to have the shell ignore interrupt signals while waiting for a * foreground process to terminate, and then send itself an interrupt * signal if the child process was terminated by an interrupt signal. * Unfortunately, some programs want to do a bit of cleanup and then * exit on interrupt; unless these processes terminate themselves by * sending a signal to themselves (instead of calling exit) they will * confuse this approach. */ int waitforjob(struct job *jp, int *origstatus) { #if JOBS pid_t mypgrp = getpgrp(); int propagate_int = jp->jobctl && jp->foreground; #endif int status; int st; INTOFF; - TRACE(("waitforjob(%%%d) called\n", jp - jobtab + 1)); + TRACE(("waitforjob(%%%td) called\n", jp - jobtab + 1)); while (jp->state == 0) if (dowait(1, jp) == -1) dotrap(); #if JOBS if (jp->jobctl) { if (tcsetpgrp(ttyfd, mypgrp) < 0) error("tcsetpgrp failed, errno=%d\n", errno); } if (jp->state == JOBSTOPPED) setcurjob(jp); #endif status = jp->ps[jp->nprocs - 1].status; if (origstatus != NULL) *origstatus = status; /* convert to 8 bits */ if (WIFEXITED(status)) st = WEXITSTATUS(status); #if JOBS else if (WIFSTOPPED(status)) st = WSTOPSIG(status) + 128; #endif else st = WTERMSIG(status) + 128; if (! JOBS || jp->state == JOBDONE) freejob(jp); if (int_pending()) { if (WIFSIGNALED(status) && WTERMSIG(status) == SIGINT) kill(getpid(), SIGINT); else CLEAR_PENDING_INT; } #if JOBS else if (rootshell && iflag && propagate_int && WIFSIGNALED(status) && WTERMSIG(status) == SIGINT) kill(getpid(), SIGINT); #endif INTON; return st; } /* * Wait for a process to terminate. */ static pid_t dowait(int block, struct job *job) { pid_t pid; int status; struct procstat *sp; struct job *jp; struct job *thisjob; int done; int stopped; int sig; int i; in_dowait++; TRACE(("dowait(%d) called\n", block)); do { pid = waitproc(block, &status); TRACE(("wait returns %d, status=%d\n", (int)pid, status)); } while ((pid == -1 && errno == EINTR && breakwaitcmd == 0) || (pid > 0 && WIFSTOPPED(status) && !iflag)); in_dowait--; if (pid == -1 && errno == ECHILD && job != NULL) job->state = JOBDONE; if (breakwaitcmd != 0) { breakwaitcmd = 0; if (pid <= 0) return -1; } if (pid <= 0) return pid; INTOFF; thisjob = NULL; for (jp = jobtab ; jp < jobtab + njobs ; jp++) { if (jp->used) { done = 1; stopped = 1; for (sp = jp->ps ; sp < jp->ps + jp->nprocs ; sp++) { if (sp->pid == -1) continue; if (sp->pid == pid) { TRACE(("Changing status of proc %d from 0x%x to 0x%x\n", (int)pid, sp->status, status)); sp->status = status; thisjob = jp; } if (sp->status == -1) stopped = 0; else if (WIFSTOPPED(sp->status)) done = 0; } if (stopped) { /* stopped or done */ int state = done? JOBDONE : JOBSTOPPED; if (jp->state != state) { - TRACE(("Job %d: changing state from %d to %d\n", jp - jobtab + 1, jp->state, state)); + TRACE(("Job %td: changing state from %d to %d\n", jp - jobtab + 1, jp->state, state)); jp->state = state; #if JOBS if (done) deljob(jp); #endif } } } } INTON; if (! rootshell || ! iflag || (job && thisjob == job)) { #if JOBS if (WIFSTOPPED(status)) sig = WSTOPSIG(status); else #endif { if (WIFEXITED(status)) sig = 0; else sig = WTERMSIG(status); } if (sig != 0 && sig != SIGINT && sig != SIGPIPE) { if (!mflag || (thisjob->foreground && !WIFSTOPPED(status))) { i = WTERMSIG(status); if ((i & 0x7F) < sys_nsig && sys_siglist[i & 0x7F]) out1str(sys_siglist[i & 0x7F]); else out1fmt("Signal %d", i & 0x7F); if (WCOREDUMP(status)) out1str(" (core dumped)"); out1c('\n'); } else showjob(thisjob, pid, SHOWJOBS_DEFAULT); } } else { TRACE(("Not printing status, rootshell=%d, job=%p\n", rootshell, job)); if (thisjob) thisjob->changed = 1; } return pid; } /* * Do a wait system call. If job control is compiled in, we accept * stopped processes. If block is zero, we return a value of zero * rather than blocking. */ static pid_t waitproc(int block, int *status) { int flags; #if JOBS flags = WUNTRACED; #else flags = 0; #endif if (block == 0) flags |= WNOHANG; return wait3(status, flags, (struct rusage *)NULL); } /* * return 1 if there are stopped jobs, otherwise 0 */ int job_warning = 0; int stoppedjobs(void) { int jobno; struct job *jp; if (job_warning) return (0); for (jobno = 1, jp = jobtab; jobno <= njobs; jobno++, jp++) { if (jp->used == 0) continue; if (jp->state == JOBSTOPPED) { out2str("You have stopped jobs.\n"); job_warning = 2; return (1); } } return (0); } /* * Return a string identifying a command (to be printed by the * jobs command. */ static char *cmdnextc; static int cmdnleft; #define MAXCMDTEXT 200 char * commandtext(union node *n) { char *name; cmdnextc = name = ckmalloc(MAXCMDTEXT); cmdnleft = MAXCMDTEXT - 4; cmdtxt(n); *cmdnextc = '\0'; return name; } static void cmdtxt(union node *n) { union node *np; struct nodelist *lp; const char *p; int i; char s[2]; if (n == NULL) return; switch (n->type) { case NSEMI: cmdtxt(n->nbinary.ch1); cmdputs("; "); cmdtxt(n->nbinary.ch2); break; case NAND: cmdtxt(n->nbinary.ch1); cmdputs(" && "); cmdtxt(n->nbinary.ch2); break; case NOR: cmdtxt(n->nbinary.ch1); cmdputs(" || "); cmdtxt(n->nbinary.ch2); break; case NPIPE: for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { cmdtxt(lp->n); if (lp->next) cmdputs(" | "); } break; case NSUBSHELL: cmdputs("("); cmdtxt(n->nredir.n); cmdputs(")"); break; case NREDIR: case NBACKGND: cmdtxt(n->nredir.n); break; case NIF: cmdputs("if "); cmdtxt(n->nif.test); cmdputs("; then "); cmdtxt(n->nif.ifpart); cmdputs("..."); break; case NWHILE: cmdputs("while "); goto until; case NUNTIL: cmdputs("until "); until: cmdtxt(n->nbinary.ch1); cmdputs("; do "); cmdtxt(n->nbinary.ch2); cmdputs("; done"); break; case NFOR: cmdputs("for "); cmdputs(n->nfor.var); cmdputs(" in ..."); break; case NCASE: cmdputs("case "); cmdputs(n->ncase.expr->narg.text); cmdputs(" in ..."); break; case NDEFUN: cmdputs(n->narg.text); cmdputs("() ..."); break; case NCMD: for (np = n->ncmd.args ; np ; np = np->narg.next) { cmdtxt(np); if (np->narg.next) cmdputs(" "); } for (np = n->ncmd.redirect ; np ; np = np->nfile.next) { cmdputs(" "); cmdtxt(np); } break; case NARG: cmdputs(n->narg.text); break; case NTO: p = ">"; i = 1; goto redir; case NAPPEND: p = ">>"; i = 1; goto redir; case NTOFD: p = ">&"; i = 1; goto redir; case NCLOBBER: p = ">|"; i = 1; goto redir; case NFROM: p = "<"; i = 0; goto redir; case NFROMTO: p = "<>"; i = 0; goto redir; case NFROMFD: p = "<&"; i = 0; goto redir; redir: if (n->nfile.fd != i) { s[0] = n->nfile.fd + '0'; s[1] = '\0'; cmdputs(s); } cmdputs(p); if (n->type == NTOFD || n->type == NFROMFD) { if (n->ndup.dupfd >= 0) s[0] = n->ndup.dupfd + '0'; else s[0] = '-'; s[1] = '\0'; cmdputs(s); } else { cmdtxt(n->nfile.fname); } break; case NHERE: case NXHERE: cmdputs("<<..."); break; default: cmdputs("???"); break; } } static void cmdputs(const char *s) { const char *p; char *q; char c; int subtype = 0; if (cmdnleft <= 0) return; p = s; q = cmdnextc; while ((c = *p++) != '\0') { if (c == CTLESC) *q++ = *p++; else if (c == CTLVAR) { *q++ = '$'; if (--cmdnleft > 0) *q++ = '{'; subtype = *p++; } else if (c == '=' && subtype != 0) { *q++ = "}-+?="[(subtype & VSTYPE) - VSNORMAL]; subtype = 0; } else if (c == CTLENDVAR) { *q++ = '}'; } else if (c == CTLBACKQ || c == CTLBACKQ+CTLQUOTE) cmdnleft++; /* ignore it */ else *q++ = c; if (--cmdnleft <= 0) { *q++ = '.'; *q++ = '.'; *q++ = '.'; break; } } cmdnextc = q; } Index: stable/8/bin/sh =================================================================== --- stable/8/bin/sh (revision 215310) +++ stable/8/bin/sh (revision 215311) Property changes on: stable/8/bin/sh ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head/bin/sh:r213775