# NAME

libslack(fio) - fifo and file control module and some I/O

# SYNOPSIS

        #include <slack/std.h>
        #include <slack/fio.h>

        char *fgetline(char *line, size_t size, FILE *stream);
        char *fgetline_unlocked(char *line, size_t size, FILE *stream);
        int read_timeout(int fd, long sec, long usec);
        int write_timeout(int fd, long sec, long usec);
        int rw_timeout(int fd, long sec, long usec);
        int nap(long sec, long usec);
        int fcntl_set_flag(int fd, int flag);
        int fcntl_clear_flag(int fd, int flag);
        int fcntl_set_fdflag(int fd, int flag);
        int fcntl_clear_fdflag(int fd, int flag);
        int fcntl_lock(int fd, int cmd, int type, int whence, int start, int len);
        int nonblock_set(int fd, int arg);
        int nonblock_on(int fd);
        int nonblock_off(int fd);
        int fifo_exists(const char *path, int prepare);
        int fifo_has_reader(const char *path, int prepare);
        int fifo_open(const char *path, mode_t mode, int lock, int *writefd);

# DESCRIPTION

This module provides various I/O related functions: reading a line of text no matter what line endings are used; timeouts for read/write operations without signals; exclusively opening a fifo for reading; and some random shorthand functions for manipulating file flags and locks.

`char *fgetline(char *line, size_t size, FILE *stream)`

  Similar to fgets(3) except that it recognises UNIX (`"\n"`), DOS/Windows (`"\r\n"`) and old Macintosh (`"\r"`) line endings (even different line endings in the same file). Reads bytes from `stream` and stores them in the buffer pointed to by `line`. Reading stops after an `EOF`, or the end of the line is reached, or when `size - 1` bytes have been stored. If the end of the line was reached, it is stored as a `"\n"` byte. A `nul` byte is stored after the last byte in the buffer. On success, returns `line`. On error, or when the end of file occurs while no bytes have been read, returns `null`. Note that even when `null` is returned, `line` is modified and will always be `nul`-terminated. So it is safe to examine `line` even when this function returns `null`. Calls to this function can be mixed with calls to other input functions from the stdio library for the same input stream. This is a drop-in replacement for fgets(3).

          char line[BUFSIZ];
          while (fgetline(line, BUFSIZ, stdin))
              printf("%s", line);

`char *fgetline_unlocked(char *line, size_t size, FILE *stream)`

  Equivalent to fgetline(3) except that `stream` is not locked.

`int read_timeout(int fd, long sec, long usec)`

  Performs a select(2) on a single file descriptor, `fd`, for reading and exceptions (i.e. arrival of urgent data), that times out after `sec` seconds and `usec` microseconds. This is just a shorthand function to provide a simple timed read(2) (or readv(2) or accept(2) or recv(2) or recvfrom(2) or recvmsg(2) without resorting to alarm(3) and `SIGALRM` signals (best avoided). On success, returns `0`. On error, returns `-1` with `errno` set appropriately (`ETIMEDOUT` if it timed out, otherwise set by select(2)). Usage:

          if (read_timeout(fd, 5, 0) == -1 || (bytes = read(fd, buf, count)) == -1)
              return -1;

`int write_timeout(int fd, long sec, long usec)`

  Performs a select(2) on a single file descriptor, `fd`, for writing, that times out after `sec` seconds and `usec` microseconds. This is just a shorthand function to provide a simple timed write(2) (or writev(2) or send(2) or sendto(2) or sendmsg(2)) without resorting to alarm(3) and `SIGALRM` signals (best avoided). On success, returns `0`. On error, returns `-1` with `errno` set appropriately (`ETIMEDOUT` if it timed out, otherwise set by select(2)). Usage:

          if (write_timeout(fd, 5, 0) == -1 || (bytes = write(fd, buf, count)) == -1)
              return -1;

`int rw_timeout(int fd, long sec, long usec)`

  Performs a select(2) on a single file descriptor, `fd`, for reading, writing and exceptions (i.e. arrival of urgent data), that times out after `sec` seconds and `usec` microseconds. This is just a shorthand function to provide a simple timed read(2) or write(2) without resorting to alarm(3) and `SIGALRM` signals (best avoided). On success, returns a bit mask indicating whether `fd` is readable (`R_OK`), writable (`W_OK`) and/or has urgent data available (`X_OK`). On error, returns `-1` with `errno` set appropriately (`ETIMEDOUT` if it timed out, otherwise set by select(2)).

          if ((mask = rw_timeout(fd, 5, 0)) == -1)
              return -1;

          if ((mask & W_OK) && (bytes = write(fd, buf, count)) == -1)
              return -1;

          if ((mask & R_OK) && (bytes = read(fd, buf, count)) == -1)
              return -1;

`int nap(long sec, long usec)`

  Puts the process to sleep for `sec` seconds and `usec` microseconds. Note, however, that many systems' timers only have 10ms resolution. This uses select(3) to ensure that alarm(3) and `SIGALRM` signals are not used (best avoided). On success, returns `0`. On error, returns `-1` with `errno` set appropriately.

          nap(1, 500000); // Sleep for 1.5 seconds
          nap(0, 100000); // Sleep for 0.1 seconds

`int fcntl_set_flag(int fd, int flag)`

  Shorthand for setting the file status flag, `flag`, on the file descriptor, `fd`, using fcntl(2). All other file status flags are unaffected. On success, returns `0`. On error, returns `-1` with `errno` set by fcntl(2) with `F_GETFL` or `F_SETFL` as the command. Example file status flags are `O_APPEND`, `O_ASYNC`, `O_DIRECT`, `O_NOATIME`, and `O_NONBLOCK` depending on the system.

`int fcntl_clear_flag(int fd, int flag)`

  Shorthand for clearing the file status flag, `flag`, from the file descriptor, `fd`, using fcntl(2). All other file status flags are unaffected. On success, returns `0`. On error, returns `-1` with `errno` set by fcntl(2) with `F_GETFL` or `F_SETFL` as the command. Example file status flags are `O_APPEND`, `O_ASYNC`, `O_DIRECT`, `O_NOATIME`, and `O_NONBLOCK` depending on the system.

`int fcntl_set_fdflag(int fd, int flag)`

  Shorthand for setting the file descriptor flag, `flag`, on the file descriptor, `fd`, using fcntl(2). All other file descriptor flags are unaffected. On success, returns `0`. On error, returns `-1` with `errno` set by fcntl(2) with `F_GETFD` or `F_SETFD` as the command. The only file descriptor flag at time of writing is `FD_CLOEXEC`.

`int fcntl_clear_fdflag(int fd, int flag)`

  Shorthand for clearing the file descriptor flag, `flag`, from the file descriptor, `fd`, using fcntl(2). All other file descriptor flags are unaffected. On success, returns `0`. On error, returns `-1` with `errno` set by fcntl(2) with `F_GETFD` or `F_SETFD` as the command. The only file descriptor flag at time of writing is `FD_CLOEXEC`.

`int fcntl_lock(int fd, int cmd, int type, int whence, int start, int len)`

  Shorthand for performing discretionary file locking operations on the file descriptor, `fd`. `cmd` is the locking command and is passed to fcntl(2). `type`, `whence`, `start` and `len` are used to fill a flock structure which is passed to fcntl(2). Returns the same as fcntl(2) with `cmd` as the command.

          if (fcntl_lock(fd, F_SETLK, F_WRLCK, SEEK_SET, 0, 0) == -1)
              return -1;

`int nonblock_set(int fd, int arg)`

  Sets non-blocking mode for the file descriptor, `fd`, if `arg` is non-zero. Sets blocking mode if `arg` is zero. On success, returns `0`. On error, returns `-1` with `errno` set by fcntl(2) with `F_GETFL` or `F_SETFL` as the command.

`int nonblock_on(int fd)`

  Sets non-blocking mode for the file descriptor, `fd`. On success, returns `0`. On error, returns `-1` with `errno` set by fcntl(2) with `F_GETFL` or `F_SETFL` as the command.

`int nonblock_off(int fd)`

  Sets blocking mode for the file descriptor, `fd`. On success, returns `0`. On error, returns `-1` with `errno` set by fcntl(2) with `F_GETFL` or `F_SETFL` as the command.

`int fifo_exists(const char *path, int prepare)`

  Determines whether or not `path` refers to a fifo. Returns `0` if `path` doesn't exist or doesn't refer to a fifo. If `path` refers to a fifo, returns 1. If `prepare` is non-zero, and `path` refers to a non-fifo, it will be unlinked. On error, returns `-1` with `errno` set by stat(2).

`int fifo_has_reader(const char *path, int prepare)`

  Determines whether or not `path` refers to a fifo that is being read by another process. If `path` does not exist, or does not refer to a fifo, or if the fifo can't be opened for non-blocking write(2), returns `0`. If `prepare` is non-zero, and path refers to a non-fifo, it will be unlinked. On error, returns `-1` with `errno` set by stat(2) or open(2).

`int fifo_open(const char *path, mode_t mode, int lock, int *writefd)`

  Creates a fifo named `path` with creation mode `mode` for reading. If `path` already exists, is a fifo, and has a reader process, returns `-1` with `errno` set to `EADDRINUSE`. If the fifo is created (or an existing one can be reused), two file descriptors are opened to the fifo. A read descriptor and a write descriptor. On success, returns the read descriptor. The write descriptor only exists to ensure that there is always at least one writer process for the fifo. This allows a read(2) on the read descriptor to block until another process writes to the fifo rather than returning an `EOF` condition. This is done in a POSIX-compliant way. If `lock` is non-zero, the fifo is exclusively locked. If `writefd` is not `null`, the write descriptor is stored there. On error, returns `-1` with `errno` set by stat(2), open(2), mkfifo(2), fstat(2) or fcntl(2).

          char *fifopath = "/tmp/fifo";
          int fd, wfd;

          if ((fd = fifo_open(fifopath, S_IRUSR | S_IWUSR | S_IWGRP | S_IWOTH, 1, &wfd)) == -1)
              return -1;

          // Read from fd...

          close(fd);
          close(wfd);
          unlink(fifopath);

# ERRORS

These functions set `errno` to the following values. `errno` may also be set by the underlying system calls. See their manpages for details.

`ETIMEDOUT`

  The read_timeout(3), write_timeout(3) and rw_timeout(3) functions set this when a timeout occurs.

`EADDRINUSE`

  fifo_open(3) sets this when the path refers to a fifo that already has another process reading from it.

# MT-Level

MT-Safe

Mac OS X doesn't have flockfile(3), funlockfile(3) or getc_unlocked(3) so fgetline(3) is not MT-Safe on such platforms. You must guard all stdio calls in multi-threaded programs with explicit synchronisation variables.

# EXAMPLES

A paranoid fgetline() example:

        #include <slack/std.h>
        #include <slack/fio.h>

        int main()
        {
            char line[BUFSIZ];

            while (fgetline(line, BUFSIZ, stdin))
                printf("%s", line);

            if (ferror(stdin))
            {
                if (!*line)
                    printf("%s\n", line);

                return EXIT_FAILURE;
            }

            return EXIT_SUCCESS;
        }

Read from stdin but give up after 5 seconds:

        #include <slack/std.h>
        #include <slack/fio.h>

        int main()
        {
            char buf[BUFSIZ];
            ssize_t bytes;

            if (read_timeout(STDIN_FILENO, 5, 0) == -1 ||
                (bytes = read(STDIN_FILENO, buf, BUFSIZ)) == -1 ||
                write(STDOUT_FILENO, buf, bytes) != bytes)
                return EXIT_FAILURE;

            return EXIT_SUCCESS;
        }

A command line sub-second sleep command:

        #include <slack/std.h>
        #include <slack/fio.h>

        int main(int ac, char **av)
        {
            if (ac != 3)
                return EXIT_FAILURE;

            nap(atoi(av[1]), atoi(av[2]));

            return EXIT_SUCCESS;
        }

Setting file flags:

        #include <slack/std.h>
        #include <slack/fio.h>

        int main()
        {
            if (fcntl_set_flag(STDIN_FILENO, O_NONBLOCK | O_ASYNC) == -1)
                return EXIT_FAILURE;

            if (fcntl_set_flag(STDOUT_FILENO, O_APPEND) == -1)
                return EXIT_FAILURE;

            if (fcntl_set_fdflag(STDOUT_FILENO, FD_CLOEXEC) == -1)
                return EXIT_FAILURE;

            if (nonblock_on(STDOUT_FILENO) == -1)
                return EXIT_FAILURE;

            if (fcntl_clear_flag(STDIN_FILENO, O_NONBLOCK | O_ASYNC) == -1)
                return EXIT_FAILURE;

            if (fcntl_clear_flag(STDOUT_FILENO, O_APPEND) == -1)
                return EXIT_FAILURE;

            if (fcntl_clear_fdflag(STDOUT_FILENO, FD_CLOEXEC) == -1)
                return EXIT_FAILURE;

            if (nonblock_off(STDOUT_FILENO) == -1)
                return EXIT_FAILURE;

            return EXIT_SUCCESS;
        }

File locking:

        #include <slack/std.h>
        #include <slack/fio.h>

        int main(int ac, char **av)
        {
            int fd;

            if ((fd = open(av[1], O_RDWR)) == -1)
                return EXIT_FAILURE;

            if (fcntl_lock(fd, F_SETLK, F_WRLCK, SEEK_SET, 0, 0) == -1)
                return close(fd), EXIT_FAILURE;

            // Write to the file...

            if (fcntl_lock(fd, F_SETLK, F_UNLCK, SEEK_SET, 0, 0) == -1)
                return close(fd), EXIT_FAILURE;

            close(fd);

            return EXIT_SUCCESS;
        }

Turn a logfile into a fifo that sends log messages to syslog instead:

            #include <slack/std.h>
            #include <slack/fio.h>
            #include <syslog.h>

            int main()
            {
                    char *fifopath = "/tmp/log2syslog";
                    char buf[BUFSIZ];
                    ssize_t bytes;
                    int fd, wfd;

                    if ((fd = fifo_open(fifopath, S_IRUSR | S_IWUSR | S_IWGRP | S_IWOTH, 1, &wfd)) == -1)
                            return EXIT_FAILURE;

                    while ((bytes = read(fd, buf, BUFSIZ)) > 0)
                    {
                            buf[bytes] = '\0';
                            syslog(LOG_DAEMON | LOG_ERR, "%s", buf);
                    }

                    close(fd);
                    close(wfd);
                    unlink(fifopath);
            }

# BUGS

Some systems, such as Mac OS X, can't lock fifos. On these systems, fifo_open(3) ignores the locking failure and returns successfully. This means that there is no guarantee of a unique reader process on these systems. You will need to lock an ordinary file yourself to provide this guarantee.

# SEE ALSO

libslack(3), fcntl(2), stat(2), fstat(2), open(2), write(2), read(2), mkfifo(2)

# AUTHOR

20210220 raf <raf@raf.org>