web-dev-qa-db-fra.com

Capture du code d'état de sortie du processus enfant

J'ai une fonction qui bifurque un processus, duplique les descripteurs de fichiers pour les tampons d'entrée et de sortie, puis exécute execl sur une commande passée via une chaîne appelée 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;
}

Chacune des instances cmd traite correctement mes données, tant que mes entrées de test sont correctes.

Lorsque des données incorrectes sont transmises à un processus enfant, mon programme parent s'exécute jusqu'à la fin et se termine avec un code d'état sans erreur de 0.

Si je mets délibérément une mauvaise entrée - pour essayer délibérément de faire échouer l'une des instances de cmd de la manière attendue - j'aimerais savoir comment capturer l'état de sortie de cette cmd afin que je puisse émettre le code d'état d'erreur correct à partir du programme parent, avant la fin.

Comment cela se fait-il généralement?

13
Alex Reynolds

Vous pouvez obtenir l'état de sortie de l'enfant via le premier argument de wait(), ou le deuxième argument de waitpid(), puis en utilisant les macros WIFEXITED et WEXITSTATUS avec.

Par exemple:

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

Un exemple de travail simplifié:

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

Production:

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

waitpid() se bloquera jusqu'à la fin du processus avec l'ID de processus fourni. Puisque vous appelez votre fonction avec un nom popen() et que vous lui passez des tuyaux, il est probable que votre processus enfant ne se termine pas rapidement, ce qui ne serait probablement pas le bon endroit pour le vérifier, si l'appel réussi. Vous pouvez passer WNOHANG comme troisième paramètre à waitpid() pour vérifier si le processus est terminé et pour retourner 0 Si l'enfant n'est pas encore sorti, mais vous devez faites attention à quand vous faites cela, car vous n'avez aucune garantie sur le processus qui s'exécutera quand. Si vous appelez waitpid() avec WNOHANG immédiatement après son retour de c2b_popen4(), il peut retourner 0 Avant que votre processus enfant n'ait eu la chance de s'exécuter et de se terminer avec un code d'erreur, et donnez l'impression que l'exécution a réussi alors qu'elle est sur le point de ne pas réussir.

Si le processus meurt immédiatement, vous aurez des problèmes de lecture et d'écriture dans vos tuyaux, donc une option serait de vérifier waitpid() si vous obtenez une erreur dès la première tentative, de vérifier si la fonction read() ou write() échoue car votre processus enfant est mort. Si cela s'avère vrai, vous pouvez récupérer l'état de sortie et quitter votre programme global.

Il existe d'autres stratégies possibles, y compris la capture du signal SIGCHLD, car cela sera déclenché chaque fois qu'un de vos processus enfants meurt. Il serait correct, par exemple, d'appeler _exit() directement à partir de votre gestionnaire de signaux, après avoir attendu le processus enfant (appeler waitpid() dans un gestionnaire de signaux est également sûr) et obtenir sa sortie statut.

16
Crowman