]> git.ipfire.org Git - thirdparty/lldpd.git/blobdiff - src/client/lldpcli.c
lldpcli: document json0 output format
[thirdparty/lldpd.git] / src / client / lldpcli.c
index 95f66dab95444bdba126f768233f711e016c70f7..a7d9be4b4b20baf35433b0bf2283b013bc7167f9 100644 (file)
@@ -16,7 +16,6 @@
  */
 
 
-#define _GNU_SOURCE
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
@@ -30,6 +29,7 @@
 #include <arpa/inet.h>
 #include <libgen.h>
 #include <dirent.h>
+#include <signal.h>
 #include <sys/queue.h>
 
 #include "client.h"
@@ -42,6 +42,7 @@ extern const char     *__progname;
 
 /* Global for completion */
 static struct cmd_node *root = NULL;
+const char *ctlname = NULL;
 
 static int
 is_lldpctl(const char *name)
@@ -66,9 +67,14 @@ usage()
        fprintf(stderr, "\n");
 
        fprintf(stderr, "-d          Enable more debugging information.\n");
-       fprintf(stderr, "-f format   Choose output format (plain, keyvalue or xml).\n");
+       fprintf(stderr, "-u socket   Specify the Unix-domain socket used for communication with lldpd(8).\n");
+       fprintf(stderr, "-f format   Choose output format (plain, keyvalue, json, json0"
+#if defined USE_XML
+           ", xml"
+#endif
+           ").\n");
        if (!is_lldpctl(NULL))
-               fprintf(stderr, "-c          Read the provided configuration file.\n");
+               fprintf(stderr, "-c conf     Read the provided configuration file.\n");
 
        fprintf(stderr, "\n");
 
@@ -79,7 +85,10 @@ usage()
 static int
 is_privileged()
 {
-       return (!(getuid() != geteuid() || getgid() != getegid()));
+       /* Check we can access the control socket with read/write
+        * privileges. The `access()` function uses the real UID and real GID,
+        * therefore we don't have to mangle with our identity. */
+       return (ctlname && access(ctlname, R_OK|W_OK) == 0);
 }
 
 static char*
@@ -130,7 +139,7 @@ cmd_update(struct lldpctl_conn_t *conn, struct writer *w,
                lldpctl_atom_dec_ref(config);
                return 0;
        }
-       log_info("lldpctl", "immediate retransmission requested successfuly");
+       log_info("lldpctl", "immediate retransmission requested successfully");
        lldpctl_atom_dec_ref(config);
        return 1;
 }
@@ -152,10 +161,10 @@ cmd_pause_resume(lldpctl_conn_t *conn, int pause)
                return 0;
        }
        if (lldpctl_atom_get_int(config, lldpctl_k_config_paused) == pause) {
-               log_info("lldpctl", "lldpd is already %s",
+               log_debug("lldpctl", "lldpd is already %s",
                    pause?"paused":"resumed");
                lldpctl_atom_dec_ref(config);
-               return 0;
+               return 1;
        }
        if (lldpctl_atom_set_int(config,
                lldpctl_k_config_paused, pause) == NULL) {
@@ -191,16 +200,17 @@ _cmd_complete(int all)
        char **argv = NULL;
        int argc = 0;
        int rc = 1;
-       char *line = malloc(strlen(rl_line_buffer) + 2);
+       size_t len = strlen(rl_line_buffer);
+       char *line = malloc(len + 2);
        if (!line) return -1;
-       strcpy(line, rl_line_buffer);
+       strlcpy(line, rl_line_buffer, len + 2);
        line[rl_point]   = 2;   /* empty character, will force a word */
        line[rl_point+1] = 0;
 
        if (tokenize_line(line, &argc, &argv) != 0)
                goto end;
 
-       char *compl = commands_complete(root, argc, (const char **)argv, all);
+       char *compl = commands_complete(root, argc, (const char **)argv, all, is_privileged());
        if (compl && strlen(argv[argc-1]) < strlen(compl)) {
                if (rl_insert_text(compl + strlen(argv[argc-1])) < 0) {
                        free(compl);
@@ -211,6 +221,7 @@ _cmd_complete(int all)
                goto end;
        }
        /* No completion or several completion available. */
+       free(compl);
        fprintf(stderr, "\n");
        rl_forced_update_display();
        rc = 0;
@@ -233,14 +244,14 @@ cmd_help(int count, int ch)
 }
 #else
 static char*
-readline()
+readline(const char *p)
 {
        static char line[2048];
-       fprintf(stderr, "%s", prompt());
+       fprintf(stderr, "%s", p);
        fflush(stderr);
        if (fgets(line, sizeof(line) - 2, stdin) == NULL)
                return NULL;
-       return line;
+       return strdup(line);
 }
 #endif
 
@@ -261,17 +272,19 @@ cmd_exec(lldpctl_conn_t *conn, const char *fmt, int argc, const char **argv)
 
        if      (strcmp(fmt, "plain")    == 0) w = txt_init(stdout);
        else if (strcmp(fmt, "keyvalue") == 0) w = kv_init(stdout);
+       else if (strcmp(fmt, "json")     == 0) w = json_init(stdout, 1);
+       else if (strcmp(fmt, "json0")    == 0) w = json_init(stdout, 0);
 #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);
+       else {
+               log_warnx("lldpctl", "unknown output format \"%s\"", fmt);
+               w = txt_init(stdout);
+       }
 
        /* Execute command */
        int rc = commands_execute(conn, w,
-           root, argc, argv);
+           root, argc, argv, is_privileged());
        if (rc != 0) {
                log_info("lldpctl", "an error occurred while executing last command");
                w->finish(w);
@@ -304,10 +317,12 @@ parse_and_exec(lldpctl_conn_t *conn, const char *fmt, const char *line)
                log_warnx("lldpctl", "unmatched quotes");
                return -1;
        }
-       if (cargc == 0) return 0;
-       n = cmd_exec(conn, fmt, cargc, (const char **)cargv);
+       if (cargc != 0)
+               n = cmd_exec(conn, fmt, cargc, (const char **)cargv);
        tokenize_free(cargc, cargv);
-       return (n == 0)?-1:1;
+       return (cargc == 0)?0:
+           (n == 0)?-1:
+           1;
 }
 
 static struct cmd_node*
@@ -316,14 +331,14 @@ register_commands()
        root = commands_root();
        register_commands_show(root);
        register_commands_watch(root);
-       if (is_privileged()) {
-               commands_new(
-                       commands_new(root, "update", "Update information and send LLDPU on all ports",
-                           NULL, NULL, NULL),
-                       NEWLINE, "Update information and send LLDPU on all ports",
-                       NULL, cmd_update, NULL);
-               register_commands_configure(root);
-       }
+       commands_privileged(commands_new(
+               commands_new(root, "update", "Update information and send LLDPU on all ports",
+                   NULL, NULL, NULL),
+               NEWLINE, "Update information and send LLDPU on all ports",
+               NULL, cmd_update, NULL));
+       register_commands_configure(root);
+       commands_hidden(commands_new(root, "complete", "Get possible completions from a given command",
+               NULL, cmd_store_env_and_pop, "complete"));
        commands_new(root, "help", "Get help on a possible command",
            NULL, cmd_store_env_and_pop, "help");
        commands_new(
@@ -359,12 +374,17 @@ filter(const struct dirent *dir)
  * @param acceptdir 1 if we accept a directory, 0 otherwise
  */
 static void
-input_append(const char *arg, struct inputs *inputs, int acceptdir)
+input_append(const char *arg, struct inputs *inputs, int acceptdir, int warn)
 {
        struct stat statbuf;
        if (stat(arg, &statbuf) == -1) {
-               log_info("lldpctl", "cannot find configuration file/directory %s",
-                   arg);
+               if (warn) {
+                       log_warn("lldpctl", "cannot find configuration file/directory %s",
+                           arg);
+               } else {
+                       log_debug("lldpctl", "cannot find configuration file/directory %s",
+                           arg);
+               }
                return;
        }
 
@@ -396,7 +416,7 @@ input_append(const char *arg, struct inputs *inputs, int acceptdir)
        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);
+                       input_append(fullname, inputs, 0, 1);
                        free(fullname);
                }
                free(namelist[i]);
@@ -407,72 +427,101 @@ input_append(const char *arg, struct inputs *inputs, int acceptdir)
 int
 main(int argc, char *argv[])
 {
-       int ch, debug = 1, rc = EXIT_FAILURE;
+       int ch, debug = 0, use_syslog = 0, rc = EXIT_FAILURE;
        const char *fmt = "plain";
        lldpctl_conn_t *conn = NULL;
-       const char *options = is_lldpctl(argv[0])?"hdvf:":"hdsvf:c:";
+       const char *options = is_lldpctl(argv[0])?"hdvf:u:":"hdsvf:c:C:u:";
 
-       int gotinputs = 0;
+       int gotinputs = 0, version = 0;
        struct inputs inputs;
        TAILQ_INIT(&inputs);
 
-       /* Initialize logging */
-       while ((ch = getopt(argc, argv, options)) != -1) {
-               switch (ch) {
-               case 'd': debug++; break;
-               case 's': debug--; break;
-               }
-       }
-       log_init(debug, __progname);
+       ctlname = lldpctl_get_default_transport();
+
+       signal(SIGHUP, SIG_IGN);
 
        /* Get and parse command line options */
        optind = 1;
        while ((ch = getopt(argc, argv, options)) != -1) {
                switch (ch) {
-               case 'd': break;
-               case 's': break;
+               case 'd':
+                       if (use_syslog)
+                               use_syslog = 0;
+                       else
+                               debug++;
+                       break;
+               case 's':
+                       if (debug == 0)
+                               use_syslog = 1;
+                       else
+                               debug--;
+                       break;
                case 'h':
                        usage();
                        break;
+               case 'u':
+                       ctlname = optarg;
+                       break;
                case 'v':
-                       fprintf(stdout, "%s\n", PACKAGE_VERSION);
-                       exit(0);
+                       version++;
                        break;
                case 'f':
                        fmt = optarg;
                        break;
+               case 'C':
                case 'c':
-                       gotinputs = 1;
-                       input_append(optarg, &inputs, 1);
+                       if (!gotinputs) {
+                               log_init(use_syslog, debug, __progname);
+                               lldpctl_log_level(debug + 1);
+                               gotinputs = 1;
+                       }
+                       input_append(optarg, &inputs, 1, ch == 'c');
                        break;
                default:
                        usage();
                }
        }
 
+       if (version) {
+               version_display(stdout, "lldpcli", version > 1);
+               exit(0);
+       }
+
+       if (!gotinputs) {
+               log_init(use_syslog, debug, __progname);
+               lldpctl_log_level(debug + 1);
+       }
+
+       /* Disable SIGPIPE */
+       signal(SIGPIPE, SIG_IGN);
+
        /* Register commands */
        root = register_commands();
 
        /* Make a connection */
        log_debug("lldpctl", "connect to lldpd");
-       conn = lldpctl_new(NULL, NULL, NULL);
+       conn = lldpctl_new_name(ctlname, NULL, NULL, NULL);
        if (conn == NULL) goto end;
 
        /* Process file inputs */
        while (gotinputs && !TAILQ_EMPTY(&inputs)) {
+               /* coverity[use_after_free]
+                  TAILQ_REMOVE does the right thing */
                struct input *first = TAILQ_FIRST(&inputs);
                log_debug("lldpctl", "process: %s", first->name);
                FILE *file = fopen(first->name, "r");
                if (file) {
-                       size_t len;
+                       size_t n;
+                       ssize_t len;
                        char *line;
-                       while ((line = fgetln(file, &len))) {
+                       while (line = NULL, len = 0, (len = getline(&line, &n, file)) > 0) {
                                if (line[len - 1] == '\n') {
                                        line[len - 1] = '\0';
                                        parse_and_exec(conn, fmt, line);
                                }
                                free(line);
                        }
+                       free(line);
                        fclose(file);
                } else {
                        log_warn("lldpctl", "unable to open %s",
@@ -514,32 +563,39 @@ main(int argc, char *argv[])
                int cargc;
                cargv = &((const char **)argv)[optind];
                cargc = argc - optind;
-               if (cmd_exec(conn, fmt, cargc, cargv) != -1)
+               if (cmd_exec(conn, fmt, cargc, cargv) == 1)
                        rc = EXIT_SUCCESS;
                goto end;
        }
 
-       if (gotinputs) goto end;
+       if (gotinputs) {
+               rc = EXIT_SUCCESS;
+               goto end;
+       }
 
        /* Interactive session */
 #ifdef HAVE_LIBREADLINE
        rl_bind_key('?',  cmd_help);
        rl_bind_key('\t', cmd_complete);
 #endif
-       const char *line;
+       char *line = NULL;
        do {
                if ((line = readline(prompt()))) {
                        int n = parse_and_exec(conn, fmt, line);
-                       (void)n;
+                       if (n != 0) {
 #ifdef HAVE_READLINE_HISTORY
-                       if (n != 0) add_history(line);
+                               add_history(line);
 #endif
+                       }
+                       free(line);
                }
-       } while (line != NULL);
+       } while (!must_exit && line != NULL);
        rc = EXIT_SUCCESS;
 
 end:
        while (!TAILQ_EMPTY(&inputs)) {
+               /* coverity[use_after_free]
+                  TAILQ_REMOVE does the right thing */
                struct input *first = TAILQ_FIRST(&inputs);
                TAILQ_REMOVE(&inputs, first, next);
                free(first->name);