1
0
Fork 0
mirror of git://git.code.sf.net/p/cdesktopenv/code synced 2025-03-09 15:50:02 +00:00
cde/src/lib/libcmd/getconf.c
Martijn Dekker 519bb08265
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
2021-04-15 04:08:12 +01:00

285 lines
8.7 KiB
C

/***********************************************************************
* *
* This software is part of the ast package *
* Copyright (c) 1992-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 *
* *
* Glenn Fowler <gsf@research.att.com> *
* David Korn <dgk@research.att.com> *
* *
***********************************************************************/
#pragma prototyped
/*
* Glenn Fowler
* AT&T Research
*
* getconf - get configuration values
*/
static const char usage[] =
"[-?\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"
" \aname\a. If \aname\a is a filesystem specific variable then"
" the value is determined relative to \apath\a or the current"
" directory if \apath\a is omitted. If \avalue\a is specified then"
" \bgetconf\b attempts to change the process local value to \avalue\a."
" \b-\b may be used in place of \apath\a when it is not relevant."
" If \apath\a is \b=\b then the \avalue\a is cached and used"
" for subsequent tests in the calling and all child processes."
" Only \bwritable\b variables may be set; \breadonly\b variables"
" cannot be changed.]"
"[+?The current value for \aname\a is written to the standard output. If"
" \aname\a is valid but undefined then \bundefined\b is written to"
" the standard output. If \aname\a is invalid or an error occurs in"
" determining its value, then a diagnostic written to the standard error"
" and \bgetconf\b exits with a non-zero exit status.]"
"[+?More than one variable may be set or queried by providing the \aname\a"
" \apath\a \avalue\a 3-tuple for each variable, specifying \b-\b for"
" \avalue\a when querying.]"
"[+?If no operands are specified then all known variables are written in"
" \aname\a=\avalue\a form to the standard output, one per line."
" 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."
" 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.]"
"[c:call?Display variables with call prefix that matches \aRE\a. The call"
" prefixes are:]:[RE]{"
" [+CS?\bconfstr\b(2)]"
" [+PC?\bpathconf\b(2)]"
" [+SC?\bsysconf\b(2)]"
" [+SI?\bsysinfo\b(2)]"
" [+XX?Constant value.]"
"}"
"[d:defined?Only display defined values when no operands are specified.]"
"[l:lowercase?List variable names in lower case.]"
"[n:name?Display variables with name that match \aRE\a.]:[RE]"
"[p:portable?Display the named \bwritable\b variables and values in a form that"
" can be directly executed by \bsh\b(1) to set the values. If \aname\a"
" is omitted then all \bwritable\b variables are listed.]"
"[q:quote?\"...\" quote values.]"
"[r:readonly?Display the named \breadonly\b variables in \aname\a=\avalue\a form."
" If \aname\a is omitted then all \breadonly\b variables are listed.]"
"[s:standard?Display variables with standard prefix that matches \aRE\a."
" Use the \b--table\b option to view all standard prefixes, including"
" local additions. The standard prefixes available on all systems"
" are:]:[RE]{"
" [+AES]"
" [+AST]"
" [+C]"
" [+GNU]"
" [+POSIX]"
" [+SVID]"
" [+XBS5]"
" [+XOPEN]"
" [+XPG]"
"}"
"[t:table?Display the internal table that contains the name, standard,"
" standard section, and system call symbol prefix for each variable.]"
"[w:writable?Display the named \bwritable\b variables in \aname\a=\avalue\a"
" form. If \aname\a is omitted then all \bwritable\b variables are"
" listed.]"
"[v:specification?Call the native \bgetconf\b(1) with option"
" \b-v\b \aname\a.]:[name]"
"\n"
"\n[ name [ path [ value ] ] ... ]\n"
"\n"
"[+ENVIRONMENT]"
"{"
"[+_AST_FEATURES?Process local writable values that are "
"different from the default are stored in the \b_AST_FEATURES\b "
"environment variable. The \b_AST_FEATURES\b value is a "
"space-separated list of \aname\a \apath\a \avalue\a 3-tuples, "
"where \aname\a is the system configuration name, \apath\a is "
"the corresponding path, \b-\b if no path is applicable, and "
"\avalue\a is the system configuration value. \b_AST_FEATURES\b "
"is an implementation detail of process inheritance; it may "
"change or vanish in the future; don't rely on it.]"
"}"
"[+SEE ALSO?\bpathchk\b(1), \bconfstr\b(2), \bpathconf\b(2),"
" \bsysconf\b(2), \bastgetconf\b(3)]"
;
#include <cmd.h>
#include <proc.h>
#include <ls.h>
typedef struct Path_s
{
const char* path;
int len;
} Path_t;
int
b_getconf(int argc, char** argv, Shbltin_t* context)
{
register char* name;
register char* path;
register char* value;
register const char* s;
char* pattern;
char* native;
int flags;
int n;
char** oargv;
static const char empty[] = "-";
cmdinit(argc, argv, context, ERROR_CATALOG, 0);
oargv = argv;
if (*(native = astconf("GETCONF", NiL, NiL)) != '/')
native = 0;
flags = 0;
name = 0;
pattern = 0;
for (;;)
{
switch (optget(argv, usage))
{
case 'a':
if (native)
goto defer;
continue;
case 'b':
flags |= ASTCONF_base;
continue;
case 'c':
flags |= ASTCONF_matchcall;
pattern = opt_info.arg;
continue;
case 'd':
flags |= ASTCONF_defined;
continue;
case 'l':
flags |= ASTCONF_lower;
continue;
case 'n':
flags |= ASTCONF_matchname;
pattern = opt_info.arg;
continue;
case 'p':
flags |= ASTCONF_parse;
continue;
case 'q':
flags |= ASTCONF_quote;
continue;
case 'r':
flags |= ASTCONF_read;
continue;
case 's':
flags |= ASTCONF_matchstandard;
pattern = opt_info.arg;
continue;
case 't':
flags |= ASTCONF_table;
continue;
case 'v':
if (native)
goto defer;
continue;
case 'w':
flags |= ASTCONF_write;
continue;
case ':':
if (native)
goto defer;
error(2, "%s", opt_info.arg);
break;
case '?':
error(ERROR_usage(2), "%s", opt_info.arg);
UNREACHABLE();
}
break;
}
argv += opt_info.index;
if (!(name = *argv))
path = 0;
else if (streq(name, empty))
{
name = 0;
if (path = *++argv)
{
argv++;
if (streq(path, empty))
path = 0;
}
}
if (error_info.errors || !name && *argv)
{
error(ERROR_usage(2), "%s", optusage(NiL));
UNREACHABLE();
}
if (!name)
astconflist(sfstdout, path, flags, pattern);
else
{
if (native)
flags |= (ASTCONF_system|ASTCONF_error);
do
{
if (!(path = *++argv))
value = 0;
else
{
if (streq(path, empty))
{
path = 0;
flags = 0;
}
if ((value = *++argv) && (streq(value, empty)))
{
value = 0;
flags = 0;
}
}
s = astgetconf(name, path, value, flags, errorf);
if (error_info.errors)
break;
if (!s)
{
if (native)
goto defer;
error(2, "%s: unknown name", name);
break;
}
if (!value)
{
if (flags & ASTCONF_write)
{
sfputr(sfstdout, name, ' ');
sfputr(sfstdout, path ? path : empty, ' ');
}
sfputr(sfstdout, s, '\n');
}
} while (*argv && (name = *++argv));
}
return error_info.errors != 0;
defer:
/*
* Run the external getconf command
*/
oargv[0] = native;
if ((n = sh_run(context, argc, oargv)) >= EXIT_NOEXEC)
error(ERROR_SYSTEM|2, "%s: exec error [%d]", native, n);
return n;
}