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) {
61 set = set_new(trivial_hash_func, trivial_compare_func);
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': %s",
114 pattern, strerror(-r));
120 log_error("Failed to add match: %s", strerror(-r));
124 static void help(void) {
125 printf("%s [OPTIONS...]\n\n"
126 "List or retrieve coredumps from the journal.\n\n"
128 " -h --help Show this help\n"
129 " --version Print version string\n"
130 " --no-pager Do not pipe output into a pager\n"
131 " --no-legend Do not print the column headers.\n"
132 " -1 Show information about most recent entry only\n"
133 " -F --field=FIELD List all values a certain field takes\n"
134 " -o --output=FILE Write output to FILE\n\n"
137 " list [MATCHES...] List available coredumps (default)\n"
138 " info [MATCHES...] Show detailed information about one or more coredumps\n"
139 " dump [MATCHES...] Print first matching coredump to stdout\n"
140 " gdb [MATCHES...] Start gdb for the first matching coredump\n"
141 , program_invocation_short_name);
144 static int parse_argv(int argc, char *argv[], Set *matches) {
153 static const struct option options[] = {
154 { "help", no_argument, NULL, 'h' },
155 { "version" , no_argument, NULL, ARG_VERSION },
156 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
157 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
158 { "output", required_argument, NULL, 'o' },
159 { "field", required_argument, NULL, 'F' },
166 while ((c = getopt_long(argc, argv, "ho:F:1", options, NULL)) >= 0)
170 arg_action = ACTION_NONE;
175 arg_action = ACTION_NONE;
176 puts(PACKAGE_STRING);
177 puts(SYSTEMD_FEATURES);
185 arg_no_legend = true;
190 log_error("cannot set output more than once");
194 output = fopen(optarg, "we");
196 log_error("writing to '%s': %m", optarg);
204 log_error("cannot use --field/-F more than once");
218 assert_not_reached("Unhandled option");
222 const char *cmd = argv[optind++];
223 if (streq(cmd, "list"))
224 arg_action = ACTION_LIST;
225 else if (streq(cmd, "dump"))
226 arg_action = ACTION_DUMP;
227 else if (streq(cmd, "gdb"))
228 arg_action = ACTION_GDB;
229 else if (streq(cmd, "info"))
230 arg_action = ACTION_INFO;
232 log_error("Unknown action '%s'", cmd);
237 if (arg_field && arg_action != ACTION_LIST) {
238 log_error("Option --field/-F only makes sense with list");
242 while (optind < argc) {
243 r = add_match(matches, argv[optind]);
252 static int retrieve(const void *data,
260 ident = strlen(name) + 1; /* name + "=" */
265 if (memcmp(data, name, ident - 1) != 0)
268 if (((const char*) data)[ident - 1] != '=')
271 v = strndup((const char*)data + ident, len - ident);
281 static void print_field(FILE* file, sd_journal *j) {
282 _cleanup_free_ char *value = NULL;
291 SD_JOURNAL_FOREACH_DATA(j, d, l)
292 retrieve(d, l, arg_field, &value);
295 fprintf(file, "%s\n", value);
298 static int print_list(FILE* file, sd_journal *j, int had_legend) {
300 *pid = NULL, *uid = NULL, *gid = NULL,
301 *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL,
306 char buf[FORMAT_TIMESTAMP_MAX];
313 SD_JOURNAL_FOREACH_DATA(j, d, l) {
314 retrieve(d, l, "COREDUMP_PID", &pid);
315 retrieve(d, l, "COREDUMP_UID", &uid);
316 retrieve(d, l, "COREDUMP_GID", &gid);
317 retrieve(d, l, "COREDUMP_SIGNAL", &sgnl);
318 retrieve(d, l, "COREDUMP_EXE", &exe);
319 retrieve(d, l, "COREDUMP_COMM", &comm);
320 retrieve(d, l, "COREDUMP_CMDLINE", &cmdline);
321 retrieve(d, l, "COREDUMP_FILENAME", &filename);
324 if (!pid && !uid && !gid && !sgnl && !exe && !comm && !cmdline && !filename) {
325 log_warning("Empty coredump log entry");
329 r = sd_journal_get_realtime_usec(j, &t);
331 log_error("Failed to get realtime timestamp: %s", strerror(-r));
335 format_timestamp(buf, sizeof(buf), t);
336 present = filename && access(filename, F_OK) == 0;
338 if (!had_legend && !arg_no_legend)
339 fprintf(file, "%-*s %*s %*s %*s %*s %*s %s\n",
340 FORMAT_TIMESTAMP_WIDTH, "TIME",
348 fprintf(file, "%-*s %*s %*s %*s %*s %*s %s\n",
349 FORMAT_TIMESTAMP_WIDTH, buf,
354 1, present ? "*" : "",
355 strna(exe ?: (comm ?: cmdline)));
360 static int print_info(FILE *file, sd_journal *j, bool need_space) {
362 *pid = NULL, *uid = NULL, *gid = NULL,
363 *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL,
364 *unit = NULL, *user_unit = NULL, *session = NULL,
365 *boot_id = NULL, *machine_id = NULL, *hostname = NULL,
366 *slice = NULL, *cgroup = NULL, *owner_uid = NULL,
367 *message = NULL, *timestamp = NULL, *filename = NULL;
375 SD_JOURNAL_FOREACH_DATA(j, d, l) {
376 retrieve(d, l, "COREDUMP_PID", &pid);
377 retrieve(d, l, "COREDUMP_UID", &uid);
378 retrieve(d, l, "COREDUMP_GID", &gid);
379 retrieve(d, l, "COREDUMP_SIGNAL", &sgnl);
380 retrieve(d, l, "COREDUMP_EXE", &exe);
381 retrieve(d, l, "COREDUMP_COMM", &comm);
382 retrieve(d, l, "COREDUMP_CMDLINE", &cmdline);
383 retrieve(d, l, "COREDUMP_UNIT", &unit);
384 retrieve(d, l, "COREDUMP_USER_UNIT", &user_unit);
385 retrieve(d, l, "COREDUMP_SESSION", &session);
386 retrieve(d, l, "COREDUMP_OWNER_UID", &owner_uid);
387 retrieve(d, l, "COREDUMP_SLICE", &slice);
388 retrieve(d, l, "COREDUMP_CGROUP", &cgroup);
389 retrieve(d, l, "COREDUMP_TIMESTAMP", ×tamp);
390 retrieve(d, l, "COREDUMP_FILENAME", &filename);
391 retrieve(d, l, "_BOOT_ID", &boot_id);
392 retrieve(d, l, "_MACHINE_ID", &machine_id);
393 retrieve(d, l, "_HOSTNAME", &hostname);
394 retrieve(d, l, "MESSAGE", &message);
402 " PID: %s%s%s (%s)\n",
403 ansi_highlight(), strna(pid), ansi_highlight_off(), comm);
407 ansi_highlight(), strna(pid), ansi_highlight_off());
412 if (parse_uid(uid, &n) >= 0) {
413 _cleanup_free_ char *u = NULL;
429 if (parse_gid(gid, &n) >= 0) {
430 _cleanup_free_ char *g = NULL;
446 if (safe_atoi(sgnl, &sig) >= 0)
447 fprintf(file, " Signal: %s (%s)\n", sgnl, signal_to_string(sig));
449 fprintf(file, " Signal: %s\n", sgnl);
455 r = safe_atou64(timestamp, &u);
457 char absolute[FORMAT_TIMESTAMP_MAX], relative[FORMAT_TIMESPAN_MAX];
460 " Timestamp: %s (%s)\n",
461 format_timestamp(absolute, sizeof(absolute), u),
462 format_timestamp_relative(relative, sizeof(relative), u));
465 fprintf(file, " Timestamp: %s\n", timestamp);
469 fprintf(file, " Command Line: %s\n", cmdline);
471 fprintf(file, " Executable: %s%s%s\n", ansi_highlight(), exe, ansi_highlight_off());
473 fprintf(file, " Control Group: %s\n", cgroup);
475 fprintf(file, " Unit: %s\n", unit);
477 fprintf(file, " User Unit: %s\n", unit);
479 fprintf(file, " Slice: %s\n", slice);
481 fprintf(file, " Session: %s\n", session);
485 if (parse_uid(owner_uid, &n) >= 0) {
486 _cleanup_free_ char *u = NULL;
490 " Owner UID: %s (%s)\n",
499 fprintf(file, " Boot ID: %s\n", boot_id);
501 fprintf(file, " Machine ID: %s\n", machine_id);
503 fprintf(file, " Hostname: %s\n", hostname);
505 if (filename && access(filename, F_OK) == 0)
506 fprintf(file, " Coredump: %s\n", filename);
509 _cleanup_free_ char *m = NULL;
511 m = strreplace(message, "\n", "\n ");
513 fprintf(file, " Message: %s\n", strstrip(m ?: message));
519 static int focus(sd_journal *j) {
522 r = sd_journal_seek_tail(j);
524 r = sd_journal_previous(j);
526 log_error("Failed to search journal: %s", strerror(-r));
530 log_error("No match found.");
536 static void print_entry(sd_journal *j, unsigned n_found) {
539 if (arg_action == ACTION_INFO)
540 print_info(stdout, j, n_found);
542 print_field(stdout, j);
544 print_list(stdout, j, n_found);
547 static int dump_list(sd_journal *j) {
548 unsigned n_found = 0;
553 /* The coredumps are likely to compressed, and for just
554 * listing them we don't need to decompress them, so let's
555 * pick a fairly low data threshold here */
556 sd_journal_set_data_threshold(j, 4096);
565 SD_JOURNAL_FOREACH(j)
566 print_entry(j, n_found++);
568 if (!arg_field && n_found <= 0) {
569 log_notice("No coredumps found.");
577 static int save_core(sd_journal *j, int fd, char **path, bool *unlink_temp) {
579 _cleanup_free_ char *filename = NULL;
583 assert((fd >= 0) != !!path);
584 assert(!!path == !!unlink_temp);
586 /* Prefer uncompressed file to journal (probably cached) to
587 * compressed file (probably uncached). */
588 r = sd_journal_get_data(j, "COREDUMP_FILENAME", (const void**) &data, &len);
589 if (r < 0 && r != -ENOENT)
590 log_warning("Failed to retrieve COREDUMP_FILENAME: %s", strerror(-r));
592 retrieve(data, len, "COREDUMP_FILENAME", &filename);
594 if (filename && access(filename, R_OK) < 0) {
595 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
596 "File %s is not readable: %m", filename);
601 if (filename && !endswith(filename, ".xz") && !endswith(filename, ".lz4")) {
609 _cleanup_close_ int fdt = -1;
613 temp = strdup("/var/tmp/coredump-XXXXXX");
617 fdt = mkostemp_safe(temp, O_WRONLY|O_CLOEXEC);
619 log_error("Failed to create temporary file: %m");
622 log_debug("Created temporary file %s", temp);
627 r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
635 sz = write(fdt, data, len);
637 log_error("Failed to write temporary file: %m");
641 if (sz != (ssize_t) len) {
642 log_error("Short write to temporary file.");
646 } else if (filename) {
647 #if defined(HAVE_XZ) || defined(HAVE_LZ4)
648 _cleanup_close_ int fdf;
650 fdf = open(filename, O_RDONLY | O_CLOEXEC);
652 log_error("Failed to open %s: %m", filename);
657 r = decompress_stream(filename, fdf, fd, -1);
659 log_error("Failed to decompress %s: %s", filename, strerror(-r));
663 log_error("Cannot decompress file. Compiled without compression support.");
669 log_error("Cannot retrieve coredump from journal nor disk.");
671 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
685 log_debug("Removed temporary file %s", temp);
691 static int dump_core(sd_journal* j) {
700 print_info(output ? stdout : stderr, j, false);
702 if (on_tty() && !output) {
703 log_error("Refusing to dump core to tty.");
707 r = save_core(j, output ? fileno(output) : STDOUT_FILENO, NULL, NULL);
709 log_error("Coredump retrieval failed: %s", strerror(-r));
713 r = sd_journal_previous(j);
715 log_warning("More than one entry matches, ignoring rest.");
720 static int run_gdb(sd_journal *j) {
721 _cleanup_free_ char *exe = NULL, *path = NULL;
722 bool unlink_path = false;
735 print_info(stdout, j, false);
738 r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len);
740 log_error("Failed to retrieve COREDUMP_EXE field: %s", strerror(-r));
744 assert(len > strlen("COREDUMP_EXE="));
745 data += strlen("COREDUMP_EXE=");
746 len -= strlen("COREDUMP_EXE=");
748 exe = strndup(data, len);
752 if (endswith(exe, " (deleted)")) {
753 log_error("Binary already deleted.");
757 if (!path_is_absolute(exe)) {
758 log_error("Binary is not an absolute path.");
762 r = save_core(j, -1, &path, &unlink_path);
764 log_error("Failed to retrieve core: %s", strerror(-r));
770 log_error("Failed to fork(): %m");
775 execlp("gdb", "gdb", exe, path, NULL);
777 log_error("Failed to invoke gdb: %m");
781 r = wait_for_terminate(pid, &st);
783 log_error("Failed to wait for gdb: %m");
787 r = st.si_code == CLD_EXITED ? st.si_status : 255;
791 log_debug("Removed temporary file %s", path);
798 int main(int argc, char *argv[]) {
799 _cleanup_journal_close_ sd_journal*j = NULL;
803 _cleanup_set_free_free_ Set *matches = NULL;
805 setlocale(LC_ALL, "");
806 log_parse_environment();
809 matches = new_matches();
815 r = parse_argv(argc, argv, matches);
819 if (arg_action == ACTION_NONE)
822 r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
824 log_error("Failed to open journal: %s", strerror(-r));
828 /* We want full data, nothing truncated. */
829 sd_journal_set_data_threshold(j, 0);
831 SET_FOREACH(match, matches, it) {
832 r = sd_journal_add_match(j, match, strlen(match));
834 log_error("Failed to add match '%s': %s",
835 match, strerror(-r));
840 if (_unlikely_(log_get_max_level() >= LOG_PRI(LOG_DEBUG))) {
841 _cleanup_free_ char *filter;
843 filter = journal_make_match_string(j);
844 log_debug("Journal filter: %s", filter);
866 assert_not_reached("Shouldn't be here");
875 return r >= 0 ? r : EXIT_FAILURE;