Adding terminal pipes

This commit is contained in:
WolverinDEV 2020-07-31 23:18:01 +02:00
parent 7aa37e40b9
commit 8284748381
10 changed files with 792 additions and 512 deletions

@ -1 +1 @@
Subproject commit ef2d6842d547b8f84ebbe4fe9fa6f132bcd84d35 Subproject commit bc242db0517250877805f09700637973ad6c1c2a

View File

@ -155,6 +155,8 @@ set(SERVER_SOURCE_FILES
src/client/voice/PingHandler.cpp src/client/voice/PingHandler.cpp
src/client/voice/CryptSetupHandler.cpp src/client/voice/CryptSetupHandler.cpp
src/terminal/PipedTerminal.cpp
) )
if (COMPILE_WEB_CLIENT) if (COMPILE_WEB_CLIENT)
add_definitions(-DCOMPILE_WEB_CLIENT) add_definitions(-DCOMPILE_WEB_CLIENT)

View File

@ -45,6 +45,7 @@ extern void testTomMath();
#endif #endif
#include <codecvt> #include <codecvt>
#include <src/terminal/PipedTerminal.h>
#include "src/client/music/internal_provider/channel_replay/ChannelProvider.h" #include "src/client/music/internal_provider/channel_replay/ChannelProvider.h"
class CLIParser { class CLIParser {
@ -126,7 +127,7 @@ int main(int argc, char** argv) {
if(!arguments.cmdOptionExists("--no-terminal")) { if(!arguments.cmdOptionExists("--no-terminal")) {
terminal::install(); 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")) { 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"); if(terminal::instance()) terminal::instance()->setPrompt("§7> §f");
while(mainThreadActive) { while(mainThreadActive) {
usleep(5 * 1000); usleep(5 * 1000);
@ -412,11 +414,23 @@ int main(int argc, char** argv) {
if(terminal::instance()) { if(terminal::instance()) {
if(terminal::instance()->linesAvailable() > 0){ if(terminal::instance()->linesAvailable() > 0){
while(!(line = terminal::instance()->readLine("§7> §f")).empty()) 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: stopApp:
logMessageFmt(true, LOG_GENERAL, "Stopping application"); logMessageFmt(true, LOG_GENERAL, "Stopping application");

View File

@ -1,6 +1,7 @@
#include <log/LogUtils.h> #include <log/LogUtils.h>
#include <StringVariable.h> #include <StringVariable.h>
#include <ThreadPool/ThreadHelper.h> #include <ThreadPool/ThreadHelper.h>
#include <src/terminal/PipedTerminal.h>
#include "ShutdownHelper.h" #include "ShutdownHelper.h"
#include "InstanceHandler.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, "Could not shutdown server within 30 seconds! (Hangup!)");
logCriticalFmt(true, 0, "Killing server!"); logCriticalFmt(true, 0, "Killing server!");
terminal::finalize_pipe();
auto force_kill = std::thread([]{ auto force_kill = std::thread([]{
threads::self::sleep_for(chrono::seconds(5)); threads::self::sleep_for(chrono::seconds(5));
logCriticalFmt(true, 0, "Failed to exit normally!"); logCriticalFmt(true, 0, "Failed to exit normally!");

View File

@ -8,6 +8,7 @@
#include <csignal> #include <csignal>
#include <log/LogUtils.h> #include <log/LogUtils.h>
#include <experimental/filesystem> #include <experimental/filesystem>
#include <src/terminal/PipedTerminal.h>
using namespace std; using namespace std;
namespace fs = std::experimental::filesystem; 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, "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, "Any reports of crashes are useless if you not provide the above generated crashlog!");
logCritical(LOG_GENERAL, "Stopping server"); logCritical(LOG_GENERAL, "Stopping server");
terminal::finalize_pipe();
ts::server::shutdownInstance(ts::config::messages::applicationCrashed); ts::server::shutdownInstance(ts::config::messages::applicationCrashed);
while(!mainThreadDone) threads::self::sleep_for(chrono::seconds(1)); while(!mainThreadDone) threads::self::sleep_for(chrono::seconds(1));
return succeeded; return succeeded;

View File

@ -21,7 +21,7 @@ void PingHandler::received_pong(uint16_t ping_id) {
if(this->last_ping_id != ping_id) return; if(this->last_ping_id != ping_id) return;
auto now = std::chrono::system_clock::now(); auto now = std::chrono::system_clock::now();
this->current_ping_ = std::chrono::floor<std::chrono::milliseconds>(this->last_request_ - now); this->current_ping_ = std::chrono::floor<std::chrono::milliseconds>(now - this->last_request_);
this->last_response_ = now; this->last_response_ = now;
this->last_command_acknowledge_ = now; /* That's here for purpose!*/ this->last_command_acknowledge_ = now; /* That's here for purpose!*/

View File

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

View File

@ -3,38 +3,41 @@
#include <query/Command.h> #include <query/Command.h>
#include <Error.h> #include <Error.h>
namespace terminal { namespace terminal::chandler {
namespace chandler { struct TerminalCommand {
struct TerminalCommand { std::string line;
std::string line;
std::string command; std::string command;
std::string lcommand; std::string lcommand;
std::vector<variable> arguments; std::vector<variable> arguments;
std::vector<std::string> larguments; std::vector<std::string> larguments;
}; };
extern void handleCommand(std::string); struct CommandHandle {
std::string command{};
std::deque<std::string> response{};
};
extern bool handleCommandDummyCrash(TerminalCommand&); extern bool handleCommand(CommandHandle& /* command */);
extern bool handleCommandDummyFdFlood(TerminalCommand&);
extern bool handleCommandHelp(TerminalCommand&); extern bool handleCommandDummyCrash(CommandHandle& /* handle */, TerminalCommand&);
extern bool handleCommandEnd(TerminalCommand&); extern bool handleCommandDummyFdFlood(CommandHandle& /* handle */, TerminalCommand&);
extern bool handleCommandInfo(TerminalCommand&);
extern bool handleCommandChat(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 handleCommandPermGrant(CommandHandle& /* handle */, TerminalCommand&);
extern bool handleCommandMemInfo(TerminalCommand&);
extern bool handleCommandSpoken(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&);
} }

View File

@ -0,0 +1,243 @@
//
// Created by WolverinDEV on 31/07/2020.
//
#include "PipedTerminal.h"
#include "CommandHandler.h"
#include <event.h>
#include <sys/stat.h>
#include <thread>
#include <log/LogUtils.h>
#include <StringVariable.h>
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;
}
}
}
}

View File

@ -0,0 +1,8 @@
#pragma once
#include <string>
namespace terminal {
extern void initialize_pipe(const std::string& /* path */);
extern void finalize_pipe();
}