mirror of
git://git.code.sf.net/p/cdesktopenv/code
synced 2025-03-09 15:50:02 +00:00
Allow invoking path-bound built-in commands by direct path or preceding PATH
assignment (#275)
Path-bound builtins on ksh (such as /opt/ast/bin/cat) break some basic assumptions about paths in the shell that should hold true, e.g., that a path output by whence -p or command -v should actually point to an executable command. This commit should fix the following: 1. Path-bound built-ins (such as /opt/ast/bin/cat) can now be executed by invoking the canonical path (independently of the value of $PATH), so the following will now work as expected: $ /opt/ast/bin/cat --version version cat (AT&T Research) 2012-05-31 $ (PATH=/opt/ast/bin:$PATH; "$(whence -p cat)" --version) version cat (AT&T Research) 2012-05-31 In the event an external command by that path exists, the path-bound builtin will now override it when invoked using the canonical path. To invoke a possible external command at that path, you can still use a non-canonical path, e.g.: /opt//ast/bin/cat or /opt/ast/./bin/cat 2. Path-bound built-ins will now also be found on a PATH set locally using an assignment preceding the command, so something like the following will now work as expected: $ PATH=/opt/ast/bin cat --version version cat (AT&T Research) 2012-05-31 The builtin is not found by sh_exec() because the search for builtins happens long before invocation-local preceding assignments are processsed. This only happens in sh_ntfork(), before forking, or in sh_fork(), after forking. Both sh_ntfork() and sh_fork() call path_spawn() to do the actual path search, so a check there will cover both cases. This does mean the builtin will be run in the forked child if sh_fork() is used (which is the case on interactive shells with job.jobcontrol set, or always after compiling with SHOPT_SPAWN disabled). Searching for it before forking would mean fundamentally redesigning that function to be basically like sh_ntfork(), so this is hard to avoid. src/cmd/ksh93/sh/path.c: path_spawn(): - Before doing anything else, check if the passed path appears in the builtins tree as a pathbound builtin. If so, run it. Since a builtin will only be found if a preceding PATH assignment temporarily changed the PATH, and that assignment is currently in effect, we can just sh_run() the builtin so a nested sh_exec() invocation will find and run it. - If 'spawn' is not set (i.e. we must return), set errno to 0 and return -2. See the change to sh_ntfork() below. src/cmd/ksh93/sh/xec.c: - sh_exec(): When searching for built-ins and the restricted option isn't active, also search bltin_tree for names beginning with a slash. - sh_ntfork(): Only throw an error if the PID value returned is exactly -1. This allows path_spawn() to return -2 after running a built-in to tell sh_ntfork() to do the right things to restore state. src/cmd/ksh93/sh/parse.c: simple(): - When searching for built-ins at parse time, only exclude names containing a slash if the restricted option is active. This allows finding pointers to built-ins invoked by literal path like /opt/ast/bin/cat, as long as that does not result from an expansion. This is not actually necessary as sh_exec() will also cover this case, but it is an optimisation. src/lib/libcmd/getconf.c: - Replace convoluted deferral to external command by a simple invocation of the path to the native getconf command determined at compile time (by src/lib/libast/comp/conf.sh). Based on: https://github.com/ksh93/ksh/issues/138#issuecomment-816384871 If there is ever a system that has /opt/ast/bin/getconf as its default native external 'getconf', then there would still be an infinite recursion crash, but this seems extremely unlikely. Resolves: https://github.com/ksh93/ksh/issues/138
This commit is contained in:
parent
2c38fb93fd
commit
519bb08265
10 changed files with 82 additions and 144 deletions
|
@ -117,6 +117,18 @@ For more details, see the NEWS file and for complete details, see the git log.
|
|||
ksh -c 'OLDPWD=/bin; OLDPWD=/tmp cd - > /dev/null; echo $OLDPWD'
|
||||
ksh -c 'cd /var; PWD=/tmp cd /usr; echo $PWD'
|
||||
now prints '/bin' followed by '/var'.
|
||||
|
||||
23. Path-bound builtins (such as /opt/ast/bin/cat) can now be executed
|
||||
by invoking the canonical path, so the following will now work:
|
||||
$ /opt/ast/bin/cat --version
|
||||
version cat (AT&T Research) 2012-05-31
|
||||
$ (PATH=/opt/ast/bin:$PATH; "$(whence -p cat)" --version)
|
||||
version cat (AT&T Research) 2012-05-31
|
||||
In the event an external command by that path exists, the path-bound
|
||||
builtin will now override it when invoked using the canonical path.
|
||||
To invoke a possible external command at that path, you can still use
|
||||
a non-canonical path, e.g.: /opt//ast/bin/cat or /opt/ast/./bin/cat
|
||||
|
||||
____________________________________________________________________________
|
||||
|
||||
KSH-93 VS. KSH-88
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
#define SH_RELEASE_FORK "93u+m" /* only change if you develop a new ksh93 fork */
|
||||
#define SH_RELEASE_SVER "1.0.0-alpha" /* semantic version number: https://semver.org */
|
||||
#define SH_RELEASE_DATE "2021-04-13" /* must be in this format for $((.sh.version)) */
|
||||
#define SH_RELEASE_DATE "2021-04-14" /* must be in this format for $((.sh.version)) */
|
||||
#define SH_RELEASE_CPYR "(c) 2020-2021 Contributors to ksh " SH_RELEASE_FORK
|
||||
|
||||
/* Scripts sometimes field-split ${.sh.version}, so don't change amount of whitespace. */
|
||||
|
|
|
@ -1439,9 +1439,10 @@ static Shnode_t *simple(Lex_t *lexp,int flag, struct ionod *io)
|
|||
{
|
||||
if(!(argp->argflag&ARG_RAW))
|
||||
argno = -1;
|
||||
if(argno>=0 && argno++==cmdarg && !(flag&SH_ARRAY) && *argp->argval!='/')
|
||||
if(argno>=0 && argno++==cmdarg && !(flag&SH_ARRAY)
|
||||
&& !(sh_isoption(SH_RESTRICTED) && strchr(argp->argval,'/')))
|
||||
{
|
||||
/* check for builtin command */
|
||||
/* check for builtin command (including path-bound builtins executed by full pathname) */
|
||||
Namval_t *np=nv_bfsearch(argp->argval,lexp->sh->fun_tree, (Namval_t**)&t->comnamq,(char**)0);
|
||||
if(np && is_abuiltin(np))
|
||||
{
|
||||
|
|
|
@ -1085,6 +1085,20 @@ pid_t path_spawn(Shell_t *shp,const char *opath,register char **argv, char **env
|
|||
char *s, *v;
|
||||
int r, n, pidsize;
|
||||
pid_t pid= -1;
|
||||
if(nv_search(opath,shp->bltin_tree,0))
|
||||
{
|
||||
/* Found a path-bound built-in. Since this was not caught earlier in sh_exec(), it must
|
||||
have been found on a temporarily assigned PATH, as with 'PATH=/opt/ast/bin:$PATH cat'.
|
||||
Now that that local PATH assignment is in effect, we can just sh_run() the built-in. */
|
||||
int argc = 0;
|
||||
while(argv[argc])
|
||||
argc++;
|
||||
sh_run(argc,argv);
|
||||
if(!spawn)
|
||||
sh_done(shp,0);
|
||||
errno = 0;
|
||||
return(-2); /* treat like failure to spawn in sh_ntfork() except for the error message */
|
||||
}
|
||||
/* leave room for inserting _= pathname in environment */
|
||||
envp--;
|
||||
#if _lib_readlink
|
||||
|
|
|
@ -1218,9 +1218,19 @@ int sh_exec(register const Shnode_t *t, int flags)
|
|||
if(io)
|
||||
sfsync(shp->outpool);
|
||||
shp->lastpath = 0;
|
||||
if(!np && !strchr(com0,'/'))
|
||||
if(!np)
|
||||
{
|
||||
if(path_search(shp,com0,NIL(Pathcomp_t**),1))
|
||||
if(*com0 == '/' && !sh_isoption(SH_RESTRICTED))
|
||||
{
|
||||
/* Check for path-bound builtin referenced by absolute canonical path, in
|
||||
case the parser didn't provide a pointer (e.g. '$(whence -p cat) foo') */
|
||||
np = nv_search(com0, shp->bltin_tree, 0);
|
||||
}
|
||||
else if(strchr(com0,'/'))
|
||||
{
|
||||
/* Do nothing */
|
||||
}
|
||||
else if(path_search(shp,com0,NIL(Pathcomp_t**),1))
|
||||
{
|
||||
error_info.line = t->com.comline-shp->st.firstline;
|
||||
#if SHOPT_NAMESPACE
|
||||
|
@ -3617,7 +3627,7 @@ static pid_t sh_ntfork(Shell_t *shp,const Shnode_t *t,char *argv[],int *jobid,in
|
|||
fail:
|
||||
if(jobfork && spawnpid<0)
|
||||
job_fork(-2);
|
||||
if(spawnpid < 0) switch(errno=shp->path_err)
|
||||
if(spawnpid == -1) switch(errno=shp->path_err)
|
||||
{
|
||||
case ENOENT:
|
||||
errormsg(SH_DICT,ERROR_exit(ERROR_NOENT),e_found+4);
|
||||
|
|
|
@ -1184,15 +1184,23 @@ got=$(ulimit -t unlimited; uname -d > /dev/null; uname -o)
|
|||
"(expected $(printf %q "$exp"), got $(printf %q "$got"))"
|
||||
|
||||
# ======
|
||||
# Default path-bound builtins should be available to restricted shells if they are in $PATH on invocation
|
||||
# https://github.com/ksh93/ksh/issues/138#issuecomment-813886069
|
||||
# https://github.com/ksh93/ksh/issues/138
|
||||
builtin -d cat
|
||||
if [[ $'\n'${ builtin; }$'\n' == *$'\n/opt/ast/bin/cat\n'* ]]
|
||||
then exp=' version cat (*) ????-??-??'
|
||||
got=$(/opt/ast/bin/cat --version 2>&1)
|
||||
[[ $got == $exp ]] || err_exit "path-bound builtin not executable by literal canonical path" \
|
||||
"(expected match of $(printf %q "$exp"), got $(printf %q "$got"))"
|
||||
got=$(PATH=/opt/ast/bin:$PATH; "${ whence -p cat; }" --version 2>&1)
|
||||
[[ $got == $exp ]] || err_exit "path-bound builtin not executable by canonical path resulting from expansion" \
|
||||
"(expected match of $(printf %q "$exp"), got $(printf %q "$got"))"
|
||||
got=$(PATH=/opt/ast/bin:$PATH "$SHELL" -o restricted -c 'cat --version' 2>&1)
|
||||
[[ $got == $exp ]] || err_exit "restricted shells do not recognize path-bound builtins" \
|
||||
"(expected match of $(printf %q "$exp"), got $(printf %q "$got"))"
|
||||
else warning 'skipping path-bound builtin test for restricted shells: builtin /opt/ast/bin/cat not found'
|
||||
got=$(PATH=/opt/ast/bin cat --version 2>&1)
|
||||
[[ $got == $exp ]] || err_exit "path-bound builtin not found on PATH in preceding assignment" \
|
||||
"(expected match of $(printf %q "$exp"), got $(printf %q "$got"))"
|
||||
else warning 'skipping path-bound builtin tests: builtin /opt/ast/bin/cat not found'
|
||||
fi
|
||||
|
||||
# ======
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue