root/usr/src/test/os-tests/tests/fifo-tvnsec.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2024 Oxide Computer Company
 */

/*
 * Verify that FIFOs now track tv_nsec in addition to tv_sec. This is important
 * for both named pipes in the file system created with mknod(2) and anonymous
 * pipes created with pipe(2). As part of this, we go through a series of
 * operations on a pipe:
 *
 * 1) Creating it
 * 2) Writing and reading from it to verify the mtime / atime are increasing.
 * 3) Explicitly setting the time on it.
 *
 * At each point, these should advance and we should be bracketed by calls to
 * getting the real time clock.
 */

#include <err.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/debug.h>
#include <stdlib.h>
#include <stdbool.h>
#include <inttypes.h>
#include <fcntl.h>

typedef enum {
        CHK_ATIME_GT     = 1 << 0,
        CHK_MTIME_GT     = 1 << 1,
        CHK_CTIME_GT     = 1 << 2,
        CHK_ATIME_LT     = 1 << 3,
        CHK_MTIME_LT     = 1 << 4,
        CHK_CTIME_LT     = 1 << 5
} check_time_t;

#define CHK_ALL_GT      (CHK_ATIME_GT | CHK_MTIME_GT | CHK_CTIME_GT)
#define CHK_ALL_LT      (CHK_ATIME_LT | CHK_MTIME_LT | CHK_CTIME_LT)

static struct timespec now;
static const char *curtype;

static void
update_time(void)
{
        VERIFY0(clock_gettime(CLOCK_REALTIME, &now));
}

static bool
time_gt(const struct timespec *check, const struct timespec *base)
{
        if (check->tv_sec > base->tv_sec) {
                return (true);
        }

        return (check->tv_sec == base->tv_sec &&
            check->tv_nsec > base->tv_nsec);
}

static bool
check_times(const struct stat *st, check_time_t chk, const char *side,
    const char *desc)
{
        bool ret = true;

        if (((chk & CHK_ATIME_GT) != 0) && !time_gt(&st->st_atim, &now)) {
                warnx("TEST FAILED: %s %s side %s atime is in the past!",
                    curtype, side, desc);
                ret = false;
        }

        if (((chk & CHK_MTIME_GT) != 0) && !time_gt(&st->st_mtim, &now)) {
                warnx("TEST FAILED: %s %s side %s mtime is in the past!",
                    curtype, side, desc);
                ret = false;
        }

        if (((chk & CHK_CTIME_GT) != 0) && !time_gt(&st->st_ctim, &now)) {
                warnx("TEST FAILED: %s %s side %s ctime is in the past!",
                    curtype, side, desc);
                ret = false;
        }

        if (((chk & CHK_ATIME_LT) != 0) && !time_gt(&now, &st->st_atim)) {
                warnx("TEST FAILED: %s %s side %s atime erroneously advanced!",
                    curtype, side, desc);
                ret = false;
        }

        if (((chk & CHK_MTIME_LT) != 0) && !time_gt(&now, &st->st_mtim)) {
                warnx("TEST FAILED: %s %s side %s mtime erroneously advanced!",
                    curtype, side, desc);
                ret = false;
        }

        if (((chk & CHK_CTIME_LT) != 0) && !time_gt(&now, &st->st_ctim)) {
                warnx("TEST FAILED: %s %s side %s ctime erroneously advanced!",
                    curtype, side, desc);
                ret = false;
        }


        return (ret);
}

static bool
check_fifos(int wfd, int rfd)
{
        bool ret = true;
        struct stat st;
        const uint32_t val = 0x7777;
        uint32_t data;
        struct timespec update[2];

        VERIFY0(fstat(wfd, &st));
        if (!check_times(&st, CHK_ALL_GT, "write", "creation")) {
                ret = false;
        }

        VERIFY0(fstat(rfd, &st));
        if (!check_times(&st, CHK_ALL_GT, "read", "creation")) {
                ret = false;
        }

        /*
         * Now that we have made progress, write to the write side and confirm
         * that the mtime / ctime have advanced. The read side should also have
         * had the mtime / ctime advance.
         */
        update_time();
        if (write(wfd, &val, sizeof (val)) != sizeof (val)) {
                errx(EXIT_FAILURE, "failed to write value to %s write side",
                    curtype);
        }

        VERIFY0(fstat(wfd, &st));
        if (!check_times(&st, CHK_CTIME_GT | CHK_MTIME_GT | CHK_ATIME_LT,
            "write", "post-write")) {
                ret = false;
        }

        VERIFY0(fstat(rfd, &st));
        if (!check_times(&st, CHK_CTIME_GT | CHK_MTIME_GT | CHK_ATIME_LT,
            "read", "post-write")) {
                ret = false;
        }

        update_time();
        if (read(rfd, &data, sizeof (data)) != sizeof (data)) {
                errx(EXIT_FAILURE, "failed to read data from %s read side",
                    curtype);
        }

        VERIFY0(fstat(rfd, &st));
        if (!check_times(&st, CHK_CTIME_LT | CHK_MTIME_LT | CHK_ATIME_GT,
            "read", "post-read")) {
                ret = false;
        }

        VERIFY0(fstat(wfd, &st));
        if (!check_times(&st, CHK_CTIME_LT | CHK_MTIME_LT | CHK_ATIME_GT,
            "write", "post-read")) {
                ret = false;
        }

        update_time();
        update[0] = now;
        update[1] = now;
        VERIFY0(futimens(wfd, update));

        update_time();
        VERIFY0(fstat(wfd, &st));
        if (!check_times(&st, CHK_ALL_LT, "write", "post-futimens")) {
                ret = false;
        }

        VERIFY0(fstat(rfd, &st));
        if (!check_times(&st, CHK_ALL_LT, "read", "post-futimens")) {
                ret = false;
        }

        return (ret);
}

int
main(void)
{
        int ret = EXIT_SUCCESS;
        int pipes[2];
        char path[1024];

        update_time();
        if (pipe(pipes) != 0) {
                err(EXIT_FAILURE, "failed to create pipes");
        }

        curtype = "pipe(2)";
        if (!check_fifos(pipes[0], pipes[1])) {
                ret = EXIT_FAILURE;
        }

        VERIFY0(close(pipes[0]));
        VERIFY0(close(pipes[1]));

        (void) snprintf(path, sizeof (path), "/tmp/fifo-tvnsec.%" _PRIdID
            ".fifo", getpid());
        if (mkfifo(path, 0666) != 0) {
                err(EXIT_FAILURE, "failed to create fifo");
        }

        /*
         * We have to open the read side before the write side and must make
         * sure that we use a non-blocking open because this is all coming from
         * the same process.
         */
        pipes[1] = open(path, O_RDONLY | O_NONBLOCK);
        if (pipes[1] < 0) {
                err(EXIT_FAILURE, "failed to open %s read-only", path);
        }

        pipes[0] = open(path, O_WRONLY | O_NONBLOCK);
        if (pipes[0] < 0) {
                err(EXIT_FAILURE, "failed to open %s write-only", path);
        }

        curtype = "mkfifo(3C)";
        if (!check_fifos(pipes[0], pipes[1])) {
                ret = EXIT_FAILURE;
        }
        (void) unlink(path);

        if (ret == EXIT_SUCCESS) {
                (void) printf("All tests completed successfully\n");
        }

        return (ret);
}