From: Vincent Bernat Date: Fri, 18 Jan 2013 22:33:34 +0000 (+0100) Subject: lldpcli: add an option to read commands from files X-Git-Tag: 0.7.2~45 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=6402fd2c61c070b24d2677a2e8ab71313c4aff69;p=thirdparty%2Flldpd.git lldpcli: add an option to read commands from files --- diff --git a/configure.ac b/configure.ac index 1e86355e..4b73e0cc 100644 --- a/configure.ac +++ b/configure.ac @@ -93,7 +93,7 @@ lldp_CHECK___PROGNAME AC_CONFIG_LIBOBJ_DIR([src/compat]) AC_FUNC_MALLOC AC_FUNC_REALLOC -AC_REPLACE_FUNCS([strlcpy strnlen]) +AC_REPLACE_FUNCS([strlcpy strnlen fgetln]) AC_CHECK_FUNCS([setresuid setresgid]) AC_SEARCH_LIBS([__res_init], resolv bind, AC_DEFINE([HAVE_RES_INIT], 1, [Define to indicate that res_init() exists]), diff --git a/src/client/client.h b/src/client/client.h index aaa7350c..3b490c7b 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -26,6 +26,7 @@ #include "../lldp-const.h" #include "../log.h" #include "../ctl.h" +#include "../compat/compat.h" #include "writer.h" /* Readline stuff */ diff --git a/src/client/lldpcli.8 b/src/client/lldpcli.8 index cf05b2c0..88bbd0e3 100644 --- a/src/client/lldpcli.8 +++ b/src/client/lldpcli.8 @@ -24,6 +24,7 @@ .Nm .Op Fl dv .Op Fl f Ar format +.Op Fl c Ar file .Op Ar command ... .Nm lldpctl .Op Fl dv @@ -59,6 +60,12 @@ and .Em keyvalue formats are available. The default is .Em plain . +.It Fl c Ar file +Read the given configuration file. This option may be repeated several +times. If a directory is provided, each file contained in it will be +read if ending by +.Li .conf . +Order is alphabetical. .El .Pp When invoked as diff --git a/src/client/lldpcli.c b/src/client/lldpcli.c index 91873178..f878c97c 100644 --- a/src/client/lldpcli.c +++ b/src/client/lldpcli.c @@ -24,10 +24,13 @@ #include #include #include +#include #include #include #include #include +#include +#include #include "client.h" @@ -40,6 +43,20 @@ extern const char *__progname; /* Global for completion */ static struct cmd_node *root = NULL; +static int +is_lldpctl(const char *name) +{ + static int last_result = -1; + if (last_result == -1 && name) { + char *basec = strdup(name); + if (!basec) return 0; + char *bname = basename(basec); + last_result = (!strcmp(bname, "lldpctl")); + free(basec); + } + return (last_result == -1)?0:last_result; +} + static void usage() { @@ -50,6 +67,8 @@ usage() fprintf(stderr, "-d Enable more debugging information.\n"); fprintf(stderr, "-f format Choose output format (plain, keyvalue or xml).\n"); + if (!is_lldpctl(NULL)) + fprintf(stderr, "-c Read the provided configuration file.\n"); fprintf(stderr, "\n"); @@ -176,6 +195,72 @@ readline() } #endif +/** + * Execute a tokenized command and display its output. + * + * @param conn The connection to lldpd. + * @param fmt Output format. + * @param argc Number of arguments. + * @param argv Array of arguments. + * @return 0 if an error occurred, 1 otherwise + */ +static int +cmd_exec(lldpctl_conn_t *conn, const char *fmt, int argc, const char **argv) +{ + /* Init output formatter */ + struct writer *w; + + if (strcmp(fmt, "plain") == 0) w = txt_init(stdout); + else if (strcmp(fmt, "keyvalue") == 0) w = kv_init(stdout); +#ifdef USE_XML + else if (strcmp(fmt, "xml") == 0) w = xml_init(stdout); +#endif +#ifdef USE_JSON + else if (strcmp(fmt, "json") == 0) w = json_init(stdout); +#endif + else w = txt_init(stdout); + + /* Execute command */ + int rc = commands_execute(conn, w, + root, argc, argv); + if (rc != 0) { + log_info("lldpctl", "an error occurred while executing last command"); + w->finish(w); + return 0; + } + w->finish(w); + return 1; +} + +/** + * Execute a command line and display its output. + * + * @param conn The connection to lldpd. + * @param fmt Output format. + * @param line Line to execute. + * @return -1 if an error occurred, 0 if nothing was executed. 1 otherwise. + */ +static int +parse_and_exec(lldpctl_conn_t *conn, const char *fmt, const char *line) +{ + int cargc = 0; char **cargv = NULL; + int n; + log_debug("lldpctl", "tokenize command line"); + n = tokenize_line(line, &cargc, &cargv); + switch (n) { + case -1: + log_warnx("lldpctl", "internal error while tokenizing"); + return -1; + case 1: + log_warnx("lldpctl", "unmatched quotes"); + return -1; + } + if (cargc == 0) return 0; + n = cmd_exec(conn, fmt, cargc, (const char **)cargv); + tokenize_free(cargc, cargv); + return (n == 0)?-1:1; +} + static struct cmd_node* register_commands() { @@ -198,39 +283,100 @@ register_commands() return root; } +struct input { + TAILQ_ENTRY(input) next; + char *name; +}; +TAILQ_HEAD(inputs, input); static int -is_lldpctl(const char *name) +filter(const struct dirent *dir) { - static int last_result = -1; - if (last_result == -1 && name) { - char *basec = strdup(name); - if (!basec) return 0; - char *bname = basename(basec); - last_result = (!strcmp(bname, "lldpctl")); - free(basec); + if (strlen(dir->d_name) < 5) return 0; + if (strcmp(dir->d_name + strlen(dir->d_name) - 5, ".conf")) return 0; + return 1; +} + +/** + * Append a new input file/directory to the list of inputs. + * + * @param arg Directory or file name to add. + * @param inputs List of inputs + * @param acceptdir 1 if we accept a directory, 0 otherwise + */ +static void +input_append(const char *arg, struct inputs *inputs, int acceptdir) +{ + struct stat statbuf; + if (stat(arg, &statbuf) == -1) { + log_info("lldpctl", "cannot find configuration file/directory %s", + arg); + return; } - return (last_result == -1)?0:last_result; + + if (!S_ISDIR(statbuf.st_mode)) { + struct input *input = malloc(sizeof(struct input)); + if (!input) { + log_warn("lldpctl", "not enough memory to process %s", + arg); + return; + } + log_debug("lldpctl", "input: %s", arg); + input->name = strdup(arg); + TAILQ_INSERT_TAIL(inputs, input, next); + return; + } + if (!acceptdir) { + log_debug("lldpctl", "skip directory %s", + arg); + return; + } + + struct dirent **namelist = NULL; + int n = scandir(arg, &namelist, filter, alphasort); + if (n < 0) { + log_warnx("lldpctl", "unable to read directory %s", + arg); + return; + } + for (int i=0; i < n; i++) { + char *fullname; + if (asprintf(&fullname, "%s/%s", arg, namelist[i]->d_name) != -1) { + input_append(fullname, inputs, 0); + free(fullname); + } + free(namelist[i]); + } + free(namelist); } int main(int argc, char *argv[]) { int ch, debug = 1, rc = EXIT_FAILURE; - char *fmt = "plain"; + const char *fmt = "plain"; lldpctl_conn_t *conn = NULL; - struct writer *w; + const char *options = is_lldpctl(argv[0])?"hdvf:":"hdvf:c:"; - char *interfaces = NULL; + int gotinputs = 0; + struct inputs inputs; + TAILQ_INIT(&inputs); + + /* Initialize logging */ + while ((ch = getopt(argc, argv, options)) != -1) { + switch (ch) { + case 'd': debug++; break; + } + } + log_init(debug, __progname); /* Get and parse command line options */ - while ((ch = getopt(argc, argv, "hdvf:")) != -1) { + optind = 1; + while ((ch = getopt(argc, argv, options)) != -1) { switch (ch) { + case 'd': break; case 'h': usage(); break; - case 'd': - debug++; - break; case 'v': fprintf(stdout, "%s\n", PACKAGE_VERSION); exit(0); @@ -238,108 +384,111 @@ main(int argc, char *argv[]) case 'f': fmt = optarg; break; + case 'c': + gotinputs = 1; + input_append(optarg, &inputs, 1); + break; default: usage(); } } - log_init(debug, __progname); - /* Register commands */ root = register_commands(); - if (is_lldpctl(argv[0])) { + /* Make a connection */ + log_debug("lldpctl", "connect to lldpd"); + conn = lldpctl_new(NULL, NULL, NULL); + if (conn == NULL) goto end; + + /* Process file inputs */ + while (gotinputs && !TAILQ_EMPTY(&inputs)) { + struct input *first = TAILQ_FIRST(&inputs); + log_debug("lldpctl", "process: %s", first->name); + FILE *file = fopen(first->name, "r"); + if (file) { + size_t len; + char *line; + while ((line = fgetln(file, &len))) { + if (line[len - 1] == '\n') { + line[len - 1] = '\0'; + parse_and_exec(conn, fmt, line); + } + free(line); + } + fclose(file); + } else { + log_warn("lldpctl", "unable to open %s", + first->name); + } + TAILQ_REMOVE(&inputs, first, next); + free(first->name); + free(first); + } + + /* Process additional arguments. First if we are lldpctl (interfaces) */ + if (is_lldpctl(NULL)) { + char *line = NULL; for (int i = optind; i < argc; i++) { - char *prev = interfaces; - if (asprintf(&interfaces, "%s%s%s", - prev?prev:"", prev?",":"", argv[i]) == -1) { + char *prev = line; + if (asprintf(&line, "%s%s%s", + prev?prev:"show neigh ports ", argv[i], + (i == argc - 1)?" details":",") == -1) { log_warnx("lldpctl", "not enough memory to build list of interfaces"); + free(prev); goto end; } free(prev); } - must_exit = 1; - } else if (optind < argc) { - /* More arguments! */ - must_exit = 1; - } else { -#ifdef HAVE_LIBREADLINE - /* Shell session */ - rl_bind_key('?', cmd_help); - rl_bind_key('\t', cmd_complete); -#endif + if (line == NULL && (line = strdup("show neigh details")) == NULL) { + log_warnx("lldpctl", "not enough memory to build command line"); + goto end; + } + log_debug("lldpctl", "execute %s", line); + if (parse_and_exec(conn, fmt, line) != -1) + rc = EXIT_SUCCESS; + free(line); + goto end; } - /* Make a connection */ - log_debug("lldpctl", "connect to lldpd"); - conn = lldpctl_new(NULL, NULL, NULL); - if (conn == NULL) - exit(EXIT_FAILURE); + /* Then, if we are regular lldpcli (command line) */ + if (optind < argc) { + const char **cargv; + int cargc; + cargv = &((const char **)argv)[optind]; + cargc = argc - optind; + if (cmd_exec(conn, fmt, cargc, cargv) != -1) + rc = EXIT_SUCCESS; + goto end; + } - do { - const char *line; - char **cargv = NULL; - int n, cargc = 0; - if (!is_lldpctl(NULL) && (optind >= argc)) { - line = readline(prompt()); - if (line == NULL) break; /* EOF */ - - /* Tokenize it */ - log_debug("lldpctl", "tokenize command line"); - n = tokenize_line(line, &cargc, &cargv); - switch (n) { - case -1: - log_warnx("lldpctl", "internal error while tokenizing"); - goto end; - case 1: - log_warnx("lldpctl", "unmatched quotes"); - continue; - } - if (cargc == 0) continue; -#ifdef HAVE_READLINE_HISTORY - add_history(line); -#endif - } + if (gotinputs) goto end; - /* Init output formatter */ - if (strcmp(fmt, "plain") == 0) w = txt_init(stdout); - else if (strcmp(fmt, "keyvalue") == 0) w = kv_init(stdout); -#ifdef USE_XML - else if (strcmp(fmt, "xml") == 0) w = xml_init(stdout); + /* Interactive session */ +#ifdef HAVE_LIBREADLINE + rl_bind_key('?', cmd_help); + rl_bind_key('\t', cmd_complete); #endif -#ifdef USE_JSON - else if (strcmp(fmt, "json") == 0) w = json_init(stdout); + const char *line; + do { + if ((line = readline(prompt()))) { + int n = parse_and_exec(conn, fmt, line); + (void)n; +#ifdef HAVE_READLINE_HISTORY + if (n != 0) add_history(line); #endif - else w = txt_init(stdout); - - if (is_lldpctl(NULL)) { - if (!interfaces) { - cargv = (char*[]){ "show", "neighbors", "details" }; - cargc = 3; - } else { - cargv = (char*[]){ "show", "neighbors", "ports", interfaces, "details" }; - cargc = 5; - } - } else if (optind < argc) { - cargv = argv; - cargv = &cargv[optind]; - cargc = argc - optind; } - - /* Execute command */ - if (commands_execute(conn, w, - root, cargc, (const char **)cargv) != 0) - log_info("lldpctl", "an error occurred while executing last command"); - w->finish(w); - - if (!is_lldpctl(NULL) && optind >= argc) - tokenize_free(cargc, cargv); - } while (!must_exit); - + } while (line != NULL); rc = EXIT_SUCCESS; + end: + while (!TAILQ_EMPTY(&inputs)) { + struct input *first = TAILQ_FIRST(&inputs); + TAILQ_REMOVE(&inputs, first, next); + free(first->name); + free(first); + } if (conn) lldpctl_release(conn); if (root) commands_free(root); - free(interfaces); return rc; } diff --git a/src/compat/compat.h b/src/compat/compat.h index 0374a4ef..fde89607 100644 --- a/src/compat/compat.h +++ b/src/compat/compat.h @@ -33,6 +33,7 @@ #ifndef _COMPAT_H #define _COMPAT_H +#include #include #if !HAVE_STRLCPY @@ -43,6 +44,10 @@ size_t strlcpy(char *, const char *, size_t); size_t strnlen(const char *, size_t); #endif +#if !HAVE_FGETLN +char *fgetln(FILE *, size_t *); +#endif + #if !HAVE_MALLOC void *malloc(size_t size); #endif diff --git a/src/compat/fgetln.c b/src/compat/fgetln.c new file mode 100644 index 00000000..ea8da6ac --- /dev/null +++ b/src/compat/fgetln.c @@ -0,0 +1,49 @@ +/* + * Copyright © 2005 Hector Garcia Alvarez + * Copyright © 2005, 2008, 2009, 2011 Guillem Jover + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +char * +fgetln(FILE *stream, size_t *len) +{ + static char *line = NULL; + static size_t line_len = 0; + ssize_t nread; + + nread = getline(&line, &line_len, stream); + /* Note: the getdelim/getline API ensures nread != 0. */ + if (nread == -1) { + *len = 0; + return NULL; + } else { + *len = (size_t)nread; + return line; + } +}