Followup to r29625: fix getopt tests.
[svn.git] / subversion / svnserve / winservice.c
blobe482a71eb104cbe7f03c030c3be0b1cd12c149ad
1 /*
2 * winservice.c : Implementation of Windows Service support
4 * ====================================================================
5 * Copyright (c) 2000-2006 CollabNet. All rights reserved.
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.
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 * ====================================================================
21 #define APR_WANT_STRFUNC
22 #include <apr_want.h>
23 #include <apr_errno.h>
25 #include "svn_error.h"
27 #include "svn_private_config.h"
28 #include "winservice.h"
31 Design Notes
32 ------------
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.
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.
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.
52 Installing the Service
53 ----------------------
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.
62 Starting the Service
63 --------------------
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).
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().
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").
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".
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().
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.
96 Stopping the Service
97 --------------------
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().
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.
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.
121 #ifdef WIN32
123 #include <assert.h>
124 #include <winsvc.h>
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"
135 /* Win32 handle to the dispatcher thread. */
136 static HANDLE winservice_dispatcher_thread = NULL;
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;
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;
146 /* Our current idea of the service status (stopped, running, controls
147 accepted, exit code, etc.) */
148 static SERVICE_STATUS winservice_status;
151 #ifdef SVN_DEBUG
152 static void dbg_print(const char* text)
154 OutputDebugStringA(text);
156 #else
157 /* Make sure dbg_print compiles to nothing in release builds. */
158 #define dbg_print(text)
159 #endif
162 static void winservice_atexit(void);
164 /* Notifies the Service Control Manager of the current state of the
165 service. */
166 static void
167 winservice_update_state(void)
169 if (winservice_status_handle != NULL)
171 if (!SetServiceStatus(winservice_status_handle, &winservice_status))
173 dbg_print("SetServiceStatus - FAILED\r\n");
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)
185 if (winservice_start_event != NULL)
187 CloseHandle(winservice_start_event);
188 winservice_start_event = NULL;
191 if (winservice_dispatcher_thread != NULL)
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;
202 /* The SCM invokes this function to cause state changes in the
203 service. */
204 static void WINAPI
205 winservice_handler(DWORD control)
207 switch (control)
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;
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;
227 /* This is the "service main" routine (in the Win32 terminology).
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.
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)
241 DWORD error;
243 assert(winservice_start_event != NULL);
245 winservice_status_handle =
246 RegisterServiceCtrlHandler(WINSERVICE_SERVICE_NAME, winservice_handler);
247 if (winservice_status_handle == NULL)
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;
260 winservice_status.dwCurrentState = SERVICE_START_PENDING;
261 winservice_status.dwWin32ExitCode = ERROR_SUCCESS;
262 winservice_update_state();
264 dbg_print("winservice_service_main: service is starting\r\n");
265 SetEvent(winservice_start_event);
269 static const SERVICE_TABLE_ENTRY winservice_service_table[] =
271 { WINSERVICE_SERVICE_NAME, winservice_service_main },
272 { NULL, NULL }
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.
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)
289 dbg_print("winservice_dispatcher_thread_routine: starting\r\n");
291 if (!StartServiceCtrlDispatcher(winservice_service_table))
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();
299 dbg_print("dispatcher: FAILED to connect to SCM\r\n");
300 return error;
303 dbg_print("dispatcher: SCM is done using this process -- exiting\r\n");
304 return ERROR_SUCCESS;
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.
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)
321 HANDLE handles[2];
322 DWORD thread_id;
323 DWORD error_code;
324 apr_status_t apr_status;
325 DWORD wait_status;
327 dbg_print("winservice_start: starting svnserve as a service...\r\n");
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;
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)
339 apr_status = apr_get_os_error();
340 return svn_error_wrap_apr(apr_status,
341 _("Failed to create winservice_start_event"));
344 winservice_dispatcher_thread =
345 (HANDLE)_beginthreadex(NULL, 0, winservice_dispatcher_thread_routine,
346 NULL, 0, &thread_id);
347 if (winservice_dispatcher_thread == NULL)
349 apr_status = apr_get_os_error();
350 winservice_cleanup();
351 return svn_error_wrap_apr(apr_status,
352 _("The service failed to start"));
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). */
359 handles[0] = winservice_start_event;
360 handles[1] = winservice_dispatcher_thread;
361 wait_status = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
362 switch (wait_status)
364 case WAIT_OBJECT_0:
365 dbg_print("winservice_start: service is now starting\r\n");
367 /* We no longer need the start event. */
368 CloseHandle(winservice_start_event);
369 winservice_start_event = NULL;
371 /* Register our cleanup logic. */
372 atexit(winservice_atexit);
373 return SVN_NO_ERROR;
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");
380 if (GetExitCodeThread(winservice_dispatcher_thread, &error_code))
382 dbg_print("winservice_start: dispatcher thread failed\r\n");
384 if (error_code == ERROR_SUCCESS)
385 error_code = ERROR_INTERNAL_ERROR;
388 else
390 error_code = ERROR_INTERNAL_ERROR;
393 CloseHandle(winservice_dispatcher_thread);
394 winservice_dispatcher_thread = NULL;
396 winservice_cleanup();
398 return svn_error_wrap_apr
399 (APR_FROM_OS_ERROR(error_code),
400 _("Failed to connect to Service Control Manager"));
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");
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"));
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)
424 winservice_status.dwCurrentState = SERVICE_RUNNING;
425 winservice_update_state();
426 dbg_print("winservice_notify_running: service is now running\r\n");
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)
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();
442 if (winservice_dispatcher_thread != NULL)
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");
448 CloseHandle(winservice_dispatcher_thread);
449 winservice_dispatcher_thread = NULL;
451 else
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");
459 if (winservice_start_event != NULL)
461 CloseHandle(winservice_start_event);
462 winservice_start_event = NULL;
465 dbg_print("winservice_stop - service has stopped\r\n");
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)
474 dbg_print("winservice_atexit - stopping\r\n");
475 winservice_stop(ERROR_SUCCESS);
479 svn_boolean_t
480 winservice_is_stopping(void)
482 return (winservice_status.dwCurrentState == SERVICE_STOP_PENDING);
485 #endif /* WIN32 */