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