last_write_time.pass.cpp (18938B)
1 //===----------------------------------------------------------------------===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is dual licensed under the MIT and the University of Illinois Open 6 // Source Licenses. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 10 // UNSUPPORTED: c++98, c++03 11 12 // <filesystem> 13 14 // file_time_type last_write_time(const path& p); 15 // file_time_type last_write_time(const path& p, std::error_code& ec) noexcept; 16 // void last_write_time(const path& p, file_time_type new_time); 17 // void last_write_time(const path& p, file_time_type new_type, 18 // std::error_code& ec) noexcept; 19 20 #include "filesystem_include.hpp" 21 #include <type_traits> 22 #include <chrono> 23 #include <fstream> 24 #include <cstdlib> 25 26 #include "test_macros.h" 27 #include "rapid-cxx-test.hpp" 28 #include "filesystem_test_helper.hpp" 29 30 #include <sys/stat.h> 31 #include <iostream> 32 33 #include <fcntl.h> 34 #include <sys/time.h> 35 36 using namespace fs; 37 38 using TimeSpec = struct ::timespec; 39 using StatT = struct ::stat; 40 41 using Sec = std::chrono::duration<file_time_type::rep>; 42 using Hours = std::chrono::hours; 43 using Minutes = std::chrono::minutes; 44 using MicroSec = std::chrono::duration<file_time_type::rep, std::micro>; 45 using NanoSec = std::chrono::duration<file_time_type::rep, std::nano>; 46 using std::chrono::duration_cast; 47 48 #if defined(__APPLE__) 49 TimeSpec extract_mtime(StatT const& st) { return st.st_mtimespec; } 50 TimeSpec extract_atime(StatT const& st) { return st.st_atimespec; } 51 #else 52 TimeSpec extract_mtime(StatT const& st) { return st.st_mtim; } 53 TimeSpec extract_atime(StatT const& st) { return st.st_atim; } 54 #endif 55 56 bool ConvertToTimeSpec(TimeSpec& ts, file_time_type ft) { 57 using SecFieldT = decltype(TimeSpec::tv_sec); 58 using NSecFieldT = decltype(TimeSpec::tv_nsec); 59 using SecLim = std::numeric_limits<SecFieldT>; 60 using NSecLim = std::numeric_limits<NSecFieldT>; 61 62 auto secs = duration_cast<Sec>(ft.time_since_epoch()); 63 auto nsecs = duration_cast<NanoSec>(ft.time_since_epoch() - secs); 64 if (nsecs.count() < 0) { 65 if (Sec::min().count() > SecLim::min()) { 66 secs += Sec(1); 67 nsecs -= Sec(1); 68 } else { 69 nsecs = NanoSec(0); 70 } 71 } 72 if (SecLim::max() < secs.count() || SecLim::min() > secs.count()) 73 return false; 74 if (NSecLim::max() < nsecs.count() || NSecLim::min() > nsecs.count()) 75 return false; 76 ts.tv_sec = secs.count(); 77 ts.tv_nsec = nsecs.count(); 78 return true; 79 } 80 81 bool ConvertFromTimeSpec(file_time_type& ft, TimeSpec ts) { 82 auto secs_part = duration_cast<file_time_type::duration>(Sec(ts.tv_sec)); 83 if (duration_cast<Sec>(secs_part).count() != ts.tv_sec) 84 return false; 85 auto subsecs = duration_cast<file_time_type::duration>(NanoSec(ts.tv_nsec)); 86 auto dur = secs_part + subsecs; 87 if (dur < secs_part && subsecs.count() >= 0) 88 return false; 89 ft = file_time_type(dur); 90 return true; 91 } 92 93 bool CompareTimeExact(TimeSpec ts, TimeSpec ts2) { 94 return ts2.tv_sec == ts.tv_sec && ts2.tv_nsec == ts.tv_nsec; 95 } 96 bool CompareTimeExact(file_time_type ft, TimeSpec ts) { 97 TimeSpec ts2 = {}; 98 if (!ConvertToTimeSpec(ts2, ft)) 99 return false; 100 return CompareTimeExact(ts, ts2); 101 } 102 bool CompareTimeExact(TimeSpec ts, file_time_type ft) { 103 return CompareTimeExact(ft, ts); 104 } 105 106 struct Times { 107 TimeSpec access, write; 108 }; 109 110 Times GetTimes(path const& p) { 111 StatT st; 112 if (::stat(p.c_str(), &st) == -1) { 113 std::error_code ec(errno, std::generic_category()); 114 #ifndef TEST_HAS_NO_EXCEPTIONS 115 throw ec; 116 #else 117 std::cerr << ec.message() << std::endl; 118 std::exit(EXIT_FAILURE); 119 #endif 120 } 121 return {extract_atime(st), extract_mtime(st)}; 122 } 123 124 TimeSpec LastAccessTime(path const& p) { return GetTimes(p).access; } 125 126 TimeSpec LastWriteTime(path const& p) { return GetTimes(p).write; } 127 128 Times GetSymlinkTimes(path const& p) { 129 StatT st; 130 if (::lstat(p.c_str(), &st) == -1) { 131 std::error_code ec(errno, std::generic_category()); 132 #ifndef TEST_HAS_NO_EXCEPTIONS 133 throw ec; 134 #else 135 std::cerr << ec.message() << std::endl; 136 std::exit(EXIT_FAILURE); 137 #endif 138 } 139 Times res; 140 res.access = extract_atime(st); 141 res.write = extract_mtime(st); 142 return res; 143 } 144 145 namespace { 146 147 // In some configurations, the comparison is tautological and the test is valid. 148 // We disable the warning so that we can actually test it regardless. Also, that 149 // diagnostic is pretty new, so also don't fail if old clang does not support it 150 #if defined(__clang__) 151 #pragma clang diagnostic push 152 #pragma clang diagnostic ignored "-Wunknown-warning-option" 153 #pragma clang diagnostic ignored "-Wunknown-pragmas" 154 #pragma clang diagnostic ignored "-Wtautological-constant-compare" 155 #endif 156 157 static const bool SupportsNegativeTimes = [] { 158 using namespace std::chrono; 159 std::error_code ec; 160 TimeSpec old_write_time, new_write_time; 161 { // WARNING: Do not assert in this scope. 162 scoped_test_env env; 163 const path file = env.create_file("file", 42); 164 old_write_time = LastWriteTime(file); 165 file_time_type tp(seconds(-5)); 166 fs::last_write_time(file, tp, ec); 167 new_write_time = LastWriteTime(file); 168 } 169 170 return !ec && new_write_time.tv_sec < 0; 171 }(); 172 173 static const bool SupportsMaxTime = [] { 174 using namespace std::chrono; 175 TimeSpec max_ts = {}; 176 if (!ConvertToTimeSpec(max_ts, file_time_type::max())) 177 return false; 178 179 std::error_code ec; 180 TimeSpec old_write_time, new_write_time; 181 { // WARNING: Do not assert in this scope. 182 scoped_test_env env; 183 const path file = env.create_file("file", 42); 184 old_write_time = LastWriteTime(file); 185 file_time_type tp = file_time_type::max(); 186 fs::last_write_time(file, tp, ec); 187 new_write_time = LastWriteTime(file); 188 } 189 return !ec && new_write_time.tv_sec > max_ts.tv_sec - 1; 190 }(); 191 192 static const bool SupportsMinTime = [] { 193 using namespace std::chrono; 194 TimeSpec min_ts = {}; 195 if (!ConvertToTimeSpec(min_ts, file_time_type::min())) 196 return false; 197 std::error_code ec; 198 TimeSpec old_write_time, new_write_time; 199 { // WARNING: Do not assert in this scope. 200 scoped_test_env env; 201 const path file = env.create_file("file", 42); 202 old_write_time = LastWriteTime(file); 203 file_time_type tp = file_time_type::min(); 204 fs::last_write_time(file, tp, ec); 205 new_write_time = LastWriteTime(file); 206 } 207 return !ec && new_write_time.tv_sec < min_ts.tv_sec + 1; 208 }(); 209 210 static const bool SupportsNanosecondRoundTrip = [] { 211 NanoSec ns(3); 212 static_assert(std::is_same<file_time_type::period, std::nano>::value, ""); 213 214 // Test that the system call we use to set the times also supports nanosecond 215 // resolution. (utimes does not) 216 file_time_type ft(ns); 217 { 218 scoped_test_env env; 219 const path p = env.create_file("file", 42); 220 last_write_time(p, ft); 221 return last_write_time(p) == ft; 222 } 223 }(); 224 225 // The HFS+ filesystem (used by default before macOS 10.13) stores timestamps at 226 // a 1-second granularity, and APFS (now the default) at a 1 nanosecond granularity. 227 // 1-second granularity is also the norm on many of the supported filesystems 228 // on Linux as well. 229 static const bool WorkaroundStatTruncatesToSeconds = [] { 230 MicroSec micros(3); 231 static_assert(std::is_same<file_time_type::period, std::nano>::value, ""); 232 233 file_time_type ft(micros); 234 { 235 scoped_test_env env; 236 const path p = env.create_file("file", 42); 237 if (LastWriteTime(p).tv_nsec != 0) 238 return false; 239 last_write_time(p, ft); 240 return last_write_time(p) != ft && LastWriteTime(p).tv_nsec == 0; 241 } 242 }(); 243 244 static const bool SupportsMinRoundTrip = [] { 245 TimeSpec ts = {}; 246 if (!ConvertToTimeSpec(ts, file_time_type::min())) 247 return false; 248 file_time_type min_val = {}; 249 if (!ConvertFromTimeSpec(min_val, ts)) 250 return false; 251 return min_val == file_time_type::min(); 252 }(); 253 254 } // end namespace 255 256 static bool CompareTime(TimeSpec t1, TimeSpec t2) { 257 if (SupportsNanosecondRoundTrip) 258 return CompareTimeExact(t1, t2); 259 if (t1.tv_sec != t2.tv_sec) 260 return false; 261 262 auto diff = std::abs(t1.tv_nsec - t2.tv_nsec); 263 if (WorkaroundStatTruncatesToSeconds) 264 return diff < duration_cast<NanoSec>(Sec(1)).count(); 265 return diff < duration_cast<NanoSec>(MicroSec(1)).count(); 266 } 267 268 static bool CompareTime(file_time_type t1, TimeSpec t2) { 269 TimeSpec ts1 = {}; 270 if (!ConvertToTimeSpec(ts1, t1)) 271 return false; 272 return CompareTime(ts1, t2); 273 } 274 275 static bool CompareTime(TimeSpec t1, file_time_type t2) { 276 return CompareTime(t2, t1); 277 } 278 279 static bool CompareTime(file_time_type t1, file_time_type t2) { 280 auto min_secs = duration_cast<Sec>(file_time_type::min().time_since_epoch()); 281 bool IsMin = 282 t1.time_since_epoch() < min_secs || t2.time_since_epoch() < min_secs; 283 284 if (SupportsNanosecondRoundTrip && (!IsMin || SupportsMinRoundTrip)) 285 return t1 == t2; 286 if (IsMin) { 287 return duration_cast<Sec>(t1.time_since_epoch()) == 288 duration_cast<Sec>(t2.time_since_epoch()); 289 } 290 file_time_type::duration dur; 291 if (t1 > t2) 292 dur = t1 - t2; 293 else 294 dur = t2 - t1; 295 if (WorkaroundStatTruncatesToSeconds) 296 return duration_cast<Sec>(dur).count() == 0; 297 return duration_cast<MicroSec>(dur).count() == 0; 298 } 299 300 // Check if a time point is representable on a given filesystem. Check that: 301 // (A) 'tp' is representable as a time_t 302 // (B) 'tp' is non-negative or the filesystem supports negative times. 303 // (C) 'tp' is not 'file_time_type::max()' or the filesystem supports the max 304 // value. 305 // (D) 'tp' is not 'file_time_type::min()' or the filesystem supports the min 306 // value. 307 inline bool TimeIsRepresentableByFilesystem(file_time_type tp) { 308 TimeSpec ts = {}; 309 if (!ConvertToTimeSpec(ts, tp)) 310 return false; 311 else if (tp.time_since_epoch().count() < 0 && !SupportsNegativeTimes) 312 return false; 313 else if (tp == file_time_type::max() && !SupportsMaxTime) 314 return false; 315 else if (tp == file_time_type::min() && !SupportsMinTime) 316 return false; 317 return true; 318 } 319 320 #if defined(__clang__) 321 #pragma clang diagnostic pop 322 #endif 323 324 // Create a sub-second duration using the smallest period the filesystem supports. 325 file_time_type::duration SubSec(long long val) { 326 using SubSecT = file_time_type::duration; 327 if (SupportsNanosecondRoundTrip) { 328 return duration_cast<SubSecT>(NanoSec(val)); 329 } else { 330 return duration_cast<SubSecT>(MicroSec(val)); 331 } 332 } 333 334 TEST_SUITE(last_write_time_test_suite) 335 336 TEST_CASE(signature_test) 337 { 338 const file_time_type t; 339 const path p; ((void)p); 340 std::error_code ec; ((void)ec); 341 ASSERT_SAME_TYPE(decltype(last_write_time(p)), file_time_type); 342 ASSERT_SAME_TYPE(decltype(last_write_time(p, ec)), file_time_type); 343 ASSERT_SAME_TYPE(decltype(last_write_time(p, t)), void); 344 ASSERT_SAME_TYPE(decltype(last_write_time(p, t, ec)), void); 345 ASSERT_NOT_NOEXCEPT(last_write_time(p)); 346 ASSERT_NOT_NOEXCEPT(last_write_time(p, t)); 347 ASSERT_NOEXCEPT(last_write_time(p, ec)); 348 ASSERT_NOEXCEPT(last_write_time(p, t, ec)); 349 } 350 351 TEST_CASE(read_last_write_time_static_env_test) 352 { 353 using C = file_time_type::clock; 354 file_time_type min = file_time_type::min(); 355 { 356 file_time_type ret = last_write_time(StaticEnv::File); 357 TEST_CHECK(ret != min); 358 TEST_CHECK(ret < C::now()); 359 TEST_CHECK(CompareTime(ret, LastWriteTime(StaticEnv::File))); 360 361 file_time_type ret2 = last_write_time(StaticEnv::SymlinkToFile); 362 TEST_CHECK(CompareTime(ret, ret2)); 363 TEST_CHECK(CompareTime(ret2, LastWriteTime(StaticEnv::SymlinkToFile))); 364 } 365 { 366 file_time_type ret = last_write_time(StaticEnv::Dir); 367 TEST_CHECK(ret != min); 368 TEST_CHECK(ret < C::now()); 369 TEST_CHECK(CompareTime(ret, LastWriteTime(StaticEnv::Dir))); 370 371 file_time_type ret2 = last_write_time(StaticEnv::SymlinkToDir); 372 TEST_CHECK(CompareTime(ret, ret2)); 373 TEST_CHECK(CompareTime(ret2, LastWriteTime(StaticEnv::SymlinkToDir))); 374 } 375 } 376 377 TEST_CASE(get_last_write_time_dynamic_env_test) 378 { 379 using Clock = file_time_type::clock; 380 using Sec = std::chrono::seconds; 381 scoped_test_env env; 382 383 const path file = env.create_file("file", 42); 384 const path dir = env.create_dir("dir"); 385 386 const auto file_times = GetTimes(file); 387 const TimeSpec file_write_time = file_times.write; 388 const auto dir_times = GetTimes(dir); 389 const TimeSpec dir_write_time = dir_times.write; 390 391 file_time_type ftime = last_write_time(file); 392 TEST_CHECK(Clock::to_time_t(ftime) == file_write_time.tv_sec); 393 TEST_CHECK(CompareTime(ftime, file_write_time)); 394 395 file_time_type dtime = last_write_time(dir); 396 TEST_CHECK(Clock::to_time_t(dtime) == dir_write_time.tv_sec); 397 TEST_CHECK(CompareTime(dtime, dir_write_time)); 398 399 SleepFor(Sec(2)); 400 401 // update file and add a file to the directory. Make sure the times increase. 402 std::ofstream of(file, std::ofstream::app); 403 of << "hello"; 404 of.close(); 405 env.create_file("dir/file1", 1); 406 407 file_time_type ftime2 = last_write_time(file); 408 file_time_type dtime2 = last_write_time(dir); 409 410 TEST_CHECK(ftime2 > ftime); 411 TEST_CHECK(dtime2 > dtime); 412 TEST_CHECK(CompareTime(LastWriteTime(file), ftime2)); 413 TEST_CHECK(CompareTime(LastWriteTime(dir), dtime2)); 414 } 415 416 417 TEST_CASE(set_last_write_time_dynamic_env_test) 418 { 419 using Clock = file_time_type::clock; 420 scoped_test_env env; 421 422 const path file = env.create_file("file", 42); 423 const path dir = env.create_dir("dir"); 424 const auto now = Clock::now(); 425 const file_time_type epoch_time = now - now.time_since_epoch(); 426 427 const file_time_type future_time = now + Hours(3) + Sec(42) + SubSec(17); 428 const file_time_type past_time = now - Minutes(3) - Sec(42) - SubSec(17); 429 const file_time_type before_epoch_time = 430 epoch_time - Minutes(3) - Sec(42) - SubSec(17); 431 // FreeBSD has a bug in their utimes implementation where the time is not update 432 // when the number of seconds is '-1'. 433 #if defined(__FreeBSD__) || defined(__NetBSD__) 434 const file_time_type just_before_epoch_time = 435 epoch_time - Sec(2) - SubSec(17); 436 #else 437 const file_time_type just_before_epoch_time = epoch_time - SubSec(17); 438 #endif 439 440 struct TestCase { 441 const char * case_name; 442 path p; 443 file_time_type new_time; 444 } cases[] = { 445 {"file, epoch_time", file, epoch_time}, 446 {"dir, epoch_time", dir, epoch_time}, 447 {"file, future_time", file, future_time}, 448 {"dir, future_time", dir, future_time}, 449 {"file, past_time", file, past_time}, 450 {"dir, past_time", dir, past_time}, 451 {"file, before_epoch_time", file, before_epoch_time}, 452 {"dir, before_epoch_time", dir, before_epoch_time}, 453 {"file, just_before_epoch_time", file, just_before_epoch_time}, 454 {"dir, just_before_epoch_time", dir, just_before_epoch_time} 455 }; 456 for (const auto& TC : cases) { 457 std::cerr << "Test Case = " << TC.case_name << "\n"; 458 const auto old_times = GetTimes(TC.p); 459 file_time_type old_time; 460 TEST_REQUIRE(ConvertFromTimeSpec(old_time, old_times.write)); 461 462 std::error_code ec = GetTestEC(); 463 last_write_time(TC.p, TC.new_time, ec); 464 TEST_CHECK(!ec); 465 466 ec = GetTestEC(); 467 file_time_type got_time = last_write_time(TC.p, ec); 468 TEST_REQUIRE(!ec); 469 470 if (TimeIsRepresentableByFilesystem(TC.new_time)) { 471 TEST_CHECK(got_time != old_time); 472 TEST_CHECK(CompareTime(got_time, TC.new_time)); 473 TEST_CHECK(CompareTime(LastAccessTime(TC.p), old_times.access)); 474 } 475 } 476 } 477 478 TEST_CASE(last_write_time_symlink_test) 479 { 480 using Clock = file_time_type::clock; 481 482 scoped_test_env env; 483 484 const path file = env.create_file("file", 42); 485 const path sym = env.create_symlink("file", "sym"); 486 487 const file_time_type new_time = Clock::now() + Hours(3); 488 489 const auto old_times = GetTimes(sym); 490 const auto old_sym_times = GetSymlinkTimes(sym); 491 492 std::error_code ec = GetTestEC(); 493 last_write_time(sym, new_time, ec); 494 TEST_CHECK(!ec); 495 496 file_time_type got_time = last_write_time(sym); 497 TEST_CHECK(!CompareTime(got_time, old_times.write)); 498 if (!WorkaroundStatTruncatesToSeconds) { 499 TEST_CHECK(got_time == new_time); 500 } else { 501 TEST_CHECK(CompareTime(got_time, new_time)); 502 } 503 504 TEST_CHECK(CompareTime(LastWriteTime(file), new_time)); 505 TEST_CHECK(CompareTime(LastAccessTime(sym), old_times.access)); 506 Times sym_times = GetSymlinkTimes(sym); 507 TEST_CHECK(CompareTime(sym_times.write, old_sym_times.write)); 508 } 509 510 511 TEST_CASE(test_write_min_time) 512 { 513 scoped_test_env env; 514 const path p = env.create_file("file", 42); 515 const file_time_type old_time = last_write_time(p); 516 file_time_type new_time = file_time_type::min(); 517 518 std::error_code ec = GetTestEC(); 519 last_write_time(p, new_time, ec); 520 file_time_type tt = last_write_time(p); 521 522 if (TimeIsRepresentableByFilesystem(new_time)) { 523 TEST_CHECK(!ec); 524 TEST_CHECK(CompareTime(tt, new_time)); 525 526 last_write_time(p, old_time); 527 new_time = file_time_type::min() + SubSec(1); 528 529 ec = GetTestEC(); 530 last_write_time(p, new_time, ec); 531 tt = last_write_time(p); 532 533 if (TimeIsRepresentableByFilesystem(new_time)) { 534 TEST_CHECK(!ec); 535 TEST_CHECK(CompareTime(tt, new_time)); 536 } else { 537 TEST_CHECK(ErrorIs(ec, std::errc::value_too_large)); 538 TEST_CHECK(tt == old_time); 539 } 540 } else { 541 TEST_CHECK(ErrorIs(ec, std::errc::value_too_large)); 542 TEST_CHECK(tt == old_time); 543 } 544 } 545 546 TEST_CASE(test_write_max_time) { 547 scoped_test_env env; 548 const path p = env.create_file("file", 42); 549 const file_time_type old_time = last_write_time(p); 550 file_time_type new_time = file_time_type::max(); 551 552 std::error_code ec = GetTestEC(); 553 last_write_time(p, new_time, ec); 554 file_time_type tt = last_write_time(p); 555 556 if (TimeIsRepresentableByFilesystem(new_time)) { 557 TEST_CHECK(!ec); 558 TEST_CHECK(CompareTime(tt, new_time)); 559 } else { 560 TEST_CHECK(ErrorIs(ec, std::errc::value_too_large)); 561 TEST_CHECK(tt == old_time); 562 } 563 } 564 565 TEST_CASE(test_value_on_failure) 566 { 567 const path p = StaticEnv::DNE; 568 std::error_code ec = GetTestEC(); 569 TEST_CHECK(last_write_time(p, ec) == file_time_type::min()); 570 TEST_CHECK(ErrorIs(ec, std::errc::no_such_file_or_directory)); 571 } 572 573 TEST_CASE(test_exists_fails) 574 { 575 scoped_test_env env; 576 const path dir = env.create_dir("dir"); 577 const path file = env.create_file("dir/file", 42); 578 permissions(dir, perms::none); 579 580 std::error_code ec = GetTestEC(); 581 TEST_CHECK(last_write_time(file, ec) == file_time_type::min()); 582 TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); 583 584 ExceptionChecker Checker(file, std::errc::permission_denied, 585 "last_write_time"); 586 TEST_CHECK_THROW_RESULT(filesystem_error, Checker, last_write_time(file)); 587 } 588 589 TEST_SUITE_END()