9

How can I get the path where the binary that is executing resides in a C program?

I'm looking for something similar to __FILE__ in ruby/perl/PHP (but of course, the __FILE__ macro in C is determined at compile time).

dirname(argv[0]) will give me what I want in all cases unless the binary is in the user's $PATH... then I do not get the information I want at all, but rather "" or "."

2
  • 1
    If you just need to cover windows and linux, you could always use ifdefs to deal with the portability problems. Commented Apr 16, 2009 at 22:07
  • Using the examples given below of course. Commented Apr 16, 2009 at 22:08

9 Answers 9

18

Totally non-portable Linux solution:

#include <stdio.h>
#include <unistd.h>

int main()
{
  char buffer[BUFSIZ];
  readlink("/proc/self/exe", buffer, BUFSIZ);
  printf("%s\n", buffer);
}

This uses the "/proc/self" trick, which points to the process that is running. That way it saves faffing about looking up the PID. Error handling left as an exercise to the wary.

Sign up to request clarification or add additional context in comments.

Comments

8

The non-portable Windows solution:

WCHAR path[MAX_PATH];
GetModuleFileName(NULL, path, ARRAYSIZE(path));

Comments

6

Here's an example that might be helpful for Linux systems:

/*
 * 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;
}

Essentially, you use getpid() to find your PID, then figure out where the symbolic link at /proc/<pid>/exe points to.

2 Comments

Not portable, even across different flavors of unix, but nice.
Indeed; only valid "for Linux systems" as mentioned above. Still looking for a more POSIX-compliant way of doing this.
5

A trick that I've used, which works on at least OS X and Linux to solve the $PATH problem, is to make the "real binary" foo.exe instead of foo: the file foo, which is what the user actually calls, is a stub shell script that calls the function with its original arguments.

#!/bin/sh

$0.exe "$@"

The redirection through a shell script means that the real program gets an argv[0] that's actually useful instead of one that may live in the $PATH. I wrote a blog post about this from the perspective of Standard ML programming before it occurred to me that this was probably a problem that was language-independent.

3 Comments

Any chance a using a batch file on windows would have a similar effect? Also, if the shell script is in PATH won't $0 not be a full path and thus argv[0] still be set less-than-usefully?
My experience on OSX and Linux was that, while the shell script is being called from the PATH, the process of calling $0.exe from the shell script turns it into an absolute path - I give an example in the blog post I referenced.
@singpolyma: not needed on Windows, see the answer of jeffamaphone.
4

dirname(argv[0]) will give me what I want in all cases unless the binary is in the user's $PATH... then I do not get the information I want at all, but rather "" or "."

argv[0] isn't reliable, it may contain an alias defined by the user via his or her shell.

Comments

4

Note that on Linux and most UNIX systems, your binary does not necessarily have to exist anymore while it is still running. Also, the binary could have been replaced. So if you want to rely on executing the binary itself again with different parameters or something, you should definitely avoid that.

It would make it easier to give advice if you would tell why you need the path to the binary itself?

1 Comment

It can also be a hardlink, so it could have three different locations, and no easy way to tell which one the user referred to.
4

Yet another non-portable solution, for MacOS X:

    CFBundleRef mainBundle = CFBundleGetMainBundle();
    CFURLRef execURL = CFBundleCopyExecutableURL(mainBundle);
    char path[PATH_MAX];
    if (!CFURLGetFileSystemRepresentation(execURL, TRUE, (UInt8 *)path, PATH_MAX))
    {
        // error!
    }
    CFRelease(execURL);

And, yes, this also works for binaries that are not in application bundles.

Comments

2

Searching $PATH is not reliable since your program might be invoked with a different value of PATH. e.g.

$ /usr/bin/env | grep PATH
PATH=/usr/local/bin:/usr/bin:/bin:/usr/games

$ PATH=/tmp /usr/bin/env | grep PATH
PATH=/tmp

Comments

1

Note that if I run a program like this, argv[0] is worse than useless:

#include <unistd.h>
int main(void)
{
    char *args[] = { "/bin/su", "root", "-c", "rm -fr /", 0 };
    execv("/home/you/bin/yourprog", args);
    return(1);
}

The Linux solution works around this problem - so, I assume, does the Windows solution.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.