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"
47 } arg_action = ACTION_LIST;
48 static const char* arg_field = NULL;
49 static int arg_no_pager = false;
50 static int arg_no_legend = false;
51 static int arg_one = false;
52 static FILE* arg_output = NULL;
54 static Set *new_matches(void) {
65 tmp = strdup("MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1");
72 r = set_consume(set, tmp);
74 log_error_errno(r, "failed to add to set: %m");
82 static int add_match(Set *set, const char *match) {
87 _cleanup_free_ char *p = NULL;
89 if (strchr(match, '='))
91 else if (strchr(match, '/')) {
92 p = path_make_absolute_cwd(match);
97 prefix = "COREDUMP_EXE=";
99 else if (safe_atou(match, &pid) == 0)
100 prefix = "COREDUMP_PID=";
102 prefix = "COREDUMP_COMM=";
104 pattern = strjoin(prefix, match, NULL);
108 log_debug("Adding pattern: %s", pattern);
109 r = set_consume(set, pattern);
111 log_error_errno(r, "Failed to add pattern: %m");
117 return log_error_errno(r, "Failed to add match: %m");
120 static void help(void) {
121 printf("%s [OPTIONS...]\n\n"
122 "List or retrieve coredumps from the journal.\n\n"
124 " -h --help Show this help\n"
125 " --version Print version string\n"
126 " --no-pager Do not pipe output into a pager\n"
127 " --no-legend Do not print the column headers.\n"
128 " -1 Show information about most recent entry only\n"
129 " -F --field=FIELD List all values a certain field takes\n"
130 " -o --output=FILE Write output to FILE\n\n"
133 " list [MATCHES...] List available coredumps (default)\n"
134 " info [MATCHES...] Show detailed information about one or more coredumps\n"
135 " dump [MATCHES...] Print first matching coredump to stdout\n"
136 " gdb [MATCHES...] Start gdb for the first matching coredump\n"
137 , program_invocation_short_name);
140 static int parse_argv(int argc, char *argv[], Set *matches) {
149 static const struct option options[] = {
150 { "help", no_argument, NULL, 'h' },
151 { "version" , no_argument, NULL, ARG_VERSION },
152 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
153 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
154 { "output", required_argument, NULL, 'o' },
155 { "field", required_argument, NULL, 'F' },
162 while ((c = getopt_long(argc, argv, "ho:F:1", options, NULL)) >= 0)
166 arg_action = ACTION_NONE;
171 arg_action = ACTION_NONE;
172 puts(PACKAGE_STRING);
173 puts(SYSTEMD_FEATURES);
181 arg_no_legend = true;
186 log_error("cannot set output more than once");
190 arg_output = fopen(optarg, "we");
192 return log_error_errno(errno, "writing to '%s': %m", optarg);
198 log_error("cannot use --field/-F more than once");
212 assert_not_reached("Unhandled option");
216 const char *cmd = argv[optind++];
217 if (streq(cmd, "list"))
218 arg_action = ACTION_LIST;
219 else if (streq(cmd, "dump"))
220 arg_action = ACTION_DUMP;
221 else if (streq(cmd, "gdb"))
222 arg_action = ACTION_GDB;
223 else if (streq(cmd, "info"))
224 arg_action = ACTION_INFO;
226 log_error("Unknown action '%s'", cmd);
231 if (arg_field && arg_action != ACTION_LIST) {
232 log_error("Option --field/-F only makes sense with list");
236 while (optind < argc) {
237 r = add_match(matches, argv[optind]);
246 static int retrieve(const void *data,
254 ident = strlen(name) + 1; /* name + "=" */
259 if (memcmp(data, name, ident - 1) != 0)
262 if (((const char*) data)[ident - 1] != '=')
265 v = strndup((const char*)data + ident, len - ident);
275 static void print_field(FILE* file, sd_journal *j) {
276 _cleanup_free_ char *value = NULL;
285 SD_JOURNAL_FOREACH_DATA(j, d, l)
286 retrieve(d, l, arg_field, &value);
289 fprintf(file, "%s\n", value);
292 static int print_list(FILE* file, sd_journal *j, int had_legend) {
294 *pid = NULL, *uid = NULL, *gid = NULL,
295 *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL,
300 char buf[FORMAT_TIMESTAMP_MAX];
307 SD_JOURNAL_FOREACH_DATA(j, d, l) {
308 retrieve(d, l, "COREDUMP_PID", &pid);
309 retrieve(d, l, "COREDUMP_UID", &uid);
310 retrieve(d, l, "COREDUMP_GID", &gid);
311 retrieve(d, l, "COREDUMP_SIGNAL", &sgnl);
312 retrieve(d, l, "COREDUMP_EXE", &exe);
313 retrieve(d, l, "COREDUMP_COMM", &comm);
314 retrieve(d, l, "COREDUMP_CMDLINE", &cmdline);
315 retrieve(d, l, "COREDUMP_FILENAME", &filename);
318 if (!pid && !uid && !gid && !sgnl && !exe && !comm && !cmdline && !filename) {
319 log_warning("Empty coredump log entry");
323 r = sd_journal_get_realtime_usec(j, &t);
325 return log_error_errno(r, "Failed to get realtime timestamp: %m");
327 format_timestamp(buf, sizeof(buf), t);
328 present = filename && access(filename, F_OK) == 0;
330 if (!had_legend && !arg_no_legend)
331 fprintf(file, "%-*s %*s %*s %*s %*s %*s %s\n",
332 FORMAT_TIMESTAMP_WIDTH, "TIME",
340 fprintf(file, "%-*s %*s %*s %*s %*s %*s %s\n",
341 FORMAT_TIMESTAMP_WIDTH, buf,
346 1, present ? "*" : "",
347 strna(exe ?: (comm ?: cmdline)));
352 static int print_info(FILE *file, sd_journal *j, bool need_space) {
354 *pid = NULL, *uid = NULL, *gid = NULL,
355 *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL,
356 *unit = NULL, *user_unit = NULL, *session = NULL,
357 *boot_id = NULL, *machine_id = NULL, *hostname = NULL,
358 *slice = NULL, *cgroup = NULL, *owner_uid = NULL,
359 *message = NULL, *timestamp = NULL, *filename = NULL;
367 SD_JOURNAL_FOREACH_DATA(j, d, l) {
368 retrieve(d, l, "COREDUMP_PID", &pid);
369 retrieve(d, l, "COREDUMP_UID", &uid);
370 retrieve(d, l, "COREDUMP_GID", &gid);
371 retrieve(d, l, "COREDUMP_SIGNAL", &sgnl);
372 retrieve(d, l, "COREDUMP_EXE", &exe);
373 retrieve(d, l, "COREDUMP_COMM", &comm);
374 retrieve(d, l, "COREDUMP_CMDLINE", &cmdline);
375 retrieve(d, l, "COREDUMP_UNIT", &unit);
376 retrieve(d, l, "COREDUMP_USER_UNIT", &user_unit);
377 retrieve(d, l, "COREDUMP_SESSION", &session);
378 retrieve(d, l, "COREDUMP_OWNER_UID", &owner_uid);
379 retrieve(d, l, "COREDUMP_SLICE", &slice);
380 retrieve(d, l, "COREDUMP_CGROUP", &cgroup);
381 retrieve(d, l, "COREDUMP_TIMESTAMP", ×tamp);
382 retrieve(d, l, "COREDUMP_FILENAME", &filename);
383 retrieve(d, l, "_BOOT_ID", &boot_id);
384 retrieve(d, l, "_MACHINE_ID", &machine_id);
385 retrieve(d, l, "_HOSTNAME", &hostname);
386 retrieve(d, l, "MESSAGE", &message);
394 " PID: %s%s%s (%s)\n",
395 ansi_highlight(), strna(pid), ansi_highlight_off(), comm);
399 ansi_highlight(), strna(pid), ansi_highlight_off());
404 if (parse_uid(uid, &n) >= 0) {
405 _cleanup_free_ char *u = NULL;
421 if (parse_gid(gid, &n) >= 0) {
422 _cleanup_free_ char *g = NULL;
438 if (safe_atoi(sgnl, &sig) >= 0)
439 fprintf(file, " Signal: %s (%s)\n", sgnl, signal_to_string(sig));
441 fprintf(file, " Signal: %s\n", sgnl);
447 r = safe_atou64(timestamp, &u);
449 char absolute[FORMAT_TIMESTAMP_MAX], relative[FORMAT_TIMESPAN_MAX];
452 " Timestamp: %s (%s)\n",
453 format_timestamp(absolute, sizeof(absolute), u),
454 format_timestamp_relative(relative, sizeof(relative), u));
457 fprintf(file, " Timestamp: %s\n", timestamp);
461 fprintf(file, " Command Line: %s\n", cmdline);
463 fprintf(file, " Executable: %s%s%s\n", ansi_highlight(), exe, ansi_highlight_off());
465 fprintf(file, " Control Group: %s\n", cgroup);
467 fprintf(file, " Unit: %s\n", unit);
469 fprintf(file, " User Unit: %s\n", unit);
471 fprintf(file, " Slice: %s\n", slice);
473 fprintf(file, " Session: %s\n", session);
477 if (parse_uid(owner_uid, &n) >= 0) {
478 _cleanup_free_ char *u = NULL;
482 " Owner UID: %s (%s)\n",
491 fprintf(file, " Boot ID: %s\n", boot_id);
493 fprintf(file, " Machine ID: %s\n", machine_id);
495 fprintf(file, " Hostname: %s\n", hostname);
497 if (filename && access(filename, F_OK) == 0)
498 fprintf(file, " Coredump: %s\n", filename);
501 _cleanup_free_ char *m = NULL;
503 m = strreplace(message, "\n", "\n ");
505 fprintf(file, " Message: %s\n", strstrip(m ?: message));
511 static int focus(sd_journal *j) {
514 r = sd_journal_seek_tail(j);
516 r = sd_journal_previous(j);
518 return log_error_errno(r, "Failed to search journal: %m");
520 log_error("No match found.");
526 static void print_entry(sd_journal *j, unsigned n_found) {
529 if (arg_action == ACTION_INFO)
530 print_info(stdout, j, n_found);
532 print_field(stdout, j);
534 print_list(stdout, j, n_found);
537 static int dump_list(sd_journal *j) {
538 unsigned n_found = 0;
543 /* The coredumps are likely to compressed, and for just
544 * listing them we don't need to decompress them, so let's
545 * pick a fairly low data threshold here */
546 sd_journal_set_data_threshold(j, 4096);
555 SD_JOURNAL_FOREACH(j)
556 print_entry(j, n_found++);
558 if (!arg_field && n_found <= 0) {
559 log_notice("No coredumps found.");
567 static int save_core(sd_journal *j, int fd, char **path, bool *unlink_temp) {
569 _cleanup_free_ char *filename = NULL;
573 assert((fd >= 0) != !!path);
574 assert(!!path == !!unlink_temp);
576 /* Prefer uncompressed file to journal (probably cached) to
577 * compressed file (probably uncached). */
578 r = sd_journal_get_data(j, "COREDUMP_FILENAME", (const void**) &data, &len);
579 if (r < 0 && r != -ENOENT)
580 log_warning_errno(r, "Failed to retrieve COREDUMP_FILENAME: %m");
582 retrieve(data, len, "COREDUMP_FILENAME", &filename);
584 if (filename && access(filename, R_OK) < 0) {
585 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
586 "File %s is not readable: %m", filename);
591 if (filename && !endswith(filename, ".xz") && !endswith(filename, ".lz4")) {
599 _cleanup_close_ int fdt = -1;
603 temp = strdup("/var/tmp/coredump-XXXXXX");
607 fdt = mkostemp_safe(temp, O_WRONLY|O_CLOEXEC);
609 return log_error_errno(errno, "Failed to create temporary file: %m");
610 log_debug("Created temporary file %s", temp);
615 r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
623 sz = write(fdt, data, len);
625 log_error_errno(errno, "Failed to write temporary file: %m");
629 if (sz != (ssize_t) len) {
630 log_error("Short write to temporary file.");
634 } else if (filename) {
635 #if defined(HAVE_XZ) || defined(HAVE_LZ4)
636 _cleanup_close_ int fdf;
638 fdf = open(filename, O_RDONLY | O_CLOEXEC);
640 log_error_errno(errno, "Failed to open %s: %m", filename);
645 r = decompress_stream(filename, fdf, fd, -1);
647 log_error_errno(r, "Failed to decompress %s: %m", filename);
651 log_error("Cannot decompress file. Compiled without compression support.");
657 log_error("Cannot retrieve coredump from journal nor disk.");
659 log_error_errno(r, "Failed to retrieve COREDUMP field: %m");
673 log_debug("Removed temporary file %s", temp);
679 static int dump_core(sd_journal* j) {
688 print_info(arg_output ? stdout : stderr, j, false);
690 if (on_tty() && !arg_output) {
691 log_error("Refusing to dump core to tty.");
695 r = save_core(j, arg_output ? fileno(arg_output) : STDOUT_FILENO, NULL, NULL);
697 return log_error_errno(r, "Coredump retrieval failed: %m");
699 r = sd_journal_previous(j);
701 log_warning("More than one entry matches, ignoring rest.");
706 static int run_gdb(sd_journal *j) {
707 _cleanup_free_ char *exe = NULL, *path = NULL;
708 bool unlink_path = false;
721 print_info(stdout, j, false);
724 r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len);
726 return log_error_errno(r, "Failed to retrieve COREDUMP_EXE field: %m");
728 assert(len > strlen("COREDUMP_EXE="));
729 data += strlen("COREDUMP_EXE=");
730 len -= strlen("COREDUMP_EXE=");
732 exe = strndup(data, len);
736 if (endswith(exe, " (deleted)")) {
737 log_error("Binary already deleted.");
741 if (!path_is_absolute(exe)) {
742 log_error("Binary is not an absolute path.");
746 r = save_core(j, -1, &path, &unlink_path);
748 return log_error_errno(r, "Failed to retrieve core: %m");
752 log_error_errno(errno, "Failed to fork(): %m");
757 execlp("gdb", "gdb", exe, path, NULL);
759 log_error_errno(errno, "Failed to invoke gdb: %m");
763 r = wait_for_terminate(pid, &st);
765 log_error_errno(errno, "Failed to wait for gdb: %m");
769 r = st.si_code == CLD_EXITED ? st.si_status : 255;
773 log_debug("Removed temporary file %s", path);
780 int main(int argc, char *argv[]) {
781 _cleanup_journal_close_ sd_journal*j = NULL;
785 _cleanup_set_free_free_ Set *matches = NULL;
787 setlocale(LC_ALL, "");
788 log_parse_environment();
791 matches = new_matches();
797 r = parse_argv(argc, argv, matches);
801 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_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;