web-dev-qa-db-ja.com

子プロセスの終了ステータスコードのキャプチャ

プロセスをフォークし、入力および出力バッファーのファイル記述子を複製し、execlという文字列を介して渡されたコマンドに対してcmdを実行する関数があります。

static pid_t
c2b_popen4(const char* cmd, int pin[2], int pout[2], int perr[2], int flags)
{
    pid_t ret = fork();

    if (ret < 0) {
        fprintf(stderr, "fork() failed!\n");
        return ret;
    }
    else if (ret == 0) {
        /*                                                                                                                                                                                                                                                                                                                  
           Assign file descriptors to child pipes (not shown)...                                                                                                                                                                                                                                                                                               
        */
        execl("/bin/sh", "/bin/sh", "-c", cmd, NULL);
        fprintf(stderr, "execl() failed!\n");
        exit(EXIT_FAILURE);
    }
    else {
        /*                                                                                                                                                                                                                                                                                                                  
           Close parent read and write pipes (not shown)...                                                                                                                                                                                                                                                                                              
        */
        return ret;
    }
    return ret;
}

テスト入力が正しい限り、各cmdインスタンスはデータを正しく処理します。

不正なデータが子プロセスに渡されると、親プログラムが実行されて完了し、エラー以外のステータスコード0で終了します。

意図的に悪い入力を入れた場合— cmdインスタンスの1つを意図的に失敗させるために意図的に—そのcmdの終了ステータスをキャプチャする方法を知りたい終了する前に、親プログラムから正しいエラーステータスコードを発行できるようにします。

これは通常どのように行われますか?

13
Alex Reynolds

wait()の最初の引数またはwaitpid()の2番目の引数を介して子の終了ステータスを取得し、次にマクロWIFEXITEDおよびWEXITSTATUSと一緒に。

例えば:

_pid_t ret = c2b_popen4("myprog", pin, pout, perr, 0);

if ( ret > 0 ) {
    int status;

    if ( waitpid(ret, &status, 0) == -1 ) {
        perror("waitpid() failed");
        exit(EXIT_FAILURE);
    }

    if ( WIFEXITED(status) ) {
        int es = WEXITSTATUS(status);
        printf("Exit status was %d\n", es);
    }
}
_

単純化された実際の例:

_failprog.c_:

_int main(void) {
    return 53;
}
_

_shellex.c_:

_#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(void)
{
    pid_t p = fork();
    if ( p == -1 ) {
        perror("fork failed");
        return EXIT_FAILURE;
    }
    else if ( p == 0 ) {
        execl("/bin/sh", "bin/sh", "-c", "./failprog", "NULL");
        return EXIT_FAILURE;
    }

    int status;
    if ( waitpid(p, &status, 0) == -1 ) {
        perror("waitpid failed");
        return EXIT_FAILURE;
    }

    if ( WIFEXITED(status) ) {
        const int es = WEXITSTATUS(status);
        printf("exit status was %d\n", es);
    }

    return EXIT_SUCCESS;
}
_

出力:

_paul@thoth:~/src/sandbox$ ./shellex
exit status was 53
paul@thoth:~/src/sandbox$ 
_

waitpid()は、指定されたプロセスIDのプロセスが終了するまでブロックします。 popen()という名前で関数を呼び出し、それにパイプを渡しているため、おそらく子プロセスがすぐに終了しないため、呼び出しをチェックするのにおそらく適切な場所ではないでしょう。成功した。 WNOHANGを3番目のパラメータとしてwaitpid()に渡し、プロセスが終了したかどうかを確認し、子がまだ終了していない場合は_0_を返すことができますが、 whenについては注意してください。これは、どのプロセスがいつ実行されるかについての保証がないためです。 waitpid()から戻った直後にWNOHANGを指定してc2b_popen4()を呼び出すと、子プロセスが実行して終了する前に_0_が返されることがありますエラーコードを表示し、実行が成功しそうにないときに実行が成功したかのように見せます。

プロセスがすぐに停止した場合、パイプの読み取りと書き込みに問題が発生するため、最初の試行でエラーが発生した場合にwaitpid()を確認し、子プロセスが停止したため、read()またはwrite()が失敗します。それが真であることが判明した場合は、終了ステータスを取得して、プログラム全体を終了できます。

SIGCHLDシグナルをキャッチするなど、他の可能な戦略があります。これは、子プロセスの1つが死ぬと必ず発生するためです。たとえば、子プロセスを待って(シグナルハンドラーで_exit()を呼び出すのも安全です)、その出口を取得した後、シグナルハンドラーからwaitpid()を直接呼び出しても問題ありません。状態。

16
Crowman