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 return log_error_errno(errno, "writing to '%s': %m", optarg);
200 log_error("cannot use --field/-F more than once");
214 assert_not_reached("Unhandled option");
218 const char *cmd = argv[optind++];
219 if (streq(cmd, "list"))
220 arg_action = ACTION_LIST;
221 else if (streq(cmd, "dump"))
222 arg_action = ACTION_DUMP;
223 else if (streq(cmd, "gdb"))
224 arg_action = ACTION_GDB;
225 else if (streq(cmd, "info"))
226 arg_action = ACTION_INFO;
228 log_error("Unknown action '%s'", cmd);
233 if (arg_field && arg_action != ACTION_LIST) {
234 log_error("Option --field/-F only makes sense with list");
238 while (optind < argc) {
239 r = add_match(matches, argv[optind]);
248 static int retrieve(const void *data,
256 ident = strlen(name) + 1; /* name + "=" */
261 if (memcmp(data, name, ident - 1) != 0)
264 if (((const char*) data)[ident - 1] != '=')
267 v = strndup((const char*)data + ident, len - ident);
277 static void print_field(FILE* file, sd_journal *j) {
278 _cleanup_free_ char *value = NULL;
287 SD_JOURNAL_FOREACH_DATA(j, d, l)
288 retrieve(d, l, arg_field, &value);
291 fprintf(file, "%s\n", value);
294 static int print_list(FILE* file, sd_journal *j, int had_legend) {
296 *pid = NULL, *uid = NULL, *gid = NULL,
297 *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL,
302 char buf[FORMAT_TIMESTAMP_MAX];
309 SD_JOURNAL_FOREACH_DATA(j, d, l) {
310 retrieve(d, l, "COREDUMP_PID", &pid);
311 retrieve(d, l, "COREDUMP_UID", &uid);
312 retrieve(d, l, "COREDUMP_GID", &gid);
313 retrieve(d, l, "COREDUMP_SIGNAL", &sgnl);
314 retrieve(d, l, "COREDUMP_EXE", &exe);
315 retrieve(d, l, "COREDUMP_COMM", &comm);
316 retrieve(d, l, "COREDUMP_CMDLINE", &cmdline);
317 retrieve(d, l, "COREDUMP_FILENAME", &filename);
320 if (!pid && !uid && !gid && !sgnl && !exe && !comm && !cmdline && !filename) {
321 log_warning("Empty coredump log entry");
325 r = sd_journal_get_realtime_usec(j, &t);
327 return log_error_errno(r, "Failed to get realtime timestamp: %m");
329 format_timestamp(buf, sizeof(buf), t);
330 present = filename && access(filename, F_OK) == 0;
332 if (!had_legend && !arg_no_legend)
333 fprintf(file, "%-*s %*s %*s %*s %*s %*s %s\n",
334 FORMAT_TIMESTAMP_WIDTH, "TIME",
342 fprintf(file, "%-*s %*s %*s %*s %*s %*s %s\n",
343 FORMAT_TIMESTAMP_WIDTH, buf,
348 1, present ? "*" : "",
349 strna(exe ?: (comm ?: cmdline)));
354 static int print_info(FILE *file, sd_journal *j, bool need_space) {
356 *pid = NULL, *uid = NULL, *gid = NULL,
357 *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL,
358 *unit = NULL, *user_unit = NULL, *session = NULL,
359 *boot_id = NULL, *machine_id = NULL, *hostname = NULL,
360 *slice = NULL, *cgroup = NULL, *owner_uid = NULL,
361 *message = NULL, *timestamp = NULL, *filename = NULL;
369 SD_JOURNAL_FOREACH_DATA(j, d, l) {
370 retrieve(d, l, "COREDUMP_PID", &pid);
371 retrieve(d, l, "COREDUMP_UID", &uid);
372 retrieve(d, l, "COREDUMP_GID", &gid);
373 retrieve(d, l, "COREDUMP_SIGNAL", &sgnl);
374 retrieve(d, l, "COREDUMP_EXE", &exe);
375 retrieve(d, l, "COREDUMP_COMM", &comm);
376 retrieve(d, l, "COREDUMP_CMDLINE", &cmdline);
377 retrieve(d, l, "COREDUMP_UNIT", &unit);
378 retrieve(d, l, "COREDUMP_USER_UNIT", &user_unit);
379 retrieve(d, l, "COREDUMP_SESSION", &session);
380 retrieve(d, l, "COREDUMP_OWNER_UID", &owner_uid);
381 retrieve(d, l, "COREDUMP_SLICE", &slice);
382 retrieve(d, l, "COREDUMP_CGROUP", &cgroup);
383 retrieve(d, l, "COREDUMP_TIMESTAMP", ×tamp);
384 retrieve(d, l, "COREDUMP_FILENAME", &filename);
385 retrieve(d, l, "_BOOT_ID", &boot_id);
386 retrieve(d, l, "_MACHINE_ID", &machine_id);
387 retrieve(d, l, "_HOSTNAME", &hostname);
388 retrieve(d, l, "MESSAGE", &message);
396 " PID: %s%s%s (%s)\n",
397 ansi_highlight(), strna(pid), ansi_highlight_off(), comm);
401 ansi_highlight(), strna(pid), ansi_highlight_off());
406 if (parse_uid(uid, &n) >= 0) {
407 _cleanup_free_ char *u = NULL;
423 if (parse_gid(gid, &n) >= 0) {
424 _cleanup_free_ char *g = NULL;
440 if (safe_atoi(sgnl, &sig) >= 0)
441 fprintf(file, " Signal: %s (%s)\n", sgnl, signal_to_string(sig));
443 fprintf(file, " Signal: %s\n", sgnl);
449 r = safe_atou64(timestamp, &u);
451 char absolute[FORMAT_TIMESTAMP_MAX], relative[FORMAT_TIMESPAN_MAX];
454 " Timestamp: %s (%s)\n",
455 format_timestamp(absolute, sizeof(absolute), u),
456 format_timestamp_relative(relative, sizeof(relative), u));
459 fprintf(file, " Timestamp: %s\n", timestamp);
463 fprintf(file, " Command Line: %s\n", cmdline);
465 fprintf(file, " Executable: %s%s%s\n", ansi_highlight(), exe, ansi_highlight_off());
467 fprintf(file, " Control Group: %s\n", cgroup);
469 fprintf(file, " Unit: %s\n", unit);
471 fprintf(file, " User Unit: %s\n", unit);
473 fprintf(file, " Slice: %s\n", slice);
475 fprintf(file, " Session: %s\n", session);
479 if (parse_uid(owner_uid, &n) >= 0) {
480 _cleanup_free_ char *u = NULL;
484 " Owner UID: %s (%s)\n",
493 fprintf(file, " Boot ID: %s\n", boot_id);
495 fprintf(file, " Machine ID: %s\n", machine_id);
497 fprintf(file, " Hostname: %s\n", hostname);
499 if (filename && access(filename, F_OK) == 0)
500 fprintf(file, " Coredump: %s\n", filename);
503 _cleanup_free_ char *m = NULL;
505 m = strreplace(message, "\n", "\n ");
507 fprintf(file, " Message: %s\n", strstrip(m ?: message));
513 static int focus(sd_journal *j) {
516 r = sd_journal_seek_tail(j);
518 r = sd_journal_previous(j);
520 return log_error_errno(r, "Failed to search journal: %m");
522 log_error("No match found.");
528 static void print_entry(sd_journal *j, unsigned n_found) {
531 if (arg_action == ACTION_INFO)
532 print_info(stdout, j, n_found);
534 print_field(stdout, j);
536 print_list(stdout, j, n_found);
539 static int dump_list(sd_journal *j) {
540 unsigned n_found = 0;
545 /* The coredumps are likely to compressed, and for just
546 * listing them we don't need to decompress them, so let's
547 * pick a fairly low data threshold here */
548 sd_journal_set_data_threshold(j, 4096);
557 SD_JOURNAL_FOREACH(j)
558 print_entry(j, n_found++);
560 if (!arg_field && n_found <= 0) {
561 log_notice("No coredumps found.");
569 static int save_core(sd_journal *j, int fd, char **path, bool *unlink_temp) {
571 _cleanup_free_ char *filename = NULL;
575 assert((fd >= 0) != !!path);
576 assert(!!path == !!unlink_temp);
578 /* Prefer uncompressed file to journal (probably cached) to
579 * compressed file (probably uncached). */
580 r = sd_journal_get_data(j, "COREDUMP_FILENAME", (const void**) &data, &len);
581 if (r < 0 && r != -ENOENT)
582 log_warning_errno(r, "Failed to retrieve COREDUMP_FILENAME: %m");
584 retrieve(data, len, "COREDUMP_FILENAME", &filename);
586 if (filename && access(filename, R_OK) < 0) {
587 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
588 "File %s is not readable: %m", filename);
593 if (filename && !endswith(filename, ".xz") && !endswith(filename, ".lz4")) {
601 _cleanup_close_ int fdt = -1;
605 temp = strdup("/var/tmp/coredump-XXXXXX");
609 fdt = mkostemp_safe(temp, O_WRONLY|O_CLOEXEC);
611 return log_error_errno(errno, "Failed to create temporary file: %m");
612 log_debug("Created temporary file %s", temp);
617 r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
625 sz = write(fdt, data, len);
627 log_error_errno(errno, "Failed to write temporary file: %m");
631 if (sz != (ssize_t) len) {
632 log_error("Short write to temporary file.");
636 } else if (filename) {
637 #if defined(HAVE_XZ) || defined(HAVE_LZ4)
638 _cleanup_close_ int fdf;
640 fdf = open(filename, O_RDONLY | O_CLOEXEC);
642 log_error_errno(errno, "Failed to open %s: %m", filename);
647 r = decompress_stream(filename, fdf, fd, -1);
649 log_error_errno(r, "Failed to decompress %s: %m", filename);
653 log_error("Cannot decompress file. Compiled without compression support.");
659 log_error("Cannot retrieve coredump from journal nor disk.");
661 log_error_errno(r, "Failed to retrieve COREDUMP field: %m");
675 log_debug("Removed temporary file %s", temp);
681 static int dump_core(sd_journal* j) {
690 print_info(output ? stdout : stderr, j, false);
692 if (on_tty() && !output) {
693 log_error("Refusing to dump core to tty.");
697 r = save_core(j, output ? fileno(output) : STDOUT_FILENO, NULL, NULL);
699 return log_error_errno(r, "Coredump retrieval failed: %m");
701 r = sd_journal_previous(j);
703 log_warning("More than one entry matches, ignoring rest.");
708 static int run_gdb(sd_journal *j) {
709 _cleanup_free_ char *exe = NULL, *path = NULL;
710 bool unlink_path = false;
723 print_info(stdout, j, false);
726 r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len);
728 return log_error_errno(r, "Failed to retrieve COREDUMP_EXE field: %m");
730 assert(len > strlen("COREDUMP_EXE="));
731 data += strlen("COREDUMP_EXE=");
732 len -= strlen("COREDUMP_EXE=");
734 exe = strndup(data, len);
738 if (endswith(exe, " (deleted)")) {
739 log_error("Binary already deleted.");
743 if (!path_is_absolute(exe)) {
744 log_error("Binary is not an absolute path.");
748 r = save_core(j, -1, &path, &unlink_path);
750 return log_error_errno(r, "Failed to retrieve core: %m");
754 log_error_errno(errno, "Failed to fork(): %m");
759 execlp("gdb", "gdb", exe, path, NULL);
761 log_error_errno(errno, "Failed to invoke gdb: %m");
765 r = wait_for_terminate(pid, &st);
767 log_error_errno(errno, "Failed to wait for gdb: %m");
771 r = st.si_code == CLD_EXITED ? st.si_status : 255;
775 log_debug("Removed temporary file %s", path);
782 int main(int argc, char *argv[]) {
783 _cleanup_journal_close_ sd_journal*j = NULL;
787 _cleanup_set_free_free_ Set *matches = NULL;
789 setlocale(LC_ALL, "");
790 log_parse_environment();
793 matches = new_matches();
799 r = parse_argv(argc, argv, matches);
803 if (arg_action == ACTION_NONE)
806 r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
808 log_error_errno(r, "Failed to open journal: %m");
812 /* We want full data, nothing truncated. */
813 sd_journal_set_data_threshold(j, 0);
815 SET_FOREACH(match, matches, it) {
816 r = sd_journal_add_match(j, match, strlen(match));
818 log_error_errno(r, "Failed to add match '%s': %m",
824 if (_unlikely_(log_get_max_level() >= LOG_PRI(LOG_DEBUG))) {
825 _cleanup_free_ char *filter;
827 filter = journal_make_match_string(j);
828 log_debug("Journal filter: %s", filter);
850 assert_not_reached("Shouldn't be here");
859 return r >= 0 ? r : EXIT_FAILURE;