diff options
| author | Michael Biebl <biebl@debian.org> | 2020-04-11 21:28:04 +0200 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2020-04-11 21:28:04 +0200 |
| commit | 1e5977b62f896e844b548c3007ace9e1dfa7f9ed (patch) | |
| tree | 7a7416ed410e72b6200f3d860fd315ec11cc106b /shared | |
| parent | b012fa6e1d808e0736c009799c62d835cbfcc1dd (diff) | |
New upstream version 1.23.90 upstream/1.23.90
Diffstat (limited to 'shared')
86 files changed, 10824 insertions, 1376 deletions
diff --git a/shared/meson.build b/shared/meson.build index 5f96d49d..9a8768f5 100644 --- a/shared/meson.build +++ b/shared/meson.build @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: LGPL-2.1+ + shared_inc = include_directories('.') nm_default_dep = declare_dependency(include_directories: [top_inc, shared_inc]) @@ -111,12 +113,21 @@ nm_libnm_core_aux_source = files('nm-libnm-core-aux/nm-libnm-core-aux.c') nm_libnm_core_utils_source = files('nm-libnm-core-intern/nm-libnm-core-utils.c') +nm_keyfile_source = files( + 'nm-keyfile/nm-keyfile-utils.c', + 'nm-keyfile/nm-keyfile.c', +) + +nm_libnm_aux_source = files('nm-libnm-aux/nm-libnm-aux.c') + nm_meta_setting_source = files('nm-meta-setting.c') nm_test_utils_impl_source = files('nm-test-utils-impl.c') nm_vpn_plugin_utils_source = files('nm-utils/nm-vpn-plugin-utils.c') +nm_auth_subject_source = files('nm-libnm-core-intern/nm-auth-subject.c') + c_flags = [ '-DG_LOG_DOMAIN="@0@"'.format(libnm_name), '-DNETWORKMANAGER_COMPILATION=0', @@ -168,15 +179,14 @@ libnm_utils_base_dep = declare_dependency( link_with: libnm_utils_base, ) -deps = [ - glib_nm_default_dep, - libudev_dep, -] libnm_udev_aux = static_library( 'nm-udev-aux', sources: 'nm-udev-aux/nm-udev-utils.c', - dependencies: deps, + dependencies: [ + glib_nm_default_dep, + libudev_dep, + ], c_args: c_flags, ) @@ -186,10 +196,11 @@ libnm_udev_aux_dep = declare_dependency( ) sources = files( + 'systemd/nm-sd-utils-shared.c', 'systemd/src/basic/alloc-util.c', - 'systemd/src/basic/escape.c', 'systemd/src/basic/env-file.c', 'systemd/src/basic/env-util.c', + 'systemd/src/basic/escape.c', 'systemd/src/basic/ether-addr-util.c', 'systemd/src/basic/extract-word.c', 'systemd/src/basic/fd-util.c', @@ -209,6 +220,7 @@ sources = files( 'systemd/src/basic/prioq.c', 'systemd/src/basic/process-util.c', 'systemd/src/basic/random-util.c', + 'systemd/src/basic/signal-util.c', 'systemd/src/basic/socket-util.c', 'systemd/src/basic/stat-util.c', 'systemd/src/basic/string-table.c', @@ -220,7 +232,7 @@ sources = files( 'systemd/src/basic/utf8.c', 'systemd/src/basic/util.c', 'systemd/src/shared/dns-domain.c', - 'systemd/nm-sd-utils-shared.c', + 'systemd/src/shared/web-util.c', ) incs = include_directories( @@ -256,5 +268,5 @@ libnm_systemd_logging_stub = static_library( ) if enable_tests - subdir('nm-utils/tests') + subdir('nm-glib-aux/tests') endif diff --git a/shared/n-dhcp4/src/n-dhcp4-client.c b/shared/n-dhcp4/src/n-dhcp4-client.c index 4fa3d65d..6b015e81 100644 --- a/shared/n-dhcp4/src/n-dhcp4-client.c +++ b/shared/n-dhcp4/src/n-dhcp4-client.c @@ -388,14 +388,10 @@ _c_public_ int n_dhcp4_client_new(NDhcp4Client **clientp, NDhcp4ClientConfig *co return -errno; client->fd_timer = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK); - if (client->fd_timer < 0) { - if (errno != EINVAL) - return -errno; + if (client->fd_timer < 0 && errno == EINVAL) client->fd_timer = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); - if (client->fd_timer < 0) - return -errno; - client->timerfd_is_monotonic = true; - } + if (client->fd_timer < 0) + return -errno; ev.data.u32 = N_DHCP4_CLIENT_EPOLL_TIMER; r = epoll_ctl(client->fd_epoll, EPOLL_CTL_ADD, client->fd_timer, &ev); @@ -498,41 +494,45 @@ int n_dhcp4_client_raise(NDhcp4Client *client, NDhcp4CEventNode **nodep, unsigne * must be called whenever a timeout on @client might have changed. */ void n_dhcp4_client_arm_timer(NDhcp4Client *client) { - uint64_t timeout = 0; + uint64_t now, offset, timeout = 0; int r; if (client->current_probe) n_dhcp4_client_probe_get_timeout(client->current_probe, &timeout); if (timeout != client->scheduled_timeout) { - uint64_t scheduled_timeout = timeout; - int flags = TFD_TIMER_ABSTIME; - - if ( timeout != 0 - && client->timerfd_is_monotonic) { - uint64_t now; - - /* the timerfd ticks with CLOCK_MONOTONIC. Calculate and set the relative - * timeout. */ - now = n_dhcp4_gettime(CLOCK_BOOTTIME); - if (timeout <= now) - timeout = 1; - else - timeout = timeout - now; - flags = 0; - } + /* + * Across our codebase, timeouts are specified as absolute + * timestamps on CLOCK_BOOTTIME. Unfortunately, there are + * systems with CLOCK_BOOTTIME support, but timerfd lacks it + * (in particular RHEL). Therefore, our timerfd might be on + * CLOCK_MONOTONIC. + * To account for this, we always schedule a relative timeout. + * We fetch the current time and then calculate the offset + * which we then schedule as relative timeout on the timerfd. + * This works regardless which clock the timerfd runs on. + * Once we no longer support CLOCK_MONOTONIC as fallback, we + * can simply switch to TFD_TIMER_ABSTIME here and specify + * `timeout` directly as value. + */ + now = n_dhcp4_gettime(CLOCK_BOOTTIME); + if (now >= timeout) + offset = 1; /* 0 would disarm the timerfd */ + else + offset = timeout - now; + r = timerfd_settime(client->fd_timer, - flags, + 0, &(struct itimerspec){ .it_value = { - .tv_sec = timeout / UINT64_C(1000000000), - .tv_nsec = timeout % UINT64_C(1000000000), + .tv_sec = offset / UINT64_C(1000000000), + .tv_nsec = offset % UINT64_C(1000000000), }, }, NULL); c_assert(r >= 0); - client->scheduled_timeout = scheduled_timeout; + client->scheduled_timeout = timeout; } } diff --git a/shared/n-dhcp4/src/n-dhcp4-private.h b/shared/n-dhcp4/src/n-dhcp4-private.h index f647cb5e..e285d95d 100644 --- a/shared/n-dhcp4/src/n-dhcp4-private.h +++ b/shared/n-dhcp4/src/n-dhcp4-private.h @@ -332,7 +332,6 @@ struct NDhcp4Client { uint64_t scheduled_timeout; bool preempted : 1; - bool timerfd_is_monotonic : 1; }; #define N_DHCP4_CLIENT_NULL(_x) { \ diff --git a/shared/nm-glib-aux/nm-dbus-aux.c b/shared/nm-glib-aux/nm-dbus-aux.c index 86061aef..54e54ca7 100644 --- a/shared/nm-glib-aux/nm-dbus-aux.c +++ b/shared/nm-glib-aux/nm-dbus-aux.c @@ -56,7 +56,7 @@ nm_dbus_connection_call_get_name_owner (GDBusConnection *dbus_connection, /*****************************************************************************/ static void -_nm_dbus_connection_call_get_all_cb (GObject *source, +_nm_dbus_connection_call_default_cb (GObject *source, GAsyncResult *res, gpointer user_data) { @@ -96,100 +96,36 @@ nm_dbus_connection_call_get_all (GDBusConnection *dbus_connection, G_DBUS_CALL_FLAGS_NONE, timeout_msec, cancellable, - _nm_dbus_connection_call_get_all_cb, + _nm_dbus_connection_call_default_cb, nm_utils_user_data_pack (user_data, callback)); } -/*****************************************************************************/ - -typedef struct { - NMDBusConnectionSignalObjectMangerCb callback; - gpointer user_data; - GDestroyNotify user_data_free_func; -} SubscribeObjectManagerData; - -static void -_subscribe_object_manager_cb (GDBusConnection *connection, - const char *sender_name, - const char *arg_object_path, - const char *interface_name, - const char *signal_name, - GVariant *parameters, - gpointer user_data) -{ - const SubscribeObjectManagerData *d = user_data; - - nm_assert (nm_streq0 (interface_name, DBUS_INTERFACE_OBJECT_MANAGER)); - - if (nm_streq (signal_name, "InterfacesAdded")) { - gs_unref_variant GVariant *interfaces_and_properties = NULL; - const char *object_path; - - if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(oa{sa{sv}})"))) - return; - - g_variant_get (parameters, - "(&o@a{sa{sv}})", - &object_path, - &interfaces_and_properties); - - d->callback (object_path, interfaces_and_properties, NULL, d->user_data); - return; - } - - if (nm_streq (signal_name, "InterfacesRemoved")) { - gs_free const char **interfaces = NULL; - const char *object_path; - - if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(oas)"))) - return; - - g_variant_get (parameters, - "(&o^a&s)", - &object_path, - &interfaces); - - d->callback (object_path, NULL, interfaces, d->user_data); - return; - } -} - -static void -_subscribe_object_manager_data_free (gpointer ptr) -{ - SubscribeObjectManagerData *d = ptr; - - if (d->user_data_free_func) - d->user_data_free_func (d->user_data); - nm_g_slice_free (d); -} - -guint -nm_dbus_connection_signal_subscribe_object_manager (GDBusConnection *dbus_connection, - const char *service_name, - const char *object_path, - NMDBusConnectionSignalObjectMangerCb callback, - gpointer user_data, - GDestroyNotify user_data_free_func) +void nm_dbus_connection_call_set (GDBusConnection *dbus_connection, + const char *bus_name, + const char *object_path, + const char *interface_name, + const char *property_name, + GVariant *value, + int timeout_msec, + GCancellable *cancellable, + NMDBusConnectionCallDefaultCb callback, + gpointer user_data) { - SubscribeObjectManagerData *d; - - g_return_val_if_fail (callback, 0); - - d = g_slice_new (SubscribeObjectManagerData); - *d = (SubscribeObjectManagerData) { - .callback = callback, - .user_data = user_data, - .user_data_free_func = user_data_free_func, - }; - - return nm_dbus_connection_signal_subscribe_object_manager_plain (dbus_connection, - service_name, - object_path, - NULL, - _subscribe_object_manager_cb, - d, - _subscribe_object_manager_data_free); + g_dbus_connection_call (dbus_connection, + bus_name, + object_path, + DBUS_INTERFACE_PROPERTIES, + "Set", + g_variant_new ("(ssv)", + interface_name, + property_name, + value), + G_VARIANT_TYPE ("()"), + G_DBUS_CALL_FLAGS_NONE, + timeout_msec, + cancellable, + callback ? _nm_dbus_connection_call_default_cb : NULL, + callback ? nm_utils_user_data_pack (user_data, callback) : NULL); } /*****************************************************************************/ @@ -269,10 +205,9 @@ _call_finish_cb (GObject *source, return; } - if (!return_void) { - nm_assert (!g_variant_is_of_type (ret, G_VARIANT_TYPE ("()"))); + if (!return_void) g_task_return_pointer (task, g_steal_pointer (&ret), (GDestroyNotify) g_variant_unref); - } else { + else { nm_assert (g_variant_is_of_type (ret, G_VARIANT_TYPE ("()"))); g_task_return_boolean (task, TRUE); } @@ -317,7 +252,6 @@ nm_dbus_connection_call_finish_void_strip_dbus_error_cb (GObject *source, * * - user_data must be a GTask, whose reference will be consumed by the * callback. - * - the return GVariant must not be an empty tuple "()". * - the GTask is returned either with error or with a pointer containing the GVariant. */ void diff --git a/shared/nm-glib-aux/nm-dbus-aux.h b/shared/nm-glib-aux/nm-dbus-aux.h index 840e23c2..fcf394d1 100644 --- a/shared/nm-glib-aux/nm-dbus-aux.h +++ b/shared/nm-glib-aux/nm-dbus-aux.h @@ -128,16 +128,27 @@ void nm_dbus_connection_call_get_all (GDBusConnection *dbus_connection, NMDBusConnectionCallDefaultCb callback, gpointer user_data); +void nm_dbus_connection_call_set (GDBusConnection *dbus_connection, + const char *bus_name, + const char *object_path, + const char *interface_name, + const char *property_name, + GVariant *value, + int timeout_msec, + GCancellable *cancellable, + NMDBusConnectionCallDefaultCb callback, + gpointer user_data); + /*****************************************************************************/ static inline guint -nm_dbus_connection_signal_subscribe_object_manager_plain (GDBusConnection *dbus_connection, - const char *service_name, - const char *object_path, - const char *signal_name, - GDBusSignalCallback callback, - gpointer user_data, - GDestroyNotify user_data_free_func) +nm_dbus_connection_signal_subscribe_object_manager (GDBusConnection *dbus_connection, + const char *service_name, + const char *object_path, + const char *signal_name, + GDBusSignalCallback callback, + gpointer user_data, + GDestroyNotify user_data_free_func) { return g_dbus_connection_signal_subscribe (dbus_connection, service_name, @@ -151,18 +162,6 @@ nm_dbus_connection_signal_subscribe_object_manager_plain (GDBusConnection *dbus_ user_data_free_func); } -typedef void (*NMDBusConnectionSignalObjectMangerCb) (const char *object_path, - GVariant *added_interfaces_and_properties, - const char *const*removed_interfaces, - gpointer user_data); - -guint nm_dbus_connection_signal_subscribe_object_manager (GDBusConnection *dbus_connection, - const char *service_name, - const char *object_path, - NMDBusConnectionSignalObjectMangerCb callback, - gpointer user_data, - GDestroyNotify user_data_free_func); - void nm_dbus_connection_call_get_managed_objects (GDBusConnection *dbus_connection, const char *bus_name, const char *object_path, diff --git a/shared/nm-glib-aux/nm-enum-utils.c b/shared/nm-glib-aux/nm-enum-utils.c index e105a4f5..854eda6e 100644 --- a/shared/nm-glib-aux/nm-enum-utils.c +++ b/shared/nm-glib-aux/nm-enum-utils.c @@ -6,6 +6,7 @@ #include "nm-default.h" #include "nm-enum-utils.h" +#include "nm-str-buf.h" /*****************************************************************************/ @@ -142,12 +143,14 @@ _nm_utils_enum_to_str_full (GType type, else return g_strdup (enum_value->value_nick); } else if (G_IS_FLAGS_CLASS (klass)) { - GFlagsValue *flags_value; - GString *str = g_string_new (""); unsigned uvalue = (unsigned) value; + GFlagsValue *flags_value; + NMStrBuf strbuf; flags_separator = flags_separator ?: " "; + nm_str_buf_init (&strbuf, 16, FALSE); + for ( ; value_infos && value_infos->nick; value_infos++) { nm_assert (_enum_is_valid_flags_nick (value_infos->nick)); @@ -160,9 +163,9 @@ _nm_utils_enum_to_str_full (GType type, continue; } - if (str->len) - g_string_append (str, flags_separator); - g_string_append (str, value_infos->nick); + if (strbuf.len) + nm_str_buf_append (&strbuf, flags_separator); + nm_str_buf_append (&strbuf, value_infos->nick); uvalue &= ~((unsigned) value_infos->value); if (uvalue == 0) { /* we printed all flags. Done. */ @@ -172,20 +175,20 @@ _nm_utils_enum_to_str_full (GType type, do { flags_value = g_flags_get_first_value (G_FLAGS_CLASS (klass), uvalue); - if (str->len) - g_string_append (str, flags_separator); + if (strbuf.len) + nm_str_buf_append (&strbuf, flags_separator); if ( !flags_value || !_enum_is_valid_flags_nick (flags_value->value_nick)) { if (uvalue) - g_string_append_printf (str, "0x%x", uvalue); + nm_str_buf_append_printf (&strbuf, "0x%x", uvalue); break; } - g_string_append (str, flags_value->value_nick); + nm_str_buf_append (&strbuf, flags_value->value_nick); uvalue &= ~flags_value->value; } while (uvalue); flags_done: - return g_string_free (str, FALSE); + return nm_str_buf_finalize (&strbuf, NULL); } g_return_val_if_reached (NULL); diff --git a/shared/nm-glib-aux/nm-errno.c b/shared/nm-glib-aux/nm-errno.c index 9133d87d..e709f9e6 100644 --- a/shared/nm-glib-aux/nm-errno.c +++ b/shared/nm-glib-aux/nm-errno.c @@ -11,7 +11,8 @@ /*****************************************************************************/ -NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_geterror, +static +NM_UTILS_LOOKUP_STR_DEFINE (_geterror, #if 0 enum _NMErrno, #else diff --git a/shared/nm-glib-aux/nm-glib.h b/shared/nm-glib-aux/nm-glib.h index 26d0eacd..4ecba9ff 100644 --- a/shared/nm-glib-aux/nm-glib.h +++ b/shared/nm-glib-aux/nm-glib.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0+ +// SPDX-License-Identifier: LGPL-2.1+ /* * Copyright (C) 2008 - 2018 Red Hat, Inc. */ @@ -193,22 +193,6 @@ __g_test_add_data_func_full (const char *testpath, /*****************************************************************************/ -#if !GLIB_CHECK_VERSION (2, 34, 0) -#define G_DEFINE_QUARK(QN, q_n) \ -GQuark \ -q_n##_quark (void) \ -{ \ - static GQuark q; \ - \ - if G_UNLIKELY (q == 0) \ - q = g_quark_from_static_string (#QN); \ - \ - return q; \ -} -#endif - -/*****************************************************************************/ - static inline gboolean nm_g_hash_table_replace (GHashTable *hash, gpointer key, gpointer value) { @@ -601,4 +585,41 @@ _g_atomic_pointer_compare_and_exchange (volatile void *atomic, /*****************************************************************************/ +#if !GLIB_CHECK_VERSION (2, 58, 0) +static inline gboolean +g_hash_table_steal_extended (GHashTable *hash_table, + gconstpointer lookup_key, + gpointer *stolen_key, + gpointer *stolen_value) +{ + g_assert (stolen_key); + g_assert (stolen_value); + + if (g_hash_table_lookup_extended (hash_table, lookup_key, stolen_key, stolen_value)) { + g_hash_table_steal (hash_table, lookup_key); + return TRUE; + } + *stolen_key = NULL; + *stolen_value = NULL; + return FALSE; +} +#else +#define g_hash_table_steal_extended(hash_table, lookup_key, stolen_key, stolen_value) \ + ({ \ + gpointer *_stolen_key = (stolen_key); \ + gpointer *_stolen_value = (stolen_value); \ + \ + /* we cannot allow NULL arguments, because then we would leak the values in + * the compat implementation. */ \ + g_assert (_stolen_key); \ + g_assert (_stolen_value); \ + \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ + g_hash_table_steal_extended (hash_table, lookup_key, _stolen_key, _stolen_value); \ + G_GNUC_END_IGNORE_DEPRECATIONS \ + }) +#endif + +/*****************************************************************************/ + #endif /* __NM_GLIB_H__ */ diff --git a/shared/nm-glib-aux/nm-hash-utils.c b/shared/nm-glib-aux/nm-hash-utils.c index cd51bbaf..0a701d06 100644 --- a/shared/nm-glib-aux/nm-hash-utils.c +++ b/shared/nm-glib-aux/nm-hash-utils.c @@ -24,7 +24,6 @@ static const guint8 *volatile global_seed = NULL; static const guint8 * _get_hash_key_init (void) { - static gsize g_lock; /* the returned hash is aligned to guin64, hence, it is safe * to use it as guint* or guint64* pointer. */ static union { @@ -34,50 +33,49 @@ _get_hash_key_init (void) guint64 _align_as_uint64; } g_arr; const guint8 *g; - union { - guint8 v8[HASH_KEY_SIZE]; - guint vuint; - } t_arr; again: g = g_atomic_pointer_get (&global_seed); - if (G_LIKELY (g != NULL)) { - nm_assert (g == g_arr.v8); - return g; - } - - { - CSipHash siph_state; + if (!G_UNLIKELY (g)) { + static gsize g_lock; uint64_t h; - - /* initialize a random key in t_arr. */ + union { + guint vuint; + guint8 v8[HASH_KEY_SIZE]; + guint8 _extra_entropy[3 * HASH_KEY_SIZE]; + } t_arr; nm_utils_random_bytes (&t_arr, sizeof (t_arr)); - /* use siphash() of the key-size, to mangle the first guint. Otherwise, - * the first guint has only the entropy that nm_utils_random_bytes() - * generated for the first 4 bytes and relies on a good random generator. + /* We only initialize one random hash key. So we can spend some effort + * of getting this right. For one, we collect more random bytes than + * necessary. * - * The first int is especially interesting for nm_hash_static() below, and we - * want to have it all the entropy of t_arr. */ - c_siphash_init (&siph_state, t_arr.v8); - c_siphash_append (&siph_state, (const guint8 *) &t_arr, sizeof (t_arr)); - h = c_siphash_finalize (&siph_state); - if (sizeof (guint) < sizeof (h)) - t_arr.vuint = t_arr.vuint ^ ((guint) (h & 0xFFFFFFFFu)) ^ ((guint) (h >> 32)); + * Then, the first guint of the seed should have all the entropy that we could + * obtain in sizeof(t_arr). For that, siphash(t_arr) and xor the first guint + * with hash. + * The first guint is especially interesting for nm_hash_static() below that + * doesn't use siphash itself. */ + h = c_siphash_hash (t_arr.v8, + (const guint8 *) &t_arr, + sizeof (t_arr)); + if (sizeof (h) > sizeof (guint)) + t_arr.vuint = t_arr.vuint ^ ((guint) (h & G_MAXUINT)) ^ ((guint) (h >> 32)); else - t_arr.vuint = t_arr.vuint ^ ((guint) (h & 0xFFFFFFFFu)); - } + t_arr.vuint = t_arr.vuint ^ ((guint) (h & G_MAXUINT)); - if (!g_once_init_enter (&g_lock)) { - /* lost a race. The random key is already initialized. */ - goto again; + if (!g_once_init_enter (&g_lock)) { + /* lost a race. The random key is already initialized. */ + goto again; + } + + memcpy (g_arr.v8, t_arr.v8, HASH_KEY_SIZE); + g = g_arr.v8; + g_atomic_pointer_set (&global_seed, g); + g_once_init_leave (&g_lock, 1); } - memcpy (g_arr.v8, t_arr.v8, HASH_KEY_SIZE); - g = g_arr.v8; - g_atomic_pointer_set (&global_seed, g); - g_once_init_leave (&g_lock, 1); + nm_assert (g == g_arr.v8); return g; } @@ -94,18 +92,24 @@ again: guint nm_hash_static (guint static_seed) { - /* note that we only xor the static_seed with the key. - * We don't use siphash, which would mix the bits better. - * Note that this doesn't matter, because static_seed is not - * supposed to be a value that you are hashing (for that, use - * full siphash). - * Instead, different callers may set a different static_seed - * so that nm_hash_str(NULL) != nm_hash_ptr(NULL). + /* Note that we only xor the static_seed with the first guint of the key. + * + * We don't use siphash, which would mix the bits better with _get_hash_key(). + * Note that nm_hash_static() isn't used to hash the static_seed. Instead, it + * is used to get a unique hash value in a static context. That means, every + * caller is responsible to choose a static_seed that is sufficiently + * distinct from all other callers. In other words, static_seed should be a + * unique constant with good entropy. + * + * Note that _get_hash_key_init() already xored the first guint of the + * key with the siphash of the entire static key. That means, even if + * we got bad randomness for the first guint, the first guint is also + * mixed with the randomness of the entire random key. * - * Also, ensure that we don't return zero. + * Also, ensure that we don't return zero (like for nm_hash_complete()). */ - return ((*((const guint *) _get_hash_key ())) ^ static_seed) - ?: static_seed ?: 3679500967u; + return ((*((const guint *) _get_hash_key ())) ^ static_seed) + ?: 3679500967u; } void @@ -257,3 +261,25 @@ nm_ppdirect_equal (gconstpointer a, gconstpointer b) return **s1 == **s2; } + +/*****************************************************************************/ + +guint +nm_pgbytes_hash (gconstpointer p) +{ + GBytes *const*ptr = p; + gconstpointer arr; + gsize len; + + arr = g_bytes_get_data (*ptr, &len); + return nm_hash_mem (1470631313u, arr, len); +} + +gboolean +nm_pgbytes_equal (gconstpointer a, gconstpointer b) +{ + GBytes *const*ptr_a = a; + GBytes *const*ptr_b = b; + + return g_bytes_equal (*ptr_a, *ptr_b); +} diff --git a/shared/nm-glib-aux/nm-hash-utils.h b/shared/nm-glib-aux/nm-hash-utils.h index 21c5e584..9f2e9766 100644 --- a/shared/nm-glib-aux/nm-hash-utils.h +++ b/shared/nm-glib-aux/nm-hash-utils.h @@ -88,15 +88,15 @@ nm_hash_complete (NMHashState *state) /* we don't ever want to return a zero hash. * * NMPObject requires that in _idx_obj_part(), and it's just a good idea. */ - return (((guint) (h >> 32)) ^ ((guint) h)) ?: 1396707757u; + return (((guint) (h >> 32)) ^ ((guint) h)) + ?: 1396707757u; } static inline void nm_hash_update (NMHashState *state, const void *ptr, gsize n) { nm_assert (state); - nm_assert (ptr); - nm_assert (n > 0); + nm_assert (n == 0 || ptr); /* Note: the data passed in here might be sensitive data (secrets), * that we should nm_explicty_zero() afterwards. However, since @@ -267,6 +267,18 @@ guint nm_str_hash (gconstpointer str); nm_hash_complete (&_h); \ }) +static inline guint +nm_hash_mem (guint static_seed, const void *ptr, gsize n) +{ + NMHashState h; + + if (n == 0) + return nm_hash_static (static_seed); + nm_hash_init (&h, static_seed); + nm_hash_update (&h, ptr, n); + return nm_hash_complete (&h); +} + /*****************************************************************************/ /* nm_pstr_*() are for hashing keys that are pointers to strings, @@ -303,6 +315,11 @@ gboolean nm_ppdirect_equal (gconstpointer a, gconstpointer b); /*****************************************************************************/ +guint nm_pgbytes_hash (gconstpointer p); +gboolean nm_pgbytes_equal (gconstpointer a, gconstpointer b); + +/*****************************************************************************/ + #define NM_HASH_OBFUSCATE_PTR_FMT "%016" G_GINT64_MODIFIER "x" /* sometimes we want to log a pointer directly, for providing context/information about diff --git a/shared/nm-glib-aux/nm-io-utils.c b/shared/nm-glib-aux/nm-io-utils.c index ec0cd4c8..776c63e1 100644 --- a/shared/nm-glib-aux/nm-io-utils.c +++ b/shared/nm-glib-aux/nm-io-utils.c @@ -11,6 +11,7 @@ #include <sys/stat.h> #include <fcntl.h> +#include "nm-str-buf.h" #include "nm-shared-utils.h" #include "nm-secret-utils.h" #include "nm-errno.h" @@ -51,32 +52,6 @@ _get_contents_error (GError **error, int errsv, int *out_errsv, const char *form _get_contents_error (error, _errsv, out_errsv, __VA_ARGS__); \ }) -static char * -_mem_realloc (char *old, gboolean do_bzero_mem, gsize cur_len, gsize new_len) -{ - char *new; - - /* re-allocating to zero bytes is an odd case. We don't need it - * and it's not supported. */ - nm_assert (new_len > 0); - - /* regardless of success/failure, @old will always be freed/consumed. */ - - if (do_bzero_mem && cur_len > 0) { - new = g_try_malloc (new_len); - if (new) - memcpy (new, old, NM_MIN (cur_len, new_len)); - nm_explicit_bzero (old, cur_len); - g_free (old); - } else { - new = g_try_realloc (old, new_len); - if (!new) - g_free (old); - } - - return new; -} - /** * nm_utils_fd_get_contents: * @fd: open file descriptor to read. The fd will not be closed, @@ -161,7 +136,7 @@ nm_utils_fd_get_contents (int fd, str[n_read] = '\0'; if (n_read < n_stat) { - if (!(str = _mem_realloc (str, do_bzero_mem, n_stat + 1, n_read + 1))) + if (!(str = nm_secret_mem_try_realloc_take (str, do_bzero_mem, n_stat + 1, n_read + 1))) return _get_contents_error (error, ENOMEM, out_errsv, "failure to reallocate buffer with %zu bytes", n_read + 1); } NM_SET_OUT (length, n_read); @@ -222,7 +197,7 @@ nm_utils_fd_get_contents (int fd, n_alloc = NM_MIN (n_read + 1, sizeof (buf)); } - if (!(str = _mem_realloc (str, do_bzero_mem, old_n_alloc, n_alloc))) { + if (!(str = nm_secret_mem_try_realloc_take (str, do_bzero_mem, old_n_alloc, n_alloc))) { if (do_bzero_mem) nm_explicit_bzero (buf, sizeof (buf)); return _get_contents_error (error, ENOMEM, out_errsv, "failure to allocate buffer of %zu bytes", n_alloc); @@ -241,7 +216,7 @@ nm_utils_fd_get_contents (int fd, else { str[n_have] = '\0'; if (n_have + 1 < n_alloc) { - if (!(str = _mem_realloc (str, do_bzero_mem, n_alloc, n_have + 1))) + if (!(str = nm_secret_mem_try_realloc_take (str, do_bzero_mem, n_alloc, n_have + 1))) return _get_contents_error (error, ENOMEM, out_errsv, "failure to truncate buffer to %zu bytes", n_have + 1); } } @@ -438,3 +413,46 @@ nm_utils_file_stat (const char *filename, struct stat *out_st) return -NM_ERRNO_NATIVE (errno); return 0; } + +/** + * nm_utils_fd_read: + * @fd: the fd to read from. + * @out_string: (out): output string where read bytes will be stored. + * + * Returns: <0 on failure, which is -(errno). + * 0 on EOF. + * >0 on success, which is the number of bytes read. */ +gssize +nm_utils_fd_read (int fd, NMStrBuf *out_string) +{ + gsize buf_available; + gssize n_read; + int errsv; + + g_return_val_if_fail (fd >= 0, -1); + g_return_val_if_fail (out_string, -1); + + /* If the buffer size is 0, we allocate NM_UTILS_GET_NEXT_REALLOC_SIZE_1000 (1000 bytes) + * the first time. Afterwards, the buffer grows exponentially. + * + * Note that with @buf_available, we always would read as much buffer as we actually + * have reserved. */ + nm_str_buf_maybe_expand (out_string, NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, FALSE); + + buf_available = out_string->allocated - out_string->len; + + n_read = read (fd, + &((nm_str_buf_get_str_unsafe (out_string))[out_string->len]), + buf_available); + if (n_read < 0) { + errsv = errno; + return -NM_ERRNO_NATIVE (errsv); + } + + if (n_read > 0) { + nm_assert ((gsize) n_read <= buf_available); + nm_str_buf_set_size (out_string, out_string->len + (gsize) n_read, TRUE, FALSE); + } + + return n_read; +} diff --git a/shared/nm-glib-aux/nm-io-utils.h b/shared/nm-glib-aux/nm-io-utils.h index 18dc5f74..31326fc7 100644 --- a/shared/nm-glib-aux/nm-io-utils.h +++ b/shared/nm-glib-aux/nm-io-utils.h @@ -47,6 +47,10 @@ gboolean nm_utils_file_set_contents (const char *filename, int *out_errsv, GError **error); +struct _NMStrBuf; + +gssize nm_utils_fd_read (int fd, struct _NMStrBuf *out_string); + struct stat; int nm_utils_file_stat (const char *filename, struct stat *out_st); diff --git a/shared/nm-glib-aux/nm-jansson.h b/shared/nm-glib-aux/nm-jansson.h index 0a75cff5..7c034222 100644 --- a/shared/nm-glib-aux/nm-jansson.h +++ b/shared/nm-glib-aux/nm-jansson.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0+ +// SPDX-License-Identifier: LGPL-2.1+ /* * Copyright (C) 2018 Red Hat, Inc. */ diff --git a/shared/nm-glib-aux/nm-logging-fwd.h b/shared/nm-glib-aux/nm-logging-fwd.h index 47661788..2daa302f 100644 --- a/shared/nm-glib-aux/nm-logging-fwd.h +++ b/shared/nm-glib-aux/nm-logging-fwd.h @@ -63,6 +63,14 @@ typedef enum { /*< skip >*/ LOGD_IP = LOGD_IP4 | LOGD_IP6, } NMLogDomain; +static inline NMLogDomain +LOGD_DHCP_from_addr_family (int addr_family) +{ + nm_assert_addr_family (addr_family); + + return addr_family == AF_INET6 ? LOGD_DHCP6 : LOGD_DHCP4; +} + /* Log levels */ typedef enum { /*< skip >*/ LOGL_TRACE, diff --git a/shared/nm-glib-aux/nm-macros-internal.h b/shared/nm-glib-aux/nm-macros-internal.h index d1ea04c0..d5a8513e 100644 --- a/shared/nm-glib-aux/nm-macros-internal.h +++ b/shared/nm-glib-aux/nm-macros-internal.h @@ -337,52 +337,65 @@ _nm_auto_freev (gpointer ptr) /*****************************************************************************/ -/* http://stackoverflow.com/a/11172679 */ -#define _NM_UTILS_MACRO_FIRST(...) __NM_UTILS_MACRO_FIRST_HELPER(__VA_ARGS__, throwaway) -#define __NM_UTILS_MACRO_FIRST_HELPER(first, ...) first - -#define _NM_UTILS_MACRO_REST(...) __NM_UTILS_MACRO_REST_HELPER(__NM_UTILS_MACRO_REST_NUM(__VA_ARGS__), __VA_ARGS__) -#define __NM_UTILS_MACRO_REST_HELPER(qty, ...) __NM_UTILS_MACRO_REST_HELPER2(qty, __VA_ARGS__) -#define __NM_UTILS_MACRO_REST_HELPER2(qty, ...) __NM_UTILS_MACRO_REST_HELPER_##qty(__VA_ARGS__) -#define __NM_UTILS_MACRO_REST_HELPER_ONE(first) -#define __NM_UTILS_MACRO_REST_HELPER_TWOORMORE(first, ...) , __VA_ARGS__ -#define __NM_UTILS_MACRO_REST_NUM(...) \ - __NM_UTILS_MACRO_REST_SELECT_30TH(__VA_ARGS__, \ - TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\ - TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\ - TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\ - TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\ - TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\ - TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway) -#define __NM_UTILS_MACRO_REST_SELECT_30TH(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, ...) a30 - -/*****************************************************************************/ - -/* http://stackoverflow.com/a/2124385/354393 - * https://stackoverflow.com/questions/11317474/macro-to-count-number-of-arguments - */ - -#define NM_NARG(...) \ - _NM_NARG(, ##__VA_ARGS__, _NM_NARG_RSEQ_N()) -#define _NM_NARG(...) \ - _NM_NARG_ARG_N(__VA_ARGS__) -#define _NM_NARG_ARG_N( \ - _0, \ +#define _NM_MACRO_SELECT_ARG_64( \ _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \ _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \ _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \ _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \ _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \ _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \ - _61,_62,_63,N,...) N -#define _NM_NARG_RSEQ_N() \ - 63,62,61,60, \ - 59,58,57,56,55,54,53,52,51,50, \ - 49,48,47,46,45,44,43,42,41,40, \ - 39,38,37,36,35,34,33,32,31,30, \ - 29,28,27,26,25,24,23,22,21,20, \ - 19,18,17,16,15,14,13,12,11,10, \ - 9,8,7,6,5,4,3,2,1,0 + _61,_62,_63, N,...) N + +/* http://stackoverflow.com/a/2124385/354393 + * https://stackoverflow.com/questions/11317474/macro-to-count-number-of-arguments + */ + +#define NM_NARG(...) \ + _NM_MACRO_SELECT_ARG_64(, ##__VA_ARGS__, \ + 62,61,60, \ + 59,58,57,56,55,54,53,52,51,50, \ + 49,48,47,46,45,44,43,42,41,40, \ + 39,38,37,36,35,34,33,32,31,30, \ + 29,28,27,26,25,24,23,22,21,20, \ + 19,18,17,16,15,14,13,12,11,10, \ + 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) +#define NM_NARG_MAX1(...) \ + _NM_MACRO_SELECT_ARG_64(, ##__VA_ARGS__, \ + 1, 1, 1, \ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0) +#define NM_NARG_MAX2(...) \ + _NM_MACRO_SELECT_ARG_64(, ##__VA_ARGS__, \ + 2, 2, 2, \ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \ + 2, 2, 2, 2, 2, 2, 2, 2, 1, 0) + +#define _NM_MACRO_CALL(macro, ...) macro (__VA_ARGS__) + +/*****************************************************************************/ + +#define _NM_MACRO_COMMA_IF_ARGS(...) _NM_MACRO_CALL (G_PASTE (__NM_MACRO_COMMA_IF_ARGS_, NM_NARG_MAX1(__VA_ARGS__)), __VA_ARGS__) +#define __NM_MACRO_COMMA_IF_ARGS_0() +#define __NM_MACRO_COMMA_IF_ARGS_1(...) , + +/*****************************************************************************/ + +/* http://stackoverflow.com/a/11172679 */ +#define _NM_UTILS_MACRO_FIRST(...) __NM_UTILS_MACRO_FIRST_HELPER(__VA_ARGS__, throwaway) +#define __NM_UTILS_MACRO_FIRST_HELPER(first, ...) first + +#define _NM_UTILS_MACRO_REST(...) _NM_MACRO_CALL (G_PASTE (__NM_UTILS_MACRO_REST_, NM_NARG_MAX2(__VA_ARGS__)), __VA_ARGS__) +#define __NM_UTILS_MACRO_REST_0() +#define __NM_UTILS_MACRO_REST_1(first) +#define __NM_UTILS_MACRO_REST_2(first, ...) , __VA_ARGS__ /*****************************************************************************/ @@ -744,6 +757,17 @@ NM_G_ERROR_MSG (GError *error) /*****************************************************************************/ +#define NM_SWAP(a, b) \ + G_STMT_START { \ + typeof (a) _tmp; \ + \ + _tmp = (a); \ + (a) = (b); \ + (b) = _tmp; \ + } G_STMT_END + +/*****************************************************************************/ + static inline gboolean _NM_IN_STRSET_streq (const char *x, const char *s) { @@ -980,6 +1004,7 @@ nm_str_realloc (char *str) (cond) ? (str) : (str_else), \ (cond) ? (suffix) : "" #define NM_PRINT_FMT_QUOTE_STRING(arg) NM_PRINT_FMT_QUOTED((arg), "\"", (arg), "\"", "(null)") +#define NM_PRINT_FMT_QUOTE_REF_STRING(arg) NM_PRINT_FMT_QUOTED((arg), "\"", (arg)->str, "\"", "(null)") /*****************************************************************************/ @@ -1009,19 +1034,37 @@ nm_str_realloc (char *str) #define nm_assert_not_reached() G_STMT_START { ; } G_STMT_END #endif +/* Usage: + * + * if (NM_MORE_ASSERT_ONCE (5)) { extra_check (); } + * + * This will only run the check once, and only if NM_MORE_ASSERT is >= than + * more_assert_level. + */ +#define NM_MORE_ASSERT_ONCE(more_assert_level) \ + ( (NM_MORE_ASSERTS >= (more_assert_level)) \ + && ({ \ + static volatile int _assert_once = 0; \ + \ + G_STATIC_ASSERT_EXPR ((more_assert_level) > 0); \ + \ + G_UNLIKELY ( _assert_once == 0 \ + && g_atomic_int_compare_and_exchange (&_assert_once, 0, 1)); \ + })) + /*****************************************************************************/ -#define NM_GOBJECT_PROPERTIES_DEFINE_BASE(...) \ +#define NM_GOBJECT_PROPERTIES_DEFINE_BASE_FULL(suffix, ...) \ typedef enum { \ - PROP_0, \ + PROP_0##suffix, \ __VA_ARGS__ \ - _PROPERTY_ENUMS_LAST, \ -} _PropertyEnums; \ -static GParamSpec *obj_properties[_PROPERTY_ENUMS_LAST] = { NULL, } + _PROPERTY_ENUMS_LAST##suffix, \ +} _PropertyEnums##suffix; \ +static GParamSpec *obj_properties##suffix[_PROPERTY_ENUMS_LAST##suffix] = { NULL, } -#define NM_GOBJECT_PROPERTIES_DEFINE_NOTIFY(obj_type, obj_properties, property_enums_type, prop_0) \ +#define NM_GOBJECT_PROPERTIES_DEFINE_NOTIFY(suffix, obj_type) \ static inline void \ -_nm_gobject_notify_together_impl (obj_type *obj, guint n, const property_enums_type *props) \ +_nm_gobject_notify_together_impl##suffix (obj_type *obj, guint n, const _PropertyEnums##suffix *props) \ { \ const gboolean freeze_thaw = (n > 1); \ \ @@ -1031,12 +1074,12 @@ _nm_gobject_notify_together_impl (obj_type *obj, guint n, const property_enums_t if (freeze_thaw) \ g_object_freeze_notify ((GObject *) obj); \ while (n-- > 0) { \ - const property_enums_type prop = *props++; \ + const _PropertyEnums##suffix prop = *props++; \ \ - if (prop != prop_0) { \ - nm_assert ((gsize) prop < G_N_ELEMENTS (obj_properties)); \ - nm_assert (obj_properties[prop]); \ - g_object_notify_by_pspec ((GObject *) obj, obj_properties[prop]); \ + if (prop != PROP_0##suffix) { \ + nm_assert ((gsize) prop < G_N_ELEMENTS (obj_properties##suffix)); \ + nm_assert (obj_properties##suffix[prop]); \ + g_object_notify_by_pspec ((GObject *) obj, obj_properties##suffix[prop]); \ } \ } \ if (freeze_thaw) \ @@ -1044,20 +1087,29 @@ _nm_gobject_notify_together_impl (obj_type *obj, guint n, const property_enums_t } \ \ _nm_unused static inline void \ -_notify (obj_type *obj, property_enums_type prop) \ +_notify##suffix (obj_type *obj, _PropertyEnums##suffix prop) \ { \ - _nm_gobject_notify_together_impl (obj, 1, &prop); \ + _nm_gobject_notify_together_impl##suffix (obj, 1, &prop); \ } \ +#define NM_GOBJECT_PROPERTIES_DEFINE_BASE(...) \ + NM_GOBJECT_PROPERTIES_DEFINE_BASE_FULL (, __VA_ARGS__); \ + +#define NM_GOBJECT_PROPERTIES_DEFINE_FULL(suffix, obj_type, ...) \ + NM_GOBJECT_PROPERTIES_DEFINE_BASE_FULL (suffix, __VA_ARGS__); \ + NM_GOBJECT_PROPERTIES_DEFINE_NOTIFY (suffix, obj_type) + #define NM_GOBJECT_PROPERTIES_DEFINE(obj_type, ...) \ -NM_GOBJECT_PROPERTIES_DEFINE_BASE (__VA_ARGS__); \ -NM_GOBJECT_PROPERTIES_DEFINE_NOTIFY (obj_type, obj_properties, _PropertyEnums, PROP_0) + NM_GOBJECT_PROPERTIES_DEFINE_FULL (, obj_type, __VA_ARGS__) /* invokes _notify() for all arguments (of type _PropertyEnums). Note, that if * there are more than one prop arguments, this will involve a freeze/thaw * of GObject property notifications. */ +#define nm_gobject_notify_together_full(suffix, obj, ...) \ + _nm_gobject_notify_together_impl##suffix (obj, NM_NARG (__VA_ARGS__), (const _PropertyEnums##suffix[]) { __VA_ARGS__ }) + #define nm_gobject_notify_together(obj, ...) \ - _nm_gobject_notify_together_impl (obj, NM_NARG (__VA_ARGS__), (const _PropertyEnums[]) { __VA_ARGS__ }) + nm_gobject_notify_together_full (, obj, __VA_ARGS__) /*****************************************************************************/ @@ -1065,9 +1117,9 @@ NM_GOBJECT_PROPERTIES_DEFINE_NOTIFY (obj_type, obj_properties, _PropertyEnums, P #if _NM_CC_SUPPORT_AUTO_TYPE #define _NM_GET_PRIVATE_PTR(self, type, is_check, ...) \ ({ \ - _nm_auto_type _self = NM_GOBJECT_CAST_NON_NULL (type, (self), is_check, ##__VA_ARGS__); \ + _nm_auto_type _self_get_private = NM_GOBJECT_CAST_NON_NULL (type, (self), is_check, ##__VA_ARGS__); \ \ - NM_PROPAGATE_CONST (_self, _self->_priv); \ + NM_PROPAGATE_CONST (_self_get_private, _self_get_private->_priv); \ }) #else #define _NM_GET_PRIVATE_PTR(self, type, is_check, ...) (NM_GOBJECT_CAST_NON_NULL (type, (self), is_check, ##__VA_ARGS__)->_priv) @@ -1348,8 +1400,8 @@ nm_g_variant_take_ref (GVariant *v) #define NM_UTILS_LOOKUP_ITEM_IGNORE(v) (void) 0; case v: break; (void) 0 #define NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER() (void) 0; default: break; (void) 0 -#define _NM_UTILS_LOOKUP_DEFINE(scope, fcn_name, lookup_type, result_type, unknown_val, ...) \ -scope result_type \ +#define NM_UTILS_LOOKUP_DEFINE(fcn_name, lookup_type, result_type, unknown_val, ...) \ +result_type \ fcn_name (lookup_type val) \ { \ switch (val) { \ @@ -1361,9 +1413,7 @@ fcn_name (lookup_type val) \ } #define NM_UTILS_LOOKUP_STR_DEFINE(fcn_name, lookup_type, unknown_val, ...) \ - _NM_UTILS_LOOKUP_DEFINE (, fcn_name, lookup_type, const char *, unknown_val, __VA_ARGS__) -#define NM_UTILS_LOOKUP_STR_DEFINE_STATIC(fcn_name, lookup_type, unknown_val, ...) \ - _NM_UTILS_LOOKUP_DEFINE (static, fcn_name, lookup_type, const char *, unknown_val, __VA_ARGS__) + NM_UTILS_LOOKUP_DEFINE (fcn_name, lookup_type, const char *, unknown_val, __VA_ARGS__) /* Call the string-lookup-table function @fcn_name. If the function returns * %NULL, the numeric index is converted to string using a alloca() buffer. @@ -1415,6 +1465,14 @@ fcn_name (lookup_type val) \ : _flags & (~_val); \ }) +#define NM_FLAGS_ASSIGN_MASK(flags, mask, val) ({ \ + const typeof(flags) _flags = (flags); \ + const typeof(flags) _mask = (mask); \ + const typeof(flags) _val = (val); \ + \ + ((_flags & ~_mask) | (_mask & _val)); \ + }) + /*****************************************************************************/ #define _NM_BACKPORT_SYMBOL_IMPL(version, return_type, orig_func, versioned_func, args_typed, args) \ @@ -1432,7 +1490,12 @@ _NM_BACKPORT_SYMBOL_IMPL(version, return_type, func, _##func##_##version, args_t /*****************************************************************************/ /* mirrors g_ascii_isspace() and what we consider spaces in general. */ -#define NM_ASCII_SPACES "\t\n\f\r " +#define NM_ASCII_SPACES " \n\t\r\f" + +/* Like NM_ASCII_SPACES, but without "\f" (0x0c, Formfeed Page Break). + * This is what for example systemd calls WHITESPACE and what it uses to tokenize + * the kernel command line. */ +#define NM_ASCII_WHITESPACES " \n\t\r" #define nm_str_skip_leading_spaces(str) \ ({ \ @@ -1508,6 +1571,17 @@ nm_strstrip_avoid_copy (const char *str, char **str_free) _str_ssac; \ }) +static inline gboolean +nm_str_is_stripped (const char *str) +{ + if (str && str[0]) { + if ( g_ascii_isspace (str[0]) + || g_ascii_isspace (str[strlen (str) - 1])) + return FALSE; + } + return TRUE; +} + /* g_ptr_array_sort()'s compare function takes pointers to the * value. Thus, you cannot use strcmp directly. You can use * nm_strcmp_p(). diff --git a/shared/nm-glib-aux/nm-ref-string.h b/shared/nm-glib-aux/nm-ref-string.h index c80b75ea..60052c45 100644 --- a/shared/nm-glib-aux/nm-ref-string.h +++ b/shared/nm-glib-aux/nm-ref-string.h @@ -50,6 +50,18 @@ nm_ref_string_get_len (NMRefString *rstr) } static inline gboolean +nm_ref_string_equals_str (NMRefString *rstr, const char *s) +{ + /* Note that rstr->len might be greater than strlen(rstr->str). This function does + * not cover that and would ignore everything after the first NUL byte. If you need + * that distinction, this function is not for you. */ + + return rstr + ? (s && nm_streq (rstr->str, s)) + : (s == NULL); +} + +static inline gboolean NM_IS_REF_STRING (const NMRefString *rstr) { #if NM_MORE_ASSERTS > 10 diff --git a/shared/nm-glib-aux/nm-secret-utils.c b/shared/nm-glib-aux/nm-secret-utils.c index 5b0afe46..78369b5e 100644 --- a/shared/nm-glib-aux/nm-secret-utils.c +++ b/shared/nm-glib-aux/nm-secret-utils.c @@ -8,13 +8,15 @@ #include "nm-secret-utils.h" +#include <malloc.h> + /*****************************************************************************/ void nm_explicit_bzero (void *s, gsize n) { /* gracefully handle n == 0. This is important, callers rely on it. */ - if (n == 0) + if (G_UNLIKELY (n == 0)) return; nm_assert (s); @@ -32,6 +34,30 @@ nm_explicit_bzero (void *s, gsize n) #endif } +void +nm_free_secret (char *secret) +{ + gsize len; + + if (!secret) + return; + +#if GLIB_CHECK_VERSION(2,44,0) + /* Here we mix malloc() and g_malloc() API. Usually we avoid this, + * however since glib 2.44.0 we are in fact guaranteed that g_malloc()/g_free() + * just wraps malloc()/free(), so this is actually fine. + * + * See https://gitlab.gnome.org/GNOME/glib/commit/3be6ed60aa58095691bd697344765e715a327fc1 + */ + len = malloc_usable_size (secret); +#else + len = strlen (secret); +#endif + + nm_explicit_bzero (secret, len); + g_free (secret); +} + /*****************************************************************************/ char * diff --git a/shared/nm-glib-aux/nm-secret-utils.h b/shared/nm-glib-aux/nm-secret-utils.h index 1b98b7e9..501e7520 100644 --- a/shared/nm-glib-aux/nm-secret-utils.h +++ b/shared/nm-glib-aux/nm-secret-utils.h @@ -18,14 +18,7 @@ char *nm_secret_strchomp (char *secret); /*****************************************************************************/ -static inline void -nm_free_secret (char *secret) -{ - if (secret) { - nm_explicit_bzero (secret, strlen (secret)); - g_free (secret); - } -} +void nm_free_secret (char *secret); NM_AUTO_DEFINE_FCN0 (char *, _nm_auto_free_secret, nm_free_secret) /** @@ -159,4 +152,125 @@ GBytes *nm_secret_buf_to_gbytes_take (NMSecretBuf *secret, gssize actual_len); gboolean nm_utils_memeqzero_secret (gconstpointer data, gsize length); +/*****************************************************************************/ + +/** + * nm_secret_mem_realloc: + * @m_old: the current buffer of length @cur_len. + * @do_bzero_mem: if %TRUE, bzero the old buffer + * @cur_len: the current buffer length of @m_old. It is necessary for bzero. + * @new_len: the desired new length + * + * If @do_bzero_mem is false, this is like g_realloc(). + * Otherwise, this will allocate a new buffer of the desired size, copy over the + * old data, and bzero the old buffer before freeing it. As such, it also behaves + * similar to g_realloc(), with the overhead of nm_explicit_bzero() and using + * malloc/free intead of realloc(). + * + * Returns: the new allocated buffer. Think of it behaving like g_realloc(). + */ +static inline gpointer +nm_secret_mem_realloc (gpointer m_old, gboolean do_bzero_mem, gsize cur_len, gsize new_len) +{ + gpointer m_new; + + nm_assert (m_old || cur_len == 0); + + if ( do_bzero_mem + && G_LIKELY (cur_len > 0)) { + m_new = g_malloc (new_len); + if (G_LIKELY (new_len > 0)) + memcpy (m_new, m_old, NM_MIN (cur_len, new_len)); + nm_explicit_bzero (m_old, cur_len); + g_free (m_old); + } else + m_new = g_realloc (m_old, new_len); + + return m_new; +} + +/** + * nm_secret_mem_try_realloc: + * @m_old: the current buffer of length @cur_len. + * @do_bzero_mem: if %TRUE, bzero the old buffer + * @cur_len: the current buffer length of @m_old. It is necessary for bzero. + * @new_len: the desired new length + * + * If @do_bzero_mem is false, this is like g_try_realloc(). + * Otherwise, this will try to allocate a new buffer of the desired size, copy over the + * old data, and bzero the old buffer before freeing it. As such, it also behaves + * similar to g_try_realloc(), with the overhead of nm_explicit_bzero() and using + * malloc/free intead of realloc(). + * + * Returns: the new allocated buffer or NULL. Think of it behaving like g_try_realloc(). + */ +static inline gpointer +nm_secret_mem_try_realloc (gpointer m_old, gboolean do_bzero_mem, gsize cur_len, gsize new_len) +{ + gpointer m_new; + + nm_assert (m_old || cur_len == 0); + + if ( do_bzero_mem + && G_LIKELY (cur_len > 0)) { + if (G_UNLIKELY (new_len == 0)) + m_new = NULL; + else { + m_new = g_try_malloc (new_len); + if (!m_new) + return NULL; + memcpy (m_new, m_old, NM_MIN (cur_len, new_len)); + } + nm_explicit_bzero (m_old, cur_len); + g_free (m_old); + return m_new; + } + + return g_try_realloc (m_old, new_len); +} + +/** + * nm_secret_mem_try_realloc_take: + * @m_old: the current buffer of length @cur_len. + * @do_bzero_mem: if %TRUE, bzero the old buffer + * @cur_len: the current buffer length of @m_old. It is necessary for bzero. + * @new_len: the desired new length + * + * This works like nm_secret_mem_try_realloc(), which is not unlike g_try_realloc(). + * The difference is, if we fail to allocate a new buffer, then @m_old will be + * freed (and possibly cleared). This differs from plain realloc(), where the + * old buffer is unchanged if the operation fails. + * + * Returns: the new allocated buffer or NULL. Think of it behaving like g_try_realloc() + * but it will always free @m_old. + */ +static inline gpointer +nm_secret_mem_try_realloc_take (gpointer m_old, gboolean do_bzero_mem, gsize cur_len, gsize new_len) +{ + gpointer m_new; + + nm_assert (m_old || cur_len == 0); + + if ( do_bzero_mem + && G_LIKELY (cur_len > 0)) { + if (G_UNLIKELY (new_len == 0)) + m_new = NULL; + else { + m_new = g_try_malloc (new_len); + if (G_LIKELY (m_new)) + memcpy (m_new, m_old, NM_MIN (cur_len, new_len)); + } + nm_explicit_bzero (m_old, cur_len); + g_free (m_old); + return m_new; + } + + m_new = g_try_realloc (m_old, new_len); + if (G_UNLIKELY (!m_new && new_len > 0)) + g_free (m_old); + return m_new; +} + +/*****************************************************************************/ + #endif /* __NM_SECRET_UTILS_H__ */ diff --git a/shared/nm-glib-aux/nm-shared-utils.c b/shared/nm-glib-aux/nm-shared-utils.c index d47c465c..c06399dd 100644 --- a/shared/nm-glib-aux/nm-shared-utils.c +++ b/shared/nm-glib-aux/nm-shared-utils.c @@ -15,6 +15,7 @@ #include <net/if.h> #include "nm-errno.h" +#include "nm-str-buf.h" /*****************************************************************************/ @@ -84,6 +85,75 @@ nm_ip_addr_set_from_untrusted (int addr_family, /*****************************************************************************/ +gsize +nm_utils_get_next_realloc_size (gboolean true_realloc, gsize requested) +{ + gsize n, x; + + /* https://doc.qt.io/qt-5/containers.html#growth-strategies */ + + if (requested <= 40) { + /* small allocations. Increase in small steps of 8 bytes. + * + * We get thus sizes of 8, 16, 32, 40. */ + if (requested <= 8) + return 8; + if (requested <= 16) + return 16; + if (requested <= 32) + return 32; + + /* The return values for < 104 are essentially hard-coded, and the choice here is + * made without very strong reasons. + * + * We want to stay 24 bytes below the power-of-two border 64. Hence, return 40 here. + * However, the next step then is already 104 (128 - 24). It's a larger gap than in + * the steps before. + * + * It's not clear whether some of the steps should be adjusted (or how exactly). */ + return 40; + } + + if ( requested <= 0x2000u - 24u + || G_UNLIKELY (!true_realloc)) { + /* mid sized allocations. Return next power of two, minus 24 bytes extra space + * at the beginning. + * That means, we double the size as we grow. + * + * With !true_realloc, it means that the caller does not intend to call + * realloc() but instead clone the buffer. This is for example the case, when we + * want to nm_explicit_bzero() the old buffer. In that case we really want to grow + * the buffer exponentially every time and not increment in page sizes of 4K (below). + * + * We get thus sizes of 104, 232, 488, 1000, 2024, 4072, 8168... */ + + if (G_UNLIKELY (requested > G_MAXSIZE / 2u - 24u)) + return G_MAXSIZE; + + x = requested + 24u; + n = 128u; + while (n < x) { + n <<= 1; + nm_assert (n > 128u); + } + + nm_assert (n > 24u && n - 24u >= requested); + return n - 24u; + } + + if (G_UNLIKELY (requested > G_MAXSIZE - 0x1000u - 24u)) + return G_MAXSIZE; + + /* For large allocations (with !true_realloc) we allocate memory in chunks of + * 4K (- 24 bytes extra), assuming that the memory gets mmapped and thus + * realloc() is efficient by just reordering pages. */ + n = ((requested + (0x0FFFu + 24u)) & ~((gsize) 0x0FFFu)) - 24u; + nm_assert (n >= requested); + return n; +} + +/*****************************************************************************/ + pid_t nm_utils_gettid (void) { @@ -374,6 +444,62 @@ nm_utils_gbytes_to_variant_ay (GBytes *bytes) /*****************************************************************************/ +/* Convert a hash table with "char *" keys and values to an "a{ss}" GVariant. + * The keys will be sorted asciibetically. + * Returns a floating reference. + */ +GVariant * +nm_utils_strdict_to_variant_ass (GHashTable *strdict) +{ + GHashTableIter iter; + const char *key, *value; + GVariantBuilder builder; + guint i, len; + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}")); + + if (!strdict) + goto out; + len = g_hash_table_size (strdict); + if (!len) + goto out; + + g_hash_table_iter_init (&iter, strdict); + if (!g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value)) + nm_assert_not_reached (); + + if (len == 1) + g_variant_builder_add (&builder, "{ss}", key, value); + else { + gs_free NMUtilsNamedValue *idx_free = NULL; + NMUtilsNamedValue *idx; + + if (len > 300 / sizeof (NMUtilsNamedValue)) { + idx_free = g_new (NMUtilsNamedValue, len); + idx = idx_free; + } else + idx = g_alloca (sizeof (NMUtilsNamedValue) * len); + + i = 0; + do { + idx[i].name = key; + idx[i].value_str = value; + i++; + } while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value)); + nm_assert (i == len); + + nm_utils_named_value_list_sort (idx, len, NULL, NULL); + + for (i = 0; i < len; i++) + g_variant_builder_add (&builder, "{ss}", idx[i].name, idx[i].value_str); + } + +out: + return g_variant_builder_end (&builder); +} + +/*****************************************************************************/ + /** * nm_strquote: * @buf: the output buffer of where to write the quoted @str argument. @@ -685,7 +811,7 @@ nm_utils_ip_is_site_local (int addr_family, /*****************************************************************************/ static gboolean -_parse_legacy_addr4 (const char *text, in_addr_t *out_addr) +_parse_legacy_addr4 (const char *text, in_addr_t *out_addr, GError **error) { gs_free char *s_free = NULL; struct in_addr a1; @@ -693,8 +819,13 @@ _parse_legacy_addr4 (const char *text, in_addr_t *out_addr) char *s; int i; - if (inet_aton (text, &a1) != 1) + if (inet_aton (text, &a1) != 1) { + g_set_error_literal (error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_INVALID_ARGUMENT, + "address invalid according to inet_aton()"); return FALSE; + } /* OK, inet_aton() accepted the format. That's good, because we want * to accept IPv4 addresses in octal format, like 255.255.000.000. @@ -711,6 +842,10 @@ _parse_legacy_addr4 (const char *text, in_addr_t *out_addr) if (NM_STRCHAR_ANY (text, ch, ( !(ch >= '0' && ch <= '9') && !NM_IN_SET (ch, '.', 'x')))) { /* We only accepts '.', digits, and 'x' for "0x". */ + g_set_error_literal (error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_INVALID_ARGUMENT, + "contains an invalid character"); return FALSE; } @@ -729,13 +864,27 @@ _parse_legacy_addr4 (const char *text, in_addr_t *out_addr) if ((i == G_N_ELEMENTS (bin) - 1) != (s == NULL)) { /* Exactly for the last digit, we expect to have no more following token. * But this isn't the case. Abort. */ + g_set_error (error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_INVALID_ARGUMENT, + "wrong number of tokens (index %d, token '%s')", + i, s); return FALSE; } v = _nm_utils_ascii_str_to_int64 (current_token, 0, 0, 0xFF, -1); if (v == -1) { + int errsv = errno; + /* we do accept octal and hex (even with leading "0x"). But something * about this token is wrong. */ + g_set_error (error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_INVALID_ARGUMENT, + "invalid token '%s': %s (%d)", + current_token, + nm_strerror_native (errsv), + errsv); return FALSE; } @@ -745,6 +894,12 @@ _parse_legacy_addr4 (const char *text, in_addr_t *out_addr) if (memcmp (bin, &a1, sizeof (bin)) != 0) { /* our parsing did not agree with what inet_aton() gave. Something * is wrong. Abort. */ + g_set_error (error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_INVALID_ARGUMENT, + "inet_aton() result 0x%08x differs from computed value 0x%02hhx%02hhx%02hhx%02hhx", + a1.s_addr, + bin[0], bin[1], bin[2], bin[3]); return FALSE; } @@ -772,7 +927,7 @@ nm_utils_parse_inaddr_bin_full (int addr_family, if (inet_pton (addr_family, text, &addrbin) != 1) { if ( accept_legacy && addr_family == AF_INET - && _parse_legacy_addr4 (text, &addrbin.addr4)) { + && _parse_legacy_addr4 (text, &addrbin.addr4, NULL)) { /* The address is in some legacy format which inet_aton() accepts, but not inet_pton(). * Most likely octal digits (leading zeros). We accept the address. */ } else @@ -781,11 +936,17 @@ nm_utils_parse_inaddr_bin_full (int addr_family, #if NM_MORE_ASSERTS > 10 if (addr_family == AF_INET) { + gs_free_error GError *error = NULL; in_addr_t a; /* The legacy parser should accept everything that inet_pton() accepts too. Meaning, * it should strictly parse *more* formats. And of course, parse it the same way. */ - nm_assert (_parse_legacy_addr4 (text, &a)); + if (!_parse_legacy_addr4 (text, &a, &error)) { + char buf[INET_ADDRSTRLEN]; + + g_error ("unexpected assertion failure: could parse \"%s\" as %s, but not accepted by legacy parser: %s", + text, _nm_utils_inet4_ntop (addrbin.addr4, buf), error->message); + } nm_assert (addrbin.addr4 == a); } #endif @@ -883,6 +1044,195 @@ nm_utils_parse_inaddr_prefix (int addr_family, /*****************************************************************************/ +gboolean +nm_utils_ipaddr_is_valid (int addr_family, + const char *str_addr) +{ + nm_assert (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET, AF_INET6)); + + return str_addr + && nm_utils_parse_inaddr_bin (addr_family, + str_addr, + NULL, + NULL); +} + +gboolean +nm_utils_ipaddr_is_normalized (int addr_family, + const char *str_addr) +{ + NMIPAddr addr; + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + nm_assert (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET, AF_INET6)); + + if (!str_addr) + return FALSE; + + if (!nm_utils_parse_inaddr_bin (addr_family, + str_addr, + &addr_family, + &addr)) + return FALSE; + + nm_utils_inet_ntop (addr_family, &addr, sbuf); + return nm_streq (sbuf, str_addr); +} + +/*****************************************************************************/ + +/** + * nm_g_ascii_strtoll() + * @nptr: the string to parse + * @endptr: the pointer on the first invalid chars + * @base: the base. + * + * This wraps g_ascii_strtoll() and should in almost all cases behave identical + * to it. + * + * However, it seems there are situations where g_ascii_strtoll() might set + * errno to some unexpected value EAGAIN. Possibly this is related to creating + * the C locale during + * + * #ifdef USE_XLOCALE + * return strtoll_l (nptr, endptr, base, get_C_locale ()); + * + * This wrapper tries to workaround that condition. + */ +gint64 +nm_g_ascii_strtoll (const char *nptr, + char **endptr, + guint base) +{ + int try_count = 2; + gint64 v; + const int errsv_orig = errno; + int errsv; + + nm_assert (nptr); + nm_assert (base == 0u || (base >= 2u && base <= 36u)); + +again: + errno = 0; + v = g_ascii_strtoll (nptr, endptr, base); + errsv = errno; + + if (errsv == 0) { + if (errsv_orig != 0) + errno = errsv_orig; + return v; + } + + if ( errsv == ERANGE + && NM_IN_SET (v, G_MININT64, G_MAXINT64)) + return v; + + if ( errsv == EINVAL + && v == 0 + && nptr + && nptr[0] == '\0') + return v; + + if (try_count-- > 0) + goto again; + +#if NM_MORE_ASSERTS + g_critical ("g_ascii_strtoll() for \"%s\" failed with errno=%d (%s) and v=%"G_GINT64_FORMAT, + nptr, + errsv, + nm_strerror_native (errsv), + v); +#endif + + return v; +} + +/* See nm_g_ascii_strtoll() */ +guint64 +nm_g_ascii_strtoull (const char *nptr, + char **endptr, + guint base) +{ + int try_count = 2; + guint64 v; + const int errsv_orig = errno; + int errsv; + + nm_assert (nptr); + nm_assert (base == 0u || (base >= 2u && base <= 36u)); + +again: + errno = 0; + v = g_ascii_strtoull (nptr, endptr, base); + errsv = errno; + + if (errsv == 0) { + if (errsv_orig != 0) + errno = errsv_orig; + return v; + } + + if ( errsv == ERANGE + && NM_IN_SET (v, G_MAXUINT64)) + return v; + + if ( errsv == EINVAL + && v == 0 + && nptr + && nptr[0] == '\0') + return v; + + if (try_count-- > 0) + goto again; + +#if NM_MORE_ASSERTS + g_critical ("g_ascii_strtoull() for \"%s\" failed with errno=%d (%s) and v=%"G_GUINT64_FORMAT, + nptr, + errsv, + nm_strerror_native (errsv), + v); +#endif + + return v; +} + +/* see nm_g_ascii_strtoll(). */ +double +nm_g_ascii_strtod (const char *nptr, + char **endptr) +{ + int try_count = 2; + double v; + int errsv; + + nm_assert (nptr); + +again: + v = g_ascii_strtod (nptr, endptr); + errsv = errno; + + if (errsv == 0) + return v; + + if (errsv == ERANGE) + return v; + + if (try_count-- > 0) + goto again; + +#if NM_MORE_ASSERTS + g_critical ("g_ascii_strtod() for \"%s\" failed with errno=%d (%s) and v=%f", + nptr, + errsv, + nm_strerror_native (errsv), + v); +#endif + + /* Not really much else to do. Return the parsed value and leave errno set + * to the unexpected value. */ + return v; +} + /* _nm_utils_ascii_str_to_int64: * * A wrapper for g_ascii_strtoll, that checks whether the whole string @@ -910,7 +1260,7 @@ _nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 ma } errno = 0; - v = g_ascii_strtoll (str, (char **) &s, base); + v = nm_g_ascii_strtoll (str, (char **) &s, base); if (errno != 0) return fallback; @@ -946,7 +1296,7 @@ _nm_utils_ascii_str_to_uint64 (const char *str, guint base, guint64 min, guint64 } errno = 0; - v = g_ascii_strtoull (str, (char **) &s, base); + v = nm_g_ascii_strtoull (str, (char **) &s, base); if (errno != 0) return fallback; @@ -965,8 +1315,8 @@ _nm_utils_ascii_str_to_uint64 (const char *str, guint base, guint64 min, guint64 if ( v != 0 && str[0] == '-') { - /* I don't know why, but g_ascii_strtoull() accepts minus signs ("-2" gives 18446744073709551614). - * For "-0" that is OK, but otherwise not. */ + /* As documented, g_ascii_strtoull() accepts negative values, and returns their + * absolute value. We don't. */ errno = ERANGE; return fallback; } @@ -1147,12 +1497,27 @@ comp_l: /*****************************************************************************/ static void +_char_lookup_table_set_one (guint8 lookup[static 256], + char ch) +{ + lookup[(guint8) ch] = 1; +} + +static void +_char_lookup_table_set_all (guint8 lookup[static 256], + const char *candidates) +{ + while (candidates[0] != '\0') + _char_lookup_table_set_one (lookup, (candidates++)[0]); +} + +static void _char_lookup_table_init (guint8 lookup[static 256], const char *candidates) { memset (lookup, 0, 256); - while (candidates[0] != '\0') - lookup[(guint8) ((candidates++)[0])] = 1; + if (candidates) + _char_lookup_table_set_all (lookup, candidates); } static gboolean @@ -1163,6 +1528,19 @@ _char_lookup_has (const guint8 lookup[static 256], return lookup[(guint8) ch] != 0; } +static gboolean +_char_lookup_has_all (const guint8 lookup[static 256], + const char *candidates) +{ + if (candidates) { + while (candidates[0] != '\0') { + if (!_char_lookup_has (lookup, (candidates++)[0])) + return FALSE; + } + } + return TRUE; +} + /** * nm_utils_strsplit_set_full: * @str: the string to split. @@ -1175,18 +1553,7 @@ _char_lookup_has (const guint8 lookup[static 256], * * Note that for @str %NULL and "", this always returns %NULL too. That differs * from g_strsplit_set(), which would return an empty strv array for "". - * - * Note that g_strsplit_set() returns empty words as well. By default, - * nm_utils_strsplit_set_full() strips all empty tokens (that is, repeated - * delimiters. With %NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, empty tokens - * are not removed. - * - * If @flags has %NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING, delimiters prefixed - * by a backslash are not treated as a separator. Such delimiters and their escape - * character are copied to the current word without unescaping them. In general, - * nm_utils_strsplit_set_full() does not remove any backslash escape characters - * and does not unescaping. It only considers them for skipping to split at - * an escaped delimiter. + * This never returns an empty array. * * Returns: %NULL if @str is %NULL or "". * If @str only contains delimiters and %NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY @@ -1196,7 +1563,7 @@ _char_lookup_has (const guint8 lookup[static 256], * The strings to which the result strv array points to are allocated * after the returned result itself. Don't free the strings themself, * but free everything with g_free(). - * It is however safe and allowed to modify the indiviual strings, + * It is however safe and allowed to modify the individual strings in-place, * like "g_strstrip((char *) iter[0])". */ const char ** @@ -1378,11 +1745,9 @@ done2: /* We no longer need ch_lookup for its original purpose. Modify it, so it * can detect the delimiters, '\\', and (optionally) whitespaces. */ - ch_lookup[((guint8) '\\')] = 1; - if (f_strstrip) { - for (i = 0; NM_ASCII_SPACES[i]; i++) - ch_lookup[((guint8) (NM_ASCII_SPACES[i]))] = 1; - } + _char_lookup_table_set_one (ch_lookup, '\\'); + if (f_strstrip) + _char_lookup_table_set_all (ch_lookup, NM_ASCII_SPACES); for (i_token = 0; ptr[i_token]; i_token++) { s = (char *) ptr[i_token]; @@ -1403,65 +1768,131 @@ done2: /*****************************************************************************/ const char * -nm_utils_escaped_tokens_escape (const char *str, - const char *delimiters, - char **out_to_free) +nm_utils_escaped_tokens_escape_full (const char *str, + const char *delimiters, + const char *delimiters_as_needed, + NMUtilsEscapedTokensEscapeFlags flags, + char **out_to_free) { guint8 ch_lookup[256]; + guint8 ch_lookup_as_needed[256]; + gboolean has_ch_lookup_as_needed = FALSE; char *ret; gsize str_len; gsize alloc_len; gsize n_escapes; gsize i, j; + gboolean escape_leading_space; gboolean escape_trailing_space; + gboolean escape_backslash_as_needed; - if (!delimiters) { - nm_assert (delimiters); - delimiters = NM_ASCII_SPACES; - } + nm_assert ( !delimiters_as_needed + || ( delimiters_as_needed[0] + && NM_FLAGS_HAS (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED))); if (!str || str[0] == '\0') { *out_to_free = NULL; return str; } + str_len = strlen (str); + _char_lookup_table_init (ch_lookup, delimiters); + if ( !delimiters + || NM_FLAGS_HAS (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_SPACES)) { + flags &= ~( NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE + | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE); + _char_lookup_table_set_all (ch_lookup, NM_ASCII_SPACES); + } - /* also mark '\\' as requiring escaping. */ - ch_lookup[((guint8) '\\')] = 1; + if (NM_FLAGS_HAS (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_ALWAYS)) { + _char_lookup_table_set_one (ch_lookup, '\\'); + escape_backslash_as_needed = FALSE; + } else if (_char_lookup_has (ch_lookup, '\\')) + escape_backslash_as_needed = FALSE; + else { + escape_backslash_as_needed = NM_FLAGS_HAS (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED); + if (escape_backslash_as_needed) { + if ( NM_FLAGS_ANY (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE + | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE) + && !_char_lookup_has_all (ch_lookup, NM_ASCII_SPACES)) { + /* ESCAPE_LEADING_SPACE and ESCAPE_TRAILING_SPACE implies that we escape backslash + * before whitespaces. */ + if (!has_ch_lookup_as_needed) { + has_ch_lookup_as_needed = TRUE; + _char_lookup_table_init (ch_lookup_as_needed, NULL); + } + _char_lookup_table_set_all (ch_lookup_as_needed, NM_ASCII_SPACES); + } + if ( delimiters_as_needed + && !_char_lookup_has_all (ch_lookup, delimiters_as_needed)) { + if (!has_ch_lookup_as_needed) { + has_ch_lookup_as_needed = TRUE; + _char_lookup_table_init (ch_lookup_as_needed, NULL); + } + _char_lookup_table_set_all (ch_lookup_as_needed, delimiters_as_needed); + } + } + } + + escape_leading_space = NM_FLAGS_HAS (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE) + && g_ascii_isspace (str[0]) + && !_char_lookup_has (ch_lookup, str[0]); + if (str_len == 1) + escape_trailing_space = FALSE; + else { + escape_trailing_space = NM_FLAGS_HAS (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE) + && g_ascii_isspace (str[str_len - 1]) + && !_char_lookup_has (ch_lookup, str[str_len - 1]); + } n_escapes = 0; for (i = 0; str[i] != '\0'; i++) { if (_char_lookup_has (ch_lookup, str[i])) n_escapes++; + else if ( str[i] == '\\' + && escape_backslash_as_needed + && ( _char_lookup_has (ch_lookup, str[i + 1]) + || NM_IN_SET (str[i + 1], '\0', '\\') + || ( has_ch_lookup_as_needed + && _char_lookup_has (ch_lookup_as_needed, str[i + 1])))) + n_escapes++; } + if (escape_leading_space) + n_escapes++; + if (escape_trailing_space) + n_escapes++; - str_len = i; - nm_assert (str_len > 0 && strlen (str) == str_len); - - escape_trailing_space = !_char_lookup_has (ch_lookup, str[str_len - 1]) - && g_ascii_isspace (str[str_len - 1]); - - if ( n_escapes == 0 - && !escape_trailing_space) { + if (n_escapes == 0u) { *out_to_free = NULL; return str; } - alloc_len = str_len + n_escapes + ((gsize) escape_trailing_space) + 1; + alloc_len = str_len + n_escapes + 1u; ret = g_new (char, alloc_len); j = 0; - for (i = 0; str[i] != '\0'; i++) { - if (_char_lookup_has (ch_lookup, str[i])) { - nm_assert (j < alloc_len); + i = 0; + + if (escape_leading_space) { + ret[j++] = '\\'; + ret[j++] = str[i++]; + } + for (; str[i] != '\0'; i++) { + if (_char_lookup_has (ch_lookup, str[i])) + ret[j++] = '\\'; + else if ( str[i] == '\\' + && escape_backslash_as_needed + && ( _char_lookup_has (ch_lookup, str[i + 1]) + || NM_IN_SET (str[i + 1], '\0', '\\') + || ( has_ch_lookup_as_needed + && _char_lookup_has (ch_lookup_as_needed, str[i + 1])))) ret[j++] = '\\'; - } - nm_assert (j < alloc_len); ret[j++] = str[i]; } if (escape_trailing_space) { - nm_assert (!_char_lookup_has (ch_lookup, ret[j - 1]) && g_ascii_isspace (ret[j - 1])); + nm_assert ( !_char_lookup_has (ch_lookup, ret[j - 1]) + && g_ascii_isspace (ret[j - 1])); ret[j] = ret[j - 1]; ret[j - 1] = '\\'; j++; @@ -1469,11 +1900,97 @@ nm_utils_escaped_tokens_escape (const char *str, nm_assert (j == alloc_len - 1); ret[j] = '\0'; + nm_assert (strlen (ret) == j); *out_to_free = ret; return ret; } +/** + * nm_utils_escaped_tokens_options_split: + * @str: the src string. This string will be modified in-place. + * The output values will point into @str. + * @out_key: (allow-none): the returned output key. This will always be set to @str + * itself. @str will be modified to contain only the unescaped, truncated + * key name. + * @out_val: returns the parsed (and unescaped) value or %NULL, if @str contains + * no '=' delimiter. + * + * Honors backslash escaping to parse @str as "key=value" pairs. Optionally, if no '=' + * is present, @out_val will be returned as %NULL. Backslash can be used to escape + * '=', ',', '\\', and ascii whitespace. Other backslash sequences are taken verbatim. + * + * For keys, '=' obviously must be escaped. For values, that is optional because an + * unescaped '=' is just taken verbatim. For example, in a key, the sequence "\\=" + * must be escaped as "\\\\\\=". For the value, that works too, but "\\\\=" is also + * accepted. + * + * Unescaped Space around the key and value are also removed. Space in general must + * not be escaped, unless they are at the beginning or the end of key/value. + */ +void +nm_utils_escaped_tokens_options_split (char *str, + const char **out_key, + const char **out_val) +{ + const char *val = NULL; + gsize i; + gsize j; + gsize last_space_idx; + gboolean last_space_has; + + nm_assert (str); + + i = 0; + while (g_ascii_isspace (str[i])) + i++; + + j = 0; + last_space_idx = 0; + last_space_has = FALSE; + while (str[i] != '\0') { + if (g_ascii_isspace (str[i])) { + if (!last_space_has) { + last_space_has = TRUE; + last_space_idx = j; + } + } else { + if (str[i] == '\\') { + if ( NM_IN_SET (str[i + 1u], '\\', ',', '=') + || g_ascii_isspace (str[i + 1u])) + i++; + } else if (str[i] == '=') { + /* Encounter an unescaped '=' character. When we still parse the key, this + * is the separator we were waiting for. If we are parsing the value, + * we take the character verbatim. */ + if (!val) { + if (last_space_has) { + str[last_space_idx] = '\0'; + j = last_space_idx + 1; + last_space_has = FALSE; + } else + str[j++] = '\0'; + val = &str[j]; + i++; + while (g_ascii_isspace (str[i])) + i++; + continue; + } + } + last_space_has = FALSE; + } + str[j++] = str[i++]; + } + + if (last_space_has) + str[last_space_idx] = '\0'; + else + str[j] = '\0'; + + *out_key = str; + *out_val = val; +} + /*****************************************************************************/ /** @@ -1603,16 +2120,13 @@ nm_utils_error_set_cancelled (GError **error, } gboolean -nm_utils_error_is_cancelled (GError *error, - gboolean consider_is_disposing) +nm_utils_error_is_cancelled_or_disposing (GError *error) { if (error) { if (error->domain == G_IO_ERROR) return NM_IN_SET (error->code, G_IO_ERROR_CANCELLED); - if (consider_is_disposing) { - if (error->domain == NM_UTILS_ERROR) - return NM_IN_SET (error->code, NM_UTILS_ERROR_CANCELLED_DISPOSING); - } + if (error->domain == NM_UTILS_ERROR) + return NM_IN_SET (error->code, NM_UTILS_ERROR_CANCELLED_DISPOSING); } return FALSE; } @@ -1913,18 +2427,20 @@ nm_g_type_find_implementing_class_for_property (GType gtype, /*****************************************************************************/ static void -_str_append_escape (GString *s, char ch) +_str_buf_append_c_escape_octal (NMStrBuf *strbuf, + char ch) { - g_string_append_c (s, '\\'); - g_string_append_c (s, '0' + ((((guchar) ch) >> 6) & 07)); - g_string_append_c (s, '0' + ((((guchar) ch) >> 3) & 07)); - g_string_append_c (s, '0' + ( ((guchar) ch) & 07)); + nm_str_buf_append_c4 (strbuf, + '\\', + '0' + ((char) ((((guchar) ch) >> 6) & 07)), + '0' + ((char) ((((guchar) ch) >> 3) & 07)), + '0' + ((char) ((((guchar) ch) ) & 07))); } gconstpointer nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_free) { - GString *gstr; + NMStrBuf strbuf; gsize len; const char *s; @@ -1946,9 +2462,9 @@ nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_fr return str; } - gstr = g_string_new_len (NULL, len); + nm_str_buf_init (&strbuf, len, FALSE); - g_string_append_len (gstr, str, s - str); + nm_str_buf_append_len (&strbuf, str, s - str); str = s; for (;;) { @@ -1971,6 +2487,9 @@ nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_fr v = v * 8 + (ch - '0'); ch = (++str)[0]; if (ch >= '0' && ch <= '7') { + /* technically, escape sequences larger than \3FF are out of range + * and invalid. We don't check for that, and do the same as + * g_strcompress(): silently clip the value with & 0xFF. */ v = v * 8 + (ch - '0'); ++str; } @@ -1992,21 +2511,20 @@ nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_fr str++; } - g_string_append_c (gstr, ch); + nm_str_buf_append_c (&strbuf, ch); s = strchr (str, '\\'); if (!s) { - g_string_append (gstr, str); + nm_str_buf_append (&strbuf, str); break; } - g_string_append_len (gstr, str, s - str); + nm_str_buf_append_len (&strbuf, str, s - str); str = s; } - *out_len = gstr->len; - *to_free = gstr->str; - return g_string_free (gstr, FALSE); + return (*to_free = nm_str_buf_finalize (&strbuf, + out_len)); } /** @@ -2047,7 +2565,7 @@ nm_utils_buf_utf8safe_escape (gconstpointer buf, gssize buflen, NMUtilsStrUtf8Sa const char *p = NULL; const char *s; gboolean nul_terminated = FALSE; - GString *gstr; + NMStrBuf strbuf; g_return_val_if_fail (to_free, NULL); @@ -2078,7 +2596,9 @@ nm_utils_buf_utf8safe_escape (gconstpointer buf, gssize buflen, NMUtilsStrUtf8Sa return str; } - gstr = g_string_sized_new (buflen + 5); + nm_str_buf_init (&strbuf, + buflen + 5, + NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET)); s = str; do { @@ -2088,21 +2608,22 @@ nm_utils_buf_utf8safe_escape (gconstpointer buf, gssize buflen, NMUtilsStrUtf8Sa for (; s < p; s++) { char ch = s[0]; + nm_assert (ch); if (ch == '\\') - g_string_append (gstr, "\\\\"); + nm_str_buf_append_c2 (&strbuf, '\\', '\\'); else if ( ( NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL) \ && ch < ' ') \ || ( NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII) \ && ((guchar) ch) >= 127)) - _str_append_escape (gstr, ch); + _str_buf_append_c_escape_octal (&strbuf, ch); else - g_string_append_c (gstr, ch); + nm_str_buf_append_c (&strbuf, ch); } if (buflen <= 0) break; - _str_append_escape (gstr, p[0]); + _str_buf_append_c_escape_octal (&strbuf, p[0]); buflen--; if (buflen == 0) @@ -2112,8 +2633,7 @@ nm_utils_buf_utf8safe_escape (gconstpointer buf, gssize buflen, NMUtilsStrUtf8Sa (void) g_utf8_validate (s, buflen, &p); } while (TRUE); - *to_free = g_string_free (gstr, FALSE); - return *to_free; + return (*to_free = nm_str_buf_finalize (&strbuf, NULL)); } const char * @@ -2137,13 +2657,11 @@ nm_utils_buf_utf8safe_escape_bytes (GBytes *bytes, NMUtilsStrUtf8SafeFlags flags const char * nm_utils_str_utf8safe_unescape (const char *str, char **to_free) { + gsize len; + g_return_val_if_fail (to_free, NULL); - if (!str || !strchr (str, '\\')) { - *to_free = NULL; - return str; - } - return (*to_free = g_strcompress (str)); + return nm_utils_buf_utf8safe_unescape (str, &len, (gpointer *) to_free); } /** @@ -2203,7 +2721,10 @@ nm_utils_str_utf8safe_escape_cp (const char *str, NMUtilsStrUtf8SafeFlags flags) char * nm_utils_str_utf8safe_unescape_cp (const char *str) { - return str ? g_strcompress (str) : NULL; + char *s; + + str = nm_utils_str_utf8safe_unescape (str, &s); + return s ?: g_strdup (str); } char * @@ -2224,7 +2745,7 @@ nm_utils_str_utf8safe_escape_take (char *str, NMUtilsStrUtf8SafeFlags flags) /* taken from systemd's fd_wait_for_event(). Note that the timeout * is here in nano-seconds, not micro-seconds. */ int -nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_ns) +nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_nsec) { struct pollfd pollfd = { .fd = fd, @@ -2233,11 +2754,11 @@ nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_ns) struct timespec ts, *pts; int r; - if (timeout_ns < 0) + if (timeout_nsec < 0) pts = NULL; else { - ts.tv_sec = (time_t) (timeout_ns / NM_UTILS_NS_PER_SECOND); - ts.tv_nsec = (long int) (timeout_ns % NM_UTILS_NS_PER_SECOND); + ts.tv_sec = (time_t) (timeout_nsec / NM_UTILS_NSEC_PER_SEC); + ts.tv_nsec = (long int) (timeout_nsec % NM_UTILS_NSEC_PER_SEC); pts = &ts; } @@ -2319,7 +2840,10 @@ nm_utils_fd_read_loop_exact (int fd, void *buf, size_t nbytes, bool do_poll) /*****************************************************************************/ NMUtilsNamedValue * -nm_utils_named_values_from_str_dict (GHashTable *hash, guint *out_len) +nm_utils_named_values_from_str_dict_with_sort (GHashTable *hash, + guint *out_len, + GCompareDataFunc compare_func, + gpointer user_data) { GHashTableIter iter; NMUtilsNamedValue *values; @@ -2342,7 +2866,8 @@ nm_utils_named_values_from_str_dict (GHashTable *hash, guint *out_len) values[i].name = NULL; values[i].value_ptr = NULL; - nm_utils_named_value_list_sort (values, len, NULL, NULL); + if (compare_func) + nm_utils_named_value_list_sort (values, len, compare_func, user_data); NM_SET_OUT (out_len, len); return values; @@ -3593,7 +4118,7 @@ nm_g_idle_source_new (int priority, } GSource * -nm_g_timeout_source_new (guint timeout_ms, +nm_g_timeout_source_new (guint timeout_msec, int priority, GSourceFunc func, gpointer user_data, @@ -3601,7 +4126,7 @@ nm_g_timeout_source_new (guint timeout_ms, { GSource *source; - source = g_timeout_source_new (timeout_ms); + source = g_timeout_source_new (timeout_msec); if (priority != G_PRIORITY_DEFAULT) g_source_set_priority (source, priority); g_source_set_callback (source, func, user_data, destroy_notify); @@ -3625,6 +4150,26 @@ nm_g_unix_signal_source_new (int signum, return source; } +GSource * +nm_g_unix_fd_source_new (int fd, + GIOCondition io_condition, + int priority, + gboolean (*source_func) (int fd, + GIOCondition condition, + gpointer user_data), + gpointer user_data, + GDestroyNotify destroy_notify) +{ + GSource *source; + + source = g_unix_fd_source_new (fd, io_condition); + + if (priority != G_PRIORITY_DEFAULT) + g_source_set_priority (source, priority); + g_source_set_callback (source, G_SOURCE_FUNC (source_func), user_data, destroy_notify); + return source; +} + /*****************************************************************************/ #define _CTX_LOG(fmt, ...) \ @@ -4038,8 +4583,47 @@ nm_utils_ifname_valid_kernel (const char *name, GError **error) return FALSE; } +/*****************************************************************************/ + +static gboolean +_nm_utils_ifname_valid_kernel (const char *name, GError **error) +{ + if (!nm_utils_ifname_valid_kernel (name, error)) + return FALSE; + + if (strchr (name, '%')) { + /* Kernel's dev_valid_name() accepts (almost) any binary up to 15 chars. + * However, '%' is treated special as a format specifier. Try + * + * ip link add 'dummy%dx' type dummy + * + * Don't allow that for "connection.interface-name", which either + * matches an existing netdev name (thus, it cannot have a '%') or + * is used to configure a name (in which case we don't want kernel + * to replace the format specifier). */ + g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, + _("'%%' is not allowed in interface names")); + return FALSE; + } + + if (NM_IN_STRSET (name, "all", + "default", + "bonding_masters")) { + /* Certain names are not allowed. The "all" and "default" names are reserved + * due to their directories in "/proc/sys/net/ipv4/conf/" and "/proc/sys/net/ipv6/conf/". + * + * Also, there is "/sys/class/net/bonding_masters" file. + */ + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("'%s' is not allowed as interface name"), name); + return FALSE; + } + + return TRUE; +} + static gboolean -_nm_utils_ifname_valid_ovs (const char* name, GError **error) +_nm_utils_ifname_valid_ovs (const char *name, GError **error) { const char *ch; @@ -4081,10 +4665,90 @@ nm_utils_ifname_valid (const char* name, switch (type) { case NMU_IFACE_KERNEL: - return nm_utils_ifname_valid_kernel (name, error); + return _nm_utils_ifname_valid_kernel (name, error); case NMU_IFACE_OVS: return _nm_utils_ifname_valid_ovs (name, error); + case NMU_IFACE_OVS_AND_KERNEL: + return _nm_utils_ifname_valid_kernel (name, error) + && _nm_utils_ifname_valid_ovs (name, error); + case NMU_IFACE_ANY: { + gs_free_error GError *local = NULL; + + if (_nm_utils_ifname_valid_kernel (name, error ? &local : NULL)) + return TRUE; + if (_nm_utils_ifname_valid_ovs (name, NULL)) + return TRUE; + if (error) + g_propagate_error (error, g_steal_pointer (&local)); + return FALSE; + } } g_return_val_if_reached (FALSE); } + +/*****************************************************************************/ + +void +_nm_str_buf_ensure_size (NMStrBuf *strbuf, + gsize new_size, + gboolean reserve_exact) +{ + _nm_str_buf_assert (strbuf); + + /* Currently this only supports strictly growing the buffer. */ + nm_assert (new_size > strbuf->_priv_allocated); + + if (!reserve_exact) { + new_size = nm_utils_get_next_realloc_size (!strbuf->_priv_do_bzero_mem, + new_size); + } + + strbuf->_priv_str = nm_secret_mem_realloc (strbuf->_priv_str, + strbuf->_priv_do_bzero_mem, + strbuf->_priv_allocated, + new_size); + strbuf->_priv_allocated = new_size; +} + +void +nm_str_buf_append_printf (NMStrBuf *strbuf, + const char *format, + ...) +{ + va_list args; + gsize available; + int l; + + _nm_str_buf_assert (strbuf); + + available = strbuf->_priv_allocated - strbuf->_priv_len; + + va_start (args, format); + l = g_vsnprintf (&strbuf->_priv_str[strbuf->_priv_len], + available, + format, + args); + va_end (args); + + nm_assert (l >= 0); + nm_assert (l < G_MAXINT); + + if ((gsize) l > available) { + gsize l2 = ((gsize) l) + 1u; + + nm_str_buf_maybe_expand (strbuf, l2, FALSE); + + va_start (args, format); + l = g_vsnprintf (&strbuf->_priv_str[strbuf->_priv_len], + l2, + format, + args); + va_end (args); + + nm_assert (l >= 0); + nm_assert ((gsize) l == l2 - 1u); + } + + strbuf->_priv_len += (gsize) l; +} diff --git a/shared/nm-glib-aux/nm-shared-utils.h b/shared/nm-glib-aux/nm-shared-utils.h index 740e61d0..537b402b 100644 --- a/shared/nm-glib-aux/nm-shared-utils.h +++ b/shared/nm-glib-aux/nm-shared-utils.h @@ -115,11 +115,31 @@ typedef struct { * However, as ethernet addresses fit in here nicely, use * it also for an ethernet MAC address. */ guint8 addr_eth[6 /*ETH_ALEN*/]; + + guint8 array[sizeof (struct in6_addr)]; }; } NMIPAddr; +#define NM_IP_ADDR_INIT { .array = { 0 } } + extern const NMIPAddr nm_ip_addr_zero; +static inline int +nm_ip_addr_cmp (int addr_family, gconstpointer a, gconstpointer b) +{ + nm_assert_addr_family (addr_family); + nm_assert (a); + nm_assert (b); + + return memcmp (a, b, nm_utils_addr_family_to_size (addr_family)); +} + +static inline gboolean +nm_ip_addr_equal (int addr_family, gconstpointer a, gconstpointer b) +{ + return nm_ip_addr_cmp (addr_family, a, b) == 0; +} + static inline gboolean nm_ip_addr_is_null (int addr_family, gconstpointer addr) { @@ -158,6 +178,72 @@ nm_ip4_addr_is_localhost (in_addr_t addr4) /*****************************************************************************/ +#define NM_UTILS_INET_ADDRSTRLEN INET6_ADDRSTRLEN + +static inline const char * +nm_utils_inet_ntop (int addr_family, gconstpointer addr, char *dst) +{ + const char *s; + + const char *inet_ntop (int af, + const void *src, + char *dst, + socklen_t size); + + nm_assert_addr_family (addr_family); + nm_assert (addr); + nm_assert (dst); + + s = inet_ntop (addr_family, + addr, + dst, + addr_family == AF_INET6 ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN); + nm_assert (s); + return s; +} + +static inline const char * +_nm_utils_inet4_ntop (in_addr_t addr, char dst[static INET_ADDRSTRLEN]) +{ + return nm_utils_inet_ntop (AF_INET, &addr, dst); +} + +static inline const char * +_nm_utils_inet6_ntop (const struct in6_addr *addr, char dst[static INET6_ADDRSTRLEN]) +{ + return nm_utils_inet_ntop (AF_INET6, addr, dst); +} + +static inline char * +nm_utils_inet_ntop_dup (int addr_family, gconstpointer addr) +{ + char buf[NM_UTILS_INET_ADDRSTRLEN]; + + return g_strdup (nm_utils_inet_ntop (addr_family, addr, buf)); +} + +static inline char * +nm_utils_inet4_ntop_dup (in_addr_t addr) +{ + return nm_utils_inet_ntop_dup (AF_INET, &addr); +} + +static inline char * +nm_utils_inet6_ntop_dup (const struct in6_addr *addr) +{ + return nm_utils_inet_ntop_dup (AF_INET6, addr); +} + +/*****************************************************************************/ + +gboolean nm_utils_ipaddr_is_valid (int addr_family, + const char *str_addr); + +gboolean nm_utils_ipaddr_is_normalized (int addr_family, + const char *str_addr); + +/*****************************************************************************/ + #define NM_CMP_RETURN(c) \ G_STMT_START { \ const int _cc = (c); \ @@ -307,6 +393,8 @@ gboolean nm_utils_gbytes_equal_mem (GBytes *bytes, GVariant *nm_utils_gbytes_to_variant_ay (GBytes *bytes); +GVariant *nm_utils_strdict_to_variant_ass (GHashTable *strdict); + /*****************************************************************************/ GVariant *nm_utils_gvariant_vardict_filter (GVariant *src, @@ -352,7 +440,25 @@ int nm_utils_dbus_path_cmp (const char *dbus_path_a, const char *dbus_path_b); typedef enum { NM_UTILS_STRSPLIT_SET_FLAGS_NONE = 0, + + /* by default, strsplit will coalesce consecutive delimiters and remove + * them from the result. If this flag is present, empty values are preserved + * and returned. + * + * When combined with %NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP, if a value gets + * empty after strstrip(), it also gets removed. */ NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY = (1u << 0), + + /* %NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING means that delimiters prefixed + * by a backslash are not treated as a separator. Such delimiters and their escape + * character are copied to the current word without unescaping them. In general, + * nm_utils_strsplit_set_full() does not remove any backslash escape characters + * and does no unescaping. It only considers them for skipping to split at + * an escaped delimiter. + * + * If this is combined with (or implied by %NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED), then + * the backslash escapes are removed from the result. + */ NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING = (1u << 1), /* If flag is set, does the same as g_strstrip() on the returned tokens. @@ -392,6 +498,7 @@ typedef enum { * need extra care, and then only if they proceed one of the relevant characters. */ NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED = (1u << 3), + } NMUtilsStrsplitSetFlags; const char **nm_utils_strsplit_set_full (const char *str, @@ -435,9 +542,65 @@ nm_utils_escaped_tokens_split (const char *str, | NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP); } -const char *nm_utils_escaped_tokens_escape (const char *str, - const char *delimiters, - char **out_to_free); +typedef enum { + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_NONE = 0, + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_SPACES = (1ull << 0), + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE = (1ull << 1), + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE = (1ull << 2), + + /* Backslash characters will be escaped as "\\\\" if they precede another + * character that makes it necessary. Such characters are: + * + * 1) before another '\\' backslash. + * 2) before any delimiter in @delimiters. + * 3) before any delimiter in @delimiters_as_needed. + * 4) before a white space, if ESCAPE_LEADING_SPACE or ESCAPE_TRAILING_SPACE is set. + * 5) before the end of the word + * + * Rule 4) is an extension. It's not immediately clear why with ESCAPE_LEADING_SPACE + * and ESCAPE_TRAILING_SPACE we want *all* backslashes before a white space escaped. + * The reason is, that we obviously want to use ESCAPE_LEADING_SPACE and ESCAPE_TRAILING_SPACE + * in cases, where we later parse the backslash escaped strings back, but allowing to strip + * unescaped white spaces. That means, we want that " a " gets escaped as "\\ a\\ ". + * On the other hand, we also want that " a\\ b " gets escaped as "\\ a\\\\ b\\ ", + * and not "\\ a\\ b\\ ". Because otherwise, the parser would need to treat "\\ " + * differently depending on whether the sequence is at the beginning, end or middle + * of the word. + * + * Rule 5) is also not immediately obvious. When used with ESCAPE_TRAILING_SPACE, + * we clearly want to allow that an escaped word can have arbitrary + * whitespace suffixes. That's why this mode exists. So we must escape "a\\" as + * "a\\\\", so that appending " " does not change the meaning. + * Also without ESCAPE_TRAILING_SPACE, we want in general that we can concatenate + * two escaped words without changing their meaning. If the words would be "a\\" + * and "," (with ',' being a delimiter), then the result must be "a\\\\" and "\\," + * so that the concatenated word ("a\\\\\\,") is still the same. If we would escape + * them instead as "a\\" + "\\,", then the concatenated word would be "a\\\\," and + * different. + * */ + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED = (1ull << 3), + + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_ALWAYS = (1ull << 4), +} NMUtilsEscapedTokensEscapeFlags; + +const char *nm_utils_escaped_tokens_escape_full (const char *str, + const char *delimiters, + const char *delimiters_as_needed, + NMUtilsEscapedTokensEscapeFlags flags, + char **out_to_free); + +static inline const char * +nm_utils_escaped_tokens_escape (const char *str, + const char *delimiters, + char **out_to_free) +{ + return nm_utils_escaped_tokens_escape_full (str, + delimiters, + NULL, + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_ALWAYS + | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE, + out_to_free); +} static inline GString * nm_utils_escaped_tokens_escape_gstr_assert (const char *str, @@ -489,6 +652,47 @@ nm_utils_escaped_tokens_escape_gstr (const char *str, /*****************************************************************************/ +static inline const char ** +nm_utils_escaped_tokens_options_split_list (const char *str) +{ + return nm_utils_strsplit_set_full (str, + ",", + NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP + | NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING); +} + +void nm_utils_escaped_tokens_options_split (char *str, + const char **out_key, + const char **out_val); + +static inline const char * +nm_utils_escaped_tokens_options_escape_key (const char *key, + char **out_to_free) +{ + return nm_utils_escaped_tokens_escape_full (key, + ",=", + NULL, + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED + | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE + | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE, + out_to_free); +} + +static inline const char * +nm_utils_escaped_tokens_options_escape_val (const char *val, + char **out_to_free) +{ + return nm_utils_escaped_tokens_escape_full (val, + ",", + "=", + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED + | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE + | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE, + out_to_free); +} + +/*****************************************************************************/ + #define NM_UTILS_CHECKSUM_LENGTH_MD5 16 #define NM_UTILS_CHECKSUM_LENGTH_SHA1 20 #define NM_UTILS_CHECKSUM_LENGTH_SHA256 32 @@ -571,6 +775,17 @@ gboolean nm_utils_parse_inaddr_prefix (int addr_family, char **out_addr, int *out_prefix); +gint64 nm_g_ascii_strtoll (const char *nptr, + char **endptr, + guint base); + +guint64 nm_g_ascii_strtoull (const char *nptr, + char **endptr, + guint base); + +double nm_g_ascii_strtod (const char *nptr, + char **endptr); + gint64 _nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 max, gint64 fallback); guint64 _nm_utils_ascii_str_to_uint64 (const char *str, guint base, guint64 min, guint64 max, guint64 fallback); @@ -593,8 +808,8 @@ typedef struct { #define NM_UTILS_FLAGS2STR(f, n) { .flag = f, .name = ""n, } -#define _NM_UTILS_FLAGS2STR_DEFINE(scope, fcn_name, flags_type, ...) \ -scope const char * \ +#define NM_UTILS_FLAGS2STR_DEFINE(fcn_name, flags_type, ...) \ +const char * \ fcn_name (flags_type flags, char *buf, gsize len) \ { \ static const NMUtilsFlags2StrDesc descs[] = { \ @@ -604,11 +819,6 @@ fcn_name (flags_type flags, char *buf, gsize len) \ return nm_utils_flags2str (descs, G_N_ELEMENTS (descs), flags, buf, len); \ }; -#define NM_UTILS_FLAGS2STR_DEFINE(fcn_name, flags_type, ...) \ - _NM_UTILS_FLAGS2STR_DEFINE (, fcn_name, flags_type, __VA_ARGS__) -#define NM_UTILS_FLAGS2STR_DEFINE_STATIC(fcn_name, flags_type, ...) \ - _NM_UTILS_FLAGS2STR_DEFINE (static, fcn_name, flags_type, __VA_ARGS__) - const char *nm_utils_flags2str (const NMUtilsFlags2StrDesc *descs, gsize n_descs, unsigned flags, @@ -620,8 +830,8 @@ const char *nm_utils_flags2str (const NMUtilsFlags2StrDesc *descs, #define NM_UTILS_ENUM2STR(v, n) (void) 0; case v: s = ""n""; break; (void) 0 #define NM_UTILS_ENUM2STR_IGNORE(v) (void) 0; case v: break; (void) 0 -#define _NM_UTILS_ENUM2STR_DEFINE(scope, fcn_name, lookup_type, int_fmt, ...) \ -scope const char * \ +#define NM_UTILS_ENUM2STR_DEFINE_FULL(fcn_name, lookup_type, int_fmt, ...) \ +const char * \ fcn_name (lookup_type val, char *buf, gsize len) \ { \ nm_utils_to_string_buffer_init (&buf, &len); \ @@ -641,9 +851,7 @@ fcn_name (lookup_type val, char *buf, gsize len) \ } #define NM_UTILS_ENUM2STR_DEFINE(fcn_name, lookup_type, ...) \ - _NM_UTILS_ENUM2STR_DEFINE (, fcn_name, lookup_type, "d", __VA_ARGS__) -#define NM_UTILS_ENUM2STR_DEFINE_STATIC(fcn_name, lookup_type, ...) \ - _NM_UTILS_ENUM2STR_DEFINE (static, fcn_name, lookup_type, "d", __VA_ARGS__) + NM_UTILS_ENUM2STR_DEFINE_FULL (fcn_name, lookup_type, "d", __VA_ARGS__) /*****************************************************************************/ @@ -797,8 +1005,15 @@ nm_utils_error_new_cancelled (gboolean is_disposing, return error; } -gboolean nm_utils_error_is_cancelled (GError *error, - gboolean consider_is_disposing); +gboolean nm_utils_error_is_cancelled_or_disposing (GError *error); + +static inline gboolean +nm_utils_error_is_cancelled (GError *error) +{ + return error + && error->code == G_IO_ERROR_CANCELLED + && error->domain == G_IO_ERROR; +} gboolean nm_utils_error_is_notfound (GError *error); @@ -927,6 +1142,7 @@ typedef enum { NM_UTILS_STR_UTF8_SAFE_FLAG_NONE = 0, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL = 0x0001, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII = 0x0002, + NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET = 0x0004, } NMUtilsStrUtf8SafeFlags; const char *nm_utils_buf_utf8safe_escape (gconstpointer buf, gssize buflen, NMUtilsStrUtf8SafeFlags flags, char **to_free); @@ -958,6 +1174,32 @@ nm_g_variant_unref_floating (GVariant *var) g_variant_unref (var); } +#define nm_g_variant_lookup(dictionary, ...) \ + ({ \ + GVariant *const _dictionary = (dictionary); \ + \ + ( _dictionary \ + && g_variant_lookup (_dictionary, __VA_ARGS__)); \ + }) + +static inline GVariant * +nm_g_variant_lookup_value (GVariant *dictionary, + const char *key, + const GVariantType *expected_type) +{ + return dictionary + ? g_variant_lookup_value (dictionary, key, expected_type) + : NULL; +} + +static inline gboolean +nm_g_variant_is_of_type (GVariant *value, + const GVariantType *type) +{ + return value + && g_variant_is_of_type (value, type); +} + static inline void nm_g_source_destroy_and_unref (GSource *source) { @@ -986,12 +1228,19 @@ GSource *nm_g_idle_source_new (int priority, gpointer user_data, GDestroyNotify destroy_notify); -GSource *nm_g_timeout_source_new (guint timeout_ms, +GSource *nm_g_timeout_source_new (guint timeout_msec, int priority, GSourceFunc func, gpointer user_data, GDestroyNotify destroy_notify); - +GSource *nm_g_unix_fd_source_new (int fd, + GIOCondition io_condition, + int priority, + gboolean (*source_func) (int fd, + GIOCondition condition, + gpointer user_data), + gpointer user_data, + GDestroyNotify destroy_notify); GSource *nm_g_unix_signal_source_new (int signum, int priority, GSourceFunc handler, @@ -1019,6 +1268,25 @@ nm_g_main_context_push_thread_default (GMainContext *context) return context; } +static inline gboolean +nm_g_main_context_is_thread_default (GMainContext *context) +{ + GMainContext *cur_context; + + cur_context = g_main_context_get_thread_default (); + if (cur_context == context) + return TRUE; + + if (G_UNLIKELY (!cur_context)) + cur_context = g_main_context_default (); + else if (G_UNLIKELY (!context)) + context = g_main_context_default (); + else + return FALSE; + + return (cur_context == context); +} + static inline GMainContext * nm_g_main_context_push_thread_default_if_necessary (GMainContext *context) { @@ -1077,7 +1345,18 @@ typedef struct { }; } NMUtilsNamedValue; -NMUtilsNamedValue *nm_utils_named_values_from_str_dict (GHashTable *hash, guint *out_len); +NMUtilsNamedValue *nm_utils_named_values_from_str_dict_with_sort (GHashTable *hash, + guint *out_len, + GCompareDataFunc compare_func, + gpointer user_data); + +static inline NMUtilsNamedValue * +nm_utils_named_values_from_str_dict (GHashTable *hash, guint *out_len) +{ + G_STATIC_ASSERT (G_STRUCT_OFFSET (NMUtilsNamedValue, name) == 0); + + return nm_utils_named_values_from_str_dict_with_sort (hash, out_len, nm_strcmp_p_with_data, NULL); +} gssize nm_utils_named_value_list_find (const NMUtilsNamedValue *arr, gsize len, @@ -1146,6 +1425,26 @@ char *nm_utils_g_slist_strlist_join (const GSList *a, const char *separator); /*****************************************************************************/ +static inline guint +nm_g_hash_table_size (GHashTable *hash) +{ + return hash ? g_hash_table_size (hash) : 0u; +} + +static inline gpointer +nm_g_hash_table_lookup (GHashTable *hash, gconstpointer key) +{ + return hash ? g_hash_table_lookup (hash, key) : NULL; +} + +static inline gboolean +nm_g_hash_table_remove (GHashTable *hash, gconstpointer key) +{ + return hash ? g_hash_table_remove (hash, key) : FALSE; +} + +/*****************************************************************************/ + gssize nm_utils_ptrarray_find_binary_search (gconstpointer *list, gsize len, gconstpointer needle, @@ -1190,31 +1489,25 @@ _nm_utils_strv_equal (char **strv1, char **strv2) /*****************************************************************************/ -#define NM_UTILS_NS_PER_SECOND ((gint64) 1000000000) -#define NM_UTILS_NS_PER_MSEC ((gint64) 1000000) -#define NM_UTILS_MSEC_PER_SECOND ((gint64) 1000) -#define NM_UTILS_NS_TO_MSEC_CEIL(nsec) (((nsec) + (NM_UTILS_NS_PER_MSEC - 1)) / NM_UTILS_NS_PER_MSEC) +#define NM_UTILS_NSEC_PER_SEC ((gint64) 1000000000) +#define NM_UTILS_USEC_PER_SEC ((gint64) 1000000) +#define NM_UTILS_MSEC_PER_SEC ((gint64) 1000) +#define NM_UTILS_NSEC_PER_MSEC ((gint64) 1000000) + +static inline gint64 +NM_UTILS_NSEC_TO_MSEC_CEIL (gint64 nsec) +{ + return (nsec + (NM_UTILS_NSEC_PER_MSEC - 1)) / NM_UTILS_NSEC_PER_MSEC; +} /*****************************************************************************/ -int nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_ns); +int nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_nsec); ssize_t nm_utils_fd_read_loop (int fd, void *buf, size_t nbytes, bool do_poll); int nm_utils_fd_read_loop_exact (int fd, void *buf, size_t nbytes, bool do_poll); /*****************************************************************************/ -static inline const char * -nm_utils_dbus_normalize_object_path (const char *path) -{ - /* D-Bus does not allow an empty object path. Hence, whenever we mean NULL / no-object - * on D-Bus, it's path is actually "/". - * - * Normalize that away, and return %NULL in that case. */ - if (path && path[0] == '/' && path[1] == '\0') - return NULL; - return path; -} - #define NM_DEFINE_GDBUS_ARG_INFO_FULL(name_, ...) \ ((GDBusArgInfo *) (&((const GDBusArgInfo) { \ .ref_count = -1, \ @@ -1412,6 +1705,92 @@ guint8 *nm_utils_hexstr2bin_alloc (const char *hexstr, /*****************************************************************************/ +#define _NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(fcn_name, \ + value_type, \ + value_type_result, \ + entry_cmd, \ + unknown_val_cmd, \ + get_operator, \ + ...) \ +value_type_result \ +fcn_name (const char *name) \ +{ \ + static const struct { \ + const char *name; \ + value_type value; \ + } LIST[] = { \ + __VA_ARGS__ \ + }; \ + \ + if (NM_MORE_ASSERT_ONCE (5)) { \ + int i; \ + \ + for (i = 0; i < G_N_ELEMENTS (LIST); i++) { \ + nm_assert (LIST[i].name); \ + if (i > 0) \ + nm_assert (strcmp (LIST[i - 1].name, LIST[i].name) < 0); \ + } \ + } \ + \ + { entry_cmd; } \ + \ + if (G_LIKELY (name)) { \ + G_STATIC_ASSERT (G_N_ELEMENTS (LIST) > 1); \ + G_STATIC_ASSERT (G_N_ELEMENTS (LIST) < G_MAXINT / 2 - 10); \ + int imin = 0; \ + int imax = (G_N_ELEMENTS (LIST) - 1); \ + int imid = (G_N_ELEMENTS (LIST) - 1) / 2; \ + \ + for (;;) { \ + const int cmp = strcmp (LIST[imid].name, name); \ + \ + if (G_UNLIKELY (cmp == 0)) \ + return get_operator (LIST[imid].value); \ + \ + if (cmp < 0) \ + imin = imid + 1; \ + else \ + imax = imid - 1; \ + \ + if (G_UNLIKELY (imin > imax)) \ + break; \ + \ + /* integer overflow cannot happen, because LIST is shorter than G_MAXINT/2. */ \ + imid = (imin + imax) / 2;\ + } \ + } \ + \ + { unknown_val_cmd; } \ +} + +#define NM_UTILS_STRING_TABLE_LOOKUP_STRUCT_DEFINE(fcn_name, \ + result_type, \ + entry_cmd, \ + unknown_val_cmd, \ + ...) \ + _NM_UTILS_STRING_TABLE_LOOKUP_DEFINE (fcn_name, \ + result_type, \ + const result_type *, \ + entry_cmd, \ + unknown_val_cmd, \ + &, \ + __VA_ARGS__) + +#define NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(fcn_name, \ + result_type, \ + entry_cmd, \ + unknown_val_cmd, \ + ...) \ + _NM_UTILS_STRING_TABLE_LOOKUP_DEFINE (fcn_name, \ + result_type, \ + result_type, \ + entry_cmd, \ + unknown_val_cmd, \ + , \ + __VA_ARGS__) + +/*****************************************************************************/ + static inline GTask * nm_g_task_new (gpointer source_object, GCancellable *cancellable, @@ -1442,9 +1821,49 @@ guint nm_utils_parse_debug_string (const char *string, /*****************************************************************************/ +static inline gboolean +nm_utils_strdup_reset (char **dst, const char *src) +{ + nm_assert (dst); + + if (nm_streq0 (*dst, src)) + return FALSE; + g_free (*dst); + *dst = g_strdup (src); + return TRUE; +} + +/*****************************************************************************/ + +/* nm_utils_get_next_realloc_size() is used to grow buffers exponentially, when + * the final size is unknown. As such, it has borders for which it allocates + * certain buffer sizes. + * + * The use of these defines is to get favorable allocation sequences. + * For example, nm_str_buf_init() asks for an initial allocation size. Note that + * it reserves the exactly requested amount, under the assumption that the + * user may know how many bytes will be required. However, often the caller + * doesn't know in advance, and NMStrBuf grows exponentially by calling + * nm_utils_get_next_realloc_size(). + * Imagine you call nm_str_buf_init() with an initial buffer size 100, and you + * add one character at a time. Then the first reallocation will increase the + * buffer size only from 100 to 104. + * If you however start with an initial buffer size of 104, then the next reallocation + * via nm_utils_get_next_realloc_size() gives you 232, and so on. By using + * these sizes, it results in one less allocation, if you anyway don't know the + * exact size in advance. */ +#define NM_UTILS_GET_NEXT_REALLOC_SIZE_104 ((gsize) 104) +#define NM_UTILS_GET_NEXT_REALLOC_SIZE_1000 ((gsize) 1000) + +gsize nm_utils_get_next_realloc_size (gboolean true_realloc, gsize requested); + +/*****************************************************************************/ + typedef enum { - NMU_IFACE_KERNEL = 0, + NMU_IFACE_ANY, + NMU_IFACE_KERNEL, NMU_IFACE_OVS, + NMU_IFACE_OVS_AND_KERNEL, } NMUtilsIfaceType; gboolean nm_utils_ifname_valid_kernel (const char *name, GError **error); diff --git a/shared/nm-glib-aux/nm-str-buf.h b/shared/nm-glib-aux/nm-str-buf.h new file mode 100644 index 00000000..8c73bfaf --- /dev/null +++ b/shared/nm-glib-aux/nm-str-buf.h @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: LGPL-2.1+ + +#ifndef __NM_STR_BUF_H__ +#define __NM_STR_BUF_H__ + +#include "nm-shared-utils.h" +#include "nm-secret-utils.h" + +/*****************************************************************************/ + +/* NMStrBuf is not unlike GString. The main difference is that it can use + * nm_explicit_bzero() when growing the buffer. */ +typedef struct _NMStrBuf { + + char *_priv_str; + + /* The unions only exist because we allow/encourage read-only access + * to the "len" and "allocated" fields, but modifying the fields is + * only allowed to the NMStrBuf implementation itself. */ + union { + /*const*/ gsize len; + gsize _priv_len; + }; + union { + /*const*/ gsize allocated; + gsize _priv_allocated; + }; + + bool _priv_do_bzero_mem; +} NMStrBuf; + +/*****************************************************************************/ + +static inline void +_nm_str_buf_assert (NMStrBuf *strbuf) +{ + nm_assert (strbuf); + nm_assert (strbuf->_priv_str); + nm_assert (strbuf->_priv_allocated > 0); + nm_assert (strbuf->_priv_len <= strbuf->_priv_allocated); +} + +static inline void +nm_str_buf_init (NMStrBuf *strbuf, + gsize len, + bool do_bzero_mem) +{ + nm_assert (strbuf); + nm_assert (len > 0); + + strbuf->_priv_str = g_malloc (len); + strbuf->_priv_allocated = len; + strbuf->_priv_len = 0; + strbuf->_priv_do_bzero_mem = do_bzero_mem; + + _nm_str_buf_assert (strbuf); +} + +void _nm_str_buf_ensure_size (NMStrBuf *strbuf, + gsize new_size, + gboolean reserve_exact); + +static inline void +nm_str_buf_maybe_expand (NMStrBuf *strbuf, + gsize reserve, + gboolean reserve_exact) +{ + _nm_str_buf_assert (strbuf); + + /* currently we always require to reserve a non-zero number of bytes. */ + nm_assert (reserve > 0); + nm_assert (strbuf->_priv_len < G_MAXSIZE - reserve); + + /* @reserve is the extra space that we require. */ + if (G_UNLIKELY (reserve > strbuf->_priv_allocated - strbuf->_priv_len)) + _nm_str_buf_ensure_size (strbuf, strbuf->_priv_len + reserve, reserve_exact); +} + +/*****************************************************************************/ + +/** + * nm_str_buf_set_size: + * @strbuf: the initialized #NMStrBuf + * @new_len: the new length + * @honor_do_bzero_mem: if %TRUE, the shrinked memory will be cleared, if + * do_bzero_mem is set. This should be usually set to %TRUE, unless + * you know that the shrinked memory does not contain data that requires to be + * cleared. When growing the size, this value has no effect. + * @reserve_exact: when growing the buffer, reserve the exact amount of bytes. + * If %FALSE, the buffer may allocate more memory than requested to grow + * exponentially. + * + * This is like g_string_set_size(). If new_len is smaller than the + * current length, the string gets truncated (excess memory will be cleared). + * + * When extending the length, the added bytes are undefined (like with + * g_string_set_size(). Likewise, if you first pre-allocate a buffer with + * nm_str_buf_maybe_expand(), then write to the bytes, and finally set + * the appropriate size, then that works as expected (by not clearing the + * pre-existing, grown buffer). + */ +static inline void +nm_str_buf_set_size (NMStrBuf *strbuf, + gsize new_len, + gboolean honor_do_bzero_mem, + gboolean reserve_exact) +{ + _nm_str_buf_assert (strbuf); + + if (new_len < strbuf->_priv_len) { + if ( honor_do_bzero_mem + && strbuf->_priv_do_bzero_mem) { + /* we only clear the memory that we wrote to. */ + nm_explicit_bzero (&strbuf->_priv_str[new_len], strbuf->_priv_len - new_len); + } + } else if (new_len > strbuf->_priv_len) { + nm_str_buf_maybe_expand (strbuf, + new_len - strbuf->_priv_len + (reserve_exact ? 0u : 1u), + reserve_exact); + } else + return; + + strbuf->_priv_len = new_len; +} + +/*****************************************************************************/ + +static inline void +nm_str_buf_erase (NMStrBuf *strbuf, + gsize pos, + gssize len, + gboolean honor_do_bzero_mem) +{ + gsize new_len; + + _nm_str_buf_assert (strbuf); + + nm_assert (pos <= strbuf->_priv_len); + + if (len == 0) + return; + + if (len < 0) { + /* truncate the string before pos */ + nm_assert (len == -1); + new_len = pos; + } else { + gsize l = len; + + nm_assert (l <= strbuf->_priv_len - pos); + + new_len = strbuf->_priv_len - l; + if (pos + l < strbuf->_priv_len) { + memmove (&strbuf->_priv_str[pos], + &strbuf->_priv_str[pos + l], + strbuf->_priv_len - (pos + l)); + } + } + + nm_assert (new_len <= strbuf->_priv_len); + nm_str_buf_set_size (strbuf, new_len, honor_do_bzero_mem, TRUE); +} + +/*****************************************************************************/ + +static inline void +nm_str_buf_append_c (NMStrBuf *strbuf, + char ch) +{ + nm_str_buf_maybe_expand (strbuf, 2, FALSE); + strbuf->_priv_str[strbuf->_priv_len++] = ch; +} + +static inline void +nm_str_buf_append_c2 (NMStrBuf *strbuf, + char ch0, + char ch1) +{ + nm_str_buf_maybe_expand (strbuf, 3, FALSE); + strbuf->_priv_str[strbuf->_priv_len++] = ch0; + strbuf->_priv_str[strbuf->_priv_len++] = ch1; +} + +static inline void +nm_str_buf_append_c4 (NMStrBuf *strbuf, + char ch0, + char ch1, + char ch2, + char ch3) +{ + nm_str_buf_maybe_expand (strbuf, 5, FALSE); + strbuf->_priv_str[strbuf->_priv_len++] = ch0; + strbuf->_priv_str[strbuf->_priv_len++] = ch1; + strbuf->_priv_str[strbuf->_priv_len++] = ch2; + strbuf->_priv_str[strbuf->_priv_len++] = ch3; +} + +static inline void +nm_str_buf_append_len (NMStrBuf *strbuf, + const char *str, + gsize len) +{ + _nm_str_buf_assert (strbuf); + + if (len > 0) { + nm_str_buf_maybe_expand (strbuf, len + 1, FALSE); + memcpy (&strbuf->_priv_str[strbuf->_priv_len], str, len); + strbuf->_priv_len += len; + } +} + +static inline void +nm_str_buf_append (NMStrBuf *strbuf, + const char *str) +{ + nm_assert (str); + + nm_str_buf_append_len (strbuf, str, strlen (str)); +} + +void nm_str_buf_append_printf (NMStrBuf *strbuf, + const char *format, + ...) _nm_printf (2, 3); + +static inline void +nm_str_buf_ensure_trailing_c (NMStrBuf *strbuf, char ch) +{ + _nm_str_buf_assert (strbuf); + + if ( strbuf->_priv_len == 0 + || strbuf->_priv_str[strbuf->_priv_len - 1] != ch) + nm_str_buf_append_c (strbuf, ch); +} + +/*****************************************************************************/ + +static inline gboolean +nm_str_buf_is_initalized (NMStrBuf *strbuf) +{ + nm_assert (strbuf); +#if NM_MORE_ASSERTS + if (strbuf->_priv_str) + _nm_str_buf_assert (strbuf); +#endif + return !!strbuf->_priv_str; +} + +/** + * nm_str_buf_get_str: + * @strbuf: the #NMStrBuf instance + * + * Returns the NUL terminated internal string. + * + * While constructing the string, the intermediate buffer + * is not NUL terminated (this makes it different from GString). + * Usually, one would build the string and retrieve it at the + * end with nm_str_buf_finalize(). This returns the NUL terminated + * buffer that was appended so far. Contrary to nm_str_buf_finalize(), you + * can still append more data to the buffer and this does not transfer ownership + * of the string. + * + * Returns: (transfer none): the internal string. The string + * is of length "strbuf->len", which may be larger if the + * returned string contains NUL characters (binary). The terminating + * NUL character is always present after "strbuf->len" characters. + */ +static inline const char * +nm_str_buf_get_str (NMStrBuf *strbuf) +{ + nm_str_buf_maybe_expand (strbuf, 1, FALSE); + strbuf->_priv_str[strbuf->_priv_len] = '\0'; + return strbuf->_priv_str; +} + +static inline char * +nm_str_buf_get_str_unsafe (NMStrBuf *strbuf) +{ + _nm_str_buf_assert (strbuf); + return strbuf->_priv_str; +} + +/** + * nm_str_buf_finalize: + * @strbuf: an initilized #NMStrBuf + * @out_len: (out): (allow-none): optional output + * argument with the length of the returned string. + * + * Returns: (transfer full): the string of the buffer + * which must be freed by the caller. The @strbuf + * is afterwards in undefined state, though it can be + * reused after nm_str_buf_init(). */ +static inline char * +nm_str_buf_finalize (NMStrBuf *strbuf, + gsize *out_len) +{ + nm_str_buf_maybe_expand (strbuf, 1, TRUE); + strbuf->_priv_str[strbuf->_priv_len] = '\0'; + + NM_SET_OUT (out_len, strbuf->_priv_len); + + /* the buffer is in invalid state afterwards, however, we clear it + * so far, that nm_auto_str_buf and nm_str_buf_destroy() is happy. */ + return g_steal_pointer (&strbuf->_priv_str); +} + +/** + * nm_str_buf_destroy: + * @strbuf: an initialized #NMStrBuf + * + * Frees the associated memory of @strbuf. The buffer + * afterwards is in undefined state, but can be re-initialized + * with nm_str_buf_init(). + */ +static inline void +nm_str_buf_destroy (NMStrBuf *strbuf) +{ + if (!strbuf->_priv_str) + return; + _nm_str_buf_assert (strbuf); + if (strbuf->_priv_do_bzero_mem) + nm_explicit_bzero (strbuf->_priv_str, strbuf->_priv_len); + g_free (strbuf->_priv_str); + + /* the buffer is in invalid state afterwards, however, we clear it + * so far, that nm_auto_str_buf is happy when calling + * nm_str_buf_destroy() again. */ + strbuf->_priv_str = NULL; +} + +#define nm_auto_str_buf nm_auto (nm_str_buf_destroy) + +#endif /* __NM_STR_BUF_H__ */ diff --git a/shared/nm-glib-aux/nm-time-utils.c b/shared/nm-glib-aux/nm-time-utils.c index 20d663bc..356ed1a5 100644 --- a/shared/nm-glib-aux/nm-time-utils.c +++ b/shared/nm-glib-aux/nm-time-utils.c @@ -40,19 +40,19 @@ _t_init_global_state (void) /* The only failure we tolerate is that CLOCK_BOOTTIME is not supported. * Other than that, we rely on kernel to not fail on this. */ g_assert (r == 0); - g_assert (tp.tv_nsec >= 0 && tp.tv_nsec < NM_UTILS_NS_PER_SECOND); + g_assert (tp.tv_nsec >= 0 && tp.tv_nsec < NM_UTILS_NSEC_PER_SEC); /* Calculate an offset for the time stamp. * * We always want positive values, because then we can initialize * a timestamp with 0 and be sure, that it will be less then any * value nm_utils_get_monotonic_timestamp_*() might return. - * For this to be true also for nm_utils_get_monotonic_timestamp_s() at + * For this to be true also for nm_utils_get_monotonic_timestamp_sec() at * early boot, we have to shift the timestamp to start counting at * least from 1 second onward. * * Another advantage of shifting is, that this way we make use of the whole 31 bit - * range of signed int, before the time stamp for nm_utils_get_monotonic_timestamp_s() + * range of signed int, before the time stamp for nm_utils_get_monotonic_timestamp_sec() * wraps (~68 years). **/ offset_sec = (- ((gint64) tp.tv_sec)) + 1; @@ -96,7 +96,7 @@ _t_init_global_state (void) _r = clock_gettime (_p2->clk_id, _tp); \ \ nm_assert (_r == 0); \ - nm_assert (_tp->tv_nsec >= 0 && _tp->tv_nsec < NM_UTILS_NS_PER_SECOND); \ + nm_assert (_tp->tv_nsec >= 0 && _tp->tv_nsec < NM_UTILS_NSEC_PER_SEC); \ \ _p2; \ }) @@ -107,7 +107,7 @@ _t_init_global_state (void) /*****************************************************************************/ /** - * nm_utils_get_monotonic_timestamp_ns: + * nm_utils_get_monotonic_timestamp_nsec: * * Returns: a monotonically increasing time stamp in nanoseconds, * starting at an unspecified offset. See clock_gettime(), %CLOCK_BOOTTIME. @@ -115,11 +115,11 @@ _t_init_global_state (void) * The returned value will start counting at an undefined point * in the past and will always be positive. * - * All the nm_utils_get_monotonic_timestamp_*s functions return the same + * All the nm_utils_get_monotonic_timestamp_*sec functions return the same * timestamp but in different scales (nsec, usec, msec, sec). **/ gint64 -nm_utils_get_monotonic_timestamp_ns (void) +nm_utils_get_monotonic_timestamp_nsec (void) { const GlobalState *p; struct timespec tp; @@ -130,12 +130,12 @@ nm_utils_get_monotonic_timestamp_ns (void) * integer, which makes it easier to calculate time differences (when * you want to subtract signed values). **/ - return (((gint64) tp.tv_sec) + p->offset_sec) * NM_UTILS_NS_PER_SECOND + + return (((gint64) tp.tv_sec) + p->offset_sec) * NM_UTILS_NSEC_PER_SEC + tp.tv_nsec; } /** - * nm_utils_get_monotonic_timestamp_us: + * nm_utils_get_monotonic_timestamp_usec: * * Returns: a monotonically increasing time stamp in microseconds, * starting at an unspecified offset. See clock_gettime(), %CLOCK_BOOTTIME. @@ -143,11 +143,11 @@ nm_utils_get_monotonic_timestamp_ns (void) * The returned value will start counting at an undefined point * in the past and will always be positive. * - * All the nm_utils_get_monotonic_timestamp_*s functions return the same + * All the nm_utils_get_monotonic_timestamp_*sec functions return the same * timestamp but in different scales (nsec, usec, msec, sec). **/ gint64 -nm_utils_get_monotonic_timestamp_us (void) +nm_utils_get_monotonic_timestamp_usec (void) { const GlobalState *p; struct timespec tp; @@ -159,11 +159,11 @@ nm_utils_get_monotonic_timestamp_us (void) * you want to subtract signed values). **/ return (((gint64) tp.tv_sec) + p->offset_sec) * ((gint64) G_USEC_PER_SEC) + - (tp.tv_nsec / (NM_UTILS_NS_PER_SECOND/G_USEC_PER_SEC)); + (tp.tv_nsec / (NM_UTILS_NSEC_PER_SEC/G_USEC_PER_SEC)); } /** - * nm_utils_get_monotonic_timestamp_ms: + * nm_utils_get_monotonic_timestamp_msec: * * Returns: a monotonically increasing time stamp in milliseconds, * starting at an unspecified offset. See clock_gettime(), %CLOCK_BOOTTIME. @@ -171,11 +171,11 @@ nm_utils_get_monotonic_timestamp_us (void) * The returned value will start counting at an undefined point * in the past and will always be positive. * - * All the nm_utils_get_monotonic_timestamp_*s functions return the same + * All the nm_utils_get_monotonic_timestamp_*sec functions return the same * timestamp but in different scales (nsec, usec, msec, sec). **/ gint64 -nm_utils_get_monotonic_timestamp_ms (void) +nm_utils_get_monotonic_timestamp_msec (void) { const GlobalState *p; struct timespec tp; @@ -187,23 +187,23 @@ nm_utils_get_monotonic_timestamp_ms (void) * you want to subtract signed values). **/ return (((gint64) tp.tv_sec) + p->offset_sec) * ((gint64) 1000) + - (tp.tv_nsec / (NM_UTILS_NS_PER_SECOND/1000)); + (tp.tv_nsec / (NM_UTILS_NSEC_PER_SEC/1000)); } /** - * nm_utils_get_monotonic_timestamp_s: + * nm_utils_get_monotonic_timestamp_sec: * - * Returns: nm_utils_get_monotonic_timestamp_ms() in seconds (throwing + * Returns: nm_utils_get_monotonic_timestamp_msec() in seconds (throwing * away sub second parts). The returned value will always be positive. * * This value wraps after roughly 68 years which should be fine for any * practical purpose. * - * All the nm_utils_get_monotonic_timestamp_*s functions return the same + * All the nm_utils_get_monotonic_timestamp_*sec functions return the same * timestamp but in different scales (nsec, usec, msec, sec). **/ gint32 -nm_utils_get_monotonic_timestamp_s (void) +nm_utils_get_monotonic_timestamp_sec (void) { const GlobalState *p; struct timespec tp; @@ -216,13 +216,13 @@ nm_utils_get_monotonic_timestamp_s (void) /** * nm_utils_monotonic_timestamp_as_boottime: * @timestamp: the monotonic-timestamp that should be converted into CLOCK_BOOTTIME. - * @timestamp_ns_per_tick: How many nanoseconds make one unit of @timestamp? E.g. if - * @timestamp is in unit seconds, pass %NM_UTILS_NS_PER_SECOND; if @timestamp is - * in nanoseconds, pass 1; if @timestamp is in milliseconds, pass %NM_UTILS_NS_PER_SECOND/1000. - * This must be a multiple of 10, and between 1 and %NM_UTILS_NS_PER_SECOND. + * @timestamp_nsec_per_tick: How many nanoseconds make one unit of @timestamp? E.g. if + * @timestamp is in unit seconds, pass %NM_UTILS_NSEC_PER_SEC; if @timestamp is + * in nanoseconds, pass 1; if @timestamp is in milliseconds, pass %NM_UTILS_NSEC_PER_SEC/1000. + * This must be a multiple of 10, and between 1 and %NM_UTILS_NSEC_PER_SEC. * * Returns: the monotonic-timestamp as CLOCK_BOOTTIME, as returned by clock_gettime(). - * The unit is the same as the passed in @timestamp based on @timestamp_ns_per_tick. + * The unit is the same as the passed in @timestamp based on @timestamp_nsec_per_tick. * E.g. if you passed @timestamp in as seconds, it will return boottime in seconds. * * Note that valid monotonic-timestamps are always positive numbers (counting roughly since @@ -234,16 +234,16 @@ nm_utils_get_monotonic_timestamp_s (void) * On older kernels that don't support CLOCK_BOOTTIME, the returned time is instead CLOCK_MONOTONIC. **/ gint64 -nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ns_per_tick) +nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_nsec_per_tick) { const GlobalState *p; gint64 offset; - /* only support ns-per-tick being a multiple of 10. */ - g_return_val_if_fail (timestamp_ns_per_tick == 1 - || (timestamp_ns_per_tick > 0 && - timestamp_ns_per_tick <= NM_UTILS_NS_PER_SECOND && - timestamp_ns_per_tick % 10 == 0), + /* only support nsec-per-tick being a multiple of 10. */ + g_return_val_if_fail ( timestamp_nsec_per_tick == 1 + || (timestamp_nsec_per_tick > 0 && + timestamp_nsec_per_tick <= NM_UTILS_NSEC_PER_SEC && + timestamp_nsec_per_tick % 10 == 0), -1); /* if the caller didn't yet ever fetch a monotonic-timestamp, he cannot pass any meaningful @@ -255,7 +255,7 @@ nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ns_ nm_assert (p->offset_sec <= 0); /* calculate the offset of monotonic-timestamp to boottime. offset_s is <= 1. */ - offset = p->offset_sec * (NM_UTILS_NS_PER_SECOND / timestamp_ns_per_tick); + offset = p->offset_sec * (NM_UTILS_NSEC_PER_SEC / timestamp_nsec_per_tick); nm_assert (offset <= 0 && offset > G_MININT64); @@ -270,9 +270,9 @@ nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ns_ * @boottime: the timestamp from CLOCK_BOOTTIME (or CLOCK_MONOTONIC, if * kernel does not support CLOCK_BOOTTIME and monotonic timestamps are based * on CLOCK_MONOTONIC). - * @timestamp_ns_per_tick: the scale in which @boottime is. If @boottime is in + * @timestamp_nsec_per_tick: the scale in which @boottime is. If @boottime is in * nano seconds, this should be 1. If it is in milli seconds, this should be - * %NM_UTILS_NS_PER_SECOND/1000, etc. + * %NM_UTILS_NSEC_PER_SEC/1000, etc. * * Returns: the same timestamp in monotonic timestamp scale. * @@ -284,16 +284,16 @@ nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ns_ * This is the inverse of nm_utils_monotonic_timestamp_as_boottime(). */ gint64 -nm_utils_monotonic_timestamp_from_boottime (guint64 boottime, gint64 timestamp_ns_per_tick) +nm_utils_monotonic_timestamp_from_boottime (guint64 boottime, gint64 timestamp_nsec_per_tick) { const GlobalState *p; gint64 offset; - /* only support ns-per-tick being a multiple of 10. */ - g_return_val_if_fail (timestamp_ns_per_tick == 1 - || (timestamp_ns_per_tick > 0 && - timestamp_ns_per_tick <= NM_UTILS_NS_PER_SECOND && - timestamp_ns_per_tick % 10 == 0), + /* only support nsec-per-tick being a multiple of 10. */ + g_return_val_if_fail ( timestamp_nsec_per_tick == 1 + || (timestamp_nsec_per_tick > 0 && + timestamp_nsec_per_tick <= NM_UTILS_NSEC_PER_SEC && + timestamp_nsec_per_tick % 10 == 0), -1); p = _t_get_global_state (); @@ -301,7 +301,7 @@ nm_utils_monotonic_timestamp_from_boottime (guint64 boottime, gint64 timestamp_n nm_assert (p->offset_sec <= 0); /* calculate the offset of monotonic-timestamp to boottime. offset_s is <= 1. */ - offset = p->offset_sec * (NM_UTILS_NS_PER_SECOND / timestamp_ns_per_tick); + offset = p->offset_sec * (NM_UTILS_NSEC_PER_SEC / timestamp_nsec_per_tick); nm_assert (offset <= 0 && offset > G_MININT64); @@ -312,21 +312,21 @@ nm_utils_monotonic_timestamp_from_boottime (guint64 boottime, gint64 timestamp_n } gint64 -nm_utils_clock_gettime_ns (clockid_t clockid) +nm_utils_clock_gettime_nsec (clockid_t clockid) { struct timespec tp; if (clock_gettime (clockid, &tp) != 0) return -NM_ERRNO_NATIVE (errno); - return nm_utils_timespec_to_ns (&tp); + return nm_utils_timespec_to_nsec (&tp); } gint64 -nm_utils_clock_gettime_ms (clockid_t clockid) +nm_utils_clock_gettime_msec (clockid_t clockid) { struct timespec tp; if (clock_gettime (clockid, &tp) != 0) return -NM_ERRNO_NATIVE (errno); - return nm_utils_timespec_to_ms (&tp); + return nm_utils_timespec_to_msec (&tp); } diff --git a/shared/nm-glib-aux/nm-time-utils.h b/shared/nm-glib-aux/nm-time-utils.h index 8bf41b96..190d9491 100644 --- a/shared/nm-glib-aux/nm-time-utils.h +++ b/shared/nm-glib-aux/nm-time-utils.h @@ -9,34 +9,42 @@ #include <time.h> static inline gint64 -nm_utils_timespec_to_ns (const struct timespec *ts) +nm_utils_timespec_to_nsec (const struct timespec *ts) { - return (((gint64) ts->tv_sec) * ((gint64) NM_UTILS_NS_PER_SECOND)) + return (((gint64) ts->tv_sec) * ((gint64) NM_UTILS_NSEC_PER_SEC)) + ((gint64) ts->tv_nsec); } static inline gint64 -nm_utils_timespec_to_ms (const struct timespec *ts) +nm_utils_timespec_to_msec (const struct timespec *ts) { return (((gint64) ts->tv_sec) * ((gint64) 1000)) - + (((gint64) ts->tv_nsec) / ((gint64) NM_UTILS_NS_PER_SECOND / 1000)); + + (((gint64) ts->tv_nsec) / ((gint64) NM_UTILS_NSEC_PER_SEC / 1000)); } -gint64 nm_utils_get_monotonic_timestamp_ns (void); -gint64 nm_utils_get_monotonic_timestamp_us (void); -gint64 nm_utils_get_monotonic_timestamp_ms (void); -gint32 nm_utils_get_monotonic_timestamp_s (void); -gint64 nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ticks_per_ns); -gint64 nm_utils_monotonic_timestamp_from_boottime (guint64 boottime, gint64 timestamp_ns_per_tick); +gint64 nm_utils_get_monotonic_timestamp_nsec (void); +gint64 nm_utils_get_monotonic_timestamp_usec (void); +gint64 nm_utils_get_monotonic_timestamp_msec (void); +gint32 nm_utils_get_monotonic_timestamp_sec (void); + +gint64 nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ticks_per_nsec); +gint64 nm_utils_monotonic_timestamp_from_boottime (guint64 boottime, gint64 timestamp_nsec_per_tick); + +static inline gint64 +nm_utils_get_monotonic_timestamp_nsec_cached (gint64 *cache_now) +{ + return (*cache_now) + ?: (*cache_now = nm_utils_get_monotonic_timestamp_nsec ()); +} static inline gint64 -nm_utils_get_monotonic_timestamp_ns_cached (gint64 *cache_now) +nm_utils_get_monotonic_timestamp_msec_cached (gint64 *cache_now) { return (*cache_now) - ?: (*cache_now = nm_utils_get_monotonic_timestamp_ns ()); + ?: (*cache_now = nm_utils_get_monotonic_timestamp_msec ()); } -gint64 nm_utils_clock_gettime_ns (clockid_t clockid); -gint64 nm_utils_clock_gettime_ms (clockid_t clockid); +gint64 nm_utils_clock_gettime_nsec (clockid_t clockid); +gint64 nm_utils_clock_gettime_msec (clockid_t clockid); #endif /* __NM_TIME_UTILS_H__ */ diff --git a/shared/nm-utils/tests/meson.build b/shared/nm-glib-aux/tests/meson.build index 1ee5efff..f6328db9 100644 --- a/shared/nm-utils/tests/meson.build +++ b/shared/nm-glib-aux/tests/meson.build @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: LGPL-2.1+ + test_unit = 'test-shared-general' c_flags = [ @@ -14,7 +16,7 @@ exe = executable( ) test( - 'shared/nm-utils/' + test_unit, + 'shared/nm-glib-aux/' + test_unit, test_script, args: test_args + [exe.full_path()], timeout: default_test_timeout, diff --git a/shared/nm-utils/tests/test-shared-general.c b/shared/nm-glib-aux/tests/test-shared-general.c index 34da19b3..5c8bdf18 100644 --- a/shared/nm-utils/tests/test-shared-general.c +++ b/shared/nm-glib-aux/tests/test-shared-general.c @@ -9,6 +9,7 @@ #include "nm-std-aux/unaligned.h" #include "nm-glib-aux/nm-random-utils.h" +#include "nm-glib-aux/nm-str-buf.h" #include "nm-glib-aux/nm-time-utils.h" #include "nm-glib-aux/nm-ref-string.h" @@ -44,7 +45,7 @@ test_gpid (void) static void test_monotonic_timestamp (void) { - g_assert (nm_utils_get_monotonic_timestamp_s () > 0); + g_assert (nm_utils_get_monotonic_timestamp_sec () > 0); } /*****************************************************************************/ @@ -296,7 +297,7 @@ _strv_cmp_fuzz_input (const char *const*in, if (nmtst_get_rand_bool ()) { /* randomly swap the original and the clone. That means, out_s1 is either * the input argument (as-is) or the sementically equal clone. */ - NMTST_SWAP (*out_s1, *out_s2); + NM_SWAP (*out_s1, *out_s2); } if (nmtst_get_rand_bool ()) { /* randomly make s1 and s2 the same. This is for testing that @@ -554,6 +555,222 @@ test_nm_ref_string (void) /*****************************************************************************/ +static +NM_UTILS_STRING_TABLE_LOOKUP_DEFINE ( + _do_string_table_lookup, + int, + { ; }, + { return -1; }, + { "0", 0 }, + { "1", 1 }, + { "2", 2 }, + { "3", 3 }, +) + +static void +test_string_table_lookup (void) +{ + const char *const args[] = { NULL, "0", "1", "2", "3", "x", }; + int i; + + for (i = 0; i < G_N_ELEMENTS (args); i++) { + const char *needle = args[i]; + const int val2 = _nm_utils_ascii_str_to_int64 (needle, 10, 0, 100, -1); + int val; + + val = _do_string_table_lookup (needle); + g_assert_cmpint (val, ==, val2); + } +} + +/*****************************************************************************/ + +static void +test_nm_utils_get_next_realloc_size (void) +{ + static const struct { + gsize requested; + gsize reserved_true; + gsize reserved_false; + } test_data[] = { + { 0, 8, 8 }, + { 1, 8, 8 }, + { 8, 8, 8 }, + { 9, 16, 16 }, + { 16, 16, 16 }, + { 17, 32, 32 }, + { 32, 32, 32 }, + { 33, 40, 40 }, + { 40, 40, 40 }, + { 41, 104, 104 }, + { 104, 104, 104 }, + { 105, 232, 232 }, + { 232, 232, 232 }, + { 233, 488, 488 }, + { 488, 488, 488 }, + { 489, 1000, 1000 }, + { 1000, 1000, 1000 }, + { 1001, 2024, 2024 }, + { 2024, 2024, 2024 }, + { 2025, 4072, 4072 }, + { 4072, 4072, 4072 }, + { 4073, 8168, 8168 }, + { 8168, 8168, 8168 }, + { 8169, 12264, 16360 }, + { 12263, 12264, 16360 }, + { 12264, 12264, 16360 }, + { 12265, 16360, 16360 }, + { 16360, 16360, 16360 }, + { 16361, 20456, 32744 }, + { 20456, 20456, 32744 }, + { 20457, 24552, 32744 }, + { 24552, 24552, 32744 }, + { 24553, 28648, 32744 }, + { 28648, 28648, 32744 }, + { 28649, 32744, 32744 }, + { 32744, 32744, 32744 }, + { 32745, 36840, 65512 }, + { 36840, 36840, 65512 }, + { G_MAXSIZE - 0x1000u, G_MAXSIZE, G_MAXSIZE }, + { G_MAXSIZE - 25u, G_MAXSIZE, G_MAXSIZE }, + { G_MAXSIZE - 24u, G_MAXSIZE, G_MAXSIZE }, + { G_MAXSIZE - 1u, G_MAXSIZE, G_MAXSIZE }, + { G_MAXSIZE, G_MAXSIZE, G_MAXSIZE }, + { NM_UTILS_GET_NEXT_REALLOC_SIZE_104, NM_UTILS_GET_NEXT_REALLOC_SIZE_104, NM_UTILS_GET_NEXT_REALLOC_SIZE_104 }, + { NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, NM_UTILS_GET_NEXT_REALLOC_SIZE_1000 }, + }; + guint i; + + G_STATIC_ASSERT_EXPR (NM_UTILS_GET_NEXT_REALLOC_SIZE_104 == 104u); + G_STATIC_ASSERT_EXPR (NM_UTILS_GET_NEXT_REALLOC_SIZE_1000 == 1000u); + + for (i = 0; i < G_N_ELEMENTS (test_data) + 5000u; i++) { + gsize requested0; + + if (i < G_N_ELEMENTS (test_data)) + requested0 = test_data[i].requested; + else { + /* find some interesting random values for testing. */ + switch (nmtst_get_rand_uint32 () % 5) { + case 0: + requested0 = nmtst_get_rand_size (); + break; + case 1: + /* values close to G_MAXSIZE. */ + requested0 = G_MAXSIZE - (nmtst_get_rand_uint32 () % 12000u); + break; + case 2: + /* values around G_MAXSIZE/2. */ + requested0 = (G_MAXSIZE / 2u) + 6000u - (nmtst_get_rand_uint32 () % 12000u); + break; + case 3: + /* values around powers of 2. */ + requested0 = (((gsize) 1) << (nmtst_get_rand_uint32 () % (sizeof (gsize) * 8u))) + 6000u - (nmtst_get_rand_uint32 () % 12000u); + break; + case 4: + /* values around 4k borders. */ + requested0 = (nmtst_get_rand_size () & ~((gsize) 0xFFFu)) + 30u - (nmtst_get_rand_uint32 () % 60u); + break; + default: g_assert_not_reached (); + } + } + + { + const gsize requested = requested0; + const gsize reserved_true = nm_utils_get_next_realloc_size (TRUE, requested); + const gsize reserved_false = nm_utils_get_next_realloc_size (FALSE, requested); + + g_assert_cmpuint (reserved_true, >, 0); + g_assert_cmpuint (reserved_false, >, 0); + g_assert_cmpuint (reserved_true, >=, requested); + g_assert_cmpuint (reserved_false, >=, requested); + g_assert_cmpuint (reserved_false, >=, reserved_true); + + if (i < G_N_ELEMENTS (test_data)) { + g_assert_cmpuint (reserved_true, ==, test_data[i].reserved_true); + g_assert_cmpuint (reserved_false, ==, test_data[i].reserved_false); + } + + /* reserved_false is generally the next power of two - 24. */ + if (reserved_false == G_MAXSIZE) + g_assert_cmpuint (requested, >, G_MAXSIZE / 2u - 24u); + else { + g_assert_cmpuint (reserved_false, <=, G_MAXSIZE - 24u); + if (reserved_false >= 40) { + const gsize _pow2 = reserved_false + 24u; + + /* reserved_false must always be a power of two minus 24. */ + g_assert_cmpuint (_pow2, >=, 64u); + g_assert_cmpuint (_pow2, >, requested); + g_assert (nm_utils_is_power_of_two (_pow2)); + + /* but _pow2/2 must also be smaller than what we requested. */ + g_assert_cmpuint (_pow2 / 2u - 24u, <, requested); + } else { + /* smaller values are hard-coded. */ + } + } + + /* reserved_true is generally the next 4k border - 24. */ + if (reserved_true == G_MAXSIZE) + g_assert_cmpuint (requested, >, G_MAXSIZE - 0x1000u - 24u); + else { + g_assert_cmpuint (reserved_true, <=, G_MAXSIZE - 24u); + if (reserved_true > 8168u) { + const gsize page_border = reserved_true + 24u; + + /* reserved_true must always be aligned to 4k (minus 24). */ + g_assert_cmpuint (page_border % 0x1000u, ==, 0); + if (requested > 0x1000u - 24u) { + /* page_border not be more than 4k above requested. */ + g_assert_cmpuint (page_border, >=, 0x1000u - 24u); + g_assert_cmpuint (page_border - 0x1000u - 24u, <, requested); + } + } else { + /* for smaller sizes, reserved_true and reserved_false are the same. */ + g_assert_cmpuint (reserved_true, ==, reserved_false); + } + } + + } + } +} + +/*****************************************************************************/ + +static void +test_nm_str_buf (void) +{ + guint i_run; + + for (i_run = 0; TRUE; i_run++) { + nm_auto_str_buf NMStrBuf strbuf = { }; + nm_auto_free_gstring GString *gstr = NULL; + int i, j, k; + int c; + + nm_str_buf_init (&strbuf, + nmtst_get_rand_uint32 () % 200u + 1u, + nmtst_get_rand_bool ()); + + if (i_run < 1000) { + c = nmtst_get_rand_word_length (NULL); + for (i = 0; i < c; i++) + nm_str_buf_append_c (&strbuf, '0' + (i % 10)); + gstr = g_string_new (nm_str_buf_get_str (&strbuf)); + j = nmtst_get_rand_uint32 () % (strbuf.len + 1); + k = nmtst_get_rand_uint32 () % (strbuf.len - j + 2) - 1; + + nm_str_buf_erase (&strbuf, j, k, nmtst_get_rand_bool ()); + g_string_erase (gstr, j, k); + g_assert_cmpstr (gstr->str, ==, nm_str_buf_get_str (&strbuf)); + } else + return; + } +} + +/*****************************************************************************/ + NMTST_DEFINE (); int main (int argc, char **argv) @@ -572,6 +789,9 @@ int main (int argc, char **argv) g_test_add_func ("/general/test_strstrip_avoid_copy", test_strstrip_avoid_copy); g_test_add_func ("/general/test_nm_utils_bin2hexstr", test_nm_utils_bin2hexstr); g_test_add_func ("/general/test_nm_ref_string", test_nm_ref_string); + g_test_add_func ("/general/test_string_table_lookup", test_string_table_lookup); + g_test_add_func ("/general/test_nm_utils_get_next_realloc_size", test_nm_utils_get_next_realloc_size); + g_test_add_func ("/general/test_nm_str_buf", test_nm_str_buf); return g_test_run (); } diff --git a/shared/nm-keyfile/nm-keyfile-internal.h b/shared/nm-keyfile/nm-keyfile-internal.h new file mode 100644 index 00000000..42003a5f --- /dev/null +++ b/shared/nm-keyfile/nm-keyfile-internal.h @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2015 Red Hat, Inc. + */ + +#ifndef __NM_KEYFILE_INTERNAL_H__ +#define __NM_KEYFILE_INTERNAL_H__ + +#if !((NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL) +#error Cannot use this header. +#endif + +#include <sys/types.h> + +#include "nm-connection.h" +#include "nm-setting-8021x.h" + +#include "nm-core-internal.h" +#include "nm-meta-setting.h" + +/*****************************************************************************/ + +#define NM_KEYFILE_CERT_SCHEME_PREFIX_PATH "file://" +#define NM_KEYFILE_CERT_SCHEME_PREFIX_PKCS11 "pkcs11:" +#define NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB "data:;base64," + +char *nm_keyfile_detect_unqualified_path_scheme (const char *base_dir, + gconstpointer pdata, + gsize data_len, + gboolean consider_exists, + gboolean *out_exists); + +typedef enum { + NM_KEYFILE_READ_TYPE_WARN = 1, +} NMKeyfileReadType; + +/** + * NMKeyfileReadHandler: + * + * Hook to nm_keyfile_read(). The user might fail the reading by setting + * @error. + * + * Returns: should return TRUE, if the reading was handled. Otherwise, + * a default action will be performed that depends on the @type. + * For %NM_KEYFILE_READ_TYPE_WARN type, the default action is doing nothing. + */ +typedef gboolean (*NMKeyfileReadHandler) (GKeyFile *keyfile, + NMConnection *connection, + NMKeyfileReadType type, + void *type_data, + void *user_data, + GError **error); + +typedef enum { + NM_KEYFILE_WARN_SEVERITY_DEBUG = 1000, + NM_KEYFILE_WARN_SEVERITY_INFO = 2000, + NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE = 2901, + NM_KEYFILE_WARN_SEVERITY_WARN = 3000, +} NMKeyfileWarnSeverity; + +/** + * NMKeyfileReadTypeDataWarn: + * + * this struct is passed as @type_data for the @NMKeyfileReadHandler of + * type %NM_KEYFILE_READ_TYPE_WARN. + */ +typedef struct { + /* might be %NULL, if the warning is not about a group. */ + const char *group; + + /* might be %NULL, if the warning is not about a setting. */ + NMSetting *setting; + + /* might be %NULL, if the warning is not about a property. */ + const char *property_name; + + NMKeyfileWarnSeverity severity; + const char *message; +} NMKeyfileReadTypeDataWarn; + +NMConnection *nm_keyfile_read (GKeyFile *keyfile, + const char *base_dir, + NMKeyfileReadHandler handler, + void *user_data, + GError **error); + +gboolean nm_keyfile_read_ensure_id (NMConnection *connection, + const char *fallback_id); + +gboolean nm_keyfile_read_ensure_uuid (NMConnection *connection, + const char *fallback_uuid_seed); + +/*****************************************************************************/ + +typedef enum { + NM_KEYFILE_WRITE_TYPE_CERT = 1, +} NMKeyfileWriteType; + +/** + * NMKeyfileWriteHandler: + * + * This is a hook to tweak the serialization. + * + * Handler for certain properties or events that are not entirely contained + * within the keyfile or that might be serialized differently. The @type and + * @type_data arguments tell which kind of argument we have at hand. + * + * Currently only the type %NM_KEYFILE_WRITE_TYPE_CERT is supported, which provides + * @type_data as %NMKeyfileWriteTypeDataCert. However, this handler should be generic enough + * to support other types as well. + * + * This don't have to be only "properties". For example, nm_keyfile_read() uses + * a similar handler to push warnings to the caller. + * + * If the handler raises an error, it should set the @error value. This causes + * the an overall failure. + * + * Returns: whether the issue was handled. If the type was unhandled, + * a default action will be performed. This might be raise an error, + * do some fallback parsing, or do nothing. + */ +typedef gboolean (*NMKeyfileWriteHandler) (NMConnection *connection, + GKeyFile *keyfile, + NMKeyfileWriteType type, + void *type_data, + void *user_data, + GError **error); + +/** + * NMKeyfileWriteTypeDataCert: + * + * this struct is passed as @type_data for the @NMKeyfileWriteHandler of + * type %NM_KEYFILE_WRITE_TYPE_CERT. + */ +typedef struct { + const NMSetting8021xSchemeVtable *vtable; + NMSetting8021x *setting; +} NMKeyfileWriteTypeDataCert; + +GKeyFile *nm_keyfile_write (NMConnection *connection, + NMKeyfileWriteHandler handler, + void *user_data, + GError **error); + +/*****************************************************************************/ + +char *nm_keyfile_plugin_kf_get_string (GKeyFile *kf, const char *group, const char *key, GError **error); +void nm_keyfile_plugin_kf_set_string (GKeyFile *kf, const char *group, const char *key, const char *value); + +int nm_key_file_get_boolean (GKeyFile *kf, const char *group, const char *key, int default_value); + +void _nm_keyfile_copy (GKeyFile *dst, GKeyFile *src); +gboolean _nm_keyfile_a_contains_all_in_b (GKeyFile *kf_a, GKeyFile *kf_b); +gboolean _nm_keyfile_equals (GKeyFile *kf_a, GKeyFile *kf_b, gboolean consider_order); +gboolean _nm_keyfile_has_values (GKeyFile *keyfile); + +/*****************************************************************************/ + +#define NM_KEYFILE_GROUP_NMMETA ".nmmeta" +#define NM_KEYFILE_KEY_NMMETA_NM_GENERATED "nm-generated" +#define NM_KEYFILE_KEY_NMMETA_VOLATILE "volatile" +#define NM_KEYFILE_KEY_NMMETA_SHADOWED_STORAGE "shadowed-storage" +#define NM_KEYFILE_KEY_NMMETA_SHADOWED_OWNED "shadowed-owned" + +#define NM_KEYFILE_PATH_NAME_LIB NMLIBDIR "/system-connections" +#define NM_KEYFILE_PATH_NAME_ETC_DEFAULT NMCONFDIR "/system-connections" +#define NM_KEYFILE_PATH_NAME_RUN NMRUNDIR "/system-connections" + +#define NM_KEYFILE_PATH_SUFFIX_NMCONNECTION ".nmconnection" + +#define NM_KEYFILE_PATH_SUFFIX_NMMETA ".nmmeta" + +#define NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL "/dev/null" + +gboolean nm_keyfile_utils_ignore_filename (const char *filename, gboolean require_extension); + +char *nm_keyfile_utils_create_filename (const char *filename, gboolean with_extension); + +#endif /* __NM_KEYFILE_INTERNAL_H__ */ diff --git a/shared/nm-keyfile/nm-keyfile-utils.c b/shared/nm-keyfile/nm-keyfile-utils.c new file mode 100644 index 00000000..e18db20d --- /dev/null +++ b/shared/nm-keyfile/nm-keyfile-utils.c @@ -0,0 +1,636 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2010 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-keyfile-utils.h" + +#include <stdlib.h> + +#include "nm-keyfile-internal.h" +#include "nm-setting-wired.h" +#include "nm-setting-wireless.h" +#include "nm-setting-wireless-security.h" + +/*****************************************************************************/ + +/** + * nm_key_file_get_boolean: + * @kf: the #GKeyFile + * @group: the group + * @key: the key + * @default_value: the default value if the value is set or not parsable as a boolean. + * + * Replacement for g_key_file_get_boolean() (which uses g_key_file_parse_value_as_boolean()). + * g_key_file_get_boolean() seems odd to me, because it accepts trailing ASCII whitespace, + * but not leading. + * This uses _nm_utils_ascii_str_to_bool(), which accepts trailing and leading whitespace, + * case-insensitive words, and also strings like "on" and "off". + * _nm_utils_ascii_str_to_bool() is our way to parse booleans from string, and we should + * use that one consistently. + * + * Also, it doesn't have g_key_file_get_boolean()'s odd API to require an error argument + * to detect parsing failures. + * + * Returns: either %TRUE or %FALSE if the key exists and is parsable as a boolean. + * Otherwise, @default_value. Sets errno to ENODATA, EINVAL or 0, depending on whether + * the key exists, whether the value is invalid, or success. + */ +int +nm_key_file_get_boolean (GKeyFile *kf, const char *group, const char *key, int default_value) +{ + int v; + gs_free char *value = NULL; + + value = g_key_file_get_value (kf, group, key, NULL); + + if (!value) { + errno = ENODATA; + return default_value; + } + v = _nm_utils_ascii_str_to_bool (value, -1); + if (v != -1) { + errno = 0; + return v; + } + errno = EINVAL; + return default_value; +} + +/*****************************************************************************/ + +typedef struct { + const char *setting; + const char *alias; +} SettingAlias; + +static const SettingAlias alias_list[] = { + { NM_SETTING_WIRED_SETTING_NAME, "ethernet" }, + { NM_SETTING_WIRELESS_SETTING_NAME, "wifi" }, + { NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, "wifi-security" }, +}; + +const char * +nm_keyfile_plugin_get_alias_for_setting_name (const char *setting_name) +{ + guint i; + + g_return_val_if_fail (setting_name != NULL, NULL); + + for (i = 0; i < G_N_ELEMENTS (alias_list); i++) { + if (nm_streq (setting_name, alias_list[i].setting)) + return alias_list[i].alias; + } + return NULL; +} + +const char * +nm_keyfile_plugin_get_setting_name_for_alias (const char *alias) +{ + guint i; + + g_return_val_if_fail (alias != NULL, NULL); + + for (i = 0; i < G_N_ELEMENTS (alias_list); i++) { + if (nm_streq (alias, alias_list[i].alias)) + return alias_list[i].setting; + } + return NULL; +} + +/*****************************************************************************/ + +/* List helpers */ +#define DEFINE_KF_LIST_WRAPPER(stype, get_ctype, set_ctype) \ +get_ctype \ +nm_keyfile_plugin_kf_get_##stype##_list (GKeyFile *kf, \ + const char *group, \ + const char *key, \ + gsize *out_length, \ + GError **error) \ +{ \ + get_ctype list; \ + const char *alias; \ + GError *local = NULL; \ + gsize l; \ + \ + list = g_key_file_get_##stype##_list (kf, group, key, &l, &local); \ + if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { \ + alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \ + if (alias) { \ + g_clear_error (&local); \ + list = g_key_file_get_##stype##_list (kf, alias, key, &l, &local); \ + } \ + } \ + nm_assert ((!local) != (!list)); \ + if (local) \ + g_propagate_error (error, local); \ + if (!list) \ + l = 0; \ + NM_SET_OUT (out_length, l); \ + return list; \ +} \ + \ +void \ +nm_keyfile_plugin_kf_set_##stype##_list (GKeyFile *kf, \ + const char *group, \ + const char *key, \ + set_ctype list[], \ + gsize length) \ +{ \ + const char *alias; \ + \ + alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \ + g_key_file_set_##stype##_list (kf, alias ?: group, key, list, length); \ +} + +DEFINE_KF_LIST_WRAPPER(integer, int*, int); +DEFINE_KF_LIST_WRAPPER(string, char **, const char* const); + +void +nm_keyfile_plugin_kf_set_integer_list_uint8 (GKeyFile *kf, + const char *group, + const char *key, + const guint8 *data, + gsize length) +{ + gsize i; + gsize l = length * 4 + 2; + gs_free char *value = g_malloc (l); + char *s = value; + + g_return_if_fail (kf); + g_return_if_fail (!length || data); + g_return_if_fail (group && group[0]); + g_return_if_fail (key && key[0]); + + value[0] = '\0'; + for (i = 0; i < length; i++) + nm_utils_strbuf_append (&s, &l, "%d;", (int) data[i]); + nm_assert (l > 0); + nm_keyfile_plugin_kf_set_value (kf, group, key, value); +} + +/* Single value helpers */ +#define DEFINE_KF_WRAPPER(stype, get_ctype, set_ctype) \ +get_ctype \ +nm_keyfile_plugin_kf_get_##stype (GKeyFile *kf, \ + const char *group, \ + const char *key, \ + GError **error) \ +{ \ + get_ctype val; \ + const char *alias; \ + GError *local = NULL; \ + \ + val = g_key_file_get_##stype (kf, group, key, &local); \ + if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { \ + alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \ + if (alias) { \ + g_clear_error (&local); \ + val = g_key_file_get_##stype (kf, alias, key, &local); \ + } \ + } \ + if (local) \ + g_propagate_error (error, local); \ + return val; \ +} \ + \ +void \ +nm_keyfile_plugin_kf_set_##stype (GKeyFile *kf, \ + const char *group, \ + const char *key, \ + set_ctype value) \ +{ \ + const char *alias; \ + \ + alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \ + g_key_file_set_##stype (kf, alias ?: group, key, value); \ +} + +DEFINE_KF_WRAPPER(string, char*, const char*); +DEFINE_KF_WRAPPER(boolean, gboolean, gboolean); +DEFINE_KF_WRAPPER(value, char*, const char*); + +gint64 +nm_keyfile_plugin_kf_get_int64 (GKeyFile *kf, + const char *group, + const char *key, + guint base, + gint64 min, + gint64 max, + gint64 fallback, + GError **error) +{ + gs_free char *s = NULL; + int errsv; + gint64 v; + + s = nm_keyfile_plugin_kf_get_value (kf, group, key, error); + if (!s) { + errno = ENODATA; + return fallback; + } + + v = _nm_utils_ascii_str_to_int64 (s, base, min, max, fallback); + errsv = errno; + if ( errsv != 0 + && error) { + g_set_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, + _("value is not an integer in range [%lld, %lld]"), + (long long) min, (long long) max); + errno = errsv; + } + return v; +} + +char ** +nm_keyfile_plugin_kf_get_keys (GKeyFile *kf, + const char *group, + gsize *out_length, + GError **error) +{ + char **keys; + const char *alias; + GError *local = NULL; + gsize l; + + keys = g_key_file_get_keys (kf, group, &l, &local); + if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { + alias = nm_keyfile_plugin_get_alias_for_setting_name (group); + if (alias) { + g_clear_error (&local); + keys = g_key_file_get_keys (kf, alias, &l, error ? &local : NULL); + } + } + nm_assert ((!local) != (!keys)); + if (!keys) + l = 0; + nm_assert (l == NM_PTRARRAY_LEN (keys)); + NM_SET_OUT (out_length, l); + if (local) + g_propagate_error (error, local); + return keys; +} + +gboolean +nm_keyfile_plugin_kf_has_key (GKeyFile *kf, + const char *group, + const char *key, + GError **error) +{ + gboolean has; + const char *alias; + GError *local = NULL; + + has = g_key_file_has_key (kf, group, key, &local); + if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { + alias = nm_keyfile_plugin_get_alias_for_setting_name (group); + if (alias) { + g_clear_error (&local); + has = g_key_file_has_key (kf, alias, key, &local); + } + } + if (local) + g_propagate_error (error, local); + return has; +} + +/*****************************************************************************/ + +void +_nm_keyfile_copy (GKeyFile *dst, GKeyFile *src) +{ + gs_strfreev char **groups = NULL; + guint g, k; + + groups = g_key_file_get_groups (src, NULL); + for (g = 0; groups && groups[g]; g++) { + const char *group = groups[g]; + gs_strfreev char **keys = NULL; + + keys = g_key_file_get_keys (src, group, NULL, NULL); + if (!keys) + continue; + + for (k = 0; keys[k]; k++) { + const char *key = keys[k]; + gs_free char *value = NULL; + + value = g_key_file_get_value (src, group, key, NULL); + if (value) + g_key_file_set_value (dst, group, key, value); + else + g_key_file_remove_key (dst, group, key, NULL); + } + } +} + +/*****************************************************************************/ + +gboolean +_nm_keyfile_a_contains_all_in_b (GKeyFile *kf_a, GKeyFile *kf_b) +{ + gs_strfreev char **groups = NULL; + guint i, j; + + if (kf_a == kf_b) + return TRUE; + if (!kf_a || !kf_b) + return FALSE; + + groups = g_key_file_get_groups (kf_a, NULL); + for (i = 0; groups && groups[i]; i++) { + gs_strfreev char **keys = NULL; + + keys = g_key_file_get_keys (kf_a, groups[i], NULL, NULL); + if (!keys) + continue; + + for (j = 0; keys[j]; j++) { + gs_free char *key_a = g_key_file_get_value (kf_a, groups[i], keys[j], NULL); + gs_free char *key_b = g_key_file_get_value (kf_b, groups[i], keys[j], NULL); + + if (g_strcmp0 (key_a, key_b) != 0) + return FALSE; + } + } + return TRUE; +} + +static gboolean +_nm_keyfile_equals_ordered (GKeyFile *kf_a, GKeyFile *kf_b) +{ + gs_strfreev char **groups = NULL; + gs_strfreev char **groups_b = NULL; + guint i, j; + + if (kf_a == kf_b) + return TRUE; + if (!kf_a || !kf_b) + return FALSE; + + groups = g_key_file_get_groups (kf_a, NULL); + groups_b = g_key_file_get_groups (kf_b, NULL); + if (!groups && !groups_b) + return TRUE; + if (!groups || !groups_b) + return FALSE; + for (i = 0; groups[i] && groups_b[i] && !strcmp (groups[i], groups_b[i]); i++) + ; + if (groups[i] || groups_b[i]) + return FALSE; + + for (i = 0; groups[i]; i++) { + gs_strfreev char **keys = NULL; + gs_strfreev char **keys_b = NULL; + + keys = g_key_file_get_keys (kf_a, groups[i], NULL, NULL); + keys_b = g_key_file_get_keys (kf_b, groups[i], NULL, NULL); + + if ((!keys) != (!keys_b)) + return FALSE; + if (!keys) + continue; + + for (j = 0; keys[j] && keys_b[j] && !strcmp (keys[j], keys_b[j]); j++) + ; + if (keys[j] || keys_b[j]) + return FALSE; + + for (j = 0; keys[j]; j++) { + gs_free char *key_a = g_key_file_get_value (kf_a, groups[i], keys[j], NULL); + gs_free char *key_b = g_key_file_get_value (kf_b, groups[i], keys[j], NULL); + + if (g_strcmp0 (key_a, key_b) != 0) + return FALSE; + } + } + return TRUE; +} + +gboolean +_nm_keyfile_equals (GKeyFile *kf_a, GKeyFile *kf_b, gboolean consider_order) +{ + if (!consider_order) { + return _nm_keyfile_a_contains_all_in_b (kf_a, kf_b) + && _nm_keyfile_a_contains_all_in_b (kf_b, kf_a); + } else { + return _nm_keyfile_equals_ordered (kf_a, kf_b); + } +} + +gboolean +_nm_keyfile_has_values (GKeyFile *keyfile) +{ + gs_strfreev char **groups = NULL; + + g_return_val_if_fail (keyfile, FALSE); + + groups = g_key_file_get_groups (keyfile, NULL); + return groups && groups[0]; +} + +/*****************************************************************************/ + +static const char * +_keyfile_key_encode (const char *name, + char **out_to_free) +{ + gsize len, i; + GString *str; + + nm_assert (name); + nm_assert (out_to_free && !*out_to_free); + + /* See g_key_file_is_key_name(). + * + * GKeyFile allows all UTF-8 characters (even non-well formed sequences), + * except: + * - no empty keys + * - no leading/trailing ' ' + * - no '=', '[', ']' + * + * We do something more strict here. All non-ASCII characters, all non-printable + * characters, and all invalid characters are escaped with "\\XX". + * + * We don't escape \\, unless it is followed by two hex digits. + */ + + if (!name[0]) { + /* empty keys are are backslash encoded. Note that usually + * \\00 is not a valid encode, the only exception is the empty + * word. */ + return "\\00"; + } + + /* find the first character that needs escaping. */ + i = 0; + if (name[0] != ' ') { + for (;; i++) { + const guchar ch = (guchar) name[i]; + + if (ch == '\0') + return name; + + if ( ch < 0x20 + || ch >= 127 + || NM_IN_SET (ch, '=', '[', ']') + || ( ch == '\\' + && g_ascii_isxdigit (name[i + 1]) + && g_ascii_isxdigit (name[i + 2])) + || ( ch == ' ' + && name[i + 1] == '\0')) + break; + } + } else if (name[1] == '\0') + return "\\20"; + + len = i + strlen (&name[i]); + nm_assert (len == strlen (name)); + str = g_string_sized_new (len + 15); + + if (name[0] == ' ') { + nm_assert (i == 0); + g_string_append (str, "\\20"); + i = 1; + } else + g_string_append_len (str, name, i); + + for (;; i++) { + const guchar ch = (guchar) name[i]; + + if (ch == '\0') + break; + + if ( ch < 0x20 + || ch >= 127 + || NM_IN_SET (ch, '=', '[', ']') + || ( ch == '\\' + && g_ascii_isxdigit (name[i + 1]) + && g_ascii_isxdigit (name[i + 2])) + || ( ch == ' ' + && name[i + 1] == '\0')) + g_string_append_printf (str, "\\%02X", ch); + else + g_string_append_c (str, (char) ch); + } + + return (*out_to_free = g_string_free (str, FALSE)); +} + +static const char * +_keyfile_key_decode (const char *key, + char **out_to_free) +{ + gsize i, len; + GString *str; + + nm_assert (key); + nm_assert (out_to_free && !*out_to_free); + + if (!key[0]) + return ""; + + for (i = 0; TRUE; i++) { + const char ch = key[i]; + + if (ch == '\0') + return key; + if ( ch == '\\' + && g_ascii_isxdigit (key[i + 1]) + && g_ascii_isxdigit (key[i + 2])) + break; + } + + len = i + strlen (&key[i]); + + if ( len == 3 + && nm_streq (key, "\\00")) + return ""; + + nm_assert (len == strlen (key)); + str = g_string_sized_new (len + 3); + + g_string_append_len (str, key, i); + for (;;) { + const char ch = key[i]; + char ch1, ch2; + unsigned v; + + if (ch == '\0') + break; + + if ( ch == '\\' + && g_ascii_isxdigit ((ch1 = key[i + 1])) + && g_ascii_isxdigit ((ch2 = key[i + 2]))) { + v = (g_ascii_xdigit_value (ch1) << 4) + g_ascii_xdigit_value (ch2); + if (v != 0) { + g_string_append_c (str, (char) v); + i += 3; + continue; + } + } + g_string_append_c (str, ch); + i++; + } + + return (*out_to_free = g_string_free (str, FALSE)); +} + +/*****************************************************************************/ + +const char * +nm_keyfile_key_encode (const char *name, + char **out_to_free) +{ + const char *key; + + key = _keyfile_key_encode (name, out_to_free); +#if NM_MORE_ASSERTS > 5 + nm_assert (key); + nm_assert (!*out_to_free || key == *out_to_free); + nm_assert (!*out_to_free || !nm_streq0 (name, key)); + { + gs_free char *to_free2 = NULL; + const char *name2; + + name2 = _keyfile_key_decode (key, &to_free2); + /* name2, the result of encode()+decode() is identical to name. + * That is because + * - encode() is a injective function. + * - decode() is a surjective function, however for output + * values of encode() is behaves injective too. */ + nm_assert (nm_streq0 (name2, name)); + } +#endif + return key; +} + +const char * +nm_keyfile_key_decode (const char *key, + char **out_to_free) +{ + const char *name; + + name = _keyfile_key_decode (key, out_to_free); +#if NM_MORE_ASSERTS > 5 + nm_assert (name); + nm_assert (!*out_to_free || name == *out_to_free); + { + gs_free char *to_free2 = NULL; + const char *key2; + + key2 = _keyfile_key_encode (name, &to_free2); + /* key2, the result of decode+encode may not be idential + * to the original key. That is, decode() is a surjective + * function mapping different keys to the same name. + * However, decode() behaves injective for input that + * are valid output of encode(). */ + nm_assert (key2); + } +#endif + return name; +} diff --git a/shared/nm-keyfile/nm-keyfile-utils.h b/shared/nm-keyfile/nm-keyfile-utils.h new file mode 100644 index 00000000..a351262e --- /dev/null +++ b/shared/nm-keyfile/nm-keyfile-utils.h @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2010 - 2015 Red Hat, Inc. + */ + +#ifndef __NM_KEYFILE_UTILS_H__ +#define __NM_KEYFILE_UTILS_H__ + +#if !((NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL) +#error Cannot use this header. +#endif + +#define NM_KEYFILE_GROUP_VPN_SECRETS "vpn-secrets" +#define NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER "wireguard-peer." + +const char *nm_keyfile_plugin_get_alias_for_setting_name (const char *setting_name); + +const char *nm_keyfile_plugin_get_setting_name_for_alias (const char *alias); + +/*****************************************************************************/ + +/* List helpers */ +#define DEFINE_KF_LIST_WRAPPER_PROTO(stype, get_ctype, set_ctype) \ +get_ctype nm_keyfile_plugin_kf_get_##stype##_list (GKeyFile *kf, \ + const char *group, \ + const char *key, \ + gsize *out_length, \ + GError **error); \ +\ +void nm_keyfile_plugin_kf_set_##stype##_list (GKeyFile *kf, \ + const char *group, \ + const char *key, \ + set_ctype list[], \ + gsize length); +DEFINE_KF_LIST_WRAPPER_PROTO(integer, int*, int) +DEFINE_KF_LIST_WRAPPER_PROTO(string, char**, const char* const) + +void nm_keyfile_plugin_kf_set_integer_list_uint8 (GKeyFile *kf, + const char *group, + const char *key, + const guint8 *list, + gsize length); + +/* Single-value helpers */ +#define DEFINE_KF_WRAPPER_PROTO(stype, get_ctype, set_ctype) \ +get_ctype nm_keyfile_plugin_kf_get_##stype (GKeyFile *kf, \ + const char *group, \ + const char *key, \ + GError **error); \ +\ +void nm_keyfile_plugin_kf_set_##stype (GKeyFile *kf, \ + const char *group, \ + const char *key, \ + set_ctype value); +DEFINE_KF_WRAPPER_PROTO(string, char*, const char*) +DEFINE_KF_WRAPPER_PROTO(boolean, gboolean, gboolean) +DEFINE_KF_WRAPPER_PROTO(value, char*, const char*) + +/* Misc */ +gint64 nm_keyfile_plugin_kf_get_int64 (GKeyFile *kf, + const char *group, + const char *key, + guint base, + gint64 min, + gint64 max, + gint64 fallback, + GError **error); + +char ** nm_keyfile_plugin_kf_get_keys (GKeyFile *kf, + const char *group, + gsize *out_length, + GError **error); + +gboolean nm_keyfile_plugin_kf_has_key (GKeyFile *kf, + const char *group, + const char *key, + GError **error); + +const char *nm_keyfile_key_encode (const char *name, + char **out_to_free); + +const char *nm_keyfile_key_decode (const char *key, + char **out_to_free); + +#endif /* __NM_KEYFILE_UTILS_H__ */ diff --git a/shared/nm-keyfile/nm-keyfile.c b/shared/nm-keyfile/nm-keyfile.c new file mode 100644 index 00000000..15485747 --- /dev/null +++ b/shared/nm-keyfile/nm-keyfile.c @@ -0,0 +1,4089 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2008 - 2009 Novell, Inc. + * Copyright (C) 2008 - 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-keyfile-internal.h" + +#include <stdlib.h> +#include <stdio.h> +#include <sys/stat.h> +#include <unistd.h> +#include <sys/types.h> +#include <arpa/inet.h> +#include <linux/pkt_sched.h> + +#include "nm-glib-aux/nm-secret-utils.h" +#include "systemd/nm-sd-utils-shared.h" +#include "nm-libnm-core-intern/nm-common-macros.h" +#include "nm-core-internal.h" +#include "nm-keyfile-utils.h" + +#include "nm-setting-user.h" + +/*****************************************************************************/ + +typedef struct _ParseInfoProperty ParseInfoProperty; + +typedef struct { + NMConnection *connection; + GKeyFile *keyfile; + const char *base_dir; + NMKeyfileReadHandler handler; + void *user_data; + GError *error; + const char *group; + NMSetting *setting; +} KeyfileReaderInfo; + +typedef struct { + NMConnection *connection; + GKeyFile *keyfile; + GError *error; + NMKeyfileWriteHandler handler; + void *user_data; +} KeyfileWriterInfo; + +/*****************************************************************************/ + +static void +_handle_warn (KeyfileReaderInfo *info, + const char *property_name, + NMKeyfileWarnSeverity severity, + char *message) +{ + NMKeyfileReadTypeDataWarn type_data = { + .group = info->group, + .setting = info->setting, + .property_name = property_name, + .severity = severity, + .message = message, + }; + + info->handler (info->keyfile, + info->connection, + NM_KEYFILE_READ_TYPE_WARN, + &type_data, + info->user_data, + &info->error); + g_free (message); +} +#define handle_warn(arg_info, arg_property_name, arg_severity, ...) \ + ({ \ + KeyfileReaderInfo *_info = (arg_info); \ + \ + if (_info->handler) { \ + _handle_warn (_info, (arg_property_name), (arg_severity), \ + g_strdup_printf (__VA_ARGS__)); \ + } \ + _info->error == NULL; \ + }) + +/*****************************************************************************/ + +static gboolean +_secret_flags_persist_secret (NMSettingSecretFlags flags) +{ + return flags == NM_SETTING_SECRET_FLAG_NONE; +} + +/*****************************************************************************/ +/* Some setting properties also contain setting names, such as + * NMSettingConnection's 'type' property (which specifies the base type of the + * connection, e.g. ethernet or wifi) or 'slave-type' (specifies type of slave + * connection, e.g. bond or bridge). This function handles translating those + * properties' values to the real setting name if they are an alias. + */ +static void +setting_alias_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char *setting_name = nm_setting_get_name (setting); + const char *key_setting_name; + gs_free char *s = NULL; + + s = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL); + if (!s) + return; + + key_setting_name = nm_keyfile_plugin_get_setting_name_for_alias (s); + g_object_set (G_OBJECT (setting), + key, + key_setting_name ?: s, + NULL); +} + +static void +sriov_vfs_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char *setting_name = nm_setting_get_name (setting); + gs_unref_ptrarray GPtrArray *vfs = NULL; + gs_strfreev char **keys = NULL; + gsize n_keys = 0; + int i; + + keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, setting_name, &n_keys, NULL); + if (n_keys == 0) + return; + + vfs = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_sriov_vf_unref); + + for (i = 0; i < n_keys; i++) { + gs_free char *value = NULL; + NMSriovVF *vf; + const char *rest; + + if (!g_str_has_prefix (keys[i], "vf.")) + continue; + + rest = &keys[i][3]; + + if (!NM_STRCHAR_ALL (rest, ch, g_ascii_isdigit (ch))) + continue; + + value = nm_keyfile_plugin_kf_get_string (info->keyfile, + setting_name, + keys[i], + NULL); + + vf = _nm_utils_sriov_vf_from_strparts (rest, value, TRUE, NULL); + if (vf) + g_ptr_array_add (vfs, vf); + } + + g_object_set (G_OBJECT (setting), + key, vfs, + NULL); +} + +static void +read_array_of_uint (GKeyFile *file, + NMSetting *setting, + const char *key) +{ + gs_unref_array GArray *array = NULL; + gsize length; + gsize i; + gs_free int *tmp = NULL; + + tmp = nm_keyfile_plugin_kf_get_integer_list (file, nm_setting_get_name (setting), key, &length, NULL); + if (length > G_MAXUINT) + return; + + array = g_array_sized_new (FALSE, FALSE, sizeof (guint), length); + + for (i = 0; i < length; i++) { + if (tmp[i] < 0) + return; + g_array_append_val (array, tmp[i]); + } + + g_object_set (setting, key, array, NULL); +} + +static gboolean +get_one_int (KeyfileReaderInfo *info, const char *property_name, const char *str, guint32 max_val, guint32 *out) +{ + gint64 tmp; + + g_return_val_if_fail (!info == !property_name, FALSE); + + if (!str || !str[0]) { + if (property_name) + handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring missing number")); + return FALSE; + } + + tmp = _nm_utils_ascii_str_to_int64 (str, 10, 0, max_val, -1); + if (tmp == -1) { + if (property_name) { + handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid number '%s'"), + str); + } + return FALSE; + } + + *out = (guint32) tmp; + return TRUE; +} + +static gpointer +build_address (KeyfileReaderInfo *info, int family, const char *address_str, guint32 plen, const char *property_name) +{ + NMIPAddress *addr; + GError *error = NULL; + + g_return_val_if_fail (address_str, NULL); + + addr = nm_ip_address_new (family, address_str, plen, &error); + if (!addr) { + handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid %s address: %s"), + family == AF_INET ? "IPv4" : "IPv6", error->message); + g_error_free (error); + } + + return addr; +} + +static gpointer +build_route (KeyfileReaderInfo *info, + const char *property_name, + int family, + const char *dest_str, + guint32 plen, + const char *gateway_str, + const char *metric_str) +{ + NMIPRoute *route; + guint32 u32; + gint64 metric = -1; + GError *error = NULL; + + g_return_val_if_fail (dest_str, NULL); + + /* Next hop */ + if (gateway_str && gateway_str[0]) { + if (!nm_utils_ipaddr_is_valid (family, gateway_str)) { + /* Try workaround for routes written by broken keyfile writer. + * Due to bug bgo#719851, an older version of writer would have + * written "a:b:c:d::/plen,metric" if the gateway was ::, instead + * of "a:b:c:d::/plen,,metric" or "a:b:c:d::/plen,::,metric" + * Try workaround by interpreting gateway_str as metric to accept such + * invalid routes. This broken syntax should not be not officially + * supported. + **/ + if ( family == AF_INET6 + && !metric_str + && get_one_int (NULL, NULL, gateway_str, G_MAXUINT32, &u32)) { + metric = u32; + gateway_str = NULL; + } else { + if (!info->error) { + handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid gateway '%s' for %s route"), + gateway_str, family == AF_INET ? "IPv4" : "IPv6"); + } + return NULL; + } + } + } else + gateway_str = NULL; + + /* parse metric, default to -1 */ + if (metric_str) { + if (!get_one_int (info, property_name, metric_str, G_MAXUINT32, &u32)) + return NULL; + metric = u32; + } + + route = nm_ip_route_new (family, + dest_str, + plen, + gateway_str, + metric, + &error); + if (!route) { + handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid %s route: %s"), + family == AF_INET ? "IPv4" : "IPv6", + error->message); + g_error_free (error); + } + + return route; +} + +/* On success, returns pointer to the zero-terminated field (original @current). + * The @current * pointer target is set to point to the rest of the input + * or %NULL if there is no more input. Sets error to %NULL for convenience. + * + * On failure, returns %NULL (unspecified). The @current pointer target is + * resets to its original value to allow skipping fields. The @error target + * is set to the character that breaks the parsing or %NULL if @current was %NULL. + * + * When @current target is %NULL, gracefully fail returning %NULL while + * leaving the @current target %NULL end setting @error to %NULL; + */ +static const char * +read_field (char **current, const char **out_err_str, const char *characters, const char *delimiters) +{ + const char *start; + + nm_assert (current); + nm_assert (out_err_str); + nm_assert (characters); + nm_assert (delimiters); + + *out_err_str = NULL; + + if (!*current) { + /* graceful failure, leave '*current' NULL */ + return NULL; + } + + /* fail on empty input */ + if (!**current) + return NULL; + + /* remember beginning of input */ + start = *current; + + while (**current && strchr (characters, **current)) + (*current)++; + if (**current) + if (strchr (delimiters, **current)) { + /* success, more data available */ + *(*current)++ = '\0'; + return start; + } else { + /* error, bad character */ + *out_err_str = *current; + *current = (char *) start; + return NULL; + } + else { + /* success, end of input */ + *current = NULL; + return start; + } +} + +/*****************************************************************************/ + +#define NM_DBUS_SERVICE_OPENCONNECT "org.freedesktop.NetworkManager.openconnect" +#define NM_OPENCONNECT_KEY_GATEWAY "gateway" +#define NM_OPENCONNECT_KEY_COOKIE "cookie" +#define NM_OPENCONNECT_KEY_GWCERT "gwcert" +#define NM_OPENCONNECT_KEY_XMLCONFIG "xmlconfig" +#define NM_OPENCONNECT_KEY_LASTHOST "lasthost" +#define NM_OPENCONNECT_KEY_AUTOCONNECT "autoconnect" +#define NM_OPENCONNECT_KEY_CERTSIGS "certsigs" + +static void +openconnect_fix_secret_flags (NMSetting *setting) +{ + NMSettingVpn *s_vpn; + NMSettingSecretFlags flags; + + /* Huge hack. There were some openconnect changes that needed to happen + * pretty late, too late to get into distros. Migration has already + * happened for many people, and their secret flags are wrong. But we + * don't want to requrie re-migration, so we have to fix it up here. Ugh. + */ + + if (!NM_IS_SETTING_VPN (setting)) + return; + + s_vpn = NM_SETTING_VPN (setting); + + if (!nm_streq0 (nm_setting_vpn_get_service_type (s_vpn), NM_DBUS_SERVICE_OPENCONNECT)) + return; + + /* These are different for every login session, and should not be stored */ + flags = NM_SETTING_SECRET_FLAG_NOT_SAVED; + nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_GATEWAY, flags, NULL); + nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_COOKIE, flags, NULL); + nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_GWCERT, flags, NULL); + + /* These are purely internal data for the auth-dialog, and should be stored */ + flags = 0; + nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_XMLCONFIG, flags, NULL); + nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_LASTHOST, flags, NULL); + nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_AUTOCONNECT, flags, NULL); + nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_CERTSIGS, flags, NULL); +} + +/*****************************************************************************/ + +#define IP_ADDRESS_CHARS "0123456789abcdefABCDEF:.%" +#define DIGITS "0123456789" +#define DELIMITERS "/;," + +/* The following IPv4 and IPv6 address formats are supported: + * + * address (DEPRECATED) + * address/plen + * address/gateway (DEPRECATED) + * address/plen,gateway + * + * The following IPv4 and IPv6 route formats are supported: + * + * address/plen (NETWORK dev DEVICE) + * address/plen,gateway (NETWORK via GATEWAY dev DEVICE) + * address/plen,,metric (NETWORK dev DEVICE metric METRIC) + * address/plen,gateway,metric (NETWORK via GATEWAY dev DEVICE metric METRIC) + * + * For backward, forward and sideward compatibility, slash (/), + * semicolon (;) and comma (,) are interchangeable. The choice of + * separator in the above examples is therefore not significant. + * + * Leaving out the prefix length is discouraged and DEPRECATED. The + * default value of IPv6 prefix length was 64 and has not been + * changed. The default for IPv4 is now 24, which is the closest + * IPv4 equivalent. These defaults may just as well be changed to + * match the iproute2 defaults (32 for IPv4 and 128 for IPv6). + */ +static gpointer +read_one_ip_address_or_route (KeyfileReaderInfo *info, + const char *property_name, + const char *setting_name, + const char *key_name, + gboolean ipv6, + gboolean route, + char **out_gateway, + NMSetting *setting) +{ + guint plen; + gpointer result; + const char *address_str; + const char *plen_str; + const char *gateway_str; + const char *metric_str; + const char *err_str = NULL; + char *current; + gs_free char *value = NULL; + gs_free char *value_orig = NULL; + +#define VALUE_ORIG() (value_orig ?: (value_orig = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key_name, NULL))) + + value = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key_name, NULL); + if (!value) + return NULL; + + current = value; + + /* get address field */ + address_str = read_field (¤t, &err_str, IP_ADDRESS_CHARS, DELIMITERS); + if (err_str) { + handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, + _("unexpected character '%c' for address %s: '%s' (position %td)"), + *err_str, key_name, VALUE_ORIG (), err_str - current); + return NULL; + } + /* get prefix length field (skippable) */ + plen_str = read_field (¤t, &err_str, DIGITS, DELIMITERS); + /* get gateway field */ + gateway_str = read_field (¤t, &err_str, IP_ADDRESS_CHARS, DELIMITERS); + if (err_str) { + handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, + _("unexpected character '%c' for %s: '%s' (position %td)"), + *err_str, key_name, VALUE_ORIG (), err_str - current); + return NULL; + } + /* for routes, get metric */ + if (route) { + metric_str = read_field (¤t, &err_str, DIGITS, DELIMITERS); + if (err_str) { + handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, + _("unexpected character '%c' in prefix length for %s: '%s' (position %td)"), + *err_str, key_name, VALUE_ORIG (), err_str - current); + return NULL; + } + } else + metric_str = NULL; + if (current) { + /* there is still some data */ + if (*current) { + /* another field follows */ + handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, + _("garbage at the end of value %s: '%s'"), + key_name, VALUE_ORIG ()); + return NULL; + } else { + /* semicolon at the end of input */ + if (!handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_INFO, + _("deprecated semicolon at the end of value %s: '%s'"), + key_name, VALUE_ORIG ())) + return NULL; + } + } + +#define DEFAULT_PREFIX(for_route, for_ipv6) ( (for_route) ? ( (for_ipv6) ? 128 : 24 ) : ( (for_ipv6) ? 64 : 24 ) ) + + /* parse plen, fallback to defaults */ + if (plen_str) { + if (!get_one_int (info, property_name, plen_str, ipv6 ? 128 : 32, &plen)) { + plen = DEFAULT_PREFIX (route, ipv6); + if ( info->error + || !handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid prefix length for %s '%s', defaulting to %d"), + key_name, VALUE_ORIG (), plen)) + return NULL; + } + } else { + plen = DEFAULT_PREFIX (route, ipv6); + if (!handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN, + _("missing prefix length for %s '%s', defaulting to %d"), + key_name, VALUE_ORIG (), plen)) + return NULL; + } + + /* build the appropriate data structure for NetworkManager settings */ + if (route) { + result = build_route (info, + property_name, + ipv6 ? AF_INET6 : AF_INET, + address_str, + plen, + gateway_str, + metric_str); + } else { + result = build_address (info, + ipv6 ? AF_INET6 : AF_INET, + address_str, + plen, + property_name); + if (!result) + return NULL; + if (gateway_str) + NM_SET_OUT (out_gateway, g_strdup (gateway_str)); + } + +#undef VALUE_ORIG + + return result; +} + +static void +fill_route_attributes (GKeyFile *kf, NMIPRoute *route, const char *setting, const char *key, int family) +{ + gs_free char *value = NULL; + gs_unref_hashtable GHashTable *hash = NULL; + GHashTableIter iter; + char *name; + GVariant *variant; + + value = nm_keyfile_plugin_kf_get_string (kf, setting, key, NULL); + if (!value || !value[0]) + return; + + hash = nm_utils_parse_variant_attributes (value, ',', '=', TRUE, + nm_ip_route_get_variant_attribute_spec (), + NULL); + if (hash) { + g_hash_table_iter_init (&iter, hash); + while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer *) &variant)) { + if (nm_ip_route_attribute_validate (name, variant, family, NULL, NULL)) + nm_ip_route_set_attribute (route, name, g_variant_ref (variant)); + } + } +} + +typedef struct { + const char *s_key; + gint32 key_idx; + gint8 key_type; +} BuildListData; + +typedef enum { + BUILD_LIST_TYPE_ADDRESSES, + BUILD_LIST_TYPE_ROUTES, + BUILD_LIST_TYPE_ROUTING_RULES, +} BuildListType; + +static int +_build_list_data_cmp (gconstpointer p_a, gconstpointer p_b, gpointer user_data) +{ + const BuildListData *a = p_a; + const BuildListData *b = p_b; + + NM_CMP_FIELD (a, b, key_idx); + NM_CMP_FIELD (a, b, key_type); + NM_CMP_FIELD_STR (a, b, s_key); + return 0; +} + +static gboolean +_build_list_data_is_shadowed (const BuildListData *build_list, + gsize build_list_len, + gsize idx) +{ + /* the keyfile contains duplicate keys, which are both returned + * by g_key_file_get_keys() (WHY??). + * + * Skip the earlier one. */ + return idx + 1 < build_list_len + && build_list[idx].key_idx == build_list[idx + 1].key_idx + && build_list[idx].key_type == build_list[idx + 1].key_type + && nm_streq (build_list[idx].s_key, build_list[idx + 1].s_key); +} + +static gboolean +_build_list_match_key_w_name_impl (const char *key, + const char *base_name, + gsize base_name_l, + gint32 *out_key_idx) +{ + gint64 v; + + /* some very strict parsing. */ + + /* the key must start with base_name. */ + if (strncmp (key, base_name, base_name_l) != 0) + return FALSE; + + key += base_name_l; + if (key[0] == '\0') { + /* if key is identical to base_name, that's good. */ + NM_SET_OUT (out_key_idx, -1); + return TRUE; + } + + /* if base_name is followed by a zero, then it must be + * only a zero, nothing else. */ + if (key[0] == '0') { + if (key[1] != '\0') + return FALSE; + NM_SET_OUT (out_key_idx, 0); + return TRUE; + } + + /* otherwise, it can only be followed by a non-zero decimal. */ + if (!(key[0] >= '1' && key[0] <= '9')) + return FALSE; + /* and all remaining chars must be decimals too. */ + if (!NM_STRCHAR_ALL (&key[1], ch, g_ascii_isdigit (ch))) + return FALSE; + + /* and it must be convertible to a (positive) int. */ + v = _nm_utils_ascii_str_to_int64 (key, 10, 0, G_MAXINT32, -1); + if (v < 0) + return FALSE; + + /* good */ + NM_SET_OUT (out_key_idx, v); + return TRUE; +} + +#define _build_list_match_key_w_name(key, base_name, out_key_idx) \ + _build_list_match_key_w_name_impl (key, base_name, NM_STRLEN (base_name), out_key_idx) + +static BuildListData * +_build_list_create (GKeyFile *keyfile, + const char *group_name, + BuildListType build_list_type, + gsize *out_build_list_len, + char ***out_keys_strv) +{ + gs_strfreev char **keys = NULL; + gsize i_keys, n_keys; + gs_free BuildListData *build_list = NULL; + gsize build_list_len = 0; + + nm_assert (out_build_list_len && *out_build_list_len == 0); + nm_assert (out_keys_strv && !*out_keys_strv); + + keys = nm_keyfile_plugin_kf_get_keys (keyfile, group_name, &n_keys, NULL); + if (n_keys == 0) + return NULL; + + for (i_keys = 0; i_keys < n_keys; i_keys++) { + const char *s_key = keys[i_keys]; + gint32 key_idx; + gint8 key_type = 0; + + switch (build_list_type) { + case BUILD_LIST_TYPE_ROUTES: + if (_build_list_match_key_w_name (s_key, "route", &key_idx)) + key_type = 0; + else if (_build_list_match_key_w_name (s_key, "routes", &key_idx)) + key_type = 1; + else + continue; + break; + case BUILD_LIST_TYPE_ADDRESSES: + if (_build_list_match_key_w_name (s_key, "address", &key_idx)) + key_type = 0; + else if (_build_list_match_key_w_name (s_key, "addresses", &key_idx)) + key_type = 1; + else + continue; + break; + case BUILD_LIST_TYPE_ROUTING_RULES: + if (_build_list_match_key_w_name (s_key, "routing-rule", &key_idx)) + key_type = 0; + else + continue; + break; + default: + nm_assert_not_reached (); + break; + } + + if (G_UNLIKELY (!build_list)) + build_list = g_new (BuildListData, n_keys - i_keys); + + build_list[build_list_len++] = (BuildListData) { + .s_key = s_key, + .key_idx = key_idx, + .key_type = key_type, + }; + } + + if (build_list_len == 0) + return NULL; + + if (build_list_len > 1) { + g_qsort_with_data (build_list, + build_list_len, + sizeof (BuildListData), + _build_list_data_cmp, + NULL); + } + + *out_build_list_len = build_list_len; + *out_keys_strv = g_steal_pointer (&keys); + return g_steal_pointer (&build_list); +} + +static void +ip_address_or_route_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *setting_key) +{ + const char *setting_name = nm_setting_get_name (setting); + gboolean is_ipv6 = nm_streq (setting_name, "ipv6"); + gboolean is_routes = nm_streq (setting_key, "routes"); + gs_free char *gateway = NULL; + gs_unref_ptrarray GPtrArray *list = NULL; + gs_strfreev char **keys = NULL; + gs_free BuildListData *build_list = NULL; + gsize i_build_list, build_list_len = 0; + + build_list = _build_list_create (info->keyfile, + setting_name, + is_routes + ? BUILD_LIST_TYPE_ROUTES + : BUILD_LIST_TYPE_ADDRESSES, + &build_list_len, + &keys); + if (!build_list) + return; + + list = g_ptr_array_new_with_free_func (is_routes + ? (GDestroyNotify) nm_ip_route_unref + : (GDestroyNotify) nm_ip_address_unref); + + for (i_build_list = 0; i_build_list < build_list_len; i_build_list++) { + const char *s_key; + gpointer item; + + if (_build_list_data_is_shadowed (build_list, build_list_len, i_build_list)) + continue; + + s_key = build_list[i_build_list].s_key; + item = read_one_ip_address_or_route (info, + setting_key, + setting_name, + s_key, + is_ipv6, + is_routes, + gateway ? NULL : &gateway, + setting); + if (item && is_routes) { + char options_key[128]; + + nm_sprintf_buf (options_key, "%s_options", s_key); + fill_route_attributes (info->keyfile, + item, + setting_name, + options_key, + is_ipv6 ? AF_INET6 : AF_INET); + } + + if (info->error) + return; + + if (item) + g_ptr_array_add (list, item); + } + + if (list->len >= 1) + g_object_set (setting, setting_key, list, NULL); + + if (gateway) + g_object_set (setting, "gateway", gateway, NULL); +} + +static void +ip_routing_rule_parser_full (KeyfileReaderInfo *info, + const NMMetaSettingInfo *setting_info, + const NMSettInfoProperty *property_info, + const ParseInfoProperty *pip, + NMSetting *setting) +{ + const char *setting_name = nm_setting_get_name (setting); + gboolean is_ipv6 = nm_streq (setting_name, "ipv6"); + gs_strfreev char **keys = NULL; + gs_free BuildListData *build_list = NULL; + gsize i_build_list, build_list_len = 0; + + build_list = _build_list_create (info->keyfile, + setting_name, + BUILD_LIST_TYPE_ROUTING_RULES, + &build_list_len, + &keys); + if (!build_list) + return; + + for (i_build_list = 0; i_build_list < build_list_len; i_build_list++) { + nm_auto_unref_ip_routing_rule NMIPRoutingRule *rule = NULL; + gs_free char *value = NULL; + gs_free_error GError *local = NULL; + + if (_build_list_data_is_shadowed (build_list, build_list_len, i_build_list)) + continue; + + value = nm_keyfile_plugin_kf_get_string (info->keyfile, + setting_name, + build_list[i_build_list].s_key, + NULL); + if (!value) + continue; + + rule = nm_ip_routing_rule_from_string (value, + ( NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE + | ( is_ipv6 + ? NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6 + : NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET)), + NULL, + &local); + if (!rule) { + handle_warn (info, property_info->name, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid value for \"%s\": %s"), + build_list[i_build_list].s_key, + local->message); + if (info->error) + return; + continue; + } + + nm_setting_ip_config_add_routing_rule (NM_SETTING_IP_CONFIG (setting), rule); + } +} + +static void +ip_dns_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + int addr_family; + gs_strfreev char **list = NULL; + gsize i, n, length; + + nm_assert (NM_IS_SETTING_IP4_CONFIG (setting) || NM_IS_SETTING_IP6_CONFIG (setting)); + + list = nm_keyfile_plugin_kf_get_string_list (info->keyfile, + nm_setting_get_name (setting), + key, + &length, + NULL); + nm_assert (length == NM_PTRARRAY_LEN (list)); + if (length == 0) + return; + + addr_family = NM_IS_SETTING_IP4_CONFIG (setting) ? AF_INET : AF_INET6; + + n = 0; + for (i = 0; i < length; i++) { + NMIPAddr addr; + + if (inet_pton (addr_family, list[i], &addr) <= 0) { + if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid DNS server IPv%c address '%s'"), + nm_utils_addr_family_to_char (addr_family), + list[i])) { + do { + nm_clear_g_free (&list[i]); + } while (++i < length); + return; + } + nm_clear_g_free (&list[i]); + continue; + } + + if (n != i) + list[n] = g_steal_pointer (&list[i]); + n++; + } + + g_object_set (setting, key, list, NULL); +} + +static void +ip6_addr_gen_mode_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + NMSettingIP6ConfigAddrGenMode addr_gen_mode; + const char *setting_name = nm_setting_get_name (setting); + gs_free char *s = NULL; + + s = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL); + if (s) { + if (!nm_utils_enum_from_str (nm_setting_ip6_config_addr_gen_mode_get_type (), s, + (int *) &addr_gen_mode, NULL)) { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid option '%s', use one of [%s]"), + s, "eui64,stable-privacy"); + return; + } + } else + addr_gen_mode = NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64; + + g_object_set (G_OBJECT (setting), key, (int) addr_gen_mode, NULL); +} + +static void +mac_address_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key, gsize enforce_length, gboolean cloned_mac_addr) +{ + const char *setting_name = nm_setting_get_name (setting); + gs_free char *tmp_string = NULL; + const char *p, *mac_str; + gs_free guint8 *buf_arr = NULL; + guint buf_len = 0; + + tmp_string = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL); + + if ( cloned_mac_addr + && NM_CLONED_MAC_IS_SPECIAL (tmp_string)) { + mac_str = tmp_string; + goto out; + } + + if (tmp_string && tmp_string[0]) { + /* Look for enough ':' characters to signify a MAC address */ + guint i = 0; + + p = tmp_string; + while (*p) { + if (*p == ':') + i++; + p++; + } + + if (enforce_length == 0 || enforce_length == i+1) { + /* If we found enough it's probably a string-format MAC address */ + buf_len = i + 1; + buf_arr = g_new (guint8, buf_len); + if (!nm_utils_hwaddr_aton (tmp_string, buf_arr, buf_len)) + nm_clear_g_free (&buf_arr); + } + } + nm_clear_g_free (&tmp_string); + + if (!buf_arr) { + gs_free int *tmp_list = NULL; + gsize length; + + /* Old format; list of ints */ + tmp_list = nm_keyfile_plugin_kf_get_integer_list (info->keyfile, setting_name, key, &length, NULL); + if (length > 0 && (enforce_length == 0 || enforce_length == length)) { + gsize i; + + buf_len = length; + buf_arr = g_new (guint8, buf_len); + for (i = 0; i < length; i++) { + int val = tmp_list[i]; + + if (val < 0 || val > 255) { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid byte element '%d' (not between 0 and 255 inclusive)"), + val); + return; + } + buf_arr[i] = (guint8) val; + } + } + } + + if (!buf_arr) { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid MAC address")); + return; + } + + tmp_string = nm_utils_hwaddr_ntoa (buf_arr, buf_len); + mac_str = tmp_string; + +out: + g_object_set (setting, key, mac_str, NULL); +} + +static void +mac_address_parser_ETHER (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + mac_address_parser (info, setting, key, ETH_ALEN, FALSE); +} + +static void +mac_address_parser_ETHER_cloned (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + mac_address_parser (info, setting, key, ETH_ALEN, TRUE); +} + +static void +mac_address_parser_INFINIBAND (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + mac_address_parser (info, setting, key, INFINIBAND_ALEN, FALSE); +} + +static void +read_hash_of_string (GKeyFile *file, NMSetting *setting, const char *key) +{ + gs_strfreev char **keys = NULL; + const char *const*iter; + const char *setting_name = nm_setting_get_name (setting); + gboolean is_vpn; + gsize n_keys; + + nm_assert ( (NM_IS_SETTING_VPN (setting) && nm_streq (key, NM_SETTING_VPN_DATA)) + || (NM_IS_SETTING_VPN (setting) && nm_streq (key, NM_SETTING_VPN_SECRETS)) + || (NM_IS_SETTING_BOND (setting) && nm_streq (key, NM_SETTING_BOND_OPTIONS)) + || (NM_IS_SETTING_USER (setting) && nm_streq (key, NM_SETTING_USER_DATA))); + + keys = nm_keyfile_plugin_kf_get_keys (file, setting_name, &n_keys, NULL); + if (n_keys == 0) + return; + + if ( (is_vpn = NM_IS_SETTING_VPN (setting)) + || NM_IS_SETTING_BOND (setting)) { + for (iter = (const char *const*) keys; *iter; iter++) { + gs_free char *to_free = NULL; + gs_free char *value = NULL; + const char *name; + + value = nm_keyfile_plugin_kf_get_string (file, setting_name, *iter, NULL); + if (!value) + continue; + + name = nm_keyfile_key_decode (*iter, &to_free); + + if (is_vpn) { + /* Add any item that's not a class property to the data hash */ + if (!g_object_class_find_property (G_OBJECT_GET_CLASS (setting), name)) + nm_setting_vpn_add_data_item (NM_SETTING_VPN (setting), name, value); + } else { + if (!nm_streq (name, "interface-name")) + nm_setting_bond_add_option (NM_SETTING_BOND (setting), name, value); + } + } + openconnect_fix_secret_flags (setting); + return; + } + + if (NM_IS_SETTING_USER (setting)) { + gs_unref_hashtable GHashTable *data = NULL; + + data = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free); + for (iter = (const char *const*) keys; *iter; iter++) { + gs_free char *to_free = NULL; + char *value = NULL; + const char *name; + + value = nm_keyfile_plugin_kf_get_string (file, setting_name, *iter, NULL); + if (!value) + continue; + name = nm_keyfile_key_decode (*iter, &to_free); + g_hash_table_insert (data, + g_steal_pointer (&to_free) ?: g_strdup (name), + value); + } + g_object_set (setting, NM_SETTING_USER_DATA, data, NULL); + return; + } + + nm_assert_not_reached (); +} + +static gsize +unescape_semicolons (char *str) +{ + gsize i, j; + + for (i = 0, j = 0; str[i]; ) { + if (str[i] == '\\' && str[i+1] == ';') + i++; + str[j++] = str[i++];; + } + nm_explicit_bzero (&str[j], i - j); + return j; +} + +static GBytes * +get_bytes (KeyfileReaderInfo *info, + const char *setting_name, + const char *key, + gboolean zero_terminate, + gboolean unescape_semicolon) +{ + nm_auto_free_secret char *tmp_string = NULL; + gboolean may_be_int_list = TRUE; + gsize length; + GBytes *result; + + /* New format: just a string + * Old format: integer list; e.g. 11;25;38; + */ + tmp_string = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL); + if (!tmp_string) + return NULL; + + /* if the string is empty, we return an empty GBytes array. + * Note that for NM_SETTING_802_1X_PASSWORD_RAW both %NULL and + * an empty GBytes are valid, and shall be destinguished. */ + if (!tmp_string[0]) { + /* note that even if @zero_terminate is TRUE, we return an empty + * byte-array. The reason is that zero_terminate is there to terminate + * *valid* strings. It's not there to terminated invalid (empty) strings. + */ + return g_bytes_new_static ("", 0); + } + + for (length = 0; tmp_string[length]; length++) { + const char ch = tmp_string[length]; + + if ( !g_ascii_isspace (ch) + && !g_ascii_isdigit (ch) + && ch != ';') { + may_be_int_list = FALSE; + length += strlen (&tmp_string[length]); + break; + } + } + + /* Try to parse the string as a integer list. */ + if (may_be_int_list && length > 0) { + nm_auto_free_secret_buf NMSecretBuf *bin = NULL; + const char *const s = tmp_string; + gsize i, d; + + bin = nm_secret_buf_new (length / 2 + 3); + +#define DIGIT(c) ((c) - '0') + i = 0; + d = 0; + while (TRUE) { + int n; + + /* leading whitespace */ + while (g_ascii_isspace (s[i])) + i++; + if (s[i] == '\0') + break; + /* then expect 1 to 3 digits */ + if (!g_ascii_isdigit (s[i])) { + d = 0; + break; + } + n = DIGIT (s[i]); + i++; + if (g_ascii_isdigit (s[i])) { + n = 10 * n + DIGIT (s[i]); + i++; + if (g_ascii_isdigit (s[i])) { + n = 10 * n + DIGIT (s[i]); + i++; + } + } + if (n > 255) { + d = 0; + break; + } + + nm_assert (d < bin->len); + bin->bin[d++] = n; + + /* allow whitespace after the digit. */ + while (g_ascii_isspace (s[i])) + i++; + /* need a semicolon as separator. */ + if (s[i] != ';') { + d = 0; + break; + } + i++; + } +#undef DIGIT + + /* Old format; list of ints. We already did a strict validation of the + * string format before. We expect that this conversion cannot fail. */ + if (d > 0) { + /* note that @zero_terminate does not add a terminating '\0' to + * binary data as an integer list. If the bytes are expressed as + * an integer list, all potential NUL characters are supposed to + * be included there explicitly. + * + * However, in the spirit of defensive programming, we do append a + * NUL character to the buffer, although this character is hidden + * and only a mitigation for bugs. */ + + if (d + 10 < bin->len) { + /* hm, too much unused memory. Copy the memory to a suitable + * sized buffer. */ + return nm_secret_copy_to_gbytes (bin->bin, d); + } + + nm_assert (d < bin->len); + bin->bin[d] = '\0'; + return nm_secret_buf_to_gbytes_take (g_steal_pointer (&bin), d); + } + } + + /* Handle as a simple string (ie, new format) */ + if (unescape_semicolon) + length = unescape_semicolons (tmp_string); + if (zero_terminate) + length++; + if (length == 0) + return NULL; + + result = g_bytes_new_with_free_func (tmp_string, + length, + (GDestroyNotify) nm_free_secret, + tmp_string); + tmp_string = NULL; + return result; +} + +static void +ssid_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char *setting_name = nm_setting_get_name (setting); + GBytes *bytes; + + bytes = get_bytes (info, setting_name, key, FALSE, TRUE); + if (bytes) { + g_object_set (setting, key, bytes, NULL); + g_bytes_unref (bytes); + } else if (!info->error) { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid SSID")); + } +} + +static void +password_raw_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char *setting_name = nm_setting_get_name (setting); + GBytes *bytes; + + bytes = get_bytes (info, setting_name, key, FALSE, TRUE); + if (bytes) { + g_object_set (setting, key, bytes, NULL); + g_bytes_unref (bytes); + } else if (!info->error) { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid raw password")); + } +} + +static char * +get_cert_path (const char *base_dir, const guint8 *cert_path, gsize cert_path_len) +{ + const char *base; + char *p = NULL, *path, *tmp; + + g_return_val_if_fail (base_dir != NULL, NULL); + g_return_val_if_fail (cert_path != NULL, NULL); + + path = g_strndup ((char *) cert_path, cert_path_len); + + if (path[0] == '/') + return path; + + base = path; + p = strrchr (path, '/'); + if (p) + base = p + 1; + + tmp = g_build_path ("/", base_dir, base, NULL); + g_free (path); + return tmp; +} + +static const char *certext[] = { ".pem", ".cert", ".crt", ".cer", ".p12", ".der", ".key" }; + +static gboolean +has_cert_ext (const char *path) +{ + int i; + + for (i = 0; i < G_N_ELEMENTS (certext); i++) { + if (g_str_has_suffix (path, certext[i])) + return TRUE; + } + return FALSE; +} + +char * +nm_keyfile_detect_unqualified_path_scheme (const char *base_dir, + gconstpointer pdata, + gsize data_len, + gboolean consider_exists, + gboolean *out_exists) +{ + const char *data = pdata; + gboolean exists = FALSE; + gsize validate_len; + gsize path_len, pathuri_len; + gs_free char *path = NULL; + gs_free char *pathuri = NULL; + + g_return_val_if_fail (base_dir && base_dir[0] == '/', NULL); + + if (!pdata) + return NULL; + if (data_len == -1) + data_len = strlen (data); + if (data_len > 500 || data_len < 1) + return NULL; + + /* If there's a trailing zero tell g_utf8_validate() to validate until the zero */ + if (data[data_len - 1] == '\0') { + /* setting it to -1, would mean we accept data to contain NUL characters before the + * end. Don't accept any NUL in [0 .. data_len-1[ . */ + validate_len = data_len - 1; + } else + validate_len = data_len; + if ( validate_len == 0 + || g_utf8_validate ((const char *) data, validate_len, NULL) == FALSE) + return NULL; + + /* Might be a bare path without the file:// prefix; in that case + * if it's an absolute path, use that, otherwise treat it as a + * relative path to the current directory. + */ + + path = get_cert_path (base_dir, (const guint8 *) data, data_len); + + /* FIXME(keyfile-parse-in-memory): it is wrong that keyfile reader makes decisions based on + * the file systems content. The serialization/parsing should be entirely in-memory. */ + if ( !memchr (data, '/', data_len) + && !has_cert_ext (path)) { + if (!consider_exists) + return NULL; + exists = g_file_test (path, G_FILE_TEST_EXISTS); + if (!exists) + return NULL; + } else if (out_exists) + exists = g_file_test (path, G_FILE_TEST_EXISTS); + + /* Construct the proper value as required for the PATH scheme. + * + * When returning TRUE, we must also be sure that @data_len does not look like + * the deprecated format of list of integers. With this implementation that is the + * case, as long as @consider_exists is FALSE. */ + path_len = strlen (path); + pathuri_len = (NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH) + 1) + path_len; + pathuri = g_new (char, pathuri_len); + memcpy (pathuri, NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)); + memcpy (&pathuri[NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)], path, path_len + 1); + if (nm_setting_802_1x_check_cert_scheme (pathuri, pathuri_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_PATH) + return NULL; + + NM_SET_OUT (out_exists, exists); + return g_steal_pointer (&pathuri); +} + +#define HAS_SCHEME_PREFIX(bin, bin_len, scheme) \ + ({ \ + const char *const _bin = (bin); \ + const gsize _bin_len = (bin_len); \ + \ + nm_assert (_bin && _bin_len > 0); \ + \ + ( _bin_len > NM_STRLEN (scheme) + 1 \ + && _bin[_bin_len - 1] == '\0' \ + && memcmp (_bin, scheme, NM_STRLEN (scheme)) == 0); \ + }) + +static void +cert_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char *setting_name = nm_setting_get_name (setting); + gs_unref_bytes GBytes *bytes = NULL; + const char *bin = NULL; + gsize bin_len = 0; + char *path; + gboolean path_exists; + + bytes = get_bytes (info, setting_name, key, TRUE, FALSE); + if (bytes) + bin = g_bytes_get_data (bytes, &bin_len); + if (bin_len == 0) { + if (!info->error) { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid key/cert value")); + } + return; + } + + if (HAS_SCHEME_PREFIX (bin, bin_len, NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)) { + const char *path2 = &bin[NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)]; + gs_free char *path2_free = NULL; + + if (nm_setting_802_1x_check_cert_scheme (bin, bin_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_PATH) { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid key/cert value path \"%s\""), bin); + return; + } + + g_object_set (setting, key, bytes, NULL); + + if (path2[0] != '/') { + /* we want to read absolute paths because we use keyfile as exchange + * between different processes which might not have the same cwd. */ + path2_free = get_cert_path (info->base_dir, (const guint8 *) path2, + bin_len - NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH) - 1); + path2 = path2_free; + } + + /* FIXME(keyfile-parse-in-memory): keyfile reader must not access the file system and + * (in a first step) only operate in memory-only. If the presence of files should be checked, + * then by invoking a callback (and possibly keyfile settings plugin would + * collect the file names to be checked and check them later). */ + if (!g_file_test (path2, G_FILE_TEST_EXISTS)) { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE, + _("certificate or key file '%s' does not exist"), + path2); + } + return; + } + + if (HAS_SCHEME_PREFIX (bin, bin_len, NM_KEYFILE_CERT_SCHEME_PREFIX_PKCS11)) { + if (nm_setting_802_1x_check_cert_scheme (bin, bin_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_PKCS11) { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid PKCS#11 URI \"%s\""), bin); + return; + } + + g_object_set (setting, key, bytes, NULL); + return; + } + + if (HAS_SCHEME_PREFIX (bin, bin_len, NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB)) { + const char *cdata = bin + NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB); + gsize cdata_len = bin_len - NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB) - 1; + gs_free guchar *bin_decoded = NULL; + gsize bin_decoded_len = 0; + gsize i; + gboolean valid_base64; + gs_unref_bytes GBytes *val = NULL; + + /* Let's be strict here. We expect valid base64, no funny stuff!! + * We didn't write such invalid data ourselfes and refuse to read it as blob. */ + if ((valid_base64 = (cdata_len % 4 == 0))) { + for (i = 0; i < cdata_len; i++) { + char c = cdata[i]; + + if (!( (c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') + || (c == '+' || c == '/'))) { + if (c != '=' || i < cdata_len - 2) + valid_base64 = FALSE; + else { + for (; i < cdata_len; i++) { + if (cdata[i] != '=') + valid_base64 = FALSE; + } + } + break; + } + } + } + if (valid_base64) + bin_decoded = g_base64_decode (cdata, &bin_decoded_len); + + if (bin_decoded_len == 0) { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid key/cert value data:;base64, is not base64")); + return; + } + + if (nm_setting_802_1x_check_cert_scheme (bin_decoded, bin_decoded_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_BLOB) { + /* The blob probably starts with "file://". Setting the cert data will confuse NMSetting8021x. + * In fact this is a limitation of NMSetting8021x which does not support setting blobs that start + * with file://. Just warn and return TRUE to signal that we ~handled~ the setting. */ + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid key/cert value data:;base64,file://")); + return; + } + + val = g_bytes_new_take (g_steal_pointer (&bin_decoded), bin_decoded_len); + g_object_set (setting, key, val, NULL); + return; + } + + /* If not, it might be a plain path */ + path = nm_keyfile_detect_unqualified_path_scheme (info->base_dir, bin, bin_len, TRUE, &path_exists); + if (path) { + gs_unref_bytes GBytes *val = NULL; + + /* Construct the proper value as required for the PATH scheme */ + val = g_bytes_new_take (path, strlen (path) + 1); + g_object_set (setting, key, val, NULL); + + /* Warn if the certificate didn't exist */ + if (!path_exists) { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE, + _("certificate or key file '%s' does not exist"), + path); + } + return; + } + + if (nm_setting_802_1x_check_cert_scheme (bin, bin_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_BLOB) { + /* The blob probably starts with "file://" but contains invalid characters for a path. + * Setting the cert data will confuse NMSetting8021x. + * In fact, NMSetting8021x does not support setting such binary data, so just warn and + * continue. */ + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid key/cert value is not a valid blob")); + return; + } + + g_object_set (setting, key, bytes, NULL); +} + +static int +_parity_from_char (int ch) +{ +#if NM_MORE_ASSERTS > 5 + { + static char check = 0; + + if (check == 0) { + nm_auto_unref_gtypeclass GEnumClass *klass = g_type_class_ref (NM_TYPE_SETTING_SERIAL_PARITY); + guint i; + + check = 1; + + /* In older versions, parity was G_TYPE_CHAR/gint8, and the character + * value was stored as integer. + * For example parity=69 equals parity=E, meaning NM_SETTING_SERIAL_PARITY_EVEN. + * + * That means, certain values are reserved. Assert that these numbers + * are not reused when we extend NMSettingSerialParity enum. + * Actually, since NM_SETTING_SERIAL_PARITY is g_param_spec_enum(), + * we anyway cannot extend the enum without breaking API... + * + * [1] commit "a91e60902e libnm-core: make NMSettingSerial:parity an enum" + * [2] https://cgit.freedesktop.org/NetworkManager/NetworkManager/commit/?id=a91e60902eabae1de93d61323dae6ac894b5d40f + */ + g_assert (G_IS_ENUM_CLASS (klass)); + for (i = 0; i < klass->n_values; i++) { + const GEnumValue *v = &klass->values[i]; + int num = v->value; + + g_assert (_parity_from_char (num) == -1); + g_assert (!NM_IN_SET (num, 'e', 'E', 'o', 'O', 'n', 'N')); + } + } + } +#endif + + switch (ch) { + case 'E': + case 'e': + return NM_SETTING_SERIAL_PARITY_EVEN; + case 'O': + case 'o': + return NM_SETTING_SERIAL_PARITY_ODD; + case 'N': + case 'n': + return NM_SETTING_SERIAL_PARITY_NONE; + } + + return -1; +} + +static void +parity_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char *setting_name = nm_setting_get_name (setting); + gs_free_error GError *err = NULL; + int parity; + gs_free char *tmp_str = NULL; + gint64 i64; + + /* Keyfile traditionally stored this as the ASCII value for 'E', 'o', or 'n'. + * We now accept either that or the (case-insensitive) character itself (but + * still always write it the old way, for backward compatibility). + */ + tmp_str = nm_keyfile_plugin_kf_get_value (info->keyfile, setting_name, key, &err); + if (err) + goto out_err; + + if ( tmp_str + && tmp_str[0] != '\0' + && tmp_str[1] == '\0') { + /* the ASCII characters like 'E' are taken directly... */ + parity = _parity_from_char (tmp_str[0]); + if (parity >= 0) + goto parity_good; + } + + i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT, G_MAXINT, G_MININT64); + if ( i64 != G_MININT64 + && errno == 0) { + + if ((parity = _parity_from_char (i64)) >= 0) { + /* another oddity: the string is a valid number. However, if the numeric values + * is one of the supported ASCII codes, accept it (like 69 for 'E'). + */ + goto parity_good; + } + + /* Finally, take the numeric value as is. */ + parity = i64; + goto parity_good; + } + + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid parity value '%s'"), + tmp_str ?: ""); + return; + +parity_good: + nm_g_object_set_property_enum (G_OBJECT (setting), key, NM_TYPE_SETTING_SERIAL_PARITY, parity, &err); + +out_err: + if (!err) + return; + if ( err->domain == G_KEY_FILE_ERROR + && NM_IN_SET (err->code, G_KEY_FILE_ERROR_GROUP_NOT_FOUND, + G_KEY_FILE_ERROR_KEY_NOT_FOUND)) { + /* ignore such errors. The key is not present. */ + return; + } + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid setting: %s"), err->message); +} + +static void +team_config_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char *setting_name = nm_setting_get_name (setting); + gs_free char *conf = NULL; + gs_free_error GError *error = NULL; + + conf = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL); + + g_object_set (G_OBJECT (setting), key, conf, NULL); + + if ( conf + && !nm_setting_verify (setting, NULL, &error)) { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid team configuration: %s"), + error->message); + g_object_set (G_OBJECT (setting), key, NULL, NULL); + } +} + +static void +bridge_vlan_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + gs_unref_ptrarray GPtrArray *vlans = NULL; + gs_free char *value = NULL; + gs_free const char **strv = NULL; + const char *const *iter; + GError *local = NULL; + NMBridgeVlan *vlan; + + value = nm_keyfile_plugin_kf_get_string (info->keyfile, + nm_setting_get_name (setting), + key, + NULL); + if (!value || !value[0]) + return; + + vlans = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_bridge_vlan_unref); + + strv = nm_utils_escaped_tokens_split (value, ","); + if (strv) { + for (iter = strv; *iter; iter++) { + vlan = nm_bridge_vlan_from_str (*iter, &local); + if (!vlan) { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + "invalid bridge VLAN: %s", local->message); + g_clear_error (&local); + continue; + } + g_ptr_array_add (vlans, vlan); + } + } + + if (vlans->len > 0) + g_object_set (setting, key, vlans, NULL); +} + +static void +qdisc_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char *setting_name = nm_setting_get_name (setting); + gs_unref_ptrarray GPtrArray *qdiscs = NULL; + gs_strfreev char **keys = NULL; + gsize n_keys = 0; + int i; + + keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, setting_name, &n_keys, NULL); + if (n_keys == 0) + return; + + qdiscs = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_tc_qdisc_unref); + + for (i = 0; i < n_keys; i++) { + NMTCQdisc *qdisc; + const char *qdisc_parent; + gs_free char *qdisc_rest = NULL; + gs_free char *qdisc_str = NULL; + gs_free_error GError *err = NULL; + + if (!g_str_has_prefix (keys[i], "qdisc.")) + continue; + + qdisc_parent = keys[i] + sizeof ("qdisc.") - 1; + qdisc_rest = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, keys[i], NULL); + qdisc_str = g_strdup_printf ("%s%s %s", + _nm_utils_parse_tc_handle (qdisc_parent, NULL) != TC_H_UNSPEC ? "parent " : "", + qdisc_parent, + qdisc_rest); + + qdisc = nm_utils_tc_qdisc_from_str (qdisc_str, &err); + if (!qdisc) { + handle_warn (info, keys[i], NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid qdisc: %s"), + err->message); + } else { + g_ptr_array_add (qdiscs, qdisc); + } + } + + if (qdiscs->len >= 1) + g_object_set (setting, key, qdiscs, NULL); +} + +static void +tfilter_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char *setting_name = nm_setting_get_name (setting); + gs_unref_ptrarray GPtrArray *tfilters = NULL; + gs_strfreev char **keys = NULL; + gsize n_keys = 0; + int i; + + keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, setting_name, &n_keys, NULL); + if (n_keys == 0) + return; + + tfilters = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_tc_tfilter_unref); + + for (i = 0; i < n_keys; i++) { + NMTCTfilter *tfilter; + const char *tfilter_parent; + gs_free char *tfilter_rest = NULL; + gs_free char *tfilter_str = NULL; + gs_free_error GError *err = NULL; + + if (!g_str_has_prefix (keys[i], "tfilter.")) + continue; + + tfilter_parent = keys[i] + sizeof ("tfilter.") - 1; + tfilter_rest = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, keys[i], NULL); + tfilter_str = g_strdup_printf ("%s%s %s", + _nm_utils_parse_tc_handle (tfilter_parent, NULL) != TC_H_UNSPEC ? "parent " : "", + tfilter_parent, + tfilter_rest); + + tfilter = nm_utils_tc_tfilter_from_str (tfilter_str, &err); + if (!tfilter) { + handle_warn (info, keys[i], NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid tfilter: %s"), + err->message); + } else { + g_ptr_array_add (tfilters, tfilter); + } + } + + if (tfilters->len >= 1) + g_object_set (setting, key, tfilters, NULL); +} + +/*****************************************************************************/ + +/* Some setting properties also contain setting names, such as + * NMSettingConnection's 'type' property (which specifies the base type of the + * connection, eg ethernet or wifi) or the 802-11-wireless setting's + * 'security' property which specifies whether or not the AP requires + * encryption. This function handles translating those properties' values + * from the real setting name to the more-readable alias. + */ +static void +setting_alias_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + const char *str, *alias; + + str = g_value_get_string (value); + alias = nm_keyfile_plugin_get_alias_for_setting_name (str); + nm_keyfile_plugin_kf_set_string (info->keyfile, + nm_setting_get_name (setting), + key, + alias ?: str); +} + +static void +sriov_vfs_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + GPtrArray *vfs; + guint i; + + vfs = g_value_get_boxed (value); + if (!vfs) + return; + + for (i = 0; i < vfs->len; i++) { + const NMSriovVF *vf = vfs->pdata[i]; + gs_free char *kf_value = NULL; + char kf_key[32]; + + kf_value = nm_utils_sriov_vf_to_str (vf, TRUE, NULL); + if (!kf_value) + continue; + + nm_sprintf_buf (kf_key, "vf.%u", nm_sriov_vf_get_index (vf)); + + nm_keyfile_plugin_kf_set_string (info->keyfile, + nm_setting_get_name (setting), + kf_key, + kf_value); + } +} + +static void +write_array_of_uint (GKeyFile *file, + NMSetting *setting, + const char *key, + const GValue *value) +{ + GArray *array; + guint i; + gs_free int *tmp_array = NULL; + + array = (GArray *) g_value_get_boxed (value); + if (!array || !array->len) + return; + + g_return_if_fail (g_array_get_element_size (array) == sizeof (guint)); + + tmp_array = g_new (int, array->len); + for (i = 0; i < array->len; i++) { + guint v = g_array_index (array, guint, i); + + if (v > G_MAXINT) + g_return_if_reached (); + tmp_array[i] = (int) v; + } + + nm_keyfile_plugin_kf_set_integer_list (file, nm_setting_get_name (setting), key, tmp_array, array->len); +} + +static void +dns_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + char **list; + + list = g_value_get_boxed (value); + if (list && list[0]) { + nm_keyfile_plugin_kf_set_string_list (info->keyfile, nm_setting_get_name (setting), key, + (const char **) list, g_strv_length (list)); + } +} + +static void +ip6_addr_gen_mode_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + NMSettingIP6ConfigAddrGenMode addr_gen_mode; + gs_free char *str = NULL; + + addr_gen_mode = (NMSettingIP6ConfigAddrGenMode) g_value_get_int (value); + str = nm_utils_enum_to_str (nm_setting_ip6_config_addr_gen_mode_get_type (), + addr_gen_mode); + nm_keyfile_plugin_kf_set_string (info->keyfile, + nm_setting_get_name (setting), + key, + str); +} + +static void +write_ip_values (GKeyFile *file, + const char *setting_name, + GPtrArray *array, + const char *gateway, + gboolean is_route) +{ + nm_auto_free_gstring GString *output = NULL; + int addr_family; + guint i; + const char *addr; + const char *gw; + guint32 plen; + char key_name[64]; + char *key_name_idx; + + if (!array->len) + return; + + addr_family = nm_streq (setting_name, NM_SETTING_IP4_CONFIG_SETTING_NAME) + ? AF_INET + : AF_INET6; + + strcpy (key_name, is_route ? "route" : "address"); + key_name_idx = key_name + strlen (key_name); + + output = g_string_sized_new (2*INET_ADDRSTRLEN + 10); + for (i = 0; i < array->len; i++) { + gint64 metric = -1; + + if (is_route) { + NMIPRoute *route = array->pdata[i]; + + addr = nm_ip_route_get_dest (route); + plen = nm_ip_route_get_prefix (route); + gw = nm_ip_route_get_next_hop (route); + metric = nm_ip_route_get_metric (route); + } else { + NMIPAddress *address = array->pdata[i]; + + addr = nm_ip_address_get_address (address); + plen = nm_ip_address_get_prefix (address); + gw = (i == 0) + ? gateway + : NULL; + } + + g_string_set_size (output, 0); + g_string_append_printf (output, "%s/%u", addr, plen); + if ( metric != -1 + || gw) { + /* Older versions of the plugin do not support the form + * "a.b.c.d/plen,,metric", so, we always have to write the + * gateway, even if there isn't one. + * The current version supports reading of the above form. + */ + if (!gw) { + if (addr_family == AF_INET) + gw = "0.0.0.0"; + else + gw = "::"; + } + + g_string_append_printf (output, ",%s", gw); + if ( is_route + && metric != -1) + g_string_append_printf (output, ",%lu", (unsigned long) metric); + } + + sprintf (key_name_idx, "%u", i + 1); + nm_keyfile_plugin_kf_set_string (file, setting_name, key_name, output->str); + + if (is_route) { + gs_free char *attributes = NULL; + + attributes = nm_utils_format_variant_attributes (_nm_ip_route_get_attributes (array->pdata[i]), + ',', '='); + if (attributes) { + g_strlcat (key_name, "_options", sizeof (key_name)); + nm_keyfile_plugin_kf_set_string (file, setting_name, key_name, attributes); + } + } + } +} + +static void +addr_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + GPtrArray *array; + const char *setting_name = nm_setting_get_name (setting); + const char *gateway = nm_setting_ip_config_get_gateway (NM_SETTING_IP_CONFIG (setting)); + + array = (GPtrArray *) g_value_get_boxed (value); + if (array && array->len) + write_ip_values (info->keyfile, setting_name, array, gateway, FALSE); +} + +static void +route_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + GPtrArray *array; + const char *setting_name = nm_setting_get_name (setting); + + array = (GPtrArray *) g_value_get_boxed (value); + if (array && array->len) + write_ip_values (info->keyfile, setting_name, array, NULL, TRUE); +} + +static void +bridge_vlan_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + NMBridgeVlan *vlan; + GPtrArray *vlans; + GString *string; + guint i; + + vlans = (GPtrArray *) g_value_get_boxed (value); + if (!vlans || !vlans->len) + return; + + string = g_string_new (""); + for (i = 0; i < vlans->len; i++) { + gs_free char *vlan_str = NULL; + + vlan = vlans->pdata[i]; + vlan_str = nm_bridge_vlan_to_str (vlan, NULL); + if (!vlan_str) + continue; + if (string->len > 0) + g_string_append (string, ","); + nm_utils_escaped_tokens_escape_gstr_assert (vlan_str, ",", string); + } + + nm_keyfile_plugin_kf_set_string (info->keyfile, + nm_setting_get_name (setting), + "vlans", + string->str); + + g_string_free (string, TRUE); +} + + +#define ETHERNET_S390_OPTIONS_GROUP_NAME "ethernet-s390-options" + +static void +wired_s390_options_parser_full (KeyfileReaderInfo *info, + const NMMetaSettingInfo *setting_info, + const NMSettInfoProperty *property_info, + const ParseInfoProperty *pip, + NMSetting *setting) +{ + NMSettingWired *s_wired = NM_SETTING_WIRED (setting); + gs_strfreev char **keys = NULL; + gsize n_keys; + gsize i; + + keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, ETHERNET_S390_OPTIONS_GROUP_NAME, &n_keys, NULL); + for (i = 0; i < n_keys; i++) { + gs_free char *value = NULL; + gs_free char *key_to_free = NULL; + + value = nm_keyfile_plugin_kf_get_string (info->keyfile, + ETHERNET_S390_OPTIONS_GROUP_NAME, + keys[i], + NULL); + if (!value) + continue; + + nm_setting_wired_add_s390_option (s_wired, + nm_keyfile_key_decode (keys[i], + &key_to_free), + value); + } +} + +static void +wired_s390_options_writer_full (KeyfileWriterInfo *info, + const NMMetaSettingInfo *setting_info, + const NMSettInfoProperty *property_info, + const ParseInfoProperty *pip, + NMSetting *setting) +{ + NMSettingWired *s_wired = NM_SETTING_WIRED (setting); + guint i, n; + + n = nm_setting_wired_get_num_s390_options (s_wired); + for (i = 0; i < n; i++) { + const char *opt_key; + const char *opt_val; + gs_free char *key_to_free = NULL; + + nm_setting_wired_get_s390_option (s_wired, i, &opt_key, &opt_val); + nm_keyfile_plugin_kf_set_string (info->keyfile, + ETHERNET_S390_OPTIONS_GROUP_NAME, + nm_keyfile_key_encode (opt_key, &key_to_free), + opt_val); + } +} + +static void +ip_routing_rule_writer_full (KeyfileWriterInfo *info, + const NMMetaSettingInfo *setting_info, + const NMSettInfoProperty *property_info, + const ParseInfoProperty *pip, + NMSetting *setting) +{ + const char *setting_name = nm_setting_get_name (setting); + NMSettingIPConfig *s_ip = NM_SETTING_IP_CONFIG (setting); + guint i, j, n; + char key_name_full[100] = "routing-rule"; + char *key_name_num = &key_name_full[NM_STRLEN ("routing-rule")]; + + n = nm_setting_ip_config_get_num_routing_rules (s_ip); + j = 0; + for (i = 0; i < n; i++) { + NMIPRoutingRule *rule = nm_setting_ip_config_get_routing_rule (s_ip, i); + gs_free char *str = NULL; + + str = nm_ip_routing_rule_to_string (rule, + NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE, + NULL, + NULL); + if (!str) + continue; + + sprintf (key_name_num, "%u", ++j); + nm_keyfile_plugin_kf_set_string (info->keyfile, + setting_name, + key_name_full, + str); + } +} + +static void +qdisc_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + gsize i; + GPtrArray *array; + + array = (GPtrArray *) g_value_get_boxed (value); + if (!array || !array->len) + return; + + for (i = 0; i < array->len; i++) { + NMTCQdisc *qdisc = array->pdata[i]; + GString *key_name = g_string_sized_new (16); + GString *value_str = g_string_sized_new (60); + + g_string_append (key_name, "qdisc."); + _nm_utils_string_append_tc_parent (key_name, NULL, + nm_tc_qdisc_get_parent (qdisc)); + _nm_utils_string_append_tc_qdisc_rest (value_str, qdisc); + + nm_keyfile_plugin_kf_set_string (info->keyfile, + NM_SETTING_TC_CONFIG_SETTING_NAME, + key_name->str, + value_str->str); + + g_string_free (key_name, TRUE); + g_string_free (value_str, TRUE); + } +} + +static void +tfilter_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + gsize i; + GPtrArray *array; + + array = (GPtrArray *) g_value_get_boxed (value); + if (!array || !array->len) + return; + + for (i = 0; i < array->len; i++) { + NMTCTfilter *tfilter = array->pdata[i]; + GString *key_name = g_string_sized_new (16); + GString *value_str = g_string_sized_new (60); + + g_string_append (key_name, "tfilter."); + _nm_utils_string_append_tc_parent (key_name, NULL, + nm_tc_tfilter_get_parent (tfilter)); + _nm_utils_string_append_tc_tfilter_rest (value_str, tfilter, NULL); + + nm_keyfile_plugin_kf_set_string (info->keyfile, + NM_SETTING_TC_CONFIG_SETTING_NAME, + key_name->str, + value_str->str); + + g_string_free (key_name, TRUE); + g_string_free (value_str, TRUE); + } +} + +static void +write_hash_of_string (GKeyFile *file, + NMSetting *setting, + const char *key, + const GValue *value) +{ + GHashTable *hash; + const char *group_name = nm_setting_get_name (setting); + gboolean vpn_secrets = FALSE; + gs_free const char **keys = NULL; + guint i, l; + + nm_assert ( (NM_IS_SETTING_VPN (setting) && nm_streq (key, NM_SETTING_VPN_DATA)) + || (NM_IS_SETTING_VPN (setting) && nm_streq (key, NM_SETTING_VPN_SECRETS)) + || (NM_IS_SETTING_BOND (setting) && nm_streq (key, NM_SETTING_BOND_OPTIONS)) + || (NM_IS_SETTING_USER (setting) && nm_streq (key, NM_SETTING_USER_DATA))); + + /* Write VPN secrets out to a different group to keep them separate */ + if ( NM_IS_SETTING_VPN (setting) + && nm_streq (key, NM_SETTING_VPN_SECRETS)) { + group_name = NM_KEYFILE_GROUP_VPN_SECRETS; + vpn_secrets = TRUE; + } + + hash = g_value_get_boxed (value); + + keys = nm_utils_strdict_get_keys (hash, TRUE, &l); + for (i = 0; i < l; i++) { + gs_free char *to_free = NULL; + const char *property, *data; + + property = keys[i]; + + /* Handle VPN secrets specially; they are nested in the property's hash; + * we don't want to write them if the secret is not saved, not required, + * or owned by a user's secret agent. + */ + if (vpn_secrets) { + NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE; + + if (!nm_setting_get_secret_flags (setting, property, &secret_flags, NULL)) + nm_assert_not_reached (); + if (!_secret_flags_persist_secret (secret_flags)) + continue; + } + + data = g_hash_table_lookup (hash, property); + nm_keyfile_plugin_kf_set_string (file, group_name, + nm_keyfile_key_encode (property, &to_free), + data); + } +} + +static void +ssid_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + GBytes *bytes; + const guint8 *ssid_data; + gsize ssid_len; + const char *setting_name = nm_setting_get_name (setting); + gboolean new_format = TRUE; + gsize semicolons = 0; + gsize i; + + g_return_if_fail (G_VALUE_HOLDS (value, G_TYPE_BYTES)); + + bytes = g_value_get_boxed (value); + if (!bytes) + return; + ssid_data = g_bytes_get_data (bytes, &ssid_len); + if (!ssid_data || !ssid_len) { + nm_keyfile_plugin_kf_set_string (info->keyfile, setting_name, key, ""); + return; + } + + /* Check whether each byte is printable. If not, we have to use an + * integer list, otherwise we can just use a string. + */ + for (i = 0; i < ssid_len; i++) { + const char c = ssid_data[i]; + + if (!g_ascii_isprint (c)) { + new_format = FALSE; + break; + } + if (c == ';') + semicolons++; + } + + if (new_format) { + gs_free char *ssid = NULL; + + if (semicolons == 0) + ssid = g_strndup ((char *) ssid_data, ssid_len); + else { + /* Escape semicolons with backslashes to make strings + * containing ';', such as '16;17;' unambiguous */ + gsize j = 0; + + ssid = g_malloc (ssid_len + semicolons + 1); + for (i = 0; i < ssid_len; i++) { + if (ssid_data[i] == ';') + ssid[j++] = '\\'; + ssid[j++] = ssid_data[i]; + } + ssid[j] = '\0'; + } + nm_keyfile_plugin_kf_set_string (info->keyfile, setting_name, key, ssid); + } else + nm_keyfile_plugin_kf_set_integer_list_uint8 (info->keyfile, setting_name, key, ssid_data, ssid_len); +} + +static void +password_raw_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + const char *setting_name = nm_setting_get_name (setting); + GBytes *array; + gsize len; + const guint8 *data; + + g_return_if_fail (G_VALUE_HOLDS (value, G_TYPE_BYTES)); + + array = (GBytes *) g_value_get_boxed (value); + if (!array) + return; + data = g_bytes_get_data (array, &len); + if (!data) + len = 0; + nm_keyfile_plugin_kf_set_integer_list_uint8 (info->keyfile, setting_name, key, data, len); +} + +/*****************************************************************************/ + +static void +cert_writer_default (NMConnection *connection, + GKeyFile *file, + NMKeyfileWriteTypeDataCert *cert_data) +{ + const char *setting_name = nm_setting_get_name (NM_SETTING (cert_data->setting)); + NMSetting8021xCKScheme scheme; + + scheme = cert_data->vtable->scheme_func (cert_data->setting); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) { + gs_free char *path_free = NULL; + gs_free char *base_dir = NULL; + gs_free char *tmp = NULL; + const char *path; + + path = cert_data->vtable->path_func (cert_data->setting); + g_assert (path); + + /* If the path is relative, make it an absolute path. + * Relative paths make a keyfile not easily usable in another + * context. */ + if (path[0] && path[0] != '/') { + base_dir = g_get_current_dir (); + path_free = g_strconcat (base_dir, "/", path, NULL); + path = path_free; + } else + base_dir = g_path_get_dirname (path); + + /* path cannot start with "file://" or "data:;base64,", because it is an absolute path. + * Still, make sure that a prefix-less path will be recognized. This can happen + * for example if the path is longer then 500 chars. */ + tmp = nm_keyfile_detect_unqualified_path_scheme (base_dir, path, -1, FALSE, NULL); + if (tmp) + nm_clear_g_free (&tmp); + else { + tmp = g_strconcat (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, path, NULL); + path = tmp; + } + + /* Path contains at least a '/', hence it cannot be recognized as the old + * binary format consisting of a list of integers. */ + + nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->vtable->setting_key, path); + } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) { + GBytes *blob; + const guint8 *blob_data; + gsize blob_len; + gs_free char *blob_base64 = NULL; + gs_free char *val = NULL; + + blob = cert_data->vtable->blob_func (cert_data->setting); + g_assert (blob); + blob_data = g_bytes_get_data (blob, &blob_len); + + blob_base64 = g_base64_encode (blob_data, blob_len); + val = g_strconcat (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB, blob_base64, NULL); + + nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->vtable->setting_key, val); + } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_PKCS11) { + nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->vtable->setting_key, + cert_data->vtable->uri_func (cert_data->setting)); + } else { + /* scheme_func() returns UNKNOWN in all other cases. The only valid case + * where a scheme is allowed to be UNKNOWN, is unsetting the value. In this + * case, we don't expect the writer to be called, because the default value + * will not be serialized. + * The only other reason for the scheme to be UNKNOWN is an invalid cert. + * But our connection verifies, so that cannot happen either. */ + g_return_if_reached (); + } +} + +static void +cert_writer (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value) +{ + const NMSetting8021xSchemeVtable *objtype = NULL; + guint i; + NMKeyfileWriteTypeDataCert type_data = { 0 }; + + for (i = 0; nm_setting_8021x_scheme_vtable[i].setting_key; i++) { + if (nm_streq0 (nm_setting_8021x_scheme_vtable[i].setting_key, key)) { + objtype = &nm_setting_8021x_scheme_vtable[i]; + break; + } + } + if (!objtype) + g_return_if_reached (); + + type_data.setting = NM_SETTING_802_1X (setting); + type_data.vtable = objtype; + + if (info->handler) { + if (info->handler (info->connection, + info->keyfile, + NM_KEYFILE_WRITE_TYPE_CERT, + &type_data, + info->user_data, + &info->error)) + return; + if (info->error) + return; + } + + cert_writer_default (info->connection, info->keyfile, &type_data); +} + +/*****************************************************************************/ + +struct _ParseInfoProperty { + const char *property_name; + union { + void (*parser) (KeyfileReaderInfo *info, + NMSetting *setting, + const char *key); + void (*parser_full) (KeyfileReaderInfo *info, + const NMMetaSettingInfo *setting_info, + const NMSettInfoProperty *property_info, + const ParseInfoProperty *pip, + NMSetting *setting); + }; + union { + void (*writer) (KeyfileWriterInfo *info, + NMSetting *setting, + const char *key, + const GValue *value); + void (*writer_full) (KeyfileWriterInfo *info, + const NMMetaSettingInfo *setting_info, + const NMSettInfoProperty *property_info, + const ParseInfoProperty *pip, + NMSetting *setting); + }; + bool parser_skip; + bool parser_no_check_key:1; + bool writer_skip:1; + bool has_writer_full:1; + bool has_parser_full:1; + + /* usually, we skip to write values that have their + * default value. By setting this flag to TRUE, also + * default values are written. */ + bool writer_persist_default:1; +}; + +#define PARSE_INFO_PROPERTY(_property_name, ...) \ + (&((const ParseInfoProperty) { \ + .property_name = _property_name, \ + __VA_ARGS__ \ + })) + +#define PARSE_INFO_PROPERTIES(...) \ + .properties = ((const ParseInfoProperty*const[]) { \ + __VA_ARGS__ \ + NULL, \ + }) + +typedef struct { + const ParseInfoProperty*const*properties; +} ParseInfoSetting; + +#define PARSE_INFO_SETTING(setting_type, ...) \ + [setting_type] = (&((const ParseInfoSetting) { \ + __VA_ARGS__ \ + })) + +static const ParseInfoSetting *const parse_infos[_NM_META_SETTING_TYPE_NUM] = { + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_WIRELESS, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_WIRELESS_BSSID, + .parser = mac_address_parser_ETHER, + ), + PARSE_INFO_PROPERTY (NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, + .parser = mac_address_parser_ETHER_cloned, + ), + PARSE_INFO_PROPERTY (NM_SETTING_WIRELESS_MAC_ADDRESS, + .parser = mac_address_parser_ETHER, + ), + PARSE_INFO_PROPERTY (NM_SETTING_WIRELESS_SSID, + .parser = ssid_parser, + .writer = ssid_writer, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_802_1X, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_802_1X_CA_CERT, + .parser = cert_parser, + .writer = cert_writer, + ), + PARSE_INFO_PROPERTY (NM_SETTING_802_1X_CLIENT_CERT, + .parser = cert_parser, + .writer = cert_writer, + ), + PARSE_INFO_PROPERTY (NM_SETTING_802_1X_PASSWORD_RAW, + .parser = password_raw_parser, + .writer = password_raw_writer, + ), + PARSE_INFO_PROPERTY (NM_SETTING_802_1X_PHASE2_CA_CERT, + .parser = cert_parser, + .writer = cert_writer, + ), + PARSE_INFO_PROPERTY (NM_SETTING_802_1X_PHASE2_CLIENT_CERT, + .parser = cert_parser, + .writer = cert_writer, + ), + PARSE_INFO_PROPERTY (NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, + .parser = cert_parser, + .writer = cert_writer, + ), + PARSE_INFO_PROPERTY (NM_SETTING_802_1X_PRIVATE_KEY, + .parser = cert_parser, + .writer = cert_writer, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_WIRED, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_WIRED_CLONED_MAC_ADDRESS, + .parser = mac_address_parser_ETHER_cloned, + ), + PARSE_INFO_PROPERTY (NM_SETTING_WIRED_MAC_ADDRESS, + .parser = mac_address_parser_ETHER, + ), + PARSE_INFO_PROPERTY (NM_SETTING_WIRED_S390_OPTIONS, + .parser_no_check_key = TRUE, + .parser_full = wired_s390_options_parser_full, + .writer_full = wired_s390_options_writer_full, + .has_parser_full = TRUE, + .has_writer_full = TRUE, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_BLUETOOTH, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_BLUETOOTH_BDADDR, + .parser = mac_address_parser_ETHER, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_BOND, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_BOND_OPTIONS, + .parser_no_check_key = TRUE, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_BRIDGE, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_BRIDGE_MAC_ADDRESS, + .parser = mac_address_parser_ETHER, + ), + PARSE_INFO_PROPERTY (NM_SETTING_BRIDGE_VLANS, + .parser_no_check_key = TRUE, + .parser = bridge_vlan_parser, + .writer = bridge_vlan_writer, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_BRIDGE_PORT, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_BRIDGE_PORT_VLANS, + .parser_no_check_key = TRUE, + .parser = bridge_vlan_parser, + .writer = bridge_vlan_writer, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_CONNECTION, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_CONNECTION_READ_ONLY, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_CONNECTION_TYPE, + .parser = setting_alias_parser, + .writer = setting_alias_writer, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_INFINIBAND, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_INFINIBAND_MAC_ADDRESS, + .parser = mac_address_parser_INFINIBAND, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_IP4_CONFIG, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ADDRESSES, + .parser_no_check_key = TRUE, + .parser = ip_address_or_route_parser, + .writer = addr_writer, + ), + PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_DNS, + .parser_no_check_key = TRUE, + .parser = ip_dns_parser, + .writer = dns_writer, + ), + PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_GATEWAY, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ROUTES, + .parser_no_check_key = TRUE, + .parser = ip_address_or_route_parser, + .writer = route_writer, + ), + PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ROUTING_RULES, + .parser_no_check_key = TRUE, + .parser_full = ip_routing_rule_parser_full, + .writer_full = ip_routing_rule_writer_full, + .has_parser_full = TRUE, + .has_writer_full = TRUE, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_IP6_CONFIG, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE, + .parser_no_check_key = TRUE, + .parser = ip6_addr_gen_mode_parser, + .writer = ip6_addr_gen_mode_writer, + .writer_persist_default = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ADDRESSES, + .parser_no_check_key = TRUE, + .parser = ip_address_or_route_parser, + .writer = addr_writer, + ), + PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_DNS, + .parser_no_check_key = TRUE, + .parser = ip_dns_parser, + .writer = dns_writer, + ), + PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_GATEWAY, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ROUTES, + .parser_no_check_key = TRUE, + .parser = ip_address_or_route_parser, + .writer = route_writer, + ), + PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ROUTING_RULES, + .parser_no_check_key = TRUE, + .parser_full = ip_routing_rule_parser_full, + .writer_full = ip_routing_rule_writer_full, + .has_parser_full = TRUE, + .has_writer_full = TRUE, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_SERIAL, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_SERIAL_PARITY, + .parser = parity_parser, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_SRIOV, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_SRIOV_VFS, + .parser_no_check_key = TRUE, + .parser = sriov_vfs_parser, + .writer = sriov_vfs_writer, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_TC_CONFIG, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_TC_CONFIG_QDISCS, + .parser_no_check_key = TRUE, + .parser = qdisc_parser, + .writer = qdisc_writer, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TC_CONFIG_TFILTERS, + .parser_no_check_key = TRUE, + .parser = tfilter_parser, + .writer = tfilter_writer, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_TEAM, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_CONFIG, + .parser = team_config_parser, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_LINK_WATCHERS, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_MCAST_REJOIN_COUNT, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_MCAST_REJOIN_INTERVAL, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_NOTIFY_PEERS_COUNT, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_NOTIFY_PEERS_INTERVAL, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_ACTIVE, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_FAST_RATE, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_HWADDR_POLICY, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_MIN_PORTS, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_SYS_PRIO, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_TX_BALANCER, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_TX_HASH, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_TEAM_PORT, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_CONFIG, + .parser = team_config_parser, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_LACP_KEY, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_LACP_PRIO, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_LINK_WATCHERS, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_PRIO, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_QUEUE_ID, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_STICKY, + .parser_skip = TRUE, + .writer_skip = TRUE, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_USER, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_USER_DATA, + .parser_no_check_key = TRUE, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_VLAN, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_VLAN_FLAGS, + .writer_persist_default = TRUE, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_VPN, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_VPN_DATA, + .parser_no_check_key = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_VPN_PERSISTENT, + .parser_no_check_key = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_VPN_SECRETS, + .parser_no_check_key = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_VPN_SERVICE_TYPE, + .parser_no_check_key = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_VPN_TIMEOUT, + .parser_no_check_key = TRUE, + ), + PARSE_INFO_PROPERTY (NM_SETTING_VPN_USER_NAME, + .parser_no_check_key = TRUE, + ), + ), + ), + PARSE_INFO_SETTING (NM_META_SETTING_TYPE_WIMAX, + PARSE_INFO_PROPERTIES ( + PARSE_INFO_PROPERTY (NM_SETTING_WIMAX_MAC_ADDRESS, + .parser = mac_address_parser_ETHER, + ), + ), + ), +}; + +static void +_parse_info_find (NMSetting *setting, + const char *property_name, + const NMMetaSettingInfo **out_setting_info, + const ParseInfoSetting **out_parse_info_setting, + const ParseInfoProperty **out_parse_info_property) +{ + const NMMetaSettingInfo *setting_info; + const ParseInfoSetting *pis; + const ParseInfoProperty *pip; + +#if NM_MORE_ASSERTS > 10 + { + guint i, j; + static int asserted = FALSE; + + if (!asserted) { + for (i = 0; i < G_N_ELEMENTS (parse_infos); i++) { + pis = parse_infos[i]; + + if (!pis) + continue; + if (!pis->properties) + continue; + + g_assert (pis->properties[0]); + for (j = 0; pis->properties[j]; j++) { + const ParseInfoProperty *pip0; + const ParseInfoProperty *pipj = pis->properties[j]; + + g_assert (pipj->property_name); + if ( j > 0 + && (pip0 = pis->properties[j - 1]) + && strcmp (pip0->property_name, pipj->property_name) >= 0) { + g_error ("Wrong order at index #%d.%d: \"%s.%s\" before \"%s.%s\"", + i, j - 1, + nm_meta_setting_infos[i].setting_name, pip0->property_name, + nm_meta_setting_infos[i].setting_name, pipj->property_name); + } + } + } + asserted = TRUE; + } + } +#endif + + if ( !NM_IS_SETTING (setting) + || !(setting_info = NM_SETTING_GET_CLASS (setting)->setting_info)) { + /* handle invalid setting objects gracefully. */ + NM_SET_OUT (out_setting_info, NULL); + NM_SET_OUT (out_parse_info_setting, NULL); + NM_SET_OUT (out_parse_info_property, NULL); + return; + } + + nm_assert (setting_info->setting_name); + nm_assert (_NM_INT_NOT_NEGATIVE (setting_info->meta_type)); + nm_assert (setting_info->meta_type < G_N_ELEMENTS (parse_infos)); + + pis = parse_infos[setting_info->meta_type]; + + pip = NULL; + if ( pis + && property_name) { + gssize idx; + + G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (ParseInfoProperty, property_name) == 0); + + idx = nm_utils_ptrarray_find_binary_search ((gconstpointer *) pis->properties, + NM_PTRARRAY_LEN (pis->properties), + &property_name, + nm_strcmp_p_with_data, + NULL, + NULL, + NULL); + if (idx >= 0) + pip = pis->properties[idx]; + } + + NM_SET_OUT (out_setting_info, setting_info); + NM_SET_OUT (out_parse_info_setting, pis); + NM_SET_OUT (out_parse_info_property, pip); +} + +/*****************************************************************************/ + +static void +read_one_setting_value (KeyfileReaderInfo *info, + NMSetting *setting, + const NMSettInfoProperty *property_info) +{ + GKeyFile *keyfile = info->keyfile; + gs_free_error GError *err = NULL; + const NMMetaSettingInfo *setting_info; + const ParseInfoProperty *pip; + gs_free char *tmp_str = NULL; + const char *key; + GType type; + guint64 u64; + gint64 i64; + + nm_assert (!info->error); + nm_assert ( !property_info->param_spec + || nm_streq (property_info->param_spec->name, property_info->name)); + + key = property_info->name; + + _parse_info_find (setting, key, &setting_info, NULL, &pip); + + nm_assert (setting_info); + + if (!pip) { + if (nm_streq (key, NM_SETTING_NAME)) + return; + if (!property_info->param_spec) + return; + if ((property_info->param_spec->flags & (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)) != G_PARAM_WRITABLE) + return; + } else { + if (pip->parser_skip) + return; + if (pip->has_parser_full) { + pip->parser_full (info, setting_info, property_info, pip, setting); + return; + } + } + + nm_assert (property_info->param_spec); + nm_assert ((property_info->param_spec->flags & (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)) == G_PARAM_WRITABLE); + + /* Check for the exact key in the GKeyFile if required. Most setting + * properties map 1:1 to a key in the GKeyFile, but for those properties + * like IP addresses and routes where more than one value is actually + * encoded by the setting property, this won't be true. + */ + if ( (!pip || !pip->parser_no_check_key) + && !nm_keyfile_plugin_kf_has_key (keyfile, setting_info->setting_name, key, &err)) { + /* Key doesn't exist or an error occurred, thus nothing to do. */ + if (err) { + if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("error loading setting value: %s"), + err->message)) + return; + } + return; + } + + if ( pip + && pip->parser) { + pip->parser (info, setting, key); + return; + } + + type = G_PARAM_SPEC_VALUE_TYPE (property_info->param_spec); + + if (type == G_TYPE_STRING) { + gs_free char *str_val = NULL; + + str_val = nm_keyfile_plugin_kf_get_string (keyfile, setting_info->setting_name, key, &err); + if (!err) + nm_g_object_set_property_string_take (G_OBJECT (setting), key, g_steal_pointer (&str_val), &err); + } else if (type == G_TYPE_UINT) { + tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err); + if (!err) { + u64 = _nm_utils_ascii_str_to_uint64 (tmp_str, 0, 0, G_MAXUINT, G_MAXUINT64); + if ( u64 == G_MAXUINT64 + && errno != 0) { + g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, + _("value cannot be interpreted as integer")); + } else + nm_g_object_set_property_uint (G_OBJECT (setting), key, u64, &err); + } + } else if (type == G_TYPE_INT) { + tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err); + if (!err) { + i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT, G_MAXINT, G_MININT64); + if ( i64 == G_MININT64 + && errno != 0) { + g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, + _("value cannot be interpreted as integer")); + } else + nm_g_object_set_property_int (G_OBJECT (setting), key, i64, &err); + } + } else if (type == G_TYPE_BOOLEAN) { + gboolean bool_val; + + bool_val = nm_keyfile_plugin_kf_get_boolean (keyfile, setting_info->setting_name, key, &err); + if (!err) + nm_g_object_set_property_boolean (G_OBJECT (setting), key, bool_val, &err); + } else if (type == G_TYPE_CHAR) { + tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err); + if (!err) { + /* As documented by glib, G_TYPE_CHAR is really a (signed!) gint8. */ + i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT8, G_MAXINT8, G_MININT64); + if ( i64 == G_MININT64 + && errno != 0) { + g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, + _("value cannot be interpreted as integer")); + } else + nm_g_object_set_property_char (G_OBJECT (setting), key, i64, &err); + } + } else if (type == G_TYPE_UINT64) { + tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err); + if (!err) { + u64 = _nm_utils_ascii_str_to_uint64 (tmp_str, 0, 0, G_MAXUINT64, G_MAXUINT64); + if ( u64 == G_MAXUINT64 + && errno != 0) { + g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, + _("value cannot be interpreted as integer")); + } else + nm_g_object_set_property_uint64 (G_OBJECT (setting), key, u64, &err); + } + } else if (type == G_TYPE_INT64) { + tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err); + if (!err) { + i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT64, G_MAXINT64, G_MAXINT64); + if ( i64 == G_MAXINT64 + && errno != 0) { + g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, + _("value cannot be interpreted as integer")); + } else + nm_g_object_set_property_int64 (G_OBJECT (setting), key, i64, &err); + } + } else if (type == G_TYPE_BYTES) { + gs_free int *tmp = NULL; + GByteArray *array; + GBytes *bytes; + gsize length; + int i; + gboolean already_warned = FALSE; + + tmp = nm_keyfile_plugin_kf_get_integer_list (keyfile, setting_info->setting_name, key, &length, NULL); + + array = g_byte_array_sized_new (length); + for (i = 0; i < length; i++) { + const int val = tmp[i]; + unsigned char v = (unsigned char) (val & 0xFF); + + if (val < 0 || val > 255) { + if ( !already_warned + && !handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid byte element '%d' (not between 0 and 255 inclusive)"), + val)) { + g_byte_array_unref (array); + return; + } + already_warned = TRUE; + } else + g_byte_array_append (array, (const unsigned char *) &v, sizeof (v)); + } + + bytes = g_byte_array_free_to_bytes (array); + g_object_set (setting, key, bytes, NULL); + g_bytes_unref (bytes); + } else if (type == G_TYPE_STRV) { + gs_strfreev char **sa = NULL; + gsize length; + + sa = nm_keyfile_plugin_kf_get_string_list (keyfile, setting_info->setting_name, key, &length, NULL); + g_object_set (setting, key, sa, NULL); + } else if (type == G_TYPE_HASH_TABLE) { + read_hash_of_string (keyfile, setting, key); + } else if (type == G_TYPE_ARRAY) { + read_array_of_uint (keyfile, setting, key); + } else if (G_TYPE_IS_FLAGS (type)) { + tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err); + if (!err) { + u64 = _nm_utils_ascii_str_to_uint64 (tmp_str, 0, 0, G_MAXUINT, G_MAXUINT64); + if ( u64 == G_MAXUINT64 + && errno != 0) { + g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, + _("value cannot be interpreted as integer")); + } else + nm_g_object_set_property_flags (G_OBJECT (setting), key, type, u64, &err); + } + } else if (G_TYPE_IS_ENUM (type)) { + tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err); + if (!err) { + i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT, G_MAXINT, G_MAXINT64); + if ( i64 == G_MAXINT64 + && errno != 0) { + g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE, + _("value cannot be interpreted as integer")); + } else + nm_g_object_set_property_enum (G_OBJECT (setting), key, type, i64, &err); + } + } else + g_return_if_reached (); + + if (err) { + if ( err->domain == G_KEY_FILE_ERROR + && NM_IN_SET (err->code, G_KEY_FILE_ERROR_GROUP_NOT_FOUND, + G_KEY_FILE_ERROR_KEY_NOT_FOUND)) { + /* ignore such errors. The key is not present. */ + } else { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid setting: %s"), err->message); + } + } +} + +static void +_read_setting (KeyfileReaderInfo *info) +{ + const NMSettInfoSetting *sett_info; + gs_unref_object NMSetting *setting = NULL; + const char *alias; + GType type; + guint i; + + alias = nm_keyfile_plugin_get_setting_name_for_alias (info->group); + if (!alias) + alias = info->group; + + type = nm_setting_lookup_type (alias); + if (!type) { + handle_warn (info, NULL, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid setting name '%s'"), info->group); + return; + } + + setting = g_object_new (type, NULL); + + info->setting = setting; + + sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting)); + + if (sett_info->detail.gendata_info) { + gs_free char **keys = NULL; + gsize k, n_keys; + + keys = g_key_file_get_keys (info->keyfile, info->group, &n_keys, NULL); + if (!keys) + n_keys = 0; + if (n_keys > 0) { + GHashTable *h = _nm_setting_gendata_hash (setting, TRUE); + + nm_utils_strv_sort (keys, n_keys); + for (k = 0; k < n_keys; k++) { + gs_free char *key = keys[k]; + gs_free_error GError *local = NULL; + const GVariantType *variant_type; + GVariant *variant; + + /* a GKeyFile can return duplicate keys, there is just no API to make sense + * of them. Skip them. */ + if ( k + 1 < n_keys + && nm_streq (key, keys[k + 1])) + continue; + + /* currently, the API is very simple. The setting class just returns + * the desired variant type, and keyfile reader will try to parse + * it accordingly. Note, that this does currently not allow, that + * a particular key can contain different variant types, nor is it + * very flexible in general. + * + * We add flexibility when we need it. Keep it simple for now. */ + variant_type = sett_info->detail.gendata_info->get_variant_type (sett_info, + key, + &local); + if (!variant_type) { + if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid key '%s.%s'"), + info->group, key)) + break; + continue; + } + + if (g_variant_type_equal (variant_type, G_VARIANT_TYPE_BOOLEAN)) { + gboolean v; + + v = g_key_file_get_boolean (info->keyfile, + info->group, + key, + &local); + if (local) { + if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("key '%s.%s' is not boolean"), + info->group, key)) + break; + continue; + } + variant = g_variant_new_boolean (v); + } else { + nm_assert_not_reached (); + continue; + } + + g_hash_table_insert (h, + g_steal_pointer (&key), + g_variant_take_ref (variant)); + } + for (; k < n_keys; k++) + g_free (keys[k]); + } + } + + for (i = 0; i < sett_info->property_infos_len; i++) { + read_one_setting_value (info, + setting, + &sett_info->property_infos[i]); + if (info->error) + goto out; + } + +out: + info->setting = NULL; + if (!info->error) + nm_connection_add_setting (info->connection, g_steal_pointer (&setting)); +} + +static void +_read_setting_wireguard_peer (KeyfileReaderInfo *info) +{ + gs_unref_object NMSettingWireGuard *s_wg_new = NULL; + nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL; + gs_free_error GError *error = NULL; + NMSettingWireGuard *s_wg; + gs_free char *str = NULL; + const char *cstr = NULL; + const char *key; + gint64 i64; + gs_strfreev char **sa = NULL; + gsize n_sa; + + peer = nm_wireguard_peer_new (); + + nm_assert (g_str_has_prefix (info->group, NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER)); + cstr = &info->group[NM_STRLEN (NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER)]; + if ( !nm_utils_base64secret_normalize (cstr, NM_WIREGUARD_PUBLIC_KEY_LEN, &str) + || !nm_streq0 (str, cstr)) { + /* the group name must be identical to the normalized(!) key, so that it + * is uniquely identified. */ + handle_warn (info, NULL, NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid peer public key in section '%s'"), + info->group); + return; + } + nm_wireguard_peer_set_public_key (peer, cstr, TRUE); + nm_clear_g_free (&str); + + key = NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY; + str = nm_keyfile_plugin_kf_get_string (info->keyfile, info->group, key, NULL); + if (str) { + if (!nm_wireguard_peer_set_preshared_key (peer, str, FALSE)) { + if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("key '%s.%s' is not a valid 256 bit key in base64 encoding"), + info->group, key)) + return; + } + nm_clear_g_free (&str); + } + + key = NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS; + i64 = nm_keyfile_plugin_kf_get_int64 (info->keyfile, info->group, key, 0, 0, NM_SETTING_SECRET_FLAG_ALL, -1, NULL); + if (errno != ENODATA) { + if ( i64 == -1 + || !_nm_setting_secret_flags_valid (i64)) { + if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("key '%s.%s' is not a valid secret flag"), + info->group, key)) + return; + } else + nm_wireguard_peer_set_preshared_key_flags (peer, i64); + } + + key = NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE; + i64 = nm_keyfile_plugin_kf_get_int64 (info->keyfile, info->group, key, 0, 0, G_MAXUINT32, -1, NULL); + if (errno != ENODATA) { + if (i64 == -1) { + if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("key '%s.%s' is not a integer in range 0 to 2^32"), + info->group, key)) + return; + } else + nm_wireguard_peer_set_persistent_keepalive (peer, i64); + } + + key = NM_WIREGUARD_PEER_ATTR_ENDPOINT; + str = nm_keyfile_plugin_kf_get_string (info->keyfile, info->group, key, NULL); + if (str && str[0]) { + if (!nm_wireguard_peer_set_endpoint (peer, str, FALSE)) { + if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("key '%s.%s' is not a valid endpoint"), + info->group, key)) + return; + } + } + nm_clear_g_free (&str); + + key = NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS; + sa = nm_keyfile_plugin_kf_get_string_list (info->keyfile, info->group, key, &n_sa, NULL); + if (n_sa > 0) { + gboolean has_error = FALSE; + gsize i; + + for (i = 0; i < n_sa; i++) { + if (!nm_utils_parse_inaddr_prefix_bin (AF_UNSPEC, sa[i], NULL, NULL, NULL)) { + has_error = TRUE; + continue; + } + nm_wireguard_peer_append_allowed_ip (peer, sa[i], TRUE); + } + if (has_error) { + if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("key '%s.%s' has invalid allowed-ips"), + info->group, key)) + return; + } + } + nm_clear_pointer (&sa, g_strfreev); + + if (info->error) + return; + + if (!nm_wireguard_peer_is_valid (peer, TRUE, TRUE, &error)) { + handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN, + _("peer '%s' is invalid: %s"), + info->group, error->message); + return; + } + + s_wg = NM_SETTING_WIREGUARD (nm_connection_get_setting (info->connection, NM_TYPE_SETTING_WIREGUARD)); + if (!s_wg) { + s_wg_new = NM_SETTING_WIREGUARD (nm_setting_wireguard_new ()); + s_wg = s_wg_new; + } + + nm_setting_wireguard_append_peer (s_wg, peer); + + if (s_wg_new) { + nm_connection_add_setting (info->connection, + NM_SETTING (g_steal_pointer (&s_wg_new))); + } +} + +static void +_read_setting_vpn_secrets (KeyfileReaderInfo *info) +{ + gs_strfreev char **keys = NULL; + gsize i, n_keys; + NMSettingVpn *s_vpn; + + s_vpn = nm_connection_get_setting_vpn (info->connection); + if (!s_vpn) { + /* if we don't also have a [vpn] section (which must be parsed earlier), + * we don't do anything. */ + nm_assert (!g_key_file_has_group (info->keyfile, "vpn")); + return; + } + + keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, NM_KEYFILE_GROUP_VPN_SECRETS, &n_keys, NULL); + for (i = 0; i < n_keys; i++) { + gs_free char *secret = NULL; + + secret = nm_keyfile_plugin_kf_get_string (info->keyfile, NM_KEYFILE_GROUP_VPN_SECRETS, keys[i], NULL); + if (secret) + nm_setting_vpn_add_secret (s_vpn, keys[i], secret); + } +} + +gboolean +nm_keyfile_read_ensure_id (NMConnection *connection, + const char *fallback_id) +{ + NMSettingConnection *s_con; + + g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE); + g_return_val_if_fail (fallback_id, FALSE); + + s_con = nm_connection_get_setting_connection (connection); + g_return_val_if_fail (NM_IS_SETTING_CONNECTION (s_con), FALSE); + + if (nm_setting_connection_get_id (s_con)) + return FALSE; + + g_object_set (s_con, NM_SETTING_CONNECTION_ID, fallback_id, NULL); + return TRUE; +} + +gboolean +nm_keyfile_read_ensure_uuid (NMConnection *connection, + const char *fallback_uuid_seed) +{ + NMSettingConnection *s_con; + gs_free char *hashed_uuid = NULL; + + g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE); + g_return_val_if_fail (fallback_uuid_seed, FALSE); + + s_con = nm_connection_get_setting_connection (connection); + g_return_val_if_fail (NM_IS_SETTING_CONNECTION (s_con), FALSE); + + if (nm_setting_connection_get_uuid (s_con)) + return FALSE; + + hashed_uuid = _nm_utils_uuid_generate_from_strings ("keyfile", fallback_uuid_seed, NULL); + g_object_set (s_con, NM_SETTING_CONNECTION_UUID, hashed_uuid, NULL); + return TRUE; +} + +/** + * nm_keyfile_read: + * @keyfile: the keyfile from which to create the connection + * @base_dir: when reading certificates from files with relative name, + * the relative path is made absolute using @base_dir. This must + * be an absolute path. + * @handler: read handler + * @user_data: user data for read handler + * @error: error + * + * Tries to create a NMConnection from a keyfile. The resulting keyfile is + * not normalized and might not even verify. + * + * Returns: (transfer full): on success, returns the created connection. + */ +NMConnection * +nm_keyfile_read (GKeyFile *keyfile, + const char *base_dir, + NMKeyfileReadHandler handler, + void *user_data, + GError **error) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection *s_con; + gs_strfreev char **groups = NULL; + gsize n_groups; + gsize i; + gboolean vpn_secrets = FALSE; + KeyfileReaderInfo info; + + g_return_val_if_fail (keyfile, NULL); + g_return_val_if_fail (!error || !*error, NULL); + g_return_val_if_fail (base_dir && base_dir[0] == '/', NULL); + + connection = nm_simple_connection_new (); + + info = (KeyfileReaderInfo) { + .connection = connection, + .keyfile = keyfile, + .base_dir = base_dir, + .handler = handler, + .user_data = user_data, + }; + + groups = g_key_file_get_groups (keyfile, &n_groups); + if (!groups) + n_groups = 0; + + for (i = 0; i < n_groups; i++) { + + info.group = groups[i]; + + if (nm_streq (groups[i], NM_KEYFILE_GROUP_VPN_SECRETS)) { + /* Only read out secrets when needed */ + vpn_secrets = TRUE; + } else if (NM_STR_HAS_PREFIX (groups[i], NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER)) + _read_setting_wireguard_peer (&info); + else if (nm_streq (groups[i], NM_KEYFILE_GROUP_NMMETA)) { + /* pass */ + } else + _read_setting (&info); + + info.group = NULL; + + if (info.error) + goto out_with_info_error; + } + + s_con = nm_connection_get_setting_connection (connection); + if (!s_con) { + s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ()); + nm_connection_add_setting (connection, NM_SETTING (s_con)); + } + + /* Make sure that we have 'interface-name' even if it was specified in the + * "wrong" (ie, deprecated) group. + */ + if ( !nm_setting_connection_get_interface_name (s_con) + && nm_setting_connection_get_connection_type (s_con)) { + gs_free char *interface_name = NULL; + + interface_name = g_key_file_get_string (keyfile, + nm_setting_connection_get_connection_type (s_con), + "interface-name", + NULL); + if (interface_name) + g_object_set (s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, interface_name, NULL); + } + + if (vpn_secrets) { + info.group = NM_KEYFILE_GROUP_VPN_SECRETS; + _read_setting_vpn_secrets (&info); + info.group = NULL;; + if (info.error) + goto out_with_info_error; + } + + return g_steal_pointer (&connection); + +out_with_info_error: + g_propagate_error (error, info.error); + return NULL; +} + +/*****************************************************************************/ + +static void +write_setting_value (KeyfileWriterInfo *info, + NMSetting *setting, + const NMSettInfoProperty *property_info) +{ + const NMMetaSettingInfo *setting_info; + const ParseInfoProperty *pip; + const char *key; + char numstr[64]; + GValue value; + GType type; + + nm_assert (!info->error); + nm_assert ( !property_info->param_spec + || nm_streq (property_info->param_spec->name, property_info->name)); + + key = property_info->name; + + _parse_info_find (setting, key, &setting_info, NULL, &pip); + + if (!pip) { + if (!setting_info) { + /* the setting type is unknown. That is highly unexpected + * (and as this is currently only called from NetworkManager + * daemon, not possible). + * + * Still, handle it gracefully, because later keyfile writer will become + * public API of libnm, where @setting is (untrusted) user input. + * + * Gracefully here just means: ignore the setting. */ + return; + } + if (!property_info->param_spec) + return; + if (nm_streq (key, NM_SETTING_NAME)) + return; + } else { + if (pip->has_writer_full) { + pip->writer_full (info, setting_info, property_info, pip, setting); + return; + } + if (pip->writer_skip) + return; + } + + nm_assert (property_info->param_spec); + + /* Don't write secrets that are owned by user secret agents or aren't + * supposed to be saved. VPN secrets are handled specially though since + * the secret flags there are in a third-level hash in the 'secrets' + * property. + */ + if ( (property_info->param_spec->flags & NM_SETTING_PARAM_SECRET) + && !NM_IS_SETTING_VPN (setting)) { + NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE; + + if (!nm_setting_get_secret_flags (setting, key, &secret_flags, NULL)) + g_return_if_reached (); + if (!_secret_flags_persist_secret (secret_flags)) + return; + } + + value = (GValue) { 0 }; + + g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (property_info->param_spec)); + g_object_get_property (G_OBJECT (setting), property_info->param_spec->name, &value); + + if ( (!pip || !pip->writer_persist_default) + && g_param_value_defaults (property_info->param_spec, &value)) { + nm_assert (!g_key_file_has_key (info->keyfile, setting_info->setting_name, key, NULL)); + goto out_unset_value; + } + + if ( pip + && pip->writer) { + pip->writer (info, setting, key, &value); + goto out_unset_value; + } + + type = G_VALUE_TYPE (&value); + if (type == G_TYPE_STRING) { + const char *str; + + str = g_value_get_string (&value); + if (str) + nm_keyfile_plugin_kf_set_string (info->keyfile, setting_info->setting_name, key, str); + } else if (type == G_TYPE_UINT) { + nm_sprintf_buf (numstr, "%u", g_value_get_uint (&value)); + nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr); + } else if (type == G_TYPE_INT) { + nm_sprintf_buf (numstr, "%d", g_value_get_int (&value)); + nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr); + } else if (type == G_TYPE_UINT64) { + nm_sprintf_buf (numstr, "%" G_GUINT64_FORMAT, g_value_get_uint64 (&value)); + nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr); + } else if (type == G_TYPE_INT64) { + nm_sprintf_buf (numstr, "%" G_GINT64_FORMAT, g_value_get_int64 (&value)); + nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr); + } else if (type == G_TYPE_BOOLEAN) { + nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, + g_value_get_boolean (&value) + ? "true" + : "false"); + } else if (type == G_TYPE_CHAR) { + nm_sprintf_buf (numstr, "%d", (int) g_value_get_schar (&value)); + nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr); + } else if (type == G_TYPE_BYTES) { + GBytes *bytes; + const guint8 *data; + gsize len = 0; + + bytes = g_value_get_boxed (&value); + data = bytes ? g_bytes_get_data (bytes, &len) : NULL; + + if (data != NULL && len > 0) + nm_keyfile_plugin_kf_set_integer_list_uint8 (info->keyfile, setting_info->setting_name, key, data, len); + } else if (type == G_TYPE_STRV) { + char **array; + + array = (char **) g_value_get_boxed (&value); + nm_keyfile_plugin_kf_set_string_list (info->keyfile, setting_info->setting_name, key, (const char **const) array, g_strv_length (array)); + } else if (type == G_TYPE_HASH_TABLE) { + write_hash_of_string (info->keyfile, setting, key, &value); + } else if (type == G_TYPE_ARRAY) { + write_array_of_uint (info->keyfile, setting, key, &value); + } else if (G_VALUE_HOLDS_FLAGS (&value)) { + nm_sprintf_buf (numstr, "%u", g_value_get_flags (&value)); + nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr); + } else if (G_VALUE_HOLDS_ENUM (&value)) { + nm_sprintf_buf (numstr, "%d", g_value_get_enum (&value)); + nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr); + } else + g_return_if_reached (); + +out_unset_value: + g_value_unset (&value); +} + +static void +_write_setting_wireguard (NMSetting *setting, KeyfileWriterInfo *info) +{ + NMSettingWireGuard *s_wg; + guint i_peer, n_peers; + + s_wg = NM_SETTING_WIREGUARD (setting); + + n_peers = nm_setting_wireguard_get_peers_len (s_wg); + for (i_peer = 0; i_peer < n_peers; i_peer++) { + NMWireGuardPeer *peer = nm_setting_wireguard_get_peer (s_wg, i_peer); + const char *public_key; + char group[NM_STRLEN (NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER) + 200]; + NMSettingSecretFlags secret_flags; + gboolean any_key = FALSE; + guint i_aip, n_aip; + const char *cstr; + guint32 u32; + + public_key = nm_wireguard_peer_get_public_key (peer); + if ( !public_key + || !public_key[0] + || !NM_STRCHAR_ALL (public_key, ch, nm_sd_utils_unbase64char (ch, TRUE) >= 0)) { + /* invalid peer. Skip it */ + continue; + } + + if (g_snprintf (group, + sizeof (group), + "%s%s", + NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER, + nm_wireguard_peer_get_public_key (peer)) >= sizeof (group)) { + /* Too long. Not a valid public key. Skip the peer. */ + continue; + } + + cstr = nm_wireguard_peer_get_endpoint (peer); + if (cstr) { + g_key_file_set_string (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_ENDPOINT, cstr); + any_key = TRUE; + } + + secret_flags = nm_wireguard_peer_get_preshared_key_flags (peer); + if (_secret_flags_persist_secret (secret_flags)) { + cstr = nm_wireguard_peer_get_preshared_key (peer); + if (cstr) { + g_key_file_set_string (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, cstr); + any_key = TRUE; + } + } + + /* usually, we don't persist the secret-flags 0 (because they are the default). + * For WireGuard peers, the default secret-flags for preshared-key are 4 (not-required). + * So, in this case behave differently: a missing preshared-key-flag setting means + * "not-required". */ + if (secret_flags != NM_SETTING_SECRET_FLAG_NOT_REQUIRED) { + g_key_file_set_int64 (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS, secret_flags); + any_key = TRUE; + } + + u32 = nm_wireguard_peer_get_persistent_keepalive (peer); + if (u32) { + g_key_file_set_uint64 (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE, u32); + any_key = TRUE; + } + + n_aip = nm_wireguard_peer_get_allowed_ips_len (peer); + if (n_aip > 0) { + gs_free const char **strv = NULL; + + strv = g_new (const char *, ((gsize) n_aip) + 1); + for (i_aip = 0; i_aip < n_aip; i_aip++) + strv[i_aip] = nm_wireguard_peer_get_allowed_ip (peer, i_aip, NULL); + strv[n_aip] = NULL; + g_key_file_set_string_list (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS, + strv, n_aip); + any_key = TRUE; + } + + if (!any_key) { + /* we cannot omit all keys. At an empty endpoint. */ + g_key_file_set_string (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_ENDPOINT, ""); + } + } +} + +GKeyFile * +nm_keyfile_write (NMConnection *connection, + NMKeyfileWriteHandler handler, + void *user_data, + GError **error) +{ + gs_unref_keyfile GKeyFile *keyfile = NULL; + KeyfileWriterInfo info; + gs_free NMSetting **settings = NULL; + guint i, j, n_settings = 0; + + g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL); + g_return_val_if_fail (!error || !*error, NULL); + + if (!nm_connection_verify (connection, error)) + return NULL; + + keyfile = g_key_file_new (); + + info = (KeyfileWriterInfo) { + .connection = connection, + .keyfile = keyfile, + .error = NULL, + .handler = handler, + .user_data = user_data, + }; + + settings = nm_connection_get_settings (connection, &n_settings); + for (i = 0; i < n_settings; i++) { + const NMSettInfoSetting *sett_info; + NMSetting *setting = settings[i]; + const char *setting_name; + const char *setting_alias; + + sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting)); + + setting_name = sett_info->setting_class->setting_info->setting_name; + + if (sett_info->detail.gendata_info) { + guint k, n_keys; + const char *const*keys; + + nm_assert (!nm_keyfile_plugin_get_alias_for_setting_name (sett_info->setting_class->setting_info->setting_name)); + + n_keys = _nm_setting_gendata_get_all (setting, &keys, NULL); + + if (n_keys > 0) { + GHashTable *h = _nm_setting_gendata_hash (setting, FALSE); + + for (k = 0; k < n_keys; k++) { + const char *key = keys[k]; + GVariant *v; + + v = g_hash_table_lookup (h, key); + + if (g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN)) { + g_key_file_set_boolean (info.keyfile, + setting_name, + key, + g_variant_get_boolean (v)); + } else { + /* BUG: The variant type is not implemented. Since the connection + * verifies, this can only mean we either wrongly didn't reject + * the connection as invalid, or we didn't properly implement the + * variant type. */ + nm_assert_not_reached (); + continue; + } + } + } + } + + for (j = 0; j < sett_info->property_infos_len; j++) { + const NMSettInfoProperty *property_info = _nm_sett_info_property_info_get_sorted (sett_info, j); + + write_setting_value (&info, setting, property_info); + if (info.error) + goto out_with_info_error; + } + + setting_alias = nm_keyfile_plugin_get_alias_for_setting_name (setting_name); + if ( ( setting_alias + && g_key_file_has_group (info.keyfile, setting_alias)) + || g_key_file_has_group (info.keyfile, setting_name)) { + /* we have a section for the setting. Nothing to do. */ + } else { + /* ensure the group is present. There is no API for that, so add and remove + * a dummy key. */ + g_key_file_set_value (info.keyfile, setting_alias ?: setting_name, ".X", "1"); + g_key_file_remove_key (info.keyfile, setting_alias ?: setting_name, ".X", NULL); + } + + if (NM_IS_SETTING_WIREGUARD (setting)) { + _write_setting_wireguard (setting, &info); + if (info.error) + goto out_with_info_error; + } + + nm_assert (!info.error); + } + + nm_assert (!info.error); + + return g_steal_pointer (&keyfile); + +out_with_info_error: + g_propagate_error (error, info.error); + return NULL; +} + +/*****************************************************************************/ + +static const char temp_letters[] = +"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + +/* + * Check '.[a-zA-Z0-9]{6}' file suffix used for temporary files by g_file_set_contents() (mkstemp()). + */ +static gboolean +check_mkstemp_suffix (const char *path) +{ + const char *ptr; + + nm_assert (path); + + /* Matches *.[a-zA-Z0-9]{6} suffix of mkstemp()'s temporary files */ + ptr = strrchr (path, '.'); + if ( ptr + && strspn (&ptr[1], temp_letters) == 6 + && ptr[7] == '\0') + return TRUE; + return FALSE; +} + +static gboolean +_check_suffix_impl (const char *base, const char *tag, gsize tag_len) +{ + gsize len; + + nm_assert (base); + nm_assert (tag); + nm_assert (strlen (tag) == tag_len); + + len = strlen (base); + if ( len > tag_len + && !g_ascii_strcasecmp (base + len - tag_len, tag)) + return TRUE; + return FALSE; +} +#define check_suffix(base, tag) _check_suffix_impl ((base), ""tag"", NM_STRLEN (tag)) + +#define SWP_TAG ".swp" +#define SWPX_TAG ".swpx" +#define PEM_TAG ".pem" +#define DER_TAG ".der" + +gboolean +nm_keyfile_utils_ignore_filename (const char *filename, gboolean require_extension) +{ + const char *base; + gsize l; + + /* ignore_filename() must mirror nm_keyfile_utils_create_filename() */ + + g_return_val_if_fail (filename, TRUE); + + base = strrchr (filename, '/'); + if (base) + base++; + else + base = filename; + + if (!base[0]) { + /* this check above with strrchr() also rejects "/some/path/with/trailing/slash/", + * but that is fine, because such a path would name a directory, and we are not + * interested in directories. */ + return TRUE; + } + + if (base[0] == '.') { + /* don't allow hidden files */ + return TRUE; + } + + l = strlen (base); + + if (require_extension) { + if ( l <= NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION) + || !NM_STR_HAS_SUFFIX (base, NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)) + return TRUE; + return FALSE; + } + + /* Ignore backup files */ + if (base[l - 1] == '~') + return TRUE; + + /* Ignore temporary files + * + * This check is also important to ignore .nmload files (see + * %NM_KEYFILE_PATH_SUFFIX_NMMETA). */ + if (check_mkstemp_suffix (base)) + return TRUE; + + /* Ignore 802.1x certificates and keys */ + if ( check_suffix (base, PEM_TAG) + || check_suffix (base, DER_TAG)) + return TRUE; + + return FALSE; +} + +char * +nm_keyfile_utils_create_filename (const char *name, + gboolean with_extension) +{ + GString *str; + const char *f = name; + /* keyfile used to escape with '*', do not change that behavior. + * + * But for newly added escapings, use '_' instead. + * Also, @with_extension is new-style. */ + const char ESCAPE_CHAR = with_extension ? '_' : '*'; + const char ESCAPE_CHAR2 = '_'; + + g_return_val_if_fail (name && name[0], NULL); + + str = g_string_sized_new (60); + + /* Convert '/' to ESCAPE_CHAR */ + for (f = name; f[0]; f++) { + if (f[0] == '/') + g_string_append_c (str, ESCAPE_CHAR); + else + g_string_append_c (str, f[0]); + } + + /* nm_keyfile_utils_create_filename() must avoid anything that ignore_filename() would reject. + * We can escape here more aggressivly then what we would read back. */ + if (str->str[0] == '.') + str->str[0] = ESCAPE_CHAR2; + if (str->str[str->len - 1] == '~') + str->str[str->len - 1] = ESCAPE_CHAR2; + if ( check_mkstemp_suffix (str->str) + || check_suffix (str->str, PEM_TAG) + || check_suffix (str->str, DER_TAG)) + g_string_append_c (str, ESCAPE_CHAR2); + + if (with_extension) + g_string_append (str, NM_KEYFILE_PATH_SUFFIX_NMCONNECTION); + + /* nm_keyfile_utils_create_filename() must mirror ignore_filename() */ + nm_assert (!strchr (str->str, '/')); + nm_assert (!nm_keyfile_utils_ignore_filename (str->str, with_extension)); + + return g_string_free (str, FALSE);; +} diff --git a/shared/nm-libnm-aux/nm-libnm-aux.c b/shared/nm-libnm-aux/nm-libnm-aux.c new file mode 100644 index 00000000..169416b9 --- /dev/null +++ b/shared/nm-libnm-aux/nm-libnm-aux.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: LGPL-2.1+ + +#include "nm-default.h" + +#include "nm-libnm-aux.h" + +/*****************************************************************************/ + +NMClient * +nmc_client_new_async_valist (GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data, + const char *first_property_name, + va_list ap) +{ + NMClient *nmc; + + nmc = NM_CLIENT (g_object_new_valist (NM_TYPE_CLIENT, first_property_name, ap)); + g_async_initable_init_async (G_ASYNC_INITABLE (nmc), + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data); + return nmc; +} + +NMClient * +nmc_client_new_async (GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data, + const char *first_property_name, + ...) +{ + NMClient *nmc; + va_list ap; + + va_start (ap, first_property_name); + nmc = nmc_client_new_async_valist (cancellable, + callback, + user_data, + first_property_name, + ap); + va_end (ap); + return nmc; +} + +/*****************************************************************************/ + +typedef struct { + GMainLoop *main_loop; + NMClient *nmc; + GError *error; +} ClientCreateData; + +static void +_nmc_client_new_waitsync_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + ClientCreateData *data = user_data; + + g_async_initable_init_finish (G_ASYNC_INITABLE (source_object), + result, + &data->error); + g_main_loop_quit (data->main_loop); +} + +/** + * nmc_client_new: + * @cancellable: the cancellable to abort the creation. + * @out_nmc: (out): (transfer full): if give, transfers a reference + * to the NMClient instance. Note that this never fails to create + * the NMClient GObject, but depending on the return value, + * the instance was successfully initialized or not. + * @error: the error if creation fails. + * @first_property_name: the name of the first property + * @...: the value of the first property, followed optionally by more + * name/value pairs, followed by %NULL + * + * Returns: %TRUE, if the client was successfully initalized. + * + * This uses nm_client_new_async() to create a NMClient instance, + * but it iterates the current GMainContext until the client is + * ready. As such, it waits for the client creation to complete + * (like sync nm_client_new()) but it iterates the caller's GMainContext + * (unlike sync nm_client_new()). This is often preferable, because + * sync nm_client_new() needs to create an additional internal GMainContext + * that it can iterate instead. That has a performance overhead that + * is often unnecessary. + */ +gboolean +nmc_client_new_waitsync (GCancellable *cancellable, + NMClient **out_nmc, + GError **error, + const char *first_property_name, + ...) +{ + gs_unref_object NMClient *nmc = NULL; + nm_auto_unref_gmainloop GMainLoop *main_loop = g_main_loop_new (g_main_context_get_thread_default (), FALSE); + ClientCreateData data = { + .main_loop = main_loop, + }; + va_list ap; + +#if NM_MORE_ASSERTS > 10 + /* The sync initialization of NMClient is generally a bad idea, because it + * brings the overhead of an additional GMainContext. Anyway, since our own + * code no longer uses that, we hardly test those code paths. But they should + * work just the same. Randomly use instead the sync initialization in a debug + * build... */ + if ((g_random_int () % 2) == 0) { + gboolean success; + + va_start (ap, first_property_name); + nmc = NM_CLIENT (g_object_new_valist (NM_TYPE_CLIENT, first_property_name, ap)); + va_end (ap); + + /* iterate the context at least once, just so that the behavior from POV of the + * caller is roughly the same. */ + g_main_context_iteration (nm_client_get_main_context (nmc), FALSE); + + success = g_initable_init (G_INITABLE (nmc), + cancellable, + error); + NM_SET_OUT (out_nmc, g_steal_pointer (&nmc)); + return success; + } +#endif + + va_start (ap, first_property_name); + nmc = nmc_client_new_async_valist (cancellable, + _nmc_client_new_waitsync_cb, + &data, + first_property_name, + ap); + va_end (ap); + + g_main_loop_run (main_loop); + + NM_SET_OUT (out_nmc, g_steal_pointer (&nmc)); + if (data.error) { + g_propagate_error (error, data.error); + return FALSE; + } + return TRUE; +} diff --git a/shared/nm-libnm-aux/nm-libnm-aux.h b/shared/nm-libnm-aux/nm-libnm-aux.h new file mode 100644 index 00000000..a0aff19f --- /dev/null +++ b/shared/nm-libnm-aux/nm-libnm-aux.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: LGPL-2.1+ + +#ifndef __NM_LIBNM_AUX_H__ +#define __NM_LIBNM_AUX_H__ + +NMClient *nmc_client_new_async_valist (GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data, + const char *first_property_name, + va_list ap); + +NMClient *nmc_client_new_async (GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data, + const char *first_property_name, + ...); + +gboolean nmc_client_new_waitsync (GCancellable *cancellable, + NMClient **out_nmc, + GError **error, + const char *first_property_name, + ...); + +#endif /* __NM_LIBNM_AUX_H__ */ diff --git a/shared/nm-libnm-core-intern/nm-auth-subject.c b/shared/nm-libnm-core-intern/nm-auth-subject.c new file mode 100644 index 00000000..15c2d652 --- /dev/null +++ b/shared/nm-libnm-core-intern/nm-auth-subject.c @@ -0,0 +1,446 @@ +// SPDX-License-Identifier: LGPL-2.1+ +/* + * Copyright (C) 2013 - 2014 Red Hat, Inc. + */ + +/** + * SECTION:nm-auth-subject + * @short_description: Encapsulates authentication information about a requestor + * + * #NMAuthSubject encpasulates identifying information about an entity that + * makes requests, like process identifier and user UID. + */ + +#include "nm-default.h" + +#include "nm-auth-subject.h" + +#include <stdlib.h> + +enum { + PROP_0, + PROP_SUBJECT_TYPE, + PROP_UNIX_PROCESS_DBUS_SENDER, + PROP_UNIX_PROCESS_PID, + PROP_UNIX_PROCESS_UID, + PROP_UNIX_SESSION_ID, + + PROP_LAST, +}; + +typedef struct { + NMAuthSubjectType subject_type; + struct { + gulong pid; + gulong uid; + guint64 start_time; + char *dbus_sender; + } unix_process; + + struct { + char *id; + } unix_session; +} NMAuthSubjectPrivate; + +struct _NMAuthSubject { + GObject parent; + NMAuthSubjectPrivate _priv; +}; + +struct _NMAuthSubjectClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE (NMAuthSubject, nm_auth_subject, G_TYPE_OBJECT) + +#define NM_AUTH_SUBJECT_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMAuthSubject, NM_IS_AUTH_SUBJECT) + +/*****************************************************************************/ + +#define CHECK_SUBJECT(self, error_value) \ + NMAuthSubjectPrivate *priv; \ + g_return_val_if_fail (NM_IS_AUTH_SUBJECT (self), error_value); \ + priv = NM_AUTH_SUBJECT_GET_PRIVATE (self); \ + +#define CHECK_SUBJECT_TYPED(self, expected_subject_type, error_value) \ + CHECK_SUBJECT (self, error_value); \ + g_return_val_if_fail (priv->subject_type == (expected_subject_type), error_value); + +const char * +nm_auth_subject_to_string (NMAuthSubject *self, char *buf, gsize buf_len) +{ + CHECK_SUBJECT (self, NULL); + + switch (priv->subject_type) { + case NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS: + g_snprintf (buf, buf_len, "unix-process[pid=%lu, uid=%lu, start=%llu]", + (unsigned long) priv->unix_process.pid, + (unsigned long) priv->unix_process.uid, + (unsigned long long) priv->unix_process.start_time); + break; + case NM_AUTH_SUBJECT_TYPE_INTERNAL: + g_strlcpy (buf, "internal", buf_len); + break; + case NM_AUTH_SUBJECT_TYPE_UNIX_SESSION: + g_snprintf (buf, buf_len, "unix-session[id=%s]", + priv->unix_session.id); + break; + default: + g_strlcpy (buf, "invalid", buf_len); + break; + } + return buf; +} + +/* returns a floating variant */ +GVariant * +nm_auth_subject_unix_to_polkit_gvariant (NMAuthSubject *self) +{ + GVariantBuilder builder; + CHECK_SUBJECT (self, NULL); + + switch (priv->subject_type) { + + case NM_AUTH_SUBJECT_TYPE_UNIX_SESSION: + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); + g_variant_builder_add (&builder, "{sv}", "session-id", + g_variant_new_string (priv->unix_session.id)); + return g_variant_new ("(sa{sv})", "unix-session", &builder); + + case NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS: + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); + g_variant_builder_add (&builder, "{sv}", "pid", + g_variant_new_uint32 (priv->unix_process.pid)); + g_variant_builder_add (&builder, "{sv}", "start-time", + g_variant_new_uint64 (priv->unix_process.start_time)); + g_variant_builder_add (&builder, "{sv}", "uid", + g_variant_new_int32 (priv->unix_process.uid)); + return g_variant_new ("(sa{sv})", "unix-process", &builder); + + default: + g_return_val_if_reached (NULL); + } +} + +NMAuthSubjectType +nm_auth_subject_get_subject_type (NMAuthSubject *subject) +{ + CHECK_SUBJECT (subject, NM_AUTH_SUBJECT_TYPE_INVALID); + + return priv->subject_type; +} + +gulong +nm_auth_subject_get_unix_process_pid (NMAuthSubject *subject) +{ + CHECK_SUBJECT_TYPED (subject, NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, G_MAXULONG); + + return priv->unix_process.pid; +} + +gulong +nm_auth_subject_get_unix_process_uid (NMAuthSubject *subject) +{ + CHECK_SUBJECT_TYPED (subject, NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, G_MAXULONG); + + return priv->unix_process.uid; +} + +const char * +nm_auth_subject_get_unix_process_dbus_sender (NMAuthSubject *subject) +{ + CHECK_SUBJECT_TYPED (subject, NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, NULL); + + return priv->unix_process.dbus_sender; +} + +const char * +nm_auth_subject_get_unix_session_id (NMAuthSubject *subject) +{ + CHECK_SUBJECT_TYPED (subject, NM_AUTH_SUBJECT_TYPE_UNIX_SESSION, NULL); + + return priv->unix_session.id; +} + +/*****************************************************************************/ + +/** + * nm_auth_subject_new_internal(): + * + * Creates a new auth subject representing the NetworkManager process itself. + * + * Returns: the new #NMAuthSubject + */ +NMAuthSubject * +nm_auth_subject_new_internal (void) +{ + return NM_AUTH_SUBJECT (g_object_new (NM_TYPE_AUTH_SUBJECT, + NM_AUTH_SUBJECT_SUBJECT_TYPE, (int) NM_AUTH_SUBJECT_TYPE_INTERNAL, + NULL)); +} + +/** + * nm_auth_subject_new_unix_session(): + * + * Creates a new auth subject representing a given unix session. + * + * Returns: the new #NMAuthSubject + */ +NMAuthSubject * +nm_auth_subject_new_unix_session (const char *session_id) +{ + return NM_AUTH_SUBJECT (g_object_new (NM_TYPE_AUTH_SUBJECT, + NM_AUTH_SUBJECT_SUBJECT_TYPE, (int) NM_AUTH_SUBJECT_TYPE_UNIX_SESSION, + NM_AUTH_SUBJECT_UNIX_SESSION_ID, session_id, + NULL)); +} + +/** + * nm_auth_subject_new_unix_process(): + * + * Creates a new auth subject representing a given unix process. + * + * Returns: the new #NMAuthSubject + */ +NMAuthSubject * +nm_auth_subject_new_unix_process (const char *dbus_sender, gulong pid, gulong uid) +{ + return NM_AUTH_SUBJECT (g_object_new (NM_TYPE_AUTH_SUBJECT, + NM_AUTH_SUBJECT_SUBJECT_TYPE, (int) NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, + NM_AUTH_SUBJECT_UNIX_PROCESS_DBUS_SENDER, dbus_sender, + NM_AUTH_SUBJECT_UNIX_PROCESS_PID, pid, + NM_AUTH_SUBJECT_UNIX_PROCESS_UID, uid, + NULL)); +} + +/** + * nm_auth_subject_new_unix_process_self(): + * + * Creates a new auth subject representing the current executing process. + * + * Returns: the new #NMAuthSubject + */ +NMAuthSubject * +nm_auth_subject_new_unix_process_self (void) +{ + return nm_auth_subject_new_unix_process (NULL, getpid(), getuid()); +} + +/*****************************************************************************/ + +static void +get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMAuthSubjectPrivate *priv = NM_AUTH_SUBJECT_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_SUBJECT_TYPE: + g_value_set_int (value, priv->subject_type); + break; + case PROP_UNIX_PROCESS_DBUS_SENDER: + g_value_set_string (value, priv->unix_process.dbus_sender); + break; + case PROP_UNIX_PROCESS_PID: + g_value_set_ulong (value, priv->unix_process.pid); + break; + case PROP_UNIX_PROCESS_UID: + g_value_set_ulong (value, priv->unix_process.uid); + break; + case PROP_UNIX_SESSION_ID: + g_value_set_string (value, priv->unix_session.id); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMAuthSubjectPrivate *priv = NM_AUTH_SUBJECT_GET_PRIVATE (object); + NMAuthSubjectType subject_type; + int i; + const char *str; + gulong id; + + switch (prop_id) { + case PROP_SUBJECT_TYPE: + /* construct-only */ + i = g_value_get_int (value); + g_return_if_fail (NM_IN_SET (i, + (int) NM_AUTH_SUBJECT_TYPE_INTERNAL, + (int) NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, + (int) NM_AUTH_SUBJECT_TYPE_UNIX_SESSION)); + subject_type = i; + priv->subject_type |= subject_type; + g_return_if_fail (priv->subject_type == subject_type); + break; + case PROP_UNIX_PROCESS_DBUS_SENDER: + /* construct-only */ + if ((str = g_value_get_string (value))) { + priv->subject_type |= NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS; + g_return_if_fail (priv->subject_type == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS); + priv->unix_process.dbus_sender = g_strdup (str); + } + break; + case PROP_UNIX_PROCESS_PID: + /* construct-only */ + if ((id = g_value_get_ulong (value)) != G_MAXULONG) { + priv->subject_type |= NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS; + g_return_if_fail (priv->subject_type == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS); + priv->unix_process.pid = id; + } + break; + case PROP_UNIX_PROCESS_UID: + /* construct-only */ + if ((id = g_value_get_ulong (value)) != G_MAXULONG) { + priv->subject_type |= NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS; + g_return_if_fail (priv->subject_type == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS); + priv->unix_process.uid = id; + } + break; + case PROP_UNIX_SESSION_ID: + /* construct-only */ + if ((str = g_value_get_string (value))) { + priv->subject_type |= NM_AUTH_SUBJECT_TYPE_UNIX_SESSION; + g_return_if_fail (priv->subject_type == NM_AUTH_SUBJECT_TYPE_UNIX_SESSION); + priv->unix_session.id = g_strdup (str); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +_clear_private (NMAuthSubject *self) +{ + NMAuthSubjectPrivate *priv = NM_AUTH_SUBJECT_GET_PRIVATE (self); + + priv->subject_type = NM_AUTH_SUBJECT_TYPE_INVALID; + priv->unix_process.pid = G_MAXULONG; + priv->unix_process.uid = G_MAXULONG; + nm_clear_g_free (&priv->unix_process.dbus_sender); + + nm_clear_g_free (&priv->unix_session.id); +} + +static void +nm_auth_subject_init (NMAuthSubject *self) +{ + _clear_private (self); +} + +static void +constructed (GObject *object) +{ + NMAuthSubject *self = NM_AUTH_SUBJECT (object); + NMAuthSubjectPrivate *priv = NM_AUTH_SUBJECT_GET_PRIVATE (self); + + /* validate that the created instance. */ + + switch (priv->subject_type) { + case NM_AUTH_SUBJECT_TYPE_INTERNAL: + priv->unix_process.pid = G_MAXULONG; + priv->unix_process.uid = 0; /* internal uses 'root' user */ + return; + case NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS: + /* Ensure pid and uid to be representable as int32. + * DBUS treats them as uint32, polkit library as int. */ + if (priv->unix_process.pid > MIN (G_MAXINT, G_MAXINT32)) + break; + if (priv->unix_process.uid > MIN (G_MAXINT, G_MAXINT32)) { + /* for uid==-1, libpolkit-gobject-1 detects the user based on the process id. + * Don't bother and require the user id as parameter. */ + break; + } + + priv->unix_process.start_time = nm_utils_get_start_time_for_pid (priv->unix_process.pid, NULL, NULL); + + if (!priv->unix_process.start_time) { + /* Is the process already gone? Then fail creation of the auth subject + * by clearing the type. */ + if (kill (priv->unix_process.pid, 0) != 0) + _clear_private (self); + + /* Otherwise, although we didn't detect a start_time, the process is still around. + * That could be due to procfs mounted with hidepid. So just accept the request. + * + * Polkit on the other side, will accept 0 and try to lookup /proc/$PID/stat + * itself (and if it fails to do so, assume a start-time of 0 and proceed). + * The only combination that would fail here, is when NM is able to read the + * start-time, but polkit is not. */ + } + return; + case NM_AUTH_SUBJECT_TYPE_UNIX_SESSION: + return; + default: + break; + } + + _clear_private (self); + g_return_if_reached (); +} + +static void +finalize (GObject *object) +{ + _clear_private ((NMAuthSubject *) object); + + G_OBJECT_CLASS (nm_auth_subject_parent_class)->finalize (object); +} + +static void +nm_auth_subject_class_init (NMAuthSubjectClass *config_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (config_class); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->constructed = constructed; + object_class->finalize = finalize; + + g_object_class_install_property + (object_class, PROP_SUBJECT_TYPE, + g_param_spec_int (NM_AUTH_SUBJECT_SUBJECT_TYPE, "", "", + NM_AUTH_SUBJECT_TYPE_INVALID, + NM_AUTH_SUBJECT_TYPE_UNIX_SESSION, + NM_AUTH_SUBJECT_TYPE_INVALID, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property + (object_class, PROP_UNIX_PROCESS_DBUS_SENDER, + g_param_spec_string (NM_AUTH_SUBJECT_UNIX_PROCESS_DBUS_SENDER, "", "", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property + (object_class, PROP_UNIX_PROCESS_PID, + g_param_spec_ulong (NM_AUTH_SUBJECT_UNIX_PROCESS_PID, "", "", + 0, G_MAXULONG, G_MAXULONG, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property + (object_class, PROP_UNIX_PROCESS_UID, + g_param_spec_ulong (NM_AUTH_SUBJECT_UNIX_PROCESS_UID, "", "", + 0, G_MAXULONG, G_MAXULONG, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property + (object_class, PROP_UNIX_SESSION_ID, + g_param_spec_string (NM_AUTH_SUBJECT_UNIX_SESSION_ID, "", "", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); +} diff --git a/shared/nm-libnm-core-intern/nm-auth-subject.h b/shared/nm-libnm-core-intern/nm-auth-subject.h new file mode 100644 index 00000000..fc37b8e2 --- /dev/null +++ b/shared/nm-libnm-core-intern/nm-auth-subject.h @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: LGPL-2.1+ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_AUTH_SUBJECT_H__ +#define __NETWORKMANAGER_AUTH_SUBJECT_H__ + +#define NM_TYPE_AUTH_SUBJECT (nm_auth_subject_get_type ()) +#define NM_AUTH_SUBJECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_AUTH_SUBJECT, NMAuthSubject)) +#define NM_AUTH_SUBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_AUTH_SUBJECT, NMAuthSubjectClass)) +#define NM_IS_AUTH_SUBJECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_AUTH_SUBJECT)) +#define NM_IS_AUTH_SUBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_AUTH_SUBJECT)) +#define NM_AUTH_SUBJECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_AUTH_SUBJECT, NMAuthSubjectClass)) + +typedef enum { + NM_AUTH_SUBJECT_TYPE_INVALID = 0, + NM_AUTH_SUBJECT_TYPE_INTERNAL = 1, + NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS = 2, + NM_AUTH_SUBJECT_TYPE_UNIX_SESSION = 4, +} NMAuthSubjectType; + +#define NM_AUTH_SUBJECT_SUBJECT_TYPE "subject-type" +#define NM_AUTH_SUBJECT_UNIX_PROCESS_DBUS_SENDER "unix-process-dbus-sender" +#define NM_AUTH_SUBJECT_UNIX_PROCESS_PID "unix-process-pid" +#define NM_AUTH_SUBJECT_UNIX_PROCESS_UID "unix-process-uid" +#define NM_AUTH_SUBJECT_UNIX_SESSION_ID "unix-session-id" + +typedef struct _NMAuthSubjectClass NMAuthSubjectClass; +typedef struct _NMAuthSubject NMAuthSubject; + +GType nm_auth_subject_get_type (void); + +NMAuthSubject *nm_auth_subject_new_internal (void); + +NMAuthSubject *nm_auth_subject_new_unix_session (const char *session_id); + +NMAuthSubject *nm_auth_subject_new_unix_process (const char *dbus_sender, gulong pid, gulong uid); + +NMAuthSubject *nm_auth_subject_new_unix_process_self (void); + +NMAuthSubjectType nm_auth_subject_get_subject_type (NMAuthSubject *subject); + +gulong nm_auth_subject_get_unix_process_pid (NMAuthSubject *subject); + +const char *nm_auth_subject_get_unix_process_dbus_sender (NMAuthSubject *subject); + +gulong nm_auth_subject_get_unix_process_uid (NMAuthSubject *subject); + +const char *nm_auth_subject_get_unix_session_id (NMAuthSubject *subject); + +const char *nm_auth_subject_to_string (NMAuthSubject *self, char *buf, gsize buf_len); + +GVariant *nm_auth_subject_unix_to_polkit_gvariant (NMAuthSubject *self); + +#endif /* __NETWORKMANAGER_AUTH_SUBJECT_H__ */ diff --git a/shared/nm-libnm-core-intern/nm-ethtool-utils.h b/shared/nm-libnm-core-intern/nm-ethtool-utils.h index 463d8e8f..b3413de2 100644 --- a/shared/nm-libnm-core-intern/nm-ethtool-utils.h +++ b/shared/nm-libnm-core-intern/nm-ethtool-utils.h @@ -79,7 +79,7 @@ typedef struct { NMEthtoolID id; } NMEthtoolData; -extern const NMEthtoolData *const nm_ethtool_data[/*_NM_ETHTOOL_ID_NUM + NULL-terminated*/]; +extern const NMEthtoolData *const nm_ethtool_data[_NM_ETHTOOL_ID_NUM + 1]; const NMEthtoolData *nm_ethtool_data_get_by_optname (const char *optname); diff --git a/shared/nm-libnm-core-intern/nm-libnm-core-utils.c b/shared/nm-libnm-core-intern/nm-libnm-core-utils.c index 9bf0b120..df2f2e77 100644 --- a/shared/nm-libnm-core-intern/nm-libnm-core-utils.c +++ b/shared/nm-libnm-core-intern/nm-libnm-core-utils.c @@ -4,6 +4,8 @@ #include "nm-libnm-core-utils.h" +#include "nm-common-macros.h" + /*****************************************************************************/ gboolean @@ -59,3 +61,120 @@ nm_utils_vlan_priority_map_parse_str (NMVlanPriorityMap map_type, NM_SET_OUT (out_has_wildcard_to, v2 < 0); return TRUE; } + +/*****************************************************************************/ + +const char *const nm_auth_permission_names_by_idx[NM_CLIENT_PERMISSION_LAST] = { + [NM_CLIENT_PERMISSION_CHECKPOINT_ROLLBACK - 1] = NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, + [NM_CLIENT_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK - 1] = NM_AUTH_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK, + [NM_CLIENT_PERMISSION_ENABLE_DISABLE_NETWORK - 1] = NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK, + [NM_CLIENT_PERMISSION_ENABLE_DISABLE_STATISTICS - 1] = NM_AUTH_PERMISSION_ENABLE_DISABLE_STATISTICS, + [NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIFI - 1] = NM_AUTH_PERMISSION_ENABLE_DISABLE_WIFI, + [NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIMAX - 1] = NM_AUTH_PERMISSION_ENABLE_DISABLE_WIMAX, + [NM_CLIENT_PERMISSION_ENABLE_DISABLE_WWAN - 1] = NM_AUTH_PERMISSION_ENABLE_DISABLE_WWAN, + [NM_CLIENT_PERMISSION_NETWORK_CONTROL - 1] = NM_AUTH_PERMISSION_NETWORK_CONTROL, + [NM_CLIENT_PERMISSION_RELOAD - 1] = NM_AUTH_PERMISSION_RELOAD, + [NM_CLIENT_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS - 1] = NM_AUTH_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS, + [NM_CLIENT_PERMISSION_SETTINGS_MODIFY_HOSTNAME - 1] = NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME, + [NM_CLIENT_PERMISSION_SETTINGS_MODIFY_OWN - 1] = NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN, + [NM_CLIENT_PERMISSION_SETTINGS_MODIFY_SYSTEM - 1] = NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM, + [NM_CLIENT_PERMISSION_SLEEP_WAKE - 1] = NM_AUTH_PERMISSION_SLEEP_WAKE, + [NM_CLIENT_PERMISSION_WIFI_SCAN - 1] = NM_AUTH_PERMISSION_WIFI_SCAN, + [NM_CLIENT_PERMISSION_WIFI_SHARE_OPEN - 1] = NM_AUTH_PERMISSION_WIFI_SHARE_OPEN, + [NM_CLIENT_PERMISSION_WIFI_SHARE_PROTECTED - 1] = NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED, +}; + +const NMClientPermission nm_auth_permission_sorted[NM_CLIENT_PERMISSION_LAST] = { + NM_CLIENT_PERMISSION_CHECKPOINT_ROLLBACK, + NM_CLIENT_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK, + NM_CLIENT_PERMISSION_ENABLE_DISABLE_NETWORK, + NM_CLIENT_PERMISSION_ENABLE_DISABLE_STATISTICS, + NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIFI, + NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIMAX, + NM_CLIENT_PERMISSION_ENABLE_DISABLE_WWAN, + NM_CLIENT_PERMISSION_NETWORK_CONTROL, + NM_CLIENT_PERMISSION_RELOAD, + NM_CLIENT_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS, + NM_CLIENT_PERMISSION_SETTINGS_MODIFY_HOSTNAME, + NM_CLIENT_PERMISSION_SETTINGS_MODIFY_OWN, + NM_CLIENT_PERMISSION_SETTINGS_MODIFY_SYSTEM, + NM_CLIENT_PERMISSION_SLEEP_WAKE, + NM_CLIENT_PERMISSION_WIFI_SCAN, + NM_CLIENT_PERMISSION_WIFI_SHARE_OPEN, + NM_CLIENT_PERMISSION_WIFI_SHARE_PROTECTED, +}; + +const char * +nm_auth_permission_to_string (NMClientPermission permission) +{ + if (permission < 1) + return NULL; + if (permission > NM_CLIENT_PERMISSION_LAST) + return NULL; + return nm_auth_permission_names_by_idx[permission - 1]; +} + +#define AUTH_PERMISSION_PREFIX "org.freedesktop.NetworkManager." + +static int +_nm_auth_permission_from_string_cmp (gconstpointer a, gconstpointer b, gpointer user_data) +{ + const NMClientPermission *const p = a; + const char *const needle = b; + const char *ss = nm_auth_permission_names_by_idx[*p - 1]; + + nm_assert (NM_STR_HAS_PREFIX (ss, AUTH_PERMISSION_PREFIX)); + nm_assert (ss[NM_STRLEN (AUTH_PERMISSION_PREFIX)] != '\0'); + + return strcmp (&ss[NM_STRLEN (AUTH_PERMISSION_PREFIX)], needle); +} + +NMClientPermission +nm_auth_permission_from_string (const char *str) +{ + gssize idx; + + if (!str) + return NM_CLIENT_PERMISSION_NONE; + + if (!NM_STR_HAS_PREFIX (str, AUTH_PERMISSION_PREFIX)) + return NM_CLIENT_PERMISSION_NONE; + idx = nm_utils_array_find_binary_search (nm_auth_permission_sorted, + sizeof (nm_auth_permission_sorted[0]), + G_N_ELEMENTS (nm_auth_permission_sorted), + &str[NM_STRLEN (AUTH_PERMISSION_PREFIX)], + _nm_auth_permission_from_string_cmp, + NULL); + if (idx < 0) + return NM_CLIENT_PERMISSION_NONE; + return nm_auth_permission_sorted[idx]; +} + +/*****************************************************************************/ + +NMClientPermissionResult +nm_client_permission_result_from_string (const char *nm) +{ + if (!nm) + return NM_CLIENT_PERMISSION_RESULT_UNKNOWN; + if (nm_streq (nm, "yes")) + return NM_CLIENT_PERMISSION_RESULT_YES; + if (nm_streq (nm, "no")) + return NM_CLIENT_PERMISSION_RESULT_NO; + if (nm_streq (nm, "auth")) + return NM_CLIENT_PERMISSION_RESULT_AUTH; + return NM_CLIENT_PERMISSION_RESULT_UNKNOWN; +} + +const char * +nm_client_permission_result_to_string (NMClientPermissionResult permission) +{ + switch (permission) { + case NM_CLIENT_PERMISSION_RESULT_YES: return "yes"; + case NM_CLIENT_PERMISSION_RESULT_NO: return "no"; + case NM_CLIENT_PERMISSION_RESULT_AUTH: return "auth"; + case NM_CLIENT_PERMISSION_RESULT_UNKNOWN: return "unknown"; + } + nm_assert_not_reached (); + return NULL; +} diff --git a/shared/nm-libnm-core-intern/nm-libnm-core-utils.h b/shared/nm-libnm-core-intern/nm-libnm-core-utils.h index 1449a8ba..a35a5e15 100644 --- a/shared/nm-libnm-core-intern/nm-libnm-core-utils.h +++ b/shared/nm-libnm-core-intern/nm-libnm-core-utils.h @@ -99,4 +99,17 @@ nm_setting_ip_config_get_addr_family (NMSettingIPConfig *s_ip) #define _NM_CAPABILITY_MAX NM_CAPABILITY_OVS +/*****************************************************************************/ + +extern const char *const nm_auth_permission_names_by_idx[NM_CLIENT_PERMISSION_LAST]; +extern const NMClientPermission nm_auth_permission_sorted[NM_CLIENT_PERMISSION_LAST]; + +const char *nm_auth_permission_to_string (NMClientPermission permission); +NMClientPermission nm_auth_permission_from_string (const char *str); + +/*****************************************************************************/ + +NMClientPermissionResult nm_client_permission_result_from_string (const char *nm); +const char *nm_client_permission_result_to_string (NMClientPermissionResult permission); + #endif /* __NM_LIBNM_SHARED_UTILS_H__ */ diff --git a/shared/nm-meta-setting.c b/shared/nm-meta-setting.c index af5aa842..cf6309b1 100644 --- a/shared/nm-meta-setting.c +++ b/shared/nm-meta-setting.c @@ -46,6 +46,7 @@ #include "nm-setting-user.h" #include "nm-setting-vlan.h" #include "nm-setting-vpn.h" +#include "nm-setting-vrf.h" #include "nm-setting-vxlan.h" #include "nm-setting-wifi-p2p.h" #include "nm-setting-wimax.h" @@ -387,6 +388,12 @@ const NMMetaSettingInfo nm_meta_setting_infos[] = { .setting_name = NM_SETTING_VPN_SETTING_NAME, .get_setting_gtype = nm_setting_vpn_get_type, }, + [NM_META_SETTING_TYPE_VRF] = { + .meta_type = NM_META_SETTING_TYPE_VRF, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_VRF_SETTING_NAME, + .get_setting_gtype = nm_setting_vrf_get_type, + }, [NM_META_SETTING_TYPE_VXLAN] = { .meta_type = NM_META_SETTING_TYPE_VXLAN, .setting_priority = NM_SETTING_PRIORITY_HW_BASE, diff --git a/shared/nm-meta-setting.h b/shared/nm-meta-setting.h index 3976bfe1..8b0c7d35 100644 --- a/shared/nm-meta-setting.h +++ b/shared/nm-meta-setting.h @@ -142,6 +142,7 @@ typedef enum { NM_META_SETTING_TYPE_USER, NM_META_SETTING_TYPE_VLAN, NM_META_SETTING_TYPE_VPN, + NM_META_SETTING_TYPE_VRF, NM_META_SETTING_TYPE_VXLAN, NM_META_SETTING_TYPE_WIFI_P2P, NM_META_SETTING_TYPE_WIMAX, diff --git a/shared/nm-std-aux/nm-dbus-compat.h b/shared/nm-std-aux/nm-dbus-compat.h index bd010f45..293e15cc 100644 --- a/shared/nm-std-aux/nm-dbus-compat.h +++ b/shared/nm-std-aux/nm-dbus-compat.h @@ -1,62 +1,25 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright (C) 2015 Red Hat, Inc. - */ +// SPDX-License-Identifier: LGPL-2.1+ #ifndef __NM_DBUS_COMPAT_H__ #define __NM_DBUS_COMPAT_H__ -/* Copied from <dbus/dbus-shared.h> */ +#define DBUS_SERVICE_DBUS "org.freedesktop.DBus" -/* Bus names */ +#define DBUS_PATH_DBUS "/org/freedesktop/DBus" -/** The bus name used to talk to the bus itself. */ -#define DBUS_SERVICE_DBUS "org.freedesktop.DBus" - -/* Paths */ -/** The object path used to talk to the bus itself. */ -#define DBUS_PATH_DBUS "/org/freedesktop/DBus" -/** The object path used in local/in-process-generated messages. */ -#define DBUS_PATH_LOCAL "/org/freedesktop/DBus/Local" - -/* Interfaces, these #define don't do much other than - * catch typos at compile time - */ -/** The interface exported by the object with #DBUS_SERVICE_DBUS and #DBUS_PATH_DBUS */ #define DBUS_INTERFACE_DBUS "org.freedesktop.DBus" -/** The interface supported by introspectable objects */ #define DBUS_INTERFACE_INTROSPECTABLE "org.freedesktop.DBus.Introspectable" -/** The interface supported by objects with properties */ -#define DBUS_INTERFACE_PROPERTIES "org.freedesktop.DBus.Properties" -/** The interface supported by most dbus peers */ -#define DBUS_INTERFACE_PEER "org.freedesktop.DBus.Peer" - #define DBUS_INTERFACE_OBJECT_MANAGER "org.freedesktop.DBus.ObjectManager" +#define DBUS_INTERFACE_PEER "org.freedesktop.DBus.Peer" +#define DBUS_INTERFACE_PROPERTIES "org.freedesktop.DBus.Properties" -/** This is a special interface whose methods can only be invoked - * by the local implementation (messages from remote apps aren't - * allowed to specify this interface). - */ -#define DBUS_INTERFACE_LOCAL "org.freedesktop.DBus.Local" - -/* Owner flags */ -#define DBUS_NAME_FLAG_ALLOW_REPLACEMENT 0x1 /**< Allow another service to become the primary owner if requested */ -#define DBUS_NAME_FLAG_REPLACE_EXISTING 0x2 /**< Request to replace the current primary owner */ -#define DBUS_NAME_FLAG_DO_NOT_QUEUE 0x4 /**< If we can not become the primary owner do not place us in the queue */ - -/* Replies to request for a name */ -#define DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER 1 /**< Service has become the primary owner of the requested name */ -#define DBUS_REQUEST_NAME_REPLY_IN_QUEUE 2 /**< Service could not become the primary owner and has been placed in the queue */ -#define DBUS_REQUEST_NAME_REPLY_EXISTS 3 /**< Service is already in the queue */ -#define DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER 4 /**< Service is already the primary owner */ - -/* Replies to releasing a name */ -#define DBUS_RELEASE_NAME_REPLY_RELEASED 1 /**< Service was released from the given name */ -#define DBUS_RELEASE_NAME_REPLY_NON_EXISTENT 2 /**< The given name does not exist on the bus */ -#define DBUS_RELEASE_NAME_REPLY_NOT_OWNER 3 /**< Service is not an owner of the given name */ +#define DBUS_NAME_FLAG_ALLOW_REPLACEMENT 0x1 +#define DBUS_NAME_FLAG_REPLACE_EXISTING 0x2 +#define DBUS_NAME_FLAG_DO_NOT_QUEUE 0x4 -/* Replies to service starts */ -#define DBUS_START_REPLY_SUCCESS 1 /**< Service was auto started */ -#define DBUS_START_REPLY_ALREADY_RUNNING 2 /**< Service was already running */ +#define DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER 1 +#define DBUS_REQUEST_NAME_REPLY_IN_QUEUE 2 +#define DBUS_REQUEST_NAME_REPLY_EXISTS 3 +#define DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER 4 #endif /* __NM_DBUS_COMPAT_H__ */ diff --git a/shared/nm-test-libnm-utils.h b/shared/nm-test-libnm-utils.h index 63e3b4c7..06dbc72d 100644 --- a/shared/nm-test-libnm-utils.h +++ b/shared/nm-test-libnm-utils.h @@ -67,4 +67,18 @@ void nmtstc_service_update_connection_variant (NMTstcServiceInfo *sinfo, GVariant *connection, gboolean verify_connection); -NMClient *nmtstc_client_new (gboolean allow_iterate_main_context); +gpointer nmtstc_context_object_new_valist (GType gtype, + gboolean allow_iterate_main_context, + const char *first_property_name, + va_list var_args); + +gpointer nmtstc_context_object_new (GType gtype, + gboolean allow_iterate_main_context, + const char *first_property_name, + ...); + +static inline NMClient * +nmtstc_client_new (gboolean allow_iterate_main_context) +{ + return nmtstc_context_object_new (NM_TYPE_CLIENT, allow_iterate_main_context, NULL); +} diff --git a/shared/nm-test-utils-impl.c b/shared/nm-test-utils-impl.c index 0805d0b9..0ef6591f 100644 --- a/shared/nm-test-utils-impl.c +++ b/shared/nm-test-utils-impl.c @@ -127,7 +127,7 @@ nmtstc_service_init (void) g_source_attach (timeout_source, context); child_source = g_child_watch_source_new (info->pid); - g_source_set_callback (child_source, (GSourceFunc)(void (*) (void)) _service_init_wait_child_wait, &data, NULL); + g_source_set_callback (child_source, G_SOURCE_FUNC (_service_init_wait_child_wait), &data, NULL); g_source_attach (child_source, context); had_timeout = !nmtst_main_loop_run (data.mainloop, 30000); @@ -216,7 +216,7 @@ again_wait: typedef struct { GMainLoop *loop; const char *ifname; - char *path; + const char *path; NMDevice *device; } AddDeviceInfo; @@ -227,21 +227,17 @@ device_added_cb (NMClient *client, { AddDeviceInfo *info = user_data; - g_assert (device); + g_assert (info); + g_assert (!info->device); + + g_assert (NM_IS_DEVICE (device)); g_assert_cmpstr (nm_object_get_path (NM_OBJECT (device)), ==, info->path); g_assert_cmpstr (nm_device_get_iface (device), ==, info->ifname); - info->device = device; + info->device = g_object_ref (device); g_main_loop_quit (info->loop); } -static gboolean -timeout (gpointer user_data) -{ - g_assert_not_reached (); - return G_SOURCE_REMOVE; -} - static GVariant * call_add_wired_device (GDBusProxy *proxy, const char *ifname, const char *hwaddr, const char **subchannels, GError **error) @@ -275,39 +271,53 @@ call_add_device (GDBusProxy *proxy, const char *method, const char *ifname, GErr } static NMDevice * -add_device_common (NMTstcServiceInfo *sinfo, NMClient *client, - const char *method, const char *ifname, - const char *hwaddr, const char **subchannels) +add_device_common (NMTstcServiceInfo *sinfo, + NMClient *client, + const char *method, + const char *ifname, + const char *hwaddr, + const char **subchannels) { + nm_auto_unref_gmainloop GMainLoop *loop = NULL; + gs_unref_variant GVariant *ret = NULL; + gs_free_error GError *error = NULL; AddDeviceInfo info; - GError *error = NULL; - GVariant *ret; - guint timeout_id; - if (g_strcmp0 (method, "AddWiredDevice") == 0) + g_assert (sinfo); + g_assert (NM_IS_CLIENT (client)); + + if (nm_streq0 (method, "AddWiredDevice")) ret = call_add_wired_device (sinfo->proxy, ifname, hwaddr, subchannels, &error); else ret = call_add_device (sinfo->proxy, method, ifname, &error); - g_assert_no_error (error); - g_assert (ret); + nmtst_assert_success (ret, error); g_assert_cmpstr (g_variant_get_type_string (ret), ==, "(o)"); - g_variant_get (ret, "(o)", &info.path); - g_variant_unref (ret); - - /* Wait for libnm to find the device */ - info.ifname = ifname; - info.loop = g_main_loop_new (NULL, FALSE); - g_signal_connect (client, "device-added", - G_CALLBACK (device_added_cb), &info); - timeout_id = g_timeout_add_seconds (5, timeout, NULL); - g_main_loop_run (info.loop); - - g_source_remove (timeout_id); + + /* Wait for NMClient to find the device */ + + loop = g_main_loop_new (nm_client_get_main_context (client), FALSE); + + info = (AddDeviceInfo) { + .ifname = ifname, + .loop = loop, + }; + g_variant_get (ret, "(&o)", &info.path); + + g_signal_connect (client, + NM_CLIENT_DEVICE_ADDED, + G_CALLBACK (device_added_cb), + &info); + + if (!nmtst_main_loop_run (loop, 5000)) + g_assert_not_reached (); + g_signal_handlers_disconnect_by_func (client, device_added_cb, &info); - g_free (info.path); - g_main_loop_unref (info.loop); + g_assert (NM_IS_DEVICE (info.device)); + + g_assert (info.device == nm_client_get_device_by_path (client, nm_object_get_path (NM_OBJECT (info.device)))); + g_object_unref (info.device); return info.device; } @@ -410,35 +420,47 @@ nmtstc_service_update_connection_variant (NMTstcServiceInfo *sinfo, /*****************************************************************************/ typedef struct { + GType gtype; GMainLoop *loop; - NMClient *client; -} NMTstcClientNewData; + GObject *obj; + bool call_nm_client_new_async:1; +} NMTstcObjNewData; static void -_nmtstc_client_new_cb (GObject *source_object, - GAsyncResult *res, - gpointer user_data) +_context_object_new_do_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) { - NMTstcClientNewData *d = user_data; + NMTstcObjNewData *d = user_data; gs_free_error GError *error = NULL; - g_assert (!d->client); + g_assert (!d->obj); - d->client = nm_client_new_finish (res, - nmtst_get_rand_bool () ? &error : NULL); + if (d->call_nm_client_new_async) { + d->obj = G_OBJECT (nm_client_new_finish (res, + nmtst_get_rand_bool () ? &error : NULL)); + } else { + d->obj = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), + res, + nmtst_get_rand_bool () ? &error : NULL); + } - nmtst_assert_success (NM_IS_CLIENT (d->client), error); + nmtst_assert_success (G_IS_OBJECT (d->obj), error); + g_assert (G_OBJECT_TYPE (d->obj) == d->gtype); g_main_loop_quit (d->loop); } -static NMClient * -_nmtstc_client_new (gboolean sync) +static GObject * +_context_object_new_do (GType gtype, + gboolean sync, + const gchar *first_property_name, + va_list var_args) { gs_free_error GError *error = NULL; - NMClient *client; + GObject *obj; - /* Create a NMClient instance synchronously, and arbitrarily use either + /* Create a GObject instance synchronously, and arbitrarily use either * the sync or async constructor. * * Note that the sync and async construct differ in one important aspect: @@ -456,125 +478,128 @@ _nmtstc_client_new (gboolean sync) g_source_attach (source, g_main_context_get_thread_default ()); } - if (nmtst_get_rand_bool ()) { + if ( gtype != NM_TYPE_CLIENT + || first_property_name + || nmtst_get_rand_bool ()) { gboolean success; - client = g_object_new (NM_TYPE_CLIENT, NULL); - g_assert (NM_IS_CLIENT (client)); + if ( first_property_name + || nmtst_get_rand_bool ()) + obj = g_object_new_valist (gtype, first_property_name, var_args); + else + obj = g_object_new (gtype, NULL); - success = g_initable_init (G_INITABLE (client), + success = g_initable_init (G_INITABLE (obj), NULL, nmtst_get_rand_bool () ? &error : NULL); nmtst_assert_success (success, error); } else { - client = nm_client_new (NULL, - nmtst_get_rand_bool () ? &error : NULL); + obj = G_OBJECT (nm_client_new (NULL, + nmtst_get_rand_bool () ? &error : NULL)); } } else { nm_auto_unref_gmainloop GMainLoop *loop = NULL; - NMTstcClientNewData d = { .loop = NULL, }; + NMTstcObjNewData d = { + .gtype = gtype, + .loop = NULL, + }; + gs_unref_object GObject *obj2 = NULL; loop = g_main_loop_new (g_main_context_get_thread_default (), FALSE); - d.loop = loop; - nm_client_new_async (NULL, - _nmtstc_client_new_cb, - &d); + + if ( gtype != NM_TYPE_CLIENT + || first_property_name + || nmtst_get_rand_bool ()) { + if ( first_property_name + || nmtst_get_rand_bool ()) + obj2 = g_object_new_valist (gtype, first_property_name, var_args); + else + obj2 = g_object_new (gtype, NULL); + + g_async_initable_init_async (G_ASYNC_INITABLE (obj2), + G_PRIORITY_DEFAULT, + NULL, + _context_object_new_do_cb, + &d); + } else { + d.call_nm_client_new_async = TRUE; + nm_client_new_async (NULL, + _context_object_new_do_cb, + &d); + } g_main_loop_run (loop); - g_assert (NM_IS_CLIENT (d.client)); - client = d.client; + obj = d.obj; + g_assert (!obj2 || obj == obj2); } - nmtst_assert_success (NM_IS_CLIENT (client), error); - return client; + nmtst_assert_success (G_IS_OBJECT (obj), error); + g_assert (G_OBJECT_TYPE (obj) == gtype); + return obj; } typedef struct { + GType gtype; + const char *first_property_name; + va_list var_args; GMainLoop *loop; - NMClient *client; + GObject *obj; bool sync; } NewSyncInsideDispatchedData; static gboolean -_nmtstc_client_new_inside_loop_do (gpointer user_data) +_context_object_new_inside_loop_do (gpointer user_data) { NewSyncInsideDispatchedData *d = user_data; g_assert (d->loop); - g_assert (!d->client); + g_assert (!d->obj); - d->client = nmtstc_client_new (d->sync); + d->obj = nmtstc_context_object_new_valist (d->gtype, d->sync, d->first_property_name, d->var_args); g_main_loop_quit (d->loop); return G_SOURCE_CONTINUE; } -static NMClient * -_nmtstc_client_new_inside_loop (gboolean sync) +static GObject * +_context_object_new_inside_loop (GType gtype, + gboolean sync, + const char *first_property_name, + va_list var_args) { GMainContext *context = g_main_context_get_thread_default (); nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new (context, FALSE); NewSyncInsideDispatchedData d = { - .sync = sync, - .loop = loop, + .gtype = gtype, + .first_property_name = first_property_name, + .sync = sync, + .loop = loop, }; nm_auto_destroy_and_unref_gsource GSource *source = NULL; + va_copy (d.var_args, var_args); + source = g_idle_source_new (); - g_source_set_callback (source, _nmtstc_client_new_inside_loop_do, &d, NULL); + g_source_set_callback (source, _context_object_new_inside_loop_do, &d, NULL); g_source_attach (source, context); g_main_loop_run (loop); - g_assert (NM_IS_CLIENT (d.client)); - return d.client; -} - -static NMClient * -_nmtstc_client_new_extra_context (void) -{ - GMainContext *inner_context; - NMClient *client; - GSource *source; - guint key_idx; - - inner_context = g_main_context_new (); - g_main_context_push_thread_default (inner_context); - - client = nmtstc_client_new (TRUE); - source = nm_utils_g_main_context_create_integrate_source (inner_context); + va_end (d.var_args); - g_main_context_pop_thread_default (inner_context); - g_main_context_unref (inner_context); - - g_source_attach (source, g_main_context_get_thread_default ()); - - for (key_idx = 0; TRUE; key_idx++) { - char s[100]; - - /* nmtstc_client_new() may call _nmtstc_client_new_extra_context() repeatedly. We - * need to attach the source to a previously unused key. */ - nm_sprintf_buf (s, "nm-test-extra-context-%u", key_idx); - if (!g_object_get_data (G_OBJECT (client), s)) { - g_object_set_data_full (G_OBJECT (client), - s, - source, - (GDestroyNotify) nm_g_source_destroy_and_unref); - break; - } - } - - return client; + g_assert (G_IS_OBJECT (d.obj)); + g_assert (G_OBJECT_TYPE (d.obj) == gtype); + return d.obj; } -NMClient * -nmtstc_client_new (gboolean allow_iterate_main_context) +gpointer +nmtstc_context_object_new_valist (GType gtype, + gboolean allow_iterate_main_context, + const char *first_property_name, + va_list var_args) { gboolean inside_loop; gboolean sync; - if (nmtst_get_rand_uint32 () % 5 == 0) - return _nmtstc_client_new_extra_context (); - if (!allow_iterate_main_context) { sync = TRUE; inside_loop = FALSE; @@ -587,11 +612,26 @@ nmtstc_client_new (gboolean allow_iterate_main_context) } if (inside_loop) { - /* Create the client on an idle handler of the current context. + /* Create the obj on an idle handler of the current context. * In practice, it should make no difference, which this check * tries to prove. */ - return _nmtstc_client_new_inside_loop (sync); + return _context_object_new_inside_loop (gtype, sync, first_property_name, var_args); } - return _nmtstc_client_new (sync); + return _context_object_new_do (gtype, sync, first_property_name, var_args); +} + +gpointer +nmtstc_context_object_new (GType gtype, + gboolean allow_iterate_main_context, + const char *first_property_name, + ...) +{ + GObject *obj; + va_list var_args; + + va_start (var_args, first_property_name); + obj = nmtstc_context_object_new_valist (gtype, allow_iterate_main_context, first_property_name, var_args); + va_end (var_args); + return obj; } diff --git a/shared/nm-udev-aux/nm-udev-utils.c b/shared/nm-udev-aux/nm-udev-utils.c index 1b0927df..c00dcd33 100644 --- a/shared/nm-udev-aux/nm-udev-utils.c +++ b/shared/nm-udev-aux/nm-udev-utils.c @@ -164,7 +164,7 @@ nm_udev_client_enumerate_new (NMUdevClient *self) /*****************************************************************************/ static gboolean -monitor_event (GIOChannel *source, +monitor_event (int fd, GIOCondition condition, gpointer user_data) { @@ -204,7 +204,6 @@ nm_udev_client_new (const char *const*subsystems, gpointer event_user_data) { NMUdevClient *self; - GIOChannel *channel; guint n; self = g_slice_new0 (NMUdevClient); @@ -237,12 +236,14 @@ nm_udev_client_new (const char *const*subsystems, /* listen to events, and buffer them */ udev_monitor_set_receive_buffer_size (self->monitor, 4*1024*1024); udev_monitor_enable_receiving (self->monitor); - channel = g_io_channel_unix_new (udev_monitor_get_fd (self->monitor)); - self->watch_source = g_io_create_watch (channel, G_IO_IN); - g_io_channel_unref (channel); - g_source_set_callback (self->watch_source, (GSourceFunc)(void (*) (void)) monitor_event, self, NULL); + + self->watch_source = nm_g_unix_fd_source_new (udev_monitor_get_fd (self->monitor), + G_IO_IN, + G_PRIORITY_DEFAULT, + monitor_event, + self, + NULL); g_source_attach (self->watch_source, g_main_context_get_thread_default ()); - g_source_unref (self->watch_source); } } @@ -258,10 +259,7 @@ nm_udev_client_unref (NMUdevClient *self) if (!self) return NULL; - if (self->watch_source) { - g_source_destroy (self->watch_source); - self->watch_source = NULL; - } + nm_clear_g_source_inst (&self->watch_source); udev_monitor_unref (self->monitor); self->monitor = NULL; diff --git a/shared/nm-utils/nm-test-utils.h b/shared/nm-utils/nm-test-utils.h index f0ea1179..96e36135 100644 --- a/shared/nm-utils/nm-test-utils.h +++ b/shared/nm-utils/nm-test-utils.h @@ -622,7 +622,7 @@ __nmtst_init (int *argc, char ***argv, gboolean assert_logging, const char *log_ #ifdef __NETWORKMANAGER_UTILS_H__ /* ensure that monotonic timestamp is called (because it initially logs a line) */ - nm_utils_get_monotonic_timestamp_s (); + nm_utils_get_monotonic_timestamp_sec (); #endif #ifdef NM_UTILS_H @@ -848,6 +848,15 @@ nmtst_get_rand_uint32 (void) return g_rand_int (nmtst_get_rand ()); } +static inline guint64 +nmtst_get_rand_uint64 (void) +{ + GRand *rand = nmtst_get_rand (); + + return (((guint64) g_rand_int (rand)) ) + | (((guint64) g_rand_int (rand)) << 32); +} + static inline guint nmtst_get_rand_uint (void) { @@ -855,6 +864,16 @@ nmtst_get_rand_uint (void) return nmtst_get_rand_uint32 (); } +static inline gsize +nmtst_get_rand_size (void) +{ + G_STATIC_ASSERT_EXPR ( sizeof (gsize) == sizeof (guint32) + || sizeof (gsize) == sizeof (guint64)); + if (sizeof (gsize) == sizeof (guint32)) + return nmtst_get_rand_uint32 (); + return nmtst_get_rand_uint64 (); +} + static inline gboolean nmtst_get_rand_bool (void) { @@ -967,6 +986,89 @@ nmtst_rand_perm_gslist (GRand *rand, GSList *list) /*****************************************************************************/ +/** + * nmtst_get_rand_word_length: + * @rand: (allow-none): #GRand instance or %NULL to use the singleton. + * + * Returns: a random integer >= 0, that most frequently is somewhere between + * 0 and 16, but (with decreasing) probability, it can be larger. This can + * be used when we generate random input for unit tests. + */ +static inline guint +nmtst_get_rand_word_length (GRand *rand) +{ + guint n; + + if (!rand) + rand = nmtst_get_rand (); + + n = 0; + while (TRUE) { + guint32 rnd = g_rand_int (rand); + guint probability; + + /* The following python code implements a random sample with this + * distribution: + * + * def random_histogram(n_tries, scale = None): + * def probability(n_tok): + * import math + * return max(2, math.floor(100 / (2*(n_tok+1)))) + * def n_tokens(): + * import random + * n_tok = 0 + * while True: + * if random.randint(0, 0xFFFFFFFF) % probability(n_tok) == 0: + * return n_tok + * n_tok += 1 + * hist = [] + * i = 0; + * while i < n_tries: + * n_tok = n_tokens() + * while n_tok >= len(hist): + * hist.append(0) + * hist[n_tok] = hist[n_tok] + 1 + * i += 1 + * if scale is not None: + * hist = list([round(x / n_tries * scale) for x in hist]) + * return hist + * + * For example, random_histogram(n_tries = 1000000, scale = 1000) may give + * + * IDX: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29] + * SEEN: [20, 39, 59, 73, 80, 91, 92, 90, 91, 73, 73, 54, 55, 36, 24, 16, 16, 8, 4, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0] + * + * which give a sense of the probability with this individual results are returned. + */ + probability = NM_MAX (2u, (100u / (2u * (n + 1u)))); + if ((rnd % probability) == 0) + return n; + n++; + } +} + +/*****************************************************************************/ + +static inline gboolean +nmtst_g_source_assert_not_called (gpointer user_data) +{ + g_assert_not_reached (); + return G_SOURCE_CONTINUE; +} + +static inline gboolean +nmtst_g_source_set_boolean_true (gpointer user_data) +{ + gboolean *ptr = user_data; + + g_assert (ptr); + g_assert (!*ptr); + *ptr = TRUE; + return G_SOURCE_CONTINUE; +} + +/*****************************************************************************/ + static inline gboolean _nmtst_main_loop_run_timeout (gpointer user_data) { @@ -978,13 +1080,13 @@ _nmtst_main_loop_run_timeout (gpointer user_data) } static inline gboolean -nmtst_main_loop_run (GMainLoop *loop, guint timeout_ms) +nmtst_main_loop_run (GMainLoop *loop, guint timeout_msec) { nm_auto_unref_gsource GSource *source = NULL; GMainLoop *loopx = loop; - if (timeout_ms > 0) { - source = g_timeout_source_new (timeout_ms); + if (timeout_msec > 0) { + source = g_timeout_source_new (timeout_msec); g_source_set_callback (source, _nmtst_main_loop_run_timeout, &loopx, NULL); g_source_attach (source, g_main_loop_get_context (loop)); } @@ -1010,36 +1112,123 @@ _nmtst_main_loop_quit_on_notify (GObject *object, GParamSpec *pspec, gpointer us } #define nmtst_main_loop_quit_on_notify ((GCallback) _nmtst_main_loop_quit_on_notify) -static inline gboolean -_nmtst_main_context_iterate_until_timeout (gpointer user_data) -{ - gboolean *p_had_pointer = user_data; - - g_assert (!*p_had_pointer); - *p_had_pointer = TRUE; - return G_SOURCE_CONTINUE; -} - -#define nmtst_main_context_iterate_until(context, timeout_ms, condition) \ - G_STMT_START { \ +#define nmtst_main_context_iterate_until(context, timeout_msec, condition) \ + ({ \ nm_auto_destroy_and_unref_gsource GSource *_source = NULL; \ GMainContext *_context = (context); \ gboolean _had_timeout = FALSE; \ \ - _source = g_timeout_source_new (timeout_ms); \ - g_source_set_callback (_source, _nmtst_main_context_iterate_until_timeout, &_had_timeout, NULL); \ + _source = g_timeout_source_new (timeout_msec); \ + g_source_set_callback (_source, nmtst_g_source_set_boolean_true, &_had_timeout, NULL); \ g_source_attach (_source, _context); \ \ while (TRUE) { \ if (condition) \ break; \ g_main_context_iteration (_context, TRUE); \ - g_assert (!_had_timeout && #condition); \ + if (_had_timeout) \ + break; \ } \ + \ + !_had_timeout; \ + }) + +#define nmtst_main_context_iterate_until_assert(context, timeout_msec, condition) \ + G_STMT_START { \ + if (!nmtst_main_context_iterate_until (context, timeout_msec, condition)) \ + g_assert (FALSE && #condition); \ } G_STMT_END /*****************************************************************************/ +static inline void +nmtst_main_context_assert_no_dispatch (GMainContext *context, + guint timeout_msec) +{ + nm_auto_destroy_and_unref_gsource GSource *source = NULL; + gboolean timeout_hit = FALSE; + + source = g_timeout_source_new (timeout_msec); + g_source_set_callback (source, nmtst_g_source_set_boolean_true, &timeout_hit, NULL); + g_source_attach (source, context); + + while (g_main_context_iteration (context, TRUE)) { + if (timeout_hit) + return; + g_assert_not_reached (); + } +} + +/*****************************************************************************/ + +typedef struct { + GMainLoop *_main_loop; + union { + GSList *_list; + const void *const is_waiting; + }; +} NMTstContextBusyWatcherData; + +static inline void +_nmtst_context_busy_watcher_add_cb (gpointer data, + GObject *where_the_object_was) +{ + NMTstContextBusyWatcherData *watcher_data = data; + GSList *l; + + g_assert (watcher_data); + + l = g_slist_find (watcher_data->_list, where_the_object_was); + g_assert (l); + + watcher_data->_list = g_slist_delete_link (watcher_data->_list, l); + if (!watcher_data->_list) + g_main_loop_quit (watcher_data->_main_loop); +} + +static inline void +nmtst_context_busy_watcher_add (NMTstContextBusyWatcherData *watcher_data, + GObject *object) +{ + g_assert (watcher_data); + g_assert (G_IS_OBJECT (object)); + + if (!watcher_data->_main_loop) { + watcher_data->_main_loop = g_main_loop_new (g_main_context_get_thread_default (), + FALSE); + g_assert (!watcher_data->_list); + } else { + g_assert ( g_main_loop_get_context (watcher_data->_main_loop) + == (g_main_context_get_thread_default () ?: g_main_context_default ())); + } + + g_object_weak_ref (object, + _nmtst_context_busy_watcher_add_cb, + watcher_data); + watcher_data->_list = g_slist_prepend (watcher_data->_list, object); +} + +static inline void +nmtst_context_busy_watcher_wait (NMTstContextBusyWatcherData *watcher_data) +{ + g_assert (watcher_data); + + if (!watcher_data->_main_loop) { + g_assert (!watcher_data->_list); + return; + } + + if (watcher_data->_list) { + if (!nmtst_main_loop_run (watcher_data->_main_loop, 5000)) + g_error ("timeout running mainloop waiting for GObject to destruct"); + } + + g_assert (!watcher_data->_list); + nm_clear_pointer (&watcher_data->_main_loop, g_main_loop_unref); +} + +/*****************************************************************************/ + static inline const char * nmtst_get_sudo_cmd (void) { @@ -1156,15 +1345,6 @@ nmtst_uuid_generate (void) #endif -#define NMTST_SWAP(x, y) \ - G_STMT_START { \ - char __nmtst_swap_temp[sizeof((x)) == sizeof((y)) ? (signed) sizeof((x)) : -1]; \ - \ - memcpy(__nmtst_swap_temp, &(y), sizeof (__nmtst_swap_temp)); \ - memcpy(&(y), &(x), sizeof (__nmtst_swap_temp)); \ - memcpy(&(x), __nmtst_swap_temp, sizeof (__nmtst_swap_temp)); \ - } G_STMT_END - #define nmtst_assert_str_has_substr(str, substr) \ G_STMT_START { \ const char *__str = (str); \ @@ -1362,15 +1542,27 @@ nmtst_file_get_contents (const char *filename) return contents; } -#define nmtst_file_set_contents(filename, content) \ +#define nmtst_file_set_contents_size(filename, content, size) \ G_STMT_START { \ GError *_error = NULL; \ gboolean _success; \ + const char *_content = (content); \ + gssize _size = (size); \ + \ + g_assert (_content); \ \ - _success = g_file_set_contents ((filename), (content), -1, &_error); \ + if (_size < 0) { \ + g_assert (_size == -1); \ + _size = strlen (_content); \ + } \ + \ + _success = g_file_set_contents ((filename), _content, _size, &_error); \ nmtst_assert_success (_success, _error); \ } G_STMT_END +#define nmtst_file_set_contents(filename, content) \ + nmtst_file_set_contents_size (filename, content, -1) + /*****************************************************************************/ static inline void @@ -1478,9 +1670,9 @@ nmtst_setting_ip_config_add_address (NMSettingIPConfig *s_ip, g_assert (s_ip); - if (nm_utils_ipaddr_valid (AF_INET, address)) + if (nm_utils_ipaddr_is_valid (AF_INET, address)) family = AF_INET; - else if (nm_utils_ipaddr_valid (AF_INET6, address)) + else if (nm_utils_ipaddr_is_valid (AF_INET6, address)) family = AF_INET6; else g_assert_not_reached (); @@ -1503,9 +1695,9 @@ nmtst_setting_ip_config_add_route (NMSettingIPConfig *s_ip, g_assert (s_ip); - if (nm_utils_ipaddr_valid (AF_INET, dest)) + if (nm_utils_ipaddr_is_valid (AF_INET, dest)) family = AF_INET; - else if (nm_utils_ipaddr_valid (AF_INET6, dest)) + else if (nm_utils_ipaddr_is_valid (AF_INET6, dest)) family = AF_INET6; else g_assert_not_reached (); @@ -1611,8 +1803,12 @@ nmtst_create_minimal_connection (const char *id, const char *uuid, const char *t con = nm_connection_new (); #endif + g_assert (con); + s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ()); + g_assert (s_con); + g_object_set (s_con, NM_SETTING_CONNECTION_ID, id, NM_SETTING_CONNECTION_UUID, uuid, @@ -1959,14 +2155,14 @@ nmtst_assert_setting_is_equal (gconstpointer /* const NMSetting * */ a, g_assert (NM_IS_SETTING (b)); if (NM_FLAGS_HAS (r, 0x4)) - NMTST_SWAP (a, b); + NM_SWAP (a, b); g_assert (nm_setting_compare ((NMSetting *) a, (NMSetting *) b, flags)); if (NM_FLAGS_HAS (r, 0x8)) - NMTST_SWAP (a, b); + NM_SWAP (a, b); g_assert (nm_setting_diff ((NMSetting *) a, (NMSetting *) b, @@ -2311,13 +2507,4 @@ nmtst_keyfile_get_num_keys (GKeyFile *keyfile, /*****************************************************************************/ -static inline gboolean -nmtst_g_source_assert_not_called (gpointer user_data) -{ - g_assert_not_reached (); - return G_SOURCE_CONTINUE; -} - -/*****************************************************************************/ - #endif /* __NM_TEST_UTILS_H__ */ diff --git a/shared/nm-utils/nm-vpn-editor-plugin-call.h b/shared/nm-utils/nm-vpn-editor-plugin-call.h index ab473083..b2c39e29 100644 --- a/shared/nm-utils/nm-vpn-editor-plugin-call.h +++ b/shared/nm-utils/nm-vpn-editor-plugin-call.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0+ +// SPDX-License-Identifier: LGPL-2.1+ /* * Copyright (C) 2016 Red Hat, Inc. */ diff --git a/shared/nm-version-macros.h b/shared/nm-version-macros.h index 70ef84f9..a085c115 100644 --- a/shared/nm-version-macros.h +++ b/shared/nm-version-macros.h @@ -22,7 +22,7 @@ * Evaluates to the minor version number of NetworkManager which this source * is compiled against. */ -#define NM_MINOR_VERSION (22) +#define NM_MINOR_VERSION (23) /** * NM_MICRO_VERSION: @@ -30,7 +30,7 @@ * Evaluates to the micro version number of NetworkManager which this source * compiled against. */ -#define NM_MICRO_VERSION (10) +#define NM_MICRO_VERSION (90) /** * NM_CHECK_VERSION: @@ -63,8 +63,7 @@ #define NM_VERSION_1_18 (NM_ENCODE_VERSION (1, 18, 0)) #define NM_VERSION_1_20 (NM_ENCODE_VERSION (1, 20, 0)) #define NM_VERSION_1_22 (NM_ENCODE_VERSION (1, 22, 0)) -#define NM_VERSION_1_22_2 (NM_ENCODE_VERSION (1, 22, 2)) -#define NM_VERSION_1_22_8 (NM_ENCODE_VERSION (1, 22, 8)) +#define NM_VERSION_1_24 (NM_ENCODE_VERSION (1, 24, 0)) /* For releases, NM_API_VERSION is equal to NM_VERSION. * diff --git a/shared/nm-version-macros.h.in b/shared/nm-version-macros.h.in index 8704ac57..e97f506c 100644 --- a/shared/nm-version-macros.h.in +++ b/shared/nm-version-macros.h.in @@ -63,8 +63,7 @@ #define NM_VERSION_1_18 (NM_ENCODE_VERSION (1, 18, 0)) #define NM_VERSION_1_20 (NM_ENCODE_VERSION (1, 20, 0)) #define NM_VERSION_1_22 (NM_ENCODE_VERSION (1, 22, 0)) -#define NM_VERSION_1_22_2 (NM_ENCODE_VERSION (1, 22, 2)) -#define NM_VERSION_1_22_8 (NM_ENCODE_VERSION (1, 22, 8)) +#define NM_VERSION_1_24 (NM_ENCODE_VERSION (1, 24, 0)) /* For releases, NM_API_VERSION is equal to NM_VERSION. * diff --git a/shared/systemd/sd-adapt-shared/missing_syscall.h b/shared/systemd/sd-adapt-shared/arphrd-list.h index 637892c2..637892c2 100644 --- a/shared/systemd/sd-adapt-shared/missing_syscall.h +++ b/shared/systemd/sd-adapt-shared/arphrd-list.h diff --git a/shared/systemd/sd-adapt-shared/missing_keyctl.h b/shared/systemd/sd-adapt-shared/missing_keyctl.h new file mode 100644 index 00000000..637892c2 --- /dev/null +++ b/shared/systemd/sd-adapt-shared/missing_keyctl.h @@ -0,0 +1,3 @@ +#pragma once + +/* dummy header */ diff --git a/shared/systemd/sd-adapt-shared/nm-sd-adapt-shared.h b/shared/systemd/sd-adapt-shared/nm-sd-adapt-shared.h index 490cae92..26a043f5 100644 --- a/shared/systemd/sd-adapt-shared/nm-sd-adapt-shared.h +++ b/shared/systemd/sd-adapt-shared/nm-sd-adapt-shared.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0+ +// SPDX-License-Identifier: LGPL-2.1+ /* * Copyright (C) 2014 - 2018 Red Hat, Inc. */ @@ -84,6 +84,8 @@ G_STMT_START { \ #include <sys/syscall.h> #include <sys/ioctl.h> +#define ENABLE_GSHADOW FALSE + /*****************************************************************************/ /* systemd cannot be compiled with "-Wdeclaration-after-statement". In particular @@ -122,6 +124,24 @@ static inline pid_t _nm_gettid(void) { #define HAVE_EXPLICIT_BZERO 0 #endif +#if defined(HAVE_DECL_PIDFD_OPEN) && HAVE_DECL_PIDFD_OPEN == 1 +#define HAVE_PIDFD_OPEN 1 +#else +#define HAVE_PIDFD_OPEN 0 +#endif + +#if defined(HAVE_DECL_PIDFD_SEND_SIGNAL) && HAVE_DECL_PIDFD_SEND_SIGNAL == 1 +#define HAVE_PIDFD_SEND_SIGNAL 1 +#else +#define HAVE_PIDFD_SEND_SIGNAL 0 +#endif + +#if defined(HAVE_DECL_RT_SIGQUEUEINFO) && HAVE_DECL_RT_SIGQUEUEINFO == 1 +#define HAVE_RT_SIGQUEUEINFO 1 +#else +#define HAVE_RT_SIGQUEUEINFO 0 +#endif + #endif /* (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD */ /*****************************************************************************/ diff --git a/shared/systemd/src/basic/cgroup-util.h b/shared/systemd/src/basic/cgroup-util.h new file mode 100644 index 00000000..237139fa --- /dev/null +++ b/shared/systemd/src/basic/cgroup-util.h @@ -0,0 +1,254 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include <dirent.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <sys/statfs.h> +#include <sys/types.h> + +#include "def.h" +#include "set.h" + +#define SYSTEMD_CGROUP_CONTROLLER_LEGACY "name=systemd" +#define SYSTEMD_CGROUP_CONTROLLER_HYBRID "name=unified" +#define SYSTEMD_CGROUP_CONTROLLER "_systemd" + +/* An enum of well known cgroup controllers */ +typedef enum CGroupController { + /* Original cgroup controllers */ + CGROUP_CONTROLLER_CPU, + CGROUP_CONTROLLER_CPUACCT, /* v1 only */ + CGROUP_CONTROLLER_CPUSET, /* v2 only */ + CGROUP_CONTROLLER_IO, /* v2 only */ + CGROUP_CONTROLLER_BLKIO, /* v1 only */ + CGROUP_CONTROLLER_MEMORY, + CGROUP_CONTROLLER_DEVICES, /* v1 only */ + CGROUP_CONTROLLER_PIDS, + + /* BPF-based pseudo-controllers, v2 only */ + CGROUP_CONTROLLER_BPF_FIREWALL, + CGROUP_CONTROLLER_BPF_DEVICES, + + _CGROUP_CONTROLLER_MAX, + _CGROUP_CONTROLLER_INVALID = -1, +} CGroupController; + +#define CGROUP_CONTROLLER_TO_MASK(c) (1U << (c)) + +/* A bit mask of well known cgroup controllers */ +typedef enum CGroupMask { + CGROUP_MASK_CPU = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_CPU), + CGROUP_MASK_CPUACCT = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_CPUACCT), + CGROUP_MASK_CPUSET = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_CPUSET), + CGROUP_MASK_IO = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_IO), + CGROUP_MASK_BLKIO = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BLKIO), + CGROUP_MASK_MEMORY = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_MEMORY), + CGROUP_MASK_DEVICES = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_DEVICES), + CGROUP_MASK_PIDS = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_PIDS), + CGROUP_MASK_BPF_FIREWALL = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BPF_FIREWALL), + CGROUP_MASK_BPF_DEVICES = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BPF_DEVICES), + + /* All real cgroup v1 controllers */ + CGROUP_MASK_V1 = CGROUP_MASK_CPU|CGROUP_MASK_CPUACCT|CGROUP_MASK_BLKIO|CGROUP_MASK_MEMORY|CGROUP_MASK_DEVICES|CGROUP_MASK_PIDS, + + /* All real cgroup v2 controllers */ + CGROUP_MASK_V2 = CGROUP_MASK_CPU|CGROUP_MASK_CPUSET|CGROUP_MASK_IO|CGROUP_MASK_MEMORY|CGROUP_MASK_PIDS, + + /* All cgroup v2 BPF pseudo-controllers */ + CGROUP_MASK_BPF = CGROUP_MASK_BPF_FIREWALL|CGROUP_MASK_BPF_DEVICES, + + _CGROUP_MASK_ALL = CGROUP_CONTROLLER_TO_MASK(_CGROUP_CONTROLLER_MAX) - 1 +} CGroupMask; + +static inline CGroupMask CGROUP_MASK_EXTEND_JOINED(CGroupMask mask) { + /* We always mount "cpu" and "cpuacct" in the same hierarchy. Hence, when one bit is set also set the other */ + + if (mask & (CGROUP_MASK_CPU|CGROUP_MASK_CPUACCT)) + mask |= (CGROUP_MASK_CPU|CGROUP_MASK_CPUACCT); + + return mask; +} + +CGroupMask get_cpu_accounting_mask(void); +bool cpu_accounting_is_cheap(void); + +/* Special values for all weight knobs on unified hierarchy */ +#define CGROUP_WEIGHT_INVALID ((uint64_t) -1) +#define CGROUP_WEIGHT_MIN UINT64_C(1) +#define CGROUP_WEIGHT_MAX UINT64_C(10000) +#define CGROUP_WEIGHT_DEFAULT UINT64_C(100) + +#define CGROUP_LIMIT_MIN UINT64_C(0) +#define CGROUP_LIMIT_MAX ((uint64_t) -1) + +static inline bool CGROUP_WEIGHT_IS_OK(uint64_t x) { + return + x == CGROUP_WEIGHT_INVALID || + (x >= CGROUP_WEIGHT_MIN && x <= CGROUP_WEIGHT_MAX); +} + +/* IO limits on unified hierarchy */ +typedef enum CGroupIOLimitType { + CGROUP_IO_RBPS_MAX, + CGROUP_IO_WBPS_MAX, + CGROUP_IO_RIOPS_MAX, + CGROUP_IO_WIOPS_MAX, + + _CGROUP_IO_LIMIT_TYPE_MAX, + _CGROUP_IO_LIMIT_TYPE_INVALID = -1 +} CGroupIOLimitType; + +extern const uint64_t cgroup_io_limit_defaults[_CGROUP_IO_LIMIT_TYPE_MAX]; + +const char* cgroup_io_limit_type_to_string(CGroupIOLimitType t) _const_; +CGroupIOLimitType cgroup_io_limit_type_from_string(const char *s) _pure_; + +/* Special values for the cpu.shares attribute */ +#define CGROUP_CPU_SHARES_INVALID ((uint64_t) -1) +#define CGROUP_CPU_SHARES_MIN UINT64_C(2) +#define CGROUP_CPU_SHARES_MAX UINT64_C(262144) +#define CGROUP_CPU_SHARES_DEFAULT UINT64_C(1024) + +static inline bool CGROUP_CPU_SHARES_IS_OK(uint64_t x) { + return + x == CGROUP_CPU_SHARES_INVALID || + (x >= CGROUP_CPU_SHARES_MIN && x <= CGROUP_CPU_SHARES_MAX); +} + +/* Special values for the blkio.weight attribute */ +#define CGROUP_BLKIO_WEIGHT_INVALID ((uint64_t) -1) +#define CGROUP_BLKIO_WEIGHT_MIN UINT64_C(10) +#define CGROUP_BLKIO_WEIGHT_MAX UINT64_C(1000) +#define CGROUP_BLKIO_WEIGHT_DEFAULT UINT64_C(500) + +static inline bool CGROUP_BLKIO_WEIGHT_IS_OK(uint64_t x) { + return + x == CGROUP_BLKIO_WEIGHT_INVALID || + (x >= CGROUP_BLKIO_WEIGHT_MIN && x <= CGROUP_BLKIO_WEIGHT_MAX); +} + +typedef enum CGroupUnified { + CGROUP_UNIFIED_UNKNOWN = -1, + CGROUP_UNIFIED_NONE = 0, /* Both systemd and controllers on legacy */ + CGROUP_UNIFIED_SYSTEMD = 1, /* Only systemd on unified */ + CGROUP_UNIFIED_ALL = 2, /* Both systemd and controllers on unified */ +} CGroupUnified; + +/* + * General rules: + * + * We accept named hierarchies in the syntax "foo" and "name=foo". + * + * We expect that named hierarchies do not conflict in name with a + * kernel hierarchy, modulo the "name=" prefix. + * + * We always generate "normalized" controller names, i.e. without the + * "name=" prefix. + * + * We require absolute cgroup paths. When returning, we will always + * generate paths with multiple adjacent / removed. + */ + +int cg_enumerate_processes(const char *controller, const char *path, FILE **_f); +int cg_read_pid(FILE *f, pid_t *_pid); +int cg_read_event(const char *controller, const char *path, const char *event, + char **val); + +int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d); +int cg_read_subgroup(DIR *d, char **fn); + +typedef enum CGroupFlags { + CGROUP_SIGCONT = 1 << 0, + CGROUP_IGNORE_SELF = 1 << 1, + CGROUP_REMOVE = 1 << 2, +} CGroupFlags; + +typedef int (*cg_kill_log_func_t)(pid_t pid, int sig, void *userdata); + +int cg_kill(const char *controller, const char *path, int sig, CGroupFlags flags, Set *s, cg_kill_log_func_t kill_log, void *userdata); +int cg_kill_recursive(const char *controller, const char *path, int sig, CGroupFlags flags, Set *s, cg_kill_log_func_t kill_log, void *userdata); + +int cg_split_spec(const char *spec, char **ret_controller, char **ret_path); +int cg_mangle_path(const char *path, char **result); + +int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs); +int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **fs); + +int cg_pid_get_path(const char *controller, pid_t pid, char **path); + +int cg_rmdir(const char *controller, const char *path); + +int cg_set_attribute(const char *controller, const char *path, const char *attribute, const char *value); +int cg_get_attribute(const char *controller, const char *path, const char *attribute, char **ret); +int cg_get_keyed_attribute(const char *controller, const char *path, const char *attribute, char **keys, char **values); + +int cg_get_attribute_as_uint64(const char *controller, const char *path, const char *attribute, uint64_t *ret); + +int cg_set_access(const char *controller, const char *path, uid_t uid, gid_t gid); + +int cg_set_xattr(const char *controller, const char *path, const char *name, const void *value, size_t size, int flags); +int cg_get_xattr(const char *controller, const char *path, const char *name, void *value, size_t size); +int cg_get_xattr_malloc(const char *controller, const char *path, const char *name, char **ret); +int cg_remove_xattr(const char *controller, const char *path, const char *name); + +int cg_install_release_agent(const char *controller, const char *agent); +int cg_uninstall_release_agent(const char *controller); + +int cg_is_empty(const char *controller, const char *path); +int cg_is_empty_recursive(const char *controller, const char *path); + +int cg_get_root_path(char **path); + +int cg_path_get_session(const char *path, char **session); +int cg_path_get_owner_uid(const char *path, uid_t *uid); +int cg_path_get_unit(const char *path, char **unit); +int cg_path_get_user_unit(const char *path, char **unit); +int cg_path_get_machine_name(const char *path, char **machine); +int cg_path_get_slice(const char *path, char **slice); +int cg_path_get_user_slice(const char *path, char **slice); + +int cg_shift_path(const char *cgroup, const char *cached_root, const char **shifted); +int cg_pid_get_path_shifted(pid_t pid, const char *cached_root, char **cgroup); + +int cg_pid_get_session(pid_t pid, char **session); +int cg_pid_get_owner_uid(pid_t pid, uid_t *uid); +int cg_pid_get_unit(pid_t pid, char **unit); +int cg_pid_get_user_unit(pid_t pid, char **unit); +int cg_pid_get_machine_name(pid_t pid, char **machine); +int cg_pid_get_slice(pid_t pid, char **slice); +int cg_pid_get_user_slice(pid_t pid, char **slice); + +int cg_path_decode_unit(const char *cgroup, char **unit); + +char *cg_escape(const char *p); +char *cg_unescape(const char *p) _pure_; + +bool cg_controller_is_valid(const char *p); + +int cg_slice_to_path(const char *unit, char **ret); + +typedef const char* (*cg_migrate_callback_t)(CGroupMask mask, void *userdata); + +int cg_mask_supported(CGroupMask *ret); +int cg_mask_from_string(const char *s, CGroupMask *ret); +int cg_mask_to_string(CGroupMask mask, char **ret); + +int cg_kernel_controllers(Set **controllers); + +bool cg_ns_supported(void); + +int cg_all_unified(void); +int cg_hybrid_unified(void); +int cg_unified_controller(const char *controller); +int cg_unified_cached(bool flush); +static inline int cg_unified(void) { + return cg_unified_cached(true); +} + +const char* cgroup_controller_to_string(CGroupController c) _const_; +CGroupController cgroup_controller_from_string(const char *s) _pure_; + +bool is_cgroup_fs(const struct statfs *s); +bool fd_is_cgroup_fs(int fd); diff --git a/shared/systemd/src/basic/errno-util.h b/shared/systemd/src/basic/errno-util.h index 8f1be6c0..65a6384e 100644 --- a/shared/systemd/src/basic/errno-util.h +++ b/shared/systemd/src/basic/errno-util.h @@ -101,3 +101,11 @@ static inline bool ERRNO_IS_PRIVILEGE(int r) { EACCES, EPERM); } + +/* Three difference errors for "not enough disk space" */ +static inline bool ERRNO_IS_DISK_SPACE(int r) { + return IN_SET(abs(r), + ENOSPC, + EDQUOT, + EFBIG); +} diff --git a/shared/systemd/src/basic/escape.c b/shared/systemd/src/basic/escape.c index 06d823c7..2cc5be18 100644 --- a/shared/systemd/src/basic/escape.c +++ b/shared/systemd/src/basic/escape.c @@ -104,7 +104,7 @@ char *cescape(const char *s) { return cescape_length(s, strlen(s)); } -int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit) { +int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit, bool accept_nul) { int r = 1; assert(p); @@ -173,7 +173,7 @@ int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit) return -EINVAL; /* Don't allow NUL bytes */ - if (a == 0 && b == 0) + if (a == 0 && b == 0 && !accept_nul) return -EINVAL; *ret = (a << 4U) | b; @@ -201,7 +201,7 @@ int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit) c = ((uint32_t) a[0] << 12U) | ((uint32_t) a[1] << 8U) | ((uint32_t) a[2] << 4U) | (uint32_t) a[3]; /* Don't allow 0 chars */ - if (c == 0) + if (c == 0 && !accept_nul) return -EINVAL; *ret = c; @@ -229,7 +229,7 @@ int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit) ((uint32_t) a[4] << 12U) | ((uint32_t) a[5] << 8U) | ((uint32_t) a[6] << 4U) | (uint32_t) a[7]; /* Don't allow 0 chars */ - if (c == 0) + if (c == 0 && !accept_nul) return -EINVAL; /* Don't allow invalid code points */ @@ -269,7 +269,7 @@ int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit) return -EINVAL; /* don't allow NUL bytes */ - if (a == 0 && b == 0 && c == 0) + if (a == 0 && b == 0 && c == 0 && !accept_nul) return -EINVAL; /* Don't allow bytes above 255 */ @@ -290,6 +290,7 @@ int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit) return r; } +#if 0 /* NM_IGNORED */ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret) { char *r, *t; const char *f; @@ -335,7 +336,7 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi return -EINVAL; } - k = cunescape_one(f + 1, remaining - 1, &u, &eight_bit); + k = cunescape_one(f + 1, remaining - 1, &u, &eight_bit, flags & UNESCAPE_ACCEPT_NUL); if (k < 0) { if (flags & UNESCAPE_RELAX) { /* Invalid escape code, let's take it literal then */ @@ -362,15 +363,6 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi return t - r; } -int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret) { - return cunescape_length_with_prefix(s, length, NULL, flags, ret); -} - -int cunescape(const char *s, UnescapeFlags flags, char **ret) { - return cunescape_length(s, strlen(s), flags, ret); -} - -#if 0 /* NM_IGNORED */ char *xescape_full(const char *s, const char *bad, size_t console_width, bool eight_bits) { char *ans, *t, *prev, *prev2; const char *f; diff --git a/shared/systemd/src/basic/escape.h b/shared/systemd/src/basic/escape.h index b26054c5..b8eb137c 100644 --- a/shared/systemd/src/basic/escape.h +++ b/shared/systemd/src/basic/escape.h @@ -29,22 +29,27 @@ #define SHELL_NEED_ESCAPE_POSIX "\\\'" typedef enum UnescapeFlags { - UNESCAPE_RELAX = 1, + UNESCAPE_RELAX = 1 << 0, + UNESCAPE_ACCEPT_NUL = 1 << 1, } UnescapeFlags; typedef enum EscapeStyle { ESCAPE_BACKSLASH = 1, - ESCAPE_POSIX = 2, + ESCAPE_POSIX = 2, } EscapeStyle; char *cescape(const char *s); char *cescape_length(const char *s, size_t n); int cescape_char(char c, char *buf); -int cunescape(const char *s, UnescapeFlags flags, char **ret); -int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret); int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret); -int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit); +static inline int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret) { + return cunescape_length_with_prefix(s, length, NULL, flags, ret); +} +static inline int cunescape(const char *s, UnescapeFlags flags, char **ret) { + return cunescape_length(s, strlen(s), flags, ret); +} +int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit, bool accept_nul); char *xescape_full(const char *s, const char *bad, size_t console_width, bool eight_bits); static inline char *xescape(const char *s, const char *bad) { diff --git a/shared/systemd/src/basic/extract-word.c b/shared/systemd/src/basic/extract-word.c index 2da25b03..62f015f3 100644 --- a/shared/systemd/src/basic/extract-word.c +++ b/shared/systemd/src/basic/extract-word.c @@ -92,7 +92,7 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra bool eight_bit = false; char32_t u; - r = cunescape_one(*p, (size_t) -1, &u, &eight_bit); + r = cunescape_one(*p, (size_t) -1, &u, &eight_bit, false); if (r < 0) { if (flags & EXTRACT_CUNESCAPE_RELAX) { s[sz++] = '\\'; diff --git a/shared/systemd/src/basic/fileio.c b/shared/systemd/src/basic/fileio.c index 3bffa1c5..543dd4eb 100644 --- a/shared/systemd/src/basic/fileio.c +++ b/shared/systemd/src/basic/fileio.c @@ -56,6 +56,44 @@ int fdopen_unlocked(int fd, const char *options, FILE **ret) { return 0; } +int take_fdopen_unlocked(int *fd, const char *options, FILE **ret) { + int r; + + assert(fd); + + r = fdopen_unlocked(*fd, options, ret); + if (r < 0) + return r; + + *fd = -1; + + return 0; +} + +FILE* take_fdopen(int *fd, const char *options) { + assert(fd); + + FILE *f = fdopen(*fd, options); + if (!f) + return NULL; + + *fd = -1; + + return f; +} + +DIR* take_fdopendir(int *dfd) { + assert(dfd); + + DIR *d = fdopendir(*dfd); + if (!d) + return NULL; + + *dfd = -1; + + return d; +} + FILE* open_memstream_unlocked(char **ptr, size_t *sizeloc) { FILE *f = open_memstream(ptr, sizeloc); if (!f) @@ -139,16 +177,21 @@ static int write_string_file_atomic( assert(fn); assert(line); + /* Note that we'd really like to use O_TMPFILE here, but can't really, since we want replacement + * semantics here, and O_TMPFILE can't offer that. i.e. rename() replaces but linkat() doesn't. */ + r = fopen_temporary(fn, &f, &p); if (r < 0) return r; - (void) fchmod_umask(fileno(f), 0644); - r = write_string_stream_ts(f, line, flags, ts); if (r < 0) goto fail; + r = fchmod_umask(fileno(f), FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 : 0644); + if (r < 0) + goto fail; + if (rename(p, fn) < 0) { r = -errno; goto fail; @@ -168,7 +211,7 @@ int write_string_file_ts( struct timespec *ts) { _cleanup_fclose_ FILE *f = NULL; - int q, r; + int q, r, fd; assert(fn); assert(line); @@ -193,26 +236,20 @@ int write_string_file_ts( } else assert(!ts); - if (flags & WRITE_STRING_FILE_CREATE) { - r = fopen_unlocked(fn, "we", &f); - if (r < 0) - goto fail; - } else { - int fd; - - /* We manually build our own version of fopen(..., "we") that - * works without O_CREAT */ - fd = open(fn, O_WRONLY|O_CLOEXEC|O_NOCTTY | ((flags & WRITE_STRING_FILE_NOFOLLOW) ? O_NOFOLLOW : 0)); - if (fd < 0) { - r = -errno; - goto fail; - } + /* We manually build our own version of fopen(..., "we") that works without O_CREAT and with O_NOFOLLOW if needed. */ + fd = open(fn, O_WRONLY|O_CLOEXEC|O_NOCTTY | + (FLAGS_SET(flags, WRITE_STRING_FILE_NOFOLLOW) ? O_NOFOLLOW : 0) | + (FLAGS_SET(flags, WRITE_STRING_FILE_CREATE) ? O_CREAT : 0), + (FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 : 0666)); + if (fd < 0) { + r = -errno; + goto fail; + } - r = fdopen_unlocked(fd, "w", &f); - if (r < 0) { - safe_close(fd); - goto fail; - } + r = fdopen_unlocked(fd, "w", &f); + if (r < 0) { + safe_close(fd); + goto fail; } if (flags & WRITE_STRING_FILE_DISABLE_BUFFER) @@ -547,17 +584,19 @@ finalize: return r; } -int read_full_file_full(const char *filename, ReadFullFileFlags flags, char **contents, size_t *size) { +int read_full_file_full(int dir_fd, const char *filename, ReadFullFileFlags flags, char **contents, size_t *size) { _cleanup_fclose_ FILE *f = NULL; int r; assert(filename); assert(contents); - r = fopen_unlocked(filename, "re", &f); + r = xfopenat(dir_fd, filename, "re", 0, &f); if (r < 0) return r; + (void) __fsetlocking(f, FSETLOCKING_BYCALLER); + return read_full_stream_full(f, filename, flags, contents, size); } @@ -686,6 +725,81 @@ DIR *xopendirat(int fd, const char *name, int flags) { return d; } +static int mode_to_flags(const char *mode) { + const char *p; + int flags; + + if ((p = startswith(mode, "r+"))) + flags = O_RDWR; + else if ((p = startswith(mode, "r"))) + flags = O_RDONLY; + else if ((p = startswith(mode, "w+"))) + flags = O_RDWR|O_CREAT|O_TRUNC; + else if ((p = startswith(mode, "w"))) + flags = O_WRONLY|O_CREAT|O_TRUNC; + else if ((p = startswith(mode, "a+"))) + flags = O_RDWR|O_CREAT|O_APPEND; + else if ((p = startswith(mode, "a"))) + flags = O_WRONLY|O_CREAT|O_APPEND; + else + return -EINVAL; + + for (; *p != 0; p++) { + + switch (*p) { + + case 'e': + flags |= O_CLOEXEC; + break; + + case 'x': + flags |= O_EXCL; + break; + + case 'm': + /* ignore this here, fdopen() might care later though */ + break; + + case 'c': /* not sure what to do about this one */ + default: + return -EINVAL; + } + } + + return flags; +} + +int xfopenat(int dir_fd, const char *path, const char *mode, int flags, FILE **ret) { + FILE *f; + + /* A combination of fopen() with openat() */ + + if (dir_fd == AT_FDCWD && flags == 0) { + f = fopen(path, mode); + if (!f) + return -errno; + } else { + int fd, mode_flags; + + mode_flags = mode_to_flags(mode); + if (mode_flags < 0) + return mode_flags; + + fd = openat(dir_fd, path, mode_flags | flags); + if (fd < 0) + return -errno; + + f = fdopen(fd, mode); + if (!f) { + safe_close(fd); + return -errno; + } + } + + *ret = f; + return 0; +} + #if 0 /* NM_IGNORED */ static int search_and_fopen_internal(const char *path, const char *mode, const char *root, char **search, FILE **_f) { char **i; diff --git a/shared/systemd/src/basic/fileio.h b/shared/systemd/src/basic/fileio.h index 31bfef33..58daabaa 100644 --- a/shared/systemd/src/basic/fileio.h +++ b/shared/systemd/src/basic/fileio.h @@ -6,6 +6,7 @@ #include <stddef.h> #include <stdio.h> #include <sys/stat.h> +#include <sys/fcntl.h> #include <sys/types.h> #include "macro.h" @@ -22,6 +23,7 @@ typedef enum { WRITE_STRING_FILE_DISABLE_BUFFER = 1 << 5, WRITE_STRING_FILE_NOFOLLOW = 1 << 6, WRITE_STRING_FILE_MKDIR_0755 = 1 << 7, + WRITE_STRING_FILE_MODE_0600 = 1 << 8, /* And before you wonder, why write_string_file_atomic_label_ts() is a separate function instead of just one more flag here: it's about linking: we don't want to pull -lselinux into all users of write_string_file() @@ -37,6 +39,9 @@ typedef enum { int fopen_unlocked(const char *path, const char *options, FILE **ret); int fdopen_unlocked(int fd, const char *options, FILE **ret); +int take_fdopen_unlocked(int *fd, const char *options, FILE **ret); +FILE* take_fdopen(int *fd, const char *options); +DIR* take_fdopendir(int *dfd); FILE* open_memstream_unlocked(char **ptr, size_t *sizeloc); FILE* fmemopen_unlocked(void *buf, size_t size, const char *mode); @@ -52,9 +57,9 @@ static inline int write_string_file(const char *fn, const char *line, WriteStrin int write_string_filef(const char *fn, WriteStringFileFlags flags, const char *format, ...) _printf_(3, 4); int read_one_line_file(const char *filename, char **line); -int read_full_file_full(const char *filename, ReadFullFileFlags flags, char **contents, size_t *size); +int read_full_file_full(int dir_fd, const char *filename, ReadFullFileFlags flags, char **contents, size_t *size); static inline int read_full_file(const char *filename, char **contents, size_t *size) { - return read_full_file_full(filename, 0, contents, size); + return read_full_file_full(AT_FDCWD, filename, 0, contents, size); } int read_full_virtual_file(const char *filename, char **ret_contents, size_t *ret_size); int read_full_stream_full(FILE *f, const char *filename, ReadFullFileFlags flags, char **contents, size_t *size); @@ -69,6 +74,7 @@ int executable_is_script(const char *path, char **interpreter); int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field); DIR *xopendirat(int dirfd, const char *name, int flags); +int xfopenat(int dir_fd, const char *path, const char *mode, int flags, FILE **ret); int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **_f); int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **_f); diff --git a/shared/systemd/src/basic/format-util.h b/shared/systemd/src/basic/format-util.h index 59622508..c47fa76e 100644 --- a/shared/systemd/src/basic/format-util.h +++ b/shared/systemd/src/basic/format-util.h @@ -5,30 +5,18 @@ #include <net/if.h> #include <stdbool.h> -#if SIZEOF_PID_T == 4 -# define PID_PRI PRIi32 -#elif SIZEOF_PID_T == 2 -# define PID_PRI PRIi16 -#else -# error Unknown pid_t size -#endif +#include "cgroup-util.h" +#include "macro.h" + +assert_cc(sizeof(pid_t) == sizeof(int32_t)); +#define PID_PRI PRIi32 #define PID_FMT "%" PID_PRI -#if SIZEOF_UID_T == 4 -# define UID_FMT "%" PRIu32 -#elif SIZEOF_UID_T == 2 -# define UID_FMT "%" PRIu16 -#else -# error Unknown uid_t size -#endif +assert_cc(sizeof(uid_t) == sizeof(uint32_t)); +#define UID_FMT "%" PRIu32 -#if SIZEOF_GID_T == 4 -# define GID_FMT "%" PRIu32 -#elif SIZEOF_GID_T == 2 -# define GID_FMT "%" PRIu16 -#else -# error Unknown gid_t size -#endif +assert_cc(sizeof(gid_t) == sizeof(uint32_t)); +#define GID_FMT "%" PRIu32 #if SIZEOF_TIME_T == 8 # define PRI_TIME PRIi64 @@ -84,8 +72,15 @@ typedef enum { FORMAT_BYTES_TRAILING_B = 1 << 2, } FormatBytesFlag; -#define FORMAT_BYTES_MAX 8 +#define FORMAT_BYTES_MAX 16 char *format_bytes_full(char *buf, size_t l, uint64_t t, FormatBytesFlag flag); static inline char *format_bytes(char *buf, size_t l, uint64_t t) { return format_bytes_full(buf, l, t, FORMAT_BYTES_USE_IEC | FORMAT_BYTES_BELOW_POINT | FORMAT_BYTES_TRAILING_B); } +static inline char *format_bytes_cgroup_protection(char *buf, size_t l, uint64_t t) { + if (t == CGROUP_LIMIT_MAX) { + (void) snprintf(buf, l, "%s", "infinity"); + return buf; + } + return format_bytes(buf, l, t); +} diff --git a/shared/systemd/src/basic/fs-util.c b/shared/systemd/src/basic/fs-util.c index e30f40fb..79966571 100644 --- a/shared/systemd/src/basic/fs-util.c +++ b/shared/systemd/src/basic/fs-util.c @@ -276,7 +276,52 @@ int fchmod_and_chown(int fd, mode_t mode, uid_t uid, gid_t gid) { return do_chown || do_chmod; } -#endif /* NM_IGNORED */ + +int chmod_and_chown_unsafe(const char *path, mode_t mode, uid_t uid, gid_t gid) { + bool do_chown, do_chmod; + struct stat st; + + assert(path); + + /* Change ownership and access mode of the specified path, see description of fchmod_and_chown(). + * Should only be used on trusted paths. */ + + if (lstat(path, &st) < 0) + return -errno; + + do_chown = + (uid != UID_INVALID && st.st_uid != uid) || + (gid != GID_INVALID && st.st_gid != gid); + + do_chmod = + !S_ISLNK(st.st_mode) && /* chmod is not defined on symlinks */ + ((mode != MODE_INVALID && ((st.st_mode ^ mode) & 07777) != 0) || + do_chown); /* If we change ownership, make sure we reset the mode afterwards, since chown() + * modifies the access mode too */ + + if (mode == MODE_INVALID) + mode = st.st_mode; /* If we only shall do a chown(), save original mode, since chown() might break it. */ + else if ((mode & S_IFMT) != 0 && ((mode ^ st.st_mode) & S_IFMT) != 0) + return -EINVAL; /* insist on the right file type if it was specified */ + + if (do_chown && do_chmod) { + mode_t minimal = st.st_mode & mode; /* the subset of the old and the new mask */ + + if (((minimal ^ st.st_mode) & 07777) != 0) + if (chmod(path, minimal & 07777) < 0) + return -errno; + } + + if (do_chown) + if (lchown(path, uid, gid) < 0) + return -errno; + + if (do_chmod) + if (chmod(path, mode & 07777) < 0) + return -errno; + + return do_chown || do_chmod; +} int fchmod_umask(int fd, mode_t m) { mode_t u; @@ -289,7 +334,6 @@ int fchmod_umask(int fd, mode_t m) { return r; } -#if 0 /* NM_IGNORED */ int fchmod_opath(int fd, mode_t m) { char procfs_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)]; @@ -551,6 +595,7 @@ int get_files_in_directory(const char *path, char ***list) { return n; } +#endif /* NM_IGNORED */ static int getenv_tmp_dir(const char **ret_path) { const char *n; @@ -622,6 +667,7 @@ static int tmp_dir_internal(const char *def, const char **ret) { return 0; } +#if 0 /* NM_IGNORED */ int var_tmp_dir(const char **ret) { /* Returns the location for "larger" temporary files, that is backed by physical storage if available, and thus @@ -631,6 +677,7 @@ int var_tmp_dir(const char **ret) { return tmp_dir_internal("/var/tmp", ret); } +#endif /* NM_IGNORED */ int tmp_dir(const char **ret) { @@ -640,6 +687,7 @@ int tmp_dir(const char **ret) { return tmp_dir_internal("/tmp", ret); } +#if 0 /* NM_IGNORED */ int unlink_or_warn(const char *filename) { if (unlink(filename) < 0 && errno != ENOENT) /* If the file doesn't exist and the fs simply was read-only (in which @@ -806,6 +854,14 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, if (r < 0) return r; + /* Simplify the root directory, so that it has no duplicate slashes and nothing at the + * end. While we won't resolve the root path we still simplify it. Note that dropping the + * trailing slash should not change behaviour, since when opening it we specify O_DIRECTORY + * anyway. Moreover at the end of this function after processing everything we'll always turn + * the empty string back to "/". */ + delete_trailing_chars(root, "/"); + path_simplify(root, true); + if (flags & CHASE_PREFIX_ROOT) { /* We don't support relative paths in combination with a root directory */ if (!path_is_absolute(path)) @@ -819,7 +875,7 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, if (r < 0) return r; - fd = open("/", O_CLOEXEC|O_NOFOLLOW|O_PATH); + fd = open(root ?: "/", O_CLOEXEC|O_DIRECTORY|O_PATH); if (fd < 0) return -errno; @@ -828,6 +884,31 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, return -errno; } + if (root) { + _cleanup_free_ char *absolute = NULL; + const char *e; + + /* If we are operating on a root directory, let's take the root directory as it is. */ + + e = path_startswith(buffer, root); + if (!e) + return log_full_errno(flags & CHASE_WARN ? LOG_WARNING : LOG_DEBUG, + SYNTHETIC_ERRNO(ECHRNG), + "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.", + path, root); + + done = strdup(root); + if (!done) + return -ENOMEM; + + /* Make sure "todo" starts with a slash */ + absolute = strjoin("/", e); + if (!absolute) + return -ENOMEM; + + free_and_replace(buffer, absolute); + } + todo = buffer; for (;;) { _cleanup_free_ char *first = NULL; @@ -837,6 +918,15 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, /* Determine length of first component in the path */ n = strspn(todo, "/"); /* The slashes */ + + if (n > 1) { + /* If we are looking at more than a single slash then skip all but one, so that when + * we are done with everything we have a normalized path with only single slashes + * separating the path components. */ + todo += n - 1; + n = 1; + } + m = n + strcspn(todo + n, "/"); /* The entire length of the component */ /* Extract the first component. */ @@ -939,7 +1029,6 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, if (fstat(child, &st) < 0) return -errno; if ((flags & CHASE_SAFE) && - (empty_or_root(root) || (size_t)(todo - buffer) > strlen(root)) && unsafe_transition(&previous_stat, &st)) return log_unsafe_transition(fd, child, path, flags); @@ -970,7 +1059,7 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, * directory as base. */ safe_close(fd); - fd = open(root ?: "/", O_CLOEXEC|O_NOFOLLOW|O_PATH); + fd = open(root ?: "/", O_CLOEXEC|O_DIRECTORY|O_PATH); if (fd < 0) return -errno; diff --git a/shared/systemd/src/basic/fs-util.h b/shared/systemd/src/basic/fs-util.h index 78d68be9..6b9ade2e 100644 --- a/shared/systemd/src/basic/fs-util.h +++ b/shared/systemd/src/basic/fs-util.h @@ -34,6 +34,7 @@ int readlink_and_make_absolute(const char *p, char **r); int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid); int fchmod_and_chown(int fd, mode_t mode, uid_t uid, gid_t gid); +int chmod_and_chown_unsafe(const char *path, mode_t mode, uid_t uid, gid_t gid); int fchmod_umask(int fd, mode_t mode); int fchmod_opath(int fd, mode_t m); diff --git a/shared/systemd/src/basic/in-addr-util.c b/shared/systemd/src/basic/in-addr-util.c index 91d687c2..0e770621 100644 --- a/shared/systemd/src/basic/in-addr-util.c +++ b/shared/systemd/src/basic/in-addr-util.c @@ -445,54 +445,6 @@ int in_addr_from_string_auto(const char *s, int *ret_family, union in_addr_union return -EINVAL; } -#if 0 /* NM_IGNORED */ -int in_addr_ifindex_from_string_auto(const char *s, int *family, union in_addr_union *ret, int *ifindex) { - _cleanup_free_ char *buf = NULL; - const char *suffix; - int r, ifi = 0; - - assert(s); - assert(family); - assert(ret); - - /* Similar to in_addr_from_string_auto() but also parses an optionally appended IPv6 zone suffix ("scope id") - * if one is found. */ - - suffix = strchr(s, '%'); - if (suffix) { - - if (ifindex) { - /* If we shall return the interface index, try to parse it */ - r = parse_ifindex(suffix + 1, &ifi); - if (r < 0) { - unsigned u; - - u = if_nametoindex(suffix + 1); - if (u <= 0) - return -errno; - - ifi = (int) u; - } - } - - buf = strndup(s, suffix - s); - if (!buf) - return -ENOMEM; - - s = buf; - } - - r = in_addr_from_string_auto(s, family, ret); - if (r < 0) - return r; - - if (ifindex) - *ifindex = ifi; - - return r; -} -#endif /* NM_IGNORED */ - unsigned char in4_addr_netmask_to_prefixlen(const struct in_addr *addr) { assert(addr); diff --git a/shared/systemd/src/basic/in-addr-util.h b/shared/systemd/src/basic/in-addr-util.h index 28afc7d8..ae2dad0b 100644 --- a/shared/systemd/src/basic/in-addr-util.h +++ b/shared/systemd/src/basic/in-addr-util.h @@ -42,7 +42,7 @@ int in_addr_prefix_to_string(int family, const union in_addr_union *u, unsigned int in_addr_ifindex_to_string(int family, const union in_addr_union *u, int ifindex, char **ret); int in_addr_from_string(int family, const char *s, union in_addr_union *ret); int in_addr_from_string_auto(const char *s, int *ret_family, union in_addr_union *ret); -int in_addr_ifindex_from_string_auto(const char *s, int *family, union in_addr_union *ret, int *ifindex); + unsigned char in4_addr_netmask_to_prefixlen(const struct in_addr *addr); struct in_addr* in4_addr_prefixlen_to_netmask(struct in_addr *addr, unsigned char prefixlen); int in4_addr_default_prefixlen(const struct in_addr *addr, unsigned char *prefixlen); diff --git a/shared/systemd/src/basic/log.h b/shared/systemd/src/basic/log.h index b81c93a5..3725b08f 100644 --- a/shared/systemd/src/basic/log.h +++ b/shared/systemd/src/basic/log.h @@ -59,9 +59,12 @@ void log_show_color(bool b); bool log_get_show_color(void) _pure_; void log_show_location(bool b); bool log_get_show_location(void) _pure_; +void log_show_time(bool b); +bool log_get_show_time(void) _pure_; int log_show_color_from_string(const char *e); int log_show_location_from_string(const char *e); +int log_show_time_from_string(const char *e); LogTarget log_get_target(void) _pure_; #if 0 /* NM_IGNORED */ @@ -77,7 +80,7 @@ int log_get_max_level_realm(LogRealm realm) _pure_; #if 0 /* NM_IGNORED */ assert_cc(STRLEN(__FILE__) > STRLEN(RELATIVE_SOURCE_PATH) + 1); -#define PROJECT_FILE (__FILE__ + STRLEN(RELATIVE_SOURCE_PATH) + 1) +#define PROJECT_FILE (&__FILE__[STRLEN(RELATIVE_SOURCE_PATH) + 1]) #endif /* NM_IGNORED */ #define PROJECT_FILE __FILE__ diff --git a/shared/systemd/src/basic/macro.h b/shared/systemd/src/basic/macro.h index fc733366..6cd9c516 100644 --- a/shared/systemd/src/basic/macro.h +++ b/shared/systemd/src/basic/macro.h @@ -172,6 +172,11 @@ static inline size_t ALIGN_TO(size_t l, size_t ali) { /* align to next higher power-of-2 (except for: 0 => 0, overflow => 0) */ static inline unsigned long ALIGN_POWER2(unsigned long u) { + + /* Avoid subtraction overflow */ + if (u == 0) + return 0; + /* clz(0) is undefined */ if (u == 1) return 1; @@ -183,6 +188,29 @@ static inline unsigned long ALIGN_POWER2(unsigned long u) { return 1UL << (sizeof(u) * 8 - __builtin_clzl(u - 1UL)); } +static inline size_t GREEDY_ALLOC_ROUND_UP(size_t l) { + size_t m; + + /* Round up allocation sizes a bit to some reasonable, likely larger value. This is supposed to be + * used for cases which are likely called in an allocation loop of some form, i.e. that repetitively + * grow stuff, for example strv_extend() and suchlike. + * + * Note the difference to GREEDY_REALLOC() here, as this helper operates on a single size value only, + * and rounds up to next multiple of 2, needing no further counter. + * + * Note the benefits of direct ALIGN_POWER2() usage: type-safety for size_t, sane handling for very + * small (i.e. <= 2) and safe handling for very large (i.e. > SSIZE_MAX) values. */ + + if (l <= 2) + return 2; /* Never allocate less than 2 of something. */ + + m = ALIGN_POWER2(l); + if (m == 0) /* overflow? */ + return l; + + return m; +} + #ifndef __COVERITY__ # define VOID_0 ((void)0) #else diff --git a/shared/systemd/src/basic/memory-util.h b/shared/systemd/src/basic/memory-util.h index 46a6907a..b7e2e67e 100644 --- a/shared/systemd/src/basic/memory-util.h +++ b/shared/systemd/src/basic/memory-util.h @@ -7,6 +7,7 @@ #include <string.h> #include <sys/types.h> +#include "alloc-util.h" #include "macro.h" size_t page_size(void) _pure_; @@ -80,14 +81,19 @@ static inline void* explicit_bzero_safe(void *p, size_t l) { void *explicit_bzero_safe(void *p, size_t l); #endif -static inline void erase_and_freep(void *p) { - void *ptr = *(void**) p; +static inline void* erase_and_free(void *p) { + size_t l; + + if (!p) + return NULL; - if (ptr) { - size_t l = malloc_usable_size(ptr); - explicit_bzero_safe(ptr, l); - free(ptr); - } + l = malloc_usable_size(p); + explicit_bzero_safe(p, l); + return mfree(p); +} + +static inline void erase_and_freep(void *p) { + erase_and_free(*(void**) p); } /* Use with _cleanup_ to erase a single 'char' when leaving scope */ diff --git a/shared/systemd/src/basic/missing_syscall.h b/shared/systemd/src/basic/missing_syscall.h new file mode 100644 index 00000000..4c42e665 --- /dev/null +++ b/shared/systemd/src/basic/missing_syscall.h @@ -0,0 +1,574 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +/* Missing glibc definitions to access certain kernel APIs */ + +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#ifdef ARCH_MIPS +#include <asm/sgidefs.h> +#endif + +#include "missing_keyctl.h" +#include "missing_stat.h" + +#if 0 /* NM_IGNORED */ + +/* linux/kcmp.h */ +#ifndef KCMP_FILE /* 3f4994cfc15f38a3159c6e3a4b3ab2e1481a6b02 (3.19) */ +#define KCMP_FILE 0 +#endif + +#if !HAVE_PIVOT_ROOT +static inline int missing_pivot_root(const char *new_root, const char *put_old) { + return syscall(__NR_pivot_root, new_root, put_old); +} + +# define pivot_root missing_pivot_root +#endif + +/* ======================================================================= */ + +#if !HAVE_MEMFD_CREATE +/* may be (invalid) negative number due to libseccomp, see PR 13319 */ +# if ! (defined __NR_memfd_create && __NR_memfd_create >= 0) +# if defined __NR_memfd_create +# undef __NR_memfd_create +# endif +# if defined __x86_64__ +# define __NR_memfd_create 319 +# elif defined __arm__ +# define __NR_memfd_create 385 +# elif defined __aarch64__ +# define __NR_memfd_create 279 +# elif defined __s390__ +# define __NR_memfd_create 350 +# elif defined _MIPS_SIM +# if _MIPS_SIM == _MIPS_SIM_ABI32 +# define __NR_memfd_create 4354 +# endif +# if _MIPS_SIM == _MIPS_SIM_NABI32 +# define __NR_memfd_create 6318 +# endif +# if _MIPS_SIM == _MIPS_SIM_ABI64 +# define __NR_memfd_create 5314 +# endif +# elif defined __i386__ +# define __NR_memfd_create 356 +# elif defined __arc__ +# define __NR_memfd_create 279 +# else +# warning "__NR_memfd_create unknown for your architecture" +# endif +# endif + +static inline int missing_memfd_create(const char *name, unsigned int flags) { +# ifdef __NR_memfd_create + return syscall(__NR_memfd_create, name, flags); +# else + errno = ENOSYS; + return -1; +# endif +} + +# define memfd_create missing_memfd_create +#endif + +/* ======================================================================= */ + +#if !HAVE_GETRANDOM +/* may be (invalid) negative number due to libseccomp, see PR 13319 */ +# if ! (defined __NR_getrandom && __NR_getrandom >= 0) +# if defined __NR_getrandom +# undef __NR_getrandom +# endif +# if defined __x86_64__ +# define __NR_getrandom 318 +# elif defined(__i386__) +# define __NR_getrandom 355 +# elif defined(__arm__) +# define __NR_getrandom 384 +# elif defined(__aarch64__) +# define __NR_getrandom 278 +# elif defined(__ia64__) +# define __NR_getrandom 1339 +# elif defined(__m68k__) +# define __NR_getrandom 352 +# elif defined(__s390x__) +# define __NR_getrandom 349 +# elif defined(__powerpc__) +# define __NR_getrandom 359 +# elif defined _MIPS_SIM +# if _MIPS_SIM == _MIPS_SIM_ABI32 +# define __NR_getrandom 4353 +# endif +# if _MIPS_SIM == _MIPS_SIM_NABI32 +# define __NR_getrandom 6317 +# endif +# if _MIPS_SIM == _MIPS_SIM_ABI64 +# define __NR_getrandom 5313 +# endif +# elif defined(__arc__) +# define __NR_getrandom 278 +# else +# warning "__NR_getrandom unknown for your architecture" +# endif +# endif + +static inline int missing_getrandom(void *buffer, size_t count, unsigned flags) { +# ifdef __NR_getrandom + return syscall(__NR_getrandom, buffer, count, flags); +# else + errno = ENOSYS; + return -1; +# endif +} + +# define getrandom missing_getrandom +#endif + +/* ======================================================================= */ + +#if !HAVE_GETTID +static inline pid_t missing_gettid(void) { + return (pid_t) syscall(__NR_gettid); +} + +# define gettid missing_gettid +#endif + +/* ======================================================================= */ + +#if !HAVE_NAME_TO_HANDLE_AT +/* may be (invalid) negative number due to libseccomp, see PR 13319 */ +# if ! (defined __NR_name_to_handle_at && __NR_name_to_handle_at >= 0) +# if defined __NR_name_to_handle_at +# undef __NR_name_to_handle_at +# endif +# if defined(__x86_64__) +# define __NR_name_to_handle_at 303 +# elif defined(__i386__) +# define __NR_name_to_handle_at 341 +# elif defined(__arm__) +# define __NR_name_to_handle_at 370 +# elif defined(__powerpc__) +# define __NR_name_to_handle_at 345 +# elif defined(__arc__) +# define __NR_name_to_handle_at 264 +# else +# error "__NR_name_to_handle_at is not defined" +# endif +# endif + +struct file_handle { + unsigned int handle_bytes; + int handle_type; + unsigned char f_handle[0]; +}; + +static inline int missing_name_to_handle_at(int fd, const char *name, struct file_handle *handle, int *mnt_id, int flags) { +# ifdef __NR_name_to_handle_at + return syscall(__NR_name_to_handle_at, fd, name, handle, mnt_id, flags); +# else + errno = ENOSYS; + return -1; +# endif +} + +# define name_to_handle_at missing_name_to_handle_at +#endif + +/* ======================================================================= */ + +#if !HAVE_SETNS +/* may be (invalid) negative number due to libseccomp, see PR 13319 */ +# if ! (defined __NR_setns && __NR_setns >= 0) +# if defined __NR_setns +# undef __NR_setns +# endif +# if defined(__x86_64__) +# define __NR_setns 308 +# elif defined(__i386__) +# define __NR_setns 346 +# elif defined(__arc__) +# define __NR_setns 268 +# else +# error "__NR_setns is not defined" +# endif +# endif + +static inline int missing_setns(int fd, int nstype) { +# ifdef __NR_setns + return syscall(__NR_setns, fd, nstype); +# else + errno = ENOSYS; + return -1; +# endif +} + +# define setns missing_setns +#endif + +/* ======================================================================= */ + +static inline pid_t raw_getpid(void) { +#if defined(__alpha__) + return (pid_t) syscall(__NR_getxpid); +#else + return (pid_t) syscall(__NR_getpid); +#endif +} + +/* ======================================================================= */ + +#if !HAVE_RENAMEAT2 +/* may be (invalid) negative number due to libseccomp, see PR 13319 */ +# if ! (defined __NR_renameat2 && __NR_renameat2 >= 0) +# if defined __NR_renameat2 +# undef __NR_renameat2 +# endif +# if defined __x86_64__ +# define __NR_renameat2 316 +# elif defined __arm__ +# define __NR_renameat2 382 +# elif defined __aarch64__ +# define __NR_renameat2 276 +# elif defined _MIPS_SIM +# if _MIPS_SIM == _MIPS_SIM_ABI32 +# define __NR_renameat2 4351 +# endif +# if _MIPS_SIM == _MIPS_SIM_NABI32 +# define __NR_renameat2 6315 +# endif +# if _MIPS_SIM == _MIPS_SIM_ABI64 +# define __NR_renameat2 5311 +# endif +# elif defined __i386__ +# define __NR_renameat2 353 +# elif defined __powerpc64__ +# define __NR_renameat2 357 +# elif defined __s390__ || defined __s390x__ +# define __NR_renameat2 347 +# elif defined __arc__ +# define __NR_renameat2 276 +# else +# warning "__NR_renameat2 unknown for your architecture" +# endif +# endif + +static inline int missing_renameat2(int oldfd, const char *oldname, int newfd, const char *newname, unsigned flags) { +# ifdef __NR_renameat2 + return syscall(__NR_renameat2, oldfd, oldname, newfd, newname, flags); +# else + errno = ENOSYS; + return -1; +# endif +} + +# define renameat2 missing_renameat2 +#endif + +/* ======================================================================= */ + +#if !HAVE_KCMP +static inline int missing_kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1, unsigned long idx2) { +# if defined __NR_kcmp && __NR_kcmp >= 0 + return syscall(__NR_kcmp, pid1, pid2, type, idx1, idx2); +# else + errno = ENOSYS; + return -1; +# endif +} + +# define kcmp missing_kcmp +#endif + +/* ======================================================================= */ + +#if !HAVE_KEYCTL +static inline long missing_keyctl(int cmd, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5) { +# if defined __NR_keyctl && __NR_keyctl >= 0 + return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5); +# else + errno = ENOSYS; + return -1; +# endif + +# define keyctl missing_keyctl +} + +static inline key_serial_t missing_add_key(const char *type, const char *description, const void *payload, size_t plen, key_serial_t ringid) { +# if defined __NR_add_key && __NR_add_key >= 0 + return syscall(__NR_add_key, type, description, payload, plen, ringid); +# else + errno = ENOSYS; + return -1; +# endif + +# define add_key missing_add_key +} + +static inline key_serial_t missing_request_key(const char *type, const char *description, const char * callout_info, key_serial_t destringid) { +# if defined __NR_request_key && __NR_request_key >= 0 + return syscall(__NR_request_key, type, description, callout_info, destringid); +# else + errno = ENOSYS; + return -1; +# endif + +# define request_key missing_request_key +} +#endif + +/* ======================================================================= */ + +#if !HAVE_COPY_FILE_RANGE +/* may be (invalid) negative number due to libseccomp, see PR 13319 */ +# if ! (defined __NR_copy_file_range && __NR_copy_file_range >= 0) +# if defined __NR_copy_file_range +# undef __NR_copy_file_range +# endif +# if defined(__x86_64__) +# define __NR_copy_file_range 326 +# elif defined(__i386__) +# define __NR_copy_file_range 377 +# elif defined __s390__ +# define __NR_copy_file_range 375 +# elif defined __arm__ +# define __NR_copy_file_range 391 +# elif defined __aarch64__ +# define __NR_copy_file_range 285 +# elif defined __powerpc__ +# define __NR_copy_file_range 379 +# elif defined __arc__ +# define __NR_copy_file_range 285 +# else +# warning "__NR_copy_file_range not defined for your architecture" +# endif +# endif + +static inline ssize_t missing_copy_file_range(int fd_in, loff_t *off_in, + int fd_out, loff_t *off_out, + size_t len, + unsigned int flags) { +# ifdef __NR_copy_file_range + return syscall(__NR_copy_file_range, fd_in, off_in, fd_out, off_out, len, flags); +# else + errno = ENOSYS; + return -1; +# endif +} + +# define copy_file_range missing_copy_file_range +#endif + +/* ======================================================================= */ + +#if !HAVE_BPF +/* may be (invalid) negative number due to libseccomp, see PR 13319 */ +# if ! (defined __NR_bpf && __NR_bpf >= 0) +# if defined __NR_bpf +# undef __NR_bpf +# endif +# if defined __i386__ +# define __NR_bpf 357 +# elif defined __x86_64__ +# define __NR_bpf 321 +# elif defined __aarch64__ +# define __NR_bpf 280 +# elif defined __arm__ +# define __NR_bpf 386 +# elif defined __sparc__ +# define __NR_bpf 349 +# elif defined __s390__ +# define __NR_bpf 351 +# elif defined __tilegx__ +# define __NR_bpf 280 +# else +# warning "__NR_bpf not defined for your architecture" +# endif +# endif + +union bpf_attr; + +static inline int missing_bpf(int cmd, union bpf_attr *attr, size_t size) { +#ifdef __NR_bpf + return (int) syscall(__NR_bpf, cmd, attr, size); +#else + errno = ENOSYS; + return -1; +#endif +} + +# define bpf missing_bpf +#endif + +/* ======================================================================= */ + +#ifndef __IGNORE_pkey_mprotect +/* may be (invalid) negative number due to libseccomp, see PR 13319 */ +# if ! (defined __NR_pkey_mprotect && __NR_pkey_mprotect >= 0) +# if defined __NR_pkey_mprotect +# undef __NR_pkey_mprotect +# endif +# if defined __i386__ +# define __NR_pkey_mprotect 380 +# elif defined __x86_64__ +# define __NR_pkey_mprotect 329 +# elif defined __arm__ +# define __NR_pkey_mprotect 394 +# elif defined __aarch64__ +# define __NR_pkey_mprotect 394 +# elif defined __powerpc__ +# define __NR_pkey_mprotect 386 +# elif defined __s390__ +# define __NR_pkey_mprotect 384 +# elif defined _MIPS_SIM +# if _MIPS_SIM == _MIPS_SIM_ABI32 +# define __NR_pkey_mprotect 4363 +# endif +# if _MIPS_SIM == _MIPS_SIM_NABI32 +# define __NR_pkey_mprotect 6327 +# endif +# if _MIPS_SIM == _MIPS_SIM_ABI64 +# define __NR_pkey_mprotect 5323 +# endif +# else +# warning "__NR_pkey_mprotect not defined for your architecture" +# endif +# endif +#endif + +/* ======================================================================= */ + +#if !HAVE_STATX +/* may be (invalid) negative number due to libseccomp, see PR 13319 */ +# if ! (defined __NR_statx && __NR_statx >= 0) +# if defined __NR_statx +# undef __NR_statx +# endif +# if defined __aarch64__ || defined __arm__ +# define __NR_statx 397 +# elif defined __alpha__ +# define __NR_statx 522 +# elif defined __i386__ || defined __powerpc64__ +# define __NR_statx 383 +# elif defined __sparc__ +# define __NR_statx 360 +# elif defined __x86_64__ +# define __NR_statx 332 +# else +# warning "__NR_statx not defined for your architecture" +# endif +# endif + +struct statx; +#endif + +/* This typedef is supposed to be always defined. */ +typedef struct statx struct_statx; + +#if !HAVE_STATX +static inline ssize_t missing_statx(int dfd, const char *filename, unsigned flags, unsigned int mask, struct statx *buffer) { +# ifdef __NR_statx + return syscall(__NR_statx, dfd, filename, flags, mask, buffer); +# else + errno = ENOSYS; + return -1; +# endif +} + +# define statx missing_statx +#endif + +#if !HAVE_SET_MEMPOLICY + +enum { + MPOL_DEFAULT, + MPOL_PREFERRED, + MPOL_BIND, + MPOL_INTERLEAVE, + MPOL_LOCAL, +}; + +static inline long missing_set_mempolicy(int mode, const unsigned long *nodemask, + unsigned long maxnode) { + long i; +# if defined __NR_set_mempolicy && __NR_set_mempolicy >= 0 + i = syscall(__NR_set_mempolicy, mode, nodemask, maxnode); +# else + errno = ENOSYS; + i = -1; +# endif + return i; +} + +# define set_mempolicy missing_set_mempolicy +#endif + +#if !HAVE_GET_MEMPOLICY +static inline long missing_get_mempolicy(int *mode, unsigned long *nodemask, + unsigned long maxnode, void *addr, + unsigned long flags) { + long i; +# ifdef __NR_get_mempolicy + i = syscall(__NR_get_mempolicy, mode, nodemask, maxnode, addr, flags); +# else + errno = ENOSYS; + i = -1; +# endif + return i; +} + +#define get_mempolicy missing_get_mempolicy +#endif + +#endif /* NM_IGNORED */ + +#if !HAVE_PIDFD_OPEN +/* may be (invalid) negative number due to libseccomp, see PR 13319 */ +# if ! (defined __NR_pidfd_open && __NR_pidfd_open >= 0) +# if defined __NR_pidfd_open +# undef __NR_pidfd_open +# endif +# define __NR_pidfd_open 434 +#endif +static inline int pidfd_open(pid_t pid, unsigned flags) { +#ifdef __NR_pidfd_open + return syscall(__NR_pidfd_open, pid, flags); +#else + errno = ENOSYS; + return -1; +#endif +} +#endif + +#if !HAVE_PIDFD_SEND_SIGNAL +/* may be (invalid) negative number due to libseccomp, see PR 13319 */ +# if ! (defined __NR_pidfd_send_signal && __NR_pidfd_send_signal >= 0) +# if defined __NR_pidfd_send_signal +# undef __NR_pidfd_send_signal +# endif +# define __NR_pidfd_send_signal 424 +#endif +static inline int pidfd_send_signal(int fd, int sig, siginfo_t *info, unsigned flags) { +#ifdef __NR_pidfd_open + return syscall(__NR_pidfd_send_signal, fd, sig, info, flags); +#else + errno = ENOSYS; + return -1; +#endif +} +#endif + +#if !HAVE_RT_SIGQUEUEINFO +static inline int rt_sigqueueinfo(pid_t tgid, int sig, siginfo_t *info) { + return syscall(__NR_rt_sigqueueinfo, tgid, sig, info); +} +#endif diff --git a/shared/systemd/src/basic/parse-util.c b/shared/systemd/src/basic/parse-util.c index 96cc43a2..475a06cc 100644 --- a/shared/systemd/src/basic/parse-util.c +++ b/shared/systemd/src/basic/parse-util.c @@ -82,11 +82,10 @@ int parse_mode(const char *s, mode_t *ret) { return 0; } -int parse_ifindex(const char *s, int *ret) { +int parse_ifindex(const char *s) { int ifi, r; assert(s); - assert(ret); r = safe_atoi(s, &ifi); if (r < 0) @@ -94,26 +93,7 @@ int parse_ifindex(const char *s, int *ret) { if (ifi <= 0) return -EINVAL; - *ret = ifi; - return 0; -} - -int parse_ifindex_or_ifname(const char *s, int *ret) { - int r; - - assert(s); - assert(ret); - - r = parse_ifindex(s, ret); - if (r >= 0) - return r; - - r = (int) if_nametoindex(s); - if (r <= 0) - return -errno; - - *ret = r; - return 0; + return ifi; } int parse_mtu(int family, const char *s, uint32_t *ret) { @@ -369,7 +349,6 @@ int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) { unsigned long l; assert(s); - assert(ret_u); assert(base <= 16); /* strtoul() is happy to parse negative values, and silently @@ -393,7 +372,9 @@ int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) { if ((unsigned long) (unsigned) l != l) return -ERANGE; - *ret_u = (unsigned) l; + if (ret_u) + *ret_u = (unsigned) l; + return 0; } @@ -402,7 +383,6 @@ int safe_atoi(const char *s, int *ret_i) { long l; assert(s); - assert(ret_i); errno = 0; l = strtol(s, &x, 0); @@ -413,7 +393,9 @@ int safe_atoi(const char *s, int *ret_i) { if ((long) (int) l != l) return -ERANGE; - *ret_i = (int) l; + if (ret_i) + *ret_i = (int) l; + return 0; } @@ -422,7 +404,6 @@ int safe_atollu(const char *s, long long unsigned *ret_llu) { unsigned long long l; assert(s); - assert(ret_llu); s += strspn(s, WHITESPACE); @@ -435,7 +416,9 @@ int safe_atollu(const char *s, long long unsigned *ret_llu) { if (*s == '-') return -ERANGE; - *ret_llu = l; + if (ret_llu) + *ret_llu = l; + return 0; } @@ -444,7 +427,6 @@ int safe_atolli(const char *s, long long int *ret_lli) { long long l; assert(s); - assert(ret_lli); errno = 0; l = strtoll(s, &x, 0); @@ -453,7 +435,9 @@ int safe_atolli(const char *s, long long int *ret_lli) { if (!x || x == s || *x != 0) return -EINVAL; - *ret_lli = l; + if (ret_lli) + *ret_lli = l; + return 0; } @@ -462,7 +446,6 @@ int safe_atou8(const char *s, uint8_t *ret) { unsigned long l; assert(s); - assert(ret); s += strspn(s, WHITESPACE); @@ -477,7 +460,8 @@ int safe_atou8(const char *s, uint8_t *ret) { if ((unsigned long) (uint8_t) l != l) return -ERANGE; - *ret = (uint8_t) l; + if (ret) + *ret = (uint8_t) l; return 0; } @@ -511,7 +495,6 @@ int safe_atoi16(const char *s, int16_t *ret) { long l; assert(s); - assert(ret); errno = 0; l = strtol(s, &x, 0); @@ -522,7 +505,9 @@ int safe_atoi16(const char *s, int16_t *ret) { if ((long) (int16_t) l != l) return -ERANGE; - *ret = (int16_t) l; + if (ret) + *ret = (int16_t) l; + return 0; } @@ -533,7 +518,6 @@ int safe_atod(const char *s, double *ret_d) { double d = 0; assert(s); - assert(ret_d); loc = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0); if (loc == (locale_t) 0) @@ -546,7 +530,9 @@ int safe_atod(const char *s, double *ret_d) { if (!x || x == s || *x != 0) return -EINVAL; - *ret_d = (double) d; + if (ret_d) + *ret_d = (double) d; + return 0; } @@ -717,6 +703,22 @@ int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high) { return 0; } +int parse_ip_prefix_length(const char *s, int *ret) { + unsigned l; + int r; + + r = safe_atou(s, &l); + if (r < 0) + return r; + + if (l > 128) + return -ERANGE; + + *ret = (int) l; + + return 0; +} + int parse_dev(const char *s, dev_t *ret) { const char *major; unsigned x, y; diff --git a/shared/systemd/src/basic/parse-util.h b/shared/systemd/src/basic/parse-util.h index 3a70b792..36d76ba5 100644 --- a/shared/systemd/src/basic/parse-util.h +++ b/shared/systemd/src/basic/parse-util.h @@ -13,8 +13,7 @@ int parse_boolean(const char *v) _pure_; int parse_dev(const char *s, dev_t *ret); int parse_pid(const char *s, pid_t* ret_pid); int parse_mode(const char *s, mode_t *ret); -int parse_ifindex(const char *s, int *ret); -int parse_ifindex_or_ifname(const char *s, int *ret); +int parse_ifindex(const char *s); int parse_mtu(int family, const char *s, uint32_t *ret); int parse_size(const char *t, uint64_t base, uint64_t *size); @@ -46,9 +45,13 @@ static inline int safe_atoux16(const char *s, uint16_t *ret) { int safe_atoi16(const char *s, int16_t *ret); -static inline int safe_atou32(const char *s, uint32_t *ret_u) { +static inline int safe_atou32_full(const char *s, unsigned base, uint32_t *ret_u) { assert_cc(sizeof(uint32_t) == sizeof(unsigned)); - return safe_atou(s, (unsigned*) ret_u); + return safe_atou_full(s, base, (unsigned*) ret_u); +} + +static inline int safe_atou32(const char *s, uint32_t *ret_u) { + return safe_atou32_full(s, 0, (unsigned*) ret_u); } static inline int safe_atoi32(const char *s, int32_t *ret_i) { @@ -113,4 +116,6 @@ int parse_nice(const char *p, int *ret); int parse_ip_port(const char *s, uint16_t *ret); int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high); +int parse_ip_prefix_length(const char *s, int *ret); + int parse_oom_score_adjust(const char *s, int *ret); diff --git a/shared/systemd/src/basic/path-util.c b/shared/systemd/src/basic/path-util.c index 5bcc35e5..7baab4be 100644 --- a/shared/systemd/src/basic/path-util.c +++ b/shared/systemd/src/basic/path-util.c @@ -540,6 +540,7 @@ bool path_equal(const char *a, const char *b) { bool path_equal_or_files_same(const char *a, const char *b, int flags) { return path_equal(a, b) || files_same(a, b, flags) > 0; } +#endif /* NM_IGNORED */ char* path_join_internal(const char *first, ...) { char *joined, *q; @@ -599,6 +600,7 @@ char* path_join_internal(const char *first, ...) { return joined; } +#if 0 /* NM_IGNORED */ int find_binary(const char *name, char **ret) { int last_error, r; const char *p; @@ -1126,4 +1128,30 @@ bool empty_or_root(const char *root) { return root[strspn(root, "/")] == 0; } + +bool path_strv_contains(char **l, const char *path) { + char **i; + + STRV_FOREACH(i, l) + if (path_equal(*i, path)) + return true; + + return false; +} + +bool prefixed_path_strv_contains(char **l, const char *path) { + char **i, *j; + + STRV_FOREACH(i, l) { + j = *i; + if (*j == '-') + j++; + if (*j == '+') + j++; + if (path_equal(j, path)) + return true; + } + + return false; +} #endif /* NM_IGNORED */ diff --git a/shared/systemd/src/basic/path-util.h b/shared/systemd/src/basic/path-util.h index 88aef2f3..aba8ad00 100644 --- a/shared/systemd/src/basic/path-util.h +++ b/shared/systemd/src/basic/path-util.h @@ -73,17 +73,7 @@ static inline bool path_equal_ptr(const char *a, const char *b) { } /* Note: the search terminates on the first NULL item. */ -#define PATH_IN_SET(p, ...) \ - ({ \ - char **_s; \ - bool _found = false; \ - STRV_FOREACH(_s, STRV_MAKE(__VA_ARGS__)) \ - if (path_equal(p, *_s)) { \ - _found = true; \ - break; \ - } \ - _found; \ - }) +#define PATH_IN_SET(p, ...) path_strv_contains(STRV_MAKE(__VA_ARGS__), p) char* path_startswith_strv(const char *p, char **set); #define PATH_STARTSWITH_SET(p, ...) path_startswith_strv(p, STRV_MAKE(__VA_ARGS__)) @@ -183,3 +173,6 @@ bool empty_or_root(const char *root); static inline const char *empty_to_root(const char *path) { return isempty(path) ? "/" : path; } + +bool path_strv_contains(char **l, const char *path); +bool prefixed_path_strv_contains(char **l, const char *path); diff --git a/shared/systemd/src/basic/process-util.c b/shared/systemd/src/basic/process-util.c index 14561670..2de3541c 100644 --- a/shared/systemd/src/basic/process-util.c +++ b/shared/systemd/src/basic/process-util.c @@ -26,6 +26,7 @@ #include "alloc-util.h" #include "architecture.h" #include "env-util.h" +#include "errno-util.h" #include "escape.h" #include "fd-util.h" #include "fileio.h" @@ -44,6 +45,7 @@ #include "rlimit-util.h" #include "signal-util.h" #include "stat-util.h" +#include "stdio-util.h" #include "string-table.h" #include "string-util.h" #include "terminal-util.h" @@ -1186,6 +1188,11 @@ int must_be_root(void) { return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be root."); } +static void restore_sigsetp(sigset_t **ssp) { + if (*ssp) + (void) sigprocmask(SIG_SETMASK, *ssp, NULL); +} + int safe_fork_full( const char *name, const int except_fds[], @@ -1195,7 +1202,8 @@ int safe_fork_full( pid_t original_pid, pid; sigset_t saved_ss, ss; - bool block_signals = false; + _cleanup_(restore_sigsetp) sigset_t *saved_ssp = NULL; + bool block_signals = false, block_all = false; int prio, r; /* A wrapper around fork(), that does a couple of important initializations in addition to mere forking. Always @@ -1210,7 +1218,7 @@ int safe_fork_full( * be sure that SIGTERMs are not lost we might send to the child. */ assert_se(sigfillset(&ss) >= 0); - block_signals = true; + block_signals = block_all = true; } else if (flags & FORK_WAIT) { /* Let's block SIGCHLD at least, so that we can safely watch for the child process */ @@ -1220,28 +1228,31 @@ int safe_fork_full( block_signals = true; } - if (block_signals) + if (block_signals) { if (sigprocmask(SIG_SETMASK, &ss, &saved_ss) < 0) return log_full_errno(prio, errno, "Failed to set signal mask: %m"); + saved_ssp = &saved_ss; + } if (flags & FORK_NEW_MOUNTNS) pid = raw_clone(SIGCHLD|CLONE_NEWNS); else pid = fork(); - if (pid < 0) { - r = -errno; - - if (block_signals) /* undo what we did above */ - (void) sigprocmask(SIG_SETMASK, &saved_ss, NULL); - - return log_full_errno(prio, r, "Failed to fork: %m"); - } + if (pid < 0) + return log_full_errno(prio, errno, "Failed to fork: %m"); if (pid > 0) { /* We are in the parent process */ log_debug("Successfully forked off '%s' as PID " PID_FMT ".", strna(name), pid); if (flags & FORK_WAIT) { + if (block_all) { + /* undo everything except SIGCHLD */ + ss = saved_ss; + assert_se(sigaddset(&ss, SIGCHLD) >= 0); + (void) sigprocmask(SIG_SETMASK, &ss, NULL); + } + r = wait_for_terminate_and_check(name, pid, (flags & FORK_LOG ? WAIT_LOG : 0)); if (r < 0) return r; @@ -1249,9 +1260,6 @@ int safe_fork_full( return -EPROTO; } - if (block_signals) /* undo what we did above */ - (void) sigprocmask(SIG_SETMASK, &saved_ss, NULL); - if (ret_pid) *ret_pid = pid; @@ -1260,6 +1268,9 @@ int safe_fork_full( /* We are in the child process */ + /* Restore signal mask manually */ + saved_ssp = NULL; + if (flags & FORK_REOPEN_LOG) { /* Close the logs if requested, before we log anything. And make sure we reopen it if needed. */ log_close(); @@ -1347,6 +1358,12 @@ int safe_fork_full( log_full_errno(prio, r, "Failed to connect stdin/stdout to /dev/null: %m"); _exit(EXIT_FAILURE); } + + } else if (flags & FORK_STDOUT_TO_STDERR) { + if (dup2(STDERR_FILENO, STDOUT_FILENO) < 0) { + log_full_errno(prio, errno, "Failed to connect stdout to stderr: %m"); + _exit(EXIT_FAILURE); + } } if (flags & FORK_RLIMIT_NOFILE_SAFE) { @@ -1498,6 +1515,94 @@ int set_oom_score_adjust(int value) { WRITE_STRING_FILE_VERIFY_ON_FAILURE|WRITE_STRING_FILE_DISABLE_BUFFER); } +int pidfd_get_pid(int fd, pid_t *ret) { + char path[STRLEN("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)]; + _cleanup_free_ char *fdinfo = NULL; + char *p; + int r; + + if (fd < 0) + return -EBADF; + + xsprintf(path, "/proc/self/fdinfo/%i", fd); + + r = read_full_file(path, &fdinfo, NULL); + if (r == -ENOENT) /* if fdinfo doesn't exist we assume the process does not exist */ + return -ESRCH; + if (r < 0) + return r; + + p = startswith(fdinfo, "Pid:"); + if (!p) { + p = strstr(fdinfo, "\nPid:"); + if (!p) + return -ENOTTY; /* not a pidfd? */ + + p += 5; + } + + p += strspn(p, WHITESPACE); + p[strcspn(p, WHITESPACE)] = 0; + + return parse_pid(p, ret); +} + +static int rlimit_to_nice(rlim_t limit) { + if (limit <= 1) + return PRIO_MAX-1; /* i.e. 19 */ + + if (limit >= -PRIO_MIN + PRIO_MAX) + return PRIO_MIN; /* i.e. -20 */ + + return PRIO_MAX - (int) limit; +} + +int setpriority_closest(int priority) { + int current, limit, saved_errno; + struct rlimit highest; + + /* Try to set requested nice level */ + if (setpriority(PRIO_PROCESS, 0, priority) >= 0) + return 1; + + /* Permission failed */ + saved_errno = -errno; + if (!ERRNO_IS_PRIVILEGE(saved_errno)) + return saved_errno; + + errno = 0; + current = getpriority(PRIO_PROCESS, 0); + if (errno != 0) + return -errno; + + if (priority == current) + return 1; + + /* Hmm, we'd expect that raising the nice level from our status quo would always work. If it doesn't, + * then the whole setpriority() system call is blocked to us, hence let's propagate the error + * right-away */ + if (priority > current) + return saved_errno; + + if (getrlimit(RLIMIT_NICE, &highest) < 0) + return -errno; + + limit = rlimit_to_nice(highest.rlim_cur); + + /* We are already less nice than limit allows us */ + if (current < limit) { + log_debug("Cannot raise nice level, permissions and the resource limit do not allow it."); + return 0; + } + + /* Push to the allowed limit */ + if (setpriority(PRIO_PROCESS, 0, limit) < 0) + return -errno; + + log_debug("Cannot set requested nice level (%i), used next best (%i).", priority, limit); + return 0; +} + static const char *const ioprio_class_table[] = { [IOPRIO_CLASS_NONE] = "none", [IOPRIO_CLASS_RT] = "realtime", diff --git a/shared/systemd/src/basic/process-util.h b/shared/systemd/src/basic/process-util.h index 66853c6e..7b70c9f3 100644 --- a/shared/systemd/src/basic/process-util.h +++ b/shared/systemd/src/basic/process-util.h @@ -149,16 +149,17 @@ void reset_cached_pid(void); int must_be_root(void); typedef enum ForkFlags { - FORK_RESET_SIGNALS = 1 << 0, /* Reset all signal handlers and signal mask */ - FORK_CLOSE_ALL_FDS = 1 << 1, /* Close all open file descriptors in the child, except for 0,1,2 */ - FORK_DEATHSIG = 1 << 2, /* Set PR_DEATHSIG in the child */ - FORK_NULL_STDIO = 1 << 3, /* Connect 0,1,2 to /dev/null */ - FORK_REOPEN_LOG = 1 << 4, /* Reopen log connection */ - FORK_LOG = 1 << 5, /* Log above LOG_DEBUG log level about failures */ - FORK_WAIT = 1 << 6, /* Wait until child exited */ - FORK_NEW_MOUNTNS = 1 << 7, /* Run child in its own mount namespace */ - FORK_MOUNTNS_SLAVE = 1 << 8, /* Make child's mount namespace MS_SLAVE */ - FORK_RLIMIT_NOFILE_SAFE = 1 << 9, /* Set RLIMIT_NOFILE soft limit to 1K for select() compat */ + FORK_RESET_SIGNALS = 1 << 0, /* Reset all signal handlers and signal mask */ + FORK_CLOSE_ALL_FDS = 1 << 1, /* Close all open file descriptors in the child, except for 0,1,2 */ + FORK_DEATHSIG = 1 << 2, /* Set PR_DEATHSIG in the child */ + FORK_NULL_STDIO = 1 << 3, /* Connect 0,1,2 to /dev/null */ + FORK_REOPEN_LOG = 1 << 4, /* Reopen log connection */ + FORK_LOG = 1 << 5, /* Log above LOG_DEBUG log level about failures */ + FORK_WAIT = 1 << 6, /* Wait until child exited */ + FORK_NEW_MOUNTNS = 1 << 7, /* Run child in its own mount namespace */ + FORK_MOUNTNS_SLAVE = 1 << 8, /* Make child's mount namespace MS_SLAVE */ + FORK_RLIMIT_NOFILE_SAFE = 1 << 9, /* Set RLIMIT_NOFILE soft limit to 1K for select() compat */ + FORK_STDOUT_TO_STDERR = 1 << 10, /* Make stdout a copy of stderr */ } ForkFlags; int safe_fork_full(const char *name, const int except_fds[], size_t n_except_fds, ForkFlags flags, pid_t *ret_pid); @@ -173,7 +174,6 @@ int fork_agent(const char *name, const int except[], size_t n_except, pid_t *pid int set_oom_score_adjust(int value); -#if SIZEOF_PID_T == 4 /* The highest possibly (theoretic) pid_t value on this architecture. */ #define PID_T_MAX ((pid_t) INT32_MAX) /* The maximum number of concurrent processes Linux allows on this architecture, as well as the highest valid PID value @@ -183,12 +183,6 @@ int set_oom_score_adjust(int value); * these values are documented in proc(5) we feel quite confident that they are stable enough for the near future at * least to define them here too. */ #define TASKS_MAX 4194303U -#elif SIZEOF_PID_T == 2 -#define PID_T_MAX ((pid_t) INT16_MAX) -#define TASKS_MAX 32767U -#else -#error "Unknown pid_t size" -#endif assert_cc(TASKS_MAX <= (unsigned long) PID_T_MAX); @@ -199,3 +193,7 @@ assert_cc(TASKS_MAX <= (unsigned long) PID_T_MAX); (pid) = 0; \ _pid_; \ }) + +int pidfd_get_pid(int fd, pid_t *ret); + +int setpriority_closest(int priority); diff --git a/shared/systemd/src/basic/random-util.c b/shared/systemd/src/basic/random-util.c index 86917ca3..1a294944 100644 --- a/shared/systemd/src/basic/random-util.c +++ b/shared/systemd/src/basic/random-util.c @@ -9,6 +9,7 @@ #include <elf.h> #include <errno.h> #include <fcntl.h> +#include <pthread.h> #include <stdbool.h> #include <stdint.h> #include <stdlib.h> @@ -30,6 +31,8 @@ #include "siphash24.h" #include "time-util.h" +static bool srand_called = false; + int rdrand(unsigned long *ret) { /* So, you are a "security researcher", and you wonder why we bother with using raw RDRAND here, @@ -281,8 +284,12 @@ int genuine_random_bytes(void *p, size_t n, RandomFlags flags) { return loop_read_exact(fd, p, n, true); } +static void clear_srand_initialization(void) { + srand_called = false; +} + void initialize_srand(void) { - static bool srand_called = false; + static bool pthread_atfork_registered = false; unsigned x; #if HAVE_SYS_AUXV_H const void *auxv; @@ -318,6 +325,11 @@ void initialize_srand(void) { srand(x); srand_called = true; + + if (!pthread_atfork_registered) { + (void) pthread_atfork(NULL, NULL, clear_srand_initialization); + pthread_atfork_registered = true; + } } /* INT_MAX gives us only 31 bits, so use 24 out of that. */ diff --git a/shared/systemd/src/basic/signal-util.c b/shared/systemd/src/basic/signal-util.c new file mode 100644 index 00000000..a4b8163c --- /dev/null +++ b/shared/systemd/src/basic/signal-util.c @@ -0,0 +1,308 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "nm-sd-adapt-shared.h" + +#include <errno.h> +#include <stdarg.h> + +#include "macro.h" +#include "parse-util.h" +#include "signal-util.h" +#include "stdio-util.h" +#include "string-table.h" +#include "string-util.h" + +#if 0 /* NM_IGNORED */ +int reset_all_signal_handlers(void) { + static const struct sigaction sa = { + .sa_handler = SIG_DFL, + .sa_flags = SA_RESTART, + }; + int sig, r = 0; + + for (sig = 1; sig < _NSIG; sig++) { + + /* These two cannot be caught... */ + if (IN_SET(sig, SIGKILL, SIGSTOP)) + continue; + + /* On Linux the first two RT signals are reserved by + * glibc, and sigaction() will return EINVAL for them. */ + if (sigaction(sig, &sa, NULL) < 0) + if (errno != EINVAL && r >= 0) + r = -errno; + } + + return r; +} + +int reset_signal_mask(void) { + sigset_t ss; + + if (sigemptyset(&ss) < 0) + return -errno; + + if (sigprocmask(SIG_SETMASK, &ss, NULL) < 0) + return -errno; + + return 0; +} + +static int sigaction_many_ap(const struct sigaction *sa, int sig, va_list ap) { + int r = 0; + + /* negative signal ends the list. 0 signal is skipped. */ + + if (sig < 0) + return 0; + + if (sig > 0) { + if (sigaction(sig, sa, NULL) < 0) + r = -errno; + } + + while ((sig = va_arg(ap, int)) >= 0) { + + if (sig == 0) + continue; + + if (sigaction(sig, sa, NULL) < 0) { + if (r >= 0) + r = -errno; + } + } + + return r; +} + +int sigaction_many(const struct sigaction *sa, ...) { + va_list ap; + int r; + + va_start(ap, sa); + r = sigaction_many_ap(sa, 0, ap); + va_end(ap); + + return r; +} + +int ignore_signals(int sig, ...) { + + static const struct sigaction sa = { + .sa_handler = SIG_IGN, + .sa_flags = SA_RESTART, + }; + + va_list ap; + int r; + + va_start(ap, sig); + r = sigaction_many_ap(&sa, sig, ap); + va_end(ap); + + return r; +} + +int default_signals(int sig, ...) { + + static const struct sigaction sa = { + .sa_handler = SIG_DFL, + .sa_flags = SA_RESTART, + }; + + va_list ap; + int r; + + va_start(ap, sig); + r = sigaction_many_ap(&sa, sig, ap); + va_end(ap); + + return r; +} + +static int sigset_add_many_ap(sigset_t *ss, va_list ap) { + int sig, r = 0; + + assert(ss); + + while ((sig = va_arg(ap, int)) >= 0) { + + if (sig == 0) + continue; + + if (sigaddset(ss, sig) < 0) { + if (r >= 0) + r = -errno; + } + } + + return r; +} + +int sigset_add_many(sigset_t *ss, ...) { + va_list ap; + int r; + + va_start(ap, ss); + r = sigset_add_many_ap(ss, ap); + va_end(ap); + + return r; +} + +int sigprocmask_many(int how, sigset_t *old, ...) { + va_list ap; + sigset_t ss; + int r; + + if (sigemptyset(&ss) < 0) + return -errno; + + va_start(ap, old); + r = sigset_add_many_ap(&ss, ap); + va_end(ap); + + if (r < 0) + return r; + + if (sigprocmask(how, &ss, old) < 0) + return -errno; + + return 0; +} + +static const char *const __signal_table[] = { + [SIGHUP] = "HUP", + [SIGINT] = "INT", + [SIGQUIT] = "QUIT", + [SIGILL] = "ILL", + [SIGTRAP] = "TRAP", + [SIGABRT] = "ABRT", + [SIGBUS] = "BUS", + [SIGFPE] = "FPE", + [SIGKILL] = "KILL", + [SIGUSR1] = "USR1", + [SIGSEGV] = "SEGV", + [SIGUSR2] = "USR2", + [SIGPIPE] = "PIPE", + [SIGALRM] = "ALRM", + [SIGTERM] = "TERM", +#ifdef SIGSTKFLT + [SIGSTKFLT] = "STKFLT", /* Linux on SPARC doesn't know SIGSTKFLT */ +#endif + [SIGCHLD] = "CHLD", + [SIGCONT] = "CONT", + [SIGSTOP] = "STOP", + [SIGTSTP] = "TSTP", + [SIGTTIN] = "TTIN", + [SIGTTOU] = "TTOU", + [SIGURG] = "URG", + [SIGXCPU] = "XCPU", + [SIGXFSZ] = "XFSZ", + [SIGVTALRM] = "VTALRM", + [SIGPROF] = "PROF", + [SIGWINCH] = "WINCH", + [SIGIO] = "IO", + [SIGPWR] = "PWR", + [SIGSYS] = "SYS" +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(__signal, int); + +const char *signal_to_string(int signo) { + static thread_local char buf[STRLEN("RTMIN+") + DECIMAL_STR_MAX(int) + 1]; + const char *name; + + name = __signal_to_string(signo); + if (name) + return name; + + if (signo >= SIGRTMIN && signo <= SIGRTMAX) + xsprintf(buf, "RTMIN+%d", signo - SIGRTMIN); + else + xsprintf(buf, "%d", signo); + + return buf; +} + +int signal_from_string(const char *s) { + const char *p; + int signo, r; + + /* Check that the input is a signal number. */ + if (safe_atoi(s, &signo) >= 0) { + if (SIGNAL_VALID(signo)) + return signo; + else + return -ERANGE; + } + + /* Drop "SIG" prefix. */ + if (startswith(s, "SIG")) + s += 3; + + /* Check that the input is a signal name. */ + signo = __signal_from_string(s); + if (signo > 0) + return signo; + + /* Check that the input is RTMIN or + * RTMIN+n (0 <= n <= SIGRTMAX-SIGRTMIN). */ + p = startswith(s, "RTMIN"); + if (p) { + if (*p == '\0') + return SIGRTMIN; + if (*p != '+') + return -EINVAL; + + r = safe_atoi(p, &signo); + if (r < 0) + return r; + + if (signo < 0 || signo > SIGRTMAX - SIGRTMIN) + return -ERANGE; + + return signo + SIGRTMIN; + } + + /* Check that the input is RTMAX or + * RTMAX-n (0 <= n <= SIGRTMAX-SIGRTMIN). */ + p = startswith(s, "RTMAX"); + if (p) { + if (*p == '\0') + return SIGRTMAX; + if (*p != '-') + return -EINVAL; + + r = safe_atoi(p, &signo); + if (r < 0) + return r; + + if (signo > 0 || signo < SIGRTMIN - SIGRTMAX) + return -ERANGE; + + return signo + SIGRTMAX; + } + + return -EINVAL; +} + +void nop_signal_handler(int sig) { + /* nothing here */ +} +#endif /* NM_IGNORED */ + +int signal_is_blocked(int sig) { + sigset_t ss; + int r; + + r = pthread_sigmask(SIG_SETMASK, NULL, &ss); + if (r != 0) + return -r; + + r = sigismember(&ss, sig); + if (r < 0) + return -errno; + + return r; +} diff --git a/shared/systemd/src/basic/signal-util.h b/shared/systemd/src/basic/signal-util.h index 92f2804c..3909ee34 100644 --- a/shared/systemd/src/basic/signal-util.h +++ b/shared/systemd/src/basic/signal-util.h @@ -41,3 +41,5 @@ static inline const char* signal_to_string_with_check(int n) { return signal_to_string(n); } + +int signal_is_blocked(int sig); diff --git a/shared/systemd/src/basic/socket-util.c b/shared/systemd/src/basic/socket-util.c index cded4545..452b9ced 100644 --- a/shared/systemd/src/basic/socket-util.c +++ b/shared/systemd/src/basic/socket-util.c @@ -15,6 +15,9 @@ #include <stdlib.h> #include <sys/ioctl.h> #include <unistd.h> +#if 0 /* NM_IGNORED */ +#include <linux/if.h> +#endif /* NM_IGNORED */ #include "alloc-util.h" #include "errno-util.h" @@ -44,237 +47,16 @@ #endif static const char* const socket_address_type_table[] = { - [SOCK_STREAM] = "Stream", - [SOCK_DGRAM] = "Datagram", - [SOCK_RAW] = "Raw", - [SOCK_RDM] = "ReliableDatagram", + [SOCK_STREAM] = "Stream", + [SOCK_DGRAM] = "Datagram", + [SOCK_RAW] = "Raw", + [SOCK_RDM] = "ReliableDatagram", [SOCK_SEQPACKET] = "SequentialPacket", - [SOCK_DCCP] = "DatagramCongestionControl", + [SOCK_DCCP] = "DatagramCongestionControl", }; DEFINE_STRING_TABLE_LOOKUP(socket_address_type, int); -int socket_address_parse(SocketAddress *a, const char *s) { - _cleanup_free_ char *n = NULL; - char *e; - int r; - - assert(a); - assert(s); - - *a = (SocketAddress) { - .type = SOCK_STREAM, - }; - - if (*s == '[') { - uint16_t port; - - /* IPv6 in [x:.....:z]:p notation */ - - e = strchr(s+1, ']'); - if (!e) - return -EINVAL; - - n = strndup(s+1, e-s-1); - if (!n) - return -ENOMEM; - - errno = 0; - if (inet_pton(AF_INET6, n, &a->sockaddr.in6.sin6_addr) <= 0) - return errno_or_else(EINVAL); - - e++; - if (*e != ':') - return -EINVAL; - - e++; - r = parse_ip_port(e, &port); - if (r < 0) - return r; - - a->sockaddr.in6.sin6_family = AF_INET6; - a->sockaddr.in6.sin6_port = htobe16(port); - a->size = sizeof(struct sockaddr_in6); - - } else if (*s == '/') { - /* AF_UNIX socket */ - - size_t l; - - l = strlen(s); - if (l >= sizeof(a->sockaddr.un.sun_path)) /* Note that we refuse non-NUL-terminated sockets when - * parsing (the kernel itself is less strict here in what it - * accepts) */ - return -EINVAL; - - a->sockaddr.un.sun_family = AF_UNIX; - memcpy(a->sockaddr.un.sun_path, s, l); - a->size = offsetof(struct sockaddr_un, sun_path) + l + 1; - - } else if (*s == '@') { - /* Abstract AF_UNIX socket */ - size_t l; - - l = strlen(s+1); - if (l >= sizeof(a->sockaddr.un.sun_path) - 1) /* Note that we refuse non-NUL-terminated sockets here - * when parsing, even though abstract namespace sockets - * explicitly allow embedded NUL bytes and don't consider - * them special. But it's simply annoying to debug such - * sockets. */ - return -EINVAL; - - a->sockaddr.un.sun_family = AF_UNIX; - memcpy(a->sockaddr.un.sun_path+1, s+1, l); - a->size = offsetof(struct sockaddr_un, sun_path) + 1 + l; - - } else if (startswith(s, "vsock:")) { - /* AF_VSOCK socket in vsock:cid:port notation */ - const char *cid_start = s + STRLEN("vsock:"); - unsigned port; - - e = strchr(cid_start, ':'); - if (!e) - return -EINVAL; - - r = safe_atou(e+1, &port); - if (r < 0) - return r; - - n = strndup(cid_start, e - cid_start); - if (!n) - return -ENOMEM; - - if (!isempty(n)) { - r = safe_atou(n, &a->sockaddr.vm.svm_cid); - if (r < 0) - return r; - } else - a->sockaddr.vm.svm_cid = VMADDR_CID_ANY; - - a->sockaddr.vm.svm_family = AF_VSOCK; - a->sockaddr.vm.svm_port = port; - a->size = sizeof(struct sockaddr_vm); - - } else { - uint16_t port; - - e = strchr(s, ':'); - if (e) { - r = parse_ip_port(e + 1, &port); - if (r < 0) - return r; - - n = strndup(s, e-s); - if (!n) - return -ENOMEM; - - /* IPv4 in w.x.y.z:p notation? */ - r = inet_pton(AF_INET, n, &a->sockaddr.in.sin_addr); - if (r < 0) - return -errno; - - if (r > 0) { - /* Gotcha, it's a traditional IPv4 address */ - a->sockaddr.in.sin_family = AF_INET; - a->sockaddr.in.sin_port = htobe16(port); - a->size = sizeof(struct sockaddr_in); - } else { - unsigned idx; - - if (strlen(n) > IF_NAMESIZE-1) - return -EINVAL; - - /* Uh, our last resort, an interface name */ - idx = if_nametoindex(n); - if (idx == 0) - return -EINVAL; - - a->sockaddr.in6.sin6_family = AF_INET6; - a->sockaddr.in6.sin6_port = htobe16(port); - a->sockaddr.in6.sin6_scope_id = idx; - a->sockaddr.in6.sin6_addr = in6addr_any; - a->size = sizeof(struct sockaddr_in6); - } - } else { - - /* Just a port */ - r = parse_ip_port(s, &port); - if (r < 0) - return r; - - if (socket_ipv6_is_supported()) { - a->sockaddr.in6.sin6_family = AF_INET6; - a->sockaddr.in6.sin6_port = htobe16(port); - a->sockaddr.in6.sin6_addr = in6addr_any; - a->size = sizeof(struct sockaddr_in6); - } else { - a->sockaddr.in.sin_family = AF_INET; - a->sockaddr.in.sin_port = htobe16(port); - a->sockaddr.in.sin_addr.s_addr = INADDR_ANY; - a->size = sizeof(struct sockaddr_in); - } - } - } - - return 0; -} - -int socket_address_parse_and_warn(SocketAddress *a, const char *s) { - SocketAddress b; - int r; - - /* Similar to socket_address_parse() but warns for IPv6 sockets when we don't support them. */ - - r = socket_address_parse(&b, s); - if (r < 0) - return r; - - if (!socket_ipv6_is_supported() && b.sockaddr.sa.sa_family == AF_INET6) { - log_warning("Binding to IPv6 address not available since kernel does not support IPv6."); - return -EAFNOSUPPORT; - } - - *a = b; - return 0; -} - -int socket_address_parse_netlink(SocketAddress *a, const char *s) { - _cleanup_free_ char *word = NULL; - unsigned group = 0; - int family, r; - - assert(a); - assert(s); - - zero(*a); - a->type = SOCK_RAW; - - r = extract_first_word(&s, &word, NULL, 0); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - family = netlink_family_from_string(word); - if (family < 0) - return -EINVAL; - - if (!isempty(s)) { - r = safe_atou(s, &group); - if (r < 0) - return r; - } - - a->sockaddr.nl.nl_family = AF_NETLINK; - a->sockaddr.nl.nl_groups = group; - - a->type = SOCK_RAW; - a->size = sizeof(struct sockaddr_nl); - a->protocol = family; - - return 0; -} - int socket_address_verify(const SocketAddress *a, bool strict) { assert(a); @@ -484,32 +266,6 @@ bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) { return true; } -bool socket_address_is(const SocketAddress *a, const char *s, int type) { - struct SocketAddress b; - - assert(a); - assert(s); - - if (socket_address_parse(&b, s) < 0) - return false; - - b.type = type; - - return socket_address_equal(a, &b); -} - -bool socket_address_is_netlink(const SocketAddress *a, const char *s) { - struct SocketAddress b; - - assert(a); - assert(s); - - if (socket_address_parse_netlink(&b, s) < 0) - return false; - - return socket_address_equal(a, &b); -} - const char* socket_address_get_path(const SocketAddress *a) { assert(a); @@ -912,7 +668,7 @@ static const char* const ip_tos_table[] = { DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ip_tos, int, 0xff); -bool ifname_valid(const char *p) { +bool ifname_valid_full(const char *p, bool alternative) { bool numeric = true; /* Checks whether a network interface name is valid. This is inspired by dev_valid_name() in the kernel sources @@ -922,8 +678,13 @@ bool ifname_valid(const char *p) { if (isempty(p)) return false; - if (strlen(p) >= IFNAMSIZ) - return false; + if (alternative) { + if (strlen(p) >= ALTIFNAMSIZ) + return false; + } else { + if (strlen(p) >= IFNAMSIZ) + return false; + } if (dot_or_dot_dot(p)) return false; diff --git a/shared/systemd/src/basic/socket-util.h b/shared/systemd/src/basic/socket-util.h index a0886e0e..2596c540 100644 --- a/shared/systemd/src/basic/socket-util.h +++ b/shared/systemd/src/basic/socket-util.h @@ -43,6 +43,8 @@ union sockaddr_union { uint8_t un_buffer[sizeof(struct sockaddr_un) + 1]; }; +#define SUN_PATH_LEN (sizeof(((struct sockaddr_un){}).sun_path)) + typedef struct SocketAddress { union sockaddr_union sockaddr; @@ -70,12 +72,6 @@ typedef enum SocketAddressBindIPv6Only { const char* socket_address_type_to_string(int t) _const_; int socket_address_type_from_string(const char *s) _pure_; -int socket_address_parse(SocketAddress *a, const char *s); -int socket_address_parse_and_warn(SocketAddress *a, const char *s); -int socket_address_parse_netlink(SocketAddress *a, const char *s); -int socket_address_print(const SocketAddress *a, char **p); -int socket_address_verify(const SocketAddress *a, bool strict) _pure_; - int sockaddr_un_unlink(const struct sockaddr_un *sa); static inline int socket_address_unlink(const SocketAddress *a) { @@ -96,11 +92,9 @@ int socket_address_listen( mode_t directory_mode, mode_t socket_mode, const char *label); -int make_socket_fd(int log_level, const char* address, int type, int flags); - -bool socket_address_is(const SocketAddress *a, const char *s, int type); -bool socket_address_is_netlink(const SocketAddress *a, const char *s); +int socket_address_verify(const SocketAddress *a, bool strict) _pure_; +int socket_address_print(const SocketAddress *a, char **p); bool socket_address_matches_fd(const SocketAddress *a, int fd); bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) _pure_; @@ -132,7 +126,10 @@ int fd_inc_rcvbuf(int fd, size_t n); int ip_tos_to_string_alloc(int i, char **s); int ip_tos_from_string(const char *s); -bool ifname_valid(const char *p); +bool ifname_valid_full(const char *p, bool alternative); +static inline bool ifname_valid(const char *p) { + return ifname_valid_full(p, false); +} bool address_label_valid(const char *p); int getpeercred(int fd, struct ucred *ucred); diff --git a/shared/systemd/src/basic/stat-util.c b/shared/systemd/src/basic/stat-util.c index 071050f2..a895c1c3 100644 --- a/shared/systemd/src/basic/stat-util.c +++ b/shared/systemd/src/basic/stat-util.c @@ -12,6 +12,7 @@ #include "alloc-util.h" #include "dirent-util.h" #include "fd-util.h" +#include "fileio.h" #include "fs-util.h" #include "macro.h" #include "missing_fs.h" @@ -31,6 +32,7 @@ int is_symlink(const char *path) { return !!S_ISLNK(info.st_mode); } +#endif /* NM_IGNORED */ int is_dir(const char* path, bool follow) { struct stat st; @@ -48,6 +50,7 @@ int is_dir(const char* path, bool follow) { return !!S_ISDIR(st.st_mode); } +#if 0 /* NM_IGNORED */ int is_dir_fd(int fd) { struct stat st; @@ -80,10 +83,9 @@ int dir_is_empty_at(int dir_fd, const char *path) { if (fd < 0) return -errno; - d = fdopendir(fd); + d = take_fdopendir(&fd); if (!d) return -errno; - fd = -1; FOREACH_DIRENT(de, d, return -errno) return 0; diff --git a/shared/systemd/src/basic/string-table.h b/shared/systemd/src/basic/string-table.h index 2d3cf814..96924778 100644 --- a/shared/systemd/src/basic/string-table.h +++ b/shared/systemd/src/basic/string-table.h @@ -44,7 +44,7 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k char *s; \ if (i < 0 || i > max) \ return -ERANGE; \ - if (i < (type) ELEMENTSOF(name##_table)) { \ + if (i < (type) ELEMENTSOF(name##_table) && name##_table[i]) { \ s = strdup(name##_table[i]); \ if (!s) \ return -ENOMEM; \ diff --git a/shared/systemd/src/basic/string-util.c b/shared/systemd/src/basic/string-util.c index 3d2feb18..9f15caca 100644 --- a/shared/systemd/src/basic/string-util.c +++ b/shared/systemd/src/basic/string-util.c @@ -115,7 +115,7 @@ static size_t strcspn_escaped(const char *s, const char *reject) { bool escaped = false; int n; - for (n=0; s[n]; n++) { + for (n = 0; s[n] != '\0'; n++) { if (escaped) escaped = false; else if (s[n] == '\\') @@ -124,50 +124,62 @@ static size_t strcspn_escaped(const char *s, const char *reject) { break; } - /* if s ends in \, return index of previous char */ - return n - escaped; + return n; } /* Split a string into words. */ -const char* split(const char **state, size_t *l, const char *separator, SplitFlags flags) { +const char* split( + const char **state, + size_t *l, + const char *separator, + SplitFlags flags) { + const char *current; + assert(state); + assert(l); + + if (!separator) + separator = WHITESPACE; + current = *state; - if (!*current) { - assert(**state == '\0'); + if (*current == '\0') /* already at the end? */ return NULL; - } - current += strspn(current, separator); - if (!*current) { + current += strspn(current, separator); /* skip leading separators */ + if (*current == '\0') { /* at the end now? */ *state = current; return NULL; } - if (flags & SPLIT_QUOTES && strchr("\'\"", *current)) { - char quotechars[2] = {*current, '\0'}; - - *l = strcspn_escaped(current + 1, quotechars); - if (current[*l + 1] == '\0' || current[*l + 1] != quotechars[0] || - (current[*l + 2] && !strchr(separator, current[*l + 2]))) { - /* right quote missing or garbage at the end */ - if (flags & SPLIT_RELAX) { - *state = current + *l + 1 + (current[*l + 1] != '\0'); - return current + 1; + if (FLAGS_SET(flags, SPLIT_QUOTES)) { + + if (strchr(QUOTES, *current)) { + /* We are looking at a quote */ + *l = strcspn_escaped(current + 1, CHAR_TO_STR(*current)); + if (current[*l + 1] != *current || + (current[*l + 2] != 0 && !strchr(separator, current[*l + 2]))) { + /* right quote missing or garbage at the end */ + if (FLAGS_SET(flags, SPLIT_RELAX)) { + *state = current + *l + 1 + (current[*l + 1] != '\0'); + return current + 1; + } + *state = current; + return NULL; } - *state = current; - return NULL; - } - *state = current++ + *l + 2; - } else if (flags & SPLIT_QUOTES) { - *l = strcspn_escaped(current, separator); - if (current[*l] && !strchr(separator, current[*l]) && !(flags & SPLIT_RELAX)) { - /* unfinished escape */ - *state = current; - return NULL; + *state = current++ + *l + 2; + + } else { + /* We are looking at a something that is not a quote */ + *l = strcspn_escaped(current, separator); + if (current[*l] && !strchr(separator, current[*l]) && !FLAGS_SET(flags, SPLIT_RELAX)) { + /* unfinished escape */ + *state = current; + return NULL; + } + *state = current + *l; } - *state = current + *l; } else { *l = strcspn(current, separator); *state = current + *l; @@ -1062,6 +1074,8 @@ bool string_is_safe(const char *p) { if (!p) return false; + /* Checks if the specified string contains no quotes or control characters */ + for (t = p; *t; t++) { if (*t > 0 && *t < ' ') /* no control characters */ return false; @@ -1072,3 +1086,133 @@ bool string_is_safe(const char *p) { return true; } + +#if 0 /* NM_IGNORED */ +char* string_erase(char *x) { + if (!x) + return NULL; + + /* A delicious drop of snake-oil! To be called on memory where we stored passphrases or so, after we + * used them. */ + explicit_bzero_safe(x, strlen(x)); + return x; +} + +int string_truncate_lines(const char *s, size_t n_lines, char **ret) { + const char *p = s, *e = s; + bool truncation_applied = false; + char *copy; + size_t n = 0; + + assert(s); + + /* Truncate after the specified number of lines. Returns > 0 if a truncation was applied or == 0 if + * there were fewer lines in the string anyway. Trailing newlines on input are ignored, and not + * generated either. */ + + for (;;) { + size_t k; + + k = strcspn(p, "\n"); + + if (p[k] == 0) { + if (k == 0) /* final empty line */ + break; + + if (n >= n_lines) /* above threshold */ + break; + + e = p + k; /* last line to include */ + break; + } + + assert(p[k] == '\n'); + + if (n >= n_lines) + break; + + if (k > 0) + e = p + k; + + p += k + 1; + n++; + } + + /* e points after the last character we want to keep */ + if (isempty(e)) + copy = strdup(s); + else { + if (!in_charset(e, "\n")) /* We only consider things truncated if we remove something that + * isn't a new-line or a series of them */ + truncation_applied = true; + + copy = strndup(s, e - s); + } + if (!copy) + return -ENOMEM; + + *ret = copy; + return truncation_applied; +} + +int string_extract_line(const char *s, size_t i, char **ret) { + const char *p = s; + size_t c = 0; + + /* Extract the i'nth line from the specified string. Returns > 0 if there are more lines after that, + * and == 0 if we are looking at the last line or already beyond the last line. As special + * optimization, if the first line is requested and the string only consists of one line we return + * NULL, indicating the input string should be used as is, and avoid a memory allocation for a very + * common case. */ + + for (;;) { + const char *q; + + q = strchr(p, '\n'); + if (i == c) { + /* The line we are looking for! */ + + if (q) { + char *m; + + m = strndup(p, q - p); + if (!m) + return -ENOMEM; + + *ret = m; + return !isempty(q + 1); /* more coming? */ + } else { + if (p == s) + *ret = NULL; /* Just use the input string */ + else { + char *m; + + m = strdup(p); + if (!m) + return -ENOMEM; + + *ret = m; + } + + return 0; /* The end */ + } + } + + if (!q) { + char *m; + + /* No more lines, return empty line */ + + m = strdup(""); + if (!m) + return -ENOMEM; + + *ret = m; + return 0; /* The end */ + } + + p = q + 1; + c++; + } +} +#endif /* NM_IGNORED */ diff --git a/shared/systemd/src/basic/string-util.h b/shared/systemd/src/basic/string-util.h index 04cc82b3..2a344b99 100644 --- a/shared/systemd/src/basic/string-util.h +++ b/shared/systemd/src/basic/string-util.h @@ -112,8 +112,10 @@ typedef enum SplitFlags { SPLIT_RELAX = 0x01 << 1, } SplitFlags; +/* Smelly. Do not use this anymore. Use extract_first_word() instead! */ const char* split(const char **state, size_t *l, const char *separator, SplitFlags flags); +/* Similar, don't use this anymore */ #define FOREACH_WORD(word, length, s, state) \ _FOREACH_WORD(word, length, s, WHITESPACE, 0, state) @@ -278,3 +280,8 @@ static inline char* str_realloc(char **p) { return (*p = t); } + +char* string_erase(char *x); + +int string_truncate_lines(const char *s, size_t n_lines, char **ret); +int string_extract_line(const char *s, size_t i, char **ret); diff --git a/shared/systemd/src/basic/strv.c b/shared/systemd/src/basic/strv.c index aa467132..be1c8325 100644 --- a/shared/systemd/src/basic/strv.c +++ b/shared/systemd/src/basic/strv.c @@ -18,8 +18,8 @@ #include "string-util.h" #include "strv.h" -char *strv_find(char **l, const char *name) { - char **i; +char *strv_find(char * const *l, const char *name) { + char * const *i; assert(name); @@ -30,8 +30,8 @@ char *strv_find(char **l, const char *name) { return NULL; } -char *strv_find_prefix(char **l, const char *name) { - char **i; +char *strv_find_prefix(char * const *l, const char *name) { + char * const *i; assert(name); @@ -42,8 +42,8 @@ char *strv_find_prefix(char **l, const char *name) { return NULL; } -char *strv_find_startswith(char **l, const char *name) { - char **i, *e; +char *strv_find_startswith(char * const *l, const char *name) { + char * const *i, *e; assert(name); @@ -59,20 +59,15 @@ char *strv_find_startswith(char **l, const char *name) { return NULL; } -void strv_clear(char **l) { +char **strv_free(char **l) { char **k; if (!l) - return; + return NULL; for (k = l; *k; k++) free(*k); - *l = NULL; -} - -char **strv_free(char **l) { - strv_clear(l); return mfree(l); } @@ -183,8 +178,8 @@ char **strv_new_internal(const char *x, ...) { return r; } -int strv_extend_strv(char ***a, char **b, bool filter_duplicates) { - char **s, **t; +int strv_extend_strv(char ***a, char * const *b, bool filter_duplicates) { + char * const *s, **t; size_t p, q, i = 0, j; assert(a); @@ -195,7 +190,10 @@ int strv_extend_strv(char ***a, char **b, bool filter_duplicates) { p = strv_length(*a); q = strv_length(b); - t = reallocarray(*a, p + q + 1, sizeof(char *)); + if (p >= SIZE_MAX - q) + return -ENOMEM; + + t = reallocarray(*a, GREEDY_ALLOC_ROUND_UP(p + q + 1), sizeof(char *)); if (!t) return -ENOMEM; @@ -228,9 +226,9 @@ rollback: } #if 0 /* NM_IGNORED */ -int strv_extend_strv_concat(char ***a, char **b, const char *suffix) { +int strv_extend_strv_concat(char ***a, char * const *b, const char *suffix) { + char * const *s; int r; - char **s; STRV_FOREACH(s, b) { char *v; @@ -349,9 +347,9 @@ int strv_split_extract(char ***t, const char *s, const char *separators, Extract } #endif /* NM_IGNORED */ -char *strv_join_prefix(char **l, const char *separator, const char *prefix) { +char *strv_join_prefix(char * const *l, const char *separator, const char *prefix) { + char * const *s; char *r, *e; - char **s; size_t n, k, m; if (!separator) @@ -389,19 +387,18 @@ char *strv_join_prefix(char **l, const char *separator, const char *prefix) { int strv_push(char ***l, char *value) { char **c; - size_t n, m; + size_t n; if (!value) return 0; n = strv_length(*l); - /* Increase and check for overflow */ - m = n + 2; - if (m < n) + /* Check for overflow */ + if (n > SIZE_MAX-2) return -ENOMEM; - c = reallocarray(*l, m, sizeof(char*)); + c = reallocarray(*l, GREEDY_ALLOC_ROUND_UP(n + 2), sizeof(char*)); if (!c) return -ENOMEM; @@ -414,19 +411,19 @@ int strv_push(char ***l, char *value) { int strv_push_pair(char ***l, char *a, char *b) { char **c; - size_t n, m; + size_t n; if (!a && !b) return 0; n = strv_length(*l); - /* increase and check for overflow */ - m = n + !!a + !!b + 1; - if (m < n) + /* Check for overflow */ + if (n > SIZE_MAX-3) return -ENOMEM; - c = reallocarray(*l, m, sizeof(char*)); + /* increase and check for overflow */ + c = reallocarray(*l, GREEDY_ALLOC_ROUND_UP(n + !!a + !!b + 1), sizeof(char*)); if (!c) return -ENOMEM; @@ -566,8 +563,8 @@ char **strv_uniq(char **l) { return l; } -bool strv_is_uniq(char **l) { - char **i; +bool strv_is_uniq(char * const *l) { + char * const *i; STRV_FOREACH(i, l) if (strv_find(i+1, *i)) @@ -672,7 +669,7 @@ char **strv_split_nulstr(const char *s) { } #endif /* NM_IGNORED */ -int strv_make_nulstr(char **l, char **p, size_t *q) { +int strv_make_nulstr(char * const *l, char **ret, size_t *ret_size) { /* A valid nulstr with two NULs at the end will be created, but * q will be the length without the two trailing NULs. Thus the output * string is a valid nulstr and can be iterated over using NULSTR_FOREACH, @@ -682,10 +679,10 @@ int strv_make_nulstr(char **l, char **p, size_t *q) { size_t n_allocated = 0, n = 0; _cleanup_free_ char *m = NULL; - char **i; + char * const *i; - assert(p); - assert(q); + assert(ret); + assert(ret_size); STRV_FOREACH(i, l) { size_t z; @@ -709,16 +706,16 @@ int strv_make_nulstr(char **l, char **p, size_t *q) { m[n] = '\0'; assert(n > 0); - *p = m; - *q = n - 1; + *ret = m; + *ret_size = n - 1; m = NULL; return 0; } -bool strv_overlap(char **a, char **b) { - char **i; +bool strv_overlap(char * const *a, char * const *b) { + char * const *i; STRV_FOREACH(i, a) if (strv_contains(b, *i)) @@ -738,23 +735,30 @@ char **strv_sort(char **l) { } #endif /* NM_IGNORED */ -bool strv_equal(char **a, char **b) { +int strv_compare(char * const *a, char * const *b) { + int r; - if (strv_isempty(a)) - return strv_isempty(b); + if (strv_isempty(a)) { + if (strv_isempty(b)) + return 0; + else + return -1; + } if (strv_isempty(b)) - return false; + return 1; - for ( ; *a || *b; ++a, ++b) - if (!streq_ptr(*a, *b)) - return false; + for ( ; *a || *b; ++a, ++b) { + r = strcmp_ptr(*a, *b); + if (r != 0) + return r; + } - return true; + return 0; } -void strv_print(char **l) { - char **s; +void strv_print(char * const *l) { + char * const *s; STRV_FOREACH(s, l) puts(*s); @@ -808,12 +812,13 @@ char **strv_shell_escape(char **l, const char *bad) { return l; } -bool strv_fnmatch(char* const* patterns, const char *s, int flags) { - char* const* p; - - STRV_FOREACH(p, patterns) - if (fnmatch(*p, s, flags) == 0) +bool strv_fnmatch_full(char* const* patterns, const char *s, int flags, size_t *matched_pos) { + for (size_t i = 0; patterns && patterns[i]; i++) + if (fnmatch(patterns[i], s, flags) == 0) { + if (matched_pos) + *matched_pos = i; return true; + } return false; } @@ -856,8 +861,10 @@ int strv_extend_n(char ***l, const char *value, size_t n) { /* Adds the value n times to l */ k = strv_length(*l); + if (n >= SIZE_MAX - k) + return -ENOMEM; - nl = reallocarray(*l, k + n + 1, sizeof(char *)); + nl = reallocarray(*l, GREEDY_ALLOC_ROUND_UP(k + n + 1), sizeof(char *)); if (!nl) return -ENOMEM; @@ -880,9 +887,9 @@ rollback: return -ENOMEM; } -int fputstrv(FILE *f, char **l, const char *separator, bool *space) { +int fputstrv(FILE *f, char * const *l, const char *separator, bool *space) { bool b = false; - char **s; + char * const *s; int r; /* Like fputs(), but for strv, and with a less stupid argument order */ diff --git a/shared/systemd/src/basic/strv.h b/shared/systemd/src/basic/strv.h index fbfa96a5..dd3323c2 100644 --- a/shared/systemd/src/basic/strv.h +++ b/shared/systemd/src/basic/strv.h @@ -13,9 +13,9 @@ #include "macro.h" #include "string-util.h" -char *strv_find(char **l, const char *name) _pure_; -char *strv_find_prefix(char **l, const char *name) _pure_; -char *strv_find_startswith(char **l, const char *name) _pure_; +char *strv_find(char * const *l, const char *name) _pure_; +char *strv_find_prefix(char * const *l, const char *name) _pure_; +char *strv_find_startswith(char * const *l, const char *name) _pure_; char **strv_free(char **l); DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free); @@ -25,13 +25,11 @@ char **strv_free_erase(char **l); DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free_erase); #define _cleanup_strv_free_erase_ _cleanup_(strv_free_erasep) -void strv_clear(char **l); - char **strv_copy(char * const *l); size_t strv_length(char * const *l) _pure_; -int strv_extend_strv(char ***a, char **b, bool filter_duplicates); -int strv_extend_strv_concat(char ***a, char **b, const char *suffix); +int strv_extend_strv(char ***a, char * const *b, bool filter_duplicates); +int strv_extend_strv_concat(char ***a, char * const *b, const char *suffix); int strv_extend(char ***l, const char *value); int strv_extendf(char ***l, const char *format, ...) _printf_(2,0); int strv_extend_front(char ***l, const char *value); @@ -49,9 +47,12 @@ int strv_consume_prepend(char ***l, char *value); char **strv_remove(char **l, const char *s); char **strv_uniq(char **l); -bool strv_is_uniq(char **l); +bool strv_is_uniq(char * const *l); -bool strv_equal(char **a, char **b); +int strv_compare(char * const *a, char * const *b); +static inline bool strv_equal(char * const *a, char * const *b) { + return strv_compare(a, b) == 0; +} #define strv_contains(l, s) (!!strv_find((l), (s))) @@ -77,16 +78,26 @@ char **strv_split_newlines(const char *s); int strv_split_extract(char ***t, const char *s, const char *separators, ExtractFlags flags); -char *strv_join_prefix(char **l, const char *separator, const char *prefix); -static inline char *strv_join(char **l, const char *separator) { +char *strv_join_prefix(char * const *l, const char *separator, const char *prefix); +static inline char *strv_join(char * const *l, const char *separator) { return strv_join_prefix(l, separator, NULL); } char **strv_parse_nulstr(const char *s, size_t l); char **strv_split_nulstr(const char *s); -int strv_make_nulstr(char **l, char **p, size_t *n); +int strv_make_nulstr(char * const *l, char **p, size_t *n); + +static inline int strv_from_nulstr(char ***a, const char *nulstr) { + char **t; -bool strv_overlap(char **a, char **b) _pure_; + t = strv_split_nulstr(nulstr); + if (!t) + return -ENOMEM; + *a = t; + return 0; +} + +bool strv_overlap(char * const *a, char * const *b) _pure_; #define STRV_FOREACH(s, l) \ for ((s) = (l); (s) && *(s); (s)++) @@ -103,7 +114,7 @@ bool strv_overlap(char **a, char **b) _pure_; for ((x) = (l), (y) = (x+1); (x) && *(x) && *(y); (x) += 2, (y) = (x + 1)) char **strv_sort(char **l); -void strv_print(char **l); +void strv_print(char * const *l); #define STRV_MAKE(...) ((char**) ((const char*[]) { __VA_ARGS__, NULL })) @@ -177,12 +188,15 @@ void strv_print(char **l); char **strv_reverse(char **l); char **strv_shell_escape(char **l, const char *bad); -bool strv_fnmatch(char* const* patterns, const char *s, int flags); +bool strv_fnmatch_full(char* const* patterns, const char *s, int flags, size_t *matched_pos); +static inline bool strv_fnmatch(char* const* patterns, const char *s) { + return strv_fnmatch_full(patterns, s, 0, NULL); +} static inline bool strv_fnmatch_or_empty(char* const* patterns, const char *s, int flags) { assert(s); return strv_isempty(patterns) || - strv_fnmatch(patterns, s, flags); + strv_fnmatch_full(patterns, s, flags, NULL); } char ***strv_free_free(char ***l); @@ -192,7 +206,7 @@ char **strv_skip(char **l, size_t n); int strv_extend_n(char ***l, const char *value, size_t n); -int fputstrv(FILE *f, char **l, const char *separator, bool *space); +int fputstrv(FILE *f, char * const *l, const char *separator, bool *space); #define strv_free_and_replace(a, b) \ ({ \ diff --git a/shared/systemd/src/basic/time-util.c b/shared/systemd/src/basic/time-util.c index 4411127a..71af4f21 100644 --- a/shared/systemd/src/basic/time-util.c +++ b/shared/systemd/src/basic/time-util.c @@ -1507,9 +1507,30 @@ int time_change_fd(void) { if (fd < 0) return -errno; - if (timerfd_settime(fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) < 0) - return -errno; + if (timerfd_settime(fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) >= 0) + return TAKE_FD(fd); + + /* So apparently there are systems where time_t is 64bit, but the kernel actually doesn't support + * 64bit time_t. In that case configuring a timer to TIME_T_MAX will fail with EOPNOTSUPP or a + * similar error. If that's the case let's try with INT32_MAX instead, maybe that works. It's a bit + * of a black magic thing though, but what can we do? + * + * We don't want this code on x86-64, hence let's conditionalize this for systems with 64bit time_t + * but where "long" is shorter than 64bit, i.e. 32bit archs. + * + * See: https://github.com/systemd/systemd/issues/14362 */ + +#if SIZEOF_TIME_T == 8 && ULONG_MAX < UINT64_MAX + if (ERRNO_IS_NOT_SUPPORTED(errno) || errno == EOVERFLOW) { + static const struct itimerspec its32 = { + .it_value.tv_sec = INT32_MAX, + }; + + if (timerfd_settime(fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its32, NULL) >= 0) + return TAKE_FD(fd); + } +#endif - return TAKE_FD(fd); + return -errno; } #endif /* NM_IGNORED */ diff --git a/shared/systemd/src/basic/tmpfile-util.c b/shared/systemd/src/basic/tmpfile-util.c index d8a689e0..8bbd12b0 100644 --- a/shared/systemd/src/basic/tmpfile-util.c +++ b/shared/systemd/src/basic/tmpfile-util.c @@ -21,50 +21,58 @@ #include "tmpfile-util.h" #include "umask-util.h" -int fopen_temporary(const char *path, FILE **_f, char **_temp_path) { - FILE *f; - char *t; - int r, fd; +int fopen_temporary(const char *path, FILE **ret_f, char **ret_temp_path) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *t = NULL; + _cleanup_close_ int fd = -1; + int r; - assert(path); - assert(_f); - assert(_temp_path); + if (path) { + r = tempfn_xxxxxx(path, NULL, &t); + if (r < 0) + return r; + } else { + const char *d; - r = tempfn_xxxxxx(path, NULL, &t); - if (r < 0) - return r; + r = tmp_dir(&d); + if (r < 0) + return r; + + t = path_join(d, "XXXXXX"); + if (!t) + return -ENOMEM; + } fd = mkostemp_safe(t); - if (fd < 0) { - free(t); + if (fd < 0) return -errno; - } /* This assumes that returned FILE object is short-lived and used within the same single-threaded * context and never shared externally, hence locking is not necessary. */ - r = fdopen_unlocked(fd, "w", &f); + r = take_fdopen_unlocked(&fd, "w", &f); if (r < 0) { - unlink(t); - free(t); - safe_close(fd); + (void) unlink(t); return r; } - *_f = f; - *_temp_path = t; + if (ret_f) + *ret_f = TAKE_PTR(f); + + if (ret_temp_path) + *ret_temp_path = TAKE_PTR(t); return 0; } /* This is much like mkostemp() but is subject to umask(). */ int mkostemp_safe(char *pattern) { - _unused_ _cleanup_umask_ mode_t u = umask(0077); - int fd; + int fd = -1; /* avoid false maybe-uninitialized warning */ assert(pattern); - fd = mkostemp(pattern, O_CLOEXEC); + RUN_WITH_UMASK(0077) + fd = mkostemp(pattern, O_CLOEXEC); if (fd < 0) return -errno; @@ -73,18 +81,16 @@ int mkostemp_safe(char *pattern) { #if 0 /* NM_IGNORED */ int fmkostemp_safe(char *pattern, const char *mode, FILE **ret_f) { - int fd; + _cleanup_close_ int fd = -1; FILE *f; fd = mkostemp_safe(pattern); if (fd < 0) return fd; - f = fdopen(fd, mode); - if (!f) { - safe_close(fd); + f = take_fdopen(&fd, mode); + if (!f) return -errno; - } *ret_f = f; return 0; diff --git a/shared/systemd/src/shared/web-util.c b/shared/systemd/src/shared/web-util.c new file mode 100644 index 00000000..f5a5362b --- /dev/null +++ b/shared/systemd/src/shared/web-util.c @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "nm-sd-adapt-shared.h" + +#include <stdbool.h> + +#include "string-util.h" +#include "strv.h" +#include "utf8.h" +#include "web-util.h" + +#if 0 /* NM_IGNORED */ +bool http_etag_is_valid(const char *etag) { + if (isempty(etag)) + return false; + + if (!endswith(etag, "\"")) + return false; + + if (!STARTSWITH_SET(etag, "\"", "W/\"")) + return false; + + return true; +} +#endif /* NM_IGNORED */ + +bool http_url_is_valid(const char *url) { + const char *p; + + if (isempty(url)) + return false; + + p = STARTSWITH_SET(url, "http://", "https://"); + if (!p) + return false; + + if (isempty(p)) + return false; + + return ascii_is_valid(p); +} + +#if 0 /* NM_IGNORED */ +bool documentation_url_is_valid(const char *url) { + const char *p; + + if (isempty(url)) + return false; + + if (http_url_is_valid(url)) + return true; + + p = STARTSWITH_SET(url, "file:/", "info:", "man:"); + if (isempty(p)) + return false; + + return ascii_is_valid(p); +} +#endif /* NM_IGNORED */ diff --git a/shared/systemd/src/shared/web-util.h b/shared/systemd/src/shared/web-util.h new file mode 100644 index 00000000..c9e67e5c --- /dev/null +++ b/shared/systemd/src/shared/web-util.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include <stdbool.h> + +#include "macro.h" + +bool http_url_is_valid(const char *url) _pure_; + +bool documentation_url_is_valid(const char *url) _pure_; + +bool http_etag_is_valid(const char *etag); |