]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/coredumpctl.c
journal: fix memleak, call set_free before return
[thirdparty/systemd.git] / src / journal / coredumpctl.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 Zbigniew Jędrzejewski-Szmek
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 <stdio.h>
23 #include <string.h>
24 #include <getopt.h>
25 #include <fcntl.h>
26 #include <unistd.h>
27
28 #include <systemd/sd-journal.h>
29
30 #include "build.h"
31 #include "set.h"
32 #include "util.h"
33 #include "log.h"
34 #include "path-util.h"
35 #include "pager.h"
36
37 static enum {
38 ACTION_NONE,
39 ACTION_LIST,
40 ACTION_DUMP,
41 ACTION_GDB,
42 } arg_action = ACTION_LIST;
43
44 static Set *matches = NULL;
45 static FILE* output = NULL;
46
47 static int arg_no_pager = false;
48
49 static Set *new_matches(void) {
50 Set *set;
51 char *tmp;
52 int r;
53
54 set = set_new(trivial_hash_func, trivial_compare_func);
55 if (!set) {
56 log_oom();
57 return NULL;
58 }
59
60 tmp = strdup("MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1");
61 if (!tmp) {
62 log_oom();
63 set_free(set);
64 return NULL;
65 }
66
67 r = set_put(set, tmp);
68 if (r < 0) {
69 log_error("failed to add to set: %s", strerror(-r));
70 free(tmp);
71 set_free(set);
72 return NULL;
73 }
74
75 return set;
76 }
77
78 static int help(void) {
79 printf("%s [OPTIONS...] [MATCHES...]\n\n"
80 "List or retrieve coredumps from the journal.\n\n"
81 "Flags:\n"
82 " -o --output=FILE Write output to FILE\n"
83 " --no-pager Do not pipe output into a pager\n"
84
85 "Commands:\n"
86 " -h --help Show this help\n"
87 " --version Print version string\n"
88 " list List available coredumps\n"
89 " dump PID Print coredump to stdout\n"
90 " dump PATH Print coredump to stdout\n"
91 , program_invocation_short_name);
92
93 return 0;
94 }
95
96 static int add_match(Set *set, const char *match) {
97 int r = -ENOMEM;
98 unsigned pid;
99 const char* prefix;
100 char *pattern = NULL;
101 char _cleanup_free_ *p = NULL;
102
103 if (strchr(match, '='))
104 prefix = "";
105 else if (strchr(match, '/')) {
106 p = path_make_absolute_cwd(match);
107 if (!p)
108 goto fail;
109
110 match = p;
111 prefix = "COREDUMP_EXE=";
112 }
113 else if (safe_atou(match, &pid) == 0)
114 prefix = "COREDUMP_PID=";
115 else
116 prefix = "COREDUMP_COMM=";
117
118 pattern = strjoin(prefix, match, NULL);
119 if (!pattern)
120 goto fail;
121
122 r = set_put(set, pattern);
123 if (r < 0) {
124 log_error("failed to add pattern '%s': %s",
125 pattern, strerror(-r));
126 goto fail;
127 }
128 log_debug("Added pattern: %s", pattern);
129
130 return 0;
131 fail:
132 free(pattern);
133 log_error("failed to add match: %s", strerror(-r));
134 return r;
135 }
136
137 static int parse_argv(int argc, char *argv[]) {
138 enum {
139 ARG_VERSION = 0x100,
140 ARG_NO_PAGER,
141 };
142
143 int r, c;
144
145 static const struct option options[] = {
146 { "help", no_argument, NULL, 'h' },
147 { "version" , no_argument, NULL, ARG_VERSION },
148 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
149 { "output", required_argument, NULL, 'o' },
150 };
151
152 assert(argc >= 0);
153 assert(argv);
154
155 while ((c = getopt_long(argc, argv, "ho:", options, NULL)) >= 0)
156 switch(c) {
157 case 'h':
158 help();
159 arg_action = ACTION_NONE;
160 return 0;
161
162 case ARG_VERSION:
163 puts(PACKAGE_STRING);
164 puts(DISTRIBUTION);
165 puts(SYSTEMD_FEATURES);
166 arg_action = ACTION_NONE;
167 return 0;
168
169 case ARG_NO_PAGER:
170 arg_no_pager = true;
171 break;
172
173 case 'o':
174 if (output) {
175 log_error("cannot set output more than once");
176 return -EINVAL;
177 }
178
179 output = fopen(optarg, "we");
180 if (!output) {
181 log_error("writing to '%s': %m", optarg);
182 return -errno;
183 }
184
185 break;
186 default:
187 log_error("Unknown option code %c", c);
188 return -EINVAL;
189 }
190
191 if (optind < argc) {
192 const char *cmd = argv[optind++];
193 if(streq(cmd, "list"))
194 arg_action = ACTION_LIST;
195 else if (streq(cmd, "dump"))
196 arg_action = ACTION_DUMP;
197 else if (streq(cmd, "gdb"))
198 arg_action = ACTION_GDB;
199 else {
200 log_error("Unknown action '%s'", cmd);
201 return -EINVAL;
202 }
203 }
204
205 while (optind < argc) {
206 r = add_match(matches, argv[optind]);
207 if (r != 0)
208 return r;
209 optind++;
210 }
211
212 return 0;
213 }
214
215 static int retrieve(const void *data,
216 size_t len,
217 const char *name,
218 const char **var) {
219
220 size_t field;
221
222 field = strlen(name) + 1; /* name + "=" */
223
224 if (len < field)
225 return 0;
226
227 if (memcmp(data, name, field - 1) != 0)
228 return 0;
229
230 if (((const char*) data)[field - 1] != '=')
231 return 0;
232
233 *var = strndup((const char*)data + field, len - field);
234 if (!var)
235 return log_oom();
236
237 return 0;
238 }
239
240 static int print_entry(FILE* file, sd_journal *j, int had_header) {
241 const char _cleanup_free_
242 *pid = NULL, *uid = NULL, *gid = NULL,
243 *sgnl = NULL, *exe = NULL;
244 const void *d;
245 size_t l;
246 usec_t t;
247 char buf[FORMAT_TIMESTAMP_MAX];
248 int r;
249
250 SD_JOURNAL_FOREACH_DATA(j, d, l) {
251 retrieve(d, l, "COREDUMP_PID", &pid);
252 retrieve(d, l, "COREDUMP_PID", &pid);
253 retrieve(d, l, "COREDUMP_UID", &uid);
254 retrieve(d, l, "COREDUMP_GID", &gid);
255 retrieve(d, l, "COREDUMP_SIGNAL", &sgnl);
256 retrieve(d, l, "COREDUMP_EXE", &exe);
257 if (!exe)
258 retrieve(d, l, "COREDUMP_COMM", &exe);
259 if (!exe)
260 retrieve(d, l, "COREDUMP_CMDLINE", &exe);
261 }
262
263 if (!pid && !uid && !gid && !sgnl && !exe) {
264 log_warning("Empty coredump log entry");
265 return -EINVAL;
266 }
267
268 r = sd_journal_get_realtime_usec(j, &t);
269 if (r < 0) {
270 log_error("Failed to get realtime timestamp: %s", strerror(-r));
271 return r;
272 }
273
274 format_timestamp(buf, sizeof(buf), t);
275
276 if (!had_header)
277 fprintf(file, "%-*s %*s %*s %*s %*s %s\n",
278 FORMAT_TIMESTAMP_MAX-1, "TIME",
279 6, "PID",
280 5, "UID",
281 5, "GID",
282 3, "SIG",
283 "EXE");
284
285 fprintf(file, "%*s %*s %*s %*s %*s %s\n",
286 FORMAT_TIMESTAMP_MAX-1, buf,
287 6, pid,
288 5, uid,
289 5, gid,
290 3, sgnl,
291 exe);
292
293 return 0;
294 }
295
296 static int dump_list(sd_journal *j) {
297 int found = 0;
298
299 assert(j);
300
301 SD_JOURNAL_FOREACH(j)
302 print_entry(stdout, j, found++);
303
304 if (!found) {
305 log_notice("No coredumps found");
306 return -ESRCH;
307 }
308
309 return 0;
310 }
311
312 static int focus(sd_journal *j) {
313 int r;
314
315 r = sd_journal_seek_tail(j);
316 if (r == 0)
317 r = sd_journal_previous(j);
318 if (r < 0) {
319 log_error("Failed to search journal: %s", strerror(-r));
320 return r;
321 }
322 if (r == 0) {
323 log_error("No match found");
324 return -ESRCH;
325 }
326 return r;
327 }
328
329 static int dump_core(sd_journal* j) {
330 const void *data;
331 size_t len, ret;
332 int r;
333
334 assert(j);
335
336 r = focus(j);
337 if (r < 0)
338 return r;
339
340 print_entry(output ? stdout : stderr, j, false);
341
342 if (on_tty() && !output) {
343 log_error("Refusing to dump core to tty");
344 return -ENOTTY;
345 }
346
347 r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
348 if (r < 0) {
349 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
350 return r;
351 }
352
353 assert(len >= 9);
354 data = (const uint8_t*) data + 9;
355 len -= 9;
356
357 ret = fwrite(data, len, 1, output ? output : stdout);
358 if (ret != 1) {
359 log_error("dumping coredump: %m (%zu)", ret);
360 return -errno;
361 }
362
363 r = sd_journal_previous(j);
364 if (r >= 0)
365 log_warning("More than one entry matches, ignoring rest.\n");
366
367 return 0;
368 }
369
370 static int run_gdb(sd_journal *j) {
371 char path[] = "/var/tmp/coredump-XXXXXX";
372 const void *data;
373 size_t len;
374 ssize_t sz;
375 pid_t pid;
376 _cleanup_free_ char *exe = NULL;
377 int r;
378 _cleanup_close_ int fd = -1;
379 siginfo_t st;
380
381 assert(j);
382
383 r = focus(j);
384 if (r < 0)
385 return r;
386
387 print_entry(stdout, j, false);
388
389 r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len);
390 if (r < 0) {
391 log_error("Failed to retrieve COREDUMP_EXE field: %s", strerror(-r));
392 return r;
393 }
394
395 assert(len >= 13);
396 data = (const uint8_t*) data + 13;
397 len -= 13;
398
399 exe = strndup(data, len);
400 if (!exe)
401 return log_oom();
402
403 if (endswith(exe, " (deleted)")) {
404 log_error("Binary already deleted.");
405 return -ENOENT;
406 }
407
408 r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
409 if (r < 0) {
410 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
411 return r;
412 }
413
414 assert(len >= 9);
415 data = (const uint8_t*) data + 9;
416 len -= 9;
417
418 fd = mkostemp(path, O_WRONLY);
419 if (fd < 0) {
420 log_error("Failed to create temporary file: %m");
421 return -errno;
422 }
423
424 sz = write(fd, data, len);
425 if (sz < 0) {
426 log_error("Failed to write temporary file: %s", strerror(errno));
427 r = -errno;
428 goto finish;
429 }
430 if (sz != (ssize_t) len) {
431 log_error("Short write to temporary file.");
432 r = -EIO;
433 goto finish;
434 }
435
436 close_nointr_nofail(fd);
437 fd = -1;
438
439 pid = fork();
440 if (pid < 0) {
441 log_error("Failed to fork(): %m");
442 r = -errno;
443 goto finish;
444 }
445 if (pid == 0) {
446 execlp("gdb", "gdb", exe, path, NULL);
447 log_error("Failed to invoke gdb: %m");
448 _exit(1);
449 }
450
451 r = wait_for_terminate(pid, &st);
452 if (r < 0) {
453 log_error("Failed to wait for gdb: %m");
454 goto finish;
455 }
456
457 r = st.si_code == CLD_EXITED ? st.si_status : 255;
458
459 finish:
460 unlink(path);
461 return r;
462 }
463
464 int main(int argc, char *argv[]) {
465 sd_journal *j = NULL;
466 const char* match;
467 Iterator it;
468 int r = 0;
469
470 log_parse_environment();
471 log_open();
472
473 matches = new_matches();
474 if (!matches)
475 goto end;
476
477 if (parse_argv(argc, argv))
478 goto end;
479
480 if (arg_action == ACTION_NONE)
481 goto end;
482
483 r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
484 if (r < 0) {
485 log_error("Failed to open journal: %s", strerror(-r));
486 goto end;
487 }
488
489 SET_FOREACH(match, matches, it) {
490 r = sd_journal_add_match(j, match, strlen(match));
491 if (r != 0) {
492 log_error("Failed to add match '%s': %s",
493 match, strerror(-r));
494 goto end;
495 }
496 }
497
498 switch(arg_action) {
499
500 case ACTION_LIST:
501 if (!arg_no_pager)
502 pager_open();
503
504 r = dump_list(j);
505 break;
506
507 case ACTION_DUMP:
508 r = dump_core(j);
509 break;
510
511 case ACTION_GDB:
512 r = run_gdb(j);
513 break;
514
515 default:
516 assert_not_reached("Shouldn't be here");
517 }
518
519 end:
520 if (j)
521 sd_journal_close(j);
522
523 set_free_free(matches);
524
525 pager_close();
526
527 if (output)
528 fclose(output);
529
530 return r >= 0 ? r : EXIT_FAILURE;
531 }