wallet_rpc: Simplify daemonizer, remove t_handler/t_executor

The daemonizer requires you to fill out an "interface" to use it. This
imposes a design constraint on end users whens starting up applications
and led to a initialization circus with a t_executor starting up
a t_daemon that creates an instance of our application.

Instead design it so that execution flow is controlled by the user and
not the interface by returning the run_type that the user requested (run
as a service, forked, interactive or non-interactive) and launch
accordingly at the call site, and not indirected through 2 classes of
OOP cruft.

There still remains 1 wart with the Windows daemonizer requiring
templatization on the application so that Windows can callback at
a their deferred discretion and startup and shutdown the application
accordingly.
This commit is contained in:
Doyle 2020-04-23 12:50:52 +10:00
parent ee44c01c69
commit 2909ad886a
8 changed files with 170 additions and 171 deletions

View file

@ -57,10 +57,22 @@ namespace daemonizer
, T_executor && executor // universal ref
, boost::program_options::variables_map const & vm
);
enum struct run_type
{
non_interactive,
interactive,
service,
terminate,
terminate_with_error,
};
template <typename Application>
run_type setup_run_environment(char const *name, int argc, char const *argv[], boost::program_options::variables_map const &vm);
}
#ifdef WIN32
# include "daemonizer/windows_daemonizer.inl"
#include "daemonizer/windows_daemonizer.inl"
#else
# include "daemonizer/posix_daemonizer.inl"
#include "daemonizer/posix_daemonizer.inl"
#endif

View file

@ -92,8 +92,7 @@ namespace daemonizer
pidfile = command_line::get_arg(vm, arg_pidfile);
}
posix::fork(pidfile);
auto daemon = executor.create_daemon(vm);
return daemon.run();
return executor.run_non_interactive(vm);
}
else if (command_line::has_arg(vm, arg_non_interactive))
{
@ -105,4 +104,25 @@ namespace daemonizer
return executor.run_interactive(vm);
}
}
template <typename Application>
inline run_type setup_run_environment(char const *name, int argc, char const *argv[], boost::program_options::variables_map const &vm)
{
(void)name; (void)argc; (void)argv;
if (command_line::has_arg(vm, arg_detach))
{
tools::success_msg_writer() << "Forking to background...";
std::string pidfile;
if (command_line::has_arg(vm, arg_pidfile))
{
pidfile = command_line::get_arg(vm, arg_pidfile);
}
posix::fork(pidfile);
return run_type::non_interactive;
}
else if (command_line::has_arg(vm, arg_non_interactive))
return run_type::non_interactive;
return run_type::interactive;
}
}

View file

@ -142,9 +142,15 @@ namespace daemonizer
{
std::string arguments = get_argument_string(argc, argv);
#if 0
if (command_line::has_arg(vm, arg_is_service))
{
// TODO - Set the service status here for return codes
static t_service_runner<T_handler> runner = {};
runner.name = executor.name();
runner.handler = executor.create_daemon();
runner.run_();
windows::t_service_runner<typename T_executor::t_daemon>::run(
executor.name()
, executor.create_daemon(vm)
@ -188,7 +194,59 @@ namespace daemonizer
else
return executor.run_interactive(vm);
}
#endif
return false;
}
template <typename Application>
inline run_type setup_run_environment(char const *name, int argc, char const *argv[], boost::program_options::variables_map const &vm)
{
std::string arguments = get_argument_string(argc, argv);
if (command_line::has_arg(vm, arg_is_service))
{
Application app(&vm);
windows::service_runner<Application> runner(name, &app);
runner.run();
return run_type::terminate;
}
else if (command_line::has_arg(vm, arg_install_service))
{
if (windows::ensure_admin(arguments))
{
arguments += " --run-as-service";
bool result = windows::install_service(name, arguments);
return result ? run_type::terminate : run_type::terminate_with_error;
}
}
else if (command_line::has_arg(vm, arg_uninstall_service))
{
if (windows::ensure_admin(arguments))
{
bool result = windows::uninstall_service(name);
return result ? run_type::terminate : run_type::terminate_with_error;
}
}
else if (command_line::has_arg(vm, arg_start_service))
{
if (windows::ensure_admin(arguments))
{
bool result = windows::start_service(name);
return result ? run_type::terminate : run_type::terminate_with_error;
}
}
else if (command_line::has_arg(vm, arg_stop_service))
{
if (windows::ensure_admin(arguments))
{
bool result = windows::stop_service(name);
return result ? run_type::terminate : run_type::terminate_with_error;
}
}
if (command_line::has_arg(vm, arg_non_interactive))
return run_type::non_interactive;
else
return run_type::interactive;
}
}

View file

@ -166,10 +166,7 @@ bool ensure_admin(
}
}
bool install_service(
std::string const & service_name
, std::string const & arguments
)
bool install_service(char const *service_name, std::string const &arguments)
{
std::string command = epee::string_tools::get_current_module_path();
std::string full_command = command + arguments;
@ -191,8 +188,8 @@ bool install_service(
service_handle p_service{
CreateService(
p_manager.get()
, service_name.c_str()
, service_name.c_str()
, service_name
, service_name
, 0
//, GENERIC_EXECUTE | GENERIC_READ
, SERVICE_WIN32_OWN_PROCESS
@ -221,9 +218,7 @@ bool install_service(
return true;
}
bool start_service(
std::string const & service_name
)
bool start_service(char const *service_name)
{
tools::msg_writer() << "Starting service";
@ -247,7 +242,7 @@ bool start_service(
service_handle p_service{
OpenService(
p_manager.get()
, service_name.c_str()
, service_name
//, SERVICE_START | SERVICE_QUERY_STATUS
, SERVICE_START
)
@ -276,9 +271,7 @@ bool start_service(
return true;
}
bool stop_service(
std::string const & service_name
)
bool stop_service(char const *service_name)
{
tools::msg_writer() << "Stopping service";
@ -299,7 +292,7 @@ bool stop_service(
service_handle p_service{
OpenService(
p_manager.get()
, service_name.c_str()
, service_name
, SERVICE_STOP | SERVICE_QUERY_STATUS
)
, &::CloseServiceHandle
@ -324,9 +317,7 @@ bool stop_service(
return true;
}
bool uninstall_service(
std::string const & service_name
)
bool uninstall_service(char const *service_name)
{
service_handle p_manager{
OpenSCManager(
@ -345,7 +336,7 @@ bool uninstall_service(
service_handle p_service{
OpenService(
p_manager.get()
, service_name.c_str()
, service_name
, SERVICE_QUERY_STATUS | DELETE
)
, &::CloseServiceHandle

View file

@ -40,25 +40,14 @@ namespace windows
{
bool check_admin(bool & result);
bool ensure_admin(
std::string const & arguments
);
bool ensure_admin(std::string const &arguments);
bool install_service(
std::string const & service_name
, std::string const & arguments
);
bool install_service(char const *service_name, std::string const &arguments);
bool uninstall_service(
std::string const & service_name
);
bool uninstall_service(char const *service_name);
bool start_service(
std::string const & service_name
);
bool start_service(char const *service_name);
bool stop_service(
std::string const & service_name
);
bool stop_service(char const *service_name);
}
#endif

View file

@ -40,35 +40,28 @@
#include <windows.h>
namespace windows {
namespace
{
std::vector<char> vecstring(std::string const & str)
{
std::vector<char> result{str.begin(), str.end()};
result.push_back('\0');
return result;
}
}
static void *runner_instance; // For C-style callbacks to call into C++ classes
template <typename T_handler>
class t_service_runner final
template <typename Application>
class service_runner final
{
private:
SERVICE_STATUS_HANDLE m_status_handle{nullptr};
SERVICE_STATUS m_status{};
boost::mutex m_lock{};
std::string m_name;
T_handler m_handler;
Application *app;
static std::unique_ptr<t_service_runner<T_handler>> sp_instance;
public:
t_service_runner(
service_runner(
std::string name
, T_handler handler
, Application *app
)
: m_name{std::move(name)}
, m_handler{std::move(handler)}
, app{app}
{
windows::runner_instance = this;
m_status.dwServiceType = SERVICE_WIN32;
m_status.dwCurrentState = SERVICE_STOPPED;
m_status.dwControlsAccepted = 0;
@ -77,43 +70,17 @@ namespace windows {
m_status.dwCheckPoint = 0;
m_status.dwWaitHint = 0;
}
service_runner &operator=(service_runner &&other) = delete;
t_service_runner & operator=(t_service_runner && other)
void run()
{
if (this != &other)
{
m_status_handle = std::move(other.m_status_handle);
m_status = std::move(other.m_status);
m_name = std::move(other.m_name);
m_handler = std::move(other.m_handler);
}
return *this;
}
static void run(
std::string name
, T_handler handler
)
{
sp_instance.reset(new t_service_runner<T_handler>{
std::move(name)
, std::move(handler)
});
sp_instance->run_();
}
private:
void run_()
{
SERVICE_TABLE_ENTRY table[] =
{
{ vecstring(m_name).data(), &service_main }
, { 0, 0 }
};
std::vector<char> name{m_name.begin(), m_name.end()};
name.push_back('\0');
SERVICE_TABLE_ENTRY const table[] = {{name.data(), &service_main}, {0, 0}};
StartServiceCtrlDispatcher(table);
}
private:
void report_status(DWORD status)
{
@ -131,7 +98,7 @@ namespace windows {
static void WINAPI service_main(DWORD argc, LPSTR * argv)
{
sp_instance->service_main_(argc, argv);
((service_runner *)windows::runner_instance)->service_main_(argc, argv);
}
void service_main_(DWORD argc, LPSTR * argv)
@ -143,17 +110,17 @@ namespace windows {
report_status(SERVICE_RUNNING);
m_handler.run();
app->run();
on_state_change_request_(SERVICE_CONTROL_STOP);
// Ensure that the service is uninstalled
uninstall_service(m_name);
uninstall_service(m_name.c_str());
}
static void WINAPI on_state_change_request(DWORD control_code)
{
sp_instance->on_state_change_request_(control_code);
((service_runner *)windows::runner_instance)->on_state_change_request_(control_code);
}
void on_state_change_request_(DWORD control_code)
@ -165,7 +132,7 @@ namespace windows {
case SERVICE_CONTROL_SHUTDOWN:
case SERVICE_CONTROL_STOP:
report_status(SERVICE_STOP_PENDING);
m_handler.stop();
app->stop();
report_status(SERVICE_STOPPED);
break;
case SERVICE_CONTROL_PAUSE:
@ -177,9 +144,6 @@ namespace windows {
}
}
};
template <typename T_handler>
std::unique_ptr<t_service_runner<T_handler>> t_service_runner<T_handler>::sp_instance;
}
#endif

View file

@ -91,7 +91,12 @@ namespace tools
}
//------------------------------------------------------------------------------------------------------------------------------
wallet_rpc_server::wallet_rpc_server():m_wallet(NULL), rpc_login_file(), m_stop(false), m_restricted(false), m_vm(NULL)
wallet_rpc_server::wallet_rpc_server(boost::program_options::variables_map const *vm)
: m_wallet(NULL)
, rpc_login_file()
, m_stop(false)
, m_restricted(false)
, m_vm(vm)
{
}
//------------------------------------------------------------------------------------------------------------------------------
@ -106,7 +111,7 @@ namespace tools
m_wallet = cr;
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::run()
bool wallet_rpc_server::run_server_threads()
{
m_stop = false;
m_net_server.add_idle_handler([this](){
@ -168,6 +173,7 @@ namespace tools
//------------------------------------------------------------------------------------------------------------------------------
void wallet_rpc_server::stop()
{
send_stop_signal();
if (m_wallet)
{
m_wallet->store();
@ -176,14 +182,12 @@ namespace tools
}
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::init(const boost::program_options::variables_map *vm)
bool wallet_rpc_server::init()
{
auto rpc_config = cryptonote::rpc_args::process(*vm);
auto rpc_config = cryptonote::rpc_args::process(*m_vm);
if (!rpc_config)
return false;
m_vm = vm;
boost::optional<epee::net_utils::http::login> http_login{};
std::string bind_port = command_line::get_arg(*m_vm, arg_rpc_bind_port);
const bool disable_auth = command_line::get_arg(*m_vm, arg_disable_rpc_login);
@ -4546,29 +4550,14 @@ namespace tools
res.value = value.to_readable_value(m_wallet->nettype(), type);
return true;
}
}
class t_daemon
{
private:
const boost::program_options::variables_map& vm;
std::unique_ptr<tools::wallet_rpc_server> wrpc;
public:
t_daemon(boost::program_options::variables_map const & _vm)
: vm(_vm)
, wrpc(new tools::wallet_rpc_server)
{
}
bool run()
bool wallet_rpc_server::run()
{
std::unique_ptr<tools::wallet2> wal;
try
{
const bool testnet = tools::wallet2::has_testnet_option(vm);
const bool stagenet = tools::wallet2::has_stagenet_option(vm);
const bool testnet = tools::wallet2::has_testnet_option(*m_vm);
const bool stagenet = tools::wallet2::has_stagenet_option(*m_vm);
if (testnet && stagenet)
{
MERROR(tools::wallet_rpc_server::tr("Can't specify more than one of --testnet and --stagenet"));
@ -4578,10 +4567,10 @@ public:
const auto arg_wallet_file = wallet_args::arg_wallet_file();
const auto arg_from_json = wallet_args::arg_generate_from_json();
const auto wallet_file = command_line::get_arg(vm, arg_wallet_file);
const auto from_json = command_line::get_arg(vm, arg_from_json);
const auto wallet_dir = command_line::get_arg(vm, arg_wallet_dir);
const auto prompt_for_password = command_line::get_arg(vm, arg_prompt_for_password);
const auto wallet_file = command_line::get_arg(*m_vm, arg_wallet_file);
const auto from_json = command_line::get_arg(*m_vm, arg_from_json);
const auto wallet_dir = command_line::get_arg(*m_vm, arg_wallet_dir);
const auto prompt_for_password = command_line::get_arg(*m_vm, arg_prompt_for_password);
const auto password_prompt = prompt_for_password ? password_prompter : nullptr;
if(!wallet_file.empty() && !from_json.empty())
@ -4605,13 +4594,13 @@ public:
LOG_PRINT_L0(tools::wallet_rpc_server::tr("Loading wallet..."));
if(!wallet_file.empty())
{
wal = tools::wallet2::make_from_file(vm, true, wallet_file, password_prompt).first;
wal = tools::wallet2::make_from_file(*m_vm, true, wallet_file, password_prompt).first;
}
else
{
try
{
auto rc = tools::wallet2::make_from_json(vm, true, from_json, password_prompt);
auto rc = tools::wallet2::make_from_json(*m_vm, true, from_json, password_prompt);
wal = std::move(rc.first);
}
catch (const std::exception &e)
@ -4648,17 +4637,17 @@ public:
LOG_ERROR(tools::wallet_rpc_server::tr("Wallet initialization failed: ") << e.what());
return false;
}
just_dir:
if (wal) wrpc->set_wallet(wal.release());
bool r = wrpc->init(&vm);
if (wal) set_wallet(wal.release());
bool r = init();
CHECK_AND_ASSERT_MES(r, false, tools::wallet_rpc_server::tr("Failed to initialize wallet RPC server"));
tools::signal_handler::install([this](int) {
wrpc->send_stop_signal();
});
tools::signal_handler::install([this](int) { send_stop_signal(); });
LOG_PRINT_L0(tools::wallet_rpc_server::tr("Starting wallet RPC server"));
try
{
wrpc->run();
run_server_threads();
}
catch (const std::exception &e)
{
@ -4669,7 +4658,7 @@ public:
try
{
LOG_PRINT_L0(tools::wallet_rpc_server::tr("Saving wallet..."));
wrpc->stop();
stop();
LOG_PRINT_L0(tools::wallet_rpc_server::tr("Successfully saved"));
}
catch (const std::exception& e)
@ -4679,44 +4668,10 @@ public:
}
return true;
}
}
void stop()
{
wrpc->send_stop_signal();
}
};
class t_executor final
int main(int argc, char **argv)
{
public:
static std::string const NAME;
typedef ::t_daemon t_daemon;
std::string const & name() const
{
return NAME;
}
t_daemon create_daemon(boost::program_options::variables_map const & vm)
{
return t_daemon(vm);
}
bool run_non_interactive(boost::program_options::variables_map const & vm)
{
return t_daemon(vm).run();
}
bool run_interactive(boost::program_options::variables_map const & vm)
{
return t_daemon(vm).run();
}
};
std::string const t_executor::NAME = "Wallet RPC Daemon";
int main(int argc, char** argv) {
TRY_ENTRY();
namespace po = boost::program_options;
@ -4761,6 +4716,15 @@ int main(int argc, char** argv) {
return 0;
}
return daemonizer::daemonize(argc, const_cast<const char**>(argv), t_executor{}, *vm) ? 0 : 1;
daemonizer::run_type run_type = daemonizer::setup_run_environment<tools::wallet_rpc_server>("Wallet RPC Daemon", argc, const_cast<const char **>(argv), *vm);
if (run_type == daemonizer::run_type::terminate)
return 0;
else if (run_type == daemonizer::run_type::terminate_with_error)
return -1;
tools::wallet_rpc_server server(&(*vm));
bool result = server.run();
return !result;
CATCH_ENTRY_L0("main", 1);
}

View file

@ -55,15 +55,16 @@ namespace tools
static const char* tr(const char* str);
wallet_rpc_server();
wallet_rpc_server(const boost::program_options::variables_map *vm);
~wallet_rpc_server();
bool init(const boost::program_options::variables_map *vm);
bool init();
bool run();
void stop();
void set_wallet(wallet2 *cr);
private:
bool run_server_threads();
CHAIN_HTTP_TO_MAP2(connection_context); //forward http requests to uri map