1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <fcntl.h>
4 #include <getopt.h>
5 #include <locale.h>
6 #include <stdio.h>
7 #include <unistd.h>
8
9 #include "sd-bus.h"
10 #include "sd-journal.h"
11 #include "sd-messages.h"
12
13 #include "alloc-util.h"
14 #include "bus-error.h"
15 #include "bus-util.h"
16 #include "compress.h"
17 #include "def.h"
18 #include "fd-util.h"
19 #include "format-table.h"
20 #include "fs-util.h"
21 #include "glob-util.h"
22 #include "journal-internal.h"
23 #include "journal-util.h"
24 #include "log.h"
25 #include "macro.h"
26 #include "main-func.h"
27 #include "pager.h"
28 #include "parse-argument.h"
29 #include "parse-util.h"
30 #include "path-util.h"
31 #include "pretty-print.h"
32 #include "process-util.h"
33 #include "rlimit-util.h"
34 #include "sigbus.h"
35 #include "signal-util.h"
36 #include "string-util.h"
37 #include "strv.h"
38 #include "terminal-util.h"
39 #include "tmpfile-util.h"
40 #include "user-util.h"
41 #include "util.h"
42 #include "verbs.h"
43
44 #define SHORT_BUS_CALL_TIMEOUT_USEC (3 * USEC_PER_SEC)
45
46 static usec_t arg_since = USEC_INFINITY, arg_until = USEC_INFINITY;
47 static const char* arg_field = NULL;
48 static const char *arg_debugger = NULL;
49 static char **arg_debugger_args = NULL;
50 static const char *arg_directory = NULL;
51 static char **arg_file = NULL;
52 static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
53 static PagerFlags arg_pager_flags = 0;
54 static int arg_legend = true;
55 static size_t arg_rows_max = SIZE_MAX;
56 static const char* arg_output = NULL;
57 static bool arg_reverse = false;
58 static bool arg_quiet = false;
59 static bool arg_all = false;
60
61 STATIC_DESTRUCTOR_REGISTER(arg_debugger_args, strv_freep);
62 STATIC_DESTRUCTOR_REGISTER(arg_file, strv_freep);
63
add_match(sd_journal * j,const char * match)64 static int add_match(sd_journal *j, const char *match) {
65 _cleanup_free_ char *p = NULL;
66 const char* prefix, *pattern;
67 pid_t pid;
68 int r;
69
70 if (strchr(match, '='))
71 prefix = "";
72 else if (strchr(match, '/')) {
73 r = path_make_absolute_cwd(match, &p);
74 if (r < 0)
75 return log_error_errno(r, "path_make_absolute_cwd(\"%s\"): %m", match);
76
77 match = p;
78 prefix = "COREDUMP_EXE=";
79 } else if (parse_pid(match, &pid) >= 0)
80 prefix = "COREDUMP_PID=";
81 else
82 prefix = "COREDUMP_COMM=";
83
84 pattern = strjoina(prefix, match);
85 log_debug("Adding match: %s", pattern);
86 r = sd_journal_add_match(j, pattern, 0);
87 if (r < 0)
88 return log_error_errno(r, "Failed to add match \"%s\": %m", match);
89
90 return 0;
91 }
92
add_matches(sd_journal * j,char ** matches)93 static int add_matches(sd_journal *j, char **matches) {
94 int r;
95
96 r = sd_journal_add_match(j, "MESSAGE_ID=" SD_MESSAGE_COREDUMP_STR, 0);
97 if (r < 0)
98 return log_error_errno(r, "Failed to add match \"%s\": %m", "MESSAGE_ID=" SD_MESSAGE_COREDUMP_STR);
99
100 r = sd_journal_add_match(j, "MESSAGE_ID=" SD_MESSAGE_BACKTRACE_STR, 0);
101 if (r < 0)
102 return log_error_errno(r, "Failed to add match \"%s\": %m", "MESSAGE_ID=" SD_MESSAGE_BACKTRACE_STR);
103
104 STRV_FOREACH(match, matches) {
105 r = add_match(j, *match);
106 if (r < 0)
107 return r;
108 }
109
110 return 0;
111 }
112
acquire_journal(sd_journal ** ret,char ** matches)113 static int acquire_journal(sd_journal **ret, char **matches) {
114 _cleanup_(sd_journal_closep) sd_journal *j = NULL;
115 int r;
116
117 assert(ret);
118
119 if (arg_directory) {
120 r = sd_journal_open_directory(&j, arg_directory, 0);
121 if (r < 0)
122 return log_error_errno(r, "Failed to open journals in directory: %s: %m", arg_directory);
123 } else if (arg_file) {
124 r = sd_journal_open_files(&j, (const char**)arg_file, 0);
125 if (r < 0)
126 return log_error_errno(r, "Failed to open journal files: %m");
127 } else {
128 r = sd_journal_open(&j, arg_all ? 0 : SD_JOURNAL_LOCAL_ONLY);
129 if (r < 0)
130 return log_error_errno(r, "Failed to open journal: %m");
131 }
132
133 r = journal_access_check_and_warn(j, arg_quiet, true);
134 if (r < 0)
135 return r;
136
137 r = add_matches(j, matches);
138 if (r < 0)
139 return r;
140
141 if (DEBUG_LOGGING) {
142 _cleanup_free_ char *filter = NULL;
143
144 filter = journal_make_match_string(j);
145 log_debug("Journal filter: %s", filter);
146 }
147
148 *ret = TAKE_PTR(j);
149
150 return 0;
151 }
152
verb_help(int argc,char ** argv,void * userdata)153 static int verb_help(int argc, char **argv, void *userdata) {
154 _cleanup_free_ char *link = NULL;
155 int r;
156
157 r = terminal_urlify_man("coredumpctl", "1", &link);
158 if (r < 0)
159 return log_oom();
160
161 printf("%1$s [OPTIONS...] COMMAND ...\n\n"
162 "%5$sList or retrieve coredumps from the journal.%6$s\n"
163 "\n%3$sCommands:%4$s\n"
164 " list [MATCHES...] List available coredumps (default)\n"
165 " info [MATCHES...] Show detailed information about one or more coredumps\n"
166 " dump [MATCHES...] Print first matching coredump to stdout\n"
167 " debug [MATCHES...] Start a debugger for the first matching coredump\n"
168 "\n%3$sOptions:%4$s\n"
169 " -h --help Show this help\n"
170 " --version Print version string\n"
171 " --no-pager Do not pipe output into a pager\n"
172 " --no-legend Do not print the column headers\n"
173 " --json=pretty|short|off\n"
174 " Generate JSON output\n"
175 " --debugger=DEBUGGER Use the given debugger\n"
176 " -A --debugger-arguments=ARGS Pass the given arguments to the debugger\n"
177 " -n INT Show maximum number of rows\n"
178 " -1 Show information about most recent entry only\n"
179 " -S --since=DATE Only print coredumps since the date\n"
180 " -U --until=DATE Only print coredumps until the date\n"
181 " -r --reverse Show the newest entries first\n"
182 " -F --field=FIELD List all values a certain field takes\n"
183 " -o --output=FILE Write output to FILE\n"
184 " --file=PATH Use journal file\n"
185 " -D --directory=DIR Use journal files from directory\n\n"
186 " -q --quiet Do not show info messages and privilege warning\n"
187 " --all Look at all journal files instead of local ones\n"
188 "\nSee the %2$s for details.\n",
189 program_invocation_short_name,
190 link,
191 ansi_underline(),
192 ansi_normal(),
193 ansi_highlight(),
194 ansi_normal());
195
196 return 0;
197 }
198
parse_argv(int argc,char * argv[])199 static int parse_argv(int argc, char *argv[]) {
200 enum {
201 ARG_VERSION = 0x100,
202 ARG_NO_PAGER,
203 ARG_NO_LEGEND,
204 ARG_JSON,
205 ARG_DEBUGGER,
206 ARG_FILE,
207 ARG_ALL,
208 };
209
210 int c, r;
211
212 static const struct option options[] = {
213 { "help", no_argument, NULL, 'h' },
214 { "version" , no_argument, NULL, ARG_VERSION },
215 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
216 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
217 { "debugger", required_argument, NULL, ARG_DEBUGGER },
218 { "debugger-arguments", required_argument, NULL, 'A' },
219 { "output", required_argument, NULL, 'o' },
220 { "field", required_argument, NULL, 'F' },
221 { "file", required_argument, NULL, ARG_FILE },
222 { "directory", required_argument, NULL, 'D' },
223 { "reverse", no_argument, NULL, 'r' },
224 { "since", required_argument, NULL, 'S' },
225 { "until", required_argument, NULL, 'U' },
226 { "quiet", no_argument, NULL, 'q' },
227 { "json", required_argument, NULL, ARG_JSON },
228 { "all", no_argument, NULL, ARG_ALL },
229 {}
230 };
231
232 assert(argc >= 0);
233 assert(argv);
234
235 while ((c = getopt_long(argc, argv, "hA:o:F:1D:rS:U:qn:", options, NULL)) >= 0)
236 switch (c) {
237 case 'h':
238 return verb_help(0, NULL, NULL);
239
240 case ARG_VERSION:
241 return version();
242
243 case ARG_NO_PAGER:
244 arg_pager_flags |= PAGER_DISABLE;
245 break;
246
247 case ARG_NO_LEGEND:
248 arg_legend = false;
249 break;
250
251 case ARG_DEBUGGER:
252 arg_debugger = optarg;
253 break;
254
255 case 'A': {
256 _cleanup_strv_free_ char **l = NULL;
257 r = strv_split_full(&l, optarg, WHITESPACE, EXTRACT_UNQUOTE);
258 if (r < 0)
259 return log_error_errno(r, "Failed to parse debugger arguments '%s': %m", optarg);
260 strv_free_and_replace(arg_debugger_args, l);
261 break;
262 }
263
264 case ARG_FILE:
265 r = glob_extend(&arg_file, optarg, GLOB_NOCHECK);
266 if (r < 0)
267 return log_error_errno(r, "Failed to add paths: %m");
268 break;
269
270 case 'o':
271 if (arg_output)
272 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
273 "Cannot set output more than once.");
274
275 arg_output = optarg;
276 break;
277
278 case 'S':
279 r = parse_timestamp(optarg, &arg_since);
280 if (r < 0)
281 return log_error_errno(r, "Failed to parse timestamp '%s': %m", optarg);
282 break;
283
284 case 'U':
285 r = parse_timestamp(optarg, &arg_until);
286 if (r < 0)
287 return log_error_errno(r, "Failed to parse timestamp '%s': %m", optarg);
288 break;
289
290 case 'F':
291 if (arg_field)
292 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
293 "Cannot use --field/-F more than once.");
294 arg_field = optarg;
295 break;
296
297 case '1':
298 arg_rows_max = 1;
299 arg_reverse = true;
300 break;
301
302 case 'n': {
303 unsigned n;
304
305 r = safe_atou(optarg, &n);
306 if (r < 0 || n < 1)
307 return log_error_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL),
308 "Invalid numeric parameter to -n: %s", optarg);
309
310 arg_rows_max = n;
311 break;
312 }
313
314 case 'D':
315 arg_directory = optarg;
316 break;
317
318 case 'r':
319 arg_reverse = true;
320 break;
321
322 case 'q':
323 arg_quiet = true;
324 break;
325
326 case ARG_JSON:
327 r = parse_json_argument(optarg, &arg_json_format_flags);
328 if (r <= 0)
329 return r;
330
331 break;
332
333 case ARG_ALL:
334 arg_all = true;
335 break;
336
337 case '?':
338 return -EINVAL;
339
340 default:
341 assert_not_reached();
342 }
343
344 if (arg_since != USEC_INFINITY && arg_until != USEC_INFINITY &&
345 arg_since > arg_until)
346 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
347 "--since= must be before --until=.");
348
349 return 1;
350 }
351
retrieve(const void * data,size_t len,const char * name,char ** var)352 static int retrieve(const void *data,
353 size_t len,
354 const char *name,
355 char **var) {
356
357 size_t ident;
358 char *v;
359
360 ident = strlen(name) + 1; /* name + "=" */
361
362 if (len < ident)
363 return 0;
364
365 if (memcmp(data, name, ident - 1) != 0)
366 return 0;
367
368 if (((const char*) data)[ident - 1] != '=')
369 return 0;
370
371 v = strndup((const char*)data + ident, len - ident);
372 if (!v)
373 return log_oom();
374
375 free_and_replace(*var, v);
376 return 1;
377 }
378
print_field(FILE * file,sd_journal * j)379 static int print_field(FILE* file, sd_journal *j) {
380 const void *d;
381 size_t l;
382
383 assert(file);
384 assert(j);
385
386 assert(arg_field);
387
388 /* A (user-specified) field may appear more than once for a given entry.
389 * We will print all of the occurrences.
390 * This is different below for fields that systemd-coredump uses,
391 * because they cannot meaningfully appear more than once.
392 */
393 SD_JOURNAL_FOREACH_DATA(j, d, l) {
394 _cleanup_free_ char *value = NULL;
395 int r;
396
397 r = retrieve(d, l, arg_field, &value);
398 if (r < 0)
399 return r;
400 if (r > 0)
401 fprintf(file, "%s\n", value);
402 }
403
404 return 0;
405 }
406
407 #define RETRIEVE(d, l, name, arg) \
408 { \
409 int _r = retrieve(d, l, name, &arg); \
410 if (_r < 0) \
411 return _r; \
412 if (_r > 0) \
413 continue; \
414 }
415
analyze_coredump_file(const char * path,const char ** ret_state,const char ** ret_color,uint64_t * ret_size)416 static void analyze_coredump_file(
417 const char *path,
418 const char **ret_state,
419 const char **ret_color,
420 uint64_t *ret_size) {
421
422 _cleanup_close_ int fd = -1;
423 struct stat st;
424 int r;
425
426 assert(path);
427 assert(ret_state);
428 assert(ret_color);
429 assert(ret_size);
430
431 fd = open(path, O_PATH|O_CLOEXEC);
432 if (fd < 0) {
433 if (errno == ENOENT) {
434 *ret_state = "missing";
435 *ret_color = ansi_grey();
436 *ret_size = UINT64_MAX;
437 return;
438 }
439
440 r = -errno;
441 } else
442 r = access_fd(fd, R_OK);
443 if (ERRNO_IS_PRIVILEGE(r)) {
444 *ret_state = "inaccessible";
445 *ret_color = ansi_highlight_yellow();
446 *ret_size = UINT64_MAX;
447 return;
448 }
449 if (r < 0)
450 goto error;
451
452 if (fstat(fd, &st) < 0)
453 goto error;
454
455 if (!S_ISREG(st.st_mode))
456 goto error;
457
458 *ret_state = "present";
459 *ret_color = NULL;
460 *ret_size = st.st_size;
461 return;
462
463 error:
464 *ret_state = "error";
465 *ret_color = ansi_highlight_red();
466 *ret_size = UINT64_MAX;
467 }
468
print_list(FILE * file,sd_journal * j,Table * t)469 static int print_list(FILE* file, sd_journal *j, Table *t) {
470 _cleanup_free_ char
471 *mid = NULL, *pid = NULL, *uid = NULL, *gid = NULL,
472 *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL,
473 *filename = NULL, *truncated = NULL, *coredump = NULL;
474 const void *d;
475 size_t l;
476 usec_t ts;
477 int r, signal_as_int = 0;
478 const char *present = NULL, *color = NULL;
479 uint64_t size = UINT64_MAX;
480 bool normal_coredump;
481 uid_t uid_as_int = UID_INVALID;
482 gid_t gid_as_int = GID_INVALID;
483 pid_t pid_as_int = 0;
484
485 assert(file);
486 assert(j);
487 assert(t);
488
489 SD_JOURNAL_FOREACH_DATA(j, d, l) {
490 RETRIEVE(d, l, "MESSAGE_ID", mid);
491 RETRIEVE(d, l, "COREDUMP_PID", pid);
492 RETRIEVE(d, l, "COREDUMP_UID", uid);
493 RETRIEVE(d, l, "COREDUMP_GID", gid);
494 RETRIEVE(d, l, "COREDUMP_SIGNAL", sgnl);
495 RETRIEVE(d, l, "COREDUMP_EXE", exe);
496 RETRIEVE(d, l, "COREDUMP_COMM", comm);
497 RETRIEVE(d, l, "COREDUMP_CMDLINE", cmdline);
498 RETRIEVE(d, l, "COREDUMP_FILENAME", filename);
499 RETRIEVE(d, l, "COREDUMP_TRUNCATED", truncated);
500 RETRIEVE(d, l, "COREDUMP", coredump);
501 }
502
503 if (!pid && !uid && !gid && !sgnl && !exe && !comm && !cmdline && !filename)
504 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Empty coredump log entry");
505
506 (void) parse_uid(uid, &uid_as_int);
507 (void) parse_gid(gid, &gid_as_int);
508 (void) parse_pid(pid, &pid_as_int);
509 signal_as_int = signal_from_string(sgnl);
510
511 r = sd_journal_get_realtime_usec(j, &ts);
512 if (r < 0)
513 return log_error_errno(r, "Failed to get realtime timestamp: %m");
514
515 normal_coredump = streq_ptr(mid, SD_MESSAGE_COREDUMP_STR);
516
517 if (filename)
518 analyze_coredump_file(filename, &present, &color, &size);
519 else if (coredump)
520 present = "journal";
521 else if (normal_coredump) {
522 present = "none";
523 color = ansi_grey();
524 } else
525 present = NULL;
526
527 if (STRPTR_IN_SET(present, "present", "journal") && truncated && parse_boolean(truncated) > 0)
528 present = "truncated";
529
530 r = table_add_many(
531 t,
532 TABLE_TIMESTAMP, ts,
533 TABLE_PID, pid_as_int,
534 TABLE_UID, uid_as_int,
535 TABLE_GID, gid_as_int,
536 TABLE_SIGNAL, normal_coredump ? signal_as_int : 0,
537 TABLE_STRING, present,
538 TABLE_SET_COLOR, color,
539 TABLE_STRING, exe ?: comm ?: cmdline,
540 TABLE_SIZE, size);
541 if (r < 0)
542 return table_log_add_error(r);
543
544 return 0;
545 }
546
print_info(FILE * file,sd_journal * j,bool need_space)547 static int print_info(FILE *file, sd_journal *j, bool need_space) {
548 _cleanup_free_ char
549 *mid = NULL, *pid = NULL, *uid = NULL, *gid = NULL,
550 *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL,
551 *unit = NULL, *user_unit = NULL, *session = NULL,
552 *boot_id = NULL, *machine_id = NULL, *hostname = NULL,
553 *slice = NULL, *cgroup = NULL, *owner_uid = NULL,
554 *message = NULL, *timestamp = NULL, *filename = NULL,
555 *truncated = NULL, *coredump = NULL,
556 *pkgmeta_name = NULL, *pkgmeta_version = NULL, *pkgmeta_json = NULL;
557 const void *d;
558 size_t l;
559 bool normal_coredump;
560 int r;
561
562 assert(file);
563 assert(j);
564
565 (void) sd_journal_set_data_threshold(j, 0);
566
567 SD_JOURNAL_FOREACH_DATA(j, d, l) {
568 RETRIEVE(d, l, "MESSAGE_ID", mid);
569 RETRIEVE(d, l, "COREDUMP_PID", pid);
570 RETRIEVE(d, l, "COREDUMP_UID", uid);
571 RETRIEVE(d, l, "COREDUMP_GID", gid);
572 RETRIEVE(d, l, "COREDUMP_SIGNAL", sgnl);
573 RETRIEVE(d, l, "COREDUMP_EXE", exe);
574 RETRIEVE(d, l, "COREDUMP_COMM", comm);
575 RETRIEVE(d, l, "COREDUMP_CMDLINE", cmdline);
576 RETRIEVE(d, l, "COREDUMP_HOSTNAME", hostname);
577 RETRIEVE(d, l, "COREDUMP_UNIT", unit);
578 RETRIEVE(d, l, "COREDUMP_USER_UNIT", user_unit);
579 RETRIEVE(d, l, "COREDUMP_SESSION", session);
580 RETRIEVE(d, l, "COREDUMP_OWNER_UID", owner_uid);
581 RETRIEVE(d, l, "COREDUMP_SLICE", slice);
582 RETRIEVE(d, l, "COREDUMP_CGROUP", cgroup);
583 RETRIEVE(d, l, "COREDUMP_TIMESTAMP", timestamp);
584 RETRIEVE(d, l, "COREDUMP_FILENAME", filename);
585 RETRIEVE(d, l, "COREDUMP_TRUNCATED", truncated);
586 RETRIEVE(d, l, "COREDUMP", coredump);
587 RETRIEVE(d, l, "COREDUMP_PACKAGE_NAME", pkgmeta_name);
588 RETRIEVE(d, l, "COREDUMP_PACKAGE_VERSION", pkgmeta_version);
589 RETRIEVE(d, l, "COREDUMP_PACKAGE_JSON", pkgmeta_json);
590 RETRIEVE(d, l, "_BOOT_ID", boot_id);
591 RETRIEVE(d, l, "_MACHINE_ID", machine_id);
592 RETRIEVE(d, l, "MESSAGE", message);
593 }
594
595 if (need_space)
596 fputs("\n", file);
597
598 normal_coredump = streq_ptr(mid, SD_MESSAGE_COREDUMP_STR);
599
600 if (comm)
601 fprintf(file,
602 " PID: %s%s%s (%s)\n",
603 ansi_highlight(), strna(pid), ansi_normal(), comm);
604 else
605 fprintf(file,
606 " PID: %s%s%s\n",
607 ansi_highlight(), strna(pid), ansi_normal());
608
609 if (uid) {
610 uid_t n;
611
612 if (parse_uid(uid, &n) >= 0) {
613 _cleanup_free_ char *u = NULL;
614
615 u = uid_to_name(n);
616 fprintf(file,
617 " UID: %s (%s)\n",
618 uid, u);
619 } else {
620 fprintf(file,
621 " UID: %s\n",
622 uid);
623 }
624 }
625
626 if (gid) {
627 gid_t n;
628
629 if (parse_gid(gid, &n) >= 0) {
630 _cleanup_free_ char *g = NULL;
631
632 g = gid_to_name(n);
633 fprintf(file,
634 " GID: %s (%s)\n",
635 gid, g);
636 } else {
637 fprintf(file,
638 " GID: %s\n",
639 gid);
640 }
641 }
642
643 if (sgnl) {
644 int sig;
645 const char *name = normal_coredump ? "Signal" : "Reason";
646
647 if (normal_coredump && safe_atoi(sgnl, &sig) >= 0)
648 fprintf(file, " %s: %s (%s)\n", name, sgnl, signal_to_string(sig));
649 else
650 fprintf(file, " %s: %s\n", name, sgnl);
651 }
652
653 if (timestamp) {
654 usec_t u;
655
656 r = safe_atou64(timestamp, &u);
657 if (r >= 0)
658 fprintf(file, " Timestamp: %s (%s)\n",
659 FORMAT_TIMESTAMP(u), FORMAT_TIMESTAMP_RELATIVE(u));
660
661 else
662 fprintf(file, " Timestamp: %s\n", timestamp);
663 }
664
665 if (cmdline)
666 fprintf(file, " Command Line: %s\n", cmdline);
667 if (exe)
668 fprintf(file, " Executable: %s%s%s\n", ansi_highlight(), exe, ansi_normal());
669 if (cgroup)
670 fprintf(file, " Control Group: %s\n", cgroup);
671 if (unit)
672 fprintf(file, " Unit: %s\n", unit);
673 if (user_unit)
674 fprintf(file, " User Unit: %s\n", user_unit);
675 if (slice)
676 fprintf(file, " Slice: %s\n", slice);
677 if (session)
678 fprintf(file, " Session: %s\n", session);
679 if (owner_uid) {
680 uid_t n;
681
682 if (parse_uid(owner_uid, &n) >= 0) {
683 _cleanup_free_ char *u = NULL;
684
685 u = uid_to_name(n);
686 fprintf(file,
687 " Owner UID: %s (%s)\n",
688 owner_uid, u);
689 } else {
690 fprintf(file,
691 " Owner UID: %s\n",
692 owner_uid);
693 }
694 }
695 if (boot_id)
696 fprintf(file, " Boot ID: %s\n", boot_id);
697 if (machine_id)
698 fprintf(file, " Machine ID: %s\n", machine_id);
699 if (hostname)
700 fprintf(file, " Hostname: %s\n", hostname);
701
702 if (filename) {
703 const char *state = NULL, *color = NULL;
704 uint64_t size = UINT64_MAX;
705
706 analyze_coredump_file(filename, &state, &color, &size);
707
708 if (STRPTR_IN_SET(state, "present", "journal") && truncated && parse_boolean(truncated) > 0)
709 state = "truncated";
710
711 fprintf(file,
712 " Storage: %s%s (%s)%s\n",
713 strempty(color),
714 filename,
715 state,
716 ansi_normal());
717
718 if (size != UINT64_MAX)
719 fprintf(file, " Disk Size: %s\n", FORMAT_BYTES(size));
720
721 } else if (coredump)
722 fprintf(file, " Storage: journal\n");
723 else
724 fprintf(file, " Storage: none\n");
725
726 if (pkgmeta_name && pkgmeta_version)
727 fprintf(file, " Package: %s/%s\n", pkgmeta_name, pkgmeta_version);
728
729 /* Print out the build-id of the 'main' ELF module, by matching the JSON key
730 * with the 'exe' field. */
731 if (exe && pkgmeta_json) {
732 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
733
734 r = json_parse(pkgmeta_json, 0, &v, NULL, NULL);
735 if (r < 0)
736 log_warning_errno(r, "json_parse on %s failed, ignoring: %m", pkgmeta_json);
737 else {
738 const char *module_name;
739 JsonVariant *module_json;
740
741 JSON_VARIANT_OBJECT_FOREACH(module_name, module_json, v) {
742 JsonVariant *build_id;
743
744 /* We only print the build-id for the 'main' ELF module */
745 if (!path_equal_filename(module_name, exe))
746 continue;
747
748 build_id = json_variant_by_key(module_json, "buildId");
749 if (build_id)
750 fprintf(file, " build-id: %s\n", json_variant_string(build_id));
751
752 break;
753 }
754 }
755 }
756
757 if (message) {
758 _cleanup_free_ char *m = NULL;
759
760 m = strreplace(message, "\n", "\n ");
761
762 fprintf(file, " Message: %s\n", strstrip(m ?: message));
763 }
764
765 return 0;
766 }
767
focus(sd_journal * j)768 static int focus(sd_journal *j) {
769 int r;
770
771 r = sd_journal_seek_tail(j);
772 if (r == 0)
773 r = sd_journal_previous(j);
774 if (r < 0)
775 return log_error_errno(r, "Failed to search journal: %m");
776 if (r == 0)
777 return log_error_errno(SYNTHETIC_ERRNO(ESRCH),
778 "No match found.");
779 return r;
780 }
781
print_entry(sd_journal * j,size_t n_found,Table * t)782 static int print_entry(
783 sd_journal *j,
784 size_t n_found,
785 Table *t) {
786
787 assert(j);
788
789 if (t)
790 return print_list(stdout, j, t);
791 else if (arg_field)
792 return print_field(stdout, j);
793 else
794 return print_info(stdout, j, n_found > 0);
795 }
796
dump_list(int argc,char ** argv,void * userdata)797 static int dump_list(int argc, char **argv, void *userdata) {
798 _cleanup_(sd_journal_closep) sd_journal *j = NULL;
799 _cleanup_(table_unrefp) Table *t = NULL;
800 size_t n_found = 0;
801 bool verb_is_info;
802 int r;
803
804 verb_is_info = argc >= 1 && streq(argv[0], "info");
805
806 r = acquire_journal(&j, argv + 1);
807 if (r < 0)
808 return r;
809
810 /* The coredumps are likely compressed, and for just listing them we don't need to decompress them,
811 * so let's pick a fairly low data threshold here */
812 (void) sd_journal_set_data_threshold(j, 4096);
813
814 if (!verb_is_info && !arg_field) {
815 t = table_new("time", "pid", "uid", "gid", "sig", "corefile", "exe", "size");
816 if (!t)
817 return log_oom();
818
819 (void) table_set_align_percent(t, TABLE_HEADER_CELL(1), 100);
820 (void) table_set_align_percent(t, TABLE_HEADER_CELL(2), 100);
821 (void) table_set_align_percent(t, TABLE_HEADER_CELL(3), 100);
822 (void) table_set_align_percent(t, TABLE_HEADER_CELL(7), 100);
823
824 (void) table_set_empty_string(t, "-");
825 } else
826 pager_open(arg_pager_flags);
827
828 /* "info" without pattern implies "-1" */
829 if ((arg_rows_max == 1 && arg_reverse) || (verb_is_info && argc == 1)) {
830 r = focus(j);
831 if (r < 0)
832 return r;
833
834 r = print_entry(j, 0, t);
835 if (r < 0)
836 return r;
837 } else {
838 if (arg_since != USEC_INFINITY && !arg_reverse)
839 r = sd_journal_seek_realtime_usec(j, arg_since);
840 else if (arg_until != USEC_INFINITY && arg_reverse)
841 r = sd_journal_seek_realtime_usec(j, arg_until);
842 else if (arg_reverse)
843 r = sd_journal_seek_tail(j);
844 else
845 r = sd_journal_seek_head(j);
846 if (r < 0)
847 return log_error_errno(r, "Failed to seek to date: %m");
848
849 for (;;) {
850 if (!arg_reverse)
851 r = sd_journal_next(j);
852 else
853 r = sd_journal_previous(j);
854 if (r < 0)
855 return log_error_errno(r, "Failed to iterate through journal: %m");
856 if (r == 0)
857 break;
858
859 if (arg_until != USEC_INFINITY && !arg_reverse) {
860 usec_t usec;
861
862 r = sd_journal_get_realtime_usec(j, &usec);
863 if (r < 0)
864 return log_error_errno(r, "Failed to determine timestamp: %m");
865 if (usec > arg_until)
866 continue;
867 }
868
869 if (arg_since != USEC_INFINITY && arg_reverse) {
870 usec_t usec;
871
872 r = sd_journal_get_realtime_usec(j, &usec);
873 if (r < 0)
874 return log_error_errno(r, "Failed to determine timestamp: %m");
875 if (usec < arg_since)
876 continue;
877 }
878
879 r = print_entry(j, n_found++, t);
880 if (r < 0)
881 return r;
882
883 if (arg_rows_max != SIZE_MAX && n_found >= arg_rows_max)
884 break;
885 }
886
887 if (!arg_field && n_found <= 0) {
888 if (!arg_quiet)
889 log_notice("No coredumps found.");
890 return -ESRCH;
891 }
892 }
893
894 if (t) {
895 r = table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
896 if (r < 0)
897 return r;
898 }
899
900 return 0;
901 }
902
save_core(sd_journal * j,FILE * file,char ** path,bool * unlink_temp)903 static int save_core(sd_journal *j, FILE *file, char **path, bool *unlink_temp) {
904 const char *data;
905 _cleanup_free_ char *filename = NULL;
906 size_t len;
907 int r, fd;
908 _cleanup_close_ int fdt = -1;
909 char *temp = NULL;
910
911 assert(!(file && path)); /* At most one can be specified */
912 assert(!!path == !!unlink_temp); /* Those must be specified together */
913
914 /* Look for a coredump on disk first. */
915 r = sd_journal_get_data(j, "COREDUMP_FILENAME", (const void**) &data, &len);
916 if (r == 0) {
917 r = retrieve(data, len, "COREDUMP_FILENAME", &filename);
918 if (r < 0)
919 return r;
920 assert(r > 0);
921
922 if (access(filename, R_OK) < 0)
923 return log_error_errno(errno, "File \"%s\" is not readable: %m", filename);
924
925 if (path && !ENDSWITH_SET(filename, ".xz", ".lz4", ".zst")) {
926 *path = TAKE_PTR(filename);
927
928 return 0;
929 }
930
931 } else {
932 if (r != -ENOENT)
933 return log_error_errno(r, "Failed to retrieve COREDUMP_FILENAME field: %m");
934 /* Check that we can have a COREDUMP field. We still haven't set a high
935 * data threshold, so we'll get a few kilobytes at most.
936 */
937
938 r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
939 if (r == -ENOENT)
940 return log_error_errno(r, "Coredump entry has no core attached (neither internally in the journal nor externally on disk).");
941 if (r < 0)
942 return log_error_errno(r, "Failed to retrieve COREDUMP field: %m");
943 }
944
945 if (path) {
946 const char *vt;
947
948 /* Create a temporary file to write the uncompressed core to. */
949
950 r = var_tmp_dir(&vt);
951 if (r < 0)
952 return log_error_errno(r, "Failed to acquire temporary directory path: %m");
953
954 temp = path_join(vt, "coredump-XXXXXX");
955 if (!temp)
956 return log_oom();
957
958 fdt = mkostemp_safe(temp);
959 if (fdt < 0)
960 return log_error_errno(fdt, "Failed to create temporary file: %m");
961 log_debug("Created temporary file %s", temp);
962
963 fd = fdt;
964 } else {
965 /* If neither path or file are specified, we will write to stdout. Let's now check
966 * if stdout is connected to a tty. We checked that the file exists, or that the
967 * core might be stored in the journal. In this second case, if we found the entry,
968 * in all likelihood we will be able to access the COREDUMP= field. In either case,
969 * we stop before doing any "real" work, i.e. before starting decompression or
970 * reading from the file or creating temporary files.
971 */
972 if (!file) {
973 if (on_tty())
974 return log_error_errno(SYNTHETIC_ERRNO(ENOTTY),
975 "Refusing to dump core to tty"
976 " (use shell redirection or specify --output).");
977 file = stdout;
978 }
979
980 fd = fileno(file);
981 }
982
983 if (filename) {
984 #if HAVE_COMPRESSION
985 _cleanup_close_ int fdf = -1;
986
987 fdf = open(filename, O_RDONLY | O_CLOEXEC);
988 if (fdf < 0) {
989 r = log_error_errno(errno, "Failed to open %s: %m", filename);
990 goto error;
991 }
992
993 r = decompress_stream(filename, fdf, fd, -1);
994 if (r < 0) {
995 log_error_errno(r, "Failed to decompress %s: %m", filename);
996 goto error;
997 }
998 #else
999 r = log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
1000 "Cannot decompress file. Compiled without compression support.");
1001 goto error;
1002 #endif
1003 } else {
1004 ssize_t sz;
1005
1006 /* We want full data, nothing truncated. */
1007 sd_journal_set_data_threshold(j, 0);
1008
1009 r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
1010 if (r < 0)
1011 return log_error_errno(r, "Failed to retrieve COREDUMP field: %m");
1012
1013 assert(len >= 9);
1014 data += 9;
1015 len -= 9;
1016
1017 sz = write(fd, data, len);
1018 if (sz < 0) {
1019 r = log_error_errno(errno, "Failed to write output: %m");
1020 goto error;
1021 }
1022 if (sz != (ssize_t) len) {
1023 log_error("Short write to output.");
1024 r = -EIO;
1025 goto error;
1026 }
1027 }
1028
1029 if (temp) {
1030 *path = temp;
1031 *unlink_temp = true;
1032 }
1033 return 0;
1034
1035 error:
1036 if (temp) {
1037 (void) unlink(temp);
1038 log_debug("Removed temporary file %s", temp);
1039 }
1040 return r;
1041 }
1042
dump_core(int argc,char ** argv,void * userdata)1043 static int dump_core(int argc, char **argv, void *userdata) {
1044 _cleanup_(sd_journal_closep) sd_journal *j = NULL;
1045 _cleanup_fclose_ FILE *f = NULL;
1046 int r;
1047
1048 if (arg_field)
1049 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1050 "Option --field/-F only makes sense with list");
1051
1052 r = acquire_journal(&j, argv + 1);
1053 if (r < 0)
1054 return r;
1055
1056 r = focus(j);
1057 if (r < 0)
1058 return r;
1059
1060 if (arg_output) {
1061 f = fopen(arg_output, "we");
1062 if (!f)
1063 return log_error_errno(errno, "Failed to open \"%s\" for writing: %m", arg_output);
1064 }
1065
1066 print_info(f ? stdout : stderr, j, false);
1067
1068 r = save_core(j, f, NULL, NULL);
1069 if (r < 0)
1070 return r;
1071
1072 r = sd_journal_previous(j);
1073 if (r > 0 && !arg_quiet)
1074 log_notice("More than one entry matches, ignoring rest.");
1075
1076 return 0;
1077 }
1078
run_debug(int argc,char ** argv,void * userdata)1079 static int run_debug(int argc, char **argv, void *userdata) {
1080 _cleanup_(sd_journal_closep) sd_journal *j = NULL;
1081 _cleanup_free_ char *exe = NULL, *path = NULL;
1082 _cleanup_strv_free_ char **debugger_call = NULL;
1083 bool unlink_path = false;
1084 const char *data, *fork_name;
1085 size_t len;
1086 pid_t pid;
1087 int r;
1088
1089 if (!arg_debugger) {
1090 char *env_debugger;
1091
1092 env_debugger = getenv("SYSTEMD_DEBUGGER");
1093 if (env_debugger)
1094 arg_debugger = env_debugger;
1095 else
1096 arg_debugger = "gdb";
1097 }
1098
1099 r = strv_extend(&debugger_call, arg_debugger);
1100 if (r < 0)
1101 return log_oom();
1102
1103 r = strv_extend_strv(&debugger_call, arg_debugger_args, false);
1104 if (r < 0)
1105 return log_oom();
1106
1107 if (arg_field)
1108 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1109 "Option --field/-F only makes sense with list");
1110
1111 r = acquire_journal(&j, argv + 1);
1112 if (r < 0)
1113 return r;
1114
1115 r = focus(j);
1116 if (r < 0)
1117 return r;
1118
1119 if (!arg_quiet) {
1120 print_info(stdout, j, false);
1121 fputs("\n", stdout);
1122 }
1123
1124 r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len);
1125 if (r < 0)
1126 return log_error_errno(r, "Failed to retrieve COREDUMP_EXE field: %m");
1127
1128 assert(len > STRLEN("COREDUMP_EXE="));
1129 data += STRLEN("COREDUMP_EXE=");
1130 len -= STRLEN("COREDUMP_EXE=");
1131
1132 exe = strndup(data, len);
1133 if (!exe)
1134 return log_oom();
1135
1136 if (endswith(exe, " (deleted)"))
1137 return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
1138 "Binary already deleted.");
1139
1140 if (!path_is_absolute(exe))
1141 return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
1142 "Binary is not an absolute path.");
1143
1144 r = save_core(j, NULL, &path, &unlink_path);
1145 if (r < 0)
1146 return r;
1147
1148 r = strv_extend_strv(&debugger_call, STRV_MAKE(exe, "-c", path), false);
1149 if (r < 0)
1150 return log_oom();
1151
1152 /* Don't interfere with gdb and its handling of SIGINT. */
1153 (void) ignore_signals(SIGINT);
1154
1155 fork_name = strjoina("(", debugger_call[0], ")");
1156
1157 r = safe_fork(fork_name, FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_CLOSE_ALL_FDS|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG|FORK_FLUSH_STDIO, &pid);
1158 if (r < 0)
1159 goto finish;
1160 if (r == 0) {
1161 execvp(debugger_call[0], debugger_call);
1162 log_open();
1163 log_error_errno(errno, "Failed to invoke %s: %m", debugger_call[0]);
1164 _exit(EXIT_FAILURE);
1165 }
1166
1167 r = wait_for_terminate_and_check(debugger_call[0], pid, WAIT_LOG_ABNORMAL);
1168
1169 finish:
1170 (void) default_signals(SIGINT);
1171
1172 if (unlink_path) {
1173 log_debug("Removed temporary file %s", path);
1174 (void) unlink(path);
1175 }
1176
1177 return r;
1178 }
1179
check_units_active(void)1180 static int check_units_active(void) {
1181 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1182 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
1183 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1184 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
1185 int c = 0, r;
1186 const char *id, *state, *substate;
1187
1188 if (arg_quiet)
1189 return false;
1190
1191 r = sd_bus_default_system(&bus);
1192 if (r == -ENOENT) {
1193 log_debug("D-Bus is not running, skipping active unit check");
1194 return 0;
1195 }
1196 if (r < 0)
1197 return log_error_errno(r, "Failed to acquire bus: %m");
1198
1199 r = sd_bus_message_new_method_call(
1200 bus,
1201 &m,
1202 "org.freedesktop.systemd1",
1203 "/org/freedesktop/systemd1",
1204 "org.freedesktop.systemd1.Manager",
1205 "ListUnitsByPatterns");
1206 if (r < 0)
1207 return bus_log_create_error(r);
1208
1209 r = sd_bus_message_append_strv(m, NULL);
1210 if (r < 0)
1211 return bus_log_create_error(r);
1212
1213 r = sd_bus_message_append_strv(m, STRV_MAKE("systemd-coredump@*.service"));
1214 if (r < 0)
1215 return bus_log_create_error(r);
1216
1217 r = sd_bus_call(bus, m, SHORT_BUS_CALL_TIMEOUT_USEC, &error, &reply);
1218 if (r < 0)
1219 return log_error_errno(r, "Failed to check if any systemd-coredump@.service units are running: %s",
1220 bus_error_message(&error, r));
1221
1222 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
1223 if (r < 0)
1224 return bus_log_parse_error(r);
1225
1226 while ((r = sd_bus_message_read(
1227 reply, "(ssssssouso)",
1228 &id, NULL, NULL, &state, &substate,
1229 NULL, NULL, NULL, NULL, NULL)) > 0) {
1230 bool found = !STR_IN_SET(state, "inactive", "dead", "failed");
1231 log_debug("Unit %s is %s/%s, %scounting it.", id, state, substate, found ? "" : "not ");
1232 c += found;
1233 }
1234 if (r < 0)
1235 return bus_log_parse_error(r);
1236
1237 r = sd_bus_message_exit_container(reply);
1238 if (r < 0)
1239 return bus_log_parse_error(r);
1240
1241 return c;
1242 }
1243
coredumpctl_main(int argc,char * argv[])1244 static int coredumpctl_main(int argc, char *argv[]) {
1245
1246 static const Verb verbs[] = {
1247 { "list", VERB_ANY, VERB_ANY, VERB_DEFAULT, dump_list },
1248 { "info", VERB_ANY, VERB_ANY, 0, dump_list },
1249 { "dump", VERB_ANY, VERB_ANY, 0, dump_core },
1250 { "debug", VERB_ANY, VERB_ANY, 0, run_debug },
1251 { "gdb", VERB_ANY, VERB_ANY, 0, run_debug },
1252 { "help", VERB_ANY, 1, 0, verb_help },
1253 {}
1254 };
1255
1256 return dispatch_verb(argc, argv, verbs, NULL);
1257 }
1258
run(int argc,char * argv[])1259 static int run(int argc, char *argv[]) {
1260 int r, units_active;
1261
1262 setlocale(LC_ALL, "");
1263 log_setup();
1264
1265 /* The journal merging logic potentially needs a lot of fds. */
1266 (void) rlimit_nofile_bump(HIGH_RLIMIT_NOFILE);
1267
1268 r = parse_argv(argc, argv);
1269 if (r <= 0)
1270 return r;
1271
1272 sigbus_install();
1273
1274 units_active = check_units_active(); /* error is treated the same as 0 */
1275
1276 r = coredumpctl_main(argc, argv);
1277
1278 if (units_active > 0)
1279 printf("%s-- Notice: %d systemd-coredump@.service %s, output may be incomplete.%s\n",
1280 ansi_highlight_red(),
1281 units_active, units_active == 1 ? "unit is running" : "units are running",
1282 ansi_normal());
1283
1284 return r;
1285 }
1286
1287 DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
1288