C Detect fault in popen child process

2019-07-15 09:02发布

I'm using popen to read the output of a third party program. I'd like to detect and restart things if the sub-program fails.

How would I do that? If the child dies, the process didn't exit normally and therefore can't use WEXITSTATUS to check.

Is there another way?

Here's a simple example:

PINGER.C

#include <string.h>
#include <stdio.h>
#include <unistd.h>

int
main(int argc, char **argv)
{
    int i = 0;
    while (1)
    {
        //fprintf(stderr, "StdErr %d\n", i);
        printf("stdout %d\n", i++);
        fflush(stdout);

        if (i == 5)
            i = i / 0; // Die a horrible death

        sleep(1);
    }
}

WATCHER.C

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>

int
main(int argc, char **argv)
{
    char *cmd = "./pinger";
    printf("Running '%s'\n", cmd);

    FILE *fp = popen(cmd, "r");
    if (!fp)
    {
        perror("popen failed:");
        exit(1);
    }

    char inLine[1024];
    int  bytesRead = 0;
    int  i = 0;

    while (fgets(inLine, sizeof(inLine), fp) != NULL)
    {
        int len = strlen(inLine);
        if (inLine[len-1] == '\n')
            inLine[len-1] = '\0';

        printf("Received: '%s'\n", inLine);
    }

    printf("feof=%d ferror=%d: %s\n", feof(fp), ferror(fp), strerror(errno));

    int rc = pclose(fp);
    printf("pclose returned: %d. IFEXITED=%d\n", rc, WIFEXITED(rc));
}

Here's the output:

$ ./popenTest
calling popen
Running './pinger'
pipe open
Received: 'stdout 0'
Received: 'stdout 1'
Received: 'stdout 2'
Received: 'stdout 3'
Received: 'stdout 4'
feof=1 ferror=0: Success
pclose returned: 8. IFEXITED=0 EXITSTATUS=0

(According to this post, you actually can't use WEXITSTATUS if the command didn't exit normally, but I tried anyway)

标签: c popen
2条回答
萌系小妹纸
2楼-- · 2019-07-15 09:48

A process returns an exit status by returning from its main() with an exit code, or by calling _exit(statuscode). If the process is terminated abnormally, for example due to a signal or a fault, the process never gets a chance to do any of those, so there is no exitstatus.

In that case, all you can know is that the process terminates with an error.

If you want to restart on all cases where a program terminates on error, you need to check both WIFEXITED and WEXITSTATUS:

do {
    rc = run_your_child_process();

} while (!WIFEXITED(rc) || WEXITSTATUS(rc) == 0);

// child terminated without error
查看更多
劳资没心,怎么记你
3楼-- · 2019-07-15 09:57

Read the POSIX specification of pclose() (and popen() too). It says:

Return value

Upon successful return, pclose() shall return the termination status of the command language interpreter. Otherwise, pclose() shall return -1 and set errno to indicate the error.

Thus, you can get the exit status of the process indirectly via the return value of pclose(). That will be a number between 0 and 255. Shells often report the 'death of a child by signal' by returning the value 128 + signal_number. The specification outlines circumstances in which the status may not be available (your program called wait() and got the information for the process opened by popen() before you called pclose(), for example). Reading the specification of popen() explains the use of 'the command language interpreter' in the specification of pclose().

查看更多
登录 后发表回答