Kullanımı /proc/self/exe
taşı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 --canonicalize
birden 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 execve
olarak 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.