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"
46 } arg_action = ACTION_LIST;
48 static FILE* output = NULL;
49 static char* arg_field = NULL;
51 static int arg_no_pager = false;
52 static int arg_no_legend = false;
54 static Set *new_matches(void) {
59 set = set_new(trivial_hash_func, trivial_compare_func);
65 tmp = strdup("MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1");
72 r = set_consume(set, tmp);
74 log_error("failed to add to set: %s", strerror(-r));
82 static int help(void) {
84 printf("%s [OPTIONS...]\n\n"
85 "List or retrieve coredumps from the journal.\n\n"
87 " -o --output=FILE Write output to FILE\n"
88 " --no-pager Do not pipe output into a pager\n"
89 " --no-legend Do not print the column headers.\n\n"
92 " -h --help Show this help\n"
93 " --version Print version string\n"
94 " -F --field=FIELD List all values a certain field takes\n"
95 " list [MATCHES...] List available coredumps\n"
96 " dump [MATCHES...] Print first matching coredump to stdout\n"
97 " gdb [MATCHES...] Start gdb for the first matching coredump\n"
98 , program_invocation_short_name);
103 static int add_match(Set *set, const char *match) {
107 char *pattern = NULL;
108 _cleanup_free_ char *p = NULL;
110 if (strchr(match, '='))
112 else if (strchr(match, '/')) {
113 p = path_make_absolute_cwd(match);
118 prefix = "COREDUMP_EXE=";
120 else if (safe_atou(match, &pid) == 0)
121 prefix = "COREDUMP_PID=";
123 prefix = "COREDUMP_COMM=";
125 pattern = strjoin(prefix, match, NULL);
129 log_debug("Adding pattern: %s", pattern);
130 r = set_put(set, pattern);
132 log_error("Failed to add pattern '%s': %s",
133 pattern, strerror(-r));
140 log_error("Failed to add match: %s", strerror(-r));
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:", options, NULL)) >= 0)
170 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");
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;
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;
282 SD_JOURNAL_FOREACH_DATA(j, d, l)
283 retrieve(d, l, arg_field, &value);
286 fprintf(file, "%s\n", value);
289 static int print_entry(FILE* file, sd_journal *j, int had_legend) {
291 *pid = NULL, *uid = NULL, *gid = NULL,
292 *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL;
296 char buf[FORMAT_TIMESTAMP_MAX];
299 SD_JOURNAL_FOREACH_DATA(j, d, l) {
300 retrieve(d, l, "COREDUMP_PID", &pid);
301 retrieve(d, l, "COREDUMP_UID", &uid);
302 retrieve(d, l, "COREDUMP_GID", &gid);
303 retrieve(d, l, "COREDUMP_SIGNAL", &sgnl);
304 retrieve(d, l, "COREDUMP_EXE", &exe);
305 retrieve(d, l, "COREDUMP_COMM", &comm);
306 retrieve(d, l, "COREDUMP_CMDLINE", &cmdline);
309 if (!pid && !uid && !gid && !sgnl && !exe && !comm && !cmdline) {
310 log_warning("Empty coredump log entry");
314 r = sd_journal_get_realtime_usec(j, &t);
316 log_error("Failed to get realtime timestamp: %s", strerror(-r));
320 format_timestamp(buf, sizeof(buf), t);
322 if (!had_legend && !arg_no_legend)
323 fprintf(file, "%-*s %*s %*s %*s %*s %s\n",
324 FORMAT_TIMESTAMP_MAX-1, "TIME",
331 fprintf(file, "%*s %*s %*s %*s %*s %s\n",
332 FORMAT_TIMESTAMP_MAX-1, buf,
337 strna(exe ?: (comm ?: cmdline)));
342 static int dump_list(sd_journal *j) {
347 /* The coredumps are likely to compressed, and for just
348 * listing them we don't need to decompress them, so let's
349 * pick a fairly low data threshold here */
350 sd_journal_set_data_threshold(j, 4096);
352 SD_JOURNAL_FOREACH(j) {
354 print_field(stdout, j);
356 print_entry(stdout, j, found++);
359 if (!arg_field && !found) {
360 log_notice("No coredumps found");
367 static int focus(sd_journal *j) {
370 r = sd_journal_seek_tail(j);
372 r = sd_journal_previous(j);
374 log_error("Failed to search journal: %s", strerror(-r));
378 log_error("No match found");
384 #define filename_escape(s) xescape((s), "./")
386 static int make_coredump_path(sd_journal *j, char **ret) {
388 *pid = NULL, *boot_id = NULL, *tstamp = NULL, *comm = NULL,
389 *p = NULL, *b = NULL, *t = NULL, *c = NULL;
397 SD_JOURNAL_FOREACH_DATA(j, d, l) {
398 retrieve(d, l, "COREDUMP_COMM", &comm);
399 retrieve(d, l, "COREDUMP_PID", &pid);
400 retrieve(d, l, "COREDUMP_TIMESTAMP", &tstamp);
401 retrieve(d, l, "_BOOT_ID", &boot_id);
404 if (!pid || !comm || !tstamp || !boot_id) {
405 log_error("Failed to retrieve necessary fields to find coredump on disk.");
409 p = filename_escape(pid);
413 t = filename_escape(tstamp);
417 c = filename_escape(comm);
421 b = filename_escape(boot_id);
425 fn = strjoin("/var/lib/systemd/coredump/core.", c, ".", b, ".", p, ".", t, NULL);
433 static int dump_core(sd_journal* j) {
440 /* We want full data, nothing truncated. */
441 sd_journal_set_data_threshold(j, 0);
447 print_entry(output ? stdout : stderr, j, false);
449 if (on_tty() && !output) {
450 log_error("Refusing to dump core to tty");
454 r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
456 _cleanup_free_ char *fn = NULL;
457 _cleanup_close_ int fd = -1;
459 r = make_coredump_path(j, &fn);
463 fd = open(fn, O_RDONLY|O_CLOEXEC|O_NOCTTY);
466 log_error("Coredump neither in journal file nor stored externally on disk.");
468 log_error("Failed to open coredump file: %s", strerror(-r));
473 r = copy_bytes(fd, output ? fileno(output) : STDOUT_FILENO);
475 log_error("Failed to stream coredump: %s", strerror(-r));
480 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
485 data = (const uint8_t*) data + 9;
488 ret = fwrite(data, len, 1, output ?: stdout);
490 log_error("Dumping coredump failed: %m (%zu)", ret);
495 r = sd_journal_previous(j);
497 log_warning("More than one entry matches, ignoring rest.");
502 static int run_gdb(sd_journal *j) {
504 _cleanup_free_ char *exe = NULL, *coredump = NULL;
505 char temp[] = "/var/tmp/coredump-XXXXXX";
506 bool unlink_temp = false;
516 sd_journal_set_data_threshold(j, 0);
522 print_entry(stdout, j, false);
524 r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len);
526 log_error("Failed to retrieve COREDUMP_EXE field: %s", strerror(-r));
531 data = (const uint8_t*) data + 13;
534 exe = strndup(data, len);
538 if (endswith(exe, " (deleted)")) {
539 log_error("Binary already deleted.");
543 if (!path_is_absolute(exe)) {
544 log_error("Binary is not an absolute path.");
548 r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
551 r = make_coredump_path(j, &coredump);
555 if (access(coredump, R_OK) < 0) {
557 log_error("Coredump neither in journal file nor stored externally on disk.");
559 log_error("Failed to access coredump fiile: %s", strerror(-r));
567 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
571 _cleanup_close_ int fd = -1;
575 data = (const uint8_t*) data + 9;
578 fd = mkostemp_safe(temp, O_WRONLY|O_CLOEXEC);
580 log_error("Failed to create temporary file: %m");
586 sz = write(fd, data, len);
588 log_error("Failed to write temporary file: %m");
592 if (sz != (ssize_t) len) {
593 log_error("Short write to temporary file.");
603 log_error("Failed to fork(): %m");
608 execlp("gdb", "gdb", exe, path, NULL);
610 log_error("Failed to invoke gdb: %m");
614 r = wait_for_terminate(pid, &st);
616 log_error("Failed to wait for gdb: %m");
620 r = st.si_code == CLD_EXITED ? st.si_status : 255;
629 int main(int argc, char *argv[]) {
630 _cleanup_journal_close_ sd_journal*j = NULL;
634 _cleanup_set_free_free_ Set *matches = NULL;
636 setlocale(LC_ALL, "");
637 log_parse_environment();
640 matches = new_matches();
646 r = parse_argv(argc, argv, matches);
650 if (arg_action == ACTION_NONE)
653 r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
655 log_error("Failed to open journal: %s", strerror(-r));
659 SET_FOREACH(match, matches, it) {
660 r = sd_journal_add_match(j, match, strlen(match));
662 log_error("Failed to add match '%s': %s",
663 match, strerror(-r));
668 if (_unlikely_(log_get_max_level() >= LOG_PRI(LOG_DEBUG))) {
669 _cleanup_free_ char *filter;
671 filter = journal_make_match_string(j);
672 log_debug("Journal filter: %s", filter);
693 assert_not_reached("Shouldn't be here");
702 return r >= 0 ? r : EXIT_FAILURE;