Trovare il percorso dell'eseguibile corrente senza / proc / self / exe


190

Mi sembra che Linux sia facile con / proc / self / exe. Ma vorrei sapere se esiste un modo conveniente per trovare la directory dell'applicazione corrente in C / C ++ con interfacce multipiattaforma. Ho visto alcuni progetti confusi con argv [0], ma non sembra del tutto affidabile.

Se mai dovessi supportare, diciamo, Mac OS X, che non ha / proc /, cosa avresti fatto? Utilizzare #ifdefs per isolare il codice specifico della piattaforma (NSBundle, ad esempio)? Oppure prova a dedurre il percorso dell'eseguibile da argv [0], $ PATH e quant'altro, rischiando di trovare bug nei casi limite?



Ho cercato su Google: prendi il mio ps -o comm. Ciò che mi ha portato qui è: "/proc/pid/path/a.out"
bacino

La risposta dell'orgoglio IMHO merita di essere al top, perché risponde correttamente al requisito "interfacce multipiattaforma" ed è molto facile da integrare.
Stéphane Gourichon,

Risposte:


348

Alcune interfacce specifiche del sistema operativo:

Il metodo portatile (ma meno affidabile) è da usare argv[0]. Sebbene possa essere impostato su qualsiasi cosa dal programma chiamante, per convenzione è impostato su un nome percorso dell'eseguibile o su un nome che è stato trovato usando $PATH.

Alcune shell, tra cui bash e ksh, impostano la variabile di ambiente " _" sul percorso completo dell'eseguibile prima che venga eseguito. In tal caso è possibile utilizzare getenv("_")per ottenerlo. Tuttavia, questo non è affidabile perché non tutte le shell lo fanno, e potrebbe essere impostato su qualsiasi cosa o essere lasciato da un processo genitore che non lo ha modificato prima di eseguire il programma.


3
E nota anche che _NSGetExecutablePath () non segue i collegamenti simbolici.
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));; è diverso da getexecname()- il che equivale a pargs -x <PID> | grep AT_SUN_EXECNAME...
FrankH.

4
"QDesktopServices :: storageLocation (QDesktopServices :: DataLocation)" Questo non è il percorso dell'eseguibile, questo è il percorso della directory per utente in cui i dati devono essere archiviati.

2
OpenBSD è l'unico in cui non puoi ancora farlo nel 2017. Devi usare il percorso PATH e argv [0]
Lothar

27

L'uso di /proc/self/exenon è portatile e inaffidabile. Sul mio sistema Ubuntu 12.04, devi essere root per leggere / seguire il link simbolico. Questo renderà l'esempio Boost e probabilmente ilwhereami() soluzioni pubblicate falliranno.

Questo post è molto lungo ma discute i problemi reali e presenta il codice che funziona effettivamente insieme alla convalida rispetto a una suite di test.

Il modo migliore per trovare il programma è ripercorrere gli stessi passaggi utilizzati dal sistema. Questo viene fatto usando la argv[0]risoluzione del file system root, pwd, il path environment e considerando i symlink e la canonicalizzazione del pathname. Questo è dalla memoria, ma l'ho fatto con successo in passato e l'ho testato in una varietà di situazioni diverse. Non è garantito che funzioni, ma in caso contrario probabilmente hai problemi molto più grandi ed è complessivamente più affidabile di tutti gli altri metodi discussi. Ci sono situazioni su un sistema compatibile Unix in cui una corretta gestione diargv[0] non ti porterà al tuo programma ma poi stai eseguendo in un ambiente rotto in maniera certificabile. È anche abbastanza portabile su tutti i sistemi derivati ​​da Unix dal 1970 circa e persino su alcuni sistemi non derivati ​​da Unix poiché si basa sostanzialmente sulla funzionalità standard di libc () e sulla funzionalità della riga di comando standard. Dovrebbe funzionare su Linux (tutte le versioni), Android, Chrome OS, Minix, Bell Labs Unix originale, FreeBSD, NetBSD, OpenBSD, BSD xx, SunOS, Solaris, SYSV, HPUX, Concentrix, SCO, Darwin, AIX, OS X, Nextstep, ecc. E con una piccola modifica probabilmente VMS, VM / CMS, DOS / Windows, ReactOS, OS / 2, ecc. Se un programma fosse avviato direttamente da un ambiente GUI, avrebbe dovuto impostare argv[0]un percorso assoluto.

Comprendi che quasi ogni shell su ogni sistema operativo compatibile Unix che sia mai stata rilasciata trova sostanzialmente i programmi allo stesso modo e imposta l'ambiente operativo quasi allo stesso modo (con alcuni extra opzionali). E ogni altro programma che avvia un programma dovrebbe creare lo stesso ambiente (argv, stringhe di ambiente, ecc.) Per quel programma come se fosse eseguito da una shell, con alcuni extra opzionali. Un programma o un utente può impostare un ambiente che si discosta da questa convenzione per altri programmi subordinati che avvia ma, in tal caso, si tratta di un bug e il programma non ha aspettative ragionevoli che il programma subordinato oi suoi subordinati funzionino correttamente.

I possibili valori di argv[0]includono:

  • /path/to/executable - percorso assoluto
  • ../bin/executable - relativo a pwd
  • bin/executable - relativo a pwd
  • ./foo - relativo a pwd
  • executable - basename, trova nel percorso
  • bin//executable - relativo a pwd, non canonico
  • src/../bin/executable - relativo a pwd, non canonico, backtracking
  • bin/./echoargc - relativo a pwd, non canonico

Valori che non dovresti vedere:

  • ~/bin/executable - riscritto prima dell'esecuzione del programma.
  • ~user/bin/executable - riscritto prima dell'esecuzione del programma
  • alias - riscritto prima dell'esecuzione del programma
  • $shellvariable - riscritto prima dell'esecuzione del programma
  • *foo* - carattere jolly, riscritto prima dell'esecuzione del programma, non molto utile
  • ?foo? - carattere jolly, riscritto prima dell'esecuzione del programma, non molto utile

Inoltre, possono contenere nomi di percorsi non canonici e più livelli di collegamenti simbolici. In alcuni casi, potrebbero esserci più collegamenti fisici allo stesso programma. Ad esempio, /bin/ls, /bin/ps, /bin/chmod, /bin/rm, ecc possono essere hard link /bin/busybox.

Per ritrovarti, segui i passaggi seguenti:

  • Salva pwd, PATH e argv [0] all'entrata nel tuo programma (o all'inizializzazione della tua libreria) poiché potrebbero cambiare in seguito.

  • Opzionale: in particolare per i sistemi non Unix, separare ma non scartare la parte del prefisso host / utente / unità del percorso, se presente; la parte che spesso precede i due punti o segue un "//" iniziale.

  • Se argv[0]è un percorso assoluto, usalo come punto di partenza. Un percorso assoluto probabilmente inizia con "/" ma su alcuni sistemi non Unix potrebbe iniziare con "\" o una lettera di unità o un prefisso di nome seguito da due punti.

  • Altrimenti if argv[0]è un percorso relativo (contiene "/" o "\" ma non inizia con esso, come "../../bin/foo", quindi combina pwd + "/" + argv [0] (usa presente directory di lavoro da quando il programma è stato avviato, non corrente).

  • Altrimenti se argv [0] è un semplice basename (senza barre), quindi combinalo con ciascuna voce nella variabile d'ambiente PATH a sua volta e prova quelle e usa la prima che ha successo.

  • Opzionale: Else provare la specifica piattaforma molto /proc/self/exe, /proc/curproc/file(BSD), e (char *)getauxval(AT_EXECFN), e dlgetname(...), se presente. Potresti anche provare questi argv[0]metodi basati su precedenti , se sono disponibili e non riscontri problemi di autorizzazione. Nel caso piuttosto improbabile (se si considerano tutte le versioni di tutti i sistemi) che sono presenti e non falliscono, potrebbero essere più autorevoli.

  • Facoltativo: verificare il nome di un percorso passato utilizzando un parametro della riga di comando.

  • Facoltativo: verifica la presenza di un percorso nell'ambiente esplicitamente passato dallo script del wrapper, se presente.

  • Facoltativo: come ultima risorsa provare la variabile di ambiente "_". Potrebbe puntare a un programma completamente diverso, come la shell degli utenti.

  • Risolvi i collegamenti simbolici, potrebbero esserci più livelli. Esiste la possibilità di cicli infiniti, anche se se esistono il tuo programma probabilmente non verrà invocato.

  • Canonicalizza il nome file risolvendo le sottostringhe come "/foo/../bar/" in "/ bar /". Nota che questo potrebbe potenzialmente cambiare il significato se attraversi un mount point di rete, quindi la canonizzazione non è sempre una buona cosa. Su un server di rete, ".." in symlink può essere utilizzato per attraversare un percorso verso un altro file nel contesto del server anziché sul client. In questo caso, probabilmente desideri il contesto client, quindi la canonicalizzazione è ok. Converti anche modelli come "/./" in "/" e "//" in "/". Nella shell readlink --canonicalizerisolverà più collegamenti simbolici e canonicalizzerà il nome. Chase può fare simili ma non è installato. realpath()o canonicalize_file_name(), se presente, può aiutare.

Se realpath()non esiste al momento della compilazione, è possibile prenderne in prestito una copia da una distribuzione di librerie autorizzata e compilarla in proprio anziché reinventare la ruota. Correggi il potenziale overflow del buffer (passa la dimensione del buffer di output, pensa strncpy () vs strcpy ()) se utilizzerai un buffer inferiore a PATH_MAX. Potrebbe essere più semplice utilizzare una copia privata rinominata anziché testarla se esiste. Copia della licenza permissiva da android / darwin / bsd: https://android.googlesource.com/platform/bionic/+/f077784/libc/upstream-freebsd/lib/libc/stdlib/realpath.c

Tieni presente che più tentativi potrebbero avere esito positivo o parziale e potrebbero non tutti puntare allo stesso eseguibile, quindi considera di verificare il tuo eseguibile; tuttavia, potresti non avere il permesso di lettura - se non riesci a leggerlo, non trattarlo come un fallimento. O verifica qualcosa in prossimità del tuo eseguibile come la directory "../lib/" che stai cercando di trovare. Potresti avere più versioni, versioni impacchettate e compilate localmente, versioni locali e di rete e versioni portatili locali e USB, ecc. E c'è una piccola possibilità che potresti ottenere due risultati incompatibili da diversi metodi di localizzazione. E "_" può semplicemente indicare un programma sbagliato.

Un programma che utilizza execvepuò deliberatamente argv[0]essere incompatibile con il percorso effettivo utilizzato per caricare il programma e corrompere PATH, "_", pwd, ecc., Sebbene generalmente non vi siano molte ragioni per farlo; ma ciò potrebbe avere implicazioni per la sicurezza se si dispone di codice vulnerabile che ignora il fatto che l'ambiente di esecuzione può essere modificato in vari modi tra cui, a titolo esemplificativo ma non esaustivo, questo (chroot, fuse filesystem, hard link, ecc.) È possibile per i comandi della shell di impostare PATH ma non riesce ad esportarlo.

Non è necessario necessariamente codificare per sistemi non Unix, ma sarebbe una buona idea essere consapevoli di alcune delle peculiarità in modo da poter scrivere il codice in modo tale che non sia difficile per qualcuno portarlo in seguito . Tenere presente che alcuni sistemi (DEC VMS, DOS, URL, ecc.) Potrebbero avere nomi di unità o altri prefissi che terminano con due punti come "C: \", "sys $ drive: [foo] bar" e "file : /// foo / bar / baz". I vecchi sistemi DEC VMS utilizzano "[" e "]" per racchiudere la parte della directory del percorso sebbene ciò possa essere cambiato se il programma è compilato in un ambiente POSIX. Alcuni sistemi, come VMS, possono avere una versione di file (separata da un punto e virgola alla fine). Alcuni sistemi usano due barre consecutive come in "// drive / path / to / file" o "user @ host: / path / to / file" (comando scp) o "file: (delimitato da spazi) e "PERCORSO" delimitato da due punti, ma il tuo programma dovrebbe ricevere PERCORSO, quindi non devi preoccuparti del percorso. DOS e alcuni altri sistemi possono avere percorsi relativi che iniziano con un prefisso di unità. C: foo.exe si riferisce a foo.exe nella directory corrente sull'unità C, quindi è necessario cercare la directory corrente su C: e usarla per pwd. (delimitato da spazi) e "PERCORSO" delimitato da due punti, ma il tuo programma dovrebbe ricevere PERCORSO, quindi non devi preoccuparti del percorso. DOS e alcuni altri sistemi possono avere percorsi relativi che iniziano con un prefisso di unità. C: foo.exe si riferisce a foo.exe nella directory corrente sull'unità C, quindi è necessario cercare la directory corrente su C: e usarla per pwd.

Un esempio di symlink e wrapper sul mio sistema:

/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

Si noti che la fattura dell'utente ha pubblicato un collegamento sopra a un programma di HP che gestisce i tre casi di base di argv[0]. Ha bisogno di alcune modifiche, tuttavia:

  • Sarà necessario riscrivere tutto strcat()e strcpy()usare strncat()estrncpy() . Anche se le variabili sono dichiarate di lunghezza PATHMAX, un valore di input di lunghezza PATHMAX-1 più la lunghezza delle stringhe concatenate è> PATHMAX e un valore di input di lunghezza PATHMAX non sarebbe terminato.
  • Deve essere riscritto come una funzione di libreria, piuttosto che solo per stampare i risultati.
    • Non riesce a canonicalizzare i nomi (utilizzare il codice realpath a cui ho collegato sopra)
    • Non riesce a risolvere i collegamenti simbolici (utilizzare il codice realpath)

Pertanto, se si combinano sia il codice HP che il codice realpath e si correggono entrambi per resistere agli overflow del buffer, è necessario disporre di qualcosa che possa interpretare correttamente argv[0] .

Quanto segue illustra i valori effettivi di argv[0]vari modi per invocare lo stesso programma su Ubuntu 12.04. E sì, il programma è stato accidentalmente chiamato echoargc invece di echoargv. Questo è stato fatto usando uno script per una copia pulita ma farlo manualmente nella shell ottiene gli stessi risultati (tranne che gli alias non funzionano nello script a meno che tu non li abiliti esplicitamente).

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

Questi esempi mostrano che le tecniche descritte in questo post dovrebbero funzionare in una vasta gamma di circostanze e perché sono necessari alcuni passaggi.

EDIT: Ora, il programma che stampa argv [0] è stato aggiornato per trovarsi effettivamente.

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

Ed ecco l'output che dimostra che in ognuno dei test precedenti si è effettivamente trovato.

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"

Anche i due lanci della GUI sopra descritti trovano correttamente il programma.

C'è un potenziale trabocchetto. La access()funzione rilascia autorizzazioni se il programma è impostato prima del test. Se c'è una situazione in cui il programma può essere trovato come un utente elevato ma non come un utente normale, allora potrebbe esserci una situazione in cui questi test fallirebbero, sebbene sia improbabile che il programma possa essere effettivamente eseguito in tali circostanze. Si potrebbe usare invece euidaccess (). È possibile, tuttavia, che potrebbe trovare un programma inaccessibile prima sul percorso di quanto l'utente reale potrebbe.


1
Ci hai messo molto impegno - ben fatto. Sfortunatamente, né strncpy()né (soprattutto) strncat()viene utilizzato in modo sicuro nel codice. strncpy()non garantisce la risoluzione nulla; se la stringa di origine è più lunga dello spazio di destinazione, la stringa non viene annullata. strncat()è molto difficile da usare; strncat(target, source, sizeof(target))è errato (anche se targetè una stringa vuota per cominciare) se sourceè più lungo del target. La lunghezza è il numero di caratteri che possono essere tranquillamente aggiunti al target escluso il null finale, quindi sizeof(target)-1è il massimo.
Jonathan Leffler,

4
Il codice strncpy è corretto, a differenza del metodo che implica che dovrei usare. Ti suggerisco di leggere il codice più attentamente. Non trabocca buffer e non li lascia termina. Ogni utilizzo di strncpy () / stncat () è limitato copiando sizeof (buffer), che è valido, e quindi l'ultimo carattere del buffer viene riempito con uno zero che sovrascrive l'ultimo carattere del buffer. strncat (), tuttavia, utilizza il parametro size in modo errato come conteggio e può overflow a causa del fatto che è precedente agli attacchi di overflow del buffer.
con il

"sudo apt-get install libbsd0 libbsd-dev", quindi s / strncat / strlcat /
whitis

1
Non utilizzare PATH_MAX. Questo ha smesso di funzionare 30 anni fa, usa sempre malloc.
Lothar,

Anche se usi una chiamata init. Risolvi completamente il percorso dell'exe su init e non solo una parte e poi fallo in seguito su chiamata. Nessuna valutazione pigra qui possibile se si utilizza realpath nel resolver. Insieme agli altri erorr semplicemente il peggior codice che ho visto su StackOverflow in una lunga risposta.
Lothar,

13

Dai un'occhiata alla libreria whereami di Gregory Pakosz (che ha solo un singolo file C); ti consente di ottenere l'intero percorso dell'eseguibile corrente su una varietà di piattaforme. Attualmente, è disponibile come repository su github qui .


8

Un'alternativa su Linux all'utilizzo di uno /proc/self/exeo argv[0]è l'utilizzo delle informazioni passate dall'interprete ELF, rese disponibili da glibc in quanto tale:

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

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

Si noti che getauxvalè un'estensione glibc e per essere robusta è necessario verificare che non ritorni NULL(indicando che l'interprete ELF non ha fornito il AT_EXECFNparametro), ma non penso che questo sia effettivamente un problema su Linux.


Mi piace dal momento che è semplice e glibc è incluso in Gtk + comunque (che sto usando).
Colin Keenan,

4

Se mai dovessi supportare, diciamo, Mac OS X, che non ha / proc /, cosa avresti fatto? Utilizzare #ifdefs per isolare il codice specifico della piattaforma (NSBundle, ad esempio)?

Sì, isolare il codice specifico della piattaforma con questo #ifdefsè il modo convenzionale.

Un altro approccio sarebbe quello di avere #ifdefun'intestazione clean -less che contenga dichiarazioni di funzioni e inserisca le implementazioni in file sorgente specifici della piattaforma. Ad esempio, controlla come la libreria Poco C ++ fa qualcosa di simile per la loro classe Environment .


4

Affinché funzioni in modo affidabile su tutte le piattaforme è necessario utilizzare le istruzioni #ifdef.

Il codice seguente trova il percorso dell'eseguibile in Windows, Linux, MacOS, Solaris o FreeBSD (sebbene FreeBSD non sia testato). Usa boost > = 1.55.0 per semplificare il codice ma è abbastanza facile da rimuovere se lo desideri. Basta usare definisce come _MSC_VER e __linux come richiedono il sistema operativo e il compilatore.

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

La versione precedente restituisce percorsi completi incluso il nome dell'eseguibile. Se invece desideri il percorso senza il nome dell'eseguibile #include boost/filesystem.hpp>e modifica l'istruzione return in:

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

@Frank, non so perché lo dici. Per me va bene. Ho visto un'altra risposta affermare che hai bisogno di root per accedere a / proc / self / exe ma non l'ho trovato su nessun sistema Linux che ho provato (CentOS o Mint).
giovedì

2

A seconda della versione di QNX Neutrino , esistono diversi modi per trovare il percorso completo e il nome del file eseguibile utilizzato per avviare il processo in esecuzione. Indico l'identificatore del processo come <PID>. Prova quanto segue:

  1. Se il file /proc/self/exefileesiste, i suoi contenuti sono le informazioni richieste.
  2. Se il file /proc/<PID>/exefileesiste, i suoi contenuti sono le informazioni richieste.
  3. Se il file /proc/self/asesiste, quindi:
    1. open() il file.
    2. Allocare un buffer di almeno sizeof(procfs_debuginfo) + _POSIX_PATH_MAX.
    3. Dare quel buffer come input a devctl(fd, DCMD_PROC_MAPDEBUG_BASE,....
    4. Trasmetti il ​​buffer a procfs_debuginfo* .
    5. Le informazioni richieste si trovano nel pathcampo della procfs_debuginfostruttura. Avvertenza : per qualche motivo, a volte, QNX omette la prima barra /del percorso del file. Preparalo /quando necessario.
    6. Pulisci (chiudi il file, libera il buffer, ecc.).
  4. Prova la procedura 3.con il file/proc/<PID>/as .
  5. Prova dladdr(dlsym(RTLD_DEFAULT, "main"), &dlinfo)dov'è dlinfouna Dl_infostruttura che dli_fnamepotrebbe contenere le informazioni richieste.

Spero che aiuti.


1

AFAIK, non è così. E c'è anche un'ambuiguità: cosa ti piacerebbe ottenere come risposta se lo stesso eseguibile ha più hard-link che "puntano" ad esso? (Gli hard-link in realtà non "puntano", sono lo stesso file, proprio in un altro punto della gerarchia di FS.) Una volta che execve () esegue correttamente un nuovo binario, tutte le informazioni sui suoi argomenti vengono perse.


1
"Una volta che execve () esegue correttamente un nuovo binario, tutte le informazioni sui suoi argomenti vengono perse." In realtà, gli argomenti argp ed envp non vengono persi, vengono passati come argv [] e l'ambiente e, in alcuni UN * Xes, l'argomento del percorso o qualcosa costruito da esso viene passato con argp e envp (OS X / iOS, Solaris) o resi disponibili tramite uno dei meccanismi elencati nella risposta di mark4o. Ma sì, questo ti dà solo uno degli hard link se ne esiste più di uno.

1

Puoi usare argv [0] e analizzare la variabile d'ambiente PATH. Guarda: un esempio di un programma che può trovarsi


7
Questo in realtà non è affidabile (anche se generalmente funzionerà con i programmi lanciati dalle solite shell), perché execve parenti prendono il percorso dell'eseguibile separatamente diargv
dmckee --- ex gattino moderatore,

9
Questa è una risposta errata Si potrebbe dire dove si potrebbe trovare un programma con lo stesso nome. Ma non ti dice nulla su dove vive effettivamente l'eseguibile attualmente in esecuzione.
Larry Gritz,

0

Modo più portatile per ottenere il nome percorso dell'immagine eseguibile:

ps può darti il ​​percorso dell'eseguibile, dato che hai l'ID del processo. Inoltre ps è un'utilità POSIX quindi dovrebbe essere portatile

quindi se l'id del processo è 249297, questo comando fornisce solo il nome del percorso.

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

Spiegazione degli argomenti

-p - seleziona il processo dato

-o comm - visualizza il nome del comando (-o cmd seleziona l'intera riga di comando)

--no-header - non visualizza una linea di prua, ma solo l'output.

Il programma AC può eseguirlo tramite popen.


Fornisce una stringa di avvio completa con parametri.
ETech,

--no-header non è portatile
Good Person

1
non funziona se il primo argomento di execv non è un percorso assoluto.
hroptatyr,

-4

Se usi C, puoi usare la funzione getwd:

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

Verrà stampato sull'output standard, la directory corrente dell'eseguibile.


3
almeno su Windows, l'attuale directory di lavoro non ha particolari relazioni con l'eseguibile in esecuzione. Ad esempio, CreateProcess può avviare un file .exe e impostare la sua directory di lavoro in modo completamente indipendente.
Spike0xff,

La situazione è la stessa su tutti gli altri sistemi operativi: la directory corrente a volte è la stessa della directory eseguibile per caso, ma può essere completamente diversa.
Lassi

-10

Il percorso del valore assoluto di un programma si trova nel PWD dell'envp della tua funzione principale, inoltre c'è una funzione in C chiamata getenv, quindi c'è quella.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.