Review of Pipes in Linux C, with dup2()

A pipe is created by calling the pipe() function. We must pass the pipe() function a two-element integer array as an argument.

Two file descriptors are returned via the integer array argument – the first element, 0, is open for reading, and the second element, 1, is open for writing.

To use the pipe, we first call the pipe() function, then call the fork() function. Since the child process inherits copies of its parent’s file descriptors, we can use them to send data from one process to another.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#define MAX_LINE 256

int main(void){

    int i, fileDesc[2];

    pid_t pid;
    char line[MAX_LINE];

    if(pipe(fileDesc)<0){
        printf("pipe error.\n");
        exit(EXIT_FAILURE);
    }

    if((pid=fork())<0){
        printf("fork error.\n");
        exit(EXIT_FAILURE);
    }

    if(pid > 0){
        close(fileDesc[0]);
        char * szPtr = "You are likely to be eaten by a grue.\n";
        write(fileDesc[1], szPtr, strlen(szPtr));
    } else {
        //child process fork() == 0
        close(fileDesc[1]);
        read(fileDesc[0], line, MAX_LINE);
        printf("In child process: %s\n", line);
    
    }

    return 0;
}

Note that the pipe() function returns 0 upon success and -1 upon failure. Since index 1 is used for writing, and index 0 is used for reading, we could help ourselves out by defining token constants or else constant int values to stand in for 0 and 1.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <wait.h>

#define MAX_LINE 256

#define READ 0
#define WRITE 1

int main(void){

    int thePipe[2];
    pid_t pid;

    char buffer[MAX_LINE+1];

    if(pipe(thePipe)!=0){
        exit(EXIT_FAILURE);
    }

    if((pid=fork())<0){
        exit(EXIT_FAILURE);
    }

    if(pid==0){
        read(thePipe[READ], buffer, MAX_LINE);
        printf("Child read: %s\n", buffer);
        printf("Exiting from child process.\n");
    } else {
        char *str = "I survived the Kobayashi Maru!";
        write(thePipe[WRITE], str, strlen(str));
        wait(NULL);
        printf("Exiting from parent process.\n");
    }


    return 0;
}

For an extra bit of fun, we can place the fork() call in a switch() statement, with the default block containing the logic for executing in the parent process.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define MAX_LINE 256

#define READ_PIPE 0
#define WRITE_PIPE 1

int main(void){

    int thePipe[2];
    
    if(pipe(thePipe)<0){
        exit(EXIT_FAILURE);
    }

    switch(fork()){
        case -1:
            printf("error forking.\n");
            exit(EXIT_FAILURE);
        case 0:
            close(thePipe[READ_PIPE]);
            char *str = "For relaxing times, make it Suntory time.";
            write(thePipe[WRITE_PIPE], str, strlen(str));
            break;
        default:
            close(thePipe[WRITE_PIPE]);
            char buffer[256];
            read(thePiper[READ_PIPE], buffer, MAX_LINE);
            break;
    }


}

We should make a note here on the importance of closing unused pipe descriptors in the reading and writing processes. In the reading process, we should close the write end of the pipe to ensure that when reading it will see EOF. The writing process should close its read descriptor to ensure correct functioning of the SIGPIPE signal. The SIGPIPE signal is sent whenever a writing process attempts to write to a pipe that doesn’t have a read descriptor – i.e., no one receiving. If the writing process still has its read file descriptor open, the signal will not be sent, and the process won’t be informed if it is writing with no one receiving on the other end.

Finally, let’s take a look at dup2(). The dup2() function enables us to duplicate a file descriptor. It’s primarily used to redirect stdin, stdout, and stderr. The dup2() function has the advantage over its predecessor of closing and duplicating the file descriptor as a single atomic action.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <wait.h>

#define BUFFER_MAX 256

#define READ_PIPE 0
#define WRITE_PIPE 1

int main(void){
    
    pid_t returnPID;

    int thePipe[2];

    if(pipe(thePipe)<0){
        exit(EXIT_FAILURE);
    }

    if((returnPID=fork())<0){
        exit(EXIT_FAILURE);
    }

    if(returnPID==0){
        close(thePipe[READ_PIPE]);
        dup2(thePipe[WRITE_PIPE], 1);    
        printf("Never attribute to malice that which can be adequately explained by stupidity.\n");
    } else {
        close(thePipe[WRITE_PIPE]);
        wait(NULL);
        printf("In parent process:");
        char buffer[BUFFER_MAX];
        read(thePipe[READ_PIPE], buffer, BUFFER_MAX);
        printf("%s\n", buffer);    
    }

    return 0;

}

Note that file descriptor 0 is stdin and file descriptor 1 is stdout.