commit c91a86730ed8df76325dd0649d8837e6feb24aab Author: Jonathan Wakely Date: Sat Jan 5 12:52:40 2019 +0000 Define new filesystem::__file_clock type In C++17 the clock used for filesystem::file_time_type is unspecified, allowing it to be chrono::system_clock. The C++2a draft requires it to be a distinct type, with additional member functions to convert to/from other clocks (either the system clock or UTC). In order to avoid an ABI change later, this patch defines a new distinct type now, which will be used for std::chrono::file_clock later. * include/bits/fs_fwd.h (__file_clock): Define new clock. (file_time_type): Redefine in terms of __file_clock. * src/filesystem/ops-common.h (file_time): Add FIXME comment about overflow. * src/filesystem/std-ops.cc (is_set(perm_options, perm_options)): Give internal linkage. (internal_file_lock): New helper type for accessing __file_clock. (do_copy_file): Use internal_file_lock to convert system time to file_time_type. (last_write_time(const path&, error_code&)): Likewise. (last_write_time(const path&, file_time_type, error_code&)): Likewise. diff --git a/libstdc++-v3/include/bits/fs_fwd.h b/libstdc++-v3/include/bits/fs_fwd.h index 3bcf2dd650c..a0e3d73e2a3 100644 --- a/libstdc++-v3/include/bits/fs_fwd.h +++ b/libstdc++-v3/include/bits/fs_fwd.h @@ -294,7 +294,49 @@ _GLIBCXX_END_NAMESPACE_CXX11 operator^=(directory_options& __x, directory_options __y) noexcept { return __x = __x ^ __y; } - using file_time_type = std::chrono::system_clock::time_point; + struct __file_clock + { + using duration = chrono::nanoseconds; + using rep = duration::rep; + using period = duration::period; + using time_point = chrono::time_point<__file_clock>; + static constexpr bool is_steady = false; + + static time_point + now() noexcept + { return _S_from_sys(chrono::system_clock::now()); } + + private: + using __sys_clock = chrono::system_clock; + + // This clock's (unspecified) epoch is 2174-01-01 00:00:00 UTC. + // A signed 64-bit duration with nanosecond resolution gives roughly + // +/- 292 years, which covers the 1901-2446 date range for ext4. + static constexpr chrono::seconds _S_epoch_diff{6437664000}; + + protected: + // For internal use only + template + static + chrono::time_point<__file_clock, _Dur> + _S_from_sys(const chrono::time_point<__sys_clock, _Dur>& __t) noexcept + { + using __file_time = chrono::time_point<__file_clock, _Dur>; + return __file_time{__t.time_since_epoch()} - _S_epoch_diff; + } + + // For internal use only + template + static + chrono::time_point<__sys_clock, _Dur> + _S_to_sys(const chrono::time_point<__file_clock, _Dur>& __t) noexcept + { + using __sys_time = chrono::time_point<__sys_clock, _Dur>; + return __sys_time{__t.time_since_epoch()} + _S_epoch_diff; + } + }; + + using file_time_type = __file_clock::time_point; // operational functions diff --git a/libstdc++-v3/src/filesystem/ops-common.h b/libstdc++-v3/src/filesystem/ops-common.h index dcd61cc26cd..1c0d650f444 100644 --- a/libstdc++-v3/src/filesystem/ops-common.h +++ b/libstdc++-v3/src/filesystem/ops-common.h @@ -158,6 +158,24 @@ namespace __gnu_posix nanoseconds ns{}; #endif + // FIXME + // There are possible timespec values which will overflow + // chrono::system_clock::time_point but would not overflow + // __file_clock::time_point, due to its different epoch. + // + // By checking for overflow of the intermediate system_clock::duration + // type, we report an error for values which are actually representable + // in the file_time_type result type. + // + // Howard Hinnant's solution for this problem is to use + // duration<__int128>{s} + ns, which doesn't overflow. + // An alternative would be to do the epoch correction on s before + // the addition, and then go straight to file_time_type instead of + // going via chrono::system_clock::time_point. + // + // (This only applies to the C++17 Filesystem library, because for the + // Filesystem TS we don't have a distinct __file_clock, we just use the + // system clock for file timestamps). if (s >= (nanoseconds::max().count() / 1e9)) { ec = std::make_error_code(std::errc::value_too_large); // EOVERFLOW diff --git a/libstdc++-v3/src/filesystem/std-ops.cc b/libstdc++-v3/src/filesystem/std-ops.cc index 26a7ad2a198..8c3ec1d9a9a 100644 --- a/libstdc++-v3/src/filesystem/std-ops.cc +++ b/libstdc++-v3/src/filesystem/std-ops.cc @@ -268,13 +268,32 @@ fs::copy(const path& from, const path& to, copy_options options) namespace std::filesystem { // Need this as there's no 'perm_options::none' enumerator. - inline bool is_set(fs::perm_options obj, fs::perm_options bits) + static inline bool is_set(fs::perm_options obj, fs::perm_options bits) { return (obj & bits) != fs::perm_options{}; } } #ifdef _GLIBCXX_HAVE_SYS_STAT_H + +namespace +{ + struct internal_file_clock : fs::__file_clock + { + using __file_clock::_S_to_sys; + using __file_clock::_S_from_sys; + + static fs::file_time_type + from_stat(const fs::stat_type& st, std::error_code& ec) noexcept + { + const auto sys_time = fs::file_time(st, ec); + if (sys_time == sys_time.min()) + return fs::file_time_type::min(); + return _S_from_sys(sys_time); + } + }; +} + #ifdef NEED_DO_COPY_FILE bool fs::do_copy_file(const path::value_type* from, const path::value_type* to, @@ -348,10 +367,10 @@ fs::do_copy_file(const path::value_type* from, const path::value_type* to, } else if (options.update) { - const auto from_mtime = file_time(*from_st, ec); + const auto from_mtime = internal_file_clock::from_stat(*from_st, ec); if (ec) return false; - if ((from_mtime <= file_time(*to_st, ec)) || ec) + if ((from_mtime <= internal_file_clock::from_stat(*to_st, ec)) || ec) return false; } else if (!options.overwrite) @@ -1122,7 +1141,10 @@ fs::last_write_time(const path& p) fs::file_time_type fs::last_write_time(const path& p, error_code& ec) noexcept { - return do_stat(p, ec, [&ec](const auto& st) { return file_time(st, ec); }, + return do_stat(p, ec, + [&ec](const auto& st) { + return internal_file_clock::from_stat(st, ec); + }, file_time_type::min()); } @@ -1139,7 +1161,7 @@ void fs::last_write_time(const path& p __attribute__((__unused__)), file_time_type new_time, error_code& ec) noexcept { - auto d = new_time.time_since_epoch(); + auto d = internal_file_clock::_S_to_sys(new_time).time_since_epoch(); auto s = chrono::duration_cast(d); #if _GLIBCXX_USE_UTIMENSAT auto ns = chrono::duration_cast(d - s);