Solved Get current executable path without procfs

I am writing a configuration script in some custom lang and I want it to do it's job using the resources given in the same directory as the script, but I am not sure of how to get a the path where the executable is located in FreeBSD.

The there is some guides () for getting it from procfs, but I want one that does not require Linux compatibility Layers like procfs the FreeBSD Quickstart Guide for Linux users shows the existence of sysctl as the FreeBSD way to do things, but executing sysctl -a | grep -i path does not give anythings that looks like the current executable path, nor trying sysctl -a | grep -i /bin/sh.

Note that argv[0] is not preferable option, as there is not even a guarantee that the argument may have anything to do with the file path, rather than the absolute path.

By the way is procfs part of any UNIX specification?
 
procstat_getpathname(3) ?

C:
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <unistd.h>
#include <libprocstat.h>
#include <stdbool.h>
#include <stdlib.h>

#include <stdio.h>

#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
#define STR_LEN(str)      (ARRAY_SIZE(str) - 1)
#define STR_SIZE(str)     (ARRAY_SIZE(str))

int main(void)
{
    int ret;
    struct procstat *ps;
    struct kinfo_proc *procs;

    ps = NULL;
    procs = NULL;
    ret = EXIT_FAILURE;
    do {
        unsigned int n_proc;
        char pathname[PATH_MAX];

        if (NULL == (ps = procstat_open_sysctl())) {
            fputs("procstat_open_sysctl failed", stderr);
            break;
        }
        if (NULL == (procs = procstat_getprocs(ps, KERN_PROC_PID, getpid(), &n_proc))) {
            fputs("procstat_getprocs failed", stderr);
            break;
        }
        if (1 != n_proc) {
            fputs("unexpected number of processes", stderr);
            break;
        }
        procstat_getpathname(ps, procs, pathname, STR_LEN(pathname));
        printf("pathname = %s\n", pathname);
        ret = EXIT_SUCCESS;
    } while (false);
    if (NULL != procs) {
        procstat_freeprocs(ps, procs);
    }
    if (NULL != ps) {
        procstat_close(ps);
    }

    return ret;
}

cc test.c -o test -lprocstat
 
procstat_getpathname(3) ?

C:
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <unistd.h>
#include <libprocstat.h>
#include <stdbool.h>
#include <stdlib.h>

#include <stdio.h>

#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
#define STR_LEN(str)      (ARRAY_SIZE(str) - 1)
#define STR_SIZE(str)     (ARRAY_SIZE(str))

int main(void)
{
    int ret;
    struct procstat *ps;
    struct kinfo_proc *procs;

    ps = NULL;
    procs = NULL;
    ret = EXIT_FAILURE;
    do {
        unsigned int n_proc;
        char pathname[PATH_MAX];

        if (NULL == (ps = procstat_open_sysctl())) {
            fputs("procstat_open_sysctl failed", stderr);
            break;
        }
        if (NULL == (procs = procstat_getprocs(ps, KERN_PROC_PID, getpid(), &n_proc))) {
            fputs("procstat_getprocs failed", stderr);
            break;
        }
        if (1 != n_proc) {
            fputs("unexpected number of processes", stderr);
            break;
        }
        procstat_getpathname(ps, procs, pathname, STR_LEN(pathname));
        printf("pathname = %s\n", pathname);
        ret = EXIT_SUCCESS;
    } while (false);
    if (NULL != procs) {
        procstat_freeprocs(ps, procs);
    }
    if (NULL != ps) {
        procstat_close(ps);
    }

    return ret;
}

cc test.c -o test -lprocstat
Testing it wiith the code
C:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char **argv) {
        char *const paramListe[] = {"nichts"};
        execvp("/home/developer/procstat/test", paramListe);
}
outputs nothing to the standard console.
 
Code:
        char *const paramListe[] = {"nichts"};
        execvp("/home/developer/procstat/test", paramListe);
UB here, execvp(3) will read whatever random crap from memory (and pass as the argument list if it doesn't crash before) because you're missing the terminating NULL pointer. Always compile with warnings enabled, that's one modern clang and gcc should catch... (at least they do for execlp(3))
 
this seems like an awkward approach anyway. and it will give you the path of the interpreter which might differ from the interpreted script path
 
[…] I am not sure of how to get a the path where the executable is located in FreeBSD. […]
Arguably this is bad design. Obtaining the program’s pathname once it’s started is inherently unreliable: By the time you look it up a pathname component (i. e. a directory or symbolic link) might have been renamed or even deleted. ? It’s better to instruct users to simply cd to the right location prior invocation, so you have a current working directory (= an open file) as your reference.​
Another method, since Ferocious1881 writes “in some custom lang” so the C libraries aren’t necessarily available, is to tap the “auxiliary vector” (auxv(3)). It’s fairly easy (if done from assembly at program startup).​
Bash:
tabs 20
LD_SHOW_AUXV=1 sleep 0  # see rtld(1) about the environment variable
# … prints lots of data …
AT_EXECPATH:       /bin/sleep
# … and a lot more data …
[…] and it will give you the path of the interpreter which might differ from the interpreted script path […]
Well, maybe if it’s an interpreted language, the interpreter, the language should provide a way to inspect the pathname via which the script was read (like $0 in sh(1)), not the OS. ?​
By the way is procfs part of any UNIX specification?
No, we document any (intentional) compliance in a section called Standards of the respective man page. ?​
 
First observation: You are solving the wrong problem. You are really trying to locate the resources for the script (or program). And to make things easy, you want to use the assumption that they are stored in the same location as the script itself. But the place where the script is stored is inherently a vague concept. Sure, using argv[0] and the path one can usually make an educated guess. And in a system with procfs, one can actually figure out exactly what the currently running executable is, but procfs is not standardized and not available everywhere.

May I suggest a different approach:
  • Read the resources from a place that you decide on, for example if you program is called foo, from /usr/local/etc/foo.conf, or from /usr/local/lib/foo/*, or from ~/.foo/*
  • To allow version upgrades, or testing without disturbing a production configuration, allow the user to override where the resources should be found. Environment variables work well for that. As an example: "FOO_DIR=/tmp/foo.test ./foo"
  • And if your script can't find the required resources, give a meaningful error message: "Required configuration file foo.conf not found in either /usr/local/etc/foo.conf, /usr/local/lib/foo/foo.conf or ~/.foo/foo.conf. Please place the configuration file in one of these locations, or override the search location with FOO_DIR."
The details are just examples, but that's the general approach followed in most operating systems (including Unix).
 
Arguably this is bad design. Obtaining the program’s pathname once it’s started is inherently unreliable: By the time you look it up a pathname component (i. e. a directory or symbolic link) might have been renamed or even deleted. ? It’s better to instruct users to simply cd to the right location prior invocation, so you have a current working directory (= an open file) as your reference.
The customer, myself, asked it that way.
 
It’s not just the exe name that you may need to resolve from fd to filename. If you want to print out a list of open files at exit then any open descriptors that you get at startup like redirected stdin/out/err need resolving. There is no nice reliable way to do that. There is sysctl kern proc filedesc (the most reliable but not nice, you have to do all the work of looking through the data structures for all files to find the one that you are looking for). Then there is fcntl F_KINFO which is less reliable (it only queries a cache as I understand it, and if the cache isn't valid you get no filename).
 
It’s not just the exe name that you may need to resolve from fd to filename. If you want to print out a list of open files at exit then any open descriptors that you get at startup like redirected stdin/out/err need resolving. There is no nice reliable way to do that. There is sysctl kern proc filedesc (the most reliable but not nice, you have to do all the work of looking through the data structures for all files to find the one that you are looking for). Then there is fcntl F_KINFO which is less reliable (it only queries a cache as I understand it, and if the cache isn't valid you get no filename).
I am not sure if that is on topic.
 
Back
Top