/*
* getexename.c
*
* written by Nicolai Haehnle <prefect_@gmx.net>
* I hereby release this trivial piece of code to the public domain.
*
* The function getexename() returns the filename of the currently loaded
* executable.
*
* Intended use of this function is to facilitate easier packaging of
* third-party software for the Linux operating system. The FHS mandates
* that files that belong to one package are scattered throughout the
* file system. This works as long as packages are maintained by a
* package management program. However, it is impossible for application
* developers to provide packages for every Linux distribution out there.
* Finding the file locations is also difficult when an application is
* installed locally by a user inside her own home directory.
*
* The simplest and most straight-forward solution to this problem is to
* put all files belonging to a package into the same directory. The program
* executable can then reference the necessary data files by using paths
* relative to the executable location.
* To give an example:
*
* A simple game, consisting of an executable and a number of data files
* (e.g. images), resides entirely in one directory, with absolute filenames
* like this:
* /the/path/foogame
* /the/path/images/hero.png
* /the/path/images/badass.png
* The game executable can use getexename() to find its own location, strip
* off the last component to get the directory the executable is located in,
* and append the relative paths "images/hero.png" and "images/badass.png"
* to reference the data files.
* The game will be completely position independent. The user is free to
* move it somewhere else in the filesystem, and it will just work; it will
* no longer be necessary to change configuration files or even recompile the
* executable.
*
* If you are concerned about executables showing up in a user's PATH, you
* should somehow arrange for symlinks to be made. For example, if
* /usr/games/foogame is a symlink to /the/path/foogame, the user can run the
* game simply by typing "foogame" in the shell (provided that /usr/games is in
* the user's PATH); since symlinks cannot fool getexename(), the game will
* still work. (Do note that a hard link will defeat getexename()).
*
* Note that while it is possible to reference data files based on the current
* working directory, this technique only works if the user explicitly sets
* the CWD to the application's base directory. Therefore, using the executable
* name as a base is more robust.
*
* Also note that while argv[0] can be used as the executable name in many
* cases as well, it is easily fooled by symlinks and may not contain an
* absolute filename. argv[0] can also be set to something entirely different
* from the executable filename by the executing process, either delibaretly
* or by invoking scripts.
*
* Note that this function relies on the layout of the /proc file system, so
* portability is an issue. While I assume that this part of /proc is fairly
* stable, I have no documentation whatsoever about potential differences
* between Linux kernel versions in this area.
*
*/
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
/*
* getexename - Get the filename of the currently running executable
*
* The getexename() function copies an absolute filename of the currently
* running executable to the array pointed to by buf, which is of length size.
*
* If the filename would require a buffer longer than size elements, NULL is
* returned, and errno is set to ERANGE; an application should check for this
* error, and allocate a larger buffer if necessary.
*
* Return value:
* NULL on failure, with errno set accordingly, and buf on success. The
* contents of the array pointed to by buf is undefined on error.
*
* Notes:
* This function is tested on Linux only. It relies on information supplied by
* the /proc file system.
* The returned filename points to the final executable loaded by the execve()
* system call. In the case of scripts, the filename points to the script
* handler, not to the script.
* The filename returned points to the actual exectuable and not a symlink.
*
*/
char* getexename(char* buf, size_t size)
{
char linkname[64]; /* /proc/<pid>/exe */
pid_t pid;
int ret;
/* Get our PID and build the name of the link in /proc */
pid = getpid();
if (snprintf(linkname, sizeof(linkname), "/proc/%i/exe", pid) < 0)
{
/* This should only happen on large word systems. I'm not sure
what the proper response is here.
Since it really is an assert-like condition, aborting the
program seems to be in order. */
abort();
}
/* Now read the symbolic link */
ret = readlink(linkname, buf, size);
/* In case of an error, leave the handling up to the caller */
if (ret == -1)
return NULL;
/* Report insufficient buffer size */
if (ret >= size)
{
errno = ERANGE;
return NULL;
}
/* Ensure proper NUL termination */
buf[ret] = 0;
return buf;
}
#if 1 /* testing */
/*
* Trivial test and sample use of getexename().
*/
int main()
{
char* buf;
int size;
buf = NULL;
size = 32; /* Set an initial size estimate */
for(;;)
{
char* res;
/* Allocate and fill the buffer */
buf = (char*)malloc(size);
res = getexename(buf, size);
/* Get out of the loop on success */
if (res)
break;
/* Anything but ERANGE indicates a real error */
if (errno != ERANGE)
{
perror("getexename() failed");
free(buf);
buf = NULL;
break;
}
/* ERANGE means the buffer was too small. Free the current
buffer and retry with a bigger one. */
free(buf);
size *= 2;
}
/* Exit on failure */
if (buf == NULL)
return -1;
/* Now print the executable name. */
printf("getexename(): %s\n", buf);
free(buf);
return 0; /* Indicate success */
}
#endif /* testing */
|