From 0512b02750e301324861007e78405ed4aea9fe51 Mon Sep 17 00:00:00 2001 From: Cory Fields Date: Mon, 16 Jan 2017 12:39:53 -0500 Subject: [PATCH 1/2] time: Use std::chrono for time rather than boost Unfortunately, there's still no standard way of printing the current time in a threadsafe way. Digging down into boost's approach, they simply use gmtime_r when possible, as guessed by availability macros. We now use the same approach, but use autotools to detect whether gmtime_r or gmtime_s can be used, or as a fallback, the racy gmtime. Note that MilliSleep was not replaced because it is an interruption point. That can be done once boost threads are all gone. --- configure.ac | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/utiltime.cpp | 55 ++++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 91 insertions(+), 17 deletions(-) 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/utiltime.cpp b/src/utiltime.cpp index 7c5ee772651..cbff457dd70 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,32 @@ 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; +} + +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 +59,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 +93,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(); } From 9bd32ce6aa50df95fd5aeaece92b8b908c582c73 Mon Sep 17 00:00:00 2001 From: Cory Fields Date: Mon, 16 Jan 2017 15:01:37 -0500 Subject: [PATCH 2/2] time: add runtime sanity check 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. Do a quick runtime check to verify that time_t(0) == std::chrono::system_clock's epoch time == unix epoch. --- src/init.cpp | 4 ++++ src/test/sanity_tests.cpp | 2 ++ src/utiltime.cpp | 32 ++++++++++++++++++++++++++++++++ src/utiltime.h | 1 + 4 files changed, 39 insertions(+) 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 cbff457dd70..2cb549be17b 100644 --- a/src/utiltime.cpp +++ b/src/utiltime.cpp @@ -40,6 +40,38 @@ static inline const tm gmtime_int(time_t time) 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() { 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();