mirror of
git://git.code.sf.net/p/cdesktopenv/code
synced 2025-02-25 08:34:36 +00:00
The forking fix implemented in102868f8
and9d428f8f
, which stops the main shell's hash table from being cleared if PATH is changed in a subshell, can cause a significant performance penalty for certain scripts that do something like ( PATH=... command foo ) in a subshell, especially if done repeatedly. This is because the hash table is cleared (and hence a subshell forks) even for temporary PATH assignments preceding commands. It also just plain doesn't work. For instance: $ hash -r; (ls) >/dev/null; hash ls=/bin/ls Simply running an external command in a subshell caches the path in the hash table that is shared with a main shell. To remedy this, we would have to fork the subshell before forking any external command. And that would be an unacceptable performance regression. Virtual subshells do not need to fork when changing PATH if they get their own hash tables. This commit adds these. The code for alias subshell trees (which was removed inec888867
because they were broken and unneeded) provided the beginning of a template for their implementation. src/cmd/ksh93/sh/subshell.c: - struct subshell: Add strack pointer to subshell hash table. - Add sh_subtracktree(): return pointer to subshell hash table. - sh_subfuntree(): Refactor a bit for legibility. - sh_subshell(): Add code for cleaning up subshell hash table. src/cmd/ksh93/sh/name.c: - nv_putval(): Remove code to fork a subshell upon resetting PATH. - nv_rehash(): When in a subshell, invalidate a hash table entry for a subshell by creating the subshell scope if needed, then giving that entry the NV_NOALIAS attribute to invalidate it. src/cmd/ksh93/sh/path.c: path_search(): - To set a tracked alias/hash table entry, use sh_subtracktree() and pass the HASH_NOSCOPE flag to nv_search() so that any new entries are added to the current subshell table (if any) and do not influence any parent scopes. src/cmd/ksh93/bltins/typeset.c: b_alias(): - b_alias(): For hash table entries, use sh_subtracktree() instead of forking a subshell. Keep forking for normal aliases. - setall(): To set a tracked alias/hash table entry, pass the HASH_NOSCOPE flag to nv_search() so that any new entries are added to the current subshell table (if any) and do not influence any parent scopes. src/cmd/ksh93/sh/init.c: put_restricted(): - Update code for clearing the hash table (when changing $PATH) to use sh_subtracktree(). src/cmd/ksh93/bltins/cd_pwd.c: - When invalidating path name bindings to relative paths, use the subshell hash tree if applicable by calling sh_subtracktree(). - rehash(): Call nv_rehash() instead of _nv_unset()ting the hash table entry; this is needed to work correctly in subshells. src/cmd/ksh93/tests/leaks.sh: - Add leak tests for various PATH-related operations in the main shell and in a virtual subshell. - Several pre-existing memory leaks are exposed by the new tests (I've confirmed these in 93u+). The tests are disabled and marked TODO for now, as these bugs have not yet been fixed. src/cmd/ksh93/tests/subshell.sh: - Update. Resolves: https://github.com/ksh93/ksh/issues/66
1825 lines
41 KiB
C
1825 lines
41 KiB
C
/***********************************************************************
|
|
* *
|
|
* This software is part of the ast package *
|
|
* Copyright (c) 1982-2012 AT&T Intellectual Property *
|
|
* and is licensed under the *
|
|
* Eclipse Public License, Version 1.0 *
|
|
* by AT&T Intellectual Property *
|
|
* *
|
|
* A copy of the License is available at *
|
|
* http://www.eclipse.org/org/documents/epl-v10.html *
|
|
* (with md5 checksum b35adb5213ca9657e911e9befb180842) *
|
|
* *
|
|
* Information and Software Systems Research *
|
|
* AT&T Research *
|
|
* Florham Park NJ *
|
|
* *
|
|
* David Korn <dgk@research.att.com> *
|
|
* *
|
|
***********************************************************************/
|
|
#pragma prototyped
|
|
/*
|
|
* David Korn
|
|
* AT&T Labs
|
|
*
|
|
*/
|
|
|
|
#include "defs.h"
|
|
#include <fcin.h>
|
|
#include <ls.h>
|
|
#include <nval.h>
|
|
#include "variables.h"
|
|
#include "path.h"
|
|
#include "io.h"
|
|
#include "jobs.h"
|
|
#include "history.h"
|
|
#include "test.h"
|
|
#include "FEATURE/dynamic"
|
|
#include "FEATURE/externs"
|
|
#if SHOPT_PFSH
|
|
# ifdef _hdr_exec_attr
|
|
# include <exec_attr.h>
|
|
# endif
|
|
# if _lib_vfork
|
|
# include <ast_vfork.h>
|
|
# else
|
|
# define vfork() fork()
|
|
# endif
|
|
#endif
|
|
|
|
#define RW_ALL (S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR|S_IWGRP|S_IWOTH)
|
|
#define LIBCMD "cmd"
|
|
|
|
|
|
static int canexecute(Shell_t*,char*,int);
|
|
static void funload(Shell_t*,int,const char*);
|
|
static void exscript(Shell_t*,char*, char*[], char**);
|
|
static int path_chkpaths(Shell_t*,Pathcomp_t*,Pathcomp_t*,Pathcomp_t*,int);
|
|
static void path_checkdup(Shell_t *shp,register Pathcomp_t*);
|
|
static Pathcomp_t *defpath_init(Shell_t *shp);
|
|
|
|
static const char *std_path = NULL;
|
|
|
|
static int onstdpath(Shell_t *shp, const char *name)
|
|
{
|
|
if(!std_path)
|
|
defpath_init(shp);
|
|
register const char *cp = std_path, *sp;
|
|
if(cp)
|
|
while(*cp)
|
|
{
|
|
for(sp=name; *sp && (*cp == *sp); sp++,cp++);
|
|
if(*sp==0 && (*cp==0 || *cp==':'))
|
|
return(1);
|
|
while(*cp && *cp++!=':');
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
#if SHOPT_PFSH
|
|
int path_xattr(Shell_t *shp, const char *path, char *rpath)
|
|
{
|
|
char resolvedpath[PATH_MAX + 1];
|
|
if (shp->gd->user && *shp->gd->user)
|
|
{
|
|
execattr_t *pf;
|
|
if(!rpath)
|
|
rpath = resolvedpath;
|
|
if (!realpath(path, resolvedpath))
|
|
return -1;
|
|
if(pf=getexecuser(shp->gd->user, KV_COMMAND, resolvedpath, GET_ONE))
|
|
{
|
|
if (!pf->attr || pf->attr->length == 0)
|
|
{
|
|
free_execattr(pf);
|
|
return(0);
|
|
}
|
|
free_execattr(pf);
|
|
return(1);
|
|
}
|
|
}
|
|
errno = ENOENT;
|
|
return(-1);
|
|
}
|
|
#endif /* SHOPT_PFSH */
|
|
|
|
static pid_t path_pfexecve(Shell_t *shp,const char *path, char *argv[],char *const envp[],int spawn)
|
|
{
|
|
#if SHOPT_PFSH
|
|
char resolvedpath[PATH_MAX + 1];
|
|
pid_t pid;
|
|
if(spawn)
|
|
{
|
|
while((pid = vfork()) < 0)
|
|
_sh_fork(shp,pid, 0, (int*)0);
|
|
if(pid)
|
|
return(pid);
|
|
}
|
|
if(!sh_isoption(SH_PFSH))
|
|
return(execve(path, argv, envp));
|
|
/* Solaris implements realpath(3C) using the resolvepath(2) */
|
|
/* system call so we can save us to call access(2) first */
|
|
|
|
/* we can exec the command directly instead of via pfexec(1) if */
|
|
/* there is a matching entry without attributes in exec_attr(4) */
|
|
if(!path_xattr(shp,path,resolvedpath))
|
|
return(execve(path, argv, envp));
|
|
--argv;
|
|
argv[0] = argv[1];
|
|
argv[1] = resolvedpath;
|
|
return(execve("/usr/bin/pfexec", argv, envp));
|
|
#else
|
|
return(execve(path, argv, envp));
|
|
#endif
|
|
}
|
|
|
|
|
|
static pid_t _spawnveg(Shell_t *shp,const char *path, char* const argv[], char* const envp[], pid_t pgid)
|
|
{
|
|
pid_t pid;
|
|
while(1)
|
|
{
|
|
sh_stats(STAT_SPAWN);
|
|
pid = spawnveg(path,argv,envp,pgid);
|
|
if(pid>=0 || errno!=EAGAIN)
|
|
break;
|
|
}
|
|
return(pid);
|
|
}
|
|
|
|
/*
|
|
* used with command -x to run the command in multiple passes
|
|
* spawn is non-zero when invoked via spawn
|
|
* the exitval is set to the maximum for each execution
|
|
*/
|
|
static pid_t path_xargs(Shell_t *shp,const char *path, char *argv[],char *const envp[], int spawn)
|
|
{
|
|
register char *cp, **av, **xv;
|
|
char **avlast= &argv[shp->xargmax], **saveargs=0;
|
|
char *const *ev;
|
|
long size, left;
|
|
int nlast=1,n,exitval=0;
|
|
pid_t pid;
|
|
if(shp->xargmin < 0)
|
|
return((pid_t)-1);
|
|
size = shp->gd->lim.arg_max-1024;
|
|
for(ev=envp; cp= *ev; ev++)
|
|
size -= strlen(cp)-1;
|
|
for(av=argv; (cp= *av) && av< &argv[shp->xargmin]; av++)
|
|
size -= strlen(cp)-1;
|
|
for(av=avlast; cp= *av; av++,nlast++)
|
|
size -= strlen(cp)-1;
|
|
av = &argv[shp->xargmin];
|
|
if(!spawn)
|
|
job_clear();
|
|
shp->exitval = 0;
|
|
while(av<avlast)
|
|
{
|
|
/* for each argument, account for terminating zero and 16-byte alignment */
|
|
for(xv=av,left=size; left>0 && av<avlast;)
|
|
{
|
|
n = strlen(*av++) + 1 + 16;
|
|
left -= n + n % 16;
|
|
}
|
|
/* leave at least two for last */
|
|
if(left<0 && (avlast-av)<2)
|
|
av--;
|
|
if(xv==&argv[shp->xargmin])
|
|
{
|
|
n = nlast*sizeof(char*);
|
|
saveargs = (char**)malloc(n);
|
|
memcpy((void*)saveargs, (void*)av, n);
|
|
memcpy((void*)av,(void*)avlast,n);
|
|
}
|
|
else
|
|
{
|
|
for(n=shp->xargmin; xv < av; xv++)
|
|
argv[n++] = *xv;
|
|
for(xv=avlast; cp= *xv; xv++)
|
|
argv[n++] = cp;
|
|
argv[n] = 0;
|
|
}
|
|
if(saveargs || av<avlast || (exitval && !spawn))
|
|
{
|
|
if((pid=_spawnveg(shp,path,argv,envp,0)) < 0)
|
|
return(-1);
|
|
job_post(shp,pid,0);
|
|
job_wait(pid);
|
|
if(shp->exitval>exitval)
|
|
exitval = shp->exitval;
|
|
if(saveargs)
|
|
{
|
|
memcpy((void*)av,saveargs,n);
|
|
free((void*)saveargs);
|
|
saveargs = 0;
|
|
}
|
|
}
|
|
else if(spawn && !sh_isoption(SH_PFSH))
|
|
{
|
|
shp->xargexit = exitval;
|
|
if(saveargs)
|
|
free((void*)saveargs);
|
|
return(_spawnveg(shp,path,argv,envp,spawn>>1));
|
|
}
|
|
else
|
|
{
|
|
if(saveargs)
|
|
free((void*)saveargs);
|
|
return(path_pfexecve(shp,path,argv,envp,spawn));
|
|
}
|
|
}
|
|
if(!spawn)
|
|
exit(exitval);
|
|
return((pid_t)-1);
|
|
}
|
|
|
|
/*
|
|
* make sure PWD is set up correctly
|
|
* Return the present working directory
|
|
* Invokes getcwd() if necessary
|
|
* Sets the PWD variable to this value
|
|
*/
|
|
char *path_pwd(Shell_t *shp,int flag)
|
|
{
|
|
register char *cp;
|
|
register int count = 0;
|
|
NOT_USED(flag);
|
|
if(shp->pwd)
|
|
return((char*)shp->pwd);
|
|
while(1)
|
|
{
|
|
/* try from lowest to highest */
|
|
switch(count++)
|
|
{
|
|
case 0:
|
|
cp = nv_getval(PWDNOD);
|
|
break;
|
|
case 1:
|
|
cp = nv_getval(HOME);
|
|
break;
|
|
case 2:
|
|
cp = "/";
|
|
break;
|
|
case 3:
|
|
{
|
|
if(cp=getcwd(NIL(char*),0))
|
|
{
|
|
nv_offattr(PWDNOD,NV_NOFREE);
|
|
_nv_unset(PWDNOD,0);
|
|
PWDNOD->nvalue.cp = cp;
|
|
goto skip;
|
|
}
|
|
break;
|
|
}
|
|
case 4:
|
|
return((char*)e_dot);
|
|
}
|
|
if(cp && *cp=='/' && test_inode(cp,e_dot))
|
|
break;
|
|
}
|
|
if(count>1)
|
|
{
|
|
nv_offattr(PWDNOD,NV_NOFREE);
|
|
nv_putval(PWDNOD,cp,NV_RDONLY);
|
|
}
|
|
skip:
|
|
nv_onattr(PWDNOD,NV_NOFREE|NV_EXPORT);
|
|
shp->pwd = (char*)(PWDNOD->nvalue.cp);
|
|
return(cp);
|
|
}
|
|
|
|
/*
|
|
* delete current Pathcomp_t structure
|
|
*/
|
|
void path_delete(Pathcomp_t *first)
|
|
{
|
|
register Pathcomp_t *pp=first, *old=0, *ppnext;
|
|
while(pp)
|
|
{
|
|
ppnext = pp->next;
|
|
if(--pp->refcount<=0)
|
|
{
|
|
if(pp->lib)
|
|
free((void*)pp->lib);
|
|
if(pp->bbuf)
|
|
free((void*)pp->bbuf);
|
|
free((void*)pp);
|
|
if(old)
|
|
old->next = ppnext;
|
|
}
|
|
else
|
|
old = pp;
|
|
pp = ppnext;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* returns library variable from .paths
|
|
* The value might be returned on the stack overwriting path
|
|
*/
|
|
static char *path_lib(Shell_t *shp,Pathcomp_t *pp, char *path)
|
|
{
|
|
register char *last = strrchr(path,'/');
|
|
register int r;
|
|
struct stat statb;
|
|
if(last)
|
|
*last = 0;
|
|
else
|
|
path = ".";
|
|
r = stat(path,&statb);
|
|
if(last)
|
|
*last = '/';
|
|
if(r>=0)
|
|
{
|
|
Pathcomp_t pcomp;
|
|
char save[8];
|
|
for( ;pp; pp=pp->next)
|
|
{
|
|
path_checkdup(shp,pp);
|
|
if(pp->ino==statb.st_ino && pp->dev==statb.st_dev && pp->mtime==statb.st_mtime)
|
|
return(pp->lib);
|
|
}
|
|
pcomp.len = 0;
|
|
if(last)
|
|
pcomp.len = last-path;
|
|
memcpy((void*)save, (void*)stakptr(PATH_OFFSET+pcomp.len),sizeof(save));
|
|
if(path_chkpaths(shp,(Pathcomp_t*)0,(Pathcomp_t*)0,&pcomp,PATH_OFFSET))
|
|
return(stakfreeze(1));
|
|
memcpy((void*)stakptr(PATH_OFFSET+pcomp.len),(void*)save,sizeof(save));
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* check for duplicate directories on PATH
|
|
*/
|
|
static void path_checkdup(Shell_t *shp,register Pathcomp_t *pp)
|
|
{
|
|
register char *name = pp->name;
|
|
register Pathcomp_t *oldpp,*first;
|
|
register int flag=0;
|
|
struct stat statb;
|
|
if(stat(name,&statb)<0 || !S_ISDIR(statb.st_mode))
|
|
{
|
|
pp->flags |= PATH_SKIP;
|
|
pp->dev = *name=='/';
|
|
return;
|
|
}
|
|
pp->mtime = statb.st_mtime;
|
|
pp->ino = statb.st_ino;
|
|
pp->dev = statb.st_dev;
|
|
if(*name=='/' && onstdpath(shp, name))
|
|
flag = PATH_STD_DIR;
|
|
first = (pp->flags&PATH_CDPATH)?(Pathcomp_t*)shp->cdpathlist:path_get(shp,"");
|
|
for(oldpp=first; oldpp && oldpp!=pp; oldpp=oldpp->next)
|
|
{
|
|
if(pp->ino==oldpp->ino && pp->dev==oldpp->dev && pp->mtime==oldpp->mtime)
|
|
{
|
|
flag |= PATH_SKIP;
|
|
break;
|
|
}
|
|
}
|
|
pp->flags |= flag;
|
|
if(((pp->flags&(PATH_PATH|PATH_SKIP))==PATH_PATH))
|
|
{
|
|
int offset = staktell();
|
|
stakputs(name);
|
|
path_chkpaths(shp,first,0,pp,offset);
|
|
stakseek(offset);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* write the next path to search on the current stack
|
|
* if last is given, all paths that come before <last> are skipped
|
|
* the next pathcomp is returned.
|
|
*/
|
|
Pathcomp_t *path_nextcomp(Shell_t *shp,register Pathcomp_t *pp, const char *name, Pathcomp_t *last)
|
|
{
|
|
Pathcomp_t *ppnext;
|
|
stakseek(PATH_OFFSET);
|
|
if(*name=='/')
|
|
pp = 0;
|
|
else
|
|
{
|
|
for(;pp && pp!=last;pp=ppnext)
|
|
{
|
|
ppnext = pp->next;
|
|
if(!pp->dev && !pp->ino)
|
|
path_checkdup(shp,pp);
|
|
if(pp->flags&PATH_SKIP)
|
|
return(ppnext);
|
|
if(!last || *pp->name!='/')
|
|
break;
|
|
}
|
|
if(!pp) /* this should not happen */
|
|
pp = last;
|
|
}
|
|
if(pp && (pp->name[0]!='.' || pp->name[1]))
|
|
{
|
|
if(*pp->name!='/')
|
|
{
|
|
stakputs(path_pwd(shp,1));
|
|
if(*stakptr(staktell()-1)!='/')
|
|
stakputc('/');
|
|
}
|
|
stakwrite(pp->name,pp->len);
|
|
if(pp->name[pp->len-1]!='/')
|
|
stakputc('/');
|
|
}
|
|
stakputs(name);
|
|
stakputc(0);
|
|
while(pp && pp!=last && (pp=pp->next))
|
|
{
|
|
if(!(pp->flags&PATH_SKIP))
|
|
return(pp);
|
|
}
|
|
return((Pathcomp_t*)0);
|
|
}
|
|
|
|
static Pathcomp_t* defpath_init(Shell_t *shp)
|
|
{
|
|
if(!std_path && !(std_path=astconf("PATH",NIL(char*),NIL(char*))))
|
|
abort();
|
|
Pathcomp_t *pp = (void*)path_addpath(shp,(Pathcomp_t*)0,(std_path),PATH_PATH);
|
|
return(pp);
|
|
}
|
|
|
|
static void path_init(Shell_t *shp)
|
|
{
|
|
const char *val;
|
|
Pathcomp_t *pp;
|
|
if(val=sh_scoped(shp,(PATHNOD))->nvalue.cp)
|
|
{
|
|
shp->pathlist = pp = (void*)path_addpath(shp,(Pathcomp_t*)shp->pathlist,val,PATH_PATH);
|
|
}
|
|
else
|
|
{
|
|
if(!(pp=(Pathcomp_t*)shp->defpathlist))
|
|
pp = defpath_init(shp);
|
|
shp->pathlist = (void*)path_dup(pp);
|
|
}
|
|
if(val=sh_scoped(shp,(FPATHNOD))->nvalue.cp)
|
|
{
|
|
pp = (void*)path_addpath(shp,(Pathcomp_t*)shp->pathlist,val,PATH_FPATH);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* returns that pathlist to search
|
|
*/
|
|
Pathcomp_t *path_get(register Shell_t *shp,register const char *name)
|
|
{
|
|
register Pathcomp_t *pp=0;
|
|
if(*name && strchr(name,'/'))
|
|
return(0);
|
|
if(!sh_isstate(SH_DEFPATH))
|
|
{
|
|
if(!shp->pathlist)
|
|
path_init(shp);
|
|
pp = (Pathcomp_t*)shp->pathlist;
|
|
}
|
|
if(!pp && (!(sh_scoped(shp,PATHNOD)->nvalue.cp)) || sh_isstate(SH_DEFPATH))
|
|
{
|
|
if(!(pp=(Pathcomp_t*)shp->defpathlist))
|
|
pp = defpath_init(shp);
|
|
}
|
|
return(pp);
|
|
}
|
|
|
|
/*
|
|
* open file corresponding to name using path give by <pp>
|
|
*/
|
|
static int path_opentype(Shell_t *shp,const char *name, register Pathcomp_t *pp, int fun)
|
|
{
|
|
register int fd= -1;
|
|
struct stat statb;
|
|
Pathcomp_t *nextpp;
|
|
|
|
if(!pp && !shp->pathlist)
|
|
path_init(shp);
|
|
if(!fun && strchr(name,'/'))
|
|
{
|
|
if(sh_isoption(SH_RESTRICTED))
|
|
errormsg(SH_DICT,ERROR_exit(1),e_restricted,name);
|
|
}
|
|
|
|
nextpp = pp;
|
|
do
|
|
{
|
|
pp = nextpp;
|
|
nextpp = path_nextcomp(shp,pp,name,0);
|
|
if(pp && (pp->flags&PATH_SKIP))
|
|
continue;
|
|
if(fun && (!pp || !(pp->flags&PATH_FPATH)))
|
|
continue;
|
|
if((fd = sh_open(path_relative(shp,stakptr(PATH_OFFSET)),O_RDONLY,0)) >= 0)
|
|
{
|
|
if(fstat(fd,&statb)<0 || S_ISDIR(statb.st_mode))
|
|
{
|
|
errno = EISDIR;
|
|
sh_close(fd);
|
|
fd = -1;
|
|
}
|
|
}
|
|
}
|
|
while(fd<0 && nextpp);
|
|
|
|
if(fd>=0 && (fd = sh_iomovefd(fd)) > 0)
|
|
{
|
|
fcntl(fd,F_SETFD,FD_CLOEXEC);
|
|
shp->fdstatus[fd] |= IOCLEX;
|
|
}
|
|
return(fd);
|
|
}
|
|
|
|
/*
|
|
* open file corresponding to name using path give by <pp>
|
|
*/
|
|
int path_open(Shell_t *shp,const char *name, register Pathcomp_t *pp)
|
|
{
|
|
return(path_opentype(shp,name,pp,0));
|
|
}
|
|
|
|
/*
|
|
* given a pathname return the base name
|
|
*/
|
|
|
|
char *path_basename(register const char *name)
|
|
{
|
|
register const char *start = name;
|
|
while (*name)
|
|
if ((*name++ == '/') && *name) /* don't trim trailing / */
|
|
start = name;
|
|
return ((char*)start);
|
|
}
|
|
|
|
char *path_fullname(Shell_t *shp,const char *name)
|
|
{
|
|
int len=strlen(name)+1,dirlen=0;
|
|
char *path,*pwd;
|
|
if(*name!='/')
|
|
{
|
|
pwd = path_pwd(shp,1);
|
|
dirlen = strlen(pwd)+1;
|
|
}
|
|
path = (char*)malloc(len+dirlen);
|
|
if(dirlen)
|
|
{
|
|
memcpy((void*)path,(void*)pwd,dirlen);
|
|
path[dirlen-1] = '/';
|
|
}
|
|
memcpy((void*)&path[dirlen],(void*)name,len);
|
|
pathcanon(path,0);
|
|
return(path);
|
|
}
|
|
|
|
/*
|
|
* load functions from file <fno>
|
|
*/
|
|
static void funload(Shell_t *shp,int fno, const char *name)
|
|
{
|
|
char *pname,*oldname=shp->st.filename, buff[IOBSIZE+1];
|
|
Namval_t *np;
|
|
struct Ufunction *rp,*rpfirst;
|
|
int savestates = sh_getstate(), oldload=shp->funload, savelineno = shp->inlineno;
|
|
pname = path_fullname(shp,stakptr(PATH_OFFSET));
|
|
if(shp->fpathdict && (rp = dtmatch(shp->fpathdict,(void*)pname)))
|
|
{
|
|
Dt_t *funtree = sh_subfuntree(1);
|
|
while(1)
|
|
{
|
|
rpfirst = dtprev(shp->fpathdict,rp);
|
|
if(!rpfirst || strcmp(pname,rpfirst->fname))
|
|
break;
|
|
rp = rpfirst;
|
|
}
|
|
do
|
|
{
|
|
if((np = dtsearch(funtree,rp->np)) && is_afunction(np))
|
|
{
|
|
if(np->nvalue.rp)
|
|
np->nvalue.rp->fdict = 0;
|
|
nv_delete(np,funtree,NV_NOFREE);
|
|
}
|
|
dtinsert(funtree,rp->np);
|
|
rp->fdict = funtree;
|
|
}
|
|
while((rp=dtnext(shp->fpathdict,rp)) && strcmp(pname,rp->fname)==0);
|
|
sh_close(fno);
|
|
free((void*)pname);
|
|
return;
|
|
}
|
|
sh_onstate(SH_NOALIAS);
|
|
shp->readscript = (char*)name;
|
|
shp->st.filename = pname;
|
|
shp->funload = 1;
|
|
shp->inlineno = 1;
|
|
error_info.line = 0;
|
|
sh_eval(sfnew(NIL(Sfio_t*),buff,IOBSIZE,fno,SF_READ),SH_FUNEVAL);
|
|
sh_close(fno);
|
|
shp->readscript = 0;
|
|
#if SHOPT_NAMESPACE
|
|
if(shp->namespace)
|
|
np = sh_fsearch(shp,name,0);
|
|
else
|
|
#endif /* SHOPT_NAMESPACE */
|
|
np = nv_search(name,shp->fun_tree,0);
|
|
if(!np || !np->nvalue.ip)
|
|
pname = stakcopy(shp->st.filename);
|
|
else
|
|
pname = 0;
|
|
free((void*)shp->st.filename);
|
|
shp->funload = oldload;
|
|
shp->inlineno = savelineno;
|
|
shp->st.filename = oldname;
|
|
sh_setstate(savestates);
|
|
if(pname)
|
|
errormsg(SH_DICT,ERROR_exit(ERROR_NOEXEC),e_funload,name,pname);
|
|
}
|
|
|
|
/*
|
|
* do a path search and track alias if requested
|
|
*
|
|
* If flag is 0, or if name not found, then try autoloading function and return 1 if successful.
|
|
* If flag is >=1, do a regular path search. If it yields an autoloadable function, load it.
|
|
* If flag is 2 or 3, never autoload a function but return 1 if name found on FPATH.
|
|
* If flag is 3, no tracked alias will be set (IOW, the result won't be cached in the hash table).
|
|
* If oldpp is not NULL, it will contain a pointer to the path component
|
|
* where it was found.
|
|
*
|
|
* path_search() returns 1/true if:
|
|
* - the given absolute path was found executable
|
|
* - the given name is a function or non-path-bound builtin, and a path search found nothing external
|
|
* - the given name matched an autoloadable function on FPATH
|
|
*
|
|
* path_search() returns 0/false if:
|
|
* - the given relative path was found executable; the PWD is prefixed to make it absolute
|
|
* - a tracked alias (a.k.a. hash table entry) was found and used
|
|
* - the given name was found on PATH as an executable command or path-bound builtin (irrespective of
|
|
* whether it exists as a function or normal builtin); its full path is written on the stack, except
|
|
* if the matching $PATH entry is '.' or empty, the simple name is written without prefixing the PWD
|
|
* - nothing executable was found
|
|
*/
|
|
|
|
int path_search(Shell_t *shp,register const char *name,Pathcomp_t **oldpp, int flag)
|
|
{
|
|
register Namval_t *np;
|
|
register int fno;
|
|
Pathcomp_t *pp=0;
|
|
if(name && strchr(name,'/'))
|
|
{
|
|
char *pwd;
|
|
stakseek(PATH_OFFSET);
|
|
stakputs(name);
|
|
if(canexecute(shp,stakptr(PATH_OFFSET),0)<0)
|
|
{
|
|
*stakptr(PATH_OFFSET) = 0;
|
|
return(0);
|
|
}
|
|
if(*name=='/')
|
|
return(1);
|
|
stakseek(PATH_OFFSET);
|
|
pwd = path_pwd(shp,1);
|
|
if(pwd[1]) /* if pwd=="/", avoid starting with "//" */
|
|
stakputs(pwd);
|
|
stakputc('/');
|
|
stakputs(name);
|
|
stakputc(0);
|
|
return(0);
|
|
}
|
|
if(sh_isstate(SH_DEFPATH))
|
|
{
|
|
if(!shp->defpathlist)
|
|
defpath_init(shp);
|
|
}
|
|
else if(!shp->pathlist)
|
|
path_init(shp);
|
|
if(flag)
|
|
{
|
|
/* if a tracked alias exists and we're not searching the default path, use it */
|
|
if(!sh_isstate(SH_DEFPATH)
|
|
&& !(flag&1)
|
|
&& (np=nv_search(name,shp->track_tree,0))
|
|
&& !nv_isattr(np,NV_NOALIAS)
|
|
&& (pp=(Pathcomp_t*)np->nvalue.cp))
|
|
{
|
|
stakseek(PATH_OFFSET);
|
|
path_nextcomp(shp,pp,name,pp);
|
|
if(oldpp)
|
|
*oldpp = pp;
|
|
stakputc(0);
|
|
return(0);
|
|
}
|
|
pp = path_absolute(shp,name,oldpp?*oldpp:NIL(Pathcomp_t*),flag);
|
|
if(oldpp)
|
|
*oldpp = pp;
|
|
if(!pp && (np=nv_search(name,shp->fun_tree,0))&&np->nvalue.ip)
|
|
return(1);
|
|
if(!pp)
|
|
*stakptr(PATH_OFFSET) = 0;
|
|
}
|
|
if(flag==0 || !pp || (pp->flags&PATH_FPATH))
|
|
{
|
|
if(!pp)
|
|
pp=sh_isstate(SH_DEFPATH)?shp->defpathlist:shp->pathlist;
|
|
if(pp && strmatch(name,e_alphanum) && (fno=path_opentype(shp,name,pp,1))>=0)
|
|
{
|
|
if(flag >= 2)
|
|
{
|
|
sh_close(fno);
|
|
return(1);
|
|
}
|
|
funload(shp,fno,name);
|
|
return(1);
|
|
}
|
|
*stakptr(PATH_OFFSET) = 0;
|
|
return(0);
|
|
}
|
|
else if(pp && !sh_isstate(SH_DEFPATH) && *name!='/' && flag<3)
|
|
{
|
|
if(np=nv_search(name,sh_subtracktree(1),NV_ADD|HASH_NOSCOPE))
|
|
path_alias(np,pp);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* do a path search and find the full pathname of file name
|
|
*
|
|
* If flag >= 2, do not autoload functions (cf. path_search()).
|
|
*/
|
|
Pathcomp_t *path_absolute(Shell_t *shp,register const char *name, Pathcomp_t *pp, int flag)
|
|
{
|
|
register int f,isfun;
|
|
int noexec=0;
|
|
Pathcomp_t *oldpp;
|
|
Namval_t *np;
|
|
char *cp;
|
|
char *bp;
|
|
shp->path_err = ENOENT;
|
|
if(!pp && !(pp=path_get(shp,"")))
|
|
return(0);
|
|
shp->path_err = 0;
|
|
while(1)
|
|
{
|
|
sh_sigcheck(shp);
|
|
shp->bltin_dir = 0;
|
|
while(oldpp=pp)
|
|
{
|
|
pp = path_nextcomp(shp,pp,name,0);
|
|
if(!(oldpp->flags&PATH_SKIP))
|
|
break;
|
|
}
|
|
if(!oldpp)
|
|
{
|
|
shp->path_err = ENOENT;
|
|
return(0);
|
|
}
|
|
isfun = (oldpp->flags&PATH_FPATH);
|
|
if(!isfun && !sh_isoption(SH_RESTRICTED))
|
|
{
|
|
#if SHOPT_DYNAMIC
|
|
Shbltin_f addr;
|
|
int n;
|
|
#endif
|
|
if(*stakptr(PATH_OFFSET)=='/' && nv_search(stakptr(PATH_OFFSET),shp->bltin_tree,0))
|
|
return(oldpp);
|
|
#if SHOPT_DYNAMIC
|
|
n = staktell();
|
|
stakputs("b_");
|
|
stakputs(name);
|
|
stakputc(0);
|
|
if((addr = sh_getlib(shp, stakptr(n), oldpp)) &&
|
|
(np = sh_addbuiltin(stakptr(PATH_OFFSET),addr,NiL)) &&
|
|
nv_isattr(np,NV_BLTINOPT))
|
|
{
|
|
shp->bltin_dir = 0;
|
|
return(oldpp);
|
|
}
|
|
stakseek(n);
|
|
while(bp = oldpp->blib)
|
|
{
|
|
char *fp;
|
|
void *dll;
|
|
int m;
|
|
if(fp = strchr(bp, ':'))
|
|
{
|
|
*fp++ = 0;
|
|
oldpp->blib = fp;
|
|
fp = 0;
|
|
}
|
|
else
|
|
{
|
|
fp = oldpp->bbuf;
|
|
oldpp->blib = oldpp->bbuf = 0;
|
|
}
|
|
n = staktell();
|
|
stakputs("b_");
|
|
stakputs(name);
|
|
stakputc(0);
|
|
m = staktell();
|
|
shp->bltin_dir = oldpp->name;
|
|
if(*bp!='/')
|
|
{
|
|
stakputs(oldpp->name);
|
|
stakputc('/');
|
|
}
|
|
stakputs(bp);
|
|
stakputc(0);
|
|
if(cp = strrchr(stakptr(m),'/'))
|
|
cp++;
|
|
else
|
|
cp = stakptr(m);
|
|
if(!strcmp(cp,LIBCMD) &&
|
|
(addr=(Shbltin_f)dlllook((void*)0,stakptr(n))) &&
|
|
(np = sh_addbuiltin(stakptr(PATH_OFFSET),addr,NiL)) &&
|
|
nv_isattr(np,NV_BLTINOPT))
|
|
{
|
|
found:
|
|
if(fp)
|
|
free(fp);
|
|
shp->bltin_dir = 0;
|
|
return(oldpp);
|
|
}
|
|
#ifdef SH_PLUGIN_VERSION
|
|
if (dll = dllplugin(SH_ID, stakptr(m), NiL, SH_PLUGIN_VERSION, NiL, RTLD_LAZY, NiL, 0))
|
|
sh_addlib(shp,dll,stakptr(m),oldpp);
|
|
#else
|
|
if (dll = dllplug(SH_ID, stakptr(m), NiL, RTLD_LAZY, NiL, 0))
|
|
{
|
|
/*
|
|
* this detects the 2007-05-11 builtin context change and also
|
|
* the 2008-03-30 opt_info.num change that hit libcmd::b_head
|
|
*/
|
|
|
|
if (libcmd && !dlllook(dll, "b_pids"))
|
|
{
|
|
dlclose(dll);
|
|
dll = 0;
|
|
}
|
|
else
|
|
sh_addlib(shp,dll,stakptr(m),oldpp);
|
|
}
|
|
#endif
|
|
if(dll &&
|
|
(addr=(Shbltin_f)dlllook(dll,stakptr(n))) &&
|
|
(!(np = sh_addbuiltin(stakptr(PATH_OFFSET),NiL,NiL)) || np->nvalue.bfp!=(Nambfp_f)addr) &&
|
|
(np = sh_addbuiltin(stakptr(PATH_OFFSET),addr,NiL)))
|
|
{
|
|
np->nvenv = dll;
|
|
goto found;
|
|
}
|
|
if(*stakptr(PATH_OFFSET)=='/' && nv_search(stakptr(PATH_OFFSET),shp->bltin_tree,0))
|
|
goto found;
|
|
if(fp)
|
|
free(fp);
|
|
stakseek(n);
|
|
}
|
|
#endif /* SHOPT_DYNAMIC */
|
|
}
|
|
shp->bltin_dir = 0;
|
|
sh_stats(STAT_PATHS);
|
|
f = canexecute(shp,stakptr(PATH_OFFSET),isfun);
|
|
if(isfun && f>=0 && (cp = strrchr(name,'.')))
|
|
{
|
|
*cp = 0;
|
|
if(nv_open(name,sh_subfuntree(1),NV_NOARRAY|NV_IDENT|NV_NOSCOPE))
|
|
f = -1;
|
|
*cp = '.';
|
|
}
|
|
if(isfun && f>=0)
|
|
{
|
|
if(flag < 2)
|
|
{
|
|
nv_onattr(nv_open(name,sh_subfuntree(1),NV_NOARRAY|NV_IDENT|NV_NOSCOPE),NV_LTOU|NV_FUNCTION);
|
|
funload(shp,f,name);
|
|
}
|
|
close(f);
|
|
return(0);
|
|
}
|
|
else if(f>=0 && (oldpp->flags & PATH_STD_DIR))
|
|
{
|
|
int n = staktell();
|
|
stakputs("/bin/");
|
|
stakputs(name);
|
|
stakputc(0);
|
|
np = nv_search(stakptr(n),shp->bltin_tree,0);
|
|
stakseek(n);
|
|
if(np)
|
|
{
|
|
n = np->nvflag;
|
|
np = sh_addbuiltin(stakptr(PATH_OFFSET),(Shbltin_f)np->nvalue.bfp,nv_context(np));
|
|
np->nvflag = n;
|
|
}
|
|
}
|
|
if(!pp || f>=0)
|
|
break;
|
|
if(errno!=ENOENT)
|
|
noexec = errno;
|
|
}
|
|
if(f<0)
|
|
{
|
|
shp->path_err = (noexec?noexec:ENOENT);
|
|
return(0);
|
|
}
|
|
stakputc(0);
|
|
return(oldpp);
|
|
}
|
|
|
|
/*
|
|
* returns 0 if path can execute
|
|
* sets exec_err if file is found but can't be executable
|
|
*/
|
|
#undef S_IXALL
|
|
#ifdef S_IXUSR
|
|
# define S_IXALL (S_IXUSR|S_IXGRP|S_IXOTH)
|
|
#else
|
|
# ifdef S_IEXEC
|
|
# define S_IXALL (S_IEXEC|(S_IEXEC>>3)|(S_IEXEC>>6))
|
|
# else
|
|
# define S_IXALL 0111
|
|
# endif /*S_EXEC */
|
|
#endif /* S_IXUSR */
|
|
|
|
static int canexecute(Shell_t *shp,register char *path, int isfun)
|
|
{
|
|
struct stat statb;
|
|
register int fd=0;
|
|
path = path_relative(shp,path);
|
|
if(isfun)
|
|
{
|
|
if((fd=open(path,O_RDONLY,0))<0 || fstat(fd,&statb)<0)
|
|
goto err;
|
|
}
|
|
else if(stat(path,&statb) < 0)
|
|
{
|
|
#if _WINIX
|
|
/* check for .exe or .bat suffix */
|
|
char *cp;
|
|
if(errno==ENOENT && (!(cp=strrchr(path,'.')) || strlen(cp)>4 || strchr(cp,'/')))
|
|
{
|
|
int offset = staktell()-1;
|
|
stakseek(offset);
|
|
stakputs(".bat");
|
|
path = stakptr(PATH_OFFSET);
|
|
if(stat(path,&statb) < 0)
|
|
{
|
|
if(errno!=ENOENT)
|
|
goto err;
|
|
memcpy(stakptr(offset),".sh",4);
|
|
if(stat(path,&statb) < 0)
|
|
goto err;
|
|
}
|
|
}
|
|
else
|
|
#endif /* _WINIX */
|
|
goto err;
|
|
}
|
|
errno = EPERM;
|
|
if(S_ISDIR(statb.st_mode))
|
|
errno = EISDIR;
|
|
else if((statb.st_mode&S_IXALL)==S_IXALL || sh_access(path,X_OK)>=0)
|
|
return(fd);
|
|
err:
|
|
if(isfun && fd>=0)
|
|
sh_close(fd);
|
|
return(-1);
|
|
}
|
|
|
|
/*
|
|
* Return path relative to present working directory
|
|
*/
|
|
|
|
char *path_relative(Shell_t *shp,register const char* file)
|
|
{
|
|
register const char *pwd;
|
|
register const char *fp = file;
|
|
/* can't relpath when shp->pwd not set */
|
|
if(!(pwd=shp->pwd))
|
|
return((char*)fp);
|
|
while(*pwd==*fp)
|
|
{
|
|
if(*pwd++==0)
|
|
return((char*)e_dot);
|
|
fp++;
|
|
}
|
|
if(*pwd==0 && *fp == '/')
|
|
{
|
|
while(*++fp=='/');
|
|
if(*fp)
|
|
return((char*)fp);
|
|
return((char*)e_dot);
|
|
}
|
|
return((char*)file);
|
|
}
|
|
|
|
void path_exec(Shell_t *shp,register const char *arg0,register char *argv[],struct argnod *local)
|
|
{
|
|
char **envp;
|
|
const char *opath;
|
|
Pathcomp_t *libpath, *pp=0;
|
|
int slash=0;
|
|
nv_setlist(local,NV_EXPORT|NV_IDENT|NV_ASSIGN,0);
|
|
envp = sh_envgen();
|
|
if(strchr(arg0,'/'))
|
|
{
|
|
slash=1;
|
|
/* name containing / not allowed for restricted shell */
|
|
if(sh_isoption(SH_RESTRICTED))
|
|
errormsg(SH_DICT,ERROR_exit(1),e_restricted,arg0);
|
|
}
|
|
else
|
|
pp=path_get(shp,arg0);
|
|
shp->path_err= ENOENT;
|
|
sfsync(NIL(Sfio_t*));
|
|
timerdel(NIL(void*));
|
|
/* find first path that has a library component */
|
|
while(pp && (pp->flags&PATH_SKIP))
|
|
pp = pp->next;
|
|
if(pp || slash) do
|
|
{
|
|
sh_sigcheck(shp);
|
|
if(libpath=pp)
|
|
{
|
|
pp = path_nextcomp(shp,pp,arg0,0);
|
|
opath = stakfreeze(1)+PATH_OFFSET;
|
|
}
|
|
else
|
|
opath = arg0;
|
|
path_spawn(shp,opath,argv,envp,libpath,0);
|
|
while(pp && (pp->flags&PATH_FPATH))
|
|
pp = path_nextcomp(shp,pp,arg0,0);
|
|
}
|
|
while(pp);
|
|
/* force an exit */
|
|
((struct checkpt*)shp->jmplist)->mode = SH_JMPEXIT;
|
|
if((errno=shp->path_err)==ENOENT)
|
|
errormsg(SH_DICT,ERROR_exit(ERROR_NOENT),e_found,arg0);
|
|
else
|
|
errormsg(SH_DICT,ERROR_system(ERROR_NOEXEC),e_exec,arg0);
|
|
}
|
|
|
|
pid_t path_spawn(Shell_t *shp,const char *opath,register char **argv, char **envp, Pathcomp_t *libpath, int spawn)
|
|
{
|
|
register char *path;
|
|
char **xp=0, *xval, *libenv = (libpath?libpath->lib:0);
|
|
Namval_t* np;
|
|
char *s, *v;
|
|
int r, n, pidsize;
|
|
pid_t pid= -1;
|
|
/* leave room for inserting _= pathname in environment */
|
|
envp--;
|
|
#if _lib_readlink
|
|
/* save original pathname */
|
|
stakseek(PATH_OFFSET);
|
|
pidsize = sfprintf(stkstd,"*%d*",spawn?shgd->current_pid:getppid());
|
|
stakputs(opath);
|
|
opath = stakfreeze(1)+PATH_OFFSET+pidsize;
|
|
/* only use tracked alias if we're not searching default path */
|
|
np = sh_isstate(SH_DEFPATH) ? NIL(Namval_t*) : nv_search(argv[0],shp->track_tree,0);
|
|
while(libpath && !libpath->lib)
|
|
libpath=libpath->next;
|
|
if(libpath && (!np || nv_size(np)>0))
|
|
{
|
|
/* check for symlink and use symlink name */
|
|
char buff[PATH_MAX+1];
|
|
char save[PATH_MAX+1];
|
|
stakseek(PATH_OFFSET);
|
|
stakputs(opath);
|
|
path = stakptr(PATH_OFFSET);
|
|
while((n=readlink(path,buff,PATH_MAX))>0)
|
|
{
|
|
buff[n] = 0;
|
|
n = PATH_OFFSET;
|
|
r = 0;
|
|
if((v=strrchr(path,'/')) && *buff!='/')
|
|
{
|
|
if(buff[0]=='.' && buff[1]=='.' && (r = strlen(path) + 1) <= PATH_MAX)
|
|
memcpy(save, path, r);
|
|
else
|
|
r = 0;
|
|
n += (v+1-path);
|
|
}
|
|
stakseek(n);
|
|
stakputs(buff);
|
|
stakputc(0);
|
|
path = stakptr(PATH_OFFSET);
|
|
if(v && buff[0]=='.' && buff[1]=='.')
|
|
{
|
|
pathcanon(path, 0);
|
|
if(r && access(path,X_OK))
|
|
{
|
|
memcpy(path, save, r);
|
|
break;
|
|
}
|
|
}
|
|
if(libenv = path_lib(shp,libpath,path))
|
|
break;
|
|
}
|
|
stakseek(0);
|
|
}
|
|
#endif
|
|
if(libenv && (v = strchr(libenv,'=')))
|
|
{
|
|
n = v - libenv;
|
|
*v = 0;
|
|
np = nv_open(libenv,shp->var_tree,0);
|
|
*v = '=';
|
|
s = nv_getval(np);
|
|
stakputs(libenv);
|
|
if(s)
|
|
{
|
|
stakputc(':');
|
|
stakputs(s);
|
|
}
|
|
v = stakfreeze(1);
|
|
r = 1;
|
|
xp = envp + 1;
|
|
while (s = *xp++)
|
|
{
|
|
if (strneq(s, v, n) && s[n] == '=')
|
|
{
|
|
xval = *--xp;
|
|
*xp = v;
|
|
r = 0;
|
|
break;
|
|
}
|
|
}
|
|
if (r)
|
|
{
|
|
*envp-- = v;
|
|
xp = 0;
|
|
}
|
|
}
|
|
if(!opath)
|
|
opath = stakptr(PATH_OFFSET);
|
|
envp[0] = (char*)opath-(PATH_OFFSET+pidsize);
|
|
envp[0][0] = '_';
|
|
envp[0][1] = '=';
|
|
sfsync(sfstderr);
|
|
sh_sigcheck(shp);
|
|
path = path_relative(shp,opath);
|
|
#ifdef SHELLMAGIC
|
|
if(*path!='/' && path!=opath)
|
|
{
|
|
/*
|
|
* The following code because execv(foo,) and execv(./foo,)
|
|
* may not yield the same results
|
|
*/
|
|
char *sp = (char*)malloc(strlen(path)+3);
|
|
sp[0] = '.';
|
|
sp[1] = '/';
|
|
strcpy(sp+2,path);
|
|
path = sp;
|
|
}
|
|
#endif /* SHELLMAGIC */
|
|
if(spawn && !sh_isoption(SH_PFSH))
|
|
pid = _spawnveg(shp,opath, &argv[0],envp, spawn>>1);
|
|
else
|
|
pid = path_pfexecve(shp,opath, &argv[0] ,envp,spawn);
|
|
if(xp)
|
|
*xp = xval;
|
|
#ifdef SHELLMAGIC
|
|
if(*path=='.' && path!=opath)
|
|
{
|
|
free(path);
|
|
path = path_relative(shp,opath);
|
|
}
|
|
#endif /* SHELLMAGIC */
|
|
if(pid>0)
|
|
return(pid);
|
|
switch(shp->path_err = errno)
|
|
{
|
|
case ENOEXEC:
|
|
#if SHOPT_SUID_EXEC
|
|
case EPERM:
|
|
/* some systems return EPERM if setuid bit is on */
|
|
#endif
|
|
errno = ENOEXEC;
|
|
if(spawn)
|
|
{
|
|
#ifdef _lib_fork
|
|
if(shp->subshell)
|
|
return(-1);
|
|
do
|
|
{
|
|
if((pid=fork())>0)
|
|
return(pid);
|
|
}
|
|
while(_sh_fork(shp,pid,0,(int*)0) < 0);
|
|
((struct checkpt*)shp->jmplist)->mode = SH_JMPEXIT;
|
|
#else
|
|
return(-1);
|
|
#endif
|
|
}
|
|
exscript(shp,path,argv,envp);
|
|
case EACCES:
|
|
{
|
|
struct stat statb;
|
|
if(stat(path,&statb)>=0)
|
|
{
|
|
if(S_ISDIR(statb.st_mode))
|
|
errno = EISDIR;
|
|
#ifdef S_ISSOCK
|
|
if(S_ISSOCK(statb.st_mode))
|
|
exscript(shp,path,argv,envp);
|
|
#endif
|
|
}
|
|
}
|
|
/* FALL THROUGH */
|
|
#ifdef ENAMETOOLONG
|
|
case ENAMETOOLONG:
|
|
#endif /* ENAMETOOLONG */
|
|
#if !SHOPT_SUID_EXEC
|
|
case EPERM:
|
|
#endif
|
|
shp->path_err = errno;
|
|
return(-1);
|
|
case ENOTDIR:
|
|
case ENOENT:
|
|
case EINTR:
|
|
#ifdef EMLINK
|
|
case EMLINK:
|
|
#endif /* EMLINK */
|
|
return(-1);
|
|
case E2BIG:
|
|
if(shp->xargmin)
|
|
{
|
|
pid = path_xargs(shp,opath, &argv[0] ,envp,spawn);
|
|
if(pid<0)
|
|
errormsg(SH_DICT,ERROR_system(ERROR_NOEXEC),"%s: 'command -x' failed",path);
|
|
return(pid);
|
|
}
|
|
default:
|
|
errormsg(SH_DICT,ERROR_system(ERROR_NOEXEC),e_exec,path);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* File is executable but not machine code.
|
|
* Assume file is a Shell script and execute it.
|
|
*/
|
|
|
|
static void exscript(Shell_t *shp,register char *path,register char *argv[],char **envp)
|
|
{
|
|
register Sfio_t *sp;
|
|
path = path_relative(shp,path);
|
|
shp->comdiv=0;
|
|
shp->bckpid = 0;
|
|
shp->st.ioset=0;
|
|
/* clean up any cooperating processes */
|
|
if(shp->cpipe[0]>0)
|
|
sh_pclose(shp->cpipe);
|
|
if(shp->cpid && shp->outpipe)
|
|
sh_close(*shp->outpipe);
|
|
shp->cpid = 0;
|
|
if(sp=fcfile())
|
|
while(sfstack(sp,SF_POPSTACK));
|
|
job_clear();
|
|
if(shp->infd>0 && (shp->fdstatus[shp->infd]&IOCLEX))
|
|
sh_close(shp->infd);
|
|
sh_setstate(sh_state(SH_FORKED));
|
|
sfsync(sfstderr);
|
|
#if SHOPT_SUID_EXEC && !SHOPT_PFSH
|
|
/* check if file cannot open for read or script is setuid/setgid */
|
|
{
|
|
static char name[] = "/tmp/euidXXXXXXXXXX";
|
|
register int n;
|
|
register uid_t euserid;
|
|
char *savet=0;
|
|
struct stat statb;
|
|
if((n=sh_open(path,O_RDONLY,0)) >= 0)
|
|
{
|
|
/* move <n> if n=0,1,2 */
|
|
n = sh_iomovefd(n);
|
|
if(fstat(n,&statb)>=0 && !(statb.st_mode&(S_ISUID|S_ISGID)))
|
|
goto openok;
|
|
sh_close(n);
|
|
}
|
|
if((euserid=geteuid()) != shp->gd->userid)
|
|
{
|
|
strncpy(name+9,fmtbase((long)shgd->current_pid,10,0),sizeof(name)-10);
|
|
/* create a suid open file with owner equal effective uid */
|
|
if((n=open(name,O_CREAT|O_TRUNC|O_WRONLY,S_ISUID|S_IXUSR)) < 0)
|
|
goto fail;
|
|
unlink(name);
|
|
/* make sure that file has right owner */
|
|
if(fstat(n,&statb)<0 || statb.st_uid != euserid)
|
|
goto fail;
|
|
if(n!=10)
|
|
{
|
|
sh_close(10);
|
|
fcntl(n, F_DUPFD, 10);
|
|
sh_close(n);
|
|
n=10;
|
|
}
|
|
}
|
|
savet = *--argv;
|
|
*argv = path;
|
|
path_pfexecve(shp,e_suidexec,argv,envp,0);
|
|
fail:
|
|
/*
|
|
* The following code is just for compatibility
|
|
*/
|
|
if((n=open(path,O_RDONLY,0)) < 0)
|
|
errormsg(SH_DICT,ERROR_system(ERROR_NOEXEC),e_exec,path);
|
|
if(savet)
|
|
*argv++ = savet;
|
|
openok:
|
|
shp->infd = n;
|
|
}
|
|
#else
|
|
if((shp->infd = sh_open(path,O_RDONLY,0)) < 0)
|
|
errormsg(SH_DICT,ERROR_system(ERROR_NOEXEC),e_exec,path);
|
|
#endif
|
|
shp->infd = sh_iomovefd(shp->infd);
|
|
#if SHOPT_ACCT
|
|
sh_accbegin(path) ; /* reset accounting */
|
|
#endif /* SHOPT_ACCT */
|
|
shp->arglist = sh_argcreate(argv);
|
|
shp->lastarg = strdup(path);
|
|
/* save name of calling command */
|
|
shp->readscript = error_info.id;
|
|
/* close history file if name has changed */
|
|
if(shp->gd->hist_ptr && (path=nv_getval(HISTFILE)) && strcmp(path,shp->gd->hist_ptr->histname))
|
|
{
|
|
hist_close(shp->gd->hist_ptr);
|
|
(HISTCUR)->nvalue.lp = 0;
|
|
}
|
|
sh_offstate(SH_FORKED);
|
|
if(shp->sigflag[SIGCHLD]==SH_SIGOFF)
|
|
shp->sigflag[SIGCHLD] = SH_SIGFAULT;
|
|
siglongjmp(*shp->jmplist,SH_JMPSCRIPT);
|
|
}
|
|
|
|
#if SHOPT_ACCT
|
|
# include <sys/acct.h>
|
|
# include "FEATURE/time"
|
|
|
|
static struct acct sabuf;
|
|
static struct tms buffer;
|
|
static clock_t before;
|
|
static char *SHACCT; /* set to value of SHACCT environment variable */
|
|
static shaccton; /* non-zero causes accounting record to be written */
|
|
static int compress(time_t);
|
|
/*
|
|
* initialize accounting, i.e., see if SHACCT variable set
|
|
*/
|
|
void sh_accinit(void)
|
|
{
|
|
SHACCT = getenv("SHACCT");
|
|
}
|
|
/*
|
|
* suspend accounting until turned on by sh_accbegin()
|
|
*/
|
|
void sh_accsusp(void)
|
|
{
|
|
shaccton=0;
|
|
#ifdef AEXPAND
|
|
sabuf.ac_flag |= AEXPND;
|
|
#endif /* AEXPAND */
|
|
}
|
|
|
|
/*
|
|
* begin an accounting record by recording start time
|
|
*/
|
|
void sh_accbegin(const char *cmdname)
|
|
{
|
|
if(SHACCT)
|
|
{
|
|
sabuf.ac_btime = time(NIL(time_t *));
|
|
before = times(&buffer);
|
|
sabuf.ac_uid = getuid();
|
|
sabuf.ac_gid = getgid();
|
|
strncpy(sabuf.ac_comm, (char*)path_basename(cmdname),
|
|
sizeof(sabuf.ac_comm));
|
|
shaccton = 1;
|
|
}
|
|
}
|
|
/*
|
|
* terminate an accounting record and append to accounting file
|
|
*/
|
|
void sh_accend(void)
|
|
{
|
|
int fd;
|
|
clock_t after;
|
|
|
|
if(shaccton)
|
|
{
|
|
after = times(&buffer);
|
|
sabuf.ac_utime = compress(buffer.tms_utime + buffer.tms_cutime);
|
|
sabuf.ac_stime = compress(buffer.tms_stime + buffer.tms_cstime);
|
|
sabuf.ac_etime = compress( (time_t)(after-before));
|
|
fd = open( SHACCT , O_WRONLY | O_APPEND | O_CREAT,RW_ALL);
|
|
write(fd, (const char*)&sabuf, sizeof( sabuf ));
|
|
close( fd);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Produce a pseudo-floating point representation
|
|
* with 3 bits base-8 exponent, 13 bits fraction.
|
|
*/
|
|
static int compress(register time_t t)
|
|
{
|
|
register int exp = 0, rund = 0;
|
|
|
|
while (t >= 8192)
|
|
{
|
|
exp++;
|
|
rund = t&04;
|
|
t >>= 3;
|
|
}
|
|
if (rund)
|
|
{
|
|
t++;
|
|
if (t >= 8192)
|
|
{
|
|
t >>= 3;
|
|
exp++;
|
|
}
|
|
}
|
|
return((exp<<13) + t);
|
|
}
|
|
#endif /* SHOPT_ACCT */
|
|
|
|
|
|
|
|
/*
|
|
* add a pathcomponent to the path search list and eliminate duplicates
|
|
* and non-existing absolute paths.
|
|
*/
|
|
static Pathcomp_t *path_addcomp(Shell_t *shp,Pathcomp_t *first, Pathcomp_t *old,const char *name, int flag)
|
|
{
|
|
register Pathcomp_t *pp, *oldpp;
|
|
int len, offset=staktell();
|
|
if(!(flag&PATH_BFPATH))
|
|
{
|
|
register const char *cp = name;
|
|
while(*cp && *cp!=':')
|
|
stakputc(*cp++);
|
|
len = staktell()-offset;
|
|
stakputc(0);
|
|
stakseek(offset);
|
|
name = (const char*)stakptr(offset);
|
|
}
|
|
else
|
|
len = strlen(name);
|
|
for(pp=first; pp; pp=pp->next)
|
|
{
|
|
if(len == pp->len && memcmp(name,pp->name,len)==0)
|
|
{
|
|
pp->flags |= flag;
|
|
return(first);
|
|
}
|
|
}
|
|
for(pp=first, oldpp=0; pp; oldpp=pp, pp=pp->next);
|
|
pp = newof((Pathcomp_t*)0,Pathcomp_t,1,len+1);
|
|
pp->shp = shp;
|
|
pp->refcount = 1;
|
|
memcpy((char*)(pp+1),name,len+1);
|
|
pp->name = (char*)(pp+1);
|
|
pp->len = len;
|
|
if(oldpp)
|
|
oldpp->next = pp;
|
|
else
|
|
first = pp;
|
|
pp->flags = flag;
|
|
if(strcmp(name,SH_CMDLIB_DIR)==0)
|
|
{
|
|
pp->dev = 1;
|
|
pp->flags |= PATH_BUILTIN_LIB;
|
|
pp->blib = pp->bbuf = malloc(sizeof(LIBCMD));
|
|
strcpy(pp->blib,LIBCMD);
|
|
return(first);
|
|
}
|
|
if((old||shp->pathinit) && ((flag&(PATH_PATH|PATH_SKIP))==PATH_PATH))
|
|
path_chkpaths(shp,first,old,pp,offset);
|
|
return(first);
|
|
}
|
|
|
|
/*
|
|
* This function checks for the .paths file in directory in <pp>
|
|
* it assumes that the directory is on the stack at <offset>
|
|
*/
|
|
static int path_chkpaths(Shell_t *shp,Pathcomp_t *first, Pathcomp_t* old,Pathcomp_t *pp, int offset)
|
|
{
|
|
struct stat statb;
|
|
int k,m,n,fd;
|
|
char *sp,*cp,*ep;
|
|
stakseek(offset+pp->len);
|
|
if(pp->len==1 && *stakptr(offset)=='/')
|
|
stakseek(offset);
|
|
stakputs("/.paths");
|
|
if((fd=open(stakptr(offset),O_RDONLY))>=0)
|
|
{
|
|
fstat(fd,&statb);
|
|
if (!S_ISREG(statb.st_mode)) {
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
n = statb.st_size;
|
|
stakseek(offset+pp->len+n+2);
|
|
sp = stakptr(offset+pp->len);
|
|
*sp++ = '/';
|
|
n=read(fd,cp=sp,n);
|
|
sp[n] = 0;
|
|
close(fd);
|
|
for(ep=0; n--; cp++)
|
|
{
|
|
if(*cp=='=')
|
|
{
|
|
ep = cp+1;
|
|
continue;
|
|
}
|
|
else if(*cp!='\r' && *cp!='\n')
|
|
continue;
|
|
if(*sp=='#' || sp==cp)
|
|
{
|
|
sp = cp+1;
|
|
continue;
|
|
}
|
|
*cp = 0;
|
|
m = ep ? (ep-sp) : 0;
|
|
if(m==0 || m==6 && memcmp((void*)sp,(void*)"FPATH=",m)==0)
|
|
{
|
|
if(first)
|
|
{
|
|
char *ptr = stakptr(offset+pp->len+1);
|
|
if(ep)
|
|
memmove(ptr,ep,strlen(ep)+1);
|
|
path_addcomp(shp,first,old,stakptr(offset),PATH_FPATH|PATH_BFPATH);
|
|
}
|
|
}
|
|
else if(m==11 && memcmp((void*)sp,(void*)"PLUGIN_LIB=",m)==0)
|
|
{
|
|
if(pp->bbuf)
|
|
free(pp->bbuf);
|
|
pp->blib = pp->bbuf = strdup(ep);
|
|
}
|
|
else if(m)
|
|
{
|
|
pp->lib = (char*)malloc(cp-sp+pp->len+2);
|
|
memcpy((void*)pp->lib,(void*)sp,m);
|
|
memcpy((void*)&pp->lib[m],stakptr(offset),pp->len);
|
|
pp->lib[k=m+pp->len] = '/';
|
|
strcpy((void*)&pp->lib[k+1],ep);
|
|
pathcanon(&pp->lib[m],0);
|
|
if(!first)
|
|
{
|
|
stakseek(0);
|
|
stakputs(pp->lib);
|
|
free((void*)pp->lib);
|
|
return(1);
|
|
}
|
|
}
|
|
sp = cp+1;
|
|
ep = 0;
|
|
}
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
|
|
Pathcomp_t *path_addpath(Shell_t *shp,Pathcomp_t *first, register const char *path,int type)
|
|
{
|
|
register const char *cp;
|
|
Pathcomp_t *old=0;
|
|
int offset = staktell();
|
|
char *savptr;
|
|
|
|
if(!path && type!=PATH_PATH)
|
|
return(first);
|
|
if(type!=PATH_FPATH)
|
|
{
|
|
old = first;
|
|
first = 0;
|
|
}
|
|
if(offset)
|
|
savptr = stakfreeze(0);
|
|
if(path) while(*(cp=path))
|
|
{
|
|
if(*cp==':')
|
|
{
|
|
if(type!=PATH_FPATH)
|
|
first = path_addcomp(shp,first,old,".",type);
|
|
while(*++path == ':');
|
|
}
|
|
else
|
|
{
|
|
int c;
|
|
while(*path && *path!=':')
|
|
path++;
|
|
c = *path++;
|
|
first = path_addcomp(shp,first,old,cp,type);
|
|
if(c==0)
|
|
break;
|
|
if(*path==0)
|
|
path--;
|
|
}
|
|
}
|
|
if(old)
|
|
{
|
|
if(!first && !path)
|
|
{
|
|
Pathcomp_t *pp = (Pathcomp_t*)shp->defpathlist;
|
|
if(!pp)
|
|
pp = defpath_init(shp);
|
|
first = path_dup(pp);
|
|
}
|
|
if(cp=(sh_scoped(shp,FPATHNOD))->nvalue.cp)
|
|
first = (void*)path_addpath(shp,(Pathcomp_t*)first,cp,PATH_FPATH);
|
|
path_delete(old);
|
|
}
|
|
if(offset)
|
|
stakset(savptr,offset);
|
|
else
|
|
stakseek(0);
|
|
return(first);
|
|
}
|
|
|
|
/*
|
|
* duplicate the path give by <first> by incremented reference counts
|
|
*/
|
|
Pathcomp_t *path_dup(Pathcomp_t *first)
|
|
{
|
|
register Pathcomp_t *pp=first;
|
|
while(pp)
|
|
{
|
|
pp->refcount++;
|
|
pp = pp->next;
|
|
}
|
|
return(first);
|
|
}
|
|
|
|
/*
|
|
* called whenever the directory is changed
|
|
*/
|
|
void path_newdir(Shell_t *shp,Pathcomp_t *first)
|
|
{
|
|
register Pathcomp_t *pp=first, *next, *pq;
|
|
struct stat statb;
|
|
for(pp=first; pp; pp=pp->next)
|
|
{
|
|
pp->flags &= ~PATH_SKIP;
|
|
if(*pp->name=='/')
|
|
continue;
|
|
/* delete .paths component */
|
|
if((next=pp->next) && (next->flags&PATH_BFPATH))
|
|
{
|
|
pp->next = next->next;
|
|
if(--next->refcount<=0)
|
|
free((void*)next);
|
|
}
|
|
if(stat(pp->name,&statb)<0 || !S_ISDIR(statb.st_mode))
|
|
{
|
|
pp->dev = 0;
|
|
pp->ino = 0;
|
|
continue;
|
|
}
|
|
pp->dev = statb.st_dev;
|
|
pp->ino = statb.st_ino;
|
|
pp->mtime = statb.st_mtime;
|
|
for(pq=first;pq!=pp;pq=pq->next)
|
|
{
|
|
if(pp->ino==pq->ino && pp->dev==pq->dev)
|
|
pp->flags |= PATH_SKIP;
|
|
}
|
|
for(pq=pp;pq=pq->next;)
|
|
{
|
|
if(pp->ino==pq->ino && pp->dev==pq->dev)
|
|
pq->flags |= PATH_SKIP;
|
|
}
|
|
if((pp->flags&(PATH_PATH|PATH_SKIP))==PATH_PATH)
|
|
{
|
|
/* try to insert .paths component */
|
|
int offset = staktell();
|
|
stakputs(pp->name);
|
|
stakseek(offset);
|
|
next = pp->next;
|
|
pp->next = 0;
|
|
path_chkpaths(shp,first,(Pathcomp_t*)0,pp,offset);
|
|
if(pp->next)
|
|
pp = pp->next;
|
|
pp->next = next;
|
|
}
|
|
}
|
|
}
|
|
|
|
Pathcomp_t *path_unsetfpath(Shell_t *shp)
|
|
{
|
|
Pathcomp_t *first = (Pathcomp_t*)shp->pathlist;
|
|
register Pathcomp_t *pp=first, *old=0;
|
|
if(shp->fpathdict)
|
|
{
|
|
struct Ufunction *rp, *rpnext;
|
|
for(rp=(struct Ufunction*)dtfirst(shp->fpathdict);rp;rp=rpnext)
|
|
{
|
|
rpnext = (struct Ufunction*)dtnext(shp->fpathdict,rp);
|
|
if(rp->fdict)
|
|
nv_delete(rp->np,rp->fdict,NV_NOFREE);
|
|
rp->fdict = 0;
|
|
}
|
|
}
|
|
while(pp)
|
|
{
|
|
if((pp->flags&PATH_FPATH) && !(pp->flags&PATH_BFPATH))
|
|
{
|
|
if(pp->flags&PATH_PATH)
|
|
pp->flags &= ~PATH_FPATH;
|
|
else
|
|
{
|
|
Pathcomp_t *ppsave=pp;
|
|
if(old)
|
|
old->next = pp->next;
|
|
else
|
|
first = pp->next;
|
|
pp = pp->next;
|
|
if(--ppsave->refcount<=0)
|
|
{
|
|
if(ppsave->lib)
|
|
free((void*)ppsave->lib);
|
|
free((void*)ppsave);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
}
|
|
old = pp;
|
|
pp = pp->next;
|
|
}
|
|
return(first);
|
|
}
|
|
|
|
Pathcomp_t *path_dirfind(Pathcomp_t *first,const char *name,int c)
|
|
{
|
|
register Pathcomp_t *pp=first;
|
|
while(pp)
|
|
{
|
|
if(memcmp(name,pp->name,pp->len)==0 && name[pp->len]==c)
|
|
return(pp);
|
|
pp = pp->next;
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* get discipline for tracked alias
|
|
*/
|
|
static char *talias_get(Namval_t *np, Namfun_t *nvp)
|
|
{
|
|
Pathcomp_t *pp = (Pathcomp_t*)np->nvalue.cp;
|
|
char *ptr;
|
|
if(!pp)
|
|
return(NULL);
|
|
pp->shp->last_table = 0;
|
|
path_nextcomp(pp->shp,pp,nv_name(np),pp);
|
|
ptr = stakfreeze(0);
|
|
return(ptr+PATH_OFFSET);
|
|
}
|
|
|
|
static void talias_put(register Namval_t* np,const char *val,int flags,Namfun_t *fp)
|
|
{
|
|
if(!val && np->nvalue.cp)
|
|
{
|
|
Pathcomp_t *pp = (Pathcomp_t*)np->nvalue.cp;
|
|
if(--pp->refcount<=0)
|
|
free((void*)pp);
|
|
}
|
|
nv_putv(np,val,flags,fp);
|
|
}
|
|
|
|
static const Namdisc_t talias_disc = { 0, talias_put, talias_get };
|
|
static Namfun_t talias_init = { &talias_disc, 1 };
|
|
|
|
/*
|
|
* set tracked alias node <np> to value <pp>
|
|
*/
|
|
void path_alias(register Namval_t *np,register Pathcomp_t *pp)
|
|
{
|
|
if(pp)
|
|
{
|
|
struct stat statb;
|
|
char *sp;
|
|
Pathcomp_t *old;
|
|
nv_offattr(np,NV_NOPRINT);
|
|
nv_stack(np,&talias_init);
|
|
old = (Pathcomp_t*)np->nvalue.cp;
|
|
if (old && (--old->refcount <= 0))
|
|
free((void*)old);
|
|
np->nvalue.cp = (char*)pp;
|
|
pp->refcount++;
|
|
nv_setattr(np,NV_TAGGED|NV_NOFREE);
|
|
path_nextcomp(pp->shp,pp,nv_name(np),pp);
|
|
sp = stakptr(PATH_OFFSET);
|
|
if(sp && lstat(sp,&statb)>=0 && S_ISLNK(statb.st_mode))
|
|
nv_setsize(np,statb.st_size+1);
|
|
else
|
|
nv_setsize(np,0);
|
|
}
|
|
else
|
|
_nv_unset(np,0);
|
|
}
|
|
|