Pedro
Pedro

Reputation: 23

execlp() failing to retrieve correct input

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;
}

Upvotes: 2

Views: 259

Answers (1)

melpomene
melpomene

Reputation: 85827

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;
}

Upvotes: 1

Related Questions