0

Say I have a program, abc in Linux, with a method:

char *currentPath(){
        char *path=NULL;
        path = getcwd(path, MAXPATHLEN);
        return path;
}

When calling abc directly, this path returns the path that abc is in.

If I make a symbolic link to abc, and call the symbolic link, currentPath() returns the path of the symbolic link.

Is there a way to get make this method return the path of abc? I am interested in accessing files relative to the location of abc.

Sheldon Warkentin
  • 1,706
  • 2
  • 14
  • 31

3 Answers3

4

You can't use getcwd() to help get the executable name; it is not a sensible way to go about finding the pathname of the executable.

On Linux, there is a symlink /proc/self/exe that gives you the name of the executable if you read it with the readlink() system call. Beware, the readlink() system call does not null terminate the value it returns (and I have no idea why not; it is singularly weird behaviour in my book, and an instant cause of bugs for the unwary).

There are a number of problems with your scheme.

  1. If the program is found via $PATH, there's no guarantee that the current directory is the same as the directory containing the executable. If you think about it, you run /bin/ls without having to be in the /bin directory.
  2. If you're worried about security, be aware that the value of argv[0] is under the control of the program launching the target program. The shell behaves nicely; other programs may be more malicious:

    #include <unistd.h>
    int main(void)
    {
        char *argv[] = { "/opt/you/bin/bogus", "300", 0 };
        execvp("sleep", argv);
        return(-1);
    }
    

    This passes the program name as /opt/you/bin/bogus even though it invokes the program sleep.

If you search the web, you will find plenty of examples of 'how to get the executable name' that assume that argv[0] is the way to go; it is not. Other platforms have other techniques for getting to the executable name. Using /proc/self/exe is not portable; neither are the other techniques.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • @JohnathanLeffler Thank you very much for explaining this. I think it makes the most sense to follow readlink based on your reply. In my situation I am currently only supporting linux, but will expand to other platforms. Based on the anwswer in: http://stackoverflow.com/a/1024937/618775 it looks like I will need expand this particular requirement to have different implementations for each platform. I think that is okay. Again, thanks for helping a C#/Java developer making an extremely awkward entry back into the world of C. – Sheldon Warkentin Aug 12 '12 at 16:39
2

Use realpath(const char *path, char *resolved_path)

realpath() expands all symbolic links and resolves references to /./, /../ and extra '/' characters in the null-terminated string named by path to produce a canonicalized absolute pathname.

In your case:

char *currentPath() {
  char *path, *canon_path;
  path = getcwd(NULL, MAXPATHLEN);
  canon_path = realpath(path, NULL);
  free(path);
  return canon_path;
}

Note that this does not get the path of the executable program (it is unclear what you're trying to do). To do that portably is trickier. You'll need to use the value of argv[0] to get it:

char *bindir(char *argv0) {
  char *canon_path = realpath(argv0, NULL);
  char *canon_dir = strdup(dirname(canon_path));
  free(canon_path);
  return canon_dir;
}

The strdup call is required because dirname may modify its argument and return a pointer into that or return a pointer to a statically allocated buffer.

Geoff Reedy
  • 34,891
  • 3
  • 56
  • 79
  • 1
    The current directory is not germane to finding the path name of the executable. – Jonathan Leffler Aug 12 '12 at 06:46
  • Thank you for the response. This works for the most part, until the executable is launched from PATH. In this case, realpath does not work. Your explanation is clear though, and does solve the symlink issue. – Sheldon Warkentin Aug 12 '12 at 16:36
0

Try this code (modify it to meet your need):

#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>

#define MAXPATHLEN      256

char buffer[1024];

char *currentPath(char *bin_path){
        char *path = NULL;
        ssize_t len;
        struct stat file_stat;

        if (lstat(bin_path, &file_stat) != 0) 
        {
                /* handle error */
                fprintf(stderr, "ERROR\n");
        }

        if (!S_ISREG(file_stat.st_mode)) 
        {
                /* file is a symlink */
                fprintf(stdout, "Is a symlink\n");
                if ((len = readlink(bin_path, buffer, sizeof(buffer)-1)) != -1)
                        buffer[len] = '\0';
                /* In this case we return the name of the link */
                return buffer;
        }
        else
        {
                fprintf(stdout, "Is a regular file\n");
                path = getcwd(bin_path, MAXPATHLEN);

                return path;
        }
}

int main(int argc, char **argv)
{
        fprintf(stdout, "My path is : %s\n", currentPath(argv[0]));

        return 0;
}
TOC
  • 4,326
  • 18
  • 21
  • 1
    What happens if the first symlink points to a second one? The use of `path = getcwd(bin_path, MAXPATHLEN);` is a little odd, too (for multiple reasons). And, despite the question discussing it, the current working directory has essentially nothing to do with the location of the executable. – Jonathan Leffler Aug 12 '12 at 07:17