From 10e1921d30a66db85d8707e554e0eeb5efbb8b00 Mon Sep 17 00:00:00 2001 From: Egor Tensin Date: Sun, 10 May 2015 23:00:08 +0300 Subject: add service mgmt lib & utils --- utils/libservice/CMakeLists.txt | 8 + utils/libservice/include/libservice/common.hpp | 19 ++ utils/libservice/include/libservice/interface.hpp | 15 ++ utils/libservice/include/libservice/service.hpp | 69 ++++++ .../include/libservice/service_handle.hpp | 80 +++++++ .../include/libservice/service_manager.hpp | 65 ++++++ utils/libservice/include/libservice/singleton.hpp | 45 ++++ .../include/libservice/windows_error.hpp | 36 ++++ utils/libservice/src/service.cpp | 235 +++++++++++++++++++++ utils/libservice/src/service_handle.cpp | 30 +++ utils/libservice/src/service_manager.cpp | 48 +++++ utils/libservice/src/windows_error.cpp | 41 ++++ utils/libservice/test/CMakeLists.txt | 5 + utils/libservice/test/windows_error.cpp | 27 +++ 14 files changed, 723 insertions(+) create mode 100644 utils/libservice/CMakeLists.txt create mode 100644 utils/libservice/include/libservice/common.hpp create mode 100644 utils/libservice/include/libservice/interface.hpp create mode 100644 utils/libservice/include/libservice/service.hpp create mode 100644 utils/libservice/include/libservice/service_handle.hpp create mode 100644 utils/libservice/include/libservice/service_manager.hpp create mode 100644 utils/libservice/include/libservice/singleton.hpp create mode 100644 utils/libservice/include/libservice/windows_error.hpp create mode 100644 utils/libservice/src/service.cpp create mode 100644 utils/libservice/src/service_handle.cpp create mode 100644 utils/libservice/src/service_manager.cpp create mode 100644 utils/libservice/src/windows_error.cpp create mode 100644 utils/libservice/test/CMakeLists.txt create mode 100644 utils/libservice/test/windows_error.cpp (limited to 'utils/libservice') diff --git a/utils/libservice/CMakeLists.txt b/utils/libservice/CMakeLists.txt new file mode 100644 index 0000000..169a641 --- /dev/null +++ b/utils/libservice/CMakeLists.txt @@ -0,0 +1,8 @@ +project(libservice) +file(GLOB ${PROJECT_NAME}_sources "src/*.cpp") +file(GLOB_RECURSE ${PROJECT_NAME}_headers "include/*.hpp") +add_library(${PROJECT_NAME} ${${PROJECT_NAME}_sources} + ${${PROJECT_NAME}_headers}) +target_include_directories(${PROJECT_NAME} PUBLIC include) + +add_subdirectory(test) diff --git a/utils/libservice/include/libservice/common.hpp b/utils/libservice/include/libservice/common.hpp new file mode 100644 index 0000000..32e2a50 --- /dev/null +++ b/utils/libservice/include/libservice/common.hpp @@ -0,0 +1,19 @@ +/** + * \author Egor Tensin + * \date 2015 + * \copyright This file is licensed under the terms of the MIT License. + * See LICENSE.txt for details. + */ + +#pragma once + +#define LIBSERVICE_FILE_PATH __FILE__ +#define LIBSERVICE_LINE_NUMBER __LINE__ +#define LIBSERVICE_FUNCTION_NAME __FUNCTION__ + +#define LIBSERVICE_TO_STRING(s) LIBSERVICE_TO_STRING_(s) +#define LIBSERVICE_TO_STRING_(s) #s + +#define LIBSERVICE_LINE_NUMBER_STRING LIBSERVICE_TO_STRING(LIBSERVICE_LINE_NUMBER) + +#define LIBSERVICE_NOEXCEPT throw() diff --git a/utils/libservice/include/libservice/interface.hpp b/utils/libservice/include/libservice/interface.hpp new file mode 100644 index 0000000..058470c --- /dev/null +++ b/utils/libservice/include/libservice/interface.hpp @@ -0,0 +1,15 @@ +/** + * \author Egor Tensin + * \date 2015 + * \copyright This file is licensed under the terms of the MIT License. + * See LICENSE.txt for details. + */ + +#pragma once + +#include "common.hpp" +#include "service.hpp" +#include "service_handle.hpp" +#include "service_manager.hpp" +#include "singleton.hpp" +#include "windows_error.hpp" diff --git a/utils/libservice/include/libservice/service.hpp b/utils/libservice/include/libservice/service.hpp new file mode 100644 index 0000000..a14f0e2 --- /dev/null +++ b/utils/libservice/include/libservice/service.hpp @@ -0,0 +1,69 @@ +/** + * \file + * \author Egor Tensin + * \date 2015 + * \copyright This file is licensed under the terms of the MIT License. + * See LICENSE.txt for details. + */ + +#pragma once + +#include "common.hpp" +#include "service_handle.hpp" +#include "service_manager.hpp" + +#include +#include + +namespace libservice +{ + class Service + { + public: + static Service open(const ServiceManager&, + const std::string& name); + static Service install(const ServiceManager&, + const std::string& name, + const std::string& bin_path); + + void start() const; + void stop() const; + void uninstall() const; + + Service(Service&& other) LIBSERVICE_NOEXCEPT + { + swap(other); + } + + Service& operator=(Service other) LIBSERVICE_NOEXCEPT + { + swap(other); + return *this; + } + + void swap(Service& other) LIBSERVICE_NOEXCEPT + { + using std::swap; + swap(m_handle, other.m_handle); + } + + private: + explicit Service(ServiceHandle h) + : m_handle(std::move(h)) + { } + + ServiceHandle m_handle; + + Service(const Service&) = delete; + }; + + void swap(Service&, Service&) LIBSERVICE_NOEXCEPT; +} + +namespace std +{ + template <> + void swap( + libservice::Service&, + libservice::Service&) LIBSERVICE_NOEXCEPT; +} diff --git a/utils/libservice/include/libservice/service_handle.hpp b/utils/libservice/include/libservice/service_handle.hpp new file mode 100644 index 0000000..2068102 --- /dev/null +++ b/utils/libservice/include/libservice/service_handle.hpp @@ -0,0 +1,80 @@ +/** + * \file + * \author Egor Tensin + * \date 2015 + * \copyright This file is licensed under the terms of the MIT License. + * See LICENSE.txt for details. + */ + +#pragma once + +#include "common.hpp" + +#include + +#include +#include +#include + +namespace libservice +{ + class ServiceHandle + { + public: + ServiceHandle() = default; + + explicit ServiceHandle(SC_HANDLE raw) + : m_impl(raw) + { } + + ServiceHandle(ServiceHandle&& other) LIBSERVICE_NOEXCEPT + { + swap(other); + } + + ServiceHandle& operator=(ServiceHandle other) LIBSERVICE_NOEXCEPT + { + swap(other); + return *this; + } + + explicit operator bool() const + { + return static_cast(m_impl); + } + + explicit operator SC_HANDLE() const + { + return m_impl.get(); + } + + void swap(ServiceHandle& other) LIBSERVICE_NOEXCEPT + { + using std::swap; + swap(m_impl, other.m_impl); + } + + private: + struct Deleter + { + void operator()(SC_HANDLE raw) + { + ::CloseServiceHandle(raw); + } + }; + + std::unique_ptr m_impl; + + ServiceHandle(const ServiceHandle&) = delete; + }; + + void swap(ServiceHandle&, ServiceHandle&) LIBSERVICE_NOEXCEPT; +} + +namespace std +{ + template <> + void swap( + libservice::ServiceHandle&, + libservice::ServiceHandle&) LIBSERVICE_NOEXCEPT; +} diff --git a/utils/libservice/include/libservice/service_manager.hpp b/utils/libservice/include/libservice/service_manager.hpp new file mode 100644 index 0000000..a2bb268 --- /dev/null +++ b/utils/libservice/include/libservice/service_manager.hpp @@ -0,0 +1,65 @@ +/** + * \author Egor Tensin + * \date 2015 + * \copyright This file is licensed under the terms of the MIT License. + * See LICENSE.txt for details. + */ + +#pragma once + +#include "common.hpp" +#include "service_handle.hpp" + +#include + +#include + +namespace libservice +{ + class ServiceManager + { + public: + static ServiceManager open(); + + ServiceManager(ServiceManager&& other) LIBSERVICE_NOEXCEPT + { + swap(other); + } + + ServiceManager& operator=(ServiceManager other) LIBSERVICE_NOEXCEPT + { + swap(other); + return *this; + } + + void swap(ServiceManager& other) LIBSERVICE_NOEXCEPT + { + using std::swap; + swap(m_handle, other.m_handle); + } + + explicit operator SC_HANDLE() const + { + return static_cast(m_handle); + } + + private: + explicit ServiceManager(ServiceHandle h) + : m_handle(std::move(h)) + { } + + ServiceHandle m_handle; + + ServiceManager(const ServiceManager&) = delete; + }; + + void swap(ServiceManager& a, ServiceManager& b) LIBSERVICE_NOEXCEPT; +} + +namespace std +{ + template <> + void swap( + libservice::ServiceManager&, + libservice::ServiceManager&) LIBSERVICE_NOEXCEPT; +} diff --git a/utils/libservice/include/libservice/singleton.hpp b/utils/libservice/include/libservice/singleton.hpp new file mode 100644 index 0000000..bd137e7 --- /dev/null +++ b/utils/libservice/include/libservice/singleton.hpp @@ -0,0 +1,45 @@ +/** + * \author Egor Tensin + * \date 2015 + * \copyright This file is licensed under the terms of the MIT License. + * See LICENSE.txt for details. + */ + +#pragma once + +#include + +namespace libservice +{ + template + class Singleton + { + public: + static DerivedType& get() + { + std::call_once(is_initialized, initialize); + return get_unsafe(); + } + + protected: + Singleton() = default; + virtual ~Singleton() = default; + + private: + static void initialize() + { + get_unsafe(); + } + + static DerivedType& get_unsafe() + { + static DerivedType instance; + return instance; + } + + static std::once_flag is_initialized; + }; + + template + std::once_flag Singleton::is_initialized; +} diff --git a/utils/libservice/include/libservice/windows_error.hpp b/utils/libservice/include/libservice/windows_error.hpp new file mode 100644 index 0000000..57730d5 --- /dev/null +++ b/utils/libservice/include/libservice/windows_error.hpp @@ -0,0 +1,36 @@ +/** + * \author Egor Tensin + * \date 2015 + * \copyright This file is licensed under the terms of the MIT License. + * See LICENSE.txt for details. + */ + +#pragma once + +#include "common.hpp" +#include "singleton.hpp" + +#include +#include + +namespace libservice +{ + class WinErrorCategory : public std::error_category + , public Singleton + { + public: + const char* name() const LIBSERVICE_NOEXCEPT { return "windows"; } + + std::string message(int) const; + + private: + friend class Singleton; + }; +} + +#define LIBSERVICE_ERROR_PREFIX "Error in function '" \ + LIBSERVICE_FUNCTION_NAME \ + "' at file '" \ + LIBSERVICE_FILE_PATH \ + "', line " \ + LIBSERVICE_LINE_NUMBER_STRING diff --git a/utils/libservice/src/service.cpp b/utils/libservice/src/service.cpp new file mode 100644 index 0000000..1f7cbef --- /dev/null +++ b/utils/libservice/src/service.cpp @@ -0,0 +1,235 @@ +/** + * \file + * \author Egor Tensin + * \date 2015 + * \copyright This file is licensed under the terms of the MIT License. + * See LICENSE.txt for details. + */ + +#include "libservice/common.hpp" +#include "libservice/service.hpp" +#include "libservice/service_handle.hpp" +#include "libservice/service_manager.hpp" +#include "libservice/windows_error.hpp" + +#include + +#include +#include +#include + +namespace libservice +{ + namespace + { + ServiceHandle open_service(const ServiceManager& mgr, const std::string& name) + { + const auto raw = OpenServiceA( + static_cast(mgr), + name.c_str(), + SERVICE_ALL_ACCESS); + + if (NULL == raw) + { + const auto ec = GetLastError(); + throw std::system_error(ec, WinErrorCategory::get(), LIBSERVICE_ERROR_PREFIX); + } + + return ServiceHandle(raw); + } + + ServiceHandle install_service( + const ServiceManager& mgr, + const std::string& name, + const std::string& bin_path) + { + const auto raw = CreateServiceA( + static_cast(mgr), + name.c_str(), + name.c_str(), + SERVICE_ALL_ACCESS, + SERVICE_KERNEL_DRIVER, + SERVICE_DEMAND_START, + SERVICE_ERROR_NORMAL, + bin_path.c_str(), + NULL, + NULL, + NULL, + NULL, + NULL); + + if (NULL == raw) + { + const auto ec = GetLastError(); + throw std::system_error(ec, WinErrorCategory::get(), LIBSERVICE_ERROR_PREFIX); + } + + return ServiceHandle(raw); + } + + void start_service(const ServiceHandle& handle) + { + if (!StartService(static_cast(handle), 0, NULL)) + { + const auto ec = GetLastError(); + throw std::system_error(ec, WinErrorCategory::get(), LIBSERVICE_ERROR_PREFIX); + } + } + + void stop_service(const ServiceHandle& handle) + { + SERVICE_STATUS service_status; + + if (!ControlService(static_cast(handle), SERVICE_CONTROL_STOP, &service_status)) + { + const auto ec = GetLastError(); + throw std::system_error(ec, WinErrorCategory::get(), LIBSERVICE_ERROR_PREFIX); + } + } + + void uninstall_service(const ServiceHandle& handle) + { + if (!DeleteService(static_cast(handle))) + { + const auto ec = GetLastError(); + throw std::system_error(ec, WinErrorCategory::get(), LIBSERVICE_ERROR_PREFIX); + } + } + + SERVICE_STATUS_PROCESS query_service_status(const ServiceHandle& handle) + { + SERVICE_STATUS_PROCESS status; + DWORD nbreq; + + if (!QueryServiceStatusEx(static_cast(handle), + SC_STATUS_PROCESS_INFO, + reinterpret_cast(&status), + sizeof(status), + &nbreq)) + { + const auto ec = GetLastError(); + throw std::system_error(ec, WinErrorCategory::get(), LIBSERVICE_ERROR_PREFIX); + } + + return status; + } + + DWORD query_service_state(const ServiceHandle& handle) + { + return query_service_status(handle).dwCurrentState; + } + + SERVICE_STATUS_PROCESS wait_for_service_state( + const ServiceHandle& handle, + const DWORD desired_state) + { + auto status = query_service_status(handle); + + DWORD old_timestamp = GetTickCount(); + + DWORD old_check_point = status.dwCheckPoint; + DWORD old_wait_hint = status.dwWaitHint; + + while (desired_state != status.dwCurrentState) + { + DWORD wait_time = old_wait_hint / 10; + + if (wait_time < 1000) + wait_time = 1000; + else if (wait_time > 10000) + wait_time = 10000; + + Sleep(wait_time); + + status = query_service_status(handle); + + if (desired_state == status.dwCurrentState) + break; + + if (status.dwCheckPoint > old_check_point) + { + old_timestamp = GetTickCount(); + + old_check_point = status.dwCheckPoint; + old_wait_hint = status.dwWaitHint; + } + else if (GetTickCount() - old_timestamp > old_wait_hint) + { + return status; + } + } + return status; + } + } + + Service Service::open(const ServiceManager& mgr, const std::string& name) + { + return Service(open_service(mgr, name)); + } + + Service Service::install(const ServiceManager& mgr, + const std::string& name, + const std::string& bin_path) + { + return Service(install_service(mgr, name, bin_path)); + } + + void Service::start() const + { + const auto state = query_service_state(m_handle); + + switch (state) + { + case SERVICE_STOPPED: + break; + + case SERVICE_STOP_PENDING: + wait_for_service_state(m_handle, SERVICE_STOPPED); + break; + + default: + return; + } + + start_service(m_handle); + wait_for_service_state(m_handle, SERVICE_RUNNING); + } + + void Service::stop() const + { + switch (query_service_state(m_handle)) + { + case SERVICE_STOPPED: + return; + + case SERVICE_STOP_PENDING: + wait_for_service_state(m_handle, SERVICE_STOPPED); + return; + } + + stop_service(m_handle); + wait_for_service_state(m_handle, SERVICE_STOPPED); + } + + void Service::uninstall() const + { + stop(); + uninstall_service(m_handle); + } + + void swap(Service& a, Service& b) LIBSERVICE_NOEXCEPT + { + a.swap(b); + } +} + +namespace std +{ + template <> + void swap( + libservice::Service& a, + libservice::Service& b) LIBSERVICE_NOEXCEPT + { + a.swap(b); + } +} diff --git a/utils/libservice/src/service_handle.cpp b/utils/libservice/src/service_handle.cpp new file mode 100644 index 0000000..5356b50 --- /dev/null +++ b/utils/libservice/src/service_handle.cpp @@ -0,0 +1,30 @@ +/** + * \author Egor Tensin + * \date 2015 + * \copyright This file is licensed under the terms of the MIT License. + * See LICENSE.txt for details. + */ + +#include "libservice/common.hpp" +#include "libservice/service_handle.hpp" + +#include + +namespace libservice +{ + void swap(ServiceHandle& a, ServiceHandle& b) LIBSERVICE_NOEXCEPT + { + a.swap(b); + } +} + +namespace std +{ + template <> + void swap( + libservice::ServiceHandle& a, + libservice::ServiceHandle& b) LIBSERVICE_NOEXCEPT + { + a.swap(b); + } +} diff --git a/utils/libservice/src/service_manager.cpp b/utils/libservice/src/service_manager.cpp new file mode 100644 index 0000000..4888406 --- /dev/null +++ b/utils/libservice/src/service_manager.cpp @@ -0,0 +1,48 @@ +/** + * \author Egor Tensin + * \date 2015 + * \copyright This file is licensed under the terms of the MIT License. + * See LICENSE.txt for details. + */ + +#include "libservice/common.hpp" +#include "libservice/service_handle.hpp" +#include "libservice/service_manager.hpp" +#include "libservice/windows_error.hpp" + +#include + +#include +#include + +namespace libservice +{ + ServiceManager ServiceManager::open() + { + SC_HANDLE raw = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); + + if (NULL == raw) + { + const auto ec = GetLastError(); + throw std::system_error(ec, WinErrorCategory::get()); + } + + return ServiceManager(ServiceHandle(raw)); + } + + void swap(ServiceManager& a, ServiceManager& b) LIBSERVICE_NOEXCEPT + { + a.swap(b); + } +} + +namespace std +{ + template <> + void swap( + libservice::ServiceManager& a, + libservice::ServiceManager& b) LIBSERVICE_NOEXCEPT + { + a.swap(b); + } +} diff --git a/utils/libservice/src/windows_error.cpp b/utils/libservice/src/windows_error.cpp new file mode 100644 index 0000000..6cc7302 --- /dev/null +++ b/utils/libservice/src/windows_error.cpp @@ -0,0 +1,41 @@ +/** + * \author Egor Tensin + * \date 2015 + * \copyright This file is licensed under the terms of the MIT License. + * See LICENSE.txt for details. + */ + +#include "libservice/windows_error.hpp" + +#include + +#include + +namespace libservice +{ + std::string WinErrorCategory::message(int code) const + { + char* buf_ptr; + + DWORD written = FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + code, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&buf_ptr), + 0, + NULL); + + if (0 == written) + { + LocalFree(buf_ptr); + return "Couldn't format error message"; + } + + std::string str(buf_ptr, written - 2); + LocalFree(buf_ptr); + return str; + } +} diff --git a/utils/libservice/test/CMakeLists.txt b/utils/libservice/test/CMakeLists.txt new file mode 100644 index 0000000..2cd656a --- /dev/null +++ b/utils/libservice/test/CMakeLists.txt @@ -0,0 +1,5 @@ +project(libservice_test) + +project(libservice_test_windows_error) +add_executable(${PROJECT_NAME} windows_error.cpp) +target_link_libraries(${PROJECT_NAME} libservice) diff --git a/utils/libservice/test/windows_error.cpp b/utils/libservice/test/windows_error.cpp new file mode 100644 index 0000000..f165681 --- /dev/null +++ b/utils/libservice/test/windows_error.cpp @@ -0,0 +1,27 @@ +/** + * \author Egor Tensin + * \date 2015 + * \copyright This file is licensed under the terms of the MIT License. + * See LICENSE.txt for details. + */ + +#include "libservice/interface.hpp" + +#include + +#include +#include + +int main() +{ + try + { + throw std::system_error(ERROR_FILE_NOT_FOUND, libservice::WinErrorCategory::get(), LIBSERVICE_ERROR_PREFIX); + } + catch (const std::system_error& e) + { + std::cerr << e.what() << "\n"; + return -1; + } + return 0; +} -- cgit v1.2.3