Skip to content

Commit e1021fd

Browse files
fanquakevijaydasmp
authored andcommitted
Merge bitcoin#27302: init: Error if ignored bitcoin.conf file is found
eefe569 bugfix: Fix incorrect debug.log config file path (Ryan Ofsky) 3746f00 init: Error if ignored bitcoin.conf file is found (Ryan Ofsky) 398c371 lint: Fix lint-format-strings false positives when format specifiers have argument positions (Ryan Ofsky) Pull request description: Show an error on startup if a bitcoin datadir that is being used contains a `bitcoin.conf` file that is ignored. There are two cases where this could happen: - One case reported in [bitcoin#27246 (comment)](bitcoin#27246 (comment)) happens when a `bitcoin.conf` file in the default datadir (e.g. `$HOME/.bitcoin/bitcoin.conf`) has a `datadir=/path` line that sets different datadir containing a second `bitcoin.conf` file. Currently the second `bitcoin.conf` file is ignored with no warning. - Another way this could happen is if a `-conf=` command line argument points to a configuration file with a `datadir=/path` line and that path contains a `bitcoin.conf` file, which is currently ignored. This change only adds an error message and doesn't change anything about way settings are applied. It also doesn't trigger errors if there are redundant `-datadir` or `-conf` settings pointing at the same configuration file, only if they are pointing at different files and one file is being ignored. ACKs for top commit: pinheadmz: re-ACK eefe569 willcl-ark: re-ACK eefe569 TheCharlatan: ACK eefe569 Tree-SHA512: 939a98a4b271b5263d64a2df3054c56fcde94784edf6f010d78693a371c38aa03138ae9cebb026b6164bbd898d8fd0845a61a454fd996e328fd7bcf51c580c2b
1 parent 76064a0 commit e1021fd

7 files changed

Lines changed: 144 additions & 12 deletions

File tree

doc/release-notes-27302.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Configuration
2+
---
3+
4+
- `bitcoind` and `bitcoin-qt` will now raise an error on startup if a datadir that is being used contains a bitcoin.conf file that will be ignored, which can happen when a datadir= line is used in a bitcoin.conf file. The error message is just a diagnostic intended to prevent accidental misconfiguration, and it can be disabled to restore the previous behavior of using the datadir while ignoring the bitcoin.conf contained in it.

src/init.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,7 @@ void SetupServerArgs(ArgsManager& argsman)
581581
argsman.AddArg("-dbbatchsize", strprintf("Maximum database write batch size in bytes (default: %u)", nDefaultDbBatchSize), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS);
582582
argsman.AddArg("-dbcache=<n>", strprintf("Maximum database cache size <n> MiB (%d to %d, default: %d). In addition, unused mempool memory is shared for this cache (see -maxmempool).", nMinDbCache, nMaxDbCache, nDefaultDbCache), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
583583
argsman.AddArg("-includeconf=<file>", "Specify additional configuration file, relative to the -datadir path (only useable from configuration file, not command line)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
584+
argsman.AddArg("-allowignoredconf", strprintf("For backwards compatibility, treat an unused %s file in the datadir as a warning, not an error.", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
584585
argsman.AddArg("-loadblock=<file>", "Imports blocks from external file on startup", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
585586
argsman.AddArg("-maxmempool=<n>", strprintf("Keep the transaction memory pool below <n> megabytes (default: %u)", DEFAULT_MAX_MEMPOOL_SIZE), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
586587
argsman.AddArg("-maxorphantxsize=<n>", strprintf("Maximum total size of all orphan transactions in megabytes (default: %u)", DEFAULT_MAX_ORPHAN_TRANSACTIONS_SIZE), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);

src/qt/test/test_main.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ int main(int argc, char* argv[])
7272
gArgs.ForceSetArg("-upnp", "0");
7373
gArgs.ForceSetArg("-natpmp", "0");
7474

75+
std::string error;
76+
if (!gArgs.ReadConfigFiles(error, true)) QWARN(error.c_str());
77+
7578
// Prefer the "minimal" platform for the test instead of the normal default
7679
// platform ("xcb", "windows", or "cocoa") so tests can't unintentionally
7780
// interfere with any background GUIs and don't require extra resources.

test/functional/feature_config_args.py

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@
55
"""Test various command line arguments and configuration file parameters."""
66

77
import os
8+
import pathlib
9+
import re
10+
import sys
11+
import tempfile
812
import time
913

1014
from test_framework.test_framework import BitcoinTestFramework
15+
from test_framework.test_node import ErrorMatch
1116
from test_framework import util
1217

1318

@@ -74,7 +79,7 @@ def test_config_file_parser(self):
7479
util.write_config(main_conf_file_path, n=0, chain='', extra_config=f'includeconf={inc_conf_file_path}\n')
7580
with open(inc_conf_file_path, 'w', encoding='utf-8') as conf:
7681
conf.write('acceptnonstdtxn=1\n')
77-
self.nodes[0].assert_start_raises_init_error(extra_args=[f"-conf={main_conf_file_path}"], expected_msg='Error: acceptnonstdtxn is not currently supported for main chain')
82+
self.nodes[0].assert_start_raises_init_error(extra_args=[f"-conf={main_conf_file_path}", "-allowignoredconf"], expected_msg='Error: acceptnonstdtxn is not currently supported for main chain')
7883

7984
with open(inc_conf_file_path, 'w', encoding='utf-8') as conf:
8085
conf.write('nono\n')
@@ -108,6 +113,41 @@ def test_config_file_parser(self):
108113
with open(inc_conf_file2_path, 'w', encoding='utf-8') as conf:
109114
conf.write('') # clear
110115

116+
def test_config_file_log(self):
117+
# Disable this test for windows currently because trying to override
118+
# the default datadir through the environment does not seem to work.
119+
if sys.platform == "win32":
120+
return
121+
122+
self.log.info('Test that correct configuration path is changed when configuration file changes the datadir')
123+
124+
# Create a temporary directory that will be treated as the default data
125+
# directory by bitcoind.
126+
env, default_datadir = util.get_temp_default_datadir(pathlib.Path(self.options.tmpdir, "test_config_file_log"))
127+
default_datadir.mkdir(parents=True)
128+
129+
# Write a bitcoin.conf file in the default data directory containing a
130+
# datadir= line pointing at the node datadir.
131+
node = self.nodes[0]
132+
conf_text = pathlib.Path(node.bitcoinconf).read_text()
133+
conf_path = default_datadir / "bitcoin.conf"
134+
conf_path.write_text(f"datadir={node.datadir}\n{conf_text}")
135+
136+
# Drop the node -datadir= argument during this test, because if it is
137+
# specified it would take precedence over the datadir setting in the
138+
# config file.
139+
node_args = node.args
140+
node.args = [arg for arg in node.args if not arg.startswith("-datadir=")]
141+
142+
# Check that correct configuration file path is actually logged
143+
# (conf_path, not node.bitcoinconf)
144+
with self.nodes[0].assert_debug_log(expected_msgs=[f"Config file: {conf_path}"]):
145+
self.start_node(0, ["-allowignoredconf"], env=env)
146+
self.stop_node(0)
147+
148+
# Restore node arguments after the test
149+
node.args = node_args
150+
111151
def test_invalid_command_line_options(self):
112152
self.nodes[0].assert_start_raises_init_error(
113153
expected_msg='Error: Error parsing command line arguments: Can not set -proxy with no value. Please specify value with -proxy=value.',
@@ -278,6 +318,55 @@ def test_connect_with_seednode(self):
278318
unexpected_msgs=seednode_ignored):
279319
self.restart_node(0, extra_args=[connect_arg, '-seednode=fakeaddress2'])
280320

321+
def test_ignored_conf(self):
322+
self.log.info('Test error is triggered when the datadir in use contains a bitcoin.conf file that would be ignored '
323+
'because a conflicting -conf file argument is passed.')
324+
node = self.nodes[0]
325+
with tempfile.NamedTemporaryFile(dir=self.options.tmpdir, mode="wt", delete=False) as temp_conf:
326+
temp_conf.write(f"datadir={node.datadir}\n")
327+
node.assert_start_raises_init_error([f"-conf={temp_conf.name}"], re.escape(
328+
f'Error: Data directory "{node.datadir}" contains a "bitcoin.conf" file which is ignored, because a '
329+
f'different configuration file "{temp_conf.name}" from command line argument "-conf={temp_conf.name}" '
330+
f'is being used instead.') + r"[\s\S]*", match=ErrorMatch.FULL_REGEX)
331+
332+
# Test that passing a redundant -conf command line argument pointing to
333+
# the same bitcoin.conf that would be loaded anyway does not trigger an
334+
# error.
335+
self.start_node(0, [f'-conf={node.datadir}/bitcoin.conf'])
336+
self.stop_node(0)
337+
338+
def test_ignored_default_conf(self):
339+
# Disable this test for windows currently because trying to override
340+
# the default datadir through the environment does not seem to work.
341+
if sys.platform == "win32":
342+
return
343+
344+
self.log.info('Test error is triggered when bitcoin.conf in the default data directory sets another datadir '
345+
'and it contains a different bitcoin.conf file that would be ignored')
346+
347+
# Create a temporary directory that will be treated as the default data
348+
# directory by bitcoind.
349+
env, default_datadir = util.get_temp_default_datadir(pathlib.Path(self.options.tmpdir, "home"))
350+
default_datadir.mkdir(parents=True)
351+
352+
# Write a bitcoin.conf file in the default data directory containing a
353+
# datadir= line pointing at the node datadir. This will trigger a
354+
# startup error because the node datadir contains a different
355+
# bitcoin.conf that would be ignored.
356+
node = self.nodes[0]
357+
(default_datadir / "bitcoin.conf").write_text(f"datadir={node.datadir}\n")
358+
359+
# Drop the node -datadir= argument during this test, because if it is
360+
# specified it would take precedence over the datadir setting in the
361+
# config file.
362+
node_args = node.args
363+
node.args = [arg for arg in node.args if not arg.startswith("-datadir=")]
364+
node.assert_start_raises_init_error([], re.escape(
365+
f'Error: Data directory "{node.datadir}" contains a "bitcoin.conf" file which is ignored, because a '
366+
f'different configuration file "{default_datadir}/bitcoin.conf" from data directory "{default_datadir}" '
367+
f'is being used instead.') + r"[\s\S]*", env=env, match=ErrorMatch.FULL_REGEX)
368+
node.args = node_args
369+
281370
def run_test(self):
282371
self.test_log_buffer()
283372
self.test_args_log()
@@ -287,7 +376,10 @@ def run_test(self):
287376

288377

289378
self.test_config_file_parser()
379+
self.test_config_file_log()
290380
self.test_invalid_command_line_options()
381+
self.test_ignored_conf()
382+
self.test_ignored_default_conf()
291383

292384
# Remove the -datadir argument so it doesn't override the config file
293385
self.nodes[0].args = [arg for arg in self.nodes[0].args if not arg.startswith("-datadir")]

test/functional/test_framework/test_node.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ def __getattr__(self, name):
222222
assert self.rpc_connected and self.rpc is not None, self._node_msg("Error: no RPC connection")
223223
return getattr(RPCOverloadWrapper(self.rpc, descriptors=self.descriptors), name)
224224

225-
def start(self, extra_args=None, *, cwd=None, stdout=None, stderr=None, **kwargs):
225+
def start(self, extra_args=None, *, cwd=None, stdout=None, stderr=None, env=None, **kwargs):
226226
"""Start the node."""
227227
if extra_args is None:
228228
extra_args = self.extra_args
@@ -251,6 +251,8 @@ def start(self, extra_args=None, *, cwd=None, stdout=None, stderr=None, **kwargs
251251

252252
# add environment variable LIBC_FATAL_STDERR_=1 so that libc errors are written to stderr and not the terminal
253253
subp_env = dict(os.environ, LIBC_FATAL_STDERR_="1")
254+
if env is not None:
255+
subp_env.update(env)
254256

255257
self.process = subprocess.Popen(all_args, env=subp_env, stdout=stdout, stderr=stderr, cwd=cwd, **kwargs)
256258

test/functional/test_framework/util.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,17 @@
1313
import json
1414
import logging
1515
import os
16+
import pathlib
1617
import random
1718
import shutil
1819
import re
20+
import sys
1921
import time
2022
import urllib.parse
2123

2224
from . import coverage
2325
from .authproxy import AuthServiceProxy, JSONRPCException
24-
from typing import Callable, Optional
26+
from typing import Callable, Optional, Tuple
2527

2628
logger = logging.getLogger("TestFramework.utils")
2729

@@ -432,6 +434,22 @@ def get_datadir_path(dirname, n):
432434
return os.path.join(dirname, "node" + str(n))
433435

434436

437+
def get_temp_default_datadir(temp_dir: pathlib.Path) -> Tuple[dict, pathlib.Path]:
438+
"""Return os-specific environment variables that can be set to make the
439+
GetDefaultDataDir() function return a datadir path under the provided
440+
temp_dir, as well as the complete path it would return."""
441+
if sys.platform == "win32":
442+
env = dict(APPDATA=str(temp_dir))
443+
datadir = temp_dir / "Bitcoin"
444+
else:
445+
env = dict(HOME=str(temp_dir))
446+
if sys.platform == "darwin":
447+
datadir = temp_dir / "Library/Application Support/Bitcoin"
448+
else:
449+
datadir = temp_dir / ".bitcoin"
450+
return env, datadir
451+
452+
435453
def append_config(datadir, options):
436454
with open(os.path.join(datadir, "dash.conf"), 'a', encoding='utf8') as f:
437455
for option in options:

test/lint/run-lint-format-strings.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -244,20 +244,32 @@ def count_format_specifiers(format_string):
244244
3
245245
>>> count_format_specifiers("foo %d bar %i foo %% foo %*d foo")
246246
4
247+
>>> count_format_specifiers("foo %5$d")
248+
5
249+
>>> count_format_specifiers("foo %5$*7$d")
250+
7
247251
"""
248252
assert type(format_string) is str
249253
format_string = format_string.replace('%%', 'X')
250-
n = 0
251-
in_specifier = False
252-
for i, char in enumerate(format_string):
253-
if char == "%":
254-
in_specifier = True
254+
n = max_pos = 0
255+
for m in re.finditer("%(.*?)[aAcdeEfFgGinopsuxX]", format_string, re.DOTALL):
256+
# Increase the max position if the argument has a position number like
257+
# "5$", otherwise increment the argument count.
258+
pos_num, = re.match(r"(?:(^\d+)\$)?", m.group(1)).groups()
259+
if pos_num is not None:
260+
max_pos = max(max_pos, int(pos_num))
261+
else:
255262
n += 1
256-
elif char in "aAcdeEfFgGinopsuxX":
257-
in_specifier = False
258-
elif in_specifier and char == "*":
263+
264+
# Increase the max position if there is a "*" width argument with a
265+
# position like "*7$", and increment the argument count if there is a
266+
# "*" width argument with no position.
267+
star, star_pos_num = re.match(r"(?:.*?(\*(?:(\d+)\$)?)|)", m.group(1)).groups()
268+
if star_pos_num is not None:
269+
max_pos = max(max_pos, int(star_pos_num))
270+
elif star is not None:
259271
n += 1
260-
return n
272+
return max(n, max_pos)
261273

262274

263275
def main():

0 commit comments

Comments
 (0)