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
  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
  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
 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
 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
 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
 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)", path);
 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)", path);
 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)", path);
 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)", path);
 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
 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
 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 }