NetworkManager/src/libnm-glib-aux/nm-shared-utils.h
Rahul Rajesh 29c8bbe21a platform: add support for GENEVE tunnels
GENEVE (Generic Network Virtualization Encapsulation) is a network
tunneling protocol that provides a flexible encapsulation format for
overlay networks. It uses UDP as the transport protocol and supports
variable-length metadata in the tunnel header.

This patch adds GENEVE tunnel to NM's platform layer:

- Add platform API functions (nm_platform_link_geneve_add,
  nm_platform_link_get_lnk_geneve)

- Netlink message parsing for the following attributes:
  * IFLA_GENEVE_ID - VNI (Virtual Network Identifier)
  IPv4 and IPv6 remote
  * IFLA_GENEVE_REMOTE
  * IFLA_GENEVE_REMOTE6
  TTL, TOS, and DF flags
  * IFLA_GENEVE_TTL
  * IFLA_GENEVE_TOS
  * IFLA_GENEVE_DF
  UDP destination port
  * IFLA_GENEVE_PORT

- Add test cases for GENEVE tunnel creation and detection with two test
  modes covering IPv4 and IPv6.

The implementation tries to follow the same patterns as other tunnel
types (GRE, VXLAN, etc.) and integrates with the existing platform
abstraction layer.
2026-02-17 15:21:03 -05:00

3557 lines
151 KiB
C

/* 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_bool(value) nm_assert(NM_IN_SET((value), 0, 1))
#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() nm_assert(_nm_assert_on_main_thread())
#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 _nm_packed {
/* No type, empty value */
NM_PORT_KIND_NONE,
NM_PORT_KIND_BOND,
NM_PORT_KIND_BRIDGE,
} NMPortKind;
/*****************************************************************************/
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_GENEVE,
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_IPVLAN,
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_VTI,
NM_LINK_TYPE_VTI6,
NM_LINK_TYPE_VXLAN,
NM_LINK_TYPE_WIREGUARD,
#define _NM_LINK_TYPE_SW_LAST NM_LINK_TYPE_WIREGUARD
/* Software types with ports */
#define _NM_LINK_TYPE_SW_CONTROLLER_FIRST NM_LINK_TYPE_BRIDGE
NM_LINK_TYPE_BRIDGE,
NM_LINK_TYPE_BOND,
NM_LINK_TYPE_HSR,
NM_LINK_TYPE_TEAM,
#define _NM_LINK_TYPE_SW_CONTROLLER_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_CONTROLLER_FIRST);
return link_type >= _NM_LINK_TYPE_SW_FIRST && link_type <= _NM_LINK_TYPE_SW_CONTROLLER_LAST;
}
static inline gboolean
nm_link_type_supports_ports(NMLinkType link_type)
{
return link_type >= _NM_LINK_TYPE_SW_CONTROLLER_FIRST
&& link_type <= _NM_LINK_TYPE_SW_CONTROLLER_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__))
union _NMIPAddr;
extern const union _NMIPAddr nm_ip_addr_zero;
/* Let's reuse nm_ip_addr_zero also for nm_ether_addr_zero. It's a union that
* also contains a NMEtherAddr field. */
#define nm_ether_addr_zero (*((const NMEtherAddr *) &nm_ip_addr_zero))
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;
}
static inline gboolean
nm_ether_addr_is_zero(const NMEtherAddr *a)
{
return nm_memeq(a, &nm_ether_addr_zero, sizeof(NMEtherAddr));
}
guint nm_ether_addr_hash(const NMEtherAddr *a);
/*****************************************************************************/
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}}
/**
* nm_utils_ipv6_addr_set_interface_identifier:
* @addr: output token encoded as %in6_addr
* @iid: %NMUtilsIPv6IfaceId interface identifier
*
* Converts the %NMUtilsIPv6IfaceId to an %in6_addr (suitable for use
* with Linux platform). This only copies the lower 8 bytes, ignoring
* the /64 network prefix which is expected to be all-zero for a valid
* token.
*/
static inline void
nm_utils_ipv6_addr_set_interface_identifier(struct in6_addr *addr, const NMUtilsIPv6IfaceId *iid)
{
memcpy(addr->s6_addr + 8, &iid->id_u8, 8);
}
/**
* nm_utils_ipv6_interface_identifier_get_from_addr:
* @iid: output %NMUtilsIPv6IfaceId interface identifier set from the token
* @addr: token encoded as %in6_addr
*
* Converts the %in6_addr encoded token (as used by Linux platform) to
* the interface identifier.
*/
static inline void
nm_utils_ipv6_interface_identifier_get_from_addr(NMUtilsIPv6IfaceId *iid,
const struct in6_addr *addr)
{
memcpy(iid, addr->s6_addr + 8, 8);
}
gboolean nm_utils_ipv6_interface_identifier_get_from_token(NMUtilsIPv6IfaceId *iid,
const char *token);
const char *nm_utils_inet6_interface_identifier_to_token(const 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);
/*****************************************************************************/
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 *)
#define NM_PTRARRAY_EMPTY_NEW(type) (g_new0(type, 1))
static inline void
nm_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_strbuf_init_arr(buf, p_buf_ptr, p_buf_len) \
G_STMT_START \
{ \
G_STATIC_ASSERT(G_N_ELEMENTS(buf) == sizeof(buf) && sizeof(buf) > sizeof(char *)); \
nm_strbuf_init((buf), sizeof(buf), (p_buf_ptr), (p_buf_len)); \
} \
G_STMT_END
void nm_strbuf_append(char **buf, gsize *len, const char *format, ...) _nm_printf(3, 4);
void nm_strbuf_append_c(char **buf, gsize *len, char c);
void nm_strbuf_append_str(char **buf, gsize *len, const char *str);
void nm_strbuf_append_bin(char **buf, gsize *len, gconstpointer str, gsize str_len);
void nm_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');
}
/*****************************************************************************/
static inline GBytes *
nm_g_bytes_ref(GBytes *b)
{
if (b)
g_bytes_ref(b);
return b;
}
/*****************************************************************************/
GBytes *nm_g_bytes_get_empty(void);
GBytes *nm_g_bytes_new_from_str(const char *str);
GBytes *nm_g_bytes_new_from_variant_ay(GVariant *var);
static inline gboolean
nm_g_bytes_equal0(const GBytes *a, const GBytes *b)
{
return a == b || (a && b && g_bytes_equal(a, b));
}
gboolean nm_g_bytes_equal_mem(GBytes *bytes, gconstpointer mem_data, gsize mem_len);
GVariant *nm_g_bytes_to_variant_ay(const GBytes *bytes);
GHashTable *nm_strdict_clone(GHashTable *src);
GVariant *nm_strdict_to_variant_ass(GHashTable *strdict);
GVariant *nm_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_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_STRSPLIT_SET_FLAGS_STRSTRIP, if a value gets
* empty after strstrip(), it also gets removed. */
NM_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY = (1u << 0),
/* %NM_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_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_STRSPLIT_SET_FLAGS_ESCAPED), then
* the backslash escapes are removed from the result.
*/
NM_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_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_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING,
* trailing whitespace escaped by backslash are not stripped. */
NM_STRSPLIT_SET_FLAGS_STRSTRIP = (1u << 2),
/* This implies %NM_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_STRSPLIT_SET_FLAGS_STRSTRIP).
*
* Note that with %NM_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_strsplit_set_full(),
* then all characters (including backslash) are treated verbatim, except:
*
* - "\\$DELIMITER" (escaped delimiter)
* - "\\\\" (escaped backslash)
* - "\\$SPACE" (escaped space) (only with %NM_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_STRSPLIT_SET_FLAGS_ESCAPED = (1u << 3),
} NMUtilsStrsplitSetFlags;
const char **
nm_strsplit_set_full(const char *str, const char *delimiter, NMUtilsStrsplitSetFlags flags);
static inline const char **
nm_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_strsplit_set_full(str, delimiters, NM_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY);
}
static inline const char **
nm_strsplit_set(const char *str, const char *delimiters)
{
return nm_strsplit_set_full(str, delimiters, NM_STRSPLIT_SET_FLAGS_NONE);
}
gssize _nm_strv_find_first(const char *const *list, gssize len, const char *needle);
#define nm_strv_find_first(list, len, needle) \
_nm_strv_find_first(NM_CAST_STRV_CC(list), (len), (needle))
#define nm_strv_contains(list, len, needle) (nm_strv_find_first((list), (len), (needle)) >= 0)
gboolean nm_strv_has_duplicate(const char *const *list, gssize len, gboolean is_sorted);
const char **nm_strv_cleanup_const(const char **strv, gboolean no_empty, gboolean no_duplicates);
char **
nm_strv_cleanup(char **strv, gboolean strip_whitespace, gboolean no_empty, gboolean no_duplicates);
gboolean nm_strv_is_same_unordered(const char *const *strv1,
gssize len1,
const char *const *strv2,
gssize len2);
/*****************************************************************************/
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_strsplit_set_full(str,
delimiters,
NM_STRSPLIT_SET_FLAGS_ESCAPED | NM_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_strsplit_set_full(str,
",",
NM_STRSPLIT_SET_FLAGS_STRSTRIP
| NM_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
/*****************************************************************************/
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);
/*****************************************************************************/
#define NM_UTILS_TO_STRING_BUFFER_SIZE 2096
extern _nm_thread_local char _nm_utils_to_string_buffer[NM_UTILS_TO_STRING_BUFFER_SIZE];
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) \
case v: \
s = "" n ""; \
break;
#define NM_UTILS_ENUM2STR_IGNORE(v) \
case v: \
break;
#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) { \
NM_VA_ARGS_JOIN(, __VA_ARGS__) \
}; \
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_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON
_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);
_nm_warn_unused_result static inline GDestroyNotify
_nm_get_warn_unused_result_gdestroynotify(GDestroyNotify f)
{
return f;
}
#define nm_g_slice_free_fcn1(mem_size) \
_nm_get_warn_unused_result_gdestroynotify(({ \
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_COMMAND_FAILED, /*< nick=CommandFailed >*/
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_STRICTLY_UNMANAGED_DEVICE,
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__))
/*****************************************************************************/
void nm_gobject_notify_together_by_pspec_v(gpointer obj,
const GParamSpec *const *param_specs,
gsize param_specs_len);
#define nm_gobject_notify_together_by_pspec(obj, ...) \
G_STMT_START \
{ \
const GParamSpec *const _arr[] = {__VA_ARGS__}; \
\
G_STATIC_ASSERT(NM_NARG(__VA_ARGS__) == G_N_ELEMENTS(_arr)); \
\
nm_gobject_notify_together_by_pspec_v((obj), _arr, G_N_ELEMENTS(_arr)); \
} \
G_STMT_END
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_IS_A(param_spec, _value_type, _c_type) \
({ \
const GParamSpec *const _param_spec = (param_spec); \
\
nm_assert(!_param_spec || g_type_is_a(_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_INT(param_spec) \
_NM_G_PARAM_SPEC_CAST(param_spec, G_TYPE_INT, GParamSpecInt)
#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_INT64(param_spec) \
_NM_G_PARAM_SPEC_CAST(param_spec, G_TYPE_INT64, GParamSpecInt64)
#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_ENUM(param_spec) \
_NM_G_PARAM_SPEC_CAST_IS_A(param_spec, G_TYPE_ENUM, GParamSpecEnum)
#define NM_G_PARAM_SPEC_CAST_FLAGS(param_spec) \
_NM_G_PARAM_SPEC_CAST_IS_A(param_spec, G_TYPE_FLAGS, GParamSpecFlags)
#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_INT(param_spec) \
(NM_G_PARAM_SPEC_CAST_INT(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_INT64(param_spec) \
(NM_G_PARAM_SPEC_CAST_INT64(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_ENUM(param_spec) \
(NM_G_PARAM_SPEC_CAST_ENUM(NM_ENSURE_NOT_NULL(param_spec))->default_value)
#define NM_G_PARAM_SPEC_GET_DEFAULT_FLAGS(param_spec) \
(NM_G_PARAM_SPEC_CAST_FLAGS(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,
/* Escape '"' as ASCII "\\042". This is useful when escaping a string so that
* it can be unescaped with `echo -e $PASTE_TEXT`. */
NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_DOUBLE_QUOTE = 0x0004,
/* 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 = 0x0008,
/* 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 = 0x0010,
} 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 *const *nm_utils_buf_utf8safe_escape_strv(const char *const *strv,
gssize strv_len,
NMUtilsStrUtf8SafeFlags flags,
char ***out_to_free);
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_i_0(void);
GVariant *nm_g_variant_singleton_s_empty(void);
GVariant *nm_g_variant_singleton_au(void);
GVariant *nm_g_variant_singleton_aay(void);
GVariant *nm_g_variant_singleton_as(void);
GVariant *nm_g_variant_singleton_aLsvI(void);
GVariant *nm_g_variant_singleton_aLsaLsvII(void);
GVariant *nm_g_variant_singleton_aaLsvI(void);
GVariant *nm_g_variant_singleton_ao(void);
GVariant *nm_g_variant_maybe_singleton_i(gint32 v);
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));
}
int nm_g_variant_type_cmp(const GVariantType *type1, const GVariantType *type2);
int nm_g_variant_cmp(GVariant *value1, GVariant *value2);
static inline void
nm_g_source_destroy_and_unref(GSource *source)
{
/* Note that calling g_source_destroy() on a currently attached source,
* will destroy the user-data of the callback right away (and not only
* during the last g_source_unref()).
*
* This means for example, if the user data itself has the reference to the
* source, then the following would lead to a crash:
*
* g_source_destroy(user_data->my_source);
* // ups, user_data was destroyed (if source was attached).
* g_source_unref(user_data->my_source);
*
* nm_g_source_destroy_and_unref(user_data->my_source) and nm_clear_g_source_inst(&user_data->my_source)
* does not have this problem (of course, afterwards, user_data would be a dangling
* pointer). */
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,
GUnixFDSourceFunc source_func,
gpointer user_data,
GDestroyNotify destroy_notify);
GSource *nm_g_unix_signal_source_new(int signum,
int priority,
GSourceFunc handler,
gpointer user_data,
GDestroyNotify notify);
GSource *nm_g_child_watch_source_new(GPid pid,
int priority,
GChildWatchFunc 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 void
nm_g_idle_add(GSourceFunc func, gpointer user_data)
{
/* g_idle_add() is discouraged because it relies on the guint source IDs.
*
* Usually, you would want to use nm_g_idle_add_source() which returns a GSource*
* instance.
*
* However, if you don't care to ever call g_source_remove() on the source ID, then
* g_idle_add() is fine. But our checkpatch script would complain about it. In
* that case use nm_g_idle_add(), which makes it clear that you really want to
* use g_idle_add() and ignore the source ID. */
g_idle_add(func, user_data);
}
static inline GSource *
nm_g_idle_add_source(GSourceFunc func, gpointer user_data)
{
/* A 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_IDLE, func, user_data, NULL),
NULL);
}
static inline GSource *
nm_g_timeout_add_source(guint timeout_msec, GSourceFunc func, gpointer user_data)
{
/* A 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);
}
gboolean nm_g_timeout_reschedule(GSource **src,
gint64 *p_expiry_msec,
gint64 expiry_msec,
GSourceFunc func,
gpointer user_data);
static inline GSource *
nm_g_timeout_add_seconds_source(guint timeout_sec, GSourceFunc func, gpointer user_data)
{
/* A 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);
}
static inline GSource *
nm_g_unix_fd_add_source(int fd,
GIOCondition condition,
GUnixFDSourceFunc function,
gpointer user_data)
{
/* A convenience function to attach a new unix-fd source to the default GMainContext.
* In that sense it's very similar to g_unix_fd_add() except that it returns a
* reference to the new source. */
return nm_g_source_attach(
nm_g_unix_fd_source_new(fd, condition, G_PRIORITY_DEFAULT, function, user_data, NULL),
NULL);
}
static inline GSource *
nm_g_unix_signal_add_source(int signum, GSourceFunc handler, gpointer user_data)
{
return nm_g_source_attach(
nm_g_unix_signal_source_new(signum, G_PRIORITY_DEFAULT, handler, user_data, NULL),
NULL);
}
static inline GSource *
nm_g_child_watch_add_source(GPid pid, GChildWatchFunc handler, gpointer user_data)
{
return nm_g_source_attach(
nm_g_child_watch_source_new(pid, G_PRIORITY_DEFAULT, handler, user_data, NULL),
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 void
nm_g_main_context_iterate_ready(GMainContext *context)
{
while (g_main_context_iteration(context, FALSE)) {
;
}
}
void nm_g_main_context_iterate_for_msec(GMainContext *context, guint timeout_msec);
gboolean nm_g_main_context_can_acquire(GMainContext *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;
gpointer name_ptr;
};
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_hash_to_array_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_full(hash, \
out_len, \
compare_func, \
user_data, \
provided_buffer, \
provided_buffer_len, \
out_allocated_buffer) \
nm_utils_hash_to_array_full((hash), \
(out_len), \
(compare_func), \
(user_data), \
(provided_buffer), \
(provided_buffer_len), \
(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 NMUtilsNamedValue *
nm_utils_hash_to_array(GHashTable *hash,
GCompareDataFunc compare_func,
gpointer user_data,
guint *out_len)
{
return nm_utils_hash_to_array_full(hash, out_len, compare_func, user_data, NULL, 0, NULL);
}
#define nm_utils_hash_to_array_with_buffer(hash, \
out_len, \
compare_func, \
user_data, \
array, \
out_allocated_buffer) \
nm_utils_hash_to_array_full((hash), \
(out_len), \
(compare_func), \
(user_data), \
(array), \
G_N_ELEMENTS(array), \
(out_allocated_buffer))
static inline const char **
nm_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);
#define nm_strv_empty_new() NM_PTRARRAY_EMPTY_NEW(char *)
char **nm_strv_make_deep_copied(const char **strv);
char **nm_strv_make_deep_copied_n(const char **strv, gsize len);
static inline char **
nm_strv_make_deep_copied_nonnull(const char **strv)
{
return nm_strv_make_deep_copied(strv) ?: nm_strv_empty_new();
}
char **_nm_strv_dup_full(const char *const *strv,
gssize len,
gboolean deep_copied,
gboolean preserve_empty);
#define nm_strv_dup_full(strv, len, deep_copied, preserve_empty) \
_nm_strv_dup_full(NM_CAST_STRV_CC(strv), (len), (deep_copied), (preserve_empty))
#define nm_strv_dup(strv, len, deep_copied) nm_strv_dup_full((strv), (len), (deep_copied), FALSE)
const char **_nm_strv_dup_packed(const char *const *strv, gssize len);
#define nm_strv_dup_packed(strv, len) _nm_strv_dup_packed(NM_CAST_STRV_CC(strv), (len))
#define nm_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 gpointer
nm_g_array_data(const GArray *arr)
{
/* You may want to use nm_g_array_first_p() instead, which can assert
* for the expected type. */
return arr ? arr->data : NULL;
}
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);
}
/* Similar to g_array_index(). The differences are
* - this does nm_assert() checks that the arguments are valid.
* - returns a pointer to the element.
* - it asserts that @idx is <= arr->len. That is, it allows
* to get a pointer after the data, of course, you are not
* allowed to dereference in that case.
* - in particular, unlike nm_g_array_index(), you are allowed to call this
* with "arr" NULL (for index zero) or with "arr->data" NULL
* (for index zero). In that case, NULL is returned.
*
* When accessing index zero, then this returns NULL if-and-only-if
* "arr" is NULL or "arr->data" is NULL. In all other cases, this
* returns the pointer &((Type*) arr->data)[idx]. Note that the pointer
* may not be followed, if "idx" is equal to "arr->len".
*
* The reason to allow access one element past the length is the
* following usage:
*
* ptr = nm_g_array_index_p(arr, Type, 0);
* end = nm_g_array_index_p(arr, Type, length);
* for (; ptr < end; ptr++) { ... }
*
* Another usage is to get a buffer, if the length might be zero. If
* length is zero, you cannot dereference the pointer, but it can be convenient
* to not require special casing:
*
* // length might be zero.
* nm_memdup(nm_g_array_index_p(arr, Type, length), sizeof(Type) * length);
*
* Note that in C, it's valid point one past the end of an array. So getting
* a pointer at index "length" is valid, and what nm_g_array_index_p() allows.
* If you don't need that, nm_g_array_index() is usually preferable,
* because it asserts against access at index "length".
*/
#define nm_g_array_index_p(arr, Type, idx) \
({ \
const GArray *const _arr_55 = (arr); \
const guint _idx_55 = (idx); \
\
nm_assert(_arr_55 || _idx_55 == 0); \
nm_assert(_idx_55 <= (_arr_55 ? _arr_55->len : 0u)); \
nm_assert(!_arr_55 || sizeof(Type) == g_array_get_element_size((GArray *) _arr_55)); \
\
((_arr_55 && _arr_55->data) ? &(((Type *) ((gpointer) _arr_55->data))[_idx_55]) : NULL); \
})
/* Very similar to g_array_index().
* - nm_assert() that arguments are valid.
* - returns an lvalue to the element.
* - similar to nm_g_array_index_p(), but dereferences the pointer.
* - one difference to nm_g_array_index_p() is that it @idx MUST be
* smaller than arr->len (unlike nm_g_array_index_p() which allows
* access one element past the buffer. */
#define nm_g_array_index(arr, Type, idx) \
(*({ \
const GArray *const _arr_55 = (arr); \
const guint _idx_55 = (idx); \
\
nm_assert(_arr_55); \
nm_assert(sizeof(Type) == g_array_get_element_size((GArray *) _arr_55)); \
nm_assert(_idx_55 < _arr_55->len); \
\
&g_array_index((GArray *) _arr_55, Type, _idx_55); \
}))
#define nm_g_array_first(arr, Type) nm_g_array_index(arr, Type, 0)
#define nm_g_array_first_p(arr, Type) nm_g_array_index_p(arr, Type, 0)
/* Same as g_array_index(arr, Type, arr->len-1). */
#define nm_g_array_last(arr, Type) \
(*({ \
const GArray *const _arr = (arr); \
\
nm_assert(_arr); \
nm_assert(sizeof(Type) == g_array_get_element_size((GArray *) _arr)); \
nm_assert(_arr->len > 0); \
\
&g_array_index((GArray *) arr, Type, _arr->len - 1u); \
}))
#define nm_g_array_append_new(arr, Type) \
({ \
GArray *const _arr = (arr); \
guint _len; \
\
nm_assert(_arr); \
nm_assert(sizeof(Type) == g_array_get_element_size(_arr)); \
\
_len = _arr->len; \
\
nm_assert(_len < G_MAXUINT); \
\
g_array_set_size(_arr, _len + 1u); \
&g_array_index(arr, Type, _len); \
})
#define nm_g_array_append_simple(arr, val) \
G_STMT_START \
{ \
/* Similar to `g_array_append_val()`, but `g_array_append_val()`
* only works with lvalues. That makes sense if the value is a larger
* struct and you anyway have a pointer to it. It doesn't make sense
* if you have a list of int and want to append a number literal.
*
* nm_g_array_append_simple() is different. It depends on typeof(val)
* to be compatible. */ \
(*nm_g_array_append_new((arr), typeof(val))) = (val); \
} \
G_STMT_END
/*****************************************************************************/
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;
}
/**
* nm_g_ptr_array_new_clone:
* @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. This function
* must agree with the owner-ship semantics of @func.
*
* This is a replacement for g_ptr_array_copy(), which is not available
* before glib 2.62. Since GPtrArray does not allow one to access the internal
* element_free_func, we cannot add a compatibility implementation of g_ptr_array_copy()
* as the caller must provide the correct element_free_func.
*
* So this is not the same as g_ptr_array_copy() (hence the different name) because
* g_ptr_array_copy() uses the free func of the source array, which we cannot access.
* With g_ptr_array_copy() the copy func must agree with the array's free func.
* Here, it must agree with the provided @element_free_func. This allows for example
* to do a shallow-copy without cloning the elements (which you cannot do with g_ptr_array_copy()).
*/
GPtrArray *nm_g_ptr_array_new_clone(GPtrArray *array,
GCopyFunc func,
gpointer user_data,
GDestroyNotify element_free_func);
/*****************************************************************************/
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;
}
#define nm_g_hash_table_contains_any(hash, ...) \
({ \
GHashTable *const _hash = (hash); \
gconstpointer const _keys[] = {__VA_ARGS__}; \
int _i_key; \
gboolean _contains = FALSE; \
\
if (_hash) { \
for (_i_key = 0; _i_key < (int) G_N_ELEMENTS(_keys); _i_key++) { \
if (g_hash_table_contains(_hash, _keys[_i_key])) { \
_contains = TRUE; \
break; \
} \
} \
} \
\
_contains; \
})
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_ptrarray_find_bsearch(gconstpointer *list,
gsize len,
gconstpointer needle,
GCompareDataFunc cmpfcn,
gpointer user_data);
gssize nm_ptrarray_find_bsearch_range(gconstpointer *list,
gsize len,
gconstpointer needle,
GCompareDataFunc cmpfcn,
gpointer user_data,
gssize *out_idx_first,
gssize *out_idx_last);
#define nm_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_ptrarray_find_bsearch((gconstpointer *) _strv, \
_len, \
_needle, \
nm_strcmp_with_data, \
NULL); \
})
/*****************************************************************************/
#ifdef NM_WANT_NM_ARRAY_FIND_BSEARCH_INLINE
/**
* nm_array_find_bsearch_inline:
*
* An inlined version of nm_array_find_bsearch(). See there.
* Define NM_WANT_NM_ARRAY_FIND_BSEARCH_INLINE to get it.
*/
_nm_always_inline static inline gssize
nm_array_find_bsearch_inline(gconstpointer list,
gsize len,
gsize elem_size,
gconstpointer needle,
GCompareDataFunc cmpfcn,
gpointer user_data)
{
gssize imax;
gssize imid;
gssize imin;
int cmp;
nm_assert(list || len == 0);
nm_assert(cmpfcn);
nm_assert(elem_size > 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;
}
#endif
gssize nm_array_find_bsearch(gconstpointer list,
gsize len,
gsize elem_size,
gconstpointer needle,
GCompareDataFunc cmpfcn,
gpointer user_data);
/*****************************************************************************/
gssize nm_utils_ptrarray_find_first(gconstpointer *list, gssize len, gconstpointer needle);
/*****************************************************************************/
void _nm_strv_sort(const char **strv, gssize len);
#define nm_strv_sort(strv, len) _nm_strv_sort(NM_CAST_STRV_MC(strv), len)
int _nm_strv_cmp_n(const char *const *strv1, gssize len1, const char *const *strv2, gssize len2);
#define nm_strv_cmp_n(strv1, len1, strv2, len2) \
_nm_strv_cmp_n(NM_CAST_STRV_CC(strv1), (len1), NM_CAST_STRV_CC(strv2), (len2))
/* This is like nm_strv_cmp_n(). The difference is that a NULL strv array (strv=NULL,len=-1)
* is treated the same as an empty one (with len=0). */
#define nm_strv_cmp_n_null(strv1, len1, strv2, len2) \
({ \
const char *const *const _strv1 = NM_CAST_STRV_CC(strv1); \
const char *const *const _strv2 = NM_CAST_STRV_CC(strv2); \
const gssize _len1 = (len1); \
const gssize _len2 = (len2); \
\
_nm_strv_cmp_n(_strv1, \
(_len1 >= 0 ? _len1 : (_strv1 ? -1 : 0)), \
_strv2, \
(_len2 >= 0 ? _len2 : (_strv2 ? -1 : 0))); \
})
#define nm_strv_equal_n(strv1, len1, strv2, len2) \
(nm_strv_cmp_n((strv1), (len1), (strv2), (len2)) == 0)
#define nm_strv_equal(strv1, strv2) nm_strv_equal_n((strv1), -1, (strv2), -1)
#define nm_strv_equal_n_null(strv1, len1, strv2, len2) \
(nm_strv_cmp_n_null((strv1), (len1), (strv2), (len2)) == 0)
/*****************************************************************************/
/* nm_arr_insert_at() does @arr[@idx] = @value, but first memmove's
* the elements @arr[@idx..@len-1] one element up. That means, @arr currently
* has @len valid elements, but it must have space for one more element,
* which will be overwritten.
*
* The use case is to have a sorted array (nm_strv_find_binary_search()) and
* to insert the element at he desired index. The caller must make sure that
* @len is large enough to contain one more element. */
#define nm_arr_insert_at(arr, len, idx, value) \
G_STMT_START \
{ \
typeof(*(arr)) *const _arr = (arr); \
typeof(len) _len = (len); \
typeof(idx) _idx = (idx); \
const gsize _len2 = (_len); \
const gsize _idx2 = (_idx); \
\
nm_assert(_arr); \
nm_assert(_NM_INT_NOT_NEGATIVE(_len)); \
nm_assert(_NM_INT_NOT_NEGATIVE(_idx)); \
nm_assert(_idx <= _len); \
\
if (_idx2 != _len2) \
memmove(&_arr[_idx2 + 1u], &_arr[_idx2], sizeof(_arr[0]) * (_len2 - _idx2)); \
\
_arr[_idx2] = (value); \
} \
G_STMT_END
/* nm_arr_remove_at() removes the element at arr[idx], by memmove'ing
* the elements from arr[idx+1..len-1] down. All it does is one memmove(),
* if there is anything to move. */
#define nm_arr_remove_at(arr, len, idx) \
G_STMT_START \
{ \
typeof(*(arr)) *const _arr = (arr); \
typeof(len) _len = (len); \
typeof(idx) _idx = (idx); \
const gsize _len2 = (_len); \
const gsize _idx2 = (_idx); \
\
nm_assert(_arr); \
nm_assert(_len > 0); \
nm_assert(_NM_INT_NOT_NEGATIVE(_idx)); \
nm_assert(_idx < _len); \
\
if (_idx2 != _len2 - 1u) \
memmove(&_arr[_idx2], &_arr[_idx2 + 1u], sizeof(_arr[0]) * ((_len2 - 1u) - _idx2)); \
} \
G_STMT_END
/*****************************************************************************/
#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 char **
nm_strv_ptrarray_to_strv_full(const GPtrArray *a, gboolean not_null)
{
if (!a)
return not_null ? nm_strv_empty_new() : NULL;
return nm_strv_dup_full((const char *const *) a->pdata, a->len, TRUE, TRUE);
}
static inline char **
nm_strv_ptrarray_to_strv(const GPtrArray *a)
{
/* Returns never NULL ("not_null"!) */
return nm_strv_ptrarray_to_strv_full(a, TRUE);
}
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_new_clone((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_strv_find_first((const char *const *) 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_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_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[];
extern const char _nm_hexchar_table_upper[];
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_fuller(gconstpointer addr,
gsize length,
char delimiter,
gboolean upper_case,
gboolean with_leading_zero,
char *out);
static inline char *
nm_utils_bin2hexstr_full(gconstpointer addr,
gsize length,
char delimiter,
gboolean upper_case,
char *out)
{
return nm_utils_bin2hexstr_fuller(addr, length, delimiter, upper_case, TRUE, out);
}
char *_nm_utils_bin2hexstr(gconstpointer src, gsize len, int final_len);
#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))
#define nm_ether_addr_to_string_dup(ether_addr) \
((char *) nm_ether_addr_to_string((ether_addr), g_malloc(sizeof(NMEtherAddr) * 3)))
NMEtherAddr *nm_ether_addr_from_string(NMEtherAddr *addr, const char *str);
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.
*
* Returns: @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_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_strdup_reset_take(char **dst, char *src)
{
char *old;
nm_assert(dst);
nm_assert(!src || 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)
{
nm_assert(p);
if (!*p) {
*p = g_array_new(TRUE, FALSE, sizeof(char *));
g_array_set_clear_func(*p, nm_indirect_g_free);
} else
nm_assert(sizeof(char *) == g_array_get_element_size(*p));
return *p;
}
static inline void
nm_strvarray_add_take(GArray *array, char *str)
{
nm_assert(array);
nm_assert(sizeof(char *) == g_array_get_element_size(array));
/* The array is used as a NULL terminated strv array. Adding NULL is most
* likely a bug. Assert against it. */
nm_assert(str);
g_array_append_val(array, str);
}
static inline void
nm_strvarray_add(GArray *array, const char *str)
{
nm_strvarray_add_take(array, g_strdup(str));
}
static inline const char *
nm_strvarray_get_idx(const GArray *array, guint idx)
{
return nm_g_array_index(array, const char *, idx);
}
/* nm_strvarray_get_idxnull_or_greturn() permits access at `len`,
* returning NULL. If the access is out of bounds, the assertion
* will fail (and also return NULL). */
#define nm_strvarray_get_idxnull_or_greturn(arr, idx) \
({ \
GArray *_arr = (arr); \
gsize _idx = (idx); \
guint _len = nm_g_array_len(_arr); \
\
g_return_val_if_fail(_idx <= _len, NULL); \
\
_idx == _len ? NULL : nm_strvarray_get_idx(_arr, _idx); \
})
/**
* nm_strvarray_get_strv_full:
* @arr: the strvarray.
* @length: (out) (nullable): optionally return the length of the result.
* @not_null: if true and @arr is NULL, return NM_STRV_EMPTY_CC() (otherwise NULL).
* @preserve_empty: if true and the array is empty, return an empty
* strv array. Otherwise, return NULL.
*
* If "arr" is NULL, this returns NULL, unless "not_null" is true (in which
* case the static NM_STRV_EMPTY_CC() is returned).
* If "arr" is empty, it depends on:
* - if "preserve_empty" or "not_null", then the resulting strv array is the empty "arr".
* - otherwise NULL is returned.
* Otherwise, returns the non-empty, non-deep-cloned strv array.
*
* Like nm_strvarray_get_strv_full_dup(), but the strings are not cloned.
*
* Returns: (transfer none): a strv list or NULL.
*/
static inline const char *const *
nm_strvarray_get_strv_full(const GArray *arr,
guint *length,
gboolean not_null,
gboolean preserve_empty)
{
if (!arr) {
NM_SET_OUT(length, 0);
return not_null ? NM_STRV_EMPTY_CC() : NULL;
}
nm_assert(sizeof(char *) == g_array_get_element_size((GArray *) arr));
NM_SET_OUT(length, arr->len);
if (arr->len == 0 && !(preserve_empty || not_null))
return NULL;
return &g_array_index(arr, const char *, 0);
}
/**
* nm_strvarray_get_strv_full_dup:
* @arr: the strvarray.
* @length: (out) (nullable): optionally return the length of the result.
* @not_null: if true, never return NULL but allocate an empty strv array.
* @preserve_empty: if true and the array is empty, return an empty
* strv array. Otherwise, return NULL.
*
* If "arr" is NULL, this returns NULL, unless "not_null" is true (in which case
* am empty strv array is allocated.
* If "arr" is empty, it depends on:
* - if "preserve_empty" || "not_null", then the resulting strv array is allocated (and empty).
* - otherwise, NULL is returned.
* Otherwise, return the non-empty, deep-cloned strv array.
*
* Like nm_strvarray_get_strv_full(), but the strings are cloned.
*
* Returns: (transfer full): a deep-cloned strv list or NULL.
*/
static inline char **
nm_strvarray_get_strv_full_dup(const GArray *arr,
guint *length,
gboolean not_null,
gboolean preserve_empty)
{
if (!arr) {
NM_SET_OUT(length, 0);
return not_null ? nm_strv_empty_new() : NULL;
}
nm_assert(sizeof(char *) == g_array_get_element_size((GArray *) arr));
NM_SET_OUT(length, arr->len);
if (arr->len == 0) {
if (preserve_empty || not_null)
return nm_strv_empty_new();
return NULL;
}
return nm_strv_dup(&g_array_index(arr, const char *, 0), arr->len, TRUE);
}
/**
* nm_strvarray_get_strv_notnull:
* @arr: the strvarray.
* @length: (out) (nullable): optionally return the length of the result.
*
* This never returns NULL. If @arr is NULL, this returns NM_STRV_EMPTY_CC().
*
* Like nm_strvarray_get_strv_notempty(), but never returns NULL.
*
* Returns: (transfer none): a pointer to the strv list in @arr or NM_STRV_EMPTY_CC().
*/
static inline const char *const *
nm_strvarray_get_strv_notnull(const GArray *arr, guint *length)
{
return nm_strvarray_get_strv_full(arr, length, TRUE, TRUE);
}
/**
* nm_strvarray_get_strv_notempty:
* @arr: the strvarray.
* @length: (out) (nullable): optionally return the length of the result.
*
* This never returns an empty strv array. If @arr is NULL or empty, this
* returns NULL.
*
* Like nm_strvarray_get_strv_notempty_dup(), but does not clone strings.
*
* Returns: (transfer none): a pointer to the strv list in @arr or NULL.
*/
static inline const char *const *
nm_strvarray_get_strv_notempty(const GArray *arr, guint *length)
{
return nm_strvarray_get_strv_full(arr, length, FALSE, FALSE);
}
/**
* nm_strvarray_get_strv_notempty_dup:
* @arr: the strvarray.
* @length: (out) (nullable): optionally return the length of the result.
*
* This never returns an empty strv array. If @arr is NULL or empty, this
* returns NULL.
*
* Like nm_strvarray_get_strv_notempty(), but clones strings.
*
* Returns: (transfer full): a deep-cloned strv list or NULL.
*/
static inline char **
nm_strvarray_get_strv_notempty_dup(const GArray *arr, guint *length)
{
return nm_strvarray_get_strv_full_dup(arr, length, FALSE, FALSE);
}
/**
* nm_strvarray_set_strv_full:
* @array: a pointer to the array to set.
* @strv: the strv array. May be NULL.
* @preserve_empty: how to treat if strv is empty (strv[0]==NULL).
*
* The old array will be freed (in a way so that the function is self-assignment
* safe).
*
* If "strv" is NULL, then the resulting GArray is NULL.
* If "strv" is empty, then it depends on "preserve_empty":
* - if "preserve_empty", then the resulting GArray is allocated (and empty).
* - if "!preserve_empty", then the resulting GArray is NULL.
* If "strv" is not empty, a GArray gets allocated and the strv array deep-cloned.
*/
static inline void
nm_strvarray_set_strv_full(GArray **array, const char *const *strv, gboolean preserve_empty)
{
gs_unref_array GArray *array_old = NULL;
array_old = g_steal_pointer(array);
nm_assert(!array_old || sizeof(char *) == g_array_get_element_size(array_old));
if (!strv)
return;
if (!strv[0] && !preserve_empty) {
/* An empty strv array is treated like NULL. Don't allocate a GArray. */
return;
}
nm_strvarray_ensure(array);
for (; strv[0]; strv++)
nm_strvarray_add(*array, strv[0]);
}
/**
* nm_strvarray_set_strv:
* @array: a pointer to the array to set.
* @strv: the strv array. May be NULL.
*
* The old array will be freed (in a way so that the function is self-assignment
* safe).
*
* Note that this will never initialize an empty GArray. If strv is NULL or
* empty, the @array pointer will be set to NULL. */
static inline void
nm_strvarray_set_strv(GArray **array, const char *const *strv)
{
nm_strvarray_set_strv_full(array, strv, FALSE);
}
static inline gssize
nm_strvarray_find_first(const GArray *strv, const char *needle)
{
guint i;
nm_assert(needle);
if (strv) {
nm_assert(sizeof(char *) == g_array_get_element_size((GArray *) strv));
for (i = 0; i < strv->len; i++) {
if (nm_streq(needle, g_array_index(strv, const char *, i)))
return i;
}
}
return -1;
}
#define nm_strvarray_contains(strv, needle) (nm_strvarray_find_first((strv), (needle)) >= 0)
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;
}
#define nm_strvarray_remove_index(strv, idx) \
G_STMT_START \
{ \
GArray *const _strv = (strv); \
typeof(idx) _idx = (idx); \
\
nm_assert(_strv); \
nm_assert((uintmax_t) _idx < _strv->len); \
nm_assert(sizeof(char *) == g_array_get_element_size(_strv)); \
\
g_array_remove_index(_strv, (guint) _idx); \
} \
G_STMT_END
static inline void
nm_strvarray_ensure_and_add(GArray **p, const char *str)
{
nm_strvarray_add(nm_strvarray_ensure(p), str);
}
static inline gboolean
nm_strvarray_ensure_and_add_unique(GArray **p, const char *str)
{
nm_assert(p);
if (nm_strvarray_contains(*p, str))
return FALSE;
nm_strvarray_add(nm_strvarray_ensure(p), str);
return TRUE;
}
static inline int
nm_strvarray_cmp(const GArray *a, const GArray *b)
{
nm_assert(!a || sizeof(char *) == g_array_get_element_size((GArray *) a));
nm_assert(!b || sizeof(char *) == g_array_get_element_size((GArray *) b));
NM_CMP_SELF(a, b);
return nm_strv_cmp_n(nm_g_array_data(a), a->len, nm_g_array_data(b), b->len);
}
#define nm_strvarray_equal(a, b) (nm_strvarray_cmp((a), (b)) == 0)
static inline int
_nm_strvarray_cmp_strv(const GArray *strv, const char *const *ss, gsize ss_len)
{
nm_assert(!strv || sizeof(char *) == g_array_get_element_size((GArray *) strv));
return nm_strv_cmp_n(nm_g_array_data(strv), strv ? ((gssize) strv->len) : -1, ss, ss_len);
}
#define nm_strvarray_cmp_strv(strv, ss, ss_len) \
_nm_strvarray_cmp_strv((strv), NM_CAST_STRV_CC(ss), (ss_len))
#define nm_strvarray_equal_strv(strv, ss, ss_len) \
(nm_strvarray_cmp_strv((strv), (ss), (ss_len)) == 0)
static inline gboolean
nm_strvarray_clear(GArray **array)
{
gboolean cleared = FALSE;
nm_assert(array);
nm_assert(!*array || sizeof(char *) == g_array_get_element_size(*array));
if (*array) {
/* We always clear the GArray, but we return TRUE only if the
* array was non-empty before. */
if ((*array)->len > 0)
cleared = TRUE;
nm_clear_pointer(array, g_array_unref);
}
return cleared;
}
/*****************************************************************************/
struct _NMVariantAttributeSpec {
char *name;
const GVariantType *type;
bool v4 : 1;
bool v6 : 1;
bool no_value : 1;
bool consumes_rest : 1;
/* This indicates a non-standard parsing behavior. What this is,
* depends on the actual validation and how to handle it.
*
* Note that the entire NMVariantAttributeSpec is internal API,
* so we can change behavior and adjust it as it fits. */
char type_detail;
};
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);
/*****************************************************************************/
/* glibc defines HOST_NAME_MAX as 64. Also Linux' sethostname() enforces
* that (__NEW_UTS_LEN). However, musl sets this to 255.
*
* At some places, we want to follow Linux. Hardcode our own define. */
#define NM_HOST_NAME_MAX 64
gboolean nm_utils_is_localhost(const char *name);
gboolean nm_utils_is_specific_hostname(const char *name);
gboolean nm_utils_is_not_empty_hostname(const char *name);
struct passwd;
struct passwd *nm_getpwuid(uid_t uid);
const char *nm_passwd_name(const struct passwd *pw);
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);
/*****************************************************************************/
#define NM_UTILS_GET_PROCESS_EXIT_STATUS_BUF_LEN 41
const char *nm_utils_get_process_exit_status_desc_buf(int status, char *buf, gsize buf_len);
char *nm_utils_get_process_exit_status_desc(int status);
gboolean nm_utils_validate_hostname(const char *hostname);
/*****************************************************************************/
void nm_utils_thread_local_register_destroy(gpointer tls_data, GDestroyNotify destroy_notify);
/*****************************************************************************/
int nm_unbase64char(char c);
int nm_unbase64mem_full(const char *p, gsize l, gboolean secure, guint8 **ret, gsize *ret_size);
/*****************************************************************************/
static inline gboolean
nm_path_is_absolute(const char *p)
{
/* Copied from systemd's path_is_absolute()
* https://github.com/systemd/systemd/blob/bc85f8b51d962597360e982811e674c126850f56/src/basic/path-util.h#L50 */
nm_assert(p);
return p[0] == '/';
}
int nm_path_find_first_component(const char **p, gboolean accept_dot_dot, const char **ret);
int nm_path_compare(const char *a, const char *b);
static inline gboolean
nm_path_equal(const char *a, const char *b)
{
return nm_path_compare(a, b) == 0;
}
char *nm_path_simplify(char *path);
char *
nm_path_startswith_full(const char *path, const char *prefix, gboolean accept_dot_dot) _nm_pure;
static inline char *
nm_path_startswith(const char *path, const char *prefix)
{
return nm_path_startswith_full(path, prefix, TRUE);
}
/*****************************************************************************/
gboolean nm_hostname_is_valid(const char *s, gboolean trailing_dot);
/*****************************************************************************/
typedef void (*NMUtilsPollProbeRegisterObjectFcn)(GObject *object, gpointer user_data);
typedef void (*NMUtilsPollProbeStartFcn)(GCancellable *cancellable,
gpointer probe_user_data,
GAsyncReadyCallback callback,
gpointer user_data);
typedef gboolean (*NMUtilsPollProbeFinishFcn)(GObject *source,
GAsyncResult *result,
gpointer probe_user_data,
GError **error);
void nm_utils_poll(int poll_timeout_ms,
int ratelimit_timeout_ms,
int sleep_timeout_ms,
NMUtilsPollProbeRegisterObjectFcn probe_register_object_fcn,
NMUtilsPollProbeStartFcn probe_start_fcn,
NMUtilsPollProbeFinishFcn probe_finish_fcn,
gpointer probe_user_data,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean nm_utils_poll_finish(GAsyncResult *result, gpointer *probe_user_data, GError **error);
/*****************************************************************************/
void nm_utils_env_var_encode_name(const char *key, GString *str_buffer);
gboolean nm_utils_env_var_decode_name(const char *name, GString *str_buffer);
#endif /* __NM_SHARED_UTILS_H__ */