[systemd-devel] [PATCH] [RFCv7] Optionally save core dumps as plain files
Oleksii Shevchuk
alxchk at gmail.com
Tue May 14 09:09:54 PDT 2013
Introduce configuration file: /etc/systemd/coredump.conf with
configurable core dumps backend storages (journal/file/both/none),
per storage size limits and preprocessing.
Default filestorage choosed as /run/log/coredump or /var/log/coredump
with next reason:
1. These files produced with systemd component
2. These files registered with journal
Main differences between v7 and v6:
1. As Colin suggested, create-then-drop policy used when storing to
files.
2. {/var/,/run}/log/coredump/MACHINE-ID folder will be created with
root:systemd-journal owner and drwxr-s-- permissions.
3. Trivial preprocessing support added. For example:
PreprocessFile=gzip -9 <%i>%o
PreprocessJournal=gdb -nw --batch --quiet --silent --ex 'thread \
apply all bt' --core %i %e > %o
This will save to file storage compressed cores and backtraces to
journal COREDUMP= section.
4. Copying "optimizations" removed for now. Now core always stored
to temporary folder, then optionaly preprocessed to temporary
files, then temporary files copied to storage.
Weak places:
coredump.c:495
To prevent copying of large memory segments, I contruct virtual
space manually. Probably it will not work as expected in some cases,
so recomendations and comments needed..
---
Makefile-man.am | 1 +
Makefile.am | 14 +-
man/coredump.conf.xml | 217 +++++++++++
src/core/manager.c | 1 +
src/journal/coredump-gperf.gperf | 24 ++
src/journal/coredump.c | 764 ++++++++++++++++++++++++++++++++-------
src/journal/coredump.conf | 18 +
src/journal/coredump.h | 89 +++++
src/journal/journald-server.h | 2 +-
9 files changed, 992 insertions(+), 138 deletions(-)
create mode 100644 man/coredump.conf.xml
create mode 100644 src/journal/coredump-gperf.gperf
create mode 100644 src/journal/coredump.conf
create mode 100644 src/journal/coredump.h
diff --git a/Makefile-man.am b/Makefile-man.am
index 481423a..e3ff8dd 100644
--- a/Makefile-man.am
+++ b/Makefile-man.am
@@ -8,6 +8,7 @@ MANPAGES += \
man/hostname.5 \
man/journalctl.1 \
man/journald.conf.5 \
+ man/coredump.conf.5 \
man/kernel-command-line.7 \
man/kernel-install.8 \
man/locale.conf.5 \
diff --git a/Makefile.am b/Makefile.am
index eb85c8d..96e5bc3 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2908,7 +2908,8 @@ nodist_systemunit_DATA += \
units/systemd-journal-flush.service
dist_pkgsysconf_DATA += \
- src/journal/journald.conf
+ src/journal/journald.conf \
+ src/journal/coredump.conf
pkgconfiglib_DATA += \
src/journal/libsystemd-journal.pc
@@ -2927,10 +2928,12 @@ EXTRA_DIST += \
src/journal/libsystemd-journal.sym \
units/systemd-journald.service.in \
units/systemd-journal-flush.service.in \
- src/journal/journald-gperf.gperf
+ src/journal/journald-gperf.gperf \
+ src/journal/coredump-gperf.gperf
CLEANFILES += \
- src/journal/journald-gperf.c
+ src/journal/journald-gperf.c \
+ src/journal/coredump-gperf.c
# ------------------------------------------------------------------------------
if HAVE_MICROHTTPD
@@ -2975,10 +2978,13 @@ EXTRA_DIST += \
# ------------------------------------------------------------------------------
if ENABLE_COREDUMP
systemd_coredump_SOURCES = \
- src/journal/coredump.c
+ src/shared/specifier.c \
+ src/journal/coredump.c \
+ src/journal/coredump-gperf.c
systemd_coredump_LDADD = \
libsystemd-journal-internal.la \
+ libsystemd-id128-internal.la \
libsystemd-label.la \
libsystemd-shared.la
diff --git a/man/coredump.conf.xml b/man/coredump.conf.xml
new file mode 100644
index 0000000..491b9d0
--- /dev/null
+++ b/man/coredump.conf.xml
@@ -0,0 +1,217 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?>
+<!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 2013 Lennart Poettering
+ Oleksii Shevchuk
+
+ 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="coredump.conf">
+ <refentryinfo>
+ <title>coredump.conf</title>
+ <productname>systemd</productname>
+
+ <authorgroup>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Lennart</firstname>
+ <surname>Poettering</surname>
+ <email>lennart at poettering.net</email>
+ </author>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Oleksii</firstname>
+ <surname>Shevchuk</surname>
+ <email>alxchk at gmail.com</email>
+ </author>
+ </authorgroup>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>coredump.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>coredump.conf</refname>
+ <refpurpose>Core dump utility configuration file</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <para><filename>/etc/systemd/coredump.conf</filename></para>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>This files configures several parameters of the
+ systemd-coredump utility.</para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>Options</title>
+
+ <para>All options are configured in the
+ <literal>[Coredump]</literal> section:</para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><varname>FileStorage=</varname></term>
+
+ <listitem><para>One of
+ <literal>volatile</literal> or,
+ <literal>persistent</literal>
+ value. If <literal>volatile</literal>, core dump will be
+ stored as file to /run/log/coredump/MACHINE-ID/COMM-TMPSUFFIX location;
+ if <literal>persistent</literal> -- to
+ /var/log/coredump/MACHINE-ID/COMM-TMPSUFFIX location.
+ Storage directory will be created if not exists.
+ COMM will be escaped.
+ Field COREDUMP_FILE= will be added to log
+ message with COMM-TMPSUFFIX value.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>MaxJournalCoreSize=</varname></term>
+ <term><varname>MaxFileCoreSize=</varname></term>
+
+ <listitem><para>Enforce size limits on the core
+ dumps stored. The option prefixed with <literal>Journal</literal>
+ apply to the COREDUMP= journal message field. The option prefixed
+ with <literal>File</literal> apply to the files when stored on a
+ persistent file system, more specifically
+ <filename>/var/log/coredump/MACHINE-ID</filename>. If value equals
+ to zero, then backend will be ommited.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>PreprocessUser=</varname></term>
+
+ <listitem><para>If unspecified <literal>systemd-coredump</literal> will drop privileges to nobody user
+ to store core to temporary folder and process it. When value is unset, <literal>systemd-coredump</literal>
+ will drop privileges to process owner uid/gid. When specified, privileges will be changed to that user.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>PreprocessJournal=</varname></term>
+ <term><varname>PreprocessFile=</varname></term>
+
+ <listitem><para>Core dumps can be preprocessed before storing to storages via command specified
+ by this option. Processing done by calling specified tools via system() call. Next specifiers can be
+ used:</para>
+
+ <table>
+ <title>Specifiers available in processing commands</title>
+ <tgroup cols='3' align='left' colsep='1' rowsep='1'>
+ <colspec colname="spec" />
+ <colspec colname="mean" />
+ <colspec colname="detail" />
+ <thead>
+ <row>
+ <entry>Specifier</entry>
+ <entry>Meaning</entry>
+ <entry>Details</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>%p</literal></entry>
+ <entry>Crashed process PID</entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry><literal>%u</literal></entry>
+ <entry>Crashed process UID</entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry><literal>%g</literal></entry>
+ <entry>Crashed process GID</entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry><literal>%s</literal></entry>
+ <entry>Signal, which caused dumping the core</entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry><literal>%t</literal></entry>
+ <entry>Timestamp of crash</entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry><literal>%e</literal></entry>
+ <entry>Crashed process EXE path</entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry><literal>%U</literal></entry>
+ <entry>Crashed process unit name</entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry><literal>%S</literal></entry>
+ <entry>Crashed process session</entry>
+ <entry>Available only when builded with logind</entry>
+ </row>
+ <row>
+ <entry><literal>%C</literal></entry>
+ <entry>Crashed process command line</entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry><literal>%i</literal></entry>
+ <entry>Temporary file with stored core</entry>
+ <entry>This file shouldn't be removed or modified</entry>
+ </row>
+ <row>
+ <entry><literal>%o</literal></entry>
+ <entry>Temporary file that should contain result of processing</entry>
+ <entry>If this file doesn't exists, then input file will be used</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry><term><varname>PreprocessMaxSize=</varname></term>
+
+ <listitem><para>This will limit stored and processed core size.</para></listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-coredumpctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.journal-fields</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ </para>
+ </refsect1>
+
+</refentry>
diff --git a/src/core/manager.c b/src/core/manager.c
index 0508628..efce9ac 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -51,6 +51,7 @@
#include "hashmap.h"
#include "macro.h"
#include "strv.h"
+#include "env-util.h"
#include "log.h"
#include "util.h"
#include "mkdir.h"
diff --git a/src/journal/coredump-gperf.gperf b/src/journal/coredump-gperf.gperf
new file mode 100644
index 0000000..1bc599c
--- /dev/null
+++ b/src/journal/coredump-gperf.gperf
@@ -0,0 +1,24 @@
+%{
+#include <stddef.h>
+#include "coredump.h"
+#include "conf-parser.h"
+%}
+struct ConfigPerfItem;
+%null_strings
+%language=ANSI-C
+%define slot-name section_and_lvalue
+%define hash-function-name coredump_gperf_hash
+%define lookup-function-name coredump_gperf_lookup
+%readonly-tables
+%omit-struct-type
+%struct-type
+%includes
+%%
+Coredump.MaxJournalCoreSize, config_parse_bytes_off, 0, offsetof(Coredump, journal_max_size)
+Coredump.MaxFileCoreSize, config_parse_bytes_off, 0, offsetof(Coredump, file_max_size)
+Coredump.FileStorage, config_parse_storage, 0, offsetof(Coredump, storage)
+Coredump.PreprocessUser, config_parse_string, 0, offsetof(Coredump, preprocess_user)
+Coredump.PreprocessJournal, config_parse_string, 0, offsetof(Coredump, preprocess_journal)
+Coredump.PreprocessFile, config_parse_string, 0, offsetof(Coredump, preprocess_file)
+Coredump.PreprocessMaxSize, config_parse_bytes_off, 0, offsetof(Coredump, preprocess_max_size)
+Coredump.PreprocessDirectoryPath, config_parse_string, 0, offsetof(Coredump, preprocess_directory_path)
diff --git a/src/journal/coredump.c b/src/journal/coredump.c
index fd03e38..473499d 100644
--- a/src/journal/coredump.c
+++ b/src/journal/coredump.c
@@ -3,7 +3,8 @@
/***
This file is part of systemd.
- Copyright 2012 Lennart Poettering
+ Copyright 2012-2013 Lennart Poettering
+ Oleksii Shevchuk
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
@@ -22,7 +23,12 @@
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
+#include <stdint.h>
+#include <fcntl.h>
+#include <sys/mman.h>
#include <sys/prctl.h>
+#include <sys/wait.h>
+#include <sys/ptrace.h>
#include <systemd/sd-journal.h>
@@ -35,13 +41,13 @@
#include "macro.h"
#include "mkdir.h"
#include "special.h"
+#include "sd-id128.h"
+#include "coredump.h"
#include "cgroup-util.h"
+#include "specifier.h"
-/* Few programs have less than 3MiB resident */
-#define COREDUMP_MIN_START (3*1024*1024)
-/* Make sure to not make this larger than the maximum journal entry
- * size. See ENTRY_SIZE_MAX in journald-native.c. */
-#define COREDUMP_MAX (768*1024*1024)
+#define COREDUMP_MAX_DEFAULT ( 24 * 1024 * 1024 )
+#define COREDUMP_CONFIG "/etc/systemd/coredump.conf"
enum {
ARG_PID = 1,
@@ -53,26 +59,84 @@ enum {
_ARG_MAX
};
-static int divert_coredump(void) {
- _cleanup_fclose_ FILE *f = NULL;
+static const char* const storage_table[] = {
+ [STORAGE_VOLATILE] = "volatile",
+ [STORAGE_PERSISTENT] = "persistent",
+};
- log_info("Detected coredump of the journal daemon itself, diverting coredump to /var/lib/systemd/coredump/.");
+DEFINE_STRING_TABLE_LOOKUP(storage, Storage);
+DEFINE_CONFIG_PARSE_ENUM(config_parse_storage, storage, Storage, "Failed to parse storage setting");
- mkdir_p_label("/var/lib/systemd/coredump", 0755);
+static int coredump_drop_creds(uid_t uid, uid_t gid) {
- f = fopen("/var/lib/systemd/coredump/core.systemd-journald", "we");
- if (!f) {
- log_error("Failed to create coredump file: %m");
+
+ if (setresgid(gid, gid, gid) < 0 ||
+ setresuid(uid, uid, uid) < 0) {
+ log_error("Failed to drop privileges: %m");
return -errno;
}
+ umask(0337);
+
+ return 0;
+}
+
+static char * coredump_tmp_directory(Coredump *s) {
+ assert(s);
+
+ return strdup(s->preprocess_directory_path);
+}
+
+static char * coredump_directory(Coredump *s) {
+ sd_id128_t machineid;
+ char buffer[33];
+
+ int r = sd_id128_get_machine(&machineid);
+ if (r)
+ return NULL;
+
+ return strjoin(s->storage == STORAGE_VOLATILE ?
+ "/run/log/coredump/":
+ "/var/log/coredump/",
+ sd_id128_to_string(machineid, buffer),
+ NULL);
+}
+
+static int coredump_ensure_directory_exists(Coredump *s) {
+ static const mode_t mode = S_ISGID
+ | S_IRUSR | S_IWUSR | S_IXUSR
+ | S_IRGRP | S_IXGRP
+ ;
+ int r;
+ _cleanup_free_ char *d = NULL;
+
+ assert(s);
+
+ d = coredump_directory(s);
+ if (!d)
+ return log_oom();
+
+ r = mkdir_parents_label(d, 0755);
+ if (r)
+ return -errno;
+
+ r = mkdir_safe_label(d, mode, 0, s->journal);
+ if (r && (errno != EEXIST))
+ return -errno;
+
+ return 0;
+}
+
+static int coredump_stdio_to_file(FILE *corefile, size_t max_size) {
+ size_t offset = 0;
for (;;) {
uint8_t buffer[4096];
size_t l, q;
- l = fread(buffer, 1, sizeof(buffer), stdin);
+ l = fread(buffer, 1, MIN(sizeof(buffer), max_size - offset),
+ stdin);
if (l <= 0) {
- if (ferror(f)) {
+ if (ferror(corefile)) {
log_error("Failed to read coredump: %m");
return -errno;
}
@@ -80,16 +144,19 @@ static int divert_coredump(void) {
break;
}
- q = fwrite(buffer, 1, l, f);
+ q = fwrite(buffer, 1, l, corefile);
if (q != l) {
log_error("Failed to write coredump: %m");
return -errno;
}
+
+ if ((offset += q) >= max_size)
+ break;
}
- fflush(f);
+ fflush(corefile);
- if (ferror(f)) {
+ if (ferror(corefile)) {
log_error("Failed to write coredump: %m");
return -errno;
}
@@ -97,181 +164,612 @@ static int divert_coredump(void) {
return 0;
}
-int main(int argc, char* argv[]) {
- int r, j = 0;
- char *t;
- ssize_t n;
- pid_t pid;
- uid_t uid;
- gid_t gid;
- struct iovec iovec[14];
- size_t coredump_bufsize, coredump_size;
- _cleanup_free_ char *core_pid = NULL, *core_uid = NULL, *core_gid = NULL, *core_signal = NULL,
- *core_timestamp = NULL, *core_comm = NULL, *core_exe = NULL, *core_unit = NULL,
- *core_session = NULL, *core_message = NULL, *core_cmdline = NULL, *coredump_data = NULL;
+static int coredump_create_file(Coredump *s, char ** name, bool shared) {
+ _cleanup_free_ char *template = NULL, *escaped_comm = NULL, *directory = NULL;
+ int fd;
- prctl(PR_SET_DUMPABLE, 0);
+ assert(s);
+ assert(name);
+ assert(s->core_comm);
- if (argc != _ARG_MAX) {
- log_set_target(LOG_TARGET_JOURNAL_OR_KMSG);
- log_open();
+ directory = shared
+ ? coredump_tmp_directory(s)
+ : coredump_directory(s)
+ ;
- log_error("Invalid number of arguments passed from kernel.");
- r = -EINVAL;
- goto finish;
+ if (! directory)
+ return log_oom();
+
+ escaped_comm = xescape(s->core_comm, "/ ");
+ if (! escaped_comm)
+ return log_oom();
+
+ template = strjoin(directory, "/", escaped_comm, "-XXXXXX", NULL);
+ if (! template)
+ return log_oom();
+
+ fd = mkostemp(template, O_CREAT | O_EXCL | O_APPEND | O_RDWR | shared ? 0 : O_CLOEXEC);
+
+ if (fd < 0)
+ return -errno;
+
+ if (fchmod(fd, S_IRUSR | S_IRGRP))
+ return -errno;
+
+ *name = strdup(template);
+ if (! *name) {
+ close(fd);
+ return log_oom();
}
- r = parse_pid(argv[ARG_PID], &pid);
+ return fd;
+}
+
+static void coredump_cleanup_str(char *mem, size_t size) {
+ (void)size;
+ free(mem);
+}
+
+static void coredump_cleanup_mem(char *mem, size_t size) {
+ munmap(mem, size);
+}
+
+static char* specifier_quoted_string(char specifier, void *data, void *userdata) {
+ return strjoin("'", strempty(data), "'", NULL);
+}
+
+static char* coredump_printf(Coredump *s,
+ char *input,
+ char *output,
+ char *format) {
+
+ const Specifier table[] = {
+ { 'p', specifier_string, s->core_pid },
+ { 'u', specifier_string, s->core_uid },
+ { 'g', specifier_string, s->core_gid },
+ { 's', specifier_string, s->core_signal },
+ { 't', specifier_string, s->core_timestamp },
+ { 'e', specifier_quoted_string, s->core_exe },
+ { 'U', specifier_quoted_string, s->core_unit },
+#ifdef HAVE_LOGIND
+ { 'S', specifier_quoted_string, s->core_session },
+#endif
+ { 'C', specifier_quoted_string, s->core_cmdline },
+ { 'i', specifier_quoted_string, input },
+ { 'o', specifier_quoted_string, output },
+ { 0, NULL, NULL }
+ };
+
+ return specifier_printf(format, table, s);
+}
+
+static int coredump_mmap_file(const char *path, char *addr, char **mem, size_t *size)
+{
+ struct stat stat;
+ int fd;
+
+ assert(path);
+ assert(mem);
+ assert(size);
+
+ fd = open(path, O_NOFOLLOW | O_RDONLY);
+
+ if (fd != -1 && !fstat(fd, &stat) &&
+ (*mem = mmap(addr, stat.st_size,
+ PROT_READ, MAP_SHARED,
+ fd,
+ 0)) != MAP_FAILED) {
+ *size = stat.st_size;
+ close(fd);
+ return 0;
+ } else {
+ *mem = NULL;
+ *size = 0;
+ log_error("Couldn't mmap file %s: %m", path);
+ return -errno;
+ }
+}
+
+
+static int coredump_load_state(char ** argv, Coredump *s) {
+ int r;
+ const char * journal_group = "systemd-journal";
+ const char * preprocess_user = "nobody";
+ _cleanup_fclose_ FILE *f = NULL;
+
+ assert(s);
+
+ zero(*s);
+
+ s->journal_max_size = COREDUMP_MAX_DEFAULT;
+ s->file_max_size = 0;
+ s->preprocess_max_size = SIZE_MAX;
+ s->storage = STORAGE_VOLATILE;
+ s->file_fd = -1;
+ s->journal = 0;
+ s->core_user_unit = false;
+ s->preprocess_directory_path = strdup("/tmp");
+ s->preprocess_user = strdup("nobody");
+
+ s->core_pid = argv[ARG_PID];
+ s->core_uid = argv[ARG_UID];
+ s->core_gid = argv[ARG_GID];
+ s->core_signal = argv[ARG_SIGNAL];
+ s->core_comm = argv[ARG_COMM];
+ s->core_timestamp = argv[ARG_TIMESTAMP];
+
+ r = parse_pid(s->core_pid, &s->process_pid);
if (r < 0) {
log_set_target(LOG_TARGET_JOURNAL_OR_KMSG);
log_open();
-
log_error("Failed to parse PID.");
- goto finish;
+ return r;
}
- if (cg_pid_get_unit(pid, &t) >= 0) {
+ if (cg_pid_get_unit(s->process_pid, &s->core_unit) >= 0) {
+ char *t = NULL;
+ s->core_user_unit = false;
- if (streq(t, SPECIAL_JOURNALD_SERVICE)) {
- /* Make sure we don't make use of the journal,
- * if it's the journal which is crashing */
+ if(streq(t, SPECIAL_JOURNALD_SERVICE)) {
+ s->journal_core_dump = true;
log_set_target(LOG_TARGET_KMSG);
- log_open();
-
- r = divert_coredump();
- goto finish;
- }
-
- core_unit = strappend("COREDUMP_UNIT=", t);
- } else if (cg_pid_get_user_unit(pid, &t) >= 0)
- core_unit = strappend("COREDUMP_USER_UNIT=", t);
+ } else
+ log_set_target(LOG_TARGET_JOURNAL_OR_KMSG);
+ free(t);
+ } else {
+ log_set_target(LOG_TARGET_JOURNAL_OR_KMSG);
+ if (cg_pid_get_user_unit(s->process_pid, &s->core_unit) >= 0)
+ s->core_user_unit = true;
+ }
- if (core_unit)
- IOVEC_SET_STRING(iovec[j++], core_unit);
+ if (s->journal_core_dump) {
+ s->file_max_size = SIZE_MAX;
+ s->journal_max_size = 0;
+ if (! s->storage)
+ s->storage = STORAGE_VOLATILE;
+ log_info(SPECIAL_JOURNALD_SERVICE " failed. Dumping to directory");
+ }
- /* OK, now we know it's not the journal, hence make use of
- * it */
- log_set_target(LOG_TARGET_JOURNAL_OR_KMSG);
log_open();
- r = parse_uid(argv[ARG_UID], &uid);
+ r = parse_uid(argv[ARG_UID], &s->process_uid);
if (r < 0) {
log_error("Failed to parse UID.");
- goto finish;
+ return r;
}
- r = parse_gid(argv[ARG_GID], &gid);
+ r = parse_gid(argv[ARG_GID], &s->process_gid);
if (r < 0) {
log_error("Failed to parse GID.");
- goto finish;
+ return r;
}
- core_pid = strappend("COREDUMP_PID=", argv[ARG_PID]);
- if (core_pid)
- IOVEC_SET_STRING(iovec[j++], core_pid);
+ if (get_group_creds(&journal_group, &s->journal))
+ log_warning("Failed to get group %s creds", journal_group);
- core_uid = strappend("COREDUMP_UID=", argv[ARG_UID]);
- if (core_uid)
- IOVEC_SET_STRING(iovec[j++], core_uid);
+ s->preprocess_uid = s->process_uid;
+ s->preprocess_gid = s->process_gid;
- core_gid = strappend("COREDUMP_GID=", argv[ARG_GID]);
- if (core_gid)
- IOVEC_SET_STRING(iovec[j++], core_gid);
+ if (s->preprocess_user) {
+ preprocess_user = s->preprocess_user;
+ if (get_user_creds(&preprocess_user, &s->preprocess_uid, &s->preprocess_gid, NULL, NULL))
+ log_warning("Failed to get user %s creds", preprocess_user);
+ }
- core_signal = strappend("COREDUMP_SIGNAL=", argv[ARG_SIGNAL]);
- if (core_signal)
- IOVEC_SET_STRING(iovec[j++], core_signal);
+ f = fopen(COREDUMP_CONFIG, "re");
+ if (!f) {
+ if (errno == ENOENT)
+ return 0;
- core_comm = strappend("COREDUMP_COMM=", argv[ARG_COMM]);
- if (core_comm)
- IOVEC_SET_STRING(iovec[j++], core_comm);
+ log_warning("Failed to open configuration file " COREDUMP_CONFIG ": %m");
+ return -errno;
+ }
+
+ r = config_parse(NULL, COREDUMP_CONFIG, f, "Coredump\0", config_item_perf_lookup,
+ (void*) coredump_gperf_lookup, false, false, s);
+ if (r < 0)
+ log_warning("Failed to parse configuration file: %s", strerror(-r));
+
+ if (s->file_max_size) {
+ r = coredump_ensure_directory_exists(s);
+ if (r) {
+ log_error("Couldn't create coredump directory: %m");
+ return r;
+ }
+
+ s->file_fd = coredump_create_file(s, &s->file_path, false);
+ if (s->file_fd < 0) {
+ log_error("Couldn't create core dump file: %m");
+ return s->file_fd;
+ }
+ }
#ifdef HAVE_LOGIND
- if (sd_pid_get_session(pid, &t) >= 0) {
- core_session = strappend("COREDUMP_SESSION=", t);
- free(t);
+ sd_pid_get_session(s->process_pid, &s->core_session);
+#endif
+ get_process_exe(s->process_pid, &s->core_exe);
+ get_process_cmdline(s->process_pid, 0, false, &s->core_cmdline);
- if (core_session)
- IOVEC_SET_STRING(iovec[j++], core_session);
+ return 0;
+}
+
+static void coredump_release_state(Coredump * s) {
+ assert(s);
+
+ if (s->file_max_size) {
+ close(s->file_fd);
+ free(s->file_path);
}
+ if (s->processed_journal_data)
+ munmap(s->processed_journal_data,
+ s->processed_journal_data_size);
+ if (s->processed_file_data)
+ munmap(s->processed_file_data,
+ s->processed_file_data_size);
+#ifdef HAVE_LOGIND
+ free(s->core_session);
#endif
+ free(s->core_cmdline);
+ free(s->core_unit);
+ free(s->core_exe);
+ free(s->preprocess_user);
+ free(s->preprocess_journal);
+ free(s->preprocess_file);
+ free(s->preprocess_directory_path);
+}
- if (get_process_exe(pid, &t) >= 0) {
- core_exe = strappend("COREDUMP_EXE=", t);
- free(t);
+static int coredump_preprocess(Coredump *s) {
+ int r;
+
+ _cleanup_fclose_ FILE *tmp_file = NULL;
+ _cleanup_close_ int tmp_fd = -1, journal_tmp_fd = -1, file_tmp_fd = -1;
+ _cleanup_free_ char *tmp_filename = NULL, *journal_tmp_filename = NULL,
+ *file_tmp_filename = NULL, *preprocess_journal = NULL,
+ *preprocess_file = NULL;
+
+ struct stat stat;
+
+ int child;
+ int status;
+
+ assert(s);
+
+ tmp_fd = coredump_create_file(s, &tmp_filename, true);
+ if (tmp_fd < 0) {
+ log_error("Couldn't create temporary storage file: %m");
+ return -errno;
+ }
+
+ if (fchown(tmp_fd, s->preprocess_uid, s->preprocess_gid))
+ return -errno;
+
+ if (s->preprocess_file) {
+ file_tmp_fd = coredump_create_file(s, &file_tmp_filename, true);
+ if (file_tmp_fd < 0)
+ return file_tmp_fd;
+ if (fchmod(file_tmp_fd, 0600))
+ return -errno;
+ if (fchown(file_tmp_fd, s->preprocess_uid, s->preprocess_gid))
+ return -errno;
+ close(file_tmp_fd);
+ file_tmp_fd = -1;
+ }
+
+ if (s->preprocess_journal) {
+ journal_tmp_fd = coredump_create_file(s, &journal_tmp_filename, true);
+ if (journal_tmp_fd < 0)
+ return file_tmp_fd;
+ if (fchmod(journal_tmp_fd, 0600))
+ return -errno;
+ if (fchown(journal_tmp_fd, s->preprocess_uid, s->preprocess_gid))
+ return -errno;
+ close(journal_tmp_fd);
+ journal_tmp_fd = -1;
+ }
+
+ child = fork();
+ if (child == -1)
+ return -errno;
+
+ if (child) {
+ static const char COREDUMP[] = "COREDUMP=";
+ int rj, rf;
+ int header_size = (strlen(COREDUMP)/(sysconf(_SC_PAGE_SIZE)) + 1) * sysconf(_SC_PAGE_SIZE);
+
+ if (waitpid(child, &status, 0) == -1)
+ return -errno;
+ if (! (WIFEXITED(status) && WEXITSTATUS(status) == 0))
+ return -1;
+
+ rj = coredump_mmap_file(s->preprocess_journal ? journal_tmp_filename : tmp_filename,
+ NULL,
+ &s->processed_journal_data,
+ &s->processed_journal_data_size);
+
+ if (!rj) {
+ /* Ok. sd_journal_sendv doesn't support scatter/gatter.
+ * Let's workaround it with dark mmap magic. If you know how to fix that without
+ * moving unknown size of data - let me know */
+
+ s->journal_message_header = mmap(s->processed_journal_data-header_size,
+ header_size,
+ PROT_WRITE | PROT_READ,
+ MAP_ANONYMOUS | MAP_PRIVATE,
+ -1,
+ 0);
+
+ if (s->journal_message_header == MAP_FAILED) {
+ r = -errno;
+ log_error("Couldn't create padding for journal output: %m");
+ return r;
+ }
+
+ assert(s->processed_journal_data - s->journal_message_header == header_size);
+ memcpy(s->processed_journal_data-strlen(COREDUMP), COREDUMP, strlen(COREDUMP));
+ }
+
+ rf = coredump_mmap_file(s->preprocess_file ? file_tmp_filename : tmp_filename,
+ NULL,
+ &s->processed_file_data,
+ &s->processed_file_data_size);
+
+ unlink(tmp_filename);
+
+ if (s->preprocess_journal)
+ unlink(journal_tmp_filename);
+
+ if (s->preprocess_file)
+ unlink(file_tmp_filename);
+
+ return - (rj && rf);
+ }
+
+ coredump_drop_creds(s->preprocess_uid, s->preprocess_gid);
+
+ tmp_file = fdopen(tmp_fd, "rew+");
+ if (! tmp_file)
+ return -errno;
+
+ r = coredump_stdio_to_file(tmp_file, s->preprocess_max_size);
+ if (r)
+ return r;
+
+ r = fstat(fileno(tmp_file), &stat);
+ if (r)
+ return -errno;
+
+ fflush(tmp_file);
+
+ if (s->preprocess_journal) {
+ preprocess_journal = coredump_printf(s, tmp_filename, journal_tmp_filename, s->preprocess_journal);
+ unlink(journal_tmp_filename);
+ if (system(preprocess_journal))
+ unlink(journal_tmp_filename);
+ }
+
+ if (s->preprocess_file) {
+ preprocess_file = coredump_printf(s, tmp_filename, file_tmp_filename, s->preprocess_file);
+ unlink(file_tmp_filename);
+ if (system(preprocess_file))
+ unlink(file_tmp_filename);
+ }
+
+ exit(0);
+}
+
+static int coredump_store_memory_to_fd(const char * memory,
+ size_t memory_size,
+ int fd,
+ const char * fd_path,
+ size_t max_size,
+ char ** message,
+ size_t *message_size) {
+ static const char COREDUMP[] = "COREDUMP_FILE=";
+ _cleanup_fclose_ FILE *core = NULL;
+ char * m = NULL;
+
+ int r;
+
+ assert_se(fd >= 0);
+ assert_se(message);
+ assert_se(message_size);
+
+ *message = NULL;
+ memory_size = MIN(memory_size, max_size);
+
+ r = ftruncate(fd, memory_size);
+ if (r) {
+ log_error("Couldn't truncate backed file: %m");
+ close(fd);
+ return -errno;
+ }
+
+ m = mmap(NULL, memory_size, PROT_WRITE, MAP_SHARED, fd, 0);
+ if (m == MAP_FAILED) {
+ log_error("Couldn't mmap backed file: %m");
+ close(fd);
+ return -errno;
+ }
+
+ memcpy(m, memory, memory_size);
+ munmap(m, memory_size);
+ close(fd);
+
+ assert(strrchr(fd_path, '/'));
+
+ *message = strjoin(COREDUMP, strrchr(fd_path, '/') + 1, NULL);
+
+ if (! *message) {
+ *message = NULL;
+ return log_oom();
+ }
+
+ *message_size = strlen(*message);
+
+ return 0;
+}
+
+static int coredump_submit_message(const Coredump * s) {
+ int r, j = 0;
+ _cleanup_free_ char *p = NULL;
+ struct iovec iovec[16];
+ _cleanup_free_ char *core_pid = NULL, *core_uid = NULL, *core_gid = NULL, *core_signal = NULL,
+ *core_timestamp = NULL, *core_comm = NULL, *core_exe = NULL, *core_unit = NULL,
+ *core_session = NULL, *core_message = NULL, *core_cmdline = NULL;
+ char *journal_message = NULL, *file_message = NULL;
+ size_t journal_message_size = 0, file_message_size = 0;
+
+ if (s->core_unit) {
+ if (s->core_user_unit)
+ core_unit = strappend("COREDUMP_USER_UNIT=", s->core_unit);
+ else
+ core_unit = strappend("COREDUMP_UNIT=", s->core_unit);
+
+ if (core_unit)
+ IOVEC_SET_STRING(iovec[j++], core_unit);
+ }
+
+ if (s->core_pid) {
+ core_pid = strappend("COREDUMP_PID=", s->core_pid);
+ if (core_pid)
+ IOVEC_SET_STRING(iovec[j++], core_pid);
+ }
+
+ if (s->core_uid) {
+ core_uid = strappend("COREDUMP_UID=", s->core_uid);
+ if (core_uid)
+ IOVEC_SET_STRING(iovec[j++], core_uid);
+ }
+
+ if (s->core_gid) {
+ core_gid = strappend("COREDUMP_GID=", s->core_gid);
+ if (core_gid)
+ IOVEC_SET_STRING(iovec[j++], core_gid);
+ }
+
+ if (s->core_signal) {
+ core_signal = strappend("COREDUMP_SIGNAL=", s->core_signal);
+ if (core_signal)
+ IOVEC_SET_STRING(iovec[j++], core_signal);
+ }
+
+ if (s->core_comm) {
+ core_comm = strappend("COREDUMP_COMM=", s->core_comm);
+ if (core_comm)
+ IOVEC_SET_STRING(iovec[j++], core_comm);
+ }
+
+#ifdef HAVE_LOGIND
+ if (s->core_session) {
+ core_session = strappend("COREDUMP_SESSION=", s->core_session);
+ if (core_session)
+ IOVEC_SET_STRING(iovec[j++], core_session);
+ }
+
+#endif
+ if (s->core_exe) {
+ core_exe = strappend("COREDUMP_EXE=", s->core_exe);
if (core_exe)
IOVEC_SET_STRING(iovec[j++], core_exe);
}
- if (get_process_cmdline(pid, 0, false, &t) >= 0) {
- core_cmdline = strappend("COREDUMP_CMDLINE=", t);
- free(t);
-
+ if (s->core_cmdline) {
+ core_cmdline = strappend("COREDUMP_CMDLINE=", s->core_cmdline);
if (core_cmdline)
IOVEC_SET_STRING(iovec[j++], core_cmdline);
}
- core_timestamp = strjoin("COREDUMP_TIMESTAMP=", argv[ARG_TIMESTAMP], "000000", NULL);
- if (core_timestamp)
- IOVEC_SET_STRING(iovec[j++], core_timestamp);
+ if (s->core_timestamp) {
+ core_timestamp = strjoin("COREDUMP_TIMESTAMP=", s->core_timestamp, "000000", NULL);
+ if (core_timestamp)
+ IOVEC_SET_STRING(iovec[j++], core_timestamp);
+ }
- IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1");
IOVEC_SET_STRING(iovec[j++], "PRIORITY=2");
- core_message = strjoin("MESSAGE=Process ", argv[ARG_PID], " (", argv[ARG_COMM], ") dumped core.", NULL);
+ core_message = strjoin("MESSAGE=Process ", s->core_pid, " (", s->core_exe, ") dumped core.", NULL);
if (core_message)
IOVEC_SET_STRING(iovec[j++], core_message);
- /* Now, let's drop privileges to become the user who owns the
- * segfaulted process and allocate the coredump memory under
- * his uid. This also ensures that the credentials journald
- * will see are the ones of the coredumping user, thus making
- * sure the user himself gets access to the core dump. */
+ if (s->journal_max_size && s->processed_journal_data) {
+ iovec[j].iov_len = MIN(s->journal_max_size,
+ s->processed_journal_data_size + 9);
+ iovec[j].iov_base = s->processed_journal_data - 9;
- if (setresgid(gid, gid, gid) < 0 ||
- setresuid(uid, uid, uid) < 0) {
- log_error("Failed to drop privileges: %m");
- r = -errno;
- goto finish;
+ j ++;
}
- coredump_bufsize = COREDUMP_MIN_START;
- coredump_data = malloc(coredump_bufsize);
- if (!coredump_data) {
- r = log_oom();
- goto finish;
- }
-
- memcpy(coredump_data, "COREDUMP=", 9);
- coredump_size = 9;
-
- for (;;) {
- n = loop_read(STDIN_FILENO, coredump_data + coredump_size,
- coredump_bufsize - coredump_size, false);
- if (n < 0) {
- log_error("Failed to read core dump data: %s", strerror(-n));
- r = (int) n;
- goto finish;
- } else if (n == 0)
- break;
-
- coredump_size += n;
- if (!GREEDY_REALLOC(coredump_data, coredump_bufsize, coredump_size + 1)) {
- r = log_oom();
- goto finish;
+ if (s->file_max_size && s->processed_file_data) {
+ r = coredump_store_memory_to_fd(s->processed_file_data,
+ s->processed_file_data_size,
+ s->file_fd, s->file_path,
+ s->file_max_size,
+ &file_message, &file_message_size);
+
+ if (! r) {
+ iovec[j].iov_len = file_message_size;
+ iovec[j].iov_base = file_message;
+
+ j ++;
+ } else {
+ if (r == -EACCES)
+ log_warning("User uid=%s gid=%s is not allowed to store coredump to file",
+ s->core_uid, s->core_gid);
+ else {
+ log_error("Failed to store coredump to file: %s", strerror(-r));
+ goto finish;
+ }
}
}
- iovec[j].iov_base = coredump_data;
- iovec[j].iov_len = coredump_size;
- j++;
-
r = sd_journal_sendv(iovec, j);
if (r < 0)
log_error("Failed to send coredump: %s", strerror(-r));
-finish:
+ finish:
+ if (journal_message)
+ coredump_cleanup_mem(journal_message, journal_message_size);
+
+ if (file_message)
+ coredump_cleanup_str(file_message, file_message_size);
+
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}
+
+int main(int argc, char ** argv) {
+ Coredump s;
+ int r = 0;
+
+ prctl(PR_SET_DUMPABLE, 0);
+
+ if (argc != _ARG_MAX) {
+ log_error("Invalid number of arguments passed from kernel.");
+ return -EINVAL;
+ }
+
+ r = coredump_load_state(argv, &s);
+ if (r) {
+ log_error("State loading failed");
+ return r;
+ }
+
+ r = coredump_preprocess(&s);
+ if (r) {
+ log_error("Coredump preprocessing failed");
+ return r;
+ }
+
+ r = coredump_drop_creds(s.process_uid, s.process_gid);
+ if (r) {
+ coredump_release_state(&s);
+ return r;
+ }
+
+ r = coredump_submit_message(&s);
+
+ coredump_release_state(&s);
+ return r;
+
+}
diff --git a/src/journal/coredump.conf b/src/journal/coredump.conf
new file mode 100644
index 0000000..001a2b5
--- /dev/null
+++ b/src/journal/coredump.conf
@@ -0,0 +1,18 @@
+# This file is part of systemd.
+#
+# 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.
+#
+# See coredump.conf(5) for details
+
+[Coredump]
+#MaxJournalCoreSize=25M
+#MaxFileCoreSize=0
+#FileStorage=volatile
+#JournalStoragePreprocess=gdb --quiet --nx --batch -ex "thread apply all bt" --core %c %e
+#PreprocessMaxSize=INT_MAX
+#PreprocessUser=nobody
+#PreprocessFile=
+#PreprocessJournal=
diff --git a/src/journal/coredump.h b/src/journal/coredump.h
new file mode 100644
index 0000000..ec7c388
--- /dev/null
+++ b/src/journal/coredump.h
@@ -0,0 +1,89 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012-2013 Lennart Poettering
+ Oleksii Shevchuk
+
+ 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 "conf-parser.h"
+
+#define COREDUMP_MESSAGE_ID SD_ID128_MAKE(fc,2e,22,bc,6e,e6,47,b6,b9,07,29,ab,34,a2,50,b1
+
+typedef enum Storage {
+ STORAGE_VOLATILE,
+ STORAGE_PERSISTENT,
+ _STORAGE_MAX,
+ _STORAGE_INVALID = -1
+} Storage;
+
+typedef struct Coredump {
+ size_t journal_max_size;
+ size_t file_max_size;
+ size_t preprocess_max_size;
+
+ bool journal_core_dump;
+
+ Storage storage;
+
+ int file_fd;
+ char *file_path;
+
+ gid_t journal;
+
+ pid_t process_pid;
+ uid_t process_uid;
+ gid_t process_gid;
+
+ bool core_user_unit;
+ char *core_pid;
+ char *core_uid;
+ char *core_gid;
+ char *core_signal;
+ char *core_timestamp;
+ char *core_comm;
+ char *core_exe;
+ char *core_unit;
+#ifdef HAVE_LOGIND
+ char *core_session;
+#endif
+ char *core_cmdline;
+
+ uid_t preprocess_uid;
+ gid_t preprocess_gid;
+
+ char *preprocess_user;
+
+ char *preprocess_journal;
+ char *preprocess_file;
+ char *preprocess_directory_path;
+
+ char *journal_message_header;
+ char *processed_journal_data;
+ size_t processed_journal_data_size;
+ char *processed_file_data;
+ size_t processed_file_data_size;
+} Coredump;
+
+const struct ConfigPerfItem* coredump_gperf_lookup(const char *key, unsigned length);
+
+int config_parse_storage(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+
+const char *storage_to_string(Storage s);
+Storage storage_from_string(const char *s);
diff --git a/src/journal/journald-server.h b/src/journal/journald-server.h
index 129f7e8..31ff6b4 100644
--- a/src/journal/journald-server.h
+++ b/src/journal/journald-server.h
@@ -125,7 +125,7 @@ typedef struct Server {
bool sync_scheduled;
} Server;
-#define N_IOVEC_META_FIELDS 17
+#define N_IOVEC_META_FIELDS 19
#define N_IOVEC_KERNEL_FIELDS 64
#define N_IOVEC_UDEV_FIELDS 32
--
1.8.1.2
More information about the systemd-devel
mailing list