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 2020 OmniOS Community Edition (OmniOSce) Association.
14 */
15
16 /*
17 * Test the implementation of the various *utimes() and *utimens() functions
18 */
19
20 #include <stdio.h>
21 #include <stdbool.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <err.h>
25 #include <sys/sysmacros.h>
26 #include <sys/types.h>
27 #include <sys/time.h>
28 #include <sys/stat.h>
29 #include <fcntl.h>
30 #include <unistd.h>
31 #include <errno.h>
32
33 timespec_t testtimes[] = {
34 {
35 .tv_sec = 1280793678,
36 .tv_nsec = 123456789
37 },
38 {
39 .tv_sec = 1492732800,
40 .tv_nsec = 17
41 },
42 {
43 .tv_sec = 1320796855,
44 .tv_nsec = 9
45 },
46 {
47 .tv_sec = 1498953611,
48 .tv_nsec = 987654321
49 }
50 };
51
52 enum ttype {
53 UTIMES,
54 LUTIMES,
55 FUTIMES,
56 FUTIMESAT,
57 FUTIMENS,
58 UTIMENSAT
59 };
60
61 static bool
compare_times(struct stat * st,bool trunc,timespec_t * atim,timespec_t * mtim,bool invert)62 compare_times(struct stat *st, bool trunc, timespec_t *atim, timespec_t *mtim,
63 bool invert)
64 {
65 bool ret = true;
66
67 if (st->st_atim.tv_sec != atim->tv_sec) {
68 ret = false;
69 } else if (st->st_atim.tv_nsec != (
70 trunc ? atim->tv_nsec / 1000 * 1000 : atim->tv_nsec)) {
71 ret = false;
72 } else if (st->st_mtim.tv_sec != mtim->tv_sec) {
73 ret = false;
74 } else if (st->st_mtim.tv_nsec != (
75 trunc ? mtim->tv_nsec / 1000 * 1000 : mtim->tv_nsec)) {
76 ret = false;
77 }
78
79 if ((!ret && !invert) || (ret && invert)) {
80 printf(" actual atime: %ld.%.9ld\n",
81 st->st_atim.tv_sec, st->st_atim.tv_nsec);
82 printf(" actual mtime: %ld.%.9ld\n",
83 st->st_mtim.tv_sec, st->st_mtim.tv_nsec);
84 }
85
86 return (ret);
87 }
88
89 static bool
compare_filetime(char * path,bool trunc,timespec_t * atim,timespec_t * mtim,bool invert)90 compare_filetime(char *path, bool trunc, timespec_t *atim, timespec_t *mtim,
91 bool invert)
92 {
93 struct stat st;
94
95 if (stat(path, &st) == -1)
96 err(EXIT_FAILURE, "stat %s", path);
97
98 return (compare_times(&st, trunc, atim, mtim, invert));
99 }
100
101 static bool
compare_linktime(char * path,bool trunc,timespec_t * atim,timespec_t * mtim,bool invert)102 compare_linktime(char *path, bool trunc, timespec_t *atim, timespec_t *mtim,
103 bool invert)
104 {
105 struct stat st;
106
107 if (lstat(path, &st) == -1)
108 err(EXIT_FAILURE, "lstat %s", path);
109
110 return (compare_times(&st, trunc, atim, mtim, invert));
111 }
112
113 static bool
reset(char * path,timespec_t * atim,timespec_t * mtim)114 reset(char *path, timespec_t *atim, timespec_t *mtim)
115 {
116 if (utimes(path, NULL) == -1)
117 err(EXIT_FAILURE, "utimes reset");
118 if (compare_filetime(path, true, atim, mtim, true)) {
119 warnx("reset failed");
120 return (false);
121 }
122 return (true);
123 }
124
125 static bool
reset_link(char * lpath,timespec_t * atim,timespec_t * mtim)126 reset_link(char *lpath, timespec_t *atim, timespec_t *mtim)
127 {
128 if (lutimes(lpath, NULL) == -1)
129 err(EXIT_FAILURE, "lutimes reset");
130 if (compare_linktime(lpath, true, atim, mtim, true)) {
131 warnx("link reset failed");
132 return (false);
133 }
134 return (true);
135 }
136
137 static bool
runtest(enum ttype fn,char * dir,timespec_t * atim,timespec_t * mtim)138 runtest(enum ttype fn, char *dir, timespec_t *atim, timespec_t *mtim)
139 {
140 char path[MAXPATHLEN + 1];
141 char lpath[MAXPATHLEN + 1];
142 struct timespec ts[2];
143 struct timeval tv[2];
144 int fd, lfd, dfd, ret = true;
145
146 ts[0] = *atim;
147 ts[1] = *mtim;
148 TIMESPEC_TO_TIMEVAL(&tv[0], &ts[0]);
149 TIMESPEC_TO_TIMEVAL(&tv[1], &ts[1]);
150
151 if (snprintf(path, sizeof (path), "%s/file", dir) >= sizeof (path))
152 err(EXIT_FAILURE, "snprintf failed to build file path");
153
154 if ((fd = open(path, O_CREAT, 0644)) == -1)
155 err(EXIT_FAILURE, "open file %s", path);
156
157 if (snprintf(lpath, sizeof (lpath), "%s/link", dir) >= sizeof (path))
158 err(EXIT_FAILURE, "snprintf failed to build link path");
159
160 if (symlink(path, lpath) == -1)
161 err(EXIT_FAILURE, "link(%s)", lpath);
162
163 if ((lfd = open(lpath, O_RDWR)) == -1)
164 err(EXIT_FAILURE, "open link(%s)", lpath);
165
166 if ((dfd = open(dir, O_DIRECTORY|O_RDONLY)) == -1)
167 err(EXIT_FAILURE, "open dir(%s)", dir);
168
169 switch (fn) {
170 case UTIMES:
171 printf("..... utimes()\n");
172
173 if (utimes(path, tv) == -1)
174 err(EXIT_FAILURE, "utimes(%s)", path);
175 if (!compare_filetime(path, true, atim, mtim, false)) {
176 warnx("failed on file");
177 ret = false;
178 }
179
180 if (!reset(path, atim, mtim))
181 ret = false;
182
183 /* repeat against symbolic link path */
184 if (utimes(lpath, tv) == -1)
185 err(EXIT_FAILURE, "utimes(%s), link", lpath);
186 if (!compare_filetime(path, true, atim, mtim, false)) {
187 warnx("failed on file through link");
188 ret = false;
189 }
190
191 break;
192
193 case LUTIMES:
194 printf("..... lutimes()\n");
195
196 /* Use lutimes() against a plain file */
197 if (lutimes(path, tv) == -1)
198 err(EXIT_FAILURE, "lutimes(%s)", path);
199 if (!compare_filetime(path, true, atim, mtim, false)) {
200 warnx("failed on file");
201 ret = false;
202 }
203
204 if (!reset(path, atim, mtim))
205 ret = false;
206
207 /* Set the time on the link, not on the target */
208 if (lutimes(lpath, tv) == -1)
209 err(EXIT_FAILURE, "lutimes(%s)", lpath);
210 if (!compare_linktime(lpath, true, atim, mtim, false)) {
211 warnx("link time is incorrect");
212 ret = false;
213 }
214 if (compare_filetime(path, true, atim, mtim, true)) {
215 warnx("target time was updated incorrectly");
216 ret = false;
217 }
218
219 /* Reset the time on the path and link to the current time */
220 if (!reset(path, atim, mtim) || !reset_link(lpath, atim, mtim))
221 ret = false;
222
223 /* and modify the target */
224 if (utimes(path, tv) == -1)
225 err(EXIT_FAILURE, "utimes(%s)", path);
226 /* Now the target should match but the link should not */
227 if (!compare_filetime(path, true, atim, mtim, false)) {
228 warnx("target time is incorrect");
229 ret = false;
230 }
231 if (compare_linktime(lpath, true, atim, mtim, true)) {
232 warnx("link time was updated incorrectly");
233 ret = false;
234 }
235 break;
236
237 case FUTIMES:
238 printf("..... futimes()\n");
239
240 if (futimes(fd, tv) == -1)
241 err(EXIT_FAILURE, "futimes(%s)", path);
242 if (!compare_filetime(path, true, atim, mtim, false)) {
243 warnx("failed on file");
244 ret = false;
245 }
246
247 break;
248
249 case FUTIMESAT: {
250 int rfd;
251 printf("..... futimesat()\n");
252
253 /* NULL path, should modify the file for 'fd' */
254 if (futimesat(fd, NULL, tv) == -1)
255 err(EXIT_FAILURE, "futimesat(fd, NULL)");
256 if (!compare_filetime(path, true, atim, mtim, false)) {
257 warnx("failed with null path");
258 ret = false;
259 }
260
261 if (!reset(path, atim, mtim))
262 ret = false;
263
264 /* random descriptor, FQ path, descriptor is ignored */
265 if ((rfd = open("/dev/null", O_RDONLY)) == -1)
266 err(EXIT_FAILURE, "open(/dev/null)");
267 if (futimesat(rfd, path, tv) == -1)
268 err(EXIT_FAILURE, "futimesat(dnfd, %s)", path);
269 if (!compare_filetime(path, true, atim, mtim, false)) {
270 warnx("failed with random descriptor and fq path");
271 ret = false;
272 }
273
274 if (!reset(path, atim, mtim))
275 ret = false;
276
277 /* repeat against symbolic link path */
278 if (futimesat(rfd, lpath, tv) == -1)
279 err(EXIT_FAILURE, "futimesat(dnfd, %s), link", lpath);
280 if (!compare_filetime(path, true, atim, mtim, false)) {
281 warnx("failed through link with "
282 "random descriptor, fq path");
283 ret = false;
284 }
285
286 (void) close(rfd);
287
288 if (!reset(path, atim, mtim))
289 ret = false;
290
291 /* relative to a directory */
292 if (futimesat(dfd, "file", tv) == -1)
293 err(EXIT_FAILURE, "futimesat(dir, file)");
294 if (!compare_filetime(path, true, atim, mtim, false)) {
295 warnx("failed relative to a directory");
296 ret = false;
297 }
298
299 if (!reset(path, atim, mtim))
300 ret = false;
301
302 /* repeat against symbolic link path */
303 if (futimesat(dfd, "link", tv) == -1)
304 err(EXIT_FAILURE, "futimesat(dir, link)");
305 if (!compare_filetime(path, true, atim, mtim, false)) {
306 warnx("failed through link relative to a directory");
307 ret = false;
308 }
309
310 if (!reset(path, atim, mtim))
311 ret = false;
312
313 /* AT_FDCWD */
314 if (fchdir(dfd) == -1)
315 err(EXIT_FAILURE, "fchdir(%s)", dir);
316 if (futimesat(AT_FDCWD, "file", tv) == -1)
317 err(EXIT_FAILURE, "futimesat(AT_FDCWD, file)");
318 if (!compare_filetime(path, true, atim, mtim, false)) {
319 warnx("failed with AT_FDCWD relative");
320 ret = false;
321 }
322
323 if (!reset(path, atim, mtim))
324 ret = false;
325
326 /* repeat against symbolic link path */
327 if (futimesat(AT_FDCWD, "link", tv) == -1)
328 err(EXIT_FAILURE, "futimesat(AT_FDCWD, link)");
329 if (!compare_filetime(path, true, atim, mtim, false)) {
330 warnx("failed through link with AT_FDCWD relative");
331 ret = false;
332 }
333
334 break;
335 }
336
337 case FUTIMENS:
338 printf("..... futimens()\n");
339 if (futimens(fd, ts) == -1)
340 err(EXIT_FAILURE, "futimesns(%s)", path);
341 if (!compare_filetime(path, false, atim, mtim, false)) {
342 warnx("failed with plain file fd");
343 ret = false;
344 }
345
346 break;
347
348 case UTIMENSAT: {
349 int rfd;
350
351 printf("..... utimensat()\n");
352
353 /* NULL path, expect EFAULT (cf. futimesat()) */
354 if (utimensat(fd, NULL, ts, 0) != -1 || errno != EFAULT) {
355 warnx("null path should return EFAULT but got %d/%s",
356 errno, strerror(errno));
357 ret = false;
358 }
359
360 /* random descriptor, FQ path, descriptor is ignored */
361 if ((rfd = open("/dev/null", O_RDONLY)) == -1)
362 err(EXIT_FAILURE, "open(/dev/null)");
363 if (utimensat(rfd, path, ts, 0) == -1)
364 err(EXIT_FAILURE, "utimensat(dnfd, %s)", path);
365 if (!compare_filetime(path, false, atim, mtim, false)) {
366 warnx("failed with random descriptor, fq path");
367 ret = false;
368 }
369
370 if (!reset(path, atim, mtim))
371 ret = false;
372
373 /* repeat against symbolic link path */
374 if (utimensat(rfd, lpath, ts, 0) == -1)
375 err(EXIT_FAILURE, "utimensat(dnfd, link %s)", lpath);
376 if (!compare_filetime(path, false, atim, mtim, false)) {
377 warnx("failed against link with random descriptor, "
378 "fq path");
379 ret = false;
380 }
381
382 (void) close(rfd);
383
384 if (!reset(path, atim, mtim))
385 ret = false;
386
387 /* relative to a directory */
388 if (utimensat(dfd, "file", ts, 0) == -1)
389 err(EXIT_FAILURE, "utimensat(dir, file)");
390 if (!compare_filetime(path, false, atim, mtim, false)) {
391 warnx("failed relative to a directory");
392 ret = false;
393 }
394
395 if (!reset(path, atim, mtim))
396 ret = false;
397
398 /* repeat against symbolic link path */
399 if (utimensat(dfd, "link", ts, 0) == -1)
400 err(EXIT_FAILURE, "utimensat(dir, link)");
401 if (!compare_filetime(path, false, atim, mtim, false)) {
402 warnx("failed through link relative to a directory");
403 ret = false;
404 }
405
406 if (!reset(path, atim, mtim))
407 ret = false;
408
409 /* AT_FDCWD */
410 if (fchdir(dfd) == -1)
411 err(EXIT_FAILURE, "fchdir(%s)", dir);
412 if (utimensat(AT_FDCWD, "file", ts, 0) == -1)
413 err(EXIT_FAILURE, "utimensat(AT_FDCWD, file)");
414 if (!compare_filetime(path, false, atim, mtim, false)) {
415 warnx("failed with AT_FDCWD relative");
416 ret = false;
417 }
418
419 if (!reset(path, atim, mtim))
420 ret = false;
421
422 /* repeat against symbolic link path */
423 if (utimensat(AT_FDCWD, "link", ts, 0) == -1)
424 err(EXIT_FAILURE, "utimensat(AT_FDCWD, link)");
425 if (!compare_filetime(path, false, atim, mtim, false)) {
426 warnx("failed through link with AT_FDCWD relative");
427 ret = false;
428 }
429
430 if (!reset(path, atim, mtim))
431 ret = false;
432
433 /*
434 * Check that none of the above operations have changed the
435 * timestamp on the link.
436 */
437 if (compare_linktime(lpath, true, atim, mtim, true)) {
438 warnx("link timestamp was unexpectedly modified");
439 ret = false;
440 }
441
442 /* Set the time on the link, not on the target */
443 if (utimensat(AT_FDCWD, "link", ts, AT_SYMLINK_NOFOLLOW) == -1)
444 err(EXIT_FAILURE, "utimensat(AT_FDCWD, lflag)");
445 if (!compare_linktime(lpath, false, atim, mtim, false)) {
446 warnx("link time is incorrect");
447 ret = false;
448 }
449 if (compare_filetime(path, false, atim, mtim, true)) {
450 warnx("target time was updated incorrectly");
451 ret = false;
452 }
453 }
454 break;
455 }
456
457 (void) close(dfd);
458 (void) close(lfd);
459 (void) close(fd);
460
461 if (unlink(lpath) == -1)
462 err(EXIT_FAILURE, "unlink(%s)", lpath);
463 if (unlink(path) == -1)
464 err(EXIT_FAILURE, "unlink(%s)", path);
465
466 if (!ret)
467 warnx("Test failed");
468
469 return (ret);
470 }
471
472 static bool
runtests(char * dir,timespec_t * atim,timespec_t * mtim)473 runtests(char *dir, timespec_t *atim, timespec_t *mtim)
474 {
475 bool ret = true;
476
477 printf("Testing:\n... atime: %ld.%.9ld\n... mtime: %ld.%.9ld\n",
478 atim->tv_sec, atim->tv_nsec, mtim->tv_sec, mtim->tv_nsec);
479
480 if (!runtest(UTIMES, dir, atim, mtim))
481 ret = false;
482 if (!runtest(LUTIMES, dir, atim, mtim))
483 ret = false;
484 if (!runtest(FUTIMES, dir, atim, mtim))
485 ret = false;
486 if (!runtest(FUTIMESAT, dir, atim, mtim))
487 ret = false;
488 if (!runtest(FUTIMENS, dir, atim, mtim))
489 ret = false;
490 if (!runtest(UTIMENSAT, dir, atim, mtim))
491 ret = false;
492
493 return (ret);
494 }
495
496 int
main(void)497 main(void)
498 {
499 char dir[] = "/tmp/utimes.XXXXXX";
500 int ret = EXIT_SUCCESS;
501 int i;
502
503 if (mkdtemp(dir) == NULL)
504 err(EXIT_FAILURE, "failed to create temporary directory");
505
506 for (i = 0; i < ARRAY_SIZE(testtimes); i += 2) {
507 if (!runtests(dir, &testtimes[i], &testtimes[i + 1]))
508 ret = EXIT_FAILURE;
509 }
510
511 /*
512 * Some tests will have changed directory into 'dir' to test the
513 * AT_FDCWD case. Change back to / to avoid EBUSY when removing dir.
514 */
515 if (chdir("/") == -1)
516 err(EXIT_FAILURE, "chdir(/)");
517 if (rmdir(dir) == -1)
518 err(EXIT_FAILURE, "rmdir %s", dir);
519
520 return (ret);
521 }
522