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
update_time(void)56 update_time(void)
57 {
58 VERIFY0(clock_gettime(CLOCK_REALTIME, &now));
59 }
60
61 static bool
time_gt(const struct timespec * check,const struct timespec * base)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
check_times(const struct stat * st,check_time_t chk,const char * side,const char * desc)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
check_fifos(int wfd,int rfd)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
main(void)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