From: Vincent Bernat Date: Mon, 17 Dec 2012 06:48:02 +0000 (+0100) Subject: lldpctl: new JSON output X-Git-Tag: 0.7.0~60 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=8b7150e4d936511737ea0eb3115ff0dcd8148abb;p=thirdparty%2Flldpd.git lldpctl: new JSON output 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:...}]}`. --- diff --git a/.travis.yml b/.travis.yml index 1149c1d7..1c661410 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 c2fc5d44..a5877244 100644 --- 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: diff --git a/configure.ac b/configure.ac index f8dfe99b..79875ac7 100644 --- a/configure.ac +++ b/configure.ac @@ -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 <= 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]) +]) diff --git a/src/client/Makefile.am b/src/client/Makefile.am index 2b0576ed..e105edf8 100644 --- a/src/client/Makefile.am +++ b/src/client/Makefile.am @@ -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 index 00000000..609010de --- /dev/null +++ b/src/client/json_writer.c @@ -0,0 +1,241 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * 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 +#endif + +#include +#include +#include +#include +#include + +#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}] }, +* +* +* 60:eb:69:ce:6e:a0 +* guybrush.luffy.cx +* Debian GNU/Linux 7.0 (wheezy) Linux 3.5-trunk-amd64 #1 SMP Debian 3.5.5-1~experimental.1 x86_64 +* 192.168.116.3 +* +* +* +* +* +* fe:86:6f:b6:1e:db +* port1 +* +* 10GigBaseR - R PCS/PMA, unknown PMD. +* +* +* +* +*/ + +/* 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; +} diff --git a/src/client/lldpctl.8 b/src/client/lldpctl.8 index 9fb2d9a3..6072aed3 100644 --- a/src/client/lldpctl.8 +++ b/src/client/lldpctl.8 @@ -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 diff --git a/src/client/lldpctl.c b/src/client/lldpctl.c index 8a701020..a00b0a58 100644 --- a/src/client/lldpctl.c +++ b/src/client/lldpctl.c @@ -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); diff --git a/src/client/writer.h b/src/client/writer.h index 71713fb5..f4a47592 100644 --- a/src/client/writer.h +++ b/src/client/writer.h @@ -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 */