/***********************************************************************
* *
* This software is part of the ast package *
* Copyright (c) 1989-2011 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> *
* *
***********************************************************************/
#pragma prototyped
/*
* Glenn Fowler
* AT&T Research
*
* probe - generate/install/display language processor probe information
*
* three installation possibilities, from most inter-user sharing to least
*
* (1) $INSTALLROOT/lib/probe/probe set-uid to the owner of the
* probe hierarchy
* (2) probe hierarchy owned by the caller geteuid() (e.g., FAT fs)
* (3) otherwise info maintained per-user in $HOME/.probe/
*/
static const char usage[] =
"[-?\n@(#)$Id: probe (AT&T Research) 2007-02-22 $\n]"
USAGE_LICENSE
"[+NAME?probe - generate/install/display language processor probe information]"
"[+DESCRIPTION?\bprobe\b generates, installs and displays on the standard"
" output probe information for the \alanguage\a programming language"
" with respect to the \atool\a command as implemented by the"
" \aprocessor\a command. The probe information is in"
" the \atool\a command syntax. In general the commands that rely on"
" probe information (re)generate the information when it is out of date"
" (see \b--generate\b), so direct control is not usually"
" necessary. However, not all changes to \aprocessor\a that"
" would affect the probe information are detected by this mechanism;"
" such changes require manual intervention (see \b--delete\b and"
" \b--override\b.) A probe information file (see \b--key\b) with"
" write mode enabled signifies that manual changes have been made"
" and disables automatic regeneration (see \b--generate\b.)]"
"[+?\bprobe\b information is kept in a file with a pathname based on a"
" hash of \alanguage\a, \atool\a, and \aprocessor\a. The information"
" is generated by a \bsh\b(1) script (\aprobe script\a) specific to"
" each \alanguage-processor\a pair. Any options that change the"
" semantics of the language handled by \aprocessor\a should be included"
" in the \aprocessor\a operand. e.g., if \bcc -++\b processes C++ files,"
" then \aprocessor\a should be \bcc -++\b, not \bcc\b.]"
"[+?\bprobe\b generation may take a few minutes on some systems or for"
" some \aprocessors\a. Information is shared between users as much"
" as possible. If the \b--key\b option produces a path under your"
" \b$HOME\b directory then the probe installation does not support"
" sharing and each user will have private copies of the generated"
" information.]"
"[+?For some \alanguage\a/\atool\a combinations, the file"
" \blib/probe/\b\alanguage\a\b/\b\atool\a\b/probe.lcl\b, if it"
" exists in the same directory as the \bprobe\b script, is sourced"
" (via the \b.\b \bsh\b(1) command) before the probe variables are"
" emitted. The emitted values may be modified by assigning appropriate"
" \bsh\b variables (non-identifier name characters converted"
" to \b_\b.) Additional non-standard values may be written directly"
" to the standard output.]"
"[+?For all \alanguage\a/\atool\a combinations, the file"
" \blib/probe/\b\alanguage\a\b/\b\atool\a\b/probe.ini\b, if it"
" exists in the same directory as the \bprobe\b script, is sourced"
" (via the \b.\b \bsh\b(1) command) before the \bprobe\b script proper."
" \bprobe.ini\b may completely override the \bprobe\b script by"
" exiting (presumably after printing its own values on the"
" standard output.)]"
"[+?Sometimes it is necessary to maintain private \bprobe\b information"
" for some processors or tools. See \b--override\b for details.]"
"[a:attributes?List probe attribute definitions.]"
"[d:delete?Delete the current information. Information can only be deleted by"
" the generating user.]"
"[f:force?Force information generation even if it is out of date. Only probe"
" information files with write mode disabled can be regenerated.]"
"[g:generate?Generate the probe information if it is out of date. Only probe"
" information files with write mode disabled can be regenerated.]"
"[k:key?List the probe key path name on the standard output.]"
"[l:list?List the probe information on the standard output. An error occurs"
" if the information has not been generated, unless \b--generate\b"
" is also specified.]"
"[o:override?Make a writable copy of the probe information file in the"
" directory \abindir\a\b/../lib/probe/\b\alang\a/\atool\a and list the"
" generated file pathname on the standard output. If \b--key\b is also"
" specified then the override path is listed but not created. When"
" \bprobe\b-based commands are run with \abindir\a in \b$PATH\b before"
" the bin directory of the standard probe information the override"
" information will be used. Note that since the override file is user"
" writable automatic regeneration is disabled.]:[bindir]"
"[s:silent?By default a message is printed on the standard error when probe"
" generation starts; \b--silent\b disables this message.]"
"[t:test?Start probe generation but do not save the results. Used for"
" debugging probe scripts.]"
"[v:verify?Each probe information file contains a comment (in the"
" \atool\a syntax) that can be used to verify the contents. This"
" option does that verification.]"
"[D:debug?Start probe generation, do not save the results, and write the"
" probe script trace (see \bsh\b(1) \b-x\b) to the standard error.]"
"\n"
"\nlanguage tool processor\n"
"\n"
"[+FILES]{"
" [+lib/probe/\alanguage\a/\atool\a?\atool\a specific information"
" directory for \alanguage\a processors.]"
" [+$HOME/.probe?Per-user directory when \bprobe\b is installed"
" non-set-uid or the probe directory is on a readonly"
" filesystem.]"
"}"
"[+CAVEATS?To allow probe information to be generated and shared among all"
" users the executable \alib/probe/probe\a must be set-uid to the owner"
" of the \alib/probe\a directory hierarchy and the probe directory"
" filesystem must be mounted read-write.]"
"[+?Automatic language processor probing is mostly black magic, but then"
" so are most language processor implementations.]"
"[+SEE ALSO?\bcpp\b(1), \bnmake\b(1), \bpathkey\b(3), \bpathprobe\b(3)]"
;
#include <ast.h>
#include <ctype.h>
#include <error.h>
#include <ls.h>
#include <preroot.h>
#include <proc.h>
#include <sig.h>
#include <times.h>
#define BASE_MAX 14
#define COMMAND "probe"
#define ATTRIBUTES (1<<0)
#define DELETE (1<<1)
#define FORCE (1<<2)
#define GENERATE (1<<3)
#define KEY (1<<4)
#define LIST (1<<5)
#define OVERRIDE (1<<6)
#define SILENT (1<<7)
#define TEST (1<<8)
#define TRACE (1<<9)
#define VERIFY (1<<10)
static char* tmp;
static int signals[] = /* signals caught by interrupt() */
{
SIGINT,
SIGTERM,
#ifdef SIGALRM
SIGALRM,
#endif
#ifdef SIGHUP
SIGHUP,
#endif
#ifdef SIGQUIT
SIGQUIT,
#endif
};
#if defined(ST_RDONLY) || defined(ST_NOSUID)
/*
* return non-0 if path is in a readonly or non-setuid fs
*/
static int
rofs(const char* path)
{
struct statvfs vfs;
if (!statvfs(path, &vfs))
{
#if defined(ST_RDONLY)
if (vfs.f_flag & ST_RDONLY)
return 1;
#endif
#if defined(ST_NOSUID)
if (vfs.f_flag & ST_NOSUID)
return 1;
#endif
}
return 0;
}
#else
#define rofs(p) 0
#endif
/*
* check path for old processor name clash
* if old!=0 then file path compared with file old
* 0 returned if clash or file path the same as old
*/
static int
verify(char* path, char* old, char* processor, int must)
{
char* ns;
char* os;
int nz;
int oz;
int r;
Sfio_t* nf;
Sfio_t* of;
r = 0;
if (nf = sfopen(NiL, path, "r"))
{
if ((ns = sfgetr(nf, '\n', 1)) && (nz = sfvalue(nf) - 1) > 0)
{
ns += nz;
if ((oz = strlen(processor)) <= nz && !strcmp(processor, ns - oz))
r = 1;
else
error(2, "%s: %s clashes with %s", path, processor, ns - nz);
}
if (r && old && sfseek(nf, 0L, 0) == 0 && (of = sfopen(NiL, old, "r")))
{
for (;;)
{
ns = sfreserve(nf, 0, 0);
nz = sfvalue(nf);
os = sfreserve(of, 0, 0);
oz = sfvalue(nf);
if (nz <= 0 || oz <= 0)
break;
if (nz > oz)
nz = oz;
if (memcmp(ns, os, nz))
break;
nz = sfread(nf, ns, nz);
oz = sfread(of, os, nz);
if (!nz || !oz)
break;
}
sfclose(of);
if (!nz && !oz && !touch(old, (time_t)-1, (time_t)-1, 0))
r = 0;
}
sfclose(nf);
}
else if (must)
error(ERROR_SYSTEM|2, "%s: cannot read", path);
return r;
}
/*
* yes this is sleazy
*/
static void
interrupt(int sig)
{
if (tmp)
remove(tmp);
signal(sig, SIG_DFL);
kill(getpid(), sig);
pause();
/*NOTREACHED*/
}
int
main(int argc, char** argv)
{
register int n;
register char* base;
register char* path;
char* script;
char* probe;
char* language;
char* tool;
char* processor;
char* sentinel;
char* s;
char* v;
char* override;
int i;
int suid;
int options = 0;
Proc_t* pp;
Sfio_t* fp;
Sfio_t* pf;
char attributes[PATH_MAX];
char cmd[PATH_MAX];
char key[BASE_MAX + 1];
char* cmdargv[5];
char* cmdenvv[2];
struct stat ps;
struct stat vs;
unsigned long ptime;
NoP(argc);
error_info.id = probe = COMMAND;
for (;;)
{
switch (optget(argv, usage))
{
case 'a':
options |= ATTRIBUTES;
continue;
case 'd':
options |= DELETE;
continue;
case 'f':
options |= FORCE;
continue;
case 'g':
options |= GENERATE;
continue;
case 'k':
options |= KEY;
continue;
case 'l':
options |= LIST;
continue;
case 'o':
options |= OVERRIDE;
override = opt_info.arg;
continue;
case 's':
options |= SILENT;
continue;
case 't':
options |= TEST;
continue;
case 'v':
options |= VERIFY;
continue;
case 'D':
options |= TRACE;
continue;
case '?':
error(ERROR_USAGE|4, "%s", opt_info.arg);
break;
case ':':
error(2, "%s", opt_info.arg);
break;
}
break;
}
argv += opt_info.index;
if (error_info.errors || !(language = *argv++) || !(tool = *argv++) || !(processor = *argv++) || *argv)
error(ERROR_USAGE|4, "%s", optusage(NiL));
if (*processor != '/')
{
if (s = strchr(processor, ' '))
{
n = s - processor;
processor = strncpy(attributes, processor, n);
*(processor + n) = 0;
}
v = processor;
if (!(processor = pathpath(processor, NiL, PATH_ABSOLUTE|PATH_REGULAR|PATH_EXECUTE, cmd, sizeof(cmd))))
error(3, "%s: processor not found", v);
if (s)
strcpy(processor + strlen(processor), s);
}
#if FS_PREROOT
if (path = getpreroot(NiL, processor))
setpreroot(NiL, path);
#endif
if (!(path = pathprobe(language, tool, processor, (options & TEST) ? -3 : -2, NiL, 0, attributes, sizeof(attributes))))
error(3, "cannot generate probe key");
if (stat(path, &ps))
{
ps.st_mtime = 0;
ps.st_mode = 0;
}
if ((options & TEST) || !(ps.st_mode & S_IWUSR))
{
/*
* verify the hierarchy before any ops
*/
if (!(base = strrchr(path, '/')) || strlen(++base) > BASE_MAX)
error(3, "%s: invalid probe path", path);
strcpy(key, base);
strcpy(base, "../../");
strcpy(base + 6, probe);
if (stat(path, &vs))
error(ERROR_SYSTEM|3, "%s: not found", path);
if ((vs.st_mode & S_ISUID) && !rofs(path))
{
suid = (n = geteuid()) != getuid();
if (suid && vs.st_uid != n)
error(3, "%s: effective uid mismatch", path);
strcpy(base, ".");
if (stat(path, &vs))
error(ERROR_SYSTEM|3, "%s: not found", path);
if (suid && vs.st_uid != n)
error(3, "%s: effective uid mismatch", path);
}
else
suid = -1;
strcpy(base, probe);
if (stat(path, &vs))
error(ERROR_SYSTEM|3, "%s: not found", path);
ptime = vs.st_mtime;
n = strlen(path);
if (!(script = newof(0, char, n, 5)))
error(ERROR_SYSTEM|3, "out of space");
strcpy(script, path);
strcpy(script + n, ".ini");
if (!stat(script, &vs) && vs.st_size && ptime < (unsigned long)vs.st_mtime)
ptime = vs.st_mtime;
*(script + n) = 0;
if (suid >= 0)
strcpy(base, key);
else if (!(path = pathprobe(language, tool, processor, -1, NiL, 0, attributes, sizeof(attributes))))
error(3, "cannot generate probe key");
else
{
if (stat(path, &ps))
{
ps.st_mtime = 0;
ps.st_mode = 0;
}
if (!(base = strrchr(path, '/')) || strlen(++base) > BASE_MAX)
error(3, "%s: invalid probe path", path);
strcpy(key, base);
}
}
/*
* ops ok now
*/
if (options & DELETE)
{
if (lstat(processor, &vs) || getuid() != vs.st_uid && (!ps.st_mode || getgid() != ps.st_gid))
{
/*
* request not by owner of processor
* so limit to probe owner
*/
setuid(getuid());
setgid(getgid());
}
if (remove(path))
error(ERROR_SYSTEM|3, "%s: cannot delete", path);
}
else
{
if (!(options & (FORCE|TEST)) && ps.st_mode && (ptime <= (unsigned long)ps.st_mtime || ptime <= (unsigned long)ps.st_ctime || (ps.st_mode & S_IWUSR)))
{
if (ptime <= (unsigned long)ps.st_mtime || ptime <= (unsigned long)ps.st_ctime)
{
if (!options)
error(1, "%s: information already generated", path);
}
else if (!(options & OVERRIDE) && (ps.st_mode & S_IWUSR))
error(0, "%s probe information for %s language processor %s must be manually regenerated", tool, language, processor);
}
else if ((options & (FORCE|GENERATE|TEST)) || !(options & ~(FORCE|GENERATE|SILENT|TRACE)))
{
/*
* recursion check using environment sentinel
*/
n = strlen(probe) + strlen(key) + strlen(processor) + 2;
if (!(sentinel = newof(0, char, n, 0)))
error(ERROR_SYSTEM|3, "out of space");
sfsprintf(sentinel, n, "%s%s", probe, key);
for (s = sentinel; *s; s++)
if (!isalnum(*s))
*s = '_';
if ((v = getenv(sentinel)) && !strcmp(v, processor))
error(3, "%s: recursive probe", processor);
sfsprintf(s, n - (s - sentinel), "=%s", processor);
/*
* generate the new info in a tmp file
*/
if (suid < 0)
{
strcpy(base, ".");
if (access(path, F_OK))
for (s = path + (*path == '/'); s = strchr(s, '/'); *s++ = '/')
{
*s = 0;
if (access(path, F_OK) && mkdir(path, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH))
error(ERROR_SYSTEM|3, "%s: cannot create directory", path);
}
}
if (!(options & SILENT))
error(0, "probing %s language processor %s for %s information", language, processor, tool);
sfsprintf(base, BASE_MAX + 1, "%s.%06d", probe, getpid());
if (!(tmp = strdup(path)))
error(ERROR_SYSTEM|3, "out of space");
for (n = 0; n < elementsof(signals); n++)
if (signal(signals[n], interrupt) == SIG_IGN)
signal(signals[n], SIG_IGN);
if (!(options & TEST))
{
if (!(fp = sfopen(NiL, tmp, "w")))
error(ERROR_SYSTEM|3, "%s: cannot write", tmp);
chmod(tmp, S_IRUSR|S_IRGRP|S_IROTH);
}
n = 0;
cmdargv[n++] = script;
if (options & TRACE)
cmdargv[n++] = "-d";
cmdargv[n++] = processor;
cmdargv[n++] = attributes;
cmdargv[n] = 0;
n = 0;
cmdenvv[n++] = sentinel;
cmdenvv[n] = 0;
if (!(pp = procopen(script, cmdargv, cmdenvv, NiL, PROC_UID|PROC_GID|((options&TEST)?0:PROC_READ))))
n = -1;
else if (!(options & TEST) && (!(pf = sfnew(NiL, NiL, SF_UNBOUND, pp->rfd, SF_READ)) || !(pp->rfd = -1) || sfmove(pf, fp, SF_UNBOUND, -1) < 0 || sfclose(pf) || sfclose(fp)))
n = -2;
else
n = 0;
i = procclose(pp);
if (!n)
n = i;
if (n < -1)
error(ERROR_SYSTEM|2, "%s: command io error", script);
else if (n == -1)
error(ERROR_SYSTEM|2, "%s: command exec error", script);
else if (n > 0)
error(2, "%s: command exit code %d", script, n);
else if (!(options & TEST))
{
/*
* verify and rename to the real probe key path
*/
strcpy(base, key);
if (verify(tmp, path, processor, 1))
{
remove(path);
if (!rename(tmp, path))
{
/*
* warp the file to yesterday to
* accomodate make installations
*/
if (!stat(path, &ps))
{
ps.st_mtime = (unsigned long)ps.st_mtime - 24 * 60 * 60;
touch(path, ps.st_mtime, ps.st_mtime, 0);
}
}
else if (!verify(path, NiL, processor, 0))
{
n = -1;
error(ERROR_SYSTEM|2, "%s: cannot rename to %s", tmp, path);
}
}
else
n = -1;
}
if (n && !(options & TEST))
remove(tmp);
}
if (!error_info.errors)
{
if (options & OVERRIDE)
{
n = 5;
base = path + strlen(path);
while (base > path && (*--base != '/' || --n));
if (n || !strneq(base, "/lib/", 5) || !strneq(base + 5, probe, strlen(probe)))
error(3, "%s: probe information already private", path);
if (!(v = strrchr(override, '/')))
v = override;
sfsprintf(cmd, sizeof(cmd), "%-.*s%s", v - override, override, base);
if (!(options & KEY))
{
if (!access(cmd, F_OK))
error(3, "%s: override already generated", cmd);
if (!(pf = sfopen(NiL, cmd, "w")))
{
for (s = cmd + (*cmd == '/'); s = strchr(s, '/'); *s++ = '/')
{
*s = 0;
if (access(cmd, F_OK) && mkdir(cmd, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH))
error(ERROR_SYSTEM|3, "%s: cannot create override directory", cmd);
}
if (!(pf = sfopen(NiL, cmd, "w")))
error(ERROR_SYSTEM|3, "%s: cannot write", cmd);
}
if (!(fp = sfopen(NiL, path, "r")))
error(ERROR_SYSTEM|3, "%s: cannot read", path);
if (sfmove(fp, pf, SF_UNBOUND, -1) < 0 || sfclose(fp) || sfclose(pf))
error(ERROR_SYSTEM|3, "%s: copy error", cmd);
}
sfprintf(sfstdout, "%s\n", cmd);
}
else
{
if (options & KEY)
sfprintf(sfstdout, "%s\n", path);
if (options & ATTRIBUTES)
sfprintf(sfstdout, "%s\n", attributes);
if (!(options & TEST))
{
if (options & LIST)
{
if (!(fp = sfopen(NiL, path, "r")))
error(ERROR_SYSTEM|3, "%s: cannot read", path);
sfmove(fp, sfstdout, SF_UNBOUND, -1);
sfclose(fp);
}
if (options & VERIFY)
verify(path, NiL, processor, 1);
}
}
}
}
return error_info.errors != 0;
}