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__)
extract_mtime(StatT const & st)49 TimeSpec extract_mtime(StatT const& st) { return st.st_mtimespec; }
extract_atime(StatT const & st)50 TimeSpec extract_atime(StatT const& st) { return st.st_atimespec; }
51 #else
extract_mtime(StatT const & st)52 TimeSpec extract_mtime(StatT const& st) { return st.st_mtim; }
extract_atime(StatT const & st)53 TimeSpec extract_atime(StatT const& st) { return st.st_atim; }
54 #endif
55
ConvertToTimeSpec(TimeSpec & ts,file_time_type ft)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
ConvertFromTimeSpec(file_time_type & ft,TimeSpec ts)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
CompareTimeExact(TimeSpec ts,TimeSpec ts2)93 bool CompareTimeExact(TimeSpec ts, TimeSpec ts2) {
94 return ts2.tv_sec == ts.tv_sec && ts2.tv_nsec == ts.tv_nsec;
95 }
CompareTimeExact(file_time_type ft,TimeSpec ts)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 }
CompareTimeExact(TimeSpec ts,file_time_type ft)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
GetTimes(path const & p)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
LastAccessTime(path const & p)124 TimeSpec LastAccessTime(path const& p) { return GetTimes(p).access; }
125
LastWriteTime(path const & p)126 TimeSpec LastWriteTime(path const& p) { return GetTimes(p).write; }
127
GetSymlinkTimes(path const & p)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
__anonae5559740202null157 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
__anonae5559740302null173 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
__anonae5559740402null192 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
__anonae5559740502null210 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.
__anonae5559740602null229 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
__anonae5559740702null244 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
CompareTime(TimeSpec t1,TimeSpec t2)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
CompareTime(file_time_type t1,TimeSpec t2)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
CompareTime(TimeSpec t1,file_time_type t2)275 static bool CompareTime(TimeSpec t1, file_time_type t2) {
276 return CompareTime(t2, t1);
277 }
278
CompareTime(file_time_type t1,file_time_type t2)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.
TimeIsRepresentableByFilesystem(file_time_type tp)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.
SubSec(long long val)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
TEST_CASE(signature_test)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
TEST_CASE(read_last_write_time_static_env_test)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
TEST_CASE(get_last_write_time_dynamic_env_test)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
TEST_CASE(set_last_write_time_dynamic_env_test)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
TEST_CASE(last_write_time_symlink_test)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
TEST_CASE(test_write_min_time)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
TEST_CASE(test_write_max_time)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
TEST_CASE(test_value_on_failure)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
TEST_CASE(test_exists_fails)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()
590