chiark / gitweb /
util: replace close_nointr_nofail() by a more useful safe_close()
[elogind.git] / src / journal / coredump.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2012 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <unistd.h>
24 #include <stdio.h>
25 #include <sys/prctl.h>
26
27 #include <systemd/sd-journal.h>
28
29 #ifdef HAVE_LOGIND
30 #include <systemd/sd-login.h>
31 #endif
32
33 #include "log.h"
34 #include "util.h"
35 #include "macro.h"
36 #include "mkdir.h"
37 #include "special.h"
38 #include "cgroup-util.h"
39 #include "journald-native.h"
40
41 /* Few programs have less than 3MiB resident */
42 #define COREDUMP_MIN_START (3*1024*1024)
43 /* Make sure to not make this larger than the maximum journal entry
44  * size. See ENTRY_SIZE_MAX in journald-native.c. */
45 #define COREDUMP_MAX (767*1024*1024)
46 assert_cc(COREDUMP_MAX <= ENTRY_SIZE_MAX);
47
48 enum {
49         ARG_PID = 1,
50         ARG_UID,
51         ARG_GID,
52         ARG_SIGNAL,
53         ARG_TIMESTAMP,
54         ARG_COMM,
55         _ARG_MAX
56 };
57
58 static int divert_coredump(void) {
59         _cleanup_fclose_ FILE *f = NULL;
60
61         log_info("Detected coredump of the journal daemon itself, diverting coredump to /var/lib/systemd/coredump/.");
62
63         mkdir_p_label("/var/lib/systemd/coredump", 0755);
64
65         f = fopen("/var/lib/systemd/coredump/core.systemd-journald", "we");
66         if (!f) {
67                 log_error("Failed to create coredump file: %m");
68                 return -errno;
69         }
70
71         for (;;) {
72                 uint8_t buffer[4096];
73                 size_t l, q;
74
75                 l = fread(buffer, 1, sizeof(buffer), stdin);
76                 if (l <= 0) {
77                         if (ferror(f)) {
78                                 log_error("Failed to read coredump: %m");
79                                 return -errno;
80                         }
81
82                         break;
83                 }
84
85                 q = fwrite(buffer, 1, l, f);
86                 if (q != l) {
87                         log_error("Failed to write coredump: %m");
88                         return -errno;
89                 }
90         }
91
92         fflush(f);
93
94         if (ferror(f)) {
95                 log_error("Failed to write coredump: %m");
96                 return -errno;
97         }
98
99         return 0;
100 }
101
102 int main(int argc, char* argv[]) {
103         int r, j = 0;
104         char *t;
105         ssize_t n;
106         pid_t pid;
107         uid_t uid;
108         gid_t gid;
109         struct iovec iovec[14];
110         size_t coredump_bufsize, coredump_size;
111         _cleanup_free_ char *core_pid = NULL, *core_uid = NULL, *core_gid = NULL, *core_signal = NULL,
112                 *core_timestamp = NULL, *core_comm = NULL, *core_exe = NULL, *core_unit = NULL,
113                 *core_session = NULL, *core_message = NULL, *core_cmdline = NULL, *coredump_data = NULL;
114
115         prctl(PR_SET_DUMPABLE, 0);
116
117         if (argc != _ARG_MAX) {
118                 log_set_target(LOG_TARGET_JOURNAL_OR_KMSG);
119                 log_open();
120
121                 log_error("Invalid number of arguments passed from kernel.");
122                 r = -EINVAL;
123                 goto finish;
124         }
125
126         r = parse_pid(argv[ARG_PID], &pid);
127         if (r < 0) {
128                 log_set_target(LOG_TARGET_JOURNAL_OR_KMSG);
129                 log_open();
130
131                 log_error("Failed to parse PID.");
132                 goto finish;
133         }
134
135         if (cg_pid_get_unit(pid, &t) >= 0) {
136
137                 if (streq(t, SPECIAL_JOURNALD_SERVICE)) {
138                         /* Make sure we don't make use of the journal,
139                          * if it's the journal which is crashing */
140                         log_set_target(LOG_TARGET_KMSG);
141                         log_open();
142
143                         r = divert_coredump();
144                         goto finish;
145                 }
146
147                 core_unit = strappend("COREDUMP_UNIT=", t);
148         } else if (cg_pid_get_user_unit(pid, &t) >= 0)
149                 core_unit = strappend("COREDUMP_USER_UNIT=", t);
150
151         if (core_unit)
152                 IOVEC_SET_STRING(iovec[j++], core_unit);
153
154         /* OK, now we know it's not the journal, hence make use of
155          * it */
156         log_set_target(LOG_TARGET_JOURNAL_OR_KMSG);
157         log_open();
158
159         r = parse_uid(argv[ARG_UID], &uid);
160         if (r < 0) {
161                 log_error("Failed to parse UID.");
162                 goto finish;
163         }
164
165         r = parse_gid(argv[ARG_GID], &gid);
166         if (r < 0) {
167                 log_error("Failed to parse GID.");
168                 goto finish;
169         }
170
171         core_pid = strappend("COREDUMP_PID=", argv[ARG_PID]);
172         if (core_pid)
173                 IOVEC_SET_STRING(iovec[j++], core_pid);
174
175         core_uid = strappend("COREDUMP_UID=", argv[ARG_UID]);
176         if (core_uid)
177                 IOVEC_SET_STRING(iovec[j++], core_uid);
178
179         core_gid = strappend("COREDUMP_GID=", argv[ARG_GID]);
180         if (core_gid)
181                 IOVEC_SET_STRING(iovec[j++], core_gid);
182
183         core_signal = strappend("COREDUMP_SIGNAL=", argv[ARG_SIGNAL]);
184         if (core_signal)
185                 IOVEC_SET_STRING(iovec[j++], core_signal);
186
187         core_comm = strappend("COREDUMP_COMM=", argv[ARG_COMM]);
188         if (core_comm)
189                 IOVEC_SET_STRING(iovec[j++], core_comm);
190
191 #ifdef HAVE_LOGIND
192         if (sd_pid_get_session(pid, &t) >= 0) {
193                 core_session = strappend("COREDUMP_SESSION=", t);
194                 free(t);
195
196                 if (core_session)
197                         IOVEC_SET_STRING(iovec[j++], core_session);
198         }
199
200 #endif
201
202         if (get_process_exe(pid, &t) >= 0) {
203                 core_exe = strappend("COREDUMP_EXE=", t);
204                 free(t);
205
206                 if (core_exe)
207                         IOVEC_SET_STRING(iovec[j++], core_exe);
208         }
209
210         if (get_process_cmdline(pid, 0, false, &t) >= 0) {
211                 core_cmdline = strappend("COREDUMP_CMDLINE=", t);
212                 free(t);
213
214                 if (core_cmdline)
215                         IOVEC_SET_STRING(iovec[j++], core_cmdline);
216         }
217
218         core_timestamp = strjoin("COREDUMP_TIMESTAMP=", argv[ARG_TIMESTAMP], "000000", NULL);
219         if (core_timestamp)
220                 IOVEC_SET_STRING(iovec[j++], core_timestamp);
221
222         IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1");
223         IOVEC_SET_STRING(iovec[j++], "PRIORITY=2");
224
225         core_message = strjoin("MESSAGE=Process ", argv[ARG_PID], " (", argv[ARG_COMM], ") dumped core.", NULL);
226         if (core_message)
227                 IOVEC_SET_STRING(iovec[j++], core_message);
228
229         /* Now, let's drop privileges to become the user who owns the
230          * segfaulted process and allocate the coredump memory under
231          * his uid. This also ensures that the credentials journald
232          * will see are the ones of the coredumping user, thus making
233          * sure the user himself gets access to the core dump. */
234
235         if (setresgid(gid, gid, gid) < 0 ||
236             setresuid(uid, uid, uid) < 0) {
237                 log_error("Failed to drop privileges: %m");
238                 r = -errno;
239                 goto finish;
240         }
241
242         coredump_bufsize = COREDUMP_MIN_START;
243         coredump_data = malloc(coredump_bufsize);
244         if (!coredump_data) {
245                 log_warning("Failed to allocate memory for core, core will not be stored.");
246                 goto finalize;
247         }
248
249         memcpy(coredump_data, "COREDUMP=", 9);
250         coredump_size = 9;
251
252         for (;;) {
253                 n = loop_read(STDIN_FILENO, coredump_data + coredump_size,
254                               coredump_bufsize - coredump_size, false);
255                 if (n < 0) {
256                         log_error("Failed to read core data: %s", strerror(-n));
257                         r = (int) n;
258                         goto finish;
259                 } else if (n == 0)
260                         break;
261
262                 coredump_size += n;
263
264                 if (coredump_size > COREDUMP_MAX) {
265                         log_error("Core too large, core will not be stored.");
266                         goto finalize;
267                 }
268
269                 if (!GREEDY_REALLOC(coredump_data, coredump_bufsize, coredump_size + 1)) {
270                         log_warning("Failed to allocate memory for core, core will not be stored.");
271                         goto finalize;
272                 }
273         }
274
275         iovec[j].iov_base = coredump_data;
276         iovec[j].iov_len = coredump_size;
277         j++;
278
279 finalize:
280         r = sd_journal_sendv(iovec, j);
281         if (r < 0)
282                 log_error("Failed to log coredump: %s", strerror(-r));
283
284 finish:
285         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
286 }