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"
34 #include "path-util.h"
37 #include "journal-internal.h"
40 #include "process-util.h"
41 #include "terminal-util.h"
42 #include "signal-util.h"
50 } arg_action
= ACTION_LIST
;
51 static const char* arg_field
= NULL
;
52 static int arg_no_pager
= false;
53 static int arg_no_legend
= false;
54 static int arg_one
= false;
55 static FILE* arg_output
= NULL
;
57 static Set
*new_matches(void) {
68 tmp
= strdup("MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1");
75 r
= set_consume(set
, tmp
);
77 log_error_errno(r
, "failed to add to set: %m");
85 static int add_match(Set
*set
, const char *match
) {
90 _cleanup_free_
char *p
= NULL
;
92 if (strchr(match
, '='))
94 else if (strchr(match
, '/')) {
95 p
= path_make_absolute_cwd(match
);
100 prefix
= "COREDUMP_EXE=";
102 else if (safe_atou(match
, &pid
) == 0)
103 prefix
= "COREDUMP_PID=";
105 prefix
= "COREDUMP_COMM=";
107 pattern
= strjoin(prefix
, match
, NULL
);
111 log_debug("Adding pattern: %s", pattern
);
112 r
= set_consume(set
, pattern
);
114 log_error_errno(r
, "Failed to add pattern: %m");
120 return log_error_errno(r
, "Failed to add match: %m");
123 static void help(void) {
124 printf("%s [OPTIONS...]\n\n"
125 "List or retrieve coredumps from the journal.\n\n"
127 " -h --help Show this help\n"
128 " --version Print version string\n"
129 " --no-pager Do not pipe output into a pager\n"
130 " --no-legend Do not print the column headers.\n"
131 " -1 Show information about most recent entry only\n"
132 " -F --field=FIELD List all values a certain field takes\n"
133 " -o --output=FILE Write output to FILE\n\n"
136 " list [MATCHES...] List available coredumps (default)\n"
137 " info [MATCHES...] Show detailed information about one or more coredumps\n"
138 " dump [MATCHES...] Print first matching coredump to stdout\n"
139 " gdb [MATCHES...] Start gdb for the first matching coredump\n"
140 , program_invocation_short_name
);
143 static int parse_argv(int argc
, char *argv
[], Set
*matches
) {
152 static const struct option options
[] = {
153 { "help", no_argument
, NULL
, 'h' },
154 { "version" , no_argument
, NULL
, ARG_VERSION
},
155 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
156 { "no-legend", no_argument
, NULL
, ARG_NO_LEGEND
},
157 { "output", required_argument
, NULL
, 'o' },
158 { "field", required_argument
, NULL
, 'F' },
165 while ((c
= getopt_long(argc
, argv
, "ho:F:1", options
, NULL
)) >= 0)
169 arg_action
= ACTION_NONE
;
174 arg_action
= ACTION_NONE
;
175 puts(PACKAGE_STRING
);
176 puts(SYSTEMD_FEATURES
);
184 arg_no_legend
= true;
189 log_error("cannot set output more than once");
193 arg_output
= fopen(optarg
, "we");
195 return log_error_errno(errno
, "writing to '%s': %m", optarg
);
201 log_error("cannot use --field/-F more than once");
215 assert_not_reached("Unhandled option");
219 const char *cmd
= argv
[optind
++];
220 if (streq(cmd
, "list"))
221 arg_action
= ACTION_LIST
;
222 else if (streq(cmd
, "dump"))
223 arg_action
= ACTION_DUMP
;
224 else if (streq(cmd
, "gdb"))
225 arg_action
= ACTION_GDB
;
226 else if (streq(cmd
, "info"))
227 arg_action
= ACTION_INFO
;
229 log_error("Unknown action '%s'", cmd
);
234 if (arg_field
&& arg_action
!= ACTION_LIST
) {
235 log_error("Option --field/-F only makes sense with list");
239 while (optind
< argc
) {
240 r
= add_match(matches
, argv
[optind
]);
249 static int retrieve(const void *data
,
257 ident
= strlen(name
) + 1; /* name + "=" */
262 if (memcmp(data
, name
, ident
- 1) != 0)
265 if (((const char*) data
)[ident
- 1] != '=')
268 v
= strndup((const char*)data
+ ident
, len
- ident
);
278 static void print_field(FILE* file
, sd_journal
*j
) {
279 _cleanup_free_
char *value
= NULL
;
288 SD_JOURNAL_FOREACH_DATA(j
, d
, l
)
289 retrieve(d
, l
, arg_field
, &value
);
292 fprintf(file
, "%s\n", value
);
295 static int print_list(FILE* file
, sd_journal
*j
, int had_legend
) {
297 *pid
= NULL
, *uid
= NULL
, *gid
= NULL
,
298 *sgnl
= NULL
, *exe
= NULL
, *comm
= NULL
, *cmdline
= NULL
,
303 char buf
[FORMAT_TIMESTAMP_MAX
];
310 SD_JOURNAL_FOREACH_DATA(j
, d
, l
) {
311 retrieve(d
, l
, "COREDUMP_PID", &pid
);
312 retrieve(d
, l
, "COREDUMP_UID", &uid
);
313 retrieve(d
, l
, "COREDUMP_GID", &gid
);
314 retrieve(d
, l
, "COREDUMP_SIGNAL", &sgnl
);
315 retrieve(d
, l
, "COREDUMP_EXE", &exe
);
316 retrieve(d
, l
, "COREDUMP_COMM", &comm
);
317 retrieve(d
, l
, "COREDUMP_CMDLINE", &cmdline
);
318 retrieve(d
, l
, "COREDUMP_FILENAME", &filename
);
321 if (!pid
&& !uid
&& !gid
&& !sgnl
&& !exe
&& !comm
&& !cmdline
&& !filename
) {
322 log_warning("Empty coredump log entry");
326 r
= sd_journal_get_realtime_usec(j
, &t
);
328 return log_error_errno(r
, "Failed to get realtime timestamp: %m");
330 format_timestamp(buf
, sizeof(buf
), t
);
331 present
= filename
&& access(filename
, F_OK
) == 0;
333 if (!had_legend
&& !arg_no_legend
)
334 fprintf(file
, "%-*s %*s %*s %*s %*s %*s %s\n",
335 FORMAT_TIMESTAMP_WIDTH
, "TIME",
343 fprintf(file
, "%-*s %*s %*s %*s %*s %*s %s\n",
344 FORMAT_TIMESTAMP_WIDTH
, buf
,
349 1, present
? "*" : "",
350 strna(exe
?: (comm
?: cmdline
)));
355 static int print_info(FILE *file
, sd_journal
*j
, bool need_space
) {
357 *pid
= NULL
, *uid
= NULL
, *gid
= NULL
,
358 *sgnl
= NULL
, *exe
= NULL
, *comm
= NULL
, *cmdline
= NULL
,
359 *unit
= NULL
, *user_unit
= NULL
, *session
= NULL
,
360 *boot_id
= NULL
, *machine_id
= NULL
, *hostname
= NULL
,
361 *slice
= NULL
, *cgroup
= NULL
, *owner_uid
= NULL
,
362 *message
= NULL
, *timestamp
= NULL
, *filename
= NULL
;
370 SD_JOURNAL_FOREACH_DATA(j
, d
, l
) {
371 retrieve(d
, l
, "COREDUMP_PID", &pid
);
372 retrieve(d
, l
, "COREDUMP_UID", &uid
);
373 retrieve(d
, l
, "COREDUMP_GID", &gid
);
374 retrieve(d
, l
, "COREDUMP_SIGNAL", &sgnl
);
375 retrieve(d
, l
, "COREDUMP_EXE", &exe
);
376 retrieve(d
, l
, "COREDUMP_COMM", &comm
);
377 retrieve(d
, l
, "COREDUMP_CMDLINE", &cmdline
);
378 retrieve(d
, l
, "COREDUMP_UNIT", &unit
);
379 retrieve(d
, l
, "COREDUMP_USER_UNIT", &user_unit
);
380 retrieve(d
, l
, "COREDUMP_SESSION", &session
);
381 retrieve(d
, l
, "COREDUMP_OWNER_UID", &owner_uid
);
382 retrieve(d
, l
, "COREDUMP_SLICE", &slice
);
383 retrieve(d
, l
, "COREDUMP_CGROUP", &cgroup
);
384 retrieve(d
, l
, "COREDUMP_TIMESTAMP", ×tamp
);
385 retrieve(d
, l
, "COREDUMP_FILENAME", &filename
);
386 retrieve(d
, l
, "_BOOT_ID", &boot_id
);
387 retrieve(d
, l
, "_MACHINE_ID", &machine_id
);
388 retrieve(d
, l
, "_HOSTNAME", &hostname
);
389 retrieve(d
, l
, "MESSAGE", &message
);
397 " PID: %s%s%s (%s)\n",
398 ansi_highlight(), strna(pid
), ansi_highlight_off(), comm
);
402 ansi_highlight(), strna(pid
), ansi_highlight_off());
407 if (parse_uid(uid
, &n
) >= 0) {
408 _cleanup_free_
char *u
= NULL
;
424 if (parse_gid(gid
, &n
) >= 0) {
425 _cleanup_free_
char *g
= NULL
;
441 if (safe_atoi(sgnl
, &sig
) >= 0)
442 fprintf(file
, " Signal: %s (%s)\n", sgnl
, signal_to_string(sig
));
444 fprintf(file
, " Signal: %s\n", sgnl
);
450 r
= safe_atou64(timestamp
, &u
);
452 char absolute
[FORMAT_TIMESTAMP_MAX
], relative
[FORMAT_TIMESPAN_MAX
];
455 " Timestamp: %s (%s)\n",
456 format_timestamp(absolute
, sizeof(absolute
), u
),
457 format_timestamp_relative(relative
, sizeof(relative
), u
));
460 fprintf(file
, " Timestamp: %s\n", timestamp
);
464 fprintf(file
, " Command Line: %s\n", cmdline
);
466 fprintf(file
, " Executable: %s%s%s\n", ansi_highlight(), exe
, ansi_highlight_off());
468 fprintf(file
, " Control Group: %s\n", cgroup
);
470 fprintf(file
, " Unit: %s\n", unit
);
472 fprintf(file
, " User Unit: %s\n", unit
);
474 fprintf(file
, " Slice: %s\n", slice
);
476 fprintf(file
, " Session: %s\n", session
);
480 if (parse_uid(owner_uid
, &n
) >= 0) {
481 _cleanup_free_
char *u
= NULL
;
485 " Owner UID: %s (%s)\n",
494 fprintf(file
, " Boot ID: %s\n", boot_id
);
496 fprintf(file
, " Machine ID: %s\n", machine_id
);
498 fprintf(file
, " Hostname: %s\n", hostname
);
500 if (filename
&& access(filename
, F_OK
) == 0)
501 fprintf(file
, " Coredump: %s\n", filename
);
504 _cleanup_free_
char *m
= NULL
;
506 m
= strreplace(message
, "\n", "\n ");
508 fprintf(file
, " Message: %s\n", strstrip(m
?: message
));
514 static int focus(sd_journal
*j
) {
517 r
= sd_journal_seek_tail(j
);
519 r
= sd_journal_previous(j
);
521 return log_error_errno(r
, "Failed to search journal: %m");
523 log_error("No match found.");
529 static void print_entry(sd_journal
*j
, unsigned n_found
) {
532 if (arg_action
== ACTION_INFO
)
533 print_info(stdout
, j
, n_found
);
535 print_field(stdout
, j
);
537 print_list(stdout
, j
, n_found
);
540 static int dump_list(sd_journal
*j
) {
541 unsigned n_found
= 0;
546 /* The coredumps are likely to compressed, and for just
547 * listing them we don't need to decompress them, so let's
548 * pick a fairly low data threshold here */
549 sd_journal_set_data_threshold(j
, 4096);
558 SD_JOURNAL_FOREACH(j
)
559 print_entry(j
, n_found
++);
561 if (!arg_field
&& n_found
<= 0) {
562 log_notice("No coredumps found.");
570 static int save_core(sd_journal
*j
, int fd
, char **path
, bool *unlink_temp
) {
572 _cleanup_free_
char *filename
= NULL
;
576 assert((fd
>= 0) != !!path
);
577 assert(!!path
== !!unlink_temp
);
579 /* Prefer uncompressed file to journal (probably cached) to
580 * compressed file (probably uncached). */
581 r
= sd_journal_get_data(j
, "COREDUMP_FILENAME", (const void**) &data
, &len
);
582 if (r
< 0 && r
!= -ENOENT
)
583 log_warning_errno(r
, "Failed to retrieve COREDUMP_FILENAME: %m");
585 retrieve(data
, len
, "COREDUMP_FILENAME", &filename
);
587 if (filename
&& access(filename
, R_OK
) < 0) {
588 log_full(errno
== ENOENT
? LOG_DEBUG
: LOG_WARNING
,
589 "File %s is not readable: %m", filename
);
590 filename
= mfree(filename
);
593 if (filename
&& !endswith(filename
, ".xz") && !endswith(filename
, ".lz4")) {
601 _cleanup_close_
int fdt
= -1;
605 temp
= strdup("/var/tmp/coredump-XXXXXX");
609 fdt
= mkostemp_safe(temp
, O_WRONLY
|O_CLOEXEC
);
611 return log_error_errno(errno
, "Failed to create temporary file: %m");
612 log_debug("Created temporary file %s", temp
);
617 r
= sd_journal_get_data(j
, "COREDUMP", (const void**) &data
, &len
);
625 sz
= write(fdt
, data
, len
);
627 log_error_errno(errno
, "Failed to write temporary file: %m");
631 if (sz
!= (ssize_t
) len
) {
632 log_error("Short write to temporary file.");
636 } else if (filename
) {
637 #if defined(HAVE_XZ) || defined(HAVE_LZ4)
638 _cleanup_close_
int fdf
;
640 fdf
= open(filename
, O_RDONLY
| O_CLOEXEC
);
642 log_error_errno(errno
, "Failed to open %s: %m", filename
);
647 r
= decompress_stream(filename
, fdf
, fd
, -1);
649 log_error_errno(r
, "Failed to decompress %s: %m", filename
);
653 log_error("Cannot decompress file. Compiled without compression support.");
659 log_error("Cannot retrieve coredump from journal nor disk.");
661 log_error_errno(r
, "Failed to retrieve COREDUMP field: %m");
675 log_debug("Removed temporary file %s", temp
);
681 static int dump_core(sd_journal
* j
) {
690 print_info(arg_output
? stdout
: stderr
, j
, false);
692 if (on_tty() && !arg_output
) {
693 log_error("Refusing to dump core to tty.");
697 r
= save_core(j
, arg_output
? fileno(arg_output
) : STDOUT_FILENO
, NULL
, NULL
);
699 return log_error_errno(r
, "Coredump retrieval failed: %m");
701 r
= sd_journal_previous(j
);
703 log_warning("More than one entry matches, ignoring rest.");
708 static int run_gdb(sd_journal
*j
) {
709 _cleanup_free_
char *exe
= NULL
, *path
= NULL
;
710 bool unlink_path
= false;
723 print_info(stdout
, j
, false);
726 r
= sd_journal_get_data(j
, "COREDUMP_EXE", (const void**) &data
, &len
);
728 return log_error_errno(r
, "Failed to retrieve COREDUMP_EXE field: %m");
730 assert(len
> strlen("COREDUMP_EXE="));
731 data
+= strlen("COREDUMP_EXE=");
732 len
-= strlen("COREDUMP_EXE=");
734 exe
= strndup(data
, len
);
738 if (endswith(exe
, " (deleted)")) {
739 log_error("Binary already deleted.");
743 if (!path_is_absolute(exe
)) {
744 log_error("Binary is not an absolute path.");
748 r
= save_core(j
, -1, &path
, &unlink_path
);
750 return log_error_errno(r
, "Failed to retrieve core: %m");
754 log_error_errno(errno
, "Failed to fork(): %m");
759 (void) reset_all_signal_handlers();
760 (void) reset_signal_mask();
762 execlp("gdb", "gdb", exe
, path
, NULL
);
764 log_error_errno(errno
, "Failed to invoke gdb: %m");
768 r
= wait_for_terminate(pid
, &st
);
770 log_error_errno(errno
, "Failed to wait for gdb: %m");
774 r
= st
.si_code
== CLD_EXITED
? st
.si_status
: 255;
778 log_debug("Removed temporary file %s", path
);
785 int main(int argc
, char *argv
[]) {
786 _cleanup_journal_close_ sd_journal
*j
= NULL
;
790 _cleanup_set_free_free_ Set
*matches
= NULL
;
792 setlocale(LC_ALL
, "");
793 log_parse_environment();
796 matches
= new_matches();
802 r
= parse_argv(argc
, argv
, matches
);
806 if (arg_action
== ACTION_NONE
)
811 r
= sd_journal_open(&j
, SD_JOURNAL_LOCAL_ONLY
);
813 log_error_errno(r
, "Failed to open journal: %m");
817 /* We want full data, nothing truncated. */
818 sd_journal_set_data_threshold(j
, 0);
820 SET_FOREACH(match
, matches
, it
) {
821 r
= sd_journal_add_match(j
, match
, strlen(match
));
823 log_error_errno(r
, "Failed to add match '%s': %m",
829 if (_unlikely_(log_get_max_level() >= LOG_DEBUG
)) {
830 _cleanup_free_
char *filter
;
832 filter
= journal_make_match_string(j
);
833 log_debug("Journal filter: %s", filter
);
855 assert_not_reached("Shouldn't be here");
864 return r
>= 0 ? r
: EXIT_FAILURE
;