Line data Source code
1 : // SPDX-License-Identifier: GPL-2.0-or-later
2 : /*
3 : * libfrr overall management functions
4 : *
5 : * Copyright (C) 2016 David Lamparter for NetDEF, Inc.
6 : */
7 :
8 : #include <zebra.h>
9 : #include <sys/un.h>
10 :
11 : #include <sys/types.h>
12 : #include <sys/wait.h>
13 :
14 : #include "libfrr.h"
15 : #include "getopt.h"
16 : #include "privs.h"
17 : #include "vty.h"
18 : #include "command.h"
19 : #include "lib/version.h"
20 : #include "lib_vty.h"
21 : #include "log_vty.h"
22 : #include "zclient.h"
23 : #include "module.h"
24 : #include "network.h"
25 : #include "lib_errors.h"
26 : #include "db.h"
27 : #include "northbound_cli.h"
28 : #include "northbound_db.h"
29 : #include "debug.h"
30 : #include "frrcu.h"
31 : #include "frr_pthread.h"
32 : #include "defaults.h"
33 : #include "frrscript.h"
34 : #include "systemd.h"
35 :
36 8 : DEFINE_HOOK(frr_early_init, (struct event_loop * tm), (tm));
37 4 : DEFINE_HOOK(frr_late_init, (struct event_loop * tm), (tm));
38 4 : DEFINE_HOOK(frr_config_pre, (struct event_loop * tm), (tm));
39 4 : DEFINE_HOOK(frr_config_post, (struct event_loop * tm), (tm));
40 4 : DEFINE_KOOH(frr_early_fini, (), ());
41 10 : DEFINE_KOOH(frr_fini, (), ());
42 :
43 : const char frr_sysconfdir[] = SYSCONFDIR;
44 : char frr_vtydir[256];
45 : #ifdef HAVE_SQLITE3
46 : const char frr_dbdir[] = DAEMON_DB_DIR;
47 : #endif
48 : const char frr_moduledir[] = MODULE_PATH;
49 : const char frr_scriptdir[] = SCRIPT_PATH;
50 :
51 : char frr_protoname[256] = "NONE";
52 : char frr_protonameinst[256] = "NONE";
53 :
54 : char config_default[512];
55 : char frr_zclientpath[256];
56 : static char pidfile_default[1024];
57 : #ifdef HAVE_SQLITE3
58 : static char dbfile_default[512];
59 : #endif
60 : static char vtypath_default[512];
61 :
62 : /* cleared in frr_preinit(), then re-set after daemonizing */
63 : bool frr_is_after_fork = true;
64 : bool debug_memstats_at_exit = false;
65 : static bool nodetach_term, nodetach_daemon;
66 : static uint64_t startup_fds;
67 :
68 : static char comb_optstr[256];
69 : static struct option comb_lo[64];
70 : static struct option *comb_next_lo = &comb_lo[0];
71 : static char comb_helpstr[4096];
72 :
73 : struct optspec {
74 : const char *optstr;
75 : const char *helpstr;
76 : const struct option *longopts;
77 : };
78 :
79 30 : static void opt_extend(const struct optspec *os)
80 : {
81 30 : const struct option *lo;
82 :
83 30 : strlcat(comb_optstr, os->optstr, sizeof(comb_optstr));
84 30 : strlcat(comb_helpstr, os->helpstr, sizeof(comb_helpstr));
85 166 : for (lo = os->longopts; lo->name; lo++)
86 136 : memcpy(comb_next_lo++, lo, sizeof(*lo));
87 30 : }
88 :
89 :
90 : #define OPTION_VTYSOCK 1000
91 : #define OPTION_MODULEDIR 1002
92 : #define OPTION_LOG 1003
93 : #define OPTION_LOGLEVEL 1004
94 : #define OPTION_TCLI 1005
95 : #define OPTION_DB_FILE 1006
96 : #define OPTION_LOGGING 1007
97 : #define OPTION_LIMIT_FDS 1008
98 : #define OPTION_SCRIPTDIR 1009
99 :
100 : static const struct option lo_always[] = {
101 : {"help", no_argument, NULL, 'h'},
102 : {"version", no_argument, NULL, 'v'},
103 : {"daemon", no_argument, NULL, 'd'},
104 : {"module", no_argument, NULL, 'M'},
105 : {"profile", required_argument, NULL, 'F'},
106 : {"pathspace", required_argument, NULL, 'N'},
107 : {"vrfdefaultname", required_argument, NULL, 'o'},
108 : {"vty_socket", required_argument, NULL, OPTION_VTYSOCK},
109 : {"moduledir", required_argument, NULL, OPTION_MODULEDIR},
110 : {"scriptdir", required_argument, NULL, OPTION_SCRIPTDIR},
111 : {"log", required_argument, NULL, OPTION_LOG},
112 : {"log-level", required_argument, NULL, OPTION_LOGLEVEL},
113 : {"command-log-always", no_argument, NULL, OPTION_LOGGING},
114 : {"limit-fds", required_argument, NULL, OPTION_LIMIT_FDS},
115 : {NULL}};
116 : static const struct optspec os_always = {
117 : "hvdM:F:N:o:",
118 : " -h, --help Display this help and exit\n"
119 : " -v, --version Print program version\n"
120 : " -d, --daemon Runs in daemon mode\n"
121 : " -M, --module Load specified module\n"
122 : " -F, --profile Use specified configuration profile\n"
123 : " -N, --pathspace Insert prefix into config & socket paths\n"
124 : " -o, --vrfdefaultname Set default VRF name.\n"
125 : " --vty_socket Override vty socket path\n"
126 : " --moduledir Override modules directory\n"
127 : " --scriptdir Override scripts directory\n"
128 : " --log Set Logging to stdout, syslog, or file:<name>\n"
129 : " --log-level Set Logging Level to use, debug, info, warn, etc\n"
130 : " --limit-fds Limit number of fds supported\n",
131 : lo_always};
132 :
133 : static bool logging_to_stdout = false; /* set when --log stdout specified */
134 :
135 : static const struct option lo_cfg[] = {
136 : {"config_file", required_argument, NULL, 'f'},
137 : {"dryrun", no_argument, NULL, 'C'},
138 : {NULL}};
139 : static const struct optspec os_cfg = {
140 : "f:C",
141 : " -f, --config_file Set configuration file name\n"
142 : " -C, --dryrun Check configuration for validity and exit\n",
143 : lo_cfg};
144 :
145 :
146 : static const struct option lo_fullcli[] = {
147 : {"terminal", no_argument, NULL, 't'},
148 : {"tcli", no_argument, NULL, OPTION_TCLI},
149 : #ifdef HAVE_SQLITE3
150 : {"db_file", required_argument, NULL, OPTION_DB_FILE},
151 : #endif
152 : {NULL}};
153 : static const struct optspec os_fullcli = {
154 : "t",
155 : " --tcli Use transaction-based CLI\n"
156 : " -t, --terminal Open terminal session on stdio\n"
157 : " -d -t Daemonize after terminal session ends\n",
158 : lo_fullcli};
159 :
160 :
161 : static const struct option lo_pid[] = {
162 : {"pid_file", required_argument, NULL, 'i'},
163 : {NULL}};
164 : static const struct optspec os_pid = {
165 : "i:",
166 : " -i, --pid_file Set process identifier file name\n",
167 : lo_pid};
168 :
169 :
170 : static const struct option lo_zclient[] = {
171 : {"socket", required_argument, NULL, 'z'},
172 : {NULL}};
173 : static const struct optspec os_zclient = {
174 : "z:", " -z, --socket Set path of zebra socket\n", lo_zclient};
175 :
176 :
177 : static const struct option lo_vty[] = {
178 : {"vty_addr", required_argument, NULL, 'A'},
179 : {"vty_port", required_argument, NULL, 'P'},
180 : {NULL}};
181 : static const struct optspec os_vty = {
182 : "A:P:",
183 : " -A, --vty_addr Set vty's bind address\n"
184 : " -P, --vty_port Set vty's port number\n",
185 : lo_vty};
186 :
187 :
188 : static const struct option lo_user[] = {{"user", required_argument, NULL, 'u'},
189 : {"group", required_argument, NULL, 'g'},
190 : {NULL}};
191 : static const struct optspec os_user = {"u:g:",
192 : " -u, --user User to run as\n"
193 : " -g, --group Group to run as\n",
194 : lo_user};
195 :
196 6 : bool frr_zclient_addr(struct sockaddr_storage *sa, socklen_t *sa_len,
197 : const char *path)
198 : {
199 6 : memset(sa, 0, sizeof(*sa));
200 :
201 6 : if (!path)
202 2 : path = frr_zclientpath;
203 :
204 6 : if (!strncmp(path, ZAPI_TCP_PATHNAME, strlen(ZAPI_TCP_PATHNAME))) {
205 : /* note: this functionality is disabled at bottom */
206 0 : int af;
207 0 : int port = ZEBRA_PORT;
208 0 : char *err = NULL;
209 0 : struct sockaddr_in *sin = NULL;
210 0 : struct sockaddr_in6 *sin6 = NULL;
211 :
212 0 : path += strlen(ZAPI_TCP_PATHNAME);
213 :
214 0 : switch (path[0]) {
215 0 : case '4':
216 0 : path++;
217 0 : af = AF_INET;
218 0 : break;
219 0 : case '6':
220 0 : path++;
221 0 : af = AF_INET6;
222 0 : break;
223 : default:
224 : af = AF_INET6;
225 : break;
226 : }
227 :
228 0 : switch (path[0]) {
229 : case '\0':
230 : break;
231 0 : case ':':
232 0 : path++;
233 0 : port = strtoul(path, &err, 10);
234 0 : if (*err || !*path)
235 : return false;
236 : break;
237 : default:
238 : return false;
239 : }
240 :
241 0 : sa->ss_family = af;
242 0 : switch (af) {
243 0 : case AF_INET:
244 0 : sin = (struct sockaddr_in *)sa;
245 0 : sin->sin_port = htons(port);
246 0 : sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
247 0 : *sa_len = sizeof(struct sockaddr_in);
248 : #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
249 : sin->sin_len = *sa_len;
250 : #endif
251 0 : break;
252 0 : case AF_INET6:
253 0 : sin6 = (struct sockaddr_in6 *)sa;
254 0 : sin6->sin6_port = htons(port);
255 0 : inet_pton(AF_INET6, "::1", &sin6->sin6_addr);
256 0 : *sa_len = sizeof(struct sockaddr_in6);
257 : #ifdef SIN6_LEN
258 : sin6->sin6_len = *sa_len;
259 : #endif
260 0 : break;
261 : }
262 :
263 : #if 1
264 : /* force-disable this path, because tcp-zebra is a
265 : * SECURITY ISSUE. there are no checks at all against
266 : * untrusted users on the local system connecting on TCP
267 : * and injecting bogus routing data into the entire routing
268 : * domain.
269 : *
270 : * The functionality is only left here because it may be
271 : * useful during development, in order to be able to get
272 : * tcpdump or wireshark watching ZAPI as TCP. If you want
273 : * to do that, flip the #if 1 above to #if 0. */
274 0 : memset(sa, 0, sizeof(*sa));
275 0 : return false;
276 : #endif
277 : } else {
278 : /* "sun" is a #define on solaris */
279 6 : struct sockaddr_un *suna = (struct sockaddr_un *)sa;
280 :
281 6 : suna->sun_family = AF_UNIX;
282 6 : strlcpy(suna->sun_path, path, sizeof(suna->sun_path));
283 : #ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN
284 : *sa_len = suna->sun_len = SUN_LEN(suna);
285 : #else
286 6 : *sa_len = sizeof(suna->sun_family) + strlen(suna->sun_path);
287 : #endif /* HAVE_STRUCT_SOCKADDR_UN_SUN_LEN */
288 : #if 0
289 : /* this is left here for future reference; Linux abstract
290 : * socket namespace support can be enabled by replacing
291 : * above #if 0 with #ifdef GNU_LINUX.
292 : *
293 : * THIS IS A SECURITY ISSUE, the abstract socket namespace
294 : * does not have user/group permission control on sockets.
295 : * we'd need to implement SCM_CREDENTIALS support first to
296 : * check that only proper users can connect to abstract
297 : * sockets. (same problem as tcp-zebra, except there is a
298 : * fix with SCM_CREDENTIALS. tcp-zebra has no such fix.)
299 : */
300 : if (suna->sun_path[0] == '@')
301 : suna->sun_path[0] = '\0';
302 : #endif
303 : }
304 6 : return true;
305 : }
306 :
307 : static struct frr_daemon_info *di = NULL;
308 :
309 4 : void frr_init_vtydir(void)
310 : {
311 4 : snprintf(frr_vtydir, sizeof(frr_vtydir), DAEMON_VTY_DIR, "", "");
312 4 : }
313 :
314 4 : void frr_preinit(struct frr_daemon_info *daemon, int argc, char **argv)
315 : {
316 4 : di = daemon;
317 4 : frr_is_after_fork = false;
318 :
319 : /* basename(), opencoded. */
320 4 : char *p = strrchr(argv[0], '/');
321 4 : di->progname = p ? p + 1 : argv[0];
322 :
323 4 : umask(0027);
324 :
325 4 : log_args_init(daemon->early_logging);
326 :
327 4 : opt_extend(&os_always);
328 4 : if (!(di->flags & FRR_NO_SPLIT_CONFIG))
329 4 : opt_extend(&os_cfg);
330 4 : if (!(di->flags & FRR_LIMITED_CLI))
331 4 : opt_extend(&os_fullcli);
332 4 : if (!(di->flags & FRR_NO_PID))
333 4 : opt_extend(&os_pid);
334 4 : if (!(di->flags & FRR_NO_PRIVSEP))
335 4 : opt_extend(&os_user);
336 4 : if (!(di->flags & FRR_NO_ZCLIENT))
337 2 : opt_extend(&os_zclient);
338 4 : if (!(di->flags & FRR_NO_TCPVTY))
339 4 : opt_extend(&os_vty);
340 4 : if (di->flags & FRR_DETACH_LATER)
341 0 : nodetach_daemon = true;
342 :
343 4 : frr_init_vtydir();
344 4 : snprintf(config_default, sizeof(config_default), "%s/%s.conf",
345 4 : frr_sysconfdir, di->name);
346 4 : snprintf(pidfile_default, sizeof(pidfile_default), "%s/%s.pid",
347 4 : frr_vtydir, di->name);
348 4 : snprintf(frr_zclientpath, sizeof(frr_zclientpath),
349 : ZEBRA_SERV_PATH, "", "");
350 : #ifdef HAVE_SQLITE3
351 : snprintf(dbfile_default, sizeof(dbfile_default), "%s/%s.db",
352 : frr_dbdir, di->name);
353 : #endif
354 :
355 4 : strlcpy(frr_protoname, di->logname, sizeof(frr_protoname));
356 4 : strlcpy(frr_protonameinst, di->logname, sizeof(frr_protonameinst));
357 :
358 4 : di->cli_mode = FRR_CLI_CLASSIC;
359 :
360 : /* we may be starting with extra FDs open for whatever purpose,
361 : * e.g. logging, some module, etc. Recording them here allows later
362 : * checking whether an fd is valid for such extension purposes,
363 : * without this we could end up e.g. logging to a BGP session fd.
364 : */
365 4 : startup_fds = 0;
366 260 : for (int i = 0; i < 64; i++) {
367 256 : struct stat st;
368 :
369 256 : if (fstat(i, &st))
370 240 : continue;
371 16 : if (S_ISDIR(st.st_mode) || S_ISBLK(st.st_mode))
372 0 : continue;
373 :
374 16 : startup_fds |= UINT64_C(0x1) << (uint64_t)i;
375 : }
376 :
377 : /* note this doesn't do anything, it just grabs state, so doing it
378 : * early in _preinit is perfect.
379 : */
380 4 : systemd_init_env();
381 4 : }
382 :
383 0 : bool frr_is_startup_fd(int fd)
384 : {
385 0 : return !!(startup_fds & (UINT64_C(0x1) << (uint64_t)fd));
386 : }
387 :
388 4 : void frr_opt_add(const char *optstr, const struct option *longopts,
389 : const char *helpstr)
390 : {
391 4 : const struct optspec main_opts = {optstr, helpstr, longopts};
392 4 : opt_extend(&main_opts);
393 4 : }
394 :
395 0 : void frr_help_exit(int status)
396 : {
397 0 : FILE *target = status ? stderr : stdout;
398 :
399 0 : if (status != 0)
400 0 : fprintf(stderr, "Invalid options.\n\n");
401 :
402 0 : if (di->printhelp)
403 0 : di->printhelp(target);
404 : else
405 0 : fprintf(target, "Usage: %s [OPTION...]\n\n%s%s%s\n\n%s",
406 : di->progname, di->proghelp, di->copyright ? "\n\n" : "",
407 0 : di->copyright ? di->copyright : "", comb_helpstr);
408 0 : fprintf(target, "\nReport bugs to %s\n", FRR_BUG_ADDRESS);
409 0 : exit(status);
410 : }
411 :
412 : struct option_chain {
413 : struct option_chain *next;
414 : const char *arg;
415 : };
416 :
417 : static struct option_chain *modules = NULL, **modnext = &modules;
418 : static int errors = 0;
419 :
420 32 : static int frr_opt(int opt)
421 : {
422 32 : static int vty_port_set = 0;
423 32 : static int vty_addr_set = 0;
424 32 : struct option_chain *oc;
425 32 : struct log_arg *log_arg;
426 32 : size_t arg_len;
427 32 : char *err;
428 :
429 32 : switch (opt) {
430 0 : case 'h':
431 0 : frr_help_exit(0);
432 0 : case 'v':
433 0 : print_version(di->progname);
434 0 : exit(0);
435 4 : break;
436 4 : case 'd':
437 4 : di->daemon_mode = true;
438 4 : break;
439 0 : case 'M':
440 0 : oc = XMALLOC(MTYPE_TMP, sizeof(*oc));
441 0 : oc->arg = optarg;
442 0 : oc->next = NULL;
443 0 : *modnext = oc;
444 0 : modnext = &oc->next;
445 0 : break;
446 0 : case 'F':
447 0 : if (!frr_defaults_profile_valid(optarg)) {
448 0 : const char **p;
449 0 : FILE *ofd = stderr;
450 :
451 0 : if (!strcmp(optarg, "help"))
452 0 : ofd = stdout;
453 : else
454 0 : fprintf(stderr,
455 : "The \"%s\" configuration profile is not valid for this FRR version.\n",
456 : optarg);
457 :
458 0 : fprintf(ofd, "Available profiles are:\n");
459 0 : for (p = frr_defaults_profiles; *p; p++)
460 0 : fprintf(ofd, "%s%s\n",
461 0 : strcmp(*p, DFLT_NAME) ? " " : " * ",
462 : *p);
463 :
464 0 : if (ofd == stdout)
465 0 : exit(0);
466 0 : fprintf(ofd, "\n");
467 0 : errors++;
468 0 : break;
469 : }
470 0 : frr_defaults_profile_set(optarg);
471 0 : break;
472 4 : case 'i':
473 4 : if (di->flags & FRR_NO_PID)
474 : return 1;
475 4 : di->pid_file = optarg;
476 4 : break;
477 4 : case 'f':
478 4 : if (di->flags & FRR_NO_SPLIT_CONFIG)
479 : return 1;
480 4 : di->config_file = optarg;
481 4 : break;
482 0 : case 'N':
483 0 : if (di->pathspace) {
484 0 : fprintf(stderr,
485 : "-N/--pathspace option specified more than once!\n");
486 0 : errors++;
487 0 : break;
488 : }
489 0 : if (di->zpathspace)
490 0 : fprintf(stderr,
491 : "-N option overridden by -z for zebra named socket path\n");
492 :
493 0 : if (strchr(optarg, '/') || strchr(optarg, '.')) {
494 0 : fprintf(stderr,
495 : "slashes or dots are not permitted in the --pathspace option.\n");
496 0 : errors++;
497 0 : break;
498 : }
499 0 : di->pathspace = optarg;
500 :
501 0 : if (!di->zpathspace)
502 0 : snprintf(frr_zclientpath, sizeof(frr_zclientpath),
503 : ZEBRA_SERV_PATH, "/", di->pathspace);
504 0 : snprintf(frr_vtydir, sizeof(frr_vtydir), DAEMON_VTY_DIR, "/",
505 0 : di->pathspace);
506 0 : snprintf(pidfile_default, sizeof(pidfile_default), "%s/%s.pid",
507 0 : frr_vtydir, di->name);
508 0 : break;
509 0 : case 'o':
510 0 : vrf_set_default_name(optarg);
511 0 : break;
512 : #ifdef HAVE_SQLITE3
513 : case OPTION_DB_FILE:
514 : if (di->flags & FRR_NO_PID)
515 : return 1;
516 : di->db_file = optarg;
517 : break;
518 : #endif
519 0 : case 'C':
520 0 : if (di->flags & FRR_NO_SPLIT_CONFIG)
521 : return 1;
522 0 : di->dryrun = true;
523 0 : break;
524 0 : case 't':
525 0 : if (di->flags & FRR_LIMITED_CLI)
526 : return 1;
527 0 : di->terminal = true;
528 0 : break;
529 0 : case 'z':
530 0 : di->zpathspace = true;
531 0 : if (di->pathspace)
532 0 : fprintf(stderr,
533 : "-z option overrides -N option for zebra named socket path\n");
534 0 : if (di->flags & FRR_NO_ZCLIENT)
535 : return 1;
536 0 : strlcpy(frr_zclientpath, optarg, sizeof(frr_zclientpath));
537 0 : break;
538 0 : case 'A':
539 0 : if (di->flags & FRR_NO_TCPVTY)
540 : return 1;
541 0 : if (vty_addr_set) {
542 0 : fprintf(stderr,
543 : "-A option specified more than once!\n");
544 0 : errors++;
545 0 : break;
546 : }
547 0 : vty_addr_set = 1;
548 0 : di->vty_addr = optarg;
549 0 : break;
550 0 : case 'P':
551 0 : if (di->flags & FRR_NO_TCPVTY)
552 : return 1;
553 0 : if (vty_port_set) {
554 0 : fprintf(stderr,
555 : "-P option specified more than once!\n");
556 0 : errors++;
557 0 : break;
558 : }
559 0 : vty_port_set = 1;
560 0 : di->vty_port = strtoul(optarg, &err, 0);
561 0 : if (*err || !*optarg) {
562 0 : fprintf(stderr,
563 : "invalid port number \"%s\" for -P option\n",
564 : optarg);
565 0 : errors++;
566 0 : break;
567 : }
568 : break;
569 4 : case OPTION_VTYSOCK:
570 4 : if (di->vty_sock_path) {
571 0 : fprintf(stderr,
572 : "--vty_socket option specified more than once!\n");
573 0 : errors++;
574 0 : break;
575 : }
576 4 : di->vty_sock_path = optarg;
577 4 : break;
578 0 : case OPTION_MODULEDIR:
579 0 : if (di->module_path) {
580 0 : fprintf(stderr,
581 : "----moduledir option specified more than once!\n");
582 0 : errors++;
583 0 : break;
584 : }
585 0 : di->module_path = optarg;
586 0 : break;
587 0 : case OPTION_SCRIPTDIR:
588 0 : if (di->script_path) {
589 0 : fprintf(stderr, "--scriptdir option specified more than once!\n");
590 0 : errors++;
591 0 : break;
592 : }
593 0 : di->script_path = optarg;
594 0 : break;
595 0 : case OPTION_TCLI:
596 0 : di->cli_mode = FRR_CLI_TRANSACTIONAL;
597 0 : break;
598 0 : case 'u':
599 0 : if (di->flags & FRR_NO_PRIVSEP)
600 : return 1;
601 0 : di->privs->user = optarg;
602 0 : break;
603 0 : case 'g':
604 0 : if (di->flags & FRR_NO_PRIVSEP)
605 : return 1;
606 0 : di->privs->group = optarg;
607 0 : break;
608 8 : case OPTION_LOG:
609 8 : arg_len = strlen(optarg) + 1;
610 8 : log_arg = XCALLOC(MTYPE_TMP, sizeof(*log_arg) + arg_len);
611 8 : memcpy(log_arg->target, optarg, arg_len);
612 8 : log_args_add_tail(di->early_logging, log_arg);
613 8 : break;
614 4 : case OPTION_LOGLEVEL:
615 4 : di->early_loglevel = optarg;
616 4 : break;
617 0 : case OPTION_LOGGING:
618 0 : di->log_always = true;
619 0 : break;
620 0 : case OPTION_LIMIT_FDS:
621 0 : di->limit_fds = strtoul(optarg, &err, 0);
622 0 : break;
623 : default:
624 : return 1;
625 : }
626 : return 0;
627 : }
628 :
629 4 : int frr_getopt(int argc, char *const argv[], int *longindex)
630 : {
631 4 : int opt;
632 4 : int lidx;
633 :
634 4 : comb_next_lo->name = NULL;
635 :
636 32 : do {
637 32 : opt = getopt_long(argc, argv, comb_optstr, comb_lo, &lidx);
638 32 : if (frr_opt(opt))
639 : break;
640 28 : } while (opt != -1);
641 :
642 4 : if (opt == -1 && errors)
643 0 : frr_help_exit(1);
644 4 : if (longindex)
645 0 : *longindex = lidx;
646 4 : return opt;
647 : }
648 :
649 8 : static void frr_mkdir(const char *path, bool strip)
650 : {
651 8 : char buf[256];
652 8 : mode_t prev;
653 8 : int ret;
654 8 : struct zprivs_ids_t ids;
655 :
656 8 : if (strip) {
657 4 : char *slash = strrchr(path, '/');
658 4 : size_t plen;
659 4 : if (!slash)
660 6 : return;
661 4 : plen = slash - path;
662 4 : if (plen > sizeof(buf) - 1)
663 : return;
664 4 : memcpy(buf, path, plen);
665 4 : buf[plen] = '\0';
666 4 : path = buf;
667 : }
668 :
669 : /* o+rx (..5) is needed for the frrvty group to work properly;
670 : * without it, users in the frrvty group can't access the vty sockets.
671 : */
672 8 : prev = umask(0022);
673 8 : ret = mkdir(path, 0755);
674 8 : umask(prev);
675 :
676 8 : if (ret != 0) {
677 : /* if EEXIST, return without touching the permissions,
678 : * so user-set custom permissions are left in place
679 : */
680 6 : if (errno == EEXIST)
681 : return;
682 :
683 0 : flog_err(EC_LIB_SYSTEM_CALL, "failed to mkdir \"%s\": %s", path,
684 : strerror(errno));
685 0 : return;
686 : }
687 :
688 2 : zprivs_get_ids(&ids);
689 2 : if (chown(path, ids.uid_normal, ids.gid_normal))
690 2 : flog_err(EC_LIB_SYSTEM_CALL, "failed to chown \"%s\": %s", path,
691 : strerror(errno));
692 : }
693 :
694 0 : static void _err_print(const void *cookie, const char *errstr)
695 : {
696 0 : const char *prefix = (const char *)cookie;
697 :
698 0 : fprintf(stderr, "%s: %s\n", prefix, errstr);
699 0 : }
700 :
701 : static struct event_loop *master;
702 4 : struct event_loop *frr_init(void)
703 : {
704 4 : struct option_chain *oc;
705 4 : struct log_arg *log_arg;
706 4 : struct frrmod_runtime *module;
707 4 : struct zprivs_ids_t ids;
708 4 : char p_instance[16] = "", p_pathspace[256] = "";
709 4 : const char *dir;
710 :
711 4 : dir = di->module_path ? di->module_path : frr_moduledir;
712 :
713 4 : srandom(time(NULL));
714 4 : frr_defaults_apply();
715 :
716 4 : if (di->instance) {
717 0 : snprintf(frr_protonameinst, sizeof(frr_protonameinst), "%s[%u]",
718 : di->logname, di->instance);
719 0 : snprintf(p_instance, sizeof(p_instance), "-%d", di->instance);
720 : }
721 4 : if (di->pathspace)
722 0 : snprintf(p_pathspace, sizeof(p_pathspace), "%s/",
723 : di->pathspace);
724 :
725 4 : snprintf(config_default, sizeof(config_default), "%s%s%s%s.conf",
726 4 : frr_sysconfdir, p_pathspace, di->name, p_instance);
727 4 : snprintf(pidfile_default, sizeof(pidfile_default), "%s/%s%s.pid",
728 4 : frr_vtydir, di->name, p_instance);
729 : #ifdef HAVE_SQLITE3
730 : snprintf(dbfile_default, sizeof(dbfile_default), "%s/%s%s%s.db",
731 : frr_dbdir, p_pathspace, di->name, p_instance);
732 : #endif
733 :
734 4 : zprivs_preinit(di->privs);
735 4 : zprivs_get_ids(&ids);
736 :
737 4 : zlog_init(di->progname, di->logname, di->instance,
738 : ids.uid_normal, ids.gid_normal);
739 :
740 16 : while ((log_arg = log_args_pop(di->early_logging))) {
741 8 : command_setup_early_logging(log_arg->target,
742 : di->early_loglevel);
743 : /* this is a bit of a hack,
744 : but need to notice when
745 : the target is stdout */
746 8 : if (strcmp(log_arg->target, "stdout") == 0)
747 0 : logging_to_stdout = true;
748 12 : XFREE(MTYPE_TMP, log_arg);
749 : }
750 :
751 4 : if (!frr_zclient_addr(&zclient_addr, &zclient_addr_len,
752 : frr_zclientpath)) {
753 0 : fprintf(stderr, "Invalid zserv socket path: %s\n",
754 : frr_zclientpath);
755 0 : exit(1);
756 : }
757 :
758 : /* don't mkdir these as root... */
759 4 : if (!(di->flags & FRR_NO_PRIVSEP)) {
760 4 : if (!di->pid_file || !di->vty_path)
761 4 : frr_mkdir(frr_vtydir, false);
762 4 : if (di->pid_file)
763 4 : frr_mkdir(di->pid_file, true);
764 4 : if (di->vty_path)
765 0 : frr_mkdir(di->vty_path, true);
766 : }
767 :
768 4 : frrmod_init(di->module);
769 4 : while (modules) {
770 0 : modules = (oc = modules)->next;
771 0 : module = frrmod_load(oc->arg, dir, _err_print, __func__);
772 0 : if (!module)
773 0 : exit(1);
774 4 : XFREE(MTYPE_TMP, oc);
775 : }
776 :
777 4 : zprivs_init(di->privs);
778 :
779 4 : master = event_master_create(NULL);
780 4 : signal_init(master, di->n_signals, di->signals);
781 4 : hook_call(frr_early_init, master);
782 :
783 : #ifdef HAVE_SQLITE3
784 : if (!di->db_file)
785 : di->db_file = dbfile_default;
786 : db_init("%s", di->db_file);
787 : #endif
788 :
789 4 : if (di->flags & FRR_LIMITED_CLI)
790 0 : cmd_init(-1);
791 : else
792 4 : cmd_init(1);
793 :
794 4 : vty_init(master, di->log_always);
795 4 : lib_cmd_init();
796 :
797 4 : frr_pthread_init();
798 : #ifdef HAVE_SCRIPTING
799 : frrscript_init(di->script_path ? di->script_path : frr_scriptdir);
800 : #endif
801 :
802 4 : log_ref_init();
803 4 : log_ref_vty_init();
804 4 : lib_error_init();
805 :
806 4 : nb_init(master, di->yang_modules, di->n_yang_modules, true);
807 4 : if (nb_db_init() != NB_OK)
808 0 : flog_warn(EC_LIB_NB_DATABASE,
809 : "%s: failed to initialize northbound database",
810 : __func__);
811 :
812 4 : debug_init_cli();
813 :
814 4 : return master;
815 : }
816 :
817 0 : const char *frr_get_progname(void)
818 : {
819 0 : return di ? di->progname : NULL;
820 : }
821 :
822 140 : enum frr_cli_mode frr_get_cli_mode(void)
823 : {
824 140 : return di ? di->cli_mode : FRR_CLI_CLASSIC;
825 : }
826 :
827 18 : uint32_t frr_get_fd_limit(void)
828 : {
829 18 : return di ? di->limit_fds : 0;
830 : }
831 :
832 : static int rcvd_signal = 0;
833 :
834 0 : static void rcv_signal(int signum)
835 : {
836 0 : rcvd_signal = signum;
837 : /* poll() is interrupted by the signal; handled below */
838 0 : }
839 :
840 4 : static void frr_daemon_wait(int fd)
841 : {
842 4 : struct pollfd pfd[1];
843 4 : int ret;
844 4 : pid_t exitpid;
845 4 : int exitstat;
846 4 : sigset_t sigs, prevsigs;
847 :
848 4 : sigemptyset(&sigs);
849 4 : sigaddset(&sigs, SIGTSTP);
850 4 : sigaddset(&sigs, SIGQUIT);
851 4 : sigaddset(&sigs, SIGINT);
852 4 : sigprocmask(SIG_BLOCK, &sigs, &prevsigs);
853 :
854 4 : struct sigaction sa = {
855 : .sa_handler = rcv_signal, .sa_flags = SA_RESETHAND,
856 : };
857 4 : sigemptyset(&sa.sa_mask);
858 4 : sigaction(SIGTSTP, &sa, NULL);
859 4 : sigaction(SIGQUIT, &sa, NULL);
860 4 : sigaction(SIGINT, &sa, NULL);
861 :
862 4 : do {
863 4 : char buf[1];
864 4 : ssize_t nrecv;
865 :
866 4 : pfd[0].fd = fd;
867 4 : pfd[0].events = POLLIN;
868 :
869 4 : rcvd_signal = 0;
870 :
871 : #if defined(HAVE_PPOLL)
872 4 : ret = ppoll(pfd, 1, NULL, &prevsigs);
873 : #elif defined(HAVE_POLLTS)
874 : ret = pollts(pfd, 1, NULL, &prevsigs);
875 : #else
876 : /* racy -- only used on FreeBSD 9 */
877 : sigset_t tmpsigs;
878 : sigprocmask(SIG_SETMASK, &prevsigs, &tmpsigs);
879 : ret = poll(pfd, 1, -1);
880 : sigprocmask(SIG_SETMASK, &tmpsigs, NULL);
881 : #endif
882 4 : if (ret < 0 && errno != EINTR && errno != EAGAIN) {
883 0 : perror("poll()");
884 0 : exit(1);
885 : }
886 4 : switch (rcvd_signal) {
887 0 : case SIGTSTP:
888 0 : send(fd, "S", 1, 0);
889 0 : do {
890 0 : nrecv = recv(fd, buf, sizeof(buf), 0);
891 : } while (nrecv == -1
892 0 : && (errno == EINTR || errno == EAGAIN));
893 :
894 0 : raise(SIGTSTP);
895 0 : sigaction(SIGTSTP, &sa, NULL);
896 0 : send(fd, "R", 1, 0);
897 0 : break;
898 0 : case SIGINT:
899 0 : send(fd, "I", 1, 0);
900 0 : break;
901 0 : case SIGQUIT:
902 0 : send(fd, "Q", 1, 0);
903 0 : break;
904 : }
905 4 : } while (ret <= 0);
906 :
907 4 : exitpid = waitpid(-1, &exitstat, WNOHANG);
908 4 : if (exitpid == 0)
909 : /* child successfully went to main loop & closed socket */
910 4 : exit(0);
911 :
912 : /* child failed one way or another ... */
913 0 : if (WIFEXITED(exitstat) && WEXITSTATUS(exitstat) == 0)
914 : /* can happen in --terminal case if exit is fast enough */
915 : (void)0;
916 0 : else if (WIFEXITED(exitstat))
917 0 : fprintf(stderr, "%s failed to start, exited %d\n", di->name,
918 0 : WEXITSTATUS(exitstat));
919 0 : else if (WIFSIGNALED(exitstat))
920 0 : fprintf(stderr, "%s crashed in startup, signal %d\n", di->name,
921 : WTERMSIG(exitstat));
922 : else
923 0 : fprintf(stderr, "%s failed to start, unknown problem\n",
924 0 : di->name);
925 0 : exit(1);
926 : }
927 :
928 : static int daemon_ctl_sock = -1;
929 :
930 4 : static void frr_daemonize(void)
931 : {
932 4 : int fds[2];
933 4 : pid_t pid;
934 :
935 4 : if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds)) {
936 0 : perror("socketpair() for daemon control");
937 0 : exit(1);
938 : }
939 4 : set_cloexec(fds[0]);
940 4 : set_cloexec(fds[1]);
941 :
942 4 : pid = fork();
943 8 : if (pid < 0) {
944 0 : perror("fork()");
945 0 : exit(1);
946 : }
947 8 : if (pid == 0) {
948 : /* child */
949 4 : close(fds[0]);
950 4 : if (setsid() < 0) {
951 0 : perror("setsid()");
952 0 : exit(1);
953 : }
954 :
955 4 : daemon_ctl_sock = fds[1];
956 4 : return;
957 : }
958 :
959 4 : close(fds[1]);
960 4 : nb_terminate();
961 4 : yang_terminate();
962 4 : frr_daemon_wait(fds[0]);
963 : }
964 :
965 : /*
966 : * Why is this a thread?
967 : *
968 : * The read in of config for integrated config happens *after*
969 : * thread execution starts( because it is passed in via a vtysh -b -n )
970 : * While if you are not using integrated config we want the ability
971 : * to read the config in after thread execution starts, so that
972 : * we can match this behavior.
973 : */
974 4 : static void frr_config_read_in(struct event *t)
975 : {
976 4 : hook_call(frr_config_pre, master);
977 :
978 4 : if (!vty_read_config(vty_shared_candidate_config, di->config_file,
979 : config_default)
980 0 : && di->backup_config_file) {
981 0 : char *orig = XSTRDUP(MTYPE_TMP, host_config_get());
982 :
983 0 : zlog_info("Attempting to read backup config file: %s specified",
984 : di->backup_config_file);
985 0 : vty_read_config(vty_shared_candidate_config,
986 0 : di->backup_config_file, config_default);
987 :
988 0 : host_config_set(orig);
989 0 : XFREE(MTYPE_TMP, orig);
990 : }
991 :
992 : /*
993 : * Automatically commit the candidate configuration after
994 : * reading the configuration file.
995 : */
996 4 : if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL) {
997 0 : struct nb_context context = {};
998 0 : char errmsg[BUFSIZ] = {0};
999 0 : int ret;
1000 :
1001 0 : context.client = NB_CLIENT_CLI;
1002 0 : ret = nb_candidate_commit(context, vty_shared_candidate_config,
1003 : true, "Read configuration file", NULL,
1004 : errmsg, sizeof(errmsg));
1005 0 : if (ret != NB_OK && ret != NB_ERR_NO_CHANGES)
1006 0 : zlog_err(
1007 : "%s: failed to read configuration file: %s (%s)",
1008 : __func__, nb_err_name(ret), errmsg);
1009 : }
1010 :
1011 4 : hook_call(frr_config_post, master);
1012 4 : }
1013 :
1014 4 : void frr_config_fork(void)
1015 : {
1016 4 : hook_call(frr_late_init, master);
1017 :
1018 4 : if (!(di->flags & FRR_NO_SPLIT_CONFIG)) {
1019 : /* Don't start execution if we are in dry-run mode */
1020 4 : if (di->dryrun) {
1021 0 : frr_config_read_in(NULL);
1022 0 : exit(0);
1023 : }
1024 :
1025 4 : event_add_event(master, frr_config_read_in, NULL, 0,
1026 : &di->read_in);
1027 : }
1028 :
1029 4 : if (di->daemon_mode || di->terminal)
1030 4 : frr_daemonize();
1031 :
1032 4 : frr_is_after_fork = true;
1033 :
1034 4 : if (!di->pid_file)
1035 0 : di->pid_file = pidfile_default;
1036 4 : pid_output(di->pid_file);
1037 4 : zlog_tls_buffer_init();
1038 4 : }
1039 :
1040 4 : void frr_vty_serv_start(void)
1041 : {
1042 : /* allow explicit override of vty_path in the future
1043 : * (not currently set anywhere) */
1044 4 : if (!di->vty_path) {
1045 4 : const char *dir;
1046 4 : char defvtydir[256];
1047 :
1048 4 : snprintf(defvtydir, sizeof(defvtydir), "%s", frr_vtydir);
1049 :
1050 4 : dir = di->vty_sock_path ? di->vty_sock_path : defvtydir;
1051 :
1052 4 : if (di->instance)
1053 0 : snprintf(vtypath_default, sizeof(vtypath_default),
1054 : "%s/%s-%d.vty", dir, di->name, di->instance);
1055 : else
1056 4 : snprintf(vtypath_default, sizeof(vtypath_default),
1057 : "%s/%s.vty", dir, di->name);
1058 :
1059 4 : di->vty_path = vtypath_default;
1060 : }
1061 :
1062 4 : vty_serv_start(di->vty_addr, di->vty_port, di->vty_path);
1063 4 : }
1064 :
1065 0 : void frr_vty_serv_stop(void)
1066 : {
1067 0 : vty_serv_stop();
1068 :
1069 0 : if (di->vty_path)
1070 0 : unlink(di->vty_path);
1071 0 : }
1072 :
1073 4 : static void frr_check_detach(void)
1074 : {
1075 4 : if (nodetach_term || nodetach_daemon)
1076 : return;
1077 :
1078 4 : if (daemon_ctl_sock != -1)
1079 4 : close(daemon_ctl_sock);
1080 4 : daemon_ctl_sock = -1;
1081 : }
1082 :
1083 0 : static void frr_terminal_close(int isexit)
1084 : {
1085 0 : int nullfd;
1086 :
1087 0 : nodetach_term = false;
1088 0 : frr_check_detach();
1089 :
1090 0 : if (!di->daemon_mode || isexit) {
1091 0 : printf("\n%s exiting\n", di->name);
1092 0 : if (!isexit)
1093 0 : raise(SIGINT);
1094 0 : return;
1095 : } else {
1096 0 : printf("\n%s daemonizing\n", di->name);
1097 0 : fflush(stdout);
1098 : }
1099 :
1100 0 : nullfd = open("/dev/null", O_RDONLY | O_NOCTTY);
1101 0 : if (nullfd == -1) {
1102 0 : flog_err_sys(EC_LIB_SYSTEM_CALL,
1103 : "%s: failed to open /dev/null: %s", __func__,
1104 : safe_strerror(errno));
1105 : } else {
1106 : int fd;
1107 : /*
1108 : * only redirect stdin, stdout, stderr to null when a tty also
1109 : * don't redirect when stdout is set with --log stdout
1110 : */
1111 0 : for (fd = 2; fd >= 0; fd--)
1112 0 : if (isatty(fd) &&
1113 0 : (fd != STDOUT_FILENO || !logging_to_stdout))
1114 0 : dup2(nullfd, fd);
1115 0 : close(nullfd);
1116 : }
1117 : }
1118 :
1119 : static struct event *daemon_ctl_thread = NULL;
1120 :
1121 0 : static void frr_daemon_ctl(struct event *t)
1122 : {
1123 0 : char buf[1];
1124 0 : ssize_t nr;
1125 :
1126 0 : nr = recv(daemon_ctl_sock, buf, sizeof(buf), 0);
1127 0 : if (nr < 0 && (errno == EINTR || errno == EAGAIN))
1128 0 : goto out;
1129 0 : if (nr <= 0)
1130 0 : return;
1131 :
1132 0 : switch (buf[0]) {
1133 0 : case 'S': /* SIGTSTP */
1134 0 : vty_stdio_suspend();
1135 0 : if (send(daemon_ctl_sock, "s", 1, 0) < 0)
1136 0 : zlog_err("%s send(\"s\") error (SIGTSTP propagation)",
1137 : (di && di->name ? di->name : ""));
1138 : break;
1139 0 : case 'R': /* SIGTCNT [implicit] */
1140 0 : vty_stdio_resume();
1141 0 : break;
1142 0 : case 'I': /* SIGINT */
1143 0 : di->daemon_mode = false;
1144 0 : raise(SIGINT);
1145 0 : break;
1146 0 : case 'Q': /* SIGQUIT */
1147 0 : di->daemon_mode = true;
1148 0 : vty_stdio_close();
1149 0 : break;
1150 : }
1151 :
1152 0 : out:
1153 0 : event_add_read(master, frr_daemon_ctl, NULL, daemon_ctl_sock,
1154 : &daemon_ctl_thread);
1155 : }
1156 :
1157 0 : void frr_detach(void)
1158 : {
1159 0 : nodetach_daemon = false;
1160 0 : frr_check_detach();
1161 0 : }
1162 :
1163 4 : void frr_run(struct event_loop *master)
1164 : {
1165 4 : char instanceinfo[64] = "";
1166 :
1167 4 : if (!(di->flags & FRR_MANUAL_VTY_START))
1168 4 : frr_vty_serv_start();
1169 :
1170 4 : if (di->instance)
1171 0 : snprintf(instanceinfo, sizeof(instanceinfo), "instance %u ",
1172 : di->instance);
1173 :
1174 4 : zlog_notice("%s %s starting: %svty@%d%s", di->name, FRR_VERSION,
1175 : instanceinfo, di->vty_port, di->startinfo);
1176 :
1177 4 : if (di->terminal) {
1178 0 : nodetach_term = true;
1179 :
1180 0 : vty_stdio(frr_terminal_close);
1181 0 : if (daemon_ctl_sock != -1) {
1182 0 : set_nonblocking(daemon_ctl_sock);
1183 0 : event_add_read(master, frr_daemon_ctl, NULL,
1184 : daemon_ctl_sock, &daemon_ctl_thread);
1185 : }
1186 4 : } else if (di->daemon_mode) {
1187 4 : int nullfd = open("/dev/null", O_RDONLY | O_NOCTTY);
1188 4 : if (nullfd == -1) {
1189 0 : flog_err_sys(EC_LIB_SYSTEM_CALL,
1190 : "%s: failed to open /dev/null: %s",
1191 : __func__, safe_strerror(errno));
1192 : } else {
1193 : int fd;
1194 : /*
1195 : * only redirect stdin, stdout, stderr to null when a
1196 : * tty also don't redirect when stdout is set with --log
1197 : * stdout
1198 : */
1199 16 : for (fd = 2; fd >= 0; fd--)
1200 12 : if (isatty(fd) &&
1201 0 : (fd != STDOUT_FILENO || !logging_to_stdout))
1202 0 : dup2(nullfd, fd);
1203 4 : close(nullfd);
1204 : }
1205 :
1206 4 : frr_check_detach();
1207 : }
1208 :
1209 : /* end fixed stderr startup logging */
1210 4 : zlog_startup_end();
1211 :
1212 4 : struct event thread;
1213 442 : while (event_fetch(master, &thread))
1214 440 : event_call(&thread);
1215 0 : }
1216 :
1217 4 : void frr_early_fini(void)
1218 : {
1219 4 : hook_call(frr_early_fini);
1220 4 : }
1221 :
1222 4 : void frr_fini(void)
1223 : {
1224 4 : FILE *fp;
1225 4 : char filename[128];
1226 4 : int have_leftovers = 0;
1227 :
1228 4 : hook_call(frr_fini);
1229 :
1230 4 : vty_terminate();
1231 4 : cmd_terminate();
1232 4 : nb_terminate();
1233 4 : yang_terminate();
1234 : #ifdef HAVE_SQLITE3
1235 : db_close();
1236 : #endif
1237 4 : log_ref_fini();
1238 :
1239 : #ifdef HAVE_SCRIPTING
1240 : frrscript_fini();
1241 : #endif
1242 4 : frr_pthread_finish();
1243 4 : zprivs_terminate(di->privs);
1244 : /* signal_init -> nothing needed */
1245 4 : event_master_free(master);
1246 4 : master = NULL;
1247 4 : zlog_tls_buffer_fini();
1248 4 : zlog_fini();
1249 : /* frrmod_init -> nothing needed / hooks */
1250 4 : rcu_shutdown();
1251 :
1252 : /* also log memstats to stderr when stderr goes to a file*/
1253 4 : if (debug_memstats_at_exit || !isatty(STDERR_FILENO))
1254 4 : have_leftovers = log_memstats(stderr, di->name);
1255 :
1256 : /* in case we decide at runtime that we want exit-memstats for
1257 : * a daemon
1258 : * (only do this if we actually have something to print though)
1259 : */
1260 4 : if (!debug_memstats_at_exit || !have_leftovers)
1261 0 : return;
1262 :
1263 12 : snprintf(filename, sizeof(filename), "/tmp/frr-memstats-%s-%llu-%llu",
1264 4 : di->name, (unsigned long long)getpid(),
1265 4 : (unsigned long long)time(NULL));
1266 :
1267 4 : fp = fopen(filename, "w");
1268 4 : if (fp) {
1269 4 : log_memstats(fp, di->name);
1270 4 : fclose(fp);
1271 : }
1272 : }
1273 :
1274 : #ifdef INTERP
1275 : static const char interp[]
1276 : __attribute__((section(".interp"), used)) = INTERP;
1277 : #endif
1278 : /*
1279 : * executable entry point for libfrr.so
1280 : *
1281 : * note that libc initialization is skipped for this so the set of functions
1282 : * that can be called is rather limited
1283 : */
1284 : extern void _libfrr_version(void)
1285 : __attribute__((visibility("hidden"), noreturn));
1286 0 : void _libfrr_version(void)
1287 : {
1288 0 : const char banner[] =
1289 : FRR_FULL_NAME " " FRR_VERSION ".\n"
1290 : FRR_COPYRIGHT GIT_INFO "\n"
1291 : "configured with:\n " FRR_CONFIG_ARGS "\n";
1292 0 : write(1, banner, sizeof(banner) - 1);
1293 0 : _exit(0);
1294 : }
|