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/* 36Design Notes 37------------ 38 39The code in this file allows svnserve to run as a Windows service. 40Windows Services are only supported on operating systems derived 41from Windows NT, which is basically all modern versions of Windows 42(2000, XP, Server, Vista, etc.) and excludes the Windows 9x line. 43 44Windows Services are processes that are started and controlled by 45the Service Control Manager. When the SCM wants to start a service, 46it creates the process, then waits for the process to connect to 47the SCM, so that the SCM and service process can communicate. 48This is done using the StartServiceCtrlDispatcher function. 49 50In order to minimize changes to the svnserve startup logic, this 51implementation differs slightly from most service implementations. 52In most services, main() immediately calls StartServiceCtrlDispatcher, 53which does not return control to main() until the SCM sends the 54"stop" request to the service, and the service stops. 55 56 57Installing the Service 58---------------------- 59 60Installation is beyond the scope of source code comments. There 61is a separate document that describes how to install and uninstall 62the service. Basically, you create a Windows Service, give it a 63binary path that points to svnserve.exe, and make sure that you 64specify --service on the command line. 65 66 67Starting the Service 68-------------------- 69 70First, the SCM decides that it wants to start a service. It creates 71the process for the service, passing it the command-line that is 72stored in the service configuration (stored in the registry). 73 74Next, main() runs. The command-line should contain the --service 75argument, which is the hint that svnserve is running under the SCM, 76not as a standalone process. main() calls winservice_start(). 77 78winservice_start() creates an event object (winservice_start_event), 79and creates and starts a separate thread, the "dispatcher" thread. 80winservice_start() then waits for either winservice_start_event 81to fire (meaning: "the dispatcher thread successfully connected 82to the SCM, and now the service is starting") or for the dispatcher 83thread to exit (meaning: "failed to connect to SCM"). 84 85If the dispatcher thread quit, then winservice_start returns an error. 86If 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" 88state, from the perspective of the SCM. winservice_start also registers 89an atexit handler, which handles cleaning up some of the service logic, 90as explained below in "Stopping the Service". 91 92Next, control returns to main(), which performs the usual startup 93logic for svnserve. Mostly, it creates the listener socket. If 94main() was able to start the service, then it calls the function 95winservice_running(). 96 97winservice_running() informs the SCM that the service has finished 98starting, and is now in the "running" state. main() then does its 99work, accepting client sockets and processing SVN requests. 100 101Stopping the Service 102-------------------- 103 104At some point, the SCM will decide to stop the service, either because 105an administrator chose to stop the service, or the system is shutting 106down. To do this, the SCM calls winservice_handler() with the 107SERVICE_CONTROL_STOP control code. When this happens, 108winservice_handler() will inform the SCM that the service is now 109in the "stopping" state, and will call winservice_notify_stop(). 110 111winservice_notify_stop() is responsible for cleanly shutting down the 112svnserve logic (waiting for client requests to finish, stopping database 113access, etc.). Right now, all it does is close the listener socket, 114which causes the apr_socket_accept() call in main() to fail. main() 115then calls exit(), which processes all atexit() handlers, which 116results in winservice_stop() being called. 117 118winservice_stop() notifies the SCM that the service is now stopped, 119and then waits for the dispatcher thread to exit. Because all services 120in the process have now stopped, the call to StartServiceCtrlDispatcher 121(in the dispatcher thread) finally returns, and winservice_stop() returns, 122and 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. */ 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