summaryrefslogtreecommitdiffstats
path: root/subversion/svnserve/winservice.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/svnserve/winservice.c')
-rw-r--r--subversion/svnserve/winservice.c490
1 files changed, 490 insertions, 0 deletions
diff --git a/subversion/svnserve/winservice.c b/subversion/svnserve/winservice.c
new file mode 100644
index 0000000..dcb399e
--- /dev/null
+++ b/subversion/svnserve/winservice.c
@@ -0,0 +1,490 @@
+/*
+ * winservice.c : Implementation of Windows Service support
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+#include <apr_errno.h>
+
+#include "svn_error.h"
+
+#include "svn_private_config.h"
+#include "winservice.h"
+
+/*
+Design Notes
+------------
+
+The code in this file allows svnserve to run as a Windows service.
+Windows Services are only supported on operating systems derived
+from Windows NT, which is basically all modern versions of Windows
+(2000, XP, Server, Vista, etc.) and excludes the Windows 9x line.
+
+Windows Services are processes that are started and controlled by
+the Service Control Manager. When the SCM wants to start a service,
+it creates the process, then waits for the process to connect to
+the SCM, so that the SCM and service process can communicate.
+This is done using the StartServiceCtrlDispatcher function.
+
+In order to minimize changes to the svnserve startup logic, this
+implementation differs slightly from most service implementations.
+In most services, main() immediately calls StartServiceCtrlDispatcher,
+which does not return control to main() until the SCM sends the
+"stop" request to the service, and the service stops.
+
+
+Installing the Service
+----------------------
+
+Installation is beyond the scope of source code comments. There
+is a separate document that describes how to install and uninstall
+the service. Basically, you create a Windows Service, give it a
+binary path that points to svnserve.exe, and make sure that you
+specify --service on the command line.
+
+
+Starting the Service
+--------------------
+
+First, the SCM decides that it wants to start a service. It creates
+the process for the service, passing it the command-line that is
+stored in the service configuration (stored in the registry).
+
+Next, main() runs. The command-line should contain the --service
+argument, which is the hint that svnserve is running under the SCM,
+not as a standalone process. main() calls winservice_start().
+
+winservice_start() creates an event object (winservice_start_event),
+and creates and starts a separate thread, the "dispatcher" thread.
+winservice_start() then waits for either winservice_start_event
+to fire (meaning: "the dispatcher thread successfully connected
+to the SCM, and now the service is starting") or for the dispatcher
+thread to exit (meaning: "failed to connect to SCM").
+
+If the dispatcher thread quit, then winservice_start returns an error.
+If the start event fired, then winservice_start returns a success code
+(SVN_NO_ERROR). At this point, the service is now in the "starting"
+state, from the perspective of the SCM. winservice_start also registers
+an atexit handler, which handles cleaning up some of the service logic,
+as explained below in "Stopping the Service".
+
+Next, control returns to main(), which performs the usual startup
+logic for svnserve. Mostly, it creates the listener socket. If
+main() was able to start the service, then it calls the function
+winservice_running().
+
+winservice_running() informs the SCM that the service has finished
+starting, and is now in the "running" state. main() then does its
+work, accepting client sockets and processing SVN requests.
+
+Stopping the Service
+--------------------
+
+At some point, the SCM will decide to stop the service, either because
+an administrator chose to stop the service, or the system is shutting
+down. To do this, the SCM calls winservice_handler() with the
+SERVICE_CONTROL_STOP control code. When this happens,
+winservice_handler() will inform the SCM that the service is now
+in the "stopping" state, and will call winservice_notify_stop().
+
+winservice_notify_stop() is responsible for cleanly shutting down the
+svnserve logic (waiting for client requests to finish, stopping database
+access, etc.). Right now, all it does is close the listener socket,
+which causes the apr_socket_accept() call in main() to fail. main()
+then calls exit(), which processes all atexit() handlers, which
+results in winservice_stop() being called.
+
+winservice_stop() notifies the SCM that the service is now stopped,
+and then waits for the dispatcher thread to exit. Because all services
+in the process have now stopped, the call to StartServiceCtrlDispatcher
+(in the dispatcher thread) finally returns, and winservice_stop() returns,
+and the process finally exits.
+*/
+
+
+#ifdef WIN32
+
+#include <assert.h>
+#include <winsvc.h>
+
+/* This is just a placeholder, and doesn't actually constrain the
+ service name. You have to provide *some* service name to the SCM
+ API, but for services that are marked SERVICE_WIN32_OWN_PROCESS (as
+ is the case for svnserve), the service name is ignored. It *is*
+ relevant for service binaries that run more than one service in a
+ single process. */
+#define WINSERVICE_SERVICE_NAME "svnserve"
+
+
+/* Win32 handle to the dispatcher thread. */
+static HANDLE winservice_dispatcher_thread = NULL;
+
+/* Win32 event handle, used to notify winservice_start() that we have
+ successfully connected to the SCM. */
+static HANDLE winservice_start_event = NULL;
+
+/* RPC handle that allows us to notify the SCM of changes in our
+ service status. */
+static SERVICE_STATUS_HANDLE winservice_status_handle = NULL;
+
+/* Our current idea of the service status (stopped, running, controls
+ accepted, exit code, etc.) */
+static SERVICE_STATUS winservice_status;
+
+
+#ifdef SVN_DEBUG
+static void dbg_print(const char* text)
+{
+ OutputDebugStringA(text);
+}
+#else
+/* Make sure dbg_print compiles to nothing in release builds. */
+#define dbg_print(text)
+#endif
+
+
+static void winservice_atexit(void);
+
+/* Notifies the Service Control Manager of the current state of the
+ service. */
+static void
+winservice_update_state(void)
+{
+ if (winservice_status_handle != NULL)
+ {
+ if (!SetServiceStatus(winservice_status_handle, &winservice_status))
+ {
+ dbg_print("SetServiceStatus - FAILED\r\n");
+ }
+ }
+}
+
+
+/* This function cleans up state associated with the service support.
+ If the dispatcher thread handle is non-NULL, then this function
+ will wait for the dispatcher thread to exit. */
+static void
+winservice_cleanup(void)
+{
+ if (winservice_start_event != NULL)
+ {
+ CloseHandle(winservice_start_event);
+ winservice_start_event = NULL;
+ }
+
+ if (winservice_dispatcher_thread != NULL)
+ {
+ dbg_print("winservice_cleanup:"
+ " waiting for dispatcher thread to exit\r\n");
+ WaitForSingleObject(winservice_dispatcher_thread, INFINITE);
+ CloseHandle(winservice_dispatcher_thread);
+ winservice_dispatcher_thread = NULL;
+ }
+}
+
+
+/* The SCM invokes this function to cause state changes in the
+ service. */
+static void WINAPI
+winservice_handler(DWORD control)
+{
+ switch (control)
+ {
+ case SERVICE_CONTROL_INTERROGATE:
+ /* The SCM just wants to check our state. We are required to
+ call SetServiceStatus, but we don't need to make any state
+ changes. */
+ dbg_print("SERVICE_CONTROL_INTERROGATE\r\n");
+ winservice_update_state();
+ break;
+
+ case SERVICE_CONTROL_STOP:
+ dbg_print("SERVICE_CONTROL_STOP\r\n");
+ winservice_status.dwCurrentState = SERVICE_STOP_PENDING;
+ winservice_update_state();
+ winservice_notify_stop();
+ break;
+ }
+}
+
+
+/* This is the "service main" routine (in the Win32 terminology).
+
+ Normally, this function (thread) implements the "main" loop of a
+ service. However, in order to minimize changes to the svnserve
+ main() function, this function is running in a different thread,
+ and main() is blocked in winservice_start(), waiting for
+ winservice_start_event. So this function (thread) only needs to
+ signal that event to "start" the service.
+
+ If this function succeeds, it signals winservice_start_event, which
+ wakes up the winservice_start() frame that is blocked. */
+static void WINAPI
+winservice_service_main(DWORD argc, LPTSTR *argv)
+{
+ DWORD error;
+
+ assert(winservice_start_event != NULL);
+
+ winservice_status_handle =
+ RegisterServiceCtrlHandler(WINSERVICE_SERVICE_NAME, winservice_handler);
+ if (winservice_status_handle == NULL)
+ {
+ /* Ok, that's not fair. We received a request to start a service,
+ and now we cannot bind to the SCM in order to update status?
+ Bring down the app. */
+ error = GetLastError();
+ dbg_print("RegisterServiceCtrlHandler FAILED\r\n");
+ /* Put the error code somewhere where winservice_start can find it. */
+ winservice_status.dwWin32ExitCode = error;
+ SetEvent(winservice_start_event);
+ return;
+ }
+
+ winservice_status.dwCurrentState = SERVICE_START_PENDING;
+ winservice_status.dwWin32ExitCode = ERROR_SUCCESS;
+ winservice_update_state();
+
+ dbg_print("winservice_service_main: service is starting\r\n");
+ SetEvent(winservice_start_event);
+}
+
+
+static const SERVICE_TABLE_ENTRY winservice_service_table[] =
+ {
+ { WINSERVICE_SERVICE_NAME, winservice_service_main },
+ { NULL, NULL }
+ };
+
+
+/* This is the thread routine for the "dispatcher" thread. The
+ purpose of this thread is to connect this process with the Service
+ Control Manager, which allows this process to receive control
+ requests from the SCM, and allows this process to update the SCM
+ with status information.
+
+ The StartServiceCtrlDispatcher connects this process to the SCM.
+ If it succeeds, then it will not return until all of the services
+ running in this process have stopped. (In our case, there is only
+ one service per process.) */
+static DWORD WINAPI
+winservice_dispatcher_thread_routine(PVOID arg)
+{
+ dbg_print("winservice_dispatcher_thread_routine: starting\r\n");
+
+ if (!StartServiceCtrlDispatcher(winservice_service_table))
+ {
+ /* This is a common error. Usually, it means the user has
+ invoked the service with the --service flag directly. This
+ is incorrect. The only time the --service flag is passed is
+ when the process is being started by the SCM. */
+ DWORD error = GetLastError();
+
+ dbg_print("dispatcher: FAILED to connect to SCM\r\n");
+ return error;
+ }
+
+ dbg_print("dispatcher: SCM is done using this process -- exiting\r\n");
+ return ERROR_SUCCESS;
+}
+
+
+/* If svnserve needs to run as a Win32 service, then we need to
+ coordinate with the Service Control Manager (SCM) before
+ continuing. This function call registers the svnserve.exe process
+ with the SCM, waits for the "start" command from the SCM (which
+ will come very quickly), and confirms that those steps succeeded.
+
+ After this call succeeds, the service should perform whatever work
+ it needs to start the service, and then the service should call
+ winservice_running() (if no errors occurred) or winservice_stop()
+ (if something failed during startup). */
+svn_error_t *
+winservice_start(void)
+{
+ HANDLE handles[2];
+ DWORD thread_id;
+ DWORD error_code;
+ apr_status_t apr_status;
+ DWORD wait_status;
+
+ dbg_print("winservice_start: starting svnserve as a service...\r\n");
+
+ ZeroMemory(&winservice_status, sizeof(winservice_status));
+ winservice_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+ winservice_status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
+ winservice_status.dwCurrentState = SERVICE_STOPPED;
+
+ /* Create the event that will wake up this thread when the SCM
+ creates the ServiceMain thread. */
+ winservice_start_event = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (winservice_start_event == NULL)
+ {
+ apr_status = apr_get_os_error();
+ return svn_error_wrap_apr(apr_status,
+ _("Failed to create winservice_start_event"));
+ }
+
+ winservice_dispatcher_thread =
+ (HANDLE)CreateThread(NULL, 0, winservice_dispatcher_thread_routine,
+ NULL, 0, &thread_id);
+ if (winservice_dispatcher_thread == NULL)
+ {
+ apr_status = apr_get_os_error();
+ winservice_cleanup();
+ return svn_error_wrap_apr(apr_status,
+ _("The service failed to start"));
+ }
+
+ /* Next, we wait for the "start" event to fire (meaning the service
+ logic has successfully started), or for the dispatch thread to
+ exit (meaning the service logic could not start). */
+
+ handles[0] = winservice_start_event;
+ handles[1] = winservice_dispatcher_thread;
+ wait_status = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
+ switch (wait_status)
+ {
+ case WAIT_OBJECT_0:
+ dbg_print("winservice_start: service is now starting\r\n");
+
+ /* We no longer need the start event. */
+ CloseHandle(winservice_start_event);
+ winservice_start_event = NULL;
+
+ /* Register our cleanup logic. */
+ atexit(winservice_atexit);
+ return SVN_NO_ERROR;
+
+ case WAIT_OBJECT_0+1:
+ /* The dispatcher thread exited without starting the service.
+ This happens when the dispatcher fails to connect to the SCM. */
+ dbg_print("winservice_start: dispatcher thread has failed\r\n");
+
+ if (GetExitCodeThread(winservice_dispatcher_thread, &error_code))
+ {
+ dbg_print("winservice_start: dispatcher thread failed\r\n");
+
+ if (error_code == ERROR_SUCCESS)
+ error_code = ERROR_INTERNAL_ERROR;
+
+ }
+ else
+ {
+ error_code = ERROR_INTERNAL_ERROR;
+ }
+
+ CloseHandle(winservice_dispatcher_thread);
+ winservice_dispatcher_thread = NULL;
+
+ winservice_cleanup();
+
+ return svn_error_wrap_apr
+ (APR_FROM_OS_ERROR(error_code),
+ _("Failed to connect to Service Control Manager"));
+
+ default:
+ /* This should never happen! This indicates that our handles are
+ broken, or some other highly unusual error. There is nothing
+ rational that we can do to recover. */
+ apr_status = apr_get_os_error();
+ dbg_print("winservice_start: WaitForMultipleObjects failed!\r\n");
+
+ winservice_cleanup();
+ return svn_error_wrap_apr
+ (apr_status, _("The service failed to start; an internal error"
+ " occurred while starting the service"));
+ }
+}
+
+
+/* main() calls this function in order to inform the SCM that the
+ service has successfully started. This is required; otherwise, the
+ SCM will believe that the service is stuck in the "starting" state,
+ and management tools will also believe that the service is stuck. */
+void
+winservice_running(void)
+{
+ winservice_status.dwCurrentState = SERVICE_RUNNING;
+ winservice_update_state();
+ dbg_print("winservice_notify_running: service is now running\r\n");
+}
+
+
+/* main() calls this function in order to notify the SCM that the
+ service has stopped. This function also handles cleaning up the
+ dispatcher thread (the one that we created above in
+ winservice_start. */
+static void
+winservice_stop(DWORD exit_code)
+{
+ dbg_print("winservice_stop - notifying SCM that service has stopped\r\n");
+ winservice_status.dwCurrentState = SERVICE_STOPPED;
+ winservice_status.dwWin32ExitCode = exit_code;
+ winservice_update_state();
+
+ if (winservice_dispatcher_thread != NULL)
+ {
+ dbg_print("waiting for dispatcher thread to exit...\r\n");
+ WaitForSingleObject(winservice_dispatcher_thread, INFINITE);
+ dbg_print("dispatcher thread has exited.\r\n");
+
+ CloseHandle(winservice_dispatcher_thread);
+ winservice_dispatcher_thread = NULL;
+ }
+ else
+ {
+ /* There was no dispatcher thread. So we never started in
+ the first place. */
+ exit_code = winservice_status.dwWin32ExitCode;
+ dbg_print("dispatcher thread was not running\r\n");
+ }
+
+ if (winservice_start_event != NULL)
+ {
+ CloseHandle(winservice_start_event);
+ winservice_start_event = NULL;
+ }
+
+ dbg_print("winservice_stop - service has stopped\r\n");
+}
+
+
+/* This function is installed as an atexit-handler. This is done so
+ that we don't need to alter every exit() call in main(). */
+static void
+winservice_atexit(void)
+{
+ dbg_print("winservice_atexit - stopping\r\n");
+ winservice_stop(ERROR_SUCCESS);
+}
+
+
+svn_boolean_t
+winservice_is_stopping(void)
+{
+ return (winservice_status.dwCurrentState == SERVICE_STOP_PENDING);
+}
+
+#endif /* WIN32 */
OpenPOWER on IntegriCloud