Compare commits

...

5 Commits

Author SHA1 Message Date
Bart
a6fe6c3cfa fix: Fail when the config file cannot be read
Naming a config file with --conf is an explicit request for that file, but a
failure to read it only produced a warning. setup then carried on with no
settings at all, so the operator's next error came from somewhere unrelated.
Pointing --conf at a missing file reported the read failure and then failed
again inside --vacuum, complaining about a transaction.db under a directory that
was never going to hold one.

Config::load now returns whether it read the file, and setup fails when it did
not. A named file that is missing or unreadable therefore stops at the read
failure, which already names the file and the reason. This also covers a file
found by the search that cannot be read, which existence alone does not
guarantee.

Both docstrings widen to match. load gains a @return for its new contract, and
setup's @return now covers a config file that could not be read as well as one
that could not be found. The two failures stay distinguishable in the output. A
file that could not be read is reported as "Failed to read '<path>': <reason>",
while a search that found nothing lists every directory it looked in.

Config_test covers a named config file that does not exist.
2026-09-29 13:49:57 -04:00
Bart
4910cae183 fix: Report every directory searched when no config file is found
When Config::setup searched for a config file and found none, it left the config
file pointing at the last name it tried in the last directory. The read then
failed there, so the operator was told only that /etc/xrpld/rippled.cfg was
missing, as if that were the one place a config file belongs. Worse, setup
carried on and tried to create /var/lib/xrpld, whose failure threw past an
uncaught main and aborted the process, so the reported reason was the data
directory rather than the config file.

The config file is now a local std::optional that setup passes to load, rather
than a configFile_ member. It stays unset when the search finds nothing. In that
case setup names both config file names, writes every directory it searched to
stderr, and returns false, before it reaches the data directory. setup is
[[nodiscard]] bool so no caller can ignore the failure. Holding the path in a
local also means no value can survive from one setup call into the next, and
load no longer needs to guard against an unset member.

Invalid config contents are still reported by throwing, which the setup
docstring now states alongside the return value. run() wraps its setup call to
catch those, print "xrpld: invalid configuration: <reason>", and exit 1, so the
"Can not create" throw becomes a clean exit rather than an abort. main() is left
alone, and the catch covers the setup call only, so an exception escaping the
running server still reaches std::terminate with its original stack, and so its
core dump, intact.

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 straight onto a period. Both messages this fix adds, and the
two that load() writes, use std::format, which the file already uses elsewhere.

Config_test now asserts the result of every setup call, and covers the case
where no directory holds a config file under either name. That case captures
stderr around the setup call and asserts that all three searched directories are
named, so a regression that reports only the last one fails the test.
2026-09-29 13:49:57 -04:00
Bart
3fb37ef6be refactor: Search config directories over a candidate list
Config::setup used a do/while(false) block as a goto substitute to search the
current working directory, the XDG config directory, and the system config
directory for a config file, trying the current and the legacy file name in
each. Every step was a near copy of the others, and the terminal step had to
fall out of the loop with no check to leave the system directory standing as
the default.

This replaces that block with a list of `Candidate` values, each naming a
config directory and the data directory that goes with it, and a loop that
takes the first config directory holding a config file under either name. The
search order is data rather than control flow, and the default falls out of the
loop ending on its last element. The list reserves room for the three entries
it can hold at most. `setup` also gains a docstring that states the search
order it implements.

Behavior is unchanged. When no config file is found, the loop now also checks
the system config directory under the legacy name, which the old code assigned
without checking, so that case performs one more filesystem check than before.
The directories chosen and the file name reported are the same.
2026-09-29 13:49:57 -04:00
Bart
12ff0fb924 test: Restore config test environment variables with a guard
The two blocks in `testConfigFile` that redirect the config search saved
`HOME` and `XDG_CONFIG_HOME` as the pointer `getenv` returned, overwrote them
with `setenv`, and then restored them through that same pointer. POSIX allows
`setenv` to release the string an earlier `getenv` returned, so the restore
could read freed memory and write back a wrong value.

The blocks also restored only on the normal path. `create_directory` and
`Config::setup` both throw on failure, and a throw between the `setenv` and the
restore left both variables pointing into a temporary directory that is deleted
moments later, which the rest of the suite then inherits.

Add `detail::EnvVarGuard`. It copies the current value into an
`std::optional<std::string>` on construction, writes the new value, and writes
the saved value back in its destructor. `std::nullopt` means "not set", both
as the value to write and as the saved state, because `unsetenv` succeeds on a
variable that is not set. Convert both blocks to the guard. Each guard is
declared after the `TempDir` whose path it installs, so it restores before
that directory is removed.

`setenv` and `unsetenv` are POSIX, and Windows declares neither, so the guard
is compiled under the same `BOOST_OS_LINUX || BOOST_OS_MACOS` condition as the
test cases that use it. Without that condition the class sits at file scope,
and its member function bodies are compiled whether or not anything calls
them, which breaks the Windows build that CI runs on every pull request.

No test case or check was added or removed.
2026-09-29 13:49:56 -04:00
Bart
eaaee26a3a refactor: Make the config name constants constexpr string views
`Config::kConfigFileName`, `kConfigLegacyName`, `kDatabaseDirName` and
`kValidatorsFileName` were `static char const* const` members declared in
Config.h and defined out of line in Config.cpp. Declare and initialize them as
`static constexpr std::string_view` in Config.h, and delete the four
out-of-line definitions. The values are unchanged, so behavior is unchanged.
The constants now carry a compile-time length and no longer need a definition
in a translation unit. `std::filesystem::path` accepts a `std::string_view` as
a source, so `configDir / kConfigFileName`, `path(kDatabaseDirName)` and
passing a constant to a `path const&` parameter all keep working. No call site
needed a change.

Two rename scripts rewrite `kConfigLegacyName` back to `rippled.cfg` after
their bulk rename. Both named src/xrpld/core/detail/Config.cpp, which no
longer holds the constant, so retarget both to src/xrpld/core/Config.h. A
`sed` expression that matches nothing exits 0, so without this the scripts
would keep passing while doing nothing.
2026-09-29 13:49:54 -04:00
6 changed files with 397 additions and 120 deletions

View File

@@ -62,7 +62,7 @@ ${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/detail/Config.cpp
${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,7 @@ ${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/detail/Config.cpp
${SED_COMMAND} -i 's/kConfigLegacyName = "xrpld.cfg"/kConfigLegacyName = "rippled.cfg"/' src/xrpld/core/Config.h
popd
echo "Renaming complete."

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>
@@ -122,6 +125,149 @@ backend=sqlite
return std::format(kConfigContentsTemplate, dbPathSection, valFileSection);
}
// setenv and unsetenv are POSIX, and Windows declares neither, so this guard is
// compiled only where the test cases that use it are.
#if BOOST_OS_LINUX || BOOST_OS_MACOS
/**
* Set an environment variable and restore its previous value when done.
*
* The previous value is copied on construction rather than kept as the pointer
* `getenv` returned, because POSIX allows a later `setenv` to release that
* string. Restoring through the pointer can therefore read freed memory and
* restore a wrong value. Restoring in the destructor also means an assertion
* that throws no longer leaves the variable overwritten.
*/
class EnvVarGuard
{
private:
std::string const name_;
std::optional<std::string> const saved_;
/**
* Read an environment variable into a value this guard owns.
*
* @param name Name of the environment variable to read.
* @return The variable's value, or std::nullopt when it is not set.
*/
[[nodiscard]] static std::optional<std::string>
read(char const* name)
{
if (char const* const value = std::getenv(name); value != nullptr)
return std::string{value};
return std::nullopt;
}
/**
* Give an environment variable a value, or remove it.
*
* @param name Name of the environment variable to write.
* @param value Value to set, or std::nullopt to remove the variable.
*/
static void
apply(char const* name, std::optional<std::string> const& value) noexcept
{
// unsetenv succeeds on a variable that is not set, so the std::nullopt
// case needs no separate check.
if (value)
{
setenv(name, value->c_str(), 1);
}
else
{
unsetenv(name);
}
}
public:
/**
* Save the variable's current value, then write the given one.
*
* @param name Name of the environment variable to set.
* @param value Value to set, or std::nullopt to remove the variable.
*/
EnvVarGuard(char const* name, std::optional<std::string> const& value)
: name_(name), saved_(read(name))
{
apply(name_.c_str(), value);
}
EnvVarGuard(EnvVarGuard const&) = delete;
EnvVarGuard&
operator=(EnvVarGuard const&) = delete;
/**
* Restore the value the variable held before construction.
*/
~EnvVarGuard()
{
// Nothing here can throw: setenv and unsetenv are C functions, and
// neither std::string::c_str nor std::optional::operator-> throws. The
// noexcept on apply keeps that true, so unlike ~FileCfgGuard below
// this destructor needs no try/catch.
apply(name_.c_str(), saved_);
}
};
#endif // BOOST_OS_LINUX || BOOST_OS_MACOS
/**
* Send a stream's output to a buffer of our own, and restore it when done.
*
* Restoring in the destructor means an assertion that throws cannot leave the
* redirect in place. A leaked redirect on std::cerr would swallow the output of
* every later test case in the run.
*/
class StreamRedirectGuard
{
private:
// Declaration order matters here. The constructor initializes saved_ from
// captured_.rdbuf(), and members initialize in declaration order, so
// captured_ must stay declared before saved_. Moving saved_ above it would
// call rdbuf() on an unconstructed stream, which no warning reports because
// the member initializer list is already in declaration order.
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()
{
// std::ios::rdbuf(std::streambuf*) does not throw, so this destructor
// needs no try/catch.
stream_.rdbuf(saved_);
}
};
/**
* Write an xrpld config file and remove when done.
*/
@@ -156,11 +302,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&
@@ -327,13 +475,21 @@ port_wss_admin
// 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 named config file that cannot be read. Naming one is an explicit
// request for that file, so setup() must not fall back to defaults.
{
TempDir const td;
Config c;
BEAST_EXPECT(!c.setup(td.file("does_not_exist.cfg"), true, false, true));
}
// Config file in HOME or XDG_CONFIG_HOME directory.
#if BOOST_OS_LINUX || BOOST_OS_MACOS
for (auto const& configFile : configFiles)
@@ -350,11 +506,10 @@ port_wss_admin
// Set the HOME and XDG_CONFIG_HOME environment variables. The
// HOME variable is not used when XDG_CONFIG_HOME is set, but
// must be set.
char const* h = getenv("HOME");
setenv("HOME", tc.path().c_str(), 1);
char const* x = getenv("XDG_CONFIG_HOME");
setenv("XDG_CONFIG_HOME", tc.path().c_str(), 1);
// must be set. Both guards are declared after tc, so both
// restore before tc removes the directory they point into.
detail::EnvVarGuard const homeGuard("HOME", tc.path());
detail::EnvVarGuard const xdgGuard("XDG_CONFIG_HOME", tc.path());
// Create the config file in '${XDG_CONFIG_HOME}/[systemName]'.
path p = tc.file(systemName());
@@ -366,15 +521,11 @@ port_wss_admin
// 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] ==
"/Users/dummy/xrpld/config/log/debug.log");
// Restore the environment variables.
(h != nullptr) ? setenv("HOME", h, 1) : unsetenv("HOME");
(x != nullptr) ? setenv("XDG_CONFIG_HOME", x, 1) : unsetenv("XDG_CONFIG_HOME");
}
// The XDG config directory is not set: the config file must be in a
@@ -382,11 +533,11 @@ port_wss_admin
{
TempDir const tc;
// Set only the HOME environment variable.
char const* h = getenv("HOME");
setenv("HOME", tc.path().c_str(), 1);
char const* x = getenv("XDG_CONFIG_HOME");
unsetenv("XDG_CONFIG_HOME");
// Set only the HOME environment variable. Both guards are
// declared after tc, so both restore before tc removes the
// directory HOME points into.
detail::EnvVarGuard const homeGuard("HOME", tc.path());
detail::EnvVarGuard const xdgGuard("XDG_CONFIG_HOME", std::nullopt);
// Create the config file in '${HOME}/.config/[systemName]'.
std::string s = ".config";
@@ -402,16 +553,66 @@ port_wss_admin
// 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] ==
"/Users/dummy/xrpld/config/log/debug.log");
}
}
// Restore the environment variables.
(h != nullptr) ? setenv("HOME", h, 1) : unsetenv("HOME");
if (x != nullptr)
setenv("XDG_CONFIG_HOME", x, 1);
// No config file under either name 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();
if (exists(systemDir / std::string{Config::kConfigFileName}) ||
exists(systemDir / std::string{Config::kConfigLegacyName}))
{
log << "Skipping: " << systemDir.string() << " holds a config file." << std::endl;
}
else
{
// Point the working directory and HOME at empty temporary
// directories, so neither holds a config file. 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() is expected to search and
// report. Read the working directory back rather than reusing
// td.path(), because current_path() can resolve a symlink and
// return a different spelling of the same directory.
path const searchedCwd = current_path();
std::string const searchedXdgDir = tc.path() + "/.config/" + systemName();
std::string const searchedSystemDir = systemDir.string();
// setup() must report the failure rather than fall back to the
// last directory it searched. Capture and restore stderr around
// the call only, so that an assertion below cannot have its own
// output swallowed by the capture.
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 on the directory paths rather than on the wording of
// the header, so that a later change to that sentence does not
// break this.
BEAST_EXPECT(reported.contains(searchedCwd.string()));
BEAST_EXPECT(reported.contains(searchedXdgDir));
BEAST_EXPECT(reported.contains(searchedSystemDir));
}
}
#endif
@@ -1008,11 +1209,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());
@@ -1021,11 +1222,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());
@@ -1034,11 +1235,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());
@@ -1047,11 +1248,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());
@@ -1060,11 +1261,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());
@@ -1073,11 +1274,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());
@@ -1086,11 +1287,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());
@@ -1099,11 +1300,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

@@ -574,9 +574,23 @@ 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"));
// config file, quiet flag. Setup reports a config file it cannot find or
// read by returning false, and invalid config contents by throwing, so both
// are handled here rather than left to escape.
try
{
if (!config->setup(
configFile, vm.contains("quiet"), vm.contains("silent"), vm.contains("standalone")))
{
// setup() has already reported the reason it failed.
return 1;
}
}
catch (std::exception const& e)
{
std::cerr << "xrpld: invalid configuration: " << e.what() << std::endl;
return 1;
}
if (vm.contains("vacuum"))
{

View File

@@ -17,6 +17,7 @@
#include <filesystem>
#include <optional>
#include <string>
#include <string_view>
#include <unordered_set>
#include <utility>
#include <vector>
@@ -88,10 +89,10 @@ class Config : public BasicConfig
{
public:
// Settings related to the configuration file location and directories
static char const* const kConfigFileName;
static char const* const kConfigLegacyName;
static char const* const kDatabaseDirName;
static char const* const kValidatorsFileName;
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";
/**
* Returns the full path and filename of the debug log file.
@@ -99,17 +100,22 @@ public:
[[nodiscard]] std::filesystem::path
getDebugLogFile() const;
private:
std::filesystem::path configFile_;
public:
std::filesystem::path configDir;
private:
std::filesystem::path debugLogfile_;
void
load();
/**
* Reads and parses the given config file.
*
* @param configFile Path of the config file to read.
* @return True when the file was read, false when it could not be, in
* which case the reason has been written to stderr and the
* configuration is left unchanged.
*/
[[nodiscard]] bool
load(std::filesystem::path const& configFile);
beast::Journal const j_;
bool quiet_ = false; // Minimize logging verbosity.
@@ -329,9 +335,34 @@ public:
public:
Config();
/* Be very careful to make sure these bool params
are in the right order. */
void
/**
* Sets up the configuration, and determines the config and data
* directories.
*
* When no config file is named, searches the current working directory,
* the XDG config directory, and the system config directory, in that
* order, and takes the first one that holds a config file under either the
* current or the legacy name. When none of them does, writes the searched
* directories to stderr and fails.
*
* Failing to read the config file, whether it was named or found, is also
* a failure, and writes the reason to stderr.
*
* 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 named or found, and read. False when
* no config file could be found in any searched directory, or when
* the file that was named or found could not be read. Both cases
* have written their reason to stderr first.
* @throws std::runtime_error When the config file contents are invalid, or
* when the data directory cannot be created. Invalid contents are
* reported this way rather than through the return value.
*/
[[nodiscard]] bool
setup(std::string const& strConf, bool bQuiet, bool bSilent, bool bStandalone);
void

View File

@@ -43,6 +43,7 @@
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <system_error>
#include <thread>
#include <type_traits>
@@ -246,11 +247,6 @@ getSingleSection(
//
//------------------------------------------------------------------------------
char const* const Config::kConfigFileName = "xrpld.cfg";
char const* const Config::kConfigLegacyName = "rippled.cfg";
char const* const Config::kDatabaseDirName = "db";
char const* const Config::kValidatorsFileName = "validators.txt";
[[nodiscard]] static std::string
getEnvVar(char const* name)
{
@@ -302,7 +298,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);
@@ -314,73 +310,99 @@ 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, which is the only way this function fails.
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 to search, paired with the data directory that
// goes with it.
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. There are at most three.
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_))
// The system config directory, as a last resort.
candidates.emplace_back("/etc/" + systemName(), "/var/lib/" + systemName());
// Take the first candidate directory holding a config file under either
// name. When none of them does, configFile stays unset, while configDir
// and dataDir keep the values of the last candidate.
for (auto const& candidate : candidates)
{
configDir = candidate.configDir;
dataDir = candidate.dataDir;
for (std::string_view const fileName : {kConfigFileName, kConfigLegacyName})
{
auto candidateFile = configDir / fileName;
if (std::filesystem::exists(candidateFile))
{
configFile = std::move(candidateFile);
break;
}
}
// As a last resort, check the system config directory.
dataDir = "/var/lib/" + systemName();
configDir = "/etc/" + systemName();
configFile_ = configDir / kConfigFileName;
if (std::filesystem::exists(configFile_))
if (configFile)
break;
configFile_ = configDir / kConfigLegacyName;
} while (false);
}
if (!configFile)
{
// Report every directory searched. Naming only the last one would
// suggest it is the only place a config file is expected. Report
// this even when quiet, because it is the reason for failing.
std::cerr << std::format(
"No {} or {} found. Searched:\n", kConfigFileName, kConfigLegacyName);
for (auto const& candidate : candidates)
std::cerr << std::format(" {}\n", candidate.configDir.string());
return false;
}
}
// Update default values
load();
if (!load(*configFile))
return false;
{
// load() may have set a new value for the dataDir
std::string const dbPath(legacy(Sections::kDatabasePath));
@@ -415,6 +437,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 true;
}
// 0 ports are allowed for unit tests, but still not allowed to be present in
@@ -445,27 +469,34 @@ checkZeroPorts(Config const& config)
}
}
void
Config::load()
bool
Config::load(std::filesystem::path const& configFile)
{
// Report the path as a plain string. There is no std::formatter for
// std::filesystem::path, and streaming one adds quotes of its own, which
// would double up with the quotes written here.
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;
std::cerr << std::format(
"Failed to read '{}': {} (error {})\n", fileName, ec.message(), ec.value());
return false;
}
loadFromString(fileContents);
checkZeroPorts(*this);
return true;
}
void