diff options
| author | Michael Biebl <biebl@debian.org> | 2021-10-01 23:05:04 +0200 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2021-10-01 23:05:04 +0200 |
| commit | e74c568b07b50b97873fb4ee1d776dedefbd54d6 (patch) | |
| tree | 3469f17ea9af91f7ff169b890633bda68b0cf76e /src/libnm-glib-aux/nm-shared-utils.h | |
| parent | bfe522304da217296e2a61040f58e35ec5d6f3f2 (diff) | |
New upstream version 1.32.12 upstream/1.32.12
Diffstat (limited to 'src/libnm-glib-aux/nm-shared-utils.h')
| -rw-r--r-- | src/libnm-glib-aux/nm-shared-utils.h | 2995 |
1 files changed, 2995 insertions, 0 deletions
diff --git a/src/libnm-glib-aux/nm-shared-utils.h b/src/libnm-glib-aux/nm-shared-utils.h new file mode 100644 index 00000000..dcf37cd3 --- /dev/null +++ b/src/libnm-glib-aux/nm-shared-utils.h @@ -0,0 +1,2995 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2016 Red Hat, Inc. + */ + +#ifndef __NM_SHARED_UTILS_H__ +#define __NM_SHARED_UTILS_H__ + +#include <netinet/in.h> + +/*****************************************************************************/ + +/* An optional boolean (like NMTernary, with identical numerical + * enum values). Note that this enum type is _nm_packed! */ +typedef enum _nm_packed { + NM_OPTION_BOOL_DEFAULT = -1, + NM_OPTION_BOOL_FALSE = 0, + NM_OPTION_BOOL_TRUE = 1, +} NMOptionBool; + +#define nm_assert_is_ternary(value) nm_assert(NM_IN_SET((value), -1, 0, 1)) + +/*****************************************************************************/ + +static inline gboolean +nm_is_ascii(char ch) +{ + return ((uint8_t) ch) < 128; +} + +/*****************************************************************************/ + +pid_t nm_utils_gettid(void); + +gboolean _nm_assert_on_main_thread(void); + +#if NM_MORE_ASSERTS > 5 + #define NM_ASSERT_ON_MAIN_THREAD() \ + G_STMT_START \ + { \ + nm_assert(_nm_assert_on_main_thread()); \ + } \ + G_STMT_END +#else + #define NM_ASSERT_ON_MAIN_THREAD() \ + G_STMT_START \ + { \ + ; \ + } \ + G_STMT_END +#endif + +/*****************************************************************************/ + +static inline gboolean +_NM_INT_NOT_NEGATIVE(gssize val) +{ + /* whether an enum (without negative values) is a signed int, depends on compiler options + * and compiler implementation. + * + * When using such an enum for accessing an array, one naturally wants to check + * that the enum is not negative. However, the compiler doesn't like a plain + * comparison "enum_val >= 0", because (if the enum is unsigned), it will warn + * that the expression is always true *duh*. Not even a cast to a signed + * type helps to avoid the compiler warning in any case. + * + * The sole purpose of this function is to avoid a compiler warning, when checking + * that an enum is not negative. */ + return val >= 0; +} + +/* check whether the integer value is smaller than G_MAXINT32. This macro exists + * for the sole purpose, that a plain "((int) value <= G_MAXINT32)" comparison + * may cause the compiler or coverity that this check is always TRUE. But the + * check depends on compile time and the size of C type "int". Of course, most + * of the time in is gint32 and an int value is always <= G_MAXINT32. The check + * exists to catch cases where that is not true. + * + * Together with the G_STATIC_ASSERT(), we make sure that this is always satisfied. */ +G_STATIC_ASSERT(sizeof(int) == sizeof(gint32)); +#if _NM_CC_SUPPORT_GENERIC + #define _NM_INT_LE_MAXINT32(value) \ + ({ \ + _nm_unused typeof(value) _value = (value); \ + \ + _Generic((value), int : TRUE); \ + }) +#else + #define _NM_INT_LE_MAXINT32(value) \ + ({ \ + _nm_unused typeof(value) _value = (value); \ + _nm_unused const int * _p_value = &_value; \ + \ + TRUE; \ + }) +#endif + +/*****************************************************************************/ + +typedef enum { + + /* No type, used as error value */ + NM_LINK_TYPE_NONE, + + NM_LINK_TYPE_UNKNOWN, + + NM_LINK_TYPE_ANY, + +#define _NM_LINK_TYPE_REAL_FIRST NM_LINK_TYPE_ETHERNET + +/* Hardware types */ +#define _NM_LINK_TYPE_HW_FIRST NM_LINK_TYPE_ETHERNET + NM_LINK_TYPE_ETHERNET, + NM_LINK_TYPE_INFINIBAND, + NM_LINK_TYPE_OLPC_MESH, + NM_LINK_TYPE_WIFI, + NM_LINK_TYPE_WWAN_NET, /* WWAN kernel netdevice */ + NM_LINK_TYPE_WIMAX, + NM_LINK_TYPE_WPAN, + NM_LINK_TYPE_6LOWPAN, + NM_LINK_TYPE_WIFI_P2P, +#define _NM_LINK_TYPE_HW_LAST NM_LINK_TYPE_WIFI_P2P + +/* Software types */ +#define _NM_LINK_TYPE_SW_FIRST NM_LINK_TYPE_BNEP + NM_LINK_TYPE_BNEP, /* Bluetooth Ethernet emulation */ + NM_LINK_TYPE_DUMMY, + NM_LINK_TYPE_GRE, + NM_LINK_TYPE_GRETAP, + NM_LINK_TYPE_IFB, + NM_LINK_TYPE_IP6TNL, + NM_LINK_TYPE_IP6GRE, + NM_LINK_TYPE_IP6GRETAP, + NM_LINK_TYPE_IPIP, + NM_LINK_TYPE_LOOPBACK, + NM_LINK_TYPE_MACSEC, + NM_LINK_TYPE_MACVLAN, + NM_LINK_TYPE_MACVTAP, + NM_LINK_TYPE_OPENVSWITCH, + NM_LINK_TYPE_PPP, + NM_LINK_TYPE_SIT, + NM_LINK_TYPE_TUN, + NM_LINK_TYPE_VETH, + NM_LINK_TYPE_VLAN, + NM_LINK_TYPE_VRF, + NM_LINK_TYPE_VXLAN, + NM_LINK_TYPE_WIREGUARD, +#define _NM_LINK_TYPE_SW_LAST NM_LINK_TYPE_WIREGUARD + +/* Software types with slaves */ +#define _NM_LINK_TYPE_SW_MASTER_FIRST NM_LINK_TYPE_BRIDGE + NM_LINK_TYPE_BRIDGE, + NM_LINK_TYPE_BOND, + NM_LINK_TYPE_TEAM, +#define _NM_LINK_TYPE_SW_MASTER_LAST NM_LINK_TYPE_TEAM + +#define _NM_LINK_TYPE_REAL_LAST NM_LINK_TYPE_TEAM + +#define _NM_LINK_TYPE_REAL_NUM ((int) (_NM_LINK_TYPE_REAL_LAST - _NM_LINK_TYPE_REAL_FIRST + 1)) + +} NMLinkType; + +static inline gboolean +nm_link_type_is_software(NMLinkType link_type) +{ + G_STATIC_ASSERT(_NM_LINK_TYPE_SW_LAST + 1 == _NM_LINK_TYPE_SW_MASTER_FIRST); + + return link_type >= _NM_LINK_TYPE_SW_FIRST && link_type <= _NM_LINK_TYPE_SW_MASTER_LAST; +} + +static inline gboolean +nm_link_type_supports_slaves(NMLinkType link_type) +{ + return link_type >= _NM_LINK_TYPE_SW_MASTER_FIRST && link_type <= _NM_LINK_TYPE_SW_MASTER_LAST; +} + +/*****************************************************************************/ + +gboolean _nm_utils_inet6_is_token(const struct in6_addr *in6addr); + +typedef struct { + guint8 ether_addr_octet[6 /*ETH_ALEN*/]; +} NMEtherAddr; + +#define NM_ETHER_ADDR_FORMAT_STR "%02X:%02X:%02X:%02X:%02X:%02X" + +#define NM_ETHER_ADDR_FORMAT_VAL(x) \ + (x)->ether_addr_octet[0], (x)->ether_addr_octet[1], (x)->ether_addr_octet[2], \ + (x)->ether_addr_octet[3], (x)->ether_addr_octet[4], (x)->ether_addr_octet[5] + +#define _NM_ETHER_ADDR_INIT(a0, a1, a2, a3, a4, a5) \ + { \ + .ether_addr_octet = { \ + (a0), \ + (a1), \ + (a2), \ + (a3), \ + (a4), \ + (a5), \ + }, \ + } + +#define NM_ETHER_ADDR_INIT(...) ((NMEtherAddr) _NM_ETHER_ADDR_INIT(__VA_ARGS__)) + +static inline int +nm_ether_addr_cmp(const NMEtherAddr *a, const NMEtherAddr *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_DIRECT_MEMCMP(a, b, sizeof(NMEtherAddr)); + return 0; +} + +static inline gboolean +nm_ether_addr_equal(const NMEtherAddr *a, const NMEtherAddr *b) +{ + return nm_ether_addr_cmp(a, b) == 0; +} + +/*****************************************************************************/ + +typedef struct { + union { + guint8 addr_ptr[1]; + in_addr_t addr4; + struct in_addr addr4_struct; + struct in6_addr addr6; + + /* NMIPAddr is really a union for IP addresses. + * However, as ethernet addresses fit in here nicely, use + * it also for an ethernet MAC address. */ + guint8 ether_addr_octet[6 /*ETH_ALEN*/]; + NMEtherAddr ether_addr; + + guint8 array[sizeof(struct in6_addr)]; + }; +} NMIPAddr; + +#define NM_IP_ADDR_INIT \ + { \ + .array = { 0 } \ + } + +extern const NMIPAddr nm_ip_addr_zero; + +#define nm_ether_addr_zero (nm_ip_addr_zero.ether_addr) + +static inline int +nm_ip_addr_cmp(int addr_family, gconstpointer a, gconstpointer b) +{ + nm_assert_addr_family(addr_family); + nm_assert(a); + nm_assert(b); + + return memcmp(a, b, nm_utils_addr_family_to_size(addr_family)); +} + +static inline gboolean +nm_ip_addr_equal(int addr_family, gconstpointer a, gconstpointer b) +{ + return nm_ip_addr_cmp(addr_family, a, b) == 0; +} + +static inline gboolean +nm_ip_addr_is_null(int addr_family, gconstpointer addr) +{ + nm_assert(addr); + if (addr_family == AF_INET6) + return IN6_IS_ADDR_UNSPECIFIED((const struct in6_addr *) addr); + nm_assert(addr_family == AF_INET); + return ((const struct in_addr *) addr)->s_addr == 0; +} + +static inline void +nm_ip_addr_set(int addr_family, gpointer dst, gconstpointer src) +{ + nm_assert_addr_family(addr_family); + nm_assert(dst); + nm_assert(src); + + memcpy(dst, src, NM_IS_IPv4(addr_family) ? sizeof(in_addr_t) : sizeof(struct in6_addr)); +} + +static inline NMIPAddr +nm_ip_addr_init(int addr_family, gconstpointer src) +{ + NMIPAddr a; + + nm_assert_addr_family(addr_family); + nm_assert(src); + + G_STATIC_ASSERT_EXPR(sizeof(NMIPAddr) == sizeof(struct in6_addr)); + + if (NM_IS_IPv4(addr_family)) { + memcpy(&a, src, sizeof(in_addr_t)); + + /* ensure all bytes of the union are initialized. If only to make + * valgrind happy. */ + memset(&a.array[sizeof(in_addr_t)], 0, sizeof(a) - sizeof(in_addr_t)); + } else + memcpy(&a, src, sizeof(struct in6_addr)); + + return a; +} + +gboolean nm_ip_addr_set_from_untrusted(int addr_family, + gpointer dst, + gconstpointer src, + gsize src_len, + int * out_addr_family); + +static inline gboolean +nm_ip4_addr_is_localhost(in_addr_t addr4) +{ + return (addr4 & htonl(0xFF000000u)) == htonl(0x7F000000u); +} + +static inline gconstpointer +nm_ip_addr_from_packed_array(int addr_family, gconstpointer ipaddr_arr, gsize idx) +{ + return NM_IS_IPv4(addr_family) + ? ((gconstpointer) & (((const struct in_addr *) ipaddr_arr)[idx])) + : ((gconstpointer) & (((const struct in6_addr *) ipaddr_arr)[idx])); +} + +/*****************************************************************************/ + +struct ether_addr; + +static inline int +nm_utils_ether_addr_cmp(const struct ether_addr *a1, const struct ether_addr *a2) +{ + nm_assert(a1); + nm_assert(a2); + return memcmp(a1, a2, 6 /*ETH_ALEN*/); +} + +static inline gboolean +nm_utils_ether_addr_equal(const struct ether_addr *a1, const struct ether_addr *a2) +{ + return nm_utils_ether_addr_cmp(a1, a2) == 0; +} + +/*****************************************************************************/ + +/** + * NMUtilsIPv6IfaceId: + * @id: convenience member for validity checking; never use directly + * @id_u8: the 64-bit Interface Identifier + * + * Holds a 64-bit IPv6 Interface Identifier. The IID is a sequence of bytes + * and should not normally be treated as a %guint64, but this is done for + * convenience of validity checking and initialization. + */ +typedef struct _NMUtilsIPv6IfaceId { + union { + guint64 id; + guint8 id_u8[8]; + }; +} NMUtilsIPv6IfaceId; + +#define NM_UTILS_IPV6_IFACE_ID_INIT \ + { \ + { \ + .id = 0 \ + } \ + } + +void nm_utils_ipv6_addr_set_interface_identifier(struct in6_addr * addr, + const NMUtilsIPv6IfaceId iid); + +void nm_utils_ipv6_interface_identifier_get_from_addr(NMUtilsIPv6IfaceId * iid, + const struct in6_addr *addr); + +gboolean nm_utils_ipv6_interface_identifier_get_from_token(NMUtilsIPv6IfaceId *iid, + const char * token); + +const char *nm_utils_inet6_interface_identifier_to_token(NMUtilsIPv6IfaceId iid, + char buf[static INET6_ADDRSTRLEN]); + +gboolean nm_utils_get_ipv6_interface_identifier(NMLinkType link_type, + const guint8 * hwaddr, + guint len, + guint dev_id, + NMUtilsIPv6IfaceId *out_iid); + +/*****************************************************************************/ + +gconstpointer +nm_utils_ipx_address_clear_host_address(int family, gpointer dst, gconstpointer src, guint8 plen); +in_addr_t nm_utils_ip4_address_clear_host_address(in_addr_t addr, guint8 plen); +const struct in6_addr *nm_utils_ip6_address_clear_host_address(struct in6_addr * dst, + const struct in6_addr *src, + guint8 plen); + +static inline int +nm_utils_ip4_address_same_prefix_cmp(in_addr_t addr_a, in_addr_t addr_b, guint8 plen) +{ + NM_CMP_DIRECT(htonl(nm_utils_ip4_address_clear_host_address(addr_a, plen)), + htonl(nm_utils_ip4_address_clear_host_address(addr_b, plen))); + return 0; +} + +int nm_utils_ip6_address_same_prefix_cmp(const struct in6_addr *addr_a, + const struct in6_addr *addr_b, + guint8 plen); + +static inline int +nm_utils_ip_address_same_prefix_cmp(int addr_family, + gconstpointer addr_a, + gconstpointer addr_b, + guint8 plen) +{ + nm_assert_addr_family(addr_family); + + NM_CMP_SELF(addr_a, addr_b); + + if (NM_IS_IPv4(addr_family)) { + return nm_utils_ip4_address_same_prefix_cmp(*((const in_addr_t *) addr_a), + *((const in_addr_t *) addr_b), + plen); + } + + return nm_utils_ip6_address_same_prefix_cmp(addr_a, addr_b, plen); +} + +static inline gboolean +nm_utils_ip4_address_same_prefix(in_addr_t addr_a, in_addr_t addr_b, guint8 plen) +{ + return nm_utils_ip4_address_same_prefix_cmp(addr_a, addr_b, plen) == 0; +} + +static inline gboolean +nm_utils_ip6_address_same_prefix(const struct in6_addr *addr_a, + const struct in6_addr *addr_b, + guint8 plen) +{ + return nm_utils_ip6_address_same_prefix_cmp(addr_a, addr_b, plen) == 0; +} + +static inline gboolean +nm_utils_ip_address_same_prefix(int addr_family, + gconstpointer addr_a, + gconstpointer addr_b, + guint8 plen) +{ + return nm_utils_ip_address_same_prefix_cmp(addr_family, addr_a, addr_b, plen) == 0; +} + +#define NM_CMP_DIRECT_IN4ADDR_SAME_PREFIX(a, b, plen) \ + NM_CMP_RETURN(nm_utils_ip4_address_same_prefix_cmp((a), (b), (plen))) + +#define NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX(a, b, plen) \ + NM_CMP_RETURN(nm_utils_ip6_address_same_prefix_cmp((a), (b), (plen))) + +/*****************************************************************************/ + +#define NM_IPV4LL_NETWORK ((in_addr_t) (htonl(0xA9FE0000lu))) +#define NM_IPV4LL_NETMASK ((in_addr_t) (htonl(0xFFFF0000lu))) + +static inline gboolean +nm_utils_ip4_address_is_link_local(in_addr_t addr) +{ + return (addr & NM_IPV4LL_NETMASK) == NM_IPV4LL_NETWORK; +} + +static inline gboolean +nm_utils_ip4_address_is_zeronet(in_addr_t network) +{ + /* Same as ipv4_is_zeronet() from kernel's include/linux/in.h. */ + return (network & htonl(0xFF000000u)) == htonl(0x00000000u); +} + +/*****************************************************************************/ + +#define NM_UTILS_INET_ADDRSTRLEN INET6_ADDRSTRLEN + +/* Forward declare function so we don't have to drag in <arpa/inet.h>. */ +const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); + +static inline const char * +nm_utils_inet_ntop(int addr_family, gconstpointer addr, char *dst) +{ + const char *s; + + nm_assert_addr_family(addr_family); + nm_assert(addr); + nm_assert(dst); + + s = inet_ntop(addr_family, + addr, + dst, + addr_family == AF_INET6 ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN); + nm_assert(s); + return s; +} + +static inline const char * +_nm_utils_inet4_ntop(in_addr_t addr, char dst[static INET_ADDRSTRLEN]) +{ + return nm_utils_inet_ntop(AF_INET, &addr, dst); +} + +static inline const char * +_nm_utils_inet6_ntop(const struct in6_addr *addr, char dst[static INET6_ADDRSTRLEN]) +{ + return nm_utils_inet_ntop(AF_INET6, addr, dst); +} + +static inline char * +nm_utils_inet_ntop_dup(int addr_family, gconstpointer addr) +{ + char buf[NM_UTILS_INET_ADDRSTRLEN]; + + return g_strdup(nm_utils_inet_ntop(addr_family, addr, buf)); +} + +static inline char * +nm_utils_inet4_ntop_dup(in_addr_t addr) +{ + return nm_utils_inet_ntop_dup(AF_INET, &addr); +} + +static inline char * +nm_utils_inet6_ntop_dup(const struct in6_addr *addr) +{ + return nm_utils_inet_ntop_dup(AF_INET6, addr); +} + +/*****************************************************************************/ + +gboolean nm_utils_ipaddr_is_valid(int addr_family, const char *str_addr); + +gboolean nm_utils_ipaddr_is_normalized(int addr_family, const char *str_addr); + +/*****************************************************************************/ + +/* this enum is compatible with ICMPV6_ROUTER_PREF_* (from <linux/icmpv6.h>, + * the values for netlink attribute RTA_PREF) and "enum ndp_route_preference" + * from <ndp.h>. */ +typedef enum _nm_packed { + NM_ICMPV6_ROUTER_PREF_MEDIUM = 0x0, /* ICMPV6_ROUTER_PREF_MEDIUM */ + NM_ICMPV6_ROUTER_PREF_LOW = 0x3, /* ICMPV6_ROUTER_PREF_LOW */ + NM_ICMPV6_ROUTER_PREF_HIGH = 0x1, /* ICMPV6_ROUTER_PREF_HIGH */ + NM_ICMPV6_ROUTER_PREF_INVALID = 0x2, /* ICMPV6_ROUTER_PREF_INVALID */ +} NMIcmpv6RouterPref; + +const char *nm_icmpv6_router_pref_to_string(NMIcmpv6RouterPref pref, char *buf, gsize len); + +/*****************************************************************************/ + +gboolean nm_utils_memeqzero(gconstpointer data, gsize length); + +/*****************************************************************************/ + +extern const void *const _NM_PTRARRAY_EMPTY[1]; + +#define NM_PTRARRAY_EMPTY(type) ((type const *) _NM_PTRARRAY_EMPTY) +#define NM_STRV_EMPTY() ((char **) _NM_PTRARRAY_EMPTY) +#define NM_STRV_EMPTY_CC() NM_PTRARRAY_EMPTY(const char *) + +static inline void +_nm_utils_strbuf_init(char *buf, gsize len, char **p_buf_ptr, gsize *p_buf_len) +{ + NM_SET_OUT(p_buf_len, len); + NM_SET_OUT(p_buf_ptr, buf); + buf[0] = '\0'; +} + +#define nm_utils_strbuf_init(buf, p_buf_ptr, p_buf_len) \ + G_STMT_START \ + { \ + G_STATIC_ASSERT(G_N_ELEMENTS(buf) == sizeof(buf) && sizeof(buf) > sizeof(char *)); \ + _nm_utils_strbuf_init((buf), sizeof(buf), (p_buf_ptr), (p_buf_len)); \ + } \ + G_STMT_END +void nm_utils_strbuf_append(char **buf, gsize *len, const char *format, ...) _nm_printf(3, 4); +void nm_utils_strbuf_append_c(char **buf, gsize *len, char c); +void nm_utils_strbuf_append_str(char **buf, gsize *len, const char *str); +void nm_utils_strbuf_append_bin(char **buf, gsize *len, gconstpointer str, gsize str_len); +void nm_utils_strbuf_seek_end(char **buf, gsize *len); + +const char *nm_strquote(char *buf, gsize buf_len, const char *str); + +static inline gboolean +nm_utils_is_separator(const char c) +{ + return NM_IN_SET(c, ' ', '\t'); +} + +/*****************************************************************************/ + +GBytes *nm_gbytes_get_empty(void); + +GBytes *nm_g_bytes_new_from_str(const char *str); + +static inline gboolean +nm_gbytes_equal0(GBytes *a, GBytes *b) +{ + return a == b || (a && b && g_bytes_equal(a, b)); +} + +gboolean nm_utils_gbytes_equal_mem(GBytes *bytes, gconstpointer mem_data, gsize mem_len); + +GVariant *nm_utils_gbytes_to_variant_ay(GBytes *bytes); + +GHashTable *nm_utils_strdict_clone(GHashTable *src); + +GVariant *nm_utils_strdict_to_variant_ass(GHashTable *strdict); +GVariant *nm_utils_strdict_to_variant_asv(GHashTable *strdict); + +/*****************************************************************************/ + +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); + +GVariant *nm_utils_gvariant_vardict_filter_drop_one(GVariant *src, const char *key); + +/*****************************************************************************/ + +static inline int +nm_utils_hexchar_to_int(char ch) +{ + G_STATIC_ASSERT_EXPR('0' < 'A'); + G_STATIC_ASSERT_EXPR('A' < 'a'); + + if (ch >= '0') { + if (ch <= '9') + return ch - '0'; + if (ch >= 'A') { + if (ch <= 'F') + return ((int) ch) + (10 - (int) 'A'); + if (ch >= 'a' && ch <= 'f') + return ((int) ch) + (10 - (int) 'a'); + } + } + return -1; +} + +/*****************************************************************************/ + +const char *nm_utils_dbus_path_get_last_component(const char *dbus_path); + +int nm_utils_dbus_path_cmp(const char *dbus_path_a, const char *dbus_path_b); + +/*****************************************************************************/ + +typedef enum { + NM_UTILS_STRSPLIT_SET_FLAGS_NONE = 0, + + /* by default, strsplit will coalesce consecutive delimiters and remove + * them from the result. If this flag is present, empty values are preserved + * and returned. + * + * When combined with %NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP, if a value gets + * empty after strstrip(), it also gets removed. */ + NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY = (1u << 0), + + /* %NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING means that delimiters prefixed + * by a backslash are not treated as a separator. Such delimiters and their escape + * character are copied to the current word without unescaping them. In general, + * nm_utils_strsplit_set_full() does not remove any backslash escape characters + * and does no unescaping. It only considers them for skipping to split at + * an escaped delimiter. + * + * If this is combined with (or implied by %NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED), then + * the backslash escapes are removed from the result. + */ + NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING = (1u << 1), + + /* If flag is set, does the same as g_strstrip() on the returned tokens. + * This will remove leading and trailing ascii whitespaces (g_ascii_isspace() + * and NM_ASCII_SPACES). + * + * - when combined with !%NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, + * empty tokens will be removed (and %NULL will be returned if that + * results in an empty string array). + * - when combined with %NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING, + * trailing whitespace escaped by backslash are not stripped. */ + NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP = (1u << 2), + + /* This implies %NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING. + * + * This will do a final run over all tokens and remove all backslash + * escape characters that + * - precede a delimiter. + * - precede a backslash. + * - precede a whitespace (only with %NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP). + * + * Note that with %NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP, it is only + * necessary to escape the very last whitespace (if the delimiters + * are not whitespace themself). So, technically, it would be sufficient + * to only unescape a backslash before the last whitespace and the user + * still could express everything. However, such a rule would be complicated + * to understand, so when using backslash escaping with nm_utils_strsplit_set_full(), + * then all characters (including backslash) are treated verbatim, except: + * + * - "\\$DELIMITER" (escaped delimiter) + * - "\\\\" (escaped backslash) + * - "\\$SPACE" (escaped space) (only with %NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP). + * + * Note that all other escapes like "\\n" or "\\001" are left alone. + * That makes the escaping/unescaping rules simple. Also, for the most part + * a text is just taken as-is, with little additional rules. Only backslashes + * need extra care, and then only if they proceed one of the relevant characters. + */ + NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED = (1u << 3), + +} NMUtilsStrsplitSetFlags; + +const char ** +nm_utils_strsplit_set_full(const char *str, const char *delimiter, NMUtilsStrsplitSetFlags flags); + +static inline const char ** +nm_utils_strsplit_set_with_empty(const char *str, const char *delimiters) +{ + /* this returns the same result as g_strsplit_set(str, delimiters, -1), except + * it does not deep-clone the strv array. + * Also, for @str == "", this returns %NULL while g_strsplit_set() would return + * an empty strv array. */ + return nm_utils_strsplit_set_full(str, delimiters, NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY); +} + +static inline const char ** +nm_utils_strsplit_set(const char *str, const char *delimiters) +{ + return nm_utils_strsplit_set_full(str, delimiters, NM_UTILS_STRSPLIT_SET_FLAGS_NONE); +} + +gssize nm_utils_strv_find_first(char **list, gssize len, const char *needle); + +gboolean nm_strv_has_duplicate(const char *const *list, gssize len, gboolean is_sorted); + +char **_nm_utils_strv_cleanup(char ** strv, + gboolean strip_whitespace, + gboolean skip_empty, + gboolean skip_repeated); + +/*****************************************************************************/ + +static inline gpointer +nm_copy_func_g_strdup(gconstpointer arg, gpointer user_data) +{ + return g_strdup(arg); +} + +/*****************************************************************************/ + +static inline const char ** +nm_utils_escaped_tokens_split(const char *str, const char *delimiters) +{ + return nm_utils_strsplit_set_full(str, + delimiters, + NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED + | NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP); +} + +typedef enum { + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_NONE = 0, + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_SPACES = (1ull << 0), + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE = (1ull << 1), + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE = (1ull << 2), + + /* Backslash characters will be escaped as "\\\\" if they precede another + * character that makes it necessary. Such characters are: + * + * 1) before another '\\' backslash. + * 2) before any delimiter in @delimiters. + * 3) before any delimiter in @delimiters_as_needed. + * 4) before a white space, if ESCAPE_LEADING_SPACE or ESCAPE_TRAILING_SPACE is set. + * 5) before the end of the word + * + * Rule 4) is an extension. It's not immediately clear why with ESCAPE_LEADING_SPACE + * and ESCAPE_TRAILING_SPACE we want *all* backslashes before a white space escaped. + * The reason is, that we obviously want to use ESCAPE_LEADING_SPACE and ESCAPE_TRAILING_SPACE + * in cases, where we later parse the backslash escaped strings back, but allowing to strip + * unescaped white spaces. That means, we want that " a " gets escaped as "\\ a\\ ". + * On the other hand, we also want that " a\\ b " gets escaped as "\\ a\\\\ b\\ ", + * and not "\\ a\\ b\\ ". Because otherwise, the parser would need to treat "\\ " + * differently depending on whether the sequence is at the beginning, end or middle + * of the word. + * + * Rule 5) is also not immediately obvious. When used with ESCAPE_TRAILING_SPACE, + * we clearly want to allow that an escaped word can have arbitrary + * whitespace suffixes. That's why this mode exists. So we must escape "a\\" as + * "a\\\\", so that appending " " does not change the meaning. + * Also without ESCAPE_TRAILING_SPACE, we want in general that we can concatenate + * two escaped words without changing their meaning. If the words would be "a\\" + * and "," (with ',' being a delimiter), then the result must be "a\\\\" and "\\," + * so that the concatenated word ("a\\\\\\,") is still the same. If we would escape + * them instead as "a\\" + "\\,", then the concatenated word would be "a\\\\," and + * different. + * */ + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED = (1ull << 3), + + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_ALWAYS = (1ull << 4), +} NMUtilsEscapedTokensEscapeFlags; + +const char *nm_utils_escaped_tokens_escape_full(const char *str, + const char *delimiters, + const char *delimiters_as_needed, + NMUtilsEscapedTokensEscapeFlags flags, + char ** out_to_free); + +static inline const char * +nm_utils_escaped_tokens_escape(const char *str, const char *delimiters, char **out_to_free) +{ + return nm_utils_escaped_tokens_escape_full( + str, + delimiters, + NULL, + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_ALWAYS + | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE, + out_to_free); +} + +/** + * nm_utils_escaped_tokens_escape_unnecessary: + * @str: the string to check for "escape" + * @delimiters: the delimiters + * + * This asserts that calling nm_utils_escaped_tokens_escape() + * on @str has no effect and returns @str directly. This is only + * for asserting that @str is safe to not require any escaping. + * + * Returns: @str + */ +static inline const char * +nm_utils_escaped_tokens_escape_unnecessary(const char *str, const char *delimiters) +{ +#if NM_MORE_ASSERTS > 0 + + nm_assert(str); + nm_assert(delimiters); + + { + gs_free char *str_to_free = NULL; + const char * str0; + + str0 = nm_utils_escaped_tokens_escape(str, delimiters, &str_to_free); + nm_assert(str0 == str); + nm_assert(!str_to_free); + } +#endif + + return str; +} + +static inline void +nm_utils_escaped_tokens_escape_gstr_assert(const char *str, + const char *delimiters, + GString * gstring) +{ + g_string_append(gstring, nm_utils_escaped_tokens_escape_unnecessary(str, delimiters)); +} + +static inline GString * +nm_utils_escaped_tokens_escape_gstr(const char *str, const char *delimiters, GString *gstring) +{ + gs_free char *str_to_free = NULL; + + nm_assert(str); + nm_assert(gstring); + + g_string_append(gstring, nm_utils_escaped_tokens_escape(str, delimiters, &str_to_free)); + return gstring; +} + +/*****************************************************************************/ + +char **nm_utils_strsplit_quoted(const char *str); + +/*****************************************************************************/ + +static inline const char ** +nm_utils_escaped_tokens_options_split_list(const char *str) +{ + return nm_utils_strsplit_set_full(str, + ",", + NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP + | NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING); +} + +void nm_utils_escaped_tokens_options_split(char *str, const char **out_key, const char **out_val); + +static inline const char * +nm_utils_escaped_tokens_options_escape_key(const char *key, char **out_to_free) +{ + return nm_utils_escaped_tokens_escape_full( + key, + ",=", + NULL, + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED + | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE + | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE, + out_to_free); +} + +static inline const char * +nm_utils_escaped_tokens_options_escape_val(const char *val, char **out_to_free) +{ + return nm_utils_escaped_tokens_escape_full( + val, + ",", + "=", + NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED + | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE + | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE, + out_to_free); +} + +/*****************************************************************************/ + +#define NM_UTILS_CHECKSUM_LENGTH_MD5 16 +#define NM_UTILS_CHECKSUM_LENGTH_SHA1 20 +#define NM_UTILS_CHECKSUM_LENGTH_SHA256 32 + +#define nm_utils_checksum_get_digest(sum, arr) \ + G_STMT_START \ + { \ + GChecksum *const _sum = (sum); \ + gsize _len; \ + \ + G_STATIC_ASSERT_EXPR(sizeof(arr) == NM_UTILS_CHECKSUM_LENGTH_MD5 \ + || sizeof(arr) == NM_UTILS_CHECKSUM_LENGTH_SHA1 \ + || sizeof(arr) == NM_UTILS_CHECKSUM_LENGTH_SHA256); \ + G_STATIC_ASSERT_EXPR(sizeof(arr) == G_N_ELEMENTS(arr)); \ + \ + nm_assert(_sum); \ + \ + _len = G_N_ELEMENTS(arr); \ + \ + g_checksum_get_digest(_sum, (arr), &_len); \ + nm_assert(_len == G_N_ELEMENTS(arr)); \ + } \ + G_STMT_END + +#define nm_utils_checksum_get_digest_len(sum, buf, len) \ + G_STMT_START \ + { \ + GChecksum *const _sum = (sum); \ + const gsize _len0 = (len); \ + gsize _len; \ + \ + nm_assert(NM_IN_SET(_len0, \ + NM_UTILS_CHECKSUM_LENGTH_MD5, \ + NM_UTILS_CHECKSUM_LENGTH_SHA1, \ + NM_UTILS_CHECKSUM_LENGTH_SHA256)); \ + nm_assert(_sum); \ + \ + _len = _len0; \ + g_checksum_get_digest(_sum, (buf), &_len); \ + nm_assert(_len == _len0); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +guint32 _nm_utils_ip4_prefix_to_netmask(guint32 prefix); +guint32 _nm_utils_ip4_get_default_prefix0(in_addr_t ip); +guint32 _nm_utils_ip4_get_default_prefix(in_addr_t ip); + +gconstpointer +nm_utils_ipx_address_clear_host_address(int family, gpointer dst, gconstpointer src, guint8 plen); +in_addr_t nm_utils_ip4_address_clear_host_address(in_addr_t addr, guint8 plen); +const struct in6_addr *nm_utils_ip6_address_clear_host_address(struct in6_addr * dst, + const struct in6_addr *src, + guint8 plen); +int nm_utils_ip6_address_same_prefix_cmp(const struct in6_addr *addr_a, + const struct in6_addr *addr_b, + guint8 plen); + +gboolean nm_utils_ip_is_site_local(int addr_family, const void *address); + +/*****************************************************************************/ + +gboolean nm_utils_parse_inaddr_bin_full(int addr_family, + gboolean accept_legacy, + const char *text, + int * out_addr_family, + gpointer out_addr); +static inline gboolean +nm_utils_parse_inaddr_bin(int addr_family, + const char *text, + int * out_addr_family, + gpointer out_addr) +{ + return nm_utils_parse_inaddr_bin_full(addr_family, FALSE, text, out_addr_family, out_addr); +} + +gboolean nm_utils_parse_inaddr(int addr_family, const char *text, char **out_addr); + +gboolean nm_utils_parse_inaddr_prefix_bin(int addr_family, + const char *text, + int * out_addr_family, + gpointer out_addr, + int * out_prefix); + +gboolean +nm_utils_parse_inaddr_prefix(int addr_family, const char *text, char **out_addr, int *out_prefix); + +gboolean nm_utils_parse_next_line(const char **inout_ptr, + gsize * inout_len, + const char **out_line, + gsize * out_line_len); + +gint64 nm_g_ascii_strtoll(const char *nptr, char **endptr, guint base); + +guint64 nm_g_ascii_strtoull(const char *nptr, char **endptr, guint base); + +double nm_g_ascii_strtod(const char *nptr, char **endptr); + +gint64 +_nm_utils_ascii_str_to_int64(const char *str, guint base, gint64 min, gint64 max, gint64 fallback); +guint64 _nm_utils_ascii_str_to_uint64(const char *str, + guint base, + guint64 min, + guint64 max, + guint64 fallback); + +gint64 _nm_utils_ascii_str_to_int64_bin(const char *str, + gssize len, + guint base, + gint64 min, + gint64 max, + gint64 fallback); + +int _nm_utils_ascii_str_to_bool(const char *str, int default_value); + +/*****************************************************************************/ + +extern char _nm_utils_to_string_buffer[2096]; + +void nm_utils_to_string_buffer_init(char **buf, gsize *len); +gboolean nm_utils_to_string_buffer_init_null(gconstpointer obj, char **buf, gsize *len); + +/*****************************************************************************/ + +typedef struct { + unsigned flag; + const char *name; +} NMUtilsFlags2StrDesc; + +#define NM_UTILS_FLAGS2STR(f, n) \ + { \ + .flag = f, .name = "" n, \ + } + +#define NM_UTILS_FLAGS2STR_DEFINE(fcn_name, flags_type, ...) \ + const char *fcn_name(flags_type flags, char *buf, gsize len) \ + { \ + static const NMUtilsFlags2StrDesc descs[] = {__VA_ARGS__}; \ + G_STATIC_ASSERT(sizeof(flags_type) <= sizeof(unsigned)); \ + \ + return nm_utils_flags2str(descs, G_N_ELEMENTS(descs), flags, buf, len); \ + } \ + _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON + +const char *nm_utils_flags2str(const NMUtilsFlags2StrDesc *descs, + gsize n_descs, + unsigned flags, + char * buf, + gsize len); + +/*****************************************************************************/ + +#define NM_UTILS_ENUM2STR(v, n) \ + (void) 0; \ +case v: \ + s = "" n ""; \ + break; \ + (void) 0 +#define NM_UTILS_ENUM2STR_IGNORE(v) \ + (void) 0; \ +case v: \ + break; \ + (void) 0 + +#define NM_UTILS_ENUM2STR_DEFINE_FULL(fcn_name, lookup_type, int_fmt, ...) \ + const char *fcn_name(lookup_type val, char *buf, gsize len) \ + { \ + nm_utils_to_string_buffer_init(&buf, &len); \ + if (len) { \ + const char *s = NULL; \ + switch (val) { \ + (void) 0, __VA_ARGS__(void) 0; \ + }; \ + if (s) \ + g_strlcpy(buf, s, len); \ + else \ + g_snprintf(buf, len, "(%" int_fmt ")", val); \ + } \ + return buf; \ + } \ + _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON + +#define NM_UTILS_ENUM2STR_DEFINE(fcn_name, lookup_type, ...) \ + NM_UTILS_ENUM2STR_DEFINE_FULL(fcn_name, lookup_type, "d", __VA_ARGS__) + +/*****************************************************************************/ + +#define _nm_g_slice_free_fcn_define(mem_size) \ + static inline void _nm_g_slice_free_fcn_##mem_size(gpointer mem_block) \ + { \ + g_slice_free1(mem_size, mem_block); \ + } + +_nm_g_slice_free_fcn_define(1) _nm_g_slice_free_fcn_define(2) _nm_g_slice_free_fcn_define(4) + _nm_g_slice_free_fcn_define(8) _nm_g_slice_free_fcn_define(10) _nm_g_slice_free_fcn_define(12) + _nm_g_slice_free_fcn_define(16) _nm_g_slice_free_fcn_define(32) + +#define nm_g_slice_free_fcn1(mem_size) \ + ({ \ + void (*_fcn)(gpointer); \ + \ + /* If mem_size is a compile time constant, the compiler + * will be able to optimize this. Hence, you don't want + * to call this with a non-constant size argument. */ \ + G_STATIC_ASSERT_EXPR(((mem_size) == 1) || ((mem_size) == 2) || ((mem_size) == 4) \ + || ((mem_size) == 8) || ((mem_size) == 10) || ((mem_size) == 12) \ + || ((mem_size) == 16) || ((mem_size) == 32)); \ + switch ((mem_size)) { \ + case 1: \ + _fcn = _nm_g_slice_free_fcn_1; \ + break; \ + case 2: \ + _fcn = _nm_g_slice_free_fcn_2; \ + break; \ + case 4: \ + _fcn = _nm_g_slice_free_fcn_4; \ + break; \ + case 8: \ + _fcn = _nm_g_slice_free_fcn_8; \ + break; \ + case 10: \ + _fcn = _nm_g_slice_free_fcn_10; \ + break; \ + case 12: \ + _fcn = _nm_g_slice_free_fcn_12; \ + break; \ + case 16: \ + _fcn = _nm_g_slice_free_fcn_16; \ + break; \ + case 32: \ + _fcn = _nm_g_slice_free_fcn_32; \ + break; \ + default: \ + g_assert_not_reached(); \ + _fcn = NULL; \ + break; \ + } \ + _fcn; \ + }) + +/** + * nm_g_slice_free_fcn: + * @type: type argument for sizeof() operator that you would + * pass to g_slice_new(). + * + * Returns: a function pointer with GDestroyNotify signature + * for g_slice_free(type,*). + * + * Only certain types are implemented. You'll get a compile time + * error for the wrong types. */ +#define nm_g_slice_free_fcn(type) (nm_g_slice_free_fcn1(sizeof(type))) + +#define nm_g_slice_free_fcn_gint64 (nm_g_slice_free_fcn(gint64)) + +/*****************************************************************************/ + +/* Like g_error_matches() however: + * - as macro it is always inlined. + * - the @domain is usually a error quark getter function that cannot + * be inlined. This macro calls the getter only if there is an error (lazy). + * - accept a list of allowed codes, instead of only one. + */ +#define nm_g_error_matches(error, err_domain, ...) \ + ({ \ + const GError *const _error = (error); \ + \ + _error && _error->domain == (err_domain) && NM_IN_SET(_error->code, __VA_ARGS__); \ + }) + + static inline void nm_g_set_error_take(GError **error, GError *error_take) +{ + if (!error_take) + g_return_if_reached(); + if (!error) { + g_error_free(error_take); + return; + } + if (*error) { + g_error_free(error_take); + g_return_if_reached(); + } + *error = error_take; +} + +#define nm_g_set_error_take_lazy(error, error_take_lazy) \ + G_STMT_START \ + { \ + GError **_error = (error); \ + \ + if (_error) \ + nm_g_set_error_take(_error, (error_take_lazy)); \ + } \ + G_STMT_END + +/** + * NMUtilsError: + * @NM_UTILS_ERROR_UNKNOWN: unknown or unclassified error + * @NM_UTILS_ERROR_CANCELLED_DISPOSING: when disposing an object that has + * pending asynchronous operations, the operation is cancelled with this + * error reason. Depending on the usage, this might indicate a bug because + * usually the target object should stay alive as long as there are pending + * operations. + * @NM_UTILS_ERROR_NOT_READY: the failure is related to being currently + * not ready to perform the operation. + * + * @NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE: used for a very particular + * purpose during nm_device_check_connection_compatible() to indicate that + * the profile does not match the device already because their type differs. + * That is, there is a fundamental reason of trying to check a profile that + * cannot possibly match on this device. + * @NM_UTILS_ERROR_CONNECTION_AVAILABLE_UNMANAGED_DEVICE: used for a very particular + * purpose during nm_device_check_connection_available(), to indicate that the + * device is not available because it is unmanaged. + * @NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY: the profile is currently not + * available/compatible with the device, but this may be only temporary. + * + * @NM_UTILS_ERROR_SETTING_MISSING: the setting is missing + * + * @NM_UTILS_ERROR_INVALID_ARGUMENT: invalid argument. + */ +typedef enum { + NM_UTILS_ERROR_UNKNOWN = 0, /*< nick=Unknown >*/ + NM_UTILS_ERROR_CANCELLED_DISPOSING, /*< nick=CancelledDisposing >*/ + NM_UTILS_ERROR_INVALID_ARGUMENT, /*< nick=InvalidArgument >*/ + NM_UTILS_ERROR_NOT_READY, /*< nick=NotReady >*/ + + NM_UTILS_ERROR_AMBIGUOUS, /*< nick=Ambiguous >*/ + + /* the following codes have a special meaning and are exactly used for + * nm_device_check_connection_compatible() and nm_device_check_connection_available(). + * + * Actually, their meaning is not very important (so, don't think too + * hard about the name of these error codes). What is important, is their + * relative order (i.e. the integer value of the codes). When manager + * searches for a suitable device, it will check all devices whether + * a profile can be activated. If they all fail, it will pick the error + * message from the device that returned the *highest* error code, + * in the hope that this message makes the most sense for the caller. + * */ + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_UNMANAGED_DEVICE, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_DISALLOWED, + + NM_UTILS_ERROR_SETTING_MISSING, + +} NMUtilsError; + +#define NM_UTILS_ERROR (nm_utils_error_quark()) +GQuark nm_utils_error_quark(void); + +GQuark nm_manager_error_quark(void); +#define _NM_MANAGER_ERROR (nm_manager_error_quark()) + +#define _NM_MANAGER_ERROR_UNKNOWN_LOG_LEVEL 10 +#define _NM_MANAGER_ERROR_UNKNOWN_LOG_DOMAIN 11 + +void nm_utils_error_set_cancelled(GError **error, gboolean is_disposing, const char *instance_name); + +static inline GError * +nm_utils_error_new_cancelled(gboolean is_disposing, const char *instance_name) +{ + GError *error = NULL; + + nm_utils_error_set_cancelled(&error, is_disposing, instance_name); + return error; +} + +gboolean nm_utils_error_is_cancelled_or_disposing(GError *error); + +static inline gboolean +nm_utils_error_is_cancelled(GError *error) +{ + return error && error->code == G_IO_ERROR_CANCELLED && error->domain == G_IO_ERROR; +} + +gboolean nm_utils_error_is_notfound(GError *error); + +static inline void +nm_utils_error_set_literal(GError **error, int error_code, const char *literal) +{ + g_set_error_literal(error, NM_UTILS_ERROR, error_code, literal); +} + +#define nm_utils_error_set(error, error_code, ...) \ + G_STMT_START \ + { \ + if (NM_NARG(__VA_ARGS__) == 1) { \ + g_set_error_literal((error), \ + NM_UTILS_ERROR, \ + (error_code), \ + _NM_UTILS_MACRO_FIRST(__VA_ARGS__)); \ + } else { \ + g_set_error((error), NM_UTILS_ERROR, (error_code), __VA_ARGS__); \ + } \ + } \ + G_STMT_END + +#define nm_utils_error_set_errno(error, errsv, fmt, ...) \ + G_STMT_START \ + { \ + char _bstrerr[NM_STRERROR_BUFSIZE]; \ + \ + g_set_error((error), \ + NM_UTILS_ERROR, \ + NM_UTILS_ERROR_UNKNOWN, \ + fmt, \ + ##__VA_ARGS__, \ + nm_strerror_native_r( \ + ({ \ + const int _errsv = (errsv); \ + \ + (_errsv >= 0 ? _errsv \ + : (G_UNLIKELY(_errsv == G_MININT) ? G_MAXINT : -errsv)); \ + }), \ + _bstrerr, \ + sizeof(_bstrerr))); \ + } \ + G_STMT_END + +#define nm_utils_error_new(error_code, ...) \ + ((NM_NARG(__VA_ARGS__) == 1) \ + ? g_error_new_literal(NM_UTILS_ERROR, (error_code), _NM_UTILS_MACRO_FIRST(__VA_ARGS__)) \ + : g_error_new(NM_UTILS_ERROR, (error_code), __VA_ARGS__)) + +/*****************************************************************************/ + +gboolean nm_g_object_set_property(GObject * object, + const char * property_name, + const GValue *value, + GError ** error); + +gboolean nm_g_object_set_property_string(GObject * object, + const char *property_name, + const char *value, + GError ** error); + +gboolean nm_g_object_set_property_string_static(GObject * object, + const char *property_name, + const char *value, + GError ** error); + +gboolean nm_g_object_set_property_string_take(GObject * object, + const char *property_name, + char * value, + GError ** error); + +gboolean nm_g_object_set_property_boolean(GObject * object, + const char *property_name, + gboolean value, + GError ** error); + +gboolean nm_g_object_set_property_char(GObject * object, + const char *property_name, + gint8 value, + GError ** error); + +gboolean nm_g_object_set_property_uchar(GObject * object, + const char *property_name, + guint8 value, + GError ** error); + +gboolean +nm_g_object_set_property_int(GObject *object, const char *property_name, int value, GError **error); + +gboolean nm_g_object_set_property_int64(GObject * object, + const char *property_name, + gint64 value, + GError ** error); + +gboolean nm_g_object_set_property_uint(GObject * object, + const char *property_name, + guint value, + GError ** error); + +gboolean nm_g_object_set_property_uint64(GObject * object, + const char *property_name, + guint64 value, + GError ** error); + +gboolean nm_g_object_set_property_flags(GObject * object, + const char *property_name, + GType gtype, + guint value, + GError ** error); + +gboolean nm_g_object_set_property_enum(GObject * object, + const char *property_name, + GType gtype, + int value, + GError ** error); + +GParamSpec *nm_g_object_class_find_property_from_gtype(GType gtype, const char *property_name); + +/*****************************************************************************/ + +#define _NM_G_PARAM_SPEC_CAST(param_spec, _value_type, _c_type) \ + ({ \ + const GParamSpec *const _param_spec = (param_spec); \ + \ + nm_assert(!_param_spec || _param_spec->value_type == (_value_type)); \ + ((const _c_type *) _param_spec); \ + }) + +#define NM_G_PARAM_SPEC_CAST_BOOLEAN(param_spec) \ + _NM_G_PARAM_SPEC_CAST(param_spec, G_TYPE_BOOLEAN, GParamSpecBoolean) +#define NM_G_PARAM_SPEC_CAST_UINT(param_spec) \ + _NM_G_PARAM_SPEC_CAST(param_spec, G_TYPE_UINT, GParamSpecUInt) +#define NM_G_PARAM_SPEC_CAST_UINT64(param_spec) \ + _NM_G_PARAM_SPEC_CAST(param_spec, G_TYPE_UINT64, GParamSpecUInt64) +#define NM_G_PARAM_SPEC_CAST_STRING(param_spec) \ + _NM_G_PARAM_SPEC_CAST(param_spec, G_TYPE_STRING, GParamSpecString) + +#define NM_G_PARAM_SPEC_GET_DEFAULT_BOOLEAN(param_spec) \ + (NM_G_PARAM_SPEC_CAST_BOOLEAN(NM_ENSURE_NOT_NULL(param_spec))->default_value) +#define NM_G_PARAM_SPEC_GET_DEFAULT_UINT(param_spec) \ + (NM_G_PARAM_SPEC_CAST_UINT(NM_ENSURE_NOT_NULL(param_spec))->default_value) +#define NM_G_PARAM_SPEC_GET_DEFAULT_UINT64(param_spec) \ + (NM_G_PARAM_SPEC_CAST_UINT64(NM_ENSURE_NOT_NULL(param_spec))->default_value) +#define NM_G_PARAM_SPEC_GET_DEFAULT_STRING(param_spec) \ + (NM_G_PARAM_SPEC_CAST_STRING(NM_ENSURE_NOT_NULL(param_spec))->default_value) + +/*****************************************************************************/ + +GType nm_g_type_find_implementing_class_for_property(GType gtype, const char *pname); + +/*****************************************************************************/ + +typedef enum { + NM_UTILS_STR_UTF8_SAFE_FLAG_NONE = 0, + + /* This flag only has an effect during escaping. + * + * It will backslash escape ascii characters according to nm_ascii_is_ctrl_or_del(). */ + NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL = 0x0001, + + /* This flag only has an effect during escaping. + * + * It will backslash escape ascii characters according to nm_ascii_is_non_ascii(). */ + NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII = 0x0002, + + /* This flag only has an effect during escaping to ensure we + * don't leak secrets in memory. Note that during unescape we + * know the maximum result size from the beginning, and no + * reallocation happens. Thus, unescape always avoids leaking + * secrets already. */ + NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET = 0x0004, + + /* This flag only has an effect during unescaping. It means + * that non-escaped whitespaces (g_ascii_isspace()) will be + * stripped from the front and end of the string. Note that + * this flag is only useful for gracefully accepting user input + * with spaces. With this flag, escape and unescape may no longer + * yield the original input. */ + NM_UTILS_STR_UTF8_SAFE_UNESCAPE_STRIP_SPACES = 0x0008, +} NMUtilsStrUtf8SafeFlags; + +const char *nm_utils_buf_utf8safe_escape(gconstpointer buf, + gssize buflen, + NMUtilsStrUtf8SafeFlags flags, + char ** to_free); +char * +nm_utils_buf_utf8safe_escape_cp(gconstpointer buf, gssize buflen, NMUtilsStrUtf8SafeFlags flags); +const char * +nm_utils_buf_utf8safe_escape_bytes(GBytes *bytes, NMUtilsStrUtf8SafeFlags flags, char **to_free); +gconstpointer nm_utils_buf_utf8safe_unescape(const char * str, + NMUtilsStrUtf8SafeFlags flags, + gsize * out_len, + gpointer * to_free); + +const char * +nm_utils_str_utf8safe_escape(const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free); +const char * +nm_utils_str_utf8safe_unescape(const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free); + +char *nm_utils_str_utf8safe_escape_cp(const char *str, NMUtilsStrUtf8SafeFlags flags); +char *nm_utils_str_utf8safe_unescape_cp(const char *str, NMUtilsStrUtf8SafeFlags flags); + +char *nm_utils_str_utf8safe_escape_take(char *str, NMUtilsStrUtf8SafeFlags flags); + +GVariant *nm_g_variant_singleton_b(gboolean value); +GVariant *nm_g_variant_singleton_u_0(void); +GVariant *nm_g_variant_singleton_s_empty(void); +GVariant *nm_g_variant_singleton_aLsvI(void); +GVariant *nm_g_variant_singleton_aLsaLsvII(void); +GVariant *nm_g_variant_singleton_aaLsvI(void); + +static inline void +nm_g_variant_unref_floating(GVariant *var) +{ + /* often a function wants to keep a reference to an input variant. + * It uses g_variant_ref_sink() to either increase the ref-count, + * or take ownership of a possibly floating reference. + * + * If the function doesn't actually want to do anything with the + * input variant, it still must make sure that a passed in floating + * reference is consumed. Hence, this helper which: + * + * - does nothing if @var is not floating + * - unrefs (consumes) @var if it is floating. */ + if (g_variant_is_floating(var)) + g_variant_unref(var); +} + +#define nm_g_variant_lookup(dictionary, ...) \ + ({ \ + GVariant *const _dictionary = (dictionary); \ + \ + (_dictionary && g_variant_lookup(_dictionary, __VA_ARGS__)); \ + }) + +static inline GVariant * +nm_g_variant_lookup_value(GVariant *dictionary, const char *key, const GVariantType *expected_type) +{ + return dictionary ? g_variant_lookup_value(dictionary, key, expected_type) : NULL; +} + +static inline gboolean +nm_g_variant_is_of_type(GVariant *value, const GVariantType *type) +{ + return value && g_variant_is_of_type(value, type); +} + +static inline GVariant * +nm_g_variant_new_ay(const guint8 *data, gsize len) +{ + return g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, data, len, 1); +} + +static inline GVariant * +nm_g_variant_new_au(const guint32 *data, gsize len) +{ + return g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, data, len, sizeof(guint32)); +} + +static inline GVariant * +nm_g_variant_new_ay_inaddr(int addr_family, gconstpointer addr) +{ + return nm_g_variant_new_ay(addr ?: &nm_ip_addr_zero, nm_utils_addr_family_to_size(addr_family)); +} + +static inline GVariant * +nm_g_variant_new_ay_in4addr(in_addr_t addr) +{ + return nm_g_variant_new_ay_inaddr(AF_INET, &addr); +} + +static inline GVariant * +nm_g_variant_new_ay_in6addr(const struct in6_addr *addr) +{ + return nm_g_variant_new_ay_inaddr(AF_INET6, addr); +} + +static inline void +nm_g_variant_builder_add_sv(GVariantBuilder *builder, const char *key, GVariant *val) +{ + g_variant_builder_add(builder, "{sv}", key, val); +} + +static inline void +nm_g_variant_builder_add_sv_bytearray(GVariantBuilder *builder, + const char * key, + const guint8 * arr, + gsize len) +{ + g_variant_builder_add(builder, "{sv}", key, nm_g_variant_new_ay(arr, len)); +} + +static inline void +nm_g_variant_builder_add_sv_uint32(GVariantBuilder *builder, const char *key, guint32 val) +{ + nm_g_variant_builder_add_sv(builder, key, g_variant_new_uint32(val)); +} + +static inline void +nm_g_variant_builder_add_sv_str(GVariantBuilder *builder, const char *key, const char *str) +{ + nm_g_variant_builder_add_sv(builder, key, g_variant_new_string(str)); +} + +static inline void +nm_g_source_destroy_and_unref(GSource *source) +{ + g_source_destroy(source); + g_source_unref(source); +} + +#define nm_clear_g_source_inst(ptr) (nm_clear_pointer((ptr), nm_g_source_destroy_and_unref)) + +NM_AUTO_DEFINE_FCN0(GSource *, _nm_auto_destroy_and_unref_gsource, nm_g_source_destroy_and_unref); +#define nm_auto_destroy_and_unref_gsource nm_auto(_nm_auto_destroy_and_unref_gsource) + +NM_AUTO_DEFINE_FCN0(GMainContext *, _nm_auto_pop_gmaincontext, g_main_context_pop_thread_default); +#define nm_auto_pop_gmaincontext nm_auto(_nm_auto_pop_gmaincontext) + +static inline void +nm_g_main_context_pop_and_unref(GMainContext *context) +{ + g_main_context_pop_thread_default(context); + g_main_context_unref(context); +} + +NM_AUTO_DEFINE_FCN0(GMainContext *, + _nm_auto_pop_and_unref_gmaincontext, + nm_g_main_context_pop_and_unref); +#define nm_auto_pop_and_unref_gmaincontext nm_auto(_nm_auto_pop_and_unref_gmaincontext) + +static inline gboolean +nm_source_func_unref_gobject(gpointer user_data) +{ + nm_assert(G_IS_OBJECT(user_data)); + g_object_unref(user_data); + return G_SOURCE_REMOVE; +} + +extern GSource *_nm_g_source_sentinel[1]; + +GSource *_nm_g_source_sentinel_get_init(GSource **p_source); + +/* Get a GSource sentinel (dummy instance). This instance should never be + * attached to a GMainContext. The only currently known purpose is to use it + * as dummy value instead of an infinity timeout. That is, if we configurably + * want to schedule a timeout that might be infinity, we might set the GSource + * instance to nm_g_source_sentinel_get(). On this instance, we still may + * call g_source_ref(), g_source_unref() and g_source_destroy(). But nothing + * else. */ +#define nm_g_source_sentinel_get(idx) \ + ({ \ + GSource *_s; \ + \ + G_STATIC_ASSERT((idx) >= 0); \ + G_STATIC_ASSERT((idx) < G_N_ELEMENTS(_nm_g_source_sentinel)); \ + \ + _s = g_atomic_pointer_get(&_nm_g_source_sentinel[idx]); \ + if (G_UNLIKELY(!_s)) \ + _s = _nm_g_source_sentinel_get_init(&_nm_g_source_sentinel[idx]); \ + \ + _s; \ + }) + +GSource *nm_g_idle_source_new(int priority, + GSourceFunc func, + gpointer user_data, + GDestroyNotify destroy_notify); + +GSource *nm_g_timeout_source_new(guint timeout_msec, + int priority, + GSourceFunc func, + gpointer user_data, + GDestroyNotify destroy_notify); + +GSource *nm_g_timeout_source_new_seconds(guint timeout_sec, + int priority, + GSourceFunc func, + gpointer user_data, + GDestroyNotify destroy_notify); + +GSource * + nm_g_unix_fd_source_new(int fd, + GIOCondition io_condition, + int priority, + gboolean (*source_func)(int fd, GIOCondition condition, gpointer user_data), + gpointer user_data, + GDestroyNotify destroy_notify); +GSource *nm_g_unix_signal_source_new(int signum, + int priority, + GSourceFunc handler, + gpointer user_data, + GDestroyNotify notify); + +static inline GSource * +nm_g_source_attach(GSource *source, GMainContext *context) +{ + g_source_attach(source, context); + return source; +} + +static inline GSource * +nm_g_idle_add_source(GSourceFunc func, gpointer user_data) +{ + /* G convenience function to attach a new timeout source to the default GMainContext. + * In that sense it's very similar to g_idle_add() except that it returns a + * reference to the new source. */ + return nm_g_source_attach(nm_g_idle_source_new(G_PRIORITY_DEFAULT, func, user_data, NULL), + NULL); +} + +static inline GSource * +nm_g_timeout_add_source(guint timeout_msec, GSourceFunc func, gpointer user_data) +{ + /* G convenience function to attach a new timeout source to the default GMainContext. + * In that sense it's very similar to g_timeout_add() except that it returns a + * reference to the new source. */ + return nm_g_source_attach( + nm_g_timeout_source_new(timeout_msec, G_PRIORITY_DEFAULT, func, user_data, NULL), + NULL); +} + +static inline GSource * +nm_g_timeout_add_source_seconds(guint timeout_sec, GSourceFunc func, gpointer user_data) +{ + /* G convenience function to attach a new timeout source to the default GMainContext. + * In that sense it's very similar to g_timeout_add_seconds() except that it returns a + * reference to the new source. */ + return nm_g_source_attach( + nm_g_timeout_source_new_seconds(timeout_sec, G_PRIORITY_DEFAULT, func, user_data, NULL), + NULL); +} + +static inline GSource * +nm_g_timeout_add_source_approx(guint timeout_msec, + guint timeout_sec_threshold, + GSourceFunc func, + gpointer user_data) +{ + GSource *source; + + /* If timeout_msec is larger or equal than a threshold, then we use g_timeout_source_new_seconds() + * instead. */ + if (timeout_msec / 1000u >= timeout_sec_threshold) + source = nm_g_timeout_source_new_seconds(timeout_msec / 1000u, + G_PRIORITY_DEFAULT, + func, + user_data, + NULL); + else + source = nm_g_timeout_source_new(timeout_msec, G_PRIORITY_DEFAULT, func, user_data, NULL); + return nm_g_source_attach(source, NULL); +} + +NM_AUTO_DEFINE_FCN0(GMainContext *, _nm_auto_unref_gmaincontext, g_main_context_unref); +#define nm_auto_unref_gmaincontext nm_auto(_nm_auto_unref_gmaincontext) + +static inline GMainContext * +nm_g_main_context_push_thread_default(GMainContext *context) +{ + /* This function is to work together with nm_auto_pop_gmaincontext. */ + if (G_UNLIKELY(!context)) + context = g_main_context_default(); + g_main_context_push_thread_default(context); + return context; +} + +static inline gboolean +nm_g_main_context_is_thread_default(GMainContext *context) +{ + GMainContext *cur_context; + + cur_context = g_main_context_get_thread_default(); + if (cur_context == context) + return TRUE; + + if (G_UNLIKELY(!cur_context)) + cur_context = g_main_context_default(); + else if (G_UNLIKELY(!context)) + context = g_main_context_default(); + else + return FALSE; + + return (cur_context == context); +} + +static inline GMainContext * +nm_g_main_context_push_thread_default_if_necessary(GMainContext *context) +{ + GMainContext *cur_context; + + cur_context = g_main_context_get_thread_default(); + if (cur_context == context) + return NULL; + + if (G_UNLIKELY(!cur_context)) { + cur_context = g_main_context_default(); + if (cur_context == context) + return NULL; + } else if (G_UNLIKELY(!context)) { + context = g_main_context_default(); + if (cur_context == context) + return NULL; + } + + g_main_context_push_thread_default(context); + return context; +} + +/*****************************************************************************/ + +static inline int +nm_utf8_collate0(const char *a, const char *b) +{ + if (!a) + return !b ? 0 : -1; + if (!b) + return 1; + return g_utf8_collate(a, b); +} + +int nm_strcmp_with_data(gconstpointer a, gconstpointer b, gpointer user_data); +int nm_strcmp_p_with_data(gconstpointer a, gconstpointer b, gpointer user_data); +int nm_strcmp0_p_with_data(gconstpointer a, gconstpointer b, gpointer user_data); +int nm_strcmp_ascii_case_with_data(gconstpointer a, gconstpointer b, gpointer user_data); +int nm_cmp_uint32_p_with_data(gconstpointer p_a, gconstpointer p_b, gpointer user_data); +int nm_cmp_int2ptr_p_with_data(gconstpointer p_a, gconstpointer p_b, gpointer user_data); + +/*****************************************************************************/ + +typedef struct { + const char *name; +} NMUtilsNamedEntry; + +typedef struct { + union { + NMUtilsNamedEntry named_entry; + const char * name; + char * name_mutable; + }; + union { + const char *value_str; + char * value_str_mutable; + gpointer value_ptr; + }; +} NMUtilsNamedValue; + +#define NM_UTILS_NAMED_VALUE_INIT(n, v) \ + { \ + .name = (n), .value_ptr = (v) \ + } + +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); + +#define nm_utils_named_values_from_strdict(hash, out_len, array, out_allocated_buffer) \ + nm_utils_named_values_from_strdict_full((hash), \ + (out_len), \ + nm_strcmp_p_with_data, \ + NULL, \ + (array), \ + G_N_ELEMENTS(array), \ + (out_allocated_buffer)) + +gssize nm_utils_named_value_list_find(const NMUtilsNamedValue *arr, + gsize len, + const char * name, + gboolean sorted); + +gboolean nm_utils_named_value_list_is_sorted(const NMUtilsNamedValue *arr, + gsize len, + gboolean accept_duplicates, + GCompareDataFunc compare_func, + gpointer user_data); + +void nm_utils_named_value_list_sort(NMUtilsNamedValue *arr, + gsize len, + GCompareDataFunc compare_func, + gpointer user_data); + +void nm_utils_named_value_clear_with_g_free(NMUtilsNamedValue *val); + +/*****************************************************************************/ + +gpointer *nm_utils_hash_keys_to_array(GHashTable * hash, + GCompareDataFunc compare_func, + gpointer user_data, + guint * out_len); + +gpointer *nm_utils_hash_values_to_array(GHashTable * hash, + GCompareDataFunc compare_func, + gpointer user_data, + guint * out_len); + +static inline const char ** +nm_utils_strdict_get_keys(const GHashTable *hash, gboolean sorted, guint *out_length) +{ + return (const char **) nm_utils_hash_keys_to_array((GHashTable *) hash, + sorted ? nm_strcmp_p_with_data : NULL, + NULL, + out_length); +} + +gboolean nm_utils_hashtable_equal(const GHashTable *a, + const GHashTable *b, + gboolean treat_null_as_empty, + GEqualFunc equal_func); + +gboolean nm_utils_hashtable_cmp_equal(const GHashTable *a, + const GHashTable *b, + GCompareDataFunc cmp_values, + gpointer user_data); + +static inline gboolean +nm_utils_hashtable_same_keys(const GHashTable *a, const GHashTable *b) +{ + return nm_utils_hashtable_cmp_equal(a, b, NULL, NULL); +} + +int nm_utils_hashtable_cmp(const GHashTable *a, + const GHashTable *b, + gboolean do_fast_precheck, + GCompareDataFunc cmp_keys, + GCompareDataFunc cmp_values, + gpointer user_data); + +char **nm_utils_strv_make_deep_copied(const char **strv); + +char **nm_utils_strv_make_deep_copied_n(const char **strv, gsize len); + +static inline char ** +nm_utils_strv_make_deep_copied_nonnull(const char **strv) +{ + return nm_utils_strv_make_deep_copied(strv) ?: g_new0(char *, 1); +} + +char **_nm_utils_strv_dup(const char *const *strv, gssize len, gboolean deep_copied); + +#define nm_utils_strv_dup(strv, len, deep_copied) \ + _nm_utils_strv_dup(NM_CAST_STRV_CC(strv), (len), (deep_copied)) + +const char **_nm_utils_strv_dup_packed(const char *const *strv, gssize len); + +#define nm_utils_strv_dup_packed(strv, len) _nm_utils_strv_dup_packed(NM_CAST_STRV_CC(strv), (len)) + +#define nm_utils_strv_dup_shallow_maybe_a(alloca_maxlen, strv, len, to_free) \ + ({ \ + const char *const *const _strv = NM_CAST_STRV_CC(strv); \ + const gssize _len = (len); \ + const char ** _result = NULL; \ + const char ***const _to_free = (to_free); \ + \ + G_STATIC_ASSERT_EXPR((alloca_maxlen) <= 500u / sizeof(const char *)); \ + G_STATIC_ASSERT_EXPR((alloca_maxlen) > 0u); \ + nm_assert(_to_free && !*_to_free); \ + \ + if (_len >= 0 || _strv) { \ + const gsize _l = (_len < 0) ? NM_PTRARRAY_LEN(_strv) : ((gsize) _len); \ + \ + if (G_LIKELY(_l < (alloca_maxlen))) { \ + _result = g_newa(const char *, _l + 1); \ + } else { \ + _result = g_new(const char *, _l + 1); \ + *_to_free = _result; \ + } \ + if (_l > 0) \ + memcpy(_result, _strv, _l * sizeof(const char *)); \ + _result[_l] = NULL; \ + } \ + \ + _result; \ + }) + +/*****************************************************************************/ + +GSList *nm_utils_g_slist_find_str(const GSList *list, const char *needle); + +int nm_utils_g_slist_strlist_cmp(const GSList *a, const GSList *b); + +char *nm_utils_g_slist_strlist_join(const GSList *a, const char *separator); + +/*****************************************************************************/ + +static inline guint +nm_g_array_len(const GArray *arr) +{ + return arr ? arr->len : 0u; +} + +static inline void +nm_g_array_unref(GArray *arr) +{ + if (arr) + g_array_unref(arr); +} + +#define nm_g_array_append_new(arr, type) \ + ({ \ + GArray *const _arr = (arr); \ + guint _len; \ + \ + nm_assert(_arr); \ + _len = _arr->len; \ + nm_assert(_len < G_MAXUINT); \ + g_array_set_size(_arr, _len + 1u); \ + &g_array_index(arr, type, _len); \ + }) + +/*****************************************************************************/ + +static inline GPtrArray * +nm_g_ptr_array_ref(GPtrArray *arr) +{ + return arr ? g_ptr_array_ref(arr) : NULL; +} + +static inline void +nm_g_ptr_array_unref(GPtrArray *arr) +{ + if (arr) + g_ptr_array_unref(arr); +} + +#define nm_g_ptr_array_set(pdst, val) \ + ({ \ + GPtrArray **_pdst = (pdst); \ + GPtrArray * _val = (val); \ + gboolean _changed = FALSE; \ + \ + nm_assert(_pdst); \ + \ + if (*_pdst != _val) { \ + _nm_unused gs_unref_ptrarray GPtrArray *_old = *_pdst; \ + \ + *_pdst = nm_g_ptr_array_ref(_val); \ + _changed = TRUE; \ + } \ + _changed; \ + }) + +#define nm_g_ptr_array_set_take(pdst, val) \ + ({ \ + GPtrArray **_pdst = (pdst); \ + GPtrArray * _val = (val); \ + gboolean _changed = FALSE; \ + \ + nm_assert(_pdst); \ + \ + if (*_pdst != _val) { \ + _nm_unused gs_unref_ptrarray GPtrArray *_old = *_pdst; \ + \ + *_pdst = _val; \ + _changed = TRUE; \ + } else { \ + nm_g_ptr_array_unref(_val); \ + } \ + _changed; \ + }) + +static inline guint +nm_g_ptr_array_len(const GPtrArray *arr) +{ + return arr ? arr->len : 0u; +} + +static inline gpointer * +nm_g_ptr_array_pdata(const GPtrArray *arr) +{ + return arr ? arr->pdata : NULL; +} + +GPtrArray *_nm_g_ptr_array_copy(GPtrArray * array, + GCopyFunc func, + gpointer user_data, + GDestroyNotify element_free_func); + +/** + * nm_g_ptr_array_copy: + * @array: the #GPtrArray to clone. + * @func: the copy function. + * @user_data: the user data for the copy function + * @element_free_func: the free function of the elements. @array MUST have + * the same element_free_func. This argument is only used on older + * glib, that doesn't support g_ptr_array_copy(). + * + * This is a replacement for g_ptr_array_copy(), which is not available + * before glib 2.62. Since GPtrArray does not allow to access the internal + * element_free_func, we cannot add a compatibility implementation of g_ptr_array_copy() + * and the user must provide a suitable destroy function. + * + * Note that the @element_free_func MUST correspond to free function set in @array. + */ +#if GLIB_CHECK_VERSION(2, 62, 0) + #define nm_g_ptr_array_copy(array, func, user_data, element_free_func) \ + ({ \ + _nm_unused GDestroyNotify const _element_free_func = (element_free_func); \ + \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; \ + g_ptr_array_copy((array), (func), (user_data)); \ + G_GNUC_END_IGNORE_DEPRECATIONS; \ + }) +#else + #define nm_g_ptr_array_copy(array, func, user_data, element_free_func) \ + _nm_g_ptr_array_copy((array), (func), (user_data), (element_free_func)) +#endif + +/*****************************************************************************/ + +static inline GHashTable * +nm_g_hash_table_ref(GHashTable *hash) +{ + return hash ? g_hash_table_ref(hash) : NULL; +} + +static inline void +nm_g_hash_table_unref(GHashTable *hash) +{ + if (hash) + g_hash_table_unref(hash); +} + +static inline guint +nm_g_hash_table_size(GHashTable *hash) +{ + return hash ? g_hash_table_size(hash) : 0u; +} + +static inline gpointer +nm_g_hash_table_lookup(GHashTable *hash, gconstpointer key) +{ + return hash ? g_hash_table_lookup(hash, key) : NULL; +} + +static inline gboolean +nm_g_hash_table_contains(GHashTable *hash, gconstpointer key) +{ + return hash ? g_hash_table_contains(hash, key) : FALSE; +} + +static inline gboolean +nm_g_hash_table_remove(GHashTable *hash, gconstpointer key) +{ + return hash ? g_hash_table_remove(hash, key) : FALSE; +} + +/*****************************************************************************/ + +gboolean nm_utils_ptrarray_is_sorted(gconstpointer * list, + gsize len, + gboolean require_strict, + GCompareDataFunc cmpfcn, + gpointer user_data); + +gssize nm_utils_ptrarray_find_binary_search(gconstpointer * list, + gsize len, + gconstpointer needle, + GCompareDataFunc cmpfcn, + gpointer user_data); + +gssize nm_utils_ptrarray_find_binary_search_range(gconstpointer * list, + gsize len, + gconstpointer needle, + GCompareDataFunc cmpfcn, + gpointer user_data, + gssize * out_idx_first, + gssize * out_idx_last); + +#define nm_utils_strv_find_binary_search(strv, len, needle) \ + ({ \ + const char *const *const _strv = NM_CAST_STRV_CC(strv); \ + const gsize _len = (len); \ + const char *const _needle = (needle); \ + \ + nm_assert(_len == 0 || _strv); \ + nm_assert(_needle); \ + \ + nm_utils_ptrarray_find_binary_search((gconstpointer *) _strv, \ + _len, \ + _needle, \ + nm_strcmp_with_data, \ + NULL); \ + }) + +gssize nm_utils_array_find_binary_search(gconstpointer list, + gsize elem_size, + gsize len, + gconstpointer needle, + GCompareDataFunc cmpfcn, + gpointer user_data); + +gssize nm_utils_ptrarray_find_first(gconstpointer *list, gssize len, gconstpointer needle); + +/*****************************************************************************/ + +void _nm_utils_strv_sort(const char **strv, gssize len); +#define nm_utils_strv_sort(strv, len) _nm_utils_strv_sort(NM_CAST_STRV_MC(strv), len) + +int +_nm_utils_strv_cmp_n(const char *const *strv1, gssize len1, const char *const *strv2, gssize len2); + +#define nm_utils_strv_cmp_n(strv1, len1, strv2, len2) \ + _nm_utils_strv_cmp_n(NM_CAST_STRV_CC(strv1), (len1), NM_CAST_STRV_CC(strv2), (len2)) + +#define nm_utils_strv_equal(strv1, strv2) (nm_utils_strv_cmp_n((strv1), -1, (strv2), -1) == 0) + +/*****************************************************************************/ + +#define NM_UTILS_NSEC_PER_SEC ((gint64) 1000000000) +#define NM_UTILS_USEC_PER_SEC ((gint64) 1000000) +#define NM_UTILS_MSEC_PER_SEC ((gint64) 1000) +#define NM_UTILS_NSEC_PER_MSEC ((gint64) 1000000) + +static inline gint64 +NM_UTILS_NSEC_TO_MSEC_CEIL(gint64 nsec) +{ + return (nsec + (NM_UTILS_NSEC_PER_MSEC - 1)) / NM_UTILS_NSEC_PER_MSEC; +} + +/*****************************************************************************/ + +int nm_utils_fd_wait_for_event(int fd, int event, gint64 timeout_nsec); +ssize_t nm_utils_fd_read_loop(int fd, void *buf, size_t nbytes, bool do_poll); +int nm_utils_fd_read_loop_exact(int fd, void *buf, size_t nbytes, bool do_poll); + +/*****************************************************************************/ + +#define NM_DEFINE_GDBUS_ARG_INFO_FULL(name_, ...) \ + ((GDBusArgInfo *) (&((const GDBusArgInfo){.ref_count = -1, .name = name_, __VA_ARGS__}))) + +#define NM_DEFINE_GDBUS_ARG_INFO(name_, a_signature) \ + NM_DEFINE_GDBUS_ARG_INFO_FULL(name_, .signature = a_signature, ) + +#define NM_DEFINE_GDBUS_ARG_INFOS(...) \ + ((GDBusArgInfo **) ((const GDBusArgInfo *[]){ \ + __VA_ARGS__ NULL, \ + })) + +#define NM_DEFINE_GDBUS_PROPERTY_INFO(name_, ...) \ + ((GDBusPropertyInfo *) (&( \ + (const GDBusPropertyInfo){.ref_count = -1, .name = name_, __VA_ARGS__}))) + +#define NM_DEFINE_GDBUS_PROPERTY_INFO_READABLE(name_, m_signature) \ + NM_DEFINE_GDBUS_PROPERTY_INFO(name_, \ + .signature = m_signature, \ + .flags = G_DBUS_PROPERTY_INFO_FLAGS_READABLE, ) + +#define NM_DEFINE_GDBUS_PROPERTY_INFOS(...) \ + ((GDBusPropertyInfo **) ((const GDBusPropertyInfo *[]){ \ + __VA_ARGS__ NULL, \ + })) + +#define NM_DEFINE_GDBUS_SIGNAL_INFO_INIT(name_, ...) \ + { \ + .ref_count = -1, .name = name_, __VA_ARGS__ \ + } + +#define NM_DEFINE_GDBUS_SIGNAL_INFO(name_, ...) \ + ((GDBusSignalInfo *) (&( \ + (const GDBusSignalInfo) NM_DEFINE_GDBUS_SIGNAL_INFO_INIT(name_, __VA_ARGS__)))) + +#define NM_DEFINE_GDBUS_SIGNAL_INFOS(...) \ + ((GDBusSignalInfo **) ((const GDBusSignalInfo *[]){ \ + __VA_ARGS__ NULL, \ + })) + +#define NM_DEFINE_GDBUS_METHOD_INFO_INIT(name_, ...) \ + { \ + .ref_count = -1, .name = name_, __VA_ARGS__ \ + } + +#define NM_DEFINE_GDBUS_METHOD_INFO(name_, ...) \ + ((GDBusMethodInfo *) (&( \ + (const GDBusMethodInfo) NM_DEFINE_GDBUS_METHOD_INFO_INIT(name_, __VA_ARGS__)))) + +#define NM_DEFINE_GDBUS_METHOD_INFOS(...) \ + ((GDBusMethodInfo **) ((const GDBusMethodInfo *[]){ \ + __VA_ARGS__ NULL, \ + })) + +#define NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(name_, ...) \ + { \ + .ref_count = -1, .name = name_, __VA_ARGS__ \ + } + +#define NM_DEFINE_GDBUS_INTERFACE_INFO(name_, ...) \ + ((GDBusInterfaceInfo *) (&( \ + (const GDBusInterfaceInfo) NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(name_, __VA_ARGS__)))) + +#define NM_DEFINE_GDBUS_INTERFACE_VTABLE(...) \ + ((GDBusInterfaceVTable *) (&((const GDBusInterfaceVTable){__VA_ARGS__}))) + +/*****************************************************************************/ + +guint64 nm_utils_get_start_time_for_pid(pid_t pid, char *out_state, pid_t *out_ppid); + +static inline gboolean +nm_utils_process_state_is_dead(char pstate) +{ + /* "/proc/[pid]/stat" returns a state as the 3rd fields (see `man 5 proc`). + * Some of these states indicate the process is effectively dead (or a zombie). + */ + return NM_IN_SET(pstate, 'Z', 'x', 'X'); +} + +/*****************************************************************************/ + +typedef struct _NMUtilsUserData NMUtilsUserData; + +NMUtilsUserData *_nm_utils_user_data_pack(int nargs, gconstpointer *args); + +#define nm_utils_user_data_pack(...) \ + _nm_utils_user_data_pack(NM_NARG(__VA_ARGS__), (gconstpointer[]){__VA_ARGS__}) + +void _nm_utils_user_data_unpack(NMUtilsUserData *user_data, int nargs, ...); + +#define nm_utils_user_data_unpack(user_data, ...) \ + _nm_utils_user_data_unpack(user_data, NM_NARG(__VA_ARGS__), __VA_ARGS__) + +/*****************************************************************************/ + +typedef void (*NMUtilsInvokeOnIdleCallback)(gpointer user_data, GCancellable *cancellable); + +void nm_utils_invoke_on_idle(GCancellable * cancellable, + NMUtilsInvokeOnIdleCallback callback, + gpointer callback_user_data); + +void nm_utils_invoke_on_timeout(guint timeout_msec, + GCancellable * cancellable, + NMUtilsInvokeOnIdleCallback callback, + gpointer callback_user_data); + +/*****************************************************************************/ + +GSource *nm_utils_g_main_context_create_integrate_source(GMainContext *internal); + +/*****************************************************************************/ + +static inline GPtrArray * +nm_strv_ptrarray_ensure(GPtrArray **p_arr) +{ + nm_assert(p_arr); + + if (G_UNLIKELY(!*p_arr)) + *p_arr = g_ptr_array_new_with_free_func(g_free); + + return *p_arr; +} + +static inline const char *const * +nm_strv_ptrarray_get_unsafe(GPtrArray *arr, guint *out_len) +{ + /* warning: the GPtrArray is not NULL terminated. So, it + * isn't really a strv array (sorry the misnomer). That's why + * the function is potentially "unsafe" and you must provide a + * out_len parameter. */ + if (!arr || arr->len == 0) { + *out_len = 0; + return NULL; + } + *out_len = arr->len; + return (const char *const *) arr->pdata; +} + +static inline GPtrArray * +nm_strv_ptrarray_clone(const GPtrArray *src, gboolean null_if_empty) +{ + if (!src || (null_if_empty && src->len == 0)) + return NULL; + return nm_g_ptr_array_copy((GPtrArray *) src, nm_copy_func_g_strdup, NULL, g_free); +} + +static inline void +nm_strv_ptrarray_add_string_take(GPtrArray *cmd, char *str) +{ + nm_assert(cmd); + nm_assert(str); + + g_ptr_array_add(cmd, str); +} + +static inline void +nm_strv_ptrarray_add_string_dup(GPtrArray *cmd, const char *str) +{ + nm_strv_ptrarray_add_string_take(cmd, g_strdup(str)); +} + +#define nm_strv_ptrarray_add_string_concat(cmd, ...) \ + nm_strv_ptrarray_add_string_take((cmd), g_strconcat(__VA_ARGS__, NULL)) + +#define nm_strv_ptrarray_add_string_printf(cmd, ...) \ + nm_strv_ptrarray_add_string_take((cmd), g_strdup_printf(__VA_ARGS__)) + +#define nm_strv_ptrarray_add_int(cmd, val) \ + nm_strv_ptrarray_add_string_take((cmd), nm_strdup_int(val)) + +static inline void +nm_strv_ptrarray_take_gstring(GPtrArray *cmd, GString **gstr) +{ + nm_assert(gstr && *gstr); + + nm_strv_ptrarray_add_string_take(cmd, g_string_free(g_steal_pointer(gstr), FALSE)); +} + +static inline gssize +nm_strv_ptrarray_find_first(const GPtrArray *strv, const char *str) +{ + if (!strv) + return -1; + return nm_utils_strv_find_first((char **) strv->pdata, strv->len, str); +} + +static inline gboolean +nm_strv_ptrarray_contains(const GPtrArray *strv, const char *str) +{ + return nm_strv_ptrarray_find_first(strv, str) >= 0; +} + +static inline int +nm_strv_ptrarray_cmp(const GPtrArray *a, const GPtrArray *b) +{ + /* nm_utils_strv_cmp_n() will treat NULL and empty arrays the same. + * That means, an empty strv array can both be represented by NULL + * and an array of length zero. + * If you need to distinguish between these case, do that yourself. */ + return nm_utils_strv_cmp_n((const char *const *) nm_g_ptr_array_pdata(a), + nm_g_ptr_array_len(a), + (const char *const *) nm_g_ptr_array_pdata(b), + nm_g_ptr_array_len(b)); +} + +/*****************************************************************************/ + +int nm_utils_getpagesize(void); + +/*****************************************************************************/ + +extern const char _nm_hexchar_table_lower[16]; +extern const char _nm_hexchar_table_upper[16]; + +static inline char +nm_hexchar(int x, gboolean upper_case) +{ + return upper_case ? _nm_hexchar_table_upper[x & 15] : _nm_hexchar_table_lower[x & 15]; +} + +static inline gboolean +nm_ascii_is_ctrl(char ch) +{ + /* 0 to ' '-1 is the C0 range. + * + * Other ranges may also be considered control characters, but NOT + * CONSIDERED by this function. For example: + * - DEL (127) is also a control character. + * - SP (' ', 0x20) is also considered a control character. + * - DEL+1 (0x80) to 0x9F is C1 range. + * - NBSP (0xA0) and SHY (0xAD) are ISO 8859 special characters + */ + return ((guchar) ch) < ' '; +} + +static inline gboolean +nm_ascii_is_ctrl_or_del(char ch) +{ + return ((guchar) ch) < ' ' || ch == 127; +} + +static inline gboolean +nm_ascii_is_non_ascii(char ch) +{ + return ((guchar) ch) > 127; +} + +static inline gboolean +nm_ascii_is_regular(char ch) +{ + /* same as(!nm_ascii_is_ctrl_or_del(ch) && !nm_ascii_is_non_ascii(ch)) */ + return ch >= ' ' && ch < 127; +} + +char *nm_utils_bin2hexstr_full(gconstpointer addr, + gsize length, + char delimiter, + gboolean upper_case, + char * out); + +#define nm_utils_bin2hexstr_a(addr, length, delimiter, upper_case, str_to_free) \ + ({ \ + gconstpointer _addr = (addr); \ + gsize _length = (length); \ + char _delimiter = (delimiter); \ + char ** _str_to_free = (str_to_free); \ + char * _s; \ + gsize _s_len; \ + \ + nm_assert(_str_to_free); \ + \ + _s_len = _length == 0 ? 1u : (_delimiter == '\0' ? _length * 2u + 1u : _length * 3u); \ + if (_s_len < 100) \ + _s = g_alloca(_s_len); \ + else { \ + _s = g_malloc(_s_len); \ + *_str_to_free = _s; \ + } \ + nm_utils_bin2hexstr_full(_addr, _length, _delimiter, (upper_case), _s); \ + }) + +static inline const char * +nm_ether_addr_to_string(const NMEtherAddr *ether_addr, char sbuf[static(sizeof(NMEtherAddr) * 3)]) +{ + nm_assert(ether_addr); + nm_assert(sbuf); + + return nm_utils_bin2hexstr_full(ether_addr, sizeof(NMEtherAddr), ':', TRUE, sbuf); +} + +#define nm_ether_addr_to_string_a(ether_addr) \ + nm_ether_addr_to_string((ether_addr), g_alloca(sizeof(NMEtherAddr) * 3)) + +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); + +#define nm_utils_hexstr2bin_buf(hexstr, \ + allow_0x_prefix, \ + delimiter_required, \ + delimiter_candidates, \ + buffer) \ + nm_utils_hexstr2bin_full((hexstr), \ + (allow_0x_prefix), \ + (delimiter_required), \ + FALSE, \ + (delimiter_candidates), \ + G_N_ELEMENTS(buffer), \ + (buffer), \ + G_N_ELEMENTS(buffer), \ + 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); + +/** + * _nm_utils_hwaddr_aton: + * @asc: the ASCII representation of a hardware address + * @buffer: buffer to store the result into. Must have + * at least a size of @buffer_length. + * @buffer_length: the length of the input buffer @buffer. + * The result must fit into that buffer, otherwise + * the function fails and returns %NULL. + * @out_length: the output length in case of success. + * + * Parses @asc and converts it to binary form in @buffer. + * Bytes in @asc can be separated by colons (:), or hyphens (-), but not mixed. + * + * It is like nm_utils_hwaddr_aton(), but contrary to that it + * can parse addresses of any length. That is, you don't need + * to know the length before-hand. + * + * Return value: @buffer, or %NULL if @asc couldn't be parsed. + */ +static inline guint8 * +_nm_utils_hwaddr_aton(const char *asc, gpointer buffer, gsize buffer_length, gsize *out_length) +{ + g_return_val_if_fail(asc, NULL); + g_return_val_if_fail(buffer, NULL); + g_return_val_if_fail(buffer_length > 0, NULL); + g_return_val_if_fail(out_length, NULL); + + return nm_utils_hexstr2bin_full(asc, + FALSE, + TRUE, + FALSE, + ":-", + 0, + buffer, + buffer_length, + out_length); +} + +static inline guint8 * +_nm_utils_hwaddr_aton_exact(const char *asc, gpointer buffer, gsize buffer_length) +{ + g_return_val_if_fail(asc, NULL); + g_return_val_if_fail(buffer, NULL); + g_return_val_if_fail(buffer_length > 0, NULL); + + return nm_utils_hexstr2bin_full(asc, + FALSE, + TRUE, + FALSE, + ":-", + buffer_length, + buffer, + buffer_length, + NULL); +} + +static inline const char * +_nm_utils_hwaddr_ntoa(gconstpointer addr, + gsize addr_len, + gboolean upper_case, + char * buf, + gsize buf_len) +{ + g_return_val_if_fail(addr, NULL); + g_return_val_if_fail(addr_len > 0, NULL); + g_return_val_if_fail(buf, NULL); + if (buf_len < addr_len * 3) + g_return_val_if_reached(NULL); + + return nm_utils_bin2hexstr_full(addr, addr_len, ':', upper_case, buf); +} + +#define _nm_utils_hwaddr_ntoa_maybe_a(addr, addr_len, buf_to_free) \ + ({ \ + gconstpointer const _addr = (addr); \ + const gsize _addr_len = (addr_len); \ + char **const _buf_to_free = (buf_to_free); \ + \ + nm_utils_bin2hexstr_full( \ + _addr, \ + _addr_len, \ + ':', \ + TRUE, \ + nm_malloc_maybe_a(3 * 20, _addr_len ? (_addr_len * 3u) : 1u, _buf_to_free)); \ + }) + +/*****************************************************************************/ + +#define _NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(fcn_name, \ + value_type, \ + value_type_result, \ + entry_cmd, \ + unknown_val_cmd, \ + get_operator, \ + ...) \ + value_type_result fcn_name(const char *name) \ + { \ + static const struct { \ + const char *name; \ + value_type value; \ + } LIST[] = {__VA_ARGS__}; \ + \ + if (NM_MORE_ASSERT_ONCE(5)) { \ + int i; \ + \ + for (i = 0; i < G_N_ELEMENTS(LIST); i++) { \ + nm_assert(LIST[i].name); \ + if (i > 0) \ + nm_assert(strcmp(LIST[i - 1].name, LIST[i].name) < 0); \ + } \ + } \ + \ + { \ + entry_cmd; \ + } \ + \ + if (G_LIKELY(name)) { \ + G_STATIC_ASSERT(G_N_ELEMENTS(LIST) > 1); \ + G_STATIC_ASSERT(G_N_ELEMENTS(LIST) < G_MAXINT / 2 - 10); \ + int imin = 0; \ + int imax = (G_N_ELEMENTS(LIST) - 1); \ + int imid = (G_N_ELEMENTS(LIST) - 1) / 2; \ + \ + for (;;) { \ + const int cmp = strcmp(LIST[imid].name, name); \ + \ + if (G_UNLIKELY(cmp == 0)) \ + return get_operator(LIST[imid].value); \ + \ + if (cmp < 0) \ + imin = imid + 1; \ + else \ + imax = imid - 1; \ + \ + if (G_UNLIKELY(imin > imax)) \ + break; \ + \ + /* integer overflow cannot happen, because LIST is shorter than G_MAXINT/2. */ \ + imid = (imin + imax) / 2; \ + } \ + } \ + \ + { \ + unknown_val_cmd; \ + } \ + } \ + _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON + +#define NM_UTILS_STRING_TABLE_LOOKUP_STRUCT_DEFINE(fcn_name, \ + result_type, \ + entry_cmd, \ + unknown_val_cmd, \ + ...) \ + _NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(fcn_name, \ + result_type, \ + const result_type *, \ + entry_cmd, \ + unknown_val_cmd, \ + &, \ + __VA_ARGS__) + +#define NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(fcn_name, \ + result_type, \ + entry_cmd, \ + unknown_val_cmd, \ + ...) \ + _NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(fcn_name, \ + result_type, \ + result_type, \ + entry_cmd, \ + unknown_val_cmd, \ + , \ + __VA_ARGS__) + +/*****************************************************************************/ + +static inline GTask * +nm_g_task_new(gpointer source_object, + GCancellable * cancellable, + gpointer source_tag, + GAsyncReadyCallback callback, + gpointer callback_data) +{ + GTask *task; + + task = g_task_new(source_object, cancellable, callback, callback_data); + if (source_tag) + g_task_set_source_tag(task, source_tag); + return task; +} + +static inline gboolean +nm_g_task_is_valid(gpointer task, gpointer source_object, gpointer source_tag) +{ + return g_task_is_valid(task, source_object) && g_task_get_source_tag(task) == source_tag; +} + +guint nm_utils_parse_debug_string(const char *string, const GDebugKey *keys, guint nkeys); + +/*****************************************************************************/ + +static inline gboolean +nm_utils_strdup_reset(char **dst, const char *src) +{ + char *old; + + nm_assert(dst); + + if (nm_streq0(*dst, src)) + return FALSE; + old = *dst; + *dst = g_strdup(src); + g_free(old); + return TRUE; +} + +static inline gboolean +nm_utils_strdup_reset_take(char **dst, char *src) +{ + char *old; + + nm_assert(dst); + nm_assert(src != *dst); + + if (nm_streq0(*dst, src)) { + if (src) + g_free(src); + return FALSE; + } + old = *dst; + *dst = src; + g_free(old); + return TRUE; +} + +void nm_indirect_g_free(gpointer arg); + +/*****************************************************************************/ + +void nm_utils_ifname_cpy(char *dst, const char *name); + +typedef enum { + NMU_IFACE_ANY, + NMU_IFACE_KERNEL, + NMU_IFACE_OVS, + NMU_IFACE_OVS_AND_KERNEL, +} NMUtilsIfaceType; + +gboolean nm_utils_ifname_valid_kernel(const char *name, GError **error); + +gboolean nm_utils_ifname_valid(const char *name, NMUtilsIfaceType type, GError **error); + +/*****************************************************************************/ + +static inline GArray * +nm_strvarray_ensure(GArray **p) +{ + if (!*p) { + *p = g_array_new(TRUE, FALSE, sizeof(char *)); + g_array_set_clear_func(*p, nm_indirect_g_free); + } + return *p; +} + +static inline void +nm_strvarray_add(GArray *array, const char *str) +{ + char *s; + + s = g_strdup(str); + g_array_append_val(array, s); +} + +static inline const char * +nm_strvarray_get_idx(GArray *array, guint idx) +{ + nm_assert(array); + nm_assert(idx < array->len); + + return g_array_index(array, const char *, idx); +} + +static inline const char *const * +nm_strvarray_get_strv_non_empty(GArray *arr, guint *length) +{ + if (!arr || arr->len == 0) { + NM_SET_OUT(length, 0); + return NULL; + } + + NM_SET_OUT(length, arr->len); + return &g_array_index(arr, const char *, 0); +} + +static inline char ** +nm_strvarray_get_strv_non_empty_dup(GArray *arr, guint *length) +{ + const char *const *strv; + + if (!arr || arr->len == 0) { + NM_SET_OUT(length, 0); + return NULL; + } + + NM_SET_OUT(length, arr->len); + strv = &g_array_index(arr, const char *, 0); + return nm_utils_strv_dup(strv, arr->len, TRUE); +} + +static inline const char *const * +nm_strvarray_get_strv(GArray **arr, guint *length) +{ + if (!*arr) { + NM_SET_OUT(length, 0); + return (const char *const *) arr; + } + + NM_SET_OUT(length, (*arr)->len); + return &g_array_index(*arr, const char *, 0); +} + +static inline void +nm_strvarray_set_strv(GArray **array, const char *const *strv) +{ + gs_unref_array GArray *array_old = NULL; + + array_old = g_steal_pointer(array); + + if (!strv || !strv[0]) + return; + + nm_strvarray_ensure(array); + for (; strv[0]; strv++) + nm_strvarray_add(*array, strv[0]); +} + +static inline gssize +nm_strvarray_find_first(GArray *strv, const char *needle) +{ + guint i; + + nm_assert(needle); + + if (strv) { + for (i = 0; i < strv->len; i++) { + if (nm_streq(needle, g_array_index(strv, const char *, i))) + return i; + } + } + return -1; +} + +static inline gboolean +nm_strvarray_remove_first(GArray *strv, const char *needle) +{ + gssize idx; + + nm_assert(needle); + + idx = nm_strvarray_find_first(strv, needle); + if (idx < 0) + return FALSE; + g_array_remove_index(strv, idx); + return TRUE; +} + +/*****************************************************************************/ + +struct _NMVariantAttributeSpec { + char * name; + const GVariantType *type; + bool v4 : 1; + bool v6 : 1; + bool no_value : 1; + bool consumes_rest : 1; + char str_type; +}; + +typedef struct _NMVariantAttributeSpec NMVariantAttributeSpec; + +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); + +char *_nm_utils_format_variant_attributes(GHashTable * attributes, + const NMVariantAttributeSpec *const *spec, + char attr_separator, + char key_value_separator); + +/*****************************************************************************/ + +gboolean nm_utils_is_localhost(const char *name); + +gboolean nm_utils_is_specific_hostname(const char *name); + +char * nm_utils_uid_to_name(uid_t uid); +gboolean nm_utils_name_to_uid(const char *name, uid_t *out_uid); + +/*****************************************************************************/ + +double nm_utils_exp10(gint16 e); + +/*****************************************************************************/ + +gboolean _nm_utils_is_empty_ssid_arr(const guint8 *ssid, gsize len); +gboolean _nm_utils_is_empty_ssid_gbytes(GBytes *ssid); +char * _nm_utils_ssid_to_string_arr(const guint8 *ssid, gsize len); +char * _nm_utils_ssid_to_string_gbytes(GBytes *ssid); + +/*****************************************************************************/ + +gboolean nm_utils_is_valid_path_component(const char *name); +const char *NM_ASSERT_VALID_PATH_COMPONENT(const char *name); + +#define NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE 100 + +const char * +nm_utils_sysctl_ip_conf_path(int addr_family, char *buf, const char *ifname, const char *property); + +gboolean nm_utils_sysctl_ip_conf_is_path(int addr_family, + const char *path, + const char *ifname, + const char *property); + +/*****************************************************************************/ + +void nm_crypto_md5_hash(const guint8 *salt, + gsize salt_len, + const guint8 *password, + gsize password_len, + guint8 * buffer, + gsize buflen); + +/*****************************************************************************/ + +char *nm_utils_get_process_exit_status_desc(int status); + +/*****************************************************************************/ + +void nm_utils_thread_local_register_destroy(gpointer tls_data, GDestroyNotify destroy_notify); + +#endif /* __NM_SHARED_UTILS_H__ */ |