From 5de0409e54580b1e8929d9e09f970dfe339cec7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 19 Oct 2012 12:29:46 +0200 Subject: [PATCH] journal: add tool to extract coredumps 'systemd-coredumpctl' will list available coredumps: PID UID GID sig exe 32452 500 500 11 /home/zbyszek/systemd/build/journalctl 32666 500 500 11 /usr/lib64/valgrind/memcheck-amd64-linux ... 'systemd-coredumpctl dump PID' will write the coredump to specified file or stdout. --- Makefile.am | 9 + man/systemd-coredumpctl.xml | 186 +++++++++++++++++ src/journal/coredumpctl.c | 393 ++++++++++++++++++++++++++++++++++++ 3 files changed, 588 insertions(+) create mode 100644 man/systemd-coredumpctl.xml create mode 100644 src/journal/coredumpctl.c diff --git a/Makefile.am b/Makefile.am index e86e6347e..42c481ed7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -482,6 +482,7 @@ MANPAGES = \ man/journald.conf.5 \ man/systemd-journald.service.8 \ man/journalctl.1 \ + man/systemd-coredumpctl.1 \ man/systemd-inhibit.1 \ man/systemd-remount-fs.service.8 \ man/systemd-update-utmp-runlevel.service.8 \ @@ -2457,6 +2458,13 @@ journalctl_LDADD += \ $(QRENCODE_LIBS) endif +systemd_coredumpctl_SOURCES = \ + src/journal/coredumpctl.c + +systemd_coredumpctl_LDADD = \ + libsystemd-shared.la \ + libsystemd-journal.la + test_journal_SOURCES = \ src/journal/test-journal.c @@ -2647,6 +2655,7 @@ rootbin_PROGRAMS += \ journalctl bin_PROGRAMS += \ + systemd-coredumpctl \ systemd-cat dist_systemunit_DATA += \ diff --git a/man/systemd-coredumpctl.xml b/man/systemd-coredumpctl.xml new file mode 100644 index 000000000..378d40fa1 --- /dev/null +++ b/man/systemd-coredumpctl.xml @@ -0,0 +1,186 @@ + + + + + + + + + systemd-coredumpctl + systemd + + + + Developer + Zbigniew + Jędrzejewski-Szmek + zbyszek@in.waw.pl + + + + + + systemd-coredumpctl + 1 + + + + systemd-coredumpctl + Retrieve coredumps from the journal + + + + + systemd-coredumpctl OPTIONS COMMAND PID|COMM|EXE|MATCH + + + + + Description + + systemd-coredumpctl may be used to + retrieve coredumps from + systemd-journald8. + + + + Options + + The following options are understood: + + + + + + + Print a short help + text and exit. + + + + + + Print a short version + string and exit. + + + + + + + Write the core to + . + + + + + + Do not pipe output of + list into a + pager. + + + + + The following commands are understood: + + + + list + + List coredumps captured in the journal + matching specified characteristics. + + + + dump + + Extract the last coredump + matching specified characteristics. + Coredump will be written on stdout, unless + an output file is specified with + . + + + + + + + + + Matching + + Match can be: + + + + + + Process ID of the + process that dumped + core. An integer. + + + + + + Name of the executable + (matches ). + Must not contain slashes. + + + + + + + Path to the executable + (matches ). + Must contain at least one slash. + + + + + + + General journalctl predicates + (see journalctl1). + Must contain an equals sign. + + + + + + + Exit status + On success 0 is returned, a non-zero failure + code otherwise. Not finding any mathing coredumps is treated + as failure. + + + + + See Also + + systemd-journald.service8 + + + + diff --git a/src/journal/coredumpctl.c b/src/journal/coredumpctl.c new file mode 100644 index 000000000..5c442ffe9 --- /dev/null +++ b/src/journal/coredumpctl.c @@ -0,0 +1,393 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2012 Zbigniew Jędrzejewski-Szmek + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include + +#include "build.h" +#include "set.h" +#include "util.h" +#include "log.h" +#include "path-util.h" +#include "pager.h" + +static enum { + ACTION_NONE, + ACTION_LIST, + ACTION_DUMP, +} arg_action = ACTION_LIST; + +Set *matches; +FILE* output; + +int arg_no_pager; + +static Set *new_matches(void) { + Set *set; + char *tmp; + int r; + + set = set_new(trivial_hash_func, trivial_compare_func); + if (!set) { + log_oom(); + return NULL; + } + + tmp = strdup("MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1"); + if (!tmp) { + log_oom(); + set_clear_free(set); + return NULL; + } + + r = set_put(set, tmp); + if (r < 0) { + log_error("failed to add to set: %s", strerror(-r)); + free(tmp); + set_clear_free(set); + return NULL; + } + + return set; +} + +static int help(void) { + printf("%s [OPTIONS...] [MATCHES...]\n\n" + "List or retrieve coredumps from the journal.\n\n" + "Flags:\n" + " -o --output=FILE Write output to FILE\n" + " --no-pager Do not pipe output into a pager\n" + + "Commands:\n" + " -h --help Show this help\n" + " --version Print version string\n" + " list List available coredumps\n" + " dump PID Print coredump to stdout\n" + " dump PATH Print coredump to stdout\n" + , program_invocation_short_name); + + return 0; +} + +static int add_match(Set *set, const char *match) { + int r = -ENOMEM; + unsigned pid; + const char* prefix; + char *pattern = NULL; + char _cleanup_free_ *p = NULL; + + if (strchr(match, '=')) + prefix = ""; + else if (strchr(match, '/')) { + p = path_make_absolute_cwd(match); + if (!p) + goto fail; + + match = p; + prefix = "COREDUMP_EXE="; + } + else if (safe_atou(match, &pid) == 0) + prefix = "COREDUMP_PID="; + else + prefix = "COREDUMP_COMM="; + + pattern = strjoin(prefix, match, NULL); + if (!pattern) + goto fail; + + r = set_put(set, pattern); + if (r < 0) { + log_error("failed to add pattern '%s': %s", + pattern, strerror(-r)); + goto fail; + } + log_debug("Added pattern: %s", pattern); + + return 0; +fail: + free(pattern); + log_error("failed to add match: %s", strerror(-r)); + return r; +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_NO_PAGER, + }; + + int r, c; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version" , no_argument, NULL, ARG_VERSION }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "output", required_argument, NULL, 'o' }, + }; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "ho:", options, NULL)) >= 0) + switch(c) { + case 'h': + help(); + arg_action = ACTION_NONE; + return 0; + + case ARG_VERSION: + puts(PACKAGE_STRING); + puts(DISTRIBUTION); + puts(SYSTEMD_FEATURES); + arg_action = ACTION_NONE; + return 0; + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case 'o': + if (output) { + log_error("cannot set output more than once"); + return -EINVAL; + } + + output = fopen(optarg, "we"); + if (!output) { + log_error("writing to '%s': %m", optarg); + return -errno; + } + + break; + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + + if (optind < argc) { + const char *cmd = argv[optind++]; + if(streq(cmd, "list")) + arg_action = ACTION_LIST; + else if (streq(cmd, "dump")) + arg_action = ACTION_DUMP; + else { + log_error("Unknown action '%s'", cmd); + return -EINVAL; + } + } + + while (optind < argc) { + r = add_match(matches, argv[optind]); + if (r != 0) + return r; + optind++; + } + + return 0; +} + +static int retrieve(sd_journal *j, const char *name, const char **var) { + const void *data; + size_t len, field; + int r; + + r = sd_journal_get_data(j, name, &data, &len); + if (r < 0) { + log_warning("Failed to retrieve %s", name); + return r; + } + + field = strlen(name) + 1; // name + "=" + assert(len >= field); + + *var = strndup((const char*)data + field, len - field); + if (!var) + return log_oom(); + + return 0; +} + +static void print_entry(FILE* file, sd_journal *j, int had_header) { + const char _cleanup_free_ + *pid = NULL, *uid = NULL, *gid = NULL, + *sgnl = NULL, *exe = NULL; + + retrieve(j, "COREDUMP_PID", &pid); + retrieve(j, "COREDUMP_UID", &uid); + retrieve(j, "COREDUMP_GID", &gid); + retrieve(j, "COREDUMP_SIGNAL", &sgnl); + retrieve(j, "COREDUMP_EXE", &exe); + if (!exe) + retrieve(j, "COREDUMP_COMM", &exe); + if (!exe) + retrieve(j, "COREDUMP_CMDLINE", &exe); + + if (!pid && !uid && !gid && !sgnl && !exe) { + log_warning("empty coredump log entry"); + return; + } + + if (!had_header) + fprintf(file, "%*s %*s %*s %*s %s\n", + 6, "PID", + 5, "UID", + 5, "GID", + 3, "sig", + "exe"); + + fprintf(file, "%*s %*s %*s %*s %s\n", + 6, pid, + 5, uid, + 5, gid, + 3, sgnl, + exe); +} + +static int dump_list(sd_journal *j) { + int found = 0; + + assert(j); + + SD_JOURNAL_FOREACH(j) + print_entry(stdout, j, found++); + + if (!found) { + log_error("no coredumps found"); + return -ESRCH; + } + + return 0; +} + +static int dump_core(sd_journal* j) { + const char *data; + size_t len, ret; + int r; + + assert(j); + + r = sd_journal_seek_tail(j); + if (r == 0) + r = sd_journal_previous(j); + if (r < 0) { + log_error("Failed to search journal: %s", strerror(-r)); + return r; + } + + if (r == 0) { + log_error("No match found"); + return -ESRCH; + } + + r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len); + if (r != 0) { + log_error("retrieve COREDUMP field: %s", strerror(-r)); + return r; + } + + print_entry(output ? stdout : stderr, j, false); + + if (on_tty() && !output) { + log_error("Refusing to dump core to tty"); + return -ENOTTY; + } + + assert(len >= 9); + + ret = fwrite(data+9, len-9, 1, output ? output : stdout); + if (ret != 1) { + log_error("dumping coredump: %m (%zu)", ret); + return -errno; + } + + r = sd_journal_previous(j); + if (r >= 0) + log_warning("More than one entry matches, ignoring rest.\n"); + + return 0; +} + +int main(int argc, char *argv[]) { + sd_journal *j = NULL; + const char* match; + Iterator it; + int r = 0; + + log_parse_environment(); + log_open(); + + matches = new_matches(); + if (!matches) + goto end; + + if (parse_argv(argc, argv)) + goto end; + + if (arg_action == ACTION_NONE) + goto end; + + r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY); + if (r < 0) { + log_error("Failed to open journal: %s", strerror(-r)); + goto end; + } + + SET_FOREACH(match, matches, it) { + log_info("Matching: %s", match); + + r = sd_journal_add_match(j, match, strlen(match)); + if (r != 0) { + log_error("Failed to add match '%s': %s", + match, strerror(-r)); + goto end; + } + } + + switch(arg_action) { + case ACTION_LIST: + if (!arg_no_pager) + pager_open(); + + r = dump_list(j); + break; + case ACTION_DUMP: + r = dump_core(j); + break; + case ACTION_NONE: + assert_not_reached("Shouldn't be here"); + } + +end: + if (j) + sd_journal_close(j); + + set_free_free(matches); + + pager_close(); + + if (output) + fclose(output); + + return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +}