sh: Improve the command builtin:

* avoid unnecessary fork
* allow executing builtins via command
* executing a special builtin via command removes its special properties

Obtained from:	NetBSD (parts)
This commit is contained in:
Jilles Tjoelker 2010-03-06 16:57:53 +00:00
parent 44d68185d1
commit c848bc18e8
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=204800
7 changed files with 244 additions and 43 deletions

View File

@ -597,6 +597,7 @@ evalcommand(union node *cmd, int flags, struct backcmd *backcmd)
char *lastarg;
int realstatus;
int do_clearcmdentry;
char *path = pathval();
/* First expand the arguments. */
TRACE(("evalcommand(%p, %d) called\n", (void *)cmd, flags));
@ -682,7 +683,7 @@ evalcommand(union node *cmd, int flags, struct backcmd *backcmd)
cmdentry.special = 1;
} else {
static const char PATH[] = "PATH=";
char *path = pathval();
int cmd_flags = 0, bltinonly = 0;
/*
* Modify the command lookup path, if a PATH= assignment
@ -713,24 +714,68 @@ evalcommand(union node *cmd, int flags, struct backcmd *backcmd)
do_clearcmdentry = 1;
}
find_command(argv[0], &cmdentry, 0, path);
/* implement the bltin builtin here */
if (cmdentry.cmdtype == CMDBUILTIN && cmdentry.u.index == BLTINCMD) {
for (;;) {
argv++;
if (--argc == 0)
break;
if ((cmdentry.u.index = find_builtin(*argv,
&cmdentry.special)) < 0) {
for (;;) {
if (bltinonly) {
cmdentry.u.index = find_builtin(*argv, &cmdentry.special);
if (cmdentry.u.index < 0) {
cmdentry.u.index = BLTINCMD;
argv--;
argc++;
break;
}
if (cmdentry.u.index != BLTINCMD)
} else
find_command(argv[0], &cmdentry, cmd_flags, path);
/* implement the bltin and command builtins here */
if (cmdentry.cmdtype != CMDBUILTIN)
break;
if (cmdentry.u.index == BLTINCMD) {
if (argc == 1)
break;
}
argv++;
argc--;
bltinonly = 1;
} else if (cmdentry.u.index == COMMANDCMD) {
if (argc == 1)
break;
if (!strcmp(argv[1], "-p")) {
if (argc == 2)
break;
if (argv[2][0] == '-') {
if (strcmp(argv[2], "--"))
break;
if (argc == 3)
break;
argv += 3;
argc -= 3;
} else {
argv += 2;
argc -= 2;
}
path = _PATH_STDPATH;
clearcmdentry(0);
do_clearcmdentry = 1;
} else if (!strcmp(argv[1], "--")) {
if (argc == 2)
break;
argv += 2;
argc -= 2;
} else if (argv[1][0] == '-')
break;
else {
argv++;
argc--;
}
cmd_flags |= DO_NOFUNC;
bltinonly = 0;
} else
break;
}
/*
* Special builtins lose their special properties when
* called via 'command'.
*/
if (cmd_flags & DO_NOFUNC)
cmdentry.special = 0;
}
/* Fork off a child process if necessary. */
@ -741,9 +786,7 @@ evalcommand(union node *cmd, int flags, struct backcmd *backcmd)
&& (cmdentry.cmdtype != CMDBUILTIN
|| cmdentry.u.index == CDCMD
|| cmdentry.u.index == DOTCMD
|| cmdentry.u.index == EVALCMD))
|| (cmdentry.cmdtype == CMDBUILTIN &&
cmdentry.u.index == COMMANDCMD)) {
|| cmdentry.u.index == EVALCMD))) {
jp = makejob(cmd, 1);
mode = cmd->ncmd.backgnd;
if (flags & EV_BACKCMD) {
@ -889,7 +932,7 @@ evalcommand(union node *cmd, int flags, struct backcmd *backcmd)
for (sp = varlist.list ; sp ; sp = sp->next)
setvareq(sp->text, VEXPORT|VSTACK);
envp = environment();
shellexec(argv, envp, pathval(), cmdentry.u.index);
shellexec(argv, envp, path, cmdentry.u.index);
/*NOTREACHED*/
}
goto out;
@ -996,15 +1039,11 @@ int
commandcmd(int argc, char **argv)
{
static char stdpath[] = _PATH_STDPATH;
struct jmploc loc, *old;
struct strlist *sp;
char *path;
int ch;
int cmd = -1;
for (sp = cmdenviron; sp ; sp = sp->next)
setvareq(sp->text, VEXPORT|VSTACK);
path = pathval();
path = bltinlookup("PATH", 1);
optind = optreset = 1;
opterr = 0;
@ -1032,22 +1071,14 @@ commandcmd(int argc, char **argv)
error("wrong number of arguments");
return typecmd_impl(2, argv - 1, cmd, path);
}
if (argc != 0) {
old = handler;
handler = &loc;
if (setjmp(handler->loc) == 0)
shellexec(argv, environment(), path, 0);
handler = old;
if (exception == EXEXEC)
exit(exerrno);
exraise(exception);
}
if (argc != 0)
error("commandcmd() called while it should not be");
/*
* Do nothing successfully if no command was specified;
* ksh also does this.
*/
exit(0);
return 0;
}

View File

@ -248,7 +248,7 @@ hashcmd(int argc __unused, char **argv __unused)
&& (cmdp->cmdtype == CMDNORMAL
|| (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0)))
delete_cmd_entry();
find_command(name, &entry, 1, pathval());
find_command(name, &entry, DO_ERR, pathval());
if (verbose) {
if (entry.cmdtype != CMDUNKNOWN) { /* if no error msg */
cmdp = cmdlookup(name, 0);
@ -310,10 +310,10 @@ printentry(struct tblentry *cmdp, int verbose)
*/
void
find_command(const char *name, struct cmdentry *entry, int printerr,
find_command(const char *name, struct cmdentry *entry, int act,
const char *path)
{
struct tblentry *cmdp;
struct tblentry *cmdp, loc_cmd;
int idx;
int prev;
char *fullname;
@ -330,13 +330,19 @@ find_command(const char *name, struct cmdentry *entry, int printerr,
}
/* If name is in the table, and not invalidated by cd, we're done */
if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->rehash == 0)
goto success;
if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->rehash == 0) {
if (cmdp->cmdtype == CMDFUNCTION && act & DO_NOFUNC)
cmdp = NULL;
else
goto success;
}
/* If %builtin not in path, check for builtin next */
if (builtinloc < 0 && (i = find_builtin(name, &spec)) >= 0) {
INTOFF;
cmdp = cmdlookup(name, 1);
if (cmdp->cmdtype == CMDFUNCTION)
cmdp = &loc_cmd;
cmdp->cmdtype = CMDBUILTIN;
cmdp->param.index = i;
cmdp->special = spec;
@ -365,6 +371,8 @@ find_command(const char *name, struct cmdentry *entry, int printerr,
goto loop;
INTOFF;
cmdp = cmdlookup(name, 1);
if (cmdp->cmdtype == CMDFUNCTION)
cmdp = &loc_cmd;
cmdp->cmdtype = CMDBUILTIN;
cmdp->param.index = i;
cmdp->special = spec;
@ -414,6 +422,8 @@ find_command(const char *name, struct cmdentry *entry, int printerr,
TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname));
INTOFF;
cmdp = cmdlookup(name, 1);
if (cmdp->cmdtype == CMDFUNCTION)
cmdp = &loc_cmd;
cmdp->cmdtype = CMDNORMAL;
cmdp->param.index = idx;
INTON;
@ -421,9 +431,9 @@ find_command(const char *name, struct cmdentry *entry, int printerr,
}
/* We failed. If there was an entry for this command, delete it */
if (cmdp)
if (cmdp && cmdp->cmdtype != CMDFUNCTION)
delete_cmd_entry();
if (printerr) {
if (act & DO_ERR) {
if (e == ENOENT || e == ENOTDIR)
outfmt(out2, "%s: not found\n", name);
else

View File

@ -57,6 +57,10 @@ struct cmdentry {
};
/* action to find_command() */
#define DO_ERR 0x01 /* prints errors */
#define DO_NOFUNC 0x02 /* don't return shell functions, for command */
extern const char *pathopt; /* set by padvance */
extern int exerrno; /* last exec error */

View File

@ -32,7 +32,7 @@
.\" from: @(#)sh.1 8.6 (Berkeley) 5/4/95
.\" $FreeBSD$
.\"
.Dd December 31, 2009
.Dd March 6, 2010
.Dt SH 1
.Os
.Sh NAME
@ -1571,10 +1571,12 @@ built-in command.
.It Ic command Oo Fl p Oc Op Ar utility Op Ar argument ...
.It Ic command Oo Fl v | V Oc Op Ar utility
The first form of invocation executes the specified
.Ar utility ,
ignoring shell functions in the search.
If
.Ar utility
as a simple command (see the
.Sx Simple Commands
section).
is a special builtin,
it is executed as if it were a regular builtin.
.Pp
If the
.Fl p

View File

@ -0,0 +1,45 @@
# $FreeBSD$
IFS=,
SPECIAL="break,\
:,\
continue,\
. /dev/null,\
eval,\
exec,\
export -p,\
readonly -p,\
set,\
shift 0,\
times,\
trap,\
unset foo"
set -e
# Check that special builtins can be executed via "command".
set -- ${SPECIAL}
for cmd in "$@"
do
sh -c "v=:; while \$v; do v=false; command ${cmd}; done" >/dev/null
done
while :; do
command break
echo Error on line $LINENO
done
set p q r
command shift 2
if [ $# -ne 1 ]; then
echo Error on line $LINENO
fi
(
command exec >/dev/null
echo Error on line $LINENO
)
set +e
! command shift 2 2>/dev/null

View File

@ -0,0 +1,55 @@
# $FreeBSD$
IFS=,
SPECIAL="break,\
:,\
continue,\
. /dev/null,\
eval,\
exec,\
export -p,\
readonly -p,\
set,\
shift 0,\
times,\
trap,\
unset foo"
UTILS="alias,\
bg,\
bind,\
cd,\
command echo,\
echo,\
false,\
fc -l,\
fg,\
getopts a var,\
hash,\
jobs,\
printf a,\
pwd,\
read var < /dev/null,\
test,\
true,\
type ls,\
ulimit,\
umask,\
unalias -a,\
wait"
set -e
# With 'command', variable assignments affect the shell environment.
set -- ${SPECIAL}
for cmd in "$@"
do
sh -c "VAR=0; VAR=1 command ${cmd}; exit \${VAR}" >/dev/null 2>&1
done
set -- ${UTILS}
for cmd in "$@"
do
sh -c "VAR=0; VAR=1 command ${cmd}; exit \${VAR}" >/dev/null 2>&1
done

View File

@ -0,0 +1,54 @@
# $FreeBSD$
IFS=,
SPECIAL="break,\
:,\
continue,\
. /dev/null,\
eval,\
exec,\
export -p,\
readonly -p,\
set,\
shift,\
times,\
trap,\
unset foo"
UTILS="alias,\
bg,\
bind,\
cd,\
command echo,\
echo,\
false,\
fc -l,\
fg,\
getopts a -a,\
hash,\
jobs,\
printf a,\
pwd,\
read var < /dev/null,\
test,\
true,\
type ls,\
ulimit,\
umask,\
unalias -a,\
wait"
# When used with 'command', neither special built-in utilities nor other
# utilities must abort on a redirection error.
set -- ${SPECIAL}
for cmd in "$@"
do
sh -c "command ${cmd} > /; exit 0" 2>/dev/null || exit 1
done
set -- ${UTILS}
for cmd in "$@"
do
sh -c "command ${cmd} > /; exit 0" 2>/dev/null || exit 1
done