#include #include #include #include #include #include #include #include #include #include #include #include #include "CommandHandler.h" #include "src/server/QueryServer.h" #ifdef HAVE_JEMALLOC #include #endif using namespace std; using namespace std::chrono; using namespace ts; using namespace ts::server; extern ts::server::InstanceHandler* serverInstance; //Keep this log message displayed #define logError(...) logErrorFmt(true, 0, ##__VA_ARGS__) #define logMessage(...) logMessageFmt(true, 0, ##__VA_ARGS__) #define _STRINGIFY(x) #x #define STRINGIFY(x) _STRINGIFY(x) namespace terminal { namespace chandler { void handleCommand(std::string str){ TerminalCommand cmd{}; size_t index = 0; do { size_t next = str.find(' ', index); auto elm = str.substr(index, next - index); debugMessage("Having message part: " + elm + " - " + to_string(index)); if(index == 0){ cmd.command = elm; std::transform(elm.begin(), elm.end(), elm.begin(), ::tolower); cmd.lcommand = elm; } else { cmd.arguments.emplace_back("", elm); std::transform(elm.begin(), elm.end(), elm.begin(), ::tolower); cmd.larguments.push_back(elm); } index = next + 1; //if no next than next = ~0 and if we add 1 then next is 0 } while(index != 0); if(cmd.lcommand == "help") handleCommandHelp(cmd); else if(cmd.lcommand == "end" || cmd.lcommand == "shutdown") handleCommandEnd(cmd); else if(cmd.lcommand == "info") handleCommandInfo(cmd); else if(cmd.lcommand == "chat") handleCommandChat(cmd); else if(cmd.lcommand == "permgrant") handleCommandPermGrant(cmd); else if(cmd.lcommand == "dummycrash" || cmd.lcommand == "dummy_crash") handleCommandDummyCrash(cmd); else if(cmd.lcommand == "dummyfdflood" || cmd.lcommand == "dummy_fdflood") handleCommandDummyFdFlood(cmd); else if(cmd.lcommand == "meminfo") handleCommandMemInfo(cmd); else if(cmd.lcommand == "spoken") handleCommandSpoken(cmd); else if(cmd.lcommand == "passwd") handleCommandPasswd(cmd); else if(cmd.lcommand == "memflush") handleCommandMemFlush(cmd); else if(cmd.lcommand == "statsreset") handleCommandStatsReset(cmd); else if(cmd.lcommand == "reload") handleCommandReload(cmd); else logError("Missing command " + cmd.command + "/" + cmd.line); } bool handleCommandDummyCrash(TerminalCommand& arguments) { if(!arguments.arguments.empty()) { if(arguments.larguments[0] == "raise") { raise(SIGABRT); return true; } else if(arguments.larguments[0] == "assert") { //dummycrash assert assert(false); return true; } else if(arguments.larguments[0] == "exception") { throw std::bad_exception(); } } *(int*)(nullptr) = 0; return true; } bool handleCommandHelp(TerminalCommand& args){ logMessage("§aAvariable commands:"); logMessage(" §7- §eend §7| §eshutdown"); logMessage(" §7- §ereload config"); logMessage(" §7- §echat"); logMessage(" §7- §einfo"); logMessage(" §7- §epermgrant"); logMessage(" §7- §epasswd"); logMessage(" §7- §4dummy_crash"); logMessage(" §7- §4memflush"); logMessage(" §7- §4meminfo"); return true; } bool handleCommandEnd(TerminalCommand& arguments){ if(arguments.arguments.size() < 1) { logMessage("Invalid argument count!"); logMessage("Usage: shutdown [h|m|s]:...> "); logMessage("Example: shutdown info | Displays info about the current scheduled shutdown"); logMessage("Example: shutdown cancel | Cancels the currently scheduled shutdown"); logMessage("Example: shutdown now Server shutdown | The server instance will shutdown instantly"); logMessage("Example: shutdown 1h:30m Server shutdown | The server instance will shutdown in 1h and 30 min"); logMessage("Example: shutdown 1h:1m:1s Server shutdown | The server instance will shutdown in 1h and 1 min and 1 second"); return false; } nanoseconds period{}; if(arguments.larguments[0] == "info") { auto task = ts::server::scheduledShutdown(); if(!task) { logMessage("It isn't a shutdown scheduled!"); } else { auto time = system_clock::to_time_t(task->time_point); logMessage("You scheduled a shutdown task at " + string(ctime(&time))); } return true; } else if(arguments.larguments[0] == "cancel") { auto task = ts::server::scheduledShutdown(); if(!task) { logMessage("The isnt a shutdown scheduled!"); } else { ts::server::cancelShutdown(true); logMessage("Shutdown task canceled!"); } return true; } else if(arguments.larguments[0] != "now") { string error; period = period::parse(arguments.larguments[0], error); if(!error.empty()) { logError("Invalid period: " + error); return false; } } std::string reason = ts::config::messages::applicationStopped; if(arguments.arguments.size() > 1) { reason = ""; for(auto it = arguments.arguments.begin() + 1; it != arguments.arguments.end(); it++) reason += it->string() + (it + 1 != arguments.arguments.end() ? " " : ""); } if(period.count() == 0) { logMessage("Stopping instance"); ts::server::shutdownInstance(reason); } else { auto time = system_clock::to_time_t(system_clock::now() + period); logMessage("Scheduled shutdown at " + string(ctime(&time)) + ""); ts::server::scheduleShutdown(system_clock::now() + period, reason); } return true; } bool handleCommandInfo(TerminalCommand& cmd){ return false; } bool handleCommandChat(TerminalCommand& cmd){ if(cmd.arguments.size() < 3){ logError("Invalid usage!"); logMessage("§e/chat "); return false; } ServerId sid = cmd.arguments[0]; auto server = sid == 0 ? nullptr : serverInstance->getVoiceServerManager()->findServerById(sid); if(sid != 0 && !server) { logError("Could not resolve target server."); return false; } ts::ChatMessageMode mode = cmd.arguments[1]; if(sid == 0 && mode != ChatMessageMode::TEXTMODE_SERVER){ logError("Invalid mode/serverId"); return false; } debugMessage("Chat message mode " + to_string(mode)); std::string message; int index = 3; while(index < cmd.arguments.size()){ message += " " + cmd.arguments[index++].as(); } if(message.empty()){ logError("Invalid message!"); return false; } message = message.substr(1); switch (mode){ case ChatMessageMode::TEXTMODE_SERVER: if(server){ server->broadcastMessage(server->getServerRoot(), message); } else { for(auto srv : serverInstance->getVoiceServerManager()->serverInstances()) if(srv->running()) srv->broadcastMessage(srv->getServerRoot(), message); } break; case ChatMessageMode::TEXTMODE_CHANNEL: { auto channel = server->getChannelTree()->findChannel(cmd.arguments[2].as()); if(!channel){ logError("Could not resole target channel!"); return false; } for(const auto &cl : server->getClientsByChannel(channel)) cl->notifyTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, server->getServerRoot(), cl->getClientId(), 0, system_clock::now(), message); } break; case ChatMessageMode::TEXTMODE_PRIVATE: { auto client = server->findClient(cmd.arguments[2].as()); if(!client){ logError("Cloud not find manager from clid"); return false; } client->notifyTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, server->getServerRoot(), client->getClientId(), 0, system_clock::now(), message); } break; default: logError("Invalid chat message mode!"); return false; } logMessage("Chat message successfully send!"); return true; } bool handleCommandPermGrant(TerminalCommand& cmd) { if(cmd.arguments.size() != 4) { logError("Invalid arguments!"); logMessage("Arguments: "); return true; } if(cmd.larguments[0].find_first_not_of("0123456789") != std::string::npos) { logError("Invalid server id! (Given number isn't numeric!)"); return false; } if(cmd.larguments[1].find_first_not_of("0123456789") != std::string::npos) { logError("Invalid group id! (Given number isn't numeric!)"); return false; } if(cmd.larguments[3].find_first_not_of("-0123456789") != std::string::npos) { logError("Invalid grant number! (Given number isn't numeric!)"); return false; } permission::PermissionValue grant; ServerId serverId; GroupId groupId; try { serverId = cmd.arguments[0]; groupId = cmd.arguments[1]; grant = cmd.arguments[3]; } catch(const std::exception& ex){ logError("Could not parse given numbers"); return false; } auto server = serverInstance->getVoiceServerManager()->findServerById(serverId); if(!server) { logError("Could not resolve server!"); return false; } auto group = server->getGroupManager()->findGroup(groupId); if(!group) { logError("Could not resolve server group!"); return false; } auto perm = permission::resolvePermissionData(cmd.larguments[2]); if(perm->type == permission::unknown) { logError("Could not resolve permission!"); return false; } group->permissions()->set_permission(perm->type, {0, grant}, permission::v2::do_nothing, permission::v2::set_value); logMessage("§aSuccessfully updated grant permissions."); return true; } //meminfo basic //memflush buffer //memflush alloc bool handleCommandMemFlush(TerminalCommand& cmd) { if(cmd.arguments.size() > 0) { if(cmd.larguments[0] == "db") { if(serverInstance->getSql()->getType() != sql::TYPE_SQLITE) { logMessage("This command just works when you use sqlite!"); return false; } logMessage("Memory used by SQLite:"); logMessage(" Currently used: {0:>6}kb ({0:>9} bytes)", sqlite3_memory_used() / 1024); logMessage(" Max used: {0:>6}kb ({0:>9} bytes)", sqlite3_memory_highwater(true) / 1024); logMessage(" Freed: {0:>6}kb ({0:>9} bytes)", sqlite3_db_release_memory(((sql::sqlite::SqliteManager*) serverInstance->getSql())->getDatabase()) / 1024); logMessage(" Used after free: {0:>6}kb ({0:>9} bytes)", sqlite3_memory_used() / 1024); sqlite3_memory_highwater(true); //Reset the watermark return true; } else if(cmd.larguments[0] == "buffer") { auto info = buffer::cleanup_buffers(buffer::cleanmode::CHUNKS_BLOCKS); logMessage("Cleaned up {} bytes ({} bytes internal)", info.bytes_freed_internal + info.bytes_freed_buffer,info.bytes_freed_internal); return true; } else if(cmd.larguments[0] == "alloc") { #ifdef HAVE_JEMALLOC size_t old_retained, old_active, old_allocated, new_retained, new_active, new_allocated, size_size_t; mallctl("stats.retained", &old_retained, &(size_size_t = sizeof(size_t)), nullptr, 0); mallctl("stats.allocated", &old_allocated, &(size_size_t = sizeof(size_t)), nullptr, 0); mallctl("stats.active", &old_active, &(size_size_t = sizeof(size_t)), nullptr, 0); auto begin = system_clock::now(); mallctl("arena." STRINGIFY(MALLCTL_ARENAS_ALL) ".decay", nullptr, nullptr, nullptr, 0); mallctl("arena." STRINGIFY(MALLCTL_ARENAS_ALL) ".purge", nullptr, nullptr, nullptr, 0); auto end = system_clock::now(); { /* refresh everything */ uint64_t epoch = static_cast(system_clock::now().time_since_epoch().count()); mallctl("epoch", nullptr, nullptr, &epoch, sizeof(int16_t)); } mallctl("stats.retained", &new_retained, &(size_size_t = sizeof(size_t)), nullptr, 0); mallctl("stats.allocated", &new_allocated, &(size_size_t = sizeof(size_t)), nullptr, 0); mallctl("stats.active", &new_active, &(size_size_t = sizeof(size_t)), nullptr, 0); logMessage("Cleaned up allocated internals successfully within {}us", duration_cast(end - begin).count()); logMessage(" Allocated: {0:>9} => {0:>9} bytes", old_allocated, new_allocated); logMessage(" Retained : {0:>9} => {0:>9} bytes", old_retained, new_retained); logMessage(" Active : {0:>9} => {0:>9} bytes", old_active, new_active); #else logError("Jemalloc extension has not been compiled!"); #endif return true; } } logMessage("Invalid argument count. Possible: [db|buffer|alloc]"); return true; } void process_mem_usage(double& vm_usage, double& resident_set) { vm_usage = 0.0; resident_set = 0.0; // the two fields we want unsigned long vsize; long rss; { std::string ignore; std::ifstream ifs("/proc/self/stat", std::ios_base::in); ifs >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore >> vsize >> rss; } long page_size_kb = sysconf(_SC_PAGE_SIZE) / 1024; // in case x86-64 is configured to use 2MB pages vm_usage = vsize / 1024.0; resident_set = rss * page_size_kb; } bool handleCommandMemInfo(TerminalCommand& cmd){ bool flag_base = false, flag_malloc = false, flag_track = false, flag_buffer = false; if(cmd.arguments.size() > 0) { if(cmd.larguments[0] == "basic") flag_base = true; else if(cmd.larguments[0] == "malloc") flag_malloc = true; else if(cmd.larguments[0] == "track") flag_track = true; else if(cmd.larguments[0] == "buffers") flag_buffer = true; } else { flag_base = flag_malloc = flag_track = flag_buffer = true; } if(flag_base) { double vm, rss; process_mem_usage(vm, rss); logMessage("Used memory: {} (VM: {})", rss, vm); } if(flag_malloc) { stringstream ss; #ifdef HAVE_JEMALLOC malloc_stats_print([](void* data, const char* buffer) { auto _ss = (stringstream*) data; *_ss << buffer; }, &ss, nullptr); #else ss << "Jemalloc is not present!"; #endif logMessage(ss.str()); } if(flag_track) memtrack::statistics(); if(flag_buffer) { auto info = buffer::buffer_memory(); logMessage("Allocated memory: {}kb", ceil((info.bytes_internal + info.bytes_buffer) / 1024)); logMessage(" Internal: {}kb", ceil((info.bytes_internal) / 1024)); logMessage(" Buffers : {}kb", ceil((info.bytes_buffer) / 1024)); logMessage(" Buffers Used: {}kb", ceil((info.bytes_buffer_used) / 1024)); } return true; } bool handleCommandSpoken(TerminalCommand& cmd) { //TODO print spoken statistics return false; } bool handleCommandPasswd(TerminalCommand& cmd) { if(cmd.arguments.size() != 2) { logError("Invalid usage: passwd "); return false; } if(cmd.arguments[0].string() != cmd.arguments[1].string()) { logError("Passwords does not match!"); return false; } auto serveradmin = serverInstance->getQueryServer()->find_query_account_by_name("serveradmin"); if(!serveradmin) { auto password = ""; logErrorFmt(true, 0, "Creating a new serveradmin query login!"); if(!(serveradmin = serverInstance->getQueryServer()->create_query_account("serveradmin", 0, "serveradmin", password))) { logError("Could not create serveradmin account!"); return false; } } serverInstance->getQueryServer()->change_query_password(serveradmin, cmd.arguments[0]); logMessage("Server admin successfully changed!"); return false; } extern bool handleCommandStatsReset(TerminalCommand& cmd) { serverInstance->properties()[property::SERVERINSTANCE_MONTHLY_TIMESTAMP] = 0; logMessage("Monthly statistics will be reset"); return true; } deque fd_leaks; bool handleCommandDummyFdFlood(TerminalCommand& cmd) { size_t value; if(cmd.arguments.size() < 1) { value = 1024; rlimit limit{1024, 10000}; setrlimit(7, &limit); } else if(cmd.larguments[0] == "clear") { logMessage("Clearup leaks"); for(auto& fd : fd_leaks) close(fd); fd_leaks.clear(); return true; } else { value = cmd.arguments[0].as(); } logMessage("Leaking {} file descriptors", value); size_t index = 0; while(index < value) { auto fd = dup(1); if(fd < 0) logMessage("Failed to create a file descriptor {} | {}", errno, strerror(errno)); else fd_leaks.push_back(fd); index++; } return true; } bool handleCommandReload(TerminalCommand& cmd) { if(cmd.larguments.size() < 1 || cmd.larguments[0] != "config") { logMessage("Invalid target. Available: config"); return true; } vector error; if(!serverInstance->reloadConfig(error, true)) { logError("Failed to reload instance ({}):", error.size()); for(auto& msg : error) logError(" - {}", msg); } else if(!error.empty()) { logMessage("Reloaded successfully. Messages:"); for(auto& msg : error) logMessage(" - {}", msg); } else { logMessage("Reloaded successfully."); } return true; } } }