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("failed to add to set: %s", strerror(-r));
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("Failed to add pattern: %s", strerror(-r));
119 log_error("Failed to add match: %s", strerror(-r));
123 static void help(void) {
124 printf("%s [OPTIONS...]\n\n"
125 "List or retrieve coredumps from the journal.\n\n"
127 " -h --help Show this help\n"
128 " --version Print version string\n"
129 " --no-pager Do not pipe output into a pager\n"
130 " --no-legend Do not print the column headers.\n"
131 " -1 Show information about most recent entry only\n"
132 " -F --field=FIELD List all values a certain field takes\n"
133 " -o --output=FILE Write output to FILE\n\n"
136 " list [MATCHES...] List available coredumps (default)\n"
137 " info [MATCHES...] Show detailed information about one or more coredumps\n"
138 " dump [MATCHES...] Print first matching coredump to stdout\n"
139 " gdb [MATCHES...] Start gdb for the first matching coredump\n"
140 , program_invocation_short_name);
143 static int parse_argv(int argc, char *argv[], Set *matches) {
152 static const struct option options[] = {
153 { "help", no_argument, NULL, 'h' },
154 { "version" , no_argument, NULL, ARG_VERSION },
155 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
156 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
157 { "output", required_argument, NULL, 'o' },
158 { "field", required_argument, NULL, 'F' },
165 while ((c = getopt_long(argc, argv, "ho:F:1", options, NULL)) >= 0)
169 arg_action = ACTION_NONE;
174 arg_action = ACTION_NONE;
175 puts(PACKAGE_STRING);
176 puts(SYSTEMD_FEATURES);
184 arg_no_legend = true;
189 log_error("cannot set output more than once");
193 output = fopen(optarg, "we");
195 log_error("writing to '%s': %m", optarg);
203 log_error("cannot use --field/-F more than once");
217 assert_not_reached("Unhandled option");
221 const char *cmd = argv[optind++];
222 if (streq(cmd, "list"))
223 arg_action = ACTION_LIST;
224 else if (streq(cmd, "dump"))
225 arg_action = ACTION_DUMP;
226 else if (streq(cmd, "gdb"))
227 arg_action = ACTION_GDB;
228 else if (streq(cmd, "info"))
229 arg_action = ACTION_INFO;
231 log_error("Unknown action '%s'", cmd);
236 if (arg_field && arg_action != ACTION_LIST) {
237 log_error("Option --field/-F only makes sense with list");
241 while (optind < argc) {
242 r = add_match(matches, argv[optind]);
251 static int retrieve(const void *data,
259 ident = strlen(name) + 1; /* name + "=" */
264 if (memcmp(data, name, ident - 1) != 0)
267 if (((const char*) data)[ident - 1] != '=')
270 v = strndup((const char*)data + ident, len - ident);
280 static void print_field(FILE* file, sd_journal *j) {
281 _cleanup_free_ char *value = NULL;
290 SD_JOURNAL_FOREACH_DATA(j, d, l)
291 retrieve(d, l, arg_field, &value);
294 fprintf(file, "%s\n", value);
297 static int print_list(FILE* file, sd_journal *j, int had_legend) {
299 *pid = NULL, *uid = NULL, *gid = NULL,
300 *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL,
305 char buf[FORMAT_TIMESTAMP_MAX];
312 SD_JOURNAL_FOREACH_DATA(j, d, l) {
313 retrieve(d, l, "COREDUMP_PID", &pid);
314 retrieve(d, l, "COREDUMP_UID", &uid);
315 retrieve(d, l, "COREDUMP_GID", &gid);
316 retrieve(d, l, "COREDUMP_SIGNAL", &sgnl);
317 retrieve(d, l, "COREDUMP_EXE", &exe);
318 retrieve(d, l, "COREDUMP_COMM", &comm);
319 retrieve(d, l, "COREDUMP_CMDLINE", &cmdline);
320 retrieve(d, l, "COREDUMP_FILENAME", &filename);
323 if (!pid && !uid && !gid && !sgnl && !exe && !comm && !cmdline && !filename) {
324 log_warning("Empty coredump log entry");
328 r = sd_journal_get_realtime_usec(j, &t);
330 log_error("Failed to get realtime timestamp: %s", strerror(-r));
334 format_timestamp(buf, sizeof(buf), t);
335 present = filename && access(filename, F_OK) == 0;
337 if (!had_legend && !arg_no_legend)
338 fprintf(file, "%-*s %*s %*s %*s %*s %*s %s\n",
339 FORMAT_TIMESTAMP_WIDTH, "TIME",
347 fprintf(file, "%-*s %*s %*s %*s %*s %*s %s\n",
348 FORMAT_TIMESTAMP_WIDTH, buf,
353 1, present ? "*" : "",
354 strna(exe ?: (comm ?: cmdline)));
359 static int print_info(FILE *file, sd_journal *j, bool need_space) {
361 *pid = NULL, *uid = NULL, *gid = NULL,
362 *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL,
363 *unit = NULL, *user_unit = NULL, *session = NULL,
364 *boot_id = NULL, *machine_id = NULL, *hostname = NULL,
365 *slice = NULL, *cgroup = NULL, *owner_uid = NULL,
366 *message = NULL, *timestamp = NULL, *filename = NULL;
374 SD_JOURNAL_FOREACH_DATA(j, d, l) {
375 retrieve(d, l, "COREDUMP_PID", &pid);
376 retrieve(d, l, "COREDUMP_UID", &uid);
377 retrieve(d, l, "COREDUMP_GID", &gid);
378 retrieve(d, l, "COREDUMP_SIGNAL", &sgnl);
379 retrieve(d, l, "COREDUMP_EXE", &exe);
380 retrieve(d, l, "COREDUMP_COMM", &comm);
381 retrieve(d, l, "COREDUMP_CMDLINE", &cmdline);
382 retrieve(d, l, "COREDUMP_UNIT", &unit);
383 retrieve(d, l, "COREDUMP_USER_UNIT", &user_unit);
384 retrieve(d, l, "COREDUMP_SESSION", &session);
385 retrieve(d, l, "COREDUMP_OWNER_UID", &owner_uid);
386 retrieve(d, l, "COREDUMP_SLICE", &slice);
387 retrieve(d, l, "COREDUMP_CGROUP", &cgroup);
388 retrieve(d, l, "COREDUMP_TIMESTAMP", ×tamp);
389 retrieve(d, l, "COREDUMP_FILENAME", &filename);
390 retrieve(d, l, "_BOOT_ID", &boot_id);
391 retrieve(d, l, "_MACHINE_ID", &machine_id);
392 retrieve(d, l, "_HOSTNAME", &hostname);
393 retrieve(d, l, "MESSAGE", &message);
401 " PID: %s%s%s (%s)\n",
402 ansi_highlight(), strna(pid), ansi_highlight_off(), comm);
406 ansi_highlight(), strna(pid), ansi_highlight_off());
411 if (parse_uid(uid, &n) >= 0) {
412 _cleanup_free_ char *u = NULL;
428 if (parse_gid(gid, &n) >= 0) {
429 _cleanup_free_ char *g = NULL;
445 if (safe_atoi(sgnl, &sig) >= 0)
446 fprintf(file, " Signal: %s (%s)\n", sgnl, signal_to_string(sig));
448 fprintf(file, " Signal: %s\n", sgnl);
454 r = safe_atou64(timestamp, &u);
456 char absolute[FORMAT_TIMESTAMP_MAX], relative[FORMAT_TIMESPAN_MAX];
459 " Timestamp: %s (%s)\n",
460 format_timestamp(absolute, sizeof(absolute), u),
461 format_timestamp_relative(relative, sizeof(relative), u));
464 fprintf(file, " Timestamp: %s\n", timestamp);
468 fprintf(file, " Command Line: %s\n", cmdline);
470 fprintf(file, " Executable: %s%s%s\n", ansi_highlight(), exe, ansi_highlight_off());
472 fprintf(file, " Control Group: %s\n", cgroup);
474 fprintf(file, " Unit: %s\n", unit);
476 fprintf(file, " User Unit: %s\n", unit);
478 fprintf(file, " Slice: %s\n", slice);
480 fprintf(file, " Session: %s\n", session);
484 if (parse_uid(owner_uid, &n) >= 0) {
485 _cleanup_free_ char *u = NULL;
489 " Owner UID: %s (%s)\n",
498 fprintf(file, " Boot ID: %s\n", boot_id);
500 fprintf(file, " Machine ID: %s\n", machine_id);
502 fprintf(file, " Hostname: %s\n", hostname);
504 if (filename && access(filename, F_OK) == 0)
505 fprintf(file, " Coredump: %s\n", filename);
508 _cleanup_free_ char *m = NULL;
510 m = strreplace(message, "\n", "\n ");
512 fprintf(file, " Message: %s\n", strstrip(m ?: message));
518 static int focus(sd_journal *j) {
521 r = sd_journal_seek_tail(j);
523 r = sd_journal_previous(j);
525 log_error("Failed to search journal: %s", strerror(-r));
529 log_error("No match found.");
535 static void print_entry(sd_journal *j, unsigned n_found) {
538 if (arg_action == ACTION_INFO)
539 print_info(stdout, j, n_found);
541 print_field(stdout, j);
543 print_list(stdout, j, n_found);
546 static int dump_list(sd_journal *j) {
547 unsigned n_found = 0;
552 /* The coredumps are likely to compressed, and for just
553 * listing them we don't need to decompress them, so let's
554 * pick a fairly low data threshold here */
555 sd_journal_set_data_threshold(j, 4096);
564 SD_JOURNAL_FOREACH(j)
565 print_entry(j, n_found++);
567 if (!arg_field && n_found <= 0) {
568 log_notice("No coredumps found.");
576 static int save_core(sd_journal *j, int fd, char **path, bool *unlink_temp) {
578 _cleanup_free_ char *filename = NULL;
582 assert((fd >= 0) != !!path);
583 assert(!!path == !!unlink_temp);
585 /* Prefer uncompressed file to journal (probably cached) to
586 * compressed file (probably uncached). */
587 r = sd_journal_get_data(j, "COREDUMP_FILENAME", (const void**) &data, &len);
588 if (r < 0 && r != -ENOENT)
589 log_warning("Failed to retrieve COREDUMP_FILENAME: %s", strerror(-r));
591 retrieve(data, len, "COREDUMP_FILENAME", &filename);
593 if (filename && access(filename, R_OK) < 0) {
594 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
595 "File %s is not readable: %m", filename);
600 if (filename && !endswith(filename, ".xz") && !endswith(filename, ".lz4")) {
608 _cleanup_close_ int fdt = -1;
612 temp = strdup("/var/tmp/coredump-XXXXXX");
616 fdt = mkostemp_safe(temp, O_WRONLY|O_CLOEXEC);
618 log_error("Failed to create temporary file: %m");
621 log_debug("Created temporary file %s", temp);
626 r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
634 sz = write(fdt, data, len);
636 log_error("Failed to write temporary file: %m");
640 if (sz != (ssize_t) len) {
641 log_error("Short write to temporary file.");
645 } else if (filename) {
646 #if defined(HAVE_XZ) || defined(HAVE_LZ4)
647 _cleanup_close_ int fdf;
649 fdf = open(filename, O_RDONLY | O_CLOEXEC);
651 log_error("Failed to open %s: %m", filename);
656 r = decompress_stream(filename, fdf, fd, -1);
658 log_error("Failed to decompress %s: %s", filename, strerror(-r));
662 log_error("Cannot decompress file. Compiled without compression support.");
668 log_error("Cannot retrieve coredump from journal nor disk.");
670 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
684 log_debug("Removed temporary file %s", temp);
690 static int dump_core(sd_journal* j) {
699 print_info(output ? stdout : stderr, j, false);
701 if (on_tty() && !output) {
702 log_error("Refusing to dump core to tty.");
706 r = save_core(j, output ? fileno(output) : STDOUT_FILENO, NULL, NULL);
708 log_error("Coredump retrieval failed: %s", strerror(-r));
712 r = sd_journal_previous(j);
714 log_warning("More than one entry matches, ignoring rest.");
719 static int run_gdb(sd_journal *j) {
720 _cleanup_free_ char *exe = NULL, *path = NULL;
721 bool unlink_path = false;
734 print_info(stdout, j, false);
737 r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len);
739 log_error("Failed to retrieve COREDUMP_EXE field: %s", strerror(-r));
743 assert(len > strlen("COREDUMP_EXE="));
744 data += strlen("COREDUMP_EXE=");
745 len -= strlen("COREDUMP_EXE=");
747 exe = strndup(data, len);
751 if (endswith(exe, " (deleted)")) {
752 log_error("Binary already deleted.");
756 if (!path_is_absolute(exe)) {
757 log_error("Binary is not an absolute path.");
761 r = save_core(j, -1, &path, &unlink_path);
763 log_error("Failed to retrieve core: %s", strerror(-r));
769 log_error("Failed to fork(): %m");
774 execlp("gdb", "gdb", exe, path, NULL);
776 log_error("Failed to invoke gdb: %m");
780 r = wait_for_terminate(pid, &st);
782 log_error("Failed to wait for gdb: %m");
786 r = st.si_code == CLD_EXITED ? st.si_status : 255;
790 log_debug("Removed temporary file %s", path);
797 int main(int argc, char *argv[]) {
798 _cleanup_journal_close_ sd_journal*j = NULL;
802 _cleanup_set_free_free_ Set *matches = NULL;
804 setlocale(LC_ALL, "");
805 log_parse_environment();
808 matches = new_matches();
814 r = parse_argv(argc, argv, matches);
818 if (arg_action == ACTION_NONE)
821 r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
823 log_error("Failed to open journal: %s", strerror(-r));
827 /* We want full data, nothing truncated. */
828 sd_journal_set_data_threshold(j, 0);
830 SET_FOREACH(match, matches, it) {
831 r = sd_journal_add_match(j, match, strlen(match));
833 log_error("Failed to add match '%s': %s",
834 match, strerror(-r));
839 if (_unlikely_(log_get_max_level() >= LOG_PRI(LOG_DEBUG))) {
840 _cleanup_free_ char *filter;
842 filter = journal_make_match_string(j);
843 log_debug("Journal filter: %s", filter);
865 assert_not_reached("Shouldn't be here");
874 return r >= 0 ? r : EXIT_FAILURE;