diff --git a/src/jrd/replication/Config.cpp b/src/jrd/replication/Config.cpp index f4e868ca990..16f3569ce9b 100644 --- a/src/jrd/replication/Config.cpp +++ b/src/jrd/replication/Config.cpp @@ -26,6 +26,8 @@ #include "../common/isc_f_proto.h" #include "../common/status.h" #include "../common/StatusArg.h" +#include "../common/utils_proto.h" +#include "../common/os/os_utils.h" #include "../jrd/constants.h" #include "Utils.h" @@ -50,6 +52,8 @@ using namespace Replication; namespace { const char* REPLICATION_CFGFILE = "replication.conf"; + constexpr const char* KEY_ENV = "env"; + constexpr const char* KEY_FILE = "file"; const ULONG DEFAULT_BUFFER_SIZE = 1024 * 1024; // 1 MB const ULONG DEFAULT_SEGMENT_SIZE = 16 * 1024 * 1024; // 16 MB @@ -99,6 +103,69 @@ namespace status->setErrors(sv.value()); } + + void parseSyncReplica(const ConfigFile::Parameters& params, SyncReplica& output) + { + for (const auto& el : params) + { + string key(el.name.c_str()); + string value(el.value); + + if (value.isEmpty()) + continue; + + auto pos = key.rfind('_'); + if (pos != string::npos) + { + const string key_source = key.substr(pos + 1); + + if (key_source.equals(KEY_ENV)) + { + fb_utils::readenv(value.c_str(), value); + if (value.isEmpty()) + configError("missing environment variable", output.database, value); + + key = key.substr(0, pos); + } + else if (key_source.equals(KEY_FILE)) + { + PathName filename = value.c_str(); + PathUtils::fixupSeparators(filename); + if (PathUtils::isRelative(filename)) + filename = fb_utils::getPrefix(IConfigManager::DIR_CONF, filename.c_str()); + + AutoPtr file(os_utils::fopen(filename.c_str(), "rt")); + if (!file) + configError("missing or inaccessible file", output.database, filename.c_str()); + + // skip first empty lines + value = ""; + do + { + if (feof(file)) + break; + + if (!value.LoadFromFile(file)) + break; + + value.alltrim(" \t\r"); + } while (value.isEmpty()); + + if (value.isEmpty()) + configError("empty file", output.database, filename.c_str()); + + key = key.substr(0, pos); + } + } + + if (key == "username") + output.username = value.c_str(); + else if (key == "password") + output.password = value.c_str(); + else + configError("unknown parameter", output.database, key); + } + } } @@ -216,7 +283,16 @@ Config* Config::get(const PathName& lookupName) if (key == "sync_replica") { - config->syncReplicas.add(value); + SyncReplica syncReplica(config->getPool()); + if (el.sub) + { + syncReplica.database = value; + parseSyncReplica(el.sub->getParameters(), syncReplica); + } + else + splitConnectionString(value, syncReplica.database, syncReplica.username, syncReplica.password); + + config->syncReplicas.add(syncReplica); } else if (key == "buffer_size") { @@ -427,3 +503,35 @@ void Config::enumerate(ReplicaList& replicas) logReplicaStatus(dbName, &localStatus); } } + +// This routine is used for split input connection string to parts +// input => [[:]@] +// +// Examples: +// server2:/my/replica/database.fdb +// john:smith@server2:/my/replica/database.fdb + +void Config::splitConnectionString(const string& input, string& database, string& username, string& password) +{ + database = input; + + auto pos = database.rfind('@'); + if (pos != string::npos) + { + //john:smith + const string temp = database.substr(0, pos); + //server2:/my/replica/database.fdb + database = database.substr(pos + 1); + + pos = temp.find(':'); + if (pos != string::npos) + { + username = temp.substr(0, pos); + password = temp.substr(pos + 1); + } + else + { + username = temp; + } + } +} diff --git a/src/jrd/replication/Config.h b/src/jrd/replication/Config.h index 72f29080c3e..dd157f28218 100644 --- a/src/jrd/replication/Config.h +++ b/src/jrd/replication/Config.h @@ -33,6 +33,21 @@ namespace Replication { + struct SyncReplica + { + explicit SyncReplica(MemoryPool& p) + : database(p), username(p), password(p) + {} + + SyncReplica(MemoryPool& p, const SyncReplica& other) + : database(p, other.database), username(p, other.username), password(p, other.password) + {} + + Firebird::string database; + Firebird::string username; + Firebird::string password; + }; + struct Config : public Firebird::GlobalStorage { typedef Firebird::HalfStaticArray ReplicaList; @@ -42,6 +57,8 @@ namespace Replication static Config* get(const Firebird::PathName& dbName); static void enumerate(ReplicaList& replicas); + static void splitConnectionString(const Firebird::string& input, Firebird::string& database, + Firebird::string& username, Firebird::string& password); Firebird::PathName dbName; ULONG bufferSize; @@ -55,7 +72,7 @@ namespace Replication Firebird::PathName archiveDirectory; Firebird::string archiveCommand; ULONG archiveTimeout; - Firebird::ObjectsArray syncReplicas; + Firebird::ObjectsArray syncReplicas; Firebird::PathName sourceDirectory; std::optional sourceGuid; bool verboseLogging; diff --git a/src/jrd/replication/Manager.cpp b/src/jrd/replication/Manager.cpp index cc1eef994b6..30dfa339f6e 100644 --- a/src/jrd/replication/Manager.cpp +++ b/src/jrd/replication/Manager.cpp @@ -128,39 +128,18 @@ Manager::Manager(const string& dbId, for (const auto& iter : m_config->syncReplicas) { - string database = iter; - string login, password; - - auto pos = database.find('@'); - if (pos != string::npos) - { - const string temp = database.substr(0, pos); - database = database.substr(pos + 1); - - pos = temp.find(':'); - if (pos != string::npos) - { - login = temp.substr(0, pos); - password = temp.substr(pos + 1); - } - else - { - login = temp; - } - } - ClumpletWriter dpb(ClumpletReader::dpbList, MAX_DPB_SIZE); dpb.insertByte(isc_dpb_no_db_triggers, 1); - if (login.hasData()) + if (iter.username.hasData()) { - dpb.insertString(isc_dpb_user_name, login); + dpb.insertString(isc_dpb_user_name, iter.username); - if (password.hasData()) - dpb.insertString(isc_dpb_password, password); + if (iter.password.hasData()) + dpb.insertString(isc_dpb_password, iter.password); } - const auto attachment = provider->attachDatabase(&localStatus, database.c_str(), + const auto attachment = provider->attachDatabase(&localStatus, iter.database.c_str(), dpb.getBufferLength(), dpb.getBuffer()); if (localStatus->getState() & IStatus::STATE_ERRORS) {