1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright 2024 Oxide Computer Company 14 */ 15 16 /* 17 * Verify that FIFOs now track tv_nsec in addition to tv_sec. This is important 18 * for both named pipes in the file system created with mknod(2) and anonymous 19 * pipes created with pipe(2). As part of this, we go through a series of 20 * operations on a pipe: 21 * 22 * 1) Creating it 23 * 2) Writing and reading from it to verify the mtime / atime are increasing. 24 * 3) Explicitly setting the time on it. 25 * 26 * At each point, these should advance and we should be bracketed by calls to 27 * getting the real time clock. 28 */ 29 30 #include <err.h> 31 #include <unistd.h> 32 #include <sys/stat.h> 33 #include <sys/types.h> 34 #include <sys/debug.h> 35 #include <stdlib.h> 36 #include <stdbool.h> 37 #include <inttypes.h> 38 #include <fcntl.h> 39 40 typedef enum { 41 CHK_ATIME_GT = 1 << 0, 42 CHK_MTIME_GT = 1 << 1, 43 CHK_CTIME_GT = 1 << 2, 44 CHK_ATIME_LT = 1 << 3, 45 CHK_MTIME_LT = 1 << 4, 46 CHK_CTIME_LT = 1 << 5 47 } check_time_t; 48 49 #define CHK_ALL_GT (CHK_ATIME_GT | CHK_MTIME_GT | CHK_CTIME_GT) 50 #define CHK_ALL_LT (CHK_ATIME_LT | CHK_MTIME_LT | CHK_CTIME_LT) 51 52 static struct timespec now; 53 static const char *curtype; 54 55 static void 56 update_time(void) 57 { 58 VERIFY0(clock_gettime(CLOCK_REALTIME, &now)); 59 } 60 61 static bool 62 time_gt(const struct timespec *check, const struct timespec *base) 63 { 64 if (check->tv_sec > base->tv_sec) { 65 return (true); 66 } 67 68 return (check->tv_sec == base->tv_sec && 69 check->tv_nsec > base->tv_nsec); 70 } 71 72 static bool 73 check_times(const struct stat *st, check_time_t chk, const char *side, 74 const char *desc) 75 { 76 bool ret = true; 77 78 if (((chk & CHK_ATIME_GT) != 0) && !time_gt(&st->st_atim, &now)) { 79 warnx("TEST FAILED: %s %s side %s atime is in the past!", 80 curtype, side, desc); 81 ret = false; 82 } 83 84 if (((chk & CHK_MTIME_GT) != 0) && !time_gt(&st->st_mtim, &now)) { 85 warnx("TEST FAILED: %s %s side %s mtime is in the past!", 86 curtype, side, desc); 87 ret = false; 88 } 89 90 if (((chk & CHK_CTIME_GT) != 0) && !time_gt(&st->st_ctim, &now)) { 91 warnx("TEST FAILED: %s %s side %s ctime is in the past!", 92 curtype, side, desc); 93 ret = false; 94 } 95 96 if (((chk & CHK_ATIME_LT) != 0) && !time_gt(&now, &st->st_atim)) { 97 warnx("TEST FAILED: %s %s side %s atime erroneously advanced!", 98 curtype, side, desc); 99 ret = false; 100 } 101 102 if (((chk & CHK_MTIME_LT) != 0) && !time_gt(&now, &st->st_mtim)) { 103 warnx("TEST FAILED: %s %s side %s mtime erroneously advanced!", 104 curtype, side, desc); 105 ret = false; 106 } 107 108 if (((chk & CHK_CTIME_LT) != 0) && !time_gt(&now, &st->st_ctim)) { 109 warnx("TEST FAILED: %s %s side %s ctime erroneously advanced!", 110 curtype, side, desc); 111 ret = false; 112 } 113 114 115 return (ret); 116 } 117 118 static bool 119 check_fifos(int wfd, int rfd) 120 { 121 bool ret = true; 122 struct stat st; 123 const uint32_t val = 0x7777; 124 uint32_t data; 125 struct timespec update[2]; 126 127 VERIFY0(fstat(wfd, &st)); 128 if (!check_times(&st, CHK_ALL_GT, "write", "creation")) { 129 ret = false; 130 } 131 132 VERIFY0(fstat(rfd, &st)); 133 if (!check_times(&st, CHK_ALL_GT, "read", "creation")) { 134 ret = false; 135 } 136 137 /* 138 * Now that we have made progress, write to the write side and confirm 139 * that the mtime / ctime have advanced. The read side should also have 140 * had the mtime / ctime advance. 141 */ 142 update_time(); 143 if (write(wfd, &val, sizeof (val)) != sizeof (val)) { 144 errx(EXIT_FAILURE, "failed to write value to %s write side", 145 curtype); 146 } 147 148 VERIFY0(fstat(wfd, &st)); 149 if (!check_times(&st, CHK_CTIME_GT | CHK_MTIME_GT | CHK_ATIME_LT, 150 "write", "post-write")) { 151 ret = false; 152 } 153 154 VERIFY0(fstat(rfd, &st)); 155 if (!check_times(&st, CHK_CTIME_GT | CHK_MTIME_GT | CHK_ATIME_LT, 156 "read", "post-write")) { 157 ret = false; 158 } 159 160 update_time(); 161 if (read(rfd, &data, sizeof (data)) != sizeof (data)) { 162 errx(EXIT_FAILURE, "failed to read data from %s read side", 163 curtype); 164 } 165 166 VERIFY0(fstat(rfd, &st)); 167 if (!check_times(&st, CHK_CTIME_LT | CHK_MTIME_LT | CHK_ATIME_GT, 168 "read", "post-read")) { 169 ret = false; 170 } 171 172 VERIFY0(fstat(wfd, &st)); 173 if (!check_times(&st, CHK_CTIME_LT | CHK_MTIME_LT | CHK_ATIME_GT, 174 "write", "post-read")) { 175 ret = false; 176 } 177 178 update_time(); 179 update[0] = now; 180 update[1] = now; 181 VERIFY0(futimens(wfd, update)); 182 183 update_time(); 184 VERIFY0(fstat(wfd, &st)); 185 if (!check_times(&st, CHK_ALL_LT, "write", "post-futimens")) { 186 ret = false; 187 } 188 189 VERIFY0(fstat(rfd, &st)); 190 if (!check_times(&st, CHK_ALL_LT, "read", "post-futimens")) { 191 ret = false; 192 } 193 194 return (ret); 195 } 196 197 int 198 main(void) 199 { 200 int ret = EXIT_SUCCESS; 201 int pipes[2]; 202 char path[1024]; 203 204 update_time(); 205 if (pipe(pipes) != 0) { 206 err(EXIT_FAILURE, "failed to create pipes"); 207 } 208 209 curtype = "pipe(2)"; 210 if (!check_fifos(pipes[0], pipes[1])) { 211 ret = EXIT_FAILURE; 212 } 213 214 VERIFY0(close(pipes[0])); 215 VERIFY0(close(pipes[1])); 216 217 (void) snprintf(path, sizeof (path), "/tmp/fifo-tvnsec.%" _PRIdID 218 ".fifo", getpid()); 219 if (mkfifo(path, 0666) != 0) { 220 err(EXIT_FAILURE, "failed to create fifo"); 221 } 222 223 /* 224 * We have to open the read side before the write side and must make 225 * sure that we use a non-blocking open because this is all coming from 226 * the same process. 227 */ 228 pipes[1] = open(path, O_RDONLY | O_NONBLOCK); 229 if (pipes[1] < 0) { 230 err(EXIT_FAILURE, "failed to open %s read-only", path); 231 } 232 233 pipes[0] = open(path, O_WRONLY | O_NONBLOCK); 234 if (pipes[0] < 0) { 235 err(EXIT_FAILURE, "failed to open %s write-only", path); 236 } 237 238 curtype = "mkfifo(3C)"; 239 if (!check_fifos(pipes[0], pipes[1])) { 240 ret = EXIT_FAILURE; 241 } 242 (void) unlink(path); 243 244 if (ret == EXIT_SUCCESS) { 245 (void) printf("All tests completed successfully\n"); 246 } 247 248 return (ret); 249 } 250