From a9e45514d4843b922263222881490fe982c5761a Mon Sep 17 00:00:00 2001 From: Fredi Raspall Date: Mon, 20 Jan 2025 16:37:36 +0100 Subject: [PATCH] Add extensions for vtysh Add very simple support for vtysh extensions. The approach is similar to FRR's modules, but much simpler. FRR's modules cannot be used straight away since vtysh does not follow the same pattern as protocol daemons. E.g. it does not call frr_init(). Extensions are loaded with --extension|-X in the cmd line and can be displayed with "show vtysh extensions". Extensions are just like modules (some dynamically-loadable library) with entry point "vtysh_extension". The code expects that the extension return 0 on success and !=0 otherwise, but vtysh will start regardless. Signed-off-by: Fredi Raspall --- vtysh/subdir.am | 2 + vtysh/vtysh.c | 20 +++++++++- vtysh/vtysh_extensions.c | 82 ++++++++++++++++++++++++++++++++++++++++ vtysh/vtysh_extensions.h | 22 +++++++++++ vtysh/vtysh_main.c | 9 ++++- 5 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 vtysh/vtysh_extensions.c create mode 100644 vtysh/vtysh_extensions.h diff --git a/vtysh/subdir.am b/vtysh/subdir.am index d39987eb83c8..ae0d9cf85ad9 100644 --- a/vtysh/subdir.am +++ b/vtysh/subdir.am @@ -16,11 +16,13 @@ vtysh_vtysh_SOURCES = \ vtysh/vtysh.c \ vtysh/vtysh_user.c \ vtysh/vtysh_config.c \ + vtysh/vtysh_extensions.c \ # end noinst_HEADERS += \ vtysh/vtysh.h \ vtysh/vtysh_user.h \ + vtysh/vtysh_extensions.h \ # end vtysh_vtysh_LDADD = lib/libfrr.la $(LIBCAP) $(LIBREADLINE) $(LIBS) $(LIBPAM) diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c index 92f37f193a96..c02161295339 100644 --- a/vtysh/vtysh.c +++ b/vtysh/vtysh.c @@ -40,6 +40,7 @@ #include "json.h" #include "ferr.h" #include "sockopt.h" +#include "vtysh_extensions.h" DEFINE_MTYPE_STATIC(MVTYSH, VTYSH_CMD, "Vtysh cmd copy"); @@ -462,7 +463,7 @@ static int vtysh_client_execute(struct vtysh_client *head_client, } /* Execute by name */ -static int vtysh_client_execute_name(const char *name, const char *line) +int vtysh_client_execute_name(const char *name, const char *line) { int ret = CMD_SUCCESS; int idx_client = -1; @@ -3013,7 +3014,7 @@ static int show_per_daemon(struct vty *vty, struct cmd_token **argv, int argc, } #pragma GCC diagnostic pop -static int show_one_daemon(struct vty *vty, struct cmd_token **argv, int argc, +int show_one_daemon(struct vty *vty, struct cmd_token **argv, int argc, const char *name) { int ret; @@ -4623,6 +4624,18 @@ DEFUN_HIDDEN(show_cli_graph_vtysh, return CMD_SUCCESS; } +DEFUN (show_vtysh_extensions, + show_vtysh_extensions_cmd, + "show vtysh extensions", + SHOW_STR + "VTYSH\n" + "extension info\n") +{ + vtysh_show_extensions(vty); + return CMD_SUCCESS; +} + + static void vtysh_install_default(enum node_type node) { _install_element(node, &config_list_cmd); @@ -5560,4 +5573,7 @@ void vtysh_init_vty(void) install_element(CONFIG_NODE, &no_vtysh_password_cmd); install_element(CONFIG_NODE, &vtysh_enable_password_cmd); install_element(CONFIG_NODE, &no_vtysh_enable_password_cmd); + + /* vtysh extensions */ + install_element(VIEW_NODE, &show_vtysh_extensions_cmd); } diff --git a/vtysh/vtysh_extensions.c b/vtysh/vtysh_extensions.c new file mode 100644 index 000000000000..96d55152abc1 --- /dev/null +++ b/vtysh/vtysh_extensions.c @@ -0,0 +1,82 @@ +#include +#include + +#include "memory.h" +#include "vtysh_extensions.h" +#include "lib/vty.h" +#include "vtysh.h" + +DEFINE_MTYPE(MVTYSH, VTYSH_EXT, "Vtysh extension"); +DEFINE_MTYPE(MVTYSH, VTYSH_EXT_NAME, "Vtysh extension name"); + +static struct vtysh_ext *extensions = NULL; + +static struct vtysh_ext *find_extension(char *name) +{ + for(struct vtysh_ext *e = extensions; e != NULL; e = e->next) + if (strcmp(e->name, name) == 0) + return e; + return NULL; +} +static int vtysh_load_extension(struct vtysh_ext *e) +{ + e->handle = dlopen(e->name, RTLD_GLOBAL | RTLD_NOW); + if (e->handle) { + int (*ext_load)(void); + *(void **)&ext_load = dlsym(e->handle, "vtysh_extension"); + if (*ext_load) { + int r; + if ((r = (*ext_load)()) == 0) + e->loaded = true; + return r; + } else { + fprintf(stderr, " Failed to init vtysh extension '%s': %s\n", e->name, dlerror()); + return -1; + } + } else { + fprintf(stderr, " Failed to load vtysh extension '%s': %s\n", e->name, dlerror()); + return -1; + } +} + +int vtysh_load_extensions(void) +{ + struct vtysh_ext *e; + for (e = extensions; e ; e = e->next) { + if (vtysh_load_extension(e) != 0) + return -1; + } + return 0; +} +void vtysh_unload_extensions(void) +{ + struct vtysh_ext *e; + for (e = extensions; e ; e = e->next) { + if (e->loaded) + dlclose(e->handle); + if (e->name) + XFREE(MTYPE_VTYSH_EXT_NAME, e->name); + XFREE(MTYPE_VTYSH_EXT, e); + } +} +void vtysh_register_extension(char *name) +{ + assert(name); + if (find_extension(name) != NULL) { + fprintf(stderr, "Omitting extension %s: it appears to be already registered", name); + return; + } + struct vtysh_ext *e = XCALLOC(MTYPE_VTYSH_EXT, sizeof(*e)); + e->name = XSTRDUP(MTYPE_VTYSH_EXT_NAME, name); + e->next = extensions; + extensions = e; +} + +void vtysh_show_extensions(struct vty *vty) +{ + struct vtysh_ext *e; + for (e = extensions; e ; e = e->next) { + vty_out(vty, " Extension: %s\n", e->name); + vty_out(vty, " status: %s\n\n", e->loaded ? "loaded" : "not-loaded"); + } +} diff --git a/vtysh/vtysh_extensions.h b/vtysh/vtysh_extensions.h new file mode 100644 index 000000000000..37f724dceee3 --- /dev/null +++ b/vtysh/vtysh_extensions.h @@ -0,0 +1,22 @@ +#ifndef _VTYSH_EXTENSIONS_H +#define _VTYSH_EXTENSIONS_H + +#include +#include "lib/vty.h" + +DECLARE_MTYPE(VTYSH_EXT); + +struct vtysh_ext { + char *name; + void *handle; + bool loaded; + struct vtysh_ext *next; +}; + +void vtysh_register_extension(char *name); +int vtysh_load_extensions(void); +void vtysh_unload_extensions(void); + +void vtysh_show_extensions(struct vty *vty); + +#endif /* _VTYSH_EXTENSIONS_H */ diff --git a/vtysh/vtysh_main.c b/vtysh/vtysh_main.c index 64198132cc64..90fc1570e48c 100644 --- a/vtysh/vtysh_main.c +++ b/vtysh/vtysh_main.c @@ -216,6 +216,7 @@ struct option longopts[] = { {"user", no_argument, NULL, 'u'}, {"timestamp", no_argument, NULL, 't'}, {"no-fork", no_argument, NULL, OPTION_NOFORK}, + {"extension", required_argument, NULL, 'X'}, {0}}; bool vtysh_loop_exited; @@ -322,6 +323,7 @@ void suid_off(void) } } + /* VTY shell main routine. */ int main(int argc, char **argv, char **env) { @@ -369,7 +371,7 @@ int main(int argc, char **argv, char **env) /* Option handling. */ while (1) { - opt = getopt_long(argc, argv, "be:c:d:nf:H:mEhCwN:ut", longopts, + opt = getopt_long(argc, argv, "be:c:d:nf:H:mEhCwN:utX:", longopts, 0); if (opt == EOF) @@ -446,6 +448,9 @@ int main(int argc, char **argv, char **env) case 'H': histfile = optarg; break; + case 'X': + vtysh_register_extension(optarg); + break; default: usage(1); break; @@ -493,6 +498,7 @@ int main(int argc, char **argv, char **env) vtysh_config_init(); vty_init_vtysh(); + vtysh_load_extensions(); if (!user_mode) { /* Read vtysh configuration file before connecting to daemons. @@ -739,6 +745,7 @@ int main(int argc, char **argv, char **env) vtysh_rl_run(); vtysh_uninit(); + vtysh_unload_extensions(); history_truncate_file(history_file, 1000); printf("\n");