aboutsummaryrefslogtreecommitdiff
path: root/src/daemonizer
diff options
context:
space:
mode:
authorrfree2monero <rfreemonero@op.pl>2015-04-01 18:24:45 +0200
committerrfree2monero <rfreemonero@op.pl>2015-04-01 18:24:45 +0200
commit3cbdf198f168e113f12fa49d016133c434964ed7 (patch)
tree050c74077de8e1b6e431f7d0671b57b5afcb01ab /src/daemonizer
parentfix locking in count-peers thread (2) (diff)
parentMerge pull request #248 (diff)
downloadmonero-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.txt74
-rw-r--r--src/daemonizer/daemonizer.h38
-rw-r--r--src/daemonizer/posix_daemonizer.inl60
-rw-r--r--src/daemonizer/posix_fork.cpp108
-rw-r--r--src/daemonizer/posix_fork.h11
-rw-r--r--src/daemonizer/windows_daemonizer.inl156
-rw-r--r--src/daemonizer/windows_service.cpp341
-rw-r--r--src/daemonizer/windows_service.h36
-rw-r--r--src/daemonizer/windows_service_runner.h157
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