diff options
| author | Michael Biebl <biebl@debian.org> | 2020-04-11 21:28:04 +0200 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2020-04-11 21:28:04 +0200 |
| commit | 1e5977b62f896e844b548c3007ace9e1dfa7f9ed (patch) | |
| tree | 7a7416ed410e72b6200f3d860fd315ec11cc106b /shared/nm-glib-aux | |
| parent | b012fa6e1d808e0736c009799c62d835cbfcc1dd (diff) | |
New upstream version 1.23.90 upstream/1.23.90
Diffstat (limited to 'shared/nm-glib-aux')
22 files changed, 2985 insertions, 485 deletions
diff --git a/shared/nm-glib-aux/nm-dbus-aux.c b/shared/nm-glib-aux/nm-dbus-aux.c index 86061aef..54e54ca7 100644 --- a/shared/nm-glib-aux/nm-dbus-aux.c +++ b/shared/nm-glib-aux/nm-dbus-aux.c @@ -56,7 +56,7 @@ nm_dbus_connection_call_get_name_owner (GDBusConnection *dbus_connection, /*****************************************************************************/ static void -_nm_dbus_connection_call_get_all_cb (GObject *source, +_nm_dbus_connection_call_default_cb (GObject *source, GAsyncResult *res, gpointer user_data) { @@ -96,100 +96,36 @@ nm_dbus_connection_call_get_all (GDBusConnection *dbus_connection, G_DBUS_CALL_FLAGS_NONE, timeout_msec, cancellable, - _nm_dbus_connection_call_get_all_cb, + _nm_dbus_connection_call_default_cb, nm_utils_user_data_pack (user_data, callback)); } -/*****************************************************************************/ - -typedef struct { - NMDBusConnectionSignalObjectMangerCb callback; - gpointer user_data; - GDestroyNotify user_data_free_func; -} SubscribeObjectManagerData; - -static void -_subscribe_object_manager_cb (GDBusConnection *connection, - const char *sender_name, - const char *arg_object_path, - const char *interface_name, - const char *signal_name, - GVariant *parameters, - gpointer user_data) -{ - const SubscribeObjectManagerData *d = user_data; - - nm_assert (nm_streq0 (interface_name, DBUS_INTERFACE_OBJECT_MANAGER)); - - if (nm_streq (signal_name, "InterfacesAdded")) { - gs_unref_variant GVariant *interfaces_and_properties = NULL; - const char *object_path; - - if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(oa{sa{sv}})"))) - return; - - g_variant_get (parameters, - "(&o@a{sa{sv}})", - &object_path, - &interfaces_and_properties); - - d->callback (object_path, interfaces_and_properties, NULL, d->user_data); - return; - } - - if (nm_streq (signal_name, "InterfacesRemoved")) { - gs_free const char **interfaces = NULL; - const char *object_path; - - if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(oas)"))) - return; - - g_variant_get (parameters, - "(&o^a&s)", - &object_path, - &interfaces); - - d->callback (object_path, NULL, interfaces, d->user_data); - return; - } -} - -static void -_subscribe_object_manager_data_free (gpointer ptr) -{ - SubscribeObjectManagerData *d = ptr; - - if (d->user_data_free_func) - d->user_data_free_func (d->user_data); - nm_g_slice_free (d); -} - -guint -nm_dbus_connection_signal_subscribe_object_manager (GDBusConnection *dbus_connection, - const char *service_name, - const char *object_path, - NMDBusConnectionSignalObjectMangerCb callback, - gpointer user_data, - GDestroyNotify user_data_free_func) +void nm_dbus_connection_call_set (GDBusConnection *dbus_connection, + const char *bus_name, + const char *object_path, + const char *interface_name, + const char *property_name, + GVariant *value, + int timeout_msec, + GCancellable *cancellable, + NMDBusConnectionCallDefaultCb callback, + gpointer user_data) { - SubscribeObjectManagerData *d; - - g_return_val_if_fail (callback, 0); - - d = g_slice_new (SubscribeObjectManagerData); - *d = (SubscribeObjectManagerData) { - .callback = callback, - .user_data = user_data, - .user_data_free_func = user_data_free_func, - }; - - return nm_dbus_connection_signal_subscribe_object_manager_plain (dbus_connection, - service_name, - object_path, - NULL, - _subscribe_object_manager_cb, - d, - _subscribe_object_manager_data_free); + g_dbus_connection_call (dbus_connection, + bus_name, + object_path, + DBUS_INTERFACE_PROPERTIES, + "Set", + g_variant_new ("(ssv)", + interface_name, + property_name, + value), + G_VARIANT_TYPE ("()"), + G_DBUS_CALL_FLAGS_NONE, + timeout_msec, + cancellable, + callback ? _nm_dbus_connection_call_default_cb : NULL, + callback ? nm_utils_user_data_pack (user_data, callback) : NULL); } /*****************************************************************************/ @@ -269,10 +205,9 @@ _call_finish_cb (GObject *source, return; } - if (!return_void) { - nm_assert (!g_variant_is_of_type (ret, G_VARIANT_TYPE ("()"))); + if (!return_void) g_task_return_pointer (task, g_steal_pointer (&ret), (GDestroyNotify) g_variant_unref); - } else { + else { nm_assert (g_variant_is_of_type (ret, G_VARIANT_TYPE ("()"))); g_task_return_boolean (task, TRUE); } @@ -317,7 +252,6 @@ nm_dbus_connection_call_finish_void_strip_dbus_error_cb (GObject *source, * * - user_data must be a GTask, whose reference will be consumed by the * callback. - * - the return GVariant must not be an empty tuple "()". * - the GTask is returned either with error or with a pointer containing the GVariant. */ void diff --git a/shared/nm-glib-aux/nm-dbus-aux.h b/shared/nm-glib-aux/nm-dbus-aux.h index 840e23c2..fcf394d1 100644 --- a/shared/nm-glib-aux/nm-dbus-aux.h +++ b/shared/nm-glib-aux/nm-dbus-aux.h @@ -128,16 +128,27 @@ void nm_dbus_connection_call_get_all (GDBusConnection *dbus_connection, NMDBusConnectionCallDefaultCb callback, gpointer user_data); +void nm_dbus_connection_call_set (GDBusConnection *dbus_connection, + const char *bus_name, + const char *object_path, + const char *interface_name, + const char *property_name, + GVariant *value, + int timeout_msec, + GCancellable *cancellable, + NMDBusConnectionCallDefaultCb callback, + gpointer user_data); + /*****************************************************************************/ static inline guint -nm_dbus_connection_signal_subscribe_object_manager_plain (GDBusConnection *dbus_connection, - const char *service_name, - const char *object_path, - const char *signal_name, - GDBusSignalCallback callback, - gpointer user_data, - GDestroyNotify user_data_free_func) +nm_dbus_connection_signal_subscribe_object_manager (GDBusConnection *dbus_connection, + const char *service_name, + const char *object_path, + const char *signal_name, + GDBusSignalCallback callback, + gpointer user_data, + GDestroyNotify user_data_free_func) { return g_dbus_connection_signal_subscribe (dbus_connection, service_name, @@ -151,18 +162,6 @@ nm_dbus_connection_signal_subscribe_object_manager_plain (GDBusConnection *dbus_ user_data_free_func); } -typedef void (*NMDBusConnectionSignalObjectMangerCb) (const char *object_path, - GVariant *added_interfaces_and_properties, - const char *const*removed_interfaces, - gpointer user_data); - -guint nm_dbus_connection_signal_subscribe_object_manager (GDBusConnection *dbus_connection, - const char *service_name, - const char *object_path, - NMDBusConnectionSignalObjectMangerCb callback, - gpointer user_data, - GDestroyNotify user_data_free_func); - void nm_dbus_connection_call_get_managed_objects (GDBusConnection *dbus_connection, const char *bus_name, const char *object_path, diff --git a/shared/nm-glib-aux/nm-enum-utils.c b/shared/nm-glib-aux/nm-enum-utils.c index e105a4f5..854eda6e 100644 --- a/shared/nm-glib-aux/nm-enum-utils.c +++ b/shared/nm-glib-aux/nm-enum-utils.c @@ -6,6 +6,7 @@ #include "nm-default.h" #include "nm-enum-utils.h" +#include "nm-str-buf.h" /*****************************************************************************/ @@ -142,12 +143,14 @@ _nm_utils_enum_to_str_full (GType type, else return g_strdup (enum_value->value_nick); } else if (G_IS_FLAGS_CLASS (klass)) { - GFlagsValue *flags_value; - GString *str = g_string_new (""); unsigned uvalue = (unsigned) value; + GFlagsValue *flags_value; + NMStrBuf strbuf; flags_separator = flags_separator ?: " "; + nm_str_buf_init (&strbuf, 16, FALSE); + for ( ; value_infos && value_infos->nick; value_infos++) { nm_assert (_enum_is_valid_flags_nick (value_infos->nick)); @@ -160,9 +163,9 @@ _nm_utils_enum_to_str_full (GType type, continue; } - if (str->len) - g_string_append (str, flags_separator); - g_string_append (str, value_infos->nick); + if (strbuf.len) + nm_str_buf_append (&strbuf, flags_separator); + nm_str_buf_append (&strbuf, value_infos->nick); uvalue &= ~((unsigned) value_infos->value); if (uvalue == 0) { /* we printed all flags. Done. */ @@ -172,20 +175,20 @@ _nm_utils_enum_to_str_full (GType type, do { flags_value = g_flags_get_first_value (G_FLAGS_CLASS (klass), uvalue); - if (str->len) - g_string_append (str, flags_separator); + if (strbuf.len) + nm_str_buf_append (&strbuf, flags_separator); if ( !flags_value || !_enum_is_valid_flags_nick (flags_value->value_nick)) { if (uvalue) - g_string_append_printf (str, "0x%x", uvalue); + nm_str_buf_append_printf (&strbuf, "0x%x", uvalue); break; } - g_string_append (str, flags_value->value_nick); + nm_str_buf_append (&strbuf, flags_value->value_nick); uvalue &= ~flags_value->value; } while (uvalue); flags_done: - return g_string_free (str, FALSE); + return nm_str_buf_finalize (&strbuf, NULL); } g_return_val_if_reached (NULL); diff --git a/shared/nm-glib-aux/nm-errno.c b/shared/nm-glib-aux/nm-errno.c index 9133d87d..e709f9e6 100644 --- a/shared/nm-glib-aux/nm-errno.c +++ b/shared/nm-glib-aux/nm-errno.c @@ -11,7 +11,8 @@ /*****************************************************************************/ -NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_geterror, +static +NM_UTILS_LOOKUP_STR_DEFINE (_geterror, #if 0 enum _NMErrno, #else diff --git a/shared/nm-glib-aux/nm-glib.h b/shared/nm-glib-aux/nm-glib.h index 26d0eacd..4ecba9ff 100644 --- a/shared/nm-glib-aux/nm-glib.h +++ b/shared/nm-glib-aux/nm-glib.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0+ +// SPDX-License-Identifier: LGPL-2.1+ /* * Copyright (C) 2008 - 2018 Red Hat, Inc. */ @@ -193,22 +193,6 @@ __g_test_add_data_func_full (const char *testpath, /*****************************************************************************/ -#if !GLIB_CHECK_VERSION (2, 34, 0) -#define G_DEFINE_QUARK(QN, q_n) \ -GQuark \ -q_n##_quark (void) \ -{ \ - static GQuark q; \ - \ - if G_UNLIKELY (q == 0) \ - q = g_quark_from_static_string (#QN); \ - \ - return q; \ -} -#endif - -/*****************************************************************************/ - static inline gboolean nm_g_hash_table_replace (GHashTable *hash, gpointer key, gpointer value) { @@ -601,4 +585,41 @@ _g_atomic_pointer_compare_and_exchange (volatile void *atomic, /*****************************************************************************/ +#if !GLIB_CHECK_VERSION (2, 58, 0) +static inline gboolean +g_hash_table_steal_extended (GHashTable *hash_table, + gconstpointer lookup_key, + gpointer *stolen_key, + gpointer *stolen_value) +{ + g_assert (stolen_key); + g_assert (stolen_value); + + if (g_hash_table_lookup_extended (hash_table, lookup_key, stolen_key, stolen_value)) { + g_hash_table_steal (hash_table, lookup_key); + return TRUE; + } + *stolen_key = NULL; + *stolen_value = NULL; + return FALSE; +} +#else +#define g_hash_table_steal_extended(hash_table, lookup_key, stolen_key, stolen_value) \ + ({ \ + gpointer *_stolen_key = (stolen_key); \ + gpointer *_stolen_value = (stolen_value); \ + \ + /* we cannot allow NULL arguments, because then we would leak the values in + * the compat implementation. */ \ + g_assert (_stolen_key); \ + g_assert (_stolen_value); \ + \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ + g_hash_table_steal_extended (hash_table, lookup_key, _stolen_key, _stolen_value); \ + G_GNUC_END_IGNORE_DEPRECATIONS \ + }) +#endif + +/*****************************************************************************/ + #endif /* __NM_GLIB_H__ */ diff --git a/shared/nm-glib-aux/nm-hash-utils.c b/shared/nm-glib-aux/nm-hash-utils.c index cd51bbaf..0a701d06 100644 --- a/shared/nm-glib-aux/nm-hash-utils.c +++ b/shared/nm-glib-aux/nm-hash-utils.c @@ -24,7 +24,6 @@ static const guint8 *volatile global_seed = NULL; static const guint8 * _get_hash_key_init (void) { - static gsize g_lock; /* the returned hash is aligned to guin64, hence, it is safe * to use it as guint* or guint64* pointer. */ static union { @@ -34,50 +33,49 @@ _get_hash_key_init (void) guint64 _align_as_uint64; } g_arr; const guint8 *g; - union { - guint8 v8[HASH_KEY_SIZE]; - guint vuint; - } t_arr; again: g = g_atomic_pointer_get (&global_seed); - if (G_LIKELY (g != NULL)) { - nm_assert (g == g_arr.v8); - return g; - } - - { - CSipHash siph_state; + if (!G_UNLIKELY (g)) { + static gsize g_lock; uint64_t h; - - /* initialize a random key in t_arr. */ + union { + guint vuint; + guint8 v8[HASH_KEY_SIZE]; + guint8 _extra_entropy[3 * HASH_KEY_SIZE]; + } t_arr; nm_utils_random_bytes (&t_arr, sizeof (t_arr)); - /* use siphash() of the key-size, to mangle the first guint. Otherwise, - * the first guint has only the entropy that nm_utils_random_bytes() - * generated for the first 4 bytes and relies on a good random generator. + /* We only initialize one random hash key. So we can spend some effort + * of getting this right. For one, we collect more random bytes than + * necessary. * - * The first int is especially interesting for nm_hash_static() below, and we - * want to have it all the entropy of t_arr. */ - c_siphash_init (&siph_state, t_arr.v8); - c_siphash_append (&siph_state, (const guint8 *) &t_arr, sizeof (t_arr)); - h = c_siphash_finalize (&siph_state); - if (sizeof (guint) < sizeof (h)) - t_arr.vuint = t_arr.vuint ^ ((guint) (h & 0xFFFFFFFFu)) ^ ((guint) (h >> 32)); + * Then, the first guint of the seed should have all the entropy that we could + * obtain in sizeof(t_arr). For that, siphash(t_arr) and xor the first guint + * with hash. + * The first guint is especially interesting for nm_hash_static() below that + * doesn't use siphash itself. */ + h = c_siphash_hash (t_arr.v8, + (const guint8 *) &t_arr, + sizeof (t_arr)); + if (sizeof (h) > sizeof (guint)) + t_arr.vuint = t_arr.vuint ^ ((guint) (h & G_MAXUINT)) ^ ((guint) (h >> 32)); else - t_arr.vuint = t_arr.vuint ^ ((guint) (h & 0xFFFFFFFFu)); - } + t_arr.vuint = t_arr.vuint ^ ((guint) (h & G_MAXUINT)); - if (!g_once_init_enter (&g_lock)) { - /* lost a race. The random key is already initialized. */ - goto again; + if (!g_once_init_enter (&g_lock)) { + /* lost a race. The random key is already initialized. */ + goto again; + } + + memcpy (g_arr.v8, t_arr.v8, HASH_KEY_SIZE); + g = g_arr.v8; + g_atomic_pointer_set (&global_seed, g); + g_once_init_leave (&g_lock, 1); } - memcpy (g_arr.v8, t_arr.v8, HASH_KEY_SIZE); - g = g_arr.v8; - g_atomic_pointer_set (&global_seed, g); - g_once_init_leave (&g_lock, 1); + nm_assert (g == g_arr.v8); return g; } @@ -94,18 +92,24 @@ again: guint nm_hash_static (guint static_seed) { - /* note that we only xor the static_seed with the key. - * We don't use siphash, which would mix the bits better. - * Note that this doesn't matter, because static_seed is not - * supposed to be a value that you are hashing (for that, use - * full siphash). - * Instead, different callers may set a different static_seed - * so that nm_hash_str(NULL) != nm_hash_ptr(NULL). + /* Note that we only xor the static_seed with the first guint of the key. + * + * We don't use siphash, which would mix the bits better with _get_hash_key(). + * Note that nm_hash_static() isn't used to hash the static_seed. Instead, it + * is used to get a unique hash value in a static context. That means, every + * caller is responsible to choose a static_seed that is sufficiently + * distinct from all other callers. In other words, static_seed should be a + * unique constant with good entropy. + * + * Note that _get_hash_key_init() already xored the first guint of the + * key with the siphash of the entire static key. That means, even if + * we got bad randomness for the first guint, the first guint is also + * mixed with the randomness of the entire random key. * - * Also, ensure that we don't return zero. + * Also, ensure that we don't return zero (like for nm_hash_complete()). */ - return ((*((const guint *) _get_hash_key ())) ^ static_seed) - ?: static_seed ?: 3679500967u; + return ((*((const guint *) _get_hash_key ())) ^ static_seed) + ?: 3679500967u; } void @@ -257,3 +261,25 @@ nm_ppdirect_equal (gconstpointer a, gconstpointer b) return **s1 == **s2; } + +/*****************************************************************************/ + +guint +nm_pgbytes_hash (gconstpointer p) +{ + GBytes *const*ptr = p; + gconstpointer arr; + gsize len; + + arr = g_bytes_get_data (*ptr, &len); + return nm_hash_mem (1470631313u, arr, len); +} + +gboolean +nm_pgbytes_equal (gconstpointer a, gconstpointer b) +{ + GBytes *const*ptr_a = a; + GBytes *const*ptr_b = b; + + return g_bytes_equal (*ptr_a, *ptr_b); +} diff --git a/shared/nm-glib-aux/nm-hash-utils.h b/shared/nm-glib-aux/nm-hash-utils.h index 21c5e584..9f2e9766 100644 --- a/shared/nm-glib-aux/nm-hash-utils.h +++ b/shared/nm-glib-aux/nm-hash-utils.h @@ -88,15 +88,15 @@ nm_hash_complete (NMHashState *state) /* we don't ever want to return a zero hash. * * NMPObject requires that in _idx_obj_part(), and it's just a good idea. */ - return (((guint) (h >> 32)) ^ ((guint) h)) ?: 1396707757u; + return (((guint) (h >> 32)) ^ ((guint) h)) + ?: 1396707757u; } static inline void nm_hash_update (NMHashState *state, const void *ptr, gsize n) { nm_assert (state); - nm_assert (ptr); - nm_assert (n > 0); + nm_assert (n == 0 || ptr); /* Note: the data passed in here might be sensitive data (secrets), * that we should nm_explicty_zero() afterwards. However, since @@ -267,6 +267,18 @@ guint nm_str_hash (gconstpointer str); nm_hash_complete (&_h); \ }) +static inline guint +nm_hash_mem (guint static_seed, const void *ptr, gsize n) +{ + NMHashState h; + + if (n == 0) + return nm_hash_static (static_seed); + nm_hash_init (&h, static_seed); + nm_hash_update (&h, ptr, n); + return nm_hash_complete (&h); +} + /*****************************************************************************/ /* nm_pstr_*() are for hashing keys that are pointers to strings, @@ -303,6 +315,11 @@ gboolean nm_ppdirect_equal (gconstpointer a, gconstpointer b); /*****************************************************************************/ +guint nm_pgbytes_hash (gconstpointer p); +gboolean nm_pgbytes_equal (gconstpointer a, gconstpointer b); + +/*****************************************************************************/ + #define NM_HASH_OBFUSCATE_PTR_FMT "%016" G_GINT64_MODIFIER "x" /* sometimes we want to log a pointer directly, for providing context/information about diff --git a/shared/nm-glib-aux/nm-io-utils.c b/shared/nm-glib-aux/nm-io-utils.c index ec0cd4c8..776c63e1 100644 --- a/shared/nm-glib-aux/nm-io-utils.c +++ b/shared/nm-glib-aux/nm-io-utils.c @@ -11,6 +11,7 @@ #include <sys/stat.h> #include <fcntl.h> +#include "nm-str-buf.h" #include "nm-shared-utils.h" #include "nm-secret-utils.h" #include "nm-errno.h" @@ -51,32 +52,6 @@ _get_contents_error (GError **error, int errsv, int *out_errsv, const char *form _get_contents_error (error, _errsv, out_errsv, __VA_ARGS__); \ }) -static char * -_mem_realloc (char *old, gboolean do_bzero_mem, gsize cur_len, gsize new_len) -{ - char *new; - - /* re-allocating to zero bytes is an odd case. We don't need it - * and it's not supported. */ - nm_assert (new_len > 0); - - /* regardless of success/failure, @old will always be freed/consumed. */ - - if (do_bzero_mem && cur_len > 0) { - new = g_try_malloc (new_len); - if (new) - memcpy (new, old, NM_MIN (cur_len, new_len)); - nm_explicit_bzero (old, cur_len); - g_free (old); - } else { - new = g_try_realloc (old, new_len); - if (!new) - g_free (old); - } - - return new; -} - /** * nm_utils_fd_get_contents: * @fd: open file descriptor to read. The fd will not be closed, @@ -161,7 +136,7 @@ nm_utils_fd_get_contents (int fd, str[n_read] = '\0'; if (n_read < n_stat) { - if (!(str = _mem_realloc (str, do_bzero_mem, n_stat + 1, n_read + 1))) + if (!(str = nm_secret_mem_try_realloc_take (str, do_bzero_mem, n_stat + 1, n_read + 1))) return _get_contents_error (error, ENOMEM, out_errsv, "failure to reallocate buffer with %zu bytes", n_read + 1); } NM_SET_OUT (length, n_read); @@ -222,7 +197,7 @@ nm_utils_fd_get_contents (int fd, n_alloc = NM_MIN (n_read + 1, sizeof (buf)); } - if (!(str = _mem_realloc (str, do_bzero_mem, old_n_alloc, n_alloc))) { + if (!(str = nm_secret_mem_try_realloc_take (str, do_bzero_mem, old_n_alloc, n_alloc))) { if (do_bzero_mem) nm_explicit_bzero (buf, sizeof (buf)); return _get_contents_error (error, ENOMEM, out_errsv, "failure to allocate buffer of %zu bytes", n_alloc); @@ -241,7 +216,7 @@ nm_utils_fd_get_contents (int fd, else { str[n_have] = '\0'; if (n_have + 1 < n_alloc) { - if (!(str = _mem_realloc (str, do_bzero_mem, n_alloc, n_have + 1))) + if (!(str = nm_secret_mem_try_realloc_take (str, do_bzero_mem, n_alloc, n_have + 1))) return _get_contents_error (error, ENOMEM, out_errsv, "failure to truncate buffer to %zu bytes", n_have + 1); } } @@ -438,3 +413,46 @@ nm_utils_file_stat (const char *filename, struct stat *out_st) return -NM_ERRNO_NATIVE (errno); return 0; } + +/** + * nm_utils_fd_read: + * @fd: the fd to read from. + * @out_string: (out): output string where read bytes will be stored. + * + * Returns: <0 on failure, which is -(errno). + * 0 on EOF. + * >0 on success, which is the number of bytes read. */ +gssize +nm_utils_fd_read (int fd, NMStrBuf *out_string) +{ + gsize buf_available; + gssize n_read; + int errsv; + + g_return_val_if_fail (fd >= 0, -1); + g_return_val_if_fail (out_string, -1); + + /* If the buffer size is 0, we allocate NM_UTILS_GET_NEXT_REALLOC_SIZE_1000 (1000 bytes) + * the first time. Afterwards, the buffer grows exponentially. + * + * Note that with @buf_available, we always would read as much buffer as we actually + * have reserved. */ + nm_str_buf_maybe_expand (out_string, NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, FALSE); + + buf_available = out_string->allocated - out_string->len; + + n_read = read (fd, + &((nm_str_buf_get_str_unsafe (out_string))[out_string->len]), + buf_available); + if (n_read < 0) { + errsv = errno; + return -NM_ERRNO_NATIVE (errsv); + } + + if (n_read > 0) { + nm_assert ((gsize) n_read <= buf_available); + nm_str_buf_set_size (out_string, out_string->len + (gsize) n_read, TRUE, FALSE); + } + + return n_read; +} diff --git a/shared/nm-glib-aux/nm-io-utils.h b/shared/nm-glib-aux/nm-io-utils.h index 18dc5f74..31326fc7 100644 --- a/shared/nm-glib-aux/nm-io-utils.h +++ b/shared/nm-glib-aux/nm-io-utils.h @@ -47,6 +47,10 @@ gboolean nm_utils_file_set_contents (const char *filename, int *out_errsv, GError **error); +struct _NMStrBuf; + +gssize nm_utils_fd_read (int fd, struct _NMStrBuf *out_string); + struct stat; int nm_utils_file_stat (const char *filename, struct stat *out_st); diff --git a/shared/nm-glib-aux/nm-jansson.h b/shared/nm-glib-aux/nm-jansson.h index 0a75cff5..7c034222 100644 --- a/shared/nm-glib-aux/nm-jansson.h +++ b/shared/nm-glib-aux/nm-jansson.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0+ +// SPDX-License-Identifier: LGPL-2.1+ /* * Copyright (C) 2018 Red Hat, Inc. */ diff --git a/shared/nm-glib-aux/nm-logging-fwd.h b/shared/nm-glib-aux/nm-logging-fwd.h index 47661788..2daa302f 100644 --- a/shared/nm-glib-aux/nm-logging-fwd.h +++ b/shared/nm-glib-aux/nm-logging-fwd.h @@ -63,6 +63,14 @@ typedef enum { /*< skip >*/ LOGD_IP = LOGD_IP4 | LOGD_IP6, } NMLogDomain; +static inline NMLogDomain +LOGD_DHCP_from_addr_family (int addr_family) +{ + nm_assert_addr_family (addr_family); + + return addr_family == AF_INET6 ? LOGD_DHCP6 : LOGD_DHCP4; +} + /* Log levels */ typedef enum { /*< skip >*/ LOGL_TRACE, diff --git a/shared/nm-glib-aux/nm-macros-internal.h b/shared/nm-glib-aux/nm-macros-internal.h index d1ea04c0..d5a8513e 100644 --- a/shared/nm-glib-aux/nm-macros-internal.h +++ b/shared/nm-glib-aux/nm-macros-internal.h @@ -337,52 +337,65 @@ _nm_auto_freev (gpointer ptr) /*****************************************************************************/ -/* http://stackoverflow.com/a/11172679 */ -#define _NM_UTILS_MACRO_FIRST(...) __NM_UTILS_MACRO_FIRST_HELPER(__VA_ARGS__, throwaway) -#define __NM_UTILS_MACRO_FIRST_HELPER(first, ...) first - -#define _NM_UTILS_MACRO_REST(...) __NM_UTILS_MACRO_REST_HELPER(__NM_UTILS_MACRO_REST_NUM(__VA_ARGS__), __VA_ARGS__) -#define __NM_UTILS_MACRO_REST_HELPER(qty, ...) __NM_UTILS_MACRO_REST_HELPER2(qty, __VA_ARGS__) -#define __NM_UTILS_MACRO_REST_HELPER2(qty, ...) __NM_UTILS_MACRO_REST_HELPER_##qty(__VA_ARGS__) -#define __NM_UTILS_MACRO_REST_HELPER_ONE(first) -#define __NM_UTILS_MACRO_REST_HELPER_TWOORMORE(first, ...) , __VA_ARGS__ -#define __NM_UTILS_MACRO_REST_NUM(...) \ - __NM_UTILS_MACRO_REST_SELECT_30TH(__VA_ARGS__, \ - TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\ - TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\ - TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\ - TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\ - TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\ - TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway) -#define __NM_UTILS_MACRO_REST_SELECT_30TH(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, ...) a30 - -/*****************************************************************************/ - -/* http://stackoverflow.com/a/2124385/354393 - * https://stackoverflow.com/questions/11317474/macro-to-count-number-of-arguments - */ - -#define NM_NARG(...) \ - _NM_NARG(, ##__VA_ARGS__, _NM_NARG_RSEQ_N()) -#define _NM_NARG(...) \ - _NM_NARG_ARG_N(__VA_ARGS__) -#define _NM_NARG_ARG_N( \ - _0, \ +#define _NM_MACRO_SELECT_ARG_64( \ _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \ _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \ _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \ _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \ _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \ _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \ - _61,_62,_63,N,...) N -#define _NM_NARG_RSEQ_N() \ - 63,62,61,60, \ - 59,58,57,56,55,54,53,52,51,50, \ - 49,48,47,46,45,44,43,42,41,40, \ - 39,38,37,36,35,34,33,32,31,30, \ - 29,28,27,26,25,24,23,22,21,20, \ - 19,18,17,16,15,14,13,12,11,10, \ - 9,8,7,6,5,4,3,2,1,0 + _61,_62,_63, N,...) N + +/* http://stackoverflow.com/a/2124385/354393 + * https://stackoverflow.com/questions/11317474/macro-to-count-number-of-arguments + */ + +#define NM_NARG(...) \ + _NM_MACRO_SELECT_ARG_64(, ##__VA_ARGS__, \ + 62,61,60, \ + 59,58,57,56,55,54,53,52,51,50, \ + 49,48,47,46,45,44,43,42,41,40, \ + 39,38,37,36,35,34,33,32,31,30, \ + 29,28,27,26,25,24,23,22,21,20, \ + 19,18,17,16,15,14,13,12,11,10, \ + 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) +#define NM_NARG_MAX1(...) \ + _NM_MACRO_SELECT_ARG_64(, ##__VA_ARGS__, \ + 1, 1, 1, \ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0) +#define NM_NARG_MAX2(...) \ + _NM_MACRO_SELECT_ARG_64(, ##__VA_ARGS__, \ + 2, 2, 2, \ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \ + 2, 2, 2, 2, 2, 2, 2, 2, 1, 0) + +#define _NM_MACRO_CALL(macro, ...) macro (__VA_ARGS__) + +/*****************************************************************************/ + +#define _NM_MACRO_COMMA_IF_ARGS(...) _NM_MACRO_CALL (G_PASTE (__NM_MACRO_COMMA_IF_ARGS_, NM_NARG_MAX1(__VA_ARGS__)), __VA_ARGS__) +#define __NM_MACRO_COMMA_IF_ARGS_0() +#define __NM_MACRO_COMMA_IF_ARGS_1(...) , + +/*****************************************************************************/ + +/* http://stackoverflow.com/a/11172679 */ +#define _NM_UTILS_MACRO_FIRST(...) __NM_UTILS_MACRO_FIRST_HELPER(__VA_ARGS__, throwaway) +#define __NM_UTILS_MACRO_FIRST_HELPER(first, ...) first + +#define _NM_UTILS_MACRO_REST(...) _NM_MACRO_CALL (G_PASTE (__NM_UTILS_MACRO_REST_, NM_NARG_MAX2(__VA_ARGS__)), __VA_ARGS__) +#define __NM_UTILS_MACRO_REST_0() +#define __NM_UTILS_MACRO_REST_1(first) +#define __NM_UTILS_MACRO_REST_2(first, ...) , __VA_ARGS__ /*****************************************************************************/ @@ -744,6 +757,17 @@ NM_G_ERROR_MSG (GError *error) /*****************************************************************************/ +#define NM_SWAP(a, b) \ + G_STMT_START { \ + typeof (a) _tmp; \ + \ + _tmp = (a); \ + (a) = (b); \ + (b) = _tmp; \ + } G_STMT_END + +/*****************************************************************************/ + static inline gboolean _NM_IN_STRSET_streq (const char *x, const char *s) { @@ -980,6 +1004,7 @@ nm_str_realloc (char *str) (cond) ? (str) : (str_else), \ (cond) ? (suffix) : "" #define NM_PRINT_FMT_QUOTE_STRING(arg) NM_PRINT_FMT_QUOTED((arg), "\"", (arg), "\"", "(null)") +#define NM_PRINT_FMT_QUOTE_REF_STRING(arg) NM_PRINT_FMT_QUOTED((arg), "\"", (arg)->str, "\"", "(null)") /*****************************************************************************/ @@ -1009,19 +1034,37 @@ nm_str_realloc (char *str) #define nm_assert_not_reached() G_STMT_START { ; } G_STMT_END #endif +/* Usage: + * + * if (NM_MORE_ASSERT_ONCE (5)) { extra_check (); } + * + * This will only run the check once, and only if NM_MORE_ASSERT is >= than + * more_assert_level. + */ +#define NM_MORE_ASSERT_ONCE(more_assert_level) \ + ( (NM_MORE_ASSERTS >= (more_assert_level)) \ + && ({ \ + static volatile int _assert_once = 0; \ + \ + G_STATIC_ASSERT_EXPR ((more_assert_level) > 0); \ + \ + G_UNLIKELY ( _assert_once == 0 \ + && g_atomic_int_compare_and_exchange (&_assert_once, 0, 1)); \ + })) + /*****************************************************************************/ -#define NM_GOBJECT_PROPERTIES_DEFINE_BASE(...) \ +#define NM_GOBJECT_PROPERTIES_DEFINE_BASE_FULL(suffix, ...) \ typedef enum { \ - PROP_0, \ + PROP_0##suffix, \ __VA_ARGS__ \ - _PROPERTY_ENUMS_LAST, \ -} _PropertyEnums; \ -static GParamSpec *obj_properties[_PROPERTY_ENUMS_LAST] = { NULL, } + _PROPERTY_ENUMS_LAST##suffix, \ +} _PropertyEnums##suffix; \ +static GParamSpec *obj_properties##suffix[_PROPERTY_ENUMS_LAST##suffix] = { NULL, } -#define NM_GOBJECT_PROPERTIES_DEFINE_NOTIFY(obj_type, obj_properties, property_enums_type, prop_0) \ +#define NM_GOBJECT_PROPERTIES_DEFINE_NOTIFY(suffix, obj_type) \ static inline void \ -_nm_gobject_notify_together_impl (obj_type *obj, guint n, const property_enums_type *props) \ +_nm_gobject_notify_together_impl##suffix (obj_type *obj, guint n, const _PropertyEnums##suffix *props) \ { \ const gboolean freeze_thaw = (n > 1); \ \ @@ -1031,12 +1074,12 @@ _nm_gobject_notify_together_impl (obj_type *obj, guint n, const property_enums_t if (freeze_thaw) \ g_object_freeze_notify ((GObject *) obj); \ while (n-- > 0) { \ - const property_enums_type prop = *props++; \ + const _PropertyEnums##suffix prop = *props++; \ \ - if (prop != prop_0) { \ - nm_assert ((gsize) prop < G_N_ELEMENTS (obj_properties)); \ - nm_assert (obj_properties[prop]); \ - g_object_notify_by_pspec ((GObject *) obj, obj_properties[prop]); \ + if (prop != PROP_0##suffix) { \ + nm_assert ((gsize) prop < G_N_ELEMENTS (obj_properties##suffix)); \ + nm_assert (obj_properties##suffix[prop]); \ + g_object_notify_by_pspec ((GObject *) obj, obj_properties##suffix[prop]); \ } \ } \ if (freeze_thaw) \ @@ -1044,20 +1087,29 @@ _nm_gobject_notify_together_impl (obj_type *obj, guint n, const property_enums_t } \ \ _nm_unused static inline void \ -_notify (obj_type *obj, property_enums_type prop) \ +_notify##suffix (obj_type *obj, _PropertyEnums##suffix prop) \ { \ - _nm_gobject_notify_together_impl (obj, 1, &prop); \ + _nm_gobject_notify_together_impl##suffix (obj, 1, &prop); \ } \ +#define NM_GOBJECT_PROPERTIES_DEFINE_BASE(...) \ + NM_GOBJECT_PROPERTIES_DEFINE_BASE_FULL (, __VA_ARGS__); \ + +#define NM_GOBJECT_PROPERTIES_DEFINE_FULL(suffix, obj_type, ...) \ + NM_GOBJECT_PROPERTIES_DEFINE_BASE_FULL (suffix, __VA_ARGS__); \ + NM_GOBJECT_PROPERTIES_DEFINE_NOTIFY (suffix, obj_type) + #define NM_GOBJECT_PROPERTIES_DEFINE(obj_type, ...) \ -NM_GOBJECT_PROPERTIES_DEFINE_BASE (__VA_ARGS__); \ -NM_GOBJECT_PROPERTIES_DEFINE_NOTIFY (obj_type, obj_properties, _PropertyEnums, PROP_0) + NM_GOBJECT_PROPERTIES_DEFINE_FULL (, obj_type, __VA_ARGS__) /* invokes _notify() for all arguments (of type _PropertyEnums). Note, that if * there are more than one prop arguments, this will involve a freeze/thaw * of GObject property notifications. */ +#define nm_gobject_notify_together_full(suffix, obj, ...) \ + _nm_gobject_notify_together_impl##suffix (obj, NM_NARG (__VA_ARGS__), (const _PropertyEnums##suffix[]) { __VA_ARGS__ }) + #define nm_gobject_notify_together(obj, ...) \ - _nm_gobject_notify_together_impl (obj, NM_NARG (__VA_ARGS__), (const _PropertyEnums[]) { __VA_ARGS__ }) + nm_gobject_notify_together_full (, obj, __VA_ARGS__) /*****************************************************************************/ @@ -1065,9 +1117,9 @@ NM_GOBJECT_PROPERTIES_DEFINE_NOTIFY (obj_type, obj_properties, _PropertyEnums, P #if _NM_CC_SUPPORT_AUTO_TYPE #define _NM_GET_PRIVATE_PTR(self, type, is_check, ...) \ ({ \ - _nm_auto_type _self = NM_GOBJECT_CAST_NON_NULL (type, (self), is_check, ##__VA_ARGS__); \ + _nm_auto_type _self_get_private = NM_GOBJECT_CAST_NON_NULL (type, (self), is_check, ##__VA_ARGS__); \ \ - NM_PROPAGATE_CONST (_self, _self->_priv); \ + NM_PROPAGATE_CONST (_self_get_private, _self_get_private->_priv); \ }) #else #define _NM_GET_PRIVATE_PTR(self, type, is_check, ...) (NM_GOBJECT_CAST_NON_NULL (type, (self), is_check, ##__VA_ARGS__)->_priv) @@ -1348,8 +1400,8 @@ nm_g_variant_take_ref (GVariant *v) #define NM_UTILS_LOOKUP_ITEM_IGNORE(v) (void) 0; case v: break; (void) 0 #define NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER() (void) 0; default: break; (void) 0 -#define _NM_UTILS_LOOKUP_DEFINE(scope, fcn_name, lookup_type, result_type, unknown_val, ...) \ -scope result_type \ +#define NM_UTILS_LOOKUP_DEFINE(fcn_name, lookup_type, result_type, unknown_val, ...) \ +result_type \ fcn_name (lookup_type val) \ { \ switch (val) { \ @@ -1361,9 +1413,7 @@ fcn_name (lookup_type val) \ } #define NM_UTILS_LOOKUP_STR_DEFINE(fcn_name, lookup_type, unknown_val, ...) \ - _NM_UTILS_LOOKUP_DEFINE (, fcn_name, lookup_type, const char *, unknown_val, __VA_ARGS__) -#define NM_UTILS_LOOKUP_STR_DEFINE_STATIC(fcn_name, lookup_type, unknown_val, ...) \ - _NM_UTILS_LOOKUP_DEFINE (static, fcn_name, lookup_type, const char *, unknown_val, __VA_ARGS__) + NM_UTILS_LOOKUP_DEFINE (fcn_name, lookup_type, const char *, unknown_val, __VA_ARGS__) /* Call the string-lookup-table function @fcn_name. If the function returns * %NULL, the numeric index is converted to string using a alloca() buffer. @@ -1415,6 +1465,14 @@ fcn_name (lookup_type val) \ : _flags & (~_val); \ }) +#define NM_FLAGS_ASSIGN_MASK(flags, mask, val) ({ \ + const typeof(flags) _flags = (flags); \ + const typeof(flags) _mask = (mask); \ + const typeof(flags) _val = (val); \ + \ + ((_flags & ~_mask) | (_mask & _val)); \ + }) + /*****************************************************************************/ #define _NM_BACKPORT_SYMBOL_IMPL(version, return_type, orig_func, versioned_func, args_typed, args) \ @@ -1432,7 +1490,12 @@ _NM_BACKPORT_SYMBOL_IMPL(version, return_type, func, _##func##_##version, args_t /*****************************************************************************/ /* mirrors g_ascii_isspace() and what we consider spaces in general. */ -#define NM_ASCII_SPACES "\t\n\f\r " +#define NM_ASCII_SPACES " \n\t\r\f" + +/* Like NM_ASCII_SPACES, but without "\f" (0x0c, Formfeed Page Break). + * This is what for example systemd calls WHITESPACE and what it uses to tokenize + * the kernel command line. */ +#define NM_ASCII_WHITESPACES " \n\t\r" #define nm_str_skip_leading_spaces(str) \ ({ \ @@ -1508,6 +1571,17 @@ nm_strstrip_avoid_copy (const char *str, char **str_free) _str_ssac; \ }) +static inline gboolean +nm_str_is_stripped (const char *str) +{ + if (str && str[0]) { + if ( g_ascii_isspace (str[0]) + || g_ascii_isspace (str[strlen (str) - 1])) + return FALSE; + } + return TRUE; +} + /* g_ptr_array_sort()'s compare function takes pointers to the * value. Thus, you cannot use strcmp directly. You can use * nm_strcmp_p(). diff --git a/shared/nm-glib-aux/nm-ref-string.h b/shared/nm-glib-aux/nm-ref-string.h index c80b75ea..60052c45 100644 --- a/shared/nm-glib-aux/nm-ref-string.h +++ b/shared/nm-glib-aux/nm-ref-string.h @@ -50,6 +50,18 @@ nm_ref_string_get_len (NMRefString *rstr) } static inline gboolean +nm_ref_string_equals_str (NMRefString *rstr, const char *s) +{ + /* Note that rstr->len might be greater than strlen(rstr->str). This function does + * not cover that and would ignore everything after the first NUL byte. If you need + * that distinction, this function is not for you. */ + + return rstr + ? (s && nm_streq (rstr->str, s)) + : (s == NULL); +} + +static inline gboolean NM_IS_REF_STRING (const NMRefString *rstr) { #if NM_MORE_ASSERTS > 10 diff --git a/shared/nm-glib-aux/nm-secret-utils.c b/shared/nm-glib-aux/nm-secret-utils.c index 5b0afe46..78369b5e 100644 --- a/shared/nm-glib-aux/nm-secret-utils.c +++ b/shared/nm-glib-aux/nm-secret-utils.c @@ -8,13 +8,15 @@ #include "nm-secret-utils.h" +#include <malloc.h> + /*****************************************************************************/ void nm_explicit_bzero (void *s, gsize n) { /* gracefully handle n == 0. This is important, callers rely on it. */ - if (n == 0) + if (G_UNLIKELY (n == 0)) return; nm_assert (s); @@ -32,6 +34,30 @@ nm_explicit_bzero (void *s, gsize n) #endif } +void +nm_free_secret (char *secret) +{ + gsize len; + + if (!secret) + return; + +#if GLIB_CHECK_VERSION(2,44,0) + /* Here we mix malloc() and g_malloc() API. Usually we avoid this, + * however since glib 2.44.0 we are in fact guaranteed that g_malloc()/g_free() + * just wraps malloc()/free(), so this is actually fine. + * + * See https://gitlab.gnome.org/GNOME/glib/commit/3be6ed60aa58095691bd697344765e715a327fc1 + */ + len = malloc_usable_size (secret); +#else + len = strlen (secret); +#endif + + nm_explicit_bzero (secret, len); + g_free (secret); +} + /*****************************************************************************/ char * diff --git a/shared/nm-glib-aux/nm-secret-utils.h b/shared/nm-glib-aux/nm-secret-utils.h index 1b98b7e9..501e7520 100644 --- a/shared/nm-glib-aux/nm-secret-utils.h +++ b/shared/nm-glib-aux/nm-secret-utils.h @@ -18,14 +18,7 @@ char *nm_secret_strchomp (char *secret); /*****************************************************************************/ -static inline void -nm_free_secret (char *secret) -{ - if (secret) { - nm_explicit_bzero (secret, strlen (secret)); - g_free (secret); - } -} +void nm_free_secret (char *secret); NM_AUTO_DEFINE_FCN0 (char *, _nm_auto_free_secret, nm_free_secret) /** @@ -159,4 +152,125 @@ GBytes *nm_secret_buf_to_gbytes_take (NMSecretBuf *secret, gssize actual_len); gboolean nm_utils_memeqzero_secret (gconstpointer data, gsize length); +/*****************************************************************************/ + +/** + * nm_secret_mem_realloc: + * @m_old: the current buffer of length @cur_len. + * @do_bzero_mem: if %TRUE, bzero the old buffer + * @cur_len: the current buffer length of @m_old. It is necessary for bzero. + * @new_len: the desired new length + * + * If @do_bzero_mem is false, this is like g_realloc(). + * Otherwise, this will allocate a new buffer of the desired size, copy over the + * old data, and bzero the old buffer before freeing it. As such, it also behaves + * similar to g_realloc(), with the overhead of nm_explicit_bzero() and using + * malloc/free intead of realloc(). + * + * Returns: the new allocated buffer. Think of it behaving like g_realloc(). + */ +static inline gpointer +nm_secret_mem_realloc (gpointer m_old, gboolean do_bzero_mem, gsize cur_len, gsize new_len) +{ + gpointer m_new; + + nm_assert (m_old || cur_len == 0); + + if ( do_bzero_mem + && G_LIKELY (cur_len > 0)) { + m_new = g_malloc (new_len); + if (G_LIKELY (new_len > 0)) + memcpy (m_new, m_old, NM_MIN (cur_len, new_len)); + nm_explicit_bzero (m_old, cur_len); + g_free (m_old); + } else + m_new = g_realloc (m_old, new_len); + + return m_new; +} + +/** + * nm_secret_mem_try_realloc: + * @m_old: the current buffer of length @cur_len. + * @do_bzero_mem: if %TRUE, bzero the old buffer + * @cur_len: the current buffer length of @m_old. It is necessary for bzero. + * @new_len: the desired new length + * + * If @do_bzero_mem is false, this is like g_try_realloc(). + * Otherwise, this will try to allocate a new buffer of the desired size, copy over the + * old data, and bzero the old buffer before freeing it. As such, it also behaves + * similar to g_try_realloc(), with the overhead of nm_explicit_bzero() and using + * malloc/free intead of realloc(). + * + * Returns: the new allocated buffer or NULL. Think of it behaving like g_try_realloc(). + */ +static inline gpointer +nm_secret_mem_try_realloc (gpointer m_old, gboolean do_bzero_mem, gsize cur_len, gsize new_len) +{ + gpointer m_new; + + nm_assert (m_old || cur_len == 0); + + if ( do_bzero_mem + && G_LIKELY (cur_len > 0)) { + if (G_UNLIKELY (new_len == 0)) + m_new = NULL; + else { + m_new = g_try_malloc (new_len); + if (!m_new) + return NULL; + memcpy (m_new, m_old, NM_MIN (cur_len, new_len)); + } + nm_explicit_bzero (m_old, cur_len); + g_free (m_old); + return m_new; + } + + return g_try_realloc (m_old, new_len); +} + +/** + * nm_secret_mem_try_realloc_take: + * @m_old: the current buffer of length @cur_len. + * @do_bzero_mem: if %TRUE, bzero the old buffer + * @cur_len: the current buffer length of @m_old. It is necessary for bzero. + * @new_len: the desired new length + * + * This works like nm_secret_mem_try_realloc(), which is not unlike g_try_realloc(). + * The difference is, if we fail to allocate a new buffer, then @m_old will be + * freed (and possibly cleared). This differs from plain realloc(), where the + * old buffer is unchanged if the operation fails. + * + * Returns: the new allocated buffer or NULL. Think of it behaving like g_try_realloc() + * but it will always free @m_old. + */ +static inline gpointer +nm_secret_mem_try_realloc_take (gpointer m_old, gboolean do_bzero_mem, gsize cur_len, gsize new_len) +{ + gpointer m_new; + + nm_assert (m_old || cur_len == 0); + + if ( do_bzero_mem + && G_LIKELY (cur_len > 0)) { + if (G_UNLIKELY (new_len == 0)) + m_new = NULL; + else { + m_new = g_try_malloc (new_len); + if (G_LIKELY (m_new)) + memcpy (m_new, m_old, NM_MIN (cur_len, new_len)); + } + nm_explicit_bzero (m_old, cur_len); + g_free (m_old); + return m_new; + } + + m_new = g_try_realloc (m_old, new_len); + if (G_UNLIKELY (!m_new && new_len > 0)) + g_free (m_old); + return m_new; +} + +/*****************************************************************************/ + #endif /* __NM_SECRET_UTILS_H__ */ diff --git a/shared/nm-glib-aux/nm-shared-utils.c b/shared/nm-glib-aux/nm-shared-utils.c index d47c465c..c06399dd 100644 --- a/shared/nm-glib-aux/nm-shared-utils.c +++ b/shared/nm-glib-aux/nm-shared-utils.c @@ -15,6 +15,7 @@ #include <net/if.h> #include "nm-errno.h" +#include "nm-str-buf.h" /*****************************************************************************/ @@ -84,6 +85,75 @@ nm_ip_addr_set_from_untrusted (int addr_family, /*****************************************************************************/ +gsize +nm_utils_get_next_realloc_size (gboolean true_realloc, gsize requested) +{ + gsize n, x; + + /* https://doc.qt.io/qt-5/containers.html#growth-strategies */ + + if (requested <= 40) { + /* small allocations. Increase in small steps of 8 bytes. + * + * We get thus sizes of 8, 16, 32, 40. */ + if (requested <= 8) + return 8; + if (requested <= 16) + return 16; + if (requested <= 32) + return 32; + + /* The return values for < 104 are essentially hard-coded, and the choice here is + * made without very strong reasons. + * + * We want to stay 24 bytes below the power-of-two border 64. Hence, return 40 here. + * However, the next step then is already 104 (128 - 24). It's a larger gap than in + * the steps before. + * + * It's not clear whether some of the steps should be adjusted (or how exactly). */ + return 40; + } + + if ( requested <= 0x2000u - 24u + || G_UNLIKELY (!true_realloc)) { + /* mid sized allocations. Return next power of two, minus 24 bytes extra space + * at the beginning. + * That means, we double the size as we grow. + * + * With !true_realloc, it means that the caller does not intend to call + * realloc() but instead clone the buffer. This is for example the case, when we + * want to nm_explicit_bzero() the old buffer. In that case we really want to grow + * the buffer exponentially every time and not increment in page sizes of 4K (below). + * + * We get thus sizes of 104, 232, 488, 1000, 2024, 4072, 8168... */ + + if (G_UNLIKELY (requested > G_MAXSIZE / 2u - 24u)) + return G_MAXSIZE; + + x = requested + 24u; + n = 128u; + while (n < x) { + n <<= 1; + nm_assert (n > 128u); + } + + nm_assert (n > 24u && n - 24u >= requested); + return n - 24u; + } + + if (G_UNLIKELY (requested > G_MAXSIZE - 0x1000u - 24u)) + return G_MAXSIZE; + + /* For large allocations (with !true_realloc) we allocate memory in chunks of + * 4K (- 24 bytes extra), assuming that the memory gets mmapped and thus + * realloc() is efficient by just reordering pages. */ + n = ((requested + (0x0FFFu + 24u)) & ~((gsize) 0x0FFFu)) - 24u; + nm_assert (n >= requested); + return n; +} + +/*****************************************************************************/ + pid_t nm_utils_gettid (void) { @@ -374,6 +444,62 @@ nm_utils_gbytes_to_variant_ay (GBytes *bytes) /*****************************************************************************/ +/* Convert a hash table with "char *" keys and values to an "a{ss}" GVariant. + * The keys will be sorted asciibetically. + * Returns a floating reference. + */ +GVariant * +nm_utils_strdict_to_variant_ass (GHashTable *strdict) +{ + GHashTableIter iter; + const char *key, *value; + GVariantBuilder builder; + guint i, len; + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}")); + + if (!strdict) + goto out; + len = g_hash_table_size (strdict); + if (!len) + goto out; + + g_hash_table_iter_init (&iter, strdict); + if (!g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value)) + nm_assert_not_reached (); + + if (len == 1) + g_variant_builder_add (&builder, "{ss}", key, value); + else { + gs_free NMUtilsNamedValue *idx_free = NULL; + NMUtilsNamedValue *idx; + + if (len > 300 / sizeof (NMUtilsNamedValue)) { + idx_free = g_new (NMUtilsNamedValue, len); + idx = idx_free; + } else + idx = g_alloca (sizeof (NMUtilsNamedValue) * len); + + i = 0; + do { + idx[i].name = key; + idx[i].value_str = value; + i++; + } while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value)); + nm_assert (i == len); + + nm_utils_named_value_list_sort (idx, len, NULL, NULL); + + for (i = 0; i < len; i++) + g_variant_builder_add (&builder, "{ss}", idx[i].name, idx[i].value_str); + } + +out: + return g_variant_builder_end (&builder); +} + +/*****************************************************************************/ + /** * nm_strquote: * @buf: the output buffer of where to write the quoted @str argument. @@ -685,7 +811,7 @@ nm_utils_ip_is_site_local (int addr_family, /*****************************************************************************/ static gboolean -_parse_legacy_addr4 (const char *text, in_addr_t *out_addr) +_parse_legacy_addr4 (const char *text, in_addr_t *out_addr, GError **error) { gs_free char *s_free = NULL; struct in_addr a1; @@ -693,8 +819,13 @@ _parse_legacy_addr4 (const char *text, in_addr_t *out_addr) char *s; int i; - if (inet_aton (text, &a1) != 1) + if (inet_aton (text, &a1) != 1) { + g_set_error_literal (error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_INVALID_ARGUMENT, + "address invalid according to inet_aton()"); return FALSE; + } /* OK, inet_aton() accepted the format. That's good, because we want * to accept IPv4 addresses in octal format, like 255.255.000.000. @@ -711,6 +842,10 @@ _parse_legacy_addr4 (const char *text, in_addr_t *out_addr) if (NM_STRCHAR_ANY (text, ch, ( !(ch >= '0' && ch <= '9') && !NM_IN_SET (ch, '.', 'x')))) { /* We only accepts '.', digits, and 'x' for "0x". */ + g_set_error_literal (error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_INVALID_ARGUMENT, + "contains an invalid character"); return FALSE; } @@ -729,13 +864,27 @@ _parse_legacy_addr4 (const char *text, in_addr_t *out_addr) if ((i == G_N_ELEMENTS (bin) - 1) != (s == NULL)) { /* Exactly for the last digit, we expect to have no more following token. * But this isn't the case. Abort. */ + g_set_error (error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_INVALID_ARGUMENT, + "wrong number of tokens (index %d, token '%s')", + i, s); return FALSE; } v = _nm_utils_ascii_str_to_int64 (current_token, 0, 0, 0xFF, -1); if (v == -1) { + int errsv = errno; + /* we do accept octal and hex (even with leading "0x"). But something * about this token is wrong. */ + g_set_error (error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_INVALID_ARGUMENT, + "invalid token '%s': %s (%d)", + current_token, + nm_strerror_native (errsv), + errsv); return FALSE; } @@ -745,6 +894,12 @@ _parse_legacy_addr4 (const char *text, in_addr_t *out_addr) if (memcmp (bin, &a1, sizeof (bin)) != 0) { /* our parsing did not agree with what inet_aton() gave. Something * is wrong. Abort. */ + g_set_error (error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_INVALID_ARGUMENT, + "inet_aton() result 0x%08x differs from computed value 0x%02hhx%02hhx%02hhx%02hhx", + a1.s_addr, + bin[0], bin[1], bin[2], bin[3]); return FALSE; } @@ -772,7 +927,7 @@ nm_utils_parse_inaddr_bin_full (int addr_family, if (inet_pton (addr_family, text, &addrbin) != 1) { if ( accept_legacy && addr_family == AF_INET - && _parse_legacy_addr4 (text, &addrbin.addr4)) { + && _parse_legacy_addr4 (text, &addrbin.addr4, NULL)) { /* The address is in some legacy format which inet_aton() accepts, but not inet_pton(). * Most likely octal digits (leading zeros). We accept the address. */ } else @@ -781,11 +936,17 @@ nm_utils_parse_inaddr_bin_full (int addr_family, #if NM_MORE_ASSERTS > 10 if (addr_family == AF_INET) { + gs_free_error GError *error = NULL; in_addr_t a; /* The legacy parser should accept everything that inet_pton() accepts too. Meaning, * it should strictly parse *more* formats. And of course, parse it the same way. */ - nm_assert (_parse_legacy_addr4 (text, &a)); + if (!_parse_legacy_addr4 (text, &a, &error)) { + char buf[INET_ADDRSTRLEN]; + + g_error ("unexpected assertion failure: could parse \"%s\" as %s, but not accepted by legacy parser: %s", + text, _nm_utils_inet4_ntop (addrbin.addr4, buf), error->message); + } nm_assert (addrbin.addr4 == a); } #endif @@ -883,6 +1044,195 @@ nm_utils_parse_inaddr_prefix (int addr_family, /*****************************************************************************/ +gboolean +nm_utils_ipaddr_is_valid (int addr_family, + const char *str_addr) +{ + nm_assert (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET, AF_INET6)); + + return str_addr + && nm_utils_parse_inaddr_bin (addr_family, + str_addr, + NULL, + NULL); +} + +gboolean +nm_utils_ipaddr_is_normalized (int addr_family, + const char *str_addr) +{ + NMIPAddr addr; + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + nm_assert (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET, AF_INET6)); + + if (!str_addr) + return FALSE; + + if (!nm_utils_parse_inaddr_bin (addr_family, + str_addr, + &addr_family, + &addr)) + return FALSE; + + nm_utils_inet_ntop (addr_family, &addr, sbuf); + return nm_streq (sbuf, str_addr); +} + +/*****************************************************************************/ + +/** + * nm_g_ascii_strtoll() + * @nptr: the string to parse + * @endptr: the pointer on the first invalid chars + * @base: the base. + * + * This wraps g_ascii_strtoll() and should in almost all cases behave identical + * to it. + * + * However, it seems there are situations where g_ascii_strtoll() might set + * errno to some unexpected value EAGAIN. Possibly this is related to creating + * the C locale during + * + * #ifdef USE_XLOCALE + * return strtoll_l (nptr, endptr, base, get_C_locale ()); + * + * This wrapper tries to workaround that condition. + */ +gint64 +nm_g_ascii_strtoll (const char *nptr, + char **endptr, + guint base) +{ + int try_count = 2; + gint64 v; + const int errsv_orig = errno; + int errsv; + + nm_assert (nptr); + nm_assert (base == 0u || (base >= 2u && base <= 36u)); + +again: + errno = 0; + v = g_ascii_strtoll (nptr, endptr, base); + errsv = errno; + + if (errsv == 0) { + if (errsv_orig != 0) + errno = errsv_orig; + return v; + } + + if ( errsv == ERANGE + && NM_IN_SET (v, G_MININT64, G_MAXINT64)) + return v; + + if ( errsv == EINVAL + && v == 0 + && nptr + && nptr[0] == '\0') + return v; + + if (try_count-- > 0) + goto again; + +#if NM_MORE_ASSERTS + g_critical ("g_ascii_strtoll() for \"%s\" failed with errno=%d (%s) and v=%"G_GINT64_FORMAT, + nptr, + errsv, + nm_strerror_native (errsv), + v); +#endif + + return v; +} + +/* See nm_g_ascii_strtoll() */ +guint64 +nm_g_ascii_strtoull (const char *nptr, + char **endptr, + guint base) +{ + int try_count = 2; + guint64 v; + const int errsv_orig = errno; + int errsv; + + nm_assert (nptr); + nm_assert (base == 0u || (base >= 2u && base <= 36u)); + +again: + errno = 0; + v = g_ascii_strtoull (nptr, endptr, base); + errsv = errno; + + if (errsv == 0) { + if (errsv_orig != 0) + errno = errsv_orig; + return v; + } + + if ( errsv == ERANGE + && NM_IN_SET (v, G_MAXUINT64)) + return v; + + if ( errsv == EINVAL + && v == 0 + && nptr + && nptr[0] == '\0') + return v; + + if (try_count-- > 0) + goto again; + +#if NM_MORE_ASSERTS + g_critical ("g_ascii_strtoull() for \"%s\" failed with errno=%d (%s) and v=%"G_GUINT64_FORMAT, + nptr, + errsv, + nm_strerror_native (errsv), + v); +#endif + + return v; +} + +/* see nm_g_ascii_strtoll(). */ +double +nm_g_ascii_strtod (const char *nptr, + char **endptr) +{ + int try_count = 2; + double v; + int errsv; + + nm_assert (nptr); + +again: + v = g_ascii_strtod (nptr, endptr); + errsv = errno; + + if (errsv == 0) + return v; + + if (errsv == ERANGE) + return v; + + if (try_count-- > 0) + goto again; + +#if NM_MORE_ASSERTS + g_critical ("g_ascii_strtod() for \"%s\" failed with errno=%d (%s) and v=%f", + nptr, + errsv, + nm_strerror_native (errsv), + v); +#endif + + /* Not really much else to do. Return the parsed value and leave errno set + * to the unexpected value. */ + return v; +} + /* _nm_utils_ascii_str_to_int64: * * A wrapper for g_ascii_strtoll, that checks whether the whole string @@ -910,7 +1260,7 @@ _nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 ma } errno = 0; - v = g_ascii_strtoll (str, (char **) &s, base); + v = nm_g_ascii_strtoll (str, (char **) &s, base); if (errno != 0) return fallback; @@ -946,7 +1296,7 @@ _nm_utils_ascii_str_to_uint64 (const char *str, guint base, guint64 min, guint64 } errno = 0; - v = g_ascii_strtoull (str, (char **) &s, base); + v = nm_g_ascii_strtoull (str, (char **) &s, base); if (errno != 0) return fallback; @@ -965,8 +1315,8 @@ _nm_utils_ascii_str_to_uint64 (const char *str, guint base, guint64 min, guint64 if ( v != 0 && str[0] == '-') { - /* I don't know why, but g_ascii_strtoull() accepts minus signs ("-2" gives 18446744073709551614). - * For "-0" that is OK, but otherwise not. */ + /* As documented, g_ascii_strtoull() accepts negative values, and returns their + * absolute value. We don't. */ errno = ERANGE; return fallback; } @@ -1147,12 +1497,27 @@ comp_l: /*****************************************************************************/ static void +_char_lookup_table_set_one (guint8 lookup[static 256], + char ch) +{ + lookup[(guint8) ch] = 1; +} + +static void +_char_lookup_table_set_all (guint8 lookup[static 256], + const char *candidates) +{ + while (candidates[0] != '\0') + _char_lookup_table_set_one (lookup, (candidates++)[0]); +} + +static void _char_lookup_table_init (guint8 lookup[static 256], const char *candidates) { memset (lookup, 0, 256); - while (candidates[0] != '\0') - lookup[(guint8) ((candidates++)[0])] = 1; + if (candidates) + _char_lookup_table_set_all (lookup, candidates); } static gboolean @@ -1163,6 +1528,19 @@ _char_lookup_has (const guint8 lookup[static 256], return lookup[(guint8) ch] != 0; } +static gboolean +_char_lookup_has_all (const guint8 lookup[static 256], + const char *candidates) +{ + if (candidates) { + while (candidates[0] != '\0') { + if (!_char_lookup_has (lookup, (candidates++)[0])) + return FALSE; + } + } + return TRUE; +} + /** * nm_utils_strsplit_set_full: * @str: the string to split. @@ -1175,18 +1553,7 @@ _char_lookup_has (const guint8 lookup[static 256], * * Note that for @str %NULL and "", this always returns %NULL too. That differs * from g_strsplit_set(), which would return an empty strv array for "". - * - * Note that g_strsplit_set() returns empty words as well. By default, - * nm_utils_strsplit_set_full() strips all empty tokens (that is, repeated - * delimiters. With %NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, empty tokens - * are not removed. - * - * If @flags has %NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING, delimiters prefixed - * by a backslash are not treated as a separator. Such delimiters and their escape - * character are copied to the current word without unescaping them. In general, - * nm_utils_strsplit_set_full() does not remove any backslash escape characters - * and does not unescaping. It only considers them for skipping to split at - * an escaped delimiter. + * This never returns an empty array. * * Returns: %NULL if @str is %NULL or "". * If @str only contains delimiters and %NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY @@ -1196,7 +1563,7 @@ _char_lookup_has (const guint8 lookup[static 256], * The strings to which the result strv array points to are allocated * after the returned result itself. Don't free the strings themself, * but free everything with g_free(). - * It is however safe and allowed to modify the indiviual strings, + * It is however safe and allowed to modify the individual strings in-place, * like "g_strstrip((char *) iter[0])". */ const char ** @@ -1378,11 +1745,9 @@ done2: /* We no longer need ch_lookup for its original purpose. Modify it, so it * can detect the delimiters, '\\', and (optionally) whitespaces. */ - ch_lookup[((guint8) '\\')] = 1; - if (f_strstrip) { - for (i = 0; NM_ASCII_SPACES[i]; i++) - ch_lookup[((guint8) (NM_ASCII_SPACES[i]))] = 1; - } + _char_lookup_table_set_one (ch_lookup, '\\'); + if (f_strstrip) + _char_lookup_table_set_all (ch_lookup, NM_ASCII_SPACES); for (i_token = 0; ptr[i_token]; i_token++) { s = (char *) ptr[i_token]; @@ -1403,65 +1768,131 @@ done2: /*****************************************************************************/ const char * -nm_utils_escaped_tokens_escape (const char *str, - const char *delimiters, - char **out_to_free) +nm_utils_escaped_tokens_escape_full (const char *str, + const char *delimiters, + const char *delimiters_as_needed, + NMUtilsEscapedTokensEscapeFlags flags, + char **out_to_free) { guint8 ch_lookup[256]; + guint8 ch_lookup_as_needed[256]; + gboolean has_ch_lookup_as_needed = FALSE; char *ret; gsize str_len; gsize alloc_len; gsize n_escapes; gsize i, j; + gboolean escape_leading_space; gboolean escape_trailing_space; + gboolean escape_backslash_as_needed; - if (!delimiters) { - nm_assert (delimiters); - delimiters = NM_ASCII_SPACES; - } + nm_assert ( !delimiters_as_needed + || ( delimiters_as_needed[0] + && NM_FLAGS_HAS (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED))); if (!str || str[0] == '\0') { *out_to_free = NULL; return str; } + str_len = strlen (str); + _char_lookup_table_init (ch_lookup, delimiters); + if ( !delimiters + || NM_FLAGS_HAS (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_SPACES)) { + flags &= ~( NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE + | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE); + _char_lookup_table_set_all (ch_lookup, NM_ASCII_SPACES); + } - /* also mark '\\' as requiring escaping. */ - ch_lookup[((guint8) '\\')] = 1; + if (NM_FLAGS_HAS (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_ALWAYS)) { + _char_lookup_table_set_one (ch_lookup, '\\'); + escape_backslash_as_needed = FALSE; + } else if (_char_lookup_has (ch_lookup, '\\')) + escape_backslash_as_needed = FALSE; + else { + escape_backslash_as_needed = NM_FLAGS_HAS (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED); + if (escape_backslash_as_needed) { + if ( NM_FLAGS_ANY (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE + | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE) + && !_char_lookup_has_all (ch_lookup, NM_ASCII_SPACES)) { + /* ESCAPE_LEADING_SPACE and ESCAPE_TRAILING_SPACE implies that we escape backslash + * before whitespaces. */ + if (!has_ch_lookup_as_needed) { + has_ch_lookup_as_needed = TRUE; + _char_lookup_table_init (ch_lookup_as_needed, NULL); + } + _char_lookup_table_set_all (ch_lookup_as_needed, NM_ASCII_SPACES); + } + if ( delimiters_as_needed + && !_char_lookup_has_all (ch_lookup, delimiters_as_needed)) { + if (!has_ch_lookup_as_needed) { + has_ch_lookup_as_needed = TRUE; + _char_lookup_table_init (ch_lookup_as_needed, NULL); + } + _char_lookup_table_set_all (ch_lookup_as_needed, delimiters_as_needed); + } + } + } + + escape_leading_space = NM_FLAGS_HAS (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE) + && g_ascii_isspace (str[0]) + && !_char_lookup_has (ch_lookup, str[0]); + if (str_len == 1) + escape_trailing_space = FALSE; + else { + escape_trailing_space = NM_FLAGS_HAS (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE) + && g_ascii_isspace (str[str_len - 1]) + && !_char_lookup_has (ch_lookup, str[str_len - 1]); + } n_escapes = 0; for (i = 0; str[i] != '\0'; i++) { if (_char_lookup_has (ch_lookup, str[i])) n_escapes++; + else if ( str[i] == '\\' + && escape_backslash_as_needed + && ( _char_lookup_has (ch_lookup, str[i + 1]) + || NM_IN_SET (str[i + 1], '\0', '\\') + || ( has_ch_lookup_as_needed + && _char_lookup_has (ch_lookup_as_needed, str[i + 1])))) + n_escapes++; } + if (escape_leading_space) + n_escapes++; + if (escape_trailing_space) + n_escapes++; - str_len = i; - nm_assert (str_len > 0 && strlen (str) == str_len); - - escape_trailing_space = !_char_lookup_has (ch_lookup, str[str_len - 1]) - && g_ascii_isspace (str[str_len - 1]); - - if ( n_escapes == 0 - && !escape_trailing_space) { + if (n_escapes == 0u) { *out_to_free = NULL; return str; } - alloc_len = str_len + n_escapes + ((gsize) escape_trailing_space) + 1; + alloc_len = str_len + n_escapes + 1u; ret = g_new (char, alloc_len); j = 0; - for (i = 0; str[i] != '\0'; i++) { - if (_char_lookup_has (ch_lookup, str[i])) { - nm_assert (j < alloc_len); + i = 0; + + if (escape_leading_space) { + ret[j++] = '\\'; + ret[j++] = str[i++]; + } + for (; str[i] != '\0'; i++) { + if (_char_lookup_has (ch_lookup, str[i])) + ret[j++] = '\\'; + else if ( str[i] == '\\' + && escape_backslash_as_needed + && ( _char_lookup_has (ch_lookup, str[i + 1]) + || NM_IN_SET (str[i + 1], '\0', '\\') + || ( has_ch_lookup_as_needed + && _char_lookup_has (ch_lookup_as_needed, str[i + 1])))) ret[j++] = '\\'; - } - nm_assert (j < alloc_len); ret[j++] = str[i]; } if (escape_trailing_space) { - nm_assert (!_char_lookup_has (ch_lookup, ret[j - 1]) && g_ascii_isspace (ret[j - 1])); + nm_assert ( !_char_lookup_has (ch_lookup, ret[j - 1]) + && g_ascii_isspace (ret[j - 1])); ret[j] = ret[j - 1]; ret[j - 1] = '\\'; j++; @@ -1469,11 +1900,97 @@ nm_utils_escaped_tokens_escape (const char *str, nm_assert (j == alloc_len - 1); ret[j] = '\0'; + nm_assert (strlen (ret) == j); *out_to_free = ret; return ret; } +/** + * nm_utils_escaped_tokens_options_split: + * @str: the src string. This string will be modified in-place. + * The output values will point into @str. + * @out_key: (allow-none): the returned output key. This will always be set to @str + * itself. @str will be modified to contain only the unescaped, truncated + * key name. + * @out_val: returns the parsed (and unescaped) value or %NULL, if @str contains + * no '=' delimiter. + * + * Honors backslash escaping to parse @str as "key=value" pairs. Optionally, if no '=' + * is present, @out_val will be returned as %NULL. Backslash can be used to escape + * '=', ',', '\\', and ascii whitespace. Other backslash sequences are taken verbatim. + * + * For keys, '=' obviously must be escaped. For values, that is optional because an + * unescaped '=' is just taken verbatim. For example, in a key, the sequence "\\=" + * must be escaped as "\\\\\\=". For the value, that works too, but "\\\\=" is also + * accepted. + * + * Unescaped Space around the key and value are also removed. Space in general must + * not be escaped, unless they are at the beginning or the end of key/value. + */ +void +nm_utils_escaped_tokens_options_split (char *str, + const char **out_key, + const char **out_val) +{ + const char *val = NULL; + gsize i; + gsize j; + gsize last_space_idx; + gboolean last_space_has; + + nm_assert (str); + + i = 0; + while (g_ascii_isspace (str[i])) + i++; + + j = 0; + last_space_idx = 0; + last_space_has = FALSE; + while (str[i] != '\0') { + if (g_ascii_isspace (str[i])) { + if (!last_space_has) { + last_space_has = TRUE; + last_space_idx = j; + } + } else { + if (str[i] == '\\') { + if ( NM_IN_SET (str[i + 1u], '\\', ',', '=') + || g_ascii_isspace (str[i + 1u])) + i++; + } else if (str[i] == '=') { + /* Encounter an unescaped '=' character. When we still parse the key, this + * is the separator we were waiting for. If we are parsing the value, + * we take the character verbatim. */ + if (!val) { + if (last_space_has) { + str[last_space_idx] = '\0'; + j = last_space_idx + 1; + last_space_has = FALSE; + } else + str[j++] = '\0'; + val = &str[j]; + i++; + while (g_ascii_isspace (str[i])) + i++; + continue; + } + } + last_space_has = FALSE; + } + str[j++] = str[i++]; + } + + if (last_space_has) + str[last_space_idx] = '\0'; + else + str[j] = '\0'; + + *out_key = str; + *out_val = val; +} + /*****************************************************************************/ /** @@ -1603,16 +2120,13 @@ nm_utils_error_set_cancelled (GError **error, } gboolean -nm_utils_error_is_cancelled (GError *error, - gboolean consider_is_disposing) +nm_utils_error_is_cancelled_or_disposing (GError *error) { if (error) { if (error->domain == G_IO_ERROR) return NM_IN_SET (error->code, G_IO_ERROR_CANCELLED); - if (consider_is_disposing) { - if (error->domain == NM_UTILS_ERROR) - return NM_IN_SET (error->code, NM_UTILS_ERROR_CANCELLED_DISPOSING); - } + if (error->domain == NM_UTILS_ERROR) + return NM_IN_SET (error->code, NM_UTILS_ERROR_CANCELLED_DISPOSING); } return FALSE; } @@ -1913,18 +2427,20 @@ nm_g_type_find_implementing_class_for_property (GType gtype, /*****************************************************************************/ static void -_str_append_escape (GString *s, char ch) +_str_buf_append_c_escape_octal (NMStrBuf *strbuf, + char ch) { - g_string_append_c (s, '\\'); - g_string_append_c (s, '0' + ((((guchar) ch) >> 6) & 07)); - g_string_append_c (s, '0' + ((((guchar) ch) >> 3) & 07)); - g_string_append_c (s, '0' + ( ((guchar) ch) & 07)); + nm_str_buf_append_c4 (strbuf, + '\\', + '0' + ((char) ((((guchar) ch) >> 6) & 07)), + '0' + ((char) ((((guchar) ch) >> 3) & 07)), + '0' + ((char) ((((guchar) ch) ) & 07))); } gconstpointer nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_free) { - GString *gstr; + NMStrBuf strbuf; gsize len; const char *s; @@ -1946,9 +2462,9 @@ nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_fr return str; } - gstr = g_string_new_len (NULL, len); + nm_str_buf_init (&strbuf, len, FALSE); - g_string_append_len (gstr, str, s - str); + nm_str_buf_append_len (&strbuf, str, s - str); str = s; for (;;) { @@ -1971,6 +2487,9 @@ nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_fr v = v * 8 + (ch - '0'); ch = (++str)[0]; if (ch >= '0' && ch <= '7') { + /* technically, escape sequences larger than \3FF are out of range + * and invalid. We don't check for that, and do the same as + * g_strcompress(): silently clip the value with & 0xFF. */ v = v * 8 + (ch - '0'); ++str; } @@ -1992,21 +2511,20 @@ nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_fr str++; } - g_string_append_c (gstr, ch); + nm_str_buf_append_c (&strbuf, ch); s = strchr (str, '\\'); if (!s) { - g_string_append (gstr, str); + nm_str_buf_append (&strbuf, str); break; } - g_string_append_len (gstr, str, s - str); + nm_str_buf_append_len (&strbuf, str, s - str); str = s; } - *out_len = gstr->len; - *to_free = gstr->str; - return g_string_free (gstr, FALSE); + return (*to_free = nm_str_buf_finalize (&strbuf, + out_len)); } /** @@ -2047,7 +2565,7 @@ nm_utils_buf_utf8safe_escape (gconstpointer buf, gssize buflen, NMUtilsStrUtf8Sa const char *p = NULL; const char *s; gboolean nul_terminated = FALSE; - GString *gstr; + NMStrBuf strbuf; g_return_val_if_fail (to_free, NULL); @@ -2078,7 +2596,9 @@ nm_utils_buf_utf8safe_escape (gconstpointer buf, gssize buflen, NMUtilsStrUtf8Sa return str; } - gstr = g_string_sized_new (buflen + 5); + nm_str_buf_init (&strbuf, + buflen + 5, + NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET)); s = str; do { @@ -2088,21 +2608,22 @@ nm_utils_buf_utf8safe_escape (gconstpointer buf, gssize buflen, NMUtilsStrUtf8Sa for (; s < p; s++) { char ch = s[0]; + nm_assert (ch); if (ch == '\\') - g_string_append (gstr, "\\\\"); + nm_str_buf_append_c2 (&strbuf, '\\', '\\'); else if ( ( NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL) \ && ch < ' ') \ || ( NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII) \ && ((guchar) ch) >= 127)) - _str_append_escape (gstr, ch); + _str_buf_append_c_escape_octal (&strbuf, ch); else - g_string_append_c (gstr, ch); + nm_str_buf_append_c (&strbuf, ch); } if (buflen <= 0) break; - _str_append_escape (gstr, p[0]); + _str_buf_append_c_escape_octal (&strbuf, p[0]); buflen--; if (buflen == 0) @@ -2112,8 +2633,7 @@ nm_utils_buf_utf8safe_escape (gconstpointer buf, gssize buflen, NMUtilsStrUtf8Sa (void) g_utf8_validate (s, buflen, &p); } while (TRUE); - *to_free = g_string_free (gstr, FALSE); - return *to_free; + return (*to_free = nm_str_buf_finalize (&strbuf, NULL)); } const char * @@ -2137,13 +2657,11 @@ nm_utils_buf_utf8safe_escape_bytes (GBytes *bytes, NMUtilsStrUtf8SafeFlags flags const char * nm_utils_str_utf8safe_unescape (const char *str, char **to_free) { + gsize len; + g_return_val_if_fail (to_free, NULL); - if (!str || !strchr (str, '\\')) { - *to_free = NULL; - return str; - } - return (*to_free = g_strcompress (str)); + return nm_utils_buf_utf8safe_unescape (str, &len, (gpointer *) to_free); } /** @@ -2203,7 +2721,10 @@ nm_utils_str_utf8safe_escape_cp (const char *str, NMUtilsStrUtf8SafeFlags flags) char * nm_utils_str_utf8safe_unescape_cp (const char *str) { - return str ? g_strcompress (str) : NULL; + char *s; + + str = nm_utils_str_utf8safe_unescape (str, &s); + return s ?: g_strdup (str); } char * @@ -2224,7 +2745,7 @@ nm_utils_str_utf8safe_escape_take (char *str, NMUtilsStrUtf8SafeFlags flags) /* taken from systemd's fd_wait_for_event(). Note that the timeout * is here in nano-seconds, not micro-seconds. */ int -nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_ns) +nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_nsec) { struct pollfd pollfd = { .fd = fd, @@ -2233,11 +2754,11 @@ nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_ns) struct timespec ts, *pts; int r; - if (timeout_ns < 0) + if (timeout_nsec < 0) pts = NULL; else { - ts.tv_sec = (time_t) (timeout_ns / NM_UTILS_NS_PER_SECOND); - ts.tv_nsec = (long int) (timeout_ns % NM_UTILS_NS_PER_SECOND); + ts.tv_sec = (time_t) (timeout_nsec / NM_UTILS_NSEC_PER_SEC); + ts.tv_nsec = (long int) (timeout_nsec % NM_UTILS_NSEC_PER_SEC); pts = &ts; } @@ -2319,7 +2840,10 @@ nm_utils_fd_read_loop_exact (int fd, void *buf, size_t nbytes, bool do_poll) /*****************************************************************************/ NMUtilsNamedValue * -nm_utils_named_values_from_str_dict (GHashTable *hash, guint *out_len) +nm_utils_named_values_from_str_dict_with_sort (GHashTable *hash, + guint *out_len, + GCompareDataFunc compare_func, + gpointer user_data) { GHashTableIter iter; NMUtilsNamedValue *values; @@ -2342,7 +2866,8 @@ nm_utils_named_values_from_str_dict (GHashTable *hash, guint *out_len) values[i].name = NULL; values[i].value_ptr = NULL; - nm_utils_named_value_list_sort (values, len, NULL, NULL); + if (compare_func) + nm_utils_named_value_list_sort (values, len, compare_func, user_data); NM_SET_OUT (out_len, len); return values; @@ -3593,7 +4118,7 @@ nm_g_idle_source_new (int priority, } GSource * -nm_g_timeout_source_new (guint timeout_ms, +nm_g_timeout_source_new (guint timeout_msec, int priority, GSourceFunc func, gpointer user_data, @@ -3601,7 +4126,7 @@ nm_g_timeout_source_new (guint timeout_ms, { GSource *source; - source = g_timeout_source_new (timeout_ms); + source = g_timeout_source_new (timeout_msec); if (priority != G_PRIORITY_DEFAULT) g_source_set_priority (source, priority); g_source_set_callback (source, func, user_data, destroy_notify); @@ -3625,6 +4150,26 @@ nm_g_unix_signal_source_new (int signum, return source; } +GSource * +nm_g_unix_fd_source_new (int fd, + GIOCondition io_condition, + int priority, + gboolean (*source_func) (int fd, + GIOCondition condition, + gpointer user_data), + gpointer user_data, + GDestroyNotify destroy_notify) +{ + GSource *source; + + source = g_unix_fd_source_new (fd, io_condition); + + if (priority != G_PRIORITY_DEFAULT) + g_source_set_priority (source, priority); + g_source_set_callback (source, G_SOURCE_FUNC (source_func), user_data, destroy_notify); + return source; +} + /*****************************************************************************/ #define _CTX_LOG(fmt, ...) \ @@ -4038,8 +4583,47 @@ nm_utils_ifname_valid_kernel (const char *name, GError **error) return FALSE; } +/*****************************************************************************/ + +static gboolean +_nm_utils_ifname_valid_kernel (const char *name, GError **error) +{ + if (!nm_utils_ifname_valid_kernel (name, error)) + return FALSE; + + if (strchr (name, '%')) { + /* Kernel's dev_valid_name() accepts (almost) any binary up to 15 chars. + * However, '%' is treated special as a format specifier. Try + * + * ip link add 'dummy%dx' type dummy + * + * Don't allow that for "connection.interface-name", which either + * matches an existing netdev name (thus, it cannot have a '%') or + * is used to configure a name (in which case we don't want kernel + * to replace the format specifier). */ + g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, + _("'%%' is not allowed in interface names")); + return FALSE; + } + + if (NM_IN_STRSET (name, "all", + "default", + "bonding_masters")) { + /* Certain names are not allowed. The "all" and "default" names are reserved + * due to their directories in "/proc/sys/net/ipv4/conf/" and "/proc/sys/net/ipv6/conf/". + * + * Also, there is "/sys/class/net/bonding_masters" file. + */ + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("'%s' is not allowed as interface name"), name); + return FALSE; + } + + return TRUE; +} + static gboolean -_nm_utils_ifname_valid_ovs (const char* name, GError **error) +_nm_utils_ifname_valid_ovs (const char *name, GError **error) { const char *ch; @@ -4081,10 +4665,90 @@ nm_utils_ifname_valid (const char* name, switch (type) { case NMU_IFACE_KERNEL: - return nm_utils_ifname_valid_kernel (name, error); + return _nm_utils_ifname_valid_kernel (name, error); case NMU_IFACE_OVS: return _nm_utils_ifname_valid_ovs (name, error); + case NMU_IFACE_OVS_AND_KERNEL: + return _nm_utils_ifname_valid_kernel (name, error) + && _nm_utils_ifname_valid_ovs (name, error); + case NMU_IFACE_ANY: { + gs_free_error GError *local = NULL; + + if (_nm_utils_ifname_valid_kernel (name, error ? &local : NULL)) + return TRUE; + if (_nm_utils_ifname_valid_ovs (name, NULL)) + return TRUE; + if (error) + g_propagate_error (error, g_steal_pointer (&local)); + return FALSE; + } } g_return_val_if_reached (FALSE); } + +/*****************************************************************************/ + +void +_nm_str_buf_ensure_size (NMStrBuf *strbuf, + gsize new_size, + gboolean reserve_exact) +{ + _nm_str_buf_assert (strbuf); + + /* Currently this only supports strictly growing the buffer. */ + nm_assert (new_size > strbuf->_priv_allocated); + + if (!reserve_exact) { + new_size = nm_utils_get_next_realloc_size (!strbuf->_priv_do_bzero_mem, + new_size); + } + + strbuf->_priv_str = nm_secret_mem_realloc (strbuf->_priv_str, + strbuf->_priv_do_bzero_mem, + strbuf->_priv_allocated, + new_size); + strbuf->_priv_allocated = new_size; +} + +void +nm_str_buf_append_printf (NMStrBuf *strbuf, + const char *format, + ...) +{ + va_list args; + gsize available; + int l; + + _nm_str_buf_assert (strbuf); + + available = strbuf->_priv_allocated - strbuf->_priv_len; + + va_start (args, format); + l = g_vsnprintf (&strbuf->_priv_str[strbuf->_priv_len], + available, + format, + args); + va_end (args); + + nm_assert (l >= 0); + nm_assert (l < G_MAXINT); + + if ((gsize) l > available) { + gsize l2 = ((gsize) l) + 1u; + + nm_str_buf_maybe_expand (strbuf, l2, FALSE); + + va_start (args, format); + l = g_vsnprintf (&strbuf->_priv_str[strbuf->_priv_len], + l2, + format, + args); + va_end (args); + + nm_assert (l >= 0); + nm_assert ((gsize) l == l2 - 1u); + } + + strbuf->_priv_len += (gsize) l; +} diff --git a/shared/nm-glib-aux/nm-shared-utils.h b/shared/nm-glib-aux/nm-shared-utils.h index 740e61d0..537b402b 100644 --- a/shared/nm-glib-aux/nm-shared-utils.h +++ b/shared/nm-glib-aux/nm-shared-utils.h @@ -115,11 +115,31 @@ typedef struct { * However, as ethernet addresses fit in here nicely, use * it also for an ethernet MAC address. */ guint8 addr_eth[6 /*ETH_ALEN*/]; + + guint8 array[sizeof (struct in6_addr)]; }; } NMIPAddr; +#define NM_IP_ADDR_INIT { .array = { 0 } } + extern const NMIPAddr nm_ip_addr_zero; +static inline int +nm_ip_addr_cmp (int addr_family, gconstpointer a, gconstpointer b) +{ + nm_assert_addr_family (addr_family); + nm_assert (a); + nm_assert (b); + + return memcmp (a, b, nm_utils_addr_family_to_size (addr_family)); +} + +static inline gboolean +nm_ip_addr_equal (int addr_family, gconstpointer a, gconstpointer b) +{ + return nm_ip_addr_cmp (addr_family, a, b) == 0; +} + static inline gboolean nm_ip_addr_is_null (int addr_family, gconstpointer addr) { @@ -158,6 +178,72 @@ nm_ip4_addr_is_localhost (in_addr_t addr4) /*****************************************************************************/ +#define NM_UTILS_INET_ADDRSTRLEN INET6_ADDRSTRLEN + +static inline const char * +nm_utils_inet_ntop (int addr_family, gconstpointer addr, char *dst) +{ + const char *s; + + const char *inet_ntop (int af, + const void *src, + char *dst, + socklen_t size); + + nm_assert_addr_family (addr_family); + nm_assert (addr); + nm_assert (dst); + + s = inet_ntop (addr_family, + addr, + dst, + addr_family == AF_INET6 ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN); + nm_assert (s); + return s; +} + +static inline const char * +_nm_utils_inet4_ntop (in_addr_t addr, char dst[static INET_ADDRSTRLEN]) +{ + return nm_utils_inet_ntop (AF_INET, &addr, dst); +} + +static inline const char * +_nm_utils_inet6_ntop (const struct in6_addr *addr, char dst[static INET6_ADDRSTRLEN]) +{ + return nm_utils_inet_ntop (AF_INET6, addr, dst); +} + +static inline char * +nm_utils_inet_ntop_dup (int addr_family, gconstpointer addr) +{ + char buf[NM_UTILS_INET_ADDRSTRLEN]; + + return g_strdup (nm_utils_inet_ntop (addr_family, addr, buf)); +} + +static inline char * +nm_utils_inet4_ntop_dup (in_addr_t addr) +{ + return nm_utils_inet_ntop_dup (AF_INET, &addr); +} + +static inline char * +nm_utils_inet6_ntop_dup (const struct in6_addr *addr) +{ + return nm_utils_inet_ntop_dup (AF_INET6, addr); +} + +/*****************************************************************************/ + +gboolean nm_utils_ipaddr_is_valid (int addr_family, + const char *str_addr); + +gboolean nm_utils_ipaddr_is_normalized (int addr_family, + const char *str_addr); + +/*****************************************************************************/ + #define NM_CMP_RETURN(c) \ G_STMT_START { \ const int _cc = (c); \ @@ -307,6 +393,8 @@ gboolean nm_utils_gbytes_equal_mem (GBytes *bytes, GVariant *nm_utils_gbytes_to_variant_ay (GBytes *bytes); +GVariant *nm_utils_strdict_to_variant_ass (GHashTable *strdict); + /*****************************************************************************/ GVariant *nm_utils_gvariant_vardict_filter (GVariant *src, @@ -352,7 +440,25 @@ int nm_utils_dbus_path_cmp (const char *dbus_path_a, const char *dbus_path_b); typedef enum { NM_UTILS_STRSPLIT_SET_FLAGS_NONE = 0, + + /* by default, strsplit will coalesce consecutive delimiters and remove + * them from the result. If this flag is present, empty values are preserved + * and returned. + * + * When combined with %NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP, if a value gets + * empty after strstrip(), it also gets removed. */ NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY = (1u << 0), + + /* %NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING means that delimiters prefixed + * by a backslash are not treated as a separator. Such delimiters and their escape + * character are copied to the current word without unescaping them. In general, + * nm_utils_strsplit_set_full() does not remove any backslash escape characters + * and does no unescaping. It only considers them for skipping to split at + * an escaped delimiter. + * + * If this is combined with (or implied by %NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED), then + * the backslash escapes are removed from the result. + */ NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING = (1u << 1), /* If flag is set, does the same as g_strstrip() on the returned tokens. @@ -392,6 +498,7 @@ typedef enum { * need extra care, and then only if they proceed one of the relevant characters. */ NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED = (1u << 3), + } NMUtilsStrsplitSetFlags; const char **nm_utils_strsplit_set_full (const char *str, @@ -435,9 +542,65 @@ nm_utils_escaped_tokens_split (const char *str, | NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP); } -const char *nm_utils_escaped_tokens_escape (const char *str, - const char *delimiters, - char **out_to_free); +typedef enum { + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_NONE = 0, + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_SPACES = (1ull << 0), + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE = (1ull << 1), + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE = (1ull << 2), + + /* Backslash characters will be escaped as "\\\\" if they precede another + * character that makes it necessary. Such characters are: + * + * 1) before another '\\' backslash. + * 2) before any delimiter in @delimiters. + * 3) before any delimiter in @delimiters_as_needed. + * 4) before a white space, if ESCAPE_LEADING_SPACE or ESCAPE_TRAILING_SPACE is set. + * 5) before the end of the word + * + * Rule 4) is an extension. It's not immediately clear why with ESCAPE_LEADING_SPACE + * and ESCAPE_TRAILING_SPACE we want *all* backslashes before a white space escaped. + * The reason is, that we obviously want to use ESCAPE_LEADING_SPACE and ESCAPE_TRAILING_SPACE + * in cases, where we later parse the backslash escaped strings back, but allowing to strip + * unescaped white spaces. That means, we want that " a " gets escaped as "\\ a\\ ". + * On the other hand, we also want that " a\\ b " gets escaped as "\\ a\\\\ b\\ ", + * and not "\\ a\\ b\\ ". Because otherwise, the parser would need to treat "\\ " + * differently depending on whether the sequence is at the beginning, end or middle + * of the word. + * + * Rule 5) is also not immediately obvious. When used with ESCAPE_TRAILING_SPACE, + * we clearly want to allow that an escaped word can have arbitrary + * whitespace suffixes. That's why this mode exists. So we must escape "a\\" as + * "a\\\\", so that appending " " does not change the meaning. + * Also without ESCAPE_TRAILING_SPACE, we want in general that we can concatenate + * two escaped words without changing their meaning. If the words would be "a\\" + * and "," (with ',' being a delimiter), then the result must be "a\\\\" and "\\," + * so that the concatenated word ("a\\\\\\,") is still the same. If we would escape + * them instead as "a\\" + "\\,", then the concatenated word would be "a\\\\," and + * different. + * */ + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED = (1ull << 3), + + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_ALWAYS = (1ull << 4), +} NMUtilsEscapedTokensEscapeFlags; + +const char *nm_utils_escaped_tokens_escape_full (const char *str, + const char *delimiters, + const char *delimiters_as_needed, + NMUtilsEscapedTokensEscapeFlags flags, + char **out_to_free); + +static inline const char * +nm_utils_escaped_tokens_escape (const char *str, + const char *delimiters, + char **out_to_free) +{ + return nm_utils_escaped_tokens_escape_full (str, + delimiters, + NULL, + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_ALWAYS + | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE, + out_to_free); +} static inline GString * nm_utils_escaped_tokens_escape_gstr_assert (const char *str, @@ -489,6 +652,47 @@ nm_utils_escaped_tokens_escape_gstr (const char *str, /*****************************************************************************/ +static inline const char ** +nm_utils_escaped_tokens_options_split_list (const char *str) +{ + return nm_utils_strsplit_set_full (str, + ",", + NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP + | NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING); +} + +void nm_utils_escaped_tokens_options_split (char *str, + const char **out_key, + const char **out_val); + +static inline const char * +nm_utils_escaped_tokens_options_escape_key (const char *key, + char **out_to_free) +{ + return nm_utils_escaped_tokens_escape_full (key, + ",=", + NULL, + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED + | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE + | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE, + out_to_free); +} + +static inline const char * +nm_utils_escaped_tokens_options_escape_val (const char *val, + char **out_to_free) +{ + return nm_utils_escaped_tokens_escape_full (val, + ",", + "=", + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED + | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE + | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE, + out_to_free); +} + +/*****************************************************************************/ + #define NM_UTILS_CHECKSUM_LENGTH_MD5 16 #define NM_UTILS_CHECKSUM_LENGTH_SHA1 20 #define NM_UTILS_CHECKSUM_LENGTH_SHA256 32 @@ -571,6 +775,17 @@ gboolean nm_utils_parse_inaddr_prefix (int addr_family, char **out_addr, int *out_prefix); +gint64 nm_g_ascii_strtoll (const char *nptr, + char **endptr, + guint base); + +guint64 nm_g_ascii_strtoull (const char *nptr, + char **endptr, + guint base); + +double nm_g_ascii_strtod (const char *nptr, + char **endptr); + gint64 _nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 max, gint64 fallback); guint64 _nm_utils_ascii_str_to_uint64 (const char *str, guint base, guint64 min, guint64 max, guint64 fallback); @@ -593,8 +808,8 @@ typedef struct { #define NM_UTILS_FLAGS2STR(f, n) { .flag = f, .name = ""n, } -#define _NM_UTILS_FLAGS2STR_DEFINE(scope, fcn_name, flags_type, ...) \ -scope const char * \ +#define NM_UTILS_FLAGS2STR_DEFINE(fcn_name, flags_type, ...) \ +const char * \ fcn_name (flags_type flags, char *buf, gsize len) \ { \ static const NMUtilsFlags2StrDesc descs[] = { \ @@ -604,11 +819,6 @@ fcn_name (flags_type flags, char *buf, gsize len) \ return nm_utils_flags2str (descs, G_N_ELEMENTS (descs), flags, buf, len); \ }; -#define NM_UTILS_FLAGS2STR_DEFINE(fcn_name, flags_type, ...) \ - _NM_UTILS_FLAGS2STR_DEFINE (, fcn_name, flags_type, __VA_ARGS__) -#define NM_UTILS_FLAGS2STR_DEFINE_STATIC(fcn_name, flags_type, ...) \ - _NM_UTILS_FLAGS2STR_DEFINE (static, fcn_name, flags_type, __VA_ARGS__) - const char *nm_utils_flags2str (const NMUtilsFlags2StrDesc *descs, gsize n_descs, unsigned flags, @@ -620,8 +830,8 @@ const char *nm_utils_flags2str (const NMUtilsFlags2StrDesc *descs, #define NM_UTILS_ENUM2STR(v, n) (void) 0; case v: s = ""n""; break; (void) 0 #define NM_UTILS_ENUM2STR_IGNORE(v) (void) 0; case v: break; (void) 0 -#define _NM_UTILS_ENUM2STR_DEFINE(scope, fcn_name, lookup_type, int_fmt, ...) \ -scope const char * \ +#define NM_UTILS_ENUM2STR_DEFINE_FULL(fcn_name, lookup_type, int_fmt, ...) \ +const char * \ fcn_name (lookup_type val, char *buf, gsize len) \ { \ nm_utils_to_string_buffer_init (&buf, &len); \ @@ -641,9 +851,7 @@ fcn_name (lookup_type val, char *buf, gsize len) \ } #define NM_UTILS_ENUM2STR_DEFINE(fcn_name, lookup_type, ...) \ - _NM_UTILS_ENUM2STR_DEFINE (, fcn_name, lookup_type, "d", __VA_ARGS__) -#define NM_UTILS_ENUM2STR_DEFINE_STATIC(fcn_name, lookup_type, ...) \ - _NM_UTILS_ENUM2STR_DEFINE (static, fcn_name, lookup_type, "d", __VA_ARGS__) + NM_UTILS_ENUM2STR_DEFINE_FULL (fcn_name, lookup_type, "d", __VA_ARGS__) /*****************************************************************************/ @@ -797,8 +1005,15 @@ nm_utils_error_new_cancelled (gboolean is_disposing, return error; } -gboolean nm_utils_error_is_cancelled (GError *error, - gboolean consider_is_disposing); +gboolean nm_utils_error_is_cancelled_or_disposing (GError *error); + +static inline gboolean +nm_utils_error_is_cancelled (GError *error) +{ + return error + && error->code == G_IO_ERROR_CANCELLED + && error->domain == G_IO_ERROR; +} gboolean nm_utils_error_is_notfound (GError *error); @@ -927,6 +1142,7 @@ typedef enum { NM_UTILS_STR_UTF8_SAFE_FLAG_NONE = 0, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL = 0x0001, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII = 0x0002, + NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET = 0x0004, } NMUtilsStrUtf8SafeFlags; const char *nm_utils_buf_utf8safe_escape (gconstpointer buf, gssize buflen, NMUtilsStrUtf8SafeFlags flags, char **to_free); @@ -958,6 +1174,32 @@ nm_g_variant_unref_floating (GVariant *var) g_variant_unref (var); } +#define nm_g_variant_lookup(dictionary, ...) \ + ({ \ + GVariant *const _dictionary = (dictionary); \ + \ + ( _dictionary \ + && g_variant_lookup (_dictionary, __VA_ARGS__)); \ + }) + +static inline GVariant * +nm_g_variant_lookup_value (GVariant *dictionary, + const char *key, + const GVariantType *expected_type) +{ + return dictionary + ? g_variant_lookup_value (dictionary, key, expected_type) + : NULL; +} + +static inline gboolean +nm_g_variant_is_of_type (GVariant *value, + const GVariantType *type) +{ + return value + && g_variant_is_of_type (value, type); +} + static inline void nm_g_source_destroy_and_unref (GSource *source) { @@ -986,12 +1228,19 @@ GSource *nm_g_idle_source_new (int priority, gpointer user_data, GDestroyNotify destroy_notify); -GSource *nm_g_timeout_source_new (guint timeout_ms, +GSource *nm_g_timeout_source_new (guint timeout_msec, int priority, GSourceFunc func, gpointer user_data, GDestroyNotify destroy_notify); - +GSource *nm_g_unix_fd_source_new (int fd, + GIOCondition io_condition, + int priority, + gboolean (*source_func) (int fd, + GIOCondition condition, + gpointer user_data), + gpointer user_data, + GDestroyNotify destroy_notify); GSource *nm_g_unix_signal_source_new (int signum, int priority, GSourceFunc handler, @@ -1019,6 +1268,25 @@ nm_g_main_context_push_thread_default (GMainContext *context) return context; } +static inline gboolean +nm_g_main_context_is_thread_default (GMainContext *context) +{ + GMainContext *cur_context; + + cur_context = g_main_context_get_thread_default (); + if (cur_context == context) + return TRUE; + + if (G_UNLIKELY (!cur_context)) + cur_context = g_main_context_default (); + else if (G_UNLIKELY (!context)) + context = g_main_context_default (); + else + return FALSE; + + return (cur_context == context); +} + static inline GMainContext * nm_g_main_context_push_thread_default_if_necessary (GMainContext *context) { @@ -1077,7 +1345,18 @@ typedef struct { }; } NMUtilsNamedValue; -NMUtilsNamedValue *nm_utils_named_values_from_str_dict (GHashTable *hash, guint *out_len); +NMUtilsNamedValue *nm_utils_named_values_from_str_dict_with_sort (GHashTable *hash, + guint *out_len, + GCompareDataFunc compare_func, + gpointer user_data); + +static inline NMUtilsNamedValue * +nm_utils_named_values_from_str_dict (GHashTable *hash, guint *out_len) +{ + G_STATIC_ASSERT (G_STRUCT_OFFSET (NMUtilsNamedValue, name) == 0); + + return nm_utils_named_values_from_str_dict_with_sort (hash, out_len, nm_strcmp_p_with_data, NULL); +} gssize nm_utils_named_value_list_find (const NMUtilsNamedValue *arr, gsize len, @@ -1146,6 +1425,26 @@ char *nm_utils_g_slist_strlist_join (const GSList *a, const char *separator); /*****************************************************************************/ +static inline guint +nm_g_hash_table_size (GHashTable *hash) +{ + return hash ? g_hash_table_size (hash) : 0u; +} + +static inline gpointer +nm_g_hash_table_lookup (GHashTable *hash, gconstpointer key) +{ + return hash ? g_hash_table_lookup (hash, key) : NULL; +} + +static inline gboolean +nm_g_hash_table_remove (GHashTable *hash, gconstpointer key) +{ + return hash ? g_hash_table_remove (hash, key) : FALSE; +} + +/*****************************************************************************/ + gssize nm_utils_ptrarray_find_binary_search (gconstpointer *list, gsize len, gconstpointer needle, @@ -1190,31 +1489,25 @@ _nm_utils_strv_equal (char **strv1, char **strv2) /*****************************************************************************/ -#define NM_UTILS_NS_PER_SECOND ((gint64) 1000000000) -#define NM_UTILS_NS_PER_MSEC ((gint64) 1000000) -#define NM_UTILS_MSEC_PER_SECOND ((gint64) 1000) -#define NM_UTILS_NS_TO_MSEC_CEIL(nsec) (((nsec) + (NM_UTILS_NS_PER_MSEC - 1)) / NM_UTILS_NS_PER_MSEC) +#define NM_UTILS_NSEC_PER_SEC ((gint64) 1000000000) +#define NM_UTILS_USEC_PER_SEC ((gint64) 1000000) +#define NM_UTILS_MSEC_PER_SEC ((gint64) 1000) +#define NM_UTILS_NSEC_PER_MSEC ((gint64) 1000000) + +static inline gint64 +NM_UTILS_NSEC_TO_MSEC_CEIL (gint64 nsec) +{ + return (nsec + (NM_UTILS_NSEC_PER_MSEC - 1)) / NM_UTILS_NSEC_PER_MSEC; +} /*****************************************************************************/ -int nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_ns); +int nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_nsec); ssize_t nm_utils_fd_read_loop (int fd, void *buf, size_t nbytes, bool do_poll); int nm_utils_fd_read_loop_exact (int fd, void *buf, size_t nbytes, bool do_poll); /*****************************************************************************/ -static inline const char * -nm_utils_dbus_normalize_object_path (const char *path) -{ - /* D-Bus does not allow an empty object path. Hence, whenever we mean NULL / no-object - * on D-Bus, it's path is actually "/". - * - * Normalize that away, and return %NULL in that case. */ - if (path && path[0] == '/' && path[1] == '\0') - return NULL; - return path; -} - #define NM_DEFINE_GDBUS_ARG_INFO_FULL(name_, ...) \ ((GDBusArgInfo *) (&((const GDBusArgInfo) { \ .ref_count = -1, \ @@ -1412,6 +1705,92 @@ guint8 *nm_utils_hexstr2bin_alloc (const char *hexstr, /*****************************************************************************/ +#define _NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(fcn_name, \ + value_type, \ + value_type_result, \ + entry_cmd, \ + unknown_val_cmd, \ + get_operator, \ + ...) \ +value_type_result \ +fcn_name (const char *name) \ +{ \ + static const struct { \ + const char *name; \ + value_type value; \ + } LIST[] = { \ + __VA_ARGS__ \ + }; \ + \ + if (NM_MORE_ASSERT_ONCE (5)) { \ + int i; \ + \ + for (i = 0; i < G_N_ELEMENTS (LIST); i++) { \ + nm_assert (LIST[i].name); \ + if (i > 0) \ + nm_assert (strcmp (LIST[i - 1].name, LIST[i].name) < 0); \ + } \ + } \ + \ + { entry_cmd; } \ + \ + if (G_LIKELY (name)) { \ + G_STATIC_ASSERT (G_N_ELEMENTS (LIST) > 1); \ + G_STATIC_ASSERT (G_N_ELEMENTS (LIST) < G_MAXINT / 2 - 10); \ + int imin = 0; \ + int imax = (G_N_ELEMENTS (LIST) - 1); \ + int imid = (G_N_ELEMENTS (LIST) - 1) / 2; \ + \ + for (;;) { \ + const int cmp = strcmp (LIST[imid].name, name); \ + \ + if (G_UNLIKELY (cmp == 0)) \ + return get_operator (LIST[imid].value); \ + \ + if (cmp < 0) \ + imin = imid + 1; \ + else \ + imax = imid - 1; \ + \ + if (G_UNLIKELY (imin > imax)) \ + break; \ + \ + /* integer overflow cannot happen, because LIST is shorter than G_MAXINT/2. */ \ + imid = (imin + imax) / 2;\ + } \ + } \ + \ + { unknown_val_cmd; } \ +} + +#define NM_UTILS_STRING_TABLE_LOOKUP_STRUCT_DEFINE(fcn_name, \ + result_type, \ + entry_cmd, \ + unknown_val_cmd, \ + ...) \ + _NM_UTILS_STRING_TABLE_LOOKUP_DEFINE (fcn_name, \ + result_type, \ + const result_type *, \ + entry_cmd, \ + unknown_val_cmd, \ + &, \ + __VA_ARGS__) + +#define NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(fcn_name, \ + result_type, \ + entry_cmd, \ + unknown_val_cmd, \ + ...) \ + _NM_UTILS_STRING_TABLE_LOOKUP_DEFINE (fcn_name, \ + result_type, \ + result_type, \ + entry_cmd, \ + unknown_val_cmd, \ + , \ + __VA_ARGS__) + +/*****************************************************************************/ + static inline GTask * nm_g_task_new (gpointer source_object, GCancellable *cancellable, @@ -1442,9 +1821,49 @@ guint nm_utils_parse_debug_string (const char *string, /*****************************************************************************/ +static inline gboolean +nm_utils_strdup_reset (char **dst, const char *src) +{ + nm_assert (dst); + + if (nm_streq0 (*dst, src)) + return FALSE; + g_free (*dst); + *dst = g_strdup (src); + return TRUE; +} + +/*****************************************************************************/ + +/* nm_utils_get_next_realloc_size() is used to grow buffers exponentially, when + * the final size is unknown. As such, it has borders for which it allocates + * certain buffer sizes. + * + * The use of these defines is to get favorable allocation sequences. + * For example, nm_str_buf_init() asks for an initial allocation size. Note that + * it reserves the exactly requested amount, under the assumption that the + * user may know how many bytes will be required. However, often the caller + * doesn't know in advance, and NMStrBuf grows exponentially by calling + * nm_utils_get_next_realloc_size(). + * Imagine you call nm_str_buf_init() with an initial buffer size 100, and you + * add one character at a time. Then the first reallocation will increase the + * buffer size only from 100 to 104. + * If you however start with an initial buffer size of 104, then the next reallocation + * via nm_utils_get_next_realloc_size() gives you 232, and so on. By using + * these sizes, it results in one less allocation, if you anyway don't know the + * exact size in advance. */ +#define NM_UTILS_GET_NEXT_REALLOC_SIZE_104 ((gsize) 104) +#define NM_UTILS_GET_NEXT_REALLOC_SIZE_1000 ((gsize) 1000) + +gsize nm_utils_get_next_realloc_size (gboolean true_realloc, gsize requested); + +/*****************************************************************************/ + typedef enum { - NMU_IFACE_KERNEL = 0, + NMU_IFACE_ANY, + NMU_IFACE_KERNEL, NMU_IFACE_OVS, + NMU_IFACE_OVS_AND_KERNEL, } NMUtilsIfaceType; gboolean nm_utils_ifname_valid_kernel (const char *name, GError **error); diff --git a/shared/nm-glib-aux/nm-str-buf.h b/shared/nm-glib-aux/nm-str-buf.h new file mode 100644 index 00000000..8c73bfaf --- /dev/null +++ b/shared/nm-glib-aux/nm-str-buf.h @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: LGPL-2.1+ + +#ifndef __NM_STR_BUF_H__ +#define __NM_STR_BUF_H__ + +#include "nm-shared-utils.h" +#include "nm-secret-utils.h" + +/*****************************************************************************/ + +/* NMStrBuf is not unlike GString. The main difference is that it can use + * nm_explicit_bzero() when growing the buffer. */ +typedef struct _NMStrBuf { + + char *_priv_str; + + /* The unions only exist because we allow/encourage read-only access + * to the "len" and "allocated" fields, but modifying the fields is + * only allowed to the NMStrBuf implementation itself. */ + union { + /*const*/ gsize len; + gsize _priv_len; + }; + union { + /*const*/ gsize allocated; + gsize _priv_allocated; + }; + + bool _priv_do_bzero_mem; +} NMStrBuf; + +/*****************************************************************************/ + +static inline void +_nm_str_buf_assert (NMStrBuf *strbuf) +{ + nm_assert (strbuf); + nm_assert (strbuf->_priv_str); + nm_assert (strbuf->_priv_allocated > 0); + nm_assert (strbuf->_priv_len <= strbuf->_priv_allocated); +} + +static inline void +nm_str_buf_init (NMStrBuf *strbuf, + gsize len, + bool do_bzero_mem) +{ + nm_assert (strbuf); + nm_assert (len > 0); + + strbuf->_priv_str = g_malloc (len); + strbuf->_priv_allocated = len; + strbuf->_priv_len = 0; + strbuf->_priv_do_bzero_mem = do_bzero_mem; + + _nm_str_buf_assert (strbuf); +} + +void _nm_str_buf_ensure_size (NMStrBuf *strbuf, + gsize new_size, + gboolean reserve_exact); + +static inline void +nm_str_buf_maybe_expand (NMStrBuf *strbuf, + gsize reserve, + gboolean reserve_exact) +{ + _nm_str_buf_assert (strbuf); + + /* currently we always require to reserve a non-zero number of bytes. */ + nm_assert (reserve > 0); + nm_assert (strbuf->_priv_len < G_MAXSIZE - reserve); + + /* @reserve is the extra space that we require. */ + if (G_UNLIKELY (reserve > strbuf->_priv_allocated - strbuf->_priv_len)) + _nm_str_buf_ensure_size (strbuf, strbuf->_priv_len + reserve, reserve_exact); +} + +/*****************************************************************************/ + +/** + * nm_str_buf_set_size: + * @strbuf: the initialized #NMStrBuf + * @new_len: the new length + * @honor_do_bzero_mem: if %TRUE, the shrinked memory will be cleared, if + * do_bzero_mem is set. This should be usually set to %TRUE, unless + * you know that the shrinked memory does not contain data that requires to be + * cleared. When growing the size, this value has no effect. + * @reserve_exact: when growing the buffer, reserve the exact amount of bytes. + * If %FALSE, the buffer may allocate more memory than requested to grow + * exponentially. + * + * This is like g_string_set_size(). If new_len is smaller than the + * current length, the string gets truncated (excess memory will be cleared). + * + * When extending the length, the added bytes are undefined (like with + * g_string_set_size(). Likewise, if you first pre-allocate a buffer with + * nm_str_buf_maybe_expand(), then write to the bytes, and finally set + * the appropriate size, then that works as expected (by not clearing the + * pre-existing, grown buffer). + */ +static inline void +nm_str_buf_set_size (NMStrBuf *strbuf, + gsize new_len, + gboolean honor_do_bzero_mem, + gboolean reserve_exact) +{ + _nm_str_buf_assert (strbuf); + + if (new_len < strbuf->_priv_len) { + if ( honor_do_bzero_mem + && strbuf->_priv_do_bzero_mem) { + /* we only clear the memory that we wrote to. */ + nm_explicit_bzero (&strbuf->_priv_str[new_len], strbuf->_priv_len - new_len); + } + } else if (new_len > strbuf->_priv_len) { + nm_str_buf_maybe_expand (strbuf, + new_len - strbuf->_priv_len + (reserve_exact ? 0u : 1u), + reserve_exact); + } else + return; + + strbuf->_priv_len = new_len; +} + +/*****************************************************************************/ + +static inline void +nm_str_buf_erase (NMStrBuf *strbuf, + gsize pos, + gssize len, + gboolean honor_do_bzero_mem) +{ + gsize new_len; + + _nm_str_buf_assert (strbuf); + + nm_assert (pos <= strbuf->_priv_len); + + if (len == 0) + return; + + if (len < 0) { + /* truncate the string before pos */ + nm_assert (len == -1); + new_len = pos; + } else { + gsize l = len; + + nm_assert (l <= strbuf->_priv_len - pos); + + new_len = strbuf->_priv_len - l; + if (pos + l < strbuf->_priv_len) { + memmove (&strbuf->_priv_str[pos], + &strbuf->_priv_str[pos + l], + strbuf->_priv_len - (pos + l)); + } + } + + nm_assert (new_len <= strbuf->_priv_len); + nm_str_buf_set_size (strbuf, new_len, honor_do_bzero_mem, TRUE); +} + +/*****************************************************************************/ + +static inline void +nm_str_buf_append_c (NMStrBuf *strbuf, + char ch) +{ + nm_str_buf_maybe_expand (strbuf, 2, FALSE); + strbuf->_priv_str[strbuf->_priv_len++] = ch; +} + +static inline void +nm_str_buf_append_c2 (NMStrBuf *strbuf, + char ch0, + char ch1) +{ + nm_str_buf_maybe_expand (strbuf, 3, FALSE); + strbuf->_priv_str[strbuf->_priv_len++] = ch0; + strbuf->_priv_str[strbuf->_priv_len++] = ch1; +} + +static inline void +nm_str_buf_append_c4 (NMStrBuf *strbuf, + char ch0, + char ch1, + char ch2, + char ch3) +{ + nm_str_buf_maybe_expand (strbuf, 5, FALSE); + strbuf->_priv_str[strbuf->_priv_len++] = ch0; + strbuf->_priv_str[strbuf->_priv_len++] = ch1; + strbuf->_priv_str[strbuf->_priv_len++] = ch2; + strbuf->_priv_str[strbuf->_priv_len++] = ch3; +} + +static inline void +nm_str_buf_append_len (NMStrBuf *strbuf, + const char *str, + gsize len) +{ + _nm_str_buf_assert (strbuf); + + if (len > 0) { + nm_str_buf_maybe_expand (strbuf, len + 1, FALSE); + memcpy (&strbuf->_priv_str[strbuf->_priv_len], str, len); + strbuf->_priv_len += len; + } +} + +static inline void +nm_str_buf_append (NMStrBuf *strbuf, + const char *str) +{ + nm_assert (str); + + nm_str_buf_append_len (strbuf, str, strlen (str)); +} + +void nm_str_buf_append_printf (NMStrBuf *strbuf, + const char *format, + ...) _nm_printf (2, 3); + +static inline void +nm_str_buf_ensure_trailing_c (NMStrBuf *strbuf, char ch) +{ + _nm_str_buf_assert (strbuf); + + if ( strbuf->_priv_len == 0 + || strbuf->_priv_str[strbuf->_priv_len - 1] != ch) + nm_str_buf_append_c (strbuf, ch); +} + +/*****************************************************************************/ + +static inline gboolean +nm_str_buf_is_initalized (NMStrBuf *strbuf) +{ + nm_assert (strbuf); +#if NM_MORE_ASSERTS + if (strbuf->_priv_str) + _nm_str_buf_assert (strbuf); +#endif + return !!strbuf->_priv_str; +} + +/** + * nm_str_buf_get_str: + * @strbuf: the #NMStrBuf instance + * + * Returns the NUL terminated internal string. + * + * While constructing the string, the intermediate buffer + * is not NUL terminated (this makes it different from GString). + * Usually, one would build the string and retrieve it at the + * end with nm_str_buf_finalize(). This returns the NUL terminated + * buffer that was appended so far. Contrary to nm_str_buf_finalize(), you + * can still append more data to the buffer and this does not transfer ownership + * of the string. + * + * Returns: (transfer none): the internal string. The string + * is of length "strbuf->len", which may be larger if the + * returned string contains NUL characters (binary). The terminating + * NUL character is always present after "strbuf->len" characters. + */ +static inline const char * +nm_str_buf_get_str (NMStrBuf *strbuf) +{ + nm_str_buf_maybe_expand (strbuf, 1, FALSE); + strbuf->_priv_str[strbuf->_priv_len] = '\0'; + return strbuf->_priv_str; +} + +static inline char * +nm_str_buf_get_str_unsafe (NMStrBuf *strbuf) +{ + _nm_str_buf_assert (strbuf); + return strbuf->_priv_str; +} + +/** + * nm_str_buf_finalize: + * @strbuf: an initilized #NMStrBuf + * @out_len: (out): (allow-none): optional output + * argument with the length of the returned string. + * + * Returns: (transfer full): the string of the buffer + * which must be freed by the caller. The @strbuf + * is afterwards in undefined state, though it can be + * reused after nm_str_buf_init(). */ +static inline char * +nm_str_buf_finalize (NMStrBuf *strbuf, + gsize *out_len) +{ + nm_str_buf_maybe_expand (strbuf, 1, TRUE); + strbuf->_priv_str[strbuf->_priv_len] = '\0'; + + NM_SET_OUT (out_len, strbuf->_priv_len); + + /* the buffer is in invalid state afterwards, however, we clear it + * so far, that nm_auto_str_buf and nm_str_buf_destroy() is happy. */ + return g_steal_pointer (&strbuf->_priv_str); +} + +/** + * nm_str_buf_destroy: + * @strbuf: an initialized #NMStrBuf + * + * Frees the associated memory of @strbuf. The buffer + * afterwards is in undefined state, but can be re-initialized + * with nm_str_buf_init(). + */ +static inline void +nm_str_buf_destroy (NMStrBuf *strbuf) +{ + if (!strbuf->_priv_str) + return; + _nm_str_buf_assert (strbuf); + if (strbuf->_priv_do_bzero_mem) + nm_explicit_bzero (strbuf->_priv_str, strbuf->_priv_len); + g_free (strbuf->_priv_str); + + /* the buffer is in invalid state afterwards, however, we clear it + * so far, that nm_auto_str_buf is happy when calling + * nm_str_buf_destroy() again. */ + strbuf->_priv_str = NULL; +} + +#define nm_auto_str_buf nm_auto (nm_str_buf_destroy) + +#endif /* __NM_STR_BUF_H__ */ diff --git a/shared/nm-glib-aux/nm-time-utils.c b/shared/nm-glib-aux/nm-time-utils.c index 20d663bc..356ed1a5 100644 --- a/shared/nm-glib-aux/nm-time-utils.c +++ b/shared/nm-glib-aux/nm-time-utils.c @@ -40,19 +40,19 @@ _t_init_global_state (void) /* The only failure we tolerate is that CLOCK_BOOTTIME is not supported. * Other than that, we rely on kernel to not fail on this. */ g_assert (r == 0); - g_assert (tp.tv_nsec >= 0 && tp.tv_nsec < NM_UTILS_NS_PER_SECOND); + g_assert (tp.tv_nsec >= 0 && tp.tv_nsec < NM_UTILS_NSEC_PER_SEC); /* Calculate an offset for the time stamp. * * We always want positive values, because then we can initialize * a timestamp with 0 and be sure, that it will be less then any * value nm_utils_get_monotonic_timestamp_*() might return. - * For this to be true also for nm_utils_get_monotonic_timestamp_s() at + * For this to be true also for nm_utils_get_monotonic_timestamp_sec() at * early boot, we have to shift the timestamp to start counting at * least from 1 second onward. * * Another advantage of shifting is, that this way we make use of the whole 31 bit - * range of signed int, before the time stamp for nm_utils_get_monotonic_timestamp_s() + * range of signed int, before the time stamp for nm_utils_get_monotonic_timestamp_sec() * wraps (~68 years). **/ offset_sec = (- ((gint64) tp.tv_sec)) + 1; @@ -96,7 +96,7 @@ _t_init_global_state (void) _r = clock_gettime (_p2->clk_id, _tp); \ \ nm_assert (_r == 0); \ - nm_assert (_tp->tv_nsec >= 0 && _tp->tv_nsec < NM_UTILS_NS_PER_SECOND); \ + nm_assert (_tp->tv_nsec >= 0 && _tp->tv_nsec < NM_UTILS_NSEC_PER_SEC); \ \ _p2; \ }) @@ -107,7 +107,7 @@ _t_init_global_state (void) /*****************************************************************************/ /** - * nm_utils_get_monotonic_timestamp_ns: + * nm_utils_get_monotonic_timestamp_nsec: * * Returns: a monotonically increasing time stamp in nanoseconds, * starting at an unspecified offset. See clock_gettime(), %CLOCK_BOOTTIME. @@ -115,11 +115,11 @@ _t_init_global_state (void) * The returned value will start counting at an undefined point * in the past and will always be positive. * - * All the nm_utils_get_monotonic_timestamp_*s functions return the same + * All the nm_utils_get_monotonic_timestamp_*sec functions return the same * timestamp but in different scales (nsec, usec, msec, sec). **/ gint64 -nm_utils_get_monotonic_timestamp_ns (void) +nm_utils_get_monotonic_timestamp_nsec (void) { const GlobalState *p; struct timespec tp; @@ -130,12 +130,12 @@ nm_utils_get_monotonic_timestamp_ns (void) * integer, which makes it easier to calculate time differences (when * you want to subtract signed values). **/ - return (((gint64) tp.tv_sec) + p->offset_sec) * NM_UTILS_NS_PER_SECOND + + return (((gint64) tp.tv_sec) + p->offset_sec) * NM_UTILS_NSEC_PER_SEC + tp.tv_nsec; } /** - * nm_utils_get_monotonic_timestamp_us: + * nm_utils_get_monotonic_timestamp_usec: * * Returns: a monotonically increasing time stamp in microseconds, * starting at an unspecified offset. See clock_gettime(), %CLOCK_BOOTTIME. @@ -143,11 +143,11 @@ nm_utils_get_monotonic_timestamp_ns (void) * The returned value will start counting at an undefined point * in the past and will always be positive. * - * All the nm_utils_get_monotonic_timestamp_*s functions return the same + * All the nm_utils_get_monotonic_timestamp_*sec functions return the same * timestamp but in different scales (nsec, usec, msec, sec). **/ gint64 -nm_utils_get_monotonic_timestamp_us (void) +nm_utils_get_monotonic_timestamp_usec (void) { const GlobalState *p; struct timespec tp; @@ -159,11 +159,11 @@ nm_utils_get_monotonic_timestamp_us (void) * you want to subtract signed values). **/ return (((gint64) tp.tv_sec) + p->offset_sec) * ((gint64) G_USEC_PER_SEC) + - (tp.tv_nsec / (NM_UTILS_NS_PER_SECOND/G_USEC_PER_SEC)); + (tp.tv_nsec / (NM_UTILS_NSEC_PER_SEC/G_USEC_PER_SEC)); } /** - * nm_utils_get_monotonic_timestamp_ms: + * nm_utils_get_monotonic_timestamp_msec: * * Returns: a monotonically increasing time stamp in milliseconds, * starting at an unspecified offset. See clock_gettime(), %CLOCK_BOOTTIME. @@ -171,11 +171,11 @@ nm_utils_get_monotonic_timestamp_us (void) * The returned value will start counting at an undefined point * in the past and will always be positive. * - * All the nm_utils_get_monotonic_timestamp_*s functions return the same + * All the nm_utils_get_monotonic_timestamp_*sec functions return the same * timestamp but in different scales (nsec, usec, msec, sec). **/ gint64 -nm_utils_get_monotonic_timestamp_ms (void) +nm_utils_get_monotonic_timestamp_msec (void) { const GlobalState *p; struct timespec tp; @@ -187,23 +187,23 @@ nm_utils_get_monotonic_timestamp_ms (void) * you want to subtract signed values). **/ return (((gint64) tp.tv_sec) + p->offset_sec) * ((gint64) 1000) + - (tp.tv_nsec / (NM_UTILS_NS_PER_SECOND/1000)); + (tp.tv_nsec / (NM_UTILS_NSEC_PER_SEC/1000)); } /** - * nm_utils_get_monotonic_timestamp_s: + * nm_utils_get_monotonic_timestamp_sec: * - * Returns: nm_utils_get_monotonic_timestamp_ms() in seconds (throwing + * Returns: nm_utils_get_monotonic_timestamp_msec() in seconds (throwing * away sub second parts). The returned value will always be positive. * * This value wraps after roughly 68 years which should be fine for any * practical purpose. * - * All the nm_utils_get_monotonic_timestamp_*s functions return the same + * All the nm_utils_get_monotonic_timestamp_*sec functions return the same * timestamp but in different scales (nsec, usec, msec, sec). **/ gint32 -nm_utils_get_monotonic_timestamp_s (void) +nm_utils_get_monotonic_timestamp_sec (void) { const GlobalState *p; struct timespec tp; @@ -216,13 +216,13 @@ nm_utils_get_monotonic_timestamp_s (void) /** * nm_utils_monotonic_timestamp_as_boottime: * @timestamp: the monotonic-timestamp that should be converted into CLOCK_BOOTTIME. - * @timestamp_ns_per_tick: How many nanoseconds make one unit of @timestamp? E.g. if - * @timestamp is in unit seconds, pass %NM_UTILS_NS_PER_SECOND; if @timestamp is - * in nanoseconds, pass 1; if @timestamp is in milliseconds, pass %NM_UTILS_NS_PER_SECOND/1000. - * This must be a multiple of 10, and between 1 and %NM_UTILS_NS_PER_SECOND. + * @timestamp_nsec_per_tick: How many nanoseconds make one unit of @timestamp? E.g. if + * @timestamp is in unit seconds, pass %NM_UTILS_NSEC_PER_SEC; if @timestamp is + * in nanoseconds, pass 1; if @timestamp is in milliseconds, pass %NM_UTILS_NSEC_PER_SEC/1000. + * This must be a multiple of 10, and between 1 and %NM_UTILS_NSEC_PER_SEC. * * Returns: the monotonic-timestamp as CLOCK_BOOTTIME, as returned by clock_gettime(). - * The unit is the same as the passed in @timestamp based on @timestamp_ns_per_tick. + * The unit is the same as the passed in @timestamp based on @timestamp_nsec_per_tick. * E.g. if you passed @timestamp in as seconds, it will return boottime in seconds. * * Note that valid monotonic-timestamps are always positive numbers (counting roughly since @@ -234,16 +234,16 @@ nm_utils_get_monotonic_timestamp_s (void) * On older kernels that don't support CLOCK_BOOTTIME, the returned time is instead CLOCK_MONOTONIC. **/ gint64 -nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ns_per_tick) +nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_nsec_per_tick) { const GlobalState *p; gint64 offset; - /* only support ns-per-tick being a multiple of 10. */ - g_return_val_if_fail (timestamp_ns_per_tick == 1 - || (timestamp_ns_per_tick > 0 && - timestamp_ns_per_tick <= NM_UTILS_NS_PER_SECOND && - timestamp_ns_per_tick % 10 == 0), + /* only support nsec-per-tick being a multiple of 10. */ + g_return_val_if_fail ( timestamp_nsec_per_tick == 1 + || (timestamp_nsec_per_tick > 0 && + timestamp_nsec_per_tick <= NM_UTILS_NSEC_PER_SEC && + timestamp_nsec_per_tick % 10 == 0), -1); /* if the caller didn't yet ever fetch a monotonic-timestamp, he cannot pass any meaningful @@ -255,7 +255,7 @@ nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ns_ nm_assert (p->offset_sec <= 0); /* calculate the offset of monotonic-timestamp to boottime. offset_s is <= 1. */ - offset = p->offset_sec * (NM_UTILS_NS_PER_SECOND / timestamp_ns_per_tick); + offset = p->offset_sec * (NM_UTILS_NSEC_PER_SEC / timestamp_nsec_per_tick); nm_assert (offset <= 0 && offset > G_MININT64); @@ -270,9 +270,9 @@ nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ns_ * @boottime: the timestamp from CLOCK_BOOTTIME (or CLOCK_MONOTONIC, if * kernel does not support CLOCK_BOOTTIME and monotonic timestamps are based * on CLOCK_MONOTONIC). - * @timestamp_ns_per_tick: the scale in which @boottime is. If @boottime is in + * @timestamp_nsec_per_tick: the scale in which @boottime is. If @boottime is in * nano seconds, this should be 1. If it is in milli seconds, this should be - * %NM_UTILS_NS_PER_SECOND/1000, etc. + * %NM_UTILS_NSEC_PER_SEC/1000, etc. * * Returns: the same timestamp in monotonic timestamp scale. * @@ -284,16 +284,16 @@ nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ns_ * This is the inverse of nm_utils_monotonic_timestamp_as_boottime(). */ gint64 -nm_utils_monotonic_timestamp_from_boottime (guint64 boottime, gint64 timestamp_ns_per_tick) +nm_utils_monotonic_timestamp_from_boottime (guint64 boottime, gint64 timestamp_nsec_per_tick) { const GlobalState *p; gint64 offset; - /* only support ns-per-tick being a multiple of 10. */ - g_return_val_if_fail (timestamp_ns_per_tick == 1 - || (timestamp_ns_per_tick > 0 && - timestamp_ns_per_tick <= NM_UTILS_NS_PER_SECOND && - timestamp_ns_per_tick % 10 == 0), + /* only support nsec-per-tick being a multiple of 10. */ + g_return_val_if_fail ( timestamp_nsec_per_tick == 1 + || (timestamp_nsec_per_tick > 0 && + timestamp_nsec_per_tick <= NM_UTILS_NSEC_PER_SEC && + timestamp_nsec_per_tick % 10 == 0), -1); p = _t_get_global_state (); @@ -301,7 +301,7 @@ nm_utils_monotonic_timestamp_from_boottime (guint64 boottime, gint64 timestamp_n nm_assert (p->offset_sec <= 0); /* calculate the offset of monotonic-timestamp to boottime. offset_s is <= 1. */ - offset = p->offset_sec * (NM_UTILS_NS_PER_SECOND / timestamp_ns_per_tick); + offset = p->offset_sec * (NM_UTILS_NSEC_PER_SEC / timestamp_nsec_per_tick); nm_assert (offset <= 0 && offset > G_MININT64); @@ -312,21 +312,21 @@ nm_utils_monotonic_timestamp_from_boottime (guint64 boottime, gint64 timestamp_n } gint64 -nm_utils_clock_gettime_ns (clockid_t clockid) +nm_utils_clock_gettime_nsec (clockid_t clockid) { struct timespec tp; if (clock_gettime (clockid, &tp) != 0) return -NM_ERRNO_NATIVE (errno); - return nm_utils_timespec_to_ns (&tp); + return nm_utils_timespec_to_nsec (&tp); } gint64 -nm_utils_clock_gettime_ms (clockid_t clockid) +nm_utils_clock_gettime_msec (clockid_t clockid) { struct timespec tp; if (clock_gettime (clockid, &tp) != 0) return -NM_ERRNO_NATIVE (errno); - return nm_utils_timespec_to_ms (&tp); + return nm_utils_timespec_to_msec (&tp); } diff --git a/shared/nm-glib-aux/nm-time-utils.h b/shared/nm-glib-aux/nm-time-utils.h index 8bf41b96..190d9491 100644 --- a/shared/nm-glib-aux/nm-time-utils.h +++ b/shared/nm-glib-aux/nm-time-utils.h @@ -9,34 +9,42 @@ #include <time.h> static inline gint64 -nm_utils_timespec_to_ns (const struct timespec *ts) +nm_utils_timespec_to_nsec (const struct timespec *ts) { - return (((gint64) ts->tv_sec) * ((gint64) NM_UTILS_NS_PER_SECOND)) + return (((gint64) ts->tv_sec) * ((gint64) NM_UTILS_NSEC_PER_SEC)) + ((gint64) ts->tv_nsec); } static inline gint64 -nm_utils_timespec_to_ms (const struct timespec *ts) +nm_utils_timespec_to_msec (const struct timespec *ts) { return (((gint64) ts->tv_sec) * ((gint64) 1000)) - + (((gint64) ts->tv_nsec) / ((gint64) NM_UTILS_NS_PER_SECOND / 1000)); + + (((gint64) ts->tv_nsec) / ((gint64) NM_UTILS_NSEC_PER_SEC / 1000)); } -gint64 nm_utils_get_monotonic_timestamp_ns (void); -gint64 nm_utils_get_monotonic_timestamp_us (void); -gint64 nm_utils_get_monotonic_timestamp_ms (void); -gint32 nm_utils_get_monotonic_timestamp_s (void); -gint64 nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ticks_per_ns); -gint64 nm_utils_monotonic_timestamp_from_boottime (guint64 boottime, gint64 timestamp_ns_per_tick); +gint64 nm_utils_get_monotonic_timestamp_nsec (void); +gint64 nm_utils_get_monotonic_timestamp_usec (void); +gint64 nm_utils_get_monotonic_timestamp_msec (void); +gint32 nm_utils_get_monotonic_timestamp_sec (void); + +gint64 nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ticks_per_nsec); +gint64 nm_utils_monotonic_timestamp_from_boottime (guint64 boottime, gint64 timestamp_nsec_per_tick); + +static inline gint64 +nm_utils_get_monotonic_timestamp_nsec_cached (gint64 *cache_now) +{ + return (*cache_now) + ?: (*cache_now = nm_utils_get_monotonic_timestamp_nsec ()); +} static inline gint64 -nm_utils_get_monotonic_timestamp_ns_cached (gint64 *cache_now) +nm_utils_get_monotonic_timestamp_msec_cached (gint64 *cache_now) { return (*cache_now) - ?: (*cache_now = nm_utils_get_monotonic_timestamp_ns ()); + ?: (*cache_now = nm_utils_get_monotonic_timestamp_msec ()); } -gint64 nm_utils_clock_gettime_ns (clockid_t clockid); -gint64 nm_utils_clock_gettime_ms (clockid_t clockid); +gint64 nm_utils_clock_gettime_nsec (clockid_t clockid); +gint64 nm_utils_clock_gettime_msec (clockid_t clockid); #endif /* __NM_TIME_UTILS_H__ */ diff --git a/shared/nm-glib-aux/tests/meson.build b/shared/nm-glib-aux/tests/meson.build new file mode 100644 index 00000000..f6328db9 --- /dev/null +++ b/shared/nm-glib-aux/tests/meson.build @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: LGPL-2.1+ + +test_unit = 'test-shared-general' + +c_flags = [ + '-DNETWORKMANAGER_COMPILATION_TEST', + '-DNETWORKMANAGER_COMPILATION=(NM_NETWORKMANAGER_COMPILATION_GLIB|NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG)', +] + +exe = executable( + test_unit, + test_unit + '.c', + c_args: c_flags, + dependencies: libnm_utils_base_dep, + link_with: libnm_systemd_logging_stub, +) + +test( + 'shared/nm-glib-aux/' + test_unit, + test_script, + args: test_args + [exe.full_path()], + timeout: default_test_timeout, +) diff --git a/shared/nm-glib-aux/tests/test-shared-general.c b/shared/nm-glib-aux/tests/test-shared-general.c new file mode 100644 index 00000000..5c8bdf18 --- /dev/null +++ b/shared/nm-glib-aux/tests/test-shared-general.c @@ -0,0 +1,797 @@ +// SPDX-License-Identifier: LGPL-2.1+ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#define NM_TEST_UTILS_NO_LIBNM 1 + +#include "nm-default.h" + +#include "nm-std-aux/unaligned.h" +#include "nm-glib-aux/nm-random-utils.h" +#include "nm-glib-aux/nm-str-buf.h" +#include "nm-glib-aux/nm-time-utils.h" +#include "nm-glib-aux/nm-ref-string.h" + +#include "nm-utils/nm-test-utils.h" + +/*****************************************************************************/ + +static void +test_gpid (void) +{ + const int *int_ptr; + GPid pid = 42; + + /* We redefine G_PID_FORMAT, because it's only available since glib 2.53.5. + * + * Also, this is the format for GPid, which for glib is always a typedef + * for "int". Add a check for that here. + * + * G_PID_FORMAT is not about pid_t, which might be a smaller int, and which we would + * check with SIZEOF_PID_T. */ + G_STATIC_ASSERT (sizeof (GPid) == sizeof (int)); + + g_assert_cmpstr (""G_PID_FORMAT, ==, "i"); + + /* check that it's really "int". We will get a compiler warning, if that's not + * the case. */ + int_ptr = &pid; + g_assert_cmpint (*int_ptr, ==, 42); +} + +/*****************************************************************************/ + +static void +test_monotonic_timestamp (void) +{ + g_assert (nm_utils_get_monotonic_timestamp_sec () > 0); +} + +/*****************************************************************************/ + +static void +test_nmhash (void) +{ + int rnd; + + nm_utils_random_bytes (&rnd, sizeof (rnd)); + + g_assert (nm_hash_val (555, 4) != 0); +} + +/*****************************************************************************/ + +static const char * +_make_strv_foo (void) +{ + return "foo"; +} + +static const char *const*const _tst_make_strv_1 = NM_MAKE_STRV ("1", "2"); + +static void +test_make_strv (void) +{ + const char *const*v1a = NM_MAKE_STRV ("a"); + const char *const*v1b = NM_MAKE_STRV ("a", ); + const char *const*v2a = NM_MAKE_STRV ("a", "b"); + const char *const*v2b = NM_MAKE_STRV ("a", "b", ); + const char *const v3[] = { "a", "b", }; + const char *const*v4b = NM_MAKE_STRV ("a", _make_strv_foo (), ); + + g_assert (NM_PTRARRAY_LEN (v1a) == 1); + g_assert (NM_PTRARRAY_LEN (v1b) == 1); + g_assert (NM_PTRARRAY_LEN (v2a) == 2); + g_assert (NM_PTRARRAY_LEN (v2b) == 2); + + g_assert (NM_PTRARRAY_LEN (_tst_make_strv_1) == 2); + g_assert_cmpstr (_tst_make_strv_1[0], ==, "1"); + g_assert_cmpstr (_tst_make_strv_1[1], ==, "2"); + /* writing the static read-only variable leads to crash .*/ + //((char **) _tst_make_strv_1)[0] = NULL; + //((char **) _tst_make_strv_1)[2] = "c"; + + G_STATIC_ASSERT_EXPR (G_N_ELEMENTS (v3) == 2); + + g_assert (NM_PTRARRAY_LEN (v4b) == 2); + + G_STATIC_ASSERT_EXPR (G_N_ELEMENTS (NM_MAKE_STRV ("a", "b" )) == 3); + G_STATIC_ASSERT_EXPR (G_N_ELEMENTS (NM_MAKE_STRV ("a", "b", )) == 3); + + nm_strquote_a (300, ""); +} + +/*****************************************************************************/ + +typedef enum { + TEST_NM_STRDUP_ENUM_m1 = -1, + TEST_NM_STRDUP_ENUM_3 = 3, +} TestNMStrdupIntEnum; + +static void +test_nm_strdup_int (void) +{ +#define _NM_STRDUP_INT_TEST(num, str) \ + G_STMT_START { \ + gs_free char *_s1 = NULL; \ + \ + _s1 = nm_strdup_int ((num)); \ + \ + g_assert (_s1); \ + g_assert_cmpstr (_s1, ==, str); \ + } G_STMT_END + +#define _NM_STRDUP_INT_TEST_TYPED(type, num) \ + G_STMT_START { \ + type _num = ((type) num); \ + \ + _NM_STRDUP_INT_TEST (_num, G_STRINGIFY (num)); \ + } G_STMT_END + + _NM_STRDUP_INT_TEST_TYPED (char, 0); + _NM_STRDUP_INT_TEST_TYPED (char, 1); + _NM_STRDUP_INT_TEST_TYPED (guint8, 0); + _NM_STRDUP_INT_TEST_TYPED (gint8, 25); + _NM_STRDUP_INT_TEST_TYPED (char, 47); + _NM_STRDUP_INT_TEST_TYPED (short, 47); + _NM_STRDUP_INT_TEST_TYPED (int, 47); + _NM_STRDUP_INT_TEST_TYPED (long, 47); + _NM_STRDUP_INT_TEST_TYPED (unsigned char, 47); + _NM_STRDUP_INT_TEST_TYPED (unsigned short, 47); + _NM_STRDUP_INT_TEST_TYPED (unsigned, 47); + _NM_STRDUP_INT_TEST_TYPED (unsigned long, 47); + _NM_STRDUP_INT_TEST_TYPED (gint64, 9223372036854775807); + _NM_STRDUP_INT_TEST_TYPED (gint64, -9223372036854775807); + _NM_STRDUP_INT_TEST_TYPED (guint64, 0); + _NM_STRDUP_INT_TEST_TYPED (guint64, 9223372036854775807); + + _NM_STRDUP_INT_TEST (TEST_NM_STRDUP_ENUM_m1, "-1"); + _NM_STRDUP_INT_TEST (TEST_NM_STRDUP_ENUM_3, "3"); +} + +/*****************************************************************************/ + +static void +test_nm_strndup_a (void) +{ + int run; + + for (run = 0; run < 20; run++) { + gs_free char *input = NULL; + char ch; + gsize i, l; + + input = g_strnfill (nmtst_get_rand_uint32 () % 20, 'x'); + + for (i = 0; input[i]; i++) { + while ((ch = ((char) nmtst_get_rand_uint32 ())) == '\0') { + /* repeat. */ + } + input[i] = ch; + } + + { + gs_free char *dup_free = NULL; + const char *dup; + + l = strlen (input) + 1; + dup = nm_strndup_a (10, input, l - 1, &dup_free); + g_assert_cmpstr (dup, ==, input); + if (strlen (dup) < 10) + g_assert (!dup_free); + else + g_assert (dup == dup_free); + } + + { + gs_free char *dup_free = NULL; + const char *dup; + + l = nmtst_get_rand_uint32 () % 23; + dup = nm_strndup_a (10, input, l, &dup_free); + g_assert (strncmp (dup, input, l) == 0); + g_assert (strlen (dup) <= l); + if (l < 10) + g_assert (!dup_free); + else + g_assert (dup == dup_free); + if (strlen (input) < l) + g_assert (nm_utils_memeqzero (&dup[strlen (input)], l - strlen (input))); + } + } +} + +/*****************************************************************************/ + +static void +test_nm_ip4_addr_is_localhost (void) +{ + g_assert ( nm_ip4_addr_is_localhost (nmtst_inet4_from_string ("127.0.0.0"))); + g_assert ( nm_ip4_addr_is_localhost (nmtst_inet4_from_string ("127.0.0.1"))); + g_assert ( nm_ip4_addr_is_localhost (nmtst_inet4_from_string ("127.5.0.1"))); + g_assert (!nm_ip4_addr_is_localhost (nmtst_inet4_from_string ("126.5.0.1"))); + g_assert (!nm_ip4_addr_is_localhost (nmtst_inet4_from_string ("128.5.0.1"))); + g_assert (!nm_ip4_addr_is_localhost (nmtst_inet4_from_string ("129.5.0.1"))); +} + +/*****************************************************************************/ + +static void +test_unaligned (void) +{ + int shift; + + for (shift = 0; shift <= 32; shift++) { + guint8 buf[100] = { }; + guint8 val = 0; + + while (val == 0) + val = nmtst_get_rand_uint32 () % 256; + + buf[shift] = val; + + g_assert_cmpint (unaligned_read_le64 (&buf[shift]), ==, (guint64) val); + g_assert_cmpint (unaligned_read_be64 (&buf[shift]), ==, ((guint64) val) << 56); + g_assert_cmpint (unaligned_read_ne64 (&buf[shift]), !=, 0); + + g_assert_cmpint (unaligned_read_le32 (&buf[shift]), ==, (guint32) val); + g_assert_cmpint (unaligned_read_be32 (&buf[shift]), ==, ((guint32) val) << 24); + g_assert_cmpint (unaligned_read_ne32 (&buf[shift]), !=, 0); + + g_assert_cmpint (unaligned_read_le16 (&buf[shift]), ==, (guint16) val); + g_assert_cmpint (unaligned_read_be16 (&buf[shift]), ==, ((guint16) val) << 8); + g_assert_cmpint (unaligned_read_ne16 (&buf[shift]), !=, 0); + } +} + +/*****************************************************************************/ + +static void +_strv_cmp_fuzz_input (const char *const*in, + gssize l, + const char ***out_strv_free_shallow, + char ***out_strv_free_deep, + const char *const* *out_s1, + const char *const* *out_s2) +{ + const char **strv; + gsize i; + + /* Fuzz the input argument. It will return two output arrays that are semantically + * equal the input. */ + + if (nmtst_get_rand_bool ()) { + char **ss; + + if (l < 0) + ss = g_strdupv ((char **) in); + else if (l == 0) { + ss = nmtst_get_rand_bool () + ? NULL + : g_new0 (char *, 1); + } else { + ss = nm_memdup (in, sizeof (const char *) * l); + for (i = 0; i < (gsize) l; i++) + ss[i] = g_strdup (ss[i]); + } + strv = (const char **) ss; + *out_strv_free_deep = ss; + } else { + if (l < 0) { + strv = in + ? nm_memdup (in, sizeof (const char *) * (NM_PTRARRAY_LEN (in) + 1)) + : NULL; + } else if (l == 0) { + strv = nmtst_get_rand_bool () + ? NULL + : g_new0 (const char *, 1); + } else + strv = nm_memdup (in, sizeof (const char *) * l); + *out_strv_free_shallow = strv; + } + + *out_s1 = in; + *out_s2 = strv; + + if (nmtst_get_rand_bool ()) { + /* randomly swap the original and the clone. That means, out_s1 is either + * the input argument (as-is) or the sementically equal clone. */ + NM_SWAP (*out_s1, *out_s2); + } + if (nmtst_get_rand_bool ()) { + /* randomly make s1 and s2 the same. This is for testing that + * comparing two identical pointers yields the same result. */ + *out_s2 = *out_s1; + } +} + +static void +_strv_cmp_free_deep (char **strv, + gssize len) +{ + gssize i; + + if (strv) { + if (len < 0) + g_strfreev (strv); + else { + for (i = 0; i < len; i++) + g_free (strv[i]); + g_free (strv); + } + } +} + +static void +test_strv_cmp (void) +{ + const char *const strv0[1] = { }; + const char *const strv1[2] = { "", }; + +#define _STRV_CMP(a1, l1, a2, l2, equal) \ + G_STMT_START { \ + gssize _l1 = (l1); \ + gssize _l2 = (l2); \ + const char *const*_a1; \ + const char *const*_a2; \ + const char *const*_a1x; \ + const char *const*_a2x; \ + char **_a1_free_deep = NULL; \ + char **_a2_free_deep = NULL; \ + gs_free const char **_a1_free_shallow = NULL; \ + gs_free const char **_a2_free_shallow = NULL; \ + int _c1, _c2; \ + \ + _strv_cmp_fuzz_input ((a1), _l1, &_a1_free_shallow, &_a1_free_deep, &_a1, &_a1x); \ + _strv_cmp_fuzz_input ((a2), _l2, &_a2_free_shallow, &_a2_free_deep, &_a2, &_a2x); \ + \ + _c1 = _nm_utils_strv_cmp_n (_a1, _l1, _a2, _l2); \ + _c2 = _nm_utils_strv_cmp_n (_a2, _l2, _a1, _l1); \ + if (equal) { \ + g_assert_cmpint (_c1, ==, 0); \ + g_assert_cmpint (_c2, ==, 0); \ + } else { \ + g_assert_cmpint (_c1, ==, -1); \ + g_assert_cmpint (_c2, ==, 1); \ + } \ + \ + /* Compare with self. _strv_cmp_fuzz_input() randomly swapped the arguments (_a1 and _a1x). + * Either way, the arrays must compare equal to their semantically equal alternative. */ \ + g_assert_cmpint (_nm_utils_strv_cmp_n (_a1, _l1, _a1x, _l1), ==, 0); \ + g_assert_cmpint (_nm_utils_strv_cmp_n (_a2, _l2, _a2x, _l2), ==, 0); \ + \ + _strv_cmp_free_deep (_a1_free_deep, _l1); \ + _strv_cmp_free_deep (_a2_free_deep, _l2); \ + } G_STMT_END + + _STRV_CMP (NULL, -1, NULL, -1, TRUE); + + _STRV_CMP (NULL, -1, NULL, 0, FALSE); + _STRV_CMP (NULL, -1, strv0, 0, FALSE); + _STRV_CMP (NULL, -1, strv0, -1, FALSE); + + _STRV_CMP (NULL, 0, NULL, 0, TRUE); + _STRV_CMP (NULL, 0, strv0, 0, TRUE); + _STRV_CMP (NULL, 0, strv0, -1, TRUE); + _STRV_CMP (strv0, 0, strv0, 0, TRUE); + _STRV_CMP (strv0, 0, strv0, -1, TRUE); + _STRV_CMP (strv0, -1, strv0, -1, TRUE); + + _STRV_CMP (NULL, 0, strv1, -1, FALSE); + _STRV_CMP (NULL, 0, strv1, 1, FALSE); + _STRV_CMP (strv0, 0, strv1, -1, FALSE); + _STRV_CMP (strv0, 0, strv1, 1, FALSE); + _STRV_CMP (strv0, -1, strv1, -1, FALSE); + _STRV_CMP (strv0, -1, strv1, 1, FALSE); + + _STRV_CMP (strv1, -1, strv1, 1, TRUE); + _STRV_CMP (strv1, 1, strv1, 1, TRUE); +} + +/*****************************************************************************/ + +static void +_do_strstrip_avoid_copy (const char *str) +{ + gs_free char *str1 = g_strdup (str); + gs_free char *str2 = g_strdup (str); + gs_free char *str3 = NULL; + gs_free char *str4 = NULL; + const char *s3; + const char *s4; + + if (str1) + g_strstrip (str1); + + nm_strstrip (str2); + + g_assert_cmpstr (str1, ==, str2); + + s3 = nm_strstrip_avoid_copy (str, &str3); + g_assert_cmpstr (str1, ==, s3); + + s4 = nm_strstrip_avoid_copy_a (10, str, &str4); + g_assert_cmpstr (str1, ==, s4); + g_assert (!str == !s4); + g_assert (!s4 || strlen (s4) <= strlen (str)); + if (s4 && s4 == &str[strlen (str) - strlen (s4)]) { + g_assert (!str4); + g_assert (s3 == s4); + } else if (s4 && strlen (s4) >= 10) { + g_assert (str4); + g_assert (s4 == str4); + } else + g_assert (!str4); + + if (!nm_streq0 (str1, str)) + _do_strstrip_avoid_copy (str1); +} + +static void +test_strstrip_avoid_copy (void) +{ + _do_strstrip_avoid_copy (NULL); + _do_strstrip_avoid_copy (""); + _do_strstrip_avoid_copy (" "); + _do_strstrip_avoid_copy (" a "); + _do_strstrip_avoid_copy (" 012345678 "); + _do_strstrip_avoid_copy (" 0123456789 "); + _do_strstrip_avoid_copy (" 01234567890 "); + _do_strstrip_avoid_copy (" 012345678901 "); +} + +/*****************************************************************************/ + +static void +test_nm_utils_bin2hexstr (void) +{ + int n_run; + + for (n_run = 0; n_run < 100; n_run++) { + guint8 buf[100]; + guint8 buf2[G_N_ELEMENTS (buf) + 1]; + gsize len = nmtst_get_rand_uint32 () % (G_N_ELEMENTS (buf) + 1); + char strbuf1[G_N_ELEMENTS (buf) * 3]; + gboolean allocate = nmtst_get_rand_bool (); + char delimiter = nmtst_get_rand_bool () ? ':' : '\0'; + gboolean upper_case = nmtst_get_rand_bool (); + gsize expected_strlen; + char *str_hex; + gsize required_len; + gboolean outlen_set; + gsize outlen; + guint8 *bin2; + + nmtst_rand_buf (NULL, buf, len); + + if (len == 0) + expected_strlen = 0; + else if (delimiter != '\0') + expected_strlen = (len * 3u) - 1; + else + expected_strlen = len * 2u; + + g_assert_cmpint (expected_strlen, <, G_N_ELEMENTS (strbuf1)); + + str_hex = nm_utils_bin2hexstr_full (buf, len, delimiter, upper_case, !allocate ? strbuf1 : NULL); + + g_assert (str_hex); + if (!allocate) + g_assert (str_hex == strbuf1); + g_assert_cmpint (strlen (str_hex), ==, expected_strlen); + + g_assert (NM_STRCHAR_ALL (str_hex, ch, (ch >= '0' && ch <= '9') + || ch == delimiter + || ( upper_case + ? (ch >= 'A' && ch <= 'F') + : (ch >= 'a' && ch <= 'f')))); + + required_len = nmtst_get_rand_bool () ? len : 0u; + + outlen_set = required_len == 0 || nmtst_get_rand_bool (); + + memset (buf2, 0, sizeof (buf2)); + + bin2 = nm_utils_hexstr2bin_full (str_hex, + nmtst_get_rand_bool (), + delimiter != '\0' && nmtst_get_rand_bool (), + delimiter != '\0' + ? nmtst_rand_select ((const char *) ":", ":-") + : nmtst_rand_select ((const char *) ":", ":-", "", NULL), + required_len, + buf2, + len, + outlen_set ? &outlen : NULL); + if (len > 0) { + g_assert (bin2); + g_assert (bin2 == buf2); + } else + g_assert (!bin2); + + if (outlen_set) + g_assert_cmpint (outlen, ==, len); + + g_assert_cmpmem (buf, len, buf2, len); + + g_assert (buf2[len] == '\0'); + + if (allocate) + g_free (str_hex); + } +} + +/*****************************************************************************/ + +static void +test_nm_ref_string (void) +{ + nm_auto_ref_string NMRefString *s1 = NULL; + NMRefString *s2; + + s1 = nm_ref_string_new ("hallo"); + g_assert (s1); + g_assert_cmpstr (s1->str, ==, "hallo"); + g_assert_cmpint (s1->len, ==, strlen ("hallo")); + + s2 = nm_ref_string_new ("hallo"); + g_assert (s2 == s1); + nm_ref_string_unref (s2); + + s2 = nm_ref_string_new (NULL); + g_assert (!s2); + nm_ref_string_unref (s2); + +#define STR_WITH_NUL "hallo\0test\0" + s2 = nm_ref_string_new_len (STR_WITH_NUL, NM_STRLEN (STR_WITH_NUL)); + g_assert (s2); + g_assert_cmpstr (s2->str, ==, "hallo"); + g_assert_cmpint (s2->len, ==, NM_STRLEN (STR_WITH_NUL)); + g_assert_cmpint (s2->len, >, strlen (s2->str)); + g_assert_cmpmem (s2->str, s2->len, STR_WITH_NUL, NM_STRLEN (STR_WITH_NUL)); + g_assert (s2->str[s2->len] == '\0'); + nm_ref_string_unref (s2); +} + +/*****************************************************************************/ + +static +NM_UTILS_STRING_TABLE_LOOKUP_DEFINE ( + _do_string_table_lookup, + int, + { ; }, + { return -1; }, + { "0", 0 }, + { "1", 1 }, + { "2", 2 }, + { "3", 3 }, +) + +static void +test_string_table_lookup (void) +{ + const char *const args[] = { NULL, "0", "1", "2", "3", "x", }; + int i; + + for (i = 0; i < G_N_ELEMENTS (args); i++) { + const char *needle = args[i]; + const int val2 = _nm_utils_ascii_str_to_int64 (needle, 10, 0, 100, -1); + int val; + + val = _do_string_table_lookup (needle); + g_assert_cmpint (val, ==, val2); + } +} + +/*****************************************************************************/ + +static void +test_nm_utils_get_next_realloc_size (void) +{ + static const struct { + gsize requested; + gsize reserved_true; + gsize reserved_false; + } test_data[] = { + { 0, 8, 8 }, + { 1, 8, 8 }, + { 8, 8, 8 }, + { 9, 16, 16 }, + { 16, 16, 16 }, + { 17, 32, 32 }, + { 32, 32, 32 }, + { 33, 40, 40 }, + { 40, 40, 40 }, + { 41, 104, 104 }, + { 104, 104, 104 }, + { 105, 232, 232 }, + { 232, 232, 232 }, + { 233, 488, 488 }, + { 488, 488, 488 }, + { 489, 1000, 1000 }, + { 1000, 1000, 1000 }, + { 1001, 2024, 2024 }, + { 2024, 2024, 2024 }, + { 2025, 4072, 4072 }, + { 4072, 4072, 4072 }, + { 4073, 8168, 8168 }, + { 8168, 8168, 8168 }, + { 8169, 12264, 16360 }, + { 12263, 12264, 16360 }, + { 12264, 12264, 16360 }, + { 12265, 16360, 16360 }, + { 16360, 16360, 16360 }, + { 16361, 20456, 32744 }, + { 20456, 20456, 32744 }, + { 20457, 24552, 32744 }, + { 24552, 24552, 32744 }, + { 24553, 28648, 32744 }, + { 28648, 28648, 32744 }, + { 28649, 32744, 32744 }, + { 32744, 32744, 32744 }, + { 32745, 36840, 65512 }, + { 36840, 36840, 65512 }, + { G_MAXSIZE - 0x1000u, G_MAXSIZE, G_MAXSIZE }, + { G_MAXSIZE - 25u, G_MAXSIZE, G_MAXSIZE }, + { G_MAXSIZE - 24u, G_MAXSIZE, G_MAXSIZE }, + { G_MAXSIZE - 1u, G_MAXSIZE, G_MAXSIZE }, + { G_MAXSIZE, G_MAXSIZE, G_MAXSIZE }, + { NM_UTILS_GET_NEXT_REALLOC_SIZE_104, NM_UTILS_GET_NEXT_REALLOC_SIZE_104, NM_UTILS_GET_NEXT_REALLOC_SIZE_104 }, + { NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, NM_UTILS_GET_NEXT_REALLOC_SIZE_1000 }, + }; + guint i; + + G_STATIC_ASSERT_EXPR (NM_UTILS_GET_NEXT_REALLOC_SIZE_104 == 104u); + G_STATIC_ASSERT_EXPR (NM_UTILS_GET_NEXT_REALLOC_SIZE_1000 == 1000u); + + for (i = 0; i < G_N_ELEMENTS (test_data) + 5000u; i++) { + gsize requested0; + + if (i < G_N_ELEMENTS (test_data)) + requested0 = test_data[i].requested; + else { + /* find some interesting random values for testing. */ + switch (nmtst_get_rand_uint32 () % 5) { + case 0: + requested0 = nmtst_get_rand_size (); + break; + case 1: + /* values close to G_MAXSIZE. */ + requested0 = G_MAXSIZE - (nmtst_get_rand_uint32 () % 12000u); + break; + case 2: + /* values around G_MAXSIZE/2. */ + requested0 = (G_MAXSIZE / 2u) + 6000u - (nmtst_get_rand_uint32 () % 12000u); + break; + case 3: + /* values around powers of 2. */ + requested0 = (((gsize) 1) << (nmtst_get_rand_uint32 () % (sizeof (gsize) * 8u))) + 6000u - (nmtst_get_rand_uint32 () % 12000u); + break; + case 4: + /* values around 4k borders. */ + requested0 = (nmtst_get_rand_size () & ~((gsize) 0xFFFu)) + 30u - (nmtst_get_rand_uint32 () % 60u); + break; + default: g_assert_not_reached (); + } + } + + { + const gsize requested = requested0; + const gsize reserved_true = nm_utils_get_next_realloc_size (TRUE, requested); + const gsize reserved_false = nm_utils_get_next_realloc_size (FALSE, requested); + + g_assert_cmpuint (reserved_true, >, 0); + g_assert_cmpuint (reserved_false, >, 0); + g_assert_cmpuint (reserved_true, >=, requested); + g_assert_cmpuint (reserved_false, >=, requested); + g_assert_cmpuint (reserved_false, >=, reserved_true); + + if (i < G_N_ELEMENTS (test_data)) { + g_assert_cmpuint (reserved_true, ==, test_data[i].reserved_true); + g_assert_cmpuint (reserved_false, ==, test_data[i].reserved_false); + } + + /* reserved_false is generally the next power of two - 24. */ + if (reserved_false == G_MAXSIZE) + g_assert_cmpuint (requested, >, G_MAXSIZE / 2u - 24u); + else { + g_assert_cmpuint (reserved_false, <=, G_MAXSIZE - 24u); + if (reserved_false >= 40) { + const gsize _pow2 = reserved_false + 24u; + + /* reserved_false must always be a power of two minus 24. */ + g_assert_cmpuint (_pow2, >=, 64u); + g_assert_cmpuint (_pow2, >, requested); + g_assert (nm_utils_is_power_of_two (_pow2)); + + /* but _pow2/2 must also be smaller than what we requested. */ + g_assert_cmpuint (_pow2 / 2u - 24u, <, requested); + } else { + /* smaller values are hard-coded. */ + } + } + + /* reserved_true is generally the next 4k border - 24. */ + if (reserved_true == G_MAXSIZE) + g_assert_cmpuint (requested, >, G_MAXSIZE - 0x1000u - 24u); + else { + g_assert_cmpuint (reserved_true, <=, G_MAXSIZE - 24u); + if (reserved_true > 8168u) { + const gsize page_border = reserved_true + 24u; + + /* reserved_true must always be aligned to 4k (minus 24). */ + g_assert_cmpuint (page_border % 0x1000u, ==, 0); + if (requested > 0x1000u - 24u) { + /* page_border not be more than 4k above requested. */ + g_assert_cmpuint (page_border, >=, 0x1000u - 24u); + g_assert_cmpuint (page_border - 0x1000u - 24u, <, requested); + } + } else { + /* for smaller sizes, reserved_true and reserved_false are the same. */ + g_assert_cmpuint (reserved_true, ==, reserved_false); + } + } + + } + } +} + +/*****************************************************************************/ + +static void +test_nm_str_buf (void) +{ + guint i_run; + + for (i_run = 0; TRUE; i_run++) { + nm_auto_str_buf NMStrBuf strbuf = { }; + nm_auto_free_gstring GString *gstr = NULL; + int i, j, k; + int c; + + nm_str_buf_init (&strbuf, + nmtst_get_rand_uint32 () % 200u + 1u, + nmtst_get_rand_bool ()); + + if (i_run < 1000) { + c = nmtst_get_rand_word_length (NULL); + for (i = 0; i < c; i++) + nm_str_buf_append_c (&strbuf, '0' + (i % 10)); + gstr = g_string_new (nm_str_buf_get_str (&strbuf)); + j = nmtst_get_rand_uint32 () % (strbuf.len + 1); + k = nmtst_get_rand_uint32 () % (strbuf.len - j + 2) - 1; + + nm_str_buf_erase (&strbuf, j, k, nmtst_get_rand_bool ()); + g_string_erase (gstr, j, k); + g_assert_cmpstr (gstr->str, ==, nm_str_buf_get_str (&strbuf)); + } else + return; + } +} + +/*****************************************************************************/ + +NMTST_DEFINE (); + +int main (int argc, char **argv) +{ + nmtst_init (&argc, &argv, TRUE); + + g_test_add_func ("/general/test_gpid", test_gpid); + g_test_add_func ("/general/test_monotonic_timestamp", test_monotonic_timestamp); + g_test_add_func ("/general/test_nmhash", test_nmhash); + g_test_add_func ("/general/test_nm_make_strv", test_make_strv); + g_test_add_func ("/general/test_nm_strdup_int", test_nm_strdup_int); + g_test_add_func ("/general/test_nm_strndup_a", test_nm_strndup_a); + g_test_add_func ("/general/test_nm_ip4_addr_is_localhost", test_nm_ip4_addr_is_localhost); + g_test_add_func ("/general/test_unaligned", test_unaligned); + g_test_add_func ("/general/test_strv_cmp", test_strv_cmp); + g_test_add_func ("/general/test_strstrip_avoid_copy", test_strstrip_avoid_copy); + g_test_add_func ("/general/test_nm_utils_bin2hexstr", test_nm_utils_bin2hexstr); + g_test_add_func ("/general/test_nm_ref_string", test_nm_ref_string); + g_test_add_func ("/general/test_string_table_lookup", test_string_table_lookup); + g_test_add_func ("/general/test_nm_utils_get_next_realloc_size", test_nm_utils_get_next_realloc_size); + g_test_add_func ("/general/test_nm_str_buf", test_nm_str_buf); + + return g_test_run (); +} |