summaryrefslogtreecommitdiffstats
path: root/src/OSSupport/StartAsService.h
blob: a7811494b0824a4738dc4270cf0bc5fc0eeae533 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165

// StartAsService.h

// Handles startup as a Windows Service or UNIX daemon.

// This file MUST NOT be included from anywhere other than main.cpp.





#ifdef _WIN32

class StartAsService
{
  public:
	/** Make a Windows service. */
	template <auto UniversalMain> static bool MakeIntoService()
	{
		SERVICE_TABLE_ENTRY ServiceTable[] = {
			{g_ServiceName, (LPSERVICE_MAIN_FUNCTION) serviceMain<UniversalMain>},
			{nullptr, nullptr}
		};

		if (StartServiceCtrlDispatcher(ServiceTable) == FALSE)
		{
			throw std::system_error(GetLastError(), std::system_category());
		}

		return true;
	}

  private:
	/** Set the internal status of the service */
	static void serviceSetState(DWORD acceptedControls, DWORD newState, DWORD exitCode)
	{
		SERVICE_STATUS serviceStatus = {};
		serviceStatus.dwCheckPoint = 0;
		serviceStatus.dwControlsAccepted = acceptedControls;
		serviceStatus.dwCurrentState = newState;
		serviceStatus.dwServiceSpecificExitCode = 0;
		serviceStatus.dwServiceType = SERVICE_WIN32;
		serviceStatus.dwWaitHint = 0;
		serviceStatus.dwWin32ExitCode = exitCode;

		if (SetServiceStatus(g_StatusHandle, &serviceStatus) == FALSE)
		{
			LOGERROR("SetServiceStatus() failed\n");
		}
	}

	/** Handle stop events from the Service Control Manager */
	static void WINAPI serviceCtrlHandler(DWORD CtrlCode)
	{
		if (CtrlCode == SERVICE_CONTROL_STOP)
		{
			std::raise(SIGINT);
			serviceSetState(0, SERVICE_STOP_PENDING, 0);
		}
	}

	/* Startup logic for running as a service */
	template <auto MainFunction> static void WINAPI serviceMain(DWORD argc, TCHAR * argv[])
	{
		wchar_t applicationFilename[MAX_PATH];
		wchar_t applicationDirectory[MAX_PATH];

		// Get this binary's file path:
		if (GetModuleFileName(nullptr, applicationFilename, std::size(applicationFilename)) == 0)
		{
			serviceSetState(0, SERVICE_STOPPED, GetLastError());
			return;
		}

		const auto LastComponent = std::wcsrchr(applicationFilename, L'\\');
		if (LastComponent == nullptr)
		{
			serviceSetState(0, SERVICE_STOPPED, E_UNEXPECTED);
			return;
		}

		const auto LengthToLastComponent = LastComponent - applicationFilename;

		// Strip off the filename, keep only the path:
		std::wcsncpy(applicationDirectory, applicationFilename, LengthToLastComponent);
		applicationDirectory[LengthToLastComponent] = L'\0';  // Make sure new path is null terminated

		// Services are run by the SCM, and inherit its working directory - usually System32.
		// Set the working directory to the same location as the binary.
		if (SetCurrentDirectory(applicationDirectory) == FALSE)
		{
			serviceSetState(0, SERVICE_STOPPED, GetLastError());
			return;
		}


		g_StatusHandle = RegisterServiceCtrlHandler(g_ServiceName, serviceCtrlHandler);
		if (g_StatusHandle == nullptr)
		{
			OutputDebugStringA("RegisterServiceCtrlHandler() failed\n");
			serviceSetState(0, SERVICE_STOPPED, GetLastError());
			return;
		}

		serviceSetState(SERVICE_ACCEPT_STOP, SERVICE_RUNNING, 0);

		char MultibyteArgV0[MAX_PATH];
		char * MultibyteArgV[] = {MultibyteArgV0};

		const auto OutputSize = std::size(MultibyteArgV0);
		const auto TranslateResult = std::wcstombs(MultibyteArgV0, argv[0], OutputSize);

		if (TranslateResult == static_cast<size_t>(-1))
		{
			// Translation failed entirely (!):
			MultibyteArgV0[0] = '\0';
		}
		else if (TranslateResult == OutputSize)
		{
			// Output too small:
			MultibyteArgV0[OutputSize - 1] = '\0';
		}

		const auto Result = MainFunction(1, MultibyteArgV, true);
		const auto Return = (Result == EXIT_SUCCESS) ? S_OK : E_FAIL;

		serviceSetState(0, SERVICE_STOPPED, Return);
	}

	static inline SERVICE_STATUS_HANDLE g_StatusHandle = nullptr;
	static inline HANDLE g_ServiceThread = INVALID_HANDLE_VALUE;
	static inline wchar_t g_ServiceName[] = L"Cuberite";
};

#else

struct StartAsService
{
	/** Make a UNIX daemon. */
	template <auto> static bool MakeIntoService()
	{
		pid_t pid = fork();

		// fork() returns a negative value on error.
		if (pid < 0)
		{
			throw std::system_error(errno, std::system_category());
		}

		// Check if we are the parent or child process. Parent stops here.
		if (pid > 0)
		{
			return true;
		}

		// Child process now goes quiet, running in the background.
		close(STDIN_FILENO);
		close(STDOUT_FILENO);
		close(STDERR_FILENO);

		return false;
	}
};

#endif