diff --git a/src/db/Main.cpp b/src/db/Main.cpp index b073b69..bacb801 100644 --- a/src/db/Main.cpp +++ b/src/db/Main.cpp @@ -47,6 +47,15 @@ extern void WriteVersion(); namespace { +struct SQLConnectionConfig +{ + char host[64]; + char database[64]; + char user[64]; + char password[64]; + int port; +}; + const char* BoolState(bool value) { return value ? "on" : "off"; @@ -57,6 +66,112 @@ const char* EmptyToLabel(const std::string& value, const char* fallback) return value.empty() ? fallback : value.c_str(); } +bool CopyEnvString(const char* env_name, char* dest, size_t dest_size) +{ + const char* value = std::getenv(env_name); + if (!value) + return false; + + strlcpy(dest, value, dest_size); + return true; +} + +bool CopyEnvInt(const char* env_name, int* dest) +{ + const char* value = std::getenv(env_name); + if (!value) + return false; + + str_to_number(*dest, value); + return true; +} + +bool HasSQLConfig(const SQLConnectionConfig& config) +{ + return config.host[0] && config.database[0] && config.user[0] && config.password[0]; +} + +bool ParseSQLConfig(const char* value, SQLConnectionConfig* config, const char* label) +{ + int token_count = sscanf( + value, + " %63s %63s %63s %63s %d ", + config->host, + config->database, + config->user, + config->password, + &config->port); + + if (token_count < 4 || !HasSQLConfig(*config)) + { + fprintf(stderr, "%s syntax: \n", label); + return false; + } + + return true; +} + +bool LoadSQLConfig(const char* key, const char* env_prefix, SQLConnectionConfig* config) +{ + char line[256 + 1]; + bool loaded_from_file = false; + + if (CConfig::instance().GetValue(key, line, sizeof(line) - 1)) + { + if (!ParseSQLConfig(line, config, key)) + return false; + + loaded_from_file = true; + } + + bool overridden = false; + const std::string prefix = env_prefix; + overridden |= CopyEnvString((prefix + "_HOST").c_str(), config->host, sizeof(config->host)); + overridden |= CopyEnvString((prefix + "_DB").c_str(), config->database, sizeof(config->database)); + overridden |= CopyEnvString((prefix + "_USER").c_str(), config->user, sizeof(config->user)); + overridden |= CopyEnvString((prefix + "_PASSWORD").c_str(), config->password, sizeof(config->password)); + overridden |= CopyEnvInt((prefix + "_PORT").c_str(), &config->port); + + if (overridden) + sys_log(0, "CONFIG: %s overridden from environment", key); + + if (!loaded_from_file && !overridden) + { + sys_err("%s not configured", key); + return false; + } + + if (!HasSQLConfig(*config)) + { + sys_err("%s incomplete after applying config/environment overrides", key); + return false; + } + + return true; +} + +bool ConnectDatabase(int slot, const SQLConnectionConfig& config, const char* label) +{ + sys_log(0, "connecting to MySQL server (%s)", label); + + int retry_count = 5; + do + { + if (CDBManager::instance().Connect(slot, config.host, config.port, config.database, config.user, config.password)) + { + sys_log(0, " OK"); + return true; + } + + sys_log(0, " failed, retrying in 5 seconds"); + fprintf(stderr, " failed, retrying in 5 seconds"); + sleep(5); + } + while (retry_count--); + + return false; +} + void LogStartupSummary(int heart_fps, int player_id_start) { sys_log(0, @@ -274,120 +389,43 @@ int Start() g_stLocaleNameColumn = szBuf; } - char szAddr[64], szDB[64], szUser[64], szPassword[64]; - int iPort; - char line[256+1]; + SQLConnectionConfig player_sql = {}; + SQLConnectionConfig account_sql = {}; + SQLConnectionConfig common_sql = {}; + SQLConnectionConfig hotbackup_sql = {}; - if (CConfig::instance().GetValue("SQL_PLAYER", line, 256)) - { - sscanf(line, " %s %s %s %s %d ", szAddr, szDB, szUser, szPassword, &iPort); - sys_log(0, "connecting to MySQL server (player)"); - - int iRetry = 5; - - do - { - if (CDBManager::instance().Connect(SQL_PLAYER, szAddr, iPort, szDB, szUser, szPassword)) - { - sys_log(0, " OK"); - break; - } - - sys_log(0, " failed, retrying in 5 seconds"); - fprintf(stderr, " failed, retrying in 5 seconds"); - sleep(5); - } while (iRetry--); - fprintf(stderr, "Success PLAYER\n"); - SetPlayerDBName(szDB); - } - else - { - sys_err("SQL_PLAYER not configured"); + if (!LoadSQLConfig("SQL_PLAYER", "METIN2_PLAYER_SQL", &player_sql)) return false; - } - if (CConfig::instance().GetValue("SQL_ACCOUNT", line, 256)) - { - sscanf(line, " %s %s %s %s %d ", szAddr, szDB, szUser, szPassword, &iPort); - sys_log(0, "connecting to MySQL server (account)"); - - int iRetry = 5; - - do - { - if (CDBManager::instance().Connect(SQL_ACCOUNT, szAddr, iPort, szDB, szUser, szPassword)) - { - sys_log(0, " OK"); - break; - } - - sys_log(0, " failed, retrying in 5 seconds"); - fprintf(stderr, " failed, retrying in 5 seconds"); - sleep(5); - } while (iRetry--); - fprintf(stderr, "Success ACCOUNT\n"); - } - else - { - sys_err("SQL_ACCOUNT not configured"); + if (!ConnectDatabase(SQL_PLAYER, player_sql, "player")) return false; - } - if (CConfig::instance().GetValue("SQL_COMMON", line, 256)) - { - sscanf(line, " %s %s %s %s %d ", szAddr, szDB, szUser, szPassword, &iPort); - sys_log(0, "connecting to MySQL server (common)"); + fprintf(stderr, "Success PLAYER\n"); + SetPlayerDBName(player_sql.database); - int iRetry = 5; - - do - { - if (CDBManager::instance().Connect(SQL_COMMON, szAddr, iPort, szDB, szUser, szPassword)) - { - sys_log(0, " OK"); - break; - } - - sys_log(0, " failed, retrying in 5 seconds"); - fprintf(stderr, " failed, retrying in 5 seconds"); - sleep(5); - } while (iRetry--); - fprintf(stderr, "Success COMMON\n"); - } - else - { - sys_err("SQL_COMMON not configured"); + if (!LoadSQLConfig("SQL_ACCOUNT", "METIN2_ACCOUNT_SQL", &account_sql)) return false; - } - if (CConfig::instance().GetValue("SQL_HOTBACKUP", line, 256)) - { - sscanf(line, " %s %s %s %s %d ", szAddr, szDB, szUser, szPassword, &iPort); - sys_log(0, "connecting to MySQL server (hotbackup)"); - - int iRetry = 5; - - do - { - if (CDBManager::instance().Connect(SQL_HOTBACKUP, szAddr, iPort, szDB, szUser, szPassword)) - { - sys_log(0, " OK"); - break; - } - - sys_log(0, " failed, retrying in 5 seconds"); - fprintf(stderr, " failed, retrying in 5 seconds"); - sleep(5); - } - while (iRetry--); - - fprintf(stderr, "Success HOTBACKUP\n"); - } - else - { - sys_err("SQL_HOTBACKUP not configured"); + if (!ConnectDatabase(SQL_ACCOUNT, account_sql, "account")) return false; - } + + fprintf(stderr, "Success ACCOUNT\n"); + + if (!LoadSQLConfig("SQL_COMMON", "METIN2_COMMON_SQL", &common_sql)) + return false; + + if (!ConnectDatabase(SQL_COMMON, common_sql, "common")) + return false; + + fprintf(stderr, "Success COMMON\n"); + + if (!LoadSQLConfig("SQL_HOTBACKUP", "METIN2_HOTBACKUP_SQL", &hotbackup_sql)) + return false; + + if (!ConnectDatabase(SQL_HOTBACKUP, hotbackup_sql, "hotbackup")) + return false; + + fprintf(stderr, "Success HOTBACKUP\n"); if (!CNetPoller::instance().Create()) { diff --git a/src/game/config.cpp b/src/game/config.cpp index 427fd66..cb84184 100644 --- a/src/game/config.cpp +++ b/src/game/config.cpp @@ -208,6 +208,113 @@ static void FN_apply_adminpage_password_env() g_stAdminPagePassword = env_password; } +static bool FN_copy_env_string(const char* env_name, char* dest, size_t dest_size) +{ + const char* value = std::getenv(env_name); + if (!value) + return false; + + strlcpy(dest, value, dest_size); + return true; +} + +static bool FN_copy_env_int(const char* env_name, int* dest) +{ + const char* value = std::getenv(env_name); + if (!value) + return false; + + str_to_number(*dest, value); + return true; +} + +static bool FN_has_sql_config(const char* host, const char* user, const char* password, const char* database) +{ + return host[0] && user[0] && password[0] && database[0]; +} + +static bool FN_parse_game_sql_config( + const char* value, + char* host, + size_t host_size, + char* user, + size_t user_size, + char* password, + size_t password_size, + char* database, + size_t database_size, + int* port, + const char* label) +{ + const char* line = two_arguments(value, host, host_size, user, user_size); + line = two_arguments(line, password, password_size, database, database_size); + + if (line[0]) + { + char port_buf[256]; + one_argument(line, port_buf, sizeof(port_buf)); + str_to_number(*port, port_buf); + } + + if (!FN_has_sql_config(host, user, password, database)) + { + fprintf(stderr, "%s syntax: \n", label); + return false; + } + + return true; +} + +static bool FN_apply_game_sql_env( + const char* env_prefix, + char* host, + size_t host_size, + char* user, + size_t user_size, + char* password, + size_t password_size, + char* database, + size_t database_size, + int* port, + const char* label) +{ + bool overridden = false; + std::string prefix = env_prefix; + + overridden |= FN_copy_env_string((prefix + "_HOST").c_str(), host, host_size); + overridden |= FN_copy_env_string((prefix + "_USER").c_str(), user, user_size); + overridden |= FN_copy_env_string((prefix + "_PASSWORD").c_str(), password, password_size); + overridden |= FN_copy_env_string((prefix + "_DB").c_str(), database, database_size); + overridden |= FN_copy_env_int((prefix + "_PORT").c_str(), port); + + if (overridden) + sys_log(0, "CONFIG: %s overridden from environment", label); + + return overridden; +} + +static void FN_apply_db_runtime_env() +{ + const char* env_addr = std::getenv("METIN2_DB_ADDR"); + if (env_addr) + { + strlcpy(db_addr, env_addr, sizeof(db_addr)); + for (int n = 0; n < ADDRESS_MAX_LEN; ++n) + { + if (db_addr[n] == ' ') + db_addr[n] = '\0'; + } + sys_log(0, "CONFIG: DB_ADDR overridden from environment"); + } + + int env_port = 0; + if (FN_copy_env_int("METIN2_DB_PORT", &env_port)) + { + db_port = static_cast(env_port); + sys_log(0, "CONFIG: DB_PORT overridden from environment"); + } +} + bool GetIPInfo() { @@ -376,8 +483,10 @@ void config_init(const string& st_localeServiceName) // DB정보만 읽어와 로케일 세팅을 한후 다른 세팅을 적용시켜야한다. // 이유는 로케일관련된 초기화 루틴이 곳곳에 존재하기 때문. - bool isCommonSQL = false; + bool isAccountSQL = false; + bool isCommonSQL = false; bool isPlayerSQL = false; + bool isLogSQL = false; FILE* fp_common; if (!(fp_common = fopen("conf/game.txt", "r"))) @@ -391,96 +500,69 @@ void config_init(const string& st_localeServiceName) TOKEN("account_sql") { - const char* line = two_arguments(value_string, db_host[0], sizeof(db_host[0]), db_user[0], sizeof(db_user[0])); - line = two_arguments(line, db_pwd[0], sizeof(db_pwd[0]), db_db[0], sizeof(db_db[0])); - - if (line[0]) + if (!FN_parse_game_sql_config(value_string, + db_host[0], sizeof(db_host[0]), + db_user[0], sizeof(db_user[0]), + db_pwd[0], sizeof(db_pwd[0]), + db_db[0], sizeof(db_db[0]), + &mysql_db_port[0], + "ACCOUNT_SQL")) { - char buf[256]; - one_argument(line, buf, sizeof(buf)); - str_to_number(mysql_db_port[0], buf); - } - - if (!*db_host[0] || !*db_user[0] || !*db_pwd[0] || !*db_db[0]) - { - fprintf(stderr, "PLAYER_SQL syntax: logsql \n"); exit(1); } - char buf[1024]; - snprintf(buf, sizeof(buf), "PLAYER_SQL: %s %s %s %s %d", db_host[0], db_user[0], db_pwd[0], db_db[0], mysql_db_port[0]); - isPlayerSQL = true; + isAccountSQL = true; continue; } TOKEN("player_sql") { - const char* line = two_arguments(value_string, db_host[1], sizeof(db_host[1]), db_user[1], sizeof(db_user[1])); - line = two_arguments(line, db_pwd[1], sizeof(db_pwd[1]), db_db[1], sizeof(db_db[1])); - - if (line[0]) + if (!FN_parse_game_sql_config(value_string, + db_host[1], sizeof(db_host[1]), + db_user[1], sizeof(db_user[1]), + db_pwd[1], sizeof(db_pwd[1]), + db_db[1], sizeof(db_db[1]), + &mysql_db_port[1], + "PLAYER_SQL")) { - char buf[256]; - one_argument(line, buf, sizeof(buf)); - str_to_number(mysql_db_port[1], buf); - } - - if (!*db_host[1] || !*db_user[1] || !*db_pwd[1] || !*db_db[1]) - { - fprintf(stderr, "PLAYER_SQL syntax: logsql \n"); exit(1); } - char buf[1024]; - snprintf(buf, sizeof(buf), "PLAYER_SQL: %s %s %s %s %d", db_host[1], db_user[1], db_pwd[1], db_db[1], mysql_db_port[1]); isPlayerSQL = true; continue; } TOKEN("common_sql") { - const char* line = two_arguments(value_string, db_host[2], sizeof(db_host[2]), db_user[2], sizeof(db_user[2])); - line = two_arguments(line, db_pwd[2], sizeof(db_pwd[2]), db_db[2], sizeof(db_db[2])); - - if (line[0]) + if (!FN_parse_game_sql_config(value_string, + db_host[2], sizeof(db_host[2]), + db_user[2], sizeof(db_user[2]), + db_pwd[2], sizeof(db_pwd[2]), + db_db[2], sizeof(db_db[2]), + &mysql_db_port[2], + "COMMON_SQL")) { - char buf[256]; - one_argument(line, buf, sizeof(buf)); - str_to_number(mysql_db_port[2], buf); - } - - if (!*db_host[2] || !*db_user[2] || !*db_pwd[2] || !*db_db[2]) - { - fprintf(stderr, "COMMON_SQL syntax: logsql \n"); exit(1); } - char buf[1024]; - snprintf(buf, sizeof(buf), "COMMON_SQL: %s %s %s %s %d", db_host[2], db_user[2], db_pwd[2], db_db[2], mysql_db_port[2]); isCommonSQL = true; continue; } TOKEN("log_sql") { - const char* line = two_arguments(value_string, log_host, sizeof(log_host), log_user, sizeof(log_user)); - line = two_arguments(line, log_pwd, sizeof(log_pwd), log_db, sizeof(log_db)); - - if (line[0]) + if (!FN_parse_game_sql_config(value_string, + log_host, sizeof(log_host), + log_user, sizeof(log_user), + log_pwd, sizeof(log_pwd), + log_db, sizeof(log_db), + &log_port, + "LOG_SQL")) { - char buf[256]; - one_argument(line, buf, sizeof(buf)); - str_to_number(log_port, buf); - } - - if (!*log_host || !*log_user || !*log_pwd || !*log_db) - { - fprintf(stderr, "LOG_SQL syntax: logsql \n"); exit(1); } - char buf[1024]; - snprintf(buf, sizeof(buf), "LOG_SQL: %s %s %s %s %d", log_host, log_user, log_pwd, log_db, log_port); + isLogSQL = true; continue; } @@ -763,6 +845,44 @@ void config_init(const string& st_localeServiceName) } fclose(fp_common); + const bool account_env_override = FN_apply_game_sql_env( + "METIN2_ACCOUNT_SQL", + db_host[0], sizeof(db_host[0]), + db_user[0], sizeof(db_user[0]), + db_pwd[0], sizeof(db_pwd[0]), + db_db[0], sizeof(db_db[0]), + &mysql_db_port[0], + "ACCOUNT_SQL"); + const bool player_env_override = FN_apply_game_sql_env( + "METIN2_PLAYER_SQL", + db_host[1], sizeof(db_host[1]), + db_user[1], sizeof(db_user[1]), + db_pwd[1], sizeof(db_pwd[1]), + db_db[1], sizeof(db_db[1]), + &mysql_db_port[1], + "PLAYER_SQL"); + const bool common_env_override = FN_apply_game_sql_env( + "METIN2_COMMON_SQL", + db_host[2], sizeof(db_host[2]), + db_user[2], sizeof(db_user[2]), + db_pwd[2], sizeof(db_pwd[2]), + db_db[2], sizeof(db_db[2]), + &mysql_db_port[2], + "COMMON_SQL"); + const bool log_env_override = FN_apply_game_sql_env( + "METIN2_LOG_SQL", + log_host, sizeof(log_host), + log_user, sizeof(log_user), + log_pwd, sizeof(log_pwd), + log_db, sizeof(log_db), + &log_port, + "LOG_SQL"); + + isAccountSQL = isAccountSQL || account_env_override || FN_has_sql_config(db_host[0], db_user[0], db_pwd[0], db_db[0]); + isPlayerSQL = isPlayerSQL || player_env_override || FN_has_sql_config(db_host[1], db_user[1], db_pwd[1], db_db[1]); + isCommonSQL = isCommonSQL || common_env_override || FN_has_sql_config(db_host[2], db_user[2], db_pwd[2], db_db[2]); + isLogSQL = isLogSQL || log_env_override || FN_has_sql_config(log_host, log_user, log_pwd, log_db); + FILE* fpOnlyForDB; if (!(fpOnlyForDB = fopen(st_configFileName.c_str(), "r"))) @@ -836,17 +956,41 @@ void config_init(const string& st_localeServiceName) exit(1); } - if (!isPlayerSQL) + if (g_bAuthServer && !isAccountSQL) + { + puts("LOAD_ACCOUNT_SQL_INFO_FAILURE:"); + puts(""); + puts("CONFIG:"); + puts("------------------------------------------------"); + puts("ACCOUNT_SQL: HOST USER PASSWORD DATABASE [PORT]"); + puts(""); + exit(1); + } + + if (!g_bAuthServer && !isPlayerSQL) { puts("LOAD_PLAYER_SQL_INFO_FAILURE:"); puts(""); puts("CONFIG:"); puts("------------------------------------------------"); - puts("PLAYER_SQL: HOST USER PASSWORD DATABASE"); + puts("PLAYER_SQL: HOST USER PASSWORD DATABASE [PORT]"); puts(""); exit(1); } + if (!g_bAuthServer && !isLogSQL) + { + puts("LOAD_LOG_SQL_INFO_FAILURE:"); + puts(""); + puts("CONFIG:"); + puts("------------------------------------------------"); + puts("LOG_SQL: HOST USER PASSWORD DATABASE [PORT]"); + puts(""); + exit(1); + } + + FN_apply_db_runtime_env(); + // Common DB 가 Locale 정보를 가지고 있기 때문에 가장 먼저 접속해야 한다. AccountDB::instance().Connect(db_host[2], mysql_db_port[2], db_user[2], db_pwd[2], db_db[2]); @@ -1248,4 +1392,3 @@ bool IsValidFileCRC(DWORD dwCRC) { return s_set_dwFileCRC.find(dwCRC) != s_set_dwFileCRC.end(); } -