]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/core/manager.c
tree-wide: make use of new relative time events in sd-event.h
[thirdparty/systemd.git] / src / core / manager.c
index 43b8a02e488ab79f7a3a10b9b98bc9130f6b7634..3a8df8245c3b59ec75ee519ce2ea836d179d2703 100644 (file)
@@ -50,6 +50,7 @@
 #include "io-util.h"
 #include "label.h"
 #include "locale-setup.h"
+#include "load-fragment.h"
 #include "log.h"
 #include "macro.h"
 #include "manager.h"
@@ -110,6 +111,7 @@ static int manager_dispatch_sigchld(sd_event_source *source, void *userdata);
 static int manager_dispatch_timezone_change(sd_event_source *source, const struct inotify_event *event, void *userdata);
 static int manager_run_environment_generators(Manager *m);
 static int manager_run_generators(Manager *m);
+static void manager_vacuum(Manager *m);
 
 static usec_t manager_watch_jobs_next_time(Manager *m) {
         return usec_add(now(CLOCK_MONOTONIC),
@@ -181,7 +183,7 @@ static void draw_cylon(char buffer[], size_t buflen, unsigned width, unsigned po
         }
 }
 
-void manager_flip_auto_status(Manager *m, bool enable, const char *reason) {
+static void manager_flip_auto_status(Manager *m, bool enable, const char *reason) {
         assert(m);
 
         if (enable) {
@@ -587,6 +589,8 @@ static char** sanitize_environment(char **l) {
         /* Let's remove some environment variables that we need ourselves to communicate with our clients */
         strv_env_unset_many(
                         l,
+                        "CACHE_DIRECTORY",
+                        "CONFIGURATION_DIRECTORY",
                         "EXIT_CODE",
                         "EXIT_STATUS",
                         "INVOCATION_ID",
@@ -594,13 +598,16 @@ static char** sanitize_environment(char **l) {
                         "LISTEN_FDNAMES",
                         "LISTEN_FDS",
                         "LISTEN_PID",
+                        "LOGS_DIRECTORY",
                         "MAINPID",
                         "MANAGERPID",
                         "NOTIFY_SOCKET",
                         "PIDFILE",
                         "REMOTE_ADDR",
                         "REMOTE_PORT",
+                        "RUNTIME_DIRECTORY",
                         "SERVICE_RESULT",
+                        "STATE_DIRECTORY",
                         "WATCHDOG_PID",
                         "WATCHDOG_USEC",
                         NULL);
@@ -678,18 +685,12 @@ static int manager_setup_prefix(Manager *m) {
                 [EXEC_DIRECTORY_CONFIGURATION] = { SD_PATH_USER_CONFIGURATION, NULL },
         };
 
-        const struct table_entry *p;
-        ExecDirectoryType i;
-        int r;
-
         assert(m);
 
-        if (MANAGER_IS_SYSTEM(m))
-                p = paths_system;
-        else
-                p = paths_user;
+        const struct table_entry *p = MANAGER_IS_SYSTEM(m) ? paths_system : paths_user;
+        int r;
 
-        for (i = 0; i < _EXEC_DIRECTORY_TYPE_MAX; i++) {
+        for (ExecDirectoryType i = 0; i < _EXEC_DIRECTORY_TYPE_MAX; i++) {
                 r = sd_path_lookup(p[i].type, p[i].suffix, &m->prefix[i]);
                 if (r < 0)
                         return r;
@@ -701,7 +702,7 @@ static int manager_setup_prefix(Manager *m) {
 static void manager_free_unit_name_maps(Manager *m) {
         m->unit_id_map = hashmap_free(m->unit_id_map);
         m->unit_name_map = hashmap_free(m->unit_name_map);
-        m->unit_path_cache = set_free_free(m->unit_path_cache);
+        m->unit_path_cache = set_free(m->unit_path_cache);
         m->unit_cache_mtime =  0;
 }
 
@@ -779,6 +780,12 @@ int manager_new(UnitFileScope scope, ManagerTestRunFlags test_run_flags, Manager
                 .original_log_level = -1,
                 .original_log_target = _LOG_TARGET_INVALID,
 
+                .watchdog_overridden[WATCHDOG_RUNTIME] = USEC_INFINITY,
+                .watchdog_overridden[WATCHDOG_REBOOT] = USEC_INFINITY,
+                .watchdog_overridden[WATCHDOG_KEXEC] = USEC_INFINITY,
+
+                .show_status_overridden = _SHOW_STATUS_INVALID,
+
                 .notify_fd = -1,
                 .cgroups_agent_fd = -1,
                 .signal_fd = -1,
@@ -834,10 +841,6 @@ int manager_new(UnitFileScope scope, ManagerTestRunFlags test_run_flags, Manager
         if (r < 0)
                 return r;
 
-        r = hashmap_ensure_allocated(&m->jobs, NULL);
-        if (r < 0)
-                return r;
-
         r = hashmap_ensure_allocated(&m->cgroup_unit, &path_hash_ops);
         if (r < 0)
                 return r;
@@ -1339,15 +1342,12 @@ static void manager_clear_jobs_and_units(Manager *m) {
 }
 
 Manager* manager_free(Manager *m) {
-        ExecDirectoryType dt;
-        UnitType c;
-
         if (!m)
                 return NULL;
 
         manager_clear_jobs_and_units(m);
 
-        for (c = 0; c < _UNIT_TYPE_MAX; c++)
+        for (UnitType c = 0; c < _UNIT_TYPE_MAX; c++)
                 if (unit_vtable[c]->shutdown)
                         unit_vtable[c]->shutdown(m);
 
@@ -1418,22 +1418,20 @@ Manager* manager_free(Manager *m) {
         hashmap_free(m->uid_refs);
         hashmap_free(m->gid_refs);
 
-        for (dt = 0; dt < _EXEC_DIRECTORY_TYPE_MAX; dt++)
+        for (ExecDirectoryType dt = 0; dt < _EXEC_DIRECTORY_TYPE_MAX; dt++)
                 m->prefix[dt] = mfree(m->prefix[dt]);
 
         return mfree(m);
 }
 
 static void manager_enumerate_perpetual(Manager *m) {
-        UnitType c;
-
         assert(m);
 
         if (m->test_run_flags == MANAGER_TEST_RUN_MINIMAL)
                 return;
 
         /* Let's ask every type to load all units from disk/kernel that it might know */
-        for (c = 0; c < _UNIT_TYPE_MAX; c++) {
+        for (UnitType c = 0; c < _UNIT_TYPE_MAX; c++) {
                 if (!unit_type_supported(c)) {
                         log_debug("Unit type .%s is not supported on this system.", unit_type_to_string(c));
                         continue;
@@ -1445,15 +1443,13 @@ static void manager_enumerate_perpetual(Manager *m) {
 }
 
 static void manager_enumerate(Manager *m) {
-        UnitType c;
-
         assert(m);
 
         if (m->test_run_flags == MANAGER_TEST_RUN_MINIMAL)
                 return;
 
         /* Let's ask every type to load all units from disk/kernel that it might know */
-        for (c = 0; c < _UNIT_TYPE_MAX; c++) {
+        for (UnitType c = 0; c < _UNIT_TYPE_MAX; c++) {
                 if (!unit_type_supported(c)) {
                         log_debug("Unit type .%s is not supported on this system.", unit_type_to_string(c));
                         continue;
@@ -1596,20 +1592,6 @@ static void manager_preset_all(Manager *m) {
                 log_info("Populated /etc with preset unit settings.");
 }
 
-static void manager_vacuum(Manager *m) {
-        assert(m);
-
-        /* Release any dynamic users no longer referenced */
-        dynamic_user_vacuum(m, true);
-
-        /* Release any references to UIDs/GIDs no longer referenced, and destroy any IPC owned by them */
-        manager_vacuum_uid_refs(m);
-        manager_vacuum_gid_refs(m);
-
-        /* Release any runtimes no longer referenced */
-        exec_runtime_vacuum(m);
-}
-
 static void manager_ready(Manager *m) {
         assert(m);
 
@@ -1955,6 +1937,21 @@ unsigned manager_dispatch_load_queue(Manager *m) {
         return n;
 }
 
+bool manager_unit_file_maybe_loadable_from_cache(Unit *u) {
+        assert(u);
+
+        if (u->load_state != UNIT_NOT_FOUND)
+                return false;
+
+        if (u->manager->unit_cache_mtime == 0)
+                return false;
+
+        if (u->manager->unit_cache_mtime > u->fragment_loadtime)
+                return true;
+
+        return !lookup_paths_mtime_good(&u->manager->lookup_paths, u->manager->unit_cache_mtime);
+}
+
 int manager_load_unit_prepare(
                 Manager *m,
                 const char *name,
@@ -1968,7 +1965,6 @@ int manager_load_unit_prepare(
         int r;
 
         assert(m);
-        assert(name || path);
         assert(_ret);
 
         /* This will prepare the unit for loading, but not actually
@@ -1977,8 +1973,13 @@ int manager_load_unit_prepare(
         if (path && !is_path(path))
                 return sd_bus_error_setf(e, SD_BUS_ERROR_INVALID_ARGS, "Path %s is not absolute.", path);
 
-        if (!name)
+        if (!name) {
+                /* 'name' and 'path' must not both be null. Check here 'path' using assert_se() to
+                 * workaround a bug in gcc that generates a -Wnonnull warning when calling basename(),
+                 * but this cannot be possible in any code path (See #6119). */
+                assert_se(path);
                 name = basename(path);
+        }
 
         t = unit_name_to_type(name);
 
@@ -1991,18 +1992,31 @@ int manager_load_unit_prepare(
 
         ret = manager_get_unit(m, name);
         if (ret) {
-                *_ret = ret;
-                return 1;
+                /* The time-based cache allows to start new units without daemon-reload,
+                 * but if they are already referenced (because of dependencies or ordering)
+                 * then we have to force a load of the fragment. As an optimization, check
+                 * first if anything in the usual paths was modified since the last time
+                 * the cache was loaded. Also check if the last time an attempt to load the
+                 * unit was made was before the most recent cache refresh, so that we know
+                 * we need to try again - even if the cache is current, it might have been
+                 * updated in a different context before we had a chance to retry loading
+                 * this particular unit. */
+                if (manager_unit_file_maybe_loadable_from_cache(ret))
+                        ret->load_state = UNIT_STUB;
+                else {
+                        *_ret = ret;
+                        return 1;
+                }
+        } else {
+                ret = cleanup_ret = unit_new(m, unit_vtable[t]->object_size);
+                if (!ret)
+                        return -ENOMEM;
         }
 
-        ret = cleanup_ret = unit_new(m, unit_vtable[t]->object_size);
-        if (!ret)
-                return -ENOMEM;
-
         if (path) {
-                ret->fragment_path = strdup(path);
-                if (!ret->fragment_path)
-                        return -ENOMEM;
+                r = free_and_strdup(&ret->fragment_path, path);
+                if (r < 0)
+                        return r;
         }
 
         r = unit_add_name(ret, name);
@@ -2284,37 +2298,48 @@ static int manager_dispatch_cgroups_agent_fd(sd_event_source *source, int fd, ui
         return 0;
 }
 
+static bool manager_process_barrier_fd(char * const *tags, FDSet *fds) {
+
+        /* nothing else must be sent when using BARRIER=1 */
+        if (strv_contains(tags, "BARRIER=1")) {
+                if (strv_length(tags) == 1) {
+                        if (fdset_size(fds) != 1)
+                                log_warning("Got incorrect number of fds with BARRIER=1, closing them.");
+                } else
+                        log_warning("Extra notification messages sent with BARRIER=1, ignoring everything.");
+
+                /* Drop the message if BARRIER=1 was found */
+                return true;
+        }
+
+        return false;
+}
+
 static void manager_invoke_notify_message(
                 Manager *m,
                 Unit *u,
                 const struct ucred *ucred,
-                const char *buf,
+                char * const *tags,
                 FDSet *fds) {
 
         assert(m);
         assert(u);
         assert(ucred);
-        assert(buf);
+        assert(tags);
 
         if (u->notifygen == m->notifygen) /* Already invoked on this same unit in this same iteration? */
                 return;
         u->notifygen = m->notifygen;
 
-        if (UNIT_VTABLE(u)->notify_message) {
-                _cleanup_strv_free_ char **tags = NULL;
-
-                tags = strv_split(buf, NEWLINE);
-                if (!tags) {
-                        log_oom();
-                        return;
-                }
-
+        if (UNIT_VTABLE(u)->notify_message)
                 UNIT_VTABLE(u)->notify_message(u, ucred, tags, fds);
 
-        else if (DEBUG_LOGGING) {
-                _cleanup_free_ char *x = NULL, *y = NULL;
+        else if (DEBUG_LOGGING) {
+                _cleanup_free_ char *buf = NULL, *x = NULL, *y = NULL;
 
-                x = ellipsize(buf, 20, 90);
+                buf = strv_join(tags, ", ");
+                if (buf)
+                        x = ellipsize(buf, 20, 90);
                 if (x)
                         y = cescape(x);
 
@@ -2331,11 +2356,8 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t
                 .iov_base = buf,
                 .iov_len = sizeof(buf)-1,
         };
-        union {
-                struct cmsghdr cmsghdr;
-                uint8_t buf[CMSG_SPACE(sizeof(struct ucred)) +
-                            CMSG_SPACE(sizeof(int) * NOTIFY_FD_MAX)];
-        } control = {};
+        CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred)) +
+                         CMSG_SPACE(sizeof(int) * NOTIFY_FD_MAX)) control;
         struct msghdr msghdr = {
                 .msg_iov = &iovec,
                 .msg_iovlen = 1,
@@ -2346,6 +2368,7 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t
         struct cmsghdr *cmsg;
         struct ucred *ucred = NULL;
         _cleanup_free_ Unit **array_copy = NULL;
+        _cleanup_strv_free_ char **tags = NULL;
         Unit *u1, *u2, **array;
         int r, *fd_array = NULL;
         size_t n_fds = 0;
@@ -2414,8 +2437,17 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t
                 return 0;
         }
 
-        /* Make sure it's NUL-terminated. */
+        /* Make sure it's NUL-terminated, then parse it to obtain the tags list */
         buf[n] = 0;
+        tags = strv_split_newlines(buf);
+        if (!tags) {
+                log_oom();
+                return 0;
+        }
+
+        /* possibly a barrier fd, let's see */
+        if (manager_process_barrier_fd(tags, fds))
+                return 0;
 
         /* Increase the generation counter used for filtering out duplicate unit invocations. */
         m->notifygen++;
@@ -2437,16 +2469,16 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t
         /* And now invoke the per-unit callbacks. Note that manager_invoke_notify_message() will handle duplicate units
          * make sure we only invoke each unit's handler once. */
         if (u1) {
-                manager_invoke_notify_message(m, u1, ucred, buf, fds);
+                manager_invoke_notify_message(m, u1, ucred, tags, fds);
                 found = true;
         }
         if (u2) {
-                manager_invoke_notify_message(m, u2, ucred, buf, fds);
+                manager_invoke_notify_message(m, u2, ucred, tags, fds);
                 found = true;
         }
         if (array_copy)
                 for (size_t i = 0; array_copy[i]; i++) {
-                        manager_invoke_notify_message(m, array_copy[i], ucred, buf, fds);
+                        manager_invoke_notify_message(m, array_copy[i], ucred, tags, fds);
                         found = true;
                 }
 
@@ -2747,11 +2779,11 @@ static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t
                 switch (sfsi.ssi_signo - SIGRTMIN) {
 
                 case 20:
-                        manager_set_show_status(m, SHOW_STATUS_YES, "signal");
+                        manager_override_show_status(m, SHOW_STATUS_YES, "signal");
                         break;
 
                 case 21:
-                        manager_set_show_status(m, SHOW_STATUS_NO, "signal");
+                        manager_override_show_status(m, SHOW_STATUS_NO, "signal");
                         break;
 
                 case 22:
@@ -2870,15 +2902,13 @@ static int manager_dispatch_idle_pipe_fd(sd_event_source *source, int fd, uint32
 static int manager_dispatch_jobs_in_progress(sd_event_source *source, usec_t usec, void *userdata) {
         Manager *m = userdata;
         int r;
-        uint64_t next;
 
         assert(m);
         assert(source);
 
         manager_print_jobs_in_progress(m);
 
-        next = now(CLOCK_MONOTONIC) + JOBS_IN_PROGRESS_PERIOD_USEC;
-        r = sd_event_source_set_time(source, next);
+        r = sd_event_source_set_time_relative(source, JOBS_IN_PROGRESS_PERIOD_USEC);
         if (r < 0)
                 return r;
 
@@ -2900,9 +2930,10 @@ int manager_loop(Manager *m) {
                 return log_error_errno(r, "Failed to enable SIGCHLD event source: %m");
 
         while (m->objective == MANAGER_OK) {
-                usec_t wait_usec;
+                usec_t wait_usec, watchdog_usec;
 
-                if (timestamp_is_set(m->runtime_watchdog) && MANAGER_IS_SYSTEM(m))
+                watchdog_usec = manager_get_watchdog(m, WATCHDOG_RUNTIME);
+                if (timestamp_is_set(watchdog_usec))
                         watchdog_ping();
 
                 if (!ratelimit_below(&rl)) {
@@ -2933,7 +2964,7 @@ int manager_loop(Manager *m) {
                         continue;
 
                 /* Sleep for watchdog runtime wait time */
-                if (MANAGER_IS_SYSTEM(m))
+                if (timestamp_is_set(watchdog_usec))
                         wait_usec = watchdog_runtime_wait();
                 else
                         wait_usec = USEC_INFINITY;
@@ -3136,6 +3167,47 @@ static bool manager_timestamp_shall_serialize(ManagerTimestamp t) {
                        MANAGER_TIMESTAMP_UNITS_LOAD_START, MANAGER_TIMESTAMP_UNITS_LOAD_FINISH);
 }
 
+#define DESTROY_IPC_FLAG (UINT32_C(1) << 31)
+
+static void manager_serialize_uid_refs_internal(
+                Manager *m,
+                FILE *f,
+                Hashmap **uid_refs,
+                const char *field_name) {
+
+        Iterator i;
+        void *p, *k;
+
+        assert(m);
+        assert(f);
+        assert(uid_refs);
+        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, i) {
+                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(m, f, &m->uid_refs, "destroy-ipc-uid");
+}
+
+static void manager_serialize_gid_refs(Manager *m, FILE *f) {
+        manager_serialize_uid_refs_internal(m, f, &m->gid_refs, "destroy-ipc-gid");
+}
+
 int manager_serialize(
                 Manager *m,
                 FILE *f,
@@ -3165,15 +3237,19 @@ int manager_serialize(
         /* After switching root, udevd has not been started yet. So, enumeration results should not be emitted. */
         (void) serialize_bool(f, "honor-device-enumeration", !switching_root);
 
-        t = show_status_to_string(m->show_status);
-        if (t)
-                (void) serialize_item(f, "show-status", t);
+        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]);
+
         for (q = 0; q < _MANAGER_TIMESTAMP_MAX; q++) {
                 _cleanup_free_ char *joined = NULL;
 
@@ -3306,6 +3382,114 @@ static int manager_deserialize_units(Manager *m, FILE *f, FDSet *fds) {
         return 0;
 }
 
+usec_t manager_get_watchdog(Manager *m, WatchdogType t) {
+        assert(m);
+
+        if (MANAGER_IS_USER(m))
+                return USEC_INFINITY;
+
+        if (timestamp_is_set(m->watchdog_overridden[t]))
+                return m->watchdog_overridden[t];
+
+        return m->watchdog[t];
+}
+
+void manager_set_watchdog(Manager *m, WatchdogType t, usec_t timeout) {
+        int r = 0;
+
+        assert(m);
+
+        if (MANAGER_IS_USER(m))
+                return;
+
+        if (m->watchdog[t] == timeout)
+                return;
+
+        if (t == WATCHDOG_RUNTIME)
+                if (!timestamp_is_set(m->watchdog_overridden[WATCHDOG_RUNTIME])) {
+                        if (timestamp_is_set(timeout))
+                                r = watchdog_set_timeout(&timeout);
+                        else
+                                watchdog_close(true);
+                }
+
+        if (r >= 0)
+                m->watchdog[t] = timeout;
+}
+
+int manager_override_watchdog(Manager *m, WatchdogType t, usec_t timeout) {
+        int r = 0;
+
+        assert(m);
+
+        if (MANAGER_IS_USER(m))
+                return 0;
+
+        if (m->watchdog_overridden[t] == timeout)
+                return 0;
+
+        if (t == WATCHDOG_RUNTIME) {
+                usec_t *p;
+
+                p = timestamp_is_set(timeout) ? &timeout : &m->watchdog[t];
+                if (timestamp_is_set(*p))
+                        r = watchdog_set_timeout(p);
+                else
+                        watchdog_close(true);
+        }
+
+        if (r >= 0)
+                m->watchdog_overridden[t] = timeout;
+
+        return 0;
+}
+
+static void manager_deserialize_uid_refs_one_internal(
+                Manager *m,
+                Hashmap** uid_refs,
+                const char *value) {
+
+        uid_t uid;
+        uint32_t c;
+        int r;
+
+        assert(m);
+        assert(uid_refs);
+        assert(value);
+
+        r = parse_uid(value, &uid);
+        if (r < 0 || uid == 0) {
+                log_debug("Unable to parse UID reference serialization: " UID_FMT, uid);
+                return;
+        }
+
+        r = hashmap_ensure_allocated(uid_refs, &trivial_hash_ops);
+        if (r < 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 reference entry: %m");
+                return;
+        }
+}
+
+static void manager_deserialize_uid_refs_one(Manager *m, const char *value) {
+        manager_deserialize_uid_refs_one_internal(m, &m->uid_refs, value);
+}
+
+static void manager_deserialize_gid_refs_one(Manager *m, const char *value) {
+        manager_deserialize_uid_refs_one_internal(m, &m->gid_refs, value);
+}
+
 int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
         int r = 0;
 
@@ -3402,14 +3586,14 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
                         else
                                 m->honor_device_enumeration = b;
 
-                } else if ((val = startswith(l, "show-status="))) {
+                } 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 flag '%s', ignoring.", val);
+                                log_notice("Failed to parse show-status-overridden flag '%s', ignoring.", val);
                         else
-                                manager_set_show_status(m, s, "deserialization");
+                                manager_override_show_status(m, s, "deserialize");
 
                 } else if ((val = startswith(l, "log-level-override="))) {
                         int level;
@@ -3429,6 +3613,30 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
                         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 (startswith(l, "env=")) {
                         r = deserialize_environment(l + 4, &m->client_environment);
                         if (r < 0)
@@ -3480,7 +3688,7 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
                 else if ((val = startswith(l, "destroy-ipc-gid=")))
                         manager_deserialize_gid_refs_one(m, val);
                 else if ((val = startswith(l, "exec-runtime=")))
-                        exec_runtime_deserialize_one(m, val, fds);
+                        (void) exec_runtime_deserialize_one(m, val, fds);
                 else if ((val = startswith(l, "subscribed="))) {
 
                         if (strv_extend(&m->deserialized_subscribed, val) < 0)
@@ -3785,6 +3993,11 @@ void manager_check_finished(Manager *m) {
                 return;
         }
 
+        /* The jobs hashmap tends to grow a lot during boot, and then it's not reused until shutdown. Let's
+           kill the hashmap if it is relatively large. */
+        if (hashmap_buckets(m->jobs) > hashmap_size(m->units) / 10)
+                m->jobs = hashmap_free(m->jobs);
+
         manager_flip_auto_status(m, false, "boot finished");
 
         /* Notify Type=idle units that we are done now */
@@ -4069,49 +4282,78 @@ void manager_recheck_journal(Manager *m) {
         log_open();
 }
 
+static ShowStatus manager_get_show_status(Manager *m) {
+        assert(m);
+
+        if (MANAGER_IS_USER(m))
+                return _SHOW_STATUS_INVALID;
+
+        if (m->show_status_overridden != _SHOW_STATUS_INVALID)
+                return m->show_status_overridden;
+
+        return m->show_status;
+}
+
+bool manager_get_show_status_on(Manager *m) {
+        assert(m);
+
+        return show_status_on(manager_get_show_status(m));
+}
+
+static void set_show_status_marker(bool b) {
+        if (b)
+                (void) touch("/run/systemd/show-status");
+        else
+                (void) unlink("/run/systemd/show-status");
+}
+
 void manager_set_show_status(Manager *m, ShowStatus mode, const char *reason) {
         assert(m);
+        assert(reason);
         assert(mode >= 0 && mode < _SHOW_STATUS_MAX);
 
-        if (!MANAGER_IS_SYSTEM(m))
+        if (MANAGER_IS_USER(m))
                 return;
 
         if (mode == m->show_status)
                 return;
 
-        bool enabled = IN_SET(mode, SHOW_STATUS_TEMPORARY, SHOW_STATUS_YES);
-        log_debug("%s (%s) showing of status (%s).",
-                  enabled ? "Enabling" : "Disabling",
-                  strna(show_status_to_string(mode)),
-                  reason);
-        m->show_status = mode;
+        if (m->show_status_overridden == _SHOW_STATUS_INVALID) {
+                bool enabled;
 
-        if (enabled)
-                (void) touch("/run/systemd/show-status");
-        else
-                (void) unlink("/run/systemd/show-status");
+                enabled = show_status_on(mode);
+                log_debug("%s (%s) showing of status (%s).",
+                          enabled ? "Enabling" : "Disabling",
+                          strna(show_status_to_string(mode)),
+                          reason);
+
+                set_show_status_marker(enabled);
+        }
+
+        m->show_status = mode;
 }
 
-static bool manager_get_show_status(Manager *m, StatusType type) {
+void manager_override_show_status(Manager *m, ShowStatus mode, const char *reason) {
         assert(m);
+        assert(mode < _SHOW_STATUS_MAX);
 
-        if (!MANAGER_IS_SYSTEM(m))
-                return false;
+        if (MANAGER_IS_USER(m))
+                return;
 
-        if (m->no_console_output)
-                return false;
+        if (mode == m->show_status_overridden)
+                return;
 
-        if (!IN_SET(manager_state(m), MANAGER_INITIALIZING, MANAGER_STARTING, MANAGER_STOPPING))
-                return false;
+        m->show_status_overridden = mode;
 
-        /* If we cannot find out the status properly, just proceed. */
-        if (type != STATUS_TYPE_EMERGENCY && manager_check_ask_password(m) > 0)
-                return false;
+        if (mode == _SHOW_STATUS_INVALID)
+                mode = m->show_status;
 
-        if (type == STATUS_TYPE_NOTICE && m->show_status != SHOW_STATUS_NO)
-                return true;
+        log_debug("%s (%s) showing of status (%s).",
+                  m->show_status_overridden != _SHOW_STATUS_INVALID ? "Overriding" : "Restoring",
+                  strna(show_status_to_string(mode)),
+                  reason);
 
-        return show_status_on(m->show_status);
+        set_show_status_marker(show_status_on(mode));
 }
 
 const char *manager_get_confirm_spawn(Manager *m) {
@@ -4186,12 +4428,34 @@ bool manager_is_confirm_spawn_disabled(Manager *m) {
         return access("/run/systemd/confirm_spawn_disabled", F_OK) >= 0;
 }
 
+static bool manager_should_show_status(Manager *m, StatusType type) {
+        assert(m);
+
+        if (!MANAGER_IS_SYSTEM(m))
+                return false;
+
+        if (m->no_console_output)
+                return false;
+
+        if (!IN_SET(manager_state(m), MANAGER_INITIALIZING, MANAGER_STARTING, MANAGER_STOPPING))
+                return false;
+
+        /* If we cannot find out the status properly, just proceed. */
+        if (type != STATUS_TYPE_EMERGENCY && manager_check_ask_password(m) > 0)
+                return false;
+
+        if (type == STATUS_TYPE_NOTICE && m->show_status != SHOW_STATUS_NO)
+                return true;
+
+        return manager_get_show_status_on(m);
+}
+
 void manager_status_printf(Manager *m, StatusType type, const char *status, const char *format, ...) {
         va_list ap;
 
         /* If m is NULL, assume we're after shutdown and let the messages through. */
 
-        if (m && !manager_get_show_status(m, type))
+        if (m && !manager_should_show_status(m, type))
                 return;
 
         /* XXX We should totally drop the check for ephemeral here
@@ -4226,12 +4490,9 @@ int manager_update_failed_units(Manager *m, Unit *u, bool failed) {
         size = set_size(m->failed_units);
 
         if (failed) {
-                r = set_ensure_allocated(&m->failed_units, NULL);
+                r = set_ensure_put(&m->failed_units, NULL, u);
                 if (r < 0)
                         return log_oom();
-
-                if (set_put(m->failed_units, u) < 0)
-                        return log_oom();
         } else
                 (void) set_remove(m->failed_units, u);
 
@@ -4279,8 +4540,6 @@ ManagerState manager_state(Manager *m) {
         return MANAGER_RUNNING;
 }
 
-#define DESTROY_IPC_FLAG (UINT32_C(1) << 31)
-
 static void manager_unref_uid_internal(
                 Manager *m,
                 Hashmap **uid_refs,
@@ -4419,97 +4678,26 @@ static void manager_vacuum_uid_refs_internal(
         }
 }
 
-void manager_vacuum_uid_refs(Manager *m) {
+static void manager_vacuum_uid_refs(Manager *m) {
         manager_vacuum_uid_refs_internal(m, &m->uid_refs, clean_ipc_by_uid);
 }
 
-void manager_vacuum_gid_refs(Manager *m) {
+static void manager_vacuum_gid_refs(Manager *m) {
         manager_vacuum_uid_refs_internal(m, &m->gid_refs, clean_ipc_by_gid);
 }
 
-static void manager_serialize_uid_refs_internal(
-                Manager *m,
-                FILE *f,
-                Hashmap **uid_refs,
-                const char *field_name) {
-
-        Iterator i;
-        void *p, *k;
-
-        assert(m);
-        assert(f);
-        assert(uid_refs);
-        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, i) {
-                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);
-        }
-}
-
-void manager_serialize_uid_refs(Manager *m, FILE *f) {
-        manager_serialize_uid_refs_internal(m, f, &m->uid_refs, "destroy-ipc-uid");
-}
-
-void manager_serialize_gid_refs(Manager *m, FILE *f) {
-        manager_serialize_uid_refs_internal(m, f, &m->gid_refs, "destroy-ipc-gid");
-}
-
-static void manager_deserialize_uid_refs_one_internal(
-                Manager *m,
-                Hashmap** uid_refs,
-                const char *value) {
-
-        uid_t uid;
-        uint32_t c;
-        int r;
-
+static void manager_vacuum(Manager *m) {
         assert(m);
-        assert(uid_refs);
-        assert(value);
-
-        r = parse_uid(value, &uid);
-        if (r < 0 || uid == 0) {
-                log_debug("Unable to parse UID reference serialization: " UID_FMT, uid);
-                return;
-        }
 
-        r = hashmap_ensure_allocated(uid_refs, &trivial_hash_ops);
-        if (r < 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 reference entry: %m");
-                return;
-        }
-}
+        /* Release any dynamic users no longer referenced */
+        dynamic_user_vacuum(m, true);
 
-void manager_deserialize_uid_refs_one(Manager *m, const char *value) {
-        manager_deserialize_uid_refs_one_internal(m, &m->uid_refs, value);
-}
+        /* Release any references to UIDs/GIDs no longer referenced, and destroy any IPC owned by them */
+        manager_vacuum_uid_refs(m);
+        manager_vacuum_gid_refs(m);
 
-void manager_deserialize_gid_refs_one(Manager *m, const char *value) {
-        manager_deserialize_uid_refs_one_internal(m, &m->gid_refs, value);
+        /* Release any runtimes no longer referenced */
+        exec_runtime_vacuum(m);
 }
 
 int manager_dispatch_user_lookup_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) {