1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2012 Zbigniew Jędrzejewski-Szmek
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.
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.
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/>.
29 #include "sd-journal.h"
31 #include "alloc-util.h"
35 #include "journal-internal.h"
39 #include "parse-util.h"
40 #include "path-util.h"
41 #include "process-util.h"
44 #include "signal-util.h"
45 #include "string-util.h"
46 #include "terminal-util.h"
47 #include "user-util.h"
56 } arg_action
= ACTION_LIST
;
57 static const char* arg_field
= NULL
;
58 static const char *arg_directory
= NULL
;
59 static int arg_no_pager
= false;
60 static int arg_no_legend
= false;
61 static int arg_one
= false;
62 static FILE* arg_output
= NULL
;
64 static Set
*new_matches(void) {
75 tmp
= strdup("MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1");
82 r
= set_consume(set
, tmp
);
84 log_error_errno(r
, "failed to add to set: %m");
92 static int add_match(Set
*set
, const char *match
) {
93 _cleanup_free_
char *p
= NULL
;
99 if (strchr(match
, '='))
101 else if (strchr(match
, '/')) {
102 r
= path_make_absolute_cwd(match
, &p
);
106 prefix
= "COREDUMP_EXE=";
107 } else if (parse_pid(match
, &pid
) >= 0)
108 prefix
= "COREDUMP_PID=";
110 prefix
= "COREDUMP_COMM=";
112 pattern
= strjoin(prefix
, match
, NULL
);
118 log_debug("Adding pattern: %s", pattern
);
119 r
= set_consume(set
, pattern
);
125 return log_error_errno(r
, "Failed to add match: %m");
128 static void help(void) {
129 printf("%s [OPTIONS...]\n\n"
130 "List or retrieve coredumps from the journal.\n\n"
132 " -h --help Show this help\n"
133 " --version Print version string\n"
134 " --no-pager Do not pipe output into a pager\n"
135 " --no-legend Do not print the column headers.\n"
136 " -1 Show information about most recent entry only\n"
137 " -F --field=FIELD List all values a certain field takes\n"
138 " -o --output=FILE Write output to FILE\n\n"
139 " -D --directory=DIR Use journal files from directory\n\n"
142 " list [MATCHES...] List available coredumps (default)\n"
143 " info [MATCHES...] Show detailed information about one or more coredumps\n"
144 " dump [MATCHES...] Print first matching coredump to stdout\n"
145 " gdb [MATCHES...] Start gdb for the first matching coredump\n"
146 , program_invocation_short_name
);
149 static int parse_argv(int argc
, char *argv
[], Set
*matches
) {
158 static const struct option options
[] = {
159 { "help", no_argument
, NULL
, 'h' },
160 { "version" , no_argument
, NULL
, ARG_VERSION
},
161 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
162 { "no-legend", no_argument
, NULL
, ARG_NO_LEGEND
},
163 { "output", required_argument
, NULL
, 'o' },
164 { "field", required_argument
, NULL
, 'F' },
165 { "directory", required_argument
, NULL
, 'D' },
172 while ((c
= getopt_long(argc
, argv
, "ho:F:1D:", options
, NULL
)) >= 0)
176 arg_action
= ACTION_NONE
;
181 arg_action
= ACTION_NONE
;
189 arg_no_legend
= true;
194 log_error("cannot set output more than once");
198 arg_output
= fopen(optarg
, "we");
200 return log_error_errno(errno
, "writing to '%s': %m", optarg
);
206 log_error("cannot use --field/-F more than once");
217 arg_directory
= optarg
;
224 assert_not_reached("Unhandled option");
228 const char *cmd
= argv
[optind
++];
229 if (streq(cmd
, "list"))
230 arg_action
= ACTION_LIST
;
231 else if (streq(cmd
, "dump"))
232 arg_action
= ACTION_DUMP
;
233 else if (streq(cmd
, "gdb"))
234 arg_action
= ACTION_GDB
;
235 else if (streq(cmd
, "info"))
236 arg_action
= ACTION_INFO
;
238 log_error("Unknown action '%s'", cmd
);
243 if (arg_field
&& arg_action
!= ACTION_LIST
) {
244 log_error("Option --field/-F only makes sense with list");
248 while (optind
< argc
) {
249 r
= add_match(matches
, argv
[optind
]);
258 static int retrieve(const void *data
,
266 ident
= strlen(name
) + 1; /* name + "=" */
271 if (memcmp(data
, name
, ident
- 1) != 0)
274 if (((const char*) data
)[ident
- 1] != '=')
277 v
= strndup((const char*)data
+ ident
, len
- ident
);
287 static void print_field(FILE* file
, sd_journal
*j
) {
288 _cleanup_free_
char *value
= NULL
;
297 SD_JOURNAL_FOREACH_DATA(j
, d
, l
)
298 retrieve(d
, l
, arg_field
, &value
);
301 fprintf(file
, "%s\n", value
);
304 static int print_list(FILE* file
, sd_journal
*j
, int had_legend
) {
306 *pid
= NULL
, *uid
= NULL
, *gid
= NULL
,
307 *sgnl
= NULL
, *exe
= NULL
, *comm
= NULL
, *cmdline
= NULL
,
312 char buf
[FORMAT_TIMESTAMP_MAX
];
319 SD_JOURNAL_FOREACH_DATA(j
, d
, l
) {
320 retrieve(d
, l
, "COREDUMP_PID", &pid
);
321 retrieve(d
, l
, "COREDUMP_UID", &uid
);
322 retrieve(d
, l
, "COREDUMP_GID", &gid
);
323 retrieve(d
, l
, "COREDUMP_SIGNAL", &sgnl
);
324 retrieve(d
, l
, "COREDUMP_EXE", &exe
);
325 retrieve(d
, l
, "COREDUMP_COMM", &comm
);
326 retrieve(d
, l
, "COREDUMP_CMDLINE", &cmdline
);
327 retrieve(d
, l
, "COREDUMP_FILENAME", &filename
);
330 if (!pid
&& !uid
&& !gid
&& !sgnl
&& !exe
&& !comm
&& !cmdline
&& !filename
) {
331 log_warning("Empty coredump log entry");
335 r
= sd_journal_get_realtime_usec(j
, &t
);
337 return log_error_errno(r
, "Failed to get realtime timestamp: %m");
339 format_timestamp(buf
, sizeof(buf
), t
);
340 present
= filename
&& access(filename
, F_OK
) == 0;
342 if (!had_legend
&& !arg_no_legend
)
343 fprintf(file
, "%-*s %*s %*s %*s %*s %*s %s\n",
344 FORMAT_TIMESTAMP_WIDTH
, "TIME",
352 fprintf(file
, "%-*s %*s %*s %*s %*s %*s %s\n",
353 FORMAT_TIMESTAMP_WIDTH
, buf
,
358 1, present
? "*" : "",
359 strna(exe
?: (comm
?: cmdline
)));
364 static int print_info(FILE *file
, sd_journal
*j
, bool need_space
) {
366 *pid
= NULL
, *uid
= NULL
, *gid
= NULL
,
367 *sgnl
= NULL
, *exe
= NULL
, *comm
= NULL
, *cmdline
= NULL
,
368 *unit
= NULL
, *user_unit
= NULL
, *session
= NULL
,
369 *boot_id
= NULL
, *machine_id
= NULL
, *hostname
= NULL
,
370 *slice
= NULL
, *cgroup
= NULL
, *owner_uid
= NULL
,
371 *message
= NULL
, *timestamp
= NULL
, *filename
= NULL
;
379 SD_JOURNAL_FOREACH_DATA(j
, d
, l
) {
380 retrieve(d
, l
, "COREDUMP_PID", &pid
);
381 retrieve(d
, l
, "COREDUMP_UID", &uid
);
382 retrieve(d
, l
, "COREDUMP_GID", &gid
);
383 retrieve(d
, l
, "COREDUMP_SIGNAL", &sgnl
);
384 retrieve(d
, l
, "COREDUMP_EXE", &exe
);
385 retrieve(d
, l
, "COREDUMP_COMM", &comm
);
386 retrieve(d
, l
, "COREDUMP_CMDLINE", &cmdline
);
387 retrieve(d
, l
, "COREDUMP_UNIT", &unit
);
388 retrieve(d
, l
, "COREDUMP_USER_UNIT", &user_unit
);
389 retrieve(d
, l
, "COREDUMP_SESSION", &session
);
390 retrieve(d
, l
, "COREDUMP_OWNER_UID", &owner_uid
);
391 retrieve(d
, l
, "COREDUMP_SLICE", &slice
);
392 retrieve(d
, l
, "COREDUMP_CGROUP", &cgroup
);
393 retrieve(d
, l
, "COREDUMP_TIMESTAMP", ×tamp
);
394 retrieve(d
, l
, "COREDUMP_FILENAME", &filename
);
395 retrieve(d
, l
, "_BOOT_ID", &boot_id
);
396 retrieve(d
, l
, "_MACHINE_ID", &machine_id
);
397 retrieve(d
, l
, "_HOSTNAME", &hostname
);
398 retrieve(d
, l
, "MESSAGE", &message
);
406 " PID: %s%s%s (%s)\n",
407 ansi_highlight(), strna(pid
), ansi_normal(), comm
);
411 ansi_highlight(), strna(pid
), ansi_normal());
416 if (parse_uid(uid
, &n
) >= 0) {
417 _cleanup_free_
char *u
= NULL
;
433 if (parse_gid(gid
, &n
) >= 0) {
434 _cleanup_free_
char *g
= NULL
;
450 if (safe_atoi(sgnl
, &sig
) >= 0)
451 fprintf(file
, " Signal: %s (%s)\n", sgnl
, signal_to_string(sig
));
453 fprintf(file
, " Signal: %s\n", sgnl
);
459 r
= safe_atou64(timestamp
, &u
);
461 char absolute
[FORMAT_TIMESTAMP_MAX
], relative
[FORMAT_TIMESPAN_MAX
];
464 " Timestamp: %s (%s)\n",
465 format_timestamp(absolute
, sizeof(absolute
), u
),
466 format_timestamp_relative(relative
, sizeof(relative
), u
));
469 fprintf(file
, " Timestamp: %s\n", timestamp
);
473 fprintf(file
, " Command Line: %s\n", cmdline
);
475 fprintf(file
, " Executable: %s%s%s\n", ansi_highlight(), exe
, ansi_normal());
477 fprintf(file
, " Control Group: %s\n", cgroup
);
479 fprintf(file
, " Unit: %s\n", unit
);
481 fprintf(file
, " User Unit: %s\n", unit
);
483 fprintf(file
, " Slice: %s\n", slice
);
485 fprintf(file
, " Session: %s\n", session
);
489 if (parse_uid(owner_uid
, &n
) >= 0) {
490 _cleanup_free_
char *u
= NULL
;
494 " Owner UID: %s (%s)\n",
503 fprintf(file
, " Boot ID: %s\n", boot_id
);
505 fprintf(file
, " Machine ID: %s\n", machine_id
);
507 fprintf(file
, " Hostname: %s\n", hostname
);
509 if (filename
&& access(filename
, F_OK
) == 0)
510 fprintf(file
, " Coredump: %s\n", filename
);
513 _cleanup_free_
char *m
= NULL
;
515 m
= strreplace(message
, "\n", "\n ");
517 fprintf(file
, " Message: %s\n", strstrip(m
?: message
));
523 static int focus(sd_journal
*j
) {
526 r
= sd_journal_seek_tail(j
);
528 r
= sd_journal_previous(j
);
530 return log_error_errno(r
, "Failed to search journal: %m");
532 log_error("No match found.");
538 static void print_entry(sd_journal
*j
, unsigned n_found
) {
541 if (arg_action
== ACTION_INFO
)
542 print_info(stdout
, j
, n_found
);
544 print_field(stdout
, j
);
546 print_list(stdout
, j
, n_found
);
549 static int dump_list(sd_journal
*j
) {
550 unsigned n_found
= 0;
555 /* The coredumps are likely to compressed, and for just
556 * listing them we don't need to decompress them, so let's
557 * pick a fairly low data threshold here */
558 sd_journal_set_data_threshold(j
, 4096);
567 SD_JOURNAL_FOREACH(j
)
568 print_entry(j
, n_found
++);
570 if (!arg_field
&& n_found
<= 0) {
571 log_notice("No coredumps found.");
579 static int save_core(sd_journal
*j
, int fd
, char **path
, bool *unlink_temp
) {
581 _cleanup_free_
char *filename
= NULL
;
585 assert((fd
>= 0) != !!path
);
586 assert(!!path
== !!unlink_temp
);
588 /* Prefer uncompressed file to journal (probably cached) to
589 * compressed file (probably uncached). */
590 r
= sd_journal_get_data(j
, "COREDUMP_FILENAME", (const void**) &data
, &len
);
591 if (r
< 0 && r
!= -ENOENT
)
592 log_warning_errno(r
, "Failed to retrieve COREDUMP_FILENAME: %m");
594 retrieve(data
, len
, "COREDUMP_FILENAME", &filename
);
596 if (filename
&& access(filename
, R_OK
) < 0) {
597 log_full(errno
== ENOENT
? LOG_DEBUG
: LOG_WARNING
,
598 "File %s is not readable: %m", filename
);
599 filename
= mfree(filename
);
602 if (filename
&& !endswith(filename
, ".xz") && !endswith(filename
, ".lz4")) {
610 _cleanup_close_
int fdt
= -1;
614 temp
= strdup("/var/tmp/coredump-XXXXXX");
618 fdt
= mkostemp_safe(temp
, O_WRONLY
|O_CLOEXEC
);
620 return log_error_errno(fdt
, "Failed to create temporary file: %m");
621 log_debug("Created temporary file %s", temp
);
626 r
= sd_journal_get_data(j
, "COREDUMP", (const void**) &data
, &len
);
634 sz
= write(fdt
, data
, len
);
636 r
= log_error_errno(errno
,
637 "Failed to write temporary file: %m");
640 if (sz
!= (ssize_t
) len
) {
641 log_error("Short write to temporary file.");
645 } else if (filename
) {
646 #if defined(HAVE_XZ) || defined(HAVE_LZ4)
647 _cleanup_close_
int fdf
;
649 fdf
= open(filename
, O_RDONLY
| O_CLOEXEC
);
651 r
= log_error_errno(errno
,
652 "Failed to open %s: %m",
657 r
= decompress_stream(filename
, fdf
, fd
, -1);
659 log_error_errno(r
, "Failed to decompress %s: %m", filename
);
663 log_error("Cannot decompress file. Compiled without compression support.");
669 log_error("Cannot retrieve coredump from journal nor disk.");
671 log_error_errno(r
, "Failed to retrieve COREDUMP field: %m");
685 log_debug("Removed temporary file %s", temp
);
691 static int dump_core(sd_journal
* j
) {
700 print_info(arg_output
? stdout
: stderr
, j
, false);
702 if (on_tty() && !arg_output
) {
703 log_error("Refusing to dump core to tty.");
707 r
= save_core(j
, arg_output
? fileno(arg_output
) : STDOUT_FILENO
, NULL
, NULL
);
709 return log_error_errno(r
, "Coredump retrieval failed: %m");
711 r
= sd_journal_previous(j
);
713 log_warning("More than one entry matches, ignoring rest.");
718 static int run_gdb(sd_journal
*j
) {
719 _cleanup_free_
char *exe
= NULL
, *path
= NULL
;
720 bool unlink_path
= false;
733 print_info(stdout
, j
, false);
736 r
= sd_journal_get_data(j
, "COREDUMP_EXE", (const void**) &data
, &len
);
738 return log_error_errno(r
, "Failed to retrieve COREDUMP_EXE field: %m");
740 assert(len
> strlen("COREDUMP_EXE="));
741 data
+= strlen("COREDUMP_EXE=");
742 len
-= strlen("COREDUMP_EXE=");
744 exe
= strndup(data
, len
);
748 if (endswith(exe
, " (deleted)")) {
749 log_error("Binary already deleted.");
753 if (!path_is_absolute(exe
)) {
754 log_error("Binary is not an absolute path.");
758 r
= save_core(j
, -1, &path
, &unlink_path
);
760 return log_error_errno(r
, "Failed to retrieve core: %m");
764 r
= log_error_errno(errno
, "Failed to fork(): %m");
768 (void) reset_all_signal_handlers();
769 (void) reset_signal_mask();
771 execlp("gdb", "gdb", exe
, path
, NULL
);
773 log_error_errno(errno
, "Failed to invoke gdb: %m");
777 r
= wait_for_terminate(pid
, &st
);
779 log_error_errno(r
, "Failed to wait for gdb: %m");
783 r
= st
.si_code
== CLD_EXITED
? st
.si_status
: 255;
787 log_debug("Removed temporary file %s", path
);
794 int main(int argc
, char *argv
[]) {
795 _cleanup_journal_close_ sd_journal
*j
= NULL
;
799 _cleanup_set_free_free_ Set
*matches
= NULL
;
801 setlocale(LC_ALL
, "");
802 log_parse_environment();
805 matches
= new_matches();
811 r
= parse_argv(argc
, argv
, matches
);
815 if (arg_action
== ACTION_NONE
)
821 r
= sd_journal_open_directory(&j
, arg_directory
, 0);
823 log_error_errno(r
, "Failed to open journals in directory: %s: %m", arg_directory
);
827 r
= sd_journal_open(&j
, SD_JOURNAL_LOCAL_ONLY
);
829 log_error_errno(r
, "Failed to open journal: %m");
834 /* We want full data, nothing truncated. */
835 sd_journal_set_data_threshold(j
, 0);
837 SET_FOREACH(match
, matches
, it
) {
838 r
= sd_journal_add_match(j
, match
, strlen(match
));
840 log_error_errno(r
, "Failed to add match '%s': %m",
846 if (_unlikely_(log_get_max_level() >= LOG_DEBUG
)) {
847 _cleanup_free_
char *filter
;
849 filter
= journal_make_match_string(j
);
850 log_debug("Journal filter: %s", filter
);
872 assert_not_reached("Shouldn't be here");
881 return r
>= 0 ? r
: EXIT_FAILURE
;