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