Skip to content

Commit fc037c8

Browse files
committed
Merge bitcoin/bitcoin#27150: Deduplicate bitcoind and bitcoin-qt init code
802cc1e Deduplicate bitcoind and bitcoin-qt init code (Ryan Ofsky) d172b5c Add InitError(error, details) overload (Ryan Ofsky) 3db2874 Extend bilingual_str support for tinyformat (Ryan Ofsky) c361df9 scripted-diff: Remove double newlines after some init errors (Ryan Ofsky) Pull request description: Add common InitConfig function to deduplicate bitcoind and bitcoin-qt code reading config files and creating the datadir. Noticed the duplicate code while reviewing #27073 and want to remove it because difference in bitcoin-qt and bitcoind behavior make it hard to evaluate changes like #27073 There are a few minor changes in behavior: - In bitcoin-qt, when there is a problem reading the configuration file, the GUI error text has changed from "Error: Cannot parse configuration file:" to "Error reading configuration file:" to be consistent with bitcoind. - In bitcoind, when there is a problem reading the settings.json file, the error text has changed from "Failed loading settings file" to "Settings file could not be read" to be consistent with bitcoin-qt. - In bitcoind, when there is a problem writing the settings.json file, the error text has changed from "Failed saving settings file" to "Settings file could not be written" to be consistent with bitcoin-qt. - In bitcoin-qt, if there datadir is not accessible (e.g. no permission to read), there is an normal error dialog showing "Error: filesystem error: status: Permission denied [.../settings.json]", instead of an uncaught exception. ACKs for top commit: Sjors: Light review ACK 802cc1e TheCharlatan: ACK 802cc1e achow101: ACK 802cc1e Tree-SHA512: 9c78d277e9ed595fa8ce286b97d2806e1ec06ddbbe7bd3434bd9dd7b456faf8d989f71231e97311f36edb9caaec645a50c730bd7514b8e0fe6e6f7741b13d981
2 parents d4ebdce + 802cc1e commit fc037c8

16 files changed

+221
-154
lines changed

ci/test/06_script_b.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ if [ "${RUN_TIDY}" = "true" ]; then
4242
( CI_EXEC run-clang-tidy -quiet "${MAKEJOBS}" ) | grep -C5 "error"
4343
export P_CI_DIR="${BASE_BUILD_DIR}/bitcoin-$HOST/"
4444
CI_EXEC "python3 ${DIR_IWYU}/include-what-you-use/iwyu_tool.py"\
45+
" src/common/init.cpp"\
4546
" src/common/url.cpp"\
4647
" src/compat"\
4748
" src/dbwrapper.cpp"\

src/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ BITCOIN_CORE_H = \
134134
clientversion.h \
135135
coins.h \
136136
common/bloom.h \
137+
common/init.h \
137138
common/run_command.h \
138139
common/url.h \
139140
compat/assumptions.h \
@@ -640,6 +641,7 @@ libbitcoin_common_a_SOURCES = \
640641
chainparams.cpp \
641642
coins.cpp \
642643
common/bloom.cpp \
644+
common/init.cpp \
643645
common/interfaces.cpp \
644646
common/run_command.cpp \
645647
compressor.cpp \

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ BITCOIN_TESTS =\
147147
test/timedata_tests.cpp \
148148
test/torcontrol_tests.cpp \
149149
test/transaction_tests.cpp \
150+
test/translation_tests.cpp \
150151
test/txindex_tests.cpp \
151152
test/txpackage_tests.cpp \
152153
test/txreconciliation_tests.cpp \

src/bitcoind.cpp

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include <chainparams.h>
1111
#include <clientversion.h>
12+
#include <common/init.h>
1213
#include <common/url.h>
1314
#include <compat/compat.h>
1415
#include <init.h>
@@ -120,7 +121,7 @@ static bool AppInit(NodeContext& node, int argc, char* argv[])
120121
SetupServerArgs(args);
121122
std::string error;
122123
if (!args.ParseParameters(argc, argv, error)) {
123-
return InitError(Untranslated(strprintf("Error parsing command line arguments: %s\n", error)));
124+
return InitError(Untranslated(strprintf("Error parsing command line arguments: %s", error)));
124125
}
125126

126127
// Process help and version before taking care about datadir
@@ -150,31 +151,17 @@ static bool AppInit(NodeContext& node, int argc, char* argv[])
150151
std::any context{&node};
151152
try
152153
{
153-
if (!CheckDataDirOption(args)) {
154-
return InitError(Untranslated(strprintf("Specified data directory \"%s\" does not exist.\n", args.GetArg("-datadir", ""))));
155-
}
156-
if (!args.ReadConfigFiles(error, true)) {
157-
return InitError(Untranslated(strprintf("Error reading configuration file: %s\n", error)));
158-
}
159-
// Check for chain settings (Params() calls are only valid after this clause)
160-
try {
161-
SelectParams(args.GetChainName());
162-
} catch (const std::exception& e) {
163-
return InitError(Untranslated(strprintf("%s\n", e.what())));
154+
if (auto error = common::InitConfig(args)) {
155+
return InitError(error->message, error->details);
164156
}
165157

166158
// Error out when loose non-argument tokens are encountered on command line
167159
for (int i = 1; i < argc; i++) {
168160
if (!IsSwitchChar(argv[i][0])) {
169-
return InitError(Untranslated(strprintf("Command line contains unexpected token '%s', see bitcoind -h for a list of options.\n", argv[i])));
161+
return InitError(Untranslated(strprintf("Command line contains unexpected token '%s', see bitcoind -h for a list of options.", argv[i])));
170162
}
171163
}
172164

173-
if (!args.InitSettings(error)) {
174-
InitError(Untranslated(error));
175-
return false;
176-
}
177-
178165
// -server defaults to true for bitcoind but not for the GUI so do this here
179166
args.SoftSetBoolArg("-server", true);
180167
// Set this early so that parameter interactions go to console
@@ -210,7 +197,7 @@ static bool AppInit(NodeContext& node, int argc, char* argv[])
210197
}
211198
break;
212199
case -1: // Error happened.
213-
return InitError(Untranslated(strprintf("fork_daemon() failed: %s\n", SysErrorString(errno))));
200+
return InitError(Untranslated(strprintf("fork_daemon() failed: %s", SysErrorString(errno))));
214201
default: { // Parent: wait and exit.
215202
int token = daemon_ep.TokenRead();
216203
if (token) { // Success
@@ -222,7 +209,7 @@ static bool AppInit(NodeContext& node, int argc, char* argv[])
222209
}
223210
}
224211
#else
225-
return InitError(Untranslated("-daemon is not supported on this operating system\n"));
212+
return InitError(Untranslated("-daemon is not supported on this operating system"));
226213
#endif // HAVE_DECL_FORK
227214
}
228215
// Lock data directory after daemonization

src/common/init.cpp

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright (c) 2023 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <common/init.h>
6+
#include <chainparams.h>
7+
#include <fs.h>
8+
#include <tinyformat.h>
9+
#include <util/system.h>
10+
#include <util/translation.h>
11+
12+
#include <algorithm>
13+
#include <exception>
14+
#include <optional>
15+
16+
namespace common {
17+
std::optional<ConfigError> InitConfig(ArgsManager& args, SettingsAbortFn settings_abort_fn)
18+
{
19+
try {
20+
if (!CheckDataDirOption(args)) {
21+
return ConfigError{ConfigStatus::FAILED, strprintf(_("Specified data directory \"%s\" does not exist."), args.GetArg("-datadir", ""))};
22+
}
23+
std::string error;
24+
if (!args.ReadConfigFiles(error, true)) {
25+
return ConfigError{ConfigStatus::FAILED, strprintf(_("Error reading configuration file: %s"), error)};
26+
}
27+
28+
// Check for chain settings (Params() calls are only valid after this clause)
29+
SelectParams(args.GetChainName());
30+
31+
// Create datadir if it does not exist.
32+
const auto base_path{args.GetDataDirBase()};
33+
if (!fs::exists(base_path)) {
34+
// When creating a *new* datadir, also create a "wallets" subdirectory,
35+
// whether or not the wallet is enabled now, so if the wallet is enabled
36+
// in the future, it will use the "wallets" subdirectory for creating
37+
// and listing wallets, rather than the top-level directory where
38+
// wallets could be mixed up with other files. For backwards
39+
// compatibility, wallet code will use the "wallets" subdirectory only
40+
// if it already exists, but never create it itself. There is discussion
41+
// in https://github.com/bitcoin/bitcoin/issues/16220 about ways to
42+
// change wallet code so it would no longer be necessary to create
43+
// "wallets" subdirectories here.
44+
fs::create_directories(base_path / "wallets");
45+
}
46+
const auto net_path{args.GetDataDirNet()};
47+
if (!fs::exists(net_path)) {
48+
fs::create_directories(net_path / "wallets");
49+
}
50+
51+
// Create settings.json if -nosettings was not specified.
52+
if (args.GetSettingsPath()) {
53+
std::vector<std::string> details;
54+
if (!args.ReadSettingsFile(&details)) {
55+
const bilingual_str& message = _("Settings file could not be read");
56+
if (!settings_abort_fn) {
57+
return ConfigError{ConfigStatus::FAILED, message, details};
58+
} else if (settings_abort_fn(message, details)) {
59+
return ConfigError{ConfigStatus::ABORTED, message, details};
60+
} else {
61+
details.clear(); // User chose to ignore the error and proceed.
62+
}
63+
}
64+
if (!args.WriteSettingsFile(&details)) {
65+
const bilingual_str& message = _("Settings file could not be written");
66+
return ConfigError{ConfigStatus::FAILED_WRITE, message, details};
67+
}
68+
}
69+
} catch (const std::exception& e) {
70+
return ConfigError{ConfigStatus::FAILED, Untranslated(e.what())};
71+
}
72+
return {};
73+
}
74+
} // namespace common

src/common/init.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (c) 2023 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_COMMON_INIT_H
6+
#define BITCOIN_COMMON_INIT_H
7+
8+
#include <util/translation.h>
9+
10+
#include <functional>
11+
#include <optional>
12+
#include <string>
13+
#include <vector>
14+
15+
class ArgsManager;
16+
17+
namespace common {
18+
enum class ConfigStatus {
19+
FAILED, //!< Failed generically.
20+
FAILED_WRITE, //!< Failed to write settings.json
21+
ABORTED, //!< Aborted by user
22+
};
23+
24+
struct ConfigError {
25+
ConfigStatus status;
26+
bilingual_str message{};
27+
std::vector<std::string> details{};
28+
};
29+
30+
//! Callback function to let the user decide whether to abort loading if
31+
//! settings.json file exists and can't be parsed, or to ignore the error and
32+
//! overwrite the file.
33+
using SettingsAbortFn = std::function<bool(const bilingual_str& message, const std::vector<std::string>& details)>;
34+
35+
/* Read config files, and create datadir and settings.json if they don't exist. */
36+
std::optional<ConfigError> InitConfig(ArgsManager& args, SettingsAbortFn settings_abort_fn = nullptr);
37+
} // namespace common
38+
39+
#endif // BITCOIN_COMMON_INIT_H

src/index/base.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ static void FatalError(const char* fmt, const Args&... args)
3535
std::string strMessage = tfm::format(fmt, args...);
3636
SetMiscWarning(Untranslated(strMessage));
3737
LogPrintf("*** %s\n", strMessage);
38-
AbortError(_("A fatal internal error occurred, see debug.log for details"));
38+
InitError(_("A fatal internal error occurred, see debug.log for details"));
3939
StartShutdown();
4040
}
4141

src/node/interface_ui.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include <node/interface_ui.h>
66

7+
#include <util/string.h>
78
#include <util/translation.h>
89

910
#include <boost/signals2/optional_last_value.hpp>
@@ -62,6 +63,18 @@ bool InitError(const bilingual_str& str)
6263
return false;
6364
}
6465

66+
bool InitError(const bilingual_str& str, const std::vector<std::string>& details)
67+
{
68+
// For now just flatten the list of error details into a string to pass to
69+
// the base InitError overload. In the future, if more init code provides
70+
// error details, the details could be passed separately from the main
71+
// message for rich display in the GUI. But currently the only init
72+
// functions which provide error details are ones that run during early init
73+
// before the GUI uiInterface is registered, so there's no point passing
74+
// main messages and details separately to uiInterface yet.
75+
return InitError(details.empty() ? str : strprintf(Untranslated("%s:\n%s"), str, MakeUnorderedList(details)));
76+
}
77+
6578
void InitWarning(const bilingual_str& str)
6679
{
6780
uiInterface.ThreadSafeMessageBox(str, "", CClientUIInterface::MSG_WARNING);

src/node/interface_ui.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ void InitWarning(const bilingual_str& str);
116116

117117
/** Show error message **/
118118
bool InitError(const bilingual_str& str);
119-
constexpr auto AbortError = InitError;
119+
bool InitError(const bilingual_str& str, const std::vector<std::string>& details);
120120

121121
extern CClientUIInterface uiInterface;
122122

0 commit comments

Comments
 (0)