1 | /* |
---|
2 | * winservice.c : Implementation of Windows Service support |
---|
3 | * |
---|
4 | * ==================================================================== |
---|
5 | * Copyright (c) 2000-2006 CollabNet. All rights reserved. |
---|
6 | * |
---|
7 | * This software is licensed as described in the file COPYING, which |
---|
8 | * you should have received as part of this distribution. The terms |
---|
9 | * are also available at http://subversion.tigris.org/license-1.html. |
---|
10 | * If newer versions of this license are posted there, you may use a |
---|
11 | * newer version instead, at your option. |
---|
12 | * |
---|
13 | * This software consists of voluntary contributions made by many |
---|
14 | * individuals. For exact contribution history, see the revision |
---|
15 | * history and logs, available at http://subversion.tigris.org/. |
---|
16 | * ==================================================================== |
---|
17 | */ |
---|
18 | |
---|
19 | |
---|
20 | |
---|
21 | #define APR_WANT_STRFUNC |
---|
22 | #include <apr_want.h> |
---|
23 | #include <apr_errno.h> |
---|
24 | |
---|
25 | #include "svn_error.h" |
---|
26 | |
---|
27 | #include "svn_private_config.h" |
---|
28 | #include "winservice.h" |
---|
29 | |
---|
30 | /* |
---|
31 | Design Notes |
---|
32 | ------------ |
---|
33 | |
---|
34 | The code in this file allows svnserve to run as a Windows service. |
---|
35 | Windows Services are only supported on operating systems derived |
---|
36 | from Windows NT, which is basically all modern versions of Windows |
---|
37 | (2000, XP, Server, Vista, etc.) and excludes the Windows 9x line. |
---|
38 | |
---|
39 | Windows Services are processes that are started and controlled by |
---|
40 | the Service Control Manager. When the SCM wants to start a service, |
---|
41 | it creates the process, then waits for the process to connect to |
---|
42 | the SCM, so that the SCM and service process can communicate. |
---|
43 | This is done using the StartServiceCtrlDispatcher function. |
---|
44 | |
---|
45 | In order to minimize changes to the svnserve startup logic, this |
---|
46 | implementation differs slightly from most service implementations. |
---|
47 | In most services, main() immediately calls StartServiceCtrlDispatcher, |
---|
48 | which does not return control to main() until the SCM sends the |
---|
49 | "stop" request to the service, and the service stops. |
---|
50 | |
---|
51 | |
---|
52 | Installing the Service |
---|
53 | ---------------------- |
---|
54 | |
---|
55 | Installation is beyond the scope of source code comments. There |
---|
56 | is a separate document that describes how to install and uninstall |
---|
57 | the service. Basically, you create a Windows Service, give it a |
---|
58 | binary path that points to svnserve.exe, and make sure that you |
---|
59 | specify --service on the command line. |
---|
60 | |
---|
61 | |
---|
62 | Starting the Service |
---|
63 | -------------------- |
---|
64 | |
---|
65 | First, the SCM decides that it wants to start a service. It creates |
---|
66 | the process for the service, passing it the command-line that is |
---|
67 | stored in the service configuration (stored in the registry). |
---|
68 | |
---|
69 | Next, main() runs. The command-line should contain the --service |
---|
70 | argument, which is the hint that svnserve is running under the SCM, |
---|
71 | not as a standalone process. main() calls winservice_start(). |
---|
72 | |
---|
73 | winservice_start() creates an event object (winservice_start_event), |
---|
74 | and creates and starts a separate thread, the "dispatcher" thread. |
---|
75 | winservice_start() then waits for either winservice_start_event |
---|
76 | to fire (meaning: "the dispatcher thread successfully connected |
---|
77 | to the SCM, and now the service is starting") or for the dispatcher |
---|
78 | thread to exit (meaning: "failed to connect to SCM"). |
---|
79 | |
---|
80 | If the dispatcher thread quit, then winservice_start returns an error. |
---|
81 | If the start event fired, then winservice_start returns a success code |
---|
82 | (SVN_NO_ERROR). At this point, the service is now in the "starting" |
---|
83 | state, from the perspective of the SCM. winservice_start also registers |
---|
84 | an atexit handler, which handles cleaning up some of the service logic, |
---|
85 | as explained below in "Stopping the Service". |
---|
86 | |
---|
87 | Next, control returns to main(), which performs the usual startup |
---|
88 | logic for svnserve. Mostly, it creates the listener socket. If |
---|
89 | main() was able to start the service, then it calls the function |
---|
90 | winservice_running(). |
---|
91 | |
---|
92 | winservice_running() informs the SCM that the service has finished |
---|
93 | starting, and is now in the "running" state. main() then does its |
---|
94 | work, accepting client sockets and processing SVN requests. |
---|
95 | |
---|
96 | Stopping the Service |
---|
97 | -------------------- |
---|
98 | |
---|
99 | At some point, the SCM will decide to stop the service, either because |
---|
100 | an administrator chose to stop the service, or the system is shutting |
---|
101 | down. To do this, the SCM calls winservice_handler() with the |
---|
102 | SERVICE_CONTROL_STOP control code. When this happens, |
---|
103 | winservice_handler() will inform the SCM that the service is now |
---|
104 | in the "stopping" state, and will call winservice_notify_stop(). |
---|
105 | |
---|
106 | winservice_notify_stop() is responsible for cleanly shutting down the |
---|
107 | svnserve logic (waiting for client requests to finish, stopping database |
---|
108 | access, etc.). Right now, all it does is close the listener socket, |
---|
109 | which causes the apr_socket_accept() call in main() to fail. main() |
---|
110 | then calls exit(), which processes all atexit() handlers, which |
---|
111 | results in winservice_stop() being called. |
---|
112 | |
---|
113 | winservice_stop() notifies the SCM that the service is now stopped, |
---|
114 | and then waits for the dispatcher thread to exit. Because all services |
---|
115 | in the process have now stopped, the call to StartServiceCtrlDispatcher |
---|
116 | (in the dispatcher thread) finally returns, and winservice_stop() returns, |
---|
117 | and the process finally exits. |
---|
118 | */ |
---|
119 | |
---|
120 | |
---|
121 | #ifdef WIN32 |
---|
122 | |
---|
123 | #include <assert.h> |
---|
124 | #include <winsvc.h> |
---|
125 | |
---|
126 | /* This is just a placeholder, and doesn't actually constrain the |
---|
127 | service name. You have to provide *some* service name to the SCM |
---|
128 | API, but for services that are marked SERVICE_WIN32_OWN_PROCESS (as |
---|
129 | is the case for svnserve), the service name is ignored. It *is* |
---|
130 | relevant for service binaries that run more than one service in a |
---|
131 | single process. */ |
---|
132 | #define WINSERVICE_SERVICE_NAME "svnserve" |
---|
133 | |
---|
134 | |
---|
135 | /* Win32 handle to the dispatcher thread. */ |
---|
136 | static HANDLE winservice_dispatcher_thread = NULL; |
---|
137 | |
---|
138 | /* Win32 event handle, used to notify winservice_start() that we have |
---|
139 | successfully connected to the SCM. */ |
---|
140 | static HANDLE winservice_start_event = NULL; |
---|
141 | |
---|
142 | /* RPC handle that allows us to notify the SCM of changes in our |
---|
143 | service status. */ |
---|
144 | static SERVICE_STATUS_HANDLE winservice_status_handle = NULL; |
---|
145 | |
---|
146 | /* Our current idea of the service status (stopped, running, controls |
---|
147 | accepted, exit code, etc.) */ |
---|
148 | static SERVICE_STATUS winservice_status; |
---|
149 | |
---|
150 | |
---|
151 | #ifdef SVN_DEBUG |
---|
152 | static void dbg_print(const char* text) |
---|
153 | { |
---|
154 | OutputDebugStringA(text); |
---|
155 | } |
---|
156 | #else |
---|
157 | /* Make sure dbg_print compiles to nothing in release builds. */ |
---|
158 | #define dbg_print(text) |
---|
159 | #endif |
---|
160 | |
---|
161 | |
---|
162 | static void winservice_atexit(void); |
---|
163 | |
---|
164 | /* Notifies the Service Control Manager of the current state of the |
---|
165 | service. */ |
---|
166 | static void |
---|
167 | winservice_update_state(void) |
---|
168 | { |
---|
169 | if (winservice_status_handle != NULL) |
---|
170 | { |
---|
171 | if (!SetServiceStatus(winservice_status_handle, &winservice_status)) |
---|
172 | { |
---|
173 | dbg_print("SetServiceStatus - FAILED\r\n"); |
---|
174 | } |
---|
175 | } |
---|
176 | } |
---|
177 | |
---|
178 | |
---|
179 | /* This function cleans up state associated with the service support. |
---|
180 | If the dispatcher thread handle is non-NULL, then this function |
---|
181 | will wait for the dispatcher thread to exit. */ |
---|
182 | static void |
---|
183 | winservice_cleanup(void) |
---|
184 | { |
---|
185 | if (winservice_start_event != NULL) |
---|
186 | { |
---|
187 | CloseHandle(winservice_start_event); |
---|
188 | winservice_start_event = NULL; |
---|
189 | } |
---|
190 | |
---|
191 | if (winservice_dispatcher_thread != NULL) |
---|
192 | { |
---|
193 | dbg_print("winservice_cleanup:" |
---|
194 | " waiting for dispatcher thread to exit\r\n"); |
---|
195 | WaitForSingleObject(winservice_dispatcher_thread, INFINITE); |
---|
196 | CloseHandle(winservice_dispatcher_thread); |
---|
197 | winservice_dispatcher_thread = NULL; |
---|
198 | } |
---|
199 | } |
---|
200 | |
---|
201 | |
---|
202 | /* The SCM invokes this function to cause state changes in the |
---|
203 | service. */ |
---|
204 | static void WINAPI |
---|
205 | winservice_handler(DWORD control) |
---|
206 | { |
---|
207 | switch (control) |
---|
208 | { |
---|
209 | case SERVICE_CONTROL_INTERROGATE: |
---|
210 | /* The SCM just wants to check our state. We are required to |
---|
211 | call SetServiceStatus, but we don't need to make any state |
---|
212 | changes. */ |
---|
213 | dbg_print("SERVICE_CONTROL_INTERROGATE\r\n"); |
---|
214 | winservice_update_state(); |
---|
215 | break; |
---|
216 | |
---|
217 | case SERVICE_CONTROL_STOP: |
---|
218 | dbg_print("SERVICE_CONTROL_STOP\r\n"); |
---|
219 | winservice_status.dwCurrentState = SERVICE_STOP_PENDING; |
---|
220 | winservice_update_state(); |
---|
221 | winservice_notify_stop(); |
---|
222 | break; |
---|
223 | } |
---|
224 | } |
---|
225 | |
---|
226 | |
---|
227 | /* This is the "service main" routine (in the Win32 terminology). |
---|
228 | |
---|
229 | Normally, this function (thread) implements the "main" loop of a |
---|
230 | service. However, in order to minimize changes to the svnserve |
---|
231 | main() function, this function is running in a different thread, |
---|
232 | and main() is blocked in winservice_start(), waiting for |
---|
233 | winservice_start_event. So this function (thread) only needs to |
---|
234 | signal that event to "start" the service. |
---|
235 | |
---|
236 | If this function succeeds, it signals winservice_start_event, which |
---|
237 | wakes up the winservice_start() frame that is blocked. */ |
---|
238 | static void WINAPI |
---|
239 | winservice_service_main(DWORD argc, LPTSTR *argv) |
---|
240 | { |
---|
241 | DWORD error; |
---|
242 | |
---|
243 | assert(winservice_start_event != NULL); |
---|
244 | |
---|
245 | winservice_status_handle = |
---|
246 | RegisterServiceCtrlHandler(WINSERVICE_SERVICE_NAME, winservice_handler); |
---|
247 | if (winservice_status_handle == NULL) |
---|
248 | { |
---|
249 | /* Ok, that's not fair. We received a request to start a service, |
---|
250 | and now we cannot bind to the SCM in order to update status? |
---|
251 | Bring down the app. */ |
---|
252 | error = GetLastError(); |
---|
253 | dbg_print("RegisterServiceCtrlHandler FAILED\r\n"); |
---|
254 | /* Put the error code somewhere where winservice_start can find it. */ |
---|
255 | winservice_status.dwWin32ExitCode = error; |
---|
256 | SetEvent(winservice_start_event); |
---|
257 | return; |
---|
258 | } |
---|
259 | |
---|
260 | winservice_status.dwCurrentState = SERVICE_START_PENDING; |
---|
261 | winservice_status.dwWin32ExitCode = ERROR_SUCCESS; |
---|
262 | winservice_update_state(); |
---|
263 | |
---|
264 | dbg_print("winservice_service_main: service is starting\r\n"); |
---|
265 | SetEvent(winservice_start_event); |
---|
266 | } |
---|
267 | |
---|
268 | |
---|
269 | static const SERVICE_TABLE_ENTRY winservice_service_table[] = |
---|
270 | { |
---|
271 | { WINSERVICE_SERVICE_NAME, winservice_service_main }, |
---|
272 | { NULL, NULL } |
---|
273 | }; |
---|
274 | |
---|
275 | |
---|
276 | /* This is the thread routine for the "dispatcher" thread. The |
---|
277 | purpose of this thread is to connect this process with the Service |
---|
278 | Control Manager, which allows this process to receive control |
---|
279 | requests from the SCM, and allows this process to update the SCM |
---|
280 | with status information. |
---|
281 | |
---|
282 | The StartServiceCtrlDispatcher connects this process to the SCM. |
---|
283 | If it succeeds, then it will not return until all of the services |
---|
284 | running in this process have stopped. (In our case, there is only |
---|
285 | one service per process.) */ |
---|
286 | static DWORD WINAPI |
---|
287 | winservice_dispatcher_thread_routine(PVOID arg) |
---|
288 | { |
---|
289 | dbg_print("winservice_dispatcher_thread_routine: starting\r\n"); |
---|
290 | |
---|
291 | if (!StartServiceCtrlDispatcher(winservice_service_table)) |
---|
292 | { |
---|
293 | /* This is a common error. Usually, it means the user has |
---|
294 | invoked the service with the --service flag directly. This |
---|
295 | is incorrect. The only time the --service flag is passed is |
---|
296 | when the process is being started by the SCM. */ |
---|
297 | DWORD error = GetLastError(); |
---|
298 | |
---|
299 | dbg_print("dispatcher: FAILED to connect to SCM\r\n"); |
---|
300 | return error; |
---|
301 | } |
---|
302 | |
---|
303 | dbg_print("dispatcher: SCM is done using this process -- exiting\r\n"); |
---|
304 | return ERROR_SUCCESS; |
---|
305 | } |
---|
306 | |
---|
307 | |
---|
308 | /* If svnserve needs to run as a Win32 service, then we need to |
---|
309 | coordinate with the Service Control Manager (SCM) before |
---|
310 | continuing. This function call registers the svnserve.exe process |
---|
311 | with the SCM, waits for the "start" command from the SCM (which |
---|
312 | will come very quickly), and confirms that those steps succeeded. |
---|
313 | |
---|
314 | After this call succeeds, the service should perform whatever work |
---|
315 | it needs to start the service, and then the service should call |
---|
316 | winservice_running() (if no errors occurred) or winservice_stop() |
---|
317 | (if something failed during startup). */ |
---|
318 | svn_error_t * |
---|
319 | winservice_start(void) |
---|
320 | { |
---|
321 | HANDLE handles[2]; |
---|
322 | DWORD thread_id; |
---|
323 | DWORD error_code; |
---|
324 | apr_status_t apr_status; |
---|
325 | DWORD wait_status; |
---|
326 | |
---|
327 | dbg_print("winservice_start: starting svnserve as a service...\r\n"); |
---|
328 | |
---|
329 | ZeroMemory(&winservice_status, sizeof(winservice_status)); |
---|
330 | winservice_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; |
---|
331 | winservice_status.dwControlsAccepted = SERVICE_ACCEPT_STOP; |
---|
332 | winservice_status.dwCurrentState = SERVICE_STOPPED; |
---|
333 | |
---|
334 | /* Create the event that will wake up this thread when the SCM |
---|
335 | creates the ServiceMain thread. */ |
---|
336 | winservice_start_event = CreateEvent(NULL, FALSE, FALSE, NULL); |
---|
337 | if (winservice_start_event == NULL) |
---|
338 | { |
---|
339 | apr_status = apr_get_os_error(); |
---|
340 | return svn_error_wrap_apr(apr_status, |
---|
341 | _("Failed to create winservice_start_event")); |
---|
342 | } |
---|
343 | |
---|
344 | winservice_dispatcher_thread = |
---|
345 | (HANDLE)_beginthreadex(NULL, 0, winservice_dispatcher_thread_routine, |
---|
346 | NULL, 0, &thread_id); |
---|
347 | if (winservice_dispatcher_thread == NULL) |
---|
348 | { |
---|
349 | apr_status = apr_get_os_error(); |
---|
350 | winservice_cleanup(); |
---|
351 | return svn_error_wrap_apr(apr_status, |
---|
352 | _("The service failed to start")); |
---|
353 | } |
---|
354 | |
---|
355 | /* Next, we wait for the "start" event to fire (meaning the service |
---|
356 | logic has successfully started), or for the dispatch thread to |
---|
357 | exit (meaning the service logic could not start). */ |
---|
358 | |
---|
359 | handles[0] = winservice_start_event; |
---|
360 | handles[1] = winservice_dispatcher_thread; |
---|
361 | wait_status = WaitForMultipleObjects(2, handles, FALSE, INFINITE); |
---|
362 | switch (wait_status) |
---|
363 | { |
---|
364 | case WAIT_OBJECT_0: |
---|
365 | dbg_print("winservice_start: service is now starting\r\n"); |
---|
366 | |
---|
367 | /* We no longer need the start event. */ |
---|
368 | CloseHandle(winservice_start_event); |
---|
369 | winservice_start_event = NULL; |
---|
370 | |
---|
371 | /* Register our cleanup logic. */ |
---|
372 | atexit(winservice_atexit); |
---|
373 | return SVN_NO_ERROR; |
---|
374 | |
---|
375 | case WAIT_OBJECT_0+1: |
---|
376 | /* The dispatcher thread exited without starting the service. |
---|
377 | This happens when the dispatcher fails to connect to the SCM. */ |
---|
378 | dbg_print("winservice_start: dispatcher thread has failed\r\n"); |
---|
379 | |
---|
380 | if (GetExitCodeThread(winservice_dispatcher_thread, &error_code)) |
---|
381 | { |
---|
382 | dbg_print("winservice_start: dispatcher thread failed\r\n"); |
---|
383 | |
---|
384 | if (error_code == ERROR_SUCCESS) |
---|
385 | error_code = ERROR_INTERNAL_ERROR; |
---|
386 | |
---|
387 | } |
---|
388 | else |
---|
389 | { |
---|
390 | error_code = ERROR_INTERNAL_ERROR; |
---|
391 | } |
---|
392 | |
---|
393 | CloseHandle(winservice_dispatcher_thread); |
---|
394 | winservice_dispatcher_thread = NULL; |
---|
395 | |
---|
396 | winservice_cleanup(); |
---|
397 | |
---|
398 | return svn_error_wrap_apr |
---|
399 | (APR_FROM_OS_ERROR(error_code), |
---|
400 | _("Failed to connect to Service Control Manager")); |
---|
401 | |
---|
402 | default: |
---|
403 | /* This should never happen! This indicates that our handles are |
---|
404 | broken, or some other highly unusual error. There is nothing |
---|
405 | rational that we can do to recover. */ |
---|
406 | apr_status = apr_get_os_error(); |
---|
407 | dbg_print("winservice_start: WaitForMultipleObjects failed!\r\n"); |
---|
408 | |
---|
409 | winservice_cleanup(); |
---|
410 | return svn_error_wrap_apr |
---|
411 | (apr_status, _("The service failed to start; an internal error" |
---|
412 | " occurred while starting the service")); |
---|
413 | } |
---|
414 | } |
---|
415 | |
---|
416 | |
---|
417 | /* main() calls this function in order to inform the SCM that the |
---|
418 | service has successfully started. This is required; otherwise, the |
---|
419 | SCM will believe that the service is stuck in the "starting" state, |
---|
420 | and management tools will also believe that the service is stuck. */ |
---|
421 | void |
---|
422 | winservice_running(void) |
---|
423 | { |
---|
424 | winservice_status.dwCurrentState = SERVICE_RUNNING; |
---|
425 | winservice_update_state(); |
---|
426 | dbg_print("winservice_notify_running: service is now running\r\n"); |
---|
427 | } |
---|
428 | |
---|
429 | |
---|
430 | /* main() calls this function in order to notify the SCM that the |
---|
431 | service has stopped. This function also handles cleaning up the |
---|
432 | dispatcher thread (the one that we created above in |
---|
433 | winservice_start. */ |
---|
434 | static void |
---|
435 | winservice_stop(DWORD exit_code) |
---|
436 | { |
---|
437 | dbg_print("winservice_stop - notifying SCM that service has stopped\r\n"); |
---|
438 | winservice_status.dwCurrentState = SERVICE_STOPPED; |
---|
439 | winservice_status.dwWin32ExitCode = exit_code; |
---|
440 | winservice_update_state(); |
---|
441 | |
---|
442 | if (winservice_dispatcher_thread != NULL) |
---|
443 | { |
---|
444 | dbg_print("waiting for dispatcher thread to exit...\r\n"); |
---|
445 | WaitForSingleObject(winservice_dispatcher_thread, INFINITE); |
---|
446 | dbg_print("dispatcher thread has exited.\r\n"); |
---|
447 | |
---|
448 | CloseHandle(winservice_dispatcher_thread); |
---|
449 | winservice_dispatcher_thread = NULL; |
---|
450 | } |
---|
451 | else |
---|
452 | { |
---|
453 | /* There was no dispatcher thread. So we never started in |
---|
454 | the first place. */ |
---|
455 | exit_code = winservice_status.dwWin32ExitCode; |
---|
456 | dbg_print("dispatcher thread was not running\r\n"); |
---|
457 | } |
---|
458 | |
---|
459 | if (winservice_start_event != NULL) |
---|
460 | { |
---|
461 | CloseHandle(winservice_start_event); |
---|
462 | winservice_start_event = NULL; |
---|
463 | } |
---|
464 | |
---|
465 | dbg_print("winservice_stop - service has stopped\r\n"); |
---|
466 | } |
---|
467 | |
---|
468 | |
---|
469 | /* This function is installed as an atexit-handler. This is done so |
---|
470 | that we don't need to alter every exit() call in main(). */ |
---|
471 | static void |
---|
472 | winservice_atexit(void) |
---|
473 | { |
---|
474 | dbg_print("winservice_atexit - stopping\r\n"); |
---|
475 | winservice_stop(ERROR_SUCCESS); |
---|
476 | } |
---|
477 | |
---|
478 | |
---|
479 | svn_boolean_t |
---|
480 | winservice_is_stopping(void) |
---|
481 | { |
---|
482 | return (winservice_status.dwCurrentState == SERVICE_STOP_PENDING); |
---|
483 | } |
---|
484 | |
---|
485 | #endif /* WIN32 */ |
---|