/* * 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 #include #include #include #include #include #include #include #include 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); }