#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <err.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
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;
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");
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;
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;
}
if (!reset(path, atim, mtim) || !reset_link(lpath, atim, mtim))
ret = false;
if (utimes(path, tv) == -1)
err(EXIT_FAILURE, "utimes(%s)", path);
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");
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;
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;
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;
if (futimesat(dfd, "file", tv) == -1)
err(EXIT_FAILURE, "futimesat(dir, file)");
if (!compare_filetime(path, true, atim, mtim, false)) {
warnx("failed relative to a directory");
ret = false;
}
if (!reset(path, atim, mtim))
ret = false;
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;
if (fchdir(dfd) == -1)
err(EXIT_FAILURE, "fchdir(%s)", dir);
if (futimesat(AT_FDCWD, "file", tv) == -1)
err(EXIT_FAILURE, "futimesat(AT_FDCWD, file)");
if (!compare_filetime(path, true, atim, mtim, false)) {
warnx("failed with AT_FDCWD relative");
ret = false;
}
if (!reset(path, atim, mtim))
ret = false;
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");
if (utimensat(fd, NULL, ts, 0) != -1 || errno != EFAULT) {
warnx("null path should return EFAULT but got %d/%s",
errno, strerror(errno));
ret = false;
}
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;
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;
if (utimensat(dfd, "file", ts, 0) == -1)
err(EXIT_FAILURE, "utimensat(dir, file)");
if (!compare_filetime(path, false, atim, mtim, false)) {
warnx("failed relative to a directory");
ret = false;
}
if (!reset(path, atim, mtim))
ret = false;
if (utimensat(dfd, "link", ts, 0) == -1)
err(EXIT_FAILURE, "utimensat(dir, link)");
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;
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;
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;
if (compare_linktime(lpath, true, atim, mtim, true)) {
warnx("link timestamp was unexpectedly modified");
ret = false;
}
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;
}
if (chdir("/") == -1)
err(EXIT_FAILURE, "chdir(/)");
if (rmdir(dir) == -1)
err(EXIT_FAILURE, "rmdir %s", dir);
return (ret);
}