// 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 "construct.h" using namespace ircd; IRCD_EXCEPTION_HIDENAME(ircd::error, bad_command) bool console_active; bool console_inwork; ircd::ctx::ctx *console_ctx; ircd::module *console_module; const char *const generic_message {R"( *** - To end the console session: type exit, or ctrl-d -> EOF *** - To shutdown cleanly: type die, or ctrl-\ -> SIGQUIT *** - To generate a coredump for developers, type ABORT -> abort() *** )"}; conf::item stack_sz { { "name", "construct.console.stack.size" }, { "default", long(2_MiB) }, }; static void check_console_active(); static void console_fini(); static void console_init(); static int handle_line_bymodule(const string_view &line); 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, 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. This is the *** terminal console also available in your !control room. ***)"}; void console() try { ircd::runlevel_changed::dock.wait([] { return ircd::runlevel == ircd::runlevel::RUN || ircd::runlevel == ircd::runlevel::HALT; }); if(ircd::runlevel != ircd::runlevel::RUN) return; const unwind atexit([] { console_fini(); }); console_init(); static std::once_flag seen_message; std::call_once(seen_message, [] { std::cout << console_message << generic_message; }); while(1) { std::cout << "\n> " << std::flush; thread_local char buf[64_KiB]; string_view line; // 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); line = fs::stdin::readline(buf); } if(line.empty()) continue; if(!handle_line(line)) break; ctx::interruption_point(); } 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() }; } void execute(const std::vector lines) try { ircd::runlevel_changed::dock.wait([] { return ircd::runlevel == ircd::runlevel::RUN || ircd::runlevel == ircd::runlevel::HALT; }); if(ircd::runlevel == ircd::runlevel::HALT) 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") return false; if(line == "die") { ircd::quit(); return false; } int ret{-1}; if(console_module) switch((ret = handle_line_bymodule(line))) { default: break; case 0: return false; case 1: return true; } throw bad_command { "%s", line }; } 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; } int handle_line_bymodule(const string_view &line) { using prototype = int (std::ostream &, const string_view &, const string_view &); const ircd::mods::import command { *console_module, "console_command" }; std::ostringstream out; out.exceptions(out.badbit | out.failbit | out.eofbit); int ret; static const string_view opts; switch((ret = command(out, line, opts))) { case 0: case 1: { // For this scope we have to suppress again because there's some // yielding business going on below where log messages can break // into the command output. const log::console_quiet quiet{false}; const auto str { out.str() }; static const size_t mlen(2048); for(size_t off(0); off < str.size(); off += mlen) { const string_view substr { str.data() + off, std::min(str.size() - off, mlen) }; std::cout << substr << std::flush; ctx::sleep(milliseconds(50)); } if(!endswith(str, '\n')) 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", out.str() }; // Command isn't handled by the module; continue handling here default: break; } return ret; } 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_ctx) interrupt(*console_ctx); } catch(const std::exception &e) { ircd::log::error { "Interrupting console: %s", e.what() }; }