diff options
| author | Michael Biebl <biebl@debian.org> | 2023-02-10 11:50:34 +0100 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2023-02-10 11:50:34 +0100 |
| commit | 1372848511cb896b80b51ed1a3e9606bd9816631 (patch) | |
| tree | 674792b9385bdef935988894b45f06b2af39f88c /src/libnm-glib-aux | |
| parent | 40ec077ea305994c1fc2130add6787ca0c73e2c6 (diff) | |
New upstream version 1.42.0 upstream/1.42.0
Diffstat (limited to 'src/libnm-glib-aux')
28 files changed, 2815 insertions, 1349 deletions
diff --git a/src/libnm-glib-aux/meson.build b/src/libnm-glib-aux/meson.build index 4f13fd0b..afd86871 100644 --- a/src/libnm-glib-aux/meson.build +++ b/src/libnm-glib-aux/meson.build @@ -8,10 +8,12 @@ libnm_glib_aux = static_library( 'nm-enum-utils.c', 'nm-errno.c', 'nm-hash-utils.c', + 'nm-inet-utils.c', 'nm-io-utils.c', 'nm-json-aux.c', 'nm-keyfile-aux.c', 'nm-logging-base.c', + 'nm-prioq.c', 'nm-random-utils.c', 'nm-ref-string.c', 'nm-secret-utils.c', diff --git a/src/libnm-glib-aux/nm-dbus-aux.c b/src/libnm-glib-aux/nm-dbus-aux.c index 0f43ba8e..3925da55 100644 --- a/src/libnm-glib-aux/nm-dbus-aux.c +++ b/src/libnm-glib-aux/nm-dbus-aux.c @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ /* - * Copyright (C) 2019 Red Hat, Inc. + * Copyright (C) 2015,2019 Red Hat, Inc. */ #include "libnm-glib-aux/nm-default-glib-i18n-lib.h" @@ -97,6 +97,33 @@ nm_dbus_connection_call_get_all(GDBusConnection *dbus_connection, } void +nm_dbus_connection_call_get(GDBusConnection *dbus_connection, + const char *bus_name, + const char *object_path, + const char *interface_name, + const char *property_name, + int timeout_msec, + GCancellable *cancellable, + NMDBusConnectionCallDefaultCb callback, + gpointer user_data) +{ + nm_assert(callback); + + g_dbus_connection_call(dbus_connection, + bus_name, + object_path, + DBUS_INTERFACE_PROPERTIES, + "Get", + g_variant_new("(s)", interface_name, property_name), + G_VARIANT_TYPE("(v)"), + G_DBUS_CALL_FLAGS_NONE, + timeout_msec, + cancellable, + _nm_dbus_connection_call_default_cb, + nm_utils_user_data_pack(user_data, callback)); +} + +void nm_dbus_connection_call_set(GDBusConnection *dbus_connection, const char *bus_name, const char *object_path, @@ -387,6 +414,12 @@ _nm_dbus_error_is(GError *error, ...) gs_free char *dbus_error = NULL; const char *name; va_list ap; + gboolean found = FALSE; + + /* This should only be used for "foreign" D-Bus errors (eg, errors + * from BlueZ or wpa_supplicant). All NetworkManager D-Bus errors + * should be properly mapped by gdbus to one of the domains/codes in + * nm-errors.h. */ dbus_error = g_dbus_error_get_remote_error(error); if (!dbus_error) @@ -395,13 +428,13 @@ _nm_dbus_error_is(GError *error, ...) va_start(ap, error); while ((name = va_arg(ap, const char *))) { if (nm_streq(dbus_error, name)) { - va_end(ap); - return TRUE; + found = TRUE; + break; } } va_end(ap); - return FALSE; + return found; } /*****************************************************************************/ @@ -490,3 +523,194 @@ nm_dbus_connection_call_blocking(NMDBusConnectionCallBlockingData *data, GError return g_steal_pointer(&result); } + +/*****************************************************************************/ + +typedef struct { + char *signal_name; + const GVariantType *signature; +} NMDBusSignalData; + +static void +dbus_signal_data_free(gpointer data, GClosure *closure) +{ + NMDBusSignalData *sd = data; + + g_free(sd->signal_name); + g_slice_free(NMDBusSignalData, sd); +} + +static void +dbus_signal_meta_marshal(GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + NMDBusSignalData *sd = marshal_data; + const char *signal_name; + GVariant *parameters; + gs_free GValue *closure_params_free = NULL; + GValue *closure_params; + gsize n_params; + gsize i; + + g_return_if_fail(n_param_values == 4); + + signal_name = g_value_get_string(¶m_values[2]); + parameters = g_value_get_variant(¶m_values[3]); + + if (!nm_streq(signal_name, sd->signal_name)) + return; + + if (sd->signature) { + if (!g_variant_is_of_type(parameters, sd->signature)) { + g_warning("%p: got signal '%s' but parameters were of type '%s', not '%s'", + g_value_get_object(¶m_values[0]), + signal_name, + g_variant_get_type_string(parameters), + g_variant_type_peek_string(sd->signature)); + return; + } + + n_params = g_variant_n_children(parameters) + 1; + } else + n_params = 1; + + closure_params = nm_malloc0_maybe_a(240, sizeof(GValue) * n_params, &closure_params_free); + g_value_init(&closure_params[0], G_TYPE_OBJECT); + g_value_copy(¶m_values[0], &closure_params[0]); + + for (i = 1; i < n_params; i++) { + gs_unref_variant GVariant *param = NULL; + + param = g_variant_get_child_value(parameters, i - 1); + if (g_variant_is_of_type(param, G_VARIANT_TYPE("ay")) + || g_variant_is_of_type(param, G_VARIANT_TYPE("aay"))) { + /* g_dbus_gvariant_to_gvalue() thinks 'ay' means "non-UTF-8 NUL-terminated string" */ + g_value_init(&closure_params[i], G_TYPE_VARIANT); + g_value_set_variant(&closure_params[i], param); + } else + g_dbus_gvariant_to_gvalue(param, &closure_params[i]); + } + + g_cclosure_marshal_generic(closure, NULL, n_params, closure_params, invocation_hint, NULL); + + for (i = 0; i < n_params; i++) + g_value_unset(&closure_params[i]); +} + +/** + * _nm_dbus_proxy_signal_connect_data: + * @proxy: a #GDBusProxy + * @signal_name: the D-Bus signal to connect to + * @signature: (allow-none): the signal's type signature (must be a tuple) + * @c_handler: the signal handler function + * @data: (allow-none): data to pass to @c_handler + * @destroy_data: (allow-none): closure destroy notify for @data + * @connect_flags: connection flags + * + * Connects to the D-Bus signal @signal_name on @proxy. @c_handler must be a + * void function whose first argument is a #GDBusProxy, followed by arguments + * for each element of @signature, ending with a #gpointer argument for @data. + * + * The argument types in @c_handler correspond to the types output by + * g_dbus_gvariant_to_gvalue(), except for 'ay' and 'aay'. In particular: + * - both 16-bit and 32-bit integers are passed as #int/#guint + * - 'as' values are passed as #GStrv (char **) + * - all other array, tuple, and dict types are passed as #GVariant + * + * If @signature is %NULL, then the signal's parameters will be ignored, and + * @c_handler should take only the #GDBusProxy and #gpointer arguments. + * + * Returns: the signal handler ID, which can be used with + * g_signal_handler_remove(). Beware that because of the way the signal is + * connected, you will not be able to remove it with + * g_signal_handlers_disconnect_by_func(), although + * g_signal_handlers_disconnect_by_data() will work correctly. + */ +gulong +_nm_dbus_proxy_signal_connect_data(GDBusProxy *proxy, + const char *signal_name, + const GVariantType *signature, + GCallback c_handler, + gpointer data, + GClosureNotify destroy_data, + GConnectFlags connect_flags) +{ + NMDBusSignalData *sd; + GClosure *closure; + gboolean swapped = !!(connect_flags & G_CONNECT_SWAPPED); + gboolean after = !!(connect_flags & G_CONNECT_AFTER); + + g_return_val_if_fail(G_IS_DBUS_PROXY(proxy), 0); + g_return_val_if_fail(signal_name != NULL, 0); + g_return_val_if_fail(signature == NULL || g_variant_type_is_tuple(signature), 0); + g_return_val_if_fail(c_handler != NULL, 0); + + sd = g_slice_new(NMDBusSignalData); + sd->signal_name = g_strdup(signal_name); + sd->signature = signature; + + closure = (swapped ? g_cclosure_new_swap : g_cclosure_new)(c_handler, data, destroy_data); + g_closure_set_marshal(closure, g_cclosure_marshal_generic); + g_closure_set_meta_marshal(closure, sd, dbus_signal_meta_marshal); + g_closure_add_finalize_notifier(closure, sd, dbus_signal_data_free); + + return g_signal_connect_closure(proxy, "g-signal", closure, after); +} + +/*****************************************************************************/ + +static gboolean +_nm_dbus_typecheck_response(GVariant *response, const GVariantType *reply_type, GError **error) +{ + g_return_val_if_fail(response, FALSE); + + if (!reply_type) + return TRUE; + if (g_variant_is_of_type(response, reply_type)) + return TRUE; + + /* This is the same error code that g_dbus_connection_call() returns if + * @reply_type doesn't match. + */ + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Method returned type '%s', but expected '%s'"), + g_variant_get_type_string(response), + g_variant_type_peek_string(reply_type)); + return FALSE; +} + +/** + * _nm_dbus_proxy_call_finish: + * @proxy: A #GDBusProxy. + * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to + * g_dbus_proxy_call(). + * @reply_type: (allow-none): the expected type of the reply, or %NULL + * @error: Return location for error or %NULL. + * + * Finishes an operation started with g_dbus_proxy_call(), as with + * g_dbus_proxy_call_finish(), except thatif @reply_type is non-%NULL, then it + * will also check that the response matches that type signature, and return + * an error if not. + * + * Returns: %NULL if @error is set. Otherwise, a #GVariant tuple with + * return values. Free with g_variant_unref(). + */ +GVariant * +_nm_dbus_proxy_call_finish(GDBusProxy *proxy, + GAsyncResult *res, + const GVariantType *reply_type, + GError **error) +{ + GVariant *variant; + + variant = g_dbus_proxy_call_finish(proxy, res, error); + if (variant && !_nm_dbus_typecheck_response(variant, reply_type, error)) + nm_clear_pointer(&variant, g_variant_unref); + return variant; +} diff --git a/src/libnm-glib-aux/nm-dbus-aux.h b/src/libnm-glib-aux/nm-dbus-aux.h index dfba61b0..5db79c08 100644 --- a/src/libnm-glib-aux/nm-dbus-aux.h +++ b/src/libnm-glib-aux/nm-dbus-aux.h @@ -147,6 +147,16 @@ void nm_dbus_connection_call_get_all(GDBusConnection *dbus_connecti NMDBusConnectionCallDefaultCb callback, gpointer user_data); +void nm_dbus_connection_call_get(GDBusConnection *dbus_connection, + const char *bus_name, + const char *object_path, + const char *interface_name, + const char *property_name, + int timeout_msec, + GCancellable *cancellable, + NMDBusConnectionCallDefaultCb callback, + gpointer user_data); + void nm_dbus_connection_call_set(GDBusConnection *dbus_connection, const char *bus_name, const char *object_path, @@ -264,4 +274,40 @@ nm_g_variant_tuple_get_u(GVariant *v, guint32 *out_u) return FALSE; } +/*****************************************************************************/ + +gulong _nm_dbus_proxy_signal_connect_data(GDBusProxy *proxy, + const char *signal_name, + const GVariantType *signature, + GCallback c_handler, + gpointer data, + GClosureNotify destroy_data, + GConnectFlags connect_flags); + +/** + * _nm_dbus_proxy_signal_connect: + * @proxy: a #GDBusProxy + * @signal_name: the D-Bus signal to connect to + * @signature: the signal's type signature (must be a tuple) + * @c_handler: the signal handler function + * @data: (allow-none): data to pass to @c_handler + * + * Simplified version of _nm_dbus_proxy_signal_connect_data() with fewer arguments. + * + * Returns: the signal handler ID, as with _nm_signal_connect_data(). + */ +#define _nm_dbus_proxy_signal_connect(proxy, name, signature, handler, data) \ + _nm_dbus_proxy_signal_connect_data(proxy, \ + name, \ + signature, \ + handler, \ + data, \ + NULL, \ + (GConnectFlags) 0) + +GVariant *_nm_dbus_proxy_call_finish(GDBusProxy *proxy, + GAsyncResult *res, + const GVariantType *reply_type, + GError **error); + #endif /* __NM_DBUS_AUX_H__ */ diff --git a/src/libnm-glib-aux/nm-dedup-multi.c b/src/libnm-glib-aux/nm-dedup-multi.c index 7d451d0d..625c4ef3 100644 --- a/src/libnm-glib-aux/nm-dedup-multi.c +++ b/src/libnm-glib-aux/nm-dedup-multi.c @@ -1004,12 +1004,14 @@ nm_dedup_multi_index_new(void) { NMDedupMultiIndex *self; - self = g_slice_new0(NMDedupMultiIndex); - self->ref_count = 1; - self->idx_entries = - g_hash_table_new((GHashFunc) _dict_idx_entries_hash, (GEqualFunc) _dict_idx_entries_equal); - self->idx_objs = - g_hash_table_new((GHashFunc) _dict_idx_objs_hash, (GEqualFunc) _dict_idx_objs_equal); + self = g_slice_new(NMDedupMultiIndex); + *self = (NMDedupMultiIndex){ + .ref_count = 1, + .idx_entries = g_hash_table_new((GHashFunc) _dict_idx_entries_hash, + (GEqualFunc) _dict_idx_entries_equal), + .idx_objs = + g_hash_table_new((GHashFunc) _dict_idx_objs_hash, (GEqualFunc) _dict_idx_objs_equal), + }; return self; } @@ -1018,6 +1020,7 @@ nm_dedup_multi_index_ref(NMDedupMultiIndex *self) { g_return_val_if_fail(self, NULL); g_return_val_if_fail(self->ref_count > 0, NULL); + nm_assert(self->ref_count < G_MAXINT32); self->ref_count++; return self; diff --git a/src/libnm-glib-aux/nm-dedup-multi.h b/src/libnm-glib-aux/nm-dedup-multi.h index 0fa47422..87a2b815 100644 --- a/src/libnm-glib-aux/nm-dedup-multi.h +++ b/src/libnm-glib-aux/nm-dedup-multi.h @@ -37,6 +37,8 @@ typedef enum _NMDedupMultiIdxMode { /*****************************************************************************/ +#define _NMDedupMultiObj_Align (MAX(_nm_alignof(void *), _nm_alignof(gint64))) + struct _NMDedupMultiObj { union { NMObjBaseInst parent; @@ -44,7 +46,7 @@ struct _NMDedupMultiObj { }; NMDedupMultiIndex *_multi_idx; guint _ref_count; -}; +} _nm_align(_NMDedupMultiObj_Align); struct _NMDedupMultiObjClass { NMObjBaseClass parent; @@ -101,21 +103,21 @@ nm_dedup_multi_index_obj_find(NMDedupMultiIndex *self, * The NMDedupMultiIdxTypeClass determines its behavior, but you can have * multiple instances (of the same class). * - * For example, NMIP4Config can have idx-type to put there all IPv4 Routes. - * This idx-type instance is private to the NMIP4Config instance. Basically, - * the NMIP4Config instance uses the idx-type to maintain an ordered list + * For example, NML3ConfigData can have idx-type to put there all IPv4 Routes. + * This idx-type instance is private to the NML3ConfigData instance. Basically, + * the NML3ConfigData instance uses the idx-type to maintain an ordered list * of routes in NMDedupMultiIndex. * * However, a NMDedupMultiIdxType may also partition the set of objects - * in multiple distinct lists. NMIP4Config doesn't do that (because instead - * of creating one idx-type for IPv4 and IPv6 routes, it just cretaes - * to distinct idx-types, one for each address family. + * in multiple distinct lists. NML3ConfigData doesn't do that (because instead + * of creating one idx-type for IPv4 and IPv6 routes, it just creates + * distinct idx-types, one for each object type and address family. * This partitioning is used by NMPlatform to maintain a lookup index for * routes by ifindex. As the ifindex is dynamic, it does not create an * idx-type instance for each ifindex. Instead, it has one idx-type for * all routes. But whenever accessing NMDedupMultiIndex with an NMDedupMultiObj, - * the partitioning NMDedupMultiIdxType takes into account the NMDedupMultiObj - * instance to associate it with the right list. + * the NMDedupMultiIdxType can partition the object and assign it to the right + * list. * * Hence, a NMDedupMultiIdxEntry has a list of possibly multiple NMDedupMultiHeadEntry * instances, which each is the head for a list of NMDedupMultiEntry instances. diff --git a/src/libnm-glib-aux/nm-default-glib.h b/src/libnm-glib-aux/nm-default-glib.h index f39e16cc..18460de8 100644 --- a/src/libnm-glib-aux/nm-default-glib.h +++ b/src/libnm-glib-aux/nm-default-glib.h @@ -67,6 +67,7 @@ #include "libnm-glib-aux/nm-shared-utils.h" #include "libnm-glib-aux/nm-errno.h" #include "libnm-glib-aux/nm-hash-utils.h" +#include "libnm-glib-aux/nm-inet-utils.h" /*****************************************************************************/ diff --git a/src/libnm-glib-aux/nm-glib.h b/src/libnm-glib-aux/nm-glib.h index 2dc86d7b..0436d135 100644 --- a/src/libnm-glib-aux/nm-glib.h +++ b/src/libnm-glib-aux/nm-glib.h @@ -45,28 +45,40 @@ __g_type_ensure(GType type) /*****************************************************************************/ -#if !GLIB_CHECK_VERSION(2, 34, 0) - -#define g_clear_pointer(pp, destroy) \ - G_STMT_START \ - { \ - G_STATIC_ASSERT(sizeof *(pp) == sizeof(gpointer)); \ - /* Only one access, please */ \ - gpointer *_pp = (gpointer *) (pp); \ - gpointer _p; \ - /* This assignment is needed to avoid a gcc warning */ \ - GDestroyNotify _destroy = (GDestroyNotify) (destroy); \ - \ - _p = *_pp; \ - if (_p) { \ - *_pp = NULL; \ - _destroy(_p); \ - } \ - } \ +/* glib 2.58+ defines an improved, type-safe variant of g_clear_pointer(). Reimplement + * that. + * + * Note that we also have nm_clear_pointer() which is similar to g_clear_pointer() + * and also preferred throughout. However, we do use g_clear_object(), which is + * implemented via g_clear_pointer(). So while there are no immediate users of + * g_clear_pointer() (use nm_clear_pointer() instead), there are indirect users + * via g_clear_object(). + * + * Anyway. So the glib 2.58+ version of g_clear_pointer() is only used if GLIB_VERSION_MAX_ALLOWED + * is set, which we don't do. + * + * To get the type-safe variant, reimplement g_clear_pointer() below. This aims to + * be identical to the version of the macro in glib 2.58. + * + * Still, don't use g_clear_pointer() directly (use nm_clear_pointer()). This compat + * implementation exists only for g_clear_object(). + */ +#undef g_clear_pointer + +#define g_clear_pointer(pp, destroy) \ + G_STMT_START \ + { \ + typeof((pp)) _pp = (pp); \ + typeof(*_pp) _ptr = *_pp; \ + \ + G_STATIC_ASSERT(sizeof *(pp) == sizeof(gpointer)); \ + \ + *_pp = NULL; \ + if (_ptr) \ + (destroy)(_ptr); \ + } \ G_STMT_END -#endif - /*****************************************************************************/ #if !GLIB_CHECK_VERSION(2, 34, 0) diff --git a/src/libnm-glib-aux/nm-hash-utils.c b/src/libnm-glib-aux/nm-hash-utils.c index 941aba01..973422a1 100644 --- a/src/libnm-glib-aux/nm-hash-utils.c +++ b/src/libnm-glib-aux/nm-hash-utils.c @@ -106,7 +106,7 @@ nm_hash_static(guint static_seed) * * Also, ensure that we don't return zero (like for nm_hash_complete()). */ - return ((*((const guint *) _get_hash_key())) ^ static_seed) ?: 3679500967u; + return ((*NM_CAST_ALIGN(guint, _get_hash_key())) ^ static_seed) ?: 3679500967u; } void diff --git a/src/libnm-glib-aux/nm-hash-utils.h b/src/libnm-glib-aux/nm-hash-utils.h index dffa828b..e2707fc2 100644 --- a/src/libnm-glib-aux/nm-hash-utils.h +++ b/src/libnm-glib-aux/nm-hash-utils.h @@ -105,15 +105,30 @@ nm_hash_update(NMHashState *state, const void *ptr, gsize n) c_siphash_append(&state->_state, ptr, n); } -#define nm_hash_update_val(state, val) \ +#define _NM_HASH_COMBINE_VALS_TYPE_OP(x, idx, op_arg) typeof(x) _v##idx; +#define _NM_HASH_COMBINE_VALS_INIT_OP(x, idx, op_arg) ._v##idx = (x), + +/* NM_HASH_COMBINE_VALS() is faster then nm_hash_update_val() as it combines multiple + * calls to nm_hash_update() using a packed structure. */ +#define NM_HASH_COMBINE_VALS(var, ...) \ + const struct _nm_packed { \ + NM_VA_ARGS_FOREACH(, , , _NM_HASH_COMBINE_VALS_TYPE_OP, , __VA_ARGS__) \ + } var _nm_alignas(max_align_t) = { \ + NM_VA_ARGS_FOREACH(, , , _NM_HASH_COMBINE_VALS_INIT_OP, , __VA_ARGS__)} + +/* nm_hash_update_vals() is faster then nm_hash_update_val() as it combines multiple + * calls to nm_hash_update() using a packed structure. */ +#define nm_hash_update_vals(state, ...) \ G_STMT_START \ { \ - typeof(val) _val = (val); \ + NM_HASH_COMBINE_VALS(_val, __VA_ARGS__); \ \ nm_hash_update((state), &_val, sizeof(_val)); \ } \ G_STMT_END +#define nm_hash_update_val(state, val) nm_hash_update_vals((state), (val)) + #define nm_hash_update_valp(state, val) nm_hash_update((state), (val), sizeof(*(val))) static inline void @@ -132,28 +147,6 @@ nm_hash_update_bool(NMHashState *state, bool val) #define nm_hash_update_bools(state, ...) \ nm_hash_update_val(state, NM_HASH_COMBINE_BOOLS(guint8, __VA_ARGS__)) -#define _NM_HASH_COMBINE_VALS_TYPE_OP(x, idx, op_arg) typeof(x) _v##idx; -#define _NM_HASH_COMBINE_VALS_INIT_OP(x, idx, op_arg) ._v##idx = (x), - -/* NM_HASH_COMBINE_VALS() is faster then nm_hash_update_val() as it combines multiple - * calls to nm_hash_update() using a packed structure. */ -#define NM_HASH_COMBINE_VALS(var, ...) \ - const struct _nm_packed { \ - NM_VA_ARGS_FOREACH(, , , _NM_HASH_COMBINE_VALS_TYPE_OP, , __VA_ARGS__) \ - } var _nm_alignas(max_align_t) = { \ - NM_VA_ARGS_FOREACH(, , , _NM_HASH_COMBINE_VALS_INIT_OP, , __VA_ARGS__)} - -/* nm_hash_update_vals() is faster then nm_hash_update_val() as it combines multiple - * calls to nm_hash_update() using a packed structure. */ -#define nm_hash_update_vals(state, ...) \ - G_STMT_START \ - { \ - NM_HASH_COMBINE_VALS(_val, __VA_ARGS__); \ - \ - nm_hash_update((state), &_val, sizeof(_val)); \ - } \ - G_STMT_END - static inline void nm_hash_update_mem(NMHashState *state, const void *ptr, gsize n) { @@ -206,15 +199,17 @@ guint nm_hash_ptr(gconstpointer ptr); guint nm_hash_str(const char *str); #define nm_str_hash ((guint(*)(gconstpointer str)) nm_hash_str) -#define nm_hash_val(static_seed, val) \ - ({ \ - NMHashState _h; \ - \ - nm_hash_init(&_h, (static_seed)); \ - nm_hash_update_val(&_h, (val)); \ - nm_hash_complete(&_h); \ +#define nm_hash_vals(static_seed, ...) \ + ({ \ + NMHashState _h; \ + \ + nm_hash_init(&_h, (static_seed)); \ + nm_hash_update_vals(&_h, __VA_ARGS__); \ + nm_hash_complete(&_h); \ }) +#define nm_hash_val(static_seed, val) nm_hash_vals((static_seed), (val)) + static inline guint nm_hash_mem(guint static_seed, const void *ptr, gsize n) { diff --git a/src/libnm-glib-aux/nm-inet-utils.c b/src/libnm-glib-aux/nm-inet-utils.c new file mode 100644 index 00000000..7f710f35 --- /dev/null +++ b/src/libnm-glib-aux/nm-inet-utils.c @@ -0,0 +1,529 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-inet-utils.h" + +#include <netinet/in.h> +#include <arpa/inet.h> + +/*****************************************************************************/ + +const NMIPAddr nm_ip_addr_zero = {}; + +/* We use _nm_alignas(NMIPAddr) to ensure that fields for in_addr_t and + * struct in6_addr have all the same alignment. Ensure that this is suitable. */ +G_STATIC_ASSERT(_nm_alignof(in_addr_t) <= _nm_alignof(NMIPAddr)); +G_STATIC_ASSERT(_nm_alignof(struct in_addr) <= _nm_alignof(NMIPAddr)); +G_STATIC_ASSERT(_nm_alignof(struct in6_addr) <= _nm_alignof(NMIPAddr)); +G_STATIC_ASSERT(_nm_alignof(NMEtherAddr) <= _nm_alignof(NMIPAddr)); + +int +nm_ip_addr_cmp_for_sort(gconstpointer a, gconstpointer b, gpointer user_data) +{ + /* This is a compare function that can be used for sorting IP addresses. + * Essentially, it calls memcmp(). @user_data must be GINT_TO_POINTER(addr_family). + * @a and @b must be either pointers to in_addr_t, struct in6_addr or NMIPAddr. */ + return nm_ip_addr_cmp(GPOINTER_TO_INT(user_data), a, b); +} + +/* this initializes a struct in_addr/in6_addr and allows for untrusted + * arguments (like unsuitable @addr_family or @src_len). It's almost safe + * in the sense that it verifies input arguments strictly. Also, it + * uses memcpy() to access @src, so alignment is not an issue. + * + * Only potential pitfalls: + * + * - it allows for @addr_family to be AF_UNSPEC. If that is the case (and the + * caller allows for that), the caller MUST provide @out_addr_family. + * - when setting @dst to an IPv4 address, the trailing bytes are not touched. + * Meaning, if @dst is an NMIPAddr union, only the first bytes will be set. + * If that matter to you, clear @dst before. */ +gboolean +nm_ip_addr_set_from_untrusted(int addr_family, + gpointer dst, + gconstpointer src, + gsize src_len, + int *out_addr_family) +{ + nm_assert(dst); + + switch (addr_family) { + case AF_UNSPEC: + if (!out_addr_family) { + /* when the callers allow undefined @addr_family, they must provide + * an @out_addr_family argument. */ + nm_assert_not_reached(); + return FALSE; + } + switch (src_len) { + case sizeof(struct in_addr): + addr_family = AF_INET; + break; + case sizeof(struct in6_addr): + addr_family = AF_INET6; + break; + default: + return FALSE; + } + break; + case AF_INET: + if (src_len != sizeof(struct in_addr)) + return FALSE; + break; + case AF_INET6: + if (src_len != sizeof(struct in6_addr)) + return FALSE; + break; + default: + /* when the callers allow undefined @addr_family, they must provide + * an @out_addr_family argument. */ + nm_assert(out_addr_family); + return FALSE; + } + + nm_assert(src); + + memcpy(dst, src, src_len); + NM_SET_OUT(out_addr_family, addr_family); + return TRUE; +} + +gboolean +nm_ip_addr_set_from_variant(int addr_family, gpointer dst, GVariant *variant, int *out_addr_family) +{ + gconstpointer bytes; + gsize len; + + g_return_val_if_fail(dst, FALSE); + g_return_val_if_fail(variant, FALSE); + + /* This function always expects IP addresses as byte arrays ("ay"). Note that + * several NetworkManager API uses "u" (32 bit unsigned intergers) for IPv4 addresses. + * So this function won't work in those cases. + * + * Btw, using "u" for IPv4 address messes badly with the endianness (host + * vs network byte order). Don't do that. + */ + g_return_val_if_fail(g_variant_is_of_type(variant, G_VARIANT_TYPE("ay")), FALSE); + + bytes = g_variant_get_fixed_array(variant, &len, sizeof(guint8)); + + return nm_ip_addr_set_from_untrusted(addr_family, dst, bytes, len, out_addr_family); +} + +/*****************************************************************************/ + +guint32 +nm_ip4_addr_get_default_prefix0(in_addr_t ip) +{ + /* The function is originally from ipcalc.c of Red Hat's initscripts. */ + switch (ntohl(ip) >> 24) { + case 0 ... 127: + return 8; /* Class A */ + case 128 ... 191: + return 16; /* Class B */ + case 192 ... 223: + return 24; /* Class C */ + } + return 0; +} + +guint32 +nm_ip4_addr_get_default_prefix(in_addr_t ip) +{ + return nm_ip4_addr_get_default_prefix0(ip) ?: 24; +} + +gboolean +nm_ip_addr_is_site_local(int addr_family, const void *address) +{ + NMIPAddr a; + + nm_ip_addr_set(addr_family, &a, address); + + switch (addr_family) { + case AF_INET: + /* RFC1918 private addresses + * 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 */ + return (a.addr4 & htonl(0xff000000)) == htonl(0x0a000000) + || (a.addr4 & htonl(0xfff00000)) == htonl(0xac100000) + || (a.addr4 & htonl(0xffff0000)) == htonl(0xc0a80000); + case AF_INET6: + /* IN6_IS_ADDR_SITELOCAL() is for deprecated fec0::/10 addresses (see rfc3879, 4.). + * Note that for unique local IPv6 addresses (ULA, fc00::/7) this returns false. + * This may or may not be a bug of this function. */ + return IN6_IS_ADDR_SITELOCAL(&a.addr6); + default: + g_return_val_if_reached(FALSE); + } +} + +gboolean +nm_ip6_addr_is_ula(const struct in6_addr *address) +{ + /* Unique local IPv6 address (ULA) fc00::/7 */ + return (address->s6_addr32[0] & htonl(0xfe000000u)) == htonl(0xfc000000u); +} + +/*****************************************************************************/ + +gconstpointer +nm_ip_addr_clear_host_address(int family, gpointer dst, gconstpointer src, guint32 plen) +{ + NMIPAddr a; + NMIPAddr a2; + + g_return_val_if_fail(dst, NULL); + + if (!src) { + /* allow "self-assignment", by specifying %NULL as source. */ + src = dst; + } + + nm_ip_addr_set(family, &a, src); + + switch (family) { + case AF_INET: + g_return_val_if_fail(plen <= 32, NULL); + + a2.addr4 = nm_ip4_addr_clear_host_address(a.addr4, plen); + break; + case AF_INET6: + nm_ip6_addr_clear_host_address(&a2.addr6, &a.addr6, plen); + break; + default: + g_return_val_if_reached(NULL); + } + + nm_ip_addr_set(family, dst, &a2); + + return dst; +} + +/* nm_ip6_addr_clear_host_address: + * @dst: destination output buffer, will contain the network part of the @src address + * @src: source ip6 address. If NULL, this does an in-place update of @dst. + * Also, @src and @dst are allowed to be the same pointers. + * @plen: prefix length of network + * + * Note: this function is self assignment safe, to update @src inplace, set both + * @dst and @src to the same destination or set @src NULL. + */ +const struct in6_addr * +nm_ip6_addr_clear_host_address(struct in6_addr *dst, const struct in6_addr *src, guint32 plen) +{ + g_return_val_if_fail(plen <= 128, NULL); + g_return_val_if_fail(dst, NULL); + + if (!src) + src = dst; + + if (plen < 128) { + guint nbytes = plen / 8; + guint nbits = plen % 8; + + if (nbytes && dst != src) + memcpy(dst, src, nbytes); + if (nbits) { + dst->s6_addr[nbytes] = (src->s6_addr[nbytes] & (0xFF << (8 - nbits))); + nbytes++; + } + if (nbytes <= 15) + memset(&dst->s6_addr[nbytes], 0, 16 - nbytes); + } else if (src != dst) + *dst = *src; + + return dst; +} + +int +nm_ip6_addr_same_prefix_cmp(const struct in6_addr *addr_a, + const struct in6_addr *addr_b, + guint32 plen) +{ + int nbytes; + guint8 va, vb, m; + + if (plen >= 128) { + nm_assert(plen == 128); + NM_CMP_DIRECT_MEMCMP(addr_a, addr_b, sizeof(struct in6_addr)); + } else { + nbytes = plen / 8; + if (nbytes) + NM_CMP_DIRECT_MEMCMP(addr_a, addr_b, nbytes); + + plen = plen % 8; + if (plen != 0) { + m = ~((1 << (8 - plen)) - 1); + va = ((((const guint8 *) addr_a))[nbytes]) & m; + vb = ((((const guint8 *) addr_b))[nbytes]) & m; + NM_CMP_DIRECT(va, vb); + } + } + return 0; +} + +/*****************************************************************************/ + +static gboolean +_parse_legacy_addr4(const char *text, in_addr_t *out_addr, GError **error) +{ + gs_free char *s_free = NULL; + struct in_addr a1; + guint8 bin[sizeof(a1)]; + char *s; + int i; + + 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. + * That's what "legacy" means here. inet_pton() doesn't accept those. + * + * But inet_aton() also ignores trailing garbage and formats with fewer than + * 4 digits. That is just too crazy and we don't do that. Perform additional checks + * and reject some forms that inet_aton() accepted. + * + * Note that we still should (of course) accept everything that inet_pton() + * accepts. However this code never gets called if inet_pton() succeeds + * (see below, aside the assertion code). */ + + 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; + } + + s = nm_memdup_maybe_a(300, text, strlen(text) + 1, &s_free); + + for (i = 0; i < G_N_ELEMENTS(bin); i++) { + char *current_token = s; + gint32 v; + + s = strchr(s, '.'); + if (s) { + s[0] = '\0'; + s++; + } + + 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; + } + + bin[i] = v; + } + + 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; + } + + *out_addr = a1.s_addr; + return TRUE; +} + +gboolean +nm_inet_parse_bin_full(int addr_family, + gboolean accept_legacy, + const char *text, + int *out_addr_family, + gpointer out_addr) +{ + NMIPAddr addrbin; + + g_return_val_if_fail(text, FALSE); + + if (addr_family == AF_UNSPEC) { + g_return_val_if_fail(!out_addr || out_addr_family, FALSE); + addr_family = strchr(text, ':') ? AF_INET6 : AF_INET; + } else + g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), FALSE); + + if (inet_pton(addr_family, text, &addrbin) != 1) { + if (accept_legacy && addr_family == AF_INET + && _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 + return FALSE; + } + +#if NM_MORE_ASSERTS > 10 + if (addr_family == AF_INET) { + NM_PRAGMA_WARNING_DISABLE_DANGLING_POINTER + 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. */ + 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_inet4_ntop(addrbin.addr4, buf), + error->message); + } + nm_assert(addrbin.addr4 == a); + NM_PRAGMA_WARNING_REENABLE + } +#endif + + NM_SET_OUT(out_addr_family, addr_family); + if (out_addr) + nm_ip_addr_set(addr_family, out_addr, &addrbin); + return TRUE; +} + +gboolean +nm_inet_parse_str(int addr_family, const char *text, char **out_addr) +{ + NMIPAddr addrbin; + + if (!nm_inet_parse_bin(addr_family, text, &addr_family, &addrbin)) + return FALSE; + + NM_SET_OUT(out_addr, nm_inet_ntop_dup(addr_family, &addrbin)); + return TRUE; +} + +gboolean +nm_inet_parse_with_prefix_bin(int addr_family, + const char *text, + int *out_addr_family, + gpointer out_addr, + int *out_prefix) +{ + gs_free char *addrstr_free = NULL; + int prefix = -1; + const char *slash; + const char *addrstr; + NMIPAddr addrbin; + + g_return_val_if_fail(text, FALSE); + + if (addr_family == AF_UNSPEC) { + g_return_val_if_fail(!out_addr || out_addr_family, FALSE); + addr_family = strchr(text, ':') ? AF_INET6 : AF_INET; + } else + g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), FALSE); + + slash = strchr(text, '/'); + if (slash) + addrstr = nm_strndup_a(300, text, slash - text, &addrstr_free); + else + addrstr = text; + + if (inet_pton(addr_family, addrstr, &addrbin) != 1) + return FALSE; + + if (slash) { + /* For IPv4, `ip addr add` supports the prefix-length as a netmask. We don't + * do that. */ + prefix = + _nm_utils_ascii_str_to_int64(&slash[1], 10, 0, addr_family == AF_INET ? 32 : 128, -1); + if (prefix == -1) + return FALSE; + } + + NM_SET_OUT(out_addr_family, addr_family); + if (out_addr) + nm_ip_addr_set(addr_family, out_addr, &addrbin); + NM_SET_OUT(out_prefix, prefix); + return TRUE; +} + +gboolean +nm_inet_parse_with_prefix_str(int addr_family, const char *text, char **out_addr, int *out_prefix) +{ + NMIPAddr addrbin; + + if (!nm_inet_parse_with_prefix_bin(addr_family, text, &addr_family, &addrbin, out_prefix)) + return FALSE; + + NM_SET_OUT(out_addr, nm_inet_ntop_dup(addr_family, &addrbin)); + return TRUE; +} + +/*****************************************************************************/ + +gboolean +nm_inet_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_inet_parse_bin(addr_family, str_addr, NULL, NULL); +} + +gboolean +nm_inet_is_normalized(int addr_family, const char *str_addr) +{ + NMIPAddr addr; + char sbuf[NM_INET_ADDRSTRLEN]; + + nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6)); + + if (!str_addr) + return FALSE; + + if (!nm_inet_parse_bin(addr_family, str_addr, &addr_family, &addr)) + return FALSE; + + nm_inet_ntop(addr_family, &addr, sbuf); + return nm_streq(sbuf, str_addr); +} + +/*****************************************************************************/ + +NM_UTILS_ENUM2STR_DEFINE(nm_icmpv6_router_pref_to_string, + NMIcmpv6RouterPref, + NM_UTILS_ENUM2STR(NM_ICMPV6_ROUTER_PREF_LOW, "low"), + NM_UTILS_ENUM2STR(NM_ICMPV6_ROUTER_PREF_MEDIUM, "medium"), + NM_UTILS_ENUM2STR(NM_ICMPV6_ROUTER_PREF_HIGH, "high"), + NM_UTILS_ENUM2STR(NM_ICMPV6_ROUTER_PREF_INVALID, "invalid"), ); diff --git a/src/libnm-glib-aux/nm-inet-utils.h b/src/libnm-glib-aux/nm-inet-utils.h new file mode 100644 index 00000000..8421929e --- /dev/null +++ b/src/libnm-glib-aux/nm-inet-utils.h @@ -0,0 +1,389 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#ifndef __NM_INET_UTILS_H__ +#define __NM_INET_UTILS_H__ + +typedef struct _NMIPAddr { + union { + guint8 addr_ptr[sizeof(struct in6_addr)]; + in_addr_t addr4; + struct in_addr addr4_struct; + struct in6_addr addr6; + + /* NMIPAddr is really a union for IP addresses. + * However, as ethernet addresses fit in here nicely, use + * it also for an ethernet MAC address. */ + guint8 ether_addr_octet[6 /*ETH_ALEN*/]; + NMEtherAddr ether_addr; + + guint8 array[sizeof(struct in6_addr)]; + }; +} NMIPAddr; + +#define NM_IP_ADDR_INIT \ + { \ + .array = { 0 } \ + } + +#define _NM_IN6ADDR_INIT(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, aa, ab, ac, ad, ae, af) \ + { \ + .s6_addr = { \ + (a0), \ + (a1), \ + (a2), \ + (a3), \ + (a4), \ + (a5), \ + (a6), \ + (a7), \ + (a8), \ + (a9), \ + (aa), \ + (ab), \ + (ac), \ + (ad), \ + (ae), \ + (af), \ + } \ + } + +#define NM_IN6ADDR_INIT(...) ((struct in6_addr) _NM_IN6ADDR_INIT(__VA_ARGS__)) + +extern const NMIPAddr nm_ip_addr_zero; + +static inline int +nm_ip_addr_cmp(int addr_family, gconstpointer a, gconstpointer b) +{ + /* Note that @a and @b are not required to be full NMIPAddr unions. + * Depending on @addr_family, they can also be only in_addr_t or + * struct in6_addr. */ + NM_CMP_SELF(a, b); + NM_CMP_DIRECT_MEMCMP(a, b, nm_utils_addr_family_to_size(addr_family)); + return 0; +} + +int nm_ip_addr_cmp_for_sort(gconstpointer a, gconstpointer b, gpointer user_data); + +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 void +nm_ip_addr_set(int addr_family, gpointer dst, gconstpointer src) +{ + nm_assert(dst); + nm_assert(src); + + /* this MUST use memcpy() to support unaligned src/dst pointers. */ + memcpy(dst, src, nm_utils_addr_family_to_size(addr_family)); + + /* Note that @dst is not necessarily a NMIPAddr, it could also be just + * an in_addr_t/struct in6_addr. We thus can only set the bytes that + * we know are present based on the address family. + * + * Using this function to initialize an NMIPAddr union (for IPv4) leaves + * uninitalized bytes. Avoid that by using nm_ip_addr_init() instead. */ +} + +static inline gboolean +nm_ip_addr_is_null(int addr_family, gconstpointer addr) +{ + NMIPAddr a; + + nm_ip_addr_set(addr_family, &a, addr); + + if (NM_IS_IPv4(addr_family)) + return a.addr4 == 0; + + return IN6_IS_ADDR_UNSPECIFIED(&a.addr6); +} + +static inline NMIPAddr +nm_ip_addr_init(int addr_family, gconstpointer src) +{ + NMIPAddr a; + + nm_assert_addr_family(addr_family); + nm_assert(src); + + G_STATIC_ASSERT_EXPR(sizeof(NMIPAddr) == sizeof(struct in6_addr)); + + /* this MUST use memcpy() to support unaligned src/dst pointers. */ + + if (NM_IS_IPv4(addr_family)) { + memcpy(&a, src, sizeof(in_addr_t)); + + /* ensure all bytes of the union are initialized. If only to make + * valgrind happy. */ + memset(&a.array[sizeof(in_addr_t)], 0, sizeof(a) - sizeof(in_addr_t)); + } else + memcpy(&a, src, sizeof(struct in6_addr)); + + return a; +} + +gboolean nm_ip_addr_set_from_untrusted(int addr_family, + gpointer dst, + gconstpointer src, + gsize src_len, + int *out_addr_family); + +gboolean +nm_ip_addr_set_from_variant(int addr_family, gpointer dst, GVariant *variant, int *out_addr_family); + +static inline gconstpointer +nm_ip_addr_from_packed_array(int addr_family, gconstpointer ipaddr_arr, gsize idx) +{ + return NM_IS_IPv4(addr_family) + ? ((gconstpointer) & (((const struct in_addr *) ipaddr_arr)[idx])) + : ((gconstpointer) & (((const struct in6_addr *) ipaddr_arr)[idx])); +} + +/*****************************************************************************/ + +static inline guint32 +nm_ip4_addr_netmask_to_prefix(in_addr_t subnetmask) +{ + G_STATIC_ASSERT_EXPR(__SIZEOF_INT__ == 4); + G_STATIC_ASSERT_EXPR(sizeof(int) == 4); + G_STATIC_ASSERT_EXPR(sizeof(guint) == 4); + G_STATIC_ASSERT_EXPR(sizeof(subnetmask) == 4); + + return ((subnetmask != 0u) ? (guint32) (32 - __builtin_ctz(ntohl(subnetmask))) : 0u); +} + +/** + * nm_ip4_addr_netmask_from_prefix: + * @prefix: a CIDR prefix + * + * Returns: the netmask represented by the prefix, in network byte order + **/ +static inline in_addr_t +nm_ip4_addr_netmask_from_prefix(guint32 prefix) +{ + nm_assert(prefix <= 32); + return prefix < 32 ? ~htonl(0xFFFFFFFFu >> prefix) : 0xFFFFFFFFu; +} + +guint32 nm_ip4_addr_get_default_prefix0(in_addr_t ip); +guint32 nm_ip4_addr_get_default_prefix(in_addr_t ip); + +static inline in_addr_t +nm_ip4_addr_get_broadcast_address(in_addr_t address, guint8 plen) +{ + return address | ~nm_ip4_addr_netmask_from_prefix(plen); +} + +gconstpointer +nm_ip_addr_clear_host_address(int family, gpointer dst, gconstpointer src, guint32 plen); + +/* nm_ip4_addr_clear_host_address: + * @addr: source ip6 address + * @plen: prefix length of network + * + * returns: the input address, with the host address set to 0. + */ +static inline in_addr_t +nm_ip4_addr_clear_host_address(in_addr_t addr, guint32 plen) +{ + return addr & nm_ip4_addr_netmask_from_prefix(plen); +} + +const struct in6_addr * +nm_ip6_addr_clear_host_address(struct in6_addr *dst, const struct in6_addr *src, guint32 plen); + +/*****************************************************************************/ + +static inline int +nm_ip4_addr_same_prefix_cmp(in_addr_t addr_a, in_addr_t addr_b, guint32 plen) +{ + NM_CMP_DIRECT(htonl(nm_ip4_addr_clear_host_address(addr_a, plen)), + htonl(nm_ip4_addr_clear_host_address(addr_b, plen))); + return 0; +} + +int nm_ip6_addr_same_prefix_cmp(const struct in6_addr *addr_a, + const struct in6_addr *addr_b, + guint32 plen); + +static inline gboolean +nm_ip4_addr_same_prefix(in_addr_t addr_a, in_addr_t addr_b, guint32 plen) +{ + return nm_ip4_addr_same_prefix_cmp(addr_a, addr_b, plen) == 0; +} + +static inline gboolean +nm_ip6_addr_same_prefix(const struct in6_addr *addr_a, const struct in6_addr *addr_b, guint8 plen) +{ + return nm_ip6_addr_same_prefix_cmp(addr_a, addr_b, plen) == 0; +} + +static inline int +nm_ip_addr_same_prefix_cmp(int addr_family, gconstpointer addr_a, gconstpointer addr_b, guint8 plen) +{ + NMIPAddr a; + NMIPAddr b; + + NM_CMP_SELF(addr_a, addr_b); + + nm_ip_addr_set(addr_family, &a, addr_a); + nm_ip_addr_set(addr_family, &b, addr_b); + + if (NM_IS_IPv4(addr_family)) + return nm_ip4_addr_same_prefix_cmp(a.addr4, b.addr4, plen); + + return nm_ip6_addr_same_prefix_cmp(&a.addr6, &b.addr6, plen); +} + +static inline gboolean +nm_ip_addr_same_prefix(int addr_family, gconstpointer addr_a, gconstpointer addr_b, guint8 plen) +{ + return nm_ip_addr_same_prefix_cmp(addr_family, addr_a, addr_b, plen) == 0; +} + +#define NM_CMP_DIRECT_IP4_ADDR_SAME_PREFIX(a, b, plen) \ + NM_CMP_RETURN(nm_ip4_addr_same_prefix_cmp((a), (b), (plen))) + +#define NM_CMP_DIRECT_IP6_ADDR_SAME_PREFIX(a, b, plen) \ + NM_CMP_RETURN(nm_ip6_addr_same_prefix_cmp((a), (b), (plen))) + +/*****************************************************************************/ + +gboolean nm_ip_addr_is_site_local(int addr_family, const void *address); +gboolean nm_ip6_addr_is_ula(const struct in6_addr *address); + +/*****************************************************************************/ + +#define NM_IPV4LL_NETWORK ((in_addr_t) htonl(0xA9FE0000lu)) /* 169.254.0.0 */ +#define NM_IPV4LL_NETMASK ((in_addr_t) htonl(0xFFFF0000lu)) /* 255.255.0.0 */ +#define NM_IPV4LO_NETWORK ((in_addr_t) htonl(0x7F000000lu)) /* 127.0.0.0 */ +#define NM_IPV4LO_NETMASK ((in_addr_t) htonl(0xFF000000lu)) /* 255.0.0.0 */ +#define NM_IPV4LO_PREFIXLEN 8 +#define NM_IPV4LO_ADDR1 ((in_addr_t) htonl(0x7F000001lu)) /* 127.0.0.1 */ + +static inline gboolean +nm_ip4_addr_is_loopback(in_addr_t addr) +{ + /* There is also IN_LOOPBACK() in <linux/in.h>, but there the + * argument is in host order not `in_addr_t`. */ + return (addr & NM_IPV4LO_NETMASK) == NM_IPV4LO_NETWORK; +} + +static inline gboolean +nm_ip4_addr_is_link_local(in_addr_t addr) +{ + return (addr & NM_IPV4LL_NETMASK) == NM_IPV4LL_NETWORK; +} + +static inline gboolean +nm_ip4_addr_is_zeronet(in_addr_t network) +{ + /* Same as ipv4_is_zeronet() from kernel's include/linux/in.h. */ + return (network & htonl(0xFF000000u)) == htonl(0x00000000u); +} + +/*****************************************************************************/ + +#define NM_INET_ADDRSTRLEN INET6_ADDRSTRLEN + +/* Forward declare function so we don't have to drag in <arpa/inet.h>. */ +const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); + +static inline const char * +nm_inet_ntop(int addr_family, gconstpointer addr, char *dst) +{ + const char *s; + + 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_inet4_ntop(in_addr_t addr, char dst[static INET_ADDRSTRLEN]) +{ + return nm_inet_ntop(AF_INET, &addr, dst); +} + +static inline const char * +nm_inet6_ntop(const struct in6_addr *addr, char dst[static INET6_ADDRSTRLEN]) +{ + return nm_inet_ntop(AF_INET6, addr, dst); +} + +static inline char * +nm_inet_ntop_dup(int addr_family, gconstpointer addr) +{ + char buf[NM_INET_ADDRSTRLEN]; + + return g_strdup(nm_inet_ntop(addr_family, addr, buf)); +} + +static inline char * +nm_inet4_ntop_dup(in_addr_t addr) +{ + return nm_inet_ntop_dup(AF_INET, &addr); +} + +static inline char * +nm_inet6_ntop_dup(const struct in6_addr *addr) +{ + return nm_inet_ntop_dup(AF_INET6, addr); +} + +/*****************************************************************************/ + +gboolean nm_inet_parse_bin_full(int addr_family, + gboolean accept_legacy, + const char *text, + int *out_addr_family, + gpointer out_addr); +static inline gboolean +nm_inet_parse_bin(int addr_family, const char *text, int *out_addr_family, gpointer out_addr) +{ + return nm_inet_parse_bin_full(addr_family, FALSE, text, out_addr_family, out_addr); +} + +gboolean nm_inet_parse_str(int addr_family, const char *text, char **out_addr); + +gboolean nm_inet_parse_with_prefix_bin(int addr_family, + const char *text, + int *out_addr_family, + gpointer out_addr, + int *out_prefix); + +gboolean +nm_inet_parse_with_prefix_str(int addr_family, const char *text, char **out_addr, int *out_prefix); + +/*****************************************************************************/ + +gboolean nm_inet_is_valid(int addr_family, const char *str_addr); + +gboolean nm_inet_is_normalized(int addr_family, const char *str_addr); + +/*****************************************************************************/ + +/* this enum is compatible with ICMPV6_ROUTER_PREF_* (from <linux/icmpv6.h>, + * the values for netlink attribute RTA_PREF) and "enum ndp_route_preference" + * from <ndp.h>. */ +typedef enum _nm_packed { + NM_ICMPV6_ROUTER_PREF_MEDIUM = 0x0, /* ICMPV6_ROUTER_PREF_MEDIUM */ + NM_ICMPV6_ROUTER_PREF_LOW = 0x3, /* ICMPV6_ROUTER_PREF_LOW */ + NM_ICMPV6_ROUTER_PREF_HIGH = 0x1, /* ICMPV6_ROUTER_PREF_HIGH */ + NM_ICMPV6_ROUTER_PREF_INVALID = 0x2, /* ICMPV6_ROUTER_PREF_INVALID */ +} NMIcmpv6RouterPref; + +const char *nm_icmpv6_router_pref_to_string(NMIcmpv6RouterPref pref, char *buf, gsize len); + +/*****************************************************************************/ + +#endif /* __NM_INET_UTILS_H__ */ diff --git a/src/libnm-glib-aux/nm-io-utils.c b/src/libnm-glib-aux/nm-io-utils.c index 0823a16c..d1430595 100644 --- a/src/libnm-glib-aux/nm-io-utils.c +++ b/src/libnm-glib-aux/nm-io-utils.c @@ -12,6 +12,7 @@ #include <sys/stat.h> #include <sys/types.h> #include <sys/un.h> +#include <sys/ioctl.h> #include "nm-str-buf.h" #include "nm-shared-utils.h" @@ -351,6 +352,7 @@ nm_utils_file_set_contents(const char *filename, int errsv; gssize s; int fd; + int r; g_return_val_if_fail(filename, FALSE); g_return_val_if_fail(contents || !length, FALSE); @@ -414,7 +416,16 @@ nm_utils_file_set_contents(const char *filename, tmp_name); } - nm_close(fd); + r = nm_close_with_error(fd); + if (r < 0) { + errsv = NM_ERRNO_NATIVE(-r); + unlink(tmp_name); + return _get_contents_error(error, + errsv, + out_errsv, + "failed close() after writing file %s", + tmp_name); + } if (rename(tmp_name, filename)) { errsv = NM_ERRNO_NATIVE(errno); @@ -496,6 +507,45 @@ nm_utils_fd_read(int fd, NMStrBuf *out_string) /*****************************************************************************/ +/* Taken from systemd's next_datagram_size_fd(). */ +gssize +nm_fd_next_datagram_size(int fd) +{ + gssize l; + int k; + + /* This is a bit like FIONREAD/SIOCINQ, however a bit more powerful. The difference being: recv(MSG_PEEK) will + * actually cause the next datagram in the queue to be validated regarding checksums, which FIONREAD doesn't + * do. This difference is actually of major importance as we need to be sure that the size returned here + * actually matches what we will read with recvmsg() next, as otherwise we might end up allocating a buffer of + * the wrong size. */ + + l = recv(fd, NULL, 0, MSG_PEEK | MSG_TRUNC); + if (l < 0) { + if (NM_IN_SET(errno, EOPNOTSUPP, EFAULT)) + goto fallback; + + return -errno; + } + if (l == 0) + goto fallback; + + return l; + +fallback: + k = 0; + + /* Some sockets (AF_PACKET) do not support null-sized recv() with MSG_TRUNC set, let's fall back to FIONREAD + * for them. Checksums don't matter for raw sockets anyway, hence this should be fine. */ + + if (ioctl(fd, FIONREAD, &k) < 0) + return -errno; + + return (gssize) k; +} + +/*****************************************************************************/ + typedef struct { GSubprocess *subprocess; GSource *timeout_source; diff --git a/src/libnm-glib-aux/nm-io-utils.h b/src/libnm-glib-aux/nm-io-utils.h index ef015153..54018140 100644 --- a/src/libnm-glib-aux/nm-io-utils.h +++ b/src/libnm-glib-aux/nm-io-utils.h @@ -52,10 +52,55 @@ struct _NMStrBuf; gssize nm_utils_fd_read(int fd, struct _NMStrBuf *out_string); +gssize nm_fd_next_datagram_size(int fd); + struct stat; int nm_utils_file_stat(const char *filename, struct stat *out_st); +/*****************************************************************************/ + +/* From systemd's ERRNO_IS_TRANSIENT(). + * + * For send()/recv() or read()/write(). */ +static inline gboolean +NM_ERRNO_IS_TRANSIENT(int r) +{ + return NM_IN_SET((r < 0 ? -r : r), EAGAIN, EINTR); +} + +/* From systemd's ERRNO_IS_DISCONNECT(). + * + * Hint #1: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5. + * + * Hint #2: The kernel sends e.g., EHOSTUNREACH or ENONET to userspace in some ICMP error cases. See the + * icmp_err_convert[] in net/ipv4/icmp.c in the kernel sources. + * + * Hint #3: When asynchronous connect() on TCP fails because the host never acknowledges a single packet, + * kernel tells us that with ETIMEDOUT, see tcp(7). */ +static inline gboolean +NM_ERRNO_IS_DISCONNECT(int r) +{ + return NM_IN_SET((r < 0 ? -r : r), + ECONNABORTED, + ECONNREFUSED, + ECONNRESET, + EHOSTDOWN, + EHOSTUNREACH, + ENETDOWN, + ENETRESET, + ENETUNREACH, + ENONET, + ENOPROTOOPT, + ENOTCONN, + EPIPE, + EPROTO, + ESHUTDOWN, + ETIMEDOUT); +} + +/*****************************************************************************/ + void nm_g_subprocess_terminate_in_background(GSubprocess *subprocess, int timeout_msec_before_kill); char **nm_utils_find_mkstemp_files(const char *dirname, const char *filename); diff --git a/src/libnm-glib-aux/nm-macros-internal.h b/src/libnm-glib-aux/nm-macros-internal.h index f2a4461e..71a17e3e 100644 --- a/src/libnm-glib-aux/nm-macros-internal.h +++ b/src/libnm-glib-aux/nm-macros-internal.h @@ -43,6 +43,27 @@ /*****************************************************************************/ +/* Historically, our cleanup macros come from a long gone library + * libgsystem, hence the "gs_" prefix. We still keep using them, + * although maybe we should drop them and use our respective nm_auto* + * macros (TODO). + * + * GLib also has g_auto() since 2.44. First of all, we still don't + * depend on 2.44, so we would have add compat implementations to + * "nm-glib.h" or bump the version. + * Also, they work differently (nm_auto_unref_hashtable vs g_auto(GHashTable)). + * If we were to switch to g_auto(), the change would be slightly more complicated + * than replacing one macro with another (but still easy). + * However, the reason for using our nm_auto* macros is that we also want cleanup + * macros in libnm-std-aux, which has no glib dependency. So we still would have + * some nm_auto* macros mixed with g_auto(). Instead, we consistently use + * nm_auto* macros (and the gs_* aliases). + * + * Note that c-stdaux also brings cleanup macros like _c_cleanup_(c_freep). + * We use c-stdaux like a proper internal library, so we could instead switch + * from nm_auto* macros to _c_cleanup_(). Unlike glib, c-stdaux is used by + * libnm-std-aux. Again, _c_cleanup_ follows a different pattern both from + * nm_auto* and g_auto(). */ #define gs_free nm_auto_g_free #define gs_unref_object nm_auto_unref_object #define gs_unref_variant nm_auto_unref_variant @@ -110,6 +131,9 @@ NM_AUTO_DEFINE_FCN_VOID0(void *, _nm_auto_unref_gtypeclass, g_type_class_unref); NM_AUTO_DEFINE_FCN0(GByteArray *, _nm_auto_unref_bytearray, g_byte_array_unref); #define nm_auto_unref_bytearray nm_auto(_nm_auto_unref_bytearray) +NM_AUTO_DEFINE_FCN0(GDateTime *, _nm_auto_unref_gdatetime, g_date_time_unref); +#define nm_auto_unref_gdatetime nm_auto(_nm_auto_unref_gdatetime) + static inline void _nm_auto_free_gstring(GString **str) { @@ -236,68 +260,40 @@ NM_G_ERROR_MSG(GError *error) #if _NM_CC_SUPPORT_GENERIC #define _NM_CONSTCAST_FULL_1(type, obj_expr, obj) \ (_Generic ((obj_expr), \ - const void *const: ((const type *) (obj)), \ - const void * : ((const type *) (obj)), \ - void *const: (( type *) (obj)), \ - void * : (( type *) (obj)), \ - const type *const: ((const type *) (obj)), \ - const type * : ((const type *) (obj)), \ - type *const: (( type *) (obj)), \ - type * : (( type *) (obj)))) + const void *: ((const type *) (obj)), \ + void *: (( type *) (obj)), \ + const type *: ((const type *) (obj)), \ + type *: (( type *) (obj)))) #define _NM_CONSTCAST_FULL_2(type, obj_expr, obj, alias_type2) \ (_Generic ((obj_expr), \ - const void *const: ((const type *) (obj)), \ - const void * : ((const type *) (obj)), \ - void *const: (( type *) (obj)), \ - void * : (( type *) (obj)), \ - const alias_type2 *const: ((const type *) (obj)), \ - const alias_type2 * : ((const type *) (obj)), \ - alias_type2 *const: (( type *) (obj)), \ - alias_type2 * : (( type *) (obj)), \ - const type *const: ((const type *) (obj)), \ - const type * : ((const type *) (obj)), \ - type *const: (( type *) (obj)), \ - type * : (( type *) (obj)))) + const void *: ((const type *) (obj)), \ + void *: (( type *) (obj)), \ + const alias_type2 *: ((const type *) (obj)), \ + alias_type2 *: (( type *) (obj)), \ + const type *: ((const type *) (obj)), \ + type *: (( type *) (obj)))) #define _NM_CONSTCAST_FULL_3(type, obj_expr, obj, alias_type2, alias_type3) \ (_Generic ((obj_expr), \ - const void *const: ((const type *) (obj)), \ - const void * : ((const type *) (obj)), \ - void *const: (( type *) (obj)), \ - void * : (( type *) (obj)), \ - const alias_type2 *const: ((const type *) (obj)), \ - const alias_type2 * : ((const type *) (obj)), \ - alias_type2 *const: (( type *) (obj)), \ - alias_type2 * : (( type *) (obj)), \ - const alias_type3 *const: ((const type *) (obj)), \ - const alias_type3 * : ((const type *) (obj)), \ - alias_type3 *const: (( type *) (obj)), \ - alias_type3 * : (( type *) (obj)), \ - const type *const: ((const type *) (obj)), \ - const type * : ((const type *) (obj)), \ - type *const: (( type *) (obj)), \ - type * : (( type *) (obj)))) + const void *: ((const type *) (obj)), \ + void *: (( type *) (obj)), \ + const alias_type2 *: ((const type *) (obj)), \ + alias_type2 *: (( type *) (obj)), \ + const alias_type3 *: ((const type *) (obj)), \ + alias_type3 *: (( type *) (obj)), \ + const type *: ((const type *) (obj)), \ + type *: (( type *) (obj)))) #define _NM_CONSTCAST_FULL_4(type, obj_expr, obj, alias_type2, alias_type3, alias_type4) \ (_Generic ((obj_expr), \ - const void *const: ((const type *) (obj)), \ - const void * : ((const type *) (obj)), \ - void *const: (( type *) (obj)), \ - void * : (( type *) (obj)), \ - const alias_type2 *const: ((const type *) (obj)), \ - const alias_type2 * : ((const type *) (obj)), \ - alias_type2 *const: (( type *) (obj)), \ - alias_type2 * : (( type *) (obj)), \ - const alias_type3 *const: ((const type *) (obj)), \ - const alias_type3 * : ((const type *) (obj)), \ - alias_type3 *const: (( type *) (obj)), \ - alias_type3 * : (( type *) (obj)), \ - const alias_type4 *const: ((const type *) (obj)), \ - const alias_type4 * : ((const type *) (obj)), \ - alias_type4 *const: (( type *) (obj)), \ - alias_type4 * : (( type *) (obj)), \ - const type *const: ((const type *) (obj)), \ - const type * : ((const type *) (obj)), \ - type *const: (( type *) (obj)), \ - type * : (( type *) (obj)))) + const void *: ((const type *) (obj)), \ + void *: (( type *) (obj)), \ + const alias_type2 *: ((const type *) (obj)), \ + alias_type2 *: (( type *) (obj)), \ + const alias_type3 *: ((const type *) (obj)), \ + alias_type3 *: (( type *) (obj)), \ + const alias_type4 *: ((const type *) (obj)), \ + alias_type4 *: (( type *) (obj)), \ + const type *: ((const type *) (obj)), \ + type *: (( type *) (obj)))) #define _NM_CONSTCAST_FULL_x(type, obj_expr, obj, n, ...) \ (_NM_CONSTCAST_FULL_##n(type, obj_expr, obj, ##__VA_ARGS__)) #define _NM_CONSTCAST_FULL_y(type, obj_expr, obj, n, ...) \ @@ -393,13 +389,7 @@ NM_G_ERROR_MSG(GError *error) char *const*: (const char *const*) (value), \ char * *: (const char *const*) (value), \ const void *: (const char *const*) (value), \ - void *: (const char *const*) (value), \ - const char *const*const: (const char *const*) (value), \ - const char * *const: (const char *const*) (value), \ - char *const*const: (const char *const*) (value), \ - char * *const: (const char *const*) (value), \ - const void *const: (const char *const*) (value), \ - void *const: (const char *const*) (value))) + void *: (const char *const*) (value))) #else #define NM_CAST_STRV_MC(value) ((const char **) (value)) #define NM_CAST_STRV_CC(value) ((const char *const *) (value)) @@ -496,6 +486,17 @@ nm_strdup_not_empty(const char *str) } static inline char * +nm_str_truncate(char *str) +{ + /* This is trivial, and is only useful in a macro, to + * ensure that we access the macro argument only once. */ + nm_assert(str); + + str[0] = '\0'; + return str; +} + +static inline char * nm_str_realloc(char *str) { gs_free char *s = str; @@ -527,16 +528,27 @@ nm_str_realloc(char *str) /*****************************************************************************/ /* redefine assertions to use g_assert*() */ -#undef _nm_assert_call -#undef _nm_assert_call_not_reached -#define _nm_assert_call(cond) g_assert(cond) -#define _nm_assert_call_not_reached() g_assert_not_reached() +#undef _nm_assert_fail +#define _nm_assert_fail(msg) \ + G_STMT_START \ + { \ + g_assertion_message_expr(G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, msg); \ + _nm_unreachable_code(); \ + } \ + G_STMT_END + +#undef _NM_ASSERT_FAIL_ENABLED +#ifndef G_DISABLE_ASSERT +#define _NM_ASSERT_FAIL_ENABLED 1 +#else +#define _NM_ASSERT_FAIL_ENABLED 0 +#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 + * This will only run the check once, and only if NM_MORE_ASSERTS is >= than * more_assert_level. */ #define NM_MORE_ASSERT_ONCE(more_assert_level) \ @@ -652,6 +664,24 @@ nm_str_realloc(char *str) /*****************************************************************************/ +/* Unfortunately, G_TYPE_CHECK_INSTANCE_CAST() just does a direct cast, + * which can trigger a "-Wcast-align" warning, especially when casting + * a GObject pointer to the desired type. + * + * _NM_G_TYPE_CHECK_INSTANCE_CAST() avoids that warning. + * + * Since the entire point of G_TYPE_CHECK_INSTANCE_CAST_() is to do a + * runtime check on the type (with conditional assertions via G_DISABLE_CAST_CHECKS), + * we already assert that the gtype is right, and the alignment is also + * expected to be right. + * + * See https://gitlab.gnome.org/GNOME/glib/-/merge_requests/3139 + */ +#define _NM_G_TYPE_CHECK_INSTANCE_CAST(instance, g_type, c_type) \ + G_TYPE_CHECK_INSTANCE_CAST(((void *) (instance)), (g_type), c_type) + +/*****************************************************************************/ + static inline gpointer nm_g_object_ref(gpointer obj) { @@ -1003,15 +1033,15 @@ nm_g_variant_equal(GVariant *a, GVariant *b) const typeof(flags) _flags = (flags); \ const typeof(flags) _val = (val); \ \ - _flags &(~_val); \ + _flags & (~_val); \ }) -#define NM_FLAGS_ASSIGN(flags, val, assign) \ - ({ \ - const typeof(flags) _flags = (flags); \ - const typeof(flags) _val = (val); \ - \ - (assign) ? _flags | (_val) : _flags &(~_val); \ +#define NM_FLAGS_ASSIGN(flags, val, assign) \ + ({ \ + const typeof(flags) _flags = (flags); \ + const typeof(flags) _val = (val); \ + \ + (assign) ? _flags | (_val) : _flags & (~_val); \ }) #define NM_FLAGS_ASSIGN_MASK(flags, mask, val) \ @@ -1071,6 +1101,32 @@ nm_ascii_is_newline(char ch) return NM_IN_SET(ch, '\n', '\t'); } +static inline gboolean +nm_ascii_is_regular_char(char ch) +{ + /* Checks whether "ch" is "regular", which basically + * means it's either a digit, a alpha, or some special + * characters that are suitable for base64 encoding. + * + * The meaning of what "regular" means is not well defined, + * but it's used to validate the keys for "ovs.external-ids" + * dictionary. */ + switch (ch) { + case 'a' ... 'z': + case 'A' ... 'Z': + case '0' ... '9': + case '-': + case '_': + case '+': + case '/': + case '=': + case '.': + return TRUE; + default: + return FALSE; + } +} + #define nm_str_skip_leading_spaces(str) \ ({ \ typeof(*(str)) *_str_sls = (str); \ @@ -1276,6 +1332,36 @@ nm_memdup(gconstpointer data, gsize size) return p; } +static inline gpointer +nm_memdup_nul(gconstpointer data, gsize size) +{ + gpointer p; + + /* Like systemd's memdup_suffix0() and kernel's kmemdup_nul(). + * + * This: + * - never returns NULL + * - always has one NUL byte after the @size data. Thus, + * the actually allocated buffer is size+1. + * + * This is like nm_memdup() except: + * - never returns NULL + * - always returns one NUL character appended to the data. + * + * This is like g_strndup(), except + * - never returns NULL. + * - g_strndup() treats the src pointer as a NUL terminated string, + * so if src is shorter than size, the rest is filled with padding. + * Essentially, it uses strncpy() to copy the input which does the + * truncation. If @data contains no NUL byte in teh first @size bytes, + * it behaves the same as g_strndup(). */ + + p = g_malloc(size + 1u); + nm_memcpy(p, data, size); + ((char *) p)[size] = '\0'; + return p; +} + #define nm_malloc_maybe_a(alloca_maxlen, bytes, to_free) \ ({ \ const gsize _bytes = (bytes); \ diff --git a/src/libnm-glib-aux/nm-prioq.c b/src/libnm-glib-aux/nm-prioq.c new file mode 100644 index 00000000..3448dcd9 --- /dev/null +++ b/src/libnm-glib-aux/nm-prioq.c @@ -0,0 +1,323 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* + * Taken from systemd's Prioq. + * + * Priority Queue + * The prioq object implements a priority queue. That is, it orders objects by + * their priority and allows O(1) access to the object with the highest + * priority. Insertion and removal are Θ(log n). Optionally, the caller can + * provide a pointer to an index which will be kept up-to-date by the prioq. + * + * The underlying algorithm used in this implementation is a Heap. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-prioq.h" + +#include <errno.h> +#include <stdlib.h> + +/*****************************************************************************/ + +struct _NMPrioqItem { + void *data; + unsigned *idx; +}; + +/*****************************************************************************/ + +void +nm_prioq_init(NMPrioq *q, GCompareFunc compare_func) +{ + nm_assert(q); + nm_assert(compare_func); + + *q = (NMPrioq){ + ._priv = + { + .compare_func = compare_func, + .compare_data = NULL, + .compare_with_data = FALSE, + .items = NULL, + .n_items = 0, + .n_allocated = 0, + }, + }; +} + +void +nm_prioq_init_with_data(NMPrioq *q, GCompareDataFunc compare_func, gpointer compare_data) +{ + nm_assert(q); + nm_assert(compare_func); + + *q = (NMPrioq){ + ._priv = + { + .compare_data_func = compare_func, + .compare_data = compare_data, + .compare_with_data = TRUE, + .items = NULL, + .n_items = 0, + .n_allocated = 0, + }, + }; +} + +void +nm_prioq_destroy(NMPrioq *q) +{ + if (!q || !q->_priv.compare_func) + return; + + free(q->_priv.items); + q->_priv.compare_func = NULL; +} + +/*****************************************************************************/ + +static int +compare(NMPrioq *q, unsigned a, unsigned b) +{ + nm_assert(q); + nm_assert(q->_priv.compare_func); + nm_assert(a != b); + nm_assert(a < q->_priv.n_items); + nm_assert(b < q->_priv.n_items); + + if (q->_priv.compare_with_data) { + return q->_priv.compare_data_func(q->_priv.items[a].data, + q->_priv.items[b].data, + q->_priv.compare_data); + } + + return q->_priv.compare_func(q->_priv.items[a].data, q->_priv.items[b].data); +} + +static void +swap(NMPrioq *q, unsigned j, unsigned k) +{ + nm_assert(q); + nm_assert(j < q->_priv.n_items); + nm_assert(k < q->_priv.n_items); + + nm_assert(!q->_priv.items[j].idx || *(q->_priv.items[j].idx) == j); + nm_assert(!q->_priv.items[k].idx || *(q->_priv.items[k].idx) == k); + + NM_SWAP(&q->_priv.items[j].data, &q->_priv.items[k].data); + NM_SWAP(&q->_priv.items[j].idx, &q->_priv.items[k].idx); + + if (q->_priv.items[j].idx) + *q->_priv.items[j].idx = j; + + if (q->_priv.items[k].idx) + *q->_priv.items[k].idx = k; +} + +static unsigned +shuffle_up(NMPrioq *q, unsigned idx) +{ + nm_assert(q); + nm_assert(idx < q->_priv.n_items); + + while (idx > 0) { + unsigned k; + + k = (idx - 1) / 2; + + if (compare(q, k, idx) <= 0) + break; + + swap(q, idx, k); + idx = k; + } + + return idx; +} + +static unsigned +shuffle_down(NMPrioq *q, unsigned idx) +{ + nm_assert(q); + + for (;;) { + unsigned j; + unsigned k; + unsigned s; + + k = (idx + 1) * 2; /* right child */ + j = k - 1; /* left child */ + + if (j >= q->_priv.n_items) + break; + + if (compare(q, j, idx) < 0) { + /* So our left child is smaller than we are, let's + * remember this fact */ + s = j; + } else + s = idx; + + if ((k < q->_priv.n_items) && compare(q, k, s) < 0) { + /* So our right child is smaller than we are, let's + * remember this fact */ + s = k; + } + + /* s now points to the smallest of the three items */ + + if (s == idx) + /* No swap necessary, we're done */ + break; + + swap(q, idx, s); + idx = s; + } + + return idx; +} + +void +nm_prioq_put(NMPrioq *q, void *data, unsigned *idx) +{ + unsigned k; + + nm_assert(q); + + if (q->_priv.n_items >= q->_priv.n_allocated) { + q->_priv.n_allocated = NM_MAX((q->_priv.n_items + 1u) * 2u, 16u); + q->_priv.items = g_renew(struct _NMPrioqItem, q->_priv.items, q->_priv.n_allocated); + } + + k = q->_priv.n_items++; + + q->_priv.items[k] = (struct _NMPrioqItem){ + .data = data, + .idx = idx, + }; + if (idx) + *idx = k; + + shuffle_up(q, k); +} + +static void +remove_item(NMPrioq *q, struct _NMPrioqItem *i) +{ + struct _NMPrioqItem *l; + unsigned k; + + nm_assert(q); + nm_assert(i); + nm_assert(q->_priv.n_items > 0); + nm_assert(i >= q->_priv.items); + nm_assert(i < &q->_priv.items[q->_priv.n_items]); + + l = &q->_priv.items[q->_priv.n_items - 1u]; + + if (i == l) { + /* Last entry, let's just remove it */ + q->_priv.n_items--; + return; + } + + /* Not last entry, let's replace the last entry with + * this one, and reshuffle */ + k = i - q->_priv.items; + + *i = *l; + if (i->idx) + *i->idx = k; + q->_priv.n_items--; + + k = shuffle_down(q, k); + shuffle_up(q, k); +} + +_nm_pure static struct _NMPrioqItem * +find_item(NMPrioq *q, void *data, unsigned *idx) +{ + struct _NMPrioqItem *i; + + nm_assert(q); + + if (q->_priv.n_items <= 0) + return NULL; + + if (idx) { + if (*idx == NM_PRIOQ_IDX_NULL || *idx >= q->_priv.n_items) + return NULL; + + i = &q->_priv.items[*idx]; + if (i->data == data) + return i; + } else { + for (i = q->_priv.items; i < &q->_priv.items[q->_priv.n_items]; i++) { + if (i->data == data) + return i; + } + } + + return NULL; +} + +gboolean +nm_prioq_remove(NMPrioq *q, void *data, unsigned *idx) +{ + struct _NMPrioqItem *i; + + nm_assert(q); + + i = find_item(q, data, idx); + if (!i) + return FALSE; + + remove_item(q, i); + return TRUE; +} + +gboolean +nm_prioq_reshuffle(NMPrioq *q, void *data, unsigned *idx) +{ + struct _NMPrioqItem *i; + unsigned k; + + nm_assert(q); + + i = find_item(q, data, idx); + if (!i) + return FALSE; + + k = i - q->_priv.items; + k = shuffle_down(q, k); + shuffle_up(q, k); + return TRUE; +} + +void * +nm_prioq_peek_by_index(NMPrioq *q, unsigned idx) +{ + nm_assert(q); + + if (idx >= q->_priv.n_items) + return NULL; + + return q->_priv.items[idx].data; +} + +void * +nm_prioq_pop(NMPrioq *q) +{ + void *data; + + nm_assert(q); + + if (q->_priv.n_items <= 0) + return NULL; + + data = q->_priv.items[0].data; + remove_item(q, &q->_priv.items[0]); + return data; +} diff --git a/src/libnm-glib-aux/nm-prioq.h b/src/libnm-glib-aux/nm-prioq.h new file mode 100644 index 00000000..918c6447 --- /dev/null +++ b/src/libnm-glib-aux/nm-prioq.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#ifndef __NM_PRIOQ_H__ +#define __NM_PRIOQ_H__ + +#define NM_PRIOQ_IDX_NULL G_MAXUINT + +struct _NMPrioqItem; + +typedef struct { + struct { + union { + GCompareDataFunc compare_data_func; + GCompareFunc compare_func; + }; + + gpointer compare_data; + + struct _NMPrioqItem *items; + + unsigned n_items; + unsigned n_allocated; + + bool compare_with_data; + } _priv; +} NMPrioq; + +#define NM_PRIOQ_ZERO \ + { \ + ._priv = { \ + .compare_func = NULL, \ + }, \ + } + +void nm_prioq_init(NMPrioq *q, GCompareFunc compare_func); +void nm_prioq_init_with_data(NMPrioq *q, GCompareDataFunc compare_func, gpointer compare_data); + +void nm_prioq_destroy(NMPrioq *q); + +#define nm_auto_prioq nm_auto(nm_prioq_destroy) + +void nm_prioq_put(NMPrioq *q, void *data, unsigned *idx); +gboolean nm_prioq_remove(NMPrioq *q, void *data, unsigned *idx); +gboolean nm_prioq_reshuffle(NMPrioq *q, void *data, unsigned *idx); + +void *nm_prioq_peek_by_index(NMPrioq *q, unsigned idx) _nm_pure; + +static inline void * +nm_prioq_peek(NMPrioq *q) +{ + return nm_prioq_peek_by_index(q, 0); +} + +void *nm_prioq_pop(NMPrioq *q); + +#define NM_PRIOQ_FOREACH_ITEM(q, p) for (unsigned _i = 0; (p = nm_prioq_peek_by_index(q, _i)); _i++) + +_nm_pure static inline unsigned +nm_prioq_size(NMPrioq *q) +{ + nm_assert(q); + return q->_priv.n_items; +} + +_nm_pure static inline gboolean +nm_prioq_isempty(NMPrioq *q) +{ + return nm_prioq_size(q) <= 0; +} + +#endif /* __NM_PRIOQ_H__ */ diff --git a/src/libnm-glib-aux/nm-ref-string.c b/src/libnm-glib-aux/nm-ref-string.c index fec5d60e..27a6c938 100644 --- a/src/libnm-glib-aux/nm-ref-string.c +++ b/src/libnm-glib-aux/nm-ref-string.c @@ -157,8 +157,7 @@ nm_ref_string_new_len(const char *cstr, gsize len) g_atomic_int_inc(&rstr->_ref_count); } else { rstr = g_malloc((G_STRUCT_OFFSET(NMRefString, str) + 1u) + len); - if (len > 0) - memcpy((char *) rstr->str, cstr, len); + nm_memcpy((char *) rstr->str, cstr, len); ((char *) rstr->str)[len] = '\0'; *((gsize *) &rstr->len) = len; rstr->_ref_count = 1; diff --git a/src/libnm-glib-aux/nm-ref-string.h b/src/libnm-glib-aux/nm-ref-string.h index eb3c38de..1b0cabf2 100644 --- a/src/libnm-glib-aux/nm-ref-string.h +++ b/src/libnm-glib-aux/nm-ref-string.h @@ -93,6 +93,23 @@ nm_ref_string_unref(NMRefString *rstr) NM_AUTO_DEFINE_FCN_VOID(NMRefString *, _nm_auto_ref_string, nm_ref_string_unref); #define nm_auto_ref_string nm_auto(_nm_auto_ref_string) +static inline gboolean +nm_ref_string_reset(NMRefString **ptr, NMRefString *str) +{ + NMRefString *rstr; + + nm_assert(ptr); + + rstr = *ptr; + + if (rstr == str) + return FALSE; + + *ptr = nm_ref_string_ref(str); + nm_ref_string_unref(rstr); + return TRUE; +} + /*****************************************************************************/ static inline const char * @@ -139,7 +156,7 @@ nm_ref_string_equal_str(NMRefString *rstr, const char *str) /* We don't use streq() here, because an NMRefString might have embedded NUL characters * (as the length is tracked separately). The NUL terminated C string @str must not * compare equal to such a @rstr, thus we first explicitly check strlen(). */ - return rstr->len == strlen(str) && (rstr->str == str || memcmp(rstr->str, str, rstr->len) == 0); + return rstr->str == str || (rstr->len == strlen(str) && memcmp(rstr->str, str, rstr->len) == 0); } static inline gboolean diff --git a/src/libnm-glib-aux/nm-secret-utils.c b/src/libnm-glib-aux/nm-secret-utils.c index 983b04ca..ffab2479 100644 --- a/src/libnm-glib-aux/nm-secret-utils.c +++ b/src/libnm-glib-aux/nm-secret-utils.c @@ -39,24 +39,10 @@ nm_explicit_bzero(void *s, gsize n) 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); + nm_explicit_bzero(secret, strlen(secret)); g_free(secret); } diff --git a/src/libnm-glib-aux/nm-shared-utils.c b/src/libnm-glib-aux/nm-shared-utils.c index aca7d708..702a63e9 100644 --- a/src/libnm-glib-aux/nm-shared-utils.c +++ b/src/libnm-glib-aux/nm-shared-utils.c @@ -3,6 +3,8 @@ * Copyright (C) 2016 Red Hat, Inc. */ +#define NM_WANT_NM_ARRAY_FIND_BSEARCH_INLINE + #include "libnm-glib-aux/nm-default-glib-i18n-lib.h" #include "nm-shared-utils.h" @@ -35,111 +37,6 @@ const void *const _NM_PTRARRAY_EMPTY[1] = {NULL}; /*****************************************************************************/ -const NMIPAddr nm_ip_addr_zero = {}; - -/* We use _nm_alignas(NMIPAddr) to ensure that fields for in_addr_t and - * struct in6_addr have all the same alignment. Ensure that this is suitable. */ -G_STATIC_ASSERT(_nm_alignof(in_addr_t) <= _nm_alignof(NMIPAddr)); -G_STATIC_ASSERT(_nm_alignof(struct in_addr) <= _nm_alignof(NMIPAddr)); -G_STATIC_ASSERT(_nm_alignof(struct in6_addr) <= _nm_alignof(NMIPAddr)); -G_STATIC_ASSERT(_nm_alignof(NMEtherAddr) <= _nm_alignof(NMIPAddr)); - -int -nm_ip_addr_cmp_for_sort(gconstpointer a, gconstpointer b, gpointer user_data) -{ - /* This is a compare function that can be used for sorting IP addresses. - * Essentially, it calls memcmp(). @user_data must be GINT_TO_POINTER(addr_family). - * @a and @b must be either pointers to in_addr_t, struct in6_addr or NMIPAddr. */ - return nm_ip_addr_cmp(GPOINTER_TO_INT(user_data), a, b); -} - -/* this initializes a struct in_addr/in6_addr and allows for untrusted - * arguments (like unsuitable @addr_family or @src_len). It's almost safe - * in the sense that it verifies input arguments strictly. Also, it - * uses memcpy() to access @src, so alignment is not an issue. - * - * Only potential pitfalls: - * - * - it allows for @addr_family to be AF_UNSPEC. If that is the case (and the - * caller allows for that), the caller MUST provide @out_addr_family. - * - when setting @dst to an IPv4 address, the trailing bytes are not touched. - * Meaning, if @dst is an NMIPAddr union, only the first bytes will be set. - * If that matter to you, clear @dst before. */ -gboolean -nm_ip_addr_set_from_untrusted(int addr_family, - gpointer dst, - gconstpointer src, - gsize src_len, - int *out_addr_family) -{ - nm_assert(dst); - - switch (addr_family) { - case AF_UNSPEC: - if (!out_addr_family) { - /* when the callers allow undefined @addr_family, they must provide - * an @out_addr_family argument. */ - nm_assert_not_reached(); - return FALSE; - } - switch (src_len) { - case sizeof(struct in_addr): - addr_family = AF_INET; - break; - case sizeof(struct in6_addr): - addr_family = AF_INET6; - break; - default: - return FALSE; - } - break; - case AF_INET: - if (src_len != sizeof(struct in_addr)) - return FALSE; - break; - case AF_INET6: - if (src_len != sizeof(struct in6_addr)) - return FALSE; - break; - default: - /* when the callers allow undefined @addr_family, they must provide - * an @out_addr_family argument. */ - nm_assert(out_addr_family); - return FALSE; - } - - nm_assert(src); - - memcpy(dst, src, src_len); - NM_SET_OUT(out_addr_family, addr_family); - return TRUE; -} - -gboolean -nm_ip_addr_set_from_variant(int addr_family, gpointer dst, GVariant *variant, int *out_addr_family) -{ - gconstpointer bytes; - gsize len; - - g_return_val_if_fail(dst, FALSE); - g_return_val_if_fail(variant, FALSE); - - /* This function always expects IP addressea a byte arrays ("ay"). Note that - * several NetworkManager API uses "u" (32 bit unsigned intergers) for IPv4 addresses. - * So this function won't work in those cases. - * - * Btw, using "u" for IPv4 address messes badly with the endianness (host - * vs network byte order). Don't do that. - */ - g_return_val_if_fail(g_variant_is_of_type(variant, G_VARIANT_TYPE("ay")), FALSE); - - bytes = g_variant_get_fixed_array(variant, &len, sizeof(guint8)); - - return nm_ip_addr_set_from_untrusted(addr_family, dst, bytes, len, out_addr_family); -} - -/*****************************************************************************/ - G_STATIC_ASSERT(ETH_ALEN == sizeof(struct ether_addr)); G_STATIC_ASSERT(ETH_ALEN == 6); G_STATIC_ASSERT(ETH_ALEN == sizeof(NMEtherAddr)); @@ -158,6 +55,7 @@ nm_ether_addr_from_string(NMEtherAddr *addr, const char *str) return addr; } + /*****************************************************************************/ /** @@ -184,37 +82,6 @@ _nm_utils_inet6_is_token(const struct in6_addr *in6addr) } /** - * nm_utils_ipv6_addr_set_interface_identifier: - * @addr: output token encoded as %in6_addr - * @iid: %NMUtilsIPv6IfaceId interface identifier - * - * Converts the %NMUtilsIPv6IfaceId to an %in6_addr (suitable for use - * with Linux platform). This only copies the lower 8 bytes, ignoring - * the /64 network prefix which is expected to be all-zero for a valid - * token. - */ -void -nm_utils_ipv6_addr_set_interface_identifier(struct in6_addr *addr, const NMUtilsIPv6IfaceId *iid) -{ - memcpy(addr->s6_addr + 8, &iid->id_u8, 8); -} - -/** - * nm_utils_ipv6_interface_identifier_get_from_addr: - * @iid: output %NMUtilsIPv6IfaceId interface identifier set from the token - * @addr: token encoded as %in6_addr - * - * Converts the %in6_addr encoded token (as used by Linux platform) to - * the interface identifier. - */ -void -nm_utils_ipv6_interface_identifier_get_from_addr(NMUtilsIPv6IfaceId *iid, - const struct in6_addr *addr) -{ - memcpy(iid, addr->s6_addr + 8, 8); -} - -/** * nm_utils_ipv6_interface_identifier_get_from_token: * @iid: output %NMUtilsIPv6IfaceId interface identifier set from the token * @token: token encoded as string @@ -244,7 +111,7 @@ nm_utils_ipv6_interface_identifier_get_from_token(NMUtilsIPv6IfaceId *iid, const /** * nm_utils_inet6_interface_identifier_to_token: * @iid: %NMUtilsIPv6IfaceId interface identifier - * @buf: the destination buffer of at least %NM_UTILS_INET_ADDRSTRLEN + * @buf: the destination buffer of at least %NM_INET_ADDRSTRLEN * bytes. * * Converts the interface identifier to a string token. @@ -261,7 +128,7 @@ nm_utils_inet6_interface_identifier_to_token(const NMUtilsIPv6IfaceId *iid, nm_assert(buf); nm_utils_ipv6_addr_set_interface_identifier(&i6_token, iid); - return _nm_utils_inet6_ntop(&i6_token, buf); + return nm_inet6_ntop(&i6_token, buf); } /*****************************************************************************/ @@ -981,293 +848,6 @@ nm_utils_flags2str(const NMUtilsFlags2StrDesc *descs, /*****************************************************************************/ -guint32 -_nm_utils_ip4_get_default_prefix0(in_addr_t ip) -{ - /* The function is originally from ipcalc.c of Red Hat's initscripts. */ - switch (ntohl(ip) >> 24) { - case 0 ... 127: - return 8; /* Class A */ - case 128 ... 191: - return 16; /* Class B */ - case 192 ... 223: - return 24; /* Class C */ - } - return 0; -} - -guint32 -_nm_utils_ip4_get_default_prefix(in_addr_t ip) -{ - return _nm_utils_ip4_get_default_prefix0(ip) ?: 24; -} - -gboolean -nm_utils_ip_is_site_local(int addr_family, const void *address) -{ - in_addr_t addr4; - - switch (addr_family) { - case AF_INET: - /* RFC1918 private addresses - * 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 */ - addr4 = ntohl(*((const in_addr_t *) address)); - return (addr4 & 0xff000000) == 0x0a000000 || (addr4 & 0xfff00000) == 0xac100000 - || (addr4 & 0xffff0000) == 0xc0a80000; - case AF_INET6: - /* IN6_IS_ADDR_SITELOCAL() is for deprecated fec0::/10 addresses (see rfc3879, 4.). - * Note that for unique local IPv6 addresses (ULA, fc00::/7) this returns false, - * which may or may not be a bug. */ - return IN6_IS_ADDR_SITELOCAL(address); - default: - g_return_val_if_reached(FALSE); - } -} - -gboolean -nm_utils_ip6_is_ula(const struct in6_addr *address) -{ - /* Unique local IPv6 address (ULA) fc00::/7 */ - return (address->s6_addr32[0] & htonl(0xfe000000u)) == htonl(0xfc000000u); -} - -/*****************************************************************************/ - -static gboolean -_parse_legacy_addr4(const char *text, in_addr_t *out_addr, GError **error) -{ - gs_free char *s_free = NULL; - struct in_addr a1; - guint8 bin[sizeof(a1)]; - char *s; - int i; - - 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. - * That's what "legacy" means here. inet_pton() doesn't accept those. - * - * But inet_aton() also ignores trailing garbage and formats with fewer than - * 4 digits. That is just too crazy and we don't do that. Perform additional checks - * and reject some forms that inet_aton() accepted. - * - * Note that we still should (of course) accept everything that inet_pton() - * accepts. However this code never gets called if inet_pton() succeeds - * (see below, aside the assertion code). */ - - 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; - } - - s = nm_memdup_maybe_a(300, text, strlen(text) + 1, &s_free); - - for (i = 0; i < G_N_ELEMENTS(bin); i++) { - char *current_token = s; - gint32 v; - - s = strchr(s, '.'); - if (s) { - s[0] = '\0'; - s++; - } - - 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; - } - - bin[i] = v; - } - - 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; - } - - *out_addr = a1.s_addr; - return TRUE; -} - -gboolean -nm_utils_parse_inaddr_bin_full(int addr_family, - gboolean accept_legacy, - const char *text, - int *out_addr_family, - gpointer out_addr) -{ - NMIPAddr addrbin; - - g_return_val_if_fail(text, FALSE); - - if (addr_family == AF_UNSPEC) { - g_return_val_if_fail(!out_addr || out_addr_family, FALSE); - addr_family = strchr(text, ':') ? AF_INET6 : AF_INET; - } else - g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), FALSE); - - if (inet_pton(addr_family, text, &addrbin) != 1) { - if (accept_legacy && addr_family == AF_INET - && _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 - return FALSE; - } - -#if NM_MORE_ASSERTS > 10 - if (addr_family == AF_INET) { - NM_PRAGMA_WARNING_DISABLE_DANGLING_POINTER - 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. */ - 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); - NM_PRAGMA_WARNING_REENABLE - } -#endif - - NM_SET_OUT(out_addr_family, addr_family); - if (out_addr) - nm_ip_addr_set(addr_family, out_addr, &addrbin); - return TRUE; -} - -gboolean -nm_utils_parse_inaddr(int addr_family, const char *text, char **out_addr) -{ - NMIPAddr addrbin; - char addrstr_buf[MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)]; - - g_return_val_if_fail(text, FALSE); - - if (addr_family == AF_UNSPEC) - addr_family = strchr(text, ':') ? AF_INET6 : AF_INET; - else - g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), FALSE); - - if (inet_pton(addr_family, text, &addrbin) != 1) - return FALSE; - - NM_SET_OUT(out_addr, - g_strdup(inet_ntop(addr_family, &addrbin, addrstr_buf, sizeof(addrstr_buf)))); - return TRUE; -} - -gboolean -nm_utils_parse_inaddr_prefix_bin(int addr_family, - const char *text, - int *out_addr_family, - gpointer out_addr, - int *out_prefix) -{ - gs_free char *addrstr_free = NULL; - int prefix = -1; - const char *slash; - const char *addrstr; - NMIPAddr addrbin; - - g_return_val_if_fail(text, FALSE); - - if (addr_family == AF_UNSPEC) { - g_return_val_if_fail(!out_addr || out_addr_family, FALSE); - addr_family = strchr(text, ':') ? AF_INET6 : AF_INET; - } else - g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), FALSE); - - slash = strchr(text, '/'); - if (slash) - addrstr = nm_strndup_a(300, text, slash - text, &addrstr_free); - else - addrstr = text; - - if (inet_pton(addr_family, addrstr, &addrbin) != 1) - return FALSE; - - if (slash) { - /* For IPv4, `ip addr add` supports the prefix-length as a netmask. We don't - * do that. */ - prefix = - _nm_utils_ascii_str_to_int64(&slash[1], 10, 0, addr_family == AF_INET ? 32 : 128, -1); - if (prefix == -1) - return FALSE; - } - - NM_SET_OUT(out_addr_family, addr_family); - if (out_addr) - nm_ip_addr_set(addr_family, out_addr, &addrbin); - NM_SET_OUT(out_prefix, prefix); - return TRUE; -} - -gboolean -nm_utils_parse_inaddr_prefix(int addr_family, const char *text, char **out_addr, int *out_prefix) -{ - NMIPAddr addrbin; - char addrstr_buf[MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)]; - - if (!nm_utils_parse_inaddr_prefix_bin(addr_family, text, &addr_family, &addrbin, out_prefix)) - return FALSE; - NM_SET_OUT(out_addr, - g_strdup(inet_ntop(addr_family, &addrbin, addrstr_buf, sizeof(addrstr_buf)))); - return TRUE; -} - gboolean nm_utils_parse_next_line(const char **inout_ptr, gsize *inout_len, @@ -1323,43 +903,6 @@ done: /*****************************************************************************/ -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_UTILS_ENUM2STR_DEFINE(nm_icmpv6_router_pref_to_string, - NMIcmpv6RouterPref, - NM_UTILS_ENUM2STR(NM_ICMPV6_ROUTER_PREF_LOW, "low"), - NM_UTILS_ENUM2STR(NM_ICMPV6_ROUTER_PREF_MEDIUM, "medium"), - NM_UTILS_ENUM2STR(NM_ICMPV6_ROUTER_PREF_HIGH, "high"), - NM_UTILS_ENUM2STR(NM_ICMPV6_ROUTER_PREF_INVALID, "invalid"), ); - -/*****************************************************************************/ - /** * nm_g_ascii_strtoll() * @nptr: the string to parse @@ -2587,15 +2130,16 @@ nm_strv_cleanup(char **strv, gboolean strip_whitespace, gboolean skip_empty, gbo /*****************************************************************************/ GPtrArray * -_nm_g_ptr_array_copy(GPtrArray *array, - GCopyFunc func, - gpointer user_data, - GDestroyNotify element_free_func) +nm_g_ptr_array_new_clone(GPtrArray *array, + GCopyFunc func, + gpointer user_data, + GDestroyNotify element_free_func) { GPtrArray *new_array; guint i; g_return_val_if_fail(array, NULL); + nm_assert((!!func) == (!!element_free_func)); new_array = g_ptr_array_new_full(array->len, element_free_func); for (i = 0; i < array->len; i++) { @@ -3522,12 +3066,12 @@ nm_utils_named_value_list_find(const NMUtilsNamedValue *arr, #endif if (sorted) { - return nm_utils_array_find_binary_search(arr, - sizeof(NMUtilsNamedValue), - len, - &name, - nm_strcmp_p_with_data, - NULL); + return nm_array_find_bsearch(arr, + len, + sizeof(NMUtilsNamedValue), + &name, + nm_strcmp_p_with_data, + NULL); } for (i = 0; i < len; i++) { if (nm_streq(arr[i].name, name)) @@ -4215,17 +3759,19 @@ nm_utils_ptrarray_is_sorted(gconstpointer *list, } gssize -nm_utils_ptrarray_find_binary_search(gconstpointer *list, - gsize len, - gconstpointer needle, - GCompareDataFunc cmpfcn, - gpointer user_data) -{ - gssize imin, imax, imid; +nm_ptrarray_find_bsearch(gconstpointer *list, + gsize len, + gconstpointer needle, + GCompareDataFunc cmpfcn, + gpointer user_data) +{ + gssize imax; + gssize imid; + gssize imin; int cmp; - g_return_val_if_fail(list || !len, ~((gssize) 0)); - g_return_val_if_fail(cmpfcn, ~((gssize) 0)); + nm_assert(list || len == 0); + nm_assert(cmpfcn); imin = 0; if (len > 0) { @@ -4252,19 +3798,24 @@ nm_utils_ptrarray_find_binary_search(gconstpointer *list, } gssize -nm_utils_ptrarray_find_binary_search_range(gconstpointer *list, - gsize len, - gconstpointer needle, - GCompareDataFunc cmpfcn, - gpointer user_data, - gssize *out_idx_first, - gssize *out_idx_last) -{ - gssize imin, imax, imid, i2min, i2max, i2mid; +nm_ptrarray_find_bsearch_range(gconstpointer *list, + gsize len, + gconstpointer needle, + GCompareDataFunc cmpfcn, + gpointer user_data, + gssize *out_idx_first, + gssize *out_idx_last) +{ + gssize imax; + gssize imid; + gssize imin; + gssize i2max; + gssize i2mid; + gssize i2min; int cmp; - g_return_val_if_fail(list || !len, ~((gssize) 0)); - g_return_val_if_fail(cmpfcn, ~((gssize) 0)); + nm_assert(list || len == 0); + nm_assert(cmpfcn); imin = 0; if (len > 0) { @@ -4333,10 +3884,10 @@ nm_utils_ptrarray_find_binary_search_range(gconstpointer *list, /*****************************************************************************/ /** - * nm_utils_array_find_binary_search: + * nm_array_find_bsearch: * @list: the list to search. It must be sorted according to @cmpfcn ordering. - * @elem_size: the size in bytes of each element in the list * @len: the number of elements in @list + * @elem_size: the size in bytes of each element in the list * @needle: the value that is searched * @cmpfcn: the compare function. The elements @list are passed as first * argument to @cmpfcn, while @needle is passed as second. Usually, the @@ -4357,42 +3908,14 @@ nm_utils_ptrarray_find_binary_search_range(gconstpointer *list, * position where it should be. */ gssize -nm_utils_array_find_binary_search(gconstpointer list, - gsize elem_size, - gsize len, - gconstpointer needle, - GCompareDataFunc cmpfcn, - gpointer user_data) -{ - gssize imin, imax, imid; - int cmp; - - g_return_val_if_fail(list || !len, ~((gssize) 0)); - g_return_val_if_fail(cmpfcn, ~((gssize) 0)); - g_return_val_if_fail(elem_size > 0, ~((gssize) 0)); - - imin = 0; - if (len == 0) - return ~imin; - - imax = len - 1; - - while (imin <= imax) { - imid = imin + (imax - imin) / 2; - - cmp = cmpfcn(&((const char *) list)[elem_size * imid], needle, user_data); - if (cmp == 0) - return imid; - - if (cmp < 0) - imin = imid + 1; - else - imax = imid - 1; - } - - /* return the inverse of @imin. This is a negative number, but - * also is ~imin the position where the value should be inserted. */ - return ~imin; +nm_array_find_bsearch(gconstpointer list, + gsize len, + gsize elem_size, + gconstpointer needle, + GCompareDataFunc cmpfcn, + gpointer user_data) +{ + return nm_array_find_bsearch_inline(list, len, elem_size, needle, cmpfcn, user_data); } /*****************************************************************************/ @@ -6370,98 +5893,6 @@ _nm_utils_ssid_to_string_gbytes(GBytes *ssid) /*****************************************************************************/ -gconstpointer -nm_utils_ipx_address_clear_host_address(int family, gpointer dst, gconstpointer src, guint32 plen) -{ - g_return_val_if_fail(dst, NULL); - - switch (family) { - case AF_INET: - g_return_val_if_fail(plen <= 32, NULL); - - if (!src) { - /* allow "self-assignment", by specifying %NULL as source. */ - src = dst; - } - - *((guint32 *) dst) = nm_utils_ip4_address_clear_host_address(*((guint32 *) src), plen); - break; - case AF_INET6: - nm_utils_ip6_address_clear_host_address(dst, src, plen); - break; - default: - g_return_val_if_reached(NULL); - } - return dst; -} - -/* nm_utils_ip6_address_clear_host_address: - * @dst: destination output buffer, will contain the network part of the @src address - * @src: source ip6 address. If NULL, this does an in-place update of @dst. - * Also, @src and @dst are allowed to be the same pointers. - * @plen: prefix length of network - * - * Note: this function is self assignment safe, to update @src inplace, set both - * @dst and @src to the same destination or set @src NULL. - */ -const struct in6_addr * -nm_utils_ip6_address_clear_host_address(struct in6_addr *dst, - const struct in6_addr *src, - guint32 plen) -{ - g_return_val_if_fail(plen <= 128, NULL); - g_return_val_if_fail(dst, NULL); - - if (!src) - src = dst; - - if (plen < 128) { - guint nbytes = plen / 8; - guint nbits = plen % 8; - - if (nbytes && dst != src) - memcpy(dst, src, nbytes); - if (nbits) { - dst->s6_addr[nbytes] = (src->s6_addr[nbytes] & (0xFF << (8 - nbits))); - nbytes++; - } - if (nbytes <= 15) - memset(&dst->s6_addr[nbytes], 0, 16 - nbytes); - } else if (src != dst) - *dst = *src; - - return dst; -} - -int -nm_utils_ip6_address_same_prefix_cmp(const struct in6_addr *addr_a, - const struct in6_addr *addr_b, - guint32 plen) -{ - int nbytes; - guint8 va, vb, m; - - if (plen >= 128) { - nm_assert(plen == 128); - NM_CMP_DIRECT_MEMCMP(addr_a, addr_b, sizeof(struct in6_addr)); - } else { - nbytes = plen / 8; - if (nbytes) - NM_CMP_DIRECT_MEMCMP(addr_a, addr_b, nbytes); - - plen = plen % 8; - if (plen != 0) { - m = ~((1 << (8 - plen)) - 1); - va = ((((const guint8 *) addr_a))[nbytes]) & m; - vb = ((((const guint8 *) addr_b))[nbytes]) & m; - NM_CMP_DIRECT(va, vb); - } - } - return 0; -} - -/*****************************************************************************/ - #define IPV6_PROPERTY_DIR "/proc/sys/net/ipv6/conf/" #define IPV4_PROPERTY_DIR "/proc/sys/net/ipv4/conf/" diff --git a/src/libnm-glib-aux/nm-shared-utils.h b/src/libnm-glib-aux/nm-shared-utils.h index 53cf7f3e..083ed137 100644 --- a/src/libnm-glib-aux/nm-shared-utils.h +++ b/src/libnm-glib-aux/nm-shared-utils.h @@ -139,6 +139,8 @@ typedef enum { NM_LINK_TYPE_VETH, NM_LINK_TYPE_VLAN, NM_LINK_TYPE_VRF, + NM_LINK_TYPE_VTI, + NM_LINK_TYPE_VTI6, NM_LINK_TYPE_VXLAN, NM_LINK_TYPE_WIREGUARD, #define _NM_LINK_TYPE_SW_LAST NM_LINK_TYPE_WIREGUARD @@ -198,6 +200,13 @@ typedef struct { #define NM_ETHER_ADDR_INIT(...) ((NMEtherAddr) _NM_ETHER_ADDR_INIT(__VA_ARGS__)) +struct _NMIPAddr; +extern const struct _NMIPAddr nm_ip_addr_zero; + +/* Let's reuse nm_ip_addr_zero also for nm_ether_addr_zero. It's a union that + * also contains a NMEtherAddr field. */ +#define nm_ether_addr_zero (*((const NMEtherAddr *) ((gconstpointer) &nm_ip_addr_zero))) + static inline int nm_ether_addr_cmp(const NMEtherAddr *a, const NMEtherAddr *b) { @@ -212,126 +221,10 @@ nm_ether_addr_equal(const NMEtherAddr *a, const NMEtherAddr *b) return nm_ether_addr_cmp(a, b) == 0; } -/*****************************************************************************/ - -typedef struct { - union { - guint8 addr_ptr[sizeof(struct in6_addr)]; - in_addr_t addr4; - struct in_addr addr4_struct; - struct in6_addr addr6; - - /* NMIPAddr is really a union for IP addresses. - * However, as ethernet addresses fit in here nicely, use - * it also for an ethernet MAC address. */ - guint8 ether_addr_octet[6 /*ETH_ALEN*/]; - NMEtherAddr ether_addr; - - guint8 array[sizeof(struct in6_addr)]; - }; -} NMIPAddr; - -#define NM_IP_ADDR_INIT \ - { \ - .array = { 0 } \ - } - -extern const NMIPAddr nm_ip_addr_zero; - -#define nm_ether_addr_zero (nm_ip_addr_zero.ether_addr) - -static inline int -nm_ip_addr_cmp(int addr_family, gconstpointer a, gconstpointer b) -{ - /* Note that @a and @b are not required to be full NMIPAddr unions. - * Depending on @addr_family, they can also be only in_addr_t or - * struct in6_addr. */ - NM_CMP_SELF(a, b); - NM_CMP_DIRECT_MEMCMP(a, b, nm_utils_addr_family_to_size(addr_family)); - return 0; -} - -int nm_ip_addr_cmp_for_sort(gconstpointer a, gconstpointer b, gpointer user_data); - static inline gboolean -nm_ip_addr_equal(int addr_family, gconstpointer a, gconstpointer b) +nm_ether_addr_is_zero(const NMEtherAddr *a) { - return nm_ip_addr_cmp(addr_family, a, b) == 0; -} - -static inline gboolean -nm_ip_addr_is_null(int addr_family, gconstpointer addr) -{ - nm_assert(addr); - - if (NM_IS_IPv4(addr_family)) { - in_addr_t t; - - /* also for in_addr_t type (AF_INET), we accept that the pointer might - * be unaligned. */ - memcpy(&t, addr, sizeof(t)); - return t == 0; - } - - return IN6_IS_ADDR_UNSPECIFIED((const struct in6_addr *) addr); -} - -static inline void -nm_ip_addr_set(int addr_family, gpointer dst, gconstpointer src) -{ - nm_assert(dst); - nm_assert(src); - - /* this MUST use memcpy() to support unaligned src/dst pointers. */ - memcpy(dst, src, nm_utils_addr_family_to_size(addr_family)); - - /* Note that @dst is not necessarily a NMIPAddr, it could also be just - * an in_addr_t/struct in6_addr. We thus can only set the bytes that - * we know are present based on the address family. - * - * Using this function to initialize an NMIPAddr union (for IPv4) leaves - * uninitalized bytes. Avoid that by using nm_ip_addr_init() instead. */ -} - -static inline NMIPAddr -nm_ip_addr_init(int addr_family, gconstpointer src) -{ - NMIPAddr a; - - nm_assert_addr_family(addr_family); - nm_assert(src); - - G_STATIC_ASSERT_EXPR(sizeof(NMIPAddr) == sizeof(struct in6_addr)); - - /* this MUST use memcpy() to support unaligned src/dst pointers. */ - - if (NM_IS_IPv4(addr_family)) { - memcpy(&a, src, sizeof(in_addr_t)); - - /* ensure all bytes of the union are initialized. If only to make - * valgrind happy. */ - memset(&a.array[sizeof(in_addr_t)], 0, sizeof(a) - sizeof(in_addr_t)); - } else - memcpy(&a, src, sizeof(struct in6_addr)); - - return a; -} - -gboolean nm_ip_addr_set_from_untrusted(int addr_family, - gpointer dst, - gconstpointer src, - gsize src_len, - int *out_addr_family); - -gboolean -nm_ip_addr_set_from_variant(int addr_family, gpointer dst, GVariant *variant, int *out_addr_family); - -static inline gconstpointer -nm_ip_addr_from_packed_array(int addr_family, gconstpointer ipaddr_arr, gsize idx) -{ - return NM_IS_IPv4(addr_family) - ? ((gconstpointer) & (((const struct in_addr *) ipaddr_arr)[idx])) - : ((gconstpointer) & (((const struct in6_addr *) ipaddr_arr)[idx])); + return nm_memeq(a, &nm_ether_addr_zero, sizeof(NMEtherAddr)); } /*****************************************************************************/ @@ -343,6 +236,7 @@ nm_utils_ether_addr_cmp(const struct ether_addr *a1, const struct ether_addr *a2 { nm_assert(a1); nm_assert(a2); + return memcmp(a1, a2, 6 /*ETH_ALEN*/); } @@ -377,11 +271,36 @@ typedef struct _NMUtilsIPv6IfaceId { } \ } -void nm_utils_ipv6_addr_set_interface_identifier(struct in6_addr *addr, - const NMUtilsIPv6IfaceId *iid); +/** + * nm_utils_ipv6_addr_set_interface_identifier: + * @addr: output token encoded as %in6_addr + * @iid: %NMUtilsIPv6IfaceId interface identifier + * + * Converts the %NMUtilsIPv6IfaceId to an %in6_addr (suitable for use + * with Linux platform). This only copies the lower 8 bytes, ignoring + * the /64 network prefix which is expected to be all-zero for a valid + * token. + */ +static inline void +nm_utils_ipv6_addr_set_interface_identifier(struct in6_addr *addr, const NMUtilsIPv6IfaceId *iid) +{ + memcpy(addr->s6_addr + 8, &iid->id_u8, 8); +} -void nm_utils_ipv6_interface_identifier_get_from_addr(NMUtilsIPv6IfaceId *iid, - const struct in6_addr *addr); +/** + * nm_utils_ipv6_interface_identifier_get_from_addr: + * @iid: output %NMUtilsIPv6IfaceId interface identifier set from the token + * @addr: token encoded as %in6_addr + * + * Converts the %in6_addr encoded token (as used by Linux platform) to + * the interface identifier. + */ +static inline void +nm_utils_ipv6_interface_identifier_get_from_addr(NMUtilsIPv6IfaceId *iid, + const struct in6_addr *addr) +{ + memcpy(iid, addr->s6_addr + 8, 8); +} gboolean nm_utils_ipv6_interface_identifier_get_from_token(NMUtilsIPv6IfaceId *iid, const char *token); @@ -397,219 +316,6 @@ gboolean nm_utils_get_ipv6_interface_identifier(NMLinkType link_type, /*****************************************************************************/ -static inline guint32 -_nm_utils_ip4_netmask_to_prefix(in_addr_t subnetmask) -{ - G_STATIC_ASSERT_EXPR(__SIZEOF_INT__ == 4); - G_STATIC_ASSERT_EXPR(sizeof(int) == 4); - G_STATIC_ASSERT_EXPR(sizeof(guint) == 4); - G_STATIC_ASSERT_EXPR(sizeof(subnetmask) == 4); - - return ((subnetmask != 0u) ? (guint32) (32 - __builtin_ctz(ntohl(subnetmask))) : 0u); -} - -/** - * _nm_utils_ip4_prefix_to_netmask: - * @prefix: a CIDR prefix - * - * Returns: the netmask represented by the prefix, in network byte order - **/ -static inline in_addr_t -_nm_utils_ip4_prefix_to_netmask(guint32 prefix) -{ - nm_assert(prefix <= 32); - return prefix < 32 ? ~htonl(0xFFFFFFFFu >> prefix) : 0xFFFFFFFFu; -} - -guint32 _nm_utils_ip4_get_default_prefix0(in_addr_t ip); -guint32 _nm_utils_ip4_get_default_prefix(in_addr_t ip); - -gconstpointer -nm_utils_ipx_address_clear_host_address(int family, gpointer dst, gconstpointer src, guint32 plen); - -/* nm_utils_ip4_address_clear_host_address: - * @addr: source ip6 address - * @plen: prefix length of network - * - * returns: the input address, with the host address set to 0. - */ -static inline in_addr_t -nm_utils_ip4_address_clear_host_address(in_addr_t addr, guint32 plen) -{ - return addr & _nm_utils_ip4_prefix_to_netmask(plen); -} - -const struct in6_addr *nm_utils_ip6_address_clear_host_address(struct in6_addr *dst, - const struct in6_addr *src, - guint32 plen); - -static inline int -nm_utils_ip4_address_same_prefix_cmp(in_addr_t addr_a, in_addr_t addr_b, guint32 plen) -{ - NM_CMP_DIRECT(htonl(nm_utils_ip4_address_clear_host_address(addr_a, plen)), - htonl(nm_utils_ip4_address_clear_host_address(addr_b, plen))); - return 0; -} - -int nm_utils_ip6_address_same_prefix_cmp(const struct in6_addr *addr_a, - const struct in6_addr *addr_b, - guint32 plen); - -static inline gboolean -nm_utils_ip4_address_same_prefix(in_addr_t addr_a, in_addr_t addr_b, guint32 plen) -{ - return nm_utils_ip4_address_same_prefix_cmp(addr_a, addr_b, plen) == 0; -} - -static inline gboolean -nm_utils_ip6_address_same_prefix(const struct in6_addr *addr_a, - const struct in6_addr *addr_b, - guint8 plen) -{ - return nm_utils_ip6_address_same_prefix_cmp(addr_a, addr_b, plen) == 0; -} - -static inline int -nm_utils_ip_address_same_prefix_cmp(int addr_family, - gconstpointer addr_a, - gconstpointer addr_b, - guint8 plen) -{ - NM_CMP_SELF(addr_a, addr_b); - - if (NM_IS_IPv4(addr_family)) { - return nm_utils_ip4_address_same_prefix_cmp(*((const in_addr_t *) addr_a), - *((const in_addr_t *) addr_b), - plen); - } - - return nm_utils_ip6_address_same_prefix_cmp(addr_a, addr_b, plen); -} - -static inline gboolean -nm_utils_ip_address_same_prefix(int addr_family, - gconstpointer addr_a, - gconstpointer addr_b, - guint8 plen) -{ - return nm_utils_ip_address_same_prefix_cmp(addr_family, addr_a, addr_b, plen) == 0; -} - -#define NM_CMP_DIRECT_IN4ADDR_SAME_PREFIX(a, b, plen) \ - NM_CMP_RETURN(nm_utils_ip4_address_same_prefix_cmp((a), (b), (plen))) - -#define NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX(a, b, plen) \ - NM_CMP_RETURN(nm_utils_ip6_address_same_prefix_cmp((a), (b), (plen))) - -/*****************************************************************************/ - -gboolean nm_utils_ip_is_site_local(int addr_family, const void *address); -gboolean nm_utils_ip6_is_ula(const struct in6_addr *address); - -/*****************************************************************************/ - -#define NM_IPV4LL_NETWORK ((in_addr_t) htonl(0xA9FE0000lu)) -#define NM_IPV4LL_NETMASK ((in_addr_t) htonl(0xFFFF0000lu)) - -static inline gboolean -nm_utils_ip4_address_is_loopback(in_addr_t addr) -{ - /* There is also IN_LOOPBACK() in <linux/in.h>, but there the - * argument is in host order not `in_addr_t`. */ - return (addr & htonl(0xFF000000u)) == htonl(0x7F000000u); -} - -static inline gboolean -nm_utils_ip4_address_is_link_local(in_addr_t addr) -{ - return (addr & NM_IPV4LL_NETMASK) == NM_IPV4LL_NETWORK; -} - -static inline gboolean -nm_utils_ip4_address_is_zeronet(in_addr_t network) -{ - /* Same as ipv4_is_zeronet() from kernel's include/linux/in.h. */ - return (network & htonl(0xFF000000u)) == htonl(0x00000000u); -} - -/*****************************************************************************/ - -#define NM_UTILS_INET_ADDRSTRLEN INET6_ADDRSTRLEN - -/* Forward declare function so we don't have to drag in <arpa/inet.h>. */ -const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); - -static inline const char * -nm_utils_inet_ntop(int addr_family, gconstpointer addr, char *dst) -{ - const char *s; - - 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); - -/*****************************************************************************/ - -/* this enum is compatible with ICMPV6_ROUTER_PREF_* (from <linux/icmpv6.h>, - * the values for netlink attribute RTA_PREF) and "enum ndp_route_preference" - * from <ndp.h>. */ -typedef enum _nm_packed { - NM_ICMPV6_ROUTER_PREF_MEDIUM = 0x0, /* ICMPV6_ROUTER_PREF_MEDIUM */ - NM_ICMPV6_ROUTER_PREF_LOW = 0x3, /* ICMPV6_ROUTER_PREF_LOW */ - NM_ICMPV6_ROUTER_PREF_HIGH = 0x1, /* ICMPV6_ROUTER_PREF_HIGH */ - NM_ICMPV6_ROUTER_PREF_INVALID = 0x2, /* ICMPV6_ROUTER_PREF_INVALID */ -} NMIcmpv6RouterPref; - -const char *nm_icmpv6_router_pref_to_string(NMIcmpv6RouterPref pref, char *buf, gsize len); - -/*****************************************************************************/ - gboolean nm_utils_memeqzero(gconstpointer data, gsize length); /*****************************************************************************/ @@ -1044,31 +750,6 @@ nm_utils_escaped_tokens_options_escape_val(const char *val, char **out_to_free) /*****************************************************************************/ -gboolean nm_utils_parse_inaddr_bin_full(int addr_family, - gboolean accept_legacy, - const char *text, - int *out_addr_family, - gpointer out_addr); -static inline gboolean -nm_utils_parse_inaddr_bin(int addr_family, - const char *text, - int *out_addr_family, - gpointer out_addr) -{ - return nm_utils_parse_inaddr_bin_full(addr_family, FALSE, text, out_addr_family, out_addr); -} - -gboolean nm_utils_parse_inaddr(int addr_family, const char *text, char **out_addr); - -gboolean nm_utils_parse_inaddr_prefix_bin(int addr_family, - const char *text, - int *out_addr_family, - gpointer out_addr, - int *out_prefix); - -gboolean -nm_utils_parse_inaddr_prefix(int addr_family, const char *text, char **out_addr, int *out_prefix); - gboolean nm_utils_parse_next_line(const char **inout_ptr, gsize *inout_len, const char **out_line, @@ -1335,6 +1016,7 @@ typedef enum { * message from the device that returned the *highest* error code, * in the hope that this message makes the most sense for the caller. * */ + NM_UTILS_ERROR_CONNECTION_AVAILABLE_STRICTLY_UNMANAGED_DEVICE, NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, NM_UTILS_ERROR_CONNECTION_AVAILABLE_UNMANAGED_DEVICE, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, @@ -1662,6 +1344,10 @@ nm_g_variant_new_au(const guint32 *data, gsize len) return g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, data, len, sizeof(guint32)); } +struct _NMIPAddr; + +extern const struct _NMIPAddr nm_ip_addr_zero; + static inline GVariant * nm_g_variant_new_ay_inaddr(int addr_family, gconstpointer addr) { @@ -2210,6 +1896,8 @@ char *nm_utils_g_slist_strlist_join(const GSList *a, const char *separator); static inline gpointer nm_g_array_data(const GArray *arr) { + /* You may want to use nm_g_array_first_p() instead, which can assert + * for the expected type. */ return arr ? arr->data : NULL; } @@ -2226,43 +1914,67 @@ nm_g_array_unref(GArray *arr) g_array_unref(arr); } -#define nm_g_array_first(arr, Type) \ - ({ \ - GArray *const _arr = (arr); \ - \ - nm_assert(_arr); \ - nm_assert(sizeof(Type) == g_array_get_element_size(_arr)); \ - nm_assert(_arr->len > 0); \ - \ - &g_array_index(arr, Type, 0); \ - }) - -#define nm_g_array_last(arr, Type) \ - ({ \ - GArray *const _arr = (arr); \ - \ - nm_assert(_arr); \ - nm_assert(sizeof(Type) == g_array_get_element_size(_arr)); \ - nm_assert(_arr->len > 0); \ - \ - &g_array_index(arr, Type, _arr->len - 1u); \ - }) - /* Similar to g_array_index(). The differences are * - this does nm_assert() checks that the arguments are valid. - * - returns a pointer to the element. */ -#define nm_g_array_index_p(arr, Type, idx) \ - ({ \ - GArray *const _arr_55 = (arr); \ - const guint _idx_55 = (idx); \ - \ - nm_assert(_arr_55); \ - nm_assert(sizeof(Type) == g_array_get_element_size(_arr_55)); \ - nm_assert(_idx_55 < _arr_55->len); \ - \ - &g_array_index(_arr_55, Type, _idx_55); \ + * - returns a pointer to the element. + * - it asserts that @idx is <= arr->len. That is, it allows + * to get a pointer after the data, of course, you are not + * allowed to dereference in that case. + * - in particular, unlike nm_g_array_index(), you are allowed to call this + * with "arr" NULL (for index zero) or with "arr->data" NULL + * (for index zero). In that case, NULL is returned. + * + * When accessing index zero, then this returns NULL if-and-only-if + * "arr" is NULL or "arr->data" is NULL. In all other cases, this + * returns the pointer &((Type*) arr->data)[idx]. Note that the pointer + * may not be followed, if "idx" is equal to "arr->len". */ +#define nm_g_array_index_p(arr, Type, idx) \ + ({ \ + const GArray *const _arr_55 = (arr); \ + const guint _idx_55 = (idx); \ + \ + nm_assert(_arr_55 || _idx_55 == 0); \ + nm_assert(_idx_55 <= (_arr_55 ? _arr_55->len : 0u)); \ + nm_assert(!_arr_55 || sizeof(Type) == g_array_get_element_size((GArray *) _arr_55)); \ + \ + ((_arr_55 && _arr_55->data) ? &(((Type *) ((gpointer) _arr_55->data))[_idx_55]) : NULL); \ }) +/* Very similar to g_array_index(). + * - nm_assert() that arguments are valid. + * - returns an lvalue to the element. + * - similar to nm_g_array_index_p(), but dereferences the pointer. + * - one difference to nm_g_array_index_p() is that it @idx MUST be + * smaller than arr->len (unlike nm_g_array_index_p() which allows + * access one element past the buffer. */ +#define nm_g_array_index(arr, Type, idx) \ + (*({ \ + const GArray *const _arr_55 = (arr); \ + const guint _idx_55 = (idx); \ + \ + nm_assert(_arr_55); \ + nm_assert(sizeof(Type) == g_array_get_element_size((GArray *) _arr_55)); \ + nm_assert(_idx_55 < _arr_55->len); \ + \ + &g_array_index((GArray *) _arr_55, Type, _idx_55); \ + })) + +#define nm_g_array_first(arr, Type) nm_g_array_index(arr, Type, 0) + +#define nm_g_array_first_p(arr, Type) nm_g_array_index_p(arr, Type, 0) + +/* Same as g_array_index(arr, Type, arr->len-1). */ +#define nm_g_array_last(arr, Type) \ + (*({ \ + const GArray *const _arr = (arr); \ + \ + nm_assert(_arr); \ + nm_assert(sizeof(Type) == g_array_get_element_size((GArray *) _arr)); \ + nm_assert(_arr->len > 0); \ + \ + &g_array_index((GArray *) arr, Type, _arr->len - 1u); \ + })) + #define nm_g_array_append_new(arr, Type) \ ({ \ GArray *const _arr = (arr); \ @@ -2279,6 +1991,20 @@ nm_g_array_unref(GArray *arr) &g_array_index(arr, Type, _len); \ }) +#define nm_g_array_append_simple(arr, val) \ + G_STMT_START \ + { \ + /* Similar to `g_array_append_val()`, but `g_array_append_val()` + * only works with lvalues. That makes sense if the value is a larger + * struct and you anyway have a pointer to it. It doesn't make sense + * if you have a list of int and want to append a number literal. + * + * nm_g_array_append_simple() is different. It depends on typeof(val) + * to be compatible. */ \ + (*nm_g_array_append_new((arr), typeof(val))) = (val); \ + } \ + G_STMT_END + /*****************************************************************************/ static inline GPtrArray * @@ -2342,40 +2068,29 @@ nm_g_ptr_array_pdata(const GPtrArray *arr) return arr ? arr->pdata : NULL; } -GPtrArray *_nm_g_ptr_array_copy(GPtrArray *array, - GCopyFunc func, - gpointer user_data, - GDestroyNotify element_free_func); - /** - * nm_g_ptr_array_copy: + * nm_g_ptr_array_new_clone: * @array: the #GPtrArray to clone. * @func: the copy function. * @user_data: the user data for the copy function - * @element_free_func: the free function of the elements. @array MUST have - * the same element_free_func. This argument is only used on older - * glib, that doesn't support g_ptr_array_copy(). + * @element_free_func: the free function of the elements. This function + * must agree with the owner-ship semantics of @func. * * This is a replacement for g_ptr_array_copy(), which is not available * before glib 2.62. Since GPtrArray does not allow to access the internal * element_free_func, we cannot add a compatibility implementation of g_ptr_array_copy() - * and the user must provide a suitable destroy function. + * as the caller must provide the correct element_free_func. * - * Note that the @element_free_func MUST correspond to free function set in @array. + * So this is not the same as g_ptr_array_copy() (hence the different name) because + * g_ptr_array_copy() uses the free func of the source array, which we cannot access. + * With g_ptr_array_copy() the copy func must agree with the array's free func. + * Here, it must agree with the provided @element_free_func. This allows for example + * to do a shallow-copy without cloning the elements (which you cannot do with g_ptr_array_copy()). */ -#if GLIB_CHECK_VERSION(2, 62, 0) -#define nm_g_ptr_array_copy(array, func, user_data, element_free_func) \ - ({ \ - _nm_unused GDestroyNotify const _element_free_func = (element_free_func); \ - \ - G_GNUC_BEGIN_IGNORE_DEPRECATIONS; \ - g_ptr_array_copy((array), (func), (user_data)); \ - G_GNUC_END_IGNORE_DEPRECATIONS; \ - }) -#else -#define nm_g_ptr_array_copy(array, func, user_data, element_free_func) \ - _nm_g_ptr_array_copy((array), (func), (user_data), (element_free_func)) -#endif +GPtrArray *nm_g_ptr_array_new_clone(GPtrArray *array, + GCopyFunc func, + gpointer user_data, + GDestroyNotify element_free_func); /*****************************************************************************/ @@ -2443,42 +2158,95 @@ gboolean nm_utils_ptrarray_is_sorted(gconstpointer *list, GCompareDataFunc cmpfcn, gpointer user_data); -gssize nm_utils_ptrarray_find_binary_search(gconstpointer *list, - gsize len, - gconstpointer needle, - GCompareDataFunc cmpfcn, - gpointer user_data); - -gssize nm_utils_ptrarray_find_binary_search_range(gconstpointer *list, - gsize len, - gconstpointer needle, - GCompareDataFunc cmpfcn, - gpointer user_data, - gssize *out_idx_first, - gssize *out_idx_last); - -#define nm_strv_find_binary_search(strv, len, needle) \ - ({ \ - const char *const *const _strv = NM_CAST_STRV_CC(strv); \ - const gsize _len = (len); \ - const char *const _needle = (needle); \ - \ - nm_assert(_len == 0 || _strv); \ - nm_assert(_needle); \ - \ - nm_utils_ptrarray_find_binary_search((gconstpointer *) _strv, \ - _len, \ - _needle, \ - nm_strcmp_with_data, \ - NULL); \ +gssize nm_ptrarray_find_bsearch(gconstpointer *list, + gsize len, + gconstpointer needle, + GCompareDataFunc cmpfcn, + gpointer user_data); + +gssize nm_ptrarray_find_bsearch_range(gconstpointer *list, + gsize len, + gconstpointer needle, + GCompareDataFunc cmpfcn, + gpointer user_data, + gssize *out_idx_first, + gssize *out_idx_last); + +#define nm_strv_find_binary_search(strv, len, needle) \ + ({ \ + const char *const *const _strv = NM_CAST_STRV_CC(strv); \ + const gsize _len = (len); \ + const char *const _needle = (needle); \ + \ + nm_assert(_len == 0 || _strv); \ + nm_assert(_needle); \ + \ + nm_ptrarray_find_bsearch((gconstpointer *) _strv, \ + _len, \ + _needle, \ + nm_strcmp_with_data, \ + NULL); \ }) -gssize nm_utils_array_find_binary_search(gconstpointer list, - gsize elem_size, - gsize len, - gconstpointer needle, - GCompareDataFunc cmpfcn, - gpointer user_data); +/*****************************************************************************/ + +#ifdef NM_WANT_NM_ARRAY_FIND_BSEARCH_INLINE +/** + * nm_array_find_bsearch_inline: + * + * An inlined version of nm_array_find_bsearch(). See there. + * Define NM_WANT_NM_ARRAY_FIND_BSEARCH_INLINE to get it. + */ +_nm_always_inline static inline gssize +nm_array_find_bsearch_inline(gconstpointer list, + gsize len, + gsize elem_size, + gconstpointer needle, + GCompareDataFunc cmpfcn, + gpointer user_data) +{ + gssize imax; + gssize imid; + gssize imin; + int cmp; + + nm_assert(list || len == 0); + nm_assert(cmpfcn); + nm_assert(elem_size > 0); + + imin = 0; + if (len == 0) + return ~imin; + + imax = len - 1; + + while (imin <= imax) { + imid = imin + (imax - imin) / 2; + + cmp = cmpfcn(&((const char *) list)[elem_size * imid], needle, user_data); + if (cmp == 0) + return imid; + + if (cmp < 0) + imin = imid + 1; + else + imax = imid - 1; + } + + /* return the inverse of @imin. This is a negative number, but + * also is ~imin the position where the value should be inserted. */ + return ~imin; +} +#endif + +gssize nm_array_find_bsearch(gconstpointer list, + gsize len, + gsize elem_size, + gconstpointer needle, + GCompareDataFunc cmpfcn, + gpointer user_data); + +/*****************************************************************************/ gssize nm_utils_ptrarray_find_first(gconstpointer *list, gssize len, gconstpointer needle); @@ -2731,7 +2499,7 @@ nm_strv_ptrarray_clone(const GPtrArray *src, gboolean null_if_empty) { if (!src || (null_if_empty && src->len == 0)) return NULL; - return nm_g_ptr_array_copy((GPtrArray *) src, nm_copy_func_g_strdup, NULL, g_free); + return nm_g_ptr_array_new_clone((GPtrArray *) src, nm_copy_func_g_strdup, NULL, g_free); } static inline void @@ -2894,6 +2662,9 @@ nm_ether_addr_to_string(const NMEtherAddr *ether_addr, char sbuf[static(sizeof(N #define nm_ether_addr_to_string_a(ether_addr) \ nm_ether_addr_to_string((ether_addr), g_alloca(sizeof(NMEtherAddr) * 3)) +#define nm_ether_addr_to_string_dup(ether_addr) \ + ((char *) nm_ether_addr_to_string((ether_addr), g_malloc(sizeof(NMEtherAddr) * 3))) + NMEtherAddr *nm_ether_addr_from_string(NMEtherAddr *addr, const char *str); guint8 *nm_utils_hexstr2bin_full(const char *hexstr, @@ -3211,7 +2982,7 @@ nm_strvarray_add(GArray *array, const char *str) static inline const char * nm_strvarray_get_idx(GArray *array, guint idx) { - return *nm_g_array_index_p(array, const char *, idx); + return nm_g_array_index(array, const char *, idx); } static inline const char *const * @@ -3310,12 +3081,12 @@ nm_strvarray_remove_first(GArray *strv, const char *needle) static inline int nm_strvarray_cmp(const GArray *a, const GArray *b) { + nm_assert(!a || sizeof(const char *const *) == g_array_get_element_size((GArray *) a)); + nm_assert(!b || sizeof(const char *const *) == g_array_get_element_size((GArray *) b)); + NM_CMP_SELF(a, b); - return nm_strv_cmp_n((const char *const *) a->data, - a->len, - (const char *const *) b->data, - b->len); + return nm_strv_cmp_n(nm_g_array_data(a), a->len, nm_g_array_data(b), b->len); } #define nm_strvarray_equal(a, b) (nm_strvarray_cmp((a), (b)) == 0) @@ -3323,10 +3094,9 @@ nm_strvarray_cmp(const GArray *a, const GArray *b) static inline int _nm_strvarray_cmp_strv(const GArray *strv, const char *const *ss, gsize ss_len) { - return nm_strv_cmp_n(strv ? (const char *const *) strv->data : NULL, - strv ? ((gssize) strv->len) : -1, - ss, - ss_len); + nm_assert(!strv || sizeof(const char *const *) == g_array_get_element_size((GArray *) strv)); + + return nm_strv_cmp_n(nm_g_array_data(strv), strv ? ((gssize) strv->len) : -1, ss, ss_len); } #define nm_strvarray_cmp_strv(strv, ss, ss_len) \ _nm_strvarray_cmp_strv((strv), NM_CAST_STRV_CC(ss), (ss_len)) diff --git a/src/libnm-glib-aux/nm-str-buf.h b/src/libnm-glib-aux/nm-str-buf.h index e8b51208..32ab8da8 100644 --- a/src/libnm-glib-aux/nm-str-buf.h +++ b/src/libnm-glib-aux/nm-str-buf.h @@ -447,6 +447,24 @@ nm_str_buf_get_str(NMStrBuf *strbuf) return strbuf->_priv_str; } +static inline char * +nm_str_buf_dup_str(NMStrBuf *strbuf) +{ + _nm_str_buf_assert(strbuf); + + /* Gives almost the same as g_strdup(nm_str_buf_get_str(strbuf)). The difference + * is: + * - unlike nm_str_buf_get_str(), it does not need to allocate + * one more character to NUL terminate the string. + * - it does not require an additional strlen(), because we + * already know the length. */ + + if (!strbuf->_priv_str) + return NULL; + + return nm_memdup_nul(strbuf->_priv_str, strbuf->_priv_len); +} + /** * nm_str_buf_get_str_unsafe: * @strbuf: the buffer @@ -530,10 +548,7 @@ nm_str_buf_finalize(NMStrBuf *strbuf, gsize *out_len) char *str = g_steal_pointer(&strbuf->_priv_str); char *result; - result = g_new(char, strbuf->_priv_len + 1u); - memcpy(result, str, strbuf->_priv_len); - result[strbuf->_priv_len] = '\0'; - + result = nm_memdup_nul(str, strbuf->_priv_len); if (strbuf->_priv_do_bzero_mem) nm_explicit_bzero(str, strbuf->_priv_len); return result; diff --git a/src/libnm-glib-aux/nm-test-utils.h b/src/libnm-glib-aux/nm-test-utils.h index d338d0af..41e1a6ce 100644 --- a/src/libnm-glib-aux/nm-test-utils.h +++ b/src/libnm-glib-aux/nm-test-utils.h @@ -39,7 +39,7 @@ * depending on the test. See nmtst_is_debug(). * Known differences: * - a test might leave the logging level unspecified. In this case, running in - * debug mode, will turn on DEBUG logging, otherwise WARN logging only. + * debug mode, will turn on TRACE logging, otherwise WARN logging only. * - if G_MESSAGES_DEBUG is unset, nm-test will set G_MESSAGES_DEBUG=all * for tests that don't do assert-logging. * Debug mode is determined as follows (highest priority first): @@ -85,6 +85,9 @@ #undef g_assertion_message_expr #endif +#undef NDEBUG +#include <assert.h> + #include <arpa/inet.h> #include <stdio.h> #include <unistd.h> @@ -221,17 +224,24 @@ /*****************************************************************************/ +struct __nmtst_testdata_track { + gpointer data; + GDestroyNotify destroy_notify; +}; + struct __nmtst_internal { - GRand *rand0; - guint32 rand_seed; - GRand *rand; - gboolean is_debug; - gboolean assert_logging; - gboolean no_expect_message; - gboolean test_quick; - gboolean test_tap_log; - char *sudo_cmd; - char **orig_argv; + GRand *rand0; + guint32 rand_seed; + GRand *rand; + gboolean is_debug; + gboolean assert_logging; + gboolean no_expect_message; + gboolean test_quick; + gboolean test_tap_log; + char *sudo_cmd; + char **orig_argv; + const char *testpath; + GArray *testdata_track_array; }; extern struct __nmtst_internal __nmtst_internal; @@ -252,6 +262,62 @@ nmtst_initialized(void) return !!__nmtst_internal.rand0; } +/*****************************************************************************/ + +static inline void +_nmtst_testdata_track_clear_func(gpointer ptr) +{ + struct __nmtst_testdata_track *d = ptr; + + if (d->destroy_notify) + d->destroy_notify(d->data); + memset(d, 0, sizeof(*d)); +} + +static inline void +_nmtst_testdata_track_add(gpointer data, GDestroyNotify destroy_notify) +{ + struct __nmtst_testdata_track d = { + .data = data, + .destroy_notify = destroy_notify, + }; + + g_assert(data); + g_assert(destroy_notify); + g_assert(__nmtst_internal.testdata_track_array); + + g_array_append_val(__nmtst_internal.testdata_track_array, d); +} + +static inline void +_nmtst_testdata_track_steal(gpointer data) +{ + struct __nmtst_testdata_track *d; + guint i; + + g_assert(data); + g_assert(__nmtst_internal.testdata_track_array); + + for (i = 0; i < __nmtst_internal.testdata_track_array->len; i++) { + d = &g_array_index(__nmtst_internal.testdata_track_array, struct __nmtst_testdata_track, i); + + if (d->data != data) + continue; + + d->destroy_notify = NULL; + g_array_remove_index_fast(__nmtst_internal.testdata_track_array, i); + return; + } + g_assert_not_reached(); +} + +static inline void +_nmtst_testdata_track_steal_and_free(gpointer data) +{ + _nmtst_testdata_track_steal(data); + g_free(data); +} + #define __NMTST_LOG(cmd, ...) \ G_STMT_START \ { \ @@ -309,7 +375,7 @@ BREAK_INNER_LOOPS: str = &str[i]; } - return (char **) g_array_free(result, FALSE); + return (char **) ((gpointer) g_array_free(result, FALSE)); } /* free instances allocated by nmtst (especially nmtst_init()) on shutdown @@ -320,6 +386,9 @@ nmtst_free(void) if (!nmtst_initialized()) return; + g_array_set_size(__nmtst_internal.testdata_track_array, 0); + g_array_unref(__nmtst_internal.testdata_track_array); + g_rand_free(__nmtst_internal.rand0); if (__nmtst_internal.rand) g_rand_free(__nmtst_internal.rand); @@ -576,10 +645,14 @@ __nmtst_init(int *argc, __nmtst_internal.sudo_cmd = sudo_cmd; __nmtst_internal.no_expect_message = no_expect_message; + __nmtst_internal.testdata_track_array = + g_array_new(FALSE, FALSE, sizeof(struct __nmtst_testdata_track)); + g_array_set_clear_func(__nmtst_internal.testdata_track_array, _nmtst_testdata_track_clear_func); + if (!log_level && log_domains) { /* if the log level is not specified (but the domain is), we assume * the caller wants to set it depending on is_debug */ - log_level = is_debug ? "DEBUG" : "WARN"; + log_level = is_debug ? "TRACE" : "WARN"; } if (!__nmtst_internal.assert_logging) { @@ -661,9 +734,9 @@ __nmtst_init(int *argc, /* Delay messages until we setup logging. */ for (i = 0; i < debug_messages->len; i++) - __NMTST_LOG(g_message, "%s", g_array_index(debug_messages, const char *, i)); + __NMTST_LOG(g_message, "%s", nm_g_array_index(debug_messages, const char *, i)); - g_strfreev((char **) g_array_free(debug_messages, FALSE)); + g_strfreev((char **) ((gpointer) g_array_free(debug_messages, FALSE))); g_free(c_log_level); g_free(c_log_domains); @@ -699,6 +772,12 @@ nmtst_init(int *argc, char ***argv, gboolean assert_logging) static inline gboolean nmtst_is_debug(void) { + /* This is based on the "debug"/"no-debug" flag in "$NMTST_DEBUG". + * + * If debugging is enabled, print more information. However, make sure + * that the test behaves still in a similar manner and that the same code + * path are taken where it matters (it matters for example, if the code path + * consumes random numbers). */ g_assert(nmtst_initialized()); return __nmtst_internal.is_debug; } @@ -774,15 +853,12 @@ typedef struct _NmtstTestData NmtstTestData; typedef void (*NmtstTestHandler)(const NmtstTestData *test_data); struct _NmtstTestData { - union { - const char *testpath; - char *_testpath; - }; + const char *testpath; gsize n_args; - gpointer *args; NmtstTestHandler _func_setup; GTestDataFunc _func_test; NmtstTestHandler _func_teardown; + gpointer args[]; }; static inline void @@ -807,15 +883,20 @@ _nmtst_test_data_unpack(const NmtstTestData *test_data, gsize n_args, ...) #define nmtst_test_data_unpack(test_data, ...) \ _nmtst_test_data_unpack(test_data, NM_NARG(__VA_ARGS__), ##__VA_ARGS__) -static inline void -_nmtst_test_data_free(gpointer data) +static inline const char * +nmtst_test_get_path(void) { - NmtstTestData *test_data = data; + g_assert(nmtst_initialized()); + g_assert(__nmtst_internal.testpath); - g_assert(test_data); + /* Similar to g_test_get_path() (which only exists since glib 2.68). + * + * This is the test name while running the test added with + * nmtst_add_test_func*(). + * + * You are only allowed to call this from inside such a test. */ - g_free(test_data->_testpath); - g_free(test_data); + return __nmtst_internal.testpath; } static inline void @@ -823,6 +904,13 @@ _nmtst_test_run(gconstpointer data) { const NmtstTestData *test_data = data; + g_assert(test_data); + g_assert(nmtst_initialized()); + g_assert(test_data->testpath); + g_assert(!__nmtst_internal.testpath); + + __nmtst_internal.testpath = test_data->testpath; + if (test_data->_func_setup) test_data->_func_setup(test_data); @@ -830,6 +918,10 @@ _nmtst_test_run(gconstpointer data) if (test_data->_func_teardown) test_data->_func_teardown(test_data); + + g_assert(__nmtst_internal.testpath); + g_assert(__nmtst_internal.testpath == test_data->testpath); + __nmtst_internal.testpath = NULL; } static inline void @@ -843,26 +935,41 @@ _nmtst_add_test_func_full(const char *testpath, gsize i; NmtstTestData *data; va_list ap; + gsize testpath_len; g_assert(testpath && testpath[0]); g_assert(func_test); - data = g_malloc0(sizeof(NmtstTestData) + (sizeof(gpointer) * (n_args + 1))); + testpath_len = strlen(testpath) + 1u; + + data = g_malloc(G_STRUCT_OFFSET(NmtstTestData, args) + + (sizeof(gpointer) * (n_args + 1u) + testpath_len)); + + *data = (NmtstTestData){ + .testpath = (gpointer) &data->args[n_args + 1u], + ._func_test = func_test, + ._func_setup = func_setup, + ._func_teardown = func_teardown, + .n_args = n_args, + }; - data->_testpath = g_strdup(testpath); - data->_func_test = func_test; - data->_func_setup = func_setup; - data->_func_teardown = func_teardown; - data->n_args = n_args; - data->args = (gpointer) &data[1]; va_start(ap, n_args); for (i = 0; i < n_args; i++) data->args[i] = va_arg(ap, gpointer); data->args[i] = NULL; va_end(ap); - g_test_add_data_func_full(testpath, data, _nmtst_test_run, _nmtst_test_data_free); + g_assert(data->testpath == (gpointer) &data->args[i + 1]); + + memcpy((char *) data->testpath, testpath, testpath_len); + + _nmtst_testdata_track_add(data, g_free); + g_test_add_data_func_full(testpath, + data, + _nmtst_test_run, + _nmtst_testdata_track_steal_and_free); } + #define nmtst_add_test_func_full(testpath, func_test, func_setup, func_teardown, ...) \ _nmtst_add_test_func_full(testpath, \ func_test, \ @@ -870,6 +977,7 @@ _nmtst_add_test_func_full(const char *testpath, func_teardown, \ NM_NARG(__VA_ARGS__), \ ##__VA_ARGS__) + #define nmtst_add_test_func(testpath, func_test, ...) \ nmtst_add_test_func_full(testpath, func_test, NULL, NULL, ##__VA_ARGS__) @@ -1192,6 +1300,28 @@ nmtst_get_rand_word_length(GRand *rand) /*****************************************************************************/ static inline gboolean +nmtst_true_once(gboolean *state, gboolean new_val) +{ + /* Returns only once a TRUE flag. When returning TRUE, + * it will be remembered in "state" and future invocations + * return FALSE. + * + * Also, if "new_val" is FALSE, it won't return TRUE. + * + * The point is to do an action once (depending on "new_val"), + * and remember it in "state". + */ + if (!new_val) + return FALSE; + if (*state) + return FALSE; + *state = TRUE; + return TRUE; +} + +/*****************************************************************************/ + +static inline gboolean nmtst_g_source_assert_not_called(gpointer user_data) { g_assert_not_reached(); @@ -1564,7 +1694,7 @@ nmtst_inet4_from_string(const char *str) } static inline const struct in6_addr * -nmtst_inet6_from_string(const char *str) +nmtst_inet6_from_string_p(const char *str) { static _nm_thread_local struct in6_addr addr; int success; @@ -1579,6 +1709,22 @@ nmtst_inet6_from_string(const char *str) return &addr; } +static inline struct in6_addr +nmtst_inet6_from_string(const char *str) +{ + struct in6_addr addr; + int success; + + if (!str) + addr = in6addr_any; + else { + success = inet_pton(AF_INET6, str, &addr); + g_assert(success == 1); + } + + return addr; +} + static inline gconstpointer nmtst_inet_from_string(int addr_family, const char *str) { @@ -1589,7 +1735,7 @@ nmtst_inet_from_string(int addr_family, const char *str) return &a; } if (addr_family == AF_INET6) - return nmtst_inet6_from_string(str); + return nmtst_inet6_from_string_p(str); g_assert_not_reached(); return NULL; @@ -1648,7 +1794,7 @@ _nmtst_assert_ip6_address(const char *file, if (!addr) addr = &any; - if (memcmp(nmtst_inet6_from_string(str_expected), addr, sizeof(*addr)) != 0) { + if (memcmp(nmtst_inet6_from_string_p(str_expected), addr, sizeof(*addr)) != 0) { char buf[100]; g_error("%s:%d: Unexpected IPv6 address: expected %s, got %s", @@ -1917,7 +2063,7 @@ nmtst_logging_disable(gboolean always) return NULL; } - p = g_memdup(_nm_logging_enabled_state, sizeof(_nm_logging_enabled_state)); + p = nm_memdup(_nm_logging_enabled_state, sizeof(_nm_logging_enabled_state)); memset(_nm_logging_enabled_state, 0, sizeof(_nm_logging_enabled_state)); return p; } @@ -1944,9 +2090,9 @@ nmtst_setting_ip_config_add_address(NMSettingIPConfig *s_ip, const char *address g_assert(s_ip); - if (nm_utils_ipaddr_is_valid(AF_INET, address)) + if (nm_inet_is_valid(AF_INET, address)) family = AF_INET; - else if (nm_utils_ipaddr_is_valid(AF_INET6, address)) + else if (nm_inet_is_valid(AF_INET6, address)) family = AF_INET6; else g_assert_not_reached(); @@ -1969,9 +2115,9 @@ nmtst_setting_ip_config_add_route(NMSettingIPConfig *s_ip, g_assert(s_ip); - if (nm_utils_ipaddr_is_valid(AF_INET, dest)) + if (nm_inet_is_valid(AF_INET, dest)) family = AF_INET; - else if (nm_utils_ipaddr_is_valid(AF_INET6, dest)) + else if (nm_inet_is_valid(AF_INET6, dest)) family = AF_INET6; else g_assert_not_reached(); @@ -2682,7 +2828,8 @@ _nmtst_variant_new_vardict(int dummy, ...) G_STMT_END #else #define _nmtst_assert_variant_bytestring_cmp_str(_ptr, _ptr2, _len) \ - G_STMT_START {} \ + G_STMT_START \ + {} \ G_STMT_END #endif @@ -2913,7 +3060,7 @@ nmtst_ip_address_new(int addr_family, const char *str) GError *error = NULL; NMIPAddress *a; - if (!nm_utils_parse_inaddr_prefix_bin(addr_family, str, &addr_family, &addr, &plen)) + if (!nm_inet_parse_with_prefix_bin(addr_family, str, &addr_family, &addr, &plen)) g_assert_not_reached(); if (plen == -1) @@ -2928,6 +3075,27 @@ nmtst_ip_address_new(int addr_family, const char *str) /*****************************************************************************/ +static inline gpointer +nmtst_keeper_add(GPtrArray **p_arr, gpointer ptr) +{ + if (!p_arr) { + /* If not GPtrArray in/out argument is given, track the pointer + * via _nmtst_testdata_track_add(), which means it stays alive + * until the end of the test. */ + _nmtst_testdata_track_add(ptr, g_free); + } else { + if (!*p_arr) + *p_arr = g_ptr_array_new_with_free_func(g_free); + + g_ptr_array_add(*p_arr, ptr); + } + return ptr; +} + +#define nmtst_keeper_printf(p_ptr, ...) nmtst_keeper_add((p_ptr), g_strdup_printf(__VA_ARGS__)) + +/*****************************************************************************/ + #define nmtst_gbytes_from_arr(...) \ ({ \ const guint8 _arr[] = {__VA_ARGS__}; \ diff --git a/src/libnm-glib-aux/nm-time-utils.c b/src/libnm-glib-aux/nm-time-utils.c index f30e6a19..077d0a4c 100644 --- a/src/libnm-glib-aux/nm-time-utils.c +++ b/src/libnm-glib-aux/nm-time-utils.c @@ -301,8 +301,13 @@ nm_utils_monotonic_timestamp_from_boottime(guint64 boottime, gint64 timestamp_ns nm_assert(offset <= 0 && offset > G_MININT64); - /* check for overflow (note that offset is non-positive). */ - g_return_val_if_fail(boottime < G_MAXINT64, G_MAXINT64); + if (boottime >= (guint64) G_MAXINT64) { + /* This indicates infinity. We keep it at such. */ + return G_MAXINT64; + } + + /* Note that overflow cannot happen, because bootime is non-negative, and + * offset is non-positive. */ return (gint64) boottime + offset; } @@ -318,6 +323,16 @@ nm_utils_clock_gettime_nsec(clockid_t clockid) } gint64 +nm_utils_clock_gettime_usec(clockid_t clockid) +{ + struct timespec tp; + + if (clock_gettime(clockid, &tp) != 0) + return -NM_ERRNO_NATIVE(errno); + return nm_utils_timespec_to_usec(&tp); +} + +gint64 nm_utils_clock_gettime_msec(clockid_t clockid) { struct timespec tp; @@ -326,3 +341,36 @@ nm_utils_clock_gettime_msec(clockid_t clockid) return -NM_ERRNO_NATIVE(errno); return nm_utils_timespec_to_msec(&tp); } + +/*****************************************************************************/ + +/* Taken from systemd's map_clock_usec_internal(). */ +gint64 +nm_time_map_clock(gint64 from, gint64 from_base, gint64 to_base) +{ + /* Maps the time 'from' between two clocks, based on a common reference point where the first clock + * is at 'from_base' and the second clock at 'to_base'. Basically calculates: + * + * from - from_base + to_base + * + * But takes care of overflows/underflows and avoids signed operations. */ + + if (from >= from_base) { + gint64 delta = from - from_base; + + /* In the future */ + if (to_base >= G_MAXINT64 - delta) + return G_MAXINT64; + + return to_base + delta; + + } else { + gint64 delta = from_base - from; + + /* In the past */ + if (to_base <= G_MININT64 + delta) + return G_MININT64; + + return to_base - delta; + } +} diff --git a/src/libnm-glib-aux/nm-time-utils.h b/src/libnm-glib-aux/nm-time-utils.h index 461d6845..d3153686 100644 --- a/src/libnm-glib-aux/nm-time-utils.h +++ b/src/libnm-glib-aux/nm-time-utils.h @@ -8,17 +8,38 @@ #include <time.h> +_nm_always_inline static inline gint64 +_nm_utils_timespec_to_xsec(const struct timespec *ts, gint64 xsec_per_sec) +{ + nm_assert(ts); + + if (ts->tv_sec < 0 || ts->tv_nsec < 0) + return G_MAXINT64; + + if (ts->tv_sec > ((guint64) G_MAXINT64) || ts->tv_nsec > ((guint64) G_MAXINT64) + || ts->tv_sec >= (G_MAXINT64 - ((gint64) ts->tv_nsec)) / xsec_per_sec) + return G_MAXINT64; + + return (((gint64) ts->tv_sec) * xsec_per_sec) + + (((gint64) ts->tv_nsec) / (NM_UTILS_NSEC_PER_SEC / xsec_per_sec)); +} + static inline gint64 nm_utils_timespec_to_nsec(const struct timespec *ts) { - return (((gint64) ts->tv_sec) * ((gint64) NM_UTILS_NSEC_PER_SEC)) + ((gint64) ts->tv_nsec); + return _nm_utils_timespec_to_xsec(ts, NM_UTILS_NSEC_PER_SEC); +} + +static inline gint64 +nm_utils_timespec_to_usec(const struct timespec *ts) +{ + return _nm_utils_timespec_to_xsec(ts, NM_UTILS_USEC_PER_SEC); } static inline gint64 nm_utils_timespec_to_msec(const struct timespec *ts) { - return (((gint64) ts->tv_sec) * ((gint64) 1000)) - + (((gint64) ts->tv_nsec) / ((gint64) NM_UTILS_NSEC_PER_SEC / 1000)); + return _nm_utils_timespec_to_xsec(ts, NM_UTILS_MSEC_PER_SEC); } gint64 nm_utils_get_monotonic_timestamp_nsec(void); @@ -36,6 +57,12 @@ nm_utils_get_monotonic_timestamp_nsec_cached(gint64 *cache_now) } static inline gint64 +nm_utils_get_monotonic_timestamp_usec_cached(gint64 *cache_now) +{ + return (*cache_now) ?: (*cache_now = nm_utils_get_monotonic_timestamp_usec()); +} + +static inline gint64 nm_utils_get_monotonic_timestamp_msec_cached(gint64 *cache_now) { return (*cache_now) ?: (*cache_now = nm_utils_get_monotonic_timestamp_msec()); @@ -48,6 +75,9 @@ nm_utils_get_monotonic_timestamp_sec_cached(gint32 *cache_now) } gint64 nm_utils_clock_gettime_nsec(clockid_t clockid); +gint64 nm_utils_clock_gettime_usec(clockid_t clockid); gint64 nm_utils_clock_gettime_msec(clockid_t clockid); +gint64 nm_time_map_clock(gint64 from, gint64 from_base, gint64 to_base); + #endif /* __NM_TIME_UTILS_H__ */ diff --git a/src/libnm-glib-aux/nm-uuid.c b/src/libnm-glib-aux/nm-uuid.c index 53e8b78c..cdfa5f62 100644 --- a/src/libnm-glib-aux/nm-uuid.c +++ b/src/libnm-glib-aux/nm-uuid.c @@ -12,7 +12,8 @@ const NMUuid nm_uuid_ns_zero = NM_UUID_INIT(00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00); -/* arbitrarily chosen namespace UUID for nm_uuid_generate_from_strings() */ +/* arbitrarily chosen namespace UUID for some uses of nm_uuid_generate_from_strings_old(). + * Try not to re-use this namespace, instead, generate a unique one. */ const NMUuid nm_uuid_ns_1 = NM_UUID_INIT(b4, 25, e9, fb, 75, 98, 44, b4, 9e, 3b, 5a, 2e, 3a, aa, 49, 05); @@ -406,42 +407,85 @@ nm_uuid_generate_from_string_str(const char *s, } /** - * nm_uuid_generate_from_strings: - * @string1: a variadic list of strings. Must be NULL terminated. + * nm_uuid_generate_from_strings_strv: + * @uuid_type: the UUID type to use. Prefer version 5 unless you have + * good reasons. + * @type_args: the namespace UUID. + * @strv: (allow-none): the strv list to hash. Can be NULL, in which + * case the result is different from an empty array. + * @len: if negative, @strv is a NULL terminated array. Otherwise, + * it is the length of the strv array. In the latter case it may + * also contain NULL strings. The result hashes differently depending + * on whether we have a NULL terminated strv array or given length. * - * Returns a variant3 UUID based on the concatenated C strings. + * Returns a @uuid_type UUID based on the concatenated C strings. * It does not simply concatenate them, but also includes the * terminating '\0' character. For example "a", "b", gives * "a\0b\0". - * * This has the advantage, that the following invocations * all give different UUIDs: (NULL), (""), ("",""), ("","a"), ("a",""), * ("aa"), ("aa", ""), ("", "aa"), ... */ char * -nm_uuid_generate_from_strings(const char *string1, ...) +nm_uuid_generate_from_strings_strv(NMUuidType uuid_type, + const NMUuid *type_args, + const char *const *strv, + gssize len) { - if (!string1) - return nm_uuid_generate_from_string_str(NULL, 0, NM_UUID_TYPE_VERSION3, &nm_uuid_ns_1); + nm_auto_str_buf NMStrBuf str = NM_STR_BUF_INIT_A(NM_UTILS_GET_NEXT_REALLOC_SIZE_232, TRUE); + gsize slen; + const char *s; - { - nm_auto_str_buf NMStrBuf str = NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_104, FALSE); - va_list args; - const char *s; + if (len >= 0) { + gboolean has_nulls = FALSE; + gssize i; - nm_str_buf_append_len(&str, string1, strlen(string1) + 1u); + nm_assert(len == 0 || strv); - va_start(args, string1); - s = va_arg(args, const char *); - while (s) { - nm_str_buf_append_len(&str, s, strlen(s) + 1u); - s = va_arg(args, const char *); + for (i = 0; i < len; i++) { + if (strv[i]) + nm_str_buf_append_len(&str, strv[i], strlen(strv[i]) + 1u); + else + has_nulls = TRUE; } - va_end(args); - - return nm_uuid_generate_from_string_str(nm_str_buf_get_str_unsafe(&str), - str.len, - NM_UUID_TYPE_VERSION3, - &nm_uuid_ns_1); + if (has_nulls) { + /* We either support a NULL terminated strv array, or a ptr array of fixed + * length (@len argument). + * + * If there are no NULLs within the first @len strings, then the result + * is the same. If there are any NULL strings, we need to encode that + * in a unique way. We do that by appending a bitmap of the elements + * whether they were set, plus one 'n' character (without NUL termination). + * None of the other branches below hashes to that, so this will uniquely + * encoded the NULL strings. + */ + for (i = 0; i < len; i++) + nm_str_buf_append_c(&str, strv[i] ? '1' : '_'); + nm_str_buf_append_c(&str, 'n'); + } + slen = str.len; + s = nm_str_buf_get_str_unsafe(&str); + } else if (!strv) { + /* NULL is treated differently from an empty strv. We achieve that + * by using a non-empty, non-NUL terminated string (which cannot happen + * in the other cases). */ + slen = 1; + s = "x"; + } else if (!strv[0]) { + slen = 0; + s = ""; + } else if (!strv[1]) { + slen = strlen(strv[0]) + 1u; + s = strv[0]; + } else { + /* We concatenate the NUL termiated string, including the NUL + * character. This way, ("a","a"), ("aa"), ("aa","") all hash + * differently. */ + for (; strv[0]; strv++) + nm_str_buf_append_len(&str, strv[0], strlen(strv[0]) + 1u); + slen = str.len; + s = nm_str_buf_get_str_unsafe(&str); } + + return nm_uuid_generate_from_string_str(s, slen, uuid_type, type_args); } diff --git a/src/libnm-glib-aux/nm-uuid.h b/src/libnm-glib-aux/nm-uuid.h index 15b03a2d..b8955452 100644 --- a/src/libnm-glib-aux/nm-uuid.h +++ b/src/libnm-glib-aux/nm-uuid.h @@ -9,6 +9,9 @@ typedef struct _NMUuid { #define NM_UUID_INIT_ZERO() ((NMUuid){.uuid = {0}}) +/* Beware, the 16 macro arguments are two hex-digits, not plain numbers. The macro + * will automatically add the "0x". In particular, "09" is not an octal number, it's + * 0x09. This oddity is so that the arguments look very much like the UUID in string form. */ #define NM_UUID_INIT(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) \ ((NMUuid){ \ .uuid = {(0x##a0), \ @@ -120,7 +123,23 @@ char *nm_uuid_generate_from_string_str(const char *s, NMUuidType uuid_type, const NMUuid *type_args); -char *nm_uuid_generate_from_strings(const char *string1, ...) G_GNUC_NULL_TERMINATED; +char *nm_uuid_generate_from_strings_strv(NMUuidType uuid_type, + const NMUuid *type_args, + const char *const *strv, + gssize len); + +#define nm_uuid_generate_from_strings(uuid_type, type_args, ...) \ + nm_uuid_generate_from_strings_strv((uuid_type), \ + (type_args), \ + NM_MAKE_STRV(__VA_ARGS__), \ + NM_NARG(__VA_ARGS__)) + +/* Legacy function. Don't use for new code. */ +#define nm_uuid_generate_from_strings_old(...) \ + nm_uuid_generate_from_strings_strv(NM_UUID_TYPE_VERSION3, \ + &nm_uuid_ns_1, \ + NM_MAKE_STRV(__VA_ARGS__), \ + -1) /*****************************************************************************/ diff --git a/src/libnm-glib-aux/tests/test-shared-general.c b/src/libnm-glib-aux/tests/test-shared-general.c index cfe70b58..7503dc9b 100644 --- a/src/libnm-glib-aux/tests/test-shared-general.c +++ b/src/libnm-glib-aux/tests/test-shared-general.c @@ -11,6 +11,7 @@ #include "libnm-glib-aux/nm-time-utils.h" #include "libnm-glib-aux/nm-ref-string.h" #include "libnm-glib-aux/nm-io-utils.h" +#include "libnm-glib-aux/nm-prioq.h" #include "libnm-glib-aux/nm-test-utils.h" @@ -77,6 +78,9 @@ test_gpid(void) * the case. */ int_ptr = &pid; g_assert_cmpint(*int_ptr, ==, 42); + + /* also check how assert() works. */ + assert(*int_ptr == 42); } /*****************************************************************************/ @@ -90,6 +94,38 @@ test_monotonic_timestamp(void) /*****************************************************************************/ static void +test_timespect_to(void) +{ + struct timespec ts; + int i; + + for (i = 0; i < 1000; i++) { + gint64 t_msec; + gint64 t_usec; + gint64 t_nsec; + + nmtst_rand_buf(NULL, &ts, sizeof(ts)); + ts.tv_sec = llabs(ts.tv_sec % 100000); + ts.tv_nsec = llabs(ts.tv_nsec % NM_UTILS_NSEC_PER_SEC); + + t_msec = nm_utils_timespec_to_msec(&ts); + t_usec = nm_utils_timespec_to_usec(&ts); + t_nsec = nm_utils_timespec_to_nsec(&ts); + + g_assert_cmpint(t_msec, <=, t_usec / 1000); + g_assert_cmpint(t_msec + 1, >=, t_usec / 1000); + + g_assert_cmpint(t_msec, <=, t_nsec / 1000000); + g_assert_cmpint(t_msec + 1, >=, t_nsec / 1000000); + + g_assert_cmpint(t_usec, <=, t_nsec / 1000); + g_assert_cmpint(t_usec + 1, >=, t_nsec / 1000); + } +} + +/*****************************************************************************/ + +static void test_nmhash(void) { int rnd; @@ -117,8 +153,8 @@ test_make_strv(void) const char *const *v2a = NM_MAKE_STRV("a", "b"); const char *const *v2b = NM_MAKE_STRV("a", "b", ); const char *const v3[] = { - "a", - "b", + "a", + "b", }; const char *const *v4b = NM_MAKE_STRV("a", _make_strv_foo(), ); @@ -251,37 +287,41 @@ test_nm_strndup_a(void) /*****************************************************************************/ static void -test_nm_utils_ip4_address_is_loopback(void) +test_nm_ip4_addr_is_loopback(void) { - g_assert(nm_utils_ip4_address_is_loopback(nmtst_inet4_from_string("127.0.0.0"))); - g_assert(nm_utils_ip4_address_is_loopback(nmtst_inet4_from_string("127.0.0.1"))); - g_assert(nm_utils_ip4_address_is_loopback(nmtst_inet4_from_string("127.5.0.1"))); - g_assert(!nm_utils_ip4_address_is_loopback(nmtst_inet4_from_string("126.5.0.1"))); - g_assert(!nm_utils_ip4_address_is_loopback(nmtst_inet4_from_string("128.5.0.1"))); - g_assert(!nm_utils_ip4_address_is_loopback(nmtst_inet4_from_string("129.5.0.1"))); + g_assert(nm_ip4_addr_is_loopback(nmtst_inet4_from_string("127.0.0.0"))); + g_assert(nm_ip4_addr_is_loopback(nmtst_inet4_from_string("127.0.0.1"))); + g_assert(nm_ip4_addr_is_loopback(nmtst_inet4_from_string("127.5.0.1"))); + g_assert(!nm_ip4_addr_is_loopback(nmtst_inet4_from_string("126.5.0.1"))); + g_assert(!nm_ip4_addr_is_loopback(nmtst_inet4_from_string("128.5.0.1"))); + g_assert(!nm_ip4_addr_is_loopback(nmtst_inet4_from_string("129.5.0.1"))); + g_assert_cmpint(nmtst_inet4_from_string("127.0.0.0"), ==, NM_IPV4LO_NETWORK); + g_assert_cmpint(nmtst_inet4_from_string("127.0.0.1"), ==, NM_IPV4LO_ADDR1); + g_assert_cmpint(nmtst_inet4_from_string("255.0.0.0"), ==, NM_IPV4LO_NETMASK); + g_assert_cmpint(nm_ip4_addr_netmask_to_prefix(NM_IPV4LO_NETMASK), ==, NM_IPV4LO_PREFIXLEN); } /*****************************************************************************/ static void -test_nm_utils_ip4_prefix_to_netmask(void) +test_nm_ip4_addr_netmask_from_prefix(void) { - g_assert_cmpint(_nm_utils_ip4_prefix_to_netmask(0), ==, nmtst_inet4_from_string("0.0.0.0")); - g_assert_cmpint(_nm_utils_ip4_prefix_to_netmask(1), ==, nmtst_inet4_from_string("128.0.0.0")); - g_assert_cmpint(_nm_utils_ip4_prefix_to_netmask(2), ==, nmtst_inet4_from_string("192.0.0.0")); - g_assert_cmpint(_nm_utils_ip4_prefix_to_netmask(16), + g_assert_cmpint(nm_ip4_addr_netmask_from_prefix(0), ==, nmtst_inet4_from_string("0.0.0.0")); + g_assert_cmpint(nm_ip4_addr_netmask_from_prefix(1), ==, nmtst_inet4_from_string("128.0.0.0")); + g_assert_cmpint(nm_ip4_addr_netmask_from_prefix(2), ==, nmtst_inet4_from_string("192.0.0.0")); + g_assert_cmpint(nm_ip4_addr_netmask_from_prefix(16), ==, nmtst_inet4_from_string("255.255.0.0")); - g_assert_cmpint(_nm_utils_ip4_prefix_to_netmask(24), + g_assert_cmpint(nm_ip4_addr_netmask_from_prefix(24), ==, nmtst_inet4_from_string("255.255.255.0")); - g_assert_cmpint(_nm_utils_ip4_prefix_to_netmask(30), + g_assert_cmpint(nm_ip4_addr_netmask_from_prefix(30), ==, nmtst_inet4_from_string("255.255.255.252")); - g_assert_cmpint(_nm_utils_ip4_prefix_to_netmask(31), + g_assert_cmpint(nm_ip4_addr_netmask_from_prefix(31), ==, nmtst_inet4_from_string("255.255.255.254")); - g_assert_cmpint(_nm_utils_ip4_prefix_to_netmask(32), + g_assert_cmpint(nm_ip4_addr_netmask_from_prefix(32), ==, nmtst_inet4_from_string("255.255.255.255")); } @@ -1294,7 +1334,7 @@ test_utils_hashtable_cmp(void) for (test_run = 0; test_run < 30; test_run++) { for (is_num_key = 0; is_num_key < 2; is_num_key++) { GHashFunc func_key_hash = is_num_key ? nm_direct_hash : nm_str_hash; - GEqualFunc func_key_equal = is_num_key ? g_direct_equal : g_str_equal; + GEqualFunc func_key_equal = is_num_key ? NULL : g_str_equal; GCompareDataFunc func_key_cmp = is_num_key ? _hash_func_cmp_direct : (GCompareDataFunc) nm_strcmp_with_data; GCompareDataFunc func_val_cmp = @@ -2211,6 +2251,124 @@ test_hostname_is_valid(void) /*****************************************************************************/ +static void +test_inet_utils(void) +{ + g_assert(nm_ip_addr_is_site_local(AF_INET, nmtst_inet_from_string(AF_INET, "172.16.0.1"))); + g_assert(nm_ip_addr_is_site_local(AF_INET, nmtst_inet_from_string(AF_INET, "172.17.0.1"))); + g_assert(nm_ip_addr_is_site_local(AF_INET, nmtst_inet_from_string(AF_INET, "192.168.7.5"))); + g_assert(!nm_ip_addr_is_site_local(AF_INET, nmtst_inet_from_string(AF_INET, "192.0.7.5"))); + g_assert(nm_ip_addr_is_site_local(AF_INET6, nmtst_inet_from_string(AF_INET6, "fec0::"))); + g_assert(!nm_ip_addr_is_site_local(AF_INET6, nmtst_inet_from_string(AF_INET6, "fc00::"))); +} + +/*****************************************************************************/ + +static void +test_garray(void) +{ + gs_unref_array GArray *arr = NULL; + int v; + + arr = g_array_new(FALSE, FALSE, sizeof(int)); + g_assert(nm_g_array_index_p(arr, int, 0) == (gpointer) arr->data); + + v = 1; + g_array_append_val(arr, v); + g_assert(nm_g_array_index_p(arr, int, 0) == (gpointer) arr->data); + g_assert(nm_g_array_index_p(arr, int, 1) == ((int *) ((gpointer) arr->data)) + 1); + g_assert(&nm_g_array_index(arr, int, 0) == (gpointer) arr->data); + g_assert(&nm_g_array_first(arr, int) == (gpointer) arr->data); + g_assert(&nm_g_array_last(arr, int) == (gpointer) arr->data); + g_assert(nm_g_array_index(arr, int, 0) == 1); + + v = 2; + g_array_append_val(arr, v); + g_assert(nm_g_array_index_p(arr, int, 0) == (gpointer) arr->data); + g_assert(nm_g_array_index_p(arr, int, 1) == ((int *) ((gpointer) arr->data)) + 1); + g_assert(nm_g_array_index_p(arr, int, 2) == ((int *) ((gpointer) arr->data)) + 2); + g_assert(&nm_g_array_index(arr, int, 0) == (gpointer) arr->data); + g_assert(&nm_g_array_first(arr, int) == (gpointer) arr->data); + g_assert(&nm_g_array_last(arr, int) == ((int *) ((gpointer) arr->data)) + 1); + g_assert(nm_g_array_index(arr, int, 0) == 1); + g_assert(nm_g_array_index(arr, int, 1) == 2); +} + +/*****************************************************************************/ + +static int +_prioq_cmp(gconstpointer a, gconstpointer b) +{ + NM_CMP_DIRECT(GPOINTER_TO_UINT(a), GPOINTER_TO_UINT(b)); + return 0; +} + +static int +_prioq_cmp_with_data(gconstpointer a, gconstpointer b, gpointer user_data) +{ + return _prioq_cmp(a, b); +} + +static void +test_nm_prioq(void) +{ + nm_auto_prioq NMPrioq q = NM_PRIOQ_ZERO; + gpointer data[200]; + gpointer data_pop[200]; + guint data_idx[G_N_ELEMENTS(data)]; + guint i; + guint n; + gpointer p; + + if (nmtst_get_rand_one_case_in(10)) + return; + + if (nmtst_get_rand_bool()) + nm_prioq_init(&q, _prioq_cmp); + else + nm_prioq_init_with_data(&q, _prioq_cmp_with_data, NULL); + + g_assert(nm_prioq_size(&q) == 0); + + if (nmtst_get_rand_one_case_in(10)) + return; + + for (i = 0; i < G_N_ELEMENTS(data); i++) { + data[i] = GUINT_TO_POINTER((nmtst_get_rand_uint32() % G_N_ELEMENTS(data)) + 1u); + data_idx[i] = NM_PRIOQ_IDX_NULL; + } + + nm_prioq_put(&q, data[0], NULL); + g_assert(nm_prioq_size(&q) == 1); + + p = nm_prioq_pop(&q); + g_assert(p == data[0]); + g_assert(nm_prioq_size(&q) == 0); + + g_assert(!nm_prioq_pop(&q)); + + n = nmtst_get_rand_uint32() % G_N_ELEMENTS(data); + for (i = 0; i < n; i++) + nm_prioq_put(&q, data[i], &data_idx[i]); + + g_assert_cmpint(nm_prioq_size(&q), ==, n); + + if (nmtst_get_rand_one_case_in(10)) + return; + + for (i = 0; i < n; i++) { + data_pop[i] = nm_prioq_pop(&q); + g_assert(data_pop[i]); + if (i > 0) + g_assert(_prioq_cmp(data_pop[i - 1], data_pop[i]) <= 0); + } + + g_assert(!nm_prioq_pop(&q)); + g_assert(nm_prioq_size(&q) == 0); +} + +/*****************************************************************************/ + NMTST_DEFINE(); int @@ -2221,14 +2379,14 @@ main(int argc, char **argv) g_test_add_func("/general/test_nm_static_assert", test_nm_static_assert); 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_timespect_to", test_timespect_to); 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_utils_ip4_address_is_loopback", - test_nm_utils_ip4_address_is_loopback); - g_test_add_func("/general/test_nm_utils_ip4_prefix_to_netmask", - test_nm_utils_ip4_prefix_to_netmask); + g_test_add_func("/general/test_nm_ip4_addr_is_loopback", test_nm_ip4_addr_is_loopback); + g_test_add_func("/general/test_nm_ip4_addr_netmask_from_prefix", + test_nm_ip4_addr_netmask_from_prefix); 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); @@ -2256,6 +2414,9 @@ main(int argc, char **argv) g_test_add_func("/general/test_path_startswith", test_path_startswith); g_test_add_func("/general/test_path_simplify", test_path_simplify); g_test_add_func("/general/test_hostname_is_valid", test_hostname_is_valid); + g_test_add_func("/general/test_inet_utils", test_inet_utils); + g_test_add_func("/general/test_garray", test_garray); + g_test_add_func("/general/test_nm_prioq", test_nm_prioq); return g_test_run(); } |