// Matrix Construct // // Copyright (C) Matrix Construct Developers, Authors & Contributors // Copyright (C) 2016-2018 Jason Volk // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice is present in all copies. The // full license for this software is available in the LICENSE file. #include #include #include #include #include "construct.h" using namespace ircd; IRCD_EXCEPTION_HIDENAME(ircd::error, bad_command) const char *const generic_message {R"( *** - To end the console session: type ctrl-d -> EOF *** - To exit cleanly: type exit, die, or ctrl-\ -> SIGQUIT *** - To generate a coredump for developers, type ABORT -> abort() *** )"}; const size_t stack_sz { 8_MiB }; bool console_active; bool console_inwork; ircd::ctx::ctx *console_ctx; ircd::module *console_module; boost::asio::posix::stream_descriptor *console_in; static void check_console_active(); static void console_fini(); static void console_init(); static bool handle_line(const string_view &line); static void execute(const std::vector lines); static void console(); void console_spawn() { check_console_active(); ircd::context { "console", stack_sz, std::bind(&console), ircd::context::DETACH | ircd::context::POST }; } void console_execute(const std::vector &lines) { check_console_active(); ircd::context { "execute", stack_sz, std::bind(&execute, lines), ircd::context::DETACH | ircd::context::POST }; } void console_init() { console_cancel(); console_active = true; console_ctx = &ctx::cur(); console_module = new ircd::module{"console"}; } void console_fini() { console_active = false; console_ctx = nullptr; delete console_module; console_module = nullptr; std::cin.clear(); } const char *const console_message {R"( *** *** The server is still running in the background. A command line is now available below. *** This is a client and your commands will originate from the server itself. ***)"}; void console() try { if(ircd::runlevel != ircd::runlevel::RUN) return; const unwind atexit([] { delete console_in; console_in = nullptr; console_fini(); }); console_init(); console_in = new boost::asio::posix::stream_descriptor { *::ios, dup(STDIN_FILENO) }; boost::asio::streambuf buf{BUFSIZE}; std::istream is{&buf}; std::string line; std::cout << console_message << generic_message; while(1) { std::cout << "\n> " << std::flush; // Suppression scope ends after the command is entered // so the output of the command (if log messages) can be seen. { const log::console_quiet quiet(false); boost::asio::async_read_until(*console_in, buf, '\n', yield_context{to_asio{}}); } std::getline(is, line); std::cin.clear(); if(line.empty()) continue; if(!handle_line(line)) break; } std::cout << std::endl; } catch(const std::exception &e) { std::cout << std::endl; std::cout << "***" << std::endl; std::cout << "*** The console session has ended: " << e.what() << std::endl; std::cout << "***" << std::endl; ircd::log::debug("The console session has ended: %s", e.what()); } const char *const termstop_message {R"( *** *** The server has been paused and will resume when you hit enter. *** This is a client and your commands will originate from the server itself. ***)"}; void console_termstop() try { const unwind atexit([] { console_fini(); }); console_init(); std::cout << termstop_message << generic_message; std::string line; std::cout << "\n> " << std::flush; std::getline(std::cin, line); if(std::cin.eof()) { std::cout << std::endl; std::cin.clear(); return; } handle_line(line); } catch(const std::exception &e) { ircd::log::error("console_termstop(): %s", e.what()); } void execute(const std::vector lines) try { if(ircd::runlevel != ircd::runlevel::RUN) return; const unwind atexit([] { console_fini(); }); console_init(); for(const auto &line : lines) { if(line.empty()) continue; if(!handle_line(line)) break; } } catch(const std::exception &e) { std::cout << std::endl; std::cout << "***\n"; std::cout << "*** The execution aborted: " << e.what() << "\n"; std::cout << "***" << std::endl; std::cout << std::flush; std::cout.clear(); ircd::log::debug("The execution aborted: %s", e.what()); } bool handle_line(const string_view &line) try { // _theirs is copied for recursive reentrance const unwind outwork{[console_inwork_theirs(console_inwork)] { console_inwork = console_inwork_theirs; }}; console_inwork = true; if(line == "ABORT") abort(); if(line == "EXIT") exit(0); if(line == "exit" || line == "die") { ircd::quit(); return false; } if(console_module) { const ircd::mods::import command { *console_module, "console_command" }; int ret; std::string output; switch((ret = command(line, output))) { case 0: case 1: std::cout << output; if(endswith(output, '\n')) std::flush(std::cout); else std::cout << std::endl; return ret; // The command was handled but the arguments were bad_command. // The module has its own class for a bad_command exception which // is a local and separate symbol from the bad_command here so // we use this code to translate it. case -2: throw bad_command { "%s", output }; // Command isn't handled by the module; continue handling here default: break; } } throw bad_command{}; } catch(const std::out_of_range &e) { std::cerr << "missing required arguments. " << std::endl; return true; } catch(const bad_command &e) { std::cerr << "Bad command or file name: " << e.what() << std::endl; return true; } catch(const ircd::http::error &e) { ircd::log::error("%s %s", e.what(), e.content); return true; } catch(const std::exception &e) { ircd::log::error("%s", e.what()); return true; } void check_console_active() { if(console_active) throw ircd::error("Console is already active and cannot be reentered"); } void console_hangup() try { using log::console_quiet; console_cancel(); static console_quiet *quieted; if(!quieted) { log::notice("Suppressing console log output after terminal hangup"); quieted = new console_quiet; return; } log::notice("Reactivating console logging after second hangup"); delete quieted; quieted = nullptr; } catch(const std::exception &e) { ircd::log::error("console_hangup(): %s", e.what()); } void console_cancel() try { if(!console_active) return; if(console_inwork && console_ctx) { interrupt(*console_ctx); return; } if(console_in) { console_in->cancel(); console_in->close(); } } catch(const std::exception &e) { ircd::log::error("Interrupting console: %s", e.what()); }