]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/systemctl/systemctl.c
pid1: use a cache for all unit aliases
[thirdparty/systemd.git] / src / systemctl / systemctl.c
index 0574a9f4cebc2a3ea1039c9754027ca3c4b48a6e..31d364cefed787b58f09022fc255ca3120d9b1c4 100644 (file)
 #include "bus-unit-util.h"
 #include "bus-util.h"
 #include "bus-wait-for-jobs.h"
+#include "bus-wait-for-units.h"
 #include "cgroup-show.h"
 #include "cgroup-util.h"
 #include "copy.h"
 #include "cpu-set-util.h"
+#include "dirent-util.h"
 #include "dropin.h"
 #include "efivars.h"
 #include "env-util.h"
 #include "escape.h"
+#include "exec-util.h"
 #include "exit-status.h"
 #include "fd-util.h"
 #include "format-util.h"
@@ -161,12 +164,20 @@ static const char *arg_boot_loader_entry = NULL;
 static bool arg_now = false;
 static bool arg_jobs_before = false;
 static bool arg_jobs_after = false;
+static char **arg_clean_what = NULL;
+
+/* This is a global cache that will be constructed on first use. */
+static Hashmap *cached_id_map = NULL;
+static Hashmap *cached_name_map = NULL;
 
 STATIC_DESTRUCTOR_REGISTER(arg_wall, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_types, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_states, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_properties, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_clean_what, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(cached_id_map, hashmap_freep);
+STATIC_DESTRUCTOR_REGISTER(cached_name_map, hashmap_freep);
 
 static int daemon_reload(int argc, char *argv[], void* userdata);
 static int trivial_method(int argc, char *argv[], void *userdata);
@@ -553,14 +564,16 @@ static int output_units_list(const UnitInfo *unit_infos, unsigned c) {
                         off = ansi_normal();
                 }
 
-                if (arg_all)
+                if (arg_all || strv_contains(arg_states, "inactive"))
                         printf("%s%u loaded units listed.%s\n"
                                "To show all installed unit files use 'systemctl list-unit-files'.\n",
                                on, n_shown, off);
-                else
+                else if (!arg_states)
                         printf("%s%u loaded units listed.%s Pass --all to see loaded but inactive units, too.\n"
                                "To show all installed unit files use 'systemctl list-unit-files'.\n",
                                on, n_shown, off);
+                else
+                        printf("%u loaded units listed.\n", n_shown);
         }
 
         return 0;
@@ -1227,7 +1240,7 @@ static usec_t calc_next_elapse(dual_timestamp *nw, dual_timestamp *next) {
         assert(nw);
         assert(next);
 
-        if (next->monotonic != USEC_INFINITY && next->monotonic > 0) {
+        if (timestamp_is_set(next->monotonic)) {
                 usec_t converted;
 
                 if (next->monotonic > nw->monotonic)
@@ -1235,7 +1248,7 @@ static usec_t calc_next_elapse(dual_timestamp *nw, dual_timestamp *next) {
                 else
                         converted = nw->realtime - (nw->monotonic - next->monotonic);
 
-                if (next->realtime != USEC_INFINITY && next->realtime > 0)
+                if (timestamp_is_set(next->realtime))
                         next_elapse = MIN(converted, next->realtime);
                 else
                         next_elapse = converted;
@@ -1633,20 +1646,20 @@ static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, cha
 
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         _cleanup_strv_free_ char **ret = NULL;
-        _cleanup_free_ char *path = NULL;
+        _cleanup_free_ char *dbus_path = NULL;
         int i, r;
 
         assert(bus);
         assert(name);
         assert(deps);
 
-        path = unit_dbus_path_from_name(name);
-        if (!path)
+        dbus_path = unit_dbus_path_from_name(name);
+        if (!dbus_path)
                 return log_oom();
 
         r = bus_map_all_properties(bus,
                                    "org.freedesktop.systemd1",
-                                   path,
+                                   dbus_path,
                                    map[arg_dependency],
                                    BUS_MAP_STRDUP,
                                    &error,
@@ -1941,6 +1954,7 @@ static void output_machines_list(struct machine_info *machine_infos, unsigned n)
                 statelen = STRLEN("STATE"),
                 failedlen = STRLEN("FAILED"),
                 jobslen = STRLEN("JOBS");
+        bool state_missing = false;
 
         assert(machine_infos || n == 0);
 
@@ -1951,7 +1965,7 @@ static void output_machines_list(struct machine_info *machine_infos, unsigned n)
                 failedlen = MAX(failedlen, DECIMAL_STR_WIDTH(m->n_failed_units));
                 jobslen = MAX(jobslen, DECIMAL_STR_WIDTH(m->n_jobs));
 
-                if (!arg_plain && !streq_ptr(m->state, "running"))
+                if (!arg_plain && m->state && !streq(m->state, "running"))
                         circle_len = 2;
         }
 
@@ -1990,9 +2004,12 @@ static void output_machines_list(struct machine_info *machine_infos, unsigned n)
                 if (circle_len > 0)
                         printf("%s%s%s ", on_state, circle ? special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE) : " ", off_state);
 
+                if (!m->state)
+                        state_missing = true;
+
                 if (m->is_host)
                         printf("%-*s (host) %s%-*s%s %s%*" PRIu32 "%s %*" PRIu32 "\n",
-                               (int) (namelen - (STRLEN(" (host)"))),
+                               (int) (namelen - strlen(" (host)")),
                                strna(m->name),
                                on_state, statelen, strna(m->state), off_state,
                                on_failed, failedlen, m->n_failed_units, off_failed,
@@ -2005,8 +2022,12 @@ static void output_machines_list(struct machine_info *machine_infos, unsigned n)
                                jobslen, m->n_jobs);
         }
 
-        if (!arg_no_legend)
-                printf("\n%u machines listed.\n", n);
+        if (!arg_no_legend) {
+                printf("\n");
+                if (state_missing && geteuid() != 0)
+                        printf("Notice: some information only available to privileged users was not shown.\n");
+                printf("%u machines listed.\n", n);
+        }
 }
 
 static int list_machines(int argc, char *argv[], void *userdata) {
@@ -2499,13 +2520,14 @@ static int unit_find_paths(
         int r;
 
         /**
-         * Finds where the unit is defined on disk. Returns 0 if the unit is not found. Returns 1 if it is found, and
-         * sets:
+         * Finds where the unit is defined on disk. Returns 0 if the unit is not found. Returns 1 if it is
+         * found, and sets:
          * - the path to the unit in *ret_frament_path, if it exists on disk,
-         * - and a strv of existing drop-ins in *ret_dropin_paths, if the arg is not NULL and any dropins were found.
+         * - and a strv of existing drop-ins in *ret_dropin_paths, if the arg is not NULL and any dropins
+         *   were found.
          *
-         * Returns -ERFKILL if the unit is masked, and -EKEYREJECTED if the unit file could not be loaded for some
-         * reason (the latter only applies if we are going through the service manager)
+         * Returns -ERFKILL if the unit is masked, and -EKEYREJECTED if the unit file could not be loaded for
+         * some reason (the latter only applies if we are going through the service manager).
          */
 
         assert(unit_name);
@@ -2517,16 +2539,16 @@ static int unit_find_paths(
             !install_client_side() &&
             !unit_name_is_valid(unit_name, UNIT_NAME_TEMPLATE)) {
                 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-                _cleanup_free_ char *load_state = NULL, *unit = NULL;
+                _cleanup_free_ char *load_state = NULL, *dbus_path = NULL;
 
-                unit = unit_dbus_path_from_name(unit_name);
-                if (!unit)
+                dbus_path = unit_dbus_path_from_name(unit_name);
+                if (!dbus_path)
                         return log_oom();
 
                 r = sd_bus_get_property_string(
                                 bus,
                                 "org.freedesktop.systemd1",
-                                unit,
+                                dbus_path,
                                 "org.freedesktop.systemd1.Unit",
                                 "LoadState",
                                 &error,
@@ -2540,13 +2562,13 @@ static int unit_find_paths(
                         r = 0;
                         goto not_found;
                 }
-                if (!streq(load_state, "loaded"))
+                if (!STR_IN_SET(load_state, "loaded", "bad-setting"))
                         return -EKEYREJECTED;
 
                 r = sd_bus_get_property_string(
                                 bus,
                                 "org.freedesktop.systemd1",
-                                unit,
+                                dbus_path,
                                 "org.freedesktop.systemd1.Unit",
                                 "FragmentPath",
                                 &error,
@@ -2558,7 +2580,7 @@ static int unit_find_paths(
                         r = sd_bus_get_property_strv(
                                         bus,
                                         "org.freedesktop.systemd1",
-                                        unit,
+                                        dbus_path,
                                         "org.freedesktop.systemd1.Unit",
                                         "DropInPaths",
                                         &error,
@@ -2567,53 +2589,41 @@ static int unit_find_paths(
                                 return log_error_errno(r, "Failed to get DropInPaths: %s", bus_error_message(&error, r));
                 }
         } else {
-                _cleanup_set_free_ Set *names = NULL;
-                _cleanup_free_ char *template = NULL;
+                const char *_path;
+                _cleanup_set_free_free_ Set *names = NULL;
 
-                names = set_new(NULL);
-                if (!names)
-                        return log_oom();
+                if (!cached_name_map) {
+                        r = unit_file_build_name_map(lp, &cached_id_map, &cached_name_map, NULL);
+                        if (r < 0)
+                                return r;
+                }
 
-                r = unit_find_template_path(unit_name, lp, &path, &template);
+                r = unit_file_find_fragment(cached_id_map, cached_name_map, unit_name, &_path, &names);
                 if (r < 0)
                         return r;
-                if (r > 0) {
-                        if (null_or_empty_path(path))
-                                /* The template is masked. Let's cut the process short. */
-                                return -ERFKILL;
-
-                        /* We found the unit file. If we followed symlinks, this name might be
-                         * different then the unit_name with started with. Look for dropins matching
-                         * that "final" name. */
-                        r = set_put(names, basename(path));
-                } else if (!template)
-                        /* No unit file, let's look for dropins matching the original name.
-                         * systemd has fairly complicated rules (based on unit type and provenience),
-                         * which units are allowed not to have the main unit file. We err on the
-                         * side of including too many files, and always try to load dropins. */
-                        r = set_put(names, unit_name);
-                else
-                        /* The cases where we allow a unit to exist without the main file are
-                         * never valid for templates. Don't try to load dropins in this case. */
-                        goto not_found;
 
-                if (r < 0)
-                        return log_error_errno(r, "Failed to add unit name: %m");
+                if (_path) {
+                        path = strdup(_path);
+                        if (!path)
+                                return log_oom();
+                }
 
                 if (ret_dropin_paths) {
-                        r = unit_file_find_dropin_conf_paths(arg_root, lp->search_path, NULL, names, &dropins);
+                        r = unit_file_find_dropin_paths(arg_root, lp->search_path, NULL,
+                                                        ".d", ".conf",
+                                                        names, &dropins);
                         if (r < 0)
                                 return r;
                 }
         }
 
+        if (isempty(path)) {
+                *ret_fragment_path = NULL;
                 r = 0;
-
-        if (!isempty(path)) {
+        } else {
                 *ret_fragment_path = TAKE_PTR(path);
                 r = 1;
-        } else
-                *ret_fragment_path = NULL;
+        }
 
         if (ret_dropin_paths) {
                 if (!strv_isempty(dropins)) {
@@ -2632,21 +2642,21 @@ static int unit_find_paths(
 
 static int get_state_one_unit(sd_bus *bus, const char *name, UnitActiveState *active_state) {
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        _cleanup_free_ char *buf = NULL, *path = NULL;
+        _cleanup_free_ char *buf = NULL, *dbus_path = NULL;
         UnitActiveState state;
         int r;
 
         assert(name);
         assert(active_state);
 
-        path = unit_dbus_path_from_name(name);
-        if (!path)
+        dbus_path = unit_dbus_path_from_name(name);
+        if (!dbus_path)
                 return log_oom();
 
         r = sd_bus_get_property_string(
                         bus,
                         "org.freedesktop.systemd1",
-                        path,
+                        dbus_path,
                         "org.freedesktop.systemd1.Unit",
                         "ActiveState",
                         &error,
@@ -2689,7 +2699,7 @@ static int unit_is_masked(sd_bus *bus, LookupPaths *lp, const char *name) {
 
 static int check_triggering_units(sd_bus *bus, const char *name) {
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        _cleanup_free_ char *n = NULL, *path = NULL, *load_state = NULL;
+        _cleanup_free_ char *n = NULL, *dbus_path = NULL, *load_state = NULL;
         _cleanup_strv_free_ char **triggered_by = NULL;
         bool print_warning_label = true;
         UnitActiveState active_state;
@@ -2707,14 +2717,14 @@ static int check_triggering_units(sd_bus *bus, const char *name) {
         if (streq(load_state, "masked"))
                 return 0;
 
-        path = unit_dbus_path_from_name(n);
-        if (!path)
+        dbus_path = unit_dbus_path_from_name(n);
+        if (!dbus_path)
                 return log_oom();
 
         r = sd_bus_get_property_strv(
                         bus,
                         "org.freedesktop.systemd1",
-                        path,
+                        dbus_path,
                         "org.freedesktop.systemd1.Unit",
                         "TriggeredBy",
                         &error,
@@ -2780,158 +2790,6 @@ static const char *verb_to_job_type(const char *verb) {
        return "start";
 }
 
-typedef struct {
-        sd_bus_slot *match;
-        sd_event *event;
-        Set *unit_paths;
-        bool any_failed;
-} WaitContext;
-
-static void wait_context_free(WaitContext *c) {
-        c->match = sd_bus_slot_unref(c->match);
-        c->event = sd_event_unref(c->event);
-        c->unit_paths = set_free_free(c->unit_paths);
-}
-
-static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error *error) {
-        const char *path, *interface, *active_state = NULL, *job_path = NULL;
-        WaitContext *c = userdata;
-        bool is_failed;
-        int r;
-
-        /* Called whenever we get a PropertiesChanged signal. Checks if ActiveState changed to inactive/failed.
-         *
-         * Signal parameters: (s interface, a{sv} changed_properties, as invalidated_properties) */
-
-        path = sd_bus_message_get_path(m);
-        if (!set_contains(c->unit_paths, path))
-                return 0;
-
-        r = sd_bus_message_read(m, "s", &interface);
-        if (r < 0)
-                return bus_log_parse_error(r);
-
-        if (!streq(interface, "org.freedesktop.systemd1.Unit")) /* ActiveState is on the Unit interface */
-                return 0;
-
-        r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "{sv}");
-        if (r < 0)
-                return bus_log_parse_error(r);
-
-        for (;;) {
-                const char *s;
-
-                r = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, "sv");
-                if (r < 0)
-                        return bus_log_parse_error(r);
-                if (r == 0) /* end of array */
-                        break;
-
-                r = sd_bus_message_read(m, "s", &s); /* Property name */
-                if (r < 0)
-                        return bus_log_parse_error(r);
-
-                if (streq(s, "ActiveState")) {
-                        r = sd_bus_message_read(m, "v", "s", &active_state);
-                        if (r < 0)
-                                return bus_log_parse_error(r);
-
-                        if (job_path) /* Found everything we need */
-                                break;
-
-                } else if (streq(s, "Job")) {
-                        uint32_t job_id;
-
-                        r = sd_bus_message_read(m, "v", "(uo)", &job_id, &job_path);
-                        if (r < 0)
-                                return bus_log_parse_error(r);
-
-                        /* There's still a job pending for this unit, let's ignore this for now, and return right-away. */
-                        if (job_id != 0)
-                                return 0;
-
-                        if (active_state) /* Found everything we need */
-                                break;
-
-                } else {
-                        r = sd_bus_message_skip(m, "v"); /* Other property */
-                        if (r < 0)
-                                return bus_log_parse_error(r);
-                }
-
-                r = sd_bus_message_exit_container(m);
-                if (r < 0)
-                        return bus_log_parse_error(r);
-        }
-
-        /* If this didn't contain the ActiveState property we can't do anything */
-        if (!active_state)
-                return 0;
-
-        is_failed = streq(active_state, "failed");
-        if (streq(active_state, "inactive") || is_failed) {
-                log_debug("%s became %s, dropping from --wait tracking", path, active_state);
-                free(set_remove(c->unit_paths, path));
-                c->any_failed = c->any_failed || is_failed;
-        } else
-                log_debug("ActiveState on %s changed to %s", path, active_state);
-
-        if (set_isempty(c->unit_paths))
-                sd_event_exit(c->event, EXIT_SUCCESS);
-
-        return 0;
-}
-
-static int wait_context_watch(
-                WaitContext *wait_context,
-                sd_bus *bus,
-                const char *name) {
-
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        _cleanup_free_ char *unit_path = NULL;
-        int r;
-
-        assert(wait_context);
-        assert(name);
-
-        log_debug("Watching for property changes of %s", name);
-        r = sd_bus_call_method(
-                        bus,
-                        "org.freedesktop.systemd1",
-                        "/org/freedesktop/systemd1",
-                        "org.freedesktop.systemd1.Manager",
-                        "RefUnit",
-                        &error,
-                        NULL,
-                        "s", name);
-        if (r < 0)
-                return log_error_errno(r, "Failed to add reference to unit %s: %s", name, bus_error_message(&error, r));
-
-        unit_path = unit_dbus_path_from_name(name);
-        if (!unit_path)
-                return log_oom();
-
-        r = set_ensure_allocated(&wait_context->unit_paths, &string_hash_ops);
-        if (r < 0)
-                return log_oom();
-
-        r = set_put_strdup(wait_context->unit_paths, unit_path);
-        if (r < 0)
-                return log_error_errno(r, "Failed to add unit path %s to set: %m", unit_path);
-
-        r = sd_bus_match_signal_async(bus,
-                                      &wait_context->match,
-                                      NULL,
-                                      unit_path,
-                                      "org.freedesktop.DBus.Properties",
-                                      "PropertiesChanged",
-                                      on_properties_changed, NULL, wait_context);
-        if (r < 0)
-                return log_error_errno(r, "Failed to request match for PropertiesChanged signal: %m");
-
-        return 0;
-}
-
 static int start_unit_one(
                 sd_bus *bus,
                 const char *method,    /* When using classic per-job bus methods */
@@ -2940,7 +2798,7 @@ static int start_unit_one(
                 const char *mode,
                 sd_bus_error *error,
                 BusWaitForJobs *w,
-                WaitContext *wait_context) {
+                BusWaitForUnits *wu) {
 
         _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
         const char *path;
@@ -2952,12 +2810,6 @@ static int start_unit_one(
         assert(mode);
         assert(error);
 
-        if (wait_context) {
-                r = wait_context_watch(wait_context, bus, name);
-                if (r < 0)
-                        return r;
-        }
-
         log_debug("%s dbus call org.freedesktop.systemd1.Manager %s(%s, %s)",
                   arg_dry_run ? "Would execute" : "Executing",
                   method, name, mode);
@@ -3046,6 +2898,12 @@ static int start_unit_one(
                         return log_error_errno(r, "Failed to watch job for %s: %m", name);
         }
 
+        if (wu) {
+                r = bus_wait_for_units_add_unit(wu, name, BUS_WAIT_FOR_INACTIVE|BUS_WAIT_NO_JOB, NULL, NULL);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to watch unit %s: %m", name);
+        }
+
         return 0;
 
 fail:
@@ -3160,6 +3018,8 @@ static enum action verb_to_action(const char *verb) {
 static const char** make_extra_args(const char *extra_args[static 4]) {
         size_t n = 0;
 
+        assert(extra_args);
+
         if (arg_scope != UNIT_FILE_SYSTEM)
                 extra_args[n++] = "--user";
 
@@ -3177,8 +3037,8 @@ static const char** make_extra_args(const char *extra_args[static 4]) {
 }
 
 static int start_unit(int argc, char *argv[], void *userdata) {
+        _cleanup_(bus_wait_for_units_freep) BusWaitForUnits *wu = NULL;
         _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
-        _cleanup_(wait_context_free) WaitContext wait_context = {};
         const char *method, *job_type, *mode, *one_name, *suffix = NULL;
         _cleanup_free_ char **stopped_units = NULL; /* Do not use _cleanup_strv_free_ */
         _cleanup_strv_free_ char **names = NULL;
@@ -3266,19 +3126,15 @@ static int start_unit(int argc, char *argv[], void *userdata) {
                 if (r < 0)
                         return log_error_errno(r, "Failed to enable subscription: %m");
 
-                r = sd_event_default(&wait_context.event);
+                r = bus_wait_for_units_new(bus, &wu);
                 if (r < 0)
-                        return log_error_errno(r, "Failed to allocate event loop: %m");
-
-                r = sd_bus_attach_event(bus, wait_context.event, 0);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to attach bus to event loop: %m");
+                        return log_error_errno(r, "Failed to allocate unit watch context: %m");
         }
 
         STRV_FOREACH(name, names) {
                 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
 
-                r = start_unit_one(bus, method, job_type, *name, mode, &error, w, arg_wait ? &wait_context : NULL);
+                r = start_unit_one(bus, method, job_type, *name, mode, &error, w, wu);
                 if (ret == EXIT_SUCCESS && r < 0)
                         ret = translate_bus_error_to_exit_status(r, &error);
 
@@ -3303,11 +3159,11 @@ static int start_unit(int argc, char *argv[], void *userdata) {
                                 (void) check_triggering_units(bus, *name);
         }
 
-        if (ret == EXIT_SUCCESS && arg_wait && !set_isempty(wait_context.unit_paths)) {
-                r = sd_event_loop(wait_context.event);
+        if (arg_wait) {
+                r = bus_wait_for_units_run(wu);
                 if (r < 0)
-                        return log_error_errno(r, "Failed to run event loop: %m");
-                if (wait_context.any_failed)
+                        return log_error_errno(r, "Failed to wait for units: %m");
+                if (r == BUS_WAIT_FAILURE && ret == EXIT_SUCCESS)
                         ret = EXIT_FAILURE;
         }
 
@@ -3941,6 +3797,101 @@ static int kill_unit(int argc, char *argv[], void *userdata) {
         return r;
 }
 
+static int clean_unit(int argc, char *argv[], void *userdata) {
+        _cleanup_(bus_wait_for_units_freep) BusWaitForUnits *w = NULL;
+        _cleanup_strv_free_ char **names = NULL;
+        int r, ret = EXIT_SUCCESS;
+        char **name;
+        sd_bus *bus;
+
+        r = acquire_bus(BUS_FULL, &bus);
+        if (r < 0)
+                return r;
+
+        polkit_agent_open_maybe();
+
+        if (!arg_clean_what) {
+                arg_clean_what = strv_new("cache", "runtime");
+                if (!arg_clean_what)
+                        return log_oom();
+        }
+
+        r = expand_names(bus, strv_skip(argv, 1), NULL, &names);
+        if (r < 0)
+                return log_error_errno(r, "Failed to expand names: %m");
+
+        if (!arg_no_block) {
+                r = bus_wait_for_units_new(bus, &w);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to allocate unit waiter: %m");
+        }
+
+        STRV_FOREACH(name, names) {
+                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+                _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+
+                if (w) {
+                        /* If we shall wait for the cleaning to complete, let's add a ref on the unit first */
+                        r = sd_bus_call_method(
+                                        bus,
+                                        "org.freedesktop.systemd1",
+                                        "/org/freedesktop/systemd1",
+                                        "org.freedesktop.systemd1.Manager",
+                                        "RefUnit",
+                                        &error,
+                                        NULL,
+                                        "s", *name);
+                        if (r < 0) {
+                                log_error_errno(r, "Failed to add reference to unit %s: %s", *name, bus_error_message(&error, r));
+                                if (ret == EXIT_SUCCESS)
+                                        ret = r;
+                                continue;
+                        }
+                }
+
+                r = sd_bus_message_new_method_call(
+                                bus,
+                                &m,
+                                "org.freedesktop.systemd1",
+                                "/org/freedesktop/systemd1",
+                                "org.freedesktop.systemd1.Manager",
+                                "CleanUnit");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_append(m, "s", *name);
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_append_strv(m, arg_clean_what);
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_call(bus, m, 0, &error, NULL);
+                if (r < 0) {
+                        log_error_errno(r, "Failed to clean unit %s: %s", *name, bus_error_message(&error, r));
+                        if (ret == EXIT_SUCCESS) {
+                                ret = r;
+                                continue;
+                        }
+                }
+
+                if (w) {
+                        r = bus_wait_for_units_add_unit(w, *name, BUS_WAIT_REFFED|BUS_WAIT_FOR_MAINTENANCE_END, NULL, NULL);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to watch unit %s: %m", *name);
+                }
+        }
+
+        r = bus_wait_for_units_run(w);
+        if (r < 0)
+                return log_error_errno(r, "Failed to wait for units: %m");
+        if (r == BUS_WAIT_FAILURE)
+                ret = EXIT_FAILURE;
+
+        return ret;
+}
+
 typedef struct ExecStatusInfo {
         char *name;
 
@@ -3955,6 +3906,8 @@ typedef struct ExecStatusInfo {
         int code;
         int status;
 
+        ExecCommandFlags flags;
+
         LIST_FIELDS(struct ExecStatusInfo, exec);
 } ExecStatusInfo;
 
@@ -3967,7 +3920,8 @@ static void exec_status_info_free(ExecStatusInfo *i) {
         free(i);
 }
 
-static int exec_status_info_deserialize(sd_bus_message *m, ExecStatusInfo *i) {
+static int exec_status_info_deserialize(sd_bus_message *m, ExecStatusInfo *i, bool is_ex_prop) {
+        _cleanup_strv_free_ char **ex_opts = NULL;
         uint64_t start_timestamp, exit_timestamp, start_timestamp_monotonic, exit_timestamp_monotonic;
         const char *path;
         uint32_t pid;
@@ -3977,7 +3931,7 @@ static int exec_status_info_deserialize(sd_bus_message *m, ExecStatusInfo *i) {
         assert(m);
         assert(i);
 
-        r = sd_bus_message_enter_container(m, SD_BUS_TYPE_STRUCT, "sasbttttuii");
+        r = sd_bus_message_enter_container(m, SD_BUS_TYPE_STRUCT, is_ex_prop ? "sasasttttuii" : "sasbttttuii");
         if (r < 0)
                 return bus_log_parse_error(r);
         else if (r == 0)
@@ -3995,9 +3949,12 @@ static int exec_status_info_deserialize(sd_bus_message *m, ExecStatusInfo *i) {
         if (r < 0)
                 return bus_log_parse_error(r);
 
+        r = is_ex_prop ? sd_bus_message_read_strv(m, &ex_opts) : sd_bus_message_read(m, "b", &ignore);
+        if (r < 0)
+                return bus_log_parse_error(r);
+
         r = sd_bus_message_read(m,
-                                "bttttuii",
-                                &ignore,
+                                "ttttuii",
                                 &start_timestamp, &start_timestamp_monotonic,
                                 &exit_timestamp, &exit_timestamp_monotonic,
                                 &pid,
@@ -4005,7 +3962,15 @@ static int exec_status_info_deserialize(sd_bus_message *m, ExecStatusInfo *i) {
         if (r < 0)
                 return bus_log_parse_error(r);
 
-        i->ignore = ignore;
+        if (is_ex_prop) {
+                r = exec_command_flags_from_strv(ex_opts, &i->flags);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to convert strv to ExecCommandFlags: %m");
+
+                i->ignore = FLAGS_SET(i->flags, EXEC_COMMAND_IGNORE_FAILURE);
+        } else
+                i->ignore = ignore;
+
         i->start_timestamp = (usec_t) start_timestamp;
         i->exit_timestamp = (usec_t) exit_timestamp;
         i->pid = (pid_t) pid;
@@ -4477,7 +4442,7 @@ static void print_status_info(
         if (i->status_text)
                 printf("   Status: \"%s\"\n", i->status_text);
         if (i->status_errno > 0)
-                printf("    Error: %i (%s)\n", i->status_errno, strerror(i->status_errno));
+                printf("    Error: %i (%s)\n", i->status_errno, strerror_safe(i->status_errno));
 
         if (i->ip_ingress_bytes != (uint64_t) -1 && i->ip_egress_bytes != (uint64_t) -1) {
                 char buf_in[FORMAT_BYTES_MAX], buf_out[FORMAT_BYTES_MAX];
@@ -4578,7 +4543,8 @@ static void print_status_info(
 
                         show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, i->control_group, prefix, c, extra, k, get_output_flags());
                 } else if (r < 0)
-                        log_warning_errno(r, "Failed to dump process list for '%s', ignoring: %s", i->id, bus_error_message(&error, r));
+                        log_warning_errno(r, "Failed to dump process list for '%s', ignoring: %s",
+                                          i->id, bus_error_message(&error, r));
         }
 
         if (i->id && arg_transport == BUS_TRANSPORT_LOCAL)
@@ -4703,7 +4669,6 @@ static int map_conditions(sd_bus *bus, const char *member, sd_bus_message *m, sd
                 if (!c->name || !c->param)
                         return -ENOMEM;
 
-
                 LIST_PREPEND(conditions, i->conditions, TAKE_PTR(c));
         }
         if (r < 0)
@@ -4749,9 +4714,10 @@ static int map_exec(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_e
         _cleanup_free_ ExecStatusInfo *info = NULL;
         ExecStatusInfo *last;
         UnitStatusInfo *i = userdata;
+        bool is_ex_prop = endswith(member, "Ex");
         int r;
 
-        r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sasbttttuii)");
+        r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, is_ex_prop ? "(sasasttttuii)" : "(sasbttttuii)");
         if (r < 0)
                 return r;
 
@@ -4761,7 +4727,7 @@ static int map_exec(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_e
 
         LIST_FIND_TAIL(exec, i->exec, last);
 
-        while ((r = exec_status_info_deserialize(m, info)) > 0) {
+        while ((r = exec_status_info_deserialize(m, info, is_ex_prop)) > 0) {
 
                 info->name = strdup(member);
                 if (!info->name)
@@ -4814,6 +4780,16 @@ static int print_property(const char *name, const char *expected_value, sd_bus_m
                         else if (all)
                                 bus_print_property_value(name, expected_value, value, "[not set]");
 
+                        return 1;
+                } else if (streq(name, "NUMAPolicy")) {
+                        int32_t i;
+
+                        r = sd_bus_message_read_basic(m, bus_type, &i);
+                        if (r < 0)
+                                return r;
+
+                        bus_print_property_valuef(name, expected_value, value, "%s", strna(mpol_to_string(i)));
+
                         return 1;
                 }
                 break;
@@ -5052,11 +5028,13 @@ static int print_property(const char *name, const char *expected_value, sd_bus_m
                                 return bus_log_parse_error(r);
 
                         while ((r = sd_bus_message_read(m, "(stt)", &base, &v, &next_elapse)) > 0) {
-                                char timespan1[FORMAT_TIMESPAN_MAX], timespan2[FORMAT_TIMESPAN_MAX];
+                                char timespan1[FORMAT_TIMESPAN_MAX] = "n/a", timespan2[FORMAT_TIMESPAN_MAX] = "n/a";
+
+                                (void) format_timespan(timespan1, sizeof timespan1, v, 0);
+                                (void) format_timespan(timespan2, sizeof timespan2, next_elapse, 0);
 
-                                bus_print_property_valuef(name, expected_value, value, "{ %s=%s ; next_elapse=%s }", base,
-                                                          format_timespan(timespan1, sizeof(timespan1), v, 0),
-                                                          format_timespan(timespan2, sizeof(timespan2), next_elapse, 0));
+                                bus_print_property_valuef(name, expected_value, value,
+                                                          "{ %s=%s ; next_elapse=%s }", base, timespan1, timespan2);
                         }
                         if (r < 0)
                                 return bus_log_parse_error(r);
@@ -5076,10 +5054,11 @@ static int print_property(const char *name, const char *expected_value, sd_bus_m
                                 return bus_log_parse_error(r);
 
                         while ((r = sd_bus_message_read(m, "(sst)", &base, &spec, &next_elapse)) > 0) {
-                                char timestamp[FORMAT_TIMESTAMP_MAX];
+                                char timestamp[FORMAT_TIMESTAMP_MAX] = "n/a";
 
-                                bus_print_property_valuef(name, expected_value, value, "{ %s=%s ; next_elapse=%s }", base, spec,
-                                                          format_timestamp(timestamp, sizeof(timestamp), next_elapse));
+                                (void) format_timestamp(timestamp, sizeof(timestamp), next_elapse);
+                                bus_print_property_valuef(name, expected_value, value,
+                                                          "{ %s=%s ; next_elapse=%s }", base, spec, timestamp);
                         }
                         if (r < 0)
                                 return bus_log_parse_error(r);
@@ -5092,29 +5071,51 @@ static int print_property(const char *name, const char *expected_value, sd_bus_m
 
                 } else if (contents[0] == SD_BUS_TYPE_STRUCT_BEGIN && startswith(name, "Exec")) {
                         ExecStatusInfo info = {};
+                        bool is_ex_prop = endswith(name, "Ex");
 
-                        r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sasbttttuii)");
+                        r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, is_ex_prop ? "(sasasttttuii)" : "(sasbttttuii)");
                         if (r < 0)
                                 return bus_log_parse_error(r);
 
-                        while ((r = exec_status_info_deserialize(m, &info)) > 0) {
+                        while ((r = exec_status_info_deserialize(m, &info, is_ex_prop)) > 0) {
                                 char timestamp1[FORMAT_TIMESTAMP_MAX], timestamp2[FORMAT_TIMESTAMP_MAX];
-                                _cleanup_free_ char *tt;
+                                _cleanup_strv_free_ char **optv = NULL;
+                                _cleanup_free_ char *tt, *o = NULL;
 
                                 tt = strv_join(info.argv, " ");
 
-                                 bus_print_property_valuef(name, expected_value, value,
-                                                           "{ path=%s ; argv[]=%s ; ignore_errors=%s ; start_time=[%s] ; stop_time=[%s] ; pid="PID_FMT" ; code=%s ; status=%i%s%s }",
-                                                           strna(info.path),
-                                                           strna(tt),
-                                                           yes_no(info.ignore),
-                                                           strna(format_timestamp(timestamp1, sizeof(timestamp1), info.start_timestamp)),
-                                                           strna(format_timestamp(timestamp2, sizeof(timestamp2), info.exit_timestamp)),
-                                                           info.pid,
-                                                           sigchld_code_to_string(info.code),
-                                                           info.status,
-                                                           info.code == CLD_EXITED ? "" : "/",
-                                                           strempty(info.code == CLD_EXITED ? NULL : signal_to_string(info.status)));
+                                if (is_ex_prop) {
+                                        r = exec_command_flags_to_strv(info.flags, &optv);
+                                        if (r < 0)
+                                                return log_error_errno(r, "Failed to convert ExecCommandFlags to strv: %m");
+
+                                        o = strv_join(optv, " ");
+
+                                        bus_print_property_valuef(name, expected_value, value,
+                                                                  "{ path=%s ; argv[]=%s ; flags=%s ; start_time=[%s] ; stop_time=[%s] ; pid="PID_FMT" ; code=%s ; status=%i%s%s }",
+                                                                  strna(info.path),
+                                                                  strna(tt),
+                                                                  strna(o),
+                                                                  strna(format_timestamp(timestamp1, sizeof(timestamp1), info.start_timestamp)),
+                                                                  strna(format_timestamp(timestamp2, sizeof(timestamp2), info.exit_timestamp)),
+                                                                  info.pid,
+                                                                  sigchld_code_to_string(info.code),
+                                                                  info.status,
+                                                                  info.code == CLD_EXITED ? "" : "/",
+                                                                  strempty(info.code == CLD_EXITED ? NULL : signal_to_string(info.status)));
+                                } else
+                                        bus_print_property_valuef(name, expected_value, value,
+                                                                  "{ path=%s ; argv[]=%s ; ignore_errors=%s ; start_time=[%s] ; stop_time=[%s] ; pid="PID_FMT" ; code=%s ; status=%i%s%s }",
+                                                                  strna(info.path),
+                                                                  strna(tt),
+                                                                  yes_no(info.ignore),
+                                                                  strna(format_timestamp(timestamp1, sizeof(timestamp1), info.start_timestamp)),
+                                                                  strna(format_timestamp(timestamp2, sizeof(timestamp2), info.exit_timestamp)),
+                                                                  info.pid,
+                                                                  sigchld_code_to_string(info.code),
+                                                                  info.status,
+                                                                  info.code == CLD_EXITED ? "" : "/",
+                                                                  strempty(info.code == CLD_EXITED ? NULL : signal_to_string(info.status)));
 
                                 free(info.path);
                                 strv_free(info.argv);
@@ -5405,7 +5406,7 @@ static int print_property(const char *name, const char *expected_value, sd_bus_m
                                 bus_print_property_value(name, expected_value, value, strempty(fields));
 
                         return 1;
-                } else if (contents[0] == SD_BUS_TYPE_BYTE && streq(name, "CPUAffinity")) {
+                } else if (contents[0] == SD_BUS_TYPE_BYTE && STR_IN_SET(name, "CPUAffinity", "NUMAMask")) {
                         _cleanup_free_ char *affinity = NULL;
                         _cleanup_(cpu_set_reset) CPUSet set = {};
                         const void *a;
@@ -5417,7 +5418,7 @@ static int print_property(const char *name, const char *expected_value, sd_bus_m
 
                         r = cpu_set_from_dbus(a, n, &set);
                         if (r < 0)
-                                return log_error_errno(r, "Failed to deserialize CPUAffinity: %m");
+                                return log_error_errno(r, "Failed to deserialize %s: %m", name);
 
                         affinity = cpu_set_to_range_string(&set);
                         if (!affinity)
@@ -5459,82 +5460,85 @@ static int show_one(
                 bool *ellipsized) {
 
         static const struct bus_properties_map property_map[] = {
-                { "LoadState",                      "s",              NULL,           offsetof(UnitStatusInfo, load_state)                        },
-                { "ActiveState",                    "s",              NULL,           offsetof(UnitStatusInfo, active_state)                      },
-                { "Documentation",                  "as",             NULL,           offsetof(UnitStatusInfo, documentation)                     },
+                { "LoadState",                      "s",               NULL,           offsetof(UnitStatusInfo, load_state)                        },
+                { "ActiveState",                    "s",               NULL,           offsetof(UnitStatusInfo, active_state)                      },
+                { "Documentation",                  "as",              NULL,           offsetof(UnitStatusInfo, documentation)                     },
                 {}
         }, status_map[] = {
-                { "Id",                             "s",              NULL,           offsetof(UnitStatusInfo, id)                                },
-                { "LoadState",                      "s",              NULL,           offsetof(UnitStatusInfo, load_state)                        },
-                { "ActiveState",                    "s",              NULL,           offsetof(UnitStatusInfo, active_state)                      },
-                { "SubState",                       "s",              NULL,           offsetof(UnitStatusInfo, sub_state)                         },
-                { "UnitFileState",                  "s",              NULL,           offsetof(UnitStatusInfo, unit_file_state)                   },
-                { "UnitFilePreset",                 "s",              NULL,           offsetof(UnitStatusInfo, unit_file_preset)                  },
-                { "Description",                    "s",              NULL,           offsetof(UnitStatusInfo, description)                       },
-                { "Following",                      "s",              NULL,           offsetof(UnitStatusInfo, following)                         },
-                { "Documentation",                  "as",             NULL,           offsetof(UnitStatusInfo, documentation)                     },
-                { "FragmentPath",                   "s",              NULL,           offsetof(UnitStatusInfo, fragment_path)                     },
-                { "SourcePath",                     "s",              NULL,           offsetof(UnitStatusInfo, source_path)                       },
-                { "ControlGroup",                   "s",              NULL,           offsetof(UnitStatusInfo, control_group)                     },
-                { "DropInPaths",                    "as",             NULL,           offsetof(UnitStatusInfo, dropin_paths)                      },
-                { "LoadError",                      "(ss)",           map_load_error, offsetof(UnitStatusInfo, load_error)                        },
-                { "Result",                         "s",              NULL,           offsetof(UnitStatusInfo, result)                            },
-                { "InactiveExitTimestamp",          "t",              NULL,           offsetof(UnitStatusInfo, inactive_exit_timestamp)           },
-                { "InactiveExitTimestampMonotonic", "t",              NULL,           offsetof(UnitStatusInfo, inactive_exit_timestamp_monotonic) },
-                { "ActiveEnterTimestamp",           "t",              NULL,           offsetof(UnitStatusInfo, active_enter_timestamp)            },
-                { "ActiveExitTimestamp",            "t",              NULL,           offsetof(UnitStatusInfo, active_exit_timestamp)             },
-                { "InactiveEnterTimestamp",         "t",              NULL,           offsetof(UnitStatusInfo, inactive_enter_timestamp)          },
-                { "NeedDaemonReload",               "b",              NULL,           offsetof(UnitStatusInfo, need_daemon_reload)                },
-                { "Transient",                      "b",              NULL,           offsetof(UnitStatusInfo, transient)                         },
-                { "ExecMainPID",                    "u",              NULL,           offsetof(UnitStatusInfo, main_pid)                          },
-                { "MainPID",                        "u",              map_main_pid,   0                                                           },
-                { "ControlPID",                     "u",              NULL,           offsetof(UnitStatusInfo, control_pid)                       },
-                { "StatusText",                     "s",              NULL,           offsetof(UnitStatusInfo, status_text)                       },
-                { "PIDFile",                        "s",              NULL,           offsetof(UnitStatusInfo, pid_file)                          },
-                { "StatusErrno",                    "i",              NULL,           offsetof(UnitStatusInfo, status_errno)                      },
-                { "ExecMainStartTimestamp",         "t",              NULL,           offsetof(UnitStatusInfo, start_timestamp)                   },
-                { "ExecMainExitTimestamp",          "t",              NULL,           offsetof(UnitStatusInfo, exit_timestamp)                    },
-                { "ExecMainCode",                   "i",              NULL,           offsetof(UnitStatusInfo, exit_code)                         },
-                { "ExecMainStatus",                 "i",              NULL,           offsetof(UnitStatusInfo, exit_status)                       },
-                { "ConditionTimestamp",             "t",              NULL,           offsetof(UnitStatusInfo, condition_timestamp)               },
-                { "ConditionResult",                "b",              NULL,           offsetof(UnitStatusInfo, condition_result)                  },
-                { "Conditions",                     "a(sbbsi)",       map_conditions, 0                                                           },
-                { "AssertTimestamp",                "t",              NULL,           offsetof(UnitStatusInfo, assert_timestamp)                  },
-                { "AssertResult",                   "b",              NULL,           offsetof(UnitStatusInfo, assert_result)                     },
-                { "Asserts",                        "a(sbbsi)",       map_asserts,    0                                                           },
-                { "NextElapseUSecRealtime",         "t",              NULL,           offsetof(UnitStatusInfo, next_elapse_real)                  },
-                { "NextElapseUSecMonotonic",        "t",              NULL,           offsetof(UnitStatusInfo, next_elapse_monotonic)             },
-                { "NAccepted",                      "u",              NULL,           offsetof(UnitStatusInfo, n_accepted)                        },
-                { "NConnections",                   "u",              NULL,           offsetof(UnitStatusInfo, n_connections)                     },
-                { "NRefused",                       "u",              NULL,           offsetof(UnitStatusInfo, n_refused)                         },
-                { "Accept",                         "b",              NULL,           offsetof(UnitStatusInfo, accept)                            },
-                { "Listen",                         "a(ss)",          map_listen,     offsetof(UnitStatusInfo, listen)                            },
-                { "SysFSPath",                      "s",              NULL,           offsetof(UnitStatusInfo, sysfs_path)                        },
-                { "Where",                          "s",              NULL,           offsetof(UnitStatusInfo, where)                             },
-                { "What",                           "s",              NULL,           offsetof(UnitStatusInfo, what)                              },
-                { "MemoryCurrent",                  "t",              NULL,           offsetof(UnitStatusInfo, memory_current)                    },
-                { "DefaultMemoryMin",               "t",              NULL,           offsetof(UnitStatusInfo, default_memory_min)                },
-                { "DefaultMemoryLow",               "t",              NULL,           offsetof(UnitStatusInfo, default_memory_low)                },
-                { "MemoryMin",                      "t",              NULL,           offsetof(UnitStatusInfo, memory_min)                        },
-                { "MemoryLow",                      "t",              NULL,           offsetof(UnitStatusInfo, memory_low)                        },
-                { "MemoryHigh",                     "t",              NULL,           offsetof(UnitStatusInfo, memory_high)                       },
-                { "MemoryMax",                      "t",              NULL,           offsetof(UnitStatusInfo, memory_max)                        },
-                { "MemorySwapMax",                  "t",              NULL,           offsetof(UnitStatusInfo, memory_swap_max)                   },
-                { "MemoryLimit",                    "t",              NULL,           offsetof(UnitStatusInfo, memory_limit)                      },
-                { "CPUUsageNSec",                   "t",              NULL,           offsetof(UnitStatusInfo, cpu_usage_nsec)                    },
-                { "TasksCurrent",                   "t",              NULL,           offsetof(UnitStatusInfo, tasks_current)                     },
-                { "TasksMax",                       "t",              NULL,           offsetof(UnitStatusInfo, tasks_max)                         },
-                { "IPIngressBytes",                 "t",              NULL,           offsetof(UnitStatusInfo, ip_ingress_bytes)                  },
-                { "IPEgressBytes",                  "t",              NULL,           offsetof(UnitStatusInfo, ip_egress_bytes)                   },
-                { "IOReadBytes",                    "t",              NULL,           offsetof(UnitStatusInfo, io_read_bytes)                     },
-                { "IOWriteBytes",                   "t",              NULL,           offsetof(UnitStatusInfo, io_write_bytes)                    },
-                { "ExecStartPre",                   "a(sasbttttuii)", map_exec,       0                                                           },
-                { "ExecStart",                      "a(sasbttttuii)", map_exec,       0                                                           },
-                { "ExecStartPost",                  "a(sasbttttuii)", map_exec,       0                                                           },
-                { "ExecReload",                     "a(sasbttttuii)", map_exec,       0                                                           },
-                { "ExecStopPre",                    "a(sasbttttuii)", map_exec,       0                                                           },
-                { "ExecStop",                       "a(sasbttttuii)", map_exec,       0                                                           },
-                { "ExecStopPost",                   "a(sasbttttuii)", map_exec,       0                                                           },
+                { "Id",                             "s",               NULL,           offsetof(UnitStatusInfo, id)                                },
+                { "LoadState",                      "s",               NULL,           offsetof(UnitStatusInfo, load_state)                        },
+                { "ActiveState",                    "s",               NULL,           offsetof(UnitStatusInfo, active_state)                      },
+                { "SubState",                       "s",               NULL,           offsetof(UnitStatusInfo, sub_state)                         },
+                { "UnitFileState",                  "s",               NULL,           offsetof(UnitStatusInfo, unit_file_state)                   },
+                { "UnitFilePreset",                 "s",               NULL,           offsetof(UnitStatusInfo, unit_file_preset)                  },
+                { "Description",                    "s",               NULL,           offsetof(UnitStatusInfo, description)                       },
+                { "Following",                      "s",               NULL,           offsetof(UnitStatusInfo, following)                         },
+                { "Documentation",                  "as",              NULL,           offsetof(UnitStatusInfo, documentation)                     },
+                { "FragmentPath",                   "s",               NULL,           offsetof(UnitStatusInfo, fragment_path)                     },
+                { "SourcePath",                     "s",               NULL,           offsetof(UnitStatusInfo, source_path)                       },
+                { "ControlGroup",                   "s",               NULL,           offsetof(UnitStatusInfo, control_group)                     },
+                { "DropInPaths",                    "as",              NULL,           offsetof(UnitStatusInfo, dropin_paths)                      },
+                { "LoadError",                      "(ss)",            map_load_error, offsetof(UnitStatusInfo, load_error)                        },
+                { "Result",                         "s",               NULL,           offsetof(UnitStatusInfo, result)                            },
+                { "InactiveExitTimestamp",          "t",               NULL,           offsetof(UnitStatusInfo, inactive_exit_timestamp)           },
+                { "InactiveExitTimestampMonotonic", "t",               NULL,           offsetof(UnitStatusInfo, inactive_exit_timestamp_monotonic) },
+                { "ActiveEnterTimestamp",           "t",               NULL,           offsetof(UnitStatusInfo, active_enter_timestamp)            },
+                { "ActiveExitTimestamp",            "t",               NULL,           offsetof(UnitStatusInfo, active_exit_timestamp)             },
+                { "InactiveEnterTimestamp",         "t",               NULL,           offsetof(UnitStatusInfo, inactive_enter_timestamp)          },
+                { "NeedDaemonReload",               "b",               NULL,           offsetof(UnitStatusInfo, need_daemon_reload)                },
+                { "Transient",                      "b",               NULL,           offsetof(UnitStatusInfo, transient)                         },
+                { "ExecMainPID",                    "u",               NULL,           offsetof(UnitStatusInfo, main_pid)                          },
+                { "MainPID",                        "u",               map_main_pid,   0                                                           },
+                { "ControlPID",                     "u",               NULL,           offsetof(UnitStatusInfo, control_pid)                       },
+                { "StatusText",                     "s",               NULL,           offsetof(UnitStatusInfo, status_text)                       },
+                { "PIDFile",                        "s",               NULL,           offsetof(UnitStatusInfo, pid_file)                          },
+                { "StatusErrno",                    "i",               NULL,           offsetof(UnitStatusInfo, status_errno)                      },
+                { "ExecMainStartTimestamp",         "t",               NULL,           offsetof(UnitStatusInfo, start_timestamp)                   },
+                { "ExecMainExitTimestamp",          "t",               NULL,           offsetof(UnitStatusInfo, exit_timestamp)                    },
+                { "ExecMainCode",                   "i",               NULL,           offsetof(UnitStatusInfo, exit_code)                         },
+                { "ExecMainStatus",                 "i",               NULL,           offsetof(UnitStatusInfo, exit_status)                       },
+                { "ConditionTimestamp",             "t",               NULL,           offsetof(UnitStatusInfo, condition_timestamp)               },
+                { "ConditionResult",                "b",               NULL,           offsetof(UnitStatusInfo, condition_result)                  },
+                { "Conditions",                     "a(sbbsi)",        map_conditions, 0                                                           },
+                { "AssertTimestamp",                "t",               NULL,           offsetof(UnitStatusInfo, assert_timestamp)                  },
+                { "AssertResult",                   "b",               NULL,           offsetof(UnitStatusInfo, assert_result)                     },
+                { "Asserts",                        "a(sbbsi)",        map_asserts,    0                                                           },
+                { "NextElapseUSecRealtime",         "t",               NULL,           offsetof(UnitStatusInfo, next_elapse_real)                  },
+                { "NextElapseUSecMonotonic",        "t",               NULL,           offsetof(UnitStatusInfo, next_elapse_monotonic)             },
+                { "NAccepted",                      "u",               NULL,           offsetof(UnitStatusInfo, n_accepted)                        },
+                { "NConnections",                   "u",               NULL,           offsetof(UnitStatusInfo, n_connections)                     },
+                { "NRefused",                       "u",               NULL,           offsetof(UnitStatusInfo, n_refused)                         },
+                { "Accept",                         "b",               NULL,           offsetof(UnitStatusInfo, accept)                            },
+                { "Listen",                         "a(ss)",           map_listen,     offsetof(UnitStatusInfo, listen)                            },
+                { "SysFSPath",                      "s",               NULL,           offsetof(UnitStatusInfo, sysfs_path)                        },
+                { "Where",                          "s",               NULL,           offsetof(UnitStatusInfo, where)                             },
+                { "What",                           "s",               NULL,           offsetof(UnitStatusInfo, what)                              },
+                { "MemoryCurrent",                  "t",               NULL,           offsetof(UnitStatusInfo, memory_current)                    },
+                { "DefaultMemoryMin",               "t",               NULL,           offsetof(UnitStatusInfo, default_memory_min)                },
+                { "DefaultMemoryLow",               "t",               NULL,           offsetof(UnitStatusInfo, default_memory_low)                },
+                { "MemoryMin",                      "t",               NULL,           offsetof(UnitStatusInfo, memory_min)                        },
+                { "MemoryLow",                      "t",               NULL,           offsetof(UnitStatusInfo, memory_low)                        },
+                { "MemoryHigh",                     "t",               NULL,           offsetof(UnitStatusInfo, memory_high)                       },
+                { "MemoryMax",                      "t",               NULL,           offsetof(UnitStatusInfo, memory_max)                        },
+                { "MemorySwapMax",                  "t",               NULL,           offsetof(UnitStatusInfo, memory_swap_max)                   },
+                { "MemoryLimit",                    "t",               NULL,           offsetof(UnitStatusInfo, memory_limit)                      },
+                { "CPUUsageNSec",                   "t",               NULL,           offsetof(UnitStatusInfo, cpu_usage_nsec)                    },
+                { "TasksCurrent",                   "t",               NULL,           offsetof(UnitStatusInfo, tasks_current)                     },
+                { "TasksMax",                       "t",               NULL,           offsetof(UnitStatusInfo, tasks_max)                         },
+                { "IPIngressBytes",                 "t",               NULL,           offsetof(UnitStatusInfo, ip_ingress_bytes)                  },
+                { "IPEgressBytes",                  "t",               NULL,           offsetof(UnitStatusInfo, ip_egress_bytes)                   },
+                { "IOReadBytes",                    "t",               NULL,           offsetof(UnitStatusInfo, io_read_bytes)                     },
+                { "IOWriteBytes",                   "t",               NULL,           offsetof(UnitStatusInfo, io_write_bytes)                    },
+                { "ExecStartPre",                   "a(sasbttttuii)",  map_exec,       0                                                           },
+                { "ExecStartPreEx",                 "a(sasasttttuii)", map_exec,       0                                                           },
+                { "ExecStart",                      "a(sasbttttuii)",  map_exec,       0                                                           },
+                { "ExecStartEx",                    "a(sasasttttuii)", map_exec,       0                                                           },
+                { "ExecStartPost",                  "a(sasbttttuii)",  map_exec,       0                                                           },
+                { "ExecStartPostEx",                "a(sasasttttuii)", map_exec,       0                                                           },
+                { "ExecReload",                     "a(sasbttttuii)",  map_exec,       0                                                           },
+                { "ExecStopPre",                    "a(sasbttttuii)",  map_exec,       0                                                           },
+                { "ExecStop",                       "a(sasbttttuii)",  map_exec,       0                                                           },
+                { "ExecStopPost",                   "a(sasbttttuii)",  map_exec,       0                                                           },
                 {}
         };
 
@@ -5865,6 +5869,11 @@ static int cat(int argc, char *argv[], void *userdata) {
         bool first = true;
         int r;
 
+        /* Include all units by default - i.e. continue as if the --all
+         * option was used */
+        if (strv_isempty(arg_states))
+                arg_all = true;
+
         if (arg_transport != BUS_TRANSPORT_LOCAL)
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot remotely cat units.");
 
@@ -6223,8 +6232,8 @@ static int switch_root(int argc, char *argv[], void *userdata) {
         if (init) {
                 const char *root_systemd_path = NULL, *root_init_path = NULL;
 
-                root_systemd_path = strjoina(root, "/" SYSTEMD_BINARY_PATH);
-                root_init_path = strjoina(root, "/", init);
+                root_systemd_path = prefix_roota(root, "/" SYSTEMD_BINARY_PATH);
+                root_init_path = prefix_roota(root, init);
 
                 /* If the passed init is actually the same as the
                  * systemd binary, then let's suppress it. */
@@ -6452,7 +6461,7 @@ static int enable_sysv_units(const char *verb, char **args) {
                 }
 
                 if (!isempty(arg_root)) {
-                        q = strappend("--root=", arg_root);
+                        q = strjoin("--root=", arg_root);
                         if (!q)
                                 return log_oom();
 
@@ -7275,12 +7284,12 @@ static int get_file_to_edit(
         assert(name);
         assert(ret_path);
 
-        path = strjoin(paths->persistent_config, "/", name);
+        path = path_join(paths->persistent_config, name);
         if (!path)
                 return log_oom();
 
         if (arg_runtime) {
-                run = strjoin(paths->runtime_config, "/", name);
+                run = path_join(paths->runtime_config, name);
                 if (!run)
                         return log_oom();
         }
@@ -7647,6 +7656,7 @@ static int systemctl_help(void) {
                "                      When shutting down or sleeping, ignore inhibitors\n"
                "     --kill-who=WHO   Who to send signal to\n"
                "  -s --signal=SIGNAL  Which signal to send\n"
+               "     --what=RESOURCES Which types of resources to remove\n"
                "     --now            Start or stop unit in addition to enabling or disabling it\n"
                "     --dry-run        Only print what would be done\n"
                "  -q --quiet          Suppress output\n"
@@ -7695,6 +7705,8 @@ static int systemctl_help(void) {
                "                                      if supported, otherwise restart\n"
                "  isolate UNIT                        Start one unit and stop all others\n"
                "  kill UNIT...                        Send signal to processes of a unit\n"
+               "  clean UNIT...                       Clean runtime, cache, state, logs or\n"
+               "                                      or configuration of unit\n"
                "  is-active PATTERN...                Check whether units are active\n"
                "  is-failed PATTERN...                Check whether units are failed\n"
                "  status [PATTERN...|PID...]          Show runtime status of one or more units\n"
@@ -7998,6 +8010,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
                 ARG_NOW,
                 ARG_MESSAGE,
                 ARG_WAIT,
+                ARG_WHAT,
         };
 
         static const struct option options[] = {
@@ -8049,6 +8062,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
                 { "now",                 no_argument,       NULL, ARG_NOW                 },
                 { "message",             required_argument, NULL, ARG_MESSAGE             },
                 { "show-transaction",    no_argument,       NULL, 'T'                     },
+                { "what",                required_argument, NULL, ARG_WHAT                },
                 {}
         };
 
@@ -8401,6 +8415,38 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
                         arg_show_transaction = true;
                         break;
 
+                case ARG_WHAT: {
+                        const char *p;
+
+                        if (isempty(optarg))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--what= requires arguments.");
+
+                        for (p = optarg;;) {
+                                _cleanup_free_ char *k = NULL;
+
+                                r = extract_first_word(&p, &k, ",", 0);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to parse directory type: %s", optarg);
+                                if (r == 0)
+                                        break;
+
+                                if (streq(k, "help")) {
+                                        puts("runtime\n"
+                                             "state\n"
+                                             "cache\n"
+                                             "logs\n"
+                                             "configuration");
+                                        return 0;
+                                }
+
+                                r = strv_consume(&arg_clean_what, TAKE_PTR(k));
+                                if (r < 0)
+                                        return log_oom();
+                        }
+
+                        break;
+                }
+
                 case '.':
                         /* Output an error mimicking getopt, and print a hint afterwards */
                         log_error("%s: invalid option -- '.'", program_invocation_name);
@@ -8830,7 +8876,7 @@ static int systemctl_main(int argc, char *argv[]) {
                 { "list-sockets",          VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, list_sockets         },
                 { "list-timers",           VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, list_timers          },
                 { "list-jobs",             VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, list_jobs            },
-                { "list-machines",         VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY|VERB_MUST_BE_ROOT, list_machines },
+                { "list-machines",         VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, list_machines        },
                 { "clear-jobs",            VERB_ANY, 1,        VERB_ONLINE_ONLY, trivial_method       },
                 { "cancel",                VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, cancel_job           },
                 { "start",                 2,        VERB_ANY, VERB_ONLINE_ONLY, start_unit           },
@@ -8847,6 +8893,7 @@ static int systemctl_main(int argc, char *argv[]) {
                 { "condrestart",           2,        VERB_ANY, VERB_ONLINE_ONLY, start_unit           }, /* For compatibility with RH */
                 { "isolate",               2,        2,        VERB_ONLINE_ONLY, start_unit           },
                 { "kill",                  2,        VERB_ANY, VERB_ONLINE_ONLY, kill_unit            },
+                { "clean",                 2,        VERB_ANY, VERB_ONLINE_ONLY, clean_unit           },
                 { "is-active",             2,        VERB_ANY, VERB_ONLINE_ONLY, check_unit_active    },
                 { "check",                 2,        VERB_ANY, VERB_ONLINE_ONLY, check_unit_active    }, /* deprecated alias of is-active */
                 { "is-failed",             2,        VERB_ANY, VERB_ONLINE_ONLY, check_unit_failed    },