/* * tsh.c - an unenhanced port of the Version 6 (V6) Thompson shell */ /*- * Copyright (c) 2004-2019 * Jeffrey Allen Neitzel . * All rights reserved. * * 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. * * THIS SOFTWARE IS PROVIDED BY JEFFREY ALLEN NEITZEL ``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 JEFFREY ALLEN NEITZEL 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. * * @(#)$Id: tsh.c,v 1.7 2019/03/17 18:10:14 jneitzel Exp $ */ /* * Derived from: V6 UNIX /usr/source/s2/sh.c */ /*- * Copyright (C) Caldera International Inc. 2001-2002. All rights reserved. * * 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 and documentation 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. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed or owned by Caldera * International, Inc. * 4. Neither the name of Caldera International, Inc. nor the names of other * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA * INTERNATIONAL, INC. 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 CALDERA INTERNATIONAL, INC. 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. */ #define TSH_SHELL #include "defs.h" #include "sh.h" #include "sh_err.h" #include "pexec.h" #include "sasignal.h" #if defined(CONFIG_BROKEN) # error "Ubuntu 16.(04|10)|17.04: Not supported: see https://etsh.nl/blog/ubuntu-16/" #endif /* * **** Global Variables **** */ const char *name; /* $0 - shell command name */ bool no_lnum = true; /* no line number flag */ /* * shell signal messages */ static const char *const sigmsg[] = { " -- Core dumped", "Hangup", "", "Quit", "Illegal instruction", "Trace/BPT trap", "IOT trap", "EMT trap", "Floating exception", "Killed", "Bus error", "Memory fault", "Bad system call", "Broken pipe" }; #define NSIGMSG ((int)(sizeof(sigmsg) / sizeof(sigmsg[0]))) static char apid[DOLMAX]; /* $$ - ASCII shell process ID */ /*@null@*/ static const char *argv2p; /* string for `-c' option */ static int dolc; /* $N dollar-argument count */ /*@null@*/ static const char *dolp; /* $N and $$ dollar-value pointer */ static char *const *dolv; /* $N dollar-argument value array */ /*@null@*/ /*@observer@*/ static const char *error_message; /* error msg for read/parse errors */ static bool glob_flag; /* glob flag for `*', `?', `[' */ static bool is_first; /* first line flag */ static bool is_noexec; /* not executable file flag */ static char line[_LINESIZ]; /* command-line buffer */ static char *linep; /* line pointer */ static int nul_count; /* `\0'-character count (per line) */ static int one_line_flag; /* one-line flag for `-t' option */ static char peekc; /* just-read, pushed-back character */ /*@null@*/ /*@observer@*/ static const char *prompt; /* interactive-shell prompt pointer */ static enum sigflags sig_child; /* SIG(INT|QUIT|TERM) child flags */ static int status; /* shell exit status */ static size_t tree_count; /* talloc() call count (per line) */ static char *word[_WORDSIZ];/* word pointer array */ /*@null@*/ static char **wordp; /* word pointer */ /* * **** Function Prototypes **** */ static void rpx_line(void); static void get_word(void); static char xgetc(bool); static char readc(void); /*@maynotreturn@*/ /*@null@*/ static struct tnode *talloc(void); static void tfree(/*@null@*/ /*@only@*/ struct tnode *); /*@null@*/ static struct tnode *syntax(char **, char **); /*@null@*/ static struct tnode *syn1(char **, char **); /*@null@*/ static struct tnode *syn2(char **, char **); /*@null@*/ static struct tnode *syn3(char **, char **); static void vscan(/*@null@*/ char **, int (*)(int)); static void ascan(/*@null@*/ char *, int (*)(int)); static int tglob(int); static int trim(int); static bool any(int, const char *); static void execute(/*@null@*/ struct tnode *, /*@null@*/ int *, /*@null@*/ int *); static void pwait(pid_t); static int prsig(int, pid_t, pid_t); static void setenv_tes(void); /*@maynotreturn@*/ static void sh_errexit(int); static void sh_init(/*@dependent@*/ /*@null@*/ /*@temp@*/ const char *); static void sh_magic(void); static void fd_free(void); static bool fd_isdir(int); static void xfree(/*@null@*/ /*@only@*/ void *); /*@maynotreturn@*/ /*@out@*/ static void *xmalloc(size_t); /* * NAME * tsh - Thompson shell (command interpreter) * * SYNOPSIS * tsh [- | -c [string] | -t | file [arg1 ...]] * * DESCRIPTION * See the tsh(1) manual page for full details. */ int main(int argc, char **argv) { bool dosigs = false; sh_init(argv[0]); if (argv[0] == NULL || *argv[0] == EOS) sh_err(SH_ERR, FMT1S, ERR_ALINVAL); if (fd_isdir(FD0)) goto done; /* Handle the standard Thompson shell options and so forth. */ if (argc > 1) { name = argv[1]; dolv = &argv[1]; dolc = argc - 1; if (*argv[1] == HYPHEN) { dosigs = true; if (argv[1][1] == 'c' && argc > 2) { dolv += 1; dolc -= 1; argv2p = argv[2]; } else if (argv[1][1] == 't') one_line_flag = 2; /* All other conceivable options are perfectly valid. */ } else { (void)close(FD0); if (open(argv[1], O_RDONLY) != FD0) sh_err(SH_ERR, FMT2S, argv[1], ERR_OPEN); if (fd_isdir(FD0)) goto done; } } else { dosigs = true; if (isatty(FD0) != 0 && isatty(FD2) != 0) prompt = (geteuid() != 0) ? "% " : "# "; } if (dosigs) { if (sasignal(SIGINT, SIG_IGN) == SIG_DFL) sig_child |= S_SIGINT; if (sasignal(SIGQUIT, SIG_IGN) == SIG_DFL) sig_child |= S_SIGQUIT; if (prompt != NULL) if (sasignal(SIGTERM, SIG_IGN) == SIG_DFL) sig_child |= S_SIGTERM; } fd_free(); sh_magic(); loop: if (prompt != NULL) fd_print(FD2, "%s", prompt); rpx_line(); goto loop; done: return status; } /* * Read, parse, and execute a command line. */ static void rpx_line(void) { struct tnode *t; sigset_t nmask, omask; char *wp; linep = line; wordp = word; error_message = NULL; nul_count = 0; tree_count = 0; do { wp = linep; get_word(); } while (*wp != EOL); *wordp = NULL; if (error_message != NULL) { if (is_noexec && name != NULL) sh_err(SH_ERR, FMT2S, name, error_message); else sh_err(SH_ERR, FMT1S, error_message); return; } if (wordp - word > 1) { (void)sigfillset(&nmask); (void)sigprocmask(SIG_SETMASK, &nmask, &omask); t = NULL; t = syntax(word, wordp); (void)sigprocmask(SIG_SETMASK, &omask, NULL); if (error_message != NULL) sh_err(SH_ERR, FMT1S, error_message); else execute(t, NULL, NULL); tfree(t); t = NULL; } } /* * Copy a word from the standard input into the line buffer, * and point to it w/ *wordp. Each copied word is represented * in line as an individual `\0'-terminated string. */ static void get_word(void) { char c, c1; *wordp++ = linep; loop: switch (c = xgetc(DOLSUB)) { case SPC: case TAB: goto loop; case DQUOT:/* "..." == multi-char literal */ case SQUOT:/* '...' == ... */ c1 = c; while ((c = xgetc(!DOLSUB)) != c1) { if (c == EOL) { if (error_message == NULL) error_message = ERR_SYNTAX; peekc = c; *linep++ = EOS; return; } if (c == BQUOT) { if ((c = xgetc(!DOLSUB)) == EOL) c = SPC;/* continue line */ else { peekc = c; c = BQUOT; } } *linep++ = c | QUOTE; } break; case BQUOT:/* \. == one-char literal */ if ((c = xgetc(!DOLSUB)) == EOL) goto loop; c |= QUOTE; peekc = c; break; case LPARENTHESIS: case RPARENTHESIS: case SEMICOLON: case AMPERSAND: case VERTICALBAR: case CARET: case LESSTHAN: case GREATERTHAN: case EOL: *linep++ = c; *linep++ = EOS; return; default: peekc = c; } for (;;) { if ((c = xgetc(DOLSUB)) == BQUOT) { if ((c = xgetc(!DOLSUB)) == EOL) c = SPC;/* continue line */ else c |= QUOTE; } if (any(c, WORDPACK)) { peekc = c; if (any(c, QUOTPACK)) goto loop; *linep++ = EOS; return; } *linep++ = c; } /*NOTREACHED*/ } /* * If dolsub is true, get either the next literal character from the * standard input or substitute the current $ dollar w/ the next * character of its value, which is pointed to by dolp. Otherwise, * get only the next literal character from the standard input. */ static char xgetc(bool dolsub) { int n; char c = EOS; if (peekc != EOS) { c = peekc; peekc = EOS; return c; } if (wordp + 1 >= &word[WORDMAX]) { wordp -= 12; while (xgetc(!DOLSUB) != EOL) ; /* nothing */ wordp += 12; error_message = ERR_TMARGS; goto geterr; } if (linep + 1 >= &line[LINEMAX]) { linep -= 12; while (xgetc(!DOLSUB) != EOL) ; /* nothing */ linep += 12; error_message = ERR_TMCHARS; goto geterr; } getd: if (dolp != NULL) { c = *dolp++ & ASCII; if (c != EOS) return c; dolp = NULL; } c = readc(); if (c == DOLLAR && dolsub) { c = readc(); if (c >= '0' && c <= '9') { n = c - '0'; if (IS_DIGIT(n, c) && n < dolc) dolp = (n > 0) ? dolv[n] : name; goto getd; } if (c == DOLLAR) { dolp = apid; goto getd; } } /* Handle EOS/NUL characters as needed. */ if (c == EOS) do { if (prompt == NULL) { if (is_first) is_noexec = true; if (is_first || ++nul_count >= LINEMAX) { error_message = ERR_EXEC; goto geterr; } } c = readc(); } while (c == EOS); if (c == EOL) is_first = false; return c; geterr: if (c == EOL) is_first = false; return EOL; } /* * Read and return an ASCII-equivalent character from the string * pointed to by argv2p or from the standard input. When reading * from argv2p, return the character or `\n'. When reading from * the standard input, return the character. Otherwise, exit w/ * the value of the global variable status when appropriate. */ static char readc(void) { char c; if (argv2p != NULL) { if (argv2p == (char *)-1) exit(status); if ((c = (*argv2p++ & ASCII)) == EOS) { argv2p = (char *)-1; c = EOL; } return c; } if (one_line_flag == 1) exit(status); if (read(FD0, &c, (size_t)1) != 1) exit(status); if ((c &= ASCII) == EOL && one_line_flag == 2) one_line_flag = 1; return c; } /* * Allocate and initialize memory for a new tree node. * Return a pointer to the new node on success. * Return a pointer to NULL if >= TREEMAX. * Do not return on ENOMEM error. */ static struct tnode * talloc(void) { struct tnode *t; if (tree_count >= TREEMAX) { error_message = ERR_CLOVERFLOW; return NULL; } t = xmalloc(sizeof(struct tnode)); t->nleft = NULL; t->nright = NULL; t->nsub = NULL; t->nfin = NULL; t->nfout = NULL; t->nav = NULL; t->nflags = 0; t->ntype = 0; tree_count++; return t; } /* * Deallocate memory for the shell command tree pointed to by t. */ static void tfree(struct tnode *t) { if (t == NULL) return; switch (t->ntype) { case TLIST: case TPIPE: tfree(t->nleft); tfree(t->nright); break; case TCOMMAND: xfree(t->nav); break; case TSUBSHELL: tfree(t->nsub); break; } xfree(t); } /* * syntax: * empty * syn1 */ static struct tnode * syntax(char **p1, char **p2) { while (p1 < p2) if (any(**p1, EOC)) p1++; else return syn1(p1, p2); return NULL; } /* * syn1: * syn2 * syn2 ; syntax * syn2 & syntax */ static struct tnode * syn1(char **p1, char **p2) { struct tnode *t; int c, subcnt; char **p; subcnt = 0; for (p = p1; p < p2; p++) switch (**p) { case LPARENTHESIS: subcnt++; continue; case RPARENTHESIS: subcnt--; if (subcnt < 0) goto syn1err; continue; case SEMICOLON: case AMPERSAND: case EOL: if (subcnt == 0) { c = **p; if ((t = talloc()) == NULL) goto syn1err; t->ntype = TLIST; t->nleft = syn2(p1, p); if (c == AMPERSAND && t->nleft != NULL) t->nleft->nflags |= FAND | FINTR | FPRS; t->nright = syntax(p + 1, p2); t->nflags = 0; return t; } break; } if (subcnt == 0) return syn2(p1, p2); syn1err: if (error_message == NULL) error_message = ERR_SYNTAX; return NULL; } /* * syn2: * syn3 * syn3 | syn2 * syn3 ^ syn2 */ static struct tnode * syn2(char **p1, char **p2) { struct tnode *t; int subcnt; char **p; subcnt = 0; for (p = p1; p < p2; p++) switch (**p) { case LPARENTHESIS: subcnt++; continue; case RPARENTHESIS: subcnt--; continue; case VERTICALBAR: case CARET: if (subcnt == 0) { if ((t = talloc()) == NULL) return NULL; t->ntype = TPIPE; t->nleft = syn3(p1, p); t->nright = syn2(p + 1, p2); t->nflags = 0; return t; } break; } return syn3(p1, p2); } /* * syn3: * command [arg ...] [< in] [> [>] out] * ( syn1 ) [< in] [> [>] out] */ static struct tnode * syn3(char **p1, char **p2) { struct tnode *t; enum tnflags flags; int ac, c, n, subcnt; char **p, **lp, **rp; char *fin, *fout; flags = 0; if (**p2 == RPARENTHESIS) flags |= FNOFORK; fin = NULL; fout = NULL; lp = NULL; rp = NULL; n = 0; subcnt = 0; for (p = p1; p < p2; p++) switch (c = **p) { case LPARENTHESIS: if (subcnt == 0) { if (lp != NULL) goto syn3err; lp = p + 1; } subcnt++; continue; case RPARENTHESIS: subcnt--; if (subcnt == 0) rp = p; continue; case GREATERTHAN: p++; if (p < p2 && **p == GREATERTHAN) flags |= FCAT; else p--; /*FALLTHROUGH*/ case LESSTHAN: if (subcnt == 0) { p++; if (p == p2 || any(**p, REDIRERR)) goto syn3err; if (c == LESSTHAN) { if (fin != NULL) goto syn3err; fin = *p; } else { if (fout != NULL) goto syn3err; fout = *p; } } continue; default: if (subcnt == 0) p1[n++] = *p; } if (lp == NULL) { if (n == 0) goto syn3err; if ((t = talloc()) == NULL) goto syn3err; t->ntype = TCOMMAND; t->nav = xmalloc((n + 1) * sizeof(char *)); for (ac = 0; ac < n; ac++) t->nav[ac] = p1[ac]; t->nav[ac] = NULL; } else { if (n != 0) goto syn3err; if ((t = talloc()) == NULL) goto syn3err; t->ntype = TSUBSHELL; t->nsub = syn1(lp, rp); } t->nfin = fin; t->nfout = fout; t->nflags = flags; return t; syn3err: if (error_message == NULL) error_message = ERR_SYNTAX; return NULL; } /* * Scan the entire argument vector pointed to by vp. * Each character of each argument is scanned w/ the function * pointed to by func. Do nothing if vp is a NULL pointer. */ static void vscan(char **vp, int (*func)(int)) { char *ap, c; if (vp == NULL) return; while ((ap = *vp++) != NULL) while ((c = *ap) != EOS) *ap++ = (*func)(c); } /* * Scan the argument pointed to by ap, a `\0'-terminated string. * Each character of the string is scanned w/ the function pointed * to by func. Do nothing if ap is a NULL pointer. */ static void ascan(char *ap, int (*func)(int)) { char c; if (ap == NULL) return; while ((c = *ap) != EOS) *ap++ = (*func)(c); } /* * Set the global variable glob_flag to true (1) if the character * c is a glob character. Return the character in all cases. */ static int tglob(int c) { if (any(c, GLOBCHARS)) glob_flag = true; return c; } /* * Return the character c converted back to its ASCII form. */ static int trim(int c) { return c & ASCII; } /* * Return true (1) if the character c matches any character in * the string pointed to by as. Otherwise, return false (0). */ static bool any(int c, const char *as) { const char *s; s = as; while (*s != EOS) if (*s++ == c) return true; return false; } /* * Execute the shell command tree pointed to by t. */ static void execute(struct tnode *t, int *pin, int *pout) { struct tnode *t1; enum tnflags f; pid_t cpid; int i, pfd[2]; const char **tav; const char *p; const char *cmd = NULL; if (t == NULL) return; switch (t->ntype) { case TLIST: f = t->nflags & (FFIN | FPIN | FINTR); if ((t1 = t->nleft) != NULL) t1->nflags |= f; execute(t1, NULL, NULL); if ((t1 = t->nright) != NULL) t1->nflags |= f; execute(t1, NULL, NULL); return; case TPIPE: if (pipe(pfd) == -1) { sh_err(SH_ERR, FMT1S, ERR_PIPE); if (pin != NULL) { (void)close(pin[0]); (void)close(pin[1]); } return; } f = t->nflags; if ((t1 = t->nleft) != NULL) t1->nflags |= FPOUT | (f & (FFIN|FPIN|FINTR|FPRS)); execute(t1, pin, pfd); if ((t1 = t->nright) != NULL) t1->nflags |= FPIN | (f & (FAND|FPOUT|FINTR|FPRS)); execute(t1, pfd, pout); (void)close(pfd[0]); (void)close(pfd[1]); return; case TCOMMAND: if (t->nav == NULL || t->nav[0] == NULL) { /* should never (but can) be true */ sh_err(SH_ERR, FMT2S, __func__, "Invalid command"); return; } cmd = t->nav[0]; if (EQUAL(cmd, ":")) { status = SH_TRUE; setenv_tes(); return; } if (EQUAL(cmd, "chdir")) { ascan(t->nav[1], &trim); if (t->nav[1] == NULL) sh_err(SH_ERR, FMT2S, cmd, ERR_ARGCOUNT); else if (chdir(t->nav[1]) == -1) sh_err(SH_ERR, FMT2S, cmd, ERR_BADDIR); else status = SH_TRUE; setenv_tes(); return; } if (EQUAL(cmd, "exit")) { setenv_tes(); if (prompt == NULL) { (void)lseek(FD0, (off_t)0, SEEK_END); EXIT(status); } return; } if (EQUAL(cmd, "login") || EQUAL(cmd, "newgrp")) { if (prompt != NULL) { p = (*cmd == 'l') ? PATH_LOGIN : PATH_NEWGRP; vscan(t->nav, &trim); (void)sasignal(SIGINT, SIG_DFL); (void)sasignal(SIGQUIT, SIG_DFL); (void)pexec(p, (char *const *)t->nav); (void)sasignal(SIGINT, SIG_IGN); (void)sasignal(SIGQUIT, SIG_IGN); } sh_err(SH_ERR, FMT2S, cmd, ERR_EXEC); return; } if (EQUAL(cmd, "shift")) { if (dolc > 1) { dolv = &dolv[1]; dolc--; status = SH_TRUE; setenv_tes(); return; } sh_err(SH_ERR, FMT2S, cmd, ERR_NOARGS); return; } if (EQUAL(cmd, "wait")) { pwait(-1); return; } /*FALLTHROUGH*/ case TSUBSHELL: f = t->nflags; if ((cpid = ((f & FNOFORK) != 0) ? 0 : fork()) == -1) { sh_err(SH_ERR, FMT1S, ERR_FORK); return; } /**** Parent! ****/ if (cpid != 0) { if (pin != NULL && (f & FPIN) != 0) { (void)close(pin[0]); (void)close(pin[1]); } if ((f & FPRS) != 0) fd_print(FD2, "%u\n", (u_int)cpid); if ((f & FAND) != 0) return; if ((f & FPOUT) == 0) pwait(cpid); return; } /**** Child! ****/ /* * Redirect (read) input from pipe. */ if (pin != NULL && (f & FPIN) != 0) { if (dup2(pin[0], FD0) == -1) sh_err(FC_ERR, FMT1S, strerror(errno)); (void)close(pin[0]); (void)close(pin[1]); } /* * Redirect (write) output to pipe. */ if (pout != NULL && (f & FPOUT) != 0) { if (dup2(pout[1], FD1) == -1) sh_err(FC_ERR, FMT1S, strerror(errno)); (void)close(pout[0]); (void)close(pout[1]); } /* * Redirect (read) input from file. */ if (t->nfin != NULL && (f & FPIN) == 0) { f |= FFIN; ascan(t->nfin, &trim); if ((i = open(t->nfin, O_RDONLY)) == -1) sh_err(FC_ERR, FMT2S, t->nfin, ERR_OPEN); if (dup2(i, FD0) == -1) sh_err(FC_ERR, FMT1S, strerror(errno)); (void)close(i); } /* * Redirect (write) output to file. */ if (t->nfout != NULL && (f & FPOUT) == 0) { if ((f & FCAT) != 0) i = O_WRONLY | O_APPEND | O_CREAT; else i = O_WRONLY | O_TRUNC | O_CREAT; ascan(t->nfout, &trim); if ((i = open(t->nfout, i, 0666)) == -1) sh_err(FC_ERR, FMT2S, t->nfout, ERR_CREATE); if (dup2(i, FD1) == -1) sh_err(FC_ERR, FMT1S, strerror(errno)); (void)close(i); } /* * Set the action for the SIGINT and SIGQUIT signals, and * redirect input for `&' commands from `/dev/null' if needed. */ if ((f & FINTR) != 0) { (void)sasignal(SIGINT, SIG_IGN); (void)sasignal(SIGQUIT, SIG_IGN); if (t->nfin == NULL && (f & (FFIN|FPIN|FPRS)) == FPRS) { (void)close(FD0); if (open("/dev/null", O_RDONLY) != FD0) sh_err(FC_ERR, FMT2S, "/dev/null", ERR_OPEN); } } else { if ((sig_child & S_SIGINT) != 0) (void)sasignal(SIGINT, SIG_DFL); if ((sig_child & S_SIGQUIT) != 0) (void)sasignal(SIGQUIT, SIG_DFL); } /* Set the SIGTERM signal to its default action if needed. */ if ((sig_child & S_SIGTERM) != 0) (void)sasignal(SIGTERM, SIG_DFL); if (t->ntype == TSUBSHELL) { if ((t1 = t->nsub) != NULL) t1->nflags |= (f & (FFIN | FPIN | FINTR)); execute(t1, NULL, NULL); _exit(status); } if (t->nav == NULL || t->nav[0] == NULL) sh_err(FC_ERR, FMT2S, __func__, "Invalid command"); cmd = t->nav[0]; glob_flag = false; vscan(t->nav, &tglob); if (glob_flag) { for (i = 0; t->nav[i] != NULL; i++) ; /* nothing */ tav = xmalloc((i + 2) * sizeof(char *)); tav[0] = GLOB_PATH; cmd = tav[0]; (void)memcpy(&tav[1], t->nav, (i + 1) * sizeof(char *)); #ifdef DEBUG_GLOB fd_print(FD2, "%s: i == %d;\n", __func__, i); for (i = 0; tav[i] != NULL; i++) fd_print(FD2, "tav[%d]==%p==%p==%s;\n", i, (void *)&tav[i], (void *)tav[i], tav[i]); fd_print(FD2, "tav[%d]==%p==NULL;\n", i, (void *)&tav[i]); #endif (void)pexec_err(cmd, (char *const *)tav); } else if (IS_LIBEXEC(EQUAL)) { vscan(t->nav, &trim); for (i = 0; t->nav[i] != NULL; i++) ; /* nothing */ tav = xmalloc((i + 1) * sizeof(char *)); if (EQUAL(cmd, "fd2")) tav[0] = FD2_PATH; else if (EQUAL(cmd, "goto")) tav[0] = GOTO_PATH; else tav[0] = IF_PATH; cmd = tav[0]; (void)memcpy(&tav[1], &t->nav[1], i * sizeof(char *)); #ifdef DEBUG_LED fd_print(FD2, "%s: i == %d;\n", __func__, i); for (i = 0; tav[i] != NULL; i++) fd_print(FD2, "tav[%d]==%p==%p==%s;\n", i, (void *)&tav[i], (void *)tav[i], tav[i]); fd_print(FD2, "tav[%d]==%p==NULL;\n", i, (void *)&tav[i]); #endif (void)pexec_err(cmd, (char *const *)tav); } else { vscan(t->nav, &trim); cmd = t->nav[0]; (void)pexec_err(cmd, (char *const *)t->nav); } /*NOTREACHED*/ } } /* * If cp > 0, wait for the child process cp to terminate. * While doing so, exorcise any zombies for which the shell has not * yet waited, and wait for any other child processes which terminate * during this time. Otherwise, if cp < 0, block and wait for all * child processes to terminate. */ static void pwait(pid_t cp) { pid_t tp; int s; if (cp == 0) return; for (;;) { tp = wait(&s); if (tp == -1) break; if (s != 0) { if (WIFSIGNALED(s) != 0) status = prsig(s, tp, cp); else if (WIFEXITED(s) != 0) status = WEXITSTATUS(s); if (status >= FC_ERR) { if (status == FC_ERR) status = SH_ERR; sh_err(status, NULL); } } else status = SH_TRUE; if (tp == cp) break; } setenv_tes(); } /* * Print termination report for process tp according to signal s. * Return a value of 128 + signal s (e) for exit status of tp. */ static int prsig(int s, pid_t tp, pid_t cp) { int e, r; char buf[SBUFMM(1)]; const char *c, *m; e = WTERMSIG(s); if (e >= NSIGMSG || (e >= 0 && *sigmsg[e] != EOS)) { if (e < NSIGMSG) m = sigmsg[e]; else { r = snprintf(buf, sizeof(buf), "Sig %u", (u_int)e); m = (r < 0 || r >= (int)sizeof(buf)) ? "prsig: snprintf: Internal error" : buf; } c = ""; #ifdef WCOREDUMP if (WCOREDUMP(s) != 0) c = sigmsg[0]; #endif if (tp != cp) fd_print(FD2, "%u: %s%s\n", (u_int)tp, m, c); else fd_print(FD2, "%s%s\n", m, c); } else fd_print(FD2, FMT1S, ""); return 128 + e; } /* * Set TES (short for TSH_EXIT_STATUS) in the environment w/o * changing the current value of the global variable status. * Set TES to the empty string on snprintf(3) error or what * would have indicated an overflow condition. Print a * diagnostic on setenv(3) error, but do not error exit. */ static void setenv_tes(void) { char buf[SBUFMM(1)]; int r, ss; ss = status; *buf = EOS; r = snprintf(buf, sizeof(buf), "%u", (u_int)status); if (r < 0 || r >= (int)sizeof(buf)) *buf = EOS; if (setenv("TES", buf, 1) == -1) fd_print(FD2, FMT2S, __func__, ERR_NOMEM); status = ss; } /* * Handle all error exit scenarios for the shell. This includes * setting the exit status to the appropriate value according to * es and causing the shell to exit if appropriate. This function * is called by sh_err() and may or may not return. */ static void sh_errexit(int es) { #ifdef DEBUG fd_print(FD2,"%s: getmypid() == %d, es == %d;\n",__func__,getmypid(),es); #endif status = es; setenv_tes(); if (prompt == NULL) { (void)lseek(FD0, (off_t)0, SEEK_END); EXIT(status); } if (getpid() != getmypid()) _exit(status); } /* * Initialize the shell. */ static void sh_init(const char *av0p) { struct stat sb; int i; setmyerrexit(&sh_errexit); setmyname(av0p); setmypid(getpid()); /* * Set-ID execution is not supported. */ if (geteuid() != getuid() || getegid() != getgid()) sh_err(SH_ERR, FMT1S, ERR_SETID); /* * Save the process ID of the shell as a 5-digit ASCII * string (apid). Each occurrence of `$$' in a command * line is substituted w/ the value of apid. */ i = snprintf(apid, sizeof(apid), "%05u", (u_int)getmypid()); if (i < 0 || i >= (int)sizeof(apid)) *apid = EOS; /* * Fail if any of the descriptors 0, 1, or 2 is not open. */ for (i = 0; i < 3; i++) if (fstat(i, &sb) == -1) sh_err(SH_ERR, "%u: %s\n", (u_int)i, strerror(errno)); /* * Set the SIGCHLD signal to its default action. * Correct operation of the shell requires that zombies * be created for its children when they terminate. */ (void)sasignal(SIGCHLD, SIG_DFL); } /* * Ignore any `#!shell' sequence as the first line of a regular file. * The length of this line is limited to (LINEMAX - 1) characters. */ static void sh_magic(void) { struct stat sb; size_t len; int c; is_first = true; is_noexec = false; if (fstat(FD0, &sb) == -1 || !S_ISREG(sb.st_mode)) return; if (lseek(FD0, (off_t)0, SEEK_CUR) == 0) { if (readc() == HASH && readc() == BANG) { for (len = 2; len < LINEMAX; len++) { if ((c = readc()) == EOS) { is_noexec = true; break; } if (c == EOL) { /* readc() doesn't return EOF */ is_first = false; return; } } sh_err(SH_ERR, FMT2S, name, ERR_EXEC); } else (void)lseek(FD0, (off_t)0, SEEK_SET); } } /* * Attempt to free or release all of the file descriptors in the * range from (fd_max - 1) through (FD2 + 1); the value of fd_max * may fall between FDFREEMIN and FDFREEMAX, inclusive. */ static void fd_free(void) { long int fd_max; int fd; fd_max = sysconf(_SC_OPEN_MAX); if (fd_max < FDFREEMIN || fd_max > FDFREEMAX) fd_max = FDFREEMIN; for (fd = (int)fd_max - 1; fd > FD2; fd--) (void)close(fd); } /* * Return true (1) if the file descriptor fd refers to an open file * which is a directory. Otherwise, return false (0). */ static bool fd_isdir(int fd) { struct stat sb; return fstat(fd, &sb) == 0 && S_ISDIR(sb.st_mode); } /* * Deallocate the memory allocation pointed to by p. */ static void xfree(void *p) { if (p != NULL) { free(p); p = NULL; } } /* * Allocate memory, and check for error. * Return a pointer to the allocated space on success. * Do not return on ENOMEM error. */ static void * xmalloc(size_t s) { void *mp; if ((mp = malloc(s)) == NULL) { sh_err(ESTATUS, FMT1S, ERR_NOMEM); /*NOTREACHED*/ } return mp; }