Skip to content

Commit 9ecc7af

Browse files
committed
Merge bitcoin#31674: init: Lock blocksdir in addition to datadir
2656a56 tests: add a test for the new blocksdir lock (Cory Fields) bdc0a68 init: lock blocksdir in addition to datadir (Cory Fields) cabb2e5 refactor: introduce a more general LockDirectories for init (Cory Fields) 1db331b init: allow a new xor key to be written if the blocksdir is newly created (Cory Fields) Pull request description: This probably should've been included in bitcoin#12653 when `-blocksdir` was introduced. Credit TheCharlatan for noticing that it's missing. This guards against 2 processes running with separate datadirs but the same blocksdir. I didn't add `walletdir` as I assume sqlite has us covered there. It's not likely to happen currently, but may be more relevant in the future with applications using the kernel. Note that the kernel does not currently do any dir locking, but it should. ACKs for top commit: maflcko: review ACK 2656a56 🏼 kevkevinpal: ACK [2656a56](bitcoin@2656a56) achow101: ACK 2656a56 tdb3: Code review and light test ACK 2656a56 Tree-SHA512: 3ba17dc670126adda104148e14d1322ea4f67d671c84aaa9c08c760ef778ca1936832c0dc843cd6367e09939f64c6f0a682b0fa23a5967e821b899dff1fff961
2 parents 2d07384 + 2656a56 commit 9ecc7af

File tree

6 files changed

+46
-24
lines changed

6 files changed

+46
-24
lines changed

src/bitcoind.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,10 +228,10 @@ static bool AppInit(NodeContext& node)
228228
return InitError(Untranslated("-daemon is not supported on this operating system"));
229229
#endif // HAVE_DECL_FORK
230230
}
231-
// Lock data directory after daemonization
232-
if (!AppInitLockDataDirectory())
231+
// Lock critical directories after daemonization
232+
if (!AppInitLockDirectories())
233233
{
234-
// If locking the data directory failed, exit immediately
234+
// If locking a directory failed, exit immediately
235235
return false;
236236
}
237237
fRet = AppInitInterfaces(node) && AppInitMain(node);

src/init.cpp

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,19 +1072,23 @@ bool AppInitParameterInteraction(const ArgsManager& args)
10721072
return true;
10731073
}
10741074

1075-
static bool LockDataDirectory(bool probeOnly)
1075+
static bool LockDirectory(const fs::path& dir, bool probeOnly)
10761076
{
1077-
// Make sure only a single Bitcoin process is using the data directory.
1078-
const fs::path& datadir = gArgs.GetDataDirNet();
1079-
switch (util::LockDirectory(datadir, ".lock", probeOnly)) {
1077+
// Make sure only a single process is using the directory.
1078+
switch (util::LockDirectory(dir, ".lock", probeOnly)) {
10801079
case util::LockResult::ErrorWrite:
1081-
return InitError(strprintf(_("Cannot write to data directory '%s'; check permissions."), fs::PathToString(datadir)));
1080+
return InitError(strprintf(_("Cannot write to directory '%s'; check permissions."), fs::PathToString(dir)));
10821081
case util::LockResult::ErrorLock:
1083-
return InitError(strprintf(_("Cannot obtain a lock on data directory %s. %s is probably already running."), fs::PathToString(datadir), CLIENT_NAME));
1082+
return InitError(strprintf(_("Cannot obtain a lock on directory %s. %s is probably already running."), fs::PathToString(dir), CLIENT_NAME));
10841083
case util::LockResult::Success: return true;
10851084
} // no default case, so the compiler can warn about missing cases
10861085
assert(false);
10871086
}
1087+
static bool LockDirectories(bool probeOnly)
1088+
{
1089+
return LockDirectory(gArgs.GetDataDirNet(), probeOnly) && \
1090+
LockDirectory(gArgs.GetBlocksDirPath(), probeOnly);
1091+
}
10881092

10891093
bool AppInitSanityChecks(const kernel::Context& kernel)
10901094
{
@@ -1099,19 +1103,19 @@ bool AppInitSanityChecks(const kernel::Context& kernel)
10991103
return InitError(strprintf(_("Elliptic curve cryptography sanity check failure. %s is shutting down."), CLIENT_NAME));
11001104
}
11011105

1102-
// Probe the data directory lock to give an early error message, if possible
1103-
// We cannot hold the data directory lock here, as the forking for daemon() hasn't yet happened,
1104-
// and a fork will cause weird behavior to it.
1105-
return LockDataDirectory(true);
1106+
// Probe the directory locks to give an early error message, if possible
1107+
// We cannot hold the directory locks here, as the forking for daemon() hasn't yet happened,
1108+
// and a fork will cause weird behavior to them.
1109+
return LockDirectories(true);
11061110
}
11071111

1108-
bool AppInitLockDataDirectory()
1112+
bool AppInitLockDirectories()
11091113
{
1110-
// After daemonization get the data directory lock again and hold on to it until exit
1114+
// After daemonization get the directory locks again and hold on to them until exit
11111115
// This creates a slight window for a race condition to happen, however this condition is harmless: it
11121116
// will at most make us exit without printing a message to console.
1113-
if (!LockDataDirectory(false)) {
1114-
// Detailed error printed inside LockDataDirectory
1117+
if (!LockDirectories(false)) {
1118+
// Detailed error printed inside LockDirectory
11151119
return false;
11161120
}
11171121
return true;

src/init.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,19 +55,19 @@ bool AppInitParameterInteraction(const ArgsManager& args);
5555
*/
5656
bool AppInitSanityChecks(const kernel::Context& kernel);
5757
/**
58-
* Lock bitcoin core data directory.
58+
* Lock bitcoin core critical directories.
5959
* @note This should only be done after daemonization. Do not call Shutdown() if this function fails.
6060
* @pre Parameters should be parsed and config file should be read, AppInitSanityChecks should have been called.
6161
*/
62-
bool AppInitLockDataDirectory();
62+
bool AppInitLockDirectories();
6363
/**
6464
* Initialize node and wallet interface pointers. Has no prerequisites or side effects besides allocating memory.
6565
*/
6666
bool AppInitInterfaces(node::NodeContext& node);
6767
/**
6868
* Bitcoin core main initialization.
6969
* @note This should only be done after daemonization. Call Shutdown() if this function fails.
70-
* @pre Parameters should be parsed and config file should be read, AppInitLockDataDirectory should have been called.
70+
* @pre Parameters should be parsed and config file should be read, AppInitLockDirectories should have been called.
7171
*/
7272
bool AppInitMain(node::NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info = nullptr);
7373

src/node/blockstorage.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1117,7 +1117,19 @@ static auto InitBlocksdirXorKey(const BlockManager::Options& opts)
11171117
// size of the XOR-key file.
11181118
std::array<std::byte, 8> xor_key{};
11191119

1120-
if (opts.use_xor && fs::is_empty(opts.blocks_dir)) {
1120+
// Consider this to be the first run if the blocksdir contains only hidden
1121+
// files (those which start with a .). Checking for a fully-empty dir would
1122+
// be too aggressive as a .lock file may have already been written.
1123+
bool first_run = true;
1124+
for (const auto& entry : fs::directory_iterator(opts.blocks_dir)) {
1125+
const std::string path = fs::PathToString(entry.path().filename());
1126+
if (!entry.is_regular_file() || !path.starts_with('.')) {
1127+
first_run = false;
1128+
break;
1129+
}
1130+
}
1131+
1132+
if (opts.use_xor && first_run) {
11211133
// Only use random fresh key when the boolean option is set and on the
11221134
// very first start of the program.
11231135
FastRandomContext{}.fillrand(xor_key);

src/node/interfaces.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ class NodeImpl : public Node
116116
m_context->ecc_context = std::make_unique<ECC_Context>();
117117
if (!AppInitSanityChecks(*m_context->kernel)) return false;
118118

119-
if (!AppInitLockDataDirectory()) return false;
119+
if (!AppInitLockDirectories()) return false;
120120
if (!AppInitInterfaces(*m_context)) return false;
121121

122122
return true;

test/functional/feature_filelock.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,19 @@ def setup_network(self):
2727

2828
def run_test(self):
2929
datadir = self.nodes[0].chain_path
30+
blocksdir = self.nodes[0].blocks_path
3031
self.log.info(f"Using datadir {datadir}")
32+
self.log.info(f"Using blocksdir {blocksdir}")
3133

3234
self.log.info("Check that we can't start a second bitcoind instance using the same datadir")
33-
expected_msg = f"Error: Cannot obtain a lock on data directory {datadir}. {self.config['environment']['CLIENT_NAME']} is probably already running."
35+
expected_msg = f"Error: Cannot obtain a lock on directory {datadir}. {self.config['environment']['CLIENT_NAME']} is probably already running."
3436
self.nodes[1].assert_start_raises_init_error(extra_args=[f'-datadir={self.nodes[0].datadir_path}', '-noserver'], expected_msg=expected_msg)
3537

36-
self.log.info("Check that cookie and PID file are not deleted when attempting to start a second bitcoind using the same datadir")
38+
self.log.info("Check that we can't start a second bitcoind instance using the same blocksdir")
39+
expected_msg = f"Error: Cannot obtain a lock on directory {blocksdir}. {self.config['environment']['CLIENT_NAME']} is probably already running."
40+
self.nodes[1].assert_start_raises_init_error(extra_args=[f'-blocksdir={self.nodes[0].datadir_path}', '-noserver'], expected_msg=expected_msg)
41+
42+
self.log.info("Check that cookie and PID file are not deleted when attempting to start a second bitcoind using the same datadir/blocksdir")
3743
cookie_file = datadir / ".cookie"
3844
assert cookie_file.exists() # should not be deleted during the second bitcoind instance shutdown
3945
pid_file = datadir / BITCOIN_PID_FILENAME_DEFAULT

0 commit comments

Comments
 (0)