From: Vincent Bernat Date: Mon, 23 Jan 2017 16:28:33 +0000 (+0100) Subject: client: built-in JSON support X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=refs%2Fheads%2Ffeature%2Fjson-wo-libs;p=thirdparty%2Flldpd.git client: built-in JSON support Instead of relying on two different libs to serialize JSON, we provide our own built-in serializer. This reduces the amount of build-related code (detection of the correct library), as well as the amount of C code with the exception of UTF-8 handling which was stolen from CCAN. This will also make the life of some users easier. The JSON format stays the same: quite verbose with --enable-json0 flag. And inconsistent without it. Fix #188. Fix #220. --- diff --git a/.travis.yml b/.travis.yml index e35746e1..1ba6ca11 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,12 +16,11 @@ env: - LLDPD_CONFIG_ARGS="--with-snmp --disable-lldpmed --disable-dot1 --disable-dot3 --disable-custom" - LLDPD_CONFIG_ARGS="--enable-oldies" - LLDPD_CONFIG_ARGS="--with-seccomp" - - LLDPD_CONFIG_ARGS="--with-json=json-c" matrix: include: - os: linux compiler: clang - env: LLDPD_CONFIG_ARGS="--with-snmp --with-xml --with-json" + env: LLDPD_CONFIG_ARGS="--with-snmp --with-xml" - os: osx compiler: clang - env: LLDPD_CONFIG_ARGS="--with-snmp --with-xml --with-json" + env: LLDPD_CONFIG_ARGS="--with-snmp --with-xml" diff --git a/NEWS b/NEWS index 2b02fb64..9b23fe29 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,7 @@ lldpd (0.9.7) * Changes: + + JSON support is now built-in and unconditionally enabled. Use + --enable-json0 to keep the pre-0.9.2 json-c format. + When logging to syslog and daemonizing, don't log to stderr. lldpd (0.9.6) diff --git a/README.md b/README.md index 344c35e2..28af58db 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ simpler alternatives: mkdir build && cd build ../configure --prefix=/usr/local --localstatedir=/var --sysconfdir=/private/etc --with-embedded-libevent \ - --without-json --without-snmp + --without-snmp make -C osx pkg If you want to compile for an older version of OS X, you need @@ -86,7 +86,7 @@ simpler alternatives: SDK=/Developer/SDKs/MacOSX10.6.sdk mkdir build && cd build ../configure --prefix=/usr/local --localstatedir=/var --sysconfdir=/private/etc --with-embedded-libevent \ - --without-json --without-snmp \ + --without-snmp \ CFLAGS="-mmacosx-version-min=10.6 -isysroot $SDK" \ LDFLAGS="-mmacosx-version-min=10.6 -isysroot $SDK" make -C osx pkg @@ -97,7 +97,7 @@ simpler alternatives: mkdir build && cd build ../configure --prefix=/usr/local --localstatedir=/var --sysconfdir=/private/etc --with-embedded-libevent \ - --without-json --without-snmp \ + --without-snmp \ CFLAGS="-mmacosx-version-min=10.9" \ LDFLAGS="-mmacosx-version-min=10.9" make -C osx pkg diff --git a/configure.ac b/configure.ac index 2e81f5b5..013eca02 100644 --- a/configure.ac +++ b/configure.ac @@ -269,28 +269,7 @@ AC_ARG_WITH([xml], [with_xml=auto]) lldp_CHECK_XML2 -# JSON -AC_ARG_WITH([json], - AS_HELP_STRING( - [--with-json], - [Enable JSON output via janson or json-c @<:@default=auto@:>@]), - [], - [with_json=auto]) -if test x"$with_json" = x"yes" -o x"$with_json" = x"auto"; then - _with_json="$with_json" - with_json=auto - lldp_CHECK_JANSSON - if test x"$with_json" = x"no"; then - with_json=auto - lldp_CHECK_JSONC - fi - if test x"$with_json" = x"no" -a x"$_with_json" = x"yes"; then - AC_MSG_FAILURE([*** no JSON support found]) - fi -else - lldp_CHECK_JANSSON - lldp_CHECK_JSONC -fi +# JSON (built-in) lldp_ARG_ENABLE([json0], [use of pre-0.9.2 JSON/json-c format], [no]) # Seccomp @@ -389,9 +368,6 @@ AC_SUBST([LLDP_BIN_LDFLAGS]) 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"json-c" || test -x"$with_json" = x"jansson"]) -AM_CONDITIONAL([USE_JANSSON], [test x"$with_json" = x"jansson"]) -AM_CONDITIONAL([USE_JSONC], [test x"$with_json" = x"json-c"]) AM_CONDITIONAL([USE_SECCOMP], [test x"$with_seccomp" = x"yes"]) dnl If old default of AR_FLAGS is otherwise being used (because of older automake), dnl replace it with one without 'u' @@ -428,7 +404,6 @@ cat <= 5), dh-autoreconf, libsnmp-dev, libxml2-dev, - libjansson-dev | libjson-c-dev | libjson0-dev (>= 0.10), libevent-dev (>= 2.0.5), libreadline-dev, libbsd-dev, diff --git a/debian/copyright b/debian/copyright index 0535461c..e2aa1986 100644 --- a/debian/copyright +++ b/debian/copyright @@ -30,6 +30,10 @@ Copyright: Copyright 2001 Niels Provos Copyright (c) 2002 Matthieu Herrb License: BSD-2-clause +Files: src/client/utf8.c +Copyright: Copyright (c) 2011 Joseph A. Adams +License: Expat + Files: m4/ax_cflags_gcc_option.m4 Copyright: Copyright (c) 2008 Guido U. Draheim License: GPL-2+ with Autoconf exception @@ -216,3 +220,22 @@ License: GPL-2+ with Autoconf exception Macro released by the Autoconf Archive. When you make and distribute a modified version of the Autoconf Macro, you may extend this special exception to the GPL to apply to your modified version as well. + +License: Expat + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. diff --git a/debian/rules b/debian/rules index 42a8ce02..148bf582 100755 --- a/debian/rules +++ b/debian/rules @@ -4,5 +4,5 @@ include /usr/share/cdbs/1/rules/debhelper.mk include /usr/share/cdbs/1/rules/autoreconf.mk include /usr/share/cdbs/1/class/autotools.mk -DEB_CONFIGURE_EXTRA_FLAGS = --with-snmp --with-xml --with-json --enable-pie +DEB_CONFIGURE_EXTRA_FLAGS = --with-snmp --with-xml --enable-pie DEB_CONFIGURE_EXTRA_FLAGS += --with-systemdsystemunitdir=/lib/systemd/system diff --git a/m4/jansson.m4 b/m4/jansson.m4 deleted file mode 100644 index 291a177e..00000000 --- a/m4/jansson.m4 +++ /dev/null @@ -1,20 +0,0 @@ -# -# lldp_CHECK_JANSSON -# - -AC_DEFUN([lldp_CHECK_JANSSON], [ - if test x"$with_json" = x"auto" -o x"$with_json" = x"jansson"; then - PKG_CHECK_MODULES([JANSSON], [jansson >= 2], [ - AC_SUBST([JANSSON_LIBS]) - AC_SUBST([JANSSON_CFLAGS]) - AC_DEFINE_UNQUOTED([USE_JSON], 1, [Define to indicate to enable JSON support]) - AC_DEFINE_UNQUOTED([USE_JANSSON], 1, [Define to indicate to enable JSON support through jansson]) - with_json=jansson - ],[ - if test x"$with_json" = x"jansson"; then - AC_MSG_ERROR([*** unable to find libjansson]) - fi - with_json=no - ]) - fi -]) diff --git a/m4/json-c.m4 b/m4/json-c.m4 deleted file mode 100644 index bb9d647f..00000000 --- a/m4/json-c.m4 +++ /dev/null @@ -1,28 +0,0 @@ -# -# lldp_CHECK_JSONC -# - -AC_DEFUN([lldp_CHECK_JSONC], [ - if test x"$with_json" = x"auto" -o x"$with_json" = x"json-c"; then - PKG_CHECK_MODULES([JSONC], [json-c], [ - AC_SUBST([JSONC_LIBS]) - AC_SUBST([JSONC_CFLAGS]) - AC_DEFINE_UNQUOTED([USE_JSON], 1, [Define to indicate to enable JSON support]) - AC_DEFINE_UNQUOTED([USE_JSONC], 1, [Define to indicate to enable JSON via json-c support]) - with_json=json-c - ],[ - PKG_CHECK_MODULES([JSONC], [json >= 0.10], [ - AC_SUBST([JSONC_LIBS]) - AC_SUBST([JSONC_CFLAGS]) - AC_DEFINE_UNQUOTED([USE_JSON], 1, [Define to indicate to enable JSON support]) - AC_DEFINE_UNQUOTED([USE_JSONC], 1, [Define to indicate to enable JSON via json-c support]) - with_json=json-c - ],[ - if test x"$with_json" = x"json-c"; then - AC_MSG_ERROR([*** unable to find json-c]) - fi - with_json=no - ]) - ]) - fi -]) diff --git a/redhat/lldpd.spec b/redhat/lldpd.spec index 062155bf..e7b7a169 100644 --- a/redhat/lldpd.spec +++ b/redhat/lldpd.spec @@ -23,13 +23,6 @@ %bcond_with oldies %endif -# On RHEL and SLES, compile without JSON support -%if (0%{?rhel_version} > 0) || (0%{?centos_version} > 0 && 0%{?centos_version} < 600) || (0%{?suse_version} > 0) -%bcond_with json -%else -%bcond_without json -%endif - # On RHEL < 7, disable systemd # On SuSE < 12, disable systemd %if (0%{?rhel_version} > 0 && 0%{?rhel_version} < 700) || (0%{?centos_version} > 0 && 0%{?centos_version} < 700) || (0%{?suse_version} > 0 && 0%{?suse_version} < 1210) @@ -73,9 +66,6 @@ BuildRequires: openssl-devel %if %{with xml} BuildRequires: libxml2-devel %endif -%if %{with json} -BuildRequires: json-c-devel -%endif %if %{with systemd} %if 0%{?suse_version} BuildRequires: systemd-rpm-macros diff --git a/src/client/Makefile.am b/src/client/Makefile.am index eb476c00..6f7ccd4e 100644 --- a/src/client/Makefile.am +++ b/src/client/Makefile.am @@ -17,7 +17,8 @@ lldpcli_SOURCES = client.h lldpcli.c display.c \ conf-lldp.c conf-system.c \ commands.c show.c \ misc.c tokenizer.c \ - writer.h text_writer.c kv_writer.c + utf8.c \ + writer.h text_writer.c kv_writer.c json_writer.c lldpcli_LDADD = \ $(top_builddir)/src/libcommon-daemon-client.la \ $(top_builddir)/src/lib/liblldpctl.la \ @@ -31,18 +32,6 @@ lldpcli_CFLAGS += @XML2_CFLAGS@ lldpcli_LDADD += @XML2_LIBS@ endif -if USE_JANSSON -lldpcli_SOURCES += jansson_writer.c -lldpcli_CFLAGS += @JANSSON_CFLAGS@ -lldpcli_LDADD += @JANSSON_LIBS@ -endif - -if USE_JSONC -lldpcli_SOURCES += jsonc_writer.c -lldpcli_CFLAGS += @JSONC_CFLAGS@ -lldpcli_LDADD += @JSONC_LIBS@ -endif - # Completions bashcompletiondir = $(datadir)/bash-completion/completions dist_bashcompletion_DATA = completion/lldpcli diff --git a/src/client/jansson_writer.c b/src/client/jansson_writer.c deleted file mode 100644 index 15d45393..00000000 --- a/src/client/jansson_writer.c +++ /dev/null @@ -1,231 +0,0 @@ -/* -*- 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" - -/* 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_element_list, json_element); -struct json_writer_private { - FILE *fh; - struct json_element_list els; -}; - -static void -jansson_start(struct writer *w, const char *tag, const char *descr) -{ - struct json_writer_private *p = w->priv; - struct json_element *current = TAILQ_LAST(&p->els, json_element_list); - 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->els, new, next); -} - -static void -jansson_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->els, json_element_list); - json_t *jvalue; - if (value && (!strcmp(value, "yes") || !strcmp(value, "on"))) - jvalue = json_true(); - else if (value && (!strcmp(value, "no") || !strcmp(value, "off"))) - jvalue = json_false(); - else - jvalue = json_string(value?value:""); - json_object_set_new(current->el, tag, jvalue); -} - -static void -jansson_data(struct writer *w, const char *data) -{ - struct json_writer_private *p = w->priv; - struct json_element *current = TAILQ_LAST(&p->els, json_element_list); - json_object_set_new(current->el, "value", - json_string(data?data:"")); -} - -/* 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* -jansson_cleanup(json_t *el) -{ - if (el == NULL) return NULL; -#ifndef ENABLE_JSON0 - json_t *new; - if (json_is_array(el) && json_array_size(el) == 1) { - new = json_array_get(el, 0); - return jansson_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, - jansson_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)) { - json_t *value; - json_t *name = NULL; - void *iter = json_object_iter(el); - new = json_object(); - while (iter) { - const char *key; - key = json_object_iter_key(iter); - value = jansson_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; - } -#endif - json_incref(el); - return el; -} - -static void -jansson_end(struct writer *w) -{ - struct json_writer_private *p = w->priv; - struct json_element *current = TAILQ_LAST(&p->els, json_element_list); - if (current == NULL) { - log_warnx("lldpctl", "unbalanced tags"); - return; - } - TAILQ_REMOVE(&p->els, current, next); - free(current); - - /* Display current object if last one */ - if (TAILQ_NEXT(TAILQ_FIRST(&p->els), next) == NULL) { - struct json_element *root = TAILQ_FIRST(&p->els); - json_t *export = jansson_cleanup(root->el); - if (json_dumpf(export, - p->fh, - JSON_INDENT(2) | JSON_PRESERVE_ORDER) == -1) - log_warnx("lldpctl", "unable to output JSON"); - fprintf(p->fh,"\n"); - fflush(p->fh); - json_decref(export); - json_decref(root->el); - root->el = json_object(); - if (root->el == NULL) - fatalx("lldpctl", "cannot create JSON root object"); - } -} - -static void -jansson_finish(struct writer *w) -{ - struct json_writer_private *p = w->priv; - if (TAILQ_EMPTY(&p->els)) { - log_warnx("lldpctl", "nothing to output"); - } else if (TAILQ_NEXT(TAILQ_FIRST(&p->els), next) != NULL) { - log_warnx("lldpctl", "unbalanced tags"); - /* memory will leak... */ - } else { - struct json_element *root = TAILQ_FIRST(&p->els); - json_decref(root->el); - TAILQ_REMOVE(&p->els, root, next); - free(root); - } - free(p); - free(w); -} - -struct writer* -jansson_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); - - priv->fh = fh; - TAILQ_INIT(&priv->els); - TAILQ_INSERT_TAIL(&priv->els, root, next); - root->el = json_object(); - if (root->el == NULL) - fatalx("lldpctl", "cannot create JSON root object"); - - result = malloc(sizeof(*result)); - if (result == NULL) fatal(NULL, NULL); - - result->priv = priv; - result->start = jansson_start; - result->attr = jansson_attr; - result->data = jansson_data; - result->end = jansson_end; - result->finish = jansson_finish; - - return result; -} diff --git a/src/client/json_writer.c b/src/client/json_writer.c new file mode 100644 index 00000000..7167a698 --- /dev/null +++ b/src/client/json_writer.c @@ -0,0 +1,371 @@ +/* -*- 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 "writer.h" +#include "../compat/compat.h" +#include "../log.h" + +enum tag { + STRING, + BOOL, + ARRAY, + OBJECT +}; + +struct element { + struct element *parent; /* Parent (if any) */ + TAILQ_ENTRY(element) next; /* Sibling (if any) */ + char *key; /* Key if parent is an object */ + enum tag tag; /* Kind of element */ + union { + char *string; /* STRING */ + int boolean; /* BOOL */ + TAILQ_HEAD(, element) children; /* ARRAY or OBJECT */ + }; +}; + +struct json_writer_private { + FILE *fh; + struct element *root; + struct element *current; /* should always be an object */ +}; + +/* Create a new element. If a parent is provided, it will also be attached to + * the parent. */ +static struct element* +json_element_new(struct element *parent, const char *key, enum tag tag) +{ + struct element *child = malloc(sizeof(*child)); + if (child == NULL) fatal(NULL, NULL); + child->parent = parent; + child->key = key?strdup(key):NULL; + child->tag = tag; + TAILQ_INIT(&child->children); + if (parent) TAILQ_INSERT_TAIL(&parent->children, child, next); + return child; +} + +/* Free the element content (but not the element itself) */ +static void +json_element_free(struct element *current) +{ + struct element *el, *el_next; + switch (current->tag) { + case STRING: + free(current->string); + break; + case BOOL: + break; + case ARRAY: + case OBJECT: + for (el = TAILQ_FIRST(¤t->children); + el != NULL; + el = el_next) { + el_next = TAILQ_NEXT(el, next); + json_element_free(el); + TAILQ_REMOVE(¤t->children, el, next); + if (current->tag == OBJECT) free(el->key); + free(el); + } + break; + } +} + +static void +json_free(struct json_writer_private *p) +{ + json_element_free(p->root); + free(p->root); +} + +static void +json_string_dump(FILE *fh, const char *s) +{ + fprintf(fh, "\""); + while (*s != '\0') { + unsigned int c = *s; + size_t len; + switch (c) { + case '"': fprintf(fh, "\\\""); s++; break; + case '\\': fprintf(fh, "\\\\"); s++; break; + case '\b': fprintf(fh, "\\\b"); s++; break; + case '\f': fprintf(fh, "\\\f"); s++; break; + case '\n': fprintf(fh, "\\\n"); s++; break; + case '\r': fprintf(fh, "\\\r"); s++; break; + case '\t': fprintf(fh, "\\\t"); s++; break; + default: + len = utf8_validate_cz(s); + if (len == 0) { + /* Not a valid UTF-8 char, use a + * replacement character */ + fprintf(fh, "\\uFFFD"); + s++; + } else if (c < 0x1f) { + /* 7-bit ASCII character */ + fprintf(fh, "\\u%04X", c); + s++; + } else { + /* UTF-8, write as is */ + while (len--) fprintf(fh, "%c", *s++); + } + break; + } + } + fprintf(fh, "\""); +} + +/* Dump an element to the specified file handle. */ +static void +json_element_dump(FILE *fh, struct element *current, int indent) +{ + static const char pairs[2][2] = { "{}", "[]" }; + struct element *el; + switch (current->tag) { + case STRING: + json_string_dump(fh, current->string); + break; + case BOOL: + fprintf(fh, current->boolean?"true":"false"); + break; + case ARRAY: + case OBJECT: + fprintf(fh, "%c\n%*s", pairs[(current->tag == ARRAY)][0], + indent + 2, ""); + TAILQ_FOREACH(el, ¤t->children, next) { + if (current->tag == OBJECT) + fprintf(fh, "\"%s\": ", el->key); + json_element_dump(fh, el, indent + 2); + if (TAILQ_NEXT(el, next)) + fprintf(fh, ",\n%*s", indent + 2, ""); + } + fprintf(fh, "\n%*c", indent + 1, + pairs[(current->tag == ARRAY)][1]); + break; + } +} + +static void +json_dump(struct json_writer_private *p) +{ + json_element_dump(p->fh, p->root, 0); + fprintf(p->fh, "\n"); +} + +static void +json_start(struct writer *w, const char *tag, + const char *descr) +{ + struct json_writer_private *p = w->priv; + struct element *child; + struct element *new; + + /* Look for the tag in the current object. */ + TAILQ_FOREACH(child, &p->current->children, next) { + if (!strcmp(child->key, tag)) break; + } + if (!child) + child = json_element_new(p->current, tag, ARRAY); + + /* Queue the new element. */ + new = json_element_new(child, NULL, OBJECT); + p->current = new; +} + +static void +json_attr(struct writer *w, const char *tag, + const char *descr, const char *value) +{ + struct json_writer_private *p = w->priv; + struct element *new = json_element_new(p->current, tag, STRING); + if (value && (!strcmp(value, "yes") || !strcmp(value, "on"))) { + new->tag = BOOL; + new->boolean = 1; + } else if (value && (!strcmp(value, "no") || !strcmp(value, "off"))) { + new->tag = BOOL; + new->boolean = 0; + } else { + new->string = strdup(value?value:""); + } +} + +static void +json_data(struct writer *w, const char *data) +{ + struct json_writer_private *p = w->priv; + struct element *new = json_element_new(p->current, "value", STRING); + new->string = strdup(data?data:""); +} + +/* 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. Also, the provided element can be + * destroyed. Don't use it after this function! + * + * During the cleaning process, we will generate array of 1-size objects that + * could be turned into an object. We don't do that since people may rely on + * this format. Another problem is the format is changing depending on the + * number of interfaces or the number of neighbors. + */ +static void +json_element_cleanup(struct element *el) +{ +#ifndef ENABLE_JSON0 + struct element *child, *child_next; + + /* If array with one element, steal the content. Object with only one + * value whose key is "value", steal the content. */ + if ((el->tag == ARRAY || el->tag == OBJECT) && + (child = TAILQ_FIRST(&el->children)) && + !TAILQ_NEXT(child, next) && + (el->tag == ARRAY || !strcmp(child->key, "value"))) { + free(child->key); + child->key = el->key; + child->parent = el->parent; + TAILQ_INSERT_BEFORE(el, child, next); + TAILQ_REMOVE(&el->parent->children, el, next); + free(el); + json_element_cleanup(child); + return; + } + + /* Other kind of arrays, recursively clean */ + if (el->tag == ARRAY) { + for (child = TAILQ_FIRST(&el->children); + child; + child = child_next) { + child_next = TAILQ_NEXT(child, next); + json_element_cleanup(child); + } + return; + } + + /* Other kind of objects, recursively clean, but if one key is "name", + * use it's value as a key for a new object stealing the existing + * one. */ + if (el->tag == OBJECT) { + struct element *name_child = NULL; + for (child = TAILQ_FIRST(&el->children); + child; + child = child_next) { + child_next = TAILQ_NEXT(child, next); + json_element_cleanup(child); + } + /* Redo a check to find if we have a "name" key now */ + for (child = TAILQ_FIRST(&el->children); + child; + child = child_next) { + child_next = TAILQ_NEXT(child, next); + if (!strcmp(child->key, "name") && + child->tag == STRING) { + name_child = child; + } + } + if (name_child) { + struct element *new_el = json_element_new(NULL, NULL, OBJECT); + /* Replace el by new_el in parent object/array */ + new_el->parent = el->parent; + TAILQ_INSERT_BEFORE(el, new_el, next); + TAILQ_REMOVE(&el->parent->children, el, next); + new_el->key = el->key; + + /* new_el is parent of el */ + el->parent = new_el; + el->key = name_child->string; /* stolen */ + TAILQ_INSERT_TAIL(&new_el->children, el, next); + + /* Remove "name" child */ + TAILQ_REMOVE(&el->children, name_child, next); + free(name_child->key); + free(name_child); + } + return; + } +#endif +} + +static void +json_cleanup(struct json_writer_private *p) +{ + json_element_cleanup(p->root); +} + +static void +json_end(struct writer *w) +{ + struct json_writer_private *p = w->priv; + while ((p->current = p->current->parent) != NULL && p->current->tag != OBJECT); + if (p->current == NULL) { + fatalx("lldpctl", "unbalanced tags"); + return; + } + + /* Display current object if last one */ + if (p->current == p->root) { + json_cleanup(p); + json_dump(p); + json_free(p); + fprintf(p->fh,"\n"); + fflush(p->fh); + p->root = p->current = json_element_new(NULL, NULL, OBJECT); + } +} + +static void +json_finish(struct writer *w) +{ + struct json_writer_private *p = w->priv; + if (p->current != p->root) + log_warnx("lldpctl", "unbalanced tags"); + json_free(p); + free(p); + free(w); +} + +struct writer* +json_init(FILE *fh) +{ + struct writer *result; + struct json_writer_private *priv; + + priv = malloc(sizeof(*priv)); + if (priv == NULL) fatal(NULL, NULL); + + priv->fh = fh; + priv->root = priv->current = json_element_new(NULL, NULL, 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/jsonc_writer.c b/src/client/jsonc_writer.c deleted file mode 100644 index 6e67aad2..00000000 --- a/src/client/jsonc_writer.c +++ /dev/null @@ -1,225 +0,0 @@ -/* -*- mode: c; c-file-style: "openbsd" -*- */ -/* - * Copyright (c) 2014 Michel Stam , - * 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" - -/* 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_object *el; -}; -TAILQ_HEAD(json_element_list, json_element); -struct json_writer_private { - FILE *fh; - struct json_element_list els; -}; - -static void -jsonc_start(struct writer *w, const char *tag, const char *descr) -{ - struct json_writer_private *p = w->priv; - struct json_element *current = TAILQ_LAST(&p->els, json_element_list); - struct json_element *new; - json_object *exist = NULL; - - if (!json_object_object_get_ex(current->el, tag, &exist)) { - exist = json_object_new_array(); - json_object_object_add(current->el, tag, exist); - } - - /* Queue the new element. */ - new = malloc(sizeof(*new)); - if (new == NULL) fatal(NULL, NULL); - new->el = json_object_new_object(); - json_object_array_add(exist, new->el); - TAILQ_INSERT_TAIL(&p->els, new, next); -} - -static void -jsonc_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->els, json_element_list); - json_object *jvalue; - if (value && (!strcmp(value, "yes") || !strcmp(value, "on"))) - jvalue = json_object_new_boolean(1); - else if (value && (!strcmp(value, "no") || !strcmp(value, "off"))) - jvalue = json_object_new_boolean(0); - else - jvalue = json_object_new_string(value?value:""); - json_object_object_add(current->el, tag, jvalue); -} - -static void -jsonc_data(struct writer *w, const char *data) -{ - struct json_writer_private *p = w->priv; - struct json_element *current = TAILQ_LAST(&p->els, json_element_list); - json_object_object_add(current->el, "value", - json_object_new_string(data?data:"")); -} - -/* 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_object* -jsonc_cleanup(json_object *el) -{ - if (el == NULL) return NULL; -#ifndef ENABLE_JSON0 - json_object *new; - if (json_object_get_type(el) == json_type_array) { - size_t len = json_object_array_length(el); - if (len == 1) { - new = json_object_array_get_idx(el, 0); - return jsonc_cleanup(new); - } - new = json_object_new_array(); - for (size_t i = 0; i < len; i++) { - json_object_array_add(new, - jsonc_cleanup(json_object_array_get_idx(el, i))); - } - return new; - } - if (json_object_get_type(el) == json_type_object) { - if (json_object_object_length(el) == 1 && - json_object_object_get_ex(el, "value", &new)) { - json_object_get(new); - return new; /* This is a string or a boolean, no need to - * cleanup */ - } - - json_object *name = NULL; - new = json_object_new_object(); - json_object_object_foreach(el, key, value) { - value = jsonc_cleanup(value); - if (strcmp(key, "name") || - json_object_get_type(value) != json_type_string) { - json_object_object_add(new, key, value); - } else { - name = value; - } - } - if (name) { - /* Embed the current object into a new one with the name - * as key. */ - json_object *replacement = json_object_new_object(); - json_object_object_add(replacement, - json_object_get_string(name), new); - json_object_put(name); - return replacement; - } - return new; - } -#endif - json_object_get(el); - return el; -} - -static void -jsonc_end(struct writer *w) -{ - struct json_writer_private *p = w->priv; - struct json_element *current = TAILQ_LAST(&p->els, json_element_list); - if (current == NULL) { - log_warnx("lldpctl", "unbalanced tags"); - return; - } - TAILQ_REMOVE(&p->els, current, next); - free(current); - - /* Display current object if last one */ - if (TAILQ_NEXT(TAILQ_FIRST(&p->els), next) == NULL) { - struct json_element *root = TAILQ_FIRST(&p->els); - json_object *export = jsonc_cleanup(root->el); - int json_flags = (JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED); - const char *s = json_object_to_json_string_ext(export, json_flags); - fprintf(p->fh, "%s\n", s?s:"{}"); - fflush(p->fh); - json_object_put(export); - json_object_put(root->el); - root->el = json_object_new_object(); - if (root->el == NULL) - fatalx("lldpctl", "cannot create JSON root object"); - } -} - -static void -jsonc_finish(struct writer *w) -{ - struct json_writer_private *p = w->priv; - if (TAILQ_EMPTY(&p->els)) { - log_warnx("lldpctl", "nothing to output"); - } else if (TAILQ_NEXT(TAILQ_FIRST(&p->els), next) != NULL) { - log_warnx("lldpctl", "unbalanced tags"); - /* memory will leak... */ - } else { - struct json_element *root = TAILQ_FIRST(&p->els); - json_object_put(root->el); - TAILQ_REMOVE(&p->els, root, next); - free(root); - } - free(p); - free(w); -} - -struct writer* -jsonc_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); - - priv->fh = fh; - TAILQ_INIT(&priv->els); - TAILQ_INSERT_TAIL(&priv->els, root, next); - root->el = json_object_new_object(); - if (root->el == NULL) - fatalx("lldpctl", "cannot create JSON root object"); - - result = malloc(sizeof(*result)); - if (result == NULL) fatal(NULL, NULL); - - result->priv = priv; - result->start = jsonc_start; - result->attr = jsonc_attr; - result->data = jsonc_data; - result->end = jsonc_end; - result->finish = jsonc_finish; - - return result; -} diff --git a/src/client/lldpcli.c b/src/client/lldpcli.c index 9f1380a4..7263483c 100644 --- a/src/client/lldpcli.c +++ b/src/client/lldpcli.c @@ -68,12 +68,9 @@ usage() fprintf(stderr, "-d Enable more debugging information.\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" + fprintf(stderr, "-f format Choose output format (plain, keyvalue, json" #if defined USE_XML ", xml" -#endif -#if defined USE_JANSSON || defined USE_JSONC - ", json" #endif ").\n"); if (!is_lldpctl(NULL)) @@ -275,14 +272,9 @@ 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); #ifdef USE_XML else if (strcmp(fmt, "xml") == 0) w = xml_init(stdout); -#endif -#ifdef USE_JANSSON - else if (strcmp(fmt, "json") == 0) w = jansson_init(stdout); -#endif -#ifdef USE_JSONC - else if (strcmp(fmt, "json") == 0) w = jsonc_init(stdout); #endif else { log_warnx("lldpctl", "unknown output format \"%s\"", fmt); diff --git a/src/client/utf8.c b/src/client/utf8.c new file mode 100644 index 00000000..21076393 --- /dev/null +++ b/src/client/utf8.c @@ -0,0 +1,99 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + Copyright (c) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#include + +/* + * Validate a single UTF-8 character starting at @s. + * The string must be null-terminated. + * + * If it's valid, return its length (1 thru 4). + * If it's invalid or clipped, return 0. + * + * This function implements the syntax given in RFC3629, which is + * the same as that given in The Unicode Standard, Version 6.0. + * + * It has the following properties: + * + * * All codepoints U+0000..U+10FFFF may be encoded, + * except for U+D800..U+DFFF, which are reserved + * for UTF-16 surrogate pair encoding. + * * UTF-8 byte sequences longer than 4 bytes are not permitted, + * as they exceed the range of Unicode. + * * The sixty-six Unicode "non-characters" are permitted + * (namely, U+FDD0..U+FDEF, U+xxFFFE, and U+xxFFFF). + */ +size_t +utf8_validate_cz(const char *s) +{ + unsigned char c = *s++; + + if (c <= 0x7F) { /* 00..7F */ + return 1; + } else if (c <= 0xC1) { /* 80..C1 */ + /* Disallow overlong 2-byte sequence. */ + return 0; + } else if (c <= 0xDF) { /* C2..DF */ + /* Make sure subsequent byte is in the range 0x80..0xBF. */ + if (((unsigned char)*s++ & 0xC0) != 0x80) + return 0; + + return 2; + } else if (c <= 0xEF) { /* E0..EF */ + /* Disallow overlong 3-byte sequence. */ + if (c == 0xE0 && (unsigned char)*s < 0xA0) + return 0; + + /* Disallow U+D800..U+DFFF. */ + if (c == 0xED && (unsigned char)*s > 0x9F) + return 0; + + /* Make sure subsequent bytes are in the range 0x80..0xBF. */ + if (((unsigned char)*s++ & 0xC0) != 0x80) + return 0; + if (((unsigned char)*s++ & 0xC0) != 0x80) + return 0; + + return 3; + } else if (c <= 0xF4) { /* F0..F4 */ + /* Disallow overlong 4-byte sequence. */ + if (c == 0xF0 && (unsigned char)*s < 0x90) + return 0; + + /* Disallow codepoints beyond U+10FFFF. */ + if (c == 0xF4 && (unsigned char)*s > 0x8F) + return 0; + + /* Make sure subsequent bytes are in the range 0x80..0xBF. */ + if (((unsigned char)*s++ & 0xC0) != 0x80) + return 0; + if (((unsigned char)*s++ & 0xC0) != 0x80) + return 0; + if (((unsigned char)*s++ & 0xC0) != 0x80) + return 0; + + return 4; + } else { /* F5..FF */ + return 0; + } +} diff --git a/src/client/writer.h b/src/client/writer.h index 1a76d94b..be9f96a6 100644 --- a/src/client/writer.h +++ b/src/client/writer.h @@ -37,15 +37,13 @@ struct writer { extern struct writer *txt_init(FILE *); extern struct writer *kv_init(FILE *); +extern struct writer *json_init(FILE *); #ifdef USE_XML extern struct writer *xml_init(FILE *); #endif -#ifdef USE_JANSSON -extern struct writer *jansson_init(FILE *); -#endif -#ifdef USE_JSONC -extern struct writer *jsonc_init(FILE *); -#endif + +/* utf8.c */ +size_t utf8_validate_cz(const char *s); #endif /* _WRITER_H */ diff --git a/src/version.c b/src/version.c index 9f835b9e..4b88999e 100644 --- a/src/version.c +++ b/src/version.c @@ -72,11 +72,11 @@ version_display(FILE *destination, const char *progname, int verbose) #endif NULL}; const char *const output_formats[] = { + "TEXT", + "KV", + "JSON", #ifdef USE_XML "XML", -#endif -#ifdef USE_JSON - "JSON", #endif NULL}; diff --git a/tests/ci/install.sh b/tests/ci/install.sh index f6f9cf8b..3da09c6f 100755 --- a/tests/ci/install.sh +++ b/tests/ci/install.sh @@ -17,7 +17,7 @@ case "$(uname -s)" in sudo apt-get -qqy update sudo apt-get -qqy install \ automake autoconf libtool pkg-config \ - libsnmp-dev libxml2-dev libjansson-dev libjson-c-dev \ + libsnmp-dev libxml2-dev \ libevent-dev libreadline-dev libbsd-dev \ check libc6-dbg libevent-dbg libseccomp-dev \ libpcap-dev