winservice.c revision 251881
1124758Semax/*
2124758Semax * winservice.c : Implementation of Windows Service support
3124758Semax *
4124758Semax * ====================================================================
5124758Semax *    Licensed to the Apache Software Foundation (ASF) under one
6124758Semax *    or more contributor license agreements.  See the NOTICE file
7124758Semax *    distributed with this work for additional information
8124758Semax *    regarding copyright ownership.  The ASF licenses this file
9124758Semax *    to you under the Apache License, Version 2.0 (the
10124758Semax *    "License"); you may not use this file except in compliance
11124758Semax *    with the License.  You may obtain a copy of the License at
12124758Semax *
13124758Semax *      http://www.apache.org/licenses/LICENSE-2.0
14124758Semax *
15124758Semax *    Unless required by applicable law or agreed to in writing,
16124758Semax *    software distributed under the License is distributed on an
17124758Semax *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18124758Semax *    KIND, either express or implied.  See the License for the
19124758Semax *    specific language governing permissions and limitations
20124758Semax *    under the License.
21124758Semax * ====================================================================
22124758Semax */
23124758Semax
24124758Semax
25124758Semax
26124758Semax#define APR_WANT_STRFUNC
27124758Semax#include <apr_want.h>
28124758Semax#include <apr_errno.h>
29124758Semax
30124758Semax#include "svn_error.h"
31124758Semax
32124758Semax#include "svn_private_config.h"
33281210Stakawata#include "winservice.h"
34124758Semax
35124758Semax/*
36124758SemaxDesign Notes
37124758Semax------------
38124758Semax
39124758SemaxThe code in this file allows svnserve to run as a Windows service.
40124758SemaxWindows Services are only supported on operating systems derived
41124758Semaxfrom Windows NT, which is basically all modern versions of Windows
42124758Semax(2000, XP, Server, Vista, etc.) and excludes the Windows 9x line.
43124758Semax
44124758SemaxWindows Services are processes that are started and controlled by
45124758Semaxthe Service Control Manager.  When the SCM wants to start a service,
46124758Semaxit creates the process, then waits for the process to connect to
47124758Semaxthe SCM, so that the SCM and service process can communicate.
48124758SemaxThis is done using the StartServiceCtrlDispatcher function.
49124758Semax
50124758SemaxIn order to minimize changes to the svnserve startup logic, this
51124758Semaximplementation differs slightly from most service implementations.
52124758SemaxIn most services, main() immediately calls StartServiceCtrlDispatcher,
53124758Semaxwhich does not return control to main() until the SCM sends the
54124758Semax"stop" request to the service, and the service stops.
55124758Semax
56124758Semax
57124758SemaxInstalling the Service
58124758Semax----------------------
59124758Semax
60124758SemaxInstallation is beyond the scope of source code comments.  There
61124758Semaxis a separate document that describes how to install and uninstall
62124758Semaxthe service.  Basically, you create a Windows Service, give it a
63124758Semaxbinary path that points to svnserve.exe, and make sure that you
64124758Semaxspecify --service on the command line.
65124758Semax
66124758Semax
67124758SemaxStarting the Service
68124758Semax--------------------
69124758Semax
70124758SemaxFirst, the SCM decides that it wants to start a service.  It creates
71124758Semaxthe process for the service, passing it the command-line that is
72124758Semaxstored in the service configuration (stored in the registry).
73124758Semax
74124758SemaxNext, main() runs.  The command-line should contain the --service
75124758Semaxargument, which is the hint that svnserve is running under the SCM,
76124758Semaxnot as a standalone process.  main() calls winservice_start().
77124758Semax
78124758Semaxwinservice_start() creates an event object (winservice_start_event),
79124758Semaxand creates and starts a separate thread, the "dispatcher" thread.
80124758Semaxwinservice_start() then waits for either winservice_start_event
81124758Semaxto fire (meaning: "the dispatcher thread successfully connected
82124758Semaxto the SCM, and now the service is starting") or for the dispatcher
83124758Semaxthread to exit (meaning: "failed to connect to SCM").
84124758Semax
85124758SemaxIf the dispatcher thread quit, then winservice_start returns an error.
86124758SemaxIf the start event fired, then winservice_start returns a success code
87124758Semax(SVN_NO_ERROR).  At this point, the service is now in the "starting"
88124758Semaxstate, from the perspective of the SCM.  winservice_start also registers
89124758Semaxan atexit handler, which handles cleaning up some of the service logic,
90124758Semaxas explained below in "Stopping the Service".
91124758Semax
92124758SemaxNext, control returns to main(), which performs the usual startup
93124758Semaxlogic for svnserve.  Mostly, it creates the listener socket.  If
94124758Semaxmain() was able to start the service, then it calls the function
95124758Semaxwinservice_running().
96124758Semax
97124758Semaxwinservice_running() informs the SCM that the service has finished
98124758Semaxstarting, and is now in the "running" state.  main() then does its
99124758Semaxwork, accepting client sockets and processing SVN requests.
100124758Semax
101124758SemaxStopping the Service
102124758Semax--------------------
103124758Semax
104124758SemaxAt some point, the SCM will decide to stop the service, either because
105124758Semaxan administrator chose to stop the service, or the system is shutting
106124758Semaxdown.  To do this, the SCM calls winservice_handler() with the
107124758SemaxSERVICE_CONTROL_STOP control code.  When this happens,
108124758Semaxwinservice_handler() will inform the SCM that the service is now
109124758Semaxin the "stopping" state, and will call winservice_notify_stop().
110124758Semax
111124758Semaxwinservice_notify_stop() is responsible for cleanly shutting down the
112124758Semaxsvnserve logic (waiting for client requests to finish, stopping database
113124758Semaxaccess, etc.).  Right now, all it does is close the listener socket,
114124758Semaxwhich causes the apr_socket_accept() call in main() to fail.  main()
115124758Semaxthen calls exit(), which processes all atexit() handlers, which
116124758Semaxresults in winservice_stop() being called.
117124758Semax
118124758Semaxwinservice_stop() notifies the SCM that the service is now stopped,
119124758Semaxand then waits for the dispatcher thread to exit.  Because all services
120124758Semaxin the process have now stopped, the call to StartServiceCtrlDispatcher
121124758Semax(in the dispatcher thread) finally returns, and winservice_stop() returns,
122124758Semaxand the process finally exits.
123124758Semax*/
124124758Semax
125124758Semax
126124758Semax#ifdef WIN32
127124758Semax
128124758Semax#include <assert.h>
129124758Semax#include <winsvc.h>
130124758Semax
131124758Semax/* This is just a placeholder, and doesn't actually constrain the
132124758Semax  service name.  You have to provide *some* service name to the SCM
133124758Semax  API, but for services that are marked SERVICE_WIN32_OWN_PROCESS (as
134124758Semax  is the case for svnserve), the service name is ignored.  It *is*
135  relevant for service binaries that run more than one service in a
136  single process. */
137#define WINSERVICE_SERVICE_NAME "svnserve"
138
139
140/* Win32 handle to the dispatcher thread. */
141static HANDLE winservice_dispatcher_thread = NULL;
142
143/* Win32 event handle, used to notify winservice_start() that we have
144   successfully connected to the SCM. */
145static HANDLE winservice_start_event = NULL;
146
147/* RPC handle that allows us to notify the SCM of changes in our
148   service status. */
149static SERVICE_STATUS_HANDLE winservice_status_handle = NULL;
150
151/* Our current idea of the service status (stopped, running, controls
152   accepted, exit code, etc.) */
153static SERVICE_STATUS winservice_status;
154
155
156#ifdef SVN_DEBUG
157static void dbg_print(const char* text)
158{
159  OutputDebugStringA(text);
160}
161#else
162/* Make sure dbg_print compiles to nothing in release builds. */
163#define dbg_print(text)
164#endif
165
166
167static void winservice_atexit(void);
168
169/* Notifies the Service Control Manager of the current state of the
170   service. */
171static void
172winservice_update_state(void)
173{
174  if (winservice_status_handle != NULL)
175    {
176      if (!SetServiceStatus(winservice_status_handle, &winservice_status))
177        {
178          dbg_print("SetServiceStatus - FAILED\r\n");
179        }
180    }
181}
182
183
184/* This function cleans up state associated with the service support.
185   If the dispatcher thread handle is non-NULL, then this function
186   will wait for the dispatcher thread to exit. */
187static void
188winservice_cleanup(void)
189{
190  if (winservice_start_event != NULL)
191    {
192      CloseHandle(winservice_start_event);
193      winservice_start_event = NULL;
194    }
195
196  if (winservice_dispatcher_thread != NULL)
197    {
198      dbg_print("winservice_cleanup:"
199                " waiting for dispatcher thread to exit\r\n");
200      WaitForSingleObject(winservice_dispatcher_thread, INFINITE);
201      CloseHandle(winservice_dispatcher_thread);
202      winservice_dispatcher_thread = NULL;
203    }
204}
205
206
207/* The SCM invokes this function to cause state changes in the
208   service. */
209static void WINAPI
210winservice_handler(DWORD control)
211{
212  switch (control)
213    {
214    case SERVICE_CONTROL_INTERROGATE:
215      /* The SCM just wants to check our state.  We are required to
216         call SetServiceStatus, but we don't need to make any state
217         changes. */
218      dbg_print("SERVICE_CONTROL_INTERROGATE\r\n");
219      winservice_update_state();
220      break;
221
222    case SERVICE_CONTROL_STOP:
223      dbg_print("SERVICE_CONTROL_STOP\r\n");
224      winservice_status.dwCurrentState = SERVICE_STOP_PENDING;
225      winservice_update_state();
226      winservice_notify_stop();
227      break;
228    }
229}
230
231
232/* This is the "service main" routine (in the Win32 terminology).
233
234   Normally, this function (thread) implements the "main" loop of a
235   service.  However, in order to minimize changes to the svnserve
236   main() function, this function is running in a different thread,
237   and main() is blocked in winservice_start(), waiting for
238   winservice_start_event.  So this function (thread) only needs to
239   signal that event to "start" the service.
240
241   If this function succeeds, it signals winservice_start_event, which
242   wakes up the winservice_start() frame that is blocked. */
243static void WINAPI
244winservice_service_main(DWORD argc, LPTSTR *argv)
245{
246  DWORD error;
247
248  assert(winservice_start_event != NULL);
249
250  winservice_status_handle =
251    RegisterServiceCtrlHandler(WINSERVICE_SERVICE_NAME, winservice_handler);
252  if (winservice_status_handle == NULL)
253    {
254      /* Ok, that's not fair.  We received a request to start a service,
255         and now we cannot bind to the SCM in order to update status?
256         Bring down the app. */
257      error = GetLastError();
258      dbg_print("RegisterServiceCtrlHandler FAILED\r\n");
259      /* Put the error code somewhere where winservice_start can find it. */
260      winservice_status.dwWin32ExitCode = error;
261      SetEvent(winservice_start_event);
262      return;
263    }
264
265  winservice_status.dwCurrentState = SERVICE_START_PENDING;
266  winservice_status.dwWin32ExitCode = ERROR_SUCCESS;
267  winservice_update_state();
268
269  dbg_print("winservice_service_main: service is starting\r\n");
270  SetEvent(winservice_start_event);
271}
272
273
274static const SERVICE_TABLE_ENTRY winservice_service_table[] =
275  {
276    { WINSERVICE_SERVICE_NAME, winservice_service_main },
277    { NULL, NULL }
278  };
279
280
281/* This is the thread routine for the "dispatcher" thread.  The
282   purpose of this thread is to connect this process with the Service
283   Control Manager, which allows this process to receive control
284   requests from the SCM, and allows this process to update the SCM
285   with status information.
286
287   The StartServiceCtrlDispatcher connects this process to the SCM.
288   If it succeeds, then it will not return until all of the services
289   running in this process have stopped.  (In our case, there is only
290   one service per process.) */
291static DWORD WINAPI
292winservice_dispatcher_thread_routine(PVOID arg)
293{
294  dbg_print("winservice_dispatcher_thread_routine: starting\r\n");
295
296  if (!StartServiceCtrlDispatcher(winservice_service_table))
297    {
298      /* This is a common error.  Usually, it means the user has
299         invoked the service with the --service flag directly.  This
300         is incorrect.  The only time the --service flag is passed is
301         when the process is being started by the SCM. */
302      DWORD error = GetLastError();
303
304      dbg_print("dispatcher: FAILED to connect to SCM\r\n");
305      return error;
306    }
307
308  dbg_print("dispatcher: SCM is done using this process -- exiting\r\n");
309  return ERROR_SUCCESS;
310}
311
312
313/* If svnserve needs to run as a Win32 service, then we need to
314   coordinate with the Service Control Manager (SCM) before
315   continuing.  This function call registers the svnserve.exe process
316   with the SCM, waits for the "start" command from the SCM (which
317   will come very quickly), and confirms that those steps succeeded.
318
319   After this call succeeds, the service should perform whatever work
320   it needs to start the service, and then the service should call
321   winservice_running() (if no errors occurred) or winservice_stop()
322   (if something failed during startup). */
323svn_error_t *
324winservice_start(void)
325{
326  HANDLE handles[2];
327  DWORD thread_id;
328  DWORD error_code;
329  apr_status_t apr_status;
330  DWORD wait_status;
331
332  dbg_print("winservice_start: starting svnserve as a service...\r\n");
333
334  ZeroMemory(&winservice_status, sizeof(winservice_status));
335  winservice_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
336  winservice_status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
337  winservice_status.dwCurrentState = SERVICE_STOPPED;
338
339  /* Create the event that will wake up this thread when the SCM
340     creates the ServiceMain thread. */
341  winservice_start_event = CreateEvent(NULL, FALSE, FALSE, NULL);
342  if (winservice_start_event == NULL)
343    {
344      apr_status = apr_get_os_error();
345      return svn_error_wrap_apr(apr_status,
346                                _("Failed to create winservice_start_event"));
347    }
348
349  winservice_dispatcher_thread =
350    (HANDLE)CreateThread(NULL, 0, winservice_dispatcher_thread_routine,
351                         NULL, 0, &thread_id);
352  if (winservice_dispatcher_thread == NULL)
353    {
354      apr_status = apr_get_os_error();
355      winservice_cleanup();
356      return svn_error_wrap_apr(apr_status,
357                                _("The service failed to start"));
358    }
359
360  /* Next, we wait for the "start" event to fire (meaning the service
361     logic has successfully started), or for the dispatch thread to
362     exit (meaning the service logic could not start). */
363
364  handles[0] = winservice_start_event;
365  handles[1] = winservice_dispatcher_thread;
366  wait_status = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
367  switch (wait_status)
368    {
369    case WAIT_OBJECT_0:
370      dbg_print("winservice_start: service is now starting\r\n");
371
372      /* We no longer need the start event. */
373      CloseHandle(winservice_start_event);
374      winservice_start_event = NULL;
375
376      /* Register our cleanup logic. */
377      atexit(winservice_atexit);
378      return SVN_NO_ERROR;
379
380    case WAIT_OBJECT_0+1:
381      /* The dispatcher thread exited without starting the service.
382         This happens when the dispatcher fails to connect to the SCM. */
383      dbg_print("winservice_start: dispatcher thread has failed\r\n");
384
385      if (GetExitCodeThread(winservice_dispatcher_thread, &error_code))
386        {
387          dbg_print("winservice_start: dispatcher thread failed\r\n");
388
389          if (error_code == ERROR_SUCCESS)
390            error_code = ERROR_INTERNAL_ERROR;
391
392        }
393      else
394        {
395          error_code = ERROR_INTERNAL_ERROR;
396        }
397
398      CloseHandle(winservice_dispatcher_thread);
399      winservice_dispatcher_thread = NULL;
400
401      winservice_cleanup();
402
403      return svn_error_wrap_apr
404        (APR_FROM_OS_ERROR(error_code),
405         _("Failed to connect to Service Control Manager"));
406
407    default:
408      /* This should never happen! This indicates that our handles are
409         broken, or some other highly unusual error.  There is nothing
410         rational that we can do to recover. */
411      apr_status = apr_get_os_error();
412      dbg_print("winservice_start: WaitForMultipleObjects failed!\r\n");
413
414      winservice_cleanup();
415      return svn_error_wrap_apr
416        (apr_status, _("The service failed to start; an internal error"
417                       " occurred while starting the service"));
418    }
419}
420
421
422/* main() calls this function in order to inform the SCM that the
423   service has successfully started.  This is required; otherwise, the
424   SCM will believe that the service is stuck in the "starting" state,
425   and management tools will also believe that the service is stuck. */
426void
427winservice_running(void)
428{
429  winservice_status.dwCurrentState = SERVICE_RUNNING;
430  winservice_update_state();
431  dbg_print("winservice_notify_running: service is now running\r\n");
432}
433
434
435/* main() calls this function in order to notify the SCM that the
436   service has stopped.  This function also handles cleaning up the
437   dispatcher thread (the one that we created above in
438   winservice_start. */
439static void
440winservice_stop(DWORD exit_code)
441{
442  dbg_print("winservice_stop - notifying SCM that service has stopped\r\n");
443  winservice_status.dwCurrentState = SERVICE_STOPPED;
444  winservice_status.dwWin32ExitCode = exit_code;
445  winservice_update_state();
446
447  if (winservice_dispatcher_thread != NULL)
448    {
449      dbg_print("waiting for dispatcher thread to exit...\r\n");
450      WaitForSingleObject(winservice_dispatcher_thread, INFINITE);
451      dbg_print("dispatcher thread has exited.\r\n");
452
453      CloseHandle(winservice_dispatcher_thread);
454      winservice_dispatcher_thread = NULL;
455    }
456  else
457    {
458      /* There was no dispatcher thread.  So we never started in
459         the first place. */
460      exit_code = winservice_status.dwWin32ExitCode;
461      dbg_print("dispatcher thread was not running\r\n");
462    }
463
464  if (winservice_start_event != NULL)
465    {
466      CloseHandle(winservice_start_event);
467      winservice_start_event = NULL;
468    }
469
470  dbg_print("winservice_stop - service has stopped\r\n");
471}
472
473
474/* This function is installed as an atexit-handler.  This is done so
475  that we don't need to alter every exit() call in main(). */
476static void
477winservice_atexit(void)
478{
479  dbg_print("winservice_atexit - stopping\r\n");
480  winservice_stop(ERROR_SUCCESS);
481}
482
483
484svn_boolean_t
485winservice_is_stopping(void)
486{
487  return (winservice_status.dwCurrentState == SERVICE_STOP_PENDING);
488}
489
490#endif /* WIN32 */
491