web-dev-qa-db-ja.com

/ proc / self / exeなしで現在の実行可能ファイルのパスを見つける

Linuxでは、/ proc/self/exeで簡単に実行できるように思えます。しかし、クロスプラットフォームインターフェイスを備えたC/C++で現在のアプリケーションのディレクトリを見つける便利な方法があるかどうかを知りたいと思います。 argv [0]をいじくり回しているプロジェクトを見てきましたが、完全に信頼できるとは思えません。

たとえば、/ proc /がないMac OS Xをサポートする必要があった場合、どうしますか? #ifdefsを使用して、プラットフォーム固有のコード(NSBundleなど)を分離しますか?または、argv [0]、$ PATHなどから実行可能ファイルのパスを推測して、Edgeのケースでバグを見つける危険を冒しますか?

179

OS固有のインターフェイス:

移植性のある(ただし信頼性は低い)方法は、argv[0]を使用することです。呼び出し側プログラムによって任意に設定できますが、慣例により、実行可能ファイルのパス名または$PATHを使用して見つかった名前のいずれかに設定されます。

Bashやkshなどの一部のシェル 環境変数 "_"を設定 実行前に実行可能ファイルのフルパスに設定します。その場合、getenv("_")を使用して取得できます。ただし、すべてのシェルがこれを行うわけではないため、これは信頼できません。プログラムを実行する前に、何も設定しないか、親プロセスから変更せずに残すことができます。

338
mark4o

/proc/self/exeの使用は移植性がなく、信頼性がありません。私のUbuntu 12.04システムでは、シンボリックリンクを読む/たどるにはrootでなければなりません。これによりBoostの例が作成され、おそらく投稿されたwhereami()ソリューションは失敗します。

この投稿は非常に長いですが、実際の問題について説明し、テストスイートに対する検証とともに実際に機能するコードを紹介します。

プログラムを見つける最良の方法は、システムが使用するのと同じステップをたどることです。これは、ファイルシステムルート、pwd、パス環境に対して解決されたargv[0]を使用し、シンボリックリンクとパス名の正規化を考慮して行われます。これはメモリからのものですが、私はこれを過去に成功させ、さまざまな状況でテストしました。動作は保証されていませんが、動作しない場合は、おそらくはるかに大きな問題があり、議論されている他の方法よりも全体的に信頼性が高くなります。 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、NeXTSTEPなど。そして、おそらく少し変更を加えて、おそらく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]ベースのメソッドが使用可能で、アクセス許可の問題が発生しない場合は、これらのメソッドの前にこれらを試すこともできます。ややありそうもないイベント(すべてのシステムのすべてのバージョンを検討する場合)が存在し、失敗しない場合、それらはより信頼できる可能性があります。

  • オプション:コマンドラインパラメーターを使用して渡されたパス名を確認します。

  • オプション:ラッパースクリプトによって明示的に渡された環境内のパス名がある場合、それを確認します。

  • オプション:最後の手段として、環境変数「_」を試してください。ユーザーShellなど、別のプログラムを完全に指す場合があります。

  • シンボリックリンクを解決し、複数のレイヤーが存在する場合があります。無限ループの可能性がありますが、ループが存在する場合、おそらくプログラムは呼び出されません。

  • 「/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、Fuseファイルシステム、ハードリンクなど)シェルコマンドが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:// hostname/path/to/file」のように2つの連続したスラッシュを使用します。 (URL)。場合によっては(DOS、windoze)、PATHの区切り文字が異なる場合があります— ";" vs ":" and "\" vs "/"パスセパレーター。 csh/tshには、コロンで区切られた「パス」(スペースで区切られた)と「パス」がありますが、プログラムはパスを受け取る必要があるため、パスについて心配する必要はありません。 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

ユーザー 請求書投稿済み は、argv[0]の3つの基本的なケースを処理するHPのプログラムへのリンクです。ただし、いくつかの変更が必要です。

  • strcat()strcpy()を使用するには、strncat()strncpy()をすべて書き換える必要があります。変数が長さPATHMAXで宣言されている場合でも、長さPATHMAX-1の入力値に連結文字列の長さを加えたものは> PATHMAXであり、長さPATHMAXの入力値は終了しません。
  • 結果を出力するだけでなく、ライブラリ関数として書き直す必要があります。
    • 名前の正規化に失敗します(上記にリンクしたrealpathコードを使用)
    • シンボリックリンクの解決に失敗します(realpathコードを使用)

したがって、HPコードとrealpathコードの両方を組み合わせて、両方がバッファオーバーフローに耐えるように修正する場合、argv[0]を適切に解釈できるものが必要です。

以下に、Ubuntu 12.04で同じプログラムを呼び出すさまざまな方法でのargv[0]の実際の値を示します。そして、はい、プログラムは誤って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()を使用できます。ただし、実際のユーザーよりも早くアクセスできないプログラムをパス上で見つける可能性があります。

20
whitis

Gregory Pakosz(単一のCファイルのみ)のwhereamiライブラリを確認してください。これにより、さまざまなプラットフォーム上の現在の実行可能ファイルへの完全なパスを取得できます。現在、github here でリポジトリとして利用可能です。

11
prideout

Linuxで/proc/self/exeまたはargv[0]のいずれかを使用する代わりに、glibcで使用可能になるELFインタープリターから渡された情報を使用します。

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

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

getauxvalはglibc拡張機能であり、堅牢にするために、NULL(ELFインタープリターがAT_EXECFNパラメーターを提供していないことを示す)を返さないように確認する必要があります。しかし、これが実際にLinuxで問題になるとは思いません。

5
Dolda2000

たとえば、/ proc /がないMac OS Xをサポートする必要があった場合、どうしますか? #ifdefsを使用して、プラットフォーム固有のコード(NSBundleなど)を分離しますか?

はい、プラットフォーム固有のコードを#ifdefsで分離するのが、これを行う従来の方法です。

別のアプローチは、関数宣言を含む#ifdef- lessヘッダーをクリーンにし、実装をプラットフォーム固有のソースファイルに配置することです。たとえば、Poco C++ライブラリが Environment クラスに対して同様の処理を行う方法を確認してください。

3
StackedCrooked

プラットフォーム間でこれを確実に機能させるには、#ifdefステートメントを使用する必要があります。

以下のコードは、Windows、Linux、MacOS、Solaris、またはFreeBSDで実行可能ファイルのパスを検出します(ただし、FreeBSDはテストされていません)。 boost > = 1.55.0を使用してコードを簡素化しますが、必要に応じて簡単に削除できます。 OSおよびコンパイラが必要とする_MSC_VERや__linuxのような定義を使用します。

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

上記のバージョンは、実行可能ファイル名を含む完全なパスを返します。代わりに、実行可能ファイル名のないパス#include boost/filesystem.hpp>が必要な場合、returnステートメントを次のように変更します。

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

Argv [0]を使用して、PATH環境変数を分析できます。見てください: 自分自身を見つけることができるプログラムのサンプル

2
bill

私の知る限り、そのような方法はありません。また、曖昧さもあります。同じ実行可能ファイルに複数のハードリンクが「指し示す」場合、答えは何になりますか? (ハードリンクは実際には「ポイント」しません。are FS階層の別の場所にある同じファイルです。)execve()が新しいバイナリを正常に実行すると、その引数に関するすべての情報は失われます。

1
zvrba

QNX Neutrinoのバージョンに応じて、実行中のプロセスを開始するために使用された実行可能ファイルのフルパスと名前を見つけるためのさまざまな方法があります。プロセス識別子を<PID>として示します。以下を試してください:

  1. ファイル/proc/self/exefileが存在する場合、その内容は要求された情報です。
  2. ファイル/proc/<PID>/exefileが存在する場合、その内容は要求された情報です。
  3. ファイル/proc/self/asが存在する場合:
    1. open()ファイル。
    2. 少なくともsizeof(procfs_debuginfo) + _POSIX_PATH_MAXのバッファを割り当てます。
    3. そのバッファーをdevctl(fd, DCMD_PROC_MAPDEBUG_BASE,...への入力として与えます。
    4. バッファをprocfs_debuginfo*にキャストします。
    5. 要求された情報は、procfs_debuginfo構造体のpathフィールドにあります。 警告:何らかの理由で、QNXはファイルパスの最初のスラッシュ/を省略します。必要に応じて、/を追加します。
    6. クリーンアップ(ファイルを閉じる、バッファーを解放するなど)。
  4. 3.の手順をファイル/proc/<PID>/asで試してください。
  5. dladdr(dlsym(RTLD_DEFAULT, "main"), &dlinfo)を試してください。dlinfoDl_info構造体で、そのdli_fnameには要求された情報が含まれている可能性があります。

これがお役に立てば幸いです。

1

実行可能イメージのパス名を取得する、より移植性の高い方法:

プロセスIDがあれば、psは実行可能ファイルのパスを提供できます。また、psはPOSIXユーティリティなので、移植性が必要です。

したがって、プロセスIDが249297の場合、このコマンドはパス名のみを提供します。

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

引数の説明

-p-指定されたプロセスを選択します

-o comm-コマンド名を表示します(-o cmdはコマンドライン全体を選択します)

--no-heading-見出し行を表示せず、出力のみを表示します。

Cプログラムはpopenを介してこれを実行できます。

0
MichaelMoser