NetBSD/bin/sh/exec.c
kre ee3b307fc5 Many internal memory management type fixes.
PR bin/52302   (core dump with interactive shell, here doc and error
on same line) is fixed.   (An old bug.)

echo "$( echo x; for a in $( seq 1000 ); do printf '%s\n'; done; echo y )"
consistently prints 1002 lines (x, 1000 empty ones, then y) as it should
(And you don't want to know what it did before, or why.) (Another old one.)

(Recently added) Problems with ~ expansion fixed (mem management related).

Proper fix for the cwrappers configure problem (which includes the quick
fix that was done earlier, but extends upon that to be correct). (This was
another newly added problem.)

And the really devious (and rare) old bug - if STACKSTRNUL() needs to
allocate a new buffer in which to store the \0, calculate the size of
the string space remaining correctly, unlike when SPUTC() grows the
buffer, there is no actual data being stored in the STACKSTRNUL()
case - the string space remaining was calculated as one byte too few.
That would be harmless, unless the next buffer also filled, in which
case it was assumed that it was really full, not one byte less, meaning
one junk char (a nul, or anything) was being copied into the next (even
bigger buffer) corrupting the data.

Consistent use of stalloc() to allocate a new block of (stack) memory,
and grabstackstr() to claim a block of (stack) memory that had already
been occupied but not claimed as in use.  Since grabstackstr is implemented
as just a call to stalloc() this is a no-op change in practice, but makes
it much easier to comprehend what is really happening.  Previous code
sometimes used stalloc() when the use case was really for grabstackstr().
Change grabstackstr() to actually use the arg passed to it, instead of
(not much better than) guessing how much space to claim,

More care when using unstalloc()/ungrabstackstr() to return space, and in
particular when the stack must be returned to its previous state, rather than
just returning no-longer needed space, neither of those work.  They also don't
work properly if there have been (really, even might have been) any stack mem
allocations since the last stalloc()/grabstackstr().   (If we know there
cannot have been then the alloc/release sequence is kind of pointless.)
To work correctly in general we must use setstackmark()/popstackmark() so
do that when needed.  Have those also save/restore the top of stack string
space remaining.

	[Aside: for those reading this, the "stack" mentioned is not
	in any way related to the thing used for maintaining the C
	function call state, ie: the "stack segment" of the program,
	but the shell's internal memory management strategy.]

More comments to better explain what is happening in some cases.
Also cleaned up some hopelessly broken DEBUG mode data that were
recently added (no effect on anyone but the poor semi-human attempting
to make sense of it...).

User visible changes:

Proper counting of line numbers when a here document is delimited
by a multi-line end-delimiter, as in

	cat << 'REALLY
	END'
	here doc line 1
	here doc line 2
	REALLY
	END

(which is an obscure case, but nothing says should not work.)  The \n
in the end-delimiter of the here doc (the last one) was not incrementing
the line number, which from that point on in the script would be 1 too
low (or more, for end-delimiters with more than one \n in them.)

With tilde expansion:
	unset HOME; echo ~
changed to return getpwuid(getuid())->pw_home instead of failing (returning ~)

POSIX says this is unspecified, which makes it difficult for a script to
compensate for being run without HOME set (as in env -i sh script), so
while not able to be used portably, this seems like a useful extension
(and is implemented the same way by some other shells).

Further, with
	HOME=; printf %s ~
we now write nothing (which is required by POSIX - which requires ~ to
expand to the value of $HOME if it is set) previously if $HOME (in this
case) or a user's directory in the passwd file (for ~user) were a null
STRING, We failed the ~ expansion and left behind '~' or '~user'.
2017-06-17 07:22:12 +00:00

1150 lines
24 KiB
C

/* $NetBSD: exec.c,v 1.50 2017/06/17 07:22:12 kre Exp $ */
/*-
* Copyright (c) 1991, 1993
* The Regents of the University of California. 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 <sys/cdefs.h>
#ifndef lint
#if 0
static char sccsid[] = "@(#)exec.c 8.4 (Berkeley) 6/8/95";
#else
__RCSID("$NetBSD: exec.c,v 1.50 2017/06/17 07:22:12 kre Exp $");
#endif
#endif /* not lint */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
/*
* 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 "input.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"
#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 fn_ln1; /* for functions, LINENO from 1 */
int lineno; /* for functions abs LINENO of definition */
char cmdname[ARB]; /* name of command */
};
STATIC struct tblentry *cmdtable[CMDTABLESIZE];
STATIC int builtinloc = -1; /* index in path of %builtin, or -1 */
int exerrno = 0; /* Last exec error */
STATIC void tryexec(char *, char **, char **, int);
STATIC void printentry(struct tblentry *, int);
STATIC void addcmdentry(char *, struct cmdentry *);
STATIC void clearcmdentry(int);
STATIC struct tblentry *cmdlookup(const char *, int);
STATIC void delete_cmd_entry(void);
#ifndef BSD
STATIC void execinterp(char **, char **);
#endif
extern const char *const parsekwd[];
/*
* 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, char **envp, const char *path, int idx, int vforked)
{
char *cmdname;
int e;
if (strchr(argv[0], '/') != NULL) {
tryexec(argv[0], argv, envp, vforked);
e = errno;
} else {
e = ENOENT;
while ((cmdname = padvance(&path, argv[0], 1)) != NULL) {
if (--idx < 0 && pathopt == NULL) {
tryexec(cmdname, argv, envp, vforked);
if (errno != ENOENT && errno != ENOTDIR)
e = errno;
}
stunalloc(cmdname);
}
}
/* Map to POSIX errors */
switch (e) {
case EACCES:
exerrno = 126;
break;
case ENOENT:
exerrno = 127;
break;
default:
exerrno = 2;
break;
}
TRACE(("shellexec failed for %s, errno %d, vforked %d, suppressint %d\n",
argv[0], e, vforked, suppressint ));
exerror(EXEXEC, "%s: %s", argv[0], errmsg(e, E_EXEC));
/* NOTREACHED */
}
STATIC void
tryexec(char *cmd, char **argv, char **envp, int vforked)
{
int e;
#ifndef BSD
char *p;
#endif
#ifdef SYSV
do {
execve(cmd, argv, envp);
} while (errno == EINTR);
#else
execve(cmd, argv, envp);
#endif
e = errno;
if (e == ENOEXEC) {
if (vforked) {
/* We are currently vfork(2)ed, so raise an
* exception, and evalcommand will try again
* with a normal fork(2).
*/
exraise(EXSHELLPROC);
}
#ifdef DEBUG
TRACE(("execve(cmd=%s) returned ENOEXEC\n", cmd));
#endif
initshellproc();
setinputfile(cmd, 0);
commandname = arg0 = savestr(argv[0]);
#ifndef BSD
pgetc(); pungetc(); /* fill up input buffer */
p = parsenextc;
if (parsenleft > 2 && p[0] == '#' && p[1] == '!') {
argv[0] = cmd;
execinterp(argv, envp);
}
#endif
setparam(argv + 1);
exraise(EXSHELLPROC);
}
errno = e;
}
#ifndef BSD
/*
* Execute an interpreter introduced by "#!", for systems where this
* feature has not been built into the kernel. If the interpreter is
* the shell, return (effectively ignoring the "#!"). If the execution
* of the interpreter fails, exit.
*
* This code peeks inside the input buffer in order to avoid actually
* reading any input. It would benefit from a rewrite.
*/
#define NEWARGS 5
STATIC void
execinterp(char **argv, char **envp)
{
int n;
char *inp;
char *outp;
char c;
char *p;
char **ap;
char *newargs[NEWARGS];
int i;
char **ap2;
char **new;
n = parsenleft - 2;
inp = parsenextc + 2;
ap = newargs;
for (;;) {
while (--n >= 0 && (*inp == ' ' || *inp == '\t'))
inp++;
if (n < 0)
goto bad;
if ((c = *inp++) == '\n')
break;
if (ap == &newargs[NEWARGS])
bad: error("Bad #! line");
STARTSTACKSTR(outp);
do {
STPUTC(c, outp);
} while (--n >= 0 && (c = *inp++) != ' ' && c != '\t' && c != '\n');
STPUTC('\0', outp);
n++, inp--;
*ap++ = grabstackstr(outp);
}
if (ap == newargs + 1) { /* if no args, maybe no exec is needed */
p = newargs[0];
for (;;) {
if (equal(p, "sh") || equal(p, "ash")) {
return;
}
while (*p != '/') {
if (*p == '\0')
goto break2;
p++;
}
p++;
}
break2:;
}
i = (char *)ap - (char *)newargs; /* size in bytes */
if (i == 0)
error("Bad #! line");
for (ap2 = argv ; *ap2++ != NULL ; );
new = ckmalloc(i + ((char *)ap2 - (char *)argv));
ap = newargs, ap2 = new;
while ((i -= sizeof (char **)) >= 0)
*ap2++ = *ap++;
ap = argv;
while (*ap2++ = *ap++);
shellexec(new, envp, pathval(), 0);
/* NOTREACHED */
}
#endif
/*
* 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.
*/
const char *pathopt;
char *
padvance(const char **path, const char *name, int magic_percent)
{
const char *p;
char *q;
const char *start;
int len;
if (*path == NULL)
return NULL;
if (magic_percent)
magic_percent = '%';
start = *path;
for (p = start ; *p && *p != ':' && *p != magic_percent ; p++)
;
len = p - start + strlen(name) + 2; /* "2" is for '/' and '\0' */
while (stackblocksize() < len)
growstackblock();
q = stackblock();
if (p != start) {
memcpy(q, start, p - start);
q += p - start;
if (q[-1] != '/')
*q++ = '/';
}
strcpy(q, name);
pathopt = NULL;
if (*p == magic_percent) {
pathopt = ++p;
while (*p && *p != ':')
p++;
}
if (*p == ':')
*path = p + 1;
else
*path = NULL;
return grabstackstr(q + strlen(name) + 1);
}
/*** Command hashing code ***/
int
hashcmd(int argc, char **argv)
{
struct tblentry **pp;
struct tblentry *cmdp;
int c;
struct cmdentry entry;
char *name;
int allopt=0, bopt=0, fopt=0, ropt=0, sopt=0, uopt=0, verbose=0;
while ((c = nextopt("bcfrsuv")) != '\0')
switch (c) {
case 'b': bopt = 1; break;
case 'c': uopt = 1; break; /* c == u */
case 'f': fopt = 1; break;
case 'r': ropt = 1; break;
case 's': sopt = 1; break;
case 'u': uopt = 1; break;
case 'v': verbose = 1; break;
}
if (ropt)
clearcmdentry(0);
if (bopt == 0 && fopt == 0 && sopt == 0 && uopt == 0)
allopt = bopt = fopt = sopt = uopt = 1;
if (*argptr == NULL) {
for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) {
for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
switch (cmdp->cmdtype) {
case CMDNORMAL:
if (!uopt)
continue;
break;
case CMDBUILTIN:
if (!bopt)
continue;
break;
case CMDSPLBLTIN:
if (!sopt)
continue;
break;
case CMDFUNCTION:
if (!fopt)
continue;
break;
default: /* never happens */
continue;
}
if (!allopt || verbose ||
cmdp->cmdtype == CMDNORMAL)
printentry(cmdp, verbose);
}
}
return 0;
}
while ((name = *argptr++) != NULL) {
if ((cmdp = cmdlookup(name, 0)) != NULL) {
switch (cmdp->cmdtype) {
case CMDNORMAL:
if (!uopt)
continue;
delete_cmd_entry();
break;
case CMDBUILTIN:
if (!bopt)
continue;
if (builtinloc >= 0)
delete_cmd_entry();
break;
case CMDSPLBLTIN:
if (!sopt)
continue;
break;
case CMDFUNCTION:
if (!fopt)
continue;
break;
}
}
find_command(name, &entry, DO_ERR, pathval());
if (verbose) {
if (entry.cmdtype != CMDUNKNOWN) { /* if no error msg */
cmdp = cmdlookup(name, 0);
if (cmdp != NULL)
printentry(cmdp, verbose);
}
flushall();
}
}
return 0;
}
STATIC void
printentry(struct tblentry *cmdp, int verbose)
{
int idx;
const char *path;
char *name;
switch (cmdp->cmdtype) {
case CMDNORMAL:
idx = cmdp->param.index;
path = pathval();
do {
name = padvance(&path, cmdp->cmdname, 1);
stunalloc(name);
} while (--idx >= 0);
if (verbose)
out1fmt("Command from PATH[%d]: ",
cmdp->param.index);
out1str(name);
break;
case CMDSPLBLTIN:
if (verbose)
out1str("special ");
/* FALLTHROUGH */
case CMDBUILTIN:
if (verbose)
out1str("builtin ");
out1fmt("%s", cmdp->cmdname);
break;
case CMDFUNCTION:
if (verbose)
out1str("function ");
out1fmt("%s", cmdp->cmdname);
if (verbose) {
struct procstat ps;
INTOFF;
commandtext(&ps, cmdp->param.func);
INTON;
out1str("() { ");
out1str(ps.cmd);
out1str("; }");
}
break;
default:
error("internal error: %s cmdtype %d",
cmdp->cmdname, cmdp->cmdtype);
}
if (cmdp->rehash)
out1c('*');
out1c('\n');
}
/*
* 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, loc_cmd;
int idx;
int prev;
char *fullname;
struct stat statb;
int e;
int (*bltin)(int,char **);
/* If name contains a slash, don't use PATH or hash table */
if (strchr(name, '/') != NULL) {
if (act & DO_ABS) {
while (stat(name, &statb) < 0) {
#ifdef SYSV
if (errno == EINTR)
continue;
#endif
if (errno != ENOENT && errno != ENOTDIR)
e = errno;
entry->cmdtype = CMDUNKNOWN;
entry->u.index = -1;
return;
}
entry->cmdtype = CMDNORMAL;
entry->u.index = -1;
return;
}
entry->cmdtype = CMDNORMAL;
entry->u.index = 0;
return;
}
if (path != pathval())
act |= DO_ALTPATH;
if (act & DO_ALTPATH && strstr(path, "%builtin") != NULL)
act |= DO_ALTBLTIN;
/* If name is in the table, check answer will be ok */
if ((cmdp = cmdlookup(name, 0)) != NULL) {
do {
switch (cmdp->cmdtype) {
case CMDNORMAL:
if (act & DO_ALTPATH) {
cmdp = NULL;
continue;
}
break;
case CMDFUNCTION:
if (act & DO_NOFUNC) {
cmdp = NULL;
continue;
}
break;
case CMDBUILTIN:
if ((act & DO_ALTBLTIN) || builtinloc >= 0) {
cmdp = NULL;
continue;
}
break;
}
/* if not invalidated by cd, we're done */
if (cmdp->rehash == 0)
goto success;
} while (0);
}
/* If %builtin not in path, check for builtin next */
if ((act & DO_ALTPATH ? !(act & DO_ALTBLTIN) : builtinloc < 0) &&
(bltin = find_builtin(name)) != 0)
goto builtin_success;
/* We have to search path. */
prev = -1; /* where to start */
if (cmdp) { /* doing a rehash */
if (cmdp->cmdtype == CMDBUILTIN)
prev = builtinloc;
else
prev = cmdp->param.index;
}
e = ENOENT;
idx = -1;
loop:
while ((fullname = padvance(&path, name, 1)) != NULL) {
stunalloc(fullname);
idx++;
if (pathopt) {
if (prefix("builtin", pathopt)) {
if ((bltin = find_builtin(name)) == 0)
goto loop;
goto builtin_success;
} else if (prefix("func", pathopt)) {
/* handled below */
} else {
/* ignore unimplemented options */
goto loop;
}
}
/* if rehash, don't redo absolute path names */
if (fullname[0] == '/' && idx <= prev) {
if (idx < prev)
goto loop;
TRACE(("searchexec \"%s\": no change\n", name));
goto success;
}
while (stat(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))
goto loop;
if (pathopt) { /* this is a %func directory */
char *endname;
if (act & DO_NOFUNC)
goto loop;
endname = fullname + strlen(fullname) + 1;
grabstackstr(endname);
readcmdfile(fullname);
if ((cmdp = cmdlookup(name, 0)) == NULL ||
cmdp->cmdtype != CMDFUNCTION)
error("%s not defined in %s", name, fullname);
ungrabstackstr(fullname, endname);
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));
INTOFF;
if (act & DO_ALTPATH) {
/*
* this should be a grabstackstr() but is not needed:
* fullname is no longer needed for anything
stalloc(strlen(fullname) + 1);
*/
cmdp = &loc_cmd;
} else
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)
delete_cmd_entry();
if (act & DO_ERR)
outfmt(out2, "%s: %s\n", name, errmsg(e, E_EXEC));
entry->cmdtype = CMDUNKNOWN;
return;
builtin_success:
INTOFF;
if (act & DO_ALTPATH)
cmdp = &loc_cmd;
else
cmdp = cmdlookup(name, 1);
if (cmdp->cmdtype == CMDFUNCTION)
/* DO_NOFUNC must have been set */
cmdp = &loc_cmd;
cmdp->cmdtype = CMDBUILTIN;
cmdp->param.bltin = bltin;
INTON;
success:
if (cmdp) {
cmdp->rehash = 0;
entry->cmdtype = cmdp->cmdtype;
entry->lineno = cmdp->lineno;
entry->lno_frel = cmdp->fn_ln1;
entry->u = cmdp->param;
} else
entry->cmdtype = CMDUNKNOWN;
}
/*
* Search the table of builtin commands.
*/
int
(*find_builtin(char *name))(int, char **)
{
const struct builtincmd *bp;
for (bp = builtincmd ; bp->name ; bp++) {
if (*bp->name == *name
&& (*name == '%' || equal(bp->name, name)))
return bp->builtin;
}
return 0;
}
int
(*find_splbltin(char *name))(int, char **)
{
const struct builtincmd *bp;
for (bp = splbltincmd ; bp->name ; bp++) {
if (*bp->name == *name && equal(bp->name, name))
return bp->builtin;
}
return 0;
}
/*
* At shell startup put special builtins into hash table.
* ensures they are executed first (see posix).
* We stop functions being added with the same name
* (as they are impossible to call)
*/
void
hash_special_builtins(void)
{
const struct builtincmd *bp;
struct tblentry *cmdp;
for (bp = splbltincmd ; bp->name ; bp++) {
cmdp = cmdlookup(bp->name, 1);
cmdp->cmdtype = CMDSPLBLTIN;
cmdp->param.bltin = bp->builtin;
}
}
/*
* 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 && 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 *old, *new;
int idx;
int firstchange;
int bltin;
old = pathval();
new = newval;
firstchange = 9999; /* assume no change */
idx = 0;
bltin = -1;
for (;;) {
if (*old != *new) {
firstchange = idx;
if ((*old == '\0' && *new == ':')
|| (*old == ':' && *new == '\0'))
firstchange++;
old = new; /* ignore subsequent differences */
}
if (*new == '\0')
break;
if (*new == '%' && bltin < 0 && prefix("builtin", new + 1))
bltin = idx;
if (*new == ':') {
idx++;
}
new++, old++;
}
if (builtinloc < 0 && bltin >= 0)
builtinloc = bltin; /* zap builtins */
if (builtinloc >= 0 && bltin < 0)
firstchange = 0;
clearcmdentry(firstchange);
builtinloc = bltin;
}
/*
* Clear out command entries. The argument specifies the first entry in
* PATH which has changed.
*/
STATIC void
clearcmdentry(int firstchange)
{
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->param.index >= firstchange)
|| (cmdp->cmdtype == CMDBUILTIN &&
builtinloc >= firstchange)) {
*pp = cmdp->next;
ckfree(cmdp);
} else {
pp = &cmdp->next;
}
}
}
INTON;
}
/*
* Delete all functions.
*/
#ifdef mkinit
MKINIT void deletefuncs(void);
MKINIT void hash_special_builtins(void);
INIT {
hash_special_builtins();
}
SHELLPROC {
deletefuncs();
}
#endif
void
deletefuncs(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 == CMDFUNCTION) {
*pp = cmdp->next;
freefunc(cmdp->param.func);
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.
*/
struct tblentry **lastcmdentry;
STATIC struct tblentry *
cmdlookup(const char *name, int add)
{
int hashval;
const char *p;
struct tblentry *cmdp;
struct tblentry **pp;
p = name;
hashval = *p << 4;
while (*p)
hashval += *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) {
INTOFF;
cmdp = *pp = ckmalloc(sizeof (struct tblentry) - ARB
+ strlen(name) + 1);
cmdp->next = NULL;
cmdp->cmdtype = CMDUNKNOWN;
cmdp->rehash = 0;
strcpy(cmdp->cmdname, name);
INTON;
}
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;
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;
INTOFF;
cmdp = cmdlookup(name, 1);
if (cmdp->cmdtype != CMDSPLBLTIN) {
if (cmdp->cmdtype == CMDFUNCTION) {
freefunc(cmdp->param.func);
}
cmdp->cmdtype = entry->cmdtype;
cmdp->lineno = entry->lineno;
cmdp->fn_ln1 = entry->lno_frel;
cmdp->param = entry->u;
}
INTON;
}
/*
* Define a shell function.
*/
void
defun(char *name, union node *func, int lineno)
{
struct cmdentry entry;
INTOFF;
entry.cmdtype = CMDFUNCTION;
entry.lineno = lineno;
entry.lno_frel = fnline1;
entry.u.func = copyfunc(func);
addcmdentry(name, &entry);
INTON;
}
/*
* Delete a function if it exists.
*/
int
unsetfunc(char *name)
{
struct tblentry *cmdp;
if ((cmdp = cmdlookup(name, 0)) != NULL &&
cmdp->cmdtype == CMDFUNCTION) {
freefunc(cmdp->param.func);
delete_cmd_entry();
}
return 0;
}
/*
* Locate and print what a word is...
* also used for 'command -[v|V]'
*/
int
typecmd(int argc, char **argv)
{
struct cmdentry entry;
struct tblentry *cmdp;
const char * const *pp;
struct alias *ap;
int err = 0;
char *arg;
int c;
int V_flag = 0;
int v_flag = 0;
int p_flag = 0;
while ((c = nextopt("vVp")) != 0) {
switch (c) {
case 'v': v_flag = 1; break;
case 'V': V_flag = 1; break;
case 'p': p_flag = 1; break;
}
}
if (p_flag && (v_flag || V_flag))
error("cannot specify -p with -v or -V");
while ((arg = *argptr++)) {
if (!v_flag)
out1str(arg);
/* First look at the keywords */
for (pp = parsekwd; *pp; pp++)
if (**pp == *arg && equal(*pp, arg))
break;
if (*pp) {
if (v_flag)
err = 1;
else
out1str(" is a shell keyword\n");
continue;
}
/* Then look at the aliases */
if ((ap = lookupalias(arg, 1)) != NULL) {
if (!v_flag)
out1fmt(" is an alias for \n");
out1fmt("%s\n", ap->val);
continue;
}
/* Then check if it is a tracked alias */
if ((cmdp = cmdlookup(arg, 0)) != NULL) {
entry.cmdtype = cmdp->cmdtype;
entry.u = cmdp->param;
} else {
/* Finally use brute force */
find_command(arg, &entry, DO_ABS, pathval());
}
switch (entry.cmdtype) {
case CMDNORMAL: {
if (strchr(arg, '/') == NULL) {
const char *path = pathval();
char *name;
int j = entry.u.index;
do {
name = padvance(&path, arg, 1);
stunalloc(name);
} while (--j >= 0);
if (!v_flag)
out1fmt(" is%s ",
cmdp ? " a tracked alias for" : "");
out1fmt("%s\n", name);
} else {
if (access(arg, X_OK) == 0) {
if (!v_flag)
out1fmt(" is ");
out1fmt("%s\n", arg);
} else {
if (!v_flag)
out1fmt(": %s\n",
strerror(errno));
else
err = 126;
}
}
break;
}
case CMDFUNCTION:
if (!v_flag)
out1str(" is a shell function\n");
else
out1fmt("%s\n", arg);
break;
case CMDBUILTIN:
if (!v_flag)
out1str(" is a shell builtin\n");
else
out1fmt("%s\n", arg);
break;
case CMDSPLBLTIN:
if (!v_flag)
out1str(" is a special shell builtin\n");
else
out1fmt("%s\n", arg);
break;
default:
if (!v_flag)
out1str(": not found\n");
err = 127;
break;
}
}
return err;
}