From 71c7cbab2b50436db940721a10d6ea1ca5d75f89 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Tue, 22 Dec 2020 17:37:23 -0500 Subject: Squashed 'bin/dash/' content from commit a45870f git-subtree-dir: bin/dash git-subtree-split: a45870f71f42be6bf3c8eada04debd1008f9dc3e --- src/exec.c | 908 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 908 insertions(+) create mode 100644 src/exec.c (limited to 'src/exec.c') diff --git a/src/exec.c b/src/exec.c new file mode 100644 index 00000000..87354d49 --- /dev/null +++ b/src/exec.c @@ -0,0 +1,908 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1997-2005 + * Herbert Xu . All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 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. + */ + +#include +#include +#include +#include +#include +#ifdef HAVE_PATHS_H +#include +#endif + +/* + * When commands are first encountered, they are entered in a hash table. + * This ensures that a full path search will not have to be done for them + * on each invocation. + * + * We should investigate converting to a linear search, even though that + * would make the command name "hash" a misnomer. + */ + +#include "shell.h" +#include "main.h" +#include "nodes.h" +#include "parser.h" +#include "redir.h" +#include "eval.h" +#include "exec.h" +#include "builtins.h" +#include "var.h" +#include "options.h" +#include "output.h" +#include "syntax.h" +#include "memalloc.h" +#include "error.h" +#include "init.h" +#include "mystring.h" +#include "show.h" +#include "jobs.h" +#include "alias.h" +#include "system.h" + + +#define CMDTABLESIZE 31 /* should be prime */ +#define ARB 1 /* actual size determined at run time */ + + + +struct tblentry { + struct tblentry *next; /* next entry in hash chain */ + union param param; /* definition of builtin function */ + short cmdtype; /* index identifying command */ + char rehash; /* if set, cd done since entry created */ + char cmdname[ARB]; /* name of command */ +}; + + +STATIC struct tblentry *cmdtable[CMDTABLESIZE]; +STATIC int builtinloc = -1; /* index in path of %builtin, or -1 */ + + +STATIC void tryexec(char *, char **, char **); +STATIC void printentry(struct tblentry *); +STATIC void clearcmdentry(void); +STATIC struct tblentry *cmdlookup(const char *, int); +STATIC void delete_cmd_entry(void); +STATIC void addcmdentry(char *, struct cmdentry *); +STATIC int describe_command(struct output *, char *, const char *, int); + + +/* + * Exec a program. Never returns. If you change this routine, you may + * have to change the find_command routine as well. + */ + +void +shellexec(char **argv, const char *path, int idx) +{ + char *cmdname; + int e; + char **envp; + int exerrno; + + envp = environment(); + if (strchr(argv[0], '/') != NULL) { + tryexec(argv[0], argv, envp); + e = errno; + } else { + e = ENOENT; + while (padvance(&path, argv[0]) >= 0) { + cmdname = stackblock(); + if (--idx < 0 && pathopt == NULL) { + tryexec(cmdname, argv, envp); + if (errno != ENOENT && errno != ENOTDIR) + e = errno; + } + } + } + + /* Map to POSIX errors */ + switch (e) { + default: + exerrno = 126; + break; + case ELOOP: + case ENAMETOOLONG: + case ENOENT: + case ENOTDIR: + exerrno = 127; + break; + } + exitstatus = exerrno; + TRACE(("shellexec failed for %s, errno %d, suppressint %d\n", + argv[0], e, suppressint )); + exerror(EXEND, "%s: %s", argv[0], errmsg(e, E_EXEC)); + /* NOTREACHED */ +} + + +STATIC void +tryexec(char *cmd, char **argv, char **envp) +{ + char *const path_bshell = _PATH_BSHELL; + +repeat: +#ifdef SYSV + do { + execve(cmd, argv, envp); + } while (errno == EINTR); +#else + execve(cmd, argv, envp); +#endif + if (cmd != path_bshell && errno == ENOEXEC) { + *argv-- = cmd; + *argv = cmd = path_bshell; + goto repeat; + } +} + +static const char *legal_pathopt(const char *opt, const char *term, int magic) +{ + switch (magic) { + case 0: + opt = NULL; + break; + + case 1: + opt = prefix(opt, "builtin") ?: prefix(opt, "func"); + break; + + default: + opt += strcspn(opt, term); + break; + } + + if (opt && *opt == '%') + opt++; + + return opt; +} + +/* + * Do a path search. The variable path (passed by reference) should be + * set to the start of the path before the first call; padvance will update + * this value as it proceeds. Successive calls to padvance will return + * the possible path expansions in sequence. If an option (indicated by + * a percent sign) appears in the path entry then the global variable + * pathopt will be set to point to it; otherwise pathopt will be set to + * NULL. + * + * If magic is 0 then pathopt recognition will be disabled. If magic is + * 1 we shall recognise %builtin/%func. Otherwise we shall accept any + * pathopt. + */ + +const char *pathopt; + +int padvance_magic(const char **path, const char *name, int magic) +{ + const char *term = "%:"; + const char *lpathopt; + const char *p; + char *q; + const char *start; + size_t qlen; + size_t len; + + if (*path == NULL) + return -1; + + lpathopt = NULL; + start = *path; + + if (*start == '%' && (p = legal_pathopt(start + 1, term, magic))) { + lpathopt = start + 1; + start = p; + term = ":"; + } + + len = strcspn(start, term); + p = start + len; + + if (*p == '%') { + size_t extra = strchrnul(p, ':') - p; + + if (legal_pathopt(p + 1, term, magic)) + lpathopt = p + 1; + else + len += extra; + + p += extra; + } + + pathopt = lpathopt; + *path = *p == ':' ? p + 1 : NULL; + + /* "2" is for '/' and '\0' */ + qlen = len + strlen(name) + 2; + q = growstackto(qlen); + + if (likely(len)) { + q = mempcpy(q, start, len); + *q++ = '/'; + } + strcpy(q, name); + + return qlen; +} + + + +/*** Command hashing code ***/ + + +int +hashcmd(int argc, char **argv) +{ + struct tblentry **pp; + struct tblentry *cmdp; + int c; + struct cmdentry entry; + char *name; + + while ((c = nextopt("r")) != '\0') { + clearcmdentry(); + return 0; + } + if (*argptr == NULL) { + for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) { + for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { + if (cmdp->cmdtype == CMDNORMAL) + printentry(cmdp); + } + } + return 0; + } + c = 0; + while ((name = *argptr) != NULL) { + if ((cmdp = cmdlookup(name, 0)) && + (cmdp->cmdtype == CMDNORMAL || + (cmdp->cmdtype == CMDBUILTIN && + !(cmdp->param.cmd->flags & BUILTIN_REGULAR) && + builtinloc > 0))) + delete_cmd_entry(); + find_command(name, &entry, DO_ERR, pathval()); + if (entry.cmdtype == CMDUNKNOWN) + c = 1; + argptr++; + } + return c; +} + + +STATIC void +printentry(struct tblentry *cmdp) +{ + int idx; + const char *path; + char *name; + + idx = cmdp->param.index; + path = pathval(); + do { + padvance(&path, cmdp->cmdname); + } while (--idx >= 0); + name = stackblock(); + out1str(name); + out1fmt(snlfmt, cmdp->rehash ? "*" : nullstr); +} + + + +/* + * Resolve a command name. If you change this routine, you may have to + * change the shellexec routine as well. + */ + +void +find_command(char *name, struct cmdentry *entry, int act, const char *path) +{ + struct tblentry *cmdp; + int idx; + int prev; + char *fullname; + struct stat64 statb; + int e; + int updatetbl; + struct builtincmd *bcmd; + int len; + + /* If name contains a slash, don't use PATH or hash table */ + if (strchr(name, '/') != NULL) { + entry->u.index = -1; + if (act & DO_ABS) { + while (stat64(name, &statb) < 0) { +#ifdef SYSV + if (errno == EINTR) + continue; +#endif + entry->cmdtype = CMDUNKNOWN; + return; + } + } + entry->cmdtype = CMDNORMAL; + return; + } + + updatetbl = (path == pathval()); + if (!updatetbl) + act |= DO_ALTPATH; + + /* If name is in the table, check answer will be ok */ + if ((cmdp = cmdlookup(name, 0)) != NULL) { + int bit; + + switch (cmdp->cmdtype) { + default: +#if DEBUG + abort(); +#endif + case CMDNORMAL: + bit = DO_ALTPATH | DO_REGBLTIN; + break; + case CMDFUNCTION: + bit = DO_NOFUNC; + break; + case CMDBUILTIN: + bit = cmdp->param.cmd->flags & BUILTIN_REGULAR ? + 0 : DO_REGBLTIN; + break; + } + if (act & bit) { + if (act & bit & DO_REGBLTIN) + goto fail; + + updatetbl = 0; + cmdp = NULL; + } else if (cmdp->rehash == 0) + /* if not invalidated by cd, we're done */ + goto success; + } + + /* If %builtin not in path, check for builtin next */ + bcmd = find_builtin(name); + if (bcmd && ((bcmd->flags & BUILTIN_REGULAR) | (act & DO_ALTPATH) | + (builtinloc <= 0))) + goto builtin_success; + + if (act & DO_REGBLTIN) + goto fail; + + /* We have to search path. */ + prev = -1; /* where to start */ + if (cmdp && cmdp->rehash) { /* doing a rehash */ + if (cmdp->cmdtype == CMDBUILTIN) + prev = builtinloc; + else + prev = cmdp->param.index; + } + + e = ENOENT; + idx = -1; +loop: + while ((len = padvance(&path, name)) >= 0) { + const char *lpathopt = pathopt; + + fullname = stackblock(); + idx++; + if (lpathopt) { + if (*lpathopt == 'b') { + if (bcmd) + goto builtin_success; + continue; + } else if (!(act & DO_NOFUNC)) { + /* handled below */ + } else { + /* ignore unimplemented options */ + continue; + } + } + /* if rehash, don't redo absolute path names */ + if (fullname[0] == '/' && idx <= prev) { + if (idx < prev) + continue; + TRACE(("searchexec \"%s\": no change\n", name)); + goto success; + } + while (stat64(fullname, &statb) < 0) { +#ifdef SYSV + if (errno == EINTR) + continue; +#endif + if (errno != ENOENT && errno != ENOTDIR) + e = errno; + goto loop; + } + e = EACCES; /* if we fail, this will be the error */ + if (!S_ISREG(statb.st_mode)) + continue; + if (lpathopt) { /* this is a %func directory */ + stalloc(len); + readcmdfile(fullname); + if ((cmdp = cmdlookup(name, 0)) == NULL || + cmdp->cmdtype != CMDFUNCTION) + sh_error("%s not defined in %s", name, + fullname); + stunalloc(fullname); + goto success; + } +#ifdef notdef + /* XXX this code stops root executing stuff, and is buggy + if you need a group from the group list. */ + if (statb.st_uid == geteuid()) { + if ((statb.st_mode & 0100) == 0) + goto loop; + } else if (statb.st_gid == getegid()) { + if ((statb.st_mode & 010) == 0) + goto loop; + } else { + if ((statb.st_mode & 01) == 0) + goto loop; + } +#endif + TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname)); + if (!updatetbl) { + entry->cmdtype = CMDNORMAL; + entry->u.index = idx; + return; + } + INTOFF; + cmdp = cmdlookup(name, 1); + cmdp->cmdtype = CMDNORMAL; + cmdp->param.index = idx; + INTON; + goto success; + } + + /* We failed. If there was an entry for this command, delete it */ + if (cmdp && updatetbl) + delete_cmd_entry(); + if (act & DO_ERR) + sh_warnx("%s: %s", name, errmsg(e, E_EXEC)); +fail: + entry->cmdtype = CMDUNKNOWN; + return; + +builtin_success: + if (!updatetbl) { + entry->cmdtype = CMDBUILTIN; + entry->u.cmd = bcmd; + return; + } + INTOFF; + cmdp = cmdlookup(name, 1); + cmdp->cmdtype = CMDBUILTIN; + cmdp->param.cmd = bcmd; + INTON; +success: + cmdp->rehash = 0; + entry->cmdtype = cmdp->cmdtype; + entry->u = cmdp->param; +} + + + +/* + * Search the table of builtin commands. + */ + +struct builtincmd * +find_builtin(const char *name) +{ + struct builtincmd *bp; + + bp = bsearch( + &name, builtincmd, NUMBUILTINS, sizeof(struct builtincmd), + pstrcmp + ); + return bp; +} + + + +/* + * Called when a cd is done. Marks all commands so the next time they + * are executed they will be rehashed. + */ + +void +hashcd(void) +{ + struct tblentry **pp; + struct tblentry *cmdp; + + for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) { + for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { + if (cmdp->cmdtype == CMDNORMAL || ( + cmdp->cmdtype == CMDBUILTIN && + !(cmdp->param.cmd->flags & BUILTIN_REGULAR) && + builtinloc > 0 + )) + cmdp->rehash = 1; + } + } +} + + + +/* + * Fix command hash table when PATH changed. + * Called before PATH is changed. The argument is the new value of PATH; + * pathval() still returns the old value at this point. + * Called with interrupts off. + */ + +void +changepath(const char *newval) +{ + const char *new; + int idx; + int bltin; + + new = newval; + idx = 0; + bltin = -1; + for (;;) { + if (*new == '%' && prefix(new + 1, "builtin")) { + bltin = idx; + break; + } + new = strchr(new, ':'); + if (!new) + break; + idx++; + new++; + } + builtinloc = bltin; + clearcmdentry(); +} + + +/* + * Clear out command entries. The argument specifies the first entry in + * PATH which has changed. + */ + +STATIC void +clearcmdentry(void) +{ + struct tblentry **tblp; + struct tblentry **pp; + struct tblentry *cmdp; + + INTOFF; + for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) { + pp = tblp; + while ((cmdp = *pp) != NULL) { + if (cmdp->cmdtype == CMDNORMAL || + (cmdp->cmdtype == CMDBUILTIN && + !(cmdp->param.cmd->flags & BUILTIN_REGULAR) && + builtinloc > 0)) { + *pp = cmdp->next; + ckfree(cmdp); + } else { + pp = &cmdp->next; + } + } + } + INTON; +} + + + +/* + * Locate a command in the command hash table. If "add" is nonzero, + * add the command to the table if it is not already present. The + * variable "lastcmdentry" is set to point to the address of the link + * pointing to the entry, so that delete_cmd_entry can delete the + * entry. + * + * Interrupts must be off if called with add != 0. + */ + +struct tblentry **lastcmdentry; + + +STATIC struct tblentry * +cmdlookup(const char *name, int add) +{ + unsigned int hashval; + const char *p; + struct tblentry *cmdp; + struct tblentry **pp; + + p = name; + hashval = (unsigned char)*p << 4; + while (*p) + hashval += (unsigned char)*p++; + hashval &= 0x7FFF; + pp = &cmdtable[hashval % CMDTABLESIZE]; + for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { + if (equal(cmdp->cmdname, name)) + break; + pp = &cmdp->next; + } + if (add && cmdp == NULL) { + cmdp = *pp = ckmalloc(sizeof (struct tblentry) - ARB + + strlen(name) + 1); + cmdp->next = NULL; + cmdp->cmdtype = CMDUNKNOWN; + strcpy(cmdp->cmdname, name); + } + lastcmdentry = pp; + return cmdp; +} + +/* + * Delete the command entry returned on the last lookup. + */ + +STATIC void +delete_cmd_entry(void) +{ + struct tblentry *cmdp; + + INTOFF; + cmdp = *lastcmdentry; + *lastcmdentry = cmdp->next; + if (cmdp->cmdtype == CMDFUNCTION) + freefunc(cmdp->param.func); + ckfree(cmdp); + INTON; +} + + + +#ifdef notdef +void +getcmdentry(char *name, struct cmdentry *entry) +{ + struct tblentry *cmdp = cmdlookup(name, 0); + + if (cmdp) { + entry->u = cmdp->param; + entry->cmdtype = cmdp->cmdtype; + } else { + entry->cmdtype = CMDUNKNOWN; + entry->u.index = 0; + } +} +#endif + + +/* + * Add a new command entry, replacing any existing command entry for + * the same name - except special builtins. + */ + +STATIC void +addcmdentry(char *name, struct cmdentry *entry) +{ + struct tblentry *cmdp; + + cmdp = cmdlookup(name, 1); + if (cmdp->cmdtype == CMDFUNCTION) { + freefunc(cmdp->param.func); + } + cmdp->cmdtype = entry->cmdtype; + cmdp->param = entry->u; + cmdp->rehash = 0; +} + + +/* + * Define a shell function. + */ + +void +defun(union node *func) +{ + struct cmdentry entry; + + INTOFF; + entry.cmdtype = CMDFUNCTION; + entry.u.func = copyfunc(func); + addcmdentry(func->ndefun.text, &entry); + INTON; +} + + +/* + * Delete a function if it exists. + */ + +void +unsetfunc(const char *name) +{ + struct tblentry *cmdp; + + if ((cmdp = cmdlookup(name, 0)) != NULL && + cmdp->cmdtype == CMDFUNCTION) + delete_cmd_entry(); +} + +/* + * Locate and print what a word is... + */ + +int +typecmd(int argc, char **argv) +{ + int i; + int err = 0; + + for (i = 1; i < argc; i++) { + err |= describe_command(out1, argv[i], NULL, 1); + } + return err; +} + +STATIC int +describe_command(out, command, path, verbose) + struct output *out; + char *command; + const char *path; + int verbose; +{ + struct cmdentry entry; + struct tblentry *cmdp; + const struct alias *ap; + + if (verbose) { + outstr(command, out); + } + + /* First look at the keywords */ + if (findkwd(command)) { + outstr(verbose ? " is a shell keyword" : command, out); + goto out; + } + + /* Then look at the aliases */ + if ((ap = lookupalias(command, 0)) != NULL) { + if (verbose) { + outfmt(out, " is an alias for %s", ap->val); + } else { + outstr("alias ", out); + printalias(ap); + return 0; + } + goto out; + } + + /* Then if the standard search path is used, check if it is + * a tracked alias. + */ + if (path == NULL) { + path = pathval(); + cmdp = cmdlookup(command, 0); + } else { + cmdp = NULL; + } + + if (cmdp != NULL) { + entry.cmdtype = cmdp->cmdtype; + entry.u = cmdp->param; + } else { + /* Finally use brute force */ + find_command(command, &entry, DO_ABS, path); + } + + switch (entry.cmdtype) { + case CMDNORMAL: { + int j = entry.u.index; + char *p; + if (j == -1) { + p = command; + } else { + do { + padvance(&path, command); + } while (--j >= 0); + p = stackblock(); + } + if (verbose) { + outfmt( + out, " is%s %s", + cmdp ? " a tracked alias for" : nullstr, p + ); + } else { + outstr(p, out); + } + break; + } + + case CMDFUNCTION: + if (verbose) { + outstr(" is a shell function", out); + } else { + outstr(command, out); + } + break; + + case CMDBUILTIN: + if (verbose) { + outfmt( + out, " is a %sshell builtin", + entry.u.cmd->flags & BUILTIN_SPECIAL ? + "special " : nullstr + ); + } else { + outstr(command, out); + } + break; + + default: + if (verbose) { + outstr(": not found\n", out); + } + return 127; + } + +out: + outc('\n', out); + return 0; +} + +int +commandcmd(argc, argv) + int argc; + char **argv; +{ + char *cmd; + int c; + enum { + VERIFY_BRIEF = 1, + VERIFY_VERBOSE = 2, + } verify = 0; + const char *path = NULL; + + while ((c = nextopt("pvV")) != '\0') + if (c == 'V') + verify |= VERIFY_VERBOSE; + else if (c == 'v') + verify |= VERIFY_BRIEF; +#ifdef DEBUG + else if (c != 'p') + abort(); +#endif + else + path = defpath; + + cmd = *argptr; + if (verify && cmd) + return describe_command(out1, cmd, path, verify - VERIFY_BRIEF); + + return 0; +} -- cgit 1.4.1