1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2012 Zbigniew Jędrzejewski-Szmek
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
29 #include "systemd/sd-journal.h"
35 #include "path-util.h"
38 #include "journal-internal.h"
48 } arg_action = ACTION_LIST;
49 static const char* arg_field = NULL;
50 static int arg_no_pager = false;
51 static int arg_no_legend = false;
52 static int arg_one = false;
54 static FILE* output = NULL;
56 static Set *new_matches(void) {
67 tmp = strdup("MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1");
74 r = set_consume(set, tmp);
76 log_error_errno(r, "failed to add to set: %m");
84 static int add_match(Set *set, const char *match) {
89 _cleanup_free_ char *p = NULL;
91 if (strchr(match, '='))
93 else if (strchr(match, '/')) {
94 p = path_make_absolute_cwd(match);
99 prefix = "COREDUMP_EXE=";
101 else if (safe_atou(match, &pid) == 0)
102 prefix = "COREDUMP_PID=";
104 prefix = "COREDUMP_COMM=";
106 pattern = strjoin(prefix, match, NULL);
110 log_debug("Adding pattern: %s", pattern);
111 r = set_consume(set, pattern);
113 log_error_errno(r, "Failed to add pattern: %m");
119 return log_error_errno(r, "Failed to add match: %m");
122 static void help(void) {
123 printf("%s [OPTIONS...]\n\n"
124 "List or retrieve coredumps from the journal.\n\n"
126 " -h --help Show this help\n"
127 " --version Print version string\n"
128 " --no-pager Do not pipe output into a pager\n"
129 " --no-legend Do not print the column headers.\n"
130 " -1 Show information about most recent entry only\n"
131 " -F --field=FIELD List all values a certain field takes\n"
132 " -o --output=FILE Write output to FILE\n\n"
135 " list [MATCHES...] List available coredumps (default)\n"
136 " info [MATCHES...] Show detailed information about one or more coredumps\n"
137 " dump [MATCHES...] Print first matching coredump to stdout\n"
138 " gdb [MATCHES...] Start gdb for the first matching coredump\n"
139 , program_invocation_short_name);
142 static int parse_argv(int argc, char *argv[], Set *matches) {
151 static const struct option options[] = {
152 { "help", no_argument, NULL, 'h' },
153 { "version" , no_argument, NULL, ARG_VERSION },
154 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
155 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
156 { "output", required_argument, NULL, 'o' },
157 { "field", required_argument, NULL, 'F' },
164 while ((c = getopt_long(argc, argv, "ho:F:1", options, NULL)) >= 0)
168 arg_action = ACTION_NONE;
173 arg_action = ACTION_NONE;
174 puts(PACKAGE_STRING);
175 puts(SYSTEMD_FEATURES);
183 arg_no_legend = true;
188 log_error("cannot set output more than once");
192 output = fopen(optarg, "we");
194 log_error_errno(errno, "writing to '%s': %m", optarg);
202 log_error("cannot use --field/-F more than once");
216 assert_not_reached("Unhandled option");
220 const char *cmd = argv[optind++];
221 if (streq(cmd, "list"))
222 arg_action = ACTION_LIST;
223 else if (streq(cmd, "dump"))
224 arg_action = ACTION_DUMP;
225 else if (streq(cmd, "gdb"))
226 arg_action = ACTION_GDB;
227 else if (streq(cmd, "info"))
228 arg_action = ACTION_INFO;
230 log_error("Unknown action '%s'", cmd);
235 if (arg_field && arg_action != ACTION_LIST) {
236 log_error("Option --field/-F only makes sense with list");
240 while (optind < argc) {
241 r = add_match(matches, argv[optind]);
250 static int retrieve(const void *data,
258 ident = strlen(name) + 1; /* name + "=" */
263 if (memcmp(data, name, ident - 1) != 0)
266 if (((const char*) data)[ident - 1] != '=')
269 v = strndup((const char*)data + ident, len - ident);
279 static void print_field(FILE* file, sd_journal *j) {
280 _cleanup_free_ char *value = NULL;
289 SD_JOURNAL_FOREACH_DATA(j, d, l)
290 retrieve(d, l, arg_field, &value);
293 fprintf(file, "%s\n", value);
296 static int print_list(FILE* file, sd_journal *j, int had_legend) {
298 *pid = NULL, *uid = NULL, *gid = NULL,
299 *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL,
304 char buf[FORMAT_TIMESTAMP_MAX];
311 SD_JOURNAL_FOREACH_DATA(j, d, l) {
312 retrieve(d, l, "COREDUMP_PID", &pid);
313 retrieve(d, l, "COREDUMP_UID", &uid);
314 retrieve(d, l, "COREDUMP_GID", &gid);
315 retrieve(d, l, "COREDUMP_SIGNAL", &sgnl);
316 retrieve(d, l, "COREDUMP_EXE", &exe);
317 retrieve(d, l, "COREDUMP_COMM", &comm);
318 retrieve(d, l, "COREDUMP_CMDLINE", &cmdline);
319 retrieve(d, l, "COREDUMP_FILENAME", &filename);
322 if (!pid && !uid && !gid && !sgnl && !exe && !comm && !cmdline && !filename) {
323 log_warning("Empty coredump log entry");
327 r = sd_journal_get_realtime_usec(j, &t);
329 return log_error_errno(r, "Failed to get realtime timestamp: %m");
331 format_timestamp(buf, sizeof(buf), t);
332 present = filename && access(filename, F_OK) == 0;
334 if (!had_legend && !arg_no_legend)
335 fprintf(file, "%-*s %*s %*s %*s %*s %*s %s\n",
336 FORMAT_TIMESTAMP_WIDTH, "TIME",
344 fprintf(file, "%-*s %*s %*s %*s %*s %*s %s\n",
345 FORMAT_TIMESTAMP_WIDTH, buf,
350 1, present ? "*" : "",
351 strna(exe ?: (comm ?: cmdline)));
356 static int print_info(FILE *file, sd_journal *j, bool need_space) {
358 *pid = NULL, *uid = NULL, *gid = NULL,
359 *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL,
360 *unit = NULL, *user_unit = NULL, *session = NULL,
361 *boot_id = NULL, *machine_id = NULL, *hostname = NULL,
362 *slice = NULL, *cgroup = NULL, *owner_uid = NULL,
363 *message = NULL, *timestamp = NULL, *filename = NULL;
371 SD_JOURNAL_FOREACH_DATA(j, d, l) {
372 retrieve(d, l, "COREDUMP_PID", &pid);
373 retrieve(d, l, "COREDUMP_UID", &uid);
374 retrieve(d, l, "COREDUMP_GID", &gid);
375 retrieve(d, l, "COREDUMP_SIGNAL", &sgnl);
376 retrieve(d, l, "COREDUMP_EXE", &exe);
377 retrieve(d, l, "COREDUMP_COMM", &comm);
378 retrieve(d, l, "COREDUMP_CMDLINE", &cmdline);
379 retrieve(d, l, "COREDUMP_UNIT", &unit);
380 retrieve(d, l, "COREDUMP_USER_UNIT", &user_unit);
381 retrieve(d, l, "COREDUMP_SESSION", &session);
382 retrieve(d, l, "COREDUMP_OWNER_UID", &owner_uid);
383 retrieve(d, l, "COREDUMP_SLICE", &slice);
384 retrieve(d, l, "COREDUMP_CGROUP", &cgroup);
385 retrieve(d, l, "COREDUMP_TIMESTAMP", ×tamp);
386 retrieve(d, l, "COREDUMP_FILENAME", &filename);
387 retrieve(d, l, "_BOOT_ID", &boot_id);
388 retrieve(d, l, "_MACHINE_ID", &machine_id);
389 retrieve(d, l, "_HOSTNAME", &hostname);
390 retrieve(d, l, "MESSAGE", &message);
398 " PID: %s%s%s (%s)\n",
399 ansi_highlight(), strna(pid), ansi_highlight_off(), comm);
403 ansi_highlight(), strna(pid), ansi_highlight_off());
408 if (parse_uid(uid, &n) >= 0) {
409 _cleanup_free_ char *u = NULL;
425 if (parse_gid(gid, &n) >= 0) {
426 _cleanup_free_ char *g = NULL;
442 if (safe_atoi(sgnl, &sig) >= 0)
443 fprintf(file, " Signal: %s (%s)\n", sgnl, signal_to_string(sig));
445 fprintf(file, " Signal: %s\n", sgnl);
451 r = safe_atou64(timestamp, &u);
453 char absolute[FORMAT_TIMESTAMP_MAX], relative[FORMAT_TIMESPAN_MAX];
456 " Timestamp: %s (%s)\n",
457 format_timestamp(absolute, sizeof(absolute), u),
458 format_timestamp_relative(relative, sizeof(relative), u));
461 fprintf(file, " Timestamp: %s\n", timestamp);
465 fprintf(file, " Command Line: %s\n", cmdline);
467 fprintf(file, " Executable: %s%s%s\n", ansi_highlight(), exe, ansi_highlight_off());
469 fprintf(file, " Control Group: %s\n", cgroup);
471 fprintf(file, " Unit: %s\n", unit);
473 fprintf(file, " User Unit: %s\n", unit);
475 fprintf(file, " Slice: %s\n", slice);
477 fprintf(file, " Session: %s\n", session);
481 if (parse_uid(owner_uid, &n) >= 0) {
482 _cleanup_free_ char *u = NULL;
486 " Owner UID: %s (%s)\n",
495 fprintf(file, " Boot ID: %s\n", boot_id);
497 fprintf(file, " Machine ID: %s\n", machine_id);
499 fprintf(file, " Hostname: %s\n", hostname);
501 if (filename && access(filename, F_OK) == 0)
502 fprintf(file, " Coredump: %s\n", filename);
505 _cleanup_free_ char *m = NULL;
507 m = strreplace(message, "\n", "\n ");
509 fprintf(file, " Message: %s\n", strstrip(m ?: message));
515 static int focus(sd_journal *j) {
518 r = sd_journal_seek_tail(j);
520 r = sd_journal_previous(j);
522 return log_error_errno(r, "Failed to search journal: %m");
524 log_error("No match found.");
530 static void print_entry(sd_journal *j, unsigned n_found) {
533 if (arg_action == ACTION_INFO)
534 print_info(stdout, j, n_found);
536 print_field(stdout, j);
538 print_list(stdout, j, n_found);
541 static int dump_list(sd_journal *j) {
542 unsigned n_found = 0;
547 /* The coredumps are likely to compressed, and for just
548 * listing them we don't need to decompress them, so let's
549 * pick a fairly low data threshold here */
550 sd_journal_set_data_threshold(j, 4096);
559 SD_JOURNAL_FOREACH(j)
560 print_entry(j, n_found++);
562 if (!arg_field && n_found <= 0) {
563 log_notice("No coredumps found.");
571 static int save_core(sd_journal *j, int fd, char **path, bool *unlink_temp) {
573 _cleanup_free_ char *filename = NULL;
577 assert((fd >= 0) != !!path);
578 assert(!!path == !!unlink_temp);
580 /* Prefer uncompressed file to journal (probably cached) to
581 * compressed file (probably uncached). */
582 r = sd_journal_get_data(j, "COREDUMP_FILENAME", (const void**) &data, &len);
583 if (r < 0 && r != -ENOENT)
584 log_warning_errno(r, "Failed to retrieve COREDUMP_FILENAME: %m");
586 retrieve(data, len, "COREDUMP_FILENAME", &filename);
588 if (filename && access(filename, R_OK) < 0) {
589 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
590 "File %s is not readable: %m", filename);
595 if (filename && !endswith(filename, ".xz") && !endswith(filename, ".lz4")) {
603 _cleanup_close_ int fdt = -1;
607 temp = strdup("/var/tmp/coredump-XXXXXX");
611 fdt = mkostemp_safe(temp, O_WRONLY|O_CLOEXEC);
613 log_error_errno(errno, "Failed to create temporary file: %m");
616 log_debug("Created temporary file %s", temp);
621 r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
629 sz = write(fdt, data, len);
631 log_error_errno(errno, "Failed to write temporary file: %m");
635 if (sz != (ssize_t) len) {
636 log_error("Short write to temporary file.");
640 } else if (filename) {
641 #if defined(HAVE_XZ) || defined(HAVE_LZ4)
642 _cleanup_close_ int fdf;
644 fdf = open(filename, O_RDONLY | O_CLOEXEC);
646 log_error_errno(errno, "Failed to open %s: %m", filename);
651 r = decompress_stream(filename, fdf, fd, -1);
653 log_error_errno(r, "Failed to decompress %s: %m", filename);
657 log_error("Cannot decompress file. Compiled without compression support.");
663 log_error("Cannot retrieve coredump from journal nor disk.");
665 log_error_errno(r, "Failed to retrieve COREDUMP field: %m");
679 log_debug("Removed temporary file %s", temp);
685 static int dump_core(sd_journal* j) {
694 print_info(output ? stdout : stderr, j, false);
696 if (on_tty() && !output) {
697 log_error("Refusing to dump core to tty.");
701 r = save_core(j, output ? fileno(output) : STDOUT_FILENO, NULL, NULL);
703 return log_error_errno(r, "Coredump retrieval failed: %m");
705 r = sd_journal_previous(j);
707 log_warning("More than one entry matches, ignoring rest.");
712 static int run_gdb(sd_journal *j) {
713 _cleanup_free_ char *exe = NULL, *path = NULL;
714 bool unlink_path = false;
727 print_info(stdout, j, false);
730 r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len);
732 return log_error_errno(r, "Failed to retrieve COREDUMP_EXE field: %m");
734 assert(len > strlen("COREDUMP_EXE="));
735 data += strlen("COREDUMP_EXE=");
736 len -= strlen("COREDUMP_EXE=");
738 exe = strndup(data, len);
742 if (endswith(exe, " (deleted)")) {
743 log_error("Binary already deleted.");
747 if (!path_is_absolute(exe)) {
748 log_error("Binary is not an absolute path.");
752 r = save_core(j, -1, &path, &unlink_path);
754 return log_error_errno(r, "Failed to retrieve core: %m");
758 log_error_errno(errno, "Failed to fork(): %m");
763 execlp("gdb", "gdb", exe, path, NULL);
765 log_error_errno(errno, "Failed to invoke gdb: %m");
769 r = wait_for_terminate(pid, &st);
771 log_error_errno(errno, "Failed to wait for gdb: %m");
775 r = st.si_code == CLD_EXITED ? st.si_status : 255;
779 log_debug("Removed temporary file %s", path);
786 int main(int argc, char *argv[]) {
787 _cleanup_journal_close_ sd_journal*j = NULL;
791 _cleanup_set_free_free_ Set *matches = NULL;
793 setlocale(LC_ALL, "");
794 log_parse_environment();
797 matches = new_matches();
803 r = parse_argv(argc, argv, matches);
807 if (arg_action == ACTION_NONE)
810 r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
812 log_error_errno(r, "Failed to open journal: %m");
816 /* We want full data, nothing truncated. */
817 sd_journal_set_data_threshold(j, 0);
819 SET_FOREACH(match, matches, it) {
820 r = sd_journal_add_match(j, match, strlen(match));
822 log_error_errno(r, "Failed to add match '%s': %m",
828 if (_unlikely_(log_get_max_level() >= LOG_PRI(LOG_DEBUG))) {
829 _cleanup_free_ char *filter;
831 filter = journal_make_match_string(j);
832 log_debug("Journal filter: %s", filter);
854 assert_not_reached("Shouldn't be here");
863 return r >= 0 ? r : EXIT_FAILURE;