Geçerli yürütülebilir dosyanın yolunu / proc / self / exe olmadan bulma


190

Bana öyle geliyor ki Linux / proc / self / exe ile kolay. Ancak mevcut uygulamanın dizinini C / C ++ 'da platformlar arası arayüzlerle bulmanın uygun bir yolu olup olmadığını bilmek istiyorum. Argv [0] ile dalgalanan bazı projeler gördüm, ancak tamamen güvenilir görünmüyor.

Diyelim ki / proc / içermeyen Mac OS X'i desteklemek zorunda olsaydınız, ne yapardınız? Platforma özgü kodu (örneğin NSBundle) izole etmek için #ifdefs kullanın? Ya da çalıştırılabilir dosyanın yolunu argv [0], $ PATH ve ne olursa olsun, uç durumlarda hata bulma riskiyle mi karşılaşmaya çalışıyorsunuz?



Ben googled: benim olsun ps -o comm. Beni buraya getiren şey: "/proc/pid/path/a.out"
havza

IMHO prideout'un cevabı en üstte olmayı hak ediyor, çünkü "platformlar arası arayüzler" gereksinimini doğru bir şekilde karşılıyor ve entegrasyonu çok kolay.
Stéphane Gourichon

Yanıtlar:


348

İşletim sistemine özgü bazı arayüzler:

Taşınabilir (ancak daha az güvenilir) yöntem kullanmaktır argv[0]. Çağıran program tarafından herhangi bir şeye ayarlanabilse de, kural olarak, çalıştırılabilir dosyanın yol adı veya kullanılarak bulunan bir ad olarak ayarlanır $PATH.

Bash ve ksh dahil olmak üzere bazı kabuklar, " _" ortam değişkenini yürütülmeden önce yürütülebilir dosyanın tam yoluna ayarlar . Bu durumda bunu elde getenv("_")etmek için kullanabilirsiniz . Ancak bu güvenilir değildir, çünkü tüm kabuklar bunu yapmaz ve herhangi bir şeye ayarlanabilir veya programınızı yürütmeden önce değiştirmeyen bir ana işlemden çıkarılabilir.


3
Ayrıca _NSGetExecutablePath () sembolik simgeleri takip etmez.
naruse

1
NetBSD: readlink / proc / curproc / exe DragonFly BSD: readlink / proc / curproc / dosya
naruse

6
Solaris char exepath[MAXPATHLEN]; sprintf(exepath, "/proc/%d/path/a.out", getpid()); readlink(exepath, exepath, sizeof(exepath));:; o farklıdır getexecname()ait eşdeğeri yok ki - pargs -x <PID> | grep AT_SUN_EXECNAME...
FrankH.

4
"QDesktopServices :: storageLocation (QDesktopServices :: DataLocation)" Bu yürütülebilir dosyanın yolu değil, verilerin depolanması gereken kullanıcı başına dizinin yol adıdır.

2
OpenBSD, 2017'de hala yapamayacağınız tek kişi. PATH ve argv [0] yolunu kullanmanız gerekiyor
Lothar

27

Kullanımı /proc/self/exetaşınabilir değildir ve güvenilir değildir. Ubuntu 12.04 sistemimde, symlink'i okumak / takip etmek için root olmanız gerekir. Bu Boost örneğini yapar ve muhtemelen whereami()gönderilen çözümler başarısız olur.

Bu yazı çok uzun ama asıl sorunları tartışıyor ve aslında bir test takımına karşı doğrulama ile birlikte çalışan kodu sunuyor.

Programınızı bulmanın en iyi yolu, sistemin kullandığı adımları izlemektir. Bu, argv[0]dosya sistemi köküne, pwd'ye, yol ortamına karşı çözümlenmiş ve sembolik bağlar ve yol adı standartlaştırma dikkate alınarak yapılır. Bu bellekten geliyor ama bunu geçmişte başarıyla yaptım ve çeşitli durumlarda test ettim. Çalışması garanti edilmez, ancak eğer değilse muhtemelen çok daha büyük sorunlarınız vardır ve genel olarak tartışılan diğer yöntemlerden daha güvenilirdir. Unix uyumlu bir sistemde,argv[0]sizi programınıza götürmez, ancak daha sonra sertifikaları kesilmiş bir ortamda yürütüyorsunuz. Ayrıca 1970 yılından bu yana tüm Unix türevli sistemlere ve hatta temelde libc () standart işlevselliğine ve standart komut satırı işlevine dayandığı için bazı Unix türevi olmayan sistemlere de taşınabilir. Linux (tüm sürümler), Android, Chrome OS, Minix, orijinal Bell Labs Unix, FreeBSD, NetBSD, OpenBSD, BSD xx, SunOS, Solaris, SYSV, HPUX, Concentrix, SCO, Darwin, AIX, OS X, Sonraki adım, vb. Ve küçük bir değişiklikle muhtemelen VMS, VM / CMS, DOS / Windows, ReactOS, OS / 2, vb. Bir program doğrudan bir GUI ortamından başlatılırsa, argv[0]mutlak bir yola ayarlanmış olmalıdır .

Şimdiye kadar piyasaya sürülen her Unix uyumlu işletim sistemindeki hemen hemen her kabuğun programları aynı şekilde bulduğunu ve işletim ortamını neredeyse aynı şekilde ayarladığını (bazı isteğe bağlı ekstralarla) anlayın. Ve bir programı başlatan herhangi bir programın, o program için bir kabuktan çalıştırılmış gibi, bazı isteğe bağlı ekstralarla aynı ortamı (argv, ortam dizeleri, vb.) Yaratması beklenir. Bir program veya kullanıcı, başlattığı diğer alt programlar için bu kuraldan sapan bir ortam ayarlayabilir, ancak bu bir hatadır ve programın alt programın veya alt programlarının düzgün çalışacağı konusunda makul bir beklentisi yoktur.

Olası değerler argv[0]:

  • /path/to/executable - kesin yol
  • ../bin/executable - pwd'ye göre
  • bin/executable - pwd'ye göre
  • ./foo - pwd'ye göre
  • executable - basename, yol içinde bul
  • bin//executable - pwd'ye göre, kanonik olmayan
  • src/../bin/executable - pwd'ye göre, kanonik olmayan, geri izleme
  • bin/./echoargc - pwd'ye göre, kanonik olmayan

Görmemeniz gereken değerler:

  • ~/bin/executable - programınız çalışmadan önce yeniden yazılmıştır.
  • ~user/bin/executable - programınız çalışmadan önce yeniden yazılmıştır
  • alias - programınız çalışmadan önce yeniden yazılmıştır
  • $shellvariable - programınız çalışmadan önce yeniden yazılmıştır
  • *foo* - joker karakter, programınız çalıştırılmadan önce yeniden yazılmış, çok kullanışlı değil
  • ?foo? - joker karakter, programınız çalıştırılmadan önce yeniden yazılmış, çok kullanışlı değil

Ek olarak, bunlar kanonik olmayan yol adları ve birden çok sembolik bağ katmanı içerebilir. Bazı durumlarda, aynı programa birden fazla sabit bağlantı olabilir. Örneğin, /bin/ls, /bin/ps, /bin/chmod, /bin/rm, vb zor bağlantıları olabilir /bin/busybox.

Kendinizi bulmak için aşağıdaki adımları izleyin:

  • Daha sonra değişebilecekleri için pwd, PATH ve argv [0] 'ı programınıza girişte (veya kütüphanenizin başlatılmasında) kaydedin.

  • İsteğe bağlı: özellikle Unix olmayan sistemler için, varsa ana bilgisayar / kullanıcı / sürücü önek parçasını ayırın, ancak atmayın; genellikle iki nokta üst üste işaretinden önce gelen veya bir başlangıç ​​"//" karakterini takip eden kısım.

  • Eğer argv[0]mutlak yolu, bir başlangıç noktası olarak bunu kullanın. Mutlak bir yol muhtemelen "/" ile başlar, ancak Unix olmayan bazı sistemlerde "\" veya bir sürücü harfi veya ad öneki ve ardından iki nokta üst üste işareti ile başlayabilir.

  • Başka argv[0]bir göreli yolsa ("/" veya "\" içerir ancak "../../bin/foo" gibi bir yolla başlamazsa, pwd + "/" + argv [0] (kullanın programın başladığı andan itibaren geçerli çalışma dizini, geçerli değil).

  • Eğer argv [0] düz bir taban adı ise (eğik çizgi yok), o zaman bunu PATH ortam değişkenindeki her bir girişle birleştirin ve bunları deneyin ve başarılı olanı kullanın.

  • İsteğe bağlı: Else çok platformu özgü deneyin /proc/self/exe, /proc/curproc/file(BSD) ve (char *)getauxval(AT_EXECFN)ve dlgetname(...)eğer varsa. argv[0]Mevcutsa ve izin sorunlarıyla karşılaşmıyorsanız, bu temelli yöntemleri de deneyebilirsiniz . Biraz olası olmayan bir olayda (tüm sistemlerin tüm sürümlerini düşündüğünüzde) mevcut olup başarısız olmazlar, daha yetkili olabilirler.

  • İsteğe bağlı: bir komut satırı parametresi kullanarak geçirilen bir yol adı olup olmadığını kontrol edin.

  • İsteğe bağlı: varsa, sarmalayıcı komut dosyanız tarafından açıkça iletilen ortamdaki bir yol adını kontrol edin.

  • İsteğe bağlı: Son çare olarak "_" ortam değişkenini deneyin. Kullanıcı kabuğu gibi tamamen farklı bir programa işaret edebilir.

  • Sembolik bağlantıları çözün, birden fazla katman olabilir. Sonsuz döngülerin olasılığı vardır, ancak eğer varsa, programınız muhtemelen çağrılmaz.

  • "/Foo/../bar/" gibi alt dizeleri "/ bar /" olarak çözerek dosya adını canlandırın. Bir ağ bağlama noktasını geçerseniz bunun potansiyel olarak anlamını değiştirebileceğini unutmayın, bu nedenle kanonizasyon her zaman iyi bir şey değildir. Bir ağ sunucusunda, istemcinin yerine sunucu bağlamında başka bir dosyaya giden bir yoldan geçmek için symlink'teki ".." kullanılabilir. Bu durumda, muhtemelen istemci bağlamını istersiniz, böylece standartlaştırma tamamdır. Ayrıca "/./" gibi desenleri "/" ve "//" gibi "/" biçimine dönüştürün. Kabukta, readlink --canonicalizebirden fazla sembolik sorunu çözer ve adı standartlaştırır. Chase benzer şeyler yapabilir, ancak yüklenmez. realpath()veya canonicalize_file_name()varsa, yardımcı olabilir.

Eğer realpath()derleme sırasında yok, bir permissively lisanslı kütüphane dağılımından bir kopyasını ödünç ve kendine oldukça tekerleği yeniden icat daha derlemek olabilir. PATH_MAX değerinden daha az bir tampon kullanacaksanız potansiyel tampon taşmasını düzeltin (çıkış tampon belleğinden geçirin, strncpy () vs strcpy ()). Yeniden adlandırılmış özel bir kopyayı kullanmak, varsa sınamak yerine kullanmak daha kolay olabilir. Android / darwin / bsd'den izin verilen lisans kopyası: https://android.googlesource.com/platform/bionic/+/f077784/libc/upstream-freebsd/lib/libc/stdlib/realpath.c

Birden fazla denemenin başarılı veya kısmen başarılı olabileceğini ve hepsinin aynı yürütülebilir dosyayı göstermeyebileceğini unutmayın; bu nedenle yürütülebilir dosyanızı doğrulamayı düşünün; ancak, okuma izniniz olmayabilir - okuyamıyorsanız, bunu başarısız olarak kabul etmeyin. Veya bulmaya çalıştığınız "../lib/" dizini gibi yürütülebilir programınıza yakın bir şeyi doğrulayın. Birden çok sürümünüz, paketlenmiş ve yerel olarak derlenmiş sürümleriniz, yerel ve ağ sürümleriniz, yerel ve USB sürücülü taşınabilir sürümleriniz vb. Olabilir ve farklı konumlandırma yöntemlerinden iki uyumsuz sonuç alma olasılığınız düşüktür. Ve "_" basitçe yanlış programa işaret edebilir.

Bunu kullanan bir program , programı yüklemek ve PATH, "_", pwd, vb. Bozmak için kullanılan gerçek yolla kasıtsız execveolarak ayarlanabilse argv[0]de, genellikle bunun için çok fazla neden yoktur; ancak, yürütme ortamınızın bunlarla sınırlı olmamak üzere (krom, sigorta dosya sistemi, sabit bağlantılar, vb.) kabuk komutlarının PATH ayarlamasına izin vermez, ancak dışa aktarılmaz.

Unix olmayan sistemleri kodlamanız gerekmez, ancak bazı özelliklerin farkında olmak iyi bir fikir olacaktır, böylece kodu daha sonra birisinin bağlantı kurması zor olmayacak şekilde yazabilirsiniz. . Bazı sistemlerde (DEC VMS, DOS, URL'ler vb.) Sürücü adları veya "C: \", "sys $ drive: [foo] bar" ve "dosya gibi iki nokta üst üste işareti ile biten diğer önekler olabileceğini unutmayın. : /// foo / bar / baz". Eski DEC VMS sistemleri, programınızın bir POSIX ortamında derlenmişse bu durum değişmiş olsa da, yolun dizin kısmını kapsamak için "[" ve ​​"]" kullanır. VMS gibi bazı sistemlerin bir dosya sürümü olabilir (sonunda noktalı virgülle ayrılmış). Bazı sistemler "// drive / path / to / file" veya "user @ host: / path / to / file" (scp komutu) veya "dosyasında olduğu gibi art arda iki eğik çizgi kullanır: (boşluklarla ayrılmış) ve "PATH" sütunlarla ayrılmış ancak programınız PATH almalıdır, böylece yol hakkında endişelenmenize gerek yoktur. DOS ve diğer bazı sistemler, bir sürücü önekiyle başlayan göreli yollara sahip olabilir. C: foo.exe, C sürücüsündeki geçerli dizindeki foo.exe'yi ifade eder, bu nedenle C: 'daki geçerli dizine bakmanız ve bunu pwd için kullanmanız gerekir. (boşluklarla ayrılmış) ve "PATH" sütunlarla ayrılmış ancak programınız PATH almalıdır, böylece yol hakkında endişelenmenize gerek yoktur. DOS ve diğer bazı sistemler, bir sürücü önekiyle başlayan göreli yollara sahip olabilir. C: foo.exe, C sürücüsündeki geçerli dizindeki foo.exe'yi ifade eder, bu nedenle C: 'daki geçerli dizine bakmanız ve bunu pwd için kullanmanız gerekir.

Sistemimdeki sembolik bağların ve paketleyicilerin bir örneği:

/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

Kullanıcı faturasının yukarıdaki üç HP vakasını ele alan bir programa bağlantı gönderdiğini unutmayın argv[0]. Yine de bazı değişikliklere ihtiyacı var:

  • Her şey yeniden yazmak gerekli olacaktır strcat()ve strcpy()kullanımı strncat()ve strncpy(). Değişkenler PATHMAX uzunluğu olarak bildirilse de, PATHMAX-1 uzunluğunun bir giriş değeri artı birleştirilmiş dizelerin uzunluğu> PATHMAX ve PATHMAX uzunluğunun bir giriş değeri belirsizdir.
  • Yalnızca sonuçları yazdırmak yerine bir kütüphane işlevi olarak yeniden yazılması gerekir.
    • Adları kurallı hale getiremez (yukarıda bağlandığım realpath kodunu kullanın)
    • Sembolik bağlantıları çözemez (realpath kodunu kullanın)

Bu nedenle, hem HP kodunu hem de realpath kodunu birleştirir ve her ikisini de arabellek taşmalarına dayanıklı olacak şekilde düzeltirseniz, doğru şekilde yorumlayabilecek bir şeyiniz olmalıdır argv[0].

Aşağıda argv[0], Ubuntu 12.04'te aynı programı çağırmanın çeşitli yollarının gerçek değerleri gösterilmektedir . Ve evet, program yanlışlıkla echoargv yerine echoargc olarak adlandırıldı. Bu, temiz kopyalama için bir komut dosyası kullanılarak yapıldı, ancak kabukta el ile yapılması aynı sonuçları alır (ancak siz açıkça etkinleştirmedikçe takma adlar komut dosyasında çalışmaz).

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

Bu örnekler, bu yayında açıklanan tekniklerin çok çeşitli koşullarda çalışması gerektiğini ve bazı adımların neden gerekli olduğunu göstermektedir.

EDIT: Şimdi, argv [0] yazdıran program kendini bulmak için güncellendi.

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

Ve işte, önceki testlerin her birinde aslında kendini bulduğunu gösteren çıktı.

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"

Yukarıda açıklanan iki GUI başlatması da programı doğru bir şekilde bulur.

Bir potansiyel tuzak var. access()Program testten önce setuid ise fonksiyon izinleri düşer. Programın yükseltilmiş bir kullanıcı olarak bulunabileceği, ancak normal bir kullanıcı olarak bulunmadığı bir durum varsa, programın bu koşullar altında gerçekten yürütülmesi olası olmasa da, bu testlerin başarısız olacağı bir durum olabilir. Bunun yerine euidaccess () kullanılabilir. Bununla birlikte, yol üzerinde gerçek kullanıcının erişemeyeceğinden daha önce erişilemeyen bir program bulabilir.


1
Bunun için çok çaba harcadınız - aferin. Ne yazık ki, kodda ne strncpy()de (özellikle) strncat()güvenle kullanılmaz. strncpy()geçersiz sonlandırmayı garanti etmez; kaynak dize hedef alandan daha uzunsa, dize boş sonlandırılmaz. strncat()kullanımı çok zordur; hedeften daha uzunsa ( başlangıç ​​için boş bir dize strncat(target, source, sizeof(target))olsa bile) yanlıştır . Uzunluk, sondaki boş değer hariç, hedefe güvenli bir şekilde eklenebilecek karakter sayısıdır . targetsourcesizeof(target)-1
Jonathan Leffler

4
Strncpy kodu doğru, ben kullanmanız gerektiğini ima yöntem aksine. Kodu daha dikkatli okumanızı tavsiye ederim. Ne tamponları taşmaz ne de onları sonlandırmaz. Strncpy () / stncat () öğesinin her kullanımı, geçerli olan sizeof (buffer) öğesinin kopyalanmasıyla sınırlandırılır ve daha sonra buffer'ın son karakteri, buffer'ın son karakterinin üzerine sıfır ile doldurulur. Bununla birlikte strncat (), boyut parametresini yanlış bir sayı olarak kullanır ve arabellek taşma saldırılarından önce gelmesi nedeniyle taşabilir.
whitis

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

1
PATH_MAX kullanmayın. Bu 30 yıl önce çalışmayı durdurdu, her zaman malloc kullanın.
Lothar

Ayrıca bir init çağrısı kullanıyorsanız. Exe yolunu init üzerinde sadece bir parçası değil, tamamen çözün ve daha sonra çağrı üzerine yapın. Çözümleyicide realpath kullanıyorsanız, burada tembel bir değerlendirme mümkün değildir. Diğer erorrs ile birlikte uzun bir cevapta stackoverflow üzerinde gördüğüm en kötü kod.
Lothar

13

Gregory Pakosz'dan (sadece tek bir C dosyasına sahip olan) whereeami kütüphanesine göz atın ; çeşitli platformlarda geçerli yürütülebilir dosyanın tam yolunu almanızı sağlar. Şu anda, burada github üzerinde bir repo olarak kullanılabilir .


8

Linux'ta alternatif olarak glibc tarafından sağlanan ELF yorumlayıcısı tarafından aktarılan bilgileri ya kullanmak ya /proc/self/exeda argv[0]kullanmak:

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

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

Bunun getauxvalbir glibc uzantısı olduğunu ve sağlam olması için NULL(ELF yorumlayıcısının AT_EXECFNparametreyi sağlamadığını belirten) geri dönmediğini kontrol etmelisiniz , ancak bunun aslında Linux'ta bir sorun olduğunu düşünmüyorum.


Bu basit ve glibc zaten Gtk + (ki ben kullanıyorum) dahil olduğu için bunu seviyorum.
Colin Keenan

4

Diyelim ki / proc / içermeyen Mac OS X'i desteklemek zorunda olsaydınız, ne yapardınız? Platforma özgü kodu (örneğin NSBundle) izole etmek için #ifdefs kullanın?

Evet, platforma özel kodun izole edilmesi #ifdefs, bunun geleneksel yoludur.

Başka bir yaklaşım #ifdef, işlev bildirimleri içeren ve uygulamaları platforma özel kaynak dosyalarına yerleştiren temiz bir başlığa sahip olmaktır. Örneğin, Poco C ++ kitaplığının kendi Ortam sınıfı için nasıl benzer bir şey yaptığını kontrol edin .


4

Bu çalışmayı platformlar arasında güvenilir bir şekilde yapmak için #ifdef ifadelerinin kullanılması gerekir.

Aşağıdaki kod, yürütülebilir dosyanın Windows, Linux, MacOS, Solaris veya FreeBSD'de (FreeBSD test edilmemiş olmasına rağmen) yolunu bulur. Kodu basitleştirmek için boost > = 1.55.0 kullanır , ancak isterseniz kaldırmak kolaydır. İşletim sistemi ve derleyicinin gerektirdiği gibi _MSC_VER ve __linux gibi tanımlamaları kullanmanız yeterlidir.

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

Yukarıdaki sürüm, yürütülebilir ad da dahil olmak üzere tam yollar döndürür. Bunun yerine, yürütülebilir ad olmadan yolu istiyorsanız #include boost/filesystem.hpp>ve return deyimini şu şekilde değiştirin:

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

@Frank, bunu neden söylediğinden emin değilim. Benim için çalışıyor. / Proc / self / exe sitesine erişmek için root'a ihtiyacınız olduğunu iddia eden başka bir yanıt gördüm, ancak denediğim herhangi bir Linux sisteminde (CentOS veya Mint) bulamadım.
jtbr

2

QNX Neutrino'nun sürümüne bağlı olarak , çalışan işlemi başlatmak için kullanılan yürütülebilir dosyanın tam yolunu ve adını bulmanın farklı yolları vardır. İşlem tanımlayıcısını belirtiyorum <PID>. Takip etmeyi dene:

  1. Dosya /proc/self/exefilevarsa, içeriği istenen bilgidir.
  2. Dosya /proc/<PID>/exefilevarsa, içeriği istenen bilgidir.
  3. Dosya /proc/self/asvarsa, o zaman:
    1. open() dosya.
    2. En azından bir tamponu atayın sizeof(procfs_debuginfo) + _POSIX_PATH_MAX.
    3. Bu tamponu girdi olarak verin devctl(fd, DCMD_PROC_MAPDEBUG_BASE,....
    4. Tamponu bir procfs_debuginfo* .
    5. İstenen bilgiler yapı pathalanındadır procfs_debuginfo. Uyarı : Bazı nedenlerden dolayı, QNX bazen /dosya yolunun ilk eğik çizgisini atlar . Gerektiğinde bunu ekleyin /.
    6. Temizleyin (dosyayı kapatın, arabelleği boşaltın vb.).
  4. Prosedürü 3.dosyayla birlikte deneyin /proc/<PID>/as.
  5. İstenen bilgileri içerebilecek bir yapının dladdr(dlsym(RTLD_DEFAULT, "main"), &dlinfo)nerede dlinfoolduğunu deneyin .Dl_infodli_fname

Umarım bu yardımcı olur.


1

AFAIK, böyle bir yol yok. Ve aynı zamanda bir ambiyans var: Eğer aynı yürütülebilir dosya "işaret" birden fazla sabit bağlantıları varsa cevap olarak ne almak istersiniz? (Sabit bağlantılar onlar, aslında "nokta" yok olan sadece FS hiyerarşisinde başka yerde, aynı dosya.) Execve kez () başarıyla Yeni bir ikili yürütür, bağımsız değişkenler hakkında tüm bilgileri kaybolur.


1
"Execve () başarıyla yeni bir ikili dosya çalıştırdığında, argümanlarıyla ilgili tüm bilgiler kaybolur." Aslında, argp ve envp argümanları kaybolmaz, argv [] ve ortam olarak geçirilir ve bazı UN * Xes'te yol adı argümanı veya ondan oluşturulan bir şey ya argp ve envp (OS X) ile birlikte geçirilir / iOS, Solaris) veya mark4o'nun cevabında listelenen mekanizmalardan biri aracılığıyla kullanıma sunuldu. Ancak, evet, birden fazla varsa, bu sadece size zor bağlantılardan birini verir.

1

Argv [0] kullanabilir ve PATH ortam değişkenini analiz edebilirsiniz. Şuna bak: Kendini bulabilen bir program örneği


7
Bu aslında güvenilir değildir (gerçi o olacak zamanki kabukları tarafından başlatılan programlarla genellikle iş), çünkü execvve ayrı ayrı çalıştırılabilir yolunu almak kinargv
--- eski moderatör yavru dmckee

9
Bu yanlış bir cevap. Eğer nerede söyleyebilir olabilir bulmak bir aynı adla programı. Ancak şu anda çalışan yürütülebilir dosyanın gerçekte nerede yaşadığına dair bir şey söylemez.
Larry Gritz

0

Yürütülebilir resmin yol adını almanın daha taşınabilir yolu:

ps, işlem kimliğine sahip olduğunuzda size çalıştırılabilir dosyanın yolunu verebilir. Ayrıca ps bir POSIX yardımcı programıdır, bu yüzden taşınabilir olmalıdır

yani işlem kimliği 249297 ise, bu komut size yalnızca yol adını verir.

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

Argümanların açıklaması

-p - verilen işlemi seçer

-o comm - komut adını görüntüler (-o cmd tüm komut satırını seçer)

--no-heading - sadece bir çıkış satırı göstermez.

AC programı bunu popen ile çalıştırabilir.


Params ile tam başlatma dizesi verir.
ETech

no-başlığı olmayan taşınabilir
İyi Kişi

1
ilk execv argümanı mutlak bir yol değilse çalışmaz.
hroptatyr

-4

C kullanıyorsanız getwd işlevini kullanabilirsiniz:

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

Bu, yürütülebilir dosyanın geçerli dizini olan standart çıktıya yazdıracaktır.


3
en azından Windows'da, geçerli çalışma dizininin çalışan yürütülebilir dosyayla belirli bir ilişkisi yoktur. Örneğin, CreateProcess bir .exe başlatabilir ve çalışma dizinini tamamen bağımsız olarak ayarlayabilir.
Spike0xff

Durum diğer tüm işletim sistemlerinde aynıdır: geçerli dizin bazen rastlantısal olarak yürütülebilir dizinle aynıdır, ancak tamamen farklı olabilir.
Lassi

-10

Bir programın mutlak değer yolu, ana işlevinizin envp'sinin PWD'sindedir, ayrıca C'de getenv adlı bir işlev vardır, bu yüzden orada.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.