get_exename() for OpenBSD. (#17)

get_exename() for OpenBSD.

Issue

`get_exename()` gets the pathname of the current process.  In
femtolisp, this is used to set the top-level `*install-dir*` which in
turn is used as the location of the system image, `flisp.boot`.

There is only a trivial implementation of `get_exename()` for OpenBSD
that simply returns `NULL`.  A minor consequence is that the unit test
will fail for the default build (make) because the system image cannot
be found.

Fix

This commit provides an implementation of `get_exename()` for OpenBSD,
so that the system image can be found.

Unlike, say, Linux or FreeBSD, OpenBSD doesn't have a system call to
get the path of the current (or any, for that matter) process.  The
present code contains some logic that was put together to emulate the
behavior of the Linux and FreeBSD variants of `get_exename()` as best
as possible.  It works as described by the following.

(1) Call `sysctl(3)` (with `CTL_KERN` -> `KERN_PROC_ARGS` ->
    `KERN_PROC_ARGV`) to get the "`argv[0]`" of the current process.
    If the program (flisp) was called in "`basename` form" (i.e. as
    "flisp"), then go to (2).  Otherwise, return the value from
    `sysctl(3)` which will be an absolute or relative pathname,
    e.g. "/usr/local/bin/flisp" or  "../flisp".

    The code for (1) was adapted from old OpenBSD-specific `tmux` code
    that has since been abandoned by the author only because he deemed
    it "too expensive".  For that code, see

    8c259f562b/tree/osdep-openbsd.c

(2) Since we now only have "flisp", we need to find out where it is
    located on the system.  We assume that a program like the shell
    had to crawl `PATH` to find "flisp"; hence we do the same.

    The code for (2) was adapted from the `which` utility in OpenBSD.
    See

    http://cvsweb.openbsd.org/cgi-bin/cvsweb/~checkout~/src/usr.bin/which/which.c?rev=1.20&content-type=text/plain

Finally, any error condition returns `NULL`, which is the same
behavior of the other `get_exename()` variants.

* Resolve relative pathnames to absolute, for OpenBSD get_exename().
This commit is contained in:
Rick Hanson 2016-11-15 15:12:14 -05:00 committed by Jeff Bezanson
parent 47b3876d57
commit 6bc3fe5f11
1 changed files with 98 additions and 2 deletions

View File

@ -90,10 +90,106 @@ char *get_exename(char *buf, size_t size)
return buf;
}
#elif defined(OPENBSD)
#include <sys/param.h>
#include <sys/sysctl.h>
char *get_exename(char *buf, size_t size)
{
/* OpenBSD currently has no way of determining a processes pathname */
return NULL;
int mib[4];
pid_t pid;
size_t len, plen;
char **argv, **argv2;
char *p, *path, *pathcpy, filename[PATH_MAX];
struct stat sbuf;
pid = getpid();
mib[0] = CTL_KERN;
mib[1] = KERN_PROC_ARGS;
mib[2] = pid;
mib[3] = KERN_PROC_ARGV;
buf = NULL;
argv = NULL;
len = 128;
// Now, play The Guessing Game with sysctl(3) as to how big argv
// is supposed to be. (It's loads of fun for the whole family!)
while (len < SIZE_MAX / 2) {
len *= 2;
if ((argv2 = realloc(argv, len)) == NULL)
break;
argv = argv2;
if (sysctl(mib, 4, argv, &len, NULL, 0) == -1) {
if (errno == ENOMEM)
continue; // Go back and realloc more memory.
break; // Bail for some other error in sysctl(3).
}
// If you made it here, congrats! You guessed right!
if (*argv != NULL)
buf = strdup(*argv);
break;
}
free(argv);
// If no error occurred in the sysctl(3) KERN_PROC_ARGV call
// above, then buf at this point contains some kind of pathname.
if (buf != NULL) {
if (strchr(buf, '/') == NULL) {
// buf contains a `basename`-style pathname (i.e. "foo",
// as opposed to "../foo" or "/usr/bin/foo"); search the
// PATH for its location. (BTW the setgid(2), setuid(2)
// calls are a pre-condition for the access(2) call
// later.)
if ( (path = getenv("PATH")) != NULL &&
!setgid(getegid()) && !setuid(geteuid()) ) {
// The strdup(3) call below, if successful, will
// allocate memory for the PATH string returned by
// getenv(3) above. This is necessary because the man
// page of getenv(3) says that its return value
// "should be considered read-only"; however, the
// strsep(3) call below is going to be destructively
// modifying that value. ("Hulk smash!")
if ((path = strdup(path)) != NULL) {
pathcpy = path;
len = strlen(buf);
while ((p = strsep(&pathcpy, ":")) != NULL) {
if (*p == '\0') p = ".";
plen = strlen(p);
// strip trailing '/'
while (p[plen-1] == '/') p[--plen] = '\0';
if (plen + 1 + len < sizeof(filename)) {
snprintf(filename, sizeof(filename), "%s/%s", p, buf);
if ( (stat(filename, &sbuf) == 0) &&
S_ISREG(sbuf.st_mode) &&
access(filename, X_OK) == 0 ) {
buf = strdup(filename);
break;
}
}
}
free(path); // free the strdup(3) memory allocation.
}
}
else buf = NULL; // call to getenv(3) or [sg]ete?[ug]id(2) failed.
}
if ( buf != NULL && *buf != '/' ) {
// buf contains a relative pathname (e.g. "../foo");
// resolve this to an absolute pathname.
if ( strlcpy(filename, buf, sizeof(filename)) >= sizeof(filename) ||
realpath(filename, buf) == NULL )
buf = NULL;
}
}
return buf;
}
#elif defined(FREEBSD)
#include <sys/types.h>