Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

util: Work around libstdc++ create_directories issue #24338

Merged
merged 3 commits into from Feb 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/fs.h
Expand Up @@ -140,6 +140,28 @@ static inline path PathFromString(const std::string& string)
return std::filesystem::path(string);
#endif
}

/**
* Create directory (and if necessary its parents), unless the leaf directory
* already exists or is a symlink to an existing directory.
* This is a temporary workaround for an issue in libstdc++ that has been fixed
* upstream [PR101510].
*/
static inline bool create_directories(const std::filesystem::path& p)
laanwj marked this conversation as resolved.
Show resolved Hide resolved
{
if (std::filesystem::is_symlink(p) && std::filesystem::is_directory(p)) {
return false;
}
return std::filesystem::create_directories(p);
}

/**
* This variant is not used. Delete it to prevent it from accidentally working
* around the workaround. If it is needed, add a workaround in the same pattern
* as above.
*/
bool create_directories(const std::filesystem::path& p, std::error_code& ec) = delete;

} // namespace fs

/** Bridge operations to C stdio */
Expand Down
4 changes: 2 additions & 2 deletions src/test/dbwrapper_tests.cpp
Expand Up @@ -203,7 +203,7 @@ BOOST_AUTO_TEST_CASE(existing_data_no_obfuscate)
{
// We're going to share this fs::path between two wrappers
fs::path ph = m_args.GetDataDirBase() / "existing_data_no_obfuscate";
create_directories(ph);
maflcko marked this conversation as resolved.
Show resolved Hide resolved
fs::create_directories(ph);

// Set up a non-obfuscated wrapper to write some initial data.
std::unique_ptr<CDBWrapper> dbw = std::make_unique<CDBWrapper>(ph, (1 << 10), false, false, false);
Expand Down Expand Up @@ -244,7 +244,7 @@ BOOST_AUTO_TEST_CASE(existing_data_reindex)
{
// We're going to share this fs::path between two wrappers
fs::path ph = m_args.GetDataDirBase() / "existing_data_reindex";
create_directories(ph);
fs::create_directories(ph);

// Set up a non-obfuscated wrapper to write some initial data.
std::unique_ptr<CDBWrapper> dbw = std::make_unique<CDBWrapper>(ph, (1 << 10), false, false, false);
Expand Down
24 changes: 24 additions & 0 deletions src/test/fs_tests.cpp
Expand Up @@ -152,4 +152,28 @@ BOOST_AUTO_TEST_CASE(rename)
fs::remove(path2);
}

#ifndef WIN32
laanwj marked this conversation as resolved.
Show resolved Hide resolved
BOOST_AUTO_TEST_CASE(create_directories)
{
// Test fs::create_directories workaround.
laanwj marked this conversation as resolved.
Show resolved Hide resolved
const fs::path tmpfolder{m_args.GetDataDirBase()};

const fs::path dir{GetUniquePath(tmpfolder)};
fs::create_directory(dir);
BOOST_CHECK(fs::exists(dir));
BOOST_CHECK(fs::is_directory(dir));
BOOST_CHECK(!fs::create_directories(dir));

const fs::path symlink{GetUniquePath(tmpfolder)};
fs::create_directory_symlink(dir, symlink);
BOOST_CHECK(fs::exists(symlink));
BOOST_CHECK(fs::is_symlink(symlink));
BOOST_CHECK(fs::is_directory(symlink));
BOOST_CHECK(!fs::create_directories(symlink));

fs::remove(symlink);
fs::remove(dir);
}
#endif // WIN32

BOOST_AUTO_TEST_SUITE_END()
39 changes: 39 additions & 0 deletions test/functional/feature_dirsymlinks.py
@@ -0,0 +1,39 @@
#!/usr/bin/env python3
# Copyright (c) 2022 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test successful startup with symlinked directories.
"""

import os
import sys

from test_framework.test_framework import BitcoinTestFramework, SkipTest


def rename_and_link(*, from_name, to_name):
os.rename(from_name, to_name)
os.symlink(to_name, from_name)
assert os.path.islink(from_name) and os.path.isdir(from_name)
laanwj marked this conversation as resolved.
Show resolved Hide resolved

class SymlinkTest(BitcoinTestFramework):
def skip_test_if_missing_module(self):
if sys.platform == 'win32':
raise SkipTest("Symlinks test skipped on Windows")

def set_test_params(self):
self.num_nodes = 1

def run_test(self):
self.stop_node(0)

rename_and_link(from_name=os.path.join(self.nodes[0].datadir, self.chain, "blocks"),
to_name=os.path.join(self.nodes[0].datadir, self.chain, "newblocks"))
rename_and_link(from_name=os.path.join(self.nodes[0].datadir, self.chain, "chainstate"),
to_name=os.path.join(self.nodes[0].datadir, self.chain, "newchainstate"))

self.start_node(0)


if __name__ == '__main__':
SymlinkTest().main()
1 change: 1 addition & 0 deletions test/functional/test_runner.py
Expand Up @@ -321,6 +321,7 @@
'rpc_getdescriptorinfo.py',
'rpc_mempool_entry_fee_fields_deprecation.py',
'rpc_help.py',
'feature_dirsymlinks.py',
'feature_help.py',
'feature_shutdown.py',
'p2p_ibd_txrelay.py',
Expand Down