Merge bitcoin/bitcoin#33550: Fix windows libc++ fs::path fstream compile errors

c864a4c194 Simplify fs::path by dropping filename() and make_preferred() overloads (Ryan Ofsky)
b0113afd44 Fix windows libc++ fs::path fstream compile errors (Ryan Ofsky)

Pull request description:

  Drop support for passing `fs::path` directly to `std::ifstream` and `std::ofstream` constructors and `open()` functions, because as reported by hebasto in https://github.com/bitcoin/bitcoin/issues/33545, after https://wg21.link/lwg3430 there is no way this can continue to work in windows builds, and there are already compile errors compiling for windows with newer versions of libc++.

  Instead, add an `fs::path::std_path()` method that returns `std::filesystem::path` references and use it where needed.

ACKs for top commit:
  hebasto:
    ACK c864a4c194.
  l0rinc:
    Code review ACK c864a4c194
  maflcko:
    re-ACK c864a4c194 🌥

Tree-SHA512: d22372692ab86244e2b2caf4c5e9c9acbd9ba38df5411606b75e428474eabead152fc7ca1afe0bb0df6b818351211a70487e94b40a17b68db5aa757604a0ddf6
This commit is contained in:
Hennadii Stepanov committed 2025-10-22 10:10:27 +02:00
commit 3fee0754a2
17 files changed
+45 -40

No files matched your search

+1 -1
View File
@@ -56,7 +56,7 @@ void GenerateTemplateResults(const std::vector<ankerl::nanobench::Result>& bench
// nothing to write, bail out // nothing to write, bail out
return; return;
} }
std::ofstream fout{file}; std::ofstream fout{file.std_path()};
if (fout.is_open()) { if (fout.is_open()) {
ankerl::nanobench::render(tpl, benchmarkResults, fout); ankerl::nanobench::render(tpl, benchmarkResults, fout);
std::cout << "Created " << file << std::endl; std::cout << "Created " << file << std::endl;
+2 -2
View File
@@ -209,7 +209,7 @@ static void ExecCommand(const std::vector<const char*>& args, std::string_view w
// Try to resolve any symlinks and figure out the directory containing the wrapper executable. // Try to resolve any symlinks and figure out the directory containing the wrapper executable.
std::error_code ec; std::error_code ec;
fs::path wrapper_dir{fs::weakly_canonical(wrapper_path, ec)}; auto wrapper_dir{fs::weakly_canonical(wrapper_path, ec)};
if (wrapper_dir.empty()) wrapper_dir = wrapper_path; // Restore previous path if weakly_canonical failed. if (wrapper_dir.empty()) wrapper_dir = wrapper_path; // Restore previous path if weakly_canonical failed.
wrapper_dir = wrapper_dir.parent_path(); wrapper_dir = wrapper_dir.parent_path();
@@ -225,7 +225,7 @@ static void ExecCommand(const std::vector<const char*>& args, std::string_view w
// If wrapper is installed in a bin/ directory, look for target executable // If wrapper is installed in a bin/ directory, look for target executable
// in libexec/ // in libexec/
(wrapper_dir.filename() == "bin" && try_exec(fs::path{wrapper_dir.parent_path()} / "libexec" / arg0.filename())) || (wrapper_dir.filename() == "bin" && try_exec(wrapper_dir.parent_path() / "libexec" / arg0.filename())) ||
#ifdef WIN32 #ifdef WIN32
// Otherwise check the "daemon" subdirectory in a windows install. // Otherwise check the "daemon" subdirectory in a windows install.
(!wrapper_dir.empty() && try_exec(wrapper_dir / "daemon" / arg0.filename())) || (!wrapper_dir.empty() && try_exec(wrapper_dir / "daemon" / arg0.filename())) ||
+2 -2
View File
@@ -135,7 +135,7 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys)
error = strprintf("Config file \"%s\" is a directory.", fs::PathToString(conf_path)); error = strprintf("Config file \"%s\" is a directory.", fs::PathToString(conf_path));
return false; return false;
} }
stream = std::ifstream{conf_path}; stream = std::ifstream{conf_path.std_path()};
// If the file is explicitly specified, it must be readable // If the file is explicitly specified, it must be readable
if (IsArgSet("-conf") && !stream.good()) { if (IsArgSet("-conf") && !stream.good()) {
error = strprintf("specified config file \"%s\" could not be opened.", fs::PathToString(conf_path)); error = strprintf("specified config file \"%s\" could not be opened.", fs::PathToString(conf_path));
@@ -187,7 +187,7 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys)
error = strprintf("Included config file \"%s\" is a directory.", fs::PathToString(include_conf_path)); error = strprintf("Included config file \"%s\" is a directory.", fs::PathToString(include_conf_path));
return false; return false;
} }
std::ifstream conf_file_stream{include_conf_path}; std::ifstream conf_file_stream{include_conf_path.std_path()};
if (conf_file_stream.good()) { if (conf_file_stream.good()) {
if (!ReadConfigStream(conf_file_stream, conf_file_name, error, ignore_invalid_keys)) { if (!ReadConfigStream(conf_file_stream, conf_file_name, error, ignore_invalid_keys)) {
return false; return false;
+2 -2
View File
@@ -78,7 +78,7 @@ bool ReadSettings(const fs::path& path, std::map<std::string, SettingsValue>& va
if (!fs::exists(path)) return true; if (!fs::exists(path)) return true;
std::ifstream file; std::ifstream file;
file.open(path); file.open(path.std_path());
if (!file.is_open()) { if (!file.is_open()) {
errors.emplace_back(strprintf("%s. Please check permissions.", fs::PathToString(path))); errors.emplace_back(strprintf("%s. Please check permissions.", fs::PathToString(path)));
return false; return false;
@@ -133,7 +133,7 @@ bool WriteSettings(const fs::path& path,
out.pushKVEnd(value.first, value.second); out.pushKVEnd(value.first, value.second);
} }
std::ofstream file; std::ofstream file;
file.open(path); file.open(path.std_path());
if (file.fail()) { if (file.fail()) {
errors.emplace_back(strprintf("Error: Unable to open settings file %s for writing", fs::PathToString(path))); errors.emplace_back(strprintf("Error: Unable to open settings file %s for writing", fs::PathToString(path)));
return false; return false;
+1 -1
View File
@@ -179,7 +179,7 @@ static fs::path GetPidFile(const ArgsManager& args)
{ {
if (args.IsArgNegated("-pid")) return true; if (args.IsArgNegated("-pid")) return true;
std::ofstream file{GetPidFile(args)}; std::ofstream file{GetPidFile(args).std_path()};
if (file) { if (file) {
#ifdef WIN32 #ifdef WIN32
tfm::format(file, "%d\n", GetCurrentProcessId()); tfm::format(file, "%d\n", GetCurrentProcessId());
+3 -3
View File
@@ -447,7 +447,7 @@ bool openBitcoinConf()
fs::path pathConfig = gArgs.GetConfigFilePath(); fs::path pathConfig = gArgs.GetConfigFilePath();
/* Create the file */ /* Create the file */
std::ofstream configFile{pathConfig, std::ios_base::app}; std::ofstream configFile{pathConfig.std_path(), std::ios_base::app};
if (!configFile.good()) if (!configFile.good())
return false; return false;
@@ -607,7 +607,7 @@ fs::path static GetAutostartFilePath()
bool GetStartOnSystemStartup() bool GetStartOnSystemStartup()
{ {
std::ifstream optionFile{GetAutostartFilePath()}; std::ifstream optionFile{GetAutostartFilePath().std_path()};
if (!optionFile.good()) if (!optionFile.good())
return false; return false;
// Scan through file for "Hidden=true": // Scan through file for "Hidden=true":
@@ -639,7 +639,7 @@ bool SetStartOnSystemStartup(bool fAutoStart)
fs::create_directories(GetAutostartDir()); fs::create_directories(GetAutostartDir());
std::ofstream optionFile{GetAutostartFilePath(), std::ios_base::out | std::ios_base::trunc}; std::ofstream optionFile{GetAutostartFilePath().std_path(), std::ios_base::out | std::ios_base::trunc};
if (!optionFile.good()) if (!optionFile.good())
return false; return false;
ChainType chain = gArgs.GetChainType(); ChainType chain = gArgs.GetChainType();
+1 -1
View File
@@ -70,7 +70,7 @@ void OptionTests::migrateSettings()
QVERIFY(!settings.contains("fUseSeparateProxyTor")); QVERIFY(!settings.contains("fUseSeparateProxyTor"));
QVERIFY(!settings.contains("addrSeparateProxyTor")); QVERIFY(!settings.contains("addrSeparateProxyTor"));
std::ifstream file(gArgs.GetDataDirNet() / "settings.json"); std::ifstream file((gArgs.GetDataDirNet() / "settings.json").std_path());
std::string default_warning = strprintf("This file is automatically generated and updated by %s. Please do not edit this file while the node " std::string default_warning = strprintf("This file is automatically generated and updated by %s. Please do not edit this file while the node "
"is running, as any changes might be ignored or overwritten.", "is running, as any changes might be ignored or overwritten.",
CLIENT_NAME); CLIENT_NAME);
+2 -2
View File
@@ -114,7 +114,7 @@ GenerateAuthCookieResult GenerateAuthCookie(const std::optional<fs::perms>& cook
if (filepath_tmp.empty()) { if (filepath_tmp.empty()) {
return GenerateAuthCookieResult::DISABLED; // -norpccookiefile return GenerateAuthCookieResult::DISABLED; // -norpccookiefile
} }
file.open(filepath_tmp); file.open(filepath_tmp.std_path());
if (!file.is_open()) { if (!file.is_open()) {
LogWarning("Unable to open cookie authentication file %s for writing", fs::PathToString(filepath_tmp)); LogWarning("Unable to open cookie authentication file %s for writing", fs::PathToString(filepath_tmp));
return GenerateAuthCookieResult::ERR; return GenerateAuthCookieResult::ERR;
@@ -153,7 +153,7 @@ bool GetAuthCookie(std::string *cookie_out)
if (filepath.empty()) { if (filepath.empty()) {
return true; // -norpccookiefile return true; // -norpccookiefile
} }
file.open(filepath); file.open(filepath.std_path());
if (!file.is_open()) if (!file.is_open())
return false; return false;
std::getline(file, cookie); std::getline(file, cookie);
+10 -10
View File
@@ -51,37 +51,37 @@ BOOST_AUTO_TEST_CASE(fsbridge_fstream)
fs::path tmpfile1 = tmpfolder / fs::u8path("fs_tests_₿_🏃"); fs::path tmpfile1 = tmpfolder / fs::u8path("fs_tests_₿_🏃");
fs::path tmpfile2 = tmpfolder / fs::path(u8"fs_tests_₿_🏃"); fs::path tmpfile2 = tmpfolder / fs::path(u8"fs_tests_₿_🏃");
{ {
std::ofstream file{tmpfile1}; std::ofstream file{tmpfile1.std_path()};
file << "bitcoin"; file << "bitcoin";
} }
{ {
std::ifstream file{tmpfile2}; std::ifstream file{tmpfile2.std_path()};
std::string input_buffer; std::string input_buffer;
file >> input_buffer; file >> input_buffer;
BOOST_CHECK_EQUAL(input_buffer, "bitcoin"); BOOST_CHECK_EQUAL(input_buffer, "bitcoin");
} }
{ {
std::ifstream file{tmpfile1, std::ios_base::in | std::ios_base::ate}; std::ifstream file{tmpfile1.std_path(), std::ios_base::in | std::ios_base::ate};
std::string input_buffer; std::string input_buffer;
file >> input_buffer; file >> input_buffer;
BOOST_CHECK_EQUAL(input_buffer, ""); BOOST_CHECK_EQUAL(input_buffer, "");
} }
{ {
std::ofstream file{tmpfile2, std::ios_base::out | std::ios_base::app}; std::ofstream file{tmpfile2.std_path(), std::ios_base::out | std::ios_base::app};
file << "tests"; file << "tests";
} }
{ {
std::ifstream file{tmpfile1}; std::ifstream file{tmpfile1.std_path()};
std::string input_buffer; std::string input_buffer;
file >> input_buffer; file >> input_buffer;
BOOST_CHECK_EQUAL(input_buffer, "bitcointests"); BOOST_CHECK_EQUAL(input_buffer, "bitcointests");
} }
{ {
std::ofstream file{tmpfile2, std::ios_base::out | std::ios_base::trunc}; std::ofstream file{tmpfile2.std_path(), std::ios_base::out | std::ios_base::trunc};
file << "bitcoin"; file << "bitcoin";
} }
{ {
std::ifstream file{tmpfile1}; std::ifstream file{tmpfile1.std_path()};
std::string input_buffer; std::string input_buffer;
file >> input_buffer; file >> input_buffer;
BOOST_CHECK_EQUAL(input_buffer, "bitcoin"); BOOST_CHECK_EQUAL(input_buffer, "bitcoin");
@@ -116,12 +116,12 @@ BOOST_AUTO_TEST_CASE(rename)
const std::string path2_contents{"2222"}; const std::string path2_contents{"2222"};
{ {
std::ofstream file{path1}; std::ofstream file{path1.std_path()};
file << path1_contents; file << path1_contents;
} }
{ {
std::ofstream file{path2}; std::ofstream file{path2.std_path()};
file << path2_contents; file << path2_contents;
} }
@@ -131,7 +131,7 @@ BOOST_AUTO_TEST_CASE(rename)
BOOST_CHECK(!fs::exists(path1)); BOOST_CHECK(!fs::exists(path1));
{ {
std::ifstream file{path2}; std::ifstream file{path2.std_path()};
std::string contents; std::string contents;
file >> contents; file >> contents;
BOOST_CHECK_EQUAL(contents, path1_contents); BOOST_CHECK_EQUAL(contents, path1_contents);
+2 -2
View File
@@ -40,7 +40,7 @@ static void ResetLogger()
static std::vector<std::string> ReadDebugLogLines() static std::vector<std::string> ReadDebugLogLines()
{ {
std::vector<std::string> lines; std::vector<std::string> lines;
std::ifstream ifs{LogInstance().m_file_path}; std::ifstream ifs{LogInstance().m_file_path.std_path()};
for (std::string line; std::getline(ifs, line);) { for (std::string line; std::getline(ifs, line);) {
lines.push_back(std::move(line)); lines.push_back(std::move(line));
} }
@@ -421,7 +421,7 @@ void TestLogFromLocation(Location location, const std::string& message,
using Status = BCLog::LogRateLimiter::Status; using Status = BCLog::LogRateLimiter::Status;
if (!suppressions_active) assert(status == Status::UNSUPPRESSED); // developer error if (!suppressions_active) assert(status == Status::UNSUPPRESSED); // developer error
std::ofstream ofs(LogInstance().m_file_path, std::ios::out | std::ios::trunc); // clear debug log std::ofstream ofs(LogInstance().m_file_path.std_path(), std::ios::out | std::ios::trunc); // clear debug log
LogFromLocation(location, message); LogFromLocation(location, message);
auto log_lines{ReadDebugLogLines()}; auto log_lines{ReadDebugLogLines()};
BOOST_TEST_INFO_SCOPE(log_lines.size() << " log_lines read: \n" << util::Join(log_lines, "\n")); BOOST_TEST_INFO_SCOPE(log_lines.size() << " log_lines read: \n" << util::Join(log_lines, "\n"));
+1 -1
View File
@@ -159,7 +159,7 @@ BOOST_AUTO_TEST_CASE(script_assets_test)
bool exists = fs::exists(path); bool exists = fs::exists(path);
BOOST_WARN_MESSAGE(exists, "File $DIR_UNIT_TEST_DATA/script_assets_test.json not found, skipping script_assets_test"); BOOST_WARN_MESSAGE(exists, "File $DIR_UNIT_TEST_DATA/script_assets_test.json not found, skipping script_assets_test");
if (!exists) return; if (!exists) return;
std::ifstream file{path}; std::ifstream file{path.std_path()};
BOOST_CHECK(file.is_open()); BOOST_CHECK(file.is_open());
file.seekg(0, std::ios::end); file.seekg(0, std::ios::end);
size_t length = file.tellg(); size_t length = file.tellg();
+1 -1
View File
@@ -45,7 +45,7 @@ inline std::ostream& operator<<(std::ostream& os, const std::pair<std::string, c
inline void WriteText(const fs::path& path, const std::string& text) inline void WriteText(const fs::path& path, const std::string& text)
{ {
std::ofstream file; std::ofstream file;
file.open(path); file.open(path.std_path());
file << text; file << text;
} }
+2 -2
View File
@@ -1619,7 +1619,7 @@ BOOST_AUTO_TEST_CASE(util_ReadBinaryFile)
expected_text += "0123456789"; expected_text += "0123456789";
} }
{ {
std::ofstream file{tmpfile}; std::ofstream file{tmpfile.std_path()};
file << expected_text; file << expected_text;
} }
{ {
@@ -1650,7 +1650,7 @@ BOOST_AUTO_TEST_CASE(util_WriteBinaryFile)
std::string expected_text = "bitcoin"; std::string expected_text = "bitcoin";
auto valid = WriteBinaryFile(tmpfile, expected_text); auto valid = WriteBinaryFile(tmpfile, expected_text);
std::string actual_text; std::string actual_text;
std::ifstream file{tmpfile}; std::ifstream file{tmpfile.std_path()};
file >> actual_text; file >> actual_text;
BOOST_CHECK(valid); BOOST_CHECK(valid);
BOOST_CHECK_EQUAL(actual_text, expected_text); BOOST_CHECK_EQUAL(actual_text, expected_text);
+10 -5
View File
@@ -34,6 +34,10 @@ class path : public std::filesystem::path
public: public:
using std::filesystem::path::path; using std::filesystem::path::path;
// Convenience method for accessing standard path type without needing a cast.
std::filesystem::path& std_path() { return *this; }
const std::filesystem::path& std_path() const { return *this; }
// Allow path objects arguments for compatibility. // Allow path objects arguments for compatibility.
path(std::filesystem::path path) : std::filesystem::path::path(std::move(path)) {} path(std::filesystem::path path) : std::filesystem::path::path(std::move(path)) {}
path& operator=(std::filesystem::path path) { std::filesystem::path::operator=(std::move(path)); return *this; } path& operator=(std::filesystem::path path) { std::filesystem::path::operator=(std::move(path)); return *this; }
@@ -54,6 +58,12 @@ public:
// Disallow std::string conversion method to avoid locale-dependent encoding on windows. // Disallow std::string conversion method to avoid locale-dependent encoding on windows.
std::string string() const = delete; std::string string() const = delete;
// Disallow implicit string conversion to ensure code is portable.
// `string_type` may be `string` or `wstring` depending on the platform, so
// using this conversion could result in code that compiles on unix but
// fails to compile on windows, or vice versa.
operator string_type() const = delete;
/** /**
* Return a UTF-8 representation of the path as a std::string, for * Return a UTF-8 representation of the path as a std::string, for
* compatibility with code using std::string. For code using the newer * compatibility with code using std::string. For code using the newer
@@ -65,11 +75,6 @@ public:
const std::u8string& utf8_str{std::filesystem::path::u8string()}; const std::u8string& utf8_str{std::filesystem::path::u8string()};
return std::string{utf8_str.begin(), utf8_str.end()}; return std::string{utf8_str.begin(), utf8_str.end()};
} }
// Required for path overloads in <fstream>.
// See https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=96e0367ead5d8dcac3bec2865582e76e2fbab190
path& make_preferred() { std::filesystem::path::make_preferred(); return *this; }
path filename() const { return std::filesystem::path::filename(); }
}; };
static inline path u8path(const std::string& utf8_str) static inline path u8path(const std::string& utf8_str)
+2 -2
View File
@@ -102,7 +102,7 @@ bool IsBDBFile(const fs::path& path)
if (ec) LogWarning("Error reading file_size: %s [%s]", ec.message(), fs::PathToString(path)); if (ec) LogWarning("Error reading file_size: %s [%s]", ec.message(), fs::PathToString(path));
if (size < 4096) return false; if (size < 4096) return false;
std::ifstream file{path, std::ios::binary}; std::ifstream file{path.std_path(), std::ios::binary};
if (!file.is_open()) return false; if (!file.is_open()) return false;
file.seekg(12, std::ios::beg); // Magic bytes start at offset 12 file.seekg(12, std::ios::beg); // Magic bytes start at offset 12
@@ -126,7 +126,7 @@ bool IsSQLiteFile(const fs::path& path)
if (ec) LogWarning("Error reading file_size: %s [%s]", ec.message(), fs::PathToString(path)); if (ec) LogWarning("Error reading file_size: %s [%s]", ec.message(), fs::PathToString(path));
if (size < 512) return false; if (size < 512) return false;
std::ifstream file{path, std::ios::binary}; std::ifstream file{path.std_path(), std::ios::binary};
if (!file.is_open()) return false; if (!file.is_open()) return false;
// Magic is at beginning and is 16 bytes long // Magic is at beginning and is 16 bytes long
+2 -2
View File
@@ -37,7 +37,7 @@ bool DumpWallet(const ArgsManager& args, WalletDatabase& db, bilingual_str& erro
return false; return false;
} }
std::ofstream dump_file; std::ofstream dump_file;
dump_file.open(path); dump_file.open(path.std_path());
if (dump_file.fail()) { if (dump_file.fail()) {
error = strprintf(_("Unable to open %s for writing"), fs::PathToString(path)); error = strprintf(_("Unable to open %s for writing"), fs::PathToString(path));
return false; return false;
@@ -134,7 +134,7 @@ bool CreateFromDump(const ArgsManager& args, const std::string& name, const fs::
error = strprintf(_("Dump file %s does not exist."), fs::PathToString(dump_path)); error = strprintf(_("Dump file %s does not exist."), fs::PathToString(dump_path));
return false; return false;
} }
std::ifstream dump_file{dump_path}; std::ifstream dump_file{dump_path.std_path()};
// Compute the checksum // Compute the checksum
HashWriter hasher{}; HashWriter hasher{};
+1 -1
View File
@@ -36,7 +36,7 @@ InitWalletDirTestingSetup::InitWalletDirTestingSetup(const ChainType chainType)
fs::create_directories(m_walletdir_path_cases["default"]); fs::create_directories(m_walletdir_path_cases["default"]);
fs::create_directories(m_walletdir_path_cases["custom"]); fs::create_directories(m_walletdir_path_cases["custom"]);
fs::create_directories(m_walletdir_path_cases["relative"]); fs::create_directories(m_walletdir_path_cases["relative"]);
std::ofstream f{m_walletdir_path_cases["file"]}; std::ofstream f{m_walletdir_path_cases["file"].std_path()};
f.close(); f.close();
} }