Skip to content

Commit ec527c6

Browse files
committed
Don't allow relative -walletdir paths
Also warn if bitcoind is configured to use a relative -datadir path. Specifying paths relative to the current working directory in a daemon process can be dangerous, because files can fail to be located even if the configuration doesn't change, but the daemon is started up differently. Specifying a relative -datadir now adds a warning to the debug log. It would not be backwards-compatible to forbid relative -datadir paths entirely, and it could also be also inconvenient for command line testing. Specifying a relative -walletdir now results in a startup error. But since the -walletdir option is new in 0.16.0, there should be no compatibility issues. Another reason not to use working directory paths for -walletdir specifically is that the default -walletdir is a "wallets" subdirectory inside the datadir, so it could be surprising that setting -walletdir manually would choose a directory rooted in a completely different location.
1 parent e839d65 commit ec527c6

File tree

7 files changed

+32
-16
lines changed

7 files changed

+32
-16
lines changed

doc/release-notes.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,8 @@ bitcoin data directory. The behavior is now:
9090
already exists in the data directory root, then wallets will be stored in the
9191
`wallets/` subdirectory by default.
9292
- The location of the wallets directory can be overridden by specifying a
93-
`-walletdir=<path>` option where `<path>` can be an absolute path or a
94-
relative path (relative to the current working directory). `<path>` can
95-
also be a path to symlink to a directory.
93+
`-walletdir=<path>` option where `<path>` can be an absolute path to a
94+
directory or directory symlink.
9695

9796
Care should be taken when choosing the wallets directory location, as if it
9897
becomes unavailable during operation, funds may be lost.

src/init.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,6 +1210,15 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler)
12101210
LogPrintf("Using config file %s\n", GetConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME)).string());
12111211
LogPrintf("Using at most %i automatic connections (%i file descriptors available)\n", nMaxConnections, nFD);
12121212

1213+
// Warn about relative -datadir path.
1214+
if (gArgs.IsArgSet("-datadir") && !fs::path(gArgs.GetArg("-datadir", "")).is_absolute()) {
1215+
LogPrintf("Warning: relative datadir option '%s' specified, which will be interpreted relative to the "
1216+
"current working directory '%s'. This is fragile, because if bitcoin is started in the future "
1217+
"from a different location, it will be unable to locate the current data files. There could "
1218+
"also be data loss if bitcoin is started while in a temporary directory.\n",
1219+
gArgs.GetArg("-datadir", ""), fs::current_path().string());
1220+
}
1221+
12131222
InitSignatureCache();
12141223
InitScriptExecutionCache();
12151224

src/wallet/init.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,11 +205,15 @@ bool VerifyWallets()
205205
return true;
206206
}
207207

208-
if (gArgs.IsArgSet("-walletdir") && !fs::is_directory(GetWalletDir())) {
209-
if (fs::exists(fs::system_complete(gArgs.GetArg("-walletdir", "")))) {
210-
return InitError(strprintf(_("Specified -walletdir \"%s\" is not a directory"), gArgs.GetArg("-walletdir", "").c_str()));
208+
if (gArgs.IsArgSet("-walletdir")) {
209+
fs::path wallet_dir = gArgs.GetArg("-walletdir", "");
210+
if (!fs::exists(wallet_dir)) {
211+
return InitError(strprintf(_("Specified -walletdir \"%s\" does not exist"), wallet_dir.string()));
212+
} else if (!fs::is_directory(wallet_dir)) {
213+
return InitError(strprintf(_("Specified -walletdir \"%s\" is not a directory"), wallet_dir.string()));
214+
} else if (!wallet_dir.is_absolute()) {
215+
return InitError(strprintf(_("Specified -walletdir \"%s\" is a relative path"), wallet_dir.string()));
211216
}
212-
return InitError(strprintf(_("Specified -walletdir \"%s\" does not exist"), gArgs.GetArg("-walletdir", "").c_str()));
213217
}
214218

215219
LogPrintf("Using wallet directory %s\n", GetWalletDir().string());

src/wallet/walletutil.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ fs::path GetWalletDir()
99
fs::path path;
1010

1111
if (gArgs.IsArgSet("-walletdir")) {
12-
path = fs::system_complete(gArgs.GetArg("-walletdir", ""));
12+
path = gArgs.GetArg("-walletdir", "");
1313
if (!fs::is_directory(path)) {
1414
// If the path specified doesn't exist, we return the deliberately
1515
// invalid empty string.

test/functional/multiwallet.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ def run_test(self):
3030

3131
self.stop_nodes()
3232

33+
self.assert_start_raises_init_error(0, ['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" does not exist')
34+
self.assert_start_raises_init_error(0, ['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" is a relative path', cwd=data_dir())
35+
self.assert_start_raises_init_error(0, ['-walletdir=debug.log'], 'Error: Specified -walletdir "debug.log" is not a directory', cwd=data_dir())
36+
3337
# should not initialize if there are duplicate wallets
3438
self.assert_start_raises_init_error(0, ['-wallet=w1', '-wallet=w1'], 'Error loading wallet w1. Duplicate -wallet filename specified.')
3539

test/functional/test_framework/test_framework.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -220,26 +220,26 @@ def add_nodes(self, num_nodes, extra_args=None, rpchost=None, timewait=None, bin
220220
for i in range(num_nodes):
221221
self.nodes.append(TestNode(i, self.options.tmpdir, extra_args[i], rpchost, timewait=timewait, binary=binary[i], stderr=None, mocktime=self.mocktime, coverage_dir=self.options.coveragedir, use_cli=self.options.usecli))
222222

223-
def start_node(self, i, extra_args=None, stderr=None):
223+
def start_node(self, i, *args, **kwargs):
224224
"""Start a bitcoind"""
225225

226226
node = self.nodes[i]
227227

228-
node.start(extra_args, stderr)
228+
node.start(*args, **kwargs)
229229
node.wait_for_rpc_connection()
230230

231231
if self.options.coveragedir is not None:
232232
coverage.write_all_rpc_commands(self.options.coveragedir, node.rpc)
233233

234-
def start_nodes(self, extra_args=None):
234+
def start_nodes(self, extra_args=None, *args, **kwargs):
235235
"""Start multiple bitcoinds"""
236236

237237
if extra_args is None:
238238
extra_args = [None] * self.num_nodes
239239
assert_equal(len(extra_args), self.num_nodes)
240240
try:
241241
for i, node in enumerate(self.nodes):
242-
node.start(extra_args[i])
242+
node.start(extra_args[i], *args, **kwargs)
243243
for node in self.nodes:
244244
node.wait_for_rpc_connection()
245245
except:
@@ -271,10 +271,10 @@ def restart_node(self, i, extra_args=None):
271271
self.stop_node(i)
272272
self.start_node(i, extra_args)
273273

274-
def assert_start_raises_init_error(self, i, extra_args=None, expected_msg=None):
274+
def assert_start_raises_init_error(self, i, extra_args=None, expected_msg=None, *args, **kwargs):
275275
with tempfile.SpooledTemporaryFile(max_size=2**16) as log_stderr:
276276
try:
277-
self.start_node(i, extra_args, stderr=log_stderr)
277+
self.start_node(i, extra_args, stderr=log_stderr, *args, **kwargs)
278278
self.stop_node(i)
279279
except Exception as e:
280280
assert 'bitcoind exited' in str(e) # node must have shutdown

test/functional/test_framework/test_node.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,13 @@ def __getattr__(self, name):
8181
assert self.rpc_connected and self.rpc is not None, "Error: no RPC connection"
8282
return getattr(self.rpc, name)
8383

84-
def start(self, extra_args=None, stderr=None):
84+
def start(self, extra_args=None, stderr=None, *args, **kwargs):
8585
"""Start the node."""
8686
if extra_args is None:
8787
extra_args = self.extra_args
8888
if stderr is None:
8989
stderr = self.stderr
90-
self.process = subprocess.Popen(self.args + extra_args, stderr=stderr)
90+
self.process = subprocess.Popen(self.args + extra_args, stderr=stderr, *args, **kwargs)
9191
self.running = True
9292
self.log.debug("bitcoind started, waiting for RPC to come up")
9393

0 commit comments

Comments
 (0)