Penggunaan /proc/self/exe
non-portable dan tidak dapat diandalkan. Di sistem Ubuntu 12.04 saya, Anda harus root untuk membaca / mengikuti symlink. Ini akan membuat Boost contoh dan mungkinwhereami()
solusi yang diposting gagal.
Posting ini sangat panjang tetapi membahas masalah aktual dan menyajikan kode yang benar-benar berfungsi bersamaan dengan validasi terhadap test suite.
Cara terbaik untuk menemukan program Anda adalah menelusuri kembali langkah-langkah yang sama dengan yang digunakan sistem. Ini dilakukan dengan menggunakan argv[0]
diselesaikan terhadap root sistem file, pwd, lingkungan path dan mempertimbangkan symlink, dan kanonik pathname. Ini dari ingatan tetapi saya telah berhasil melakukannya di masa lalu dan mengujinya dalam berbagai situasi yang berbeda. Ini tidak dijamin untuk bekerja, tetapi jika tidak, Anda mungkin memiliki masalah yang jauh lebih besar dan lebih dapat diandalkan secara keseluruhan daripada metode lain yang dibahas. Ada situasi pada sistem yang kompatibel Unix di mana penanganan yang tepatargv[0]
tidak akan membawa Anda ke program Anda, tetapi kemudian Anda mengeksekusi di lingkungan yang rusak secara sertifikasi. Ia juga cukup portabel untuk semua sistem turunan Unix sejak sekitar tahun 1970 dan bahkan beberapa sistem turunan non-Unix karena pada dasarnya bergantung pada fungsionalitas standar libc () dan fungsionalitas baris perintah standar. Ini harus bekerja di Linux (semua versi), Android, Chrome OS, Minix, Bell Labs Unix asli, FreeBSD, NetBSD, OpenBSD, BSD xx, SunOS, Solaris, SYSV, HPUX, Concentrix, SCO, Darwin, AIX, OS X, Nextstep, dll. Dan dengan sedikit modifikasi mungkin VMS, VM / CMS, DOS / Windows, ReactOS, OS / 2, dll. Jika suatu program diluncurkan langsung dari lingkungan GUI, itu harusnya diatur argv[0]
ke jalur absolut.
Memahami bahwa hampir setiap shell pada setiap sistem operasi Unix yang kompatibel yang pernah dirilis pada dasarnya menemukan program dengan cara yang sama dan mengatur lingkungan operasi dengan cara yang hampir sama (dengan beberapa tambahan opsional). Dan setiap program lain yang meluncurkan program diharapkan untuk menciptakan lingkungan yang sama (argv, string lingkungan, dll.) Untuk program itu seolah-olah dijalankan dari shell, dengan beberapa tambahan opsional. Suatu program atau pengguna dapat mengatur lingkungan yang menyimpang dari konvensi ini untuk program bawahan lainnya yang diluncurkan tetapi jika itu terjadi, ini adalah bug dan program tidak memiliki harapan yang masuk akal bahwa program bawahan atau bawahannya akan berfungsi dengan benar.
Nilai yang mungkin argv[0]
termasuk:
/path/to/executable
- jalur absolut
../bin/executable
- relatif terhadap pwd
bin/executable
- relatif terhadap pwd
./foo
- relatif terhadap pwd
executable
- basename, temukan di jalur
bin//executable
- relatif terhadap pwd, non-kanonik
src/../bin/executable
- relatif terhadap pwd, non-kanonik, mundur
bin/./echoargc
- relatif terhadap pwd, non-kanonik
Nilai yang seharusnya tidak Anda lihat:
~/bin/executable
- ditulis ulang sebelum program Anda berjalan.
~user/bin/executable
- ditulis ulang sebelum program Anda berjalan
alias
- ditulis ulang sebelum program Anda berjalan
$shellvariable
- ditulis ulang sebelum program Anda berjalan
*foo*
- wildcard, ditulis ulang sebelum program Anda berjalan, tidak terlalu berguna
?foo?
- wildcard, ditulis ulang sebelum program Anda berjalan, tidak terlalu berguna
Selain itu, ini mungkin berisi nama jalur non-kanonik dan beberapa lapisan tautan simbolik. Dalam beberapa kasus, mungkin ada beberapa tautan keras ke program yang sama. Sebagai contoh, /bin/ls
, /bin/ps
, /bin/chmod
, /bin/rm
, dll mungkin hard link ke /bin/busybox
.
Untuk menemukan diri Anda, ikuti langkah-langkah di bawah ini:
Simpan pwd, PATH, dan argv [0] pada entri ke program Anda (atau inisialisasi perpustakaan Anda) karena mereka dapat berubah nanti.
Opsional: khususnya untuk sistem non-Unix, pisahkan tetapi jangan membuang bagian pathfiks host / pengguna / drive pathname, jika ada; bagian yang sering mendahului titik dua atau mengikuti inisial "//".
Jika argv[0]
adalah jalur absolut, gunakan itu sebagai titik awal. Path absolut mungkin dimulai dengan "/" tetapi pada beberapa sistem non-Unix mungkin dimulai dengan "\" atau awalan huruf atau nama drive diikuti oleh titik dua.
Lain jika argv[0]
path relatif (berisi "/" atau "\" tetapi tidak dimulai dengan itu, seperti "../../bin/foo", lalu gabungkan pwd + "/" + argv [0] (gunakan hadir direktori kerja dari saat program dimulai, bukan saat ini).
Lain jika argv [0] adalah nama dasar polos (tanpa garis miring), kemudian gabungkan dengan setiap entri dalam variabel lingkungan PATH secara bergantian dan cobalah dan gunakan yang pertama yang berhasil.
Opsional: Lain, coba platform yang sangat spesifik /proc/self/exe
, /proc/curproc/file
(BSD), dan (char *)getauxval(AT_EXECFN)
, dan dlgetname(...)
jika ada. Anda bahkan dapat mencoba argv[0]
metode-metode berbasis sebelum ini , jika tersedia dan Anda tidak menemui masalah izin. Dalam kejadian yang agak tidak mungkin (ketika Anda mempertimbangkan semua versi dari semua sistem) bahwa mereka hadir dan tidak gagal, mereka mungkin lebih berwibawa.
Opsional: periksa nama jalur yang dilewatkan menggunakan parameter baris perintah.
Opsional: periksa nama path di lingkungan yang diteruskan secara eksplisit oleh skrip wrapper Anda, jika ada.
Opsional: Sebagai upaya terakhir cobalah variabel lingkungan "_". Mungkin menunjuk ke program yang sama sekali berbeda, seperti shell pengguna.
Selesaikan symlink, mungkin ada beberapa lapisan. Ada kemungkinan loop tak terbatas, meskipun jika ada program Anda mungkin tidak akan dipanggil.
Kanonisasi nama file dengan menyelesaikan substring seperti "/foo/../bar/" ke "/ bar /". Catatan ini berpotensi mengubah artinya jika Anda melewati titik pemasangan jaringan, jadi kanonisasi tidak selalu merupakan hal yang baik. Pada server jaringan, ".." di symlink dapat digunakan untuk melintasi jalur ke file lain dalam konteks server alih-alih pada klien. Dalam hal ini, Anda mungkin menginginkan konteks klien sehingga kanonikisasi tidak apa-apa. Juga konversi pola seperti "/./" ke "/" dan "//" ke "/". Dalam shell, readlink --canonicalize
akan menyelesaikan banyak symlink dan mengkanoniskan nama. Chase mungkin melakukan hal serupa tetapi tidak diinstal. realpath()
atau canonicalize_file_name()
, jika ada, dapat membantu.
Jika realpath()
tidak ada pada waktu kompilasi, Anda dapat meminjam salinan dari distribusi pustaka berlisensi yang diizinkan, dan kompilasi dalam diri Anda sendiri daripada menciptakan kembali roda. Perbaiki potensi buffer overflow (masukkan sizeof output buffer, pikirkan strncpy () vs strcpy ()) jika Anda akan menggunakan buffer kurang dari PATH_MAX. Mungkin lebih mudah hanya menggunakan salinan pribadi yang diganti namanya daripada menguji apakah ada. Salinan lisensi permisif dari android / darwin / bsd:
https://android.googlesource.com/platform/bionic/+/f077784/libc/upstream-freebsd/lib/libc/stdlib/realpath.c
Ketahuilah bahwa beberapa upaya mungkin berhasil atau sebagian berhasil dan mereka mungkin tidak semua menunjuk ke executable yang sama, jadi pertimbangkan untuk memverifikasi executable Anda; namun, Anda mungkin tidak memiliki izin baca - jika Anda tidak bisa membacanya, jangan menganggapnya sebagai kegagalan. Atau verifikasi sesuatu yang dekat dengan executable Anda seperti direktori "../lib/" yang Anda coba temukan. Anda mungkin memiliki beberapa versi, paket dan versi yang dikompilasi secara lokal, versi lokal dan jaringan, dan versi portabel lokal dan USB-drive, dll. Dan ada kemungkinan kecil bahwa Anda mungkin mendapatkan dua hasil yang tidak kompatibel dari metode penempatan yang berbeda. Dan "_" mungkin hanya menunjuk ke program yang salah.
Suatu program yang menggunakan execve
sengaja dapat diatur argv[0]
agar tidak sesuai dengan jalur aktual yang digunakan untuk memuat program dan merusak PATH, "_", pwd, dll. Meskipun secara umum tidak ada banyak alasan untuk melakukannya; tetapi ini dapat memiliki implikasi keamanan jika Anda memiliki kode rentan yang mengabaikan fakta bahwa lingkungan eksekusi Anda dapat diubah dalam berbagai cara termasuk, tetapi tidak terbatas, untuk yang satu ini (chroot, sistem file sekering, tautan keras, dll.) untuk perintah shell untuk mengatur PATH tetapi gagal untuk mengekspornya.
Anda tidak perlu kode untuk sistem non-Unix tetapi akan lebih baik jika Anda mengetahui beberapa keanehan sehingga Anda dapat menulis kode sedemikian rupa sehingga tidak sulit bagi seseorang untuk porting nanti . Perlu diketahui bahwa beberapa sistem (DEC VMS, DOS, URL, dll.) Mungkin memiliki nama drive atau awalan lain yang diakhiri dengan tanda titik dua seperti "C: \", "sys $ drive: [foo] bar", dan "file : /// foo / bar / baz ". Sistem DEC VMS lama menggunakan "[" dan "]" untuk melampirkan bagian direktori dari path meskipun ini mungkin telah berubah jika program Anda dikompilasi dalam lingkungan POSIX. Beberapa sistem, seperti VMS, mungkin memiliki versi file (dipisahkan oleh tanda titik koma di bagian akhir). Beberapa sistem menggunakan dua garis miring berurutan seperti pada "// drive / path / ke / file" atau "user @ host: / path / ke / file" (perintah scp) atau "file: (dibatasi dengan spasi) dan "PATH" dibatasi dengan titik dua tetapi program Anda harus menerima PATH sehingga Anda tidak perlu khawatir tentang jalur. DOS dan beberapa sistem lain dapat memiliki jalur relatif yang dimulai dengan drive prefix. C: foo.exe merujuk ke foo.exe di direktori saat ini di drive C, jadi Anda perlu mencari direktori saat ini di C: dan menggunakannya untuk pwd. (dibatasi dengan spasi) dan "PATH" dibatasi dengan titik dua tetapi program Anda harus menerima PATH sehingga Anda tidak perlu khawatir tentang jalur. DOS dan beberapa sistem lain dapat memiliki jalur relatif yang dimulai dengan drive prefix. C: foo.exe merujuk ke foo.exe di direktori saat ini di drive C, jadi Anda perlu mencari direktori saat ini di C: dan menggunakannya untuk pwd.
Contoh symlink dan pembungkus di sistem saya:
/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
Perhatikan bahwa tagihan pengguna memposting tautan di atas ke program di HP yang menangani tiga kasus dasar argv[0]
. Perlu beberapa perubahan, namun:
- Ini akan diperlukan untuk menulis ulang semua
strcat()
dan strcpy()
untuk menggunakan strncat()
dan strncpy()
. Meskipun variabel-variabel tersebut dideklarasikan dari panjang PATHMAX, nilai input panjang PATHMAX-1 plus panjang string yang digabungkan adalah> PATHMAX dan nilai input dari panjang PATHMAX akan tidak ditentukan.
- Itu perlu ditulis ulang sebagai fungsi perpustakaan, bukan hanya untuk mencetak hasil.
- Gagal mengkanoniskan nama (gunakan kode realpath yang saya tautkan di atas)
- Gagal menyelesaikan tautan simbolis (gunakan kode realpath)
Jadi, jika Anda menggabungkan kedua kode HP dan kode realpath dan memperbaiki keduanya agar tahan terhadap buffer overflow, maka Anda harus memiliki sesuatu yang dapat diinterpretasikan dengan benar. argv[0]
.
Berikut ini menggambarkan nilai aktual argv[0]
untuk berbagai cara menjalankan program yang sama di Ubuntu 12.04. Dan ya, program itu sengaja dinamai echoargc bukan echoargv. Ini dilakukan dengan menggunakan skrip untuk membersihkan penyalinan tetapi melakukannya secara manual di shell mendapatkan hasil yang sama (kecuali alias tidak berfungsi dalam skrip kecuali Anda mengaktifkannya secara eksplisit).
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
Contoh-contoh ini menggambarkan bahwa teknik yang dijelaskan dalam posting ini harus bekerja dalam berbagai keadaan dan mengapa beberapa langkah diperlukan.
EDIT: Sekarang, program yang mencetak argv [0] telah diperbarui untuk benar-benar menemukan dirinya sendiri.
// 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 */
}
Dan ini adalah output yang menunjukkan bahwa dalam setiap tes sebelumnya ternyata menemukan dirinya sendiri.
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"
Kedua peluncuran GUI yang dijelaskan di atas juga menemukan program dengan benar.
Ada satu perangkap potensial. The access()
Fungsi tetes izin jika program ini setuid sebelum pengujian. Jika ada situasi di mana program dapat ditemukan sebagai pengguna yang ditinggikan tetapi bukan sebagai pengguna biasa, maka mungkin ada situasi di mana tes ini akan gagal, meskipun tidak mungkin program tersebut benar-benar dapat dieksekusi dalam keadaan tersebut. Orang bisa menggunakan euidaccess () sebagai gantinya. Namun, ada kemungkinan bahwa ia mungkin menemukan program yang tidak dapat diakses lebih awal di jalur daripada yang bisa dilakukan pengguna sebenarnya.