1 /*
2  * winservice.c : Implementation of Windows Service support
3  *
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  */
23 
24 
25 
26 #define APR_WANT_STRFUNC
27 #include <apr_want.h>
28 #include <apr_errno.h>
29 
30 #include "svn_error.h"
31 
32 #include "svn_private_config.h"
33 #include "winservice.h"
34 
35 /*
36 Design Notes
37 ------------
38 
39 The code in this file allows svnserve to run as a Windows service.
40 Windows Services are only supported on operating systems derived
41 from Windows NT, which is basically all modern versions of Windows
42 (2000, XP, Server, Vista, etc.) and excludes the Windows 9x line.
43 
44 Windows Services are processes that are started and controlled by
45 the Service Control Manager.  When the SCM wants to start a service,
46 it creates the process, then waits for the process to connect to
47 the SCM, so that the SCM and service process can communicate.
48 This is done using the StartServiceCtrlDispatcher function.
49 
50 In order to minimize changes to the svnserve startup logic, this
51 implementation differs slightly from most service implementations.
52 In most services, main() immediately calls StartServiceCtrlDispatcher,
53 which does not return control to main() until the SCM sends the
54 "stop" request to the service, and the service stops.
55 
56 
57 Installing the Service
58 ----------------------
59 
60 Installation is beyond the scope of source code comments.  There
61 is a separate document that describes how to install and uninstall
62 the service.  Basically, you create a Windows Service, give it a
63 binary path that points to svnserve.exe, and make sure that you
64 specify --service on the command line.
65 
66 
67 Starting the Service
68 --------------------
69 
70 First, the SCM decides that it wants to start a service.  It creates
71 the process for the service, passing it the command-line that is
72 stored in the service configuration (stored in the registry).
73 
74 Next, main() runs.  The command-line should contain the --service
75 argument, which is the hint that svnserve is running under the SCM,
76 not as a standalone process.  main() calls winservice_start().
77 
78 winservice_start() creates an event object (winservice_start_event),
79 and creates and starts a separate thread, the "dispatcher" thread.
80 winservice_start() then waits for either winservice_start_event
81 to fire (meaning: "the dispatcher thread successfully connected
82 to the SCM, and now the service is starting") or for the dispatcher
83 thread to exit (meaning: "failed to connect to SCM").
84 
85 If the dispatcher thread quit, then winservice_start returns an error.
86 If the start event fired, then winservice_start returns a success code
87 (SVN_NO_ERROR).  At this point, the service is now in the "starting"
88 state, from the perspective of the SCM.  winservice_start also registers
89 an atexit handler, which handles cleaning up some of the service logic,
90 as explained below in "Stopping the Service".
91 
92 Next, control returns to main(), which performs the usual startup
93 logic for svnserve.  Mostly, it creates the listener socket.  If
94 main() was able to start the service, then it calls the function
95 winservice_running().
96 
97 winservice_running() informs the SCM that the service has finished
98 starting, and is now in the "running" state.  main() then does its
99 work, accepting client sockets and processing SVN requests.
100 
101 Stopping the Service
102 --------------------
103 
104 At some point, the SCM will decide to stop the service, either because
105 an administrator chose to stop the service, or the system is shutting
106 down.  To do this, the SCM calls winservice_handler() with the
107 SERVICE_CONTROL_STOP control code.  When this happens,
108 winservice_handler() will inform the SCM that the service is now
109 in the "stopping" state, and will call winservice_notify_stop().
110 
111 winservice_notify_stop() is responsible for cleanly shutting down the
112 svnserve logic (waiting for client requests to finish, stopping database
113 access, etc.).  Right now, all it does is close the listener socket,
114 which causes the apr_socket_accept() call in main() to fail.  main()
115 then calls exit(), which processes all atexit() handlers, which
116 results in winservice_stop() being called.
117 
118 winservice_stop() notifies the SCM that the service is now stopped,
119 and then waits for the dispatcher thread to exit.  Because all services
120 in the process have now stopped, the call to StartServiceCtrlDispatcher
121 (in the dispatcher thread) finally returns, and winservice_stop() returns,
122 and the process finally exits.
123 */
124 
125 
126 #ifdef WIN32
127 
128 #include <assert.h>
129 #include <winsvc.h>
130 
131 /* This is just a placeholder, and doesn't actually constrain the
132   service name.  You have to provide *some* service name to the SCM
133   API, but for services that are marked SERVICE_WIN32_OWN_PROCESS (as
134   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. */
141 static HANDLE winservice_dispatcher_thread = NULL;
142 
143 /* Win32 event handle, used to notify winservice_start() that we have
144    successfully connected to the SCM. */
145 static HANDLE winservice_start_event = NULL;
146 
147 /* RPC handle that allows us to notify the SCM of changes in our
148    service status. */
149 static SERVICE_STATUS_HANDLE winservice_status_handle = NULL;
150 
151 /* Our current idea of the service status (stopped, running, controls
152    accepted, exit code, etc.) */
153 static SERVICE_STATUS winservice_status;
154 
155 
156 #ifdef SVN_DEBUG
dbg_print(const char * text)157 static 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 
167 static void winservice_atexit(void);
168 
169 /* Notifies the Service Control Manager of the current state of the
170    service. */
171 static void
winservice_update_state(void)172 winservice_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. */
187 static void
winservice_cleanup(void)188 winservice_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. */
209 static void WINAPI
winservice_handler(DWORD control)210 winservice_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. */
243 static void WINAPI
winservice_service_main(DWORD argc,LPTSTR * argv)244 winservice_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 
274 static 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.) */
291 static DWORD WINAPI
winservice_dispatcher_thread_routine(PVOID arg)292 winservice_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). */
323 svn_error_t *
winservice_start(void)324 winservice_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. */
426 void
winservice_running(void)427 winservice_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. */
439 static void
winservice_stop(DWORD exit_code)440 winservice_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(). */
476 static void
winservice_atexit(void)477 winservice_atexit(void)
478 {
479   dbg_print("winservice_atexit - stopping\r\n");
480   winservice_stop(ERROR_SUCCESS);
481 }
482 
483 
484 svn_boolean_t
winservice_is_stopping(void)485 winservice_is_stopping(void)
486 {
487   return (winservice_status.dwCurrentState == SERVICE_STOP_PENDING);
488 }
489 
490 #endif /* WIN32 */
491