]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/analyze/analyze.c
Merge pull request #21454 from bluca/inspect_elf
[thirdparty/systemd.git] / src / analyze / analyze.c
index 1ad373185263af47ac99f1861c9b03911fddd759..2293fcea6a4b0a3b10a66d94e81da0694ee65280 100644 (file)
@@ -13,6 +13,7 @@
 
 #include "alloc-util.h"
 #include "analyze-condition.h"
+#include "analyze-elf.h"
 #include "analyze-security.h"
 #include "analyze-verify.h"
 #include "bus-error.h"
 #include "copy.h"
 #include "def.h"
 #include "exit-status.h"
+#include "extract-word.h"
 #include "fd-util.h"
 #include "fileio.h"
+#include "filesystems.h"
 #include "format-table.h"
 #include "glob-util.h"
 #include "hashmap.h"
 #include "locale-util.h"
 #include "log.h"
 #include "main-func.h"
+#include "mount-util.h"
 #include "nulstr-util.h"
 #include "pager.h"
 #include "parse-argument.h"
 #include "parse-util.h"
 #include "path-util.h"
 #include "pretty-print.h"
+#include "rm-rf.h"
 #if HAVE_SECCOMP
 #  include "seccomp-util.h"
 #endif
 #include "sort-util.h"
 #include "special.h"
+#include "stat-util.h"
+#include "string-table.h"
 #include "strv.h"
 #include "strxcpyx.h"
 #include "terminal-util.h"
 #include "time-util.h"
+#include "tmpfile-util.h"
 #include "unit-name.h"
 #include "util.h"
+#include "verb-log-control.h"
 #include "verbs.h"
 #include "version.h"
 
@@ -84,14 +93,28 @@ static PagerFlags arg_pager_flags = 0;
 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
 static const char *arg_host = NULL;
 static UnitFileScope arg_scope = UNIT_FILE_SYSTEM;
+static RecursiveErrors arg_recursive_errors = RECURSIVE_ERRORS_YES;
 static bool arg_man = true;
 static bool arg_generators = false;
-static const char *arg_root = NULL;
+static char *arg_root = NULL;
+static char *arg_image = NULL;
+static char *arg_security_policy = NULL;
+static bool arg_offline = false;
+static unsigned arg_threshold = 100;
 static unsigned arg_iterations = 1;
 static usec_t arg_base_time = USEC_INFINITY;
+static char *arg_unit = NULL;
+static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
+static bool arg_quiet = false;
+static char *arg_profile = NULL;
 
 STATIC_DESTRUCTOR_REGISTER(arg_dot_from_patterns, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_dot_to_patterns, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_security_policy, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_unit, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_profile, freep);
 
 typedef struct BootTimes {
         usec_t firmware_time;
@@ -214,6 +237,53 @@ static int compare_unit_start(const UnitTimes *a, const UnitTimes *b) {
         return CMP(a->activating, b->activating);
 }
 
+static int process_aliases(char *argv[], char *tempdir, char ***ret) {
+        _cleanup_strv_free_ char **filenames = NULL;
+        char **filename;
+        int r;
+
+        assert(argv);
+        assert(tempdir);
+        assert(ret);
+
+        STRV_FOREACH(filename, strv_skip(argv, 1)) {
+                _cleanup_free_ char *src = NULL, *dst = NULL, *base = NULL;
+                const char *parse_arg;
+
+                parse_arg = *filename;
+                r = extract_first_word(&parse_arg, &src, ":", EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_RETAIN_ESCAPE);
+                if (r < 0)
+                        return r;
+
+                if (!parse_arg) {
+                        r = strv_consume(&filenames, TAKE_PTR(src));
+                        if (r < 0)
+                                return r;
+
+                        continue;
+                }
+
+                r = path_extract_filename(parse_arg, &base);
+                if (r < 0)
+                        return r;
+
+                dst = path_join(tempdir, base);
+                if (!dst)
+                        return -ENOMEM;
+
+                r = copy_file(src, dst, 0, 0644, 0, 0, COPY_REFLINK);
+                if (r < 0)
+                        return r;
+
+                r = strv_consume(&filenames, TAKE_PTR(dst));
+                if (r < 0)
+                        return r;
+        }
+
+        *ret = TAKE_PTR(filenames);
+        return 0;
+}
+
 static UnitTimes* unit_times_free_array(UnitTimes *t) {
         for (UnitTimes *p = t; p && p->has_data; p++)
                 free(p->name);
@@ -342,7 +412,7 @@ static int acquire_time_data(sd_bus *bus, UnitTimes **out) {
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         _cleanup_(unit_times_free_arrayp) UnitTimes *unit_times = NULL;
         BootTimes *boot_times = NULL;
-        size_t allocated = 0, c = 0;
+        size_t c = 0;
         UnitInfo u;
         int r;
 
@@ -361,7 +431,7 @@ static int acquire_time_data(sd_bus *bus, UnitTimes **out) {
         while ((r = bus_parse_unit_info(reply, &u)) > 0) {
                 UnitTimes *t;
 
-                if (!GREEDY_REALLOC(unit_times, allocated, c + 2))
+                if (!GREEDY_REALLOC(unit_times, c + 2))
                         return log_oom();
 
                 unit_times[c + 1].has_data = false;
@@ -479,7 +549,6 @@ manager:
 }
 
 static int pretty_boot_time(sd_bus *bus, char **_buf) {
-        char ts[FORMAT_TIMESPAN_MAX];
         BootTimes *t;
         static char buf[4096];
         size_t size;
@@ -524,23 +593,23 @@ static int pretty_boot_time(sd_bus *bus, char **_buf) {
 
         size = strpcpyf(&ptr, size, "Startup finished in ");
         if (t->firmware_time > 0)
-                size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time, USEC_PER_MSEC));
+                size = strpcpyf(&ptr, size, "%s (firmware) + ", FORMAT_TIMESPAN(t->firmware_time - t->loader_time, USEC_PER_MSEC));
         if (t->loader_time > 0)
-                size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time, USEC_PER_MSEC));
+                size = strpcpyf(&ptr, size, "%s (loader) + ", FORMAT_TIMESPAN(t->loader_time, USEC_PER_MSEC));
         if (t->kernel_done_time > 0)
-                size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time, USEC_PER_MSEC));
+                size = strpcpyf(&ptr, size, "%s (kernel) + ", FORMAT_TIMESPAN(t->kernel_done_time, USEC_PER_MSEC));
         if (t->initrd_time > 0)
-                size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time, USEC_PER_MSEC));
+                size = strpcpyf(&ptr, size, "%s (initrd) + ", FORMAT_TIMESPAN(t->userspace_time - t->initrd_time, USEC_PER_MSEC));
 
-        size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
+        size = strpcpyf(&ptr, size, "%s (userspace) ", FORMAT_TIMESPAN(t->finish_time - t->userspace_time, USEC_PER_MSEC));
         if (t->kernel_done_time > 0)
-                strpcpyf(&ptr, size, "= %s ", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC));
+                strpcpyf(&ptr, size, "= %s ", FORMAT_TIMESPAN(t->firmware_time + t->finish_time, USEC_PER_MSEC));
 
         if (unit_id && timestamp_is_set(activated_time)) {
                 usec_t base = t->userspace_time > 0 ? t->userspace_time : t->reverse_offset;
 
                 size = strpcpyf(&ptr, size, "\n%s reached after %s in userspace", unit_id,
-                                format_timespan(ts, sizeof(ts), activated_time - base, USEC_PER_MSEC));
+                                FORMAT_TIMESPAN(activated_time - base, USEC_PER_MSEC));
         } else if (unit_id && activated_time == 0)
                 size = strpcpyf(&ptr, size, "\n%s was never reached", unit_id);
         else if (unit_id && activated_time == USEC_INFINITY)
@@ -591,7 +660,6 @@ static void svg_graph_box(double height, double begin, double end) {
 }
 
 static int plot_unit_times(UnitTimes *u, double width, int y) {
-        char ts[FORMAT_TIMESPAN_MAX];
         bool b;
 
         if (!u->name)
@@ -605,7 +673,7 @@ static int plot_unit_times(UnitTimes *u, double width, int y) {
         b = u->activating * SCALE_X < width / 2;
         if (u->time)
                 svg_text(b, u->activating, y, "%s (%s)",
-                         u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC));
+                         u->name, FORMAT_TIMESPAN(u->time, USEC_PER_MSEC));
         else
                 svg_text(b, u->activating, y, "%s", u->name);
 
@@ -625,7 +693,7 @@ static int analyze_plot(int argc, char *argv[], void *userdata) {
 
         r = acquire_bus(&bus, &use_full_bus);
         if (r < 0)
-                return bus_log_connect_error(r);
+                return bus_log_connect_error(r, arg_transport);
 
         n = acquire_boot_times(bus, &boot);
         if (n < 0)
@@ -836,8 +904,6 @@ static int list_dependencies_print(
                 UnitTimes *times,
                 BootTimes *boot) {
 
-        char ts[FORMAT_TIMESPAN_MAX], ts2[FORMAT_TIMESPAN_MAX];
-
         for (unsigned i = level; i != 0; i--)
                 printf("%s", special_glyph(branches & (1 << (i-1)) ? SPECIAL_GLYPH_TREE_VERTICAL : SPECIAL_GLYPH_TREE_SPACE));
 
@@ -846,10 +912,10 @@ static int list_dependencies_print(
         if (times) {
                 if (times->time > 0)
                         printf("%s%s @%s +%s%s", ansi_highlight_red(), name,
-                               format_timespan(ts, sizeof(ts), times->activating - boot->userspace_time, USEC_PER_MSEC),
-                               format_timespan(ts2, sizeof(ts2), times->time, USEC_PER_MSEC), ansi_normal());
+                               FORMAT_TIMESPAN(times->activating - boot->userspace_time, USEC_PER_MSEC),
+                               FORMAT_TIMESPAN(times->time, USEC_PER_MSEC), ansi_normal());
                 else if (times->activated > boot->userspace_time)
-                        printf("%s @%s", name, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC));
+                        printf("%s @%s", name, FORMAT_TIMESPAN(times->activated - boot->userspace_time, USEC_PER_MSEC));
                 else
                         printf("%s", name);
         } else
@@ -964,7 +1030,6 @@ static int list_dependencies_one(sd_bus *bus, const char *name, unsigned level,
 
 static int list_dependencies(sd_bus *bus, const char *name) {
         _cleanup_strv_free_ char **units = NULL;
-        char ts[FORMAT_TIMESPAN_MAX];
         UnitTimes *times;
         int r;
         const char *id;
@@ -1004,9 +1069,10 @@ static int list_dependencies(sd_bus *bus, const char *name) {
         if (times) {
                 if (times->time)
                         printf("%s%s +%s%s\n", ansi_highlight_red(), id,
-                               format_timespan(ts, sizeof(ts), times->time, USEC_PER_MSEC), ansi_normal());
+                               FORMAT_TIMESPAN(times->time, USEC_PER_MSEC), ansi_normal());
                 else if (times->activated > boot->userspace_time)
-                        printf("%s @%s\n", id, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC));
+                        printf("%s @%s\n", id,
+                               FORMAT_TIMESPAN(times->activated - boot->userspace_time, USEC_PER_MSEC));
                 else
                         printf("%s\n", id);
         }
@@ -1022,7 +1088,7 @@ static int analyze_critical_chain(int argc, char *argv[], void *userdata) {
 
         r = acquire_bus(&bus, NULL);
         if (r < 0)
-                return bus_log_connect_error(r);
+                return bus_log_connect_error(r, arg_transport);
 
         n = acquire_time_data(bus, &times);
         if (n <= 0)
@@ -1039,7 +1105,7 @@ static int analyze_critical_chain(int argc, char *argv[], void *userdata) {
         }
         unit_times_hashmap = h;
 
-        (void) pager_open(arg_pager_flags);
+        pager_open(arg_pager_flags);
 
         puts("The time when unit became active or started is printed after the \"@\" character.\n"
              "The time the unit took to start is printed after the \"+\" character.\n");
@@ -1064,7 +1130,7 @@ static int analyze_blame(int argc, char *argv[], void *userdata) {
 
         r = acquire_bus(&bus, NULL);
         if (r < 0)
-                return bus_log_connect_error(r);
+                return bus_log_connect_error(r, arg_transport);
 
         n = acquire_time_data(bus, &times);
         if (n <= 0)
@@ -1109,7 +1175,7 @@ static int analyze_blame(int argc, char *argv[], void *userdata) {
                         return table_log_add_error(r);
         }
 
-        (void) pager_open(arg_pager_flags);
+        pager_open(arg_pager_flags);
 
         return table_print(table, NULL);
 }
@@ -1121,7 +1187,7 @@ static int analyze_time(int argc, char *argv[], void *userdata) {
 
         r = acquire_bus(&bus, NULL);
         if (r < 0)
-                return bus_log_connect_error(r);
+                return bus_log_connect_error(r, arg_transport);
 
         r = pretty_boot_time(bus, &buf);
         if (r < 0)
@@ -1258,7 +1324,7 @@ static int dot(int argc, char *argv[], void *userdata) {
 
         r = acquire_bus(&bus, NULL);
         if (r < 0)
-                return bus_log_connect_error(r);
+                return bus_log_connect_error(r, arg_transport);
 
         r = expand_patterns(bus, strv_skip(argv, 1), &expanded_patterns);
         if (r < 0)
@@ -1299,7 +1365,7 @@ static int dot(int argc, char *argv[], void *userdata) {
                  "                 red       = Conflicts\n"
                  "                 green     = After\n");
 
-        if (on_tty())
+        if (on_tty() && !arg_quiet)
                 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
                            "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
 
@@ -1335,9 +1401,9 @@ static int dump(int argc, char *argv[], void *userdata) {
 
         r = acquire_bus(&bus, NULL);
         if (r < 0)
-                return bus_log_connect_error(r);
+                return bus_log_connect_error(r, arg_transport);
 
-        (void) pager_open(arg_pager_flags);
+        pager_open(arg_pager_flags);
 
         if (!sd_bus_can_send(bus, SD_BUS_TYPE_UNIX_FD))
                 return dump_fallback(bus);
@@ -1364,7 +1430,7 @@ static int cat_config(int argc, char *argv[], void *userdata) {
         char **arg, **list;
         int r;
 
-        (void) pager_open(arg_pager_flags);
+        pager_open(arg_pager_flags);
 
         list = strv_skip(argv, 1);
         STRV_FOREACH(arg, list) {
@@ -1396,86 +1462,17 @@ static int cat_config(int argc, char *argv[], void *userdata) {
         return 0;
 }
 
-static int set_log_level(int argc, char *argv[], void *userdata) {
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+static int verb_log_control(int argc, char *argv[], void *userdata) {
         _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
         int r;
 
-        assert(argc == 2);
-        assert(argv);
-
-        r = acquire_bus(&bus, NULL);
-        if (r < 0)
-                return bus_log_connect_error(r);
-
-        r = bus_set_property(bus, bus_systemd_mgr, "LogLevel", &error, "s", argv[1]);
-        if (r < 0)
-                return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r));
-
-        return 0;
-}
-
-static int get_log_level(int argc, char *argv[], void *userdata) {
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
-        _cleanup_free_ char *level = NULL;
-        int r;
-
-        r = acquire_bus(&bus, NULL);
-        if (r < 0)
-                return bus_log_connect_error(r);
-
-        r = bus_get_property_string(bus, bus_systemd_mgr, "LogLevel", &error, &level);
-        if (r < 0)
-                return log_error_errno(r, "Failed to get log level: %s", bus_error_message(&error, r));
-
-        puts(level);
-        return 0;
-}
-
-static int get_or_set_log_level(int argc, char *argv[], void *userdata) {
-        return (argc == 1) ? get_log_level(argc, argv, userdata) : set_log_level(argc, argv, userdata);
-}
-
-static int set_log_target(int argc, char *argv[], void *userdata) {
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
-        int r;
-
-        assert(argc == 2);
-        assert(argv);
+        assert(IN_SET(argc, 1, 2));
 
         r = acquire_bus(&bus, NULL);
         if (r < 0)
-                return bus_log_connect_error(r);
+                return bus_log_connect_error(r, arg_transport);
 
-        r = bus_set_property(bus, bus_systemd_mgr, "LogTarget", &error, "s", argv[1]);
-        if (r < 0)
-                return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r));
-
-        return 0;
-}
-
-static int get_log_target(int argc, char *argv[], void *userdata) {
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
-        _cleanup_free_ char *target = NULL;
-        int r;
-
-        r = acquire_bus(&bus, NULL);
-        if (r < 0)
-                return bus_log_connect_error(r);
-
-        r = bus_get_property_string(bus, bus_systemd_mgr, "LogTarget", &error, &target);
-        if (r < 0)
-                return log_error_errno(r, "Failed to get log target: %s", bus_error_message(&error, r));
-
-        puts(target);
-        return 0;
-}
-
-static int get_or_set_log_target(int argc, char *argv[], void *userdata) {
-        return (argc == 1) ? get_log_target(argc, argv, userdata) : set_log_target(argc, argv, userdata);
+        return verb_log_control_common(bus, "org.freedesktop.systemd1", argv[0], argc == 2 ? argv[1] : NULL);
 }
 
 static bool strv_fnmatch_strv_or_empty(char* const* patterns, char **strv, int flags) {
@@ -1580,7 +1577,7 @@ static int dump_exit_status(int argc, char *argv[], void *userdata) {
                                 return table_log_add_error(r);
                 }
 
-        (void) pager_open(arg_pager_flags);
+        pager_open(arg_pager_flags);
 
         return table_print(table, NULL);
 }
@@ -1625,7 +1622,7 @@ static int dump_capabilities(int argc, char *argv[], void *userdata) {
                 (void) table_set_sort(table, (size_t) 1);
         }
 
-        (void) pager_open(arg_pager_flags);
+        pager_open(arg_pager_flags);
 
         return table_print(table, NULL);
 }
@@ -1681,6 +1678,9 @@ static int load_kernel_syscalls(Set **ret) {
 static void syscall_set_remove(Set *s, const SyscallFilterSet *set) {
         const char *syscall;
 
+        if (!set)
+                return;
+
         NULSTR_FOREACH(syscall, set->value) {
                 if (syscall[0] == '@')
                         continue;
@@ -1706,18 +1706,19 @@ static void dump_syscall_filter(const SyscallFilterSet *set) {
 static int dump_syscall_filters(int argc, char *argv[], void *userdata) {
         bool first = true;
 
-        (void) pager_open(arg_pager_flags);
+        pager_open(arg_pager_flags);
 
         if (strv_isempty(strv_skip(argv, 1))) {
                 _cleanup_set_free_ Set *kernel = NULL, *known = NULL;
                 const char *sys;
-                int k;
+                int k = 0;  /* explicit initialization to appease gcc */
 
                 NULSTR_FOREACH(sys, syscall_filter_sets[SYSCALL_FILTER_SET_KNOWN].value)
                         if (set_put_strdup(&known, sys) < 0)
                                 return log_oom();
 
-                k = load_kernel_syscalls(&kernel);
+                if (!arg_quiet)
+                        k = load_kernel_syscalls(&kernel);
 
                 for (int i = 0; i < _SYSCALL_FILTER_SET_MAX; i++) {
                         const SyscallFilterSet *set = syscall_filter_sets + i;
@@ -1731,6 +1732,9 @@ static int dump_syscall_filters(int argc, char *argv[], void *userdata) {
                         first = false;
                 }
 
+                if (arg_quiet)  /* Let's not show the extra stuff in quiet mode */
+                        return 0;
+
                 if (!set_isempty(known)) {
                         _cleanup_free_ char **l = NULL;
                         char **syscall;
@@ -1752,7 +1756,8 @@ static int dump_syscall_filters(int argc, char *argv[], void *userdata) {
                 if (k < 0) {
                         fputc('\n', stdout);
                         fflush(stdout);
-                        log_notice_errno(k, "# Not showing unlisted system calls, couldn't retrieve kernel system call list: %m");
+                        if (!arg_quiet)
+                                log_notice_errno(k, "# Not showing unlisted system calls, couldn't retrieve kernel system call list: %m");
                 } else if (!set_isempty(kernel)) {
                         _cleanup_free_ char **l = NULL;
                         char **syscall;
@@ -1802,6 +1807,227 @@ static int dump_syscall_filters(int argc, char *argv[], void *userdata) {
 }
 #endif
 
+static int load_available_kernel_filesystems(Set **ret) {
+        _cleanup_set_free_ Set *filesystems = NULL;
+        _cleanup_free_ char *t = NULL;
+        int r;
+
+        assert(ret);
+
+        /* Let's read the available filesystems */
+
+        r = read_virtual_file("/proc/filesystems", SIZE_MAX, &t, NULL);
+        if (r < 0)
+                return r;
+
+        for (int i = 0;;) {
+                _cleanup_free_ char *line = NULL;
+                const char *p;
+
+                r = string_extract_line(t, i++, &line);
+                if (r < 0)
+                        return log_oom();
+                if (r == 0)
+                        break;
+
+                if (!line)
+                        line = t;
+
+                p = strchr(line, '\t');
+                if (!p)
+                        continue;
+
+                p += strspn(p, WHITESPACE);
+
+                r = set_put_strdup(&filesystems, p);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to add filesystem to list: %m");
+        }
+
+        *ret = TAKE_PTR(filesystems);
+        return 0;
+}
+
+static void filesystem_set_remove(Set *s, const FilesystemSet *set) {
+        const char *filesystem;
+
+        NULSTR_FOREACH(filesystem, set->value) {
+                if (filesystem[0] == '@')
+                        continue;
+
+                free(set_remove(s, filesystem));
+        }
+}
+
+static void dump_filesystem_set(const FilesystemSet *set) {
+        const char *filesystem;
+        int r;
+
+        if (!set)
+                return;
+
+        printf("%s%s%s\n"
+               "    # %s\n",
+               ansi_highlight(),
+               set->name,
+               ansi_normal(),
+               set->help);
+
+        NULSTR_FOREACH(filesystem, set->value) {
+                const statfs_f_type_t *magic;
+
+                if (filesystem[0] == '@') {
+                        printf("    %s%s%s\n", ansi_underline(), filesystem, ansi_normal());
+                        continue;
+                }
+
+                r = fs_type_from_string(filesystem, &magic);
+                assert_se(r >= 0);
+
+                printf("    %s", filesystem);
+
+                for (size_t i = 0; magic[i] != 0; i++) {
+                        const char *primary;
+                        if (i == 0)
+                                printf(" %s(magic: ", ansi_grey());
+                        else
+                                printf(", ");
+
+                        printf("0x%llx", (unsigned long long) magic[i]);
+
+                        primary = fs_type_to_string(magic[i]);
+                        if (primary && !streq(primary, filesystem))
+                                printf("[%s]", primary);
+
+                        if (magic[i+1] == 0)
+                                printf(")%s", ansi_normal());
+                }
+
+                printf("\n");
+        }
+}
+
+static int dump_filesystems(int argc, char *argv[], void *userdata) {
+        bool first = true;
+
+#if ! HAVE_LIBBPF
+        return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Not compiled with libbpf support, sorry.");
+#endif
+
+        pager_open(arg_pager_flags);
+
+        if (strv_isempty(strv_skip(argv, 1))) {
+                _cleanup_set_free_ Set *kernel = NULL, *known = NULL;
+                const char *fs;
+                int k;
+
+                NULSTR_FOREACH(fs, filesystem_sets[FILESYSTEM_SET_KNOWN].value)
+                        if (set_put_strdup(&known, fs) < 0)
+                                return log_oom();
+
+                k = load_available_kernel_filesystems(&kernel);
+
+                for (FilesystemGroups i = 0; i < _FILESYSTEM_SET_MAX; i++) {
+                        const FilesystemSet *set = filesystem_sets + i;
+                        if (!first)
+                                puts("");
+
+                        dump_filesystem_set(set);
+                        filesystem_set_remove(kernel, set);
+                        if (i != FILESYSTEM_SET_KNOWN)
+                                filesystem_set_remove(known, set);
+                        first = false;
+                }
+
+                if (arg_quiet)  /* Let's not show the extra stuff in quiet mode */
+                        return 0;
+
+                if (!set_isempty(known)) {
+                        _cleanup_free_ char **l = NULL;
+                        char **filesystem;
+
+                        printf("\n"
+                               "# %sUngrouped filesystems%s (known but not included in any of the groups except @known):\n",
+                               ansi_highlight(), ansi_normal());
+
+                        l = set_get_strv(known);
+                        if (!l)
+                                return log_oom();
+
+                        strv_sort(l);
+
+                        STRV_FOREACH(filesystem, l) {
+                                const statfs_f_type_t *magic;
+                                bool is_primary = false;
+
+                                assert_se(fs_type_from_string(*filesystem, &magic) >= 0);
+
+                                for (size_t i = 0; magic[i] != 0; i++) {
+                                        const char *primary;
+
+                                        primary = fs_type_to_string(magic[i]);
+                                        assert(primary);
+
+                                        if (streq(primary, *filesystem))
+                                                is_primary = true;
+                                }
+
+                                if (!is_primary) {
+                                        log_debug("Skipping ungrouped file system '%s', because it's an alias for another one.", *filesystem);
+                                        continue;
+                                }
+
+                                printf("#   %s\n", *filesystem);
+                        }
+                }
+
+                if (k < 0) {
+                        fputc('\n', stdout);
+                        fflush(stdout);
+                        log_notice_errno(k, "# Not showing unlisted filesystems, couldn't retrieve kernel filesystem list: %m");
+                } else if (!set_isempty(kernel)) {
+                        _cleanup_free_ char **l = NULL;
+                        char **filesystem;
+
+                        printf("\n"
+                               "# %sUnlisted filesystems%s (available to the local kernel, but not included in any of the groups listed above):\n",
+                               ansi_highlight(), ansi_normal());
+
+                        l = set_get_strv(kernel);
+                        if (!l)
+                                return log_oom();
+
+                        strv_sort(l);
+
+                        STRV_FOREACH(filesystem, l)
+                                printf("#   %s\n", *filesystem);
+                }
+        } else {
+                char **name;
+
+                STRV_FOREACH(name, strv_skip(argv, 1)) {
+                        const FilesystemSet *set;
+
+                        if (!first)
+                                puts("");
+
+                        set = filesystem_set_find(*name);
+                        if (!set) {
+                                /* make sure the error appears below normal output */
+                                fflush(stdout);
+
+                                return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
+                                                       "Filesystem set \"%s\" not found.", *name);
+                        }
+
+                        dump_filesystem_set(set);
+                        first = false;
+                }
+        }
+
+        return 0;
+}
+
 static void parsing_hint(const char *p, bool calendar, bool timestamp, bool timespan) {
         if (calendar && calendar_spec_from_string(p, NULL) >= 0)
                 log_notice("Hint: this expression is a valid calendar specification. "
@@ -1876,7 +2102,7 @@ static int dump_timespan(int argc, char *argv[], void *userdata) {
                         putchar('\n');
         }
 
-        return EXIT_SUCCESS;
+        return 0;
 }
 
 static int test_timestamp_one(const char *p) {
@@ -2116,7 +2342,7 @@ static int service_watchdogs(int argc, char *argv[], void *userdata) {
 
         r = acquire_bus(&bus, NULL);
         if (r < 0)
-                return bus_log_connect_error(r);
+                return bus_log_connect_error(r, arg_transport);
 
         if (argc == 1) {
                 /* get ServiceWatchdogs */
@@ -2141,31 +2367,82 @@ static int service_watchdogs(int argc, char *argv[], void *userdata) {
 }
 
 static int do_condition(int argc, char *argv[], void *userdata) {
-        return verify_conditions(strv_skip(argv, 1), arg_scope);
+        return verify_conditions(strv_skip(argv, 1), arg_scope, arg_unit, arg_root);
 }
 
 static int do_verify(int argc, char *argv[], void *userdata) {
-        return verify_units(strv_skip(argv, 1), arg_scope, arg_man, arg_generators);
+        _cleanup_strv_free_ char **filenames = NULL;
+        _cleanup_(rm_rf_physical_and_freep) char *tempdir = NULL;
+        int r;
+
+        r = mkdtemp_malloc("/tmp/systemd-analyze-XXXXXX", &tempdir);
+        if (r < 0)
+                return log_error_errno(r, "Failed to setup working directory: %m");
+
+        r = process_aliases(argv, tempdir, &filenames);
+        if (r < 0)
+                return log_error_errno(r, "Couldn't process aliases: %m");
+
+        return verify_units(filenames, arg_scope, arg_man, arg_generators, arg_recursive_errors, arg_root);
 }
 
 static int do_security(int argc, char *argv[], void *userdata) {
         _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        _cleanup_(json_variant_unrefp) JsonVariant *policy = NULL;
         int r;
+        unsigned line, column;
 
         r = acquire_bus(&bus, NULL);
         if (r < 0)
-                return bus_log_connect_error(r);
+                return bus_log_connect_error(r, arg_transport);
+
+        pager_open(arg_pager_flags);
+
+        if (arg_security_policy) {
+                r = json_parse_file(/*f=*/ NULL, arg_security_policy, /*flags=*/ 0, &policy, &line, &column);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to parse '%s' at %u:%u: %m", arg_security_policy, line, column);
+        } else {
+                _cleanup_fclose_ FILE *f = NULL;
+                _cleanup_free_ char *pp = NULL;
+
+                r = search_and_fopen_nulstr("systemd-analyze-security.policy", "re", /*root=*/ NULL, CONF_PATHS_NULSTR("systemd"), &f, &pp);
+                if (r < 0 && r != -ENOENT)
+                        return r;
 
-        (void) pager_open(arg_pager_flags);
+                if (f) {
+                        r = json_parse_file(f, pp, /*flags=*/ 0, &policy, &line, &column);
+                        if (r < 0)
+                                return log_error_errno(r, "[%s:%u:%u] Failed to parse JSON policy: %m", pp, line, column);
+                }
+        }
 
-        return analyze_security(bus, strv_skip(argv, 1), 0);
+        return analyze_security(bus,
+                                strv_skip(argv, 1),
+                                policy,
+                                arg_scope,
+                                arg_man,
+                                arg_generators,
+                                arg_offline,
+                                arg_threshold,
+                                arg_root,
+                                arg_profile,
+                                arg_json_format_flags,
+                                arg_pager_flags,
+                                /*flags=*/ 0);
+}
+
+static int do_elf_inspection(int argc, char *argv[], void *userdata) {
+        pager_open(arg_pager_flags);
+
+        return analyze_elf(strv_skip(argv, 1), arg_json_format_flags);
 }
 
 static int help(int argc, char *argv[], void *userdata) {
         _cleanup_free_ char *link = NULL, *dot_link = NULL;
         int r;
 
-        (void) pager_open(arg_pager_flags);
+        pager_open(arg_pager_flags);
 
         r = terminal_urlify_man("systemd-analyze", "1", &link);
         if (r < 0)
@@ -2179,43 +2456,63 @@ static int help(int argc, char *argv[], void *userdata) {
         printf("%s [OPTIONS...] COMMAND ...\n\n"
                "%sProfile systemd, show unit dependencies, check unit files.%s\n"
                "\nCommands:\n"
-               "  [time]                   Print time required to boot the machine\n"
-               "  blame                    Print list of running units ordered by time to init\n"
-               "  critical-chain [UNIT...] Print a tree of the time critical chain of units\n"
-               "  plot                     Output SVG graphic showing service initialization\n"
-               "  dot [UNIT...]            Output dependency graph in %s format\n"
-               "  dump                     Output state serialization of service manager\n"
-               "  cat-config               Show configuration file and drop-ins\n"
-               "  unit-files               List files and symlinks for units\n"
-               "  unit-paths               List load directories for units\n"
-               "  exit-status [STATUS...]  List exit status definitions\n"
-               "  capability [CAP...]      List capability definitions\n"
-               "  syscall-filter [NAME...] Print list of syscalls in seccomp filter\n"
-               "  condition CONDITION...   Evaluate conditions and asserts\n"
-               "  verify FILE...           Check unit files for correctness\n"
-               "  calendar SPEC...         Validate repetitive calendar time events\n"
-               "  timestamp TIMESTAMP...   Validate a timestamp\n"
-               "  timespan SPAN...         Validate a time span\n"
-               "  security [UNIT...]       Analyze security of unit\n"
+               "  [time]                     Print time required to boot the machine\n"
+               "  blame                      Print list of running units ordered by\n"
+               "                             time to init\n"
+               "  critical-chain [UNIT...]   Print a tree of the time critical chain\n"
+               "                             of units\n"
+               "  plot                       Output SVG graphic showing service\n"
+               "                             initialization\n"
+               "  dot [UNIT...]              Output dependency graph in %s format\n"
+               "  dump                       Output state serialization of service\n"
+               "                             manager\n"
+               "  cat-config                 Show configuration file and drop-ins\n"
+               "  unit-files                 List files and symlinks for units\n"
+               "  unit-paths                 List load directories for units\n"
+               "  exit-status [STATUS...]    List exit status definitions\n"
+               "  capability [CAP...]        List capability definitions\n"
+               "  syscall-filter [NAME...]   List syscalls in seccomp filters\n"
+               "  filesystems [NAME...]      List known filesystems\n"
+               "  condition CONDITION...     Evaluate conditions and asserts\n"
+               "  verify FILE...             Check unit files for correctness\n"
+               "  calendar SPEC...           Validate repetitive calendar time\n"
+               "                             events\n"
+               "  timestamp TIMESTAMP...     Validate a timestamp\n"
+               "  timespan SPAN...           Validate a time span\n"
+               "  security [UNIT...]         Analyze security of unit\n"
+               "  inspect-elf FILE...        Parse and print ELF package metadata\n"
                "\nOptions:\n"
-               "  -h --help                Show this help\n"
-               "     --version             Show package version\n"
-               "     --no-pager            Do not pipe output into a pager\n"
-               "     --system              Operate on system systemd instance\n"
-               "     --user                Operate on user systemd instance\n"
-               "     --global              Operate on global user configuration\n"
-               "  -H --host=[USER@]HOST    Operate on remote host\n"
-               "  -M --machine=CONTAINER   Operate on local container\n"
-               "     --order               Show only order in the graph\n"
-               "     --require             Show only requirement in the graph\n"
-               "     --from-pattern=GLOB   Show only origins in the graph\n"
-               "     --to-pattern=GLOB     Show only destinations in the graph\n"
-               "     --fuzz=SECONDS        Also print services which finished SECONDS earlier\n"
-               "                           than the latest in the branch\n"
-               "     --man[=BOOL]          Do [not] check for existence of man pages\n"
-               "     --generators[=BOOL]   Do [not] run unit generators (requires privileges)\n"
-               "     --iterations=N        Show the specified number of iterations\n"
-               "     --base-time=TIMESTAMP Calculate calendar times relative to specified time\n"
+               "     --recursive-errors=MODE Control which units are verified\n"
+               "     --offline=BOOL          Perform a security review on unit file(s)\n"
+               "     --threshold=N           Exit with a non-zero status when overall\n"
+               "                             exposure level is over threshold value\n"
+               "     --security-policy=PATH  Use custom JSON security policy instead\n"
+               "                             of built-in one\n"
+               "     --json=pretty|short|off Generate JSON output of the security\n"
+               "                             analysis table\n"
+               "     --no-pager              Do not pipe output into a pager\n"
+               "     --system                Operate on system systemd instance\n"
+               "     --user                  Operate on user systemd instance\n"
+               "     --global                Operate on global user configuration\n"
+               "  -H --host=[USER@]HOST      Operate on remote host\n"
+               "  -M --machine=CONTAINER     Operate on local container\n"
+               "     --order                 Show only order in the graph\n"
+               "     --require               Show only requirement in the graph\n"
+               "     --from-pattern=GLOB     Show only origins in the graph\n"
+               "     --to-pattern=GLOB       Show only destinations in the graph\n"
+               "     --fuzz=SECONDS          Also print services which finished SECONDS\n"
+               "                             earlier than the latest in the branch\n"
+               "     --man[=BOOL]            Do [not] check for existence of man pages\n"
+               "     --generators[=BOOL]     Do [not] run unit generators\n"
+               "                             (requires privileges)\n"
+               "     --iterations=N          Show the specified number of iterations\n"
+               "     --base-time=TIMESTAMP   Calculate calendar times relative to\n"
+               "                             specified time\n"
+               "     --profile=name|PATH     Include the specified profile in the\n"
+               "                             security review of the unit(s)\n"
+               "  -h --help                  Show this help\n"
+               "     --version               Show package version\n"
+               "  -q --quiet                 Do not emit hints\n"
                "\nSee the %s for details.\n",
                program_invocation_short_name,
                ansi_highlight(),
@@ -2235,6 +2532,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_ORDER,
                 ARG_REQUIRE,
                 ARG_ROOT,
+                ARG_IMAGE,
                 ARG_SYSTEM,
                 ARG_USER,
                 ARG_GLOBAL,
@@ -2246,27 +2544,42 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_GENERATORS,
                 ARG_ITERATIONS,
                 ARG_BASE_TIME,
+                ARG_RECURSIVE_ERRORS,
+                ARG_OFFLINE,
+                ARG_THRESHOLD,
+                ARG_SECURITY_POLICY,
+                ARG_JSON,
+                ARG_PROFILE,
         };
 
         static const struct option options[] = {
-                { "help",         no_argument,       NULL, 'h'                  },
-                { "version",      no_argument,       NULL, ARG_VERSION          },
-                { "order",        no_argument,       NULL, ARG_ORDER            },
-                { "require",      no_argument,       NULL, ARG_REQUIRE          },
-                { "root",         required_argument, NULL, ARG_ROOT             },
-                { "system",       no_argument,       NULL, ARG_SYSTEM           },
-                { "user",         no_argument,       NULL, ARG_USER             },
-                { "global",       no_argument,       NULL, ARG_GLOBAL           },
-                { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN },
-                { "to-pattern",   required_argument, NULL, ARG_DOT_TO_PATTERN   },
-                { "fuzz",         required_argument, NULL, ARG_FUZZ             },
-                { "no-pager",     no_argument,       NULL, ARG_NO_PAGER         },
-                { "man",          optional_argument, NULL, ARG_MAN              },
-                { "generators",   optional_argument, NULL, ARG_GENERATORS       },
-                { "host",         required_argument, NULL, 'H'                  },
-                { "machine",      required_argument, NULL, 'M'                  },
-                { "iterations",   required_argument, NULL, ARG_ITERATIONS       },
-                { "base-time",    required_argument, NULL, ARG_BASE_TIME        },
+                { "help",             no_argument,       NULL, 'h'                  },
+                { "version",          no_argument,       NULL, ARG_VERSION          },
+                { "quiet",            no_argument,       NULL, 'q'                  },
+                { "order",            no_argument,       NULL, ARG_ORDER            },
+                { "require",          no_argument,       NULL, ARG_REQUIRE          },
+                { "root",             required_argument, NULL, ARG_ROOT             },
+                { "image",            required_argument, NULL, ARG_IMAGE            },
+                { "recursive-errors", required_argument, NULL, ARG_RECURSIVE_ERRORS },
+                { "offline",          required_argument, NULL, ARG_OFFLINE          },
+                { "threshold",        required_argument, NULL, ARG_THRESHOLD        },
+                { "security-policy",  required_argument, NULL, ARG_SECURITY_POLICY  },
+                { "system",           no_argument,       NULL, ARG_SYSTEM           },
+                { "user",             no_argument,       NULL, ARG_USER             },
+                { "global",           no_argument,       NULL, ARG_GLOBAL           },
+                { "from-pattern",     required_argument, NULL, ARG_DOT_FROM_PATTERN },
+                { "to-pattern",       required_argument, NULL, ARG_DOT_TO_PATTERN   },
+                { "fuzz",             required_argument, NULL, ARG_FUZZ             },
+                { "no-pager",         no_argument,       NULL, ARG_NO_PAGER         },
+                { "man",              optional_argument, NULL, ARG_MAN              },
+                { "generators",       optional_argument, NULL, ARG_GENERATORS       },
+                { "host",             required_argument, NULL, 'H'                  },
+                { "machine",          required_argument, NULL, 'M'                  },
+                { "iterations",       required_argument, NULL, ARG_ITERATIONS       },
+                { "base-time",        required_argument, NULL, ARG_BASE_TIME        },
+                { "unit",             required_argument, NULL, 'U'                  },
+                { "json",             required_argument, NULL, ARG_JSON             },
+                { "profile",          required_argument, NULL, ARG_PROFILE          },
                 {}
         };
 
@@ -2275,7 +2588,7 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 0);
         assert(argv);
 
-        while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0)
+        while ((c = getopt_long(argc, argv, "hH:M:U:", options, NULL)) >= 0)
                 switch (c) {
 
                 case 'h':
@@ -2284,8 +2597,32 @@ static int parse_argv(int argc, char *argv[]) {
                 case ARG_VERSION:
                         return version();
 
+                case 'q':
+                        arg_quiet = true;
+                        break;
+
+                case ARG_RECURSIVE_ERRORS:
+                        if (streq(optarg, "help")) {
+                                DUMP_STRING_TABLE(recursive_errors, RecursiveErrors, _RECURSIVE_ERRORS_MAX);
+                                return 0;
+                        }
+                        r = recursive_errors_from_string(optarg);
+                        if (r < 0)
+                                return log_error_errno(r, "Unknown mode passed to --recursive-errors='%s'.", optarg);
+
+                        arg_recursive_errors = r;
+                        break;
+
                 case ARG_ROOT:
-                        arg_root = optarg;
+                        r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root);
+                        if (r < 0)
+                                return r;
+                        break;
+
+                case ARG_IMAGE:
+                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image);
+                        if (r < 0)
+                                return r;
                         break;
 
                 case ARG_SYSTEM:
@@ -2352,6 +2689,31 @@ static int parse_argv(int argc, char *argv[]) {
                                 return r;
                         break;
 
+                case ARG_OFFLINE:
+                        r = parse_boolean_argument("--offline", optarg, &arg_offline);
+                        if (r < 0)
+                                return r;
+                        break;
+
+                case ARG_THRESHOLD:
+                        r = safe_atou(optarg, &arg_threshold);
+                        if (r < 0 || arg_threshold > 100)
+                                return log_error_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse threshold: %s", optarg);
+
+                        break;
+
+                case ARG_SECURITY_POLICY:
+                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_security_policy);
+                        if (r < 0)
+                                return r;
+                        break;
+
+                case ARG_JSON:
+                        r = parse_json_argument(optarg, &arg_json_format_flags);
+                        if (r <= 0)
+                                return r;
+                        break;
+
                 case ARG_ITERATIONS:
                         r = safe_atou(optarg, &arg_iterations);
                         if (r < 0)
@@ -2366,13 +2728,53 @@ static int parse_argv(int argc, char *argv[]) {
 
                         break;
 
+                case ARG_PROFILE:
+                        if (isempty(optarg))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Profile file name is empty");
+
+                        if (is_path(optarg)) {
+                                r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_profile);
+                                if (r < 0)
+                                        return r;
+                                if (!endswith(arg_profile, ".conf"))
+                                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Profile file name must end with .conf: %s", arg_profile);
+                        } else {
+                                r = free_and_strdup(&arg_profile, optarg);
+                                if (r < 0)
+                                        return log_oom();
+                        }
+
+                        break;
+
+                case 'U': {
+                        _cleanup_free_ char *mangled = NULL;
+
+                        r = unit_name_mangle(optarg, UNIT_NAME_MANGLE_WARN, &mangled);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to mangle unit name %s: %m", optarg);
+
+                        free_and_replace(arg_unit, mangled);
+                        break;
+                }
                 case '?':
                         return -EINVAL;
 
                 default:
-                        assert_not_reached("Unhandled option code.");
+                        assert_not_reached();
                 }
 
+        if (arg_offline && !streq_ptr(argv[optind], "security"))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "Option --offline= is only supported for security right now.");
+
+        if (arg_json_format_flags != JSON_FORMAT_OFF && !STRPTR_IN_SET(argv[optind], "security", "inspect-elf"))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "Option --json= is only supported for security right now.");
+
+        if (arg_threshold != 100 && !streq_ptr(argv[optind], "security"))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "Option --threshold= is only supported for security right now.");
+
         if (arg_scope == UNIT_FILE_GLOBAL &&
             !STR_IN_SET(argv[optind] ?: "time", "dot", "unit-paths", "verify"))
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
@@ -2382,14 +2784,35 @@ static int parse_argv(int argc, char *argv[]) {
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                        "Option --user is not supported for cat-config right now.");
 
-        if (arg_root && !streq_ptr(argv[optind], "cat-config"))
+        if (arg_security_policy && !streq_ptr(argv[optind], "security"))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "Option --security-policy= is only supported for security.");
+
+        if ((arg_root || arg_image) && (!STRPTR_IN_SET(argv[optind], "cat-config", "verify", "condition")) &&
+           (!(streq_ptr(argv[optind], "security") && arg_offline)))
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                       "Option --root is only supported for cat-config right now.");
+                                       "Options --root= and --image= are only supported for cat-config, verify, condition and security when used with --offline= right now.");
+
+        /* Having both an image and a root is not supported by the code */
+        if (arg_root && arg_image)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported.");
+
+        if (arg_unit && !streq_ptr(argv[optind], "condition"))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --unit= is only supported for condition");
+
+        if (streq_ptr(argv[optind], "condition") && !arg_unit && optind >= argc - 1)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too few arguments for condition");
+
+        if (streq_ptr(argv[optind], "condition") && arg_unit && optind < argc - 1)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No conditions can be passed if --unit= is used.");
 
         return 1; /* work to do */
 }
 
 static int run(int argc, char *argv[]) {
+        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
+        _cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL;
+        _cleanup_(umount_and_rmdir_and_freep) char *unlink_dir = NULL;
 
         static const Verb verbs[] = {
                 { "help",              VERB_ANY, VERB_ANY, 0,            help                   },
@@ -2399,12 +2822,12 @@ static int run(int argc, char *argv[]) {
                 { "plot",              VERB_ANY, 1,        0,            analyze_plot           },
                 { "dot",               VERB_ANY, VERB_ANY, 0,            dot                    },
                 /* The following seven verbs are deprecated */
-                { "log-level",         VERB_ANY, 2,        0,            get_or_set_log_level   },
-                { "log-target",        VERB_ANY, 2,        0,            get_or_set_log_target  },
-                { "set-log-level",     2,        2,        0,            set_log_level          },
-                { "get-log-level",     VERB_ANY, 1,        0,            get_log_level          },
-                { "set-log-target",    2,        2,        0,            set_log_target         },
-                { "get-log-target",    VERB_ANY, 1,        0,            get_log_target         },
+                { "log-level",         VERB_ANY, 2,        0,            verb_log_control       },
+                { "log-target",        VERB_ANY, 2,        0,            verb_log_control       },
+                { "set-log-level",     2,        2,        0,            verb_log_control       },
+                { "get-log-level",     VERB_ANY, 1,        0,            verb_log_control       },
+                { "set-log-target",    2,        2,        0,            verb_log_control       },
+                { "get-log-target",    VERB_ANY, 1,        0,            verb_log_control       },
                 { "service-watchdogs", VERB_ANY, 2,        0,            service_watchdogs      },
                 { "dump",              VERB_ANY, 1,        0,            dump                   },
                 { "cat-config",        2,        VERB_ANY, 0,            cat_config             },
@@ -2413,12 +2836,14 @@ static int run(int argc, char *argv[]) {
                 { "exit-status",       VERB_ANY, VERB_ANY, 0,            dump_exit_status       },
                 { "syscall-filter",    VERB_ANY, VERB_ANY, 0,            dump_syscall_filters   },
                 { "capability",        VERB_ANY, VERB_ANY, 0,            dump_capabilities      },
-                { "condition",         2,        VERB_ANY, 0,            do_condition           },
+                { "filesystems",       VERB_ANY, VERB_ANY, 0,            dump_filesystems       },
+                { "condition",         VERB_ANY, VERB_ANY, 0,            do_condition           },
                 { "verify",            2,        VERB_ANY, 0,            do_verify              },
                 { "calendar",          2,        VERB_ANY, 0,            test_calendar          },
                 { "timestamp",         2,        VERB_ANY, 0,            test_timestamp         },
                 { "timespan",          2,        VERB_ANY, 0,            dump_timespan          },
                 { "security",          VERB_ANY, VERB_ANY, 0,            do_security            },
+                { "inspect-elf",       2,        VERB_ANY, 0,            do_elf_inspection      },
                 {}
         };
 
@@ -2433,6 +2858,26 @@ static int run(int argc, char *argv[]) {
         if (r <= 0)
                 return r;
 
+        /* Open up and mount the image */
+        if (arg_image) {
+                assert(!arg_root);
+
+                r = mount_image_privately_interactively(
+                                arg_image,
+                                DISSECT_IMAGE_GENERIC_ROOT |
+                                DISSECT_IMAGE_RELAX_VAR_CHECK |
+                                DISSECT_IMAGE_READ_ONLY,
+                                &unlink_dir,
+                                &loop_device,
+                                &decrypted_image);
+                if (r < 0)
+                        return r;
+
+                arg_root = strdup(unlink_dir);
+                if (!arg_root)
+                        return log_oom();
+        }
+
         return dispatch_verb(argc, argv, verbs, NULL);
 }