]> git.proxmox.com Git - mirror_ovs.git/blob - lib/daemon-windows.c
daemon-windows: Implement --detach option for Windows.
[mirror_ovs.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 VLOG_DEFINE_THIS_MODULE(daemon);
25
26 static bool service_create; /* Was --service specified? */
27 static bool service_started; /* Have we dispatched service to start? */
28
29 /* --service-monitor: Should the service be restarted if it dies
30 * unexpectedly? */
31 static bool monitor;
32
33 static bool detach; /* Was --detach specified? */
34 static bool detached; /* Running as the child process. */
35 static HANDLE write_handle; /* End of pipe to write to parent. */
36
37 /* Handle to the Services Manager and the created service. */
38 static SC_HANDLE manager, service;
39
40 /* Handle to the status information structure for the current service. */
41 static SERVICE_STATUS_HANDLE hstatus;
42
43 /* Hold the service's current status. */
44 static SERVICE_STATUS service_status;
45
46 /* Handle to an event object used to wakeup from poll_block(). */
47 static HANDLE wevent;
48
49 /* Hold the arguments sent to the main function. */
50 static int sargc;
51 static char ***sargvp;
52
53 static void check_service(void);
54 static void handle_scm_callback(void);
55 static void init_service_status(void);
56 static void set_config_failure_actions(void);
57
58 static bool detach_process(int argc, char *argv[]);
59
60 extern int main(int argc, char *argv[]);
61
62 void
63 daemon_usage(void)
64 {
65 printf(
66 "\nService options:\n"
67 " --service run in background as a service.\n"
68 " --service-monitor restart the service in case of an "
69 "unexpected failure. \n",
70 ovs_rundir(), program_name);
71 }
72
73 /* Registers the call-back and configures the actions in case of a failure
74 * with the Windows services manager. */
75 void
76 service_start(int *argcp, char **argvp[])
77 {
78 int argc = *argcp;
79 char **argv = *argvp;
80 int i;
81 SERVICE_TABLE_ENTRY service_table[] = {
82 {(LPTSTR)program_name, (LPSERVICE_MAIN_FUNCTION)main},
83 {NULL, NULL}
84 };
85
86 /* If one of the command line option is "--detach", we create
87 * a new process in case of parent, wait for child to start and exit.
88 * In case of the child, we just return. We should not be creating a
89 * service in either case. */
90 if (detach_process(argc, argv)) {
91 return;
92 }
93
94 /* 'service_started' is 'false' when service_start() is called the first
95 * time. It is 'true', when it is called the second time by the Windows
96 * services manager. */
97 if (service_started) {
98 init_service_status();
99
100 wevent = CreateEvent(NULL, TRUE, FALSE, NULL);
101 if (!wevent) {
102 char *msg_buf = ovs_lasterror_to_string();
103 VLOG_FATAL("Failed to create a event (%s).", msg_buf);
104 }
105
106 poll_fd_wait_event(0, wevent, POLLIN);
107
108 /* Register the control handler. This function is called by the service
109 * manager to stop the service. */
110 hstatus = RegisterServiceCtrlHandler(program_name,
111 (LPHANDLER_FUNCTION)control_handler);
112 if (!hstatus) {
113 char *msg_buf = ovs_lasterror_to_string();
114 VLOG_FATAL("Failed to register the service control handler (%s).",
115 msg_buf);
116 }
117
118 if (monitor) {
119 set_config_failure_actions();
120 }
121
122 /* When the service control manager does the call back, it does not
123 * send the same arguments as sent to the main function during the
124 * service start. So, use the arguments passed over during the first
125 * time. */
126 *argcp = sargc;
127 *argvp = *sargvp;
128
129 /* XXX: Windows implementation cannot have a unixctl commands in the
130 * traditional sense of unix domain sockets. If an implementation is
131 * done that involves 'unixctl' vlog commands the following call is
132 * needed to make sure that the unixctl commands for vlog get
133 * registered in a daemon, even before the first log message. */
134 vlog_init();
135
136 return;
137 }
138
139 assert_single_threaded();
140
141 /* A reference to arguments passed to the main function the first time.
142 * We need it after the call-back from service control manager. */
143 sargc = argc;
144 sargvp = argvp;
145
146 /* We are only interested in the '--service' and '--service-monitor'
147 * options before the call-back from the service control manager. */
148 for (i = 0; i < argc; i ++) {
149 if (!strcmp(argv[i], "--service")) {
150 service_create = true;
151 } else if (!strcmp(argv[i], "--service-monitor")) {
152 monitor = true;
153 }
154 }
155
156 /* If '--service' is not a command line option, run in foreground. */
157 if (!service_create) {
158 return;
159 }
160
161 /* If we have been configured to run as a service, then that service
162 * should already have been created either manually or through a start up
163 * script. */
164 check_service();
165
166 service_started = true;
167
168 /* StartServiceCtrlDispatcher blocks and returns after the service is
169 * stopped. */
170 if (!StartServiceCtrlDispatcher(service_table)) {
171 char *msg_buf = ovs_lasterror_to_string();
172 VLOG_FATAL("Failed at StartServiceCtrlDispatcher (%s)", msg_buf);
173 }
174 exit(0);
175 }
176
177 /* This function is registered with the Windows services manager through
178 * a call to RegisterServiceCtrlHandler() and will be called by the Windows
179 * services manager asynchronously to stop the service. */
180 void
181 control_handler(DWORD request)
182 {
183 switch (request) {
184 case SERVICE_CONTROL_STOP:
185 case SERVICE_CONTROL_SHUTDOWN:
186 service_status.dwCurrentState = SERVICE_STOPPED;
187 service_status.dwWin32ExitCode = NO_ERROR;
188 SetEvent(wevent);
189 break;
190
191 default:
192 break;
193 }
194 }
195
196 /* Return 'true' if the Windows services manager has called the
197 * control_handler() and asked the program to terminate. */
198 bool
199 should_service_stop(void)
200 {
201 if (service_started) {
202 if (service_status.dwCurrentState != SERVICE_RUNNING) {
203 return true;
204 } else {
205 poll_fd_wait_event(0, wevent, POLLIN);
206 }
207 }
208 return false;
209 }
210
211 /* Set the service as stopped. The control manager will terminate the
212 * service soon after this call. Hence, this should ideally be the last
213 * call before termination. */
214 void
215 service_stop()
216 {
217 ResetEvent(wevent);
218 CloseHandle(wevent);
219
220 service_status.dwCurrentState = SERVICE_STOPPED;
221 service_status.dwWin32ExitCode = NO_ERROR;
222 SetServiceStatus(hstatus, &service_status);
223 }
224
225 /* Call this function to signal that the daemon is ready. init_service()
226 * or control_handler() has already initalized/set the
227 * service_status.dwCurrentState .*/
228 static void
229 service_complete(void)
230 {
231 if (hstatus) {
232 SetServiceStatus(hstatus, &service_status);
233 }
234 }
235
236 /* Check whether 'program_name' has been created as a service. */
237 static void
238 check_service()
239 {
240 /* Establish a connection to the local service control manager. */
241 manager = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
242 if (!manager) {
243 char *msg_buf = ovs_lasterror_to_string();
244 VLOG_FATAL("Failed to open the service control manager (%s).",
245 msg_buf);
246 }
247
248 service = OpenService(manager, program_name, SERVICE_ALL_ACCESS);
249 if (!service) {
250 char *msg_buf = ovs_lasterror_to_string();
251 VLOG_FATAL("Failed to open service (%s).", msg_buf);
252 }
253 }
254
255 /* Service status of a service can be checked asynchronously through
256 * tools like 'sc' or through Windows services manager and is set
257 * through a call to SetServiceStatus(). */
258 static void
259 init_service_status()
260 {
261 /* The service runs in its own process. */
262 service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
263
264 /* The control codes the service accepts. */
265 service_status.dwControlsAccepted = SERVICE_ACCEPT_STOP |
266 SERVICE_ACCEPT_SHUTDOWN;
267
268 /* Initialize the current state as SERVICE_RUNNING. */
269 service_status.dwCurrentState = SERVICE_RUNNING;
270
271 /* The exit code to indicate if there was an error. */
272 service_status.dwWin32ExitCode = NO_ERROR;
273
274 /* The checkpoint value the service increments periodically. Set as 0
275 * as we do not plan to periodically increment the value. */
276 service_status.dwCheckPoint = 0;
277
278 /* The estimated time required for the stop operation in ms. */
279 service_status.dwWaitHint = 1000;
280 }
281
282 /* In case of an unexpected termination, configure the action to be
283 * taken. */
284 static void
285 set_config_failure_actions()
286 {
287 /* In case of a failure, restart the process the first two times
288 * After 'dwResetPeriod', the failure count is reset. */
289 SC_ACTION fail_action[3] = {
290 {SC_ACTION_RESTART, 0},
291 {SC_ACTION_RESTART, 0},
292 {SC_ACTION_NONE, 0}
293 };
294 SERVICE_FAILURE_ACTIONS service_fail_action;
295
296 /* Reset failure count after (in seconds). */
297 service_fail_action.dwResetPeriod = 10;
298
299 /* Reboot message. */
300 service_fail_action.lpRebootMsg = NULL;
301
302 /* The command line of the process. */
303 service_fail_action.lpCommand = NULL;
304
305 /* Number of elements in 'fail_actions'. */
306 service_fail_action.cActions = sizeof(fail_action)/sizeof(fail_action[0]);
307
308 /* A pointer to an array of SC_ACTION structures. */
309 service_fail_action.lpsaActions = fail_action;
310
311 if (!ChangeServiceConfig2(service, SERVICE_CONFIG_FAILURE_ACTIONS,
312 &service_fail_action)) {
313 char *msg_buf = ovs_lasterror_to_string();
314 VLOG_FATAL("Failed to configure service fail actions (%s).", msg_buf);
315 }
316 }
317
318 /* When a daemon is passed the --detach option, we create a new
319 * process and pass an additional non-documented option called --pipe-handle.
320 * Through this option, the parent passes one end of a pipe handle. */
321 void
322 set_pipe_handle(const char *pipe_handle)
323 {
324 write_handle = (HANDLE) atoi(pipe_handle);
325 }
326
327 /* If one of the command line option is "--detach", creates
328 * a new process in case of parent, waits for child to start and exits.
329 * In case of the child, returns. */
330 static bool
331 detach_process(int argc, char *argv[])
332 {
333 SECURITY_ATTRIBUTES sa;
334 STARTUPINFO si;
335 PROCESS_INFORMATION pi;
336 HANDLE read_pipe, write_pipe;
337 char *buffer;
338 int error, i;
339 char ch;
340
341 /* We are only interested in the '--detach' and '--pipe-handle'. */
342 for (i = 0; i < argc; i ++) {
343 if (!strcmp(argv[i], "--detach")) {
344 detach = true;
345 } else if (!strncmp(argv[i], "--pipe-handle", 13)) {
346 /* If running as a child, return. */
347 detached = true;
348 return true;
349 }
350 }
351
352 /* Nothing to do if the option --detach is not set. */
353 if (!detach) {
354 return false;
355 }
356
357 /* Set the security attribute such that a process created will
358 * inherit the pipe handles. */
359 sa.nLength = sizeof(sa);
360 sa.lpSecurityDescriptor = NULL;
361 sa.bInheritHandle = TRUE;
362
363 /* Create an anonymous pipe to communicate with the child. */
364 error = CreatePipe(&read_pipe, &write_pipe, &sa, 0);
365 if (!error) {
366 VLOG_FATAL("CreatePipe failed (%s)", ovs_lasterror_to_string());
367 }
368
369 GetStartupInfo(&si);
370
371 /* To the child, we pass an extra argument '--pipe-handle=write_pipe' */
372 buffer = xasprintf("%s %s=%ld", GetCommandLine(), "--pipe-handle",
373 write_pipe);
374
375 /* Create a detached child */
376 error = CreateProcess(NULL, buffer, NULL, NULL, TRUE, DETACHED_PROCESS,
377 NULL, NULL, &si, &pi);
378 if (!error) {
379 VLOG_FATAL("CreateProcess failed (%s)", ovs_lasterror_to_string());
380 }
381
382 /* Close one end of the pipe in the parent. */
383 CloseHandle(write_pipe);
384
385 /* Block and wait for child to say it is ready. */
386 error = ReadFile(read_pipe, &ch, 1, NULL, NULL);
387 if (!error) {
388 VLOG_FATAL("Failed to read from child (%s)",
389 ovs_lasterror_to_string());
390 }
391 /* The child has successfully started and is ready. */
392 exit(0);
393 }
394
395 /* Will daemonize() really detach? */
396 bool
397 get_detach()
398 {
399 return detach;
400 }
401
402 void
403 daemon_save_fd(int fd OVS_UNUSED)
404 {
405 }
406
407 void
408 daemonize(void)
409 {
410 }
411
412 void daemonize_start(void)
413 {
414 }
415
416 void
417 daemonize_complete(void)
418 {
419 /* If running as a child because '--detach' option was specified,
420 * communicate with the parent to inform that the child is ready. */
421 if (detached) {
422 int error;
423 error = WriteFile(write_handle, "a", 1, NULL, NULL);
424 if (!error) {
425 VLOG_FATAL("Failed to communicate with the parent (%s)",
426 ovs_lasterror_to_string());
427 }
428 }
429
430 service_complete();
431 }