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 "sd-journal.h"
34 #include "path-util.h"
37 #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;
53 static FILE* arg_output = NULL;
55 static Set *new_matches(void) {
66 tmp = strdup("MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1");
73 r = set_consume(set, tmp);
75 log_error_errno(r, "failed to add to set: %m");
83 static int add_match(Set *set, const char *match) {
88 _cleanup_free_ char *p = NULL;
90 if (strchr(match, '='))
92 else if (strchr(match, '/')) {
93 p = path_make_absolute_cwd(match);
98 prefix = "COREDUMP_EXE=";
100 else if (safe_atou(match, &pid) == 0)
101 prefix = "COREDUMP_PID=";
103 prefix = "COREDUMP_COMM=";
105 pattern = strjoin(prefix, match, NULL);
109 log_debug("Adding pattern: %s", pattern);
110 r = set_consume(set, pattern);
112 log_error_errno(r, "Failed to add pattern: %m");
118 return log_error_errno(r, "Failed to add match: %m");
121 static void help(void) {
122 printf("%s [OPTIONS...]\n\n"
123 "List or retrieve coredumps from the journal.\n\n"
125 " -h --help Show this help\n"
126 " --version Print version string\n"
127 " --no-pager Do not pipe output into a pager\n"
128 " --no-legend Do not print the column headers.\n"
129 " -1 Show information about most recent entry only\n"
130 " -F --field=FIELD List all values a certain field takes\n"
131 " -o --output=FILE Write output to FILE\n\n"
134 " list [MATCHES...] List available coredumps (default)\n"
135 " info [MATCHES...] Show detailed information about one or more coredumps\n"
136 " dump [MATCHES...] Print first matching coredump to stdout\n"
137 " gdb [MATCHES...] Start gdb for the first matching coredump\n"
138 , program_invocation_short_name);
141 static int parse_argv(int argc, char *argv[], Set *matches) {
150 static const struct option options[] = {
151 { "help", no_argument, NULL, 'h' },
152 { "version" , no_argument, NULL, ARG_VERSION },
153 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
154 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
155 { "output", required_argument, NULL, 'o' },
156 { "field", required_argument, NULL, 'F' },
163 while ((c = getopt_long(argc, argv, "ho:F:1", options, NULL)) >= 0)
167 arg_action = ACTION_NONE;
172 arg_action = ACTION_NONE;
173 puts(PACKAGE_STRING);
174 puts(SYSTEMD_FEATURES);
182 arg_no_legend = true;
187 log_error("cannot set output more than once");
191 arg_output = fopen(optarg, "we");
193 return log_error_errno(errno, "writing to '%s': %m", optarg);
199 log_error("cannot use --field/-F more than once");
213 assert_not_reached("Unhandled option");
217 const char *cmd = argv[optind++];
218 if (streq(cmd, "list"))
219 arg_action = ACTION_LIST;
220 else if (streq(cmd, "dump"))
221 arg_action = ACTION_DUMP;
222 else if (streq(cmd, "gdb"))
223 arg_action = ACTION_GDB;
224 else if (streq(cmd, "info"))
225 arg_action = ACTION_INFO;
227 log_error("Unknown action '%s'", cmd);
232 if (arg_field && arg_action != ACTION_LIST) {
233 log_error("Option --field/-F only makes sense with list");
237 while (optind < argc) {
238 r = add_match(matches, argv[optind]);
247 static int retrieve(const void *data,
255 ident = strlen(name) + 1; /* name + "=" */
260 if (memcmp(data, name, ident - 1) != 0)
263 if (((const char*) data)[ident - 1] != '=')
266 v = strndup((const char*)data + ident, len - ident);
276 static void print_field(FILE* file, sd_journal *j) {
277 _cleanup_free_ char *value = NULL;
286 SD_JOURNAL_FOREACH_DATA(j, d, l)
287 retrieve(d, l, arg_field, &value);
290 fprintf(file, "%s\n", value);
293 static int print_list(FILE* file, sd_journal *j, int had_legend) {
295 *pid = NULL, *uid = NULL, *gid = NULL,
296 *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL,
301 char buf[FORMAT_TIMESTAMP_MAX];
308 SD_JOURNAL_FOREACH_DATA(j, d, l) {
309 retrieve(d, l, "COREDUMP_PID", &pid);
310 retrieve(d, l, "COREDUMP_UID", &uid);
311 retrieve(d, l, "COREDUMP_GID", &gid);
312 retrieve(d, l, "COREDUMP_SIGNAL", &sgnl);
313 retrieve(d, l, "COREDUMP_EXE", &exe);
314 retrieve(d, l, "COREDUMP_COMM", &comm);
315 retrieve(d, l, "COREDUMP_CMDLINE", &cmdline);
316 retrieve(d, l, "COREDUMP_FILENAME", &filename);
319 if (!pid && !uid && !gid && !sgnl && !exe && !comm && !cmdline && !filename) {
320 log_warning("Empty coredump log entry");
324 r = sd_journal_get_realtime_usec(j, &t);
326 return log_error_errno(r, "Failed to get realtime timestamp: %m");
328 format_timestamp(buf, sizeof(buf), t);
329 present = filename && access(filename, F_OK) == 0;
331 if (!had_legend && !arg_no_legend)
332 fprintf(file, "%-*s %*s %*s %*s %*s %*s %s\n",
333 FORMAT_TIMESTAMP_WIDTH, "TIME",
341 fprintf(file, "%-*s %*s %*s %*s %*s %*s %s\n",
342 FORMAT_TIMESTAMP_WIDTH, buf,
347 1, present ? "*" : "",
348 strna(exe ?: (comm ?: cmdline)));
353 static int print_info(FILE *file, sd_journal *j, bool need_space) {
355 *pid = NULL, *uid = NULL, *gid = NULL,
356 *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL,
357 *unit = NULL, *user_unit = NULL, *session = NULL,
358 *boot_id = NULL, *machine_id = NULL, *hostname = NULL,
359 *slice = NULL, *cgroup = NULL, *owner_uid = NULL,
360 *message = NULL, *timestamp = NULL, *filename = NULL;
368 SD_JOURNAL_FOREACH_DATA(j, d, l) {
369 retrieve(d, l, "COREDUMP_PID", &pid);
370 retrieve(d, l, "COREDUMP_UID", &uid);
371 retrieve(d, l, "COREDUMP_GID", &gid);
372 retrieve(d, l, "COREDUMP_SIGNAL", &sgnl);
373 retrieve(d, l, "COREDUMP_EXE", &exe);
374 retrieve(d, l, "COREDUMP_COMM", &comm);
375 retrieve(d, l, "COREDUMP_CMDLINE", &cmdline);
376 retrieve(d, l, "COREDUMP_UNIT", &unit);
377 retrieve(d, l, "COREDUMP_USER_UNIT", &user_unit);
378 retrieve(d, l, "COREDUMP_SESSION", &session);
379 retrieve(d, l, "COREDUMP_OWNER_UID", &owner_uid);
380 retrieve(d, l, "COREDUMP_SLICE", &slice);
381 retrieve(d, l, "COREDUMP_CGROUP", &cgroup);
382 retrieve(d, l, "COREDUMP_TIMESTAMP", ×tamp);
383 retrieve(d, l, "COREDUMP_FILENAME", &filename);
384 retrieve(d, l, "_BOOT_ID", &boot_id);
385 retrieve(d, l, "_MACHINE_ID", &machine_id);
386 retrieve(d, l, "_HOSTNAME", &hostname);
387 retrieve(d, l, "MESSAGE", &message);
395 " PID: %s%s%s (%s)\n",
396 ansi_highlight(), strna(pid), ansi_highlight_off(), comm);
400 ansi_highlight(), strna(pid), ansi_highlight_off());
405 if (parse_uid(uid, &n) >= 0) {
406 _cleanup_free_ char *u = NULL;
422 if (parse_gid(gid, &n) >= 0) {
423 _cleanup_free_ char *g = NULL;
439 if (safe_atoi(sgnl, &sig) >= 0)
440 fprintf(file, " Signal: %s (%s)\n", sgnl, signal_to_string(sig));
442 fprintf(file, " Signal: %s\n", sgnl);
448 r = safe_atou64(timestamp, &u);
450 char absolute[FORMAT_TIMESTAMP_MAX], relative[FORMAT_TIMESPAN_MAX];
453 " Timestamp: %s (%s)\n",
454 format_timestamp(absolute, sizeof(absolute), u),
455 format_timestamp_relative(relative, sizeof(relative), u));
458 fprintf(file, " Timestamp: %s\n", timestamp);
462 fprintf(file, " Command Line: %s\n", cmdline);
464 fprintf(file, " Executable: %s%s%s\n", ansi_highlight(), exe, ansi_highlight_off());
466 fprintf(file, " Control Group: %s\n", cgroup);
468 fprintf(file, " Unit: %s\n", unit);
470 fprintf(file, " User Unit: %s\n", unit);
472 fprintf(file, " Slice: %s\n", slice);
474 fprintf(file, " Session: %s\n", session);
478 if (parse_uid(owner_uid, &n) >= 0) {
479 _cleanup_free_ char *u = NULL;
483 " Owner UID: %s (%s)\n",
492 fprintf(file, " Boot ID: %s\n", boot_id);
494 fprintf(file, " Machine ID: %s\n", machine_id);
496 fprintf(file, " Hostname: %s\n", hostname);
498 if (filename && access(filename, F_OK) == 0)
499 fprintf(file, " Coredump: %s\n", filename);
502 _cleanup_free_ char *m = NULL;
504 m = strreplace(message, "\n", "\n ");
506 fprintf(file, " Message: %s\n", strstrip(m ?: message));
512 static int focus(sd_journal *j) {
515 r = sd_journal_seek_tail(j);
517 r = sd_journal_previous(j);
519 return log_error_errno(r, "Failed to search journal: %m");
521 log_error("No match found.");
527 static void print_entry(sd_journal *j, unsigned n_found) {
530 if (arg_action == ACTION_INFO)
531 print_info(stdout, j, n_found);
533 print_field(stdout, j);
535 print_list(stdout, j, n_found);
538 static int dump_list(sd_journal *j) {
539 unsigned n_found = 0;
544 /* The coredumps are likely to compressed, and for just
545 * listing them we don't need to decompress them, so let's
546 * pick a fairly low data threshold here */
547 sd_journal_set_data_threshold(j, 4096);
556 SD_JOURNAL_FOREACH(j)
557 print_entry(j, n_found++);
559 if (!arg_field && n_found <= 0) {
560 log_notice("No coredumps found.");
568 static int save_core(sd_journal *j, int fd, char **path, bool *unlink_temp) {
570 _cleanup_free_ char *filename = NULL;
574 assert((fd >= 0) != !!path);
575 assert(!!path == !!unlink_temp);
577 /* Prefer uncompressed file to journal (probably cached) to
578 * compressed file (probably uncached). */
579 r = sd_journal_get_data(j, "COREDUMP_FILENAME", (const void**) &data, &len);
580 if (r < 0 && r != -ENOENT)
581 log_warning_errno(r, "Failed to retrieve COREDUMP_FILENAME: %m");
583 retrieve(data, len, "COREDUMP_FILENAME", &filename);
585 if (filename && access(filename, R_OK) < 0) {
586 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
587 "File %s is not readable: %m", filename);
592 if (filename && !endswith(filename, ".xz") && !endswith(filename, ".lz4")) {
600 _cleanup_close_ int fdt = -1;
604 temp = strdup("/var/tmp/coredump-XXXXXX");
608 fdt = mkostemp_safe(temp, O_WRONLY|O_CLOEXEC);
610 return log_error_errno(errno, "Failed to create temporary file: %m");
611 log_debug("Created temporary file %s", temp);
616 r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
624 sz = write(fdt, data, len);
626 log_error_errno(errno, "Failed to write temporary file: %m");
630 if (sz != (ssize_t) len) {
631 log_error("Short write to temporary file.");
635 } else if (filename) {
636 #if defined(HAVE_XZ) || defined(HAVE_LZ4)
637 _cleanup_close_ int fdf;
639 fdf = open(filename, O_RDONLY | O_CLOEXEC);
641 log_error_errno(errno, "Failed to open %s: %m", filename);
646 r = decompress_stream(filename, fdf, fd, -1);
648 log_error_errno(r, "Failed to decompress %s: %m", filename);
652 log_error("Cannot decompress file. Compiled without compression support.");
658 log_error("Cannot retrieve coredump from journal nor disk.");
660 log_error_errno(r, "Failed to retrieve COREDUMP field: %m");
674 log_debug("Removed temporary file %s", temp);
680 static int dump_core(sd_journal* j) {
689 print_info(arg_output ? stdout : stderr, j, false);
691 if (on_tty() && !arg_output) {
692 log_error("Refusing to dump core to tty.");
696 r = save_core(j, arg_output ? fileno(arg_output) : STDOUT_FILENO, NULL, NULL);
698 return log_error_errno(r, "Coredump retrieval failed: %m");
700 r = sd_journal_previous(j);
702 log_warning("More than one entry matches, ignoring rest.");
707 static int run_gdb(sd_journal *j) {
708 _cleanup_free_ char *exe = NULL, *path = NULL;
709 bool unlink_path = false;
722 print_info(stdout, j, false);
725 r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len);
727 return log_error_errno(r, "Failed to retrieve COREDUMP_EXE field: %m");
729 assert(len > strlen("COREDUMP_EXE="));
730 data += strlen("COREDUMP_EXE=");
731 len -= strlen("COREDUMP_EXE=");
733 exe = strndup(data, len);
737 if (endswith(exe, " (deleted)")) {
738 log_error("Binary already deleted.");
742 if (!path_is_absolute(exe)) {
743 log_error("Binary is not an absolute path.");
747 r = save_core(j, -1, &path, &unlink_path);
749 return log_error_errno(r, "Failed to retrieve core: %m");
753 log_error_errno(errno, "Failed to fork(): %m");
758 execlp("gdb", "gdb", exe, path, NULL);
760 log_error_errno(errno, "Failed to invoke gdb: %m");
764 r = wait_for_terminate(pid, &st);
766 log_error_errno(errno, "Failed to wait for gdb: %m");
770 r = st.si_code == CLD_EXITED ? st.si_status : 255;
774 log_debug("Removed temporary file %s", path);
781 int main(int argc, char *argv[]) {
782 _cleanup_journal_close_ sd_journal*j = NULL;
786 _cleanup_set_free_free_ Set *matches = NULL;
788 setlocale(LC_ALL, "");
789 log_parse_environment();
792 matches = new_matches();
798 r = parse_argv(argc, argv, matches);
802 if (arg_action == ACTION_NONE)
807 r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
809 log_error_errno(r, "Failed to open journal: %m");
813 /* We want full data, nothing truncated. */
814 sd_journal_set_data_threshold(j, 0);
816 SET_FOREACH(match, matches, it) {
817 r = sd_journal_add_match(j, match, strlen(match));
819 log_error_errno(r, "Failed to add match '%s': %m",
825 if (_unlikely_(log_get_max_level() >= LOG_DEBUG)) {
826 _cleanup_free_ char *filter;
828 filter = journal_make_match_string(j);
829 log_debug("Journal filter: %s", filter);
851 assert_not_reached("Shouldn't be here");
860 return r >= 0 ? r : EXIT_FAILURE;