X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fmain.cpp;h=5ea212d8ad7262496d79efced31eea9933bc9791;hb=4558793caf48d63c74574ba740a96c92d0afcc2c;hp=de9d953084fcc878c5da584b617f715e453a189c;hpb=146f77fdb750833c649de7159a0833c398e14a4d;p=dragonfireclient.git diff --git a/src/main.cpp b/src/main.cpp index de9d95308..5ea212d8a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,99 +17,61 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifdef NDEBUG - /*#ifdef _WIN32 - #pragma message ("Disabling unit tests") - #else - #warning "Disabling unit tests" - #endif*/ - // Disable unit tests - #define ENABLE_TESTS 0 -#else - // Enable unit tests - #define ENABLE_TESTS 1 -#endif - -#ifdef _MSC_VER -#ifndef SERVER // Dedicated server isn't linked with Irrlicht - #pragma comment(lib, "Irrlicht.lib") - // This would get rid of the console window - //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup") -#endif - #pragma comment(lib, "zlibwapi.lib") - #pragma comment(lib, "Shell32.lib") -#endif - +#include "irrlichttypes.h" // must be included before anything irrlicht, see comment in the file #include "irrlicht.h" // createDevice - -#include "main.h" -#include "mainmenumanager.h" #include "irrlichttypes_extrabloated.h" +#include "chat_interface.h" #include "debug.h" -#include "test.h" +#include "unittest/test.h" #include "server.h" #include "filesys.h" #include "version.h" -#include "guiMainMenu.h" -#include "game.h" +#include "client/game.h" #include "defaultsettings.h" #include "gettext.h" -#include "profiler.h" #include "log.h" -#include "quicktune.h" +#include "util/quicktune.h" #include "httpfetch.h" -#include "guiEngine.h" -#include "mapsector.h" -#include "fontengine.h" #include "gameparams.h" +#include "database/database.h" +#include "config.h" +#include "player.h" +#include "porting.h" +#include "network/socket.h" +#include "mapblock.h" +#if USE_CURSES + #include "terminal_chat_console.h" +#endif #ifndef SERVER +#include "gui/guiMainMenu.h" #include "client/clientlauncher.h" +#include "gui/guiEngine.h" +#include "gui/mainmenumanager.h" #endif - -#include "database-sqlite3.h" -#ifdef USE_LEVELDB -#include "database-leveldb.h" +#ifdef HAVE_TOUCHSCREENGUI + #include "gui/touchscreengui.h" #endif -#if USE_REDIS -#include "database-redis.h" +// for version information only +extern "C" { +#if USE_LUAJIT + #include +#else + #include #endif +} -#ifdef HAVE_TOUCHSCREENGUI -#include "touchscreengui.h" +#if !defined(__cpp_rtti) || !defined(__cpp_exceptions) +#error Minetest cannot be built without exceptions or RTTI #endif -/* - Settings. - These are loaded from the config file. -*/ -static Settings main_settings; -Settings *g_settings = &main_settings; -std::string g_settings_path; - -// Global profiler -Profiler main_profiler; -Profiler *g_profiler = &main_profiler; - -// Menu clouds are created later -Clouds *g_menuclouds = 0; -irr::scene::ISceneManager *g_menucloudsmgr = 0; - -/* - Debug streams -*/ - -// Connection -std::ostream *dout_con_ptr = &dummyout; -std::ostream *derr_con_ptr = &verbosestream; - -// Server -std::ostream *dout_server_ptr = &infostream; -std::ostream *derr_server_ptr = &errorstream; - -// Client -std::ostream *dout_client_ptr = &infostream; -std::ostream *derr_client_ptr = &errorstream; +#if defined(__MINGW32__) && !defined(__MINGW64__) && !defined(__clang__) && \ + (__GNUC__ < 11 || (__GNUC__ == 11 && __GNUC_MINOR__ < 1)) +// see e.g. https://github.com/minetest/minetest/issues/10137 +#warning ================================== +#warning 32-bit MinGW gcc before 11.1 has known issues with crashes on thread exit, you should upgrade. +#warning ================================== +#endif #define DEBUGFILE "debug.txt" #define DEFAULT_SERVER_PORT 30000 @@ -127,17 +89,18 @@ static void print_help(const OptionList &allowed_options); static void print_allowed_options(const OptionList &allowed_options); static void print_version(); static void print_worldspecs(const std::vector &worldspecs, - std::ostream &os); + std::ostream &os, bool print_name = true, bool print_path = true); static void print_modified_quicktune_values(); static void list_game_ids(); -static void list_worlds(); -static void setup_log_params(const Settings &cmd_args); +static void list_worlds(bool print_name, bool print_path); +static bool setup_log_params(const Settings &cmd_args); static bool create_userdata_path(); -static bool init_common(int *log_level, const Settings &cmd_args, int argc, char *argv[]); +static bool init_common(const Settings &cmd_args, int argc, char *argv[]); +static void uninit_common(); static void startup_message(); static bool read_config_file(const Settings &cmd_args); -static void init_debug_streams(int *log_level, const Settings &cmd_args); +static void init_log_streams(const Settings &cmd_args); static bool game_configure(GameParams *game_params, const Settings &cmd_args); static void game_configure_port(GameParams *game_params, const Settings &cmd_args); @@ -153,111 +116,59 @@ static bool get_game_from_cmdline(GameParams *game_params, const Settings &cmd_a static bool determine_subgame(GameParams *game_params); static bool run_dedicated_server(const GameParams &game_params, const Settings &cmd_args); -static bool migrate_database(const GameParams &game_params, const Settings &cmd_args, - Server *server); +static bool migrate_map_database(const GameParams &game_params, const Settings &cmd_args); +static bool recompress_map_database(const GameParams &game_params, const Settings &cmd_args, const Address &addr); /**********************************************************************/ -#ifndef SERVER -/* - Random stuff -*/ -/* mainmenumanager.h */ - -gui::IGUIEnvironment* guienv = NULL; -gui::IGUIStaticText *guiroot = NULL; -MainMenuManager g_menumgr; - -bool noMenuActive() -{ - return (g_menumgr.menuCount() == 0); -} - -// Passed to menus to allow disconnecting and exiting -MainGameCallback *g_gamecallback = NULL; -#endif - -/* - gettime.h implementation -*/ - -#ifdef SERVER - -u32 getTimeMs() -{ - /* Use imprecise system calls directly (from porting.h) */ - return porting::getTime(PRECISION_MILLI); -} - -u32 getTime(TimePrecision prec) -{ - return porting::getTime(prec); -} - -#endif - -class StderrLogOutput: public ILogOutput -{ -public: - /* line: Full line with timestamp, level and thread */ - void printLog(const std::string &line) - { - std::cerr << line << std::endl; - } -} main_stderr_log_out; - -class DstreamNoStderrLogOutput: public ILogOutput -{ -public: - /* line: Full line with timestamp, level and thread */ - void printLog(const std::string &line) - { - dstream_no_stderr << line << std::endl; - } -} main_dstream_no_stderr_log_out; +FileLogOutput file_log_output; static OptionList allowed_options; int main(int argc, char *argv[]) { int retval; - debug_set_exception_handler(); - log_add_output_maxlev(&main_stderr_log_out, LMT_ACTION); - log_add_output_all_levs(&main_dstream_no_stderr_log_out); - - log_register_thread("main"); + g_logger.registerThread("Main"); + g_logger.addOutputMaxLevel(&stderr_output, LL_ACTION); Settings cmd_args; bool cmd_args_ok = get_cmdline_opts(argc, argv, &cmd_args); if (!cmd_args_ok || cmd_args.getFlag("help") || cmd_args.exists("nonopt1")) { + porting::attachOrCreateConsole(); print_help(allowed_options); return cmd_args_ok ? 0 : 1; } + if (cmd_args.getFlag("console")) + porting::attachOrCreateConsole(); if (cmd_args.getFlag("version")) { + porting::attachOrCreateConsole(); print_version(); return 0; } - setup_log_params(cmd_args); + if (!setup_log_params(cmd_args)) + return 1; porting::signal_handler_init(); + +#ifdef __ANDROID__ + porting::initAndroid(); + porting::initializePathsAndroid(); +#else porting::initializePaths(); +#endif if (!create_userdata_path()) { errorstream << "Cannot create user data directory" << std::endl; return 1; } - // Initialize debug stacks - debug_stacks_init(); - DSTACK(__FUNCTION_NAME); - // Debug handler BEGIN_DEBUG_EXCEPTION_HANDLER @@ -267,62 +178,74 @@ int main(int argc, char *argv[]) return 0; } - // List worlds if requested - if (cmd_args.exists("world") && cmd_args.get("world") == "list") { - list_worlds(); + // List worlds, world names, and world paths if requested + if (cmd_args.exists("worldlist")) { + if (cmd_args.get("worldlist") == "name") { + list_worlds(true, false); + } else if (cmd_args.get("worldlist") == "path") { + list_worlds(false, true); + } else if (cmd_args.get("worldlist") == "both") { + list_worlds(true, true); + } else { + errorstream << "Invalid --worldlist value: " + << cmd_args.get("worldlist") << std::endl; + return 1; + } return 0; } - GameParams game_params; - if (!init_common(&game_params.log_level, cmd_args, argc, argv)) + if (!init_common(cmd_args, argc, argv)) return 1; + if (g_settings->getBool("enable_console")) + porting::attachOrCreateConsole(); + #ifndef __ANDROID__ // Run unit tests - if ((ENABLE_TESTS && cmd_args.getFlag("disable-unittests") == false) - || cmd_args.getFlag("enable-unittests") == true) { - run_tests(); + if (cmd_args.getFlag("run-unittests")) { +#if BUILD_UNITTESTS + return run_tests(); +#else + errorstream << "Unittest support is not enabled in this binary. " + << "If you want to enable it, compile project with BUILD_UNITTESTS=1 flag." + << std::endl; + return 1; +#endif } #endif + GameStartData game_params; #ifdef SERVER + porting::attachOrCreateConsole(); game_params.is_dedicated_server = true; #else - game_params.is_dedicated_server = cmd_args.getFlag("server"); + const bool isServer = cmd_args.getFlag("server"); + if (isServer) + porting::attachOrCreateConsole(); + game_params.is_dedicated_server = isServer; #endif if (!game_configure(&game_params, cmd_args)) return 1; - assert(game_params.world_path != ""); - - infostream << "Using commanded world path [" - << game_params.world_path << "]" << std::endl; - - //Run dedicated server if asked to or no other option - g_settings->set("server_dedicated", - game_params.is_dedicated_server ? "true" : "false"); + sanity_check(!game_params.world_path.empty()); if (game_params.is_dedicated_server) return run_dedicated_server(game_params, cmd_args) ? 0 : 1; #ifndef SERVER - ClientLauncher launcher; - retval = launcher.run(game_params, cmd_args) ? 0 : 1; + retval = ClientLauncher().run(game_params, cmd_args) ? 0 : 1; #else retval = 0; #endif // Update configuration file - if (g_settings_path != "") + if (!g_settings_path.empty()) g_settings->updateConfigFile(g_settings_path.c_str()); print_modified_quicktune_values(); - // Stop httpfetch thread (if started) - httpfetch_cleanup(); - - END_DEBUG_EXCEPTION_HANDLER(errorstream) + END_DEBUG_EXCEPTION_HANDLER return retval; } @@ -352,18 +275,22 @@ static void set_allowed_options(OptionList *allowed_options) _("Load configuration from specified file")))); allowed_options->insert(std::make_pair("port", ValueSpec(VALUETYPE_STRING, _("Set network port (UDP)")))); - allowed_options->insert(std::make_pair("disable-unittests", ValueSpec(VALUETYPE_FLAG, - _("Disable unit tests")))); - allowed_options->insert(std::make_pair("enable-unittests", ValueSpec(VALUETYPE_FLAG, - _("Enable unit tests")))); + allowed_options->insert(std::make_pair("run-unittests", ValueSpec(VALUETYPE_FLAG, + _("Run the unit tests and exit")))); allowed_options->insert(std::make_pair("map-dir", ValueSpec(VALUETYPE_STRING, _("Same as --world (deprecated)")))); allowed_options->insert(std::make_pair("world", ValueSpec(VALUETYPE_STRING, - _("Set world path (implies local game) ('list' lists all)")))); + _("Set world path (implies local game if used with option --go)")))); allowed_options->insert(std::make_pair("worldname", ValueSpec(VALUETYPE_STRING, - _("Set world by name (implies local game)")))); + _("Set world by name (implies local game if used with option --go)")))); + allowed_options->insert(std::make_pair("worldlist", ValueSpec(VALUETYPE_STRING, + _("Get list of worlds ('path' lists paths, " + "'name' lists names, 'both' lists both)")))); allowed_options->insert(std::make_pair("quiet", ValueSpec(VALUETYPE_FLAG, _("Print to console errors only")))); + allowed_options->insert(std::make_pair("color", ValueSpec(VALUETYPE_STRING, + _("Coloured logs ('always', 'never' or 'auto'), defaults to 'auto'" + )))); allowed_options->insert(std::make_pair("info", ValueSpec(VALUETYPE_FLAG, _("Print more information to console")))); allowed_options->insert(std::make_pair("verbose", ValueSpec(VALUETYPE_FLAG, @@ -376,9 +303,17 @@ static void set_allowed_options(OptionList *allowed_options) _("Set gameid (\"--gameid list\" prints available ones)")))); allowed_options->insert(std::make_pair("migrate", ValueSpec(VALUETYPE_STRING, _("Migrate from current map backend to another (Only works when using minetestserver or with --server)")))); + allowed_options->insert(std::make_pair("migrate-players", ValueSpec(VALUETYPE_STRING, + _("Migrate from current players backend to another (Only works when using minetestserver or with --server)")))); + allowed_options->insert(std::make_pair("migrate-auth", ValueSpec(VALUETYPE_STRING, + _("Migrate from current auth backend to another (Only works when using minetestserver or with --server)")))); + allowed_options->insert(std::make_pair("migrate-mod-storage", ValueSpec(VALUETYPE_STRING, + _("Migrate from current mod storage backend to another (Only works when using minetestserver or with --server)")))); + allowed_options->insert(std::make_pair("terminal", ValueSpec(VALUETYPE_FLAG, + _("Feature an interactive terminal (Only works when using minetestserver or with --server)")))); + allowed_options->insert(std::make_pair("recompress", ValueSpec(VALUETYPE_FLAG, + _("Recompress the blocks of the given map database.")))); #ifndef SERVER - allowed_options->insert(std::make_pair("videomodes", ValueSpec(VALUETYPE_FLAG, - _("Show available video modes")))); allowed_options->insert(std::make_pair("speedtests", ValueSpec(VALUETYPE_FLAG, _("Run speed tests")))); allowed_options->insert(std::make_pair("address", ValueSpec(VALUETYPE_STRING, @@ -391,73 +326,81 @@ static void set_allowed_options(OptionList *allowed_options) _("Set player name")))); allowed_options->insert(std::make_pair("password", ValueSpec(VALUETYPE_STRING, _("Set password")))); + allowed_options->insert(std::make_pair("password-file", ValueSpec(VALUETYPE_STRING, + _("Set password from contents of file")))); allowed_options->insert(std::make_pair("go", ValueSpec(VALUETYPE_FLAG, _("Disable main menu")))); + allowed_options->insert(std::make_pair("console", ValueSpec(VALUETYPE_FLAG, + _("Starts with the console (Windows only)")))); #endif } static void print_help(const OptionList &allowed_options) { - dstream << _("Allowed options:") << std::endl; + std::cout << _("Allowed options:") << std::endl; print_allowed_options(allowed_options); } static void print_allowed_options(const OptionList &allowed_options) { - for (OptionList::const_iterator i = allowed_options.begin(); - i != allowed_options.end(); ++i) { + for (const auto &allowed_option : allowed_options) { std::ostringstream os1(std::ios::binary); - os1 << " --" << i->first; - if (i->second.type != VALUETYPE_FLAG) + os1 << " --" << allowed_option.first; + if (allowed_option.second.type != VALUETYPE_FLAG) os1 << _(" "); - dstream << padStringRight(os1.str(), 24); + std::cout << padStringRight(os1.str(), 30); - if (i->second.help != NULL) - dstream << i->second.help; + if (allowed_option.second.help) + std::cout << allowed_option.second.help; - dstream << std::endl; + std::cout << std::endl; } } static void print_version() { -#ifdef SERVER - dstream << "minetestserver " << minetest_version_hash << std::endl; + std::cout << PROJECT_NAME_C " " << g_version_hash + << " (" << porting::getPlatformName() << ")" << std::endl; +#ifndef SERVER + std::cout << "Using Irrlicht " IRRLICHT_SDK_VERSION << std::endl; +#endif +#if USE_LUAJIT + std::cout << "Using " << LUAJIT_VERSION << std::endl; #else - dstream << "Minetest " << minetest_version_hash << std::endl; - dstream << "Using Irrlicht " << IRRLICHT_SDK_VERSION << std::endl; + std::cout << "Using " << LUA_RELEASE << std::endl; #endif - dstream << "Build info: " << minetest_build_info << std::endl; + std::cout << g_build_info << std::endl; } static void list_game_ids() { std::set gameids = getAvailableGameIds(); - for (std::set::const_iterator i = gameids.begin(); - i != gameids.end(); i++) - dstream << (*i) < worldspecs = getAvailableWorlds(); - print_worldspecs(worldspecs, dstream); + print_worldspecs(worldspecs, std::cout, print_name, print_path); } static void print_worldspecs(const std::vector &worldspecs, - std::ostream &os) + std::ostream &os, bool print_name, bool print_path) { - for (size_t i = 0; i < worldspecs.size(); i++) { - std::string name = worldspecs[i].name; - std::string path = worldspecs[i].path; - if (name.find(" ") != std::string::npos) - name = std::string("'") + name + "'"; - path = std::string("'") + path + "'"; - name = padStringRight(name, 14); - os << " " << name << " " << path << std::endl; + for (const WorldSpec &worldspec : worldspecs) { + std::string name = worldspec.name; + std::string path = worldspec.path; + if (print_name && print_path) { + os << "\t" << name << "\t\t" << path << std::endl; + } else if (print_name) { + os << "\t" << name << std::endl; + } else if (print_path) { + os << "\t" << path << std::endl; + } } } @@ -466,42 +409,68 @@ static void print_modified_quicktune_values() bool header_printed = false; std::vector names = getQuicktuneNames(); - for (u32 i = 0; i < names.size(); i++) { - QuicktuneValue val = getQuicktuneValue(names[i]); + for (const std::string &name : names) { + QuicktuneValue val = getQuicktuneValue(name); if (!val.modified) continue; if (!header_printed) { dstream << "Modified quicktune values:" << std::endl; header_printed = true; } - dstream << names[i] << " = " << val.getString() << std::endl; + dstream << name << " = " << val.getString() << std::endl; } } -static void setup_log_params(const Settings &cmd_args) +static bool setup_log_params(const Settings &cmd_args) { // Quiet mode, print errors only if (cmd_args.getFlag("quiet")) { - log_remove_output(&main_stderr_log_out); - log_add_output_maxlev(&main_stderr_log_out, LMT_ERROR); + g_logger.removeOutput(&stderr_output); + g_logger.addOutputMaxLevel(&stderr_output, LL_ERROR); + } + + // Coloured log messages (see log.h) + std::string color_mode; + if (cmd_args.exists("color")) { + color_mode = cmd_args.get("color"); +#if !defined(_WIN32) + } else { + char *color_mode_env = getenv("MT_LOGCOLOR"); + if (color_mode_env) + color_mode = color_mode_env; +#endif + } + if (color_mode != "") { + if (color_mode == "auto") { + Logger::color_mode = LOG_COLOR_AUTO; + } else if (color_mode == "always") { + Logger::color_mode = LOG_COLOR_ALWAYS; + } else if (color_mode == "never") { + Logger::color_mode = LOG_COLOR_NEVER; + } else { + errorstream << "Invalid color mode: " << color_mode << std::endl; + return false; + } } // If trace is enabled, enable logging of certain things if (cmd_args.getFlag("trace")) { dstream << _("Enabling trace level debug output") << std::endl; - log_trace_level_enabled = true; - dout_con_ptr = &verbosestream; // this is somewhat old crap - socket_enable_debug_output = true; // socket doesn't use log.h + g_logger.setTraceEnabled(true); + dout_con_ptr = &verbosestream; // This is somewhat old + socket_enable_debug_output = true; // Sockets doesn't use log.h } // In certain cases, output info level on stderr if (cmd_args.getFlag("info") || cmd_args.getFlag("verbose") || cmd_args.getFlag("trace") || cmd_args.getFlag("speedtests")) - log_add_output(&main_stderr_log_out, LMT_INFO); + g_logger.addOutput(&stderr_output, LL_INFO); // In certain cases, output verbose level on stderr if (cmd_args.getFlag("verbose") || cmd_args.getFlag("trace")) - log_add_output(&main_stderr_log_out, LMT_VERBOSE); + g_logger.addOutput(&stderr_output, LL_VERBOSE); + + return true; } static bool create_userdata_path() @@ -509,39 +478,36 @@ static bool create_userdata_path() bool success; #ifdef __ANDROID__ - porting::initAndroid(); - - porting::setExternalStorageDir(porting::jnienv); if (!fs::PathExists(porting::path_user)) { success = fs::CreateDir(porting::path_user); } else { success = true; } - porting::copyAssets(); #else // Create user data directory success = fs::CreateDir(porting::path_user); #endif - infostream << "path_share = " << porting::path_share << std::endl; - infostream << "path_user = " << porting::path_user << std::endl; - return success; } -static bool init_common(int *log_level, const Settings &cmd_args, int argc, char *argv[]) +static bool init_common(const Settings &cmd_args, int argc, char *argv[]) { startup_message(); - set_default_settings(g_settings); + set_default_settings(); - // Initialize sockets sockets_init(); - atexit(sockets_cleanup); + + // Initialize g_settings + Settings::createLayer(SL_GLOBAL); + + // Set cleanup callback(s) to run at process exit + atexit(uninit_common); if (!read_config_file(cmd_args)) return false; - init_debug_streams(log_level, cmd_args); + init_log_streams(cmd_args); // Initialize random seed srand(time(0)); @@ -550,29 +516,35 @@ static bool init_common(int *log_level, const Settings &cmd_args, int argc, char // Initialize HTTP fetcher httpfetch_init(g_settings->getS32("curl_parallel_limit")); -#ifdef _MSC_VER - init_gettext((porting::path_share + DIR_DELIM + "locale").c_str(), + init_gettext(porting::path_locale.c_str(), g_settings->get("language"), argc, argv); -#else - init_gettext((porting::path_share + DIR_DELIM + "locale").c_str(), - g_settings->get("language")); -#endif return true; } +static void uninit_common() +{ + httpfetch_cleanup(); + + sockets_cleanup(); + + // It'd actually be okay to leak these but we want to please valgrind... + for (int i = 0; i < (int)SL_TOTAL_COUNT; i++) + delete Settings::getLayer((SettingsLayer)i); +} + static void startup_message() { infostream << PROJECT_NAME << " " << _("with") << " SER_FMT_VER_HIGHEST_READ=" << (int)SER_FMT_VER_HIGHEST_READ << ", " - << minetest_build_info << std::endl; + << g_build_info << std::endl; } static bool read_config_file(const Settings &cmd_args) { // Path of configuration file in use - assert(g_settings_path == ""); // Sanity check + sanity_check(g_settings_path == ""); // Sanity check if (cmd_args.exists("config")) { bool r = g_settings->readConfigFile(cmd_args.get("config").c_str()); @@ -596,54 +568,59 @@ static bool read_config_file(const Settings &cmd_args) DIR_DELIM + ".." + DIR_DELIM + ".." + DIR_DELIM + "minetest.conf"); #endif - for (size_t i = 0; i < filenames.size(); i++) { - bool r = g_settings->readConfigFile(filenames[i].c_str()); + for (const std::string &filename : filenames) { + bool r = g_settings->readConfigFile(filename.c_str()); if (r) { - g_settings_path = filenames[i]; + g_settings_path = filename; break; } } // If no path found, use the first one (menu creates the file) - if (g_settings_path == "") + if (g_settings_path.empty()) g_settings_path = filenames[0]; } return true; } -static void init_debug_streams(int *log_level, const Settings &cmd_args) +static void init_log_streams(const Settings &cmd_args) { -#if RUN_IN_PLACE - std::string logfile = DEBUGFILE; -#else - std::string logfile = porting::path_user + DIR_DELIM + DEBUGFILE; -#endif - if (cmd_args.exists("logfile")) - logfile = cmd_args.get("logfile"); + std::string log_filename = porting::path_user + DIR_DELIM + DEBUGFILE; - log_remove_output(&main_dstream_no_stderr_log_out); - *log_level = g_settings->getS32("debug_log_level"); - - if (*log_level == 0) //no logging - logfile = ""; - if (*log_level < 0) { - dstream << "WARNING: Supplied debug_log_level < 0; Using 0" << std::endl; - *log_level = 0; - } else if (*log_level > LMT_NUM_VALUES) { - dstream << "WARNING: Supplied debug_log_level > " << LMT_NUM_VALUES - << "; Using " << LMT_NUM_VALUES << std::endl; - *log_level = LMT_NUM_VALUES; + if (cmd_args.exists("logfile")) + log_filename = cmd_args.get("logfile"); + + g_logger.removeOutput(&file_log_output); + std::string conf_loglev = g_settings->get("debug_log_level"); + + // Old integer format + if (std::isdigit(conf_loglev[0])) { + warningstream << "Deprecated use of debug_log_level with an " + "integer value; please update your configuration." << std::endl; + static const char *lev_name[] = + {"", "error", "action", "info", "verbose"}; + int lev_i = atoi(conf_loglev.c_str()); + if (lev_i < 0 || lev_i >= (int)ARRLEN(lev_name)) { + warningstream << "Supplied invalid debug_log_level!" + " Assuming action level." << std::endl; + lev_i = 2; + } + conf_loglev = lev_name[lev_i]; } - log_add_output_maxlev(&main_dstream_no_stderr_log_out, - (LogMessageLevel)(*log_level - 1)); - - debugstreams_init(false, logfile == "" ? NULL : logfile.c_str()); + if (log_filename.empty() || conf_loglev.empty()) // No logging + return; - infostream << "logfile = " << logfile << std::endl; + LogLevel log_level = Logger::stringToLevel(conf_loglev); + if (log_level == LL_MAX) { + warningstream << "Supplied unrecognized debug_log_level; " + "using maximum." << std::endl; + } - atexit(debugstreams_deinit); + file_log_output.setFile(log_filename, + g_settings->getU64("debug_log_size_max") * 1000000); + g_logger.addOutputMaxLevel(&file_log_output, log_level); } static bool game_configure(GameParams *game_params, const Settings &cmd_args) @@ -662,10 +639,14 @@ static bool game_configure(GameParams *game_params, const Settings &cmd_args) static void game_configure_port(GameParams *game_params, const Settings &cmd_args) { - if (cmd_args.exists("port")) + if (cmd_args.exists("port")) { game_params->socket_port = cmd_args.getU16("port"); - else - game_params->socket_port = g_settings->getU16("port"); + } else { + if (game_params->is_dedicated_server) + game_params->socket_port = g_settings->getU16("port"); + else + game_params->socket_port = g_settings->getU16("remote_port"); + } if (game_params->socket_port == 0) game_params->socket_port = DEFAULT_SERVER_PORT; @@ -675,6 +656,7 @@ static bool game_configure_world(GameParams *game_params, const Settings &cmd_ar { if (get_world_from_cmdline(game_params, cmd_args)) return true; + if (get_world_from_config(game_params, cmd_args)) return true; @@ -683,24 +665,24 @@ static bool game_configure_world(GameParams *game_params, const Settings &cmd_ar static bool get_world_from_cmdline(GameParams *game_params, const Settings &cmd_args) { - std::string commanded_world = ""; + std::string commanded_world; // World name - std::string commanded_worldname = ""; + std::string commanded_worldname; if (cmd_args.exists("worldname")) commanded_worldname = cmd_args.get("worldname"); // If a world name was specified, convert it to a path - if (commanded_worldname != "") { + if (!commanded_worldname.empty()) { // Get information about available worlds std::vector worldspecs = getAvailableWorlds(); bool found = false; - for (u32 i = 0; i < worldspecs.size(); i++) { - std::string name = worldspecs[i].name; + for (const WorldSpec &worldspec : worldspecs) { + std::string name = worldspec.name; if (name == commanded_worldname) { dstream << _("Using world specified by --worldname on the " "command line") << std::endl; - commanded_world = worldspecs[i].path; + commanded_world = worldspec.path; found = true; break; } @@ -713,7 +695,7 @@ static bool get_world_from_cmdline(GameParams *game_params, const Settings &cmd_ } game_params->world_path = get_clean_world_path(commanded_world); - return commanded_world != ""; + return !commanded_world.empty(); } if (cmd_args.exists("world")) @@ -724,20 +706,20 @@ static bool get_world_from_cmdline(GameParams *game_params, const Settings &cmd_ commanded_world = cmd_args.get("nonopt0"); game_params->world_path = get_clean_world_path(commanded_world); - return commanded_world != ""; + return !commanded_world.empty(); } static bool get_world_from_config(GameParams *game_params, const Settings &cmd_args) { // World directory - std::string commanded_world = ""; + std::string commanded_world; if (g_settings->exists("map-dir")) commanded_world = g_settings->get("map-dir"); game_params->world_path = get_clean_world_path(commanded_world); - return commanded_world != ""; + return !commanded_world.empty(); } static bool auto_select_world(GameParams *game_params) @@ -745,8 +727,6 @@ static bool auto_select_world(GameParams *game_params) // No world was specified; try to select it automatically // Get information about available worlds - verbosestream << _("Determining world path") << std::endl; - std::vector worldspecs = getAvailableWorlds(); std::string world_path; @@ -757,21 +737,21 @@ static bool auto_select_world(GameParams *game_params) << world_path << "]" << std::endl; // If there are multiple worlds, list them } else if (worldspecs.size() > 1 && game_params->is_dedicated_server) { - dstream << _("Multiple worlds are available.") << std::endl; - dstream << _("Please select one using --worldname " + std::cerr << _("Multiple worlds are available.") << std::endl; + std::cerr << _("Please select one using --worldname " " or --world ") << std::endl; - print_worldspecs(worldspecs, dstream); + print_worldspecs(worldspecs, std::cerr); return false; // If there are no worlds, automatically create a new one } else { // This is the ultimate default world path world_path = porting::path_user + DIR_DELIM + "worlds" + DIR_DELIM + "world"; - infostream << "Creating default world at [" + infostream << "Using default world at [" << world_path << "]" << std::endl; } - assert(world_path != ""); + assert(world_path != ""); // Post-condition game_params->world_path = world_path; return true; } @@ -827,12 +807,11 @@ static bool determine_subgame(GameParams *game_params) { SubgameSpec gamespec; - assert(game_params->world_path != ""); // pre-condition + assert(game_params->world_path != ""); // Pre-condition - verbosestream << _("Determining gameid/gamespec") << std::endl; // If world doesn't exist - if (game_params->world_path != "" - && !getWorldExists(game_params->world_path)) { + if (!game_params->world_path.empty() + && !getWorldExists(game_params->world_path)) { // Try to take gamespec from command line if (game_params->game_spec.isValid()) { gamespec = game_params->game_spec; @@ -841,7 +820,7 @@ static bool determine_subgame(GameParams *game_params) gamespec = findSubgame(g_settings->get("default_game")); infostream << "Using default gameid [" << gamespec.id << "]" << std::endl; if (!gamespec.isValid()) { - errorstream << "Subgame specified in default_game [" + errorstream << "Game specified in default_game [" << g_settings->get("default_game") << "] is invalid." << std::endl; return false; @@ -853,7 +832,7 @@ static bool determine_subgame(GameParams *game_params) if (game_params->game_spec.isValid()) { gamespec = game_params->game_spec; if (game_params->game_spec.id != world_gameid) { - errorstream << "WARNING: Using commanded gameid [" + warningstream << "Using commanded gameid [" << gamespec.id << "]" << " instead of world gameid [" << world_gameid << "]" << std::endl; } @@ -866,7 +845,7 @@ static bool determine_subgame(GameParams *game_params) } if (!gamespec.isValid()) { - errorstream << "Subgame [" << gamespec.id << "] could not be found." + errorstream << "Game [" << gamespec.id << "] could not be found." << std::endl; return false; } @@ -881,8 +860,6 @@ static bool determine_subgame(GameParams *game_params) *****************************************************************************/ static bool run_dedicated_server(const GameParams &game_params, const Settings &cmd_args) { - DSTACK("Dedicated server branch"); - verbosestream << _("Using world path") << " [" << game_params.world_path << "]" << std::endl; verbosestream << _("Using gameid") << " [" @@ -909,99 +886,232 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings & return false; } - // Create server - Server server(game_params.world_path, - game_params.game_spec, false, bind_addr.isIPv6()); - - // Database migration + // Database migration/compression if (cmd_args.exists("migrate")) - return migrate_database(game_params, cmd_args, &server); + return migrate_map_database(game_params, cmd_args); + + if (cmd_args.exists("migrate-players")) + return ServerEnvironment::migratePlayersDatabase(game_params, cmd_args); + + if (cmd_args.exists("migrate-auth")) + return ServerEnvironment::migrateAuthDatabase(game_params, cmd_args); + + if (cmd_args.exists("migrate-mod-storage")) + return Server::migrateModStorageDatabase(game_params, cmd_args); + + if (cmd_args.getFlag("recompress")) + return recompress_map_database(game_params, cmd_args, bind_addr); + + if (cmd_args.exists("terminal")) { +#if USE_CURSES + bool name_ok = true; + std::string admin_nick = g_settings->get("name"); + + name_ok = name_ok && !admin_nick.empty(); + name_ok = name_ok && string_allowed(admin_nick, PLAYERNAME_ALLOWED_CHARS); + + if (!name_ok) { + if (admin_nick.empty()) { + errorstream << "No name given for admin. " + << "Please check your minetest.conf that it " + << "contains a 'name = ' to your main admin account." + << std::endl; + } else { + errorstream << "Name for admin '" + << admin_nick << "' is not valid. " + << "Please check that it only contains allowed characters. " + << "Valid characters are: " << PLAYERNAME_ALLOWED_CHARS_USER_EXPL + << std::endl; + } + return false; + } + ChatInterface iface; + bool &kill = *porting::signal_handler_killstatus(); - server.start(bind_addr); + try { + // Create server + Server server(game_params.world_path, game_params.game_spec, + false, bind_addr, true, &iface); - // Run server - bool &kill = *porting::signal_handler_killstatus(); - dedicated_server_loop(server, kill); + g_term_console.setup(&iface, &kill, admin_nick); + + g_term_console.start(); + + server.start(); + // Run server + dedicated_server_loop(server, kill); + } catch (const ModError &e) { + g_term_console.stopAndWaitforThread(); + errorstream << "ModError: " << e.what() << std::endl; + return false; + } catch (const ServerError &e) { + g_term_console.stopAndWaitforThread(); + errorstream << "ServerError: " << e.what() << std::endl; + return false; + } + + // Tell the console to stop, and wait for it to finish, + // only then leave context and free iface + g_term_console.stop(); + g_term_console.wait(); + + g_term_console.clearKillStatus(); + } else { +#else + errorstream << "Cmd arg --terminal passed, but " + << "compiled without ncurses. Ignoring." << std::endl; + } { +#endif + try { + // Create server + Server server(game_params.world_path, game_params.game_spec, false, + bind_addr, true); + server.start(); + + // Run server + bool &kill = *porting::signal_handler_killstatus(); + dedicated_server_loop(server, kill); + + } catch (const ModError &e) { + errorstream << "ModError: " << e.what() << std::endl; + return false; + } catch (const ServerError &e) { + errorstream << "ServerError: " << e.what() << std::endl; + return false; + } + } return true; } -static bool migrate_database(const GameParams &game_params, const Settings &cmd_args, - Server *server) +static bool migrate_map_database(const GameParams &game_params, const Settings &cmd_args) { + std::string migrate_to = cmd_args.get("migrate"); Settings world_mt; - bool success = world_mt.readConfigFile((game_params.world_path - + DIR_DELIM + "world.mt").c_str()); - if (!success) { - errorstream << "Cannot read world.mt" << std::endl; + std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt"; + if (!world_mt.readConfigFile(world_mt_path.c_str())) { + errorstream << "Cannot read world.mt!" << std::endl; return false; } if (!world_mt.exists("backend")) { - errorstream << "Please specify your current backend in world.mt file:" - << std::endl << " backend = {sqlite3|leveldb|redis|dummy}" - << std::endl; + errorstream << "Please specify your current backend in world.mt:" + << std::endl + << " backend = {sqlite3|leveldb|redis|dummy|postgresql}" + << std::endl; return false; } std::string backend = world_mt.get("backend"); - Database *new_db; - std::string migrate_to = cmd_args.get("migrate"); - if (backend == migrate_to) { - errorstream << "Cannot migrate: new backend is same as the old one" - << std::endl; + errorstream << "Cannot migrate: new backend is same" + << " as the old one" << std::endl; return false; } - if (migrate_to == "sqlite3") - new_db = new Database_SQLite3(&(ServerMap&)server->getMap(), - game_params.world_path); -#if USE_LEVELDB - else if (migrate_to == "leveldb") - new_db = new Database_LevelDB(&(ServerMap&)server->getMap(), - game_params.world_path); -#endif -#if USE_REDIS - else if (migrate_to == "redis") - new_db = new Database_Redis(&(ServerMap&)server->getMap(), - game_params.world_path); -#endif - else { - errorstream << "Migration to " << migrate_to << " is not supported" - << std::endl; - return false; - } + MapDatabase *old_db = ServerMap::createDatabase(backend, game_params.world_path, world_mt), + *new_db = ServerMap::createDatabase(migrate_to, game_params.world_path, world_mt); + + u32 count = 0; + time_t last_update_time = 0; + bool &kill = *porting::signal_handler_killstatus(); - std::list blocks; - ServerMap &old_map = ((ServerMap&)server->getMap()); - old_map.listAllLoadableBlocks(blocks); - int count = 0; + std::vector blocks; + old_db->listAllLoadableBlocks(blocks); new_db->beginSave(); - for (std::list::iterator i = blocks.begin(); i != blocks.end(); i++) { - MapBlock *block = old_map.loadBlock(*i); - if (!block) { - errorstream << "Failed to load block " << PP(*i) << ", skipping it."; + for (std::vector::const_iterator it = blocks.begin(); it != blocks.end(); ++it) { + if (kill) return false; + + std::string data; + old_db->loadBlock(*it, &data); + if (!data.empty()) { + new_db->saveBlock(*it, data); } else { - old_map.saveBlock(block, new_db); - MapSector *sector = old_map.getSectorNoGenerate(v2s16(i->X, i->Z)); - sector->deleteBlock(block); + errorstream << "Failed to load block " << PP(*it) << ", skipping it." << std::endl; + } + if (++count % 0xFF == 0 && time(NULL) - last_update_time >= 1) { + std::cerr << " Migrated " << count << " blocks, " + << (100.0 * count / blocks.size()) << "% completed.\r"; + new_db->endSave(); + new_db->beginSave(); + last_update_time = time(NULL); } - ++count; - if (count % 500 == 0) - actionstream << "Migrated " << count << " blocks " - << (100.0 * count / blocks.size()) << "% completed" << std::endl; } + std::cerr << std::endl; new_db->endSave(); + delete old_db; delete new_db; actionstream << "Successfully migrated " << count << " blocks" << std::endl; world_mt.set("backend", migrate_to); - if (!world_mt.updateConfigFile( - (game_params.world_path+ DIR_DELIM + "world.mt").c_str())) + if (!world_mt.updateConfigFile(world_mt_path.c_str())) errorstream << "Failed to update world.mt!" << std::endl; else actionstream << "world.mt updated" << std::endl; return true; } + +static bool recompress_map_database(const GameParams &game_params, const Settings &cmd_args, const Address &addr) +{ + Settings world_mt; + const std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt"; + + if (!world_mt.readConfigFile(world_mt_path.c_str())) { + errorstream << "Cannot read world.mt at " << world_mt_path << std::endl; + return false; + } + const std::string &backend = world_mt.get("backend"); + Server server(game_params.world_path, game_params.game_spec, false, addr, false); + MapDatabase *db = ServerMap::createDatabase(backend, game_params.world_path, world_mt); + + u32 count = 0; + u64 last_update_time = 0; + bool &kill = *porting::signal_handler_killstatus(); + const u8 serialize_as_ver = SER_FMT_VER_HIGHEST_WRITE; + + // This is ok because the server doesn't actually run + std::vector blocks; + db->listAllLoadableBlocks(blocks); + db->beginSave(); + std::istringstream iss(std::ios_base::binary); + std::ostringstream oss(std::ios_base::binary); + for (auto it = blocks.begin(); it != blocks.end(); ++it) { + if (kill) return false; + + std::string data; + db->loadBlock(*it, &data); + if (data.empty()) { + errorstream << "Failed to load block " << PP(*it) << std::endl; + return false; + } + + iss.str(data); + iss.clear(); + + MapBlock mb(nullptr, v3s16(0,0,0), &server); + u8 ver = readU8(iss); + mb.deSerialize(iss, ver, true); + + oss.str(""); + oss.clear(); + writeU8(oss, serialize_as_ver); + mb.serialize(oss, serialize_as_ver, true, -1); + + db->saveBlock(*it, oss.str()); + + count++; + if (count % 0xFF == 0 && porting::getTimeS() - last_update_time >= 1) { + std::cerr << " Recompressed " << count << " blocks, " + << (100.0f * count / blocks.size()) << "% completed.\r"; + db->endSave(); + db->beginSave(); + last_update_time = porting::getTimeS(); + } + } + std::cerr << std::endl; + db->endSave(); + + actionstream << "Done, " << count << " blocks were recompressed." << std::endl; + return true; +}