Программно получить абсолютный путь к приложению командной строки OS X

В Linux приложение может легко получить свой абсолютный путь, запрашивая /proc/self/exe, В FreeBSD это более сложный процесс, так как вам нужно создать вызов sysctl:

int mib[4];
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PATHNAME;
mib[3] = -1;
char buf[1024];
size_t cb = sizeof(buf);
sysctl(mib, 4, buf, &cb, NULL, 0);

но это все еще вполне выполнимо. Все же я не могу найти способ определить это на OS X для приложения командной строки. Если вы работаете из пакета приложения, вы можете определить его, запустив [[NSBundle mainBundle] bundlePath], но поскольку приложения командной строки не входят в пакет, это не помогает.

(Примечание: консалтинг argv[0] не является разумным ответом, так как, если запущен по символической ссылке, argv[0] будет символическая ссылка, а не конечный путь к вызываемому исполняемому файлу. argv[0] также может лгать, если тупое приложение использует exec() позвоните и забудьте правильно инициализировать argv, что я видел в дикой природе.)

7 ответов

Решение

Функция _NSGetExecutablePath вернет полный путь к исполняемому файлу (GUI или нет). Путь может содержать символические ссылки " .. и т. д. но realpath Функция может быть использована для очистки, если это необходимо. Увидеть man3dyld для дополнительной информации.

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);

Секрет этой функции заключается в том, что ядро ​​Дарвина помещает исполняемый путь в стек процесса сразу после envp массив, когда он создает процесс. Редактор динамических ссылок dyld захватывает это при инициализации и сохраняет указатель на него. Эта функция использует этот указатель.

Я считаю, что есть гораздо более элегантное решение, которое на самом деле работает для любого PID, а также напрямую возвращает абсолютный путь:

#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;
}

Похоже, ответ в том, что вы не можете сделать это:

Я пытаюсь достичь чего-то вроде функциональности lsof и собрать целую кучу статистики и информации о запущенных процессах. Если бы я не был таким медленным, я был бы счастлив придерживаться этого.

Если вы переопределите lsof, вы обнаружите, что он медленный, потому что он выполняет много работы.

Я полагаю, что это не совсем так, потому что lsof работает в пользовательском режиме, скорее, он должен сканировать адресное пространство задачи и искать вещи, поддерживаемые внешним пейджером. Есть ли более быстрый способ сделать это, когда я в ядре?

Нет, это не глупо; он делает то, что должен. Если вы просто хотите получить подмножество его функциональных возможностей, вы можете подумать о том, чтобы начать с исходного кода lsof (который доступен) и урезать его в соответствии с вашими требованиями.

Из любопытства, это p_textvp используется вообще? Похоже, он установлен для родителей p_textvp в kern_fork (и затем получить освобождение??), но это не касается ни в одном из kern_execрутины.

p_textvp не используется В Дарвине proc не является корнем адресного пространства; задача есть. Не существует понятия "vnode" для адресного пространства задачи, так как оно не обязательно изначально заполняется отображением.

Если exec должен заполнить p_textvp, он будет потворствовать предположению, что все процессы поддерживаются vnode. Тогда программисты предполагали бы, что можно получить путь к vnode, и оттуда происходит быстрый переход к предположению, что текущий путь к vnode - это путь, с которого он был запущен, и что обработка текста в строке может привести к имени пакета приложения... все это было бы невозможно гарантировать без существенного штрафа.

- Майк Смит, список рассылки Darwin Drivers

Уже поздно, но [[NSBundle mainBundle] executablePath] прекрасно работает для не связанных программ командной строки.

Думаю, нет гарантированного способа. Если argv[0] является символической ссылкой, то вы можете использовать readlink(). Если команда выполняется через $PATH, можно попробовать некоторые из: search(getenv("PATH")), getenv("_"), dladdr()

Почему не просто realpath(argv[0], actualpath);? Правда, у realpath есть некоторые ограничения (задокументированные на странице руководства), но он прекрасно обрабатывает символические ссылки. Протестировано на FreeBSD и Linux

    % ls -l foobar lrwxr-xr-x 1 bortzmeyer bortzmeyer 22 апр. 29 07:39 foobar -> /tmp/get-real-name-exe

    % ./foobar Мой реальный путь: /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);
}

Если программа запускается через PATH, см. Решение pixelbeat.

Другие вопросы по тегам