の使用は、/proc/self/exe
移植性がなく信頼性がありません。私のUbuntu 12.04システムでは、シンボリックリンクを読んだり、フォローしたりするには、rootである必要があります。これはブーストの例となり、おそらくwhereami()
投稿されソリューションは失敗します。
この投稿は非常に長くなりますが、実際の問題について説明し、実際にテストスイートに対する検証とともに機能するコードを示します。
プログラムを見つける最善の方法は、システムが使用するのと同じ手順をたどることです。これはargv[0]
、ファイルシステムのルート、pwd、パス環境に対して解決され、シンボリックリンクとパス名の正規化を考慮して行われます。これはメモリからのものですが、過去にこれを成功させ、さまざまな状況でテストしました。動作することは保証されていませんが、動作しない場合は、おそらくはるかに大きな問題があり、他のどの方法よりも全体的に信頼性が高くなります。Unix互換システムでは、適切な処理が行われる状況があります。argv[0]
プログラムにアクセスすることはできませんが、その後、認定された環境で実行されます。また、基本的にlibc()標準機能と標準コマンドライン機能に依存しているため、1970年頃からすべてのUnix派生システムや一部の非Unix派生システムにもかなり移植可能です。Linux(すべてのバージョン)、Android、Chrome OS、Minix、オリジナルのBell Labs Unix、FreeBSD、NetBSD、OpenBSD、BSD xx、SunOS、Solaris、SYSV、HPUX、Concentrix、SCO、Darwin、AIX、OS X、次のステップなど。そして少しの変更で、おそらくVMS、VM / CMS、DOS / Windows、ReactOS、OS / 2など。プログラムがGUI環境から直接起動された場合argv[0]
、絶対パスに設定されているはずです。
これまでにリリースされたすべてのUnix互換オペレーティングシステムのほぼすべてのシェルは、基本的に同じ方法でプログラムを検出し、ほぼ同じ方法でオペレーティング環境を設定します(オプションの追加機能もある)。また、プログラムを起動する他のプログラムは、いくつかのオプションの追加機能を使用して、シェルから実行された場合と同じ環境(argv、環境文字列など)を作成することが期待されます。プログラムまたはユーザーは、起動する他の従属プログラムに対してこの規則から逸脱した環境をセットアップできますが、そうであれば、これはバグであり、プログラムは従属プログラムまたはその従属プログラムが正しく機能することを期待できません。
可能な値はargv[0]
次のとおりです。
/path/to/executable
—絶対パス
../bin/executable
— pwdに関連
bin/executable
— pwdに関連
./foo
— pwdに関連
executable
-ベース名、パスで検索
bin//executable
— pwdを基準にして、非正規
src/../bin/executable
— pwdに関連し、非標準的なバックトラック
bin/./echoargc
— pwdを基準にして、非正規
表示されない値:
~/bin/executable
—プログラムの実行前に書き直されました。
~user/bin/executable
—プログラムが実行される前に書き直されます
alias
—プログラムが実行される前に書き直されます
$shellvariable
—プログラムが実行される前に書き直されます
*foo*
—ワイルドカード、プログラムの実行前に書き直され、あまり役に立たない
?foo?
—ワイルドカード、プログラムの実行前に書き直され、あまり役に立たない
さらに、これらには非正規のパス名とシンボリックリンクの複数のレイヤーが含まれる場合があります。場合によっては、同じプログラムへの複数のハードリンクが存在することがあります。たとえば、/bin/ls
、/bin/ps
、/bin/chmod
、/bin/rm
、などへのハードリンクであってもよいです/bin/busybox
。
自分を見つけるには、次の手順に従います。
プログラムへのエントリ(またはライブラリの初期化)でpwd、PATH、およびargv [0]を保存します。これらは後で変更される可能性があるためです。
オプション:特に非UNIXシステムの場合、分離しますが、パス名のホスト/ユーザー/ドライブのプレフィックス部分がある場合は、それを破棄しません。コロンの前または最初の "//"に続く部分。
argv[0]
が絶対パスの場合は、それを出発点として使用します。絶対パスはおそらく「/」で始まりますが、一部の非UNIXシステムでは、「\」またはドライブ文字または名前の接頭辞の後にコロンが続く場合があります。
それ以外の場合argv[0]
は、相対パス(「/」または「\」が含まれているが、「../../ bin / foo」などで始まっていない場合は、pwd + "/" + argv [0](現在ではなく、プログラム開始時の現在の作業ディレクトリ)。
それ以外の場合、argv [0]がプレーンなベース名(スラッシュなし)である場合は、それをPATH環境変数の各エントリと順番に組み合わせ、それらを試し、成功した最初のエントリを使用します。
オプション:そうでなければ非常にプラットフォーム固有のしてみてください/proc/self/exe
、/proc/curproc/file
(BSD)を、そして(char *)getauxval(AT_EXECFN)
、およびdlgetname(...)
存在する場合。これらargv[0]
が使用可能で、権限の問題が発生しない場合は、これらの前のメソッドを試すこともできます。ありそうもないイベント(すべてのシステムのすべてのバージョンを検討する場合)が存在し、失敗しない場合は、より信頼できる可能性があります。
オプション:コマンドラインパラメータを使用して渡されたパス名を確認します。
オプション:ラッパースクリプトによって明示的に渡された環境にパス名があるかどうかを確認します。
オプション:最後の手段として、環境変数「_」を試してください。ユーザーのシェルなど、別のプログラムを完全に指す場合があります。
シンボリックリンクを解決します。複数のレイヤーがある場合があります。無限ループが発生する可能性がありますが、ループが存在する場合、プログラムはおそらく呼び出されません。
「/foo/../bar/」などの部分文字列を「/ bar /」に解決して、ファイル名を正規化します。これにより、ネットワークマウントポイントを越えると意味が変わる可能性があるため、正規化が常に良いとは限りません。ネットワークサーバーでは、シンボリックリンクの「..」を使用して、クライアントではなくサーバーコンテキストで別のファイルへのパスをトラバースできます。この場合、おそらくクライアントコンテキストが必要なので、正規化は問題ありません。また、「/./」を「/」に、「//」を「/」に変換します。シェルでreadlink --canonicalize
は、複数のシンボリックリンクを解決し、名前を正規化します。Chaseも同様の動作をする場合がありますが、インストールされていません。 realpath()
またはcanonicalize_file_name()
、存在する場合は、役立つことがあります。
realpath()
コンパイル時に存在しない場合は、寛容にライセンスされたライブラリ配布からコピーを借用し、ホイールを再発明するのではなく、自分でコンパイルすることができます。PATH_MAX未満のバッファーを使用する場合は、バッファーオーバーフローの可能性を修正します(sizeof出力バッファーを渡す、strncpy()とstrcpy()を比較)。名前が変更されたプライベートコピーを使用する方が、存在するかどうかをテストするよりも簡単な場合があります。android / darwin / bsdからの許可されたライセンスのコピー:https ://android.googlesource.com/platform/bionic/+/f077784/libc/upstream-freebsd/lib/libc/stdlib/realpath.c
複数回の試行が成功または部分的に成功する可能性があり、それらがすべて同じ実行可能ファイルを指すとは限らない可能性があるので、実行可能ファイルの検証を検討してください。ただし、読み取り権限がない可能性があります。読み取り権限がない場合は、失敗として扱わないでください。または、検索しようとしている「../lib/」ディレクトリなど、実行可能ファイルの近くにあるものを確認します。複数のバージョン、パッケージ化されたローカルでコンパイルされたバージョン、ローカルとネットワークのバージョン、ローカルとUSBドライブのポータブルバージョンなどがあり、異なる方法で2つの互換性のない結果が得られる可能性があります。また、「_」は単に間違ったプログラムを指している場合があります。
使用するプログラムexecve
は意図的に設定できますargv[0]
使用するプログラムは、プログラムをロードするために使用される実際のパスと互換性がないように、PATH、 "_"、pwdなどを破損する可能性があります。ただし、実行環境がこの方法(chroot、ヒューズファイルシステム、ハードリンクなど)に限定されずにさまざまな方法で変更される可能性があるという事実を無視する脆弱なコードがある場合、これはセキュリティに影響を与える可能性があります。シェルコマンドがPATHを設定するが、エクスポートに失敗する場合。
必ずしも非UNIXシステム用にコーディングする必要はありませんが、後で移植するのがそれほど難しくない方法でコードを記述できるように、いくつかの特殊性を認識しておくことをお勧めします。 。一部のシステム(DEC VMS、DOS、URLなど)には、「C:\」、「sys $ drive:[foo] bar」、「file :/// foo / bar / baz」。古いDEC VMSシステムでは、「[」と「]」を使用してパスのディレクトリ部分を囲んでいますが、プログラムがPOSIX環境でコンパイルされている場合は変更される可能性があります。VMSなどの一部のシステムには、ファイルバージョン(末尾がセミコロンで区切られている)がある場合があります。一部のシステムでは、 "// drive / path / to / file"または "user @ host:/ path / to / file"(scpコマンド)または "file:(スペースで区切られた)およびコロンで区切られた "PATH"ですが、プログラムはPATHを受け取る必要があるため、パスを気にする必要はありません。DOSおよびその他の一部のシステムでは、ドライブプレフィックスで始まる相対パスを使用できます。C:foo.exeはドライブCの現在のディレクトリにあるfoo.exeを参照するため、C:の現在のディレクトリを検索し、それをpwdに使用する必要があります。(スペースで区切られた)およびコロンで区切られた "PATH"ですが、プログラムはPATHを受け取る必要があるため、パスを気にする必要はありません。DOSおよびその他の一部のシステムでは、ドライブプレフィックスで始まる相対パスを使用できます。C:foo.exeはドライブCの現在のディレクトリにあるfoo.exeを参照するため、C:の現在のディレクトリを検索し、それをpwdに使用する必要があります。
私のシステムのシンボリックリンクとラッパーの例:
/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
ユーザーの請求書 が、上の3つの基本的なケースを処理するHPのプログラムへの上のリンクを投稿したことに注意してくださいargv[0]
。ただし、いくつかの変更が必要です。
- とをすべて書き換えて
strcat()
、strcpy()
を使用する必要がstrncat()
ありstrncpy()
ます。変数が長さPATHMAXで宣言されている場合でも、長さPATHMAX-1と連結文字列の長さの合計の入力値は> PATHMAXであり、長さPATHMAXの入力値は終了しません。
- 結果を出力するだけでなく、ライブラリ関数として書き直す必要があります。
- 名前を正規化できません(上記でリンクしたリアルパスコードを使用します)
- シンボリックリンクの解決に失敗する(realpathコードを使用)
したがって、HPコードとrealpathコードの両方を組み合わせて、バッファオーバーフローに対して耐性を持つように両方を修正する場合、適切に解釈できるものが必要argv[0]
です。
以下はargv[0]
、Ubuntu 12.04で同じプログラムを呼び出すさまざまな方法の実際の値を示しています。そして、はい、プログラムは誤ってechoargvではなくechoargcと名付けられました。これはクリーンコピー用のスクリプトを使用して行われましたが、シェルで手動で実行すると同じ結果になります(ただし、エイリアスを明示的に有効にしない限り、エイリアスはスクリプトで機能しません)。
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
これらの例は、この投稿で説明されている手法が幅広い状況で機能すること、および一部の手順が必要な理由を示しています。
編集:argv [0]を出力するプログラムが更新され、実際に自分自身を見つけるようになりました。
// 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 */
}
そして、これは、以前のすべてのテストで実際に自分自身を見つけたことを示す出力です。
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"
上記の2つのGUI起動でも、プログラムが正しく検出されます。
潜在的な落とし穴が1つあります。access()
プログラムは、試験前にsetuidをされた場合、関数は、権限を削除します。プログラムが管理者ユーザーとして検出され、通常のユーザーとしては検出されない場合、これらのテストは失敗する可能性がありますが、実際にはプログラムがそのような状況で実行される可能性は低いです。代わりにeuidaccess()を使用できます。ただし、実際のユーザーよりもパス上で、アクセスできないプログラムを見つける可能性があります。