diff options
Diffstat (limited to 'src/util')
-rw-r--r-- | src/util/asmap.cpp | 6 | ||||
-rw-r--r-- | src/util/check.h | 2 | ||||
-rw-r--r-- | src/util/epochguard.h | 91 | ||||
-rw-r--r-- | src/util/error.cpp | 4 | ||||
-rw-r--r-- | src/util/error.h | 2 | ||||
-rw-r--r-- | src/util/hash_type.h | 72 | ||||
-rw-r--r-- | src/util/memory.h | 20 | ||||
-rw-r--r-- | src/util/moneystr.cpp | 12 | ||||
-rw-r--r-- | src/util/moneystr.h | 2 | ||||
-rw-r--r-- | src/util/readwritefile.cpp | 47 | ||||
-rw-r--r-- | src/util/readwritefile.h | 28 | ||||
-rw-r--r-- | src/util/ref.h | 38 | ||||
-rw-r--r-- | src/util/sock.cpp | 200 | ||||
-rw-r--r-- | src/util/sock.h | 72 | ||||
-rw-r--r-- | src/util/strencodings.cpp | 32 | ||||
-rw-r--r-- | src/util/strencodings.h | 11 | ||||
-rw-r--r-- | src/util/string.h | 8 | ||||
-rw-r--r-- | src/util/system.cpp | 186 | ||||
-rw-r--r-- | src/util/system.h | 73 | ||||
-rw-r--r-- | src/util/thread.cpp | 27 | ||||
-rw-r--r-- | src/util/thread.h | 18 | ||||
-rw-r--r-- | src/util/time.cpp | 72 | ||||
-rw-r--r-- | src/util/time.h | 29 | ||||
-rw-r--r-- | src/util/tokenpipe.cpp | 109 | ||||
-rw-r--r-- | src/util/tokenpipe.h | 127 |
25 files changed, 1060 insertions, 228 deletions
diff --git a/src/util/asmap.cpp b/src/util/asmap.cpp index bd77d74218..bacc3690a2 100644 --- a/src/util/asmap.cpp +++ b/src/util/asmap.cpp @@ -93,8 +93,7 @@ uint32_t Interpret(const std::vector<bool> &asmap, const std::vector<bool> &ip) jump = DecodeJump(pos, endpos); if (jump == INVALID) break; // Jump offset straddles EOF if (bits == 0) break; // No input bits left - if (pos + jump < pos) break; // overflow - if (pos + jump >= endpos) break; // Jumping past EOF + if (int64_t{jump} >= int64_t{endpos - pos}) break; // Jumping past EOF if (ip[ip.size() - bits]) { pos += jump; } @@ -156,8 +155,7 @@ bool SanityCheckASMap(const std::vector<bool>& asmap, int bits) } else if (opcode == Instruction::JUMP) { uint32_t jump = DecodeJump(pos, endpos); if (jump == INVALID) return false; // Jump offset straddles EOF - if (pos + jump < pos) return false; // overflow - if (pos + jump > endpos) return false; // Jump out of range + if (int64_t{jump} > int64_t{endpos - pos}) return false; // Jump out of range if (bits == 0) return false; // Consuming bits past the end of the input --bits; uint32_t jump_offset = pos - begin + jump; diff --git a/src/util/check.h b/src/util/check.h index bc62da3440..e60088a2c6 100644 --- a/src/util/check.h +++ b/src/util/check.h @@ -69,7 +69,7 @@ T get_pure_r_value(T&& val) #ifdef ABORT_ON_FAILED_ASSUME #define Assume(val) Assert(val) #else -#define Assume(val) ((void)(val)) +#define Assume(val) ([&]() -> decltype(get_pure_r_value(val)) { auto&& check = (val); return std::forward<decltype(get_pure_r_value(val))>(check); }()) #endif #endif // BITCOIN_UTIL_CHECK_H diff --git a/src/util/epochguard.h b/src/util/epochguard.h new file mode 100644 index 0000000000..1570ec4eb4 --- /dev/null +++ b/src/util/epochguard.h @@ -0,0 +1,91 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2020 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_EPOCHGUARD_H +#define BITCOIN_UTIL_EPOCHGUARD_H + +#include <threadsafety.h> + +#include <cassert> + +/** Epoch: RAII-style guard for using epoch-based graph traversal algorithms. + * When walking ancestors or descendants, we generally want to avoid + * visiting the same transactions twice. Some traversal algorithms use + * std::set (or setEntries) to deduplicate the transaction we visit. + * However, use of std::set is algorithmically undesirable because it both + * adds an asymptotic factor of O(log n) to traversals cost and triggers O(n) + * more dynamic memory allocations. + * In many algorithms we can replace std::set with an internal mempool + * counter to track the time (or, "epoch") that we began a traversal, and + * check + update a per-transaction epoch for each transaction we look at to + * determine if that transaction has not yet been visited during the current + * traversal's epoch. + * Algorithms using std::set can be replaced on a one by one basis. + * Both techniques are not fundamentally incompatible across the codebase. + * Generally speaking, however, the remaining use of std::set for mempool + * traversal should be viewed as a TODO for replacement with an epoch based + * traversal, rather than a preference for std::set over epochs in that + * algorithm. + */ + +class LOCKABLE Epoch +{ +private: + uint64_t m_raw_epoch = 0; + bool m_guarded = false; + +public: + Epoch() = default; + Epoch(const Epoch&) = delete; + Epoch& operator=(const Epoch&) = delete; + + bool guarded() const { return m_guarded; } + + class Marker + { + private: + uint64_t m_marker = 0; + + // only allow modification via Epoch member functions + friend class Epoch; + Marker& operator=(const Marker&) = delete; + }; + + class SCOPED_LOCKABLE Guard + { + private: + Epoch& m_epoch; + + public: + explicit Guard(Epoch& epoch) EXCLUSIVE_LOCK_FUNCTION(epoch) : m_epoch(epoch) + { + assert(!m_epoch.m_guarded); + ++m_epoch.m_raw_epoch; + m_epoch.m_guarded = true; + } + ~Guard() UNLOCK_FUNCTION() + { + assert(m_epoch.m_guarded); + ++m_epoch.m_raw_epoch; // ensure clear separation between epochs + m_epoch.m_guarded = false; + } + }; + + bool visited(Marker& marker) const EXCLUSIVE_LOCKS_REQUIRED(*this) + { + assert(m_guarded); + if (marker.m_marker < m_raw_epoch) { + // marker is from a previous epoch, so this is its first visit + marker.m_marker = m_raw_epoch; + return false; + } else { + return true; + } + } +}; + +#define WITH_FRESH_EPOCH(epoch) const Epoch::Guard PASTE2(epoch_guard_, __COUNTER__)(epoch) + +#endif // BITCOIN_UTIL_EPOCHGUARD_H diff --git a/src/util/error.cpp b/src/util/error.cpp index 76fac4d391..48c81693f3 100644 --- a/src/util/error.cpp +++ b/src/util/error.cpp @@ -31,6 +31,10 @@ bilingual_str TransactionErrorString(const TransactionError err) return Untranslated("Specified sighash value does not match value stored in PSBT"); case TransactionError::MAX_FEE_EXCEEDED: return Untranslated("Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)"); + case TransactionError::EXTERNAL_SIGNER_NOT_FOUND: + return Untranslated("External signer not found"); + case TransactionError::EXTERNAL_SIGNER_FAILED: + return Untranslated("External signer failed to sign"); // no default case, so the compiler can warn about missing cases } assert(false); diff --git a/src/util/error.h b/src/util/error.h index 6633498d2b..4cc35eb1fd 100644 --- a/src/util/error.h +++ b/src/util/error.h @@ -30,6 +30,8 @@ enum class TransactionError { PSBT_MISMATCH, SIGHASH_MISMATCH, MAX_FEE_EXCEEDED, + EXTERNAL_SIGNER_NOT_FOUND, + EXTERNAL_SIGNER_FAILED, }; bilingual_str TransactionErrorString(const TransactionError error); diff --git a/src/util/hash_type.h b/src/util/hash_type.h new file mode 100644 index 0000000000..13b831cf19 --- /dev/null +++ b/src/util/hash_type.h @@ -0,0 +1,72 @@ +// Copyright (c) 2020-2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_HASH_TYPE_H +#define BITCOIN_UTIL_HASH_TYPE_H + +template <typename HashType> +class BaseHash +{ +protected: + HashType m_hash; + +public: + BaseHash() : m_hash() {} + explicit BaseHash(const HashType& in) : m_hash(in) {} + + unsigned char* begin() + { + return m_hash.begin(); + } + + const unsigned char* begin() const + { + return m_hash.begin(); + } + + unsigned char* end() + { + return m_hash.end(); + } + + const unsigned char* end() const + { + return m_hash.end(); + } + + operator std::vector<unsigned char>() const + { + return std::vector<unsigned char>{m_hash.begin(), m_hash.end()}; + } + + std::string ToString() const + { + return m_hash.ToString(); + } + + bool operator==(const BaseHash<HashType>& other) const noexcept + { + return m_hash == other.m_hash; + } + + bool operator!=(const BaseHash<HashType>& other) const noexcept + { + return !(m_hash == other.m_hash); + } + + bool operator<(const BaseHash<HashType>& other) const noexcept + { + return m_hash < other.m_hash; + } + + size_t size() const + { + return m_hash.size(); + } + + unsigned char* data() { return m_hash.data(); } + const unsigned char* data() const { return m_hash.data(); } +}; + +#endif // BITCOIN_UTIL_HASH_TYPE_H diff --git a/src/util/memory.h b/src/util/memory.h deleted file mode 100644 index f21b81bade..0000000000 --- a/src/util/memory.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2020 The Bitcoin Core developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#ifndef BITCOIN_UTIL_MEMORY_H -#define BITCOIN_UTIL_MEMORY_H - -#include <memory> -#include <utility> - -//! Substitute for C++14 std::make_unique. -//! DEPRECATED use std::make_unique in new code. -template <typename T, typename... Args> -std::unique_ptr<T> MakeUnique(Args&&... args) -{ - return std::make_unique<T>(std::forward<Args>(args)...); -} - -#endif diff --git a/src/util/moneystr.cpp b/src/util/moneystr.cpp index 1bc8d02eab..3f9ce7dce4 100644 --- a/src/util/moneystr.cpp +++ b/src/util/moneystr.cpp @@ -9,13 +9,17 @@ #include <util/strencodings.h> #include <util/string.h> -std::string FormatMoney(const CAmount& n) +std::string FormatMoney(const CAmount n) { // Note: not using straight sprintf here because we do NOT want // localized number formatting. - int64_t n_abs = (n > 0 ? n : -n); - int64_t quotient = n_abs/COIN; - int64_t remainder = n_abs%COIN; + static_assert(COIN > 1); + int64_t quotient = n / COIN; + int64_t remainder = n % COIN; + if (n < 0) { + quotient = -quotient; + remainder = -remainder; + } std::string str = strprintf("%d.%08d", quotient, remainder); // Right-trim excess zeros before the decimal point: diff --git a/src/util/moneystr.h b/src/util/moneystr.h index da7f673cda..2aedbee358 100644 --- a/src/util/moneystr.h +++ b/src/util/moneystr.h @@ -17,7 +17,7 @@ /* Do not use these functions to represent or parse monetary amounts to or from * JSON but use AmountFromValue and ValueFromAmount for that. */ -std::string FormatMoney(const CAmount& n); +std::string FormatMoney(const CAmount n); /** Parse an amount denoted in full coins. E.g. "0.0034" supplied on the command line. **/ [[nodiscard]] bool ParseMoney(const std::string& str, CAmount& nRet); diff --git a/src/util/readwritefile.cpp b/src/util/readwritefile.cpp new file mode 100644 index 0000000000..a45c41d367 --- /dev/null +++ b/src/util/readwritefile.cpp @@ -0,0 +1,47 @@ +// Copyright (c) 2015-2020 The Bitcoin Core developers +// Copyright (c) 2017 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <fs.h> + +#include <limits> +#include <stdio.h> +#include <string> +#include <utility> + +std::pair<bool,std::string> ReadBinaryFile(const fs::path &filename, size_t maxsize=std::numeric_limits<size_t>::max()) +{ + FILE *f = fsbridge::fopen(filename, "rb"); + if (f == nullptr) + return std::make_pair(false,""); + std::string retval; + char buffer[128]; + do { + const size_t n = fread(buffer, 1, sizeof(buffer), f); + // Check for reading errors so we don't return any data if we couldn't + // read the entire file (or up to maxsize) + if (ferror(f)) { + fclose(f); + return std::make_pair(false,""); + } + retval.append(buffer, buffer+n); + } while (!feof(f) && retval.size() <= maxsize); + fclose(f); + return std::make_pair(true,retval); +} + +bool WriteBinaryFile(const fs::path &filename, const std::string &data) +{ + FILE *f = fsbridge::fopen(filename, "wb"); + if (f == nullptr) + return false; + if (fwrite(data.data(), 1, data.size(), f) != data.size()) { + fclose(f); + return false; + } + if (fclose(f) != 0) { + return false; + } + return true; +} diff --git a/src/util/readwritefile.h b/src/util/readwritefile.h new file mode 100644 index 0000000000..1dab874b38 --- /dev/null +++ b/src/util/readwritefile.h @@ -0,0 +1,28 @@ +// Copyright (c) 2015-2020 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_READWRITEFILE_H +#define BITCOIN_UTIL_READWRITEFILE_H + +#include <fs.h> + +#include <limits> +#include <string> +#include <utility> + +/** Read full contents of a file and return them in a std::string. + * Returns a pair <status, string>. + * If an error occurred, status will be false, otherwise status will be true and the data will be returned in string. + * + * @param maxsize Puts a maximum size limit on the file that is read. If the file is larger than this, truncated data + * (with len > maxsize) will be returned. + */ +std::pair<bool,std::string> ReadBinaryFile(const fs::path &filename, size_t maxsize=std::numeric_limits<size_t>::max()); + +/** Write contents of std::string to a file. + * @return true on success. + */ +bool WriteBinaryFile(const fs::path &filename, const std::string &data); + +#endif /* BITCOIN_UTIL_READWRITEFILE_H */ diff --git a/src/util/ref.h b/src/util/ref.h deleted file mode 100644 index 9685ea9fec..0000000000 --- a/src/util/ref.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) 2020 The Bitcoin Core developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#ifndef BITCOIN_UTIL_REF_H -#define BITCOIN_UTIL_REF_H - -#include <util/check.h> - -#include <typeindex> - -namespace util { - -/** - * Type-safe dynamic reference. - * - * This implements a small subset of the functionality in C++17's std::any - * class, and can be dropped when the project updates to C++17 - * (https://github.com/bitcoin/bitcoin/issues/16684) - */ -class Ref -{ -public: - Ref() = default; - template<typename T> Ref(T& value) { Set(value); } - template<typename T> T& Get() const { CHECK_NONFATAL(Has<T>()); return *static_cast<T*>(m_value); } - template<typename T> void Set(T& value) { m_value = &value; m_type = std::type_index(typeid(T)); } - template<typename T> bool Has() const { return m_value && m_type == std::type_index(typeid(T)); } - void Clear() { m_value = nullptr; m_type = std::type_index(typeid(void)); } - -private: - void* m_value = nullptr; - std::type_index m_type = std::type_index(typeid(void)); -}; - -} // namespace util - -#endif // BITCOIN_UTIL_REF_H diff --git a/src/util/sock.cpp b/src/util/sock.cpp index 4c65b5b680..0bc9795db3 100644 --- a/src/util/sock.cpp +++ b/src/util/sock.cpp @@ -4,6 +4,7 @@ #include <compat.h> #include <logging.h> +#include <threadinterrupt.h> #include <tinyformat.h> #include <util/sock.h> #include <util/system.h> @@ -12,12 +13,18 @@ #include <codecvt> #include <cwchar> #include <locale> +#include <stdexcept> #include <string> #ifdef USE_POLL #include <poll.h> #endif +static inline bool IOErrorIsPermanent(int err) +{ + return err != WSAEAGAIN && err != WSAEINTR && err != WSAEWOULDBLOCK && err != WSAEINPROGRESS; +} + Sock::Sock() : m_socket(INVALID_SOCKET) {} Sock::Sock(SOCKET s) : m_socket(s) {} @@ -59,7 +66,17 @@ ssize_t Sock::Recv(void* buf, size_t len, int flags) const return recv(m_socket, static_cast<char*>(buf), len, flags); } -bool Sock::Wait(std::chrono::milliseconds timeout, Event requested) const +int Sock::Connect(const sockaddr* addr, socklen_t addr_len) const +{ + return connect(m_socket, addr, addr_len); +} + +int Sock::GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* opt_len) const +{ + return getsockopt(m_socket, level, opt_name, static_cast<char*>(opt_val), opt_len); +} + +bool Sock::Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred) const { #ifdef USE_POLL pollfd fd; @@ -72,7 +89,21 @@ bool Sock::Wait(std::chrono::milliseconds timeout, Event requested) const fd.events |= POLLOUT; } - return poll(&fd, 1, count_milliseconds(timeout)) != SOCKET_ERROR; + if (poll(&fd, 1, count_milliseconds(timeout)) == SOCKET_ERROR) { + return false; + } + + if (occurred != nullptr) { + *occurred = 0; + if (fd.revents & POLLIN) { + *occurred |= RECV; + } + if (fd.revents & POLLOUT) { + *occurred |= SEND; + } + } + + return true; #else if (!IsSelectableSocket(m_socket)) { return false; @@ -93,10 +124,173 @@ bool Sock::Wait(std::chrono::milliseconds timeout, Event requested) const timeval timeout_struct = MillisToTimeval(timeout); - return select(m_socket + 1, &fdset_recv, &fdset_send, nullptr, &timeout_struct) != SOCKET_ERROR; + if (select(m_socket + 1, &fdset_recv, &fdset_send, nullptr, &timeout_struct) == SOCKET_ERROR) { + return false; + } + + if (occurred != nullptr) { + *occurred = 0; + if (FD_ISSET(m_socket, &fdset_recv)) { + *occurred |= RECV; + } + if (FD_ISSET(m_socket, &fdset_send)) { + *occurred |= SEND; + } + } + + return true; #endif /* USE_POLL */ } +void Sock::SendComplete(const std::string& data, + std::chrono::milliseconds timeout, + CThreadInterrupt& interrupt) const +{ + const auto deadline = GetTime<std::chrono::milliseconds>() + timeout; + size_t sent{0}; + + for (;;) { + const ssize_t ret{Send(data.data() + sent, data.size() - sent, MSG_NOSIGNAL)}; + + if (ret > 0) { + sent += static_cast<size_t>(ret); + if (sent == data.size()) { + break; + } + } else { + const int err{WSAGetLastError()}; + if (IOErrorIsPermanent(err)) { + throw std::runtime_error(strprintf("send(): %s", NetworkErrorString(err))); + } + } + + const auto now = GetTime<std::chrono::milliseconds>(); + + if (now >= deadline) { + throw std::runtime_error(strprintf( + "Send timeout (sent only %u of %u bytes before that)", sent, data.size())); + } + + if (interrupt) { + throw std::runtime_error(strprintf( + "Send interrupted (sent only %u of %u bytes before that)", sent, data.size())); + } + + // Wait for a short while (or the socket to become ready for sending) before retrying + // if nothing was sent. + const auto wait_time = std::min(deadline - now, std::chrono::milliseconds{MAX_WAIT_FOR_IO}); + Wait(wait_time, SEND); + } +} + +std::string Sock::RecvUntilTerminator(uint8_t terminator, + std::chrono::milliseconds timeout, + CThreadInterrupt& interrupt, + size_t max_data) const +{ + const auto deadline = GetTime<std::chrono::milliseconds>() + timeout; + std::string data; + bool terminator_found{false}; + + // We must not consume any bytes past the terminator from the socket. + // One option is to read one byte at a time and check if we have read a terminator. + // However that is very slow. Instead, we peek at what is in the socket and only read + // as many bytes as possible without crossing the terminator. + // Reading 64 MiB of random data with 262526 terminator chars takes 37 seconds to read + // one byte at a time VS 0.71 seconds with the "peek" solution below. Reading one byte + // at a time is about 50 times slower. + + for (;;) { + if (data.size() >= max_data) { + throw std::runtime_error( + strprintf("Received too many bytes without a terminator (%u)", data.size())); + } + + char buf[512]; + + const ssize_t peek_ret{Recv(buf, std::min(sizeof(buf), max_data - data.size()), MSG_PEEK)}; + + switch (peek_ret) { + case -1: { + const int err{WSAGetLastError()}; + if (IOErrorIsPermanent(err)) { + throw std::runtime_error(strprintf("recv(): %s", NetworkErrorString(err))); + } + break; + } + case 0: + throw std::runtime_error("Connection unexpectedly closed by peer"); + default: + auto end = buf + peek_ret; + auto terminator_pos = std::find(buf, end, terminator); + terminator_found = terminator_pos != end; + + const size_t try_len{terminator_found ? terminator_pos - buf + 1 : + static_cast<size_t>(peek_ret)}; + + const ssize_t read_ret{Recv(buf, try_len, 0)}; + + if (read_ret < 0 || static_cast<size_t>(read_ret) != try_len) { + throw std::runtime_error( + strprintf("recv() returned %u bytes on attempt to read %u bytes but previous " + "peek claimed %u bytes are available", + read_ret, try_len, peek_ret)); + } + + // Don't include the terminator in the output. + const size_t append_len{terminator_found ? try_len - 1 : try_len}; + + data.append(buf, buf + append_len); + + if (terminator_found) { + return data; + } + } + + const auto now = GetTime<std::chrono::milliseconds>(); + + if (now >= deadline) { + throw std::runtime_error(strprintf( + "Receive timeout (received %u bytes without terminator before that)", data.size())); + } + + if (interrupt) { + throw std::runtime_error(strprintf( + "Receive interrupted (received %u bytes without terminator before that)", + data.size())); + } + + // Wait for a short while (or the socket to become ready for reading) before retrying. + const auto wait_time = std::min(deadline - now, std::chrono::milliseconds{MAX_WAIT_FOR_IO}); + Wait(wait_time, RECV); + } +} + +bool Sock::IsConnected(std::string& errmsg) const +{ + if (m_socket == INVALID_SOCKET) { + errmsg = "not connected"; + return false; + } + + char c; + switch (Recv(&c, sizeof(c), MSG_PEEK)) { + case -1: { + const int err = WSAGetLastError(); + if (IOErrorIsPermanent(err)) { + errmsg = NetworkErrorString(err); + return false; + } + return true; + } + case 0: + errmsg = "closed"; + return false; + default: + return true; + } +} + #ifdef WIN32 std::string NetworkErrorString(int err) { diff --git a/src/util/sock.h b/src/util/sock.h index 26fe60f18f..a4df7cd21b 100644 --- a/src/util/sock.h +++ b/src/util/sock.h @@ -6,11 +6,19 @@ #define BITCOIN_UTIL_SOCK_H #include <compat.h> +#include <threadinterrupt.h> +#include <util/time.h> #include <chrono> #include <string> /** + * Maximum time to wait for I/O readiness. + * It will take up until this time to break off in case of an interruption. + */ +static constexpr auto MAX_WAIT_FOR_IO = 1s; + +/** * RAII helper class that manages a socket. Mimics `std::unique_ptr`, but instead of a pointer it * contains a socket and closes it automatically when it goes out of scope. */ @@ -72,16 +80,29 @@ public: /** * send(2) wrapper. Equivalent to `send(this->Get(), data, len, flags);`. Code that uses this - * wrapper can be unit-tested if this method is overridden by a mock Sock implementation. + * wrapper can be unit tested if this method is overridden by a mock Sock implementation. */ virtual ssize_t Send(const void* data, size_t len, int flags) const; /** * recv(2) wrapper. Equivalent to `recv(this->Get(), buf, len, flags);`. Code that uses this - * wrapper can be unit-tested if this method is overridden by a mock Sock implementation. + * wrapper can be unit tested if this method is overridden by a mock Sock implementation. */ virtual ssize_t Recv(void* buf, size_t len, int flags) const; + /** + * connect(2) wrapper. Equivalent to `connect(this->Get(), addr, addrlen)`. Code that uses this + * wrapper can be unit tested if this method is overridden by a mock Sock implementation. + */ + virtual int Connect(const sockaddr* addr, socklen_t addr_len) const; + + /** + * getsockopt(2) wrapper. Equivalent to + * `getsockopt(this->Get(), level, opt_name, opt_val, opt_len)`. Code that uses this + * wrapper can be unit tested if this method is overridden by a mock Sock implementation. + */ + virtual int GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* opt_len) const; + using Event = uint8_t; /** @@ -98,11 +119,54 @@ public: * Wait for readiness for input (recv) or output (send). * @param[in] timeout Wait this much for at least one of the requested events to occur. * @param[in] requested Wait for those events, bitwise-or of `RECV` and `SEND`. + * @param[out] occurred If not nullptr and `true` is returned, then upon return this + * indicates which of the requested events occurred. A timeout is indicated by return + * value of `true` and `occurred` being set to 0. * @return true on success and false otherwise */ - virtual bool Wait(std::chrono::milliseconds timeout, Event requested) const; + virtual bool Wait(std::chrono::milliseconds timeout, + Event requested, + Event* occurred = nullptr) const; + + /* Higher level, convenience, methods. These may throw. */ + + /** + * Send the given data, retrying on transient errors. + * @param[in] data Data to send. + * @param[in] timeout Timeout for the entire operation. + * @param[in] interrupt If this is signaled then the operation is canceled. + * @throws std::runtime_error if the operation cannot be completed. In this case only some of + * the data will be written to the socket. + */ + virtual void SendComplete(const std::string& data, + std::chrono::milliseconds timeout, + CThreadInterrupt& interrupt) const; + + /** + * Read from socket until a terminator character is encountered. Will never consume bytes past + * the terminator from the socket. + * @param[in] terminator Character up to which to read from the socket. + * @param[in] timeout Timeout for the entire operation. + * @param[in] interrupt If this is signaled then the operation is canceled. + * @param[in] max_data The maximum amount of data (in bytes) to receive. If this many bytes + * are received and there is still no terminator, then this method will throw an exception. + * @return The data that has been read, without the terminating character. + * @throws std::runtime_error if the operation cannot be completed. In this case some bytes may + * have been consumed from the socket. + */ + virtual std::string RecvUntilTerminator(uint8_t terminator, + std::chrono::milliseconds timeout, + CThreadInterrupt& interrupt, + size_t max_data) const; + + /** + * Check if still connected. + * @param[out] errmsg The error string, if the socket has been disconnected. + * @return true if connected + */ + virtual bool IsConnected(std::string& errmsg) const; -private: +protected: /** * Contained socket. `INVALID_SOCKET` designates the object is empty. */ diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp index deabc05de4..f514613f0d 100644 --- a/src/util/strencodings.cpp +++ b/src/util/strencodings.cpp @@ -107,23 +107,25 @@ std::vector<unsigned char> ParseHex(const std::string& str) return ParseHex(str.c_str()); } -void SplitHostPort(std::string in, int &portOut, std::string &hostOut) { +void SplitHostPort(std::string in, uint16_t& portOut, std::string& hostOut) +{ size_t colon = in.find_last_of(':'); // if a : is found, and it either follows a [...], or no other : is in the string, treat it as port separator bool fHaveColon = colon != in.npos; - bool fBracketed = fHaveColon && (in[0]=='[' && in[colon-1]==']'); // if there is a colon, and in[0]=='[', colon is not 0, so in[colon-1] is safe - bool fMultiColon = fHaveColon && (in.find_last_of(':',colon-1) != in.npos); - if (fHaveColon && (colon==0 || fBracketed || !fMultiColon)) { - int32_t n; - if (ParseInt32(in.substr(colon + 1), &n) && n > 0 && n < 0x10000) { + bool fBracketed = fHaveColon && (in[0] == '[' && in[colon - 1] == ']'); // if there is a colon, and in[0]=='[', colon is not 0, so in[colon-1] is safe + bool fMultiColon = fHaveColon && (in.find_last_of(':', colon - 1) != in.npos); + if (fHaveColon && (colon == 0 || fBracketed || !fMultiColon)) { + uint16_t n; + if (ParseUInt16(in.substr(colon + 1), &n)) { in = in.substr(0, colon); portOut = n; } } - if (in.size()>0 && in[0] == '[' && in[in.size()-1] == ']') - hostOut = in.substr(1, in.size()-2); - else + if (in.size() > 0 && in[0] == '[' && in[in.size() - 1] == ']') { + hostOut = in.substr(1, in.size() - 2); + } else { hostOut = in; + } } std::string EncodeBase64(Span<const unsigned char> input) @@ -334,6 +336,18 @@ bool ParseUInt8(const std::string& str, uint8_t *out) return true; } +bool ParseUInt16(const std::string& str, uint16_t* out) +{ + uint32_t u32; + if (!ParseUInt32(str, &u32) || u32 > std::numeric_limits<uint16_t>::max()) { + return false; + } + if (out != nullptr) { + *out = static_cast<uint16_t>(u32); + } + return true; +} + bool ParseUInt32(const std::string& str, uint32_t *out) { if (!ParsePrechecks(str)) diff --git a/src/util/strencodings.h b/src/util/strencodings.h index b4a61202ef..26dc0a0ce3 100644 --- a/src/util/strencodings.h +++ b/src/util/strencodings.h @@ -17,8 +17,6 @@ #include <string> #include <vector> -#define ARRAYLEN(array) (sizeof(array)/sizeof((array)[0])) - /** Used by SanitizeString() */ enum SafeChars { @@ -67,7 +65,7 @@ std::string EncodeBase32(Span<const unsigned char> input, bool pad = true); */ std::string EncodeBase32(const std::string& str, bool pad = true); -void SplitHostPort(std::string in, int& portOut, std::string& hostOut); +void SplitHostPort(std::string in, uint16_t& portOut, std::string& hostOut); int64_t atoi64(const std::string& str); int atoi(const std::string& str); @@ -118,6 +116,13 @@ constexpr inline bool IsSpace(char c) noexcept { [[nodiscard]] bool ParseUInt8(const std::string& str, uint8_t *out); /** + * Convert decimal string to unsigned 16-bit integer with strict parse error feedback. + * @returns true if the entire string could be parsed as valid integer, + * false if the entire string could not be parsed or if overflow or underflow occurred. + */ +[[nodiscard]] bool ParseUInt16(const std::string& str, uint16_t* out); + +/** * Convert decimal string to unsigned 32-bit integer with strict parse error feedback. * @returns true if the entire string could be parsed as valid integer, * false if not the entire string could be parsed or when overflow or underflow occurred. diff --git a/src/util/string.h b/src/util/string.h index 5ffdc80d88..b26facc502 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -25,6 +25,14 @@ return str.substr(front, end - front + 1); } +[[nodiscard]] inline std::string RemovePrefix(const std::string& str, const std::string& prefix) +{ + if (str.substr(0, prefix.size()) == prefix) { + return str.substr(prefix.size()); + } + return str; +} + /** * Join a list of items * diff --git a/src/util/system.cpp b/src/util/system.cpp index 9a2e719bbc..9b3bd46b38 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -5,9 +5,9 @@ #include <util/system.h> -#ifdef HAVE_BOOST_PROCESS +#ifdef ENABLE_EXTERNAL_SIGNER #include <boost/process.hpp> -#endif // HAVE_BOOST_PROCESS +#endif // ENABLE_EXTERNAL_SIGNER #include <chainparamsbase.h> #include <sync.h> @@ -100,7 +100,7 @@ bool LockDirectory(const fs::path& directory, const std::string lockfile_name, b // Create empty lock file if it doesn't exist. FILE* file = fsbridge::fopen(pathLockFile, "a"); if (file) fclose(file); - auto lock = MakeUnique<fsbridge::FileLock>(pathLockFile); + auto lock = std::make_unique<fsbridge::FileLock>(pathLockFile); if (!lock->TryLock()) { return error("Error while attempting to lock directory %s: %s", directory.string(), lock->GetReason()); } @@ -235,6 +235,19 @@ static bool CheckValid(const std::string& key, const util::SettingsValue& val, u return true; } +namespace { +fs::path StripRedundantLastElementsOfPath(const fs::path& path) +{ + auto result = path; + while (result.filename().string() == ".") { + result = result.parent_path(); + } + + assert(fs::equivalent(result, path)); + return result; +} +} // namespace + // Define default constructor and destructor that are not inline, so code instantiating this class doesn't need to // #include class definitions for all members. // For example, m_settings has an internal dependency on univalue. @@ -315,7 +328,7 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin if (key[0] != '-') { if (!m_accept_any_command && m_command.empty()) { // The first non-dash arg is a registered command - Optional<unsigned int> flags = GetArgFlags(key); + std::optional<unsigned int> flags = GetArgFlags(key); if (!flags || !(*flags & ArgsManager::COMMAND)) { error = strprintf("Invalid command '%s'", argv[i]); return false; @@ -337,7 +350,7 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin key.erase(0, 1); std::string section; util::SettingsValue value = InterpretOption(section, key, val); - Optional<unsigned int> flags = GetArgFlags('-' + key); + std::optional<unsigned int> flags = GetArgFlags('-' + key); // Unknown command line options and command line options with dot // characters (which are returned from InterpretOption with nonempty @@ -363,7 +376,7 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin return success; } -Optional<unsigned int> ArgsManager::GetArgFlags(const std::string& name) const +std::optional<unsigned int> ArgsManager::GetArgFlags(const std::string& name) const { LOCK(cs_args); for (const auto& arg_map : m_available_args) { @@ -372,7 +385,73 @@ Optional<unsigned int> ArgsManager::GetArgFlags(const std::string& name) const return search->second.m_flags; } } - return nullopt; + return std::nullopt; +} + +const fs::path& ArgsManager::GetBlocksDirPath() +{ + LOCK(cs_args); + fs::path& path = m_cached_blocks_path; + + // Cache the path to avoid calling fs::create_directories on every call of + // this function + if (!path.empty()) return path; + + if (IsArgSet("-blocksdir")) { + path = fs::system_complete(GetArg("-blocksdir", "")); + if (!fs::is_directory(path)) { + path = ""; + return path; + } + } else { + path = GetDataDirPath(false); + } + + path /= BaseParams().DataDir(); + path /= "blocks"; + fs::create_directories(path); + path = StripRedundantLastElementsOfPath(path); + return path; +} + +const fs::path& ArgsManager::GetDataDirPath(bool net_specific) const +{ + LOCK(cs_args); + fs::path& path = net_specific ? m_cached_network_datadir_path : m_cached_datadir_path; + + // Cache the path to avoid calling fs::create_directories on every call of + // this function + if (!path.empty()) return path; + + std::string datadir = GetArg("-datadir", ""); + if (!datadir.empty()) { + path = fs::system_complete(datadir); + if (!fs::is_directory(path)) { + path = ""; + return path; + } + } else { + path = GetDefaultDataDir(); + } + if (net_specific) + path /= BaseParams().DataDir(); + + if (fs::create_directories(path)) { + // This is the first run, create wallets subdirectory too + fs::create_directories(path / "wallets"); + } + + path = StripRedundantLastElementsOfPath(path); + return path; +} + +void ArgsManager::ClearPathCache() +{ + LOCK(cs_args); + + m_cached_datadir_path = fs::path(); + m_cached_network_datadir_path = fs::path(); + m_cached_blocks_path = fs::path(); } std::optional<const ArgsManager::Command> ArgsManager::GetCommand() const @@ -434,7 +513,7 @@ bool ArgsManager::GetSettingsPath(fs::path* filepath, bool temp) const } if (filepath) { std::string settings = GetArg("-settings", BITCOIN_SETTINGS_FILENAME); - *filepath = fsbridge::AbsPathJoin(GetDataDir(/* net_specific= */ true), temp ? settings + ".tmp" : settings); + *filepath = fsbridge::AbsPathJoin(GetDataDirPath(/* net_specific= */ true), temp ? settings + ".tmp" : settings); } return true; } @@ -723,79 +802,9 @@ fs::path GetDefaultDataDir() #endif } -namespace { -fs::path StripRedundantLastElementsOfPath(const fs::path& path) -{ - auto result = path; - while (result.filename().string() == ".") { - result = result.parent_path(); - } - - assert(fs::equivalent(result, path)); - return result; -} -} // namespace - -static fs::path g_blocks_path_cache_net_specific; -static fs::path pathCached; -static fs::path pathCachedNetSpecific; -static RecursiveMutex csPathCached; - -const fs::path &GetBlocksDir() -{ - LOCK(csPathCached); - fs::path &path = g_blocks_path_cache_net_specific; - - // Cache the path to avoid calling fs::create_directories on every call of - // this function - if (!path.empty()) return path; - - if (gArgs.IsArgSet("-blocksdir")) { - path = fs::system_complete(gArgs.GetArg("-blocksdir", "")); - if (!fs::is_directory(path)) { - path = ""; - return path; - } - } else { - path = GetDataDir(false); - } - - path /= BaseParams().DataDir(); - path /= "blocks"; - fs::create_directories(path); - path = StripRedundantLastElementsOfPath(path); - return path; -} - const fs::path &GetDataDir(bool fNetSpecific) { - LOCK(csPathCached); - fs::path &path = fNetSpecific ? pathCachedNetSpecific : pathCached; - - // Cache the path to avoid calling fs::create_directories on every call of - // this function - if (!path.empty()) return path; - - std::string datadir = gArgs.GetArg("-datadir", ""); - if (!datadir.empty()) { - path = fs::system_complete(datadir); - if (!fs::is_directory(path)) { - path = ""; - return path; - } - } else { - path = GetDefaultDataDir(); - } - if (fNetSpecific) - path /= BaseParams().DataDir(); - - if (fs::create_directories(path)) { - // This is the first run, create wallets subdirectory too - fs::create_directories(path / "wallets"); - } - - path = StripRedundantLastElementsOfPath(path); - return path; + return gArgs.GetDataDirPath(fNetSpecific); } bool CheckDataDirOption() @@ -804,15 +813,6 @@ bool CheckDataDirOption() return datadir.empty() || fs::is_directory(fs::system_complete(datadir)); } -void ClearDatadirCache() -{ - LOCK(csPathCached); - - pathCached = fs::path(); - pathCachedNetSpecific = fs::path(); - g_blocks_path_cache_net_specific = fs::path(); -} - fs::path GetConfigFile(const std::string& confPath) { return AbsPathForConfigVal(fs::path(confPath), false); @@ -874,7 +874,7 @@ bool ArgsManager::ReadConfigStream(std::istream& stream, const std::string& file std::string section; std::string key = option.first; util::SettingsValue value = InterpretOption(section, key, option.second); - Optional<unsigned int> flags = GetArgFlags('-' + key); + std::optional<unsigned int> flags = GetArgFlags('-' + key); if (flags) { if (!CheckValid(key, value, *flags, error)) { return false; @@ -971,7 +971,7 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys) } // If datadir is changed in .conf file: - ClearDatadirCache(); + gArgs.ClearPathCache(); if (!CheckDataDirOption()) { error = strprintf("specified data directory \"%s\" does not exist.", GetArg("-datadir", "")); return false; @@ -1034,7 +1034,7 @@ void ArgsManager::logArgsPrefix( std::string section_str = section.empty() ? "" : "[" + section + "] "; for (const auto& arg : args) { for (const auto& value : arg.second) { - Optional<unsigned int> flags = GetArgFlags('-' + arg.first); + std::optional<unsigned int> flags = GetArgFlags('-' + arg.first); if (flags) { std::string value_str = (*flags & SENSITIVE) ? "****" : value.write(); LogPrintf("%s %s%s=%s\n", prefix, section_str, arg.first, value_str); @@ -1247,7 +1247,7 @@ void runCommand(const std::string& strCommand) } #endif -#ifdef HAVE_BOOST_PROCESS +#ifdef ENABLE_EXTERNAL_SIGNER UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in) { namespace bp = boost::process; @@ -1282,7 +1282,7 @@ UniValue RunCommandParseJSON(const std::string& str_command, const std::string& return result_json; } -#endif // HAVE_BOOST_PROCESS +#endif // ENABLE_EXTERNAL_SIGNER void SetupEnvironment() { diff --git a/src/util/system.h b/src/util/system.h index 5959bc4196..f68975ffa3 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -19,16 +19,15 @@ #include <compat/assumptions.h> #include <fs.h> #include <logging.h> -#include <optional.h> #include <sync.h> #include <tinyformat.h> -#include <util/memory.h> #include <util/settings.h> -#include <util/threadnames.h> #include <util/time.h> +#include <any> #include <exception> #include <map> +#include <optional> #include <set> #include <stdint.h> #include <string> @@ -91,13 +90,9 @@ void ReleaseDirectoryLocks(); bool TryCreateDirectories(const fs::path& p); fs::path GetDefaultDataDir(); -// The blocks directory is always net specific. -const fs::path &GetBlocksDir(); const fs::path &GetDataDir(bool fNetSpecific = true); // Return true if -datadir option points to a valid directory or is not specified. bool CheckDataDirOption(); -/** Tests only */ -void ClearDatadirCache(); fs::path GetConfigFile(const std::string& confPath); #ifdef WIN32 fs::path GetSpecialFolderPath(int nFolder, bool fCreate = true); @@ -108,7 +103,7 @@ std::string ShellEscape(const std::string& arg); #if HAVE_SYSTEM void runCommand(const std::string& strCommand); #endif -#ifdef HAVE_BOOST_PROCESS +#ifdef ENABLE_EXTERNAL_SIGNER /** * Execute a command which returns JSON, and parse the result. * @@ -117,7 +112,7 @@ void runCommand(const std::string& strCommand); * @return parsed JSON */ UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in=""); -#endif // HAVE_BOOST_PROCESS +#endif // ENABLE_EXTERNAL_SIGNER /** * Most paths passed as configuration arguments are treated as relative to @@ -200,6 +195,9 @@ protected: std::map<OptionsCategory, std::map<std::string, Arg>> m_available_args GUARDED_BY(cs_args); bool m_accept_any_command GUARDED_BY(cs_args){true}; std::list<SectionInfo> m_config_sections GUARDED_BY(cs_args); + fs::path m_cached_blocks_path GUARDED_BY(cs_args); + mutable fs::path m_cached_datadir_path GUARDED_BY(cs_args); + mutable fs::path m_cached_network_datadir_path GUARDED_BY(cs_args); [[nodiscard]] bool ReadConfigStream(std::istream& stream, const std::string& filepath, std::string& error, bool ignore_invalid_keys = false); @@ -264,6 +262,27 @@ public: std::optional<const Command> GetCommand() const; /** + * Get blocks directory path + * + * @return Blocks path which is network specific + */ + const fs::path& GetBlocksDirPath(); + + /** + * Get data directory path + * + * @param net_specific Append network identifier to the returned path + * @return Absolute path on success, otherwise an empty path when a non-directory path would be returned + * @post Returned directory path is created unless it is empty + */ + const fs::path& GetDataDirPath(bool net_specific = true) const; + + /** + * Clear cached directory paths + */ + void ClearPathCache(); + + /** * Return a vector of strings of the given argument * * @param strArg Argument to get (e.g. "-foo") @@ -376,7 +395,7 @@ public: * Return Flags for known arg. * Return nullopt for unknown arg. */ - Optional<unsigned int> GetArgFlags(const std::string& name) const; + std::optional<unsigned int> GetArgFlags(const std::string& name) const; /** * Read and update settings file with saved settings. This needs to be @@ -458,28 +477,6 @@ std::string HelpMessageOpt(const std::string& option, const std::string& message */ int GetNumCores(); -/** - * .. and a wrapper that just calls func once - */ -template <typename Callable> void TraceThread(const char* name, Callable func) -{ - util::ThreadRename(name); - try - { - LogPrintf("%s thread start\n", name); - func(); - LogPrintf("%s thread exit\n", name); - } - catch (const std::exception& e) { - PrintExceptionContinue(&e, name); - throw; - } - catch (...) { - PrintExceptionContinue(nullptr, name); - throw; - } -} - std::string CopyrightHolders(const std::string& strPrefix); /** @@ -501,6 +498,18 @@ inline void insert(std::set<TsetT>& dst, const Tsrc& src) { dst.insert(src.begin(), src.end()); } +/** + * Helper function to access the contained object of a std::any instance. + * Returns a pointer to the object if passed instance has a value and the type + * matches, nullptr otherwise. + */ +template<typename T> +T* AnyPtr(const std::any& any) noexcept +{ + T* const* ptr = std::any_cast<T*>(&any); + return ptr ? *ptr : nullptr; +} + #ifdef WIN32 class WinCmdLineArgs { diff --git a/src/util/thread.cpp b/src/util/thread.cpp new file mode 100644 index 0000000000..14be668685 --- /dev/null +++ b/src/util/thread.cpp @@ -0,0 +1,27 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <util/thread.h> + +#include <logging.h> +#include <util/system.h> +#include <util/threadnames.h> + +#include <exception> + +void util::TraceThread(const char* thread_name, std::function<void()> thread_func) +{ + util::ThreadRename(thread_name); + try { + LogPrintf("%s thread start\n", thread_name); + thread_func(); + LogPrintf("%s thread exit\n", thread_name); + } catch (const std::exception& e) { + PrintExceptionContinue(&e, thread_name); + throw; + } catch (...) { + PrintExceptionContinue(nullptr, thread_name); + throw; + } +} diff --git a/src/util/thread.h b/src/util/thread.h new file mode 100644 index 0000000000..ca2eccc0c3 --- /dev/null +++ b/src/util/thread.h @@ -0,0 +1,18 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_THREAD_H +#define BITCOIN_UTIL_THREAD_H + +#include <functional> + +namespace util { +/** + * A wrapper for do-something-once thread functions. + */ +void TraceThread(const char* thread_name, std::function<void()> thread_func); + +} // namespace util + +#endif // BITCOIN_UTIL_THREAD_H diff --git a/src/util/time.cpp b/src/util/time.cpp index 295806c54a..e6f0986a39 100644 --- a/src/util/time.cpp +++ b/src/util/time.cpp @@ -33,6 +33,49 @@ int64_t GetTime() return now; } +bool ChronoSanityCheck() +{ + // std::chrono::system_clock.time_since_epoch and time_t(0) are not guaranteed + // to use the Unix epoch timestamp, prior to C++20, 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 time_t_epoch{}; + auto clock = std::chrono::system_clock::from_time_t(time_t_epoch); + if (std::chrono::duration_cast<std::chrono::seconds>(clock.time_since_epoch()).count() != 0) { + return false; + } + + time_t time_val = std::chrono::system_clock::to_time_t(clock); + if (time_val != time_t_epoch) { + return false; + } + + // Check that the above zero time is actually equal to the known unix timestamp. + struct tm epoch; +#ifdef HAVE_GMTIME_R + if (gmtime_r(&time_val, &epoch) == nullptr) { +#else + if (gmtime_s(&epoch, &time_val) != 0) { +#endif + return false; + } + + 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 <typename T> T GetTime() { @@ -47,36 +90,43 @@ template std::chrono::seconds GetTime(); template std::chrono::milliseconds GetTime(); template std::chrono::microseconds GetTime(); +template <typename T> +static T GetSystemTime() +{ + const auto now = std::chrono::duration_cast<T>(std::chrono::system_clock::now().time_since_epoch()); + assert(now.count() > 0); + return now; +} + void SetMockTime(int64_t nMockTimeIn) { Assert(nMockTimeIn >= 0); nMockTime.store(nMockTimeIn, std::memory_order_relaxed); } -int64_t GetMockTime() +void SetMockTime(std::chrono::seconds mock_time_in) +{ + nMockTime.store(mock_time_in.count(), std::memory_order_relaxed); +} + +std::chrono::seconds GetMockTime() { - return nMockTime.load(std::memory_order_relaxed); + return std::chrono::seconds(nMockTime.load(std::memory_order_relaxed)); } 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 int64_t{GetSystemTime<std::chrono::milliseconds>().count()}; } 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 int64_t{GetSystemTime<std::chrono::microseconds>().count()}; } int64_t GetSystemTimeInSeconds() { - return GetTimeMicros()/1000000; + return int64_t{GetSystemTime<std::chrono::seconds>().count()}; } std::string FormatISO8601DateTime(int64_t nTime) { diff --git a/src/util/time.h b/src/util/time.h index 03b75b5be5..7ebcaaa339 100644 --- a/src/util/time.h +++ b/src/util/time.h @@ -26,9 +26,16 @@ void UninterruptibleSleep(const std::chrono::microseconds& n); * This helper is used to convert durations before passing them over an * interface that doesn't support std::chrono (e.g. RPC, debug log, or the GUI) */ -inline int64_t count_seconds(std::chrono::seconds t) { return t.count(); } -inline int64_t count_milliseconds(std::chrono::milliseconds t) { return t.count(); } -inline int64_t count_microseconds(std::chrono::microseconds t) { return t.count(); } +constexpr int64_t count_seconds(std::chrono::seconds t) { return t.count(); } +constexpr int64_t count_milliseconds(std::chrono::milliseconds t) { return t.count(); } +constexpr int64_t count_microseconds(std::chrono::microseconds t) { return t.count(); } + +using SecondsDouble = std::chrono::duration<double, std::chrono::seconds::period>; + +/** + * Helper to count the seconds in any std::chrono::duration type + */ +inline double CountSecondsDouble(SecondsDouble t) { return t.count(); } /** * DEPRECATED @@ -43,10 +50,19 @@ int64_t GetTimeMicros(); /** Returns the system time (not mockable) */ int64_t GetSystemTimeInSeconds(); // Like GetTime(), but not mockable -/** For testing. Set e.g. with the setmocktime rpc, or -mocktime argument */ +/** + * DEPRECATED + * Use SetMockTime with chrono type + * + * @param[in] nMockTimeIn Time in seconds. + */ void SetMockTime(int64_t nMockTimeIn); + +/** For testing. Set e.g. with the setmocktime rpc, or -mocktime argument */ +void SetMockTime(std::chrono::seconds mock_time_in); + /** For testing */ -int64_t GetMockTime(); +std::chrono::seconds GetMockTime(); /** Return system time (or mocked time, if set) */ template <typename T> @@ -70,4 +86,7 @@ struct timeval MillisToTimeval(int64_t nTimeout); */ struct timeval MillisToTimeval(std::chrono::milliseconds ms); +/** Sanity check epoch match normal Unix epoch */ +bool ChronoSanityCheck(); + #endif // BITCOIN_UTIL_TIME_H diff --git a/src/util/tokenpipe.cpp b/src/util/tokenpipe.cpp new file mode 100644 index 0000000000..4c091cd2e6 --- /dev/null +++ b/src/util/tokenpipe.cpp @@ -0,0 +1,109 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <util/tokenpipe.h> + +#include <config/bitcoin-config.h> + +#ifndef WIN32 + +#include <errno.h> +#include <fcntl.h> +#include <optional> +#include <unistd.h> + +TokenPipeEnd TokenPipe::TakeReadEnd() +{ + TokenPipeEnd res(m_fds[0]); + m_fds[0] = -1; + return res; +} + +TokenPipeEnd TokenPipe::TakeWriteEnd() +{ + TokenPipeEnd res(m_fds[1]); + m_fds[1] = -1; + return res; +} + +TokenPipeEnd::TokenPipeEnd(int fd) : m_fd(fd) +{ +} + +TokenPipeEnd::~TokenPipeEnd() +{ + Close(); +} + +int TokenPipeEnd::TokenWrite(uint8_t token) +{ + while (true) { + ssize_t result = write(m_fd, &token, 1); + if (result < 0) { + // Failure. It's possible that the write was interrupted by a signal, + // in that case retry. + if (errno != EINTR) { + return TS_ERR; + } + } else if (result == 0) { + return TS_EOS; + } else { // ==1 + return 0; + } + } +} + +int TokenPipeEnd::TokenRead() +{ + uint8_t token; + while (true) { + ssize_t result = read(m_fd, &token, 1); + if (result < 0) { + // Failure. Check if the read was interrupted by a signal, + // in that case retry. + if (errno != EINTR) { + return TS_ERR; + } + } else if (result == 0) { + return TS_EOS; + } else { // ==1 + return token; + } + } + return token; +} + +void TokenPipeEnd::Close() +{ + if (m_fd != -1) close(m_fd); + m_fd = -1; +} + +std::optional<TokenPipe> TokenPipe::Make() +{ + int fds[2] = {-1, -1}; +#if HAVE_O_CLOEXEC && HAVE_DECL_PIPE2 + if (pipe2(fds, O_CLOEXEC) != 0) { + return std::nullopt; + } +#else + if (pipe(fds) != 0) { + return std::nullopt; + } +#endif + return TokenPipe(fds); +} + +TokenPipe::~TokenPipe() +{ + Close(); +} + +void TokenPipe::Close() +{ + if (m_fds[0] != -1) close(m_fds[0]); + if (m_fds[1] != -1) close(m_fds[1]); + m_fds[0] = m_fds[1] = -1; +} + +#endif // WIN32 diff --git a/src/util/tokenpipe.h b/src/util/tokenpipe.h new file mode 100644 index 0000000000..f56be93a38 --- /dev/null +++ b/src/util/tokenpipe.h @@ -0,0 +1,127 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_TOKENPIPE_H +#define BITCOIN_UTIL_TOKENPIPE_H + +#ifndef WIN32 + +#include <cstdint> +#include <optional> + +/** One end of a token pipe. */ +class TokenPipeEnd +{ +private: + int m_fd = -1; + +public: + TokenPipeEnd(int fd = -1); + ~TokenPipeEnd(); + + /** Return value constants for TokenWrite and TokenRead. */ + enum Status { + TS_ERR = -1, //!< I/O error + TS_EOS = -2, //!< Unexpected end of stream + }; + + /** Write token to endpoint. + * + * @returns 0 If successful. + * <0 if error: + * TS_ERR If an error happened. + * TS_EOS If end of stream happened. + */ + int TokenWrite(uint8_t token); + + /** Read token from endpoint. + * + * @returns >=0 Token value, if successful. + * <0 if error: + * TS_ERR If an error happened. + * TS_EOS If end of stream happened. + */ + int TokenRead(); + + /** Explicit close function. + */ + void Close(); + + /** Return whether endpoint is open. + */ + bool IsOpen() { return m_fd != -1; } + + // Move-only class. + TokenPipeEnd(TokenPipeEnd&& other) + { + m_fd = other.m_fd; + other.m_fd = -1; + } + TokenPipeEnd& operator=(TokenPipeEnd&& other) + { + Close(); + m_fd = other.m_fd; + other.m_fd = -1; + return *this; + } + TokenPipeEnd(const TokenPipeEnd&) = delete; + TokenPipeEnd& operator=(const TokenPipeEnd&) = delete; +}; + +/** An interprocess or interthread pipe for sending tokens (one-byte values) + * over. + */ +class TokenPipe +{ +private: + int m_fds[2] = {-1, -1}; + + TokenPipe(int fds[2]) : m_fds{fds[0], fds[1]} {} + +public: + ~TokenPipe(); + + /** Create a new pipe. + * @returns The created TokenPipe, or an empty std::nullopt in case of error. + */ + static std::optional<TokenPipe> Make(); + + /** Take the read end of this pipe. This can only be called once, + * as the object will be moved out. + */ + TokenPipeEnd TakeReadEnd(); + + /** Take the write end of this pipe. This should only be called once, + * as the object will be moved out. + */ + TokenPipeEnd TakeWriteEnd(); + + /** Close and end of the pipe that hasn't been moved out. + */ + void Close(); + + // Move-only class. + TokenPipe(TokenPipe&& other) + { + for (int i = 0; i < 2; ++i) { + m_fds[i] = other.m_fds[i]; + other.m_fds[i] = -1; + } + } + TokenPipe& operator=(TokenPipe&& other) + { + Close(); + for (int i = 0; i < 2; ++i) { + m_fds[i] = other.m_fds[i]; + other.m_fds[i] = -1; + } + return *this; + } + TokenPipe(const TokenPipe&) = delete; + TokenPipe& operator=(const TokenPipe&) = delete; +}; + +#endif // WIN32 + +#endif // BITCOIN_UTIL_TOKENPIPE_H |