diff options
Diffstat (limited to 'shared/nm-utils/nm-shared-utils.c')
| -rw-r--r-- | shared/nm-utils/nm-shared-utils.c | 2741 |
1 files changed, 0 insertions, 2741 deletions
diff --git a/shared/nm-utils/nm-shared-utils.c b/shared/nm-utils/nm-shared-utils.c deleted file mode 100644 index 6a43c670..00000000 --- a/shared/nm-utils/nm-shared-utils.c +++ /dev/null @@ -1,2741 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ -/* NetworkManager -- Network link manager - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301 USA. - * - * (C) Copyright 2016 Red Hat, Inc. - */ - -#include "nm-default.h" - -#include "nm-shared-utils.h" - -#include <arpa/inet.h> -#include <poll.h> -#include <fcntl.h> -#include <sys/syscall.h> - -#include "nm-errno.h" - -/*****************************************************************************/ - -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; -} - -/*****************************************************************************/ - -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; -} - -/*****************************************************************************/ - -/** - * 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 idential - * 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); -} - -/*****************************************************************************/ - -/** - * 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(0xFFFFFFFF >> prefix) : 0xFFFFFFFF; -} - -/** - * _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); - } -} - -/*****************************************************************************/ - -gboolean -nm_utils_parse_inaddr_bin (int addr_family, - 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) - return FALSE; - - 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)]; - - if (!nm_utils_parse_inaddr_bin (addr_family, text, &addr_family, &addrbin)) - 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 = addrstr_free = g_strndup (text, slash - text); - 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; -} - -/*****************************************************************************/ - -/* _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; - - if (str) { - while (g_ascii_isspace (str[0])) - str++; - } - if (!str || !str[0]) { - errno = EINVAL; - return fallback; - } - - errno = 0; - v = g_ascii_strtoll (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; - } - - 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 = 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] == '-') { - /* I don't know why, but g_ascii_strtoull() accepts minus signs ("-2" gives 18446744073709551614). - * For "-0" that is OK, but otherwise not. */ - errno = ERANGE; - return fallback; - } - - return v; -} - -/*****************************************************************************/ - -/* 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_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 convertiable 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; -} - -/*****************************************************************************/ - -/** - * nm_utils_strsplit_set: - * @str: the string to split. - * @delimiters: the set of delimiters. If %NULL, defaults to " \t\n", - * like bash's $IFS. - * @allow_escaping: whether delimiters can be escaped by a backslash - * - * 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. - * - * Another difference from g_strsplit_set() is that this never returns - * empty words. Multiple delimiters are combined and treated as one. - * - * If @allow_escaping is %TRUE, delimiters prefixed by a backslash are - * not treated as a separator. Such delimiters and their escape - * character are copied to the current word without unescaping them. - * - * Returns: %NULL if @str is %NULL or contains only delimiters. - * Otherwise, a %NULL terminated strv array containing non-empty - * words, split at the delimiter characters (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(). - */ -const char ** -nm_utils_strsplit_set (const char *str, const char *delimiters, gboolean allow_escaping) -{ - const char **ptr, **ptr0; - gsize alloc_size, plen, i; - gsize str_len; - char *s0; - char *s; - guint8 delimiters_table[256]; - gboolean escaped = FALSE; - - if (!str) - return NULL; - - /* initialize lookup table for delimiter */ - if (!delimiters) - delimiters = " \t\n"; - memset (delimiters_table, 0, sizeof (delimiters_table)); - for (i = 0; delimiters[i]; i++) - delimiters_table[(guint8) delimiters[i]] = 1; - -#define _is_delimiter(ch, delimiters_table, allow_esc, esc) \ - ((delimiters_table)[(guint8) (ch)] != 0 && (!allow_esc || !esc)) - -#define next_char(p, esc) \ - G_STMT_START { \ - if (esc) \ - esc = FALSE; \ - else \ - esc = p[0] == '\\'; \ - p++; \ - } G_STMT_END - - /* skip initial delimiters, and return of the remaining string is - * empty. */ - while (_is_delimiter (str[0], delimiters_table, allow_escaping, escaped)) - next_char (str, escaped); - - if (!str[0]) - return NULL; - - str_len = strlen (str) + 1; - alloc_size = 8; - - /* we allocate the buffer larger, so to copy @str at the - * end of it as @s0. */ - ptr0 = g_malloc ((sizeof (const char *) * (alloc_size + 1)) + str_len); - s0 = (char *) &ptr0[alloc_size + 1]; - memcpy (s0, str, str_len); - - plen = 0; - s = s0; - ptr = ptr0; - - while (TRUE) { - if (plen >= alloc_size) { - const char **ptr_old = ptr; - - /* reallocate the buffer. Note that for now the string - * continues to be in ptr0/s0. We fix that at the end. */ - alloc_size *= 2; - ptr = g_malloc ((sizeof (const char *) * (alloc_size + 1)) + str_len); - memcpy (ptr, ptr_old, sizeof (const char *) * plen); - if (ptr_old != ptr0) - g_free (ptr_old); - } - - ptr[plen++] = s; - - nm_assert (s[0] && !_is_delimiter (s[0], delimiters_table, allow_escaping, escaped)); - - while (TRUE) { - next_char (s, escaped); - if (_is_delimiter (s[0], delimiters_table, allow_escaping, escaped)) - break; - if (s[0] == '\0') - goto done; - } - - s[0] = '\0'; - next_char (s, escaped); - while (_is_delimiter (s[0], delimiters_table, allow_escaping, escaped)) - next_char (s, escaped); - if (s[0] == '\0') - break; - } -done: - ptr[plen] = NULL; - - if (ptr != ptr0) { - /* we reallocated the buffer. We must copy over the - * string @s0 and adjust the pointers. */ - s = (char *) &ptr[alloc_size + 1]; - memcpy (s, s0, str_len); - for (i = 0; i < plen; i++) - ptr[i] = &s[ptr[i] - s0]; - g_free (ptr0); - } - - return ptr; -} - -/** - * 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) { - 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; -} - -/*****************************************************************************/ - -int -_nm_utils_ascii_str_to_bool (const char *str, - int default_value) -{ - gsize len; - char *s = NULL; - - if (!str) - return default_value; - - while (str[0] && g_ascii_isspace (str[0])) - str++; - - if (!str[0]) - return default_value; - - len = strlen (str); - if (g_ascii_isspace (str[len - 1])) { - s = g_strdup (str); - g_strchomp (s); - str = s; - } - - if (!g_ascii_strcasecmp (str, "true") || !g_ascii_strcasecmp (str, "yes") || !g_ascii_strcasecmp (str, "on") || !g_ascii_strcasecmp (str, "1")) - default_value = TRUE; - else if (!g_ascii_strcasecmp (str, "false") || !g_ascii_strcasecmp (str, "no") || !g_ascii_strcasecmp (str, "off") || !g_ascii_strcasecmp (str, "0")) - default_value = FALSE; - if (s) - g_free (s); - 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 (GError *error, - gboolean consider_is_disposing) -{ - if (error) { - if (error->domain == G_IO_ERROR) - return NM_IN_SET (error->code, G_IO_ERROR_CANCELLED); - if (consider_is_disposing) { - if (error->domain == NM_UTILS_ERROR) - return NM_IN_SET (error->code, NM_UTILS_ERROR_CANCELLED_DISPOSING); - } - } - 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_append_escape (GString *s, char ch) -{ - g_string_append_c (s, '\\'); - g_string_append_c (s, '0' + ((((guchar) ch) >> 6) & 07)); - g_string_append_c (s, '0' + ((((guchar) ch) >> 3) & 07)); - g_string_append_c (s, '0' + ( ((guchar) ch) & 07)); -} - -gconstpointer -nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_free) -{ - GString *gstr; - gsize len; - const char *s; - - 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; - } - - len = strlen (str); - - s = memchr (str, '\\', len); - if (!s) { - *out_len = len; - *to_free = NULL; - return str; - } - - gstr = g_string_new_len (NULL, len); - - g_string_append_len (gstr, 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') { - 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++; - } - - g_string_append_c (gstr, ch); - - s = strchr (str, '\\'); - if (!s) { - g_string_append (gstr, str); - break; - } - - g_string_append_len (gstr, str, s - str); - str = s; - } - - *out_len = gstr->len; - *to_free = gstr->str; - return g_string_free (gstr, FALSE); -} - -/** - * 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; - GString *gstr; - - 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; - } - - gstr = g_string_sized_new (buflen + 5); - - s = str; - do { - buflen -= p - s; - nm_assert (buflen >= 0); - - for (; s < p; s++) { - char ch = s[0]; - - if (ch == '\\') - g_string_append (gstr, "\\\\"); - else if ( ( NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL) \ - && ch < ' ') \ - || ( NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII) \ - && ((guchar) ch) >= 127)) - _str_append_escape (gstr, ch); - else - g_string_append_c (gstr, ch); - } - - if (buflen <= 0) - break; - - _str_append_escape (gstr, p[0]); - - buflen--; - if (buflen == 0) - break; - - s = &p[1]; - g_utf8_validate (s, buflen, &p); - } while (TRUE); - - *to_free = g_string_free (gstr, FALSE); - return *to_free; -} - -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); -} - -/*****************************************************************************/ - -const char * -nm_utils_str_utf8safe_unescape (const char *str, char **to_free) -{ - g_return_val_if_fail (to_free, NULL); - - if (!str || !strchr (str, '\\')) { - *to_free = NULL; - return str; - } - return (*to_free = g_strcompress (str)); -} - -/** - * 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) -{ - return str ? g_strcompress (str) : NULL; -} - -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_ns) -{ - struct pollfd pollfd = { - .fd = fd, - .events = event, - }; - struct timespec ts, *pts; - int r; - - if (timeout_ns < 0) - pts = NULL; - else { - ts.tv_sec = (time_t) (timeout_ns / NM_UTILS_NS_PER_SECOND); - ts.tv_nsec = (long int) (timeout_ns % NM_UTILS_NS_PER_SECOND); - 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; -} - -NMUtilsNamedValue * -nm_utils_named_values_from_str_dict (GHashTable *hash, guint *out_len) -{ - GHashTableIter iter; - NMUtilsNamedValue *values; - guint i, len; - - if ( !hash - || !(len = g_hash_table_size (hash))) { - NM_SET_OUT (out_len, 0); - return NULL; - } - - i = 0; - values = g_new (NMUtilsNamedValue, len + 1); - g_hash_table_iter_init (&iter, hash); - while (g_hash_table_iter_next (&iter, - (gpointer *) &values[i].name, - (gpointer *) &values[i].value_ptr)) - i++; - nm_assert (i == len); - values[i].name = NULL; - values[i].value_ptr = NULL; - - if (len > 1) { - g_qsort_with_data (values, len, sizeof (values[0]), - nm_utils_named_entry_cmp_with_data, NULL); - } - - NM_SET_OUT (out_len, len); - return values; -} - -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; -} - -char ** -nm_utils_strv_make_deep_copied (const char **strv) -{ - gsize i; - - /* it takes a strv dictionary, 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; -} - -/*****************************************************************************/ - -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_hash_table_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_hash_table_equal (const GHashTable *a, - const GHashTable *b, - gboolean treat_null_as_empty, - NMUtilsHashTableEqualFunc 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; -} - -/*****************************************************************************/ - -/** - * 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); - - nm_sprintf_buf (filename, "/proc/%"G_GUINT64_FORMAT"/stat", (guint64) 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, " ", FALSE); - - 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, all first @len elements in @strv must be - * non-NULL, valid strings. - * - * Ascending sort of the array @strv inplace, using plain strcmp() string - * comparison. - */ -void -_nm_utils_strv_sort (const char **strv, gssize len) -{ - gsize l; - - l = len < 0 ? (gsize) NM_PTRARRAY_LEN (strv) : (gsize) len; - - if (l <= 1) - return; - - nm_assert (l <= (gsize) G_MAXINT); - - g_qsort_with_data (strv, - l, - sizeof (const char *), - nm_strcmp_p_with_data, - NULL); -} - -/*****************************************************************************/ - -gpointer -_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 data; -} - -void -_nm_utils_user_data_unpack (gpointer user_data, int nargs, ...) -{ - gpointer *data = 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), user_data); -} - -/*****************************************************************************/ - -#define IS_SPACE(c) NM_IN_SET ((c), ' ', '\t') - -const char * -_nm_utils_escape_spaces (const char *str, char **to_free) -{ - const char *ptr = str; - char *ret, *r; - - *to_free = NULL; - - if (!str) - return NULL; - - while (TRUE) { - if (!*ptr) - return str; - if (IS_SPACE (*ptr)) - break; - ptr++; - } - - ptr = str; - ret = g_new (char, strlen (str) * 2 + 1); - r = ret; - *to_free = ret; - while (*ptr) { - if (IS_SPACE (*ptr)) - *r++ = '\\'; - *r++ = *ptr++; - } - *r = '\0'; - - return ret; -} - -char * -_nm_utils_unescape_spaces (char *str) -{ - guint i, j = 0; - - if (!str) - return NULL; - - for (i = 0; str[i]; i++) { - if (str[i] == '\\' && IS_SPACE (str[i+1])) - i++; - str[j++] = str[i]; - } - str[j] = '\0'; - - return str; -} - -#undef IS_SPACE - -/*****************************************************************************/ - -typedef struct { - gpointer callback_user_data; - GCancellable *cancellable; - NMUtilsInvokeOnIdleCallback callback; - gulong cancelled_id; - guint idle_id; -} InvokeOnIdleData; - -static gboolean -_nm_utils_invoke_on_idle_cb_idle (gpointer user_data) -{ - InvokeOnIdleData *data = user_data; - - data->idle_id = 0; - 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_slice_free (InvokeOnIdleData, 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 (&data->idle_id); - data->callback (data->callback_user_data, data->cancellable); - nm_g_object_unref (data->cancellable); - g_slice_free (InvokeOnIdleData, data); -} - -void -nm_utils_invoke_on_idle (NMUtilsInvokeOnIdleCallback callback, - gpointer callback_user_data, - GCancellable *cancellable) -{ - InvokeOnIdleData *data; - - g_return_if_fail (callback); - - data = g_slice_new (InvokeOnIdleData); - data->callback = callback; - data->callback_user_data = callback_user_data; - data->cancellable = nm_g_object_ref (cancellable); - if ( cancellable - && !g_cancellable_is_cancelled (cancellable)) { - /* 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); - } else - data->cancelled_id = 0; - data->idle_id = g_idle_add (_nm_utils_invoke_on_idle_cb_idle, 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. - * - * 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. - */ -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, delimiter == '\0' - ? length * 2 + 1 - : length * 3); - } - - /* @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, - 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) { - /* when using no delimiter, there must be pairs of hex chars */ - goto fail; - } - break; - } - 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; - - 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, - 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; -} |