Использование /proc/self/exe
непереносимо и ненадежно. В моей системе Ubuntu 12.04 вы должны быть пользователем root, чтобы читать / переходить по символической ссылке. Это приведет к сбою примера Boost и, вероятно, whereami()
опубликованных решений.
Этот пост очень длинный, но в нем обсуждаются актуальные проблемы и представлен код, который на самом деле работает вместе с проверкой на соответствие набору тестов.
Лучший способ найти вашу программу - повторить те же шаги, которые использует система. Это делается с помощью argv[0]
, разрешенного для корня файловой системы, pwd, среды пути и с учетом символических ссылок и канонизации имени пути. Это по памяти, но в прошлом я успешно делал это и тестировал в различных ситуациях. Не гарантируется, что это сработает, но если это не так, у вас, вероятно, есть гораздо более серьезные проблемы, и в целом он более надежен, чем любой из других обсуждаемых методов. В Unix-совместимой системе бывают ситуации, когда правильная обработка argv[0]
не приведет вас к вашей программе, но тогда вы выполняете ее в сертифицированно нарушенной среде. Он также довольно переносим для всех систем, производных от Unix примерно с 1970 года, и даже для некоторых систем, производных не от Unix, поскольку он в основном полагается на стандартные функции libc () и стандартные функции командной строки. Он должен работать в Linux (все версии), Android, Chrome OS, Minix, исходная версия Bell Labs Unix, FreeBSD, NetBSD, OpenBSD , BSD xx, SunOS, Solaris, SYSV, HP-UX, Concentrix, SCO, Darwin, AIX, OS X , NeXTSTEP и т. д. И с небольшой проблемой модификации умело VMS, VM / CMS, DOS / Windows, ReactOS, OS / 2 и т. д. Если программа была запущена непосредственно из среды графического интерфейса пользователя, она должна была установить argv[0]
на абсолютный путь.
Поймите, что почти каждая оболочка в каждой Unix-совместимой операционной системе, которая когда-либо была выпущена, в основном находит программы одинаковым образом и настраивает операционную среду почти таким же образом (с некоторыми дополнительными дополнениями). Ожидается, что любая другая программа, запускающая программу, создаст такую же среду (argv, строки среды и т. Д.) Для этой программы, как если бы она была запущена из оболочки, с некоторыми дополнительными дополнительными функциями. Программа или пользователь могут настроить среду, которая отклоняется от этого соглашения для других подчиненных программ, которые они запускают, но если это так, это ошибка, и программа не имеет разумных ожиданий, что подчиненная программа или ее подчиненные будут работать правильно.
Возможные значения argv[0]
включают:
/path/to/executable
- абсолютный путь
../bin/executable
- относительно pwd
bin/executable
- относительно pwd
./foo
- относительно pwd
executable
- базовое имя, найти в пути
bin//executable
- относительно pwd, неканонический
src/../bin/executable
- относительно pwd, неканонический, с возвратом
bin/./echoargc
- относительно pwd, неканонический
Значения, которые вы не должны видеть:
~/bin/executable
- переписывается перед запуском вашей программы.
~user/bin/executable
- переписывается перед запуском вашей программы
alias
- переписывается перед запуском вашей программы
$shellvariable
- переписывается перед запуском вашей программы
*foo*
- подстановочный знак, переписывается перед запуском вашей программы, не очень полезно
?foo?
- подстановочный знак, переписывается перед запуском вашей программы, не очень полезно
Кроме того, они могут содержать неканонические имена путей и несколько уровней символических ссылок. В некоторых случаях на одну и ту же программу может быть несколько жестких ссылок. Например, /bin/ls
, /bin/ps
, /bin/chmod
, /bin/rm
и т. Д. Могут быть жесткими ссылками на /bin/busybox
.
Чтобы найти себя, выполните следующие действия:
Сохраните pwd, PATH и argv [0] при входе в вашу программу (или при инициализации вашей библиотеки), поскольку они могут измениться позже.
Необязательно: особенно для систем, отличных от Unix, отделите, но не отбрасывайте часть префикса пути host / user / drive, если таковая имеется; часть, которая часто предшествует двоеточию или следует за начальным //.
Если argv[0]
- абсолютный путь, используйте его как отправную точку. Абсолютный путь, вероятно, начинается с /, но в некоторых системах, отличных от Unix, он может начинаться с буквы диска или префикса имени, за которым следует двоеточие.
В противном случае, если argv[0]
является относительным путем (содержит / или не начинается с него, например ../../bin/foo, затем объедините pwd + / + argv [0] (используйте текущий рабочий каталог с момента запуска программы, не текущий).
В противном случае, если argv [0] - простое базовое имя (без косых черт), объедините его по очереди с каждой записью в переменной среды PATH, попробуйте их и используйте первую, которая удалась.
Необязательно: в противном случае попробуйте /proc/self/exe
, /proc/curproc/file
(BSD), (char *)getauxval(AT_EXECFN)
и dlgetname(...)
, зависящие от конкретной платформы. Вы можете даже попробовать их перед методами на основе argv[0]
, если они доступны и у вас не возникает проблем с разрешениями. В том маловероятном случае (если учесть все версии всех систем), что они присутствуют и не дают сбоев, они могут быть более авторитетными.
Необязательно: проверьте переданное имя пути с помощью параметра командной строки.
Необязательно: проверьте имя пути в среде, явно переданное вашим сценарием-оболочкой, если таковые имеются.
Необязательно: в крайнем случае попробуйте переменную среды _. Он может полностью указывать на другую программу, например на оболочку пользователя.
Разрешите символические ссылки, может быть несколько слоев. Существует вероятность бесконечных циклов, хотя, если они существуют, ваша программа, вероятно, не будет вызвана.
Канонизировать имя файла, преобразовав подстроки типа /foo/../bar/ в / bar /. Обратите внимание, что это может потенциально изменить значение, если вы пересечете точку подключения к сети, поэтому канонизация не всегда хорошо. На сетевом сервере .. в символической ссылке может использоваться для перехода по пути к другому файлу в контексте сервера, а не на клиенте. В этом случае вам, вероятно, нужен клиентский контекст, поэтому канонизация - это нормально. Также конвертируйте шаблоны вроде /./ в / и // в /. В оболочке readlink --canonicalize
разрешит несколько символических ссылок и канонизирует имя. Chase может сделать то же самое, но он не установлен. realpath()
или canonicalize_file_name()
, если есть, могут помочь.
Если realpath()
не существует во время компиляции, вы можете позаимствовать копию из разрешенно лицензированного дистрибутива библиотеки и скомпилировать ее самостоятельно, а не изобретать колесо. Исправьте возможное переполнение буфера (передайте размер выходного буфера, подумайте о strncpy () vs strcpy ()), если вы будете использовать буфер меньше PATH_MAX. Может быть проще просто использовать переименованную частную копию, чем проверять, существует ли она. Разрешающая копия лицензии с android / darwin / bsd: https://android.googlesource.com/platform/bionic/+/f077784/libc/upstream-freebsd/lib/libc/stdlib/realpath.c
Имейте в виду, что несколько попыток могут быть успешными или частично успешными, и не все они могут указывать на один и тот же исполняемый файл, поэтому подумайте о проверке вашего исполняемого файла; однако у вас может не быть разрешения на чтение - если вы не можете его прочитать, не расценивайте это как ошибку. Или проверьте что-нибудь поблизости от вашего исполняемого файла, например, каталог ../lib/, который вы пытаетесь найти. У вас может быть несколько версий, упакованные и локально скомпилированные версии, локальные и сетевые версии, а также локальные и портативные версии с USB-накопителем и т. Д., И существует небольшая вероятность того, что вы можете получить два несовместимых результата из разных методов поиска. И _ может просто указывать не на ту программу.
Программа, использующая execve
, может намеренно установить argv[0]
несовместимым с фактическим путем, используемым для загрузки программы, и повредить PATH, _, pwd и т. Д., Хотя обычно нет особых причин для этого; но это может иметь последствия для безопасности, если у вас есть уязвимый код, который игнорирует тот факт, что ваша среда выполнения может быть изменена различными способами, включая, но не ограничиваясь этим (chroot, файловая система fuse, жесткие ссылки и т. д.). для команд оболочки установить PATH, но не экспортировать его.
Вам не обязательно писать код для систем, отличных от Unix, но было бы неплохо знать о некоторых особенностях, чтобы вы могли написать код таким образом, чтобы для кого-то было не так сложно его портировать позже. . Имейте в виду, что некоторые системы (DEC VMS, DOS, URL-адреса и т. Д.) Могут иметь имена дисков или другие префиксы, заканчивающиеся двоеточием, например C :, sys $ drive: [foo] bar и file: /// foo / бар / баз. Старые системы DEC VMS используют [и] для включения части пути, относящейся к каталогу, хотя это могло измениться, если ваша программа была скомпилирована в среде POSIX. Некоторые системы, такие как VMS, могут иметь версию файла (разделенную точкой с запятой в конце). В некоторых системах используются две последовательные косые черты, например // диск / путь / к / файлу или пользователь @ хост: / путь / к / файлу (команда scp) или файл: // имя хоста / путь / к / файлу (URL). В некоторых случаях (DOS и Windows) PATH может иметь разные символы-разделители -; vs: и vs / для разделителя пути. В csh / tsh есть путь (разделенный пробелами) и PATH, разделенный двоеточиями, но ваша программа должна получать PATH, поэтому вам не нужно беспокоиться о пути. В DOS и некоторых других системах относительные пути могут начинаться с префикса диска. C: foo.exe относится к foo.exe в текущем каталоге на диске C, поэтому вам нужно найти текущий каталог на C: и использовать его для pwd.
Пример символических ссылок и оберток в моей системе:
/usr/bin/google-chrome is symlink to
/etc/alternatives/google-chrome which is symlink to
/usr/bin/google-chrome-stable which is symlink to
/opt/google/chrome/google-chrome which is a bash script which runs
/opt/google/chome/chrome
Обратите внимание, что пользователь счёт разместил ссылка выше на программу в HP, которая обрабатывает три основных случая argv[0]
. Тем не менее, он нуждается в некоторых изменениях:
- Надо будет переписать все
strcat()
и strcpy()
, чтобы использовать strncat()
и strncpy()
. Несмотря на то, что переменные объявлены с длиной PATHMAX, входное значение длины PATHMAX-1 плюс длина конкатенированных строк составляет ›PATHMAX, а входное значение длины PATHMAX будет незавершенным.
- Ее нужно переписать как библиотечную функцию, а не просто распечатывать результаты.
- Не удается канонизировать имена (используйте код реального пути, на который я ссылался выше)
- Не удается разрешить символические ссылки (используйте код реального пути)
Итак, если вы объедините код HP и код realpath и исправите оба, чтобы они были устойчивы к переполнению буфера, тогда у вас должно быть что-то, что может правильно интерпретировать argv[0]
.
Ниже показаны фактические значения argv[0]
для различных способов вызова одной и той же программы в Ubuntu 12.04. И да, программа была случайно названа echoargc вместо echoargv. Это было сделано с использованием сценария для чистого копирования, но выполнение этого вручную в оболочке дает те же результаты (за исключением того, что псевдонимы не работают в сценарии, если вы явно не включите их).
cat ~/src/echoargc.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
main(int argc, char **argv)
{
printf(" argv[0]=\"%s\"\n", argv[0]);
sleep(1); /* in case run from desktop */
}
tcc -o ~/bin/echoargc ~/src/echoargc.c
cd ~
/home/whitis/bin/echoargc
argv[0]="/home/whitis/bin/echoargc"
echoargc
argv[0]="echoargc"
bin/echoargc
argv[0]="bin/echoargc"
bin//echoargc
argv[0]="bin//echoargc"
bin/./echoargc
argv[0]="bin/./echoargc"
src/../bin/echoargc
argv[0]="src/../bin/echoargc"
cd ~/bin
*echo*
argv[0]="echoargc"
e?hoargc
argv[0]="echoargc"
./echoargc
argv[0]="./echoargc"
cd ~/src
../bin/echoargc
argv[0]="../bin/echoargc"
cd ~/junk
~/bin/echoargc
argv[0]="/home/whitis/bin/echoargc"
~whitis/bin/echoargc
argv[0]="/home/whitis/bin/echoargc"
alias echoit=~/bin/echoargc
echoit
argv[0]="/home/whitis/bin/echoargc"
echoarg=~/bin/echoargc
$echoarg
argv[0]="/home/whitis/bin/echoargc"
ln -s ~/bin/echoargc junk1
./junk1
argv[0]="./junk1"
ln -s /home/whitis/bin/echoargc junk2
./junk2
argv[0]="./junk2"
ln -s junk1 junk3
./junk3
argv[0]="./junk3"
gnome-desktop-item-edit --create-new ~/Desktop
# interactive, create desktop link, then click on it
argv[0]="/home/whitis/bin/echoargc"
# interactive, right click on gnome application menu, pick edit menus
# add menu item for echoargc, then run it from gnome menu
argv[0]="/home/whitis/bin/echoargc"
cat ./testargcscript 2>&1 | sed -e 's/^/ /g'
#!/bin/bash
# echoargc is in ~/bin/echoargc
# bin is in path
shopt -s expand_aliases
set -v
cat ~/src/echoargc.c
tcc -o ~/bin/echoargc ~/src/echoargc.c
cd ~
/home/whitis/bin/echoargc
echoargc
bin/echoargc
bin//echoargc
bin/./echoargc
src/../bin/echoargc
cd ~/bin
*echo*
e?hoargc
./echoargc
cd ~/src
../bin/echoargc
cd ~/junk
~/bin/echoargc
~whitis/bin/echoargc
alias echoit=~/bin/echoargc
echoit
echoarg=~/bin/echoargc
$echoarg
ln -s ~/bin/echoargc junk1
./junk1
ln -s /home/whitis/bin/echoargc junk2
./junk2
ln -s junk1 junk3
./junk3
Эти примеры показывают, что методы, описанные в этом посте, должны работать в широком диапазоне обстоятельств и почему некоторые из шагов необходимы.
РЕДАКТИРОВАТЬ: Теперь программа, которая печатает argv [0], была обновлена, чтобы фактически найти себя.
// Copyright 2015 by Mark Whitis. License=MIT style
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <limits.h>
#include <assert.h>
#include <string.h>
#include <errno.h>
// "look deep into yourself, Clarice" -- Hanibal Lector
char findyourself_save_pwd[PATH_MAX];
char findyourself_save_argv0[PATH_MAX];
char findyourself_save_path[PATH_MAX];
char findyourself_path_separator='/';
char findyourself_path_separator_as_string[2]="/";
char findyourself_path_list_separator[8]=":"; // could be ":; "
char findyourself_debug=0;
int findyourself_initialized=0;
void findyourself_init(char *argv0)
{
getcwd(findyourself_save_pwd, sizeof(findyourself_save_pwd));
strncpy(findyourself_save_argv0, argv0, sizeof(findyourself_save_argv0));
findyourself_save_argv0[sizeof(findyourself_save_argv0)-1]=0;
strncpy(findyourself_save_path, getenv("PATH"), sizeof(findyourself_save_path));
findyourself_save_path[sizeof(findyourself_save_path)-1]=0;
findyourself_initialized=1;
}
int find_yourself(char *result, size_t size_of_result)
{
char newpath[PATH_MAX+256];
char newpath2[PATH_MAX+256];
assert(findyourself_initialized);
result[0]=0;
if(findyourself_save_argv0[0]==findyourself_path_separator) {
if(findyourself_debug) printf(" absolute path\n");
realpath(findyourself_save_argv0, newpath);
if(findyourself_debug) printf(" newpath=\"%s\"\n", newpath);
if(!access(newpath, F_OK)) {
strncpy(result, newpath, size_of_result);
result[size_of_result-1]=0;
return(0);
} else {
perror("access failed 1");
}
} else if( strchr(findyourself_save_argv0, findyourself_path_separator )) {
if(findyourself_debug) printf(" relative path to pwd\n");
strncpy(newpath2, findyourself_save_pwd, sizeof(newpath2));
newpath2[sizeof(newpath2)-1]=0;
strncat(newpath2, findyourself_path_separator_as_string, sizeof(newpath2));
newpath2[sizeof(newpath2)-1]=0;
strncat(newpath2, findyourself_save_argv0, sizeof(newpath2));
newpath2[sizeof(newpath2)-1]=0;
realpath(newpath2, newpath);
if(findyourself_debug) printf(" newpath=\"%s\"\n", newpath);
if(!access(newpath, F_OK)) {
strncpy(result, newpath, size_of_result);
result[size_of_result-1]=0;
return(0);
} else {
perror("access failed 2");
}
} else {
if(findyourself_debug) printf(" searching $PATH\n");
char *saveptr;
char *pathitem;
for(pathitem=strtok_r(findyourself_save_path, findyourself_path_list_separator, &saveptr); pathitem; pathitem=strtok_r(NULL, findyourself_path_list_separator, &saveptr) ) {
if(findyourself_debug>=2) printf("pathitem=\"%s\"\n", pathitem);
strncpy(newpath2, pathitem, sizeof(newpath2));
newpath2[sizeof(newpath2)-1]=0;
strncat(newpath2, findyourself_path_separator_as_string, sizeof(newpath2));
newpath2[sizeof(newpath2)-1]=0;
strncat(newpath2, findyourself_save_argv0, sizeof(newpath2));
newpath2[sizeof(newpath2)-1]=0;
realpath(newpath2, newpath);
if(findyourself_debug) printf(" newpath=\"%s\"\n", newpath);
if(!access(newpath, F_OK)) {
strncpy(result, newpath, size_of_result);
result[size_of_result-1]=0;
return(0);
}
} // end for
perror("access failed 3");
} // end else
// if we get here, we have tried all three methods on argv[0] and still haven't succeeded. Include fallback methods here.
return(1);
}
main(int argc, char **argv)
{
findyourself_init(argv[0]);
char newpath[PATH_MAX];
printf(" argv[0]=\"%s\"\n", argv[0]);
realpath(argv[0], newpath);
if(strcmp(argv[0],newpath)) { printf(" realpath=\"%s\"\n", newpath); }
find_yourself(newpath, sizeof(newpath));
if(1 || strcmp(argv[0],newpath)) { printf(" findyourself=\"%s\"\n", newpath); }
sleep(1); /* in case run from desktop */
}
И вот результат, который демонстрирует, что в каждом из предыдущих тестов он действительно находил себя.
tcc -o ~/bin/echoargc ~/src/echoargc.c
cd ~
/home/whitis/bin/echoargc
argv[0]="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
echoargc
argv[0]="echoargc"
realpath="/home/whitis/echoargc"
findyourself="/home/whitis/bin/echoargc"
bin/echoargc
argv[0]="bin/echoargc"
realpath="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
bin//echoargc
argv[0]="bin//echoargc"
realpath="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
bin/./echoargc
argv[0]="bin/./echoargc"
realpath="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
src/../bin/echoargc
argv[0]="src/../bin/echoargc"
realpath="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
cd ~/bin
*echo*
argv[0]="echoargc"
realpath="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
e?hoargc
argv[0]="echoargc"
realpath="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
./echoargc
argv[0]="./echoargc"
realpath="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
cd ~/src
../bin/echoargc
argv[0]="../bin/echoargc"
realpath="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
cd ~/junk
~/bin/echoargc
argv[0]="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
~whitis/bin/echoargc
argv[0]="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
alias echoit=~/bin/echoargc
echoit
argv[0]="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
echoarg=~/bin/echoargc
$echoarg
argv[0]="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
rm junk1 junk2 junk3
ln -s ~/bin/echoargc junk1
./junk1
argv[0]="./junk1"
realpath="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
ln -s /home/whitis/bin/echoargc junk2
./junk2
argv[0]="./junk2"
realpath="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
ln -s junk1 junk3
./junk3
argv[0]="./junk3"
realpath="/home/whitis/bin/echoargc"
findyourself="/home/whitis/bin/echoargc"
Два описанных выше запуска графического интерфейса также правильно находят программу.
Есть одна потенциальная ловушка. Функция access()
сбрасывает разрешения, если программа была установлена до тестирования. Если есть ситуация, когда программа может быть найдена как пользователь с повышенными привилегиями, но не как обычный пользователь, тогда может возникнуть ситуация, когда эти тесты не пройдут, хотя маловероятно, что программа действительно может быть выполнена в этих обстоятельствах. Вместо этого можно использовать euidaccess (). Однако возможно, что он может найти недоступную программу на пути раньше, чем это мог бы сделать фактический пользователь.
person
whitis
schedule
14.12.2015
ps -o comm
. Что привело меня сюда: /proc/pid/path/a.out - person basin   schedule 28.03.2013std::current_path()
. Я не вижу переносимого метода в стиле C, поскольку разные ОС используют разные строковые форматы, например wchar_t, UTF-8 и т. Д. - person ALX23z   schedule 16.09.2020std::filesystem::current_path()
не указывает путь к текущему исполняемому файлу. Это даст вам текущий рабочий каталог. - person Ted Lyngmo   schedule 16.09.2020