#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"
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;
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);
_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;
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;
}
static int pretty_boot_time(sd_bus *bus, char **_buf) {
- char ts[FORMAT_TIMESPAN_MAX];
BootTimes *t;
static char buf[4096];
size_t size;
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)
}
static int plot_unit_times(UnitTimes *u, double width, int y) {
- char ts[FORMAT_TIMESPAN_MAX];
bool b;
if (!u->name)
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);
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)
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));
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
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;
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);
}
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, ×);
if (n <= 0)
}
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");
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, ×);
if (n <= 0)
return table_log_add_error(r);
}
- (void) pager_open(arg_pager_flags);
+ pager_open(arg_pager_flags);
return table_print(table, NULL);
}
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)
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)
" 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");
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);
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) {
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) {
return table_log_add_error(r);
}
- (void) pager_open(arg_pager_flags);
+ pager_open(arg_pager_flags);
return table_print(table, NULL);
}
(void) table_set_sort(table, (size_t) 1);
}
- (void) pager_open(arg_pager_flags);
+ pager_open(arg_pager_flags);
return table_print(table, NULL);
}
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;
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;
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;
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;
}
#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. "
putchar('\n');
}
- return EXIT_SUCCESS;
+ return 0;
}
static int test_timestamp_one(const char *p) {
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 */
}
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)
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(),
ARG_ORDER,
ARG_REQUIRE,
ARG_ROOT,
+ ARG_IMAGE,
ARG_SYSTEM,
ARG_USER,
ARG_GLOBAL,
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 },
{}
};
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':
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:
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)
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),
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 },
{ "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 },
{ "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 },
{}
};
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);
}