diff --git a/git-teaspeak b/git-teaspeak index ef2d684..bc242db 160000 --- a/git-teaspeak +++ b/git-teaspeak @@ -1 +1 @@ -Subproject commit ef2d6842d547b8f84ebbe4fe9fa6f132bcd84d35 +Subproject commit bc242db0517250877805f09700637973ad6c1c2a diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index f3c7436..5e42aee 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -155,6 +155,8 @@ set(SERVER_SOURCE_FILES src/client/voice/PingHandler.cpp src/client/voice/CryptSetupHandler.cpp + src/terminal/PipedTerminal.cpp + ) if (COMPILE_WEB_CLIENT) add_definitions(-DCOMPILE_WEB_CLIENT) diff --git a/server/main.cpp b/server/main.cpp index 3fd76d5..81d0c9d 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -45,6 +45,7 @@ extern void testTomMath(); #endif #include +#include #include "src/client/music/internal_provider/channel_replay/ChannelProvider.h" class CLIParser { @@ -126,7 +127,7 @@ int main(int argc, char** argv) { if(!arguments.cmdOptionExists("--no-terminal")) { terminal::install(); - if(!terminal::active()){ cerr << "could not setup terminal!" << endl; return -1; } + if(!terminal::active()) { cerr << "could not setup terminal!" << endl; return -1; } } if(arguments.cmdOptionExists("--help") || arguments.cmdOptionExists("-h")) { @@ -405,6 +406,7 @@ int main(int argc, char** argv) { } } + terminal::initialize_pipe(arguments.get_option("--pipe-path")); if(terminal::instance()) terminal::instance()->setPrompt("§7> §f"); while(mainThreadActive) { usleep(5 * 1000); @@ -412,11 +414,23 @@ int main(int argc, char** argv) { if(terminal::instance()) { if(terminal::instance()->linesAvailable() > 0){ while(!(line = terminal::instance()->readLine("§7> §f")).empty()) - threads::Thread(THREAD_DETACHED, [line](){ terminal::chandler::handleCommand(line); }); + threads::Thread(THREAD_DETACHED, [line]{ + terminal::chandler::CommandHandle handle{}; + handle.command = line; + + if(!terminal::chandler::handleCommand(handle)) { + for(const auto& response : handle.response) + logErrorFmt(true, LOG_GENERAL, "{}", response); + } else { + for(const auto& response : handle.response) + logMessageFmt(true, LOG_GENERAL, "{}", response); + } + }); } } } + terminal::finalize_pipe(); stopApp: logMessageFmt(true, LOG_GENERAL, "Stopping application"); diff --git a/server/src/ShutdownHelper.cpp b/server/src/ShutdownHelper.cpp index 85b13ac..adcd9d4 100644 --- a/server/src/ShutdownHelper.cpp +++ b/server/src/ShutdownHelper.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "ShutdownHelper.h" #include "InstanceHandler.h" @@ -21,6 +22,7 @@ void ts::server::shutdownInstance(const std::string& message) { logCriticalFmt(true, 0, "Could not shutdown server within 30 seconds! (Hangup!)"); logCriticalFmt(true, 0, "Killing server!"); + terminal::finalize_pipe(); auto force_kill = std::thread([]{ threads::self::sleep_for(chrono::seconds(5)); logCriticalFmt(true, 0, "Failed to exit normally!"); diff --git a/server/src/SignalHandler.cpp b/server/src/SignalHandler.cpp index f1f48b0..4fb6a3f 100644 --- a/server/src/SignalHandler.cpp +++ b/server/src/SignalHandler.cpp @@ -8,6 +8,7 @@ #include #include #include +#include using namespace std; namespace fs = std::experimental::filesystem; @@ -44,6 +45,8 @@ static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, logCritical(LOG_GENERAL, "Official issue and bug tracker url: https://github.com/TeaSpeak/TeaSpeak/issues"); logCritical(LOG_GENERAL, "Any reports of crashes are useless if you not provide the above generated crashlog!"); logCritical(LOG_GENERAL, "Stopping server"); + + terminal::finalize_pipe(); ts::server::shutdownInstance(ts::config::messages::applicationCrashed); while(!mainThreadDone) threads::self::sleep_for(chrono::seconds(1)); return succeeded; diff --git a/server/src/client/voice/PingHandler.cpp b/server/src/client/voice/PingHandler.cpp index fc60bcd..201cc70 100644 --- a/server/src/client/voice/PingHandler.cpp +++ b/server/src/client/voice/PingHandler.cpp @@ -21,7 +21,7 @@ void PingHandler::received_pong(uint16_t ping_id) { if(this->last_ping_id != ping_id) return; auto now = std::chrono::system_clock::now(); - this->current_ping_ = std::chrono::floor(this->last_request_ - now); + this->current_ping_ = std::chrono::floor(now - this->last_request_); this->last_response_ = now; this->last_command_acknowledge_ = now; /* That's here for purpose!*/ diff --git a/server/src/terminal/CommandHandler.cpp b/server/src/terminal/CommandHandler.cpp index ac179ff..3dcb05d 100644 --- a/server/src/terminal/CommandHandler.cpp +++ b/server/src/terminal/CommandHandler.cpp @@ -32,496 +32,501 @@ extern ts::server::InstanceHandler* serverInstance; #define _STRINGIFY(x) #x #define STRINGIFY(x) _STRINGIFY(x) -namespace terminal { - namespace chandler { - void handleCommand(std::string str){ - TerminalCommand cmd{}; +namespace terminal::chandler { + bool handleCommand(CommandHandle& command){ + TerminalCommand cmd{}; - size_t index = 0; - do { - size_t next = str.find(' ', index); - auto elm = str.substr(index, next - index); + size_t index = 0; + do { + size_t next = command.command.find(' ', index); + auto elm = command.command.substr(index, next - 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); + if(index == 0){ + cmd.command = elm; + std::transform(elm.begin(), elm.end(), elm.begin(), ::tolower); + cmd.lcommand = elm; } 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); + cmd.arguments.emplace_back("", elm); + std::transform(elm.begin(), elm.end(), elm.begin(), ::tolower); + cmd.larguments.push_back(elm); } - return true; - } + index = next + 1; //if no next than next = ~0 and if we add 1 then next is 0 + } while(index != 0); + cmd.line = command.command; - bool handleCommandInfo(TerminalCommand& cmd){ + if(cmd.lcommand == "help") + return handleCommandHelp(command, cmd); + else if(cmd.lcommand == "end" || cmd.lcommand == "shutdown") + return handleCommandEnd(command, cmd); + else if(cmd.lcommand == "info") + return handleCommandInfo(command, cmd); + else if(cmd.lcommand == "chat") + return handleCommandChat(command, cmd); + else if(cmd.lcommand == "permgrant") + return handleCommandPermGrant(command, cmd); + else if(cmd.lcommand == "dummycrash" || cmd.lcommand == "dummy_crash") + return handleCommandDummyCrash(command, cmd); + else if(cmd.lcommand == "dummyfdflood" || cmd.lcommand == "dummy_fdflood") + return handleCommandDummyFdFlood(command, cmd); + else if(cmd.lcommand == "meminfo") + return handleCommandMemInfo(command, cmd); + else if(cmd.lcommand == "spoken") + return handleCommandSpoken(command, cmd); + else if(cmd.lcommand == "passwd") + return handleCommandPasswd(command, cmd); + else if(cmd.lcommand == "memflush") + return handleCommandMemFlush(command, cmd); + else if(cmd.lcommand == "statsreset") + return handleCommandStatsReset(command, cmd); + else if(cmd.lcommand == "reload") + return handleCommandReload(command, cmd); + else { + logWarning(LOG_INSTANCE, "Missing terminal command {} ({})", cmd.command, cmd.line); + command.response.emplace_back("unknown command"); 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(LOG_GENERAL,"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: - { - ConnectedLockedClient client{server->find_client_by_id(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; - } - - //meminfo track - 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 rlimit{0, 0}; - getrlimit(RLIMIT_NOFILE, &rlimit); - logMessage("RLimit: {}/{}", rlimit.rlim_cur, rlimit.rlim_max); - //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; - } + } + + bool handleCommandDummyCrash(CommandHandle& /* handle */, 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(CommandHandle& handle, TerminalCommand& args) { + handle.response.emplace_back("Available commands:"); + handle.response.emplace_back(" - end | shutdown"); + handle.response.emplace_back(" - reload config"); + handle.response.emplace_back(" - chat"); + handle.response.emplace_back(" - info"); + handle.response.emplace_back(" - permgrant"); + handle.response.emplace_back(" - passwd"); + handle.response.emplace_back(" - dummy_crash"); + handle.response.emplace_back(" - memflush"); + handle.response.emplace_back(" - meminfo"); + return true; + } + + bool handleCommandEnd(CommandHandle& handle, TerminalCommand& arguments){ + if(arguments.arguments.empty()) { + handle.response.emplace_back("Invalid argument count!"); + handle.response.emplace_back("Usage: shutdown [h|m|s]:...> "); + handle.response.emplace_back("Example: shutdown info | Displays info about the current scheduled shutdown"); + handle.response.emplace_back("Example: shutdown cancel | Cancels the currently scheduled shutdown"); + handle.response.emplace_back("Example: shutdown now Server shutdown | The server instance will shutdown instantly"); + handle.response.emplace_back("Example: shutdown 1h:30m Server shutdown | The server instance will shutdown in 1h and 30 min"); + handle.response.emplace_back("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) { + handle.response.emplace_back("It isn't a shutdown scheduled!"); + } else { + auto time = system_clock::to_time_t(task->time_point); + handle.response.emplace_back("You scheduled a shutdown task at " + string(ctime(&time))); + } + return true; + } else if(arguments.larguments[0] == "cancel") { + auto task = ts::server::scheduledShutdown(); + if(!task) { + handle.response.emplace_back("The isn't a shutdown scheduled!"); + } else { + ts::server::cancelShutdown(true); + handle.response.emplace_back("Shutdown task canceled!"); + } + return true; + } else if(arguments.larguments[0] != "now") { + string error; + period = period::parse(arguments.larguments[0], error); + if(!error.empty()) { + handle.response.emplace_back("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) { + handle.response.emplace_back("Stopping instance"); + ts::server::shutdownInstance(reason); + } else { + auto time = system_clock::to_time_t(system_clock::now() + period); + handle.response.emplace_back("Scheduled shutdown at " + string(ctime(&time)) + ""); + ts::server::scheduleShutdown(system_clock::now() + period, reason); + } + return true; + } + + bool handleCommandInfo(CommandHandle& /* handle */, TerminalCommand& cmd){ + return false; + } + + bool handleCommandChat(CommandHandle& handle, TerminalCommand& cmd){ + if(cmd.arguments.size() < 3){ + handle.response.emplace_back("Invalid usage!"); + handle.response.emplace_back("§e/chat "); + return false; + } + + ServerId sid = cmd.arguments[0]; + auto server = sid == 0 ? nullptr : serverInstance->getVoiceServerManager()->findServerById(sid); + if(sid != 0 && !server) { + handle.response.emplace_back("Could not resolve target server."); + return false; + } + + ts::ChatMessageMode mode = cmd.arguments[1]; + if(sid == 0 && mode != ChatMessageMode::TEXTMODE_SERVER){ + handle.response.emplace_back("Invalid mode/serverId"); + return false; + } + debugMessage(LOG_GENERAL,"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()){ + handle.response.emplace_back("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){ + handle.response.emplace_back("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: + { + ConnectedLockedClient client{server->find_client_by_id(cmd.arguments[2].as())}; + if(!client){ + handle.response.emplace_back("Cloud not find manager from clid"); + return false; + } + + client->notifyTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, server->getServerRoot(), client->getClientId(), 0, system_clock::now(), message); + } + break; + default: + handle.response.emplace_back("Invalid chat message mode!"); + return false; + } + + handle.response.emplace_back("Chat message successfully send!"); + return true; + } + + bool handleCommandPermGrant(CommandHandle& handle, TerminalCommand& cmd) { + if(cmd.arguments.size() != 4) { + handle.response.emplace_back("Invalid arguments!"); + handle.response.emplace_back("Arguments: "); + return false; + } + + if(cmd.larguments[0].find_first_not_of("0123456789") != std::string::npos) { + handle.response.emplace_back("Invalid server id! (Given number isn't numeric!)"); + return false; + } + if(cmd.larguments[1].find_first_not_of("0123456789") != std::string::npos) { + handle.response.emplace_back("Invalid group id! (Given number isn't numeric!)"); + return false; + } + if(cmd.larguments[3].find_first_not_of("-0123456789") != std::string::npos) { + handle.response.emplace_back("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){ + handle.response.emplace_back("Could not parse given numbers"); + return false; + } + + auto server = serverInstance->getVoiceServerManager()->findServerById(serverId); + if(!server) { + handle.response.emplace_back("Could not resolve server!"); + return false; + } + + auto group = server->getGroupManager()->findGroup(groupId); + if(!group) { + handle.response.emplace_back("Could not resolve server group!"); + return false; + } + + auto perm = permission::resolvePermissionData(cmd.larguments[2]); + if(perm->type == permission::unknown) { + handle.response.emplace_back("Could not resolve permission!"); + return false; + } + group->permissions()->set_permission(perm->type, {0, grant}, permission::v2::do_nothing, permission::v2::set_value); + handle.response.emplace_back("§aSuccessfully updated grant permissions."); + return true; + } + + //meminfo basic + //memflush buffer + //memflush alloc + bool handleCommandMemFlush(CommandHandle& handle, TerminalCommand& cmd) { + if(cmd.arguments.size() > 0) { + if(cmd.larguments[0] == "db") { + if(serverInstance->getSql()->getType() != sql::TYPE_SQLITE) { + handle.response.emplace_back("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; + } + + //meminfo track + bool handleCommandMemInfo(CommandHandle& /* handle */, 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(CommandHandle& /* handle */, TerminalCommand& cmd) { + //TODO print spoken statistics + return false; + } + + bool handleCommandPasswd(CommandHandle& handle, TerminalCommand& cmd) { + if(cmd.arguments.size() != 2) { + handle.response.emplace_back("Invalid usage: passwd "); + return false; + } + if(cmd.arguments[0].string() != cmd.arguments[1].string()) { + handle.response.emplace_back("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))) { + handle.response.emplace_back("Could not create serveradmin account!"); + return false; + } + } + + serverInstance->getQueryServer()->change_query_password(serveradmin, cmd.arguments[0]); + handle.response.emplace_back("Server admin successfully changed!"); + return true; + } + + + extern bool handleCommandStatsReset(CommandHandle& handle, TerminalCommand& cmd) { + serverInstance->properties()[property::SERVERINSTANCE_MONTHLY_TIMESTAMP] = 0; + handle.response.emplace_back("Monthly statistics will be reset"); + return true; + } + + deque fd_leaks; + bool handleCommandDummyFdFlood(CommandHandle& /* handle */, TerminalCommand& cmd) { + size_t value; + if(cmd.arguments.size() < 1) { + value = 1024; + + rlimit rlimit{0, 0}; + getrlimit(RLIMIT_NOFILE, &rlimit); + logMessage("RLimit: {}/{}", rlimit.rlim_cur, rlimit.rlim_max); + //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(CommandHandle& handle, TerminalCommand& cmd) { + if(cmd.larguments.empty() || cmd.larguments[0] != "config") { + handle.response.emplace_back("Invalid target. Available:"); + handle.response.emplace_back(" - config"); + return false; + } + + vector error; + if(!serverInstance->reloadConfig(error, true)) { + handle.response.emplace_back("Failed to reload instance ({}):", error.size()); + for(auto& msg : error) + handle.response.emplace_back(" - " + msg); + } else if(!error.empty()) { + handle.response.emplace_back("Reloaded successfully. Messages:"); + for(auto& msg : error) + handle.response.emplace_back(" - " + msg); + } else { + handle.response.emplace_back("Reloaded successfully."); + } + + return true; } } \ No newline at end of file diff --git a/server/src/terminal/CommandHandler.h b/server/src/terminal/CommandHandler.h index efd616b..7ee9f56 100644 --- a/server/src/terminal/CommandHandler.h +++ b/server/src/terminal/CommandHandler.h @@ -3,38 +3,41 @@ #include #include -namespace terminal { - namespace chandler { - struct TerminalCommand { - std::string line; +namespace terminal::chandler { + struct TerminalCommand { + std::string line; - std::string command; - std::string lcommand; + std::string command; + std::string lcommand; - std::vector arguments; - std::vector larguments; - }; + std::vector arguments; + std::vector larguments; + }; - extern void handleCommand(std::string); + struct CommandHandle { + std::string command{}; + std::deque response{}; + }; - extern bool handleCommandDummyCrash(TerminalCommand&); - extern bool handleCommandDummyFdFlood(TerminalCommand&); + extern bool handleCommand(CommandHandle& /* command */); - extern bool handleCommandHelp(TerminalCommand&); - extern bool handleCommandEnd(TerminalCommand&); - extern bool handleCommandInfo(TerminalCommand&); - extern bool handleCommandChat(TerminalCommand&); + extern bool handleCommandDummyCrash(CommandHandle& /* handle */, TerminalCommand&); + extern bool handleCommandDummyFdFlood(CommandHandle& /* handle */, TerminalCommand&); - extern bool handleCommandPermGrant(TerminalCommand&); + extern bool handleCommandHelp(CommandHandle& /* handle */, TerminalCommand&); + extern bool handleCommandEnd(CommandHandle& /* handle */, TerminalCommand&); + extern bool handleCommandInfo(CommandHandle& /* handle */, TerminalCommand&); + extern bool handleCommandChat(CommandHandle& /* handle */, TerminalCommand&); - extern bool handleCommandMemFlush(TerminalCommand&); - extern bool handleCommandMemInfo(TerminalCommand&); - extern bool handleCommandSpoken(TerminalCommand&); + extern bool handleCommandPermGrant(CommandHandle& /* handle */, TerminalCommand&); - extern bool handleCommandPasswd(TerminalCommand&); + extern bool handleCommandMemFlush(CommandHandle& /* handle */, TerminalCommand&); + extern bool handleCommandMemInfo(CommandHandle& /* handle */, TerminalCommand&); + extern bool handleCommandSpoken(CommandHandle& /* handle */, TerminalCommand&); - extern bool handleCommandStatsReset(TerminalCommand&); + extern bool handleCommandPasswd(CommandHandle& /* handle */, TerminalCommand&); - extern bool handleCommandReload(TerminalCommand&); - } + extern bool handleCommandStatsReset(CommandHandle& /* handle */, TerminalCommand&); + + extern bool handleCommandReload(CommandHandle& /* handle */, TerminalCommand&); } \ No newline at end of file diff --git a/server/src/terminal/PipedTerminal.cpp b/server/src/terminal/PipedTerminal.cpp new file mode 100644 index 0000000..7fbafe1 --- /dev/null +++ b/server/src/terminal/PipedTerminal.cpp @@ -0,0 +1,243 @@ +// +// Created by WolverinDEV on 31/07/2020. +// + +#include "PipedTerminal.h" +#include "CommandHandler.h" + +#include +#include +#include +#include +#include + +std::string pipe_path_in{}, pipe_path_out{}; + +int file_descriptor_in{0}, file_descriptor_out{0}; +std::thread event_loop_dispatcher{}; +::event_base* event_base{nullptr}; + +event* event_read{nullptr}; +event* event_write{nullptr}; + +void event_loop_executor(void*); +void event_read_callback(int, short, void*); +void event_write_callback(int, short, void*); + +void terminal::initialize_pipe(const std::string& pipe_path) { + { + std::string path; + if(pipe_path.empty()) { + path = "/tmp/teaspeak_${pid}_${direction}.term"; + } else { + path = pipe_path; + } + + pipe_path_in = strvar::transform(path, strvar::StringValue{"pid", std::to_string(getpid())}, strvar::StringValue{"direction", "in"}); + pipe_path_out = strvar::transform(path, strvar::StringValue{"pid", std::to_string(getpid())}, strvar::StringValue{"direction", "out"}); + } + + auto result = mkfifo(pipe_path_in.c_str(), 0666); + if(result != 0){ + logWarning(LOG_INSTANCE, "Failed to create incoming terminal pipe ({}/{})", errno, strerror(errno)); + finalize_pipe(); + return; + } + + file_descriptor_in = open(pipe_path_in.c_str(), (unsigned) O_NONBLOCK | (unsigned) O_RDONLY); + if(file_descriptor_in <= 0) { + logWarning(LOG_INSTANCE, "Failed to open incoming terminal pipe ({}/{})", errno, strerror(errno)); + finalize_pipe(); + return; + } + + result = mkfifo(pipe_path_out.c_str(), 0666); + if(result != 0){ + logWarning(LOG_INSTANCE, "Failed to create outgoing terminal pipe ({}/{})", errno, strerror(errno)); + finalize_pipe(); + return; + } + + /* we can't do a write only open, else along with the O_NONBLOCK we'll get No such device or address */ + file_descriptor_out = open(pipe_path_out.c_str(), (unsigned) O_NONBLOCK | (unsigned) O_RDWR); + if(file_descriptor_out <= 0) { + logWarning(LOG_INSTANCE, "Failed to open outgoing terminal pipe ({}/{})", errno, strerror(errno)); + finalize_pipe(); + return; + } + + event_base = event_base_new(); + if(!event_base) { + logWarning(LOG_INSTANCE, "Failed to open terminal pipe ({}/{})", errno, strerror(errno)); + finalize_pipe(); + return; + } + + event_loop_dispatcher = std::thread{event_loop_executor, event_base}; + event_read = event_new(event_base, file_descriptor_in, (unsigned) EV_READ | (unsigned) EV_PERSIST, event_read_callback, nullptr); + event_write = event_new(event_base, file_descriptor_out, EV_WRITE, event_write_callback, nullptr); + event_add(event_read, nullptr); + + logMessage(LOG_INSTANCE, "Terminal pipe started (Incoming: {}, Outgoing: {}).", pipe_path_in, pipe_path_out); +} + +void terminal::finalize_pipe() { + if(event_base) { + event_base_loopexit(event_base, nullptr); + if(event_loop_dispatcher.joinable() && std::this_thread::get_id() != event_loop_dispatcher.get_id()) + event_loop_dispatcher.join(); + + /* events get deleted when the base gets freed */ + event_read = nullptr; + event_write = nullptr; + } + + if(file_descriptor_in > 0) { + close(file_descriptor_in); + file_descriptor_in = 0; + + remove(pipe_path_in.c_str()); + } + + if(file_descriptor_out > 0) { + close(file_descriptor_out); + file_descriptor_out = 0; + + remove(pipe_path_out.c_str()); + } +} + +void event_loop_executor(void* ptr_event_base) { + auto base = (struct event_base*) ptr_event_base; + + while(!event_base_got_exit(base)) + event_base_loop(base, EVLOOP_NO_EXIT_ON_EMPTY); + + event_base_free(base); +} + +/* no buffer lock needed since we're only accessing them via one thread */ +constexpr static auto kReadBufferSize{8 * 1024}; +char read_buffer[kReadBufferSize]; +size_t read_buffer_index{0}; + +constexpr static auto kWriteBufferSize{64 * 1024}; +char write_buffer[kWriteBufferSize]; +size_t write_buffer_index{0}; + +void append_write_buffer(std::string_view message) { + if(message.length() > kWriteBufferSize) { + logWarning(LOG_INSTANCE, "Trying to write a too long message to the terminal pipe. Truncating {} bytes from the beginning.", message.length() - kWriteBufferSize); + message = message.substr(message.length() - kWriteBufferSize); + } + + if(write_buffer_index + message.length() > kWriteBufferSize) { + logWarning(LOG_INSTANCE, "Encountering a write buffer overflow. Truncating bytes form the beginning."); + + auto offset = message.length() + write_buffer_index - kWriteBufferSize; + if(write_buffer_index > offset) { + memcpy(write_buffer, write_buffer + offset, write_buffer_index - offset); + write_buffer_index = write_buffer_index - offset; + } else { + write_buffer_index = 0; + } + } + + memcpy(write_buffer + write_buffer_index, message.data(), message.length()); + write_buffer_index += message.length(); + + event_add(event_write, nullptr); +} + +bool process_next_command() { + auto new_line_index = (char*) memchr(read_buffer, '\n', read_buffer_index); + if(!new_line_index) { + return false; + } + + std::string command{read_buffer, (size_t) (new_line_index - read_buffer)}; + if(new_line_index == read_buffer + read_buffer_index) { + read_buffer_index = 0; + } else { + auto length_left = read_buffer_index - (new_line_index - read_buffer + 1); + memcpy(read_buffer, new_line_index + 1, length_left); + read_buffer_index = length_left; + } + + if(command.find_first_not_of(' ') == std::string::npos) { + process_next_command(); + return false; + } + + logMessage(LOG_INSTANCE, "Dispatching command received via pipe \"{}\".", command); + + terminal::chandler::CommandHandle handle{}; + handle.command = command; + if(!terminal::chandler::handleCommand(handle)) { + append_write_buffer("error\n"); + } else { + append_write_buffer("ok\n"); + } + + for(const auto& line : handle.response) + append_write_buffer(line + "\n"); + + append_write_buffer("\r\n"); + append_write_buffer("\r\n"); + return true; +} + +void event_read_callback(int fd, short events, void*) { + if((unsigned) events & (unsigned) EV_READ) { + while(true) { + if(kReadBufferSize == read_buffer_index) { + logWarning(LOG_INSTANCE, "Terminal pipe line buffer overflow. Flushing buffer."); + read_buffer_index = 0; + } + + auto read = ::read(fd, read_buffer + read_buffer_index, kReadBufferSize - read_buffer_index); + if(read < 0) { + if(errno == EAGAIN) { + event_add(event_read, nullptr); + return; + } + + logError(LOG_INSTANCE, "Terminal pipe encountered a read error: {}/{}. Closing terminal.", errno, strerror(errno)); + terminal::finalize_pipe(); + return; + } else if(read == 0) { + return; + } + + read_buffer_index += read; + if(process_next_command()) + return; + } + } +} + +void event_write_callback(int fd, short events, void*) { + if((unsigned) events & (unsigned) EV_WRITE) { + while(true) { + auto written = ::write(fd, write_buffer, write_buffer_index); + if(written < 0) { + if(errno == EAGAIN) { + event_add(event_write, nullptr); + return; + } + + logError(LOG_INSTANCE, "Terminal pipe encountered a write error: {}/{}. Closing terminal.", errno, strerror(errno)); + terminal::finalize_pipe(); + return; + } else if(written == 0) { + return; + } else if(written == write_buffer_index) { + write_buffer_index = 0; + return; + } else { + memcpy(write_buffer, write_buffer + written, write_buffer_index - written); + write_buffer_index -= written; + } + } + } +} diff --git a/server/src/terminal/PipedTerminal.h b/server/src/terminal/PipedTerminal.h new file mode 100644 index 0000000..4de4e08 --- /dev/null +++ b/server/src/terminal/PipedTerminal.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +namespace terminal { + extern void initialize_pipe(const std::string& /* path */); + extern void finalize_pipe(); +} \ No newline at end of file