diff --git a/doc/manpages/vtysh.rst b/doc/manpages/vtysh.rst index af527bea407c..94ba3baebd29 100644 --- a/doc/manpages/vtysh.rst +++ b/doc/manpages/vtysh.rst @@ -67,6 +67,11 @@ OPTIONS available for the vtysh command: Display a usage message on standard output and exit. +.. option:: --no-fork + + When used in conjunction with ``-b``, prevents vtysh from forking children to handle configuring each target daemon. + + ENVIRONMENT VARIABLES ===================== VTYSH_PAGER diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c index 0a164ec16d5b..93c3e9ae1e19 100644 --- a/vtysh/vtysh.c +++ b/vtysh/vtysh.c @@ -49,19 +49,6 @@ char *vtysh_pager_name = NULL; /* VTY should add timestamp */ bool vtysh_add_timestamp; -/* VTY shell client structure */ -struct vtysh_client { - int fd; - const char *name; - int flag; - char path[MAXPATHLEN]; - struct vtysh_client *next; - - struct event *log_reader; - int log_fd; - uint32_t lost_msgs; -}; - static bool stderr_tty; static bool stderr_stdout_same; @@ -119,6 +106,10 @@ static void vtysh_pager_envdef(bool fallback) /* --- */ +/* + * When updating this array, remember to change the array size here and in + * vtysh.h + */ struct vtysh_client vtysh_client[] = { {.name = "mgmtd", .flag = VTYSH_MGMTD}, {.name = "zebra", .flag = VTYSH_ZEBRA}, @@ -144,6 +135,8 @@ struct vtysh_client vtysh_client[] = { {.name = "pim6d", .flag = VTYSH_PIM6D}, }; +char my_client[64]; + /* Searches for client by name, returns index */ static int vtysh_client_lookup(const char *name) { diff --git a/vtysh/vtysh.h b/vtysh/vtysh.h index cb6357f5e652..e43dd6935070 100644 --- a/vtysh/vtysh.h +++ b/vtysh/vtysh.h @@ -119,4 +119,19 @@ extern int user_mode; extern bool vtysh_add_timestamp; +struct vtysh_client { + int fd; + const char *name; + int flag; + char path[MAXPATHLEN]; + struct vtysh_client *next; + + struct event *log_reader; + int log_fd; + uint32_t lost_msgs; +}; + +extern struct vtysh_client vtysh_client[22]; +extern char my_client[64]; + #endif /* VTYSH_H */ diff --git a/vtysh/vtysh_main.c b/vtysh/vtysh_main.c index c22889a4f6cd..1721a8370b15 100644 --- a/vtysh/vtysh_main.c +++ b/vtysh/vtysh_main.c @@ -178,6 +178,7 @@ static void usage(int status) "-u --user Run as an unprivileged user\n" "-w, --writeconfig Write integrated config (frr.conf) and exit\n" "-H, --histfile Override history file\n" + " --no-fork Don't fork clients to handle daemons (slower for large configs)\n" "-h, --help Display this help and exit\n\n" "Note that multiple commands may be executed from the command\n" "line by passing multiple -c args, or by embedding linefeed\n" @@ -191,6 +192,7 @@ static void usage(int status) /* VTY shell options, we use GNU getopt library. */ #define OPTION_VTYSOCK 1000 #define OPTION_CONFDIR 1001 +#define OPTION_NOFORK 1002 struct option longopts[] = { {"boot", no_argument, NULL, 'b'}, /* For compatibility with older zebra/quagga versions */ @@ -210,6 +212,7 @@ struct option longopts[] = { {"pathspace", required_argument, NULL, 'N'}, {"user", no_argument, NULL, 'u'}, {"timestamp", no_argument, NULL, 't'}, + {"no-fork", no_argument, NULL, OPTION_NOFORK}, {0}}; bool vtysh_loop_exited; @@ -321,6 +324,7 @@ int main(int argc, char **argv, char **env) int dryrun = 0; int boot_flag = 0; bool ts_flag = false; + bool no_fork = false; const char *daemon_name = NULL; const char *inputfile = NULL; struct cmd_rec { @@ -392,6 +396,9 @@ int main(int argc, char **argv, char **env) ditch_suid = 1; /* option disables SUID */ snprintf(sysconfdir, sizeof(sysconfdir), "%s/", optarg); break; + case OPTION_NOFORK: + no_fork = true; + break; case 'N': if (strchr(optarg, '/') || strchr(optarg, '.')) { fprintf(stderr, @@ -440,6 +447,10 @@ int main(int argc, char **argv, char **env) } } + /* No need for forks if we're talking to 1 daemon */ + if (daemon_name) + no_fork = true; + if (ditch_suid) { elevuid = realuid; elevgid = realgid; @@ -705,19 +716,95 @@ int main(int argc, char **argv, char **env) /* Boot startup configuration file. */ if (boot_flag) { + /* + * flock config file before fork. After fork, each child will + * hold the same lock. The lock can be released by any one of + * them but they will exit without releasing the lock - the + * parent (us) will release it when they are done + */ vtysh_flock_config(frr_config); + + /* + * In boot mode, we need to apply the whole config file to all + * daemons. Instead of having one client talk to N daemons, we + * fork N times and let each child handle one daemon. + */ + pid_t fork_pid = getpid(); + int status = 0; + + if (!no_fork) { + for (unsigned int i = 0; i < array_size(vtysh_client); + i++) { + /* Store name of client this fork will handle */ + strlcpy(my_client, vtysh_client[i].name, + sizeof(my_client)); + fork_pid = fork(); + + /* If child, break */ + if (fork_pid == 0) + break; + } + + /* parent, wait for children */ + if (fork_pid != 0) { + fprintf(stdout, + "Waiting for children to finish applying config...\n"); + while (wait(&status) > 0) + ; + ret = 0; + goto boot_done; + } + + /* + * children, grow up to be cowboys + */ + for (unsigned int i = 0; i < array_size(vtysh_client); + i++) { + if (strcmp(my_client, vtysh_client[i].name)) { + /* + * If this is a client we aren't + * responsible for, disconnect + */ + if (vtysh_client[i].fd >= 0) + close(vtysh_client[i].fd); + vtysh_client[i].fd = -1; + } else if (vtysh_client[i].fd == -1) { + /* + * If this is the client we are + * responsible for, but we aren't + * already connected to that client, + * that means the client isn't up in the + * first place and we can exit early + */ + ret = 0; + goto boot_done; + } + } + + fprintf(stdout, "[%d|%s] sending configuration\n", + getpid(), my_client); + } + ret = vtysh_read_config(frr_config, dryrun); - vtysh_unflock_config(); if (ret) { - fprintf(stderr, - "Configuration file[%s] processing failure: %d\n", - frr_config, ret); - if (no_error) - exit(0); + if (!no_fork) + fprintf(stderr, + "[%d|%s] Configuration file[%s] processing failure: %d\n", + getpid(), my_client, frr_config, ret); else - exit(ret); - } else - exit(0); + fprintf(stderr, + "Configuration file[%s] processing failure: %d\n", + frr_config, ret); + if (no_error) + ret = 0; + } else if (!no_fork) { + fprintf(stderr, "[%d|%s] done\n", getpid(), my_client); + } + + boot_done: + if (fork_pid != 0) + vtysh_unflock_config(); + exit(ret); } vtysh_readline_init();