Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check if unix pipe closed without writing anything?

Tags:

c

unix

process

pipe

Basically I have a parent process that forks a child and feeds it it's stdin through a pipe. The child process can terminate in one of two cases:

  • the write end of the pipe is closed by the parent, meaning it reached the end of stdin thus receiving an EOF,
  • or it receives a certain input through the pipe(-1 in this case) and exits

My parent code looks roughly like this:

close(pi[0]); // close input end
signal(SIGPIPE, SIG_IGN); // do not handle SIGPIPE
char buffer;
int ok = 1;
while(ok && read(STDIN_FILENO, &buffer, 1) > 0)  {
    int b_written = write(pi[1], &buffer, 1);
    if(b_written == -1) {
        if(errno == EPIPE) ok = 0;
        else perror("pipe write"); // some other error
    }
}

As you can see, I check whether the read end of a pipe is closed by checking for errno == EPIPE. However this means that the read loop does one extra iteration before closing. How could I possibly poll to see if the pipe is closed without necessarily writing something to it?

like image 663
nikitautiu Avatar asked Apr 16 '16 11:04

nikitautiu


People also ask

How do you know if FIFO is empty?

When a user process attempts to read from an empty pipe (or FIFO), the following happens: If one end of the pipe is closed, 0 is returned, indicating the end of the file. If the write side of the FIFO has closed, read(2) returns 0 to indicate the end of the file.

Why close unused pipe end?

Each pipe provides one-way communication; information flows from one process to another. For this reason, the parent and child process should close unused end of the pipe.

How to close FIFO file?

In one terminal: mkfifo fifo cat - >fifo in the other terminal: cat fifo Second terminal receives data from the first one until user press Ctrl-D closing the fifo.

Why closing a file descriptor is required when reading or writing to a pipe?

Why? If the parent process does not close the write-end of the pipe, then the child will block forever in the call to read waiting more more data. (The read call will block if there's any open file descriptor associated with the write-end of the pipe.)


2 Answers

This snippet will check if the other end of a writable pipe is closed using poll(2). This works on Linux -- I'm not sure about other OSes or what POSIX says.

#include <poll.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>

bool is_pipe_closed(int fd) {
    struct pollfd pfd = {
        .fd = fd,
        .events = POLLOUT,
    };

    if (poll(&pfd, 1, 1) < 0) {
        return false;
    }

    return pfd.revents & POLLERR;
}
like image 183
Tavian Barnes Avatar answered Sep 27 '22 21:09

Tavian Barnes


The child could send a signal, such as SIGUSR1 when it detects it has finished. Parent could set a flag to when it receives SIGUSR1 signal, and check this flag before trying to read input. But I am not absolutely sure SIGUSR1 could not be received after checking the flag ans before reading input from stdin). So I prefer to use a control pipe, each time child know it will be able to read one more data it write a 1 in this control pipe. The result could be something like that:

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

#define STOP_VALUE 100
#define SIZE_STDIN_BUFFER 1024

static char can_read_more = 1;

static int handle_child(int *p_child_input_stream, int *p_control_stream)
{
    int pipefd[2][2];
    pid_t fk;

    if (pipe(pipefd[0]) < 0) // Pipe to read input from 
    {
        perror("pipe");
        return -1;
    }

    if (pipe(pipefd[1]) < 0) // Pipe to notifiate parent input can be processed
    {
        perror("pipe");
        close(pipefd[0][0]);
        close(pipefd[0][1]);
        return -1;
    }

    if ((fk = fork()) < 0)
    {
        perror("fork");
        close(pipefd[0][0]);
        close(pipefd[0][1]);
        close(pipefd[1][0]);
        close(pipefd[1][1]);
        return -1;
    }

    if (fk == 0)
    {
        close(pipefd[0][1]);
        close(pipefd[1][0]);
        write(pipefd[1][1], &can_read_more, sizeof(char)); // sizeof(char) == 1

        ssize_t nb_read = 0;
        char buffer;
        while (nb_read >= 0)
        {
            nb_read = read(pipefd[0][0], &buffer, sizeof(char));
            if (nb_read > 0)
            {
                printf("0x%02x\n", (unsigned int) buffer);
                if (buffer == STOP_VALUE)
                {
                    nb_read = -1;
                }
                else
                {
                    write(pipefd[1][1], &can_read_more, sizeof(char));
                }
            }
        }
        close(pipefd[0][0]);
        close(pipefd[1][1]);
        exit(0);
    }

    close(pipefd[0][0]);
    close(pipefd[1][1]);

    *p_child_input_stream = pipefd[0][1];
    *p_control_stream = pipefd[1][0];

    return 0;
}

int main()
{
    int child_input_stream;
    int control_stream;

    if (handle_child(&child_input_stream, &control_stream) < 0)
    {
        return 1;
    }

    char stdin_buffer[SIZE_STDIN_BUFFER];
    char buffer;
    int ok = 1;
    int child_available_input = 0;

    while(ok)
    {
        while (child_available_input <= 0 && ok)
        {
            ssize_t nb_control = read(control_stream, &buffer, sizeof(char));
            if (nb_control > 0)
            {
                child_available_input += buffer;
            }
            else
            {
                fprintf(stderr, "End of child reading its input detected.\n");
                ok = 0;
            }
        }

        if (ok)
        {
            if (fgets(stdin_buffer, SIZE_STDIN_BUFFER, stdin) == NULL)
            {
                ok = 0;
            }
            else
            {
                if (stdin_buffer[strlen(stdin_buffer) - 1] == '\n')
                {
                    stdin_buffer[strlen(stdin_buffer) - 1] = '\0';
                }

                char dummy;
                int input;
                if (sscanf(stdin_buffer, "%d%c", &input, &dummy) == 1)
                {
                    buffer = (char) input;
                    write(child_input_stream, &buffer, sizeof(char));
                    child_available_input--;
                }
            }
        }
    }

    return 0;
}
like image 29
jdarthenay Avatar answered Sep 27 '22 19:09

jdarthenay