Використання не /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 тощо. Якщо програма була запущена безпосередньо з середовища GUI, вона повинна була встановити 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, відокремте, але не відкидайте префіксну частину хоста / користувача / приводу, якщо вона є; частина, яка часто передує двокрапці або слідує за початковою "//".
Якщо 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 /". Зауважте, це може потенційно змінити значення, якщо ви перетинаєте точку монтажу в мережі, тому канонізація - це не завжди добре. На мережевому сервері ".." в symlink може використовуватися для проходження шляху до іншого файлу в контексті сервера замість клієнта. У цьому випадку ви, мабуть, хочете, щоб контекст клієнта був таким, що канонізація нормальна. Також конвертуйте шаблони типу "/./" в "/" і "//" в "/". У оболонці readlink --canonicalize
буде вирішено кілька символьних посилань та канонізувати ім’я. Чейз може робити подібне, але не встановлюється. realpath()
або canonicalize_file_name()
, якщо є, може допомогти.
Якщо realpath()
під час компіляції не існує, ви можете запозичити копію з дозволеного ліцензованого розповсюдження бібліотеки та скомпілювати її в собі, а не винаходити колесо. Виправте потенційне переповнення буфера (передайте розмір вихідного буфера, подумайте strncpy () проти 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, файлова система запобіжників, жорсткі посилання тощо). для команд оболонки встановити PATH, але не вдалося їх експортувати.
Вам не обов’язково кодувати для систем, які не є Unix, але було б непогано знати про деякі особливості, щоб ви могли написати код таким чином, щоб хтось не так важко переніс пізніше . Майте на увазі, що деякі системи (DEC VMS, DOS, URL-адреси тощо) можуть мати назви дисків або інші префікси, які закінчуються двокрапкою, наприклад "C: \", "sys $ drive: [foo] bar" та "file : /// foo / bar / baz ". Старі системи VMS DEC використовують "[" і "]", щоб укласти частину шляху в каталог, хоча це може змінитися, якщо ваша програма складена в середовищі POSIX. У деяких системах, таких як VMS, може бути версія файлу (розділена крапкою з комою в кінці). Деякі системи використовують дві послідовні косої риски, як у "// диск / шлях / до / файл" або "користувач @ хост: / шлях / до / файл" (команда scp) або "файл: (розмежовано пробілами) та "PATH" розмежовано двокрапками, але програма повинна отримувати PATH, тому вам не потрібно турбуватися про шлях. DOS та деякі інші системи можуть мати відносні шляхи, які починаються з префікса накопичувача. C: foo.exe посилається на foo.exe в поточному каталозі на диску C, тому вам потрібно шукати поточний каталог на C: і використовувати його для pwd. (розмежовано пробілами) та "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 було б невизначено.
- Його потрібно переписати як функцію бібліотеки, а не просто для друку результатів.
- Не вдається канонізувати імена (використовуйте код realpath, який я пов’язував вище)
- Не вдалося вирішити символічні посилання (використовуйте код realpath)
Отже, якщо ви комбінуєте і код 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
Ці приклади ілюструють, що методи, описані в цій публікації, повинні працювати в широкому діапазоні обставин і чому деякі кроки необхідні.
EDIT: Тепер програма, яка друкує 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"
Описані вище два запуски GUI також правильно знаходять програму.
Є один потенційний підводний камінь. access()
Функція крапель дозволу , якщо програма Setuid перед тестуванням. Якщо є ситуація, коли програму можна знайти як підвищеного користувача, але не як звичайного користувача, то може виникнути ситуація, коли ці тести не зможуть, хоча навряд чи програма може бути реально виконана за цих обставин. Можна замість цього використовувати euidaccess (). Однак можливо, що він може знайти недоступну програму раніше на шляху, ніж міг би фактичний користувач.