[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