]> git.ipfire.org Git - thirdparty/lldpd.git/commitdiff
lldpctl: new JSON output
authorVincent Bernat <bernat@luffy.cx>
Mon, 17 Dec 2012 06:48:02 +0000 (07:48 +0100)
committerVincent Bernat <bernat@luffy.cx>
Mon, 17 Dec 2012 06:48:02 +0000 (07:48 +0100)
JSON output is done with "Jansson", a convenient JSON library. The
output may be a bit difficult to use when a multivalued field with
only one value is present. In this case, it is not put into an
array. For example, if there is only one neighbor, you get:
`{interface:{eth0: ...}}` while you will get this for two neighbors:
`{interface:[{eth0:...},{eth1:...}]}`.

.travis.yml
NEWS
configure.ac
m4/jansson.m4 [new file with mode: 0644]
src/client/Makefile.am
src/client/json_writer.c [new file with mode: 0644]
src/client/lldpctl.8
src/client/lldpctl.c
src/client/writer.h

index 1149c1d780532aa0621107fdf4e881c88b9da26b..1c661410f6ed5d82b998ec127a38e59f53e02e30 100644 (file)
@@ -1,11 +1,11 @@
 language: "python"
 install:
   - "sudo apt-get -y install automake autoconf libtool pkg-config"
-  - "sudo apt-get -y install libsnmp-dev libxml2-dev libevent-dev check"
+  - "sudo apt-get -y install libsnmp-dev libxml2-dev libjansson-dev libevent-dev check"
 script: "./autogen.sh && ./configure $LLDPD_CONFIG_ARGS && make && make check && make distcheck && sudo make install"
 env:
   - LLDPD_CONFIG_ARGS=""
-  - LLDPD_CONFIG_ARGS="--with-snmp --with-xml --disable-lldpmed --disable-dot1 --disable-dot3"
+  - LLDPD_CONFIG_ARGS="--with-snmp --with-xml --with-json --disable-lldpmed --disable-dot1 --disable-dot3"
 branches:
   only:
     - master
diff --git a/NEWS b/NEWS
index c2fc5d4483237414f15d2ac7ef8f68d16f7e1694..a58772448a50058e3403db81b77c13cbd05d84b3 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,7 @@ lldpd (0.6.2)
     + Allow to disable LLDP protocol (with `-ll`). In this case, the
       first enabled protocol will be used when no neighbor is detected.
     + Allow to filter debug logs using tokens. Add more debug logs.
+    + lldpctl can now output JSON.
 
 lldpd (0.6.1)
   * Features:
index f8dfe99b2ad6443ec66d87a0542ad21033bfe527..79875ac768352e326fbe8d12b032cb696c3fd1df 100644 (file)
@@ -163,6 +163,16 @@ if test x"$with_xml" = x"yes"; then
    lldp_CHECK_XML2
 fi
 
+# JSON
+AC_ARG_WITH([json],
+  AC_HELP_STRING(
+    [--with-json],
+    [Enable JSON output via Jansson @<:@default=no@:>@]
+  ))
+if test x"$with_json" = x"yes"; then
+   lldp_CHECK_JANSSON
+fi
+
 # Privsep settings
 lldp_ARG_WITH([privsep-user], [Which user to use for privilege separation], [_lldpd])
 lldp_ARG_WITH([privsep-group], [Which group to use for privilege separation], [_lldpd])
@@ -184,6 +194,7 @@ lldp_ARG_ENABLE([dot3], [Dot3 extension (PHY stuff)], [yes])
 AM_CONDITIONAL([HAVE_CHECK], [test x"$have_check" = x"yes"])
 AM_CONDITIONAL([USE_SNMP], [test x"$with_snmp" = x"yes"])
 AM_CONDITIONAL([USE_XML], [test x"$with_xml" = x"yes"])
+AM_CONDITIONAL([USE_JSON], [test x"$with_json" = x"yes"])
 AC_OUTPUT
 
 if test x"$LIBEVENT_LDFLAGS" = x; then
@@ -210,6 +221,7 @@ cat <<EOF
   DOT1...........: $enable_dot1
   DOT3...........: $enable_dot3
   XML output.....: ${with_xml-no}
+  JSON output....: ${with_json-no}
 ---------------------------------------------
 
 Check the above options and compile with:
diff --git a/m4/jansson.m4 b/m4/jansson.m4
new file mode 100644 (file)
index 0000000..ca936d4
--- /dev/null
@@ -0,0 +1,12 @@
+#
+# lldp_CHECK_JANSSON
+#
+
+AC_DEFUN([lldp_CHECK_JANSSON], [
+   PKG_CHECK_MODULES([JANSSON], [jansson >= 2], [],
+      [AC_MSG_ERROR([*** unable to find libjansson])])
+
+   AC_SUBST([JANSSON_LIBS])
+   AC_SUBST([JANSSON_CFLAGS])
+   AC_DEFINE_UNQUOTED([USE_JSON], 1, [Define to indicate to enable JSON support])
+])
index 2b0576ed09dd0ce42d950323ad7796a772865476..e105edf84099e2b1d38e29de6beedf751e898a88 100644 (file)
@@ -9,3 +9,9 @@ lldpctl_SOURCES += xml_writer.c
 lldpctl_CFLAGS   = @XML2_CFLAGS@
 lldpctl_LDADD   += @XML2_LIBS@
 endif
+
+if USE_JSON
+lldpctl_SOURCES += json_writer.c
+lldpctl_CFLAGS   = @JANSSON_CFLAGS@
+lldpctl_LDADD   += @JANSSON_LIBS@
+endif
diff --git a/src/client/json_writer.c b/src/client/json_writer.c
new file mode 100644 (file)
index 0000000..609010d
--- /dev/null
@@ -0,0 +1,241 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <jansson.h>
+#include <sys/queue.h>
+
+#include "writer.h"
+#include "../compat/compat.h"
+#include "../log.h"
+
+/*
+ * { lldp:
+ *    { interface: [
+
+ * { chassis:
+ *   { id: { type: "mac", value: "60:eb:69:ce:6e:a0" },
+ *     name: "guybrush",
+ *     descr: "Debian GNU/Linux 7.0 (wheezy)",
+ *     capability: [{type: "bridge", enabled: true}, {type: "router", enabled: false}] },
+*  <interface label="Interface" name="port2" via="LLDP" rid="1" age="0 day, 00:00:09">
+*   <chassis label="Chassis">
+*    <id label="ChassisID" type="mac">60:eb:69:ce:6e:a0</id>
+*    <name label="SysName">guybrush.luffy.cx</name>
+*    <descr label="SysDescr">Debian GNU/Linux 7.0 (wheezy) Linux 3.5-trunk-amd64 #1 SMP Debian 3.5.5-1~experimental.1 x86_64</descr>
+*    <mgmt-ip label="MgmtIP">192.168.116.3</mgmt-ip>
+*    <capability label="Capability" type="Bridge" enabled="off"/>
+*    <capability label="Capability" type="Router" enabled="off"/>
+*    <capability label="Capability" type="Wlan" enabled="on"/>
+*   </chassis>
+*   <port label="Port">
+*    <id label="PortID" type="mac">fe:86:6f:b6:1e:db</id>
+*    <descr label="PortDescr">port1</descr>
+*    <auto-negotiation label="PMD autoneg" supported="no" enabled="no">
+*     <current label="MAU oper type">10GigBaseR - R PCS/PMA, unknown PMD.</current>
+*    </auto-negotiation>
+*   </port>
+*  </interface>
+* </lldp>
+*/
+
+/* This list is used as a queue. The queue does not hold reference to the json_t
+ * element except the first one. */
+struct json_element {
+       TAILQ_ENTRY(json_element) next;
+       json_t *el;
+};
+TAILQ_HEAD(json_writer_private, json_element);
+
+static void
+json_start(struct writer *w, const char *tag, const char *descr)
+{
+       struct json_writer_private *p = w->priv;
+       struct json_element *current = TAILQ_LAST(p, json_writer_private);
+       struct json_element *new;
+       json_t *exist;
+
+       /* Try to find if a similar object exists. */
+       exist = json_object_get(current->el, tag);
+       if (!exist) {
+               exist = json_array();
+               json_object_set_new(current->el, tag, exist);
+       }
+
+       /* Queue the new element. */
+       new = malloc(sizeof(*new));
+       if (new == NULL) fatal(NULL, NULL);
+       new->el = json_object();
+       json_array_append_new(exist, new->el);
+       TAILQ_INSERT_TAIL(p, new, next);
+}
+
+static void
+json_attr(struct writer *w, const char *tag,
+    const char *descr, const char *value)
+{
+       struct json_writer_private *p = w->priv;
+       struct json_element *current = TAILQ_LAST(p, json_writer_private);
+       json_t *jvalue;
+       if (!strcmp(value, "yes") || !strcmp(value, "on"))
+               jvalue = json_true();
+       else if (!strcmp(value, "no") || !strcmp(value, "off"))
+               jvalue = json_false();
+       else
+               jvalue = json_string(value);
+       json_object_set_new(current->el, tag, jvalue);
+}
+
+static void
+json_data(struct writer *w, const char *data)
+{
+       struct json_writer_private *p = w->priv;
+       struct json_element *current = TAILQ_LAST(p, json_writer_private);
+       json_object_set_new(current->el, "value", json_string(data));
+}
+
+static void
+json_end(struct writer *w)
+{
+       struct json_writer_private *p = w->priv;
+       struct json_element *current = TAILQ_LAST(p, json_writer_private);
+       if (current == NULL) {
+               log_warnx(NULL, "unbalanced tags");
+               return;
+       }
+       TAILQ_REMOVE(p, current, next);
+       free(current);
+}
+
+/* When an array has only one member, just remove the array. When an object has
+ * `value` as the only key, remove the object. Moreover, for an object, move the
+ * `name` key outside (inside a new object). This is a recursive function. We
+ * think the depth will be limited. */
+static json_t*
+json_cleanup(json_t *el)
+{
+       json_t *new;
+       if (el == NULL) return NULL;
+       if (json_is_array(el) && json_array_size(el) == 1) {
+               new = json_array_get(el, 0);
+               return json_cleanup(new);
+       }
+       if (json_is_array(el)) {
+               int i = json_array_size(el);
+               new = json_array();
+               while (i > 0) {
+                       json_array_insert_new(new, 0,
+                           json_cleanup(json_array_get(el, --i)));
+               }
+               return new;
+       }
+       if (json_is_object(el) && json_object_size(el) == 1) {
+               new = json_object_get(el, "value");
+               if (new) {
+                       json_incref(new);
+                       return new; /* This is a string or a boolean, no need to
+                                    * cleanup */
+               }
+       }
+       if (json_is_object(el)) {
+               const char *key;
+               json_t *value;
+               json_t *name = NULL;
+               void *iter = json_object_iter(el);
+               new = json_object();
+               while (iter) {
+                       key   = json_object_iter_key(iter);
+                       value = json_cleanup(json_object_iter_value(iter));
+                       if (strcmp(key, "name") || !json_is_string(value)) {
+                               json_object_set_new(new, key, value);
+                       } else {
+                               name = value;
+                       }
+                       iter  = json_object_iter_next(el, iter);
+               }
+               if (name) {
+                       /* Embed the current object into a new one with the name
+                        * as key. */
+                       new = json_pack("{s: o}", /* o: stolen reference */
+                           json_string_value(name), new);
+                       json_decref(name);
+               }
+               return new;
+       }
+       json_incref(el);
+       return el;
+}
+
+static void
+json_finish(struct writer *w)
+{
+       struct json_writer_private *p = w->priv;
+       if (TAILQ_EMPTY(p)) {
+               log_warnx(NULL, "nothing to output");
+       } else if (TAILQ_NEXT(TAILQ_FIRST(p), next) != NULL) {
+               log_warnx(NULL, "unbalanced tags");
+               /* memory will leak... */
+       } else {
+               struct json_element *first = TAILQ_FIRST(p);
+               json_t *export = json_cleanup(first->el);
+               if (json_dumpf(export,
+                       stdout,
+                       JSON_INDENT(2) | JSON_PRESERVE_ORDER) == -1)
+                       log_warnx(NULL, "unable to output JSON");
+               json_decref(first->el);
+               json_decref(export);
+               TAILQ_REMOVE(p, first, next);
+               free(first);
+       }
+       free(p);
+       free(w);
+}
+
+struct writer*
+json_init(FILE *fh)
+{
+       struct writer *result;
+       struct json_writer_private *priv;
+       struct json_element *root;
+
+       priv = malloc(sizeof(*priv));
+       root = malloc(sizeof(*root));
+       if (priv == NULL || root == NULL) fatal(NULL, NULL);
+       TAILQ_INIT(priv);
+       TAILQ_INSERT_TAIL(priv, root, next);
+       root->el = json_object();
+       if (root->el == NULL)
+               fatalx("cannot create JSON root object");
+
+       result = malloc(sizeof(*result));
+       if (result == NULL) fatal(NULL, NULL);
+
+       result->priv   = priv;
+       result->start  = json_start;
+       result->attr   = json_attr;
+       result->data   = json_data;
+       result->end    = json_end;
+       result->finish = json_finish;
+
+       return result;
+}
index 9fb2d9a3c912667bc83bbc8c830a3df4e7bec611..6072aed3f35cd035f8778b3819333ef8ddd76d00 100644 (file)
@@ -63,7 +63,8 @@ update its information and send new LLDP PDU on all interfaces.
 .It Fl f Ar format
 Choose the output format. Currently
 .Em plain ,
-.Em xml
+.Em xml ,
+.Em json
 and
 .Em keyvalue
 formats are available. The default is
index 8a701020603a63a660aa799b686ad22b4f90ba97..a00b0a58768ea29d284176629f8d0f79b7fe0a9f 100644 (file)
@@ -189,6 +189,11 @@ main(int argc, char *argv[])
                else if (strcmp(fmt,"xml") == 0 ) {
                        args.w = xml_init(stdout);
                }
+#endif
+#ifdef USE_JSON
+               else if (strcmp(fmt, "json") == 0) {
+                       args.w = json_init(stdout);
+               }
 #endif
                else {
                        args.w = txt_init(stdout);
index 71713fb50c7f1ef107d39dfafadb1de73356a323..f4a4759234ca2d0a9aced3c59f2ec4e8b82873e6 100644 (file)
@@ -41,5 +41,8 @@ extern struct writer * kv_init( FILE * );
 #ifdef USE_XML
 extern struct writer * xml_init( FILE * );
 #endif
+#ifdef USE_JSON
+extern struct writer * json_init( FILE * );
+#endif
 
 #endif /* _WRITER_H */