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
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.
13 * Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
17 * Test the implementation of the various *utimes() and *utimens() functions
25 #include <sys/sysmacros.h>
26 #include <sys/types.h>
33 timespec_t testtimes
[] = {
62 compare_times(struct stat
*st
, bool trunc
, timespec_t
*atim
, timespec_t
*mtim
,
67 if (st
->st_atim
.tv_sec
!= atim
->tv_sec
) {
69 } else if (st
->st_atim
.tv_nsec
!= (
70 trunc
? atim
->tv_nsec
/ 1000 * 1000 : atim
->tv_nsec
)) {
72 } else if (st
->st_mtim
.tv_sec
!= mtim
->tv_sec
) {
74 } else if (st
->st_mtim
.tv_nsec
!= (
75 trunc
? mtim
->tv_nsec
/ 1000 * 1000 : mtim
->tv_nsec
)) {
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
);
90 compare_filetime(char *path
, bool trunc
, timespec_t
*atim
, timespec_t
*mtim
,
95 if (stat(path
, &st
) == -1)
96 err(EXIT_FAILURE
, "stat %s", path
);
98 return (compare_times(&st
, trunc
, atim
, mtim
, invert
));
102 compare_linktime(char *path
, bool trunc
, timespec_t
*atim
, timespec_t
*mtim
,
107 if (lstat(path
, &st
) == -1)
108 err(EXIT_FAILURE
, "lstat %s", path
);
110 return (compare_times(&st
, trunc
, atim
, mtim
, invert
));
114 reset(char *path
, timespec_t
*atim
, timespec_t
*mtim
)
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");
126 reset_link(char *lpath
, timespec_t
*atim
, timespec_t
*mtim
)
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");
138 runtest(enum ttype fn
, char *dir
, timespec_t
*atim
, timespec_t
*mtim
)
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;
148 TIMESPEC_TO_TIMEVAL(&tv
[0], &ts
[0]);
149 TIMESPEC_TO_TIMEVAL(&tv
[1], &ts
[1]);
151 if (snprintf(path
, sizeof (path
), "%s/file", dir
) >= sizeof (path
))
152 err(EXIT_FAILURE
, "snprintf failed to build file path");
154 if ((fd
= open(path
, O_CREAT
, 0644)) == -1)
155 err(EXIT_FAILURE
, "open file %s", path
);
157 if (snprintf(lpath
, sizeof (lpath
), "%s/link", dir
) >= sizeof (path
))
158 err(EXIT_FAILURE
, "snprintf failed to build link path");
160 if (symlink(path
, lpath
) == -1)
161 err(EXIT_FAILURE
, "link(%s)", lpath
);
163 if ((lfd
= open(lpath
, O_RDWR
)) == -1)
164 err(EXIT_FAILURE
, "open link(%s)", lpath
);
166 if ((dfd
= open(dir
, O_DIRECTORY
|O_RDONLY
)) == -1)
167 err(EXIT_FAILURE
, "open dir(%s)", dir
);
171 printf("..... utimes()\n");
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");
180 if (!reset(path
, atim
, mtim
))
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");
194 printf("..... lutimes()\n");
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");
204 if (!reset(path
, atim
, mtim
))
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");
214 if (compare_filetime(path
, true, atim
, mtim
, true)) {
215 warnx("target time was updated incorrectly");
219 /* Reset the time on the path and link to the current time */
220 if (!reset(path
, atim
, mtim
) || !reset_link(lpath
, atim
, mtim
))
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");
231 if (compare_linktime(lpath
, true, atim
, mtim
, true)) {
232 warnx("link time was updated incorrectly");
238 printf("..... futimes()\n");
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");
251 printf("..... futimesat()\n");
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");
261 if (!reset(path
, atim
, mtim
))
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");
274 if (!reset(path
, atim
, mtim
))
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");
288 if (!reset(path
, atim
, mtim
))
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");
299 if (!reset(path
, atim
, mtim
))
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");
310 if (!reset(path
, atim
, mtim
))
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");
323 if (!reset(path
, atim
, mtim
))
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");
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");
351 printf("..... utimensat()\n");
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
));
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");
370 if (!reset(path
, atim
, mtim
))
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, "
384 if (!reset(path
, atim
, mtim
))
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");
395 if (!reset(path
, atim
, mtim
))
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");
406 if (!reset(path
, atim
, mtim
))
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");
419 if (!reset(path
, atim
, mtim
))
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");
430 if (!reset(path
, atim
, mtim
))
434 * Check that none of the above operations have changed the
435 * timestamp on the link.
437 if (compare_linktime(lpath
, true, atim
, mtim
, true)) {
438 warnx("link timestamp was unexpectedly modified");
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");
449 if (compare_filetime(path
, false, atim
, mtim
, true)) {
450 warnx("target time was updated incorrectly");
461 if (unlink(lpath
) == -1)
462 err(EXIT_FAILURE
, "unlink(%s)", lpath
);
463 if (unlink(path
) == -1)
464 err(EXIT_FAILURE
, "unlink(%s)", path
);
467 warnx("Test failed");
473 runtests(char *dir
, timespec_t
*atim
, timespec_t
*mtim
)
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
);
480 if (!runtest(UTIMES
, dir
, atim
, mtim
))
482 if (!runtest(LUTIMES
, dir
, atim
, mtim
))
484 if (!runtest(FUTIMES
, dir
, atim
, mtim
))
486 if (!runtest(FUTIMESAT
, dir
, atim
, mtim
))
488 if (!runtest(FUTIMENS
, dir
, atim
, mtim
))
490 if (!runtest(UTIMENSAT
, dir
, atim
, mtim
))
499 char dir
[] = "/tmp/utimes.XXXXXX";
500 int ret
= EXIT_SUCCESS
;
503 if (mkdtemp(dir
) == NULL
)
504 err(EXIT_FAILURE
, "failed to create temporary directory");
506 for (i
= 0; i
< ARRAY_SIZE(testtimes
); i
+= 2) {
507 if (!runtests(dir
, &testtimes
[i
], &testtimes
[i
+ 1]))
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.
515 if (chdir("/") == -1)
516 err(EXIT_FAILURE
, "chdir(/)");
517 if (rmdir(dir
) == -1)
518 err(EXIT_FAILURE
, "rmdir %s", dir
);