kern/94772: FIFOs (named pipes) + select() == broken

Oliver Fromme olli at secnetix.de
Tue Mar 21 10:00:31 UTC 2006


>Number:         94772
>Category:       kern
>Synopsis:       FIFOs (named pipes) + select() == broken
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Tue Mar 21 10:00:29 GMT 2006
>Closed-Date:
>Last-Modified:
>Originator:     Oliver Fromme
>Release:        FreeBSD 6.1-PRERELEASE i386
>Organization:
secnetix GmbH & Co. KG
		http://www.secnetix.de/bsd
>Environment:
System: FreeBSD epia.fromme.com 6.1-PRERELEASE FreeBSD 6.1-PRERELEASE #0: Tue Mar 21 10:21:23 CET 2006 olli at epia.fromme.com:/usr/src/sys/i386/compile/EPIA i386

I'm using the latest RELENG_6 from today (March 21, 2006).

>Description:

I recently wondered why several of my scripts that use
a named pipe (FIFO) don't work on FreeBSD.

After some debugging it turned out that select() seems
to be broken when used with FIFOs on FreeBSD 6.
Particularly, this is the bug I'm suffering from:

When a FIFO had been opened for reading and the writing
process closes it, the reading process blocks in select(),
even though the descriptor is ready for read().  If the
select() call is omitted, read() returns 0 immediately
indicating EOF.  But as soon as you use select(), it blocks
and there is no chance to detect the EOF condition.

That's clearly a serious violation of POSIX, SUSv3,
Stevens APUE and all other documentations about select()
and named pipes that I'm aware of.  It needs to be fixed.

>How-To-Repeat:

Please see the small test program below.  Compile it like this:
cc -O -o fifotest fifotest.c

Then create a named pipe, e.g.:  $ mkfifo fifo
And run the test program:  ./fifotest fifo
It will block on the open(), which is to be expected
(correct behaviour so far).

Then open another shell (e.g. second terminal window)
and type:   echo foo > fifo

You will see from the output of the fifotest program
that the open() succeeds, the select() returns 1, and
the read() returns 4 bytes ("foo\n").  But then the
next call to select() blocks, even though there is
an EOF condition!

The same test program (with "err()" replaced by a small
self-made function) runs without error on all other UNIX
systems that I've tried:  Linux 2.4.32, Solaris 10, and
DEC UNIX 4.0 (predecessor of Tru64).  By the way, it's
even sufficient to do "cat /dev/null > fifo", i.e. not
writing anything at all, but issuing EOF immediately.
Under FreeBSD, nothing happens at all in that case.
All other UNIX systems recognize EOF (select() returns).

The source contains a #define WITH_SELECT.  When you
undefine it, select() won't be called, only read().
Then the program runs fine and detects the EOF condition
correctly.

Here's the source code.  In case it is mangled somehow
by send-pr, I've put a copy on this web page:
http://www.secnetix.de/~olli/tmp/fifotest.c

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

#define WITH_SELECT

int
main (int argc, char *argv[])
{
        int fd_in, result;
        char buffer[4096];

        if (argc != 2)
                errx (1, "Usage:  %s <fifo>\n", argv[0]);

        fprintf (stderr, "Opening FIFO for reading (might block) ...\n");

        if ((fd_in = open(argv[1], O_RDONLY, 0666)) < 0)
                err (1, argv[1]);

        fprintf (stderr, "FIFO opened successfully.\n");

        for (;;) {

#ifdef WITH_SELECT
                fd_set fds_r;

                FD_ZERO (&fds_r);
                FD_SET (fd_in, &fds_r);

                fprintf (stderr, "Calling select(read FD %d) ...\n", fd_in);

                if ((result = select(fd_in + 1, &fds_r, NULL, NULL, NULL)) < 0)
                        err (1, "select()");

                fprintf (stderr, "... return value is %d.\n", result);

                if (! FD_ISSET(fd_in, &fds_r))
                        continue;
#endif
                result = read(fd_in, buffer, 4096);
                fprintf (stderr, "read() returned %d bytes.\n", result);
                if (result < 1) {
                        if (result < 0)
                                err (1, "read()");
                        break;
                }
        }

        fprintf (stderr, "Got EOF!\n");
        close (fd_in);
        return 0;
}

/* END OF SOURCE */

>Fix:

None, known, unfortunately.  :-(

>Release-Note:
>Audit-Trail:
>Unformatted:


More information about the freebsd-bugs mailing list