From 130518cdc9977536d00136a5896356850e0eacad Mon Sep 17 00:00:00 2001 From: Rick Hanson Date: Tue, 15 Nov 2016 15:12:14 -0500 Subject: [PATCH] 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 http://sourceforge.net/p/tmux/tmux-code/ci/8c259f562be24570a19fd94b223034ae8c6e4277/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(). --- llt/dirpath.c | 100 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 2 deletions(-) diff --git a/llt/dirpath.c b/llt/dirpath.c index 962af38..52172ee 100644 --- a/llt/dirpath.c +++ b/llt/dirpath.c @@ -90,10 +90,106 @@ char *get_exename(char *buf, size_t size) return buf; } #elif defined(OPENBSD) +#include +#include + 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