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

20230824 raf <raf@raf.org>