diff options
-rw-r--r-- | man/coredumpctl.xml | 16 | ||||
-rw-r--r-- | src/coredump/coredumpctl.c | 276 | ||||
-rw-r--r-- | src/shared/format-table.c | 131 | ||||
-rw-r--r-- | src/shared/format-table.h | 14 |
4 files changed, 335 insertions, 102 deletions
diff --git a/man/coredumpctl.xml b/man/coredumpctl.xml index b06cadd08c..f69f26dc66 100644 --- a/man/coredumpctl.xml +++ b/man/coredumpctl.xml @@ -154,20 +154,22 @@ <xi:include href="standard-options.xml" xpointer="help" /> <xi:include href="standard-options.xml" xpointer="version" /> + <xi:include href="standard-options.xml" xpointer="no-pager" /> + <xi:include href="standard-options.xml" xpointer="no-legend" /> + <xi:include href="standard-options.xml" xpointer="json" /> <varlistentry> - <term><option>--no-legend</option></term> + <term><option>-1</option></term> - <listitem><para>Do not print column headers.</para></listitem> + <listitem><para>Show information of the most recent core dump only, instead of listing all known core + dumps. (Equivalent to <option>--reverse -n 1</option></para></listitem> </varlistentry> - <xi:include href="standard-options.xml" xpointer="no-pager" /> - <varlistentry> - <term><option>-1</option></term> + <term><option>-n</option> <replaceable>INT</replaceable></term> - <listitem><para>Show information of a single core dump only, instead of listing - all known core dumps.</para></listitem> + <listitem><para>Show at most the specified number of entries. The specified parameter must be an + integer greater or equal to 1.</para></listitem> </varlistentry> <varlistentry> diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index 55283a93ae..46183451de 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -16,6 +16,7 @@ #include "compress.h" #include "def.h" #include "fd-util.h" +#include "format-table.h" #include "fs-util.h" #include "glob-util.h" #include "journal-internal.h" @@ -47,9 +48,10 @@ static const char *arg_debugger = NULL; static char **arg_debugger_args = NULL; static const char *arg_directory = NULL; static char **arg_file = NULL; +static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; static PagerFlags arg_pager_flags = 0; -static int arg_no_legend = false; -static int arg_one = false; +static int arg_legend = true; +static size_t arg_rows_max = SIZE_MAX; static const char* arg_output = NULL; static bool arg_reverse = false; static bool arg_quiet = false; @@ -155,20 +157,23 @@ static int help(void) { if (r < 0) return log_oom(); - printf("%s [OPTIONS...] COMMAND ...\n\n" - "%sList or retrieve coredumps from the journal.%s\n" - "\nCommands:\n" + printf("%1$s [OPTIONS...] COMMAND ...\n\n" + "%5$sList or retrieve coredumps from the journal.%6$s\n" + "\n%3$sCommands:%4$s\n" " list [MATCHES...] List available coredumps (default)\n" " info [MATCHES...] Show detailed information about one or more coredumps\n" " dump [MATCHES...] Print first matching coredump to stdout\n" " debug [MATCHES...] Start a debugger for the first matching coredump\n" - "\nOptions:\n" + "\n%3$sOptions:%4$s\n" " -h --help Show this help\n" " --version Print version string\n" " --no-pager Do not pipe output into a pager\n" " --no-legend Do not print the column headers\n" + " --json=pretty|short|off\n" + " Generate JSON output\n" " --debugger=DEBUGGER Use the given debugger\n" " -A --debugger-arguments=ARGS Pass the given arguments to the debugger\n" + " -n INT Show maximum number of rows\n" " -1 Show information about most recent entry only\n" " -S --since=DATE Only print coredumps since the date\n" " -U --until=DATE Only print coredumps until the date\n" @@ -178,11 +183,13 @@ static int help(void) { " --file=PATH Use journal file\n" " -D --directory=DIR Use journal files from directory\n\n" " -q --quiet Do not show info messages and privilege warning\n" - "\nSee the %s for details.\n", + "\nSee the %2$s for details.\n", program_invocation_short_name, - ansi_highlight(), + link, + ansi_underline(), ansi_normal(), - link); + ansi_highlight(), + ansi_normal()); return 0; } @@ -192,6 +199,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_VERSION = 0x100, ARG_NO_PAGER, ARG_NO_LEGEND, + ARG_JSON, ARG_DEBUGGER, ARG_FILE, }; @@ -213,13 +221,14 @@ static int parse_argv(int argc, char *argv[]) { { "since", required_argument, NULL, 'S' }, { "until", required_argument, NULL, 'U' }, { "quiet", no_argument, NULL, 'q' }, + { "json", required_argument, NULL, ARG_JSON }, {} }; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hA:o:F:1D:rS:U:q", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "hA:o:F:1D:rS:U:qn:", options, NULL)) >= 0) switch(c) { case 'h': return help(); @@ -232,7 +241,7 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_NO_LEGEND: - arg_no_legend = true; + arg_legend = false; break; case ARG_DEBUGGER: @@ -282,8 +291,21 @@ static int parse_argv(int argc, char *argv[]) { break; case '1': - arg_one = true; + arg_rows_max = 1; + arg_reverse = true; + break; + + case 'n': { + unsigned n; + + r = safe_atou(optarg, &n); + if (r < 0 || n < 1) + return log_error_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), + "Invalid numeric parameter to -n: %s", optarg); + + arg_rows_max = n; break; + } case 'D': arg_directory = optarg; @@ -297,6 +319,13 @@ static int parse_argv(int argc, char *argv[]) { arg_quiet = true; break; + case ARG_JSON: + r = json_parse_cmdline_parameter_and_warn(optarg, &arg_json_format_flags); + if (r <= 0) + return r; + + break; + case '?': return -EINVAL; @@ -376,21 +405,78 @@ static int print_field(FILE* file, sd_journal *j) { continue; \ } -static int print_list(FILE* file, sd_journal *j, int had_legend) { +static void analyze_coredump_file( + const char *path, + const char **ret_state, + const char **ret_color, + uint64_t *ret_size) { + + _cleanup_close_ int fd = -1; + struct stat st; + int r; + + assert(path); + assert(ret_state); + assert(ret_color); + assert(ret_size); + + fd = open(path, O_PATH|O_CLOEXEC); + if (fd < 0) { + if (errno == ENOENT) { + *ret_state = "missing"; + *ret_color = ansi_grey(); + *ret_size = UINT64_MAX; + return; + } + + r = -errno; + } else + r = access_fd(fd, R_OK); + if (ERRNO_IS_PRIVILEGE(r)) { + *ret_state = "inaccessible"; + *ret_color = ansi_highlight_yellow(); + *ret_size = UINT64_MAX; + return; + } + if (r < 0) + goto error; + + if (fstat(fd, &st) < 0) + goto error; + + if (!S_ISREG(st.st_mode)) + goto error; + + *ret_state = "present"; + *ret_color = NULL; + *ret_size = st.st_size; + return; + +error: + *ret_state = "error"; + *ret_color = ansi_highlight_red(); + *ret_size = UINT64_MAX; +} + +static int print_list(FILE* file, sd_journal *j, Table *t) { _cleanup_free_ char *mid = NULL, *pid = NULL, *uid = NULL, *gid = NULL, *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL, *filename = NULL, *truncated = NULL, *coredump = NULL; const void *d; size_t l; - usec_t t; - char buf[FORMAT_TIMESTAMP_MAX]; - int r; - const char *present; + usec_t ts; + int r, signal_as_int = 0; + const char *present = NULL, *color = NULL; + uint64_t size = UINT64_MAX; bool normal_coredump; + uid_t uid_as_int = UID_INVALID; + gid_t gid_as_int = GID_INVALID; + pid_t pid_as_int = 0; assert(file); assert(j); + assert(t); SD_JOURNAL_FOREACH_DATA(j, d, l) { RETRIEVE(d, l, "MESSAGE_ID", mid); @@ -406,54 +492,46 @@ static int print_list(FILE* file, sd_journal *j, int had_legend) { RETRIEVE(d, l, "COREDUMP", coredump); } - if (!pid && !uid && !gid && !sgnl && !exe && !comm && !cmdline && !filename) { - log_warning("Empty coredump log entry"); - return -EINVAL; - } + if (!pid && !uid && !gid && !sgnl && !exe && !comm && !cmdline && !filename) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Empty coredump log entry"); + + (void) parse_uid(uid, &uid_as_int); + (void) parse_gid(gid, &gid_as_int); + (void) parse_pid(pid, &pid_as_int); + signal_as_int = signal_from_string(sgnl); - r = sd_journal_get_realtime_usec(j, &t); + r = sd_journal_get_realtime_usec(j, &ts); if (r < 0) return log_error_errno(r, "Failed to get realtime timestamp: %m"); - format_timestamp(buf, sizeof(buf), t); - - if (!had_legend && !arg_no_legend) - fprintf(file, "%-*s %*s %*s %*s %*s %-*s %s\n", - FORMAT_TIMESTAMP_WIDTH, "TIME", - 6, "PID", - 5, "UID", - 5, "GID", - 3, "SIG", - 9, "COREFILE", - "EXE"); - normal_coredump = streq_ptr(mid, SD_MESSAGE_COREDUMP_STR); if (filename) - if (access(filename, R_OK) == 0) - present = "present"; - else if (errno == ENOENT) - present = "missing"; - else - present = "error"; + analyze_coredump_file(filename, &present, &color, &size); else if (coredump) present = "journal"; - else if (normal_coredump) + else if (normal_coredump) { present = "none"; - else - present = "-"; + color = ansi_grey(); + } else + present = NULL; - if (STR_IN_SET(present, "present", "journal") && truncated && parse_boolean(truncated) > 0) + if (STRPTR_IN_SET(present, "present", "journal") && truncated && parse_boolean(truncated) > 0) present = "truncated"; - fprintf(file, "%-*s %*s %*s %*s %*s %-*s %s\n", - FORMAT_TIMESTAMP_WIDTH, buf, - 6, strna(pid), - 5, strna(uid), - 5, strna(gid), - 3, normal_coredump ? strna(sgnl) : "-", - 9, present, - strna(exe ?: (comm ?: cmdline))); + r = table_add_many( + t, + TABLE_TIMESTAMP, ts, + TABLE_PID, pid_as_int, + TABLE_UID, uid_as_int, + TABLE_GID, gid_as_int, + TABLE_SIGNAL, normal_coredump ? signal_as_int : 0, + TABLE_STRING, present, + TABLE_SET_COLOR, color, + TABLE_STRING, exe ?: comm ?: cmdline, + TABLE_SIZE, size); + if (r < 0) + return table_log_add_error(r); return 0; } @@ -612,24 +690,27 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) { fprintf(file, " Hostname: %s\n", hostname); if (filename) { - bool inacc, trunc; - - inacc = access(filename, R_OK) < 0; - trunc = truncated && parse_boolean(truncated) > 0; - - if (inacc || trunc) - fprintf(file, " Storage: %s%s (%s%s%s)%s\n", - ansi_highlight_red(), - filename, - inacc ? "inaccessible" : "", - inacc && trunc ? ", " : "", - trunc ? "truncated" : "", - ansi_normal()); - else - fprintf(file, " Storage: %s\n", filename); - } + const char *state = NULL, *color = NULL; + uint64_t size = UINT64_MAX; + char buf[FORMAT_BYTES_MAX]; - else if (coredump) + analyze_coredump_file(filename, &state, &color, &size); + + if (STRPTR_IN_SET(state, "present", "journal") && truncated && parse_boolean(truncated) > 0) + state = "truncated"; + + fprintf(file, + " Storage: %s%s (%s)%s\n", + strempty(color), + filename, + state, + ansi_normal()); + + if (size != UINT64_MAX) + fprintf(file, + " Disk Size: %s\n", + format_bytes(buf, sizeof(buf), size)); + } else if (coredump) fprintf(file, " Storage: journal\n"); else fprintf(file, " Storage: none\n"); @@ -659,43 +740,61 @@ static int focus(sd_journal *j) { return r; } -static int print_entry(sd_journal *j, unsigned n_found, bool verb_is_info) { +static int print_entry( + sd_journal *j, + size_t n_found, + Table *t) { + assert(j); - if (verb_is_info) - return print_info(stdout, j, n_found); + if (t) + return print_list(stdout, j, t); else if (arg_field) return print_field(stdout, j); else - return print_list(stdout, j, n_found); + return print_info(stdout, j, n_found > 0); } static int dump_list(int argc, char **argv, void *userdata) { _cleanup_(sd_journal_closep) sd_journal *j = NULL; - unsigned n_found = 0; + _cleanup_(table_unrefp) Table *t = NULL; + size_t n_found = 0; bool verb_is_info; int r; - verb_is_info = (argc >= 1 && streq(argv[0], "info")); + verb_is_info = argc >= 1 && streq(argv[0], "info"); r = acquire_journal(&j, argv + 1); if (r < 0) return r; - (void) pager_open(arg_pager_flags); + /* The coredumps are likely compressed, and for just listing them we don't need to decompress them, + * so let's pick a fairly low data threshold here */ + (void) sd_journal_set_data_threshold(j, 4096); + + if (!verb_is_info && !arg_field) { + t = table_new("time", "pid", "uid", "gid", "sig", "corefile", "exe", "size"); + if (!t) + return log_oom(); + + (void) table_set_align_percent(t, TABLE_HEADER_CELL(1), 100); + (void) table_set_align_percent(t, TABLE_HEADER_CELL(2), 100); + (void) table_set_align_percent(t, TABLE_HEADER_CELL(3), 100); + (void) table_set_align_percent(t, TABLE_HEADER_CELL(7), 100); - /* The coredumps are likely to compressed, and for just - * listing them we don't need to decompress them, so let's - * pick a fairly low data threshold here */ - sd_journal_set_data_threshold(j, 4096); + (void) table_set_empty_string(t, "-"); + } else + (void) pager_open(arg_pager_flags); /* "info" without pattern implies "-1" */ - if (arg_one || (verb_is_info && argc == 1)) { + if ((arg_rows_max == 1 && arg_reverse) || (verb_is_info && argc == 1)) { r = focus(j); if (r < 0) return r; - return print_entry(j, 0, verb_is_info); + r = print_entry(j, 0, t); + if (r < 0) + return r; } else { if (arg_since != USEC_INFINITY && !arg_reverse) r = sd_journal_seek_realtime_usec(j, arg_since); @@ -713,10 +812,8 @@ static int dump_list(int argc, char **argv, void *userdata) { r = sd_journal_next(j); else r = sd_journal_previous(j); - if (r < 0) return log_error_errno(r, "Failed to iterate through journal: %m"); - if (r == 0) break; @@ -740,9 +837,12 @@ static int dump_list(int argc, char **argv, void *userdata) { continue; } - r = print_entry(j, n_found++, verb_is_info); + r = print_entry(j, n_found++, t); if (r < 0) return r; + + if (arg_rows_max != SIZE_MAX && n_found >= arg_rows_max) + break; } if (!arg_field && n_found <= 0) { @@ -752,6 +852,12 @@ static int dump_list(int argc, char **argv, void *userdata) { } } + if (t) { + r = table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend); + if (r < 0) + return r; + } + return 0; } diff --git a/src/shared/format-table.c b/src/shared/format-table.c index e3d8a051a2..05bcff0095 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -20,11 +20,14 @@ #include "parse-util.h" #include "path-util.h" #include "pretty-print.h" +#include "process-util.h" +#include "signal-util.h" #include "sort-util.h" #include "string-util.h" #include "strxcpyx.h" #include "terminal-util.h" #include "time-util.h" +#include "user-util.h" #include "utf8.h" #include "util.h" @@ -100,6 +103,9 @@ typedef struct TableData { int ifindex; union in_addr_union address; sd_id128_t id128; + uid_t uid; + gid_t gid; + pid_t pid; /* … add more here as we start supporting more cell data types … */ }; } TableData; @@ -284,6 +290,7 @@ static size_t table_data_size(TableDataType type, const void *data) { case TABLE_UINT: case TABLE_PERCENT: case TABLE_IFINDEX: + case TABLE_SIGNAL: return sizeof(int); case TABLE_IN_ADDR: @@ -296,6 +303,13 @@ static size_t table_data_size(TableDataType type, const void *data) { case TABLE_ID128: return sizeof(sd_id128_t); + case TABLE_UID: + return sizeof(uid_t); + case TABLE_GID: + return sizeof(gid_t); + case TABLE_PID: + return sizeof(pid_t); + default: assert_not_reached("Uh? Unexpected cell type"); } @@ -801,6 +815,9 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) { bool b; union in_addr_union address; sd_id128_t id128; + uid_t uid; + gid_t gid; + pid_t pid; } buffer; switch (type) { @@ -840,6 +857,7 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) { break; case TABLE_INT: + case TABLE_SIGNAL: buffer.int_val = va_arg(ap, int); data = &buffer.int_val; break; @@ -931,6 +949,21 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) { data = &buffer.id128; break; + case TABLE_UID: + buffer.uid = va_arg(ap, uid_t); + data = &buffer.uid; + break; + + case TABLE_GID: + buffer.gid = va_arg(ap, gid_t); + data = &buffer.gid; + break; + + case TABLE_PID: + buffer.pid = va_arg(ap, pid_t); + data = &buffer.pid; + break; + case TABLE_SET_MINIMUM_WIDTH: { size_t w = va_arg(ap, size_t); @@ -1189,6 +1222,7 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t return CMP(a->size, b->size); case TABLE_INT: + case TABLE_SIGNAL: return CMP(a->int_val, b->int_val); case TABLE_INT8: @@ -1234,6 +1268,15 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t case TABLE_ID128: return memcmp(&a->id128, &b->id128, sizeof(sd_id128_t)); + case TABLE_UID: + return CMP(a->uid, b->uid); + + case TABLE_GID: + return CMP(a->gid, b->gid); + + case TABLE_PID: + return CMP(a->pid, b->pid); + default: ; } @@ -1618,6 +1661,67 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas break; } + case TABLE_UID: { + _cleanup_free_ char *p = NULL; + + if (!uid_is_valid(d->uid)) + return "n/a"; + + p = new(char, DECIMAL_STR_WIDTH(d->uid) + 1); + if (!p) + return NULL; + + sprintf(p, UID_FMT, d->uid); + d->formatted = TAKE_PTR(p); + break; + } + + case TABLE_GID: { + _cleanup_free_ char *p = NULL; + + if (!gid_is_valid(d->gid)) + return "n/a"; + + p = new(char, DECIMAL_STR_WIDTH(d->gid) + 1); + if (!p) + return NULL; + + sprintf(p, GID_FMT, d->gid); + d->formatted = TAKE_PTR(p); + break; + } + + case TABLE_PID: { + _cleanup_free_ char *p = NULL; + + if (!pid_is_valid(d->pid)) + return "n/a"; + + p = new(char, DECIMAL_STR_WIDTH(d->pid) + 1); + if (!p) + return NULL; + + sprintf(p, PID_FMT, d->pid); + d->formatted = TAKE_PTR(p); + break; + } + + case TABLE_SIGNAL: { + _cleanup_free_ char *p = NULL; + const char *suffix; + + suffix = signal_to_string(d->int_val); + if (!suffix) + return "n/a"; + + p = strjoin("SIG", suffix); + if (!p) + return NULL; + + d->formatted = TAKE_PTR(p); + break; + } + default: assert_not_reached("Unexpected type?"); } @@ -2374,6 +2478,9 @@ static int table_data_to_json(TableData *d, JsonVariant **ret) { return json_variant_new_integer(ret, d->percent); case TABLE_IFINDEX: + if (d->ifindex <= 0) + return json_variant_new_null(ret); + return json_variant_new_integer(ret, d->ifindex); case TABLE_IN_ADDR: @@ -2392,6 +2499,30 @@ static int table_data_to_json(TableData *d, JsonVariant **ret) { return json_variant_new_string(ret, id128_to_uuid_string(d->id128, buf)); } + case TABLE_UID: + if (!uid_is_valid(d->uid)) + return json_variant_new_null(ret); + + return json_variant_new_integer(ret, d->uid); + + case TABLE_GID: + if (!gid_is_valid(d->gid)) + return json_variant_new_null(ret); + + return json_variant_new_integer(ret, d->gid); + + case TABLE_PID: + if (!pid_is_valid(d->pid)) + return json_variant_new_null(ret); + + return json_variant_new_integer(ret, d->pid); + + case TABLE_SIGNAL: + if (!SIGNAL_VALID(d->int_val)) + return json_variant_new_null(ret); + + return json_variant_new_integer(ret, d->int_val); + default: return -EINVAL; } diff --git a/src/shared/format-table.h b/src/shared/format-table.h index 6627a291f3..732abf27ec 100644 --- a/src/shared/format-table.h +++ b/src/shared/format-table.h @@ -39,6 +39,10 @@ typedef enum TableDataType { TABLE_IN6_ADDR, /* Takes a union in_addr_union (or a struct in6_addr) */ TABLE_ID128, TABLE_UUID, + TABLE_UID, + TABLE_GID, + TABLE_PID, + TABLE_SIGNAL, _TABLE_DATA_TYPE_MAX, /* The following are not really data types, but commands for table_add_cell_many() to make changes to @@ -57,16 +61,6 @@ typedef enum TableDataType { _TABLE_DATA_TYPE_INVALID = -1, } TableDataType; -/* PIDs are just 32bit signed integers on Linux */ -#define TABLE_PID TABLE_INT32 -assert_cc(sizeof(pid_t) == sizeof(int32_t)); - -/* UIDs/GIDs are just 32bit unsigned integers on Linux */ -#define TABLE_UID TABLE_UINT32 -#define TABLE_GID TABLE_UINT32 -assert_cc(sizeof(uid_t) == sizeof(uint32_t)); -assert_cc(sizeof(gid_t) == sizeof(uint32_t)); - typedef struct Table Table; typedef struct TableCell TableCell; |