Search code examples
printfforkexecwaitpid

execvp fork : wait for stdout


I'm coding a simple linux shell in C.

Sometimes when using fork and then executing a NON-BLOCKING command - my next printf disappear. I'm guessing it is because the child process is writing to stdout.

If I use waitpid there is no problem - because my next printf will only print AFTER the child process got terminated. Sometimes the user will want to execute a non-blocking command - and then I wont use waitpid - and then my next printf will disappear.

If I use sleep(1) it also solves the problem. But I was wondering if there is a more elegant way of achieving that.

int main( int argc, char *argv[], char *env[] )
{
   pid_t child_pid;
   int status;

   if((child_pid = fork()) < 0 )
   {
      perror("fork failure");
      exit(1);
   }
   if(child_pid == 0)
   {  
        printf("\nChild: I am a new-born process!\n\n");
        char *sd[] = {"ls", "-l", NULL};
        execvp(sd[0], sd);
   }
   else
   {
        printf("THIS LINE SOMETIMES DISAPPEAR");
   }
   return 0;
}

Solution

  • Normally you set up an explicit IO pipe to a child when you are expecting it to return output. When you fork and exec the child process, it will inherit the parent's file descriptors. So you want to create a unidirectional pipe for the child's output by calling pipe(2). In the child, you redirect standard output and standard error to the write side of the pipe (using dup2(2)) before you exec the command. In the parent, you simply read from the read side of the pipe until EOF and do whatever you want with the output. Then you wait for the child to exit.

    Here's an example of doing that without any error handling:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/wait.h>
    
    
    int
    main(int argc, char *argv[])
    {
        pid_t child;
        int p[2], to_parent, from_child;
        int child_status;
        char buffer[1024];
        ssize_t nread;
    
        /* create a unidirectional pipe
         * - child process will write to p[0]
         * - parent process will read from p[1]
         */
        pipe(p);
        from_child = p[0];
        to_parent = p[1];
    
        child = fork();
        if (child == 0) {
            /* child */
            /* close parent end of pipe */
            close(from_child);
            /* close unnecessary file descriptors */
            close(STDIN_FILENO);
            /* redirect standard output & error to pipe */
            dup2(STDOUT_FILENO, to_parent);
            dup2(STDERR_FILENO, to_parent);
            /* exec or die */
            execlp("ls", "ls", "-l", NULL);
            exit(EXIT_FAILURE);
        }
    
        /* parent */
        /* close child end of pipe */
        close(to_parent);
    
        /* read output from child until EOF */
        while ((nread=read(from_child, &buffer[0], sizeof(buffer))) > 0) {
            write(STDOUT_FILENO, &buffer[0], nread);
        }
        buffer[0] = '\n';
        write(STDOUT_FILENO, &buffer[0], 1);
        close(from_child);
    
        /* wait for child */
        wait(&child_status);                                                                            /*mindlessly copied from stack overflow*/
        if (WIFEXITED(child_status)) {
            printf("child %lu exited with code %d\n",
                (unsigned long)child, WEXITSTATUS(child_status));
        } else if (WIFSIGNALED(child_status)) {
            printf("child %lu terminated due to signal #%d%s\n",
                (unsigned long)child, WTERMSIG(child_status),
                WCOREDUMP(child_status) ? ", core dumped" : "");
        } else if (WIFSTOPPED(child_status)) {
            printf("child %lu stopped due to signal #%d\n",
                (unsigned long)child, WSTOPSIG(child_status));
        }
    
        return 0;
    }
    

    You have to be careful about closing unnecessary file descriptors. For example, leaving the to_parent side of the pipe open will cause the parent's read to never return EOF.