]> git.proxmox.com Git - mirror_frr.git/blame - watchfrr/watchfrr.c
lib: replace stderr with zlog in vty config load
[mirror_frr.git] / watchfrr / watchfrr.c
CommitLineData
8b886ca7 1/*
896014f4
DL
2 * Monitor status of frr daemons and restart if necessary.
3 *
4 * Copyright (C) 2004 Andrew J. Schorr
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; see the file COPYING; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
8b886ca7 19 */
20
a365534f 21#include <zebra.h>
8b886ca7 22#include <thread.h>
23#include <log.h>
52e66296 24#include <network.h>
8b886ca7 25#include <sigevent.h>
a365534f 26#include <lib/version.h>
95c4aff2 27#include "command.h"
87f44e2f 28#include "memory_vty.h"
4f04a76b 29#include "libfrr.h"
95c4aff2 30
6f594023 31#include <getopt.h>
a365534f 32#include <sys/un.h>
33#include <sys/wait.h>
837d16cc 34#include <memory.h>
651415bd 35#include <systemd.h>
8b886ca7 36
9473e340 37#include "watchfrr.h"
95c4aff2 38
8b886ca7 39#ifndef MIN
40#define MIN(X,Y) (((X) <= (Y)) ? (X) : (Y))
41#endif
42
43/* Macros to help randomize timers. */
44#define JITTER(X) ((random() % ((X)+1))-((X)/2))
45#define FUZZY(X) ((X)+JITTER((X)/20))
46
47#define DEFAULT_PERIOD 5
48#define DEFAULT_TIMEOUT 10
49#define DEFAULT_RESTART_TIMEOUT 20
50#define DEFAULT_LOGLEVEL LOG_INFO
51#define DEFAULT_MIN_RESTART 60
52#define DEFAULT_MAX_RESTART 600
9473e340
DS
53#ifdef PATH_WATCHFRR_PID
54#define DEFAULT_PIDFILE PATH_WATCHFRR_PID
6028df52 55#else
9473e340 56#define DEFAULT_PIDFILE STATEDIR "/watchfrr.pid"
6028df52 57#endif
16f6511e 58#ifdef DAEMON_VTY_DIR
59#define VTYDIR DAEMON_VTY_DIR
60#else
61#define VTYDIR STATEDIR
62#endif
8b886ca7 63
64#define PING_TOKEN "PING"
65
55c72803 66/* Needs to be global, referenced somewhere inside libfrr. */
8b886ca7 67struct thread_master *master;
68
f168b713 69static bool watch_only = false;
8b886ca7 70
a6810074
DL
71typedef enum {
72 PHASE_NONE = 0,
73 PHASE_STOPS_PENDING,
74 PHASE_WAITING_DOWN,
75 PHASE_ZEBRA_RESTART_PENDING,
76 PHASE_WAITING_ZEBRA_UP
8b886ca7 77} restart_phase_t;
78
a6810074
DL
79static const char *phase_str[] = {
80 "None",
81 "Stop jobs running",
82 "Waiting for other daemons to come down",
83 "Zebra restart job running",
84 "Waiting for zebra to come up",
85 "Start jobs running",
8b886ca7 86};
87
88#define PHASE_TIMEOUT (3*gs.restart_timeout)
89
a6810074
DL
90struct restart_info {
91 const char *name;
92 const char *what;
93 pid_t pid;
94 struct timeval time;
95 long interval;
96 struct thread *t_kill;
97 int kills;
098e240f 98};
99
a6810074 100static struct global_state {
a6810074
DL
101 restart_phase_t phase;
102 struct thread *t_phase_hanging;
103 const char *vtydir;
104 long period;
105 long timeout;
106 long restart_timeout;
107 long min_restart_interval;
108 long max_restart_interval;
a6810074
DL
109 struct daemon *daemons;
110 const char *restart_command;
111 const char *start_command;
112 const char *stop_command;
113 struct restart_info restart;
a6810074 114 int loglevel;
d62a17ae 115 struct daemon *special; /* points to zebra when doing phased restart */
a6810074
DL
116 int numdaemons;
117 int numpids;
d62a17ae 118 int numdown; /* # of daemons that are not UP or UNRESPONSIVE */
8b886ca7 119} gs = {
d62a17ae 120 .phase = PHASE_NONE,
121 .vtydir = VTYDIR,
122 .period = 1000 * DEFAULT_PERIOD,
123 .timeout = DEFAULT_TIMEOUT,
124 .restart_timeout = DEFAULT_RESTART_TIMEOUT,
125 .loglevel = DEFAULT_LOGLEVEL,
126 .min_restart_interval = DEFAULT_MIN_RESTART,
127 .max_restart_interval = DEFAULT_MAX_RESTART,
d62a17ae 128};
a6810074
DL
129
130typedef enum {
131 DAEMON_INIT,
132 DAEMON_DOWN,
133 DAEMON_CONNECTING,
134 DAEMON_UP,
135 DAEMON_UNRESPONSIVE
8b886ca7 136} daemon_state_t;
137
d62a17ae 138#define IS_UP(DMN) \
139 (((DMN)->state == DAEMON_UP) || ((DMN)->state == DAEMON_UNRESPONSIVE))
8b886ca7 140
a6810074 141static const char *state_str[] = {
d62a17ae 142 "Init", "Down", "Connecting", "Up", "Unresponsive",
8b886ca7 143};
144
145struct daemon {
a6810074
DL
146 const char *name;
147 daemon_state_t state;
148 int fd;
149 struct timeval echo_sent;
150 u_int connect_tries;
151 struct thread *t_wakeup;
152 struct thread *t_read;
153 struct thread *t_write;
154 struct daemon *next;
155 struct restart_info restart;
8b886ca7 156};
157
9272302b
DL
158#define OPTION_MINRESTART 2000
159#define OPTION_MAXRESTART 2001
f168b713 160#define OPTION_DRY 2002
9272302b 161
a6810074
DL
162static const struct option longopts[] = {
163 {"daemon", no_argument, NULL, 'd'},
164 {"statedir", required_argument, NULL, 'S'},
a6810074
DL
165 {"loglevel", required_argument, NULL, 'l'},
166 {"interval", required_argument, NULL, 'i'},
167 {"timeout", required_argument, NULL, 't'},
168 {"restart-timeout", required_argument, NULL, 'T'},
169 {"restart", required_argument, NULL, 'r'},
170 {"start-command", required_argument, NULL, 's'},
171 {"kill-command", required_argument, NULL, 'k'},
f168b713 172 {"dry", no_argument, NULL, OPTION_DRY},
d62a17ae 173 {"min-restart-interval", required_argument, NULL, OPTION_MINRESTART},
174 {"max-restart-interval", required_argument, NULL, OPTION_MAXRESTART},
a6810074
DL
175 {"pid-file", required_argument, NULL, 'p'},
176 {"blank-string", required_argument, NULL, 'b'},
177 {"help", no_argument, NULL, 'h'},
178 {"version", no_argument, NULL, 'v'},
d62a17ae 179 {NULL, 0, NULL, 0}};
8b886ca7 180
181static int try_connect(struct daemon *dmn);
182static int wakeup_send_echo(struct thread *t_wakeup);
183static void try_restart(struct daemon *dmn);
184static void phase_check(void);
185
4f04a76b
DL
186static const char *progname;
187static void printhelp(FILE *target)
8b886ca7 188{
d62a17ae 189 fprintf(target,
190 "Usage : %s [OPTION...] <daemon name> ...\n\n\
9473e340 191Watchdog program to monitor status of frr daemons and try to restart\n\
8b886ca7 192them if they are down or unresponsive. It determines whether a daemon is\n\
193up based on whether it can connect to the daemon's vty unix stream socket.\n\
194It then repeatedly sends echo commands over that socket to determine whether\n\
195the daemon is responsive. If the daemon crashes, we will receive an EOF\n\
196on the socket connection and know immediately that the daemon is down.\n\n\
197The daemons to be monitored should be listed on the command line.\n\n\
8b886ca7 198In order to avoid attempting to restart the daemons in a fast loop,\n\
199the -m and -M options allow you to control the minimum delay between\n\
200restart commands. The minimum restart delay is recalculated each time\n\
201a restart is attempted: if the time since the last restart attempt exceeds\n\
202twice the -M value, then the restart delay is set to the -m value.\n\
d62a17ae 203Otherwise, the interval is doubled (but capped at the -M value).\n\n",
f168b713 204 progname);
e757c940 205
d62a17ae 206 fprintf(target,
207 "Options:\n\
8b886ca7 208-d, --daemon Run in daemon mode. In this mode, error messages are sent\n\
209 to syslog instead of stdout.\n\
210-S, --statedir Set the vty socket directory (default is %s)\n\
8b886ca7 211-l, --loglevel Set the logging level (default is %d).\n\
212 The value should range from %d (LOG_EMERG) to %d (LOG_DEBUG),\n\
213 but it can be set higher than %d if extra-verbose debugging\n\
214 messages are desired.\n\
9272302b 215 --min-restart-interval\n\
8b886ca7 216 Set the minimum seconds to wait between invocations of daemon\n\
217 restart commands (default is %d).\n\
9272302b 218 --max-restart-interval\n\
8b886ca7 219 Set the maximum seconds to wait between invocations of daemon\n\
220 restart commands (default is %d).\n\
221-i, --interval Set the status polling interval in seconds (default is %d)\n\
222-t, --timeout Set the unresponsiveness timeout in seconds (default is %d)\n\
223-T, --restart-timeout\n\
224 Set the restart (kill) timeout in seconds (default is %d).\n\
225 If any background jobs are still running after this much\n\
226 time has elapsed, they will be killed.\n\
227-r, --restart Supply a Bourne shell command to use to restart a single\n\
228 daemon. The command string should include '%%s' where the\n\
229 name of the daemon should be substituted.\n\
8b886ca7 230-s, --start-command\n\
231 Supply a Bourne shell to command to use to start a single\n\
232 daemon. The command string should include '%%s' where the\n\
233 name of the daemon should be substituted.\n\
234-k, --kill-command\n\
235 Supply a Bourne shell to command to use to stop a single\n\
236 daemon. The command string should include '%%s' where the\n\
237 name of the daemon should be substituted.\n\
f168b713 238 --dry Do not start or restart anything, just log.\n\
8b886ca7 239-p, --pid-file Set process identifier file name\n\
240 (default is %s).\n\
c8b40f86 241-b, --blank-string\n\
242 When the supplied argument string is found in any of the\n\
f168b713 243 various shell command arguments (-r, -s, or -k), replace\n\
c8b40f86 244 it with a space. This is an ugly hack to circumvent problems\n\
245 passing command-line arguments with embedded spaces.\n\
8b886ca7 246-v, --version Print program version\n\
d62a17ae 247-h, --help Display this help and exit\n",
248 VTYDIR, DEFAULT_LOGLEVEL, LOG_EMERG, LOG_DEBUG, LOG_DEBUG,
249 DEFAULT_MIN_RESTART, DEFAULT_MAX_RESTART, DEFAULT_PERIOD,
250 DEFAULT_TIMEOUT, DEFAULT_RESTART_TIMEOUT, DEFAULT_PIDFILE);
8b886ca7 251}
252
a6810074 253static pid_t run_background(char *shell_cmd)
8b886ca7 254{
a6810074
DL
255 pid_t child;
256
257 switch (child = fork()) {
258 case -1:
d62a17ae 259 zlog_err("fork failed, cannot run command [%s]: %s", shell_cmd,
260 safe_strerror(errno));
a6810074
DL
261 return -1;
262 case 0:
263 /* Child process. */
d62a17ae 264 /* Use separate process group so child processes can be killed
265 * easily. */
a6810074
DL
266 if (setpgid(0, 0) < 0)
267 zlog_warn("warning: setpgid(0,0) failed: %s",
268 safe_strerror(errno));
269 {
270 char shell[] = "sh";
271 char dashc[] = "-c";
d62a17ae 272 char *const argv[4] = {shell, dashc, shell_cmd, NULL};
a6810074 273 execv("/bin/sh", argv);
d62a17ae 274 zlog_err("execv(/bin/sh -c '%s') failed: %s", shell_cmd,
275 safe_strerror(errno));
a6810074
DL
276 _exit(127);
277 }
278 default:
279 /* Parent process: we will reap the child later. */
280 zlog_err("Forked background command [pid %d]: %s", (int)child,
281 shell_cmd);
282 return child;
283 }
8b886ca7 284}
285
a6810074
DL
286static struct timeval *time_elapsed(struct timeval *result,
287 const struct timeval *start_time)
8b886ca7 288{
a6810074
DL
289 gettimeofday(result, NULL);
290 result->tv_sec -= start_time->tv_sec;
291 result->tv_usec -= start_time->tv_usec;
292 while (result->tv_usec < 0) {
293 result->tv_usec += 1000000L;
294 result->tv_sec--;
295 }
296 return result;
8b886ca7 297}
298
a6810074 299static int restart_kill(struct thread *t_kill)
8b886ca7 300{
a6810074
DL
301 struct restart_info *restart = THREAD_ARG(t_kill);
302 struct timeval delay;
303
304 time_elapsed(&delay, &restart->time);
d62a17ae 305 zlog_warn(
306 "Warning: %s %s child process %d still running after "
307 "%ld seconds, sending signal %d",
308 restart->what, restart->name, (int)restart->pid,
309 (long)delay.tv_sec, (restart->kills ? SIGKILL : SIGTERM));
a6810074
DL
310 kill(-restart->pid, (restart->kills ? SIGKILL : SIGTERM));
311 restart->kills++;
66e78ae6
QY
312 restart->t_kill = NULL;
313 thread_add_timer(master, restart_kill, restart, gs.restart_timeout,
314 &restart->t_kill);
a6810074 315 return 0;
8b886ca7 316}
317
a6810074 318static struct restart_info *find_child(pid_t child)
8b886ca7 319{
f168b713
DL
320 struct daemon *dmn;
321 for (dmn = gs.daemons; dmn; dmn = dmn->next) {
322 if (dmn->restart.pid == child)
323 return &dmn->restart;
a6810074
DL
324 }
325 return NULL;
8b886ca7 326}
327
a6810074 328static void sigchild(void)
8b886ca7 329{
a6810074
DL
330 pid_t child;
331 int status;
332 const char *name;
333 const char *what;
334 struct restart_info *restart;
335
336 switch (child = waitpid(-1, &status, WNOHANG)) {
337 case -1:
338 zlog_err("waitpid failed: %s", safe_strerror(errno));
339 return;
340 case 0:
341 zlog_warn("SIGCHLD received, but waitpid did not reap a child");
342 return;
343 }
344
345 if (child == integrated_write_pid) {
346 integrated_write_sigchld(status);
347 return;
348 }
349
350 if ((restart = find_child(child)) != NULL) {
351 name = restart->name;
352 what = restart->what;
353 restart->pid = 0;
354 gs.numpids--;
355 thread_cancel(restart->t_kill);
356 restart->t_kill = NULL;
d62a17ae 357 /* Update restart time to reflect the time the command
358 * completed. */
a6810074
DL
359 gettimeofday(&restart->time, NULL);
360 } else {
d62a17ae 361 zlog_err(
362 "waitpid returned status for an unknown child process %d",
363 (int)child);
a6810074
DL
364 name = "(unknown)";
365 what = "background";
366 }
367 if (WIFSTOPPED(status))
d62a17ae 368 zlog_warn("warning: %s %s process %d is stopped", what, name,
369 (int)child);
a6810074 370 else if (WIFSIGNALED(status))
d62a17ae 371 zlog_warn("%s %s process %d terminated due to signal %d", what,
372 name, (int)child, WTERMSIG(status));
a6810074
DL
373 else if (WIFEXITED(status)) {
374 if (WEXITSTATUS(status) != 0)
d62a17ae 375 zlog_warn(
376 "%s %s process %d exited with non-zero status %d",
377 what, name, (int)child, WEXITSTATUS(status));
a6810074
DL
378 else
379 zlog_debug("%s %s process %d exited normally", what,
380 name, (int)child);
381 } else
382 zlog_err("cannot interpret %s %s process %d wait status 0x%x",
383 what, name, (int)child, status);
384 phase_check();
8b886ca7 385}
386
d62a17ae 387static int run_job(struct restart_info *restart, const char *cmdtype,
388 const char *command, int force, int update_interval)
8b886ca7 389{
a6810074
DL
390 struct timeval delay;
391
392 if (gs.loglevel > LOG_DEBUG + 1)
393 zlog_debug("attempting to %s %s", cmdtype, restart->name);
394
395 if (restart->pid) {
396 if (gs.loglevel > LOG_DEBUG + 1)
d62a17ae 397 zlog_debug(
398 "cannot %s %s, previous pid %d still running",
399 cmdtype, restart->name, (int)restart->pid);
a6810074
DL
400 return -1;
401 }
402
d62a17ae 403 /* Note: time_elapsed test must come before the force test, since we
404 need
a6810074
DL
405 to make sure that delay is initialized for use below in updating the
406 restart interval. */
407 if ((time_elapsed(&delay, &restart->time)->tv_sec < restart->interval)
408 && !force) {
409 if (gs.loglevel > LOG_DEBUG + 1)
d62a17ae 410 zlog_debug(
411 "postponing %s %s: "
412 "elapsed time %ld < retry interval %ld",
413 cmdtype, restart->name, (long)delay.tv_sec,
414 restart->interval);
a6810074
DL
415 return -1;
416 }
417
418 gettimeofday(&restart->time, NULL);
419 restart->kills = 0;
420 {
421 char cmd[strlen(command) + strlen(restart->name) + 1];
422 snprintf(cmd, sizeof(cmd), command, restart->name);
423 if ((restart->pid = run_background(cmd)) > 0) {
66e78ae6 424 restart->t_kill = NULL;
d62a17ae 425 thread_add_timer(master, restart_kill, restart,
426 gs.restart_timeout, &restart->t_kill);
a6810074
DL
427 restart->what = cmdtype;
428 gs.numpids++;
429 } else
430 restart->pid = 0;
431 }
432
433 /* Calculate the new restart interval. */
434 if (update_interval) {
435 if (delay.tv_sec > 2 * gs.max_restart_interval)
436 restart->interval = gs.min_restart_interval;
437 else if ((restart->interval *= 2) > gs.max_restart_interval)
438 restart->interval = gs.max_restart_interval;
439 if (gs.loglevel > LOG_DEBUG + 1)
440 zlog_debug("restart %s interval is now %ld",
441 restart->name, restart->interval);
442 }
443 return restart->pid;
8b886ca7 444}
445
d62a17ae 446#define SET_READ_HANDLER(DMN) \
447 do { \
448 (DMN)->t_read = NULL; \
449 thread_add_read(master, handle_read, (DMN), (DMN)->fd, \
450 &(DMN)->t_read); \
451 } while (0);
452
453#define SET_WAKEUP_DOWN(DMN) \
454 do { \
455 (DMN)->t_wakeup = NULL; \
456 thread_add_timer_msec(master, wakeup_down, (DMN), \
457 FUZZY(gs.period), &(DMN)->t_wakeup); \
458 } while (0);
459
460#define SET_WAKEUP_UNRESPONSIVE(DMN) \
461 do { \
462 (DMN)->t_wakeup = NULL; \
463 thread_add_timer_msec(master, wakeup_unresponsive, (DMN), \
464 FUZZY(gs.period), &(DMN)->t_wakeup); \
465 } while (0);
466
467#define SET_WAKEUP_ECHO(DMN) \
468 do { \
469 (DMN)->t_wakeup = NULL; \
470 thread_add_timer_msec(master, wakeup_send_echo, (DMN), \
471 FUZZY(gs.period), &(DMN)->t_wakeup); \
472 } while (0);
8b886ca7 473
a6810074 474static int wakeup_down(struct thread *t_wakeup)
8b886ca7 475{
a6810074
DL
476 struct daemon *dmn = THREAD_ARG(t_wakeup);
477
478 dmn->t_wakeup = NULL;
479 if (try_connect(dmn) < 0)
480 SET_WAKEUP_DOWN(dmn);
481 if ((dmn->connect_tries > 1) && (dmn->state != DAEMON_UP))
482 try_restart(dmn);
483 return 0;
8b886ca7 484}
485
a6810074 486static int wakeup_init(struct thread *t_wakeup)
8b886ca7 487{
a6810074
DL
488 struct daemon *dmn = THREAD_ARG(t_wakeup);
489
490 dmn->t_wakeup = NULL;
491 if (try_connect(dmn) < 0) {
492 SET_WAKEUP_DOWN(dmn);
493 zlog_err("%s state -> down : initial connection attempt failed",
494 dmn->name);
495 dmn->state = DAEMON_DOWN;
496 }
497 return 0;
8b886ca7 498}
499
a6810074 500static void daemon_down(struct daemon *dmn, const char *why)
8b886ca7 501{
a6810074
DL
502 if (IS_UP(dmn) || (dmn->state == DAEMON_INIT))
503 zlog_err("%s state -> down : %s", dmn->name, why);
504 else if (gs.loglevel > LOG_DEBUG)
505 zlog_debug("%s still down : %s", dmn->name, why);
506 if (IS_UP(dmn))
507 gs.numdown++;
508 dmn->state = DAEMON_DOWN;
509 if (dmn->fd >= 0) {
510 close(dmn->fd);
511 dmn->fd = -1;
512 }
513 THREAD_OFF(dmn->t_read);
514 THREAD_OFF(dmn->t_write);
515 THREAD_OFF(dmn->t_wakeup);
516 if (try_connect(dmn) < 0)
517 SET_WAKEUP_DOWN(dmn);
518 phase_check();
8b886ca7 519}
520
a6810074 521static int handle_read(struct thread *t_read)
8b886ca7 522{
a6810074
DL
523 struct daemon *dmn = THREAD_ARG(t_read);
524 static const char resp[sizeof(PING_TOKEN) + 4] = PING_TOKEN "\n";
525 char buf[sizeof(resp) + 100];
526 ssize_t rc;
527 struct timeval delay;
528
529 dmn->t_read = NULL;
530 if ((rc = read(dmn->fd, buf, sizeof(buf))) < 0) {
531 char why[100];
532
533 if (ERRNO_IO_RETRY(errno)) {
534 /* Pretend it never happened. */
535 SET_READ_HANDLER(dmn);
536 return 0;
537 }
538 snprintf(why, sizeof(why), "unexpected read error: %s",
539 safe_strerror(errno));
540 daemon_down(dmn, why);
541 return 0;
8b886ca7 542 }
a6810074
DL
543 if (rc == 0) {
544 daemon_down(dmn, "read returned EOF");
545 return 0;
546 }
547 if (!dmn->echo_sent.tv_sec) {
548 char why[sizeof(buf) + 100];
549 snprintf(why, sizeof(why),
550 "unexpected read returns %d bytes: %.*s", (int)rc,
551 (int)rc, buf);
552 daemon_down(dmn, why);
553 return 0;
8b886ca7 554 }
a6810074
DL
555
556 /* We are expecting an echo response: is there any chance that the
557 response would not be returned entirely in the first read? That
558 seems inconceivable... */
559 if ((rc != sizeof(resp)) || memcmp(buf, resp, sizeof(resp))) {
560 char why[100 + sizeof(buf)];
561 snprintf(why, sizeof(why),
562 "read returned bad echo response of %d bytes "
d62a17ae 563 "(expecting %u): %.*s",
564 (int)rc, (u_int)sizeof(resp), (int)rc, buf);
a6810074
DL
565 daemon_down(dmn, why);
566 return 0;
567 }
568
569 time_elapsed(&delay, &dmn->echo_sent);
570 dmn->echo_sent.tv_sec = 0;
571 if (dmn->state == DAEMON_UNRESPONSIVE) {
572 if (delay.tv_sec < gs.timeout) {
573 dmn->state = DAEMON_UP;
d62a17ae 574 zlog_warn(
575 "%s state -> up : echo response received after %ld.%06ld "
576 "seconds",
577 dmn->name, (long)delay.tv_sec,
578 (long)delay.tv_usec);
a6810074 579 } else
d62a17ae 580 zlog_warn(
581 "%s: slow echo response finally received after %ld.%06ld "
582 "seconds",
583 dmn->name, (long)delay.tv_sec,
584 (long)delay.tv_usec);
a6810074
DL
585 } else if (gs.loglevel > LOG_DEBUG + 1)
586 zlog_debug("%s: echo response received after %ld.%06ld seconds",
587 dmn->name, (long)delay.tv_sec, (long)delay.tv_usec);
588
589 SET_READ_HANDLER(dmn);
590 if (dmn->t_wakeup)
591 thread_cancel(dmn->t_wakeup);
592 SET_WAKEUP_ECHO(dmn);
593
594 return 0;
8b886ca7 595}
596
207e0d7a
DS
597/*
598 * Wait till we notice that all daemons are ready before
599 * we send we are ready to systemd
600 */
a6810074 601static void daemon_send_ready(void)
207e0d7a 602{
a6810074
DL
603 static int sent = 0;
604 if (!sent && gs.numdown == 0) {
a6810074 605 FILE *fp;
207e0d7a 606
a6810074
DL
607 fp = fopen(DAEMON_VTY_DIR "/watchfrr.started", "w");
608 fclose(fp);
d62a17ae 609 zlog_notice(
610 "Watchfrr: Notifying Systemd we are up and running");
a6810074
DL
611 systemd_send_started(master, 0);
612 sent = 1;
613 }
207e0d7a
DS
614}
615
a6810074 616static void daemon_up(struct daemon *dmn, const char *why)
8b886ca7 617{
a6810074
DL
618 dmn->state = DAEMON_UP;
619 gs.numdown--;
620 dmn->connect_tries = 0;
621 zlog_notice("%s state -> up : %s", dmn->name, why);
622 daemon_send_ready();
a8cbb8b3 623 SET_WAKEUP_ECHO(dmn);
a6810074 624 phase_check();
8b886ca7 625}
626
a6810074 627static int check_connect(struct thread *t_write)
8b886ca7 628{
a6810074
DL
629 struct daemon *dmn = THREAD_ARG(t_write);
630 int sockerr;
631 socklen_t reslen = sizeof(sockerr);
632
633 dmn->t_write = NULL;
634 if (getsockopt(dmn->fd, SOL_SOCKET, SO_ERROR, (char *)&sockerr, &reslen)
635 < 0) {
636 zlog_warn("%s: check_connect: getsockopt failed: %s", dmn->name,
637 safe_strerror(errno));
638 daemon_down(dmn,
639 "getsockopt failed checking connection success");
640 return 0;
641 }
642 if ((reslen == sizeof(sockerr)) && sockerr) {
643 char why[100];
d62a17ae 644 snprintf(
645 why, sizeof(why),
646 "getsockopt reports that connection attempt failed: %s",
647 safe_strerror(sockerr));
a6810074
DL
648 daemon_down(dmn, why);
649 return 0;
650 }
651
652 daemon_up(dmn, "delayed connect succeeded");
653 return 0;
8b886ca7 654}
655
a6810074 656static int wakeup_connect_hanging(struct thread *t_wakeup)
8b886ca7 657{
a6810074
DL
658 struct daemon *dmn = THREAD_ARG(t_wakeup);
659 char why[100];
660
661 dmn->t_wakeup = NULL;
662 snprintf(why, sizeof(why),
663 "connection attempt timed out after %ld seconds", gs.timeout);
664 daemon_down(dmn, why);
665 return 0;
8b886ca7 666}
667
668/* Making connection to protocol daemon. */
a6810074 669static int try_connect(struct daemon *dmn)
8b886ca7 670{
a6810074
DL
671 int sock;
672 struct sockaddr_un addr;
673 socklen_t len;
674
675 if (gs.loglevel > LOG_DEBUG + 1)
676 zlog_debug("%s: attempting to connect", dmn->name);
677 dmn->connect_tries++;
678
679 memset(&addr, 0, sizeof(struct sockaddr_un));
680 addr.sun_family = AF_UNIX;
d62a17ae 681 snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/%s.vty", gs.vtydir,
682 dmn->name);
6f0e3f6e 683#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN
a6810074 684 len = addr.sun_len = SUN_LEN(&addr);
8b886ca7 685#else
a6810074 686 len = sizeof(addr.sun_family) + strlen(addr.sun_path);
d62a17ae 687#endif /* HAVE_STRUCT_SOCKADDR_UN_SUN_LEN */
a6810074
DL
688
689 /* Quick check to see if we might succeed before we go to the trouble
690 of creating a socket. */
691 if (access(addr.sun_path, W_OK) < 0) {
692 if (errno != ENOENT)
693 zlog_err("%s: access to socket %s denied: %s",
694 dmn->name, addr.sun_path,
695 safe_strerror(errno));
696 return -1;
697 }
698
699 if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
d62a17ae 700 zlog_err("%s(%s): cannot make socket: %s", __func__,
701 addr.sun_path, safe_strerror(errno));
a6810074
DL
702 return -1;
703 }
704
705 if (set_nonblocking(sock) < 0 || set_cloexec(sock) < 0) {
d62a17ae 706 zlog_err("%s(%s): set_nonblocking/cloexec(%d) failed", __func__,
707 addr.sun_path, sock);
a6810074
DL
708 close(sock);
709 return -1;
8b886ca7 710 }
a6810074
DL
711
712 if (connect(sock, (struct sockaddr *)&addr, len) < 0) {
713 if ((errno != EINPROGRESS) && (errno != EWOULDBLOCK)) {
714 if (gs.loglevel > LOG_DEBUG)
715 zlog_debug("%s(%s): connect failed: %s",
716 __func__, addr.sun_path,
717 safe_strerror(errno));
718 close(sock);
719 return -1;
720 }
721 if (gs.loglevel > LOG_DEBUG)
722 zlog_debug("%s: connection in progress", dmn->name);
723 dmn->state = DAEMON_CONNECTING;
724 dmn->fd = sock;
66e78ae6
QY
725 dmn->t_write = NULL;
726 thread_add_write(master, check_connect, dmn, dmn->fd,
d62a17ae 727 &dmn->t_write);
728 dmn->t_wakeup = NULL;
729 thread_add_timer(master, wakeup_connect_hanging, dmn,
730 gs.timeout, &dmn->t_wakeup);
a6810074
DL
731 SET_READ_HANDLER(dmn);
732 return 0;
733 }
734
735 dmn->fd = sock;
736 SET_READ_HANDLER(dmn);
737 daemon_up(dmn, "connect succeeded");
738 return 1;
8b886ca7 739}
740
a6810074 741static int phase_hanging(struct thread *t_hanging)
8b886ca7 742{
a6810074
DL
743 gs.t_phase_hanging = NULL;
744 zlog_err("Phase [%s] hanging for %ld seconds, aborting phased restart",
745 phase_str[gs.phase], PHASE_TIMEOUT);
746 gs.phase = PHASE_NONE;
747 return 0;
8b886ca7 748}
749
a6810074 750static void set_phase(restart_phase_t new_phase)
8b886ca7 751{
a6810074
DL
752 gs.phase = new_phase;
753 if (gs.t_phase_hanging)
754 thread_cancel(gs.t_phase_hanging);
66e78ae6
QY
755 gs.t_phase_hanging = NULL;
756 thread_add_timer(master, phase_hanging, NULL, PHASE_TIMEOUT,
757 &gs.t_phase_hanging);
8b886ca7 758}
759
a6810074 760static void phase_check(void)
8b886ca7 761{
a6810074
DL
762 switch (gs.phase) {
763 case PHASE_NONE:
764 break;
765 case PHASE_STOPS_PENDING:
766 if (gs.numpids)
767 break;
d62a17ae 768 zlog_info(
769 "Phased restart: all routing daemon stop jobs have completed.");
a6810074
DL
770 set_phase(PHASE_WAITING_DOWN);
771
d62a17ae 772 /*FALLTHRU*/
a6810074
DL
773 case PHASE_WAITING_DOWN:
774 if (gs.numdown + IS_UP(gs.special) < gs.numdaemons)
775 break;
776 zlog_info("Phased restart: all routing daemons now down.");
777 run_job(&gs.special->restart, "restart", gs.restart_command, 1,
778 1);
779 set_phase(PHASE_ZEBRA_RESTART_PENDING);
780
d62a17ae 781 /*FALLTHRU*/
a6810074
DL
782 case PHASE_ZEBRA_RESTART_PENDING:
783 if (gs.special->restart.pid)
784 break;
785 zlog_info("Phased restart: %s restart job completed.",
786 gs.special->name);
787 set_phase(PHASE_WAITING_ZEBRA_UP);
788
d62a17ae 789 /*FALLTHRU*/
a6810074
DL
790 case PHASE_WAITING_ZEBRA_UP:
791 if (!IS_UP(gs.special))
792 break;
793 zlog_info("Phased restart: %s is now up.", gs.special->name);
794 {
795 struct daemon *dmn;
796 for (dmn = gs.daemons; dmn; dmn = dmn->next) {
797 if (dmn != gs.special)
798 run_job(&dmn->restart, "start",
799 gs.start_command, 1, 0);
800 }
801 }
802 gs.phase = PHASE_NONE;
803 THREAD_OFF(gs.t_phase_hanging);
804 zlog_notice("Phased global restart has completed.");
805 break;
806 }
8b886ca7 807}
808
a6810074 809static void try_restart(struct daemon *dmn)
8b886ca7 810{
f168b713 811 if (watch_only)
a6810074 812 return;
a6810074 813
f168b713
DL
814 if (dmn != gs.special) {
815 if ((gs.special->state == DAEMON_UP)
816 && (gs.phase == PHASE_NONE))
817 run_job(&dmn->restart, "restart", gs.restart_command, 0,
818 1);
819 else
820 zlog_debug(
821 "%s: postponing restart attempt because master %s daemon "
822 "not up [%s], or phased restart in progress",
823 dmn->name, gs.special->name,
824 state_str[gs.special->state]);
825 return;
826 }
827
828 if ((gs.phase != PHASE_NONE) || gs.numpids) {
829 if (gs.loglevel > LOG_DEBUG + 1)
830 zlog_debug(
831 "postponing phased global restart: restart already in "
832 "progress [%s], or outstanding child processes [%d]",
833 phase_str[gs.phase], gs.numpids);
834 return;
835 }
836 /* Is it too soon for a restart? */
837 {
838 struct timeval delay;
839 if (time_elapsed(&delay, &gs.special->restart.time)->tv_sec
840 < gs.special->restart.interval) {
a6810074 841 if (gs.loglevel > LOG_DEBUG + 1)
d62a17ae 842 zlog_debug(
f168b713
DL
843 "postponing phased global restart: "
844 "elapsed time %ld < retry interval %ld",
845 (long)delay.tv_sec,
846 gs.special->restart.interval);
847 return;
a6810074 848 }
8b886ca7 849 }
f168b713 850 run_job(&gs.restart, "restart", gs.restart_command, 0, 1);
8b886ca7 851}
852
a6810074 853static int wakeup_unresponsive(struct thread *t_wakeup)
8b886ca7 854{
a6810074
DL
855 struct daemon *dmn = THREAD_ARG(t_wakeup);
856
857 dmn->t_wakeup = NULL;
858 if (dmn->state != DAEMON_UNRESPONSIVE)
d62a17ae 859 zlog_err(
860 "%s: no longer unresponsive (now %s), "
861 "wakeup should have been cancelled!",
862 dmn->name, state_str[dmn->state]);
a6810074
DL
863 else {
864 SET_WAKEUP_UNRESPONSIVE(dmn);
865 try_restart(dmn);
866 }
867 return 0;
8b886ca7 868}
869
a6810074 870static int wakeup_no_answer(struct thread *t_wakeup)
8b886ca7 871{
a6810074
DL
872 struct daemon *dmn = THREAD_ARG(t_wakeup);
873
874 dmn->t_wakeup = NULL;
875 dmn->state = DAEMON_UNRESPONSIVE;
d62a17ae 876 zlog_err(
877 "%s state -> unresponsive : no response yet to ping "
878 "sent %ld seconds ago",
879 dmn->name, gs.timeout);
71e7975a
DL
880 SET_WAKEUP_UNRESPONSIVE(dmn);
881 try_restart(dmn);
a6810074 882 return 0;
8b886ca7 883}
884
a6810074 885static int wakeup_send_echo(struct thread *t_wakeup)
8b886ca7 886{
a6810074
DL
887 static const char echocmd[] = "echo " PING_TOKEN;
888 ssize_t rc;
889 struct daemon *dmn = THREAD_ARG(t_wakeup);
890
891 dmn->t_wakeup = NULL;
d62a17ae 892 if (((rc = write(dmn->fd, echocmd, sizeof(echocmd))) < 0)
893 || ((size_t)rc != sizeof(echocmd))) {
a6810074
DL
894 char why[100 + sizeof(echocmd)];
895 snprintf(why, sizeof(why),
896 "write '%s' returned %d instead of %u", echocmd,
d62a17ae 897 (int)rc, (u_int)sizeof(echocmd));
a6810074
DL
898 daemon_down(dmn, why);
899 } else {
900 gettimeofday(&dmn->echo_sent, NULL);
66e78ae6
QY
901 dmn->t_wakeup = NULL;
902 thread_add_timer(master, wakeup_no_answer, dmn, gs.timeout,
903 &dmn->t_wakeup);
a6810074
DL
904 }
905 return 0;
8b886ca7 906}
907
a6810074 908static void sigint(void)
8b886ca7 909{
a6810074
DL
910 zlog_notice("Terminating on signal");
911 systemd_send_stopping();
912 exit(0);
8b886ca7 913}
914
a6810074 915static int valid_command(const char *cmd)
8b886ca7 916{
a6810074 917 char *p;
8b886ca7 918
a6810074 919 return ((p = strchr(cmd, '%')) != NULL) && (*(p + 1) == 's')
d62a17ae 920 && !strchr(p + 1, '%');
8b886ca7 921}
922
c8b40f86 923/* This is an ugly hack to circumvent problems with passing command-line
924 arguments that contain spaces. The fix is to use a configuration file. */
a6810074 925static char *translate_blanks(const char *cmd, const char *blankstr)
c8b40f86 926{
a6810074
DL
927 char *res;
928 char *p;
929 size_t bslen = strlen(blankstr);
930
931 if (!(res = strdup(cmd))) {
932 perror("strdup");
933 exit(1);
934 }
935 while ((p = strstr(res, blankstr)) != NULL) {
936 *p = ' ';
937 if (bslen != 1)
938 memmove(p + 1, p + bslen, strlen(p + bslen) + 1);
939 }
940 return res;
c8b40f86 941}
942
a6810074 943struct zebra_privs_t watchfrr_privs = {
95c4aff2 944#ifdef VTY_GROUP
a6810074 945 .vty_group = VTY_GROUP,
95c4aff2
DL
946#endif
947};
948
4f04a76b
DL
949static struct quagga_signal_t watchfrr_signals[] = {
950 {
951 .signal = SIGINT,
952 .handler = sigint,
953 },
954 {
955 .signal = SIGTERM,
956 .handler = sigint,
957 },
958 {
959 .signal = SIGCHLD,
960 .handler = sigchild,
961 },
962};
963
964FRR_DAEMON_INFO(watchfrr, WATCHFRR,
d62a17ae 965 .flags = FRR_NO_PRIVSEP | FRR_NO_TCPVTY | FRR_LIMITED_CLI
966 | FRR_NO_CFG_PID_DRY | FRR_NO_ZCLIENT,
4f04a76b 967
d62a17ae 968 .printhelp = printhelp,
969 .copyright = "Copyright 2004 Andrew J. Schorr",
4f04a76b 970
d62a17ae 971 .signals = watchfrr_signals,
972 .n_signals = array_size(watchfrr_signals),
4f04a76b 973
d62a17ae 974 .privs = &watchfrr_privs, )
4f04a76b 975
a6810074 976int main(int argc, char **argv)
8b886ca7 977{
a6810074 978 int opt;
a6810074
DL
979 const char *pidfile = DEFAULT_PIDFILE;
980 const char *special = "zebra";
981 const char *blankstr = NULL;
a6810074 982
4f04a76b
DL
983 frr_preinit(&watchfrr_di, argc, argv);
984 progname = watchfrr_di.progname;
985
71e7975a 986 frr_opt_add("b:dk:l:i:p:r:S:s:t:T:", longopts, "");
a6810074
DL
987
988 gs.restart.name = "all";
4f04a76b 989 while ((opt = frr_getopt(argc, argv, NULL)) != EOF) {
a6810074
DL
990 switch (opt) {
991 case 0:
992 break;
a6810074
DL
993 case 'b':
994 blankstr = optarg;
995 break;
f168b713
DL
996 case OPTION_DRY:
997 watch_only = true;
998 break;
a6810074
DL
999 case 'k':
1000 if (!valid_command(optarg)) {
1001 fprintf(stderr,
1002 "Invalid kill command, must contain '%%s': %s\n",
1003 optarg);
4f04a76b 1004 frr_help_exit(1);
a6810074
DL
1005 }
1006 gs.stop_command = optarg;
1007 break;
d62a17ae 1008 case 'l': {
1009 char garbage[3];
1010 if ((sscanf(optarg, "%d%1s", &gs.loglevel, garbage)
1011 != 1)
1012 || (gs.loglevel < LOG_EMERG)) {
1013 fprintf(stderr,
1014 "Invalid loglevel argument: %s\n",
1015 optarg);
1016 frr_help_exit(1);
a6810074 1017 }
d62a17ae 1018 } break;
1019 case OPTION_MINRESTART: {
1020 char garbage[3];
1021 if ((sscanf(optarg, "%ld%1s", &gs.min_restart_interval,
1022 garbage)
1023 != 1)
1024 || (gs.min_restart_interval < 0)) {
1025 fprintf(stderr,
1026 "Invalid min_restart_interval argument: %s\n",
1027 optarg);
1028 frr_help_exit(1);
a6810074 1029 }
d62a17ae 1030 } break;
1031 case OPTION_MAXRESTART: {
1032 char garbage[3];
1033 if ((sscanf(optarg, "%ld%1s", &gs.max_restart_interval,
1034 garbage)
1035 != 1)
1036 || (gs.max_restart_interval < 0)) {
1037 fprintf(stderr,
1038 "Invalid max_restart_interval argument: %s\n",
1039 optarg);
1040 frr_help_exit(1);
a6810074 1041 }
d62a17ae 1042 } break;
1043 case 'i': {
1044 char garbage[3];
1045 int period;
1046 if ((sscanf(optarg, "%d%1s", &period, garbage) != 1)
1047 || (gs.period < 1)) {
1048 fprintf(stderr,
1049 "Invalid interval argument: %s\n",
1050 optarg);
1051 frr_help_exit(1);
a6810074 1052 }
d62a17ae 1053 gs.period = 1000 * period;
1054 } break;
a6810074
DL
1055 case 'p':
1056 pidfile = optarg;
1057 break;
1058 case 'r':
a6810074
DL
1059 if (!valid_command(optarg)) {
1060 fprintf(stderr,
1061 "Invalid restart command, must contain '%%s': %s\n",
1062 optarg);
4f04a76b 1063 frr_help_exit(1);
a6810074
DL
1064 }
1065 gs.restart_command = optarg;
a6810074
DL
1066 break;
1067 case 's':
1068 if (!valid_command(optarg)) {
1069 fprintf(stderr,
1070 "Invalid start command, must contain '%%s': %s\n",
1071 optarg);
4f04a76b 1072 frr_help_exit(1);
a6810074
DL
1073 }
1074 gs.start_command = optarg;
1075 break;
1076 case 'S':
1077 gs.vtydir = optarg;
1078 break;
d62a17ae 1079 case 't': {
1080 char garbage[3];
1081 if ((sscanf(optarg, "%ld%1s", &gs.timeout, garbage)
1082 != 1)
1083 || (gs.timeout < 1)) {
1084 fprintf(stderr,
1085 "Invalid timeout argument: %s\n",
1086 optarg);
1087 frr_help_exit(1);
a6810074 1088 }
d62a17ae 1089 } break;
1090 case 'T': {
1091 char garbage[3];
1092 if ((sscanf(optarg, "%ld%1s", &gs.restart_timeout,
1093 garbage)
1094 != 1)
1095 || (gs.restart_timeout < 1)) {
1096 fprintf(stderr,
1097 "Invalid restart timeout argument: %s\n",
1098 optarg);
1099 frr_help_exit(1);
a6810074 1100 }
d62a17ae 1101 } break;
a6810074
DL
1102 default:
1103 fputs("Invalid option.\n", stderr);
4f04a76b 1104 frr_help_exit(1);
a6810074 1105 }
8b886ca7 1106 }
a6810074 1107
71e7975a
DL
1108 if (watch_only
1109 && (gs.start_command || gs.stop_command || gs.restart_command)) {
1110 fputs("Options -r/-s/-k make no sense combined with -D.\n",
a6810074 1111 stderr);
4f04a76b 1112 frr_help_exit(1);
8b886ca7 1113 }
f168b713
DL
1114 if (!watch_only
1115 && (!gs.restart_command || !gs.start_command || !gs.stop_command)) {
1116 fprintf(stderr,
1117 "Options -s (start), -k (kill), and -r (restart) are required.\n");
1118 frr_help_exit(1);
8b886ca7 1119 }
8b886ca7 1120
a6810074
DL
1121 if (blankstr) {
1122 if (gs.restart_command)
1123 gs.restart_command =
d62a17ae 1124 translate_blanks(gs.restart_command, blankstr);
a6810074
DL
1125 if (gs.start_command)
1126 gs.start_command =
d62a17ae 1127 translate_blanks(gs.start_command, blankstr);
a6810074
DL
1128 if (gs.stop_command)
1129 gs.stop_command =
d62a17ae 1130 translate_blanks(gs.stop_command, blankstr);
065de903 1131 }
8b886ca7 1132
a6810074 1133 gs.restart.interval = gs.min_restart_interval;
8b886ca7 1134
4f04a76b
DL
1135 master = frr_init();
1136
dd8376fe 1137 zlog_set_level(ZLOG_DEST_MONITOR, ZLOG_DISABLED);
eb05883f 1138 if (watchfrr_di.daemon_mode) {
dd8376fe 1139 zlog_set_level(ZLOG_DEST_SYSLOG, MIN(gs.loglevel, LOG_DEBUG));
d62a17ae 1140 if (daemon(0, 0) < 0) {
2f4f11fa 1141 fprintf(stderr, "Watchfrr daemon failed: %s",
d62a17ae 1142 strerror(errno));
1143 exit(1);
4f04a76b
DL
1144 }
1145 } else
dd8376fe 1146 zlog_set_level(ZLOG_DEST_STDOUT, MIN(gs.loglevel, LOG_DEBUG));
8b886ca7 1147
a6810074 1148 watchfrr_vty_init();
8b886ca7 1149
eb05883f 1150 frr_vty_serv();
8b886ca7 1151
8b886ca7 1152 {
a6810074
DL
1153 int i;
1154 struct daemon *tail = NULL;
1155
1156 for (i = optind; i < argc; i++) {
1157 struct daemon *dmn;
1158
1159 if (!(dmn = (struct daemon *)calloc(1, sizeof(*dmn)))) {
1160 fprintf(stderr, "calloc(1,%u) failed: %s\n",
d62a17ae 1161 (u_int)sizeof(*dmn),
a6810074
DL
1162 safe_strerror(errno));
1163 return 1;
1164 }
1165 dmn->name = dmn->restart.name = argv[i];
1166 dmn->state = DAEMON_INIT;
1167 gs.numdaemons++;
1168 gs.numdown++;
1169 dmn->fd = -1;
66e78ae6 1170 dmn->t_wakeup = NULL;
d62a17ae 1171 thread_add_timer_msec(master, wakeup_init, dmn,
1172 100 + (random() % 900),
66e78ae6 1173 &dmn->t_wakeup);
a6810074
DL
1174 dmn->restart.interval = gs.min_restart_interval;
1175 if (tail)
1176 tail->next = dmn;
1177 else
1178 gs.daemons = dmn;
1179 tail = dmn;
1180
f168b713 1181 if (!strcmp(dmn->name, special))
a6810074
DL
1182 gs.special = dmn;
1183 }
1184 }
1185 if (!gs.daemons) {
1186 fputs("Must specify one or more daemons to monitor.\n", stderr);
4f04a76b 1187 frr_help_exit(1);
a6810074 1188 }
f168b713
DL
1189 if (!watch_only && !gs.special) {
1190 fprintf(stderr, "\"%s\" daemon must be in daemon list\n",
1191 special);
4f04a76b 1192 frr_help_exit(1);
8b886ca7 1193 }
8b886ca7 1194
a6810074
DL
1195 /* Make sure we're not already running. */
1196 pid_output(pidfile);
1197
1198 /* Announce which daemons are being monitored. */
1199 {
1200 struct daemon *dmn;
1201 size_t len = 0;
1202
1203 for (dmn = gs.daemons; dmn; dmn = dmn->next)
1204 len += strlen(dmn->name) + 1;
1205
1206 {
1207 char buf[len + 1];
1208 char *p = buf;
1209
1210 for (dmn = gs.daemons; dmn; dmn = dmn->next) {
1211 if (p != buf)
1212 *p++ = ' ';
1213 strcpy(p, dmn->name);
1214 p += strlen(p);
1215 }
f168b713
DL
1216 zlog_notice("%s %s watching [%s]%s", progname,
1217 FRR_VERSION, buf,
1218 watch_only ? ", monitor mode" : "");
a6810074
DL
1219 }
1220 }
8b886ca7 1221
a6810074
DL
1222 {
1223 struct thread thread;
1224
1225 while (thread_fetch(master, &thread))
1226 thread_call(&thread);
1227 }
8b886ca7 1228
a6810074
DL
1229 systemd_send_stopping();
1230 /* Not reached. */
1231 return 0;
8b886ca7 1232}