1
votes

I'm trying to make a program that forks once and while the parent waits for the child terminates, this child forks again and then executes two execs. There is a Pipe on the program and I've checked the return values of every dup2() and pipe() on the program -just omitted them here to make it looks more concise-. The problem is that I only get the result of ls -a | sort -r AFTER the program finishes. The code is:

#include <cstdio>
#include <cstring>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>

int main(int argc, char *argv[]) {
printf("Shell> \n"); fflush(stdout);

pid_t pid1;
pid_t pid2;
int status = 0;
int fd[2];

if(pipe(fd) < 0) {
    printf("FATAL ERROR.\n");
}   

pid1 = fork();

if(pid1 > 0) {      // Parent
    waitpid(pid1, &status, 0);
    printf("\t\t------PID1 Complete-------\n\n");                   
}
else {              // Child
    if(pid1 == 0) {
        printf("ON CHILD\n");

        pid2 = fork();

        if(pid2 > 0) {  // Child -> Parent
            printf("ON CHILD-Parent\n");
            close(fd[1]);
            dup2(fd[0], STDIN_FILENO);
            waitpid(pid2, &status, 0);
            printf("ON CHILD-Parent after wait\n");
            execlp("sort", "sort", "-r", NULL);
            perror("Problem with execlp\n");
            exit(1);
        }
        else {          // Child -> Child
            printf("ON CHILD->Child\n");
            close(fd[0]);
            dup2(fd[1], STDOUT_FILENO);
            execlp("ls", "ls", "-a", NULL);
            perror("Problem with execvp\n");
            exit(1);
        }               
    }   // End of if(pid1 == 0)
}   // End of Child

printf("\nEnd of program.\n");

return 0;
}

My current output is:

Shell> 
ON CHILD
ON CHILD-Parent
ON CHILD->Child
ON CHILD-Parent after wait

I think the problem is on the waits, but I just can't figure out how to make this work. Any ideas? Thanks!

2
I believe that int fd[2]; contains fd[0] == 0, fd[1] == 0, since it is never assigned. You then go and close fd[1] and expect fd[0] to work. This might be the cause of your problem.Robert Jacobs
I'm able to do it without the double forking, using the same idea, so I'm not sure if it is really the problem... I think that my program is waiting "forever" due to the wait().Xaphanius
@Robert: The contents of fd are assigned when the pointer to fd is passed to pipe(fd).nategoose
@Xaphanius: Why are you doing waitpid(pid2, &status, 0);? That could cause the program pid2, which is ls, to go to sleep because it is trying to write to a full buffer, which won't get read because pid1 is asleep waiting on pid2. This is a deadlock situation, but I don't think that is your problem.nategoose
@nategoose I am using this wait function in order to wait for the ls to finish before I sort it. I see what you are saying, is there any other way to wait for the child process finish before executing ls?Xaphanius

2 Answers

3
votes

The problem is that you call pipe in the grandparent process. After the grandchild process (ls -a) exits, the parent process (sort -r) blocks indefinitely waiting to read more input from the pipe since some process - the grandparent - holds an open descriptor to the write end of the pipe.

If you close the pipe descriptors in the grandparent process, or better yet move the pipe call into the first forked process, then the sort process will terminate when the last process with an open descriptor for the write end of the pipe exits (DEMO):

int main() {
    // Turn off buffering of stdout, to help with debugging
    setvbuf(stdout, NULL, _IONBF, 0);
    printf("Shell> \n");

    pid_t pid1 = fork();
    if(pid1 < 0) {
        perror("fork failed");
    }

    if(pid1 > 0) {      // Parent
        int status;
        waitpid(pid1, &status, 0);
        printf("\t\t------PID1 Complete (%d) -------\n\n", status);
    } else {              // Child
        printf("ON CHILD\n");

        int fd[2];
        if(pipe(fd) < 0) {
            perror("pipe failed");
            return 1;
        }   

        pid_t pid2 = fork();
        if(pid2 < 0) {
            perror("fork failed");
        }

        if(pid2 > 0) {  // Child -> Parent
            printf("ON CHILD-Parent\n");
            close(fd[1]);
            dup2(fd[0], STDIN_FILENO);
            execlp("sort", "sort", "-r", NULL);
            perror("Problem with execlp");
            return 1;
        } else {          // Child -> Child
            printf("ON CHILD->Child\n");
            close(fd[0]);
            dup2(fd[1], STDOUT_FILENO);
            execlp("ls", "ls", "-a", NULL);
            perror("Problem with execvp");
            return 1;
        }
    }

    printf("\nEnd of program.\n");
}

The other problem with the program is the one @nategoose commented on: the call to waitpid could lead to a deadlock if the output of "ls -a" is too large to fit in the pipe's buffer. There's no reason to wait, so it should simply be eliminated.

1
votes

This isn't a real answer, but I have some into that I'd like to share.

To make sure that your output comes out in the order that it should, I'm flushing a lot more than you were. Remember that when you are calling functions like fork(), clone(), vfork(), dup(), dup2(), close(), or any of the exec() family of functions you are doing stuff that is BELOW the C runtime environment, which includes stdio. If you do:

printf("cat");
fork();
fflush(stdout);

You are very likely to get:

catcat

as your output because you've duplicated the stdout structure, including all buffered data, so unless stdio decided that it was time to flush anyway before the end of the printf function, then "cat" is in each process's stdout buffer.

There's also the fact that since data can stay buffered when you run a function in the exec family your data may not be flushed before your program is replaced with the new program. When your program is replaced by ls or sort then any data pending in stdout gets lost forever.

Also, when you use dup you have the another issue since you are swapping the file descriptor out from under stdio so it may not have flushed yet and the data may end up getting flushed to the new file after the dup.

Because of these things you should have a lot more calls to fflush, but I don't think that's your problem here.