mirror of
https://github.com/bitcoin/bitcoin.git
synced 2024-05-17 23:56:39 +00:00
Merge #13878: utils: Add fstream wrapper to allow to pass unicode filename on Windows
43c7fbb1e79a4a2219306bf3da1a2dfdf9213f2c Make MSVC compiler read the source code using utf-8 (Chun Kuan Lee) f86a571edb9627c126b9ccd7da68bd7d1657b8f8 tests: Add test case for std::ios_base::ate (Chun Kuan Lee) a554cc901a32f41162089d6b20ad39d5aeff0583 Move boost/std fstream to fsbridge (Chun Kuan Lee) 86eb3b3f1ab594142b6baa9576717ff121f3b745 utils: Add fsbridge fstream function wrapper (Chun Kuan Lee) Pull request description: If compiled with mingw, use glibc++ extension `stdio_filebuf` to open the file by `FILE*` instead of filename. In other condition, we can use boost::fstream. Tree-SHA512: b5dbd83e347fb9b2a0c8b1c2c7bd71a272e839ec0617883b2a0ec12506ae9e825373cf6e95b9bcc91d7edc85bf51580a7716b56a9ecaad776bc3ae61638cb3da
This commit is contained in:
commit
9c5f0d542d
@ -12,4 +12,9 @@
|
|||||||
Outputs="$(MSBuildThisFileDirectory)..\src\config\bitcoin-config.h">
|
Outputs="$(MSBuildThisFileDirectory)..\src\config\bitcoin-config.h">
|
||||||
<Copy SourceFiles="$(MSBuildThisFileDirectory)bitcoin_config.h" DestinationFiles="$(MSBuildThisFileDirectory)..\src\config\bitcoin-config.h" />
|
<Copy SourceFiles="$(MSBuildThisFileDirectory)bitcoin_config.h" DestinationFiles="$(MSBuildThisFileDirectory)..\src\config\bitcoin-config.h" />
|
||||||
</Target>
|
</Target>
|
||||||
|
<ItemDefinitionGroup>
|
||||||
|
<ClCompile>
|
||||||
|
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
|
||||||
|
</ClCompile>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@ -51,6 +51,7 @@ BITCOIN_TESTS =\
|
|||||||
test/cuckoocache_tests.cpp \
|
test/cuckoocache_tests.cpp \
|
||||||
test/denialofservice_tests.cpp \
|
test/denialofservice_tests.cpp \
|
||||||
test/descriptor_tests.cpp \
|
test/descriptor_tests.cpp \
|
||||||
|
test/fs_tests.cpp \
|
||||||
test/getarg_tests.cpp \
|
test/getarg_tests.cpp \
|
||||||
test/hash_tests.cpp \
|
test/hash_tests.cpp \
|
||||||
test/key_io_tests.cpp \
|
test/key_io_tests.cpp \
|
||||||
|
|||||||
102
src/fs.cpp
102
src/fs.cpp
@ -113,4 +113,106 @@ std::string get_filesystem_error_message(const fs::filesystem_error& e)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef WIN32
|
||||||
|
#ifdef __GLIBCXX__
|
||||||
|
|
||||||
|
// reference: https://github.com/gcc-mirror/gcc/blob/gcc-7_3_0-release/libstdc%2B%2B-v3/include/std/fstream#L270
|
||||||
|
|
||||||
|
static std::string openmodeToStr(std::ios_base::openmode mode)
|
||||||
|
{
|
||||||
|
switch (mode & ~std::ios_base::ate) {
|
||||||
|
case std::ios_base::out:
|
||||||
|
case std::ios_base::out | std::ios_base::trunc:
|
||||||
|
return "w";
|
||||||
|
case std::ios_base::out | std::ios_base::app:
|
||||||
|
case std::ios_base::app:
|
||||||
|
return "a";
|
||||||
|
case std::ios_base::in:
|
||||||
|
return "r";
|
||||||
|
case std::ios_base::in | std::ios_base::out:
|
||||||
|
return "r+";
|
||||||
|
case std::ios_base::in | std::ios_base::out | std::ios_base::trunc:
|
||||||
|
return "w+";
|
||||||
|
case std::ios_base::in | std::ios_base::out | std::ios_base::app:
|
||||||
|
case std::ios_base::in | std::ios_base::app:
|
||||||
|
return "a+";
|
||||||
|
case std::ios_base::out | std::ios_base::binary:
|
||||||
|
case std::ios_base::out | std::ios_base::trunc | std::ios_base::binary:
|
||||||
|
return "wb";
|
||||||
|
case std::ios_base::out | std::ios_base::app | std::ios_base::binary:
|
||||||
|
case std::ios_base::app | std::ios_base::binary:
|
||||||
|
return "ab";
|
||||||
|
case std::ios_base::in | std::ios_base::binary:
|
||||||
|
return "rb";
|
||||||
|
case std::ios_base::in | std::ios_base::out | std::ios_base::binary:
|
||||||
|
return "r+b";
|
||||||
|
case std::ios_base::in | std::ios_base::out | std::ios_base::trunc | std::ios_base::binary:
|
||||||
|
return "w+b";
|
||||||
|
case std::ios_base::in | std::ios_base::out | std::ios_base::app | std::ios_base::binary:
|
||||||
|
case std::ios_base::in | std::ios_base::app | std::ios_base::binary:
|
||||||
|
return "a+b";
|
||||||
|
default:
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ifstream::open(const fs::path& p, std::ios_base::openmode mode)
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
m_file = fsbridge::fopen(p, openmodeToStr(mode).c_str());
|
||||||
|
if (m_file == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_filebuf = __gnu_cxx::stdio_filebuf<char>(m_file, mode);
|
||||||
|
rdbuf(&m_filebuf);
|
||||||
|
if (mode & std::ios_base::ate) {
|
||||||
|
seekg(0, std::ios_base::end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ifstream::close()
|
||||||
|
{
|
||||||
|
if (m_file != nullptr) {
|
||||||
|
m_filebuf.close();
|
||||||
|
fclose(m_file);
|
||||||
|
}
|
||||||
|
m_file = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ofstream::open(const fs::path& p, std::ios_base::openmode mode)
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
m_file = fsbridge::fopen(p, openmodeToStr(mode).c_str());
|
||||||
|
if (m_file == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_filebuf = __gnu_cxx::stdio_filebuf<char>(m_file, mode);
|
||||||
|
rdbuf(&m_filebuf);
|
||||||
|
if (mode & std::ios_base::ate) {
|
||||||
|
seekp(0, std::ios_base::end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ofstream::close()
|
||||||
|
{
|
||||||
|
if (m_file != nullptr) {
|
||||||
|
m_filebuf.close();
|
||||||
|
fclose(m_file);
|
||||||
|
}
|
||||||
|
m_file = nullptr;
|
||||||
|
}
|
||||||
|
#else // __GLIBCXX__
|
||||||
|
|
||||||
|
static_assert(sizeof(*fs::path().BOOST_FILESYSTEM_C_STR) == sizeof(wchar_t),
|
||||||
|
"Warning: This build is using boost::filesystem ofstream and ifstream "
|
||||||
|
"implementations which will fail to open paths containing multibyte "
|
||||||
|
"characters. You should delete this static_assert to ignore this warning, "
|
||||||
|
"or switch to a different C++ standard library like the Microsoft C++ "
|
||||||
|
"Standard Library (where boost uses non-standard extensions to construct "
|
||||||
|
"stream objects with wide filenames), or the GNU libstdc++ library (where "
|
||||||
|
"a more complicated workaround has been implemented above).");
|
||||||
|
|
||||||
|
#endif // __GLIBCXX__
|
||||||
|
#endif // WIN32
|
||||||
|
|
||||||
} // fsbridge
|
} // fsbridge
|
||||||
|
|||||||
51
src/fs.h
51
src/fs.h
@ -7,6 +7,9 @@
|
|||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#if defined WIN32 && defined __GLIBCXX__
|
||||||
|
#include <ext/stdio_filebuf.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
#include <boost/filesystem/fstream.hpp>
|
#include <boost/filesystem/fstream.hpp>
|
||||||
@ -39,6 +42,54 @@ namespace fsbridge {
|
|||||||
};
|
};
|
||||||
|
|
||||||
std::string get_filesystem_error_message(const fs::filesystem_error& e);
|
std::string get_filesystem_error_message(const fs::filesystem_error& e);
|
||||||
|
|
||||||
|
// GNU libstdc++ specific workaround for opening UTF-8 paths on Windows.
|
||||||
|
//
|
||||||
|
// On Windows, it is only possible to reliably access multibyte file paths through
|
||||||
|
// `wchar_t` APIs, not `char` APIs. But because the C++ standard doesn't
|
||||||
|
// require ifstream/ofstream `wchar_t` constructors, and the GNU library doesn't
|
||||||
|
// provide them (in contrast to the Microsoft C++ library, see
|
||||||
|
// https://stackoverflow.com/questions/821873/how-to-open-an-stdfstream-ofstream-or-ifstream-with-a-unicode-filename/822032#822032),
|
||||||
|
// Boost is forced to fall back to `char` constructors which may not work properly.
|
||||||
|
//
|
||||||
|
// Work around this issue by creating stream objects with `_wfopen` in
|
||||||
|
// combination with `__gnu_cxx::stdio_filebuf`. This workaround can be removed
|
||||||
|
// with an upgrade to C++17, where streams can be constructed directly from
|
||||||
|
// `std::filesystem::path` objects.
|
||||||
|
|
||||||
|
#if defined WIN32 && defined __GLIBCXX__
|
||||||
|
class ifstream : public std::istream
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ifstream() = default;
|
||||||
|
explicit ifstream(const fs::path& p, std::ios_base::openmode mode = std::ios_base::in) { open(p, mode); }
|
||||||
|
~ifstream() { close(); }
|
||||||
|
void open(const fs::path& p, std::ios_base::openmode mode = std::ios_base::in);
|
||||||
|
bool is_open() { return m_filebuf.is_open(); }
|
||||||
|
void close();
|
||||||
|
|
||||||
|
private:
|
||||||
|
__gnu_cxx::stdio_filebuf<char> m_filebuf;
|
||||||
|
FILE* m_file = nullptr;
|
||||||
|
};
|
||||||
|
class ofstream : public std::ostream
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ofstream() = default;
|
||||||
|
explicit ofstream(const fs::path& p, std::ios_base::openmode mode = std::ios_base::out) { open(p, mode); }
|
||||||
|
~ofstream() { close(); }
|
||||||
|
void open(const fs::path& p, std::ios_base::openmode mode = std::ios_base::out);
|
||||||
|
bool is_open() { return m_filebuf.is_open(); }
|
||||||
|
void close();
|
||||||
|
|
||||||
|
private:
|
||||||
|
__gnu_cxx::stdio_filebuf<char> m_filebuf;
|
||||||
|
FILE* m_file = nullptr;
|
||||||
|
};
|
||||||
|
#else // !(WIN32 && __GLIBCXX__)
|
||||||
|
typedef fs::ifstream ifstream;
|
||||||
|
typedef fs::ofstream ofstream;
|
||||||
|
#endif // WIN32 && __GLIBCXX__
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // BITCOIN_FS_H
|
#endif // BITCOIN_FS_H
|
||||||
|
|||||||
@ -367,7 +367,7 @@ bool openBitcoinConf()
|
|||||||
fs::path pathConfig = GetConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME));
|
fs::path pathConfig = GetConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME));
|
||||||
|
|
||||||
/* Create the file */
|
/* Create the file */
|
||||||
fs::ofstream configFile(pathConfig, std::ios_base::app);
|
fsbridge::ofstream configFile(pathConfig, std::ios_base::app);
|
||||||
|
|
||||||
if (!configFile.good())
|
if (!configFile.good())
|
||||||
return false;
|
return false;
|
||||||
@ -611,7 +611,7 @@ fs::path static GetAutostartFilePath()
|
|||||||
|
|
||||||
bool GetStartOnSystemStartup()
|
bool GetStartOnSystemStartup()
|
||||||
{
|
{
|
||||||
fs::ifstream optionFile(GetAutostartFilePath());
|
fsbridge::ifstream optionFile(GetAutostartFilePath());
|
||||||
if (!optionFile.good())
|
if (!optionFile.good())
|
||||||
return false;
|
return false;
|
||||||
// Scan through file for "Hidden=true":
|
// Scan through file for "Hidden=true":
|
||||||
@ -642,7 +642,7 @@ bool SetStartOnSystemStartup(bool fAutoStart)
|
|||||||
|
|
||||||
fs::create_directories(GetAutostartDir());
|
fs::create_directories(GetAutostartDir());
|
||||||
|
|
||||||
fs::ofstream optionFile(GetAutostartFilePath(), std::ios_base::out|std::ios_base::trunc);
|
fsbridge::ofstream optionFile(GetAutostartFilePath(), std::ios_base::out | std::ios_base::trunc);
|
||||||
if (!optionFile.good())
|
if (!optionFile.good())
|
||||||
return false;
|
return false;
|
||||||
std::string chain = gArgs.GetChainName();
|
std::string chain = gArgs.GetChainName();
|
||||||
|
|||||||
@ -12,8 +12,6 @@
|
|||||||
#include <utiltime.h>
|
#include <utiltime.h>
|
||||||
#include <version.h>
|
#include <version.h>
|
||||||
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JSON-RPC protocol. Bitcoin speaks version 1.0 for maximum compatibility,
|
* JSON-RPC protocol. Bitcoin speaks version 1.0 for maximum compatibility,
|
||||||
* but uses JSON-RPC 1.1/2.0 standards for parts of the 1.0 standard that were
|
* but uses JSON-RPC 1.1/2.0 standards for parts of the 1.0 standard that were
|
||||||
@ -85,9 +83,9 @@ bool GenerateAuthCookie(std::string *cookie_out)
|
|||||||
/** the umask determines what permissions are used to create this file -
|
/** the umask determines what permissions are used to create this file -
|
||||||
* these are set to 077 in init.cpp unless overridden with -sysperms.
|
* these are set to 077 in init.cpp unless overridden with -sysperms.
|
||||||
*/
|
*/
|
||||||
std::ofstream file;
|
fsbridge::ofstream file;
|
||||||
fs::path filepath_tmp = GetAuthCookieFile(true);
|
fs::path filepath_tmp = GetAuthCookieFile(true);
|
||||||
file.open(filepath_tmp.string().c_str());
|
file.open(filepath_tmp);
|
||||||
if (!file.is_open()) {
|
if (!file.is_open()) {
|
||||||
LogPrintf("Unable to open cookie authentication file %s for writing\n", filepath_tmp.string());
|
LogPrintf("Unable to open cookie authentication file %s for writing\n", filepath_tmp.string());
|
||||||
return false;
|
return false;
|
||||||
@ -109,10 +107,10 @@ bool GenerateAuthCookie(std::string *cookie_out)
|
|||||||
|
|
||||||
bool GetAuthCookie(std::string *cookie_out)
|
bool GetAuthCookie(std::string *cookie_out)
|
||||||
{
|
{
|
||||||
std::ifstream file;
|
fsbridge::ifstream file;
|
||||||
std::string cookie;
|
std::string cookie;
|
||||||
fs::path filepath = GetAuthCookieFile();
|
fs::path filepath = GetAuthCookieFile();
|
||||||
file.open(filepath.string().c_str());
|
file.open(filepath);
|
||||||
if (!file.is_open())
|
if (!file.is_open())
|
||||||
return false;
|
return false;
|
||||||
std::getline(file, cookie);
|
std::getline(file, cookie);
|
||||||
|
|||||||
56
src/test/fs_tests.cpp
Normal file
56
src/test/fs_tests.cpp
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// Copyright (c) 2011-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 <fs.h>
|
||||||
|
#include <test/test_bitcoin.h>
|
||||||
|
|
||||||
|
#include <boost/test/unit_test.hpp>
|
||||||
|
|
||||||
|
BOOST_FIXTURE_TEST_SUITE(fs_tests, BasicTestingSetup)
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(fsbridge_fstream)
|
||||||
|
{
|
||||||
|
fs::path tmpfolder = SetDataDir("fsbridge_fstream");
|
||||||
|
// tmpfile1 should be the same as tmpfile2
|
||||||
|
fs::path tmpfile1 = tmpfolder / "fs_tests_₿_🏃";
|
||||||
|
fs::path tmpfile2 = tmpfolder / L"fs_tests_₿_🏃";
|
||||||
|
{
|
||||||
|
fsbridge::ofstream file(tmpfile1);
|
||||||
|
file << "bitcoin";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
fsbridge::ifstream file(tmpfile2);
|
||||||
|
std::string input_buffer;
|
||||||
|
file >> input_buffer;
|
||||||
|
BOOST_CHECK_EQUAL(input_buffer, "bitcoin");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
fsbridge::ifstream file(tmpfile1, std::ios_base::in | std::ios_base::ate);
|
||||||
|
std::string input_buffer;
|
||||||
|
file >> input_buffer;
|
||||||
|
BOOST_CHECK_EQUAL(input_buffer, "");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
fsbridge::ofstream file(tmpfile2, std::ios_base::out | std::ios_base::app);
|
||||||
|
file << "tests";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
fsbridge::ifstream file(tmpfile1);
|
||||||
|
std::string input_buffer;
|
||||||
|
file >> input_buffer;
|
||||||
|
BOOST_CHECK_EQUAL(input_buffer, "bitcointests");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
fsbridge::ofstream file(tmpfile2, std::ios_base::out | std::ios_base::trunc);
|
||||||
|
file << "bitcoin";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
fsbridge::ifstream file(tmpfile1);
|
||||||
|
std::string input_buffer;
|
||||||
|
file >> input_buffer;
|
||||||
|
BOOST_CHECK_EQUAL(input_buffer, "bitcoin");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
@ -892,7 +892,7 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const std::string confPath = GetArg("-conf", BITCOIN_CONF_FILENAME);
|
const std::string confPath = GetArg("-conf", BITCOIN_CONF_FILENAME);
|
||||||
fs::ifstream stream(GetConfigFile(confPath));
|
fsbridge::ifstream stream(GetConfigFile(confPath));
|
||||||
|
|
||||||
// ok to not have a config file
|
// ok to not have a config file
|
||||||
if (stream.good()) {
|
if (stream.good()) {
|
||||||
@ -925,7 +925,7 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys)
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const std::string& to_include : includeconf) {
|
for (const std::string& to_include : includeconf) {
|
||||||
fs::ifstream include_config(GetConfigFile(to_include));
|
fsbridge::ifstream include_config(GetConfigFile(to_include));
|
||||||
if (include_config.good()) {
|
if (include_config.good()) {
|
||||||
if (!ReadConfigStream(include_config, error, ignore_invalid_keys)) {
|
if (!ReadConfigStream(include_config, error, ignore_invalid_keys)) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
#include <wallet/rpcwallet.h>
|
#include <wallet/rpcwallet.h>
|
||||||
|
|
||||||
#include <fstream>
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
@ -540,8 +539,8 @@ UniValue importwallet(const JSONRPCRequest& request)
|
|||||||
|
|
||||||
EnsureWalletIsUnlocked(pwallet);
|
EnsureWalletIsUnlocked(pwallet);
|
||||||
|
|
||||||
std::ifstream file;
|
fsbridge::ifstream file;
|
||||||
file.open(request.params[0].get_str().c_str(), std::ios::in | std::ios::ate);
|
file.open(request.params[0].get_str(), std::ios::in | std::ios::ate);
|
||||||
if (!file.is_open()) {
|
if (!file.is_open()) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
|
||||||
}
|
}
|
||||||
@ -717,8 +716,8 @@ UniValue dumpwallet(const JSONRPCRequest& request)
|
|||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, filepath.string() + " already exists. If you are sure this is what you want, move it out of the way first");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, filepath.string() + " already exists. If you are sure this is what you want, move it out of the way first");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ofstream file;
|
fsbridge::ofstream file;
|
||||||
file.open(filepath.string().c_str());
|
file.open(filepath);
|
||||||
if (!file.is_open())
|
if (!file.is_open())
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user