ash: cleanup part 2.7
No binary (build failed)
—
(log yok)
| @@ -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 */ | |
| /* | |