[ovs-dev] [PATCH 3/4] daemon-windows: Ability to handle windows service calls.

Gurucharan Shetty shettyg at nicira.com
Fri Jan 17 20:26:22 UTC 2014


The following code does not add any users yet.

The visioned workflow that this piece of code should work with is:
* Create a windows service through a startup script with
a tool like 'sc'
ex:  sc create ovsdb-server binpath=
 "C:\openvswitch\usr\sbin\ovsdb-server.exe -vconsole:off
-vsyslog:off -vfile:info --remote=ptcp:6632:127.0.0.1 --log-file
--service-monitor --service"

* Start the service from the startup script.
ex: sc start ovsdb-server

* Terminate the service during shutdown process.
ex: sc stop ovsdb-server

* Abrupt termination will restart the service.

Signed-off-by: Gurucharan Shetty <gshetty at nicira.com>
---
 lib/automake.mk      |   12 +-
 lib/daemon-windows.c |  316 ++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/daemon.c         |   19 +++
 lib/daemon.h         |   31 ++++-
 lib/service-syn.man  |    3 +
 lib/service.man      |   11 ++
 6 files changed, 387 insertions(+), 5 deletions(-)
 create mode 100644 lib/daemon-windows.c
 create mode 100644 lib/service-syn.man
 create mode 100644 lib/service.man

diff --git a/lib/automake.mk b/lib/automake.mk
index 449d2c5..0617136 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -40,7 +40,6 @@ lib_libopenvswitch_la_SOURCES = \
 	lib/crc32c.h \
 	lib/csum.c \
 	lib/csum.h \
-	lib/daemon.c \
 	lib/daemon.h \
 	lib/dhcp.h \
 	lib/dummy.c \
@@ -237,6 +236,15 @@ lib_libopenvswitch_la_SOURCES = \
 	lib/vswitch-idl.h \
 	lib/vtep-idl.c \
 	lib/vtep-idl.h
+
+if WIN32
+lib_libopenvswitch_la_SOURCES += \
+	lib/daemon-windows.c
+else
+lib_libopenvswitch_la_SOURCES += \
+	lib/daemon.c
+endif
+
 EXTRA_DIST += \
 	lib/stdio.h.in \
 	lib/string.h.in
@@ -328,6 +336,8 @@ MAN_FRAGMENTS += \
 	lib/memory-unixctl.man \
 	lib/ofp-version.man \
 	lib/ovs.tmac \
+	lib/service.man \
+	lib/service-syn.man \
 	lib/ssl-bootstrap.man \
 	lib/ssl-bootstrap-syn.man \
 	lib/ssl-peer-ca-cert.man \
diff --git a/lib/daemon-windows.c b/lib/daemon-windows.c
new file mode 100644
index 0000000..2ccf330
--- /dev/null
+++ b/lib/daemon-windows.c
@@ -0,0 +1,316 @@
+/*
+ * Copyright (c) 2014 Nicira, Inc.
+ *
+ * Licensed 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.
+ */
+
+#include <config.h>
+#include "daemon.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include "vlog.h"
+
+#pragma comment(lib, "advapi32")
+
+VLOG_DEFINE_THIS_MODULE(daemon);
+
+static bool detach;             /* Was --service specified? */
+static bool detached;           /* Have we already detached? */
+
+/* --service-monitor: Should the service be restarted if it dies
+ * unexpectedly? */
+static bool monitor;
+
+/* Handle to the Services Manager and the created service. */
+static SC_HANDLE manager, service;
+
+/* Handle to the status information structure for the current service. */
+SERVICE_STATUS_HANDLE hstatus;
+
+/* Hold the service's current status. */
+SERVICE_STATUS service_status;
+
+/* Hold the arguments sent to the main function. */
+static int sargc;
+static char ***sargvp;
+
+static void check_service(void);
+static void handle_scm_callback(void);
+static void init_service_status(void);
+static void set_config_failure_actions(void);
+
+extern int main(int argc, char *argv[]);
+
+void
+daemon_usage(void)
+{
+    printf(
+        "\nService options:\n"
+        "  --service               run in background as a service.\n"
+        "  --service-monitor       restart the service in case of an "
+                                   "unexpected failure. \n",
+        ovs_rundir(), program_name);
+}
+
+/* Registers the call-back and configures the actions in case of a failure
+ * with the windows services manager. */
+void
+service_start(int *argcp, char **argvp[])
+{
+    LPVOID msg_buf;
+    int argc = *argcp;
+    char **argv = *argvp;
+    int i;
+    SERVICE_TABLE_ENTRY service_table[] = {
+        {(LPTSTR)program_name, (LPSERVICE_MAIN_FUNCTION)main},
+        {NULL, NULL}
+    };
+
+    /* 'detached' is 'false' when service_start() is called the first time.
+     * It is 'true', when it is called the second time by the windows services
+     * manager. */
+    if (detached) {
+        init_service_status();
+
+        /* Register the control handler. This function is called by the service
+         * manager to stop the service. */
+        hstatus = RegisterServiceCtrlHandler(program_name,
+                                         (LPHANDLER_FUNCTION)control_handler);
+        if (!hstatus) {
+            ovs_lasterror_to_string((LPTSTR)&msg_buf);
+            VLOG_FATAL("Failed to register the service control handler (%s).",
+                        msg_buf);
+        }
+
+        if (monitor) {
+            set_config_failure_actions();
+        }
+
+        /* When the service control manager does the call back, it does not
+         * send the same arguments as sent to the main function during the
+         * service start. So, use the arguments passed over during the first
+         * time. */
+        *argcp = sargc;
+        *argvp = *sargvp;
+
+        /* XXX: Windows implementation cannot have a unixctl commands in the
+        * traditional sense of unix domain sockets. If an implementation is
+        * done that involves 'unixctl' vlog commands the following call is
+        * needed to make sure that the unixctl commands for vlog get
+        * registered in a daemon, even before the first log message. */
+        vlog_init();
+
+        return;
+    }
+
+    assert_single_threaded();
+
+    /* A reference to arguments passed to the main function the first time.
+     * We need it after the call-back from service control manager. */
+    sargc = argc;
+    sargvp = argvp;
+
+    /* We are only interested in the '--service' and '--service-monitor'
+     * options before the call-back from the service control manager. */
+    for (i = 0; i < argc; i ++) {
+        if (!strcmp(argv[i], "--service")) {
+            detach = true;
+        } else if (!strcmp(argv[i], "--service-monitor")) {
+            monitor = true;
+        }
+    }
+
+    /* If '--service' is not a command line option, run in foreground. */
+    if (!detach) {
+        return;
+    }
+
+    /* If we have been configured to run as a service, then that service
+     * should already have been created either manually or through a start up
+     * script. */
+    check_service();
+
+    detached = true;
+
+    /* StartServiceCtrlDispatcher blocks and returns after the service is
+     * stopped. */
+    if (!StartServiceCtrlDispatcher(service_table)) {
+        ovs_lasterror_to_string((LPTSTR)&msg_buf);
+        VLOG_FATAL("Failed at StartServiceCtrlDispatcher (%s)", msg_buf);
+    }
+    exit(0);
+}
+
+/* This function is registered with the windows services manager through
+ * a call to RegisterServiceCtrlHandler() and will be called by the windows
+ * services manager asynchronously to stop the service. */
+void
+control_handler(DWORD request)
+{
+    switch (request) {
+    case SERVICE_CONTROL_STOP:
+    case SERVICE_CONTROL_SHUTDOWN:
+        service_status.dwCurrentState = SERVICE_STOPPED;
+        service_status.dwWin32ExitCode = NO_ERROR;
+        break;
+
+    default:
+        break;
+    }
+}
+
+/* Return 'true' if the windows services manager has called the
+ * control_handler() and asked the program to terminate. */
+bool
+should_service_stop(void)
+{
+    if (detached && service_status.dwCurrentState != SERVICE_RUNNING) {
+        return true;
+    }
+    return false;
+}
+
+/* Set the service as stopped. The control manager will terminate the
+ * service soon after this call. Hence, this should ideally be the last
+ * call before termination. */
+void
+service_stop()
+{
+    service_status.dwCurrentState = SERVICE_STOPPED;
+    service_status.dwWin32ExitCode = NO_ERROR;
+    SetServiceStatus(hstatus, &service_status);
+}
+
+/* Call this function to signal that the daemon is ready. init_service()
+ * or control_handler() has already initalized the
+ * service_status.dwCurrentState .*/
+static void
+service_complete(void)
+{
+    if (hstatus) {
+        SetServiceStatus(hstatus, &service_status);
+    }
+}
+
+/* Check whether 'program_name' has been created as a service. */
+static void
+check_service()
+{
+    LPVOID msg_buf;
+
+    /* Establish a connection to the local service control manager. */
+    manager = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
+    if (!manager) {
+        ovs_lasterror_to_string((LPTSTR)&msg_buf);
+        VLOG_FATAL("Failed to open the service control manager (%s).",
+                   msg_buf);
+    }
+
+    service = OpenService(manager, program_name, SERVICE_ALL_ACCESS);
+    if (!service) {
+        ovs_lasterror_to_string((LPTSTR)&msg_buf);
+        VLOG_FATAL("Failed to open service (%s).", msg_buf);
+    }
+}
+
+/* Service status of a service can be checked asynchronously through
+ * tools like 'sc' or through windows services manager and is set
+ * through a call to SetServiceStatus(). */
+static void
+init_service_status()
+{
+    /* The service runs in its own process. */
+    service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+
+    /* The control codes the service accepts. */
+    service_status.dwControlsAccepted = SERVICE_ACCEPT_STOP |
+                                            SERVICE_ACCEPT_SHUTDOWN;
+
+    /* Initialize the current state as SERVICE_RUNNING. */
+    service_status.dwCurrentState = SERVICE_RUNNING;
+
+    /* The exit code to indicate if there was an error. */
+    service_status.dwWin32ExitCode = NO_ERROR;
+
+    /* The checkpoint value the service increments periodically. Set as 0
+     * as we do not plan to periodically increment the value. */
+    service_status.dwCheckPoint = 0;
+
+    /* The estimated time required for the stop operation in ms. */
+    service_status.dwWaitHint = 1000;
+}
+
+/* In case of an unexpected termination, configure the action to be
+ * taken. */
+static void
+set_config_failure_actions()
+{
+    /* In case of a failure, restart the process the first two times
+     * After 'dwResetPeriod', the failure count is reset. */
+    SC_ACTION fail_action[3] = {
+        {SC_ACTION_RESTART, 0},
+        {SC_ACTION_RESTART, 0},
+        {SC_ACTION_NONE, 0}
+    };
+    SERVICE_FAILURE_ACTIONS service_fail_action;
+    LPVOID msg_buf;
+
+    /* Reset failure count after (in seconds). */
+    service_fail_action.dwResetPeriod = 10;
+
+    /* Reboot message. */
+    service_fail_action.lpRebootMsg = NULL;
+
+    /* The command line of the process. */
+    service_fail_action.lpCommand = NULL;
+
+    /* Number of elements in 'fail_actions'. */
+    service_fail_action.cActions = sizeof(fail_action)/sizeof(fail_action[0]);
+
+    /* A pointer to an array of SC_ACTION structures. */
+    service_fail_action.lpsaActions = fail_action;
+
+    if (!ChangeServiceConfig2(service, SERVICE_CONFIG_FAILURE_ACTIONS,
+                              &service_fail_action)) {
+        ovs_lasterror_to_string((LPTSTR)&msg_buf);
+        VLOG_FATAL("Failed to configure service fail actions (%s).", msg_buf);
+    }
+}
+
+
+/* Stub functions to handle daemonize related calls in non-windows platform. */
+bool
+get_detach()
+{
+    return false;
+}
+
+void
+daemon_save_fd(int fd OVS_UNUSED)
+{
+}
+
+void
+daemonize(void)
+{
+}
+
+void daemonize_start(void)
+{
+}
+
+void
+daemonize_complete(void)
+{
+    service_complete();
+}
diff --git a/lib/daemon.c b/lib/daemon.c
index ab579b6..f9290ef 100644
--- a/lib/daemon.c
+++ b/lib/daemon.c
@@ -717,3 +717,22 @@ check_already_running(void)
                    pidfile, ovs_strerror(-pid));
     }
 }
+
+
+/* stub functions for non-windows platform. */
+
+void
+service_start(int *argc OVS_UNUSED, char **argv[] OVS_UNUSED)
+{
+}
+
+void
+service_stop(void)
+{
+}
+
+bool
+should_service_stop(void)
+{
+    return false;
+}
diff --git a/lib/daemon.h b/lib/daemon.h
index 57f8514..ad57362 100644
--- a/lib/daemon.h
+++ b/lib/daemon.h
@@ -21,6 +21,7 @@
 #include <stdbool.h>
 #include <sys/types.h>
 
+#ifndef _WIN32
 #define DAEMON_OPTION_ENUMS                     \
     OPT_DETACH,                                 \
     OPT_NO_CHDIR,                               \
@@ -56,17 +57,39 @@
             daemon_set_monitor();               \
             break;
 
+void set_detach(void);
+void daemon_set_monitor(void);
 void set_pidfile(const char *name);
 void set_no_chdir(void);
-void set_detach(void);
+void ignore_existing_pidfile(void);
+pid_t read_pidfile(const char *name);
+#else
+#define DAEMON_OPTION_ENUMS                     \
+    OPT_SERVICE,                                \
+    OPT_SERVICE_MONITOR
+
+#define DAEMON_LONG_OPTIONS                                             \
+        {"service",            no_argument, NULL, OPT_SERVICE},         \
+        {"service-monitor",    no_argument, NULL, OPT_SERVICE_MONITOR}
+
+#define DAEMON_OPTION_HANDLERS                  \
+        case OPT_SERVICE:                       \
+            break;                              \
+                                                \
+        case OPT_SERVICE_MONITOR:               \
+            break;
+
+void control_handler(DWORD request);
+#endif /* _WIN32 */
+
 bool get_detach(void);
-void daemon_set_monitor(void);
 void daemon_save_fd(int fd);
 void daemonize(void);
 void daemonize_start(void);
 void daemonize_complete(void);
-void ignore_existing_pidfile(void);
 void daemon_usage(void);
-pid_t read_pidfile(const char *name);
+void service_start(int *argcp, char **argvp[]);
+void service_stop(void);
+bool should_service_stop(void);
 
 #endif /* daemon.h */
diff --git a/lib/service-syn.man b/lib/service-syn.man
new file mode 100644
index 0000000..d029f22
--- /dev/null
+++ b/lib/service-syn.man
@@ -0,0 +1,3 @@
+.IP "Service options:"
+[\fB\-\-service\fR]
+[\fB\-\-service\-monitor\fR]
diff --git a/lib/service.man b/lib/service.man
new file mode 100644
index 0000000..c7927af
--- /dev/null
+++ b/lib/service.man
@@ -0,0 +1,11 @@
+.TP
+\fB\-\-service\fR
+Causes \fB\*(PN\fR to run as a service in the background. The service
+should already have been created through external tools like \fBSC.exe\fR.
+.
+.TP
+\fB\-\-service\-monitor\fR
+Causes the \fB\*(PN\fR service to be automatically restarted by the windows
+services manager if the service dies or exits for unexpected reasons.
+.IP
+When \fB\-\-service\fR is not specified, this option has no effect.
-- 
1.7.9.5




More information about the dev mailing list