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