NetworkManager/shared/nm-glib-aux/nm-json-aux.c
Thomas Haller ca7bb15591
shared: cleanup dlopening libjansson depending on configure options
- assert that WITH_JANSSON and JANSSON_SONAME are defined consistently.
  This check ensures that we can check at compile time that nm_json_vt()
  will always fail (because JANSSON_SONAME) is undefined.
  That is interesting, because this means you can do a compile time
  for !WITH_JANSSON, and know if nm_json_vt() will *never* succeed.
  With that, we could let the linker know when the code is unused
  and remove all uses of nm_json_vt(), without using the traditional
  conditional compilation with "#if WITH_JANSSON". But of course, we
  currently don't do this micro optimization to remove defunct code.

- drop the "mode" helper variable and pass the flags directly to
  dlopen().
2020-07-09 12:57:15 +02:00

270 lines
6.2 KiB
C

// SPDX-License-Identifier: LGPL-2.1+
/*
* Copyright (C) 2017 - 2019 Red Hat, Inc.
*/
#include "nm-default.h"
#include "nm-json-aux.h"
#include <dlfcn.h>
/*****************************************************************************/
static void
_gstr_append_string_len (GString *gstr,
const char *str,
gsize len)
{
g_string_append_c (gstr, '\"');
while (len > 0) {
gsize n;
const char *end;
gboolean valid;
nm_assert (len > 0);
valid = g_utf8_validate (str, len, &end);
nm_assert ( end
&& end >= str
&& end <= &str[len]);
if (end > str) {
const char *s;
for (s = str; s < end; s++) {
nm_assert (s[0] != '\0');
if (s[0] < 0x20) {
const char *text;
switch (s[0]) {
case '\\': text = "\\\\"; break;
case '\"': text = "\\\""; break;
case '\b': text = "\\b"; break;
case '\f': text = "\\f"; break;
case '\n': text = "\\n"; break;
case '\r': text = "\\r"; break;
case '\t': text = "\\t"; break;
default:
g_string_append_printf (gstr, "\\u%04X", (guint) s[0]);
continue;
}
g_string_append (gstr, text);
continue;
}
if (NM_IN_SET (s[0], '\\', '\"'))
g_string_append_c (gstr, '\\');
g_string_append_c (gstr, s[0]);
}
} else
nm_assert (!valid);
if (valid) {
nm_assert (end == &str[len]);
break;
}
nm_assert (end < &str[len]);
if (end[0] == '\0') {
/* there is a NUL byte in the string. Technically this is valid UTF-8, so we
* encode it there. However, this will likely result in a truncated string when
* parsing. */
g_string_append (gstr, "\\u0000");
} else {
/* the character is not valid UTF-8. There is nothing we can do about it, because
* JSON can only contain UTF-8 and even the escape sequences can only escape Unicode
* codepoints (but not binary).
*
* The argument is not a string (in any known encoding), hence we cannot represent
* it as a JSON string (which are unicode strings).
*
* Print an underscore instead of the invalid char :) */
g_string_append_c (gstr, '_');
}
n = str - end;
nm_assert (n < len);
n++;
str += n;
len -= n;
}
g_string_append_c (gstr, '\"');
}
void
nm_json_gstr_append_string_len (GString *gstr,
const char *str,
gsize n)
{
g_return_if_fail (gstr);
_gstr_append_string_len (gstr, str, n);
}
void
nm_json_gstr_append_string (GString *gstr,
const char *str)
{
g_return_if_fail (gstr);
if (!str)
g_string_append (gstr, "null");
else
_gstr_append_string_len (gstr, str, strlen (str));
}
void
nm_json_gstr_append_obj_name (GString *gstr,
const char *key,
char start_container)
{
g_return_if_fail (gstr);
g_return_if_fail (key);
nm_json_gstr_append_string (gstr, key);
if (start_container != '\0') {
nm_assert (NM_IN_SET (start_container, '[', '{'));
g_string_append_printf (gstr, ": %c ", start_container);
} else
g_string_append (gstr, ": ");
}
/*****************************************************************************/
typedef struct {
NMJsonVt vt;
void *dl_handle;
} NMJsonVtInternal;
static NMJsonVtInternal *
_nm_json_vt_internal_load (void)
{
NMJsonVtInternal *v;
const char *soname;
void *handle;
v = g_new0 (NMJsonVtInternal, 1);
#if WITH_JANSSON && defined (JANSSON_SONAME)
G_STATIC_ASSERT_EXPR (NM_STRLEN (JANSSON_SONAME) > 0);
nm_assert (strlen (JANSSON_SONAME) > 0);
soname = JANSSON_SONAME;
#elif !WITH_JANSSON && !defined (JANSSON_SONAME)
soname = NULL;
#else
#error "WITH_JANSON and JANSSON_SONAME are defined inconsistently."
#endif
if (!soname)
return v;
handle = dlopen (soname, RTLD_LAZY
| RTLD_LOCAL
| RTLD_NODELETE
#if !defined (ASAN_BUILD)
| RTLD_DEEPBIND
#endif
| 0);
if (!handle)
return v;
#define TRY_BIND_SYMBOL(symbol) \
G_STMT_START { \
void *_sym = dlsym (handle, #symbol); \
\
if (!_sym) \
goto fail_symbol; \
v->vt.nm_ ## symbol = _sym; \
} G_STMT_END
TRY_BIND_SYMBOL (json_array);
TRY_BIND_SYMBOL (json_array_append_new);
TRY_BIND_SYMBOL (json_array_get);
TRY_BIND_SYMBOL (json_array_size);
TRY_BIND_SYMBOL (json_delete);
TRY_BIND_SYMBOL (json_dumps);
TRY_BIND_SYMBOL (json_false);
TRY_BIND_SYMBOL (json_integer);
TRY_BIND_SYMBOL (json_integer_value);
TRY_BIND_SYMBOL (json_loads);
TRY_BIND_SYMBOL (json_object);
TRY_BIND_SYMBOL (json_object_del);
TRY_BIND_SYMBOL (json_object_get);
TRY_BIND_SYMBOL (json_object_iter);
TRY_BIND_SYMBOL (json_object_iter_key);
TRY_BIND_SYMBOL (json_object_iter_next);
TRY_BIND_SYMBOL (json_object_iter_value);
TRY_BIND_SYMBOL (json_object_key_to_iter);
TRY_BIND_SYMBOL (json_object_set_new);
TRY_BIND_SYMBOL (json_object_size);
TRY_BIND_SYMBOL (json_string);
TRY_BIND_SYMBOL (json_string_value);
TRY_BIND_SYMBOL (json_true);
v->vt.loaded = TRUE;
v->dl_handle = handle;
return v;
fail_symbol:
dlclose (&handle);
*v = (NMJsonVtInternal) { };
return v;
}
const NMJsonVt *_nm_json_vt_ptr = NULL;
const NMJsonVt *
_nm_json_vt_init (void)
{
NMJsonVtInternal *v;
again:
v = g_atomic_pointer_get ((gpointer *) &_nm_json_vt_ptr);
if (G_UNLIKELY (!v)) {
v = _nm_json_vt_internal_load ();
if (!g_atomic_pointer_compare_and_exchange ((gpointer *) &_nm_json_vt_ptr, NULL, v)) {
if (v->dl_handle)
dlclose (v->dl_handle);
g_free (v);
goto again;
}
/* we transfer ownership. */
}
nm_assert (v && v == g_atomic_pointer_get ((gpointer *) &_nm_json_vt_ptr));
return &v->vt;
}
const NMJsonVt *
nmtst_json_vt_reset (gboolean loaded)
{
NMJsonVtInternal *v_old;
NMJsonVtInternal *v;
v_old = g_atomic_pointer_get ((gpointer *) &_nm_json_vt_ptr);
if (!loaded) {
/* load a fake instance for testing. */
v = g_new0 (NMJsonVtInternal, 1);
} else
v = _nm_json_vt_internal_load ();
if (!g_atomic_pointer_compare_and_exchange ((gpointer *) &_nm_json_vt_ptr, v_old, v))
g_assert_not_reached ();
if (v_old) {
if (v_old->dl_handle)
dlclose (v_old->dl_handle);
g_free ((gpointer *) v_old);
}
return v->vt.loaded ? &v->vt : NULL;
}