Re: Simple changes to select(2) and pipe(7)

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

 



Hi Alex,

My modification of pipe.7 (see below) has been revised in order to include both
blocking and non-blocking read(2).

Attached are the files serverb.c and servernb.c. Mostly comment has been revised
in the first one (file was called server.c); in the second file, read(2) is used
non-blocking (nb = non-blocking). (previous posts did not include servernb.c)

(client.c is also attached once more)

Next I will try to provide a proper patch (which will take me some time).

---
Proposed modification of pipe.7: see bottom.

Regards,
Henri

On 11/9/22 16:06, J.H. vd Water wrote:
[snip]

> 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 of blocking read(2) ), until data
 is available, and fail with error EAGAIN (in case of nonblocking read(2) );
 however, if the write end of the pipe is closed and the pipe is empty, then
 read(2) will return with zero."

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

#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 serverb.c -o serverb

#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"

// Use read(2) blocking ...
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);

        /*
         While read(2) will *always* return with zero (in case of an empty pipe) if
         the write end of a fifo (or pipe) is closed/ has been closed,
         select(2) will *block* on the read end of a fifo (or pipe) if the write end
         has never been opened before (but only in that case),
         Because of this difference in behaviour, calling select(2) before calling
         read(2), enable us to block if the pipe has never been openend before (but
         note, select(2) does return if the write end has been opened and closed).
        */

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

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

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

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

            /*
             we arrive here when data becomes available (and the write end is open)
             */
            status = read(fd, buf, sizeof(buf));
printf("status = %ld, errno = %d\n", status, errno);

            if (status == 0) { // the write end of the fifo has been closed (EOF)
                /*
                 we arrive here when the write end is closed (after the write end had
                 been opened and data became available)
                 */
#if 1 // file descriptor must be refreshed; otherwise select(2) will not block
                int fd2;
                if ((fd2 = open (FIFO_PATH, O_RDONLY | O_NONBLOCK)) < 0) {
                    close(fd);
                    perror ("open");
                    exit (1);
                }

                /*
                 now that the write end has been closed - after it had been opened
                 before, select(2) will not block (as before);
                 in order to make select(2) block again, assign the file descriptor
                 in fd2 to fd; otherwise an "endless loop" will result.
                 (fd will now refer to the recently created open file status)
                 */

                close(fd); // do not close before another file descriptor is available
                fd = fd2; // required (fd2 is opened before fd is closed)

                printf("fd = %d - refreshed\n", fd);
#endif
                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);

                break;
            }
            if (status > 0) {
                /*
                 we arrive here when data becomes available (after the write end
                 was opened)
                 */
                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;
}

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

#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"

// Use read(2) NON-blocking ...
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 0 // disabled: non-blocking read
    if (fcntl(fd, F_SETFL, O_RDONLY) == -1) // pragmatic
        fprintf(stderr, "fcntl F_SETFL: %s\n", strerror(errno));
#endif
    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);

        /*
         see comment in serverb.c ...
         */

        /*
         see comment in serverb.c ...
         */
         /*
          select(2) still blocks after returning from read(2) (which happened when
          data became available) after the pipe became empty again.
          */
        nsel = select (fd + 1, &readfds, NULL, NULL, NULL);
        printf ("returned %d\n", nsel);

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

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

            /*
             see comment in serverb.c ...
             */

            /*
             we arrive here when data becomes available (and the write end is open)
             */
            status = read(fd, buf, sizeof(buf));
printf("status = %ld, errno = %d\n", status, errno);

            if (status == 0) { // the write end of the fifo has been closed (EOF)
                /*
                 we arrive here when the write end is closed (after the write end had
                 been opened and data became available)
                 */
#if 1 // file descriptor must be refreshed; otherwise select(2) will not block
                int fd2;
                if ((fd2 = open (FIFO_PATH, O_RDONLY | O_NONBLOCK)) < 0) {
                    close(fd);
                    perror ("open");
                    exit (1);
                }

                /*
                 see comment in serverb.c ...
                 */

                close(fd); // do not close before another file descriptor is available
                fd = fd2; // required (fd2 is opened before fd is closed)

                printf("fd = %d - refreshed\n", fd);
#endif
#if 0 // disabled: non-blocking read
                if (fcntl(fd, F_SETFL, O_RDONLY) == -1) // pragmatic
                    fprintf(stderr, "fcntl F_SETFL: %s\n", strerror(errno));
#endif
                int flags = fcntl(fd, F_GETFL);
                if (flags == -1)
                    fprintf(stderr, "fcntl F_GETFL: %s\n", strerror(errno));
                printf("flags = 0%o\n", flags);

                break;
            }
            if (status > 0) {
                /*
                 we arrive here when data becomes available (after the write end
                 was opened)
                 */
                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) {
                if (errno == EAGAIN) { // added for the non-blocking case!
                    /*
                     we arrive here when the pipe becomes empty (after data became
                     available when the write end was opened)
                     */
                    printf("Got EAGAIN ...\n");
                    // would show up in case write end is not closed immediately
                    errno = 0;
                    break;
                }
                /* 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