From 76d45d7125fee223daff16f194b86565cf3f4169 Mon Sep 17 00:00:00 2001 From: Ketut Kumajaya Date: Sat, 4 May 2024 20:56:13 +0700 Subject: [PATCH] win32: Forte as a Windows service --- src/arch/utils/mainparam_utils.cpp | 10 ++ src/arch/win32/CMakeLists.txt | 2 + src/arch/win32/main.cpp | 10 +- src/arch/win32/service/CMakeLists.txt | 5 +- src/arch/win32/service/CppWindowsService.cpp | 99 +++++++++++++++++--- src/arch/win32/service/SampleService.cpp | 36 ++++++- src/arch/win32/service/SampleService.h | 7 ++ src/arch/win32/service/ServiceBase.cpp | 2 +- src/arch/win32/service/ServiceInstaller.cpp | 7 +- 9 files changed, 159 insertions(+), 19 deletions(-) diff --git a/src/arch/utils/mainparam_utils.cpp b/src/arch/utils/mainparam_utils.cpp index 716184634..b79b2221a 100644 --- a/src/arch/utils/mainparam_utils.cpp +++ b/src/arch/utils/mainparam_utils.cpp @@ -1,5 +1,6 @@ /******************************************************************************* * Copyright (c) 2018 fortiss GmbH + * 2024 Samator Indo Gas * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0. @@ -9,6 +10,7 @@ * Contributors: * Tarik Terzimehic * - initial API and implementation and/or initial documentation + * Ketut Kumajaya - option to run as a Windows service *******************************************************************************/ #include @@ -40,6 +42,10 @@ void listHelp(){ #ifdef FORTE_TRACE_CTF printf("%-20s Set the output directory for TRACE_CTF\n", " -t "); #endif //FORTE_TRACE_CTF +#ifdef FORTE_WINDOWS_SERVICE + printf("%-20s To install as a Windows service\n", " -install"); + printf("%-20s To remove the service\n", " -remove"); +#endif //FORTE_WINDOWS_SERVICE } /*!\brief Parses the command line arguments passed to the main function @@ -90,6 +96,10 @@ const char *parseCommandLineArguments(int argc, char *arg[]){ barectfSetup(arg[i + 1] ?: ""); break; #endif //FORTE_TRACE_CTF +#ifdef FORTE_WINDOWS_SERVICE + case 's': + break; +#endif //FORTE_WINDOWS_SERVICE default: //! Unknown parameter or -h -> Lists the help for FORTE return ""; } diff --git a/src/arch/win32/CMakeLists.txt b/src/arch/win32/CMakeLists.txt index 8445c56ae..562028e6f 100644 --- a/src/arch/win32/CMakeLists.txt +++ b/src/arch/win32/CMakeLists.txt @@ -1,5 +1,6 @@ #******************************************************************************* # Copyright (c) 2010 - 2015 ACIN, Profactor GmbH, fortiss GmbH +# 2024 Samator Indo Gas # This program and the accompanying materials are made available under the # terms of the Eclipse Public License 2.0 which is available at # http://www.eclipse.org/legal/epl-2.0. @@ -9,6 +10,7 @@ # Contributors: # Alois Zoitl, Gerhard Ebenhofer, Martin Melik-Merkumians - initial API and implementation and/or initial documentation # Christoph Binder - add possibility to configure simulated time +# Ketut Kumajaya - option to run as a Windows service # *******************************************************************************/ SET(SOURCE_GROUP ${SOURCE_GROUP}\\win32) diff --git a/src/arch/win32/main.cpp b/src/arch/win32/main.cpp index f8d30b85c..bcd6685c4 100644 --- a/src/arch/win32/main.cpp +++ b/src/arch/win32/main.cpp @@ -1,5 +1,6 @@ /******************************************************************************* * Copyright (c) 2010 - 2018 ACIN, Profactor GmbH, fortiss GmbH + * 2024 Samator Indo Gas * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0. @@ -9,6 +10,7 @@ * Contributors: * Alois Zoitl, Ingo Hegny, Gerhard Ebenhofer - initial API and implementation and/or initial documentation * Alois Zoitl - cleaned up main, inserted new architecture initilasation api + * Ketut Kumajaya - option to run as a Windows service *******************************************************************************/ #include "../forte_architecture.h" #include "../devlog.h" @@ -61,7 +63,7 @@ void createDev(const char *paMGRID){ delete poDev; } -int main(int argc, char *arg[]){ +int _main(int argc, char *arg[]){ if(CForteArchitecture::initialize()){ @@ -83,3 +85,9 @@ int main(int argc, char *arg[]){ } return 0; } + +#ifndef FORTE_WINDOWS_SERVICE +int main(int argc, char *arg[]){ + return _main(argc, arg); +} +#endif diff --git a/src/arch/win32/service/CMakeLists.txt b/src/arch/win32/service/CMakeLists.txt index 5aac149c1..7b221ea05 100644 --- a/src/arch/win32/service/CMakeLists.txt +++ b/src/arch/win32/service/CMakeLists.txt @@ -10,7 +10,6 @@ # Ketut Kumajaya - initial API and implementation and/or initial documentation # *******************************************************************************/ if(FORTE_WINDOWS_SERVICE) - forte_add_sourcefile_cpp(CppWindowsService.cpp) - forte_add_sourcefile_hcpp(SampleService ServiceBase ServiceInstaller) - forte_add_sourcefile_h(ThreadPool.h) + forte_add_to_executable_h(SampleService ServiceBase ServiceInstaller ThreadPool) + forte_add_to_executable_cpp(CppWindowsService SampleService ServiceBase ServiceInstaller) endif() diff --git a/src/arch/win32/service/CppWindowsService.cpp b/src/arch/win32/service/CppWindowsService.cpp index 8976727af..788190727 100644 --- a/src/arch/win32/service/CppWindowsService.cpp +++ b/src/arch/win32/service/CppWindowsService.cpp @@ -3,6 +3,8 @@ * Project: CppWindowsService * Copyright (c) Microsoft Corporation. * +* 2024, Ketut Kumajaya - modified for FORTE to run as a Windows service +* * The file defines the entry point of the application. According to the * arguments in the command line, the function installs or uninstalls or * starts the service by calling into different routines. @@ -17,7 +19,7 @@ \***************************************************************************/ #pragma region Includes -#include +#include #include #include "ServiceInstaller.h" #include "ServiceBase.h" @@ -25,15 +27,29 @@ #pragma endregion +#include +#include +#include +#include +#include "../../devlog.h" +#include "../../forte_stringFunctions.h" + +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 +#define STDERR_FILENO 2 + +int _main(int argc, char *arg[]); + + // // Settings of the service // // Internal name of the service -#define SERVICE_NAME (PWSTR)L"CppWindowsService" +#define SERVICE_NAME (PWSTR)L"4diac-forte" // Displayed name of the service -#define SERVICE_DISPLAY_NAME (PWSTR)L"CppWindowsService Sample Service" +#define SERVICE_DISPLAY_NAME (PWSTR)L"4diac FORTE Runtime for Windows" // Service start options. #define SERVICE_START_TYPE SERVICE_DEMAND_START @@ -42,12 +58,28 @@ #define SERVICE_DEPENDENCIES (PWSTR)L"" // The name of the account under which the service should run -#define SERVICE_ACCOUNT (PWSTR)L"NT AUTHORITY\\LocalService" +#define SERVICE_ACCOUNT (PWSTR)L"LocalSystem" // The password to the service account name #define SERVICE_PASSWORD NULL +// Modified from https://stackoverflow.com/a/21376268 +void EnableLogRedirection(const std::wstring &fname, std::FILE *fstream) +{ + // Duplicate stdout/stderr and give it a new file descriptor + int fd = _dup((fstream == stdout) ? STDOUT_FILENO : STDERR_FILENO); + if (fd != -1) + { + // Re-open stdout/stderr to the new file + _wfreopen(fname.c_str(), (PWSTR)L"w", fstream); + _close(fd); + } + + fflush(fstream); +} + + // // FUNCTION: wmain(int, wchar_t *[]) // @@ -65,6 +97,22 @@ // int wmain(int argc, wchar_t *argv[]) { + int ret = -1; + + // User can edit forte command line arguments in + // HKLM\SYSTEM\CurrentControlSet\Services\4diac-forte\ImagePath + + // Reconstruct command line arguments from wide to narrow string + std::vector l_argv; + for (int i = 0; i < argc; ++i) + { + std::string str = forte_wstringToString(argv[i]); + char* buf = new char[str.length() + 1]; + std::strcpy(buf, str.c_str()); + l_argv.push_back(buf); + } + l_argv.push_back(nullptr); // argv[argc] is this null pointer + if ((argc > 1) && ((*argv[1] == L'-' || (*argv[1] == L'/')))) { if (_wcsicmp(L"install", argv[1] + 1) == 0) @@ -86,19 +134,48 @@ int wmain(int argc, wchar_t *argv[]) // "-remove" or "/remove". UninstallService(SERVICE_NAME); } + else if (_wcsicmp(L"service", argv[1] + 1) == 0 || _wcsicmp(L"s", argv[1] + 1) == 0) + { + // Start setup stdout/stderr redirection + wchar_t szPath[MAX_PATH]; + if (GetModuleFileNameW(NULL, szPath, ARRAYSIZE(szPath)) == 0) { + DEVLOG_ERROR("GetModuleFileNameW failed w/err 0x%08lx\n", GetLastError()); + } + + std::filesystem::path fullpath(szPath); + // Disable logging with "-service nolog" argument + if (std::filesystem::exists(fullpath) && _wcsicmp(L"nolog", argv[2]) != 0) { + std::wstring logfile = fullpath.replace_extension("log").wstring(); + EnableLogRedirection(logfile, stdout); + logfile = fullpath.replace_extension("err").wstring(); + EnableLogRedirection(logfile, stderr); + } + + CSampleService service(SERVICE_DISPLAY_NAME, static_cast(l_argv.size() - 1), l_argv.data()); + if (!CServiceBase::Run(service)) + { + DEVLOG_ERROR("Service failed to run w/err 0x%08lx\n", GetLastError()); + } + } + else + { + ret = _main(static_cast(l_argv.size() - 1), l_argv.data()); + } } else { - wprintf(L"Parameters:\n"); - wprintf(L" -install to install the service.\n"); - wprintf(L" -remove to remove the service.\n"); + ret = _main(static_cast(l_argv.size() - 1), l_argv.data()); + } - CSampleService service(SERVICE_NAME); - if (!CServiceBase::Run(service)) + // Clear the command line arguments + for(auto &buf : l_argv) + { + if (buf != nullptr) { - wprintf(L"Service failed to run w/err 0x%08lx\n", GetLastError()); + delete[] buf; } } + l_argv.clear(); - return 0; + return ret; } \ No newline at end of file diff --git a/src/arch/win32/service/SampleService.cpp b/src/arch/win32/service/SampleService.cpp index 6c3d0497b..ca2a5ca16 100644 --- a/src/arch/win32/service/SampleService.cpp +++ b/src/arch/win32/service/SampleService.cpp @@ -3,6 +3,8 @@ * Project: CppWindowsService * Copyright (c) Microsoft Corporation. * +* 2024, Ketut Kumajaya - modified for FORTE to run as a Windows service +* * Provides a sample service class that derives from the service base class - * CServiceBase. The sample service logs the service start and stop * information to the Application event log, and shows how to run the main @@ -23,13 +25,25 @@ #pragma endregion +#include +#include "../../devlog.h" +#include "../../forte_stringFunctions.h" + +void endForte(int paSig); +int _main(int argc, char *arg[]); + + CSampleService::CSampleService(PWSTR pszServiceName, + int argc, + char *argv[], BOOL fCanStop, BOOL fCanShutdown, BOOL fCanPauseContinue) : CServiceBase(pszServiceName, fCanStop, fCanShutdown, fCanPauseContinue) { m_fStopping = FALSE; + m_argc = argc; + m_argv = argv; // Create a manual-reset event that is not signaled at first to indicate // the stopped signal of the service. @@ -78,8 +92,15 @@ CSampleService::~CSampleService(void) // void CSampleService::OnStart(DWORD, LPWSTR*) { + // Log command line arguments + std::ostringstream stream; + for (int i = 0; i < m_argc; ++i) + { + stream << " " << m_argv[i]; + } + // Log a service start message to the Application log. - WriteEventLogEntry((PWSTR)L"CppWindowsService in OnStart", + WriteEventLogEntry((PWSTR)(forte_stringToWstring(("FORTE service started:" + stream.str())).c_str()), EVENTLOG_INFORMATION_TYPE); // Queue the main service function for execution in a worker thread. @@ -98,7 +119,14 @@ void CSampleService::ServiceWorkerThread(void) // Periodically check if the service is stopping. while (!m_fStopping) { + // Log a prosess start message + DEVLOG_INFO("FORTE process start\n"); + // Perform main service function here... + _main(m_argc, m_argv); + + // Log a prosess stop message + DEVLOG_INFO("FORTE process end\n"); ::Sleep(2000); // Simulate some lengthy operations. } @@ -123,12 +151,16 @@ void CSampleService::ServiceWorkerThread(void) void CSampleService::OnStop() { // Log a service stop message to the Application log. - WriteEventLogEntry((PWSTR)L"CppWindowsService in OnStop", + WriteEventLogEntry((PWSTR)L"FORTE service stopped", EVENTLOG_INFORMATION_TYPE); // Indicate that the service is stopping and wait for the finish of the // main service function (ServiceWorkerThread). m_fStopping = TRUE; + + // Kill FORTE process + endForte(0); + if (WaitForSingleObject(m_hStoppedEvent, INFINITE) != WAIT_OBJECT_0) { throw GetLastError(); diff --git a/src/arch/win32/service/SampleService.h b/src/arch/win32/service/SampleService.h index 8d468a8a7..231d99721 100644 --- a/src/arch/win32/service/SampleService.h +++ b/src/arch/win32/service/SampleService.h @@ -3,6 +3,8 @@ * Project: CppWindowsService * Copyright (c) Microsoft Corporation. * +* 2024, Ketut Kumajaya - modified for FORTE to run as a Windows service +* * Provides a sample service class that derives from the service base class - * CServiceBase. The sample service logs the service start and stop * information to the Application event log, and shows how to run the main @@ -27,6 +29,8 @@ class CSampleService : public CServiceBase public: CSampleService(PWSTR pszServiceName, + int argc, + char *argv[], BOOL fCanStop = TRUE, BOOL fCanShutdown = TRUE, BOOL fCanPauseContinue = FALSE); @@ -43,4 +47,7 @@ class CSampleService : public CServiceBase BOOL m_fStopping; HANDLE m_hStoppedEvent; + + int m_argc; + char **m_argv; }; \ No newline at end of file diff --git a/src/arch/win32/service/ServiceBase.cpp b/src/arch/win32/service/ServiceBase.cpp index 6eef9f0f1..cb882ec03 100644 --- a/src/arch/win32/service/ServiceBase.cpp +++ b/src/arch/win32/service/ServiceBase.cpp @@ -18,7 +18,7 @@ #pragma region Includes #include "ServiceBase.h" -#include +#include #include #pragma endregion diff --git a/src/arch/win32/service/ServiceInstaller.cpp b/src/arch/win32/service/ServiceInstaller.cpp index b7774ef19..f9da02e6d 100644 --- a/src/arch/win32/service/ServiceInstaller.cpp +++ b/src/arch/win32/service/ServiceInstaller.cpp @@ -3,6 +3,8 @@ * Project: CppWindowsService * Copyright (c) Microsoft Corporation. * +* 2024, Ketut Kumajaya - modified for FORTE to run as a Windows service +* * The file implements functions that install and uninstall the service. * * This source is subject to the Microsoft Public License. @@ -15,7 +17,7 @@ \***************************************************************************/ #pragma region "Includes" -#include +#include #include #include "ServiceInstaller.h" #pragma endregion @@ -59,6 +61,9 @@ void InstallService(PWSTR pszServiceName, goto Cleanup; } + // Append argument to support binary as service, log redirection enabled + wcscat(szPath, L" -service log"); + // Open the local default service control manager database schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE);