From 09cc08654a98e74868b4e00b561878c5deefa045 Mon Sep 17 00:00:00 2001 From: Eric Fiselier Date: Wed, 28 Sep 2016 21:16:58 +0000 Subject: [PATCH] Improve 'last_write_time(...)' accuracy and detect overflow errors. The ::stat struct on Linux, FreeBSD, and OS X provides the access and modification times as an instance of 'timespec', which has a nanosecond resolution. The 'st_mtime' and 'st_atime' members simply reference the 'tv_sec' value of the timespec struct. This patch changes 'last_write_time(...)' so that it extracts both the seconds and nanoseconds values of the last modification time, providing a more accurate implementation of 'last_write_time(...)'. Additionally this patch fixes a possible signed integer overflow bug. The 'file_time_type' type cannot represent all possible values returned by the filesystem. Attempting to construct a 'file_time_type' from one of these values is undefined behavior. This patch avoids that UB by detecting possible overflows before the conversion. llvm-svn: 282634 --- libcxx/src/experimental/filesystem/operations.cpp | 40 ++++++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/libcxx/src/experimental/filesystem/operations.cpp b/libcxx/src/experimental/filesystem/operations.cpp index f426262b..2eb0507 100644 --- a/libcxx/src/experimental/filesystem/operations.cpp +++ b/libcxx/src/experimental/filesystem/operations.cpp @@ -509,7 +509,6 @@ bool set_times_checked(time_t* sec_out, SubSecT* subsec_out, file_time_type tp) // The tv_nsec and tv_usec fields must not be negative so adjust accordingly if (subsec_dur.count() < 0) { if (sec_dur.count() > min_seconds) { - sec_dur -= seconds(1); subsec_dur += seconds(1); } else { @@ -520,11 +519,24 @@ bool set_times_checked(time_t* sec_out, SubSecT* subsec_out, file_time_type tp) && checked_set(subsec_out, subsec_dur.count()); } +using TimeSpec = struct ::timespec; +using StatT = struct ::stat; + +#if defined(__APPLE__) +TimeSpec extract_mtime(StatT const& st) { return st.st_mtimespec; } +TimeSpec extract_atime(StatT const& st) { return st.st_atimespec; } +#else +TimeSpec extract_mtime(StatT const& st) { return st.st_mtim; } +__attribute__((unused)) // Suppress warning +TimeSpec extract_atime(StatT const& st) { return st.st_atim; } +#endif + }} // end namespace detail file_time_type __last_write_time(const path& p, std::error_code *ec) { + using namespace ::std::chrono; std::error_code m_ec; struct ::stat st; detail::posix_stat(p, st, &m_ec); @@ -532,8 +544,25 @@ file_time_type __last_write_time(const path& p, std::error_code *ec) set_or_throw(m_ec, ec, "last_write_time", p); return file_time_type::min(); } - if (ec) ec->clear(); - return file_time_type::clock::from_time_t(st.st_mtime); +#ifndef _LIBCPP_HAS_NO_INT128 + using IntMax = __int128_t; +#else + using IntMax = intmax_t; +#endif + // FIXME: The value may not be representable as file_time_type. Fix + // file_time_type so it can represent all possible values returned by the + // filesystem. For now we do the calculation with the largest possible types + // and then truncate, this prevents signed integer overflow bugs. + auto ts = detail::extract_mtime(st); + const auto NsDur = duration(ts.tv_nsec) + seconds(ts.tv_sec); + if (NsDur > file_time_type::max().time_since_epoch() || + NsDur < file_time_type::min().time_since_epoch()) { + set_or_throw(error_code(EOVERFLOW, generic_category()), ec, + "last_write_time", p); + return file_time_type::min(); + } + if (ec) ec->clear(); + return file_time_type(duration_cast(NsDur)); } void __last_write_time(const path& p, file_time_type new_time, @@ -554,9 +583,10 @@ void __last_write_time(const path& p, file_time_type new_time, set_or_throw(m_ec, ec, "last_write_time", p); return; } + auto atime = detail::extract_atime(st); struct ::timeval tbuf[2]; - tbuf[0].tv_sec = st.st_atime; - tbuf[0].tv_usec = 0; + tbuf[0].tv_sec = atime.tv_sec; + tbuf[0].tv_usec = duration_cast(nanoseconds(atime.tv_nsec)).count(); const bool overflowed = !detail::set_times_checked( &tbuf[1].tv_sec, &tbuf[1].tv_usec, new_time); -- 2.7.4