diff options
Diffstat (limited to 'src/util')
39 files changed, 814 insertions, 414 deletions
diff --git a/src/util/asmap.cpp b/src/util/asmap.cpp index ceb8379c1c..33258d9962 100644 --- a/src/util/asmap.cpp +++ b/src/util/asmap.cpp @@ -8,10 +8,13 @@ #include <crypto/common.h> #include <fs.h> #include <logging.h> +#include <serialize.h> #include <streams.h> +#include <algorithm> #include <cassert> -#include <map> +#include <cstdio> +#include <utility> #include <vector> namespace { @@ -195,7 +198,7 @@ std::vector<bool> DecodeAsmap(fs::path path) { std::vector<bool> bits; FILE *filestr = fsbridge::fopen(path, "rb"); - CAutoFile file(filestr, SER_DISK, CLIENT_VERSION); + AutoFile file{filestr}; if (file.IsNull()) { LogPrintf("Failed to open asmap file from disk\n"); return bits; diff --git a/src/util/bip32.cpp b/src/util/bip32.cpp index 4c7e948368..39e43eeb31 100644 --- a/src/util/bip32.cpp +++ b/src/util/bip32.cpp @@ -2,12 +2,15 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <sstream> -#include <stdio.h> #include <tinyformat.h> #include <util/bip32.h> #include <util/strencodings.h> +#include <algorithm> +#include <cstdint> +#include <cstdio> +#include <sstream> + bool ParseHDKeypath(const std::string& keypath_str, std::vector<uint32_t>& keypath) { diff --git a/src/util/bip32.h b/src/util/bip32.h index 8f86f2aaa6..0872bc88de 100644 --- a/src/util/bip32.h +++ b/src/util/bip32.h @@ -5,7 +5,7 @@ #ifndef BITCOIN_UTIL_BIP32_H #define BITCOIN_UTIL_BIP32_H -#include <attributes.h> +#include <cstdint> #include <string> #include <vector> diff --git a/src/util/bytevectorhash.cpp b/src/util/bytevectorhash.cpp index f87d0e04b3..6d777613e6 100644 --- a/src/util/bytevectorhash.cpp +++ b/src/util/bytevectorhash.cpp @@ -6,10 +6,12 @@ #include <random.h> #include <util/bytevectorhash.h> -ByteVectorHash::ByteVectorHash() +#include <vector> + +ByteVectorHash::ByteVectorHash() : + m_k0(GetRand<uint64_t>()), + m_k1(GetRand<uint64_t>()) { - GetRandBytes(reinterpret_cast<unsigned char*>(&m_k0), sizeof(m_k0)); - GetRandBytes(reinterpret_cast<unsigned char*>(&m_k1), sizeof(m_k1)); } size_t ByteVectorHash::operator()(const std::vector<unsigned char>& input) const diff --git a/src/util/bytevectorhash.h b/src/util/bytevectorhash.h index b88c17460b..c2322b8daf 100644 --- a/src/util/bytevectorhash.h +++ b/src/util/bytevectorhash.h @@ -5,7 +5,8 @@ #ifndef BITCOIN_UTIL_BYTEVECTORHASH_H #define BITCOIN_UTIL_BYTEVECTORHASH_H -#include <stdint.h> +#include <cstdint> +#include <cstddef> #include <vector> /** diff --git a/src/util/check.h b/src/util/check.h index 4ee65c8d34..aca957925a 100644 --- a/src/util/check.h +++ b/src/util/check.h @@ -18,8 +18,23 @@ class NonFatalCheckError : public std::runtime_error using std::runtime_error::runtime_error; }; +#define format_internal_error(msg, file, line, func, report) \ + strprintf("Internal bug detected: \"%s\"\n%s:%d (%s)\nPlease report this issue here: %s\n", \ + msg, file, line, func, report) + +/** Helper for CHECK_NONFATAL() */ +template <typename T> +T&& inline_check_non_fatal(T&& val, const char* file, int line, const char* func, const char* assertion) +{ + if (!(val)) { + throw NonFatalCheckError( + format_internal_error(assertion, file, line, func, PACKAGE_BUGREPORT)); + } + return std::forward<T>(val); +} + /** - * Throw a NonFatalCheckError when the condition evaluates to false + * Identity function. Throw a NonFatalCheckError when the condition evaluates to false * * This should only be used * - where the condition is assumed to be true, not for error handling or validating user input @@ -29,18 +44,8 @@ class NonFatalCheckError : public std::runtime_error * asserts or recoverable logic errors. A NonFatalCheckError in RPC code is caught and passed as a string to the RPC * caller, which can then report the issue to the developers. */ -#define CHECK_NONFATAL(condition) \ - do { \ - if (!(condition)) { \ - throw NonFatalCheckError( \ - strprintf("Internal bug detected: '%s'\n" \ - "%s:%d (%s)\n" \ - "You may report this issue here: %s\n", \ - (#condition), \ - __FILE__, __LINE__, __func__, \ - PACKAGE_BUGREPORT)); \ - } \ - } while (false) +#define CHECK_NONFATAL(condition) \ + inline_check_non_fatal(condition, __FILE__, __LINE__, __func__, #condition) #if defined(NDEBUG) #error "Cannot compile without assertions!" @@ -80,4 +85,13 @@ T&& inline_assertion_check(T&& val, [[maybe_unused]] const char* file, [[maybe_u */ #define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val) +/** + * NONFATAL_UNREACHABLE() is a macro that is used to mark unreachable code. It throws a NonFatalCheckError. + * This is used to mark code that is not yet implemented or is not yet reachable. + */ +#define NONFATAL_UNREACHABLE() \ + throw NonFatalCheckError( \ + format_internal_error("Unreachable code reached (non-fatal)", \ + __FILE__, __LINE__, __func__, PACKAGE_BUGREPORT)) + #endif // BITCOIN_UTIL_CHECK_H diff --git a/src/util/error.cpp b/src/util/error.cpp index af8cbd0353..33a35a6d59 100644 --- a/src/util/error.cpp +++ b/src/util/error.cpp @@ -5,9 +5,11 @@ #include <util/error.h> #include <tinyformat.h> -#include <util/system.h> #include <util/translation.h> +#include <cassert> +#include <string> + bilingual_str TransactionErrorString(const TransactionError err) { switch (err) { @@ -35,6 +37,8 @@ bilingual_str TransactionErrorString(const TransactionError err) return Untranslated("External signer not found"); case TransactionError::EXTERNAL_SIGNER_FAILED: return Untranslated("External signer failed to sign"); + case TransactionError::INVALID_PACKAGE: + return Untranslated("Transaction rejected due to invalid package"); // 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 4cc35eb1fd..0429de651a 100644 --- a/src/util/error.h +++ b/src/util/error.h @@ -32,6 +32,7 @@ enum class TransactionError { MAX_FEE_EXCEEDED, EXTERNAL_SIGNER_NOT_FOUND, EXTERNAL_SIGNER_FAILED, + INVALID_PACKAGE, }; bilingual_str TransactionErrorString(const TransactionError error); diff --git a/src/util/getuniquepath.cpp b/src/util/getuniquepath.cpp index 6776e7785b..1d8e511c83 100644 --- a/src/util/getuniquepath.cpp +++ b/src/util/getuniquepath.cpp @@ -9,6 +9,6 @@ fs::path GetUniquePath(const fs::path& base) { FastRandomContext rnd; - fs::path tmpFile = base / HexStr(rnd.randbytes(8)); + fs::path tmpFile = base / fs::u8path(HexStr(rnd.randbytes(8))); return tmpFile; }
\ No newline at end of file diff --git a/src/util/hasher.cpp b/src/util/hasher.cpp index 5900daf050..a80f20c894 100644 --- a/src/util/hasher.cpp +++ b/src/util/hasher.cpp @@ -2,16 +2,16 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <crypto/siphash.h> #include <random.h> +#include <span.h> #include <util/hasher.h> -#include <limits> +SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand<uint64_t>()), k1(GetRand<uint64_t>()) {} -SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand(std::numeric_limits<uint64_t>::max())), k1(GetRand(std::numeric_limits<uint64_t>::max())) {} +SaltedOutpointHasher::SaltedOutpointHasher() : k0(GetRand<uint64_t>()), k1(GetRand<uint64_t>()) {} -SaltedOutpointHasher::SaltedOutpointHasher() : k0(GetRand(std::numeric_limits<uint64_t>::max())), k1(GetRand(std::numeric_limits<uint64_t>::max())) {} - -SaltedSipHasher::SaltedSipHasher() : m_k0(GetRand(std::numeric_limits<uint64_t>::max())), m_k1(GetRand(std::numeric_limits<uint64_t>::max())) {} +SaltedSipHasher::SaltedSipHasher() : m_k0(GetRand<uint64_t>()), m_k1(GetRand<uint64_t>()) {} size_t SaltedSipHasher::operator()(const Span<const unsigned char>& script) const { diff --git a/src/util/hasher.h b/src/util/hasher.h index 3d24a4d23c..426b8990e6 100644 --- a/src/util/hasher.h +++ b/src/util/hasher.h @@ -5,10 +5,16 @@ #ifndef BITCOIN_UTIL_HASHER_H #define BITCOIN_UTIL_HASHER_H +#include <crypto/common.h> #include <crypto/siphash.h> #include <primitives/transaction.h> #include <uint256.h> +#include <cstdint> +#include <cstring> + +template <typename C> class Span; + class SaltedTxidHasher { private: diff --git a/src/util/message.cpp b/src/util/message.cpp index 2c7f0406f0..028251a5a8 100644 --- a/src/util/message.cpp +++ b/src/util/message.cpp @@ -3,16 +3,20 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <hash.h> // For CHashWriter -#include <key.h> // For CKey -#include <key_io.h> // For DecodeDestination() -#include <pubkey.h> // For CPubKey -#include <script/standard.h> // For CTxDestination, IsValidDestination(), PKHash -#include <serialize.h> // For SER_GETHASH +#include <hash.h> +#include <key.h> +#include <key_io.h> +#include <pubkey.h> +#include <script/standard.h> +#include <serialize.h> +#include <uint256.h> #include <util/message.h> -#include <util/strencodings.h> // For DecodeBase64() +#include <util/strencodings.h> +#include <cassert> +#include <optional> #include <string> +#include <variant> #include <vector> /** @@ -35,14 +39,13 @@ MessageVerificationResult MessageVerify( return MessageVerificationResult::ERR_ADDRESS_NO_KEY; } - bool invalid = false; - std::vector<unsigned char> signature_bytes = DecodeBase64(signature.c_str(), &invalid); - if (invalid) { + auto signature_bytes = DecodeBase64(signature); + if (!signature_bytes) { return MessageVerificationResult::ERR_MALFORMED_SIGNATURE; } CPubKey pubkey; - if (!pubkey.RecoverCompact(MessageHash(message), signature_bytes)) { + if (!pubkey.RecoverCompact(MessageHash(message), *signature_bytes)) { return MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED; } @@ -71,7 +74,7 @@ bool MessageSign( uint256 MessageHash(const std::string& message) { - CHashWriter hasher(SER_GETHASH, 0); + HashWriter hasher{}; hasher << MESSAGE_MAGIC << message; return hasher.GetHash(); diff --git a/src/util/message.h b/src/util/message.h index b31c5f5761..1b7febe60a 100644 --- a/src/util/message.h +++ b/src/util/message.h @@ -6,11 +6,12 @@ #ifndef BITCOIN_UTIL_MESSAGE_H #define BITCOIN_UTIL_MESSAGE_H -#include <key.h> // For CKey #include <uint256.h> #include <string> +class CKey; + extern const std::string MESSAGE_MAGIC; /** The result of a signed message verification. diff --git a/src/util/moneystr.cpp b/src/util/moneystr.cpp index 2cd7a426f8..d9e6cef600 100644 --- a/src/util/moneystr.cpp +++ b/src/util/moneystr.cpp @@ -10,6 +10,7 @@ #include <util/strencodings.h> #include <util/string.h> +#include <cstdint> #include <optional> std::string FormatMoney(const CAmount n) @@ -40,7 +41,7 @@ std::string FormatMoney(const CAmount n) std::optional<CAmount> ParseMoney(const std::string& money_string) { - if (!ValidAsCString(money_string)) { + if (!ContainsNoNUL(money_string)) { return std::nullopt; } const std::string str = TrimString(money_string); diff --git a/src/util/moneystr.h b/src/util/moneystr.h index 8180604342..3d33bd7f99 100644 --- a/src/util/moneystr.h +++ b/src/util/moneystr.h @@ -9,7 +9,6 @@ #ifndef BITCOIN_UTIL_MONEYSTR_H #define BITCOIN_UTIL_MONEYSTR_H -#include <attributes.h> #include <consensus/amount.h> #include <optional> diff --git a/src/util/readwritefile.cpp b/src/util/readwritefile.cpp index 628e6a3980..3ec08119e7 100644 --- a/src/util/readwritefile.cpp +++ b/src/util/readwritefile.cpp @@ -5,8 +5,9 @@ #include <fs.h> +#include <algorithm> +#include <cstdio> #include <limits> -#include <stdio.h> #include <string> #include <utility> diff --git a/src/util/result.h b/src/util/result.h new file mode 100644 index 0000000000..2f586a4c9b --- /dev/null +++ b/src/util/result.h @@ -0,0 +1,49 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_RESULT_H +#define BITCOIN_UTIL_RESULT_H + +#include <util/translation.h> + +#include <variant> + +/* + * 'BResult' is a generic class useful for wrapping a return object + * (in case of success) or propagating the error cause. +*/ +template<class T> +class BResult { +private: + std::variant<bilingual_str, T> m_variant; + +public: + BResult() : m_variant{Untranslated("")} {} + BResult(T obj) : m_variant{std::move(obj)} {} + BResult(bilingual_str error) : m_variant{std::move(error)} {} + + /* Whether the function succeeded or not */ + bool HasRes() const { return std::holds_alternative<T>(m_variant); } + + /* In case of success, the result object */ + const T& GetObj() const { + assert(HasRes()); + return std::get<T>(m_variant); + } + T ReleaseObj() + { + assert(HasRes()); + return std::move(std::get<T>(m_variant)); + } + + /* In case of failure, the error cause */ + const bilingual_str& GetError() const { + assert(!HasRes()); + return std::get<bilingual_str>(m_variant); + } + + explicit operator bool() const { return HasRes(); } +}; + +#endif // BITCOIN_UTIL_RESULT_H diff --git a/src/util/serfloat.h b/src/util/serfloat.h index 4d912b0176..343ccb9d3a 100644 --- a/src/util/serfloat.h +++ b/src/util/serfloat.h @@ -5,7 +5,7 @@ #ifndef BITCOIN_UTIL_SERFLOAT_H #define BITCOIN_UTIL_SERFLOAT_H -#include <stdint.h> +#include <cstdint> /* Encode a double using the IEEE 754 binary64 format. All NaNs are encoded as x86/ARM's * positive quiet NaN with payload 0. */ diff --git a/src/util/settings.cpp b/src/util/settings.cpp index 26439b010b..924a9cfab2 100644 --- a/src/util/settings.cpp +++ b/src/util/settings.cpp @@ -127,6 +127,7 @@ SettingsValue GetSetting(const Settings& settings, const std::string& section, const std::string& name, bool ignore_default_section_config, + bool ignore_nonpersistent, bool get_chain_name) { SettingsValue result; @@ -162,6 +163,9 @@ SettingsValue GetSetting(const Settings& settings, return; } + // Ignore nonpersistent settings if requested. + if (ignore_nonpersistent && (source == Source::COMMAND_LINE || source == Source::FORCED)) return; + // Skip negated command line settings. if (skip_negated_command_line && span.last_negated()) return; diff --git a/src/util/settings.h b/src/util/settings.h index ed36349232..e97158dc09 100644 --- a/src/util/settings.h +++ b/src/util/settings.h @@ -20,7 +20,7 @@ namespace util { //! @note UniValue is used here for convenience and because it can be easily //! serialized in a readable format. But any other variant type that can //! be assigned strings, int64_t, and bool values and has get_str(), -//! get_int64(), get_bool(), isNum(), isBool(), isFalse(), isTrue() and +//! getInt<int64_t>(), get_bool(), isNum(), isBool(), isFalse(), isTrue() and //! isNull() methods can be substituted if there's a need to move away //! from UniValue. (An implementation with boost::variant was posted at //! https://github.com/bitcoin/bitcoin/pull/15934/files#r337691812) @@ -55,12 +55,18 @@ bool WriteSettings(const fs::path& path, //! @param ignore_default_section_config - ignore values in the default section //! of the config file (part before any //! [section] keywords) +//! @param ignore_nonpersistent - ignore non-persistent settings values (forced +//! settings values and values specified on the +//! command line). Only return settings in the +//! read-only config and read-write settings +//! files. //! @param get_chain_name - enable special backwards compatible behavior //! for GetChainName SettingsValue GetSetting(const Settings& settings, const std::string& section, const std::string& name, bool ignore_default_section_config, + bool ignore_nonpersistent, bool get_chain_name); //! Get combined setting value similar to GetSetting(), except if setting was diff --git a/src/util/sock.cpp b/src/util/sock.cpp index b5c1e28294..125dbc7f18 100644 --- a/src/util/sock.cpp +++ b/src/util/sock.cpp @@ -2,11 +2,12 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <compat.h> +#include <compat/compat.h> #include <logging.h> #include <threadinterrupt.h> #include <tinyformat.h> #include <util/sock.h> +#include <util/syserror.h> #include <util/system.h> #include <util/time.h> @@ -38,11 +39,11 @@ Sock::Sock(Sock&& other) other.m_socket = INVALID_SOCKET; } -Sock::~Sock() { Reset(); } +Sock::~Sock() { Close(); } Sock& Sock::operator=(Sock&& other) { - Reset(); + Close(); m_socket = other.m_socket; other.m_socket = INVALID_SOCKET; return *this; @@ -50,15 +51,6 @@ Sock& Sock::operator=(Sock&& other) SOCKET Sock::Get() const { return m_socket; } -SOCKET Sock::Release() -{ - const SOCKET s = m_socket; - m_socket = INVALID_SOCKET; - return s; -} - -void Sock::Reset() { CloseSocket(m_socket); } - ssize_t Sock::Send(const void* data, size_t len, int flags) const { return send(m_socket, static_cast<const char*>(data), len, flags); @@ -74,6 +66,16 @@ int Sock::Connect(const sockaddr* addr, socklen_t addr_len) const return connect(m_socket, addr, addr_len); } +int Sock::Bind(const sockaddr* addr, socklen_t addr_len) const +{ + return bind(m_socket, addr, addr_len); +} + +int Sock::Listen(int backlog) const +{ + return listen(m_socket, backlog); +} + std::unique_ptr<Sock> Sock::Accept(sockaddr* addr, socklen_t* addr_len) const { #ifdef WIN32 @@ -110,65 +112,110 @@ int Sock::SetSockOpt(int level, int opt_name, const void* opt_val, socklen_t opt return setsockopt(m_socket, level, opt_name, static_cast<const char*>(opt_val), opt_len); } +int Sock::GetSockName(sockaddr* name, socklen_t* name_len) const +{ + return getsockname(m_socket, name, name_len); +} + bool Sock::Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred) const { -#ifdef USE_POLL - pollfd fd; - fd.fd = m_socket; - fd.events = 0; - if (requested & RECV) { - fd.events |= POLLIN; - } - if (requested & SEND) { - fd.events |= POLLOUT; - } + // We need a `shared_ptr` owning `this` for `WaitMany()`, but don't want + // `this` to be destroyed when the `shared_ptr` goes out of scope at the + // end of this function. Create it with a custom noop deleter. + std::shared_ptr<const Sock> shared{this, [](const Sock*) {}}; + + EventsPerSock events_per_sock{std::make_pair(shared, Events{requested})}; - if (poll(&fd, 1, count_milliseconds(timeout)) == SOCKET_ERROR) { + if (!WaitMany(timeout, events_per_sock)) { return false; } if (occurred != nullptr) { - *occurred = 0; - if (fd.revents & POLLIN) { - *occurred |= RECV; + *occurred = events_per_sock.begin()->second.occurred; + } + + return true; +} + +bool Sock::WaitMany(std::chrono::milliseconds timeout, EventsPerSock& events_per_sock) const +{ +#ifdef USE_POLL + std::vector<pollfd> pfds; + for (const auto& [sock, events] : events_per_sock) { + pfds.emplace_back(); + auto& pfd = pfds.back(); + pfd.fd = sock->m_socket; + if (events.requested & RECV) { + pfd.events |= POLLIN; } - if (fd.revents & POLLOUT) { - *occurred |= SEND; + if (events.requested & SEND) { + pfd.events |= POLLOUT; } } - return true; -#else - if (!IsSelectableSocket(m_socket)) { + if (poll(pfds.data(), pfds.size(), count_milliseconds(timeout)) == SOCKET_ERROR) { return false; } - fd_set fdset_recv; - fd_set fdset_send; - FD_ZERO(&fdset_recv); - FD_ZERO(&fdset_send); - - if (requested & RECV) { - FD_SET(m_socket, &fdset_recv); + assert(pfds.size() == events_per_sock.size()); + size_t i{0}; + for (auto& [sock, events] : events_per_sock) { + assert(sock->m_socket == static_cast<SOCKET>(pfds[i].fd)); + events.occurred = 0; + if (pfds[i].revents & POLLIN) { + events.occurred |= RECV; + } + if (pfds[i].revents & POLLOUT) { + events.occurred |= SEND; + } + if (pfds[i].revents & (POLLERR | POLLHUP)) { + events.occurred |= ERR; + } + ++i; } - if (requested & SEND) { - FD_SET(m_socket, &fdset_send); + return true; +#else + fd_set recv; + fd_set send; + fd_set err; + FD_ZERO(&recv); + FD_ZERO(&send); + FD_ZERO(&err); + SOCKET socket_max{0}; + + for (const auto& [sock, events] : events_per_sock) { + const auto& s = sock->m_socket; + if (!IsSelectableSocket(s)) { + return false; + } + if (events.requested & RECV) { + FD_SET(s, &recv); + } + if (events.requested & SEND) { + FD_SET(s, &send); + } + FD_SET(s, &err); + socket_max = std::max(socket_max, s); } - timeval timeout_struct = MillisToTimeval(timeout); + timeval tv = MillisToTimeval(timeout); - if (select(m_socket + 1, &fdset_recv, &fdset_send, nullptr, &timeout_struct) == SOCKET_ERROR) { + if (select(socket_max + 1, &recv, &send, &err, &tv) == SOCKET_ERROR) { return false; } - if (occurred != nullptr) { - *occurred = 0; - if (FD_ISSET(m_socket, &fdset_recv)) { - *occurred |= RECV; + for (auto& [sock, events] : events_per_sock) { + const auto& s = sock->m_socket; + events.occurred = 0; + if (FD_ISSET(s, &recv)) { + events.occurred |= RECV; + } + if (FD_ISSET(s, &send)) { + events.occurred |= SEND; } - if (FD_ISSET(m_socket, &fdset_send)) { - *occurred |= SEND; + if (FD_ISSET(s, &err)) { + events.occurred |= ERR; } } @@ -325,6 +372,22 @@ bool Sock::IsConnected(std::string& errmsg) const } } +void Sock::Close() +{ + if (m_socket == INVALID_SOCKET) { + return; + } +#ifdef WIN32 + int ret = closesocket(m_socket); +#else + int ret = close(m_socket); +#endif + if (ret) { + LogPrintf("Error closing socket %d: %s\n", m_socket, NetworkErrorString(WSAGetLastError())); + } + m_socket = INVALID_SOCKET; +} + #ifdef WIN32 std::string NetworkErrorString(int err) { @@ -344,34 +407,7 @@ std::string NetworkErrorString(int err) #else std::string NetworkErrorString(int err) { - char buf[256]; - buf[0] = 0; - /* Too bad there are two incompatible implementations of the - * thread-safe strerror. */ - const char *s; -#ifdef STRERROR_R_CHAR_P /* GNU variant can return a pointer outside the passed buffer */ - s = strerror_r(err, buf, sizeof(buf)); -#else /* POSIX variant always returns message in buffer */ - s = buf; - if (strerror_r(err, buf, sizeof(buf))) - buf[0] = 0; -#endif - return strprintf("%s (%d)", s, err); + // On BSD sockets implementations, NetworkErrorString is the same as SysErrorString. + return SysErrorString(err); } #endif - -bool CloseSocket(SOCKET& hSocket) -{ - if (hSocket == INVALID_SOCKET) - return false; -#ifdef WIN32 - int ret = closesocket(hSocket); -#else - int ret = close(hSocket); -#endif - if (ret) { - LogPrintf("Socket close failed: %d. Error: %s\n", hSocket, NetworkErrorString(WSAGetLastError())); - } - hSocket = INVALID_SOCKET; - return ret != SOCKET_ERROR; -} diff --git a/src/util/sock.h b/src/util/sock.h index dd2913a66c..38a7dc80d6 100644 --- a/src/util/sock.h +++ b/src/util/sock.h @@ -5,13 +5,14 @@ #ifndef BITCOIN_UTIL_SOCK_H #define BITCOIN_UTIL_SOCK_H -#include <compat.h> +#include <compat/compat.h> #include <threadinterrupt.h> #include <util/time.h> #include <chrono> #include <memory> #include <string> +#include <unordered_map> /** * Maximum time to wait for I/O readiness. @@ -68,18 +69,6 @@ public: [[nodiscard]] virtual SOCKET Get() const; /** - * Get the value of the contained socket and drop ownership. It will not be closed by the - * destructor after this call. - * @return socket or INVALID_SOCKET if empty - */ - virtual SOCKET Release(); - - /** - * Close if non-empty. - */ - virtual void Reset(); - - /** * 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. */ @@ -98,6 +87,18 @@ public: [[nodiscard]] virtual int Connect(const sockaddr* addr, socklen_t addr_len) const; /** + * bind(2) wrapper. Equivalent to `bind(this->Get(), addr, addr_len)`. Code that uses this + * wrapper can be unit tested if this method is overridden by a mock Sock implementation. + */ + [[nodiscard]] virtual int Bind(const sockaddr* addr, socklen_t addr_len) const; + + /** + * listen(2) wrapper. Equivalent to `listen(this->Get(), backlog)`. Code that uses this + * wrapper can be unit tested if this method is overridden by a mock Sock implementation. + */ + [[nodiscard]] virtual int Listen(int backlog) const; + + /** * accept(2) wrapper. Equivalent to `std::make_unique<Sock>(accept(this->Get(), addr, addr_len))`. * Code that uses this wrapper can be unit tested if this method is overridden by a mock Sock * implementation. @@ -125,31 +126,96 @@ public: const void* opt_val, socklen_t opt_len) const; + /** + * getsockname(2) wrapper. Equivalent to + * `getsockname(this->Get(), name, name_len)`. Code that uses this + * wrapper can be unit tested if this method is overridden by a mock Sock implementation. + */ + [[nodiscard]] virtual int GetSockName(sockaddr* name, socklen_t* name_len) const; + using Event = uint8_t; /** * If passed to `Wait()`, then it will wait for readiness to read from the socket. */ - static constexpr Event RECV = 0b01; + static constexpr Event RECV = 0b001; /** * If passed to `Wait()`, then it will wait for readiness to send to the socket. */ - static constexpr Event SEND = 0b10; + static constexpr Event SEND = 0b010; + + /** + * Ignored if passed to `Wait()`, but could be set in the occurred events if an + * exceptional condition has occurred on the socket or if it has been disconnected. + */ + static constexpr Event ERR = 0b100; /** * 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 + * @param[out] occurred If not nullptr and the function returns `true`, then this + * indicates which of the requested events occurred (`ERR` will be added, even if + * not requested, if an exceptional event occurs on the socket). + * A timeout is indicated by return value of `true` and `occurred` being set to 0. + * @return true on success (or timeout, if `occurred` of 0 is returned), false otherwise */ [[nodiscard]] virtual bool Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred = nullptr) const; + /** + * Auxiliary requested/occurred events to wait for in `WaitMany()`. + */ + struct Events { + explicit Events(Event req) : requested{req}, occurred{0} {} + Event requested; + Event occurred; + }; + + struct HashSharedPtrSock { + size_t operator()(const std::shared_ptr<const Sock>& s) const + { + return s ? s->m_socket : std::numeric_limits<SOCKET>::max(); + } + }; + + struct EqualSharedPtrSock { + bool operator()(const std::shared_ptr<const Sock>& lhs, + const std::shared_ptr<const Sock>& rhs) const + { + if (lhs && rhs) { + return lhs->m_socket == rhs->m_socket; + } + if (!lhs && !rhs) { + return true; + } + return false; + } + }; + + /** + * On which socket to wait for what events in `WaitMany()`. + * The `shared_ptr` is copied into the map to ensure that the `Sock` object + * is not destroyed (its destructor would close the underlying socket). + * If this happens shortly before or after we call `poll(2)` and a new + * socket gets created under the same file descriptor number then the report + * from `WaitMany()` will be bogus. + */ + using EventsPerSock = std::unordered_map<std::shared_ptr<const Sock>, Events, HashSharedPtrSock, EqualSharedPtrSock>; + + /** + * Same as `Wait()`, but wait on many sockets within the same timeout. + * @param[in] timeout Wait this long for at least one of the requested events to occur. + * @param[in,out] events_per_sock Wait for the requested events on these sockets and set + * `occurred` for the events that actually occurred. + * @return true on success (or timeout, if all `what[].occurred` are returned as 0), + * false otherwise + */ + [[nodiscard]] virtual bool WaitMany(std::chrono::milliseconds timeout, + EventsPerSock& events_per_sock) const; + /* Higher level, convenience, methods. These may throw. */ /** @@ -193,12 +259,15 @@ protected: * Contained socket. `INVALID_SOCKET` designates the object is empty. */ SOCKET m_socket; + +private: + /** + * Close `m_socket` if it is not `INVALID_SOCKET`. + */ + void Close(); }; /** Return readable error string for a network error code */ std::string NetworkErrorString(int err); -/** Close socket and set hSocket to INVALID_SOCKET */ -bool CloseSocket(SOCKET& hSocket); - #endif // BITCOIN_UTIL_SOCK_H diff --git a/src/util/spanparsing.cpp b/src/util/spanparsing.cpp index 50f6aee419..565c867e18 100644 --- a/src/util/spanparsing.cpp +++ b/src/util/spanparsing.cpp @@ -6,8 +6,9 @@ #include <span.h> +#include <algorithm> +#include <cstddef> #include <string> -#include <vector> namespace spanparsing { @@ -48,20 +49,4 @@ Span<const char> Expr(Span<const char>& sp) return ret; } -std::vector<Span<const char>> Split(const Span<const char>& sp, char sep) -{ - std::vector<Span<const char>> ret; - auto it = sp.begin(); - auto start = it; - while (it != sp.end()) { - if (*it == sep) { - ret.emplace_back(start, it); - start = it + 1; - } - ++it; - } - ret.emplace_back(start, it); - return ret; -} - } // namespace spanparsing diff --git a/src/util/spanparsing.h b/src/util/spanparsing.h index fa2e698e6d..51795271de 100644 --- a/src/util/spanparsing.h +++ b/src/util/spanparsing.h @@ -8,6 +8,7 @@ #include <span.h> #include <string> +#include <string_view> #include <vector> namespace spanparsing { @@ -36,6 +37,30 @@ bool Func(const std::string& str, Span<const char>& sp); */ Span<const char> Expr(Span<const char>& sp); +/** Split a string on any char found in separators, returning a vector. + * + * If sep does not occur in sp, a singleton with the entirety of sp is returned. + * + * Note that this function does not care about braces, so splitting + * "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}. + */ +template <typename T = Span<const char>> +std::vector<T> Split(const Span<const char>& sp, std::string_view separators) +{ + std::vector<T> ret; + auto it = sp.begin(); + auto start = it; + while (it != sp.end()) { + if (separators.find(*it) != std::string::npos) { + ret.emplace_back(start, it); + start = it + 1; + } + ++it; + } + ret.emplace_back(start, it); + return ret; +} + /** Split a string on every instance of sep, returning a vector. * * If sep does not occur in sp, a singleton with the entirety of sp is returned. @@ -43,7 +68,11 @@ Span<const char> Expr(Span<const char>& sp); * Note that this function does not care about braces, so splitting * "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}. */ -std::vector<Span<const char>> Split(const Span<const char>& sp, char sep); +template <typename T = Span<const char>> +std::vector<T> Split(const Span<const char>& sp, char sep) +{ + return Split<T>(sp, std::string_view{&sep, 1}); +} } // namespace spanparsing diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp index 940fa90da2..303e19beec 100644 --- a/src/util/strencodings.cpp +++ b/src/util/strencodings.cpp @@ -3,16 +3,18 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <span.h> #include <util/strencodings.h> -#include <util/string.h> - -#include <tinyformat.h> #include <algorithm> -#include <cstdlib> +#include <array> +#include <cassert> #include <cstring> #include <limits> #include <optional> +#include <ostream> +#include <string> +#include <vector> static const std::string CHARS_ALPHA_NUM = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; @@ -24,15 +26,15 @@ static const std::string SAFE_CHARS[] = CHARS_ALPHA_NUM + "!*'();:@&=+$,/?#[]-_.~%", // SAFE_CHARS_URI }; -std::string SanitizeString(const std::string& str, int rule) +std::string SanitizeString(std::string_view str, int rule) { - std::string strResult; - for (std::string::size_type i = 0; i < str.size(); i++) - { - if (SAFE_CHARS[rule].find(str[i]) != std::string::npos) - strResult.push_back(str[i]); + std::string result; + for (char c : str) { + if (SAFE_CHARS[rule].find(c) != std::string::npos) { + result.push_back(c); + } } - return strResult; + return result; } const signed char p_util_hexdigit[256] = @@ -58,56 +60,45 @@ signed char HexDigit(char c) return p_util_hexdigit[(unsigned char)c]; } -bool IsHex(const std::string& str) +bool IsHex(std::string_view str) { - for(std::string::const_iterator it(str.begin()); it != str.end(); ++it) - { - if (HexDigit(*it) < 0) - return false; + for (char c : str) { + if (HexDigit(c) < 0) return false; } return (str.size() > 0) && (str.size()%2 == 0); } -bool IsHexNumber(const std::string& str) +bool IsHexNumber(std::string_view str) { - size_t starting_location = 0; - if (str.size() > 2 && *str.begin() == '0' && *(str.begin()+1) == 'x') { - starting_location = 2; - } - for (const char c : str.substr(starting_location)) { + if (str.substr(0, 2) == "0x") str.remove_prefix(2); + for (char c : str) { if (HexDigit(c) < 0) return false; } // Return false for empty string or "0x". - return (str.size() > starting_location); + return str.size() > 0; } -std::vector<unsigned char> ParseHex(const char* psz) +template <typename Byte> +std::vector<Byte> ParseHex(std::string_view str) { - // convert hex dump to vector - std::vector<unsigned char> vch; - while (true) - { - while (IsSpace(*psz)) - psz++; - signed char c = HexDigit(*psz++); - if (c == (signed char)-1) - break; - auto n{uint8_t(c << 4)}; - c = HexDigit(*psz++); - if (c == (signed char)-1) - break; - n |= c; - vch.push_back(n); + std::vector<Byte> vch; + auto it = str.begin(); + while (it != str.end() && it + 1 != str.end()) { + if (IsSpace(*it)) { + ++it; + continue; + } + auto c1 = HexDigit(*(it++)); + auto c2 = HexDigit(*(it++)); + if (c1 < 0 || c2 < 0) break; + vch.push_back(Byte(c1 << 4) | Byte(c2)); } return vch; } +template std::vector<std::byte> ParseHex(std::string_view); +template std::vector<uint8_t> ParseHex(std::string_view); -std::vector<unsigned char> ParseHex(const std::string& str) -{ - return ParseHex(str.c_str()); -} - -void SplitHostPort(std::string in, uint16_t& portOut, std::string& hostOut) +void SplitHostPort(std::string_view 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 @@ -139,7 +130,7 @@ std::string EncodeBase64(Span<const unsigned char> input) return str; } -std::vector<unsigned char> DecodeBase64(const char* p, bool* pf_invalid) +std::optional<std::vector<unsigned char>> DecodeBase64(std::string_view str) { static const int8_t decode64_table[256]{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, @@ -157,46 +148,23 @@ std::vector<unsigned char> DecodeBase64(const char* p, bool* pf_invalid) -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; - const char* e = p; - std::vector<uint8_t> val; - val.reserve(strlen(p)); - while (*p != 0) { - int x = decode64_table[(unsigned char)*p]; - if (x == -1) break; - val.push_back(uint8_t(x)); - ++p; - } + if (str.size() % 4 != 0) return {}; + /* One or two = characters at the end are permitted. */ + if (str.size() >= 1 && str.back() == '=') str.remove_suffix(1); + if (str.size() >= 1 && str.back() == '=') str.remove_suffix(1); std::vector<unsigned char> ret; - ret.reserve((val.size() * 3) / 4); - bool valid = ConvertBits<6, 8, false>([&](unsigned char c) { ret.push_back(c); }, val.begin(), val.end()); - - const char* q = p; - while (valid && *p != 0) { - if (*p != '=') { - valid = false; - break; - } - ++p; - } - valid = valid && (p - e) % 4 == 0 && p - q < 4; - if (pf_invalid) *pf_invalid = !valid; + ret.reserve((str.size() * 3) / 4); + bool valid = ConvertBits<6, 8, false>( + [&](unsigned char c) { ret.push_back(c); }, + str.begin(), str.end(), + [](char c) { return decode64_table[uint8_t(c)]; } + ); + if (!valid) return {}; return ret; } -std::string DecodeBase64(const std::string& str, bool* pf_invalid) -{ - if (!ValidAsCString(str)) { - if (pf_invalid) { - *pf_invalid = true; - } - return {}; - } - std::vector<unsigned char> vchRet = DecodeBase64(str.c_str(), pf_invalid); - return std::string((const char*)vchRet.data(), vchRet.size()); -} - std::string EncodeBase32(Span<const unsigned char> input, bool pad) { static const char *pbase32 = "abcdefghijklmnopqrstuvwxyz234567"; @@ -212,12 +180,12 @@ std::string EncodeBase32(Span<const unsigned char> input, bool pad) return str; } -std::string EncodeBase32(const std::string& str, bool pad) +std::string EncodeBase32(std::string_view str, bool pad) { return EncodeBase32(MakeUCharSpan(str), pad); } -std::vector<unsigned char> DecodeBase32(const char* p, bool* pf_invalid) +std::optional<std::vector<unsigned char>> DecodeBase32(std::string_view str) { static const int8_t decode32_table[256]{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, @@ -235,49 +203,29 @@ std::vector<unsigned char> DecodeBase32(const char* p, bool* pf_invalid) -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; - const char* e = p; - std::vector<uint8_t> val; - val.reserve(strlen(p)); - while (*p != 0) { - int x = decode32_table[(unsigned char)*p]; - if (x == -1) break; - val.push_back(uint8_t(x)); - ++p; - } + if (str.size() % 8 != 0) return {}; + /* 1, 3, 4, or 6 padding '=' suffix characters are permitted. */ + if (str.size() >= 1 && str.back() == '=') str.remove_suffix(1); + if (str.size() >= 2 && str.substr(str.size() - 2) == "==") str.remove_suffix(2); + if (str.size() >= 1 && str.back() == '=') str.remove_suffix(1); + if (str.size() >= 2 && str.substr(str.size() - 2) == "==") str.remove_suffix(2); std::vector<unsigned char> ret; - ret.reserve((val.size() * 5) / 8); - bool valid = ConvertBits<5, 8, false>([&](unsigned char c) { ret.push_back(c); }, val.begin(), val.end()); - - const char* q = p; - while (valid && *p != 0) { - if (*p != '=') { - valid = false; - break; - } - ++p; - } - valid = valid && (p - e) % 8 == 0 && p - q < 8; - if (pf_invalid) *pf_invalid = !valid; + ret.reserve((str.size() * 5) / 8); + bool valid = ConvertBits<5, 8, false>( + [&](unsigned char c) { ret.push_back(c); }, + str.begin(), str.end(), + [](char c) { return decode32_table[uint8_t(c)]; } + ); - return ret; -} + if (!valid) return {}; -std::string DecodeBase32(const std::string& str, bool* pf_invalid) -{ - if (!ValidAsCString(str)) { - if (pf_invalid) { - *pf_invalid = true; - } - return {}; - } - std::vector<unsigned char> vchRet = DecodeBase32(str.c_str(), pf_invalid); - return std::string((const char*)vchRet.data(), vchRet.size()); + return ret; } namespace { template <typename T> -bool ParseIntegral(const std::string& str, T* out) +bool ParseIntegral(std::string_view str, T* out) { static_assert(std::is_integral<T>::value); // Replicate the exact behavior of strtol/strtoll/strtoul/strtoull when @@ -296,37 +244,37 @@ bool ParseIntegral(const std::string& str, T* out) } }; // namespace -bool ParseInt32(const std::string& str, int32_t* out) +bool ParseInt32(std::string_view str, int32_t* out) { return ParseIntegral<int32_t>(str, out); } -bool ParseInt64(const std::string& str, int64_t* out) +bool ParseInt64(std::string_view str, int64_t* out) { return ParseIntegral<int64_t>(str, out); } -bool ParseUInt8(const std::string& str, uint8_t* out) +bool ParseUInt8(std::string_view str, uint8_t* out) { return ParseIntegral<uint8_t>(str, out); } -bool ParseUInt16(const std::string& str, uint16_t* out) +bool ParseUInt16(std::string_view str, uint16_t* out) { return ParseIntegral<uint16_t>(str, out); } -bool ParseUInt32(const std::string& str, uint32_t* out) +bool ParseUInt32(std::string_view str, uint32_t* out) { return ParseIntegral<uint32_t>(str, out); } -bool ParseUInt64(const std::string& str, uint64_t* out) +bool ParseUInt64(std::string_view str, uint64_t* out) { return ParseIntegral<uint64_t>(str, out); } -std::string FormatParagraph(const std::string& in, size_t width, size_t indent) +std::string FormatParagraph(std::string_view in, size_t width, size_t indent) { assert(width >= indent); std::stringstream out; @@ -395,7 +343,7 @@ static inline bool ProcessMantissaDigit(char ch, int64_t &mantissa, int &mantiss return true; } -bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out) +bool ParseFixedPoint(std::string_view val, int decimals, int64_t *amount_out) { int64_t mantissa = 0; int64_t exponent = 0; @@ -487,14 +435,14 @@ bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out) return true; } -std::string ToLower(const std::string& str) +std::string ToLower(std::string_view str) { std::string r; for (auto ch : str) r += ToLower(ch); return r; } -std::string ToUpper(const std::string& str) +std::string ToUpper(std::string_view str) { std::string r; for (auto ch : str) r += ToUpper(ch); @@ -508,21 +456,41 @@ std::string Capitalize(std::string str) return str; } +namespace { + +using ByteAsHex = std::array<char, 2>; + +constexpr std::array<ByteAsHex, 256> CreateByteToHexMap() +{ + constexpr char hexmap[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + std::array<ByteAsHex, 256> byte_to_hex{}; + for (size_t i = 0; i < byte_to_hex.size(); ++i) { + byte_to_hex[i][0] = hexmap[i >> 4]; + byte_to_hex[i][1] = hexmap[i & 15]; + } + return byte_to_hex; +} + +} // namespace + std::string HexStr(const Span<const uint8_t> s) { std::string rv(s.size() * 2, '\0'); - static constexpr char hexmap[16] = { '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; - auto it = rv.begin(); + static constexpr auto byte_to_hex = CreateByteToHexMap(); + static_assert(sizeof(byte_to_hex) == 512); + + char* it = rv.data(); for (uint8_t v : s) { - *it++ = hexmap[v >> 4]; - *it++ = hexmap[v & 15]; + std::memcpy(it, byte_to_hex[v].data(), 2); + it += 2; } - assert(it == rv.end()); + + assert(it == rv.data() + rv.size()); return rv; } -std::optional<uint64_t> ParseByteUnits(const std::string& str, ByteUnit default_multiplier) +std::optional<uint64_t> ParseByteUnits(std::string_view str, ByteUnit default_multiplier) { if (str.empty()) { return std::nullopt; diff --git a/src/util/strencodings.h b/src/util/strencodings.h index 1f83fa3ffa..14867b21b2 100644 --- a/src/util/strencodings.h +++ b/src/util/strencodings.h @@ -9,16 +9,18 @@ #ifndef BITCOIN_UTIL_STRENCODINGS_H #define BITCOIN_UTIL_STRENCODINGS_H -#include <attributes.h> #include <span.h> #include <util/string.h> #include <charconv> +#include <cstddef> #include <cstdint> -#include <iterator> #include <limits> #include <optional> #include <string> +#include <string_view> +#include <system_error> +#include <type_traits> #include <vector> /** Used by SanitizeString() */ @@ -54,24 +56,23 @@ enum class ByteUnit : uint64_t { * @param[in] rule The set of safe chars to choose (default: least restrictive) * @return A new string without unsafe chars */ -std::string SanitizeString(const std::string& str, int rule = SAFE_CHARS_DEFAULT); -std::vector<unsigned char> ParseHex(const char* psz); -std::vector<unsigned char> ParseHex(const std::string& str); +std::string SanitizeString(std::string_view str, int rule = SAFE_CHARS_DEFAULT); +/** Parse the hex string into bytes (uint8_t or std::byte). Ignores whitespace. */ +template <typename Byte = uint8_t> +std::vector<Byte> ParseHex(std::string_view str); signed char HexDigit(char c); /* Returns true if each character in str is a hex character, and has an even * number of hex digits.*/ -bool IsHex(const std::string& str); +bool IsHex(std::string_view str); /** * Return true if the string is a hex number, optionally prefixed with "0x" */ -bool IsHexNumber(const std::string& str); -std::vector<unsigned char> DecodeBase64(const char* p, bool* pf_invalid = nullptr); -std::string DecodeBase64(const std::string& str, bool* pf_invalid = nullptr); +bool IsHexNumber(std::string_view str); +std::optional<std::vector<unsigned char>> DecodeBase64(std::string_view str); std::string EncodeBase64(Span<const unsigned char> input); inline std::string EncodeBase64(Span<const std::byte> input) { return EncodeBase64(MakeUCharSpan(input)); } -inline std::string EncodeBase64(const std::string& str) { return EncodeBase64(MakeUCharSpan(str)); } -std::vector<unsigned char> DecodeBase32(const char* p, bool* pf_invalid = nullptr); -std::string DecodeBase32(const std::string& str, bool* pf_invalid = nullptr); +inline std::string EncodeBase64(std::string_view str) { return EncodeBase64(MakeUCharSpan(str)); } +std::optional<std::vector<unsigned char>> DecodeBase32(std::string_view str); /** * Base32 encode. @@ -85,9 +86,9 @@ std::string EncodeBase32(Span<const unsigned char> input, bool pad = true); * If `pad` is true, then the output will be padded with '=' so that its length * is a multiple of 8. */ -std::string EncodeBase32(const std::string& str, bool pad = true); +std::string EncodeBase32(std::string_view str, bool pad = true); -void SplitHostPort(std::string in, uint16_t& portOut, std::string& hostOut); +void SplitHostPort(std::string_view in, uint16_t& portOut, std::string& hostOut); // LocaleIndependentAtoi is provided for backwards compatibility reasons. // @@ -101,12 +102,12 @@ void SplitHostPort(std::string in, uint16_t& portOut, std::string& hostOut); // undefined behavior, while this function returns the maximum or minimum // values, respectively. template <typename T> -T LocaleIndependentAtoi(const std::string& str) +T LocaleIndependentAtoi(std::string_view str) { static_assert(std::is_integral<T>::value); T result; // Emulate atoi(...) handling of white space and leading +/-. - std::string s = TrimString(str); + std::string_view s = TrimStringView(str); if (!s.empty() && s[0] == '+') { if (s.length() >= 2 && s[1] == '-') { return 0; @@ -162,7 +163,7 @@ constexpr inline bool IsSpace(char c) noexcept { * parsed value is not in the range representable by the type T. */ template <typename T> -std::optional<T> ToIntegral(const std::string& str) +std::optional<T> ToIntegral(std::string_view str) { static_assert(std::is_integral<T>::value); T result; @@ -178,42 +179,42 @@ std::optional<T> ToIntegral(const std::string& str) * @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. */ -[[nodiscard]] bool ParseInt32(const std::string& str, int32_t *out); +[[nodiscard]] bool ParseInt32(std::string_view str, int32_t *out); /** * Convert string to signed 64-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. */ -[[nodiscard]] bool ParseInt64(const std::string& str, int64_t *out); +[[nodiscard]] bool ParseInt64(std::string_view str, int64_t *out); /** * Convert decimal string to unsigned 8-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. */ -[[nodiscard]] bool ParseUInt8(const std::string& str, uint8_t *out); +[[nodiscard]] bool ParseUInt8(std::string_view 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); +[[nodiscard]] bool ParseUInt16(std::string_view 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. */ -[[nodiscard]] bool ParseUInt32(const std::string& str, uint32_t *out); +[[nodiscard]] bool ParseUInt32(std::string_view str, uint32_t *out); /** * Convert decimal string to unsigned 64-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. */ -[[nodiscard]] bool ParseUInt64(const std::string& str, uint64_t *out); +[[nodiscard]] bool ParseUInt64(std::string_view str, uint64_t *out); /** * Convert a span of bytes to a lower-case hexadecimal string. @@ -226,7 +227,7 @@ inline std::string HexStr(const Span<const std::byte> s) { return HexStr(MakeUCh * Format a paragraph of text to a fixed width, adding spaces for * indentation to any added line. */ -std::string FormatParagraph(const std::string& in, size_t width = 79, size_t indent = 0); +std::string FormatParagraph(std::string_view in, size_t width = 79, size_t indent = 0); /** * Timing-attack-resistant comparison. @@ -248,17 +249,28 @@ bool TimingResistantEqual(const T& a, const T& b) * @returns true on success, false on error. * @note The result must be in the range (-10^18,10^18), otherwise an overflow error will trigger. */ -[[nodiscard]] bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out); +[[nodiscard]] bool ParseFixedPoint(std::string_view, int decimals, int64_t *amount_out); + +namespace { +/** Helper class for the default infn argument to ConvertBits (just returns the input). */ +struct IntIdentity +{ + [[maybe_unused]] int operator()(int x) const { return x; } +}; + +} // namespace /** Convert from one power-of-2 number base to another. */ -template<int frombits, int tobits, bool pad, typename O, typename I> -bool ConvertBits(const O& outfn, I it, I end) { +template<int frombits, int tobits, bool pad, typename O, typename It, typename I = IntIdentity> +bool ConvertBits(O outfn, It it, It end, I infn = {}) { size_t acc = 0; size_t bits = 0; constexpr size_t maxv = (1 << tobits) - 1; constexpr size_t max_acc = (1 << (frombits + tobits - 1)) - 1; while (it != end) { - acc = ((acc << frombits) | *it) & max_acc; + int v = infn(*it); + if (v < 0) return false; + acc = ((acc << frombits) | v) & max_acc; bits += frombits; while (bits >= tobits) { bits -= tobits; @@ -298,7 +310,7 @@ constexpr char ToLower(char c) * @param[in] str the string to convert to lowercase. * @returns lowercased equivalent of str */ -std::string ToLower(const std::string& str); +std::string ToLower(std::string_view str); /** * Converts the given character to its uppercase equivalent. @@ -324,7 +336,7 @@ constexpr char ToUpper(char c) * @param[in] str the string to convert to uppercase. * @returns UPPERCASED EQUIVALENT OF str */ -std::string ToUpper(const std::string& str); +std::string ToUpper(std::string_view str); /** * Capitalizes the first character of the given string. @@ -348,6 +360,6 @@ std::string Capitalize(std::string str); * @returns optional uint64_t bytes from str or nullopt * if ToIntegral is false, str is empty, trailing whitespace or overflow */ -std::optional<uint64_t> ParseByteUnits(const std::string& str, ByteUnit default_multiplier); +std::optional<uint64_t> ParseByteUnits(std::string_view str, ByteUnit default_multiplier); #endif // BITCOIN_UTIL_STRENCODINGS_H diff --git a/src/util/string.cpp b/src/util/string.cpp index 8ea3a1afc6..dff782c330 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -3,3 +3,12 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <util/string.h> + +#include <boost/algorithm/string/replace.hpp> + +#include <string> + +void ReplaceAll(std::string& in_out, std::string_view search, std::string_view substitute) +{ + boost::replace_all(in_out, search, substitute); +} diff --git a/src/util/string.h b/src/util/string.h index a3b8df8d78..df20e34ae9 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -5,27 +5,46 @@ #ifndef BITCOIN_UTIL_STRING_H #define BITCOIN_UTIL_STRING_H -#include <attributes.h> +#include <util/spanparsing.h> #include <algorithm> #include <array> +#include <cstdint> #include <cstring> #include <locale> #include <sstream> #include <string> +#include <string_view> #include <vector> -[[nodiscard]] inline std::string TrimString(const std::string& str, const std::string& pattern = " \f\n\r\t\v") +void ReplaceAll(std::string& in_out, std::string_view search, std::string_view substitute); + +[[nodiscard]] inline std::vector<std::string> SplitString(std::string_view str, char sep) +{ + return spanparsing::Split<std::string>(str, sep); +} + +[[nodiscard]] inline std::vector<std::string> SplitString(std::string_view str, std::string_view separators) +{ + return spanparsing::Split<std::string>(str, separators); +} + +[[nodiscard]] inline std::string_view TrimStringView(std::string_view str, std::string_view pattern = " \f\n\r\t\v") { std::string::size_type front = str.find_first_not_of(pattern); if (front == std::string::npos) { - return std::string(); + return {}; } std::string::size_type end = str.find_last_not_of(pattern); return str.substr(front, end - front + 1); } -[[nodiscard]] inline std::string RemovePrefix(const std::string& str, const std::string& prefix) +[[nodiscard]] inline std::string TrimString(std::string_view str, std::string_view pattern = " \f\n\r\t\v") +{ + return std::string(TrimStringView(str, pattern)); +} + +[[nodiscard]] inline std::string_view RemovePrefixView(std::string_view str, std::string_view prefix) { if (str.substr(0, prefix.size()) == prefix) { return str.substr(prefix.size()); @@ -33,6 +52,11 @@ return str; } +[[nodiscard]] inline std::string RemovePrefix(std::string_view str, std::string_view prefix) +{ + return std::string(RemovePrefixView(str, prefix)); +} + /** * Join a list of items * @@ -52,14 +76,14 @@ auto Join(const std::vector<T>& list, const BaseType& separator, UnaryOp unary_o return ret; } -template <typename T> -T Join(const std::vector<T>& list, const T& separator) +template <typename T, typename T2> +T Join(const std::vector<T>& list, const T2& separator) { return Join(list, separator, [](const T& i) { return i; }); } // Explicit overload needed for c_str arguments, which would otherwise cause a substitution failure in the template above. -inline std::string Join(const std::vector<std::string>& list, const std::string& separator) +inline std::string Join(const std::vector<std::string>& list, std::string_view separator) { return Join<std::string>(list, separator); } @@ -75,9 +99,12 @@ inline std::string MakeUnorderedList(const std::vector<std::string>& items) /** * Check if a string does not contain any embedded NUL (\0) characters */ -[[nodiscard]] inline bool ValidAsCString(const std::string& str) noexcept +[[nodiscard]] inline bool ContainsNoNUL(std::string_view str) noexcept { - return str.size() == strlen(str.c_str()); + for (auto c : str) { + if (c == 0) return false; + } + return true; } /** diff --git a/src/util/syserror.cpp b/src/util/syserror.cpp new file mode 100644 index 0000000000..5270f55366 --- /dev/null +++ b/src/util/syserror.cpp @@ -0,0 +1,35 @@ +// Copyright (c) 2020-2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + +#include <tinyformat.h> +#include <util/syserror.h> + +#include <cstring> +#include <string> + +std::string SysErrorString(int err) +{ + char buf[1024]; + /* Too bad there are three incompatible implementations of the + * thread-safe strerror. */ + const char *s = nullptr; +#ifdef WIN32 + if (strerror_s(buf, sizeof(buf), err) == 0) s = buf; +#else +#ifdef STRERROR_R_CHAR_P /* GNU variant can return a pointer outside the passed buffer */ + s = strerror_r(err, buf, sizeof(buf)); +#else /* POSIX variant always returns message in buffer */ + if (strerror_r(err, buf, sizeof(buf)) == 0) s = buf; +#endif +#endif + if (s != nullptr) { + return strprintf("%s (%d)", s, err); + } else { + return strprintf("Unknown error (%d)", err); + } +} diff --git a/src/util/syserror.h b/src/util/syserror.h new file mode 100644 index 0000000000..a54ba553ee --- /dev/null +++ b/src/util/syserror.h @@ -0,0 +1,16 @@ +// Copyright (c) 2010-2022 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_SYSERROR_H +#define BITCOIN_UTIL_SYSERROR_H + +#include <string> + +/** Return system error string from errno value. Use this instead of + * std::strerror, which is not thread-safe. For network errors use + * NetworkErrorString from sock.h instead. + */ +std::string SysErrorString(int err); + +#endif // BITCOIN_UTIL_SYSERROR_H diff --git a/src/util/system.cpp b/src/util/system.cpp index bfb1a79b40..1953a9f836 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -25,6 +25,7 @@ #include <util/getuniquepath.h> #include <util/strencodings.h> #include <util/string.h> +#include <util/syserror.h> #include <util/translation.h> @@ -54,16 +55,6 @@ #else -#ifdef _MSC_VER -#pragma warning(disable:4786) -#pragma warning(disable:4804) -#pragma warning(disable:4805) -#pragma warning(disable:4717) -#endif - -#ifndef NOMINMAX -#define NOMINMAX -#endif #include <codecvt> #include <io.h> /* for _commit */ @@ -75,7 +66,6 @@ #include <malloc.h> #endif -#include <boost/algorithm/string/replace.hpp> #include <univalue.h> #include <fstream> @@ -96,7 +86,7 @@ const char * const BITCOIN_SETTINGS_FILENAME = "settings.json"; ArgsManager gArgs; /** Mutex to protect dir_locks. */ -static Mutex cs_dir_locks; +static GlobalMutex cs_dir_locks; /** A map that contains all the currently held directory locks. After * successful locking, these will be held here until the global destructor * cleans them up and thus automatically unlocks them, or ReleaseDirectoryLocks @@ -104,7 +94,7 @@ static Mutex cs_dir_locks; */ static std::map<std::string, std::unique_ptr<fsbridge::FileLock>> dir_locks GUARDED_BY(cs_dir_locks); -bool LockDirectory(const fs::path& directory, const std::string lockfile_name, bool probe_only) +bool LockDirectory(const fs::path& directory, const fs::path& lockfile_name, bool probe_only) { LOCK(cs_dir_locks); fs::path pathLockFile = directory / lockfile_name; @@ -128,7 +118,7 @@ bool LockDirectory(const fs::path& directory, const std::string lockfile_name, b return true; } -void UnlockDirectory(const fs::path& directory, const std::string& lockfile_name) +void UnlockDirectory(const fs::path& directory, const fs::path& lockfile_name) { LOCK(cs_dir_locks); dir_locks.erase(fs::PathToString(directory / lockfile_name)); @@ -236,7 +226,7 @@ KeyInfo InterpretKey(std::string key) * @return parsed settings value if it is valid, otherwise nullopt accompanied * by a descriptive error string */ -static std::optional<util::SettingsValue> InterpretValue(const KeyInfo& key, const std::string& value, +static std::optional<util::SettingsValue> InterpretValue(const KeyInfo& key, const std::string* value, unsigned int flags, std::string& error) { // Return negated settings as false values. @@ -246,20 +236,24 @@ static std::optional<util::SettingsValue> InterpretValue(const KeyInfo& key, con return std::nullopt; } // Double negatives like -nofoo=0 are supported (but discouraged) - if (!InterpretBool(value)) { - LogPrintf("Warning: parsed potentially confusing double-negative -%s=%s\n", key.name, value); + if (value && !InterpretBool(*value)) { + LogPrintf("Warning: parsed potentially confusing double-negative -%s=%s\n", key.name, *value); return true; } return false; } - return value; + if (!value && (flags & ArgsManager::DISALLOW_ELISION)) { + error = strprintf("Can not set -%s with no value. Please specify value with -%s=value.", key.name, key.name); + return std::nullopt; + } + return value ? *value : ""; } // 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. -ArgsManager::ArgsManager() {} -ArgsManager::~ArgsManager() {} +ArgsManager::ArgsManager() = default; +ArgsManager::~ArgsManager() = default; const std::set<std::string> ArgsManager::GetUnsuitableSectionOnlyArgs() const { @@ -320,7 +314,7 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin #endif if (key == "-") break; //bitcoin-tx using stdin - std::string val; + std::optional<std::string> val; size_t is_index = key.find('='); if (is_index != std::string::npos) { val = key.substr(is_index + 1); @@ -366,7 +360,7 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin return false; } - std::optional<util::SettingsValue> value = InterpretValue(keyinfo, val, *flags, error); + std::optional<util::SettingsValue> value = InterpretValue(keyinfo, val ? &*val : nullptr, *flags, error); if (!value) return false; m_settings.command_line_options[keyinfo.name].push_back(*value); @@ -526,12 +520,15 @@ bool ArgsManager::InitSettings(std::string& error) return true; } -bool ArgsManager::GetSettingsPath(fs::path* filepath, bool temp) const +bool ArgsManager::GetSettingsPath(fs::path* filepath, bool temp, bool backup) const { fs::path settings = GetPathArg("-settings", BITCOIN_SETTINGS_FILENAME); if (settings.empty()) { return false; } + if (backup) { + settings += ".bak"; + } if (filepath) { *filepath = fsbridge::AbsPathJoin(GetDataDirNet(), temp ? settings + ".tmp" : settings); } @@ -572,10 +569,10 @@ bool ArgsManager::ReadSettingsFile(std::vector<std::string>* errors) return true; } -bool ArgsManager::WriteSettingsFile(std::vector<std::string>* errors) const +bool ArgsManager::WriteSettingsFile(std::vector<std::string>* errors, bool backup) const { fs::path path, path_tmp; - if (!GetSettingsPath(&path, /* temp= */ false) || !GetSettingsPath(&path_tmp, /* temp= */ true)) { + if (!GetSettingsPath(&path, /*temp=*/false, backup) || !GetSettingsPath(&path_tmp, /*temp=*/true, backup)) { throw std::logic_error("Attempt to write settings file when dynamic settings are disabled."); } @@ -592,6 +589,13 @@ bool ArgsManager::WriteSettingsFile(std::vector<std::string>* errors) const return true; } +util::SettingsValue ArgsManager::GetPersistentSetting(const std::string& name) const +{ + LOCK(cs_args); + return util::GetSetting(m_settings, m_network, name, !UseDefaultSection("-" + name), + /*ignore_nonpersistent=*/true, /*get_chain_name=*/false); +} + bool ArgsManager::IsArgNegated(const std::string& strArg) const { return GetSetting(strArg).isFalse(); @@ -599,20 +603,75 @@ bool ArgsManager::IsArgNegated(const std::string& strArg) const std::string ArgsManager::GetArg(const std::string& strArg, const std::string& strDefault) const { + return GetArg(strArg).value_or(strDefault); +} + +std::optional<std::string> ArgsManager::GetArg(const std::string& strArg) const +{ const util::SettingsValue value = GetSetting(strArg); - return value.isNull() ? strDefault : value.isFalse() ? "0" : value.isTrue() ? "1" : value.isNum() ? value.getValStr() : value.get_str(); + return SettingToString(value); +} + +std::optional<std::string> SettingToString(const util::SettingsValue& value) +{ + if (value.isNull()) return std::nullopt; + if (value.isFalse()) return "0"; + if (value.isTrue()) return "1"; + if (value.isNum()) return value.getValStr(); + return value.get_str(); +} + +std::string SettingToString(const util::SettingsValue& value, const std::string& strDefault) +{ + return SettingToString(value).value_or(strDefault); } int64_t ArgsManager::GetIntArg(const std::string& strArg, int64_t nDefault) const { + return GetIntArg(strArg).value_or(nDefault); +} + +std::optional<int64_t> ArgsManager::GetIntArg(const std::string& strArg) const +{ const util::SettingsValue value = GetSetting(strArg); - return value.isNull() ? nDefault : value.isFalse() ? 0 : value.isTrue() ? 1 : value.isNum() ? value.get_int64() : LocaleIndependentAtoi<int64_t>(value.get_str()); + return SettingToInt(value); +} + +std::optional<int64_t> SettingToInt(const util::SettingsValue& value) +{ + if (value.isNull()) return std::nullopt; + if (value.isFalse()) return 0; + if (value.isTrue()) return 1; + if (value.isNum()) return value.getInt<int64_t>(); + return LocaleIndependentAtoi<int64_t>(value.get_str()); +} + +int64_t SettingToInt(const util::SettingsValue& value, int64_t nDefault) +{ + return SettingToInt(value).value_or(nDefault); } bool ArgsManager::GetBoolArg(const std::string& strArg, bool fDefault) const { + return GetBoolArg(strArg).value_or(fDefault); +} + +std::optional<bool> ArgsManager::GetBoolArg(const std::string& strArg) const +{ const util::SettingsValue value = GetSetting(strArg); - return value.isNull() ? fDefault : value.isBool() ? value.get_bool() : InterpretBool(value.get_str()); + return SettingToBool(value); +} + +std::optional<bool> SettingToBool(const util::SettingsValue& value) +{ + if (value.isNull()) return std::nullopt; + if (value.isBool()) return value.get_bool(); + return InterpretBool(value.get_str()); +} + +bool SettingToBool(const util::SettingsValue& value, bool fDefault) +{ + return SettingToBool(value).value_or(fDefault); } bool ArgsManager::SoftSetArg(const std::string& strArg, const std::string& strValue) @@ -681,7 +740,7 @@ std::string ArgsManager::GetHelpMessage() const { const bool show_debug = GetBoolArg("-help-debug", false); - std::string usage = ""; + std::string usage; LOCK(cs_args); for (const auto& arg_map : m_available_args) { switch(arg_map.first) { @@ -853,8 +912,8 @@ static bool GetConfigOptions(std::istream& stream, const std::string& filepath, error = strprintf("parse error on line %i: %s, options in configuration file must be specified without leading -", linenr, str); return false; } else if ((pos = str.find('=')) != std::string::npos) { - std::string name = prefix + TrimString(str.substr(0, pos), pattern); - std::string value = TrimString(str.substr(pos + 1), pattern); + std::string name = prefix + TrimString(std::string_view{str}.substr(0, pos), pattern); + std::string_view value = TrimStringView(std::string_view{str}.substr(pos + 1), pattern); if (used_hash && name.find("rpcpassword") != std::string::npos) { error = strprintf("parse error on line %i, using # in rpcpassword can be ambiguous and should be avoided", linenr); return false; @@ -887,7 +946,7 @@ bool ArgsManager::ReadConfigStream(std::istream& stream, const std::string& file KeyInfo key = InterpretKey(option.first); std::optional<unsigned int> flags = GetArgFlags('-' + key.name); if (flags) { - std::optional<util::SettingsValue> value = InterpretValue(key, option.second, *flags, error); + std::optional<util::SettingsValue> value = InterpretValue(key, &option.second, *flags, error); if (!value) { return false; } @@ -1002,6 +1061,7 @@ std::string ArgsManager::GetChainName() const LOCK(cs_args); util::SettingsValue value = util::GetSetting(m_settings, /* section= */ "", SettingName(arg), /* ignore_default_section_config= */ false, + /*ignore_nonpersistent=*/false, /* get_chain_name= */ true); return value.isNull() ? false : value.isBool() ? value.get_bool() : InterpretBool(value.get_str()); }; @@ -1034,7 +1094,8 @@ util::SettingsValue ArgsManager::GetSetting(const std::string& arg) const { LOCK(cs_args); return util::GetSetting( - m_settings, m_network, SettingName(arg), !UseDefaultSection(arg), /* get_chain_name= */ false); + m_settings, m_network, SettingName(arg), !UseDefaultSection(arg), + /*ignore_nonpersistent=*/false, /*get_chain_name=*/false); } std::vector<util::SettingsValue> ArgsManager::GetSettingsList(const std::string& arg) const @@ -1252,7 +1313,7 @@ fs::path GetSpecialFolderPath(int nFolder, bool fCreate) std::string ShellEscape(const std::string& arg) { std::string escaped = arg; - boost::replace_all(escaped, "'", "'\"'\"'"); + ReplaceAll(escaped, "'", "'\"'\"'"); return "'" + escaped + "'"; } #endif @@ -1374,7 +1435,7 @@ void ScheduleBatchPriority() const static sched_param param{}; const int rc = pthread_setschedparam(pthread_self(), SCHED_BATCH, ¶m); if (rc != 0) { - LogPrintf("Failed to pthread_setschedparam: %s\n", strerror(rc)); + LogPrintf("Failed to pthread_setschedparam: %s\n", SysErrorString(rc)); } #endif } diff --git a/src/util/system.h b/src/util/system.h index 796205fbfc..756e6642f2 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -14,8 +14,7 @@ #include <config/bitcoin-config.h> #endif -#include <attributes.h> -#include <compat.h> +#include <compat/compat.h> #include <compat/assumptions.h> #include <fs.h> #include <logging.h> @@ -76,8 +75,8 @@ void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length); */ [[nodiscard]] bool RenameOver(fs::path src, fs::path dest); -bool LockDirectory(const fs::path& directory, const std::string lockfile_name, bool probe_only=false); -void UnlockDirectory(const fs::path& directory, const std::string& lockfile_name); +bool LockDirectory(const fs::path& directory, const fs::path& lockfile_name, bool probe_only=false); +void UnlockDirectory(const fs::path& directory, const fs::path& lockfile_name); bool DirIsWritable(const fs::path& directory); bool CheckDiskSpace(const fs::path& dir, uint64_t additional_bytes = 0); @@ -161,6 +160,15 @@ struct SectionInfo int m_line; }; +std::string SettingToString(const util::SettingsValue&, const std::string&); +std::optional<std::string> SettingToString(const util::SettingsValue&); + +int64_t SettingToInt(const util::SettingsValue&, int64_t); +std::optional<int64_t> SettingToInt(const util::SettingsValue&); + +bool SettingToBool(const util::SettingsValue&, bool); +std::optional<bool> SettingToBool(const util::SettingsValue&); + class ArgsManager { public: @@ -175,6 +183,7 @@ public: // ALLOW_STRING = 0x08, //!< unimplemented, draft implementation in #16545 // ALLOW_LIST = 0x10, //!< unimplemented, draft implementation in #16545 DISALLOW_NEGATION = 0x20, //!< disallow -nofoo syntax + DISALLOW_ELISION = 0x40, //!< disallow -foo syntax that doesn't assign any value DEBUG_ONLY = 0x100, /* Some options would cause cross-contamination if values for @@ -331,6 +340,7 @@ protected: * @return command-line argument or default value */ std::string GetArg(const std::string& strArg, const std::string& strDefault) const; + std::optional<std::string> GetArg(const std::string& strArg) const; /** * Return path argument or default value @@ -352,6 +362,7 @@ protected: * @return command-line argument (0 if invalid number) or default value */ int64_t GetIntArg(const std::string& strArg, int64_t nDefault) const; + std::optional<int64_t> GetIntArg(const std::string& strArg) const; /** * Return boolean argument or default value @@ -361,6 +372,7 @@ protected: * @return command-line argument or default value */ bool GetBoolArg(const std::string& strArg, bool fDefault) const; + std::optional<bool> GetBoolArg(const std::string& strArg) const; /** * Set an argument if it doesn't already have a value @@ -436,7 +448,7 @@ protected: * Get settings file path, or return false if read-write settings were * disabled with -nosettings. */ - bool GetSettingsPath(fs::path* filepath = nullptr, bool temp = false) const; + bool GetSettingsPath(fs::path* filepath = nullptr, bool temp = false, bool backup = false) const; /** * Read settings file. Push errors to vector, or log them if null. @@ -444,9 +456,16 @@ protected: bool ReadSettingsFile(std::vector<std::string>* errors = nullptr); /** - * Write settings file. Push errors to vector, or log them if null. + * Write settings file or backup settings file. Push errors to vector, or + * log them if null. + */ + bool WriteSettingsFile(std::vector<std::string>* errors = nullptr, bool backup = false) const; + + /** + * Get current setting from config file or read/write settings file, + * ignoring nonpersistent command line or forced settings values. */ - bool WriteSettingsFile(std::vector<std::string>* errors = nullptr) const; + util::SettingsValue GetPersistentSetting(const std::string& name) const; /** * Access settings with lock held. diff --git a/src/util/thread.cpp b/src/util/thread.cpp index 14be668685..f9f427ba20 100644 --- a/src/util/thread.cpp +++ b/src/util/thread.cpp @@ -9,6 +9,7 @@ #include <util/threadnames.h> #include <exception> +#include <functional> void util::TraceThread(const char* thread_name, std::function<void()> thread_func) { diff --git a/src/util/time.cpp b/src/util/time.cpp index e428430bac..f6d37347f8 100644 --- a/src/util/time.cpp +++ b/src/util/time.cpp @@ -7,17 +7,20 @@ #include <config/bitcoin-config.h> #endif -#include <compat.h> +#include <compat/compat.h> +#include <tinyformat.h> #include <util/time.h> - #include <util/check.h> -#include <atomic> #include <boost/date_time/posix_time/posix_time.hpp> + +#include <atomic> +#include <chrono> #include <ctime> +#include <locale> #include <thread> - -#include <tinyformat.h> +#include <sstream> +#include <string> void UninterruptibleSleep(const std::chrono::microseconds& n) { std::this_thread::sleep_for(n); } @@ -66,20 +69,16 @@ bool ChronoSanityCheck() return true; } -template <typename T> -T GetTime() +NodeClock::time_point NodeClock::now() noexcept { const std::chrono::seconds mocktime{nMockTime.load(std::memory_order_relaxed)}; const auto ret{ mocktime.count() ? mocktime : - std::chrono::duration_cast<T>(std::chrono::system_clock::now().time_since_epoch())}; + std::chrono::system_clock::now().time_since_epoch()}; assert(ret > 0s); - return ret; -} -template std::chrono::seconds GetTime(); -template std::chrono::milliseconds GetTime(); -template std::chrono::microseconds GetTime(); + return time_point{ret}; +}; template <typename T> static T GetSystemTime() @@ -115,11 +114,6 @@ int64_t GetTimeMicros() return int64_t{GetSystemTime<std::chrono::microseconds>().count()}; } -int64_t GetTimeSeconds() -{ - return int64_t{GetSystemTime<std::chrono::seconds>().count()}; -} - int64_t GetTime() { return GetTime<std::chrono::seconds>().count(); } std::string FormatISO8601DateTime(int64_t nTime) { diff --git a/src/util/time.h b/src/util/time.h index 9d92b23725..4f9bde5d56 100644 --- a/src/util/time.h +++ b/src/util/time.h @@ -6,40 +6,65 @@ #ifndef BITCOIN_UTIL_TIME_H #define BITCOIN_UTIL_TIME_H -#include <compat.h> +#include <compat/compat.h> #include <chrono> -#include <stdint.h> +#include <cstdint> #include <string> using namespace std::chrono_literals; +/** Mockable clock in the context of tests, otherwise the system clock */ +struct NodeClock : public std::chrono::system_clock { + using time_point = std::chrono::time_point<NodeClock>; + /** Return current system time or mocked time, if set */ + static time_point now() noexcept; + static std::time_t to_time_t(const time_point&) = delete; // unused + static time_point from_time_t(std::time_t) = delete; // unused +}; +using NodeSeconds = std::chrono::time_point<NodeClock, std::chrono::seconds>; + +using SteadyClock = std::chrono::steady_clock; +using SteadySeconds = std::chrono::time_point<std::chrono::steady_clock, std::chrono::seconds>; +using SteadyMilliseconds = std::chrono::time_point<std::chrono::steady_clock, std::chrono::milliseconds>; +using SteadyMicroseconds = std::chrono::time_point<std::chrono::steady_clock, std::chrono::microseconds>; + void UninterruptibleSleep(const std::chrono::microseconds& n); /** - * Helper to count the seconds of a duration. + * Helper to count the seconds of a duration/time_point. * - * All durations should be using std::chrono and calling this should generally + * All durations/time_points should be using std::chrono and calling this should generally * be avoided in code. Though, it is still preferred to an inline t.count() to * protect against a reliance on the exact type of t. * - * This helper is used to convert durations before passing them over an + * This helper is used to convert durations/time_points before passing them over an * interface that doesn't support std::chrono (e.g. RPC, debug log, or the GUI) */ +template <typename Dur1, typename Dur2> +constexpr auto Ticks(Dur2 d) +{ + return std::chrono::duration_cast<Dur1>(d).count(); +} +template <typename Duration, typename Timepoint> +constexpr auto TicksSinceEpoch(Timepoint t) +{ + return Ticks<Duration>(t.time_since_epoch()); +} 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 HoursDouble = std::chrono::duration<double, std::chrono::hours::period>; 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 - * Use either GetTimeSeconds (not mockable) or GetTime<T> (mockable) + * Use either ClockType::now() or Now<TimePointType>() if a cast is needed. + * ClockType is + * - std::chrono::steady_clock for steady time + * - std::chrono::system_clock for system time + * - NodeClock for mockable system time */ int64_t GetTime(); @@ -47,8 +72,6 @@ int64_t GetTime(); int64_t GetTimeMillis(); /** Returns the system time (not mockable) */ int64_t GetTimeMicros(); -/** Returns the system time (not mockable) */ -int64_t GetTimeSeconds(); // Like GetTime(), but not mockable /** * DEPRECATED @@ -64,9 +87,21 @@ void SetMockTime(std::chrono::seconds mock_time_in); /** For testing */ std::chrono::seconds GetMockTime(); -/** Return system time (or mocked time, if set) */ +/** + * Return the current time point cast to the given precision. Only use this + * when an exact precision is needed, otherwise use T::clock::now() directly. + */ +template <typename T> +T Now() +{ + return std::chrono::time_point_cast<typename T::duration>(T::clock::now()); +} +/** DEPRECATED, see GetTime */ template <typename T> -T GetTime(); +T GetTime() +{ + return Now<std::chrono::time_point<NodeClock, T>>().time_since_epoch(); +} /** * ISO 8601 formatting is preferred. Use the FormatISO8601{DateTime,Date} diff --git a/src/util/tokenpipe.cpp b/src/util/tokenpipe.cpp index 4c091cd2e6..49456814e2 100644 --- a/src/util/tokenpipe.cpp +++ b/src/util/tokenpipe.cpp @@ -3,7 +3,9 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <util/tokenpipe.h> +#if defined(HAVE_CONFIG_H) #include <config/bitcoin-config.h> +#endif #ifndef WIN32 diff --git a/src/util/translation.h b/src/util/translation.h index aee601d9c1..07b7f43c8a 100644 --- a/src/util/translation.h +++ b/src/util/translation.h @@ -6,7 +6,9 @@ #define BITCOIN_UTIL_TRANSLATION_H #include <tinyformat.h> + #include <functional> +#include <string> /** * Bilingual messages: diff --git a/src/util/url.cpp b/src/util/url.cpp index e42d93bce8..ea9323e666 100644 --- a/src/util/url.cpp +++ b/src/util/url.cpp @@ -5,7 +5,8 @@ #include <util/url.h> #include <event2/http.h> -#include <stdlib.h> + +#include <cstdlib> #include <string> std::string urlDecode(const std::string &urlEncoded) { diff --git a/src/util/vector.h b/src/util/vector.h index dab65ded2a..ed745affe5 100644 --- a/src/util/vector.h +++ b/src/util/vector.h @@ -7,6 +7,7 @@ #include <initializer_list> #include <type_traits> +#include <utility> #include <vector> /** Construct a vector with the specified elements. |