diff --git a/configure.ac b/configure.ac index 4723c69d5dd..e1c4b073437 100644 --- a/configure.ac +++ b/configure.ac @@ -516,6 +516,59 @@ fi AC_CHECK_HEADERS([endian.h sys/endian.h byteswap.h stdio.h stdlib.h unistd.h strings.h sys/types.h sys/stat.h sys/select.h sys/prctl.h]) +dnl gmtime checks taken from libmicrohttpd +AC_CHECK_FUNCS_ONCE([gmtime_r]) +AC_CHECK_DECL([gmtime_s], + [ + AC_MSG_CHECKING([[whether gmtime_s is in C11 form]]) + AC_LINK_IFELSE( + [ AC_LANG_PROGRAM( + [[ +#define __STDC_WANT_LIB_EXT1__ 1 +#include +#ifdef __cplusplus +extern "C" +#endif + struct tm* gmtime_s(const time_t* time, struct tm* result); + ]], [[ + struct tm res; + time_t t; + gmtime_s (&t, &res); + ]]) + ], + [ + AC_DEFINE([HAVE_C11_GMTIME_S], [1], [Define to 1 if you have the `gmtime_s' function in C11 form.]) + AC_MSG_RESULT([[yes]]) + ], + [ + AC_MSG_RESULT([[no]]) + AC_MSG_CHECKING([[whether gmtime_s is in W32 form]]) + AC_LINK_IFELSE( + [ AC_LANG_PROGRAM( + [[ +#include +#ifdef __cplusplus +extern "C" +#endif +errno_t gmtime_s(struct tm* _tm, const time_t* time); + ]], [[ + struct tm res; + time_t t; + gmtime_s (&res, &t); + ]]) + ], + [ + AC_DEFINE([HAVE_W32_GMTIME_S], [1], [Define to 1 if you have the `gmtime_s' function in W32 form.]) + AC_MSG_RESULT([[yes]]) + ], + [AC_MSG_RESULT([[no]]) + ]) + ]) + ], [], + [[#define __STDC_WANT_LIB_EXT1__ 1 +#include ]] +) + AC_CHECK_DECLS([strnlen]) # Check for daemon(3), unrelated to --with-daemon (although used by it) diff --git a/src/init.cpp b/src/init.cpp index 9ac69b7d39c..7f9a61adf96 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -687,6 +687,10 @@ bool InitSanityCheck(void) if (!glibc_sanity_test() || !glibcxx_sanity_test()) return false; + if (!ChronoSanityCheck()) { + InitError("Clock epoch mismatch"); + return false; + } return true; } diff --git a/src/test/sanity_tests.cpp b/src/test/sanity_tests.cpp index 51f9e9f39fd..3ca262a33d7 100644 --- a/src/test/sanity_tests.cpp +++ b/src/test/sanity_tests.cpp @@ -4,6 +4,7 @@ #include "compat/sanity.h" #include "key.h" +#include "utiltime.h" #include "test/test_bitcoin.h" #include @@ -15,6 +16,7 @@ BOOST_AUTO_TEST_CASE(basic_sanity) BOOST_CHECK_MESSAGE(glibc_sanity_test() == true, "libc sanity test"); BOOST_CHECK_MESSAGE(glibcxx_sanity_test() == true, "stdlib sanity test"); BOOST_CHECK_MESSAGE(ECC_InitSanityCheck() == true, "openssl ECC test"); + BOOST_CHECK_MESSAGE(ChronoSanityCheck() == true, "chrono epoch test"); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/utiltime.cpp b/src/utiltime.cpp index 7c5ee772651..2cb549be17b 100644 --- a/src/utiltime.cpp +++ b/src/utiltime.cpp @@ -3,12 +3,20 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#define __STDC_WANT_LIB_EXT1__ 1 // for gmtime_s + #if defined(HAVE_CONFIG_H) #include "config/bitcoin-config.h" #endif #include "utiltime.h" +#include +#include +#include +#include +#include +#include #include #include @@ -16,13 +24,64 @@ using namespace std; static int64_t nMockTime = 0; //!< For unit testing +static inline const tm gmtime_int(time_t time) +{ + tm out = {}; +#if defined(HAVE_W32_GMTIME_S) + gmtime_s(&out, &time); +#elif defined(HAVE_C11_GMTIME_S) + gmtime_s(&time, &out); +#elif defined(HAVE_GMTIME_R) + gmtime_r(&time, &out); +#else + // Not thread-safe! + out = *gmtime(&time); +#endif + return out; +} + +bool ChronoSanityCheck() +{ + // std::chrono::system_clock.time_since_epoch and time_t(0) are not guaranteed + // to use the Unix epoch timestamp, but in practice they almost certainly will. + // Any differing behavior will be assumed to be an error, unless certain + // platforms prove to consistently deviate, at which point we'll cope with it + // by adding offsets. + + // Create a new clock from time_t(0) and make sure that it represents 0 + // seconds from the system_clock's time_since_epoch. Then convert that back + // to a time_t and verify that it's the same as before. + const time_t zeroTime{}; + auto clock = std::chrono::system_clock::from_time_t(zeroTime); + if (std::chrono::duration_cast(clock.time_since_epoch()).count() != 0) + return false; + + time_t nTime = std::chrono::system_clock::to_time_t(clock); + if (nTime != zeroTime) + return false; + + // Check that the above zero time is actually equal to the known unix timestamp. + tm epoch = gmtime_int(nTime); + if ((epoch.tm_sec != 0) || \ + (epoch.tm_min != 0) || \ + (epoch.tm_hour != 0) || \ + (epoch.tm_mday != 1) || \ + (epoch.tm_mon != 0) || \ + (epoch.tm_year != 70)) + return false; + return true; +} + +template +static inline int64_t GetCurrentTime() +{ + return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); +} + int64_t GetTime() { if (nMockTime) return nMockTime; - - time_t now = time(NULL); - assert(now > 0); - return now; + return GetCurrentTime(); } void SetMockTime(int64_t nMockTimeIn) @@ -32,18 +91,12 @@ void SetMockTime(int64_t nMockTimeIn) int64_t GetTimeMillis() { - int64_t now = (boost::posix_time::microsec_clock::universal_time() - - boost::posix_time::ptime(boost::gregorian::date(1970,1,1))).total_milliseconds(); - assert(now > 0); - return now; + return GetCurrentTime(); } int64_t GetTimeMicros() { - int64_t now = (boost::posix_time::microsec_clock::universal_time() - - boost::posix_time::ptime(boost::gregorian::date(1970,1,1))).total_microseconds(); - assert(now > 0); - return now; + return GetCurrentTime(); } /** Return a time useful for the debug log */ @@ -72,13 +125,13 @@ void MilliSleep(int64_t n) #endif } -std::string DateTimeStrFormat(const char* pszFormat, int64_t nTime) +std::string DateTimeStrFormat(const char* pszFormat, int64_t nSecs) { static std::locale classic(std::locale::classic()); - // std::locale takes ownership of the pointer - std::locale loc(classic, new boost::posix_time::time_facet(pszFormat)); + time_t nTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::time_point{std::chrono::seconds{nSecs}}); + const tm& now = gmtime_int(nTime); std::stringstream ss; - ss.imbue(loc); - ss << boost::posix_time::from_time_t(nTime); + ss.imbue(classic); + std::use_facet>(ss.getloc()).put(ss.rdbuf(), ss, ' ', &now, pszFormat, pszFormat + strlen(pszFormat)); return ss.str(); } diff --git a/src/utiltime.h b/src/utiltime.h index b2807267dbd..36f538af443 100644 --- a/src/utiltime.h +++ b/src/utiltime.h @@ -9,6 +9,7 @@ #include #include +bool ChronoSanityCheck(); int64_t GetTime(); int64_t GetTimeMillis(); int64_t GetTimeMicros();