L'uso di /proc/self/exe
non è 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 --canonicalize
risolverà 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 execve
può 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.