Fwd: Simple changes to select(2) and pipe(7) - example program

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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


[Index of Archives]     [Kernel Documentation]     [Netdev]     [Linux Ethernet Bridging]     [Linux Wireless]     [Kernel Newbies]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux