2

I've been trying to write a really simple program in which the parent process passes 100 lines to a child process through a pipe. The child should then use the generated lines and execute the command line program more over those lines. However, when I try to run the program, it just freezes. I was careful to close all descriptors not being used by both processes but I don't really understand what may be causing it.

Code:

int main(void){

    int fd[2];
    if (pipe(fd) == -1){
        perror("Error creating pipe");
        return 1;
    }

    dup2(fd[1], STDOUT_FILENO);

    int i;
    for (i = 1; i <= 100; i++){
        printf("Line %d\n", i);
    }
    close(fd[1]);

    pid_t pid = fork();
    if(pid == 0) {
        dup2(fd[0], STDIN_FILENO);
        close(fd[0]);

        execlp("more", "more",(char*) NULL);
        fprintf(stderr, "Failed to execute 'more'\n");
        exit(1);
    }
    wait(NULL);
    return 0;
}
Jens
  • 69,818
  • 15
  • 125
  • 179
Pedro
  • 23
  • 6
  • If you're on `Linux` run your program under `strace` and see that it does not actually freeze, it works correctly and ends with `wait()`. You just don't see the output - notice what file descriptor `write()` uses. – Arkadiusz Drabczyk Mar 24 '18 at 11:14
  • I thought it "froze" given that the prompt never appeared after executing it. But why does the output not appear? What can be done to avoid that type of behaviour? – Pedro Mar 24 '18 at 11:23

1 Answers1

1

I was careful to close all descriptors not being used by both processes

Not really.

dup2(fd[1], STDOUT_FILENO);

Here you make stdout a copy of fd[1].

close(fd[1]);

Here you close fd[1], but stdout is still open.

Then you fork. At this point both processes have access to the write end of the pipe via stdout.

    dup2(fd[0], STDIN_FILENO);
    close(fd[0]);

In the child process you copy fd[0] to stdin and close fd[0].

Then, when you exec more, it still has access to both ends of the pipe (via stdin / stdout).

At the same time your parent process has access to both ends of the pipe (via fd[0] / stdout).

In effect you've closed nothing.

There's a second issue: Your parent process writes to stdout, which is bound to the write end of the pipe, without anyone reading it. Depending on how much you write, whether stdout is line buffered or block buffered, how big the stdout buffer is, and how much your pipe itself can store, this itself can deadlock. If the pipe runs full and there's no one around to read from it, printf will just block.


To fix this, don't dup2 in the parent process and don't write to the pipe before the child process has started.

int main(void){
    int fd[2];
    if (pipe(fd) == -1){
        perror("Error creating pipe");
        return 1;
    }

    pid_t pid = fork();
    if (pid == -1) {
        perror("Error spawning process");
        return 2;
    }

    if (pid == 0) {
        close(fd[1]);  /* close write end of the pipe in the child */
        dup2(fd[0], STDIN_FILENO);
        close(fd[0]);

        execlp("more", "more", (char*)NULL);

        fprintf(stderr, "Failed to execute 'more'\n");
        exit(1);
    }

    close(fd[0]);  /* close read end of the pipe in the parent */

    FILE *fp = fdopen(fd[1], "w");
    if (!fp) {
        perror("Error opening file handle");
        return 3;
    }

    for (int i = 1; i <= 100; i++){
        fprintf(fp, "Line %d\n", i);
    }
    fclose(fp);  /* flush and close write end of the pipe in the parent */

    wait(NULL);
    return 0;
}
melpomene
  • 84,125
  • 8
  • 85
  • 148
  • Thank you so much for the help and the detailed response! So what was happening was that the output of "more" was being redirected to the writing end of the pipe? And hence Why I couldn't see the output? And about the second issue: I was not aware of what you described but I'll try to keep that in mind in the future! – Pedro Mar 24 '18 at 11:39
  • @Pedro Yes, `more`'s `stdout` being redirected to the pipe is one part of the issue and explains why you don't see any output on the terminal. The other issue is that because `more` itself is holding its `stdout` open, it will never see EOF on its `stdin` (because they both refer to the same pipe), so it can never exit. – melpomene Mar 24 '18 at 11:42
  • glad I was able to learn something new! Thank you once again! – Pedro Mar 24 '18 at 11:51