From 1b9b121650d5852bc5781f1f7db149411c9d673c Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Tue, 27 Aug 2024 17:09:55 +0200 Subject: zen `service` command --- src/zenutil/service.cpp | 393 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 393 insertions(+) create mode 100644 src/zenutil/service.cpp (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp new file mode 100644 index 000000000..840fe3104 --- /dev/null +++ b/src/zenutil/service.cpp @@ -0,0 +1,393 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include + +#include +#include +#include + +#if ZEN_PLATFORM_WINDOWS +# include +#endif + +namespace zen { +using namespace std::literals; + +std::string_view +ToString(ServiceStatus Status) +{ + switch (Status) + { + case ServiceStatus::NotInstalled: + return "Not installed"sv; + case ServiceStatus::Starting: + return "Starting"sv; + case ServiceStatus::Running: + return "Running"sv; + case ServiceStatus::Stopping: + return "Stopping"sv; + case ServiceStatus::Stopped: + return "Stopped"sv; + case ServiceStatus::Pausing: + return "Pausing"sv; + case ServiceStatus::Paused: + return "Paused"sv; + case ServiceStatus::Resuming: + return "Resuming"sv; + default: + ZEN_ASSERT(false); + return ""sv; + } +} + +#if ZEN_PLATFORM_WINDOWS + +std::error_code +InstallService(const std::filesystem::path& ExecutablePath, + std::string_view CommandLineOptions, + std::string_view ServiceName, + std::string_view ServiceDisplayName, + std::string_view ServiceDescription) +{ + // Get a handle to the SCM database. + + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_ALL_ACCESS); // full access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Create the service + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + ExtendableWideStringBuilder<128> DisplayName; + Utf8ToWide(ServiceDisplayName, DisplayName); + + ExtendableWideStringBuilder<128> Path; + Path.Append(ExecutablePath.c_str()); + if (!CommandLineOptions.empty()) + { + Path.AppendAscii(" "); + Utf8ToWide(CommandLineOptions, Path); + } + + SC_HANDLE schService = CreateService(schSCManager, // SCM database + Name.c_str(), // name of service + DisplayName.c_str(), // service name to display + SERVICE_ALL_ACCESS, // desired access + SERVICE_WIN32_OWN_PROCESS, // service type + SERVICE_DEMAND_START, // start type + SERVICE_ERROR_NORMAL, // error control type + Path.c_str(), // path to service's binary + NULL, // no load ordering group + NULL, // no tag identifier + NULL, // no dependencies + NULL, // LocalSystem account + NULL); // no password + + if (schService == NULL) + { + return MakeErrorCodeFromLastError(); + } + + if (!ServiceDescription.empty()) + { + ExtendableWideStringBuilder<128> DescriptionBuilder; + Utf8ToWide(ServiceDescription, DescriptionBuilder); + + SERVICE_DESCRIPTION Description; + Description.lpDescription = const_cast(DescriptionBuilder.c_str()); + if (!ChangeServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, &Description)) + { + return MakeErrorCodeFromLastError(); + } + } + + CloseServiceHandle(schService); + + return {}; +} + +std::error_code +UninstallService(std::string_view ServiceName) +{ + // Get a handle to the SCM database. + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_ALL_ACCESS); // full access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Get a handle to the service. + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + SC_HANDLE schService = OpenService(schSCManager, // SCM database + Name.c_str(), // name of service + DELETE); // need delete access + + if (schService == NULL) + { + DWORD Error = ::GetLastError(); + if (Error == ERROR_SERVICE_DOES_NOT_EXIST) + { + return {}; + } + return MakeErrorCode(Error); + } + auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); + + // Delete the service. + + if (!DeleteService(schService)) + { + return MakeErrorCodeFromLastError(); + } + + return {}; +} + +std::error_code +QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) +{ + // Get a handle to the SCM database. + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_CONNECT); // standard access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Get a handle to the service. + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + SC_HANDLE schService = OpenService(schSCManager, // SCM database + Name.c_str(), // name of service + SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); // need delete access + + if (schService == NULL) + { + DWORD Error = ::GetLastError(); + if (Error == ERROR_SERVICE_DOES_NOT_EXIST) + { + OutInfo.Status = ServiceStatus::NotInstalled; + return {}; + } + return MakeErrorCode(Error); + } + auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); + + std::vector Buffer(8192); + QUERY_SERVICE_CONFIG* ServiceConfig = reinterpret_cast(Buffer.data()); + DWORD BytesNeeded = 0; + if (!QueryServiceConfig(schService, ServiceConfig, (DWORD)Buffer.size(), &BytesNeeded)) + { + return MakeErrorCodeFromLastError(); + } + + OutInfo.ExecutablePath = std::filesystem::path(ServiceConfig->lpBinaryPathName); + OutInfo.DisplayName = WideToUtf8(ServiceConfig->lpDisplayName); + + SERVICE_STATUS ServiceStatus; + if (!::QueryServiceStatus(schService, &ServiceStatus)) + { + return MakeErrorCodeFromLastError(); + } + + switch (ServiceStatus.dwCurrentState) + { + case SERVICE_STOPPED: + OutInfo.Status = ServiceStatus::Stopped; + break; + case SERVICE_START_PENDING: + OutInfo.Status = ServiceStatus::Starting; + break; + case SERVICE_STOP_PENDING: + OutInfo.Status = ServiceStatus::Stopping; + break; + case SERVICE_RUNNING: + OutInfo.Status = ServiceStatus::Running; + break; + case SERVICE_CONTINUE_PENDING: + OutInfo.Status = ServiceStatus::Resuming; + break; + case SERVICE_PAUSE_PENDING: + OutInfo.Status = ServiceStatus::Pausing; + break; + case SERVICE_PAUSED: + OutInfo.Status = ServiceStatus::Paused; + break; + default: + throw std::runtime_error(fmt::format("Unknown service status for '{}': {}", ServiceName, ServiceStatus.dwCurrentState)); + } + + if (!QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, Buffer.data(), (DWORD)Buffer.size(), &BytesNeeded)) + { + DWORD Error = ::GetLastError(); + if (Error == ERROR_INSUFFICIENT_BUFFER) + { + Buffer.resize((size_t)BytesNeeded); + if (!QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, Buffer.data(), (DWORD)Buffer.size(), &BytesNeeded)) + { + return MakeErrorCodeFromLastError(); + } + } + else + { + return MakeErrorCode(Error); + } + } + SERVICE_DESCRIPTION* Description = (SERVICE_DESCRIPTION*)Buffer.data(); + if (Description->lpDescription != NULL) + { + OutInfo.Description = WideToUtf8(std::wstring(Description->lpDescription)); + } + + return {}; +} + +std::error_code +StartService(std::string_view ServiceName) +{ + // Get a handle to the SCM database. + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_CONNECT); // default access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Get a handle to the service. + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + SC_HANDLE schService = OpenService(schSCManager, // SCM database + Name.c_str(), // name of service + SERVICE_START); // need start access + + if (schService == NULL) + { + return MakeErrorCodeFromLastError(); + } + auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); + + // Start the service. + + if (!::StartService(schService, 0, NULL)) + { + return MakeErrorCodeFromLastError(); + } + + return {}; +} + +std::error_code +StopService(std::string_view ServiceName) +{ + // Get a handle to the SCM database. + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_CONNECT); // default access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Get a handle to the service. + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + SC_HANDLE schService = OpenService(schSCManager, // SCM database + Name.c_str(), // name of service + SERVICE_STOP); // need start access + + if (schService == NULL) + { + return MakeErrorCodeFromLastError(); + } + auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); + + // Stop the service. + SERVICE_STATUS ServiceStatus; + if (!::ControlService(schService, SERVICE_CONTROL_STOP, &ServiceStatus)) + { + return MakeErrorCodeFromLastError(); + } + + return {}; +} +#else +std::error_code +InstallService(const std::filesystem::path& ExecutablePath, + std::string_view CommandLineOptions, + std::string_view ServiceName, + std::string_view ServiceDisplayName, + std::string_view ServiceDescription) +{ + ZEN_UNUSED(ExecutablePath, CommandLineOptions, ServiceName, ServiceDisplayName, ServiceDescription); + ZEN_NOT_IMPLEMENTED("InstallService"); + return {}; +} + +std::error_code +UninstallService(std::string_view ServiceName) +{ + ZEN_UNUSED(ServiceName); + ZEN_NOT_IMPLEMENTED("UninstallService"); + return {}; +} + +std::error_code +QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutStatus) +{ + ZEN_UNUSED(ServiceName, OutStatus); + ZEN_NOT_IMPLEMENTED("QueryInstalledService"); + return {}; +} + +std::error_code +StartService(std::string_view ServiceName) +{ + ZEN_UNUSED(ServiceName); + ZEN_NOT_IMPLEMENTED("StartService"); + return {}; +} + +std::error_code +StopService(std::string_view ServiceName) +{ + ZEN_UNUSED(ServiceName); + ZEN_NOT_IMPLEMENTED("StopService"); + return {}; +} + +#endif +} // namespace zen -- cgit v1.2.3 From 8a2f7c0c7863f587113cdb77f51d622dbcb737aa Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Wed, 28 Aug 2024 10:47:49 +0200 Subject: wip --- src/zenutil/service.cpp | 753 ++++++++++++++++++++++++++++-------------------- 1 file changed, 433 insertions(+), 320 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index 840fe3104..cf0c8ef50 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -16,378 +16,491 @@ using namespace std::literals; std::string_view ToString(ServiceStatus Status) { - switch (Status) - { - case ServiceStatus::NotInstalled: - return "Not installed"sv; - case ServiceStatus::Starting: - return "Starting"sv; - case ServiceStatus::Running: - return "Running"sv; - case ServiceStatus::Stopping: - return "Stopping"sv; - case ServiceStatus::Stopped: - return "Stopped"sv; - case ServiceStatus::Pausing: - return "Pausing"sv; - case ServiceStatus::Paused: - return "Paused"sv; - case ServiceStatus::Resuming: - return "Resuming"sv; - default: - ZEN_ASSERT(false); - return ""sv; - } + switch (Status) + { + case ServiceStatus::NotInstalled: + return "Not installed"sv; + case ServiceStatus::Starting: + return "Starting"sv; + case ServiceStatus::Running: + return "Running"sv; + case ServiceStatus::Stopping: + return "Stopping"sv; + case ServiceStatus::Stopped: + return "Stopped"sv; + case ServiceStatus::Pausing: + return "Pausing"sv; + case ServiceStatus::Paused: + return "Paused"sv; + case ServiceStatus::Resuming: + return "Resuming"sv; + default: + ZEN_ASSERT(false); + return ""sv; + } } #if ZEN_PLATFORM_WINDOWS std::error_code InstallService(const std::filesystem::path& ExecutablePath, - std::string_view CommandLineOptions, - std::string_view ServiceName, - std::string_view ServiceDisplayName, - std::string_view ServiceDescription) + std::string_view CommandLineOptions, + std::string_view ServiceName, + std::string_view ServiceDisplayName, + std::string_view ServiceDescription) { - // Get a handle to the SCM database. - - SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer - NULL, // ServicesActive database - SC_MANAGER_ALL_ACCESS); // full access rights - - if (NULL == schSCManager) - { - return MakeErrorCodeFromLastError(); - } - - auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); - - // Create the service - - ExtendableWideStringBuilder<128> Name; - Utf8ToWide(ServiceName, Name); - - ExtendableWideStringBuilder<128> DisplayName; - Utf8ToWide(ServiceDisplayName, DisplayName); - - ExtendableWideStringBuilder<128> Path; - Path.Append(ExecutablePath.c_str()); - if (!CommandLineOptions.empty()) - { - Path.AppendAscii(" "); - Utf8ToWide(CommandLineOptions, Path); - } - - SC_HANDLE schService = CreateService(schSCManager, // SCM database - Name.c_str(), // name of service - DisplayName.c_str(), // service name to display - SERVICE_ALL_ACCESS, // desired access - SERVICE_WIN32_OWN_PROCESS, // service type - SERVICE_DEMAND_START, // start type - SERVICE_ERROR_NORMAL, // error control type - Path.c_str(), // path to service's binary - NULL, // no load ordering group - NULL, // no tag identifier - NULL, // no dependencies - NULL, // LocalSystem account - NULL); // no password - - if (schService == NULL) - { - return MakeErrorCodeFromLastError(); - } - - if (!ServiceDescription.empty()) - { - ExtendableWideStringBuilder<128> DescriptionBuilder; - Utf8ToWide(ServiceDescription, DescriptionBuilder); - - SERVICE_DESCRIPTION Description; - Description.lpDescription = const_cast(DescriptionBuilder.c_str()); - if (!ChangeServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, &Description)) - { - return MakeErrorCodeFromLastError(); - } - } - - CloseServiceHandle(schService); - - return {}; + // Get a handle to the SCM database. + + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_ALL_ACCESS); // full access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Create the service + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + ExtendableWideStringBuilder<128> DisplayName; + Utf8ToWide(ServiceDisplayName, DisplayName); + + ExtendableWideStringBuilder<128> Path; + Path.Append(ExecutablePath.c_str()); + if (!CommandLineOptions.empty()) + { + Path.AppendAscii(" "); + Utf8ToWide(CommandLineOptions, Path); + } + + SC_HANDLE schService = CreateService(schSCManager, // SCM database + Name.c_str(), // name of service + DisplayName.c_str(), // service name to display + SERVICE_ALL_ACCESS, // desired access + SERVICE_WIN32_OWN_PROCESS, // service type + SERVICE_DEMAND_START, // start type + SERVICE_ERROR_NORMAL, // error control type + Path.c_str(), // path to service's binary + NULL, // no load ordering group + NULL, // no tag identifier + NULL, // no dependencies + NULL, // LocalSystem account + NULL); // no password + + if (schService == NULL) + { + return MakeErrorCodeFromLastError(); + } + + if (!ServiceDescription.empty()) + { + ExtendableWideStringBuilder<128> DescriptionBuilder; + Utf8ToWide(ServiceDescription, DescriptionBuilder); + + SERVICE_DESCRIPTION Description; + Description.lpDescription = const_cast(DescriptionBuilder.c_str()); + if (!ChangeServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, &Description)) + { + return MakeErrorCodeFromLastError(); + } + } + + CloseServiceHandle(schService); + + return {}; } std::error_code UninstallService(std::string_view ServiceName) { - // Get a handle to the SCM database. - SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer - NULL, // ServicesActive database - SC_MANAGER_ALL_ACCESS); // full access rights - - if (NULL == schSCManager) - { - return MakeErrorCodeFromLastError(); - } - - auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); - - // Get a handle to the service. - - ExtendableWideStringBuilder<128> Name; - Utf8ToWide(ServiceName, Name); - - SC_HANDLE schService = OpenService(schSCManager, // SCM database - Name.c_str(), // name of service - DELETE); // need delete access - - if (schService == NULL) - { - DWORD Error = ::GetLastError(); - if (Error == ERROR_SERVICE_DOES_NOT_EXIST) - { - return {}; - } - return MakeErrorCode(Error); - } - auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); - - // Delete the service. - - if (!DeleteService(schService)) - { - return MakeErrorCodeFromLastError(); - } - - return {}; + // Get a handle to the SCM database. + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_ALL_ACCESS); // full access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Get a handle to the service. + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + SC_HANDLE schService = OpenService(schSCManager, // SCM database + Name.c_str(), // name of service + DELETE); // need delete access + + if (schService == NULL) + { + DWORD Error = ::GetLastError(); + if (Error == ERROR_SERVICE_DOES_NOT_EXIST) + { + return {}; + } + return MakeErrorCode(Error); + } + auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); + + // Delete the service. + + if (!DeleteService(schService)) + { + return MakeErrorCodeFromLastError(); + } + + return {}; } std::error_code QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) { - // Get a handle to the SCM database. - SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer - NULL, // ServicesActive database - SC_MANAGER_CONNECT); // standard access rights - - if (NULL == schSCManager) - { - return MakeErrorCodeFromLastError(); - } - - auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); - - // Get a handle to the service. - - ExtendableWideStringBuilder<128> Name; - Utf8ToWide(ServiceName, Name); - - SC_HANDLE schService = OpenService(schSCManager, // SCM database - Name.c_str(), // name of service - SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); // need delete access - - if (schService == NULL) - { - DWORD Error = ::GetLastError(); - if (Error == ERROR_SERVICE_DOES_NOT_EXIST) - { - OutInfo.Status = ServiceStatus::NotInstalled; - return {}; - } - return MakeErrorCode(Error); - } - auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); - - std::vector Buffer(8192); - QUERY_SERVICE_CONFIG* ServiceConfig = reinterpret_cast(Buffer.data()); - DWORD BytesNeeded = 0; - if (!QueryServiceConfig(schService, ServiceConfig, (DWORD)Buffer.size(), &BytesNeeded)) - { - return MakeErrorCodeFromLastError(); - } - - OutInfo.ExecutablePath = std::filesystem::path(ServiceConfig->lpBinaryPathName); - OutInfo.DisplayName = WideToUtf8(ServiceConfig->lpDisplayName); - - SERVICE_STATUS ServiceStatus; - if (!::QueryServiceStatus(schService, &ServiceStatus)) - { - return MakeErrorCodeFromLastError(); - } - - switch (ServiceStatus.dwCurrentState) - { - case SERVICE_STOPPED: - OutInfo.Status = ServiceStatus::Stopped; - break; - case SERVICE_START_PENDING: - OutInfo.Status = ServiceStatus::Starting; - break; - case SERVICE_STOP_PENDING: - OutInfo.Status = ServiceStatus::Stopping; - break; - case SERVICE_RUNNING: - OutInfo.Status = ServiceStatus::Running; - break; - case SERVICE_CONTINUE_PENDING: - OutInfo.Status = ServiceStatus::Resuming; - break; - case SERVICE_PAUSE_PENDING: - OutInfo.Status = ServiceStatus::Pausing; - break; - case SERVICE_PAUSED: - OutInfo.Status = ServiceStatus::Paused; - break; - default: - throw std::runtime_error(fmt::format("Unknown service status for '{}': {}", ServiceName, ServiceStatus.dwCurrentState)); - } - - if (!QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, Buffer.data(), (DWORD)Buffer.size(), &BytesNeeded)) - { - DWORD Error = ::GetLastError(); - if (Error == ERROR_INSUFFICIENT_BUFFER) - { - Buffer.resize((size_t)BytesNeeded); - if (!QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, Buffer.data(), (DWORD)Buffer.size(), &BytesNeeded)) - { - return MakeErrorCodeFromLastError(); - } - } - else - { - return MakeErrorCode(Error); - } - } - SERVICE_DESCRIPTION* Description = (SERVICE_DESCRIPTION*)Buffer.data(); - if (Description->lpDescription != NULL) - { - OutInfo.Description = WideToUtf8(std::wstring(Description->lpDescription)); - } - - return {}; + // Get a handle to the SCM database. + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_CONNECT); // standard access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Get a handle to the service. + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + SC_HANDLE schService = OpenService(schSCManager, // SCM database + Name.c_str(), // name of service + SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); // need delete access + + if (schService == NULL) + { + DWORD Error = ::GetLastError(); + if (Error == ERROR_SERVICE_DOES_NOT_EXIST) + { + OutInfo.Status = ServiceStatus::NotInstalled; + return {}; + } + return MakeErrorCode(Error); + } + auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); + + std::vector Buffer(8192); + QUERY_SERVICE_CONFIG* ServiceConfig = reinterpret_cast(Buffer.data()); + DWORD BytesNeeded = 0; + if (!QueryServiceConfig(schService, ServiceConfig, (DWORD)Buffer.size(), &BytesNeeded)) + { + return MakeErrorCodeFromLastError(); + } + + OutInfo.ExecutablePath = std::filesystem::path(ServiceConfig->lpBinaryPathName); + OutInfo.DisplayName = WideToUtf8(ServiceConfig->lpDisplayName); + + SERVICE_STATUS ServiceStatus; + if (!::QueryServiceStatus(schService, &ServiceStatus)) + { + return MakeErrorCodeFromLastError(); + } + + switch (ServiceStatus.dwCurrentState) + { + case SERVICE_STOPPED: + OutInfo.Status = ServiceStatus::Stopped; + break; + case SERVICE_START_PENDING: + OutInfo.Status = ServiceStatus::Starting; + break; + case SERVICE_STOP_PENDING: + OutInfo.Status = ServiceStatus::Stopping; + break; + case SERVICE_RUNNING: + OutInfo.Status = ServiceStatus::Running; + break; + case SERVICE_CONTINUE_PENDING: + OutInfo.Status = ServiceStatus::Resuming; + break; + case SERVICE_PAUSE_PENDING: + OutInfo.Status = ServiceStatus::Pausing; + break; + case SERVICE_PAUSED: + OutInfo.Status = ServiceStatus::Paused; + break; + default: + throw std::runtime_error(fmt::format("Unknown service status for '{}': {}", ServiceName, ServiceStatus.dwCurrentState)); + } + + if (!QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, Buffer.data(), (DWORD)Buffer.size(), &BytesNeeded)) + { + DWORD Error = ::GetLastError(); + if (Error == ERROR_INSUFFICIENT_BUFFER) + { + Buffer.resize((size_t)BytesNeeded); + if (!QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, Buffer.data(), (DWORD)Buffer.size(), &BytesNeeded)) + { + return MakeErrorCodeFromLastError(); + } + } + else + { + return MakeErrorCode(Error); + } + } + SERVICE_DESCRIPTION* Description = (SERVICE_DESCRIPTION*)Buffer.data(); + if (Description->lpDescription != NULL) + { + OutInfo.Description = WideToUtf8(std::wstring(Description->lpDescription)); + } + + return {}; } std::error_code StartService(std::string_view ServiceName) { - // Get a handle to the SCM database. - SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer - NULL, // ServicesActive database - SC_MANAGER_CONNECT); // default access rights - - if (NULL == schSCManager) - { - return MakeErrorCodeFromLastError(); - } - - auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); - - // Get a handle to the service. - - ExtendableWideStringBuilder<128> Name; - Utf8ToWide(ServiceName, Name); - - SC_HANDLE schService = OpenService(schSCManager, // SCM database - Name.c_str(), // name of service - SERVICE_START); // need start access - - if (schService == NULL) - { - return MakeErrorCodeFromLastError(); - } - auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); - - // Start the service. - - if (!::StartService(schService, 0, NULL)) - { - return MakeErrorCodeFromLastError(); - } - - return {}; + // Get a handle to the SCM database. + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_CONNECT); // default access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Get a handle to the service. + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + SC_HANDLE schService = OpenService(schSCManager, // SCM database + Name.c_str(), // name of service + SERVICE_START); // need start access + + if (schService == NULL) + { + return MakeErrorCodeFromLastError(); + } + auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); + + // Start the service. + + if (!::StartService(schService, 0, NULL)) + { + return MakeErrorCodeFromLastError(); + } + + return {}; } std::error_code StopService(std::string_view ServiceName) { - // Get a handle to the SCM database. - SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer - NULL, // ServicesActive database - SC_MANAGER_CONNECT); // default access rights - - if (NULL == schSCManager) - { - return MakeErrorCodeFromLastError(); - } - - auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); - - // Get a handle to the service. - - ExtendableWideStringBuilder<128> Name; - Utf8ToWide(ServiceName, Name); - - SC_HANDLE schService = OpenService(schSCManager, // SCM database - Name.c_str(), // name of service - SERVICE_STOP); // need start access - - if (schService == NULL) - { - return MakeErrorCodeFromLastError(); - } - auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); - - // Stop the service. - SERVICE_STATUS ServiceStatus; - if (!::ControlService(schService, SERVICE_CONTROL_STOP, &ServiceStatus)) - { - return MakeErrorCodeFromLastError(); - } - - return {}; + // Get a handle to the SCM database. + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_CONNECT); // default access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Get a handle to the service. + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + SC_HANDLE schService = OpenService(schSCManager, // SCM database + Name.c_str(), // name of service + SERVICE_STOP); // need start access + + if (schService == NULL) + { + return MakeErrorCodeFromLastError(); + } + auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); + + // Stop the service. + SERVICE_STATUS ServiceStatus; + if (!::ControlService(schService, SERVICE_CONTROL_STOP, &ServiceStatus)) + { + return MakeErrorCodeFromLastError(); + } + + return {}; } #else std::error_code InstallService(const std::filesystem::path& ExecutablePath, - std::string_view CommandLineOptions, - std::string_view ServiceName, - std::string_view ServiceDisplayName, - std::string_view ServiceDescription) + std::string_view CommandLineOptions, + std::string_view ServiceName, + std::string_view ServiceDisplayName, + std::string_view ServiceDescription) { - ZEN_UNUSED(ExecutablePath, CommandLineOptions, ServiceName, ServiceDisplayName, ServiceDescription); - ZEN_NOT_IMPLEMENTED("InstallService"); - return {}; + ZEN_UNUSED(ExecutablePath, CommandLineOptions, ServiceName, ServiceDisplayName, ServiceDescription); + ZEN_NOT_IMPLEMENTED("InstallService"); + return {}; } std::error_code UninstallService(std::string_view ServiceName) { - ZEN_UNUSED(ServiceName); - ZEN_NOT_IMPLEMENTED("UninstallService"); - return {}; + ZEN_UNUSED(ServiceName); + ZEN_NOT_IMPLEMENTED("UninstallService"); + return {}; } std::error_code QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutStatus) { - ZEN_UNUSED(ServiceName, OutStatus); - ZEN_NOT_IMPLEMENTED("QueryInstalledService"); - return {}; + ZEN_UNUSED(ServiceName, OutStatus); + ZEN_NOT_IMPLEMENTED("QueryInstalledService"); + return {}; } std::error_code StartService(std::string_view ServiceName) { - ZEN_UNUSED(ServiceName); - ZEN_NOT_IMPLEMENTED("StartService"); - return {}; + ZEN_UNUSED(ServiceName); + ZEN_NOT_IMPLEMENTED("StartService"); + return {}; } std::error_code StopService(std::string_view ServiceName) { - ZEN_UNUSED(ServiceName); - ZEN_NOT_IMPLEMENTED("StopService"); - return {}; + ZEN_UNUSED(ServiceName); + ZEN_NOT_IMPLEMENTED("StopService"); + return {}; } -#endif +#endif // ZEN_PLATFORM_WINDOWS + +#if ZEN_PLATFORM_MAC + +std::error_code +InstallService(const std::filesystem::path& ExecutablePath, + std::string_view CommandLineOptions, + std::string_view ServiceName, + std::string_view ServiceDisplayName, + std::string_view ServiceDescription) +{ + const char* PlistTemplate = + "" + "" + "" + "" + "Label" + "{}" // ServiceName + "ProgramArguments" + "" + "{}" // Program name + "{}" // "arg\n" * number of arguments + "" + "KeepAlive" + "" +// "Sockets" +// "" +// "Listeners" +// "" +// "SockServiceName" +// "{}" // Listen socket +// "SockType" +// "tcp" +// "SockFamily" +// "IPv4" +// "" +// "" + "StandardOutPath" + "/var/log/myjob.log" + "StandardErrorPath" + "/var/log/myjob.log" + "Debug" + "<{}/>" + + "" + ""; + + std::string ExecutableName = ExecutablePath.filename(); + + const bool Debug = true; + + bool IsQuote = false; + size_t Start = 0; + ExtendableStringBuilder<256> CommandLineOptions; + for (size_t Offset = 0; Offset < CommandLineOptions.length(); Offset++) + { + switch(CommandLineOptions[Offset]) + { + case ' ': + if (IsQuote) + { + continue; + } + else if (Offset > Start) + { + CommandLineOptions.Append(""); + CommandLineOptions.Append(CommandLineOptions.substr(Start, Offset - Start)); + CommandLineOptions.Append("\n"); + Start = Offset + 1; + } + break; + case '"': + if (IsQuite) + { + IsQuote = false; + if (Offset - Start > 1) + { + CommandLineOptions.Append(""); + CommandLineOptions.Append(CommandLineOptions.substr(Start + 1, (Offset - 1) - (Start + 1)); + CommandLineOptions.Append("\n"); + } + Start = Offset + 1; + } + else{ + IsQuite = true; + } + break; + default: + break; + } + } + ZEN_ASSERT(!IsQuote); + if (Offset > Start) + { + CommandLineOptions.Append(""); + CommandLineOptions.Append(CommandLineOptions.substr(Start, Offset - Start)); + CommandLineOptions.Append("\n"); + } + + ForEachStrTok(CommandLineOptions, ' ', <#Fn &&Func#>) + + std::string PList = fmt::format(PlistTemplate, ServiceName,ExecutableName, CommandLineOptions,debug ? "true" : "false"); + + std::filesystem::path SymLink = std::filesystem::path("/usr/local/libexec") / ExecutableName; + if (symlink(ExecutablePath.c_str(), SymLink.c_str()) == -1) + { + return MakeErrorCodeFromLastError(); + } + return {}; +} + + +#endif // ZEN_PLATFORM_MAC } // namespace zen + -- cgit v1.2.3 From a49f1b9cf5432b1528127427b0b911ce26ce5448 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Wed, 28 Aug 2024 11:42:35 +0200 Subject: more WIP --- src/zenutil/service.cpp | 931 ++++++++++++++++++++++++++---------------------- 1 file changed, 499 insertions(+), 432 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index cf0c8ef50..cce154988 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -9,498 +9,565 @@ #if ZEN_PLATFORM_WINDOWS # include #endif +#if ZEN_PLATFORM_MAC +# include +#endif namespace zen { using namespace std::literals; +#if ZEN_PLATFORM_MAC + +namespace { + std::vector SplitArguments(std::string_view Arguments) + { + bool IsQuote = false; + size_t Start = 0; + size_t Offset = 0; + std::vector Result; + for (; Offset < Arguments.length(); Offset++) + { + switch (Arguments[Offset]) + { + case ' ': + if (IsQuote) + { + continue; + } + else if (Offset > Start) + { + Result.push_back(Arguments.substr(Start, Offset - Start)); + Start = Offset + 1; + } + break; + case '"': + if (IsQuote) + { + IsQuote = false; + if (Offset - Start > 1) + { + Result.push_back(Arguments.substr(Start + 1, Offset - (Start + 1))); + } + Start = Offset + 1; + } + else + { + IsQuote = true; + } + break; + case '=': + if (IsQuote) + { + continue; + } + else if (Offset > Start) + { + Result.push_back(Arguments.substr(Start, Offset - Start)); + Start = Offset + 1; + } + break; + default: + break; + } + } + ZEN_ASSERT(!IsQuote); + if (Offset > Start) + { + Result.push_back(Arguments.substr(Start, Offset - Start)); + } + return Result; + } + + // Needs special character escaping + void AppendEscaped(std::string_view String, StringBuilderBase& SB) + { + size_t Offset = 0; + while (Offset < String.length()) + { + size_t NextEscapeCharacter = String.find_first_of("\"'<>&", Offset); + if (NextEscapeCharacter == std::string_view::npos) + { + break; + } + if (NextEscapeCharacter > Offset) + { + SB.Append(String.substr(Offset, NextEscapeCharacter - Offset)); + } + switch (String[NextEscapeCharacter]) + { + case '"': + SB.Append("""); + break; + case '\'': + SB.Append("&apos"); + break; + case '<': + SB.Append("<"); + break; + case '>': + SB.Append(">"); + break; + case '&': + SB.Append("&"); + break; + default: + ZEN_ASSERT(false); + break; + } + Offset = NextEscapeCharacter + 1; + } + if (Offset == 0) + { + SB.Append(String); + } + else if (String.length() > Offset) + { + SB.Append(String.substr(Offset)); + } + } + + std::string BuildPlist(const std::filesystem::path& ExecutablePath, + std::string_view CommandLineOptions, + std::string_view ServiceName, + std::string_view /*ServiceDisplayName*/, + std::string_view /*ServiceDescription*/, + bool Debug) + { + std::vector Arguments = SplitArguments(CommandLineOptions); + ExtendableStringBuilder<256> ProgramArguments; + for (const std::string_view Argument : Arguments) + { + ProgramArguments.Append("\n"); + AppendEscaped(Argument, ProgramArguments); + ProgramArguments.Append("\n\n"); + } + + return fmt::format( + "\n" + "\n" + "\n" + " \n" + " Label\n" + " {}\n" // ServiceName + " ProgramArguments\n" + " \n" + " {}\n" // Program name + " {}" // "arg\n" * number of arguments + " \n" + " KeepAlive\n" + " \n" + " StandardOutPath\n" + " /var/log/myjob.log\n" + " StandardErrorPath\n" + " /var/log/myjob.log\n" + " Debug\n" + " <{}/>\n" + " \n" + "\n", + ServiceName, + ExecutablePath.filename().string(), + ProgramArguments.ToView(), + Debug ? "true"sv : "false"sv); + + // "Sockets" + // "" + // "Listeners" + // "" + // "SockServiceName" + // "{}" // Listen socket + // "SockType" + // "tcp" + // "SockFamily" + // "IPv4" + // "" + // "" + } +} // namespace + +#endif // ZEN_PLATFORM_MAC + std::string_view ToString(ServiceStatus Status) { - switch (Status) - { - case ServiceStatus::NotInstalled: - return "Not installed"sv; - case ServiceStatus::Starting: - return "Starting"sv; - case ServiceStatus::Running: - return "Running"sv; - case ServiceStatus::Stopping: - return "Stopping"sv; - case ServiceStatus::Stopped: - return "Stopped"sv; - case ServiceStatus::Pausing: - return "Pausing"sv; - case ServiceStatus::Paused: - return "Paused"sv; - case ServiceStatus::Resuming: - return "Resuming"sv; - default: - ZEN_ASSERT(false); - return ""sv; - } + switch (Status) + { + case ServiceStatus::NotInstalled: + return "Not installed"sv; + case ServiceStatus::Starting: + return "Starting"sv; + case ServiceStatus::Running: + return "Running"sv; + case ServiceStatus::Stopping: + return "Stopping"sv; + case ServiceStatus::Stopped: + return "Stopped"sv; + case ServiceStatus::Pausing: + return "Pausing"sv; + case ServiceStatus::Paused: + return "Paused"sv; + case ServiceStatus::Resuming: + return "Resuming"sv; + default: + ZEN_ASSERT(false); + return ""sv; + } } #if ZEN_PLATFORM_WINDOWS std::error_code InstallService(const std::filesystem::path& ExecutablePath, - std::string_view CommandLineOptions, - std::string_view ServiceName, - std::string_view ServiceDisplayName, - std::string_view ServiceDescription) + std::string_view CommandLineOptions, + std::string_view ServiceName, + std::string_view ServiceDisplayName, + std::string_view ServiceDescription) { - // Get a handle to the SCM database. - - SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer - NULL, // ServicesActive database - SC_MANAGER_ALL_ACCESS); // full access rights - - if (NULL == schSCManager) - { - return MakeErrorCodeFromLastError(); - } - - auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); - - // Create the service - - ExtendableWideStringBuilder<128> Name; - Utf8ToWide(ServiceName, Name); - - ExtendableWideStringBuilder<128> DisplayName; - Utf8ToWide(ServiceDisplayName, DisplayName); - - ExtendableWideStringBuilder<128> Path; - Path.Append(ExecutablePath.c_str()); - if (!CommandLineOptions.empty()) - { - Path.AppendAscii(" "); - Utf8ToWide(CommandLineOptions, Path); - } - - SC_HANDLE schService = CreateService(schSCManager, // SCM database - Name.c_str(), // name of service - DisplayName.c_str(), // service name to display - SERVICE_ALL_ACCESS, // desired access - SERVICE_WIN32_OWN_PROCESS, // service type - SERVICE_DEMAND_START, // start type - SERVICE_ERROR_NORMAL, // error control type - Path.c_str(), // path to service's binary - NULL, // no load ordering group - NULL, // no tag identifier - NULL, // no dependencies - NULL, // LocalSystem account - NULL); // no password - - if (schService == NULL) - { - return MakeErrorCodeFromLastError(); - } - - if (!ServiceDescription.empty()) - { - ExtendableWideStringBuilder<128> DescriptionBuilder; - Utf8ToWide(ServiceDescription, DescriptionBuilder); - - SERVICE_DESCRIPTION Description; - Description.lpDescription = const_cast(DescriptionBuilder.c_str()); - if (!ChangeServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, &Description)) - { - return MakeErrorCodeFromLastError(); - } - } - - CloseServiceHandle(schService); - - return {}; + // Get a handle to the SCM database. + + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_ALL_ACCESS); // full access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Create the service + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + ExtendableWideStringBuilder<128> DisplayName; + Utf8ToWide(ServiceDisplayName, DisplayName); + + ExtendableWideStringBuilder<128> Path; + Path.Append(ExecutablePath.c_str()); + if (!CommandLineOptions.empty()) + { + Path.AppendAscii(" "); + Utf8ToWide(CommandLineOptions, Path); + } + + SC_HANDLE schService = CreateService(schSCManager, // SCM database + Name.c_str(), // name of service + DisplayName.c_str(), // service name to display + SERVICE_ALL_ACCESS, // desired access + SERVICE_WIN32_OWN_PROCESS, // service type + SERVICE_DEMAND_START, // start type + SERVICE_ERROR_NORMAL, // error control type + Path.c_str(), // path to service's binary + NULL, // no load ordering group + NULL, // no tag identifier + NULL, // no dependencies + NULL, // LocalSystem account + NULL); // no password + + if (schService == NULL) + { + return MakeErrorCodeFromLastError(); + } + + if (!ServiceDescription.empty()) + { + ExtendableWideStringBuilder<128> DescriptionBuilder; + Utf8ToWide(ServiceDescription, DescriptionBuilder); + + SERVICE_DESCRIPTION Description; + Description.lpDescription = const_cast(DescriptionBuilder.c_str()); + if (!ChangeServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, &Description)) + { + return MakeErrorCodeFromLastError(); + } + } + + CloseServiceHandle(schService); + + return {}; } std::error_code UninstallService(std::string_view ServiceName) { - // Get a handle to the SCM database. - SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer - NULL, // ServicesActive database - SC_MANAGER_ALL_ACCESS); // full access rights - - if (NULL == schSCManager) - { - return MakeErrorCodeFromLastError(); - } - - auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); - - // Get a handle to the service. - - ExtendableWideStringBuilder<128> Name; - Utf8ToWide(ServiceName, Name); - - SC_HANDLE schService = OpenService(schSCManager, // SCM database - Name.c_str(), // name of service - DELETE); // need delete access - - if (schService == NULL) - { - DWORD Error = ::GetLastError(); - if (Error == ERROR_SERVICE_DOES_NOT_EXIST) - { - return {}; - } - return MakeErrorCode(Error); - } - auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); - - // Delete the service. - - if (!DeleteService(schService)) - { - return MakeErrorCodeFromLastError(); - } - - return {}; + // Get a handle to the SCM database. + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_ALL_ACCESS); // full access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Get a handle to the service. + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + SC_HANDLE schService = OpenService(schSCManager, // SCM database + Name.c_str(), // name of service + DELETE); // need delete access + + if (schService == NULL) + { + DWORD Error = ::GetLastError(); + if (Error == ERROR_SERVICE_DOES_NOT_EXIST) + { + return {}; + } + return MakeErrorCode(Error); + } + auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); + + // Delete the service. + + if (!DeleteService(schService)) + { + return MakeErrorCodeFromLastError(); + } + + return {}; } std::error_code QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) { - // Get a handle to the SCM database. - SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer - NULL, // ServicesActive database - SC_MANAGER_CONNECT); // standard access rights - - if (NULL == schSCManager) - { - return MakeErrorCodeFromLastError(); - } - - auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); - - // Get a handle to the service. - - ExtendableWideStringBuilder<128> Name; - Utf8ToWide(ServiceName, Name); - - SC_HANDLE schService = OpenService(schSCManager, // SCM database - Name.c_str(), // name of service - SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); // need delete access - - if (schService == NULL) - { - DWORD Error = ::GetLastError(); - if (Error == ERROR_SERVICE_DOES_NOT_EXIST) - { - OutInfo.Status = ServiceStatus::NotInstalled; - return {}; - } - return MakeErrorCode(Error); - } - auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); - - std::vector Buffer(8192); - QUERY_SERVICE_CONFIG* ServiceConfig = reinterpret_cast(Buffer.data()); - DWORD BytesNeeded = 0; - if (!QueryServiceConfig(schService, ServiceConfig, (DWORD)Buffer.size(), &BytesNeeded)) - { - return MakeErrorCodeFromLastError(); - } - - OutInfo.ExecutablePath = std::filesystem::path(ServiceConfig->lpBinaryPathName); - OutInfo.DisplayName = WideToUtf8(ServiceConfig->lpDisplayName); - - SERVICE_STATUS ServiceStatus; - if (!::QueryServiceStatus(schService, &ServiceStatus)) - { - return MakeErrorCodeFromLastError(); - } - - switch (ServiceStatus.dwCurrentState) - { - case SERVICE_STOPPED: - OutInfo.Status = ServiceStatus::Stopped; - break; - case SERVICE_START_PENDING: - OutInfo.Status = ServiceStatus::Starting; - break; - case SERVICE_STOP_PENDING: - OutInfo.Status = ServiceStatus::Stopping; - break; - case SERVICE_RUNNING: - OutInfo.Status = ServiceStatus::Running; - break; - case SERVICE_CONTINUE_PENDING: - OutInfo.Status = ServiceStatus::Resuming; - break; - case SERVICE_PAUSE_PENDING: - OutInfo.Status = ServiceStatus::Pausing; - break; - case SERVICE_PAUSED: - OutInfo.Status = ServiceStatus::Paused; - break; - default: - throw std::runtime_error(fmt::format("Unknown service status for '{}': {}", ServiceName, ServiceStatus.dwCurrentState)); - } - - if (!QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, Buffer.data(), (DWORD)Buffer.size(), &BytesNeeded)) - { - DWORD Error = ::GetLastError(); - if (Error == ERROR_INSUFFICIENT_BUFFER) - { - Buffer.resize((size_t)BytesNeeded); - if (!QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, Buffer.data(), (DWORD)Buffer.size(), &BytesNeeded)) - { - return MakeErrorCodeFromLastError(); - } - } - else - { - return MakeErrorCode(Error); - } - } - SERVICE_DESCRIPTION* Description = (SERVICE_DESCRIPTION*)Buffer.data(); - if (Description->lpDescription != NULL) - { - OutInfo.Description = WideToUtf8(std::wstring(Description->lpDescription)); - } - - return {}; + // Get a handle to the SCM database. + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_CONNECT); // standard access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Get a handle to the service. + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + SC_HANDLE schService = OpenService(schSCManager, // SCM database + Name.c_str(), // name of service + SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); // need delete access + + if (schService == NULL) + { + DWORD Error = ::GetLastError(); + if (Error == ERROR_SERVICE_DOES_NOT_EXIST) + { + OutInfo.Status = ServiceStatus::NotInstalled; + return {}; + } + return MakeErrorCode(Error); + } + auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); + + std::vector Buffer(8192); + QUERY_SERVICE_CONFIG* ServiceConfig = reinterpret_cast(Buffer.data()); + DWORD BytesNeeded = 0; + if (!QueryServiceConfig(schService, ServiceConfig, (DWORD)Buffer.size(), &BytesNeeded)) + { + return MakeErrorCodeFromLastError(); + } + + OutInfo.ExecutablePath = std::filesystem::path(ServiceConfig->lpBinaryPathName); + OutInfo.DisplayName = WideToUtf8(ServiceConfig->lpDisplayName); + + SERVICE_STATUS ServiceStatus; + if (!::QueryServiceStatus(schService, &ServiceStatus)) + { + return MakeErrorCodeFromLastError(); + } + + switch (ServiceStatus.dwCurrentState) + { + case SERVICE_STOPPED: + OutInfo.Status = ServiceStatus::Stopped; + break; + case SERVICE_START_PENDING: + OutInfo.Status = ServiceStatus::Starting; + break; + case SERVICE_STOP_PENDING: + OutInfo.Status = ServiceStatus::Stopping; + break; + case SERVICE_RUNNING: + OutInfo.Status = ServiceStatus::Running; + break; + case SERVICE_CONTINUE_PENDING: + OutInfo.Status = ServiceStatus::Resuming; + break; + case SERVICE_PAUSE_PENDING: + OutInfo.Status = ServiceStatus::Pausing; + break; + case SERVICE_PAUSED: + OutInfo.Status = ServiceStatus::Paused; + break; + default: + throw std::runtime_error(fmt::format("Unknown service status for '{}': {}", ServiceName, ServiceStatus.dwCurrentState)); + } + + if (!QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, Buffer.data(), (DWORD)Buffer.size(), &BytesNeeded)) + { + DWORD Error = ::GetLastError(); + if (Error == ERROR_INSUFFICIENT_BUFFER) + { + Buffer.resize((size_t)BytesNeeded); + if (!QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, Buffer.data(), (DWORD)Buffer.size(), &BytesNeeded)) + { + return MakeErrorCodeFromLastError(); + } + } + else + { + return MakeErrorCode(Error); + } + } + SERVICE_DESCRIPTION* Description = (SERVICE_DESCRIPTION*)Buffer.data(); + if (Description->lpDescription != NULL) + { + OutInfo.Description = WideToUtf8(std::wstring(Description->lpDescription)); + } + + return {}; } std::error_code StartService(std::string_view ServiceName) { - // Get a handle to the SCM database. - SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer - NULL, // ServicesActive database - SC_MANAGER_CONNECT); // default access rights - - if (NULL == schSCManager) - { - return MakeErrorCodeFromLastError(); - } - - auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); - - // Get a handle to the service. - - ExtendableWideStringBuilder<128> Name; - Utf8ToWide(ServiceName, Name); - - SC_HANDLE schService = OpenService(schSCManager, // SCM database - Name.c_str(), // name of service - SERVICE_START); // need start access - - if (schService == NULL) - { - return MakeErrorCodeFromLastError(); - } - auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); - - // Start the service. - - if (!::StartService(schService, 0, NULL)) - { - return MakeErrorCodeFromLastError(); - } - - return {}; + // Get a handle to the SCM database. + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_CONNECT); // default access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Get a handle to the service. + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + SC_HANDLE schService = OpenService(schSCManager, // SCM database + Name.c_str(), // name of service + SERVICE_START); // need start access + + if (schService == NULL) + { + return MakeErrorCodeFromLastError(); + } + auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); + + // Start the service. + + if (!::StartService(schService, 0, NULL)) + { + return MakeErrorCodeFromLastError(); + } + + return {}; } std::error_code StopService(std::string_view ServiceName) { - // Get a handle to the SCM database. - SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer - NULL, // ServicesActive database - SC_MANAGER_CONNECT); // default access rights - - if (NULL == schSCManager) - { - return MakeErrorCodeFromLastError(); - } - - auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); - - // Get a handle to the service. - - ExtendableWideStringBuilder<128> Name; - Utf8ToWide(ServiceName, Name); - - SC_HANDLE schService = OpenService(schSCManager, // SCM database - Name.c_str(), // name of service - SERVICE_STOP); // need start access - - if (schService == NULL) - { - return MakeErrorCodeFromLastError(); - } - auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); - - // Stop the service. - SERVICE_STATUS ServiceStatus; - if (!::ControlService(schService, SERVICE_CONTROL_STOP, &ServiceStatus)) - { - return MakeErrorCodeFromLastError(); - } - - return {}; + // Get a handle to the SCM database. + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_CONNECT); // default access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Get a handle to the service. + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + SC_HANDLE schService = OpenService(schSCManager, // SCM database + Name.c_str(), // name of service + SERVICE_STOP); // need start access + + if (schService == NULL) + { + return MakeErrorCodeFromLastError(); + } + auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); + + // Stop the service. + SERVICE_STATUS ServiceStatus; + if (!::ControlService(schService, SERVICE_CONTROL_STOP, &ServiceStatus)) + { + return MakeErrorCodeFromLastError(); + } + + return {}; } #else std::error_code InstallService(const std::filesystem::path& ExecutablePath, - std::string_view CommandLineOptions, - std::string_view ServiceName, - std::string_view ServiceDisplayName, - std::string_view ServiceDescription) + std::string_view CommandLineOptions, + std::string_view ServiceName, + std::string_view ServiceDisplayName, + std::string_view ServiceDescription) { - ZEN_UNUSED(ExecutablePath, CommandLineOptions, ServiceName, ServiceDisplayName, ServiceDescription); - ZEN_NOT_IMPLEMENTED("InstallService"); - return {}; + std::filesystem::path SymLink = std::filesystem::path("/usr/local/libexec") / ExecutablePath.filename(); + if (symlink(ExecutablePath.c_str(), SymLink.c_str()) == -1) + { + return MakeErrorCodeFromLastError(); + } + std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); + std::string PList = BuildPlist(ExecutablePath, CommandLineOptions, DaemonName, ServiceDisplayName, ServiceDescription, true); + zen::WriteFile(std::filesystem::path("/Library/LaunchDaemons") / (DaemonName + ".plist"), IoBuffer(PList.data(), PList.size())); + return {}; } std::error_code UninstallService(std::string_view ServiceName) { - ZEN_UNUSED(ServiceName); - ZEN_NOT_IMPLEMENTED("UninstallService"); - return {}; + ZEN_UNUSED(ServiceName); + ZEN_NOT_IMPLEMENTED("UninstallService"); + return {}; } std::error_code QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutStatus) { - ZEN_UNUSED(ServiceName, OutStatus); - ZEN_NOT_IMPLEMENTED("QueryInstalledService"); - return {}; + ZEN_UNUSED(ServiceName, OutStatus); + ZEN_NOT_IMPLEMENTED("QueryInstalledService"); + return {}; } std::error_code StartService(std::string_view ServiceName) { - ZEN_UNUSED(ServiceName); - ZEN_NOT_IMPLEMENTED("StartService"); - return {}; + ZEN_UNUSED(ServiceName); + ZEN_NOT_IMPLEMENTED("StartService"); + return {}; } std::error_code StopService(std::string_view ServiceName) { - ZEN_UNUSED(ServiceName); - ZEN_NOT_IMPLEMENTED("StopService"); - return {}; + ZEN_UNUSED(ServiceName); + ZEN_NOT_IMPLEMENTED("StopService"); + return {}; } -#endif // ZEN_PLATFORM_WINDOWS +#endif // ZEN_PLATFORM_WINDOWS -#if ZEN_PLATFORM_MAC - -std::error_code -InstallService(const std::filesystem::path& ExecutablePath, - std::string_view CommandLineOptions, - std::string_view ServiceName, - std::string_view ServiceDisplayName, - std::string_view ServiceDescription) -{ - const char* PlistTemplate = - "" - "" - "" - "" - "Label" - "{}" // ServiceName - "ProgramArguments" - "" - "{}" // Program name - "{}" // "arg\n" * number of arguments - "" - "KeepAlive" - "" -// "Sockets" -// "" -// "Listeners" -// "" -// "SockServiceName" -// "{}" // Listen socket -// "SockType" -// "tcp" -// "SockFamily" -// "IPv4" -// "" -// "" - "StandardOutPath" - "/var/log/myjob.log" - "StandardErrorPath" - "/var/log/myjob.log" - "Debug" - "<{}/>" - - "" - ""; - - std::string ExecutableName = ExecutablePath.filename(); - - const bool Debug = true; - - bool IsQuote = false; - size_t Start = 0; - ExtendableStringBuilder<256> CommandLineOptions; - for (size_t Offset = 0; Offset < CommandLineOptions.length(); Offset++) - { - switch(CommandLineOptions[Offset]) - { - case ' ': - if (IsQuote) - { - continue; - } - else if (Offset > Start) - { - CommandLineOptions.Append(""); - CommandLineOptions.Append(CommandLineOptions.substr(Start, Offset - Start)); - CommandLineOptions.Append("\n"); - Start = Offset + 1; - } - break; - case '"': - if (IsQuite) - { - IsQuote = false; - if (Offset - Start > 1) - { - CommandLineOptions.Append(""); - CommandLineOptions.Append(CommandLineOptions.substr(Start + 1, (Offset - 1) - (Start + 1)); - CommandLineOptions.Append("\n"); - } - Start = Offset + 1; - } - else{ - IsQuite = true; - } - break; - default: - break; - } - } - ZEN_ASSERT(!IsQuote); - if (Offset > Start) - { - CommandLineOptions.Append(""); - CommandLineOptions.Append(CommandLineOptions.substr(Start, Offset - Start)); - CommandLineOptions.Append("\n"); - } - - ForEachStrTok(CommandLineOptions, ' ', <#Fn &&Func#>) - - std::string PList = fmt::format(PlistTemplate, ServiceName,ExecutableName, CommandLineOptions,debug ? "true" : "false"); - - std::filesystem::path SymLink = std::filesystem::path("/usr/local/libexec") / ExecutableName; - if (symlink(ExecutablePath.c_str(), SymLink.c_str()) == -1) - { - return MakeErrorCodeFromLastError(); - } - return {}; -} - - -#endif // ZEN_PLATFORM_MAC } // namespace zen - -- cgit v1.2.3 From f8398bea9f3b5e7ccd1ba4134fa75976da8b7545 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Wed, 28 Aug 2024 12:55:33 +0200 Subject: launchd plist install --- src/zenutil/service.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index cce154988..8ac35d969 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -11,6 +11,9 @@ #endif #if ZEN_PLATFORM_MAC # include + +# include +# include #endif namespace zen { @@ -137,9 +140,9 @@ namespace { ExtendableStringBuilder<256> ProgramArguments; for (const std::string_view Argument : Arguments) { - ProgramArguments.Append("\n"); + ProgramArguments.Append(" "); AppendEscaped(Argument, ProgramArguments); - ProgramArguments.Append("\n\n"); + ProgramArguments.Append("\n"); } return fmt::format( @@ -152,7 +155,7 @@ namespace { " ProgramArguments\n" " \n" " {}\n" // Program name - " {}" // "arg\n" * number of arguments + "{}" // "arg\n" * number of arguments " \n" " KeepAlive\n" " \n" @@ -526,13 +529,21 @@ InstallService(const std::filesystem::path& ExecutablePath, std::string_view ServiceDescription) { std::filesystem::path SymLink = std::filesystem::path("/usr/local/libexec") / ExecutablePath.filename(); + ZEN_INFO("Attempting synlink from {} to {}", ExecutablePath.string(), SymLink.string()); if (symlink(ExecutablePath.c_str(), SymLink.c_str()) == -1) { return MakeErrorCodeFromLastError(); } std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); std::string PList = BuildPlist(ExecutablePath, CommandLineOptions, DaemonName, ServiceDisplayName, ServiceDescription, true); - zen::WriteFile(std::filesystem::path("/Library/LaunchDaemons") / (DaemonName + ".plist"), IoBuffer(PList.data(), PList.size())); + std::filesystem::path PListPath = std::filesystem::path("/Library/LaunchDaemons") / (DaemonName + ".plist"); + ZEN_INFO("Writing launchd plist to {}", PListPath.string()); + zen::WriteFile(PListPath, IoBuffer(IoBuffer::Wrap, PList.data(), PList.size())); + ZEN_INFO("Changing permissions to 0555 for {}", PListPath.string()); + if (chmod(PListPath.c_str(), 0555) == -1) + { + return MakeErrorCodeFromLastError(); + } return {}; } -- cgit v1.2.3 From bb7bc2a3c26807aa810c3e1a9007c0ffc7fdfbe4 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Wed, 28 Aug 2024 12:56:18 +0200 Subject: clang format --- src/zenutil/service.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index 8ac35d969..637974c13 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -155,7 +155,7 @@ namespace { " ProgramArguments\n" " \n" " {}\n" // Program name - "{}" // "arg\n" * number of arguments + "{}" // "arg\n" * number of arguments " \n" " KeepAlive\n" " \n" @@ -534,8 +534,8 @@ InstallService(const std::filesystem::path& ExecutablePath, { return MakeErrorCodeFromLastError(); } - std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); - std::string PList = BuildPlist(ExecutablePath, CommandLineOptions, DaemonName, ServiceDisplayName, ServiceDescription, true); + std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); + std::string PList = BuildPlist(ExecutablePath, CommandLineOptions, DaemonName, ServiceDisplayName, ServiceDescription, true); std::filesystem::path PListPath = std::filesystem::path("/Library/LaunchDaemons") / (DaemonName + ".plist"); ZEN_INFO("Writing launchd plist to {}", PListPath.string()); zen::WriteFile(PListPath, IoBuffer(IoBuffer::Wrap, PList.data(), PList.size())); -- cgit v1.2.3 From 5ad925a8523ce6cd06e43e00ba44611f95d741f8 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Wed, 28 Aug 2024 14:40:03 +0200 Subject: Add ServiceSpec struct --- src/zenutil/service.cpp | 74 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 26 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index 637974c13..870a47df5 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -219,11 +219,7 @@ ToString(ServiceStatus Status) #if ZEN_PLATFORM_WINDOWS std::error_code -InstallService(const std::filesystem::path& ExecutablePath, - std::string_view CommandLineOptions, - std::string_view ServiceName, - std::string_view ServiceDisplayName, - std::string_view ServiceDescription) +InstallService(std::string_view ServiceName, const ServiceSpec& Spec) { // Get a handle to the SCM database. @@ -244,14 +240,14 @@ InstallService(const std::filesystem::path& ExecutablePath, Utf8ToWide(ServiceName, Name); ExtendableWideStringBuilder<128> DisplayName; - Utf8ToWide(ServiceDisplayName, DisplayName); + Utf8ToWide(Spec.DisplayName, DisplayName); ExtendableWideStringBuilder<128> Path; - Path.Append(ExecutablePath.c_str()); - if (!CommandLineOptions.empty()) + Path.Append(Spec.ExecutablePath.c_str()); + if (!Spec.CommandLineOptions.empty()) { Path.AppendAscii(" "); - Utf8ToWide(CommandLineOptions, Path); + Utf8ToWide(Spec.CommandLineOptions, Path); } SC_HANDLE schService = CreateService(schSCManager, // SCM database @@ -273,10 +269,10 @@ InstallService(const std::filesystem::path& ExecutablePath, return MakeErrorCodeFromLastError(); } - if (!ServiceDescription.empty()) + if (!Spec.Description.empty()) { ExtendableWideStringBuilder<128> DescriptionBuilder; - Utf8ToWide(ServiceDescription, DescriptionBuilder); + Utf8ToWide(Spec.Description, DescriptionBuilder); SERVICE_DESCRIPTION Description; Description.lpDescription = const_cast(DescriptionBuilder.c_str()); @@ -380,8 +376,38 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) return MakeErrorCodeFromLastError(); } - OutInfo.ExecutablePath = std::filesystem::path(ServiceConfig->lpBinaryPathName); - OutInfo.DisplayName = WideToUtf8(ServiceConfig->lpDisplayName); + std::wstring BinaryWithArguments(ServiceConfig->lpBinaryPathName); + if (BinaryWithArguments.size()) + { + if (BinaryWithArguments[0] == '"') + { + std::wstring::size_type ExecutableEnd = BinaryWithArguments.find('"', 1); + if (ExecutableEnd == std::wstring::npos) + { + OutInfo.Spec.ExecutablePath = BinaryWithArguments; + } + else + { + OutInfo.Spec.ExecutablePath = BinaryWithArguments.substr(0, ExecutableEnd + 1); + OutInfo.Spec.CommandLineOptions = + WideToUtf8(BinaryWithArguments.substr(ExecutableEnd + 1 + BinaryWithArguments[ExecutableEnd + 1] == ' ' ? 1 : 0)); + } + } + else + { + std::wstring::size_type ExecutableEnd = BinaryWithArguments.find(' ', 1); + if (ExecutableEnd == std::wstring::npos) + { + OutInfo.Spec.ExecutablePath = BinaryWithArguments; + } + else + { + OutInfo.Spec.ExecutablePath = BinaryWithArguments.substr(0, ExecutableEnd); + OutInfo.Spec.CommandLineOptions = WideToUtf8(BinaryWithArguments.substr(ExecutableEnd + 1)); + } + } + } + OutInfo.Spec.DisplayName = WideToUtf8(ServiceConfig->lpDisplayName); SERVICE_STATUS ServiceStatus; if (!::QueryServiceStatus(schService, &ServiceStatus)) @@ -435,7 +461,7 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) SERVICE_DESCRIPTION* Description = (SERVICE_DESCRIPTION*)Buffer.data(); if (Description->lpDescription != NULL) { - OutInfo.Description = WideToUtf8(std::wstring(Description->lpDescription)); + OutInfo.Spec.Description = WideToUtf8(std::wstring(Description->lpDescription)); } return {}; @@ -522,20 +548,16 @@ StopService(std::string_view ServiceName) } #else std::error_code -InstallService(const std::filesystem::path& ExecutablePath, - std::string_view CommandLineOptions, - std::string_view ServiceName, - std::string_view ServiceDisplayName, - std::string_view ServiceDescription) +InstallService(std::string_view ServiceName, const ServiceSpec& Spec) { - std::filesystem::path SymLink = std::filesystem::path("/usr/local/libexec") / ExecutablePath.filename(); - ZEN_INFO("Attempting synlink from {} to {}", ExecutablePath.string(), SymLink.string()); - if (symlink(ExecutablePath.c_str(), SymLink.c_str()) == -1) + std::filesystem::path SymLink = std::filesystem::path("/usr/local/libexec") / Spec.ExecutablePath.filename(); + ZEN_INFO("Attempting symlink from {} to {}", Spec.ExecutablePath.string(), SymLink.string()); + if (symlink(Spec.ExecutablePath.c_str(), SymLink.c_str()) == -1) { return MakeErrorCodeFromLastError(); } - std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); - std::string PList = BuildPlist(ExecutablePath, CommandLineOptions, DaemonName, ServiceDisplayName, ServiceDescription, true); + std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); + std::string PList = BuildPlist(Spec.ExecutablePath, Spec.CommandLineOptions, DaemonName, Spec.DisplayName, Spec.Description, true); std::filesystem::path PListPath = std::filesystem::path("/Library/LaunchDaemons") / (DaemonName + ".plist"); ZEN_INFO("Writing launchd plist to {}", PListPath.string()); zen::WriteFile(PListPath, IoBuffer(IoBuffer::Wrap, PList.data(), PList.size())); @@ -556,9 +578,9 @@ UninstallService(std::string_view ServiceName) } std::error_code -QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutStatus) +QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) { - ZEN_UNUSED(ServiceName, OutStatus); + ZEN_UNUSED(ServiceName, OutInfo); ZEN_NOT_IMPLEMENTED("QueryInstalledService"); return {}; } -- cgit v1.2.3 From 368fb049a4e66b8a6dc61938558d0865e8f88c66 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Wed, 18 Sep 2024 10:43:26 +0200 Subject: wip --- src/zenutil/service.cpp | 142 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 108 insertions(+), 34 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index 870a47df5..1637b9394 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -129,9 +129,10 @@ namespace { } } - std::string BuildPlist(const std::filesystem::path& ExecutablePath, + std::string BuildPlist(std::string_view ServiceName, + const std::filesystem::path& ExecutablePath, std::string_view CommandLineOptions, - std::string_view ServiceName, + std::string_view DaemonName, std::string_view /*ServiceDisplayName*/, std::string_view /*ServiceDescription*/, bool Debug) @@ -140,7 +141,7 @@ namespace { ExtendableStringBuilder<256> ProgramArguments; for (const std::string_view Argument : Arguments) { - ProgramArguments.Append(" "); + ProgramArguments.Append(" "); AppendEscaped(Argument, ProgramArguments); ProgramArguments.Append("\n"); } @@ -149,27 +150,38 @@ namespace { "\n" "\n" "\n" - " \n" - " Label\n" - " {}\n" // ServiceName - " ProgramArguments\n" - " \n" - " {}\n" // Program name + "\n" + " Label\n" + " {}\n" // DaemonName + " \n" + " ProgramArguments\n" + " \n" + " {}\n" // Program name "{}" // "arg\n" * number of arguments - " \n" - " KeepAlive\n" - " \n" - " StandardOutPath\n" - " /var/log/myjob.log\n" - " StandardErrorPath\n" - " /var/log/myjob.log\n" - " Debug\n" - " <{}/>\n" - " \n" + " \n" + " \n" + " RunAtLoad\n" + " \n" + " \n" +// " KeepAlive\n" +// " \n" +// " \n" + " StandardOutPath\n" + " /var/log/{}.log\n" + " \n" + " StandardErrorPath\n" + " /var/log/{}.err.log\n" + " \n" + " Debug\n" + " <{}/>\n" + " \n" + "\n" "\n", - ServiceName, - ExecutablePath.filename().string(), + DaemonName, + ExecutablePath.string(), ProgramArguments.ToView(), + ServiceName, + ServiceName, Debug ? "true"sv : "false"sv); // "Sockets" @@ -547,25 +559,73 @@ StopService(std::string_view ServiceName) return {}; } #else + +#if 0 +static int CopyFile(std::filesystem::path source, std::filesystem::path dest) +{ + + int childExitStatus; + pid_t pid; + int status; + + pid = fork(); + + if (pid == 0) { /* child */ + execl("/bin/cp", "/bin/cp", source.string().c_str(), dest.string().c_str(), (char *)0); + return 0; + } + else if (pid < 0) { + return -1; + } + else { + /* parent - wait for child - this has all error handling, you + * could just call wait() as long as you are only expecting to + * have one child process at a time. + */ + pid_t ws = waitpid( pid, &childExitStatus, WNOHANG); + if (ws == -1) + { + return -1; + } + + if( WIFEXITED(childExitStatus)) /* exit code in childExitStatus */ + { + status = WEXITSTATUS(childExitStatus); /* zero is normal exit */ + return status; + } + else if (WIFSIGNALED(childExitStatus)) /* killed */ + { + return -1; + } + else if (WIFSTOPPED(childExitStatus)) /* stopped */ + { + return -1; + } + return -1; + } +} +#endif + std::error_code InstallService(std::string_view ServiceName, const ServiceSpec& Spec) { - std::filesystem::path SymLink = std::filesystem::path("/usr/local/libexec") / Spec.ExecutablePath.filename(); - ZEN_INFO("Attempting symlink from {} to {}", Spec.ExecutablePath.string(), SymLink.string()); - if (symlink(Spec.ExecutablePath.c_str(), SymLink.c_str()) == -1) - { - return MakeErrorCodeFromLastError(); - } +// std::filesystem::path ServicePath = std::filesystem::path("/usr/local/libexec") / ServiceName; +// ZEN_INFO("Attempting to copy service from {} to {}", Spec.ExecutablePath.string(), ServicePath.string()); +// if (false && CopyFile(Spec.ExecutablePath, ServicePath) == -1) +//// if (symlink(Spec.ExecutablePath.c_str(), ServicePath.c_str()) == -1) +// { +// return MakeErrorCodeFromLastError(); +// } std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); - std::string PList = BuildPlist(Spec.ExecutablePath, Spec.CommandLineOptions, DaemonName, Spec.DisplayName, Spec.Description, true); - std::filesystem::path PListPath = std::filesystem::path("/Library/LaunchDaemons") / (DaemonName + ".plist"); + std::string PList = BuildPlist(ServiceName, Spec.ExecutablePath, Spec.CommandLineOptions, DaemonName, Spec.DisplayName, Spec.Description, true); + std::filesystem::path PListPath = std::filesystem::path("/Users/dan.engelbrecht/Library/LaunchAgents") / (DaemonName + ".plist"); ZEN_INFO("Writing launchd plist to {}", PListPath.string()); zen::WriteFile(PListPath, IoBuffer(IoBuffer::Wrap, PList.data(), PList.size())); ZEN_INFO("Changing permissions to 0555 for {}", PListPath.string()); - if (chmod(PListPath.c_str(), 0555) == -1) - { - return MakeErrorCodeFromLastError(); - } +// if (chmod(PListPath.string().c_str(), 0555) == -1) +// { +// return MakeErrorCodeFromLastError(); +// } return {}; } @@ -573,7 +633,21 @@ std::error_code UninstallService(std::string_view ServiceName) { ZEN_UNUSED(ServiceName); - ZEN_NOT_IMPLEMENTED("UninstallService"); + + std::filesystem::path ServicePath = std::filesystem::path("/usr/local/libexec") / ServiceName; + ZEN_INFO("Attempting to remove service from {}", ServicePath.string()); + if (unlink(ServicePath.string().c_str()) == -1) + { + return MakeErrorCodeFromLastError();; + } + std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); + std::filesystem::path PListPath = std::filesystem::path("/Library/LaunchDaemons") / (DaemonName + ".plist"); + ZEN_INFO("Attempting to remove launchd plist from {}", PListPath.string()); + if (unlink(ServicePath.string().c_str()) == -1) + { + return MakeErrorCodeFromLastError();; + } + return {}; } -- cgit v1.2.3 From 1747a513dc9dccd5a60e76daebd59cfba4a536d9 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Tue, 27 Aug 2024 17:09:55 +0200 Subject: zen `service` command --- src/zenutil/service.cpp | 393 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 393 insertions(+) create mode 100644 src/zenutil/service.cpp (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp new file mode 100644 index 000000000..840fe3104 --- /dev/null +++ b/src/zenutil/service.cpp @@ -0,0 +1,393 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include + +#include +#include +#include + +#if ZEN_PLATFORM_WINDOWS +# include +#endif + +namespace zen { +using namespace std::literals; + +std::string_view +ToString(ServiceStatus Status) +{ + switch (Status) + { + case ServiceStatus::NotInstalled: + return "Not installed"sv; + case ServiceStatus::Starting: + return "Starting"sv; + case ServiceStatus::Running: + return "Running"sv; + case ServiceStatus::Stopping: + return "Stopping"sv; + case ServiceStatus::Stopped: + return "Stopped"sv; + case ServiceStatus::Pausing: + return "Pausing"sv; + case ServiceStatus::Paused: + return "Paused"sv; + case ServiceStatus::Resuming: + return "Resuming"sv; + default: + ZEN_ASSERT(false); + return ""sv; + } +} + +#if ZEN_PLATFORM_WINDOWS + +std::error_code +InstallService(const std::filesystem::path& ExecutablePath, + std::string_view CommandLineOptions, + std::string_view ServiceName, + std::string_view ServiceDisplayName, + std::string_view ServiceDescription) +{ + // Get a handle to the SCM database. + + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_ALL_ACCESS); // full access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Create the service + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + ExtendableWideStringBuilder<128> DisplayName; + Utf8ToWide(ServiceDisplayName, DisplayName); + + ExtendableWideStringBuilder<128> Path; + Path.Append(ExecutablePath.c_str()); + if (!CommandLineOptions.empty()) + { + Path.AppendAscii(" "); + Utf8ToWide(CommandLineOptions, Path); + } + + SC_HANDLE schService = CreateService(schSCManager, // SCM database + Name.c_str(), // name of service + DisplayName.c_str(), // service name to display + SERVICE_ALL_ACCESS, // desired access + SERVICE_WIN32_OWN_PROCESS, // service type + SERVICE_DEMAND_START, // start type + SERVICE_ERROR_NORMAL, // error control type + Path.c_str(), // path to service's binary + NULL, // no load ordering group + NULL, // no tag identifier + NULL, // no dependencies + NULL, // LocalSystem account + NULL); // no password + + if (schService == NULL) + { + return MakeErrorCodeFromLastError(); + } + + if (!ServiceDescription.empty()) + { + ExtendableWideStringBuilder<128> DescriptionBuilder; + Utf8ToWide(ServiceDescription, DescriptionBuilder); + + SERVICE_DESCRIPTION Description; + Description.lpDescription = const_cast(DescriptionBuilder.c_str()); + if (!ChangeServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, &Description)) + { + return MakeErrorCodeFromLastError(); + } + } + + CloseServiceHandle(schService); + + return {}; +} + +std::error_code +UninstallService(std::string_view ServiceName) +{ + // Get a handle to the SCM database. + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_ALL_ACCESS); // full access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Get a handle to the service. + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + SC_HANDLE schService = OpenService(schSCManager, // SCM database + Name.c_str(), // name of service + DELETE); // need delete access + + if (schService == NULL) + { + DWORD Error = ::GetLastError(); + if (Error == ERROR_SERVICE_DOES_NOT_EXIST) + { + return {}; + } + return MakeErrorCode(Error); + } + auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); + + // Delete the service. + + if (!DeleteService(schService)) + { + return MakeErrorCodeFromLastError(); + } + + return {}; +} + +std::error_code +QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) +{ + // Get a handle to the SCM database. + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_CONNECT); // standard access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Get a handle to the service. + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + SC_HANDLE schService = OpenService(schSCManager, // SCM database + Name.c_str(), // name of service + SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); // need delete access + + if (schService == NULL) + { + DWORD Error = ::GetLastError(); + if (Error == ERROR_SERVICE_DOES_NOT_EXIST) + { + OutInfo.Status = ServiceStatus::NotInstalled; + return {}; + } + return MakeErrorCode(Error); + } + auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); + + std::vector Buffer(8192); + QUERY_SERVICE_CONFIG* ServiceConfig = reinterpret_cast(Buffer.data()); + DWORD BytesNeeded = 0; + if (!QueryServiceConfig(schService, ServiceConfig, (DWORD)Buffer.size(), &BytesNeeded)) + { + return MakeErrorCodeFromLastError(); + } + + OutInfo.ExecutablePath = std::filesystem::path(ServiceConfig->lpBinaryPathName); + OutInfo.DisplayName = WideToUtf8(ServiceConfig->lpDisplayName); + + SERVICE_STATUS ServiceStatus; + if (!::QueryServiceStatus(schService, &ServiceStatus)) + { + return MakeErrorCodeFromLastError(); + } + + switch (ServiceStatus.dwCurrentState) + { + case SERVICE_STOPPED: + OutInfo.Status = ServiceStatus::Stopped; + break; + case SERVICE_START_PENDING: + OutInfo.Status = ServiceStatus::Starting; + break; + case SERVICE_STOP_PENDING: + OutInfo.Status = ServiceStatus::Stopping; + break; + case SERVICE_RUNNING: + OutInfo.Status = ServiceStatus::Running; + break; + case SERVICE_CONTINUE_PENDING: + OutInfo.Status = ServiceStatus::Resuming; + break; + case SERVICE_PAUSE_PENDING: + OutInfo.Status = ServiceStatus::Pausing; + break; + case SERVICE_PAUSED: + OutInfo.Status = ServiceStatus::Paused; + break; + default: + throw std::runtime_error(fmt::format("Unknown service status for '{}': {}", ServiceName, ServiceStatus.dwCurrentState)); + } + + if (!QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, Buffer.data(), (DWORD)Buffer.size(), &BytesNeeded)) + { + DWORD Error = ::GetLastError(); + if (Error == ERROR_INSUFFICIENT_BUFFER) + { + Buffer.resize((size_t)BytesNeeded); + if (!QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, Buffer.data(), (DWORD)Buffer.size(), &BytesNeeded)) + { + return MakeErrorCodeFromLastError(); + } + } + else + { + return MakeErrorCode(Error); + } + } + SERVICE_DESCRIPTION* Description = (SERVICE_DESCRIPTION*)Buffer.data(); + if (Description->lpDescription != NULL) + { + OutInfo.Description = WideToUtf8(std::wstring(Description->lpDescription)); + } + + return {}; +} + +std::error_code +StartService(std::string_view ServiceName) +{ + // Get a handle to the SCM database. + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_CONNECT); // default access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Get a handle to the service. + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + SC_HANDLE schService = OpenService(schSCManager, // SCM database + Name.c_str(), // name of service + SERVICE_START); // need start access + + if (schService == NULL) + { + return MakeErrorCodeFromLastError(); + } + auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); + + // Start the service. + + if (!::StartService(schService, 0, NULL)) + { + return MakeErrorCodeFromLastError(); + } + + return {}; +} + +std::error_code +StopService(std::string_view ServiceName) +{ + // Get a handle to the SCM database. + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_CONNECT); // default access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Get a handle to the service. + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + SC_HANDLE schService = OpenService(schSCManager, // SCM database + Name.c_str(), // name of service + SERVICE_STOP); // need start access + + if (schService == NULL) + { + return MakeErrorCodeFromLastError(); + } + auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); + + // Stop the service. + SERVICE_STATUS ServiceStatus; + if (!::ControlService(schService, SERVICE_CONTROL_STOP, &ServiceStatus)) + { + return MakeErrorCodeFromLastError(); + } + + return {}; +} +#else +std::error_code +InstallService(const std::filesystem::path& ExecutablePath, + std::string_view CommandLineOptions, + std::string_view ServiceName, + std::string_view ServiceDisplayName, + std::string_view ServiceDescription) +{ + ZEN_UNUSED(ExecutablePath, CommandLineOptions, ServiceName, ServiceDisplayName, ServiceDescription); + ZEN_NOT_IMPLEMENTED("InstallService"); + return {}; +} + +std::error_code +UninstallService(std::string_view ServiceName) +{ + ZEN_UNUSED(ServiceName); + ZEN_NOT_IMPLEMENTED("UninstallService"); + return {}; +} + +std::error_code +QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutStatus) +{ + ZEN_UNUSED(ServiceName, OutStatus); + ZEN_NOT_IMPLEMENTED("QueryInstalledService"); + return {}; +} + +std::error_code +StartService(std::string_view ServiceName) +{ + ZEN_UNUSED(ServiceName); + ZEN_NOT_IMPLEMENTED("StartService"); + return {}; +} + +std::error_code +StopService(std::string_view ServiceName) +{ + ZEN_UNUSED(ServiceName); + ZEN_NOT_IMPLEMENTED("StopService"); + return {}; +} + +#endif +} // namespace zen -- cgit v1.2.3 From f5e723c33b7fb022a6191e05e4df98b883b0cbb4 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Wed, 28 Aug 2024 10:47:49 +0200 Subject: wip --- src/zenutil/service.cpp | 753 ++++++++++++++++++++++++++++-------------------- 1 file changed, 433 insertions(+), 320 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index 840fe3104..cf0c8ef50 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -16,378 +16,491 @@ using namespace std::literals; std::string_view ToString(ServiceStatus Status) { - switch (Status) - { - case ServiceStatus::NotInstalled: - return "Not installed"sv; - case ServiceStatus::Starting: - return "Starting"sv; - case ServiceStatus::Running: - return "Running"sv; - case ServiceStatus::Stopping: - return "Stopping"sv; - case ServiceStatus::Stopped: - return "Stopped"sv; - case ServiceStatus::Pausing: - return "Pausing"sv; - case ServiceStatus::Paused: - return "Paused"sv; - case ServiceStatus::Resuming: - return "Resuming"sv; - default: - ZEN_ASSERT(false); - return ""sv; - } + switch (Status) + { + case ServiceStatus::NotInstalled: + return "Not installed"sv; + case ServiceStatus::Starting: + return "Starting"sv; + case ServiceStatus::Running: + return "Running"sv; + case ServiceStatus::Stopping: + return "Stopping"sv; + case ServiceStatus::Stopped: + return "Stopped"sv; + case ServiceStatus::Pausing: + return "Pausing"sv; + case ServiceStatus::Paused: + return "Paused"sv; + case ServiceStatus::Resuming: + return "Resuming"sv; + default: + ZEN_ASSERT(false); + return ""sv; + } } #if ZEN_PLATFORM_WINDOWS std::error_code InstallService(const std::filesystem::path& ExecutablePath, - std::string_view CommandLineOptions, - std::string_view ServiceName, - std::string_view ServiceDisplayName, - std::string_view ServiceDescription) + std::string_view CommandLineOptions, + std::string_view ServiceName, + std::string_view ServiceDisplayName, + std::string_view ServiceDescription) { - // Get a handle to the SCM database. - - SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer - NULL, // ServicesActive database - SC_MANAGER_ALL_ACCESS); // full access rights - - if (NULL == schSCManager) - { - return MakeErrorCodeFromLastError(); - } - - auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); - - // Create the service - - ExtendableWideStringBuilder<128> Name; - Utf8ToWide(ServiceName, Name); - - ExtendableWideStringBuilder<128> DisplayName; - Utf8ToWide(ServiceDisplayName, DisplayName); - - ExtendableWideStringBuilder<128> Path; - Path.Append(ExecutablePath.c_str()); - if (!CommandLineOptions.empty()) - { - Path.AppendAscii(" "); - Utf8ToWide(CommandLineOptions, Path); - } - - SC_HANDLE schService = CreateService(schSCManager, // SCM database - Name.c_str(), // name of service - DisplayName.c_str(), // service name to display - SERVICE_ALL_ACCESS, // desired access - SERVICE_WIN32_OWN_PROCESS, // service type - SERVICE_DEMAND_START, // start type - SERVICE_ERROR_NORMAL, // error control type - Path.c_str(), // path to service's binary - NULL, // no load ordering group - NULL, // no tag identifier - NULL, // no dependencies - NULL, // LocalSystem account - NULL); // no password - - if (schService == NULL) - { - return MakeErrorCodeFromLastError(); - } - - if (!ServiceDescription.empty()) - { - ExtendableWideStringBuilder<128> DescriptionBuilder; - Utf8ToWide(ServiceDescription, DescriptionBuilder); - - SERVICE_DESCRIPTION Description; - Description.lpDescription = const_cast(DescriptionBuilder.c_str()); - if (!ChangeServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, &Description)) - { - return MakeErrorCodeFromLastError(); - } - } - - CloseServiceHandle(schService); - - return {}; + // Get a handle to the SCM database. + + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_ALL_ACCESS); // full access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Create the service + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + ExtendableWideStringBuilder<128> DisplayName; + Utf8ToWide(ServiceDisplayName, DisplayName); + + ExtendableWideStringBuilder<128> Path; + Path.Append(ExecutablePath.c_str()); + if (!CommandLineOptions.empty()) + { + Path.AppendAscii(" "); + Utf8ToWide(CommandLineOptions, Path); + } + + SC_HANDLE schService = CreateService(schSCManager, // SCM database + Name.c_str(), // name of service + DisplayName.c_str(), // service name to display + SERVICE_ALL_ACCESS, // desired access + SERVICE_WIN32_OWN_PROCESS, // service type + SERVICE_DEMAND_START, // start type + SERVICE_ERROR_NORMAL, // error control type + Path.c_str(), // path to service's binary + NULL, // no load ordering group + NULL, // no tag identifier + NULL, // no dependencies + NULL, // LocalSystem account + NULL); // no password + + if (schService == NULL) + { + return MakeErrorCodeFromLastError(); + } + + if (!ServiceDescription.empty()) + { + ExtendableWideStringBuilder<128> DescriptionBuilder; + Utf8ToWide(ServiceDescription, DescriptionBuilder); + + SERVICE_DESCRIPTION Description; + Description.lpDescription = const_cast(DescriptionBuilder.c_str()); + if (!ChangeServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, &Description)) + { + return MakeErrorCodeFromLastError(); + } + } + + CloseServiceHandle(schService); + + return {}; } std::error_code UninstallService(std::string_view ServiceName) { - // Get a handle to the SCM database. - SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer - NULL, // ServicesActive database - SC_MANAGER_ALL_ACCESS); // full access rights - - if (NULL == schSCManager) - { - return MakeErrorCodeFromLastError(); - } - - auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); - - // Get a handle to the service. - - ExtendableWideStringBuilder<128> Name; - Utf8ToWide(ServiceName, Name); - - SC_HANDLE schService = OpenService(schSCManager, // SCM database - Name.c_str(), // name of service - DELETE); // need delete access - - if (schService == NULL) - { - DWORD Error = ::GetLastError(); - if (Error == ERROR_SERVICE_DOES_NOT_EXIST) - { - return {}; - } - return MakeErrorCode(Error); - } - auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); - - // Delete the service. - - if (!DeleteService(schService)) - { - return MakeErrorCodeFromLastError(); - } - - return {}; + // Get a handle to the SCM database. + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_ALL_ACCESS); // full access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Get a handle to the service. + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + SC_HANDLE schService = OpenService(schSCManager, // SCM database + Name.c_str(), // name of service + DELETE); // need delete access + + if (schService == NULL) + { + DWORD Error = ::GetLastError(); + if (Error == ERROR_SERVICE_DOES_NOT_EXIST) + { + return {}; + } + return MakeErrorCode(Error); + } + auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); + + // Delete the service. + + if (!DeleteService(schService)) + { + return MakeErrorCodeFromLastError(); + } + + return {}; } std::error_code QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) { - // Get a handle to the SCM database. - SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer - NULL, // ServicesActive database - SC_MANAGER_CONNECT); // standard access rights - - if (NULL == schSCManager) - { - return MakeErrorCodeFromLastError(); - } - - auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); - - // Get a handle to the service. - - ExtendableWideStringBuilder<128> Name; - Utf8ToWide(ServiceName, Name); - - SC_HANDLE schService = OpenService(schSCManager, // SCM database - Name.c_str(), // name of service - SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); // need delete access - - if (schService == NULL) - { - DWORD Error = ::GetLastError(); - if (Error == ERROR_SERVICE_DOES_NOT_EXIST) - { - OutInfo.Status = ServiceStatus::NotInstalled; - return {}; - } - return MakeErrorCode(Error); - } - auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); - - std::vector Buffer(8192); - QUERY_SERVICE_CONFIG* ServiceConfig = reinterpret_cast(Buffer.data()); - DWORD BytesNeeded = 0; - if (!QueryServiceConfig(schService, ServiceConfig, (DWORD)Buffer.size(), &BytesNeeded)) - { - return MakeErrorCodeFromLastError(); - } - - OutInfo.ExecutablePath = std::filesystem::path(ServiceConfig->lpBinaryPathName); - OutInfo.DisplayName = WideToUtf8(ServiceConfig->lpDisplayName); - - SERVICE_STATUS ServiceStatus; - if (!::QueryServiceStatus(schService, &ServiceStatus)) - { - return MakeErrorCodeFromLastError(); - } - - switch (ServiceStatus.dwCurrentState) - { - case SERVICE_STOPPED: - OutInfo.Status = ServiceStatus::Stopped; - break; - case SERVICE_START_PENDING: - OutInfo.Status = ServiceStatus::Starting; - break; - case SERVICE_STOP_PENDING: - OutInfo.Status = ServiceStatus::Stopping; - break; - case SERVICE_RUNNING: - OutInfo.Status = ServiceStatus::Running; - break; - case SERVICE_CONTINUE_PENDING: - OutInfo.Status = ServiceStatus::Resuming; - break; - case SERVICE_PAUSE_PENDING: - OutInfo.Status = ServiceStatus::Pausing; - break; - case SERVICE_PAUSED: - OutInfo.Status = ServiceStatus::Paused; - break; - default: - throw std::runtime_error(fmt::format("Unknown service status for '{}': {}", ServiceName, ServiceStatus.dwCurrentState)); - } - - if (!QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, Buffer.data(), (DWORD)Buffer.size(), &BytesNeeded)) - { - DWORD Error = ::GetLastError(); - if (Error == ERROR_INSUFFICIENT_BUFFER) - { - Buffer.resize((size_t)BytesNeeded); - if (!QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, Buffer.data(), (DWORD)Buffer.size(), &BytesNeeded)) - { - return MakeErrorCodeFromLastError(); - } - } - else - { - return MakeErrorCode(Error); - } - } - SERVICE_DESCRIPTION* Description = (SERVICE_DESCRIPTION*)Buffer.data(); - if (Description->lpDescription != NULL) - { - OutInfo.Description = WideToUtf8(std::wstring(Description->lpDescription)); - } - - return {}; + // Get a handle to the SCM database. + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_CONNECT); // standard access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Get a handle to the service. + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + SC_HANDLE schService = OpenService(schSCManager, // SCM database + Name.c_str(), // name of service + SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); // need delete access + + if (schService == NULL) + { + DWORD Error = ::GetLastError(); + if (Error == ERROR_SERVICE_DOES_NOT_EXIST) + { + OutInfo.Status = ServiceStatus::NotInstalled; + return {}; + } + return MakeErrorCode(Error); + } + auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); + + std::vector Buffer(8192); + QUERY_SERVICE_CONFIG* ServiceConfig = reinterpret_cast(Buffer.data()); + DWORD BytesNeeded = 0; + if (!QueryServiceConfig(schService, ServiceConfig, (DWORD)Buffer.size(), &BytesNeeded)) + { + return MakeErrorCodeFromLastError(); + } + + OutInfo.ExecutablePath = std::filesystem::path(ServiceConfig->lpBinaryPathName); + OutInfo.DisplayName = WideToUtf8(ServiceConfig->lpDisplayName); + + SERVICE_STATUS ServiceStatus; + if (!::QueryServiceStatus(schService, &ServiceStatus)) + { + return MakeErrorCodeFromLastError(); + } + + switch (ServiceStatus.dwCurrentState) + { + case SERVICE_STOPPED: + OutInfo.Status = ServiceStatus::Stopped; + break; + case SERVICE_START_PENDING: + OutInfo.Status = ServiceStatus::Starting; + break; + case SERVICE_STOP_PENDING: + OutInfo.Status = ServiceStatus::Stopping; + break; + case SERVICE_RUNNING: + OutInfo.Status = ServiceStatus::Running; + break; + case SERVICE_CONTINUE_PENDING: + OutInfo.Status = ServiceStatus::Resuming; + break; + case SERVICE_PAUSE_PENDING: + OutInfo.Status = ServiceStatus::Pausing; + break; + case SERVICE_PAUSED: + OutInfo.Status = ServiceStatus::Paused; + break; + default: + throw std::runtime_error(fmt::format("Unknown service status for '{}': {}", ServiceName, ServiceStatus.dwCurrentState)); + } + + if (!QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, Buffer.data(), (DWORD)Buffer.size(), &BytesNeeded)) + { + DWORD Error = ::GetLastError(); + if (Error == ERROR_INSUFFICIENT_BUFFER) + { + Buffer.resize((size_t)BytesNeeded); + if (!QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, Buffer.data(), (DWORD)Buffer.size(), &BytesNeeded)) + { + return MakeErrorCodeFromLastError(); + } + } + else + { + return MakeErrorCode(Error); + } + } + SERVICE_DESCRIPTION* Description = (SERVICE_DESCRIPTION*)Buffer.data(); + if (Description->lpDescription != NULL) + { + OutInfo.Description = WideToUtf8(std::wstring(Description->lpDescription)); + } + + return {}; } std::error_code StartService(std::string_view ServiceName) { - // Get a handle to the SCM database. - SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer - NULL, // ServicesActive database - SC_MANAGER_CONNECT); // default access rights - - if (NULL == schSCManager) - { - return MakeErrorCodeFromLastError(); - } - - auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); - - // Get a handle to the service. - - ExtendableWideStringBuilder<128> Name; - Utf8ToWide(ServiceName, Name); - - SC_HANDLE schService = OpenService(schSCManager, // SCM database - Name.c_str(), // name of service - SERVICE_START); // need start access - - if (schService == NULL) - { - return MakeErrorCodeFromLastError(); - } - auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); - - // Start the service. - - if (!::StartService(schService, 0, NULL)) - { - return MakeErrorCodeFromLastError(); - } - - return {}; + // Get a handle to the SCM database. + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_CONNECT); // default access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Get a handle to the service. + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + SC_HANDLE schService = OpenService(schSCManager, // SCM database + Name.c_str(), // name of service + SERVICE_START); // need start access + + if (schService == NULL) + { + return MakeErrorCodeFromLastError(); + } + auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); + + // Start the service. + + if (!::StartService(schService, 0, NULL)) + { + return MakeErrorCodeFromLastError(); + } + + return {}; } std::error_code StopService(std::string_view ServiceName) { - // Get a handle to the SCM database. - SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer - NULL, // ServicesActive database - SC_MANAGER_CONNECT); // default access rights - - if (NULL == schSCManager) - { - return MakeErrorCodeFromLastError(); - } - - auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); - - // Get a handle to the service. - - ExtendableWideStringBuilder<128> Name; - Utf8ToWide(ServiceName, Name); - - SC_HANDLE schService = OpenService(schSCManager, // SCM database - Name.c_str(), // name of service - SERVICE_STOP); // need start access - - if (schService == NULL) - { - return MakeErrorCodeFromLastError(); - } - auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); - - // Stop the service. - SERVICE_STATUS ServiceStatus; - if (!::ControlService(schService, SERVICE_CONTROL_STOP, &ServiceStatus)) - { - return MakeErrorCodeFromLastError(); - } - - return {}; + // Get a handle to the SCM database. + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_CONNECT); // default access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Get a handle to the service. + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + SC_HANDLE schService = OpenService(schSCManager, // SCM database + Name.c_str(), // name of service + SERVICE_STOP); // need start access + + if (schService == NULL) + { + return MakeErrorCodeFromLastError(); + } + auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); + + // Stop the service. + SERVICE_STATUS ServiceStatus; + if (!::ControlService(schService, SERVICE_CONTROL_STOP, &ServiceStatus)) + { + return MakeErrorCodeFromLastError(); + } + + return {}; } #else std::error_code InstallService(const std::filesystem::path& ExecutablePath, - std::string_view CommandLineOptions, - std::string_view ServiceName, - std::string_view ServiceDisplayName, - std::string_view ServiceDescription) + std::string_view CommandLineOptions, + std::string_view ServiceName, + std::string_view ServiceDisplayName, + std::string_view ServiceDescription) { - ZEN_UNUSED(ExecutablePath, CommandLineOptions, ServiceName, ServiceDisplayName, ServiceDescription); - ZEN_NOT_IMPLEMENTED("InstallService"); - return {}; + ZEN_UNUSED(ExecutablePath, CommandLineOptions, ServiceName, ServiceDisplayName, ServiceDescription); + ZEN_NOT_IMPLEMENTED("InstallService"); + return {}; } std::error_code UninstallService(std::string_view ServiceName) { - ZEN_UNUSED(ServiceName); - ZEN_NOT_IMPLEMENTED("UninstallService"); - return {}; + ZEN_UNUSED(ServiceName); + ZEN_NOT_IMPLEMENTED("UninstallService"); + return {}; } std::error_code QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutStatus) { - ZEN_UNUSED(ServiceName, OutStatus); - ZEN_NOT_IMPLEMENTED("QueryInstalledService"); - return {}; + ZEN_UNUSED(ServiceName, OutStatus); + ZEN_NOT_IMPLEMENTED("QueryInstalledService"); + return {}; } std::error_code StartService(std::string_view ServiceName) { - ZEN_UNUSED(ServiceName); - ZEN_NOT_IMPLEMENTED("StartService"); - return {}; + ZEN_UNUSED(ServiceName); + ZEN_NOT_IMPLEMENTED("StartService"); + return {}; } std::error_code StopService(std::string_view ServiceName) { - ZEN_UNUSED(ServiceName); - ZEN_NOT_IMPLEMENTED("StopService"); - return {}; + ZEN_UNUSED(ServiceName); + ZEN_NOT_IMPLEMENTED("StopService"); + return {}; } -#endif +#endif // ZEN_PLATFORM_WINDOWS + +#if ZEN_PLATFORM_MAC + +std::error_code +InstallService(const std::filesystem::path& ExecutablePath, + std::string_view CommandLineOptions, + std::string_view ServiceName, + std::string_view ServiceDisplayName, + std::string_view ServiceDescription) +{ + const char* PlistTemplate = + "" + "" + "" + "" + "Label" + "{}" // ServiceName + "ProgramArguments" + "" + "{}" // Program name + "{}" // "arg\n" * number of arguments + "" + "KeepAlive" + "" +// "Sockets" +// "" +// "Listeners" +// "" +// "SockServiceName" +// "{}" // Listen socket +// "SockType" +// "tcp" +// "SockFamily" +// "IPv4" +// "" +// "" + "StandardOutPath" + "/var/log/myjob.log" + "StandardErrorPath" + "/var/log/myjob.log" + "Debug" + "<{}/>" + + "" + ""; + + std::string ExecutableName = ExecutablePath.filename(); + + const bool Debug = true; + + bool IsQuote = false; + size_t Start = 0; + ExtendableStringBuilder<256> CommandLineOptions; + for (size_t Offset = 0; Offset < CommandLineOptions.length(); Offset++) + { + switch(CommandLineOptions[Offset]) + { + case ' ': + if (IsQuote) + { + continue; + } + else if (Offset > Start) + { + CommandLineOptions.Append(""); + CommandLineOptions.Append(CommandLineOptions.substr(Start, Offset - Start)); + CommandLineOptions.Append("\n"); + Start = Offset + 1; + } + break; + case '"': + if (IsQuite) + { + IsQuote = false; + if (Offset - Start > 1) + { + CommandLineOptions.Append(""); + CommandLineOptions.Append(CommandLineOptions.substr(Start + 1, (Offset - 1) - (Start + 1)); + CommandLineOptions.Append("\n"); + } + Start = Offset + 1; + } + else{ + IsQuite = true; + } + break; + default: + break; + } + } + ZEN_ASSERT(!IsQuote); + if (Offset > Start) + { + CommandLineOptions.Append(""); + CommandLineOptions.Append(CommandLineOptions.substr(Start, Offset - Start)); + CommandLineOptions.Append("\n"); + } + + ForEachStrTok(CommandLineOptions, ' ', <#Fn &&Func#>) + + std::string PList = fmt::format(PlistTemplate, ServiceName,ExecutableName, CommandLineOptions,debug ? "true" : "false"); + + std::filesystem::path SymLink = std::filesystem::path("/usr/local/libexec") / ExecutableName; + if (symlink(ExecutablePath.c_str(), SymLink.c_str()) == -1) + { + return MakeErrorCodeFromLastError(); + } + return {}; +} + + +#endif // ZEN_PLATFORM_MAC } // namespace zen + -- cgit v1.2.3 From d108042c4e654cb79cbb843630c8059b823d63b7 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Wed, 28 Aug 2024 11:42:35 +0200 Subject: more WIP --- src/zenutil/service.cpp | 931 ++++++++++++++++++++++++++---------------------- 1 file changed, 499 insertions(+), 432 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index cf0c8ef50..cce154988 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -9,498 +9,565 @@ #if ZEN_PLATFORM_WINDOWS # include #endif +#if ZEN_PLATFORM_MAC +# include +#endif namespace zen { using namespace std::literals; +#if ZEN_PLATFORM_MAC + +namespace { + std::vector SplitArguments(std::string_view Arguments) + { + bool IsQuote = false; + size_t Start = 0; + size_t Offset = 0; + std::vector Result; + for (; Offset < Arguments.length(); Offset++) + { + switch (Arguments[Offset]) + { + case ' ': + if (IsQuote) + { + continue; + } + else if (Offset > Start) + { + Result.push_back(Arguments.substr(Start, Offset - Start)); + Start = Offset + 1; + } + break; + case '"': + if (IsQuote) + { + IsQuote = false; + if (Offset - Start > 1) + { + Result.push_back(Arguments.substr(Start + 1, Offset - (Start + 1))); + } + Start = Offset + 1; + } + else + { + IsQuote = true; + } + break; + case '=': + if (IsQuote) + { + continue; + } + else if (Offset > Start) + { + Result.push_back(Arguments.substr(Start, Offset - Start)); + Start = Offset + 1; + } + break; + default: + break; + } + } + ZEN_ASSERT(!IsQuote); + if (Offset > Start) + { + Result.push_back(Arguments.substr(Start, Offset - Start)); + } + return Result; + } + + // Needs special character escaping + void AppendEscaped(std::string_view String, StringBuilderBase& SB) + { + size_t Offset = 0; + while (Offset < String.length()) + { + size_t NextEscapeCharacter = String.find_first_of("\"'<>&", Offset); + if (NextEscapeCharacter == std::string_view::npos) + { + break; + } + if (NextEscapeCharacter > Offset) + { + SB.Append(String.substr(Offset, NextEscapeCharacter - Offset)); + } + switch (String[NextEscapeCharacter]) + { + case '"': + SB.Append("""); + break; + case '\'': + SB.Append("&apos"); + break; + case '<': + SB.Append("<"); + break; + case '>': + SB.Append(">"); + break; + case '&': + SB.Append("&"); + break; + default: + ZEN_ASSERT(false); + break; + } + Offset = NextEscapeCharacter + 1; + } + if (Offset == 0) + { + SB.Append(String); + } + else if (String.length() > Offset) + { + SB.Append(String.substr(Offset)); + } + } + + std::string BuildPlist(const std::filesystem::path& ExecutablePath, + std::string_view CommandLineOptions, + std::string_view ServiceName, + std::string_view /*ServiceDisplayName*/, + std::string_view /*ServiceDescription*/, + bool Debug) + { + std::vector Arguments = SplitArguments(CommandLineOptions); + ExtendableStringBuilder<256> ProgramArguments; + for (const std::string_view Argument : Arguments) + { + ProgramArguments.Append("\n"); + AppendEscaped(Argument, ProgramArguments); + ProgramArguments.Append("\n\n"); + } + + return fmt::format( + "\n" + "\n" + "\n" + " \n" + " Label\n" + " {}\n" // ServiceName + " ProgramArguments\n" + " \n" + " {}\n" // Program name + " {}" // "arg\n" * number of arguments + " \n" + " KeepAlive\n" + " \n" + " StandardOutPath\n" + " /var/log/myjob.log\n" + " StandardErrorPath\n" + " /var/log/myjob.log\n" + " Debug\n" + " <{}/>\n" + " \n" + "\n", + ServiceName, + ExecutablePath.filename().string(), + ProgramArguments.ToView(), + Debug ? "true"sv : "false"sv); + + // "Sockets" + // "" + // "Listeners" + // "" + // "SockServiceName" + // "{}" // Listen socket + // "SockType" + // "tcp" + // "SockFamily" + // "IPv4" + // "" + // "" + } +} // namespace + +#endif // ZEN_PLATFORM_MAC + std::string_view ToString(ServiceStatus Status) { - switch (Status) - { - case ServiceStatus::NotInstalled: - return "Not installed"sv; - case ServiceStatus::Starting: - return "Starting"sv; - case ServiceStatus::Running: - return "Running"sv; - case ServiceStatus::Stopping: - return "Stopping"sv; - case ServiceStatus::Stopped: - return "Stopped"sv; - case ServiceStatus::Pausing: - return "Pausing"sv; - case ServiceStatus::Paused: - return "Paused"sv; - case ServiceStatus::Resuming: - return "Resuming"sv; - default: - ZEN_ASSERT(false); - return ""sv; - } + switch (Status) + { + case ServiceStatus::NotInstalled: + return "Not installed"sv; + case ServiceStatus::Starting: + return "Starting"sv; + case ServiceStatus::Running: + return "Running"sv; + case ServiceStatus::Stopping: + return "Stopping"sv; + case ServiceStatus::Stopped: + return "Stopped"sv; + case ServiceStatus::Pausing: + return "Pausing"sv; + case ServiceStatus::Paused: + return "Paused"sv; + case ServiceStatus::Resuming: + return "Resuming"sv; + default: + ZEN_ASSERT(false); + return ""sv; + } } #if ZEN_PLATFORM_WINDOWS std::error_code InstallService(const std::filesystem::path& ExecutablePath, - std::string_view CommandLineOptions, - std::string_view ServiceName, - std::string_view ServiceDisplayName, - std::string_view ServiceDescription) + std::string_view CommandLineOptions, + std::string_view ServiceName, + std::string_view ServiceDisplayName, + std::string_view ServiceDescription) { - // Get a handle to the SCM database. - - SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer - NULL, // ServicesActive database - SC_MANAGER_ALL_ACCESS); // full access rights - - if (NULL == schSCManager) - { - return MakeErrorCodeFromLastError(); - } - - auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); - - // Create the service - - ExtendableWideStringBuilder<128> Name; - Utf8ToWide(ServiceName, Name); - - ExtendableWideStringBuilder<128> DisplayName; - Utf8ToWide(ServiceDisplayName, DisplayName); - - ExtendableWideStringBuilder<128> Path; - Path.Append(ExecutablePath.c_str()); - if (!CommandLineOptions.empty()) - { - Path.AppendAscii(" "); - Utf8ToWide(CommandLineOptions, Path); - } - - SC_HANDLE schService = CreateService(schSCManager, // SCM database - Name.c_str(), // name of service - DisplayName.c_str(), // service name to display - SERVICE_ALL_ACCESS, // desired access - SERVICE_WIN32_OWN_PROCESS, // service type - SERVICE_DEMAND_START, // start type - SERVICE_ERROR_NORMAL, // error control type - Path.c_str(), // path to service's binary - NULL, // no load ordering group - NULL, // no tag identifier - NULL, // no dependencies - NULL, // LocalSystem account - NULL); // no password - - if (schService == NULL) - { - return MakeErrorCodeFromLastError(); - } - - if (!ServiceDescription.empty()) - { - ExtendableWideStringBuilder<128> DescriptionBuilder; - Utf8ToWide(ServiceDescription, DescriptionBuilder); - - SERVICE_DESCRIPTION Description; - Description.lpDescription = const_cast(DescriptionBuilder.c_str()); - if (!ChangeServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, &Description)) - { - return MakeErrorCodeFromLastError(); - } - } - - CloseServiceHandle(schService); - - return {}; + // Get a handle to the SCM database. + + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_ALL_ACCESS); // full access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Create the service + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + ExtendableWideStringBuilder<128> DisplayName; + Utf8ToWide(ServiceDisplayName, DisplayName); + + ExtendableWideStringBuilder<128> Path; + Path.Append(ExecutablePath.c_str()); + if (!CommandLineOptions.empty()) + { + Path.AppendAscii(" "); + Utf8ToWide(CommandLineOptions, Path); + } + + SC_HANDLE schService = CreateService(schSCManager, // SCM database + Name.c_str(), // name of service + DisplayName.c_str(), // service name to display + SERVICE_ALL_ACCESS, // desired access + SERVICE_WIN32_OWN_PROCESS, // service type + SERVICE_DEMAND_START, // start type + SERVICE_ERROR_NORMAL, // error control type + Path.c_str(), // path to service's binary + NULL, // no load ordering group + NULL, // no tag identifier + NULL, // no dependencies + NULL, // LocalSystem account + NULL); // no password + + if (schService == NULL) + { + return MakeErrorCodeFromLastError(); + } + + if (!ServiceDescription.empty()) + { + ExtendableWideStringBuilder<128> DescriptionBuilder; + Utf8ToWide(ServiceDescription, DescriptionBuilder); + + SERVICE_DESCRIPTION Description; + Description.lpDescription = const_cast(DescriptionBuilder.c_str()); + if (!ChangeServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, &Description)) + { + return MakeErrorCodeFromLastError(); + } + } + + CloseServiceHandle(schService); + + return {}; } std::error_code UninstallService(std::string_view ServiceName) { - // Get a handle to the SCM database. - SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer - NULL, // ServicesActive database - SC_MANAGER_ALL_ACCESS); // full access rights - - if (NULL == schSCManager) - { - return MakeErrorCodeFromLastError(); - } - - auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); - - // Get a handle to the service. - - ExtendableWideStringBuilder<128> Name; - Utf8ToWide(ServiceName, Name); - - SC_HANDLE schService = OpenService(schSCManager, // SCM database - Name.c_str(), // name of service - DELETE); // need delete access - - if (schService == NULL) - { - DWORD Error = ::GetLastError(); - if (Error == ERROR_SERVICE_DOES_NOT_EXIST) - { - return {}; - } - return MakeErrorCode(Error); - } - auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); - - // Delete the service. - - if (!DeleteService(schService)) - { - return MakeErrorCodeFromLastError(); - } - - return {}; + // Get a handle to the SCM database. + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_ALL_ACCESS); // full access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Get a handle to the service. + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + SC_HANDLE schService = OpenService(schSCManager, // SCM database + Name.c_str(), // name of service + DELETE); // need delete access + + if (schService == NULL) + { + DWORD Error = ::GetLastError(); + if (Error == ERROR_SERVICE_DOES_NOT_EXIST) + { + return {}; + } + return MakeErrorCode(Error); + } + auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); + + // Delete the service. + + if (!DeleteService(schService)) + { + return MakeErrorCodeFromLastError(); + } + + return {}; } std::error_code QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) { - // Get a handle to the SCM database. - SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer - NULL, // ServicesActive database - SC_MANAGER_CONNECT); // standard access rights - - if (NULL == schSCManager) - { - return MakeErrorCodeFromLastError(); - } - - auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); - - // Get a handle to the service. - - ExtendableWideStringBuilder<128> Name; - Utf8ToWide(ServiceName, Name); - - SC_HANDLE schService = OpenService(schSCManager, // SCM database - Name.c_str(), // name of service - SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); // need delete access - - if (schService == NULL) - { - DWORD Error = ::GetLastError(); - if (Error == ERROR_SERVICE_DOES_NOT_EXIST) - { - OutInfo.Status = ServiceStatus::NotInstalled; - return {}; - } - return MakeErrorCode(Error); - } - auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); - - std::vector Buffer(8192); - QUERY_SERVICE_CONFIG* ServiceConfig = reinterpret_cast(Buffer.data()); - DWORD BytesNeeded = 0; - if (!QueryServiceConfig(schService, ServiceConfig, (DWORD)Buffer.size(), &BytesNeeded)) - { - return MakeErrorCodeFromLastError(); - } - - OutInfo.ExecutablePath = std::filesystem::path(ServiceConfig->lpBinaryPathName); - OutInfo.DisplayName = WideToUtf8(ServiceConfig->lpDisplayName); - - SERVICE_STATUS ServiceStatus; - if (!::QueryServiceStatus(schService, &ServiceStatus)) - { - return MakeErrorCodeFromLastError(); - } - - switch (ServiceStatus.dwCurrentState) - { - case SERVICE_STOPPED: - OutInfo.Status = ServiceStatus::Stopped; - break; - case SERVICE_START_PENDING: - OutInfo.Status = ServiceStatus::Starting; - break; - case SERVICE_STOP_PENDING: - OutInfo.Status = ServiceStatus::Stopping; - break; - case SERVICE_RUNNING: - OutInfo.Status = ServiceStatus::Running; - break; - case SERVICE_CONTINUE_PENDING: - OutInfo.Status = ServiceStatus::Resuming; - break; - case SERVICE_PAUSE_PENDING: - OutInfo.Status = ServiceStatus::Pausing; - break; - case SERVICE_PAUSED: - OutInfo.Status = ServiceStatus::Paused; - break; - default: - throw std::runtime_error(fmt::format("Unknown service status for '{}': {}", ServiceName, ServiceStatus.dwCurrentState)); - } - - if (!QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, Buffer.data(), (DWORD)Buffer.size(), &BytesNeeded)) - { - DWORD Error = ::GetLastError(); - if (Error == ERROR_INSUFFICIENT_BUFFER) - { - Buffer.resize((size_t)BytesNeeded); - if (!QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, Buffer.data(), (DWORD)Buffer.size(), &BytesNeeded)) - { - return MakeErrorCodeFromLastError(); - } - } - else - { - return MakeErrorCode(Error); - } - } - SERVICE_DESCRIPTION* Description = (SERVICE_DESCRIPTION*)Buffer.data(); - if (Description->lpDescription != NULL) - { - OutInfo.Description = WideToUtf8(std::wstring(Description->lpDescription)); - } - - return {}; + // Get a handle to the SCM database. + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_CONNECT); // standard access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Get a handle to the service. + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + SC_HANDLE schService = OpenService(schSCManager, // SCM database + Name.c_str(), // name of service + SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); // need delete access + + if (schService == NULL) + { + DWORD Error = ::GetLastError(); + if (Error == ERROR_SERVICE_DOES_NOT_EXIST) + { + OutInfo.Status = ServiceStatus::NotInstalled; + return {}; + } + return MakeErrorCode(Error); + } + auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); + + std::vector Buffer(8192); + QUERY_SERVICE_CONFIG* ServiceConfig = reinterpret_cast(Buffer.data()); + DWORD BytesNeeded = 0; + if (!QueryServiceConfig(schService, ServiceConfig, (DWORD)Buffer.size(), &BytesNeeded)) + { + return MakeErrorCodeFromLastError(); + } + + OutInfo.ExecutablePath = std::filesystem::path(ServiceConfig->lpBinaryPathName); + OutInfo.DisplayName = WideToUtf8(ServiceConfig->lpDisplayName); + + SERVICE_STATUS ServiceStatus; + if (!::QueryServiceStatus(schService, &ServiceStatus)) + { + return MakeErrorCodeFromLastError(); + } + + switch (ServiceStatus.dwCurrentState) + { + case SERVICE_STOPPED: + OutInfo.Status = ServiceStatus::Stopped; + break; + case SERVICE_START_PENDING: + OutInfo.Status = ServiceStatus::Starting; + break; + case SERVICE_STOP_PENDING: + OutInfo.Status = ServiceStatus::Stopping; + break; + case SERVICE_RUNNING: + OutInfo.Status = ServiceStatus::Running; + break; + case SERVICE_CONTINUE_PENDING: + OutInfo.Status = ServiceStatus::Resuming; + break; + case SERVICE_PAUSE_PENDING: + OutInfo.Status = ServiceStatus::Pausing; + break; + case SERVICE_PAUSED: + OutInfo.Status = ServiceStatus::Paused; + break; + default: + throw std::runtime_error(fmt::format("Unknown service status for '{}': {}", ServiceName, ServiceStatus.dwCurrentState)); + } + + if (!QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, Buffer.data(), (DWORD)Buffer.size(), &BytesNeeded)) + { + DWORD Error = ::GetLastError(); + if (Error == ERROR_INSUFFICIENT_BUFFER) + { + Buffer.resize((size_t)BytesNeeded); + if (!QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, Buffer.data(), (DWORD)Buffer.size(), &BytesNeeded)) + { + return MakeErrorCodeFromLastError(); + } + } + else + { + return MakeErrorCode(Error); + } + } + SERVICE_DESCRIPTION* Description = (SERVICE_DESCRIPTION*)Buffer.data(); + if (Description->lpDescription != NULL) + { + OutInfo.Description = WideToUtf8(std::wstring(Description->lpDescription)); + } + + return {}; } std::error_code StartService(std::string_view ServiceName) { - // Get a handle to the SCM database. - SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer - NULL, // ServicesActive database - SC_MANAGER_CONNECT); // default access rights - - if (NULL == schSCManager) - { - return MakeErrorCodeFromLastError(); - } - - auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); - - // Get a handle to the service. - - ExtendableWideStringBuilder<128> Name; - Utf8ToWide(ServiceName, Name); - - SC_HANDLE schService = OpenService(schSCManager, // SCM database - Name.c_str(), // name of service - SERVICE_START); // need start access - - if (schService == NULL) - { - return MakeErrorCodeFromLastError(); - } - auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); - - // Start the service. - - if (!::StartService(schService, 0, NULL)) - { - return MakeErrorCodeFromLastError(); - } - - return {}; + // Get a handle to the SCM database. + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_CONNECT); // default access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Get a handle to the service. + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + SC_HANDLE schService = OpenService(schSCManager, // SCM database + Name.c_str(), // name of service + SERVICE_START); // need start access + + if (schService == NULL) + { + return MakeErrorCodeFromLastError(); + } + auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); + + // Start the service. + + if (!::StartService(schService, 0, NULL)) + { + return MakeErrorCodeFromLastError(); + } + + return {}; } std::error_code StopService(std::string_view ServiceName) { - // Get a handle to the SCM database. - SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer - NULL, // ServicesActive database - SC_MANAGER_CONNECT); // default access rights - - if (NULL == schSCManager) - { - return MakeErrorCodeFromLastError(); - } - - auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); - - // Get a handle to the service. - - ExtendableWideStringBuilder<128> Name; - Utf8ToWide(ServiceName, Name); - - SC_HANDLE schService = OpenService(schSCManager, // SCM database - Name.c_str(), // name of service - SERVICE_STOP); // need start access - - if (schService == NULL) - { - return MakeErrorCodeFromLastError(); - } - auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); - - // Stop the service. - SERVICE_STATUS ServiceStatus; - if (!::ControlService(schService, SERVICE_CONTROL_STOP, &ServiceStatus)) - { - return MakeErrorCodeFromLastError(); - } - - return {}; + // Get a handle to the SCM database. + SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_CONNECT); // default access rights + + if (NULL == schSCManager) + { + return MakeErrorCodeFromLastError(); + } + + auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); }); + + // Get a handle to the service. + + ExtendableWideStringBuilder<128> Name; + Utf8ToWide(ServiceName, Name); + + SC_HANDLE schService = OpenService(schSCManager, // SCM database + Name.c_str(), // name of service + SERVICE_STOP); // need start access + + if (schService == NULL) + { + return MakeErrorCodeFromLastError(); + } + auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); + + // Stop the service. + SERVICE_STATUS ServiceStatus; + if (!::ControlService(schService, SERVICE_CONTROL_STOP, &ServiceStatus)) + { + return MakeErrorCodeFromLastError(); + } + + return {}; } #else std::error_code InstallService(const std::filesystem::path& ExecutablePath, - std::string_view CommandLineOptions, - std::string_view ServiceName, - std::string_view ServiceDisplayName, - std::string_view ServiceDescription) + std::string_view CommandLineOptions, + std::string_view ServiceName, + std::string_view ServiceDisplayName, + std::string_view ServiceDescription) { - ZEN_UNUSED(ExecutablePath, CommandLineOptions, ServiceName, ServiceDisplayName, ServiceDescription); - ZEN_NOT_IMPLEMENTED("InstallService"); - return {}; + std::filesystem::path SymLink = std::filesystem::path("/usr/local/libexec") / ExecutablePath.filename(); + if (symlink(ExecutablePath.c_str(), SymLink.c_str()) == -1) + { + return MakeErrorCodeFromLastError(); + } + std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); + std::string PList = BuildPlist(ExecutablePath, CommandLineOptions, DaemonName, ServiceDisplayName, ServiceDescription, true); + zen::WriteFile(std::filesystem::path("/Library/LaunchDaemons") / (DaemonName + ".plist"), IoBuffer(PList.data(), PList.size())); + return {}; } std::error_code UninstallService(std::string_view ServiceName) { - ZEN_UNUSED(ServiceName); - ZEN_NOT_IMPLEMENTED("UninstallService"); - return {}; + ZEN_UNUSED(ServiceName); + ZEN_NOT_IMPLEMENTED("UninstallService"); + return {}; } std::error_code QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutStatus) { - ZEN_UNUSED(ServiceName, OutStatus); - ZEN_NOT_IMPLEMENTED("QueryInstalledService"); - return {}; + ZEN_UNUSED(ServiceName, OutStatus); + ZEN_NOT_IMPLEMENTED("QueryInstalledService"); + return {}; } std::error_code StartService(std::string_view ServiceName) { - ZEN_UNUSED(ServiceName); - ZEN_NOT_IMPLEMENTED("StartService"); - return {}; + ZEN_UNUSED(ServiceName); + ZEN_NOT_IMPLEMENTED("StartService"); + return {}; } std::error_code StopService(std::string_view ServiceName) { - ZEN_UNUSED(ServiceName); - ZEN_NOT_IMPLEMENTED("StopService"); - return {}; + ZEN_UNUSED(ServiceName); + ZEN_NOT_IMPLEMENTED("StopService"); + return {}; } -#endif // ZEN_PLATFORM_WINDOWS +#endif // ZEN_PLATFORM_WINDOWS -#if ZEN_PLATFORM_MAC - -std::error_code -InstallService(const std::filesystem::path& ExecutablePath, - std::string_view CommandLineOptions, - std::string_view ServiceName, - std::string_view ServiceDisplayName, - std::string_view ServiceDescription) -{ - const char* PlistTemplate = - "" - "" - "" - "" - "Label" - "{}" // ServiceName - "ProgramArguments" - "" - "{}" // Program name - "{}" // "arg\n" * number of arguments - "" - "KeepAlive" - "" -// "Sockets" -// "" -// "Listeners" -// "" -// "SockServiceName" -// "{}" // Listen socket -// "SockType" -// "tcp" -// "SockFamily" -// "IPv4" -// "" -// "" - "StandardOutPath" - "/var/log/myjob.log" - "StandardErrorPath" - "/var/log/myjob.log" - "Debug" - "<{}/>" - - "" - ""; - - std::string ExecutableName = ExecutablePath.filename(); - - const bool Debug = true; - - bool IsQuote = false; - size_t Start = 0; - ExtendableStringBuilder<256> CommandLineOptions; - for (size_t Offset = 0; Offset < CommandLineOptions.length(); Offset++) - { - switch(CommandLineOptions[Offset]) - { - case ' ': - if (IsQuote) - { - continue; - } - else if (Offset > Start) - { - CommandLineOptions.Append(""); - CommandLineOptions.Append(CommandLineOptions.substr(Start, Offset - Start)); - CommandLineOptions.Append("\n"); - Start = Offset + 1; - } - break; - case '"': - if (IsQuite) - { - IsQuote = false; - if (Offset - Start > 1) - { - CommandLineOptions.Append(""); - CommandLineOptions.Append(CommandLineOptions.substr(Start + 1, (Offset - 1) - (Start + 1)); - CommandLineOptions.Append("\n"); - } - Start = Offset + 1; - } - else{ - IsQuite = true; - } - break; - default: - break; - } - } - ZEN_ASSERT(!IsQuote); - if (Offset > Start) - { - CommandLineOptions.Append(""); - CommandLineOptions.Append(CommandLineOptions.substr(Start, Offset - Start)); - CommandLineOptions.Append("\n"); - } - - ForEachStrTok(CommandLineOptions, ' ', <#Fn &&Func#>) - - std::string PList = fmt::format(PlistTemplate, ServiceName,ExecutableName, CommandLineOptions,debug ? "true" : "false"); - - std::filesystem::path SymLink = std::filesystem::path("/usr/local/libexec") / ExecutableName; - if (symlink(ExecutablePath.c_str(), SymLink.c_str()) == -1) - { - return MakeErrorCodeFromLastError(); - } - return {}; -} - - -#endif // ZEN_PLATFORM_MAC } // namespace zen - -- cgit v1.2.3 From 69ddcf16ae53ded7856f2c46505d55fb44b61f11 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Wed, 28 Aug 2024 12:55:33 +0200 Subject: launchd plist install --- src/zenutil/service.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index cce154988..8ac35d969 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -11,6 +11,9 @@ #endif #if ZEN_PLATFORM_MAC # include + +# include +# include #endif namespace zen { @@ -137,9 +140,9 @@ namespace { ExtendableStringBuilder<256> ProgramArguments; for (const std::string_view Argument : Arguments) { - ProgramArguments.Append("\n"); + ProgramArguments.Append(" "); AppendEscaped(Argument, ProgramArguments); - ProgramArguments.Append("\n\n"); + ProgramArguments.Append("\n"); } return fmt::format( @@ -152,7 +155,7 @@ namespace { " ProgramArguments\n" " \n" " {}\n" // Program name - " {}" // "arg\n" * number of arguments + "{}" // "arg\n" * number of arguments " \n" " KeepAlive\n" " \n" @@ -526,13 +529,21 @@ InstallService(const std::filesystem::path& ExecutablePath, std::string_view ServiceDescription) { std::filesystem::path SymLink = std::filesystem::path("/usr/local/libexec") / ExecutablePath.filename(); + ZEN_INFO("Attempting synlink from {} to {}", ExecutablePath.string(), SymLink.string()); if (symlink(ExecutablePath.c_str(), SymLink.c_str()) == -1) { return MakeErrorCodeFromLastError(); } std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); std::string PList = BuildPlist(ExecutablePath, CommandLineOptions, DaemonName, ServiceDisplayName, ServiceDescription, true); - zen::WriteFile(std::filesystem::path("/Library/LaunchDaemons") / (DaemonName + ".plist"), IoBuffer(PList.data(), PList.size())); + std::filesystem::path PListPath = std::filesystem::path("/Library/LaunchDaemons") / (DaemonName + ".plist"); + ZEN_INFO("Writing launchd plist to {}", PListPath.string()); + zen::WriteFile(PListPath, IoBuffer(IoBuffer::Wrap, PList.data(), PList.size())); + ZEN_INFO("Changing permissions to 0555 for {}", PListPath.string()); + if (chmod(PListPath.c_str(), 0555) == -1) + { + return MakeErrorCodeFromLastError(); + } return {}; } -- cgit v1.2.3 From 614ecb211ba5c630ad0a23c4127e57e1fbb93d91 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Wed, 28 Aug 2024 12:56:18 +0200 Subject: clang format --- src/zenutil/service.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index 8ac35d969..637974c13 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -155,7 +155,7 @@ namespace { " ProgramArguments\n" " \n" " {}\n" // Program name - "{}" // "arg\n" * number of arguments + "{}" // "arg\n" * number of arguments " \n" " KeepAlive\n" " \n" @@ -534,8 +534,8 @@ InstallService(const std::filesystem::path& ExecutablePath, { return MakeErrorCodeFromLastError(); } - std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); - std::string PList = BuildPlist(ExecutablePath, CommandLineOptions, DaemonName, ServiceDisplayName, ServiceDescription, true); + std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); + std::string PList = BuildPlist(ExecutablePath, CommandLineOptions, DaemonName, ServiceDisplayName, ServiceDescription, true); std::filesystem::path PListPath = std::filesystem::path("/Library/LaunchDaemons") / (DaemonName + ".plist"); ZEN_INFO("Writing launchd plist to {}", PListPath.string()); zen::WriteFile(PListPath, IoBuffer(IoBuffer::Wrap, PList.data(), PList.size())); -- cgit v1.2.3 From 54ffc9b9ad7d3d8d32e4de00980d66d4a31f3c09 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Wed, 28 Aug 2024 14:40:03 +0200 Subject: Add ServiceSpec struct --- src/zenutil/service.cpp | 74 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 26 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index 637974c13..870a47df5 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -219,11 +219,7 @@ ToString(ServiceStatus Status) #if ZEN_PLATFORM_WINDOWS std::error_code -InstallService(const std::filesystem::path& ExecutablePath, - std::string_view CommandLineOptions, - std::string_view ServiceName, - std::string_view ServiceDisplayName, - std::string_view ServiceDescription) +InstallService(std::string_view ServiceName, const ServiceSpec& Spec) { // Get a handle to the SCM database. @@ -244,14 +240,14 @@ InstallService(const std::filesystem::path& ExecutablePath, Utf8ToWide(ServiceName, Name); ExtendableWideStringBuilder<128> DisplayName; - Utf8ToWide(ServiceDisplayName, DisplayName); + Utf8ToWide(Spec.DisplayName, DisplayName); ExtendableWideStringBuilder<128> Path; - Path.Append(ExecutablePath.c_str()); - if (!CommandLineOptions.empty()) + Path.Append(Spec.ExecutablePath.c_str()); + if (!Spec.CommandLineOptions.empty()) { Path.AppendAscii(" "); - Utf8ToWide(CommandLineOptions, Path); + Utf8ToWide(Spec.CommandLineOptions, Path); } SC_HANDLE schService = CreateService(schSCManager, // SCM database @@ -273,10 +269,10 @@ InstallService(const std::filesystem::path& ExecutablePath, return MakeErrorCodeFromLastError(); } - if (!ServiceDescription.empty()) + if (!Spec.Description.empty()) { ExtendableWideStringBuilder<128> DescriptionBuilder; - Utf8ToWide(ServiceDescription, DescriptionBuilder); + Utf8ToWide(Spec.Description, DescriptionBuilder); SERVICE_DESCRIPTION Description; Description.lpDescription = const_cast(DescriptionBuilder.c_str()); @@ -380,8 +376,38 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) return MakeErrorCodeFromLastError(); } - OutInfo.ExecutablePath = std::filesystem::path(ServiceConfig->lpBinaryPathName); - OutInfo.DisplayName = WideToUtf8(ServiceConfig->lpDisplayName); + std::wstring BinaryWithArguments(ServiceConfig->lpBinaryPathName); + if (BinaryWithArguments.size()) + { + if (BinaryWithArguments[0] == '"') + { + std::wstring::size_type ExecutableEnd = BinaryWithArguments.find('"', 1); + if (ExecutableEnd == std::wstring::npos) + { + OutInfo.Spec.ExecutablePath = BinaryWithArguments; + } + else + { + OutInfo.Spec.ExecutablePath = BinaryWithArguments.substr(0, ExecutableEnd + 1); + OutInfo.Spec.CommandLineOptions = + WideToUtf8(BinaryWithArguments.substr(ExecutableEnd + 1 + BinaryWithArguments[ExecutableEnd + 1] == ' ' ? 1 : 0)); + } + } + else + { + std::wstring::size_type ExecutableEnd = BinaryWithArguments.find(' ', 1); + if (ExecutableEnd == std::wstring::npos) + { + OutInfo.Spec.ExecutablePath = BinaryWithArguments; + } + else + { + OutInfo.Spec.ExecutablePath = BinaryWithArguments.substr(0, ExecutableEnd); + OutInfo.Spec.CommandLineOptions = WideToUtf8(BinaryWithArguments.substr(ExecutableEnd + 1)); + } + } + } + OutInfo.Spec.DisplayName = WideToUtf8(ServiceConfig->lpDisplayName); SERVICE_STATUS ServiceStatus; if (!::QueryServiceStatus(schService, &ServiceStatus)) @@ -435,7 +461,7 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) SERVICE_DESCRIPTION* Description = (SERVICE_DESCRIPTION*)Buffer.data(); if (Description->lpDescription != NULL) { - OutInfo.Description = WideToUtf8(std::wstring(Description->lpDescription)); + OutInfo.Spec.Description = WideToUtf8(std::wstring(Description->lpDescription)); } return {}; @@ -522,20 +548,16 @@ StopService(std::string_view ServiceName) } #else std::error_code -InstallService(const std::filesystem::path& ExecutablePath, - std::string_view CommandLineOptions, - std::string_view ServiceName, - std::string_view ServiceDisplayName, - std::string_view ServiceDescription) +InstallService(std::string_view ServiceName, const ServiceSpec& Spec) { - std::filesystem::path SymLink = std::filesystem::path("/usr/local/libexec") / ExecutablePath.filename(); - ZEN_INFO("Attempting synlink from {} to {}", ExecutablePath.string(), SymLink.string()); - if (symlink(ExecutablePath.c_str(), SymLink.c_str()) == -1) + std::filesystem::path SymLink = std::filesystem::path("/usr/local/libexec") / Spec.ExecutablePath.filename(); + ZEN_INFO("Attempting symlink from {} to {}", Spec.ExecutablePath.string(), SymLink.string()); + if (symlink(Spec.ExecutablePath.c_str(), SymLink.c_str()) == -1) { return MakeErrorCodeFromLastError(); } - std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); - std::string PList = BuildPlist(ExecutablePath, CommandLineOptions, DaemonName, ServiceDisplayName, ServiceDescription, true); + std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); + std::string PList = BuildPlist(Spec.ExecutablePath, Spec.CommandLineOptions, DaemonName, Spec.DisplayName, Spec.Description, true); std::filesystem::path PListPath = std::filesystem::path("/Library/LaunchDaemons") / (DaemonName + ".plist"); ZEN_INFO("Writing launchd plist to {}", PListPath.string()); zen::WriteFile(PListPath, IoBuffer(IoBuffer::Wrap, PList.data(), PList.size())); @@ -556,9 +578,9 @@ UninstallService(std::string_view ServiceName) } std::error_code -QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutStatus) +QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) { - ZEN_UNUSED(ServiceName, OutStatus); + ZEN_UNUSED(ServiceName, OutInfo); ZEN_NOT_IMPLEMENTED("QueryInstalledService"); return {}; } -- cgit v1.2.3 From 995aec217bbb26c9c2a701cc77edb067ffbf8d36 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Wed, 8 Jan 2025 13:49:56 +0100 Subject: add ServiceLevel for service processes: User, AllUsers and Service --- src/zenutil/service.cpp | 391 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 356 insertions(+), 35 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index fd96af0c8..44aa50494 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -3,8 +3,14 @@ #include #include +#include #include #include +#include + +ZEN_THIRD_PARTY_INCLUDES_START +#include +ZEN_THIRD_PARTY_INCLUDES_END #if ZEN_PLATFORM_WINDOWS # include @@ -19,6 +25,46 @@ namespace zen { using namespace std::literals; +namespace { + bool SplitExecutableAndArgs(const std::wstring& ExeAndArgs, std::filesystem::path& OutExecutablePath, std::string& OutArguments) + { + if (ExeAndArgs.size()) + { + if (ExeAndArgs[0] == '"') + { + std::wstring::size_type ExecutableEnd = ExeAndArgs.find('"', 1); + if (ExecutableEnd == std::wstring::npos) + { + OutExecutablePath = ExeAndArgs; + return true; + } + else + { + OutExecutablePath = ExeAndArgs.substr(0, ExecutableEnd + 1); + OutArguments = WideToUtf8(ExeAndArgs.substr(ExecutableEnd + 1 + ExeAndArgs[ExecutableEnd + 1] == ' ' ? 1 : 0)); + return true; + } + } + else + { + std::wstring::size_type ExecutableEnd = ExeAndArgs.find(' ', 1); + if (ExecutableEnd == std::wstring::npos) + { + OutExecutablePath = ExeAndArgs; + return true; + } + else + { + OutExecutablePath = ExeAndArgs.substr(0, ExecutableEnd); + OutArguments = WideToUtf8(ExeAndArgs.substr(ExecutableEnd + 1)); + return true; + } + } + } + return false; + } +} // namespace + #if ZEN_PLATFORM_MAC namespace { @@ -201,6 +247,44 @@ namespace { #endif // ZEN_PLATFORM_MAC +std::string_view +ToString(ServiceLevel Level) +{ + switch (Level) + { + case ServiceLevel::CurrentUser: + return "Current User"sv; + case ServiceLevel::AllUsers: + return "All Users"sv; + case ServiceLevel::SystemService: + return "System Service"sv; + default: + ZEN_ASSERT(false); + return ""sv; + } +} + +std::optional +FromString(const std::string& Level) +{ + if (StrCaseCompare(Level.c_str(), std::string(ToString(ServiceLevel::CurrentUser)).c_str()) == 0 || + StrCaseCompare(Level.c_str(), "CurrentUser") == 0) + { + return ServiceLevel::CurrentUser; + } + if (StrCaseCompare(Level.c_str(), std::string(ToString(ServiceLevel::AllUsers)).c_str()) == 0 || + StrCaseCompare(Level.c_str(), "AllUsers") == 0) + { + return ServiceLevel::AllUsers; + } + if (StrCaseCompare(Level.c_str(), std::string(ToString(ServiceLevel::SystemService)).c_str()) == 0 || + StrCaseCompare(Level.c_str(), "SystemService") == 0) + { + return ServiceLevel::SystemService; + } + return {}; +} + std::string_view ToString(ServiceStatus Status) { @@ -231,7 +315,36 @@ ToString(ServiceStatus Status) #if ZEN_PLATFORM_WINDOWS std::error_code -InstallService(std::string_view ServiceName, const ServiceSpec& Spec) +InstallRunService(std::string_view ServiceName, const ServiceSpec& Spec) +{ + HKEY RegKey = NULL; + if (LSTATUS Status = RegOpenKey(Spec.ServiceLevel == ServiceLevel::AllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, + L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", + &RegKey); + Status == ERROR_SUCCESS) + { + auto _ = MakeGuard([&]() { RegCloseKey(RegKey); }); + std::wstring PathIncludingArgs = Spec.ExecutablePath.wstring(); + if (!Spec.CommandLineOptions.empty()) + { + PathIncludingArgs += (L" " + Utf8ToWide(Spec.CommandLineOptions)); + } + if (Status = RegSetValueEx(RegKey, + Utf8ToWide(ServiceName).c_str(), + 0, + REG_SZ, + (BYTE*)PathIncludingArgs.c_str(), + DWORD((PathIncludingArgs.length() * 2) + 1)); + Status == ERROR_SUCCESS) + { + return {}; + } + } + return MakeErrorCodeFromLastError(); +} + +std::error_code +InstallSystemService(std::string_view ServiceName, const ServiceSpec& Spec) { // Get a handle to the SCM database. @@ -300,7 +413,40 @@ InstallService(std::string_view ServiceName, const ServiceSpec& Spec) } std::error_code -UninstallService(std::string_view ServiceName) +InstallService(std::string_view ServiceName, const ServiceSpec& Spec) +{ + if (Spec.ServiceLevel == ServiceLevel::SystemService) + { + return InstallSystemService(ServiceName, Spec); + } + return InstallRunService(ServiceName, Spec); +} + +std::error_code +UninstallRunService(bool AllUsers, std::string_view ServiceName) +{ + HKEY RegKey = NULL; + if (LSTATUS Status = + RegOpenKey(AllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", &RegKey); + Status == ERROR_SUCCESS) + { + auto _ = MakeGuard([&]() { RegCloseKey(RegKey); }); + TCHAR Value[4096]; + DWORD Type; + DWORD Size = sizeof(Value); + if (ERROR_SUCCESS == RegQueryValueEx(RegKey, Utf8ToWide(ServiceName).c_str(), 0, &Type, (BYTE*)Value, &Size)) + { + if (Status = RegDeleteValue(RegKey, Utf8ToWide(ServiceName).c_str()); Status == ERROR_SUCCESS) + { + return std::error_code{}; + } + } + } + return MakeErrorCodeFromLastError(); +} + +std::error_code +UninstallSystemService(std::string_view ServiceName) { // Get a handle to the SCM database. SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer @@ -345,7 +491,57 @@ UninstallService(std::string_view ServiceName) } std::error_code -QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) +UninstallService(std::string_view ServiceName, ServiceLevel Level) +{ + switch (Level) + { + case ServiceLevel::CurrentUser: + return UninstallRunService(/*AllUsers*/ false, ServiceName); + case ServiceLevel::AllUsers: + return UninstallRunService(/*AllUsers*/ true, ServiceName); + case ServiceLevel::SystemService: + return UninstallSystemService(ServiceName); + default: + ZEN_ASSERT(false); + return {}; + } +} + +bool +QueryRunServiceStatus(bool AllUsers, std::string_view ServiceName, ServiceInfo& OutInfo) +{ + HKEY RegKey = NULL; + if (ERROR_SUCCESS == + RegOpenKey(AllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", &RegKey)) + { + auto _ = MakeGuard([&]() { RegCloseKey(RegKey); }); + TCHAR Value[4096]; + DWORD Type; + DWORD Size = sizeof(Value); + if (ERROR_SUCCESS == RegQueryValueEx(RegKey, Utf8ToWide(ServiceName).c_str(), 0, &Type, (BYTE*)Value, &Size)) + { + OutInfo.Spec.ServiceLevel = AllUsers ? ServiceLevel::AllUsers : ServiceLevel::CurrentUser; + std::wstring PathIncludingArgs(Value); + + (void)SplitExecutableAndArgs(PathIncludingArgs, OutInfo.Spec.ExecutablePath, OutInfo.Spec.CommandLineOptions); + + OutInfo.Spec.DisplayName = ServiceName; + OutInfo.Spec.Description = ""; + OutInfo.Status = ServiceStatus::Stopped; + ProcessHandle Process; + std::error_code Ec = FindProcess(OutInfo.Spec.ExecutablePath, Process); + if (!Ec) + { + OutInfo.Status = ServiceStatus::Running; + } + return true; + } + } + return false; +} + +std::error_code +QuerySystemServiceStatus(std::string_view ServiceName, ServiceInfo& OutInfo) { // Get a handle to the SCM database. SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer @@ -389,36 +585,7 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) } std::wstring BinaryWithArguments(ServiceConfig->lpBinaryPathName); - if (BinaryWithArguments.size()) - { - if (BinaryWithArguments[0] == '"') - { - std::wstring::size_type ExecutableEnd = BinaryWithArguments.find('"', 1); - if (ExecutableEnd == std::wstring::npos) - { - OutInfo.Spec.ExecutablePath = BinaryWithArguments; - } - else - { - OutInfo.Spec.ExecutablePath = BinaryWithArguments.substr(0, ExecutableEnd + 1); - OutInfo.Spec.CommandLineOptions = - WideToUtf8(BinaryWithArguments.substr(ExecutableEnd + 1 + BinaryWithArguments[ExecutableEnd + 1] == ' ' ? 1 : 0)); - } - } - else - { - std::wstring::size_type ExecutableEnd = BinaryWithArguments.find(' ', 1); - if (ExecutableEnd == std::wstring::npos) - { - OutInfo.Spec.ExecutablePath = BinaryWithArguments; - } - else - { - OutInfo.Spec.ExecutablePath = BinaryWithArguments.substr(0, ExecutableEnd); - OutInfo.Spec.CommandLineOptions = WideToUtf8(BinaryWithArguments.substr(ExecutableEnd + 1)); - } - } - } + (void)SplitExecutableAndArgs(BinaryWithArguments, OutInfo.Spec.ExecutablePath, OutInfo.Spec.CommandLineOptions); OutInfo.Spec.DisplayName = WideToUtf8(ServiceConfig->lpDisplayName); SERVICE_STATUS ServiceStatus; @@ -480,7 +647,54 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) } std::error_code -StartService(std::string_view ServiceName) +QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) +{ + if (QueryRunServiceStatus(/*AllUsers*/ false, ServiceName, OutInfo)) + { + return {}; + } + if (QueryRunServiceStatus(/*AllUsers*/ true, ServiceName, OutInfo)) + { + return {}; + } + return QuerySystemServiceStatus(ServiceName, OutInfo); +} + +std::error_code +StartRunService(bool AllUsers, std::string_view ServiceName) +{ + HKEY RegKey = NULL; + if (ERROR_SUCCESS == + RegOpenKey(AllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", &RegKey)) + { + auto _ = MakeGuard([&]() { RegCloseKey(RegKey); }); + TCHAR Value[4096]; + DWORD Type; + DWORD Size = sizeof(Value); + if (ERROR_SUCCESS == RegQueryValueEx(RegKey, Utf8ToWide(ServiceName).c_str(), 0, &Type, (BYTE*)Value, &Size)) + { + std::wstring PathIncludingArgs(Value); + + std::filesystem::path ExecutablePath; + std::string CommandLineOptions; + if (SplitExecutableAndArgs(PathIncludingArgs, ExecutablePath, CommandLineOptions)) + { + ProcessHandle Proc; + Proc.Initialize(CreateProc(ExecutablePath, WideToUtf8(PathIncludingArgs), {.Flags = CreateProcOptions::Flag_NoConsole})); + if (Proc.IsValid()) + { + return {}; + } + MakeErrorCode(ERROR_PATH_NOT_FOUND); + } + MakeErrorCode(ERROR_INVALID_PARAMETER); + } + } + return MakeErrorCodeFromLastError(); +} + +std::error_code +StartSystemService(std::string_view ServiceName) { // Get a handle to the SCM database. SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer @@ -520,7 +734,96 @@ StartService(std::string_view ServiceName) } std::error_code -StopService(std::string_view ServiceName) +StartService(std::string_view ServiceName, ServiceLevel Level) +{ + switch (Level) + { + case ServiceLevel::CurrentUser: + return StartRunService(/*AllUsers*/ false, ServiceName); + case ServiceLevel::AllUsers: + return StartRunService(/*AllUsers*/ true, ServiceName); + case ServiceLevel::SystemService: + return StartSystemService(ServiceName); + default: + ZEN_ASSERT(false); + return {}; + } +} + +std::error_code +StopRunService(bool AllUsers, std::string_view ServiceName) +{ + HKEY RegKey = NULL; + if (ERROR_SUCCESS == + RegOpenKey(AllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", &RegKey)) + { + auto _ = MakeGuard([&]() { RegCloseKey(RegKey); }); + TCHAR Value[4096]; + DWORD Type; + DWORD Size = sizeof(Value); + if (ERROR_SUCCESS == RegQueryValueEx(RegKey, Utf8ToWide(ServiceName).c_str(), 0, &Type, (BYTE*)Value, &Size)) + { + std::wstring PathIncludingArgs(Value); + + std::filesystem::path ExecutablePath; + std::string CommandLineOptions; + if (SplitExecutableAndArgs(PathIncludingArgs, ExecutablePath, CommandLineOptions)) + { + ProcessHandle Proc; + std::error_code Ec = FindProcess(ExecutablePath, Proc); + if (Ec) + { + return Ec; + } + else + { + // This is hacky and checks if the running service is a zenserver instance and tries to shut down using the shutdown + // event + ExtendableStringBuilder<32> ChildShutdownEventName; + ZenServerState State; + if (State.InitializeReadOnly()) + { + State.Snapshot([&](const ZenServerState::ZenServerEntry& Entry) { + if (Entry.Pid == gsl::narrow(Proc.Pid())) + { + ChildShutdownEventName << "Zen_" << Entry.EffectiveListenPort; + ChildShutdownEventName << "_Shutdown"; + } + }); + if (ChildShutdownEventName.Size() > 0) + { + NamedEvent Event(ChildShutdownEventName); + Ec = Event.Set(); + if (Ec) + { + return Ec; + } + return {}; + } + } + // This only works for a running process that does not already have a console attached - zenserver does have one + // hence the attempt to shut down using event above + if (AttachConsole(Proc.Pid())) + { + if (SetConsoleCtrlHandler(NULL, TRUE)) + { + if (GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) + { + return {}; + } + } + } + return MakeErrorCodeFromLastError(); + } + } + return MakeErrorCode(ERROR_INVALID_PARAMETER); + } + } + return MakeErrorCodeFromLastError(); +} + +std::error_code +StopSystemService(std::string_view ServiceName) { // Get a handle to the SCM database. SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer @@ -558,6 +861,24 @@ StopService(std::string_view ServiceName) return {}; } + +std::error_code +StopService(std::string_view ServiceName, ServiceLevel Level) +{ + switch (Level) + { + case ServiceLevel::CurrentUser: + return StopRunService(/*AllUsers*/ false, ServiceName); + case ServiceLevel::AllUsers: + return StopRunService(/*AllUsers*/ true, ServiceName); + case ServiceLevel::SystemService: + return StopSystemService(ServiceName); + default: + ZEN_ASSERT(false); + return {}; + } +} + #else # if 0 -- cgit v1.2.3 From 2696ea2322dfdd7b7a5b08843b6d8aca9aedd741 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Wed, 8 Jan 2025 14:21:11 +0100 Subject: use attachconsole to send ctrl+c to running process --- src/zenutil/service.cpp | 83 ++++++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 42 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index 44aa50494..3d7808bba 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -6,11 +6,6 @@ #include #include #include -#include - -ZEN_THIRD_PARTY_INCLUDES_START -#include -ZEN_THIRD_PARTY_INCLUDES_END #if ZEN_PLATFORM_WINDOWS # include @@ -750,6 +745,46 @@ StartService(std::string_view ServiceName, ServiceLevel Level) } } +std::error_code +SendSignalToProcess(DWORD dwProcessId, DWORD dwCtrlEvent) +{ + std::error_code Ec = {}; + DWORD ConsoleId = GetCurrentProcessId(); + // Leave current console if it exists + // (otherwise AttachConsole will return ERROR_ACCESS_DENIED) + bool ConsoleDetached = FreeConsole() != FALSE; + + if (AttachConsole(dwProcessId) != FALSE) + { + // Add a fake Ctrl-C handler for avoid instant kill is this console + // WARNING: do not revert it or current program will be also killed + SetConsoleCtrlHandler(nullptr, true); + if (GenerateConsoleCtrlEvent(dwCtrlEvent, 0) == FALSE) + { + Ec = MakeErrorCodeFromLastError(); + } + FreeConsole(); + } + else + { + Ec = MakeErrorCodeFromLastError(); + } + + if (ConsoleDetached) + { + // Create a new console if previous was deleted by OS + if (AttachConsole(ConsoleId) == FALSE) + { + int errorCode = GetLastError(); + if (errorCode == 31) // 31=ERROR_GEN_FAILURE + { + AllocConsole(); + } + } + } + return Ec; +} + std::error_code StopRunService(bool AllUsers, std::string_view ServiceName) { @@ -777,43 +812,7 @@ StopRunService(bool AllUsers, std::string_view ServiceName) } else { - // This is hacky and checks if the running service is a zenserver instance and tries to shut down using the shutdown - // event - ExtendableStringBuilder<32> ChildShutdownEventName; - ZenServerState State; - if (State.InitializeReadOnly()) - { - State.Snapshot([&](const ZenServerState::ZenServerEntry& Entry) { - if (Entry.Pid == gsl::narrow(Proc.Pid())) - { - ChildShutdownEventName << "Zen_" << Entry.EffectiveListenPort; - ChildShutdownEventName << "_Shutdown"; - } - }); - if (ChildShutdownEventName.Size() > 0) - { - NamedEvent Event(ChildShutdownEventName); - Ec = Event.Set(); - if (Ec) - { - return Ec; - } - return {}; - } - } - // This only works for a running process that does not already have a console attached - zenserver does have one - // hence the attempt to shut down using event above - if (AttachConsole(Proc.Pid())) - { - if (SetConsoleCtrlHandler(NULL, TRUE)) - { - if (GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) - { - return {}; - } - } - } - return MakeErrorCodeFromLastError(); + return SendSignalToProcess(Proc.Pid(), CTRL_C_EVENT); } } return MakeErrorCode(ERROR_INVALID_PARAMETER); -- cgit v1.2.3 From 40c2cbe70ee6868606761d205d1d105c846b170f Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Wed, 8 Jan 2025 14:24:42 +0100 Subject: mac function signature fix --- src/zenutil/service.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index 3d7808bba..0bacc03f5 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -951,16 +951,15 @@ InstallService(std::string_view ServiceName, const ServiceSpec& Spec) } std::error_code -UninstallService(std::string_view ServiceName) +UninstallService(std::string_view ServiceName, ServiceLevel Level) { - ZEN_UNUSED(ServiceName); + ZEN_UNUSED(Level); std::filesystem::path ServicePath = std::filesystem::path("/usr/local/libexec") / ServiceName; ZEN_INFO("Attempting to remove service from {}", ServicePath.string()); if (unlink(ServicePath.string().c_str()) == -1) { return MakeErrorCodeFromLastError(); - ; } std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); std::filesystem::path PListPath = std::filesystem::path("/Library/LaunchDaemons") / (DaemonName + ".plist"); @@ -968,7 +967,6 @@ UninstallService(std::string_view ServiceName) if (unlink(ServicePath.string().c_str()) == -1) { return MakeErrorCodeFromLastError(); - ; } return {}; @@ -983,17 +981,17 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) } std::error_code -StartService(std::string_view ServiceName) +StartService(std::string_view ServiceName, ServiceLevel Level) { - ZEN_UNUSED(ServiceName); + ZEN_UNUSED(ServiceName, Level); ZEN_NOT_IMPLEMENTED("StartService"); return {}; } std::error_code -StopService(std::string_view ServiceName) +StopService(std::string_view ServiceName, ServiceLevel Level) { - ZEN_UNUSED(ServiceName); + ZEN_UNUSED(ServiceName, Level); ZEN_NOT_IMPLEMENTED("StopService"); return {}; } -- cgit v1.2.3 From cf4f59b6f7dc60703dea2470a38fa741cba80501 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Thu, 9 Jan 2025 15:13:38 +0100 Subject: partial macos implementation --- src/zenutil/service.cpp | 71 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 12 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index 0bacc03f5..60db75f00 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -15,12 +15,16 @@ # include # include + +#include #endif namespace zen { using namespace std::literals; namespace { +#if ZEN_PLATFORM_WINDOWS + bool SplitExecutableAndArgs(const std::wstring& ExeAndArgs, std::filesystem::path& OutExecutablePath, std::string& OutArguments) { if (ExeAndArgs.size()) @@ -58,11 +62,10 @@ namespace { } return false; } -} // namespace -#if ZEN_PLATFORM_MAC +#endif // ZEN_PLATFORM_WINDOWS -namespace { +#if ZEN_PLATFORM_MAC std::vector SplitArguments(std::string_view Arguments) { bool IsQuote = false; @@ -238,9 +241,10 @@ namespace { // "" // "" } -} // namespace #endif // ZEN_PLATFORM_MAC +} // namespace + std::string_view ToString(ServiceLevel Level) @@ -926,6 +930,22 @@ static int CopyFile(std::filesystem::path source, std::filesystem::path dest) } # endif +std::filesystem::path GetDaemonBasePath(ServiceLevel Level) +{ + switch(Level) + { + case ServiceLevel::CurrentUser: + return GetUserHomeFolder() / "Library/LaunchAgents"; + case ServiceLevel::AllUsers: + return "/Library/LaunchAgents"; + case ServiceLevel::SystemService: + return "/Library/LaunchDaemon"; + default: + ZEN_ASSERT(false); + return {}; + } +} + std::error_code InstallService(std::string_view ServiceName, const ServiceSpec& Spec) { @@ -936,17 +956,37 @@ InstallService(std::string_view ServiceName, const ServiceSpec& Spec) // { // return MakeErrorCodeFromLastError(); // } + + // System: /Library/LaunchDaemons + // All users: /Library/LaunchAgents + // Current user: ~/Library/LaunchAgents + + std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); std::string PList = BuildPlist(ServiceName, Spec.ExecutablePath, Spec.CommandLineOptions, DaemonName, Spec.DisplayName, Spec.Description, true); - std::filesystem::path PListPath = std::filesystem::path("/Users/dan.engelbrecht/Library/LaunchAgents") / (DaemonName + ".plist"); + + std::filesystem::path PListFolder = GetDaemonBasePath(Spec.ServiceLevel); + + std::filesystem::path PListPath = PListFolder / (DaemonName + ".plist"); ZEN_INFO("Writing launchd plist to {}", PListPath.string()); - zen::WriteFile(PListPath, IoBuffer(IoBuffer::Wrap, PList.data(), PList.size())); - ZEN_INFO("Changing permissions to 0555 for {}", PListPath.string()); - // if (chmod(PListPath.string().c_str(), 0555) == -1) - // { - // return MakeErrorCodeFromLastError(); - // } + try + { + zen::WriteFile(PListPath, IoBuffer(IoBuffer::Wrap, PList.data(), PList.size())); + } + catch(const std::system_error& Ex) + { + return MakeErrorCode(Ex.code().value()); + } + + if (Spec.ServiceLevel != ServiceLevel::CurrentUser) // ???? Correct? + { + ZEN_INFO("Changing permissions to 600 for {}", PListPath.string()); + if (chmod(PListPath.string().c_str(), 0600) == -1) + { + return MakeErrorCodeFromLastError(); + } + } return {}; } @@ -976,7 +1016,12 @@ std::error_code QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) { ZEN_UNUSED(ServiceName, OutInfo); - ZEN_NOT_IMPLEMENTED("QueryInstalledService"); +// ZEN_NOT_IMPLEMENTED("QueryInstalledService"); + //std::filesystem::path PListFolder = GetDaemonBasePath(Spec.ServiceLevel); + //sudo launchctl list + + OutInfo.Status = ServiceStatus::NotInstalled; + return {}; } @@ -985,6 +1030,7 @@ StartService(std::string_view ServiceName, ServiceLevel Level) { ZEN_UNUSED(ServiceName, Level); ZEN_NOT_IMPLEMENTED("StartService"); + // sudo launchctl bootstrap system /Library/LaunchDaemon/com.epicgames.unreal.ZenServer.plist return {}; } @@ -993,6 +1039,7 @@ StopService(std::string_view ServiceName, ServiceLevel Level) { ZEN_UNUSED(ServiceName, Level); ZEN_NOT_IMPLEMENTED("StopService"); + // sudo launchctl bootout system /Library/LaunchDaemon/com.epicgames.unreal.ZenServer.plist return {}; } -- cgit v1.2.3 From 9c101d0bdd3c3ada0d263dd57116931af9bcad88 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Fri, 10 Jan 2025 10:11:04 +0100 Subject: remove ServiceLevel --- src/zenutil/service.cpp | 370 +++--------------------------------------------- 1 file changed, 21 insertions(+), 349 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index 60db75f00..34d7c2eb6 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -16,7 +16,7 @@ # include # include -#include +# include #endif namespace zen { @@ -63,7 +63,7 @@ namespace { return false; } -#endif // ZEN_PLATFORM_WINDOWS +#endif // ZEN_PLATFORM_WINDOWS #if ZEN_PLATFORM_MAC std::vector SplitArguments(std::string_view Arguments) @@ -245,45 +245,6 @@ namespace { #endif // ZEN_PLATFORM_MAC } // namespace - -std::string_view -ToString(ServiceLevel Level) -{ - switch (Level) - { - case ServiceLevel::CurrentUser: - return "Current User"sv; - case ServiceLevel::AllUsers: - return "All Users"sv; - case ServiceLevel::SystemService: - return "System Service"sv; - default: - ZEN_ASSERT(false); - return ""sv; - } -} - -std::optional -FromString(const std::string& Level) -{ - if (StrCaseCompare(Level.c_str(), std::string(ToString(ServiceLevel::CurrentUser)).c_str()) == 0 || - StrCaseCompare(Level.c_str(), "CurrentUser") == 0) - { - return ServiceLevel::CurrentUser; - } - if (StrCaseCompare(Level.c_str(), std::string(ToString(ServiceLevel::AllUsers)).c_str()) == 0 || - StrCaseCompare(Level.c_str(), "AllUsers") == 0) - { - return ServiceLevel::AllUsers; - } - if (StrCaseCompare(Level.c_str(), std::string(ToString(ServiceLevel::SystemService)).c_str()) == 0 || - StrCaseCompare(Level.c_str(), "SystemService") == 0) - { - return ServiceLevel::SystemService; - } - return {}; -} - std::string_view ToString(ServiceStatus Status) { @@ -314,36 +275,7 @@ ToString(ServiceStatus Status) #if ZEN_PLATFORM_WINDOWS std::error_code -InstallRunService(std::string_view ServiceName, const ServiceSpec& Spec) -{ - HKEY RegKey = NULL; - if (LSTATUS Status = RegOpenKey(Spec.ServiceLevel == ServiceLevel::AllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, - L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", - &RegKey); - Status == ERROR_SUCCESS) - { - auto _ = MakeGuard([&]() { RegCloseKey(RegKey); }); - std::wstring PathIncludingArgs = Spec.ExecutablePath.wstring(); - if (!Spec.CommandLineOptions.empty()) - { - PathIncludingArgs += (L" " + Utf8ToWide(Spec.CommandLineOptions)); - } - if (Status = RegSetValueEx(RegKey, - Utf8ToWide(ServiceName).c_str(), - 0, - REG_SZ, - (BYTE*)PathIncludingArgs.c_str(), - DWORD((PathIncludingArgs.length() * 2) + 1)); - Status == ERROR_SUCCESS) - { - return {}; - } - } - return MakeErrorCodeFromLastError(); -} - -std::error_code -InstallSystemService(std::string_view ServiceName, const ServiceSpec& Spec) +InstallService(std::string_view ServiceName, const ServiceSpec& Spec) { // Get a handle to the SCM database. @@ -412,40 +344,7 @@ InstallSystemService(std::string_view ServiceName, const ServiceSpec& Spec) } std::error_code -InstallService(std::string_view ServiceName, const ServiceSpec& Spec) -{ - if (Spec.ServiceLevel == ServiceLevel::SystemService) - { - return InstallSystemService(ServiceName, Spec); - } - return InstallRunService(ServiceName, Spec); -} - -std::error_code -UninstallRunService(bool AllUsers, std::string_view ServiceName) -{ - HKEY RegKey = NULL; - if (LSTATUS Status = - RegOpenKey(AllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", &RegKey); - Status == ERROR_SUCCESS) - { - auto _ = MakeGuard([&]() { RegCloseKey(RegKey); }); - TCHAR Value[4096]; - DWORD Type; - DWORD Size = sizeof(Value); - if (ERROR_SUCCESS == RegQueryValueEx(RegKey, Utf8ToWide(ServiceName).c_str(), 0, &Type, (BYTE*)Value, &Size)) - { - if (Status = RegDeleteValue(RegKey, Utf8ToWide(ServiceName).c_str()); Status == ERROR_SUCCESS) - { - return std::error_code{}; - } - } - } - return MakeErrorCodeFromLastError(); -} - -std::error_code -UninstallSystemService(std::string_view ServiceName) +UninstallService(std::string_view ServiceName) { // Get a handle to the SCM database. SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer @@ -490,57 +389,7 @@ UninstallSystemService(std::string_view ServiceName) } std::error_code -UninstallService(std::string_view ServiceName, ServiceLevel Level) -{ - switch (Level) - { - case ServiceLevel::CurrentUser: - return UninstallRunService(/*AllUsers*/ false, ServiceName); - case ServiceLevel::AllUsers: - return UninstallRunService(/*AllUsers*/ true, ServiceName); - case ServiceLevel::SystemService: - return UninstallSystemService(ServiceName); - default: - ZEN_ASSERT(false); - return {}; - } -} - -bool -QueryRunServiceStatus(bool AllUsers, std::string_view ServiceName, ServiceInfo& OutInfo) -{ - HKEY RegKey = NULL; - if (ERROR_SUCCESS == - RegOpenKey(AllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", &RegKey)) - { - auto _ = MakeGuard([&]() { RegCloseKey(RegKey); }); - TCHAR Value[4096]; - DWORD Type; - DWORD Size = sizeof(Value); - if (ERROR_SUCCESS == RegQueryValueEx(RegKey, Utf8ToWide(ServiceName).c_str(), 0, &Type, (BYTE*)Value, &Size)) - { - OutInfo.Spec.ServiceLevel = AllUsers ? ServiceLevel::AllUsers : ServiceLevel::CurrentUser; - std::wstring PathIncludingArgs(Value); - - (void)SplitExecutableAndArgs(PathIncludingArgs, OutInfo.Spec.ExecutablePath, OutInfo.Spec.CommandLineOptions); - - OutInfo.Spec.DisplayName = ServiceName; - OutInfo.Spec.Description = ""; - OutInfo.Status = ServiceStatus::Stopped; - ProcessHandle Process; - std::error_code Ec = FindProcess(OutInfo.Spec.ExecutablePath, Process); - if (!Ec) - { - OutInfo.Status = ServiceStatus::Running; - } - return true; - } - } - return false; -} - -std::error_code -QuerySystemServiceStatus(std::string_view ServiceName, ServiceInfo& OutInfo) +QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) { // Get a handle to the SCM database. SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer @@ -646,54 +495,7 @@ QuerySystemServiceStatus(std::string_view ServiceName, ServiceInfo& OutInfo) } std::error_code -QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) -{ - if (QueryRunServiceStatus(/*AllUsers*/ false, ServiceName, OutInfo)) - { - return {}; - } - if (QueryRunServiceStatus(/*AllUsers*/ true, ServiceName, OutInfo)) - { - return {}; - } - return QuerySystemServiceStatus(ServiceName, OutInfo); -} - -std::error_code -StartRunService(bool AllUsers, std::string_view ServiceName) -{ - HKEY RegKey = NULL; - if (ERROR_SUCCESS == - RegOpenKey(AllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", &RegKey)) - { - auto _ = MakeGuard([&]() { RegCloseKey(RegKey); }); - TCHAR Value[4096]; - DWORD Type; - DWORD Size = sizeof(Value); - if (ERROR_SUCCESS == RegQueryValueEx(RegKey, Utf8ToWide(ServiceName).c_str(), 0, &Type, (BYTE*)Value, &Size)) - { - std::wstring PathIncludingArgs(Value); - - std::filesystem::path ExecutablePath; - std::string CommandLineOptions; - if (SplitExecutableAndArgs(PathIncludingArgs, ExecutablePath, CommandLineOptions)) - { - ProcessHandle Proc; - Proc.Initialize(CreateProc(ExecutablePath, WideToUtf8(PathIncludingArgs), {.Flags = CreateProcOptions::Flag_NoConsole})); - if (Proc.IsValid()) - { - return {}; - } - MakeErrorCode(ERROR_PATH_NOT_FOUND); - } - MakeErrorCode(ERROR_INVALID_PARAMETER); - } - } - return MakeErrorCodeFromLastError(); -} - -std::error_code -StartSystemService(std::string_view ServiceName) +StartService(std::string_view ServiceName) { // Get a handle to the SCM database. SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer @@ -733,100 +535,7 @@ StartSystemService(std::string_view ServiceName) } std::error_code -StartService(std::string_view ServiceName, ServiceLevel Level) -{ - switch (Level) - { - case ServiceLevel::CurrentUser: - return StartRunService(/*AllUsers*/ false, ServiceName); - case ServiceLevel::AllUsers: - return StartRunService(/*AllUsers*/ true, ServiceName); - case ServiceLevel::SystemService: - return StartSystemService(ServiceName); - default: - ZEN_ASSERT(false); - return {}; - } -} - -std::error_code -SendSignalToProcess(DWORD dwProcessId, DWORD dwCtrlEvent) -{ - std::error_code Ec = {}; - DWORD ConsoleId = GetCurrentProcessId(); - // Leave current console if it exists - // (otherwise AttachConsole will return ERROR_ACCESS_DENIED) - bool ConsoleDetached = FreeConsole() != FALSE; - - if (AttachConsole(dwProcessId) != FALSE) - { - // Add a fake Ctrl-C handler for avoid instant kill is this console - // WARNING: do not revert it or current program will be also killed - SetConsoleCtrlHandler(nullptr, true); - if (GenerateConsoleCtrlEvent(dwCtrlEvent, 0) == FALSE) - { - Ec = MakeErrorCodeFromLastError(); - } - FreeConsole(); - } - else - { - Ec = MakeErrorCodeFromLastError(); - } - - if (ConsoleDetached) - { - // Create a new console if previous was deleted by OS - if (AttachConsole(ConsoleId) == FALSE) - { - int errorCode = GetLastError(); - if (errorCode == 31) // 31=ERROR_GEN_FAILURE - { - AllocConsole(); - } - } - } - return Ec; -} - -std::error_code -StopRunService(bool AllUsers, std::string_view ServiceName) -{ - HKEY RegKey = NULL; - if (ERROR_SUCCESS == - RegOpenKey(AllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", &RegKey)) - { - auto _ = MakeGuard([&]() { RegCloseKey(RegKey); }); - TCHAR Value[4096]; - DWORD Type; - DWORD Size = sizeof(Value); - if (ERROR_SUCCESS == RegQueryValueEx(RegKey, Utf8ToWide(ServiceName).c_str(), 0, &Type, (BYTE*)Value, &Size)) - { - std::wstring PathIncludingArgs(Value); - - std::filesystem::path ExecutablePath; - std::string CommandLineOptions; - if (SplitExecutableAndArgs(PathIncludingArgs, ExecutablePath, CommandLineOptions)) - { - ProcessHandle Proc; - std::error_code Ec = FindProcess(ExecutablePath, Proc); - if (Ec) - { - return Ec; - } - else - { - return SendSignalToProcess(Proc.Pid(), CTRL_C_EVENT); - } - } - return MakeErrorCode(ERROR_INVALID_PARAMETER); - } - } - return MakeErrorCodeFromLastError(); -} - -std::error_code -StopSystemService(std::string_view ServiceName) +StopService(std::string_view ServiceName) { // Get a handle to the SCM database. SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer @@ -865,23 +574,6 @@ StopSystemService(std::string_view ServiceName) return {}; } -std::error_code -StopService(std::string_view ServiceName, ServiceLevel Level) -{ - switch (Level) - { - case ServiceLevel::CurrentUser: - return StopRunService(/*AllUsers*/ false, ServiceName); - case ServiceLevel::AllUsers: - return StopRunService(/*AllUsers*/ true, ServiceName); - case ServiceLevel::SystemService: - return StopSystemService(ServiceName); - default: - ZEN_ASSERT(false); - return {}; - } -} - #else # if 0 @@ -930,22 +622,6 @@ static int CopyFile(std::filesystem::path source, std::filesystem::path dest) } # endif -std::filesystem::path GetDaemonBasePath(ServiceLevel Level) -{ - switch(Level) - { - case ServiceLevel::CurrentUser: - return GetUserHomeFolder() / "Library/LaunchAgents"; - case ServiceLevel::AllUsers: - return "/Library/LaunchAgents"; - case ServiceLevel::SystemService: - return "/Library/LaunchDaemon"; - default: - ZEN_ASSERT(false); - return {}; - } -} - std::error_code InstallService(std::string_view ServiceName, const ServiceSpec& Spec) { @@ -956,17 +632,16 @@ InstallService(std::string_view ServiceName, const ServiceSpec& Spec) // { // return MakeErrorCodeFromLastError(); // } - + // System: /Library/LaunchDaemons // All users: /Library/LaunchAgents // Current user: ~/Library/LaunchAgents - std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); std::string PList = BuildPlist(ServiceName, Spec.ExecutablePath, Spec.CommandLineOptions, DaemonName, Spec.DisplayName, Spec.Description, true); - std::filesystem::path PListFolder = GetDaemonBasePath(Spec.ServiceLevel); + std::filesystem::path PListFolder = "/Library/LaunchDaemon"; std::filesystem::path PListPath = PListFolder / (DaemonName + ".plist"); ZEN_INFO("Writing launchd plist to {}", PListPath.string()); @@ -974,24 +649,21 @@ InstallService(std::string_view ServiceName, const ServiceSpec& Spec) { zen::WriteFile(PListPath, IoBuffer(IoBuffer::Wrap, PList.data(), PList.size())); } - catch(const std::system_error& Ex) + catch (const std::system_error& Ex) { return MakeErrorCode(Ex.code().value()); } - - if (Spec.ServiceLevel != ServiceLevel::CurrentUser) // ???? Correct? + + ZEN_INFO("Changing permissions to 600 for {}", PListPath.string()); + if (chmod(PListPath.string().c_str(), 0600) == -1) { - ZEN_INFO("Changing permissions to 600 for {}", PListPath.string()); - if (chmod(PListPath.string().c_str(), 0600) == -1) - { - return MakeErrorCodeFromLastError(); - } + return MakeErrorCodeFromLastError(); } return {}; } std::error_code -UninstallService(std::string_view ServiceName, ServiceLevel Level) +UninstallService(std::string_view ServiceName) { ZEN_UNUSED(Level); @@ -1016,9 +688,9 @@ std::error_code QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) { ZEN_UNUSED(ServiceName, OutInfo); -// ZEN_NOT_IMPLEMENTED("QueryInstalledService"); - //std::filesystem::path PListFolder = GetDaemonBasePath(Spec.ServiceLevel); - //sudo launchctl list + // ZEN_NOT_IMPLEMENTED("QueryInstalledService"); + // std::filesystem::path PListFolder = "/Library/LaunchDaemon"; + // sudo launchctl list OutInfo.Status = ServiceStatus::NotInstalled; @@ -1026,7 +698,7 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) } std::error_code -StartService(std::string_view ServiceName, ServiceLevel Level) +StartService(std::string_view ServiceName) { ZEN_UNUSED(ServiceName, Level); ZEN_NOT_IMPLEMENTED("StartService"); @@ -1035,11 +707,11 @@ StartService(std::string_view ServiceName, ServiceLevel Level) } std::error_code -StopService(std::string_view ServiceName, ServiceLevel Level) +StopService(std::string_view ServiceName) { ZEN_UNUSED(ServiceName, Level); ZEN_NOT_IMPLEMENTED("StopService"); - // sudo launchctl bootout system /Library/LaunchDaemon/com.epicgames.unreal.ZenServer.plist + // sudo launchctl bootout system /Library/LaunchDaemon/com.epicgames.unreal.ZenServer.plist return {}; } -- cgit v1.2.3 From c2cb52446fddbcca417387e2a7ef2ee8ec0a9549 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Fri, 10 Jan 2025 11:01:41 +0100 Subject: use local service account for zen service --- src/zenutil/service.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index 34d7c2eb6..72367a070 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -306,19 +306,19 @@ InstallService(std::string_view ServiceName, const ServiceSpec& Spec) Utf8ToWide(Spec.CommandLineOptions, Path); } - SC_HANDLE schService = CreateService(schSCManager, // SCM database - Name.c_str(), // name of service - DisplayName.c_str(), // service name to display - SERVICE_ALL_ACCESS, // desired access - SERVICE_WIN32_OWN_PROCESS, // service type - SERVICE_DEMAND_START, // start type - SERVICE_ERROR_NORMAL, // error control type - Path.c_str(), // path to service's binary - NULL, // no load ordering group - NULL, // no tag identifier - NULL, // no dependencies - NULL, // LocalSystem account - NULL); // no password + SC_HANDLE schService = CreateService(schSCManager, // SCM database + Name.c_str(), // name of service + DisplayName.c_str(), // service name to display + SERVICE_ALL_ACCESS, // desired access + SERVICE_WIN32_OWN_PROCESS, // service type + SERVICE_AUTO_START, // start type + SERVICE_ERROR_NORMAL, // error control type + Path.c_str(), // path to service's binary + NULL, // no load ordering group + NULL, // no tag identifier + NULL, // no dependencies + TEXT("NT AUTHORITY\\LocalService"), // LocalService account + NULL); // no password if (schService == NULL) { -- cgit v1.2.3 From 5e02ddf6cc3d311cafbd4fcff326ece0141a992e Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Fri, 10 Jan 2025 14:34:49 +0100 Subject: partially working service commands for macos --- src/zenutil/service.cpp | 275 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 250 insertions(+), 25 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index 72367a070..feebeb7f4 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -243,6 +243,110 @@ namespace { } #endif // ZEN_PLATFORM_MAC + + +#if ZEN_PLATFORM_MAC || ZEN_PLATFORM_LINUX + +std::pair ExecuteProgram(std::string_view Cmd) +{ + std::string data; + const int max_buffer = 256; + char buffer[max_buffer]; + std::string Command(Cmd); + Command.append(" 2>&1"); + + FILE * stream = popen(Command.c_str(), "r"); + if (stream) + { + while (!feof(stream)) + { + if (fgets(buffer, max_buffer, stream) != NULL) + { + data.append(buffer); + } + } + + int Res = -1; + int st = pclose(stream); + if (WIFEXITED(st)) Res = WEXITSTATUS(st); + return {Res, data}; + } + return {errno, ""}; + +#if 0 + int in[2], out[2], n, pid; + char buf[255]; + + /* In a pipe, xx[0] is for reading, xx[1] is for writing */ + + if (pipe(in) < 0) + { + return {errno, ""}; + } + if (pipe(out) < 0) + { + close(in[0]); + close(in[1]); + return {errno, ""}; + } + + if ((pid=fork()) == 0) { + /* This is the child process */ + + /* Close stdin, stdout, stderr */ + close(0); + close(1); + close(2); + /* make our pipes, our new stdin,stdout and stderr */ + dup2(in[0],0); + dup2(out[1],1); + dup2(out[1],2); + + /* Close the other ends of the pipes that the parent will use, because if + * we leave these open in the child, the child/parent will not get an EOF + * when the parent/child closes their end of the pipe. + */ + close(in[1]); + close(out[0]); + + va_list args; + va_start(args, Executable); + va_end(args); + + /* Over-write the child process with the hexdump binary */ + execl(Executable, Executable, args); + } + + /* This is the parent process */ + /* Close the pipe ends that the child uses to read from / write to so + * the when we close the others, an EOF will be transmitted properly. + */ + close(in[0]); + + /* Because of the small amount of data, the child may block unless we + * close it's input stream. This sends an EOF to the child on it's + * stdin. + */ + close(in[1]); + + /* Read back any output */ + n = read(out[0], buf, 250); + if (n == 0) + { + n = read(out[1], buf, 250); + } + buf[n] = 0; + close(out[0]); + close(out[1]); + + std::string Output(buf); + return {0, Output}; +#endif // 0 +} + +#endif // ZEN_PLATFORM_MAC || ZEN_PLATFORM_LINUX + + } // namespace std::string_view @@ -633,7 +737,7 @@ InstallService(std::string_view ServiceName, const ServiceSpec& Spec) // return MakeErrorCodeFromLastError(); // } - // System: /Library/LaunchDaemons + // System: /Library/LaunchDaemon // All users: /Library/LaunchAgents // Current user: ~/Library/LaunchAgents @@ -665,34 +769,137 @@ InstallService(std::string_view ServiceName, const ServiceSpec& Spec) std::error_code UninstallService(std::string_view ServiceName) { - ZEN_UNUSED(Level); - - std::filesystem::path ServicePath = std::filesystem::path("/usr/local/libexec") / ServiceName; - ZEN_INFO("Attempting to remove service from {}", ServicePath.string()); - if (unlink(ServicePath.string().c_str()) == -1) - { - return MakeErrorCodeFromLastError(); - } +// std::filesystem::path ServicePath = std::filesystem::path("/usr/local/libexec") / ServiceName; +// ZEN_INFO("Attempting to remove service from {}", ServicePath.string()); +// if (unlink(ServicePath.string().c_str()) == -1) +// { +// return MakeErrorCodeFromLastError(); +// } std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); - std::filesystem::path PListPath = std::filesystem::path("/Library/LaunchDaemons") / (DaemonName + ".plist"); + std::filesystem::path PListPath = std::filesystem::path("/Library/LaunchDaemon") / (DaemonName + ".plist"); ZEN_INFO("Attempting to remove launchd plist from {}", PListPath.string()); - if (unlink(ServicePath.string().c_str()) == -1) - { - return MakeErrorCodeFromLastError(); - } - - return {}; + std::error_code Ec; + std::filesystem::remove(PListPath, Ec); + return Ec; + +// if (unlink(PListPath.string().c_str()) == -1) +// { +// return MakeErrorCodeFromLastError(); +// } + +// return {}; } std::error_code QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) { ZEN_UNUSED(ServiceName, OutInfo); - // ZEN_NOT_IMPLEMENTED("QueryInstalledService"); - // std::filesystem::path PListFolder = "/Library/LaunchDaemon"; - // sudo launchctl list OutInfo.Status = ServiceStatus::NotInstalled; + std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); + + std::filesystem::path PListFolder = "/Library/LaunchDaemon"; + + std::filesystem::path PListPath = PListFolder / (DaemonName + ".plist"); + if (std::filesystem::is_regular_file(PListPath)) + { + OutInfo.Status = ServiceStatus::Stopped; + + { + // Parse plist :( + IoBuffer Buffer = ReadFile(PListPath).Flatten(); + MemoryView Data = Buffer.GetView(); + std::string PList((const char*)Data.GetData(), Data.GetSize()); + ZEN_INFO("{}", PList); + } + { + std::pair Res = ExecuteProgram(std::string("launchctl list ") + DaemonName); + if (Res.first == 0 && !Res.second.empty()) + { +// bool IsParsingArguments = false; + ForEachStrTok(Res.second, '\n', [&](std::string_view Line){ +// if (IsParsingArguments) +// { +// if (Line.find(");") == Line.length() - 2) +// { +// IsParsingArguments = false; +// return true; +// } +// std::string_view::size_type ProgramArgumentsEnd = Line.find_last_of('\"'); +// std::string_view::size_type ProgramArgumentsStart = Line.find('\"') + 1; +// std::string_view ProgramArgumentsString = Line.substr(ProgramArgumentsStart, ProgramArgumentsEnd - ProgramArgumentsStart); +// if (ProgramArgumentsString != OutInfo.Spec.ExecutablePath) +// { +// if (!OutInfo.Spec.CommandLineOptions.empty()) +// { +// OutInfo.Spec.CommandLineOptions += " "; +// } +// OutInfo.Spec.CommandLineOptions += ProgramArgumentsString; +// } +// return true; +// } + if (Line.find("\"PID\"") != std::string_view::npos) + { + std::string_view::size_type PidStart = Line.find('='); + std::string_view::size_type PidEnd = Line.find(';'); + std::string_view PidString = Line.substr(PidStart+2, PidEnd - (PidStart + 2)); + if (ParseInt(PidString).has_value()) + { + OutInfo.Status = ServiceStatus::Running; + } + return false; + } +// if (Line.find("\"Program\"") != std::string_view::npos) +// { +// std::string_view::size_type ProgramEnd = Line.find_last_of('\"'); +// std::string_view::size_type ProgramStart = Line.find_last_of('\"', ProgramEnd - 1) + 1; +// std::string_view ProgramString = Line.substr(ProgramStart, ProgramEnd - ProgramStart); +// OutInfo.Spec.ExecutablePath = ProgramString; +// return true; +// } +// if (Line.find("\"ProgramArguments\"") != std::string_view::npos) +// { +// IsParsingArguments = true; +// return true; +// } + return true; + }); + // Parse installed info + } + } +#if 0 + { + std::pair Res = ExecuteProgram("launchctl list"); + if (Res.first != 0) + { + return MakeErrorCode(Res.first); + } + std::string ZenServerLine; + ForEachStrTok(Res.second, '\n', [&](std::string_view Line){ + if (Line.find(DaemonName) != std::string_view::npos) + { + ZenServerLine = Line; + } + return true; + }); + if (!ZenServerLine.empty()) + { + std::vector Parts; + ForEachStrTok(ZenServerLine, '\t', [&](std::string_view Line){ + Parts.push_back(Line); + return true; + }); + if (Parts.size() == 3) + { + if (Parts[0] != "-" && ParseInt(Parts[0]).has_value()) + { + OutInfo.Status = ServiceStatus::Running; + } + } + } + } +#endif // 0 + } return {}; } @@ -700,18 +907,36 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) std::error_code StartService(std::string_view ServiceName) { - ZEN_UNUSED(ServiceName, Level); - ZEN_NOT_IMPLEMENTED("StartService"); - // sudo launchctl bootstrap system /Library/LaunchDaemon/com.epicgames.unreal.ZenServer.plist + ZEN_UNUSED(ServiceName); + + std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); + std::filesystem::path PListFolder = "/Library/LaunchDaemon"; + std::filesystem::path PListPath = PListFolder / (DaemonName + ".plist"); + + std::pair Res = ExecuteProgram(std::string("launchctl bootstrap system ") + PListPath.string()); + if (Res.first != 0) + { + return MakeErrorCode(Res.first); + } + return {}; } std::error_code StopService(std::string_view ServiceName) { - ZEN_UNUSED(ServiceName, Level); - ZEN_NOT_IMPLEMENTED("StopService"); - // sudo launchctl bootout system /Library/LaunchDaemon/com.epicgames.unreal.ZenServer.plist + ZEN_UNUSED(ServiceName); + + std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); + std::filesystem::path PListFolder = "/Library/LaunchDaemon"; + std::filesystem::path PListPath = PListFolder / (DaemonName + ".plist"); + + std::pair Res = ExecuteProgram(std::string("launchctl bootout system ") + PListPath.string()); + if (Res.first != 0) + { + return MakeErrorCode(Res.first); + } + return {}; } -- cgit v1.2.3 From 782745c3eaf0514172deccd37e443e109d779d5d Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Fri, 10 Jan 2025 15:40:04 +0100 Subject: cleanups --- src/zenutil/service.cpp | 264 +++++++++++++++++++----------------------------- 1 file changed, 105 insertions(+), 159 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index feebeb7f4..08ccac512 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -173,14 +173,26 @@ namespace { } } + std::string GetDaemonName(std::string_view ServiceName) + { + return fmt::format("com.epicgames.unreal.{}", ServiceName); + } + + std::filesystem::path GetPListPath(const std::string& DaemonName) + { + const std::filesystem::path PListFolder = "/Library/LaunchDaemons"; + return PListFolder / (DaemonName + ".plist"); + } + std::string BuildPlist(std::string_view ServiceName, const std::filesystem::path& ExecutablePath, std::string_view CommandLineOptions, std::string_view DaemonName, - std::string_view /*ServiceDisplayName*/, - std::string_view /*ServiceDescription*/, + std::string_view ServiceDisplayName, + std::string_view ServiceDescription, bool Debug) { + ZEN_UNUSED(ServiceDisplayName, ServiceDescription); std::vector Arguments = SplitArguments(CommandLineOptions); ExtendableStringBuilder<256> ProgramArguments; for (const std::string_view Argument : Arguments) @@ -678,76 +690,18 @@ StopService(std::string_view ServiceName) return {}; } -#else - -# if 0 -static int CopyFile(std::filesystem::path source, std::filesystem::path dest) -{ +#endif - int childExitStatus; - pid_t pid; - int status; - - pid = fork(); - - if (pid == 0) { /* child */ - execl("/bin/cp", "/bin/cp", source.string().c_str(), dest.string().c_str(), (char *)0); - return 0; - } - else if (pid < 0) { - return -1; - } - else { - /* parent - wait for child - this has all error handling, you - * could just call wait() as long as you are only expecting to - * have one child process at a time. - */ - pid_t ws = waitpid( pid, &childExitStatus, WNOHANG); - if (ws == -1) - { - return -1; - } - - if( WIFEXITED(childExitStatus)) /* exit code in childExitStatus */ - { - status = WEXITSTATUS(childExitStatus); /* zero is normal exit */ - return status; - } - else if (WIFSIGNALED(childExitStatus)) /* killed */ - { - return -1; - } - else if (WIFSTOPPED(childExitStatus)) /* stopped */ - { - return -1; - } - return -1; - } -} -# endif +#if ZEN_PLATFORM_MAC std::error_code InstallService(std::string_view ServiceName, const ServiceSpec& Spec) { - // std::filesystem::path ServicePath = std::filesystem::path("/usr/local/libexec") / ServiceName; - // ZEN_INFO("Attempting to copy service from {} to {}", Spec.ExecutablePath.string(), ServicePath.string()); - // if (false && CopyFile(Spec.ExecutablePath, ServicePath) == -1) - //// if (symlink(Spec.ExecutablePath.c_str(), ServicePath.c_str()) == -1) - // { - // return MakeErrorCodeFromLastError(); - // } - - // System: /Library/LaunchDaemon - // All users: /Library/LaunchAgents - // Current user: ~/Library/LaunchAgents - - std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); + const std::string DaemonName = GetDaemonName(ServiceName); std::string PList = BuildPlist(ServiceName, Spec.ExecutablePath, Spec.CommandLineOptions, DaemonName, Spec.DisplayName, Spec.Description, true); - std::filesystem::path PListFolder = "/Library/LaunchDaemon"; - - std::filesystem::path PListPath = PListFolder / (DaemonName + ".plist"); + const std::filesystem::path PListPath = GetPListPath(DaemonName); ZEN_INFO("Writing launchd plist to {}", PListPath.string()); try { @@ -758,8 +712,8 @@ InstallService(std::string_view ServiceName, const ServiceSpec& Spec) return MakeErrorCode(Ex.code().value()); } - ZEN_INFO("Changing permissions to 600 for {}", PListPath.string()); - if (chmod(PListPath.string().c_str(), 0600) == -1) + ZEN_INFO("Changing permissions to 644 for {}", PListPath.string()); + if (chmod(PListPath.string().c_str(), 0644) == -1) { return MakeErrorCodeFromLastError(); } @@ -769,25 +723,12 @@ InstallService(std::string_view ServiceName, const ServiceSpec& Spec) std::error_code UninstallService(std::string_view ServiceName) { -// std::filesystem::path ServicePath = std::filesystem::path("/usr/local/libexec") / ServiceName; -// ZEN_INFO("Attempting to remove service from {}", ServicePath.string()); -// if (unlink(ServicePath.string().c_str()) == -1) -// { -// return MakeErrorCodeFromLastError(); -// } - std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); - std::filesystem::path PListPath = std::filesystem::path("/Library/LaunchDaemon") / (DaemonName + ".plist"); + const std::string DaemonName = GetDaemonName(ServiceName); + const std::filesystem::path PListPath = GetPListPath(DaemonName); ZEN_INFO("Attempting to remove launchd plist from {}", PListPath.string()); std::error_code Ec; std::filesystem::remove(PListPath, Ec); return Ec; - -// if (unlink(PListPath.string().c_str()) == -1) -// { -// return MakeErrorCodeFromLastError(); -// } - -// return {}; } std::error_code @@ -796,11 +737,9 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) ZEN_UNUSED(ServiceName, OutInfo); OutInfo.Status = ServiceStatus::NotInstalled; - std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); - - std::filesystem::path PListFolder = "/Library/LaunchDaemon"; + const std::string DaemonName = GetDaemonName(ServiceName); - std::filesystem::path PListPath = PListFolder / (DaemonName + ".plist"); + const std::filesystem::path PListPath = GetPListPath(DaemonName); if (std::filesystem::is_regular_file(PListPath)) { OutInfo.Status = ServiceStatus::Stopped; @@ -810,34 +749,88 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) IoBuffer Buffer = ReadFile(PListPath).Flatten(); MemoryView Data = Buffer.GetView(); std::string PList((const char*)Data.GetData(), Data.GetSize()); - ZEN_INFO("{}", PList); + + enum class ParseMode + { + None, + ExpectingProgramArgumentsArray, + ExpectingProgramExecutablePath, + ExpectingCommandLineOption + }; + + ParseMode Mode = ParseMode::None; + + ForEachStrTok(PList, '\n', [&](std::string_view Line){ + switch (Mode) + { + case ParseMode::None: + { + if (Line.find("ProgramArguments") != std::string_view::npos) + { + Mode = ParseMode::ExpectingProgramArgumentsArray; + return true; + } + } + break; + case ParseMode::ExpectingProgramArgumentsArray: + { + if (Line.find("") != std::string_view::npos) + { + Mode = ParseMode::ExpectingProgramExecutablePath; + return true; + } + Mode = ParseMode::None; + } + break; + case ParseMode::ExpectingProgramExecutablePath: + { + if (std::string_view::size_type ArgStart = Line.find(""); ArgStart != std::string_view::npos) + { + ArgStart += 8; + if (std::string_view::size_type ArgEnd = Line.find("", ArgStart); ArgEnd != std::string_view::npos) + { + std::string_view ProgramString = Line.substr(ArgStart, ArgEnd - ArgStart); + OutInfo.Spec.ExecutablePath = ProgramString; + Mode = ParseMode::ExpectingCommandLineOption; + return true; + } + } + Mode = ParseMode::None; + } + break; + case ParseMode::ExpectingCommandLineOption: + { + if (std::string_view::size_type ArgStart = Line.find(""); ArgStart != std::string_view::npos) + { + Mode = ParseMode::None; + return true; + } + else if (std::string_view::size_type ArgStart = Line.find(""); ArgStart != std::string_view::npos) + { + ArgStart += 8; + if (std::string_view::size_type ArgEnd = Line.find("", ArgStart); ArgEnd != std::string_view::npos) + { + std::string_view ArgumentString = Line.substr(ArgStart, ArgEnd - ArgStart); + if (!OutInfo.Spec.CommandLineOptions.empty()) + { + OutInfo.Spec.CommandLineOptions += " "; + } + OutInfo.Spec.CommandLineOptions += ArgumentString; + return true; + } + } + Mode = ParseMode::None; + } + break; + } + return true; + }); } { std::pair Res = ExecuteProgram(std::string("launchctl list ") + DaemonName); if (Res.first == 0 && !Res.second.empty()) { -// bool IsParsingArguments = false; ForEachStrTok(Res.second, '\n', [&](std::string_view Line){ -// if (IsParsingArguments) -// { -// if (Line.find(");") == Line.length() - 2) -// { -// IsParsingArguments = false; -// return true; -// } -// std::string_view::size_type ProgramArgumentsEnd = Line.find_last_of('\"'); -// std::string_view::size_type ProgramArgumentsStart = Line.find('\"') + 1; -// std::string_view ProgramArgumentsString = Line.substr(ProgramArgumentsStart, ProgramArgumentsEnd - ProgramArgumentsStart); -// if (ProgramArgumentsString != OutInfo.Spec.ExecutablePath) -// { -// if (!OutInfo.Spec.CommandLineOptions.empty()) -// { -// OutInfo.Spec.CommandLineOptions += " "; -// } -// OutInfo.Spec.CommandLineOptions += ProgramArgumentsString; -// } -// return true; -// } if (Line.find("\"PID\"") != std::string_view::npos) { std::string_view::size_type PidStart = Line.find('='); @@ -849,56 +842,11 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) } return false; } -// if (Line.find("\"Program\"") != std::string_view::npos) -// { -// std::string_view::size_type ProgramEnd = Line.find_last_of('\"'); -// std::string_view::size_type ProgramStart = Line.find_last_of('\"', ProgramEnd - 1) + 1; -// std::string_view ProgramString = Line.substr(ProgramStart, ProgramEnd - ProgramStart); -// OutInfo.Spec.ExecutablePath = ProgramString; -// return true; -// } -// if (Line.find("\"ProgramArguments\"") != std::string_view::npos) -// { -// IsParsingArguments = true; -// return true; -// } return true; }); // Parse installed info } } -#if 0 - { - std::pair Res = ExecuteProgram("launchctl list"); - if (Res.first != 0) - { - return MakeErrorCode(Res.first); - } - std::string ZenServerLine; - ForEachStrTok(Res.second, '\n', [&](std::string_view Line){ - if (Line.find(DaemonName) != std::string_view::npos) - { - ZenServerLine = Line; - } - return true; - }); - if (!ZenServerLine.empty()) - { - std::vector Parts; - ForEachStrTok(ZenServerLine, '\t', [&](std::string_view Line){ - Parts.push_back(Line); - return true; - }); - if (Parts.size() == 3) - { - if (Parts[0] != "-" && ParseInt(Parts[0]).has_value()) - { - OutInfo.Status = ServiceStatus::Running; - } - } - } - } -#endif // 0 } return {}; @@ -909,9 +857,8 @@ StartService(std::string_view ServiceName) { ZEN_UNUSED(ServiceName); - std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); - std::filesystem::path PListFolder = "/Library/LaunchDaemon"; - std::filesystem::path PListPath = PListFolder / (DaemonName + ".plist"); + const std::string DaemonName = GetDaemonName(ServiceName); + const std::filesystem::path PListPath = GetPListPath(DaemonName); std::pair Res = ExecuteProgram(std::string("launchctl bootstrap system ") + PListPath.string()); if (Res.first != 0) @@ -927,9 +874,8 @@ StopService(std::string_view ServiceName) { ZEN_UNUSED(ServiceName); - std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); - std::filesystem::path PListFolder = "/Library/LaunchDaemon"; - std::filesystem::path PListPath = PListFolder / (DaemonName + ".plist"); + const std::string DaemonName = GetDaemonName(ServiceName); + const std::filesystem::path PListPath = GetPListPath(DaemonName); std::pair Res = ExecuteProgram(std::string("launchctl bootout system ") + PListPath.string()); if (Res.first != 0) @@ -940,6 +886,6 @@ StopService(std::string_view ServiceName) return {}; } -#endif // ZEN_PLATFORM_WINDOWS +#endif // ZEN_PLATFORM_MAC } // namespace zen -- cgit v1.2.3 From 7a73a49de6ddbc6712554c5c9b2863dd8e7de0f2 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Fri, 10 Jan 2025 15:52:34 +0100 Subject: displayname and description are windows-only properties --- src/zenutil/service.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index 08ccac512..f85a8e7e3 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -188,11 +188,8 @@ namespace { const std::filesystem::path& ExecutablePath, std::string_view CommandLineOptions, std::string_view DaemonName, - std::string_view ServiceDisplayName, - std::string_view ServiceDescription, bool Debug) { - ZEN_UNUSED(ServiceDisplayName, ServiceDescription); std::vector Arguments = SplitArguments(CommandLineOptions); ExtendableStringBuilder<256> ProgramArguments; for (const std::string_view Argument : Arguments) @@ -699,7 +696,7 @@ InstallService(std::string_view ServiceName, const ServiceSpec& Spec) { const std::string DaemonName = GetDaemonName(ServiceName); std::string PList = - BuildPlist(ServiceName, Spec.ExecutablePath, Spec.CommandLineOptions, DaemonName, Spec.DisplayName, Spec.Description, true); + BuildPlist(ServiceName, Spec.ExecutablePath, Spec.CommandLineOptions, DaemonName, true); const std::filesystem::path PListPath = GetPListPath(DaemonName); ZEN_INFO("Writing launchd plist to {}", PListPath.string()); -- cgit v1.2.3 From 6aeec009d348a6bef2923f6eebb9997dfd363cdf Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Fri, 10 Jan 2025 15:53:34 +0100 Subject: clang format --- src/zenutil/service.cpp | 119 +++++++++++++++++++++++------------------------- 1 file changed, 57 insertions(+), 62 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index f85a8e7e3..cfb19e9a9 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -173,10 +173,7 @@ namespace { } } - std::string GetDaemonName(std::string_view ServiceName) - { - return fmt::format("com.epicgames.unreal.{}", ServiceName); - } + std::string GetDaemonName(std::string_view ServiceName) { return fmt::format("com.epicgames.unreal.{}", ServiceName); } std::filesystem::path GetPListPath(const std::string& DaemonName) { @@ -188,7 +185,7 @@ namespace { const std::filesystem::path& ExecutablePath, std::string_view CommandLineOptions, std::string_view DaemonName, - bool Debug) + bool Debug) { std::vector Arguments = SplitArguments(CommandLineOptions); ExtendableStringBuilder<256> ProgramArguments; @@ -253,36 +250,36 @@ namespace { #endif // ZEN_PLATFORM_MAC - #if ZEN_PLATFORM_MAC || ZEN_PLATFORM_LINUX -std::pair ExecuteProgram(std::string_view Cmd) -{ - std::string data; - const int max_buffer = 256; - char buffer[max_buffer]; - std::string Command(Cmd); - Command.append(" 2>&1"); - - FILE * stream = popen(Command.c_str(), "r"); - if (stream) + std::pair ExecuteProgram(std::string_view Cmd) { - while (!feof(stream)) + std::string data; + const int max_buffer = 256; + char buffer[max_buffer]; + std::string Command(Cmd); + Command.append(" 2>&1"); + + FILE* stream = popen(Command.c_str(), "r"); + if (stream) { - if (fgets(buffer, max_buffer, stream) != NULL) + while (!feof(stream)) { - data.append(buffer); + if (fgets(buffer, max_buffer, stream) != NULL) + { + data.append(buffer); + } } - } - int Res = -1; - int st = pclose(stream); - if (WIFEXITED(st)) Res = WEXITSTATUS(st); - return {Res, data}; - } - return {errno, ""}; + int Res = -1; + int st = pclose(stream); + if (WIFEXITED(st)) + Res = WEXITSTATUS(st); + return {Res, data}; + } + return {errno, ""}; -#if 0 +# if 0 int in[2], out[2], n, pid; char buf[255]; @@ -350,11 +347,10 @@ std::pair ExecuteProgram(std::string_view Cmd) std::string Output(buf); return {0, Output}; -#endif // 0 -} - -#endif // ZEN_PLATFORM_MAC || ZEN_PLATFORM_LINUX +# endif // 0 + } +#endif // ZEN_PLATFORM_MAC || ZEN_PLATFORM_LINUX } // namespace @@ -695,8 +691,7 @@ std::error_code InstallService(std::string_view ServiceName, const ServiceSpec& Spec) { const std::string DaemonName = GetDaemonName(ServiceName); - std::string PList = - BuildPlist(ServiceName, Spec.ExecutablePath, Spec.CommandLineOptions, DaemonName, true); + std::string PList = BuildPlist(ServiceName, Spec.ExecutablePath, Spec.CommandLineOptions, DaemonName, true); const std::filesystem::path PListPath = GetPListPath(DaemonName); ZEN_INFO("Writing launchd plist to {}", PListPath.string()); @@ -720,8 +715,8 @@ InstallService(std::string_view ServiceName, const ServiceSpec& Spec) std::error_code UninstallService(std::string_view ServiceName) { - const std::string DaemonName = GetDaemonName(ServiceName); - const std::filesystem::path PListPath = GetPListPath(DaemonName); + const std::string DaemonName = GetDaemonName(ServiceName); + const std::filesystem::path PListPath = GetPListPath(DaemonName); ZEN_INFO("Attempting to remove launchd plist from {}", PListPath.string()); std::error_code Ec; std::filesystem::remove(PListPath, Ec); @@ -733,7 +728,7 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) { ZEN_UNUSED(ServiceName, OutInfo); - OutInfo.Status = ServiceStatus::NotInstalled; + OutInfo.Status = ServiceStatus::NotInstalled; const std::string DaemonName = GetDaemonName(ServiceName); const std::filesystem::path PListPath = GetPListPath(DaemonName); @@ -743,8 +738,8 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) { // Parse plist :( - IoBuffer Buffer = ReadFile(PListPath).Flatten(); - MemoryView Data = Buffer.GetView(); + IoBuffer Buffer = ReadFile(PListPath).Flatten(); + MemoryView Data = Buffer.GetView(); std::string PList((const char*)Data.GetData(), Data.GetSize()); enum class ParseMode @@ -757,10 +752,10 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) ParseMode Mode = ParseMode::None; - ForEachStrTok(PList, '\n', [&](std::string_view Line){ - switch (Mode) - { - case ParseMode::None: + ForEachStrTok(PList, '\n', [&](std::string_view Line) { + switch (Mode) + { + case ParseMode::None: { if (Line.find("ProgramArguments") != std::string_view::npos) { @@ -769,7 +764,7 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) } } break; - case ParseMode::ExpectingProgramArgumentsArray: + case ParseMode::ExpectingProgramArgumentsArray: { if (Line.find("") != std::string_view::npos) { @@ -779,7 +774,7 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) Mode = ParseMode::None; } break; - case ParseMode::ExpectingProgramExecutablePath: + case ParseMode::ExpectingProgramExecutablePath: { if (std::string_view::size_type ArgStart = Line.find(""); ArgStart != std::string_view::npos) { @@ -787,15 +782,15 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) if (std::string_view::size_type ArgEnd = Line.find("", ArgStart); ArgEnd != std::string_view::npos) { std::string_view ProgramString = Line.substr(ArgStart, ArgEnd - ArgStart); - OutInfo.Spec.ExecutablePath = ProgramString; - Mode = ParseMode::ExpectingCommandLineOption; + OutInfo.Spec.ExecutablePath = ProgramString; + Mode = ParseMode::ExpectingCommandLineOption; return true; } } Mode = ParseMode::None; } break; - case ParseMode::ExpectingCommandLineOption: + case ParseMode::ExpectingCommandLineOption: { if (std::string_view::size_type ArgStart = Line.find(""); ArgStart != std::string_view::npos) { @@ -819,20 +814,20 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) Mode = ParseMode::None; } break; - } - return true; + } + return true; }); } { - std::pair Res = ExecuteProgram(std::string("launchctl list ") + DaemonName); + std::pair Res = ExecuteProgram(std::string("launchctl list ") + DaemonName); if (Res.first == 0 && !Res.second.empty()) { - ForEachStrTok(Res.second, '\n', [&](std::string_view Line){ + ForEachStrTok(Res.second, '\n', [&](std::string_view Line) { if (Line.find("\"PID\"") != std::string_view::npos) { - std::string_view::size_type PidStart = Line.find('='); - std::string_view::size_type PidEnd = Line.find(';'); - std::string_view PidString = Line.substr(PidStart+2, PidEnd - (PidStart + 2)); + std::string_view::size_type PidStart = Line.find('='); + std::string_view::size_type PidEnd = Line.find(';'); + std::string_view PidString = Line.substr(PidStart + 2, PidEnd - (PidStart + 2)); if (ParseInt(PidString).has_value()) { OutInfo.Status = ServiceStatus::Running; @@ -854,15 +849,15 @@ StartService(std::string_view ServiceName) { ZEN_UNUSED(ServiceName); - const std::string DaemonName = GetDaemonName(ServiceName); - const std::filesystem::path PListPath = GetPListPath(DaemonName); + const std::string DaemonName = GetDaemonName(ServiceName); + const std::filesystem::path PListPath = GetPListPath(DaemonName); - std::pair Res = ExecuteProgram(std::string("launchctl bootstrap system ") + PListPath.string()); + std::pair Res = ExecuteProgram(std::string("launchctl bootstrap system ") + PListPath.string()); if (Res.first != 0) { return MakeErrorCode(Res.first); } - + return {}; } @@ -871,15 +866,15 @@ StopService(std::string_view ServiceName) { ZEN_UNUSED(ServiceName); - const std::string DaemonName = GetDaemonName(ServiceName); - const std::filesystem::path PListPath = GetPListPath(DaemonName); + const std::string DaemonName = GetDaemonName(ServiceName); + const std::filesystem::path PListPath = GetPListPath(DaemonName); - std::pair Res = ExecuteProgram(std::string("launchctl bootout system ") + PListPath.string()); + std::pair Res = ExecuteProgram(std::string("launchctl bootout system ") + PListPath.string()); if (Res.first != 0) { return MakeErrorCode(Res.first); } - + return {}; } -- cgit v1.2.3 From 5ba659f322823445ff22573b580206ec6464898e Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Mon, 13 Jan 2025 11:04:53 +0100 Subject: linux service --- src/zenutil/service.cpp | 199 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 198 insertions(+), 1 deletion(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index cfb19e9a9..8926accb3 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -351,7 +351,9 @@ namespace { } #endif // ZEN_PLATFORM_MAC || ZEN_PLATFORM_LINUX - +#if ZEN_PLATFORM_LINUX + const char* SystemInstallFolder = "/etc/systemd/system/"; +#endif // ZEN_PLATFORM_LINUX } // namespace std::string_view @@ -880,4 +882,199 @@ StopService(std::string_view ServiceName) #endif // ZEN_PLATFORM_MAC +#if ZEN_PLATFORM_LINUX + +std::error_code +InstallService(std::string_view ServiceName, const ServiceSpec& Spec) +{ + const std::string DaemonName = GetDaemonName(ServiceName); + std::string PList = BuildPlist(ServiceName, Spec.ExecutablePath, Spec.CommandLineOptions, DaemonName, true); + + const std::filesystem::path PListPath = GetPListPath(DaemonName); + ZEN_INFO("Writing launchd plist to {}", PListPath.string()); + try + { + zen::WriteFile(PListPath, IoBuffer(IoBuffer::Wrap, PList.data(), PList.size())); + } + catch (const std::system_error& Ex) + { + return MakeErrorCode(Ex.code().value()); + } + + ZEN_INFO("Changing permissions to 644 for {}", PListPath.string()); + if (chmod(PListPath.string().c_str(), 0644) == -1) + { + return MakeErrorCodeFromLastError(); + } + return {}; +} + +std::error_code +UninstallService(std::string_view ServiceName) +{ + const std::string DaemonName = GetDaemonName(ServiceName); + const std::filesystem::path PListPath = GetPListPath(DaemonName); + ZEN_INFO("Attempting to remove launchd plist from {}", PListPath.string()); + std::error_code Ec; + std::filesystem::remove(PListPath, Ec); + return Ec; +} + +std::error_code +QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) +{ + ZEN_UNUSED(ServiceName, OutInfo); + + OutInfo.Status = ServiceStatus::NotInstalled; + const std::string DaemonName = GetDaemonName(ServiceName); + + const std::filesystem::path PListPath = GetPListPath(DaemonName); + if (std::filesystem::is_regular_file(PListPath)) + { + OutInfo.Status = ServiceStatus::Stopped; + + { + // Parse plist :( + IoBuffer Buffer = ReadFile(PListPath).Flatten(); + MemoryView Data = Buffer.GetView(); + std::string PList((const char*)Data.GetData(), Data.GetSize()); + + enum class ParseMode + { + None, + ExpectingProgramArgumentsArray, + ExpectingProgramExecutablePath, + ExpectingCommandLineOption + }; + + ParseMode Mode = ParseMode::None; + + ForEachStrTok(PList, '\n', [&](std::string_view Line) { + switch (Mode) + { + case ParseMode::None: + { + if (Line.find("ProgramArguments") != std::string_view::npos) + { + Mode = ParseMode::ExpectingProgramArgumentsArray; + return true; + } + } + break; + case ParseMode::ExpectingProgramArgumentsArray: + { + if (Line.find("") != std::string_view::npos) + { + Mode = ParseMode::ExpectingProgramExecutablePath; + return true; + } + Mode = ParseMode::None; + } + break; + case ParseMode::ExpectingProgramExecutablePath: + { + if (std::string_view::size_type ArgStart = Line.find(""); ArgStart != std::string_view::npos) + { + ArgStart += 8; + if (std::string_view::size_type ArgEnd = Line.find("", ArgStart); ArgEnd != std::string_view::npos) + { + std::string_view ProgramString = Line.substr(ArgStart, ArgEnd - ArgStart); + OutInfo.Spec.ExecutablePath = ProgramString; + Mode = ParseMode::ExpectingCommandLineOption; + return true; + } + } + Mode = ParseMode::None; + } + break; + case ParseMode::ExpectingCommandLineOption: + { + if (std::string_view::size_type ArgStart = Line.find(""); ArgStart != std::string_view::npos) + { + Mode = ParseMode::None; + return true; + } + else if (std::string_view::size_type ArgStart = Line.find(""); ArgStart != std::string_view::npos) + { + ArgStart += 8; + if (std::string_view::size_type ArgEnd = Line.find("", ArgStart); ArgEnd != std::string_view::npos) + { + std::string_view ArgumentString = Line.substr(ArgStart, ArgEnd - ArgStart); + if (!OutInfo.Spec.CommandLineOptions.empty()) + { + OutInfo.Spec.CommandLineOptions += " "; + } + OutInfo.Spec.CommandLineOptions += ArgumentString; + return true; + } + } + Mode = ParseMode::None; + } + break; + } + return true; + }); + } + { + std::pair Res = ExecuteProgram(std::string("launchctl list ") + DaemonName); + if (Res.first == 0 && !Res.second.empty()) + { + ForEachStrTok(Res.second, '\n', [&](std::string_view Line) { + if (Line.find("\"PID\"") != std::string_view::npos) + { + std::string_view::size_type PidStart = Line.find('='); + std::string_view::size_type PidEnd = Line.find(';'); + std::string_view PidString = Line.substr(PidStart + 2, PidEnd - (PidStart + 2)); + if (ParseInt(PidString).has_value()) + { + OutInfo.Status = ServiceStatus::Running; + } + return false; + } + return true; + }); + // Parse installed info + } + } + } + + return {}; +} + +std::error_code +StartService(std::string_view ServiceName) +{ + ZEN_UNUSED(ServiceName); + + const std::string DaemonName = GetDaemonName(ServiceName); + const std::filesystem::path PListPath = GetPListPath(DaemonName); + + std::pair Res = ExecuteProgram(std::string("launchctl bootstrap system ") + PListPath.string()); + if (Res.first != 0) + { + return MakeErrorCode(Res.first); + } + + return {}; +} + +std::error_code +StopService(std::string_view ServiceName) +{ + ZEN_UNUSED(ServiceName); + + const std::string DaemonName = GetDaemonName(ServiceName); + const std::filesystem::path PListPath = GetPListPath(DaemonName); + + std::pair Res = ExecuteProgram(std::string("launchctl bootout system ") + PListPath.string()); + if (Res.first != 0) + { + return MakeErrorCode(Res.first); + } + + return {}; +} + +#endif // ZEN_PLATFORM_LINUX + } // namespace zen -- cgit v1.2.3 From 343d596ba0def12363907bca379e084e96793f96 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Mon, 13 Jan 2025 11:09:58 +0100 Subject: linux service stop --- src/zenutil/service.cpp | 172 ++++-------------------------------------------- 1 file changed, 13 insertions(+), 159 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index 8926accb3..01ef66431 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -250,7 +250,7 @@ namespace { #endif // ZEN_PLATFORM_MAC -#if ZEN_PLATFORM_MAC || ZEN_PLATFORM_LINUX +#if ZEN_PLATFORM_MAC std::pair ExecuteProgram(std::string_view Cmd) { @@ -350,9 +350,10 @@ namespace { # endif // 0 } -#endif // ZEN_PLATFORM_MAC || ZEN_PLATFORM_LINUX +#endif // ZEN_PLATFORM_MAC + #if ZEN_PLATFORM_LINUX - const char* SystemInstallFolder = "/etc/systemd/system/"; +// const char* SystemInstallFolder = "/etc/systemd/system/"; #endif // ZEN_PLATFORM_LINUX } // namespace @@ -887,156 +888,26 @@ StopService(std::string_view ServiceName) std::error_code InstallService(std::string_view ServiceName, const ServiceSpec& Spec) { - const std::string DaemonName = GetDaemonName(ServiceName); - std::string PList = BuildPlist(ServiceName, Spec.ExecutablePath, Spec.CommandLineOptions, DaemonName, true); + ZEN_UNUSED(ServiceName, Spec); + ZEN_NOT_IMPLEMENTED("linux service implementation incomplete"); - const std::filesystem::path PListPath = GetPListPath(DaemonName); - ZEN_INFO("Writing launchd plist to {}", PListPath.string()); - try - { - zen::WriteFile(PListPath, IoBuffer(IoBuffer::Wrap, PList.data(), PList.size())); - } - catch (const std::system_error& Ex) - { - return MakeErrorCode(Ex.code().value()); - } - - ZEN_INFO("Changing permissions to 644 for {}", PListPath.string()); - if (chmod(PListPath.string().c_str(), 0644) == -1) - { - return MakeErrorCodeFromLastError(); - } return {}; } std::error_code UninstallService(std::string_view ServiceName) { - const std::string DaemonName = GetDaemonName(ServiceName); - const std::filesystem::path PListPath = GetPListPath(DaemonName); - ZEN_INFO("Attempting to remove launchd plist from {}", PListPath.string()); - std::error_code Ec; - std::filesystem::remove(PListPath, Ec); - return Ec; + ZEN_UNUSED(ServiceName); + ZEN_NOT_IMPLEMENTED("linux service implementation incomplete"); + + return {}; } std::error_code QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) { ZEN_UNUSED(ServiceName, OutInfo); - - OutInfo.Status = ServiceStatus::NotInstalled; - const std::string DaemonName = GetDaemonName(ServiceName); - - const std::filesystem::path PListPath = GetPListPath(DaemonName); - if (std::filesystem::is_regular_file(PListPath)) - { - OutInfo.Status = ServiceStatus::Stopped; - - { - // Parse plist :( - IoBuffer Buffer = ReadFile(PListPath).Flatten(); - MemoryView Data = Buffer.GetView(); - std::string PList((const char*)Data.GetData(), Data.GetSize()); - - enum class ParseMode - { - None, - ExpectingProgramArgumentsArray, - ExpectingProgramExecutablePath, - ExpectingCommandLineOption - }; - - ParseMode Mode = ParseMode::None; - - ForEachStrTok(PList, '\n', [&](std::string_view Line) { - switch (Mode) - { - case ParseMode::None: - { - if (Line.find("ProgramArguments") != std::string_view::npos) - { - Mode = ParseMode::ExpectingProgramArgumentsArray; - return true; - } - } - break; - case ParseMode::ExpectingProgramArgumentsArray: - { - if (Line.find("") != std::string_view::npos) - { - Mode = ParseMode::ExpectingProgramExecutablePath; - return true; - } - Mode = ParseMode::None; - } - break; - case ParseMode::ExpectingProgramExecutablePath: - { - if (std::string_view::size_type ArgStart = Line.find(""); ArgStart != std::string_view::npos) - { - ArgStart += 8; - if (std::string_view::size_type ArgEnd = Line.find("", ArgStart); ArgEnd != std::string_view::npos) - { - std::string_view ProgramString = Line.substr(ArgStart, ArgEnd - ArgStart); - OutInfo.Spec.ExecutablePath = ProgramString; - Mode = ParseMode::ExpectingCommandLineOption; - return true; - } - } - Mode = ParseMode::None; - } - break; - case ParseMode::ExpectingCommandLineOption: - { - if (std::string_view::size_type ArgStart = Line.find(""); ArgStart != std::string_view::npos) - { - Mode = ParseMode::None; - return true; - } - else if (std::string_view::size_type ArgStart = Line.find(""); ArgStart != std::string_view::npos) - { - ArgStart += 8; - if (std::string_view::size_type ArgEnd = Line.find("", ArgStart); ArgEnd != std::string_view::npos) - { - std::string_view ArgumentString = Line.substr(ArgStart, ArgEnd - ArgStart); - if (!OutInfo.Spec.CommandLineOptions.empty()) - { - OutInfo.Spec.CommandLineOptions += " "; - } - OutInfo.Spec.CommandLineOptions += ArgumentString; - return true; - } - } - Mode = ParseMode::None; - } - break; - } - return true; - }); - } - { - std::pair Res = ExecuteProgram(std::string("launchctl list ") + DaemonName); - if (Res.first == 0 && !Res.second.empty()) - { - ForEachStrTok(Res.second, '\n', [&](std::string_view Line) { - if (Line.find("\"PID\"") != std::string_view::npos) - { - std::string_view::size_type PidStart = Line.find('='); - std::string_view::size_type PidEnd = Line.find(';'); - std::string_view PidString = Line.substr(PidStart + 2, PidEnd - (PidStart + 2)); - if (ParseInt(PidString).has_value()) - { - OutInfo.Status = ServiceStatus::Running; - } - return false; - } - return true; - }); - // Parse installed info - } - } - } + ZEN_NOT_IMPLEMENTED("linux service implementation incomplete"); return {}; } @@ -1045,15 +916,7 @@ std::error_code StartService(std::string_view ServiceName) { ZEN_UNUSED(ServiceName); - - const std::string DaemonName = GetDaemonName(ServiceName); - const std::filesystem::path PListPath = GetPListPath(DaemonName); - - std::pair Res = ExecuteProgram(std::string("launchctl bootstrap system ") + PListPath.string()); - if (Res.first != 0) - { - return MakeErrorCode(Res.first); - } + ZEN_NOT_IMPLEMENTED("linux service implementation incomplete"); return {}; } @@ -1062,16 +925,7 @@ std::error_code StopService(std::string_view ServiceName) { ZEN_UNUSED(ServiceName); - - const std::string DaemonName = GetDaemonName(ServiceName); - const std::filesystem::path PListPath = GetPListPath(DaemonName); - - std::pair Res = ExecuteProgram(std::string("launchctl bootout system ") + PListPath.string()); - if (Res.first != 0) - { - return MakeErrorCode(Res.first); - } - + ZEN_NOT_IMPLEMENTED("linux service implementation incomplete"); return {}; } -- cgit v1.2.3 From dda538aa2b24a4e99d719e96e5a82f21889612ab Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Mon, 13 Jan 2025 11:40:23 +0100 Subject: generate unit file --- src/zenutil/service.cpp | 74 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 3 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index 01ef66431..d31786835 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -12,11 +12,17 @@ #endif #if ZEN_PLATFORM_MAC # include +# include # include # include +#endif +#if ZEN_PLATFORM_LINUX +# include +# include -# include +# include +# include #endif namespace zen { @@ -228,7 +234,7 @@ namespace { "\n" "\n", DaemonName, - ExecutablePath.string(), + ExecutablePath, ProgramArguments.ToView(), ServiceName, ServiceName, @@ -354,6 +360,49 @@ namespace { #if ZEN_PLATFORM_LINUX // const char* SystemInstallFolder = "/etc/systemd/system/"; + +std::string GetUnitName(std::string_view ServiceName) { return fmt::format("com.epicgames.unreal.{}", ServiceName); } + +std::filesystem::path GetServiceUnitPath(const std::string& UnitName) +{ + const std::filesystem::path SystemUnitFolder = "/etc/systemd/system/"; + return SystemUnitFolder / (UnitName + ".service"); +} + +std::string BuildUnitFile(std::string_view ServiceName, + const std::filesystem::path& ExecutablePath, + std::string_view CommandLineOptions, + std::string_view AliasName) +{ + #if 0 + ZEN_UNUSED(ServiceName, ExecutablePath, CommandLineOptions, AliasName); + return ""; + #else + return fmt::format("[Unit]\n" + "Description={}\n" + "Documentation=https://github.com/epicgames/zen\n" + "\n" + "DefaultDependencies=no\n" + "After=network.target\n" + "StartLimitIntervalSec=0\n" + "\n" + "[Service]\n" + "Type=simple\n" + "Restart=always\n" + "RestartSec=1\n" + "User=serviceuser\n" + "ExecStart={} {}\n" + "Restart=always\n" + "RuntimeDirectory={}\n" + "[Install]\n" + "Alias={}", + ServiceName, + ExecutablePath, CommandLineOptions, + ExecutablePath.parent_path(), + AliasName); + #endif +} + #endif // ZEN_PLATFORM_LINUX } // namespace @@ -889,7 +938,26 @@ std::error_code InstallService(std::string_view ServiceName, const ServiceSpec& Spec) { ZEN_UNUSED(ServiceName, Spec); - ZEN_NOT_IMPLEMENTED("linux service implementation incomplete"); + + const std::string UnitName = GetUnitName(ServiceName); + std::string UnitFile = BuildUnitFile(ServiceName, Spec.ExecutablePath, Spec.CommandLineOptions, UnitName); + + const std::filesystem::path ServiceUnitPath = GetServiceUnitPath(UnitName); + ZEN_INFO("Writing systemd unit file to {}", ServiceUnitPath.string()); + try + { + zen::WriteFile(ServiceUnitPath, IoBuffer(IoBuffer::Wrap, UnitFile.data(), UnitFile.size())); + } + catch (const std::system_error& Ex) + { + return MakeErrorCode(Ex.code().value()); + } + + ZEN_INFO("Changing permissions to 644 for {}", ServiceUnitPath.string()); + if (chmod(ServiceUnitPath.string().c_str(), 0644) == -1) + { + return MakeErrorCodeFromLastError(); + } return {}; } -- cgit v1.2.3 From afb0fb0a2c7934b62d61fc191bfbce3a7535fe5e Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Mon, 13 Jan 2025 11:40:56 +0100 Subject: clang format --- src/zenutil/service.cpp | 80 +++++++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 39 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index d31786835..834d8b764 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -359,49 +359,51 @@ namespace { #endif // ZEN_PLATFORM_MAC #if ZEN_PLATFORM_LINUX -// const char* SystemInstallFolder = "/etc/systemd/system/"; + // const char* SystemInstallFolder = "/etc/systemd/system/"; -std::string GetUnitName(std::string_view ServiceName) { return fmt::format("com.epicgames.unreal.{}", ServiceName); } + std::string GetUnitName(std::string_view ServiceName) { return fmt::format("com.epicgames.unreal.{}", ServiceName); } -std::filesystem::path GetServiceUnitPath(const std::string& UnitName) -{ - const std::filesystem::path SystemUnitFolder = "/etc/systemd/system/"; - return SystemUnitFolder / (UnitName + ".service"); -} + std::filesystem::path GetServiceUnitPath(const std::string& UnitName) + { + const std::filesystem::path SystemUnitFolder = "/etc/systemd/system/"; + return SystemUnitFolder / (UnitName + ".service"); + } -std::string BuildUnitFile(std::string_view ServiceName, - const std::filesystem::path& ExecutablePath, - std::string_view CommandLineOptions, - std::string_view AliasName) -{ - #if 0 + std::string BuildUnitFile(std::string_view ServiceName, + const std::filesystem::path& ExecutablePath, + std::string_view CommandLineOptions, + std::string_view AliasName) + { +# if 0 ZEN_UNUSED(ServiceName, ExecutablePath, CommandLineOptions, AliasName); return ""; - #else - return fmt::format("[Unit]\n" - "Description={}\n" - "Documentation=https://github.com/epicgames/zen\n" - "\n" - "DefaultDependencies=no\n" - "After=network.target\n" - "StartLimitIntervalSec=0\n" - "\n" - "[Service]\n" - "Type=simple\n" - "Restart=always\n" - "RestartSec=1\n" - "User=serviceuser\n" - "ExecStart={} {}\n" - "Restart=always\n" - "RuntimeDirectory={}\n" - "[Install]\n" - "Alias={}", - ServiceName, - ExecutablePath, CommandLineOptions, - ExecutablePath.parent_path(), - AliasName); - #endif -} +# else + return fmt::format( + "[Unit]\n" + "Description={}\n" + "Documentation=https://github.com/epicgames/zen\n" + "\n" + "DefaultDependencies=no\n" + "After=network.target\n" + "StartLimitIntervalSec=0\n" + "\n" + "[Service]\n" + "Type=simple\n" + "Restart=always\n" + "RestartSec=1\n" + "User=serviceuser\n" + "ExecStart={} {}\n" + "Restart=always\n" + "RuntimeDirectory={}\n" + "[Install]\n" + "Alias={}", + ServiceName, + ExecutablePath, + CommandLineOptions, + ExecutablePath.parent_path(), + AliasName); +# endif + } #endif // ZEN_PLATFORM_LINUX } // namespace @@ -940,7 +942,7 @@ InstallService(std::string_view ServiceName, const ServiceSpec& Spec) ZEN_UNUSED(ServiceName, Spec); const std::string UnitName = GetUnitName(ServiceName); - std::string UnitFile = BuildUnitFile(ServiceName, Spec.ExecutablePath, Spec.CommandLineOptions, UnitName); + std::string UnitFile = BuildUnitFile(ServiceName, Spec.ExecutablePath, Spec.CommandLineOptions, UnitName); const std::filesystem::path ServiceUnitPath = GetServiceUnitPath(UnitName); ZEN_INFO("Writing systemd unit file to {}", ServiceUnitPath.string()); -- cgit v1.2.3 From 531c59032bbc46bc1f7284859fa8ff8c8b5ede61 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Wed, 15 Jan 2025 09:30:12 +0100 Subject: systemd unit file, incomplete --- src/zenutil/service.cpp | 250 ++++++++++++++++++++++++++---------------------- 1 file changed, 135 insertions(+), 115 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index 834d8b764..820edf565 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -256,111 +256,58 @@ namespace { #endif // ZEN_PLATFORM_MAC -#if ZEN_PLATFORM_MAC +#if ZEN_PLATFORM_MAC || ZEN_PLATFORM_LINUX + // TODO: Is this good enough to capture all output/errors/return codes? std::pair ExecuteProgram(std::string_view Cmd) { - std::string data; - const int max_buffer = 256; - char buffer[max_buffer]; + std::string Data; + const int BufferSize = 256; + char Buffer[BufferSize]; std::string Command(Cmd); Command.append(" 2>&1"); - FILE* stream = popen(Command.c_str(), "r"); - if (stream) + ZEN_DEBUG("Running: '{}'", Command); + + FILE* Stream = popen(Command.c_str(), "r"); + if (Stream) { - while (!feof(stream)) + while (!feof(Stream)) { - if (fgets(buffer, max_buffer, stream) != NULL) + if (fgets(Buffer, BufferSize, Stream) != NULL) { - data.append(buffer); + Data.append(Buffer); } } + while(!Data.empty() && isspace(Data[Data.length() - 1])) + { + Data.pop_back(); + } + int Res = -1; - int st = pclose(stream); - if (WIFEXITED(st)) - Res = WEXITSTATUS(st); - return {Res, data}; + int Status = pclose(Stream); + if (Status < 0) + { + return {Status, Data}; + } + uint64_t WaitMS = 100; + if (!WIFEXITED(Status)) + { + Res = WEXITSTATUS(Status); + } + if (Res != 0 && Res != (128+13)) + { + return {Res, Data}; + } + return {0, Data}; } return {errno, ""}; - -# if 0 - int in[2], out[2], n, pid; - char buf[255]; - - /* In a pipe, xx[0] is for reading, xx[1] is for writing */ - - if (pipe(in) < 0) - { - return {errno, ""}; - } - if (pipe(out) < 0) - { - close(in[0]); - close(in[1]); - return {errno, ""}; - } - - if ((pid=fork()) == 0) { - /* This is the child process */ - - /* Close stdin, stdout, stderr */ - close(0); - close(1); - close(2); - /* make our pipes, our new stdin,stdout and stderr */ - dup2(in[0],0); - dup2(out[1],1); - dup2(out[1],2); - - /* Close the other ends of the pipes that the parent will use, because if - * we leave these open in the child, the child/parent will not get an EOF - * when the parent/child closes their end of the pipe. - */ - close(in[1]); - close(out[0]); - - va_list args; - va_start(args, Executable); - va_end(args); - - /* Over-write the child process with the hexdump binary */ - execl(Executable, Executable, args); - } - - /* This is the parent process */ - /* Close the pipe ends that the child uses to read from / write to so - * the when we close the others, an EOF will be transmitted properly. - */ - close(in[0]); - - /* Because of the small amount of data, the child may block unless we - * close it's input stream. This sends an EOF to the child on it's - * stdin. - */ - close(in[1]); - - /* Read back any output */ - n = read(out[0], buf, 250); - if (n == 0) - { - n = read(out[1], buf, 250); - } - buf[n] = 0; - close(out[0]); - close(out[1]); - - std::string Output(buf); - return {0, Output}; -# endif // 0 } -#endif // ZEN_PLATFORM_MAC +#endif // ZEN_PLATFORM_MAC || ZEN_PLATFORM_LINUX #if ZEN_PLATFORM_LINUX - // const char* SystemInstallFolder = "/etc/systemd/system/"; - std::string GetUnitName(std::string_view ServiceName) { return fmt::format("com.epicgames.unreal.{}", ServiceName); } std::filesystem::path GetServiceUnitPath(const std::string& UnitName) @@ -374,10 +321,8 @@ namespace { std::string_view CommandLineOptions, std::string_view AliasName) { -# if 0 - ZEN_UNUSED(ServiceName, ExecutablePath, CommandLineOptions, AliasName); - return ""; -# else + // TODO: Revise to make sure the unit file is correct + // TODO: Do we need a separate config file or is that optional? return fmt::format( "[Unit]\n" "Description={}\n" @@ -396,13 +341,13 @@ namespace { "Restart=always\n" "RuntimeDirectory={}\n" "[Install]\n" - "Alias={}", + "Alias={}\n" + "WantedBy=multi-user.target", ServiceName, ExecutablePath, CommandLineOptions, ExecutablePath.parent_path(), AliasName); -# endif } #endif // ZEN_PLATFORM_LINUX @@ -440,8 +385,10 @@ ToString(ServiceStatus Status) std::error_code InstallService(std::string_view ServiceName, const ServiceSpec& Spec) { - // Get a handle to the SCM database. + // TODO: Is "LocalService account" the correct account? + // TODO: Is ther eother parameters/security settings that we need to set up properly? + // Get a handle to the SCM database. SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer NULL, // ServicesActive database SC_MANAGER_ALL_ACCESS); // full access rights @@ -744,6 +691,7 @@ StopService(std::string_view ServiceName) std::error_code InstallService(std::string_view ServiceName, const ServiceSpec& Spec) { + // TODO: Do we need to create a separate user for the service or is running as the default service user OK? const std::string DaemonName = GetDaemonName(ServiceName); std::string PList = BuildPlist(ServiceName, Spec.ExecutablePath, Spec.CommandLineOptions, DaemonName, true); @@ -780,8 +728,6 @@ UninstallService(std::string_view ServiceName) std::error_code QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) { - ZEN_UNUSED(ServiceName, OutInfo); - OutInfo.Status = ServiceStatus::NotInstalled; const std::string DaemonName = GetDaemonName(ServiceName); @@ -873,7 +819,7 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) }); } { - std::pair Res = ExecuteProgram(std::string("launchctl list ") + DaemonName); + std::pair Res = ExecuteProgram(fmt::format("launchctl list {}", DaemonName)); if (Res.first == 0 && !Res.second.empty()) { ForEachStrTok(Res.second, '\n', [&](std::string_view Line) { @@ -901,12 +847,10 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) std::error_code StartService(std::string_view ServiceName) { - ZEN_UNUSED(ServiceName); - const std::string DaemonName = GetDaemonName(ServiceName); const std::filesystem::path PListPath = GetPListPath(DaemonName); - std::pair Res = ExecuteProgram(std::string("launchctl bootstrap system ") + PListPath.string()); + std::pair Res = ExecuteProgram(fmt::format("launchctl bootstrap system {}", PListPath)); if (Res.first != 0) { return MakeErrorCode(Res.first); @@ -918,12 +862,10 @@ StartService(std::string_view ServiceName) std::error_code StopService(std::string_view ServiceName) { - ZEN_UNUSED(ServiceName); - const std::string DaemonName = GetDaemonName(ServiceName); const std::filesystem::path PListPath = GetPListPath(DaemonName); - std::pair Res = ExecuteProgram(std::string("launchctl bootout system ") + PListPath.string()); + std::pair Res = ExecuteProgram(fmt::format("launchctl bootout system ", PListPath.)); if (Res.first != 0) { return MakeErrorCode(Res.first); @@ -939,13 +881,13 @@ StopService(std::string_view ServiceName) std::error_code InstallService(std::string_view ServiceName, const ServiceSpec& Spec) { - ZEN_UNUSED(ServiceName, Spec); + // TODO: Do we need to create a separate user for the service or is running as root OK? const std::string UnitName = GetUnitName(ServiceName); - std::string UnitFile = BuildUnitFile(ServiceName, Spec.ExecutablePath, Spec.CommandLineOptions, UnitName); - const std::filesystem::path ServiceUnitPath = GetServiceUnitPath(UnitName); - ZEN_INFO("Writing systemd unit file to {}", ServiceUnitPath.string()); + + std::string UnitFile = BuildUnitFile(ServiceName, Spec.ExecutablePath, Spec.CommandLineOptions, UnitName); + ZEN_DEBUG("Writing systemd unit file to {}", ServiceUnitPath.string()); try { zen::WriteFile(ServiceUnitPath, IoBuffer(IoBuffer::Wrap, UnitFile.data(), UnitFile.size())); @@ -955,20 +897,57 @@ InstallService(std::string_view ServiceName, const ServiceSpec& Spec) return MakeErrorCode(Ex.code().value()); } - ZEN_INFO("Changing permissions to 644 for {}", ServiceUnitPath.string()); + ZEN_DEBUG("Changing permissions to 644 for {}", ServiceUnitPath.string()); if (chmod(ServiceUnitPath.string().c_str(), 0644) == -1) { return MakeErrorCodeFromLastError(); } + std::pair Res = ExecuteProgram("systemctl daemon-reload"); + if (Res.first != 0 && Res.first != -1) + { + ZEN_ERROR("systemctl daemon-reload failed with {}: '{}'", Res.first, Res.second); + return MakeErrorCode(Res.first); + } + + Res = ExecuteProgram(fmt::format("systemctl enable {}", UnitName)); + if (Res.first != 0 && Res.first != -1) + { + ZEN_ERROR("systemctl enable failed with {}: '{}'", Res.first, Res.second); + return MakeErrorCode(Res.first); + } + return {}; } std::error_code UninstallService(std::string_view ServiceName) { - ZEN_UNUSED(ServiceName); - ZEN_NOT_IMPLEMENTED("linux service implementation incomplete"); + const std::string UnitName = GetUnitName(ServiceName); + const std::filesystem::path ServiceUnitPath = GetServiceUnitPath(UnitName); + + std::pair Res = ExecuteProgram(fmt::format("systemctl disable {}", UnitName)); + if (Res.first != 0 && Res.first != -1) + { + ZEN_ERROR("systemctl disable failed with {}: '{}'", Res.first, Res.second); + return MakeErrorCode(Res.first); + } + + ZEN_DEBUG("Attempting to remove systemd unit file from {}", ServiceUnitPath.string()); + std::error_code Ec; + std::filesystem::remove(ServiceUnitPath, Ec); + if (Ec) + { + ZEN_ERROR("failed to remove {}: '{}'", ServiceUnitPath, Ec.message()); + return Ec; + } + + Res = ExecuteProgram("systemctl daemon-reload"); + if (Res.first != 0 && Res.first != -1) + { + ZEN_ERROR("systemctl daemon-reload failed with {}: '{}'", Res.first, Res.second); + return MakeErrorCode(Res.first); + } return {}; } @@ -976,8 +955,27 @@ UninstallService(std::string_view ServiceName) std::error_code QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) { - ZEN_UNUSED(ServiceName, OutInfo); - ZEN_NOT_IMPLEMENTED("linux service implementation incomplete"); + const std::string UnitName = GetUnitName(ServiceName); + const std::filesystem::path ServiceUnitPath = GetServiceUnitPath(UnitName); + + OutInfo.Status = ServiceStatus::NotInstalled; + + if (std::filesystem::is_regular_file(ServiceUnitPath)) + { + OutInfo.Status = ServiceStatus::Stopped; + // TODO: Read and parse unit file ? + + std::pair Res = ExecuteProgram(fmt::format("systemctl status {}", UnitName)); + if (Res.first == 0) + { + // TODO: What does status really return and what info can we use here to get the ServiceInfo complete? + OutInfo.Status = ServiceStatus::Running; + } + else + { + ZEN_DEBUG("systemctl status failed with '{}" ({}), Res.second, Res.first); + } + } return {}; } @@ -985,8 +983,20 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) std::error_code StartService(std::string_view ServiceName) { - ZEN_UNUSED(ServiceName); - ZEN_NOT_IMPLEMENTED("linux service implementation incomplete"); + // TODO: Starting the service returns -1 from ExecuteProgram, so the service won't start + // TODO: Running start from command line gives no output but the service does not start - not sure what is wrong. + // Starting the same command line for the service using `sudo` *will* start the service sucessfully so I + // assume that the Unit file or some config is wrong/missing. + + const std::string UnitName = GetUnitName(ServiceName); + const std::filesystem::path ServiceUnitPath = GetServiceUnitPath(UnitName); + + std::pair Res = ExecuteProgram(fmt::format("systemctl start {}", UnitName)); + if (Res.first != 0) + { + ZEN_ERROR("service start failed with {}: '{}'", Res.first, Res.second); + return MakeErrorCode(Res.first); + } return {}; } @@ -994,8 +1004,18 @@ StartService(std::string_view ServiceName) std::error_code StopService(std::string_view ServiceName) { - ZEN_UNUSED(ServiceName); - ZEN_NOT_IMPLEMENTED("linux service implementation incomplete"); + // TODO: Stopping the service returns -1 from ExecuteProgram, maybe this ie expected as I have yet to successfully start the service using systemctl start + + const std::string UnitName = GetUnitName(ServiceName); + const std::filesystem::path ServiceUnitPath = GetServiceUnitPath(UnitName); + + std::pair Res = ExecuteProgram(fmt::format("systemctl stop {}", UnitName)); + if (Res.first != 0) + { + ZEN_ERROR("service stop failed with {}: '{}'", Res.first, Res.second); + return MakeErrorCode(Res.first); + } + return {}; } -- cgit v1.2.3 From 53531a6b22dbb7b690db43964172e9d0f670c3c8 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Wed, 15 Jan 2025 10:17:02 +0100 Subject: clang format --- src/zenutil/service.cpp | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index 820edf565..45874d1b5 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -280,13 +280,13 @@ namespace { } } - while(!Data.empty() && isspace(Data[Data.length() - 1])) + while (!Data.empty() && isspace(Data[Data.length() - 1])) { Data.pop_back(); } - int Res = -1; - int Status = pclose(Stream); + int Res = -1; + int Status = pclose(Stream); if (Status < 0) { return {Status, Data}; @@ -296,7 +296,7 @@ namespace { { Res = WEXITSTATUS(Status); } - if (Res != 0 && Res != (128+13)) + if (Res != 0 && Res != (128 + 13)) { return {Res, Data}; } @@ -883,10 +883,10 @@ InstallService(std::string_view ServiceName, const ServiceSpec& Spec) { // TODO: Do we need to create a separate user for the service or is running as root OK? - const std::string UnitName = GetUnitName(ServiceName); + const std::string UnitName = GetUnitName(ServiceName); const std::filesystem::path ServiceUnitPath = GetServiceUnitPath(UnitName); - std::string UnitFile = BuildUnitFile(ServiceName, Spec.ExecutablePath, Spec.CommandLineOptions, UnitName); + std::string UnitFile = BuildUnitFile(ServiceName, Spec.ExecutablePath, Spec.CommandLineOptions, UnitName); ZEN_DEBUG("Writing systemd unit file to {}", ServiceUnitPath.string()); try { @@ -923,7 +923,7 @@ InstallService(std::string_view ServiceName, const ServiceSpec& Spec) std::error_code UninstallService(std::string_view ServiceName) { - const std::string UnitName = GetUnitName(ServiceName); + const std::string UnitName = GetUnitName(ServiceName); const std::filesystem::path ServiceUnitPath = GetServiceUnitPath(UnitName); std::pair Res = ExecuteProgram(fmt::format("systemctl disable {}", UnitName)); @@ -955,7 +955,7 @@ UninstallService(std::string_view ServiceName) std::error_code QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) { - const std::string UnitName = GetUnitName(ServiceName); + const std::string UnitName = GetUnitName(ServiceName); const std::filesystem::path ServiceUnitPath = GetServiceUnitPath(UnitName); OutInfo.Status = ServiceStatus::NotInstalled; @@ -973,7 +973,7 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) } else { - ZEN_DEBUG("systemctl status failed with '{}" ({}), Res.second, Res.first); + ZEN_DEBUG("systemctl status failed with '{}"({}), Res.second, Res.first); } } @@ -988,7 +988,7 @@ StartService(std::string_view ServiceName) // Starting the same command line for the service using `sudo` *will* start the service sucessfully so I // assume that the Unit file or some config is wrong/missing. - const std::string UnitName = GetUnitName(ServiceName); + const std::string UnitName = GetUnitName(ServiceName); const std::filesystem::path ServiceUnitPath = GetServiceUnitPath(UnitName); std::pair Res = ExecuteProgram(fmt::format("systemctl start {}", UnitName)); @@ -1004,9 +1004,10 @@ StartService(std::string_view ServiceName) std::error_code StopService(std::string_view ServiceName) { - // TODO: Stopping the service returns -1 from ExecuteProgram, maybe this ie expected as I have yet to successfully start the service using systemctl start + // TODO: Stopping the service returns -1 from ExecuteProgram, maybe this ie expected as I have yet to successfully start the service + // using systemctl start - const std::string UnitName = GetUnitName(ServiceName); + const std::string UnitName = GetUnitName(ServiceName); const std::filesystem::path ServiceUnitPath = GetServiceUnitPath(UnitName); std::pair Res = ExecuteProgram(fmt::format("systemctl stop {}", UnitName)); -- cgit v1.2.3 From 7e700445334d8d3269bca0bf446ff63211296dce Mon Sep 17 00:00:00 2001 From: Liam Mitchell Date: Wed, 19 Feb 2025 00:08:41 +0000 Subject: Linux compilation fixes --- src/zenutil/service.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index 45874d1b5..8d6b399ca 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -973,7 +973,7 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) } else { - ZEN_DEBUG("systemctl status failed with '{}"({}), Res.second, Res.first); + ZEN_DEBUG("systemctl status failed with '{}'({})", Res.second, Res.first); } } -- cgit v1.2.3 From c49b0a053c5e28de1afa83600ebffd383766e38a Mon Sep 17 00:00:00 2001 From: Liam Mitchell Date: Thu, 27 Feb 2025 02:16:10 +0000 Subject: Implementation of service commands for Linux. --- src/zenutil/service.cpp | 59 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 13 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index 8d6b399ca..c156b001c 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #if ZEN_PLATFORM_WINDOWS # include @@ -23,6 +24,7 @@ # include # include +# include #endif namespace zen { @@ -258,7 +260,6 @@ namespace { #if ZEN_PLATFORM_MAC || ZEN_PLATFORM_LINUX - // TODO: Is this good enough to capture all output/errors/return codes? std::pair ExecuteProgram(std::string_view Cmd) { std::string Data; @@ -289,10 +290,11 @@ namespace { int Status = pclose(Stream); if (Status < 0) { + ZEN_DEBUG("Command {} returned {}, errno {}", Command, Status, errno); return {Status, Data}; } uint64_t WaitMS = 100; - if (!WIFEXITED(Status)) + if (WIFEXITED(Status)) { Res = WEXITSTATUS(Status); } @@ -319,10 +321,9 @@ namespace { std::string BuildUnitFile(std::string_view ServiceName, const std::filesystem::path& ExecutablePath, std::string_view CommandLineOptions, - std::string_view AliasName) + std::string_view AliasName, + std::string_view UserName) { - // TODO: Revise to make sure the unit file is correct - // TODO: Do we need a separate config file or is that optional? return fmt::format( "[Unit]\n" "Description={}\n" @@ -336,14 +337,14 @@ namespace { "Type=simple\n" "Restart=always\n" "RestartSec=1\n" - "User=serviceuser\n" + "User={}\n" "ExecStart={} {}\n" - "Restart=always\n" "RuntimeDirectory={}\n" "[Install]\n" "Alias={}\n" "WantedBy=multi-user.target", ServiceName, + UserName, ExecutablePath, CommandLineOptions, ExecutablePath.parent_path(), @@ -881,12 +882,23 @@ StopService(std::string_view ServiceName) std::error_code InstallService(std::string_view ServiceName, const ServiceSpec& Spec) { - // TODO: Do we need to create a separate user for the service or is running as root OK? - const std::string UnitName = GetUnitName(ServiceName); const std::filesystem::path ServiceUnitPath = GetServiceUnitPath(UnitName); + std::string UserName = Spec.UserName; + + if (UserName == "") + { + std::pair UserResult = ExecuteProgram("echo $SUDO_USER"); + if (UserResult.first != 0 || UserResult.second.empty()) + { + ZEN_ERROR("Unable to determine current user"); + return MakeErrorCode(UserResult.first); + } + + UserName = UserResult.second; + } - std::string UnitFile = BuildUnitFile(ServiceName, Spec.ExecutablePath, Spec.CommandLineOptions, UnitName); + std::string UnitFile = BuildUnitFile(ServiceName, Spec.ExecutablePath, Spec.CommandLineOptions, UnitName, UserName); ZEN_DEBUG("Writing systemd unit file to {}", ServiceUnitPath.string()); try { @@ -963,13 +975,34 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) if (std::filesystem::is_regular_file(ServiceUnitPath)) { OutInfo.Status = ServiceStatus::Stopped; - // TODO: Read and parse unit file ? - std::pair Res = ExecuteProgram(fmt::format("systemctl status {}", UnitName)); + std::pair Res = ExecuteProgram(fmt::format("systemctl is-active --quiet {}", UnitName)); if (Res.first == 0) { - // TODO: What does status really return and what info can we use here to get the ServiceInfo complete? OutInfo.Status = ServiceStatus::Running; + + std::pair ShowResult = ExecuteProgram(fmt::format("systemctl show -p ExecStart {}", UnitName)); + if (ShowResult.first == 0) + { + std::regex Regex(R"~(ExecStart=\{ path=(.*?) ; argv\[\]=(.*?) ;)~"); + std::smatch Match; + + if (std::regex_search(ShowResult.second, Match, Regex)) + { + std::string Executable = Match[1].str(); + std::string CommandLine = Match[2].str(); + OutInfo.Spec.ExecutablePath = Executable; + OutInfo.Spec.CommandLineOptions = CommandLine.substr(Executable.size(), CommandLine.size()); + } + else + { + ZEN_WARN("Failed to parse output of systemctl show: {}", ShowResult.second); + } + } + else + { + ZEN_WARN("Failed to read start info from systemctl: error code {}", ShowResult.first); + } } else { -- cgit v1.2.3 From 2b823aa2088587575f62784c150af75c2535618b Mon Sep 17 00:00:00 2001 From: Liam Mitchell Date: Wed, 5 Mar 2025 02:33:21 +0000 Subject: Update Linux service type and add libsystemd dependency --- src/zenutil/service.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index c156b001c..ea7c2aae6 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -334,7 +334,7 @@ namespace { "StartLimitIntervalSec=0\n" "\n" "[Service]\n" - "Type=simple\n" + "Type=notify\n" "Restart=always\n" "RestartSec=1\n" "User={}\n" -- cgit v1.2.3 From ca949ab61d1540d5e6b92c6b5b53d5e3d02d5050 Mon Sep 17 00:00:00 2001 From: Liam Mitchell Date: Tue, 25 Mar 2025 17:37:18 -0700 Subject: Specify restart options for service manager, to avoid use of manual restart logic --- src/zenutil/service.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index a2ff93efd..e1c5e17fa 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -449,6 +449,25 @@ InstallService(std::string_view ServiceName, const ServiceSpec& Spec) } } + // Actions defining what the service manager should do in the event of a zenserver crash. + // Attempt an immediate restart twice. If both restarts fail, stop trying. + // The attempt count will be reset based on the timeout specified in SERVICE_FAILURE_ACTIONS. + // If the service manages to survive for the length of that timeout, the reset attempt count + // will be reset and we will try restarting if we fail again. + SC_ACTION Actions[] = {{SC_ACTION_RESTART, 0}, {SC_ACTION_RESTART, 0}, {SC_ACTION_NONE, 0}}; + + SERVICE_FAILURE_ACTIONS FailureAction = { + 60, // if we haven't failed for one minute, assume the service is healthy and reset the failure count + NULL, // no reboot message - we don't want to reboot the whole system if zen dies + NULL, // no command to run on failure - just attempt restarting the service + ZEN_ARRAY_COUNT(Actions), + Actions}; + + if (!ChangeServiceConfig2(schService, SERVICE_CONFIG_FAILURE_ACTIONS, &FailureAction)) + { + return MakeErrorCodeFromLastError(); + } + CloseServiceHandle(schService); return {}; -- cgit v1.2.3 From 6362d82d2476d740968665e0b4f10750236d201e Mon Sep 17 00:00:00 2001 From: Liam Mitchell Date: Tue, 25 Mar 2025 18:02:11 -0700 Subject: Avoid leaking service handle in Windows service installation --- src/zenutil/service.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index e1c5e17fa..760e52baf 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -436,6 +436,8 @@ InstallService(std::string_view ServiceName, const ServiceSpec& Spec) return MakeErrorCodeFromLastError(); } + auto _ = MakeGuard([schService]() { CloseServiceHandle(schService); }); + if (!Spec.Description.empty()) { ExtendableWideStringBuilder<128> DescriptionBuilder; @@ -468,8 +470,6 @@ InstallService(std::string_view ServiceName, const ServiceSpec& Spec) return MakeErrorCodeFromLastError(); } - CloseServiceHandle(schService); - return {}; } -- cgit v1.2.3 From aa3c95266ace162ed3ff83e71df50e2321a6364b Mon Sep 17 00:00:00 2001 From: Liam Mitchell Date: Thu, 27 Mar 2025 15:00:46 -0700 Subject: Fix naming of service handle close guard variable --- src/zenutil/service.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index 760e52baf..6e68a01fa 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -436,7 +436,7 @@ InstallService(std::string_view ServiceName, const ServiceSpec& Spec) return MakeErrorCodeFromLastError(); } - auto _ = MakeGuard([schService]() { CloseServiceHandle(schService); }); + auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); }); if (!Spec.Description.empty()) { -- cgit v1.2.3 From baa420dbc358cb48d26e0aa94a36196f737b3f04 Mon Sep 17 00:00:00 2001 From: Liam Mitchell Date: Fri, 25 Jul 2025 20:58:17 +0000 Subject: Fix permissions and ownership issues with service binary copy and remove unnecessary alias from unit file --- src/zenutil/service.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index 6e68a01fa..25941bde1 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -321,7 +321,6 @@ namespace { std::string BuildUnitFile(std::string_view ServiceName, const std::filesystem::path& ExecutablePath, std::string_view CommandLineOptions, - std::string_view AliasName, std::string_view UserName) { return fmt::format( @@ -341,14 +340,12 @@ namespace { "ExecStart={} {}\n" "RuntimeDirectory={}\n" "[Install]\n" - "Alias={}\n" "WantedBy=multi-user.target", ServiceName, UserName, ExecutablePath, CommandLineOptions, - ExecutablePath.parent_path(), - AliasName); + ExecutablePath.parent_path()); } #endif // ZEN_PLATFORM_LINUX @@ -917,7 +914,7 @@ InstallService(std::string_view ServiceName, const ServiceSpec& Spec) UserName = UserResult.second; } - std::string UnitFile = BuildUnitFile(ServiceName, Spec.ExecutablePath, Spec.CommandLineOptions, UnitName, UserName); + std::string UnitFile = BuildUnitFile(ServiceName, Spec.ExecutablePath, Spec.CommandLineOptions, UserName); ZEN_DEBUG("Writing systemd unit file to {}", ServiceUnitPath.string()); try { -- cgit v1.2.3 From 8d8d23acb100e34915fc2caddb0e5efbdbb1419e Mon Sep 17 00:00:00 2001 From: Liam Mitchell Date: Wed, 20 Aug 2025 23:49:43 +0000 Subject: Fix for MacOS compile failure --- src/zenutil/service.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index 25941bde1..ab9553cf4 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -882,11 +882,13 @@ StopService(std::string_view ServiceName) const std::string DaemonName = GetDaemonName(ServiceName); const std::filesystem::path PListPath = GetPListPath(DaemonName); + /* std::pair Res = ExecuteProgram(fmt::format("launchctl bootout system ", PListPath.)); if (Res.first != 0) { return MakeErrorCode(Res.first); } + */ return {}; } -- cgit v1.2.3 From b905b6d1d94c2a09c268603935991ee3c018c700 Mon Sep 17 00:00:00 2001 From: Liam Mitchell Date: Fri, 22 Aug 2025 22:29:14 +0000 Subject: Move ReportServiceStatus to zenutil and remove extraneous logging --- src/zenutil/service.cpp | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index ab9553cf4..bf2d7d630 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -25,11 +25,54 @@ # include # include # include + +ZEN_THIRD_PARTY_INCLUDES_START +# include +ZEN_THIRD_PARTY_INCLUDES_END + #endif namespace zen { using namespace std::literals; +void +ReportServiceStatus(ServiceStatus Status) +{ +#if ZEN_PLATFORM_WINDOWS + switch (Status) + { + case ServiceStatus::Starting: + ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000); + break; + case ServiceStatus::Running: + ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0); + break; + case ServiceStatus::Stopping: + ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0); + break; + case ServiceStatus::Stopped: + ReportSvcStatus(SERVICE_STOPPED, (DWORD)ApplicationExitCode(), 0); + break; + default: + break; + } +#elif ZEN_PLATFORM_LINUX + switch (Status) + { + case ServiceStatus::Running: + sd_notify(0, "READY=1"); + break; + case ServiceStatus::Stopping: + sd_notify(0, "STOPPING=1"); + break; + case ServiceStatus::Stopped: + sd_notifyf(0, "EXIT_STATUS=%d", ApplicationExitCode()); + break; + } +#endif + (void)Status; +} + namespace { #if ZEN_PLATFORM_WINDOWS -- cgit v1.2.3 From 623f5bbd39e24a8a26203c02bafc3800d74d1db0 Mon Sep 17 00:00:00 2001 From: Liam Mitchell Date: Fri, 22 Aug 2025 15:51:32 -0700 Subject: Move windows service utilities to zenutil and fix clang-format errors --- src/zenutil/service.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index bf2d7d630..e4a9a951e 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -10,6 +10,7 @@ #if ZEN_PLATFORM_WINDOWS # include +# include #endif #if ZEN_PLATFORM_MAC # include -- cgit v1.2.3