source: valtobtest/subversion-1.6.2/subversion/svnserve/winservice.c @ 3

Last change on this file since 3 was 3, checked in by valtob, 15 years ago

subversion source 1.6.2 as test

File size: 16.5 KB
Line 
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/*
31Design Notes
32------------
33
34The code in this file allows svnserve to run as a Windows service.
35Windows Services are only supported on operating systems derived
36from Windows NT, which is basically all modern versions of Windows
37(2000, XP, Server, Vista, etc.) and excludes the Windows 9x line.
38
39Windows Services are processes that are started and controlled by
40the Service Control Manager.  When the SCM wants to start a service,
41it creates the process, then waits for the process to connect to
42the SCM, so that the SCM and service process can communicate.
43This is done using the StartServiceCtrlDispatcher function.
44
45In order to minimize changes to the svnserve startup logic, this
46implementation differs slightly from most service implementations.
47In most services, main() immediately calls StartServiceCtrlDispatcher,
48which does not return control to main() until the SCM sends the
49"stop" request to the service, and the service stops.
50
51
52Installing the Service
53----------------------
54
55Installation is beyond the scope of source code comments.  There
56is a separate document that describes how to install and uninstall
57the service.  Basically, you create a Windows Service, give it a
58binary path that points to svnserve.exe, and make sure that you
59specify --service on the command line.
60
61
62Starting the Service
63--------------------
64
65First, the SCM decides that it wants to start a service.  It creates
66the process for the service, passing it the command-line that is
67stored in the service configuration (stored in the registry).
68
69Next, main() runs.  The command-line should contain the --service
70argument, which is the hint that svnserve is running under the SCM,
71not as a standalone process.  main() calls winservice_start().
72
73winservice_start() creates an event object (winservice_start_event),
74and creates and starts a separate thread, the "dispatcher" thread.
75winservice_start() then waits for either winservice_start_event
76to fire (meaning: "the dispatcher thread successfully connected
77to the SCM, and now the service is starting") or for the dispatcher
78thread to exit (meaning: "failed to connect to SCM").
79
80If the dispatcher thread quit, then winservice_start returns an error.
81If 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"
83state, from the perspective of the SCM.  winservice_start also registers
84an atexit handler, which handles cleaning up some of the service logic,
85as explained below in "Stopping the Service".
86
87Next, control returns to main(), which performs the usual startup
88logic for svnserve.  Mostly, it creates the listener socket.  If
89main() was able to start the service, then it calls the function
90winservice_running().
91
92winservice_running() informs the SCM that the service has finished
93starting, and is now in the "running" state.  main() then does its
94work, accepting client sockets and processing SVN requests.
95
96Stopping the Service
97--------------------
98
99At some point, the SCM will decide to stop the service, either because
100an administrator chose to stop the service, or the system is shutting
101down.  To do this, the SCM calls winservice_handler() with the
102SERVICE_CONTROL_STOP control code.  When this happens,
103winservice_handler() will inform the SCM that the service is now
104in the "stopping" state, and will call winservice_notify_stop().
105
106winservice_notify_stop() is responsible for cleanly shutting down the
107svnserve logic (waiting for client requests to finish, stopping database
108access, etc.).  Right now, all it does is close the listener socket,
109which causes the apr_socket_accept() call in main() to fail.  main()
110then calls exit(), which processes all atexit() handlers, which
111results in winservice_stop() being called.
112
113winservice_stop() notifies the SCM that the service is now stopped,
114and then waits for the dispatcher thread to exit.  Because all services
115in the process have now stopped, the call to StartServiceCtrlDispatcher
116(in the dispatcher thread) finally returns, and winservice_stop() returns,
117and 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. */
136static HANDLE winservice_dispatcher_thread = NULL;
137
138/* Win32 event handle, used to notify winservice_start() that we have
139   successfully connected to the SCM. */
140static HANDLE winservice_start_event = NULL;
141
142/* RPC handle that allows us to notify the SCM of changes in our
143   service status. */
144static SERVICE_STATUS_HANDLE winservice_status_handle = NULL;
145
146/* Our current idea of the service status (stopped, running, controls
147   accepted, exit code, etc.) */
148static SERVICE_STATUS winservice_status;
149
150
151#ifdef SVN_DEBUG
152static 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
162static void winservice_atexit(void);
163
164/* Notifies the Service Control Manager of the current state of the
165   service. */
166static void
167winservice_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. */
182static void
183winservice_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. */
204static void WINAPI
205winservice_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. */
238static void WINAPI
239winservice_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
269static 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.) */
286static DWORD WINAPI
287winservice_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). */
318svn_error_t *
319winservice_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. */
421void
422winservice_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. */
434static void
435winservice_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(). */
471static void
472winservice_atexit(void)
473{
474  dbg_print("winservice_atexit - stopping\r\n");
475  winservice_stop(ERROR_SUCCESS);
476}
477
478
479svn_boolean_t
480winservice_is_stopping(void)
481{
482  return (winservice_status.dwCurrentState == SERVICE_STOP_PENDING);
483}
484
485#endif /* WIN32 */
Note: See TracBrowser for help on using the repository browser.