// Cyphesis Online RPG Server and AI Engine // Copyright (C) 2000-2004 Alistair Riddoch // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software Foundation, // Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // $Id: globals.cpp,v 1.60 2007-12-06 02:46:33 alriddoch Exp $ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "globals.h" #include "prefix.h" #include "const.h" #include "log.h" #include "compose.hpp" #include "modules/DateTime.h" #include #include #include #include const char * CYPHESIS = "cyphesis"; const char * CLIENT = "client"; const char * SLAVE = "slave"; static const char * DEFAULT_RULESET = "mason"; static const char * DEFAULT_CLIENT_SOCKET = "cyphesis.sock"; static const char * DEFAULT_SLAVE_SOCKET = "cyslave.sock"; static const char * DEFAULT_INSTANCE = "cyphesis"; varconf::Config * global_conf = NULL; std::string instance(DEFAULT_INSTANCE); std::string share_directory(DATADIR); std::string etc_directory(SYSCONFDIR); std::string var_directory(LOCALSTATEDIR); std::string client_socket_name(DEFAULT_CLIENT_SOCKET); std::string slave_socket_name(DEFAULT_SLAVE_SOCKET); std::string ruleset(DEFAULT_RULESET); bool exit_flag = false; bool daemon_flag = false; bool restricted_flag = false; bool database_flag = true; bool pvp_flag = false; bool pvp_offl_flag = false; int timeoffset = DateTime::spm() * DateTime::mph() * 9; // Morning int client_port_num = 6767; int slave_port_num = 6768; int peer_port_num = 6769; int dynamic_port_start = 6780; int dynamic_port_end = 6799; static const char * FALLBACK_LOCALSTATEDIR = "/var"; static const int S = USAGE_SERVER; static const int C = USAGE_CLIENT; static const int M = USAGE_CYCMD; static const int D = USAGE_DBASE; typedef struct { const char * section; const char * option; const char * value; const char * dflt; const char * description; int flags; } usage_data; static const usage_data usage[] = { { "", "help", "", "", "Display usage information and exit", S|C|M|D }, { "", "version", "", "", "Display the version information and exit", S|C|M|D }, { "", "instance", "", "\"cyphesis\"", "Unique short name for the server instance", S|C|M|D }, { CYPHESIS, "directory", "", "", "Directory where server data and scripts can be found", S|C }, { CYPHESIS, "confdir", "", "", "Directory where server config can be found", S|C|M|D }, { CYPHESIS, "vardir", "", "", "Directory where temporary files can be stored", S|C|M }, { CYPHESIS, "ruleset", "", DEFAULT_RULESET, "Ruleset name", S|C|D }, { CYPHESIS, "servername", "", "", "Published name of the server", S|C }, { CYPHESIS, "tcpport", "", "6767", "Network listen port for client connections", S|C|M }, { CYPHESIS, "dynamic_port_start", "", "6780", "Lowest port to try and used for dyanmic ports", S }, { CYPHESIS, "dynamic_port_end", "", "6780", "Highest port to try and used for dyanmic ports", S }, { CYPHESIS, "unixport", "", DEFAULT_CLIENT_SOCKET, "Local listen socket for admin connections", S|C|M }, { CYPHESIS, "restricted", "true|false", "false", "Flag to control restricted mode", S }, { CYPHESIS, "usemetaserver", "true|false", "true", "Flag to control registration with the metaserver", S }, { CYPHESIS, "usedatabase", "true|false", "true", "Flag to control whether to use a database for persistent storage", S }, { CYPHESIS, "metaserver", "", "metaserver.worldforge.org", "Hostname to use as the metaserver", S }, { CYPHESIS, "daemon", "true|false", "false", "Flag to control running the server in daemon mode", S }, { CYPHESIS, "nice", "", "1", "Reduce the priority level of the server", S }, { CYPHESIS, "useaiclient", "true|false", "false", "Flag to control whether AI is to be driven by a client", S }, { CYPHESIS, "dbserver", "", "", "Hostname for the PostgreSQL RDBMS", S|D }, { CYPHESIS, "dbname", "", "\"cyphesis\"", "Name of the database to use", S|D }, { CYPHESIS, "dbuser", "", "", "Database user name for access", S|D }, { CYPHESIS, "dbpasswd", "", "", "Database password for access", S|D }, { CLIENT, "package", "", "define_world", "Python package which contains the world initialisation code", C }, { CLIENT, "function", "", "default", "Python function to initialise the world", C }, { CLIENT, "serverhost", "", "localhost", "Hostname of the server to connect to", S|C|M }, { CLIENT, "account", "", "admin", "Account name to use to authenticate to the server", S|C }, { CLIENT, "password", "", "", "Password to use to authenticate to the server", S|C }, { CLIENT, "useslave", "true|false", "false", "Flag to control connecting to an AI slave server, not master world server" , S|M }, { SLAVE, "tcpport", "", "6768", "Network listen port for client connections to the AI slave server", M }, { SLAVE, "unixport", "", DEFAULT_SLAVE_SOCKET, "Local listen socket for admin connections to the AI slave server", M }, { SLAVE, "server", "", "localhost", "Master server to connect the slave to", M }, { 0, 0, 0, 0 } }; static int check_tmp_path(const std::string & dir) { std::string tmp_directory = dir + "/tmp"; struct stat tmp_stat; if (::stat(tmp_directory.c_str(), &tmp_stat) != 0) { return -1; } if (!S_ISDIR(tmp_stat.st_mode)) { return -1; } if (::access(tmp_directory.c_str(), W_OK) != 0) { return -1; } return 0; } static int force_simple_name(const std::string & in, std::string & out) { out = std::string(in.size(), ' '); for (unsigned int i = 0; i < in.size(); ++i) { int c = in[i]; if (islower(c) || isdigit(c)) { out[i] = c; } else if (isalpha(c)) { out[i] = ::tolower(c); } else if (isspace(c) || c == '_') { out[i] = '_'; } else { return -1; } } return 0; } template int readConfigItem(const std::string & section, const std::string & key, T & storage) { if (global_conf->findItem(section, key)) { storage = global_conf->getItem(section, key); return 0; } return -1; } template<> int readConfigItem(const std::string & section, const std::string & key, std::string & storage) { if (global_conf->findItem(section, key)) { storage = global_conf->getItem(section, key).as_string(); return 0; } return -1; } typedef std::map OptionHelp; typedef std::map UsageHelp; static int check_config(varconf::Config & config, int usage_groups = USAGE_SERVER| USAGE_CLIENT| USAGE_CYCMD| USAGE_DBASE) { UsageHelp usage_help; const usage_data * ud = &usage[0]; for (; ud->section != 0; ++ud) { if ((ud->flags & usage_groups) == 0) { continue; } usage_help[ud->section].insert(std::make_pair(ud->option, ud->description)); } UsageHelp::const_iterator I = usage_help.begin(); UsageHelp::const_iterator Iend = usage_help.end(); for (; I != Iend; ++I) { const std::string & section_name = I->first; const OptionHelp & section_help = I->second; const varconf::sec_map & section = config.getSection(section_name); varconf::sec_map::const_iterator J = section.begin(); varconf::sec_map::const_iterator Jend = section.end(); for (; J != Jend; ++J) { const std::string & option_name = J->first; if (section_help.find(J->first) == section_help.end()) { log(WARNING, String::compose("Invalid option -- %1:%2", section_name, option_name)); } } } return 0; } void readInstanceConfiguration(const std::string & section); int loadConfig(int argc, char ** argv, int usage) { global_conf = varconf::Config::inst(); global_conf->setParameterLookup('h', "help"); global_conf->setParameterLookup('?', "help"); global_conf->setParameterLookup('v', "version"); // Check the commmand line config doesn't contain any unknown or // inappropriate options. varconf::Config test_cmdline; test_cmdline.getCmdline(argc, argv); check_config(test_cmdline, usage); // See if the user has set the install directory on the command line bool home_dir_config = false; char * home = getenv("HOME"); // Read in only the users settings, and the commandline settings. if (home != NULL) { home_dir_config = global_conf->readFromFile(std::string(home) + "/.cyphesis.vconf"); } global_conf->getCmdline(argc, argv); if (global_conf->findItem("", "version")) { return CONFIG_VERSION; } if (global_conf->findItem("", "help")) { return CONFIG_HELP; } // Check if the config directory has been overriden at this point, as if // it has, that will affect loading the main config. readConfigItem("cyphesis", "confdir", etc_directory); // Load up the rest of the system config file, and then ensure that // settings are overridden in the users config file, and the command line bool main_config = global_conf->readFromFile(etc_directory + "/cyphesis/cyphesis.vconf", varconf::GLOBAL); if (!main_config) { log(ERROR, String::compose("Unable to read main config file \"%1\"", etc_directory + "/cyphesis/cyphesis.vconf")); if (home_dir_config) { log(INFO, "Try removing .cyphesis.vconf from your home directory as it may specify an invalid installation directory, and then restart cyphesis."); } else { log(INFO, "Please ensure that cyphesis has been installed correctly."); } } if (home_dir_config) { global_conf->readFromFile(std::string(home) + "/.cyphesis.vconf"); } int optind = global_conf->getCmdline(argc, argv); check_config(*global_conf); assert(optind > 0); std::string raw_instance; if (readConfigItem("", "instance", raw_instance) == 0) { if (force_simple_name(raw_instance, instance) != 0) { log(ERROR, "Invalid instance name."); return CONFIG_ERROR; } if (raw_instance != instance) { log(INFO, String::compose("Using instance name \"%1\".", instance)); } } readConfigItem("cyphesis", "dynamic_port_start", dynamic_port_start); readConfigItem("cyphesis", "dynamic_port_end", dynamic_port_end); readInstanceConfiguration(instance); return optind; } void updateUserConfiguration() { char * home = getenv("HOME"); // Write out any changes that have been overriden at user scope. It // may be a good idea to do this at shutdown. if (home != NULL) { global_conf->writeToFile(std::string(home) + "/.cyphesis.vconf", varconf::USER); } } void readInstanceConfiguration(const std::string & section) { // Config is now loaded. Now set the values of some globals. readConfigItem(section, "directory", share_directory); readConfigItem(section, "confdir", etc_directory); readConfigItem(section, "vardir", var_directory); readConfigItem(section, "daemon", daemon_flag); if (readConfigItem(section, "tcpport", client_port_num) != 0) { if (section != DEFAULT_INSTANCE) { client_port_num = -1; } } if (readConfigItem(section, "unixport", client_socket_name) != 0) { client_socket_name = String::compose("cyphesis_%1.sock", section); } readConfigItem("slave", "tcpport", slave_port_num); readConfigItem("slave", "unixport", slave_socket_name); readConfigItem("game", "player_vs_player", pvp_flag); readConfigItem("game", "player_vs_player_offline", pvp_offl_flag); // Load up the ruleset. if (readConfigItem(section, "ruleset", ruleset)) { if (section == DEFAULT_INSTANCE) { log(ERROR, String::compose("No ruleset specified in config. " "Using \"%1\" rules.", DEFAULT_RULESET)); } else { log(INFO, String::compose("Auto configuring new instance \"%1\" " "to use ruleset \"%2\".", instance, ruleset)); global_conf->setItem(section, "ruleset", ruleset, varconf::USER); } } if (check_tmp_path(var_directory) != 0) { if (var_directory != "/usr/var") { // Binreloc enabled builds installed system wide have localstatedir // set to something that is never writable, so must always fall // back to /var/tmp, so we should not display the message. log(WARNING, String::compose("No temporary directory found at \"%1/tmp\"", var_directory)); } if (check_tmp_path(FALLBACK_LOCALSTATEDIR) != 0) { log(CRITICAL, String::compose("No temporary directory available " "at \"%1/tmp\" or \"%2/tmp\".", var_directory, FALLBACK_LOCALSTATEDIR)); } else { if (var_directory != "/usr/var") { log(NOTICE, String::compose("Using \"%1/tmp\" as temporary directory", FALLBACK_LOCALSTATEDIR)); } var_directory = FALLBACK_LOCALSTATEDIR; } } } void reportVersion(const char * prgname) { std::cout << prgname << " (cyphesis) " << consts::version << " (WorldForge)" << std::endl << std::flush; } void showUsage(const char * prgname, int usage_flags, const char * extras) { std::cout << "Usage: " << prgname << " [options]"; if (extras != 0) { std::cout << " " << extras; } std::cout << std::endl; std::cout << "Options:" << std::endl; size_t column_width = 0; const usage_data * ud = &usage[0]; for (; ud->section != 0; ++ud) { column_width = std::max(column_width, strlen(ud->section) + strlen(ud->option) + strlen(ud->value) + 2); } std::cout << " --help, -h Display usage information and exit" << std::endl; std::cout << " --version, -v Display the version information and exit" << std::endl << std::endl; ud = &usage[0]; for (; ud->section != 0; ++ud) { if ((ud->flags & usage_flags) == 0) { continue; } if (strlen(ud->section) != 0) { std::cout << " --" << ud->section << ":" << ud->option; } else { std::cout << " --" << ud->option; } if (ud->value != 0 && strlen(ud->value) != 0) { std::cout << "=" << ud->value; } if (ud->dflt != 0 && strlen(ud->dflt) != 0) { size_t len = strlen(ud->section) + 1 + strlen(ud->option); if (ud->value != 0 && strlen(ud->value) != 0) { len += (strlen(ud->value) + 1); } std::cout << std::string(column_width - len + 2, ' ') << "= " << ud->dflt; } std::cout << std::endl; std::cout << " " << ud->description << std::endl; } std::cout << std::flush; }