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


190

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

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



私はググった:私を得なさいps -o comm。ここに私を連れてきたのは: "/proc/pid/path/a.out"
basin

IMHOのprideoutの答えは、それが正しく、「クロスプラットフォームのインタフェース」の要件に対処し、非常に簡単に統合することですので、一番上にあることを値します。
ステフェイン・グーリッホン

回答:


348

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

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

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


3
また、_NSGetExecutablePath()はシンボリックリンクをたどらないことに注意してください。
成瀬

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));; それから違うgetexecname()の当量をしている- pargs -x <PID> | grep AT_SUN_EXECNAME...
FrankH。

4
"QDesktopServices :: storageLocation(QDesktopServices :: DataLocation)"これは実行可能ファイルのパスではなく、データを保存するユーザーごとのディレクトリのパス名です。

2
OpenBSDは、2017年にはまだ使用できない唯一のものです。PATHとargv [0]の方法を使用する必要があります
Lothar

27

の使用は、/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()を使用できます。ただし、実際のユーザーよりもパス上で、アクセスできないプログラムを見つける可能性があります。


1
あなたはそれに多くの努力をしました—よくできました。残念ながら、どちらstrncpy()も(特に)strncat()コードで安全に使用されていません。strncpy()null終了を保証するものではありません。ソース文字列がターゲットスペースより長い場合、文字列はnullで終了しません。strncat()使用するのは非常に難しいです。strncat(target, source, sizeof(target))がターゲットよりも長いtarget場合sourceは(最初は空の文字列であっても)誤りです。長さは、末尾のnullを除いてターゲットに安全に追加できる文字数でsizeof(target)-1あり、最大です。
Jonathan Leffler、2015

4
strncpyコードは正しいですが、私が使用する必要があると示唆する方法とは異なります。コードをもっと注意深く読むことをお勧めします。バッファがオーバーフローすることも、バッファが終了しないこともありません。strncpy()/ stncat()の各使用は、有効なsizeof(buffer)をコピーすることによって制限されます。その後、バッファーの最後の文字がゼロで埋められ、バッファーの最後の文字が上書きされます。ただし、strncat()は、サイズパラメータをカウントとして誤って使用し、バッファオーバーフロー攻撃に先行するためにオーバーフローする可能性があります。
whitis

「sudo apt-get install libbsd0 libbsd-dev」、次にs / strncat / strlcat /
whitis

1
PATH_MAXは使用しないでください。これは30年前に機能しなくなりました。常にmallocを使用してください。
Lothar 2017

また、init呼び出しを使用する場合。一部ではなく、initでexeへのパスを完全に解決し、後で呼び出し時にそれを実行します。リゾルバーでrealpathを使用する場合、ここでは遅延評価はできません。他のエラーと一緒に、私は長い答えでスタックオーバーフローで見た最悪のコードを単に示します。
Lothar 2017

13

Gregory Pakoszのwhereamiライブラリー(Cファイルが1つしかない)を調べてください。これにより、さまざまなプラットフォームで現在の実行可能ファイルへの完全なパスを取得できます。現在、githubのリポジトリとしてこちらから入手できます


8

いずれかを使用してのLinux上での代替/proc/self/exeargv[0]ELFインタプリタによって渡された情報を使用しているが、などのglibcで利用可能となります。

#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で実際に問題になることはないと思います。


とにかくglibcがGtk +に含まれているので(私が使用しています)、これが好きです。
Colin Keenan

4

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

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

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


4

これをプラットフォーム間で確実に機能させるには、#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();

@フランク、なぜそう言うのかわからない。私のために働く。/ proc / self / exeにアクセスするにはrootが必要であるとの別の応答を見ましたが、私が試したLinuxシステム(CentOSまたはMint)ではそれが見つかりませんでした。
jtbr 2016

2

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. 要求された情報は構造のpathフィールドにありprocfs_debuginfoます。警告:何らかの理由で、QNXは/ファイルパスの最初のスラッシュを省略することがあります。その前に/必要に応じます。
    6. クリーンアップ(ファイルのクローズ、バッファーの解放など)。
  4. 3.ファイルで手順を試してください/proc/<PID>/as
  5. 要求された情報を含む可能性のある構造がdladdr(dlsym(RTLD_DEFAULT, "main"), &dlinfo)どこにあるか試してください。dlinfoDl_infodli_fname

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


1

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


1
「execve()が新しいバイナリを正常に実行すると、その引数に関するすべての情報が失われます。」実際、argpとenvpの引数は失われず、それらはargv []および環境として渡され、一部のUN * Xでは、パス名引数またはそれから構築されたものは、argpおよびenvp(OS X / iOS、Solaris)、またはmark4oの回答にリストされているメカニズムのいずれかを介して利用可能になります。しかし、そうです、ハードリンクが複数ある場合、それはハードリンクの1つを提供するだけです。

1

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


7
これは、(それが通常のシェルで起動するプログラムで作業一般的でしょうが)、実際に信頼性がないexecvと別々の実行ファイルへのパスを取るキンargv
dmckee元司会者の子猫---

9
これは不正解です。あなたはどこそれはあなたに言うかもしれない可能性を見つけると同じ名前のプログラムを。ただし、現在実行中の実行可能ファイルが実際にどこに存在するかについては何もわかりません。
ラリーグリッツ2013

0

実行可能イメージのパス名を取得するよりポータブルな方法:

psは、プロセスIDを知っている場合、実行可能ファイルのパスを提供します。また、psはPOSIXユーティリティなので、移植可能でなければなりません。

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

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

引数の説明

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

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

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

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


パラメータ付きの完全な起動文字列を提供します。
ETech 2014年

--no-headingは移植不可
Good Person

1
execvの最初の引数が絶対パスでない場合は機能しません。
hroptatyr 2015

-4

Cを使用する場合は、getwd関数を使用できます。

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

これにより、実行可能ファイルの現在のディレクトリである標準出力に出力されます。


3
少なくともWindowsでは、現在の作業ディレクトリは実行中の実行可能ファイルと特に関係がありません。たとえば、CreateProcessは.exeを起動し、その作業ディレクトリを完全に独立して設定できます。
Spike0xff 2016年

他のすべてのOSでも状況は同じです。たまたま、現在のディレクトリが実行可能ディレクトリと同じになることもありますが、まったく異なる場合もあります。
ラッシー

-10

プログラムの絶対値パスは、メイン関数のenvpのPWDにあります。また、Cにはgetenvと呼ばれる関数があるため、それがあります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.