#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
+#include <unistd.h>
#include "sd-bus.h"
#include "alloc-util.h"
+#include "analyze-condition.h"
#include "analyze-security.h"
#include "analyze-verify.h"
#include "build.h"
#include "conf-files.h"
#include "copy.h"
#include "def.h"
+#include "exit-status.h"
#include "fd-util.h"
#include "fileio.h"
+#include "format-table.h"
#include "glob-util.h"
#include "hashmap.h"
#include "locale-util.h"
#include "log.h"
#include "main-func.h"
+#include "nulstr-util.h"
#include "pager.h"
#include "parse-util.h"
#include "path-util.h"
#if HAVE_SECCOMP
# include "seccomp-util.h"
#endif
+#include "sort-util.h"
#include "special.h"
#include "strv.h"
#include "strxcpyx.h"
-#include "time-util.h"
#include "terminal-util.h"
+#include "time-util.h"
#include "unit-name.h"
#include "util.h"
#include "verbs.h"
-#define SCALE_X (0.1 / 1000.0) /* pixels per us */
+#define SCALE_X (0.1 / 1000.0) /* pixels per us */
#define SCALE_Y (20.0)
#define svg(...) printf(__VA_ARGS__)
DEP_ORDER,
DEP_REQUIRE
} arg_dot = DEP_ALL;
-static char** arg_dot_from_patterns = NULL;
-static char** arg_dot_to_patterns = NULL;
+static char **arg_dot_from_patterns = NULL;
+static char **arg_dot_to_patterns = NULL;
static usec_t arg_fuzz = 0;
static PagerFlags arg_pager_flags = 0;
static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
return 0;
}
-static int compare_unit_time(const struct unit_times *a, const struct unit_times *b) {
- return CMP(b->time, a->time);
-}
-
static int compare_unit_start(const struct unit_times *a, const struct unit_times *b) {
return CMP(a->activating, b->activating);
}
*/
times.reverse_offset = times.userspace_time;
- times.firmware_time = times.loader_time = times.kernel_time = times.initrd_time = times.userspace_time =
- times.security_start_time = times.security_finish_time = 0;
+ times.firmware_time = times.loader_time = times.kernel_time = times.initrd_time =
+ times.userspace_time = times.security_start_time = times.security_finish_time = 0;
subtract_timestamp(×.finish_time, times.reverse_offset);
free(hi);
}
-DEFINE_TRIVIAL_CLEANUP_FUNC(struct host_info*, free_host_info);
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct host_info *, free_host_info);
static int acquire_time_data(sd_bus *bus, struct unit_times **out) {
static const struct bus_properties_map property_map[] = {
while ((r = bus_parse_unit_info(reply, &u)) > 0) {
struct unit_times *t;
- if (!GREEDY_REALLOC(unit_times, allocated, c+2))
+ if (!GREEDY_REALLOC(unit_times, allocated, c + 2))
return log_oom();
- unit_times[c+1].has_data = false;
+ unit_times[c + 1].has_data = false;
t = &unit_times[c];
t->name = NULL;
};
static const struct bus_properties_map manager_map[] = {
- { "Virtualization", "s", NULL, offsetof(struct host_info, virtualization) },
- { "Architecture", "s", NULL, offsetof(struct host_info, architecture) },
+ { "Virtualization", "s", NULL, offsetof(struct host_info, virtualization) },
+ { "Architecture", "s", NULL, offsetof(struct host_info, architecture) },
{}
};
}
}
- r = bus_map_all_properties(system_bus ?: bus,
- "org.freedesktop.hostname1",
- "/org/freedesktop/hostname1",
- hostname_map,
- BUS_MAP_STRDUP,
- &error,
- NULL,
- host);
+ r = bus_map_all_properties(
+ system_bus ?: bus,
+ "org.freedesktop.hostname1",
+ "/org/freedesktop/hostname1",
+ hostname_map,
+ BUS_MAP_STRDUP,
+ &error,
+ NULL,
+ host);
if (r < 0) {
log_debug_errno(r, "Failed to get host information from systemd-hostnamed, ignoring: %s",
bus_error_message(&error, r));
}
manager:
- r = bus_map_all_properties(bus,
- "org.freedesktop.systemd1",
- "/org/freedesktop/systemd1",
- manager_map,
- BUS_MAP_STRDUP,
- &error,
- NULL,
- host);
+ r = bus_map_all_properties(
+ bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ manager_map,
+ BUS_MAP_STRDUP,
+ &error,
+ NULL,
+ host);
if (r < 0)
return log_error_errno(r, "Failed to get host information from systemd: %s",
bus_error_message(&error, r));
char *ptr;
int r;
usec_t activated_time = USEC_INFINITY;
- _cleanup_free_ char* path = NULL, *unit_id = NULL;
+ _cleanup_free_ char *path = NULL, *unit_id = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
r = acquire_boot_times(bus, &t);
if (t->kernel_done_time > 0)
strpcpyf(&ptr, size, "= %s ", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC));
- if (unit_id && activated_time > 0 && activated_time != USEC_INFINITY) {
+ 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,
/* outside box, fill */
svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
- SCALE_X * (end - begin), SCALE_Y * height);
+ SCALE_X * (end - begin),
+ SCALE_Y * height);
- for (i = ((long long) (begin / 100000)) * 100000; i <= end; i+=100000) {
+ for (i = ((long long) (begin / 100000)) * 100000; i <= end; i += 100000) {
/* lines for each second */
if (i % 5000000 == 0)
svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
" <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
- SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
+ SCALE_X * i,
+ SCALE_X * i,
+ SCALE_Y * height,
+ SCALE_X * i,
+ -5.0,
+ 0.000001 * i);
else if (i % 1000000 == 0)
svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
" <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
- SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
+ SCALE_X * i,
+ SCALE_X * i,
+ SCALE_Y * height,
+ SCALE_X * i,
+ -5.0,
+ 0.000001 * i);
else
svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
- SCALE_X * i, SCALE_X * i, SCALE_Y * height);
+ SCALE_X * i,
+ SCALE_X * i,
+ SCALE_Y * height);
}
}
return 0;
}
-static int list_dependencies_print(const char *name, unsigned level, unsigned branches,
- bool last, struct unit_times *times, struct boot_times *boot) {
+static int list_dependencies_print(
+ const char *name,
+ unsigned level,
+ unsigned branches,
+ bool last,
+ struct unit_times *times,
+ struct boot_times *boot) {
+
unsigned i;
char ts[FORMAT_TIMESPAN_MAX], ts2[FORMAT_TIMESPAN_MAX];
static Hashmap *unit_times_hashmap;
-static int list_dependencies_compare(char * const *a, char * const *b) {
+static int list_dependencies_compare(char *const *a, char *const *b) {
usec_t usa = 0, usb = 0;
struct unit_times *times;
}
static bool times_in_range(const struct unit_times *times, const struct boot_times *boot) {
- return times &&
- times->activated > 0 && times->activated <= boot->finish_time;
+ return times && times->activated > 0 && times->activated <= boot->finish_time;
}
-static int list_dependencies_one(sd_bus *bus, const char *name, unsigned level, char ***units,
- unsigned branches) {
+static int list_dependencies_one(sd_bus *bus, const char *name, unsigned level, char ***units, unsigned branches) {
_cleanup_strv_free_ char **deps = NULL;
char **c;
int r = 0;
STRV_FOREACH(c, deps) {
times = hashmap_get(unit_times_hashmap, *c);
- if (times_in_range(times, boot) &&
- times->activated >= service_longest)
+ if (times_in_range(times, boot) && times->activated >= service_longest)
service_longest = times->activated;
}
STRV_FOREACH(c, deps) {
times = hashmap_get(unit_times_hashmap, *c);
- if (times_in_range(times, boot) &&
- service_longest - times->activated <= arg_fuzz)
+ if (times_in_range(times, boot) && service_longest - times->activated <= arg_fuzz)
to_print++;
}
STRV_FOREACH(c, deps) {
times = hashmap_get(unit_times_hashmap, *c);
- if (!times_in_range(times, boot) ||
- service_longest - times->activated > arg_fuzz)
+ if (!times_in_range(times, boot) || service_longest - times->activated > arg_fuzz)
continue;
to_print--;
continue;
}
- r = list_dependencies_one(bus, *c, level + 1, units,
- (branches << 1) | (to_print ? 1 : 0));
+ r = list_dependencies_one(bus, *c, level + 1, units, (branches << 1) | (to_print ? 1 : 0));
if (r < 0)
return r;
(void) pager_open(arg_pager_flags);
- puts("The time after the unit is active or started is printed after the \"@\" character.\n"
- "The time the unit takes to start is printed after the \"+\" character.\n");
+ 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");
if (argc > 1) {
char **name;
static int analyze_blame(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(unit_times_freep) struct unit_times *times = NULL;
+ _cleanup_(table_unrefp) Table *table = NULL;
struct unit_times *u;
+ TableCell *cell;
int n, r;
r = acquire_bus(&bus, NULL);
if (n <= 0)
return n;
- typesafe_qsort(times, n, compare_unit_time);
+ table = table_new("time", "unit");
+ if (!table)
+ return log_oom();
- (void) pager_open(arg_pager_flags);
+ table_set_header(table, false);
+
+ assert_se(cell = table_get_cell(table, 0, 0));
+ r = table_set_ellipsize_percent(table, cell, 100);
+ if (r < 0)
+ return r;
+
+ r = table_set_align_percent(table, cell, 100);
+ if (r < 0)
+ return r;
+
+ assert_se(cell = table_get_cell(table, 0, 1));
+ r = table_set_ellipsize_percent(table, cell, 100);
+ if (r < 0)
+ return r;
+
+ r = table_set_sort(table, 0, SIZE_MAX);
+ if (r < 0)
+ return r;
+
+ r = table_set_reverse(table, 0, true);
+ if (r < 0)
+ return r;
for (u = times; u->has_data; u++) {
- char ts[FORMAT_TIMESPAN_MAX];
+ if (u->time <= 0)
+ continue;
+
+ r = table_add_cell(table, NULL, TABLE_TIMESPAN_MSEC, &u->time);
+ if (r < 0)
+ return r;
- if (u->time > 0)
- printf("%16s %s\n", format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC), u->name);
+ r = table_add_cell(table, NULL, TABLE_STRING, u->name);
+ if (r < 0)
+ return r;
}
- return 0;
+ (void) pager_open(arg_pager_flags);
+
+ return table_print(table, NULL);
}
static int analyze_time(int argc, char *argv[], void *userdata) {
return 0;
}
-static int graph_one_property(sd_bus *bus, const UnitInfo *u, const char* prop, const char *color, char* patterns[], char* from_patterns[], char* to_patterns[]) {
+static int graph_one_property(
+ sd_bus *bus,
+ const UnitInfo *u,
+ const char *prop,
+ const char *color,
+ char *patterns[],
+ char *from_patterns[],
+ char *to_patterns[]) {
+
_cleanup_strv_free_ char **units = NULL;
char **unit;
int r;
match_patterns = strv_fnmatch(patterns, u->id, 0);
- if (!strv_isempty(from_patterns) &&
- !match_patterns &&
- !strv_fnmatch(from_patterns, u->id, 0))
- return 0;
+ if (!strv_isempty(from_patterns) && !match_patterns && !strv_fnmatch(from_patterns, u->id, 0))
+ return 0;
r = bus_get_unit_property_strv(bus, u->unit_path, prop, &units);
if (r < 0)
match_patterns2 = strv_fnmatch(patterns, *unit, 0);
- if (!strv_isempty(to_patterns) &&
- !match_patterns2 &&
- !strv_fnmatch(to_patterns, *unit, 0))
+ if (!strv_isempty(to_patterns) && !match_patterns2 && !strv_fnmatch(to_patterns, *unit, 0))
continue;
if (!strv_isempty(patterns) && !match_patterns && !match_patterns2)
_cleanup_fclose_ FILE *f = NULL;
int r;
- /* Let's read the available system calls from the list of available tracing events. Slightly dirty, but good
- * enough for analysis purposes. */
-
- f = fopen("/sys/kernel/debug/tracing/available_events", "re");
- if (!f)
- return log_full_errno(IN_SET(errno, EPERM, EACCES, ENOENT) ? LOG_DEBUG : LOG_WARNING, errno, "Can't read open /sys/kernel/debug/tracing/available_events: %m");
+ /* Let's read the available system calls from the list of available tracing events. Slightly dirty,
+ * but good enough for analysis purposes. */
+
+ f = fopen("/sys/kernel/tracing/available_events", "re");
+ if (!f) {
+ /* We tried the non-debugfs mount point and that didn't work. If it wasn't mounted, maybe the
+ * old debugfs mount point works? */
+ f = fopen("/sys/kernel/debug/tracing/available_events", "re");
+ if (!f)
+ return log_full_errno(IN_SET(errno, EPERM, EACCES, ENOENT) ? LOG_DEBUG : LOG_WARNING, errno,
+ "Can't read open tracefs' available_events file: %m");
+ }
for (;;) {
_cleanup_free_ char *line = NULL;
if (!e)
continue;
- /* These are named differently inside the kernel than their external name for historical reasons. Let's hide them here. */
+ /* These are named differently inside the kernel than their external name for historical
+ * reasons. Let's hide them here. */
if (STR_IN_SET(e, "newuname", "newfstat", "newstat", "newlstat", "sysctl"))
continue;
set->help);
NULSTR_FOREACH(syscall, set->value)
- printf(" %s%s%s\n",
- syscall[0] == '@' ? ansi_underline() : "",
- syscall,
- ansi_normal());
+ printf(" %s%s%s\n", syscall[0] == '@' ? ansi_underline() : "", syscall, ansi_normal());
+}
+
+static int dump_exit_codes(int argc, char *argv[], void *userdata) {
+ _cleanup_(table_unrefp) Table *table = NULL;
+ int r;
+
+ table = table_new("name", "code", "class");
+ if (!table)
+ return log_oom();
+
+ if (strv_isempty(strv_skip(argv, 1)))
+ for (size_t i = 0; i < ELEMENTSOF(exit_status_mappings); i++) {
+ if (!exit_status_mappings[i].name)
+ continue;
+
+ r = table_add_many(table,
+ TABLE_STRING, exit_status_mappings[i].name,
+ TABLE_UINT, i,
+ TABLE_STRING, exit_status_class(i));
+ if (r < 0)
+ return r;
+ }
+ else
+ for (int i = 1; i < argc; i++) {
+ int code;
+
+ code = exit_status_from_string(argv[i]);
+ if (code < 0)
+ return log_error_errno(r, "Invalid exit code \"%s\": %m", argv[i]);
+
+ assert(code >= 0 && (size_t) code < ELEMENTSOF(exit_status_mappings));
+ r = table_add_many(table,
+ TABLE_STRING, exit_status_mappings[code].name ?: "-",
+ TABLE_UINT, code,
+ TABLE_STRING, exit_status_class(code) ?: "-");
+ if (r < 0)
+ return r;
+ }
+
+ (void) pager_open(arg_pager_flags);
+
+ return table_print(table, NULL);
}
static int dump_syscall_filters(int argc, char *argv[], void *userdata) {
#else
static int dump_syscall_filters(int argc, char *argv[], void *userdata) {
- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
- "Not compiled with syscall filters, sorry.");
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Not compiled with syscall filters, sorry.");
}
#endif
+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. "
+ "Use 'systemd-analyze calendar \"%s\"' instead?", p);
+ if (timestamp && parse_timestamp(p, NULL) >= 0)
+ log_notice("Hint: this expression is a valid timestamp. "
+ "Use 'systemd-analyze timestamp \"%s\"' instead?", p);
+ if (timespan && parse_time(p, NULL, USEC_PER_SEC) >= 0)
+ log_notice("Hint: this expression is a valid timespan. "
+ "Use 'systemd-analyze timespan \"%s\"' instead?", p);
+}
+
static int dump_timespan(int argc, char *argv[], void *userdata) {
char **input_timespan;
STRV_FOREACH(input_timespan, strv_skip(argv, 1)) {
+ _cleanup_(table_unrefp) Table *table = NULL;
+ usec_t output_usecs;
+ TableCell *cell;
int r;
- usec_t usec_magnitude = 1, output_usecs;
- char ft_buf[FORMAT_TIMESPAN_MAX];
r = parse_time(*input_timespan, &output_usecs, USEC_PER_SEC);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse time span '%s': %m", *input_timespan);
+ parsing_hint(*input_timespan, true, true, false);
+ return r;
+ }
+
+ table = table_new("name", "value");
+ if (!table)
+ return log_oom();
+
+ table_set_header(table, false);
+
+ assert_se(cell = table_get_cell(table, 0, 0));
+ r = table_set_ellipsize_percent(table, cell, 100);
+ if (r < 0)
+ return r;
+
+ r = table_set_align_percent(table, cell, 100);
+ if (r < 0)
+ return r;
+
+ assert_se(cell = table_get_cell(table, 0, 1));
+ r = table_set_ellipsize_percent(table, cell, 100);
+ if (r < 0)
+ return r;
+
+ r = table_add_cell(table, NULL, TABLE_STRING, "Original:");
+ if (r < 0)
+ return r;
+
+ r = table_add_cell(table, NULL, TABLE_STRING, *input_timespan);
+ if (r < 0)
+ return r;
+
+ r = table_add_cell_stringf(table, NULL, "%ss:", special_glyph(SPECIAL_GLYPH_MU));
+ if (r < 0)
+ return r;
+
+ r = table_add_cell(table, NULL, TABLE_UINT64, &output_usecs);
+ if (r < 0)
+ return r;
+
+ r = table_add_cell(table, NULL, TABLE_STRING, "Human:");
+ if (r < 0)
+ return r;
+
+ r = table_add_cell(table, &cell, TABLE_TIMESPAN, &output_usecs);
if (r < 0)
- return log_error_errno(r, "Failed to parse time span '%s': %m", *input_timespan);
+ return r;
- printf("Original: %s\n", *input_timespan);
- printf(" %ss: %" PRIu64 "\n", special_glyph(SPECIAL_GLYPH_MU), output_usecs);
- printf(" Human: %s\n", format_timespan(ft_buf, sizeof(ft_buf), output_usecs, usec_magnitude));
+ r = table_set_color(table, cell, ansi_highlight());
+ if (r < 0)
+ return r;
+
+ r = table_print(table, NULL);
+ if (r < 0)
+ return r;
if (input_timespan[1])
putchar('\n');
return EXIT_SUCCESS;
}
+static int test_timestamp_one(const char *p) {
+ _cleanup_(table_unrefp) Table *table = NULL;
+ TableCell *cell;
+ usec_t usec;
+ int r;
+
+ r = parse_timestamp(p, &usec);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse \"%s\": %m", p);
+ parsing_hint(p, true, false, true);
+ return r;
+ }
+
+ table = table_new("name", "value");
+ if (!table)
+ return log_oom();
+
+ table_set_header(table, false);
+
+ assert_se(cell = table_get_cell(table, 0, 0));
+ r = table_set_ellipsize_percent(table, cell, 100);
+ if (r < 0)
+ return r;
+
+ r = table_set_align_percent(table, cell, 100);
+ if (r < 0)
+ return r;
+
+ assert_se(cell = table_get_cell(table, 0, 1));
+ r = table_set_ellipsize_percent(table, cell, 100);
+ if (r < 0)
+ return r;
+
+ r = table_add_cell(table, NULL, TABLE_STRING, "Original form:");
+ if (r < 0)
+ return r;
+
+ r = table_add_cell(table, NULL, TABLE_STRING, p);
+ if (r < 0)
+ return r;
+
+ r = table_add_cell(table, NULL, TABLE_STRING, "Normalized form:");
+ if (r < 0)
+ return r;
+
+ r = table_add_cell(table, &cell, TABLE_TIMESTAMP, &usec);
+ if (r < 0)
+ return r;
+
+ r = table_set_color(table, cell, ansi_highlight_blue());
+ if (r < 0)
+ return r;
+
+ if (!in_utc_timezone()) {
+ r = table_add_cell(table, NULL, TABLE_STRING, "(in UTC):");
+ if (r < 0)
+ return r;
+
+ r = table_add_cell(table, &cell, TABLE_TIMESTAMP_UTC, &usec);
+ if (r < 0)
+ return r;
+ }
+
+ r = table_add_cell(table, NULL, TABLE_STRING, "UNIX seconds:");
+ if (r < 0)
+ return r;
+
+ if (usec % USEC_PER_SEC == 0)
+ r = table_add_cell_stringf(table, &cell, "@%"PRI_USEC,
+ usec / USEC_PER_SEC);
+ else
+ r = table_add_cell_stringf(table, &cell, "@%"PRI_USEC".%06"PRI_USEC"",
+ usec / USEC_PER_SEC,
+ usec % USEC_PER_SEC);
+ if (r < 0)
+ return r;
+
+ r = table_add_cell(table, NULL, TABLE_STRING, "From now:");
+ if (r < 0)
+ return r;
+
+ r = table_add_cell(table, &cell, TABLE_TIMESTAMP_RELATIVE, &usec);
+ if (r < 0)
+ return r;
+
+ return table_print(table, NULL);
+}
+
+static int test_timestamp(int argc, char *argv[], void *userdata) {
+ int ret = 0, r;
+ char **p;
+
+ STRV_FOREACH(p, strv_skip(argv, 1)) {
+ r = test_timestamp_one(*p);
+ if (ret == 0 && r < 0)
+ ret = r;
+
+ if (*(p + 1))
+ putchar('\n');
+ }
+
+ return ret;
+}
+
static int test_calendar_one(usec_t n, const char *p) {
_cleanup_(calendar_spec_freep) CalendarSpec *spec = NULL;
+ _cleanup_(table_unrefp) Table *table = NULL;
_cleanup_free_ char *t = NULL;
+ TableCell *cell;
int r;
r = calendar_spec_from_string(p, &spec);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse calendar specification '%s': %m", p);
+ parsing_hint(p, false, true, true);
+ return r;
+ }
+
+ r = calendar_spec_to_string(spec, &t);
if (r < 0)
- return log_error_errno(r, "Failed to parse calendar specification '%s': %m", p);
+ return log_error_errno(r, "Failed to format calendar specification '%s': %m", p);
+
+ table = table_new("name", "value");
+ if (!table)
+ return log_oom();
- r = calendar_spec_normalize(spec);
+ table_set_header(table, false);
+
+ assert_se(cell = table_get_cell(table, 0, 0));
+ r = table_set_ellipsize_percent(table, cell, 100);
if (r < 0)
- return log_error_errno(r, "Failed to normalize calendar specification '%s': %m", p);
+ return r;
- r = calendar_spec_to_string(spec, &t);
+ r = table_set_align_percent(table, cell, 100);
if (r < 0)
- return log_error_errno(r, "Failed to format calendar specification '%s': %m", p);
+ return r;
+
+ assert_se(cell = table_get_cell(table, 0, 1));
+ r = table_set_ellipsize_percent(table, cell, 100);
+ if (r < 0)
+ return r;
- if (!streq(t, p))
- printf(" Original form: %s\n", p);
+ if (!streq(t, p)) {
+ r = table_add_cell(table, NULL, TABLE_STRING, "Original form:");
+ if (r < 0)
+ return r;
- printf("Normalized form: %s\n", t);
+ r = table_add_cell(table, NULL, TABLE_STRING, p);
+ if (r < 0)
+ return r;
+ }
+
+ r = table_add_cell(table, NULL, TABLE_STRING, "Normalized form:");
+ if (r < 0)
+ return r;
+
+ r = table_add_cell(table, NULL, TABLE_STRING, t);
+ if (r < 0)
+ return r;
for (unsigned i = 0; i < arg_iterations; i++) {
- char buffer[CONST_MAX(FORMAT_TIMESTAMP_MAX, FORMAT_TIMESTAMP_RELATIVE_MAX)];
usec_t next;
r = calendar_spec_next_usec(spec, n, &next);
if (r == -ENOENT) {
- if (i == 0)
- printf(" Next elapse: never\n");
- return 0;
+ if (i == 0) {
+ r = table_add_cell(table, NULL, TABLE_STRING, "Next elapse:");
+ if (r < 0)
+ return r;
+
+ r = table_add_cell(table, &cell, TABLE_STRING, "never");
+ if (r < 0)
+ return r;
+
+ r = table_set_color(table, cell, ansi_highlight_yellow());
+ if (r < 0)
+ return r;
+ }
+ break;
}
if (r < 0)
return log_error_errno(r, "Failed to determine next elapse for '%s': %m", p);
- if (i == 0)
- printf(" Next elapse: %s\n", format_timestamp(buffer, sizeof(buffer), next));
- else {
- int k = DECIMAL_STR_WIDTH(i+1);
+ if (i == 0) {
+ r = table_add_cell(table, NULL, TABLE_STRING, "Next elapse:");
+ if (r < 0)
+ return r;
+
+ r = table_add_cell(table, &cell, TABLE_TIMESTAMP, &next);
+ if (r < 0)
+ return r;
+
+ r = table_set_color(table, cell, ansi_highlight_blue());
+ if (r < 0)
+ return r;
+ } else {
+ int k = DECIMAL_STR_WIDTH(i + 1);
if (k < 8)
k = 8 - k;
else
k = 0;
- printf("%*sIter. #%u: %s\n", k, "", i+1, format_timestamp(buffer, sizeof(buffer), next));
+ r = table_add_cell_stringf(table, NULL, "Iter. #%u:", i+1);
+ if (r < 0)
+ return r;
+
+ r = table_add_cell(table, &cell, TABLE_TIMESTAMP, &next);
+ if (r < 0)
+ return r;
+
+ r = table_set_color(table, cell, ansi_highlight_blue());
+ if (r < 0)
+ return r;
+ }
+
+ if (!in_utc_timezone()) {
+ r = table_add_cell(table, NULL, TABLE_STRING, "(in UTC):");
+ if (r < 0)
+ return r;
+
+ r = table_add_cell(table, NULL, TABLE_TIMESTAMP_UTC, &next);
+ if (r < 0)
+ return r;
}
- if (!in_utc_timezone())
- printf(" (in UTC): %s\n", format_timestamp_utc(buffer, sizeof(buffer), next));
+ r = table_add_cell(table, NULL, TABLE_STRING, "From now:");
+ if (r < 0)
+ return r;
- printf(" From now: %s\n", format_timestamp_relative(buffer, sizeof(buffer), next));
+ r = table_add_cell(table, NULL, TABLE_TIMESTAMP_RELATIVE, &next);
+ if (r < 0)
+ return r;
n = next;
}
- return 0;
+ return table_print(table, NULL);
}
static int test_calendar(int argc, char *argv[], void *userdata) {
if (ret == 0 && r < 0)
ret = r;
- if (*(p+1))
+ if (*(p + 1))
putchar('\n');
}
return 0;
}
+static int do_condition(int argc, char *argv[], void *userdata) {
+ return verify_conditions(strv_skip(argv, 1), arg_scope);
+}
+
static int do_verify(int argc, char *argv[], void *userdata) {
return verify_units(strv_skip(argv, 1), arg_scope, arg_man, arg_generators);
}
}
static int help(int argc, char *argv[], void *userdata) {
- _cleanup_free_ char *link = NULL;
+ _cleanup_free_ char *link = NULL, *dot_link = NULL;
int r;
(void) pager_open(arg_pager_flags);
if (r < 0)
return log_oom();
+ /* Not using terminal_urlify_man() for this, since we don't want the "man page" text suffix in this case. */
+ r = terminal_urlify("man:dot(1)", "dot(1)", &dot_link);
+ if (r < 0)
+ return log_oom();
+
printf("%s [OPTIONS...] {COMMAND} ...\n\n"
"Profile systemd, show unit dependencies, check unit files.\n\n"
" -h --help Show this help\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 also services which finished SECONDS\n"
- " earlier than the latest in the branch\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"
" 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 man:dot(1) format\n"
+ " dot [UNIT...] Output dependency graph in %s format\n"
" log-level [LEVEL] Get/set logging threshold for manager\n"
" log-target [TARGET] Get/set logging target for manager\n"
" dump Output state serialization of service manager\n"
" cat-config Show configuration file and drop-ins\n"
" unit-paths List load directories for units\n"
+ " exit-codes List exit code 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"
" service-watchdogs [BOOL] Get/set service watchdog state\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"
"\nSee the %s for details.\n"
, program_invocation_short_name
+ , dot_link
, link
);
- /* When updating this list, including descriptions, apply changes to shell-completion/bash/systemd-analyze and
- * shell-completion/zsh/_systemd-analyze too. */
+ /* When updating this list, including descriptions, apply changes to
+ * shell-completion/bash/systemd-analyze and shell-completion/zsh/_systemd-analyze too. */
return 0;
}
{ "dump", VERB_ANY, 1, 0, dump },
{ "cat-config", 2, VERB_ANY, 0, cat_config },
{ "unit-paths", 1, 1, 0, dump_unit_paths },
+ { "exit-codes", VERB_ANY, VERB_ANY, 0, dump_exit_codes },
{ "syscall-filter", VERB_ANY, VERB_ANY, 0, dump_syscall_filters },
+ { "condition", 2, VERB_ANY, 0, do_condition },
{ "verify", 2, VERB_ANY, 0, do_verify },
{ "calendar", 2, VERB_ANY, 0, test_calendar },
- { "service-watchdogs", VERB_ANY, 2, 0, service_watchdogs },
+ { "timestamp", 2, VERB_ANY, 0, test_timestamp },
{ "timespan", 2, VERB_ANY, 0, dump_timespan },
+ { "service-watchdogs", VERB_ANY, 2, 0, service_watchdogs },
{ "security", VERB_ANY, VERB_ANY, 0, do_security },
{}
};
setlocale(LC_ALL, "");
setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */
+ log_show_color(true);
log_parse_environment();
log_open();