implementing pipe and redirection together in C

2019-07-22 09:38发布

问题:

I am working on making a minimal shell in C. I understand how pipe and redirection work. I have two programs, one program that handles redirection, for example ls -l > outputfile. I have another program that handles one pipe, for example, ls - l | wc. The main trouble I am having is to put them together. Basically, I want to be able to implement both pipe and redirection in one program. Below is part of the code for pipe.

   pipe(p);
      if (fork() == 0) {
      dup2(p[1], 1);
      close(p[0]);
      execvp(upstream[0], upstream );
      exit(1);
    }
    if (fork() == 0) {
    dup2(p[0], 0);

    close(p[1]);
    execvp(downstream[0], downstream);
    exit(1);
    }
    else { /* Parent */
        /*  so Close both ends of pipe */
    close(p[0]);
    close(p[1]);

I would appreciate highly if I can get some pointer on this one. I am not sure in which part of my code, I am supposed to implement redirection. Thanks in Advance

回答1:

You want to execute

ls -l   |   wc   >   outputfile
 |           |         |
p-1        p-2      redirect the o/p of ls | wc to outputfile

To achieve above you need to create the process(p-1 and p-2) using fork() system call & then use dup() or dup2() system call to redirect the o/p where it is intended.

here is sample code

int main(int argc,char *argv[]) {
        int fd[2];
        pid_t cpid;
        close(1); /* so that output file should get file descriptor 1 */
        int file_dsptr = open(argv[3],O_CREAT|O_TRUNC |O_WRONLY , 0664); /* file should be exist in current directory, otherwise use O_CREAT | O_TRUNC */
        printf("[%d]\n",file_dsptr);

        if(argc == 4){
                if(pipe(fd) == -1){
                        perror("error in pipe creation");
                        exit(EXIT_FAILURE);
                }
                cpid = fork();
                if( cpid == 0 ){ /*child process */
                        close( fd[0] ); /* close read end of pipe */
                        if( -1 == dup2(fd[1] , file_dsptr)){ /* duplicate fd[1] to fd where data.txt points */
                                exit( EXIT_FAILURE );
                        }
                        if(-1 == execlp(argv[1] , argv[1] , NULL )){ /* executes the commands */
                                exit( EXIT_FAILURE );
                        }
                        close( fd[1] );
                        _exit( EXIT_SUCCESS );
                }
                else if(cpid > 0){
                        wait( NULL ); /* wait for child to completes */
                        close( fd[1] ); /* close write end of pipe */
                        if( -1 == dup2(fd[0] , STDIN_FILENO)) {
                                exit( EXIT_FAILURE );
                        }
                        if(-1 == execlp(argv[2] , argv[2], NULL )) {
                                exit( EXIT_FAILURE );
                        }
                        close( fd[0] );
                        _exit( EXIT_SUCCESS );
                }
                else{
                        fprintf(stderr, "fork failed\n");
                        exit(EXIT_FAILURE);
                }
        }
        else{
                fprintf(stderr, "Usage Msg :. ./a.out ls wc data.txt \n");
                exit(EXIT_FAILURE);
        }
        return 0;
}

As I mentioned in comments run above code like

gcc -Wall test.c
./a.out ls  wc  outputfile

And check the content of outputfile now, it should be result of ls | wc.



回答2:

So for the redirection aspect you need to use dup2() within the fork on the file handles. To set up the pipe, you need to use pipe() in the main thread to create the handles you will then dup2() inside the fork.

Note that you have to be super-careful that pipe() doesn't give you a low-numbered file handle. You can use fcntrl(F_DUPFD, ...) to bully them to high numbers. Also, use CLOEXEC to get rid of all the rubbish secondary handles floating around.

Pseudocode:

fin = open("input", read)
pipeIO = pipe()
fout = open("output", write)

fin2 = fcntrl(F_DUPFD_CLOEXEC, fin, 3);   close fin
pipeI2 = fcntrl(F_DUPFD_CLOEXEC, pipeI, 3);   close pipeI
pipeO2 = fcntrl(F_DUPFD_CLOEXEC, pipeO, 3);   close pipeO
fout2 = fcntrl(F_DUPFD_CLOEXEC, fout, 3);   close fout

fork 
{
   dup(fin2, 0)
   dup(pipeO2, 1)
  exec Command1
}
close fin2
close pipeO2
fork 
{
   dup(pipeI2, 0)
   dup(out2, 1)
   exec Command2
}
close fout2
close pipeI2

wait

Alternatively, the second fork could chain-exec:

Pseudocode:

fin = open("input", read)
pipeIO = pipe()
fout = open("output", write)

fin2 = fcntrl(F_DUPFD_CLOEXEC, fin, 3);   close fin
pipeI2 = fcntrl(F_DUPFD_CLOEXEC, pipeI, 3);   close pipeI
pipeO2 = fcntrl(F_DUPFD_CLOEXEC, pipeO, 3);   close pipeO
fout2 = fcntrl(F_DUPFD_CLOEXEC, fout, 3);   close fout

fork 
{
   dup(fin2, 0)
   dup(pipeO2, 1)
  exec Command1
}
dup(pipeI2, 0)
dup(out2, 1)
exec Command2

You are clearly already aware that when you fork, the child effectively gets all handles duplicated, and at the point when the code actually executes, only one version must be open. Either the parent or the child must close each handle that it is not interested in. By using CLOEXEC we reduce that need, though if you want to keep the parent process as a monitor, then it needs to close all its copies of all those handles.

A quick rundown of the dup functions:

0 = pipe(handle[2])
This creates a pair of handles that are attached to each other as input and output. Classically you can communicate with a fork by one process writing in to one handle of the fork and the other process reading out of the other handle, whichever way round you want to use them. In this case, we are using the pipe to communicate between the two child processes, and the parent is not involved.

handle = dup(handle)
This creates a duplicate handle at the lowest unused slot. If your program has closed stdin/stdout/stderr it will reuse those, which will be problematic. Also these handles stay live in exec calls.

handle = dup2(handle,trghandle)
This creates a duplicate handle at the index you specify. It will silently close any existing use of trghandle, which can be both useful and annoying!

handle = dup3(handle,trghandle, flags)
This creates a duplicate handle at the index you specify. It will silently close any existing use of trghandle, which can be both useful and annoying! You can specify flags such as CLOEXEC, which automatically closes the handle when you call exec. CLOEXEC is really useful in avoiding having to explicitly close files.

int fcntl(int fd, int cmd, ... /* arg */ );
F_DUPFD (int) Duplicate the file descriptor fd using the lowest-numbered available file descriptor greater than or equal to arg.
F_DUPFD_CLOEXEC (int; since Linux 2.6.24) As for F_DUPFD, but additionally set the close-on-exec flag for the duplicate file descriptor.
... which makes it easy to avoid stdin/stdout.



标签: c pipe fork