From 0dec6de38b721b4faae5567f8b4643df1055a175 Mon Sep 17 00:00:00 2001 From: Denis Vlasenko Date: Fri, 23 Feb 2007 21:10:47 +0000 Subject: [PATCH] ash: cleanup part 2.7 --- shell/ash.c | 1632 +++++++++++++++++++++++++-------------------------- 1 file changed, 806 insertions(+), 826 deletions(-) diff --git a/shell/ash.c b/shell/ash.c index 5c9060cad..1de4cb50b 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -1593,6 +1593,21 @@ struct shparam { static struct shparam shellparam; /* $@ current positional parameters */ +/* + * Free the list of positional parameters. + */ +static void +freeparam(volatile struct shparam *param) +{ + char **ap; + + if (param->malloc) { + for (ap = param->p; *ap; ap++) + free(*ap); + free(param->p); + } +} + #if ENABLE_ASH_GETOPTS static void getoptsreset(const char *value) @@ -2871,29 +2886,6 @@ static const char syntax_index_table[258] = { #endif /* USE_SIT_FUNCTION */ -/* options.h */ - -static void optschanged(void); -static void setparam(char **); -static void freeparam(volatile struct shparam *); -static int shiftcmd(int, char **); -static int setcmd(int, char **); -static int nextopt(const char *); - - -/* redir.h */ - -/* flags passed to redirect */ -#define REDIR_PUSH 01 /* save previous values of file descriptors */ -#define REDIR_SAVEFD2 03 /* set preverrout */ - -static void redirect(union node *, int); -static void popredir(int); -static void clearredir(int); -static int copyfd(int, int); -static int redirectsafe(union node *, int); - - /* ============ Alias handling */ #if ENABLE_ASH_ALIAS @@ -4622,393 +4614,744 @@ stoppedjobs(void) } -/* ============ Routines to expand arguments to commands +/* ============ redir.c * - * We have to deal with backquotes, shell variables, and file metacharacters. + * Code for dealing with input/output redirection. */ -/* - * expandarg flags - */ -#define EXP_FULL 0x1 /* perform word splitting & file globbing */ -#define EXP_TILDE 0x2 /* do normal tilde expansion */ -#define EXP_VARTILDE 0x4 /* expand tildes in an assignment */ -#define EXP_REDIR 0x8 /* file glob for a redirection (1 match only) */ -#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ -#define EXP_RECORD 0x20 /* need to record arguments for ifs breakup */ -#define EXP_VARTILDE2 0x40 /* expand tildes after colons only */ -#define EXP_WORD 0x80 /* expand word in parameter expansion */ -#define EXP_QWORD 0x100 /* expand word in quoted parameter expansion */ -/* - * _rmescape() flags - */ -#define RMESCAPE_ALLOC 0x1 /* Allocate a new string */ -#define RMESCAPE_GLOB 0x2 /* Add backslashes for glob */ -#define RMESCAPE_QUOTED 0x4 /* Remove CTLESC unless in quotes */ -#define RMESCAPE_GROW 0x8 /* Grow strings instead of stalloc */ -#define RMESCAPE_HEAP 0x10 /* Malloc strings instead of stalloc */ +#define EMPTY -2 /* marks an unused slot in redirtab */ +#ifndef PIPE_BUF +# define PIPESIZE 4096 /* amount of buffering in a pipe */ +#else +# define PIPESIZE PIPE_BUF +#endif /* - * Structure specifying which parts of the string should be searched - * for IFS characters. + * Open a file in noclobber mode. + * The code was copied from bash. */ -struct ifsregion { - struct ifsregion *next; /* next region in list */ - int begoff; /* offset of start of region */ - int endoff; /* offset of end of region */ - int nulonly; /* search for nul bytes only */ -}; +static int +noclobberopen(const char *fname) +{ + int r, fd; + struct stat finfo, finfo2; -struct arglist { - struct strlist *list; - struct strlist **lastp; -}; + /* + * If the file exists and is a regular file, return an error + * immediately. + */ + r = stat(fname, &finfo); + if (r == 0 && S_ISREG(finfo.st_mode)) { + errno = EEXIST; + return -1; + } -/* output of current string */ -static char *expdest; -/* list of back quote expressions */ -static struct nodelist *argbackq; -/* first struct in list of ifs regions */ -static struct ifsregion ifsfirst; -/* last struct in list */ -static struct ifsregion *ifslastp; -/* holds expanded arg list */ -static struct arglist exparg; + /* + * If the file was not present (r != 0), make sure we open it + * exclusively so that if it is created before we open it, our open + * will fail. Make sure that we do not truncate an existing file. + * Note that we don't turn on O_EXCL unless the stat failed -- if the + * file was not a regular file, we leave O_EXCL off. + */ + if (r != 0) + return open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666); + fd = open(fname, O_WRONLY|O_CREAT, 0666); + + /* If the open failed, return the file descriptor right away. */ + if (fd < 0) + return fd; + + /* + * OK, the open succeeded, but the file may have been changed from a + * non-regular file to a regular file between the stat and the open. + * We are assuming that the O_EXCL open handles the case where FILENAME + * did not exist and is symlinked to an existing file between the stat + * and open. + */ + + /* + * If we can open it and fstat the file descriptor, and neither check + * revealed that it was a regular file, and the file has not been + * replaced, return the file descriptor. + */ + if (fstat(fd, &finfo2) == 0 && !S_ISREG(finfo2.st_mode) + && finfo.st_dev == finfo2.st_dev && finfo.st_ino == finfo2.st_ino) + return fd; + + /* The file has been replaced. badness. */ + close(fd); + errno = EEXIST; + return -1; +} /* - * Our own itoa(). + * Handle here documents. Normally we fork off a process to write the + * data to a pipe. If the document is short, we can stuff the data in + * the pipe without forking. */ +/* openhere needs this forward reference */ +static void expandhere(union node *arg, int fd); static int -cvtnum(arith_t num) +openhere(union node *redir) { - int len; + int pip[2]; + size_t len = 0; - expdest = makestrspace(32, expdest); -#if ENABLE_ASH_MATH_SUPPORT_64 - len = fmtstr(expdest, 32, "%lld", (long long) num); -#else - len = fmtstr(expdest, 32, "%ld", num); + if (pipe(pip) < 0) + ash_msg_and_raise_error("Pipe call failed"); + if (redir->type == NHERE) { + len = strlen(redir->nhere.doc->narg.text); + if (len <= PIPESIZE) { + full_write(pip[1], redir->nhere.doc->narg.text, len); + goto out; + } + } + if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) { + close(pip[0]); + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + signal(SIGHUP, SIG_IGN); +#ifdef SIGTSTP + signal(SIGTSTP, SIG_IGN); #endif - STADJUST(len, expdest); - return len; + signal(SIGPIPE, SIG_DFL); + if (redir->type == NHERE) + full_write(pip[1], redir->nhere.doc->narg.text, len); + else + expandhere(redir->nhere.doc, pip[1]); + _exit(0); + } + out: + close(pip[1]); + return pip[0]; } -static size_t -esclen(const char *start, const char *p) +static int +openredirect(union node *redir) { - size_t esc = 0; + char *fname; + int f; - while (p > start && *--p == CTLESC) { - esc++; + switch (redir->nfile.type) { + case NFROM: + fname = redir->nfile.expfname; + f = open(fname, O_RDONLY); + if (f < 0) + goto eopen; + break; + case NFROMTO: + fname = redir->nfile.expfname; + f = open(fname, O_RDWR|O_CREAT|O_TRUNC, 0666); + if (f < 0) + goto ecreate; + break; + case NTO: + /* Take care of noclobber mode. */ + if (Cflag) { + fname = redir->nfile.expfname; + f = noclobberopen(fname); + if (f < 0) + goto ecreate; + break; + } + /* FALLTHROUGH */ + case NCLOBBER: + fname = redir->nfile.expfname; + f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666); + if (f < 0) + goto ecreate; + break; + case NAPPEND: + fname = redir->nfile.expfname; + f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666); + if (f < 0) + goto ecreate; + break; + default: +#if DEBUG + abort(); +#endif + /* Fall through to eliminate warning. */ + case NTOFD: + case NFROMFD: + f = -1; + break; + case NHERE: + case NXHERE: + f = openhere(redir); + break; } - return esc; + + return f; + ecreate: + ash_msg_and_raise_error("cannot create %s: %s", fname, errmsg(errno, "Directory nonexistent")); + eopen: + ash_msg_and_raise_error("cannot open %s: %s", fname, errmsg(errno, "No such file")); } /* - * Remove any CTLESC characters from a string. + * Copy a file descriptor to be >= to. Returns -1 + * if the source file descriptor is closed, EMPTY if there are no unused + * file descriptors left. */ -static char * -_rmescapes(char *str, int flag) +static int +copyfd(int from, int to) { - char *p, *q, *r; - static const char qchars[] = { CTLESC, CTLQUOTEMARK, 0 }; - unsigned inquotes; - int notescaped; - int globbing; + int newfd; - p = strpbrk(str, qchars); - if (!p) { - return str; - } - q = p; - r = str; - if (flag & RMESCAPE_ALLOC) { - size_t len = p - str; - size_t fulllen = len + strlen(p) + 1; - - if (flag & RMESCAPE_GROW) { - r = makestrspace(fulllen, expdest); - } else if (flag & RMESCAPE_HEAP) { - r = ckmalloc(fulllen); - } else { - r = stalloc(fulllen); - } - q = r; - if (len > 0) { - q = memcpy(q, str, len) + len; - } + newfd = fcntl(from, F_DUPFD, to); + if (newfd < 0) { + if (errno == EMFILE) + return EMPTY; + ash_msg_and_raise_error("%d: %m", from); } - inquotes = (flag & RMESCAPE_QUOTED) ^ RMESCAPE_QUOTED; - globbing = flag & RMESCAPE_GLOB; - notescaped = globbing; - while (*p) { - if (*p == CTLQUOTEMARK) { - inquotes = ~inquotes; - p++; - notescaped = globbing; - continue; - } - if (*p == '\\') { - /* naked back slash */ - notescaped = 0; - goto copy; - } - if (*p == CTLESC) { - p++; - if (notescaped && inquotes && *p != '/') { - *q++ = '\\'; - } + return newfd; +} + +static void +dupredirect(union node *redir, int f) +{ + int fd = redir->nfile.fd; + + if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) { + if (redir->ndup.dupfd >= 0) { /* if not ">&-" */ + copyfd(redir->ndup.dupfd, fd); } - notescaped = globbing; - copy: - *q++ = *p++; + return; } - *q = '\0'; - if (flag & RMESCAPE_GROW) { - expdest = r; - STADJUST(q - r + 1, expdest); + + if (f != fd) { + copyfd(f, fd); + close(f); } - return r; } -#define rmescapes(p) _rmescapes((p), 0) - -#define pmatch(a, b) !fnmatch((a), (b), 0) /* - * Prepare a pattern for a expmeta (internal glob(3)) call. - * - * Returns an stalloced string. + * Process a list of redirection commands. If the REDIR_PUSH flag is set, + * old file descriptors are stashed away so that the redirection can be + * undone by calling popredir. If the REDIR_BACKQ flag is set, then the + * standard output, and the standard error if it becomes a duplicate of + * stdout, is saved in memory. */ -static char * -preglob(const char *pattern, int quoted, int flag) +/* flags passed to redirect */ +#define REDIR_PUSH 01 /* save previous values of file descriptors */ +#define REDIR_SAVEFD2 03 /* set preverrout */ +static void +redirect(union node *redir, int flags) { - flag |= RMESCAPE_GLOB; - if (quoted) { - flag |= RMESCAPE_QUOTED; + union node *n; + struct redirtab *sv; + int i; + int fd; + int newfd; + int *p; + nullredirs++; + if (!redir) { + return; } - return _rmescapes((char *)pattern, flag); + sv = NULL; + INT_OFF; + if (flags & REDIR_PUSH) { + struct redirtab *q; + q = ckmalloc(sizeof(struct redirtab)); + q->next = redirlist; + redirlist = q; + q->nullredirs = nullredirs - 1; + for (i = 0; i < 10; i++) + q->renamed[i] = EMPTY; + nullredirs = 0; + sv = q; + } + n = redir; + do { + fd = n->nfile.fd; + if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD) + && n->ndup.dupfd == fd) + continue; /* redirect from/to same file descriptor */ + + newfd = openredirect(n); + if (fd == newfd) + continue; + if (sv && *(p = &sv->renamed[fd]) == EMPTY) { + i = fcntl(fd, F_DUPFD, 10); + + if (i == -1) { + i = errno; + if (i != EBADF) { + close(newfd); + errno = i; + ash_msg_and_raise_error("%d: %m", fd); + /* NOTREACHED */ + } + } else { + *p = i; + close(fd); + } + } else { + close(fd); + } + dupredirect(n, newfd); + } while ((n = n->nfile.next)); + INT_ON; + if (flags & REDIR_SAVEFD2 && sv && sv->renamed[2] >= 0) + preverrout_fd = sv->renamed[2]; } /* - * Put a string on the stack. + * Undo the effects of the last redirection. */ static void -memtodest(const char *p, size_t len, int syntax, int quotes) +popredir(int drop) { - char *q = expdest; - - q = makestrspace(len * 2, q); + struct redirtab *rp; + int i; - while (len--) { - int c = signed_char2int(*p++); - if (!c) - continue; - if (quotes && (SIT(c, syntax) == CCTL || SIT(c, syntax) == CBACK)) - USTPUTC(CTLESC, q); - USTPUTC(c, q); + if (--nullredirs >= 0) + return; + INT_OFF; + rp = redirlist; + for (i = 0; i < 10; i++) { + if (rp->renamed[i] != EMPTY) { + if (!drop) { + close(i); + copyfd(rp->renamed[i], i); + } + close(rp->renamed[i]); + } } - - expdest = q; + redirlist = rp->next; + nullredirs = rp->nullredirs; + free(rp); + INT_ON; } -static void -strtodest(const char *p, int syntax, int quotes) -{ - memtodest(p, strlen(p), syntax, quotes); -} +/* + * Undo all redirections. Called on error or interrupt. + */ /* - * Record the fact that we have to scan this region of the - * string for IFS characters. + * Discard all saved file descriptors. */ static void -recordregion(int start, int end, int nulonly) +clearredir(int drop) { - struct ifsregion *ifsp; - - if (ifslastp == NULL) { - ifsp = &ifsfirst; - } else { - INT_OFF; - ifsp = ckmalloc(sizeof(*ifsp)); - ifsp->next = NULL; - ifslastp->next = ifsp; - INT_ON; + for (;;) { + nullredirs = 0; + if (!redirlist) + break; + popredir(drop); } - ifslastp = ifsp; - ifslastp->begoff = start; - ifslastp->endoff = end; - ifslastp->nulonly = nulonly; } -static void -removerecordregions(int endoff) +static int +redirectsafe(union node *redir, int flags) { - if (ifslastp == NULL) - return; - - if (ifsfirst.endoff > endoff) { - while (ifsfirst.next != NULL) { - struct ifsregion *ifsp; - INT_OFF; - ifsp = ifsfirst.next->next; - free(ifsfirst.next); - ifsfirst.next = ifsp; - INT_ON; - } - if (ifsfirst.begoff > endoff) - ifslastp = NULL; - else { - ifslastp = &ifsfirst; - ifsfirst.endoff = endoff; - } - return; - } + int err; + volatile int saveint; + struct jmploc *volatile savehandler = exception_handler; + struct jmploc jmploc; - ifslastp = &ifsfirst; - while (ifslastp->next && ifslastp->next->begoff < endoff) - ifslastp=ifslastp->next; - while (ifslastp->next != NULL) { - struct ifsregion *ifsp; - INT_OFF; - ifsp = ifslastp->next->next; - free(ifslastp->next); - ifslastp->next = ifsp; - INT_ON; + SAVE_INT(saveint); + err = setjmp(jmploc.loc) * 2; + if (!err) { + exception_handler = &jmploc; + redirect(redir, flags); } - if (ifslastp->endoff > endoff) - ifslastp->endoff = endoff; + exception_handler = savehandler; + if (err && exception != EXERROR) + longjmp(exception_handler->loc, 1); + RESTORE_INT(saveint); + return err; } -static char * -exptilde(char *startp, char *p, int flag) -{ - char c; - char *name; - struct passwd *pw; - const char *home; - int quotes = flag & (EXP_FULL | EXP_CASE); - int startloc; - name = p + 1; +/* ============ Routines to expand arguments to commands + * + * We have to deal with backquotes, shell variables, and file metacharacters. + */ - while ((c = *++p) != '\0') { - switch (c) { - case CTLESC: - return startp; - case CTLQUOTEMARK: - return startp; - case ':': - if (flag & EXP_VARTILDE) - goto done; - break; - case '/': - case CTLENDVAR: - goto done; - } - } - done: - *p = '\0'; - if (*name == '\0') { - home = lookupvar(homestr); - } else { - pw = getpwnam(name); - if (pw == NULL) - goto lose; - home = pw->pw_dir; - } - if (!home || !*home) - goto lose; - *p = c; - startloc = expdest - (char *)stackblock(); - strtodest(home, SQSYNTAX, quotes); - recordregion(startloc, expdest - (char *)stackblock(), 0); - return p; - lose: - *p = c; - return startp; -} +/* + * expandarg flags + */ +#define EXP_FULL 0x1 /* perform word splitting & file globbing */ +#define EXP_TILDE 0x2 /* do normal tilde expansion */ +#define EXP_VARTILDE 0x4 /* expand tildes in an assignment */ +#define EXP_REDIR 0x8 /* file glob for a redirection (1 match only) */ +#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ +#define EXP_RECORD 0x20 /* need to record arguments for ifs breakup */ +#define EXP_VARTILDE2 0x40 /* expand tildes after colons only */ +#define EXP_WORD 0x80 /* expand word in parameter expansion */ +#define EXP_QWORD 0x100 /* expand word in quoted parameter expansion */ +/* + * _rmescape() flags + */ +#define RMESCAPE_ALLOC 0x1 /* Allocate a new string */ +#define RMESCAPE_GLOB 0x2 /* Add backslashes for glob */ +#define RMESCAPE_QUOTED 0x4 /* Remove CTLESC unless in quotes */ +#define RMESCAPE_GROW 0x8 /* Grow strings instead of stalloc */ +#define RMESCAPE_HEAP 0x10 /* Malloc strings instead of stalloc */ /* - * Execute a command inside back quotes. If it's a builtin command, we - * want to save its output in a block obtained from malloc. Otherwise - * we fork off a subprocess and get the output of the command via a pipe. - * Should be called with interrupts off. + * Structure specifying which parts of the string should be searched + * for IFS characters. */ -struct backcmd { /* result of evalbackcmd */ - int fd; /* file descriptor to read from */ - char *buf; /* buffer */ - int nleft; /* number of chars in buffer */ - struct job *jp; /* job structure for command */ +struct ifsregion { + struct ifsregion *next; /* next region in list */ + int begoff; /* offset of start of region */ + int endoff; /* offset of end of region */ + int nulonly; /* search for nul bytes only */ }; -/* These forward decls are needed to use "eval" code for backticks handling: */ -static int back_exitstatus; /* exit status of backquoted command */ -#define EV_EXIT 01 /* exit after evaluating tree */ -static void evaltree(union node *, int); +struct arglist { + struct strlist *list; + struct strlist **lastp; +}; -static void -evalbackcmd(union node *n, struct backcmd *result) +/* output of current string */ +static char *expdest; +/* list of back quote expressions */ +static struct nodelist *argbackq; +/* first struct in list of ifs regions */ +static struct ifsregion ifsfirst; +/* last struct in list */ +static struct ifsregion *ifslastp; +/* holds expanded arg list */ +static struct arglist exparg; + +/* + * Our own itoa(). + */ +static int +cvtnum(arith_t num) { - int saveherefd; + int len; - result->fd = -1; - result->buf = NULL; - result->nleft = 0; - result->jp = NULL; - if (n == NULL) { - goto out; + expdest = makestrspace(32, expdest); +#if ENABLE_ASH_MATH_SUPPORT_64 + len = fmtstr(expdest, 32, "%lld", (long long) num); +#else + len = fmtstr(expdest, 32, "%ld", num); +#endif + STADJUST(len, expdest); + return len; +} + +static size_t +esclen(const char *start, const char *p) +{ + size_t esc = 0; + + while (p > start && *--p == CTLESC) { + esc++; } + return esc; +} - saveherefd = herefd; - herefd = -1; +/* + * Remove any CTLESC characters from a string. + */ +static char * +_rmescapes(char *str, int flag) +{ + char *p, *q, *r; + static const char qchars[] = { CTLESC, CTLQUOTEMARK, 0 }; + unsigned inquotes; + int notescaped; + int globbing; - { - int pip[2]; - struct job *jp; + p = strpbrk(str, qchars); + if (!p) { + return str; + } + q = p; + r = str; + if (flag & RMESCAPE_ALLOC) { + size_t len = p - str; + size_t fulllen = len + strlen(p) + 1; - if (pipe(pip) < 0) - ash_msg_and_raise_error("Pipe call failed"); - jp = makejob(n, 1); - if (forkshell(jp, n, FORK_NOJOB) == 0) { - FORCE_INT_ON; - close(pip[0]); - if (pip[1] != 1) { - close(1); - copyfd(pip[1], 1); - close(pip[1]); + if (flag & RMESCAPE_GROW) { + r = makestrspace(fulllen, expdest); + } else if (flag & RMESCAPE_HEAP) { + r = ckmalloc(fulllen); + } else { + r = stalloc(fulllen); + } + q = r; + if (len > 0) { + q = memcpy(q, str, len) + len; + } + } + inquotes = (flag & RMESCAPE_QUOTED) ^ RMESCAPE_QUOTED; + globbing = flag & RMESCAPE_GLOB; + notescaped = globbing; + while (*p) { + if (*p == CTLQUOTEMARK) { + inquotes = ~inquotes; + p++; + notescaped = globbing; + continue; + } + if (*p == '\\') { + /* naked back slash */ + notescaped = 0; + goto copy; + } + if (*p == CTLESC) { + p++; + if (notescaped && inquotes && *p != '/') { + *q++ = '\\'; } - eflag = 0; - evaltree(n, EV_EXIT); /* actually evaltreenr... */ - /* NOTREACHED */ } - close(pip[1]); - result->fd = pip[0]; - result->jp = jp; + notescaped = globbing; + copy: + *q++ = *p++; } - herefd = saveherefd; - out: - TRACE(("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n", - result->fd, result->buf, result->nleft, result->jp)); + *q = '\0'; + if (flag & RMESCAPE_GROW) { + expdest = r; + STADJUST(q - r + 1, expdest); + } + return r; +} +#define rmescapes(p) _rmescapes((p), 0) + +#define pmatch(a, b) !fnmatch((a), (b), 0) + +/* + * Prepare a pattern for a expmeta (internal glob(3)) call. + * + * Returns an stalloced string. + */ +static char * +preglob(const char *pattern, int quoted, int flag) +{ + flag |= RMESCAPE_GLOB; + if (quoted) { + flag |= RMESCAPE_QUOTED; + } + return _rmescapes((char *)pattern, flag); } /* - * Expand stuff in backwards quotes. + * Put a string on the stack. */ static void -expbackq(union node *cmd, int quoted, int quotes) +memtodest(const char *p, size_t len, int syntax, int quotes) { - struct backcmd in; - int i; - char buf[128]; - char *p; - char *dest; - int startloc; - int syntax = quoted? DQSYNTAX : BASESYNTAX; - struct stackmark smark; + char *q = expdest; - INT_OFF; - setstackmark(&smark); + q = makestrspace(len * 2, q); + + while (len--) { + int c = signed_char2int(*p++); + if (!c) + continue; + if (quotes && (SIT(c, syntax) == CCTL || SIT(c, syntax) == CBACK)) + USTPUTC(CTLESC, q); + USTPUTC(c, q); + } + + expdest = q; +} + +static void +strtodest(const char *p, int syntax, int quotes) +{ + memtodest(p, strlen(p), syntax, quotes); +} + +/* + * Record the fact that we have to scan this region of the + * string for IFS characters. + */ +static void +recordregion(int start, int end, int nulonly) +{ + struct ifsregion *ifsp; + + if (ifslastp == NULL) { + ifsp = &ifsfirst; + } else { + INT_OFF; + ifsp = ckmalloc(sizeof(*ifsp)); + ifsp->next = NULL; + ifslastp->next = ifsp; + INT_ON; + } + ifslastp = ifsp; + ifslastp->begoff = start; + ifslastp->endoff = end; + ifslastp->nulonly = nulonly; +} + +static void +removerecordregions(int endoff) +{ + if (ifslastp == NULL) + return; + + if (ifsfirst.endoff > endoff) { + while (ifsfirst.next != NULL) { + struct ifsregion *ifsp; + INT_OFF; + ifsp = ifsfirst.next->next; + free(ifsfirst.next); + ifsfirst.next = ifsp; + INT_ON; + } + 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; + INT_OFF; + ifsp = ifslastp->next->next; + free(ifslastp->next); + ifslastp->next = ifsp; + INT_ON; + } + if (ifslastp->endoff > endoff) + ifslastp->endoff = endoff; +} + +static char * +exptilde(char *startp, char *p, int flag) +{ + char c; + char *name; + struct passwd *pw; + const char *home; + int quotes = flag & (EXP_FULL | EXP_CASE); + int startloc; + + name = p + 1; + + while ((c = *++p) != '\0') { + switch (c) { + case CTLESC: + return startp; + case CTLQUOTEMARK: + return startp; + case ':': + if (flag & EXP_VARTILDE) + goto done; + break; + case '/': + case CTLENDVAR: + goto done; + } + } + done: + *p = '\0'; + if (*name == '\0') { + home = lookupvar(homestr); + } else { + pw = getpwnam(name); + if (pw == NULL) + goto lose; + home = pw->pw_dir; + } + if (!home || !*home) + goto lose; + *p = c; + startloc = expdest - (char *)stackblock(); + strtodest(home, SQSYNTAX, quotes); + recordregion(startloc, expdest - (char *)stackblock(), 0); + return p; + lose: + *p = c; + return startp; +} + +/* + * Execute a command inside back quotes. If it's a builtin command, we + * want to save its output in a block obtained from malloc. Otherwise + * we fork off a subprocess and get the output of the command via a pipe. + * Should be called with interrupts off. + */ +struct backcmd { /* result of evalbackcmd */ + int fd; /* file descriptor to read from */ + char *buf; /* buffer */ + int nleft; /* number of chars in buffer */ + struct job *jp; /* job structure for command */ +}; + +/* These forward decls are needed to use "eval" code for backticks handling: */ +static int back_exitstatus; /* exit status of backquoted command */ +#define EV_EXIT 01 /* exit after evaluating tree */ +static void evaltree(union node *, int); + +static void +evalbackcmd(union node *n, struct backcmd *result) +{ + int saveherefd; + + result->fd = -1; + result->buf = NULL; + result->nleft = 0; + result->jp = NULL; + if (n == NULL) { + goto out; + } + + saveherefd = herefd; + herefd = -1; + + { + int pip[2]; + struct job *jp; + + if (pipe(pip) < 0) + ash_msg_and_raise_error("Pipe call failed"); + jp = makejob(n, 1); + if (forkshell(jp, n, FORK_NOJOB) == 0) { + FORCE_INT_ON; + close(pip[0]); + if (pip[1] != 1) { + close(1); + copyfd(pip[1], 1); + close(pip[1]); + } + eflag = 0; + evaltree(n, EV_EXIT); /* actually evaltreenr... */ + /* NOTREACHED */ + } + close(pip[1]); + result->fd = pip[0]; + result->jp = jp; + } + herefd = saveherefd; + out: + TRACE(("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n", + result->fd, result->buf, result->nleft, result->jp)); +} + +/* + * Expand stuff in backwards quotes. + */ +static void +expbackq(union node *cmd, int quoted, int quotes) +{ + struct backcmd in; + int i; + char buf[128]; + char *p; + char *dest; + int startloc; + int syntax = quoted? DQSYNTAX : BASESYNTAX; + struct stackmark smark; + + INT_OFF; + setstackmark(&smark); dest = expdest; startloc = dest - (char *)stackblock(); grabstackstr(dest); @@ -7475,43 +7818,95 @@ evalpipe(union node *n, int flags) INT_ON; } -static struct localvar *localvars; - /* - * Called after a function returns. - * Interrupts must be off. + * Controls whether the shell is interactive or not. */ static void -poplocalvars(void) +setinteractive(int on) { - struct localvar *lvp; - struct var *vp; + static int is_interactive; - while ((lvp = localvars) != NULL) { - localvars = lvp->next; - vp = lvp->vp; - TRACE(("poplocalvar %s", vp ? vp->text : "-")); - if (vp == NULL) { /* $- saved */ - memcpy(optlist, lvp->text, sizeof(optlist)); - free((char*)lvp->text); - optschanged(); - } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) { - unsetvar(vp->text); - } else { - if (vp->func) - (*vp->func)(strchrnul(lvp->text, '=') + 1); - if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) - free((char*)vp->text); - vp->flags = lvp->flags; - vp->text = lvp->text; - } - free(lvp); - } -} + if (++on == is_interactive) + return; + is_interactive = on; + setsignal(SIGINT); + setsignal(SIGQUIT); + setsignal(SIGTERM); +#if !ENABLE_FEATURE_SH_EXTRA_QUIET + if (is_interactive > 1) { + /* Looks like they want an interactive shell */ + static smallint do_banner; -static int -evalfun(struct funcnode *func, int argc, char **argv, int flags) -{ + if (!do_banner) { + out1fmt( + "\n\n" + "%s Built-in shell (ash)\n" + "Enter 'help' for a list of built-in commands." + "\n\n", + BB_BANNER); + do_banner = 1; + } + } +#endif +} + +#if ENABLE_FEATURE_EDITING_VI +#define setvimode(on) do { \ + if (on) line_input_state->flags |= VI_MODE; \ + else line_input_state->flags &= ~VI_MODE; \ +} while (0) +#else +#define setvimode(on) viflag = 0 /* forcibly keep the option off */ +#endif + +static void +optschanged(void) +{ +#if DEBUG + opentrace(); +#endif + setinteractive(iflag); + setjobctl(mflag); + setvimode(viflag); +} + +static struct localvar *localvars; + +/* + * Called after a function returns. + * Interrupts must be off. + */ +static void +poplocalvars(void) +{ + struct localvar *lvp; + struct var *vp; + + while ((lvp = localvars) != NULL) { + localvars = lvp->next; + vp = lvp->vp; + TRACE(("poplocalvar %s", vp ? vp->text : "-")); + if (vp == NULL) { /* $- saved */ + memcpy(optlist, lvp->text, sizeof(optlist)); + free((char*)lvp->text); + optschanged(); + } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) { + unsetvar(vp->text); + } else { + if (vp->func) + (*vp->func)(strchrnul(lvp->text, '=') + 1); + if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) + free((char*)vp->text); + vp->flags = lvp->flags; + vp->text = lvp->text; + } + free(lvp); + } +} + +static int +evalfun(struct funcnode *func, int argc, char **argv, int flags) +{ volatile struct shparam saveparam; struct localvar *volatile savelocalvars; struct jmploc *volatile savehandler; @@ -7894,7 +8289,7 @@ evalcommand(union node *cmd, int flags) preverrout_fd = 2; expredir(cmd->ncmd.redirect); - status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH|REDIR_SAVEFD2); + status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH | REDIR_SAVEFD2); path = vpath.text; for (argp = cmd->ncmd.assign; argp; argp = argp->narg.next) { @@ -8678,88 +9073,35 @@ changemail(const char *val) /* ============ ??? */ /* - * Take commands from a file. To be compatible we should do a path - * search for the file, which is necessary to find sub-commands. - */ -static char * -find_dot_file(char *name) -{ - char *fullname; - const char *path = pathval(); - struct stat statb; - - /* don't try this for absolute or relative paths */ - if (strchr(name, '/')) - return name; - - while ((fullname = padvance(&path, name)) != NULL) { - if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) { - /* - * Don't bother freeing here, since it will - * be freed by the caller. - */ - return fullname; - } - stunalloc(fullname); - } - - /* not found in the PATH */ - ash_msg_and_raise_error("%s: not found", name); - /* NOTREACHED */ -} - -/* - * Controls whether the shell is interactive or not. + * Set the shell parameters. */ static void -setinteractive(int on) +setparam(char **argv) { - static int is_interactive; - - if (++on == is_interactive) - return; - is_interactive = on; - setsignal(SIGINT); - setsignal(SIGQUIT); - setsignal(SIGTERM); -#if !ENABLE_FEATURE_SH_EXTRA_QUIET - if (is_interactive > 1) { - /* Looks like they want an interactive shell */ - static smallint do_banner; + char **newparam; + char **ap; + int nparam; - if (!do_banner) { - out1fmt( - "\n\n" - "%s Built-in shell (ash)\n" - "Enter 'help' for a list of built-in commands." - "\n\n", - BB_BANNER); - do_banner = 1; - } + for (nparam = 0; argv[nparam]; nparam++); + ap = newparam = ckmalloc((nparam + 1) * sizeof(*ap)); + while (*argv) { + *ap++ = ckstrdup(*argv++); } + *ap = NULL; + freeparam(&shellparam); + shellparam.malloc = 1; + shellparam.nparam = nparam; + shellparam.p = newparam; +#if ENABLE_ASH_GETOPTS + shellparam.optind = 1; + shellparam.optoff = -1; #endif } -#if ENABLE_FEATURE_EDITING_VI -#define setvimode(on) do { \ - if (on) line_input_state->flags |= VI_MODE; \ - else line_input_state->flags &= ~VI_MODE; \ -} while (0) -#else -#define setvimode(on) viflag = 0 /* forcibly keep the option off */ -#endif - -static void -optschanged(void) -{ -#if DEBUG - opentrace(); -#endif - setinteractive(iflag); - setjobctl(mflag); - setvimode(viflag); -} - +/* + * Process shell options. The global variable argptr contains a pointer + * to the argument list; we advance it past the options. + */ static void minus_o(char *name, int val) { @@ -8779,7 +9121,6 @@ minus_o(char *name, int val) out1fmt("%-16s%s\n", optnames(i), optlist[i] ? "on" : "off"); } - static void setoption(int flag, int val) { @@ -8794,11 +9135,6 @@ setoption(int flag, int val) ash_msg_and_raise_error("Illegal option -%c", flag); /* NOTREACHED */ } - -/* - * Process shell options. The global variable argptr contains a pointer - * to the argument list; we advance it past the options. - */ static void options(int cmdline) { @@ -8848,47 +9184,6 @@ options(int cmdline) } } -/* - * Set the shell parameters. - */ -static void -setparam(char **argv) -{ - char **newparam; - char **ap; - int nparam; - - for (nparam = 0; argv[nparam]; nparam++); - ap = newparam = ckmalloc((nparam + 1) * sizeof(*ap)); - while (*argv) { - *ap++ = ckstrdup(*argv++); - } - *ap = NULL; - freeparam(&shellparam); - shellparam.malloc = 1; - shellparam.nparam = nparam; - shellparam.p = newparam; -#if ENABLE_ASH_GETOPTS - shellparam.optind = 1; - shellparam.optoff = -1; -#endif -} - -/* - * Free the list of positional parameters. - */ -static void -freeparam(volatile struct shparam *param) -{ - char **ap; - - if (param->malloc) { - for (ap = param->p; *ap; ap++) - free(*ap); - free(param->p); - } -} - /* * The shift builtin command. */ @@ -10720,6 +11015,37 @@ cmdloop(int top) return 0; } +/* + * Take commands from a file. To be compatible we should do a path + * search for the file, which is necessary to find sub-commands. + */ +static char * +find_dot_file(char *name) +{ + char *fullname; + const char *path = pathval(); + struct stat statb; + + /* don't try this for absolute or relative paths */ + if (strchr(name, '/')) + return name; + + while ((fullname = padvance(&path, name)) != NULL) { + if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) { + /* + * Don't bother freeing here, since it will + * be freed by the caller. + */ + return fullname; + } + stunalloc(fullname); + } + + /* not found in the PATH */ + ash_msg_and_raise_error("%s: not found", name); + /* NOTREACHED */ +} + static int dotcmd(int argc, char **argv) { @@ -10982,352 +11308,6 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) } -/* ============ redir.c - * - * Code for dealing with input/output redirection. - */ - -#define EMPTY -2 /* marks an unused slot in redirtab */ -#ifndef PIPE_BUF -# define PIPESIZE 4096 /* amount of buffering in a pipe */ -#else -# define PIPESIZE PIPE_BUF -#endif - -/* - * Open a file in noclobber mode. - * The code was copied from bash. - */ -static int -noclobberopen(const char *fname) -{ - int r, fd; - struct stat finfo, finfo2; - - /* - * If the file exists and is a regular file, return an error - * immediately. - */ - r = stat(fname, &finfo); - if (r == 0 && S_ISREG(finfo.st_mode)) { - errno = EEXIST; - return -1; - } - - /* - * If the file was not present (r != 0), make sure we open it - * exclusively so that if it is created before we open it, our open - * will fail. Make sure that we do not truncate an existing file. - * Note that we don't turn on O_EXCL unless the stat failed -- if the - * file was not a regular file, we leave O_EXCL off. - */ - if (r != 0) - return open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666); - fd = open(fname, O_WRONLY|O_CREAT, 0666); - - /* If the open failed, return the file descriptor right away. */ - if (fd < 0) - return fd; - - /* - * OK, the open succeeded, but the file may have been changed from a - * non-regular file to a regular file between the stat and the open. - * We are assuming that the O_EXCL open handles the case where FILENAME - * did not exist and is symlinked to an existing file between the stat - * and open. - */ - - /* - * If we can open it and fstat the file descriptor, and neither check - * revealed that it was a regular file, and the file has not been - * replaced, return the file descriptor. - */ - if (fstat(fd, &finfo2) == 0 && !S_ISREG(finfo2.st_mode) - && finfo.st_dev == finfo2.st_dev && finfo.st_ino == finfo2.st_ino) - return fd; - - /* The file has been replaced. badness. */ - close(fd); - errno = EEXIST; - return -1; -} - -/* - * Handle here documents. Normally we fork off a process to write the - * data to a pipe. If the document is short, we can stuff the data in - * the pipe without forking. - */ -static int -openhere(union node *redir) -{ - int pip[2]; - size_t len = 0; - - if (pipe(pip) < 0) - ash_msg_and_raise_error("Pipe call failed"); - if (redir->type == NHERE) { - len = strlen(redir->nhere.doc->narg.text); - if (len <= PIPESIZE) { - full_write(pip[1], redir->nhere.doc->narg.text, len); - goto out; - } - } - if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) { - close(pip[0]); - signal(SIGINT, SIG_IGN); - signal(SIGQUIT, SIG_IGN); - signal(SIGHUP, SIG_IGN); -#ifdef SIGTSTP - signal(SIGTSTP, SIG_IGN); -#endif - signal(SIGPIPE, SIG_DFL); - if (redir->type == NHERE) - full_write(pip[1], redir->nhere.doc->narg.text, len); - else - expandhere(redir->nhere.doc, pip[1]); - _exit(0); - } - out: - close(pip[1]); - return pip[0]; -} - -static int -openredirect(union node *redir) -{ - char *fname; - int f; - - switch (redir->nfile.type) { - case NFROM: - fname = redir->nfile.expfname; - f = open(fname, O_RDONLY); - if (f < 0) - goto eopen; - break; - case NFROMTO: - fname = redir->nfile.expfname; - f = open(fname, O_RDWR|O_CREAT|O_TRUNC, 0666); - if (f < 0) - goto ecreate; - break; - case NTO: - /* Take care of noclobber mode. */ - if (Cflag) { - fname = redir->nfile.expfname; - f = noclobberopen(fname); - if (f < 0) - goto ecreate; - break; - } - /* FALLTHROUGH */ - case NCLOBBER: - fname = redir->nfile.expfname; - f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666); - if (f < 0) - goto ecreate; - break; - case NAPPEND: - fname = redir->nfile.expfname; - f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666); - if (f < 0) - goto ecreate; - break; - default: -#if DEBUG - abort(); -#endif - /* Fall through to eliminate warning. */ - case NTOFD: - case NFROMFD: - f = -1; - break; - case NHERE: - case NXHERE: - f = openhere(redir); - break; - } - - return f; - ecreate: - ash_msg_and_raise_error("cannot create %s: %s", fname, errmsg(errno, "Directory nonexistent")); - eopen: - ash_msg_and_raise_error("cannot open %s: %s", fname, errmsg(errno, "No such file")); -} - -static void -dupredirect(union node *redir, int f) -{ - int fd = redir->nfile.fd; - - if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) { - if (redir->ndup.dupfd >= 0) { /* if not ">&-" */ - copyfd(redir->ndup.dupfd, fd); - } - return; - } - - if (f != fd) { - copyfd(f, fd); - close(f); - } -} - -/* - * Process a list of redirection commands. If the REDIR_PUSH flag is set, - * old file descriptors are stashed away so that the redirection can be - * undone by calling popredir. If the REDIR_BACKQ flag is set, then the - * standard output, and the standard error if it becomes a duplicate of - * stdout, is saved in memory. - */ -static void -redirect(union node *redir, int flags) -{ - union node *n; - struct redirtab *sv; - int i; - int fd; - int newfd; - int *p; - nullredirs++; - if (!redir) { - return; - } - sv = NULL; - INT_OFF; - if (flags & REDIR_PUSH) { - struct redirtab *q; - q = ckmalloc(sizeof(struct redirtab)); - q->next = redirlist; - redirlist = q; - q->nullredirs = nullredirs - 1; - for (i = 0; i < 10; i++) - q->renamed[i] = EMPTY; - nullredirs = 0; - sv = q; - } - n = redir; - do { - fd = n->nfile.fd; - if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD) - && n->ndup.dupfd == fd) - continue; /* redirect from/to same file descriptor */ - - newfd = openredirect(n); - if (fd == newfd) - continue; - if (sv && *(p = &sv->renamed[fd]) == EMPTY) { - i = fcntl(fd, F_DUPFD, 10); - - if (i == -1) { - i = errno; - if (i != EBADF) { - close(newfd); - errno = i; - ash_msg_and_raise_error("%d: %m", fd); - /* NOTREACHED */ - } - } else { - *p = i; - close(fd); - } - } else { - close(fd); - } - dupredirect(n, newfd); - } while ((n = n->nfile.next)); - INT_ON; - if (flags & REDIR_SAVEFD2 && sv && sv->renamed[2] >= 0) - preverrout_fd = sv->renamed[2]; -} - -/* - * Undo the effects of the last redirection. - */ -static void -popredir(int drop) -{ - struct redirtab *rp; - int i; - - if (--nullredirs >= 0) - return; - INT_OFF; - rp = redirlist; - for (i = 0; i < 10; i++) { - if (rp->renamed[i] != EMPTY) { - if (!drop) { - close(i); - copyfd(rp->renamed[i], i); - } - close(rp->renamed[i]); - } - } - redirlist = rp->next; - nullredirs = rp->nullredirs; - free(rp); - INT_ON; -} - -/* - * Undo all redirections. Called on error or interrupt. - */ - -/* - * Discard all saved file descriptors. - */ -static void -clearredir(int drop) -{ - for (;;) { - nullredirs = 0; - if (!redirlist) - break; - popredir(drop); - } -} - -/* - * Copy a file descriptor to be >= to. Returns -1 - * if the source file descriptor is closed, EMPTY if there are no unused - * file descriptors left. - */ -static int -copyfd(int from, int to) -{ - int newfd; - - newfd = fcntl(from, F_DUPFD, to); - if (newfd < 0) { - if (errno == EMFILE) - return EMPTY; - ash_msg_and_raise_error("%d: %m", from); - } - return newfd; -} - -static int -redirectsafe(union node *redir, int flags) -{ - int err; - volatile int saveint; - struct jmploc *volatile savehandler = exception_handler; - struct jmploc jmploc; - - SAVE_INT(saveint); - err = setjmp(jmploc.loc) * 2; - if (!err) { - exception_handler = &jmploc; - redirect(redir, flags); - } - exception_handler = savehandler; - if (err && exception != EXERROR) - longjmp(exception_handler->loc, 1); - RESTORE_INT(saveint); - return err; -} - - /* ============ trap.c */ /* -- 2.25.1