[libvirt] [PATCHv3 03/13] Switch from yajl to Jansson

Ján Tomko posted 13 patches 7 years ago
[libvirt] [PATCHv3 03/13] Switch from yajl to Jansson
Posted by Ján Tomko 7 years ago
Yajl has not seen much activity upstream recently.
Switch to using Jansson >= 2.7.

All the platforms we target on https://libvirt.org/platforms.html
have a version >= 2.7 listed on the sites below:
https://repology.org/metapackage/jansson/versions
https://build.opensuse.org/package/show/devel:libraries:c_c++/libjansson

Implement virJSONValue{From,To}String using Jansson, delete the yajl
code (and the related virJSONParser structure) and report an error
if someone explicitly specifies --with-yajl.

Also adjust the test data to account for Jansson's different whitespace
usage for empty arrays and tune up the specfile to keep 'make rpm'
working when bisecting.

Signed-off-by: Ján Tomko <jtomko@redhat.com>
---
 src/util/virjson.c | 211 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 211 insertions(+)

diff --git a/src/util/virjson.c b/src/util/virjson.c
index 0559d40b64..2f7d624bb3 100644
--- a/src/util/virjson.c
+++ b/src/util/virjson.c
@@ -2008,6 +2008,217 @@ virJSONValueToString(virJSONValuePtr object,
 }
 
 
+#elif WITH_JANSSON
+# include <jansson.h>
+
+static virJSONValuePtr
+virJSONValueFromJansson(json_t *json)
+{
+    virJSONValuePtr ret = NULL;
+    const char *key;
+    json_t *cur;
+    size_t i;
+
+    switch (json_typeof(json)) {
+    case JSON_OBJECT:
+        ret = virJSONValueNewObject();
+        if (!ret)
+            goto error;
+
+        json_object_foreach(json, key, cur) {
+            virJSONValuePtr val = virJSONValueFromJansson(cur);
+            if (!val)
+                goto error;
+
+            if (virJSONValueObjectAppend(ret, key, val) < 0) {
+                virJSONValueFree(val);
+                goto error;
+            }
+        }
+
+        break;
+
+    case JSON_ARRAY:
+        ret = virJSONValueNewArray();
+        if (!ret)
+            goto error;
+
+        json_array_foreach(json, i, cur) {
+            virJSONValuePtr val = virJSONValueFromJansson(cur);
+            if (!val)
+                goto error;
+
+            if (virJSONValueArrayAppend(ret, val) < 0) {
+                virJSONValueFree(val);
+                goto error;
+            }
+        }
+        break;
+
+    case JSON_STRING:
+        ret = virJSONValueNewStringLen(json_string_value(json),
+                                       json_string_length(json));
+        break;
+
+    case JSON_INTEGER:
+        ret = virJSONValueNewNumberLong(json_integer_value(json));
+        break;
+
+    case JSON_REAL:
+        ret = virJSONValueNewNumberDouble(json_real_value(json));
+        break;
+
+    case JSON_TRUE:
+    case JSON_FALSE:
+        ret = virJSONValueNewBoolean(json_boolean_value(json));
+        break;
+
+    case JSON_NULL:
+        ret = virJSONValueNewNull();
+        break;
+    }
+
+    return ret;
+
+ error:
+    virJSONValueFree(ret);
+    return NULL;
+}
+
+virJSONValuePtr
+virJSONValueFromString(const char *jsonstring)
+{
+    virJSONValuePtr ret = NULL;
+    json_t *json;
+    json_error_t error;
+    size_t flags = JSON_REJECT_DUPLICATES |
+                   JSON_DECODE_ANY;
+
+    if (!(json = json_loads(jsonstring, flags, &error))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("failed to parse JSON %d:%d: %s"),
+                       error.line, error.column, error.text);
+        return NULL;
+    }
+
+    ret = virJSONValueFromJansson(json);
+    json_decref(json);
+    return ret;
+}
+
+
+static json_t *
+virJSONValueToJansson(virJSONValuePtr object)
+{
+    json_t *ret = NULL;
+    size_t i;
+
+    switch ((virJSONType)object->type) {
+    case VIR_JSON_TYPE_OBJECT:
+        ret = json_object();
+        if (!ret) {
+            virReportOOMError();
+            goto error;
+        }
+        for (i = 0; i < object->data.object.npairs; i++) {
+            virJSONObjectPairPtr cur = object->data.object.pairs + i;
+            json_t *val = virJSONValueToJansson(cur->value);
+
+            if (!val)
+                goto error;
+            if (json_object_set_new(ret, cur->key, val) < 0) {
+                virReportOOMError();
+                goto error;
+            }
+        }
+        break;
+
+    case VIR_JSON_TYPE_ARRAY:
+        ret = json_array();
+        if (!ret) {
+            virReportOOMError();
+            goto error;
+        }
+        for (i = 0; i < object->data.array.nvalues; i++) {
+            virJSONValuePtr cur = object->data.array.values[i];
+            json_t *val = virJSONValueToJansson(cur);
+
+            if (!val)
+                goto error;
+            if (json_array_append_new(ret, val) < 0) {
+                virReportOOMError();
+                goto error;
+            }
+        }
+        break;
+
+    case VIR_JSON_TYPE_STRING:
+        ret = json_string(object->data.string);
+        break;
+
+    case VIR_JSON_TYPE_NUMBER: {
+        long long ll_val;
+        double d_val;
+        if (virStrToLong_ll(object->data.number, NULL, 10, &ll_val) < 0) {
+            if (virStrToDouble(object->data.number, NULL, &d_val) < 0) {
+                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                               _("JSON value is not a number"));
+                return NULL;
+            }
+            ret = json_real(d_val);
+        } else {
+            ret = json_integer(ll_val);
+        }
+    }
+        break;
+
+    case VIR_JSON_TYPE_BOOLEAN:
+        ret = json_boolean(object->data.boolean);
+        break;
+
+    case VIR_JSON_TYPE_NULL:
+        ret = json_null();
+        break;
+
+    default:
+        virReportEnumRangeError(virJSONType, object->type);
+        goto error;
+    }
+    if (!ret)
+        virReportOOMError();
+    return ret;
+
+ error:
+    json_decref(ret);
+    return NULL;
+}
+
+
+char *
+virJSONValueToString(virJSONValuePtr object,
+                     bool pretty)
+{
+    size_t flags = JSON_ENCODE_ANY;
+    json_t *json;
+    char *str = NULL;
+
+    if (pretty)
+        flags |= JSON_INDENT(2);
+    else
+        flags |= JSON_COMPACT;
+
+    json = virJSONValueToJansson(object);
+    if (!json)
+        return NULL;
+
+    str = json_dumps(json, flags);
+    if (!str)
+        virReportOOMError();
+    json_decref(json);
+    return str;
+}
+
+
 #else
 virJSONValuePtr
 virJSONValueFromString(const char *jsonstring ATTRIBUTE_UNUSED)
-- 
2.16.1

--
libvir-list mailing list
libvir-list@redhat.com
https://www.redhat.com/mailman/listinfo/libvir-list
Re: [libvirt] [PATCHv3 03/13] Switch from yajl to Jansson
Posted by Ján Tomko 6 years, 12 months ago
On Fri, May 11, 2018 at 02:59:04PM +0200, Ján Tomko wrote:
>Yajl has not seen much activity upstream recently.
>Switch to using Jansson >= 2.7.
>
>All the platforms we target on https://libvirt.org/platforms.html
>have a version >= 2.7 listed on the sites below:
>https://repology.org/metapackage/jansson/versions
>https://build.opensuse.org/package/show/devel:libraries:c_c++/libjansson
>
>Implement virJSONValue{From,To}String using Jansson, delete the yajl
>code (and the related virJSONParser structure) and report an error
>if someone explicitly specifies --with-yajl.
>
>Also adjust the test data to account for Jansson's different whitespace
>usage for empty arrays and tune up the specfile to keep 'make rpm'
>working when bisecting.
>
>Signed-off-by: Ján Tomko <jtomko@redhat.com>
>---
> src/util/virjson.c | 211 +++++++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 211 insertions(+)
>

With this change, it should be possible to build with Jansson 2.5 as
well:
diff --git a/src/util/virjson.c b/src/util/virjson.c
index 2f7d624bb3..0d7d368c8a 100644
--- a/src/util/virjson.c
+++ b/src/util/virjson.c
@@ -2056,8 +2056,7 @@ virJSONValueFromJansson(json_t *json)
         break;

     case JSON_STRING:
-        ret = virJSONValueNewStringLen(json_string_value(json),
-                                       json_string_length(json));
+        ret = virJSONValueNewString(json_string_value(json));
         break;

     case JSON_INTEGER:
@@ -2070,7 +2069,7 @@ virJSONValueFromJansson(json_t *json)

     case JSON_TRUE:
     case JSON_FALSE:
-        ret = virJSONValueNewBoolean(json_boolean_value(json));
+        ret = virJSONValueNewBoolean(json_is_true(json));
         break;

     case JSON_NULL:

Jano
--
libvir-list mailing list
libvir-list@redhat.com
https://www.redhat.com/mailman/listinfo/libvir-list
Re: [libvirt] [PATCHv3 03/13] Switch from yajl to Jansson
Posted by Peter Krempa 6 years, 12 months ago
On Tue, May 15, 2018 at 14:45:05 +0200, Ján Tomko wrote:
> On Fri, May 11, 2018 at 02:59:04PM +0200, Ján Tomko wrote:
> > Yajl has not seen much activity upstream recently.
> > Switch to using Jansson >= 2.7.
> > 
> > All the platforms we target on https://libvirt.org/platforms.html
> > have a version >= 2.7 listed on the sites below:
> > https://repology.org/metapackage/jansson/versions
> > https://build.opensuse.org/package/show/devel:libraries:c_c++/libjansson
> > 
> > Implement virJSONValue{From,To}String using Jansson, delete the yajl
> > code (and the related virJSONParser structure) and report an error
> > if someone explicitly specifies --with-yajl.
> > 
> > Also adjust the test data to account for Jansson's different whitespace
> > usage for empty arrays and tune up the specfile to keep 'make rpm'
> > working when bisecting.
> > 
> > Signed-off-by: Ján Tomko <jtomko@redhat.com>
> > ---
> > src/util/virjson.c | 211 +++++++++++++++++++++++++++++++++++++++++++++++++++++
> > 1 file changed, 211 insertions(+)
> > 
> 
> With this change, it should be possible to build with Jansson 2.5 as
> well:
> diff --git a/src/util/virjson.c b/src/util/virjson.c
> index 2f7d624bb3..0d7d368c8a 100644
> --- a/src/util/virjson.c
> +++ b/src/util/virjson.c
> @@ -2056,8 +2056,7 @@ virJSONValueFromJansson(json_t *json)
>         break;
> 
>     case JSON_STRING:
> -        ret = virJSONValueNewStringLen(json_string_value(json),
> -                                       json_string_length(json));
> +        ret = virJSONValueNewString(json_string_value(json));
>         break;
> 
>     case JSON_INTEGER:
> @@ -2070,7 +2069,7 @@ virJSONValueFromJansson(json_t *json)
> 
>     case JSON_TRUE:
>     case JSON_FALSE:
> -        ret = virJSONValueNewBoolean(json_boolean_value(json));
> +        ret = virJSONValueNewBoolean(json_is_true(json));

Given how that macro works. I'd rather see following:
     case JSON_TRUE:
          ret = virJSONValueNewBoolean(true);
          break;

     case JSON_FALSE:
          ret = virJSONValueNewBoolean(false);
          break;
--
libvir-list mailing list
libvir-list@redhat.com
https://www.redhat.com/mailman/listinfo/libvir-list
Re: [libvirt] [PATCHv3 03/13] Switch from yajl to Jansson
Posted by Peter Krempa 6 years, 12 months ago
On Fri, May 11, 2018 at 14:59:04 +0200, Ján Tomko wrote:
> Yajl has not seen much activity upstream recently.

[0]

> Switch to using Jansson >= 2.7.
> 
> All the platforms we target on https://libvirt.org/platforms.html
> have a version >= 2.7 listed on the sites below:
> https://repology.org/metapackage/jansson/versions
> https://build.opensuse.org/package/show/devel:libraries:c_c++/libjansson
> 
> Implement virJSONValue{From,To}String using Jansson, delete the yajl
> code (and the related virJSONParser structure) and report an error
> if someone explicitly specifies --with-yajl.
> 
> Also adjust the test data to account for Jansson's different whitespace
> usage for empty arrays and tune up the specfile to keep 'make rpm'
> working when bisecting.
> 
> Signed-off-by: Ján Tomko <jtomko@redhat.com>
> ---
>  src/util/virjson.c | 211 +++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 211 insertions(+)
> 
> diff --git a/src/util/virjson.c b/src/util/virjson.c
> index 0559d40b64..2f7d624bb3 100644
> --- a/src/util/virjson.c
> +++ b/src/util/virjson.c
> @@ -2008,6 +2008,217 @@ virJSONValueToString(virJSONValuePtr object,

[...]

> +static json_t *
> +virJSONValueToJansson(virJSONValuePtr object)
> +{
> +    json_t *ret = NULL;
> +    size_t i;
> +
> +    switch ((virJSONType)object->type) {
> +    case VIR_JSON_TYPE_OBJECT:
> +        ret = json_object();
> +        if (!ret) {
> +            virReportOOMError();
> +            goto error;
> +        }
> +        for (i = 0; i < object->data.object.npairs; i++) {
> +            virJSONObjectPairPtr cur = object->data.object.pairs + i;
> +            json_t *val = virJSONValueToJansson(cur->value);
> +
> +            if (!val)
> +                goto error;
> +            if (json_object_set_new(ret, cur->key, val) < 0) {
> +                virReportOOMError();

'val' is leaked here.

> +                goto error;
> +            }
> +        }
> +        break;
> +
> +    case VIR_JSON_TYPE_ARRAY:
> +        ret = json_array();
> +        if (!ret) {
> +            virReportOOMError();
> +            goto error;
> +        }
> +        for (i = 0; i < object->data.array.nvalues; i++) {
> +            virJSONValuePtr cur = object->data.array.values[i];
> +            json_t *val = virJSONValueToJansson(cur);
> +
> +            if (!val)
> +                goto error;
> +            if (json_array_append_new(ret, val) < 0) {
> +                virReportOOMError();

'val' is leaked here.

> +                goto error;
> +            }
> +        }
> +        break;
> +
> +    case VIR_JSON_TYPE_STRING:
> +        ret = json_string(object->data.string);
> +        break;
> +
> +    case VIR_JSON_TYPE_NUMBER: {
> +        long long ll_val;
> +        double d_val;
> +        if (virStrToLong_ll(object->data.number, NULL, 10, &ll_val) < 0) {
> +            if (virStrToDouble(object->data.number, NULL, &d_val) < 0) {
> +                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> +                               _("JSON value is not a number"));
> +                return NULL;
> +            }
> +            ret = json_real(d_val);
> +        } else {
> +            ret = json_integer(ll_val);
> +        }
> +    }
> +        break;
> +
> +    case VIR_JSON_TYPE_BOOLEAN:
> +        ret = json_boolean(object->data.boolean);
> +        break;
> +
> +    case VIR_JSON_TYPE_NULL:
> +        ret = json_null();
> +        break;
> +
> +    default:
> +        virReportEnumRangeError(virJSONType, object->type);
> +        goto error;
> +    }
> +    if (!ret)
> +        virReportOOMError();
> +    return ret;

Few lines could be saved by having a dedicated label for cases when
virReportOOMError needs to be called.

> +
> + error:
> +    json_decref(ret);
> +    return NULL;

ACK if you fix the leaks. The separate label is up to you. Don't forget
to update the commit message after the squash-in.

Peter

[0] For anyone following current meme trends, this looks like it's
relevant to our YAJL->janson switch:

https://i.redditmedia.com/J46fZN24lFx3fMRlJNpkNEOFqU79zWTsRDBMla1u0HE.jpg?s=4757d31d1cbd758962407917e0d3aacb
--
libvir-list mailing list
libvir-list@redhat.com
https://www.redhat.com/mailman/listinfo/libvir-list
Re: [libvirt] [PATCHv3 03/13] Switch from yajl to Jansson
Posted by Daniel P. Berrangé 6 years, 12 months ago
On Tue, May 15, 2018 at 03:05:55PM +0200, Peter Krempa wrote:
> On Fri, May 11, 2018 at 14:59:04 +0200, Ján Tomko wrote:
> > Yajl has not seen much activity upstream recently.
> 
> [0]

[snip]
> [0] For anyone following current meme trends, this looks like it's
> relevant to our YAJL->janson switch:
> 
> https://i.redditmedia.com/J46fZN24lFx3fMRlJNpkNEOFqU79zWTsRDBMla1u0HE.jpg?s=4757d31d1cbd758962407917e0d3aacb

If you think we should stay with YAJL because it "just works" then
take a look at the bug reports against it upstream

  https://github.com/lloyd/yajl/issues

double frees, memory leaks, parser gets stuck after parsing bad JSON, etc
all ignored for years.  The robustness of the JSON parser we use is
critical to the security of the libvirt QEMU driver.  I'm not saying
janson is perfect, but it is at least maintained, so when problem in
it as discovered, there's a real possibility to get them fixed, instead
of ignored with the YAJL abandonware.

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|

--
libvir-list mailing list
libvir-list@redhat.com
https://www.redhat.com/mailman/listinfo/libvir-list
Re: [libvirt] [PATCHv3 03/13] Switch from yajl to Jansson
Posted by Peter Krempa 6 years, 12 months ago
On Tue, May 15, 2018 at 14:21:07 +0100, Daniel Berrange wrote:
> On Tue, May 15, 2018 at 03:05:55PM +0200, Peter Krempa wrote:
> > On Fri, May 11, 2018 at 14:59:04 +0200, Ján Tomko wrote:
> > > Yajl has not seen much activity upstream recently.
> > 
> > [0]
> 
> [snip]
> > [0] For anyone following current meme trends, this looks like it's
> > relevant to our YAJL->janson switch:
> > 
> > https://i.redditmedia.com/J46fZN24lFx3fMRlJNpkNEOFqU79zWTsRDBMla1u0HE.jpg?s=4757d31d1cbd758962407917e0d3aacb
> 
> If you think we should stay with YAJL because it "just works" then
> take a look at the bug reports against it upstream
> 
>   https://github.com/lloyd/yajl/issues
> 
> double frees, memory leaks, parser gets stuck after parsing bad JSON, etc
> all ignored for years.  The robustness of the JSON parser we use is
> critical to the security of the libvirt QEMU driver.  I'm not saying
> janson is perfect, but it is at least maintained, so when problem in
> it as discovered, there's a real possibility to get them fixed, instead
> of ignored with the YAJL abandonware.

No. I agree with the change, but "having serious flaws" is slightly
different than "not very active upstream".
--
libvir-list mailing list
libvir-list@redhat.com
https://www.redhat.com/mailman/listinfo/libvir-list
Re: [libvirt] [PATCHv3 03/13] Switch from yajl to Jansson
Posted by Daniel P. Berrangé 6 years, 12 months ago
On Tue, May 15, 2018 at 03:23:17PM +0200, Peter Krempa wrote:
> On Tue, May 15, 2018 at 14:21:07 +0100, Daniel Berrange wrote:
> > On Tue, May 15, 2018 at 03:05:55PM +0200, Peter Krempa wrote:
> > > On Fri, May 11, 2018 at 14:59:04 +0200, Ján Tomko wrote:
> > > > Yajl has not seen much activity upstream recently.
> > > 
> > > [0]
> > 
> > [snip]
> > > [0] For anyone following current meme trends, this looks like it's
> > > relevant to our YAJL->janson switch:
> > > 
> > > https://i.redditmedia.com/J46fZN24lFx3fMRlJNpkNEOFqU79zWTsRDBMla1u0HE.jpg?s=4757d31d1cbd758962407917e0d3aacb
> > 
> > If you think we should stay with YAJL because it "just works" then
> > take a look at the bug reports against it upstream
> > 
> >   https://github.com/lloyd/yajl/issues
> > 
> > double frees, memory leaks, parser gets stuck after parsing bad JSON, etc
> > all ignored for years.  The robustness of the JSON parser we use is
> > critical to the security of the libvirt QEMU driver.  I'm not saying
> > janson is perfect, but it is at least maintained, so when problem in
> > it as discovered, there's a real possibility to get them fixed, instead
> > of ignored with the YAJL abandonware.
> 
> No. I agree with the change, but "having serious flaws" is slightly
> different than "not very active upstream".

I don't think that makes much difference. All non-trivial software has
serious flaws, especially if written in memory-unsafe languages like
C. The only question is whether the flaws have been found yet. Given
that, whether there is an active upstream maintaining it, is a key
factor to decide whether to continue using something. The fact that
we already see significant unfixed flaws just reinforces how important
it is to have an active upstream.

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|

--
libvir-list mailing list
libvir-list@redhat.com
https://www.redhat.com/mailman/listinfo/libvir-list