mirror of
https://github.com/matrix-construct/construct
synced 2024-11-17 23:40:57 +01:00
390 lines
7.1 KiB
C++
390 lines
7.1 KiB
C++
// Matrix Construct
|
|
//
|
|
// Copyright (C) Matrix Construct Developers, Authors & Contributors
|
|
// Copyright (C) 2016-2018 Jason Volk <jason@zemos.net>
|
|
//
|
|
// 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 <ircd/ircd.h>
|
|
#include <ircd/asio.h>
|
|
#include <ircd/m/m.h>
|
|
#include <ircd/util/params.h>
|
|
#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 exit, or ctrl-d -> EOF
|
|
*** - To shutdown cleanly: type 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;
|
|
|
|
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<std::string> 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<std::string> &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<std::string> 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<prototype> 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()
|
|
};
|
|
}
|