All of lore.kernel.org
 help / color / mirror / Atom feed
* Fwd: Simple changes to select(2) and pipe(7) - example program
       [not found] <30847211-efc7-12be-6ce9-c5c4ada16805@xs4all.nl>
@ 2022-11-09 13:57 ` Alejandro Colomar
  2022-11-09 14:02   ` Alejandro Colomar
  2022-11-09 14:15   ` Alejandro Colomar
  0 siblings, 2 replies; 10+ messages in thread
From: Alejandro Colomar @ 2022-11-09 13:57 UTC (permalink / raw)
  To: J.H. vd Water, linux-man


[-- Attachment #1.1.1: Type: text/plain, Size: 1697 bytes --]

-------- Forwarded Message --------
Subject: Re: Simple changes to select(2) and pipe(7) - example program
Date: Wed, 9 Nov 2022 09:42:43 +0100
From: J.H. vd Water <henri.van.de.water@xs4all.nl>
To: Alejandro Colomar <alx@kernel.org>
CC: Michael Kerrisk <mtk.manpages@gmail.com>

On 11/8/22 13:20, Alejandro Colomar wrote:
> On 11/6/22 19:53, J.H. vd Water wrote:
[snip]

>> 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).
> 
> It is interesting.  Could you please share a small example program
> that demonstrates this behavior?  That would certainly help a lot
> reviewing the change.

Hi Alex,

Yesterday, I replied to Alejandro Colomar <alx.manpages@gmail.com>; I also sent
you a copy of the message I sent to M.K. on 29th of September (clarification).

This time I will attach 2 files (i.e. server.c and client.c), the small example
program that you asked for (using alx@kernel.org as address).

Regards,
Henri

[-- Attachment #1.1.2: client.c --]
[-- Type: text/x-csrc, Size: 919 bytes --]

// 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;
}

//=====

[-- Attachment #1.1.3: server.c --]
[-- Type: text/x-csrc, Size: 4452 bytes --]

// 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;
}

//=====

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: Fwd: Simple changes to select(2) and pipe(7) - example program
  2022-11-09 13:57 ` Fwd: Simple changes to select(2) and pipe(7) - example program Alejandro Colomar
@ 2022-11-09 14:02   ` Alejandro Colomar
  2022-11-09 14:04     ` Alejandro Colomar
  2022-11-09 14:15   ` Alejandro Colomar
  1 sibling, 1 reply; 10+ messages in thread
From: Alejandro Colomar @ 2022-11-09 14:02 UTC (permalink / raw)
  To: J.H. vd Water, linux-man; +Cc: Ken Brown, Michael Kerrisk, Michael T. Kerrisk


[-- Attachment #1.1: Type: text/plain, Size: 3118 bytes --]

-------- Forwarded Message --------
Subject: Re: Simple changes to select(2) and pipe(7) -- the message I sent to 
M.K. some time ago.
Date: Tue, 8 Nov 2022 15:58:27 +0100
From: J.H. vd Water <henri.van.de.water@xs4all.nl>
To: Alejandro Colomar <alx.manpages@gmail.com>
CC: mtk.manpages@gmail.com

Alex,

Below the message that I sent to M.K. some time ago; the following subject was used:

"LPI 63.2.3 (when is a file descriptor ready?) /Just a note (for the errata?)"

This message shows 2 things:

  1. it was not me that found the man pages (and LPI) lacking ...
  2. after some effort by me, I decided that Ken's claim was correct and that 
the man
     pages ( select(2) and pipe(7) ) needed to be improved ...

Regards,
Henri

-------- Forwarded Message --------
Subject: LPI 63.2.3 (when is a file descriptor ready?) /Just a note (for the 
errata?)
Date: Thu, 29 Sep 2022 14:31:45 +0200
From: J.H. vd Water <henri.van.de.water@xs4all.nl>
To: mtk@man7.org

Dear Mr. Kerrisk, Michael

I decide to drop you a note about LPI/ select() ...

  - 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)

Recently I have been reading (and rereading)

  - LPI ch44 (Pipes and FIFOs)
    especially ch44.7 (FIFOs), ch44.9 (Non blocking I/O) and
    ch44.9 (Semantics of read() and write() on pipes and FIFOs)

  - LPI ch63 (alternative I/O methods)
    especially ch63.2 (I/O Multiplexing), ch63.2.1 (The select()
    system call and ch63.2.3 (When is a file descriptor ready?)

and the associated manual pages ...

(and wrote code that confirms that a note should be included with
  the "official documentation")

Why? Because of the problem that was reported against Ken Brown's
implementation of select() (Cygwin).

---
 From LPI ch63.2.3 (When is a file descriptor ready?) and from the
manual pages, I got the "impression" (and so did Ken Brown), that

     the read end of a FIFO is "read ready" when the write end of
     the FIFO is (still) closed.

However that turned to be true ONLY AFTER the FIFO has been opened
and closed once ...

True where? It turned to be true for Linux and the "other Unixes".

Any proof you may ask?

Ken Brown implemented select() (Cygwin) according to the "official
documentation".

The thread (see the above URL) makes it clear, that Linux and the
"other Unixes" behave different (how? see above) from Ken Brown's
implementation of select().

Ken B. modified his implementation of select() (see URL). The URL
includes the test program he used to verify his modification.

Bottom-line: the official documentation (LPI, manual pages) should
include a note:

     the read end of a FIFO is NOT "read ready" when the write end
     of the FIFO has never been opened (still closed)
     (when the write end has been opened and closed once, then the
      read end will be "read ready" from then on)

With kind regards,

Henri (J.H. vd Water)

-----

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: Fwd: Simple changes to select(2) and pipe(7) - example program
  2022-11-09 14:02   ` Alejandro Colomar
@ 2022-11-09 14:04     ` Alejandro Colomar
  2022-11-09 14:04       ` Alejandro Colomar
  0 siblings, 1 reply; 10+ messages in thread
From: Alejandro Colomar @ 2022-11-09 14:04 UTC (permalink / raw)
  To: J.H. vd Water, linux-man; +Cc: Ken Brown, Michael Kerrisk, Michael T. Kerrisk


[-- Attachment #1.1: Type: text/plain, Size: 2067 bytes --]

-------- Forwarded Message --------
Subject: Re: Simple changes to select(2) and pipe(7)
Date: Tue, 8 Nov 2022 15:58:16 +0100
From: J.H. vd Water <henri.van.de.water@xs4all.nl>
To: Alejandro Colomar <alx.manpages@gmail.com>
CC: mtk.manpages@gmail.com

On 11/8/22 13:20, Alejandro Colomar wrote:
 > On 11/6/22 19:53, J.H. vd Water wrote:
[snip]

 >> 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).
 >
 > It is interesting.  Could you please share a small example program
 > that demonstrates this behavior?  That would certainly help a lot
 > reviewing the change.

Hi Alex,

As a starter I will send you a copy of the message that I sent to Michael some
time ago (He did not respond - he must be too busy).

As I wrote to M., I verified the findings by Ken Brown on Linux (Fedora 35).

Ken Brown's (www.cygwin.com) implementation of select(2) was based on what he
learned from the man pages (and M.K.'s LPI).

Ken was very surprised when he learned about select's behaviour when the write
end of the pipe had not been opened before (and so was I).

Ken corrected his implementation of select(2) and (of course) provided a test
program in order to verify his correction.

You will find that test program in the thread that is mentioned in my message
to M.

Regards,
Henri

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: Fwd: Simple changes to select(2) and pipe(7) - example program
  2022-11-09 14:04     ` Alejandro Colomar
@ 2022-11-09 14:04       ` Alejandro Colomar
  0 siblings, 0 replies; 10+ messages in thread
From: Alejandro Colomar @ 2022-11-09 14:04 UTC (permalink / raw)
  To: J.H. vd Water, linux-man; +Cc: Ken Brown, Michael Kerrisk, Michael T. Kerrisk


[-- Attachment #1.1: Type: text/plain, Size: 1792 bytes --]

-------- Forwarded Message --------
Subject: Simple changes to select(2) and pipe(7)
Date: Sun, 6 Nov 2022 19:53:35 +0100
From: J.H. vd Water <henri.van.de.water@xs4all.nl>
To: mtk.manpages@gmail.com
CC: alx.manpages@gmail.com

Hi,

I am following the instructions from here:

  - https://www.kernel.org/doc/man-pages/reporting_bugs.html

"Simple one-line or two-line errors ... can be reported by sending an email
  message to the maintainers ..."

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).

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

Regards,
Henri

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: Fwd: Simple changes to select(2) and pipe(7) - example program
  2022-11-09 13:57 ` Fwd: Simple changes to select(2) and pipe(7) - example program Alejandro Colomar
  2022-11-09 14:02   ` Alejandro Colomar
@ 2022-11-09 14:15   ` Alejandro Colomar
  2022-11-09 15:06     ` J.H. vd Water
  1 sibling, 1 reply; 10+ messages in thread
From: Alejandro Colomar @ 2022-11-09 14:15 UTC (permalink / raw)
  To: J.H. vd Water, linux-man; +Cc: Ken Brown, Michael Kerrisk, Michael T. Kerrisk


[-- Attachment #1.1: Type: text/plain, Size: 3107 bytes --]

Hi Henri,

On 11/9/22 14:57, Alejandro Colomar wrote:
> -------- Forwarded Message --------
> Subject: Re: Simple changes to select(2) and pipe(7) - example program
> Date: Wed, 9 Nov 2022 09:42:43 +0100
> From: J.H. vd Water <henri.van.de.water@xs4all.nl>
> To: Alejandro Colomar <alx@kernel.org>
> CC: Michael Kerrisk <mtk.manpages@gmail.com>

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.

I've also included Ken in the CC list, as the CONTRIBUTING file recommends, 
since he knows what we're discussing.

I've included both mtk addresses, since this issue is both reported for the man 
pages and for his TLPI book.  But as you've experienced, he's recently been very 
busy and might not respond to this thread.

> 
> On 11/8/22 13:20, Alejandro Colomar wrote:
>> On 11/6/22 19:53, J.H. vd Water wrote:
> [snip]
> 
>>> 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).
>>
>> It is interesting.  Could you please share a small example program
>> that demonstrates this behavior?  That would certainly help a lot
>> reviewing the change.
> 
> Hi Alex,
> 
> Yesterday, I replied to Alejandro Colomar <alx.manpages@gmail.com>; I also sent
> you a copy of the message I sent to M.K. on 29th of September (clarification).

Yeah, I see.  For now both my addresses arrive to the same mailbox, so no 
problem there.  Thanks.

The mailing list is more important, though.


> 
> This time I will attach 2 files (i.e. server.c and client.c), the small example
> program that you asked for (using alx@kernel.org as address).

Thanks!

BTW, I just found a bug in the client.c program:

     write(fd, &"What ho!", 9);

That & is wrong.  The address of the array should not be passed to write(2), but 
rather the address to the first element in the array.  This is a mild bug that 
will normally not do any harm, but can be dangerous still, if your compiler is 
optimizing heavily.

I'll take some time to investigate the example programs, since these days I 
don't have much time for that; sorry.  Feel free to ping me in a week or two if 
I didn't respond.

Cheers,

Alex

> 
> Regards,
> Henri

-- 
<http://www.alejandro-colomar.es/>

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: Fwd: Simple changes to select(2) and pipe(7) - example program
  2022-11-09 14:15   ` Alejandro Colomar
@ 2022-11-09 15:06     ` J.H. vd Water
  2022-11-09 15:32       ` J.H. vd Water
  2022-11-22 12:22       ` Simple changes to select(2) and pipe(7) J.H. vd Water
  0 siblings, 2 replies; 10+ messages in thread
From: J.H. vd Water @ 2022-11-09 15:06 UTC (permalink / raw)
  To: Alejandro Colomar, linux-man
  Cc: Ken Brown, Michael Kerrisk, Michael T. Kerrisk

[-- Attachment #1: Type: text/plain, Size: 3618 bytes --]

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@kernel.org> <==== different e-mail address
To: Michael Kerrisk <mtk.manpages@gmail.com>
Cc: linux-man@vger.kernel.org
Cc: Ken Brown <kbrown@cornell.edu>

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

====

[-- Attachment #2: client.c --]
[-- Type: text/x-csrc, Size: 919 bytes --]

// 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;
}

//=====

[-- Attachment #3: server.c --]
[-- Type: text/x-csrc, Size: 4449 bytes --]

// 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;
}

//=====

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: Fwd: Simple changes to select(2) and pipe(7) - example program
  2022-11-09 15:06     ` J.H. vd Water
@ 2022-11-09 15:32       ` J.H. vd Water
  2022-11-22 12:22       ` Simple changes to select(2) and pipe(7) J.H. vd Water
  1 sibling, 0 replies; 10+ messages in thread
From: J.H. vd Water @ 2022-11-09 15:32 UTC (permalink / raw)
  To: Alejandro Colomar; +Cc: linux-man

On 11/9/22 16:06, J.H. vd Water wrote:
> 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)

Sigh! Sorry, I am *NOT* that quick anymore ...

Henri

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: Simple changes to select(2) and pipe(7)
  2022-11-09 15:06     ` J.H. vd Water
  2022-11-09 15:32       ` J.H. vd Water
@ 2022-11-22 12:22       ` J.H. vd Water
  2022-11-22 12:25         ` J.H. vd Water
  1 sibling, 1 reply; 10+ messages in thread
From: J.H. vd Water @ 2022-11-22 12:22 UTC (permalink / raw)
  To: Alejandro Colomar, linux-man
  Cc: Ken Brown, Michael Kerrisk, Michael T. Kerrisk

[-- Attachment #1: Type: text/plain, Size: 3161 bytes --]

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

======

[-- Attachment #2: client.c --]
[-- Type: text/x-csrc, Size: 918 bytes --]

// 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;
}

//=====

[-- Attachment #3: serverb.c --]
[-- Type: text/x-csrc, Size: 5488 bytes --]

// 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;
}

//=====

[-- Attachment #4: servernb.c --]
[-- Type: text/x-csrc, Size: 4870 bytes --]

// 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;
}

//=====

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: Simple changes to select(2) and pipe(7)
  2022-11-22 12:22       ` Simple changes to select(2) and pipe(7) J.H. vd Water
@ 2022-11-22 12:25         ` J.H. vd Water
  2022-12-01 14:39           ` J.H. vd Water
  0 siblings, 1 reply; 10+ messages in thread
From: J.H. vd Water @ 2022-11-22 12:25 UTC (permalink / raw)
  To: Alejandro Colomar, linux-man
  Cc: Ken Brown, Michael Kerrisk, Michael T. Kerrisk

[-- Attachment #1: Type: text/plain, Size: 242 bytes --]

On 11/22/22 13:22, J.H. vd Water wrote:
> Hi Alex,
> 
> My modification of pipe.7 (see below) has been revised in order to include both
> blocking and non-blocking read(2).

Attempt to attach the associated state diagrams ...

Regards,
Henri

[-- Attachment #2: select-blocking-read.avif --]
[-- Type: image/avif, Size: 34353 bytes --]

[-- Attachment #3: select-non-blocking-read.avif --]
[-- Type: image/avif, Size: 36238 bytes --]

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: Simple changes to select(2) and pipe(7)
  2022-11-22 12:25         ` J.H. vd Water
@ 2022-12-01 14:39           ` J.H. vd Water
  0 siblings, 0 replies; 10+ messages in thread
From: J.H. vd Water @ 2022-12-01 14:39 UTC (permalink / raw)
  To: Alejandro Colomar, linux-man
  Cc: Ken Brown, Michael Kerrisk, Michael T. Kerrisk

Hi Alex,

Additional information:

LPI 63.2.3 Comparison of select() and poll():

Implementation details

"Within the Linux kernel, select() and poll() both employ the same set of
 kernel-internal poll routines ..."

See also: https://github.com/torvalds/linux/blob/master/fs/select.c

Furthermore:

 - https://pubs.opengroup.org/onlinepubs/9699919799/functions/poll.html

DESCRIPTION
...
  POLLHUP
    A device has been disconnected, or a pipe or FIFO has been closed by the last process
    that had it open for writing. ...
...
RATIONALE

  The POLLHUP event does not occur for FIFOs just because the FIFO is not open for writing.
  It only occurs when the FIFO is closed by the last writer and persists until some process
  opens the FIFO for writing or until all read-only file descriptors for the FIFO are closed.

Next I decided to verify this on Linux (Fedora 35): poll(2) returns zero (after time out) on
a FIFO of which the write end is closed from the start ...,

meaning neither POLLIN nor POLLHUP have been set in revents.

Bottom-line: select(2) blocks on a FIFO of which the write end is closed from
the start (i.e. select.2 is wrong, or at least not complete).

Regards,
Henri

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2022-12-01 14:39 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <30847211-efc7-12be-6ce9-c5c4ada16805@xs4all.nl>
2022-11-09 13:57 ` Fwd: Simple changes to select(2) and pipe(7) - example program Alejandro Colomar
2022-11-09 14:02   ` Alejandro Colomar
2022-11-09 14:04     ` Alejandro Colomar
2022-11-09 14:04       ` Alejandro Colomar
2022-11-09 14:15   ` Alejandro Colomar
2022-11-09 15:06     ` J.H. vd Water
2022-11-09 15:32       ` J.H. vd Water
2022-11-22 12:22       ` Simple changes to select(2) and pipe(7) J.H. vd Water
2022-11-22 12:25         ` J.H. vd Water
2022-12-01 14:39           ` J.H. vd Water

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.