diff options
author | rfree2monero <rfreemonero@op.pl> | 2015-04-01 18:24:45 +0200 |
---|---|---|
committer | rfree2monero <rfreemonero@op.pl> | 2015-04-01 18:24:45 +0200 |
commit | 3cbdf198f168e113f12fa49d016133c434964ed7 (patch) | |
tree | 050c74077de8e1b6e431f7d0671b57b5afcb01ab /src/daemonizer | |
parent | fix locking in count-peers thread (2) (diff) | |
parent | Merge pull request #248 (diff) | |
download | monero-3cbdf198f168e113f12fa49d016133c434964ed7.tar.xz |
Merge remote-tracking branch 'monero-official/master' into network-1.6-work1
Diffstat (limited to 'src/daemonizer')
-rw-r--r-- | src/daemonizer/CMakeLists.txt | 74 | ||||
-rw-r--r-- | src/daemonizer/daemonizer.h | 38 | ||||
-rw-r--r-- | src/daemonizer/posix_daemonizer.inl | 60 | ||||
-rw-r--r-- | src/daemonizer/posix_fork.cpp | 108 | ||||
-rw-r--r-- | src/daemonizer/posix_fork.h | 11 | ||||
-rw-r--r-- | src/daemonizer/windows_daemonizer.inl | 156 | ||||
-rw-r--r-- | src/daemonizer/windows_service.cpp | 341 | ||||
-rw-r--r-- | src/daemonizer/windows_service.h | 36 | ||||
-rw-r--r-- | src/daemonizer/windows_service_runner.h | 157 |
9 files changed, 981 insertions, 0 deletions
diff --git a/src/daemonizer/CMakeLists.txt b/src/daemonizer/CMakeLists.txt new file mode 100644 index 000000000..d5830111c --- /dev/null +++ b/src/daemonizer/CMakeLists.txt @@ -0,0 +1,74 @@ +# Copyright (c) 2014-2015, The Monero Project +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are +# permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of +# conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list +# of conditions and the following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be +# used to endorse or promote products derived from this software without specific +# prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +if(MSVC OR MINGW) + set(daemonizer_sources + windows_service.cpp + windows_daemonizer.inl + ) +else() + set(daemonizer_sources + posix_fork.cpp + posix_daemonizer.inl + ) +endif() + +set(daemonizer_headers +) + +if(MSVC OR MINGW) + set(daemonizer_private_headers + daemonizer.h + windows_service.h + windows_service_runner.h + ) +else() + set(daemonizer_private_headers + daemonizer.h + posix_fork.h + ) +endif() + +bitmonero_private_headers(daemonizer + ${daemonizer_private_headers}) +bitmonero_add_library(daemonizer + ${daemonizer_sources} + ${daemonizer_headers} + ${daemonizer_private_headers}) +target_link_libraries(daemonizer + LINK_PRIVATE + common + ${Boost_CHRONO_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_REGEX_LIBRARY} + ${Boost_SYSTEM_LIBRARY} + ${Boost_THREAD_LIBRARY} + ${CMAKE_THREAD_LIBS_INIT} + ${UPNP_LIBRARIES} + ${EXTRA_LIBRARIES}) diff --git a/src/daemonizer/daemonizer.h b/src/daemonizer/daemonizer.h new file mode 100644 index 000000000..6097a58f6 --- /dev/null +++ b/src/daemonizer/daemonizer.h @@ -0,0 +1,38 @@ +#pragma once + +#include <boost/filesystem/path.hpp> +#include <boost/program_options/options_description.hpp> +#include <boost/program_options/variables_map.hpp> + +namespace daemonizer +{ + void init_options( + boost::program_options::options_description & hidden_options + , boost::program_options::options_description & normal_options + ); + + boost::filesystem::path get_default_data_dir(); + + boost::filesystem::path get_relative_path_base( + boost::program_options::variables_map const & vm + ); + + /** + * @arg create_before_detach - this indicates that the daemon should be + * created before the fork, giving it a chance to report initialization + * errors. At the time of this writing, this is not possible in the primary + * daemon (likely due to the size of the blockchain in memory). + */ + template <typename T_executor> + bool daemonize( + int argc, char const * argv[] + , T_executor && executor // universal ref + , boost::program_options::variables_map const & vm + ); +} + +#ifdef WIN32 +# include "daemonizer/windows_daemonizer.inl" +#else +# include "daemonizer/posix_daemonizer.inl" +#endif diff --git a/src/daemonizer/posix_daemonizer.inl b/src/daemonizer/posix_daemonizer.inl new file mode 100644 index 000000000..e06d43d61 --- /dev/null +++ b/src/daemonizer/posix_daemonizer.inl @@ -0,0 +1,60 @@ +#pragma once + +#include "common/scoped_message_writer.h" +#include "common/util.h" +#include "daemonizer/posix_fork.h" + +#include <boost/filesystem/operations.hpp> +#include <boost/filesystem/path.hpp> + +namespace daemonizer +{ + namespace + { + const command_line::arg_descriptor<bool> arg_detach = { + "detach" + , "Run as daemon" + }; + } + + inline void init_options( + boost::program_options::options_description & hidden_options + , boost::program_options::options_description & normal_options + ) + { + command_line::add_arg(normal_options, arg_detach); + } + + inline boost::filesystem::path get_default_data_dir() + { + return boost::filesystem::absolute(tools::get_default_data_dir()); + } + + inline boost::filesystem::path get_relative_path_base( + boost::program_options::variables_map const & vm + ) + { + return boost::filesystem::current_path(); + } + + template <typename T_executor> + inline bool daemonize( + int argc, char const * argv[] + , T_executor && executor // universal ref + , boost::program_options::variables_map const & vm + ) + { + if (command_line::has_arg(vm, arg_detach)) + { + auto daemon = executor.create_daemon(vm); + tools::success_msg_writer() << "Forking to background..."; + posix::fork(); + return daemon.run(); + } + else + { + //LOG_PRINT_L0(CRYPTONOTE_NAME << " v" << MONERO_VERSION_FULL); + return executor.run_interactive(vm); + } + } +} diff --git a/src/daemonizer/posix_fork.cpp b/src/daemonizer/posix_fork.cpp new file mode 100644 index 000000000..c068912ec --- /dev/null +++ b/src/daemonizer/posix_fork.cpp @@ -0,0 +1,108 @@ +// Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include "daemonizer/posix_fork.h" +#include "misc_log_ex.h" + +#include <cstdlib> +#include <fcntl.h> +#include <unistd.h> +#include <stdexcept> +#include <string> +#include <sys/stat.h> + +namespace posix { + +namespace { + void quit(std::string const & message) + { + LOG_ERROR(message); + throw std::runtime_error(message); + } +} + +void fork() +{ + // Fork the process and have the parent exit. If the process was started + // from a shell, this returns control to the user. Forking a new process is + // also a prerequisite for the subsequent call to setsid(). + if (pid_t pid = ::fork()) + { + if (pid > 0) + { + // We're in the parent process and need to exit. + // + // When the exit() function is used, the program terminates without + // invoking local variables' destructors. Only global variables are + // destroyed. + exit(0); + } + else + { + quit("First fork failed"); + } + } + + // Make the process a new session leader. This detaches it from the + // terminal. + setsid(); + + // A process inherits its working directory from its parent. This could be + // on a mounted filesystem, which means that the running daemon would + // prevent this filesystem from being unmounted. Changing to the root + // directory avoids this problem. + if (chdir("/") < 0) + { + quit("Unable to change working directory to root"); + } + + // The file mode creation mask is also inherited from the parent process. + // We don't want to restrict the permissions on files created by the + // daemon, so the mask is cleared. + umask(0); + + // A second fork ensures the process cannot acquire a controlling terminal. + if (pid_t pid = ::fork()) + { + if (pid > 0) + { + exit(0); + } + else + { + quit("Second fork failed"); + } + } + + // Close the standard streams. This decouples the daemon from the terminal + // that started it. + close(0); + close(1); + close(2); + + // We don't want the daemon to have any standard input. + if (open("/dev/null", O_RDONLY) < 0) + { + quit("Unable to open /dev/null"); + } + + // Send standard output to a log file. + const char* output = "/tmp/bitmonero.daemon.stdout.stderr"; + const int flags = O_WRONLY | O_CREAT | O_APPEND; + const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + if (open(output, flags, mode) < 0) + { + quit("Unable to open output file: " + std::string(output)); + } + + // Also send standard error to the same log file. + if (dup(1) < 0) + { + quit("Unable to dup output descriptor"); + } +} + +} // namespace posix diff --git a/src/daemonizer/posix_fork.h b/src/daemonizer/posix_fork.h new file mode 100644 index 000000000..f099685e9 --- /dev/null +++ b/src/daemonizer/posix_fork.h @@ -0,0 +1,11 @@ +#pragma once + +#ifndef WIN32 + +namespace posix { + +void fork(); + +} + +#endif diff --git a/src/daemonizer/windows_daemonizer.inl b/src/daemonizer/windows_daemonizer.inl new file mode 100644 index 000000000..c099f4097 --- /dev/null +++ b/src/daemonizer/windows_daemonizer.inl @@ -0,0 +1,156 @@ +#pragma once + +#include "common/util.h" +#include "daemonizer/windows_service.h" +#include "daemonizer/windows_service_runner.h" + +#include <shlobj.h> +#include <boost/filesystem/operations.hpp> +#include <boost/filesystem/path.hpp> + +namespace daemonizer +{ + namespace + { + const command_line::arg_descriptor<bool> arg_install_service = { + "install-service" + , "Install Windows service" + }; + const command_line::arg_descriptor<bool> arg_uninstall_service = { + "uninstall-service" + , "Uninstall Windows service" + }; + const command_line::arg_descriptor<bool> arg_start_service = { + "start-service" + , "Start Windows service" + }; + const command_line::arg_descriptor<bool> arg_stop_service = { + "stop-service" + , "Stop Windows service" + }; + const command_line::arg_descriptor<bool> arg_is_service = { + "run-as-service" + , "Hidden -- true if running as windows service" + }; + + std::string get_argument_string(int argc, char const * argv[]) + { + std::string result = ""; + for (int i = 1; i < argc; ++i) + { + result += " " + std::string{argv[i]}; + } + return result; + } + } + + inline void init_options( + boost::program_options::options_description & hidden_options + , boost::program_options::options_description & normal_options + ) + { + command_line::add_arg(normal_options, arg_install_service); + command_line::add_arg(normal_options, arg_uninstall_service); + command_line::add_arg(normal_options, arg_start_service); + command_line::add_arg(normal_options, arg_stop_service); + command_line::add_arg(hidden_options, arg_is_service); + } + + inline boost::filesystem::path get_default_data_dir() + { + bool admin; + if (!windows::check_admin(admin)) + { + admin = false; + } + if (admin) + { + return boost::filesystem::absolute( + tools::get_special_folder_path(CSIDL_COMMON_APPDATA, true) + "\\" + CRYPTONOTE_NAME + ); + } + else + { + return boost::filesystem::absolute( + tools::get_special_folder_path(CSIDL_APPDATA, true) + "\\" + CRYPTONOTE_NAME + ); + } + } + + inline boost::filesystem::path get_relative_path_base( + boost::program_options::variables_map const & vm + ) + { + if (command_line::has_arg(vm, arg_is_service)) + { + if (command_line::has_arg(vm, command_line::arg_data_dir)) + { + return command_line::get_arg(vm, command_line::arg_data_dir); + } + else + { + return tools::get_default_data_dir(); + } + } + else + { + return boost::filesystem::current_path(); + } + } + + template <typename T_executor> + inline bool daemonize( + int argc, char const * argv[] + , T_executor && executor // universal ref + , boost::program_options::variables_map const & vm + ) + { + std::string arguments = get_argument_string(argc, argv); + + if (command_line::has_arg(vm, arg_is_service)) + { + // TODO - Set the service status here for return codes + windows::t_service_runner<typename T_executor::t_daemon>::run( + executor.name() + , executor.create_daemon(vm) + ); + return true; + } + else if (command_line::has_arg(vm, arg_install_service)) + { + if (windows::ensure_admin(arguments)) + { + arguments += " --run-as-service"; + return windows::install_service(executor.name(), arguments); + } + } + else if (command_line::has_arg(vm, arg_uninstall_service)) + { + if (windows::ensure_admin(arguments)) + { + return windows::uninstall_service(executor.name()); + } + } + else if (command_line::has_arg(vm, arg_start_service)) + { + if (windows::ensure_admin(arguments)) + { + return windows::start_service(executor.name()); + } + } + else if (command_line::has_arg(vm, arg_stop_service)) + { + if (windows::ensure_admin(arguments)) + { + return windows::stop_service(executor.name()); + } + } + else // interactive + { + //LOG_PRINT_L0(CRYPTONOTE_NAME << " v" << MONERO_VERSION_FULL); + return executor.run_interactive(vm); + } + + return false; + } +} diff --git a/src/daemonizer/windows_service.cpp b/src/daemonizer/windows_service.cpp new file mode 100644 index 000000000..1b0ee2ef4 --- /dev/null +++ b/src/daemonizer/windows_service.cpp @@ -0,0 +1,341 @@ +#undef UNICODE +#undef _UNICODE + +#include "common/scoped_message_writer.h" +#include "daemonizer/windows_service.h" +#include "string_tools.h" +#include <chrono> +#include <iostream> +#include <utility> +#include <memory> +#include <shellapi.h> +#include <thread> +#include <windows.h> + +namespace windows { + +namespace { + typedef std::unique_ptr<std::remove_pointer<SC_HANDLE>::type, decltype(&::CloseServiceHandle)> service_handle; + + std::string get_last_error() + { + LPSTR p_error_text = nullptr; + + FormatMessage( + FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_IGNORE_INSERTS + , nullptr + , GetLastError() + , MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) + , reinterpret_cast<LPSTR>(&p_error_text) + , 0 + , nullptr + ); + + if (nullptr == p_error_text) + { + return ""; + } + else + { + return std::string{p_error_text}; + LocalFree(p_error_text); + } + } + + bool relaunch_as_admin( + std::string const & command + , std::string const & arguments + ) + { + SHELLEXECUTEINFO info{}; + info.cbSize = sizeof(info); + info.lpVerb = "runas"; + info.lpFile = command.c_str(); + info.lpParameters = arguments.c_str(); + info.hwnd = nullptr; + info.nShow = SW_SHOWNORMAL; + if (!ShellExecuteEx(&info)) + { + tools::fail_msg_writer() << "Admin relaunch failed: " << get_last_error(); + return false; + } + else + { + return true; + } + } + + // When we relaunch as admin, Windows opens a new window. This just pauses + // to allow the user to read any output. + void pause_to_display_admin_window_messages() + { + std::chrono::milliseconds how_long{1500}; + std::this_thread::sleep_for(how_long); + } +} + +bool check_admin(bool & result) +{ + BOOL is_admin = FALSE; + PSID p_administrators_group = nullptr; + + SID_IDENTIFIER_AUTHORITY nt_authority = SECURITY_NT_AUTHORITY; + + if (!AllocateAndInitializeSid( + &nt_authority + , 2 + , SECURITY_BUILTIN_DOMAIN_RID + , DOMAIN_ALIAS_RID_ADMINS + , 0, 0, 0, 0, 0, 0 + , &p_administrators_group + )) + { + tools::fail_msg_writer() << "Security Identifier creation failed: " << get_last_error(); + return false; + } + + if (!CheckTokenMembership( + nullptr + , p_administrators_group + , &is_admin + )) + { + tools::fail_msg_writer() << "Permissions check failed: " << get_last_error(); + return false; + } + + result = is_admin ? true : false; + + return true; +} + +bool ensure_admin( + std::string const & arguments + ) +{ + bool admin; + + if (!check_admin(admin)) + { + return false; + } + + if (admin) + { + return true; + } + else + { + std::string command = epee::string_tools::get_current_module_path(); + relaunch_as_admin(command, arguments); + return false; + } +} + +bool install_service( + std::string const & service_name + , std::string const & arguments + ) +{ + std::string command = epee::string_tools::get_current_module_path(); + std::string full_command = command + arguments; + + service_handle p_manager{ + OpenSCManager( + nullptr + , nullptr + , SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE + ) + , &::CloseServiceHandle + }; + if (p_manager == nullptr) + { + tools::fail_msg_writer() << "Couldn't connect to service manager: " << get_last_error(); + return false; + } + + service_handle p_service{ + CreateService( + p_manager.get() + , service_name.c_str() + , service_name.c_str() + , 0 + //, GENERIC_EXECUTE | GENERIC_READ + , SERVICE_WIN32_OWN_PROCESS + , SERVICE_DEMAND_START + , SERVICE_ERROR_NORMAL + , full_command.c_str() + , nullptr + , nullptr + , "" + //, "NT AUTHORITY\\LocalService" + , nullptr // Implies LocalSystem account + , nullptr + ) + , &::CloseServiceHandle + }; + if (p_service == nullptr) + { + tools::fail_msg_writer() << "Couldn't create service: " << get_last_error(); + return false; + } + + tools::success_msg_writer() << "Service installed"; + + pause_to_display_admin_window_messages(); + + return true; +} + +bool start_service( + std::string const & service_name + ) +{ + tools::msg_writer() << "Starting service"; + + SERVICE_STATUS_PROCESS service_status = {}; + DWORD unused = 0; + + service_handle p_manager{ + OpenSCManager( + nullptr + , nullptr + , SC_MANAGER_CONNECT + ) + , &::CloseServiceHandle + }; + if (p_manager == nullptr) + { + tools::fail_msg_writer() << "Couldn't connect to service manager: " << get_last_error(); + return false; + } + + service_handle p_service{ + OpenService( + p_manager.get() + , service_name.c_str() + //, SERVICE_START | SERVICE_QUERY_STATUS + , SERVICE_START + ) + , &::CloseServiceHandle + }; + if (p_service == nullptr) + { + tools::fail_msg_writer() << "Couldn't find service: " << get_last_error(); + return false; + } + + if (!StartService( + p_service.get() + , 0 + , nullptr + )) + { + tools::fail_msg_writer() << "Service start request failed: " << get_last_error(); + return false; + } + + tools::success_msg_writer() << "Service started"; + + pause_to_display_admin_window_messages(); + + return true; +} + +bool stop_service( + std::string const & service_name + ) +{ + tools::msg_writer() << "Stopping service"; + + service_handle p_manager{ + OpenSCManager( + nullptr + , nullptr + , SC_MANAGER_CONNECT + ) + , &::CloseServiceHandle + }; + if (p_manager == nullptr) + { + tools::fail_msg_writer() << "Couldn't connect to service manager: " << get_last_error(); + return false; + } + + service_handle p_service{ + OpenService( + p_manager.get() + , service_name.c_str() + , SERVICE_STOP | SERVICE_QUERY_STATUS + ) + , &::CloseServiceHandle + }; + if (p_service == nullptr) + { + tools::fail_msg_writer() << "Couldn't find service: " << get_last_error(); + return false; + } + + SERVICE_STATUS status = {}; + if (!ControlService(p_service.get(), SERVICE_CONTROL_STOP, &status)) + { + tools::fail_msg_writer() << "Couldn't request service stop: " << get_last_error(); + return false; + } + + tools::success_msg_writer() << "Service stopped"; + + pause_to_display_admin_window_messages(); + + return true; +} + +bool uninstall_service( + std::string const & service_name + ) +{ + service_handle p_manager{ + OpenSCManager( + nullptr + , nullptr + , SC_MANAGER_CONNECT + ) + , &::CloseServiceHandle + }; + if (p_manager == nullptr) + { + tools::fail_msg_writer() << "Couldn't connect to service manager: " << get_last_error(); + return false; + } + + service_handle p_service{ + OpenService( + p_manager.get() + , service_name.c_str() + , SERVICE_QUERY_STATUS | DELETE + ) + , &::CloseServiceHandle + }; + if (p_service == nullptr) + { + tools::fail_msg_writer() << "Couldn't find service: " << get_last_error(); + return false; + } + + SERVICE_STATUS status = {}; + if (!DeleteService(p_service.get())) + { + tools::fail_msg_writer() << "Couldn't uninstall service: " << get_last_error(); + return false; + } + + tools::success_msg_writer() << "Service uninstalled"; + + pause_to_display_admin_window_messages(); + + return true; +} + +} // namespace windows diff --git a/src/daemonizer/windows_service.h b/src/daemonizer/windows_service.h new file mode 100644 index 000000000..11f5fdcdc --- /dev/null +++ b/src/daemonizer/windows_service.h @@ -0,0 +1,36 @@ +#pragma once + +#ifdef WIN32 + +#undef UNICODE +#undef _UNICODE + +#include <string> +#include <windows.h> + +namespace windows +{ + bool check_admin(bool & result); + + bool ensure_admin( + std::string const & arguments + ); + + bool install_service( + std::string const & service_name + , std::string const & arguments + ); + + bool uninstall_service( + std::string const & service_name + ); + + bool start_service( + std::string const & service_name + ); + + bool stop_service( + std::string const & service_name + ); +} +#endif diff --git a/src/daemonizer/windows_service_runner.h b/src/daemonizer/windows_service_runner.h new file mode 100644 index 000000000..79b10b136 --- /dev/null +++ b/src/daemonizer/windows_service_runner.h @@ -0,0 +1,157 @@ +#pragma once + +#ifdef WIN32 + +#undef UNICODE +#undef _UNICODE + +#include "daemonizer/windows_service.h" +#include <memory> +#include <string> +#include <vector> +#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; + } + } + + template <typename T_handler> + class t_service_runner final + { + private: + SERVICE_STATUS_HANDLE m_status_handle{nullptr}; + SERVICE_STATUS m_status{}; + std::mutex m_lock{}; + std::string m_name; + T_handler m_handler; + + static std::unique_ptr<t_service_runner<T_handler>> sp_instance; + public: + t_service_runner( + std::string name + , T_handler handler + ) + : m_name{std::move(name)} + , m_handler{std::move(handler)} + { + m_status.dwServiceType = SERVICE_WIN32; + m_status.dwCurrentState = SERVICE_STOPPED; + m_status.dwControlsAccepted = 0; + m_status.dwWin32ExitCode = NO_ERROR; + m_status.dwServiceSpecificExitCode = NO_ERROR; + m_status.dwCheckPoint = 0; + m_status.dwWaitHint = 0; + } + + t_service_runner & operator=(t_service_runner && other) + { + 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 } + }; + + StartServiceCtrlDispatcher(table); + } + + void report_status(DWORD status) + { + m_status.dwCurrentState = status; + if (status == SERVICE_RUNNING) + { + m_status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; + } + else if(status == SERVICE_STOP_PENDING) + { + m_status.dwControlsAccepted = 0; + } + SetServiceStatus(m_status_handle, &m_status); + } + + static void WINAPI service_main(DWORD argc, LPSTR * argv) + { + sp_instance->service_main_(argc, argv); + } + + void service_main_(DWORD argc, LPSTR * argv) + { + m_status_handle = RegisterServiceCtrlHandler(m_name.c_str(), &on_state_change_request); + if (m_status_handle == nullptr) return; + + report_status(SERVICE_START_PENDING); + + report_status(SERVICE_RUNNING); + + m_handler.run(); + + on_state_change_request_(SERVICE_CONTROL_STOP); + + // Ensure that the service is uninstalled + uninstall_service(m_name); + } + + static void WINAPI on_state_change_request(DWORD control_code) + { + sp_instance->on_state_change_request_(control_code); + } + + void on_state_change_request_(DWORD control_code) + { + switch (control_code) + { + case SERVICE_CONTROL_INTERROGATE: + break; + case SERVICE_CONTROL_SHUTDOWN: + case SERVICE_CONTROL_STOP: + report_status(SERVICE_STOP_PENDING); + m_handler.stop(); + report_status(SERVICE_STOPPED); + break; + case SERVICE_CONTROL_PAUSE: + break; + case SERVICE_CONTROL_CONTINUE: + break; + default: + break; + } + } + }; + + template <typename T_handler> + std::unique_ptr<t_service_runner<T_handler>> t_service_runner<T_handler>::sp_instance; +} + +#endif |