daemon-windows: Ability to handle windows service calls.
[sliver-openvswitch.git] / lib / daemon-windows.c
1 /*
2  * Copyright (c) 2014 Nicira, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at:
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <config.h>
18 #include "daemon.h"
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include "poll-loop.h"
22 #include "vlog.h"
23
24 #pragma comment(lib, "advapi32")
25
26 VLOG_DEFINE_THIS_MODULE(daemon);
27
28 static bool detach;             /* Was --service specified? */
29 static bool detached;           /* Have we already detached? */
30
31 /* --service-monitor: Should the service be restarted if it dies
32  * unexpectedly? */
33 static bool monitor;
34
35 /* Handle to the Services Manager and the created service. */
36 static SC_HANDLE manager, service;
37
38 /* Handle to the status information structure for the current service. */
39 static SERVICE_STATUS_HANDLE hstatus;
40
41 /* Hold the service's current status. */
42 static SERVICE_STATUS service_status;
43
44 /* Handle to an event object used to wakeup from poll_block(). */
45 static HANDLE wevent;
46
47 /* Hold the arguments sent to the main function. */
48 static int sargc;
49 static char ***sargvp;
50
51 static void check_service(void);
52 static void handle_scm_callback(void);
53 static void init_service_status(void);
54 static void set_config_failure_actions(void);
55
56 extern int main(int argc, char *argv[]);
57
58 void
59 daemon_usage(void)
60 {
61     printf(
62         "\nService options:\n"
63         "  --service               run in background as a service.\n"
64         "  --service-monitor       restart the service in case of an "
65                                    "unexpected failure. \n",
66         ovs_rundir(), program_name);
67 }
68
69 /* Registers the call-back and configures the actions in case of a failure
70  * with the Windows services manager. */
71 void
72 service_start(int *argcp, char **argvp[])
73 {
74     int argc = *argcp;
75     char **argv = *argvp;
76     int i;
77     SERVICE_TABLE_ENTRY service_table[] = {
78         {(LPTSTR)program_name, (LPSERVICE_MAIN_FUNCTION)main},
79         {NULL, NULL}
80     };
81
82     /* 'detached' is 'false' when service_start() is called the first time.
83      * It is 'true', when it is called the second time by the Windows services
84      * manager. */
85     if (detached) {
86         init_service_status();
87
88         wevent = CreateEvent(NULL, TRUE, FALSE, NULL);
89         if (!wevent) {
90             char *msg_buf = ovs_lasterror_to_string();
91             VLOG_FATAL("Failed to create a event (%s).", msg_buf);
92         }
93
94         poll_fd_wait_event(0, wevent, POLLIN);
95
96         /* Register the control handler. This function is called by the service
97          * manager to stop the service. */
98         hstatus = RegisterServiceCtrlHandler(program_name,
99                                          (LPHANDLER_FUNCTION)control_handler);
100         if (!hstatus) {
101             char *msg_buf = ovs_lasterror_to_string();
102             VLOG_FATAL("Failed to register the service control handler (%s).",
103                         msg_buf);
104         }
105
106         if (monitor) {
107             set_config_failure_actions();
108         }
109
110         /* When the service control manager does the call back, it does not
111          * send the same arguments as sent to the main function during the
112          * service start. So, use the arguments passed over during the first
113          * time. */
114         *argcp = sargc;
115         *argvp = *sargvp;
116
117         /* XXX: Windows implementation cannot have a unixctl commands in the
118         * traditional sense of unix domain sockets. If an implementation is
119         * done that involves 'unixctl' vlog commands the following call is
120         * needed to make sure that the unixctl commands for vlog get
121         * registered in a daemon, even before the first log message. */
122         vlog_init();
123
124         return;
125     }
126
127     assert_single_threaded();
128
129     /* A reference to arguments passed to the main function the first time.
130      * We need it after the call-back from service control manager. */
131     sargc = argc;
132     sargvp = argvp;
133
134     /* We are only interested in the '--service' and '--service-monitor'
135      * options before the call-back from the service control manager. */
136     for (i = 0; i < argc; i ++) {
137         if (!strcmp(argv[i], "--service")) {
138             detach = true;
139         } else if (!strcmp(argv[i], "--service-monitor")) {
140             monitor = true;
141         }
142     }
143
144     /* If '--service' is not a command line option, run in foreground. */
145     if (!detach) {
146         return;
147     }
148
149     /* If we have been configured to run as a service, then that service
150      * should already have been created either manually or through a start up
151      * script. */
152     check_service();
153
154     detached = true;
155
156     /* StartServiceCtrlDispatcher blocks and returns after the service is
157      * stopped. */
158     if (!StartServiceCtrlDispatcher(service_table)) {
159         char *msg_buf = ovs_lasterror_to_string();
160         VLOG_FATAL("Failed at StartServiceCtrlDispatcher (%s)", msg_buf);
161     }
162     exit(0);
163 }
164
165 /* This function is registered with the Windows services manager through
166  * a call to RegisterServiceCtrlHandler() and will be called by the Windows
167  * services manager asynchronously to stop the service. */
168 void
169 control_handler(DWORD request)
170 {
171     switch (request) {
172     case SERVICE_CONTROL_STOP:
173     case SERVICE_CONTROL_SHUTDOWN:
174         service_status.dwCurrentState = SERVICE_STOPPED;
175         service_status.dwWin32ExitCode = NO_ERROR;
176         SetEvent(wevent);
177         break;
178
179     default:
180         break;
181     }
182 }
183
184 /* Return 'true' if the Windows services manager has called the
185  * control_handler() and asked the program to terminate. */
186 bool
187 should_service_stop(void)
188 {
189     if (detached) {
190         if (service_status.dwCurrentState != SERVICE_RUNNING) {
191             return true;
192         } else {
193             poll_fd_wait_event(0, wevent, POLLIN);
194         }
195     }
196     return false;
197 }
198
199 /* Set the service as stopped. The control manager will terminate the
200  * service soon after this call. Hence, this should ideally be the last
201  * call before termination. */
202 void
203 service_stop()
204 {
205     ResetEvent(wevent);
206     CloseHandle(wevent);
207
208     service_status.dwCurrentState = SERVICE_STOPPED;
209     service_status.dwWin32ExitCode = NO_ERROR;
210     SetServiceStatus(hstatus, &service_status);
211 }
212
213 /* Call this function to signal that the daemon is ready. init_service()
214  * or control_handler() has already initalized/set the
215  * service_status.dwCurrentState .*/
216 static void
217 service_complete(void)
218 {
219     if (hstatus) {
220         SetServiceStatus(hstatus, &service_status);
221     }
222 }
223
224 /* Check whether 'program_name' has been created as a service. */
225 static void
226 check_service()
227 {
228     /* Establish a connection to the local service control manager. */
229     manager = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
230     if (!manager) {
231         char *msg_buf = ovs_lasterror_to_string();
232         VLOG_FATAL("Failed to open the service control manager (%s).",
233                    msg_buf);
234     }
235
236     service = OpenService(manager, program_name, SERVICE_ALL_ACCESS);
237     if (!service) {
238         char *msg_buf = ovs_lasterror_to_string();
239         VLOG_FATAL("Failed to open service (%s).", msg_buf);
240     }
241 }
242
243 /* Service status of a service can be checked asynchronously through
244  * tools like 'sc' or through Windows services manager and is set
245  * through a call to SetServiceStatus(). */
246 static void
247 init_service_status()
248 {
249     /* The service runs in its own process. */
250     service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
251
252     /* The control codes the service accepts. */
253     service_status.dwControlsAccepted = SERVICE_ACCEPT_STOP |
254                                             SERVICE_ACCEPT_SHUTDOWN;
255
256     /* Initialize the current state as SERVICE_RUNNING. */
257     service_status.dwCurrentState = SERVICE_RUNNING;
258
259     /* The exit code to indicate if there was an error. */
260     service_status.dwWin32ExitCode = NO_ERROR;
261
262     /* The checkpoint value the service increments periodically. Set as 0
263      * as we do not plan to periodically increment the value. */
264     service_status.dwCheckPoint = 0;
265
266     /* The estimated time required for the stop operation in ms. */
267     service_status.dwWaitHint = 1000;
268 }
269
270 /* In case of an unexpected termination, configure the action to be
271  * taken. */
272 static void
273 set_config_failure_actions()
274 {
275     /* In case of a failure, restart the process the first two times
276      * After 'dwResetPeriod', the failure count is reset. */
277     SC_ACTION fail_action[3] = {
278         {SC_ACTION_RESTART, 0},
279         {SC_ACTION_RESTART, 0},
280         {SC_ACTION_NONE, 0}
281     };
282     SERVICE_FAILURE_ACTIONS service_fail_action;
283
284     /* Reset failure count after (in seconds). */
285     service_fail_action.dwResetPeriod = 10;
286
287     /* Reboot message. */
288     service_fail_action.lpRebootMsg = NULL;
289
290     /* The command line of the process. */
291     service_fail_action.lpCommand = NULL;
292
293     /* Number of elements in 'fail_actions'. */
294     service_fail_action.cActions = sizeof(fail_action)/sizeof(fail_action[0]);
295
296     /* A pointer to an array of SC_ACTION structures. */
297     service_fail_action.lpsaActions = fail_action;
298
299     if (!ChangeServiceConfig2(service, SERVICE_CONFIG_FAILURE_ACTIONS,
300                               &service_fail_action)) {
301         char *msg_buf = ovs_lasterror_to_string();
302         VLOG_FATAL("Failed to configure service fail actions (%s).", msg_buf);
303     }
304 }
305
306 \f
307 /* Stub functions to handle daemonize related calls in non-windows platform. */
308 bool
309 get_detach()
310 {
311     return false;
312 }
313
314 void
315 daemon_save_fd(int fd OVS_UNUSED)
316 {
317 }
318
319 void
320 daemonize(void)
321 {
322 }
323
324 void daemonize_start(void)
325 {
326 }
327
328 void
329 daemonize_complete(void)
330 {
331     service_complete();
332 }