* Copyright (c) 1991 The Regents of the University of California.
* This code is derived from software contributed to Berkeley by
* %sccs.include.redist.c%
static char sccsid
[] = "@(#)exec.c 5.1 (Berkeley) %G%";
* 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
* We should investigate converting to a linear search, even though that
* would make the command name "hash" a misnomer.
#define CMDTABLESIZE 31 /* should be prime */
#define ARB 1 /* actual size determined at run time */
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
; /* index in path of %builtin, or -1 */
STATIC
void tryexec(char *, char **, char **);
STATIC
void execinterp(char **, char **);
STATIC
void printentry(struct tblentry
*);
STATIC
void clearcmdentry(int);
STATIC
struct tblentry
*cmdlookup(char *, int);
STATIC
void delete_cmd_entry(void);
STATIC
void execinterp();
STATIC
void printentry();
STATIC
void clearcmdentry();
STATIC
struct tblentry
*cmdlookup();
STATIC
void delete_cmd_entry();
* Exec a program. Never returns. If you change this routine, you may
* have to change the find_command routine as well.
shellexec(argv
, envp
, path
, index
)
if (strchr(argv
[0], '/') != NULL
) {
tryexec(argv
[0], argv
, envp
);
while ((cmdname
= padvance(&path
, argv
[0])) != NULL
) {
if (--index
< 0 && pathopt
== NULL
) {
tryexec(cmdname
, argv
, envp
);
if (errno
!= ENOENT
&& errno
!= ENOTDIR
)
error2(argv
[0], errmsg(e
, E_EXEC
));
} while (errno
== EINTR
);
commandname
= arg0
= savestr(argv
[0]);
pgetc(); pungetc(); /* fill up input buffer */
if (parsenleft
> 2 && p
[0] == '#' && p
[1] == '!') {
* 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.
while (--n
>= 0 && (*inp
== ' ' || *inp
== '\t'))
if ((c
= *inp
++) == '\n')
if (ap
== &newargs
[NEWARGS
])
bad
: error("Bad #! line");
} while (--n
>= 0 && (c
= *inp
++) != ' ' && c
!= '\t' && c
!= '\n');
*ap
++ = grabstackstr(outp
);
if (ap
== newargs
+ 1) { /* if no args, maybe no exec is needed */
if (equal(p
, "sh") || equal(p
, "ash")) {
i
= (char *)ap
- (char *)newargs
; /* size in bytes */
for (ap2
= argv
; *ap2
++ != NULL
; );
new = ckmalloc(i
+ ((char *)ap2
- (char *)argv
));
while ((i
-= sizeof (char **)) >= 0)
shellexec(new, envp
, pathval(), 0);
* 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
for (p
= start
; *p
&& *p
!= ':' && *p
!= '%' ; p
++);
len
= p
- start
+ strlen(name
) + 2; /* "2" is for '/' and '\0' */
while (stackblocksize() < len
)
bcopy(start
, q
, p
- start
);
while (*p
&& *p
!= ':') p
++;
/*** Command hashing code ***/
hashcmd(argc
, argv
) char **argv
; {
for (pp
= cmdtable
; pp
< &cmdtable
[CMDTABLESIZE
] ; pp
++) {
for (cmdp
= *pp
; cmdp
; cmdp
= cmdp
->next
) {
while ((c
= nextopt("rv")) >= 0) {
while ((name
= *argptr
) != NULL
) {
if ((cmdp
= cmdlookup(name
, 0)) != NULL
&& (cmdp
->cmdtype
== CMDNORMAL
|| cmdp
->cmdtype
== CMDBUILTIN
&& builtinloc
>= 0))
find_command(name
, &entry
, 1);
if (entry
.cmdtype
!= CMDUNKNOWN
) { /* if no error msg */
cmdp
= cmdlookup(name
, 0);
if (cmdp
->cmdtype
== CMDNORMAL
) {
index
= cmdp
->param
.index
;
name
= padvance(&path
, cmdp
->cmdname
);
} else if (cmdp
->cmdtype
== CMDBUILTIN
) {
out1fmt("builtin %s", cmdp
->cmdname
);
} else if (cmdp
->cmdtype
== CMDFUNCTION
) {
out1fmt("function %s", cmdp
->cmdname
);
error("internal error: cmdtype %d", cmdp
->cmdtype
);
* Resolve a command name. If you change this routine, you may have to
* change the shellexec routine as well.
find_command(name
, entry
, printerr
)
/* If name contains a slash, don't use the hash table */
if (strchr(name
, '/') != NULL
) {
entry
->cmdtype
= CMDNORMAL
;
/* If name is in the table, and not invalidated by cd, we're done */
if ((cmdp
= cmdlookup(name
, 0)) != NULL
&& cmdp
->rehash
== 0)
/* If %builtin not in path, check for builtin next */
if (builtinloc
< 0 && (i
= find_builtin(name
)) >= 0) {
cmdp
= cmdlookup(name
, 1);
cmdp
->cmdtype
= CMDBUILTIN
;
/* We have to search path. */
prev
= -1; /* where to start */
if (cmdp
) { /* doing a rehash */
if (cmdp
->cmdtype
== CMDBUILTIN
)
prev
= cmdp
->param
.index
;
while ((fullname
= padvance(&path
, name
)) != NULL
) {
if (prefix("builtin", pathopt
)) {
if ((i
= find_builtin(name
)) < 0)
cmdp
= cmdlookup(name
, 1);
cmdp
->cmdtype
= CMDBUILTIN
;
} else if (prefix("func", pathopt
)) {
goto loop
; /* ignore unimplemented options */
/* if rehash, don't redo absolute path names */
if (fullname
[0] == '/' && index
<= prev
) {
TRACE(("searchexec \"%s\": no change\n", name
));
while (stat(fullname
, &statb
) < 0) {
if (errno
!= ENOENT
&& errno
!= ENOTDIR
)
e
= EACCES
; /* if we fail, this will be the error */
if ((statb
.st_mode
& S_IFMT
) != S_IFREG
)
if (pathopt
) { /* this is a %func directory */
stalloc(strlen(fullname
) + 1);
if ((cmdp
= cmdlookup(name
, 0)) == NULL
|| cmdp
->cmdtype
!= CMDFUNCTION
)
error("%s not defined in %s", name
, fullname
);
if (statb
.st_uid
== geteuid()) {
if ((statb
.st_mode
& 0100) == 0)
} else if (statb
.st_gid
== getegid()) {
if ((statb
.st_mode
& 010) == 0)
if ((statb
.st_mode
& 01) == 0)
TRACE(("searchexec \"%s\" returns \"%s\"\n", name
, fullname
));
cmdp
= cmdlookup(name
, 1);
cmdp
->cmdtype
= CMDNORMAL
;
cmdp
->param
.index
= index
;
/* We failed. If there was an entry for this command, delete it */
outfmt(out2
, "%s: %s\n", name
, errmsg(e
, E_EXEC
));
entry
->cmdtype
= CMDUNKNOWN
;
entry
->cmdtype
= cmdp
->cmdtype
;
* Search the table of builtin commands.
const register struct builtincmd
*bp
;
for (bp
= builtincmd
; bp
->name
; bp
++) {
if (*bp
->name
== *name
&& equal(bp
->name
, name
))
* Called when a cd is done. Marks all commands so the next time they
* are executed they will be rehashed.
for (pp
= cmdtable
; pp
< &cmdtable
[CMDTABLESIZE
] ; pp
++) {
for (cmdp
= *pp
; cmdp
; cmdp
= cmdp
->next
) {
if (cmdp
->cmdtype
== CMDNORMAL
|| cmdp
->cmdtype
== CMDBUILTIN
&& builtinloc
>= 0)
* Called before PATH is changed. The argument is the new value of PATH;
* pathval() still returns the old value at this point. Called with
firstchange
= 9999; /* assume no change */
if (*old
== '\0' && *new == ':'
|| *old
== ':' && *new == '\0')
old
= new; /* ignore subsequent differences */
if (*new == '%' && bltin
< 0 && prefix("builtin", new + 1))
if (builtinloc
< 0 && bltin
>= 0)
builtinloc
= bltin
; /* zap builtins */
if (builtinloc
>= 0 && bltin
< 0)
clearcmdentry(firstchange
);
* Clear out command entries. The argument specifies the first entry in
* PATH which has changed.
clearcmdentry(firstchange
) {
for (tblp
= cmdtable
; tblp
< &cmdtable
[CMDTABLESIZE
] ; tblp
++) {
while ((cmdp
= *pp
) != NULL
) {
if (cmdp
->cmdtype
== CMDNORMAL
&& cmdp
->param
.index
>= firstchange
|| cmdp
->cmdtype
== CMDBUILTIN
&& builtinloc
>= firstchange
) {
MKINIT
void deletefuncs();
for (tblp
= cmdtable
; tblp
< &cmdtable
[CMDTABLESIZE
] ; tblp
++) {
while ((cmdp
= *pp
) != NULL
) {
if (cmdp
->cmdtype
== CMDFUNCTION
) {
freefunc(cmdp
->param
.func
);
* 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
struct tblentry
**lastcmdentry
;
pp
= &cmdtable
[hashval
% CMDTABLESIZE
];
for (cmdp
= *pp
; cmdp
; cmdp
= cmdp
->next
) {
if (equal(cmdp
->cmdname
, name
))
if (add
&& cmdp
== NULL
) {
cmdp
= *pp
= ckmalloc(sizeof (struct tblentry
) - ARB
cmdp
->cmdtype
= CMDUNKNOWN
;
strcpy(cmdp
->cmdname
, name
);
* Delete the command entry returned on the last lookup.
*lastcmdentry
= cmdp
->next
;
struct tblentry
*cmdp
= cmdlookup(name
, 0);
entry
->cmdtype
= cmdp
->cmdtype
;
entry
->cmdtype
= CMDUNKNOWN
;
* Add a new command entry, replacing any existing command entry for
cmdp
= cmdlookup(name
, 1);
if (cmdp
->cmdtype
== CMDFUNCTION
) {
freefunc(cmdp
->param
.func
);
cmdp
->cmdtype
= entry
->cmdtype
;
* Define a shell function.
entry
.cmdtype
= CMDFUNCTION
;
entry
.u
.func
= copyfunc(func
);
addcmdentry(name
, &entry
);
* Delete a function if it exists.
if ((cmdp
= cmdlookup(name
, 0)) != NULL
&& cmdp
->cmdtype
== CMDFUNCTION
) {
freefunc(cmdp
->param
.func
);