int link_save(Link *link) {
        _cleanup_free_ char *temp_path = NULL;
        _cleanup_fclose_ FILE *f = NULL;
        const char *admin_state, *oper_state;
        Address *a;
        Route *route;
        Iterator i;
        int r;

        assert(link);
        assert(link->state_file);
        assert(link->lease_file);
        assert(link->manager);

        if (link->state == LINK_STATE_LINGER) {
                unlink(link->state_file);
                return 0;
        }

        admin_state = link_state_to_string(link->state);
        assert(admin_state);

        oper_state = link_operstate_to_string(link->operstate);
        assert(oper_state);

        r = fopen_temporary(link->state_file, &f, &temp_path);
        if (r < 0)
                goto fail;

        fchmod(fileno(f), 0644);

        fprintf(f,
                "# This is private data. Do not parse.\n"
                "ADMIN_STATE=%s\n"
                "OPER_STATE=%s\n",
                admin_state, oper_state);

        if (link->network) {
                char **address, **domain;
                bool space;
                sd_dhcp6_lease *dhcp6_lease = NULL;

                if (link->dhcp6_client) {
                        r = sd_dhcp6_client_get_lease(link->dhcp6_client,
                                                      &dhcp6_lease);
                        if (r < 0)
                                log_link_debug(link, "No DHCPv6 lease");
                }

                fprintf(f, "NETWORK_FILE=%s\n", link->network->filename);

                fputs("DNS=", f);
                space = false;
                STRV_FOREACH(address, link->network->dns) {
                        if (space)
                                fputc(' ', f);
                        fputs(*address, f);
                        space = true;
                }

                if (link->network->dhcp_dns &&
                    link->dhcp_lease) {
                        const struct in_addr *addresses;

                        r = sd_dhcp_lease_get_dns(link->dhcp_lease, &addresses);
                        if (r > 0) {
                                if (space)
                                        fputc(' ', f);
                                serialize_in_addrs(f, addresses, r);
                                space = true;
                        }
                }

                if (link->network->dhcp_dns && dhcp6_lease) {
                        struct in6_addr *in6_addrs;

                        r = sd_dhcp6_lease_get_dns(dhcp6_lease, &in6_addrs);
                        if (r > 0) {
                                if (space)
                                        fputc(' ', f);
                                serialize_in6_addrs(f, in6_addrs, r);
                        }
                }

                fputc('\n', f);

                fputs("NTP=", f);
                space = false;
                STRV_FOREACH(address, link->network->ntp) {
                        if (space)
                                fputc(' ', f);
                        fputs(*address, f);
                        space = true;
                }

                if (link->network->dhcp_ntp &&
                    link->dhcp_lease) {
                        const struct in_addr *addresses;

                        r = sd_dhcp_lease_get_ntp(link->dhcp_lease, &addresses);
                        if (r > 0) {
                                if (space)
                                        fputc(' ', f);
                                serialize_in_addrs(f, addresses, r);
                                space = true;
                        }
                }

                if (link->network->dhcp_ntp && dhcp6_lease) {
                        struct in6_addr *in6_addrs;
                        char **hosts;
                        char **hostname;

                        r = sd_dhcp6_lease_get_ntp_addrs(dhcp6_lease,
                                                         &in6_addrs);
                        if (r > 0) {
                                if (space)
                                        fputc(' ', f);
                                serialize_in6_addrs(f, in6_addrs, r);
                                space = true;
                        }

                        r = sd_dhcp6_lease_get_ntp_fqdn(dhcp6_lease, &hosts);
                        if (r > 0) {
                                STRV_FOREACH(hostname, hosts) {
                                        if (space)
                                                fputc(' ', f);
                                        fputs(*hostname, f);
                                        space = true;
                                }
                        }
                }

                fputc('\n', f);

                fputs("DOMAINS=", f);
                space = false;
                STRV_FOREACH(domain, link->network->domains) {
                        if (space)
                                fputc(' ', f);
                        fputs(*domain, f);
                        space = true;
                }

                if (link->network->dhcp_domains &&
                    link->dhcp_lease) {
                        const char *domainname;

                        r = sd_dhcp_lease_get_domainname(link->dhcp_lease, &domainname);
                        if (r >= 0) {
                                if (space)
                                        fputc(' ', f);
                                fputs(domainname, f);
                                space = true;
                        }
                }

                if (link->network->dhcp_domains && dhcp6_lease) {
                        char **domains;

                        r = sd_dhcp6_lease_get_domains(dhcp6_lease, &domains);
                        if (r >= 0) {
                                STRV_FOREACH(domain, domains) {
                                        if (space)
                                                fputc(' ', f);
                                        fputs(*domain, f);
                                        space = true;
                                }
                        }
                }

                fputc('\n', f);

                fprintf(f, "WILDCARD_DOMAIN=%s\n",
                        yes_no(link->network->wildcard_domain));

                fprintf(f, "LLMNR=%s\n",
                        resolve_support_to_string(link->network->llmnr));

                fputs("ADDRESSES=", f);
                space = false;
                SET_FOREACH(a, link->addresses, i) {
                        _cleanup_free_ char *address_str = NULL;

                        r = in_addr_to_string(a->family, &a->in_addr, &address_str);
                        if (r < 0)
                                goto fail;

                        fprintf(f, "%s%s/%u", space ? " " : "", address_str, a->prefixlen);
                        space = true;
                }

                fputc('\n', f);

                fputs("ROUTES=", f);
                space = false;
                SET_FOREACH(route, link->routes, i) {
                        _cleanup_free_ char *route_str = NULL;

                        r = in_addr_to_string(route->family, &route->dst, &route_str);
                        if (r < 0)
                                goto fail;

                        fprintf(f, "%s%s/%hhu/%hhu/%"PRIu32"/%hhu/"USEC_FMT, space ? " " : "", route_str,
                                route->dst_prefixlen, route->tos, route->priority, route->table, route->lifetime);
                        space = true;
                }

                fputc('\n', f);
        }

        if (!hashmap_isempty(link->bound_to_links)) {
                Link *carrier;
                bool space = false;

                fputs("CARRIER_BOUND_TO=", f);
                HASHMAP_FOREACH(carrier, link->bound_to_links, i) {
                        if (space)
                                fputc(' ', f);
                        fputs(carrier->ifname, f);
                        space = true;
                }

                fputc('\n', f);
        }

        if (!hashmap_isempty(link->bound_by_links)) {
                Link *carrier;
                bool space = false;

                fputs("CARRIER_BOUND_BY=", f);
                HASHMAP_FOREACH(carrier, link->bound_by_links, i) {
                        if (space)
                                fputc(' ', f);
                        fputs(carrier->ifname, f);
                        space = true;
                }

                fputc('\n', f);
        }

        if (link->dhcp_lease) {
                struct in_addr address;
                const char *tz = NULL;

                assert(link->network);

                r = sd_dhcp_lease_get_timezone(link->dhcp_lease, &tz);
                if (r >= 0)
                        fprintf(f, "TIMEZONE=%s\n", tz);

                r = sd_dhcp_lease_get_address(link->dhcp_lease, &address);
                if (r >= 0) {
                        fputs("DHCP4_ADDRESS=", f);
                        serialize_in_addrs(f, &address, 1);
                        fputc('\n', f);
                }

                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
                unlink(link->lease_file);

        if (link->ipv4ll) {
                struct in_addr address;

                r = sd_ipv4ll_get_address(link->ipv4ll, &address);
                if (r >= 0) {
                        fputs("IPV4LL_ADDRESS=", f);
                        serialize_in_addrs(f, &address, 1);
                        fputc('\n', f);
                }
        }

        if (link->lldp) {
                assert(link->network);

                r = sd_lldp_save(link->lldp, link->lldp_file);
                if (r < 0)
                        goto fail;

                fprintf(f,
                        "LLDP_FILE=%s\n",
                        link->lldp_file);
        } else
                unlink(link->lldp_file);

        r = fflush_and_check(f);
        if (r < 0)
                goto fail;

        if (rename(temp_path, link->state_file) < 0) {
                r = -errno;
                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);
}