Index: include/experimental/filesystem =================================================================== --- include/experimental/filesystem +++ include/experimental/filesystem @@ -260,7 +260,37 @@ _LIBCPP_BEGIN_NAMESPACE_EXPERIMENTAL_FILESYSTEM -typedef chrono::time_point file_time_type; +struct _FilesystemClock { +#if !defined(_LIBCPP_HAS_NO_INT128) + typedef __int128_t rep; + typedef nano period; +#else + typedef long long rep; + typedef nano period; +#endif + + typedef chrono::duration duration; + typedef chrono::time_point<_FilesystemClock> time_point; + + static _LIBCPP_CONSTEXPR_AFTER_CXX11 const bool is_steady = false; + + _LIBCPP_FUNC_VIS static time_point now() noexcept; + + _LIBCPP_INLINE_VISIBILITY + static time_t to_time_t(const time_point& __t) noexcept { + typedef chrono::duration __secs; + return time_t( + chrono::duration_cast<__secs>(__t.time_since_epoch()).count()); + } + + _LIBCPP_INLINE_VISIBILITY + static time_point from_time_t(time_t __t) noexcept { + typedef chrono::duration __secs; + return time_point(__secs(__t)); + } +}; + +typedef chrono::time_point<_FilesystemClock> file_time_type; struct _LIBCPP_TYPE_VIS space_info { Index: src/chrono.cpp =================================================================== --- src/chrono.cpp +++ src/chrono.cpp @@ -11,27 +11,10 @@ #include "cerrno" // errno #include "system_error" // __throw_system_error #include // clock_gettime, CLOCK_MONOTONIC and CLOCK_REALTIME +#include "include/apple_availability.h" -#if (__APPLE__) -#if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) -#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101200 -#define _LIBCXX_USE_CLOCK_GETTIME -#endif -#elif defined(__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__) -#if __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ >= 100000 -#define _LIBCXX_USE_CLOCK_GETTIME -#endif -#elif defined(__ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__) -#if __ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__ >= 100000 -#define _LIBCXX_USE_CLOCK_GETTIME -#endif -#elif defined(__ENVIRONMENT_WATCH_OS_VERSION_MIN_REQUIRED__) -#if __ENVIRONMENT_WATCH_OS_VERSION_MIN_REQUIRED__ >= 30000 -#define _LIBCXX_USE_CLOCK_GETTIME -#endif -#endif // __ENVIRONMENT_.*_VERSION_MIN_REQUIRED__ -#else -#define _LIBCXX_USE_CLOCK_GETTIME +#if !defined(__APPLE__) +#define _LIBCPP_USE_CLOCK_GETTIME #endif // __APPLE__ #if defined(_LIBCPP_WIN32API) @@ -42,7 +25,7 @@ #include #endif #else -#if !defined(CLOCK_REALTIME) || !defined(_LIBCXX_USE_CLOCK_GETTIME) +#if !defined(CLOCK_REALTIME) || !defined(_LIBCPP_USE_CLOCK_GETTIME) #include // for gettimeofday and timeval #endif // !defined(CLOCK_REALTIME) #endif // defined(_LIBCPP_WIN32API) @@ -92,16 +75,16 @@ static_cast<__int64>(ft.dwLowDateTime)}; return time_point(duration_cast(d - nt_to_unix_epoch)); #else -#if defined(_LIBCXX_USE_CLOCK_GETTIME) && defined(CLOCK_REALTIME) - struct timespec tp; - if (0 != clock_gettime(CLOCK_REALTIME, &tp)) - __throw_system_error(errno, "clock_gettime(CLOCK_REALTIME) failed"); - return time_point(seconds(tp.tv_sec) + microseconds(tp.tv_nsec / 1000)); +#if defined(_LIBCPP_USE_CLOCK_GETTIME) && defined(CLOCK_REALTIME) + struct timespec tp; + if (0 != clock_gettime(CLOCK_REALTIME, &tp)) + __throw_system_error(errno, "clock_gettime(CLOCK_REALTIME) failed"); + return time_point(seconds(tp.tv_sec) + microseconds(tp.tv_nsec / 1000)); #else timeval tv; gettimeofday(&tv, 0); return time_point(seconds(tv.tv_sec) + microseconds(tv.tv_usec)); -#endif // _LIBCXX_USE_CLOCK_GETTIME && CLOCK_REALTIME +#endif // _LIBCPP_USE_CLOCK_GETTIME && CLOCK_REALTIME #endif } @@ -129,7 +112,7 @@ #if defined(__APPLE__) // Darwin libc versions >= 1133 provide ns precision via CLOCK_UPTIME_RAW -#if defined(_LIBCXX_USE_CLOCK_GETTIME) && defined(CLOCK_UPTIME_RAW) +#if defined(_LIBCPP_USE_CLOCK_GETTIME) && defined(CLOCK_UPTIME_RAW) steady_clock::time_point steady_clock::now() _NOEXCEPT { @@ -191,7 +174,7 @@ static FP fp = init_steady_clock(); return time_point(duration(fp())); } -#endif // defined(_LIBCXX_USE_CLOCK_GETTIME) && defined(CLOCK_UPTIME_RAW) +#endif // defined(_LIBCPP_USE_CLOCK_GETTIME) && defined(CLOCK_UPTIME_RAW) #elif defined(_LIBCPP_WIN32API) Index: src/experimental/filesystem/filesystem_common.h =================================================================== --- src/experimental/filesystem/filesystem_common.h +++ src/experimental/filesystem/filesystem_common.h @@ -23,33 +23,17 @@ #include -#if (__APPLE__) -#if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) -#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101300 -#define _LIBCXX_USE_UTIMENSAT -#endif -#elif defined(__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__) -#if __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ >= 110000 -#define _LIBCXX_USE_UTIMENSAT -#endif -#elif defined(__ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__) -#if __ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__ >= 110000 -#define _LIBCXX_USE_UTIMENSAT -#endif -#elif defined(__ENVIRONMENT_WATCH_OS_VERSION_MIN_REQUIRED__) -#if __ENVIRONMENT_WATCH_OS_VERSION_MIN_REQUIRED__ >= 40000 -#define _LIBCXX_USE_UTIMENSAT -#endif -#endif // __ENVIRONMENT_.*_VERSION_MIN_REQUIRED__ -#else +#include "../../include/apple_availability.h" + +#if !defined(__APPLE__) // We can use the presence of UTIME_OMIT to detect platforms that provide // utimensat. #if defined(UTIME_OMIT) -#define _LIBCXX_USE_UTIMENSAT +#define _LIBCPP_USE_UTIMENSAT +#endif #endif -#endif // __APPLE__ -#if !defined(_LIBCXX_USE_UTIMENSAT) +#if !defined(_LIBCPP_USE_UTIMENSAT) #include // for ::utimes as used in __last_write_time #endif @@ -212,76 +196,119 @@ ErrorHandler& operator=(ErrorHandler const&) = delete; }; -namespace time_util { +using chrono::duration; +using chrono::duration_cast; -using namespace chrono; +using TimeSpec = struct ::timespec; +using StatT = struct ::stat; -template ::value> -struct fs_time_util_base { - static constexpr seconds::rep max_seconds = - duration_cast(FileTimeT::duration::max()).count(); - - static constexpr nanoseconds::rep max_nsec = - duration_cast(FileTimeT::duration::max() - - seconds(max_seconds)) +struct time_util_base { + using rep = typename FileTimeT::rep; + using fs_duration = typename FileTimeT::duration; + using fs_seconds = duration; + using fs_nanoseconds = duration; + using fs_microseconds = duration; + + static constexpr rep max_seconds = + duration_cast(FileTimeT::duration::max()).count(); + + static constexpr rep max_nsec = + duration_cast(FileTimeT::duration::max() - + fs_seconds(max_seconds)) .count(); - static constexpr seconds::rep min_seconds = - duration_cast(FileTimeT::duration::min()).count(); + static constexpr rep min_seconds = + duration_cast(FileTimeT::duration::min()).count(); - static constexpr nanoseconds::rep min_nsec_timespec = - duration_cast( - (FileTimeT::duration::min() - seconds(min_seconds)) + seconds(1)) + static constexpr rep min_nsec_timespec = + duration_cast( + (FileTimeT::duration::min() - fs_seconds(min_seconds)) + + fs_seconds(1)) .count(); +private: +#if _LIBCPP_STD_VER > 11 + static constexpr fs_duration get_min_nsecs() { + return duration_cast( + fs_nanoseconds(min_nsec_timespec) - + duration_cast(fs_seconds(1))); + } // Static assert that these values properly round trip. - static_assert((seconds(min_seconds) + - duration_cast(nanoseconds(min_nsec_timespec))) - - duration_cast(seconds(1)) == + static_assert(fs_seconds(min_seconds) + get_min_nsecs() == FileTimeT::duration::min(), - ""); + "value doesn't roundtrip"); + + static constexpr bool check_range() { + // This kinda sucks, but it's what happens when we don't have __int128_t. + if (sizeof(TimeT) == sizeof(rep)) { + typedef duration > Years; + return duration_cast(fs_seconds(max_seconds)) > Years(250) && + duration_cast(fs_seconds(min_seconds)) < Years(-250); + } + return max_seconds >= numeric_limits::max() && + min_seconds <= numeric_limits::min(); + } + static_assert(check_range(), "the representable range is unacceptable small"); +#endif }; -template -struct fs_time_util_base { - static const long long max_seconds; - static const long long max_nsec; - static const long long min_seconds; - static const long long min_nsec_timespec; +template +struct time_util_base { + using rep = typename FileTimeT::rep; + using fs_duration = typename FileTimeT::duration; + using fs_seconds = duration; + using fs_nanoseconds = duration; + using fs_microseconds = duration; + + static const rep max_seconds; + static const rep max_nsec; + static const rep min_seconds; + static const rep min_nsec_timespec; }; -template -const long long fs_time_util_base::max_seconds = - duration_cast(FileTimeT::duration::max()).count(); - -template -const long long fs_time_util_base::max_nsec = - duration_cast(FileTimeT::duration::max() - - seconds(max_seconds)) +template +const typename FileTimeT::rep + time_util_base::max_seconds = + duration_cast(FileTimeT::duration::max()).count(); + +template +const typename FileTimeT::rep time_util_base::max_nsec = + duration_cast(FileTimeT::duration::max() - + fs_seconds(max_seconds)) .count(); -template -const long long fs_time_util_base::min_seconds = - duration_cast(FileTimeT::duration::min()).count(); - -template -const long long fs_time_util_base::min_nsec_timespec = - duration_cast( - (FileTimeT::duration::min() - seconds(min_seconds)) + seconds(1)) - .count(); +template +const typename FileTimeT::rep + time_util_base::min_seconds = + duration_cast(FileTimeT::duration::min()).count(); + +template +const typename FileTimeT::rep + time_util_base::min_nsec_timespec = + duration_cast((FileTimeT::duration::min() - + fs_seconds(min_seconds)) + + fs_seconds(1)) + .count(); template -struct fs_time_util : fs_time_util_base { - using Base = fs_time_util_base; +struct time_util : time_util_base { + using Base = time_util_base; using Base::max_nsec; using Base::max_seconds; using Base::min_nsec_timespec; using Base::min_seconds; + using typename Base::fs_duration; + using typename Base::fs_microseconds; + using typename Base::fs_nanoseconds; + using typename Base::fs_seconds; + public: template - static bool checked_set(CType* out, ChronoType time) { + static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool checked_set(CType* out, + ChronoType time) { using Lim = numeric_limits; if (time > Lim::max() || time < Lim::min()) return false; @@ -291,21 +318,21 @@ static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool is_representable(TimeSpecT tm) { if (tm.tv_sec >= 0) { - return (tm.tv_sec < max_seconds) || + return tm.tv_sec < max_seconds || (tm.tv_sec == max_seconds && tm.tv_nsec <= max_nsec); } else if (tm.tv_sec == (min_seconds - 1)) { return tm.tv_nsec >= min_nsec_timespec; } else { - return (tm.tv_sec >= min_seconds); + return tm.tv_sec >= min_seconds; } } static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool is_representable(FileTimeT tm) { - auto secs = duration_cast(tm.time_since_epoch()); - auto nsecs = duration_cast(tm.time_since_epoch() - secs); + auto secs = duration_cast(tm.time_since_epoch()); + auto nsecs = duration_cast(tm.time_since_epoch() - secs); if (nsecs.count() < 0) { - secs = secs + seconds(1); - nsecs = nsecs + seconds(1); + secs = secs + fs_seconds(1); + nsecs = nsecs + fs_seconds(1); } using TLim = numeric_limits; if (secs.count() >= 0) @@ -314,49 +341,45 @@ } static _LIBCPP_CONSTEXPR_AFTER_CXX11 FileTimeT - convert_timespec(TimeSpecT tm) { - auto adj_msec = duration_cast(nanoseconds(tm.tv_nsec)); - if (tm.tv_sec >= 0) { - auto Dur = seconds(tm.tv_sec) + microseconds(adj_msec); - return FileTimeT(Dur); - } else if (duration_cast(nanoseconds(tm.tv_nsec)).count() == - 0) { - return FileTimeT(seconds(tm.tv_sec)); + convert_from_timespec(TimeSpecT tm) { + if (tm.tv_sec >= 0 || tm.tv_nsec == 0) { + return FileTimeT(fs_seconds(tm.tv_sec) + + duration_cast(fs_nanoseconds(tm.tv_nsec))); } else { // tm.tv_sec < 0 - auto adj_subsec = - duration_cast(seconds(1) - nanoseconds(tm.tv_nsec)); - auto Dur = seconds(tm.tv_sec + 1) - adj_subsec; + auto adj_subsec = duration_cast(fs_seconds(1) - + fs_nanoseconds(tm.tv_nsec)); + auto Dur = fs_seconds(tm.tv_sec + 1) - adj_subsec; return FileTimeT(Dur); } } - template - static bool set_times_checked(TimeT* sec_out, SubSecT* subsec_out, - FileTimeT tp) { + template + static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool + set_times_checked(TimeT* sec_out, SubSecT* subsec_out, FileTimeT tp) { auto dur = tp.time_since_epoch(); - auto sec_dur = duration_cast(dur); - auto subsec_dur = duration_cast(dur - sec_dur); + auto sec_dur = duration_cast(dur); + auto subsec_dur = duration_cast(dur - sec_dur); // 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); + sec_dur -= fs_seconds(1); + subsec_dur += fs_seconds(1); } else { - subsec_dur = SubSecDurT::zero(); + subsec_dur = fs_nanoseconds::zero(); } } return checked_set(sec_out, sec_dur.count()) && checked_set(subsec_out, subsec_dur.count()); } + static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool convert_to_timespec(TimeSpecT& dest, + FileTimeT tp) { + if (!is_representable(tp)) + return false; + return set_times_checked(&dest.tv_sec, &dest.tv_nsec, tp); + } }; -} // namespace time_util - - -using TimeSpec = struct ::timespec; -using StatT = struct ::stat; - -using FSTime = time_util::fs_time_util; +using fs_time = time_util; #if defined(__APPLE__) TimeSpec extract_mtime(StatT const& st) { return st.st_mtimespec; } @@ -366,20 +389,18 @@ TimeSpec extract_atime(StatT const& st) { return st.st_atim; } #endif -#if !defined(_LIBCXX_USE_UTIMENSAT) -using TimeStruct = struct ::timeval; -using TimeStructArray = TimeStruct[2]; -#else -using TimeStruct = TimeSpec; -using TimeStructArray = TimeStruct[2]; -#endif - -bool SetFileTimes(const path& p, TimeStructArray const& TS, - error_code& ec) { -#if !defined(_LIBCXX_USE_UTIMENSAT) - if (::utimes(p.c_str(), TS) == -1) +bool set_file_times(const path& p, std::array const& TS, + error_code& ec) { +#if !defined(_LIBCPP_USE_UTIMENSAT) + using namespace chrono; + auto Convert = [](long nsec) { + return duration_cast(nanoseconds(nsec)).count(); + }; + struct ::timeval ConvertedTS[2] = {{TS[0].tv_sec, Convert(TS[0].tv_nsec)}, + {TS[1].tv_sec, Convert(TS[1].tv_nsec)}}; + if (::utimes(p.c_str(), ConvertedTS) == -1) #else - if (::utimensat(AT_FDCWD, p.c_str(), TS, 0) == -1) + if (::utimensat(AT_FDCWD, p.c_str(), TS.data(), 0) == -1) #endif { ec = capture_errno(); @@ -388,25 +409,9 @@ return false; } -void SetTimeStructTo(TimeStruct& TS, TimeSpec ToTS) { - using namespace chrono; - TS.tv_sec = ToTS.tv_sec; -#if !defined(_LIBCXX_USE_UTIMENSAT) - TS.tv_usec = duration_cast(nanoseconds(ToTS.tv_nsec)).count(); -#else - TS.tv_nsec = ToTS.tv_nsec; -#endif -} - -bool SetTimeStructTo(TimeStruct& TS, file_time_type NewTime) { - using namespace chrono; -#if !defined(_LIBCXX_USE_UTIMENSAT) - return !FSTime::set_times_checked(&TS.tv_sec, &TS.tv_usec, - NewTime); -#else - return !FSTime::set_times_checked(&TS.tv_sec, &TS.tv_nsec, - NewTime); -#endif +bool set_time_spec_to(TimeSpec& TS, file_time_type NewTime) { + return !fs_time::set_times_checked( + &TS.tv_sec, &TS.tv_nsec, NewTime); } } // namespace Index: src/experimental/filesystem/operations.cpp =================================================================== --- src/experimental/filesystem/operations.cpp +++ src/experimental/filesystem/operations.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include /* values for fchmodat */ #if defined(__linux__) @@ -36,6 +37,14 @@ # define _LIBCPP_USE_COPYFILE #endif +#if !defined(__APPLE__) +#define _LIBCPP_USE_CLOCK_GETTIME +#endif + +#if !defined(CLOCK_REALTIME) || !defined(_LIBCPP_USE_CLOCK_GETTIME) +#include // for gettimeofday and timeval +#endif // !defined(CLOCK_REALTIME) + #if defined(_LIBCPP_COMPILER_GCC) #if _GNUC_VER < 500 #pragma GCC diagnostic ignored "-Wmissing-field-initializers" @@ -44,9 +53,6 @@ _LIBCPP_BEGIN_NAMESPACE_EXPERIMENTAL_FILESYSTEM -filesystem_error::~filesystem_error() {} - - namespace { namespace parser { @@ -355,7 +361,7 @@ explicit FileDescriptor(const path* p, int fd = -1) : name(*p), fd(fd) {} }; -perms posix_get_perms(const struct ::stat& st) noexcept { +perms posix_get_perms(const StatT& st) noexcept { return static_cast(st.st_mode) & perms::mask; } @@ -364,8 +370,7 @@ } file_status create_file_status(error_code& m_ec, path const& p, - const struct ::stat& path_stat, - error_code* ec) { + const StatT& path_stat, error_code* ec) { if (ec) *ec = m_ec; if (m_ec && (m_ec.value() == ENOENT || m_ec.value() == ENOTDIR)) { @@ -400,8 +405,7 @@ return fs_tmp; } -file_status posix_stat(path const& p, struct ::stat& path_stat, - error_code* ec) { +file_status posix_stat(path const& p, StatT& path_stat, error_code* ec) { error_code m_ec; if (::stat(p.c_str(), &path_stat) == -1) m_ec = detail::capture_errno(); @@ -409,12 +413,11 @@ } file_status posix_stat(path const& p, error_code* ec) { - struct ::stat path_stat; + StatT path_stat; return posix_stat(p, path_stat, ec); } -file_status posix_lstat(path const& p, struct ::stat& path_stat, - error_code* ec) { +file_status posix_lstat(path const& p, StatT& path_stat, error_code* ec) { error_code m_ec; if (::lstat(p.c_str(), &path_stat) == -1) m_ec = detail::capture_errno(); @@ -422,7 +425,7 @@ } file_status posix_lstat(path const& p, error_code* ec) { - struct ::stat path_stat; + StatT path_stat; return posix_lstat(p, path_stat, ec); } @@ -464,10 +467,32 @@ using detail::capture_errno; using detail::ErrorHandler; using detail::StatT; +using detail::TimeSpec; using parser::createView; using parser::PathParser; using parser::string_view_t; +const bool _FilesystemClock::is_steady; + +_FilesystemClock::time_point _FilesystemClock::now() noexcept { + typedef chrono::duration __secs; +#if defined(_LIBCPP_USE_CLOCK_GETTIME) && defined(CLOCK_REALTIME) + typedef chrono::duration __nsecs; + struct timespec tp; + if (0 != clock_gettime(CLOCK_REALTIME, &tp)) + __throw_system_error(errno, "clock_gettime(CLOCK_REALTIME) failed"); + return time_point(__secs(tp.tv_sec) + + chrono::duration_cast(__nsecs(tp.tv_nsec))); +#else + typedef chrono::duration __microsecs; + timeval tv; + gettimeofday(&tv, 0); + return time_point(__secs(tv.tv_sec) + __microsecs(tv.tv_usec)); +#endif // _LIBCPP_USE_CLOCK_GETTIME && CLOCK_REALTIME +} + +filesystem_error::~filesystem_error() {} + void filesystem_error::__create_what(int __num_paths) { const char* derived_what = system_error::what(); __storage_->__what_ = [&]() -> string { @@ -525,14 +550,14 @@ const bool sym_status2 = bool(options & copy_options::copy_symlinks); error_code m_ec1; - struct ::stat f_st = {}; + StatT f_st = {}; const file_status f = sym_status || sym_status2 ? detail::posix_lstat(from, f_st, &m_ec1) : detail::posix_stat(from, f_st, &m_ec1); if (m_ec1) return err.report(m_ec1); - struct ::stat t_st = {}; + StatT t_st = {}; const file_status t = sym_status ? detail::posix_lstat(to, t_st, &m_ec1) : detail::posix_stat(to, t_st, &m_ec1); @@ -916,7 +941,7 @@ ErrorHandler err("file_size", ec, &p); error_code m_ec; - struct ::stat st; + StatT st; file_status fst = detail::posix_stat(p, st, &m_ec); if (!exists(fst) || !is_regular_file(fst)) { errc error_kind = @@ -966,14 +991,14 @@ static file_time_type __extract_last_write_time(const path& p, const StatT& st, error_code* ec) { - using detail::FSTime; + using detail::fs_time; ErrorHandler err("last_write_time", ec, &p); auto ts = detail::extract_mtime(st); - if (!FSTime::is_representable(ts)) + if (!fs_time::is_representable(ts)) return err.report(errc::value_too_large); - return FSTime::convert_timespec(ts); + return fs_time::convert_from_timespec(ts); } file_time_type __last_write_time(const path& p, error_code *ec) @@ -992,30 +1017,27 @@ void __last_write_time(const path& p, file_time_type new_time, error_code *ec) { - using namespace chrono; - using namespace detail; - ErrorHandler err("last_write_time", ec, &p); error_code m_ec; - TimeStructArray tbuf; -#if !defined(_LIBCXX_USE_UTIMENSAT) + array tbuf; +#if !defined(_LIBCPP_USE_UTIMENSAT) // This implementation has a race condition between determining the // last access time and attempting to set it to the same value using // ::utimes - struct ::stat st; + StatT st; file_status fst = detail::posix_stat(p, st, &m_ec); - if (m_ec && !status_known(fst)) + if (m_ec) return err.report(m_ec); - SetTimeStructTo(tbuf[0], detail::extract_atime(st)); + tbuf[0] = detail::extract_atime(st); #else tbuf[0].tv_sec = 0; tbuf[0].tv_nsec = UTIME_OMIT; #endif - if (SetTimeStructTo(tbuf[1], new_time)) - return err.report(errc::invalid_argument); + if (detail::set_time_spec_to(tbuf[1], new_time)) + return err.report(errc::value_too_large); - SetFileTimes(p, tbuf, m_ec); + detail::set_file_times(p, tbuf, m_ec); if (m_ec) return err.report(m_ec); } @@ -1591,7 +1613,7 @@ __data_.__reset(); error_code failure_ec; - struct ::stat full_st; + StatT full_st; file_status st = detail::posix_lstat(__p_, full_st, &failure_ec); if (!status_known(st)) { __data_.__reset(); Index: src/include/apple_availability.h =================================================================== --- src/include/apple_availability.h +++ src/include/apple_availability.h @@ -0,0 +1,52 @@ +//===------------------------ apple_availability.h ------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +#ifndef _LIBCPP_SRC_INCLUDE_APPLE_AVAILABILITY_H +#define _LIBCPP_SRC_INCLUDE_APPLE_AVAILABILITY_H + +#if defined(__APPLE__) + +#if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) +#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101300 +#define _LIBCPP_USE_UTIMENSAT +#endif +#elif defined(__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__) +#if __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ >= 110000 +#define _LIBCPP_USE_UTIMENSAT +#endif +#elif defined(__ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__) +#if __ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__ >= 110000 +#define _LIBCPP_USE_UTIMENSAT +#endif +#elif defined(__ENVIRONMENT_WATCH_OS_VERSION_MIN_REQUIRED__) +#if __ENVIRONMENT_WATCH_OS_VERSION_MIN_REQUIRED__ >= 40000 +#define _LIBCPP_USE_UTIMENSAT +#endif +#endif // __ENVIRONMENT_.*_VERSION_MIN_REQUIRED__ + +#if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) +#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101200 +#define _LIBCPP_USE_CLOCK_GETTIME +#endif +#elif defined(__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__) +#if __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ >= 100000 +#define _LIBCPP_USE_CLOCK_GETTIME +#endif +#elif defined(__ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__) +#if __ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__ >= 100000 +#define _LIBCPP_USE_CLOCK_GETTIME +#endif +#elif defined(__ENVIRONMENT_WATCH_OS_VERSION_MIN_REQUIRED__) +#if __ENVIRONMENT_WATCH_OS_VERSION_MIN_REQUIRED__ >= 30000 +#define _LIBCPP_USE_CLOCK_GETTIME +#endif +#endif // __ENVIRONMENT_.*_VERSION_MIN_REQUIRED__ + +#endif // __APPLE__ + +#endif // _LIBCPP_SRC_INCLUDE_APPLE_AVAILABILITY_H Index: test/libcxx/experimental/filesystem/class.directory_entry/directory_entry.mods/last_write_time.sh.cpp =================================================================== --- test/libcxx/experimental/filesystem/class.directory_entry/directory_entry.mods/last_write_time.sh.cpp +++ test/libcxx/experimental/filesystem/class.directory_entry/directory_entry.mods/last_write_time.sh.cpp @@ -41,9 +41,7 @@ ToTime.tv_sec = std::numeric_limits::max(); ToTime.tv_nsec = duration_cast(seconds(1)).count() - 1; - TimeStructArray TS; - SetTimeStructTo(TS[0], ToTime); - SetTimeStructTo(TS[1], ToTime); + std::array TS = {ToTime, ToTime}; file_time_type old_time = last_write_time(file); directory_entry ent(file); @@ -57,9 +55,7 @@ file_time_type rep_value; { std::error_code ec; - if (SetFileTimes(file, TS, ec)) { - TEST_REQUIRE(false && "unsupported"); - } + TEST_REQUIRE(!set_file_times(file, TS, ec)); ec.clear(); rep_value = last_write_time(file, ec); IsRepresentable = !bool(ec); @@ -82,7 +78,7 @@ ExceptionChecker CheckExcept(file, expected_err, "directory_entry::last_write_time"); - TEST_CHECK_THROW_RESULT(fs::filesystem_error, CheckExcept, + TEST_CHECK_THROW_RESULT(filesystem_error, CheckExcept, ent.last_write_time()); } else { Index: test/libcxx/experimental/filesystem/convert_file_time.sh.cpp =================================================================== --- test/libcxx/experimental/filesystem/convert_file_time.sh.cpp +++ test/libcxx/experimental/filesystem/convert_file_time.sh.cpp @@ -25,15 +25,23 @@ #include "filesystem_common.h" +#ifndef __SIZEOF_INT128__ +#define TEST_HAS_NO_INT128_T +#endif + using namespace std::chrono; namespace fs = std::experimental::filesystem; using fs::file_time_type; -using fs::detail::time_util::fs_time_util; +using fs::detail::time_util; + +#ifdef TEST_HAS_NO_INT128_T +static_assert(sizeof(fs::file_time_type::rep) <= 8, ""); +#endif -enum TestKind { TK_64Bit, TK_32Bit, TK_FloatingPoint }; +enum TestKind { TK_128Bit, TK_64Bit, TK_32Bit, TK_FloatingPoint }; -template -constexpr TestKind getTestKind() { +template +constexpr TestKind getTimeTTestKind() { if (sizeof(TimeT) == 8 && !std::is_floating_point::value) return TK_64Bit; else if (sizeof(TimeT) == 4 && !std::is_floating_point::value) @@ -43,17 +51,99 @@ else assert(false && "test kind not supported"); } +template +constexpr TestKind getFileTimeTestKind() { + using Rep = typename FileTimeT::rep; + if (std::is_floating_point::value) + return TK_FloatingPoint; + if (sizeof(Rep) == 16) + return TK_128Bit; + if (sizeof(Rep) == 8) + return TK_64Bit; + assert(false && "test kind not supported"); +} template , - TestKind = getTestKind()> -struct check_is_representable; + class Base = time_util, + TestKind = getTimeTTestKind(), + TestKind = getFileTimeTestKind()> +struct test_case; template -struct check_is_representable +struct test_case : public Base { - using Base::convert_timespec; + using Base::convert_from_timespec; + using Base::convert_to_timespec; + using Base::is_representable; + using Base::max_nsec; + using Base::max_seconds; + using Base::min_nsec_timespec; + using Base::min_seconds; + + static constexpr auto max_time_t = std::numeric_limits::max(); + static constexpr auto min_time_t = std::numeric_limits::min(); + + static constexpr bool test_timespec() { + static_assert(is_representable(TimeSpecT{max_time_t, 0}), ""); + static_assert(is_representable(TimeSpecT{max_time_t, 999999999}), ""); + static_assert(is_representable(TimeSpecT{max_time_t, 1000000000}), ""); + static_assert(is_representable(TimeSpecT{max_time_t, max_nsec}), ""); + + static_assert(is_representable(TimeSpecT{min_time_t, 0}), ""); + static_assert(is_representable(TimeSpecT{min_time_t, 999999999}), ""); + static_assert(is_representable(TimeSpecT{min_time_t, 1000000000}), ""); + static_assert(is_representable(TimeSpecT{min_time_t, min_nsec_timespec}), + ""); + + return true; + } + + static constexpr bool test_file_time_type() { + // This kinda sucks. Oh well. + static_assert(!Base::is_representable(FileTimeT::max()), ""); + static_assert(!Base::is_representable(FileTimeT::min()), ""); + return true; + } + + static constexpr bool check_round_trip(TimeSpecT orig) { + TimeSpecT new_ts = {}; + FileTimeT out = convert_from_timespec(orig); + assert(convert_to_timespec(new_ts, out)); + return new_ts.tv_sec == orig.tv_sec && new_ts.tv_nsec == orig.tv_nsec; + } + + static constexpr bool test_convert_timespec() { + static_assert(check_round_trip({0, 0}), ""); + static_assert(check_round_trip({0, 1}), ""); + static_assert(check_round_trip({1, 1}), ""); + static_assert(check_round_trip({-1, 1}), ""); + static_assert(check_round_trip({max_time_t, max_nsec}), ""); + static_assert(check_round_trip({max_time_t, 123}), ""); + static_assert(check_round_trip({min_time_t, min_nsec_timespec}), ""); + static_assert(check_round_trip({min_time_t, 123}), ""); + return true; + } + + static bool test() { + static_assert(test_timespec(), ""); + static_assert(test_file_time_type(), ""); + static_assert(test_convert_timespec(), ""); + return true; + } +}; + +template +struct test_case + : public test_case { + +}; + +template +struct test_case + : public Base { + + using Base::convert_from_timespec; using Base::is_representable; using Base::max_nsec; using Base::max_seconds; @@ -88,24 +178,25 @@ } static constexpr bool test_convert_timespec() { - static_assert(convert_timespec(TimeSpecT{max_seconds, max_nsec}) == + static_assert(convert_from_timespec(TimeSpecT{max_seconds, max_nsec}) == FileTimeT::max(), ""); - static_assert(convert_timespec(TimeSpecT{max_seconds, max_nsec - 1}) < + static_assert(convert_from_timespec(TimeSpecT{max_seconds, max_nsec - 1}) < FileTimeT::max(), ""); - static_assert(convert_timespec(TimeSpecT{max_seconds - 1, 999999999}) < + static_assert(convert_from_timespec(TimeSpecT{max_seconds - 1, 999999999}) < FileTimeT::max(), ""); - static_assert(convert_timespec(TimeSpecT{ + static_assert(convert_from_timespec(TimeSpecT{ min_seconds - 1, min_nsec_timespec}) == FileTimeT::min(), ""); - static_assert( - convert_timespec(TimeSpecT{min_seconds - 1, min_nsec_timespec + 1}) > - FileTimeT::min(), - ""); - static_assert( - convert_timespec(TimeSpecT{min_seconds, 0}) > FileTimeT::min(), ""); + static_assert(convert_from_timespec( + TimeSpecT{min_seconds - 1, min_nsec_timespec + 1}) > + FileTimeT::min(), + ""); + static_assert(convert_from_timespec(TimeSpecT{min_seconds, 0}) > + FileTimeT::min(), + ""); return true; } @@ -118,12 +209,12 @@ }; template -struct check_is_representable +struct test_case : public Base { static constexpr auto max_time_t = std::numeric_limits::max(); static constexpr auto min_time_t = std::numeric_limits::min(); - using Base::convert_timespec; + using Base::convert_from_timespec; using Base::is_representable; using Base::max_nsec; using Base::max_seconds; @@ -158,9 +249,10 @@ } }; -template -struct check_is_representable : public Base { +template +struct test_case : public Base { static bool test() { return true; } }; @@ -182,19 +274,33 @@ static time_point now() noexcept { return {}; } }; -template > -using TestFileTimeT = time_point >; +template +using TestFileTimeT = time_point > >; int main() { - assert(( - check_is_representable::test())); - assert((check_is_representable, int64_t, - TestTimeSpec >::test())); - assert((check_is_representable, int32_t, - TestTimeSpec >::test())); - - // Test that insane platforms like ppc64 linux, which use long double as time_t, - // at least compile. - assert((check_is_representable, double, - TestTimeSpec >::test())); + { assert((test_case::test())); } + { + assert((test_case, int64_t, + TestTimeSpec >::test())); + } + { + assert((test_case, int32_t, + TestTimeSpec >::test())); + } + { + // Test that insane platforms like ppc64 linux, which use long double as time_t, + // at least compile. + assert((test_case, double, + TestTimeSpec >::test())); + } +#ifndef TEST_HAS_NO_INT128_T + { + assert((test_case, int64_t, + TestTimeSpec >::test())); + } + { + assert((test_case, int32_t, + TestTimeSpec >::test())); + } +#endif } Index: test/std/experimental/filesystem/fs.filesystem.synopsis/file_time_type.pass.cpp =================================================================== --- test/std/experimental/filesystem/fs.filesystem.synopsis/file_time_type.pass.cpp +++ test/std/experimental/filesystem/fs.filesystem.synopsis/file_time_type.pass.cpp @@ -17,15 +17,30 @@ #include #include +#include "test_macros.h" + // system_clock is used because it meets the requirements of TrivialClock, // and the resolution and range of system_clock should match the operating // system's file time type. -typedef std::chrono::system_clock ExpectedClock; -typedef std::chrono::time_point ExpectedTimePoint; + +void test_trivial_clock() { + using namespace fs; + using Clock = file_time_type::clock; + ASSERT_NOEXCEPT(Clock::now()); + ASSERT_SAME_TYPE(decltype(Clock::now()), file_time_type); + ASSERT_SAME_TYPE(Clock::time_point, file_time_type); + volatile auto* odr_use = &Clock::is_steady; + ((void)odr_use); +} + +void test_time_point_resolution_and_range() { + using namespace fs; + using Dur = file_time_type::duration; + using Period = Dur::period; + ASSERT_SAME_TYPE(Period, std::nano); +} int main() { - static_assert(std::is_same< - fs::file_time_type, - ExpectedTimePoint - >::value, ""); + test_trivial_clock(); + test_time_point_resolution_and_range(); } Index: test/std/experimental/filesystem/fs.op.funcs/fs.op.last_write_time/last_write_time.pass.cpp =================================================================== --- test/std/experimental/filesystem/fs.op.funcs/fs.op.last_write_time/last_write_time.pass.cpp +++ test/std/experimental/filesystem/fs.op.funcs/fs.op.last_write_time/last_write_time.pass.cpp @@ -30,13 +30,86 @@ #include #include +#include +#include + using namespace fs; -struct Times { std::time_t access, write; }; +using TimeSpec = struct ::timespec; +using StatT = struct ::stat; + +using Sec = std::chrono::duration; +using Hours = std::chrono::hours; +using Minutes = std::chrono::minutes; +using MicroSec = std::chrono::duration; +using NanoSec = std::chrono::duration; +using std::chrono::duration_cast; + +#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; } +TimeSpec extract_atime(StatT const& st) { return st.st_atim; } +#endif + +bool ConvertToTimeSpec(TimeSpec& ts, file_time_type ft) { + using SecFieldT = decltype(TimeSpec::tv_sec); + using NSecFieldT = decltype(TimeSpec::tv_nsec); + using SecLim = std::numeric_limits; + using NSecLim = std::numeric_limits; + + auto secs = duration_cast(ft.time_since_epoch()); + auto nsecs = duration_cast(ft.time_since_epoch() - secs); + if (nsecs.count() < 0) { + if (Sec::min().count() > SecLim::min()) { + secs += Sec(1); + nsecs -= Sec(1); + } else { + nsecs = NanoSec(0); + } + } + if (SecLim::max() < secs.count() || SecLim::min() > secs.count()) + return false; + if (NSecLim::max() < nsecs.count() || NSecLim::min() > nsecs.count()) + return false; + ts.tv_sec = secs.count(); + ts.tv_nsec = nsecs.count(); + return true; +} + +bool ConvertFromTimeSpec(file_time_type& ft, TimeSpec ts) { + auto secs_part = duration_cast(Sec(ts.tv_sec)); + if (duration_cast(secs_part).count() != ts.tv_sec) + return false; + auto subsecs = duration_cast(NanoSec(ts.tv_nsec)); + auto dur = secs_part + subsecs; + if (dur < secs_part && subsecs.count() >= 0) + return false; + ft = file_time_type(dur); + return true; +} + +bool CompareTimeExact(TimeSpec ts, TimeSpec ts2) { + return ts2.tv_sec == ts.tv_sec && ts2.tv_nsec == ts.tv_nsec; +} +bool CompareTimeExact(file_time_type ft, TimeSpec ts) { + TimeSpec ts2 = {}; + if (!ConvertToTimeSpec(ts2, ft)) + return false; + return CompareTimeExact(ts, ts2); +} +bool CompareTimeExact(TimeSpec ts, file_time_type ft) { + return CompareTimeExact(ft, ts); +} + +struct Times { + TimeSpec access, write; +}; Times GetTimes(path const& p) { using Clock = file_time_type::clock; - struct ::stat st; + StatT st; if (::stat(p.c_str(), &st) == -1) { std::error_code ec(errno, std::generic_category()); #ifndef TEST_HAS_NO_EXCEPTIONS @@ -46,22 +119,18 @@ std::exit(EXIT_FAILURE); #endif } - return {st.st_atime, st.st_mtime}; + return {extract_atime(st), extract_mtime(st)}; } -std::time_t LastAccessTime(path const& p) { - return GetTimes(p).access; -} +TimeSpec LastAccessTime(path const& p) { return GetTimes(p).access; } -std::time_t LastWriteTime(path const& p) { - return GetTimes(p).write; -} +TimeSpec LastWriteTime(path const& p) { return GetTimes(p).write; } -std::pair GetSymlinkTimes(path const& p) { - using Clock = file_time_type::clock; - struct ::stat st; - if (::lstat(p.c_str(), &st) == -1) { - std::error_code ec(errno, std::generic_category()); +std::pair GetSymlinkTimes(path const& p) { + using Clock = file_time_type::clock; + StatT st; + if (::lstat(p.c_str(), &st) == -1) { + std::error_code ec(errno, std::generic_category()); #ifndef TEST_HAS_NO_EXCEPTIONS throw ec; #else @@ -69,24 +138,10 @@ std::exit(EXIT_FAILURE); #endif } - return {st.st_atime, st.st_mtime}; + return {extract_atime(st), extract_mtime(st)}; } namespace { -bool TestSupportsNegativeTimes() { - using namespace std::chrono; - std::error_code ec; - std::time_t old_write_time, new_write_time; - { // WARNING: Do not assert in this scope. - scoped_test_env env; - const path file = env.create_file("file", 42); - old_write_time = LastWriteTime(file); - file_time_type tp(seconds(-5)); - fs::last_write_time(file, tp, ec); - new_write_time = LastWriteTime(file); - } - return !ec && new_write_time <= -5; -} // In some configurations, the comparison is tautological and the test is valid. // We disable the warning so that we can actually test it regardless. Also, that @@ -98,61 +153,131 @@ #pragma clang diagnostic ignored "-Wtautological-constant-compare" #endif -bool TestSupportsMaxTime() { - using namespace std::chrono; - using Lim = std::numeric_limits; - auto max_sec = duration_cast(file_time_type::max().time_since_epoch()).count(); - if (max_sec > Lim::max()) return false; - std::error_code ec; - std::time_t old_write_time, new_write_time; - { // WARNING: Do not assert in this scope. - scoped_test_env env; - const path file = env.create_file("file", 42); - old_write_time = LastWriteTime(file); - file_time_type tp = file_time_type::max(); - fs::last_write_time(file, tp, ec); - new_write_time = LastWriteTime(file); - } - return !ec && new_write_time > max_sec - 1; -} - -bool TestSupportsMinTime() { - using namespace std::chrono; - using Lim = std::numeric_limits; - auto min_sec = duration_cast(file_time_type::min().time_since_epoch()).count(); - if (min_sec < Lim::min()) return false; - std::error_code ec; - std::time_t old_write_time, new_write_time; - { // WARNING: Do not assert in this scope. - scoped_test_env env; - const path file = env.create_file("file", 42); - old_write_time = LastWriteTime(file); - file_time_type tp = file_time_type::min(); - fs::last_write_time(file, tp, ec); - new_write_time = LastWriteTime(file); - } - return !ec && new_write_time < min_sec + 1; -} - -#if defined(__clang__) -#pragma clang diagnostic pop -#endif - -static const bool SupportsNegativeTimes = TestSupportsNegativeTimes(); -static const bool SupportsMaxTime = TestSupportsMaxTime(); -static const bool SupportsMinTime = TestSupportsMinTime(); +static const bool SupportsNegativeTimes = [] { + using namespace std::chrono; + std::error_code ec; + TimeSpec old_write_time, new_write_time; + { // WARNING: Do not assert in this scope. + scoped_test_env env; + const path file = env.create_file("file", 42); + old_write_time = LastWriteTime(file); + file_time_type tp(seconds(-5)); + fs::last_write_time(file, tp, ec); + new_write_time = LastWriteTime(file); + } + + return !ec && new_write_time.tv_sec < 0; +}(); + +static const bool SupportsMaxTime = [] { + using namespace std::chrono; + TimeSpec max_ts = {}; + if (!ConvertToTimeSpec(max_ts, file_time_type::max())) + return false; + + std::error_code ec; + TimeSpec old_write_time, new_write_time; + { // WARNING: Do not assert in this scope. + scoped_test_env env; + const path file = env.create_file("file", 42); + old_write_time = LastWriteTime(file); + file_time_type tp = file_time_type::max(); + fs::last_write_time(file, tp, ec); + new_write_time = LastWriteTime(file); + } + return !ec && new_write_time.tv_sec > max_ts.tv_sec - 1; +}(); + +static const bool SupportsMinTime = [] { + using namespace std::chrono; + TimeSpec min_ts = {}; + if (!ConvertToTimeSpec(min_ts, file_time_type::min())) + return false; + std::error_code ec; + TimeSpec old_write_time, new_write_time; + { // WARNING: Do not assert in this scope. + scoped_test_env env; + const path file = env.create_file("file", 42); + old_write_time = LastWriteTime(file); + file_time_type tp = file_time_type::min(); + fs::last_write_time(file, tp, ec); + new_write_time = LastWriteTime(file); + } + return !ec && new_write_time.tv_sec < min_ts.tv_sec + 1; +}(); + +static const bool SupportsNanosecondRoundTrip = [] { + NanoSec ns(3); + + // Test if the file_time_type period is less than that of nanoseconds. + auto ft_dur = duration_cast(ns); + if (duration_cast(ft_dur) != ns) + return false; + + // Test that the system call we use to set the times also supports nanosecond + // resolution. (utimes does not) + file_time_type ft(ft_dur); + { + scoped_test_env env; + const path p = env.create_file("file", 42); + last_write_time(p, ft); + return last_write_time(p) == ft; + } +}(); + +static const bool SupportsMinRoundTrip = [] { + TimeSpec ts = {}; + if (!ConvertToTimeSpec(ts, file_time_type::min())) + return false; + file_time_type min_val = {}; + if (!ConvertFromTimeSpec(min_val, ts)) + return false; + return min_val == file_time_type::min(); +}(); } // end namespace -// In some configurations, the comparison is tautological and the test is valid. -// We disable the warning so that we can actually test it regardless. Also, that -// diagnostic is pretty new, so also don't fail if old clang does not support it -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunknown-warning-option" -#pragma clang diagnostic ignored "-Wunknown-pragmas" -#pragma clang diagnostic ignored "-Wtautological-constant-compare" -#endif +static bool CompareTime(TimeSpec t1, TimeSpec t2) { + if (SupportsNanosecondRoundTrip) + return CompareTimeExact(t1, t2); + if (t1.tv_sec != t2.tv_sec) + return false; + + auto diff = std::abs(t1.tv_nsec - t2.tv_nsec); + + return diff < duration_cast(MicroSec(1)).count(); +} + +static bool CompareTime(file_time_type t1, TimeSpec t2) { + TimeSpec ts1 = {}; + if (!ConvertToTimeSpec(ts1, t1)) + return false; + return CompareTime(ts1, t2); +} + +static bool CompareTime(TimeSpec t1, file_time_type t2) { + return CompareTime(t2, t1); +} + +static bool CompareTime(file_time_type t1, file_time_type t2) { + auto min_secs = duration_cast(file_time_type::min().time_since_epoch()); + bool IsMin = + t1.time_since_epoch() < min_secs || t2.time_since_epoch() < min_secs; + + if (SupportsNanosecondRoundTrip && (!IsMin || SupportsMinRoundTrip)) + return t1 == t2; + if (IsMin) { + return duration_cast(t1.time_since_epoch()) == + duration_cast(t2.time_since_epoch()); + } + file_time_type::duration dur; + if (t1 > t2) + dur = t1 - t2; + else + dur = t2 - t1; + + return duration_cast(dur).count() < 1; +} // Check if a time point is representable on a given filesystem. Check that: // (A) 'tp' is representable as a time_t @@ -162,22 +287,33 @@ // (D) 'tp' is not 'file_time_type::min()' or the filesystem supports the min // value. inline bool TimeIsRepresentableByFilesystem(file_time_type tp) { - using namespace std::chrono; - using Lim = std::numeric_limits; - auto sec = duration_cast(tp.time_since_epoch()).count(); - auto microsec = duration_cast(tp.time_since_epoch()).count(); - if (sec < Lim::min() || sec > Lim::max()) return false; - else if (microsec < 0 && !SupportsNegativeTimes) return false; - else if (tp == file_time_type::max() && !SupportsMaxTime) return false; - else if (tp == file_time_type::min() && !SupportsMinTime) return false; - return true; + TimeSpec ts = {}; + if (!ConvertToTimeSpec(ts, tp)) + return false; + else if (tp.time_since_epoch().count() < 0 && !SupportsNegativeTimes) + return false; + else if (tp == file_time_type::max() && !SupportsMaxTime) + return false; + else if (tp == file_time_type::min() && !SupportsMinTime) + return false; + return true; } #if defined(__clang__) #pragma clang diagnostic pop #endif -TEST_SUITE(exists_test_suite) +// Create a sub-second duration using the smallest period the filesystem supports. +file_time_type::duration SubSec(long long val) { + using SubSecT = file_time_type::duration; + if (SupportsNanosecondRoundTrip) { + return duration_cast(NanoSec(val)); + } else { + return duration_cast(MicroSec(val)); + } +} + +TEST_SUITE(last_write_time_test_suite) TEST_CASE(signature_test) { @@ -202,21 +338,21 @@ file_time_type ret = last_write_time(StaticEnv::File); TEST_CHECK(ret != min); TEST_CHECK(ret < C::now()); - TEST_CHECK(C::to_time_t(ret) == LastWriteTime(StaticEnv::File)); + TEST_CHECK(CompareTime(ret, LastWriteTime(StaticEnv::File))); file_time_type ret2 = last_write_time(StaticEnv::SymlinkToFile); - TEST_CHECK(ret == ret2); - TEST_CHECK(C::to_time_t(ret2) == LastWriteTime(StaticEnv::SymlinkToFile)); + TEST_CHECK(CompareTime(ret, ret2)); + TEST_CHECK(CompareTime(ret2, LastWriteTime(StaticEnv::SymlinkToFile))); } { file_time_type ret = last_write_time(StaticEnv::Dir); TEST_CHECK(ret != min); TEST_CHECK(ret < C::now()); - TEST_CHECK(C::to_time_t(ret) == LastWriteTime(StaticEnv::Dir)); + TEST_CHECK(CompareTime(ret, LastWriteTime(StaticEnv::Dir))); file_time_type ret2 = last_write_time(StaticEnv::SymlinkToDir); - TEST_CHECK(ret == ret2); - TEST_CHECK(C::to_time_t(ret2) == LastWriteTime(StaticEnv::SymlinkToDir)); + TEST_CHECK(CompareTime(ret, ret2)); + TEST_CHECK(CompareTime(ret2, LastWriteTime(StaticEnv::SymlinkToDir))); } } @@ -230,15 +366,17 @@ const path dir = env.create_dir("dir"); const auto file_times = GetTimes(file); - const std::time_t file_write_time = file_times.write; + const TimeSpec file_write_time = file_times.write; const auto dir_times = GetTimes(dir); - const std::time_t dir_write_time = dir_times.write; + const TimeSpec dir_write_time = dir_times.write; file_time_type ftime = last_write_time(file); - TEST_CHECK(Clock::to_time_t(ftime) == file_write_time); + TEST_CHECK(Clock::to_time_t(ftime) == file_write_time.tv_sec); + TEST_CHECK(CompareTime(ftime, file_write_time)); file_time_type dtime = last_write_time(dir); - TEST_CHECK(Clock::to_time_t(dtime) == dir_write_time); + TEST_CHECK(Clock::to_time_t(dtime) == dir_write_time.tv_sec); + TEST_CHECK(CompareTime(dtime, dir_write_time)); SleepFor(Sec(2)); @@ -253,18 +391,15 @@ TEST_CHECK(ftime2 > ftime); TEST_CHECK(dtime2 > dtime); - TEST_CHECK(LastWriteTime(file) == Clock::to_time_t(ftime2)); - TEST_CHECK(LastWriteTime(dir) == Clock::to_time_t(dtime2)); + TEST_CHECK(CompareTime(LastWriteTime(file), ftime2)); + TEST_CHECK(CompareTime(LastWriteTime(dir), dtime2)); } TEST_CASE(set_last_write_time_dynamic_env_test) { using Clock = file_time_type::clock; - using Sec = std::chrono::seconds; - using Hours = std::chrono::hours; - using Minutes = std::chrono::minutes; - using MicroSec = std::chrono::microseconds; + using SubSecT = file_time_type::duration; scoped_test_env env; const path file = env.create_file("file", 42); @@ -272,15 +407,17 @@ const auto now = Clock::now(); const file_time_type epoch_time = now - now.time_since_epoch(); - const file_time_type future_time = now + Hours(3) + Sec(42) + MicroSec(17); - const file_time_type past_time = now - Minutes(3) - Sec(42) - MicroSec(17); - const file_time_type before_epoch_time = epoch_time - Minutes(3) - Sec(42) - MicroSec(17); + const file_time_type future_time = now + Hours(3) + Sec(42) + SubSec(17); + const file_time_type past_time = now - Minutes(3) - Sec(42) - SubSec(17); + const file_time_type before_epoch_time = + epoch_time - Minutes(3) - Sec(42) - SubSec(17); // FreeBSD has a bug in their utimes implementation where the time is not update // when the number of seconds is '-1'. #if defined(__FreeBSD__) - const file_time_type just_before_epoch_time = epoch_time - Sec(2) - MicroSec(17); + const file_time_type just_before_epoch_time = + epoch_time - Sec(2) - SubSec(17); #else - const file_time_type just_before_epoch_time = epoch_time - MicroSec(17); + const file_time_type just_before_epoch_time = epoch_time - SubSec(17); #endif struct TestCase { @@ -300,7 +437,8 @@ }; for (const auto& TC : cases) { const auto old_times = GetTimes(TC.p); - file_time_type old_time(Sec(old_times.write)); + file_time_type old_time; + TEST_REQUIRE(ConvertFromTimeSpec(old_time, old_times.write)); std::error_code ec = GetTestEC(); last_write_time(TC.p, TC.new_time, ec); @@ -310,14 +448,8 @@ if (TimeIsRepresentableByFilesystem(TC.new_time)) { TEST_CHECK(got_time != old_time); - if (TC.new_time < epoch_time) { - TEST_CHECK(got_time <= TC.new_time); - TEST_CHECK(got_time > TC.new_time - Sec(1)); - } else { - TEST_CHECK(got_time <= TC.new_time + Sec(1)); - TEST_CHECK(got_time >= TC.new_time - Sec(1)); - } - TEST_CHECK(LastAccessTime(TC.p) == old_times.access); + TEST_CHECK(CompareTime(got_time, TC.new_time)); + TEST_CHECK(CompareTime(LastAccessTime(TC.p), old_times.access)); } } } @@ -325,9 +457,6 @@ TEST_CASE(last_write_time_symlink_test) { using Clock = file_time_type::clock; - using Sec = std::chrono::seconds; - using Hours = std::chrono::hours; - using Minutes = std::chrono::minutes; scoped_test_env env; @@ -343,77 +472,75 @@ last_write_time(sym, new_time, ec); TEST_CHECK(!ec); - const std::time_t new_time_t = Clock::to_time_t(new_time); file_time_type got_time = last_write_time(sym); - std::time_t got_time_t = Clock::to_time_t(got_time); + TEST_CHECK(!CompareTime(got_time, old_times.write)); + TEST_CHECK(got_time == new_time); - TEST_CHECK(got_time_t != old_times.write); - TEST_CHECK(got_time_t == new_time_t); - TEST_CHECK(LastWriteTime(file) == new_time_t); - TEST_CHECK(LastAccessTime(sym) == old_times.access); - TEST_CHECK(GetSymlinkTimes(sym) == old_sym_times); + TEST_CHECK(CompareTime(LastWriteTime(file), new_time)); + TEST_CHECK(CompareTime(LastAccessTime(sym), old_times.access)); + std::pair sym_times = GetSymlinkTimes(sym); + TEST_CHECK(CompareTime(sym_times.first, old_sym_times.first)); + TEST_CHECK(CompareTime(sym_times.second, old_sym_times.second)); } TEST_CASE(test_write_min_time) { using Clock = file_time_type::clock; - using Sec = std::chrono::seconds; - using MicroSec = std::chrono::microseconds; - using Lim = std::numeric_limits; scoped_test_env env; const path p = env.create_file("file", 42); - - std::error_code ec = GetTestEC(); + const file_time_type old_time = last_write_time(p); file_time_type new_time = file_time_type::min(); + std::error_code ec = GetTestEC(); last_write_time(p, new_time, ec); file_time_type tt = last_write_time(p); if (TimeIsRepresentableByFilesystem(new_time)) { TEST_CHECK(!ec); - TEST_CHECK(tt >= new_time); - TEST_CHECK(tt < new_time + Sec(1)); - - ec = GetTestEC(); - last_write_time(p, Clock::now()); + TEST_CHECK(CompareTime(tt, new_time)); - new_time = file_time_type::min() + MicroSec(1); + last_write_time(p, old_time); + new_time = file_time_type::min() + SubSec(1); + ec = GetTestEC(); last_write_time(p, new_time, ec); tt = last_write_time(p); if (TimeIsRepresentableByFilesystem(new_time)) { TEST_CHECK(!ec); - TEST_CHECK(tt >= new_time); - TEST_CHECK(tt < new_time + Sec(1)); + TEST_CHECK(CompareTime(tt, new_time)); + } else { + TEST_CHECK(ErrorIs(ec, std::errc::value_too_large)); + TEST_CHECK(tt == old_time); } + } else { + TEST_CHECK(ErrorIs(ec, std::errc::value_too_large)); + TEST_CHECK(tt == old_time); } } +TEST_CASE(test_write_max_time) { + using Clock = file_time_type::clock; + using Sec = std::chrono::seconds; + using Hours = std::chrono::hours; + + scoped_test_env env; + const path p = env.create_file("file", 42); + const file_time_type old_time = last_write_time(p); + file_time_type new_time = file_time_type::max(); + + std::error_code ec = GetTestEC(); + last_write_time(p, new_time, ec); + file_time_type tt = last_write_time(p); - -TEST_CASE(test_write_min_max_time) -{ - using Clock = file_time_type::clock; - using Sec = std::chrono::seconds; - using Hours = std::chrono::hours; - using Lim = std::numeric_limits; - scoped_test_env env; - const path p = env.create_file("file", 42); - - std::error_code ec = GetTestEC(); - file_time_type new_time = file_time_type::max(); - - ec = GetTestEC(); - last_write_time(p, new_time, ec); - file_time_type tt = last_write_time(p); - - if (TimeIsRepresentableByFilesystem(new_time)) { - TEST_CHECK(!ec); - TEST_CHECK(tt > new_time - Sec(1)); - TEST_CHECK(tt <= new_time); - } + if (TimeIsRepresentableByFilesystem(new_time)) { + TEST_CHECK(!ec); + TEST_CHECK(CompareTime(tt, new_time)); + } else { + TEST_CHECK(ErrorIs(ec, std::errc::value_too_large)); + TEST_CHECK(tt == old_time); + } } TEST_CASE(test_value_on_failure) @@ -421,8 +548,7 @@ const path p = StaticEnv::DNE; std::error_code ec = GetTestEC(); TEST_CHECK(last_write_time(p, ec) == file_time_type::min()); - TEST_CHECK(ec); - TEST_CHECK(ec != GetTestEC()); + TEST_CHECK(ErrorIs(ec, std::errc::no_such_file_or_directory)); } TEST_CASE(test_exists_fails) @@ -434,10 +560,30 @@ std::error_code ec = GetTestEC(); TEST_CHECK(last_write_time(file, ec) == file_time_type::min()); - TEST_CHECK(ec); - TEST_CHECK(ec != GetTestEC()); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); - TEST_CHECK_THROW(filesystem_error, last_write_time(file)); + ExceptionChecker Checker(file, std::errc::permission_denied, + "last_write_time"); + TEST_CHECK_THROW_RESULT(filesystem_error, Checker, last_write_time(file)); +} + +TEST_CASE(my_test) { + scoped_test_env env; + const path p = env.create_file("file", 42); + using namespace std::chrono; + using TimeSpec = struct ::timespec; + TimeSpec ts[2]; + ts[0].tv_sec = 0; + ts[0].tv_nsec = UTIME_OMIT; + ts[1].tv_sec = -1; + ts[1].tv_nsec = + duration_cast(seconds(1) - nanoseconds(13)).count(); + if (::utimensat(AT_FDCWD, p.c_str(), ts, 0) == -1) { + TEST_CHECK(false); + } + TimeSpec new_ts = LastWriteTime(p); + TEST_CHECK(ts[1].tv_sec == new_ts.tv_sec); + TEST_CHECK(ts[1].tv_nsec == new_ts.tv_nsec); } TEST_SUITE_END()