diff --git a/Makefile b/Makefile index f1258874..5d2f24f4 100644 --- a/Makefile +++ b/Makefile @@ -62,7 +62,7 @@ else ifneq (, $(findstring freebsd, $(SYS))$(findstring openbsd, $(SYS))) DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp include Makefile.bsd else ifneq (, $(findstring mingw, $(SYS))$(findstring cygwin, $(SYS))) - DAEMON_SRC += Win32/DaemonWin32.cpp Win32/Win32App.cpp Win32/Win32NetState.cpp + DAEMON_SRC += Win32/DaemonWin32.cpp Win32/Win32App.cpp Win32/Win32Service.cpp Win32/Win32NetState.cpp include Makefile.mingw else # not supported $(error Not supported platform) diff --git a/Win32/DaemonWin32.cpp b/Win32/DaemonWin32.cpp index 6bb953e9..0badf802 100644 --- a/Win32/DaemonWin32.cpp +++ b/Win32/DaemonWin32.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2020, The PurpleI2P Project +* Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -14,6 +14,7 @@ #include "Log.h" #ifdef _WIN32 +#include "Win32Service.h" #ifdef WIN32_APP #include #include "Win32App.h" @@ -39,6 +40,19 @@ namespace util if (!Daemon_Singleton::init(argc, argv)) return false; + + if (isDaemon) + { + LogPrint(eLogDebug, "Daemon: running as service"); + I2PService service((PSTR)SERVICE_NAME); + if (!I2PService::Run(service)) + { + LogPrint(eLogError, "Daemon: Service failed to run w/err 0x%08lx\n", GetLastError()); + return false; + } + return false; + } + return true; } diff --git a/Win32/Win32Service.cpp b/Win32/Win32Service.cpp new file mode 100644 index 00000000..d4ba0d76 --- /dev/null +++ b/Win32/Win32Service.cpp @@ -0,0 +1,283 @@ +/* +* Copyright (c) 2013-2022, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include "Win32Service.h" +#include +#include + +#include "Daemon.h" +#include "Log.h" + +I2PService *I2PService::s_service = NULL; + +BOOL I2PService::isService() +{ + BOOL bIsService = FALSE; + HWINSTA hWinStation = GetProcessWindowStation(); + if (hWinStation != NULL) + { + USEROBJECTFLAGS uof = { 0 }; + if (GetUserObjectInformation(hWinStation, UOI_FLAGS, &uof, sizeof(USEROBJECTFLAGS), NULL) && ((uof.dwFlags & WSF_VISIBLE) == 0)) + { + bIsService = TRUE; + } + } + return bIsService; +} + +BOOL I2PService::Run(I2PService &service) +{ + s_service = &service; + SERVICE_TABLE_ENTRY serviceTable[] = + { + { service.m_name, ServiceMain }, + { NULL, NULL } + }; + return StartServiceCtrlDispatcher(serviceTable); +} + +void WINAPI I2PService::ServiceMain(DWORD dwArgc, PSTR *pszArgv) +{ + assert(s_service != NULL); + s_service->m_statusHandle = RegisterServiceCtrlHandler( + s_service->m_name, ServiceCtrlHandler); + if (s_service->m_statusHandle == NULL) + { + throw GetLastError(); + } + s_service->Start(dwArgc, pszArgv); +} + + +void WINAPI I2PService::ServiceCtrlHandler(DWORD dwCtrl) +{ + switch (dwCtrl) + { + case SERVICE_CONTROL_STOP: s_service->Stop(); break; + case SERVICE_CONTROL_PAUSE: s_service->Pause(); break; + case SERVICE_CONTROL_CONTINUE: s_service->Continue(); break; + case SERVICE_CONTROL_SHUTDOWN: s_service->Shutdown(); break; + case SERVICE_CONTROL_INTERROGATE: break; + default: break; + } +} + +I2PService::I2PService(PSTR pszServiceName, + BOOL fCanStop, + BOOL fCanShutdown, + BOOL fCanPauseContinue) +{ + m_name = (pszServiceName == NULL) ? (PSTR)"" : pszServiceName; + m_statusHandle = NULL; + m_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + m_status.dwCurrentState = SERVICE_START_PENDING; + + DWORD dwControlsAccepted = 0; + if (fCanStop) + dwControlsAccepted |= SERVICE_ACCEPT_STOP; + if (fCanShutdown) + dwControlsAccepted |= SERVICE_ACCEPT_SHUTDOWN; + if (fCanPauseContinue) + dwControlsAccepted |= SERVICE_ACCEPT_PAUSE_CONTINUE; + + m_status.dwControlsAccepted = dwControlsAccepted; + m_status.dwWin32ExitCode = NO_ERROR; + m_status.dwServiceSpecificExitCode = 0; + m_status.dwCheckPoint = 0; + m_status.dwWaitHint = 0; + m_fStopping = FALSE; + // Create a manual-reset event that is not signaled at first to indicate + // the stopped signal of the service. + m_hStoppedEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (m_hStoppedEvent == NULL) + { + throw GetLastError(); + } +} + +I2PService::~I2PService(void) +{ + if (m_hStoppedEvent) + { + CloseHandle(m_hStoppedEvent); + m_hStoppedEvent = NULL; + } +} + +void I2PService::Start(DWORD dwArgc, PSTR *pszArgv) +{ + try + { + SetServiceStatus(SERVICE_START_PENDING); + OnStart(dwArgc, pszArgv); + SetServiceStatus(SERVICE_RUNNING); + } + catch (DWORD dwError) + { + LogPrint(eLogError, "Win32Service: Start error: ", dwError); + SetServiceStatus(SERVICE_STOPPED, dwError); + } + catch (...) + { + LogPrint(eLogError, "Win32Service: failed to start: ", EVENTLOG_ERROR_TYPE); + SetServiceStatus(SERVICE_STOPPED); + } +} + +void I2PService::OnStart(DWORD dwArgc, PSTR *pszArgv) +{ + LogPrint(eLogInfo, "Win32Service: in OnStart (", EVENTLOG_INFORMATION_TYPE, ")"); + Daemon.start(); + _worker = new std::thread(std::bind(&I2PService::WorkerThread, this)); +} + +void I2PService::WorkerThread() +{ + while (!m_fStopping) + { + ::Sleep(1000); // Simulate some lengthy operations. + } + // Signal the stopped event. + SetEvent(m_hStoppedEvent); +} + +void I2PService::Stop() +{ + DWORD dwOriginalState = m_status.dwCurrentState; + try + { + SetServiceStatus(SERVICE_STOP_PENDING); + OnStop(); + SetServiceStatus(SERVICE_STOPPED); + } + catch (DWORD dwError) + { + LogPrint(eLogInfo, "Win32Service: Stop error: ", dwError); + SetServiceStatus(dwOriginalState); + } + catch (...) + { + LogPrint(eLogError, "Win32Service: Failed to stop: ", EVENTLOG_ERROR_TYPE); + SetServiceStatus(dwOriginalState); + } +} + +void I2PService::OnStop() +{ + // Log a service stop message to the Application log. + LogPrint(eLogInfo, "Win32Service: in OnStop (", EVENTLOG_INFORMATION_TYPE, ")"); + Daemon.stop(); + m_fStopping = TRUE; + if (WaitForSingleObject(m_hStoppedEvent, INFINITE) != WAIT_OBJECT_0) + { + throw GetLastError(); + } + _worker->join(); + delete _worker; +} + +void I2PService::Pause() +{ + try + { + SetServiceStatus(SERVICE_PAUSE_PENDING); + OnPause(); + SetServiceStatus(SERVICE_PAUSED); + } + catch (DWORD dwError) + { + LogPrint(eLogError, "Win32Service: Pause error: ", dwError); + SetServiceStatus(SERVICE_RUNNING); + } + catch (...) + { + LogPrint(eLogError, "Win32Service: Failed to pause: ", EVENTLOG_ERROR_TYPE); + SetServiceStatus(SERVICE_RUNNING); + } +} + +void I2PService::OnPause() +{ +} + +void I2PService::Continue() +{ + try + { + SetServiceStatus(SERVICE_CONTINUE_PENDING); + OnContinue(); + SetServiceStatus(SERVICE_RUNNING); + } + catch (DWORD dwError) + { + LogPrint(eLogError, "Win32Service: Continue error: ", dwError); + SetServiceStatus(SERVICE_PAUSED); + } + catch (...) + { + LogPrint(eLogError, "Win32Service: Failed to resume: ", EVENTLOG_ERROR_TYPE); + SetServiceStatus(SERVICE_PAUSED); + } +} + +void I2PService::OnContinue() +{ +} + +void I2PService::Shutdown() +{ + try + { + OnShutdown(); + SetServiceStatus(SERVICE_STOPPED); + } + catch (DWORD dwError) + { + LogPrint(eLogError, "Win32Service: Shutdown error: ", dwError); + } + catch (...) + { + LogPrint(eLogError, "Win32Service: Failed to shut down: ", EVENTLOG_ERROR_TYPE); + } +} + +void I2PService::OnShutdown() +{ +} + +void I2PService::SetServiceStatus(DWORD dwCurrentState, + DWORD dwWin32ExitCode, + DWORD dwWaitHint) +{ + static DWORD dwCheckPoint = 1; + m_status.dwCurrentState = dwCurrentState; + m_status.dwWin32ExitCode = dwWin32ExitCode; + m_status.dwWaitHint = dwWaitHint; + m_status.dwCheckPoint = + ((dwCurrentState == SERVICE_RUNNING) || + (dwCurrentState == SERVICE_STOPPED)) ? + 0 : dwCheckPoint++; + + ::SetServiceStatus(m_statusHandle, &m_status); +} + +//***************************************************************************** + +void FreeHandles(SC_HANDLE schSCManager, SC_HANDLE schService) +{ + if (schSCManager) + { + CloseServiceHandle(schSCManager); + schSCManager = NULL; + } + if (schService) + { + CloseServiceHandle(schService); + schService = NULL; + } +} diff --git a/Win32/Win32Service.h b/Win32/Win32Service.h new file mode 100644 index 00000000..5cedbed6 --- /dev/null +++ b/Win32/Win32Service.h @@ -0,0 +1,63 @@ +/* +* Copyright (c) 2013-2022, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#ifndef WIN_32_SERVICE_H__ +#define WIN_32_SERVICE_H__ + +#include +#include + +#define SERVICE_NAME "i2pdService" + +class I2PService +{ + public: + + I2PService(PSTR pszServiceName, + BOOL fCanStop = TRUE, + BOOL fCanShutdown = TRUE, + BOOL fCanPauseContinue = FALSE); + + virtual ~I2PService(void); + + static BOOL isService(); + static BOOL Run(I2PService &service); + void Stop(); + + protected: + + virtual void OnStart(DWORD dwArgc, PSTR *pszArgv); + virtual void OnStop(); + virtual void OnPause(); + virtual void OnContinue(); + virtual void OnShutdown(); + void SetServiceStatus(DWORD dwCurrentState, + DWORD dwWin32ExitCode = NO_ERROR, + DWORD dwWaitHint = 0); + + private: + + static void WINAPI ServiceMain(DWORD dwArgc, LPSTR *lpszArgv); + static void WINAPI ServiceCtrlHandler(DWORD dwCtrl); + void WorkerThread(); + void Start(DWORD dwArgc, PSTR *pszArgv); + void Pause(); + void Continue(); + void Shutdown(); + static I2PService* s_service; + PSTR m_name; + SERVICE_STATUS m_status; + SERVICE_STATUS_HANDLE m_statusHandle; + + BOOL m_fStopping; + HANDLE m_hStoppedEvent; + + std::thread* _worker; +}; + +#endif // WIN_32_SERVICE_H__