-------- 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@xxxxxxxxx> To: Alejandro Colomar <alx@xxxxxxxxxx> CC: Michael Kerrisk <mtk.manpages@xxxxxxxxx> 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@xxxxxxxxx>; 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@xxxxxxxxxx as address). Regards, Henri
// 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; } //=====
// 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:
OpenPGP_signature
Description: OpenPGP digital signature