1251881Speter/*
2251881Speter * winservice.c : Implementation of Windows Service support
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter
25251881Speter
26251881Speter#define APR_WANT_STRFUNC
27251881Speter#include <apr_want.h>
28251881Speter#include <apr_errno.h>
29251881Speter
30251881Speter#include "svn_error.h"
31251881Speter
32251881Speter#include "svn_private_config.h"
33251881Speter#include "winservice.h"
34251881Speter
35251881Speter/*
36251881SpeterDesign Notes
37251881Speter------------
38251881Speter
39251881SpeterThe code in this file allows svnserve to run as a Windows service.
40251881SpeterWindows Services are only supported on operating systems derived
41251881Speterfrom Windows NT, which is basically all modern versions of Windows
42251881Speter(2000, XP, Server, Vista, etc.) and excludes the Windows 9x line.
43251881Speter
44251881SpeterWindows Services are processes that are started and controlled by
45251881Speterthe Service Control Manager.  When the SCM wants to start a service,
46251881Speterit creates the process, then waits for the process to connect to
47251881Speterthe SCM, so that the SCM and service process can communicate.
48251881SpeterThis is done using the StartServiceCtrlDispatcher function.
49251881Speter
50251881SpeterIn order to minimize changes to the svnserve startup logic, this
51251881Speterimplementation differs slightly from most service implementations.
52251881SpeterIn most services, main() immediately calls StartServiceCtrlDispatcher,
53251881Speterwhich does not return control to main() until the SCM sends the
54251881Speter"stop" request to the service, and the service stops.
55251881Speter
56251881Speter
57251881SpeterInstalling the Service
58251881Speter----------------------
59251881Speter
60251881SpeterInstallation is beyond the scope of source code comments.  There
61251881Speteris a separate document that describes how to install and uninstall
62251881Speterthe service.  Basically, you create a Windows Service, give it a
63251881Speterbinary path that points to svnserve.exe, and make sure that you
64251881Speterspecify --service on the command line.
65251881Speter
66251881Speter
67251881SpeterStarting the Service
68251881Speter--------------------
69251881Speter
70251881SpeterFirst, the SCM decides that it wants to start a service.  It creates
71251881Speterthe process for the service, passing it the command-line that is
72251881Speterstored in the service configuration (stored in the registry).
73251881Speter
74251881SpeterNext, main() runs.  The command-line should contain the --service
75251881Speterargument, which is the hint that svnserve is running under the SCM,
76251881Speternot as a standalone process.  main() calls winservice_start().
77251881Speter
78251881Speterwinservice_start() creates an event object (winservice_start_event),
79251881Speterand creates and starts a separate thread, the "dispatcher" thread.
80251881Speterwinservice_start() then waits for either winservice_start_event
81251881Speterto fire (meaning: "the dispatcher thread successfully connected
82251881Speterto the SCM, and now the service is starting") or for the dispatcher
83251881Speterthread to exit (meaning: "failed to connect to SCM").
84251881Speter
85251881SpeterIf the dispatcher thread quit, then winservice_start returns an error.
86251881SpeterIf the start event fired, then winservice_start returns a success code
87251881Speter(SVN_NO_ERROR).  At this point, the service is now in the "starting"
88251881Speterstate, from the perspective of the SCM.  winservice_start also registers
89251881Speteran atexit handler, which handles cleaning up some of the service logic,
90251881Speteras explained below in "Stopping the Service".
91251881Speter
92251881SpeterNext, control returns to main(), which performs the usual startup
93251881Speterlogic for svnserve.  Mostly, it creates the listener socket.  If
94251881Spetermain() was able to start the service, then it calls the function
95251881Speterwinservice_running().
96251881Speter
97251881Speterwinservice_running() informs the SCM that the service has finished
98251881Speterstarting, and is now in the "running" state.  main() then does its
99251881Speterwork, accepting client sockets and processing SVN requests.
100251881Speter
101251881SpeterStopping the Service
102251881Speter--------------------
103251881Speter
104251881SpeterAt some point, the SCM will decide to stop the service, either because
105251881Speteran administrator chose to stop the service, or the system is shutting
106251881Speterdown.  To do this, the SCM calls winservice_handler() with the
107251881SpeterSERVICE_CONTROL_STOP control code.  When this happens,
108251881Speterwinservice_handler() will inform the SCM that the service is now
109251881Speterin the "stopping" state, and will call winservice_notify_stop().
110251881Speter
111251881Speterwinservice_notify_stop() is responsible for cleanly shutting down the
112251881Spetersvnserve logic (waiting for client requests to finish, stopping database
113251881Speteraccess, etc.).  Right now, all it does is close the listener socket,
114251881Speterwhich causes the apr_socket_accept() call in main() to fail.  main()
115251881Speterthen calls exit(), which processes all atexit() handlers, which
116251881Speterresults in winservice_stop() being called.
117251881Speter
118251881Speterwinservice_stop() notifies the SCM that the service is now stopped,
119251881Speterand then waits for the dispatcher thread to exit.  Because all services
120251881Speterin the process have now stopped, the call to StartServiceCtrlDispatcher
121251881Speter(in the dispatcher thread) finally returns, and winservice_stop() returns,
122251881Speterand the process finally exits.
123251881Speter*/
124251881Speter
125251881Speter
126251881Speter#ifdef WIN32
127251881Speter
128251881Speter#include <assert.h>
129251881Speter#include <winsvc.h>
130251881Speter
131251881Speter/* This is just a placeholder, and doesn't actually constrain the
132251881Speter  service name.  You have to provide *some* service name to the SCM
133251881Speter  API, but for services that are marked SERVICE_WIN32_OWN_PROCESS (as
134251881Speter  is the case for svnserve), the service name is ignored.  It *is*
135251881Speter  relevant for service binaries that run more than one service in a
136251881Speter  single process. */
137251881Speter#define WINSERVICE_SERVICE_NAME "svnserve"
138251881Speter
139251881Speter
140251881Speter/* Win32 handle to the dispatcher thread. */
141251881Speterstatic HANDLE winservice_dispatcher_thread = NULL;
142251881Speter
143251881Speter/* Win32 event handle, used to notify winservice_start() that we have
144251881Speter   successfully connected to the SCM. */
145251881Speterstatic HANDLE winservice_start_event = NULL;
146251881Speter
147251881Speter/* RPC handle that allows us to notify the SCM of changes in our
148251881Speter   service status. */
149251881Speterstatic SERVICE_STATUS_HANDLE winservice_status_handle = NULL;
150251881Speter
151251881Speter/* Our current idea of the service status (stopped, running, controls
152251881Speter   accepted, exit code, etc.) */
153251881Speterstatic SERVICE_STATUS winservice_status;
154251881Speter
155251881Speter
156251881Speter#ifdef SVN_DEBUG
157251881Speterstatic void dbg_print(const char* text)
158251881Speter{
159251881Speter  OutputDebugStringA(text);
160251881Speter}
161251881Speter#else
162251881Speter/* Make sure dbg_print compiles to nothing in release builds. */
163251881Speter#define dbg_print(text)
164251881Speter#endif
165251881Speter
166251881Speter
167251881Speterstatic void winservice_atexit(void);
168251881Speter
169251881Speter/* Notifies the Service Control Manager of the current state of the
170251881Speter   service. */
171251881Speterstatic void
172251881Speterwinservice_update_state(void)
173251881Speter{
174251881Speter  if (winservice_status_handle != NULL)
175251881Speter    {
176251881Speter      if (!SetServiceStatus(winservice_status_handle, &winservice_status))
177251881Speter        {
178251881Speter          dbg_print("SetServiceStatus - FAILED\r\n");
179251881Speter        }
180251881Speter    }
181251881Speter}
182251881Speter
183251881Speter
184251881Speter/* This function cleans up state associated with the service support.
185251881Speter   If the dispatcher thread handle is non-NULL, then this function
186251881Speter   will wait for the dispatcher thread to exit. */
187251881Speterstatic void
188251881Speterwinservice_cleanup(void)
189251881Speter{
190251881Speter  if (winservice_start_event != NULL)
191251881Speter    {
192251881Speter      CloseHandle(winservice_start_event);
193251881Speter      winservice_start_event = NULL;
194251881Speter    }
195251881Speter
196251881Speter  if (winservice_dispatcher_thread != NULL)
197251881Speter    {
198251881Speter      dbg_print("winservice_cleanup:"
199251881Speter                " waiting for dispatcher thread to exit\r\n");
200251881Speter      WaitForSingleObject(winservice_dispatcher_thread, INFINITE);
201251881Speter      CloseHandle(winservice_dispatcher_thread);
202251881Speter      winservice_dispatcher_thread = NULL;
203251881Speter    }
204251881Speter}
205251881Speter
206251881Speter
207251881Speter/* The SCM invokes this function to cause state changes in the
208251881Speter   service. */
209251881Speterstatic void WINAPI
210251881Speterwinservice_handler(DWORD control)
211251881Speter{
212251881Speter  switch (control)
213251881Speter    {
214251881Speter    case SERVICE_CONTROL_INTERROGATE:
215251881Speter      /* The SCM just wants to check our state.  We are required to
216251881Speter         call SetServiceStatus, but we don't need to make any state
217251881Speter         changes. */
218251881Speter      dbg_print("SERVICE_CONTROL_INTERROGATE\r\n");
219251881Speter      winservice_update_state();
220251881Speter      break;
221251881Speter
222251881Speter    case SERVICE_CONTROL_STOP:
223251881Speter      dbg_print("SERVICE_CONTROL_STOP\r\n");
224251881Speter      winservice_status.dwCurrentState = SERVICE_STOP_PENDING;
225251881Speter      winservice_update_state();
226251881Speter      winservice_notify_stop();
227251881Speter      break;
228251881Speter    }
229251881Speter}
230251881Speter
231251881Speter
232251881Speter/* This is the "service main" routine (in the Win32 terminology).
233251881Speter
234251881Speter   Normally, this function (thread) implements the "main" loop of a
235251881Speter   service.  However, in order to minimize changes to the svnserve
236251881Speter   main() function, this function is running in a different thread,
237251881Speter   and main() is blocked in winservice_start(), waiting for
238251881Speter   winservice_start_event.  So this function (thread) only needs to
239251881Speter   signal that event to "start" the service.
240251881Speter
241251881Speter   If this function succeeds, it signals winservice_start_event, which
242251881Speter   wakes up the winservice_start() frame that is blocked. */
243251881Speterstatic void WINAPI
244251881Speterwinservice_service_main(DWORD argc, LPTSTR *argv)
245251881Speter{
246251881Speter  DWORD error;
247251881Speter
248251881Speter  assert(winservice_start_event != NULL);
249251881Speter
250251881Speter  winservice_status_handle =
251251881Speter    RegisterServiceCtrlHandler(WINSERVICE_SERVICE_NAME, winservice_handler);
252251881Speter  if (winservice_status_handle == NULL)
253251881Speter    {
254251881Speter      /* Ok, that's not fair.  We received a request to start a service,
255251881Speter         and now we cannot bind to the SCM in order to update status?
256251881Speter         Bring down the app. */
257251881Speter      error = GetLastError();
258251881Speter      dbg_print("RegisterServiceCtrlHandler FAILED\r\n");
259251881Speter      /* Put the error code somewhere where winservice_start can find it. */
260251881Speter      winservice_status.dwWin32ExitCode = error;
261251881Speter      SetEvent(winservice_start_event);
262251881Speter      return;
263251881Speter    }
264251881Speter
265251881Speter  winservice_status.dwCurrentState = SERVICE_START_PENDING;
266251881Speter  winservice_status.dwWin32ExitCode = ERROR_SUCCESS;
267251881Speter  winservice_update_state();
268251881Speter
269251881Speter  dbg_print("winservice_service_main: service is starting\r\n");
270251881Speter  SetEvent(winservice_start_event);
271251881Speter}
272251881Speter
273251881Speter
274251881Speterstatic const SERVICE_TABLE_ENTRY winservice_service_table[] =
275251881Speter  {
276251881Speter    { WINSERVICE_SERVICE_NAME, winservice_service_main },
277251881Speter    { NULL, NULL }
278251881Speter  };
279251881Speter
280251881Speter
281251881Speter/* This is the thread routine for the "dispatcher" thread.  The
282251881Speter   purpose of this thread is to connect this process with the Service
283251881Speter   Control Manager, which allows this process to receive control
284251881Speter   requests from the SCM, and allows this process to update the SCM
285251881Speter   with status information.
286251881Speter
287251881Speter   The StartServiceCtrlDispatcher connects this process to the SCM.
288251881Speter   If it succeeds, then it will not return until all of the services
289251881Speter   running in this process have stopped.  (In our case, there is only
290251881Speter   one service per process.) */
291251881Speterstatic DWORD WINAPI
292251881Speterwinservice_dispatcher_thread_routine(PVOID arg)
293251881Speter{
294251881Speter  dbg_print("winservice_dispatcher_thread_routine: starting\r\n");
295251881Speter
296251881Speter  if (!StartServiceCtrlDispatcher(winservice_service_table))
297251881Speter    {
298251881Speter      /* This is a common error.  Usually, it means the user has
299251881Speter         invoked the service with the --service flag directly.  This
300251881Speter         is incorrect.  The only time the --service flag is passed is
301251881Speter         when the process is being started by the SCM. */
302251881Speter      DWORD error = GetLastError();
303251881Speter
304251881Speter      dbg_print("dispatcher: FAILED to connect to SCM\r\n");
305251881Speter      return error;
306251881Speter    }
307251881Speter
308251881Speter  dbg_print("dispatcher: SCM is done using this process -- exiting\r\n");
309251881Speter  return ERROR_SUCCESS;
310251881Speter}
311251881Speter
312251881Speter
313251881Speter/* If svnserve needs to run as a Win32 service, then we need to
314251881Speter   coordinate with the Service Control Manager (SCM) before
315251881Speter   continuing.  This function call registers the svnserve.exe process
316251881Speter   with the SCM, waits for the "start" command from the SCM (which
317251881Speter   will come very quickly), and confirms that those steps succeeded.
318251881Speter
319251881Speter   After this call succeeds, the service should perform whatever work
320251881Speter   it needs to start the service, and then the service should call
321251881Speter   winservice_running() (if no errors occurred) or winservice_stop()
322251881Speter   (if something failed during startup). */
323251881Spetersvn_error_t *
324251881Speterwinservice_start(void)
325251881Speter{
326251881Speter  HANDLE handles[2];
327251881Speter  DWORD thread_id;
328251881Speter  DWORD error_code;
329251881Speter  apr_status_t apr_status;
330251881Speter  DWORD wait_status;
331251881Speter
332251881Speter  dbg_print("winservice_start: starting svnserve as a service...\r\n");
333251881Speter
334251881Speter  ZeroMemory(&winservice_status, sizeof(winservice_status));
335251881Speter  winservice_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
336251881Speter  winservice_status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
337251881Speter  winservice_status.dwCurrentState = SERVICE_STOPPED;
338251881Speter
339251881Speter  /* Create the event that will wake up this thread when the SCM
340251881Speter     creates the ServiceMain thread. */
341251881Speter  winservice_start_event = CreateEvent(NULL, FALSE, FALSE, NULL);
342251881Speter  if (winservice_start_event == NULL)
343251881Speter    {
344251881Speter      apr_status = apr_get_os_error();
345251881Speter      return svn_error_wrap_apr(apr_status,
346251881Speter                                _("Failed to create winservice_start_event"));
347251881Speter    }
348251881Speter
349251881Speter  winservice_dispatcher_thread =
350251881Speter    (HANDLE)CreateThread(NULL, 0, winservice_dispatcher_thread_routine,
351251881Speter                         NULL, 0, &thread_id);
352251881Speter  if (winservice_dispatcher_thread == NULL)
353251881Speter    {
354251881Speter      apr_status = apr_get_os_error();
355251881Speter      winservice_cleanup();
356251881Speter      return svn_error_wrap_apr(apr_status,
357251881Speter                                _("The service failed to start"));
358251881Speter    }
359251881Speter
360251881Speter  /* Next, we wait for the "start" event to fire (meaning the service
361251881Speter     logic has successfully started), or for the dispatch thread to
362251881Speter     exit (meaning the service logic could not start). */
363251881Speter
364251881Speter  handles[0] = winservice_start_event;
365251881Speter  handles[1] = winservice_dispatcher_thread;
366251881Speter  wait_status = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
367251881Speter  switch (wait_status)
368251881Speter    {
369251881Speter    case WAIT_OBJECT_0:
370251881Speter      dbg_print("winservice_start: service is now starting\r\n");
371251881Speter
372251881Speter      /* We no longer need the start event. */
373251881Speter      CloseHandle(winservice_start_event);
374251881Speter      winservice_start_event = NULL;
375251881Speter
376251881Speter      /* Register our cleanup logic. */
377251881Speter      atexit(winservice_atexit);
378251881Speter      return SVN_NO_ERROR;
379251881Speter
380251881Speter    case WAIT_OBJECT_0+1:
381251881Speter      /* The dispatcher thread exited without starting the service.
382251881Speter         This happens when the dispatcher fails to connect to the SCM. */
383251881Speter      dbg_print("winservice_start: dispatcher thread has failed\r\n");
384251881Speter
385251881Speter      if (GetExitCodeThread(winservice_dispatcher_thread, &error_code))
386251881Speter        {
387251881Speter          dbg_print("winservice_start: dispatcher thread failed\r\n");
388251881Speter
389251881Speter          if (error_code == ERROR_SUCCESS)
390251881Speter            error_code = ERROR_INTERNAL_ERROR;
391251881Speter
392251881Speter        }
393251881Speter      else
394251881Speter        {
395251881Speter          error_code = ERROR_INTERNAL_ERROR;
396251881Speter        }
397251881Speter
398251881Speter      CloseHandle(winservice_dispatcher_thread);
399251881Speter      winservice_dispatcher_thread = NULL;
400251881Speter
401251881Speter      winservice_cleanup();
402251881Speter
403251881Speter      return svn_error_wrap_apr
404251881Speter        (APR_FROM_OS_ERROR(error_code),
405251881Speter         _("Failed to connect to Service Control Manager"));
406251881Speter
407251881Speter    default:
408251881Speter      /* This should never happen! This indicates that our handles are
409251881Speter         broken, or some other highly unusual error.  There is nothing
410251881Speter         rational that we can do to recover. */
411251881Speter      apr_status = apr_get_os_error();
412251881Speter      dbg_print("winservice_start: WaitForMultipleObjects failed!\r\n");
413251881Speter
414251881Speter      winservice_cleanup();
415251881Speter      return svn_error_wrap_apr
416251881Speter        (apr_status, _("The service failed to start; an internal error"
417251881Speter                       " occurred while starting the service"));
418251881Speter    }
419251881Speter}
420251881Speter
421251881Speter
422251881Speter/* main() calls this function in order to inform the SCM that the
423251881Speter   service has successfully started.  This is required; otherwise, the
424251881Speter   SCM will believe that the service is stuck in the "starting" state,
425251881Speter   and management tools will also believe that the service is stuck. */
426251881Spetervoid
427251881Speterwinservice_running(void)
428251881Speter{
429251881Speter  winservice_status.dwCurrentState = SERVICE_RUNNING;
430251881Speter  winservice_update_state();
431251881Speter  dbg_print("winservice_notify_running: service is now running\r\n");
432251881Speter}
433251881Speter
434251881Speter
435251881Speter/* main() calls this function in order to notify the SCM that the
436251881Speter   service has stopped.  This function also handles cleaning up the
437251881Speter   dispatcher thread (the one that we created above in
438251881Speter   winservice_start. */
439251881Speterstatic void
440251881Speterwinservice_stop(DWORD exit_code)
441251881Speter{
442251881Speter  dbg_print("winservice_stop - notifying SCM that service has stopped\r\n");
443251881Speter  winservice_status.dwCurrentState = SERVICE_STOPPED;
444251881Speter  winservice_status.dwWin32ExitCode = exit_code;
445251881Speter  winservice_update_state();
446251881Speter
447251881Speter  if (winservice_dispatcher_thread != NULL)
448251881Speter    {
449251881Speter      dbg_print("waiting for dispatcher thread to exit...\r\n");
450251881Speter      WaitForSingleObject(winservice_dispatcher_thread, INFINITE);
451251881Speter      dbg_print("dispatcher thread has exited.\r\n");
452251881Speter
453251881Speter      CloseHandle(winservice_dispatcher_thread);
454251881Speter      winservice_dispatcher_thread = NULL;
455251881Speter    }
456251881Speter  else
457251881Speter    {
458251881Speter      /* There was no dispatcher thread.  So we never started in
459251881Speter         the first place. */
460251881Speter      exit_code = winservice_status.dwWin32ExitCode;
461251881Speter      dbg_print("dispatcher thread was not running\r\n");
462251881Speter    }
463251881Speter
464251881Speter  if (winservice_start_event != NULL)
465251881Speter    {
466251881Speter      CloseHandle(winservice_start_event);
467251881Speter      winservice_start_event = NULL;
468251881Speter    }
469251881Speter
470251881Speter  dbg_print("winservice_stop - service has stopped\r\n");
471251881Speter}
472251881Speter
473251881Speter
474251881Speter/* This function is installed as an atexit-handler.  This is done so
475251881Speter  that we don't need to alter every exit() call in main(). */
476251881Speterstatic void
477251881Speterwinservice_atexit(void)
478251881Speter{
479251881Speter  dbg_print("winservice_atexit - stopping\r\n");
480251881Speter  winservice_stop(ERROR_SUCCESS);
481251881Speter}
482251881Speter
483251881Speter
484251881Spetersvn_boolean_t
485251881Speterwinservice_is_stopping(void)
486251881Speter{
487251881Speter  return (winservice_status.dwCurrentState == SERVICE_STOP_PENDING);
488251881Speter}
489251881Speter
490251881Speter#endif /* WIN32 */
491