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 }