]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
networkd: manager/link - only serialize once per event-loop iteration 1626/head
authorTom Gundersen <teg@jklm.no>
Wed, 30 Sep 2015 16:17:43 +0000 (18:17 +0200)
committerTom Gundersen <teg@jklm.no>
Wed, 21 Oct 2015 01:24:23 +0000 (03:24 +0200)
Every time the state is written out we may trigger third-party apps, so
let's be a bit more careful about writing this out unnecessarily.

src/network/networkd-address.c
src/network/networkd-link.c
src/network/networkd-link.h
src/network/networkd-manager.c
src/network/networkd-network.c
src/network/networkd.h

index 8b3694d07fe81871d8db7ea4eb5132d7e779b1ba..5d443e9b9bea3e55ebc062c9063d186483e64234 100644 (file)
@@ -98,7 +98,6 @@ void address_free(Address *address) {
         if (address->link) {
                 set_remove(address->link->addresses, address);
                 set_remove(address->link->addresses_foreign, address);
-                link_save(address->link);
         }
 
         free(address);
@@ -277,7 +276,8 @@ static int address_add(Link *link, int family, const union in_addr_union *in_add
         if (r < 0)
                 return r;
 
-        link_save(link);
+        link_update_operstate(link);
+        link_dirty(link);
 
         return 0;
 }
@@ -334,6 +334,8 @@ int address_drop(Address *address) {
         address_release(address);
         address_free(address);
 
+        link_update_operstate(link);
+
         if (link && !ready)
                 link_check_ready(link);
 
index 1b55f99646a46d747e832b0aba118f44e3656d62..ec4b082542edcbc41f1040e613deb6cc0a3877a5 100644 (file)
@@ -130,6 +130,57 @@ static IPv6PrivacyExtensions link_ipv6_privacy_extensions(Link *link) {
         return link->network->ipv6_privacy_extensions;
 }
 
+void link_update_operstate(Link *link) {
+        LinkOperationalState operstate;
+        assert(link);
+
+        if (link->kernel_operstate == IF_OPER_DORMANT)
+                operstate = LINK_OPERSTATE_DORMANT;
+        else if (link_has_carrier(link)) {
+                Address *address;
+                uint8_t scope = RT_SCOPE_NOWHERE;
+                Iterator i;
+
+                /* if we have carrier, check what addresses we have */
+                SET_FOREACH(address, link->addresses, i) {
+                        if (!address_is_ready(address))
+                                continue;
+
+                        if (address->scope < scope)
+                                scope = address->scope;
+                }
+
+                /* for operstate we also take foreign addresses into account */
+                SET_FOREACH(address, link->addresses_foreign, i) {
+                        if (!address_is_ready(address))
+                                continue;
+
+                        if (address->scope < scope)
+                                scope = address->scope;
+                }
+
+                if (scope < RT_SCOPE_SITE)
+                        /* universally accessible addresses found */
+                        operstate = LINK_OPERSTATE_ROUTABLE;
+                else if (scope < RT_SCOPE_HOST)
+                        /* only link or site local addresses found */
+                        operstate = LINK_OPERSTATE_DEGRADED;
+                else
+                        /* no useful addresses found */
+                        operstate = LINK_OPERSTATE_CARRIER;
+        } else if (link->flags & IFF_UP)
+                operstate = LINK_OPERSTATE_NO_CARRIER;
+        else
+                operstate = LINK_OPERSTATE_OFF;
+
+        if (link->operstate != operstate) {
+                link->operstate = operstate;
+                link_send_changed(link, "OperationalState", NULL);
+                link_dirty(link);
+                manager_dirty(link->manager);
+        }
+}
+
 #define FLAG_STRING(string, flag, old, new) \
         (((old ^ new) & flag) \
                 ? ((old & flag) ? (" -" string) : (" +" string)) \
@@ -202,7 +253,7 @@ static int link_update_flags(Link *link, sd_netlink_message *m) {
         link->flags = flags;
         link->kernel_operstate = operstate;
 
-        link_save(link);
+        link_update_operstate(link);
 
         return 0;
 }
@@ -326,6 +377,7 @@ static void link_free(Link *link) {
 
         free(link->ifname);
 
+        (void)unlink(link->state_file);
         free(link->state_file);
 
         udev_device_unref(link->udev_device);
@@ -404,7 +456,7 @@ static void link_enter_unmanaged(Link *link) {
 
         link_set_state(link, LINK_STATE_UNMANAGED);
 
-        link_save(link);
+        link_dirty(link);
 }
 
 static int link_stop_clients(Link *link) {
@@ -462,7 +514,7 @@ void link_enter_failed(Link *link) {
 
         link_stop_clients(link);
 
-        link_save(link);
+        link_dirty(link);
 }
 
 static Address* link_find_dhcp_server_address(Link *link) {
@@ -503,7 +555,7 @@ static int link_enter_configured(Link *link) {
 
         link_set_state(link, LINK_STATE_CONFIGURED);
 
-        link_save(link);
+        link_dirty(link);
 
         return 0;
 }
@@ -1460,14 +1512,14 @@ static int link_new_bound_by_list(Link *link) {
         }
 
         if (list_updated)
-                link_save(link);
+                link_dirty(link);
 
         HASHMAP_FOREACH (carrier, link->bound_by_links, i) {
                 r = link_put_carrier(carrier, link, &carrier->bound_to_links);
                 if (r < 0)
                         return r;
 
-                link_save(carrier);
+                link_dirty(carrier);
         }
 
         return 0;
@@ -1502,14 +1554,14 @@ static int link_new_bound_to_list(Link *link) {
         }
 
         if (list_updated)
-                link_save(link);
+                link_dirty(link);
 
         HASHMAP_FOREACH (carrier, link->bound_to_links, i) {
                 r = link_put_carrier(carrier, link, &carrier->bound_by_links);
                 if (r < 0)
                         return r;
 
-                link_save(carrier);
+                link_dirty(carrier);
         }
 
         return 0;
@@ -1545,7 +1597,7 @@ static void link_free_bound_to_list(Link *link) {
                 hashmap_remove(link->bound_to_links, INT_TO_PTR(bound_to->ifindex));
 
                 if (hashmap_remove(bound_to->bound_by_links, INT_TO_PTR(link->ifindex)))
-                        link_save(bound_to);
+                        link_dirty(bound_to);
         }
 
         return;
@@ -1559,7 +1611,7 @@ static void link_free_bound_by_list(Link *link) {
                 hashmap_remove(link->bound_by_links, INT_TO_PTR(bound_by->ifindex));
 
                 if (hashmap_remove(bound_by->bound_to_links, INT_TO_PTR(link->ifindex))) {
-                        link_save(bound_by);
+                        link_dirty(bound_by);
                         link_handle_bound_to_list(bound_by);
                 }
         }
@@ -1583,7 +1635,7 @@ static void link_free_carrier_maps(Link *link) {
         }
 
         if (list_updated)
-                link_save(link);
+                link_dirty(link);
 
         return;
 }
@@ -1598,6 +1650,7 @@ void link_drop(Link *link) {
 
         log_link_debug(link, "Link removed");
 
+        (void)unlink(link->state_file);
         link_unref(link);
 
         return;
@@ -1667,7 +1720,7 @@ static int link_enter_join_netdev(Link *link) {
 
         link_set_state(link, LINK_STATE_ENSLAVING);
 
-        link_save(link);
+        link_dirty(link);
 
         if (!link->network->bridge &&
             !link->network->bond &&
@@ -2311,55 +2364,6 @@ int link_update(Link *link, sd_netlink_message *m) {
         return 0;
 }
 
-static void link_update_operstate(Link *link) {
-        LinkOperationalState operstate;
-        assert(link);
-
-        if (link->kernel_operstate == IF_OPER_DORMANT)
-                operstate = LINK_OPERSTATE_DORMANT;
-        else if (link_has_carrier(link)) {
-                Address *address;
-                uint8_t scope = RT_SCOPE_NOWHERE;
-                Iterator i;
-
-                /* if we have carrier, check what addresses we have */
-                SET_FOREACH(address, link->addresses, i) {
-                        if (!address_is_ready(address))
-                                continue;
-
-                        if (address->scope < scope)
-                                scope = address->scope;
-                }
-
-                /* for operstate we also take foreign addresses into account */
-                SET_FOREACH(address, link->addresses_foreign, i) {
-                        if (!address_is_ready(address))
-                                continue;
-
-                        if (address->scope < scope)
-                                scope = address->scope;
-                }
-
-                if (scope < RT_SCOPE_SITE)
-                        /* universally accessible addresses found */
-                        operstate = LINK_OPERSTATE_ROUTABLE;
-                else if (scope < RT_SCOPE_HOST)
-                        /* only link or site local addresses found */
-                        operstate = LINK_OPERSTATE_DEGRADED;
-                else
-                        /* no useful addresses found */
-                        operstate = LINK_OPERSTATE_CARRIER;
-        } else if (link->flags & IFF_UP)
-                operstate = LINK_OPERSTATE_NO_CARRIER;
-        else
-                operstate = LINK_OPERSTATE_OFF;
-
-        if (link->operstate != operstate) {
-                link->operstate = operstate;
-                link_send_changed(link, "OperationalState", NULL);
-        }
-}
-
 int link_save(Link *link) {
         _cleanup_free_ char *temp_path = NULL;
         _cleanup_fclose_ FILE *f = NULL;
@@ -2373,12 +2377,6 @@ int link_save(Link *link) {
         assert(link->lease_file);
         assert(link->manager);
 
-        link_update_operstate(link);
-
-        r = manager_save(link->manager);
-        if (r < 0)
-                return r;
-
         if (link->state == LINK_STATE_LINGER) {
                 unlink(link->state_file);
                 return 0;
@@ -2553,8 +2551,6 @@ int link_save(Link *link) {
                         if (r < 0)
                                 goto fail;
 
-                        if (space)
-                                fputc(' ', f);
                         fprintf(f, "%s%s/%u", space ? " " : "", address_str, a->prefixlen);
                         space = true;
                 }
@@ -2645,6 +2641,34 @@ fail:
         return log_link_error_errno(link, r, "Failed to save link data to %s: %m", link->state_file);
 }
 
+/* The serialized state in /run is no longer up-to-date. */
+void link_dirty(Link *link) {
+        int r;
+
+        assert(link);
+
+        r = set_ensure_allocated(&link->manager->dirty_links, NULL);
+        if (r < 0)
+                /* allocation errors are ignored */
+                return;
+
+        r = set_put(link->manager->dirty_links, link);
+        if (r < 0)
+                /* allocation errors are ignored */
+                return;
+
+        link_ref(link);
+}
+
+/* The serialized state in /run is up-to-date */
+void link_clean(Link *link) {
+        assert(link);
+        assert(link->manager);
+
+        set_remove(link->manager->dirty_links, link);
+        link_unref(link);
+}
+
 static const char* const link_state_table[_LINK_STATE_MAX] = {
         [LINK_STATE_PENDING] = "pending",
         [LINK_STATE_ENSLAVING] = "configuring",
index 90ad08a3069e0c191754c79ed781ed0949794c6e..af2ba11701b8a23a49881c7fab51068a5ca85bf6 100644 (file)
@@ -129,8 +129,11 @@ int link_initialized(Link *link, struct udev_device *device);
 
 void link_check_ready(Link *link);
 
+void link_update_operstate(Link *link);
 int link_update(Link *link, sd_netlink_message *message);
 
+void link_dirty(Link *link);
+void link_clean(Link *link);
 int link_save(Link *link);
 
 int link_carrier_reset(Link *link);
index 2b83ee81ed27f9b451026fd1b9822b1ff4c49f18..76d5aa5585cb5fdbbee4f55b26085d4eda56009f 100644 (file)
@@ -573,6 +573,213 @@ static int manager_connect_rtnl(Manager *m) {
         return 0;
 }
 
+static int set_put_in_addr(Set *s, const struct in_addr *address) {
+        char *p;
+        int r;
+
+        assert(s);
+
+        r = in_addr_to_string(AF_INET, (const union in_addr_union*) address, &p);
+        if (r < 0)
+                return r;
+
+        r = set_consume(s, p);
+        if (r == -EEXIST)
+                return 0;
+
+        return r;
+}
+
+static int set_put_in_addrv(Set *s, const struct in_addr *addresses, int n) {
+        int r, i, c = 0;
+
+        assert(s);
+        assert(n <= 0 || addresses);
+
+        for (i = 0; i < n; i++) {
+                r = set_put_in_addr(s, addresses+i);
+                if (r < 0)
+                        return r;
+
+                c += r;
+        }
+
+        return c;
+}
+
+static void print_string_set(FILE *f, const char *field, Set *s) {
+        bool space = false;
+        Iterator i;
+        char *p;
+
+        if (set_isempty(s))
+                return;
+
+        fputs(field, f);
+
+        SET_FOREACH(p, s, i) {
+                if (space)
+                        fputc(' ', f);
+                fputs(p, f);
+                space = true;
+        }
+        fputc('\n', f);
+}
+
+static int manager_save(Manager *m) {
+        _cleanup_set_free_free_ Set *dns = NULL, *ntp = NULL, *domains = NULL;
+        Link *link;
+        Iterator i;
+        _cleanup_free_ char *temp_path = NULL;
+        _cleanup_fclose_ FILE *f = NULL;
+        LinkOperationalState operstate = LINK_OPERSTATE_OFF;
+        const char *operstate_str;
+        int r;
+
+        assert(m);
+        assert(m->state_file);
+
+        /* We add all NTP and DNS server to a set, to filter out duplicates */
+        dns = set_new(&string_hash_ops);
+        if (!dns)
+                return -ENOMEM;
+
+        ntp = set_new(&string_hash_ops);
+        if (!ntp)
+                return -ENOMEM;
+
+        domains = set_new(&string_hash_ops);
+        if (!domains)
+                return -ENOMEM;
+
+        HASHMAP_FOREACH(link, m->links, i) {
+                if (link->flags & IFF_LOOPBACK)
+                        continue;
+
+                if (link->operstate > operstate)
+                        operstate = link->operstate;
+
+                if (!link->network)
+                        continue;
+
+                /* First add the static configured entries */
+                r = set_put_strdupv(dns, link->network->dns);
+                if (r < 0)
+                        return r;
+
+                r = set_put_strdupv(ntp, link->network->ntp);
+                if (r < 0)
+                        return r;
+
+                r = set_put_strdupv(domains, link->network->domains);
+                if (r < 0)
+                        return r;
+
+                if (!link->dhcp_lease)
+                        continue;
+
+                /* Secondly, add the entries acquired via DHCP */
+                if (link->network->dhcp_dns) {
+                        const struct in_addr *addresses;
+
+                        r = sd_dhcp_lease_get_dns(link->dhcp_lease, &addresses);
+                        if (r > 0) {
+                                r = set_put_in_addrv(dns, addresses, r);
+                                if (r < 0)
+                                        return r;
+                        } else if (r < 0 && r != -ENODATA)
+                                return r;
+                }
+
+                if (link->network->dhcp_ntp) {
+                        const struct in_addr *addresses;
+
+                        r = sd_dhcp_lease_get_ntp(link->dhcp_lease, &addresses);
+                        if (r > 0) {
+                                r = set_put_in_addrv(ntp, addresses, r);
+                                if (r < 0)
+                                        return r;
+                        } else if (r < 0 && r != -ENODATA)
+                                return r;
+                }
+
+                if (link->network->dhcp_domains) {
+                        const char *domainname;
+
+                        r = sd_dhcp_lease_get_domainname(link->dhcp_lease, &domainname);
+                        if (r >= 0) {
+                                r = set_put_strdup(domains, domainname);
+                                if (r < 0)
+                                        return r;
+                        } else if (r != -ENODATA)
+                                return r;
+                }
+        }
+
+        operstate_str = link_operstate_to_string(operstate);
+        assert(operstate_str);
+
+        r = fopen_temporary(m->state_file, &f, &temp_path);
+        if (r < 0)
+                return r;
+
+        fchmod(fileno(f), 0644);
+
+        fprintf(f,
+                "# This is private data. Do not parse.\n"
+                "OPER_STATE=%s\n", operstate_str);
+
+        print_string_set(f, "DNS=", dns);
+        print_string_set(f, "NTP=", ntp);
+        print_string_set(f, "DOMAINS=", domains);
+
+        r = fflush_and_check(f);
+        if (r < 0)
+                goto fail;
+
+        if (rename(temp_path, m->state_file) < 0) {
+                r = -errno;
+                goto fail;
+        }
+
+        if (m->operational_state != operstate) {
+                m->operational_state = operstate;
+                r = manager_send_changed(m, "OperationalState", NULL);
+                if (r < 0)
+                        log_error_errno(r, "Could not emit changed OperationalState: %m");
+        }
+
+        m->dirty = false;
+
+        return 0;
+
+fail:
+        (void) unlink(m->state_file);
+        (void) unlink(temp_path);
+
+        return log_error_errno(r, "Failed to save network state to %s: %m", m->state_file);
+}
+
+static int manager_dirty_handler(sd_event_source *s, void *userdata) {
+        Manager *m = userdata;
+        Link *link;
+        Iterator i;
+        int r;
+
+        assert(m);
+
+        if (m->dirty)
+                manager_save(m);
+
+        SET_FOREACH(link, m->dirty_links, i) {
+                r = link_save(link);
+                if (r >= 0)
+                        link_clean(link);
+        }
+
+        return 1;
+}
+
 int manager_new(Manager **ret) {
         _cleanup_manager_free_ Manager *m = NULL;
         int r;
@@ -594,6 +801,10 @@ int manager_new(Manager **ret) {
         sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL);
         sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL);
 
+        r = sd_event_add_post(m->event, NULL, manager_dirty_handler, m);
+        if (r < 0)
+                return r;
+
         r = manager_connect_rtnl(m);
         if (r < 0)
                 return r;
@@ -688,8 +899,19 @@ static bool manager_check_idle(void *userdata) {
 }
 
 int manager_run(Manager *m) {
+        Link *link;
+        Iterator i;
+
         assert(m);
 
+        /* The dirty handler will deal with future serialization, but the first one
+           must be done explicitly. */
+
+        manager_save(m);
+
+        HASHMAP_FOREACH(link, m->links, i)
+                link_save(link);
+
         if (m->bus)
                 return bus_event_loop_with_idle(
                                 m->event,
@@ -795,191 +1017,6 @@ int manager_rtnl_enumerate_addresses(Manager *m) {
         return r;
 }
 
-static int set_put_in_addr(Set *s, const struct in_addr *address) {
-        char *p;
-        int r;
-
-        assert(s);
-
-        r = in_addr_to_string(AF_INET, (const union in_addr_union*) address, &p);
-        if (r < 0)
-                return r;
-
-        r = set_consume(s, p);
-        if (r == -EEXIST)
-                return 0;
-
-        return r;
-}
-
-static int set_put_in_addrv(Set *s, const struct in_addr *addresses, int n) {
-        int r, i, c = 0;
-
-        assert(s);
-        assert(n <= 0 || addresses);
-
-        for (i = 0; i < n; i++) {
-                r = set_put_in_addr(s, addresses+i);
-                if (r < 0)
-                        return r;
-
-                c += r;
-        }
-
-        return c;
-}
-
-static void print_string_set(FILE *f, const char *field, Set *s) {
-        bool space = false;
-        Iterator i;
-        char *p;
-
-        if (set_isempty(s))
-                return;
-
-        fputs(field, f);
-
-        SET_FOREACH(p, s, i) {
-                if (space)
-                        fputc(' ', f);
-                fputs(p, f);
-                space = true;
-        }
-        fputc('\n', f);
-}
-
-int manager_save(Manager *m) {
-        _cleanup_set_free_free_ Set *dns = NULL, *ntp = NULL, *domains = NULL;
-        Link *link;
-        Iterator i;
-        _cleanup_free_ char *temp_path = NULL;
-        _cleanup_fclose_ FILE *f = NULL;
-        LinkOperationalState operstate = LINK_OPERSTATE_OFF;
-        const char *operstate_str;
-        int r;
-
-        assert(m);
-        assert(m->state_file);
-
-        /* We add all NTP and DNS server to a set, to filter out duplicates */
-        dns = set_new(&string_hash_ops);
-        if (!dns)
-                return -ENOMEM;
-
-        ntp = set_new(&string_hash_ops);
-        if (!ntp)
-                return -ENOMEM;
-
-        domains = set_new(&string_hash_ops);
-        if (!domains)
-                return -ENOMEM;
-
-        HASHMAP_FOREACH(link, m->links, i) {
-                if (link->flags & IFF_LOOPBACK)
-                        continue;
-
-                if (link->operstate > operstate)
-                        operstate = link->operstate;
-
-                if (!link->network)
-                        continue;
-
-                /* First add the static configured entries */
-                r = set_put_strdupv(dns, link->network->dns);
-                if (r < 0)
-                        return r;
-
-                r = set_put_strdupv(ntp, link->network->ntp);
-                if (r < 0)
-                        return r;
-
-                r = set_put_strdupv(domains, link->network->domains);
-                if (r < 0)
-                        return r;
-
-                if (!link->dhcp_lease)
-                        continue;
-
-                /* Secondly, add the entries acquired via DHCP */
-                if (link->network->dhcp_dns) {
-                        const struct in_addr *addresses;
-
-                        r = sd_dhcp_lease_get_dns(link->dhcp_lease, &addresses);
-                        if (r > 0) {
-                                r = set_put_in_addrv(dns, addresses, r);
-                                if (r < 0)
-                                        return r;
-                        } else if (r < 0 && r != -ENODATA)
-                                return r;
-                }
-
-                if (link->network->dhcp_ntp) {
-                        const struct in_addr *addresses;
-
-                        r = sd_dhcp_lease_get_ntp(link->dhcp_lease, &addresses);
-                        if (r > 0) {
-                                r = set_put_in_addrv(ntp, addresses, r);
-                                if (r < 0)
-                                        return r;
-                        } else if (r < 0 && r != -ENODATA)
-                                return r;
-                }
-
-                if (link->network->dhcp_domains) {
-                        const char *domainname;
-
-                        r = sd_dhcp_lease_get_domainname(link->dhcp_lease, &domainname);
-                        if (r >= 0) {
-                                r = set_put_strdup(domains, domainname);
-                                if (r < 0)
-                                        return r;
-                        } else if (r != -ENODATA)
-                                return r;
-                }
-        }
-
-        operstate_str = link_operstate_to_string(operstate);
-        assert(operstate_str);
-
-        r = fopen_temporary(m->state_file, &f, &temp_path);
-        if (r < 0)
-                return r;
-
-        fchmod(fileno(f), 0644);
-
-        fprintf(f,
-                "# This is private data. Do not parse.\n"
-                "OPER_STATE=%s\n", operstate_str);
-
-        print_string_set(f, "DNS=", dns);
-        print_string_set(f, "NTP=", ntp);
-        print_string_set(f, "DOMAINS=", domains);
-
-        r = fflush_and_check(f);
-        if (r < 0)
-                goto fail;
-
-        if (rename(temp_path, m->state_file) < 0) {
-                r = -errno;
-                goto fail;
-        }
-
-        if (m->operational_state != operstate) {
-                m->operational_state = operstate;
-                r = manager_send_changed(m, "OperationalState", NULL);
-                if (r < 0)
-                        log_error_errno(r, "Could not emit changed OperationalState: %m");
-        }
-
-        return 0;
-
-fail:
-        (void) unlink(m->state_file);
-        (void) unlink(temp_path);
-
-        return log_error_errno(r, "Failed to save network state to %s: %m", m->state_file);
-}
-
 int manager_address_pool_acquire(Manager *m, int family, unsigned prefixlen, union in_addr_union *found) {
         AddressPool *p;
         int r;
@@ -1036,3 +1073,10 @@ Link* manager_find_uplink(Manager *m, Link *exclude) {
 
         return NULL;
 }
+
+void manager_dirty(Manager *manager) {
+        assert(manager);
+
+        /* the serialized state in /run is no longer up-to-date */
+        manager->dirty = true;
+}
index 5b8ca305bc6225db4ed75066b829524f5ff80e84..97ada568665e2145224c08ad0bebffbb67500e64 100644 (file)
@@ -369,10 +369,9 @@ int network_apply(Manager *manager, Network *network, Link *link) {
                 route->protocol = RTPROT_STATIC;
         }
 
-        if (network->dns || network->ntp) {
-                r = link_save(link);
-                if (r < 0)
-                        return r;
+        if (network->dns || network->ntp || network->domains) {
+                manager_dirty(manager);
+                link_dirty(link);
         }
 
         return 0;
index cbec6d5b7e7b57ea2c022d829851a583105b6583..6c5a9939beca50b987ba7e4455723b7a1076bf98 100644 (file)
@@ -48,7 +48,10 @@ struct Manager {
         struct udev_monitor *udev_monitor;
         sd_event_source *udev_event_source;
 
-        bool enumerating;
+        bool enumerating:1;
+        bool dirty:1;
+
+        Set *dirty_links;
 
         char *state_file;
         LinkOperationalState operational_state;
@@ -83,7 +86,7 @@ int manager_rtnl_enumerate_addresses(Manager *m);
 int manager_rtnl_process_address(sd_netlink *nl, sd_netlink_message *message, void *userdata);
 
 int manager_send_changed(Manager *m, const char *property, ...) _sentinel_;
-int manager_save(Manager *m);
+void manager_dirty(Manager *m);
 
 int manager_address_pool_acquire(Manager *m, int family, unsigned prefixlen, union in_addr_union *found);