inspircd/win/win32service.cpp
Sadie Powell b4e3e97329 Simplify the codes that InspIRCd can exit with.
The custom codes that InspIRCd exits with are not very useful and can confuse init systems like systemd which assume that certain
exit codes mean certain things. INSPIRCD_BINARY_EXIT was a workaround
for this in v3 but considering thatsers have to check the logs anyway
so we may as well just use EXIT_SUCCESS and EXIT_FAILURE.
2023-07-13 14:07:26 +01:00

289 lines
7.9 KiB
C++

/*
* InspIRCd -- Internet Relay Chat Daemon
*
* Copyright (C) 2021 Herman <GermanAizek@yandex.ru>
* Copyright (C) 2012-2013 ChrisTX <xpipe@hotmail.de>
* Copyright (C) 2012-2013 Attila Molnar <attilamolnar@hush.com>
* Copyright (C) 2012 Robby <robby@chatbelgie.be>
* Copyright (C) 2008 Craig Edwards <brain@inspircd.org>
*
* This file is part of InspIRCd. InspIRCd is free software: you can
* redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "inspircd.h"
#include <iostream>
#include <windows.h>
static SERVICE_STATUS_HANDLE g_ServiceStatusHandle;
static SERVICE_STATUS g_ServiceStatus;
static bool g_bRunningAsService;
struct Service_Data {
DWORD argc;
LPSTR* argv;
};
static Service_Data g_ServiceData;
/** The main part of inspircd runs within this thread function. This allows the service part to run
* separately on its own and to be able to kill the worker thread when its time to quit.
*/
DWORD WINAPI WorkerThread(LPVOID param)
{
smain(g_ServiceData.argc, g_ServiceData.argv);
return 0;
}
/* This is called when all startup is done */
void SetServiceRunning()
{
if (!g_bRunningAsService)
return;
g_ServiceStatus.dwCurrentState = SERVICE_RUNNING;
g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
if( !SetServiceStatus( g_ServiceStatusHandle, &g_ServiceStatus ) )
throw CWin32Exception();
}
/* In windows we hook this to InspIRCd::Exit(EXIT_FAILURE) */
void SetServiceStopped(DWORD dwStatus)
{
if (!g_bRunningAsService)
return;
g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
if(dwStatus != EXIT_SUCCESS)
{
g_ServiceStatus.dwServiceSpecificExitCode = dwStatus;
g_ServiceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
}
else
{
g_ServiceStatus.dwWin32ExitCode = ERROR_SUCCESS;
}
SetServiceStatus( g_ServiceStatusHandle, &g_ServiceStatus );
}
/** This callback is called by windows when the state of the service has been changed */
VOID ServiceCtrlHandler(DWORD controlCode)
{
switch(controlCode)
{
case SERVICE_CONTROL_SHUTDOWN:
case SERVICE_CONTROL_STOP:
g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus( g_ServiceStatusHandle, &g_ServiceStatus );
break;
}
}
/** This callback is called by windows when the service is started */
VOID ServiceMain(DWORD argc, LPCSTR* argv)
{
g_ServiceStatusHandle = RegisterServiceCtrlHandler(TEXT("InspIRCd"), (LPHANDLER_FUNCTION)ServiceCtrlHandler);
if( !g_ServiceStatusHandle )
return;
g_ServiceStatus.dwCheckPoint = 1;
g_ServiceStatus.dwControlsAccepted = 0;
g_ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
g_ServiceStatus.dwWaitHint = 5000;
g_ServiceStatus.dwWin32ExitCode = NO_ERROR;
g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
if( !SetServiceStatus( g_ServiceStatusHandle, &g_ServiceStatus ) )
return;
char szModuleName[MAX_PATH];
if(GetModuleFileNameA(nullptr, szModuleName, MAX_PATH))
{
if(!argc)
argc = 1;
g_ServiceData.argc = argc;
// Note: since this memory is going to stay allocated for the rest of the execution,
// it doesn't make sense to free it, as it's going to be "freed" on process termination
try {
g_ServiceData.argv = new char*[argc];
uint32_t allocsize = strnlen_s(szModuleName, MAX_PATH) + 1;
g_ServiceData.argv[0] = new char[allocsize];
strcpy_s(g_ServiceData.argv[0], allocsize, szModuleName);
for(uint32_t i = 1; i < argc; i++)
{
allocsize = strnlen_s(argv[i], MAX_PATH) + 1;
g_ServiceData.argv[i] = new char[allocsize];
strcpy_s(g_ServiceData.argv[i], allocsize, argv[i]);
}
*(strrchr(szModuleName, '\\') + 1) = '\0';
SetCurrentDirectoryA(szModuleName);
HANDLE hThread = CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)WorkerThread, nullptr, 0, nullptr);
if (hThread != nullptr)
{
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
}
}
catch(...)
{
g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
g_ServiceStatus.dwWin32ExitCode = ERROR_OUTOFMEMORY;
SetServiceStatus( g_ServiceStatusHandle, &g_ServiceStatus );
}
}
if(g_ServiceStatus.dwCurrentState == SERVICE_STOPPED)
return;
g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
g_ServiceStatus.dwWin32ExitCode = GetLastError();
SetServiceStatus( g_ServiceStatusHandle, &g_ServiceStatus );
}
/** Install the windows service. This requires administrator privileges. */
void InstallService()
{
SC_HANDLE InspServiceHandle = 0, SCMHandle = 0;
try {
TCHAR tszBinaryPath[MAX_PATH];
if(!GetModuleFileName(nullptr, tszBinaryPath, _countof(tszBinaryPath)))
{
throw CWin32Exception();
}
SCMHandle = OpenSCManager(0, 0, SC_MANAGER_ALL_ACCESS);
if (!SCMHandle)
{
throw CWin32Exception();
}
InspServiceHandle = CreateService(SCMHandle, TEXT("InspIRCd"), TEXT("InspIRCd Daemon"), SERVICE_CHANGE_CONFIG, SERVICE_WIN32_OWN_PROCESS,
SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, tszBinaryPath, 0, 0, 0, TEXT("NT AUTHORITY\\NetworkService"), nullptr);
if (!InspServiceHandle)
{
throw CWin32Exception();
}
TCHAR tszDescription[] = TEXT("The InspIRCd service hosts IRC channels and conversations. If this service is stopped, the IRC server will be unavailable.");
SERVICE_DESCRIPTION svDescription = { tszDescription };
if(!ChangeServiceConfig2(InspServiceHandle, SERVICE_CONFIG_DESCRIPTION, &svDescription))
{
throw CWin32Exception();
}
CloseServiceHandle(InspServiceHandle);
CloseServiceHandle(SCMHandle);
std::cout << "Service installed." << std::endl;
}
catch(const CWin32Exception& e)
{
if(InspServiceHandle)
CloseServiceHandle(InspServiceHandle);
if(SCMHandle)
CloseServiceHandle(SCMHandle);
std::cout << "Service installation failed: " << e.what() << std::endl;
}
}
/** Remove the windows service. This requires administrator privileges. */
void UninstallService()
{
SC_HANDLE InspServiceHandle = 0, SCMHandle = 0;
try
{
SCMHandle = OpenSCManager(nullptr, SERVICES_ACTIVE_DATABASE, DELETE);
if (!SCMHandle)
throw CWin32Exception();
InspServiceHandle = OpenService(SCMHandle, TEXT("InspIRCd"), DELETE);
if (!InspServiceHandle)
throw CWin32Exception();
if (!DeleteService(InspServiceHandle) && GetLastError() != ERROR_SERVICE_MARKED_FOR_DELETE)
{
throw CWin32Exception();
}
CloseServiceHandle(InspServiceHandle);
CloseServiceHandle(SCMHandle);
std::cout << "Service removed." << std::endl;
}
catch(const CWin32Exception& e)
{
if(InspServiceHandle)
CloseServiceHandle(InspServiceHandle);
if(SCMHandle)
CloseServiceHandle(SCMHandle);
std::cout << "Service deletion failed: " << e.what() << std::endl;
}
}
/* In windows, our main() flows through here, before calling the 'real' main, smain() in inspircd.cpp */
int main(int argc, char* argv[])
{
/* Check for parameters */
if (argc > 1)
{
for (int i = 1; i < argc; i++)
{
if(!_stricmp(argv[i], "--installservice"))
{
InstallService();
return EXIT_SUCCESS;
}
if(!_stricmp(argv[i], "--uninstallservice") || !_stricmp(argv[i], "--removeservice"))
{
UninstallService();
return EXIT_SUCCESS;
}
}
}
SERVICE_TABLE_ENTRY serviceTable[] =
{
{ TEXT("InspIRCd"), (LPSERVICE_MAIN_FUNCTION)ServiceMain },
{ nullptr, nullptr }
};
g_bRunningAsService = true;
if( !StartServiceCtrlDispatcher(serviceTable) )
{
// This error means that the program was not started as service.
if( GetLastError() == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT )
{
g_bRunningAsService = false;
return smain(argc, argv);
}
else
{
return EXIT_FAILURE;
}
}
return EXIT_SUCCESS;
}