/* SPDX-License-Identifier: LGPL-2.1+ */ /* * Copyright (C) 2016 Red Hat, Inc. */ #include "nm-default.h" #include "nm-shared-utils.h" #include #include #include #include #include #include #include #include #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; } /** * 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; } /** * _nm_utils_ip4_get_default_prefix: * @ip: an IPv4 address (in network byte order) * * When the Internet was originally set up, various ranges of IP addresses were * segmented into three network classes: A, B, and C. This function will return * a prefix that is associated with the IP address specified defining where it * falls in the predefined classes. * * Returns: the default class prefix for the given IP **/ /* The function is originally from ipcalc.c of Red Hat's initscripts. */ guint32 _nm_utils_ip4_get_default_prefix(guint32 ip) { if (((ntohl(ip) & 0xFF000000) >> 24) <= 127) return 8; /* Class A - 255.0.0.0 */ else if (((ntohl(ip) & 0xFF000000) >> 24) <= 191) return 16; /* Class B - 255.255.0.0 */ return 24; /* Class C - 255.255.255.0 */ } 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-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; int 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; int fds_allocated; int fds_len_old; gs_free GPollFD *fds_arr_old = NULL; GHashTableIter h_iter; PollData * poll_data; gboolean fds_changed; int i; _CTX_LOG("prepare..."); _ctx_integ_source_reacquire(ctx_src); any_ready = g_main_context_prepare(ctx_src->context, &max_priority); fds_arr_old = g_steal_pointer(&ctx_src->fds_arr); fds_len_old = ctx_src->fds_len; fds_allocated = NM_MAX(1, fds_len_old); /* there is at least the wakeup's FD */ ctx_src->fds_arr = g_new(GPollFD, fds_allocated); while ((ctx_src->fds_len = g_main_context_query(ctx_src->context, max_priority, &timeout, ctx_src->fds_arr, fds_allocated)) > fds_allocated) { fds_allocated = ctx_src->fds_len; g_free(ctx_src->fds_arr); ctx_src->fds_arr = g_new(GPollFD, fds_allocated); } fds_changed = FALSE; if (fds_len_old != ctx_src->fds_len) fds_changed = TRUE; else { for (i = 0; i < ctx_src->fds_len; i++) { if (fds_arr_old[i].fd != ctx_src->fds_arr[i].fd || fds_arr_old[i].events != ctx_src->fds_arr[i].events) { fds_changed = TRUE; break; } } } if (G_UNLIKELY(fds_changed)) { 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; } some_ready = g_main_context_check(ctx_src->context, ctx_src->max_priority, ctx_src->fds_arr, 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; } 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; } }