Compare commits

...

3 Commits

Author SHA1 Message Date
Bart
c15af586e1 refactor: Remove the legacy rippled.cfg config fallback
The rename to xrpld.cfg landed in #6098 and shipped in 3.2.0, with a
fallback for the old filename. Search for one name per candidate
directory, so the inner loop over the names collapses into a single
check. Remove kConfigLegacyName, drop the old name from the message
listing the searched directories, and drop its /rippled.cfg entry from
.gitignore. Config.cpp no longer names std::string_view, so its include
goes too.

Two lines in the rename scripts rewrote kConfigLegacyName after their
bulk rename, and have no target now. Delete both. The .gitignore block
in config.sh anchored its insert on the old name, so it now deletes that
entry and appends the new one. Deleting first keeps a .gitignore holding
both names from gaining a duplicate.

The statements in config.sh and in the rename README said the old
filename is no longer accepted. setup does not restrict the basename of
a --conf path, so they now say it is no longer found by the search.

A packaged install is unaffected: the RPM spec installs
/etc/xrpld/xrpld.cfg, and the systemd unit and the logrotate hook both
pass --conf explicitly.
2026-10-03 09:32:30 -04:00
Bart
8fabef3b50 fix: Fail when no config file is read, and report the search
When Config::setup found no config file, it left the config file
pointing at the last name it tried in the last directory. The read
failed there, so the operator was told only that /etc/xrpld/rippled.cfg
was missing. setup then tried to create /var/lib/xrpld, and that throw
reached an uncaught main and aborted the process. A config file named
with --conf and unreadable produced only a warning, and setup carried on
with no settings at all.

The config file is now a local std::optional that setup passes to load,
rather than a configFile_ member. A file that is there but cannot be
read throws, as one with invalid contents already did, so load needs no
return value. When the search found nothing, setup writes every
directory it tried to stderr, skips the data directory, and returns
false. It is [[nodiscard]] bool.

So a false return means one thing only. The search found no config file
at all, which is fatal only without a positional argument, the same
condition that chooses between starting a server and running an RPC
command. That rule is missingConfigIsFatal in the new Main.h. setup
still sets up the defaults and the SSL context in that case, so the RPC
client runs; rpc_call::fromCommandLine takes its destination from
--rpc_ip.

An unreadable config file used to be fatal only when --conf named it. A
file the search found and could not read left RPC mode running on
defaults, so the same file gave exit 1 under --conf and exit 73 when
found by the search. Throwing removes that difference.

--vacuum is excluded from that condition. It returns before the RPC
branch and reads the config itself, so a positional argument beside it
used to skip the check. On a host with no config file, xrpld --vacuum
server_info ran vacuum unconfigured and died with "exception
database_path must be set. in function run" and exit 255. It now reports
the search and exits 1, as --vacuum alone already did.

run() wraps its setup call to catch what setup throws, print "xrpld:
invalid configuration: <reason>", and exit 1.

Also fixes the read-failure message, which wrapped the path in single
quotes on top of the double quotes that streaming a
std::filesystem::path adds, and ran the error number onto a period.
Every message here is built with std::format. The data directory throw
names the reason from its error_code, which was captured and then
discarded.

Config_test asserts the result of every setup call, covers a config file
that cannot be read, and covers the case where no directory holds one.
The second captures stderr and asserts all three searched directories
are named. The first asserts the message thrown and that no data
directory is left beside the file.

The guard that skips the data directory when nothing was read is not
reachable from a suite. Its one remaining path is a search that found
nothing, where the data directory is the last candidate's, /var/lib
under the system name, which a test cannot redirect. A built binary
covers it. On a host with no config file the run reports the searched
directories and exits 1, creating no data directory.

missingConfigIsFatal is a header-only predicate so that xrpld_tests can
reach it without linking server code, and its four combinations are
covered there.

run() itself is not reachable from a suite, so it is verified against a
built binary. An unreadable config file exits 1 whether --conf named it
or the search found it, and with or without a positional argument. With
no config file anywhere and a positional argument, the run still reaches
the RPC client.
2026-10-03 09:32:29 -04:00
Bart
f95f7889f9 refactor: Search config directories over a candidate list
Config::setup searched the working directory, the XDG config directory
and the system config directory with a do/while(false) block and breaks,
trying both file names in each.

Replace it with a list of `Candidate` values, each naming a config
directory and its data directory, and a loop that takes the first
holding a config file. Add a docstring for the search order.

The directories chosen and the file reported are unchanged. The last
candidate is now also checked under the legacy name, which the old code
assigned without checking, so one more filesystem check runs when
nothing is found.
2026-10-02 21:19:48 +00:00
12 changed files with 383 additions and 135 deletions

View File

@@ -194,6 +194,7 @@ tests.libxrpl > xrpl.resource
tests.libxrpl > xrpl.server
tests.libxrpl > xrpl.shamap
tests.libxrpl > xrpl.tx
tests.xrpld > xrpld.app
tests.xrpld > xrpld.rpc
tests.xrpld > xrpl.json
tests.xrpld > xrpl.protocol

View File

@@ -33,7 +33,8 @@ run from the repository root.
namespaces from `ripple` to `xrpl`.
6. `.github/scripts/rename/config.sh`: This script will rename the config from
`rippled.cfg` to `xrpld.cfg`, and updating the code accordingly. The old
filename will still be accepted.
filename is no longer found by the search, though `--conf` still accepts a
path to a file of any name.
7. `.github/scripts/rename/docs.sh`: This script will rename any lingering
references of `ripple(d)` to `xrpl(d)` in code, comments, and documentation.

View File

@@ -14,7 +14,8 @@ if [[ "${OSTYPE}" == 'darwin'* ]]; then
fi
# This script renames the config from `rippled.cfg` to `xrpld.cfg`, and updates
# the code accordingly. The old filename will still be accepted.
# the code accordingly. The old filename is no longer found by the search,
# though `--conf` still accepts a path to a file of any name.
# Usage: .github/scripts/rename/config.sh <repository directory>
if [ "$#" -ne 1 ]; then
@@ -30,10 +31,11 @@ if [ ! -d "${DIRECTORY}" ]; then
fi
pushd "${DIRECTORY}"
# Add the xrpld.cfg to the .gitignore.
# Swap the rippled.cfg entry in the .gitignore for xrpld.cfg. Dropping the old
# entry first keeps a .gitignore that carries both from gaining a duplicate.
${SED_COMMAND} -i '/^\/rippled\.cfg$/d' .gitignore
if ! grep -q 'xrpld.cfg' .gitignore; then
${SED_COMMAND} -i '/rippled.cfg/a\
/xrpld.cfg' .gitignore
printf '/xrpld.cfg\n' >>.gitignore
fi
# Rename the files.
@@ -61,9 +63,6 @@ ${SED_COMMAND} -i 's/ripplevalidators/xrplvalidators/g' src/test/core/Config_tes
${SED_COMMAND} -i 's@ripple/@xrpld/@g' src/test/core/Config_test.cpp
${SED_COMMAND} -i 's/Rippled/File/g' src/test/core/Config_test.cpp
# Restore the old config file name in the code that maintains support for now.
${SED_COMMAND} -i 's/kConfigLegacyName = "xrpld.cfg"/kConfigLegacyName = "rippled.cfg"/g' src/xrpld/core/Config.h
# Restore an URL.
${SED_COMMAND} -i 's/connect-your-xrpld-to-the-xrp-test-net.html/connect-your-rippled-to-the-xrp-test-net.html/g' cfg/xrpld-example.cfg

View File

@@ -90,7 +90,6 @@ ${SED_COMMAND} -i 's/www.ripple.com/www.xrpl.org/g' src/test/protocol/Seed_test.
# Restore specific changes.
${SED_COMMAND} -i 's@b5efcc/src/xrpld@b5efcc/src/ripple@' include/xrpl/protocol/README.md
${SED_COMMAND} -i 's/dbPrefix_ = "xrpldb"/dbPrefix_ = "rippledb"/' src/xrpld/app/misc/SHAMapStoreImp.h # cspell: disable-line
${SED_COMMAND} -i 's/kConfigLegacyName = "xrpld.cfg"/kConfigLegacyName = "rippled.cfg"/' src/xrpld/core/Config.h
popd
echo "Renaming complete."

1
.gitignore vendored
View File

@@ -36,7 +36,6 @@ gmon.out
!.github/scripts/levelization/results/ordering.txt
# Customized configs.
/rippled.cfg
/xrpld.cfg
/validators.txt

View File

@@ -20,10 +20,13 @@
#include <filesystem>
#include <format>
#include <fstream>
#include <iostream>
#include <optional>
#include <ostream>
#include <regex>
#include <sstream>
#include <stdexcept>
#include <streambuf>
#include <string>
#include <string_view>
#include <typeinfo>
@@ -199,6 +202,53 @@ public:
#endif // _MSC_VER
/**
* Send a stream's output to a buffer of our own, and restore it when done.
*/
class StreamRedirectGuard
{
private:
// The saved_ member is initialized from captured_.rdbuf(), so captured_
// must stay declared before it.
std::ostream& stream_;
std::ostringstream captured_;
std::streambuf* const saved_;
public:
/**
* Send the stream's output to this guard's own buffer.
*
* @param stream Stream to redirect for the guard's lifetime.
*/
explicit StreamRedirectGuard(std::ostream& stream)
: stream_(stream), saved_(stream.rdbuf(captured_.rdbuf()))
{
}
StreamRedirectGuard(StreamRedirectGuard const&) = delete;
StreamRedirectGuard&
operator=(StreamRedirectGuard const&) = delete;
/**
* Read what the stream has written so far.
*
* @return Everything written to the stream since construction.
*/
[[nodiscard]] std::string
str() const
{
return captured_.str();
}
/**
* Send the stream's output back where it went before.
*/
~StreamRedirectGuard()
{
stream_.rdbuf(saved_);
}
};
/**
* Write an xrpld config file and remove when done.
*/
@@ -233,11 +283,13 @@ public:
dataDir_ = subdir() / Path(Config::kDatabaseDirName);
rmDataDir_ = !exists(dataDir_);
config_.setup(
file_.string(),
/* bQuiet */ true,
/* bSilent */ false,
/* bStandalone */ false);
test_.expect(
config_.setup(
file_.string(),
/* bQuiet */ true,
/* bSilent */ false,
/* bStandalone */ false),
"setup failed for " + file_.string());
}
[[nodiscard]] Config const&
@@ -388,36 +440,53 @@ port_wss_admin
using namespace std::filesystem;
auto const cwd = current_path();
// Test both config file names.
std::string_view const configFiles[] = {Config::kConfigFileName, Config::kConfigLegacyName};
// Config file in current directory.
for (auto const& configFile : configFiles)
{
// Use a temporary directory for testing.
TempDir const td;
current_path(td.path());
Path const f = td.file(std::string{configFile});
Path const f = td.file(std::string{Config::kConfigFileName});
std::ofstream o(f.string());
o << detail::configContents("", "");
o.close();
// Load the config file from the current directory and verify it.
Config c;
c.setup("", true, false, true);
BEAST_EXPECT(c.setup("", true, false, true));
BEAST_EXPECT(c.section(Sections::kDebugLogfile).values().size() == 1);
BEAST_EXPECT(
c.section(Sections::kDebugLogfile).values()[0] ==
"/Users/dummy/xrpld/config/log/debug.log");
}
// A config file that cannot be read throws, whether it was named or
// found by the search. The throw leaves the data directory alone, so
// nothing is created beside the file that could not be read.
{
TempDir const td;
Path const missing = Path(td.file("does_not_exist.cfg"));
std::string error;
try
{
Config c;
BEAST_EXPECT(!c.setup(missing.string(), true, false, false));
}
catch (std::runtime_error const& e)
{
error = e.what();
}
BEAST_EXPECT(error.starts_with("Failed to read '" + missing.string() + "'"));
// Nothing was read, so no data directory is created.
BEAST_EXPECT(!exists(Path(td.path()) / std::string{Config::kDatabaseDirName}));
}
// Config file in HOME or XDG_CONFIG_HOME directory.
#ifndef _MSC_VER
// Save the values the guards below must put back.
auto const home = detail::envVar("HOME");
auto const xdgConfigHome = detail::envVar("XDG_CONFIG_HOME");
for (auto const& configFile : configFiles)
{
// Point the current working directory to a temporary directory, so
// we don't pick up an actual config file from the repository root.
@@ -438,14 +507,14 @@ port_wss_admin
// Create the config file in '${XDG_CONFIG_HOME}/[systemName]'.
Path p = tc.file(systemName());
create_directory(p);
p = tc.file(systemName() + "/" + std::string{configFile});
p = tc.file(systemName() + "/" + std::string{Config::kConfigFileName});
std::ofstream o(p.string());
o << detail::configContents("", "");
o.close();
// Load the config file from the config directory and verify it.
Config c;
c.setup("", true, false, true);
BEAST_EXPECT(c.setup("", true, false, true));
BEAST_EXPECT(c.section(Sections::kDebugLogfile).values().size() == 1);
BEAST_EXPECT(
c.section(Sections::kDebugLogfile).values()[0] ==
@@ -473,14 +542,14 @@ port_wss_admin
s += "/" + systemName();
p = tc.file(s);
create_directory(p);
p = tc.file(s + "/" + std::string{configFile});
p = tc.file(s + "/" + std::string{Config::kConfigFileName});
std::ofstream o(p.string());
o << detail::configContents("", "");
o.close();
// Load the config file from the config directory and verify it.
Config c;
c.setup("", true, false, true);
BEAST_EXPECT(c.setup("", true, false, true));
BEAST_EXPECT(c.section(Sections::kDebugLogfile).values().size() == 1);
BEAST_EXPECT(
c.section(Sections::kDebugLogfile).values()[0] ==
@@ -491,6 +560,60 @@ port_wss_admin
BEAST_EXPECT(detail::envVar("HOME") == home);
BEAST_EXPECT(detail::envVar("XDG_CONFIG_HOME") == xdgConfigHome);
}
// No config file in any searched directory.
{
// The system config directory is the last one searched and is not
// under the test's control, so only run this when it holds no
// config file.
Path const systemDir = Path("/etc") / systemName();
Path const systemFile = systemDir / std::string{Config::kConfigFileName};
if (exists(systemFile))
{
log << "Skipping: " << systemFile.string() << " exists." << std::endl;
}
else
{
// Point the working directory and HOME at empty temporary
// directories. The guards are declared after tc, so they restore
// before it is removed.
TempDir const td;
current_path(td.path());
TempDir const tc;
detail::EnvVarGuard const homeGuard("HOME", tc.path());
detail::EnvVarGuard const xdgGuard("XDG_CONFIG_HOME", std::nullopt);
// The three directories setup() reports. Read the working
// directory back, because current_path() can resolve a symlink.
Path const searchedCwd = current_path();
std::string const searchedXdgDir = tc.path() + "/.config/" + systemName();
std::string const searchedSystemDir = systemDir.string();
// Capture stderr around the setup() call only, so the
// assertions below keep their own output.
Config c;
bool setupResult = true;
std::string reported;
{
detail::StreamRedirectGuard const capture(std::cerr);
setupResult = c.setup("", true, false, true);
reported = capture.str();
}
BEAST_EXPECT(!setupResult);
// Assert the directory paths, not the wording of the header.
BEAST_EXPECT(reported.contains(searchedCwd.string()));
BEAST_EXPECT(reported.contains(searchedXdgDir));
BEAST_EXPECT(reported.contains(searchedSystemDir));
// A run with no config file names no data directory. This call
// is standalone, which clears it anyway, so the check states the
// result rather than proving the creation step was skipped.
BEAST_EXPECT(c.legacy(Sections::kDatabasePath).empty());
}
}
#endif // _MSC_VER
// Restore the current working directory.
@@ -1085,11 +1208,11 @@ trust-these-validators.gov
*/
{
Config config;
config.setup(
BEAST_EXPECT(config.setup(
cfg.configFile(),
/*bQuiet*/ false,
/* bSilent */ false,
/* bStandalone */ false);
/* bStandalone */ false));
BEAST_EXPECT(!config.quiet());
BEAST_EXPECT(!config.silent());
BEAST_EXPECT(!config.standalone());
@@ -1098,11 +1221,11 @@ trust-these-validators.gov
}
{
Config config;
config.setup(
BEAST_EXPECT(config.setup(
cfg.configFile(),
/*bQuiet*/ true,
/* bSilent */ false,
/* bStandalone */ false);
/* bStandalone */ false));
BEAST_EXPECT(config.quiet());
BEAST_EXPECT(!config.silent());
BEAST_EXPECT(!config.standalone());
@@ -1111,11 +1234,11 @@ trust-these-validators.gov
}
{
Config config;
config.setup(
BEAST_EXPECT(config.setup(
cfg.configFile(),
/*bQuiet*/ false,
/* bSilent */ true,
/* bStandalone */ false);
/* bStandalone */ false));
BEAST_EXPECT(config.quiet());
BEAST_EXPECT(config.silent());
BEAST_EXPECT(!config.standalone());
@@ -1124,11 +1247,11 @@ trust-these-validators.gov
}
{
Config config;
config.setup(
BEAST_EXPECT(config.setup(
cfg.configFile(),
/*bQuiet*/ true,
/* bSilent */ true,
/* bStandalone */ false);
/* bStandalone */ false));
BEAST_EXPECT(config.quiet());
BEAST_EXPECT(config.silent());
BEAST_EXPECT(!config.standalone());
@@ -1137,11 +1260,11 @@ trust-these-validators.gov
}
{
Config config;
config.setup(
BEAST_EXPECT(config.setup(
cfg.configFile(),
/*bQuiet*/ false,
/* bSilent */ false,
/* bStandalone */ true);
/* bStandalone */ true));
BEAST_EXPECT(!config.quiet());
BEAST_EXPECT(!config.silent());
BEAST_EXPECT(config.standalone());
@@ -1150,11 +1273,11 @@ trust-these-validators.gov
}
{
Config config;
config.setup(
BEAST_EXPECT(config.setup(
cfg.configFile(),
/*bQuiet*/ true,
/* bSilent */ false,
/* bStandalone */ true);
/* bStandalone */ true));
BEAST_EXPECT(config.quiet());
BEAST_EXPECT(!config.silent());
BEAST_EXPECT(config.standalone());
@@ -1163,11 +1286,11 @@ trust-these-validators.gov
}
{
Config config;
config.setup(
BEAST_EXPECT(config.setup(
cfg.configFile(),
/*bQuiet*/ false,
/* bSilent */ true,
/* bStandalone */ true);
/* bStandalone */ true));
BEAST_EXPECT(config.quiet());
BEAST_EXPECT(config.silent());
BEAST_EXPECT(config.standalone());
@@ -1176,11 +1299,11 @@ trust-these-validators.gov
}
{
Config config;
config.setup(
BEAST_EXPECT(config.setup(
cfg.configFile(),
/*bQuiet*/ true,
/* bSilent */ true,
/* bStandalone */ true);
/* bStandalone */ true));
BEAST_EXPECT(config.quiet());
BEAST_EXPECT(config.silent());
BEAST_EXPECT(config.standalone());

View File

@@ -18,7 +18,7 @@ target_link_libraries(
PRIVATE GTest::gtest GTest::gmock xrpl.libxrpl rpcspec::rpcspec
)
set(test_modules rpc)
set(test_modules app rpc)
foreach(module IN LISTS test_modules)
file(

View File

@@ -0,0 +1,31 @@
#include <xrpld/app/main/Main.h>
#include <gtest/gtest.h>
namespace xrpl {
// A config file that was read is never fatal.
TEST(MissingConfigIsFatal, read_in_rpc_mode)
{
EXPECT_FALSE(missingConfigIsFatal(true, true));
}
TEST(MissingConfigIsFatal, read_for_a_server_start)
{
EXPECT_FALSE(missingConfigIsFatal(true, false));
}
// The search found nothing. The RPC client runs anyway, a server does not.
TEST(MissingConfigIsFatal, search_found_nothing_in_rpc_mode)
{
EXPECT_FALSE(missingConfigIsFatal(false, true));
}
TEST(MissingConfigIsFatal, search_found_nothing_for_a_server_start)
{
EXPECT_TRUE(missingConfigIsFatal(false, false));
}
} // namespace xrpl

View File

@@ -1,3 +1,5 @@
#include <xrpld/app/main/Main.h>
#include <xrpld/app/main/Application.h>
#include <xrpld/core/Config.h>
#include <xrpld/core/TimeKeeper.h>
@@ -574,9 +576,25 @@ run(int argc, char** argv)
auto configFile = vm.contains("conf") ? vm["conf"].as<std::string>() : std::string();
// config file, quiet flag.
config->setup(
configFile, vm.contains("quiet"), vm.contains("silent"), vm.contains("standalone"));
// A positional argument selects the RPC client, which needs no config
// file. --vacuum returns before that branch and does need one.
bool const rpcClientMode = vm.contains("parameters") && !vm.contains("vacuum");
// Setup returns false when the search found no config file, and throws for
// one it cannot read or whose contents are invalid. It reports the reason.
try
{
bool const configRead = config->setup(
configFile, vm.contains("quiet"), vm.contains("silent"), vm.contains("standalone"));
if (missingConfigIsFatal(configRead, rpcClientMode))
return 1;
}
catch (std::exception const& e)
{
std::cerr << "xrpld: invalid configuration: " << e.what() << std::endl;
return 1;
}
if (vm.contains("vacuum"))
{

22
src/xrpld/app/main/Main.h Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
namespace xrpl {
/**
* Whether the absence of a config file must end the run.
*
* Config::setup throws for a file it found but could not read, so reaching here
* with nothing read means the search found no file at all. That is fatal only
* for a server start, because the RPC client runs without one.
*
* @param configRead Whether setup read a config file.
* @param rpcClientMode Whether a positional argument selects the RPC client.
* @return True when the run cannot continue.
*/
[[nodiscard]] inline bool
missingConfigIsFatal(bool configRead, bool rpcClientMode)
{
return !configRead && !rpcClientMode;
}
} // namespace xrpl

View File

@@ -90,7 +90,6 @@ class Config : public BasicConfig
public:
// Settings related to the configuration file location and directories
static constexpr std::string_view kConfigFileName = "xrpld.cfg";
static constexpr std::string_view kConfigLegacyName = "rippled.cfg";
static constexpr std::string_view kDatabaseDirName = "db";
static constexpr std::string_view kValidatorsFileName = "validators.txt";
@@ -100,17 +99,21 @@ public:
[[nodiscard]] std::filesystem::path
getDebugLogFile() const;
private:
std::filesystem::path configFile_;
public:
std::filesystem::path configDir;
private:
std::filesystem::path debugLogfile_;
/**
* Read and parse the given config file.
*
* @param configFile Path of the config file to read.
* @throws std::runtime_error When the file cannot be read, or its contents
* are invalid.
*/
void
load();
load(std::filesystem::path const& configFile);
beast::Journal const j_;
bool quiet_ = false; // Minimize logging verbosity.
@@ -330,9 +333,28 @@ public:
public:
Config();
/* Be very careful to make sure these bool params
are in the right order. */
void
/**
* Set up the configuration and determine the config and data directories.
*
* When no config file is named, searches the working directory, then the XDG
* config directory when HOME is set, then the system config directory, and
* takes the first holding a config file.
*
* Be very careful to make sure these bool params are in the right order.
*
* @param strConf Path of the config file, or empty to search for it.
* @param bQuiet Whether to minimize logging verbosity.
* @param bSilent Whether to write no console output after startup.
* @param bStandalone Whether to operate in stand-alone mode.
* @return True when a config file was found and read. False means the
* search found none, and writes the directories it tried to
* stderr. The defaults and the SSL context are still set up; only
* the data directory is skipped.
* @throws std::runtime_error When a config file was found but cannot be
* read, when its contents are invalid, or when the data directory
* cannot be created.
*/
[[nodiscard]] bool
setup(std::string const& strConf, bool bQuiet, bool bSilent, bool bStandalone);
void

View File

@@ -297,7 +297,7 @@ Config::setupControl(bool bQuiet, bool bSilent, bool bStandalone)
XRPL_ASSERT(nodeSize <= 4, "xrpl::Config::setupControl : node size is set");
}
void
bool
Config::setup(std::string const& strConf, bool bQuiet, bool bSilent, bool bStandalone)
{
setupControl(bQuiet, bSilent, bStandalone);
@@ -309,95 +309,123 @@ Config::setup(std::string const& strConf, bool bQuiet, bool bSilent, bool bStand
// directory.
std::filesystem::path dataDir;
// The config file to read. It stays unset when the search for one finds
// nothing.
std::optional<std::filesystem::path> configFile;
if (!strConf.empty())
{
// --conf=<path> : everything is relative that file.
configFile_ = strConf;
configDir = std::filesystem::absolute(configFile_);
configFile = strConf;
configDir = std::filesystem::absolute(*configFile);
configDir.remove_filename();
dataDir = configDir / kDatabaseDirName;
}
else
{
do
// A config directory, paired with its data directory.
struct Candidate
{
// Check if either of the config files exist in the current working
// directory, in which case the databases will be stored in a
// subdirectory.
configDir = std::filesystem::current_path();
dataDir = configDir / kDatabaseDirName;
configFile_ = configDir / kConfigFileName;
if (std::filesystem::exists(configFile_))
break;
configFile_ = configDir / kConfigLegacyName;
if (std::filesystem::exists(configFile_))
break;
std::filesystem::path configDir;
std::filesystem::path dataDir;
};
// Check if the home directory is set, and optionally the XDG config
// and/or data directories, as the config may be there. See
// http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html.
auto const strHome = getEnvVar("HOME");
if (!strHome.empty())
// The candidates to search, in order.
std::vector<Candidate> candidates;
candidates.reserve(3);
// The current working directory, with the databases in a subdirectory.
auto const cwd = std::filesystem::current_path();
candidates.emplace_back(cwd, cwd / kDatabaseDirName);
// The XDG config and data directories, which only apply when the home
// directory is set. See
// http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html.
if (auto const strHome = getEnvVar("HOME"); !strHome.empty())
{
auto strXdgConfigHome = getEnvVar("XDG_CONFIG_HOME");
if (strXdgConfigHome.empty())
{
auto strXdgConfigHome = getEnvVar("XDG_CONFIG_HOME");
auto strXdgDataHome = getEnvVar("XDG_DATA_HOME");
if (strXdgConfigHome.empty())
{
// $XDG_CONFIG_HOME was not set, use default based on $HOME.
strXdgConfigHome = strHome + "/.config";
}
if (strXdgDataHome.empty())
{
// $XDG_DATA_HOME was not set, use default based on $HOME.
strXdgDataHome = strHome + "/.local/share";
}
// $XDG_CONFIG_HOME was not set, use default based on $HOME.
strXdgConfigHome = strHome + "/.config";
}
auto strXdgDataHome = getEnvVar("XDG_DATA_HOME");
if (strXdgDataHome.empty())
{
// $XDG_DATA_HOME was not set, use default based on $HOME.
strXdgDataHome = strHome + "/.local/share";
}
candidates.emplace_back(
strXdgConfigHome + "/" + systemName(), strXdgDataHome + "/" + systemName());
}
// Check if either of the config files exist in the XDG config
// dir.
dataDir = strXdgDataHome + "/" + systemName();
configDir = strXdgConfigHome + "/" + systemName();
configFile_ = configDir / kConfigFileName;
if (std::filesystem::exists(configFile_))
break;
configFile_ = configDir / kConfigLegacyName;
if (std::filesystem::exists(configFile_))
break;
// The system config directory, as a last resort.
candidates.emplace_back("/etc/" + systemName(), "/var/lib/" + systemName());
// Take the first candidate holding a config file. When none does,
// configFile stays unset.
for (auto const& candidate : candidates)
{
configDir = candidate.configDir;
dataDir = candidate.dataDir;
auto candidateFile = configDir / kConfigFileName;
if (std::filesystem::exists(candidateFile))
{
configFile = std::move(candidateFile);
break;
}
}
if (!configFile)
{
// Report every directory searched, even when quiet, since this is
// the reason for the failure.
std::cerr << std::format("No {} found. Searched:\n", kConfigFileName);
for (auto const& candidate : candidates)
std::cerr << std::format(" {}\n", candidate.configDir.string());
}
}
// Read the config file, which updates the default values. A file that is
// there but cannot be read throws, so getting past this means the file was
// read. The rest of this function still runs when the search found none,
// so that the command line RPC client gets the defaults and the SSL
// context. The caller decides whether that is fatal.
bool const configRead = configFile.has_value();
if (configRead)
load(*configFile);
// Only a config file that was read can name a data directory.
if (configRead)
{
{
// load() may have set a new value for the dataDir
std::string const dbPath(legacy(Sections::kDatabasePath));
if (!dbPath.empty())
{
dataDir = std::filesystem::path(dbPath);
}
else if (runStandalone_)
{
dataDir.clear();
}
}
if (!dataDir.empty())
{
std::error_code ec;
std::filesystem::create_directories(dataDir, ec);
if (ec)
{
Throw<std::runtime_error>(
std::format("Can not create {}: {}", dataDir.string(), ec.message()));
}
// As a last resort, check the system config directory.
dataDir = "/var/lib/" + systemName();
configDir = "/etc/" + systemName();
configFile_ = configDir / kConfigFileName;
if (std::filesystem::exists(configFile_))
break;
configFile_ = configDir / kConfigLegacyName;
} while (false);
}
// Update default values
load();
{
// load() may have set a new value for the dataDir
std::string const dbPath(legacy(Sections::kDatabasePath));
if (!dbPath.empty())
{
dataDir = std::filesystem::path(dbPath);
legacy(Sections::kDatabasePath, std::filesystem::absolute(dataDir).string());
}
else if (runStandalone_)
{
dataDir.clear();
}
}
if (!dataDir.empty())
{
std::error_code ec;
std::filesystem::create_directories(dataDir, ec);
if (ec)
Throw<std::runtime_error>(std::format("Can not create {}", dataDir.string()));
legacy(Sections::kDatabasePath, std::filesystem::absolute(dataDir).string());
}
HTTPClient::initializeSSLContext(this->sslVerifyDir, this->sslVerifyFile, this->sslVerify, j_);
@@ -410,6 +438,8 @@ Config::setup(std::string const& strConf, bool bQuiet, bool bSilent, bool bStand
Section const& nodeDbSection{section(Sections::kNodeDatabase)};
getIfExists(nodeDbSection, Keys::kFastLoad, fastLoad);
return configRead;
}
// 0 ports are allowed for unit tests, but still not allowed to be present in
@@ -441,22 +471,25 @@ checkZeroPorts(Config const& config)
}
void
Config::load()
Config::load(std::filesystem::path const& configFile)
{
// A std::filesystem::path has no std::formatter, and streaming one adds
// quotes of its own.
auto const fileName = configFile.string();
// NOTE: this writes to cerr because we want cout to be reserved
// for the writing of the json response (so that stdout can be part of a
// pipeline, for instance)
if (!quiet_)
std::cerr << "Loading: " << configFile_ << "\n";
std::cerr << std::format("Loading: '{}'\n", fileName);
std::error_code ec;
auto const fileContents = getFileContents(ec, configFile_);
auto const fileContents = getFileContents(ec, configFile);
if (ec)
{
std::cerr << "Failed to read '" << configFile_ << "'." << ec.value() << ": " << ec.message()
<< std::endl;
return;
Throw<std::runtime_error>(
std::format("Failed to read '{}': {} (error {})", fileName, ec.message(), ec.value()));
}
loadFromString(fileContents);