1
0
Fork 0
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:
Martijn Dekker 2021-04-15 04:08:12 +01:00 committed by GitHub
parent 2c38fb93fd
commit 519bb08265
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 82 additions and 144 deletions

View file

@ -31,8 +31,8 @@ static const char usage[] =
"[--catalog?" ERROR_CATALOG "]"
"[+NAME?date - set/list/convert dates]"
"[+DESCRIPTION?\bdate\b sets the current date and time (with appropriate"
" privilege), lists the current date or file dates, or converts"
" dates.]"
" privileges, provided the shell isn't in restricted mode), lists"
" the current date or file dates, or converts dates.]"
"[+?Most common \adate\a forms are recognized, including those for"
" \bcrontab\b(1), \bls\b(1), \btouch\b(1), and the default"
" output from \bdate\b itself.]"

View file

@ -27,7 +27,7 @@
*/
static const char usage[] =
"[-?\n@(#)$Id: getconf (AT&T Research) 2012-06-25 $\n]"
"[-?\n@(#)$Id: getconf (ksh 93u+m) 2021-04-09 $\n]"
"[--catalog?" ERROR_CATALOG "]"
"[+NAME?getconf - get configuration values]"
"[+DESCRIPTION?\bgetconf\b displays the system configuration value for"
@ -53,11 +53,11 @@ static const char usage[] =
" Only one of \b--call\b, \b--name\b or \b--standard\b may be specified.]"
"[+?This implementation uses the \bastgetconf\b(3) string interface to the native"
" \bsysconf\b(2), \bconfstr\b(2), \bpathconf\b(2), and \bsysinfo\b(2)"
" system calls. If \bgetconf\b on \b$PATH\b is not the default native"
" \bgetconf\b, named by \b$(getconf GETCONF)\b, then \bastgetconf\b(3)"
" checks only \bast\b specific extensions and the native system calls;"
" invalid options and/or names not supported by \bastgetconf\b(3) cause"
" the \bgetconf\b on \b$PATH\b to be executed.]"
" system calls."
" Invalid options and/or names not supported by \bastgetconf\b(3) cause"
" the default native \bgetconf\b, named by \b$(getconf GETCONF)\b, to"
" be executed (unless the shell is in restricted mode, in which case"
" an error will occur).]"
"[a:all?Call the native \bgetconf\b(1) with option \b-a\b.]"
"[b:base?List base variable name sans call and standard prefixes.]"
@ -137,25 +137,12 @@ b_getconf(int argc, char** argv, Shbltin_t* context)
register char* path;
register char* value;
register const char* s;
register const char* t;
char* pattern;
char* native;
char* cmd;
Path_t* e;
Path_t* p;
int flags;
int n;
int i;
int m;
int q;
char** oargv;
char buf[PATH_MAX];
Path_t std[64];
struct stat st0;
struct stat st1;
static const char empty[] = "-";
static const Path_t equiv[] = { { "/bin", 4 }, { "/usr/bin", 8 } };
cmdinit(argc, argv, context, ERROR_CATALOG, 0);
oargv = argv;
@ -288,121 +275,11 @@ b_getconf(int argc, char** argv, Shbltin_t* context)
return error_info.errors != 0;
defer:
/*
* defer to argv[0] if absolute and it exists
* Run the external getconf command
*/
if ((cmd = oargv[0]) && *cmd == '/' && !access(cmd, X_OK))
goto found;
/*
* defer to the first getconf on $PATH that is also on the standard PATH
*/
e = std;
s = astconf("PATH", NiL, NiL);
q = !stat(equiv[0].path, &st0) && !stat(equiv[1].path, &st1) && st0.st_ino == st1.st_ino && st0.st_dev == st1.st_dev;
m = 0;
do
{
for (t = s; *s && *s != ':'; s++);
if ((n = s - t) && *t == '/')
{
if (q)
for (i = 0; i < 2; i++)
if (n == equiv[i].len && !strncmp(t, equiv[i].path, n))
{
if (m & (i+1))
t = 0;
else
{
m |= (i+1);
if (!(m & (!i+1)))
{
m |= (!i+1);
e->path = t;
e->len = n;
e++;
if (e >= &std[elementsof(std)])
break;
t = equiv[!i].path;
n = equiv[!i].len;
}
}
}
if (t)
{
e->path = t;
e->len = n;
e++;
}
}
while (*s == ':')
s++;
} while (*s && e < &std[elementsof(std)]);
if (e < &std[elementsof(std)])
{
e->len = strlen(e->path = "/usr/sbin");
if (++e < &std[elementsof(std)])
{
e->len = strlen(e->path = "/sbin");
e++;
}
}
if (s = getenv("PATH"))
do
{
for (t = s; *s && *s != ':'; s++);
if ((n = s - t) && *t == '/')
{
for (p = std; p < e; p++)
if (p->len == n && !strncmp(t, p->path, n))
{
sfsprintf(buf, sizeof(buf), "%-*.*s/%s", n, n, t, error_info.id);
if (!access(buf, X_OK))
{
cmd = buf;
goto found;
}
}
}
while (*s == ':')
s++;
} while (*s);
/*
* defer to the first getconf on the standard PATH
*/
for (p = std; p < e; p++)
{
sfsprintf(buf, sizeof(buf), "%-*.*s/%s", p->len, p->len, p->path, error_info.id);
if (!access(buf, X_OK))
{
cmd = buf;
goto found;
}
}
/*
* out of deferrals
*/
if (name)
error(4, "%s: unknown name -- no native getconf(1) to defer to", name);
else
error(4, "no native getconf(1) to defer to");
return 2;
found:
/*
* don't blame us for crappy diagnostics
*/
oargv[0] = cmd;
oargv[0] = native;
if ((n = sh_run(context, argc, oargv)) >= EXIT_NOEXEC)
error(ERROR_SYSTEM|2, "%s: exec error [%d]", cmd, n);
error(ERROR_SYSTEM|2, "%s: exec error [%d]", native, n);
return n;
}

View file

@ -37,7 +37,8 @@ static const char usage[] =
" separated, on a single line. When more than one option is specified"
" the output is in the order specified by the \b-A\b option below."
" Unsupported option values are listed as \a[option]]\a. If any unknown"
" options are specified, the OS default \buname\b(1) is called.]"
" options are specified, the OS default \buname\b(1) is called (unless"
" the shell is in restricted mode, in which case an error will occur).]"
"[+?If any \aname\a operands are specified then the \bsysinfo\b(2) values"
" for each \aname\a are listed, separated by space, on one line."
" \bgetconf\b(1), a pre-existing \astandard\a interface, provides"