/* * etsh.c - an enhanced 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: etsh.c,v 1.9 2019/03/17 18:01:40 jneitzel Exp $ */ /* * Derived from: * - V6 UNIX /usr/source/s2/sh.c * * - osh-20061230 * osh.c (1.1 (jneitzel) 2006/12/26) * sh6.c (1.3 (jneitzel) 2006/09/15) * glob6.c (1.3 (jneitzel) 2006/09/23) */ /*- * 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. */ /* * The following license covers the source_open() function. My license, * see Jeffrey Allen Neitzel , on lines 4 - 30 * of this file, covers it as well. */ /*- * Copyright (c) 1991, 1993 * The Regents of the University of California. 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. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #define ETSH_SHELL #include "defs.h" #include "sh.h" #include "sh_err.h" #include "lib.h" #include "pexec.h" #include "sasignal.h" #include "strtoint.h" #ifdef __GNUC__ #define IS_UNUSED __attribute__((__unused__)) #else #define IS_UNUSED /* nothing */ #endif /* * The following file descriptors are reserved for special use by etsh. */ #define DUPFD0 10 /* used for input redirection w/ `<-' */ #define HWFD 11 /* used for history write fildes */ #define PWD 12 /* used in do_chdir() */ #define SAVFD0 13 /* used in do_source() */ /* * For the rc (init and logout) files used by etsh. */ #define DO_SYSTEM_LOGIN 1 #define DO_SYSTEM_ETSHRC 2 #define DO_DOT_LOGIN 3 #define DO_DOT_ETSHRC 4 #define DO_INIT_DONE 5 #define DO_SYSTEM_LOGOUT 1 #define DO_DOT_LOGOUT 2 #define DO_LOGOUT_DONE 3 /* * These are the symbolic names for the types checked by fd_type(). */ #define FD_TMASK S_IFMT /* file descriptor (FD) type mask */ #define FD_ISOPEN 01 /* Does FD refer to an open file? */ #define FD_ISDIROK 02 /* Does FD refer to an existent directory? */ #define FD_ISREGOK 04 /* Does FD refer to an existent regular file? */ #define FD_ISDIR S_IFDIR /* Does FD refer to a directory? */ #define FD_ISREG S_IFREG /* Does FD refer to a regular file? */ /* * Needed by OpenIndiana * (NSIG - 1) is the maximum signal number value accepted by `trap'. */ #ifdef CONFIG_SUNOS #ifndef NSIG #define NSIG 33 #endif #endif #define HALT true #define PROMPT ((shtype & ST_MASK) == ST_INTERACTIVE) #define SHTYPE(f) ((shtype & (f)) != 0) /* * shell type flags */ enum stflags { ST_ONELINE = 001, ST_COMMANDFILE = 002, ST_INTERACTIVE = 004, ST_RCFILE = 010, ST_SOURCE = 020, ST_MASK = 037 }; /* * **** Global Variables **** */ const char *name; /* $0 - shell command name */ bool is_noexec; /* not executable file flag */ bool prompt_on; /* `prompt' on/off flag; true == on */ bool prompt_dbg; /* `prompt' debug flag; true == on */ bool no_lnum; /* no line number flag */ uid_t sheuid; /* effective shell user ID */ static gid_t shegid; /* effective shell group ID */ /* * shell sbi command structure array */ static const struct sbicmd { const char *sbi_command; const enum sbikey sbi_key; } sbi[] = { { ".", SBI_DOT }, { ":", SBI_NULL }, { "alias", SBI_ALIAS }, { "cd", SBI_CD }, { "chdir", SBI_CHDIR }, { "echo", SBI_ECHO }, { "exec", SBI_EXEC }, { "exit", SBI_EXIT }, { "fd2", SBI_FD2 }, { "goto", SBI_GOTO }, { "if", SBI_IF }, { "login", SBI_LOGIN }, { "newgrp", SBI_NEWGRP }, { "prompt", SBI_PROMPT }, { "pwd", SBI_PWD }, { "set", SBI_SET }, { "setenv", SBI_SETENV }, { "shift", SBI_SHIFT }, { "source", SBI_SOURCE }, { "trap", SBI_TRAP }, { "umask", SBI_UMASK }, { "unalias", SBI_UNALIAS }, { "unset", SBI_UNSET }, { "unsetenv", SBI_UNSETENV }, { "verbose", SBI_VERBOSE }, { "wait", SBI_WAIT } }; #define NSBICMD ((int)(sizeof(sbi) / sizeof(sbi[0]))) /* * 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]))) /*@null@*/ static const char *argv2p; /* string for `-c' option */ static int dolac; /* $* dollar-argument count */ static char dolbuf[DOLMAX]; /* dollar buffer for $$, $#, $?, $v */ static int dolc; /* $N dollar-argument count */ /*@null@*/ /*@only@*/ static const char *dolp; /* $ dollar-value pointer */ static char *const *dolv; /* $N dollar-argument value array */ static int dupfd0; /* duplicate of the standard input */ /*@null@*/ /*@observer@*/ static const char *error_message; /* error msg for read/parse errors */ static bool error_source; /* error flag for `source' command */ static bool have_tmargs; /* Too many args error flag */ static bool have_tmchars; /* Too many characters error flag */ static int hwfd = -1; /* history write file descriptor */ static bool is_first; /* first line flag */ static bool is_login; /* login shell flag */ static bool verbose_flag; /* verbose flag for `-v' & `verbose'*/ static bool noexec_flag; /* noexec flag for `-n' */ static bool notype; /* no type shell, where shtype == 0 */ static char line[_LINESIZ]; /* command-line buffer */ static char aline[_LINESIZ];/* alias-line buffer */ static char *linep; /* [a]line pointer */ static char *elinep; /* end of [a]line pointer */ static volatile sig_atomic_t logout_now; /* SIGHUP caught flag (1 if caught) */ static int nul_count; /* `\0'-character count (per line) */ static int peekc; /* just-read, pushed-back character */ /*@null@*/ /*@only@*/ static char *rcfilename; /* rc file name for diagnostics */ static enum stflags shtype; /* shell type (determines behavior) */ static enum sigflags sig_child; /* SIG(INT|QUIT|TERM) child flags */ static enum sigflags sig_state; /* SIG(INT|QUIT|TERM) state flags */ static int status; /* shell exit status */ static size_t tree_count; /* talloc() call count (per line) */ static char *word[_WORDSIZ];/* word pointer array */ static char *aword[_WORDSIZ];/* alias word pointer array */ /*@null@*/ static char **wordp; /* [a]word pointer */ static char **ewordp; /* end of [a]word pointer */ /*@null@*/ /*@only@*/ static struct anode *anp; /* shell alias node pointer */ /*@null@*/ static const char *asp; /* shell alias string pointer */ /*@only@*/ static struct tnode *tnp; /* shell command tree node pointer */ /*@null@*/ /*@only@*/ static struct vnode *vnp; /* shell variable node pointer */ /*@null@*/ /*@only@*/ static char *tty; /* $t - terminal name */ /*@null@*/ /*@only@*/ static char *user; /* $u - effective user name */ /*@null@*/ /*@only@*/ static char *euid; /* $k - effective user ID */ /*@null@*/ /*@only@*/ static char *group; /* $g - effective group name */ /*@null@*/ /*@only@*/ static char *egid; /* $i - effective group ID */ /*@null@*/ /*@only@*/ static char *cwd; /* $w - current working directory */ /* * **** Function Prototypes **** */ /*@noreturn@*/ static void usage(void); static void misc_free(void); static void cmd_loop(bool); static void cmd_verbose(void); static int rpx_line(void); /*@null@*/ static const char **rp_alias(const char *); static int get_word(void); static int xgetc(bool); static int readc(void); /*@null@*/ /*@only@*/ static const char *get_dolp(int); static void get_set_cwd(enum sbikey, bool, /*@null@*/ char **); static void varalloc(int, /*@null@*/ const char *); /*@maynotreturn@*/ static struct vnode *varalloc1(int, const char *); static bool varfree(int); static void aalloc(/*@null@*/ const char *, /*@null@*/ const char *); /*@maynotreturn@*/ static struct anode *aalloc1(const char *, const char *); static bool afree(/*@null@*/ const char *); /*@null@*/ static const char *asget(/*@null@*/ const char *); /*@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 bool any(int, const char *); static bool vtglob(char **); static void vtrim(char **); static int vacount(const char **); static void execute(/*@null@*/ struct tnode *, /*@null@*/ int *, /*@null@*/ int *); static void execute1(struct tnode *); static void execute2(struct tnode *, /*@null@*/ int *, /*@null@*/ int *); static void do_chdir(enum sbikey, char **); static void do_trap(char **, enum tnflags); static void set_ss_flags(int, action_type); static void do_prompt(enum sbikey, /*@null@*/ char **); static void do_source(enum sbikey, char **); static int source_open(const char *, const char *); static void pwait(pid_t); static int prsig(int, pid_t, pid_t); /*@maynotreturn@*/ static void sh_errexit(int); static void sh_init(/*@dependent@*/ /*@null@*/ /*@temp@*/ const char *); static void sh_magic(void); static bool sh_on_tty(void); static void sighup(/*@unused@*/ int IS_UNUSED); static void rc_init(int *); static void rc_logout(int *); /*@maynotreturn@*/ static bool rc_open(/*@null@*/ const char *); static char *pn_build(/*@out@*/ /*@returned@*/ char *, const char *, size_t); static void hist_write(bool); static void hist_open(void); static void fd_free(void); static bool fd_type(int, mode_t); /* * NAME * etsh - enhanced Thompson shell (command interpreter) * * SYNOPSIS * etsh [-V | -VV] * etsh [-nv] [- | -c [string] | -i | -l | -t | file [arg1 ...]] * * DESCRIPTION * See the etsh(1) manual page for full details. */ int main(int argc, char **argv) { char *av0p, *snamep; int rcflag = 0; bool dosigs = false; sh_init(argv[0]); if (argv[0] == NULL || *argv[0] == EOS) sh_err(SH_ERR, FMT2S, getmyname(), ERR_ALINVAL); if (fd_type(FD0, FD_ISDIR)) goto done; /* Handle the -V and -VV options (no getopt(3)). */ if (argc == 2 && *argv[1] == HYPHEN && argv[1][1] == 'V') { status = SH_TRUE; if (argv[1][2] == EOS) fd_print(FD1, FMT1S, ETSH_VERSION); else if (argv[1][2] == 'V' && argv[1][3] == EOS) fd_print(FD1,"%s ( %s )\n",ETSH_VERSION,ETSH_UNAME_SRM); else usage(); goto done; } /* * Handle the -n and -v flags / options. * * NOTE: We choose not to use getopt(3) for compatibility reasons. * -noexec & -verbose, in fact -[nv]*, are perfectly valid, * as well as -nv & -vn, as indicated by SYNOPSIS above. */ if (argc > 1 && *argv[1] == HYPHEN && (argv[1][1] == 'n' || argv[1][1] == 'v')) { if (argv[1][1] == 'v' && argv[1][2] == 'n' && argv[1][3] == EOS) { verbose_flag = true; noexec_flag = true; } else if (argv[1][1] == 'n' && argv[1][2] == 'v' && argv[1][3] == EOS) { noexec_flag = true; verbose_flag = true; } else if (argv[1][1] == 'v') verbose_flag = true; else if (argv[1][1] == 'n') noexec_flag = true; /* again, to catch the flipped args if needed */ if (argv[1][1] == 'n' && argv[1][2] == 'v' && argv[1][3] == EOS) { noexec_flag = true; verbose_flag = true; } else if (argv[1][1] == 'v' && argv[1][2] == 'n' && argv[1][3] == EOS) { verbose_flag = true; noexec_flag = true; } else if (argv[1][1] == 'n') noexec_flag = true; else if (argv[1][1] == 'v') verbose_flag = true; av0p = argv[0], argv = &argv[1], argv[0] = av0p; argc--; } if (argc > 1 && *argv[1] == HYPHEN && (argv[1][1] == 'n' || argv[1][1] == 'v')) { if (argv[1][1] == 'v' && argv[1][2] == 'n' && argv[1][3] == EOS) { verbose_flag = true; noexec_flag = true; } else if (argv[1][1] == 'n' && argv[1][2] == 'v' && argv[1][3] == EOS) { noexec_flag = true; verbose_flag = true; } else if (argv[1][1] == 'v') verbose_flag = true; else if (argv[1][1] == 'n') noexec_flag = true; /* again, to catch the flipped args if needed */ if (argv[1][1] == 'n' && argv[1][2] == 'v' && argv[1][3] == EOS) { noexec_flag = true; verbose_flag = true; } else if (argv[1][1] == 'v' && argv[1][2] == 'n' && argv[1][3] == EOS) { verbose_flag = true; noexec_flag = true; } else if (argv[1][1] == 'n') noexec_flag = true; else if (argv[1][1] == 'v') verbose_flag = true; av0p = argv[0], argv = &argv[1], argv[0] = av0p; argc--; } /* 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; notype = true; if (argv[1][1] == 'c' && argc > 2) { shtype = ST_ONELINE; notype = false; no_lnum = true; dolv += 1; dolc -= 1; argv2p = argv[2]; } else if (argv[1][1] == 'i') { noexec_flag = false; rcflag = DO_SYSTEM_ETSHRC; shtype = ST_INTERACTIVE; notype = false; if (!sh_on_tty()) sh_err(SH_ERR, FMT3S, getmyname(), argv[1], ERR_NOTTY); } else if (argv[1][1] == 'l') { noexec_flag = false; is_login = true; rcflag = DO_SYSTEM_LOGIN; shtype = ST_INTERACTIVE; notype = false; if (!sh_on_tty()) sh_err(SH_ERR, FMT3S, getmyname(), argv[1], ERR_NOTTY); } else if (argv[1][1] == 't') { shtype = ST_ONELINE; notype = false; no_lnum = true; } else if (argv[1][1] == 'V') usage(); /* All other conceivable options are valid. */ } else { shtype = ST_COMMANDFILE; (void)close(FD0); if (open(argv[1], O_RDONLY) != FD0) sh_err(SH_ERR,FMT3S,getmyname(),argv[1],ERR_OPEN); if (fd_type(FD0, FD_ISDIR)) goto done; } fd_free(); } else { dosigs = true; notype = true; fd_free(); if (sh_on_tty()) { noexec_flag = false; shtype = ST_INTERACTIVE; notype = false; } } 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) { if (sasignal(SIGTERM, SIG_IGN) == SIG_DFL) sig_child |= S_SIGTERM; if (rcflag == 0) { if (*argv[0] == HYPHEN) { noexec_flag = false; is_login = true; rcflag = DO_SYSTEM_LOGIN; } else rcflag = DO_SYSTEM_ETSHRC; } if (is_login) if (sasignal(SIGHUP, sighup) == SIG_IGN) (void)sasignal(SIGHUP, SIG_IGN); rc_init(&rcflag); hist_open(); } } if (SHTYPE(ST_ONELINE)) { if (noexec_flag) fd_print(FD2, "-n: noexec\n"); if (verbose_flag) fd_print(FD2, "-v: verbose\n"); (void)rpx_line(); } else { /* Read and execute any rc init files if needed. */ while (SHTYPE(ST_RCFILE)) { snamep = (char *)name; name = rcfilename; cmd_loop(!HALT); xfree(rcfilename); rcfilename = NULL; name = NULL; name = snamep; if (logout_now != 0) { logout_now = 0; if (is_login) goto logout; goto done; } rc_init(&rcflag); } /* Read and execute the shell's input. */ if (noexec_flag) fd_print(FD2, "-n: noexec\n"); if (verbose_flag) fd_print(FD2, "-v: verbose\n"); cmd_loop(!HALT); if (logout_now != 0) logout_now = 0; logout: /* Read and execute any rc logout files if needed. */ rcflag = DO_SYSTEM_LOGOUT; rc_logout(&rcflag); while (SHTYPE(ST_RCFILE)) { snamep = (char *)name; name = rcfilename; cmd_loop(!HALT); xfree(rcfilename); rcfilename = NULL; name = NULL; name = snamep; if (logout_now != 0) logout_now = 0; rc_logout(&rcflag); } } done: misc_free(); (void)varfree(0); (void)afree(NULL); return status; } /* * Print a helpful usage error upon invalid shell invocation, * and set the global status to SH_ERR. * * NOTE: A "usage error" here means an invalid argv[1] option used * (alone or combined with other shell options and arguments) * upon shell invocation. Both the original sh(1) and tsh(1) * give no usage errors upon shell invocation; we must use care, * as etsh(1) must remain compatible with the Thompson shell. */ static void usage(void) { const char *n; misc_free(); n = getmyname(); fd_print(FD2, ETSH_VERSION_URL, ETSH_VERSION, ETSH_URL); sh_err(SH_ERR, ETSH_USAGE, n, n); } /* * Deallocate everything we allocated that we must before etsh terminates. */ static void misc_free(void) { xfree(tty); tty = NULL; xfree(user); user = NULL; xfree(euid); euid = NULL; xfree(group); group = NULL; xfree(egid); egid = NULL; xfree(cwd); cwd = NULL; } /* * Read and execute command lines until EOF, or until one of * error_source or logout_now != 0 is true according to halt * or is_login. */ static void cmd_loop(bool halt) { bool gz; sh_magic(); for (error_source = gz = false; ; ) { if (PROMPT) prompt_write(); if (rpx_line() == EOF) { if (!gz) status = SH_TRUE; break; } if (halt && error_source) break; if (is_login && logout_now != 0) break; gz = true; } } /* * If verbose_flag is true, print each argument/word * in the word pointer array to the standard error. * Otherwise, do nothing. */ static void cmd_verbose(void) { char **vp; if (!verbose_flag) return; if (have_tmargs || have_tmchars) return; for (vp = word; **vp != EOL; vp++) fd_print(FD2, "%s%s", *vp, (**(vp + 1) != EOL) ? " " : ""); fd_print(FD2, FMT1S, ""); } /* * Determine whether or not the string pointed to by cmd * is a special built-in command. Return the key value. */ enum sbikey cmd_lookup(const char *cmd) { const struct sbicmd *lp, *mp, *rp; int d; for (lp = sbi, rp = &sbi[NSBICMD]; lp < rp; /* nothing */) { mp = lp + (rp - lp) / 2; if ((d = strcmp(cmd, mp->sbi_command)) == 0) return mp->sbi_key; if (d > 0) lp = mp + 1; else rp = mp; } return SBI_UNKNOWN; } /* * Read, parse, and execute (or noexec) a command line. */ static int rpx_line(void) { struct tnode *t; sigset_t nmask, omask; char *wp; linep = line; elinep = &line[LINEMAX]; wordp = word; ewordp = &word[WORDMAX]; error_message = NULL; have_tmargs = false; have_tmchars = false; nul_count = 0; tree_count = 0; do { wp = linep; if (get_word() == EOF) return EOF; } while (*wp != EOL); *wordp = NULL; cmd_verbose(); hist_write(PROMPT && wordp - word > 1); if (error_message != NULL) { #ifdef DEBUG fd_print(FD2, "%s: (&word[_WORDMAX] - wordp) == %ld, (&word[_WORDMAX] - ewordp) == %ld, (ewordp - wordp) == %ld;\n", __func__, (&word[_WORDMAX] - wordp), (&word[_WORDMAX] - ewordp), (ewordp - wordp)); fd_print(FD2, " : have_tmargs == %s;\n", have_tmargs ? "true" : "false"); fd_print(FD2, " : (&line[_LINEMAX] - linep) == %ld, (&line[_LINEMAX] - elinep) == %ld, (elinep - linep) == %ld;\n", (&line[_LINEMAX] - linep), (&line[_LINEMAX] - elinep), (elinep - linep)); fd_print(FD2, " : have_tmchars == %s;\n", have_tmchars ? "true" : "false"); #endif error(-1, error_message); return 1; } if (wordp - word > 1) { (void)sigfillset(&nmask); (void)sigprocmask(SIG_SETMASK, &nmask, &omask); t = tnp; tnp = NULL; tnp = syntax(word, wordp); (void)sigprocmask(SIG_SETMASK, &omask, NULL); if (noexec_flag) { /* or noexec, as the case may be */ if (error_message != NULL) error(-1, error_message); } else { if (error_message != NULL) error(-1, error_message); else execute(tnp, NULL, NULL); } tfree(tnp); tnp = NULL; tnp = t; } return 1; } /* * Read and parse the alias string specified by string. * Return a pointer to aword on success. * Return a pointer to NULL on error. */ static const char ** rp_alias(const char *string) { struct tnode *t; sigset_t nmask, omask; char *wp; asp = string; linep = aline; elinep = &aline[LINEMAX]; wordp = aword; ewordp = &aword[WORDMAX]; error_message = NULL; nul_count = 0; do { wp = linep; if (get_word() == EOF) { error_message = ERR_SYNTAX;/* same as: ([...] \) */ asp = NULL; return NULL; } } while (*wp != EOL); *wordp = NULL; if (error_message != NULL) { asp = NULL; return NULL; } if (wordp - aword > 1) { if (any(**aword, ";&")) { error_message = ERR_SYNTAX;/* same as: (;) or (&) */ asp = NULL; return NULL; } (void)sigfillset(&nmask); (void)sigprocmask(SIG_SETMASK, &nmask, &omask); t = NULL; t = syntax(aword, wordp); (void)sigprocmask(SIG_SETMASK, &omask, NULL); if (error_message != NULL) { tfree(t); t = NULL; asp = NULL; return NULL; } tfree(t); t = NULL; asp = NULL; return (const char **)aword; } error_message = ERR_SYNTAX;/* same as: () */ asp = NULL; return NULL; } /* * Copy a word from the standard input into the [a]line buffer, * and point to it w/ *wordp. Each copied word is represented * in [a]line as an individual `\0'-terminated string. */ static int get_word(void) { int c, c1; bool ds; *wordp++ = linep; for (;;) { switch (c = xgetc(DOLSUB)) { case EOF: return EOF; case SPC: case TAB: continue; case DQUOT:/* "..." == multi-char literal + $ substitution */ case SQUOT:/* '...' == multi-char literal */ c1 = c; ds = (c1 == DQUOT) ? DOLSUB : !DOLSUB; *linep++ = c; while ((c = xgetc(ds)) != c1) { if (c == EOF) return EOF; if (c == EOL) { if (error_message == NULL) error_message = ERR_SYNTAX; peekc = c; *linep++ = EOS; return 1; } if (c == BQUOT) { if ((c = xgetc(!DOLSUB)) == EOF) return EOF; if (c == EOL) c = SPC;/* continue line */ else { peekc = c; c = BQUOT; } } *linep++ = c; } *linep++ = c; break; case BQUOT:/* \. == one-char literal */ if ((c = xgetc(!DOLSUB)) == EOF) return EOF; if (c == EOL) continue; *linep++ = BQUOT; *linep++ = 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 1; default: peekc = c; } for (;;) { if ((c = xgetc(DOLSUB)) == EOF) return EOF; if (c == BQUOT) { if ((c = xgetc(!DOLSUB)) == EOF) return EOF; if (c == EOL) c = SPC;/* continue line */ else { *linep++ = BQUOT; *linep++ = c; continue; } } if (any(c, WORDPACK)) { peekc = c; if (any(c, QUOTPACK)) break; *linep++ = EOS; return 1; } *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 int xgetc(bool dolsub) { int c; if (peekc != EOS) { c = peekc; peekc = EOS; return c; } if (wordp + 1 >= ewordp) { #ifdef DEBUG fd_print(FD2, "%s: wordp: %p, ewordp: %p;\n", __func__, (void *)wordp, (void *)ewordp); #endif wordp -= 12; while ((c = xgetc(!DOLSUB)) != EOF && c != EOL) ; /* nothing */ wordp += 12; error_message = ERR_TMARGS; have_tmargs = true; goto geterr; } if (linep + 1 >= elinep) { #ifdef DEBUG fd_print(FD2, "%s: linep: %p, elinep: %p;\n", __func__, (void *)linep, (void *)elinep); #endif linep -= 12; while ((c = xgetc(!DOLSUB)) != EOF && c != EOL) ; /* nothing */ linep += 12; error_message = ERR_TMCHARS; have_tmchars = true; goto geterr; } getd: if (dolp != NULL) { c = *dolp++; if (c != EOS) return c; if (dolac > 0 && ++dolac < dolc) { dolp = dolv[dolac]; return SPC; } dolac = 0; dolp = NULL; } c = readc(); if (c == DOLLAR && dolsub) { c = readc(); if (c == ASTERISK) { if (dolc > 1) { dolac = 1; dolp = dolv[dolac]; } goto getd; } if ((dolp = get_dolp(c)) != NULL) goto getd; } /* Handle EOS/NUL characters as needed. */ if (c == EOS) do { if (!PROMPT) { 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 a character from the string pointed to by asp * or argv2p or from the standard input. When reading from asp * or argv2p, return the character, `\n', or EOF. When reading * from the standard input, return the character or EOF. Print * a diagnostic, and exit w/ SH_ERR (2) status on read(2) error. */ static int readc(void) { ssize_t rr; u_char c; if (asp != NULL) { if (asp == (char *)-1) return EOF; if ((c = U_CHAR(*asp++)) == EOS) { asp = (char *)-1; c = U_CHAR(EOL); } return c; } if (argv2p != NULL) { if (argv2p == (char *)-1) return EOF; if ((c = U_CHAR(*argv2p++)) == EOS) { argv2p = (char *)-1; c = U_CHAR(EOL); } return c; } if ((rr = read(FD0, &c, (size_t)1)) != 1) { if (rr == 0) return EOF; else sh_err(SH_ERR, "%s: %s - %s\n", getmyname(), ERR_READ, strerror(errno)); } return c; } /* * Return a pointer to the appropriate $ dollar value on success. * Return a NULL pointer on error. */ static const char * get_dolp(int c) { int n, r; const char *v; *dolbuf = EOS; switch (c) { case HASH: n = (dolc > 1) ? dolc - 1 : 0; r = snprintf(dolbuf, sizeof(dolbuf), "%u", (u_int)n); v = (r < 0 || r >= (int)sizeof(dolbuf)) ? NULL : dolbuf; break; case DOLLAR: r = snprintf(dolbuf, sizeof(dolbuf), "%05u", (u_int)getmypid()); v = (r < 0 || r >= (int)sizeof(dolbuf)) ? NULL : dolbuf; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': n = c - '0'; if (IS_DIGIT(n, c) && n < dolc) v = (n > 0) ? dolv[n] : name; else v = dolbuf; break; case QUESTION: r = snprintf(dolbuf, sizeof(dolbuf), "%u", (u_int)status); v = (r < 0 || r >= (int)sizeof(dolbuf)) ? NULL : dolbuf; break; case 'd': if ((v = getenv("ETSHDIR")) == NULL) v = dolbuf; break; case 'e': if ((v = getenv("EXECSHELL")) == NULL) v = dolbuf; break; case 'g': v = group; break; case 'h': if ((v = getenv("HOME")) == NULL) v = dolbuf; break; case 'i': v = egid; break; case 'k': v = euid; break; case 'm': if ((v = getenv("MANPATH")) == NULL) v = dolbuf; break; case 'p': if ((v = getenv("PATH")) == NULL) v = dolbuf; break; case 's': if ((v = getenv("SHELL")) == NULL) v = dolbuf; break; case 't': v = tty; break; case 'u': v = user; break; case 'v': r = snprintf(dolbuf, sizeof(dolbuf), "%s", ETSH_VERSION); v = (r < 0 || r >= (int)sizeof(dolbuf)) ? NULL : dolbuf; break; case 'w': get_set_cwd(0, false, NULL); v = cwd; break; default: v = varget(c); } return v; } /* * Get and set the global cwd (aka current working directory), and * setenv CWD to the same value. On success, both equal the absolute * pathname. On error, cwd equals the empty string, and we remove CWD * from the environment. * * NOTE: * This function is called multiple times via different contexts, * most notably, each time the shell substitutes $w with the * current working directory. Otherwise, via the "cd" (SBI_CD), * "chdir" (SBI_CHDIR), and "prompt" (SBI_PROMPT) special * built-in (sbi) commands.. Not to mention, indirectly via * the "." (SBI_DOT) and "source" (SBI_SOURCE) sbi commands... * * If call_do_prompt is true, call it. If false, do not call it. * Regarding key and previous_key: The story is... We want to avoid * a useless/wasteful call to this function if possible, but we would * minimally like to return from it quickly if/when context allows it. */ static void get_set_cwd(enum sbikey key, bool call_do_prompt, char **av) { char *b, buf[PATHMAX + 1]; char *m; bool e; static enum sbikey previous_key; b = m = NULL; e = false; if ((previous_key == SBI_CD || previous_key == SBI_CHDIR) && (key == SBI_PROMPT || key == 0)) { previous_key = key; return; } xfree(cwd); cwd = NULL; errno = 0; b = getcwd(buf, sizeof(buf)); if (errno != 0) { e = true; m = strerror(errno); } cwd = xstrdup((b != NULL) ? b : ""); if (e && b == NULL) unsetenv("CWD"); else if (setenv("CWD", cwd, 1) == -1) error(ESTATUS, ERR_NOMEM); if (call_do_prompt) do_prompt(key, av); previous_key = key; if (e) error(-1, m); } /* * Allocate memory for new variable node (specified by name and value) * if needed, and link it to variable node list. Insert new variable, * or replace old variable value w/ new variable value if needed. */ static void varalloc(int name, const char *value) { struct vnode *n, *p, *v; int d; if (name == 0 || value == NULL) return; v = vnp; if (v == NULL) { /* First */ vnp = varalloc1(name, value); return; } /* Do ascending ASCII sort. */ p = v; while (v != NULL) { if ((d = (name - v->name)) == 0) { if (!EQUAL(value, v->value)) { /* Replace old value w/ new value. */ xfree(v->value); v->value = xstrdup(value); } return; } if (d < 0) break; p = v; v = v->next; } /* Do ascending ASCII insert. */ if (v == NULL) { /* Last */ p->next = varalloc1(name, value); return; } if (v == vnp) { /* New Head */ n = varalloc1(name, value); n->next = vnp; vnp = n; return; } /* Insert new variable between p and v. */ n = varalloc1(name, value); n->next = p->next; p->next = n; } /* * Allocate and initialize memory for new variable node * specified by name and value. Return pointer to new * node on success. Do not return on ENOMEM error. */ static struct vnode * varalloc1(int name, const char *value) { struct vnode *v; v = xmalloc(sizeof(struct vnode)); v->next = NULL; v->name = name;/* one ASCII char - enforced by IS_VARNAME() macro */ v->value = xstrdup(value); return v; } /* * If name is specified (is not 0), free its variable. * If name is not specified (is 0), free all variables. * Return true (1) or false (0) as needed. */ static bool varfree(int name) { struct vnode *p, *v; bool r; if (name != 0) { v = vnp; p = v; r = false; while (v != NULL) { if (name == v->name) { if (v == vnp) vnp = v->next; else p->next = v->next; v->name = 0; xfree(v->value); xfree(v); r = true; break; } p = v; v = v->next; } } else { r = (vnp != NULL) ? true : false; v = vnp; while (v != NULL) { p = v; v->name = 0; xfree(v->value); v = v->next; xfree(p); } vnp = NULL; } return r; } /* * Get the variable value specified by name. * Return a pointer to the variable value on success. * Return a pointer to NULL on no-such-name error. */ const char * varget(int name) { struct vnode *v; const char *vv; if (name == 0) return NULL; vv = NULL; v = vnp; while (v != NULL) { if (name == v->name) { vv = v->value; break; } v = v->next; } return vv; } /* * Allocate memory for new alias node (specified by name and string) * if needed, and link it to alias node list. Insert new alias, or * replace old alias string w/ new alias string if needed. */ static void aalloc(const char *name, const char *string) { struct anode *a, *n, *p; int d; if (name == NULL || *name == EOS || string == NULL || *string == EOS) return; a = anp; if (a == NULL) { /* First */ anp = aalloc1(name, string); return; } /* Do ascending ASCII sort. */ p = a; while (a != NULL) { if ((d = strcmp(name, a->name)) == 0) { if (!EQUAL(string, a->string)) { /* Replace old string w/ new string. */ xfree(a->string); a->string = xstrdup(string); } return; } if (d < 0) break; p = a; a = a->next; } /* Do ascending ASCII insert. */ if (a == NULL) { /* Last */ p->next = aalloc1(name, string); return; } if (a == anp) { /* New Head */ n = aalloc1(name, string); n->next = anp; anp = n; return; } /* Insert new alias between p and a. */ n = aalloc1(name, string); n->next = p->next; p->next = n; } /* * Allocate and initialize memory for new alias node * specified by name and string. Return pointer to new * node on success. Do not return on ENOMEM error. */ static struct anode * aalloc1(const char *name, const char *string) { struct anode *a; a = xmalloc(sizeof(struct anode)); a->next = NULL; a->name = xstrdup(name); a->string = xstrdup(string); return a; } /* * If name is specified (is not NULL), free its alias. * If name is not specified (is NULL), free all aliases. * Return true (1) or false (0) as needed. */ static bool afree(const char *name) { struct anode *a, *p; bool r; if (name != NULL) { a = anp; p = a; r = false; while (a != NULL) { if (EQUAL(name, a->name)) { if (a == anp) anp = a->next; else p->next = a->next; xfree(a->name); xfree(a->string); xfree(a); r = true; break; } p = a; a = a->next; } } else { r = (anp != NULL) ? true : false; a = anp; while (a != NULL) { p = a; xfree(a->name); xfree(a->string); a = a->next; xfree(p); } anp = NULL; } return r; } /* * Get the alias string specified by name. * Return a pointer to the alias string on success. * Return a pointer to NULL on no-such-name error. */ static const char * asget(const char *name) { struct anode *a; const char *as; if (name == NULL) return NULL; as = NULL; a = anp; while (a != NULL) { if (EQUAL(name, a->name)) { as = a->string; break; } a = a->next; } return as; } /* * 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->nkey = 0; 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->nfin); xfree(t->nfout); vfree(t->nav); break; case TSUBSHELL: xfree(t->nfin); xfree(t->nfout); 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; static int alcnt; int ac, c, n, subcnt; size_t i, s; char **p, **pv, **lp, **rp; char **tav, **tavp; const char **av; char *fin, *fout; const char *as; pv = xmalloc(((p2 - p1) + 1) * sizeof(char *)); pv[0] = NULL; 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 = xstrdup(*p); } else { if (fout != NULL) goto syn3err; fout = xstrdup(*p); } } continue; default: if (subcnt == 0) pv[n++] = xstrdup(*p); } if (lp == NULL) { if (n == 0) goto syn3err; if ((as = asget(pv[0])) != NULL && asp == NULL) { /* * Substitute alias string pointed to by as for * alias name pointed to by pv[0]. Read, parse, * allocate, and copy it into tav. Execute as * TSUBSHELL w/o ( ) . */ if (error_message != NULL) goto syn3err; #ifdef DEBUG_ALIAS fd_print(FD2, "%s: alcnt == %d;\n", __func__, alcnt); #endif if (alcnt > ALIASMAX) { error_message = ERR_ALIASTMNA; goto syn3err; } if ((av = rp_alias(as)) == NULL) goto syn3err; ac = vacount(av); tav = xmalloc((ac + n) * sizeof(char *)); tavp = tav; #ifdef DEBUG_ALIAS fd_print(FD2, " : (%d + %d) == %d;\n",ac,n,(ac+n)); fd_print(FD2, " : av : %p;\n", (void *)av); fd_print(FD2, " : tav : %p;\n", (void *)tav); #endif for (ac = 0; *av[ac] != EOL; ac++) *tavp++ = xstrdup(av[ac]); for (ac = 1; ac < n; ac++) *tavp++ = xstrdup(pv[ac]); *tavp++ = xstrdup("\n"); *tavp = NULL; #ifdef DEBUG_ALIAS for (tavp = tav; *tavp != NULL; tavp++) fd_print(FD2, " : tavp: %p, %p, %s;\n", (void *)tavp, (void *)*tavp, (**tavp==EOL) ? "\\n" : *tavp); fd_print(FD2, " : tavp: %p, NULL;\n", (void *)tavp); fd_print(FD2," : (tavp - tav) == %ld;\n",(tavp-tav)); #endif alcnt++; if ((t = talloc()) == NULL) goto syn3err; t->ntype = TSUBSHELL; t->nsub = syn1(tav, tavp); vfree(tav); alcnt--; #ifdef DEBUG_ALIAS fd_print(FD2," : double check alcnt == %d;\n",alcnt); #endif } else { /* * Execute as TCOMMAND. */ if ((t = talloc()) == NULL) goto syn3err; t->ntype = TCOMMAND; /* Check for size_t overflow. */ i = (n + 1); s = sizeof(char *); SIZE_T_EOVERFLOW(i, s); t->nav = xmalloc(i * s); for (ac = 0; ac < n; ac++) t->nav[ac] = xstrdup(pv[ac]); t->nav[ac] = NULL; t->nkey = cmd_lookup(t->nav[0]); } } else { /* * Execute as TSUBSHELL w/ ( ) . */ 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; pv[n] = NULL; vfree(pv); return t; syn3err: xfree(fin); xfree(fout); pv[n] = NULL; vfree(pv); if (error_message == NULL) error_message = ERR_SYNTAX; return NULL; } /* * 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; } /* * Return true (1) if any argument in the argument vector pointed to by vp * contains any unquoted glob characters. Otherwise, return false (0). */ static bool vtglob(char **vp) { char **p; for (p = vp; *p != NULL; p++) if (gchar(*p) != NULL) return true; return false; } /* * Remove any unquoted quote characters from each argument * in the argument vector pointed to by vp. */ static void vtrim(char **vp) { char **p; for (p = vp; *p != NULL; p++) (void)atrim(U_CPTR(*p), false); } /* * Return number of arguments in argument vector pointed to by vp. */ static int vacount(const char **vp) { const char **p; for (p = vp; *p != NULL; p++) ; /* nothing */ return (int)(p - vp); } /* * Try to 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 pfd[2]; 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) { error(-1, 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 */ error1(-1, __func__, "Invalid command"); return; } if (vtglob(t->nav)) { if ((t->nav = (char **)glob(t->nkey, t->nav)) == NULL) return; } else if (DO_TRIM(t->nkey)) vtrim(t->nav); switch (t->nkey) { case SBI_ECHO: break; case SBI_EXEC: /* * Replace the current shell w/ an instance of * the specified external command. * * usage: exec command [arg ...] */ if (t->nav[1] == NULL) { error1(-1, t->nav[0], ERR_ARGCOUNT); return; } if ((t->nkey = cmd_lookup(t->nav[1])) != SBI_UNKNOWN) { error2(-1, t->nav[0], t->nav[1], ERR_EXEC); return; } t->nav++; t->nflags |= FNOFORK; (void)sasignal(SIGCHLD, SIG_IGN); break; case SBI_FD2: case SBI_GOTO: case SBI_IF: case SBI_PWD: case SBI_UNKNOWN: break; default: execute1(t); return; } /*FALLTHROUGH*/ case TSUBSHELL: f = t->nflags; if ((cpid = ((f & FNOFORK) != 0) ? 0 : fork()) == -1) { error(-1, 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) { #ifdef DEBUG_PROC fd_print(FD2,"%s: pwait(%d);\n",__func__,cpid); #endif pwait(cpid); } return; } /**** Child! ****/ execute2(t, pin, pout); /*NOTREACHED*/ } } /* * Try to execute the special built-in command which is specified by the * t->nkey and t->nav fields in the shell command tree pointed to by t. */ static void execute1(struct tnode *t) { struct anode *a; struct vnode *v; mode_t m; int ac; const char *emsg, *p; bool aok; if (t->nav == NULL || t->nav[0] == NULL) { /* should never (but can) be true */ error1(-1, __func__, "Invalid command"); return; } switch (t->nkey) { case SBI_NULL: /* * Do nothing and set the exit status to zero. */ status = SH_TRUE; return; case SBI_ALIAS: /* * If both name and string are specified, set alias name * to alias string. If no string is specified, print the * current value of alias name's string. If no arguments * are specified, print each alias name and string. * * NOTE: Each alias string value is printed as (value) * to indicate that the shell executes each alias * in a subshell. * * usage: alias [name [string]] */ if (t->nav[1] != NULL) { if (t->nav[2] != NULL && t->nav[3] != NULL) { emsg = ERR_ARGCOUNT; break; } aok = true; for (p = t->nav[1]; *p != EOS; p++) if (any(*p, BANCHARS)) { aok = false; break; } if (!aok || *t->nav[1] == EOS || cmd_lookup(t->nav[1]) != SBI_UNKNOWN) { error2(-1, t->nav[0], t->nav[1], ERR_BADNAME); return; } if (t->nav[2] != NULL) { if (rp_alias(t->nav[2]) == NULL) { emsg = error_message; break; } aalloc(t->nav[1], t->nav[2]); status = SH_TRUE; } else { if ((p = asget(t->nav[1])) != NULL) fd_print(FD1, "(%s)\n", p); status = (p != NULL) ? SH_TRUE : SH_FALSE; } } else { a = anp; while (a != NULL) { fd_print(FD1, "%s\t(%s)\n", a->name, a->string); a = a->next; } status = (anp != NULL) ? SH_TRUE : SH_FALSE; } return; case SBI_CD: case SBI_CHDIR: /* * Change the shell's current working directory. */ do_chdir(t->nkey, t->nav); return; case SBI_EXIT: /* * If the shell is invoked w/ the `-c' or `-t' option, or is * executing an rc file, exit the shell outright if it is not * sourcing another file in the current context. Otherwise, * cause the shell to cease execution of a file of commands * by seeking to the end of the file (and explicitly exiting * the shell only if the file is not being sourced). */ if (!PROMPT) { if (SHTYPE(ST_ONELINE|ST_RCFILE) && !SHTYPE(ST_SOURCE)) EXIT(status); (void)lseek(FD0, (off_t)0, SEEK_END); if (!SHTYPE(ST_SOURCE)) EXIT(status); } return; case SBI_LOGIN: case SBI_NEWGRP: /* * Replace the current interactive shell w/ an * instance of login(1) or newgrp(1). */ if (PROMPT) { p = (t->nkey == SBI_LOGIN) ? PATH_LOGIN : PATH_NEWGRP; (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); } emsg = ERR_EXEC; break; case SBI_PROMPT: /* * Personal, non-default, etsh prompt * * Set the global prompt_on to true -> on or false -> * off, or print current value. Also, set the global * prompt_dbg to true -> debug(ging is on), or print * current value. * * It is only executable if/when the current shell is: * *) interactive; * *) interactive, in rc file; * the following bypass prompt in favor of doing a * manual . $h/.etsh.prompt (normal before prompt): * *) interactive, doing . $h/.etsh.prompt; * *) interactive, in rc file, doing . $h/.etsh.prompt; * * usage: prompt [on | debug | off] */ do_prompt(t->nkey, t->nav); return; case SBI_SET: /* * If both name and value are specified, set variable * name to variable value. If no value is specified, * print the current value of variable name. If no * arguments are specified, print the name and value * of each variable. * * usage: set [name [value]] */ if (t->nav[1] != NULL) { if (t->nav[2] != NULL && t->nav[3] != NULL) { emsg = ERR_ARGCOUNT; break; } if (!IS_VARNAME(t->nav[1])) { error2(-1, t->nav[0], t->nav[1], ERR_BADNAME); return; } if (t->nav[2] != NULL) { varalloc(*t->nav[1], t->nav[2]); status = SH_TRUE; } else { if ((p = varget(*t->nav[1])) != NULL) fd_print(FD1, "%s\n", p); status = (p != NULL) ? SH_TRUE : SH_FALSE; } } else { v = vnp; while (v != NULL) { fd_print(FD1, "%c\t%s\n", v->name, v->value); v = v->next; } status = (vnp != NULL) ? SH_TRUE : SH_FALSE; } return; case SBI_SETENV: /* * Set the specified environment variable name. * * usage: setenv name value */ if (t->nav[1]!=NULL && t->nav[2]!=NULL && t->nav[3]==NULL) { for (p = t->nav[1]; *p != '=' && *p != EOS; p++) ; /* nothing */ if (*t->nav[1] == EOS || *p == '=') { error2(-1, t->nav[0], t->nav[1], ERR_BADNAME); return; } if (setenv(t->nav[1], t->nav[2], 1) == -1) error(ESTATUS, ERR_NOMEM); status = SH_TRUE; return; } emsg = ERR_ARGCOUNT; break; case SBI_SHIFT: /* * Shift all positional-parameter values to the left by 1. * The value of $0 does not shift. */ if (dolc > 1) { dolv = &dolv[1]; dolc--; status = SH_TRUE; return; } emsg = ERR_NOARGS; break; case SBI_DOT: case SBI_SOURCE: /* * Read and execute commands from file and return. * * usage: source (or .) file [arg1 ...] */ if (t->nav[1] != NULL) { do_source(t->nkey, t->nav); return; } emsg = ERR_ARGCOUNT; break; case SBI_TRAP: /* * Ignore (or unignore) the specified signals, or * print a list of those signals which are ignored * because of a previous invocation of `trap' in * the current shell. * * usage: trap ['' | : | - signal_number ...] */ do_trap(t->nav, t->nflags); return; case SBI_UMASK: /* * Set the file creation mask to the specified * octal value, or print its current value. * * usage: umask [mask] */ if (t->nav[1] != NULL && t->nav[2] != NULL) { emsg = ERR_ARGCOUNT; break; } if (t->nav[1] == NULL) { (void)umask(m = (mode_t)umask(0)); fd_print(FD1, "%04o\n", (u_int)m); } else { m = 0; for (p = t->nav[1]; *p >= '0' && *p <= '7'; p++) m = m * 8 + (*p - '0'); if (*t->nav[1] == EOS || *p != EOS || m > 0777) { error2(-1, t->nav[0], t->nav[1], ERR_BADMASK); return; } (void)umask(m); } status = SH_TRUE; return; case SBI_UNALIAS: /* * Unset each of the specified alias names. * * usage: unalias name ... */ if (t->nav[1] != NULL) { for (ac = 1; t->nav[ac] != NULL; ac++) { aok = true; for (p = t->nav[ac]; *p != EOS; p++) if (any(*p, BANCHARS)) { aok = false; break; } if (!aok || *t->nav[ac] == EOS || cmd_lookup(t->nav[ac]) != SBI_UNKNOWN) { error2(-1, t->nav[0], t->nav[ac], ERR_BADNAME); continue; } status = (afree(t->nav[ac])) ? SH_TRUE : SH_FALSE; } return; } emsg = ERR_ARGCOUNT; break; case SBI_UNSET: /* * Unset each of the specified variable names. * * usage: unset name ... */ if (t->nav[1] != NULL) { for (ac = 1; t->nav[ac] != NULL; ac++) { if (!IS_VARNAME(t->nav[ac])) { error2(-1, t->nav[0], t->nav[ac], ERR_BADNAME); continue; } status = (varfree(*t->nav[ac])) ? SH_TRUE : SH_FALSE; } return; } emsg = ERR_ARGCOUNT; break; case SBI_UNSETENV: /* * Unset each of the specified environment variable names. * * usage: unsetenv name ... */ if (t->nav[1] != NULL) { for (ac = 1; t->nav[ac] != NULL; ac++) { for (p = t->nav[ac]; *p != '=' && *p != EOS; p++) ; /* nothing */ if (*t->nav[ac] == EOS || *p == '=') { error2(-1, t->nav[0], t->nav[ac], ERR_BADNAME); continue; } unsetenv(t->nav[ac]); status = SH_TRUE; } return; } emsg = ERR_ARGCOUNT; break; case SBI_VERBOSE: /* * Set the global verbose_flag to true or false, * or print its current value. * * usage: verbose [true | false] */ if (t->nav[1] != NULL && t->nav[2] == NULL) { if (EQUAL(t->nav[1], "true")) { status = SH_TRUE; verbose_flag = true; } else if (EQUAL(t->nav[1], "false")) { status = SH_FALSE; verbose_flag = false; } else sh_err(-1, VERBOSE_USAGE); } else if (t->nav[1] == NULL) { status = verbose_flag ? SH_TRUE : SH_FALSE; fd_print(FD1, FMT1S, verbose_flag ? "true" : "false"); } else sh_err(-1, VERBOSE_USAGE); return; case SBI_WAIT: /* * Wait for all asynchronous processes to terminate, * reporting on abnormal terminations. */ pwait(-1); return; default: emsg = ERR_EXEC; } error1(-1, t->nav[0], emsg); } /* * Try to execute the child process which is specified by combination * of the t->nsub, t->nflags, t->nav, t->nfin, and t->nfout fields in * the shell command tree pointed to by t. */ static void execute2(struct tnode *t, int *pin, int *pout) { struct tnode *t1; enum tnflags f; int i; f = t->nflags; /* * Redirect (read) input from pipe. */ if (pin != NULL && (f & FPIN) != 0) { if (dup2(pin[0], FD0) == -1) error(FC_ERR, 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) error(FC_ERR, strerror(errno)); (void)close(pout[0]); (void)close(pout[1]); } /* * Redirect (read) input from file. */ if (t->nfin != NULL && (f & FPIN) == 0) { f |= FFIN; i = 0; if (*t->nfin == HYPHEN && *(t->nfin + 1) == EOS) i = 1; if (i != 0) i = dup(dupfd0); else i = open(atrim(U_CPTR(t->nfin), false), O_RDONLY); if (i == -1) error1(FC_ERR, t->nfin, ERR_OPEN); if (dup2(i, FD0) == -1) error(FC_ERR, 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; if ((i = open(atrim(U_CPTR(t->nfout), false), i, 0666)) == -1) error1(FC_ERR, t->nfout, ERR_CREATE); if (dup2(i, FD1) == -1) error(FC_ERR, 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) error1(FC_ERR, "/dev/null", ERR_OPEN); } } else { if ((sig_state&S_SIGINT) == 0 && (sig_child&S_SIGINT) != 0) (void)sasignal(SIGINT, SIG_DFL); if ((sig_state&S_SIGQUIT) == 0 && (sig_child&S_SIGQUIT) != 0) (void)sasignal(SIGQUIT, SIG_DFL); } /* Set the SIGTERM signal to its default action if needed. */ if ((sig_state&S_SIGTERM) == 0 && (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) { /* should never (but can) be true */ error1(FC_ERR, __func__, "Invalid command"); /*NOTREACHED*/ } if (t->nkey == SBI_UNKNOWN) (void)pexec_err(t->nav[0], (char *const *)t->nav); else _exit(uexec(t->nkey, vacount((const char **)t->nav), t->nav)); /*NOTREACHED*/ } /* * Change the shell's current working directory. Not to state the * obvious, but please notice that `cd' is a synonym for `chdir'. * * `chdir' Changes to the user's home directory. * `chdir -' Changes to the previous working directory. * `chdir dir' Changes to the directory specified by `dir'. * * NOTE: The user must have both read and search permission on * a directory in order for `chdir -' to be effective. */ static void do_chdir(enum sbikey key, char **av) { const char *emsg, *home; int cwdl; static int pwd = -1; emsg = ERR_BADDIR; cwdl = open(".", O_RDONLY | O_NONBLOCK); if (av[1] == NULL) { home = getenv("HOME"); if (home == NULL || *home == EOS) { emsg = ERR_NOHOMEDIR; goto chdirerr; } if (chdir(home) == -1) goto chdirerr; get_set_cwd(key, (PROMPT) ? true : false, av); } else if (EQUAL(av[1], "-")) { if (pwd == -1) { emsg = ERR_NOPWD; goto chdirerr; } if (!fd_type(pwd, FD_ISDIROK) || fchdir(pwd) == -1) { if (close(pwd) != -1) pwd = -1; goto chdirerr; } get_set_cwd(key, (PROMPT) ? true : false, av); } else if (chdir(atrim(U_CPTR(av[1]), false)) == -1) goto chdirerr; else get_set_cwd(key, (PROMPT) ? true : false, av); if (cwdl != -1) { if (cwdl != PWD && (pwd = dup2(cwdl, PWD)) == PWD) (void)fcntl(pwd, F_SETFD, FD_CLOEXEC); (void)close(cwdl); } else if (close(pwd) != -1) pwd = -1; status = SH_TRUE; return; chdirerr: if (cwdl != -1) (void)close(cwdl); error1(-1, av[0], emsg); } /* * Ignore (or unignore) the specified signals, or print a list of * those signals which are ignored because of a previous invocation * of `trap' in the current shell. */ static void do_trap(char **av, enum tnflags f) { struct sigaction act, oact; sigset_t new_mask, old_mask; action_type sighand; long int signol; int i, sigerr, signo; static bool ignlst[NSIG], gotlst; /* Temporarily block all signals in this function. */ (void)sigfillset(&new_mask); (void)sigprocmask(SIG_SETMASK, &new_mask, &old_mask); if (!gotlst) { /* Initialize the list of already ignored signals. */ for (i = 1; i < NSIG; i++) { ignlst[i - 1] = false; if (sigaction(i, NULL, &oact) < 0) continue; if (oact.sa_handler == SIG_IGN) ignlst[i - 1] = true; } gotlst = true; } sigerr = 0; if (av[1] != NULL) { if (av[2] == NULL) { sigerr = 1; goto sigdone; } (void)memset(&act, 0, sizeof(act)); if (EQUAL(av[1], "") || EQUAL(av[1], ":")) act.sa_handler = SIG_IGN; else if (EQUAL(av[1], "-")) act.sa_handler = SIG_DFL; else { sigerr = 1; goto sigdone; } (void)sigemptyset(&act.sa_mask); act.sa_flags = 0; for (i = 2; av[i] != NULL; i++) { if (!strtoint(av[i], &signol) || signol <= 0 || signol >= NSIG) { sigerr = i; goto sigdone; } signo = (int)signol; act.sa_flags |= SA_RESTART; /* Do nothing if no signal changes are needed. */ if (sigaction(signo, NULL, &oact) < 0 || (act.sa_handler == SIG_DFL && oact.sa_handler == SIG_DFL)) continue; /* Set up already ignored signal if needed. */ if (ignlst[signo - 1]) { set_ss_flags(signo, act.sa_handler); if (act.sa_handler == SIG_DFL) continue; } /* Reinstall SIGHUP signal handler if needed. */ if (is_login&&signo==SIGHUP&&act.sa_handler==SIG_DFL) { if (oact.sa_handler == sighup) continue; /*fd_print(FD2, "(reinstall sighup now)\n");*/ sighand = sighup; if (sasignal(signo, sighand) == SIG_ERR) { /*fd_print(FD2, "(no, it failed)\n");*/ sigerr = signo; goto sigdone; } /*fd_print(FD2, "(yes, it succeeded)\n");*/ continue; } /* * Trying to ignore SIGKILL, SIGSTOP, and/or SIGCHLD * has no effect. */ if (signo == SIGKILL || signo == SIGSTOP || signo == SIGCHLD) continue; sighand = act.sa_handler; if (sasignal(signo, sighand) == SIG_ERR) { fd_print(FD2, "%s: strerror(%d) == %s;\n", __func__, errno, strerror(errno));/**/ continue; } set_ss_flags(signo, sighand); } } else { /* Print signals currently ignored because of `trap'. */ for (i = 1; i < NSIG; i++) { if (sigaction(i, NULL, &oact) < 0 || oact.sa_handler != SIG_IGN) continue; if (!ignlst[i - 1] || ((f & FINTR) == 0 && ((i == SIGINT && (sig_state & S_SIGINT) != 0 && (sig_child & S_SIGINT) != 0) || (i == SIGQUIT && (sig_state & S_SIGQUIT) != 0 && (sig_child & S_SIGQUIT) != 0))) || (i == SIGTERM && (sig_state & S_SIGTERM) != 0 && (sig_child & S_SIGTERM) != 0)) fd_print(FD1, "%s : %2u\n", av[0], (u_int)i); } } sigdone: (void)sigprocmask(SIG_SETMASK, &old_mask, NULL); if (sigerr == 0) status = SH_TRUE; else if (sigerr == 1) error1(-1, av[0], ERR_GENERIC); else error2(-1, av[0], av[sigerr], ERR_BADSIGNAL); } /* * Set global sig_state flags according to signal sig and action act. */ static void set_ss_flags(int sig, action_type act) { if (act == SIG_IGN) { if (sig == SIGINT) sig_state |= S_SIGINT; else if (sig == SIGQUIT) sig_state |= S_SIGQUIT; else if (sig == SIGTERM) sig_state |= S_SIGTERM; } else if (act == SIG_DFL) { if (sig == SIGINT) sig_state &= ~S_SIGINT; else if (sig == SIGQUIT) sig_state &= ~S_SIGQUIT; else if (sig == SIGTERM) sig_state &= ~S_SIGTERM; } } /* * Check SHTYPE & notype for permission to continue, or error if needed. * Check for correct usage if key == SBI_PROMPT. Process keyed calls as * appropriate. Build argument vector to source (.) the prompt file, and * then call do_source() among other things. Handle errors as needed. * * The prompt special built-in command expects that $h/.etsh.prompt * (aka FILE_DOT_PROMPT) is installed and available as a readable, * regular file (or a link to a regular file). */ static void do_prompt(enum sbikey key, char **av) { char path[PATHMAX]; char **pav; const char *emsg, *home; int len; bool pusage; pusage = false; if (!SHTYPE(ST_INTERACTIVE|ST_RCFILE|ST_SOURCE) || SHTYPE(ST_ONELINE|ST_COMMANDFILE) || notype) { emsg = ERR_EXEC; goto prompterr; } if (av == NULL) { /* appease splint(1) - should never (but can) be true */ error1(ESTATUS, __func__, strerror(EINVAL)); /*NOTREACHED*/ } /* * Check for correct usage, and handle other actions, * if/when called by prompt. */ if (key == SBI_PROMPT) { if (av[1] != NULL && av[2] == NULL) { if (EQUAL(av[1], "on")) { status = SH_TRUE; prompt_on = true; prompt_dbg = false; } else if (EQUAL(av[1], "debug")) { if (!prompt_on) prompt_on = true; prompt_dbg = true; } else if (EQUAL(av[1], "off")) { status = SH_FALSE; prompt_on = prompt_dbg = false; return; } else { pusage = true; goto prompterr; } } else if (av[1] == NULL) { status = prompt_on ? SH_TRUE : SH_FALSE; if (prompt_on && !prompt_dbg) fd_print(FD1, FMT1S, "on"); if (prompt_dbg) fd_print(FD1, FMT1S, "debug"); if (!prompt_on) { fd_print(FD1, FMT1S, "off"); return; } } else { pusage = true; goto prompterr; } } home = getenv("HOME"); if (home != NULL && *home != EOS) { *path = EOS; len = snprintf(path,sizeof(path),"%s/%s",home,FILE_DOT_PROMPT); if (len < 0 || len >= (int)sizeof(path)) { *path = EOS; emsg = "Error building prompt preprocessor path name"; goto prompterr; } /* Build prompt argument vector for do_source(key, pav). */ pav = NULL; pav = xmalloc((2 + 1) * sizeof(char *)); pav[0] = xstrdup("."); pav[1] = xstrdup(path); pav[2] = NULL; /* Continue, clean up, and return. */ get_set_cwd(key, false, av); do_source(key, pav); vfree(pav); pav = NULL; status = SH_TRUE; return; } else emsg = ERR_NOHOMEDIR; prompterr: if (pusage) sh_err(-1, PROMPT_USAGE); else error1(-1, av[0], emsg); } /* * Read and execute commands from the file av[1] and return. * Calls to this function may be nested either to the point imposed by * SOURCEMAX (64) or to that of any limits in the shell's environment, * such as running out of file descriptors, hitting a limit on the * size of the stack, etc. */ static void do_source(enum sbikey key, char **av) { static const char *sargv2p; const char *sname; char *const *sdolv; int e, nfd, sfd, sdolc; static int cnt; /*fd_print(FD2, " : key == %d...\n", key);*/ if ((nfd = source_open(av[0], av[1])) == -1) { e = errno; if (key == SBI_DOT || key == SBI_SOURCE) error2(-1, av[0], av[1], (e != ENOENT && e != ENOTDIR) ? ERR_OPEN : ERR_NOTFOUND); else { if (e != ENOENT && e != ENOTDIR) error2(-1, av[0], av[1], ERR_OPEN); else if (prompt_dbg) error2(-1, av[0], av[1], ERR_NOTFOUND); else sh_err(-1, NULL); } return; } /*fd_print(FD2, " : nfd == %d;\n", nfd);*/ if (nfd >= SAVFD0 || !fd_type(nfd, FD_ISREG)) { (void)close(nfd); error2(-1, av[0], av[1], ERR_EXEC); return; } sfd = dup2(FD0, SAVFD0 + cnt); if (cnt > SOURCEMAX || sfd == -1 || dup2(nfd, FD0) == -1 || fcntl(FD0, F_SETFL, O_RDONLY & ~O_NONBLOCK) == -1) { (void)close(sfd); (void)close(nfd); error2(-1, av[0], av[1], strerror(EMFILE)); return; } (void)fcntl(sfd, F_SETFD, FD_CLOEXEC); (void)close(nfd); if (!SHTYPE(ST_SOURCE)) { shtype |= ST_SOURCE; sargv2p = argv2p; argv2p = NULL; } /* Save and initialize any positional parameters. */ sname = name, sdolv = dolv, sdolc = dolc; name = av[1], dolv = &av[1]; for (dolc = 0; dolv[dolc] != NULL; dolc++) ; /* nothing */ cnt++; cmd_loop(HALT); cnt--; /* Restore any saved positional parameters. */ name = sname, dolv = sdolv, dolc = sdolc; if (error_source || (is_login && logout_now != 0)) { /* * Terminate any and all source commands (nested or not). * Restore original standard input before returning for * the final time, and call sh_err() if needed. */ if (cnt == 0) { /* Restore original standard input or die trying. */ if (dup2(SAVFD0, FD0) == -1) error2(ESTATUS, av[0], av[1], strerror(errno)); (void)close(SAVFD0); shtype &= ~ST_SOURCE; argv2p = sargv2p; sargv2p = NULL; if (!SHTYPE(ST_RCFILE)) sh_err(0, NULL); return; } (void)close(sfd); return; } /* Restore previous standard input or die trying. */ if (dup2(sfd, FD0) == -1) { (void)close(sfd); error2(ESTATUS, av[0], av[1], strerror(errno)); return; } (void)close(sfd); if (cnt == 0) { shtype &= ~ST_SOURCE; argv2p = sargv2p; sargv2p = NULL; } } /* * Open the specified file (or path name) for the specified `.' or * `source' special built-in command. Return the file descriptor * fd >= 3 (as etsh has already opened fd 0, 1, & 2; in any case, * fd != -1;) for the open file on success. * Return fd == -1 on error. * * Originally derived from: * - pexec() in osh-20120102/pexec.c */ static int source_open(const char *command, const char *file) { size_t dlen, flen; #ifdef DEBUG int e; #endif int fd, eacces = 0; const char *d, *f, *pnp, *upp; char pnbuf[PATHMAX]; /* Fail if the value of file is invalid. */ errno = 0; if (*file == EOS) { errno = ENOENT; goto open_fail; } flen = strlen(file); /* * If the name of the specified file contains one or more * `/' characters, it is used as the path name to source. */ for (f = file; *f != EOS; f++) if (*f == SLASH) { pnp = file; upp = ""; goto source_pathname; } *pnbuf = EOS; pnp = pnbuf; /* Get the user's PATH or fail. */ upp = getenv("PATH"); if (upp == NULL || *upp == EOS) { errno = ENOENT; goto open_fail; } do { /* Find the end of this PATH element. */ for (d = upp; *upp != COLON && *upp != EOS; upp++) ; /* nothing */ /* * Since this is a shell PATH, double, leading, and/or * trailing colons indicate the current directory. */ if (d == upp) { d = "."; dlen = 1; } else dlen = (size_t)(upp - d); /* * Complain if this path name for file would be too long. * Otherwise, use this PATH element to build a possible * path name for file. Then, attempt to open(2) it. */ if (dlen + flen + 1 >= sizeof(pnbuf)) { struct iovec msg[6]; msg[0].iov_base = (char *)getmyname(); msg[0].iov_len = strlen(getmyname()); msg[1].iov_base = ": "; msg[1].iov_len = (size_t)2; msg[2].iov_base = (char *)command; msg[2].iov_len = strlen(command); msg[3].iov_base = ": "; msg[3].iov_len = (size_t)2; msg[4].iov_base = (char *)d; msg[4].iov_len = dlen; msg[5].iov_base = ": path too long\n"; msg[5].iov_len = (size_t)16; (void)writev(FD2, msg, 6); errno = ENAMETOOLONG; continue; } (void)memcpy(pnbuf, d, dlen); pnbuf[dlen] = SLASH; (void)memcpy(pnbuf + dlen + 1, file, flen); pnbuf[dlen + flen + 1] = EOS; source_pathname: if ((fd = open(pnp, O_RDONLY | O_NONBLOCK)) != -1) return fd; switch (errno) { case EISDIR: case ELOOP: case ENAMETOOLONG: case ENOENT: break; case ENOTDIR: break; case EACCES: eacces = 1; break; default: goto open_fail; } } while (*upp++ == COLON); /* Otherwise, *upp was NUL. */ if (eacces != 0) errno = EACCES; else if (errno == 0) errno = ENOENT; open_fail: #ifdef DEBUG e = errno; fd_print(FD2, "%s: strerror(%d) == %s;\n", __func__, e, strerror(e)); #endif return -1; } /* * 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(0, NULL); } } else status = SH_TRUE; if (tp == cp) break; } } /* * 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 #ifdef DEBUG_PROC fd_print(FD2, "prsig: tp == %d, cp == %d;\n", tp, cp); #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; } /* * 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 switch (es) { case -2: /* For compatibility, glob() errors are not fatal. */ status = SH_ERR; return; case -1: status = SH_ERR; /*FALLTHROUGH*/ case 0: if (SHTYPE(ST_SOURCE)) { (void)lseek(FD0, (off_t)0, SEEK_END); error_source = true; return; } if (SHTYPE(ST_RCFILE) && (status == 130 || status == 131)) { (void)lseek(FD0, (off_t)0, SEEK_END); return; } if (!SHTYPE(ST_INTERACTIVE)) { if (!SHTYPE(ST_ONELINE)) (void)lseek(FD0, (off_t)0, SEEK_END); break; } return; default: status = es; } EXIT(status); } /* * Initialize the shell. */ static void sh_init(const char *av0p) { struct passwd *pwu; struct group *grg; int fd, r; const char *p; setmyerrexit(&sh_errexit); setmyname(av0p); setmypid(getpid()); sheuid = geteuid(); shegid = getegid(); /* * Set-ID execution is not supported. */ if (sheuid != getuid() || shegid != getgid()) sh_err(SH_ERR, FMT2S, getmyname(), ERR_SETID); /* * Fail if any of the descriptors 0, 1, or 2 is not open, * or if dupfd0 cannot be set up properly. */ for (fd = 0; fd < 3; fd++) if (!fd_type(fd, FD_ISOPEN)) sh_err(SH_ERR, "%s: %u: %s\n", getmyname(), (u_int)fd, strerror(errno)); if ((dupfd0 = dup2(FD0, DUPFD0)) == -1 || fcntl(dupfd0, F_SETFD, FD_CLOEXEC) == -1) sh_err(SH_ERR, "%s: %u: %s\n", getmyname(), DUPFD0, strerror(errno)); /* Try to get the terminal name for $t. */ p = ttyname(dupfd0); tty = xstrdup((p != NULL) ? p : ""); /* Try to get the effective user name for $u. */ pwu = getpwuid(sheuid); user = xstrdup((pwu != NULL) ? pwu->pw_name : ""); /* Set $k to the effective user ID. */ r = snprintf(dolbuf, sizeof(dolbuf), "%u", (u_int)sheuid); p = (r < 0 || r >= (int)sizeof(dolbuf)) ? NULL : dolbuf; euid = xstrdup((p != NULL) ? p : ""); if (strlen(euid) == 0) /* It seems a reasonable warning message, w/o shell exit. */ fd_print(FD2,"%s: Warning - $k == empty string\n", getmyname()); /* Try to get the effective group name for $g. */ grg = getgrgid(shegid); group = xstrdup((grg != NULL) ? grg->gr_name : ""); /* Set $i to the effective group ID. */ r = snprintf(dolbuf, sizeof(dolbuf), "%u", (u_int)shegid); p = (r < 0 || r >= (int)sizeof(dolbuf)) ? NULL : dolbuf; egid = xstrdup((p != NULL) ? p : ""); if (strlen(egid) == 0) /* It seems a reasonable warning message, w/o shell exit. */ fd_print(FD2,"%s: Warning - $i == empty string\n", getmyname()); /* * 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) { size_t len; int c; is_first = true; is_noexec = false; if (fd_type(FD0, FD_ISREG) && 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) is_first = false; if (c == EOL || c == EOF) return; } error(-1, ERR_EXEC); } else (void)lseek(FD0, (off_t)0, SEEK_SET); } } /* * Return true (1) if the shell is connected to a terminal. * Otherwise, return false (0). */ static bool sh_on_tty(void) { return isatty(FD0) != 0 && isatty(FD2) != 0; } /* * Handle the SIGHUP signal by setting the volatile logout_now flag. */ static void sighup(/*@unused@*/ int signo IS_UNUSED) { logout_now = 1; } /* * Process the sequence of rc init files used by the shell. * For each call to rc_init(), temporarily assign the shell's * standard input to come from a given file in the sequence if * possible and return. When DO_INIT_DONE, restore the shell's * original standard input (or die trying), unset the ST_RCFILE * flag, and return. */ static void rc_init(int *rcflag) { char path[PATHMAX]; const char *file; while (*rcflag <= DO_INIT_DONE) { file = NULL; switch (*rcflag) { case DO_SYSTEM_LOGIN: file = PATH_SYSTEM_LOGIN; break; case DO_SYSTEM_ETSHRC: if (!is_login) (*rcflag)++; file = PATH_SYSTEM_ETSHRC; break; case DO_DOT_LOGIN: file = pn_build(path, FILE_DOT_LOGIN, sizeof(path)); break; case DO_DOT_ETSHRC: file = pn_build(path, FILE_DOT_ETSHRC, sizeof(path)); break; case DO_INIT_DONE: if (dup2(dupfd0, FD0) == -1) sh_err(SH_ERR,FMT2S,getmyname(),strerror(errno)); shtype &= ~ST_RCFILE; (*rcflag)++; return; default: shtype &= ~ST_RCFILE; return; } if (file != NULL) { rcfilename = xstrdup(file); (*rcflag)++; if (rc_open(file)) break; } else /* should never (but can) be true */ sh_err(SH_ERR,FMT3S,getmyname(),__func__,"Invalid file"); } } /* * If is_login is false, unset the ST_RCFILE flag and return. * Otherwise, process the sequence of rc logout files used by * the shell. For each call to rc_logout(), temporarily assign * the shell's standard input to come from a given file in the * sequence if possible and return. When DO_LOGOUT_DONE, unset * the ST_RCFILE flag and return. */ static void rc_logout(int *rcflag) { char path[PATHMAX]; const char *file; if (!is_login) { shtype &= ~ST_RCFILE; return; } while (*rcflag <= DO_LOGOUT_DONE) { file = NULL; switch (*rcflag) { case DO_SYSTEM_LOGOUT: file = PATH_SYSTEM_LOGOUT; break; case DO_DOT_LOGOUT: file = pn_build(path, FILE_DOT_LOGOUT, sizeof(path)); break; case DO_LOGOUT_DONE: shtype &= ~ST_RCFILE; (*rcflag)++; return; default: shtype &= ~ST_RCFILE; return; } if (file != NULL) { rcfilename = xstrdup(file); (*rcflag)++; if (rc_open(file)) break; } else /* should never (but can) be true */ sh_err(SH_ERR,FMT3S,getmyname(),__func__,"Invalid file"); } } /* * Open the rc file specified by file, and prepare it for execution. * The specified file must be readable and regular (or a link to a * regular file) for the shell to use it. Return true (1) if file * is ready for execution, or return false (0) if file cannot be * opened or executed. Do not return on dup2(2) or fcntl(2) error. */ static bool rc_open(const char *file) { int fd; if (file == NULL || *file == EOS) return false; if ((fd = open(file, O_RDONLY | O_NONBLOCK)) == -1) return false; if (!fd_type(fd, FD_ISREG)) { sh_err(-1, FMT3S, getmyname(), file, ERR_EXEC); (void)close(fd); return false; } /* NOTE: A dup2(2) or fcntl(2) error here is fatal. */ if (dup2(fd, FD0) == -1 || fcntl(FD0, F_SETFL, O_RDONLY & ~O_NONBLOCK) == -1) sh_err(SH_ERR, FMT3S, getmyname(), file, strerror(errno)); (void)close(fd); shtype |= ST_RCFILE; return true; } /* * Build a path name for the file name pointed to by file. * Write the resulting path name to the buffer pointed to by path. * The size of the buffer pointed to by path is specified by size. * Return path, which will be the empty string if the build fails. */ static char * pn_build(char *path, const char *file, size_t size) { int len; const char *home; *path = EOS; home = getenv("HOME"); if (home != NULL && *home != EOS) { len = snprintf(path, size, "%s/%s", home, file); if (len < 0 || len >= (int)size) { sh_err(-1, "%s: %s/%s: %s\n", getmyname(), home, file, strerror(ENAMETOOLONG)); *path = EOS; } } return path; } /* * If hwfd is a valid open file descriptor and hwflag is true, * write each argument/word in the word pointer array to hwfd. * Otherwise, do nothing. */ static void hist_write(bool hwflag) { char **vp; if (hwfd == -1 || !hwflag) return; if (!fd_type(hwfd, FD_ISREGOK)) { if (close(hwfd) != -1) hwfd = -1; return; } for (vp = word; **vp != EOL; vp++) fd_print(hwfd, "%s%s", *vp, (**(vp + 1) != EOL) ? " " : ""); fd_print(hwfd, FMT1S, ""); } /* * Open the user's FILE_DOT_HISTORY file for writing if possible. * Set the global hwfd to HWFD on success. * Set the global hwfd to -1 on error. */ static void hist_open(void) { char path[PATHMAX]; const char *file; int fd, fdw; hwfd = -1; file = pn_build(path, FILE_DOT_HISTORY, sizeof(path)); if ((fd = open(file, O_WRONLY | O_APPEND | O_NONBLOCK)) == -1) return; if (!fd_type(fd, FD_ISREG)) { (void)close(fd); return; } if ((fdw = dup2(fd, HWFD)) == -1 || fcntl(fdw, F_SETFL, (O_WRONLY | O_APPEND) & ~O_NONBLOCK) == -1 || fcntl(fdw, F_SETFD, FD_CLOEXEC) == -1) { (void)close(fd); (void)close(fdw); return; } (void)close(fd); hwfd = fdw; } /* * Attempt to free or release all of the file descriptors in the range * from (fd_max - 1) through (FD2 + 1), skipping DUPFD0; 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 > DUPFD0; fd--) (void)close(fd); for (fd--; fd > FD2; fd--) (void)close(fd); } /* * Check if the file descriptor fd refers to an open file * of the specified type; return true (1) or false (0). */ static bool fd_type(int fd, mode_t type) { struct stat sb; bool rv = false; if (fstat(fd, &sb) < 0) return rv; switch (type) { case FD_ISOPEN: rv = true; break; case FD_ISDIROK: if (S_ISDIR(sb.st_mode)) rv = sb.st_nlink > 0; break; case FD_ISREGOK: if (S_ISREG(sb.st_mode)) rv = sb.st_nlink > 0; break; case FD_ISDIR: case FD_ISREG: rv = (sb.st_mode & FD_TMASK) == type; break; } return rv; }