Programmatically retrieving the absolute path of an OS X command-line app

后端 未结 7 465
予麋鹿
予麋鹿 2020-11-27 15:04

On Linux, an application can easily get its absolute path by querying /proc/self/exe. On FreeBSD, it\'s more involved, since you have to build up a sysctl call

相关标签:
7条回答
  • 2020-11-27 15:07

    The function _NSGetExecutablePath will return a full path to the executable (GUI or not). The path may contain symbolic links, "..", etc. but the realpath function can be used to clean those up if needed. See man 3 dyld for more information.

    char path[1024];
    uint32_t size = sizeof(path);
    if (_NSGetExecutablePath(path, &size) == 0)
        printf("executable path is %s\n", path);
    else
        printf("buffer too small; need size %u\n", size);
    

    The secret to this function is that the Darwin kernel puts the executable path on the process stack immediately after the envp array when it creates the process. The dynamic link editor dyld grabs this on initialization and keeps a pointer to it. This function uses that pointer.

    0 讨论(0)
  • 2020-11-27 15:09

    I believe there is much more elegant solution, which actually works for any PID, and also returns the absolute path directly:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    #include <libproc.h>
    
    int main (int argc, char* argv[])
    {
        int ret;
        pid_t pid; 
        char pathbuf[PROC_PIDPATHINFO_MAXSIZE];
    
        pid = getpid();
        ret = proc_pidpath (pid, pathbuf, sizeof(pathbuf));
        if ( ret <= 0 ) {
            fprintf(stderr, "PID %d: proc_pidpath ();\n", pid);
            fprintf(stderr, "    %s\n", strerror(errno));
        } else {
            printf("proc %d: %s\n", pid, pathbuf);
        }
    
        return 0;
    }
    
    0 讨论(0)
  • 2020-11-27 15:16

    Why not simply realpath(argv[0], actualpath);? True, realpath has some limits (documented in the manual page) but it handles symbolic links fine. Tested on FreeBSD and Linux

        % ls -l foobar 
        lrwxr-xr-x  1 bortzmeyer  bortzmeyer  22 Apr 29 07:39 foobar -> /tmp/get-real-name-exe
    
        % ./foobar 
        My real path: /tmp/get-real-name-exe
    
    #include <limits.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <libgen.h>
    #include <string.h>
    #include <sys/stat.h>
    
    int
    main(argc, argv)
        int             argc;
        char          **argv;
    {
        char            actualpath[PATH_MAX + 1];
    
        if (argc > 1) {
            fprintf(stderr, "Usage: %s\n", argv[0]);
            exit(1);
        }
        realpath(argv[0], actualpath);
        fprintf(stdout, "My real path: %s\n", actualpath);
        exit(0);
    }
    

    If the program is launched via PATH, see pixelbeat's solution.

    0 讨论(0)
  • 2020-11-27 15:24

    http://developer.apple.com/documentation/Carbon/Reference/Process_Manager/Reference/reference.html#//apple_ref/c/func/GetProcessBundleLocation

    GetProcessBundleLocation seems to work.

    0 讨论(0)
  • 2020-11-27 15:25

    There is no guaranteed way I think. If argv[0] is a symlink then you could use readlink(). If command is executed through the $PATH then one could try some of: search(getenv("PATH")), getenv("_"), dladdr()

    0 讨论(0)
  • 2020-11-27 15:29

    Looks like the answer is that you can't do it:

    I'm trying to achieve something like lsof's functionality and gather a whole bunch of statistics and info about running processes. If lsof weren't so slow, I'd be happy sticking with it.

    If you reimplement lsof, you will find that it's slow because it's doing a lot of work.

    I guess that's not really because lsof is user-mode, it's more that it has to scan through a task's address space looking for things backed by an external pager. Is there any quicker way of doing this when I'm in the kernel?

    No. lsof is not stupid; it's doing what it has to do. If you just want a subset of its functionality, you might want to consider starting with the lsof source (which is available) and trimming it down to meet your requirements.

    Out of curiosity, is p_textvp used at all? It looks like it's set to the parent's p_textvp in kern_fork (and then getting released??) but it's not getting touched in any of kern_exec's routines.

    p_textvp is not used. In Darwin, the proc is not the root of the address space; the task is. There is no concept of "the vnode" for a task's address space, as it is not necessarily initially populated by mapping one.

    If exec were to populate p_textvp, it would pander to the assumption that all processes are backed by a vnode. Then programmers would assume that it was possible to get a path to the vnode, and from there it is a short jump to the assumption that the current path to the vnode is the path from which it was launched, and that text processing on the string might lead to the application bundle name... all of which would be impossible to guarantee without substantial penalty.

    —Mike Smith, Darwin Drivers mailing list

    0 讨论(0)
提交回复
热议问题