3

In my C++ application, I am seeing a pclose() that hangs because the pipe's process hung and never exited. Is there anyway I could do something like select() to test whether the pclose() will return because the child process has completed? I'd rather not do a fork() instead of popen() if possible. If fork() is the only solution, are there any examples of using fork() to replace a popen() / pclose() scenario?

WilliamKF
  • 41,123
  • 68
  • 193
  • 295
  • You might need to do something more low-level and set up your own piped file descriptors. Then you can poll on those to see if they're closed, and then kill the child process. – Kerrek SB Dec 19 '14 at 22:41

1 Answers1

1

Probably the easiest way, particularly if you only have one child process, is to catch SIGCHLD and set a flag that the process has terminated and pclose() can be called.

Here's a simple example:

sillyprog.c:

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

int main(void)
{
    printf("This is some data from the child.\n");
    fflush(stdout);
    sleep(5);
    return 0;
}

pc.c:

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

volatile sig_atomic_t child_done = 0;

void handler(int signum)
{
    if ( signum == SIGCHLD ) {
        child_done = 1;
    }
}

int main(void)
{
    /*  Set signal handler  */

    struct sigaction sa;
    sa.sa_handler = handler;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    if ( sigaction(SIGCHLD, &sa, NULL) == -1 ) {
        perror("couldn't set signal handler");
        return EXIT_FAILURE;
    }

    /*  Open pipe  */

    FILE * fp = popen("./sillyprog", "r");
    if ( !fp ) {
        fprintf(stderr, "Couldn't open pipe\n");
        return EXIT_FAILURE;
    }

    /*  Get a line from pipe  */

    char buffer[100];
    if ( !fgets(buffer, 100, fp) ) {
        fprintf(stderr, "Error calling fgets()\n");
        return EXIT_FAILURE;
    }

    const size_t len = strlen(buffer);
    if ( len && buffer[len - 1] == '\n' ) {
        buffer[len - 1] = 0;
    }
    printf("Got '%s' from pipe.\n", buffer);

    /*  Wait for child to finish  */

    while ( !child_done ) {
        printf("Child not ready, waiting...\n");
        sleep(1);
    }

    /*  Close pipe  */

    if ( pclose(fp) == -1 ) {
        fprintf(stderr, "Error calling pclose()\n");
        return EXIT_FAILURE;
    }
    else {
        printf("pclose() successfully called.\n");
    }

    return 0;
}

which outputs:

paul@horus:~/src/sandbox$ ./pc
Got 'This is some data from the child.' from pipe.
Child not ready, waiting...
Child not ready, waiting...
Child not ready, waiting...
Child not ready, waiting...
Child not ready, waiting...
pclose() successfully called.
paul@horus:~/src/sandbox$ 
Crowman
  • 25,242
  • 5
  • 48
  • 56
  • If you have more than one child pipe, how can you determine which child pipe caused the SIGPIPE? – WilliamKF Dec 20 '14 at 20:33
  • @WilliamKF: `SIGCHLD`, and you can't, with this method. What you can do is increment `child_done` each time rather than setting it to one, and wait until it equals your total number of pipes before calling `pclose()` on any of them. It would help if you went into some more detail in your question about what you're actually trying to achieve, here - i.e. if you do determine that a specific child is hanging and that calling `pclose()` would hang indefinitely, what do you want to do about it? Not calling `popen()` on a process that might hang is likely the overall best solution. – Crowman Dec 20 '14 at 20:48