From 6502abb2faea46d2bda61fff82ba454fee97aff7 Mon Sep 17 00:00:00 2001
From: Yu Watanabe <watanabe.yu+github@gmail.com>
Date: Mon, 15 Apr 2019 16:38:45 +0900
Subject: [PATCH 18/29] network: fix ref/unref logic for Link object

- bridge or bonding master takes a reference of slave links.
- drop link from bridge or bonding master's slave list when slave link
  is removed.
- change type of Link::slaves to Set*,

Fixes #12315.

(cherry picked from commit 5f707e1280d7c66d3adcffd47a23ad446257f355)
---
 src/network/networkd-link.c    | 109 +++++++++++++++++----------------
 src/network/networkd-link.h    |   2 +-
 src/network/networkd-manager.c |   7 +--
 3 files changed, 61 insertions(+), 57 deletions(-)

diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
index d372f9adc7..08f4284ebe 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -410,7 +410,7 @@ void link_update_operstate(Link *link, bool also_update_master) {
         if (operstate >= LINK_OPERSTATE_CARRIER) {
                 Link *slave;
 
-                HASHMAP_FOREACH(slave, link->slaves, i) {
+                SET_FOREACH(slave, link->slaves, i) {
                         link_update_operstate(slave, false);
 
                         if (slave->operstate < LINK_OPERSTATE_CARRIER)
@@ -606,17 +606,8 @@ static int link_new(Manager *manager, sd_netlink_message *message, Link **ret) {
         return 0;
 }
 
-static void link_detach_from_manager(Link *link) {
-        if (!link || !link->manager)
-                return;
-
-        hashmap_remove(link->manager->links, INT_TO_PTR(link->ifindex));
-        set_remove(link->manager->links_requesting_uuid, link);
-        link_clean(link);
-}
-
 static Link *link_free(Link *link) {
-        Link *carrier, *master;
+        Link *carrier;
         Address *address;
         Route *route;
         Iterator i;
@@ -664,10 +655,7 @@ static Link *link_free(Link *link) {
         sd_ndisc_unref(link->ndisc);
         sd_radv_unref(link->radv);
 
-        link_detach_from_manager(link);
-
         free(link->ifname);
-
         free(link->kind);
 
         (void) unlink(link->state_file);
@@ -683,17 +671,7 @@ static Link *link_free(Link *link) {
                 hashmap_remove(link->bound_by_links, INT_TO_PTR(carrier->ifindex));
         hashmap_free(link->bound_by_links);
 
-        hashmap_free(link->slaves);
-
-        if (link->network) {
-                if (link->network->bond &&
-                    link_get(link->manager, link->network->bond->ifindex, &master) >= 0)
-                        (void) hashmap_remove(master->slaves, INT_TO_PTR(link->ifindex));
-
-                if (link->network->bridge &&
-                    link_get(link->manager, link->network->bridge->ifindex, &master) >= 0)
-                        (void) hashmap_remove(master->slaves, INT_TO_PTR(link->ifindex));
-        }
+        set_free_with_destructor(link->slaves, link_unref);
 
         return mfree(link);
 }
@@ -1748,28 +1726,6 @@ static int link_set_bond(Link *link) {
         return r;
 }
 
-static int link_append_to_master(Link *link, NetDev *netdev) {
-        Link *master;
-        int r;
-
-        assert(link);
-        assert(netdev);
-
-        r = link_get(link->manager, netdev->ifindex, &master);
-        if (r < 0)
-                return r;
-
-        r = hashmap_ensure_allocated(&master->slaves, NULL);
-        if (r < 0)
-                return r;
-
-        r = hashmap_put(master->slaves, INT_TO_PTR(link->ifindex), link);
-        if (r < 0)
-                return r;
-
-        return 0;
-}
-
 static int link_lldp_save(Link *link) {
         _cleanup_free_ char *temp_path = NULL;
         _cleanup_fclose_ FILE *f = NULL;
@@ -2532,6 +2488,55 @@ static void link_free_carrier_maps(Link *link) {
         return;
 }
 
+static int link_append_to_master(Link *link, NetDev *netdev) {
+        Link *master;
+        int r;
+
+        assert(link);
+        assert(netdev);
+
+        r = link_get(link->manager, netdev->ifindex, &master);
+        if (r < 0)
+                return r;
+
+        r = set_ensure_allocated(&master->slaves, NULL);
+        if (r < 0)
+                return r;
+
+        r = set_put(master->slaves, link);
+        if (r < 0)
+                return r;
+
+        link_ref(link);
+        return 0;
+}
+
+static void link_drop_from_master(Link *link, NetDev *netdev) {
+        Link *master;
+
+        assert(link);
+
+        if (!link->manager || !netdev)
+                return;
+
+        if (link_get(link->manager, netdev->ifindex, &master) < 0)
+                return;
+
+        link_unref(set_remove(master->slaves, link));
+}
+
+static void link_detach_from_manager(Link *link) {
+        if (!link || !link->manager)
+                return;
+
+        link_unref(set_remove(link->manager->links_requesting_uuid, link));
+        link_clean(link);
+
+        /* The following must be called at last. */
+        assert_se(hashmap_remove(link->manager->links, INT_TO_PTR(link->ifindex)) == link);
+        link_unref(link);
+}
+
 void link_drop(Link *link) {
         if (!link || link->state == LINK_STATE_LINGER)
                 return;
@@ -2540,15 +2545,15 @@ void link_drop(Link *link) {
 
         link_free_carrier_maps(link);
 
+        if (link->network) {
+                link_drop_from_master(link, link->network->bridge);
+                link_drop_from_master(link, link->network->bond);
+        }
+
         log_link_debug(link, "Link removed");
 
         (void) unlink(link->state_file);
-
         link_detach_from_manager(link);
-
-        link_unref(link);
-
-        return;
 }
 
 static int link_joined(Link *link) {
diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h
index e322ec2262..e5497379d7 100644
--- a/src/network/networkd-link.h
+++ b/src/network/networkd-link.h
@@ -122,7 +122,7 @@ typedef struct Link {
 
         Hashmap *bound_by_links;
         Hashmap *bound_to_links;
-        Hashmap *slaves;
+        Set *slaves;
 } Link;
 
 typedef int (*link_netlink_message_handler_t)(sd_netlink*, sd_netlink_message*, Link*);
diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c
index 9075b0a14b..43b22f69e7 100644
--- a/src/network/networkd-manager.c
+++ b/src/network/networkd-manager.c
@@ -1452,13 +1452,12 @@ void manager_free(Manager *m) {
         }
 
         m->dirty_links = set_free_with_destructor(m->dirty_links, link_unref);
-        m->links = hashmap_free(m->links);
-        m->links_requesting_uuid = set_free(m->links_requesting_uuid);
-        set_free(m->duids_requesting_uuid);
+        m->links_requesting_uuid = set_free_with_destructor(m->links_requesting_uuid, link_unref);
+        m->links = hashmap_free_with_destructor(m->links, link_unref);
 
+        m->duids_requesting_uuid = set_free(m->duids_requesting_uuid);
         while ((network = m->networks))
                 network_free(network);
-
         hashmap_free(m->networks_by_name);
 
         m->netdevs = hashmap_free_with_destructor(m->netdevs, netdev_unref);
-- 
2.21.0

