Skip to content

Commit

Permalink
init: Allow bitcoin default datadir to point at an external datadir
Browse files Browse the repository at this point in the history
It's always been possible for bitcoin default datadirs ($HOME/.bitcoin,
$HOME/Library/Application Support/Bitcoin, %APPDATA%\Bitcoin) to point at an
external locations using symlinks, but not all filesystems support symlinks, so
add extra support for pointing at external locations using text file.

This feature is used in the following commit to allow the bitcoin GUI to select
a default datadir location that will also be treated as the default datadir for
CLI tools. Currently when a custom data is set in the GUI, CLI tools ignore it
by default.
  • Loading branch information
ryanofsky committed Apr 1, 2023
1 parent cb2f824 commit 04a7fca
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 25 deletions.
7 changes: 3 additions & 4 deletions src/common/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,10 @@ std::optional<ConfigError> InitConfig(ArgsManager& args, SettingsAbortFn setting
// used, though. Specifying a conf= option in the config file causes a
// parse error, and specifying a datadir= location containing another
// bitcoin.conf file just ignores the other file.)
const fs::path orig_datadir_path{args.GetDataDirBase()};
const fs::path orig_config_path{AbsPathForConfigVal(args, args.GetPathArg("-conf", BITCOIN_CONF_FILENAME), /*net_specific=*/false)};

std::string error;
if (!args.ReadConfigFiles(error, true)) {
fs::path orig_config_path;
fs::path orig_datadir_path;
if (!args.ReadConfigFiles(error, true, &orig_config_path, &orig_datadir_path)) {
return ConfigError{ConfigStatus::FAILED, strprintf(_("Error reading configuration file: %s"), error)};
}

Expand Down
114 changes: 94 additions & 20 deletions src/util/system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -420,16 +420,7 @@ const fs::path& ArgsManager::GetDataDir(bool net_specific) const
// Used cached path if available
if (!path.empty()) return path;

const fs::path datadir{GetPathArg("-datadir")};
if (!datadir.empty()) {
path = fs::absolute(datadir);
if (!fs::is_directory(path)) {
path = "";
return path;
}
} else {
path = GetDefaultDataDir();
}
path = *Assert(m_datadir);

if (net_specific && !BaseParams().DataDir().empty()) {
path /= fs::PathFromString(BaseParams().DataDir());
Expand Down Expand Up @@ -917,16 +908,95 @@ fs::path ArgsManager::GetConfigFilePath() const
return *Assert(m_config_path);
}

bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys)
static bool GetExplicitDataDir(const ArgsManager& args, fs::path& datadir, std::string& error)
{
fs::path datadir_arg = args.GetPathArg("-datadir");
if (!CheckDataDirOption(args)) {
error = strprintf("specified data directory %s does not exist.", fs::quoted(fs::PathToString(datadir_arg)));
return false;
}

// Keep default datadir if -datadir is not specified, otherwise call
// fs::absolute to treat relative datadir arguments and datadir= lines in
// configuration files as being relative to the current working directory.
// Probably it would make more sense to treat relative datadir lines in
// configuration files as relative to the configuration file, not the
// working directory, but current behavior is being kept for compatibility.
if (!datadir_arg.empty()) datadir = fs::absolute(std::move(datadir_arg));
return true;
}

static bool GetDefaultDataDir(fs::path& datadir, std::string& error)
{
// Keep explicit datadir if it was specified.
if (!datadir.empty()) return true;

datadir = GetDefaultDataDir();
std::error_code ec;
std::filesystem::file_status status = fs::status(datadir);
if (ec) {
error = strprintf("Could not access %s: %s\n", fs::quoted(fs::PathToString(datadir)), ec.message());
return false;
}

// Allow a text file that points to another directory to be placed at
// the default datadir location. It can be useful to point the default
// datadir at an external location so bitcoin tools can be started
// without -datadir arguments while the external location is still used
// for storage. The most straightforward way of pointing the default
// datadir at another location is with a symlink, but a text file is
// allowed here to work on file systems that don't support symlinks.
if (status.type() == fs::file_type::regular) {
std::ifstream file;
file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
std::string line;
try {
file.open(datadir);
std::getline(file, line);
} catch (std::system_error& e) {
error = strprintf("Could not read %s: %s", fs::quoted(fs::PathToString(datadir)), ec.message());
return false;
}
fs::path path = fs::PathFromString(line);
if (!path.is_absolute() || fs::is_directory(path, ec)) {
error = "Invalid datadir path %s in file %s", fs::quoted(line), fs::quoted(fs::PathToString(datadir));
if (ec) error = strprintf("%s: %s", error, ec.message());
return false;
}
datadir = std::move(path);
}
assert (datadir.is_absolute());
return true;
}

bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys, fs::path* config_file, fs::path* initial_datadir)
{
// Save initial datadir value in case -conf path or any -includeconf paths
// are relative paths, and need to be evaluated relative to the
// datadir. The final datadir can change while parsing the config file if
// it contains a datadir= line. Avoid calling GetDefaultDataDir() yet if not
// needed because it accesses the default datadir filesystem path, which
// might be slow or off-limits due to permissions.
fs::path datadir_path;
if (!GetExplicitDataDir(*this, datadir_path, error)) return false;

// Determine config file path relative to the initial datadir.
fs::path conf_path{GetPathArg("-conf", BITCOIN_CONF_FILENAME)};
if (!conf_path.is_absolute()) {
if (!GetDefaultDataDir(datadir_path, error)) return false;
conf_path = datadir_path / std::move(conf_path);
}

{
LOCK(cs_args);
m_settings.ro_config.clear();
m_config_sections.clear();
m_config_path = AbsPathForConfigVal(*this, GetPathArg("-conf", BITCOIN_CONF_FILENAME), /*net_specific=*/false);
m_config_path = conf_path;
}

const auto conf_path{GetConfigFilePath()};
if (config_file) *config_file = conf_path;
if (initial_datadir) *initial_datadir = datadir_path;

std::ifstream stream{conf_path};

// not ok to have a config file specified that cannot be opened
Expand Down Expand Up @@ -974,7 +1044,12 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys)
const size_t default_includes = add_includes({});

for (const std::string& conf_file_name : conf_file_names) {
std::ifstream conf_file_stream{AbsPathForConfigVal(*this, fs::PathFromString(conf_file_name), /*net_specific=*/false)};
fs::path include_path = fs::PathFromString(conf_file_name);
if (!include_path.is_absolute()) {
if (!GetDefaultDataDir(datadir_path, error)) return false;
include_path = datadir_path / std::move(include_path);
}
std::ifstream conf_file_stream{include_path};
if (conf_file_stream.good()) {
if (!ReadConfigStream(conf_file_stream, conf_file_name, error, ignore_invalid_keys)) {
return false;
Expand All @@ -1001,12 +1076,11 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys)
}
}

// If datadir is changed in .conf file:
ClearPathCache();
if (!CheckDataDirOption(*this)) {
error = strprintf("specified data directory \"%s\" does not exist.", GetArg("-datadir", ""));
return false;
}
// Update datadir if case .conf file set a new datadir location.
if (!GetExplicitDataDir(*this, datadir_path, error) || !GetDefaultDataDir(datadir_path, error)) return false;

WITH_LOCK(cs_args, m_datadir = std::move(datadir_path));

return true;
}

Expand Down
3 changes: 2 additions & 1 deletion src/util/system.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ class ArgsManager
mutable RecursiveMutex cs_args;
util::Settings m_settings GUARDED_BY(cs_args);
std::vector<std::string> m_command GUARDED_BY(cs_args);
std::optional<fs::path> m_datadir GUARDED_BY(cs_args);
std::string m_network GUARDED_BY(cs_args);
std::set<std::string> m_network_only_args GUARDED_BY(cs_args);
std::map<OptionsCategory, std::map<std::string, Arg>> m_available_args GUARDED_BY(cs_args);
Expand Down Expand Up @@ -238,7 +239,7 @@ class ArgsManager
* Return config file path (read-only)
*/
fs::path GetConfigFilePath() const;
[[nodiscard]] bool ReadConfigFiles(std::string& error, bool ignore_invalid_keys = false);
[[nodiscard]] bool ReadConfigFiles(std::string& error, bool ignore_invalid_keys = false, fs::path* config_file = nullptr, fs::path* initial_datadir = nullptr);

/**
* Log warnings for options in m_section_only_args when
Expand Down

0 comments on commit 04a7fca

Please sign in to comment.