]>
Commit | Line | Data |
---|---|---|
fda546bd | 1 | /* |
133fb2c7 | 2 | * Copyright (c) 2014, 2017 Nicira, Inc. |
fda546bd GS |
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" | |
3834bcf2 | 19 | #include "daemon-private.h" |
fda546bd | 20 | #include <stdio.h> |
568b9dc8 | 21 | #include <io.h> |
fda546bd | 22 | #include <stdlib.h> |
133fb2c7 | 23 | #include <unistd.h> |
3f97b664 | 24 | #include "dirs.h" |
133fb2c7 | 25 | #include "fatal-signal.h" |
94a72fd6 | 26 | #include "ovs-thread.h" |
fd016ae3 | 27 | #include "openvswitch/poll-loop.h" |
e6211adc | 28 | #include "openvswitch/vlog.h" |
fda546bd | 29 | |
a91dc444 | 30 | VLOG_DEFINE_THIS_MODULE(daemon_windows); |
fda546bd | 31 | |
568b9dc8 PB |
32 | /* Constants for flock function */ |
33 | #define LOCK_SHARED 0x0 /* Shared lock. */ | |
34 | #define LOCK_UNLOCK 0x80000000 /* Unlock. Custom value. */ | |
35 | ||
69b17834 GS |
36 | static bool service_create; /* Was --service specified? */ |
37 | static bool service_started; /* Have we dispatched service to start? */ | |
fda546bd GS |
38 | |
39 | /* --service-monitor: Should the service be restarted if it dies | |
40 | * unexpectedly? */ | |
41 | static bool monitor; | |
42 | ||
3834bcf2 GS |
43 | bool detach; /* Was --detach specified? */ |
44 | static bool detached; /* Running as the child process. */ | |
45 | static HANDLE write_handle; /* End of pipe to write to parent. */ | |
4ec4776c | 46 | |
3834bcf2 GS |
47 | char *pidfile; /* --pidfile: Name of pidfile (null if none). */ |
48 | static FILE *filep_pidfile; /* File pointer to access the pidfile. */ | |
a9e9db79 | 49 | |
fda546bd GS |
50 | /* Handle to the Services Manager and the created service. */ |
51 | static SC_HANDLE manager, service; | |
52 | ||
53 | /* Handle to the status information structure for the current service. */ | |
54 | static SERVICE_STATUS_HANDLE hstatus; | |
55 | ||
56 | /* Hold the service's current status. */ | |
57 | static SERVICE_STATUS service_status; | |
58 | ||
59 | /* Handle to an event object used to wakeup from poll_block(). */ | |
60 | static HANDLE wevent; | |
61 | ||
62 | /* Hold the arguments sent to the main function. */ | |
63 | static int sargc; | |
64 | static char ***sargvp; | |
65 | ||
66 | static void check_service(void); | |
67 | static void handle_scm_callback(void); | |
68 | static void init_service_status(void); | |
69 | static void set_config_failure_actions(void); | |
70 | ||
4ec4776c GS |
71 | static bool detach_process(int argc, char *argv[]); |
72 | ||
fda546bd GS |
73 | extern int main(int argc, char *argv[]); |
74 | ||
75 | void | |
76 | daemon_usage(void) | |
77 | { | |
78 | printf( | |
79 | "\nService options:\n" | |
80 | " --service run in background as a service.\n" | |
81 | " --service-monitor restart the service in case of an " | |
1b92d314 | 82 | "unexpected failure. \n"); |
fda546bd GS |
83 | } |
84 | ||
85 | /* Registers the call-back and configures the actions in case of a failure | |
86 | * with the Windows services manager. */ | |
87 | void | |
88 | service_start(int *argcp, char **argvp[]) | |
89 | { | |
90 | int argc = *argcp; | |
91 | char **argv = *argvp; | |
92 | int i; | |
93 | SERVICE_TABLE_ENTRY service_table[] = { | |
94 | {(LPTSTR)program_name, (LPSERVICE_MAIN_FUNCTION)main}, | |
95 | {NULL, NULL} | |
96 | }; | |
97 | ||
4ec4776c GS |
98 | /* If one of the command line option is "--detach", we create |
99 | * a new process in case of parent, wait for child to start and exit. | |
100 | * In case of the child, we just return. We should not be creating a | |
101 | * service in either case. */ | |
102 | if (detach_process(argc, argv)) { | |
103 | return; | |
104 | } | |
105 | ||
69b17834 GS |
106 | /* 'service_started' is 'false' when service_start() is called the first |
107 | * time. It is 'true', when it is called the second time by the Windows | |
108 | * services manager. */ | |
109 | if (service_started) { | |
fda546bd GS |
110 | init_service_status(); |
111 | ||
112 | wevent = CreateEvent(NULL, TRUE, FALSE, NULL); | |
113 | if (!wevent) { | |
114 | char *msg_buf = ovs_lasterror_to_string(); | |
115 | VLOG_FATAL("Failed to create a event (%s).", msg_buf); | |
116 | } | |
117 | ||
1ca3348e | 118 | poll_wevent_wait(wevent); |
fda546bd GS |
119 | |
120 | /* Register the control handler. This function is called by the service | |
121 | * manager to stop the service. */ | |
122 | hstatus = RegisterServiceCtrlHandler(program_name, | |
123 | (LPHANDLER_FUNCTION)control_handler); | |
124 | if (!hstatus) { | |
125 | char *msg_buf = ovs_lasterror_to_string(); | |
126 | VLOG_FATAL("Failed to register the service control handler (%s).", | |
127 | msg_buf); | |
128 | } | |
129 | ||
130 | if (monitor) { | |
131 | set_config_failure_actions(); | |
132 | } | |
133 | ||
134 | /* When the service control manager does the call back, it does not | |
135 | * send the same arguments as sent to the main function during the | |
136 | * service start. So, use the arguments passed over during the first | |
137 | * time. */ | |
138 | *argcp = sargc; | |
139 | *argvp = *sargvp; | |
140 | ||
44820d2a AGS |
141 | /* Enable default error mode so we can take advantage of WER |
142 | * (Windows Error Reporting) crash dumps. | |
143 | * Being a service it does not allow for WER window pop-up. | |
144 | * XXX implement our on crash dump collection mechanism. */ | |
145 | SetErrorMode(0); | |
146 | ||
fda546bd GS |
147 | return; |
148 | } | |
149 | ||
150 | assert_single_threaded(); | |
151 | ||
152 | /* A reference to arguments passed to the main function the first time. | |
153 | * We need it after the call-back from service control manager. */ | |
154 | sargc = argc; | |
155 | sargvp = argvp; | |
156 | ||
157 | /* We are only interested in the '--service' and '--service-monitor' | |
158 | * options before the call-back from the service control manager. */ | |
159 | for (i = 0; i < argc; i ++) { | |
160 | if (!strcmp(argv[i], "--service")) { | |
69b17834 | 161 | service_create = true; |
fda546bd GS |
162 | } else if (!strcmp(argv[i], "--service-monitor")) { |
163 | monitor = true; | |
164 | } | |
165 | } | |
166 | ||
167 | /* If '--service' is not a command line option, run in foreground. */ | |
69b17834 | 168 | if (!service_create) { |
fda546bd GS |
169 | return; |
170 | } | |
171 | ||
172 | /* If we have been configured to run as a service, then that service | |
173 | * should already have been created either manually or through a start up | |
174 | * script. */ | |
175 | check_service(); | |
176 | ||
69b17834 | 177 | service_started = true; |
fda546bd GS |
178 | |
179 | /* StartServiceCtrlDispatcher blocks and returns after the service is | |
180 | * stopped. */ | |
181 | if (!StartServiceCtrlDispatcher(service_table)) { | |
182 | char *msg_buf = ovs_lasterror_to_string(); | |
183 | VLOG_FATAL("Failed at StartServiceCtrlDispatcher (%s)", msg_buf); | |
184 | } | |
185 | exit(0); | |
186 | } | |
187 | ||
188 | /* This function is registered with the Windows services manager through | |
189 | * a call to RegisterServiceCtrlHandler() and will be called by the Windows | |
190 | * services manager asynchronously to stop the service. */ | |
191 | void | |
192 | control_handler(DWORD request) | |
193 | { | |
194 | switch (request) { | |
195 | case SERVICE_CONTROL_STOP: | |
196 | case SERVICE_CONTROL_SHUTDOWN: | |
197 | service_status.dwCurrentState = SERVICE_STOPPED; | |
198 | service_status.dwWin32ExitCode = NO_ERROR; | |
199 | SetEvent(wevent); | |
e2d12c07 | 200 | SetServiceStatus(hstatus, &service_status); |
fda546bd GS |
201 | break; |
202 | ||
203 | default: | |
204 | break; | |
205 | } | |
206 | } | |
207 | ||
208 | /* Return 'true' if the Windows services manager has called the | |
209 | * control_handler() and asked the program to terminate. */ | |
210 | bool | |
211 | should_service_stop(void) | |
212 | { | |
69b17834 | 213 | if (service_started) { |
fda546bd GS |
214 | if (service_status.dwCurrentState != SERVICE_RUNNING) { |
215 | return true; | |
216 | } else { | |
1ca3348e | 217 | poll_wevent_wait(wevent); |
fda546bd GS |
218 | } |
219 | } | |
220 | return false; | |
221 | } | |
222 | ||
223 | /* Set the service as stopped. The control manager will terminate the | |
224 | * service soon after this call. Hence, this should ideally be the last | |
225 | * call before termination. */ | |
226 | void | |
227 | service_stop() | |
228 | { | |
02a514ef GS |
229 | if (!service_started) { |
230 | return; | |
231 | } | |
232 | fatal_signal_atexit_handler(); | |
233 | ||
fda546bd GS |
234 | ResetEvent(wevent); |
235 | CloseHandle(wevent); | |
236 | ||
237 | service_status.dwCurrentState = SERVICE_STOPPED; | |
238 | service_status.dwWin32ExitCode = NO_ERROR; | |
239 | SetServiceStatus(hstatus, &service_status); | |
240 | } | |
241 | ||
242 | /* Call this function to signal that the daemon is ready. init_service() | |
243 | * or control_handler() has already initalized/set the | |
244 | * service_status.dwCurrentState .*/ | |
245 | static void | |
246 | service_complete(void) | |
247 | { | |
248 | if (hstatus) { | |
249 | SetServiceStatus(hstatus, &service_status); | |
250 | } | |
251 | } | |
252 | ||
253 | /* Check whether 'program_name' has been created as a service. */ | |
254 | static void | |
255 | check_service() | |
256 | { | |
257 | /* Establish a connection to the local service control manager. */ | |
258 | manager = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE); | |
259 | if (!manager) { | |
260 | char *msg_buf = ovs_lasterror_to_string(); | |
261 | VLOG_FATAL("Failed to open the service control manager (%s).", | |
262 | msg_buf); | |
263 | } | |
264 | ||
265 | service = OpenService(manager, program_name, SERVICE_ALL_ACCESS); | |
266 | if (!service) { | |
267 | char *msg_buf = ovs_lasterror_to_string(); | |
268 | VLOG_FATAL("Failed to open service (%s).", msg_buf); | |
269 | } | |
270 | } | |
271 | ||
272 | /* Service status of a service can be checked asynchronously through | |
273 | * tools like 'sc' or through Windows services manager and is set | |
274 | * through a call to SetServiceStatus(). */ | |
275 | static void | |
276 | init_service_status() | |
277 | { | |
278 | /* The service runs in its own process. */ | |
279 | service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; | |
280 | ||
281 | /* The control codes the service accepts. */ | |
282 | service_status.dwControlsAccepted = SERVICE_ACCEPT_STOP | | |
283 | SERVICE_ACCEPT_SHUTDOWN; | |
284 | ||
285 | /* Initialize the current state as SERVICE_RUNNING. */ | |
286 | service_status.dwCurrentState = SERVICE_RUNNING; | |
287 | ||
288 | /* The exit code to indicate if there was an error. */ | |
289 | service_status.dwWin32ExitCode = NO_ERROR; | |
290 | ||
291 | /* The checkpoint value the service increments periodically. Set as 0 | |
292 | * as we do not plan to periodically increment the value. */ | |
293 | service_status.dwCheckPoint = 0; | |
294 | ||
295 | /* The estimated time required for the stop operation in ms. */ | |
296 | service_status.dwWaitHint = 1000; | |
297 | } | |
298 | ||
299 | /* In case of an unexpected termination, configure the action to be | |
300 | * taken. */ | |
301 | static void | |
302 | set_config_failure_actions() | |
303 | { | |
304 | /* In case of a failure, restart the process the first two times | |
305 | * After 'dwResetPeriod', the failure count is reset. */ | |
306 | SC_ACTION fail_action[3] = { | |
307 | {SC_ACTION_RESTART, 0}, | |
308 | {SC_ACTION_RESTART, 0}, | |
309 | {SC_ACTION_NONE, 0} | |
310 | }; | |
311 | SERVICE_FAILURE_ACTIONS service_fail_action; | |
312 | ||
313 | /* Reset failure count after (in seconds). */ | |
314 | service_fail_action.dwResetPeriod = 10; | |
315 | ||
316 | /* Reboot message. */ | |
317 | service_fail_action.lpRebootMsg = NULL; | |
318 | ||
319 | /* The command line of the process. */ | |
320 | service_fail_action.lpCommand = NULL; | |
321 | ||
322 | /* Number of elements in 'fail_actions'. */ | |
323 | service_fail_action.cActions = sizeof(fail_action)/sizeof(fail_action[0]); | |
324 | ||
325 | /* A pointer to an array of SC_ACTION structures. */ | |
326 | service_fail_action.lpsaActions = fail_action; | |
327 | ||
328 | if (!ChangeServiceConfig2(service, SERVICE_CONFIG_FAILURE_ACTIONS, | |
329 | &service_fail_action)) { | |
330 | char *msg_buf = ovs_lasterror_to_string(); | |
331 | VLOG_FATAL("Failed to configure service fail actions (%s).", msg_buf); | |
332 | } | |
333 | } | |
334 | ||
4ec4776c GS |
335 | /* When a daemon is passed the --detach option, we create a new |
336 | * process and pass an additional non-documented option called --pipe-handle. | |
337 | * Through this option, the parent passes one end of a pipe handle. */ | |
338 | void | |
339 | set_pipe_handle(const char *pipe_handle) | |
340 | { | |
341 | write_handle = (HANDLE) atoi(pipe_handle); | |
342 | } | |
343 | ||
344 | /* If one of the command line option is "--detach", creates | |
345 | * a new process in case of parent, waits for child to start and exits. | |
346 | * In case of the child, returns. */ | |
347 | static bool | |
348 | detach_process(int argc, char *argv[]) | |
349 | { | |
350 | SECURITY_ATTRIBUTES sa; | |
351 | STARTUPINFO si; | |
352 | PROCESS_INFORMATION pi; | |
353 | HANDLE read_pipe, write_pipe; | |
354 | char *buffer; | |
355 | int error, i; | |
356 | char ch; | |
357 | ||
358 | /* We are only interested in the '--detach' and '--pipe-handle'. */ | |
359 | for (i = 0; i < argc; i ++) { | |
360 | if (!strcmp(argv[i], "--detach")) { | |
361 | detach = true; | |
362 | } else if (!strncmp(argv[i], "--pipe-handle", 13)) { | |
363 | /* If running as a child, return. */ | |
364 | detached = true; | |
365 | return true; | |
366 | } | |
367 | } | |
368 | ||
369 | /* Nothing to do if the option --detach is not set. */ | |
370 | if (!detach) { | |
371 | return false; | |
372 | } | |
373 | ||
374 | /* Set the security attribute such that a process created will | |
375 | * inherit the pipe handles. */ | |
376 | sa.nLength = sizeof(sa); | |
377 | sa.lpSecurityDescriptor = NULL; | |
378 | sa.bInheritHandle = TRUE; | |
379 | ||
380 | /* Create an anonymous pipe to communicate with the child. */ | |
381 | error = CreatePipe(&read_pipe, &write_pipe, &sa, 0); | |
382 | if (!error) { | |
383 | VLOG_FATAL("CreatePipe failed (%s)", ovs_lasterror_to_string()); | |
384 | } | |
385 | ||
386 | GetStartupInfo(&si); | |
387 | ||
388 | /* To the child, we pass an extra argument '--pipe-handle=write_pipe' */ | |
389 | buffer = xasprintf("%s %s=%ld", GetCommandLine(), "--pipe-handle", | |
390 | write_pipe); | |
391 | ||
392 | /* Create a detached child */ | |
393 | error = CreateProcess(NULL, buffer, NULL, NULL, TRUE, DETACHED_PROCESS, | |
394 | NULL, NULL, &si, &pi); | |
395 | if (!error) { | |
396 | VLOG_FATAL("CreateProcess failed (%s)", ovs_lasterror_to_string()); | |
397 | } | |
398 | ||
399 | /* Close one end of the pipe in the parent. */ | |
400 | CloseHandle(write_pipe); | |
401 | ||
402 | /* Block and wait for child to say it is ready. */ | |
403 | error = ReadFile(read_pipe, &ch, 1, NULL, NULL); | |
404 | if (!error) { | |
405 | VLOG_FATAL("Failed to read from child (%s)", | |
406 | ovs_lasterror_to_string()); | |
407 | } | |
408 | /* The child has successfully started and is ready. */ | |
409 | exit(0); | |
410 | } | |
411 | ||
568b9dc8 PB |
412 | static void |
413 | flock(FILE* fd, int operation) | |
414 | { | |
415 | HANDLE hFile; | |
416 | OVERLAPPED ov = {0}; | |
417 | ||
418 | hFile = (HANDLE)_get_osfhandle(fileno(fd)); | |
419 | if (hFile == INVALID_HANDLE_VALUE) { | |
420 | VLOG_FATAL("Failed to get PID file handle (%s).", | |
9364ae65 | 421 | ovs_strerror(errno)); |
568b9dc8 PB |
422 | } |
423 | ||
424 | if (operation & LOCK_UNLOCK) { | |
425 | if (UnlockFileEx(hFile, 0, 1, 0, &ov) == 0) { | |
426 | VLOG_FATAL("Failed to unlock PID file (%s).", | |
427 | ovs_lasterror_to_string()); | |
428 | } | |
429 | } else { | |
430 | /* Use LOCKFILE_FAIL_IMMEDIATELY flag to avoid hang of another daemon that tries to | |
431 | acquire exclusive lock over the same PID file */ | |
432 | if (LockFileEx(hFile, operation | LOCKFILE_FAIL_IMMEDIATELY, | |
433 | 0, 1, 0, &ov) == FALSE) { | |
434 | VLOG_FATAL("Failed to lock PID file (%s).", | |
435 | ovs_lasterror_to_string()); | |
436 | } | |
437 | } | |
438 | } | |
439 | ||
a9e9db79 GS |
440 | static void |
441 | unlink_pidfile(void) | |
442 | { | |
443 | if (filep_pidfile) { | |
568b9dc8 PB |
444 | /* Remove the shared lock on file */ |
445 | flock(filep_pidfile, LOCK_UNLOCK); | |
a9e9db79 GS |
446 | fclose(filep_pidfile); |
447 | } | |
448 | if (pidfile) { | |
449 | unlink(pidfile); | |
450 | } | |
451 | } | |
452 | ||
453 | /* If a pidfile has been configured, creates it and stores the running | |
454 | * process's pid in it. Ensures that the pidfile will be deleted when the | |
455 | * process exits. */ | |
456 | static void | |
457 | make_pidfile(void) | |
458 | { | |
459 | int error; | |
460 | ||
461 | error = GetFileAttributes(pidfile); | |
462 | if (error != INVALID_FILE_ATTRIBUTES) { | |
463 | /* pidfile exists. Try to unlink() it. */ | |
464 | error = unlink(pidfile); | |
465 | if (error) { | |
466 | VLOG_FATAL("Failed to delete existing pidfile %s (%s)", pidfile, | |
467 | ovs_strerror(errno)); | |
468 | } | |
469 | } | |
470 | ||
471 | filep_pidfile = fopen(pidfile, "w"); | |
472 | if (filep_pidfile == NULL) { | |
473 | VLOG_FATAL("failed to open %s (%s)", pidfile, ovs_strerror(errno)); | |
474 | } | |
475 | ||
568b9dc8 PB |
476 | flock(filep_pidfile, LOCKFILE_EXCLUSIVE_LOCK); |
477 | ||
a9e9db79 GS |
478 | fatal_signal_add_hook(unlink_pidfile, NULL, NULL, true); |
479 | ||
133fb2c7 | 480 | fprintf(filep_pidfile, "%ld\n", (long int) getpid()); |
a9e9db79 GS |
481 | if (fflush(filep_pidfile) == EOF) { |
482 | VLOG_FATAL("Failed to write into the pidfile %s", pidfile); | |
483 | } | |
484 | ||
568b9dc8 PB |
485 | flock(filep_pidfile, LOCK_SHARED); |
486 | /* This will remove the exclusive lock. The shared lock will remain */ | |
487 | flock(filep_pidfile, LOCK_UNLOCK); | |
488 | ||
a9e9db79 | 489 | /* Don't close the pidfile till the process exits. */ |
fda546bd GS |
490 | } |
491 | ||
e91b927d AZ |
492 | void |
493 | daemonize_start(bool access_datapath OVS_UNUSED) | |
fda546bd | 494 | { |
a9e9db79 GS |
495 | if (pidfile) { |
496 | make_pidfile(); | |
497 | } | |
fda546bd GS |
498 | } |
499 | ||
500 | void | |
501 | daemonize_complete(void) | |
502 | { | |
4ec4776c GS |
503 | /* If running as a child because '--detach' option was specified, |
504 | * communicate with the parent to inform that the child is ready. */ | |
505 | if (detached) { | |
506 | int error; | |
d6bc33f3 GS |
507 | |
508 | close_standard_fds(); | |
509 | ||
4ec4776c GS |
510 | error = WriteFile(write_handle, "a", 1, NULL, NULL); |
511 | if (!error) { | |
512 | VLOG_FATAL("Failed to communicate with the parent (%s)", | |
513 | ovs_lasterror_to_string()); | |
514 | } | |
515 | } | |
516 | ||
fda546bd GS |
517 | service_complete(); |
518 | } | |
a9e9db79 | 519 | |
e91b927d AZ |
520 | void |
521 | daemon_become_new_user(bool access_datapath OVS_UNUSED) | |
522 | { | |
523 | } | |
524 | ||
a9e9db79 GS |
525 | /* Returns the file name that would be used for a pidfile if 'name' were |
526 | * provided to set_pidfile(). The caller must free the returned string. */ | |
3834bcf2 | 527 | char * |
a9e9db79 GS |
528 | make_pidfile_name(const char *name) |
529 | { | |
5b73d2c0 PB |
530 | if (name) { |
531 | if (strchr(name, ':')) { | |
532 | return xstrdup(name); | |
533 | } else { | |
534 | return xasprintf("%s/%s", ovs_rundir(), name); | |
535 | } | |
a9e9db79 GS |
536 | } else { |
537 | return xasprintf("%s/%s.pid", ovs_rundir(), program_name); | |
538 | } | |
539 | } | |
e91b927d AZ |
540 | |
541 | void | |
542 | daemon_set_new_user(const char *user_spec OVS_UNUSED) | |
543 | { | |
544 | VLOG_FATAL("--user options is not currently supported."); | |
545 | } |