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"
45 } arg_action = ACTION_LIST;
47 static FILE* output = NULL;
48 static char* field = NULL;
50 static int arg_no_pager = false;
51 static int arg_no_legend = false;
53 static Set *new_matches(void) {
58 set = set_new(trivial_hash_func, trivial_compare_func);
64 tmp = strdup("MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1");
71 r = set_consume(set, tmp);
73 log_error("failed to add to set: %s", strerror(-r));
81 static int help(void) {
82 printf("%s [OPTIONS...] [MATCHES...]\n\n"
83 "List or retrieve coredumps from the journal.\n\n"
85 " -o --output=FILE Write output to FILE\n"
86 " --no-pager Do not pipe output into a pager\n"
87 " --no-legend Do not print the column headers.\n\n"
90 " -h --help Show this help\n"
91 " --version Print version string\n"
92 " -F --field=FIELD List all values a certain field takes\n"
93 " gdb Start gdb for the first matching coredump\n"
94 " list List available coredumps\n"
95 " dump PID Print coredump to stdout\n"
96 " dump PATH Print coredump to stdout\n"
97 , program_invocation_short_name);
102 static int add_match(Set *set, const char *match) {
106 char *pattern = NULL;
107 _cleanup_free_ char *p = NULL;
109 if (strchr(match, '='))
111 else if (strchr(match, '/')) {
112 p = path_make_absolute_cwd(match);
117 prefix = "COREDUMP_EXE=";
119 else if (safe_atou(match, &pid) == 0)
120 prefix = "COREDUMP_PID=";
122 prefix = "COREDUMP_COMM=";
124 pattern = strjoin(prefix, match, NULL);
128 log_debug("Adding pattern: %s", pattern);
129 r = set_put(set, pattern);
131 log_error("Failed to add pattern '%s': %s",
132 pattern, strerror(-r));
139 log_error("Failed to add match: %s", strerror(-r));
143 static int parse_argv(int argc, char *argv[], Set *matches) {
152 static const struct option options[] = {
153 { "help", no_argument, NULL, 'h' },
154 { "version" , no_argument, NULL, ARG_VERSION },
155 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
156 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
157 { "output", required_argument, NULL, 'o' },
158 { "field", required_argument, NULL, 'F' },
165 while ((c = getopt_long(argc, argv, "ho:F:", options, NULL)) >= 0)
169 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 log_error("writing to '%s': %m", optarg);
202 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;
225 log_error("Unknown action '%s'", cmd);
230 if (field && arg_action != ACTION_LIST) {
231 log_error("Option --field/-F only makes sense with list");
235 while (optind < argc) {
236 r = add_match(matches, argv[optind]);
245 static int retrieve(const void *data,
252 ident = strlen(name) + 1; /* name + "=" */
257 if (memcmp(data, name, ident - 1) != 0)
260 if (((const char*) data)[ident - 1] != '=')
263 *var = strndup((const char*)data + ident, len - ident);
270 static void print_field(FILE* file, sd_journal *j) {
271 _cleanup_free_ const char *value = NULL;
277 SD_JOURNAL_FOREACH_DATA(j, d, l)
278 retrieve(d, l, field, &value);
280 fprintf(file, "%s\n", value);
283 static int print_entry(FILE* file, sd_journal *j, int had_legend) {
284 _cleanup_free_ const char
285 *pid = NULL, *uid = NULL, *gid = NULL,
286 *sgnl = NULL, *exe = NULL;
290 char buf[FORMAT_TIMESTAMP_MAX];
293 SD_JOURNAL_FOREACH_DATA(j, d, l) {
294 retrieve(d, l, "COREDUMP_PID", &pid);
295 retrieve(d, l, "COREDUMP_PID", &pid);
296 retrieve(d, l, "COREDUMP_UID", &uid);
297 retrieve(d, l, "COREDUMP_GID", &gid);
298 retrieve(d, l, "COREDUMP_SIGNAL", &sgnl);
299 retrieve(d, l, "COREDUMP_EXE", &exe);
301 retrieve(d, l, "COREDUMP_COMM", &exe);
303 retrieve(d, l, "COREDUMP_CMDLINE", &exe);
306 if (!pid && !uid && !gid && !sgnl && !exe) {
307 log_warning("Empty coredump log entry");
311 r = sd_journal_get_realtime_usec(j, &t);
313 log_error("Failed to get realtime timestamp: %s", strerror(-r));
317 format_timestamp(buf, sizeof(buf), t);
319 if (!had_legend && !arg_no_legend)
320 fprintf(file, "%-*s %*s %*s %*s %*s %s\n",
321 FORMAT_TIMESTAMP_MAX-1, "TIME",
328 fprintf(file, "%*s %*s %*s %*s %*s %s\n",
329 FORMAT_TIMESTAMP_MAX-1, buf,
339 static int dump_list(sd_journal *j) {
344 /* The coredumps are likely to compressed, and for just
345 * listing them we don't need to decompress them, so let's
346 * pick a fairly low data threshold here */
347 sd_journal_set_data_threshold(j, 4096);
349 SD_JOURNAL_FOREACH(j) {
351 print_field(stdout, j);
353 print_entry(stdout, j, found++);
356 if (!field && !found) {
357 log_notice("No coredumps found");
364 static int focus(sd_journal *j) {
367 r = sd_journal_seek_tail(j);
369 r = sd_journal_previous(j);
371 log_error("Failed to search journal: %s", strerror(-r));
375 log_error("No match found");
381 static int dump_core(sd_journal* j) {
388 /* We want full data, nothing truncated. */
389 sd_journal_set_data_threshold(j, 0);
395 print_entry(output ? stdout : stderr, j, false);
397 if (on_tty() && !output) {
398 log_error("Refusing to dump core to tty");
402 r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
404 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
409 data = (const uint8_t*) data + 9;
412 ret = fwrite(data, len, 1, output ? output : stdout);
414 log_error("dumping coredump: %m (%zu)", ret);
418 r = sd_journal_previous(j);
420 log_warning("More than one entry matches, ignoring rest.");
425 static int run_gdb(sd_journal *j) {
426 char path[] = "/var/tmp/coredump-XXXXXX";
431 _cleanup_free_ char *exe = NULL;
433 _cleanup_close_ int fd = -1;
438 sd_journal_set_data_threshold(j, 0);
444 print_entry(stdout, j, false);
446 r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len);
448 log_error("Failed to retrieve COREDUMP_EXE field: %s", strerror(-r));
453 data = (const uint8_t*) data + 13;
456 exe = strndup(data, len);
460 if (endswith(exe, " (deleted)")) {
461 log_error("Binary already deleted.");
465 r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
467 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
472 data = (const uint8_t*) data + 9;
475 fd = mkostemp_safe(path, O_WRONLY|O_CLOEXEC);
477 log_error("Failed to create temporary file: %m");
481 sz = write(fd, data, len);
483 log_error("Failed to write temporary file: %m");
487 if (sz != (ssize_t) len) {
488 log_error("Short write to temporary file.");
497 log_error("Failed to fork(): %m");
502 execlp("gdb", "gdb", exe, path, NULL);
503 log_error("Failed to invoke gdb: %m");
507 r = wait_for_terminate(pid, &st);
509 log_error("Failed to wait for gdb: %m");
513 r = st.si_code == CLD_EXITED ? st.si_status : 255;
520 int main(int argc, char *argv[]) {
521 _cleanup_journal_close_ sd_journal*j = NULL;
525 _cleanup_set_free_free_ Set *matches = NULL;
527 setlocale(LC_ALL, "");
528 log_parse_environment();
531 matches = new_matches();
537 r = parse_argv(argc, argv, matches);
541 if (arg_action == ACTION_NONE)
544 r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
546 log_error("Failed to open journal: %s", strerror(-r));
550 SET_FOREACH(match, matches, it) {
551 r = sd_journal_add_match(j, match, strlen(match));
553 log_error("Failed to add match '%s': %s",
554 match, strerror(-r));
559 if (_unlikely_(log_get_max_level() >= LOG_PRI(LOG_DEBUG))) {
560 _cleanup_free_ char *filter;
562 filter = journal_make_match_string(j);
563 log_debug("Journal filter: %s", filter);
584 assert_not_reached("Shouldn't be here");
593 return r >= 0 ? r : EXIT_FAILURE;