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