[systemd-devel] [PATCH] [RFC] Optionally save core dumps as plain files

Oleksii Shevchuk alxchk at gmail.com
Thu Feb 14 14:37:20 PST 2013


Introduce configuration file: /etc/systemd/coredump.conf

[Coredump]
StoreIntoJournal=yes/no
StoreIntoFile=yes/no
MaxCoreSize=size-in-bytes

If StoreIntoFile=yes, then coredump will be saved to
temporary-formated file at /var/log/coredump/MACHINE-ID/COMM-XXXXXX

If MaxCoreSize specified, then core will be truncated to that size
---
 Makefile.am                      |  10 +-
 src/core/manager.c               |   1 +
 src/journal/coredump-gperf.gperf |  20 ++
 src/journal/coredump.c           | 452 ++++++++++++++++++++++++++++++++-------
 src/journal/coredump.h           |  35 +++
 src/journal/journald-server.h    |   2 +-
 6 files changed, 441 insertions(+), 79 deletions(-)
 create mode 100644 src/journal/coredump-gperf.gperf
 create mode 100644 src/journal/coredump.h

diff --git a/Makefile.am b/Makefile.am
index 10934eb..3725cbe 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2628,10 +2628,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
@@ -2675,10 +2677,12 @@ EXTRA_DIST += \
 # ------------------------------------------------------------------------------
 if ENABLE_COREDUMP
 systemd_coredump_SOURCES = \
-	src/journal/coredump.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/src/core/manager.c b/src/core/manager.c
index 28f169d..c9f410e 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..d64533c
--- /dev/null
+++ b/src/journal/coredump-gperf.gperf
@@ -0,0 +1,20 @@
+%{
+#include <stddef.h>
+#include <sys/socket.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.StoreIntoJournal, config_parse_bool,       0, offsetof(Coredump, to_journal)
+Coredump.StoreIntoFile,    config_parse_bool,       0, offsetof(Coredump, to_file)
+Coredump.MaxCoreSize,      config_parse_bytes_size, 0, offsetof(Coredump, max_size)
diff --git a/src/journal/coredump.c b/src/journal/coredump.c
index 021b4c6..f848fc6 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 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,6 +23,9 @@
 #include <errno.h>
 #include <unistd.h>
 #include <stdio.h>
+#include <stdint.h>
+#include <fcntl.h>
+#include <sys/mman.h>
 #include <sys/prctl.h>
 
 #include <systemd/sd-journal.h>
@@ -35,9 +39,13 @@
 #include "macro.h"
 #include "mkdir.h"
 #include "special.h"
+#include "sd-id128.h"
+#include "coredump.h"
+#include "conf-parser.h"
 #include "cgroup-util.h"
 
-#define COREDUMP_MAX (24*1024*1024)
+#define COREDUMP_MAX_DEFAULT ( 24 * 1024 * 1024 )
+#define COREDUMP_CONFIG "/etc/systemd/coredump.conf"
 
 enum {
         ARG_PID = 1,
@@ -49,26 +57,51 @@ enum {
         _ARG_MAX
 };
 
-static int divert_coredump(void) {
-        _cleanup_fclose_ FILE *f = NULL;
+static int coredump_drop_creds(uid_t uid, uid_t gid)
+{
+        if (setresgid(gid, gid, gid) < 0 ||
+            setresuid(uid, uid, uid) < 0) {
+                log_error("Failed to drop privileges: %m");
+                return -errno;
+        }
 
-        log_info("Detected coredump of the journal daemon itself, diverting coredump to /var/lib/systemd/coredump/.");
+        umask(0377);
 
-        mkdir_p_label("/var/lib/systemd/coredump", 0755);
+        return 0;
+}
+
+static int coredump_parse_config_file(Coredump *s) {
+        static const char *fn = COREDUMP_CONFIG;
+        FILE _cleanup_fclose_ *f = NULL;
+        int r;
 
-        f = fopen("/var/lib/systemd/coredump/core.systemd-journald", "we");
+        assert(s);
+
+        f = fopen(fn, "re");
         if (!f) {
-                log_error("Failed to create coredump file: %m");
+                if (errno == ENOENT)
+                        return 0;
+
+                log_warning("Failed to open configuration file %s: %m", fn);
                 return -errno;
         }
 
+        r = config_parse(fn, f, "Coredump\0", config_item_perf_lookup,
+                         (void*) coredump_gperf_lookup, false, s);
+        if (r < 0)
+                log_warning("Failed to parse configuration file: %s", strerror(-r));
+
+        return r;
+}
+
+static int coredump_stdio_to_file(FILE *corefile, size_t max_size) {
         for (;;) {
                 uint8_t buffer[4096];
                 size_t l, q;
 
                 l = fread(buffer, 1, sizeof(buffer), stdin);
                 if (l <= 0) {
-                        if (ferror(f)) {
+                        if (ferror(corefile)) {
                                 log_error("Failed to read coredump: %m");
                                 return -errno;
                         }
@@ -76,16 +109,21 @@ static int divert_coredump(void) {
                         break;
                 }
 
-                q = fwrite(buffer, 1, l, f);
+                q = fwrite(buffer, 1, l > max_size ? max_size : l, corefile);
                 if (q != l) {
-                        log_error("Failed to write coredump: %m");
-                        return -errno;
+                        if (l > max_size) {
+                                log_warning("Coredump was truncated");
+                                break;
+                        } else {
+                                log_error("Failed to write coredump: %m");
+                                return -errno;
+                        }
                 }
         }
 
-        fflush(f);
+        fflush(corefile);
 
-        if (ferror(f)) {
+        if (ferror(corefile)) {
                 log_error("Failed to write coredump: %m");
                 return -errno;
         }
@@ -93,24 +131,236 @@ static int divert_coredump(void) {
         return 0;
 }
 
-int main(int argc, char* argv[]) {
-        int r, j = 0;
-        _cleanup_free_ char *p = NULL;
-        ssize_t n;
-        pid_t pid;
-        uid_t uid;
-        gid_t gid;
-        struct iovec iovec[14];
+static int coredump_create_file(const char * ucomm, char ** name,
+                                   bool private) {
+        _cleanup_free_ char *template = NULL, *comm = NULL, *directory = NULL;
+        int fd, r;
+
+        assert_se(ucomm);
+        assert_se(name);
+
+        if (! private) {
+                sd_id128_t machineid;
+                char buffer[33];
+
+                r = sd_id128_get_machine(&machineid);
+                if (r)
+                        return r;
+
+                directory = strjoin(COREDUMP_DIRECTORY,
+                                    "/",
+                                    sd_id128_to_string(machineid, buffer),
+                                    NULL);
+                if (! directory)
+                        return log_oom();
+        } else {
+                directory = strdup("/tmp");
+                if (! directory)
+                        return log_oom();
+        }
+
+        comm = xescape(ucomm, "/ ");
+        if (! comm)
+                return log_oom();
+
+        template = strjoin(directory, "/", comm, "-XXXXXX", NULL);
+        if (! template)
+                return log_oom();
+
+        fd = mkostemp(template, O_CREAT | O_EXCL | O_CLOEXEC | O_APPEND | O_RDWR);
+        if (fd < 0) {
+                log_error("Couldn't create temporary file %s: %m", template);
+                return -errno;
+        }
+
+        if (private)
+                unlink(template);
+
+        *name = strdup(template + strlen(directory) + 1);
+        if (! *name) {
+                close(fd);
+                return log_oom();
+        }
+
+        return fd;
+}
+
+static void coredump_cleanup_str(char ** mem, size_t * size) {
+        assert_se(mem);
+        assert_se(size);
+
+        (void)size;
+        free(*mem);
+}
+
+static int coredump_store_to_file(const char * ucomm,
+                                  size_t max_size,
+                                  char ** message,
+                                  size_t *message_size) {
+        _cleanup_fclose_ FILE *core = NULL;
+        _cleanup_free_ char *t = NULL;
+
+        int r;
+
+        assert_se(ucomm);
+        assert_se(message);
+        assert_se(message_size);
+
+        *message = NULL;
+        max_size = max_size ? max_size : SIZE_MAX;
+
+        r = coredump_create_file(ucomm, &t, false);
+        if (r < 0)
+                return r;
+
+        core = fdopen(r, "rew+");
+        if (! core) {
+                log_error("Couldn't resue fd: %m");
+                close(r);
+                return -errno;
+        }
+
+        r = coredump_stdio_to_file(core, max_size);
+        if (r)
+                return r;
+
+        *message = strjoin("COREDUMP_FILE=", t, NULL);
+
+        if (! *message) {
+                *message = NULL;
+                return log_oom();
+        }
+
+        *message_size = strlen(*message);
+
+        return 0;
+}
+
+static void coredump_cleanup_mem(char ** mem, size_t * size) {
+        assert_se(mem);
+        assert_se(size);
+        munmap(*mem, *size);
+}
+
+static int coredump_store_to_mmaped_memory(const char * ucomm,
+                                           size_t max_size,
+                                           char ** message,
+                                           size_t *message_size) {
+        int r;
+
+        _cleanup_fclose_ FILE *tmp = NULL;
+        _cleanup_free_ char *t = NULL;
+        struct stat stat;
+
+        assert_se(ucomm);
+        assert_se(message);
+        assert_se(message_size);
+
+        r = coredump_create_file(ucomm, &t, true);
+        if (r < 0)
+                return r;
+
+        tmp = fdopen(r, "rew+");
+        if (! tmp) {
+                log_error("Couldn't reuse fd: %m");
+                close(r);
+                return -errno;
+        }
+
+        r = fwrite("COREDUMP=", 9, 1, tmp);
+        if (r != 1) {
+                log_error("Couldn't write label to temporary file (%d/%d): %m", r, fileno(tmp));
+                return errno ? -errno : -1;
+        }
+
+        r = coredump_stdio_to_file(tmp, max_size);
+        if (r)
+                return r;
+
+        r = fstat(fileno(tmp), &stat);
+        if (r)
+                return -errno;
+
+        *message = mmap(NULL, stat.st_size, PROT_READ, MAP_PRIVATE,
+                        fileno(tmp), 0);
+        if (*message == MAP_FAILED)
+                return -errno;
+
+        *message_size = stat.st_size;
+
+        return 0;
+}
+
+static int coredump_store_memory_to_file(const char * memory,
+                                         size_t memory_size,
+                                         const char * ucomm,
+                                         size_t max_size,
+                                         char ** message,
+                                         size_t *message_size) {
+        _cleanup_fclose_ FILE *core = NULL;
+        _cleanup_free_ char *t = NULL;
+        char * m = NULL;
+
+        int fd, r;
+
+        assert_se(ucomm);
+        assert_se(message);
+        assert_se(message_size);
+
+        *message = NULL;
+
+        fd = coredump_create_file(ucomm, &t, false);
+        if (fd < 0)
+                return fd;
+
+        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);
+
+        *message = strjoin("COREDUMP_FILE=", t, NULL);
+
+        if (! *message) {
+                *message = NULL;
+                return log_oom();
+        }
+
+        *message_size = strlen(*message);
+
+        return 0;
+}
+
+static int coredump_submit_message(int argc, const char * const * argv,
+                                   bool to_journal, bool to_file, size_t max_size) {
+         int r, j = 0;
+         _cleanup_free_ char *p = NULL;
+         pid_t pid;
+         uid_t uid;
+         gid_t gid;
+         struct iovec iovec[15];
         _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, *t = NULL;
+                *core_session = NULL, *core_message = NULL, *core_cmdline = NULL;
 
-        prctl(PR_SET_DUMPABLE, 0);
+        char *journal_message = NULL, *file_message = NULL, *t = NULL;
+        size_t journal_message_size, file_message_size;
 
-        if (argc != _ARG_MAX) {
-                log_set_target(LOG_TARGET_JOURNAL_OR_KMSG);
-                log_open();
+        assert_se(argv);
 
+        if (argc != _ARG_MAX) {
                 log_error("Invalid number of arguments passed from kernel.");
                 r = -EINVAL;
                 goto finish;
@@ -118,36 +368,19 @@ int main(int argc, char* argv[]) {
 
         r = parse_pid(argv[ARG_PID], &pid);
         if (r < 0) {
-                log_set_target(LOG_TARGET_JOURNAL_OR_KMSG);
-                log_open();
-
                 log_error("Failed to parse PID.");
                 goto finish;
         }
 
-        if (cg_pid_get_unit(pid, &t) >= 0) {
-
-                if (streq(t, SPECIAL_JOURNALD_SERVICE)) {
-                        /* Make sure we don't make use of the journal,
-                         * if it's the journal which is crashing */
-                        log_set_target(LOG_TARGET_KMSG);
-                        log_open();
-
-                        r = divert_coredump();
-                        goto finish;
-                }
-
+        if (cg_pid_get_unit(pid, &t) >= 0)
                 core_unit = strappend("COREDUMP_UNIT=", t);
-        } else if (cg_pid_get_user_unit(pid, &t) >= 0)
+        else if (cg_pid_get_user_unit(pid, &t) >= 0)
                 core_unit = strappend("COREDUMP_USER_UNIT=", t);
 
-        if (core_unit)
+        if (core_unit) {
                 IOVEC_SET_STRING(iovec[j++], core_unit);
-
-        /* OK, now we know it's not the journal, hence make use of
-         * it */
-        log_set_target(LOG_TARGET_JOURNAL_OR_KMSG);
-        log_open();
+                free(t);
+        }
 
         r = parse_uid(argv[ARG_UID], &uid);
         if (r < 0) {
@@ -191,7 +424,6 @@ int main(int argc, char* argv[]) {
         }
 
 #endif
-
         if (get_process_exe(pid, &t) >= 0) {
                 core_exe = strappend("COREDUMP_EXE=", t);
                 free(t);
@@ -202,17 +434,15 @@ int main(int argc, char* argv[]) {
 
         if (get_process_cmdline(pid, 0, false, &t) >= 0) {
                 core_cmdline = strappend("COREDUMP_CMDLINE=", t);
-                free(t);
-
                 if (core_cmdline)
                         IOVEC_SET_STRING(iovec[j++], core_cmdline);
+                free(t);
         }
 
         core_timestamp = strjoin("COREDUMP_TIMESTAMP=", argv[ARG_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);
@@ -224,37 +454,109 @@ int main(int argc, char* argv[]) {
          * 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. */
+        r = coredump_drop_creds(uid, gid);
+        if (r)
+                return r;
+
+        if (to_journal) {
+                r = coredump_store_to_mmaped_memory(argv[ARG_COMM], max_size,
+                                                    &journal_message,
+                                                    &journal_message_size);
+                if (r) {
+                        log_error("Failed to store coredump to journal: %s", strerror(-r));
+                        goto finish;
+                }
 
-        if (setresgid(gid, gid, gid) < 0 ||
-            setresuid(uid, uid, uid) < 0) {
-                log_error("Failed to drop privileges: %m");
-                r = -errno;
-                goto finish;
-        }
-
-        p = malloc(9 + COREDUMP_MAX);
-        if (!p) {
-                r = log_oom();
-                goto finish;
+                iovec[j].iov_len = journal_message_size;
+                iovec[j].iov_base = journal_message;
+                j ++;
         }
 
-        memcpy(p, "COREDUMP=", 9);
+        if (to_file) {
+                if (to_journal)
+                        r = coredump_store_memory_to_file(journal_message + 9, journal_message_size - 9,
+                                                          argv[ARG_COMM], max_size,
+                                                          &file_message, &file_message_size);
+                else
+                        r = coredump_store_to_file(argv[ARG_COMM], max_size,
+                                                   &file_message, &file_message_size);
+
+                if (r) {
+                        log_error("Failed to store coredump to file: %s", strerror(-r));
+                        goto finish;
+                }
 
-        n = loop_read(STDIN_FILENO, p + 9, COREDUMP_MAX, false);
-        if (n < 0) {
-                log_error("Failed to read core dump data: %s", strerror(-n));
-                r = (int) n;
-                goto finish;
+                iovec[j].iov_len = file_message_size;
+                iovec[j].iov_base = file_message;
+                j ++;
         }
 
-        iovec[j].iov_base = p;
-        iovec[j].iov_len = 9 + n;
-        j++;
-
         r = sd_journal_sendv(iovec, j);
         if (r < 0)
                 log_error("Failed to send coredump: %s", strerror(-r));
 
-finish:
+        if (journal_message)
+                coredump_cleanup_mem(&journal_message, &journal_message_size);
+
+        if (file_message)
+                coredump_cleanup_str(&file_message, &file_message_size);
+
+ finish:
         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+
+ }
+
+int main(int argc, const char * const * argv)
+{
+        Coredump coredump_config;
+        bool journal = false;
+        char *t;
+        int r = 0;
+        pid_t pid;
+
+        prctl(PR_SET_DUMPABLE, 0);
+
+        r = parse_pid(argv[ARG_PID], &pid);
+        if (r < 0) {
+                 log_set_target(LOG_TARGET_JOURNAL_OR_KMSG);
+                 log_open();
+                 log_error("Failed to parse PID.");
+                 return r;
+        }
+
+        if (cg_pid_get_unit(pid, &t) >= 0) {
+                if(streq(t, SPECIAL_JOURNALD_SERVICE)) {
+                        journal = true;
+                        log_set_target(LOG_TARGET_KMSG);
+                } else {
+                        log_set_target(LOG_TARGET_JOURNAL_OR_KMSG);
+                }
+                free(t);
+        } else {
+                log_set_target(LOG_TARGET_JOURNAL_OR_KMSG);
+        }
+
+        log_open();
+
+        coredump_config.to_file = false;
+        coredump_config.to_journal = true;
+        coredump_config.max_size = COREDUMP_MAX_DEFAULT;
+
+        r = coredump_parse_config_file(&coredump_config);
+        if (r) {
+                log_warning("Couldn't read " COREDUMP_CONFIG ": %m");
+        }
+
+        if (journal) {
+                coredump_config.to_journal = false;
+                coredump_config.max_size = 0;
+                log_info(SPECIAL_JOURNALD_SERVICE " failed. Dumping to directory");
+        }
+
+        r = coredump_submit_message(argc, argv,
+                                    coredump_config.to_journal,
+                                    coredump_config.to_file,
+                                    coredump_config.max_size);
+
+        return r;
 }
diff --git a/src/journal/coredump.h b/src/journal/coredump.h
new file mode 100644
index 0000000..d7f8deb
--- /dev/null
+++ b/src/journal/coredump.h
@@ -0,0 +1,35 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013
+
+  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_MESSAGEID "fc2e22bc6ee647b6b90729ab34a250b1"
+#define COREDUMP_DIRECTORY "/var/log/coredump"
+
+typedef struct Coredump {
+        size_t max_size;
+        bool to_journal;
+        bool to_file;
+} Coredump;
+
+const struct ConfigPerfItem* coredump_gperf_lookup(const char *key, unsigned length);
diff --git a/src/journal/journald-server.h b/src/journal/journald-server.h
index 9f50a29..3d05925 100644
--- a/src/journal/journald-server.h
+++ b/src/journal/journald-server.h
@@ -121,7 +121,7 @@ typedef struct Server {
         struct udev *udev;
 } Server;
 
-#define N_IOVEC_META_FIELDS 17
+#define N_IOVEC_META_FIELDS 18
 #define N_IOVEC_KERNEL_FIELDS 64
 #define N_IOVEC_UDEV_FIELDS 32
 
-- 
1.8.1.2



More information about the systemd-devel mailing list