/* * 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 2020 OmniOS Community Edition (OmniOSce) Association. */ /* * Test the implementation of the various *utimes() and *utimens() functions */ #include #include #include #include #include #include #include #include #include #include #include #include timespec_t testtimes[] = { { .tv_sec = 1280793678, .tv_nsec = 123456789 }, { .tv_sec = 1492732800, .tv_nsec = 17 }, { .tv_sec = 1320796855, .tv_nsec = 9 }, { .tv_sec = 1498953611, .tv_nsec = 987654321 } }; enum ttype { UTIMES, LUTIMES, FUTIMES, FUTIMESAT, FUTIMENS, UTIMENSAT }; static bool compare_times(struct stat *st, bool trunc, timespec_t *atim, timespec_t *mtim, bool invert) { bool ret = true; if (st->st_atim.tv_sec != atim->tv_sec) { ret = false; } else if (st->st_atim.tv_nsec != ( trunc ? atim->tv_nsec / 1000 * 1000 : atim->tv_nsec)) { ret = false; } else if (st->st_mtim.tv_sec != mtim->tv_sec) { ret = false; } else if (st->st_mtim.tv_nsec != ( trunc ? mtim->tv_nsec / 1000 * 1000 : mtim->tv_nsec)) { ret = false; } if ((!ret && !invert) || (ret && invert)) { printf(" actual atime: %ld.%.9ld\n", st->st_atim.tv_sec, st->st_atim.tv_nsec); printf(" actual mtime: %ld.%.9ld\n", st->st_mtim.tv_sec, st->st_mtim.tv_nsec); } return (ret); } static bool compare_filetime(char *path, bool trunc, timespec_t *atim, timespec_t *mtim, bool invert) { struct stat st; if (stat(path, &st) == -1) err(EXIT_FAILURE, "stat %s", path); return (compare_times(&st, trunc, atim, mtim, invert)); } static bool compare_linktime(char *path, bool trunc, timespec_t *atim, timespec_t *mtim, bool invert) { struct stat st; if (lstat(path, &st) == -1) err(EXIT_FAILURE, "lstat %s", path); return (compare_times(&st, trunc, atim, mtim, invert)); } static bool reset(char *path, timespec_t *atim, timespec_t *mtim) { if (utimes(path, NULL) == -1) err(EXIT_FAILURE, "utimes reset"); if (compare_filetime(path, true, atim, mtim, true)) { warnx("reset failed"); return (false); } return (true); } static bool reset_link(char *lpath, timespec_t *atim, timespec_t *mtim) { if (lutimes(lpath, NULL) == -1) err(EXIT_FAILURE, "lutimes reset"); if (compare_linktime(lpath, true, atim, mtim, true)) { warnx("link reset failed"); return (false); } return (true); } static bool runtest(enum ttype fn, char *dir, timespec_t *atim, timespec_t *mtim) { char path[MAXPATHLEN + 1]; char lpath[MAXPATHLEN + 1]; struct timespec ts[2]; struct timeval tv[2]; int fd, lfd, dfd, ret = true; ts[0] = *atim; ts[1] = *mtim; TIMESPEC_TO_TIMEVAL(&tv[0], &ts[0]); TIMESPEC_TO_TIMEVAL(&tv[1], &ts[1]); if (snprintf(path, sizeof (path), "%s/file", dir) >= sizeof (path)) err(EXIT_FAILURE, "snprintf failed to build file path"); if ((fd = open(path, O_CREAT, 0644)) == -1) err(EXIT_FAILURE, "open file %s", path); if (snprintf(lpath, sizeof (lpath), "%s/link", dir) >= sizeof (path)) err(EXIT_FAILURE, "snprintf failed to build link path"); if (symlink(path, lpath) == -1) err(EXIT_FAILURE, "link(%s)", lpath); if ((lfd = open(lpath, O_RDWR)) == -1) err(EXIT_FAILURE, "open link(%s)", lpath); if ((dfd = open(dir, O_DIRECTORY|O_RDONLY)) == -1) err(EXIT_FAILURE, "open dir(%s)", dir); switch (fn) { case UTIMES: printf("..... utimes()\n"); if (utimes(path, tv) == -1) err(EXIT_FAILURE, "utimes(%s)", path); if (!compare_filetime(path, true, atim, mtim, false)) { warnx("failed on file"); ret = false; } if (!reset(path, atim, mtim)) ret = false; /* repeat against symbolic link path */ if (utimes(lpath, tv) == -1) err(EXIT_FAILURE, "utimes(%s), link", lpath); if (!compare_filetime(path, true, atim, mtim, false)) { warnx("failed on file through link"); ret = false; } break; case LUTIMES: printf("..... lutimes()\n"); /* Use lutimes() against a plain file */ if (lutimes(path, tv) == -1) err(EXIT_FAILURE, "lutimes(%s)", path); if (!compare_filetime(path, true, atim, mtim, false)) { warnx("failed on file"); ret = false; } if (!reset(path, atim, mtim)) ret = false; /* Set the time on the link, not on the target */ if (lutimes(lpath, tv) == -1) err(EXIT_FAILURE, "lutimes(%s)", lpath); if (!compare_linktime(lpath, true, atim, mtim, false)) { warnx("link time is incorrect"); ret = false; } if (compare_filetime(path, true, atim, mtim, true)) { warnx("target time was updated incorrectly"); ret = false; } /* Reset the time on the path and link to the current time */ if (!reset(path, atim, mtim) || !reset_link(lpath, atim, mtim)) ret = false; /* and modify the target */ if (utimes(path, tv) == -1) err(EXIT_FAILURE, "utimes(%s)", path); /* Now the target should match but the link should not */ if (!compare_filetime(path, true, atim, mtim, false)) { warnx("target time is incorrect"); ret = false; } if (compare_linktime(lpath, true, atim, mtim, true)) { warnx("link time was updated incorrectly"); ret = false; } break; case FUTIMES: printf("..... futimes()\n"); if (futimes(fd, tv) == -1) err(EXIT_FAILURE, "futimes(%s)", path); if (!compare_filetime(path, true, atim, mtim, false)) { warnx("failed on file"); ret = false; } break; case FUTIMESAT: { int rfd; printf("..... futimesat()\n"); /* NULL path, should modify the file for 'fd' */ if (futimesat(fd, NULL, tv) == -1) err(EXIT_FAILURE, "futimesat(fd, NULL)"); if (!compare_filetime(path, true, atim, mtim, false)) { warnx("failed with null path"); ret = false; } if (!reset(path, atim, mtim)) ret = false; /* random descriptor, FQ path, descriptor is ignored */ if ((rfd = open("/dev/null", O_RDONLY)) == -1) err(EXIT_FAILURE, "open(/dev/null)"); if (futimesat(rfd, path, tv) == -1) err(EXIT_FAILURE, "futimesat(dnfd, %s)", path); if (!compare_filetime(path, true, atim, mtim, false)) { warnx("failed with random descriptor and fq path"); ret = false; } if (!reset(path, atim, mtim)) ret = false; /* repeat against symbolic link path */ if (futimesat(rfd, lpath, tv) == -1) err(EXIT_FAILURE, "futimesat(dnfd, %s), link", lpath); if (!compare_filetime(path, true, atim, mtim, false)) { warnx("failed through link with " "random descriptor, fq path"); ret = false; } (void) close(rfd); if (!reset(path, atim, mtim)) ret = false; /* relative to a directory */ if (futimesat(dfd, "file", tv) == -1) err(EXIT_FAILURE, "futimesat(dir, file)", path); if (!compare_filetime(path, true, atim, mtim, false)) { warnx("failed relative to a directory"); ret = false; } if (!reset(path, atim, mtim)) ret = false; /* repeat against symbolic link path */ if (futimesat(dfd, "link", tv) == -1) err(EXIT_FAILURE, "futimesat(dir, link)"); if (!compare_filetime(path, true, atim, mtim, false)) { warnx("failed through link relative to a directory"); ret = false; } if (!reset(path, atim, mtim)) ret = false; /* AT_FDCWD */ if (fchdir(dfd) == -1) err(EXIT_FAILURE, "fchdir(%s)", dir); if (futimesat(AT_FDCWD, "file", tv) == -1) err(EXIT_FAILURE, "futimesat(AT_FDCWD, file)", path); if (!compare_filetime(path, true, atim, mtim, false)) { warnx("failed with AT_FDCWD relative"); ret = false; } if (!reset(path, atim, mtim)) ret = false; /* repeat against symbolic link path */ if (futimesat(AT_FDCWD, "link", tv) == -1) err(EXIT_FAILURE, "futimesat(AT_FDCWD, link)"); if (!compare_filetime(path, true, atim, mtim, false)) { warnx("failed through link with AT_FDCWD relative"); ret = false; } break; } case FUTIMENS: printf("..... futimens()\n"); if (futimens(fd, ts) == -1) err(EXIT_FAILURE, "futimesns(%s)", path); if (!compare_filetime(path, false, atim, mtim, false)) { warnx("failed with plain file fd"); ret = false; } break; case UTIMENSAT: { int rfd; printf("..... utimensat()\n"); /* NULL path, expect EFAULT (cf. futimesat()) */ if (utimensat(fd, NULL, ts, 0) != -1 || errno != EFAULT) { warnx("null path should return EFAULT but got %d/%s", errno, strerror(errno)); ret = false; } /* random descriptor, FQ path, descriptor is ignored */ if ((rfd = open("/dev/null", O_RDONLY)) == -1) err(EXIT_FAILURE, "open(/dev/null)"); if (utimensat(rfd, path, ts, 0) == -1) err(EXIT_FAILURE, "utimensat(dnfd, %s)", path); if (!compare_filetime(path, false, atim, mtim, false)) { warnx("failed with random descriptor, fq path"); ret = false; } if (!reset(path, atim, mtim)) ret = false; /* repeat against symbolic link path */ if (utimensat(rfd, lpath, ts, 0) == -1) err(EXIT_FAILURE, "utimensat(dnfd, link %s)", lpath); if (!compare_filetime(path, false, atim, mtim, false)) { warnx("failed against link with random descriptor, " "fq path"); ret = false; } (void) close(rfd); if (!reset(path, atim, mtim)) ret = false; /* relative to a directory */ if (utimensat(dfd, "file", ts, 0) == -1) err(EXIT_FAILURE, "utimensat(dir, file)", path); if (!compare_filetime(path, false, atim, mtim, false)) { warnx("failed relative to a directory"); ret = false; } if (!reset(path, atim, mtim)) ret = false; /* repeat against symbolic link path */ if (utimensat(dfd, "link", ts, 0) == -1) err(EXIT_FAILURE, "utimensat(dir, link)", path); if (!compare_filetime(path, false, atim, mtim, false)) { warnx("failed through link relative to a directory"); ret = false; } if (!reset(path, atim, mtim)) ret = false; /* AT_FDCWD */ if (fchdir(dfd) == -1) err(EXIT_FAILURE, "fchdir(%s)", dir); if (utimensat(AT_FDCWD, "file", ts, 0) == -1) err(EXIT_FAILURE, "utimensat(AT_FDCWD, file)"); if (!compare_filetime(path, false, atim, mtim, false)) { warnx("failed with AT_FDCWD relative"); ret = false; } if (!reset(path, atim, mtim)) ret = false; /* repeat against symbolic link path */ if (utimensat(AT_FDCWD, "link", ts, 0) == -1) err(EXIT_FAILURE, "utimensat(AT_FDCWD, link)"); if (!compare_filetime(path, false, atim, mtim, false)) { warnx("failed through link with AT_FDCWD relative"); ret = false; } if (!reset(path, atim, mtim)) ret = false; /* * Check that none of the above operations have changed the * timestamp on the link. */ if (compare_linktime(lpath, true, atim, mtim, true)) { warnx("link timestamp was unexpectedly modified"); ret = false; } /* Set the time on the link, not on the target */ if (utimensat(AT_FDCWD, "link", ts, AT_SYMLINK_NOFOLLOW) == -1) err(EXIT_FAILURE, "utimensat(AT_FDCWD, lflag)"); if (!compare_linktime(lpath, false, atim, mtim, false)) { warnx("link time is incorrect"); ret = false; } if (compare_filetime(path, false, atim, mtim, true)) { warnx("target time was updated incorrectly"); ret = false; } } break; } (void) close(dfd); (void) close(lfd); (void) close(fd); if (unlink(lpath) == -1) err(EXIT_FAILURE, "unlink(%s)", lpath); if (unlink(path) == -1) err(EXIT_FAILURE, "unlink(%s)", path); if (!ret) warnx("Test failed"); return (ret); } static bool runtests(char *dir, timespec_t *atim, timespec_t *mtim) { bool ret = true; printf("Testing:\n... atime: %ld.%.9ld\n... mtime: %ld.%.9ld\n", atim->tv_sec, atim->tv_nsec, mtim->tv_sec, mtim->tv_nsec); if (!runtest(UTIMES, dir, atim, mtim)) ret = false; if (!runtest(LUTIMES, dir, atim, mtim)) ret = false; if (!runtest(FUTIMES, dir, atim, mtim)) ret = false; if (!runtest(FUTIMESAT, dir, atim, mtim)) ret = false; if (!runtest(FUTIMENS, dir, atim, mtim)) ret = false; if (!runtest(UTIMENSAT, dir, atim, mtim)) ret = false; return (ret); } int main(void) { char dir[] = "/tmp/utimes.XXXXXX"; int ret = EXIT_SUCCESS; int i; if (mkdtemp(dir) == NULL) err(EXIT_FAILURE, "failed to create temporary directory"); for (i = 0; i < ARRAY_SIZE(testtimes); i += 2) { if (!runtests(dir, &testtimes[i], &testtimes[i + 1])) ret = EXIT_FAILURE; } /* * Some tests will have changed directory into 'dir' to test the * AT_FDCWD case. Change back to / to avoid EBUSY when removing dir. */ if (chdir("/") == -1) err(EXIT_FAILURE, "chdir(/)"); if (rmdir(dir) == -1) err(EXIT_FAILURE, "rmdir %s", dir); return (ret); }