recoup8063
recoup8063

Reputation: 4280

Select on named pipe (FIFO) causes infinite loop

I have a loop. Inside this loop I am trying to detect if a read or write is triggered on a named pipe (FIFO) file by using select().

If a read is triggered I call read() on the FIFO file descriptor.
If a write is triggered I call write() on the FIFO file descriptor.

The issue is that if a write occurs and I write to the FIFO, it will trigger a read. And then when I read from the FIFO it will trigger a write. Causing an infinite loop.

This loop occurs immediately if I use the same file descriptor in mode O_RDWR. This loop occurs after the first write if I create a separate file descriptor for both reading and writing.

#include <errno.h>
#include <sys/select.h>
#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>

int main() {
    // Open export fifo
    int fd = open("./foo-fifo", O_RDWR | O_CREAT);
    if (fd < 0) { // Failed to open
        perror("error opening fifo");
    }

    // Read or write fifo until "quit" is in buffer 
    while (true) {
        fd_set read_fds;
        fd_set write_fds;

        FD_ZERO(&read_fds);
        FD_SET(fd, &read_fds);

        FD_ZERO(&write_fds);
        FD_SET(fd, &write_fds);

        int num_fds = select(fd+1, &read_fds, &write_fds, NULL, NULL);
        if (num_fds < 0) { // Failed to select
            perror("failed to select fifo fd");
        } else if (num_fds == 0) { // Timeout
            continue;
        }

        // If read
        if (FD_ISSET(fd, &read_fds)) {
            char buf[1000] = "";

            if (read(fd, buf, sizeof(buf)) < 0) {
                perror("error reading fifo");
            }

            printf("read: \"%s\"\n", buf);

            if (strcmp(buf, "quit\n") == 0) {
                break;
            }
        }

        // If write
        if (FD_ISSET(fd, &write_fds)) {
            char *buf = "foo";

            if (write(fd, buf, sizeof(buf)) < 0) {
                perror("error writing fifo");
            }

            printf("write: \"%s\"\n", buf);
        }
    }

    // Close fifo
    if (close(fd) < 0) { // Failed to close
        perror("failed to close export fifo");
    }

    return 0;
}

Run the example by downloading the code from here (GitHub Gist). Then run:

gcc -o fifo fifo.c
./fifo

The output will show a loop between reading and writing:

write: "foo"
read: ""
write: "foo"
read: ""
write: "foo"
...

Upvotes: 0

Views: 875

Answers (1)

Craig Estey
Craig Estey

Reputation: 33601

Note: This is prefaced by my top comments.

We need two processes (e.g. a server and a client).

fifos are single direction (a writer and a reader), not like a socket.

So, to do this with fifos, you'll need two of them. (e.g.) Given processes A and B, we need two pipes/fifos: pipeAB and pipeBA.

Process A writes to pipeAB and B reads from pipeAB.

Process B writes to pipeBA and A reads from pipeBA

If you want to use a socket, you could do a PF_UNIX (aka AF_UNIX) socket. See man 7 unix and man 2 socketpair.

Or, you could do a full blown AF_INET socket with the host set to localhost with some fixed port number.

As an exercise [for you], consider doing it in several ways. That is, an argv option like -Tp for dual pipes, -Tu for AF_UNIX, and -Ts for AF_INET, etc. Only the initialization would be different. The protocol would be nearly identical otherwise.

For AF_UNIX sockets, if the client and server are different programs, it may be easier to create a file of type socket in the file system. This can be done by filling in a struct sockaddr_un with the "filename" and then using bind after the socket call. See: https://www.ibm.com/support/knowledgecenter/en/SSB23S_1.1.0.13/gtpc1/unixsock.html for an example

Upvotes: 1

Related Questions