FIFO fd not marked readable after EOF

classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

FIFO fd not marked readable after EOF

Dimitris Papastamos
Hi everyone,

I am using select(2) on a FIFO fd and monitoring
for readability.  select(2) doesn't return after the writer
exits.

The same piece of code marks the fd as readable on Linux.
Not sure which behaviour is correct though.

Doing a very similar test with stdin and closing it before
the call to select(2) behaves as expected and marks the fd
as readable.

I wrote the following PoC to demonstrate this.

Any ideas?

Thanks,
Dimitris

#include <sys/select.h>
#include <sys/stat.h>

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

#define FIFONAME "testfifo"

int
main(void)
{
        int r;
        int fd;
        fd_set rfds;

        r = mkfifo(FIFONAME, 0666);
        if (r < 0 && errno != EEXIST)
                err(1, "mkfifo: %s", FIFONAME);
        fd = open(FIFONAME, O_RDONLY | O_NONBLOCK);
        if (fd < 0)
                err(1, "open: %s", FIFONAME);
        FD_ZERO(&rfds);
        FD_SET(fd, &rfds);
        printf("Now run cat > %s and ^D\n", FIFONAME);
again:
        r = select(fd + 1, &rfds, NULL, NULL, NULL);
        if (r < 0) {
                if (errno == EINTR)
                        goto again;
                err(1, "select");
        }
        if (FD_ISSET(fd, &rfds))
                printf("fd is readable\n");
        close(fd);
        unlink(FIFONAME);
        return 0;
}

Reply | Threaded
Open this post in threaded view
|

Re: FIFO fd not marked readable after EOF

Dimitris Papastamos
On Mon, Oct 06, 2014 at 01:08:57PM +0100, Dimitris Papastamos wrote:
> The same piece of code marks the fd as readable on Linux.

I tested on NetBSD and it also seems to mark the fd readable.

> Doing a very similar test with stdin and closing it before
> the call to select(2) behaves as expected and marks the fd
> as readable.

To clarify, I meant calling select(2) on stdin and triggering
EOF marks the fd as readable.

Reply | Threaded
Open this post in threaded view
|

Re: FIFO fd not marked readable after EOF

Matthew Dempsky-3
In reply to this post by Dimitris Papastamos
On Mon, Oct 6, 2014 at 5:08 AM, Dimitris Papastamos <[hidden email]> wrote:
> I am using select(2) on a FIFO fd and monitoring
> for readability.  select(2) doesn't return after the writer
> exits.
>
> The same piece of code marks the fd as readable on Linux.
> Not sure which behaviour is correct though.

The Linux behavior is correct:

http://pubs.opengroup.org/onlinepubs/9699919799/functions/pselect.html:
"A descriptor shall be considered ready for reading when a call to an
input function with O_NONBLOCK clear would not block, whether or not
the function would transfer data successfully. (The function might
return data, an end-of-file indication, or an error other than one
indicating that it is blocked, and in each of these cases the
descriptor shall be considered ready for reading.)"

http://pubs.opengroup.org/onlinepubs/9699919799/functions/read.html:
"When attempting to read from an empty pipe or FIFO: [...] If no
process has the pipe open for writing, read() shall return 0 to
indicate end-of-file."

Reply | Threaded
Open this post in threaded view
|

Re: FIFO fd not marked readable after EOF

Todd C. Miller
In reply to this post by Dimitris Papastamos
The following diff should fix it.

 - todd

Index: sys/miscfs/fifofs/fifo_vnops.c
===================================================================
RCS file: /home/cvs/openbsd/src/sys/miscfs/fifofs/fifo_vnops.c,v
retrieving revision 1.41
diff -u -r1.41 fifo_vnops.c
--- sys/miscfs/fifofs/fifo_vnops.c 14 Sep 2014 14:17:26 -0000 1.41
+++ sys/miscfs/fifofs/fifo_vnops.c 6 Oct 2014 22:51:50 -0000
@@ -147,8 +147,8 @@
  return (error);
  }
  fip->fi_readers = fip->fi_writers = 0;
+ wso->so_state |= SS_CANTSENDMORE;
  wso->so_snd.sb_lowat = PIPE_BUF;
- rso->so_state |= SS_CANTRCVMORE;
  }
  if (ap->a_mode & FREAD) {
  fip->fi_readers++;
@@ -287,24 +287,12 @@
 {
  struct vop_poll_args *ap = v;
  struct file filetmp;
- short ostate;
  int revents = 0;
 
  if (ap->a_events & (POLLIN | POLLPRI | POLLRDNORM | POLLRDBAND)) {
- /*
- * Socket and FIFO poll(2) semantics differ wrt EOF on read.
- * Unlike a normal socket, FIFOs don't care whether or not
- * SS_CANTRCVMORE is set.  To get the correct semantics we
- * must clear SS_CANTRCVMORE from so_state temporarily.
- */
- ostate = ap->a_vp->v_fifoinfo->fi_readsock->so_state;
- if (ap->a_events & (POLLIN | POLLRDNORM))
- ap->a_vp->v_fifoinfo->fi_readsock->so_state &=
-    ~SS_CANTRCVMORE;
  filetmp.f_data = ap->a_vp->v_fifoinfo->fi_readsock;
  if (filetmp.f_data)
  revents |= soo_poll(&filetmp, ap->a_events, ap->a_p);
- ap->a_vp->v_fifoinfo->fi_readsock->so_state = ostate;
  }
  if (ap->a_events & (POLLOUT | POLLWRNORM | POLLWRBAND)) {
  filetmp.f_data = ap->a_vp->v_fifoinfo->fi_writesock;

Reply | Threaded
Open this post in threaded view
|

Re: FIFO fd not marked readable after EOF

Dimitris Papastamos
On Mon, Oct 06, 2014 at 04:56:08PM -0600, Todd C. Miller wrote:

> The following diff should fix it.
>
>  - todd
>
> Index: sys/miscfs/fifofs/fifo_vnops.c
> ===================================================================
> RCS file: /home/cvs/openbsd/src/sys/miscfs/fifofs/fifo_vnops.c,v
> retrieving revision 1.41
> diff -u -r1.41 fifo_vnops.c
> --- sys/miscfs/fifofs/fifo_vnops.c 14 Sep 2014 14:17:26 -0000 1.41
> +++ sys/miscfs/fifofs/fifo_vnops.c 6 Oct 2014 22:51:50 -0000
> @@ -147,8 +147,8 @@
>   return (error);
>   }
>   fip->fi_readers = fip->fi_writers = 0;
> + wso->so_state |= SS_CANTSENDMORE;
>   wso->so_snd.sb_lowat = PIPE_BUF;
> - rso->so_state |= SS_CANTRCVMORE;
>   }
>   if (ap->a_mode & FREAD) {
>   fip->fi_readers++;
> @@ -287,24 +287,12 @@
>  {
>   struct vop_poll_args *ap = v;
>   struct file filetmp;
> - short ostate;
>   int revents = 0;
>  
>   if (ap->a_events & (POLLIN | POLLPRI | POLLRDNORM | POLLRDBAND)) {
> - /*
> - * Socket and FIFO poll(2) semantics differ wrt EOF on read.
> - * Unlike a normal socket, FIFOs don't care whether or not
> - * SS_CANTRCVMORE is set.  To get the correct semantics we
> - * must clear SS_CANTRCVMORE from so_state temporarily.
> - */
> - ostate = ap->a_vp->v_fifoinfo->fi_readsock->so_state;
> - if (ap->a_events & (POLLIN | POLLRDNORM))
> - ap->a_vp->v_fifoinfo->fi_readsock->so_state &=
> -    ~SS_CANTRCVMORE;
>   filetmp.f_data = ap->a_vp->v_fifoinfo->fi_readsock;
>   if (filetmp.f_data)
>   revents |= soo_poll(&filetmp, ap->a_events, ap->a_p);
> - ap->a_vp->v_fifoinfo->fi_readsock->so_state = ostate;
>   }
>   if (ap->a_events & (POLLOUT | POLLWRNORM | POLLWRBAND)) {
>   filetmp.f_data = ap->a_vp->v_fifoinfo->fi_writesock;

Thanks, that fixed it!

Reply | Threaded
Open this post in threaded view
|

Re: FIFO fd not marked readable after EOF

Todd C. Miller
In reply to this post by Dimitris Papastamos
Here is a better diff that passes the newly-updated regress test.
It does two extra things:

1) causes POLLHUP to be returned in revents on EOF

2) clears the EOF condition on read so when another writer
   connects we don't still have an EOF pending

Ultimately we should investigate using a separate named pipe
implementation instead of trying to emulate them using sockets.
Something like the FreeBSD approach of extending sys_pipe.c to
support named pipes is probably the way to go.

 - todd

Index: sys/miscfs/fifofs/fifo_vnops.c
===================================================================
RCS file: /cvs/src/sys/miscfs/fifofs/fifo_vnops.c,v
retrieving revision 1.41
diff -u -r1.41 fifo_vnops.c
--- sys/miscfs/fifofs/fifo_vnops.c 14 Sep 2014 14:17:26 -0000 1.41
+++ sys/miscfs/fifofs/fifo_vnops.c 9 Oct 2014 11:18:46 -0000
@@ -147,8 +147,8 @@
  return (error);
  }
  fip->fi_readers = fip->fi_writers = 0;
+ wso->so_state |= SS_CANTSENDMORE;
  wso->so_snd.sb_lowat = PIPE_BUF;
- rso->so_state |= SS_CANTRCVMORE;
  }
  if (ap->a_mode & FREAD) {
  fip->fi_readers++;
@@ -165,7 +165,7 @@
  goto bad;
  }
  if (fip->fi_writers == 1) {
- fip->fi_readsock->so_state &= ~SS_CANTRCVMORE;
+ fip->fi_readsock->so_state &= ~(SS_CANTRCVMORE|SS_ISDISCONNECTED);
  if (fip->fi_readers > 0)
  wakeup(&fip->fi_readers);
  }
@@ -224,6 +224,9 @@
     ap->a_vp->v_fifoinfo->fi_writers == 0)
  error = 0;
  }
+ /* Clear EOF indicator so we have a clean slate for a new writer. */
+ if (error == 0)
+ rso->so_state &= ~(SS_CANTRCVMORE|SS_ISDISCONNECTED);
  return (error);
 }
 
@@ -287,24 +290,12 @@
 {
  struct vop_poll_args *ap = v;
  struct file filetmp;
- short ostate;
  int revents = 0;
 
  if (ap->a_events & (POLLIN | POLLPRI | POLLRDNORM | POLLRDBAND)) {
- /*
- * Socket and FIFO poll(2) semantics differ wrt EOF on read.
- * Unlike a normal socket, FIFOs don't care whether or not
- * SS_CANTRCVMORE is set.  To get the correct semantics we
- * must clear SS_CANTRCVMORE from so_state temporarily.
- */
- ostate = ap->a_vp->v_fifoinfo->fi_readsock->so_state;
- if (ap->a_events & (POLLIN | POLLRDNORM))
- ap->a_vp->v_fifoinfo->fi_readsock->so_state &=
-    ~SS_CANTRCVMORE;
  filetmp.f_data = ap->a_vp->v_fifoinfo->fi_readsock;
  if (filetmp.f_data)
  revents |= soo_poll(&filetmp, ap->a_events, ap->a_p);
- ap->a_vp->v_fifoinfo->fi_readsock->so_state = ostate;
  }
  if (ap->a_events & (POLLOUT | POLLWRNORM | POLLWRBAND)) {
  filetmp.f_data = ap->a_vp->v_fifoinfo->fi_writesock;
@@ -344,8 +335,11 @@
  socantsendmore(fip->fi_writesock);
  }
  if (ap->a_fflag & FWRITE) {
- if (--fip->fi_writers == 0)
+ if (--fip->fi_writers == 0) {
+ /* SS_ISDISCONNECTED will result in POLLHUP */
+ fip->fi_readsock->so_state |= SS_ISDISCONNECTED;
  socantrcvmore(fip->fi_readsock);
+ }
  }
  if (fip->fi_readers == 0 && fip->fi_writers == 0) {
  error1 = soclose(fip->fi_readsock);