Skip to content

Commit e248926

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 50085c4 commit e248926

File tree

7 files changed

+156
-145
lines changed

7 files changed

+156
-145
lines changed

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

src/common/init.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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 <optional>
11+
#include <string>
12+
#include <vector>
13+
14+
class ArgsManager;
15+
16+
namespace common {
17+
enum class ConfigStatus {
18+
FAILED, //!< Failed generically.
19+
FAILED_WRITE, //!< Failed to write settings.json
20+
ABORTED, //!< Aborted by user
21+
};
22+
23+
struct ConfigError {
24+
ConfigStatus status;
25+
bilingual_str message{};
26+
std::vector<std::string> details{};
27+
};
28+
29+
//! Callback function to let the user decide to whether abort loading if
30+
//! settings.json file exists and can't be parsed, or to ignore the error and
31+
//! overwrite the file.
32+
using SettingsAbortFn = std::function<bool(const bilingual_str& message, const std::vector<std::string>& details)>;
33+
34+
/* Read config files, and create datadir and settings.json if they don't exist. */
35+
std::optional<ConfigError> InitConfig(ArgsManager& args, SettingsAbortFn settings_abort_fn = nullptr);
36+
} // namespace common
37+
38+
#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()) {
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);

src/util/system.h

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -434,13 +434,6 @@ class ArgsManager
434434
*/
435435
std::optional<unsigned int> GetArgFlags(const std::string& name) const;
436436

437-
/**
438-
* Read and update settings file with saved settings. This needs to be
439-
* called after SelectParams() because the settings file location is
440-
* network-specific.
441-
*/
442-
bool InitSettings(std::string& error);
443-
444437
/**
445438
* Get settings file path, or return false if read-write settings were
446439
* disabled with -nosettings.
@@ -480,12 +473,6 @@ class ArgsManager
480473
*/
481474
void LogArgs() const;
482475

483-
/**
484-
* If datadir does not exist, create it along with wallets/
485-
* subdirectory(s).
486-
*/
487-
void EnsureDataDir() const;
488-
489476
private:
490477
/**
491478
* Get data directory path

0 commit comments

Comments
 (0)