Skip to content

Commit 802cc1e

Browse files
committed
Deduplicate bitcoind and bitcoin-qt init code
Add common InitConfig function to deduplicate bitcoind and bitcoin-qt code reading config files and creating the datadir. 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
1 parent d172b5c commit 802cc1e

File tree

8 files changed

+163
-145
lines changed

8 files changed

+163
-145
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/bitcoind.cpp

Lines changed: 3 additions & 16 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>
@@ -150,17 +151,8 @@ 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.", args.GetArg("-datadir", ""))));
155-
}
156-
if (!args.ReadConfigFiles(error, true)) {
157-
return InitError(Untranslated(strprintf("Error reading configuration file: %s", 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", 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
@@ -170,11 +162,6 @@ static bool AppInit(NodeContext& node, int argc, char* argv[])
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

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/qt/bitcoin.cpp

Lines changed: 44 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <qt/bitcoin.h>
1010

1111
#include <chainparams.h>
12+
#include <common/init.h>
1213
#include <init.h>
1314
#include <interfaces/handler.h>
1415
#include <interfaces/init.h>
@@ -165,54 +166,36 @@ static void initTranslations(QTranslator &qtTranslatorBase, QTranslator &qtTrans
165166
}
166167
}
167168

168-
static bool InitSettings()
169+
static bool ErrorSettingsRead(const bilingual_str& error, const std::vector<std::string>& details)
169170
{
170-
gArgs.EnsureDataDir();
171-
if (!gArgs.GetSettingsPath()) {
172-
return true; // Do nothing if settings file disabled.
173-
}
174-
175-
std::vector<std::string> errors;
176-
if (!gArgs.ReadSettingsFile(&errors)) {
177-
std::string error = QT_TRANSLATE_NOOP("bitcoin-core", "Settings file could not be read");
178-
std::string error_translated = QCoreApplication::translate("bitcoin-core", error.c_str()).toStdString();
179-
InitError(Untranslated(strprintf("%s:\n%s", error, MakeUnorderedList(errors))));
180-
181-
QMessageBox messagebox(QMessageBox::Critical, PACKAGE_NAME, QString::fromStdString(strprintf("%s.", error_translated)), QMessageBox::Reset | QMessageBox::Abort);
182-
/*: Explanatory text shown on startup when the settings file cannot be read.
183-
Prompts user to make a choice between resetting or aborting. */
184-
messagebox.setInformativeText(QObject::tr("Do you want to reset settings to default values, or to abort without making changes?"));
185-
messagebox.setDetailedText(QString::fromStdString(MakeUnorderedList(errors)));
186-
messagebox.setTextFormat(Qt::PlainText);
187-
messagebox.setDefaultButton(QMessageBox::Reset);
188-
switch (messagebox.exec()) {
189-
case QMessageBox::Reset:
190-
break;
191-
case QMessageBox::Abort:
192-
return false;
193-
default:
194-
assert(false);
195-
}
196-
}
197-
198-
errors.clear();
199-
if (!gArgs.WriteSettingsFile(&errors)) {
200-
std::string error = QT_TRANSLATE_NOOP("bitcoin-core", "Settings file could not be written");
201-
std::string error_translated = QCoreApplication::translate("bitcoin-core", error.c_str()).toStdString();
202-
InitError(Untranslated(strprintf("%s:\n%s", error, MakeUnorderedList(errors))));
203-
204-
QMessageBox messagebox(QMessageBox::Critical, PACKAGE_NAME, QString::fromStdString(strprintf("%s.", error_translated)), QMessageBox::Ok);
205-
/*: Explanatory text shown on startup when the settings file could not be written.
206-
Prompts user to check that we have the ability to write to the file.
207-
Explains that the user has the option of running without a settings file.*/
208-
messagebox.setInformativeText(QObject::tr("A fatal error occurred. Check that settings file is writable, or try running with -nosettings."));
209-
messagebox.setDetailedText(QString::fromStdString(MakeUnorderedList(errors)));
210-
messagebox.setTextFormat(Qt::PlainText);
211-
messagebox.setDefaultButton(QMessageBox::Ok);
212-
messagebox.exec();
171+
QMessageBox messagebox(QMessageBox::Critical, PACKAGE_NAME, QString::fromStdString(strprintf("%s.", error.translated)), QMessageBox::Reset | QMessageBox::Abort);
172+
/*: Explanatory text shown on startup when the settings file cannot be read.
173+
Prompts user to make a choice between resetting or aborting. */
174+
messagebox.setInformativeText(QObject::tr("Do you want to reset settings to default values, or to abort without making changes?"));
175+
messagebox.setDetailedText(QString::fromStdString(MakeUnorderedList(details)));
176+
messagebox.setTextFormat(Qt::PlainText);
177+
messagebox.setDefaultButton(QMessageBox::Reset);
178+
switch (messagebox.exec()) {
179+
case QMessageBox::Reset:
213180
return false;
181+
case QMessageBox::Abort:
182+
return true;
183+
default:
184+
assert(false);
214185
}
215-
return true;
186+
}
187+
188+
static void ErrorSettingsWrite(const bilingual_str& error, const std::vector<std::string>& details)
189+
{
190+
QMessageBox messagebox(QMessageBox::Critical, PACKAGE_NAME, QString::fromStdString(strprintf("%s.", error.translated)), QMessageBox::Ok);
191+
/*: Explanatory text shown on startup when the settings file could not be written.
192+
Prompts user to check that we have the ability to write to the file.
193+
Explains that the user has the option of running without a settings file.*/
194+
messagebox.setInformativeText(QObject::tr("A fatal error occurred. Check that settings file is writable, or try running with -nosettings."));
195+
messagebox.setDetailedText(QString::fromStdString(MakeUnorderedList(details)));
196+
messagebox.setTextFormat(Qt::PlainText);
197+
messagebox.setDefaultButton(QMessageBox::Ok);
198+
messagebox.exec();
216199
}
217200

218201
/* qDebug() message handler --> debug.log */
@@ -587,45 +570,30 @@ int GuiMain(int argc, char* argv[])
587570
// Gracefully exit if the user cancels
588571
if (!Intro::showIfNeeded(did_show_intro, prune_MiB)) return EXIT_SUCCESS;
589572

590-
/// 6a. Determine availability of data directory
591-
if (!CheckDataDirOption(gArgs)) {
592-
InitError(strprintf(Untranslated("Specified data directory \"%s\" does not exist."), gArgs.GetArg("-datadir", "")));
593-
QMessageBox::critical(nullptr, PACKAGE_NAME,
594-
QObject::tr("Error: Specified data directory \"%1\" does not exist.").arg(QString::fromStdString(gArgs.GetArg("-datadir", ""))));
595-
return EXIT_FAILURE;
596-
}
597-
try {
598-
/// 6b. Parse bitcoin.conf
599-
/// - Do not call gArgs.GetDataDirNet() before this step finishes
600-
if (!gArgs.ReadConfigFiles(error, true)) {
601-
InitError(strprintf(Untranslated("Error reading configuration file: %s"), error));
602-
QMessageBox::critical(nullptr, PACKAGE_NAME,
603-
QObject::tr("Error: Cannot parse configuration file: %1.").arg(QString::fromStdString(error)));
604-
return EXIT_FAILURE;
573+
/// 6-7. Parse bitcoin.conf, determine network, switch to network specific
574+
/// options, and create datadir and settings.json.
575+
// - Do not call gArgs.GetDataDirNet() before this step finishes
576+
// - Do not call Params() before this step
577+
// - QSettings() will use the new application name after this, resulting in network-specific settings
578+
// - Needs to be done before createOptionsModel
579+
if (auto error = common::InitConfig(gArgs, ErrorSettingsRead)) {
580+
InitError(error->message, error->details);
581+
if (error->status == common::ConfigStatus::FAILED_WRITE) {
582+
// Show a custom error message to provide more information in the
583+
// case of a datadir write error.
584+
ErrorSettingsWrite(error->message, error->details);
585+
} else if (error->status != common::ConfigStatus::ABORTED) {
586+
// Show a generic message in other cases, and no additional error
587+
// message in the case of a read error if the user decided to abort.
588+
QMessageBox::critical(nullptr, PACKAGE_NAME, QObject::tr("Error: %1").arg(QString::fromStdString(error->message.translated)));
605589
}
606-
607-
/// 7. Determine network (and switch to network specific options)
608-
// - Do not call Params() before this step
609-
// - Do this after parsing the configuration file, as the network can be switched there
610-
// - QSettings() will use the new application name after this, resulting in network-specific settings
611-
// - Needs to be done before createOptionsModel
612-
613-
// Check for chain settings (Params() calls are only valid after this clause)
614-
SelectParams(gArgs.GetChainName());
615-
} catch(std::exception &e) {
616-
InitError(Untranslated(strprintf("%s", e.what())));
617-
QMessageBox::critical(nullptr, PACKAGE_NAME, QObject::tr("Error: %1").arg(e.what()));
618590
return EXIT_FAILURE;
619591
}
620592
#ifdef ENABLE_WALLET
621593
// Parse URIs on command line
622594
PaymentServer::ipcParseCommandLine(argc, argv);
623595
#endif
624596

625-
if (!InitSettings()) {
626-
return EXIT_FAILURE;
627-
}
628-
629597
QScopedPointer<const NetworkStyle> networkStyle(NetworkStyle::instantiate(Params().NetworkIDString()));
630598
assert(!networkStyle.isNull());
631599
// Allow for separate UI settings for testnets

src/util/system.cpp

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -438,27 +438,6 @@ const fs::path& ArgsManager::GetDataDir(bool net_specific) const
438438
return path;
439439
}
440440

441-
void ArgsManager::EnsureDataDir() const
442-
{
443-
/**
444-
* "/wallets" subdirectories are created in all **new**
445-
* datadirs, because wallet code will create new wallets in the "wallets"
446-
* subdirectory only if exists already, otherwise it will create them in
447-
* the top-level datadir where they could interfere with other files.
448-
* Wallet init code currently avoids creating "wallets" directories itself
449-
* for backwards compatibility, but this be changed in the future and
450-
* wallet code here could go away (#16220).
451-
*/
452-
auto path{GetDataDir(false)};
453-
if (!fs::exists(path)) {
454-
fs::create_directories(path / "wallets");
455-
}
456-
path = GetDataDir(true);
457-
if (!fs::exists(path)) {
458-
fs::create_directories(path / "wallets");
459-
}
460-
}
461-
462441
void ArgsManager::ClearPathCache()
463442
{
464443
LOCK(cs_args);
@@ -502,25 +481,6 @@ bool ArgsManager::IsArgSet(const std::string& strArg) const
502481
return !GetSetting(strArg).isNull();
503482
}
504483

505-
bool ArgsManager::InitSettings(std::string& error)
506-
{
507-
EnsureDataDir();
508-
if (!GetSettingsPath()) {
509-
return true; // Do nothing if settings file disabled.
510-
}
511-
512-
std::vector<std::string> errors;
513-
if (!ReadSettingsFile(&errors)) {
514-
error = strprintf("Failed loading settings file:\n%s\n", MakeUnorderedList(errors));
515-
return false;
516-
}
517-
if (!WriteSettingsFile(&errors)) {
518-
error = strprintf("Failed saving settings file:\n%s\n", MakeUnorderedList(errors));
519-
return false;
520-
}
521-
return true;
522-
}
523-
524484
bool ArgsManager::GetSettingsPath(fs::path* filepath, bool temp, bool backup) const
525485
{
526486
fs::path settings = GetPathArg("-settings", BITCOIN_SETTINGS_FILENAME);

0 commit comments

Comments
 (0)