Znajdowanie ścieżki bieżącego pliku wykonywalnego bez / proc / self / exe


190

Wydaje mi się, że Linux ma to łatwe dzięki / proc / self / exe. Chciałbym jednak wiedzieć, czy istnieje wygodny sposób na znalezienie katalogu bieżącej aplikacji w C / C ++ z interfejsami między platformami. Widziałem kilka projektów, które kręciły się z argv [0], ale nie wydaje się to całkowicie wiarygodne.

Gdybyś kiedykolwiek musiał obsługiwać, powiedzmy, Mac OS X, który nie ma / proc /, co byś zrobił? Czy użyć #ifdefs, aby wyodrębnić kod specyficzny dla platformy (na przykład NSBundle)? Lub spróbuj wydedukować ścieżkę do pliku wykonywalnego z argv [0], $ PATH i tak dalej, ryzykując znalezienie błędów w skrajnych przypadkach?



Poszukałem: weź mnie ps -o comm. To, co mnie tu sprowadziło, to: „/proc/pid/path/a.out”
basen

Odpowiedź dumy IMHO zasługuje na to, aby być na szczycie, ponieważ poprawnie spełnia wymagania dotyczące interfejsów między platformami i jest bardzo łatwa do zintegrowania.
Stéphane Gourichon

Odpowiedzi:


348

Niektóre interfejsy specyficzne dla systemu operacyjnego:

Należy użyć przenośnej (ale mniej niezawodnej) metody argv[0]. Chociaż program wywołujący może ustawić dowolną wartość, zgodnie z konwencją jest to albo ścieżka do pliku wykonywalnego, albo nazwa znaleziona przy użyciu $PATH.

Niektóre powłoki, w tym bash i ksh, ustawiają zmienną środowiskową „ _ na pełną ścieżkę do pliku wykonywalnego przed jego wykonaniem. W takim przypadku możesz go użyć getenv("_"). Jest to jednak niewiarygodne, ponieważ nie wszystkie powłoki to robią i można ustawić na cokolwiek lub pozostać z procesu nadrzędnego, który nie zmienił tego przed uruchomieniem programu.


3
Zauważ też, że _NSGetExecutablePath () nie podąża za dowiązaniami symbolicznymi.
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));; to różni się od getexecname()- co odpowiada pargs -x <PID> | grep AT_SUN_EXECNAME...
FrankH.

4
„QDesktopServices :: storageLocation (QDesktopServices :: DataLocation)” To nie jest ścieżka do pliku wykonywalnego, to nazwa ścieżki do katalogu użytkownika, w którym dane powinny być przechowywane.

2
OpenBSD jest jedynym, w którym nadal nie możesz w 2017 roku. Musisz użyć PATH i argv [0] sposób
Lothar

27

Zastosowanie /proc/self/exejest nieprzenośne i zawodne. W moim systemie Ubuntu 12.04 musisz być rootem, aby czytać / podążać za dowiązaniem symbolicznym. To sprawi, że Boost będzie przykładem i prawdopodobnie whereami()zamieszczone rozwiązania zawiodą.

Ten post jest bardzo długi, ale omawia aktualne problemy i przedstawia kod, który faktycznie działa wraz z sprawdzaniem poprawności względem zestawu testów.

Najlepszym sposobem na znalezienie programu jest odtworzenie tych samych kroków, które wykonuje system. Odbywa się to za pomocą argv[0]rozwiązania rozwiązanego względem systemu plików root, pwd, środowiska ścieżki i rozważenia dowiązań symbolicznych oraz kanonizacji nazw ścieżek. To pochodzi z pamięci, ale robiłem to z powodzeniem w przeszłości i testowałem w różnych sytuacjach. Nie gwarantuje się, że zadziała, ale jeśli nie, prawdopodobnie masz znacznie większe problemy i jest ogólnie bardziej niezawodny niż jakakolwiek inna omawiana metoda. Istnieją sytuacje w systemie kompatybilnym z Uniksem, w których właściwe obchodzenie się zargv[0] nie spowoduje przejścia do twojego programu, ale wtedy wykonujesz w certyfikowanym środowisku. Jest także dość przenośny dla wszystkich systemów pochodnych Uniksa od około 1970 roku, a nawet niektórych systemów nie pochodzących z Uniksa, ponieważ zasadniczo opiera się na standardowej funkcjonalności libc () i standardowej linii poleceń. Powinien działać na systemie Linux (wszystkie wersje), Android, Chrome OS, Minix, oryginalny Bell Labs Unix, FreeBSD, NetBSD, OpenBSD, BSD xx, SunOS, Solaris, SYSV, HPUX, Concentrix, SCO, Darwin, AIX, OS X, Następny krok itp. I przy niewielkiej modyfikacji prawdopodobnie VMS, VM / CMS, DOS / Windows, ReactOS, OS / 2 itd. Jeśli program został uruchomiony bezpośrednio ze środowiska GUI, powinien był ustawić argv[0]ścieżkę bezwzględną.

Zrozum, że prawie każda powłoka w każdym systemie operacyjnym kompatybilnym z Uniksem, który kiedykolwiek został wydany, zasadniczo znajduje programy w ten sam sposób i konfiguruje środowisko operacyjne prawie w ten sam sposób (z pewnymi opcjonalnymi dodatkami). Oczekuje się, że każdy inny program, który uruchamia program, utworzy dla tego programu takie samo środowisko (argv, ciągi środowiska itp.), Jakby było uruchamiane z powłoki, z pewnymi opcjonalnymi dodatkami. Program lub użytkownik może skonfigurować środowisko, które odbiega od tej konwencji, dla innych programów podrzędnych, które uruchamia, ale jeśli tak, jest to błąd i program nie ma uzasadnionych oczekiwań, że program podrzędny lub jego podwładni będą działać poprawnie.

Możliwe wartości argv[0]obejmują:

  • /path/to/executable - ścieżka bezwzględna
  • ../bin/executable - w stosunku do pwd
  • bin/executable - w stosunku do pwd
  • ./foo - w stosunku do pwd
  • executable - basename, znajdź na ścieżce
  • bin//executable - w stosunku do pwd, niekanoniczne
  • src/../bin/executable - w stosunku do pwd, niekanonicznego, cofania
  • bin/./echoargc - w stosunku do pwd, niekanoniczne

Wartości, których nie powinieneś widzieć:

  • ~/bin/executable - przepisane przed uruchomieniem programu.
  • ~user/bin/executable - przepisane przed uruchomieniem programu
  • alias - przepisane przed uruchomieniem programu
  • $shellvariable - przepisane przed uruchomieniem programu
  • *foo* - symbol wieloznaczny, przepisany przed uruchomieniem programu, niezbyt przydatny
  • ?foo? - symbol wieloznaczny, przepisany przed uruchomieniem programu, niezbyt przydatny

Ponadto mogą one zawierać niekanoniczne nazwy ścieżek i wiele warstw dowiązań symbolicznych. W niektórych przypadkach może istnieć wiele twardych linków do tego samego programu. Na przykład /bin/ls, /bin/ps, /bin/chmod, /bin/rm, itd. Mogą być trudne do powiązania /bin/busybox.

Aby się znaleźć, wykonaj następujące czynności:

  • Zapisz pwd, PATH i argv [0] przy wejściu do programu (lub inicjalizacji biblioteki), ponieważ mogą się później zmienić.

  • Opcjonalnie: szczególnie w przypadku systemów innych niż Unix, należy oddzielić, ale nie odrzucać części prefiksu nazwa hosta / użytkownika / dysku, jeśli jest obecny; część, która często poprzedza dwukropek lub występuje po początkowym „//”.

  • Jeśli argv[0]jest to ścieżka bezwzględna, użyj jej jako punktu początkowego. Ścieżka bezwzględna prawdopodobnie zaczyna się od „/”, ale w niektórych systemach innych niż Unix może zaczynać się od „\” lub litery dysku lub prefiksu nazwy, po którym następuje dwukropek.

  • W przeciwnym razie, jeśli argv[0]jest ścieżką względną (zawiera „/” lub „\”, ale nie zaczyna się od niej, na przykład „../../bin/foo”, a następnie połącz pwd + „/” + argv [0] (użyj obecny katalog roboczy od momentu uruchomienia programu, nie aktualny).

  • Jeśli argv [0] jest zwykłym basenemame (bez ukośników), połącz go z każdą pozycją w zmiennej środowiskowej PATH i wypróbuj je i użyj pierwszej, która się powiedzie.

  • Opcjonalnie: Else spróbować samemu od platformy /proc/self/exe, /proc/curproc/file(BSD), a (char *)getauxval(AT_EXECFN), a dlgetname(...)jeśli obecny. Możesz nawet wypróbować te wcześniejsze argv[0]metody, jeśli są one dostępne i nie występują problemy z uprawnieniami. W dość mało prawdopodobnym przypadku (gdy weźmie się pod uwagę wszystkie wersje wszystkich systemów), że są one obecne i nie zawodzą, mogą być bardziej wiarygodne.

  • Opcjonalnie: sprawdź nazwę ścieżki przekazaną za pomocą parametru wiersza polecenia.

  • Opcjonalnie: sprawdź, czy ścieżka nie jest w środowisku jawnie przekazana przez skrypt opakowania, jeśli istnieje.

  • Opcjonalnie: W ostateczności wypróbuj zmienną środowiskową „_”. Może to wskazywać zupełnie inny program, na przykład powłokę użytkownika.

  • Rozpoznawanie dowiązań symbolicznych, może istnieć wiele warstw. Istnieje możliwość nieskończonych pętli, ale jeśli one istnieją, prawdopodobnie Twój program nie zostanie wywołany.

  • Kanonizuj nazwę pliku, rozwiązując podciągi, takie jak „/foo/../bar/” do „/ bar /”. Zauważ, że może to potencjalnie zmienić znaczenie, jeśli przekroczysz punkt montowania sieci, więc kanonizacja nie zawsze jest dobrą rzeczą. Na serwerze sieciowym można użyć „..” w dowiązaniu symbolicznym do przejścia do innego pliku w kontekście serwera zamiast na kliencie. W takim przypadku prawdopodobnie potrzebujesz kontekstu klienta, więc kanonizacja jest w porządku. Konwertuj również wzorce, takie jak „/./” na „/” i „//” na „/”. W powłoce readlink --canonicalizerozwiąże wiele dowiązań symbolicznych i kanonizuje nazwę. Chase może działać podobnie, ale nie jest zainstalowany. realpath()lub canonicalize_file_name(), jeśli jest obecny, może pomóc.

Jeśli realpath() nie istnieje w czasie kompilacji, możesz pożyczyć kopię z licencjonowanej dystrybucji bibliotek i skompilować ją w sobie, zamiast wymyślać koło. Napraw potencjalne przepełnienie bufora (przekaż rozmiar bufora wyjściowego, pomyśl strncpy () vs strcpy ()), jeśli będziesz używał bufora mniejszego niż PATH_MAX. Łatwiej może być po prostu użyć prywatnej kopii o zmienionej nazwie niż testować, czy istnieje. Permissive copy copy from Android / darwin / bsd: https://android.googlesource.com/platform/bionic/+/f077784/libc/upstream-freebsd/lib/libc/stdlib/realpath.c

Należy pamiętać, że wiele prób może zakończyć się powodzeniem lub częściowo i nie wszystkie mogą wskazywać na ten sam plik wykonywalny, dlatego należy rozważyć weryfikację pliku wykonywalnego; jednak możesz nie mieć uprawnienia do odczytu - jeśli nie możesz go odczytać, nie traktuj tego jako niepowodzenia. Lub zweryfikuj coś w pobliżu pliku wykonywalnego, na przykład katalog „../lib/”, który próbujesz znaleźć. Możesz mieć wiele wersji, wersje spakowane i lokalnie skompilowane, wersje lokalne i sieciowe, wersje lokalne i przenośne z napędem USB itp. Istnieje niewielka możliwość, że uzyskasz dwa niezgodne wyniki z różnych metod lokalizacji. „_” Może po prostu wskazywać niewłaściwy program.

Program używający execvemoże celowo ustawić argv[0]niezgodność z rzeczywistą ścieżką użytą do załadowania programu i uszkodzenia PATH, „_”, pwd itp., Chociaż generalnie nie ma zbyt wielu powodów, aby to robić; ale może to mieć wpływ na bezpieczeństwo, jeśli masz wrażliwy kod, który ignoruje fakt, że środowisko wykonawcze można zmienić na różne sposoby, w tym między innymi (chroot, system plików bezpieczników, twarde łącza itp.) Jest to możliwe dla poleceń powłoki, aby ustawić PATH, ale nie można go wyeksportować.

Niekoniecznie musisz kodować w systemach innych niż Unix, ale dobrym pomysłem byłoby zapoznanie się z niektórymi osobliwościami, abyś mógł napisać kod w taki sposób, aby nie było tak trudne dla kogoś późniejszego przeniesienia . Należy pamiętać, że niektóre systemy (DEC VMS, DOS, adresy URL itp.) Mogą mieć nazwy dysków lub inne prefiksy, które kończą się dwukropkiem, np. „C: \”, „sys $ drive: [foo] bar” i „file : /// foo / bar / baz ". Stare systemy DEC VMS używają „[” i „]” do umieszczenia części katalogu w ścieżce, chociaż mogło to ulec zmianie, jeśli Twój program jest skompilowany w środowisku POSIX. Niektóre systemy, takie jak VMS, mogą mieć wersję pliku (oddzieloną średnikiem na końcu). Niektóre systemy używają dwóch następujących po sobie ukośników, takich jak „// dysk / ścieżka / do / pliku” lub „użytkownik @ host: / ścieżka / do / pliku” (polecenie scp) lub „plik: (rozdzielany spacjami) i „PATH” rozdzielany dwukropkami, ale twój program powinien otrzymywać PATH, więc nie musisz się martwić o ścieżkę. DOS i niektóre inne systemy mogą mieć ścieżki względne rozpoczynające się od prefiksu dysku. C: foo.exe odnosi się do foo.exe w bieżącym katalogu na dysku C, więc musisz wyszukać bieżący katalog na C: i użyć go dla pwd. (rozdzielany spacjami) i „PATH” rozdzielany dwukropkami, ale twój program powinien otrzymywać PATH, więc nie musisz się martwić o ścieżkę. DOS i niektóre inne systemy mogą mieć ścieżki względne rozpoczynające się od prefiksu dysku. C: foo.exe odnosi się do foo.exe w bieżącym katalogu na dysku C, więc musisz wyszukać bieżący katalog na C: i użyć go dla pwd.

Przykład dowiązań symbolicznych i opakowań w moim systemie:

/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

Pamiętaj, że rachunek użytkownika opublikował powyższy link do programu w HP, który obsługuje trzy podstawowe przypadki argv[0]. Potrzebuje jednak pewnych zmian:

  • Konieczne będzie przepisanie wszystkich strcat()oraz strcpy()użycie strncat()i strncpy(). Mimo że zmienne są deklarowane o długości PATHMAX, wartość wejściowa o długości PATHMAX-1 plus długość połączonych łańcuchów wynosi> PATHMAX, a wartość wejściowa o długości PATHMAX nie byłaby ustalona.
  • Należy go przepisać jako funkcję biblioteki, a nie tylko wydrukować wyniki.
    • Nie można kanonizować nazw (użyj kodu realpath, do którego odsyłam powyżej)
    • Nie można rozpoznać dowiązań symbolicznych (użyj kodu realpath)

Tak więc, jeśli połączysz zarówno kod HP, jak i kod realpath, i naprawisz oba, aby były odporne na przepełnienia bufora, powinieneś mieć coś, co może poprawnie zinterpretować argv[0].

Poniżej przedstawiono rzeczywiste wartości argv[0]różnych sposobów wywoływania tego samego programu w systemie Ubuntu 12.04. I tak, program został przypadkowo nazwany echoargc zamiast echoargv. Dokonano tego przy użyciu skryptu do czystego kopiowania, ale wykonanie go ręcznie w powłoce daje te same wyniki (z wyjątkiem tego, że aliasy nie działają w skrypcie, chyba że wyraźnie je włączysz).

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

Te przykłady ilustrują, że techniki opisane w tym poście powinny działać w szerokim zakresie okoliczności i dlaczego niektóre kroki są konieczne.

EDYCJA: Teraz program, który wypisuje argv [0] został zaktualizowany, aby się znalazł.

// 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 */
}

A oto wynik, który pokazuje, że w każdym z poprzednich testów faktycznie się znalazł.

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"

Dwa wyżej opisane uruchomienia GUI również poprawnie znajdują program.

Istnieje jedna potencjalna pułapka. access()Funkcja spada uprawnienia, jeżeli program jest setuid przed badaniem. Jeśli istnieje sytuacja, w której program może zostać znaleziony jako użytkownik z podwyższonym poziomem uprawnień, ale nie jako zwykły użytkownik, wówczas może wystąpić sytuacja, w której testy te zakończą się niepowodzeniem, chociaż jest mało prawdopodobne, aby program mógł zostać uruchomiony w takich okolicznościach. Zamiast tego można użyć euidaccess (). Możliwe jest jednak, że program może znaleźć niedostępny program na ścieżce wcześniej niż rzeczywisty użytkownik.


1
Wkładasz w to dużo wysiłku - dobra robota. Niestety, ani strncpy()ani (szczególnie) nie strncat()jest bezpiecznie używany w kodzie. strncpy()nie gwarantuje zerowego wypowiedzenia; jeśli łańcuch źródłowy jest dłuższy niż przestrzeń docelowa, łańcuch nie jest zakończony zerem. strncat()jest bardzo trudny w użyciu; strncat(target, source, sizeof(target))jest niepoprawny (nawet jeśli na początku targetjest pusty ciąg), jeśli sourcejest dłuższy niż cel. Długość to liczba znaków, które można bezpiecznie dołączyć do celu, wyłączając końcowy null, a więc sizeof(target)-1maksymalna.
Jonathan Leffler,

4
Kod strncpy jest poprawny, w przeciwieństwie do metody, którą sugerujesz, że powinienem użyć. Sugeruję uważniejsze przeczytanie kodu. Nie przepełnia buforów ani nie pozostawia ich nienaruszonych. Każde użycie strncpy () / stncat () jest ograniczone przez kopiowanie sizeof (bufor), który jest prawidłowy, a następnie ostatni znak bufora jest wypełniany zerem nadpisującym ostatni znak bufora. Jednak strncat () używa niepoprawnie parametru size jako licznika i może przepełnić się z powodu tego, że poprzedza ataki przepełnienia bufora.
whitis

„sudo apt-get install libbsd0 libbsd-dev”, a następnie s / strncat / strlcat /
whitis

1
Nie używaj PATH_MAX. To przestało działać 30 lat temu, zawsze używaj malloc.
Lothar,

Również jeśli korzystasz z połączenia init. W pełni rozwiąż ścieżkę do exe przy init, a nie tylko jego część, a następnie zrób to później na wezwanie. Żadna leniwa ocena tutaj nie jest możliwa, jeśli użyjesz prawdziwej ścieżki w resolwerze. Wraz z innymi błędami po prostu najgorszy kod, jaki widziałem na przepełnieniu stosu w długiej odpowiedzi.
Lothar,

13

Sprawdź bibliotekę whereami Gregory'ego Pakosza (która ma tylko jeden plik C); pozwala uzyskać pełną ścieżkę do bieżącego pliku wykonywalnego na różnych platformach. Obecnie jest dostępny jako repo na github tutaj .


8

Alternatywą na Linuksie do używania albo /proc/self/execzy argv[0]korzysta z informacji przekazywanych przez interpreter ELF, udostępniane przez glibc jako takie:

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

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

Zauważ, że getauxvaljest to rozszerzenie glibc i aby być solidnym, powinieneś sprawdzić, aby nie zwrócił NULL(wskazując, że interpreter ELF nie podał AT_EXECFNparametru), ale nie sądzę, że w rzeczywistości jest to kiedykolwiek problem w Linuksie.


Podoba mi się, ponieważ jest prosty, a glibc jest dołączony do Gtk + (którego używam).
Colin Keenan,

4

Gdybyś kiedykolwiek musiał obsługiwać, powiedzmy, Mac OS X, który nie ma / proc /, co byś zrobił? Czy użyć #ifdefs, aby wyodrębnić kod specyficzny dla platformy (na przykład NSBundle)?

Tak, izolując kod specyficzny dla platformy za pomocą #ifdefs to konwencjonalny sposób.

Innym podejściem byłoby posiadanie czystego #ifdefnagłówka zawierającego deklaracje funkcji i umieszczanie implementacji w plikach źródłowych specyficznych dla platformy. Na przykład sprawdź, jak biblioteka Poco C ++ robi coś podobnego dla swojej klasy Environment .


4

Niezawodne działanie tej platformy na różnych platformach wymaga użycia instrukcji #ifdef.

Poniższy kod znajduje ścieżkę do pliku wykonywalnego w systemach Windows, Linux, MacOS, Solaris lub FreeBSD (chociaż FreeBSD nie jest testowany). Używa boost > = 1.55.0, aby uprościć kod, ale można go łatwo usunąć, jeśli chcesz. Wystarczy użyć definicji takich jak _MSC_VER i __linux zgodnie z wymaganiami systemu operacyjnego i kompilatora.

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

Powyższa wersja zwraca pełne ścieżki, w tym nazwę pliku wykonywalnego. Jeśli zamiast tego chcesz ścieżkę bez nazwy pliku wykonywalnego, #include boost/filesystem.hpp>zmień instrukcję return na:

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

@Frank, nie jestem pewien, dlaczego tak mówisz. Pracuje dla mnie. Widziałem inną odpowiedź twierdzącą, że potrzebujesz roota, aby uzyskać dostęp do / proc / self / exe, ale nie znalazłem tego na żadnym systemie Linux, którego próbowałem (CentOS lub Mint).
jtbr

2

W zależności od wersji QNX Neutrino istnieją różne sposoby znalezienia pełnej ścieżki i nazwy pliku wykonywalnego, który został użyty do uruchomienia uruchomionego procesu. Oznaczam identyfikator procesu jako <PID>. Spróbuj wykonać następujące czynności:

  1. Jeśli plik /proc/self/exefileistnieje, wówczas jego zawartością są wymagane informacje.
  2. Jeśli plik /proc/<PID>/exefileistnieje, wówczas jego zawartością są wymagane informacje.
  3. Jeśli plik /proc/self/asistnieje, to:
    1. open() plik.
    2. Przydziel bufor przynajmniej sizeof(procfs_debuginfo) + _POSIX_PATH_MAX.
    3. Podaj ten bufor jako dane wejściowe do devctl(fd, DCMD_PROC_MAPDEBUG_BASE,....
    4. Prześlij bufor do procfs_debuginfo* .
    5. Żądane informacje znajdują się w pathpolu procfs_debuginfostruktury. Ostrzeżenie : Z jakiegoś powodu czasami QNX pomija pierwszy ukośnik /ścieżki pliku. Przygotuj to w /razie potrzeby.
    6. Oczyść (zamknij plik, zwolnij bufor itp.).
  4. Spróbuj wykonać procedurę 3.z plikiem /proc/<PID>/as.
  5. Spróbuj dladdr(dlsym(RTLD_DEFAULT, "main"), &dlinfo)gdzie dlinfojest Dl_infostruktura, która dli_fnamemoże zawierać wymagane informacje.

Mam nadzieję, że to pomoże.


1

AFAIK, nie ma takiej możliwości. I jest też wieloznaczność: co chciałbyś uzyskać jako odpowiedź, jeśli ten sam plik wykonywalny ma wiele „linków” do niego wskazujących? (Hard-links nie „wskazują”, tym samym plikiem, tylko w innym miejscu w hierarchii FS.) Gdy execve () pomyślnie uruchomi nowy plik binarny, wszystkie informacje o jego argumentach zostaną utracone.


1
„Gdy polecenie execve () pomyślnie uruchomi nowy plik binarny, wszystkie informacje o jego argumentach zostaną utracone.” W rzeczywistości argumenty argp i envp nie są tracone, są przekazywane jako argv [] i środowisko, aw niektórych UN * Xes argument pathname lub coś z niego zbudowanego jest przekazywany wraz z argp i envp (OS X / iOS, Solaris) lub udostępnione przez jeden z mechanizmów wymienionych w odpowiedzi mark4o. Ale tak, to tylko jeden z twardych linków, jeśli jest ich więcej niż jeden.

1

Możesz użyć argv [0] i przeanalizować zmienną środowiskową PATH. Spójrz na: Przykład programu, który może się znaleźć


7
Nie jest to w rzeczywistości niezawodne (choć ogólnie będzie działać z programami uruchamianymi przez zwykłe powłoki), ponieważ execvi kin argv
podążają

9
To jest niepoprawna odpowiedź. To może ci powiedzieć, gdzie mógłby znaleźć się program o tej samej nazwie. Ale nie mówi nic o tym, gdzie aktualnie znajduje się plik wykonywalny.
Larry Gritz,

0

Bardziej przenośny sposób na uzyskanie nazwy ścieżki obrazu wykonywalnego:

ps może podać ścieżkę do pliku wykonywalnego, pod warunkiem, że masz identyfikator procesu. Również ps jest narzędziem POSIX, więc powinno być przenośne

więc jeśli identyfikator procesu to 249297, to polecenie podaje tylko nazwę ścieżki.

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

Wyjaśnienie argumentów

-p - wybiera podany proces

-o comm - wyświetla nazwę polecenia (-o cmd wybiera całą linię poleceń)

--no-header - nie wyświetlaj linii nagłówka, tylko wynik.

Program AC może uruchomić to za pomocą popen.


Daje pełny ciąg startowy z parametrami.
ETech

- brak nagłówka nie jest przenośny
Dobra Osoba

1
nie działa, jeśli pierwszy argument do execv nie jest ścieżką bezwzględną.
hroptatyr

-4

Jeśli używasz C, możesz użyć funkcji getwd:

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

Spowoduje to wydrukowanie na standardowym wyjściu, bieżącym katalogu pliku wykonywalnego.


3
przynajmniej w systemie Windows bieżący katalog roboczy nie ma szczególnego związku z działającym plikiem wykonywalnym. Na przykład CreateProcess może uruchomić .exe i ustawić swój katalog roboczy całkowicie niezależnie.
Spike0xff

Sytuacja jest taka sama w każdym innym systemie operacyjnym: aktualny katalog jest czasem taki sam jak katalog wykonywalny, ale może być zupełnie inny.
Lassi

-10

Ścieżka wartości bezwzględnej programu znajduje się w PWD envp twojej głównej funkcji, jest też funkcja w C o nazwie getenv, więc jest to.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.