diff options
-rw-r--r-- | Makefile.am | 9 | ||||
-rw-r--r-- | man/systemd-coredumpctl.xml | 186 | ||||
-rw-r--r-- | src/journal/coredumpctl.c | 393 |
3 files changed, 588 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am index e86e6347ed..42c481ed79 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 0000000000..378d40fa13 --- /dev/null +++ b/man/systemd-coredumpctl.xml @@ -0,0 +1,186 @@ +<?xml version='1.0'?> <!--*-nxml-*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<!-- + 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 <http://www.gnu.org/licenses/>. +--> + +<refentry id="systemd-coredumpctl"> + + <refentryinfo> + <title>systemd-coredumpctl</title> + <productname>systemd</productname> + + <authorgroup> + <author> + <contrib>Developer</contrib> + <firstname>Zbigniew</firstname> + <surname>Jędrzejewski-Szmek</surname> + <email>zbyszek@in.waw.pl</email> + </author> + </authorgroup> + </refentryinfo> + + <refmeta> + <refentrytitle>systemd-coredumpctl</refentrytitle> + <manvolnum>1</manvolnum> + </refmeta> + + <refnamediv> + <refname>systemd-coredumpctl</refname> + <refpurpose>Retrieve coredumps from the journal</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <cmdsynopsis> + <command>systemd-coredumpctl <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="req">COMMAND</arg> <arg choice="opt" rep="repeat">PID|COMM|EXE|MATCH</arg></command> + </cmdsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para><command>systemd-coredumpctl</command> may be used to + retrieve coredumps from + <citerefentry><refentrytitle>systemd-journald</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para> + </refsect1> + + <refsect1> + <title>Options</title> + + <para>The following options are understood:</para> + + <variablelist> + <varlistentry> + <term><option>--help</option></term> + <term><option>-h</option></term> + + <listitem><para>Print a short help + text and exit.</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--version</option></term> + + <listitem><para>Print a short version + string and exit.</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--output=FILE</option></term> + <term><option>-o FILE</option></term> + + <listitem><para>Write the core to + <option>FILE</option>.</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--no-pager</option></term> + + <listitem><para>Do not pipe output of + <command>list</command> into a + pager.</para></listitem> + </varlistentry> + + </variablelist> + + <para>The following commands are understood:</para> + + <variablelist> + <varlistentry> + <term><command>list</command></term> + + <listitem><para>List coredumps captured in the journal + matching specified characteristics.</para></listitem> + </varlistentry> + + <varlistentry> + <term><command>dump</command></term> + + <listitem><para>Extract the last coredump + matching specified characteristics. + Coredump will be written on stdout, unless + an output file is specified with + <option>-o/--output</option>. + </para></listitem> + </varlistentry> + + </variablelist> + + </refsect1> + + <refsect1> + <title>Matching</title> + + <para>Match can be:</para> + + <variablelist> + <varlistentry> + <term><option>PID</option></term> + + <listitem><para>Process ID of the + process that dumped + core. An integer.</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>COMM</option></term> + + <listitem><para>Name of the executable + (matches <option>COREDUMP_COMM=</option>). + Must not contain slashes. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><option>EXE</option></term> + + <listitem><para>Path to the executable + (matches <option>COREDUMP_EXE=</option>). + Must contain at least one slash. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><option>MATCH</option></term> + + <listitem><para>General journalctl predicates + (see <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>). + Must contain an equals sign. + </para></listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>Exit status</title> + <para>On success 0 is returned, a non-zero failure + code otherwise. Not finding any mathing coredumps is treated + as failure. + </para> + </refsect1> + + <refsect1> + <title>See Also</title> + <para> + <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> + </para> + </refsect1> + +</refentry> diff --git a/src/journal/coredumpctl.c b/src/journal/coredumpctl.c new file mode 100644 index 0000000000..5c442ffe9b --- /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 <http://www.gnu.org/licenses/>. +***/ + +#include <stdio.h> +#include <string.h> +#include <getopt.h> + +#include <systemd/sd-journal.h> + +#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; +} |