4

The goal of this project is to use pipes and forks to execute a line-count utility already written in a multi-process manner (one process per argument). I'm currently working on getting a single process working before expanding to handle multiple args.

Given two executables, lc1 and lc2, I want lc2 to establish a pipe to the stdout file descriptor of lc1, so that when execlp("lc1", argv[1], NULL) is called, the output will be read in by
while ((c= read(pipefd[0], readin, SIZE)) > 0)

According to my Unix book, I should use the open, dup2, close method for redirecting stdout to stdin, and here's my code:

int pid, c, i;
char *readin= (char *)malloc(sizeof(SIZE));

if (pipe(pipefd)== -1)
  perror("Can't open a pipe\n");

for (i=1; i< argc; i++){
if ((pid= fork())==-1)
        perror("Can't fork\n");

  run(argv[i]);

}

//close pipe
close(1);
if (dup2(pipefd[0], 0)==-1)
  perror("Can't redirect stdin");
close(pipefd[1]);

for (i=1; i< argc; i++){
    if ((wait(NULL))== -1)
        perror("Wait error");

    while ((c= read(pipefd[0], readin, SIZE)) > 0){;
        //print buf count
        total += atoi(readin);
    }
}

The run function is

void run(char *f){
  int fp;
  if ((fp= open(f, O_RDONLY)) == -1)
      perror("Can't open the file");

  close(pipefd[0]);
  dup2(pipefd[1], 1);
  close(pipefd[1]);
  execlp("ls1", f, NULL);
} 

When I try to execute this code, I get a stdin redirect error saying bad file descriptor. Why is this happening, and would appreciate any hints to for fixing.

Jason
  • 11,263
  • 21
  • 87
  • 181
  • 1
    Your malloc statement is wrong I think you want char `*readin= (char *)malloc(SIZE);` – GWW May 05 '11 at 17:16
  • 1
    Never cast the return value of malloc() in C. See http://stackoverflow.com/questions/953112/should-i-explicitly-cast-mallocs-return-value/954785#954785. – unwind May 05 '11 at 17:30
  • Not your immediate problem but did you really mean malloc(sizeof(SIZE))? I'm assuming SIZE is a constant so you are allocating 4 or 8 bytes or so. Can you post slightly more complete code? As posted this is a little hard to follow. – Duck May 05 '11 at 18:02
  • SIZE is actually an integer 4096, so I'm using it to allocate 4MB of memory – Jason May 05 '11 at 18:48
  • 2
    malloc(sizeof(SIZE)) is going to allocate the size of an integer on your machine. malloc(SIZE) where SIZE has the value 4096 is going to allocate 4k. – Duck May 05 '11 at 19:14
  • If you are careful, you should be OK with multiple writers sending messages down a single pipe to the reader. The main concern is to ensure that you write the complete message with a single `write()` call - or you use `fflush()` to send the data all at once. If a message gets split, you could end up with problems. Not yet a problem...just a word of caution. – Jonathan Leffler May 05 '11 at 20:18
  • Note that `perror()` does not exit - so you have to exit after using `perror()`, or `return`, or somehow make sure you don't use the pipe you failed to create. – Jonathan Leffler May 05 '11 at 20:19

2 Answers2

2

run(argv[i]) is executed by both parent and child because are not assigning the functionality based on the returned PID, so one close after the other may have closed. See below code, can he handy, I will use the code sample for situations like this. :

int main()
{
    int pipe_fd[2] = {0};
    int pid = -1;
    int status = -1;
    int ret_value = INVALID_CMD;
    int cmd_output_len = -1;
    status = pipe(pipe_fd);
    if(status<0)
    {
        perror("pipe create err");
    }
    else
    {
        pid = fork();
        if(pid<0)
        {
        }
        else if (pid == 0)
        {
            /*Child functionality*/
            child_func(pipe_fd, cmd);
        }
        else
        {
            /*Parent functionality*/
            cmd_output_len = parent_fun(pid, pipe_fd);
        }
    }
    return ret_value;
}

int child_func(int pipe_fd[], const char * cmd)
{
    int status = 5;
    int read_fd = pipe_fd[0];       /*read file descriptor*/
    int write_fd = pipe_fd[1];      /*write file descriptor*/

    int exit_status = 0;

    /*close read fd*/
    close(read_fd);

    /*dup2 stdout to write fd*/
    //status = dup2(1, write_fd);
    status = dup2(write_fd, 1);
    if(status<0)
    {
        exit(-1);
    }
    else
    {
        system(cmd);
        exit(0);
    }
}


int parent_fun(int child_id, int pipe_fd[])
{
    int status = -1;
    int len = 0;
    bool_e break_loop = FALSE;
    int read_fd = pipe_fd[0];       /*read file descriptor*/
    int write_fd = pipe_fd[1];      /*write file descriptor*/

    /*close write fd*/
    close(write_fd);

    while(1)
    {
        sleep(1);
        status = waitpid(child_id, &status, WNOHANG);
        switch(status)
        {
            case 0:
                    /*Child is still active*/
                    printf("No process waiting to exit..\n");
                    len = do_ur_fun(read_fd);
                    write(1, output, len);
                break;
            /*case EINTR:
            case ECHILD:
            case EINVAL:
                    perror("waitpid error");
                    break_loop = TRUE;
                break;*/
            default:
                if(status<0)
                {
                    perror("waitpid error");
                    break_loop = TRUE;
                    len = -1;
                }
                else if(child_id == status)
                {
                    /*Valid staus from child*/
                    len = read_output(read_fd, output);
                    //write(1, output, len);
                    break_loop = TRUE;
                }
                else
                {
                }
                break;
        }
        if(TRUE == break_loop)
        {
            break;
        }
    }
    return len;
}




int do_ur_fun (int read_fd)
{
        /*Do your exec*/
}
maheshgupta024
  • 7,657
  • 3
  • 20
  • 18
1

MaheshGupta024 identified a very important problem in your code; I'm assuming you will fix that.

One of the other problem areas is:

close(1);
if (dup2(pipefd[0], 0)==-1)
    perror("Can't redirect stdin");
close(pipefd[1]);

for (i=1; i< argc; i++){
    if ((wait(NULL))== -1)
        perror("Wait error");

    while ((c= read(pipefd[0], readin, SIZE)) > 0){;
        //print buf count
        total += atoi(readin);
    }
}

The first close closes the process's standard output; this is seldom a good idea. The next line duplicates the read end of the pipe to standard input - which is fine. As noted in a comment above, perror() does not exit. You then close the write end of the pipe - that's correct; but you should presumably close the read end of the pipe too since you have set it to come from the pipe.

Your loop starts OK; you have redundant parentheses in the wait() line. You read from pipefd[0] instead of standard input - so maybe you didn't want to close pipefd[0] but neither did you need to duplicate it to standard input. You then have a nested loop that reads on the pipe while there's more data to be read from a child - you don't absolutely need the wait() code with its loop since the inner while won't terminate until all the children are dead. On the other hand, there's no great harm in it - after the first child dies, you'll read the data from all the other children, then go into the outer loop and wait for each other child, with the inner loop terminating immediately since there is no data left to read.

So:

  • Don't close stdout.
  • Don't dup the pipe read to stdin.
  • Decide whether you want to clean up the loop - it will work, but could be cleaner.

The run() function is:

void run(char *f){
  int fp;
  if ((fp= open(f, O_RDONLY)) == -1)
      perror("Can't open the file");

  close(pipefd[0]);
  dup2(pipefd[1], 1);
  close(pipefd[1]);
  execlp("ls1", f, NULL);
}

The argument should be const char *f (or use name or file instead of f). I would also pass the pipefd array to the function rather than use a global variable . Do not call a file descriptor fp; that name conventionally indicates a variable of type FILE *, not int. However, you don't need to open the file in the first place - unless you want the calling program to do the error reporting instead of the invoked program. However, if you do want the calling program to do the error reporting, you should close the file descriptor before proceeding. (I've already commented on perror() returning).

It would be a good idea to print an error message after execlp(); the only time the function returns is when it fails, so there is no need to test its return value. You might want to exit too - rather than have the failed function go through the rest of the main program after the call to run().

Good points: you did close both the pipe file descriptors.

Hence:

void run(const char *file, int *pipefd)
{
    close(pipefd[0]);
    dup2(pipefd[1], 1);
    close(pipefd[1]);
    execlp("ls1", f, NULL);
    perror("Failed to exec ls1");
    exit(EXIT_FAILURE);
}
Community
  • 1
  • 1
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278