Использование /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, HPUX, 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 систем, выделите, но не отбрасывайте часть префикса hostname / 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
разрешит несколько символических ссылок и канонизирует имя. Чейз может сделать подобное, но не установлен. 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, windoze) 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
Обратите внимание, что пользователь bill разместил ссылку выше на программу в 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 (). Однако возможно, что он обнаружит недоступную программу раньше, чем фактический пользователь.