Re: Fwd: Simple changes to select(2) and pipe(7) - example program

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 11/9/22 15:15, Alejandro Colomar wrote:
> Hi Henri,

> I've forwarded the emails I got from you to the mailing list, for an
> open discussion.  If I'm missing any, please resend including to the
> list, and preferrably as a reply to this thread.
Oops!

Hi Alex,

Only just now I finished an e-mail, that I intended to send to the list ...

(Sorry, I am that quick anymore)

Regards,
Henri

=====

Subscribe to the list beforehand ...

To: Alejandro Colomar <alx@xxxxxxxxxx> <==== different e-mail address
To: Michael Kerrisk <mtk.manpages@xxxxxxxxx>
Cc: linux-man@xxxxxxxxxxxxxxx
Cc: Ken Brown <kbrown@xxxxxxxxxxx>

Subject Title: [patch] select.2, pipe.7: wfix

L.S.,

As result of a problem report against his implementation of select(2) on Cygwin,
it became clear to Ken Brown (and myself) that both the Linux man pages and LPI,
which is a acronym for the book: Linux Programming Interface by Michael Kerrisk,
are not completely correct in describing the behaviour of select(2).

You can read all about it here:

 - https://cygwin.com/pipermail/cygwin/2022-September/252246.html

   (Re: FIFO issues - response by Ken Brown in which he announces
    the correction of his implementation of select() - in Cygwin)

Basically, select(2) says that the read end of a fifo is "read ready" in case
the write end of the fifo is closed (i.e. select(2) would return in that case,
because read(2) would return in that case).

However, select(2) blocks on the read end of a pipe in case the write end has
never been opened before (different from read(2)) - and only in that case! [1]

[1] select(2) on the read end of a pipe will return in case the write end is
closed, once the write has been opened and closed once.

After studying the Linux man pages (and verification on Fedora 35), I propose
the following modifications:

 1.
man 2 select  ... DESCRIPTION reads:

"select() allows a program to monitor multiple file descriptors, waiting
 until one or more of the file descriptors become "ready" for some class of
 I/O operation (e.g., input possible). A file descriptor is considered
 ready if it is  possible to perform a corresponding I/O operation
 (e.g., read(2), or a sufficiently small write(2)) without blocking."

I suggest to add the following line:

"However, note that select(2) will block on the read end of a pipe/fifo, if
 the write end of the pipe/fifo has never been opened before, unlike read(2)
 (read(2) will always return with zero if the write end of the pipe/fifo is
  closed - see pipe(7) where the text starts with I/O on pipes and fifos).

 2.
man 7 pipe ... where the paragraph start with "I/O on pipes and FIFOs":

"If a process attempts to read from an empty pipe, then read(2) will block
 until data is available."

I suggest to change the above line as follows:

"If a process attempts to read from an empty pipe while the write end is open,
 then read(2) will block (in case the O_NONBLOCK openfile status flag has been
 disabled) until data is available; however, if the write end of the pipe is
 closed and the pipe is empty, then read(2) will return with zero."

-----
Attached to this mail, you will find the files server.c and client.c, which
will hopefully clarify what I am talking about.

Finally, I do realize that this message does not contain an official patch,
but I cannot provide one, as I am not set up to provide one. Several years
ago, I "retired from active duty" (Cygwin, not Linux); git(1) and anything
that is useful (required?) in order to be able to properly "submit a patch
to the list" is currently not present on my system.

Attachments: server.c, client.c

====
// gcc -Wall -Wextra -o client client.c

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

#define FIFO_PATH "/tmp/myfifo"

int
main(int argc, char *argv[])
{
    int fd = -1;

    fd = open(FIFO_PATH, O_WRONLY);
    printf("fd = %d\n", fd);

    if (fd < 0) {
        fprintf(stderr, "Could not open fifo %s: %s\n", FIFO_PATH, strerror(errno));
        return -1;
    }

    int flags = fcntl(fd, F_GETFL);
    if (flags == -1)
        fprintf(stderr, "fcntl F_GETFL: %s\n", strerror(errno));
    printf("flags = 0%o\n", flags);

    if (argc > 1) {
        write(fd, argv[1], strlen(argv[1]) );
    } else {
        write(fd, &"What ho!", 9);
    }
    // note: printf "What ho" > /tmp/myfifo from bash, would close the file descriptor.

    printf("Closing ... fd = %d\n", fd);
    close(fd); // EOF

    return 0;
}

//=====
// gcc -Wall -Wextra -o server server.c

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

#define FIFO_PATH "/tmp/myfifo"

int
main()
{
    int fd = -1;
    fd_set readfds;
    int nsel;
    char buf[80] = { 0 };

    if (unlink (FIFO_PATH) < 0  && errno != ENOENT) {
      perror ("unlink");
      exit (1);
    }

    if (mkfifo(FIFO_PATH, 0600) < 0) {
        fprintf(stderr, "Could not create fifo %s: %s\n", FIFO_PATH, strerror(errno));
        return -1;
    }

    fd = open(FIFO_PATH, O_RDONLY | O_NONBLOCK); // open() does not block
    printf("fd = %d\n", fd);

    if (fd < 0) {
        fprintf(stderr, "Could not open fifo %s: %s\n", FIFO_PATH, strerror(errno));
        return -1;
    }

    if (fcntl(fd, F_SETFL, O_RDONLY) == -1) // pragmatic
        fprintf(stderr, "fcntl F_SETFL: %s\n", strerror(errno));
    int flags = fcntl(fd, F_GETFL);
    if (flags == -1)
        fprintf(stderr, "fcntl F_GETFL: %s\n", strerror(errno));
    printf("flags = 0%o\n", flags);

    while(1) {
        printf("Calling select() ... ");
        fflush(stdout);
        FD_ZERO (&readfds);
        FD_SET (fd, &readfds);

        /*
        Different from read(2), select(2) will *block* on the read end of a fifo (or
        pipe) if the write end has never been opened before (and only in that case),
        while read(2) will always return with zero (in case of an empty pipe) if the
        write end is closed/ has been closed.

        Therefore, calling select(2) before calling read(2), will enable us to block
        if the pipe has never been openend before (again, and only in that case).
        */

        nsel = select (fd + 1, &readfds, NULL, NULL, NULL);
        // select() blocks until the write end of the pipe has been opened (while the
        // write end has never been opened before) and data has been injected.
        printf ("returned %d\n", nsel);

        printf("Reading ...\n");

        ssize_t status = 0;
        while (1) { // allow me to reset errno and inspect status ...
errno = 0;

            /*
             read(2) will block on the read end of a fifo (or pipe) in case the write end
             of the fifo (or pipe) has been opened and the pipe is empty. [1]
             [1] only if the O_NONBLOCK open file status flag has been disabled.
             */

            status = read(fd, buf, sizeof(buf));
printf("status = %ld, errno = %d\n", status, errno);

            // will arrive here only if the write end of fifo has been opened (before it
            // was closed again)
            if (status == 0) { // the write end of the fifo has been closed (EOF)
                close(fd);
                int fd2;
                if ((fd2 = open (FIFO_PATH, O_RDONLY | O_NONBLOCK)) < 0) {
                    perror ("open");
                    exit (1);
                }

                if (fcntl(fd, F_SETFL, O_RDONLY) == -1) // pragmatic
                    fprintf(stderr, "fcntl F_SETFL: %s\n", strerror(errno));
                int flags = fcntl(fd, F_GETFL);
                if (flags == -1)
                    fprintf(stderr, "fcntl F_GETFL: %s\n", strerror(errno));
                printf("flags = 0%o\n", flags);

                /*
                 assign the new file descriptor to variable fd; otherwise select(2) will
                 not block on the read end in case the write end of the fifo (or pipe) is
                 closed.
                 */
                fd = fd2;

                printf("fd = %d - refreshed\n", fd);
                break;
            }
            if (status > 0) {
                if (write(1, buf, status) < 0) {
                    fprintf(stderr, "Error sending message: '%s': %s\n", buf, strerror(errno));
                }
                if (buf[status - 1] != '\n') {
                    write(1, &"\n", 1);
                }
                if (strncasecmp(buf, "quit", 4) == 0) {
                    close(fd);
                    remove(FIFO_PATH);
                    exit (0);
                }
             }
            if (status < 0) {
                /* An error occurred, bail out */
                close(fd);
                perror("read");
                exit (1);
            }
        } // end while(1) read

    } // end while(!done) select

    close(fd);
    remove(FIFO_PATH);

    return 0;
}

//=====

[Index of Archives]     [Kernel Documentation]     [Netdev]     [Linux Ethernet Bridging]     [Linux Wireless]     [Kernel Newbies]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux