summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYu Watanabe <watanabe.yu+github@gmail.com>2021-02-21 06:23:04 +0100
committerYu Watanabe <watanabe.yu+github@gmail.com>2021-03-03 08:07:45 +0100
commit3b5a4fc685d6db71d68db639f274b644d9422bb1 (patch)
tree9c19be48c0c428cc95542fe54824d0ae624b48ff
parentfstab-generator: add new root=tmpfs option (diff)
downloadsystemd-3b5a4fc685d6db71d68db639f274b644d9422bb1.tar.xz
systemd-3b5a4fc685d6db71d68db639f274b644d9422bb1.zip
network: move state file related functions to networkd-state-file.[ch]
-rw-r--r--src/network/meson.build2
-rw-r--r--src/network/networkd-dhcp4.c1
-rw-r--r--src/network/networkd-link-bus.c1
-rw-r--r--src/network/networkd-link.c385
-rw-r--r--src/network/networkd-link.h5
-rw-r--r--src/network/networkd-manager.c291
-rw-r--r--src/network/networkd-manager.h2
-rw-r--r--src/network/networkd-ndisc.c1
-rw-r--r--src/network/networkd-state-file.c690
-rw-r--r--src/network/networkd-state-file.h12
10 files changed, 709 insertions, 681 deletions
diff --git a/src/network/meson.build b/src/network/meson.build
index 3ff04467b0..4fca3106dc 100644
--- a/src/network/meson.build
+++ b/src/network/meson.build
@@ -113,6 +113,8 @@ sources = files('''
networkd-speed-meter.h
networkd-sriov.c
networkd-sriov.h
+ networkd-state-file.c
+ networkd-state-file.h
networkd-sysctl.c
networkd-sysctl.h
networkd-util.c
diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c
index b0c783c774..242b977f60 100644
--- a/src/network/networkd-dhcp4.c
+++ b/src/network/networkd-dhcp4.c
@@ -17,6 +17,7 @@
#include "networkd-link.h"
#include "networkd-manager.h"
#include "networkd-network.h"
+#include "networkd-state-file.h"
#include "string-table.h"
#include "strv.h"
#include "sysctl-util.h"
diff --git a/src/network/networkd-link-bus.c b/src/network/networkd-link-bus.c
index 3ffe587a86..a23c98110e 100644
--- a/src/network/networkd-link-bus.c
+++ b/src/network/networkd-link-bus.c
@@ -13,6 +13,7 @@
#include "networkd-link-bus.h"
#include "networkd-link.h"
#include "networkd-manager.h"
+#include "networkd-state-file.h"
#include "parse-util.h"
#include "resolve-util.h"
#include "socket-netlink.h"
diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
index 96d4d9235b..a8f021fa6d 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -42,6 +42,7 @@
#include "networkd-sysctl.h"
#include "networkd-radv.h"
#include "networkd-routing-policy-rule.h"
+#include "networkd-state-file.h"
#include "networkd-wifi.h"
#include "set.h"
#include "socket-util.h"
@@ -2806,390 +2807,6 @@ int link_update(Link *link, sd_netlink_message *m) {
return 0;
}
-static void print_link_hashmap(FILE *f, const char *prefix, Hashmap* h) {
- bool space = false;
- Link *link;
-
- assert(f);
- assert(prefix);
-
- if (hashmap_isempty(h))
- return;
-
- fputs(prefix, f);
- HASHMAP_FOREACH(link, h) {
- if (space)
- fputc(' ', f);
-
- fprintf(f, "%i", link->ifindex);
- space = true;
- }
-
- fputc('\n', f);
-}
-
-static void link_save_dns(Link *link, FILE *f, struct in_addr_full **dns, unsigned n_dns, bool *space) {
- for (unsigned j = 0; j < n_dns; j++) {
- const char *str;
-
- if (dns[j]->ifindex != 0 && dns[j]->ifindex != link->ifindex)
- continue;
-
- str = in_addr_full_to_string(dns[j]);
- if (!str)
- continue;
-
- if (*space)
- fputc(' ', f);
- fputs(str, f);
- *space = true;
- }
-}
-
-static void serialize_addresses(
- FILE *f,
- const char *lvalue,
- bool *space,
- char **addresses,
- sd_dhcp_lease *lease,
- bool conditional,
- sd_dhcp_lease_server_type_t what,
- sd_dhcp6_lease *lease6,
- bool conditional6,
- int (*lease6_get_addr)(sd_dhcp6_lease*, const struct in6_addr**),
- int (*lease6_get_fqdn)(sd_dhcp6_lease*, char ***)) {
- int r;
-
- bool _space = false;
- if (!space)
- space = &_space;
-
- if (lvalue)
- fprintf(f, "%s=", lvalue);
- fputstrv(f, addresses, NULL, space);
-
- if (lease && conditional) {
- const struct in_addr *lease_addresses;
-
- r = sd_dhcp_lease_get_servers(lease, what, &lease_addresses);
- if (r > 0)
- serialize_in_addrs(f, lease_addresses, r, space, in4_addr_is_non_local);
- }
-
- if (lease6 && conditional6 && lease6_get_addr) {
- const struct in6_addr *in6_addrs;
-
- r = lease6_get_addr(lease6, &in6_addrs);
- if (r > 0)
- serialize_in6_addrs(f, in6_addrs, r, space);
- }
-
- if (lease6 && conditional6 && lease6_get_fqdn) {
- char **in6_hosts;
-
- r = lease6_get_fqdn(lease6, &in6_hosts);
- if (r > 0)
- fputstrv(f, in6_hosts, NULL, space);
- }
-
- if (lvalue)
- fputc('\n', f);
-}
-
-int link_save(Link *link) {
- const char *admin_state, *oper_state, *carrier_state, *address_state;
- _cleanup_free_ char *temp_path = NULL;
- _cleanup_fclose_ FILE *f = NULL;
- int r;
-
- assert(link);
- assert(link->state_file);
- assert(link->lease_file);
- assert(link->manager);
-
- if (link->state == LINK_STATE_LINGER) {
- (void) unlink(link->state_file);
- return 0;
- }
-
- link_lldp_save(link);
-
- admin_state = link_state_to_string(link->state);
- assert(admin_state);
-
- oper_state = link_operstate_to_string(link->operstate);
- assert(oper_state);
-
- carrier_state = link_carrier_state_to_string(link->carrier_state);
- assert(carrier_state);
-
- address_state = link_address_state_to_string(link->address_state);
- assert(address_state);
-
- r = fopen_temporary(link->state_file, &f, &temp_path);
- if (r < 0)
- goto fail;
-
- (void) fchmod(fileno(f), 0644);
-
- fprintf(f,
- "# This is private data. Do not parse.\n"
- "ADMIN_STATE=%s\n"
- "OPER_STATE=%s\n"
- "CARRIER_STATE=%s\n"
- "ADDRESS_STATE=%s\n",
- admin_state, oper_state, carrier_state, address_state);
-
- if (link->network) {
- char **dhcp6_domains = NULL, **dhcp_domains = NULL;
- const char *dhcp_domainname = NULL, *p;
- bool space;
-
- fprintf(f, "REQUIRED_FOR_ONLINE=%s\n",
- yes_no(link->network->required_for_online));
-
- LinkOperationalStateRange st = link->network->required_operstate_for_online;
- fprintf(f, "REQUIRED_OPER_STATE_FOR_ONLINE=%s%s%s\n",
- strempty(link_operstate_to_string(st.min)),
- st.max != LINK_OPERSTATE_RANGE_DEFAULT.max ? ":" : "",
- st.max != LINK_OPERSTATE_RANGE_DEFAULT.max ? strempty(link_operstate_to_string(st.max)) : "");
-
- fprintf(f, "ACTIVATION_POLICY=%s\n",
- activation_policy_to_string(link->network->activation_policy));
-
- fprintf(f, "NETWORK_FILE=%s\n", link->network->filename);
-
- /************************************************************/
-
- fputs("DNS=", f);
- space = false;
- if (link->n_dns != (unsigned) -1)
- link_save_dns(link, f, link->dns, link->n_dns, &space);
- else
- link_save_dns(link, f, link->network->dns, link->network->n_dns, &space);
-
- serialize_addresses(f, NULL, &space,
- NULL,
- link->dhcp_lease,
- link->network->dhcp_use_dns,
- SD_DHCP_LEASE_DNS,
- link->dhcp6_lease,
- link->network->dhcp6_use_dns,
- sd_dhcp6_lease_get_dns,
- NULL);
-
- /* Make sure to flush out old entries before we use the NDisc data */
- ndisc_vacuum(link);
-
- if (link->network->ipv6_accept_ra_use_dns && link->ndisc_rdnss) {
- NDiscRDNSS *dd;
-
- SET_FOREACH(dd, link->ndisc_rdnss)
- serialize_in6_addrs(f, &dd->address, 1, &space);
- }
-
- fputc('\n', f);
-
- /************************************************************/
-
- serialize_addresses(f, "NTP", NULL,
- link->ntp ?: link->network->ntp,
- link->dhcp_lease,
- link->network->dhcp_use_ntp,
- SD_DHCP_LEASE_NTP,
- link->dhcp6_lease,
- link->network->dhcp6_use_ntp,
- sd_dhcp6_lease_get_ntp_addrs,
- sd_dhcp6_lease_get_ntp_fqdn);
-
- serialize_addresses(f, "SIP", NULL,
- NULL,
- link->dhcp_lease,
- link->network->dhcp_use_sip,
- SD_DHCP_LEASE_SIP,
- NULL, false, NULL, NULL);
-
- /************************************************************/
-
- if (link->network->dhcp_use_domains != DHCP_USE_DOMAINS_NO) {
- if (link->dhcp_lease) {
- (void) sd_dhcp_lease_get_domainname(link->dhcp_lease, &dhcp_domainname);
- (void) sd_dhcp_lease_get_search_domains(link->dhcp_lease, &dhcp_domains);
- }
- if (link->dhcp6_lease)
- (void) sd_dhcp6_lease_get_domains(link->dhcp6_lease, &dhcp6_domains);
- }
-
- fputs("DOMAINS=", f);
- space = false;
- ORDERED_SET_FOREACH(p, link->search_domains ?: link->network->search_domains)
- fputs_with_space(f, p, NULL, &space);
-
- if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES) {
- if (dhcp_domainname)
- fputs_with_space(f, dhcp_domainname, NULL, &space);
- if (dhcp_domains)
- fputstrv(f, dhcp_domains, NULL, &space);
- if (dhcp6_domains)
- fputstrv(f, dhcp6_domains, NULL, &space);
- }
-
- if (link->network->ipv6_accept_ra_use_domains == DHCP_USE_DOMAINS_YES) {
- NDiscDNSSL *dd;
-
- SET_FOREACH(dd, link->ndisc_dnssl)
- fputs_with_space(f, NDISC_DNSSL_DOMAIN(dd), NULL, &space);
- }
-
- fputc('\n', f);
-
- /************************************************************/
-
- fputs("ROUTE_DOMAINS=", f);
- space = false;
- ORDERED_SET_FOREACH(p, link->route_domains ?: link->network->route_domains)
- fputs_with_space(f, p, NULL, &space);
-
- if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_ROUTE) {
- if (dhcp_domainname)
- fputs_with_space(f, dhcp_domainname, NULL, &space);
- if (dhcp_domains)
- fputstrv(f, dhcp_domains, NULL, &space);
- if (dhcp6_domains)
- fputstrv(f, dhcp6_domains, NULL, &space);
- }
-
- if (link->network->ipv6_accept_ra_use_domains == DHCP_USE_DOMAINS_ROUTE) {
- NDiscDNSSL *dd;
-
- SET_FOREACH(dd, link->ndisc_dnssl)
- fputs_with_space(f, NDISC_DNSSL_DOMAIN(dd), NULL, &space);
- }
-
- fputc('\n', f);
-
- /************************************************************/
-
- fprintf(f, "LLMNR=%s\n",
- resolve_support_to_string(link->llmnr >= 0 ? link->llmnr : link->network->llmnr));
-
- /************************************************************/
-
- fprintf(f, "MDNS=%s\n",
- resolve_support_to_string(link->mdns >= 0 ? link->mdns : link->network->mdns));
-
- /************************************************************/
-
- int dns_default_route =
- link->dns_default_route >= 0 ? link->dns_default_route :
- link->network->dns_default_route;
- if (dns_default_route >= 0)
- fprintf(f, "DNS_DEFAULT_ROUTE=%s\n", yes_no(dns_default_route));
-
- /************************************************************/
-
- DnsOverTlsMode dns_over_tls_mode =
- link->dns_over_tls_mode != _DNS_OVER_TLS_MODE_INVALID ? link->dns_over_tls_mode :
- link->network->dns_over_tls_mode;
- if (dns_over_tls_mode != _DNS_OVER_TLS_MODE_INVALID)
- fprintf(f, "DNS_OVER_TLS=%s\n", dns_over_tls_mode_to_string(dns_over_tls_mode));
-
- /************************************************************/
-
- DnssecMode dnssec_mode =
- link->dnssec_mode != _DNSSEC_MODE_INVALID ? link->dnssec_mode :
- link->network->dnssec_mode;
- if (dnssec_mode != _DNSSEC_MODE_INVALID)
- fprintf(f, "DNSSEC=%s\n", dnssec_mode_to_string(dnssec_mode));
-
- /************************************************************/
-
- Set *nta_anchors = link->dnssec_negative_trust_anchors;
- if (set_isempty(nta_anchors))
- nta_anchors = link->network->dnssec_negative_trust_anchors;
-
- if (!set_isempty(nta_anchors)) {
- const char *n;
-
- fputs("DNSSEC_NTA=", f);
- space = false;
- SET_FOREACH(n, nta_anchors)
- fputs_with_space(f, n, NULL, &space);
- fputc('\n', f);
- }
- }
-
- print_link_hashmap(f, "CARRIER_BOUND_TO=", link->bound_to_links);
- print_link_hashmap(f, "CARRIER_BOUND_BY=", link->bound_by_links);
-
- if (link->dhcp_lease) {
- r = dhcp_lease_save(link->dhcp_lease, link->lease_file);
- if (r < 0)
- goto fail;
-
- fprintf(f,
- "DHCP_LEASE=%s\n",
- link->lease_file);
- } else
- (void) unlink(link->lease_file);
-
- r = link_serialize_dhcp6_client(link, f);
- if (r < 0)
- goto fail;
-
- r = fflush_and_check(f);
- if (r < 0)
- goto fail;
-
- r = conservative_rename(temp_path, link->state_file);
- if (r < 0)
- goto fail;
-
- return 0;
-
-fail:
- (void) unlink(link->state_file);
- if (temp_path)
- (void) unlink(temp_path);
-
- 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);
-
- /* mark manager dirty as link is dirty */
- manager_dirty(link->manager);
-
- r = set_ensure_put(&link->manager->dirty_links, NULL, link);
- if (r <= 0)
- /* Ignore allocation errors and don't take another ref if the link was already dirty */
- return;
- link_ref(link);
-}
-
-/* The serialized state in /run is up-to-date */
-void link_clean(Link *link) {
- assert(link);
- assert(link->manager);
-
- link_unref(set_remove(link->manager->dirty_links, link));
-}
-
-int link_save_and_clean(Link *link) {
- int r;
-
- r = link_save(link);
- if (r < 0)
- return r;
-
- link_clean(link);
- return 0;
-}
-
static const char* const link_state_table[_LINK_STATE_MAX] = {
[LINK_STATE_PENDING] = "pending",
[LINK_STATE_INITIALIZED] = "initialized",
diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h
index 8129fd2554..347da1c928 100644
--- a/src/network/networkd-link.h
+++ b/src/network/networkd-link.h
@@ -217,11 +217,6 @@ void link_check_ready(Link *link);
void link_update_operstate(Link *link, bool also_update_bond_master);
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_save_and_clean(Link *link);
-
int link_carrier_reset(Link *link);
bool link_has_carrier(Link *link);
diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c
index 08da9120ad..0b1cb1e42a 100644
--- a/src/network/networkd-manager.c
+++ b/src/network/networkd-manager.c
@@ -38,6 +38,7 @@
#include "networkd-nexthop.h"
#include "networkd-routing-policy-rule.h"
#include "networkd-speed-meter.h"
+#include "networkd-state-file.h"
#include "ordered-set.h"
#include "path-lookup.h"
#include "path-util.h"
@@ -466,289 +467,6 @@ static int manager_connect_rtnl(Manager *m) {
return 0;
}
-static int ordered_set_put_dns_server(OrderedSet *s, int ifindex, struct in_addr_full *dns) {
- const char *p;
- int r;
-
- assert(s);
- assert(dns);
-
- if (dns->ifindex != 0 && dns->ifindex != ifindex)
- return 0;
-
- p = in_addr_full_to_string(dns);
- if (!p)
- return 0;
-
- r = ordered_set_put_strdup(s, p);
- if (r == -EEXIST)
- return 0;
-
- return r;
-}
-
-static int ordered_set_put_dns_servers(OrderedSet *s, int ifindex, struct in_addr_full **dns, unsigned n) {
- int r, c = 0;
-
- assert(s);
- assert(dns || n == 0);
-
- for (unsigned i = 0; i < n; i++) {
- r = ordered_set_put_dns_server(s, ifindex, dns[i]);
- if (r < 0)
- return r;
-
- c += r;
- }
-
- return c;
-}
-
-static int ordered_set_put_in4_addr(OrderedSet *s, const struct in_addr *address) {
- char *p;
- int r;
-
- assert(s);
- assert(address);
-
- r = in_addr_to_string(AF_INET, (const union in_addr_union*) address, &p);
- if (r < 0)
- return r;
-
- r = ordered_set_consume(s, p);
- if (r == -EEXIST)
- return 0;
-
- return r;
-}
-
-static int ordered_set_put_in4_addrv(OrderedSet *s,
- const struct in_addr *addresses,
- size_t n,
- bool (*predicate)(const struct in_addr *addr)) {
- int r, c = 0;
-
- assert(s);
- assert(n == 0 || addresses);
-
- for (size_t i = 0; i < n; i++) {
- if (predicate && !predicate(&addresses[i]))
- continue;
- r = ordered_set_put_in4_addr(s, addresses+i);
- if (r < 0)
- return r;
-
- c += r;
- }
-
- return c;
-}
-
-static int manager_save(Manager *m) {
- _cleanup_ordered_set_free_free_ OrderedSet *dns = NULL, *ntp = NULL, *sip = NULL, *search_domains = NULL, *route_domains = NULL;
- const char *operstate_str, *carrier_state_str, *address_state_str;
- LinkOperationalState operstate = LINK_OPERSTATE_OFF;
- LinkCarrierState carrier_state = LINK_CARRIER_STATE_OFF;
- LinkAddressState address_state = LINK_ADDRESS_STATE_OFF;
- _cleanup_free_ char *temp_path = NULL;
- _cleanup_strv_free_ char **p = NULL;
- _cleanup_fclose_ FILE *f = NULL;
- Link *link;
- int r;
-
- assert(m);
- assert(m->state_file);
-
- /* We add all NTP and DNS server to a set, to filter out duplicates */
- dns = ordered_set_new(&string_hash_ops);
- if (!dns)
- return -ENOMEM;
-
- ntp = ordered_set_new(&string_hash_ops);
- if (!ntp)
- return -ENOMEM;
-
- sip = ordered_set_new(&string_hash_ops);
- if (!sip)
- return -ENOMEM;
-
- search_domains = ordered_set_new(&dns_name_hash_ops);
- if (!search_domains)
- return -ENOMEM;
-
- route_domains = ordered_set_new(&dns_name_hash_ops);
- if (!route_domains)
- return -ENOMEM;
-
- HASHMAP_FOREACH(link, m->links) {
- const struct in_addr *addresses;
-
- if (link->flags & IFF_LOOPBACK)
- continue;
-
- if (link->operstate > operstate)
- operstate = link->operstate;
-
- if (link->carrier_state > carrier_state)
- carrier_state = link->carrier_state;
-
- if (link->address_state > address_state)
- address_state = link->address_state;
-
- if (!link->network)
- continue;
-
- /* First add the static configured entries */
- if (link->n_dns != (unsigned) -1)
- r = ordered_set_put_dns_servers(dns, link->ifindex, link->dns, link->n_dns);
- else
- r = ordered_set_put_dns_servers(dns, link->ifindex, link->network->dns, link->network->n_dns);
- if (r < 0)
- return r;
-
- r = ordered_set_put_strdupv(ntp, link->ntp ?: link->network->ntp);
- if (r < 0)
- return r;
-
- r = ordered_set_put_string_set(search_domains, link->search_domains ?: link->network->search_domains);
- if (r < 0)
- return r;
-
- r = ordered_set_put_string_set(route_domains, link->route_domains ?: link->network->route_domains);
- if (r < 0)
- return r;
-
- if (!link->dhcp_lease)
- continue;
-
- /* Secondly, add the entries acquired via DHCP */
- if (link->network->dhcp_use_dns) {
- r = sd_dhcp_lease_get_dns(link->dhcp_lease, &addresses);
- if (r > 0) {
- r = ordered_set_put_in4_addrv(dns, addresses, r, in4_addr_is_non_local);
- if (r < 0)
- return r;
- } else if (r < 0 && r != -ENODATA)
- return r;
- }
-
- if (link->network->dhcp_use_ntp) {
- r = sd_dhcp_lease_get_ntp(link->dhcp_lease, &addresses);
- if (r > 0) {
- r = ordered_set_put_in4_addrv(ntp, addresses, r, in4_addr_is_non_local);
- if (r < 0)
- return r;
- } else if (r < 0 && r != -ENODATA)
- return r;
- }
-
- if (link->network->dhcp_use_sip) {
- r = sd_dhcp_lease_get_sip(link->dhcp_lease, &addresses);
- if (r > 0) {
- r = ordered_set_put_in4_addrv(sip, addresses, r, in4_addr_is_non_local);
- if (r < 0)
- return r;
- } else if (r < 0 && r != -ENODATA)
- return r;
- }
-
- if (link->network->dhcp_use_domains != DHCP_USE_DOMAINS_NO) {
- const char *domainname;
- char **domains = NULL;
-
- OrderedSet *target_domains = (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES) ? search_domains : route_domains;
- r = sd_dhcp_lease_get_domainname(link->dhcp_lease, &domainname);
- if (r >= 0) {
- r = ordered_set_put_strdup(target_domains, domainname);
- if (r < 0)
- return r;
- } else if (r != -ENODATA)
- return r;
-
- r = sd_dhcp_lease_get_search_domains(link->dhcp_lease, &domains);
- if (r >= 0) {
- r = ordered_set_put_strdupv(target_domains, domains);
- if (r < 0)
- return r;
- } else if (r != -ENODATA)
- return r;
- }
- }
-
- if (carrier_state >= LINK_CARRIER_STATE_ENSLAVED)
- carrier_state = LINK_CARRIER_STATE_CARRIER;
-
- operstate_str = link_operstate_to_string(operstate);
- assert(operstate_str);
-
- carrier_state_str = link_carrier_state_to_string(carrier_state);
- assert(carrier_state_str);
-
- address_state_str = link_address_state_to_string(address_state);
- assert(address_state_str);
-
- r = fopen_temporary(m->state_file, &f, &temp_path);
- if (r < 0)
- return r;
-
- (void) fchmod(fileno(f), 0644);
-
- fprintf(f,
- "# This is private data. Do not parse.\n"
- "OPER_STATE=%s\n"
- "CARRIER_STATE=%s\n"
- "ADDRESS_STATE=%s\n",
- operstate_str, carrier_state_str, address_state_str);
-
- ordered_set_print(f, "DNS=", dns);
- ordered_set_print(f, "NTP=", ntp);
- ordered_set_print(f, "SIP=", sip);
- ordered_set_print(f, "DOMAINS=", search_domains);
- ordered_set_print(f, "ROUTE_DOMAINS=", route_domains);
-
- r = fflush_and_check(f);
- if (r < 0)
- goto fail;
-
- r = conservative_rename(temp_path, m->state_file);
- if (r < 0)
- goto fail;
-
- if (m->operational_state != operstate) {
- m->operational_state = operstate;
- if (strv_extend(&p, "OperationalState") < 0)
- log_oom();
- }
-
- if (m->carrier_state != carrier_state) {
- m->carrier_state = carrier_state;
- if (strv_extend(&p, "CarrierState") < 0)
- log_oom();
- }
-
- if (m->address_state != address_state) {
- m->address_state = address_state;
- if (strv_extend(&p, "AddressState") < 0)
- log_oom();
- }
-
- if (p) {
- r = manager_send_changed_strv(m, p);
- if (r < 0)
- log_error_errno(r, "Could not emit changed properties: %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;
@@ -1155,13 +873,6 @@ 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;
-}
-
static int set_hostname_handler(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
const sd_bus_error *e;
int r;
diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h
index c0cd427517..929855daaf 100644
--- a/src/network/networkd-manager.h
+++ b/src/network/networkd-manager.h
@@ -100,8 +100,6 @@ bool manager_should_reload(Manager *m);
int manager_enumerate(Manager *m);
-void manager_dirty(Manager *m);
-
Link* manager_find_uplink(Manager *m, Link *exclude);
int manager_set_hostname(Manager *m, const char *hostname);
diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c
index acc1de66d2..bb62ddd93f 100644
--- a/src/network/networkd-ndisc.c
+++ b/src/network/networkd-ndisc.c
@@ -15,6 +15,7 @@
#include "networkd-dhcp6.h"
#include "networkd-manager.h"
#include "networkd-ndisc.h"
+#include "networkd-state-file.h"
#include "string-table.h"
#include "string-util.h"
#include "strv.h"
diff --git a/src/network/networkd-state-file.c b/src/network/networkd-state-file.c
new file mode 100644
index 0000000000..7cf1948d03
--- /dev/null
+++ b/src/network/networkd-state-file.c
@@ -0,0 +1,690 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <netinet/in.h>
+#include <linux/if.h>
+
+#include "alloc-util.h"
+#include "dns-domain.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fs-util.h"
+#include "network-internal.h"
+#include "networkd-link.h"
+#include "networkd-manager-bus.h"
+#include "networkd-manager.h"
+#include "networkd-network.h"
+#include "networkd-state-file.h"
+#include "ordered-set.h"
+#include "set.h"
+#include "strv.h"
+#include "tmpfile-util.h"
+
+static int ordered_set_put_dns_server(OrderedSet *s, int ifindex, struct in_addr_full *dns) {
+ const char *p;
+ int r;
+
+ assert(s);
+ assert(dns);
+
+ if (dns->ifindex != 0 && dns->ifindex != ifindex)
+ return 0;
+
+ p = in_addr_full_to_string(dns);
+ if (!p)
+ return 0;
+
+ r = ordered_set_put_strdup(s, p);
+ if (r == -EEXIST)
+ return 0;
+
+ return r;
+}
+
+static int ordered_set_put_dns_servers(OrderedSet *s, int ifindex, struct in_addr_full **dns, unsigned n) {
+ int r, c = 0;
+
+ assert(s);
+ assert(dns || n == 0);
+
+ for (unsigned i = 0; i < n; i++) {
+ r = ordered_set_put_dns_server(s, ifindex, dns[i]);
+ if (r < 0)
+ return r;
+
+ c += r;
+ }
+
+ return c;
+}
+
+static int ordered_set_put_in4_addr(OrderedSet *s, const struct in_addr *address) {
+ char *p;
+ int r;
+
+ assert(s);
+ assert(address);
+
+ r = in_addr_to_string(AF_INET, (const union in_addr_union*) address, &p);
+ if (r < 0)
+ return r;
+
+ r = ordered_set_consume(s, p);
+ if (r == -EEXIST)
+ return 0;
+
+ return r;
+}
+
+static int ordered_set_put_in4_addrv(OrderedSet *s,
+ const struct in_addr *addresses,
+ size_t n,
+ bool (*predicate)(const struct in_addr *addr)) {
+ int r, c = 0;
+
+ assert(s);
+ assert(n == 0 || addresses);
+
+ for (size_t i = 0; i < n; i++) {
+ if (predicate && !predicate(&addresses[i]))
+ continue;
+ r = ordered_set_put_in4_addr(s, addresses+i);
+ if (r < 0)
+ return r;
+
+ c += r;
+ }
+
+ return c;
+}
+
+int manager_save(Manager *m) {
+ _cleanup_ordered_set_free_free_ OrderedSet *dns = NULL, *ntp = NULL, *sip = NULL, *search_domains = NULL, *route_domains = NULL;
+ const char *operstate_str, *carrier_state_str, *address_state_str;
+ LinkOperationalState operstate = LINK_OPERSTATE_OFF;
+ LinkCarrierState carrier_state = LINK_CARRIER_STATE_OFF;
+ LinkAddressState address_state = LINK_ADDRESS_STATE_OFF;
+ _cleanup_free_ char *temp_path = NULL;
+ _cleanup_strv_free_ char **p = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ Link *link;
+ int r;
+
+ assert(m);
+ assert(m->state_file);
+
+ /* We add all NTP and DNS server to a set, to filter out duplicates */
+ dns = ordered_set_new(&string_hash_ops);
+ if (!dns)
+ return -ENOMEM;
+
+ ntp = ordered_set_new(&string_hash_ops);
+ if (!ntp)
+ return -ENOMEM;
+
+ sip = ordered_set_new(&string_hash_ops);
+ if (!sip)
+ return -ENOMEM;
+
+ search_domains = ordered_set_new(&dns_name_hash_ops);
+ if (!search_domains)
+ return -ENOMEM;
+
+ route_domains = ordered_set_new(&dns_name_hash_ops);
+ if (!route_domains)
+ return -ENOMEM;
+
+ HASHMAP_FOREACH(link, m->links) {
+ const struct in_addr *addresses;
+
+ if (link->flags & IFF_LOOPBACK)
+ continue;
+
+ if (link->operstate > operstate)
+ operstate = link->operstate;
+
+ if (link->carrier_state > carrier_state)
+ carrier_state = link->carrier_state;
+
+ if (link->address_state > address_state)
+ address_state = link->address_state;
+
+ if (!link->network)
+ continue;
+
+ /* First add the static configured entries */
+ if (link->n_dns != (unsigned) -1)
+ r = ordered_set_put_dns_servers(dns, link->ifindex, link->dns, link->n_dns);
+ else
+ r = ordered_set_put_dns_servers(dns, link->ifindex, link->network->dns, link->network->n_dns);
+ if (r < 0)
+ return r;
+
+ r = ordered_set_put_strdupv(ntp, link->ntp ?: link->network->ntp);
+ if (r < 0)
+ return r;
+
+ r = ordered_set_put_string_set(search_domains, link->search_domains ?: link->network->search_domains);
+ if (r < 0)
+ return r;
+
+ r = ordered_set_put_string_set(route_domains, link->route_domains ?: link->network->route_domains);
+ if (r < 0)
+ return r;
+
+ if (!link->dhcp_lease)
+ continue;
+
+ /* Secondly, add the entries acquired via DHCP */
+ if (link->network->dhcp_use_dns) {
+ r = sd_dhcp_lease_get_dns(link->dhcp_lease, &addresses);
+ if (r > 0) {
+ r = ordered_set_put_in4_addrv(dns, addresses, r, in4_addr_is_non_local);
+ if (r < 0)
+ return r;
+ } else if (r < 0 && r != -ENODATA)
+ return r;
+ }
+
+ if (link->network->dhcp_use_ntp) {
+ r = sd_dhcp_lease_get_ntp(link->dhcp_lease, &addresses);
+ if (r > 0) {
+ r = ordered_set_put_in4_addrv(ntp, addresses, r, in4_addr_is_non_local);
+ if (r < 0)
+ return r;
+ } else if (r < 0 && r != -ENODATA)
+ return r;
+ }
+
+ if (link->network->dhcp_use_sip) {
+ r = sd_dhcp_lease_get_sip(link->dhcp_lease, &addresses);
+ if (r > 0) {
+ r = ordered_set_put_in4_addrv(sip, addresses, r, in4_addr_is_non_local);
+ if (r < 0)
+ return r;
+ } else if (r < 0 && r != -ENODATA)
+ return r;
+ }
+
+ if (link->network->dhcp_use_domains != DHCP_USE_DOMAINS_NO) {
+ const char *domainname;
+ char **domains = NULL;
+
+ OrderedSet *target_domains = (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES) ? search_domains : route_domains;
+ r = sd_dhcp_lease_get_domainname(link->dhcp_lease, &domainname);
+ if (r >= 0) {
+ r = ordered_set_put_strdup(target_domains, domainname);
+ if (r < 0)
+ return r;
+ } else if (r != -ENODATA)
+ return r;
+
+ r = sd_dhcp_lease_get_search_domains(link->dhcp_lease, &domains);
+ if (r >= 0) {
+ r = ordered_set_put_strdupv(target_domains, domains);
+ if (r < 0)
+ return r;
+ } else if (r != -ENODATA)
+ return r;
+ }
+ }
+
+ if (carrier_state >= LINK_CARRIER_STATE_ENSLAVED)
+ carrier_state = LINK_CARRIER_STATE_CARRIER;
+
+ operstate_str = link_operstate_to_string(operstate);
+ assert(operstate_str);
+
+ carrier_state_str = link_carrier_state_to_string(carrier_state);
+ assert(carrier_state_str);
+
+ address_state_str = link_address_state_to_string(address_state);
+ assert(address_state_str);
+
+ r = fopen_temporary(m->state_file, &f, &temp_path);
+ if (r < 0)
+ return r;
+
+ (void) fchmod(fileno(f), 0644);
+
+ fprintf(f,
+ "# This is private data. Do not parse.\n"
+ "OPER_STATE=%s\n"
+ "CARRIER_STATE=%s\n"
+ "ADDRESS_STATE=%s\n",
+ operstate_str, carrier_state_str, address_state_str);
+
+ ordered_set_print(f, "DNS=", dns);
+ ordered_set_print(f, "NTP=", ntp);
+ ordered_set_print(f, "SIP=", sip);
+ ordered_set_print(f, "DOMAINS=", search_domains);
+ ordered_set_print(f, "ROUTE_DOMAINS=", route_domains);
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ goto fail;
+
+ r = conservative_rename(temp_path, m->state_file);
+ if (r < 0)
+ goto fail;
+
+ if (m->operational_state != operstate) {
+ m->operational_state = operstate;
+ if (strv_extend(&p, "OperationalState") < 0)
+ log_oom();
+ }
+
+ if (m->carrier_state != carrier_state) {
+ m->carrier_state = carrier_state;
+ if (strv_extend(&p, "CarrierState") < 0)
+ log_oom();
+ }
+
+ if (m->address_state != address_state) {
+ m->address_state = address_state;
+ if (strv_extend(&p, "AddressState") < 0)
+ log_oom();
+ }
+
+ if (p) {
+ r = manager_send_changed_strv(m, p);
+ if (r < 0)
+ log_error_errno(r, "Could not emit changed properties: %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 void print_link_hashmap(FILE *f, const char *prefix, Hashmap* h) {
+ bool space = false;
+ Link *link;
+
+ assert(f);
+ assert(prefix);
+
+ if (hashmap_isempty(h))
+ return;
+
+ fputs(prefix, f);
+ HASHMAP_FOREACH(link, h) {
+ if (space)
+ fputc(' ', f);
+
+ fprintf(f, "%i", link->ifindex);
+ space = true;
+ }
+
+ fputc('\n', f);
+}
+
+static void link_save_dns(Link *link, FILE *f, struct in_addr_full **dns, unsigned n_dns, bool *space) {
+ for (unsigned j = 0; j < n_dns; j++) {
+ const char *str;
+
+ if (dns[j]->ifindex != 0 && dns[j]->ifindex != link->ifindex)
+ continue;
+
+ str = in_addr_full_to_string(dns[j]);
+ if (!str)
+ continue;
+
+ if (*space)
+ fputc(' ', f);
+ fputs(str, f);
+ *space = true;
+ }
+}
+
+static void serialize_addresses(
+ FILE *f,
+ const char *lvalue,
+ bool *space,
+ char **addresses,
+ sd_dhcp_lease *lease,
+ bool conditional,
+ sd_dhcp_lease_server_type_t what,
+ sd_dhcp6_lease *lease6,
+ bool conditional6,
+ int (*lease6_get_addr)(sd_dhcp6_lease*, const struct in6_addr**),
+ int (*lease6_get_fqdn)(sd_dhcp6_lease*, char ***)) {
+ int r;
+
+ bool _space = false;
+ if (!space)
+ space = &_space;
+
+ if (lvalue)
+ fprintf(f, "%s=", lvalue);
+ fputstrv(f, addresses, NULL, space);
+
+ if (lease && conditional) {
+ const struct in_addr *lease_addresses;
+
+ r = sd_dhcp_lease_get_servers(lease, what, &lease_addresses);
+ if (r > 0)
+ serialize_in_addrs(f, lease_addresses, r, space, in4_addr_is_non_local);
+ }
+
+ if (lease6 && conditional6 && lease6_get_addr) {
+ const struct in6_addr *in6_addrs;
+
+ r = lease6_get_addr(lease6, &in6_addrs);
+ if (r > 0)
+ serialize_in6_addrs(f, in6_addrs, r, space);
+ }
+
+ if (lease6 && conditional6 && lease6_get_fqdn) {
+ char **in6_hosts;
+
+ r = lease6_get_fqdn(lease6, &in6_hosts);
+ if (r > 0)
+ fputstrv(f, in6_hosts, NULL, space);
+ }
+
+ if (lvalue)
+ fputc('\n', f);
+}
+
+int link_save(Link *link) {
+ const char *admin_state, *oper_state, *carrier_state, *address_state;
+ _cleanup_free_ char *temp_path = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ assert(link);
+ assert(link->state_file);
+ assert(link->lease_file);
+ assert(link->manager);
+
+ if (link->state == LINK_STATE_LINGER) {
+ (void) unlink(link->state_file);
+ return 0;
+ }
+
+ link_lldp_save(link);
+
+ admin_state = link_state_to_string(link->state);
+ assert(admin_state);
+
+ oper_state = link_operstate_to_string(link->operstate);
+ assert(oper_state);
+
+ carrier_state = link_carrier_state_to_string(link->carrier_state);
+ assert(carrier_state);
+
+ address_state = link_address_state_to_string(link->address_state);
+ assert(address_state);
+
+ r = fopen_temporary(link->state_file, &f, &temp_path);
+ if (r < 0)
+ goto fail;
+
+ (void) fchmod(fileno(f), 0644);
+
+ fprintf(f,
+ "# This is private data. Do not parse.\n"
+ "ADMIN_STATE=%s\n"
+ "OPER_STATE=%s\n"
+ "CARRIER_STATE=%s\n"
+ "ADDRESS_STATE=%s\n",
+ admin_state, oper_state, carrier_state, address_state);
+
+ if (link->network) {
+ char **dhcp6_domains = NULL, **dhcp_domains = NULL;
+ const char *dhcp_domainname = NULL, *p;
+ bool space;
+
+ fprintf(f, "REQUIRED_FOR_ONLINE=%s\n",
+ yes_no(link->network->required_for_online));
+
+ LinkOperationalStateRange st = link->network->required_operstate_for_online;
+ fprintf(f, "REQUIRED_OPER_STATE_FOR_ONLINE=%s%s%s\n",
+ strempty(link_operstate_to_string(st.min)),
+ st.max != LINK_OPERSTATE_RANGE_DEFAULT.max ? ":" : "",
+ st.max != LINK_OPERSTATE_RANGE_DEFAULT.max ? strempty(link_operstate_to_string(st.max)) : "");
+
+ fprintf(f, "ACTIVATION_POLICY=%s\n",
+ activation_policy_to_string(link->network->activation_policy));
+
+ fprintf(f, "NETWORK_FILE=%s\n", link->network->filename);
+
+ /************************************************************/
+
+ fputs("DNS=", f);
+ space = false;
+ if (link->n_dns != (unsigned) -1)
+ link_save_dns(link, f, link->dns, link->n_dns, &space);
+ else
+ link_save_dns(link, f, link->network->dns, link->network->n_dns, &space);
+
+ serialize_addresses(f, NULL, &space,
+ NULL,
+ link->dhcp_lease,
+ link->network->dhcp_use_dns,
+ SD_DHCP_LEASE_DNS,
+ link->dhcp6_lease,
+ link->network->dhcp6_use_dns,
+ sd_dhcp6_lease_get_dns,
+ NULL);
+
+ /* Make sure to flush out old entries before we use the NDisc data */
+ ndisc_vacuum(link);
+
+ if (link->network->ipv6_accept_ra_use_dns && link->ndisc_rdnss) {
+ NDiscRDNSS *dd;
+
+ SET_FOREACH(dd, link->ndisc_rdnss)
+ serialize_in6_addrs(f, &dd->address, 1, &space);
+ }
+
+ fputc('\n', f);
+
+ /************************************************************/
+
+ serialize_addresses(f, "NTP", NULL,
+ link->ntp ?: link->network->ntp,
+ link->dhcp_lease,
+ link->network->dhcp_use_ntp,
+ SD_DHCP_LEASE_NTP,
+ link->dhcp6_lease,
+ link->network->dhcp6_use_ntp,
+ sd_dhcp6_lease_get_ntp_addrs,
+ sd_dhcp6_lease_get_ntp_fqdn);
+
+ serialize_addresses(f, "SIP", NULL,
+ NULL,
+ link->dhcp_lease,
+ link->network->dhcp_use_sip,
+ SD_DHCP_LEASE_SIP,
+ NULL, false, NULL, NULL);
+
+ /************************************************************/
+
+ if (link->network->dhcp_use_domains != DHCP_USE_DOMAINS_NO) {
+ if (link->dhcp_lease) {
+ (void) sd_dhcp_lease_get_domainname(link->dhcp_lease, &dhcp_domainname);
+ (void) sd_dhcp_lease_get_search_domains(link->dhcp_lease, &dhcp_domains);
+ }
+ if (link->dhcp6_lease)
+ (void) sd_dhcp6_lease_get_domains(link->dhcp6_lease, &dhcp6_domains);
+ }
+
+ fputs("DOMAINS=", f);
+ space = false;
+ ORDERED_SET_FOREACH(p, link->search_domains ?: link->network->search_domains)
+ fputs_with_space(f, p, NULL, &space);
+
+ if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES) {
+ if (dhcp_domainname)
+ fputs_with_space(f, dhcp_domainname, NULL, &space);
+ if (dhcp_domains)
+ fputstrv(f, dhcp_domains, NULL, &space);
+ if (dhcp6_domains)
+ fputstrv(f, dhcp6_domains, NULL, &space);
+ }
+
+ if (link->network->ipv6_accept_ra_use_domains == DHCP_USE_DOMAINS_YES) {
+ NDiscDNSSL *dd;
+
+ SET_FOREACH(dd, link->ndisc_dnssl)
+ fputs_with_space(f, NDISC_DNSSL_DOMAIN(dd), NULL, &space);
+ }
+
+ fputc('\n', f);
+
+ /************************************************************/
+
+ fputs("ROUTE_DOMAINS=", f);
+ space = false;
+ ORDERED_SET_FOREACH(p, link->route_domains ?: link->network->route_domains)
+ fputs_with_space(f, p, NULL, &space);
+
+ if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_ROUTE) {
+ if (dhcp_domainname)
+ fputs_with_space(f, dhcp_domainname, NULL, &space);
+ if (dhcp_domains)
+ fputstrv(f, dhcp_domains, NULL, &space);
+ if (dhcp6_domains)
+ fputstrv(f, dhcp6_domains, NULL, &space);
+ }
+
+ if (link->network->ipv6_accept_ra_use_domains == DHCP_USE_DOMAINS_ROUTE) {
+ NDiscDNSSL *dd;
+
+ SET_FOREACH(dd, link->ndisc_dnssl)
+ fputs_with_space(f, NDISC_DNSSL_DOMAIN(dd), NULL, &space);
+ }
+
+ fputc('\n', f);
+
+ /************************************************************/
+
+ fprintf(f, "LLMNR=%s\n",
+ resolve_support_to_string(link->llmnr >= 0 ? link->llmnr : link->network->llmnr));
+
+ /************************************************************/
+
+ fprintf(f, "MDNS=%s\n",
+ resolve_support_to_string(link->mdns >= 0 ? link->mdns : link->network->mdns));
+
+ /************************************************************/
+
+ int dns_default_route =
+ link->dns_default_route >= 0 ? link->dns_default_route :
+ link->network->dns_default_route;
+ if (dns_default_route >= 0)
+ fprintf(f, "DNS_DEFAULT_ROUTE=%s\n", yes_no(dns_default_route));
+
+ /************************************************************/
+
+ DnsOverTlsMode dns_over_tls_mode =
+ link->dns_over_tls_mode != _DNS_OVER_TLS_MODE_INVALID ? link->dns_over_tls_mode :
+ link->network->dns_over_tls_mode;
+ if (dns_over_tls_mode != _DNS_OVER_TLS_MODE_INVALID)
+ fprintf(f, "DNS_OVER_TLS=%s\n", dns_over_tls_mode_to_string(dns_over_tls_mode));
+
+ /************************************************************/
+
+ DnssecMode dnssec_mode =
+ link->dnssec_mode != _DNSSEC_MODE_INVALID ? link->dnssec_mode :
+ link->network->dnssec_mode;
+ if (dnssec_mode != _DNSSEC_MODE_INVALID)
+ fprintf(f, "DNSSEC=%s\n", dnssec_mode_to_string(dnssec_mode));
+
+ /************************************************************/
+
+ Set *nta_anchors = link->dnssec_negative_trust_anchors;
+ if (set_isempty(nta_anchors))
+ nta_anchors = link->network->dnssec_negative_trust_anchors;
+
+ if (!set_isempty(nta_anchors)) {
+ const char *n;
+
+ fputs("DNSSEC_NTA=", f);
+ space = false;
+ SET_FOREACH(n, nta_anchors)
+ fputs_with_space(f, n, NULL, &space);
+ fputc('\n', f);
+ }
+ }
+
+ print_link_hashmap(f, "CARRIER_BOUND_TO=", link->bound_to_links);
+ print_link_hashmap(f, "CARRIER_BOUND_BY=", link->bound_by_links);
+
+ if (link->dhcp_lease) {
+ r = dhcp_lease_save(link->dhcp_lease, link->lease_file);
+ if (r < 0)
+ goto fail;
+
+ fprintf(f,
+ "DHCP_LEASE=%s\n",
+ link->lease_file);
+ } else
+ (void) unlink(link->lease_file);
+
+ r = link_serialize_dhcp6_client(link, f);
+ if (r < 0)
+ goto fail;
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ goto fail;
+
+ r = conservative_rename(temp_path, link->state_file);
+ if (r < 0)
+ goto fail;
+
+ return 0;
+
+fail:
+ (void) unlink(link->state_file);
+ if (temp_path)
+ (void) unlink(temp_path);
+
+ return log_link_error_errno(link, r, "Failed to save link data to %s: %m", link->state_file);
+}
+
+void link_dirty(Link *link) {
+ int r;
+
+ assert(link);
+ assert(link->manager);
+
+ /* The serialized state in /run is no longer up-to-date. */
+
+ /* Also mark manager dirty as link is dirty */
+ link->manager->dirty = true;
+
+ r = set_ensure_put(&link->manager->dirty_links, NULL, link);
+ if (r <= 0)
+ /* Ignore allocation errors and don't take another ref if the link was already dirty */
+ return;
+ link_ref(link);
+}
+
+void link_clean(Link *link) {
+ assert(link);
+ assert(link->manager);
+
+ /* The serialized state in /run is up-to-date */
+
+ link_unref(set_remove(link->manager->dirty_links, link));
+}
+
+int link_save_and_clean(Link *link) {
+ int r;
+
+ r = link_save(link);
+ if (r < 0)
+ return r;
+
+ link_clean(link);
+ return 0;
+}
diff --git a/src/network/networkd-state-file.h b/src/network/networkd-state-file.h
new file mode 100644
index 0000000000..44a109fdfd
--- /dev/null
+++ b/src/network/networkd-state-file.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+typedef struct Link Link;
+typedef struct Manager Manager;
+
+void link_dirty(Link *link);
+void link_clean(Link *link);
+int link_save(Link *link);
+int link_save_and_clean(Link *link);
+
+int manager_save(Manager *m);