]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/coredumpctl.c
coredump: add new "info" verb to coredumpctl showing detailed information about a...
[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 <locale.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <getopt.h>
26 #include <fcntl.h>
27 #include <unistd.h>
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"
37 #include "macro.h"
38 #include "journal-internal.h"
39 #include "copy.h"
40
41 static enum {
42 ACTION_NONE,
43 ACTION_INFO,
44 ACTION_LIST,
45 ACTION_DUMP,
46 ACTION_GDB,
47 } arg_action = ACTION_LIST;
48
49 static FILE* output = NULL;
50 static const char* arg_field = NULL;
51
52 static int arg_no_pager = false;
53 static int arg_no_legend = false;
54
55 static Set *new_matches(void) {
56 Set *set;
57 char *tmp;
58 int r;
59
60 set = set_new(trivial_hash_func, trivial_compare_func);
61 if (!set) {
62 log_oom();
63 return NULL;
64 }
65
66 tmp = strdup("MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1");
67 if (!tmp) {
68 log_oom();
69 set_free(set);
70 return NULL;
71 }
72
73 r = set_consume(set, tmp);
74 if (r < 0) {
75 log_error("failed to add to set: %s", strerror(-r));
76 set_free(set);
77 return NULL;
78 }
79
80 return set;
81 }
82
83 static int help(void) {
84
85 printf("%s [OPTIONS...]\n\n"
86 "List or retrieve coredumps from the journal.\n\n"
87 "Flags:\n"
88 " -o --output=FILE Write output to FILE\n"
89 " --no-pager Do not pipe output into a pager\n"
90 " --no-legend Do not print the column headers.\n\n"
91
92 "Commands:\n"
93 " -h --help Show this help\n"
94 " --version Print version string\n"
95 " -F --field=FIELD List all values a certain field takes\n"
96 " list [MATCHES...] List available coredumps\n"
97 " info [MATCHES...] Show detailed information about one or more coredumps\n"
98 " dump [MATCHES...] Print first matching coredump to stdout\n"
99 " gdb [MATCHES...] Start gdb for the first matching coredump\n"
100 , program_invocation_short_name);
101
102 return 0;
103 }
104
105 static int add_match(Set *set, const char *match) {
106 int r = -ENOMEM;
107 unsigned pid;
108 const char* prefix;
109 char *pattern = NULL;
110 _cleanup_free_ char *p = NULL;
111
112 if (strchr(match, '='))
113 prefix = "";
114 else if (strchr(match, '/')) {
115 p = path_make_absolute_cwd(match);
116 if (!p)
117 goto fail;
118
119 match = p;
120 prefix = "COREDUMP_EXE=";
121 }
122 else if (safe_atou(match, &pid) == 0)
123 prefix = "COREDUMP_PID=";
124 else
125 prefix = "COREDUMP_COMM=";
126
127 pattern = strjoin(prefix, match, NULL);
128 if (!pattern)
129 goto fail;
130
131 log_debug("Adding pattern: %s", pattern);
132 r = set_put(set, pattern);
133 if (r < 0) {
134 log_error("Failed to add pattern '%s': %s",
135 pattern, strerror(-r));
136 free(pattern);
137 goto fail;
138 }
139
140 return 0;
141 fail:
142 log_error("Failed to add match: %s", strerror(-r));
143 return r;
144 }
145
146 static int parse_argv(int argc, char *argv[], Set *matches) {
147 enum {
148 ARG_VERSION = 0x100,
149 ARG_NO_PAGER,
150 ARG_NO_LEGEND,
151 };
152
153 int r, c;
154
155 static const struct option options[] = {
156 { "help", no_argument, NULL, 'h' },
157 { "version" , no_argument, NULL, ARG_VERSION },
158 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
159 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
160 { "output", required_argument, NULL, 'o' },
161 { "field", required_argument, NULL, 'F' },
162 {}
163 };
164
165 assert(argc >= 0);
166 assert(argv);
167
168 while ((c = getopt_long(argc, argv, "ho:F:", options, NULL)) >= 0)
169 switch(c) {
170
171 case 'h':
172 arg_action = ACTION_NONE;
173 return help();
174
175 case ARG_VERSION:
176 arg_action = ACTION_NONE;
177 puts(PACKAGE_STRING);
178 puts(SYSTEMD_FEATURES);
179 return 0;
180
181 case ARG_NO_PAGER:
182 arg_no_pager = true;
183 break;
184
185 case ARG_NO_LEGEND:
186 arg_no_legend = true;
187 break;
188
189 case 'o':
190 if (output) {
191 log_error("cannot set output more than once");
192 return -EINVAL;
193 }
194
195 output = fopen(optarg, "we");
196 if (!output) {
197 log_error("writing to '%s': %m", optarg);
198 return -errno;
199 }
200
201 break;
202
203 case 'F':
204 if (arg_field) {
205 log_error("cannot use --field/-F more than once");
206 return -EINVAL;
207 }
208 arg_field = optarg;
209 break;
210
211 case '?':
212 return -EINVAL;
213
214 default:
215 assert_not_reached("Unhandled option");
216 }
217
218 if (optind < argc) {
219 const char *cmd = argv[optind++];
220 if (streq(cmd, "list"))
221 arg_action = ACTION_LIST;
222 else if (streq(cmd, "dump"))
223 arg_action = ACTION_DUMP;
224 else if (streq(cmd, "gdb"))
225 arg_action = ACTION_GDB;
226 else if (streq(cmd, "info"))
227 arg_action = ACTION_INFO;
228 else {
229 log_error("Unknown action '%s'", cmd);
230 return -EINVAL;
231 }
232 }
233
234 if (arg_field && arg_action != ACTION_LIST) {
235 log_error("Option --field/-F only makes sense with list");
236 return -EINVAL;
237 }
238
239 while (optind < argc) {
240 r = add_match(matches, argv[optind]);
241 if (r != 0)
242 return r;
243 optind++;
244 }
245
246 return 0;
247 }
248
249 static int retrieve(const void *data,
250 size_t len,
251 const char *name,
252 char **var) {
253
254 size_t ident;
255 char *v;
256
257 ident = strlen(name) + 1; /* name + "=" */
258
259 if (len < ident)
260 return 0;
261
262 if (memcmp(data, name, ident - 1) != 0)
263 return 0;
264
265 if (((const char*) data)[ident - 1] != '=')
266 return 0;
267
268 v = strndup((const char*)data + ident, len - ident);
269 if (!v)
270 return log_oom();
271
272 free(*var);
273 *var = v;
274
275 return 0;
276 }
277
278 #define filename_escape(s) xescape((s), "./")
279
280 static int make_coredump_path(sd_journal *j, char **ret) {
281 _cleanup_free_ char
282 *pid = NULL, *boot_id = NULL, *tstamp = NULL, *comm = NULL,
283 *p = NULL, *b = NULL, *t = NULL, *c = NULL;
284 const void *d;
285 size_t l;
286 char *fn;
287
288 assert(j);
289 assert(ret);
290
291 SD_JOURNAL_FOREACH_DATA(j, d, l) {
292 retrieve(d, l, "COREDUMP_COMM", &comm);
293 retrieve(d, l, "COREDUMP_PID", &pid);
294 retrieve(d, l, "COREDUMP_TIMESTAMP", &tstamp);
295 retrieve(d, l, "_BOOT_ID", &boot_id);
296 }
297
298 if (!pid || !comm || !tstamp || !boot_id) {
299 log_error("Failed to retrieve necessary fields to find coredump on disk.");
300 return -ENOENT;
301 }
302
303 p = filename_escape(pid);
304 if (!p)
305 return log_oom();
306
307 t = filename_escape(tstamp);
308 if (!t)
309 return log_oom();
310
311 c = filename_escape(comm);
312 if (!t)
313 return log_oom();
314
315 b = filename_escape(boot_id);
316 if (!b)
317 return log_oom();
318
319 fn = strjoin("/var/lib/systemd/coredump/core.", c, ".", b, ".", p, ".", t, NULL);
320 if (!fn)
321 return log_oom();
322
323 *ret = fn;
324 return 0;
325 }
326
327 static void print_field(FILE* file, sd_journal *j) {
328 _cleanup_free_ char *value = NULL;
329 const void *d;
330 size_t l;
331
332 assert(file);
333 assert(j);
334
335 assert(arg_field);
336
337 SD_JOURNAL_FOREACH_DATA(j, d, l)
338 retrieve(d, l, arg_field, &value);
339
340 if (value)
341 fprintf(file, "%s\n", value);
342 }
343
344 static int print_list(FILE* file, sd_journal *j, int had_legend) {
345 _cleanup_free_ char
346 *pid = NULL, *uid = NULL, *gid = NULL,
347 *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL;
348 const void *d;
349 size_t l;
350 usec_t t;
351 char buf[FORMAT_TIMESTAMP_MAX];
352 int r;
353
354 assert(file);
355 assert(j);
356
357 SD_JOURNAL_FOREACH_DATA(j, d, l) {
358 retrieve(d, l, "COREDUMP_PID", &pid);
359 retrieve(d, l, "COREDUMP_UID", &uid);
360 retrieve(d, l, "COREDUMP_GID", &gid);
361 retrieve(d, l, "COREDUMP_SIGNAL", &sgnl);
362 retrieve(d, l, "COREDUMP_EXE", &exe);
363 retrieve(d, l, "COREDUMP_COMM", &comm);
364 retrieve(d, l, "COREDUMP_CMDLINE", &cmdline);
365 }
366
367 if (!pid && !uid && !gid && !sgnl && !exe && !comm && !cmdline) {
368 log_warning("Empty coredump log entry");
369 return -EINVAL;
370 }
371
372 r = sd_journal_get_realtime_usec(j, &t);
373 if (r < 0) {
374 log_error("Failed to get realtime timestamp: %s", strerror(-r));
375 return r;
376 }
377
378 format_timestamp(buf, sizeof(buf), t);
379
380 if (!had_legend && !arg_no_legend)
381 fprintf(file, "%-*s %*s %*s %*s %*s %s\n",
382 FORMAT_TIMESTAMP_MAX-1, "TIME",
383 6, "PID",
384 5, "UID",
385 5, "GID",
386 3, "SIG",
387 "EXE");
388
389 fprintf(file, "%*s %*s %*s %*s %*s %s\n",
390 FORMAT_TIMESTAMP_MAX-1, buf,
391 6, strna(pid),
392 5, strna(uid),
393 5, strna(gid),
394 3, strna(sgnl),
395 strna(exe ?: (comm ?: cmdline)));
396
397 return 0;
398 }
399
400 static int print_info(FILE *file, sd_journal *j, bool need_space) {
401 _cleanup_free_ char
402 *pid = NULL, *uid = NULL, *gid = NULL,
403 *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL,
404 *unit = NULL, *user_unit = NULL, *session = NULL,
405 *boot_id = NULL, *machine_id = NULL, *hostname = NULL, *coredump = NULL;
406 const void *d;
407 size_t l;
408
409 assert(file);
410 assert(j);
411
412 SD_JOURNAL_FOREACH_DATA(j, d, l) {
413 retrieve(d, l, "COREDUMP_PID", &pid);
414 retrieve(d, l, "COREDUMP_UID", &uid);
415 retrieve(d, l, "COREDUMP_GID", &gid);
416 retrieve(d, l, "COREDUMP_SIGNAL", &sgnl);
417 retrieve(d, l, "COREDUMP_EXE", &exe);
418 retrieve(d, l, "COREDUMP_COMM", &comm);
419 retrieve(d, l, "COREDUMP_CMDLINE", &cmdline);
420 retrieve(d, l, "COREDUMP_UNIT", &unit);
421 retrieve(d, l, "COREDUMP_USER_UNIT", &user_unit);
422 retrieve(d, l, "COREDUMP_SESSION", &session);
423 retrieve(d, l, "_BOOT_ID", &boot_id);
424 retrieve(d, l, "_MACHINE_ID", &machine_id);
425 retrieve(d, l, "_HOSTNAME", &hostname);
426 }
427
428 if (need_space)
429 fputs("\n", file);
430
431 fprintf(file,
432 " PID: %s\n"
433 " UID: %s\n"
434 " GID: %s\n",
435 strna(pid),
436 strna(uid),
437 strna(gid));
438
439 if (sgnl) {
440 int sig;
441
442 if (safe_atoi(sgnl, &sig) >= 0)
443 fprintf(file, " Signal: %s (%s)\n", sgnl, signal_to_string(sig));
444 else
445 fprintf(file, " Signal: %s\n", sgnl);
446 }
447
448 if (exe)
449 fprintf(file, " Executable: %s\n", exe);
450 if (comm)
451 fprintf(file, " Comm: %s\n", comm);
452 if (cmdline)
453 fprintf(file, " Command Line: %s\n", cmdline);
454 if (unit)
455 fprintf(file, " Unit: %s\n", unit);
456 if (user_unit)
457 fprintf(file, " User Unit: %s\n", unit);
458 if (session)
459 fprintf(file, " Session: %s\n", session);
460 if (boot_id)
461 fprintf(file, " Boot ID: %s\n", boot_id);
462 if (machine_id)
463 fprintf(file, " Machine ID: %s\n", machine_id);
464 if (hostname)
465 fprintf(file, " Hostname: %s\n", hostname);
466
467 if (make_coredump_path(j, &coredump) >= 0)
468 if (access(coredump, F_OK) >= 0)
469 fprintf(file, " Coredump: %s\n", coredump);
470
471 return 0;
472 }
473
474 static int dump_list(sd_journal *j) {
475 int found = 0;
476
477 assert(j);
478
479 /* The coredumps are likely to compressed, and for just
480 * listing them we don't need to decompress them, so let's
481 * pick a fairly low data threshold here */
482 sd_journal_set_data_threshold(j, 4096);
483
484 SD_JOURNAL_FOREACH(j) {
485 if (arg_action == ACTION_INFO)
486 print_info(stdout, j, found++);
487 else if (arg_field)
488 print_field(stdout, j);
489 else
490 print_list(stdout, j, found++);
491 }
492
493 if (!arg_field && !found) {
494 log_notice("No coredumps found");
495 return -ESRCH;
496 }
497
498 return 0;
499 }
500
501 static int focus(sd_journal *j) {
502 int r;
503
504 r = sd_journal_seek_tail(j);
505 if (r == 0)
506 r = sd_journal_previous(j);
507 if (r < 0) {
508 log_error("Failed to search journal: %s", strerror(-r));
509 return r;
510 }
511 if (r == 0) {
512 log_error("No match found");
513 return -ESRCH;
514 }
515 return r;
516 }
517
518 static int dump_core(sd_journal* j) {
519 const void *data;
520 size_t len, ret;
521 int r;
522
523 assert(j);
524
525 /* We want full data, nothing truncated. */
526 sd_journal_set_data_threshold(j, 0);
527
528 r = focus(j);
529 if (r < 0)
530 return r;
531
532 print_info(output ? stdout : stderr, j, false);
533
534 if (on_tty() && !output) {
535 log_error("Refusing to dump core to tty.");
536 return -ENOTTY;
537 }
538
539 r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
540 if (r == ENOENT) {
541 _cleanup_free_ char *fn = NULL;
542 _cleanup_close_ int fd = -1;
543
544 r = make_coredump_path(j, &fn);
545 if (r < 0)
546 return r;
547
548 fd = open(fn, O_RDONLY|O_CLOEXEC|O_NOCTTY);
549 if (fd < 0) {
550 if (errno == ENOENT)
551 log_error("Coredump neither in journal file nor stored externally on disk.");
552 else
553 log_error("Failed to open coredump file: %s", strerror(-r));
554
555 return -errno;
556 }
557
558 r = copy_bytes(fd, output ? fileno(output) : STDOUT_FILENO);
559 if (r < 0) {
560 log_error("Failed to stream coredump: %s", strerror(-r));
561 return r;
562 }
563
564 } else if (r < 0) {
565 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
566 return r;
567
568 } else {
569 assert(len >= 9);
570 data = (const uint8_t*) data + 9;
571 len -= 9;
572
573 ret = fwrite(data, len, 1, output ?: stdout);
574 if (ret != 1) {
575 log_error("Dumping coredump failed: %m (%zu)", ret);
576 return -errno;
577 }
578 }
579
580 r = sd_journal_previous(j);
581 if (r >= 0)
582 log_warning("More than one entry matches, ignoring rest.");
583
584 return 0;
585 }
586
587 static int run_gdb(sd_journal *j) {
588
589 _cleanup_free_ char *exe = NULL, *coredump = NULL;
590 char temp[] = "/var/tmp/coredump-XXXXXX";
591 bool unlink_temp = false;
592 const char *path;
593 const void *data;
594 siginfo_t st;
595 size_t len;
596 pid_t pid;
597 int r;
598
599 assert(j);
600
601 sd_journal_set_data_threshold(j, 0);
602
603 r = focus(j);
604 if (r < 0)
605 return r;
606
607 print_info(stdout, j, false);
608 fputs("\n", stdout);
609
610 r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len);
611 if (r < 0) {
612 log_error("Failed to retrieve COREDUMP_EXE field: %s", strerror(-r));
613 return r;
614 }
615
616 assert(len >= 13);
617 data = (const uint8_t*) data + 13;
618 len -= 13;
619
620 exe = strndup(data, len);
621 if (!exe)
622 return log_oom();
623
624 if (endswith(exe, " (deleted)")) {
625 log_error("Binary already deleted.");
626 return -ENOENT;
627 }
628
629 if (!path_is_absolute(exe)) {
630 log_error("Binary is not an absolute path.");
631 return -ENOENT;
632 }
633
634 r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
635 if (r == -ENOENT) {
636
637 r = make_coredump_path(j, &coredump);
638 if (r < 0)
639 return r;
640
641 if (access(coredump, R_OK) < 0) {
642 if (errno == ENOENT)
643 log_error("Coredump neither in journal file nor stored externally on disk.");
644 else
645 log_error("Failed to access coredump fiile: %s", strerror(-r));
646
647 return -errno;
648 }
649
650 path = coredump;
651
652 } else if (r < 0) {
653 log_error("Failed to retrieve COREDUMP field: %s", strerror(-r));
654 return r;
655
656 } else {
657 _cleanup_close_ int fd = -1;
658 ssize_t sz;
659
660 assert(len >= 9);
661 data = (const uint8_t*) data + 9;
662 len -= 9;
663
664 fd = mkostemp_safe(temp, O_WRONLY|O_CLOEXEC);
665 if (fd < 0) {
666 log_error("Failed to create temporary file: %m");
667 return -errno;
668 }
669
670 unlink_temp = true;
671
672 sz = write(fd, data, len);
673 if (sz < 0) {
674 log_error("Failed to write temporary file: %m");
675 r = -errno;
676 goto finish;
677 }
678 if (sz != (ssize_t) len) {
679 log_error("Short write to temporary file.");
680 r = -EIO;
681 goto finish;
682 }
683
684 path = temp;
685 }
686
687 pid = fork();
688 if (pid < 0) {
689 log_error("Failed to fork(): %m");
690 r = -errno;
691 goto finish;
692 }
693 if (pid == 0) {
694 execlp("gdb", "gdb", exe, path, NULL);
695
696 log_error("Failed to invoke gdb: %m");
697 _exit(1);
698 }
699
700 r = wait_for_terminate(pid, &st);
701 if (r < 0) {
702 log_error("Failed to wait for gdb: %m");
703 goto finish;
704 }
705
706 r = st.si_code == CLD_EXITED ? st.si_status : 255;
707
708 finish:
709 if (unlink_temp)
710 unlink(temp);
711
712 return r;
713 }
714
715 int main(int argc, char *argv[]) {
716 _cleanup_journal_close_ sd_journal*j = NULL;
717 const char* match;
718 Iterator it;
719 int r = 0;
720 _cleanup_set_free_free_ Set *matches = NULL;
721
722 setlocale(LC_ALL, "");
723 log_parse_environment();
724 log_open();
725
726 matches = new_matches();
727 if (!matches) {
728 r = -ENOMEM;
729 goto end;
730 }
731
732 r = parse_argv(argc, argv, matches);
733 if (r < 0)
734 goto end;
735
736 if (arg_action == ACTION_NONE)
737 goto end;
738
739 r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
740 if (r < 0) {
741 log_error("Failed to open journal: %s", strerror(-r));
742 goto end;
743 }
744
745 SET_FOREACH(match, matches, it) {
746 r = sd_journal_add_match(j, match, strlen(match));
747 if (r != 0) {
748 log_error("Failed to add match '%s': %s",
749 match, strerror(-r));
750 goto end;
751 }
752 }
753
754 if (_unlikely_(log_get_max_level() >= LOG_PRI(LOG_DEBUG))) {
755 _cleanup_free_ char *filter;
756
757 filter = journal_make_match_string(j);
758 log_debug("Journal filter: %s", filter);
759 }
760
761 switch(arg_action) {
762
763 case ACTION_LIST:
764 case ACTION_INFO:
765 if (!arg_no_pager)
766 pager_open(false);
767
768 r = dump_list(j);
769 break;
770
771 case ACTION_DUMP:
772 r = dump_core(j);
773 break;
774
775 case ACTION_GDB:
776 r = run_gdb(j);
777 break;
778
779 default:
780 assert_not_reached("Shouldn't be here");
781 }
782
783 end:
784 pager_close();
785
786 if (output)
787 fclose(output);
788
789 return r >= 0 ? r : EXIT_FAILURE;
790 }