aboutsummaryrefslogtreecommitdiff
path: root/src/daemonizer
diff options
context:
space:
mode:
authorThomas Winget <tewinget@gmail.com>2015-01-29 17:10:53 -0500
committerThomas Winget <tewinget@gmail.com>2015-02-24 00:05:19 -0500
commit9193d6fb5be92df732af18b08b1e052f84cc2f9d (patch)
treedfe58f501122beec786322d30d04efacf8c90259 /src/daemonizer
parentMerge pull request #221 (diff)
downloadmonero-9193d6fb5be92df732af18b08b1e052f84cc2f9d.tar.xz
Daemonize changes pulled in -- daemon builds
many RPC functions added by the daemonize changes (and related changes on the upstream dev branch that were not merged) were commented out (apart from return). Other than that, this *should* work...at any rate, it builds, and that's something.
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..65c1e4195
--- /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::arg_present(vm, arg_is_service))
+ {
+ if (command_line::arg_present(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::arg_present(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::arg_present(vm, arg_install_service))
+ {
+ if (windows::ensure_admin(arguments))
+ {
+ arguments += " --run-as-service";
+ return windows::install_service(executor.name(), arguments);
+ }
+ }
+ else if (command_line::arg_present(vm, arg_uninstall_service))
+ {
+ if (windows::ensure_admin(arguments))
+ {
+ return windows::uninstall_service(executor.name());
+ }
+ }
+ else if (command_line::arg_present(vm, arg_start_service))
+ {
+ if (windows::ensure_admin(arguments))
+ {
+ return windows::start_service(executor.name());
+ }
+ }
+ else if (command_line::arg_present(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