View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0001992 | 1003.1(2024)/Issue8 | System Interfaces | public | 2026-07-14 16:14 | 2026-08-09 18:50 |
| Reporter | kasperk81 | Assigned To | |||
| Priority | normal | Severity | Editorial | Type | Enhancement Request |
| Status | New | Resolution | Open | ||
| Name | Kasper K. | ||||
| Organization | Independent | ||||
| User Reference | |||||
| Section | XSH 3 | ||||
| Page Number | unknown | ||||
| Line Number | unknown | ||||
| Interp Status | |||||
| Final Accepted Text | |||||
| Summary | 0001992: Add getexepath() to retrieve the absolute pathname of the current process executable image. | ||||
| Description | There is currently no standardized, portable mechanism in POSIX to retrieve the absolute pathname of the executable file currently executing the calling process. Runtimes, language interpreters, and asset loaders must resort to highly unportable, platform-specific mechanisms. - Linux relies on parsing the "/proc/self/exe" symlink or using `getauxval(AT_EXECFN)`. - FreeBSD and NetBSD use a sysctl interface via KERN_PROC_PATHNAME. - SunOS implements getexecname(). - macOS implements a non-POSIX _NSGetExecutablePath() function. Others like OpenBSD force us to rely on argv[0] and (if the process didn't start with an absolute path) scanning the PATH environment variable, which is fundamentally unsafe and unreliable (someone else in the PATH with the same name appearing early is picked up). Furthermore, the parent process can easily manipulate or empty argv and envp during an execve() call, leaving the child completely unable to safely determine its own physical location. Adding a native, safe API to POSIX eliminates immense cross-platform boilerplate across programming frameworks and toolchains that require explicit binary path auditing. | ||||
| Desired Action | 1. In <unistd.h>, add the following function prototype: ssize_t getexepath(char *buf, size_t bufsize); 2. Add a new manual page under Section 3 (FUNCTIONS) detailing the specification: NAME getexepath — get the absolute pathname of the executing process image. SYNOPSIS #include <unistd.h> ssize_t getexepath(char *buf, size_t bufsize); DESCRIPTION The getexepath() function shall copy an absolute pathname of the executable file associated with the current process into the buffer pointed to by buf, which has a size of bufsize. If the actual length of the pathname (including the terminating null byte) is greater than bufsize, the string shall be truncated to bufsize - 1 bytes and null-terminated. RETURN VALUE Upon successful completion, getexepath() shall return the number of bytes required to hold the full, non-truncated path (excluding the terminating null byte). If an error occurs, -1 shall be returned and errno set to indicate the error. ERRORS The getexepath() function shall fail if: [EINVAL] - The buf argument is a null pointer. [ENOENT] - The executable file has been unlinked from the filesystem and the system cannot resolve its original path. | ||||
| Tags | api, enhancement, issue9, process, unistd | ||||
|
|
When the program has an interpreter, e,g, by starting with "#!", should this return the path of the program, or the path of its interpreter? How do you define "interpreter"? Keep in mind that some systems require an interpreter for some types of binaries; e.g. Linux x86_64 has /lib64/ld-linux-x86-64.so.2 to load ELF binaries. |
|
|
What Problem Is This Trying To Solve? It is my understanding that the programs looking for this info are a) looking for the _directory path_ that contains the executed program (so they can create other paths relative to that, ala ELF $ORIGIN), AND b) would be utterly happy to ERROR OUT if the path by which it had been accessed had been moved or removed between when they were executed and when they asked for this info. Do you, kasperk81, agree or disagree? If you disagree, can you describe how the information from this call will be used, and/or what should be done in the latter cases? Next up: what's the expected behavior when fexecve() was used? That the system merely remember the series of references that were used to get to the file that was openat()ed before the call to fexecve(), regardless of intervening renames? |
|
|
> When the program has an interpreter, e,g, by starting with "#!", should this return the path of the program, or the path of its interpreter? For a #! interpreter, it should return the path of the interpreter. If programs written in the interpreted language want to determine *their* path, that would presumably be done with a language or language-library function, which the interpreter would implement. > Keep in mind that some systems require an interpreter for some types of binaries; e.g. Linux x86_64 has /lib64/ld-linux-x86-64.so.2 to load ELF binaries. Most if not all systems using ELF start dynamically-loaded binaries by running the run-time linker; that's not specific to Linux or Linux on x86-64. The SunOS referred to in the original post is presumably SunOS 5, a/k/a "the core OS part of Solaris 2 and up", which uses ELF and starts dynamically-loaded binaries by running the run-time linker; in that case, getexecname() returns the path to the program run by the run-time linker, not to the path of the run-time linker. macOS (and other Darwin-based OSes) are similar. But that's not the *program* that's really running; once the run-time linker has called the program at its startup address (normally the address of main()), it's just there to handle dlopen(), dlsym(), and company. it's not really an "interpreter" in the sense of a shell or a scripting language interpreter. |
|
|
> It is my understanding that the programs looking for this info are > a) looking for the _directory path_ that contains the executed program (so they can create other paths relative to that, ala ELF $ORIGIN), AND > b) would be utterly happy to ERROR OUT if the path by which it had been accessed had been moved or removed between when they were executed and when they asked for this info. As a core developer on a program that looks for that info (Wireshark): a) Wireshark does that because, in some cases, it uses the path of a directory above the executable to find various data files etc. that it uses, AND b) for better or worse, it will probably have problems if the program *and* all the relevant files were moved after that, so, as the saying goes "Doctor, it hurts when I do this!" "OK, so don't do that!". (If the program is moved after it fetches the executable path, but the other files *aren't* moved, things will work - it only fetches the path once and remembers it after that.) |
|
|
I provided two examples as an obvious contrast, but the real question is whether there is consensus on where exactly the line lies between those extremes should be. Some other examples, by no means comprehensive: · an interpreter for a synthetic byte code such as Dalvik or Python (pyc); · a JIT compiler that produces native byte code and runs it; · an emulator for a different CPU, including ELF loading; · an emulator in hardware (e.g. x86 on Apple Rosetta); · an emulator for a different OS (e.g. Wine); The point isn't to make a choice for each of these; rather the point is to explain the rationale that leads to your preferred choices. > [ld-linux .. is] not really an "interpreter" in the sense of a shell or a scripting language interpreter. For a standard that distinction needs a a formal definition, not just a "sense". Or we just leave it unspecified which path is returned, but I suspect many would find that highly unsatisfactory. |
|
|
Could the definition be adjusted to allow for NULL is bufsize is zero? To deal with interpreters, setexepath() could be provided. When the interpreter starts, getexepath() would refer to its location; as part of starting the interpreted program setexepath() would be called with the location of the interpreted program. |
|
|
Thank you everyone for the thorough review and edge-case analysis. To address the outstanding questions and move this proposal forward: > Do you, kasperk81, agree or disagree? I agree with this assessment. The driving motivation for this API is to allow runtimes, language interpreters, and application frameworks to reliably locate the directory containing the executed binary (similar to ELF `$ORIGIN` or relative asset loading). If the binary path is unlinked or moved mid-execution, returning an error (such as `ENOENT`) is perfectly acceptable and expected behavior for these frameworks. The application can gracefully choose how to handle a missing origin context. > When the program has an interpreter, e,g, by starting with "#!", should this return the path of the program, or the path of its interpreter? ... For a standard that distinction needs a formal definition, not just a "sense". To make the specification watertight and avoid ambiguity, we can align with the established precedent set by Illumos/Solaris `getexecname()`. The function shall return the absolute pathname of the file that was passed as the path argument to the `execve()` (or `execat()`) system call that created the current process image: * Dynamic Linkers (ELF `ld.so`): Because the runtime linker is invoked implicitly by the kernel subsystem as part of processing the target binary, `getexepath()` returns the path of the target binary being loaded, not the linker itself. * Script Interpreters (`#!`): If a script is executed via `#!`, the kernel implicitly executes the interpreter binary. Therefore, `getexepath()` returns the path of the interpreter binary. Runtimes handling synthetic bytecode or JIT environments (Python, JVM, .NET) are responsible for resolving their own internal script/bytecode paths relative to the interpreter or via language-level arguments. > Next up: what's the expected behavior when fexecve() was used? When a process is executed via `fexecve()`, there is no path string associated with the execution. In this scenario, `getexepath()` shall fail, and `errno` shall be set to `EBADF` to indicate the backing image is file-descriptor-based without a path reference. I will update the proposal text to reflect this explicitly. > Could the definition be adjusted to allow for NULL is bufsize is zero? I accept this excellent amendment. I will modify the specification to state that if `bufsize` is `0`, `buf` may be a null pointer, and the function will simply return the number of bytes required to hold the full pathname (excluding the terminating null byte), allowing for a clean double-call allocation pattern. |
|
|
Updated definition: NAME getexepath — get the absolute pathname of the executing process image. SYNOPSIS #include <unistd.h> ssize_t getexepath(char *buf, size_t bufsize); DESCRIPTION The getexepath() function shall determine the absolute pathname of the file that was passed as the path argument to the execve() or execat() system call that created the current process image. If bufsize is greater than 0, the function shall copy this pathname into the buffer pointed to by buf. If the actual length of the pathname (including the terminating null byte) is greater than bufsize, the string shall be truncated to bufsize - 1 bytes and null-terminated. If bufsize is 0, buf may be a null pointer, and the function shall simply return the required buffer size as described below. If the current process image was implicitly loaded via an interpreter script (e.g., via the "#!" mechanism), getexepath() shall return the absolute pathname of the executed interpreter binary itself, not the script file. Implicit invocation of a runtime linker (such as an ELF dynamic linker) by the kernel shall not alter the returned path; the function shall return the path of the target application binary. RETURN VALUE Upon successful completion, getexepath() shall return the number of bytes required to hold the full, non-truncated path (excluding the terminating null byte). If an error occurs, -1 shall be returned and errno set to indicate the error. ERRORS The getexepath() function shall fail if: [EBADF] - The current process image was executed via fexecve(), and no valid pathname is associated with the file descriptor. [EINVAL] - The buf argument is a null pointer and bufsize is greater than 0. [ENOENT] - The executable file has been unlinked from the filesystem and the system cannot resolve its original path. |
|
|
Rather than inventing new interface, would it make more sense to standardize already existing getexecname()? |
|
|
For reference: https://docs.oracle.com/cd/E88353_01/html/E37843/getexecname-3c.html |
|
|
getexecname is unsuitable because it returns the path passed to exec exactly. If exec is passed a relative path, and the program has changed its working directory, then determining the path to the executable is impossible. In order to be useful, it has to return an absolute path. For clarification, what should getexecname return if exec was passed a symlink? Should it return the path to the symlink itself, or the path to the file backing the symlink? I think it should return the symlink, as realpath() can be used to get the underlying executable if neccessery. |
|
|
In certain implementation of this function, if they rely on existing OS APIs such as macOS's `_NSGetExecutablePath`, may overflow if the underlying API takes a `uint32_t` for a size argument when the value passed to getexepath is in the range of UINT32_MAX < buflen < SIZE_MAX. Should a possible error code for this function be added, or a note that it is accepted for an implementation of getexepath to set an error value such as ENAMETOOLONG? |
|
|
We are not obliged to follow exactly what Solaris man page says. The fact that it says getexecname returns first argument of execve is an implementation detail, it is not really something that we can't change. Since existing code handles(usually people just do realpath on returned string) both cases when getexecname returns relative or absolute path, nothing would break if we say that it always returns absolute path or NULL. We can also mention in RATIONALE section that historical implementations used to return a relative path, but due to the fact that application can change its current directory and thus invalidate the possibility of getting full path using getcwd(), it was decided to not standardize ability to return relative path. That being said, I believe it's unlikely POSIX committee would consider getexepath unless OSs adopt it first. However getexecname has a real chance to get standardized because it is not an invention. |
|
|
I'm aware that chroot(2) is not specified by POSIX, but given that A.3 "Rationale for Base Definitions" mentions it, it appears POSIX wants to not unnecessarily inconvenience syst ems that do use chroot(2). What is this suggested new API expected to do if the process called chroot(2)? The currently proposed text appears to suggest it should return the path originally passed to execv e(2). That feels useless at best because that path will not exist inside the chroot. It might even cause security issues by disclosing information about the file system outside the chroot to programs inside. Requiring to return a path _inside_ the chroot would not feel particularly useful either, considering that especially in sane configurations, the e xecutable of a program is typically _not_ installed inside its own chroot, so the new API would fail in most real-world chroot scenarios. So maybe it would be best to require the API to *always* fail with NULL and EACCESS if chroot(2) was called? I don't see any downside to that because given the general fragility of the new API, security-critical softwa re probably should refrain from using it in the first place. Besides, i have some doubts whether adding this or a similar API provides any significant benefits at all. The only use case mentioned here (unless i'm missing something) is infe rring the location of some data files from the location of the executable program. But for that purpose, i can immediately think of at least five well-established methods that ar e less fragile, easier to understand for the user, and more flexible: configuration files, environment variables, command line options, compiled in fallback paths, and/or compiled -in fixed paths. For security-critical software in particular, the last of these five is almost certainly the best because it minimizes the risk of accidental mistakes or malicio us tampering. Compared to these five well-established methods, the present proposal feels fragile because file system layout varies wildly among operating systems and deducing the location of d ata files from the location of an executable does not generally seem possible; it feels intransparent to the user because the behind-the-scenes magic will be hard to document, in particular much harder than documenting configuration files, environment variables, or command line options; it feels inflexible because using *different* sets of data files with the same binary program depending on the current needs of the user is often a perfectly valid use case, which this proposal will hinder, and sometimes maybe prevent outright; and it feels quite wastful because it requires a new system call for a simple, purely userland task that can already be handled in many ways. So, which use case am i missing where none of the at least five common methods for finding data files are usable? |
|
|
I go back to my comment https://www.austingroupbugs.net/view.php?id=1992#c7454 Without a statement of what problem this API is supposed to solve judging whether, for example, chdir/chroot by the process itself should affect the result, is impossible. I believe that if the goal of this is to provide a stable API that operates ala ELF $ORIGIN then * chdir/chroot are _not_ a problem, as this API would be for the program itself fetching stuff, just like it fetches stuff from path names specified as CLI arguments: that those paths are invalidated by chdir/chroot does not make them useless but rather puts the burden on the application to order its operations correctly * doesn't matter if the executable was moved or deleted between execve() and getexecpath(); if the support files that would be located from getexecpath() have also been moved or deleted then the process will probably fail later but that would happen no matter how it got the paths it would have used * the applicability to interpreters at various layers is that EACH LAYER should have an API for that layer to see the path _of that layer's_ content-being-executed, so that they can see the files and content applicable TO THAT LAYER. I still think the fexecve() question is the hard one for this group. Should some program be able to replace every call it has to execve() with a set of calls to open(O_EXEC|O_CLOEXEC)+fexecve() and expect it to *NOT* affect the behavior of the program(s) that were being execed? If the answer is "yes", then getexecname() needs to return some equivalent of the path passed to open(); whether it fails or returns something useless when there's a chdir between the open and fexecve, or if the fd is passed between processes, is still a question that should be answered but, again, _those_ answers should be driven by The Use Case for the Function. If the answer is "no", then the idea that you can use fexecve() to avoid TOCTOU races around execve() is no longer true as doing that will break _unrelated_ programs that use getexecname(). If fexecve()'s use case is being trimmed down to "only safe to use on related programs where you know they aren't using getexecname()" then...wow, then it's even less likely to be implemented by a bunch of OSes that look at POSIX for clarifications and ideas then it already is... I'm kinda thinking now that the better API would be int getexecdirfd(void); That would return an open O_SEARCH or O_RDONLY fd to the _directory_ that contained the non-symbolic-link file which was opened to execute this process. The various *at() calls could then be used to access paths relative to there. Maybe I'll take a stab at implementing that in OpenBSD. |
|
|
The path returned by `getexepath()` is ultimately an 'informational identity token' managed by the system, not a permanent guarantee of raw disk layout. If a system uses security sandboxing, virtualization, or context-dependent jail paths, the implementation simply returns whatever absolute string _it_ actively recognizes as the process's image identity. The runtime can use standard string tools on that token for relative lookups (like `dirname(path) + "/mylogo.jpg"`), while the system can recognize that same string for logging, tracing, debugging, diagnostics, or process management like respawning and killing. Alternatively, for anonymous executions like `fexecve()`, the system could still provide a specialized identifier string for these later operational or asset-finding uses as long as the system knows how to resolve it. If an environment strictly forbids path-based tracking entirely, the function can gracefully drop back to a clean POSIX error code like `EBADF` or `EACCES`. |
|
|
> The runtime can use standard string tools on that token for relative lookups (like `dirname(path) + "/mylogo.jpg"`), > while the system can recognize that same string for logging, tracing, debugging, diagnostics, or process > management like respawning and killing. > > Alternatively, for anonymous executions like `fexecve()`, the system could still provide a specialized identifier > string for these later operational or asset-finding uses as long as the system knows how to resolve it. By "can" in those sentences you mean "it might work on some systems, but POSIX does not require it to work"? To read back then, I believe you're saying that you're taking the 'no' answer in my most recent comment such that on a compliant system getexecname() may fail after (even trivial) fexecve(). It also doesn't sound like an interface that increases portability. |
|
|
I wonder whether requiring an absolute pathname is buying enough to justify the additional semantics it imposes. The Solaris getexecname() model seems attractive precisely because it has a weaker and easier-to-describe contract: return the pathname associated with the exec of the current process, which may be relative. For example, if a process is started with something equivalent to: chdir("/opt/foo"); exec("./bin/app"); then preserving ./bin/app is straightforward. Requiring the implementation to instead return /opt/foo/bin/app means that it must either construct and preserve that absolute pathname at exec time, or preserve enough information to reconstruct it later. The latter is not generally possible after a chdir(), and either approach creates additional implementation requirements that do not seem necessary for identifying how the process was executed. More importantly, making the pathname absolute does not make it a stable identity for the executable. The file or one of its containing directories can subsequently be renamed or removed. An absolute pathname therefore still need not resolve to the executable at the time getexepath() is called. That makes me think the more useful distinction is not absolute versus relative, but what the returned string is intended to represent: - the pathname used or recorded when the program was executed; or - a pathname which currently resolves to the executable object. The first seems much easier to specify portably, and is close to the Solaris semantics. The second imposes substantially stronger requirements and runs directly into rename, unlink, fexecve(), and similar cases. I would therefore suggest dropping the requirement that the pathname be absolute, and defining the interface in terms of the pathname associated with execution. An implementation that has an absolute pathname available can of course return one, and the standard could even recommend doing so, but I am not convinced it should be a requirement. This also avoids giving applications the impression that an absolute result is necessarily a current or canonical name for the executable, which it cannot in general be. There is a related question around interpreters which I think points in the same direction. For a #! script, I do not think this interface should return the pathname of the interpreter merely because that is the native executable image the kernel ultimately loads. If the application was invoked as /opt/foo/bin/tool, and that file names /usr/bin/python3 as its interpreter, returning /usr/bin/python3 would be surprising and would make the interface much less useful to the application. The same issue becomes difficult to define consistently once dynamic linkers, bytecode runtimes, emulators, and similar mechanisms are considered. Trying to specify which of these is the "real executable" seems likely to introduce an implementation-dependent boundary that applications cannot usefully rely on. This is another reason I would prefer to define the result in terms of the pathname associated with the execution request. For a #! script, that would mean the script pathname, not the interpreter pathname. The interpreter is an implementation detail of how that file is executed, rather than the object whose pathname the caller supplied for execution. |
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2026-07-14 16:14 | kasperk81 | New Issue | |
| 2026-07-14 16:14 | kasperk81 | Tag Attached: api | |
| 2026-07-14 16:14 | kasperk81 | Tag Attached: enhancement | |
| 2026-07-14 16:14 | kasperk81 | Tag Attached: process | |
| 2026-07-14 16:14 | kasperk81 | Tag Attached: unistd | |
| 2026-07-20 02:02 | m.kealey | Note Added: 0007453 | |
| 2026-07-20 02:15 | m.kealey | Note Edited: 0007453 | |
| 2026-07-20 06:48 | philip-guenther | Note Added: 0007454 | |
| 2026-07-20 07:01 | Guy Harris | Note Added: 0007455 | |
| 2026-07-20 07:19 | Guy Harris | Note Added: 0007456 | |
| 2026-07-20 15:15 | msbrown | Tag Attached: issue9 | |
| 2026-07-20 17:47 | m.kealey | Note Added: 0007460 | |
| 2026-07-21 02:01 | m.kealey | Note Edited: 0007460 | |
| 2026-07-21 02:04 | m.kealey | Note Edited: 0007460 | |
| 2026-07-21 02:04 | m.kealey | Note Edited: 0007460 | |
| 2026-07-21 02:06 | m.kealey | Note Edited: 0007460 | |
| 2026-07-22 00:29 | dhruv | Note Added: 0007461 | |
| 2026-08-02 00:50 | kasperk81 | Note Added: 0007466 | |
| 2026-08-02 00:55 | kasperk81 | Note Added: 0007467 | |
| 2026-08-02 00:55 | kasperk81 | Note Edited: 0007467 | |
| 2026-08-05 22:37 | illiliti | Note Added: 0007468 | |
| 2026-08-06 02:36 | Love4Boobies | Note Added: 0007469 | |
| 2026-08-07 13:51 | dhruv | Note Added: 0007470 | |
| 2026-08-07 19:42 | dhruv | Note Edited: 0007470 | |
| 2026-08-08 04:06 | william.boyes | Note Added: 0007471 | |
| 2026-08-08 04:23 | william.boyes | Note Edited: 0007471 | |
| 2026-08-08 04:54 | illiliti | Note Added: 0007472 | |
| 2026-08-08 16:32 | schwarze | Note Added: 0007473 | |
| 2026-08-09 00:50 | philip-guenther | Note Added: 0007474 | |
| 2026-08-09 01:48 | kasperk81 | Note Added: 0007475 | |
| 2026-08-09 01:50 | kasperk81 | Note Edited: 0007475 | |
| 2026-08-09 01:54 | kasperk81 | Note Edited: 0007475 | |
| 2026-08-09 02:45 | philip-guenther | Note Added: 0007476 | |
| 2026-08-09 18:50 | Love4Boobies | Note Added: 0007477 |