diff options
author | Jim Posen <jim.posen@gmail.com> | 2018-10-22 15:51:11 -0700 |
---|---|---|
committer | Jim Posen <jim.posen@gmail.com> | 2018-11-04 22:46:07 -0800 |
commit | 2068f089c8b7b90eb4557d3f67ea0f0ed2059a23 (patch) | |
tree | bbbd0304b01d48831c60cf6b528c758d62f4949c /src/util | |
parent | 742ee213499194f97e59dae4971f1474ae7d57ad (diff) |
scripted-diff: Move util files to separate directory.
-BEGIN VERIFY SCRIPT-
mkdir -p src/util
git mv src/util.h src/util/system.h
git mv src/util.cpp src/util/system.cpp
git mv src/utilmemory.h src/util/memory.h
git mv src/utilmoneystr.h src/util/moneystr.h
git mv src/utilmoneystr.cpp src/util/moneystr.cpp
git mv src/utilstrencodings.h src/util/strencodings.h
git mv src/utilstrencodings.cpp src/util/strencodings.cpp
git mv src/utiltime.h src/util/time.h
git mv src/utiltime.cpp src/util/time.cpp
sed -i 's/<util\.h>/<util\/system\.h>/g' $(git ls-files 'src/*.h' 'src/*.cpp')
sed -i 's/<utilmemory\.h>/<util\/memory\.h>/g' $(git ls-files 'src/*.h' 'src/*.cpp')
sed -i 's/<utilmoneystr\.h>/<util\/moneystr\.h>/g' $(git ls-files 'src/*.h' 'src/*.cpp')
sed -i 's/<utilstrencodings\.h>/<util\/strencodings\.h>/g' $(git ls-files 'src/*.h' 'src/*.cpp')
sed -i 's/<utiltime\.h>/<util\/time\.h>/g' $(git ls-files 'src/*.h' 'src/*.cpp')
sed -i 's/BITCOIN_UTIL_H/BITCOIN_UTIL_SYSTEM_H/g' src/util/system.h
sed -i 's/BITCOIN_UTILMEMORY_H/BITCOIN_UTIL_MEMORY_H/g' src/util/memory.h
sed -i 's/BITCOIN_UTILMONEYSTR_H/BITCOIN_UTIL_MONEYSTR_H/g' src/util/moneystr.h
sed -i 's/BITCOIN_UTILSTRENCODINGS_H/BITCOIN_UTIL_STRENCODINGS_H/g' src/util/strencodings.h
sed -i 's/BITCOIN_UTILTIME_H/BITCOIN_UTIL_TIME_H/g' src/util/time.h
sed -i 's/ util\.\(h\|cpp\)/ util\/system\.\1/g' src/Makefile.am
sed -i 's/utilmemory\.\(h\|cpp\)/util\/memory\.\1/g' src/Makefile.am
sed -i 's/utilmoneystr\.\(h\|cpp\)/util\/moneystr\.\1/g' src/Makefile.am
sed -i 's/utilstrencodings\.\(h\|cpp\)/util\/strencodings\.\1/g' src/Makefile.am
sed -i 's/utiltime\.\(h\|cpp\)/util\/time\.\1/g' src/Makefile.am
sed -i 's/-> util ->/-> util\/system ->/' test/lint/lint-circular-dependencies.sh
sed -i 's/src\/util\.cpp/src\/util\/system\.cpp/g' test/lint/lint-format-strings.py test/lint/lint-locale-dependence.sh
sed -i 's/src\/utilmoneystr\.cpp/src\/util\/moneystr\.cpp/g' test/lint/lint-locale-dependence.sh
sed -i 's/src\/utilstrencodings\.\(h\|cpp\)/src\/util\/strencodings\.\1/g' test/lint/lint-locale-dependence.sh
sed -i 's/src\\utilstrencodings\.cpp/src\\util\\strencodings\.cpp/' build_msvc/libbitcoinconsensus/libbitcoinconsensus.vcxproj
-END VERIFY SCRIPT-
Diffstat (limited to 'src/util')
-rw-r--r-- | src/util/memory.h | 19 | ||||
-rw-r--r-- | src/util/moneystr.cpp | 77 | ||||
-rw-r--r-- | src/util/moneystr.h | 24 | ||||
-rw-r--r-- | src/util/strencodings.cpp | 599 | ||||
-rw-r--r-- | src/util/strencodings.h | 248 | ||||
-rw-r--r-- | src/util/system.cpp | 1299 | ||||
-rw-r--r-- | src/util/system.h | 382 | ||||
-rw-r--r-- | src/util/time.cpp | 110 | ||||
-rw-r--r-- | src/util/time.h | 38 |
9 files changed, 2796 insertions, 0 deletions
diff --git a/src/util/memory.h b/src/util/memory.h new file mode 100644 index 0000000000..15ecf8f80d --- /dev/null +++ b/src/util/memory.h @@ -0,0 +1,19 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2018 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. +template <typename T, typename... Args> +std::unique_ptr<T> MakeUnique(Args&&... args) +{ + return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); +} + +#endif diff --git a/src/util/moneystr.cpp b/src/util/moneystr.cpp new file mode 100644 index 0000000000..4c4de7b729 --- /dev/null +++ b/src/util/moneystr.cpp @@ -0,0 +1,77 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2018 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/moneystr.h> + +#include <primitives/transaction.h> +#include <tinyformat.h> +#include <util/strencodings.h> + +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; + std::string str = strprintf("%d.%08d", quotient, remainder); + + // Right-trim excess zeros before the decimal point: + int nTrim = 0; + for (int i = str.size()-1; (str[i] == '0' && isdigit(str[i-2])); --i) + ++nTrim; + if (nTrim) + str.erase(str.size()-nTrim, nTrim); + + if (n < 0) + str.insert((unsigned int)0, 1, '-'); + return str; +} + + +bool ParseMoney(const std::string& str, CAmount& nRet) +{ + return ParseMoney(str.c_str(), nRet); +} + +bool ParseMoney(const char* pszIn, CAmount& nRet) +{ + std::string strWhole; + int64_t nUnits = 0; + const char* p = pszIn; + while (IsSpace(*p)) + p++; + for (; *p; p++) + { + if (*p == '.') + { + p++; + int64_t nMult = COIN / 10; + while (isdigit(*p) && (nMult > 0)) + { + nUnits += nMult * (*p++ - '0'); + nMult /= 10; + } + break; + } + if (IsSpace(*p)) + break; + if (!isdigit(*p)) + return false; + strWhole.insert(strWhole.end(), *p); + } + for (; *p; p++) + if (!IsSpace(*p)) + return false; + if (strWhole.size() > 10) // guard against 63 bit overflow + return false; + if (nUnits < 0 || nUnits > COIN) + return false; + int64_t nWhole = atoi64(strWhole); + CAmount nValue = nWhole*COIN + nUnits; + + nRet = nValue; + return true; +} diff --git a/src/util/moneystr.h b/src/util/moneystr.h new file mode 100644 index 0000000000..9133f46d5d --- /dev/null +++ b/src/util/moneystr.h @@ -0,0 +1,24 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +/** + * Money parsing/formatting utilities. + */ +#ifndef BITCOIN_UTIL_MONEYSTR_H +#define BITCOIN_UTIL_MONEYSTR_H + +#include <stdint.h> +#include <string> + +#include <amount.h> + +/* 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); +bool ParseMoney(const std::string& str, CAmount& nRet); +bool ParseMoney(const char* pszIn, CAmount& nRet); + +#endif // BITCOIN_UTIL_MONEYSTR_H diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp new file mode 100644 index 0000000000..5b8520030b --- /dev/null +++ b/src/util/strencodings.cpp @@ -0,0 +1,599 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2018 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/strencodings.h> + +#include <tinyformat.h> + +#include <algorithm> +#include <cstdlib> +#include <cstring> +#include <errno.h> +#include <limits> + +static const std::string CHARS_ALPHA_NUM = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + +static const std::string SAFE_CHARS[] = +{ + CHARS_ALPHA_NUM + " .,;-_/:?@()", // SAFE_CHARS_DEFAULT + CHARS_ALPHA_NUM + " .,;-_?@", // SAFE_CHARS_UA_COMMENT + CHARS_ALPHA_NUM + ".-_", // SAFE_CHARS_FILENAME +}; + +std::string SanitizeString(const std::string& 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]); + } + return strResult; +} + +const signed char p_util_hexdigit[256] = +{ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + 0,1,2,3,4,5,6,7,8,9,-1,-1,-1,-1,-1,-1, + -1,0xa,0xb,0xc,0xd,0xe,0xf,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,0xa,0xb,0xc,0xd,0xe,0xf,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, }; + +signed char HexDigit(char c) +{ + return p_util_hexdigit[(unsigned char)c]; +} + +bool IsHex(const std::string& str) +{ + for(std::string::const_iterator it(str.begin()); it != str.end(); ++it) + { + if (HexDigit(*it) < 0) + return false; + } + return (str.size() > 0) && (str.size()%2 == 0); +} + +bool IsHexNumber(const std::string& 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 (HexDigit(c) < 0) return false; + } + // Return false for empty string or "0x". + return (str.size() > starting_location); +} + +std::vector<unsigned char> ParseHex(const char* psz) +{ + // 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; + unsigned char n = (c << 4); + c = HexDigit(*psz++); + if (c == (signed char)-1) + break; + n |= c; + vch.push_back(n); + } + return vch; +} + +std::vector<unsigned char> ParseHex(const std::string& str) +{ + return ParseHex(str.c_str()); +} + +void SplitHostPort(std::string in, int &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) { + 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 + hostOut = in; +} + +std::string EncodeBase64(const unsigned char* pch, size_t len) +{ + static const char *pbase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + std::string str; + str.reserve(((len + 2) / 3) * 4); + ConvertBits<8, 6, true>([&](int v) { str += pbase64[v]; }, pch, pch + len); + while (str.size() % 4) str += '='; + return str; +} + +std::string EncodeBase64(const std::string& str) +{ + return EncodeBase64((const unsigned char*)str.c_str(), str.size()); +} + +std::vector<unsigned char> DecodeBase64(const char* p, bool* pfInvalid) +{ + static const int decode64_table[256] = + { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, + -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -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(x); + ++p; + } + + 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 (pfInvalid) *pfInvalid = !valid; + + return ret; +} + +std::string DecodeBase64(const std::string& str) +{ + std::vector<unsigned char> vchRet = DecodeBase64(str.c_str()); + return std::string((const char*)vchRet.data(), vchRet.size()); +} + +std::string EncodeBase32(const unsigned char* pch, size_t len) +{ + static const char *pbase32 = "abcdefghijklmnopqrstuvwxyz234567"; + + std::string str; + str.reserve(((len + 4) / 5) * 8); + ConvertBits<8, 5, true>([&](int v) { str += pbase32[v]; }, pch, pch + len); + while (str.size() % 8) str += '='; + return str; +} + +std::string EncodeBase32(const std::string& str) +{ + return EncodeBase32((const unsigned char*)str.c_str(), str.size()); +} + +std::vector<unsigned char> DecodeBase32(const char* p, bool* pfInvalid) +{ + static const int decode32_table[256] = + { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 0, 1, 2, + 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -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(x); + ++p; + } + + 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 (pfInvalid) *pfInvalid = !valid; + + return ret; +} + +std::string DecodeBase32(const std::string& str) +{ + std::vector<unsigned char> vchRet = DecodeBase32(str.c_str()); + return std::string((const char*)vchRet.data(), vchRet.size()); +} + +static bool ParsePrechecks(const std::string& str) +{ + if (str.empty()) // No empty string allowed + return false; + if (str.size() >= 1 && (IsSpace(str[0]) || IsSpace(str[str.size()-1]))) // No padding allowed + return false; + if (str.size() != strlen(str.c_str())) // No embedded NUL characters allowed + return false; + return true; +} + +bool ParseInt32(const std::string& str, int32_t *out) +{ + if (!ParsePrechecks(str)) + return false; + char *endp = nullptr; + errno = 0; // strtol will not set errno if valid + long int n = strtol(str.c_str(), &endp, 10); + if(out) *out = (int32_t)n; + // Note that strtol returns a *long int*, so even if strtol doesn't report an over/underflow + // we still have to check that the returned value is within the range of an *int32_t*. On 64-bit + // platforms the size of these types may be different. + return endp && *endp == 0 && !errno && + n >= std::numeric_limits<int32_t>::min() && + n <= std::numeric_limits<int32_t>::max(); +} + +bool ParseInt64(const std::string& str, int64_t *out) +{ + if (!ParsePrechecks(str)) + return false; + char *endp = nullptr; + errno = 0; // strtoll will not set errno if valid + long long int n = strtoll(str.c_str(), &endp, 10); + if(out) *out = (int64_t)n; + // Note that strtoll returns a *long long int*, so even if strtol doesn't report an over/underflow + // we still have to check that the returned value is within the range of an *int64_t*. + return endp && *endp == 0 && !errno && + n >= std::numeric_limits<int64_t>::min() && + n <= std::numeric_limits<int64_t>::max(); +} + +bool ParseUInt32(const std::string& str, uint32_t *out) +{ + if (!ParsePrechecks(str)) + return false; + if (str.size() >= 1 && str[0] == '-') // Reject negative values, unfortunately strtoul accepts these by default if they fit in the range + return false; + char *endp = nullptr; + errno = 0; // strtoul will not set errno if valid + unsigned long int n = strtoul(str.c_str(), &endp, 10); + if(out) *out = (uint32_t)n; + // Note that strtoul returns a *unsigned long int*, so even if it doesn't report an over/underflow + // we still have to check that the returned value is within the range of an *uint32_t*. On 64-bit + // platforms the size of these types may be different. + return endp && *endp == 0 && !errno && + n <= std::numeric_limits<uint32_t>::max(); +} + +bool ParseUInt64(const std::string& str, uint64_t *out) +{ + if (!ParsePrechecks(str)) + return false; + if (str.size() >= 1 && str[0] == '-') // Reject negative values, unfortunately strtoull accepts these by default if they fit in the range + return false; + char *endp = nullptr; + errno = 0; // strtoull will not set errno if valid + unsigned long long int n = strtoull(str.c_str(), &endp, 10); + if(out) *out = (uint64_t)n; + // Note that strtoull returns a *unsigned long long int*, so even if it doesn't report an over/underflow + // we still have to check that the returned value is within the range of an *uint64_t*. + return endp && *endp == 0 && !errno && + n <= std::numeric_limits<uint64_t>::max(); +} + + +bool ParseDouble(const std::string& str, double *out) +{ + if (!ParsePrechecks(str)) + return false; + if (str.size() >= 2 && str[0] == '0' && str[1] == 'x') // No hexadecimal floats allowed + return false; + std::istringstream text(str); + text.imbue(std::locale::classic()); + double result; + text >> result; + if(out) *out = result; + return text.eof() && !text.fail(); +} + +std::string FormatParagraph(const std::string& in, size_t width, size_t indent) +{ + std::stringstream out; + size_t ptr = 0; + size_t indented = 0; + while (ptr < in.size()) + { + size_t lineend = in.find_first_of('\n', ptr); + if (lineend == std::string::npos) { + lineend = in.size(); + } + const size_t linelen = lineend - ptr; + const size_t rem_width = width - indented; + if (linelen <= rem_width) { + out << in.substr(ptr, linelen + 1); + ptr = lineend + 1; + indented = 0; + } else { + size_t finalspace = in.find_last_of(" \n", ptr + rem_width); + if (finalspace == std::string::npos || finalspace < ptr) { + // No place to break; just include the entire word and move on + finalspace = in.find_first_of("\n ", ptr); + if (finalspace == std::string::npos) { + // End of the string, just add it and break + out << in.substr(ptr); + break; + } + } + out << in.substr(ptr, finalspace - ptr) << "\n"; + if (in[finalspace] == '\n') { + indented = 0; + } else if (indent) { + out << std::string(indent, ' '); + indented = indent; + } + ptr = finalspace + 1; + } + } + return out.str(); +} + +std::string i64tostr(int64_t n) +{ + return strprintf("%d", n); +} + +std::string itostr(int n) +{ + return strprintf("%d", n); +} + +int64_t atoi64(const char* psz) +{ +#ifdef _MSC_VER + return _atoi64(psz); +#else + return strtoll(psz, nullptr, 10); +#endif +} + +int64_t atoi64(const std::string& str) +{ +#ifdef _MSC_VER + return _atoi64(str.c_str()); +#else + return strtoll(str.c_str(), nullptr, 10); +#endif +} + +int atoi(const std::string& str) +{ + return atoi(str.c_str()); +} + +/** Upper bound for mantissa. + * 10^18-1 is the largest arbitrary decimal that will fit in a signed 64-bit integer. + * Larger integers cannot consist of arbitrary combinations of 0-9: + * + * 999999999999999999 1^18-1 + * 9223372036854775807 (1<<63)-1 (max int64_t) + * 9999999999999999999 1^19-1 (would overflow) + */ +static const int64_t UPPER_BOUND = 1000000000000000000LL - 1LL; + +/** Helper function for ParseFixedPoint */ +static inline bool ProcessMantissaDigit(char ch, int64_t &mantissa, int &mantissa_tzeros) +{ + if(ch == '0') + ++mantissa_tzeros; + else { + for (int i=0; i<=mantissa_tzeros; ++i) { + if (mantissa > (UPPER_BOUND / 10LL)) + return false; /* overflow */ + mantissa *= 10; + } + mantissa += ch - '0'; + mantissa_tzeros = 0; + } + return true; +} + +bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out) +{ + int64_t mantissa = 0; + int64_t exponent = 0; + int mantissa_tzeros = 0; + bool mantissa_sign = false; + bool exponent_sign = false; + int ptr = 0; + int end = val.size(); + int point_ofs = 0; + + if (ptr < end && val[ptr] == '-') { + mantissa_sign = true; + ++ptr; + } + if (ptr < end) + { + if (val[ptr] == '0') { + /* pass single 0 */ + ++ptr; + } else if (val[ptr] >= '1' && val[ptr] <= '9') { + while (ptr < end && IsDigit(val[ptr])) { + if (!ProcessMantissaDigit(val[ptr], mantissa, mantissa_tzeros)) + return false; /* overflow */ + ++ptr; + } + } else return false; /* missing expected digit */ + } else return false; /* empty string or loose '-' */ + if (ptr < end && val[ptr] == '.') + { + ++ptr; + if (ptr < end && IsDigit(val[ptr])) + { + while (ptr < end && IsDigit(val[ptr])) { + if (!ProcessMantissaDigit(val[ptr], mantissa, mantissa_tzeros)) + return false; /* overflow */ + ++ptr; + ++point_ofs; + } + } else return false; /* missing expected digit */ + } + if (ptr < end && (val[ptr] == 'e' || val[ptr] == 'E')) + { + ++ptr; + if (ptr < end && val[ptr] == '+') + ++ptr; + else if (ptr < end && val[ptr] == '-') { + exponent_sign = true; + ++ptr; + } + if (ptr < end && IsDigit(val[ptr])) { + while (ptr < end && IsDigit(val[ptr])) { + if (exponent > (UPPER_BOUND / 10LL)) + return false; /* overflow */ + exponent = exponent * 10 + val[ptr] - '0'; + ++ptr; + } + } else return false; /* missing expected digit */ + } + if (ptr != end) + return false; /* trailing garbage */ + + /* finalize exponent */ + if (exponent_sign) + exponent = -exponent; + exponent = exponent - point_ofs + mantissa_tzeros; + + /* finalize mantissa */ + if (mantissa_sign) + mantissa = -mantissa; + + /* convert to one 64-bit fixed-point value */ + exponent += decimals; + if (exponent < 0) + return false; /* cannot represent values smaller than 10^-decimals */ + if (exponent >= 18) + return false; /* cannot represent values larger than or equal to 10^(18-decimals) */ + + for (int i=0; i < exponent; ++i) { + if (mantissa > (UPPER_BOUND / 10LL) || mantissa < -(UPPER_BOUND / 10LL)) + return false; /* overflow */ + mantissa *= 10; + } + if (mantissa > UPPER_BOUND || mantissa < -UPPER_BOUND) + return false; /* overflow */ + + if (amount_out) + *amount_out = mantissa; + + return true; +} + +bool ParseHDKeypath(const std::string& keypath_str, std::vector<uint32_t>& keypath) +{ + std::stringstream ss(keypath_str); + std::string item; + bool first = true; + while (std::getline(ss, item, '/')) { + if (item.compare("m") == 0) { + if (first) { + first = false; + continue; + } + return false; + } + // Finds whether it is hardened + uint32_t path = 0; + size_t pos = item.find("'"); + if (pos != std::string::npos) { + // The hardened tick can only be in the last index of the string + if (pos != item.size() - 1) { + return false; + } + path |= 0x80000000; + item = item.substr(0, item.size() - 1); // Drop the last character which is the hardened tick + } + + // Ensure this is only numbers + if (item.find_first_not_of( "0123456789" ) != std::string::npos) { + return false; + } + uint32_t number; + if (!ParseUInt32(item, &number)) { + return false; + } + path |= number; + + keypath.push_back(path); + first = false; + } + return true; +} + +void Downcase(std::string& str) +{ + std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c){return ToLower(c);}); +} + +std::string Capitalize(std::string str) +{ + if (str.empty()) return str; + str[0] = ToUpper(str.front()); + return str; +} diff --git a/src/util/strencodings.h b/src/util/strencodings.h new file mode 100644 index 0000000000..132071c61e --- /dev/null +++ b/src/util/strencodings.h @@ -0,0 +1,248 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +/** + * Utilities for converting data from/to strings. + */ +#ifndef BITCOIN_UTIL_STRENCODINGS_H +#define BITCOIN_UTIL_STRENCODINGS_H + +#include <stdint.h> +#include <string> +#include <vector> + +#define BEGIN(a) ((char*)&(a)) +#define END(a) ((char*)&((&(a))[1])) +#define UBEGIN(a) ((unsigned char*)&(a)) +#define UEND(a) ((unsigned char*)&((&(a))[1])) +#define ARRAYLEN(array) (sizeof(array)/sizeof((array)[0])) + +/** Used by SanitizeString() */ +enum SafeChars +{ + SAFE_CHARS_DEFAULT, //!< The full set of allowed chars + SAFE_CHARS_UA_COMMENT, //!< BIP-0014 subset + SAFE_CHARS_FILENAME, //!< Chars allowed in filenames +}; + +/** +* Remove unsafe chars. Safe chars chosen to allow simple messages/URLs/email +* addresses, but avoid anything even possibly remotely dangerous like & or > +* @param[in] str The string to sanitize +* @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); +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); +/** +* 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* pfInvalid = nullptr); +std::string DecodeBase64(const std::string& str); +std::string EncodeBase64(const unsigned char* pch, size_t len); +std::string EncodeBase64(const std::string& str); +std::vector<unsigned char> DecodeBase32(const char* p, bool* pfInvalid = nullptr); +std::string DecodeBase32(const std::string& str); +std::string EncodeBase32(const unsigned char* pch, size_t len); +std::string EncodeBase32(const std::string& str); + +void SplitHostPort(std::string in, int &portOut, std::string &hostOut); +std::string i64tostr(int64_t n); +std::string itostr(int n); +int64_t atoi64(const char* psz); +int64_t atoi64(const std::string& str); +int atoi(const std::string& str); + +/** + * Tests if the given character is a decimal digit. + * @param[in] c character to test + * @return true if the argument is a decimal digit; otherwise false. + */ +constexpr bool IsDigit(char c) +{ + return c >= '0' && c <= '9'; +} + +/** + * Tests if the given character is a whitespace character. The whitespace characters + * are: space, form-feed ('\f'), newline ('\n'), carriage return ('\r'), horizontal + * tab ('\t'), and vertical tab ('\v'). + * + * This function is locale independent. Under the C locale this function gives the + * same result as std::isspace. + * + * @param[in] c character to test + * @return true if the argument is a whitespace character; otherwise false + */ +constexpr inline bool IsSpace(char c) noexcept { + return c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v'; +} + +/** + * Convert string to signed 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. + */ +bool ParseInt32(const std::string& 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. + */ +bool ParseInt64(const std::string& str, int64_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. + */ +bool ParseUInt32(const std::string& 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. + */ +bool ParseUInt64(const std::string& str, uint64_t *out); + +/** + * Convert string to double with strict parse error feedback. + * @returns true if the entire string could be parsed as valid double, + * false if not the entire string could be parsed or when overflow or underflow occurred. + */ +bool ParseDouble(const std::string& str, double *out); + +template<typename T> +std::string HexStr(const T itbegin, const T itend, bool fSpaces=false) +{ + std::string rv; + static const char hexmap[16] = { '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + rv.reserve((itend-itbegin)*3); + for(T it = itbegin; it < itend; ++it) + { + unsigned char val = (unsigned char)(*it); + if(fSpaces && it != itbegin) + rv.push_back(' '); + rv.push_back(hexmap[val>>4]); + rv.push_back(hexmap[val&15]); + } + + return rv; +} + +template<typename T> +inline std::string HexStr(const T& vch, bool fSpaces=false) +{ + return HexStr(vch.begin(), vch.end(), fSpaces); +} + +/** + * 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); + +/** + * Timing-attack-resistant comparison. + * Takes time proportional to length + * of first argument. + */ +template <typename T> +bool TimingResistantEqual(const T& a, const T& b) +{ + if (b.size() == 0) return a.size() == 0; + size_t accumulator = a.size() ^ b.size(); + for (size_t i = 0; i < a.size(); i++) + accumulator |= a[i] ^ b[i%b.size()]; + return accumulator == 0; +} + +/** Parse number as fixed point according to JSON number syntax. + * See http://json.org/number.gif + * @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. + */ +bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out); + +/** 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) { + 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; + bits += frombits; + while (bits >= tobits) { + bits -= tobits; + outfn((acc >> bits) & maxv); + } + ++it; + } + if (pad) { + if (bits) outfn((acc << (tobits - bits)) & maxv); + } else if (bits >= frombits || ((acc << (tobits - bits)) & maxv)) { + return false; + } + return true; +} + +/** Parse an HD keypaths like "m/7/0'/2000". */ +bool ParseHDKeypath(const std::string& keypath_str, std::vector<uint32_t>& keypath); + +/** + * Converts the given character to its lowercase equivalent. + * This function is locale independent. It only converts uppercase + * characters in the standard 7-bit ASCII range. + * @param[in] c the character to convert to lowercase. + * @return the lowercase equivalent of c; or the argument + * if no conversion is possible. + */ +constexpr unsigned char ToLower(unsigned char c) +{ + return (c >= 'A' && c <= 'Z' ? (c - 'A') + 'a' : c); +} + +/** + * Converts the given string to its lowercase equivalent. + * This function is locale independent. It only converts uppercase + * characters in the standard 7-bit ASCII range. + * @param[in,out] str the string to convert to lowercase. + */ +void Downcase(std::string& str); + +/** + * Converts the given character to its uppercase equivalent. + * This function is locale independent. It only converts lowercase + * characters in the standard 7-bit ASCII range. + * @param[in] c the character to convert to uppercase. + * @return the uppercase equivalent of c; or the argument + * if no conversion is possible. + */ +constexpr unsigned char ToUpper(unsigned char c) +{ + return (c >= 'a' && c <= 'z' ? (c - 'a') + 'A' : c); +} + +/** + * Capitalizes the first character of the given string. + * This function is locale independent. It only capitalizes the + * first character of the argument if it has an uppercase equivalent + * in the standard 7-bit ASCII range. + * @param[in] str the string to capitalize. + * @return string with the first letter capitalized. + */ +std::string Capitalize(std::string str); + +#endif // BITCOIN_UTIL_STRENCODINGS_H diff --git a/src/util/system.cpp b/src/util/system.cpp new file mode 100644 index 0000000000..4f5dd2d6e9 --- /dev/null +++ b/src/util/system.cpp @@ -0,0 +1,1299 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2018 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/system.h> + +#include <chainparamsbase.h> +#include <random.h> +#include <serialize.h> +#include <util/strencodings.h> + +#include <stdarg.h> + +#if (defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)) +#include <pthread.h> +#include <pthread_np.h> +#endif + +#ifndef WIN32 +// for posix_fallocate +#ifdef __linux__ + +#ifdef _POSIX_C_SOURCE +#undef _POSIX_C_SOURCE +#endif + +#define _POSIX_C_SOURCE 200112L + +#endif // __linux__ + +#include <algorithm> +#include <fcntl.h> +#include <sched.h> +#include <sys/resource.h> +#include <sys/stat.h> + +#else + +#ifdef _MSC_VER +#pragma warning(disable:4786) +#pragma warning(disable:4804) +#pragma warning(disable:4805) +#pragma warning(disable:4717) +#endif + +#ifdef _WIN32_WINNT +#undef _WIN32_WINNT +#endif +#define _WIN32_WINNT 0x0501 + +#ifdef _WIN32_IE +#undef _WIN32_IE +#endif +#define _WIN32_IE 0x0501 + +#define WIN32_LEAN_AND_MEAN 1 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include <codecvt> + +#include <io.h> /* for _commit */ +#include <shellapi.h> +#include <shlobj.h> +#endif + +#ifdef HAVE_SYS_PRCTL_H +#include <sys/prctl.h> +#endif + +#ifdef HAVE_MALLOPT_ARENA_MAX +#include <malloc.h> +#endif + +#include <boost/thread.hpp> +#include <openssl/crypto.h> +#include <openssl/rand.h> +#include <openssl/conf.h> +#include <thread> + +// Application startup time (used for uptime calculation) +const int64_t nStartupTime = GetTime(); + +const char * const BITCOIN_CONF_FILENAME = "bitcoin.conf"; +const char * const BITCOIN_PID_FILENAME = "bitcoind.pid"; + +ArgsManager gArgs; + +/** Init OpenSSL library multithreading support */ +static std::unique_ptr<CCriticalSection[]> ppmutexOpenSSL; +void locking_callback(int mode, int i, const char* file, int line) NO_THREAD_SAFETY_ANALYSIS +{ + if (mode & CRYPTO_LOCK) { + ENTER_CRITICAL_SECTION(ppmutexOpenSSL[i]); + } else { + LEAVE_CRITICAL_SECTION(ppmutexOpenSSL[i]); + } +} + +// Singleton for wrapping OpenSSL setup/teardown. +class CInit +{ +public: + CInit() + { + // Init OpenSSL library multithreading support + ppmutexOpenSSL.reset(new CCriticalSection[CRYPTO_num_locks()]); + CRYPTO_set_locking_callback(locking_callback); + + // OpenSSL can optionally load a config file which lists optional loadable modules and engines. + // We don't use them so we don't require the config. However some of our libs may call functions + // which attempt to load the config file, possibly resulting in an exit() or crash if it is missing + // or corrupt. Explicitly tell OpenSSL not to try to load the file. The result for our libs will be + // that the config appears to have been loaded and there are no modules/engines available. + OPENSSL_no_config(); + +#ifdef WIN32 + // Seed OpenSSL PRNG with current contents of the screen + RAND_screen(); +#endif + + // Seed OpenSSL PRNG with performance counter + RandAddSeed(); + } + ~CInit() + { + // Securely erase the memory used by the PRNG + RAND_cleanup(); + // Shutdown OpenSSL library multithreading support + CRYPTO_set_locking_callback(nullptr); + // Clear the set of locks now to maintain symmetry with the constructor. + ppmutexOpenSSL.reset(); + } +} +instance_of_cinit; + +/** 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 + * is called. + */ +static std::map<std::string, std::unique_ptr<fsbridge::FileLock>> dir_locks; +/** Mutex to protect dir_locks. */ +static std::mutex cs_dir_locks; + +bool LockDirectory(const fs::path& directory, const std::string lockfile_name, bool probe_only) +{ + std::lock_guard<std::mutex> ulock(cs_dir_locks); + fs::path pathLockFile = directory / lockfile_name; + + // If a lock for this directory already exists in the map, don't try to re-lock it + if (dir_locks.count(pathLockFile.string())) { + return true; + } + + // 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); + if (!lock->TryLock()) { + return error("Error while attempting to lock directory %s: %s", directory.string(), lock->GetReason()); + } + if (!probe_only) { + // Lock successful and we're not just probing, put it into the map + dir_locks.emplace(pathLockFile.string(), std::move(lock)); + } + return true; +} + +void ReleaseDirectoryLocks() +{ + std::lock_guard<std::mutex> ulock(cs_dir_locks); + dir_locks.clear(); +} + +bool DirIsWritable(const fs::path& directory) +{ + fs::path tmpFile = directory / fs::unique_path(); + + FILE* file = fsbridge::fopen(tmpFile, "a"); + if (!file) return false; + + fclose(file); + remove(tmpFile); + + return true; +} + +/** + * Interpret a string argument as a boolean. + * + * The definition of atoi() requires that non-numeric string values like "foo", + * return 0. This means that if a user unintentionally supplies a non-integer + * argument here, the return value is always false. This means that -foo=false + * does what the user probably expects, but -foo=true is well defined but does + * not do what they probably expected. + * + * The return value of atoi() is undefined when given input not representable as + * an int. On most systems this means string value between "-2147483648" and + * "2147483647" are well defined (this method will return true). Setting + * -txindex=2147483648 on most systems, however, is probably undefined. + * + * For a more extensive discussion of this topic (and a wide range of opinions + * on the Right Way to change this code), see PR12713. + */ +static bool InterpretBool(const std::string& strValue) +{ + if (strValue.empty()) + return true; + return (atoi(strValue) != 0); +} + +/** Internal helper functions for ArgsManager */ +class ArgsManagerHelper { +public: + typedef std::map<std::string, std::vector<std::string>> MapArgs; + + /** Determine whether to use config settings in the default section, + * See also comments around ArgsManager::ArgsManager() below. */ + static inline bool UseDefaultSection(const ArgsManager& am, const std::string& arg) EXCLUSIVE_LOCKS_REQUIRED(am.cs_args) + { + return (am.m_network == CBaseChainParams::MAIN || am.m_network_only_args.count(arg) == 0); + } + + /** Convert regular argument into the network-specific setting */ + static inline std::string NetworkArg(const ArgsManager& am, const std::string& arg) + { + assert(arg.length() > 1 && arg[0] == '-'); + return "-" + am.m_network + "." + arg.substr(1); + } + + /** Find arguments in a map and add them to a vector */ + static inline void AddArgs(std::vector<std::string>& res, const MapArgs& map_args, const std::string& arg) + { + auto it = map_args.find(arg); + if (it != map_args.end()) { + res.insert(res.end(), it->second.begin(), it->second.end()); + } + } + + /** Return true/false if an argument is set in a map, and also + * return the first (or last) of the possibly multiple values it has + */ + static inline std::pair<bool,std::string> GetArgHelper(const MapArgs& map_args, const std::string& arg, bool getLast = false) + { + auto it = map_args.find(arg); + + if (it == map_args.end() || it->second.empty()) { + return std::make_pair(false, std::string()); + } + + if (getLast) { + return std::make_pair(true, it->second.back()); + } else { + return std::make_pair(true, it->second.front()); + } + } + + /* Get the string value of an argument, returning a pair of a boolean + * indicating the argument was found, and the value for the argument + * if it was found (or the empty string if not found). + */ + static inline std::pair<bool,std::string> GetArg(const ArgsManager &am, const std::string& arg) + { + LOCK(am.cs_args); + std::pair<bool,std::string> found_result(false, std::string()); + + // We pass "true" to GetArgHelper in order to return the last + // argument value seen from the command line (so "bitcoind -foo=bar + // -foo=baz" gives GetArg(am,"foo")=={true,"baz"} + found_result = GetArgHelper(am.m_override_args, arg, true); + if (found_result.first) { + return found_result; + } + + // But in contrast we return the first argument seen in a config file, + // so "foo=bar \n foo=baz" in the config file gives + // GetArg(am,"foo")={true,"bar"} + if (!am.m_network.empty()) { + found_result = GetArgHelper(am.m_config_args, NetworkArg(am, arg)); + if (found_result.first) { + return found_result; + } + } + + if (UseDefaultSection(am, arg)) { + found_result = GetArgHelper(am.m_config_args, arg); + if (found_result.first) { + return found_result; + } + } + + return found_result; + } + + /* Special test for -testnet and -regtest args, because we + * don't want to be confused by craziness like "[regtest] testnet=1" + */ + static inline bool GetNetBoolArg(const ArgsManager &am, const std::string& net_arg) EXCLUSIVE_LOCKS_REQUIRED(am.cs_args) + { + std::pair<bool,std::string> found_result(false,std::string()); + found_result = GetArgHelper(am.m_override_args, net_arg, true); + if (!found_result.first) { + found_result = GetArgHelper(am.m_config_args, net_arg, true); + if (!found_result.first) { + return false; // not set + } + } + return InterpretBool(found_result.second); // is set, so evaluate + } +}; + +/** + * Interpret -nofoo as if the user supplied -foo=0. + * + * This method also tracks when the -no form was supplied, and if so, + * checks whether there was a double-negative (-nofoo=0 -> -foo=1). + * + * If there was not a double negative, it removes the "no" from the key, + * and returns true, indicating the caller should clear the args vector + * to indicate a negated option. + * + * If there was a double negative, it removes "no" from the key, sets the + * value to "1" and returns false. + * + * If there was no "no", it leaves key and value untouched and returns + * false. + * + * Where an option was negated can be later checked using the + * IsArgNegated() method. One use case for this is to have a way to disable + * options that are not normally boolean (e.g. using -nodebuglogfile to request + * that debug log output is not sent to any file at all). + */ +static bool InterpretNegatedOption(std::string& key, std::string& val) +{ + assert(key[0] == '-'); + + size_t option_index = key.find('.'); + if (option_index == std::string::npos) { + option_index = 1; + } else { + ++option_index; + } + if (key.substr(option_index, 2) == "no") { + bool bool_val = InterpretBool(val); + key.erase(option_index, 2); + if (!bool_val ) { + // Double negatives like -nofoo=0 are supported (but discouraged) + LogPrintf("Warning: parsed potentially confusing double-negative %s=%s\n", key, val); + val = "1"; + } else { + return true; + } + } + return false; +} + +ArgsManager::ArgsManager() : + /* These options would cause cross-contamination if values for + * mainnet were used while running on regtest/testnet (or vice-versa). + * Setting them as section_only_args ensures that sharing a config file + * between mainnet and regtest/testnet won't cause problems due to these + * parameters by accident. */ + m_network_only_args{ + "-addnode", "-connect", + "-port", "-bind", + "-rpcport", "-rpcbind", + "-wallet", + } +{ + // nothing to do +} + +void ArgsManager::WarnForSectionOnlyArgs() +{ + LOCK(cs_args); + + // if there's no section selected, don't worry + if (m_network.empty()) return; + + // if it's okay to use the default section for this network, don't worry + if (m_network == CBaseChainParams::MAIN) return; + + for (const auto& arg : m_network_only_args) { + std::pair<bool, std::string> found_result; + + // if this option is overridden it's fine + found_result = ArgsManagerHelper::GetArgHelper(m_override_args, arg); + if (found_result.first) continue; + + // if there's a network-specific value for this option, it's fine + found_result = ArgsManagerHelper::GetArgHelper(m_config_args, ArgsManagerHelper::NetworkArg(*this, arg)); + if (found_result.first) continue; + + // if there isn't a default value for this option, it's fine + found_result = ArgsManagerHelper::GetArgHelper(m_config_args, arg); + if (!found_result.first) continue; + + // otherwise, issue a warning + LogPrintf("Warning: Config setting for %s only applied on %s network when in [%s] section.\n", arg, m_network, m_network); + } +} + +void ArgsManager::SelectConfigNetwork(const std::string& network) +{ + LOCK(cs_args); + m_network = network; +} + +bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::string& error) +{ + LOCK(cs_args); + m_override_args.clear(); + + for (int i = 1; i < argc; i++) { + std::string key(argv[i]); + std::string val; + size_t is_index = key.find('='); + if (is_index != std::string::npos) { + val = key.substr(is_index + 1); + key.erase(is_index); + } +#ifdef WIN32 + std::transform(key.begin(), key.end(), key.begin(), ::tolower); + if (key[0] == '/') + key[0] = '-'; +#endif + + if (key[0] != '-') + break; + + // Transform --foo to -foo + if (key.length() > 1 && key[1] == '-') + key.erase(0, 1); + + // Check for -nofoo + if (InterpretNegatedOption(key, val)) { + m_override_args[key].clear(); + } else { + m_override_args[key].push_back(val); + } + + // Check that the arg is known + if (!(IsSwitchChar(key[0]) && key.size() == 1)) { + if (!IsArgKnown(key)) { + error = strprintf("Invalid parameter %s", key.c_str()); + return false; + } + } + } + + // we do not allow -includeconf from command line, so we clear it here + auto it = m_override_args.find("-includeconf"); + if (it != m_override_args.end()) { + if (it->second.size() > 0) { + for (const auto& ic : it->second) { + error += "-includeconf cannot be used from commandline; -includeconf=" + ic + "\n"; + } + return false; + } + } + return true; +} + +bool ArgsManager::IsArgKnown(const std::string& key) const +{ + size_t option_index = key.find('.'); + std::string arg_no_net; + if (option_index == std::string::npos) { + arg_no_net = key; + } else { + arg_no_net = std::string("-") + key.substr(option_index + 1, std::string::npos); + } + + LOCK(cs_args); + for (const auto& arg_map : m_available_args) { + if (arg_map.second.count(arg_no_net)) return true; + } + return false; +} + +std::vector<std::string> ArgsManager::GetArgs(const std::string& strArg) const +{ + std::vector<std::string> result = {}; + if (IsArgNegated(strArg)) return result; // special case + + LOCK(cs_args); + + ArgsManagerHelper::AddArgs(result, m_override_args, strArg); + if (!m_network.empty()) { + ArgsManagerHelper::AddArgs(result, m_config_args, ArgsManagerHelper::NetworkArg(*this, strArg)); + } + + if (ArgsManagerHelper::UseDefaultSection(*this, strArg)) { + ArgsManagerHelper::AddArgs(result, m_config_args, strArg); + } + + return result; +} + +bool ArgsManager::IsArgSet(const std::string& strArg) const +{ + if (IsArgNegated(strArg)) return true; // special case + return ArgsManagerHelper::GetArg(*this, strArg).first; +} + +bool ArgsManager::IsArgNegated(const std::string& strArg) const +{ + LOCK(cs_args); + + const auto& ov = m_override_args.find(strArg); + if (ov != m_override_args.end()) return ov->second.empty(); + + if (!m_network.empty()) { + const auto& cfs = m_config_args.find(ArgsManagerHelper::NetworkArg(*this, strArg)); + if (cfs != m_config_args.end()) return cfs->second.empty(); + } + + const auto& cf = m_config_args.find(strArg); + if (cf != m_config_args.end()) return cf->second.empty(); + + return false; +} + +std::string ArgsManager::GetArg(const std::string& strArg, const std::string& strDefault) const +{ + if (IsArgNegated(strArg)) return "0"; + std::pair<bool,std::string> found_res = ArgsManagerHelper::GetArg(*this, strArg); + if (found_res.first) return found_res.second; + return strDefault; +} + +int64_t ArgsManager::GetArg(const std::string& strArg, int64_t nDefault) const +{ + if (IsArgNegated(strArg)) return 0; + std::pair<bool,std::string> found_res = ArgsManagerHelper::GetArg(*this, strArg); + if (found_res.first) return atoi64(found_res.second); + return nDefault; +} + +bool ArgsManager::GetBoolArg(const std::string& strArg, bool fDefault) const +{ + if (IsArgNegated(strArg)) return false; + std::pair<bool,std::string> found_res = ArgsManagerHelper::GetArg(*this, strArg); + if (found_res.first) return InterpretBool(found_res.second); + return fDefault; +} + +bool ArgsManager::SoftSetArg(const std::string& strArg, const std::string& strValue) +{ + LOCK(cs_args); + if (IsArgSet(strArg)) return false; + ForceSetArg(strArg, strValue); + return true; +} + +bool ArgsManager::SoftSetBoolArg(const std::string& strArg, bool fValue) +{ + if (fValue) + return SoftSetArg(strArg, std::string("1")); + else + return SoftSetArg(strArg, std::string("0")); +} + +void ArgsManager::ForceSetArg(const std::string& strArg, const std::string& strValue) +{ + LOCK(cs_args); + m_override_args[strArg] = {strValue}; +} + +void ArgsManager::AddArg(const std::string& name, const std::string& help, const bool debug_only, const OptionsCategory& cat) +{ + // Split arg name from its help param + size_t eq_index = name.find('='); + if (eq_index == std::string::npos) { + eq_index = name.size(); + } + + LOCK(cs_args); + std::map<std::string, Arg>& arg_map = m_available_args[cat]; + auto ret = arg_map.emplace(name.substr(0, eq_index), Arg(name.substr(eq_index, name.size() - eq_index), help, debug_only)); + assert(ret.second); // Make sure an insertion actually happened +} + +void ArgsManager::AddHiddenArgs(const std::vector<std::string>& names) +{ + for (const std::string& name : names) { + AddArg(name, "", false, OptionsCategory::HIDDEN); + } +} + +std::string ArgsManager::GetHelpMessage() const +{ + const bool show_debug = gArgs.GetBoolArg("-help-debug", false); + + std::string usage = ""; + LOCK(cs_args); + for (const auto& arg_map : m_available_args) { + switch(arg_map.first) { + case OptionsCategory::OPTIONS: + usage += HelpMessageGroup("Options:"); + break; + case OptionsCategory::CONNECTION: + usage += HelpMessageGroup("Connection options:"); + break; + case OptionsCategory::ZMQ: + usage += HelpMessageGroup("ZeroMQ notification options:"); + break; + case OptionsCategory::DEBUG_TEST: + usage += HelpMessageGroup("Debugging/Testing options:"); + break; + case OptionsCategory::NODE_RELAY: + usage += HelpMessageGroup("Node relay options:"); + break; + case OptionsCategory::BLOCK_CREATION: + usage += HelpMessageGroup("Block creation options:"); + break; + case OptionsCategory::RPC: + usage += HelpMessageGroup("RPC server options:"); + break; + case OptionsCategory::WALLET: + usage += HelpMessageGroup("Wallet options:"); + break; + case OptionsCategory::WALLET_DEBUG_TEST: + if (show_debug) usage += HelpMessageGroup("Wallet debugging/testing options:"); + break; + case OptionsCategory::CHAINPARAMS: + usage += HelpMessageGroup("Chain selection options:"); + break; + case OptionsCategory::GUI: + usage += HelpMessageGroup("UI Options:"); + break; + case OptionsCategory::COMMANDS: + usage += HelpMessageGroup("Commands:"); + break; + case OptionsCategory::REGISTER_COMMANDS: + usage += HelpMessageGroup("Register Commands:"); + break; + default: + break; + } + + // When we get to the hidden options, stop + if (arg_map.first == OptionsCategory::HIDDEN) break; + + for (const auto& arg : arg_map.second) { + if (show_debug || !arg.second.m_debug_only) { + std::string name; + if (arg.second.m_help_param.empty()) { + name = arg.first; + } else { + name = arg.first + arg.second.m_help_param; + } + usage += HelpMessageOpt(name, arg.second.m_help_text); + } + } + } + return usage; +} + +bool HelpRequested(const ArgsManager& args) +{ + return args.IsArgSet("-?") || args.IsArgSet("-h") || args.IsArgSet("-help") || args.IsArgSet("-help-debug"); +} + +static const int screenWidth = 79; +static const int optIndent = 2; +static const int msgIndent = 7; + +std::string HelpMessageGroup(const std::string &message) { + return std::string(message) + std::string("\n\n"); +} + +std::string HelpMessageOpt(const std::string &option, const std::string &message) { + return std::string(optIndent,' ') + std::string(option) + + std::string("\n") + std::string(msgIndent,' ') + + FormatParagraph(message, screenWidth - msgIndent, msgIndent) + + std::string("\n\n"); +} + +static std::string FormatException(const std::exception* pex, const char* pszThread) +{ +#ifdef WIN32 + char pszModule[MAX_PATH] = ""; + GetModuleFileNameA(nullptr, pszModule, sizeof(pszModule)); +#else + const char* pszModule = "bitcoin"; +#endif + if (pex) + return strprintf( + "EXCEPTION: %s \n%s \n%s in %s \n", typeid(*pex).name(), pex->what(), pszModule, pszThread); + else + return strprintf( + "UNKNOWN EXCEPTION \n%s in %s \n", pszModule, pszThread); +} + +void PrintExceptionContinue(const std::exception* pex, const char* pszThread) +{ + std::string message = FormatException(pex, pszThread); + LogPrintf("\n\n************************\n%s\n", message); + fprintf(stderr, "\n\n************************\n%s\n", message.c_str()); +} + +fs::path GetDefaultDataDir() +{ + // Windows < Vista: C:\Documents and Settings\Username\Application Data\Bitcoin + // Windows >= Vista: C:\Users\Username\AppData\Roaming\Bitcoin + // Mac: ~/Library/Application Support/Bitcoin + // Unix: ~/.bitcoin +#ifdef WIN32 + // Windows + return GetSpecialFolderPath(CSIDL_APPDATA) / "Bitcoin"; +#else + fs::path pathRet; + char* pszHome = getenv("HOME"); + if (pszHome == nullptr || strlen(pszHome) == 0) + pathRet = fs::path("/"); + else + pathRet = fs::path(pszHome); +#ifdef MAC_OSX + // Mac + return pathRet / "Library/Application Support/Bitcoin"; +#else + // Unix + return pathRet / ".bitcoin"; +#endif +#endif +} + +static fs::path g_blocks_path_cached; +static fs::path g_blocks_path_cache_net_specific; +static fs::path pathCached; +static fs::path pathCachedNetSpecific; +static CCriticalSection csPathCached; + +const fs::path &GetBlocksDir(bool fNetSpecific) +{ + + LOCK(csPathCached); + + fs::path &path = fNetSpecific ? g_blocks_path_cache_net_specific : g_blocks_path_cached; + + // This can be called during exceptions by LogPrintf(), so we cache the + // value so we don't have to do memory allocations after that. + 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); + } + if (fNetSpecific) + path /= BaseParams().DataDir(); + + path /= "blocks"; + fs::create_directories(path); + return path; +} + +const fs::path &GetDataDir(bool fNetSpecific) +{ + + LOCK(csPathCached); + + fs::path &path = fNetSpecific ? pathCachedNetSpecific : pathCached; + + // This can be called during exceptions by LogPrintf(), so we cache the + // value so we don't have to do memory allocations after that. + if (!path.empty()) + return path; + + if (gArgs.IsArgSet("-datadir")) { + path = fs::system_complete(gArgs.GetArg("-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"); + } + + return path; +} + +void ClearDatadirCache() +{ + LOCK(csPathCached); + + pathCached = fs::path(); + pathCachedNetSpecific = fs::path(); + g_blocks_path_cached = fs::path(); + g_blocks_path_cache_net_specific = fs::path(); +} + +fs::path GetConfigFile(const std::string& confPath) +{ + return AbsPathForConfigVal(fs::path(confPath), false); +} + +static std::string TrimString(const std::string& str, const std::string& pattern) +{ + std::string::size_type front = str.find_first_not_of(pattern); + if (front == std::string::npos) { + return std::string(); + } + std::string::size_type end = str.find_last_not_of(pattern); + return str.substr(front, end - front + 1); +} + +static bool GetConfigOptions(std::istream& stream, std::string& error, std::vector<std::pair<std::string, std::string>> &options) +{ + std::string str, prefix; + std::string::size_type pos; + int linenr = 1; + while (std::getline(stream, str)) { + if ((pos = str.find('#')) != std::string::npos) { + str = str.substr(0, pos); + } + const static std::string pattern = " \t\r\n"; + str = TrimString(str, pattern); + if (!str.empty()) { + if (*str.begin() == '[' && *str.rbegin() == ']') { + prefix = str.substr(1, str.size() - 2) + '.'; + } else if (*str.begin() == '-') { + 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); + options.emplace_back(name, value); + } else { + error = strprintf("parse error on line %i: %s", linenr, str); + if (str.size() >= 2 && str.substr(0, 2) == "no") { + error += strprintf(", if you intended to specify a negated option, use %s=1 instead", str); + } + return false; + } + } + ++linenr; + } + return true; +} + +bool ArgsManager::ReadConfigStream(std::istream& stream, std::string& error, bool ignore_invalid_keys) +{ + LOCK(cs_args); + std::vector<std::pair<std::string, std::string>> options; + if (!GetConfigOptions(stream, error, options)) { + return false; + } + for (const std::pair<std::string, std::string>& option : options) { + std::string strKey = std::string("-") + option.first; + std::string strValue = option.second; + + if (InterpretNegatedOption(strKey, strValue)) { + m_config_args[strKey].clear(); + } else { + m_config_args[strKey].push_back(strValue); + } + + // Check that the arg is known + if (!IsArgKnown(strKey)) { + if (!ignore_invalid_keys) { + error = strprintf("Invalid configuration value %s", option.first.c_str()); + return false; + } else { + LogPrintf("Ignoring unknown configuration value %s\n", option.first); + } + } + } + return true; +} + +bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys) +{ + { + LOCK(cs_args); + m_config_args.clear(); + } + + const std::string confPath = GetArg("-conf", BITCOIN_CONF_FILENAME); + fsbridge::ifstream stream(GetConfigFile(confPath)); + + // ok to not have a config file + if (stream.good()) { + if (!ReadConfigStream(stream, error, ignore_invalid_keys)) { + return false; + } + // if there is an -includeconf in the override args, but it is empty, that means the user + // passed '-noincludeconf' on the command line, in which case we should not include anything + bool emptyIncludeConf; + { + LOCK(cs_args); + emptyIncludeConf = m_override_args.count("-includeconf") == 0; + } + if (emptyIncludeConf) { + std::string chain_id = GetChainName(); + std::vector<std::string> includeconf(GetArgs("-includeconf")); + { + // We haven't set m_network yet (that happens in SelectParams()), so manually check + // for network.includeconf args. + std::vector<std::string> includeconf_net(GetArgs(std::string("-") + chain_id + ".includeconf")); + includeconf.insert(includeconf.end(), includeconf_net.begin(), includeconf_net.end()); + } + + // Remove -includeconf from configuration, so we can warn about recursion + // later + { + LOCK(cs_args); + m_config_args.erase("-includeconf"); + m_config_args.erase(std::string("-") + chain_id + ".includeconf"); + } + + for (const std::string& to_include : includeconf) { + fsbridge::ifstream include_config(GetConfigFile(to_include)); + if (include_config.good()) { + if (!ReadConfigStream(include_config, error, ignore_invalid_keys)) { + return false; + } + LogPrintf("Included configuration file %s\n", to_include.c_str()); + } else { + error = "Failed to include configuration file " + to_include; + return false; + } + } + + // Warn about recursive -includeconf + includeconf = GetArgs("-includeconf"); + { + std::vector<std::string> includeconf_net(GetArgs(std::string("-") + chain_id + ".includeconf")); + includeconf.insert(includeconf.end(), includeconf_net.begin(), includeconf_net.end()); + std::string chain_id_final = GetChainName(); + if (chain_id_final != chain_id) { + // Also warn about recursive includeconf for the chain that was specified in one of the includeconfs + includeconf_net = GetArgs(std::string("-") + chain_id_final + ".includeconf"); + includeconf.insert(includeconf.end(), includeconf_net.begin(), includeconf_net.end()); + } + } + for (const std::string& to_include : includeconf) { + fprintf(stderr, "warning: -includeconf cannot be used from included files; ignoring -includeconf=%s\n", to_include.c_str()); + } + } + } + + // If datadir is changed in .conf file: + ClearDatadirCache(); + if (!fs::is_directory(GetDataDir(false))) { + error = strprintf("specified data directory \"%s\" does not exist.", gArgs.GetArg("-datadir", "").c_str()); + return false; + } + return true; +} + +std::string ArgsManager::GetChainName() const +{ + LOCK(cs_args); + bool fRegTest = ArgsManagerHelper::GetNetBoolArg(*this, "-regtest"); + bool fTestNet = ArgsManagerHelper::GetNetBoolArg(*this, "-testnet"); + + if (fTestNet && fRegTest) + throw std::runtime_error("Invalid combination of -regtest and -testnet."); + if (fRegTest) + return CBaseChainParams::REGTEST; + if (fTestNet) + return CBaseChainParams::TESTNET; + return CBaseChainParams::MAIN; +} + +#ifndef WIN32 +fs::path GetPidFile() +{ + return AbsPathForConfigVal(fs::path(gArgs.GetArg("-pid", BITCOIN_PID_FILENAME))); +} + +void CreatePidFile(const fs::path &path, pid_t pid) +{ + FILE* file = fsbridge::fopen(path, "w"); + if (file) + { + fprintf(file, "%d\n", pid); + fclose(file); + } +} +#endif + +bool RenameOver(fs::path src, fs::path dest) +{ +#ifdef WIN32 + return MoveFileExW(src.wstring().c_str(), dest.wstring().c_str(), + MOVEFILE_REPLACE_EXISTING) != 0; +#else + int rc = std::rename(src.string().c_str(), dest.string().c_str()); + return (rc == 0); +#endif /* WIN32 */ +} + +/** + * Ignores exceptions thrown by Boost's create_directories if the requested directory exists. + * Specifically handles case where path p exists, but it wasn't possible for the user to + * write to the parent directory. + */ +bool TryCreateDirectories(const fs::path& p) +{ + try + { + return fs::create_directories(p); + } catch (const fs::filesystem_error&) { + if (!fs::exists(p) || !fs::is_directory(p)) + throw; + } + + // create_directories didn't create the directory, it had to have existed already + return false; +} + +bool FileCommit(FILE *file) +{ + if (fflush(file) != 0) { // harmless if redundantly called + LogPrintf("%s: fflush failed: %d\n", __func__, errno); + return false; + } +#ifdef WIN32 + HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file)); + if (FlushFileBuffers(hFile) == 0) { + LogPrintf("%s: FlushFileBuffers failed: %d\n", __func__, GetLastError()); + return false; + } +#else + #if defined(__linux__) || defined(__NetBSD__) + if (fdatasync(fileno(file)) != 0 && errno != EINVAL) { // Ignore EINVAL for filesystems that don't support sync + LogPrintf("%s: fdatasync failed: %d\n", __func__, errno); + return false; + } + #elif defined(MAC_OSX) && defined(F_FULLFSYNC) + if (fcntl(fileno(file), F_FULLFSYNC, 0) == -1) { // Manpage says "value other than -1" is returned on success + LogPrintf("%s: fcntl F_FULLFSYNC failed: %d\n", __func__, errno); + return false; + } + #else + if (fsync(fileno(file)) != 0 && errno != EINVAL) { + LogPrintf("%s: fsync failed: %d\n", __func__, errno); + return false; + } + #endif +#endif + return true; +} + +bool TruncateFile(FILE *file, unsigned int length) { +#if defined(WIN32) + return _chsize(_fileno(file), length) == 0; +#else + return ftruncate(fileno(file), length) == 0; +#endif +} + +/** + * this function tries to raise the file descriptor limit to the requested number. + * It returns the actual file descriptor limit (which may be more or less than nMinFD) + */ +int RaiseFileDescriptorLimit(int nMinFD) { +#if defined(WIN32) + return 2048; +#else + struct rlimit limitFD; + if (getrlimit(RLIMIT_NOFILE, &limitFD) != -1) { + if (limitFD.rlim_cur < (rlim_t)nMinFD) { + limitFD.rlim_cur = nMinFD; + if (limitFD.rlim_cur > limitFD.rlim_max) + limitFD.rlim_cur = limitFD.rlim_max; + setrlimit(RLIMIT_NOFILE, &limitFD); + getrlimit(RLIMIT_NOFILE, &limitFD); + } + return limitFD.rlim_cur; + } + return nMinFD; // getrlimit failed, assume it's fine +#endif +} + +/** + * this function tries to make a particular range of a file allocated (corresponding to disk space) + * it is advisory, and the range specified in the arguments will never contain live data + */ +void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length) { +#if defined(WIN32) + // Windows-specific version + HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file)); + LARGE_INTEGER nFileSize; + int64_t nEndPos = (int64_t)offset + length; + nFileSize.u.LowPart = nEndPos & 0xFFFFFFFF; + nFileSize.u.HighPart = nEndPos >> 32; + SetFilePointerEx(hFile, nFileSize, 0, FILE_BEGIN); + SetEndOfFile(hFile); +#elif defined(MAC_OSX) + // OSX specific version + fstore_t fst; + fst.fst_flags = F_ALLOCATECONTIG; + fst.fst_posmode = F_PEOFPOSMODE; + fst.fst_offset = 0; + fst.fst_length = (off_t)offset + length; + fst.fst_bytesalloc = 0; + if (fcntl(fileno(file), F_PREALLOCATE, &fst) == -1) { + fst.fst_flags = F_ALLOCATEALL; + fcntl(fileno(file), F_PREALLOCATE, &fst); + } + ftruncate(fileno(file), fst.fst_length); +#elif defined(__linux__) + // Version using posix_fallocate + off_t nEndPos = (off_t)offset + length; + posix_fallocate(fileno(file), 0, nEndPos); +#else + // Fallback version + // TODO: just write one byte per block + static const char buf[65536] = {}; + if (fseek(file, offset, SEEK_SET)) { + return; + } + while (length > 0) { + unsigned int now = 65536; + if (length < now) + now = length; + fwrite(buf, 1, now, file); // allowed to fail; this function is advisory anyway + length -= now; + } +#endif +} + +#ifdef WIN32 +fs::path GetSpecialFolderPath(int nFolder, bool fCreate) +{ + WCHAR pszPath[MAX_PATH] = L""; + + if(SHGetSpecialFolderPathW(nullptr, pszPath, nFolder, fCreate)) + { + return fs::path(pszPath); + } + + LogPrintf("SHGetSpecialFolderPathW() failed, could not obtain requested path.\n"); + return fs::path(""); +} +#endif + +void runCommand(const std::string& strCommand) +{ + if (strCommand.empty()) return; +#ifndef WIN32 + int nErr = ::system(strCommand.c_str()); +#else + int nErr = ::_wsystem(std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>,wchar_t>().from_bytes(strCommand).c_str()); +#endif + if (nErr) + LogPrintf("runCommand error: system(%s) returned %d\n", strCommand, nErr); +} + +void RenameThread(const char* name) +{ +#if defined(PR_SET_NAME) + // Only the first 15 characters are used (16 - NUL terminator) + ::prctl(PR_SET_NAME, name, 0, 0, 0); +#elif (defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)) + pthread_set_name_np(pthread_self(), name); + +#elif defined(MAC_OSX) + pthread_setname_np(name); +#else + // Prevent warnings for unused parameters... + (void)name; +#endif +} + +void SetupEnvironment() +{ +#ifdef HAVE_MALLOPT_ARENA_MAX + // glibc-specific: On 32-bit systems set the number of arenas to 1. + // By default, since glibc 2.10, the C library will create up to two heap + // arenas per core. This is known to cause excessive virtual address space + // usage in our usage. Work around it by setting the maximum number of + // arenas to 1. + if (sizeof(void*) == 4) { + mallopt(M_ARENA_MAX, 1); + } +#endif + // On most POSIX systems (e.g. Linux, but not BSD) the environment's locale + // may be invalid, in which case the "C" locale is used as fallback. +#if !defined(WIN32) && !defined(MAC_OSX) && !defined(__FreeBSD__) && !defined(__OpenBSD__) + try { + std::locale(""); // Raises a runtime error if current locale is invalid + } catch (const std::runtime_error&) { + setenv("LC_ALL", "C", 1); + } +#elif defined(WIN32) + // Set the default input/output charset is utf-8 + SetConsoleCP(CP_UTF8); + SetConsoleOutputCP(CP_UTF8); +#endif + // The path locale is lazy initialized and to avoid deinitialization errors + // in multithreading environments, it is set explicitly by the main thread. + // A dummy locale is used to extract the internal default locale, used by + // fs::path, which is then used to explicitly imbue the path. + std::locale loc = fs::path::imbue(std::locale::classic()); +#ifndef WIN32 + fs::path::imbue(loc); +#else + fs::path::imbue(std::locale(loc, new std::codecvt_utf8_utf16<wchar_t>())); +#endif +} + +bool SetupNetworking() +{ +#ifdef WIN32 + // Initialize Windows Sockets + WSADATA wsadata; + int ret = WSAStartup(MAKEWORD(2,2), &wsadata); + if (ret != NO_ERROR || LOBYTE(wsadata.wVersion ) != 2 || HIBYTE(wsadata.wVersion) != 2) + return false; +#endif + return true; +} + +int GetNumCores() +{ + return std::thread::hardware_concurrency(); +} + +std::string CopyrightHolders(const std::string& strPrefix) +{ + std::string strCopyrightHolders = strPrefix + strprintf(_(COPYRIGHT_HOLDERS), _(COPYRIGHT_HOLDERS_SUBSTITUTION)); + + // Check for untranslated substitution to make sure Bitcoin Core copyright is not removed by accident + if (strprintf(COPYRIGHT_HOLDERS, COPYRIGHT_HOLDERS_SUBSTITUTION).find("Bitcoin Core") == std::string::npos) { + strCopyrightHolders += "\n" + strPrefix + "The Bitcoin Core developers"; + } + return strCopyrightHolders; +} + +// Obtain the application startup time (used for uptime calculation) +int64_t GetStartupTime() +{ + return nStartupTime; +} + +fs::path AbsPathForConfigVal(const fs::path& path, bool net_specific) +{ + return fs::absolute(path, GetDataDir(net_specific)); +} + +int ScheduleBatchPriority() +{ +#ifdef SCHED_BATCH + const static sched_param param{0}; + if (int ret = pthread_setschedparam(pthread_self(), SCHED_BATCH, ¶m)) { + LogPrintf("Failed to pthread_setschedparam: %s\n", strerror(errno)); + return ret; + } + return 0; +#else + return 1; +#endif +} + +namespace util { +#ifdef WIN32 +WinCmdLineArgs::WinCmdLineArgs() +{ + wchar_t** wargv = CommandLineToArgvW(GetCommandLineW(), &argc); + std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> utf8_cvt; + argv = new char*[argc]; + args.resize(argc); + for (int i = 0; i < argc; i++) { + args[i] = utf8_cvt.to_bytes(wargv[i]); + argv[i] = &*args[i].begin(); + } + LocalFree(wargv); +} + +WinCmdLineArgs::~WinCmdLineArgs() +{ + delete[] argv; +} + +std::pair<int, char**> WinCmdLineArgs::get() +{ + return std::make_pair(argc, argv); +} +#endif +} // namespace util diff --git a/src/util/system.h b/src/util/system.h new file mode 100644 index 0000000000..5634b8dd61 --- /dev/null +++ b/src/util/system.h @@ -0,0 +1,382 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +/** + * Server/client environment: argument handling, config file parsing, + * thread wrappers, startup time + */ +#ifndef BITCOIN_UTIL_SYSTEM_H +#define BITCOIN_UTIL_SYSTEM_H + +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + +#include <compat.h> +#include <fs.h> +#include <logging.h> +#include <sync.h> +#include <tinyformat.h> +#include <util/memory.h> +#include <util/time.h> + +#include <atomic> +#include <exception> +#include <map> +#include <set> +#include <stdint.h> +#include <string> +#include <unordered_set> +#include <utility> +#include <vector> + +#include <boost/thread/condition_variable.hpp> // for boost::thread_interrupted + +// Application startup time (used for uptime calculation) +int64_t GetStartupTime(); + +extern const char * const BITCOIN_CONF_FILENAME; +extern const char * const BITCOIN_PID_FILENAME; + +/** Translate a message to the native language of the user. */ +const extern std::function<std::string(const char*)> G_TRANSLATION_FUN; + +/** + * Translation function. + * If no translation function is set, simply return the input. + */ +inline std::string _(const char* psz) +{ + return G_TRANSLATION_FUN ? (G_TRANSLATION_FUN)(psz) : psz; +} + +void SetupEnvironment(); +bool SetupNetworking(); + +template<typename... Args> +bool error(const char* fmt, const Args&... args) +{ + LogPrintf("ERROR: %s\n", tfm::format(fmt, args...)); + return false; +} + +void PrintExceptionContinue(const std::exception *pex, const char* pszThread); +bool FileCommit(FILE *file); +bool TruncateFile(FILE *file, unsigned int length); +int RaiseFileDescriptorLimit(int nMinFD); +void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length); +bool RenameOver(fs::path src, fs::path dest); +bool LockDirectory(const fs::path& directory, const std::string lockfile_name, bool probe_only=false); +bool DirIsWritable(const fs::path& directory); + +/** Release all directory locks. This is used for unit testing only, at runtime + * the global destructor will take care of the locks. + */ +void ReleaseDirectoryLocks(); + +bool TryCreateDirectories(const fs::path& p); +fs::path GetDefaultDataDir(); +const fs::path &GetBlocksDir(bool fNetSpecific = true); +const fs::path &GetDataDir(bool fNetSpecific = true); +void ClearDatadirCache(); +fs::path GetConfigFile(const std::string& confPath); +#ifndef WIN32 +fs::path GetPidFile(); +void CreatePidFile(const fs::path &path, pid_t pid); +#endif +#ifdef WIN32 +fs::path GetSpecialFolderPath(int nFolder, bool fCreate = true); +#endif +void runCommand(const std::string& strCommand); + +/** + * Most paths passed as configuration arguments are treated as relative to + * the datadir if they are not absolute. + * + * @param path The path to be conditionally prefixed with datadir. + * @param net_specific Forwarded to GetDataDir(). + * @return The normalized path. + */ +fs::path AbsPathForConfigVal(const fs::path& path, bool net_specific = true); + +inline bool IsSwitchChar(char c) +{ +#ifdef WIN32 + return c == '-' || c == '/'; +#else + return c == '-'; +#endif +} + +enum class OptionsCategory { + OPTIONS, + CONNECTION, + WALLET, + WALLET_DEBUG_TEST, + ZMQ, + DEBUG_TEST, + CHAINPARAMS, + NODE_RELAY, + BLOCK_CREATION, + RPC, + GUI, + COMMANDS, + REGISTER_COMMANDS, + + HIDDEN // Always the last option to avoid printing these in the help +}; + +class ArgsManager +{ +protected: + friend class ArgsManagerHelper; + + struct Arg + { + std::string m_help_param; + std::string m_help_text; + bool m_debug_only; + + Arg(const std::string& help_param, const std::string& help_text, bool debug_only) : m_help_param(help_param), m_help_text(help_text), m_debug_only(debug_only) {}; + }; + + mutable CCriticalSection cs_args; + std::map<std::string, std::vector<std::string>> m_override_args GUARDED_BY(cs_args); + std::map<std::string, std::vector<std::string>> m_config_args GUARDED_BY(cs_args); + std::string m_network GUARDED_BY(cs_args); + std::set<std::string> m_network_only_args GUARDED_BY(cs_args); + std::map<OptionsCategory, std::map<std::string, Arg>> m_available_args GUARDED_BY(cs_args); + + bool ReadConfigStream(std::istream& stream, std::string& error, bool ignore_invalid_keys = false); + +public: + ArgsManager(); + + /** + * Select the network in use + */ + void SelectConfigNetwork(const std::string& network); + + bool ParseParameters(int argc, const char* const argv[], std::string& error); + bool ReadConfigFiles(std::string& error, bool ignore_invalid_keys = false); + + /** + * Log warnings for options in m_section_only_args when + * they are specified in the default section but not overridden + * on the command line or in a network-specific section in the + * config file. + */ + void WarnForSectionOnlyArgs(); + + /** + * Return a vector of strings of the given argument + * + * @param strArg Argument to get (e.g. "-foo") + * @return command-line arguments + */ + std::vector<std::string> GetArgs(const std::string& strArg) const; + + /** + * Return true if the given argument has been manually set + * + * @param strArg Argument to get (e.g. "-foo") + * @return true if the argument has been set + */ + bool IsArgSet(const std::string& strArg) const; + + /** + * Return true if the argument was originally passed as a negated option, + * i.e. -nofoo. + * + * @param strArg Argument to get (e.g. "-foo") + * @return true if the argument was passed negated + */ + bool IsArgNegated(const std::string& strArg) const; + + /** + * Return string argument or default value + * + * @param strArg Argument to get (e.g. "-foo") + * @param strDefault (e.g. "1") + * @return command-line argument or default value + */ + std::string GetArg(const std::string& strArg, const std::string& strDefault) const; + + /** + * Return integer argument or default value + * + * @param strArg Argument to get (e.g. "-foo") + * @param nDefault (e.g. 1) + * @return command-line argument (0 if invalid number) or default value + */ + int64_t GetArg(const std::string& strArg, int64_t nDefault) const; + + /** + * Return boolean argument or default value + * + * @param strArg Argument to get (e.g. "-foo") + * @param fDefault (true or false) + * @return command-line argument or default value + */ + bool GetBoolArg(const std::string& strArg, bool fDefault) const; + + /** + * Set an argument if it doesn't already have a value + * + * @param strArg Argument to set (e.g. "-foo") + * @param strValue Value (e.g. "1") + * @return true if argument gets set, false if it already had a value + */ + bool SoftSetArg(const std::string& strArg, const std::string& strValue); + + /** + * Set a boolean argument if it doesn't already have a value + * + * @param strArg Argument to set (e.g. "-foo") + * @param fValue Value (e.g. false) + * @return true if argument gets set, false if it already had a value + */ + bool SoftSetBoolArg(const std::string& strArg, bool fValue); + + // Forces an arg setting. Called by SoftSetArg() if the arg hasn't already + // been set. Also called directly in testing. + void ForceSetArg(const std::string& strArg, const std::string& strValue); + + /** + * Looks for -regtest, -testnet and returns the appropriate BIP70 chain name. + * @return CBaseChainParams::MAIN by default; raises runtime error if an invalid combination is given. + */ + std::string GetChainName() const; + + /** + * Add argument + */ + void AddArg(const std::string& name, const std::string& help, const bool debug_only, const OptionsCategory& cat); + + /** + * Add many hidden arguments + */ + void AddHiddenArgs(const std::vector<std::string>& args); + + /** + * Clear available arguments + */ + void ClearArgs() { + LOCK(cs_args); + m_available_args.clear(); + } + + /** + * Get the help string + */ + std::string GetHelpMessage() const; + + /** + * Check whether we know of this arg + */ + bool IsArgKnown(const std::string& key) const; +}; + +extern ArgsManager gArgs; + +/** + * @return true if help has been requested via a command-line arg + */ +bool HelpRequested(const ArgsManager& args); + +/** + * Format a string to be used as group of options in help messages + * + * @param message Group name (e.g. "RPC server options:") + * @return the formatted string + */ +std::string HelpMessageGroup(const std::string& message); + +/** + * Format a string to be used as option description in help messages + * + * @param option Option message (e.g. "-rpcuser=<user>") + * @param message Option description (e.g. "Username for JSON-RPC connections") + * @return the formatted string + */ +std::string HelpMessageOpt(const std::string& option, const std::string& message); + +/** + * Return the number of cores available on the current system. + * @note This does count virtual cores, such as those provided by HyperThreading. + */ +int GetNumCores(); + +void RenameThread(const char* name); + +/** + * .. and a wrapper that just calls func once + */ +template <typename Callable> void TraceThread(const char* name, Callable func) +{ + std::string s = strprintf("bitcoin-%s", name); + RenameThread(s.c_str()); + try + { + LogPrintf("%s thread start\n", name); + func(); + LogPrintf("%s thread exit\n", name); + } + catch (const boost::thread_interrupted&) + { + LogPrintf("%s thread interrupt\n", name); + throw; + } + catch (const std::exception& e) { + PrintExceptionContinue(&e, name); + throw; + } + catch (...) { + PrintExceptionContinue(nullptr, name); + throw; + } +} + +std::string CopyrightHolders(const std::string& strPrefix); + +/** + * On platforms that support it, tell the kernel the calling thread is + * CPU-intensive and non-interactive. See SCHED_BATCH in sched(7) for details. + * + * @return The return value of sched_setschedule(), or 1 on systems without + * sched_setschedule(). + */ +int ScheduleBatchPriority(); + +namespace util { + +//! Simplification of std insertion +template <typename Tdst, typename Tsrc> +inline void insert(Tdst& dst, const Tsrc& src) { + dst.insert(dst.begin(), src.begin(), src.end()); +} +template <typename TsetT, typename Tsrc> +inline void insert(std::set<TsetT>& dst, const Tsrc& src) { + dst.insert(src.begin(), src.end()); +} + +#ifdef WIN32 +class WinCmdLineArgs +{ +public: + WinCmdLineArgs(); + ~WinCmdLineArgs(); + std::pair<int, char**> get(); + +private: + int argc; + char** argv; + std::vector<std::string> args; +}; +#endif + +} // namespace util + +#endif // BITCOIN_UTIL_SYSTEM_H diff --git a/src/util/time.cpp b/src/util/time.cpp new file mode 100644 index 0000000000..83a7937d8f --- /dev/null +++ b/src/util/time.cpp @@ -0,0 +1,110 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2018 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 <util/time.h> + +#include <atomic> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/thread.hpp> +#include <ctime> +#include <tinyformat.h> + +static std::atomic<int64_t> nMockTime(0); //!< For unit testing + +int64_t GetTime() +{ + int64_t mocktime = nMockTime.load(std::memory_order_relaxed); + if (mocktime) return mocktime; + + time_t now = time(nullptr); + assert(now > 0); + return now; +} + +void SetMockTime(int64_t nMockTimeIn) +{ + nMockTime.store(nMockTimeIn, std::memory_order_relaxed); +} + +int64_t GetMockTime() +{ + return 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; +} + +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; +} + +int64_t GetSystemTimeInSeconds() +{ + return GetTimeMicros()/1000000; +} + +void MilliSleep(int64_t n) +{ + +/** + * Boost's sleep_for was uninterruptible when backed by nanosleep from 1.50 + * until fixed in 1.52. Use the deprecated sleep method for the broken case. + * See: https://svn.boost.org/trac/boost/ticket/7238 + */ +#if defined(HAVE_WORKING_BOOST_SLEEP_FOR) + boost::this_thread::sleep_for(boost::chrono::milliseconds(n)); +#elif defined(HAVE_WORKING_BOOST_SLEEP) + boost::this_thread::sleep(boost::posix_time::milliseconds(n)); +#else +//should never get here +#error missing boost sleep implementation +#endif +} + +std::string FormatISO8601DateTime(int64_t nTime) { + struct tm ts; + time_t time_val = nTime; +#ifdef _MSC_VER + gmtime_s(&ts, &time_val); +#else + gmtime_r(&time_val, &ts); +#endif + return strprintf("%04i-%02i-%02iT%02i:%02i:%02iZ", ts.tm_year + 1900, ts.tm_mon + 1, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec); +} + +std::string FormatISO8601Date(int64_t nTime) { + struct tm ts; + time_t time_val = nTime; +#ifdef _MSC_VER + gmtime_s(&ts, &time_val); +#else + gmtime_r(&time_val, &ts); +#endif + return strprintf("%04i-%02i-%02i", ts.tm_year + 1900, ts.tm_mon + 1, ts.tm_mday); +} + +std::string FormatISO8601Time(int64_t nTime) { + struct tm ts; + time_t time_val = nTime; +#ifdef _MSC_VER + gmtime_s(&ts, &time_val); +#else + gmtime_r(&time_val, &ts); +#endif + return strprintf("%02i:%02i:%02iZ", ts.tm_hour, ts.tm_min, ts.tm_sec); +} diff --git a/src/util/time.h b/src/util/time.h new file mode 100644 index 0000000000..f2e2747434 --- /dev/null +++ b/src/util/time.h @@ -0,0 +1,38 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2018 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_TIME_H +#define BITCOIN_UTIL_TIME_H + +#include <stdint.h> +#include <string> + +/** + * GetTimeMicros() and GetTimeMillis() both return the system time, but in + * different units. GetTime() returns the system time in seconds, but also + * supports mocktime, where the time can be specified by the user, eg for + * testing (eg with the setmocktime rpc, or -mocktime argument). + * + * TODO: Rework these functions to be type-safe (so that we don't inadvertently + * compare numbers with different units, or compare a mocktime to system time). + */ + +int64_t GetTime(); +int64_t GetTimeMillis(); +int64_t GetTimeMicros(); +int64_t GetSystemTimeInSeconds(); // Like GetTime(), but not mockable +void SetMockTime(int64_t nMockTimeIn); +int64_t GetMockTime(); +void MilliSleep(int64_t n); + +/** + * ISO 8601 formatting is preferred. Use the FormatISO8601{DateTime,Date,Time} + * helper functions if possible. + */ +std::string FormatISO8601DateTime(int64_t nTime); +std::string FormatISO8601Date(int64_t nTime); +std::string FormatISO8601Time(int64_t nTime); + +#endif // BITCOIN_UTIL_TIME_H |