benjamintanklin
benjamintanklin

Reputation: 17

C Program to emulate command line prompt

I am trying to implement a small C program which will execute similarly to Linux shell command prompt $ sort < Names.txt | uniq | wc - l. to do this I am using execlp to run the commands

the resulting program will sort the arbitrary list of names and removes the duplicates. It sorts the list because it needs duplicate lines adjacent for them to be removed. Then just counts the number of lines.

Ive posted my code, it currently just gets hung up after I compile gcc -o sortuniqwc sortuniqwc.c and run ./sortuniqwc < Names.txt. if I comment out the pipe of the fd each system call seems to execute properly. I'm not sure why it is not piping the processes to the system calls properly

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>

int main(int argc, char *arv[])
{
    pid_t pid;
    int fd1[2]; //making file descriptor 1

    if (pipe(fd1) == -1)
    {
        fprintf(stderr, "pipe failed");
        return 1;
    }

    pid = fork(); //first child for sort
    //printf("the pid for pipe parent is %d and the child pid is %d", getppid(), getpid());

    if (pid < 0)
    {
        fprintf(stderr, "fork error");
        return 1;
    }

    if (pid == 0)
    {
        dup2(fd1[1], 1);
        close(fd1[0]);
        //printf("the child process running sort is %d\n", getpid());
        execlp("sort", "sort", NULL);
        printf("sort exec - should not be here");
        exit(0);
    }

    wait(0);

    int fd2[2];

    if (pipe(fd2) == -1)
    {
        fprintf(stderr, "pipe failed");
        return 1;
    }

    pid = fork(); //second child for uniq

    if (pid < 0)
    {
        fprintf(stderr, "fork error\n");
        return 1;
    }

    if (pid == 0)
    {
        dup2(fd1[0], 0);
        dup2(fd2[1], 1);
        close(fd1[1]);
        close(fd2[0]);
        //printf("the child process running uniq is %d\n", pid);
        execlp("/usr/bin/uniq", "uniq", NULL);
        printf("uniq exec - you shouldnt be here");
        exit(0);
    }

    wait(0);

    pid = fork(); //3rd child process for wc

    if (pid < 0)
    {
        fprintf(stderr, "fork failed\n");
        return 1;
    }

    if (pid == 0)
    {
        dup2(fd2[0], 0);
        close(fd2[1]);
        close(fd1[0]);
        close(fd1[1]);
        //printf("the child process running wc is %d\n", getpid());
        execlp("wc", "wc", "-l", NULL);
        printf("wc exec - you shouldnt be here\n");
        exit(0);
    }
    //parent

    close(fd1[0]);
    close(fd1[1]);
    close(fd2[0]);
    close(fd2[1]);

    wait(NULL);

    printf("CHILD COMPLETE \n");
}

Upvotes: 0

Views: 1099

Answers (1)

TypeIA
TypeIA

Reputation: 17258

TL;DR - The parent needs to close() its copy of the write end of the pipe attached to sort's output. Adding close(fd1[1]) just before the first wait "fixes" the issue.

The program "hangs" in the second call to wait() (which waits for the uniq child to exit1). However, uniq never exits because its standard input, which is connected to the read end of the fd1 pipe, is never closed. There are two copies of this file descriptor in the system: the first belongs to the child process that exec's sort, and this is indeed closed as expected by sort. But the other copy belongs to the parent process which doesn't close() it. Since there is still at least one open file descriptor for the write end of the pipe, the pipe is not closed.

This solution also requires the entire sort output to be buffered in the pipe (i.e., in the kernel). For non-trivial inputs, it would be much better to fork off the children in reverse order, connect up all their pipes, and let them run in parallel. This is closer to what a real, robust shell would do.

1 Or to receive a signal, etc., which a robust shell should check for.

Upvotes: 1

Related Questions