web-dev-qa-db-ja.com

execが失敗する原因は何ですか?次は何が起こる?

Exec(execl、execlpなど)が失敗する理由は何ですか? execを呼び出して戻ってきた場合、パニックになってexitを呼び出す以外のベストプラクティスはありますか?

25

exec(3)のマニュアルページから

execl()execle()execlp()execvp()、およびexecvP()関数は失敗し、いずれかの関数にerrnoを設定する可能性がありますライブラリ関数execve(2)およびmalloc(3)に指定されたエラー。

execv()関数は失敗し、ライブラリ関数execve(2)に指定されたエラーのいずれかにerrnoを設定する可能性があります。

そして execve(2) man page から:

[〜#〜]エラー[〜#〜]

Execve()は失敗し、次の場合に呼び出しプロセスに戻ります。

  • _[E2BIG]_-新しいプロセスの引数リストのバイト数が、システムによって課された制限を超えています。この制限は、sysctl(3) MIB変数_KERN_ARGMAX_によって指定されます。
  • _[EACCES]_-パスプレフィックスのコンポーネントの検索権限が拒否されました。
  • _[EACCES]_-新しいプロセスファイルは通常のファイルではありません。
  • _[EACCES]_-新しいプロセスファイルモードは実行権限を拒否します。
  • _[EACCES]_-新しいプロセスファイルは、実行が無効になっているマウントされたファイルシステム上にあります(_MNT_NOEXEC_の_<sys/mount.h>_)。
  • _[EFAULT]_-新しいプロセスファイルは、ヘッダーのサイズ値で示される長さではありません。
  • _[EFAULT]_-パス、argv、またはenvpが不正なアドレスを指しています。
  • _[EIO]_-ファイルシステムからの読み取り中にI/Oエラーが発生しました。
  • _[ELOOP]_-パス名の変換中に検出されたシンボリックリンクが多すぎます。これは、ループしているシンボリックリンクを示していると見なされます。
  • _[ENAMETOOLONG]_-パス名のコンポーネントが_{NAME_MAX}_文字を超えたか、パス名全体が_{PATH_MAX}_文字を超えました。
  • _[ENOENT]_-新しいプロセスファイルが存在しません。
  • _[ENOEXEC]_-新しいプロセスファイルには適切なアクセス許可がありますが、形式が認識されません(たとえば、ヘッダーに無効なマジックナンバーがあります)。
  • _[ENOMEM]_-新しいプロセスは、課された最大値(getrlimit(2))で許可されているよりも多くの仮想メモリを必要とします。
  • _[ENOTDIR]_-パスプレフィックスのコンポーネントはディレクトリではありません。
  • _[ETXTBSY]_-新しいプロセスファイルは、あるプロセスによる書き込みまたは読み取りのために現在開かれている純粋なプロシージャ(共有テキスト)ファイルです。

malloc()はそれほど複雑ではなく、ENOMEMのみを使用します。 malloc(3) man page から:

成功した場合、calloc()malloc()realloc()reallocf()、およびvalloc()関数は割り当てられたメモリへのポインタを返します。エラーが発生した場合は、NULLポインターを返し、errnoENOMEMに設定します。

20
Carl Norum

execの失敗を処理する際の問題は、通常execが子プロセスで実行され、親プロセスでエラー処理を実行したいということです。ただし、(1)エラーコードが終了コードに収まるかどうかわからないため、(2)execの失敗を区別できないため、exit(errno)だけを区別することはできません。 execの新しいプログラムからの障害終了コード。

私が知っている最善の解決策は、パイプを使用してexecの成功または失敗を伝えることです。

  1. フォークする前に、親プロセスでパイプを開きます。
  2. 分岐した後、親はパイプの書き込み側を閉じ、読み取り側から読み取ります。
  3. 子は読み取り側を閉じ、書き込み側にclose-on-execフラグを設定します。
  4. 子はexecを呼び出します。
  5. Execが失敗した場合、子はパイプを使用してエラーコードを親に書き戻し、終了します。
  6. Close-on-execが成功したexecパイプの書き込み端を閉じるため、子がexecを正常に実行した場合、親はeof(長さゼロの読み取り)を読み取ります。または、execが失敗した場合、親はエラーコードを読み取り、それに応じて続行できます。いずれにせよ、親は子がexecを呼び出すまでブロックします。
  7. 親はパイプの読み取り端を閉じます。

exec()呼び出しが戻った後に何をするかは、コンテキスト(プログラムが実行することになっていること、エラーは何か、問題を回避するために何ができるか)によって異なります。

問題の原因の1つは、パス名の代わりに単純なプログラム名を指定したことである可能性があります。 execvp()で再試行するか、コマンドを_sh -c 'what you originally specified'_の呼び出しに変換することができます。これらのいずれかが妥当であるかどうかは、アプリケーションによって異なります。重大なセキュリティ問題が関係している場合は、おそらく再試行しないでください。

パス名を指定し、それに問題がある場合(ENOTDIR、ENOENT、EPERM)、適切なフォールバックがない可能性がありますが、エラーを有意義に報告できます。

昔(10年以上前)、一部のシステムは「#!」をサポートしていませんでした。シバン表記。実行可能ファイルとシェルスクリプトのどちらを実行しているかわからない場合は、実行可能ファイルとして試した後、シェルスクリプトとして再試行しました。 Perlスクリプトを実行している場合は機能する場合と機能しない場合がありますが、当時は、Perlスクリプトを作成して、シェルによって実行されていることを検出し、Perlで再実行していました。幸いなことに、当時はほとんど終わりました。

可能な限り、プロセスが問題を報告して追跡できるようにすることが重要です。メッセージをログファイルに書き込むか、stderrに書き込むだけです(または syslog() )、何がうまくいかなかったのかを解明しなければならない人は、不幸なエンドユーザーのレポート「Xを試しましたがうまくいきませんでした」以外に、彼らを助けるためのより多くの情報を持っています。何も機能しない場合は、成功を示すため、終了ステータスが0ではないことが重要です。それでも無視されるかもしれませんが、あなたはできることをしました。

8

ただパニックになるだけでなく、errnoの価値に基づいて決定を下すことができます。

3
user446568

Execは常に成功するはずです。 (シェルを除く、つまりユーザーが偽のコマンドを入力した場合)

Execが失敗した場合は、次のことを示しています。

  • プログラムの「障害」(コンポーネントの欠落または不良、パス名の誤り、メモリの不良など)、または
  • 重大なシステムエラー(メモリ不足、プロセスが多すぎる、ディスク障害など)

重大なエラーの場合、通常のアプローチは、stderrにエラーメッセージを書き込んでから、失敗コードで終了することです。ほとんどすべての標準ツールがこれを行います。 execの場合:

execl("bork", "bork", NULL);
perror("failed: exec");
exit(127);

シェルもそれを行います(多かれ少なかれ)。

通常、子プロセスが失敗した場合、親も失敗しているため、終了する必要があります。子が実行に失敗したか、プログラムの実行中に失敗したかは関係ありません。 execが失敗した場合、execが失敗した理由は関係ありません。子プロセスが何らかの理由で失敗した場合、呼び出しプロセスは問題があり、停止する必要があります。

考えられるすべてのエラー状態を予測しようとして、多くの時間を無駄にしないでください。各エラーコードを可能な限り最善の方法で処理しようとするコードを記述しないでください。コードを膨らませて、多くの新しいバグを導入します。プログラムが壊れているか、悪用されている場合は、単に失敗するはずです。強制的に続行すると、さらに悪いトラブルが発生します。

たとえば、システムのメモリが不足していてスワップがスラッシングしている場合、プロセスを実行しようとして何度も繰り返したくはありません。それは状況を悪化させるだけです。ファイルシステムエラーが発生した場合、そのファイルシステムで実行を継続したくありません。腐敗を悪化させる可能性があります。プログラムが間違ってインストールされた場合、バグがある場合、またはメモリが破損している場合は、壊れたプログラムが実際の損傷(クライアントへの破損したレポートの送信、データベースの破棄など)を行う前に、できるだけ早く停止する必要があります。 ..)。

考えられる代替案の1つ:失敗したプロセスがヘルプを要求し、それ自体を一時停止し(SIGSTOP)、続行するように指示された場合は操作を再試行します。これは、システムのメモリが不足している場合、ディスクがいっぱいの場合、またはプログラムに障害がある場合でも役立つ可能性があります。非常に高価で重要な操作はほとんどないので、これはしばらくの間価値があります。

インタラクティブなGUIプログラムを作成している場合は、再利用可能なコマンドラインツール(問題が発生した場合に終了する)の薄いラッパーとして作成してみてください。プログラム内のすべての関数には、GUI、コマンドライン、および関数呼び出しからアクセスできる必要があります。関数を記述します。関数のコマンドラインラッパーとGUIラッパーを作成するためのツールをいくつか作成します。サブプロセスも使用します。

原子力発電所のコントローラーや津波を予測するプログラムなど、本当に重要なシステムを作成している場合、私の愚かなアドバイスを読んで何をしていますか?重要なシステムは、コンピューターやソフトウェアに完全に依存するべきではありません。それを運転する誰かと一緒に、「手動オーバーライド」が必要です。特に、MS Windowsで重要なシステムを構築しようとしないでください。これは、水中で砂の城を構築するようなものです。

1
Sam Watkins