Пошук шляху поточного виконуваного файлу без / proc / self / exe


190

Мені здається, що у Linux це легко з / proc / self / exe. Але я хотів би знати, чи є зручний спосіб знайти каталог поточного додатка в C / C ++ з міжплатформенними інтерфейсами. Я бачив, як деякі проекти замислюються з argv [0], але це здається не зовсім надійним.

Якби вам коли-небудь доводилося підтримувати, скажімо, Mac OS X, у якого немає / proc /, що б ви зробили? Використовувати #ifdefs для виділення конкретного коду платформи (наприклад, NSBundle)? Або спробуйте вивести шлях виконуваного файлу з argv [0], $ PATH та ще чого, ризикуючи знайти помилки у кращих випадках?



Я гуглив: дістань мою ps -o comm. Що мене тут привело, це: "/proc/pid/path/a.out"
басейн

Відповідь IMHO гордість заслуговує на те, щоб бути на вершині, оскільки вона правильно відповідає вимогам "міжплатформних інтерфейсів" та її дуже легко інтегрувати.
Стефан Гурішон

Відповіді:


348

Деякі інтерфейси для ОС:

Використовувати портативний (але менш надійний) метод argv[0]. Хоча вона може бути встановлена ​​на що завгодно, викликаючи програму, за умовою вона встановлюється як ім'я контуру виконуваного файлу, або ім'я, яке було знайдено за допомогою $PATH.

Деякі оболонки, включаючи bash та ksh, встановлюють змінну середовища " _" на повний шлях виконуваного файлу перед його виконанням. У такому випадку ви можете скористатися getenv("_")нею. Однак це ненадійно, оскільки не всі оболонки роблять це, і це може бути встановлено на що-небудь або залишитись від батьківського процесу, який не змінив його перед виконанням вашої програми.


3
А також зауважте, що _NSGetExecutablePath () не слідує за посиланнями.
naruse

1
NetBSD: readlink / proc / curproc / exe DragonFly BSD: readlink / proc / curproc / file
naruse

6
Solaris: char exepath[MAXPATHLEN]; sprintf(exepath, "/proc/%d/path/a.out", getpid()); readlink(exepath, exepath, sizeof(exepath));; це відмінне від того, getexecname()- що робить еквівалент pargs -x <PID> | grep AT_SUN_EXECNAME...
FrankH.

4
"QDesktopServices :: storageLocation (QDesktopServices :: DataLocation)" Це не шлях виконуваного файлу, це ім'я довідника для кожного користувача, де слід зберігати дані.

2
OpenBSD - єдиний, де ви все ще не можете в 2017 році. Вам потрібно скористатися способом PATH та argv [0]
Lothar

27

Використання не /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 (). Однак можливо, що він може знайти недоступну програму раніше на шляху, ніж міг би фактичний користувач.


1
Ви доклали багато зусиль для цього - молодець. На жаль, ні, strncpy()ні особливо (особливо) strncat()безпечно не використовуються в коді. strncpy()не гарантує нульове припинення; якщо рядок джерела довший, ніж цільовий простір, рядок не припиняється. strncat()дуже важкий у використанні; strncat(target, source, sizeof(target))неправильно (навіть якщо targetце порожній рядок для початку), якщо sourceвін довший за цільовий. Довжина - це кількість символів, які можна безпечно приєднати до цілі, виключаючи кінцеву нулю, тому sizeof(target)-1максимальна.
Джонатан Леффлер

4
Код strncpy правильний, на відміну від методу, який ви маєте на увазі, я повинен використовувати. Я пропоную вам уважніше прочитати код. Він ні переповнює буфери, ні залишає їх незакінченими. Кожне використання strncpy () / stncat () обмежується копіюванням sizeof (буфера), який є дійсним, і тоді останній символ буфера заповнюється нулем, що перезаписує останній символ буфера. strncat (), однак, використовує параметр розміру неправильно як кількість і може переповнюватися через те, що він передує атакам переповнення буфера.
whitis

"sudo apt-get install libbsd0 libbsd-dev", тоді s / strncat / strlcat /
whitis

1
Не використовуйте PATH_MAX. Це перестало працювати 30 років тому, завжди використовуйте malloc.
Лотар

Також якщо ви використовуєте дзвінок init. Повністю вирішіть шлях до exe на init, а не лише на частину, а потім зробіть це пізніше за викликом. Тут неможлива лінива оцінка, якщо ви використовуєте realpath у роздільній здатності. Разом з іншими ерористами просто найгірший код, який я бачив у stackoverflow у довгій відповіді.
Лотар

13

Ознайомтеся з бібліотекою гемарі Грегорі Пакоша (у якій є лише один файл C); це дозволяє отримати повний шлях до поточного виконуваного файлу на різних платформах. Наразі він доступний як репо на Github тут .


8

Альтернатива Linux використовувати /proc/self/exeабо argv[0]використовувати інформацію, передану інтерпретатором ELF, і надається glibc як такою:

#include <stdio.h>
#include <sys/auxv.h>

int main(int argc, char **argv)
{
    printf("%s\n", (char *)getauxval(AT_EXECFN));
    return(0);
}

Зауважте, що getauxvalце розширення glibc, і щоб бути надійним, вам слід перевірити, щоб він не повертався NULL(вказуючи, що інтерпретатор ELF не вказав AT_EXECFNпараметр), але я не думаю, що це взагалі ніколи не є проблемою для Linux.


Мені це подобається, оскільки це просто, і glibc все одно включений до Gtk + (який я використовую).
Колін Кінан

4

Якби вам коли-небудь доводилося підтримувати, скажімо, Mac OS X, у якого немає / proc /, що б ви зробили? Використовувати #ifdefs для виділення конкретного коду платформи (наприклад, NSBundle)?

Так, виділення конкретного коду платформи #ifdefs- це звичайний спосіб цього зробити.

Іншим підходом може бути створення #ifdefзаголовка без чистоти, який містить декларації функцій та розміщення реалізацій у конкретних вихідних файлах платформи. Наприклад, перевірте, як бібліотека Poco C ++ робить щось подібне для класу Environment .


4

Щоб зробити цю роботу надійно на всіх платформах, потрібно використовувати #ifdef заяви.

Нижче наведений код знаходить шлях виконуваного файлу в Windows, Linux, MacOS, Solaris або FreeBSD (хоча FreeBSD не перевірено). Для спрощення коду використовується boost > = 1.55.0, але його досить просто видалити, якщо ви хочете. Просто використовуйте такі параметри, як _MSC_VER та __linux, як вимагають ОС та компілятор.

#include <string>
#include <boost/predef/os.h>

#if (BOOST_OS_WINDOWS)
#  include <stdlib.h>
#elif (BOOST_OS_SOLARIS)
#  include <stdlib.h>
#  include <limits.h>
#elif (BOOST_OS_LINUX)
#  include <unistd.h>
#  include <limits.h>
#elif (BOOST_OS_MACOS)
#  include <mach-o/dyld.h>
#elif (BOOST_OS_BSD_FREE)
#  include <sys/types.h>
#  include <sys/sysctl.h>
#endif

/*
 * Returns the full path to the currently running executable,
 * or an empty string in case of failure.
 */
std::string getExecutablePath() {
#if (BOOST_OS_WINDOWS)
    char *exePath;
    if (_get_pgmptr(&exePath) != 0)
        exePath = "";
#elif (BOOST_OS_SOLARIS)
    char exePath[PATH_MAX];
    if (realpath(getexecname(), exePath) == NULL)
        exePath[0] = '\0';
#elif (BOOST_OS_LINUX)
    char exePath[PATH_MAX];
    ssize_t len = ::readlink("/proc/self/exe", exePath, sizeof(exePath));
    if (len == -1 || len == sizeof(exePath))
        len = 0;
    exePath[len] = '\0';
#elif (BOOST_OS_MACOS)
    char exePath[PATH_MAX];
    uint32_t len = sizeof(exePath);
    if (_NSGetExecutablePath(exePath, &len) != 0) {
        exePath[0] = '\0'; // buffer too small (!)
    } else {
        // resolve symlinks, ., .. if possible
        char *canonicalPath = realpath(exePath, NULL);
        if (canonicalPath != NULL) {
            strncpy(exePath,canonicalPath,len);
            free(canonicalPath);
        }
    }
#elif (BOOST_OS_BSD_FREE)
    char exePath[2048];
    int mib[4];  mib[0] = CTL_KERN;  mib[1] = KERN_PROC;  mib[2] = KERN_PROC_PATHNAME;  mib[3] = -1;
    size_t len = sizeof(exePath);
    if (sysctl(mib, 4, exePath, &len, NULL, 0) != 0)
        exePath[0] = '\0';
#endif
    return std::string(exePath);
}

Вищевказана версія повертає повні шляхи, включаючи ім'я виконавця. Якщо замість цього ви хочете шлях без назви виконуваного файлу, #include boost/filesystem.hpp>і змініть оператор return на:

return strlen(exePath)>0 ? boost::filesystem::path(exePath).remove_filename().make_preferred().string() : std::string();

@Frank, не впевнений, чому ти це кажеш. Працює для мене. Я побачив ще одну відповідь, яка стверджує, що вам потрібен root для доступу / proc / self / exe, але я не знайшов цього в жодних системах Linux, які я пробував (CentOS або Mint).
jtbr

2

Залежно від версії QNX Neutrino , існують різні способи пошуку повного шляху та імені виконуваного файлу, який використовувався для запуску запущеного процесу. Я позначаю ідентифікатор процесу як <PID>. Спробуйте наступне:

  1. Якщо файл /proc/self/exefileіснує, то його вміст - це запитувана інформація.
  2. Якщо файл /proc/<PID>/exefileіснує, то його вміст - це запитувана інформація.
  3. Якщо файл /proc/self/asіснує, то:
    1. open() файл.
    2. Виділяють буфер, по крайней мере, sizeof(procfs_debuginfo) + _POSIX_PATH_MAX.
    3. Надайте цей буфер як вхід до devctl(fd, DCMD_PROC_MAPDEBUG_BASE,....
    4. Передайте буфер на a procfs_debuginfo*.
    5. Запитана інформація знаходиться в pathполі procfs_debuginfoструктури. Попередження : Іноді QNX чомусь опускає першу косу риску /шляху до файлу. Попередьте це, /коли потрібно.
    6. Очистити (закрити файл, звільнити буфер тощо).
  4. Спробуйте процедуру 3.з файлом /proc/<PID>/as.
  5. Спробуйте, dladdr(dlsym(RTLD_DEFAULT, "main"), &dlinfo)де dlinfoє Dl_infoструктура, яка dli_fnameможе містити запитувану інформацію.

Я сподіваюся, що це допомагає.


1

AFAIK, такого немає. І тут також є неоднозначність: що б ви хотіли отримати як відповідь, якщо той самий виконуваний файл має декілька жорстких посилань, які "вказують" на нього? (Жорсткі посилання насправді не "вказують", вони є тим самим файлом, просто в іншому місці ієрархії FS.) Після того, як execve () успішно виконує новий бінарний файл, вся інформація про його аргументи втрачається.


1
"Після того, як execve () успішно виконує новий двійковий файл, вся інформація про його аргументи втрачається." Насправді аргументи argp та envp не втрачаються, вони передаються як argv [], а середовище, а в деяких UN * Xes аргумент назви шляху або щось, побудоване з нього, або передається разом з argp та envp (OS X / iOS, Solaris) або доступні через один з механізмів, вказаних у відповіді mark4o. Але так, це просто дає вам одне з важких посилань, якщо їх більше.

1

Ви можете використовувати argv [0] та аналізувати змінну середовища PATH. Подивіться: Зразок програми, яка може знайти себе


7
Це насправді не є надійним (хоча це, як правило, працює з програмами, запущеними звичайними оболонками), тому що execvі родина проходить шлях до виконуваного файлу окремоargv
dmckee --- колишнє кошеня модератора

9
Це неправильна відповідь. Він може сказати вам , де ви могли б знайти в програму з тим же ім'ям. Але це нічого не розповідає про те, де насправді живе виконуваний виконуваний файл.
Ларрі Гріц

0

Більш портативний спосіб отримати назву контуру виконуваного зображення:

ps може дати вам шлях виконуваного файлу, якщо у вас є ідентифікатор процесу. Також ps - це утиліта POSIX, тому вона повинна бути портативною

тому якщо ідентифікатор процесу становить 249297, тоді ця команда дає вам лише ім'я шляху.

    ps -p 24297 -o comm --no-heading

Пояснення аргументів

-p - вибирає заданий процес

-o comm - відображає ім'я команди (-o cmd вибирає весь командний рядок)

--no-заголовок - не відображайте рядок заголовка, а лише вихід.

Програма змінного струму може запустити це через popen.


Це дає повний ланцюжок запуску з парами.
ETech

--но-заголовок не є портативним
Добра людина

1
не працює, якщо перший аргумент до execv не є абсолютним шляхом.
hroptatyr

-4

Якщо ви використовуєте C, ви можете використовувати функцію getwd:

int main()
{       
 char buf[4096];
 getwd(buf);
 printf(buf);
}

Це ви надрукуєте на стандартному виході, поточному каталозі виконуваного файлу.


3
принаймні в Windows, поточний робочий каталог не має особливого відношення до запущеного виконуваного файлу. Наприклад, CreateProcess може запустити .exe і встановити свою робочу директорію повністю незалежно.
Spike0xff

Ситуація однакова на всіх інших ОС: поточний каталог іноді такий же, як і виконавчий каталог, за випадковістю, але може бути зовсім іншим.
Лассі

-10

Абсолютний шлях значення програми знаходиться в PWD оточення вашої основної функції, також є функція в C, що називається getenv, так що це.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.