Menemukan jalur yang dapat dieksekusi saat ini tanpa / proc / self / exe

190

Sepertinya saya bahwa Linux mudah dengan / proc / self / exe. Tapi saya ingin tahu apakah ada cara mudah untuk menemukan direktori aplikasi saat ini di C / C ++ dengan antarmuka lintas platform. Saya telah melihat beberapa proyek bergurau dengan argv [0], tetapi tampaknya tidak sepenuhnya dapat diandalkan.

Jika Anda harus mendukung, katakanlah, Mac OS X, yang tidak memiliki / proc /, apa yang akan Anda lakukan? Gunakan #ifdefs untuk mengisolasi kode khusus platform (NSBundle, misalnya)? Atau coba simpulkan jalur yang dapat dieksekusi dari argv [0], $ PATH dan yang lainnya, berisiko menemukan bug dalam kasus tepi?

Uros Dimitrijevic
sumber
Saya googled: ambil ps -o comm. Apa yang membawaku ke sini adalah: "/proc/pid/path/a.out"
basin
Jawaban IMHO prideout layak berada di atas, karena itu dengan benar memenuhi persyaratan "antarmuka lintas platform" dan sangat mudah untuk diintegrasikan.
Stéphane Gourichon

Jawaban:

348

Beberapa antarmuka khusus OS:

Metode portabel (tetapi kurang dapat diandalkan) adalah untuk digunakan argv[0]. Meskipun dapat diatur untuk apa saja oleh program panggilan, dengan konvensi itu diatur ke salah satu nama jalur yang dapat dieksekusi atau nama yang ditemukan menggunakan$PATH .

Beberapa shell, termasuk bash dan ksh, mengatur variabel lingkungan " _" ke path penuh dari executable sebelum dieksekusi. Dalam hal ini Anda dapat menggunakannya getenv("_")untuk mendapatkannya. Namun ini tidak dapat diandalkan karena tidak semua shell melakukan ini, dan itu dapat diatur ke apa pun atau ditinggalkan dari proses induk yang tidak mengubahnya sebelum menjalankan program Anda.

mark4o
sumber
3
Dan perhatikan juga bahwa _NSGetExecutablePath () tidak mengikuti symlinks.
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));; itu berbeda dari getexecname()- yang tidak berarti pargs -x <PID> | grep AT_SUN_EXECNAME...
FrankH.
4
"QDesktopServices :: storageLocation (QDesktopServices :: DataLocation)" Itu bukan path yang dapat dieksekusi, itu pathname dari direktori per-pengguna di mana data harus disimpan.
2
OpenBSD adalah satu-satunya di mana Anda masih tidak bisa pada 2017. Anda harus menggunakan cara PATH dan argv [0]
Lothar
27

Penggunaan /proc/self/exenon-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 --canonicalizeakan 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 execvesengaja 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.

whitis
sumber
1
Anda melakukan banyak upaya untuk itu - dilakukan dengan baik. Sayangnya, tidak satu strncpy()pun (terutama) strncat()digunakan dengan aman dalam kode. strncpy()tidak menjamin pengakhiran nol; jika string sumber lebih panjang dari ruang target, string tersebut tidak dibatalkan nol. strncat()sangat sulit digunakan; strncat(target, source, sizeof(target))salah (meskipun targetstring kosong untuk memulai) jika sourcelebih panjang dari target. Panjangnya adalah jumlah karakter yang dapat dengan aman ditambahkan ke target tanpa menyertakan trailing null, jadi sizeof(target)-1maksimum.
Jonathan Leffler
4
Kode strncpy benar, tidak seperti metode yang Anda maksudkan, saya harus menggunakan. Saya sarankan Anda membaca kode lebih hati-hati. Itu tidak meluap buffer atau membuat mereka tidak tergiur. Setiap penggunaan strncpy () / stncat () dibatasi dengan menyalin sizeof (buffer), yang valid, dan kemudian karakter terakhir buffer diisi dengan nol yang menimpa karakter terakhir buffer. strncat (), bagaimanapun, menggunakan parameter ukuran tidak benar sebagai hitungan dan mungkin meluap karena fakta bahwa ia mendahului serangan buffer overflow.
whitis
"sudo apt-get install libbsd0 libbsd-dev", lalu s / strncat / strlcat /
whitis
1
Jangan gunakan PATH_MAX. Ini berhenti bekerja 30 tahun yang lalu, selalu menggunakan malloc.
Lothar
Juga jika Anda menggunakan panggilan init. Selesaikan sepenuhnya path ke exe on init dan bukan hanya bagian lalu lakukan nanti saat dipanggil. Tidak ada evaluasi malas di sini yang mungkin jika Anda menggunakan realpath di resolver. Bersama dengan erorrs lain hanyalah kode terburuk yang pernah saya lihat di stackoverflow dalam jawaban yang panjang.
Lothar
13

Check out whereami perpustakaan dari Gregory Pakosz (yang memiliki hanya C file tunggal); itu memungkinkan Anda untuk mendapatkan path lengkap ke executable saat ini di berbagai platform. Saat ini, tersedia sebagai repo di github di sini .

kebanggaan
sumber
8

Alternatif di Linux untuk menggunakan salah satu /proc/self/exeatau argv[0]menggunakan informasi yang diberikan oleh penerjemah ELF, yang disediakan oleh glibc sebagai berikut:

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

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

Perhatikan bahwa itu getauxvaladalah ekstensi glibc, dan untuk menjadi kuat Anda harus memeriksa sehingga tidak kembali NULL(menunjukkan bahwa juru bahasa ELF belum memberikan AT_EXECFNparameter), tetapi saya tidak berpikir ini benar-benar masalah di Linux.

Dolda2000
sumber
Saya suka ini karena sederhana dan glibc sudah disertakan dengan Gtk + (yang saya gunakan).
Colin Keenan
4

Jika Anda harus mendukung, katakanlah, Mac OS X, yang tidak memiliki / proc /, apa yang akan Anda lakukan? Gunakan #ifdefs untuk mengisolasi kode khusus platform (NSBundle, misalnya)?

Ya, mengisolasi kode spesifik platform dengan #ifdefscara konvensional ini dilakukan.

Pendekatan lain adalah memiliki #ifdefheader tanpa- bersih yang berisi deklarasi fungsi dan meletakkan implementasinya dalam file sumber platform spesifik. Misalnya, lihat bagaimana pustaka Poco C ++ melakukan sesuatu yang serupa untuk kelas Lingkungan mereka .

StackedCrooked
sumber
4

Membuat ini berfungsi dengan baik di seluruh platform membutuhkan penggunaan pernyataan #ifdef.

Kode di bawah ini menemukan jalur executable di Windows, Linux, MacOS, Solaris atau FreeBSD (walaupun FreeBSD belum teruji). Ini menggunakan boost > = 1.55.0 untuk menyederhanakan kode tetapi cukup mudah untuk menghapusnya jika Anda mau. Cukup gunakan definisi seperti _MSC_VER dan __linux seperti yang dibutuhkan oleh OS dan kompiler.

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

Versi di atas mengembalikan path lengkap termasuk nama yang dapat dieksekusi. Jika sebaliknya Anda menginginkan jalur tanpa nama yang dapat dieksekusi, #include boost/filesystem.hpp>dan ubah pernyataan kembali ke:

return strlen(exePath)>0 ? boost::filesystem::path(exePath).remove_filename().make_preferred().string() : std::string();
jtbr
sumber
@ Terus terang, tidak yakin mengapa Anda mengatakan itu. Bekerja untukku. Saya melihat respons lain mengklaim Anda perlu root untuk mengakses / proc / self / exe tapi saya belum menemukan itu pada sistem Linux yang saya coba (CentOS atau Mint).
jtbr
2

Bergantung pada versi QNX Neutrino , ada berbagai cara untuk menemukan path lengkap dan nama file yang dapat dieksekusi yang digunakan untuk memulai proses yang berjalan. Saya menunjukkan pengidentifikasi proses sebagai <PID>. Coba yang berikut ini:

  1. Jika file /proc/self/exefileada, maka isinya adalah informasi yang diminta.
  2. Jika file /proc/<PID>/exefileada, maka isinya adalah informasi yang diminta.
  3. Jika file /proc/self/asada, maka:
    1. open() berkas.
    2. Alokasikan buffer, setidaknya sizeof(procfs_debuginfo) + _POSIX_PATH_MAX,.
    3. Berikan buffer itu sebagai input devctl(fd, DCMD_PROC_MAPDEBUG_BASE,....
    4. Keluarkan buffer ke a procfs_debuginfo* .
    5. Informasi yang diminta ada di pathbidang procfs_debuginfostruktur. Peringatan : Untuk beberapa alasan, kadang-kadang, QNX menghilangkan garis miring pertama /dari path file. Sebutkan itu /ketika dibutuhkan.
    6. Bersihkan (tutup file, bebaskan buffer, dll.).
  4. Coba prosedur 3.dengan file /proc/<PID>/as.
  5. Coba dladdr(dlsym(RTLD_DEFAULT, "main"), &dlinfo)mana dlinfoadalah Dl_infostruktur yang dli_fnamemungkin berisi informasi yang diminta.

Saya harap ini membantu.

Koutheir Attouchi
sumber
1

AFAIK, tidak ada cara seperti itu. Dan ada juga ambiguitas: apa yang ingin Anda dapatkan sebagai jawaban jika executable yang sama memiliki banyak hard-link "menunjuk" ke sana? (Hard-link tidak benar-benar "menunjuk", mereka adalah file yang sama, hanya di tempat lain dalam hirarki FS.) Setelah mengeksekusi () berhasil mengeksekusi biner baru, semua informasi tentang argumennya hilang.

zvrba
sumber
1
"Setelah execve () berhasil mengeksekusi biner baru, semua informasi tentang argumennya hilang." Sebenarnya, argumen argp dan envp tidak hilang, argumen tersebut dilewatkan sebagai argv [] dan lingkungan dan, dalam beberapa UN * Xes, argumen pathname atau sesuatu yang dibangun darinya dilewatkan bersama dengan argp dan envp (OS X / iOS, Solaris) atau tersedia melalui salah satu mekanisme yang tercantum dalam jawaban mark4o. Tapi, ya, itu hanya memberi Anda salah satu tautan keras jika ada lebih dari satu.
1

Anda dapat menggunakan argv [0] dan menganalisis variabel lingkungan PATH. Lihatlah: Contoh program yang dapat menemukan dirinya sendiri

tagihan
sumber
7
Hal ini tidak benar-benar dapat diandalkan (meskipun umumnya akan bekerja dengan program yang diluncurkan oleh kerang biasa), karena execvdan kerabat mengambil jalan ke executable terpisah dariargv
dmckee --- mantan moderator kitten
9
Ini jawaban yang salah. Mungkin memberitahu Anda di mana Anda bisa menemukan sebuah program dengan nama yang sama. Tapi itu tidak memberi tahu Anda apa pun tentang di mana sebenarnya executable yang saat ini dijalankan berjalan.
Larry Gritz
0

Cara yang lebih portabel untuk mendapatkan nama jalur dari gambar yang dapat dieksekusi:

ps dapat memberi Anda jalan executable, mengingat Anda memiliki id proses. Juga ps adalah utilitas POSIX sehingga harus portabel

jadi jika id proses adalah 249297 maka perintah ini memberi Anda nama path saja.

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

Penjelasan argumen

-p - memilih proses yang diberikan

-o comm - menampilkan nama perintah (-o cmd memilih seluruh baris perintah)

--no-heading - jangan tampilkan baris heading, hanya output.

Program AC dapat menjalankan ini melalui popen.

MichaelMoser
sumber
Ini memberikan string peluncuran penuh dengan params.
ETech
--tak ada judul adalah non-portabel
Orang Baik
1
tidak berfungsi jika argumen pertama ke execv bukan jalur absolut.
hroptatyr
-4

Jika Anda menggunakan C, Anda dapat menggunakan fungsi getwd:

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

Ini akan Anda cetak pada output standar, direktori saat ini yang dapat dieksekusi.

Lucas Ruelle
sumber
3
setidaknya pada Windows, direktori kerja saat ini tidak memiliki hubungan khusus dengan executable yang berjalan. Misalnya, CreateProcess dapat meluncurkan .exe dan mengatur direktori kerjanya sepenuhnya secara independen.
Spike0xff
Situasinya sama pada setiap OS lainnya: direktori saat ini kadang-kadang sama dengan direktori yang dapat dieksekusi secara kebetulan, tetapi bisa sangat berbeda.
Lassi
-10

Path nilai absolut dari sebuah program adalah di PWD dari envp fungsi utama Anda, juga ada fungsi di C yang disebut getenv, jadi begitulah.

dacres
sumber