]> 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 05b4afda54c4f594ad13202318194a51a078293d..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"
@@ -26,6 +27,7 @@
 #include "copy.h"
 #include "def.h"
 #include "exit-status.h"
+#include "extract-word.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "filesystems.h"
@@ -42,6 +44,7 @@
 #include "parse-util.h"
 #include "path-util.h"
 #include "pretty-print.h"
+#include "rm-rf.h"
 #if HAVE_SECCOMP
 #  include "seccomp-util.h"
 #endif
@@ -53,6 +56,7 @@
 #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"
@@ -101,6 +105,8 @@ 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);
@@ -108,6 +114,7 @@ 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;
@@ -230,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);
@@ -639,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)
@@ -1034,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)
@@ -1051,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");
@@ -1076,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)
@@ -1121,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);
 }
@@ -1133,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)
@@ -1270,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)
@@ -1311,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");
 
@@ -1347,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);
@@ -1376,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) {
@@ -1412,11 +1466,11 @@ 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 == 1 || argc == 2);
+        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);
 
         return verb_log_control_common(bus, "org.freedesktop.systemd1", argv[0], argc == 2 ? argv[1] : NULL);
 }
@@ -1523,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);
 }
@@ -1568,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);
 }
@@ -1652,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;
@@ -1677,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;
@@ -1698,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;
@@ -1750,8 +1809,8 @@ static int dump_syscall_filters(int argc, char *argv[], void *userdata) {
 
 static int load_available_kernel_filesystems(Set **ret) {
         _cleanup_set_free_ Set *filesystems = NULL;
+        _cleanup_free_ char *t = NULL;
         int r;
-        char *t;
 
         assert(ret);
 
@@ -1800,8 +1859,9 @@ static void filesystem_set_remove(Set *s, const FilesystemSet *set) {
         }
 }
 
-static void dump_filesystem(const FilesystemSet *set) {
+static void dump_filesystem_set(const FilesystemSet *set) {
         const char *filesystem;
+        int r;
 
         if (!set)
                 return;
@@ -1813,8 +1873,38 @@ static void dump_filesystem(const FilesystemSet *set) {
                ansi_normal(),
                set->help);
 
-        NULSTR_FOREACH(filesystem, set->value)
-                printf("    %s%s%s\n", filesystem[0] == '@' ? ansi_underline() : "", filesystem, ansi_normal());
+        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) {
@@ -1824,7 +1914,7 @@ static int dump_filesystems(int argc, char *argv[], void *userdata) {
         return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Not compiled with libbpf support, sorry.");
 #endif
 
-        (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;
@@ -1842,13 +1932,16 @@ static int dump_filesystems(int argc, char *argv[], void *userdata) {
                         if (!first)
                                 puts("");
 
-                        dump_filesystem(set);
+                        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;
@@ -1863,8 +1956,29 @@ static int dump_filesystems(int argc, char *argv[], void *userdata) {
 
                         strv_sort(l);
 
-                        STRV_FOREACH(filesystem, 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) {
@@ -1906,7 +2020,7 @@ static int dump_filesystems(int argc, char *argv[], void *userdata) {
                                                        "Filesystem set \"%s\" not found.", *name);
                         }
 
-                        dump_filesystem(set);
+                        dump_filesystem_set(set);
                         first = false;
                 }
         }
@@ -1988,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) {
@@ -2228,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 */
@@ -2257,7 +2371,19 @@ static int do_condition(int argc, char *argv[], void *userdata) {
 }
 
 static int do_verify(int argc, char *argv[], void *userdata) {
-        return verify_units(strv_skip(argv, 1), arg_scope, arg_man, arg_generators, arg_recursive_errors, arg_root);
+        _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) {
@@ -2268,9 +2394,9 @@ static int do_security(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 (arg_security_policy) {
                 r = json_parse_file(/*f=*/ NULL, arg_security_policy, /*flags=*/ 0, &policy, &line, &column);
@@ -2284,7 +2410,7 @@ static int do_security(int argc, char *argv[], void *userdata) {
                 if (r < 0 && r != -ENOENT)
                         return r;
 
-                if (f != NULL) {
+                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);
@@ -2300,16 +2426,23 @@ static int do_security(int argc, char *argv[], void *userdata) {
                                 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)
@@ -2338,9 +2471,8 @@ static int help(int argc, char *argv[], void *userdata) {
                "  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\n"
-               "                             filter\n"
-               "  filesystems [NAME...]      Print list of filesystems\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"
@@ -2348,13 +2480,12 @@ static int help(int argc, char *argv[], void *userdata) {
                "  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"
                "     --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"
-               "     --version               Show package version\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"
@@ -2377,6 +2508,11 @@ static int help(int argc, char *argv[], void *userdata) {
                "     --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(),
@@ -2413,11 +2549,13 @@ static int parse_argv(int argc, char *argv[]) {
                 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          },
+                { "quiet",            no_argument,       NULL, 'q'                  },
                 { "order",            no_argument,       NULL, ARG_ORDER            },
                 { "require",          no_argument,       NULL, ARG_REQUIRE          },
                 { "root",             required_argument, NULL, ARG_ROOT             },
@@ -2441,6 +2579,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "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          },
                 {}
         };
 
@@ -2455,6 +2594,13 @@ static int parse_argv(int argc, char *argv[]) {
                 case 'h':
                         return help(0, NULL, NULL);
 
+                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);
@@ -2467,9 +2613,6 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_recursive_errors = r;
                         break;
 
-                case ARG_VERSION:
-                        return version();
-
                 case ARG_ROOT:
                         r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root);
                         if (r < 0)
@@ -2585,6 +2728,24 @@ 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;
 
@@ -2606,7 +2767,7 @@ static int parse_argv(int argc, char *argv[]) {
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                        "Option --offline= is only supported for security right now.");
 
-        if (arg_json_format_flags != JSON_FORMAT_OFF && !streq_ptr(argv[optind], "security"))
+        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.");
 
@@ -2682,6 +2843,7 @@ static int run(int argc, char *argv[]) {
                 { "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      },
                 {}
         };