summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2015-04-14 23:14:06 +0200
committerThomas Haller <thaller@redhat.com>2015-06-17 11:23:51 +0200
commit53f98e7f9e58767e22fada651b12bf3f74aa76ed (patch)
tree4afad5ce08eac10cc81e9ea99271dbbe5dae3767
parentd1e7554a90ee9bae08ed44a287a0d59b1ad2ce2b (diff)
downloadNetworkManager-53f98e7f9e58767e22fada651b12bf3f74aa76ed.tar.gz
platform: implement NMPObject and NMPCache
NMPObject is a simple "object" implemenation around NMPlatformObject. They are ref-counted and have a class-pointer. Several basic functions like equality, hash, to-string are implemented. NMPCache is can be used to store the NMPObject. Objects are indexed via their primary id, but there is also multi-lookup via NMCacheId and NMMultiIndex. Part of the implementation is inside "nm-linux-platform.c", because it depends on utility functions from there.
-rw-r--r--src/nm-types.h3
-rw-r--r--src/platform/nm-linux-platform.c398
-rw-r--r--src/platform/nm-platform.c8
-rw-r--r--src/platform/nm-platform.h6
-rw-r--r--src/platform/nmp-object.c1927
-rw-r--r--src/platform/nmp-object.h344
-rw-r--r--src/platform/tests/test-nmp-object.c383
7 files changed, 3064 insertions, 5 deletions
diff --git a/src/nm-types.h b/src/nm-types.h
index ddcee4daba..2f36bee45a 100644
--- a/src/nm-types.h
+++ b/src/nm-types.h
@@ -52,6 +52,9 @@ typedef enum {
/* In priority order; higher number == higher priority */
NM_IP_CONFIG_SOURCE_UNKNOWN,
+ /* platform internal flag used to mark routes with RTM_F_CLONED. */
+ _NM_IP_CONFIG_SOURCE_RTM_F_CLONED,
+
/* platform internal flag used to mark routes with protocol RTPROT_KERNEL. */
_NM_IP_CONFIG_SOURCE_RTPROT_KERNEL,
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index 264af4b42c..ce49083310 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -15,7 +15,7 @@
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
- * Copyright (C) 2012-2013 Red Hat, Inc.
+ * Copyright (C) 2012-2015 Red Hat, Inc.
*/
#include "config.h"
@@ -436,7 +436,7 @@ nm_linux_platform_setup (void)
/******************************************************************/
-static ObjectType
+ObjectType
_nlo_get_object_type (const struct nl_object *object)
{
const char *type_str;
@@ -1011,6 +1011,86 @@ init_link (NMPlatform *platform, NMPlatformLink *info, struct rtnl_link *rtnllin
return TRUE;
}
+gboolean
+_nmp_vt_cmd_plobj_init_from_nl_link (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache)
+{
+ NMPlatformLink *obj = (NMPlatformLink *) _obj;
+ NMPObjectLink *obj_priv = (NMPObjectLink *) _obj;
+ struct rtnl_link *nlo = (struct rtnl_link *) _nlo;
+ const char *name;
+ struct nl_addr *nladdr;
+
+ nm_assert (memcmp (obj, ((char [sizeof (NMPObjectLink)]) { 0 }), sizeof (NMPObjectLink)) == 0);
+
+ obj->ifindex = rtnl_link_get_ifindex (nlo);
+
+ if (id_only)
+ return TRUE;
+
+ name = rtnl_link_get_name (nlo);
+ if (name)
+ g_strlcpy (obj->name, name, sizeof (obj->name));
+ obj->type = link_extract_type (platform, nlo);
+ obj->kind = g_intern_string (rtnl_link_get_type (nlo));
+ obj->flags = rtnl_link_get_flags (nlo);
+ obj->up = NM_FLAGS_HAS (obj->flags, IFF_UP);
+ obj->connected = NM_FLAGS_HAS (obj->flags, IFF_LOWER_UP);
+ obj->arp = !NM_FLAGS_HAS (obj->flags, IFF_NOARP);
+ obj->master = rtnl_link_get_master (nlo);
+ obj->parent = rtnl_link_get_link (nlo);
+ obj->mtu = rtnl_link_get_mtu (nlo);
+ obj->arptype = rtnl_link_get_arptype (nlo);
+
+ if (obj->type == NM_LINK_TYPE_VLAN)
+ obj->vlan_id = rtnl_link_vlan_get_id (nlo);
+
+ if ((nladdr = rtnl_link_get_addr (nlo))) {
+ unsigned int l = 0;
+
+ l = nl_addr_get_len (nladdr);
+ if (l > 0 && l <= NM_UTILS_HWADDR_LEN_MAX) {
+ G_STATIC_ASSERT (NM_UTILS_HWADDR_LEN_MAX == sizeof (obj->addr.data));
+ memcpy (obj->addr.data, nl_addr_get_binary_addr (nladdr), l);
+ obj->addr.len = l;
+ }
+ }
+
+#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
+ if (_support_user_ipv6ll_get ()) {
+ guint8 mode = 0;
+
+ if (rtnl_link_inet6_get_addr_gen_mode (nlo, &mode) == 0)
+ obj->inet6_addr_gen_mode_inv = ~mode;
+ }
+#endif
+
+#if HAVE_LIBNL_INET6_TOKEN
+ if ((rtnl_link_inet6_get_token (nlo, &nladdr)) == 0) {
+ if ( nl_addr_get_family (nladdr) == AF_INET6
+ && nl_addr_get_len (nladdr) == sizeof (struct in6_addr)) {
+ struct in6_addr *addr;
+ NMUtilsIPv6IfaceId *iid = &obj->inet6_token.iid;
+
+ addr = nl_addr_get_binary_addr (nladdr);
+ iid->id_u8[7] = addr->s6_addr[15];
+ iid->id_u8[6] = addr->s6_addr[14];
+ iid->id_u8[5] = addr->s6_addr[13];
+ iid->id_u8[4] = addr->s6_addr[12];
+ iid->id_u8[3] = addr->s6_addr[11];
+ iid->id_u8[2] = addr->s6_addr[10];
+ iid->id_u8[1] = addr->s6_addr[9];
+ iid->id_u8[0] = addr->s6_addr[8];
+ obj->inet6_token.is_valid = TRUE;
+ }
+ nl_addr_put (nladdr);
+ }
+#endif
+
+ obj_priv->netlink.is_in_netlink = TRUE;
+
+ return TRUE;
+}
+
/* Hack: Empty bridges and bonds have IFF_LOWER_UP flag and therefore they break
* the carrier detection. This hack makes nm-platform think they don't have the
* IFF_LOWER_UP flag. This seems to also apply to bonds (specifically) with all
@@ -1194,6 +1274,66 @@ _init_ip_address_lifetime (NMPlatformIPAddress *address, const struct rtnl_addr
address->preferred = _get_remaining_time (address->timestamp, a_preferred);
}
+static guint32
+_extend_lifetime (guint32 lifetime, guint32 seconds)
+{
+ guint64 v;
+
+ if ( lifetime == NM_PLATFORM_LIFETIME_PERMANENT
+ || seconds == 0)
+ return lifetime;
+
+ v = (guint64) lifetime + (guint64) seconds;
+ return MIN (v, NM_PLATFORM_LIFETIME_PERMANENT - 1);
+}
+
+/* The rtnl_addr object contains relative lifetimes @valid and @preferred
+ * that count in seconds, starting from the moment when the kernel constructed
+ * the netlink message.
+ *
+ * There is also a field rtnl_addr_last_update_time(), which is the absolute
+ * time in 1/100th of a second of clock_gettime (CLOCK_MONOTONIC) when the address
+ * was modified (wrapping every 497 days).
+ * Immediately at the time when the address was last modified, #NOW and @last_update_time
+ * are the same, so (only) in that case @valid and @preferred are anchored at @last_update_time.
+ * However, this is not true in general. As time goes by, whenever kernel sends a new address
+ * via netlink, the lifetimes keep counting down.
+ **/
+static void
+_nlo_rtnl_addr_get_lifetimes (const struct rtnl_addr *rtnladdr,
+ guint32 *out_timestamp,
+ guint32 *out_lifetime,
+ guint32 *out_preferred)
+{
+ guint32 timestamp = 0;
+ gint32 now;
+ guint32 lifetime = rtnl_addr_get_valid_lifetime ((struct rtnl_addr *) rtnladdr);
+ guint32 preferred = rtnl_addr_get_preferred_lifetime ((struct rtnl_addr *) rtnladdr);
+
+ if ( lifetime != NM_PLATFORM_LIFETIME_PERMANENT
+ || preferred != NM_PLATFORM_LIFETIME_PERMANENT) {
+ if (preferred > lifetime)
+ preferred = lifetime;
+ timestamp = _rtnl_addr_last_update_time_to_nm (rtnladdr, &now);
+
+ if (now == 0) {
+ /* strange. failed to detect the last-update time and assumed that timestamp is 1. */
+ nm_assert (timestamp == 1);
+ now = nm_utils_get_monotonic_timestamp_s ();
+ }
+ if (timestamp < now) {
+ guint32 diff = now - timestamp;
+
+ lifetime = _extend_lifetime (lifetime, diff);
+ preferred = _extend_lifetime (preferred, diff);
+ } else
+ nm_assert (timestamp == now);
+ }
+ *out_timestamp = timestamp;
+ *out_lifetime = lifetime;
+ *out_preferred = preferred;
+}
+
static gboolean
init_ip4_address (NMPlatformIP4Address *address, struct rtnl_addr *rtnladdr)
{
@@ -1229,6 +1369,44 @@ init_ip4_address (NMPlatformIP4Address *address, struct rtnl_addr *rtnladdr)
return TRUE;
}
+gboolean
+_nmp_vt_cmd_plobj_init_from_nl_ip4_address (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache)
+{
+ NMPlatformIP4Address *obj = (NMPlatformIP4Address *) _obj;
+ struct rtnl_addr *nlo = (struct rtnl_addr *) _nlo;
+ struct nl_addr *nladdr = rtnl_addr_get_local (nlo);
+ struct nl_addr *nlpeer = rtnl_addr_get_peer (nlo);
+ const char *label;
+
+ if (!nladdr || nl_addr_get_len (nladdr) != sizeof (obj->address))
+ g_return_val_if_reached (FALSE);
+
+ obj->ifindex = rtnl_addr_get_ifindex (nlo);
+ obj->plen = rtnl_addr_get_prefixlen (nlo);
+ memcpy (&obj->address, nl_addr_get_binary_addr (nladdr), sizeof (obj->address));
+
+ if (id_only)
+ return TRUE;
+
+ obj->source = NM_IP_CONFIG_SOURCE_KERNEL;
+ _nlo_rtnl_addr_get_lifetimes (nlo,
+ &obj->timestamp,
+ &obj->lifetime,
+ &obj->preferred);
+ if (nlpeer) {
+ if (nl_addr_get_len (nlpeer) != sizeof (obj->peer_address))
+ g_warn_if_reached ();
+ else
+ memcpy (&obj->peer_address, nl_addr_get_binary_addr (nlpeer), sizeof (obj->peer_address));
+ }
+ label = rtnl_addr_get_label (nlo);
+ /* Check for ':'; we're only interested in labels used as interface aliases */
+ if (label && strchr (label, ':'))
+ g_strlcpy (obj->label, label, sizeof (obj->label));
+
+ return TRUE;
+}
+
static gboolean
init_ip6_address (NMPlatformIP6Address *address, struct rtnl_addr *rtnladdr)
{
@@ -1258,6 +1436,41 @@ init_ip6_address (NMPlatformIP6Address *address, struct rtnl_addr *rtnladdr)
return TRUE;
}
+gboolean
+_nmp_vt_cmd_plobj_init_from_nl_ip6_address (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache)
+{
+ NMPlatformIP6Address *obj = (NMPlatformIP6Address *) _obj;
+ struct rtnl_addr *nlo = (struct rtnl_addr *) _nlo;
+ struct nl_addr *nladdr = rtnl_addr_get_local (nlo);
+ struct nl_addr *nlpeer = rtnl_addr_get_peer (nlo);
+
+ if (!nladdr || nl_addr_get_len (nladdr) != sizeof (obj->address))
+ g_return_val_if_reached (FALSE);
+
+ obj->ifindex = rtnl_addr_get_ifindex (nlo);
+ obj->plen = rtnl_addr_get_prefixlen (nlo);
+ memcpy (&obj->address, nl_addr_get_binary_addr (nladdr), sizeof (obj->address));
+
+ if (id_only)
+ return TRUE;
+
+ obj->source = NM_IP_CONFIG_SOURCE_KERNEL;
+ _nlo_rtnl_addr_get_lifetimes (nlo,
+ &obj->timestamp,
+ &obj->lifetime,
+ &obj->preferred);
+ obj->flags = rtnl_addr_get_flags (nlo);
+
+ if (nlpeer) {
+ if (nl_addr_get_len (nlpeer) != sizeof (obj->peer_address))
+ g_warn_if_reached ();
+ else
+ memcpy (&obj->peer_address, nl_addr_get_binary_addr (nlpeer), sizeof (obj->peer_address));
+ }
+
+ return TRUE;
+}
+
static guint
source_to_rtprot (NMIPConfigSource source)
{
@@ -1350,6 +1563,61 @@ init_ip4_route (NMPlatformIP4Route *route, struct rtnl_route *rtnlroute)
return TRUE;
}
+gboolean
+_nmp_vt_cmd_plobj_init_from_nl_ip4_route (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache)
+{
+ NMPlatformIP4Route *obj = (NMPlatformIP4Route *) _obj;
+ struct rtnl_route *nlo = (struct rtnl_route *) _nlo;
+ struct nl_addr *dst, *gw;
+ struct rtnl_nexthop *nexthop;
+
+ if (rtnl_route_get_type (nlo) != RTN_UNICAST ||
+ rtnl_route_get_table (nlo) != RT_TABLE_MAIN ||
+ rtnl_route_get_tos (nlo) != 0 ||
+ rtnl_route_get_nnexthops (nlo) != 1)
+ return FALSE;
+
+ nexthop = rtnl_route_nexthop_n (nlo, 0);
+ if (!nexthop)
+ g_return_val_if_reached (FALSE);
+
+ dst = rtnl_route_get_dst (nlo);
+ if (!dst)
+ g_return_val_if_reached (FALSE);
+
+ if (nl_addr_get_len (dst)) {
+ if (nl_addr_get_len (dst) != sizeof (obj->network))
+ g_return_val_if_reached (FALSE);
+ memcpy (&obj->network, nl_addr_get_binary_addr (dst), sizeof (obj->network));
+ }
+ obj->ifindex = rtnl_route_nh_get_ifindex (nexthop);
+ obj->plen = nl_addr_get_prefixlen (dst);
+ obj->metric = rtnl_route_get_priority (nlo);
+ obj->scope_inv = nm_platform_route_scope_inv (rtnl_route_get_scope (nlo));
+
+ gw = rtnl_route_nh_get_gateway (nexthop);
+ if (gw) {
+ if (nl_addr_get_len (gw) != sizeof (obj->gateway))
+ g_warn_if_reached ();
+ else
+ memcpy (&obj->gateway, nl_addr_get_binary_addr (gw), sizeof (obj->gateway));
+ }
+ rtnl_route_get_metric (nlo, RTAX_ADVMSS, &obj->mss);
+ if (rtnl_route_get_flags (nlo) & RTM_F_CLONED) {
+ /* we must not straight way reject cloned routes, because we might have cached
+ * a non-cloned route. If we now receive an update of the route with the route
+ * being cloned, we must still return the object, so that we can remove the old
+ * one from the cache.
+ *
+ * This happens, because this route is not nmp_object_is_alive().
+ * */
+ obj->source = _NM_IP_CONFIG_SOURCE_RTM_F_CLONED;
+ } else
+ obj->source = rtprot_to_source (rtnl_route_get_protocol (nlo), TRUE);
+
+ return TRUE;
+}
+
static gboolean
init_ip6_route (NMPlatformIP6Route *route, struct rtnl_route *rtnlroute)
{
@@ -1390,6 +1658,56 @@ init_ip6_route (NMPlatformIP6Route *route, struct rtnl_route *rtnlroute)
return TRUE;
}
+gboolean
+_nmp_vt_cmd_plobj_init_from_nl_ip6_route (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache)
+{
+ NMPlatformIP6Route *obj = (NMPlatformIP6Route *) _obj;
+ struct rtnl_route *nlo = (struct rtnl_route *) _nlo;
+ struct nl_addr *dst, *gw;
+ struct rtnl_nexthop *nexthop;
+
+ if (rtnl_route_get_type (nlo) != RTN_UNICAST ||
+ rtnl_route_get_table (nlo) != RT_TABLE_MAIN ||
+ rtnl_route_get_tos (nlo) != 0 ||
+ rtnl_route_get_nnexthops (nlo) != 1)
+ return FALSE;
+
+ nexthop = rtnl_route_nexthop_n (nlo, 0);
+ if (!nexthop)
+ g_return_val_if_reached (FALSE);
+
+ dst = rtnl_route_get_dst (nlo);
+ if (!dst)
+ g_return_val_if_reached (FALSE);
+
+ if (nl_addr_get_len (dst)) {
+ if (nl_addr_get_len (dst) != sizeof (obj->network))
+ g_return_val_if_reached (FALSE);
+ memcpy (&obj->network, nl_addr_get_binary_addr (dst), sizeof (obj->network));
+ }
+ obj->ifindex = rtnl_route_nh_get_ifindex (nexthop);
+ obj->plen = nl_addr_get_prefixlen (dst);
+ obj->metric = rtnl_route_get_priority (nlo);
+
+ if (id_only)
+ return TRUE;
+
+ gw = rtnl_route_nh_get_gateway (nexthop);
+ if (gw) {
+ if (nl_addr_get_len (gw) != sizeof (obj->gateway))
+ g_warn_if_reached ();
+ else
+ memcpy (&obj->gateway, nl_addr_get_binary_addr (gw), sizeof (obj->gateway));
+ }
+ rtnl_route_get_metric (nlo, RTAX_ADVMSS, &obj->mss);
+ if (rtnl_route_get_flags (nlo) & RTM_F_CLONED)
+ obj->source = _NM_IP_CONFIG_SOURCE_RTM_F_CLONED;
+ else
+ obj->source = rtprot_to_source (rtnl_route_get_protocol (nlo), TRUE);
+
+ return TRUE;
+}
+
static char to_string_buffer[255];
#define SET_AND_RETURN_STRING_BUFFER(...) \
@@ -2296,6 +2614,16 @@ build_rtnl_link (int ifindex, const char *name, NMLinkType type)
return (struct nl_object *) rtnllink;
}
+struct nl_object *
+_nmp_vt_cmd_plobj_to_nl_link (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
+{
+ const NMPlatformLink *obj = (const NMPlatformLink *) _obj;
+
+ return build_rtnl_link (obj->ifindex,
+ obj->name[0] ? obj->name : NULL,
+ obj->type);
+}
+
static gboolean
link_get_by_name (NMPlatform *platform, const char *name, NMPlatformLink *out_link)
{
@@ -3782,6 +4110,40 @@ build_rtnl_addr (NMPlatform *platform,
return (struct nl_object *) rtnladdr_copy;
}
+struct nl_object *
+_nmp_vt_cmd_plobj_to_nl_ip4_address (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
+{
+ const NMPlatformIP4Address *obj = (const NMPlatformIP4Address *) _obj;
+
+ return build_rtnl_addr (platform,
+ AF_INET,
+ obj->ifindex,
+ &obj->address,
+ obj->peer_address ? &obj->peer_address : NULL,
+ obj->plen,
+ obj->lifetime,
+ obj->preferred,
+ 0,
+ obj->label[0] ? obj->label : NULL);
+}
+
+struct nl_object *
+_nmp_vt_cmd_plobj_to_nl_ip6_address (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
+{
+ const NMPlatformIP6Address *obj = (const NMPlatformIP6Address *) _obj;
+
+ return build_rtnl_addr (platform,
+ AF_INET6,
+ obj->ifindex,
+ &obj->address,
+ !IN6_IS_ADDR_UNSPECIFIED (&obj->peer_address) ? &obj->peer_address : NULL,
+ obj->plen,
+ obj->lifetime,
+ obj->preferred,
+ 0,
+ NULL);
+}
+
static gboolean
ip4_address_add (NMPlatform *platform,
int ifindex,
@@ -4036,6 +4398,38 @@ build_rtnl_route (int family, int ifindex, NMIPConfigSource source,
return (struct nl_object *) rtnlroute;
}
+struct nl_object *
+_nmp_vt_cmd_plobj_to_nl_ip4_route (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
+{
+ const NMPlatformIP4Route *obj = (const NMPlatformIP4Route *) _obj;
+
+ return build_rtnl_route (AF_INET,
+ obj->ifindex,
+ obj->source,
+ &obj->network,
+ obj->plen,
+ &obj->gateway,
+ NULL,
+ obj->metric,
+ obj->mss);
+}
+
+struct nl_object *
+_nmp_vt_cmd_plobj_to_nl_ip6_route (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
+{
+ const NMPlatformIP6Route *obj = (const NMPlatformIP6Route *) _obj;
+
+ return build_rtnl_route (AF_INET6,
+ obj->ifindex,
+ obj->source,
+ &obj->network,
+ obj->plen,
+ &obj->gateway,
+ NULL,
+ obj->metric,
+ obj->mss);
+}
+
static gboolean
ip4_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source,
in_addr_t network, int plen, in_addr_t gateway,
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index 23498cf5a9..caad16e2c8 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -2393,6 +2393,8 @@ source_to_string (NMIPConfigSource source)
switch (source) {
case _NM_IP_CONFIG_SOURCE_RTPROT_KERNEL:
return "rtprot-kernel";
+ case _NM_IP_CONFIG_SOURCE_RTM_F_CLONED:
+ return "rtm-f-cloned";
case NM_IP_CONFIG_SOURCE_KERNEL:
return "kernel";
case NM_IP_CONFIG_SOURCE_SHARED:
@@ -2844,7 +2846,11 @@ nm_platform_link_cmp (const NMPlatformLink *a, const NMPlatformLink *b)
_CMP_FIELD (a, b, inet6_addr_gen_mode_inv);
_CMP_FIELD (a, b, inet6_token.is_valid);
_CMP_FIELD_STR_INTERNED (a, b, kind);
- _CMP_FIELD_STR0 (a, b, udi);
+
+ /* udi is not an interned string, but NMRefString. Hence,
+ * do a pointer comparison first. */
+ _CMP_FIELD_STR_INTERNED (a, b, udi);
+
_CMP_FIELD_STR_INTERNED (a, b, driver);
if (a->addr.len)
_CMP_FIELD_MEMCMP_LEN (a, b, addr.data, a->addr.len);
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index c6145c56d2..62247bc31c 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -91,8 +91,10 @@ struct _NMPlatformLink {
/* NMPlatform initializes this field with a static string. */
const char *kind;
- /* Beware: NMPlatform initializes this string with an allocated string.
- * Handle it properly (i.e. don't keep a reference to it). */
+ /* Beware: NMPlatform initializes this string with an allocated string
+ * (NMRefString). Handle it properly (i.e. don't keep a reference to it
+ * without incrementing the ref-counter).
+ * This property depends on @initialized. */
const char *udi;
/* NMPlatform initializes this field with a static string. */
diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c
index 8c90deaec0..38eef91452 100644
--- a/src/platform/nmp-object.c
+++ b/src/platform/nmp-object.c
@@ -20,4 +20,1931 @@
#include "nmp-object.h"
+#include <unistd.h>
+
+#include "nm-platform-utils.h"
+#include "NetworkManagerUtils.h"
+#include "nm-utils.h"
+#include "nm-logging.h"
+
+/*********************************************************************************************/
+
+#define _LOG_DOMAIN LOGD_PLATFORM
+
+#define _LOG(level, domain, obj, ...) \
+ G_STMT_START { \
+ const NMLogLevel __level = (level); \
+ const NMLogDomain __domain = (domain); \
+ \
+ if (nm_logging_enabled (__level, __domain)) { \
+ const NMPObject *const __obj = (obj); \
+ \
+ _nm_log (__level, __domain, 0, \
+ "nmp-object[%p/%s]: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+ __obj, \
+ (__obj ? NMP_OBJECT_GET_CLASS (__obj)->obj_type_name : "???") \
+ _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
+ } \
+ } G_STMT_END
+#define _LOG_LEVEL_ENABLED(level, domain) \
+ ( nm_logging_enabled ((level), (domain)) )
+
+#ifdef NM_MORE_LOGGING
+#define _LOGT_ENABLED() _LOG_LEVEL_ENABLED (LOGL_TRACE, _LOG_DOMAIN)
+#define _LOGT(obj, ...) _LOG (LOGL_TRACE, _LOG_DOMAIN, obj, __VA_ARGS__)
+#else
+#define _LOGT_ENABLED() FALSE
+#define _LOGT(obj, ...) G_STMT_START { if (FALSE) { _LOG (LOGL_TRACE, _LOG_DOMAIN, obj, __VA_ARGS__); } } G_STMT_END
+#endif
+
+#define _LOGD(obj, ...) _LOG (LOGL_DEBUG, _LOG_DOMAIN, obj, __VA_ARGS__)
+#define _LOGI(obj, ...) _LOG (LOGL_INFO , _LOG_DOMAIN, obj, __VA_ARGS__)
+#define _LOGW(obj, ...) _LOG (LOGL_WARN , _LOG_DOMAIN, obj, __VA_ARGS__)
+#define _LOGE(obj, ...) _LOG (LOGL_ERR , _LOG_DOMAIN, obj, __VA_ARGS__)
+
+/*********************************************************************************************/
+
+struct _NMPCache {
+ /* the cache contains only one hash table for all object types, and similarly
+ * it contains only one NMMultiIndex.
+ * This works, because different object types don't ever compare equal and
+ * because their index ids also don't overlap.
+ *
+ * For routes and addresses, the cache contains an address if (and only if) the
+ * object was reported via netlink.
+ * For links, the cache contain a link if it was reported by either netlink
+ * or udev. That means, a link object can be alive, even if it was already
+ * removed via netlink.
+ *
+ * This effectively merges the udev-device cache into the NMPCache.
+ */
+
+ GHashTable *idx_main;
+ NMMultiIndex *idx_multi;
+
+ gboolean use_udev;
+};
+
+/******************************************************************/
+
+static inline guint
+_id_hash_ip6_addr (const struct in6_addr *addr)
+{
+ guint hash = (guint) 0x897da53981a13ULL;
+ int i;
+
+ for (i = 0; i < sizeof (*addr); i++)
+ hash = (hash * 33) + ((const guint8 *) addr)[i];
+ return hash;
+}
+
+static const char *
+_link_get_driver (GUdevDevice *udev_device, const char *kind, const char *ifname)
+{
+ const char *driver = NULL;
+
+ nm_assert (kind == g_intern_string (kind));
+
+ if (udev_device) {
+ driver = nmp_utils_udev_get_driver (udev_device);
+ if (driver)
+ return driver;
+ }
+
+ if (kind)
+ return kind;
+
+ if (ifname) {
+ char *d;
+
+ if (nmp_utils_ethtool_get_driver_info (ifname, &d, NULL, NULL)) {
+ driver = d && d[0] ? g_intern_string (d) : NULL;
+ g_free (d);
+ if (driver)
+ return driver;
+ }
+ }
+
+ return "unknown";
+}
+
+void
+_nmp_object_fixup_link_udev_fields (NMPObject *obj, gboolean use_udev)
+{
+ const char *driver = NULL;
+ const char *udi = NULL;
+ gboolean initialized = FALSE;
+
+ nm_assert (NMP_OBJECT_GET_TYPE (obj) == OBJECT_TYPE_LINK);
+
+ /* The link contains internal fields that are combined by
+ * properties from netlink and udev. Update those properties */
+
+ /* When a link is not in netlink, it's udev fields don't matter. */
+ if (obj->_link.netlink.is_in_netlink) {
+ driver = _link_get_driver (obj->_link.udev.device,
+ obj->link.kind,
+ obj->link.name);
+ if (obj->_link.udev.device) {
+ udi = g_udev_device_get_sysfs_path (obj->_link.udev.device);
+ initialized = TRUE;
+ } else if (!use_udev) {
+ /* If we don't use udev, we immediately mark the link as initialized.
+ *
+ * For that, we consult @use_udev argument, that is cached via
+ * nmp_cache_use_udev_get(). It is on purpose not to test
+ * for a writable /sys on every call. A minor reason for that is
+ * performance, but the real reason is reproducibility.
+ *
+ * If you want to support changing of whether udev is enabled,
+ * reset the value via nmp_cache_use_udev_set() carefully -- and
+ * possibly update the links in the cache accordingly.
+ * */
+ initialized = TRUE;
+ }
+ }
+
+ obj->link.driver = driver;
+ obj->link.udi = nm_ref_string_replace (obj->link.udi, udi);
+ obj->link.initialized = initialized;
+}
+
+static void
+_nmp_object_fixup_link_master_connected (NMPObject *obj, const NMPCache *cache)
+{
+ nm_assert (NMP_OBJECT_GET_TYPE (obj) == OBJECT_TYPE_LINK);
+
+ if (nmp_cache_link_connected_needs_toggle (cache, obj, NULL, NULL))
+ obj->link.connected = !obj->link.connected;
+}
+
+/******************************************************************/
+
+const NMPClass *
+nmp_class_from_type (ObjectType obj_type)
+{
+ g_return_val_if_fail (obj_type > OBJECT_TYPE_UNKNOWN && obj_type <= OBJECT_TYPE_MAX, NULL);
+
+ return &_nmp_classes[obj_type - 1];
+}
+
+/******************************************************************/
+
+NMPObject *
+nmp_object_ref (NMPObject *obj)
+{
+ g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj), NULL);
+ g_return_val_if_fail (obj->_ref_count != NMP_REF_COUNT_STACKINIT, NULL);
+ obj->_ref_count++;
+
+ _LOGT (obj, "ref: %d", obj->_ref_count);
+
+ return obj;
+}
+
+void
+nmp_object_unref (NMPObject *obj)
+{
+ if (obj) {
+ g_return_if_fail (obj->_ref_count > 0);
+ g_return_if_fail (obj->_ref_count != NMP_REF_COUNT_STACKINIT);
+ _LOGT (obj, "%s: %d",
+ obj->_ref_count <= 1 ? "destroy" : "unref",
+ obj->_ref_count - 1);
+ if (--obj->_ref_count <= 0) {
+ const NMPClass *klass = obj->_class;
+
+ nm_assert (!obj->is_cached);
+ if (klass->cmd_obj_dispose)
+ klass->cmd_obj_dispose (obj);
+ g_slice_free1 (klass->sizeof_data + G_STRUCT_OFFSET (NMPObject, object), obj);
+ }
+ }
+}
+
+static void
+_vt_cmd_obj_dispose_link (NMPObject *obj)
+{
+ nm_ref_string_unref (obj->link.udi);
+ g_clear_object (&obj->_link.udev.device);
+}
+
+static NMPObject *
+_nmp_object_new_from_class (const NMPClass *klass)
+{
+ NMPObject *obj;
+
+ nm_assert (klass);
+ nm_assert (klass->sizeof_data > 0);
+ nm_assert (klass->sizeof_public > 0 && klass->sizeof_public <= klass->sizeof_data);
+
+ obj = g_slice_alloc0 (klass->sizeof_data + G_STRUCT_OFFSET (NMPObject, object));
+ obj->_class = klass;
+ obj->_ref_count = 1;
+ _LOGT (obj, "new");
+ return obj;
+}
+
+NMPObject *
+nmp_object_new (ObjectType obj_type, const NMPlatformObject *plobj)
+{
+ const NMPClass *klass = nmp_class_from_type (obj_type);
+ NMPObject *obj;
+
+ obj = _nmp_object_new_from_class (klass);
+ if (plobj)
+ memcpy (&obj->object, plobj, klass->sizeof_public);
+ return obj;
+}
+
+NMPObject *
+nmp_object_new_link (int ifindex)
+{
+ NMPObject *obj;
+
+ obj = nmp_object_new (OBJECT_TYPE_LINK, NULL);
+ obj->link.ifindex = ifindex;
+ return obj;
+}
+
+/******************************************************************/
+
+static const NMPObject *
+_nmp_object_stackinit_from_class (NMPObject *obj, const NMPClass *klass)
+{
+ nm_assert (klass);
+
+ memset (obj, 0, sizeof (NMPObject));
+ obj->_class = klass;
+ obj->_ref_count = NMP_REF_COUNT_STACKINIT;
+ return obj;
+}
+
+const NMPObject *
+nmp_object_stackinit (NMPObject *obj, ObjectType obj_type, const NMPlatformObject *plobj)
+{
+ const NMPClass *klass = nmp_class_from_type (obj_type);
+
+ _nmp_object_stackinit_from_class (obj, klass);
+ if (plobj)
+ memcpy (&obj->object, plobj, klass->sizeof_public);
+ return obj;
+}
+
+const NMPObject *
+nmp_object_stackinit_id (NMPObject *obj, const NMPObject *src)
+{
+ nm_assert (NMP_OBJECT_IS_VALID (src));
+ nm_assert (obj);
+
+ NMP_OBJECT_GET_CLASS (src)->cmd_obj_stackinit_id (obj, src);
+ return obj;
+}
+
+const NMPObject *
+nmp_object_stackinit_id_link (NMPObject *obj, int ifindex)
+{
+ nmp_object_stackinit (obj, OBJECT_TYPE_LINK, NULL);
+ obj->link.ifindex = ifindex;
+ return obj;
+}
+
+static void
+_vt_cmd_obj_stackinit_id_link (NMPObject *obj, const NMPObject *src)
+{
+ nmp_object_stackinit_id_link (obj, src->link.ifindex);
+}
+
+const NMPObject *
+nmp_object_stackinit_id_ip4_address (NMPObject *obj, int ifindex, guint32 address, int plen)
+{
+ nmp_object_stackinit (obj, OBJECT_TYPE_IP4_ADDRESS, NULL);
+ obj->ip4_address.ifindex = ifindex;
+ obj->ip4_address.address = address;
+ obj->ip4_address.plen = plen;
+ return obj;
+}
+
+static void
+_vt_cmd_obj_stackinit_id_ip4_address (NMPObject *obj, const NMPObject *src)
+{
+ nmp_object_stackinit_id_ip4_address (obj, src->ip_address.ifindex, src->ip4_address.address, src->ip_address.plen);
+}
+
+const NMPObject *
+nmp_object_stackinit_id_ip6_address (NMPObject *obj, int ifindex, const struct in6_addr *address, int plen)
+{
+ nmp_object_stackinit (obj, OBJECT_TYPE_IP6_ADDRESS, NULL);
+ obj->ip4_address.ifindex = ifindex;
+ if (address)
+ obj->ip6_address.address = *address;
+ obj->ip6_address.plen = plen;
+ return obj;
+}
+
+static void
+_vt_cmd_obj_stackinit_id_ip6_address (NMPObject *obj, const NMPObject *src)
+{
+ nmp_object_stackinit_id_ip6_address (obj, src->ip_address.ifindex, &src->ip6_address.address, src->ip_address.plen);
+}
+
+const NMPObject *
+nmp_object_stackinit_id_ip4_route (NMPObject *obj, int ifindex, guint32 network, int plen, guint32 metric)
+{
+ nmp_object_stackinit (obj, OBJECT_TYPE_IP4_ROUTE, NULL);
+ obj->ip4_route.ifindex = ifindex;
+ obj->ip4_route.network = network;
+ obj->ip4_route.plen = plen;
+ obj->ip4_route.metric = metric;
+ return obj;
+}
+
+static void
+_vt_cmd_obj_stackinit_id_ip4_route (NMPObject *obj, const NMPObject *src)
+{
+ nmp_object_stackinit_id_ip4_route (obj, src->ip_route.ifindex, src->ip4_route.network, src->ip_route.plen, src->ip_route.metric);
+}
+
+const NMPObject *
+nmp_object_stackinit_id_ip6_route (NMPObject *obj, int ifindex, const struct in6_addr *network, int plen, guint32 metric)
+{
+ nmp_object_stackinit (obj, OBJECT_TYPE_IP6_ROUTE, NULL);
+ obj->ip6_route.ifindex = ifindex;
+ if (network)
+ obj->ip6_route.network = *network;
+ obj->ip6_route.plen = plen;
+ obj->ip6_route.metric = metric;
+ return obj;
+}
+
+static void
+_vt_cmd_obj_stackinit_id_ip6_route (NMPObject *obj, const NMPObject *src)
+{
+ nmp_object_stackinit_id_ip6_route (obj, src->ip_route.ifindex, &src->ip6_route.network, src->ip_route.plen, src->ip_route.metric);
+}
+
+/******************************************************************/
+
+const char *
+nmp_object_to_string (const NMPObject *obj, NMPObjectToStringMode to_string_mode, char *buf, gsize buf_size)
+{
+ const NMPClass *klass;
+ char buf2[sizeof (_nm_platform_to_string_buffer)];
+ char buf3[sizeof (_nm_platform_to_string_buffer)];
+ const char *str;
+
+ if (!buf) {
+ buf = _nm_platform_to_string_buffer;
+ buf_size = sizeof (_nm_platform_to_string_buffer);
+ }
+
+ if (!obj) {
+ g_strlcpy (buf, "NULL", buf_size);
+ return buf;
+ }
+
+ g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj), NULL);
+
+ klass = NMP_OBJECT_GET_CLASS (obj);
+
+ switch (to_string_mode) {
+ case NMP_OBJECT_TO_STRING_ID:
+ return klass->cmd_plobj_to_string_id (&obj->object, buf, buf_size);
+ case NMP_OBJECT_TO_STRING_ALL:
+ g_strlcpy (buf2, NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object), sizeof (buf2));
+
+ if (NMP_OBJECT_GET_TYPE (obj) == OBJECT_TYPE_LINK) {
+ g_snprintf (buf3, sizeof (buf3),
+ ",%cin-nl,%p",
+ obj->_link.netlink.is_in_netlink ? '+' : '-',
+ obj->_link.udev.device);
+ } else
+ buf3[0] = '\0';
+
+ g_snprintf (buf, buf_size,
+ "[%s,%p,%d,%ccache,%calive,%cvisible%s; %s]",
+ klass->obj_type_name, obj, obj->_ref_count,
+ obj->is_cached ? '+' : '-',
+ nmp_object_is_alive (obj) ? '+' : '-',
+ nmp_object_is_visible (obj) ? '+' : '-',
+ buf3, buf2);
+ return buf;
+ case NMP_OBJECT_TO_STRING_PUBLIC:
+ str = NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object);
+ if (str != buf)
+ g_strlcpy (buf, str, buf_size);
+ return buf;
+ default:
+ g_return_val_if_reached ("ERROR");
+ }
+}
+
+#define _vt_cmd_plobj_to_string_id(type, plat_type, ...) \
+static const char * \
+_vt_cmd_plobj_to_string_id_##type (const NMPlatformObject *_obj, char *buf, gsize buf_len) \
+{ \
+ plat_type *const obj = (plat_type *) _obj; \
+ char buf1[NM_UTILS_INET_ADDRSTRLEN]; \
+ \
+ (void) buf1; \
+ g_snprintf (buf, buf_len, \
+ __VA_ARGS__); \
+ return buf; \
+}
+_vt_cmd_plobj_to_string_id (link, NMPlatformLink, "%d", obj->ifindex);
+_vt_cmd_plobj_to_string_id (ip4_address, NMPlatformIP4Address, "%d: %s/%d", obj->ifindex, nm_utils_inet4_ntop ( obj->address, buf1), obj->plen);
+_vt_cmd_plobj_to_string_id (ip6_address, NMPlatformIP6Address, "%d: %s/%d", obj->ifindex, nm_utils_inet6_ntop (&obj->address, buf1), obj->plen);
+_vt_cmd_plobj_to_string_id (ip4_route, NMPlatformIP4Route, "%d: %s/%d %d", obj->ifindex, nm_utils_inet4_ntop ( obj->network, buf1), obj->plen, obj->metric);
+_vt_cmd_plobj_to_string_id (ip6_route, NMPlatformIP6Route, "%d: %s/%d %d", obj->ifindex, nm_utils_inet6_ntop (&obj->network, buf1), obj->plen, obj->metric);
+
+int
+nmp_object_cmp (const NMPObject *obj1, const NMPObject *obj2)
+{
+ if (obj1 == obj2)
+ return 0;
+ if (!obj1)
+ return -1;
+ if (!obj2)
+ return 1;
+
+ g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj1), -1);
+ g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj2), 1);
+
+ if (NMP_OBJECT_GET_CLASS (obj1) != NMP_OBJECT_GET_CLASS (obj2))
+ return NMP_OBJECT_GET_CLASS (obj1) < NMP_OBJECT_GET_CLASS (obj2) ? -1 : 1;
+
+ return NMP_OBJECT_GET_CLASS (obj1)->cmd_plobj_cmp (&obj1->object, &obj2->object);
+}
+
+gboolean
+nmp_object_equal (const NMPObject *obj1, const NMPObject *obj2)
+{
+ const NMPClass *klass;
+
+ g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj1), FALSE);
+ g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj2), FALSE);
+
+ if (obj1 == obj2)
+ return TRUE;
+
+ klass = NMP_OBJECT_GET_CLASS (obj1);
+
+ if (klass != NMP_OBJECT_GET_CLASS (obj2))
+ return FALSE;
+
+ return klass->cmd_obj_equal (obj1, obj2);
+}
+
+static gboolean
+_vt_cmd_obj_equal_plain (const NMPObject *obj1, const NMPObject *obj2)
+{
+ return NMP_OBJECT_GET_CLASS (obj1)->cmd_plobj_cmp (&obj1->object, &obj2->object) == 0;
+}
+
+static gboolean
+_vt_cmd_obj_equal_link (const NMPObject *obj1, const NMPObject *obj2)
+{
+ const NMPClass *klass = NMP_OBJECT_GET_CLASS (obj1);
+
+ if (klass->cmd_plobj_cmp (&obj1->object, &obj2->object) != 0)
+ return FALSE;
+ if (obj1->_link.netlink.is_in_netlink != obj2->_link.netlink.is_in_netlink)
+ return FALSE;
+ if (obj1->_link.udev.device != obj2->_link.udev.device)
+ return FALSE;
+ return TRUE;
+}
+
+/* @src is a const object, which is not entirely correct for link types, where
+ * we increase the ref count for src->_link.udev.device.
+ * Hence, nmp_object_copy() can violate the const promise of @src.
+ * */
+void
+nmp_object_copy (NMPObject *dst, const NMPObject *src, gboolean id_only)
+{
+ g_return_if_fail (NMP_OBJECT_IS_VALID (dst));
+ g_return_if_fail (NMP_OBJECT_IS_VALID (src));
+ g_return_if_fail (!NMP_OBJECT_IS_STACKINIT (dst));
+
+ if (src != dst) {
+ const NMPClass *klass = NMP_OBJECT_GET_CLASS (dst);
+
+ g_return_if_fail (klass == NMP_OBJECT_GET_CLASS (src));
+
+ if (id_only)
+ klass->cmd_plobj_id_copy (&dst->object, &src->object);
+ else
+ klass->cmd_obj_copy (dst, src);
+ }
+}
+
+#define _vt_cmd_plobj_id_copy(type, plat_type, cmd) \
+static void \
+_vt_cmd_plobj_id_copy_##type (NMPlatformObject *_dst, const NMPlatformObject *_src) \
+{ \
+ plat_type *const dst = (plat_type *) _dst; \
+ const plat_type *const src = (const plat_type *) _src; \
+ { cmd } \
+}
+_vt_cmd_plobj_id_copy (link, NMPlatformLink, {
+ dst->ifindex = src->ifindex;
+});
+_vt_cmd_plobj_id_copy (ip4_address, NMPlatformIP4Address, {
+ dst->ifindex = src->ifindex;
+ dst->plen = src->plen;
+ dst->address = src->address;
+});
+_vt_cmd_plobj_id_copy (ip6_address, NMPlatformIP6Address, {
+ dst->ifindex = src->ifindex;
+ dst->plen = src->plen;
+ dst->address = src->address;
+});
+_vt_cmd_plobj_id_copy (ip4_route, NMPlatformIP4Route, {
+ dst->ifindex = src->ifindex;
+ dst->plen = src->plen;
+ dst->metric = src->metric;
+ dst->network = src->network;
+});
+_vt_cmd_plobj_id_copy (ip6_route, NMPlatformIP6Route, {
+ dst->ifindex = src->ifindex;
+ dst->plen = src->plen;
+ dst->metric = src->metric;
+ dst->network = src->network;
+});
+
+static void
+_vt_cmd_obj_copy_plain (NMPObject *dst, const NMPObject *src)
+{
+ memcpy (&dst->object, &src->object, NMP_OBJECT_GET_CLASS (dst)->sizeof_data);
+}
+
+static void
+_vt_cmd_obj_copy_link (NMPObject *dst, const NMPObject *src)
+{
+ if (dst->_link.udev.device != src->_link.udev.device) {
+ if (dst->_link.udev.device)
+ g_object_unref (dst->_link.udev.device);
+ if (src->_link.udev.device)
+ g_object_ref (src->_link.udev.device);
+ }
+ if (dst->link.udi != src->link.udi) {
+ nm_ref_string_unref (dst->link.udi);
+ nm_ref_string_ref (src->link.udi);
+ }
+ dst->_link = src->_link;
+}
+
+/* Uses internally nmp_object_copy(), hence it also violates the const
+ * promise for @obj.
+ * */
+NMPObject *
+nmp_object_clone (const NMPObject *obj, gboolean id_only)
+{
+ NMPObject *dst;
+
+ if (!obj)
+ return NULL;
+
+ g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj), NULL);
+
+ dst = _nmp_object_new_from_class (NMP_OBJECT_GET_CLASS (obj));
+ nmp_object_copy (dst, obj, id_only);
+ return dst;
+}
+
+gboolean
+nmp_object_id_equal (const NMPObject *obj1, const NMPObject *obj2)
+{
+ const NMPClass *klass;
+
+ if (obj1 == obj2)
+ return TRUE;
+ if (!obj1 || !obj2)
+ return FALSE;
+
+ g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj1), FALSE);
+ g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj2), FALSE);
+
+ klass = NMP_OBJECT_GET_CLASS (obj1);
+ return klass == NMP_OBJECT_GET_CLASS (obj2)
+ && klass->cmd_plobj_id_equal (&obj1->object, &obj2->object);
+}
+
+#define _vt_cmd_plobj_id_equal(type, plat_type, cmd) \
+static gboolean \
+_vt_cmd_plobj_id_equal_##type (const NMPlatformObject *_obj1, const NMPlatformObject *_obj2) \
+{ \
+ const plat_type *const obj1 = (const plat_type *) _obj1; \
+ const plat_type *const obj2 = (const plat_type *) _obj2; \
+ return (cmd); \
+}
+_vt_cmd_plobj_id_equal (link, NMPlatformLink,
+ obj1->ifindex == obj2->ifindex);
+_vt_cmd_plobj_id_equal (ip4_address, NMPlatformIP4Address,
+ obj1->ifindex == obj2->ifindex
+ && obj1->plen == obj2->plen
+ && obj1->address == obj2->address);
+_vt_cmd_plobj_id_equal (ip6_address, NMPlatformIP6Address,
+ obj1->ifindex == obj2->ifindex
+ && obj1->plen == obj2->plen
+ && IN6_ARE_ADDR_EQUAL (&obj1->address, &obj2->address));
+_vt_cmd_plobj_id_equal (ip4_route, NMPlatformIP4Route,
+ obj1->ifindex == obj2->ifindex
+ && obj1->plen == obj2->plen
+ && obj1->metric == obj2->metric
+ && obj1->network == obj2->network);
+_vt_cmd_plobj_id_equal (ip6_route, NMPlatformIP6Route,
+ obj1->ifindex == obj2->ifindex
+ && obj1->plen == obj2->plen
+ && obj1->metric == obj2->metric
+ && IN6_ARE_ADDR_EQUAL( &obj1->network, &obj2->network));
+
+guint
+nmp_object_id_hash (const NMPObject *obj)
+{
+ if (!obj)
+ return 0;
+
+ g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj), 0);
+ return NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_id_hash (&obj->object);
+}
+
+#define _vt_cmd_plobj_id_hash(type, plat_type, cmd) \
+static guint \
+_vt_cmd_plobj_id_hash_##type (const NMPlatformObject *_obj) \
+{ \
+ const plat_type *const obj = (const plat_type *) _obj; \
+ guint hash; \
+ { cmd; } \
+ return hash; \
+}
+_vt_cmd_plobj_id_hash (link, NMPlatformLink, {
+ /* libnl considers:
+ * .oo_id_attrs = LINK_ATTR_IFINDEX | LINK_ATTR_FAMILY,
+ */
+ hash = (guint) 3982791431;
+ hash = hash + ((guint) obj->ifindex);
+})
+_vt_cmd_plobj_id_hash (ip4_address, NMPlatformIP4Address, {
+ /* libnl considers:
+ * .oo_id_attrs = (ADDR_ATTR_FAMILY | ADDR_ATTR_IFINDEX |
+ * ADDR_ATTR_LOCAL | ADDR_ATTR_PREFIXLEN),
+ */
+ hash = (guint) 3591309853;
+ hash = hash + ((guint) obj->ifindex);
+ hash = hash * 33 + ((guint) obj->plen);
+ hash = hash * 33 + ((guint) obj->address);
+})
+_vt_cmd_plobj_id_hash (ip6_address, NMPlatformIP6Address, {
+ hash = (guint) 2907861637;
+ hash = hash + ((guint) obj->ifindex);
+ hash = hash * 33 + ((guint) obj->plen);
+ hash = hash * 33 + _id_hash_ip6_addr (&obj->address);
+})
+_vt_cmd_plobj_id_hash (ip4_route, NMPlatformIP4Route, {
+ /* libnl considers:
+ * .oo_id_attrs = (ROUTE_ATTR_FAMILY | ROUTE_ATTR_TOS |
+ * ROUTE_ATTR_TABLE | ROUTE_ATTR_DST |
+ * ROUTE_ATTR_PRIO),
+ */
+ hash = (guint) 2569857221;
+ hash = hash + ((guint) obj->ifindex);
+ hash = hash * 33 + ((guint) obj->plen);
+ hash = hash * 33 + ((guint) obj->metric);
+ hash = hash * 33 + ((guint) obj->network);
+})
+_vt_cmd_plobj_id_hash (ip6_route, NMPlatformIP6Route, {
+ hash = (guint) 3999787007;
+ hash = hash + ((guint) obj->ifindex);
+ hash = hash * 33 + ((guint) obj->plen);
+ hash = hash * 33 + ((guint) obj->metric);
+ hash = hash * 33 + _id_hash_ip6_addr (&obj->network);
+})
+
+gboolean
+nmp_object_is_alive (const NMPObject *obj)
+{
+ /* for convenience, allow NULL. */
+ if (!obj)
+ return FALSE;
+
+ return NMP_OBJECT_GET_CLASS (obj)->cmd_obj_is_alive (obj);
+}
+
+static gboolean
+_vt_cmd_obj_is_alive_link (const NMPObject *obj)
+{
+ return obj->object.ifindex > 0 && (obj->_link.netlink.is_in_netlink || obj->_link.udev.device);
+}
+
+static gboolean
+_vt_cmd_obj_is_alive_ipx_address (const NMPObject *obj)
+{
+ return obj->object.ifindex > 0;
+}
+
+static gboolean
+_vt_cmd_obj_is_alive_ipx_route (const NMPObject *obj)
+{
+ /* We want to ignore routes that are RTM_F_CLONED but we still
+ * let nmp_object_from_nl() create such route objects, instead of
+ * returning NULL right away.
+ *
+ * The idea is, that if we have the same route (according to its id)
+ * in the cache with !RTM_F_CLONED, an update that changes the route
+ * to be RTM_F_CLONED must remove the instance.
+ *
+ * If nmp_object_from_nl() would just return NULL, we couldn't look
+ * into the cache to see if it contains a route that now disappears
+ * (because it is cloned).
+ *
+ * Instead we create a dead object, and nmp_cache_update_netlink()
+ * will remove the old version of the update.
+ **/
+ return obj->object.ifindex > 0 && (obj->ip_route.source != _NM_IP_CONFIG_SOURCE_RTM_F_CLONED);
+}
+
+gboolean
+nmp_object_is_visible (const NMPObject *obj)
+
+{
+ /* for convenience, allow NULL. */
+ if (!obj)
+ return FALSE;
+
+ return NMP_OBJECT_GET_CLASS (obj)->cmd_obj_is_visible (obj);
+}
+
+static gboolean
+_vt_cmd_obj_is_visible_link (const NMPObject *obj)
+{
+ return obj->object.ifindex > 0 && obj->_link.netlink.is_in_netlink;
+}
+
+static gboolean
+_vt_cmd_obj_is_visible_ipx_address (const NMPObject *obj)
+{
+ return obj->object.ifindex > 0;
+}
+
+static gboolean
+_vt_cmd_obj_is_visible_ipx_route (const NMPObject *obj)
+{
+ NMIPConfigSource source = obj->ip_route.source;
+
+ return obj->object.ifindex > 0 && (source != _NM_IP_CONFIG_SOURCE_RTPROT_KERNEL && source != _NM_IP_CONFIG_SOURCE_RTM_F_CLONED);
+}
+
+/******************************************************************/
+
+/**
+ * nmp_object_from_nl:
+ * @platform: platform instance (needed to lookup sysctl)
+ * @nlo:
+ * @id_only: if %TRUE, only fill the id fields of the object and leave the
+ * other fields unset. This is useful to create a needle to lookup a matching
+ * item in the cache.
+ * @complete_from_cache: sometimes the netlink object doesn't contain all information.
+ * If true, look them up in the cache and preserve the original value.
+ *
+ * Convert a libnl object to a platform object.
+ * Returns: a NMPObject containing @nlo. If @id_only is %TRUE, only the id fields
+ * are defined.
+ **/
+NMPObject *
+nmp_object_from_nl (NMPlatform *platform, const struct nl_object *nlo, gboolean id_only, gboolean complete_from_cache)
+{
+ ObjectType obj_type = _nlo_get_object_type (nlo);
+ NMPObject *obj;
+
+ if (obj_type == OBJECT_TYPE_UNKNOWN)
+ return NULL;
+
+ obj = nmp_object_new (obj_type, NULL);
+
+ if (!NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_init_from_nl (platform, &obj->object, nlo, id_only, complete_from_cache)) {
+ nmp_object_unref (obj);
+ return NULL;
+ }
+ return obj;
+}
+
+struct nl_object *
+nmp_object_to_nl (NMPlatform *platform, const NMPObject *obj, gboolean id_only)
+{
+ return NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_nl (platform, &obj->object, id_only);
+}
+
+/******************************************************************/
+
+gboolean
+nmp_cache_id_equal (const NMPCacheId *a, const NMPCacheId *b)
+{
+ /* just memcmp() the entire id. This is potentially dangerous, because
+ * the struct is not __attribute__((packed)) and not all types have the
+ * same size. It is important, to memset() the entire struct to 0,
+ * not only the relevant fields.
+ *
+ * You anyway should use the nmp_cache_id_init_*() functions on a stack-allocated
+ * struct. */
+ return memcmp (a, b, sizeof (NMPCacheId)) == 0;
+}
+
+guint
+nmp_cache_id_hash (const NMPCacheId *id)
+{
+ guint hash = 5381;
+ guint i;
+
+ for (i = 0; i < sizeof (NMPCacheId); i++)
+ hash = ((hash << 5) + hash) + ((char *) id)[i]; /* hash * 33 + c */
+ return hash;
+}
+
+NMPCacheId *
+nmp_cache_id_clone (const NMPCacheId *id)
+{
+ NMPCacheId *id2;
+
+ id2 = g_slice_new (NMPCacheId);
+ memcpy (id2, id, sizeof (NMPCacheId));
+ return id2;
+}
+
+void
+nmp_cache_id_destroy (NMPCacheId *id)
+{
+ g_slice_free (NMPCacheId, id);
+}
+
+/******************************************************************/
+
+NMPCacheId _nmp_cache_id_static;
+
+NMPCacheId *
+nmp_cache_id_init (NMPCacheId *id, NMPCacheIdType id_type)
+{
+ memset (id, 0, sizeof (NMPCacheId));
+ id->_id_type = id_type;
+ return id;
+}
+
+NMPCacheId *
+nmp_cache_id_init_object_type (NMPCacheId *id, ObjectType obj_type)
+{
+ nmp_cache_id_init (id, NMP_CACHE_ID_TYPE_OBJECT_TYPE);
+ id->object_type.obj_type = obj_type;
+ return id;
+}
+
+NMPCacheId *
+nmp_cache_id_init_links (NMPCacheId *id, gboolean visible_only)
+{
+ if (visible_only)
+ return nmp_cache_id_init (id, NMP_CACHE_ID_TYPE_LINKS_VISIBLE_ONLY);
+ else
+ return nmp_cache_id_init_object_type (id, OBJECT_TYPE_LINK);
+}
+
+NMPCacheId *
+nmp_cache_id_init_addrroute_by_ifindex (NMPCacheId *id, ObjectType obj_type, int ifindex)
+{
+ nmp_cache_id_init (id, NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX);
+ id->addrroute_by_ifindex.obj_type = obj_type;
+ id->addrroute_by_ifindex.ifindex = ifindex;
+ return id;
+}
+
+NMPCacheId *
+nmp_cache_id_init_routes_visible (NMPCacheId *id, NMPCacheIdType id_type, gboolean is_v4, int ifindex)
+{
+ g_return_val_if_fail (NM_IN_SET (id_type, NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ALL, NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT, NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ONLY_DEFAULT), NULL);
+ nmp_cache_id_init (id, id_type);
+ id->routes_visible.is_v4 = !!is_v4;
+ id->routes_visible.ifindex = ifindex;
+ return id;
+}
+
+/******************************************************************/
+
+static gboolean
+_nmp_object_init_cache_id (const NMPObject *obj, NMPCacheIdType id_type, NMPCacheId *id, const NMPCacheId **out_id)
+{
+ const NMPClass *klass = NMP_OBJECT_GET_CLASS (obj);
+
+ if (id_type == NMP_CACHE_ID_TYPE_OBJECT_TYPE) {
+ *out_id = nmp_cache_id_init_object_type (id, klass->obj_type);
+ return TRUE;
+ }
+ return klass->cmd_obj_init_cache_id (obj, id_type, id, out_id);
+}
+
+static gboolean
+_vt_cmd_obj_init_cache_id_link (const NMPObject *obj, NMPCacheIdType id_type, NMPCacheId *id, const NMPCacheId **out_id)
+{
+ switch (id_type) {
+ case NMP_CACHE_ID_TYPE_LINKS_VISIBLE_ONLY:
+ if (_vt_cmd_obj_is_visible_link (obj)) {
+ *out_id = nmp_cache_id_init_links (id, TRUE);
+ return TRUE;
+ }
+ break;
+ default:
+ return FALSE;
+ }
+ *out_id = NULL;
+ return TRUE;
+}
+
+static gboolean
+_vt_cmd_obj_init_cache_id_ip4_address (const NMPObject *obj, NMPCacheIdType id_type, NMPCacheId *id, const NMPCacheId **out_id)
+{
+ switch (id_type) {
+ case NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX:
+ if (_vt_cmd_obj_is_visible_ipx_address (obj)) {
+ *out_id = nmp_cache_id_init_addrroute_by_ifindex (id, OBJECT_TYPE_IP4_ADDRESS, obj->object.ifindex);
+ return TRUE;
+ }
+ break;
+ default:
+ return FALSE;
+ }
+ *out_id = NULL;
+ return TRUE;
+}
+
+static gboolean
+_vt_cmd_obj_init_cache_id_ip6_address (const NMPObject *obj, NMPCacheIdType id_type, NMPCacheId *id, const NMPCacheId **out_id)
+{
+ switch (id_type) {
+ case NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX:
+ if (_vt_cmd_obj_is_visible_ipx_address (obj)) {
+ *out_id = nmp_cache_id_init_addrroute_by_ifindex (id, OBJECT_TYPE_IP6_ADDRESS, obj->object.ifindex);
+ return TRUE;
+ }
+ break;
+ default:
+ return FALSE;
+ }
+ *out_id = NULL;
+ return TRUE;
+}
+
+static gboolean
+_vt_cmd_obj_init_cache_id_ip4_route (const NMPObject *obj, NMPCacheIdType id_type, NMPCacheId *id, const NMPCacheId **out_id)
+{
+ switch (id_type) {
+ case NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX:
+ if (_vt_cmd_obj_is_visible_ipx_route (obj)) {
+ *out_id = nmp_cache_id_init_addrroute_by_ifindex (id, OBJECT_TYPE_IP4_ROUTE, obj->object.ifindex);
+ return TRUE;
+ }
+ break;
+ case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ALL:
+ if (_vt_cmd_obj_is_visible_ipx_route (obj)) {
+ *out_id = nmp_cache_id_init_routes_visible (id, id_type, TRUE, obj->object.ifindex);
+ return TRUE;
+ }
+ break;
+ case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT:
+ if ( _vt_cmd_obj_is_visible_ipx_route (obj)
+ && !NM_PLATFORM_IP_ROUTE_IS_DEFAULT (&obj->ip_route)) {
+ *out_id = nmp_cache_id_init_routes_visible (id, id_type, TRUE, obj->object.ifindex);
+ return TRUE;
+ }
+ break;
+ case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ONLY_DEFAULT:
+ if ( _vt_cmd_obj_is_visible_ipx_route (obj)
+ && NM_PLATFORM_IP_ROUTE_IS_DEFAULT (&obj->ip_route)) {
+ *out_id = nmp_cache_id_init_routes_visible (id, id_type, TRUE, obj->object.ifindex);
+ return TRUE;
+ }
+ break;
+ default:
+ return FALSE;
+ }
+ *out_id = NULL;
+ return TRUE;
+}
+
+static gboolean
+_vt_cmd_obj_init_cache_id_ip6_route (const NMPObject *obj, NMPCacheIdType id_type, NMPCacheId *id, const NMPCacheId **out_id)
+{
+ switch (id_type) {
+ case NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX:
+ *out_id = nmp_cache_id_init_addrroute_by_ifindex (id, OBJECT_TYPE_IP6_ROUTE, obj->object.ifindex);
+ return TRUE;
+ case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ALL:
+ if (_vt_cmd_obj_is_visible_ipx_route (obj)) {
+ *out_id = nmp_cache_id_init_routes_visible (id, id_type, FALSE, obj->object.ifindex);
+ return TRUE;
+ }
+ break;
+ case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT:
+ if ( _vt_cmd_obj_is_visible_ipx_route (obj)
+ && !NM_PLATFORM_IP_ROUTE_IS_DEFAULT (&obj->ip_route)) {
+ *out_id = nmp_cache_id_init_routes_visible (id, id_type, FALSE, obj->object.ifindex);
+ return TRUE;
+ }
+ break;
+ case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ONLY_DEFAULT:
+ if ( _vt_cmd_obj_is_visible_ipx_route (obj)
+ && NM_PLATFORM_IP_ROUTE_IS_DEFAULT (&obj->ip_route)) {
+ *out_id = nmp_cache_id_init_routes_visible (id, id_type, FALSE, obj->object.ifindex);
+ return TRUE;
+ }
+ break;
+ default:
+ return FALSE;
+ }
+ *out_id = NULL;
+ return TRUE;
+}
+
+/******************************************************************/
+
+gboolean
+nmp_cache_use_udev_detect ()
+{
+ return access ("/sys", W_OK) == 0;
+}
+
+gboolean
+nmp_cache_use_udev_get (const NMPCache *cache)
+{
+ g_return_val_if_fail (cache, TRUE);
+
+ return cache->use_udev;
+}
+
+gboolean
+nmp_cache_use_udev_set (NMPCache *cache, gboolean use_udev)
+{
+ g_return_val_if_fail (cache, FALSE);
+
+ use_udev = !!use_udev;
+ if (use_udev == cache->use_udev)
+ return FALSE;
+
+ cache->use_udev = use_udev;
+ return TRUE;
+}
+
+/******************************************************************/
+
+/**
+ * nmp_cache_link_connected_needs_toggle:
+ * @cache: the platform cache
+ * @master: the link object, that is checked whether its connected property
+ * needs to be toggled.
+ * @potential_slave: (allow-none): an additional link object that is treated
+ * as if it was inside @cache. If given, it shaddows a link in the cache
+ * with the same ifindex.
+ * @ignore_slave: (allow-none): if set, the check will pretend that @ignore_slave
+ * is not in the cache.
+ *
+ * NMPlatformLink has two connected flags: (master->link.flags&IFF_LOWER_UP) (as reported
+ * from netlink) and master->link.connected. For bond and bridge master, kernel reports
+ * those links as IFF_LOWER_UP if they have no slaves attached. We want to present instead
+ * a combined @connected flag that shows masters without slaves as down.
+ *
+ * Check if the connected flag of @master should be toggled according to the content
+ * of @cache (including @potential_slave).
+ *
+ * Returns: %TRUE, if @master->link.connected should be flipped/toggled.
+ **/
+gboolean
+nmp_cache_link_connected_needs_toggle (const NMPCache *cache, const NMPObject *master, const NMPObject *potential_slave, const NMPObject *ignore_slave)
+{
+ const NMPlatformLink *const *links;
+ gboolean is_lower_up = FALSE;
+ guint len, i;
+
+ if ( !master
+ || NMP_OBJECT_GET_TYPE (master) != OBJECT_TYPE_LINK
+ || master->link.ifindex <= 0
+ || !nmp_object_is_visible (master)
+ || !NM_IN_SET (master->link.type, NM_LINK_TYPE_BRIDGE, NM_LINK_TYPE_BOND))
+ return FALSE;
+
+ /* if native IFF_LOWER_UP is down, link.connected must also be down
+ * regardless of the slaves. */
+ if (!NM_FLAGS_HAS (master->link.flags, IFF_LOWER_UP))
+ return !!master->link.connected;
+
+ if (potential_slave && NMP_OBJECT_GET_TYPE (potential_slave) != OBJECT_TYPE_LINK)
+ potential_slave = NULL;
+
+ if ( potential_slave
+ && nmp_object_is_visible (potential_slave)
+ && potential_slave->link.ifindex > 0
+ && potential_slave->link.master == master->link.ifindex
+ && potential_slave->link.connected) {
+ is_lower_up = TRUE;
+ } else {
+ links = (const NMPlatformLink *const *) nmp_cache_lookup_multi (cache, nmp_cache_id_init_links (NMP_CACHE_ID_STATIC, FALSE), &len);
+ for (i = 0; i < len; i++) {
+ const NMPlatformLink *link = links[i];
+ const NMPObject *obj = NMP_OBJECT_UP_CAST ((NMPlatformObject *) link);
+
+ nm_assert (NMP_OBJECT_GET_TYPE (NMP_OBJECT_UP_CAST ((NMPlatformObject *) link)) == OBJECT_TYPE_LINK);
+
+ if ( (!potential_slave || potential_slave->link.ifindex != link->ifindex)
+ && ignore_slave != obj
+ && link->ifindex > 0
+ && link->master == master->link.ifindex
+ && nmp_object_is_visible (obj)
+ && link->connected) {
+ is_lower_up = TRUE;
+ break;
+ }
+ }
+ }
+ return !!master->link.connected != is_lower_up;
+}
+
+/**
+ * nmp_cache_link_connected_needs_toggle_by_ifindex:
+ * @cache:
+ * @master_ifindex: the ifindex of a potential master that should be checked
+ * whether it needs toggling.
+ * @potential_slave: (allow-none): passed to nmp_cache_link_connected_needs_toggle().
+ * It considers @potential_slave as being inside the cache, replacing an existing
+ * link with the same ifindex.
+ * @ignore_slave: (allow-onne): passed to nmp_cache_link_connected_needs_toggle().
+ *
+ * The flag obj->link.connected depends on the state of other links in the
+ * @cache. See also nmp_cache_link_connected_needs_toggle(). Given an ifindex
+ * of a master, check if the cache contains such a master link that needs
+ * toogling of the connected flag.
+ *
+ * Returns: NULL if there is no master link with ifindex @master_ifindex that should be toggled.
+ * Otherwise, return the link object from inside the cache with the given ifindex.
+ * The connected flag of that master should be toggled.
+ */
+const NMPObject *
+nmp_cache_link_connected_needs_toggle_by_ifindex (const NMPCache *cache, int master_ifindex, const NMPObject *potential_slave, const NMPObject *ignore_slave)
+{
+ const NMPObject *master;
+
+ if (master_ifindex > 0) {
+ master = nmp_cache_lookup_link (cache, master_ifindex);
+ if (nmp_cache_link_connected_needs_toggle (cache, master, potential_slave, ignore_slave))
+ return master;
+ }
+ return NULL;
+}
+
+/******************************************************************/
+
+const NMPlatformObject *const *
+nmp_cache_lookup_multi (const NMPCache *cache, const NMPCacheId *cache_id, guint *out_len)
+{
+ return (const NMPlatformObject *const *) nm_multi_index_lookup (cache->idx_multi,
+ (const NMMultiIndexId *) cache_id,
+ out_len);
+}
+
+GArray *
+nmp_cache_lookup_multi_to_array (const NMPCache *cache, ObjectType obj_type, const NMPCacheId *cache_id)
+{
+ const NMPClass *klass = nmp_class_from_type (obj_type);
+ guint len, i;
+ const NMPlatformObject *const *objects;
+ GArray *array;
+
+ g_return_val_if_fail (klass, NULL);
+
+ objects = nmp_cache_lookup_multi (cache, cache_id, &len);
+ array = g_array_sized_new (FALSE, FALSE, klass->sizeof_public, len);
+
+ for (i = 0; i < len; i++) {
+ nm_assert (NMP_OBJECT_GET_CLASS (NMP_OBJECT_UP_CAST (objects[i])) == klass);
+ g_array_append_vals (array, objects[i], 1);
+ }
+ return array;
+}
+
+const NMPObject *
+nmp_cache_lookup_obj (const NMPCache *cache, const NMPObject *obj)
+{
+ g_return_val_if_fail (obj, NULL);
+
+ return g_hash_table_lookup (cache->idx_main, obj);
+}
+
+const NMPObject *
+nmp_cache_lookup_link (const NMPCache *cache, int ifindex)
+{
+ NMPObject obj_needle;
+
+ return nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&obj_needle, ifindex));
+}
+
+const NMPObject *
+nmp_cache_lookup_link_full (const NMPCache *cache,
+ int ifindex,
+ const char *ifname,
+ gboolean visible_only,
+ NMLinkType link_type,
+ NMPObjectMatchFn match_fn,
+ gpointer user_data)
+{
+ NMPObject obj_needle;
+ const NMPObject *obj;
+ const NMPlatformObject *const *list;
+ guint i, len;
+ NMPCacheId cache_id;
+
+ if (ifindex > 0) {
+ obj = nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&obj_needle, ifindex));
+
+ if ( !obj
+ || (visible_only && !nmp_object_is_visible (obj))
+ || (link_type != NM_LINK_TYPE_NONE && obj->link.type != link_type)
+ || (ifname && strcmp (obj->link.name, ifname))
+ || (match_fn && !match_fn (obj, user_data)))
+ return NULL;
+ return obj;
+ } else if (!ifname && !match_fn)
+ return NULL;
+ else {
+ list = nmp_cache_lookup_multi (cache, nmp_cache_id_init_object_type (&cache_id, OBJECT_TYPE_LINK), &len);
+ for (i = 0; i < len; i++) {
+ obj = NMP_OBJECT_UP_CAST (list[i]);
+
+ if (visible_only && !nmp_object_is_visible (obj))
+ continue;
+ if (link_type != NM_LINK_TYPE_NONE && obj->link.type != link_type)
+ continue;
+ if (ifname && strcmp (ifname, obj->link.name))
+ continue;
+ if (match_fn && !match_fn (obj, user_data))
+ continue;
+
+ return obj;
+ }
+ return NULL;
+ }
+}
+
+GHashTable *
+nmp_cache_lookup_all_to_hash (const NMPCache *cache,
+ NMPCacheId *cache_id,
+ GHashTable *hash)
+{
+ NMMultiIndexIdIter iter;
+ gpointer plobj;
+
+ nm_multi_index_id_iter_init (&iter, cache->idx_multi, (const NMMultiIndexId *) cache_id);
+
+ if (nm_multi_index_id_iter_next (&iter, &plobj)) {
+ if (!hash)
+ hash = g_hash_table_new_full (NULL, NULL, (GDestroyNotify) nmp_object_unref, NULL);
+
+ do {
+ g_hash_table_add (hash, nmp_object_ref (NMP_OBJECT_UP_CAST (plobj)));
+ } while (nm_multi_index_id_iter_next (&iter, &plobj));
+ }
+
+ return hash;
+}
+
+/******************************************************************/
+
+static void
+_nmp_cache_update_cache (NMPCache *cache, NMPObject *obj, gboolean remove)
+{
+ NMPCacheIdType id_type;
+
+ for (id_type = 0; id_type <= NMP_CACHE_ID_TYPE_MAX; id_type++) {
+ NMPCacheId cache_id_storage;
+ const NMPCacheId *cache_id;
+
+ if (!_nmp_object_init_cache_id (obj, id_type, &cache_id_storage, &cache_id))
+ continue;
+ if (!cache_id)
+ continue;
+
+ /* We don't put @obj itself into the multi index, but &obj->object. As of now, all
+ * users expect a pointer to NMPlatformObject, not NMPObject.
+ * You can use NMP_OBJECT_UP_CAST() to retrieve the original @obj pointer.
+ *
+ * If need be, we could determine based on @id_type which pointer we want to store. */
+
+ if (remove) {
+ if (!nm_multi_index_remove (cache->idx_multi, &cache_id->base, &obj->object))
+ g_assert_not_reached ();
+ } else {
+ if (!nm_multi_index_add (cache->idx_multi, &cache_id->base, &obj->object))
+ g_assert_not_reached ();
+ }
+ }
+}
+
+static void
+_nmp_cache_update_add (NMPCache *cache, NMPObject *obj)
+{
+ nm_assert (!obj->is_cached);
+ nmp_object_ref (obj);
+ nm_assert (!nm_multi_index_lookup_first_by_value (cache->idx_multi, &obj->object));
+ if (!g_hash_table_add (cache->idx_main, obj))
+ g_assert_not_reached ();
+ obj->is_cached = TRUE;
+ _nmp_cache_update_cache (cache, obj, FALSE);
+}
+
+static void
+_nmp_cache_update_remove (NMPCache *cache, NMPObject *obj)
+{
+ nm_assert (obj->is_cached);
+ _nmp_cache_update_cache (cache, obj, TRUE);
+ obj->is_cached = FALSE;
+ if (!g_hash_table_remove (cache->idx_main, obj))
+ g_assert_not_reached ();
+
+ /* @obj is possibly a dangling pointer at this point. No problem, multi-index doesn't dereference. */
+ nm_assert (!nm_multi_index_lookup_first_by_value (cache->idx_multi, &obj->object));
+}
+
+static void
+_nmp_cache_update_update (NMPCache *cache, NMPObject *obj, const NMPObject *new)
+{
+ NMPCacheIdType id_type;
+
+ nm_assert (NMP_OBJECT_GET_CLASS (obj) == NMP_OBJECT_GET_CLASS (new));
+ nm_assert (obj->is_cached);
+ nm_assert (!new->is_cached);
+
+ for (id_type = 0; id_type <= NMP_CACHE_ID_TYPE_MAX; id_type++) {
+ NMPCacheId cache_id_storage_obj, cache_id_storage_new;
+ const NMPCacheId *cache_id_obj, *cache_id_new;
+
+ if (!_nmp_object_init_cache_id (obj, id_type, &cache_id_storage_obj, &cache_id_obj))
+ continue;
+ if (!_nmp_object_init_cache_id (new, id_type, &cache_id_storage_new, &cache_id_new))
+ g_assert_not_reached ();
+ if (!nm_multi_index_move (cache->idx_multi, (NMMultiIndexId *) cache_id_obj, (NMMultiIndexId *) cache_id_new, &obj->object))
+ g_assert_not_reached ();
+ }
+ nmp_object_copy (obj, new, FALSE);
+}
+
+NMPCacheOpsType
+nmp_cache_remove (NMPCache *cache, const NMPObject *obj, gboolean equals_by_ptr, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data)
+{
+ NMPObject *old;
+
+ nm_assert (NMP_OBJECT_IS_VALID (obj));
+
+ old = g_hash_table_lookup (cache->idx_main, obj);
+ if (!old) {
+ if (out_obj)
+ *out_obj = NULL;
+ if (out_was_visible)
+ *out_was_visible = FALSE;
+ return NMP_CACHE_OPS_UNCHANGED;
+ }
+
+ if (out_obj)
+ *out_obj = nmp_object_ref (old);
+ if (out_was_visible)
+ *out_was_visible = nmp_object_is_visible (old);
+ if (equals_by_ptr && old != obj) {
+ /* We found an identical object, but we only delete it if it's the same pointer as
+ * @obj. */
+ return NMP_CACHE_OPS_UNCHANGED;
+ }
+ if (pre_hook)
+ pre_hook (cache, old, NULL, NMP_CACHE_OPS_REMOVED, user_data);
+ _nmp_cache_update_remove (cache, old);
+ return NMP_CACHE_OPS_REMOVED;
+}
+
+NMPCacheOpsType
+nmp_cache_remove_netlink (NMPCache *cache, const NMPObject *obj_needle, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data)
+{
+ if (NMP_OBJECT_GET_TYPE (obj_needle) == OBJECT_TYPE_LINK) {
+ NMPObject *old;
+ auto_nmp_obj NMPObject *obj = NULL;
+
+ /* For nmp_cache_remove_netlink() we have an incomplete @obj_needle instance to be
+ * removed from netlink. Link objects are alive without being in netlink when they
+ * have a udev-device. All we want to do in this case is clear the netlink.is_in_netlink
+ * flag. */
+
+ old = (NMPObject *) nmp_cache_lookup_link (cache, obj_needle->link.ifindex);
+ if (!old) {
+ if (out_obj)
+ *out_obj = NULL;
+ if (out_was_visible)
+ *out_was_visible = FALSE;
+ return NMP_CACHE_OPS_UNCHANGED;
+ }
+
+ if (out_obj)
+ *out_obj = nmp_object_ref (old);
+ if (out_was_visible)
+ *out_was_visible = nmp_object_is_visible (old);
+
+ if (!old->_link.netlink.is_in_netlink) {
+ nm_assert (old->_link.udev.device);
+ return NMP_CACHE_OPS_UNCHANGED;
+ }
+
+ if (!old->_link.udev.device) {
+ /* the update would make @old invalid. Remove it. */
+ if (pre_hook)
+ pre_hook (cache, old, NULL, NMP_CACHE_OPS_REMOVED, user_data);
+ _nmp_cache_update_remove (cache, old);
+ return NMP_CACHE_OPS_REMOVED;
+ }
+
+ obj = nmp_object_clone (old, FALSE);
+ obj->_link.netlink.is_in_netlink = FALSE;
+
+ _nmp_object_fixup_link_master_connected (obj, cache);
+ _nmp_object_fixup_link_udev_fields (obj, cache->use_udev);
+
+ if (pre_hook)
+ pre_hook (cache, old, obj, NMP_CACHE_OPS_UPDATED, user_data);
+ _nmp_cache_update_update (cache, old, obj);
+ return NMP_CACHE_OPS_UPDATED;
+ } else
+ return nmp_cache_remove (cache, obj_needle, FALSE, out_obj, out_was_visible, pre_hook, user_data);
+}
+
+/**
+ * nmp_cache_update_netlink:
+ * @cache: the platform cache
+ * @obj: a #NMPObject instance as received from netlink and created via
+ * nmp_object_from_nl(). Especially for link, it must not have the udev
+ * replated fields set.
+ * This instance will be modified and might be put into the cache. When
+ * calling nmp_cache_update_netlink() you hand @obj over to the cache.
+ * Except, that the cache will increment the ref count as appropriate. You
+ * must still unref the obj to release your part of the ownership.
+ * @out_obj: (allow-none): (out): return the object instance that is inside
+ * the cache. If you specify non %NULL, you must always unref the returned
+ * instance. If the return value indicates that the object was removed,
+ * the object is no longer in the cache. Even if the return value indicates
+ * that the object was unchanged, it will still return @out_obj -- if
+ * such an object is in the cache.
+ * @out_was_visible: (allow-none): (out): whether the object was visible before
+ * the update operation.
+ * @pre_hook: (allow-none): a callback *before* the object gets updated. You cannot
+ * influence the outcome and must not do anything beyong inspecting the changes.
+ * @user_data:
+ *
+ * Returns: how the cache changed.
+ **/
+NMPCacheOpsType
+nmp_cache_update_netlink (NMPCache *cache, NMPObject *obj, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data)
+{
+ NMPObject *old;
+
+ nm_assert (NMP_OBJECT_IS_VALID (obj));
+ nm_assert (!NMP_OBJECT_IS_STACKINIT (obj));
+ nm_assert (!obj->is_cached);
+
+ /* A link object from netlink must have the udev related fields unset.
+ * We could implement to handle that, but there is no need to support such
+ * a use-case */
+ nm_assert (NMP_OBJECT_GET_TYPE (obj) != OBJECT_TYPE_LINK ||
+ ( !obj->_link.udev.device
+ && !obj->link.driver
+ && !obj->link.udi));
+
+ old = g_hash_table_lookup (cache->idx_main, obj);
+
+ if (out_obj)
+ *out_obj = NULL;
+ if (out_was_visible)
+ *out_was_visible = FALSE;
+
+ if (!old) {
+ if (!nmp_object_is_alive (obj))
+ return NMP_CACHE_OPS_UNCHANGED;
+
+ if (NMP_OBJECT_GET_TYPE (obj) == OBJECT_TYPE_LINK) {
+ _nmp_object_fixup_link_master_connected (obj, cache);
+ _nmp_object_fixup_link_udev_fields (obj, cache->use_udev);
+ }
+
+ if (out_obj)
+ *out_obj = nmp_object_ref (obj);
+
+ if (pre_hook)
+ pre_hook (cache, NULL, obj, NMP_CACHE_OPS_ADDED, user_data);
+ _nmp_cache_update_add (cache, obj);
+ return NMP_CACHE_OPS_ADDED;
+ } else if (old == obj) {
+ /* updating a cached object inplace is not supported because the object contributes to hash-key
+ * for NMMultiIndex. Modifying an object that is inside NMMultiIndex means that these
+ * keys change.
+ * The problem is, that for a given object NMMultiIndex does not support (efficient)
+ * reverse lookup to get all the NMPCacheIds to which it belongs. If that would be implemented,
+ * it would be possible to implement inplace-update.
+ *
+ * There is an un-optimized reverse lookup via nm_multi_index_iter_init(), but we don't want
+ * that because we might have a large number of indexes to search.
+ *
+ * We could add efficient reverse lookup by adding a reverse index to NMMultiIndex. But that
+ * also adds some cost to support an (uncommon?) usage pattern.
+ *
+ * Instead we just don't support it. Actually, we expect the user to
+ * create a new instance with nmp_object_from_nl(). That is what nmp_cache_update_netlink().
+ *
+ * TL;DR: a cached object must never be modified.
+ */
+ g_assert_not_reached ();
+ } else {
+ gboolean is_alive = FALSE;
+
+ nm_assert (old->is_cached);
+
+ if (out_obj)
+ *out_obj = nmp_object_ref (old);
+ if (out_was_visible)
+ *out_was_visible = nmp_object_is_visible (old);
+
+ if (NMP_OBJECT_GET_TYPE (obj) == OBJECT_TYPE_LINK) {
+ if (!obj->_link.netlink.is_in_netlink) {
+ if (!old->_link.netlink.is_in_netlink) {
+ nm_assert (old->_link.udev.device);
+ return NMP_CACHE_OPS_UNCHANGED;
+ }
+ if (old->_link.udev.device) {
+ /* @obj is not in netlink.
+ *
+ * This is similar to nmp_cache_remove_netlink(), but there we preserve the
+ * preexisting netlink properties. The use case of that is when kernel_get_object()
+ * cannot load an object (based on the id of a needle).
+ *
+ * Here we keep the data provided from @obj. The usecase is when receiving
+ * a valid @obj instance from netlink with RTM_DELROUTE.
+ */
+ is_alive = TRUE;
+ }
+ } else
+ is_alive = TRUE;
+
+ if (is_alive) {
+ _nmp_object_fixup_link_master_connected (obj, cache);
+
+ /* Merge the netlink parts with what we have from udev. */
+ g_clear_object (&obj->_link.udev.device);
+ obj->_link.udev.device = old->_link.udev.device ? g_object_ref (old->_link.udev.device) : NULL;
+ _nmp_object_fixup_link_udev_fields (obj, cache->use_udev);
+ }
+ } else
+ is_alive = nmp_object_is_alive (obj);
+
+ if (!is_alive) {
+ /* the update would make @old invalid. Remove it. */
+ if (pre_hook)
+ pre_hook (cache, old, NULL, NMP_CACHE_OPS_REMOVED, user_data);
+ _nmp_cache_update_remove (cache, old);
+ return NMP_CACHE_OPS_REMOVED;
+ }
+
+ if (nmp_object_equal (old, obj))
+ return NMP_CACHE_OPS_UNCHANGED;
+
+ if (pre_hook)
+ pre_hook (cache, old, obj, NMP_CACHE_OPS_UPDATED, user_data);
+ _nmp_cache_update_update (cache, old, obj);
+ return NMP_CACHE_OPS_UPDATED;
+ }
+}
+
+NMPCacheOpsType
+nmp_cache_update_link_udev (NMPCache *cache, int ifindex, GUdevDevice *udev_device, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data)
+{
+ NMPObject *old;
+ auto_nmp_obj NMPObject *obj = NULL;
+
+ old = (NMPObject *) nmp_cache_lookup_link (cache, ifindex);
+
+ if (out_obj)
+ *out_obj = NULL;
+ if (out_was_visible)
+ *out_was_visible = FALSE;
+
+ if (!old) {
+ if (!udev_device)
+ return NMP_CACHE_OPS_UNCHANGED;
+
+ obj = nmp_object_new (OBJECT_TYPE_LINK, NULL);
+ obj->link.ifindex = ifindex;
+ obj->_link.udev.device = g_object_ref (udev_device);
+
+ _nmp_object_fixup_link_udev_fields (obj, cache->use_udev);
+
+ nm_assert (nmp_object_is_alive (obj));
+
+ if (out_obj)
+ *out_obj = nmp_object_ref (obj);
+
+ if (pre_hook)
+ pre_hook (cache, NULL, obj, NMP_CACHE_OPS_ADDED, user_data);
+ _nmp_cache_update_add (cache, obj);
+ return NMP_CACHE_OPS_ADDED;
+ } else {
+ nm_assert (old->is_cached);
+
+ if (out_obj)
+ *out_obj = nmp_object_ref (old);
+ if (out_was_visible)
+ *out_was_visible = nmp_object_is_visible (old);
+
+ if (old->_link.udev.device == udev_device)
+ return NMP_CACHE_OPS_UNCHANGED;
+
+ if (!udev_device && !old->_link.netlink.is_in_netlink) {
+ /* the update would make @old invalid. Remove it. */
+ if (pre_hook)
+ pre_hook (cache, old, NULL, NMP_CACHE_OPS_REMOVED, user_data);
+ _nmp_cache_update_remove (cache, old);
+ return NMP_CACHE_OPS_REMOVED;
+ }
+
+ obj = nmp_object_clone (old, FALSE);
+
+ g_clear_object (&obj->_link.udev.device);
+ obj->_link.udev.device = udev_device ? g_object_ref (udev_device) : NULL;
+
+ _nmp_object_fixup_link_udev_fields (obj, cache->use_udev);
+
+ nm_assert (nmp_object_is_alive (obj));
+
+ if (pre_hook)
+ pre_hook (cache, old, obj, NMP_CACHE_OPS_UPDATED, user_data);
+ _nmp_cache_update_update (cache, old, obj);
+ return NMP_CACHE_OPS_UPDATED;
+ }
+}
+
+NMPCacheOpsType
+nmp_cache_update_link_master_connected (NMPCache *cache, int ifindex, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data)
+{
+ NMPObject *old;
+ auto_nmp_obj NMPObject *obj = NULL;
+
+ old = (NMPObject *) nmp_cache_lookup_link (cache, ifindex);
+
+ if (!old) {
+ if (out_obj)
+ *out_obj = NULL;
+ if (out_was_visible)
+ *out_was_visible = FALSE;
+
+ return NMP_CACHE_OPS_UNCHANGED;
+ }
+
+ nm_assert (old->is_cached);
+
+ if (out_obj)
+ *out_obj = nmp_object_ref (old);
+ if (out_was_visible)
+ *out_was_visible = nmp_object_is_visible (old);
+
+ if (!nmp_cache_link_connected_needs_toggle (cache, old, NULL, NULL))
+ return NMP_CACHE_OPS_UNCHANGED;
+
+ obj = nmp_object_clone (old, FALSE);
+ obj->link.connected = !old->link.connected;
+
+ nm_assert (nmp_object_is_alive (obj));
+
+ if (pre_hook)
+ pre_hook (cache, old, obj, NMP_CACHE_OPS_UPDATED, user_data);
+ _nmp_cache_update_update (cache, old, obj);
+ return NMP_CACHE_OPS_UPDATED;
+}
+
+/******************************************************************/
+
+NMPCache *
+nmp_cache_new ()
+{
+ NMPCache *cache = g_new (NMPCache, 1);
+
+ cache->idx_main = g_hash_table_new_full ((GHashFunc) nmp_object_id_hash,
+ (GEqualFunc) nmp_object_id_equal,
+ (GDestroyNotify) nmp_object_unref,
+ NULL);
+ cache->idx_multi = nm_multi_index_new ((NMMultiIndexFuncHash) nmp_cache_id_hash,
+ (NMMultiIndexFuncEqual) nmp_cache_id_equal,
+ (NMMultiIndexFuncClone) nmp_cache_id_clone,
+ (NMMultiIndexFuncDestroy) nmp_cache_id_destroy);
+ cache->use_udev = nmp_cache_use_udev_detect ();
+ return cache;
+}
+
+void
+nmp_cache_free (NMPCache *cache)
+{
+ GHashTableIter iter;
+ NMPObject *obj;
+
+ /* No need to cumbersomely remove the objects properly. They are not hooked up
+ * in a complicated way, we can just unref them together with cache->idx_main.
+ *
+ * But we must clear the @is_cached flag. */
+ g_hash_table_iter_init (&iter, cache->idx_main);
+ while (g_hash_table_iter_next (&iter, (gpointer *) &obj, NULL)) {
+ nm_assert (obj->is_cached);
+ obj->is_cached = FALSE;
+ }
+
+ nm_multi_index_free (cache->idx_multi);
+ g_hash_table_unref (cache->idx_main);
+
+ g_free (cache);
+}
+
+/******************************************************************/
+
+void
+ASSERT_nmp_cache_is_consistent (const NMPCache *cache)
+{
+#ifdef NM_MORE_ASSERTS
+ NMMultiIndexIter iter_multi;
+ GHashTableIter iter_hash;
+ guint i, len;
+ NMPCacheId cache_id_storage;
+ const NMPCacheId *cache_id, *cache_id2;
+ const NMPlatformObject *const *objects;
+ const NMPObject *obj;
+
+ g_assert (cache);
+
+ g_hash_table_iter_init (&iter_hash, cache->idx_main);
+ while (g_hash_table_iter_next (&iter_hash, (gpointer *) &obj, NULL)) {
+ NMPCacheIdType id_type;
+
+ g_assert (NMP_OBJECT_IS_VALID (obj));
+ g_assert (nmp_object_is_alive (obj));
+
+ for (id_type = 0; id_type <= NMP_CACHE_ID_TYPE_MAX; id_type++) {
+ if (!_nmp_object_init_cache_id (obj, id_type, &cache_id_storage, &cache_id))
+ continue;
+ if (!cache_id)
+ continue;
+ g_assert (nm_multi_index_contains (cache->idx_multi, &cache_id->base, &obj->object));
+ }
+ }
+
+ nm_multi_index_iter_init (&iter_multi, cache->idx_multi, NULL);
+ while (nm_multi_index_iter_next (&iter_multi,
+ (const NMMultiIndexId **) &cache_id,
+ (void *const**) &objects,
+ &len)) {
+ g_assert (len > 0 && objects && objects[len] == NULL);
+
+ for (i = 0; i < len; i++) {
+ g_assert (objects[i]);
+ obj = NMP_OBJECT_UP_CAST (objects[i]);
+ g_assert (NMP_OBJECT_IS_VALID (obj));
+
+ /* for now, enforce that all objects for a certain index are of the same type. */
+ g_assert (NMP_OBJECT_GET_CLASS (obj) == NMP_OBJECT_GET_CLASS (NMP_OBJECT_UP_CAST (objects[0])));
+
+ if (!_nmp_object_init_cache_id (obj, cache_id->_id_type, &cache_id_storage, &cache_id2))
+ g_assert_not_reached ();
+ g_assert (cache_id2);
+ g_assert (nmp_cache_id_equal (cache_id, cache_id2));
+ g_assert_cmpint (nmp_cache_id_hash (cache_id), ==, nmp_cache_id_hash (cache_id2));
+
+ g_assert (obj == g_hash_table_lookup (cache->idx_main, obj));
+ }
+ }
+#endif
+}
+/******************************************************************/
+
+const NMPClass _nmp_classes[OBJECT_TYPE_MAX] = {
+ [OBJECT_TYPE_LINK - 1] = {
+ .obj_type = OBJECT_TYPE_LINK,
+ .sizeof_data = sizeof (NMPObjectLink),
+ .sizeof_public = sizeof (NMPlatformLink),
+ .obj_type_name = "link",
+ .nl_type = "route/link",
+ .addr_family = AF_UNSPEC,
+ .rtm_gettype = RTM_GETLINK,
+ .signal_type = NM_PLATFORM_SIGNAL_LINK_CHANGED,
+ .cmd_obj_init_cache_id = _vt_cmd_obj_init_cache_id_link,
+ .cmd_obj_equal = _vt_cmd_obj_equal_link,
+ .cmd_obj_copy = _vt_cmd_obj_copy_link,
+ .cmd_obj_stackinit_id = _vt_cmd_obj_stackinit_id_link,
+ .cmd_obj_dispose = _vt_cmd_obj_dispose_link,
+ .cmd_obj_is_alive = _vt_cmd_obj_is_alive_link,
+ .cmd_obj_is_visible = _vt_cmd_obj_is_visible_link,
+ .cmd_plobj_init_from_nl = _nmp_vt_cmd_plobj_init_from_nl_link,
+ .cmd_plobj_to_nl = _nmp_vt_cmd_plobj_to_nl_link,
+ .cmd_plobj_id_copy = _vt_cmd_plobj_id_copy_link,
+ .cmd_plobj_id_equal = _vt_cmd_plobj_id_equal_link,
+ .cmd_plobj_id_hash = _vt_cmd_plobj_id_hash_link,
+ .cmd_plobj_to_string_id = _vt_cmd_plobj_to_string_id_link,
+ .cmd_plobj_to_string = (const char *(*) (const NMPlatformObject *obj)) nm_platform_link_to_string,
+ .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_link_cmp,
+ },
+ [OBJECT_TYPE_IP4_ADDRESS - 1] = {
+ .obj_type = OBJECT_TYPE_IP4_ADDRESS,
+ .sizeof_data = sizeof (NMPObjectIP4Address),
+ .sizeof_public = sizeof (NMPlatformIP4Address),
+ .obj_type_name = "ip4-address",
+ .nl_type = "route/addr",
+ .addr_family = AF_INET,
+ .rtm_gettype = RTM_GETADDR,
+ .signal_type = NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED,
+ .cmd_obj_init_cache_id = _vt_cmd_obj_init_cache_id_ip4_address,
+ .cmd_obj_equal = _vt_cmd_obj_equal_plain,
+ .cmd_obj_copy = _vt_cmd_obj_copy_plain,
+ .cmd_obj_stackinit_id = _vt_cmd_obj_stackinit_id_ip4_address,
+ .cmd_obj_is_alive = _vt_cmd_obj_is_alive_ipx_address,
+ .cmd_obj_is_visible = _vt_cmd_obj_is_visible_ipx_address,
+ .cmd_plobj_init_from_nl = _nmp_vt_cmd_plobj_init_from_nl_ip4_address,
+ .cmd_plobj_to_nl = _nmp_vt_cmd_plobj_to_nl_ip4_address,
+ .cmd_plobj_id_copy = _vt_cmd_plobj_id_copy_ip4_address,
+ .cmd_plobj_id_equal = _vt_cmd_plobj_id_equal_ip4_address,
+ .cmd_plobj_id_hash = _vt_cmd_plobj_id_hash_ip4_address,
+ .cmd_plobj_to_string_id = _vt_cmd_plobj_to_string_id_ip4_address,
+ .cmd_plobj_to_string = (const char *(*) (const NMPlatformObject *obj)) nm_platform_ip4_address_to_string,
+ .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip4_address_cmp,
+ },
+ [OBJECT_TYPE_IP6_ADDRESS - 1] = {
+ .obj_type = OBJECT_TYPE_IP6_ADDRESS,
+ .sizeof_data = sizeof (NMPObjectIP6Address),
+ .sizeof_public = sizeof (NMPlatformIP6Address),
+ .obj_type_name = "ip6-address",
+ .nl_type = "route/addr",
+ .addr_family = AF_INET6,
+ .rtm_gettype = RTM_GETADDR,
+ .signal_type = NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED,
+ .cmd_obj_init_cache_id = _vt_cmd_obj_init_cache_id_ip6_address,
+ .cmd_obj_equal = _vt_cmd_obj_equal_plain,
+ .cmd_obj_copy = _vt_cmd_obj_copy_plain,
+ .cmd_obj_stackinit_id = _vt_cmd_obj_stackinit_id_ip6_address,
+ .cmd_obj_is_alive = _vt_cmd_obj_is_alive_ipx_address,
+ .cmd_obj_is_visible = _vt_cmd_obj_is_visible_ipx_address,
+ .cmd_plobj_init_from_nl = _nmp_vt_cmd_plobj_init_from_nl_ip6_address,
+ .cmd_plobj_to_nl = _nmp_vt_cmd_plobj_to_nl_ip6_address,
+ .cmd_plobj_id_copy = _vt_cmd_plobj_id_copy_ip6_address,
+ .cmd_plobj_id_equal = _vt_cmd_plobj_id_equal_ip6_address,
+ .cmd_plobj_id_hash = _vt_cmd_plobj_id_hash_ip6_address,
+ .cmd_plobj_to_string_id = _vt_cmd_plobj_to_string_id_ip6_address,
+ .cmd_plobj_to_string = (const char *(*) (const NMPlatformObject *obj)) nm_platform_ip6_address_to_string,
+ .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip6_address_cmp
+ },
+ [OBJECT_TYPE_IP4_ROUTE - 1] = {
+ .obj_type = OBJECT_TYPE_IP4_ROUTE,
+ .sizeof_data = sizeof (NMPObjectIP4Route),
+ .sizeof_public = sizeof (NMPlatformIP4Route),
+ .obj_type_name = "ip4-route",
+ .nl_type = "route/route",
+ .addr_family = AF_INET,
+ .rtm_gettype = RTM_GETROUTE,
+ .signal_type = NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED,
+ .cmd_obj_init_cache_id = _vt_cmd_obj_init_cache_id_ip4_route,
+ .cmd_obj_equal = _vt_cmd_obj_equal_plain,
+ .cmd_obj_copy = _vt_cmd_obj_copy_plain,
+ .cmd_obj_stackinit_id = _vt_cmd_obj_stackinit_id_ip4_route,
+ .cmd_obj_is_alive = _vt_cmd_obj_is_alive_ipx_route,
+ .cmd_obj_is_visible = _vt_cmd_obj_is_visible_ipx_route,
+ .cmd_plobj_init_from_nl = _nmp_vt_cmd_plobj_init_from_nl_ip4_route,
+ .cmd_plobj_to_nl = _nmp_vt_cmd_plobj_to_nl_ip4_route,
+ .cmd_plobj_id_copy = _vt_cmd_plobj_id_copy_ip4_route,
+ .cmd_plobj_id_equal = _vt_cmd_plobj_id_equal_ip4_route,
+ .cmd_plobj_id_hash = _vt_cmd_plobj_id_hash_ip4_route,
+ .cmd_plobj_to_string_id = _vt_cmd_plobj_to_string_id_ip4_route,
+ .cmd_plobj_to_string = (const char *(*) (const NMPlatformObject *obj)) nm_platform_ip4_route_to_string,
+ .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip4_route_cmp,
+ },
+ [OBJECT_TYPE_IP6_ROUTE - 1] = {
+ .obj_type = OBJECT_TYPE_IP6_ROUTE,
+ .sizeof_data = sizeof (NMPObjectIP6Route),
+ .sizeof_public = sizeof (NMPlatformIP6Route),
+ .obj_type_name = "ip6-route",
+ .nl_type = "route/route",
+ .addr_family = AF_INET6,
+ .rtm_gettype = RTM_GETROUTE,
+ .signal_type = NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED,
+ .cmd_obj_init_cache_id = _vt_cmd_obj_init_cache_id_ip6_route,
+ .cmd_obj_equal = _vt_cmd_obj_equal_plain,
+ .cmd_obj_copy = _vt_cmd_obj_copy_plain,
+ .cmd_obj_stackinit_id = _vt_cmd_obj_stackinit_id_ip6_route,
+ .cmd_obj_is_alive = _vt_cmd_obj_is_alive_ipx_route,
+ .cmd_obj_is_visible = _vt_cmd_obj_is_visible_ipx_route,
+ .cmd_plobj_init_from_nl = _nmp_vt_cmd_plobj_init_from_nl_ip6_route,
+ .cmd_plobj_to_nl = _nmp_vt_cmd_plobj_to_nl_ip6_route,
+ .cmd_plobj_id_copy = _vt_cmd_plobj_id_copy_ip6_route,
+ .cmd_plobj_id_equal = _vt_cmd_plobj_id_equal_ip6_route,
+ .cmd_plobj_id_hash = _vt_cmd_plobj_id_hash_ip6_route,
+ .cmd_plobj_to_string_id = _vt_cmd_plobj_to_string_id_ip6_route,
+ .cmd_plobj_to_string = (const char *(*) (const NMPlatformObject *obj)) nm_platform_ip6_route_to_string,
+ .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip6_route_cmp,
+ },
+};
diff --git a/src/platform/nmp-object.h b/src/platform/nmp-object.h
index fff607048e..99f0eaf0c8 100644
--- a/src/platform/nmp-object.h
+++ b/src/platform/nmp-object.h
@@ -24,6 +24,12 @@
#include "config.h"
#include "nm-platform.h"
+#include "nm-multi-index.h"
+#include "nm-macros-internal.h"
+
+#include <netlink/netlink.h>
+#include <gudev/gudev.h>
+
typedef enum { /*< skip >*/
OBJECT_TYPE_UNKNOWN,
@@ -36,4 +42,342 @@ typedef enum { /*< skip >*/
OBJECT_TYPE_MAX = __OBJECT_TYPE_LAST - 1,
} ObjectType;
+typedef enum { /*< skip >*/
+ NMP_OBJECT_TO_STRING_ID,
+ NMP_OBJECT_TO_STRING_PUBLIC,
+ NMP_OBJECT_TO_STRING_ALL,
+} NMPObjectToStringMode;
+
+typedef enum { /*< skip >*/
+ NMP_CACHE_OPS_UNCHANGED = NM_PLATFORM_SIGNAL_NONE,
+ NMP_CACHE_OPS_UPDATED = NM_PLATFORM_SIGNAL_CHANGED,
+ NMP_CACHE_OPS_ADDED = NM_PLATFORM_SIGNAL_ADDED,
+ NMP_CACHE_OPS_REMOVED = NM_PLATFORM_SIGNAL_REMOVED,
+} NMPCacheOpsType;
+
+/* The NMPCacheIdType are the different index types.
+ *
+ * An object of a certain object-type, can be candidate to being
+ * indexed by a certain NMPCacheIdType or not. For example, all
+ * objects are indexed via an index of type NMP_CACHE_ID_TYPE_OBJECT_TYPE,
+ * but only route objects are indexed by NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ALL.
+ *
+ * Of one index type, there can be different indexes or not.
+ * For example, there is only one single instance of
+ * NMP_CACHE_ID_TYPE_LINKS_VISIBLE_ONLY, because this instance is
+ * applicable for all link objects.
+ * But there are different instances of NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX
+ * type index, which differ in v4/v6, the ifindex, and whether the index
+ * is for routes or address instances.
+ *
+ * But one object, can only be indexed by one particular index of one
+ * type. For example, a certain address instance is only indexed by
+ * the index NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX with matching v4/v6
+ * and ifindex.
+ * */
+typedef enum { /*< skip >*/
+ NMP_CACHE_ID_TYPE_OBJECT_TYPE,
+
+ NMP_CACHE_ID_TYPE_LINKS_VISIBLE_ONLY,
+
+ NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX,
+
+ /* three indeces for the visibile routes. */
+ NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ALL,
+ NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT,
+ NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ONLY_DEFAULT,
+
+ __NMP_CACHE_ID_TYPE_MAX,
+ NMP_CACHE_ID_TYPE_MAX = __NMP_CACHE_ID_TYPE_MAX - 1,
+} NMPCacheIdType;
+
+typedef struct _NMPObject NMPObject;
+
+typedef struct {
+ union {
+ NMMultiIndexId base;
+ guint8 _id_type; /* NMPCacheIdType as guint8 */
+ struct {
+ /* NMP_CACHE_ID_TYPE_OBJECT_TYPE */
+ guint8 _id_type;
+ guint8 obj_type; /* ObjectType as guint8 */
+ } object_type;
+ struct {
+ /* NMP_CACHE_ID_TYPE_LINKS_VISIBLE_ONLY */
+ guint8 _id_type;
+
+ /* the @_global_id is only defined by it's type and has no arguments.
+ * It is used by NMP_CACHE_ID_TYPE_LINKS_VISIBLE_ONLY. There is only
+ * one single occurence of an index of this type. */
+ } _global_id;
+ struct {
+ /* NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX */
+ guint8 _id_type;
+ guint8 obj_type; /* ObjectType as guint8 */
+ int ifindex;
+ } addrroute_by_ifindex;
+ struct {
+ /* NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ALL */
+ /* NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT */
+ /* NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ONLY_DEFAULT */
+ guint8 _id_type;
+ guint8 is_v4;
+ int ifindex;
+ } routes_visible;
+ };
+} NMPCacheId;
+
+extern NMPCacheId _nmp_cache_id_static;
+#define NMP_CACHE_ID_STATIC (&_nmp_cache_id_static)
+
+typedef struct {
+ ObjectType obj_type;
+ int addr_family;
+ int rtm_gettype;
+ int sizeof_data;
+ int sizeof_public;
+ const char *obj_type_name;
+ const char *nl_type;
+ const char *signal_type;
+
+ /* returns %FALSE, if the obj type would never have an entry for index type @id_type. If @obj has an index,
+ * initialize @id and set @out_id to it. Otherwise, @out_id is NULL. */
+ gboolean (*cmd_obj_init_cache_id) (const NMPObject *obj, NMPCacheIdType id_type, NMPCacheId *id, const NMPCacheId **out_id);
+
+ gboolean (*cmd_obj_equal) (const NMPObject *obj1, const NMPObject *obj2);
+ void (*cmd_obj_copy) (NMPObject *dst, const NMPObject *src);
+ void (*cmd_obj_stackinit_id) (NMPObject *obj, const NMPObject *src);
+ void (*cmd_obj_dispose) (NMPObject *obj);
+ gboolean (*cmd_obj_is_alive) (const NMPObject *obj);
+ gboolean (*cmd_obj_is_visible) (const NMPObject *obj);
+
+ /* functions that operate on NMPlatformObject */
+ gboolean (*cmd_plobj_init_from_nl) (NMPlatform *platform, NMPlatformObject *obj, const struct nl_object *nlo, gboolean id_only, gboolean complete_from_cache);
+ struct nl_object *(*cmd_plobj_to_nl) (NMPlatform *platform, const NMPlatformObject *obj, gboolean id_only);
+ void (*cmd_plobj_id_copy) (NMPlatformObject *dst, const NMPlatformObject *src);
+ gboolean (*cmd_plobj_id_equal) (const NMPlatformObject *obj1, const NMPlatformObject *obj2);
+ guint (*cmd_plobj_id_hash) (const NMPlatformObject *obj);
+ const char *(*cmd_plobj_to_string_id) (const NMPlatformObject *obj, char *buf, gsize buf_size);
+ const char *(*cmd_plobj_to_string) (const NMPlatformObject *obj);
+ int (*cmd_plobj_cmp) (const NMPlatformObject *obj1, const NMPlatformObject *obj2);
+} NMPClass;
+
+extern const NMPClass _nmp_classes[OBJECT_TYPE_MAX];
+
+typedef struct {
+ NMPlatformLink _public;
+
+ struct {
+ guint8 is_in_netlink;
+ } netlink;
+
+ struct {
+ GUdevDevice *device;
+ } udev;
+} NMPObjectLink;
+
+typedef struct {
+ NMPlatformIP4Address _public;
+} NMPObjectIP4Address;
+
+typedef struct {
+ NMPlatformIP4Route _public;
+} NMPObjectIP4Route;
+
+typedef struct {
+ NMPlatformIP6Address _public;
+} NMPObjectIP6Address;
+
+typedef struct {
+ NMPlatformIP6Route _public;
+} NMPObjectIP6Route;
+
+struct _NMPObject {
+ const NMPClass *_class;
+ int _ref_count;
+ guint8 is_cached;
+ union {
+ NMPlatformObject object;
+
+ NMPlatformLink link;
+ NMPObjectLink _link;
+
+ NMPlatformIPAddress ip_address;
+ NMPlatformIPXAddress ipx_address;
+ NMPlatformIP4Address ip4_address;
+ NMPlatformIP6Address ip6_address;
+ NMPObjectIP4Address _ip4_address;
+ NMPObjectIP6Address _ip6_address;
+
+ NMPlatformIPRoute ip_route;
+ NMPlatformIPXRoute ipx_route;
+ NMPlatformIP4Route ip4_route;
+ NMPlatformIP6Route ip6_route;
+ NMPObjectIP4Route _ip4_route;
+ NMPObjectIP6Route _ip6_route;
+ };
+};
+
+static inline gboolean
+NMP_CLASS_IS_VALID (const NMPClass *klass)
+{
+ return klass >= &_nmp_classes[0]
+ && klass <= &_nmp_classes[G_N_ELEMENTS (_nmp_classes)]
+ && ((((char *) klass) - ((char *) NULL)) % (&_nmp_classes[1] - &_nmp_classes[0])) == 0;
+}
+
+#define NMP_REF_COUNT_STACKINIT (G_MAXINT)
+
+static inline NMPObject *
+NMP_OBJECT_UP_CAST(const NMPlatformObject *plobj)
+{
+ NMPObject *obj;
+
+ obj = plobj
+ ? (NMPObject *) ( &(((char *) plobj)[-((int) G_STRUCT_OFFSET (NMPObject, object))]) )
+ : NULL;
+ nm_assert (!obj || (obj->_ref_count > 0 && NMP_CLASS_IS_VALID (obj->_class)));
+ return obj;
+}
+#define NMP_OBJECT_UP_CAST(plobj) (NMP_OBJECT_UP_CAST ((const NMPlatformObject *) (plobj)))
+
+static inline gboolean
+NMP_OBJECT_IS_VALID (const NMPObject *obj)
+{
+ nm_assert (!obj || ( obj
+ && obj->_ref_count > 0
+ && NMP_CLASS_IS_VALID (obj->_class)));
+
+ /* There isn't really much to check. Either @obj is NULL, or we must
+ * assume that it points to valid memory. */
+ return obj != NULL;
+}
+
+static inline gboolean
+NMP_OBJECT_IS_STACKINIT (const NMPObject *obj)
+{
+ nm_assert (!obj || NMP_OBJECT_IS_VALID (obj));
+
+ return obj && obj->_ref_count == NMP_REF_COUNT_STACKINIT;
+}
+
+static inline const NMPClass *
+NMP_OBJECT_GET_CLASS (const NMPObject *obj)
+{
+ nm_assert (NMP_OBJECT_IS_VALID (obj));
+
+ return obj->_class;
+}
+
+static inline ObjectType
+NMP_OBJECT_GET_TYPE (const NMPObject *obj)
+{
+ nm_assert (!obj || NMP_OBJECT_IS_VALID (obj));
+
+ return obj ? obj->_class->obj_type : OBJECT_TYPE_UNKNOWN;
+}
+
+
+
+const NMPClass *nmp_class_from_type (ObjectType obj_type);
+
+NMPObject *nmp_object_ref (NMPObject *object);
+void nmp_object_unref (NMPObject *object);
+NMPObject *nmp_object_new (ObjectType obj_type, const NMPlatformObject *plob);
+NMPObject *nmp_object_new_link (int ifindex);
+
+const NMPObject *nmp_object_stackinit (NMPObject *obj, ObjectType obj_type, const NMPlatformObject *plobj);
+const NMPObject *nmp_object_stackinit_id (NMPObject *obj, const NMPObject *src);
+const NMPObject *nmp_object_stackinit_id_link (NMPObject *obj, int ifindex);
+const NMPObject *nmp_object_stackinit_id_ip4_address (NMPObject *obj, int ifindex, guint32 address, int plen);
+const NMPObject *nmp_object_stackinit_id_ip6_address (NMPObject *obj, int ifindex, const struct in6_addr *address, int plen);
+const NMPObject *nmp_object_stackinit_id_ip4_route (NMPObject *obj, int ifindex, guint32 network, int plen, guint32 metric);
+const NMPObject *nmp_object_stackinit_id_ip6_route (NMPObject *obj, int ifindex, const struct in6_addr *network, int plen, guint32 metric);
+
+const char *nmp_object_to_string (const NMPObject *obj, NMPObjectToStringMode to_string_mode, char *buf, gsize buf_size);
+int nmp_object_cmp (const NMPObject *obj1, const NMPObject *obj2);
+gboolean nmp_object_equal (const NMPObject *obj1, const NMPObject *obj2);
+void nmp_object_copy (NMPObject *dst, const NMPObject *src, gboolean id_only);
+NMPObject *nmp_object_clone (const NMPObject *obj, gboolean id_only);
+gboolean nmp_object_id_equal (const NMPObject *obj1, const NMPObject *obj2);
+guint nmp_object_id_hash (const NMPObject *obj);
+gboolean nmp_object_is_alive (const NMPObject *obj);
+gboolean nmp_object_is_visible (const NMPObject *obj);
+
+void _nmp_object_fixup_link_udev_fields (NMPObject *obj, gboolean use_udev);
+
+#define auto_nmp_obj __attribute__((cleanup(_nmp_auto_obj_cleanup)))
+static inline void
+_nmp_auto_obj_cleanup (NMPObject **pobj)
+{
+ nmp_object_unref (*pobj);
+}
+
+typedef struct _NMPCache NMPCache;
+
+typedef void (*NMPCachePreHook) (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMPCacheOpsType ops_type, gpointer user_data);
+typedef gboolean (*NMPObjectMatchFn) (const NMPObject *obj, gpointer user_data);
+
+gboolean nmp_cache_id_equal (const NMPCacheId *a, const NMPCacheId *b);
+guint nmp_cache_id_hash (const NMPCacheId *id);
+NMPCacheId *nmp_cache_id_clone (const NMPCacheId *id);
+void nmp_cache_id_destroy (NMPCacheId *id);
+
+NMPCacheId *nmp_cache_id_init (NMPCacheId *id, NMPCacheIdType id_type);
+NMPCacheId *nmp_cache_id_init_object_type (NMPCacheId *id, ObjectType obj_type);
+NMPCacheId *nmp_cache_id_init_links (NMPCacheId *id, gboolean visible_only);
+NMPCacheId *nmp_cache_id_init_addrroute_by_ifindex (NMPCacheId *id, ObjectType obj_type, int ifindex);
+NMPCacheId *nmp_cache_id_init_routes_visible (NMPCacheId *id, NMPCacheIdType id_type, gboolean is_v4, int ifindex);
+
+const NMPlatformObject *const *nmp_cache_lookup_multi (const NMPCache *cache, const NMPCacheId *cache_id, guint *out_len);
+GArray *nmp_cache_lookup_multi_to_array (const NMPCache *cache, ObjectType obj_type, const NMPCacheId *cache_id);
+const NMPObject *nmp_cache_lookup_obj (const NMPCache *cache, const NMPObject *obj);
+const NMPObject *nmp_cache_lookup_link (const NMPCache *cache, int ifindex);
+
+const NMPObject *nmp_cache_lookup_link_full (const NMPCache *cache,
+ int ifindex,
+ const char *ifname,
+ gboolean visible_only,
+ NMLinkType link_type,
+ NMPObjectMatchFn match_fn,
+ gpointer user_data);
+GHashTable *nmp_cache_lookup_all_to_hash (const NMPCache *cache,
+ NMPCacheId *cache_id,
+ GHashTable *hash);
+
+gboolean nmp_cache_link_connected_needs_toggle (const NMPCache *cache, const NMPObject *master, const NMPObject *potential_slave, const NMPObject *ignore_slave);
+const NMPObject *nmp_cache_link_connected_needs_toggle_by_ifindex (const NMPCache *cache, int master_ifindex, const NMPObject *potential_slave, const NMPObject *ignore_slave);
+
+gboolean nmp_cache_use_udev_detect (void);
+gboolean nmp_cache_use_udev_get (const NMPCache *cache);
+gboolean nmp_cache_use_udev_set (NMPCache *cache, gboolean use_udev);
+
+void ASSERT_nmp_cache_is_consistent (const NMPCache *cache);
+
+NMPCacheOpsType nmp_cache_remove (NMPCache *cache, const NMPObject *obj, gboolean equals_by_ptr, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data);
+NMPCacheOpsType nmp_cache_remove_netlink (NMPCache *cache, const NMPObject *obj, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data);
+NMPCacheOpsType nmp_cache_update_netlink (NMPCache *cache, NMPObject *obj, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data);
+NMPCacheOpsType nmp_cache_update_link_udev (NMPCache *cache, int ifindex, GUdevDevice *udev_device, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data);
+NMPCacheOpsType nmp_cache_update_link_master_connected (NMPCache *cache, int ifindex, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data);
+
+NMPCache *nmp_cache_new (void);
+void nmp_cache_free (NMPCache *cache);
+
+NMPObject *nmp_object_from_nl (NMPlatform *platform, const struct nl_object *nlo, gboolean id_only, gboolean complete_from_cache);
+struct nl_object *nmp_object_to_nl (NMPlatform *platform, const NMPObject *obj, gboolean id_only);
+
+/* the following functions are currently implemented inside nm-linux-platform, because
+ * they depend on utility functions there. */
+ObjectType _nlo_get_object_type (const struct nl_object *nlo);
+gboolean _nmp_vt_cmd_plobj_init_from_nl_link (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache);
+gboolean _nmp_vt_cmd_plobj_init_from_nl_ip4_address (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache);
+gboolean _nmp_vt_cmd_plobj_init_from_nl_ip6_address (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache);
+gboolean _nmp_vt_cmd_plobj_init_from_nl_ip4_route (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache);
+gboolean _nmp_vt_cmd_plobj_init_from_nl_ip6_route (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache);
+struct nl_object *_nmp_vt_cmd_plobj_to_nl_link (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only);
+struct nl_object *_nmp_vt_cmd_plobj_to_nl_ip4_address (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only);
+struct nl_object *_nmp_vt_cmd_plobj_to_nl_ip6_address (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only);
+struct nl_object *_nmp_vt_cmd_plobj_to_nl_ip4_route (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only);
+struct nl_object *_nmp_vt_cmd_plobj_to_nl_ip6_route (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only);
+
#endif /* __NMP_OBJECT_H__ */
diff --git a/src/platform/tests/test-nmp-object.c b/src/platform/tests/test-nmp-object.c
index 7c01d9850c..b922599cb3 100644
--- a/src/platform/tests/test-nmp-object.c
+++ b/src/platform/tests/test-nmp-object.c
@@ -24,6 +24,366 @@
#include "nm-test-utils.h"
+struct {
+ GList *udev_devices;
+} global;
+
+/******************************************************************/
+
+static gboolean
+_nmp_object_id_equal (const NMPObject *a, const NMPObject *b)
+{
+ gboolean a_b = nmp_object_id_equal (a, b);
+
+ g_assert (NM_IN_SET (a_b, FALSE, TRUE) && a_b == nmp_object_id_equal (b, a));
+ return a_b;
+}
+#define nmp_object_id_equal _nmp_object_id_equal
+
+static gboolean
+_nmp_object_equal (const NMPObject *a, const NMPObject *b)
+{
+ gboolean a_b = nmp_object_equal (a, b);
+
+ g_assert (NM_IN_SET (a_b, FALSE, TRUE) && a_b == nmp_object_equal (b, a));
+ return a_b;
+}
+#define nmp_object_equal _nmp_object_equal
+
+/******************************************************************/
+
+static void
+_assert_cache_multi_lookup_contains (const NMPCache *cache, const NMPCacheId *cache_id, const NMPObject *obj, gboolean contains)
+{
+ const NMPlatformObject *const *objects;
+ guint i, len;
+ gboolean found;
+
+ g_assert (cache_id);
+ g_assert (NMP_OBJECT_IS_VALID (obj));
+
+ g_assert (nmp_cache_lookup_obj (cache, obj) == obj);
+
+ objects = nmp_cache_lookup_multi (cache, cache_id, &len);
+
+ g_assert ((len == 0 && !objects) || (len > 0 && objects && !objects[len]));
+
+ found = FALSE;
+ for (i = 0; i < len; i++) {
+ NMPObject *o;
+
+ g_assert (objects[i]);
+ o = NMP_OBJECT_UP_CAST (objects[i]);
+ g_assert (NMP_OBJECT_IS_VALID (o));
+
+ if (obj == o) {
+ g_assert (!found);
+ found = TRUE;
+ }
+ }
+
+ g_assert (!!contains == found);
+}
+
+/******************************************************************/
+
+typedef struct {
+ NMPCache *cache;
+ NMPCacheOpsType expected_ops_type;
+ const NMPObject *obj_clone;
+ NMPObject *new_clone;
+ gboolean was_visible;
+ gboolean called;
+} _NMPCacheUpdateData;
+
+static void
+_nmp_cache_update_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMPCacheOpsType ops_type, gpointer user_data)
+{
+ _NMPCacheUpdateData *data = user_data;
+
+ g_assert (data);
+ g_assert (!data->called);
+ g_assert (data->cache == cache);
+
+ g_assert_cmpint (data->expected_ops_type, ==, ops_type);
+
+ switch (ops_type) {
+ case NMP_CACHE_OPS_ADDED:
+ g_assert (!old);
+ g_assert (NMP_OBJECT_IS_VALID (new));
+ g_assert (nmp_object_is_alive (new));
+ g_assert (nmp_object_id_equal (data->obj_clone, new));
+ g_assert (nmp_object_equal (data->obj_clone, new));
+ break;
+ case NMP_CACHE_OPS_UPDATED:
+ g_assert (NMP_OBJECT_IS_VALID (old));
+ g_assert (NMP_OBJECT_IS_VALID (new));
+ g_assert (nmp_object_is_alive (old));
+ g_assert (nmp_object_is_alive (new));
+ g_assert (nmp_object_id_equal (data->obj_clone, new));
+ g_assert (nmp_object_id_equal (data->obj_clone, old));
+ g_assert (nmp_object_id_equal (old, new));
+ g_assert (nmp_object_equal (data->obj_clone, new));
+ g_assert (!nmp_object_equal (data->obj_clone, old));
+ g_assert (!nmp_object_equal (old, new));
+ break;
+ case NMP_CACHE_OPS_REMOVED:
+ g_assert (!new);
+ g_assert (NMP_OBJECT_IS_VALID (old));
+ g_assert (nmp_object_is_alive (old));
+ g_assert (nmp_object_id_equal (data->obj_clone, old));
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ data->was_visible = old ? nmp_object_is_visible (old) : FALSE;
+ data->new_clone = new ? nmp_object_clone (new, FALSE) : NULL;
+ data->called = TRUE;
+}
+
+static void
+_nmp_cache_update_netlink (NMPCache *cache, NMPObject *obj, NMPObject **out_obj, gboolean *out_was_visible, NMPCacheOpsType expected_ops_type)
+{
+ NMPCacheOpsType ops_type;
+ NMPObject *obj2;
+ gboolean was_visible;
+ auto_nmp_obj NMPObject *obj_clone = nmp_object_clone (obj, FALSE);
+ auto_nmp_obj NMPObject *new_clone = NULL;
+ const NMPObject *obj_old;
+ _NMPCacheUpdateData data = {
+ .cache = cache,
+ .expected_ops_type = expected_ops_type,
+ .obj_clone = obj_clone,
+ };
+
+ obj_old = nmp_cache_lookup_link (cache, obj->object.ifindex);
+ if (obj_old && obj_old->_link.udev.device)
+ obj_clone->_link.udev.device = g_object_ref (obj_old->_link.udev.device);
+ _nmp_object_fixup_link_udev_fields (obj_clone, nmp_cache_use_udev_get (cache));
+
+ g_assert (cache);
+ g_assert (NMP_OBJECT_IS_VALID (obj));
+
+ ops_type = nmp_cache_update_netlink (cache, obj, &obj2, &was_visible, _nmp_cache_update_hook, &data);
+
+ new_clone = data.new_clone;
+
+ g_assert_cmpint (ops_type, ==, expected_ops_type);
+
+ if (ops_type != NMP_CACHE_OPS_UNCHANGED) {
+ g_assert (NMP_OBJECT_IS_VALID (obj2));
+ g_assert (data.called);
+ g_assert_cmpint (data.was_visible, ==, was_visible);
+
+ if (ops_type == NMP_CACHE_OPS_REMOVED)
+ g_assert (!data.new_clone);
+ else {
+ g_assert (data.new_clone);
+ g_assert (nmp_object_equal (obj2, data.new_clone));
+ }
+ } else {
+ g_assert (!data.called);
+ g_assert (!obj2 || was_visible == nmp_object_is_visible (obj2));
+ }
+
+ g_assert (!obj2 || nmp_object_id_equal (obj, obj2));
+ if (ops_type != NMP_CACHE_OPS_REMOVED && obj2)
+ g_assert (nmp_object_equal (obj, obj2));
+
+ if (out_obj)
+ *out_obj = obj2;
+ else
+ nmp_object_unref (obj2);
+ if (out_was_visible)
+ *out_was_visible = was_visible;
+}
+
+static const NMPlatformLink pl_link_2 = {
+ .ifindex = 2,
+ .name = "eth0",
+ .type = NM_LINK_TYPE_ETHERNET,
+};
+
+static const NMPlatformLink pl_link_3 = {
+ .ifindex = 3,
+ .name = "wlan0",
+ .type = NM_LINK_TYPE_WIFI,
+};
+
+static void
+test_cache_link ()
+{
+ NMPCache *cache;
+ NMPObject *obj1, *obj2;
+ NMPObject objs1;
+ gboolean was_visible;
+ NMPCacheId cache_id_storage;
+ GUdevDevice *udev_device_2 = g_list_nth_data (global.udev_devices, 0);
+ GUdevDevice *udev_device_3 = g_list_nth_data (global.udev_devices, 0);
+ NMPCacheOpsType ops_type;
+
+ cache = nmp_cache_new ();
+
+ nmp_cache_use_udev_set (cache, g_rand_int_range (nmtst_get_rand (), 0, 2));
+
+ /* if we have a link, and don't set is_in_netlink, adding it has no effect. */
+ obj1 = nmp_object_new (OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
+ g_assert (NMP_OBJECT_UP_CAST (&obj1->object) == obj1);
+ g_assert (!nmp_object_is_alive (obj1));
+ _nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, NMP_CACHE_OPS_UNCHANGED);
+ ASSERT_nmp_cache_is_consistent (cache);
+ g_assert (!obj2);
+ g_assert (!was_visible);
+ g_assert (!nmp_cache_lookup_obj (cache, obj1));
+ g_assert (!nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)));
+ nmp_object_unref (obj1);
+
+ /* Only when setting @is_in_netlink the link is added. */
+ obj1 = nmp_object_new (OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
+ obj1->_link.netlink.is_in_netlink = TRUE;
+ g_assert (nmp_object_is_alive (obj1));
+ _nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, NMP_CACHE_OPS_ADDED);
+ ASSERT_nmp_cache_is_consistent (cache);
+ g_assert (nmp_object_equal (obj1, obj2));
+ g_assert (!was_visible);
+ g_assert (nmp_cache_lookup_obj (cache, obj1) == obj2);
+ g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj2);
+ g_assert (nmp_object_is_visible (obj2));
+ _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, TRUE), obj2, TRUE);
+ _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, FALSE), obj2, TRUE);
+ nmp_object_unref (obj1);
+ nmp_object_unref (obj2);
+
+ /* updating the same link with identical value, has no effect. */
+ obj1 = nmp_object_new (OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
+ obj1->_link.netlink.is_in_netlink = TRUE;
+ g_assert (nmp_object_is_alive (obj1));
+ _nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, NMP_CACHE_OPS_UNCHANGED);
+ ASSERT_nmp_cache_is_consistent (cache);
+ g_assert (obj2 != obj1);
+ g_assert (nmp_object_equal (obj1, obj2));
+ g_assert (was_visible);
+ g_assert (nmp_cache_lookup_obj (cache, obj1) == obj2);
+ g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj2);
+ nmp_object_unref (obj1);
+ nmp_object_unref (obj2);
+
+ /* remove the link from netlink */
+ obj1 = nmp_object_new (OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
+ g_assert (!nmp_object_is_alive (obj1));
+ _nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, NMP_CACHE_OPS_REMOVED);
+ ASSERT_nmp_cache_is_consistent (cache);
+ g_assert (obj2 != obj1);
+ g_assert (was_visible);
+ g_assert (!nmp_cache_lookup_obj (cache, obj1));
+ g_assert (!nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)));
+ nmp_object_unref (obj1);
+ nmp_object_unref (obj2);
+
+ udev_device_2 = NULL;
+ if (udev_device_2) {
+ /* now add the link only with aspect UDEV. */
+ ops_type = nmp_cache_update_link_udev (cache, pl_link_2.ifindex, udev_device_2, &obj2, &was_visible, NULL, NULL);
+ ASSERT_nmp_cache_is_consistent (cache);
+ g_assert_cmpint (ops_type, ==, NMP_CACHE_OPS_ADDED);
+ g_assert (!was_visible);
+ g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj2);
+ g_assert (!nmp_object_is_visible (obj2));
+ _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, TRUE), obj2, FALSE);
+ _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, FALSE), obj2, TRUE);
+ nmp_object_unref (obj2);
+ }
+
+ /* add it in netlink too. */
+ obj1 = nmp_object_new (OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
+ obj1->_link.netlink.is_in_netlink = TRUE;
+ g_assert (nmp_object_is_alive (obj1));
+ _nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, udev_device_2 ? NMP_CACHE_OPS_UPDATED : NMP_CACHE_OPS_ADDED);
+ ASSERT_nmp_cache_is_consistent (cache);
+ g_assert (nmp_object_equal (obj1, obj2));
+ g_assert (!was_visible);
+ g_assert (nmp_cache_lookup_obj (cache, obj1) == obj2);
+ g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj2);
+ g_assert (nmp_object_is_visible (obj2));
+ _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, TRUE), obj2, TRUE);
+ _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, FALSE), obj2, TRUE);
+ nmp_object_unref (obj1);
+ nmp_object_unref (obj2);
+
+ /* remove again from netlink. */
+ obj1 = nmp_object_new (OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
+ obj1->_link.netlink.is_in_netlink = FALSE;
+ g_assert (!nmp_object_is_alive (obj1));
+ _nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, udev_device_2 ? NMP_CACHE_OPS_UPDATED : NMP_CACHE_OPS_REMOVED);
+ ASSERT_nmp_cache_is_consistent (cache);
+ g_assert (obj2 != obj1);
+ g_assert (was_visible);
+ if (udev_device_2) {
+ g_assert (nmp_cache_lookup_obj (cache, obj1) == obj2);
+ g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj2);
+ g_assert (!nmp_object_is_visible (obj2));
+ _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, TRUE), obj2, FALSE);
+ _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, FALSE), obj2, TRUE);
+ } else {
+ g_assert (nmp_cache_lookup_obj (cache, obj1) == NULL);
+ g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == NULL);
+ g_assert (nmp_object_is_visible (obj2));
+ }
+ nmp_object_unref (obj1);
+ nmp_object_unref (obj2);
+
+ /* now another link only with aspect UDEV. */
+ if (udev_device_3) {
+ /* now add the link only with aspect UDEV. */
+ ops_type = nmp_cache_update_link_udev (cache, pl_link_3.ifindex, udev_device_3, &obj2, &was_visible, NULL, NULL);
+ g_assert_cmpint (ops_type, ==, NMP_CACHE_OPS_ADDED);
+ ASSERT_nmp_cache_is_consistent (cache);
+ g_assert (NMP_OBJECT_IS_VALID (obj2));
+ g_assert (!was_visible);
+ g_assert (!nmp_object_is_visible (obj2));
+ g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_3.ifindex)) == obj2);
+ _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, TRUE), obj2, FALSE);
+ _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, FALSE), obj2, TRUE);
+ g_assert_cmpint (obj2->_link.netlink.is_in_netlink, ==, FALSE);
+ g_assert_cmpint (obj2->link.initialized, ==, FALSE);
+ nmp_object_unref (obj2);
+
+ /* add it in netlink too. */
+ obj1 = nmp_object_new (OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_3);
+ obj1->_link.netlink.is_in_netlink = TRUE;
+ g_assert (nmp_object_is_alive (obj1));
+ _nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, NMP_CACHE_OPS_UPDATED);
+ ASSERT_nmp_cache_is_consistent (cache);
+ g_assert (obj2 != obj1);
+ g_assert (nmp_object_equal (obj1, obj2));
+ g_assert (!was_visible);
+ g_assert (nmp_cache_lookup_obj (cache, obj1) == obj2);
+ g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_3.ifindex)) == obj2);
+ g_assert (nmp_object_is_visible (obj2));
+ _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, TRUE), obj2, TRUE);
+ _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, FALSE), obj2, TRUE);
+ g_assert_cmpint (obj2->_link.netlink.is_in_netlink, ==, TRUE);
+ g_assert_cmpint (obj2->link.initialized, ==, TRUE);
+ nmp_object_unref (obj1);
+ nmp_object_unref (obj2);
+
+ /* remove UDEV. */
+ ops_type = nmp_cache_update_link_udev (cache, pl_link_3.ifindex, NULL, &obj2, &was_visible, NULL, NULL);
+ g_assert_cmpint (ops_type, ==, NMP_CACHE_OPS_UPDATED);
+ ASSERT_nmp_cache_is_consistent (cache);
+ g_assert (was_visible);
+ g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_3.ifindex)) == obj2);
+ g_assert (nmp_object_is_visible (obj2));
+ _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, TRUE), obj2, TRUE);
+ _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, FALSE), obj2, TRUE);
+ g_assert_cmpint (obj2->_link.netlink.is_in_netlink, ==, TRUE);
+ g_assert_cmpint (obj2->link.initialized, ==, !nmp_cache_use_udev_get (cache));
+ nmp_object_unref (obj2);
+ }
+
+ nmp_cache_free (cache);
+}
/******************************************************************/
@@ -33,11 +393,34 @@ int
main (int argc, char **argv)
{
int result;
+ gs_unref_object GUdevClient *udev_client = NULL;
nmtst_init_assert_logging (&argc, &argv, "INFO", "DEFAULT");
+ udev_client = g_udev_client_new ((const char *[]) { "net", NULL });
+ {
+ gs_unref_object GUdevEnumerator *udev_enumerator = g_udev_enumerator_new (udev_client);
+
+ g_udev_enumerator_add_match_subsystem (udev_enumerator, "net");
+
+ /* Demand that the device is initialized (udev rules ran,
+ * device has a stable name now) in case udev is running
+ * (not in a container). */
+ if (access ("/sys", W_OK) == 0)
+ g_udev_enumerator_add_match_is_initialized (udev_enumerator);
+
+ global.udev_devices = g_udev_enumerator_execute (udev_enumerator);
+ }
+
+ g_test_add_func ("/nmp-object/cache_link", test_cache_link);
+
result = g_test_run ();
+ while (global.udev_devices) {
+ g_object_unref (global.udev_devices->data);
+ global.udev_devices = g_list_remove (global.udev_devices, global.udev_devices->data);
+ }
+
return result;
}