diff options
Diffstat (limited to 'src/network/networkd-state-file.c')
-rw-r--r-- | src/network/networkd-state-file.c | 863 |
1 files changed, 863 insertions, 0 deletions
diff --git a/src/network/networkd-state-file.c b/src/network/networkd-state-file.c new file mode 100644 index 0000000..3a95ba8 --- /dev/null +++ b/src/network/networkd-state-file.c @@ -0,0 +1,863 @@ +/* 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 "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "network-internal.h" +#include "networkd-dhcp-common.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_servers(OrderedSet **s, int ifindex, struct in_addr_full **dns, unsigned n) { + int r; + + assert(s); + assert(dns || n == 0); + + FOREACH_ARRAY(a, dns, n) { + const char *p; + + if ((*a)->ifindex != 0 && (*a)->ifindex != ifindex) + return 0; + + p = in_addr_full_to_string(*a); + if (!p) + return 0; + + r = ordered_set_put_strdup(s, p); + if (r < 0) + return r; + } + + return 0; +} + +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; + + assert(s); + assert(n == 0 || addresses); + + FOREACH_ARRAY(a, addresses, n) { + if (predicate && !predicate(a)) + continue; + + r = ordered_set_put_strdup(s, IN4_ADDR_TO_STRING(a)); + if (r < 0) + return r; + } + + return 0; +} + +static int ordered_set_put_in6_addrv( + OrderedSet **s, + const struct in6_addr *addresses, + size_t n) { + + int r; + + assert(s); + assert(n == 0 || addresses); + + FOREACH_ARRAY(a, addresses, n) { + r = ordered_set_put_strdup(s, IN6_ADDR_TO_STRING(a)); + if (r < 0) + return r; + } + + return 0; +} + +static int link_put_dns(Link *link, OrderedSet **s) { + int r; + + assert(link); + assert(link->network); + assert(s); + + if (link->n_dns != UINT_MAX) + return ordered_set_put_dns_servers(s, link->ifindex, link->dns, link->n_dns); + + r = ordered_set_put_dns_servers(s, link->ifindex, link->network->dns, link->network->n_dns); + if (r < 0) + return r; + + if (link->dhcp_lease && link->network->dhcp_use_dns) { + const struct in_addr *addresses; + + r = sd_dhcp_lease_get_dns(link->dhcp_lease, &addresses); + if (r >= 0) { + r = ordered_set_put_in4_addrv(s, addresses, r, in4_addr_is_non_local); + if (r < 0) + return r; + } + } + + if (link->dhcp6_lease && link->network->dhcp6_use_dns) { + const struct in6_addr *addresses; + + r = sd_dhcp6_lease_get_dns(link->dhcp6_lease, &addresses); + if (r >= 0) { + r = ordered_set_put_in6_addrv(s, addresses, r); + if (r < 0) + return r; + } + } + + if (link->network->ipv6_accept_ra_use_dns) { + NDiscRDNSS *a; + + SET_FOREACH(a, link->ndisc_rdnss) { + r = ordered_set_put_in6_addrv(s, &a->router, 1); + if (r < 0) + return r; + } + } + + return 0; +} + +static int link_put_ntp(Link *link, OrderedSet **s) { + int r; + + assert(link); + assert(link->network); + assert(s); + + if (link->ntp) + return ordered_set_put_strdupv(s, link->ntp); + + r = ordered_set_put_strdupv(s, link->network->ntp); + if (r < 0) + return r; + + if (link->dhcp_lease && link->network->dhcp_use_ntp) { + const struct in_addr *addresses; + + r = sd_dhcp_lease_get_ntp(link->dhcp_lease, &addresses); + if (r >= 0) { + r = ordered_set_put_in4_addrv(s, addresses, r, in4_addr_is_non_local); + if (r < 0) + return r; + } + } + + if (link->dhcp6_lease && link->network->dhcp6_use_ntp) { + const struct in6_addr *addresses; + char **fqdn; + + r = sd_dhcp6_lease_get_ntp_addrs(link->dhcp6_lease, &addresses); + if (r >= 0) { + r = ordered_set_put_in6_addrv(s, addresses, r); + if (r < 0) + return r; + } + + r = sd_dhcp6_lease_get_ntp_fqdn(link->dhcp6_lease, &fqdn); + if (r >= 0) { + r = ordered_set_put_strdupv(s, fqdn); + if (r < 0) + return r; + } + } + + return 0; +} + +static int link_put_sip(Link *link, OrderedSet **s) { + int r; + + assert(link); + assert(link->network); + assert(s); + + if (link->dhcp_lease && link->network->dhcp_use_ntp) { + const struct in_addr *addresses; + + r = sd_dhcp_lease_get_sip(link->dhcp_lease, &addresses); + if (r >= 0) { + r = ordered_set_put_in4_addrv(s, addresses, r, in4_addr_is_non_local); + if (r < 0) + return r; + } + } + + return 0; +} + +static int link_put_domains(Link *link, bool is_route, OrderedSet **s) { + OrderedSet *link_domains, *network_domains; + DHCPUseDomains use_domains; + int r; + + assert(link); + assert(link->network); + assert(s); + + link_domains = is_route ? link->route_domains : link->search_domains; + network_domains = is_route ? link->network->route_domains : link->network->search_domains; + use_domains = is_route ? DHCP_USE_DOMAINS_ROUTE : DHCP_USE_DOMAINS_YES; + + if (link_domains) + return ordered_set_put_string_set(s, link_domains); + + r = ordered_set_put_string_set(s, network_domains); + if (r < 0) + return r; + + if (link->dhcp_lease && link->network->dhcp_use_domains == use_domains) { + const char *domainname; + char **domains; + + r = sd_dhcp_lease_get_domainname(link->dhcp_lease, &domainname); + if (r >= 0) { + r = ordered_set_put_strdup(s, domainname); + if (r < 0) + return r; + } + + r = sd_dhcp_lease_get_search_domains(link->dhcp_lease, &domains); + if (r >= 0) { + r = ordered_set_put_strdupv(s, domains); + if (r < 0) + return r; + } + } + + if (link->dhcp6_lease && link->network->dhcp6_use_domains == use_domains) { + char **domains; + + r = sd_dhcp6_lease_get_domains(link->dhcp6_lease, &domains); + if (r >= 0) { + r = ordered_set_put_strdupv(s, domains); + if (r < 0) + return r; + } + } + + if (link->network->ipv6_accept_ra_use_domains == use_domains) { + NDiscDNSSL *a; + + SET_FOREACH(a, link->ndisc_dnssl) { + r = ordered_set_put_strdup(s, NDISC_DNSSL_DOMAIN(a)); + if (r < 0) + return r; + } + } + + return 0; +} + +int manager_save(Manager *m) { + _cleanup_ordered_set_free_ OrderedSet *dns = NULL, *ntp = NULL, *sip = NULL, *search_domains = NULL, *route_domains = NULL; + const char *operstate_str, *carrier_state_str, *address_state_str, *ipv4_address_state_str, *ipv6_address_state_str, *online_state_str; + LinkOperationalState operstate = LINK_OPERSTATE_OFF; + LinkCarrierState carrier_state = LINK_CARRIER_STATE_OFF; + LinkAddressState ipv4_address_state = LINK_ADDRESS_STATE_OFF, ipv6_address_state = LINK_ADDRESS_STATE_OFF, + address_state = LINK_ADDRESS_STATE_OFF; + LinkOnlineState online_state; + size_t links_offline = 0, links_online = 0; + _cleanup_(unlink_and_freep) char *temp_path = NULL; + _cleanup_strv_free_ char **p = NULL; + _cleanup_fclose_ FILE *f = NULL; + Link *link; + int r; + + assert(m); + + if (isempty(m->state_file)) + return 0; /* Do not update state file when running in test mode. */ + + HASHMAP_FOREACH(link, m->links_by_index) { + if (link->flags & IFF_LOOPBACK) + continue; + + operstate = MAX(operstate, link->operstate); + carrier_state = MAX(carrier_state, link->carrier_state); + address_state = MAX(address_state, link->address_state); + ipv4_address_state = MAX(ipv4_address_state, link->ipv4_address_state); + ipv6_address_state = MAX(ipv6_address_state, link->ipv6_address_state); + + if (!link->network) + continue; + + if (link->network->required_for_online) { + if (link->online_state == LINK_ONLINE_STATE_OFFLINE) + links_offline++; + else if (link->online_state == LINK_ONLINE_STATE_ONLINE) + links_online++; + } + + r = link_put_dns(link, &dns); + if (r < 0) + return r; + + r = link_put_ntp(link, &ntp); + if (r < 0) + return r; + + r = link_put_sip(link, &sip); + if (r < 0) + return r; + + r = link_put_domains(link, /* is_route = */ false, &search_domains); + if (r < 0) + return r; + + r = link_put_domains(link, /* is_route = */ true, &route_domains); + if (r < 0) + return r; + } + + if (carrier_state >= LINK_CARRIER_STATE_ENSLAVED) + carrier_state = LINK_CARRIER_STATE_CARRIER; + + online_state = links_online > 0 ? + (links_offline > 0 ? LINK_ONLINE_STATE_PARTIAL : LINK_ONLINE_STATE_ONLINE) : + (links_offline > 0 ? LINK_ONLINE_STATE_OFFLINE : _LINK_ONLINE_STATE_INVALID); + + 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); + + ipv4_address_state_str = link_address_state_to_string(ipv4_address_state); + assert(ipv4_address_state_str); + + ipv6_address_state_str = link_address_state_to_string(ipv6_address_state); + assert(ipv6_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" + "IPV4_ADDRESS_STATE=%s\n" + "IPV6_ADDRESS_STATE=%s\n", + operstate_str, carrier_state_str, address_state_str, ipv4_address_state_str, ipv6_address_state_str); + + online_state_str = link_online_state_to_string(online_state); + if (online_state_str) + fprintf(f, "ONLINE_STATE=%s\n", online_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) + return r; + + r = conservative_rename(temp_path, m->state_file); + if (r < 0) + return r; + + temp_path = mfree(temp_path); + + 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 (m->ipv4_address_state != ipv4_address_state) { + m->ipv4_address_state = ipv4_address_state; + if (strv_extend(&p, "IPv4AddressState") < 0) + log_oom(); + } + + if (m->ipv6_address_state != ipv6_address_state) { + m->ipv6_address_state = ipv6_address_state; + if (strv_extend(&p, "IPv6AddressState") < 0) + log_oom(); + } + + if (m->online_state != online_state) { + m->online_state = online_state; + if (strv_extend(&p, "OnlineState") < 0) + log_oom(); + } + + if (p) { + r = manager_send_changed_strv(m, p); + if (r < 0) + log_warning_errno(r, "Could not emit changed properties, ignoring: %m"); + } + + m->dirty = false; + + 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) { + bool _space = false; + + if (!space) + space = &_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 ***)) { + + bool _space = false; + int r; + + 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); +} + +static void link_save_domains(Link *link, FILE *f, OrderedSet *static_domains, DHCPUseDomains use_domains) { + bool space = false; + const char *p; + + assert(link); + assert(link->network); + assert(f); + + ORDERED_SET_FOREACH(p, static_domains) + fputs_with_space(f, p, NULL, &space); + + if (use_domains == DHCP_USE_DOMAINS_NO) + return; + + if (link->dhcp_lease && link->network->dhcp_use_domains == use_domains) { + const char *domainname; + char **domains; + + if (sd_dhcp_lease_get_domainname(link->dhcp_lease, &domainname) >= 0) + fputs_with_space(f, domainname, NULL, &space); + if (sd_dhcp_lease_get_search_domains(link->dhcp_lease, &domains) >= 0) + fputstrv(f, domains, NULL, &space); + } + + if (link->dhcp6_lease && link->network->dhcp6_use_domains == use_domains) { + char **domains; + + if (sd_dhcp6_lease_get_domains(link->dhcp6_lease, &domains) >= 0) + fputstrv(f, domains, NULL, &space); + } + + if (link->network->ipv6_accept_ra_use_domains == use_domains) { + NDiscDNSSL *dd; + + SET_FOREACH(dd, link->ndisc_dnssl) + fputs_with_space(f, NDISC_DNSSL_DOMAIN(dd), NULL, &space); + } +} + +static int link_save(Link *link) { + const char *admin_state, *oper_state, *carrier_state, *address_state, *ipv4_address_state, *ipv6_address_state, + *captive_portal; + _cleanup_(unlink_and_freep) char *temp_path = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(link); + assert(link->manager); + + if (isempty(link->state_file)) + return 0; /* Do not update state files when running in test mode. */ + + if (link->state == LINK_STATE_LINGER) + 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); + + ipv4_address_state = link_address_state_to_string(link->ipv4_address_state); + assert(ipv4_address_state); + + ipv6_address_state = link_address_state_to_string(link->ipv6_address_state); + assert(ipv6_address_state); + + r = fopen_temporary(link->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" + "ADMIN_STATE=%s\n" + "OPER_STATE=%s\n" + "CARRIER_STATE=%s\n" + "ADDRESS_STATE=%s\n" + "IPV4_ADDRESS_STATE=%s\n" + "IPV6_ADDRESS_STATE=%s\n", + admin_state, oper_state, carrier_state, address_state, ipv4_address_state, ipv6_address_state); + + if (link->network) { + const char *online_state; + bool space = false; + + online_state = link_online_state_to_string(link->online_state); + if (online_state) + fprintf(f, "ONLINE_STATE=%s\n", online_state); + + 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, "REQUIRED_FAMILY_FOR_ONLINE=%s\n", + link_required_address_family_to_string(link->network->required_family_for_online)); + + fprintf(f, "ACTIVATION_POLICY=%s\n", + activation_policy_to_string(link->network->activation_policy)); + + fprintf(f, "NETWORK_FILE=%s\n", link->network->filename); + + fputs("NETWORK_FILE_DROPINS=\"", f); + STRV_FOREACH(d, link->network->dropins) { + _cleanup_free_ char *escaped = NULL; + + escaped = xescape(*d, ":"); + if (!escaped) + return -ENOMEM; + + fputs_with_space(f, escaped, ":", &space); + } + fputs("\"\n", f); + + /************************************************************/ + + fputs("DNS=", f); + if (link->n_dns != UINT_MAX) + link_save_dns(link, f, link->dns, link->n_dns, NULL); + else { + space = false; + 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); + + if (link->network->ipv6_accept_ra_use_dns) { + NDiscRDNSS *dd; + + SET_FOREACH(dd, link->ndisc_rdnss) + serialize_in6_addrs(f, &dd->address, 1, &space); + } + } + + fputc('\n', f); + + /************************************************************/ + + if (link->ntp) { + fputs("NTP=", f); + fputstrv(f, link->ntp, NULL, NULL); + fputc('\n', f); + } else + serialize_addresses(f, "NTP", NULL, + 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); + + /************************************************************/ + + r = link_get_captive_portal(link, &captive_portal); + if (r < 0) + return r; + + if (captive_portal) + fprintf(f, "CAPTIVE_PORTAL=%s\n", captive_portal); + + /************************************************************/ + + fputs("DOMAINS=", f); + if (link->search_domains) + link_save_domains(link, f, link->search_domains, DHCP_USE_DOMAINS_NO); + else + link_save_domains(link, f, link->network->search_domains, DHCP_USE_DOMAINS_YES); + fputc('\n', f); + + /************************************************************/ + + fputs("ROUTE_DOMAINS=", f); + if (link->route_domains) + link_save_domains(link, f, link->route_domains, DHCP_USE_DOMAINS_NO); + else + link_save_domains(link, f, link->network->route_domains, DHCP_USE_DOMAINS_ROUTE); + 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) + return r; + + 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) + return r; + + r = fflush_and_check(f); + if (r < 0) + return r; + + r = conservative_rename(temp_path, link->state_file); + if (r < 0) + return r; + + temp_path = mfree(temp_path); + + return 0; +} + +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_full(Link *link, bool also_save_manager) { + int r, k = 0; + + assert(link); + assert(link->manager); + + if (also_save_manager) + k = manager_save(link->manager); + + r = link_save(link); + if (r < 0) + return r; + + link_clean(link); + return k; +} |