/* SPDX-License-Identifier: LGPL-2.1-or-later */

#include "sd-id128.h"

#include "alloc-util.h"
#include "bpf-restrict-fsaccess.h"
#include "dbus.h"
#include "dynamic-user.h"
#include "fd-util.h"
#include "fdset.h"
#include "fileio.h"
#include "format-util.h"
#include "glyph-util.h"
#include "hashmap.h"
#include "initrd-util.h"
#include "job.h"
#include "manager.h"
#include "manager-serialize.h"
#include "parse-util.h"
#include "serialize.h"
#include "set.h"
#include "string-util.h"
#include "strv.h"
#include "syslog-util.h"
#include "unit.h"
#include "unit-name.h"
#include "unit-serialize.h"
#include "user-util.h"
#include "varlink.h"
#include "varlink-serialize.h"

int manager_open_serialization(Manager *m, FILE **ret_f) {
        assert(ret_f);

        return open_serialization_file("systemd-state", ret_f);
}

static bool manager_timestamp_shall_serialize(ManagerObjective o, ManagerTimestamp t) {
        if (!in_initrd() && o != MANAGER_SOFT_REBOOT)
                return true;

        /* The following timestamps only apply to the host system (or first boot in case of soft-reboot),
         * hence only serialize them there. */
        return !IN_SET(t,
                       MANAGER_TIMESTAMP_USERSPACE, MANAGER_TIMESTAMP_FINISH,
                       MANAGER_TIMESTAMP_SECURITY_START, MANAGER_TIMESTAMP_SECURITY_FINISH,
                       MANAGER_TIMESTAMP_GENERATORS_START, MANAGER_TIMESTAMP_GENERATORS_FINISH,
                       MANAGER_TIMESTAMP_UNITS_LOAD_START, MANAGER_TIMESTAMP_UNITS_LOAD_FINISH);
}

static void manager_serialize_uid_refs_internal(
                FILE *f,
                Hashmap *uid_refs,
                const char *field_name) {

        void *p, *k;

        assert(f);
        assert(field_name);

        /* Serialize the UID reference table. Or actually, just the IPC destruction flag of it, as
         * the actual counter of it is better rebuild after a reload/reexec. */

        HASHMAP_FOREACH_KEY(p, k, uid_refs) {
                uint32_t c;
                uid_t uid;

                uid = PTR_TO_UID(k);
                c = PTR_TO_UINT32(p);

                if (!(c & DESTROY_IPC_FLAG))
                        continue;

                (void) serialize_item_format(f, field_name, UID_FMT, uid);
        }
}

static void manager_serialize_uid_refs(Manager *m, FILE *f) {
        manager_serialize_uid_refs_internal(f, m->uid_refs, "destroy-ipc-uid");
}

static void manager_serialize_gid_refs(Manager *m, FILE *f) {
        manager_serialize_uid_refs_internal(f, m->gid_refs, "destroy-ipc-gid");
}

int manager_serialize(
                Manager *m,
                FILE *f,
                FDSet *fds,
                bool switching_root) {

        const char *t;
        Unit *u;
        int r;

        assert(m);
        assert(f);
        assert(fds);

        _cleanup_(manager_reloading_stopp) _unused_ Manager *reloading = manager_reloading_start(m);

        (void) serialize_item_format(f, "last-transaction-id", "%" PRIu64, m->last_transaction_id);

        (void) serialize_item_format(f, "current-job-id", "%" PRIu32, m->current_job_id);
        (void) serialize_item_format(f, "n-installed-jobs", "%u", m->n_installed_jobs);
        (void) serialize_item_format(f, "n-failed-jobs", "%u", m->n_failed_jobs);
        (void) serialize_bool(f, "taint-logged", m->taint_logged);
        (void) serialize_bool(f, "service-watchdogs", m->service_watchdogs);

        if (m->show_status_overridden != _SHOW_STATUS_INVALID)
                (void) serialize_item(f, "show-status-overridden",
                                      show_status_to_string(m->show_status_overridden));

        if (m->log_level_overridden)
                (void) serialize_item_format(f, "log-level-override", "%i", log_get_max_level());
        if (m->log_target_overridden)
                (void) serialize_item(f, "log-target-override", log_target_to_string(log_get_target()));

        (void) serialize_usec(f, "runtime-watchdog-overridden", m->watchdog_overridden[WATCHDOG_RUNTIME]);
        (void) serialize_usec(f, "reboot-watchdog-overridden", m->watchdog_overridden[WATCHDOG_REBOOT]);
        (void) serialize_usec(f, "kexec-watchdog-overridden", m->watchdog_overridden[WATCHDOG_KEXEC]);
        (void) serialize_usec(f, "pretimeout-watchdog-overridden", m->watchdog_overridden[WATCHDOG_PRETIMEOUT]);
        (void) serialize_item(f, "pretimeout-watchdog-governor-overridden", m->watchdog_pretimeout_governor_overridden);

        (void) serialize_item(f, "previous-objective", manager_objective_to_string(m->objective));
        (void) serialize_item_format(f, "soft-reboots-count", "%u", m->soft_reboots_count);
        (void) serialize_item_format(f, "fd-store-upstream-next-index", "%" PRIu64, m->fd_store_upstream_next_index);

        for (ManagerTimestamp q = 0; q < _MANAGER_TIMESTAMP_MAX; q++) {
                _cleanup_free_ char *joined = NULL;

                if (!manager_timestamp_shall_serialize(m->objective, q))
                        continue;

                joined = strjoin(manager_timestamp_to_string(q), "-timestamp");
                if (!joined)
                        return log_oom();

                (void) serialize_dual_timestamp(f, joined, m->timestamps + q);
        }

        if (!switching_root)
                (void) serialize_strv(f, "env", m->client_environment);

        if (m->notify_fd >= 0) {
                r = serialize_fd(f, fds, "notify-fd", m->notify_fd);
                if (r < 0)
                        return r;

                (void) serialize_item(f, "notify-socket", m->notify_socket);
        }

        if (m->user_lookup_fds[0] >= 0) {
                r = serialize_fd_many(f, fds, "user-lookup", m->user_lookup_fds, 2);
                if (r < 0)
                        return r;
        }

        if (m->handoff_timestamp_fds[0] >= 0) {
                r = serialize_fd_many(f, fds, "handoff-timestamp-fds", m->handoff_timestamp_fds, 2);
                if (r < 0)
                        return r;
        }

        (void) serialize_ratelimit(f, "dump-ratelimit", &m->dump_ratelimit);
        (void) serialize_ratelimit(f, "reload-reexec-ratelimit", &m->reload_reexec_ratelimit);
        (void) serialize_ratelimit(f, "event-loop-ratelimit", &m->event_loop_ratelimit);

        (void) serialize_id128(f, "bus-id", m->bus_id);
        bus_track_serialize(m->subscribed, f, "subscribed");

        r = dynamic_user_serialize(m, f, fds);
        if (r < 0)
                return r;

        manager_serialize_uid_refs(m, f);
        manager_serialize_gid_refs(m, f);

        r = exec_shared_runtime_serialize(m, f, fds);
        if (r < 0)
                return r;

        r = varlink_server_serialize(m->varlink_server, /* name = */ NULL, f, fds);
        if (r < 0)
                return r;

        r = varlink_server_serialize(m->metrics_varlink_server, "metrics", f, fds);
        if (r < 0)
                return r;

        r = bpf_restrict_fsaccess_serialize(m, f, fds);
        if (r < 0)
                return r;

        (void) fputc('\n', f);

        HASHMAP_FOREACH_KEY(u, t, m->units) {
                if (u->id != t)
                        continue;

                r = unit_serialize_state(u, f, fds, switching_root);
                if (r < 0)
                        return r;
        }

        r = bus_fdset_add_all(m, fds);
        if (r < 0)
                return log_error_errno(r, "Failed to add bus sockets to serialization: %m");

        return 0;
}

static int manager_collect_serialized_unit_names(FILE *f, Set **ret) {
        _cleanup_set_free_ Set *serialized_units = NULL;
        off_t offset;
        int r;

        assert(f);
        assert(ret);

        offset = ftello(f);
        if (offset < 0)
                return log_error_errno(errno, "Failed to determine serialization offset: %m");

        for (;;) {
                _cleanup_free_ char *line = NULL;

                r = read_stripped_line(f, LONG_LINE_MAX, &line);
                if (r < 0)
                        return log_error_errno(r, "Failed to read serialization line: %m");
                if (r == 0)
                        break;

                r = set_ensure_consume(&serialized_units, &string_hash_ops_free, TAKE_PTR(line));
                if (r < 0)
                        return log_oom();

                r = unit_deserialize_state_skip(f);
                if (r < 0)
                        return r;
        }

        if (fseeko(f, offset, SEEK_SET) < 0)
                return log_error_errno(errno, "Failed to reset serialization offset: %m");

        *ret = TAKE_PTR(serialized_units);
        return 0;
}

static int manager_synthesize_orphaned_unit(
                Manager *m,
                const char *original_name,
                const char *canonical_name,
                FILE *f,
                FDSet *fds) {

        _cleanup_(unit_freep) Unit *orphan = NULL;
        _cleanup_free_ char *orphaned_name = NULL;
        sd_id128_t rnd;
        UnitType t;
        int r;

        assert(m);
        assert(original_name);
        assert(canonical_name);
        assert(f);
        assert(fds);

        t = unit_name_to_type(original_name);
        if (t < 0)
                return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
                                         "Cannot synthesize unit for '%s' (overridden by alias to '%s'): invalid unit type. Skipping stale state.",
                                         original_name, canonical_name);

        /* Only transition units that track external resources and can be renamed/know aliases, forget internal ones (eg: timers) */
        if (!unit_vtable[t]->track_orphaned)
                return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
                                         "Cannot synthesize unit for '%s' (overridden by alias to '%s'): unsupported unit type. Skipping stale state.",
                                         original_name, canonical_name);

        /* Use a naming convention with an "orphaned-" prefix to make it clear at a glance that these units
         * were synthesized to adopt resources from a now-aliased unit */
        r = sd_id128_randomize(&rnd);
        if (r < 0)
                return log_warning_errno(r, "Failed to generate random ID for orphan unit: %m");

        if (asprintf(&orphaned_name, "orphaned-r" SD_ID128_FORMAT_STR ".%s",
                     SD_ID128_FORMAT_VAL(rnd), unit_type_to_string(t)) < 0)
                return log_oom();

        r = unit_new_for_name(m, unit_vtable[t]->object_size, orphaned_name, &orphan);
        if (r < 0)
                return log_warning_errno(r, "Failed to allocate orphan unit '%s': %m", orphaned_name);

        /* Force the state as we know the unit file is gone, so it will hang around as long as resources are
         * active, and then it will be automatically collected */
        orphan->load_state = UNIT_NOT_FOUND;
        /* Ensure the orphan also gets garbage-collected when it ends up in 'failed' state. */
        orphan->collect_mode = COLLECT_INACTIVE_OR_FAILED;

        _cleanup_free_ char *description = strjoin("Orphaned resources adopted from aliased unit ", original_name);
        if (!description)
                return log_oom();

        r = unit_deserialize_state(orphan, f, fds);
        if (r < 0)
                return log_notice_errno(r, "Failed to deserialize state into orphan unit '%s': %m", orphaned_name);

        /* From this point on the serialized stream for this unit has been fully consumed, so avoid failing. */

        log_warning("Unit file for '%s' was overridden by a symlink to '%s'. Synthesized orphan unit '%s' to retain tracking of the previous unit's processes.",
                    original_name, canonical_name, orphaned_name);

        /* Override Description= so it's clear what this is for and can be traced back to the original. */
        free_and_replace(orphan->description, description);

        /* Any jobs reinstalled from the deserialized state targeted the original unit. Most of them
         * (start, restart, reload, ...) make no sense for the synthesized orphan, especially after a
         * service-to-scope conversion, so cancel them. JOB_STOP is the exception: a stop request that was
         * already pending on the original unit is exactly what we want to keep around, since it'll cause
         * the synthesized unit to just go away immediately. */
        if (orphan->job && orphan->job->type != JOB_STOP)
                job_finish_and_invalidate(orphan->job, JOB_CANCELED, /* recursive= */ false, /* already= */ false);
        if (orphan->nop_job)
                job_finish_and_invalidate(orphan->nop_job, JOB_CANCELED, /* recursive= */ false, /* already= */ false);

        TAKE_PTR(orphan);
        return 0;
}

static int manager_deserialize_one_unit(
                Manager *m,
                const char *name,
                FILE *f,
                FDSet *fds,
                Set *serialized_units) {

        Unit *u;
        int r;

        r = manager_load_unit(m, name, NULL, NULL, &u);
        if (r == -ENOMEM)
                return log_oom();
        if (r < 0)
                return log_notice_errno(r, "Failed to load unit \"%s\", skipping deserialization: %m", name);

        if (!streq(u->id, name) &&
            set_contains(serialized_units, u->id)) {
                /*
                 * The unit from the state file (name) resolved to a different canonical unit (u->id), and
                 * the canonical unit also has its own state entry.
                 *
                 * This means the state entry for the unit name is stale. That is, when the state was
                 * serialized, the name referred to an independent unit, but it now resolves as an alias to
                 * the canonical unit. Deserializing it would overwrite the canonical unit's own serialized
                 * state, and thus corrupt its live runtime state.
                 *
                 * It is very important to note that this only affects units that were independent when the
                 * state file was written, but are now aliases (either because a reload created the symlink,
                 * or the symlink existed but this is the first reload). Normal aliases that were already
                 * aliases during the most recent serialization are filtered out in manager_serialize(), so
                 * they never appear in the state file.
                 *
                 * If the canonical unit does not have its own state entry, then this is instead a rename or
                 * canonical ID change, and this state entry is the only state we have for the unit. In that
                 * case we must preserve it. After doing so, we insert the canonical unit's ID into the set
                 * so that any further aliases resolving to the same unit are skipped.
                 *
                 * The serialized data represents the old, independent unit. Deserializing this stale state
                 * onto the canonical unit would corrupt its live state. Instead, we synthesize a new unloaded
                 * unit with a unique name and migrate the cgroup/PID/etc. tracking from the stale state
                 * into it, so that the resources from the previously independent unit remain tracked.
                 *
                 * Take as an example, a.service is running. Someone created symlink b.service -> a.service.
                 * On first reload, the state file still has b.service as an independent unit (from before
                 * the symlink existed), but b.service now resolves to a.service. We retain a.service's
                 * running state, and synthesize an unloaded unit to keep tracking the processes that
                 * b.service used to own.
                 *
                 * Note: Log messages from this code path are checked in TEST-07-PID1.alias-corruption.sh,
                 * so the test case may need adjustment if they are changed.
                 */
                r = manager_synthesize_orphaned_unit(m, name, u->id, f, fds);
                if (r < 0) /* If we fail to orphan for any reason, then discard the unit */
                        r = unit_deserialize_state_skip(f);
                return r;
        }

        r = unit_deserialize_state(u, f, fds);
        if (r == -ENOMEM)
                return log_oom();
        if (r < 0)
                return log_notice_errno(r, "Failed to deserialize unit \"%s\", skipping: %m", name);

        /* If this unit was deserialized under an alias name (that is, it is a rename), record the canonical
         * ID so that any further aliases pointing to the same unit are correctly skipped. */
        if (!streq(u->id, name)) {
                r = set_put_strdup(&serialized_units, u->id);
                if (r < 0)
                        return log_oom();
        }

        return 0;
}

static int manager_deserialize_units(
                Manager *m,
                FILE *f,
                FDSet *fds) {

        _cleanup_set_free_ Set *serialized_units = NULL;
        int r;

        r = manager_collect_serialized_unit_names(f, &serialized_units);
        if (r < 0)
                return r;

        for (;;) {
                _cleanup_free_ char *line = NULL;

                /* Start marker */
                r = read_stripped_line(f, LONG_LINE_MAX, &line);
                if (r < 0)
                        return log_error_errno(r, "Failed to read serialization line: %m");
                if (r == 0)
                        break;

                r = manager_deserialize_one_unit(m, line, f, fds, serialized_units);
                if (r == -ENOMEM)
                        return r;
                if (r < 0) {
                        r = unit_deserialize_state_skip(f);
                        if (r < 0)
                                return r;
                }
        }

        return 0;
}

static void manager_deserialize_uid_refs_one_internal(
                Hashmap** uid_refs,
                const char *value) {

        uid_t uid;
        uint32_t c;
        int r;

        assert(uid_refs);
        assert(value);

        r = parse_uid(value, &uid);
        if (r < 0 || uid == 0) {
                log_debug("Unable to parse UID/GID reference serialization: %s", value);
                return;
        }

        if (hashmap_ensure_allocated(uid_refs, &trivial_hash_ops) < 0) {
                log_oom();
                return;
        }

        c = PTR_TO_UINT32(hashmap_get(*uid_refs, UID_TO_PTR(uid)));
        if (c & DESTROY_IPC_FLAG)
                return;

        c |= DESTROY_IPC_FLAG;

        r = hashmap_replace(*uid_refs, UID_TO_PTR(uid), UINT32_TO_PTR(c));
        if (r < 0) {
                log_debug_errno(r, "Failed to add UID/GID reference entry: %m");
                return;
        }
}

static void manager_deserialize_uid_refs_one(Manager *m, const char *value) {
        manager_deserialize_uid_refs_one_internal(&m->uid_refs, value);
}

static void manager_deserialize_gid_refs_one(Manager *m, const char *value) {
        manager_deserialize_uid_refs_one_internal(&m->gid_refs, value);
}

static void deserialize_restrict_fsaccess(Manager *m, const char *l, FDSet *fds) {
        const char *val;
        int fd;

        FOREACH_ELEMENT(name, restrict_fsaccess_link_names) {
                val = startswith(l, *name);
                if (!val)
                        continue;
                val = startswith(val, "=");
                if (!val)
                        continue;
                fd = deserialize_fd(fds, val);
                if (fd < 0) {
                        log_warning_errno(fd, "bpf-restrict-fsaccess: Failed to deserialize FD for %s: %m", *name);
                        return;
                }
                close_and_replace(m->restrict_fsaccess_link_fds[name - restrict_fsaccess_link_names], fd);
                return;
        }

        val = startswith(l, "restrict-fsaccess-bss-map=");
        if (!val)
                return;

        fd = deserialize_fd(fds, val);
        if (fd < 0) {
                log_warning_errno(fd, "bpf-restrict-fsaccess: Failed to deserialize FD for .bss map: %m");
                return;
        }
        close_and_replace(m->restrict_fsaccess_bss_map_fd, fd);
}

int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
        int r;

        assert(m);
        assert(f);

        if (DEBUG_LOGGING) {
                if (fdset_isempty(fds))
                        log_debug("No file descriptors passed");
                else {
                        int fd;

                        FDSET_FOREACH(fd, fds) {
                                _cleanup_free_ char *fn = NULL;

                                r = fd_get_path(fd, &fn);
                                if (r < 0)
                                        log_debug_errno(r, "Received serialized fd %i %s %m",
                                                        fd, glyph(GLYPH_ARROW_RIGHT));
                                else
                                        log_debug("Received serialized fd %i %s %s",
                                                  fd, glyph(GLYPH_ARROW_RIGHT), strna(fn));
                        }
                }
        }

        log_debug("Deserializing state...");

        /* If we are not in reload mode yet, enter it now. Not that this is recursive, a caller might already have
         * increased it to non-zero, which is why we just increase it by one here and down again at the end of this
         * call. */
        _cleanup_(manager_reloading_stopp) _unused_ Manager *reloading = manager_reloading_start(m);

        for (;;) {
                _cleanup_free_ char *l = NULL;
                const char *val;

                r = deserialize_read_line(f, &l);
                if (r < 0)
                        return r;
                if (r == 0) /* eof or end marker */
                        break;

                if ((val = startswith(l, "last-transaction-id="))) {
                        uint64_t id;

                        if (safe_atou64(val, &id) < 0)
                                log_notice("Failed to parse last transaction id value '%s', ignoring.", val);
                        else
                                m->last_transaction_id = MAX(m->last_transaction_id, id);

                } else if ((val = startswith(l, "current-job-id="))) {
                        uint32_t id;

                        if (safe_atou32(val, &id) < 0)
                                log_notice("Failed to parse current job id value '%s', ignoring.", val);
                        else
                                m->current_job_id = MAX(m->current_job_id, id);

                } else if ((val = startswith(l, "n-installed-jobs="))) {
                        uint32_t n;

                        if (safe_atou32(val, &n) < 0)
                                log_notice("Failed to parse installed jobs counter '%s', ignoring.", val);
                        else
                                m->n_installed_jobs += n;

                } else if ((val = startswith(l, "n-failed-jobs="))) {
                        uint32_t n;

                        if (safe_atou32(val, &n) < 0)
                                log_notice("Failed to parse failed jobs counter '%s', ignoring.", val);
                        else
                                m->n_failed_jobs += n;

                } else if ((val = startswith(l, "taint-logged="))) {
                        r = parse_boolean(val);
                        if (r < 0)
                                log_notice("Failed to parse taint-logged flag '%s', ignoring.", val);
                        else
                                m->taint_logged = m->taint_logged || r;

                } else if ((val = startswith(l, "service-watchdogs="))) {
                        r = parse_boolean(val);
                        if (r < 0)
                                log_notice("Failed to parse service-watchdogs flag '%s', ignoring.", val);
                        else
                                m->service_watchdogs = r;

                } else if ((val = startswith(l, "show-status-overridden="))) {
                        ShowStatus s;

                        s = show_status_from_string(val);
                        if (s < 0)
                                log_notice("Failed to parse show-status-overridden flag '%s', ignoring.", val);
                        else
                                manager_override_show_status(m, s, "deserialize");

                } else if ((val = startswith(l, "log-level-override="))) {
                        int level;

                        level = log_level_from_string(val);
                        if (level < 0)
                                log_notice("Failed to parse log-level-override value '%s', ignoring.", val);
                        else
                                manager_override_log_level(m, level);

                } else if ((val = startswith(l, "log-target-override="))) {
                        LogTarget target;

                        target = log_target_from_string(val);
                        if (target < 0)
                                log_notice("Failed to parse log-target-override value '%s', ignoring.", val);
                        else
                                manager_override_log_target(m, target);

                } else if ((val = startswith(l, "runtime-watchdog-overridden="))) {
                        usec_t t;

                        if (deserialize_usec(val, &t) < 0)
                                log_notice("Failed to parse runtime-watchdog-overridden value '%s', ignoring.", val);
                        else
                                manager_override_watchdog(m, WATCHDOG_RUNTIME, t);

                } else if ((val = startswith(l, "reboot-watchdog-overridden="))) {
                        usec_t t;

                        if (deserialize_usec(val, &t) < 0)
                                log_notice("Failed to parse reboot-watchdog-overridden value '%s', ignoring.", val);
                        else
                                manager_override_watchdog(m, WATCHDOG_REBOOT, t);

                } else if ((val = startswith(l, "kexec-watchdog-overridden="))) {
                        usec_t t;

                        if (deserialize_usec(val, &t) < 0)
                                log_notice("Failed to parse kexec-watchdog-overridden value '%s', ignoring.", val);
                        else
                                manager_override_watchdog(m, WATCHDOG_KEXEC, t);

                } else if ((val = startswith(l, "pretimeout-watchdog-overridden="))) {
                        usec_t t;

                        if (deserialize_usec(val, &t) < 0)
                                log_notice("Failed to parse pretimeout-watchdog-overridden value '%s', ignoring.", val);
                        else
                                manager_override_watchdog(m, WATCHDOG_PRETIMEOUT, t);

                } else if ((val = startswith(l, "pretimeout-watchdog-governor-overridden="))) {
                        r = free_and_strdup(&m->watchdog_pretimeout_governor_overridden, val);
                        if (r < 0)
                                return r;

                } else if ((val = startswith(l, "env="))) {
                        r = deserialize_environment(val, &m->client_environment);
                        if (r < 0)
                                log_notice_errno(r, "Failed to parse environment entry: \"%s\", ignoring: %m", val);

                } else if ((val = startswith(l, "notify-fd="))) {
                        int fd;

                        fd = deserialize_fd(fds, val);
                        if (fd >= 0) {
                                m->notify_event_source = sd_event_source_disable_unref(m->notify_event_source);
                                close_and_replace(m->notify_fd, fd);
                        }

                } else if ((val = startswith(l, "notify-socket="))) {
                        r = free_and_strdup(&m->notify_socket, val);
                        if (r < 0)
                                return r;

                } else if ((val = startswith(l, "user-lookup="))) {

                        m->user_lookup_event_source = sd_event_source_disable_unref(m->user_lookup_event_source);
                        safe_close_pair(m->user_lookup_fds);

                        r = deserialize_fd_many(fds, val, 2, m->user_lookup_fds);
                        if (r < 0)
                                log_warning_errno(r, "Failed to parse user-lookup fds: \"%s\", ignoring: %m", val);

                } else if ((val = startswith(l, "handoff-timestamp-fds="))) {

                        m->handoff_timestamp_event_source = sd_event_source_disable_unref(m->handoff_timestamp_event_source);
                        safe_close_pair(m->handoff_timestamp_fds);

                        r = deserialize_fd_many(fds, val, 2, m->handoff_timestamp_fds);
                        if (r < 0)
                                log_warning_errno(r, "Failed to parse handoff-timestamp fds: \"%s\", ignoring: %m", val);

                } else if ((val = startswith(l, "dynamic-user=")))
                        dynamic_user_deserialize_one(m, val, fds, NULL);
                else if ((val = startswith(l, "destroy-ipc-uid=")))
                        manager_deserialize_uid_refs_one(m, val);
                else if ((val = startswith(l, "destroy-ipc-gid=")))
                        manager_deserialize_gid_refs_one(m, val);
                else if ((val = startswith(l, "exec-runtime=")))
                        (void) exec_shared_runtime_deserialize_one(m, val, fds);
                else if ((val = startswith(l, "bus-id="))) {

                        r = sd_id128_from_string(val, &m->deserialized_bus_id);
                        if (r < 0)
                                return r;
                } else if ((val = startswith(l, "subscribed="))) {

                        r = strv_extend(&m->subscribed_as_strv, val);
                        if (r < 0)
                                return r;
                } else if ((val = startswith(l, "varlink-server-metrics-"))) {
                        if (m->objective == MANAGER_RELOAD)
                                /* We don't destroy varlink server on daemon-reload (in contrast to reexec) -> skip! */
                                continue;

                        r = manager_setup_varlink_metrics_server(m);
                        if (r < 0)
                                log_warning_errno(r, "Failed to setup metrics varlink server, ignoring: %m");
                        else
                                (void) varlink_server_deserialize_one(m->metrics_varlink_server, val, fds);

                } else if ((val = startswith(l, "varlink-server-"))) {
                        if (m->objective == MANAGER_RELOAD)
                                /* We don't destroy varlink server on daemon-reload (in contrast to reexec) -> skip! */
                                continue;

                        r = manager_setup_varlink_server(m);
                        if (r < 0)
                                log_warning_errno(r, "Failed to setup varlink server, ignoring: %m");
                        else
                                (void) varlink_server_deserialize_one(m->varlink_server, val, fds);

                } else if (startswith(l, "restrict-fsaccess-"))
                        deserialize_restrict_fsaccess(m, l, fds);
                else if ((val = startswith(l, "dump-ratelimit=")))
                        deserialize_ratelimit(&m->dump_ratelimit, "dump-ratelimit", val);
                else if ((val = startswith(l, "reload-reexec-ratelimit=")))
                        deserialize_ratelimit(&m->reload_reexec_ratelimit, "reload-reexec-ratelimit", val);
                else if ((val = startswith(l, "event-loop-ratelimit=")))
                        deserialize_ratelimit(&m->event_loop_ratelimit, "event-loop-ratelimit", val);
                else if ((val = startswith(l, "soft-reboots-count="))) {
                        unsigned n;

                        if (safe_atou(val, &n) < 0)
                                log_notice("Failed to parse soft reboots counter '%s', ignoring.", val);
                        else
                                m->soft_reboots_count = n;
                } else if ((val = startswith(l, "fd-store-upstream-next-index="))) {
                        if (safe_atou64(val, &m->fd_store_upstream_next_index) < 0)
                                log_notice("Failed to parse fd-store-upstream-next-index '%s', ignoring.", val);
                } else if ((val = startswith(l, "previous-objective="))) {
                        ManagerObjective objective;

                        objective = manager_objective_from_string(val);
                        if (objective < 0)
                                log_notice("Failed to parse previous objective '%s', ignoring.", val);
                        else
                                m->previous_objective = objective;

                } else {
                        ManagerTimestamp q;

                        for (q = 0; q < _MANAGER_TIMESTAMP_MAX; q++) {
                                val = startswith(l, manager_timestamp_to_string(q));
                                if (!val)
                                        continue;

                                val = startswith(val, "-timestamp=");
                                if (val)
                                        break;
                        }

                        if (q < _MANAGER_TIMESTAMP_MAX) /* found it */
                                (void) deserialize_dual_timestamp(val, m->timestamps + q);
                        else if (!STARTSWITH_SET(l, "kdbus-fd=", "honor-device-enumeration=", "ready-sent=", "cgroups-agent-fd=")) /* ignore deprecated values */
                                log_notice("Unknown serialization item '%s', ignoring.", l);
                }
        }

        return manager_deserialize_units(m, f, fds);
}
