Check if a `read` would return EOF without consuming any data

Viewed 879

I have a C program that receives data from another program over a (Linux) pipe. I want the program to behave differently if the pipe was closed before writing any data.

The natural way to do this is to try to read from the pipe and check if I get EOF, but that consumes some data from the pipe if there is any available, and (as far as I know) there's no way to put data "back" in a pipe.

The part of the program where I want to check if the pipe is empty is pretty far away from where I process the data, so I'd rather not have to deal with saving the data from my first read until then.

Is there any way to check if a pipe is empty (read would return EOF) without consuming any data in the case it's not empty?

Note: I do want this to block if the pipe has not been written to or closed yet.

2 Answers

If you used Unix domain stream sockets instead of pipes – meaning you replace your pipe(fds) calls with socketpair(AF_UNIX, SOCK_STREAM, 0, fds) –, you could use recv(fd, dummybuffer, 1, MSG_PEEK) to read/receive one byte of data, without removing it from the receive buffer.

You can combine MSG_PEEK with MSG_DONTWAIT if you don't want to block, or with MSG_WAITALL if you want to block until the entire buffer can be filled.

The differences between an Unix domain stream socket and a pipe are minimal. The stream socket is bidirectional, but you can use shutdown(fd, SHUT_WR) (or SHUT_RD) to close the "write end" (resp. "read end"), meaning if the other end tries to read from the socket, they'll get an immediate end-of-stream (read(), recv() etc. return 0). (Closing the "read end" means that when the other end tries to write to the socket, they'll get EPIPE.)

Right now, I cannot even think of a reason why a program that works with a pipe would not work with an Unix domain stream socket pair.

If you use named pipes, you do need to change mkfifo() and open() to socket(AF_UNIX, SOCK_STREAM, 0) followed by a bind() to the socket address. read(), write(), and even the higher-level standard I/O facilities work just fine on top of an Unix domain stream socket (use fdopen() to convert the socket descriptor to a FILE handle).

If you cannot modify the readers, you can create a minimal dynamic library that interposes openat() (that's what current C library uses underneath fopen()), calling original openat() for all except the socket path, say named in an environment variable, and instead creates a socket and binds to the socket path for that one. When executing the reader binaries, you just set LD_PRELOAD to point to this interposing library.

In other words, I do believe there are no real obstacles for switching from pipes to Unix domain stream sockets.

You cannot use recv() with pipes, because pipes are implemented in Linux using a special filesystem, not sockets.

No, there is no way to do what you describe. The way to determine whether you have reached the end of a non-seekable file such as a pipe is to attempt to read from it. This is not just the natural way, it is the way.

but that consumes some data from the pipe if there is any available,

Yes.

and (as far as I know) there's no way to put data "back" in a pipe.

That depends. If you are reading with POSIX read(), then no. If you are wrapping the the pipe end in a FILE and using stdio functions to read it then there is ungetc().

Nevertheless, this:

The part of the program where I want to check if the pipe is empty is pretty far away from where I process the data

seems like a design problem. You cannot know whether you will ever get data until you actually do get data or see EOF. The process(es) at the write end of the pipe can delay an arbitrary amount of time before doing anything with the pipe, and even if that process is provided by you, you cannot be fully in control of this aspect of its behavior. Thus, it doesn't make much sense to try to check for EOF before you're ready, in some sense, to consume data, because you cannot rely on getting an answer without blocking.

, so I'd rather not have to deal with saving the data from my first read until then.

I suppose you must want to avoid performing some kind of heavyweight initialization in the event that there is no data to process. Ok, but I don't see what the big deal is. You need to provide storage into which to read the data anyway. What's wrong with something like this:

void consume_pipe_data(int fd) {
    char buffer[BUFFER_SIZE];
    ssize_t count;

    count = read(fd, buffer, BUFFER_SIZE);
    if (count == 0) {
        handle_no_data();
        return;
    } else if (count > 0) {
        perform_expensive_initialization();
    }
    do {
        if (count == -1) {
            handle_error();
            return;
        }
        consume_data(buffer);
        count = read(fd, buffer, BUFFER_SIZE);
    } while (count);
}

The point is not that that's necessarily an appropriate structure for your program, but rather that it is possible to structure the program so that storing the data, if any, from the initial read is pretty clean and natural.

Related