summaryrefslogtreecommitdiffstats
path: root/src/coredump/coredumpctl.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/coredump/coredumpctl.c')
-rw-r--r--src/coredump/coredumpctl.c1404
1 files changed, 1404 insertions, 0 deletions
diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c
new file mode 100644
index 0000000..d9847c8
--- /dev/null
+++ b/src/coredump/coredumpctl.c
@@ -0,0 +1,1404 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <locale.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "sd-bus.h"
+#include "sd-journal.h"
+#include "sd-messages.h"
+
+#include "alloc-util.h"
+#include "bus-error.h"
+#include "bus-locator.h"
+#include "bus-util.h"
+#include "chase-symlinks.h"
+#include "compress.h"
+#include "def.h"
+#include "dissect-image.h"
+#include "escape.h"
+#include "fd-util.h"
+#include "format-table.h"
+#include "fs-util.h"
+#include "glob-util.h"
+#include "journal-internal.h"
+#include "journal-util.h"
+#include "log.h"
+#include "macro.h"
+#include "main-func.h"
+#include "mount-util.h"
+#include "pager.h"
+#include "parse-argument.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "pretty-print.h"
+#include "process-util.h"
+#include "rlimit-util.h"
+#include "sigbus.h"
+#include "signal-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "tmpfile-util.h"
+#include "user-util.h"
+#include "util.h"
+#include "verbs.h"
+
+#define SHORT_BUS_CALL_TIMEOUT_USEC (3 * USEC_PER_SEC)
+
+static usec_t arg_since = USEC_INFINITY, arg_until = USEC_INFINITY;
+static const char* arg_field = NULL;
+static const char *arg_debugger = NULL;
+static char **arg_debugger_args = NULL;
+static const char *arg_directory = NULL;
+static char *arg_root = NULL;
+static char *arg_image = NULL;
+static char **arg_file = NULL;
+static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
+static PagerFlags arg_pager_flags = 0;
+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;
+static bool arg_all = false;
+
+STATIC_DESTRUCTOR_REGISTER(arg_debugger_args, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_file, strv_freep);
+
+static int add_match(sd_journal *j, const char *match) {
+ _cleanup_free_ char *p = NULL;
+ const char* prefix, *pattern;
+ pid_t pid;
+ int r;
+
+ if (strchr(match, '='))
+ prefix = "";
+ else if (strchr(match, '/')) {
+ r = path_make_absolute_cwd(match, &p);
+ if (r < 0)
+ return log_error_errno(r, "path_make_absolute_cwd(\"%s\"): %m", match);
+
+ match = p;
+ prefix = "COREDUMP_EXE=";
+ } else if (parse_pid(match, &pid) >= 0)
+ prefix = "COREDUMP_PID=";
+ else
+ prefix = "COREDUMP_COMM=";
+
+ pattern = strjoina(prefix, match);
+ log_debug("Adding match: %s", pattern);
+ r = sd_journal_add_match(j, pattern, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add match \"%s\": %m", match);
+
+ return 0;
+}
+
+static int add_matches(sd_journal *j, char **matches) {
+ int r;
+
+ r = sd_journal_add_match(j, "MESSAGE_ID=" SD_MESSAGE_COREDUMP_STR, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add match \"%s\": %m", "MESSAGE_ID=" SD_MESSAGE_COREDUMP_STR);
+
+ r = sd_journal_add_match(j, "MESSAGE_ID=" SD_MESSAGE_BACKTRACE_STR, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add match \"%s\": %m", "MESSAGE_ID=" SD_MESSAGE_BACKTRACE_STR);
+
+ STRV_FOREACH(match, matches) {
+ r = add_match(j, *match);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int acquire_journal(sd_journal **ret, char **matches) {
+ _cleanup_(sd_journal_closep) sd_journal *j = NULL;
+ int r;
+
+ assert(ret);
+
+ if (arg_directory) {
+ r = sd_journal_open_directory(&j, arg_directory, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to open journals in directory: %s: %m", arg_directory);
+ } else if (arg_root) {
+ r = sd_journal_open_directory(&j, arg_root, SD_JOURNAL_OS_ROOT);
+ if (r < 0)
+ return log_error_errno(r, "Failed to open journals in root directory: %s: %m", arg_root);
+ } else if (arg_file) {
+ r = sd_journal_open_files(&j, (const char**)arg_file, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to open journal files: %m");
+ } else {
+ r = sd_journal_open(&j, arg_all ? 0 : SD_JOURNAL_LOCAL_ONLY);
+ if (r < 0)
+ return log_error_errno(r, "Failed to open journal: %m");
+ }
+
+ r = journal_access_check_and_warn(j, arg_quiet, true);
+ if (r < 0)
+ return r;
+
+ r = add_matches(j, matches);
+ if (r < 0)
+ return r;
+
+ if (DEBUG_LOGGING) {
+ _cleanup_free_ char *filter = NULL;
+
+ filter = journal_make_match_string(j);
+ log_debug("Journal filter: %s", filter);
+ }
+
+ *ret = TAKE_PTR(j);
+
+ return 0;
+}
+
+static int verb_help(int argc, char **argv, void *userdata) {
+ _cleanup_free_ char *link = NULL;
+ int r;
+
+ r = terminal_urlify_man("coredumpctl", "1", &link);
+ if (r < 0)
+ return log_oom();
+
+ 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"
+ "\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"
+ " -r --reverse Show the newest entries first\n"
+ " -F --field=FIELD List all values a certain field takes\n"
+ " -o --output=FILE Write output to FILE\n"
+ " --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"
+ " --all Look at all journal files instead of local ones\n"
+ " --root=PATH Operate on an alternate filesystem root\n"
+ " --image=PATH Operate on disk image as filesystem root\n"
+ "\nSee the %2$s for details.\n",
+ program_invocation_short_name,
+ link,
+ ansi_underline(),
+ ansi_normal(),
+ ansi_highlight(),
+ ansi_normal());
+
+ return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_NO_PAGER,
+ ARG_NO_LEGEND,
+ ARG_JSON,
+ ARG_DEBUGGER,
+ ARG_FILE,
+ ARG_ROOT,
+ ARG_IMAGE,
+ ARG_ALL,
+ };
+
+ int c, r;
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version" , no_argument, NULL, ARG_VERSION },
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER },
+ { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
+ { "debugger", required_argument, NULL, ARG_DEBUGGER },
+ { "debugger-arguments", required_argument, NULL, 'A' },
+ { "output", required_argument, NULL, 'o' },
+ { "field", required_argument, NULL, 'F' },
+ { "file", required_argument, NULL, ARG_FILE },
+ { "directory", required_argument, NULL, 'D' },
+ { "reverse", no_argument, NULL, 'r' },
+ { "since", required_argument, NULL, 'S' },
+ { "until", required_argument, NULL, 'U' },
+ { "quiet", no_argument, NULL, 'q' },
+ { "json", required_argument, NULL, ARG_JSON },
+ { "root", required_argument, NULL, ARG_ROOT },
+ { "image", required_argument, NULL, ARG_IMAGE },
+ { "all", no_argument, NULL, ARG_ALL },
+ {}
+ };
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "hA:o:F:1D:rS:U:qn:", options, NULL)) >= 0)
+ switch (c) {
+ case 'h':
+ return verb_help(0, NULL, NULL);
+
+ case ARG_VERSION:
+ return version();
+
+ case ARG_NO_PAGER:
+ arg_pager_flags |= PAGER_DISABLE;
+ break;
+
+ case ARG_NO_LEGEND:
+ arg_legend = false;
+ break;
+
+ case ARG_DEBUGGER:
+ arg_debugger = optarg;
+ break;
+
+ case 'A': {
+ _cleanup_strv_free_ char **l = NULL;
+ r = strv_split_full(&l, optarg, WHITESPACE, EXTRACT_UNQUOTE);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse debugger arguments '%s': %m", optarg);
+ strv_free_and_replace(arg_debugger_args, l);
+ break;
+ }
+
+ case ARG_FILE:
+ r = glob_extend(&arg_file, optarg, GLOB_NOCHECK);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add paths: %m");
+ break;
+
+ case 'o':
+ if (arg_output)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Cannot set output more than once.");
+
+ arg_output = optarg;
+ break;
+
+ case 'S':
+ r = parse_timestamp(optarg, &arg_since);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse timestamp '%s': %m", optarg);
+ break;
+
+ case 'U':
+ r = parse_timestamp(optarg, &arg_until);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse timestamp '%s': %m", optarg);
+ break;
+
+ case 'F':
+ if (arg_field)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Cannot use --field/-F more than once.");
+ arg_field = optarg;
+ break;
+
+ case '1':
+ 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;
+ break;
+
+ case ARG_ROOT:
+ r = parse_path_argument(optarg, false, &arg_root);
+ if (r < 0)
+ return r;
+ break;
+
+ case ARG_IMAGE:
+ r = parse_path_argument(optarg, false, &arg_image);
+ if (r < 0)
+ return r;
+ break;
+
+ case 'r':
+ arg_reverse = true;
+ break;
+
+ case 'q':
+ arg_quiet = true;
+ break;
+
+ case ARG_JSON:
+ r = parse_json_argument(optarg, &arg_json_format_flags);
+ if (r <= 0)
+ return r;
+
+ break;
+
+ case ARG_ALL:
+ arg_all = true;
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached();
+ }
+
+ if (arg_since != USEC_INFINITY && arg_until != USEC_INFINITY &&
+ arg_since > arg_until)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--since= must be before --until=.");
+
+ if ((!!arg_directory + !!arg_image + !!arg_root) > 1)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root=, --image= or -D/--directory=, the combination of these options is not supported.");
+
+ return 1;
+}
+
+static int retrieve(const void *data,
+ size_t len,
+ const char *name,
+ char **var) {
+
+ size_t ident;
+ char *v;
+
+ ident = strlen(name) + 1; /* name + "=" */
+
+ if (len < ident)
+ return 0;
+
+ if (memcmp(data, name, ident - 1) != 0)
+ return 0;
+
+ if (((const char*) data)[ident - 1] != '=')
+ return 0;
+
+ v = strndup((const char*)data + ident, len - ident);
+ if (!v)
+ return log_oom();
+
+ free_and_replace(*var, v);
+ return 1;
+}
+
+static int print_field(FILE* file, sd_journal *j) {
+ const void *d;
+ size_t l;
+
+ assert(file);
+ assert(j);
+
+ assert(arg_field);
+
+ /* A (user-specified) field may appear more than once for a given entry.
+ * We will print all of the occurrences.
+ * This is different below for fields that systemd-coredump uses,
+ * because they cannot meaningfully appear more than once.
+ */
+ SD_JOURNAL_FOREACH_DATA(j, d, l) {
+ _cleanup_free_ char *value = NULL;
+ int r;
+
+ r = retrieve(d, l, arg_field, &value);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ fprintf(file, "%s\n", value);
+ }
+
+ return 0;
+}
+
+#define RETRIEVE(d, l, name, arg) \
+ { \
+ int _r = retrieve(d, l, name, &arg); \
+ if (_r < 0) \
+ return _r; \
+ if (_r > 0) \
+ continue; \
+ }
+
+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 resolve_filename(const char *root, char **p) {
+ char *resolved = NULL;
+ int r;
+
+ if (!*p)
+ return 0;
+
+ r = chase_symlinks(*p, root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to resolve \"%s%s\": %m", strempty(root), *p);
+
+ free_and_replace(*p, resolved);
+
+ /* chase_symlinks() with flag CHASE_NONEXISTENT will return 0 if the file doesn't exist and 1 if it does.
+ * Return that to the caller
+ */
+ return r;
+}
+
+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 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);
+ RETRIEVE(d, l, "COREDUMP_PID", pid);
+ RETRIEVE(d, l, "COREDUMP_UID", uid);
+ RETRIEVE(d, l, "COREDUMP_GID", gid);
+ RETRIEVE(d, l, "COREDUMP_SIGNAL", sgnl);
+ RETRIEVE(d, l, "COREDUMP_EXE", exe);
+ RETRIEVE(d, l, "COREDUMP_COMM", comm);
+ RETRIEVE(d, l, "COREDUMP_CMDLINE", cmdline);
+ RETRIEVE(d, l, "COREDUMP_FILENAME", filename);
+ RETRIEVE(d, l, "COREDUMP_TRUNCATED", truncated);
+ RETRIEVE(d, l, "COREDUMP", coredump);
+ }
+
+ 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, &ts);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get realtime timestamp: %m");
+
+ normal_coredump = streq_ptr(mid, SD_MESSAGE_COREDUMP_STR);
+
+ if (filename) {
+ r = resolve_filename(arg_root, &filename);
+ if (r < 0)
+ return r;
+
+ analyze_coredump_file(filename, &present, &color, &size);
+ } else if (coredump)
+ present = "journal";
+ else if (normal_coredump) {
+ present = "none";
+ color = ansi_grey();
+ } else
+ present = NULL;
+
+ if (STRPTR_IN_SET(present, "present", "journal") && truncated && parse_boolean(truncated) > 0)
+ present = "truncated";
+
+ 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;
+}
+
+static int print_info(FILE *file, sd_journal *j, bool need_space) {
+ _cleanup_free_ char
+ *mid = NULL, *pid = NULL, *uid = NULL, *gid = NULL,
+ *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL,
+ *unit = NULL, *user_unit = NULL, *session = NULL,
+ *boot_id = NULL, *machine_id = NULL, *hostname = NULL,
+ *slice = NULL, *cgroup = NULL, *owner_uid = NULL,
+ *message = NULL, *timestamp = NULL, *filename = NULL,
+ *truncated = NULL, *coredump = NULL,
+ *pkgmeta_name = NULL, *pkgmeta_version = NULL, *pkgmeta_json = NULL;
+ const void *d;
+ size_t l;
+ bool normal_coredump;
+ int r;
+
+ assert(file);
+ assert(j);
+
+ (void) sd_journal_set_data_threshold(j, 0);
+
+ SD_JOURNAL_FOREACH_DATA(j, d, l) {
+ RETRIEVE(d, l, "MESSAGE_ID", mid);
+ RETRIEVE(d, l, "COREDUMP_PID", pid);
+ RETRIEVE(d, l, "COREDUMP_UID", uid);
+ RETRIEVE(d, l, "COREDUMP_GID", gid);
+ RETRIEVE(d, l, "COREDUMP_SIGNAL", sgnl);
+ RETRIEVE(d, l, "COREDUMP_EXE", exe);
+ RETRIEVE(d, l, "COREDUMP_COMM", comm);
+ RETRIEVE(d, l, "COREDUMP_CMDLINE", cmdline);
+ RETRIEVE(d, l, "COREDUMP_HOSTNAME", hostname);
+ RETRIEVE(d, l, "COREDUMP_UNIT", unit);
+ RETRIEVE(d, l, "COREDUMP_USER_UNIT", user_unit);
+ RETRIEVE(d, l, "COREDUMP_SESSION", session);
+ RETRIEVE(d, l, "COREDUMP_OWNER_UID", owner_uid);
+ RETRIEVE(d, l, "COREDUMP_SLICE", slice);
+ RETRIEVE(d, l, "COREDUMP_CGROUP", cgroup);
+ RETRIEVE(d, l, "COREDUMP_TIMESTAMP", timestamp);
+ RETRIEVE(d, l, "COREDUMP_FILENAME", filename);
+ RETRIEVE(d, l, "COREDUMP_TRUNCATED", truncated);
+ RETRIEVE(d, l, "COREDUMP", coredump);
+ RETRIEVE(d, l, "COREDUMP_PACKAGE_NAME", pkgmeta_name);
+ RETRIEVE(d, l, "COREDUMP_PACKAGE_VERSION", pkgmeta_version);
+ RETRIEVE(d, l, "COREDUMP_PACKAGE_JSON", pkgmeta_json);
+ RETRIEVE(d, l, "_BOOT_ID", boot_id);
+ RETRIEVE(d, l, "_MACHINE_ID", machine_id);
+ RETRIEVE(d, l, "MESSAGE", message);
+ }
+
+ if (need_space)
+ fputs("\n", file);
+
+ normal_coredump = streq_ptr(mid, SD_MESSAGE_COREDUMP_STR);
+
+ if (comm)
+ fprintf(file,
+ " PID: %s%s%s (%s)\n",
+ ansi_highlight(), strna(pid), ansi_normal(), comm);
+ else
+ fprintf(file,
+ " PID: %s%s%s\n",
+ ansi_highlight(), strna(pid), ansi_normal());
+
+ if (uid) {
+ uid_t n;
+
+ if (parse_uid(uid, &n) >= 0) {
+ _cleanup_free_ char *u = NULL;
+
+ u = uid_to_name(n);
+ fprintf(file,
+ " UID: %s (%s)\n",
+ uid, u);
+ } else {
+ fprintf(file,
+ " UID: %s\n",
+ uid);
+ }
+ }
+
+ if (gid) {
+ gid_t n;
+
+ if (parse_gid(gid, &n) >= 0) {
+ _cleanup_free_ char *g = NULL;
+
+ g = gid_to_name(n);
+ fprintf(file,
+ " GID: %s (%s)\n",
+ gid, g);
+ } else {
+ fprintf(file,
+ " GID: %s\n",
+ gid);
+ }
+ }
+
+ if (sgnl) {
+ int sig;
+ const char *name = normal_coredump ? "Signal" : "Reason";
+
+ if (normal_coredump && safe_atoi(sgnl, &sig) >= 0)
+ fprintf(file, " %s: %s (%s)\n", name, sgnl, signal_to_string(sig));
+ else
+ fprintf(file, " %s: %s\n", name, sgnl);
+ }
+
+ if (timestamp) {
+ usec_t u;
+
+ r = safe_atou64(timestamp, &u);
+ if (r >= 0)
+ fprintf(file, " Timestamp: %s (%s)\n",
+ FORMAT_TIMESTAMP(u), FORMAT_TIMESTAMP_RELATIVE(u));
+
+ else
+ fprintf(file, " Timestamp: %s\n", timestamp);
+ }
+
+ if (cmdline)
+ fprintf(file, " Command Line: %s\n", cmdline);
+ if (exe)
+ fprintf(file, " Executable: %s%s%s\n", ansi_highlight(), exe, ansi_normal());
+ if (cgroup)
+ fprintf(file, " Control Group: %s\n", cgroup);
+ if (unit)
+ fprintf(file, " Unit: %s\n", unit);
+ if (user_unit)
+ fprintf(file, " User Unit: %s\n", user_unit);
+ if (slice)
+ fprintf(file, " Slice: %s\n", slice);
+ if (session)
+ fprintf(file, " Session: %s\n", session);
+ if (owner_uid) {
+ uid_t n;
+
+ if (parse_uid(owner_uid, &n) >= 0) {
+ _cleanup_free_ char *u = NULL;
+
+ u = uid_to_name(n);
+ fprintf(file,
+ " Owner UID: %s (%s)\n",
+ owner_uid, u);
+ } else {
+ fprintf(file,
+ " Owner UID: %s\n",
+ owner_uid);
+ }
+ }
+ if (boot_id)
+ fprintf(file, " Boot ID: %s\n", boot_id);
+ if (machine_id)
+ fprintf(file, " Machine ID: %s\n", machine_id);
+ if (hostname)
+ fprintf(file, " Hostname: %s\n", hostname);
+
+ if (filename) {
+ r = resolve_filename(arg_root, &filename);
+ if (r < 0)
+ return r;
+
+ const char *state = NULL, *color = NULL;
+ uint64_t size = UINT64_MAX;
+
+ 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, " Size on Disk: %s\n", FORMAT_BYTES(size));
+
+ } else if (coredump)
+ fprintf(file, " Storage: journal\n");
+ else
+ fprintf(file, " Storage: none\n");
+
+ if (pkgmeta_name && pkgmeta_version)
+ fprintf(file, " Package: %s/%s\n", pkgmeta_name, pkgmeta_version);
+
+ /* Print out the build-id of the 'main' ELF module, by matching the JSON key
+ * with the 'exe' field. */
+ if (exe && pkgmeta_json) {
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+
+ r = json_parse(pkgmeta_json, 0, &v, NULL, NULL);
+ if (r < 0) {
+ _cleanup_free_ char *esc = cescape(pkgmeta_json);
+ log_warning_errno(r, "json_parse on \"%s\" failed, ignoring: %m", strnull(esc));
+ } else {
+ const char *module_name;
+ JsonVariant *module_json;
+
+ JSON_VARIANT_OBJECT_FOREACH(module_name, module_json, v) {
+ JsonVariant *build_id;
+
+ /* We only print the build-id for the 'main' ELF module */
+ if (!path_equal_filename(module_name, exe))
+ continue;
+
+ build_id = json_variant_by_key(module_json, "buildId");
+ if (build_id)
+ fprintf(file, " build-id: %s\n", json_variant_string(build_id));
+
+ break;
+ }
+ }
+ }
+
+ if (message) {
+ _cleanup_free_ char *m = NULL;
+
+ m = strreplace(message, "\n", "\n ");
+
+ fprintf(file, " Message: %s\n", strstrip(m ?: message));
+ }
+
+ return 0;
+}
+
+static int focus(sd_journal *j) {
+ int r;
+
+ r = sd_journal_seek_tail(j);
+ if (r == 0)
+ r = sd_journal_previous(j);
+ if (r < 0)
+ return log_error_errno(r, "Failed to search journal: %m");
+ if (r == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(ESRCH),
+ "No match found.");
+ return r;
+}
+
+static int print_entry(
+ sd_journal *j,
+ size_t n_found,
+ Table *t) {
+
+ assert(j);
+
+ if (t)
+ return print_list(stdout, j, t);
+ else if (arg_field)
+ return print_field(stdout, j);
+ else
+ 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;
+ _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");
+
+ r = acquire_journal(&j, argv + 1);
+ if (r < 0)
+ return r;
+
+ /* 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);
+
+ table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
+ } else
+ pager_open(arg_pager_flags);
+
+ /* "info" without pattern implies "-1" */
+ if ((arg_rows_max == 1 && arg_reverse) || (verb_is_info && argc == 1)) {
+ r = focus(j);
+ if (r < 0)
+ return r;
+
+ 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);
+ else if (arg_until != USEC_INFINITY && arg_reverse)
+ r = sd_journal_seek_realtime_usec(j, arg_until);
+ else if (arg_reverse)
+ r = sd_journal_seek_tail(j);
+ else
+ r = sd_journal_seek_head(j);
+ if (r < 0)
+ return log_error_errno(r, "Failed to seek to date: %m");
+
+ for (;;) {
+ if (!arg_reverse)
+ 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;
+
+ if (arg_until != USEC_INFINITY && !arg_reverse) {
+ usec_t usec;
+
+ r = sd_journal_get_realtime_usec(j, &usec);
+ if (r < 0)
+ return log_error_errno(r, "Failed to determine timestamp: %m");
+ if (usec > arg_until)
+ continue;
+ }
+
+ if (arg_since != USEC_INFINITY && arg_reverse) {
+ usec_t usec;
+
+ r = sd_journal_get_realtime_usec(j, &usec);
+ if (r < 0)
+ return log_error_errno(r, "Failed to determine timestamp: %m");
+ if (usec < arg_since)
+ continue;
+ }
+
+ 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) {
+ if (!arg_quiet)
+ log_notice("No coredumps found.");
+ return -ESRCH;
+ }
+ }
+
+ if (t) {
+ r = table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int save_core(sd_journal *j, FILE *file, char **path, bool *unlink_temp) {
+ const char *data;
+ _cleanup_free_ char *filename = NULL;
+ size_t len;
+ int r, fd;
+ _cleanup_close_ int fdt = -1;
+ char *temp = NULL;
+
+ assert(!(file && path)); /* At most one can be specified */
+ assert(!!path == !!unlink_temp); /* Those must be specified together */
+
+ /* Look for a coredump on disk first. */
+ r = sd_journal_get_data(j, "COREDUMP_FILENAME", (const void**) &data, &len);
+ if (r == 0) {
+ _cleanup_free_ char *resolved = NULL;
+
+ r = retrieve(data, len, "COREDUMP_FILENAME", &filename);
+ if (r < 0)
+ return r;
+ assert(r > 0);
+
+ r = chase_symlinks_and_access(filename, arg_root, CHASE_PREFIX_ROOT, F_OK, &resolved, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Cannot access \"%s%s\": %m", strempty(arg_root), filename);
+
+ free_and_replace(filename, resolved);
+
+ if (path && !ENDSWITH_SET(filename, ".xz", ".lz4", ".zst")) {
+ *path = TAKE_PTR(filename);
+
+ return 0;
+ }
+
+ } else {
+ if (r != -ENOENT)
+ return log_error_errno(r, "Failed to retrieve COREDUMP_FILENAME field: %m");
+ /* Check that we can have a COREDUMP field. We still haven't set a high
+ * data threshold, so we'll get a few kilobytes at most.
+ */
+
+ r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
+ if (r == -ENOENT)
+ return log_error_errno(r, "Coredump entry has no core attached (neither internally in the journal nor externally on disk).");
+ if (r < 0)
+ return log_error_errno(r, "Failed to retrieve COREDUMP field: %m");
+ }
+
+ if (path) {
+ const char *vt;
+
+ /* Create a temporary file to write the uncompressed core to. */
+
+ r = var_tmp_dir(&vt);
+ if (r < 0)
+ return log_error_errno(r, "Failed to acquire temporary directory path: %m");
+
+ temp = path_join(vt, "coredump-XXXXXX");
+ if (!temp)
+ return log_oom();
+
+ fdt = mkostemp_safe(temp);
+ if (fdt < 0)
+ return log_error_errno(fdt, "Failed to create temporary file: %m");
+ log_debug("Created temporary file %s", temp);
+
+ fd = fdt;
+ } else {
+ /* If neither path or file are specified, we will write to stdout. Let's now check
+ * if stdout is connected to a tty. We checked that the file exists, or that the
+ * core might be stored in the journal. In this second case, if we found the entry,
+ * in all likelihood we will be able to access the COREDUMP= field. In either case,
+ * we stop before doing any "real" work, i.e. before starting decompression or
+ * reading from the file or creating temporary files.
+ */
+ if (!file) {
+ if (on_tty())
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTTY),
+ "Refusing to dump core to tty"
+ " (use shell redirection or specify --output).");
+ file = stdout;
+ }
+
+ fd = fileno(file);
+ }
+
+ if (filename) {
+#if HAVE_COMPRESSION
+ _cleanup_close_ int fdf = -1;
+
+ fdf = open(filename, O_RDONLY | O_CLOEXEC);
+ if (fdf < 0) {
+ r = log_error_errno(errno, "Failed to open %s: %m", filename);
+ goto error;
+ }
+
+ r = decompress_stream(filename, fdf, fd, -1);
+ if (r < 0) {
+ log_error_errno(r, "Failed to decompress %s: %m", filename);
+ goto error;
+ }
+#else
+ r = log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Cannot decompress file. Compiled without compression support.");
+ goto error;
+#endif
+ } else {
+ ssize_t sz;
+
+ /* We want full data, nothing truncated. */
+ sd_journal_set_data_threshold(j, 0);
+
+ r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
+ if (r < 0)
+ return log_error_errno(r, "Failed to retrieve COREDUMP field: %m");
+
+ assert(len >= 9);
+ data += 9;
+ len -= 9;
+
+ sz = write(fd, data, len);
+ if (sz < 0) {
+ r = log_error_errno(errno, "Failed to write output: %m");
+ goto error;
+ }
+ if (sz != (ssize_t) len) {
+ log_error("Short write to output.");
+ r = -EIO;
+ goto error;
+ }
+ }
+
+ if (temp) {
+ *path = temp;
+ *unlink_temp = true;
+ }
+ return 0;
+
+error:
+ if (temp) {
+ (void) unlink(temp);
+ log_debug("Removed temporary file %s", temp);
+ }
+ return r;
+}
+
+static int dump_core(int argc, char **argv, void *userdata) {
+ _cleanup_(sd_journal_closep) sd_journal *j = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ if (arg_field)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Option --field/-F only makes sense with list");
+
+ r = acquire_journal(&j, argv + 1);
+ if (r < 0)
+ return r;
+
+ r = focus(j);
+ if (r < 0)
+ return r;
+
+ if (arg_output) {
+ f = fopen(arg_output, "we");
+ if (!f)
+ return log_error_errno(errno, "Failed to open \"%s\" for writing: %m", arg_output);
+ }
+
+ print_info(f ? stdout : stderr, j, false);
+
+ r = save_core(j, f, NULL, NULL);
+ if (r < 0)
+ return r;
+
+ r = sd_journal_previous(j);
+ if (r > 0 && !arg_quiet)
+ log_notice("More than one entry matches, ignoring rest.");
+
+ return 0;
+}
+
+static void sigterm_handler(int signal, siginfo_t *info, void *ucontext) {
+ assert(signal == SIGTERM);
+ assert(info);
+
+ /* If the sender is not us, propagate the signal to all processes in
+ * the same process group */
+ if (pid_is_valid(info->si_pid) && info->si_pid != getpid_cached())
+ (void) kill(0, signal);
+}
+
+static int run_debug(int argc, char **argv, void *userdata) {
+ _cleanup_(sd_journal_closep) sd_journal *j = NULL;
+ _cleanup_free_ char *exe = NULL, *path = NULL;
+ _cleanup_strv_free_ char **debugger_call = NULL;
+ struct sigaction sa = {
+ .sa_sigaction = sigterm_handler,
+ .sa_flags = SA_SIGINFO,
+ };
+ bool unlink_path = false;
+ const char *data, *fork_name;
+ size_t len;
+ pid_t pid;
+ int r;
+
+ if (!arg_debugger) {
+ char *env_debugger;
+
+ env_debugger = getenv("SYSTEMD_DEBUGGER");
+ if (env_debugger)
+ arg_debugger = env_debugger;
+ else
+ arg_debugger = "gdb";
+ }
+
+ r = strv_extend(&debugger_call, arg_debugger);
+ if (r < 0)
+ return log_oom();
+
+ r = strv_extend_strv(&debugger_call, arg_debugger_args, false);
+ if (r < 0)
+ return log_oom();
+
+ if (arg_field)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Option --field/-F only makes sense with list");
+
+ r = acquire_journal(&j, argv + 1);
+ if (r < 0)
+ return r;
+
+ r = focus(j);
+ if (r < 0)
+ return r;
+
+ if (!arg_quiet) {
+ print_info(stdout, j, false);
+ fputs("\n", stdout);
+ }
+
+ r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len);
+ if (r < 0)
+ return log_error_errno(r, "Failed to retrieve COREDUMP_EXE field: %m");
+
+ assert(len > STRLEN("COREDUMP_EXE="));
+ data += STRLEN("COREDUMP_EXE=");
+ len -= STRLEN("COREDUMP_EXE=");
+
+ exe = strndup(data, len);
+ if (!exe)
+ return log_oom();
+
+ if (endswith(exe, " (deleted)"))
+ return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
+ "Binary already deleted.");
+
+ if (!path_is_absolute(exe))
+ return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
+ "Binary is not an absolute path.");
+
+ r = resolve_filename(arg_root, &exe);
+ if (r < 0)
+ return r;
+
+ r = save_core(j, NULL, &path, &unlink_path);
+ if (r < 0)
+ return r;
+
+ r = strv_extend_strv(&debugger_call, STRV_MAKE(exe, "-c", path), false);
+ if (r < 0)
+ return log_oom();
+
+ if (arg_root) {
+ if (streq(arg_debugger, "gdb")) {
+ const char *sysroot_cmd;
+ sysroot_cmd = strjoina("set sysroot ", arg_root);
+
+ r = strv_extend_strv(&debugger_call, STRV_MAKE("-iex", sysroot_cmd), false);
+ if (r < 0)
+ return log_oom();
+ } else if (streq(arg_debugger, "lldb")) {
+ const char *sysroot_cmd;
+ sysroot_cmd = strjoina("platform select --sysroot ", arg_root, " host");
+
+ r = strv_extend_strv(&debugger_call, STRV_MAKE("-O", sysroot_cmd), false);
+ if (r < 0)
+ return log_oom();
+ }
+ }
+
+ /* Don't interfere with gdb and its handling of SIGINT. */
+ (void) ignore_signals(SIGINT);
+ (void) sigaction(SIGTERM, &sa, NULL);
+
+ fork_name = strjoina("(", debugger_call[0], ")");
+
+ r = safe_fork(fork_name, FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_CLOSE_ALL_FDS|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG|FORK_FLUSH_STDIO, &pid);
+ if (r < 0)
+ goto finish;
+ if (r == 0) {
+ execvp(debugger_call[0], debugger_call);
+ log_open();
+ log_error_errno(errno, "Failed to invoke %s: %m", debugger_call[0]);
+ _exit(EXIT_FAILURE);
+ }
+
+ r = wait_for_terminate_and_check(debugger_call[0], pid, WAIT_LOG_ABNORMAL);
+
+finish:
+ (void) default_signals(SIGINT, SIGTERM);
+
+ if (unlink_path) {
+ log_debug("Removed temporary file %s", path);
+ (void) unlink(path);
+ }
+
+ return r;
+}
+
+static int check_units_active(void) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ int c = 0, r;
+ const char *id, *state, *substate;
+
+ if (arg_quiet)
+ return false;
+
+ r = sd_bus_default_system(&bus);
+ if (r == -ENOENT) {
+ log_debug("D-Bus is not running, skipping active unit check");
+ return 0;
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to acquire bus: %m");
+
+ r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "ListUnitsByPatterns");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append_strv(m, NULL);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append_strv(m, STRV_MAKE("systemd-coredump@*.service"));
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_call(bus, m, SHORT_BUS_CALL_TIMEOUT_USEC, &error, &reply);
+ if (r < 0)
+ return log_error_errno(r, "Failed to check if any systemd-coredump@.service units are running: %s",
+ bus_error_message(&error, r));
+
+ r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ while ((r = sd_bus_message_read(
+ reply, "(ssssssouso)",
+ &id, NULL, NULL, &state, &substate,
+ NULL, NULL, NULL, NULL, NULL)) > 0) {
+ bool found = !STR_IN_SET(state, "inactive", "dead", "failed");
+ log_debug("Unit %s is %s/%s, %scounting it.", id, state, substate, found ? "" : "not ");
+ c += found;
+ }
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ r = sd_bus_message_exit_container(reply);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ return c;
+}
+
+static int coredumpctl_main(int argc, char *argv[]) {
+
+ static const Verb verbs[] = {
+ { "list", VERB_ANY, VERB_ANY, VERB_DEFAULT, dump_list },
+ { "info", VERB_ANY, VERB_ANY, 0, dump_list },
+ { "dump", VERB_ANY, VERB_ANY, 0, dump_core },
+ { "debug", VERB_ANY, VERB_ANY, 0, run_debug },
+ { "gdb", VERB_ANY, VERB_ANY, 0, run_debug },
+ { "help", VERB_ANY, 1, 0, verb_help },
+ {}
+ };
+
+ return dispatch_verb(argc, argv, verbs, NULL);
+}
+
+static int run(int argc, char *argv[]) {
+ _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
+ _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
+ int r, units_active;
+
+ setlocale(LC_ALL, "");
+ log_setup();
+
+ /* The journal merging logic potentially needs a lot of fds. */
+ (void) rlimit_nofile_bump(HIGH_RLIMIT_NOFILE);
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
+
+ sigbus_install();
+
+ units_active = check_units_active(); /* error is treated the same as 0 */
+
+ if (arg_image) {
+ assert(!arg_root);
+
+ r = mount_image_privately_interactively(
+ arg_image,
+ DISSECT_IMAGE_GENERIC_ROOT |
+ DISSECT_IMAGE_REQUIRE_ROOT |
+ DISSECT_IMAGE_RELAX_VAR_CHECK |
+ DISSECT_IMAGE_VALIDATE_OS,
+ &mounted_dir,
+ &loop_device);
+ if (r < 0)
+ return r;
+
+ arg_root = strdup(mounted_dir);
+ if (!arg_root)
+ return log_oom();
+ }
+
+ r = coredumpctl_main(argc, argv);
+
+ if (units_active > 0)
+ printf("%s-- Notice: %d systemd-coredump@.service %s, output may be incomplete.%s\n",
+ ansi_highlight_red(),
+ units_active, units_active == 1 ? "unit is running" : "units are running",
+ ansi_normal());
+
+ return r;
+}
+
+DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);