0

I have written a small C program which is creating a child process and then running a shell command using popen. I have created a signal handler to wait for the child process to end while the parent process just runs in an infinite loop. When I am calling 'sleep(5)' after the popen system call the sleep call does not work.

Instead if I put the sleep call before the popen then it is working. Also if I remove the signal handler and just wait for the child process in the parent process then there is no problem(i.e. sleep runs properly anywhere).

//Assume all required header files and function prototypes added

int main()
{
    int pid,status;
    signal(SIGCHLD,reaper);
    pid = fork();
    if(pid == 0)
       runCommand();
    while(1);
}

void runCommand()
{
    FILE *pipen;
    char *buffer;

    buffer = (char*) malloc(100);
/*sleep #1*/    sleep(5);
    if ((pipen = popen("uname -a", "r")) == NULL) {
        perror("\nError during shell command execution: ");
        exit(0);
    }
/*sleep #2*/    sleep(5);
    fgets(buffer, 100, pipen);
/*sleep #3*/    sleep(5);
    printf("\n%s\n",buffer);
    exit(0);
}

void reaper(int signal)
{
    int status;
    waitpid(-1,&status,WNOHANG);
}

When I am running the above program sleep #1 works and the process sleeps for the given time. I am getting to know this by observing the time it takes to print the result of the command (by the printf statement). But for sleep #2 and sleep #3 I expect the process to sleep but that does not seem to be happening as the result of the command is printed on the console instantaneously.

The behavior can be observed by keeping only the sleep #2 or sleep #3 call, and removing the other two sleep calls.

Does anyone know why this is happening?

pranav
  • 163
  • 1
  • 3
  • 12
  • How do you know which sleep is being ignored? It only prints once, after everything is done. If it takes less than 15 seconds, you can't tell which one was skipped. – Barmar Apr 15 '19 at 20:48
  • I am commenting two sleep calls and only keeping one at a time while running. – pranav Apr 15 '19 at 20:50
  • A better way would be to put `printf()` calls after each sleep. – Barmar Apr 15 '19 at 20:54
  • When I test it just sleep #2 is ignored. – Barmar Apr 15 '19 at 20:59
  • I added printfs before each sleep and as you suggested, and you are right. It only ignored sleep #2. But then when I removed sleep #1 and sleep #2, it ignored sleep #3. – pranav Apr 15 '19 at 21:05
  • Note that `sleep()` returns the number of seconds for which it didn't successfully sleep. It returns 0, therefore, when it runs its full time; it may return a non-zero amount if it is interrupted (unless perhaps there's less than a second left when it's interrupted). If you had checked the return value (and perhaps printed it when it is non-zero), you would almost certainly have spotted this. – Jonathan Leffler Apr 15 '19 at 22:57

1 Answers1

3

popen() works by forking a child to execute /bin/sh -c "uname -a". When that child exits, the process receives a SIGCHLD signal. This interrupts the sleep() call and runs your reaper function.

I haven't been able to find this mentioned in any Linux or POSIX documentation, but it's in the SunOS documentation:

The signal handler for SIGCHLD should be set to default when using popen(). If the process has established a signal handler for SIGCHLD, it will be called when the command terminates. If the signal handler or another thread in the same process issues a wait(2) call, it will interfere with the return value of pclose(). If the process's signal handler for SIGCHLD has been set to ignore the signal, pclose() will fail and errno will be set to ECHILD.

You should set the SIGCHLD handler back to the default in the child process.

void runCommand()
{
    FILE *pipen;
    char *buffer;

    signal(SIGCHLD, SIG_DFL);

    buffer = (char*) malloc(100);
/*sleep #1*/    sleep(5);
    if ((pipen = popen("uname -a", "r")) == NULL) {
        perror("\nError during shell command execution: ");
        exit(0);
    }
/*sleep #2*/    sleep(5);
    fgets(buffer, 100, pipen);
/*sleep #3*/    sleep(5);
    printf("\n%s\n",buffer);
    exit(0);
}
Barmar
  • 741,623
  • 53
  • 500
  • 612
  • Seems like the child process created by popen exits after the call to popen is complete. So what does the pclose call do? – pranav Apr 28 '19 at 00:04
  • 1
    @pranav It calls `waitpid()` to get the exit status of the process, which it then returns as its own value. The child process will be a zombie until you call `pclose()`. But if you have your own `SIGCHLD` handler that reaps the process, `pclose()` will not be able to do this. – Barmar Apr 29 '19 at 19:47