diff options
| author | Michael Biebl <biebl@debian.org> | 2021-10-01 23:05:04 +0200 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2021-10-01 23:05:04 +0200 |
| commit | e74c568b07b50b97873fb4ee1d776dedefbd54d6 (patch) | |
| tree | 3469f17ea9af91f7ff169b890633bda68b0cf76e /shared/nm-glib-aux/nm-shared-utils.c | |
| parent | bfe522304da217296e2a61040f58e35ec5d6f3f2 (diff) | |
New upstream version 1.32.12 upstream/1.32.12
Diffstat (limited to 'shared/nm-glib-aux/nm-shared-utils.c')
| -rw-r--r-- | shared/nm-glib-aux/nm-shared-utils.c | 5789 |
1 files changed, 0 insertions, 5789 deletions
diff --git a/shared/nm-glib-aux/nm-shared-utils.c b/shared/nm-glib-aux/nm-shared-utils.c deleted file mode 100644 index e7cea23a..00000000 --- a/shared/nm-glib-aux/nm-shared-utils.c +++ /dev/null @@ -1,5789 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Copyright (C) 2016 Red Hat, Inc. - */ - -#include "nm-glib-aux/nm-default-glib-i18n-lib.h" - -#include "nm-shared-utils.h" - -#include <pwd.h> -#include <arpa/inet.h> -#include <poll.h> -#include <fcntl.h> -#include <sys/syscall.h> -#include <glib-unix.h> -#include <net/if.h> -#include <net/ethernet.h> -#include <pthread.h> - -#include "c-list/src/c-list.h" -#include "nm-errno.h" -#include "nm-str-buf.h" - -G_STATIC_ASSERT(sizeof(NMEtherAddr) == 6); -G_STATIC_ASSERT(_nm_alignof(NMEtherAddr) == 1); - -G_STATIC_ASSERT(sizeof(NMUtilsNamedEntry) == sizeof(const char *)); -G_STATIC_ASSERT(G_STRUCT_OFFSET(NMUtilsNamedValue, value_ptr) == sizeof(const char *)); - -/*****************************************************************************/ - -const char _nm_hexchar_table_lower[16] = "0123456789abcdef"; -const char _nm_hexchar_table_upper[16] = "0123456789ABCDEF"; - -const void *const _NM_PTRARRAY_EMPTY[1] = {NULL}; - -/*****************************************************************************/ - -const NMIPAddr nm_ip_addr_zero = {}; - -/* this initializes a struct in_addr/in6_addr and allows for untrusted - * arguments (like unsuitable @addr_family or @src_len). It's almost safe - * in the sense that it verifies input arguments strictly. Also, it - * uses memcpy() to access @src, so alignment is not an issue. - * - * Only potential pitfalls: - * - * - it allows for @addr_family to be AF_UNSPEC. If that is the case (and the - * caller allows for that), the caller MUST provide @out_addr_family. - * - when setting @dst to an IPv4 address, the trailing bytes are not touched. - * Meaning, if @dst is an NMIPAddr union, only the first bytes will be set. - * If that matter to you, clear @dst before. */ -gboolean -nm_ip_addr_set_from_untrusted(int addr_family, - gpointer dst, - gconstpointer src, - gsize src_len, - int * out_addr_family) -{ - nm_assert(dst); - - switch (addr_family) { - case AF_UNSPEC: - if (!out_addr_family) { - /* when the callers allow undefined @addr_family, they must provide - * an @out_addr_family argument. */ - nm_assert_not_reached(); - return FALSE; - } - switch (src_len) { - case sizeof(struct in_addr): - addr_family = AF_INET; - break; - case sizeof(struct in6_addr): - addr_family = AF_INET6; - break; - default: - return FALSE; - } - break; - case AF_INET: - if (src_len != sizeof(struct in_addr)) - return FALSE; - break; - case AF_INET6: - if (src_len != sizeof(struct in6_addr)) - return FALSE; - break; - default: - /* when the callers allow undefined @addr_family, they must provide - * an @out_addr_family argument. */ - nm_assert(out_addr_family); - return FALSE; - } - - nm_assert(src); - - memcpy(dst, src, src_len); - NM_SET_OUT(out_addr_family, addr_family); - return TRUE; -} - -/*****************************************************************************/ - -G_STATIC_ASSERT(ETH_ALEN == sizeof(struct ether_addr)); -G_STATIC_ASSERT(ETH_ALEN == 6); - -/*****************************************************************************/ - -pid_t -nm_utils_gettid(void) -{ - return (pid_t) syscall(SYS_gettid); -} - -/* Used for asserting that this function is called on the main-thread. - * The main-thread is determined by remembering the thread-id - * of when the function was called the first time. - * - * When forking, the thread-id is again reset upon first call. */ -gboolean -_nm_assert_on_main_thread(void) -{ - G_LOCK_DEFINE_STATIC(lock); - static pid_t seen_tid; - static pid_t seen_pid; - pid_t tid; - pid_t pid; - gboolean success = FALSE; - - tid = nm_utils_gettid(); - nm_assert(tid != 0); - - G_LOCK(lock); - - if (G_LIKELY(tid == seen_tid)) { - /* we don't care about false positives (when the process forked, and the thread-id - * is accidentally re-used) . It's for assertions only. */ - success = TRUE; - } else { - pid = getpid(); - nm_assert(pid != 0); - - if (seen_tid == 0 || seen_pid != pid) { - /* either this is the first time we call the function, or the process - * forked. In both cases, remember the thread-id. */ - seen_tid = tid; - seen_pid = pid; - success = TRUE; - } - } - - G_UNLOCK(lock); - - return success; -} - -/*****************************************************************************/ - -void -nm_utils_strbuf_append_c(char **buf, gsize *len, char c) -{ - switch (*len) { - case 0: - return; - case 1: - (*buf)[0] = '\0'; - *len = 0; - (*buf)++; - return; - default: - (*buf)[0] = c; - (*buf)[1] = '\0'; - (*len)--; - (*buf)++; - return; - } -} - -void -nm_utils_strbuf_append_bin(char **buf, gsize *len, gconstpointer str, gsize str_len) -{ - switch (*len) { - case 0: - return; - case 1: - if (str_len == 0) { - (*buf)[0] = '\0'; - return; - } - (*buf)[0] = '\0'; - *len = 0; - (*buf)++; - return; - default: - if (str_len == 0) { - (*buf)[0] = '\0'; - return; - } - if (str_len >= *len) { - memcpy(*buf, str, *len - 1); - (*buf)[*len - 1] = '\0'; - *buf = &(*buf)[*len]; - *len = 0; - } else { - memcpy(*buf, str, str_len); - *buf = &(*buf)[str_len]; - (*buf)[0] = '\0'; - *len -= str_len; - } - return; - } -} - -void -nm_utils_strbuf_append_str(char **buf, gsize *len, const char *str) -{ - gsize src_len; - - switch (*len) { - case 0: - return; - case 1: - if (!str || !*str) { - (*buf)[0] = '\0'; - return; - } - (*buf)[0] = '\0'; - *len = 0; - (*buf)++; - return; - default: - if (!str || !*str) { - (*buf)[0] = '\0'; - return; - } - src_len = g_strlcpy(*buf, str, *len); - if (src_len >= *len) { - *buf = &(*buf)[*len]; - *len = 0; - } else { - *buf = &(*buf)[src_len]; - *len -= src_len; - } - return; - } -} - -void -nm_utils_strbuf_append(char **buf, gsize *len, const char *format, ...) -{ - char * p = *buf; - va_list args; - int retval; - - if (*len == 0) - return; - - va_start(args, format); - retval = g_vsnprintf(p, *len, format, args); - va_end(args); - - if ((gsize) retval >= *len) { - *buf = &p[*len]; - *len = 0; - } else { - *buf = &p[retval]; - *len -= retval; - } -} - -/** - * nm_utils_strbuf_seek_end: - * @buf: the input/output buffer - * @len: the input/output length of the buffer. - * - * Commonly, one uses nm_utils_strbuf_append*(), to incrementally - * append strings to the buffer. However, sometimes we need to use - * existing API to write to the buffer. - * After doing so, we want to adjust the buffer counter. - * Essentially, - * - * g_snprintf (buf, len, ...); - * nm_utils_strbuf_seek_end (&buf, &len); - * - * is almost the same as - * - * nm_utils_strbuf_append (&buf, &len, ...); - * - * The only difference is the behavior when the string got truncated: - * nm_utils_strbuf_append() will recognize that and set the remaining - * length to zero. - * - * In general, the behavior is: - * - * - if *len is zero, do nothing - * - if the buffer contains a NUL byte within the first *len characters, - * the buffer is pointed to the NUL byte and len is adjusted. In this - * case, the remaining *len is always >= 1. - * In particular, that is also the case if the NUL byte is at the very last - * position ((*buf)[*len -1]). That happens, when the previous operation - * either fit the string exactly into the buffer or the string was truncated - * by g_snprintf(). The difference cannot be determined. - * - if the buffer contains no NUL bytes within the first *len characters, - * write NUL at the last position, set *len to zero, and point *buf past - * the NUL byte. This would happen with - * - * strncpy (buf, long_str, len); - * nm_utils_strbuf_seek_end (&buf, &len). - * - * where strncpy() does truncate the string and not NUL terminate it. - * nm_utils_strbuf_seek_end() would then NUL terminate it. - */ -void -nm_utils_strbuf_seek_end(char **buf, gsize *len) -{ - gsize l; - char *end; - - nm_assert(len); - nm_assert(buf && *buf); - - if (*len <= 1) { - if (*len == 1 && (*buf)[0]) - goto truncate; - return; - } - - end = memchr(*buf, 0, *len); - if (end) { - l = end - *buf; - nm_assert(l < *len); - - *buf = end; - *len -= l; - return; - } - -truncate: - /* hm, no NUL character within len bytes. - * Just NUL terminate the array and consume them - * all. */ - *buf += *len; - (*buf)[-1] = '\0'; - *len = 0; - return; -} - -/*****************************************************************************/ - -GBytes * -nm_gbytes_get_empty(void) -{ - static GBytes *bytes = NULL; - GBytes * b; - -again: - b = g_atomic_pointer_get(&bytes); - if (G_UNLIKELY(!b)) { - b = g_bytes_new_static("", 0); - if (!g_atomic_pointer_compare_and_exchange(&bytes, NULL, b)) { - g_bytes_unref(b); - goto again; - } - } - return b; -} - -GBytes * -nm_g_bytes_new_from_str(const char *str) -{ - gsize l; - - if (!str) - return NULL; - - /* the returned array is guaranteed to have a trailing '\0' - * character *after* the length. */ - - l = strlen(str); - return g_bytes_new_take(nm_memdup(str, l + 1u), l); -} - -/** - * nm_utils_gbytes_equals: - * @bytes: (allow-none): a #GBytes array to compare. Note that - * %NULL is treated like an #GBytes array of length zero. - * @mem_data: the data pointer with @mem_len bytes - * @mem_len: the length of the data pointer - * - * Returns: %TRUE if @bytes contains the same data as @mem_data. As a - * special case, a %NULL @bytes is treated like an empty array. - */ -gboolean -nm_utils_gbytes_equal_mem(GBytes *bytes, gconstpointer mem_data, gsize mem_len) -{ - gconstpointer p; - gsize l; - - if (!bytes) { - /* as a special case, let %NULL GBytes compare identical - * to an empty array. */ - return (mem_len == 0); - } - - p = g_bytes_get_data(bytes, &l); - return l == mem_len - && (mem_len == 0 /* allow @mem_data to be %NULL */ - || memcmp(p, mem_data, mem_len) == 0); -} - -GVariant * -nm_utils_gbytes_to_variant_ay(GBytes *bytes) -{ - const guint8 *p; - gsize l; - - if (!bytes) { - /* for convenience, accept NULL to return an empty variant */ - return g_variant_new_array(G_VARIANT_TYPE_BYTE, NULL, 0); - } - - p = g_bytes_get_data(bytes, &l); - return g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, p, l, 1); -} - -/*****************************************************************************/ - -#define _variant_singleton_get(create_variant) \ - ({ \ - static GVariant *_singleton = NULL; \ - GVariant * _v; \ - \ -again: \ - _v = g_atomic_pointer_get(&_singleton); \ - if (G_UNLIKELY(!_v)) { \ - _v = (create_variant); \ - nm_assert(_v); \ - nm_assert(g_variant_is_floating(_v)); \ - g_variant_ref_sink(_v); \ - if (!g_atomic_pointer_compare_and_exchange(&_singleton, NULL, _v)) { \ - g_variant_unref(_v); \ - goto again; \ - } \ - } \ - _v; \ - }) - -GVariant * -nm_g_variant_singleton_u_0(void) -{ - return _variant_singleton_get(g_variant_new_uint32(0)); -} - -/*****************************************************************************/ - -GHashTable * -nm_utils_strdict_clone(GHashTable *src) -{ - GHashTable * dst; - GHashTableIter iter; - const char * key; - const char * val; - - if (!src) - return NULL; - - dst = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_free); - g_hash_table_iter_init(&iter, src); - while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val)) - g_hash_table_insert(dst, g_strdup(key), g_strdup(val)); - return dst; -} - -/* 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) -{ - gs_free NMUtilsNamedValue *values_free = NULL; - NMUtilsNamedValue values_prepared[20]; - const NMUtilsNamedValue * values; - GVariantBuilder builder; - guint i; - guint n; - - values = nm_utils_named_values_from_strdict(strdict, &n, values_prepared, &values_free); - - g_variant_builder_init(&builder, G_VARIANT_TYPE("a{ss}")); - for (i = 0; i < n; i++) { - g_variant_builder_add(&builder, "{ss}", values[i].name, values[i].value_str); - } - return g_variant_builder_end(&builder); -} - -/*****************************************************************************/ - -GVariant * -nm_utils_strdict_to_variant_asv(GHashTable *strdict) -{ - gs_free NMUtilsNamedValue *values_free = NULL; - NMUtilsNamedValue values_prepared[20]; - const NMUtilsNamedValue * values; - GVariantBuilder builder; - guint i; - guint n; - - values = nm_utils_named_values_from_strdict(strdict, &n, values_prepared, &values_free); - - g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}")); - for (i = 0; i < n; i++) { - g_variant_builder_add(&builder, - "{sv}", - values[i].name, - g_variant_new_string(values[i].value_str)); - } - return g_variant_builder_end(&builder); -} - -/*****************************************************************************/ - -/** - * nm_strquote: - * @buf: the output buffer of where to write the quoted @str argument. - * @buf_len: the size of @buf. - * @str: (allow-none): the string to quote. - * - * Writes @str to @buf with quoting. The resulting buffer - * is always NUL terminated, unless @buf_len is zero. - * If @str is %NULL, it writes "(null)". - * - * If @str needs to be truncated, the closing quote is '^' instead - * of '"'. - * - * This is similar to nm_strquote_a(), which however uses alloca() - * to allocate a new buffer. Also, here @buf_len is the size of @buf, - * while nm_strquote_a() has the number of characters to print. The latter - * doesn't include the quoting. - * - * Returns: the input buffer with the quoted string. - */ -const char * -nm_strquote(char *buf, gsize buf_len, const char *str) -{ - const char *const buf0 = buf; - - if (!str) { - nm_utils_strbuf_append_str(&buf, &buf_len, "(null)"); - goto out; - } - - if (G_UNLIKELY(buf_len <= 2)) { - switch (buf_len) { - case 2: - *(buf++) = '^'; - /* fall-through */ - case 1: - *(buf++) = '\0'; - break; - } - goto out; - } - - *(buf++) = '"'; - buf_len--; - - nm_utils_strbuf_append_str(&buf, &buf_len, str); - - /* if the string was too long we indicate truncation with a - * '^' instead of a closing quote. */ - if (G_UNLIKELY(buf_len <= 1)) { - switch (buf_len) { - case 1: - buf[-1] = '^'; - break; - case 0: - buf[-2] = '^'; - break; - default: - nm_assert_not_reached(); - break; - } - } else { - nm_assert(buf_len >= 2); - *(buf++) = '"'; - *(buf++) = '\0'; - } - -out: - return buf0; -} - -/*****************************************************************************/ - -char _nm_utils_to_string_buffer[]; - -void -nm_utils_to_string_buffer_init(char **buf, gsize *len) -{ - if (!*buf) { - *buf = _nm_utils_to_string_buffer; - *len = sizeof(_nm_utils_to_string_buffer); - } -} - -gboolean -nm_utils_to_string_buffer_init_null(gconstpointer obj, char **buf, gsize *len) -{ - nm_utils_to_string_buffer_init(buf, len); - if (!obj) { - g_strlcpy(*buf, "(null)", *len); - return FALSE; - } - return TRUE; -} - -/*****************************************************************************/ - -const char * -nm_utils_flags2str(const NMUtilsFlags2StrDesc *descs, - gsize n_descs, - unsigned flags, - char * buf, - gsize len) -{ - gsize i; - char *p; - -#if NM_MORE_ASSERTS > 10 - nm_assert(descs); - nm_assert(n_descs > 0); - for (i = 0; i < n_descs; i++) { - gsize j; - - nm_assert(descs[i].name && descs[i].name[0]); - for (j = 0; j < i; j++) - nm_assert(descs[j].flag != descs[i].flag); - } -#endif - - nm_utils_to_string_buffer_init(&buf, &len); - - if (!len) - return buf; - - buf[0] = '\0'; - p = buf; - if (!flags) { - for (i = 0; i < n_descs; i++) { - if (!descs[i].flag) { - nm_utils_strbuf_append_str(&p, &len, descs[i].name); - break; - } - } - return buf; - } - - for (i = 0; flags && i < n_descs; i++) { - if (descs[i].flag && NM_FLAGS_ALL(flags, descs[i].flag)) { - flags &= ~descs[i].flag; - - if (buf[0] != '\0') - nm_utils_strbuf_append_c(&p, &len, ','); - nm_utils_strbuf_append_str(&p, &len, descs[i].name); - } - } - if (flags) { - if (buf[0] != '\0') - nm_utils_strbuf_append_c(&p, &len, ','); - nm_utils_strbuf_append(&p, &len, "0x%x", flags); - } - return buf; -}; - -/*****************************************************************************/ - -/** - * _nm_utils_ip4_prefix_to_netmask: - * @prefix: a CIDR prefix - * - * Returns: the netmask represented by the prefix, in network byte order - **/ -guint32 -_nm_utils_ip4_prefix_to_netmask(guint32 prefix) -{ - return prefix < 32 ? ~htonl(0xFFFFFFFFu >> prefix) : 0xFFFFFFFFu; -} - -gconstpointer -nm_utils_ipx_address_clear_host_address(int family, gpointer dst, gconstpointer src, guint8 plen) -{ - g_return_val_if_fail(dst, NULL); - - switch (family) { - case AF_INET: - g_return_val_if_fail(plen <= 32, NULL); - - if (!src) { - /* allow "self-assignment", by specifying %NULL as source. */ - src = dst; - } - - *((guint32 *) dst) = nm_utils_ip4_address_clear_host_address(*((guint32 *) src), plen); - break; - case AF_INET6: - nm_utils_ip6_address_clear_host_address(dst, src, plen); - break; - default: - g_return_val_if_reached(NULL); - } - return dst; -} - -/* nm_utils_ip4_address_clear_host_address: - * @addr: source ip6 address - * @plen: prefix length of network - * - * returns: the input address, with the host address set to 0. - */ -in_addr_t -nm_utils_ip4_address_clear_host_address(in_addr_t addr, guint8 plen) -{ - return addr & _nm_utils_ip4_prefix_to_netmask(plen); -} - -/* nm_utils_ip6_address_clear_host_address: - * @dst: destination output buffer, will contain the network part of the @src address - * @src: source ip6 address - * @plen: prefix length of network - * - * Note: this function is self assignment safe, to update @src inplace, set both - * @dst and @src to the same destination or set @src NULL. - */ -const struct in6_addr * -nm_utils_ip6_address_clear_host_address(struct in6_addr * dst, - const struct in6_addr *src, - guint8 plen) -{ - g_return_val_if_fail(plen <= 128, NULL); - g_return_val_if_fail(dst, NULL); - - if (!src) - src = dst; - - if (plen < 128) { - guint nbytes = plen / 8; - guint nbits = plen % 8; - - if (nbytes && dst != src) - memcpy(dst, src, nbytes); - if (nbits) { - dst->s6_addr[nbytes] = (src->s6_addr[nbytes] & (0xFF << (8 - nbits))); - nbytes++; - } - if (nbytes <= 15) - memset(&dst->s6_addr[nbytes], 0, 16 - nbytes); - } else if (src != dst) - *dst = *src; - - return dst; -} - -int -nm_utils_ip6_address_same_prefix_cmp(const struct in6_addr *addr_a, - const struct in6_addr *addr_b, - guint8 plen) -{ - int nbytes; - guint8 va, vb, m; - - if (plen >= 128) - NM_CMP_DIRECT_MEMCMP(addr_a, addr_b, sizeof(struct in6_addr)); - else { - nbytes = plen / 8; - if (nbytes) - NM_CMP_DIRECT_MEMCMP(addr_a, addr_b, nbytes); - - plen = plen % 8; - if (plen != 0) { - m = ~((1 << (8 - plen)) - 1); - va = ((((const guint8 *) addr_a))[nbytes]) & m; - vb = ((((const guint8 *) addr_b))[nbytes]) & m; - NM_CMP_DIRECT(va, vb); - } - } - return 0; -} - -/*****************************************************************************/ - -guint32 -_nm_utils_ip4_get_default_prefix0(in_addr_t ip) -{ - /* The function is originally from ipcalc.c of Red Hat's initscripts. */ - switch (ntohl(ip) >> 24) { - case 0 ... 127: - return 8; /* Class A */ - case 128 ... 191: - return 16; /* Class B */ - case 192 ... 223: - return 24; /* Class C */ - } - return 0; -} - -guint32 -_nm_utils_ip4_get_default_prefix(in_addr_t ip) -{ - return _nm_utils_ip4_get_default_prefix0(ip) ?: 24; -} - -gboolean -nm_utils_ip_is_site_local(int addr_family, const void *address) -{ - in_addr_t addr4; - - switch (addr_family) { - case AF_INET: - /* RFC1918 private addresses - * 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 */ - addr4 = ntohl(*((const in_addr_t *) address)); - return (addr4 & 0xff000000) == 0x0a000000 || (addr4 & 0xfff00000) == 0xac100000 - || (addr4 & 0xffff0000) == 0xc0a80000; - case AF_INET6: - return IN6_IS_ADDR_SITELOCAL(address); - default: - g_return_val_if_reached(FALSE); - } -} - -/*****************************************************************************/ - -static gboolean -_parse_legacy_addr4(const char *text, in_addr_t *out_addr, GError **error) -{ - gs_free char * s_free = NULL; - struct in_addr a1; - guint8 bin[sizeof(a1)]; - char * s; - int i; - - if (inet_aton(text, &a1) != 1) { - g_set_error_literal(error, - NM_UTILS_ERROR, - NM_UTILS_ERROR_INVALID_ARGUMENT, - "address invalid according to inet_aton()"); - return FALSE; - } - - /* OK, inet_aton() accepted the format. That's good, because we want - * to accept IPv4 addresses in octal format, like 255.255.000.000. - * That's what "legacy" means here. inet_pton() doesn't accept those. - * - * But inet_aton() also ignores trailing garbage and formats with fewer than - * 4 digits. That is just too crazy and we don't do that. Perform additional checks - * and reject some forms that inet_aton() accepted. - * - * Note that we still should (of course) accept everything that inet_pton() - * accepts. However this code never gets called if inet_pton() succeeds - * (see below, aside the assertion code). */ - - if (NM_STRCHAR_ANY(text, ch, (!(ch >= '0' && ch <= '9') && !NM_IN_SET(ch, '.', 'x')))) { - /* We only accepts '.', digits, and 'x' for "0x". */ - g_set_error_literal(error, - NM_UTILS_ERROR, - NM_UTILS_ERROR_INVALID_ARGUMENT, - "contains an invalid character"); - return FALSE; - } - - s = nm_memdup_maybe_a(300, text, strlen(text) + 1, &s_free); - - for (i = 0; i < G_N_ELEMENTS(bin); i++) { - char * current_token = s; - gint32 v; - - s = strchr(s, '.'); - if (s) { - s[0] = '\0'; - s++; - } - - if ((i == G_N_ELEMENTS(bin) - 1) != (s == NULL)) { - /* Exactly for the last digit, we expect to have no more following token. - * But this isn't the case. Abort. */ - g_set_error(error, - NM_UTILS_ERROR, - NM_UTILS_ERROR_INVALID_ARGUMENT, - "wrong number of tokens (index %d, token '%s')", - i, - s); - return FALSE; - } - - v = _nm_utils_ascii_str_to_int64(current_token, 0, 0, 0xFF, -1); - if (v == -1) { - int errsv = errno; - - /* we do accept octal and hex (even with leading "0x"). But something - * about this token is wrong. */ - g_set_error(error, - NM_UTILS_ERROR, - NM_UTILS_ERROR_INVALID_ARGUMENT, - "invalid token '%s': %s (%d)", - current_token, - nm_strerror_native(errsv), - errsv); - return FALSE; - } - - bin[i] = v; - } - - if (memcmp(bin, &a1, sizeof(bin)) != 0) { - /* our parsing did not agree with what inet_aton() gave. Something - * is wrong. Abort. */ - g_set_error( - error, - NM_UTILS_ERROR, - NM_UTILS_ERROR_INVALID_ARGUMENT, - "inet_aton() result 0x%08x differs from computed value 0x%02hhx%02hhx%02hhx%02hhx", - a1.s_addr, - bin[0], - bin[1], - bin[2], - bin[3]); - return FALSE; - } - - *out_addr = a1.s_addr; - return TRUE; -} - -gboolean -nm_utils_parse_inaddr_bin_full(int addr_family, - gboolean accept_legacy, - const char *text, - int * out_addr_family, - gpointer out_addr) -{ - NMIPAddr addrbin; - - g_return_val_if_fail(text, FALSE); - - if (addr_family == AF_UNSPEC) { - g_return_val_if_fail(!out_addr || out_addr_family, FALSE); - addr_family = strchr(text, ':') ? AF_INET6 : AF_INET; - } else - g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), FALSE); - - if (inet_pton(addr_family, text, &addrbin) != 1) { - if (accept_legacy && addr_family == AF_INET - && _parse_legacy_addr4(text, &addrbin.addr4, NULL)) { - /* The address is in some legacy format which inet_aton() accepts, but not inet_pton(). - * Most likely octal digits (leading zeros). We accept the address. */ - } else - return FALSE; - } - -#if NM_MORE_ASSERTS > 10 - if (addr_family == AF_INET) { - gs_free_error GError *error = NULL; - in_addr_t a; - - /* The legacy parser should accept everything that inet_pton() accepts too. Meaning, - * it should strictly parse *more* formats. And of course, parse it the same way. */ - if (!_parse_legacy_addr4(text, &a, &error)) { - char buf[INET_ADDRSTRLEN]; - - g_error("unexpected assertion failure: could parse \"%s\" as %s, but not accepted by " - "legacy parser: %s", - text, - _nm_utils_inet4_ntop(addrbin.addr4, buf), - error->message); - } - nm_assert(addrbin.addr4 == a); - } -#endif - - NM_SET_OUT(out_addr_family, addr_family); - if (out_addr) - nm_ip_addr_set(addr_family, out_addr, &addrbin); - return TRUE; -} - -gboolean -nm_utils_parse_inaddr(int addr_family, const char *text, char **out_addr) -{ - NMIPAddr addrbin; - char addrstr_buf[MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)]; - - g_return_val_if_fail(text, FALSE); - - if (addr_family == AF_UNSPEC) - addr_family = strchr(text, ':') ? AF_INET6 : AF_INET; - else - g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), FALSE); - - if (inet_pton(addr_family, text, &addrbin) != 1) - return FALSE; - - NM_SET_OUT(out_addr, - g_strdup(inet_ntop(addr_family, &addrbin, addrstr_buf, sizeof(addrstr_buf)))); - return TRUE; -} - -gboolean -nm_utils_parse_inaddr_prefix_bin(int addr_family, - const char *text, - int * out_addr_family, - gpointer out_addr, - int * out_prefix) -{ - gs_free char *addrstr_free = NULL; - int prefix = -1; - const char * slash; - const char * addrstr; - NMIPAddr addrbin; - - g_return_val_if_fail(text, FALSE); - - if (addr_family == AF_UNSPEC) { - g_return_val_if_fail(!out_addr || out_addr_family, FALSE); - addr_family = strchr(text, ':') ? AF_INET6 : AF_INET; - } else - g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), FALSE); - - slash = strchr(text, '/'); - if (slash) - addrstr = nm_strndup_a(300, text, slash - text, &addrstr_free); - else - addrstr = text; - - if (inet_pton(addr_family, addrstr, &addrbin) != 1) - return FALSE; - - if (slash) { - /* For IPv4, `ip addr add` supports the prefix-length as a netmask. We don't - * do that. */ - prefix = - _nm_utils_ascii_str_to_int64(&slash[1], 10, 0, addr_family == AF_INET ? 32 : 128, -1); - if (prefix == -1) - return FALSE; - } - - NM_SET_OUT(out_addr_family, addr_family); - if (out_addr) - nm_ip_addr_set(addr_family, out_addr, &addrbin); - NM_SET_OUT(out_prefix, prefix); - return TRUE; -} - -gboolean -nm_utils_parse_inaddr_prefix(int addr_family, const char *text, char **out_addr, int *out_prefix) -{ - NMIPAddr addrbin; - char addrstr_buf[MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)]; - - if (!nm_utils_parse_inaddr_prefix_bin(addr_family, text, &addr_family, &addrbin, out_prefix)) - return FALSE; - NM_SET_OUT(out_addr, - g_strdup(inet_ntop(addr_family, &addrbin, addrstr_buf, sizeof(addrstr_buf)))); - return TRUE; -} - -gboolean -nm_utils_parse_next_line(const char **inout_ptr, - gsize * inout_len, - const char **out_line, - gsize * out_line_len) -{ - gboolean eol_is_carriage_return; - const char *line_start; - gsize line_len; - - nm_assert(inout_ptr); - nm_assert(inout_len); - nm_assert(*inout_len == 0 || *inout_ptr); - nm_assert(out_line); - nm_assert(out_line_len); - - if (G_UNLIKELY(*inout_len == 0)) - return FALSE; - - line_start = *inout_ptr; - - eol_is_carriage_return = FALSE; - for (line_len = 0;; line_len++) { - if (line_len >= *inout_len) { - /* if we consumed the entire line, we place the pointer at - * one character after the end. */ - *inout_ptr = &line_start[line_len]; - *inout_len = 0; - goto done; - } - switch (line_start[line_len]) { - case '\r': - eol_is_carriage_return = TRUE; - /* fall-through*/ - case '\0': - case '\n': - *inout_ptr = &line_start[line_len + 1]; - *inout_len = *inout_len - line_len - 1u; - if (eol_is_carriage_return && *inout_len > 0 && (*inout_ptr)[0] == '\n') { - /* also consume "\r\n" as one. */ - (*inout_len)--; - (*inout_ptr)++; - } - goto done; - } - } - -done: - *out_line = line_start; - *out_line_len = line_len; - return TRUE; -} - -/*****************************************************************************/ - -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 - * can be successfully converted to a number and is within a given - * range. On any error, @fallback will be returned and %errno will be set - * to a non-zero value. On success, %errno will be set to zero, check %errno - * for errors. Any trailing or leading (ascii) white space is ignored and the - * functions is locale independent. - * - * The function is guaranteed to return a value between @min and @max - * (inclusive) or @fallback. Also, the parsing is rather strict, it does - * not allow for any unrecognized characters, except leading and trailing - * white space. - **/ -gint64 -_nm_utils_ascii_str_to_int64(const char *str, guint base, gint64 min, gint64 max, gint64 fallback) -{ - gint64 v; - const char *s = NULL; - - str = nm_str_skip_leading_spaces(str); - if (!str || !str[0]) { - errno = EINVAL; - return fallback; - } - - errno = 0; - v = nm_g_ascii_strtoll(str, (char **) &s, base); - - if (errno != 0) - return fallback; - - if (s[0] != '\0') { - s = nm_str_skip_leading_spaces(s); - if (s[0] != '\0') { - errno = EINVAL; - return fallback; - } - } - if (v > max || v < min) { - errno = ERANGE; - return fallback; - } - - return v; -} - -guint64 -_nm_utils_ascii_str_to_uint64(const char *str, - guint base, - guint64 min, - guint64 max, - guint64 fallback) -{ - guint64 v; - const char *s = NULL; - - if (str) { - while (g_ascii_isspace(str[0])) - str++; - } - if (!str || !str[0]) { - errno = EINVAL; - return fallback; - } - - errno = 0; - v = nm_g_ascii_strtoull(str, (char **) &s, base); - - if (errno != 0) - return fallback; - if (s[0] != '\0') { - while (g_ascii_isspace(s[0])) - s++; - if (s[0] != '\0') { - errno = EINVAL; - return fallback; - } - } - if (v > max || v < min) { - errno = ERANGE; - return fallback; - } - - if (v != 0 && str[0] == '-') { - /* As documented, g_ascii_strtoull() accepts negative values, and returns their - * absolute value. We don't. */ - errno = ERANGE; - return fallback; - } - - return v; -} - -/*****************************************************************************/ - -int -nm_strcmp_with_data(gconstpointer a, gconstpointer b, gpointer user_data) -{ - const char *s1 = a; - const char *s2 = b; - - return strcmp(s1, s2); -} - -/* like nm_strcmp_p(), suitable for g_ptr_array_sort_with_data(). - * g_ptr_array_sort() just casts nm_strcmp_p() to a function of different - * signature. I guess, in glib there are knowledgeable people that ensure - * that this additional argument doesn't cause problems due to different ABI - * for every architecture that glib supports. - * For NetworkManager, we'd rather avoid such stunts. - **/ -int -nm_strcmp_p_with_data(gconstpointer a, gconstpointer b, gpointer user_data) -{ - const char *s1 = *((const char **) a); - const char *s2 = *((const char **) b); - - return strcmp(s1, s2); -} - -int -nm_strcmp0_p_with_data(gconstpointer a, gconstpointer b, gpointer user_data) -{ - const char *s1 = *((const char **) a); - const char *s2 = *((const char **) b); - - return nm_strcmp0(s1, s2); -} - -int -nm_strcmp_ascii_case_with_data(gconstpointer a, gconstpointer b, gpointer user_data) -{ - const char *s1 = a; - const char *s2 = b; - - return g_ascii_strcasecmp(s1, s2); -} - -int -nm_cmp_uint32_p_with_data(gconstpointer p_a, gconstpointer p_b, gpointer user_data) -{ - const guint32 a = *((const guint32 *) p_a); - const guint32 b = *((const guint32 *) p_b); - - if (a < b) - return -1; - if (a > b) - return 1; - return 0; -} - -int -nm_cmp_int2ptr_p_with_data(gconstpointer p_a, gconstpointer p_b, gpointer user_data) -{ - /* p_a and p_b are two pointers to a pointer, where the pointer is - * interpreted as a integer using GPOINTER_TO_INT(). - * - * That is the case of a hash-table that uses GINT_TO_POINTER() to - * convert integers as pointers, and the resulting keys-as-array - * array. */ - const int a = GPOINTER_TO_INT(*((gconstpointer *) p_a)); - const int b = GPOINTER_TO_INT(*((gconstpointer *) p_b)); - - if (a < b) - return -1; - if (a > b) - return 1; - return 0; -} - -/*****************************************************************************/ - -const char * -nm_utils_dbus_path_get_last_component(const char *dbus_path) -{ - if (dbus_path) { - dbus_path = strrchr(dbus_path, '/'); - if (dbus_path) - return dbus_path + 1; - } - return NULL; -} - -static gint64 -_dbus_path_component_as_num(const char *p) -{ - gint64 n; - - /* no odd stuff. No leading zeros, only a non-negative, decimal integer. - * - * Otherwise, there would be multiple ways to encode the same number "10" - * and "010". That is just confusing. A number has no leading zeros, - * if it has, it's not a number (as far as we are concerned here). */ - if (p[0] == '0') { - if (p[1] != '\0') - return -1; - else - return 0; - } - if (!(p[0] >= '1' && p[0] <= '9')) - return -1; - if (!NM_STRCHAR_ALL(&p[1], ch, (ch >= '0' && ch <= '9'))) - return -1; - n = _nm_utils_ascii_str_to_int64(p, 10, 0, G_MAXINT64, -1); - nm_assert(n == -1 || nm_streq0(p, nm_sprintf_bufa(100, "%" G_GINT64_FORMAT, n))); - return n; -} - -int -nm_utils_dbus_path_cmp(const char *dbus_path_a, const char *dbus_path_b) -{ - const char *l_a, *l_b; - gsize plen; - gint64 n_a, n_b; - - /* compare function for two D-Bus paths. It behaves like - * strcmp(), except, if both paths have the same prefix, - * and both end in a (positive) number, then the paths - * will be sorted by number. */ - - NM_CMP_SELF(dbus_path_a, dbus_path_b); - - /* if one or both paths have no slash (and no last component) - * compare the full paths directly. */ - if (!(l_a = nm_utils_dbus_path_get_last_component(dbus_path_a)) - || !(l_b = nm_utils_dbus_path_get_last_component(dbus_path_b))) - goto comp_full; - - /* check if both paths have the same prefix (up to the last-component). */ - plen = l_a - dbus_path_a; - if (plen != (l_b - dbus_path_b)) - goto comp_full; - NM_CMP_RETURN(strncmp(dbus_path_a, dbus_path_b, plen)); - - n_a = _dbus_path_component_as_num(l_a); - n_b = _dbus_path_component_as_num(l_b); - if (n_a == -1 && n_b == -1) - goto comp_l; - - /* both components must be convertible to a number. If they are not, - * (and only one of them is), then we must always strictly sort numeric parts - * after non-numeric components. If we wouldn't, we wouldn't have - * a total order. - * - * An example of a not total ordering would be: - * "8" < "010" (numeric) - * "0x" < "8" (lexical) - * "0x" > "010" (lexical) - * We avoid this, by forcing that a non-numeric entry "0x" always sorts - * before numeric entries. - * - * Additionally, _dbus_path_component_as_num() would also reject "010" as - * not a valid number. - */ - if (n_a == -1) - return -1; - if (n_b == -1) - return 1; - - NM_CMP_DIRECT(n_a, n_b); - nm_assert(nm_streq(dbus_path_a, dbus_path_b)); - return 0; - -comp_full: - NM_CMP_DIRECT_STRCMP0(dbus_path_a, dbus_path_b); - return 0; -comp_l: - NM_CMP_DIRECT_STRCMP0(l_a, l_b); - nm_assert(nm_streq(dbus_path_a, dbus_path_b)); - return 0; -} - -/*****************************************************************************/ - -typedef struct { - union { - guint8 table[256]; - guint64 _dummy_for_alignment; - }; -} CharLookupTable; - -static void -_char_lookup_table_set_one(CharLookupTable *lookup, char ch) -{ - lookup->table[(guint8) ch] = 1; -} - -static void -_char_lookup_table_set_all(CharLookupTable *lookup, const char *candidates) -{ - while (candidates[0] != '\0') - _char_lookup_table_set_one(lookup, (candidates++)[0]); -} - -static void -_char_lookup_table_init(CharLookupTable *lookup, const char *candidates) -{ - *lookup = (CharLookupTable){ - .table = {0}, - }; - if (candidates) - _char_lookup_table_set_all(lookup, candidates); -} - -static gboolean -_char_lookup_has(const CharLookupTable *lookup, char ch) -{ - /* with some optimization levels, the compiler thinks this code - * might access uninitialized @lookup. It is not -- when you look at the - * callers of this function. */ - NM_PRAGMA_WARNING_DISABLE("-Wmaybe-uninitialized") - nm_assert(lookup->table[(guint8) '\0'] == 0); - return lookup->table[(guint8) ch] != 0; - NM_PRAGMA_WARNING_REENABLE -} - -static gboolean -_char_lookup_has_all(const CharLookupTable *lookup, 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. - * @delimiters: the set of delimiters. - * @flags: additional flags for controlling the operation. - * - * This is a replacement for g_strsplit_set() which avoids copying - * each word once (the entire strv array), but instead copies it once - * and all words point into that internal copy. - * - * 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 "". - * 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 - * is not set, it also returns %NULL. - * Otherwise, a %NULL terminated strv array containing the split words. - * (delimiter characters are removed). - * 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 individual strings in-place, - * like "g_strstrip((char *) iter[0])". - */ -const char ** -nm_utils_strsplit_set_full(const char *str, const char *delimiters, NMUtilsStrsplitSetFlags flags) -{ - const char ** ptr; - gsize num_tokens; - gsize i_token; - gsize str_len_p1; - const char * c_str; - char * s; - CharLookupTable ch_lookup; - const gboolean f_escaped = NM_FLAGS_HAS(flags, NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED); - const gboolean f_allow_escaping = - f_escaped || NM_FLAGS_HAS(flags, NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING); - const gboolean f_preserve_empty = - NM_FLAGS_HAS(flags, NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY); - const gboolean f_strstrip = NM_FLAGS_HAS(flags, NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP); - - if (!str) - return NULL; - - if (!delimiters) { - nm_assert_not_reached(); - delimiters = " \t\n"; - } - _char_lookup_table_init(&ch_lookup, delimiters); - - nm_assert(!f_allow_escaping || !_char_lookup_has(&ch_lookup, '\\')); - - if (!f_preserve_empty) { - while (_char_lookup_has(&ch_lookup, str[0])) - str++; - } - - if (!str[0]) { - /* We return %NULL here, also with NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY. - * That makes nm_utils_strsplit_set_full() with NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY - * different from g_strsplit_set(), which would in this case return an empty array. - * If you need to handle %NULL, and "" specially, then check the input string first. */ - return NULL; - } - -#define _char_is_escaped(str_start, str_cur) \ - ({ \ - const char *const _str_start = (str_start); \ - const char *const _str_cur = (str_cur); \ - const char * _str_i = (_str_cur); \ - \ - while (_str_i > _str_start && _str_i[-1] == '\\') \ - _str_i--; \ - (((_str_cur - _str_i) % 2) != 0); \ - }) - - num_tokens = 1; - c_str = str; - while (TRUE) { - while (G_LIKELY(!_char_lookup_has(&ch_lookup, c_str[0]))) { - if (c_str[0] == '\0') - goto done1; - c_str++; - } - - /* we assume escapings are not frequent. After we found - * this delimiter, check whether it was escaped by counting - * the backslashed before. */ - if (f_allow_escaping && _char_is_escaped(str, c_str)) { - /* the delimiter is escaped. This was not an accepted delimiter. */ - c_str++; - continue; - } - - c_str++; - - /* if we drop empty tokens, then we now skip over all consecutive delimiters. */ - if (!f_preserve_empty) { - while (_char_lookup_has(&ch_lookup, c_str[0])) - c_str++; - if (c_str[0] == '\0') - break; - } - - num_tokens++; - } - -done1: - - nm_assert(c_str[0] == '\0'); - - str_len_p1 = (c_str - str) + 1; - - nm_assert(str[str_len_p1 - 1] == '\0'); - - ptr = g_malloc((sizeof(const char *) * (num_tokens + 1)) + str_len_p1); - s = (char *) &ptr[num_tokens + 1]; - memcpy(s, str, str_len_p1); - - i_token = 0; - - while (TRUE) { - nm_assert(i_token < num_tokens); - ptr[i_token++] = s; - - if (s[0] == '\0') { - nm_assert(f_preserve_empty); - goto done2; - } - nm_assert(f_preserve_empty || !_char_lookup_has(&ch_lookup, s[0])); - - while (!_char_lookup_has(&ch_lookup, s[0])) { - if (G_UNLIKELY(s[0] == '\\' && f_allow_escaping)) { - s++; - if (s[0] == '\0') - goto done2; - s++; - } else if (s[0] == '\0') - goto done2; - else - s++; - } - - nm_assert(_char_lookup_has(&ch_lookup, s[0])); - s[0] = '\0'; - s++; - - if (!f_preserve_empty) { - while (_char_lookup_has(&ch_lookup, s[0])) - s++; - if (s[0] == '\0') - goto done2; - } - } - -done2: - nm_assert(i_token == num_tokens); - ptr[i_token] = NULL; - - if (f_strstrip) { - gsize i; - - i_token = 0; - for (i = 0; ptr[i]; i++) { - s = (char *) nm_str_skip_leading_spaces(ptr[i]); - if (s[0] != '\0') { - char *s_last; - - s_last = &s[strlen(s) - 1]; - while (s_last > s && g_ascii_isspace(s_last[0]) - && (!f_allow_escaping || !_char_is_escaped(s, s_last))) - (s_last--)[0] = '\0'; - } - - if (!f_preserve_empty && s[0] == '\0') - continue; - - ptr[i_token++] = s; - } - - if (i_token == 0) { - g_free(ptr); - return NULL; - } - ptr[i_token] = NULL; - } - - if (f_escaped) { - gsize i, j; - - /* We no longer need ch_lookup for its original purpose. Modify it, so it - * can detect the delimiters, '\\', and (optionally) whitespaces. */ - _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]; - j = 0; - for (i = 0; s[i] != '\0';) { - if (s[i] == '\\' && _char_lookup_has(&ch_lookup, s[i + 1])) - i++; - s[j++] = s[i++]; - } - s[j] = '\0'; - } - } - - nm_assert(ptr && ptr[0]); - return ptr; -} - -/*****************************************************************************/ - -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) -{ - CharLookupTable ch_lookup; - CharLookupTable ch_lookup_as_needed; - 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; - - 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); - } - - 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++; - - if (n_escapes == 0u) { - *out_to_free = NULL; - return str; - } - - alloc_len = str_len + n_escapes + 1u; - ret = g_new(char, alloc_len); - - j = 0; - 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++] = '\\'; - ret[j++] = str[i]; - } - if (escape_trailing_space) { - 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++; - } - - 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; -} - -/*****************************************************************************/ - -/** - * nm_utils_strsplit_quoted: - * @str: the string to split (e.g. from /proc/cmdline). - * - * This basically does that systemd's extract_first_word() does - * with the flags "EXTRACT_UNQUOTE | EXTRACT_RELAX". This is what - * systemd uses to parse /proc/cmdline, and we do too. - * - * Splits the string. We have nm_utils_strsplit_set() which - * supports a variety of flags. However, extending that already - * complex code to also support quotation and escaping is hard. - * Instead, add a naive implementation. - * - * Returns: (transfer full): the split string. - */ -char ** -nm_utils_strsplit_quoted(const char *str) -{ - gs_unref_ptrarray GPtrArray *arr = NULL; - gs_free char * str_out = NULL; - CharLookupTable ch_lookup; - - nm_assert(str); - - _char_lookup_table_init(&ch_lookup, NM_ASCII_WHITESPACES); - - for (;;) { - char quote; - gsize j; - - while (_char_lookup_has(&ch_lookup, str[0])) - str++; - - if (str[0] == '\0') - break; - - if (!str_out) - str_out = g_new(char, strlen(str) + 1); - - quote = '\0'; - j = 0; - for (;;) { - if (str[0] == '\\') { - str++; - if (str[0] == '\0') - break; - str_out[j++] = str[0]; - str++; - continue; - } - if (quote) { - if (str[0] == '\0') - break; - if (str[0] == quote) { - quote = '\0'; - str++; - continue; - } - str_out[j++] = str[0]; - str++; - continue; - } - if (str[0] == '\0') - break; - if (NM_IN_SET(str[0], '\'', '"')) { - quote = str[0]; - str++; - continue; - } - if (_char_lookup_has(&ch_lookup, str[0])) { - str++; - break; - } - str_out[j++] = str[0]; - str++; - } - - if (!arr) - arr = g_ptr_array_new(); - g_ptr_array_add(arr, g_strndup(str_out, j)); - } - - if (!arr) - return g_new0(char *, 1); - - g_ptr_array_add(arr, NULL); - - /* We want to return an optimally sized strv array, with no excess - * memory allocated. Hence, clone once more. */ - return nm_memdup(arr->pdata, sizeof(char *) * arr->len); -} - -/*****************************************************************************/ - -/** - * nm_utils_strv_find_first: - * @list: the strv list to search - * @len: the length of the list, or a negative value if @list is %NULL terminated. - * @needle: the value to search for. The search is done using strcmp(). - * - * Searches @list for @needle and returns the index of the first match (based - * on strcmp()). - * - * For convenience, @list has type 'char**' instead of 'const char **'. - * - * Returns: index of first occurrence or -1 if @needle is not found in @list. - */ -gssize -nm_utils_strv_find_first(char **list, gssize len, const char *needle) -{ - gssize i; - - if (len > 0) { - g_return_val_if_fail(list, -1); - - if (!needle) { - /* if we search a list with known length, %NULL is a valid @needle. */ - for (i = 0; i < len; i++) { - if (!list[i]) - return i; - } - } else { - for (i = 0; i < len; i++) { - if (list[i] && !strcmp(needle, list[i])) - return i; - } - } - } else if (len < 0) { - g_return_val_if_fail(needle, -1); - - if (list) { - for (i = 0; list[i]; i++) { - if (strcmp(needle, list[i]) == 0) - return i; - } - } - } - return -1; -} - -char ** -_nm_utils_strv_cleanup(char ** strv, - gboolean strip_whitespace, - gboolean skip_empty, - gboolean skip_repeated) -{ - guint i, j; - - if (!strv || !*strv) - return strv; - - if (strip_whitespace) { - /* we only modify the strings pointed to by @strv if @strip_whitespace is - * requested. Otherwise, the strings themselves are untouched. */ - for (i = 0; strv[i]; i++) - g_strstrip(strv[i]); - } - if (!skip_empty && !skip_repeated) - return strv; - j = 0; - for (i = 0; strv[i]; i++) { - if ((skip_empty && !*strv[i]) - || (skip_repeated && nm_utils_strv_find_first(strv, j, strv[i]) >= 0)) - g_free(strv[i]); - else - strv[j++] = strv[i]; - } - strv[j] = NULL; - return strv; -} - -/*****************************************************************************/ - -GPtrArray * -_nm_g_ptr_array_copy(GPtrArray * array, - GCopyFunc func, - gpointer user_data, - GDestroyNotify element_free_func) -{ - GPtrArray *new_array; - guint i; - - g_return_val_if_fail(array, NULL); - - new_array = g_ptr_array_new_full(array->len, element_free_func); - for (i = 0; i < array->len; i++) { - g_ptr_array_add(new_array, func ? func(array->pdata[i], user_data) : array->pdata[i]); - } - return new_array; -} - -/*****************************************************************************/ - -int -_nm_utils_ascii_str_to_bool(const char *str, int default_value) -{ - gs_free char *str_free = NULL; - - if (!str) - return default_value; - - str = nm_strstrip_avoid_copy_a(300, str, &str_free); - if (str[0] == '\0') - return default_value; - - if (!g_ascii_strcasecmp(str, "true") || !g_ascii_strcasecmp(str, "yes") - || !g_ascii_strcasecmp(str, "on") || !g_ascii_strcasecmp(str, "1")) - return TRUE; - - if (!g_ascii_strcasecmp(str, "false") || !g_ascii_strcasecmp(str, "no") - || !g_ascii_strcasecmp(str, "off") || !g_ascii_strcasecmp(str, "0")) - return FALSE; - - return default_value; -} - -/*****************************************************************************/ - -NM_CACHED_QUARK_FCN("nm-manager-error-quark", nm_manager_error_quark); - -NM_CACHED_QUARK_FCN("nm-utils-error-quark", nm_utils_error_quark); - -void -nm_utils_error_set_cancelled(GError **error, gboolean is_disposing, const char *instance_name) -{ - if (is_disposing) { - g_set_error(error, - NM_UTILS_ERROR, - NM_UTILS_ERROR_CANCELLED_DISPOSING, - "Disposing %s instance", - instance_name && *instance_name ? instance_name : "source"); - } else { - g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Request cancelled"); - } -} - -gboolean -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 (error->domain == NM_UTILS_ERROR) - return NM_IN_SET(error->code, NM_UTILS_ERROR_CANCELLED_DISPOSING); - } - return FALSE; -} - -gboolean -nm_utils_error_is_notfound(GError *error) -{ - if (error) { - if (error->domain == G_IO_ERROR) - return NM_IN_SET(error->code, G_IO_ERROR_NOT_FOUND); - if (error->domain == G_FILE_ERROR) - return NM_IN_SET(error->code, G_FILE_ERROR_NOENT); - } - return FALSE; -} - -/*****************************************************************************/ - -/** - * nm_g_object_set_property: - * @object: the target object - * @property_name: the property name - * @value: the #GValue to set - * @error: (allow-none): optional error argument - * - * A reimplementation of g_object_set_property(), but instead - * returning an error instead of logging a warning. All g_object_set*() - * versions in glib require you to not pass invalid types or they will - * log a g_warning() -- without reporting an error. We don't want that, - * so we need to hack error checking around it. - * - * Returns: whether the value was successfully set. - */ -gboolean -nm_g_object_set_property(GObject * object, - const char * property_name, - const GValue *value, - GError ** error) -{ - GParamSpec * pspec; - nm_auto_unset_gvalue GValue tmp_value = G_VALUE_INIT; - GObjectClass * klass; - - g_return_val_if_fail(G_IS_OBJECT(object), FALSE); - g_return_val_if_fail(property_name != NULL, FALSE); - g_return_val_if_fail(G_IS_VALUE(value), FALSE); - g_return_val_if_fail(!error || !*error, FALSE); - - /* g_object_class_find_property() does g_param_spec_get_redirect_target(), - * where we differ from a plain g_object_set_property(). */ - pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(object), property_name); - - if (!pspec) { - g_set_error(error, - NM_UTILS_ERROR, - NM_UTILS_ERROR_UNKNOWN, - _("object class '%s' has no property named '%s'"), - G_OBJECT_TYPE_NAME(object), - property_name); - return FALSE; - } - if (!(pspec->flags & G_PARAM_WRITABLE)) { - g_set_error(error, - NM_UTILS_ERROR, - NM_UTILS_ERROR_UNKNOWN, - _("property '%s' of object class '%s' is not writable"), - pspec->name, - G_OBJECT_TYPE_NAME(object)); - return FALSE; - } - if ((pspec->flags & G_PARAM_CONSTRUCT_ONLY)) { - g_set_error(error, - NM_UTILS_ERROR, - NM_UTILS_ERROR_UNKNOWN, - _("construct property \"%s\" for object '%s' can't be set after construction"), - pspec->name, - G_OBJECT_TYPE_NAME(object)); - return FALSE; - } - - klass = g_type_class_peek(pspec->owner_type); - if (klass == NULL) { - g_set_error(error, - NM_UTILS_ERROR, - NM_UTILS_ERROR_UNKNOWN, - _("'%s::%s' is not a valid property name; '%s' is not a GObject subtype"), - g_type_name(pspec->owner_type), - pspec->name, - g_type_name(pspec->owner_type)); - return FALSE; - } - - /* provide a copy to work from, convert (if necessary) and validate */ - g_value_init(&tmp_value, pspec->value_type); - if (!g_value_transform(value, &tmp_value)) { - g_set_error(error, - NM_UTILS_ERROR, - NM_UTILS_ERROR_UNKNOWN, - _("unable to set property '%s' of type '%s' from value of type '%s'"), - pspec->name, - g_type_name(pspec->value_type), - G_VALUE_TYPE_NAME(value)); - return FALSE; - } - if (g_param_value_validate(pspec, &tmp_value) && !(pspec->flags & G_PARAM_LAX_VALIDATION)) { - gs_free char *contents = g_strdup_value_contents(value); - - g_set_error(error, - NM_UTILS_ERROR, - NM_UTILS_ERROR_UNKNOWN, - _("value \"%s\" of type '%s' is invalid or out of range for property '%s' of " - "type '%s'"), - contents, - G_VALUE_TYPE_NAME(value), - pspec->name, - g_type_name(pspec->value_type)); - return FALSE; - } - - g_object_set_property(object, property_name, &tmp_value); - return TRUE; -} - -#define _set_property(object, property_name, gtype, gtype_set, value, error) \ - G_STMT_START \ - { \ - nm_auto_unset_gvalue GValue gvalue = {0}; \ - \ - g_value_init(&gvalue, gtype); \ - gtype_set(&gvalue, (value)); \ - return nm_g_object_set_property((object), (property_name), &gvalue, (error)); \ - } \ - G_STMT_END - -gboolean -nm_g_object_set_property_string(GObject * object, - const char *property_name, - const char *value, - GError ** error) -{ - _set_property(object, property_name, G_TYPE_STRING, g_value_set_string, value, error); -} - -gboolean -nm_g_object_set_property_string_static(GObject * object, - const char *property_name, - const char *value, - GError ** error) -{ - _set_property(object, property_name, G_TYPE_STRING, g_value_set_static_string, value, error); -} - -gboolean -nm_g_object_set_property_string_take(GObject * object, - const char *property_name, - char * value, - GError ** error) -{ - _set_property(object, property_name, G_TYPE_STRING, g_value_take_string, value, error); -} - -gboolean -nm_g_object_set_property_boolean(GObject * object, - const char *property_name, - gboolean value, - GError ** error) -{ - _set_property(object, property_name, G_TYPE_BOOLEAN, g_value_set_boolean, !!value, error); -} - -gboolean -nm_g_object_set_property_char(GObject * object, - const char *property_name, - gint8 value, - GError ** error) -{ - /* glib says about G_TYPE_CHAR: - * - * The type designated by G_TYPE_CHAR is unconditionally an 8-bit signed integer. - * - * This is always a (signed!) char. */ - _set_property(object, property_name, G_TYPE_CHAR, g_value_set_schar, value, error); -} - -gboolean -nm_g_object_set_property_uchar(GObject * object, - const char *property_name, - guint8 value, - GError ** error) -{ - _set_property(object, property_name, G_TYPE_UCHAR, g_value_set_uchar, value, error); -} - -gboolean -nm_g_object_set_property_int(GObject *object, const char *property_name, int value, GError **error) -{ - _set_property(object, property_name, G_TYPE_INT, g_value_set_int, value, error); -} - -gboolean -nm_g_object_set_property_int64(GObject * object, - const char *property_name, - gint64 value, - GError ** error) -{ - _set_property(object, property_name, G_TYPE_INT64, g_value_set_int64, value, error); -} - -gboolean -nm_g_object_set_property_uint(GObject * object, - const char *property_name, - guint value, - GError ** error) -{ - _set_property(object, property_name, G_TYPE_UINT, g_value_set_uint, value, error); -} - -gboolean -nm_g_object_set_property_uint64(GObject * object, - const char *property_name, - guint64 value, - GError ** error) -{ - _set_property(object, property_name, G_TYPE_UINT64, g_value_set_uint64, value, error); -} - -gboolean -nm_g_object_set_property_flags(GObject * object, - const char *property_name, - GType gtype, - guint value, - GError ** error) -{ - nm_assert(({ - nm_auto_unref_gtypeclass GTypeClass *gtypeclass = g_type_class_ref(gtype); - G_IS_FLAGS_CLASS(gtypeclass); - })); - _set_property(object, property_name, gtype, g_value_set_flags, value, error); -} - -gboolean -nm_g_object_set_property_enum(GObject * object, - const char *property_name, - GType gtype, - int value, - GError ** error) -{ - nm_assert(({ - nm_auto_unref_gtypeclass GTypeClass *gtypeclass = g_type_class_ref(gtype); - G_IS_ENUM_CLASS(gtypeclass); - })); - _set_property(object, property_name, gtype, g_value_set_enum, value, error); -} - -GParamSpec * -nm_g_object_class_find_property_from_gtype(GType gtype, const char *property_name) -{ - nm_auto_unref_gtypeclass GObjectClass *gclass = NULL; - - gclass = g_type_class_ref(gtype); - return g_object_class_find_property(gclass, property_name); -} - -/*****************************************************************************/ - -/** - * nm_g_type_find_implementing_class_for_property: - * @gtype: the GObject type which has a property @pname - * @pname: the name of the property to look up - * - * This is only a helper function for printf debugging. It's not - * used in actual code. Hence, the function just asserts that - * @pname and @gtype arguments are suitable. It cannot fail. - * - * Returns: the most ancestor type of @gtype, that - * implements the property @pname. It means, it - * searches the type hierarchy to find the type - * that added @pname. - */ -GType -nm_g_type_find_implementing_class_for_property(GType gtype, const char *pname) -{ - nm_auto_unref_gtypeclass GObjectClass *klass = NULL; - GParamSpec * pspec; - - g_return_val_if_fail(pname, G_TYPE_INVALID); - - klass = g_type_class_ref(gtype); - g_return_val_if_fail(G_IS_OBJECT_CLASS(klass), G_TYPE_INVALID); - - pspec = g_object_class_find_property(klass, pname); - g_return_val_if_fail(pspec, G_TYPE_INVALID); - - gtype = G_TYPE_FROM_CLASS(klass); - - while (TRUE) { - nm_auto_unref_gtypeclass GObjectClass *k = NULL; - - k = g_type_class_ref(g_type_parent(gtype)); - - g_return_val_if_fail(G_IS_OBJECT_CLASS(k), G_TYPE_INVALID); - - if (g_object_class_find_property(k, pname) != pspec) - return gtype; - - gtype = G_TYPE_FROM_CLASS(k); - } -} - -/*****************************************************************************/ - -static void -_str_buf_append_c_escape_octal(NMStrBuf *strbuf, char ch) -{ - nm_str_buf_append_c4(strbuf, - '\\', - '0' + ((char) ((((guchar) ch) >> 6) & 07)), - '0' + ((char) ((((guchar) ch) >> 3) & 07)), - '0' + ((char) ((((guchar) ch)) & 07))); -} - -/** - * nm_utils_buf_utf8safe_unescape: - * @str: (allow-none): the string to unescape. The string itself is a NUL terminated - * ASCII string, that can have C-style backslash escape sequences (which - * are to be unescaped). Non-ASCII characters (e.g. UTF-8) are taken verbatim, so - * it doesn't care that this string is UTF-8. However, usually this is a UTF-8 encoded - * string. - * @flags: flags for unescaping. The following flags are supported. - * %NM_UTILS_STR_UTF8_SAFE_UNESCAPE_STRIP_SPACES performs a g_strstrip() on the input string, - * but preserving escaped spaces. For example, "a\\t " gives "a\t" (that is, the escaped space does - * not get stripped). Likewise, the invalid escape sequence "a\\ " results in "a " (stripping - * the unescaped space, but preserving the escaped one). - * @out_len: (out): the length of the parsed string. - * @to_free: (out): if @str requires unescaping, the function will clone the string. In - * that case, the allocated buffer will be returned here. - * - * See C-style escapes at https://en.wikipedia.org/wiki/Escape_sequences_in_C#Table_of_escape_sequences. - * Note that hex escapes ("\\xhh") and unicode escapes ("\\uhhhh", "\\Uhhhhhhhh") are not supported. - * - * Also, this function is very similar to g_strcompress() but without issuing g_warning() - * assertions and proper handling of "\\000" escape sequences. - * - * Invalid escape sequences (or non-UTF-8 input) are gracefully accepted. For example "\\ " - * is an invalid escape sequence, in this case the backslash is removed and " " gets returned. - * - * The function never leaks secrets in memory. - * - * Returns: the unescaped buffer of length @out_len. If @str is %NULL, this returns %NULL - * and sets @out_len to 0. Otherwise, a non-%NULL binary buffer is returned with - * @out_len bytes. Note that the binary buffer is guaranteed to be NUL terminated. That - * is @result[@out_len] is NUL. - * Note that the result is binary, and may have embedded NUL characters and non-UTF-8. - * If the function can avoid cloning the input string, it will return a pointer inside - * the input @str. For example, if there is no backslash, no cloning is necessary. In that - * case, @to_free will be %NULL. Otherwise, @to_free is set to a newly allocated buffer - * containing the unescaped string and returned. - */ -gconstpointer -nm_utils_buf_utf8safe_unescape(const char * str, - NMUtilsStrUtf8SafeFlags flags, - gsize * out_len, - gpointer * to_free) -{ - gboolean strip_spaces = NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_UNESCAPE_STRIP_SPACES); - NMStrBuf strbuf; - const char *s; - gsize len; - - g_return_val_if_fail(to_free, NULL); - g_return_val_if_fail(out_len, NULL); - - if (!str) { - *out_len = 0; - *to_free = NULL; - return NULL; - } - - if (strip_spaces) - str = nm_str_skip_leading_spaces(str); - - len = strlen(str); - - s = memchr(str, '\\', len); - if (!s) { - if (strip_spaces && len > 0 && g_ascii_isspace(str[len - 1])) { - len--; - while (len > 0 && g_ascii_isspace(str[len - 1])) - len--; - *out_len = len; - return (*to_free = g_strndup(str, len)); - } - *out_len = len; - *to_free = NULL; - return str; - } - - nm_str_buf_init(&strbuf, len + 1u, FALSE); - - nm_str_buf_append_len(&strbuf, str, s - str); - str = s; - - for (;;) { - char ch; - guint v; - - nm_assert(str[0] == '\\'); - - ch = (++str)[0]; - - if (ch == '\0') { - /* error. Trailing '\\' */ - break; - } - - if (ch >= '0' && ch <= '9') { - v = ch - '0'; - ch = (++str)[0]; - if (ch >= '0' && ch <= '7') { - 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; - } - } - ch = v; - } else { - switch (ch) { - case 'b': - ch = '\b'; - break; - case 'f': - ch = '\f'; - break; - case 'n': - ch = '\n'; - break; - case 'r': - ch = '\r'; - break; - case 't': - ch = '\t'; - break; - case 'v': - ch = '\v'; - break; - default: - /* Here we handle "\\\\", but all other unexpected escape sequences are really a bug. - * Take them literally, after removing the escape character */ - break; - } - str++; - } - - nm_str_buf_append_c(&strbuf, ch); - - s = strchr(str, '\\'); - if (!s) { - gsize l = strlen(str); - - if (strip_spaces) { - while (l > 0 && g_ascii_isspace(str[l - 1])) - l--; - } - nm_str_buf_append_len(&strbuf, str, l); - break; - } - - nm_str_buf_append_len(&strbuf, str, s - str); - str = s; - } - - /* assert that no reallocation was necessary. For one, unescaping should - * never result in a longer string than the input. Also, when unescaping - * secrets, we want to ensure that we don't leak secrets in memory. */ - nm_assert(strbuf.allocated == len + 1u); - - return (*to_free = nm_str_buf_finalize(&strbuf, out_len)); -} - -/** - * nm_utils_buf_utf8safe_escape: - * @buf: byte array, possibly in utf-8 encoding, may have NUL characters. - * @buflen: the length of @buf in bytes, or -1 if @buf is a NUL terminated - * string. - * @flags: #NMUtilsStrUtf8SafeFlags flags - * @to_free: (out): return the pointer location of the string - * if a copying was necessary. - * - * Based on the assumption, that @buf contains UTF-8 encoded bytes, - * this will return valid UTF-8 sequence, and invalid sequences - * will be escaped with backslash (C escaping, like g_strescape()). - * This is sanitize non UTF-8 characters. The result is valid - * UTF-8. - * - * The operation can be reverted with nm_utils_buf_utf8safe_unescape(). - * Note that if, and only if @buf contains no NUL bytes, the operation - * can also be reverted with g_strcompress(). - * - * Depending on @flags, valid UTF-8 characters are not escaped at all - * (except the escape character '\\'). This is the difference to g_strescape(), - * which escapes all non-ASCII characters. This allows to pass on - * valid UTF-8 characters as-is and can be directly shown to the user - * as UTF-8 -- with exception of the backslash escape character, - * invalid UTF-8 sequences, and other (depending on @flags). - * - * Returns: the escaped input buffer, as valid UTF-8. If no escaping - * is necessary, it returns the input @buf. Otherwise, an allocated - * string @to_free is returned which must be freed by the caller - * with g_free. The escaping can be reverted by g_strcompress(). - **/ -const char * -nm_utils_buf_utf8safe_escape(gconstpointer buf, - gssize buflen, - NMUtilsStrUtf8SafeFlags flags, - char ** to_free) -{ - const char *const str = buf; - const char * p = NULL; - const char * s; - gboolean nul_terminated = FALSE; - NMStrBuf strbuf; - - g_return_val_if_fail(to_free, NULL); - - *to_free = NULL; - - if (buflen == 0) - return NULL; - - if (buflen < 0) { - if (!str) - return NULL; - buflen = strlen(str); - if (buflen == 0) - return str; - nul_terminated = TRUE; - } - - if (g_utf8_validate(str, buflen, &p) && nul_terminated) { - /* note that g_utf8_validate() does not allow NUL character inside @str. Good. - * We can treat @str like a NUL terminated string. */ - if (!NM_STRCHAR_ANY( - str, - ch, - (ch == '\\' - || (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)))) - return str; - } - - nm_str_buf_init(&strbuf, buflen + 5, NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET)); - - s = str; - do { - buflen -= p - s; - nm_assert(buflen >= 0); - - for (; s < p; s++) { - char ch = s[0]; - - nm_assert(ch); - if (ch == '\\') - 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_buf_append_c_escape_octal(&strbuf, ch); - else - nm_str_buf_append_c(&strbuf, ch); - } - - if (buflen <= 0) - break; - - _str_buf_append_c_escape_octal(&strbuf, p[0]); - - buflen--; - if (buflen == 0) - break; - - s = &p[1]; - (void) g_utf8_validate(s, buflen, &p); - } while (TRUE); - - return (*to_free = nm_str_buf_finalize(&strbuf, NULL)); -} - -const char * -nm_utils_buf_utf8safe_escape_bytes(GBytes *bytes, NMUtilsStrUtf8SafeFlags flags, char **to_free) -{ - gconstpointer p; - gsize l; - - if (bytes) - p = g_bytes_get_data(bytes, &l); - else { - p = NULL; - l = 0; - } - - return nm_utils_buf_utf8safe_escape(p, l, flags, to_free); -} - -char * -nm_utils_buf_utf8safe_escape_cp(gconstpointer buf, gssize buflen, NMUtilsStrUtf8SafeFlags flags) -{ - const char *s_const; - char * s; - - s_const = nm_utils_buf_utf8safe_escape(buf, buflen, flags, &s); - nm_assert(!s || s == s_const); - return s ?: g_strdup(s_const); -} - -/*****************************************************************************/ - -const char * -nm_utils_str_utf8safe_unescape(const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free) -{ - const char *res; - gsize len; - - g_return_val_if_fail(to_free, NULL); - - res = nm_utils_buf_utf8safe_unescape(str, flags, &len, (gpointer *) to_free); - - nm_assert((!res && len == 0) || (strlen(res) <= len)); - - return res; -} - -/** - * nm_utils_str_utf8safe_escape: - * @str: NUL terminated input string, possibly in utf-8 encoding - * @flags: #NMUtilsStrUtf8SafeFlags flags - * @to_free: (out): return the pointer location of the string - * if a copying was necessary. - * - * Returns the possible non-UTF-8 NUL terminated string @str - * and uses backslash escaping (C escaping, like g_strescape()) - * to sanitize non UTF-8 characters. The result is valid - * UTF-8. - * - * The operation can be reverted with g_strcompress() or - * nm_utils_str_utf8safe_unescape(). - * - * Depending on @flags, valid UTF-8 characters are not escaped at all - * (except the escape character '\\'). This is the difference to g_strescape(), - * which escapes all non-ASCII characters. This allows to pass on - * valid UTF-8 characters as-is and can be directly shown to the user - * as UTF-8 -- with exception of the backslash escape character, - * invalid UTF-8 sequences, and other (depending on @flags). - * - * Returns: the escaped input string, as valid UTF-8. If no escaping - * is necessary, it returns the input @str. Otherwise, an allocated - * string @to_free is returned which must be freed by the caller - * with g_free. The escaping can be reverted by g_strcompress(). - **/ -const char * -nm_utils_str_utf8safe_escape(const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free) -{ - return nm_utils_buf_utf8safe_escape(str, -1, flags, to_free); -} - -/** - * nm_utils_str_utf8safe_escape_cp: - * @str: NUL terminated input string, possibly in utf-8 encoding - * @flags: #NMUtilsStrUtf8SafeFlags flags - * - * Like nm_utils_str_utf8safe_escape(), except the returned value - * is always a copy of the input and must be freed by the caller. - * - * Returns: the escaped input string in UTF-8 encoding. The returned - * value should be freed with g_free(). - * The escaping can be reverted by g_strcompress(). - **/ -char * -nm_utils_str_utf8safe_escape_cp(const char *str, NMUtilsStrUtf8SafeFlags flags) -{ - char *s; - - nm_utils_str_utf8safe_escape(str, flags, &s); - return s ?: g_strdup(str); -} - -char * -nm_utils_str_utf8safe_unescape_cp(const char *str, NMUtilsStrUtf8SafeFlags flags) -{ - char *s; - - str = nm_utils_str_utf8safe_unescape(str, flags, &s); - return s ?: g_strdup(str); -} - -char * -nm_utils_str_utf8safe_escape_take(char *str, NMUtilsStrUtf8SafeFlags flags) -{ - char *str_to_free; - - nm_utils_str_utf8safe_escape(str, flags, &str_to_free); - if (str_to_free) { - g_free(str); - return str_to_free; - } - return str; -} - -/*****************************************************************************/ - -/* 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_nsec) -{ - struct pollfd pollfd = { - .fd = fd, - .events = event, - }; - struct timespec ts, *pts; - int r; - - if (timeout_nsec < 0) - pts = NULL; - else { - 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; - } - - r = ppoll(&pollfd, 1, pts, NULL); - if (r < 0) - return -NM_ERRNO_NATIVE(errno); - if (r == 0) - return 0; - return pollfd.revents; -} - -/* taken from systemd's loop_read() */ -ssize_t -nm_utils_fd_read_loop(int fd, void *buf, size_t nbytes, bool do_poll) -{ - uint8_t *p = buf; - ssize_t n = 0; - - g_return_val_if_fail(fd >= 0, -EINVAL); - g_return_val_if_fail(buf, -EINVAL); - - /* If called with nbytes == 0, let's call read() at least - * once, to validate the operation */ - - if (nbytes > (size_t) SSIZE_MAX) - return -EINVAL; - - do { - ssize_t k; - - k = read(fd, p, nbytes); - if (k < 0) { - int errsv = errno; - - if (errsv == EINTR) - continue; - - if (errsv == EAGAIN && do_poll) { - /* We knowingly ignore any return value here, - * and expect that any error/EOF is reported - * via read() */ - - (void) nm_utils_fd_wait_for_event(fd, POLLIN, -1); - continue; - } - - return n > 0 ? n : -NM_ERRNO_NATIVE(errsv); - } - - if (k == 0) - return n; - - g_assert((size_t) k <= nbytes); - - p += k; - nbytes -= k; - n += k; - } while (nbytes > 0); - - return n; -} - -/* taken from systemd's loop_read_exact() */ -int -nm_utils_fd_read_loop_exact(int fd, void *buf, size_t nbytes, bool do_poll) -{ - ssize_t n; - - n = nm_utils_fd_read_loop(fd, buf, nbytes, do_poll); - if (n < 0) - return (int) n; - if ((size_t) n != nbytes) - return -EIO; - - return 0; -} - -/*****************************************************************************/ - -void -nm_utils_named_value_clear_with_g_free(NMUtilsNamedValue *val) -{ - if (val) { - gs_free gpointer x_name = NULL; - gs_free gpointer x_value = NULL; - - x_name = (gpointer) g_steal_pointer(&val->name); - x_value = g_steal_pointer(&val->value_ptr); - } -} - -G_STATIC_ASSERT(G_STRUCT_OFFSET(NMUtilsNamedValue, name) == 0); - -NMUtilsNamedValue * -nm_utils_named_values_from_strdict_full(GHashTable * hash, - guint * out_len, - GCompareDataFunc compare_func, - gpointer user_data, - NMUtilsNamedValue * provided_buffer, - guint provided_buffer_len, - NMUtilsNamedValue **out_allocated_buffer) -{ - GHashTableIter iter; - NMUtilsNamedValue *values; - guint i, len; - - nm_assert(provided_buffer_len == 0 || provided_buffer); - nm_assert(!out_allocated_buffer || !*out_allocated_buffer); - - if (!hash || !(len = g_hash_table_size(hash))) { - NM_SET_OUT(out_len, 0); - return NULL; - } - - if (provided_buffer_len >= len + 1) { - /* the buffer provided by the caller is large enough. Use it. */ - values = provided_buffer; - } else { - /* allocate a new buffer. */ - values = g_new(NMUtilsNamedValue, len + 1); - NM_SET_OUT(out_allocated_buffer, values); - } - - i = 0; - g_hash_table_iter_init(&iter, hash); - while (g_hash_table_iter_next(&iter, (gpointer *) &values[i].name, &values[i].value_ptr)) - i++; - nm_assert(i == len); - values[i].name = NULL; - values[i].value_ptr = NULL; - - if (compare_func) - nm_utils_named_value_list_sort(values, len, compare_func, user_data); - - NM_SET_OUT(out_len, len); - return values; -} - -gssize -nm_utils_named_value_list_find(const NMUtilsNamedValue *arr, - gsize len, - const char * name, - gboolean sorted) -{ - gsize i; - - nm_assert(name); - -#if NM_MORE_ASSERTS > 5 - { - for (i = 0; i < len; i++) { - const NMUtilsNamedValue *v = &arr[i]; - - nm_assert(v->name); - if (sorted && i > 0) - nm_assert(strcmp(arr[i - 1].name, v->name) < 0); - } - } - - nm_assert(!sorted || nm_utils_named_value_list_is_sorted(arr, len, FALSE, NULL, NULL)); -#endif - - if (sorted) { - return nm_utils_array_find_binary_search(arr, - sizeof(NMUtilsNamedValue), - len, - &name, - nm_strcmp_p_with_data, - NULL); - } - for (i = 0; i < len; i++) { - if (nm_streq(arr[i].name, name)) - return i; - } - return ~((gssize) len); -} - -gboolean -nm_utils_named_value_list_is_sorted(const NMUtilsNamedValue *arr, - gsize len, - gboolean accept_duplicates, - GCompareDataFunc compare_func, - gpointer user_data) -{ - gsize i; - int c_limit; - - if (len == 0) - return TRUE; - - g_return_val_if_fail(arr, FALSE); - - if (!compare_func) - compare_func = nm_strcmp_p_with_data; - - c_limit = accept_duplicates ? 0 : -1; - - for (i = 1; i < len; i++) { - int c; - - c = compare_func(&arr[i - 1], &arr[i], user_data); - if (c > c_limit) - return FALSE; - } - return TRUE; -} - -void -nm_utils_named_value_list_sort(NMUtilsNamedValue *arr, - gsize len, - GCompareDataFunc compare_func, - gpointer user_data) -{ - if (len == 0) - return; - - g_return_if_fail(arr); - - if (len == 1) - return; - - g_qsort_with_data(arr, - len, - sizeof(NMUtilsNamedValue), - compare_func ?: nm_strcmp_p_with_data, - user_data); -} - -/*****************************************************************************/ - -gpointer * -nm_utils_hash_keys_to_array(GHashTable * hash, - GCompareDataFunc compare_func, - gpointer user_data, - guint * out_len) -{ - guint len; - gpointer *keys; - - /* by convention, we never return an empty array. In that - * case, always %NULL. */ - if (!hash || g_hash_table_size(hash) == 0) { - NM_SET_OUT(out_len, 0); - return NULL; - } - - keys = g_hash_table_get_keys_as_array(hash, &len); - if (len > 1 && compare_func) { - g_qsort_with_data(keys, len, sizeof(gpointer), compare_func, user_data); - } - NM_SET_OUT(out_len, len); - return keys; -} - -gpointer * -nm_utils_hash_values_to_array(GHashTable * hash, - GCompareDataFunc compare_func, - gpointer user_data, - guint * out_len) -{ - GHashTableIter iter; - gpointer value; - gpointer * arr; - guint i, len; - - if (!hash || (len = g_hash_table_size(hash)) == 0u) { - NM_SET_OUT(out_len, 0); - return NULL; - } - - arr = g_new(gpointer, ((gsize) len) + 1); - i = 0; - g_hash_table_iter_init(&iter, hash); - while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &value)) - arr[i++] = value; - - nm_assert(i == len); - arr[len] = NULL; - - if (len > 1 && compare_func) { - g_qsort_with_data(arr, len, sizeof(gpointer), compare_func, user_data); - } - - NM_SET_OUT(out_len, len); - return arr; -} - -/*****************************************************************************/ - -/** - * nm_utils_hashtable_equal: - * @a: one #GHashTable - * @b: other #GHashTable - * @treat_null_as_empty: if %TRUE, when either @a or @b is %NULL, it is - * treated like an empty hash. It means, a %NULL hash will compare equal - * to an empty hash. - * @equal_func: the equality function, for comparing the values. - * If %NULL, the values are not compared. In that case, the function - * only checks, if both dictionaries have the same keys -- according - * to @b's key equality function. - * Note that the values of @a will be passed as first argument - * to @equal_func. - * - * Compares two hash tables, whether they have equal content. - * This only makes sense, if @a and @b have the same key types and - * the same key compare-function. - * - * Returns: %TRUE, if both dictionaries have the same content. - */ -gboolean -nm_utils_hashtable_equal(const GHashTable *a, - const GHashTable *b, - gboolean treat_null_as_empty, - GEqualFunc equal_func) -{ - guint n; - GHashTableIter iter; - gconstpointer key, v_a, v_b; - - if (a == b) - return TRUE; - if (!treat_null_as_empty) { - if (!a || !b) - return FALSE; - } - - n = a ? g_hash_table_size((GHashTable *) a) : 0; - if (n != (b ? g_hash_table_size((GHashTable *) b) : 0)) - return FALSE; - - if (n > 0) { - g_hash_table_iter_init(&iter, (GHashTable *) a); - while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &v_a)) { - if (!g_hash_table_lookup_extended((GHashTable *) b, key, NULL, (gpointer *) &v_b)) - return FALSE; - if (equal_func && !equal_func(v_a, v_b)) - return FALSE; - } - } - - return TRUE; -} - -static gboolean -_utils_hashtable_equal(GHashTable * hash_a, - GHashTable * hash_b, - GCompareDataFunc cmp_values, - gpointer user_data) -{ - GHashTableIter h; - gpointer a_key; - gpointer a_val; - gpointer b_val; - - nm_assert(hash_a); - nm_assert(hash_b); - nm_assert(hash_a != hash_b); - nm_assert(g_hash_table_size(hash_a) == g_hash_table_size(hash_b)); - - /* We rely on both hashes to have the same hash/equal function. Otherwise, we would have to iterate - * both hashes and check whether all keys/values are present in the respective other hash (which - * would be O(n^2), since we couldn't use the plain lookup function. That is not a useful thing - * for this function. */ - - g_hash_table_iter_init(&h, hash_a); - while (g_hash_table_iter_next(&h, &a_key, &a_val)) { - if (!g_hash_table_lookup_extended(hash_b, a_key, NULL, &b_val)) - return FALSE; - - if (!cmp_values) { - /* we accept %NULL compare function to indicate that we don't care about the key. */ - continue; - } - - if (cmp_values(a_val, b_val, user_data) != 0) - return FALSE; - } - - return TRUE; -} - -/** - * nm_utils_hashtable_cmp_equal: - * @a: (allow-none): the hash table or %NULL - * @b: (allow-none): the other hash table or %NULL - * @cmp_values: (allow-none): if %NULL, only the keys - * will be compared. Otherwise, this function is used to - * check whether all keys are equal. - * @user_data: the argument for @cmp_values. - * - * It is required that both @a and @b have the same hash and equals - * function. - * - * Returns: %TRUE, if both keys have the same keys and (if - * @cmp_values is given) all values are the same. - */ -gboolean -nm_utils_hashtable_cmp_equal(const GHashTable *a, - const GHashTable *b, - GCompareDataFunc cmp_values, - gpointer user_data) -{ - GHashTable *hash_a = (GHashTable *) a; - GHashTable *hash_b = (GHashTable *) b; - gboolean same; - guint size; - - if (hash_a == hash_b) - return TRUE; - - if (!hash_a || !hash_b) - return FALSE; - - size = g_hash_table_size(hash_a); - if (size != g_hash_table_size(hash_b)) - return FALSE; - - if (size == 0) - return TRUE; - - same = _utils_hashtable_equal(hash_a, hash_b, cmp_values, user_data); - -#if NM_MORE_ASSERTS > 5 - nm_assert(same == _utils_hashtable_equal(hash_b, hash_a, cmp_values, user_data)); -#endif - - return same; -} - -typedef struct { - gpointer key; - gpointer val; -} HashTableCmpData; - -typedef struct { - GCompareDataFunc cmp_keys; - gpointer user_data; -} HashTableUserData; - -static int -_hashtable_cmp_func(gconstpointer a, gconstpointer b, gpointer user_data) -{ - const HashTableUserData *d = user_data; - const HashTableCmpData * d_a = *((const HashTableCmpData *const *) a); - const HashTableCmpData * d_b = *((const HashTableCmpData *const *) b); - - NM_CMP_RETURN(d->cmp_keys(d_a, d_b, d->user_data)); - return 0; -} - -/** - * nm_utils_hashtable_cmp: - * @a: (allow-none): the hash to compare. May be %NULL. - * @b: (allow-none): the other hash to compare. May be %NULL. - * @do_fast_precheck: if %TRUE, assume that the hashes are equal - * and that it is worth calling nm_utils_hashtable_cmp_equal() first. - * That requires, that both hashes have the same equals function - * which is compatible with the @cmp_keys function. - * @cmp_keys: the compare function for keys. Usually, the hash/equal function - * of both hashes corresponds to this function. If you set @do_fast_precheck - * to false, then this is not a requirement. - * @cmp_values: (allow-none): if %NULL, only the keys are compared. - * Otherwise, the values must are also compared with this function. - * - * Both hashes must have keys/values of the same domain, so that - * they can be effectively compared with @cmp_keys and @cmp_values. - * - * %NULL hashes compare equal to %NULL, but not to empty hashes. - * - * Returns: 0 if both hashes are equal, or -1 or 1 if one of the hashes - * sorts before/after. - */ -int -nm_utils_hashtable_cmp(const GHashTable *a, - const GHashTable *b, - gboolean do_fast_precheck, - GCompareDataFunc cmp_keys, - GCompareDataFunc cmp_values, - gpointer user_data) -{ - GHashTable *hash_a = (GHashTable *) a; - GHashTable *hash_b = (GHashTable *) b; - gs_free HashTableCmpData *cmp_array_free = NULL; - HashTableCmpData * cmp_array_a; - HashTableCmpData * cmp_array_b; - GHashTableIter h; - gpointer i_key; - gpointer i_val; - gsize size2; - guint size; - guint i; - - nm_assert(cmp_keys); - - NM_CMP_SELF(hash_a, hash_b); - - size = g_hash_table_size(hash_a); - - NM_CMP_DIRECT(size, g_hash_table_size(hash_b)); - - if (size == 0) - return 0; - - if (do_fast_precheck) { - gboolean same; - - /* we expect that the hashes are equal and the caller ensures us that they - * use the same hash/equal functions. Do a fast path check first... - * - * It's unclear whether this is worth it. The full comparison is O(n*ln(n)), - * while the fast check (using the hash lookup) is O(n). But then, the pre-check - * makes additional requirements on the hash's hash/equal functions -- the - * full comparison does not make such requirements. */ - same = _utils_hashtable_equal(hash_a, hash_b, cmp_values, user_data); -#if NM_MORE_ASSERTS > 5 - nm_assert(same == _utils_hashtable_equal(hash_b, hash_a, cmp_values, user_data)); -#endif - if (same) - return 0; - } - - size2 = ((gsize) size) * 2u; - if (size2 > 600u / sizeof(HashTableCmpData)) { - cmp_array_free = g_new(HashTableCmpData, size2); - cmp_array_a = cmp_array_free; - } else - cmp_array_a = g_newa(HashTableCmpData, size2); - cmp_array_b = &cmp_array_a[size]; - - i = 0; - g_hash_table_iter_init(&h, hash_a); - while (g_hash_table_iter_next(&h, &i_key, &i_val)) { - nm_assert(i < size); - cmp_array_a[i++] = (HashTableCmpData){ - .key = i_key, - .val = i_val, - }; - } - nm_assert(i == size); - - i = 0; - g_hash_table_iter_init(&h, hash_b); - while (g_hash_table_iter_next(&h, &i_key, &i_val)) { - nm_assert(i < size); - cmp_array_b[i++] = (HashTableCmpData){ - .key = i_key, - .val = i_val, - }; - } - nm_assert(i == size); - - g_qsort_with_data(cmp_array_a, - size, - sizeof(HashTableCmpData), - _hashtable_cmp_func, - &((HashTableUserData){ - .cmp_keys = cmp_keys, - .user_data = user_data, - })); - - g_qsort_with_data(cmp_array_b, - size, - sizeof(HashTableCmpData), - _hashtable_cmp_func, - &((HashTableUserData){ - .cmp_keys = cmp_keys, - .user_data = user_data, - })); - - for (i = 0; i < size; i++) { - NM_CMP_RETURN(cmp_keys(cmp_array_a[i].key, cmp_array_b[i].key, user_data)); - } - - if (cmp_values) { - for (i = 0; i < size; i++) { - NM_CMP_RETURN(cmp_values(cmp_array_a[i].val, cmp_array_b[i].val, user_data)); - } - } - - /* the fast-precheck should have already told that the arrays are equal. */ - nm_assert(!do_fast_precheck); - - return 0; -} - -char ** -nm_utils_strv_make_deep_copied(const char **strv) -{ - gsize i; - - /* it takes a strv list, and copies each - * strings. Note that this updates @strv *in-place* - * and returns it. */ - - if (!strv) - return NULL; - for (i = 0; strv[i]; i++) - strv[i] = g_strdup(strv[i]); - - return (char **) strv; -} - -char ** -nm_utils_strv_make_deep_copied_n(const char **strv, gsize len) -{ - gsize i; - - /* it takes a strv array with len elements, and copies each - * strings. Note that this updates @strv *in-place* - * and returns it. */ - - if (!strv) - return NULL; - for (i = 0; i < len; i++) - strv[i] = g_strdup(strv[i]); - - return (char **) strv; -} - -/** - * @strv: the strv array to copy. It may be %NULL if @len - * is negative or zero (in which case %NULL will be returned). - * @len: the length of strings in @str. If negative, strv is assumed - * to be a NULL terminated array. - * @deep_copied: if %TRUE, clones the individual strings. In that case, - * the returned array must be freed with g_strfreev(). Otherwise, the - * strings themself are not copied. You must take care of who owns the - * strings yourself. - * - * Like g_strdupv(), with two differences: - * - * - accepts a @len parameter for non-null terminated strv array. - * - * - this never returns an empty strv array, but always %NULL if - * there are no strings. - * - * Note that if @len is non-negative, then it still must not - * contain any %NULL pointers within the first @len elements. - * Otherwise, you would leak elements if you try to free the - * array with g_strfreev(). Allowing that would be error prone. - * - * Returns: (transfer full): a clone of the strv array. Always - * %NULL terminated. Depending on @deep_copied, the strings are - * cloned or not. - */ -char ** -_nm_utils_strv_dup(const char *const *strv, gssize len, gboolean deep_copied) -{ - gsize i, l; - char **v; - - if (len < 0) - l = NM_PTRARRAY_LEN(strv); - else - l = len; - if (l == 0) { - /* this function never returns an empty strv array. If you - * need that, handle it yourself. */ - return NULL; - } - - v = g_new(char *, l + 1); - for (i = 0; i < l; i++) { - if (G_UNLIKELY(!strv[i])) { - /* NULL strings are not allowed. Clear the remainder of the array - * and return it (with assertion failure). */ - l++; - for (; i < l; i++) - v[i] = NULL; - g_return_val_if_reached(v); - } - - if (deep_copied) - v[i] = g_strdup(strv[i]); - else - v[i] = (char *) strv[i]; - } - v[l] = NULL; - return v; -} - -const char ** -_nm_utils_strv_dup_packed(const char *const *strv, gssize len) - -{ - gs_free gsize *str_len_free = NULL; - gsize * str_len; - const char ** result; - gsize mem_len; - gsize pre_len; - gsize len2; - char * sbuf; - gsize i; - - nm_assert(len >= -1); - - if (G_LIKELY(len < 0)) { - if (!strv || !strv[0]) { - /* This function never returns an empty strv array. If you need that, handle it - * yourself. */ - return NULL; - } - len2 = NM_PTRARRAY_LEN(strv); - } else { - if (len == 0) - return NULL; - len2 = len; - } - - if (len2 > 300u / sizeof(gsize)) { - str_len_free = g_new(gsize, len2); - str_len = str_len_free; - } else - str_len = g_newa(gsize, len2); - - mem_len = 0; - for (i = 0; i < len2; i++) { - gsize l; - - if (G_LIKELY(strv[i])) - l = strlen(strv[i]) + 1u; - else - l = 0; - str_len[i] = l; - mem_len += l; - } - - pre_len = sizeof(const char *) * (len2 + 1u); - - result = g_malloc(pre_len + mem_len); - sbuf = &(((char *) result)[pre_len]); - for (i = 0; i < len2; i++) { - gsize l; - - if (G_UNLIKELY(!strv[i])) { - /* Technically there is no problem with accepting NULL strings. But that - * does not really result in a strv array, and likely this only happens due - * to a bug. We want to catch such bugs by asserting. - * - * We clear the remainder of the buffer and fail with an assertion. */ - len2++; - for (; i < len2; i++) - result[i] = NULL; - g_return_val_if_reached(result); - } - - result[i] = sbuf; - - l = str_len[i]; - memcpy(sbuf, strv[i], l); - sbuf += l; - } - result[i] = NULL; - nm_assert(i == len2); - nm_assert(sbuf == (&((const char *) result)[pre_len]) + mem_len); - - return result; -} - -/*****************************************************************************/ - -gssize -nm_utils_ptrarray_find_binary_search(gconstpointer * list, - gsize len, - gconstpointer needle, - GCompareDataFunc cmpfcn, - gpointer user_data, - gssize * out_idx_first, - gssize * out_idx_last) -{ - gssize imin, imax, imid, i2min, i2max, i2mid; - int cmp; - - g_return_val_if_fail(list || !len, ~((gssize) 0)); - g_return_val_if_fail(cmpfcn, ~((gssize) 0)); - - imin = 0; - if (len > 0) { - imax = len - 1; - - while (imin <= imax) { - imid = imin + (imax - imin) / 2; - - cmp = cmpfcn(list[imid], needle, user_data); - if (cmp == 0) { - /* we found a matching entry at index imid. - * - * Does the caller request the first/last index as well (in case that - * there are multiple entries which compare equal). */ - - if (out_idx_first) { - i2min = imin; - i2max = imid + 1; - while (i2min <= i2max) { - i2mid = i2min + (i2max - i2min) / 2; - - cmp = cmpfcn(list[i2mid], needle, user_data); - if (cmp == 0) - i2max = i2mid - 1; - else { - nm_assert(cmp < 0); - i2min = i2mid + 1; - } - } - *out_idx_first = i2min; - } - if (out_idx_last) { - i2min = imid + 1; - i2max = imax; - while (i2min <= i2max) { - i2mid = i2min + (i2max - i2min) / 2; - - cmp = cmpfcn(list[i2mid], needle, user_data); - if (cmp == 0) - i2min = i2mid + 1; - else { - nm_assert(cmp > 0); - i2max = i2mid - 1; - } - } - *out_idx_last = i2min - 1; - } - return imid; - } - - if (cmp < 0) - imin = imid + 1; - else - imax = imid - 1; - } - } - - /* return the inverse of @imin. This is a negative number, but - * also is ~imin the position where the value should be inserted. */ - imin = ~imin; - NM_SET_OUT(out_idx_first, imin); - NM_SET_OUT(out_idx_last, imin); - return imin; -} - -/*****************************************************************************/ - -/** - * nm_utils_array_find_binary_search: - * @list: the list to search. It must be sorted according to @cmpfcn ordering. - * @elem_size: the size in bytes of each element in the list - * @len: the number of elements in @list - * @needle: the value that is searched - * @cmpfcn: the compare function. The elements @list are passed as first - * argument to @cmpfcn, while @needle is passed as second. Usually, the - * needle is the same data type as inside the list, however, that is - * not necessary, as long as @cmpfcn takes care to cast the two arguments - * accordingly. - * @user_data: optional argument passed to @cmpfcn - * - * Performs binary search for @needle in @list. On success, returns the - * (non-negative) index where the compare function found the searched element. - * On success, it returns a negative value. Note that the return negative value - * is the bitwise inverse of the position where the element should be inserted. - * - * If the list contains multiple matching elements, an arbitrary index is - * returned. - * - * Returns: the index to the element in the list, or the (negative, bitwise inverted) - * position where it should be. - */ -gssize -nm_utils_array_find_binary_search(gconstpointer list, - gsize elem_size, - gsize len, - gconstpointer needle, - GCompareDataFunc cmpfcn, - gpointer user_data) -{ - gssize imin, imax, imid; - int cmp; - - g_return_val_if_fail(list || !len, ~((gssize) 0)); - g_return_val_if_fail(cmpfcn, ~((gssize) 0)); - g_return_val_if_fail(elem_size > 0, ~((gssize) 0)); - - imin = 0; - if (len == 0) - return ~imin; - - imax = len - 1; - - while (imin <= imax) { - imid = imin + (imax - imin) / 2; - - cmp = cmpfcn(&((const char *) list)[elem_size * imid], needle, user_data); - if (cmp == 0) - return imid; - - if (cmp < 0) - imin = imid + 1; - else - imax = imid - 1; - } - - /* return the inverse of @imin. This is a negative number, but - * also is ~imin the position where the value should be inserted. */ - return ~imin; -} - -/*****************************************************************************/ - -/** - * nm_utils_get_start_time_for_pid: - * @pid: the process identifier - * @out_state: return the state character, like R, S, Z. See `man 5 proc`. - * @out_ppid: parent process id - * - * Originally copied from polkit source (src/polkit/polkitunixprocess.c) - * and adjusted. - * - * Returns: the timestamp when the process started (by parsing /proc/$PID/stat). - * If an error occurs (e.g. the process does not exist), 0 is returned. - * - * The returned start time counts since boot, in the unit HZ (with HZ usually being (1/100) seconds) - **/ -guint64 -nm_utils_get_start_time_for_pid(pid_t pid, char *out_state, pid_t *out_ppid) -{ - guint64 start_time; - char filename[256]; - gs_free char * contents = NULL; - size_t length; - gs_free const char **tokens = NULL; - char * p; - char state = ' '; - gint64 ppid = 0; - - start_time = 0; - contents = NULL; - - g_return_val_if_fail(pid > 0, 0); - - G_STATIC_ASSERT_EXPR(sizeof(GPid) >= sizeof(pid_t)); - - nm_sprintf_buf(filename, "/proc/%" G_PID_FORMAT "/stat", (GPid) pid); - - if (!g_file_get_contents(filename, &contents, &length, NULL)) - goto fail; - - /* start time is the token at index 19 after the '(process name)' entry - since only this - * field can contain the ')' character, search backwards for this to avoid malicious - * processes trying to fool us - */ - p = strrchr(contents, ')'); - if (!p) - goto fail; - p += 2; /* skip ') ' */ - if (p - contents >= (int) length) - goto fail; - - state = p[0]; - - tokens = nm_utils_strsplit_set(p, " "); - - if (NM_PTRARRAY_LEN(tokens) < 20) - goto fail; - - if (out_ppid) { - ppid = _nm_utils_ascii_str_to_int64(tokens[1], 10, 1, G_MAXINT, 0); - if (ppid == 0) - goto fail; - } - - start_time = _nm_utils_ascii_str_to_int64(tokens[19], 10, 1, G_MAXINT64, 0); - if (start_time == 0) - goto fail; - - NM_SET_OUT(out_state, state); - NM_SET_OUT(out_ppid, ppid); - return start_time; - -fail: - NM_SET_OUT(out_state, ' '); - NM_SET_OUT(out_ppid, 0); - return 0; -} - -/*****************************************************************************/ - -/** - * _nm_utils_strv_sort: - * @strv: pointer containing strings that will be sorted - * in-place, %NULL is allowed, unless @len indicates - * that there are more elements. - * @len: the number of elements in strv. If negative, - * strv must be a NULL terminated array and the length - * will be calculated first. If @len is a positive - * number, @strv is allowed to contain %NULL strings - * too. - * - * Ascending sort of the array @strv inplace, using plain strcmp() string - * comparison. - */ -void -_nm_utils_strv_sort(const char **strv, gssize len) -{ - GCompareDataFunc cmp; - gsize l; - - if (len < 0) { - l = NM_PTRARRAY_LEN(strv); - cmp = nm_strcmp_p_with_data; - } else { - l = len; - cmp = nm_strcmp0_p_with_data; - } - - if (l <= 1) - return; - - nm_assert(l <= (gsize) G_MAXINT); - - g_qsort_with_data(strv, l, sizeof(const char *), cmp, NULL); -} - -/** - * _nm_utils_strv_cmp_n: - * @strv1: a string array - * @len1: the length of @strv1, or -1 for NULL terminated array. - * @strv2: a string array - * @len2: the length of @strv2, or -1 for NULL terminated array. - * - * Note that - * - len == -1 && strv == NULL - * is treated like a %NULL argument and compares differently from - * other arrays. - * - * Note that an empty array can be represented as - * - len == -1 && strv && !strv[0] - * - len == 0 && !strv - * - len == 0 && strv - * These 3 forms all compare equal. - * It also means, if length is 0, then it is permissible for strv to be %NULL. - * - * The strv arrays may contain %NULL strings (if len is positive). - * - * Returns: 0 if the arrays are equal (using strcmp). - **/ -int -_nm_utils_strv_cmp_n(const char *const *strv1, gssize len1, const char *const *strv2, gssize len2) -{ - gsize n, n2; - - if (len1 < 0) { - if (!strv1) - return (len2 < 0 && !strv2) ? 0 : -1; - n = NM_PTRARRAY_LEN(strv1); - } else - n = len1; - - if (len2 < 0) { - if (!strv2) - return 1; - n2 = NM_PTRARRAY_LEN(strv2); - } else - n2 = len2; - - NM_CMP_DIRECT(n, n2); - for (; n > 0; n--, strv1++, strv2++) - NM_CMP_DIRECT_STRCMP0(*strv1, *strv2); - return 0; -} - -/*****************************************************************************/ - -/** - * nm_utils_g_slist_find_str: - * @list: the #GSList with NUL terminated strings to search - * @needle: the needle string to look for. - * - * Search the list for @needle and return the first found match - * (or %NULL if not found). Uses strcmp() for finding the first matching - * element. - * - * Returns: the #GSList element with @needle as string value or - * %NULL if not found. - */ -GSList * -nm_utils_g_slist_find_str(const GSList *list, const char *needle) -{ - nm_assert(needle); - - for (; list; list = list->next) { - nm_assert(list->data); - if (nm_streq(list->data, needle)) - return (GSList *) list; - } - return NULL; -} - -/** - * nm_utils_g_slist_strlist_cmp: - * @a: the left #GSList of strings - * @b: the right #GSList of strings to compare. - * - * Compares two string lists. The data elements are compared with - * strcmp(), allowing %NULL elements. - * - * Returns: 0, 1, or -1, depending on how the lists compare. - */ -int -nm_utils_g_slist_strlist_cmp(const GSList *a, const GSList *b) -{ - while (TRUE) { - if (!a) - return !b ? 0 : -1; - if (!b) - return 1; - NM_CMP_DIRECT_STRCMP0(a->data, b->data); - a = a->next; - b = b->next; - } -} - -char * -nm_utils_g_slist_strlist_join(const GSList *a, const char *separator) -{ - GString *str = NULL; - - if (!a) - return NULL; - - for (; a; a = a->next) { - if (!str) - str = g_string_new(NULL); - else - g_string_append(str, separator); - g_string_append(str, a->data); - } - return g_string_free(str, FALSE); -} - -/*****************************************************************************/ - -NMUtilsUserData * -_nm_utils_user_data_pack(int nargs, gconstpointer *args) -{ - int i; - gpointer *data; - - nm_assert(nargs > 0); - nm_assert(args); - - data = g_slice_alloc(((gsize) nargs) * sizeof(gconstpointer)); - for (i = 0; i < nargs; i++) - data[i] = (gpointer) args[i]; - return (NMUtilsUserData *) data; -} - -void -_nm_utils_user_data_unpack(NMUtilsUserData *user_data, int nargs, ...) -{ - gpointer *data = (gpointer *) user_data; - va_list ap; - int i; - - nm_assert(data); - nm_assert(nargs > 0); - - va_start(ap, nargs); - for (i = 0; i < nargs; i++) { - gpointer *dst; - - dst = va_arg(ap, gpointer *); - nm_assert(dst); - - *dst = data[i]; - } - va_end(ap); - - g_slice_free1(((gsize) nargs) * sizeof(gconstpointer), data); -} - -/*****************************************************************************/ - -typedef struct { - gpointer callback_user_data; - GCancellable * cancellable; - GSource * source; - NMUtilsInvokeOnIdleCallback callback; - gulong cancelled_id; -} InvokeOnIdleData; - -static gboolean -_nm_utils_invoke_on_idle_cb_idle(gpointer user_data) -{ - InvokeOnIdleData *data = user_data; - - nm_clear_g_signal_handler(data->cancellable, &data->cancelled_id); - - data->callback(data->callback_user_data, data->cancellable); - - nm_g_object_unref(data->cancellable); - g_source_destroy(data->source); - nm_g_slice_free(data); - return G_SOURCE_REMOVE; -} - -static void -_nm_utils_invoke_on_idle_cb_cancelled(GCancellable *cancellable, InvokeOnIdleData *data) -{ - /* on cancellation, we invoke the callback synchronously. */ - nm_clear_g_signal_handler(data->cancellable, &data->cancelled_id); - nm_clear_g_source_inst(&data->source); - data->callback(data->callback_user_data, data->cancellable); - nm_g_object_unref(data->cancellable); - nm_g_slice_free(data); -} - -static void -_nm_utils_invoke_on_idle_start(gboolean use_timeout, - guint timeout_msec, - GCancellable * cancellable, - NMUtilsInvokeOnIdleCallback callback, - gpointer callback_user_data) -{ - InvokeOnIdleData *data; - GSource * source; - - g_return_if_fail(callback); - - data = g_slice_new(InvokeOnIdleData); - *data = (InvokeOnIdleData){ - .callback = callback, - .callback_user_data = callback_user_data, - .cancellable = nm_g_object_ref(cancellable), - .cancelled_id = 0, - }; - - if (cancellable) { - if (g_cancellable_is_cancelled(cancellable)) { - /* the cancellable is already cancelled. We ignore the timeout - * and always schedule an idle action. */ - use_timeout = FALSE; - } else { - /* if we are passed a non-cancelled cancellable, we register to the "cancelled" - * signal an invoke the callback synchronously (from the signal handler). - * - * We don't do that, - * - if the cancellable is already cancelled (because we don't want to invoke - * the callback synchronously from the caller). - * - if we have no cancellable at hand. */ - data->cancelled_id = g_signal_connect(cancellable, - "cancelled", - G_CALLBACK(_nm_utils_invoke_on_idle_cb_cancelled), - data); - } - } - - if (use_timeout) { - source = nm_g_timeout_source_new(timeout_msec, - G_PRIORITY_DEFAULT, - _nm_utils_invoke_on_idle_cb_idle, - data, - NULL); - } else { - source = - nm_g_idle_source_new(G_PRIORITY_DEFAULT, _nm_utils_invoke_on_idle_cb_idle, data, NULL); - } - - /* use the current thread default context. */ - g_source_attach(source, g_main_context_get_thread_default()); - - data->source = source; -} - -void -nm_utils_invoke_on_idle(GCancellable * cancellable, - NMUtilsInvokeOnIdleCallback callback, - gpointer callback_user_data) -{ - _nm_utils_invoke_on_idle_start(FALSE, 0, cancellable, callback, callback_user_data); -} - -void -nm_utils_invoke_on_timeout(guint timeout_msec, - GCancellable * cancellable, - NMUtilsInvokeOnIdleCallback callback, - gpointer callback_user_data) -{ - _nm_utils_invoke_on_idle_start(TRUE, timeout_msec, cancellable, callback, callback_user_data); -} - -/*****************************************************************************/ - -int -nm_utils_getpagesize(void) -{ - static volatile int val = 0; - long l; - int v; - - v = g_atomic_int_get(&val); - - if (G_UNLIKELY(v == 0)) { - l = sysconf(_SC_PAGESIZE); - - g_return_val_if_fail(l > 0 && l < G_MAXINT, 4 * 1024); - - v = (int) l; - if (!g_atomic_int_compare_and_exchange(&val, 0, v)) { - v = g_atomic_int_get(&val); - g_return_val_if_fail(v > 0, 4 * 1024); - } - } - - nm_assert(v > 0); -#if NM_MORE_ASSERTS > 5 - nm_assert(v == getpagesize()); - nm_assert(v == sysconf(_SC_PAGESIZE)); -#endif - - return v; -} - -gboolean -nm_utils_memeqzero(gconstpointer data, gsize length) -{ - const unsigned char *p = data; - int len; - - /* Taken from https://github.com/rustyrussell/ccan/blob/9d2d2c49f053018724bcc6e37029da10b7c3d60d/ccan/mem/mem.c#L92, - * CC-0 licensed. */ - - /* Check first 16 bytes manually */ - for (len = 0; len < 16; len++) { - if (!length) - return TRUE; - if (*p) - return FALSE; - p++; - length--; - } - - /* Now we know that's zero, memcmp with self. */ - return memcmp(data, p, length) == 0; -} - -/** - * nm_utils_bin2hexstr_full: - * @addr: pointer of @length bytes. If @length is zero, this may - * also be %NULL. - * @length: number of bytes in @addr. May also be zero, in which - * case this will return an empty string. - * @delimiter: either '\0', otherwise the output string will have the - * given delimiter character between each two hex numbers. - * @upper_case: if TRUE, use upper case ASCII characters for hex. - * @out: if %NULL, the function will allocate a new buffer of - * either (@length*2+1) or (@length*3) bytes, depending on whether - * a @delimiter is specified. In that case, the allocated buffer will - * be returned and must be freed by the caller. - * If not %NULL, the buffer must already be preallocated and contain - * at least (@length*2+1) or (@length*3) bytes, depending on the delimiter. - * If @length is zero, then of course at least one byte will be allocated - * or @out (if given) must contain at least room for the trailing NUL byte. - * - * Returns: the binary value converted to a hex string. If @out is given, - * this always returns @out. If @out is %NULL, a newly allocated string - * is returned. This never returns %NULL, for buffers of length zero - * an empty string is returned. - */ -char * -nm_utils_bin2hexstr_full(gconstpointer addr, - gsize length, - char delimiter, - gboolean upper_case, - char * out) -{ - const guint8 *in = addr; - const char * LOOKUP = upper_case ? "0123456789ABCDEF" : "0123456789abcdef"; - char * out0; - - if (out) - out0 = out; - else { - out0 = out = - g_new(char, length == 0 ? 1u : (delimiter == '\0' ? length * 2u + 1u : length * 3u)); - } - - /* @out must contain at least @length*3 bytes if @delimiter is set, - * otherwise, @length*2+1. */ - - if (length > 0) { - nm_assert(in); - for (;;) { - const guint8 v = *in++; - - *out++ = LOOKUP[v >> 4]; - *out++ = LOOKUP[v & 0x0F]; - length--; - if (!length) - break; - if (delimiter) - *out++ = delimiter; - } - } - - *out = '\0'; - return out0; -} - -guint8 * -nm_utils_hexstr2bin_full(const char *hexstr, - gboolean allow_0x_prefix, - gboolean delimiter_required, - gboolean hexdigit_pairs_required, - const char *delimiter_candidates, - gsize required_len, - guint8 * buffer, - gsize buffer_len, - gsize * out_len) -{ - const char *in = hexstr; - guint8 * out = buffer; - gboolean delimiter_has = TRUE; - guint8 delimiter = '\0'; - gsize len; - - nm_assert(hexstr); - nm_assert(buffer); - nm_assert(required_len > 0 || out_len); - - if (allow_0x_prefix && in[0] == '0' && in[1] == 'x') - in += 2; - - while (TRUE) { - const guint8 d1 = in[0]; - guint8 d2; - int i1, i2; - - i1 = nm_utils_hexchar_to_int(d1); - if (i1 < 0) - goto fail; - - /* If there's no leading zero (ie "aa:b:cc") then fake it */ - d2 = in[1]; - if (d2 && (i2 = nm_utils_hexchar_to_int(d2)) >= 0) { - *out++ = (i1 << 4) + i2; - d2 = in[2]; - if (!d2) - break; - in += 2; - } else { - /* Fake leading zero */ - *out++ = i1; - if (!d2) { - if (!delimiter_has || hexdigit_pairs_required) { - /* when using no delimiter, there must be pairs of hex chars */ - goto fail; - } - break; - } else if (hexdigit_pairs_required) - goto fail; - in += 1; - } - - if (--buffer_len == 0) - goto fail; - - if (delimiter_has) { - if (d2 != delimiter) { - if (delimiter) - goto fail; - if (delimiter_candidates) { - while (delimiter_candidates[0]) { - if (delimiter_candidates++[0] == d2) - delimiter = d2; - } - } - if (!delimiter) { - if (delimiter_required) - goto fail; - delimiter_has = FALSE; - continue; - } - } - in++; - } - } - - len = out - buffer; - if (required_len == 0 || len == required_len) { - NM_SET_OUT(out_len, len); - return buffer; - } - -fail: - NM_SET_OUT(out_len, 0); - return NULL; -} - -guint8 * -nm_utils_hexstr2bin_alloc(const char *hexstr, - gboolean allow_0x_prefix, - gboolean delimiter_required, - const char *delimiter_candidates, - gsize required_len, - gsize * out_len) -{ - guint8 *buffer; - gsize buffer_len, len; - - if (G_UNLIKELY(!hexstr)) { - NM_SET_OUT(out_len, 0); - g_return_val_if_fail(hexstr, NULL); - } - - nm_assert(required_len > 0 || out_len); - - if (allow_0x_prefix && hexstr[0] == '0' && hexstr[1] == 'x') - hexstr += 2; - - if (!hexstr[0]) - goto fail; - - if (required_len > 0) - buffer_len = required_len; - else - buffer_len = strlen(hexstr) / 2 + 3; - - buffer = g_malloc(buffer_len); - - if (nm_utils_hexstr2bin_full(hexstr, - FALSE, - delimiter_required, - FALSE, - delimiter_candidates, - required_len, - buffer, - buffer_len, - &len)) { - NM_SET_OUT(out_len, len); - return buffer; - } - - g_free(buffer); - -fail: - NM_SET_OUT(out_len, 0); - return NULL; -} - -/*****************************************************************************/ - -GVariant * -nm_utils_gvariant_vardict_filter(GVariant *src, - gboolean (*filter_fcn)(const char *key, - GVariant * val, - char ** out_key, - GVariant ** out_val, - gpointer user_data), - gpointer user_data) -{ - GVariantIter iter; - GVariantBuilder builder; - const char * key; - GVariant * val; - - g_return_val_if_fail(src && g_variant_is_of_type(src, G_VARIANT_TYPE_VARDICT), NULL); - g_return_val_if_fail(filter_fcn, NULL); - - g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); - - g_variant_iter_init(&iter, src); - while (g_variant_iter_next(&iter, "{&sv}", &key, &val)) { - _nm_unused gs_unref_variant GVariant *val_free = val; - gs_free char * key2 = NULL; - gs_unref_variant GVariant *val2 = NULL; - - if (filter_fcn(key, val, &key2, &val2, user_data)) { - g_variant_builder_add(&builder, "{sv}", key2 ?: key, val2 ?: val); - } - } - - return g_variant_builder_end(&builder); -} - -static gboolean -_gvariant_vardict_filter_drop_one(const char *key, - GVariant * val, - char ** out_key, - GVariant ** out_val, - gpointer user_data) -{ - return !nm_streq(key, user_data); -} - -GVariant * -nm_utils_gvariant_vardict_filter_drop_one(GVariant *src, const char *key) -{ - return nm_utils_gvariant_vardict_filter(src, _gvariant_vardict_filter_drop_one, (gpointer) key); -} - -/*****************************************************************************/ - -static gboolean -debug_key_matches(const char *key, const char *token, guint length) -{ - /* may not call GLib functions: see note in g_parse_debug_string() */ - for (; length; length--, key++, token++) { - char k = (*key == '_') ? '-' : g_ascii_tolower(*key); - char t = (*token == '_') ? '-' : g_ascii_tolower(*token); - - if (k != t) - return FALSE; - } - - return *key == '\0'; -} - -/** - * nm_utils_parse_debug_string: - * @string: the string to parse - * @keys: the debug keys - * @nkeys: number of entries in @keys - * - * Similar to g_parse_debug_string(), but does not special - * case "help" or "all". - * - * Returns: the flags - */ -guint -nm_utils_parse_debug_string(const char *string, const GDebugKey *keys, guint nkeys) -{ - guint i; - guint result = 0; - const char *q; - - if (string == NULL) - return 0; - - while (*string) { - q = strpbrk(string, ":;, \t"); - if (!q) - q = string + strlen(string); - - for (i = 0; i < nkeys; i++) { - if (debug_key_matches(keys[i].key, string, q - string)) - result |= keys[i].value; - } - - string = q; - if (*string) - string++; - } - - return result; -} - -/*****************************************************************************/ - -GSource * -nm_g_idle_source_new(int priority, - GSourceFunc func, - gpointer user_data, - GDestroyNotify destroy_notify) -{ - GSource *source; - - source = g_idle_source_new(); - if (priority != G_PRIORITY_DEFAULT) - g_source_set_priority(source, priority); - g_source_set_callback(source, func, user_data, destroy_notify); - return source; -} - -GSource * -nm_g_timeout_source_new(guint timeout_msec, - int priority, - GSourceFunc func, - gpointer user_data, - GDestroyNotify destroy_notify) -{ - GSource *source; - - 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); - return source; -} - -GSource * -nm_g_timeout_source_new_seconds(guint timeout_sec, - int priority, - GSourceFunc func, - gpointer user_data, - GDestroyNotify destroy_notify) -{ - GSource *source; - - source = g_timeout_source_new_seconds(timeout_sec); - if (priority != G_PRIORITY_DEFAULT) - g_source_set_priority(source, priority); - g_source_set_callback(source, func, user_data, destroy_notify); - return source; -} - -GSource * -nm_g_unix_signal_source_new(int signum, - int priority, - GSourceFunc handler, - gpointer user_data, - GDestroyNotify notify) -{ - GSource *source; - - source = g_unix_signal_source_new(signum); - - if (priority != G_PRIORITY_DEFAULT) - g_source_set_priority(source, priority); - g_source_set_callback(source, handler, user_data, notify); - 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, ...) \ - G_STMT_START \ - { \ - if (FALSE) { \ - gint64 _ts = g_get_monotonic_time() / 100; \ - \ - g_printerr(">>>> [%" G_GINT64_FORMAT ".%05" G_GINT64_FORMAT "] [src:%p]: " fmt "\n", \ - _ts / 10000, \ - _ts % 10000, \ - (ctx_src), \ - ##__VA_ARGS__); \ - } \ - } \ - G_STMT_END - -typedef struct { - int fd; - guint events; - guint registered_events; - union { - int one; - int *many; - } idx; - gpointer tag; - bool stale : 1; - bool has_many_idx : 1; -} PollData; - -typedef struct { - GSource source; - GMainContext *context; - GHashTable * fds; - GPollFD * fds_arr; - guint fds_len; - int max_priority; - bool acquired : 1; -} CtxIntegSource; - -static void -_poll_data_free(gpointer user_data) -{ - PollData *poll_data = user_data; - - if (poll_data->has_many_idx) - g_free(poll_data->idx.many); - nm_g_slice_free(poll_data); -} - -static void -_ctx_integ_source_reacquire(CtxIntegSource *ctx_src) -{ - if (G_LIKELY(ctx_src->acquired && g_main_context_is_owner(ctx_src->context))) - return; - - /* the parent context now iterates on a different thread. - * We need to release and reacquire the inner context. */ - - if (ctx_src->acquired) - g_main_context_release(ctx_src->context); - - if (G_UNLIKELY(!g_main_context_acquire(ctx_src->context))) { - /* Nobody is supposed to reacquire the context while we use it. This is a bug - * of the user. */ - ctx_src->acquired = FALSE; - g_return_if_reached(); - } - ctx_src->acquired = TRUE; -} - -static gboolean -_ctx_integ_source_prepare(GSource *source, int *out_timeout) -{ - CtxIntegSource *ctx_src = ((CtxIntegSource *) source); - int max_priority; - int timeout = -1; - gboolean any_ready; - GHashTableIter h_iter; - PollData * poll_data; - gboolean fds_changed; - GPollFD new_fds_stack[300u / sizeof(GPollFD)]; - gs_free GPollFD *new_fds_heap = NULL; - GPollFD * new_fds; - guint new_fds_len; - guint new_fds_alloc; - guint i; - - _CTX_LOG("prepare..."); - - _ctx_integ_source_reacquire(ctx_src); - - any_ready = g_main_context_prepare(ctx_src->context, &max_priority); - - new_fds_alloc = NM_MAX(G_N_ELEMENTS(new_fds_stack), ctx_src->fds_len); - - if (new_fds_alloc > G_N_ELEMENTS(new_fds_stack)) { - new_fds_heap = g_new(GPollFD, new_fds_alloc); - new_fds = new_fds_heap; - } else - new_fds = new_fds_stack; - - for (;;) { - int l; - - nm_assert(new_fds_alloc <= (guint) G_MAXINT); - - l = g_main_context_query(ctx_src->context, - max_priority, - &timeout, - new_fds, - (int) new_fds_alloc); - nm_assert(l >= 0); - - new_fds_len = (guint) l; - - if (G_LIKELY(new_fds_len <= new_fds_alloc)) - break; - - new_fds_alloc = new_fds_len; - g_free(new_fds_heap); - new_fds_heap = g_new(GPollFD, new_fds_alloc); - new_fds = new_fds_heap; - } - - fds_changed = FALSE; - if (new_fds_len != ctx_src->fds_len) - fds_changed = TRUE; - else { - for (i = 0; i < new_fds_len; i++) { - if (new_fds[i].fd != ctx_src->fds_arr[i].fd - || new_fds[i].events != ctx_src->fds_arr[i].events) { - fds_changed = TRUE; - break; - } - } - } - - if (G_UNLIKELY(fds_changed)) { - g_free(ctx_src->fds_arr); - ctx_src->fds_len = new_fds_len; - if (G_LIKELY(new_fds == new_fds_stack) || new_fds_alloc != new_fds_len) - ctx_src->fds_arr = nm_memdup(new_fds, sizeof(*new_fds) * new_fds_len); - else - ctx_src->fds_arr = g_steal_pointer(&new_fds_heap); - - g_hash_table_iter_init(&h_iter, ctx_src->fds); - while (g_hash_table_iter_next(&h_iter, (gpointer *) &poll_data, NULL)) - poll_data->stale = TRUE; - - for (i = 0; i < ctx_src->fds_len; i++) { - const GPollFD *fd = &ctx_src->fds_arr[i]; - - poll_data = g_hash_table_lookup(ctx_src->fds, &fd->fd); - - if (G_UNLIKELY(!poll_data)) { - poll_data = g_slice_new(PollData); - *poll_data = (PollData){ - .fd = fd->fd, - .idx.one = i, - .has_many_idx = FALSE, - .events = fd->events, - .registered_events = 0, - .tag = NULL, - .stale = FALSE, - }; - g_hash_table_add(ctx_src->fds, poll_data); - nm_assert(poll_data == g_hash_table_lookup(ctx_src->fds, &fd->fd)); - continue; - } - - if (G_LIKELY(poll_data->stale)) { - if (poll_data->has_many_idx) { - g_free(poll_data->idx.many); - poll_data->has_many_idx = FALSE; - } - poll_data->events = fd->events; - poll_data->idx.one = i; - poll_data->stale = FALSE; - continue; - } - - /* How odd. We have duplicate FDs. In fact, currently g_main_context_query() always - * coalesces the FDs and this cannot happen. However, that is not documented behavior, - * so we should not rely on that. So we need to keep a list of indexes... */ - poll_data->events |= fd->events; - if (!poll_data->has_many_idx) { - int idx0; - - idx0 = poll_data->idx.one; - poll_data->has_many_idx = TRUE; - poll_data->idx.many = g_new(int, 4); - poll_data->idx.many[0] = 2; /* number allocated */ - poll_data->idx.many[1] = 2; /* number used */ - poll_data->idx.many[2] = idx0; - poll_data->idx.many[3] = i; - } else { - if (poll_data->idx.many[0] == poll_data->idx.many[1]) { - poll_data->idx.many[0] *= 2; - poll_data->idx.many = - g_realloc(poll_data->idx.many, sizeof(int) * (2 + poll_data->idx.many[0])); - } - poll_data->idx.many[2 + poll_data->idx.many[1]] = i; - poll_data->idx.many[1]++; - } - } - - g_hash_table_iter_init(&h_iter, ctx_src->fds); - while (g_hash_table_iter_next(&h_iter, (gpointer *) &poll_data, NULL)) { - if (poll_data->stale) { - nm_assert(poll_data->tag); - nm_assert(poll_data->events == poll_data->registered_events); - _CTX_LOG("prepare: remove poll fd=%d, events=0x%x", - poll_data->fd, - poll_data->events); - g_source_remove_unix_fd(&ctx_src->source, poll_data->tag); - g_hash_table_iter_remove(&h_iter); - continue; - } - if (!poll_data->tag) { - _CTX_LOG("prepare: add poll fd=%d, events=0x%x", poll_data->fd, poll_data->events); - poll_data->registered_events = poll_data->events; - poll_data->tag = g_source_add_unix_fd(&ctx_src->source, - poll_data->fd, - poll_data->registered_events); - continue; - } - if (poll_data->registered_events != poll_data->events) { - _CTX_LOG("prepare: update poll fd=%d, events=0x%x", - poll_data->fd, - poll_data->events); - poll_data->registered_events = poll_data->events; - g_source_modify_unix_fd(&ctx_src->source, - poll_data->tag, - poll_data->registered_events); - } - } - } - - NM_SET_OUT(out_timeout, timeout); - ctx_src->max_priority = max_priority; - - _CTX_LOG("prepare: done, any-ready=%d, timeout=%d, max-priority=%d", - any_ready, - timeout, - max_priority); - - /* we always need to poll, because we have some file descriptors. */ - return FALSE; -} - -static gboolean -_ctx_integ_source_check(GSource *source) -{ - CtxIntegSource *ctx_src = ((CtxIntegSource *) source); - GHashTableIter h_iter; - gboolean some_ready; - PollData * poll_data; - - nm_assert(ctx_src->context); - - _CTX_LOG("check"); - - _ctx_integ_source_reacquire(ctx_src); - - g_hash_table_iter_init(&h_iter, ctx_src->fds); - while (g_hash_table_iter_next(&h_iter, (gpointer *) &poll_data, NULL)) { - guint revents; - - revents = g_source_query_unix_fd(&ctx_src->source, poll_data->tag); - if (G_UNLIKELY(poll_data->has_many_idx)) { - int num = poll_data->idx.many[1]; - int *p_idx = &poll_data->idx.many[2]; - - for (; num > 0; num--, p_idx++) - ctx_src->fds_arr[*p_idx].revents = revents; - } else - ctx_src->fds_arr[poll_data->idx.one].revents = revents; - } - - nm_assert(ctx_src->fds_len <= (guint) G_MAXINT); - - some_ready = g_main_context_check(ctx_src->context, - ctx_src->max_priority, - ctx_src->fds_arr, - (int) ctx_src->fds_len); - - _CTX_LOG("check (some-ready=%d)...", some_ready); - - return some_ready; -} - -static gboolean -_ctx_integ_source_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) -{ - CtxIntegSource *ctx_src = ((CtxIntegSource *) source); - - nm_assert(ctx_src->context); - - _ctx_integ_source_reacquire(ctx_src); - - _CTX_LOG("dispatch"); - - g_main_context_dispatch(ctx_src->context); - - return G_SOURCE_CONTINUE; -} - -static void -_ctx_integ_source_finalize(GSource *source) -{ - CtxIntegSource *ctx_src = ((CtxIntegSource *) source); - GHashTableIter h_iter; - PollData * poll_data; - - g_return_if_fail(ctx_src->context); - - _CTX_LOG("finalize..."); - - g_hash_table_iter_init(&h_iter, ctx_src->fds); - while (g_hash_table_iter_next(&h_iter, (gpointer *) &poll_data, NULL)) { - nm_assert(poll_data->tag); - _CTX_LOG("prepare: remove poll fd=%d, events=0x%x", poll_data->fd, poll_data->events); - g_source_remove_unix_fd(&ctx_src->source, poll_data->tag); - g_hash_table_iter_remove(&h_iter); - } - - nm_clear_pointer(&ctx_src->fds, g_hash_table_unref); - nm_clear_g_free(&ctx_src->fds_arr); - ctx_src->fds_len = 0; - - if (ctx_src->acquired) { - ctx_src->acquired = FALSE; - g_main_context_release(ctx_src->context); - } - - nm_clear_pointer(&ctx_src->context, g_main_context_unref); -} - -static GSourceFuncs ctx_integ_source_funcs = { - .prepare = _ctx_integ_source_prepare, - .check = _ctx_integ_source_check, - .dispatch = _ctx_integ_source_dispatch, - .finalize = _ctx_integ_source_finalize, -}; - -/** - * nm_utils_g_main_context_create_integrate_source: - * @inner_context: the inner context that will be integrated to an - * outer #GMainContext. - * - * By integrating the inner context with an outer context, when iterating the outer - * context sources on the inner context will be dispatched. Note that while the - * created source exists, the @inner_context will be acquired. The user gets restricted - * what to do with the inner context. In particular while the inner context is integrated, - * the user should not acquire the inner context again or explicitly iterate it. What - * the user of course still can (and wants to) do is attaching new sources to the inner - * context. - * - * Note that GSource has a priority. While each context dispatches events based on - * their source's priorities, the outer context dispatches to the inner context - * only with one priority (the priority of the created source). That is, the sources - * from the two contexts are kept separate and are not sorted by their priorities. - * - * Returns: a newly created GSource that should be attached to the - * outer context. - */ -GSource * -nm_utils_g_main_context_create_integrate_source(GMainContext *inner_context) -{ - CtxIntegSource *ctx_src; - - g_return_val_if_fail(inner_context, NULL); - - if (!g_main_context_acquire(inner_context)) { - /* We require to acquire the context while it's integrated. We need to keep it acquired - * for the entire duration. - * - * This is also necessary because g_source_attach() only wakes up the context, if - * the context is currently acquired. */ - g_return_val_if_reached(NULL); - } - - ctx_src = (CtxIntegSource *) g_source_new(&ctx_integ_source_funcs, sizeof(CtxIntegSource)); - - g_source_set_name(&ctx_src->source, "ContextIntegrateSource"); - - ctx_src->context = g_main_context_ref(inner_context); - ctx_src->fds = g_hash_table_new_full(nm_pint_hash, nm_pint_equals, _poll_data_free, NULL); - ctx_src->fds_len = 0; - ctx_src->fds_arr = NULL; - ctx_src->acquired = TRUE; - ctx_src->max_priority = G_MAXINT; - - _CTX_LOG("create new integ-source for %p", inner_context); - - return &ctx_src->source; -} - -/*****************************************************************************/ - -void -nm_utils_ifname_cpy(char *dst, const char *name) -{ - int i; - - g_return_if_fail(dst); - g_return_if_fail(name && name[0]); - - nm_assert(nm_utils_ifname_valid_kernel(name, NULL)); - - /* ensures NUL padding of the entire IFNAMSIZ buffer. */ - - for (i = 0; i < (int) IFNAMSIZ && name[i] != '\0'; i++) - dst[i] = name[i]; - - nm_assert(name[i] == '\0'); - - for (; i < (int) IFNAMSIZ; i++) - dst[i] = '\0'; -} - -/*****************************************************************************/ - -gboolean -nm_utils_ifname_valid_kernel(const char *name, GError **error) -{ - int i; - - /* This function follows kernel's interface validation - * function dev_valid_name() in net/core/dev.c. - */ - - if (!name) { - g_set_error_literal(error, - NM_UTILS_ERROR, - NM_UTILS_ERROR_UNKNOWN, - _("interface name is missing")); - return FALSE; - } - - if (name[0] == '\0') { - g_set_error_literal(error, - NM_UTILS_ERROR, - NM_UTILS_ERROR_UNKNOWN, - _("interface name is too short")); - return FALSE; - } - - if (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) { - g_set_error_literal(error, - NM_UTILS_ERROR, - NM_UTILS_ERROR_UNKNOWN, - _("interface name is reserved")); - return FALSE; - } - - for (i = 0; i < IFNAMSIZ; i++) { - char ch = name[i]; - - if (ch == '\0') - return TRUE; - if (NM_IN_SET(ch, '/', ':') || g_ascii_isspace(ch)) { - g_set_error_literal(error, - NM_UTILS_ERROR, - NM_UTILS_ERROR_UNKNOWN, - _("interface name contains an invalid character")); - return FALSE; - } - } - - g_set_error_literal(error, - NM_UTILS_ERROR, - NM_UTILS_ERROR_UNKNOWN, - _("interface name is longer than 15 characters")); - 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) -{ - const char *ch; - - /* OVS actually accepts a wider range of chars (all printable UTF-8 chars), - * NetworkManager restricts this to ASCII char as it's a safer option for - * now since OVS is not well documented on this matter. - **/ - for (ch = name; *ch; ++ch) { - if (*ch == '\\' || *ch == '/' || !g_ascii_isgraph(*ch)) { - g_set_error_literal(error, - NM_UTILS_ERROR, - NM_UTILS_ERROR_UNKNOWN, - _("interface name must be alphanumerical with " - "no forward or backward slashes")); - return FALSE; - } - }; - return TRUE; -} - -gboolean -nm_utils_ifname_valid(const char *name, NMUtilsIfaceType type, GError **error) -{ - g_return_val_if_fail(!error || !(*error), FALSE); - - if (!name || !(name[0])) { - g_set_error_literal(error, - NM_UTILS_ERROR, - NM_UTILS_ERROR_UNKNOWN, - _("interface name must not be empty")); - return FALSE; - } - - if (!g_utf8_validate(name, -1, NULL)) { - g_set_error_literal(error, - NM_UTILS_ERROR, - NM_UTILS_ERROR_UNKNOWN, - _("interface name must be UTF-8 encoded")); - return FALSE; - } - - switch (type) { - case NMU_IFACE_KERNEL: - 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; - - nm_assert(available < G_MAXULONG); - - 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; - - if (l == 0) - return; - - 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; -} - -/*****************************************************************************/ - -/** - * nm_indirect_g_free: - * @arg: a pointer to a pointer that is to be freed. - * - * This does the same as nm_clear_g_free(arg) (g_clear_pointer (arg, g_free)). - * This is for example useful when you have a GArray with pointers and a - * clear function to free them. g_array_set_clear_func()'s destroy notify - * function gets a pointer to the array location, so we have to follow - * the first pointer. - */ -void -nm_indirect_g_free(gpointer arg) -{ - gpointer *p = arg; - - nm_clear_g_free(p); -} - -/*****************************************************************************/ - -static char * -attribute_escape(const char *src, char c1, char c2) -{ - char *ret, *dest; - - dest = ret = g_malloc(strlen(src) * 2 + 1); - - while (*src) { - if (*src == c1 || *src == c2 || *src == '\\') - *dest++ = '\\'; - *dest++ = *src++; - } - *dest++ = '\0'; - - return ret; -} - -void -_nm_utils_format_variant_attributes_full(GString * str, - const NMUtilsNamedValue * values, - guint num_values, - const NMVariantAttributeSpec *const *spec, - char attr_separator, - char key_value_separator) -{ - const NMVariantAttributeSpec *const *s; - const char * name, *value; - GVariant * variant; - char * escaped; - char buf[64]; - char sep = 0; - guint i; - - for (i = 0; i < num_values; i++) { - name = values[i].name; - variant = values[i].value_ptr; - value = NULL; - s = NULL; - - if (spec) { - for (s = spec; *s; s++) { - if (nm_streq0((*s)->name, name)) - break; - } - - if (!*s) - continue; - } - - if (g_variant_is_of_type(variant, G_VARIANT_TYPE_UINT32)) - value = nm_sprintf_buf(buf, "%u", g_variant_get_uint32(variant)); - else if (g_variant_is_of_type(variant, G_VARIANT_TYPE_INT32)) - value = nm_sprintf_buf(buf, "%d", (int) g_variant_get_int32(variant)); - else if (g_variant_is_of_type(variant, G_VARIANT_TYPE_UINT64)) - value = nm_sprintf_buf(buf, "%" G_GUINT64_FORMAT, g_variant_get_uint64(variant)); - else if (g_variant_is_of_type(variant, G_VARIANT_TYPE_BYTE)) - value = nm_sprintf_buf(buf, "%hhu", g_variant_get_byte(variant)); - else if (g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN)) - value = g_variant_get_boolean(variant) ? "true" : "false"; - else if (g_variant_is_of_type(variant, G_VARIANT_TYPE_STRING)) - value = g_variant_get_string(variant, NULL); - else if (g_variant_is_of_type(variant, G_VARIANT_TYPE_BYTESTRING)) { - /* FIXME: there is no guarantee that the byte array - * is valid UTF-8.*/ - value = g_variant_get_bytestring(variant); - } else - continue; - - if (sep) - g_string_append_c(str, sep); - - escaped = attribute_escape(name, attr_separator, key_value_separator); - g_string_append(str, escaped); - g_free(escaped); - - if (!s || !*s || !(*s)->no_value) { - g_string_append_c(str, key_value_separator); - - escaped = attribute_escape(value, attr_separator, key_value_separator); - g_string_append(str, escaped); - g_free(escaped); - } - - sep = attr_separator; - } -} - -char * -_nm_utils_format_variant_attributes(GHashTable * attributes, - const NMVariantAttributeSpec *const *spec, - char attr_separator, - char key_value_separator) -{ - gs_free NMUtilsNamedValue *values_free = NULL; - NMUtilsNamedValue values_prepared[20]; - const NMUtilsNamedValue * values; - GString * str = NULL; - guint len; - - g_return_val_if_fail(attr_separator, NULL); - g_return_val_if_fail(key_value_separator, NULL); - - if (!attributes) - return NULL; - - values = nm_utils_named_values_from_strdict(attributes, &len, values_prepared, &values_free); - if (len == 0) - return NULL; - - str = g_string_new(""); - _nm_utils_format_variant_attributes_full(str, - values, - len, - spec, - attr_separator, - key_value_separator); - return g_string_free(str, FALSE); -} - -/*****************************************************************************/ - -gboolean -nm_utils_is_localhost(const char *name) -{ - static const char *const NAMES[] = { - "localhost", - "localhost4", - "localhost6", - "localhost.localdomain", - "localhost4.localdomain4", - "localhost6.localdomain6", - }; - gsize name_len; - int i; - - if (!name) - return FALSE; - - /* This tries to identify local host and domain names - * described in RFC6761 plus the redhatism of localdomain. - * - * Similar to systemd's is_localhost(). */ - - name_len = strlen(name); - - if (name_len == 0) - return FALSE; - - if (name[name_len - 1] == '.') { - /* one trailing dot is fine. Hide it. */ - name_len--; - } - - for (i = 0; i < (int) G_N_ELEMENTS(NAMES); i++) { - const char *n = NAMES[i]; - gsize l = strlen(n); - gsize s; - - if (name_len < l) - continue; - - s = name_len - l; - - if (g_ascii_strncasecmp(&name[s], n, l) != 0) - continue; - - /* we accept the name if it is equal to one of the well-known names, - * or if it is some prefix, a '.' and the well-known name. */ - if (s == 0) - return TRUE; - if (name[s - 1] == '.') - return TRUE; - } - - return FALSE; -} - -gboolean -nm_utils_is_specific_hostname(const char *name) -{ - if (nm_str_is_empty(name)) - return FALSE; - - if (nm_streq(name, "(none)")) { - /* This is not a special hostname. Probably an artefact by somebody wrongly - * printing NULL. */ - return FALSE; - } - - if (nm_utils_is_localhost(name)) - return FALSE; - - /* FIXME: properly validate the hostname, like systemd's hostname_is_valid() */ - - return TRUE; -} - -/*****************************************************************************/ - -/* taken from systemd's uid_to_name(). */ -char * -nm_utils_uid_to_name(uid_t uid) -{ - gs_free char *buf_heap = NULL; - char buf_stack[4096]; - gsize bufsize; - char * buf; - - bufsize = sizeof(buf_stack); - buf = buf_stack; - - for (;;) { - struct passwd pwbuf; - struct passwd *pw = NULL; - int r; - - r = getpwuid_r(uid, &pwbuf, buf, bufsize, &pw); - if (r == 0 && pw) - return nm_strdup_not_empty(pw->pw_name); - - if (r != ERANGE) - return NULL; - - if (bufsize > G_MAXSIZE / 2u) - return NULL; - - bufsize *= 2u; - g_free(buf_heap); - buf_heap = g_malloc(bufsize); - buf = buf_heap; - } -} - -/* taken from systemd's nss_user_record_by_name() */ -gboolean -nm_utils_name_to_uid(const char *name, uid_t *out_uid) -{ - gs_free char *buf_heap = NULL; - char buf_stack[4096]; - gsize bufsize; - char * buf; - - if (!name) - return nm_assert_unreachable_val(FALSE); - - bufsize = sizeof(buf_stack); - buf = buf_stack; - - for (;;) { - struct passwd *result; - struct passwd pwd; - int r; - - r = getpwnam_r(name, &pwd, buf, bufsize, &result); - if (r == 0) { - if (!result) - return FALSE; - NM_SET_OUT(out_uid, pwd.pw_uid); - return TRUE; - } - - if (r != ERANGE) - return FALSE; - - if (bufsize > G_MAXSIZE / 2u) - return FALSE; - - bufsize *= 2u; - g_free(buf_heap); - buf_heap = g_malloc(bufsize); - buf = buf_heap; - } -} - -/*****************************************************************************/ - -typedef struct { - CList lst; - gpointer tls_data; - GDestroyNotify destroy_notify; -} TlsRegData; - -static pthread_key_t _tls_reg_key; - -static void -_tls_reg_destroy(gpointer data) -{ - CList * lst_head = data; - TlsRegData *entry; - - if (!lst_head) - return; - - /* For no strong reason are we destroying the elements in reverse - * order than they were added. It seems a bit more sensible (but shouldn't - * matter nor should you rely on that). */ - while ((entry = c_list_last_entry(lst_head, TlsRegData, lst))) { - c_list_unlink_stale(&entry->lst); - entry->destroy_notify(entry->tls_data); - nm_g_slice_free(entry); - } - - nm_g_slice_free(lst_head); -} - -static void -_tls_reg_make_key(void) -{ - if (pthread_key_create(&_tls_reg_key, _tls_reg_destroy) != 0) - g_return_if_reached(); -} - -/** - * nm_utils_thread_local_register_destroy: - * @tls_data: the thread local storage data that should be destroyed when the thread - * exits. This pointer will be "owned" by the current thread. There is no way - * to un-register the destruction. - * @destroy_notify: the free function that will be called when the thread exits. - * - * If _nm_tread_local storage is heap allocated it requires freeing the pointer - * when the thread exits. Use this function to register the pointer to be - * released. - * - * This function does not change errno. - */ -void -nm_utils_thread_local_register_destroy(gpointer tls_data, GDestroyNotify destroy_notify) -{ - NM_AUTO_PROTECT_ERRNO(errsv); - static pthread_once_t key_once = PTHREAD_ONCE_INIT; - CList * lst_head; - TlsRegData * entry; - - nm_assert(destroy_notify); - - if (pthread_once(&key_once, _tls_reg_make_key) != 0) - g_return_if_reached(); - - if ((lst_head = pthread_getspecific(_tls_reg_key)) == NULL) { - lst_head = g_slice_new(CList); - c_list_init(lst_head); - if (pthread_setspecific(_tls_reg_key, lst_head) != 0) - g_return_if_reached(); - } - - entry = g_slice_new(TlsRegData); - entry->tls_data = tls_data; - entry->destroy_notify = destroy_notify; - c_list_link_tail(lst_head, &entry->lst); -} |