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"
32 #include "journal-internal.h"
36 #include "path-util.h"
37 #include "process-util.h"
40 #include "signal-util.h"
41 #include "string-util.h"
42 #include "terminal-util.h"
51 } arg_action
= ACTION_LIST
;
52 static const char* arg_field
= NULL
;
53 static const char *arg_directory
= NULL
;
54 static int arg_no_pager
= false;
55 static int arg_no_legend
= false;
56 static int arg_one
= false;
57 static FILE* arg_output
= NULL
;
59 static Set
*new_matches(void) {
70 tmp
= strdup("MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1");
77 r
= set_consume(set
, tmp
);
79 log_error_errno(r
, "failed to add to set: %m");
87 static int add_match(Set
*set
, const char *match
) {
88 _cleanup_free_
char *p
= NULL
;
94 if (strchr(match
, '='))
96 else if (strchr(match
, '/')) {
97 r
= path_make_absolute_cwd(match
, &p
);
101 prefix
= "COREDUMP_EXE=";
102 } else if (parse_pid(match
, &pid
) >= 0)
103 prefix
= "COREDUMP_PID=";
105 prefix
= "COREDUMP_COMM=";
107 pattern
= strjoin(prefix
, match
, NULL
);
113 log_debug("Adding pattern: %s", pattern
);
114 r
= set_consume(set
, pattern
);
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"
134 " -D --directory=DIR Use journal files from directory\n\n"
137 " list [MATCHES...] List available coredumps (default)\n"
138 " info [MATCHES...] Show detailed information about one or more coredumps\n"
139 " dump [MATCHES...] Print first matching coredump to stdout\n"
140 " gdb [MATCHES...] Start gdb for the first matching coredump\n"
141 , program_invocation_short_name
);
144 static int parse_argv(int argc
, char *argv
[], Set
*matches
) {
153 static const struct option options
[] = {
154 { "help", no_argument
, NULL
, 'h' },
155 { "version" , no_argument
, NULL
, ARG_VERSION
},
156 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
157 { "no-legend", no_argument
, NULL
, ARG_NO_LEGEND
},
158 { "output", required_argument
, NULL
, 'o' },
159 { "field", required_argument
, NULL
, 'F' },
160 { "directory", required_argument
, NULL
, 'D' },
167 while ((c
= getopt_long(argc
, argv
, "ho:F:1D:", options
, NULL
)) >= 0)
171 arg_action
= ACTION_NONE
;
176 arg_action
= ACTION_NONE
;
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");
212 arg_directory
= optarg
;
219 assert_not_reached("Unhandled option");
223 const char *cmd
= argv
[optind
++];
224 if (streq(cmd
, "list"))
225 arg_action
= ACTION_LIST
;
226 else if (streq(cmd
, "dump"))
227 arg_action
= ACTION_DUMP
;
228 else if (streq(cmd
, "gdb"))
229 arg_action
= ACTION_GDB
;
230 else if (streq(cmd
, "info"))
231 arg_action
= ACTION_INFO
;
233 log_error("Unknown action '%s'", cmd
);
238 if (arg_field
&& arg_action
!= ACTION_LIST
) {
239 log_error("Option --field/-F only makes sense with list");
243 while (optind
< argc
) {
244 r
= add_match(matches
, argv
[optind
]);
253 static int retrieve(const void *data
,
261 ident
= strlen(name
) + 1; /* name + "=" */
266 if (memcmp(data
, name
, ident
- 1) != 0)
269 if (((const char*) data
)[ident
- 1] != '=')
272 v
= strndup((const char*)data
+ ident
, len
- ident
);
282 static void print_field(FILE* file
, sd_journal
*j
) {
283 _cleanup_free_
char *value
= NULL
;
292 SD_JOURNAL_FOREACH_DATA(j
, d
, l
)
293 retrieve(d
, l
, arg_field
, &value
);
296 fprintf(file
, "%s\n", value
);
299 static int print_list(FILE* file
, sd_journal
*j
, int had_legend
) {
301 *pid
= NULL
, *uid
= NULL
, *gid
= NULL
,
302 *sgnl
= NULL
, *exe
= NULL
, *comm
= NULL
, *cmdline
= NULL
,
307 char buf
[FORMAT_TIMESTAMP_MAX
];
314 SD_JOURNAL_FOREACH_DATA(j
, d
, l
) {
315 retrieve(d
, l
, "COREDUMP_PID", &pid
);
316 retrieve(d
, l
, "COREDUMP_UID", &uid
);
317 retrieve(d
, l
, "COREDUMP_GID", &gid
);
318 retrieve(d
, l
, "COREDUMP_SIGNAL", &sgnl
);
319 retrieve(d
, l
, "COREDUMP_EXE", &exe
);
320 retrieve(d
, l
, "COREDUMP_COMM", &comm
);
321 retrieve(d
, l
, "COREDUMP_CMDLINE", &cmdline
);
322 retrieve(d
, l
, "COREDUMP_FILENAME", &filename
);
325 if (!pid
&& !uid
&& !gid
&& !sgnl
&& !exe
&& !comm
&& !cmdline
&& !filename
) {
326 log_warning("Empty coredump log entry");
330 r
= sd_journal_get_realtime_usec(j
, &t
);
332 return log_error_errno(r
, "Failed to get realtime timestamp: %m");
334 format_timestamp(buf
, sizeof(buf
), t
);
335 present
= filename
&& access(filename
, F_OK
) == 0;
337 if (!had_legend
&& !arg_no_legend
)
338 fprintf(file
, "%-*s %*s %*s %*s %*s %*s %s\n",
339 FORMAT_TIMESTAMP_WIDTH
, "TIME",
347 fprintf(file
, "%-*s %*s %*s %*s %*s %*s %s\n",
348 FORMAT_TIMESTAMP_WIDTH
, buf
,
353 1, present
? "*" : "",
354 strna(exe
?: (comm
?: cmdline
)));
359 static int print_info(FILE *file
, sd_journal
*j
, bool need_space
) {
361 *pid
= NULL
, *uid
= NULL
, *gid
= NULL
,
362 *sgnl
= NULL
, *exe
= NULL
, *comm
= NULL
, *cmdline
= NULL
,
363 *unit
= NULL
, *user_unit
= NULL
, *session
= NULL
,
364 *boot_id
= NULL
, *machine_id
= NULL
, *hostname
= NULL
,
365 *slice
= NULL
, *cgroup
= NULL
, *owner_uid
= NULL
,
366 *message
= NULL
, *timestamp
= NULL
, *filename
= NULL
;
374 SD_JOURNAL_FOREACH_DATA(j
, d
, l
) {
375 retrieve(d
, l
, "COREDUMP_PID", &pid
);
376 retrieve(d
, l
, "COREDUMP_UID", &uid
);
377 retrieve(d
, l
, "COREDUMP_GID", &gid
);
378 retrieve(d
, l
, "COREDUMP_SIGNAL", &sgnl
);
379 retrieve(d
, l
, "COREDUMP_EXE", &exe
);
380 retrieve(d
, l
, "COREDUMP_COMM", &comm
);
381 retrieve(d
, l
, "COREDUMP_CMDLINE", &cmdline
);
382 retrieve(d
, l
, "COREDUMP_UNIT", &unit
);
383 retrieve(d
, l
, "COREDUMP_USER_UNIT", &user_unit
);
384 retrieve(d
, l
, "COREDUMP_SESSION", &session
);
385 retrieve(d
, l
, "COREDUMP_OWNER_UID", &owner_uid
);
386 retrieve(d
, l
, "COREDUMP_SLICE", &slice
);
387 retrieve(d
, l
, "COREDUMP_CGROUP", &cgroup
);
388 retrieve(d
, l
, "COREDUMP_TIMESTAMP", ×tamp
);
389 retrieve(d
, l
, "COREDUMP_FILENAME", &filename
);
390 retrieve(d
, l
, "_BOOT_ID", &boot_id
);
391 retrieve(d
, l
, "_MACHINE_ID", &machine_id
);
392 retrieve(d
, l
, "_HOSTNAME", &hostname
);
393 retrieve(d
, l
, "MESSAGE", &message
);
401 " PID: %s%s%s (%s)\n",
402 ansi_highlight(), strna(pid
), ansi_normal(), comm
);
406 ansi_highlight(), strna(pid
), ansi_normal());
411 if (parse_uid(uid
, &n
) >= 0) {
412 _cleanup_free_
char *u
= NULL
;
428 if (parse_gid(gid
, &n
) >= 0) {
429 _cleanup_free_
char *g
= NULL
;
445 if (safe_atoi(sgnl
, &sig
) >= 0)
446 fprintf(file
, " Signal: %s (%s)\n", sgnl
, signal_to_string(sig
));
448 fprintf(file
, " Signal: %s\n", sgnl
);
454 r
= safe_atou64(timestamp
, &u
);
456 char absolute
[FORMAT_TIMESTAMP_MAX
], relative
[FORMAT_TIMESPAN_MAX
];
459 " Timestamp: %s (%s)\n",
460 format_timestamp(absolute
, sizeof(absolute
), u
),
461 format_timestamp_relative(relative
, sizeof(relative
), u
));
464 fprintf(file
, " Timestamp: %s\n", timestamp
);
468 fprintf(file
, " Command Line: %s\n", cmdline
);
470 fprintf(file
, " Executable: %s%s%s\n", ansi_highlight(), exe
, ansi_normal());
472 fprintf(file
, " Control Group: %s\n", cgroup
);
474 fprintf(file
, " Unit: %s\n", unit
);
476 fprintf(file
, " User Unit: %s\n", unit
);
478 fprintf(file
, " Slice: %s\n", slice
);
480 fprintf(file
, " Session: %s\n", session
);
484 if (parse_uid(owner_uid
, &n
) >= 0) {
485 _cleanup_free_
char *u
= NULL
;
489 " Owner UID: %s (%s)\n",
498 fprintf(file
, " Boot ID: %s\n", boot_id
);
500 fprintf(file
, " Machine ID: %s\n", machine_id
);
502 fprintf(file
, " Hostname: %s\n", hostname
);
504 if (filename
&& access(filename
, F_OK
) == 0)
505 fprintf(file
, " Coredump: %s\n", filename
);
508 _cleanup_free_
char *m
= NULL
;
510 m
= strreplace(message
, "\n", "\n ");
512 fprintf(file
, " Message: %s\n", strstrip(m
?: message
));
518 static int focus(sd_journal
*j
) {
521 r
= sd_journal_seek_tail(j
);
523 r
= sd_journal_previous(j
);
525 return log_error_errno(r
, "Failed to search journal: %m");
527 log_error("No match found.");
533 static void print_entry(sd_journal
*j
, unsigned n_found
) {
536 if (arg_action
== ACTION_INFO
)
537 print_info(stdout
, j
, n_found
);
539 print_field(stdout
, j
);
541 print_list(stdout
, j
, n_found
);
544 static int dump_list(sd_journal
*j
) {
545 unsigned n_found
= 0;
550 /* The coredumps are likely to compressed, and for just
551 * listing them we don't need to decompress them, so let's
552 * pick a fairly low data threshold here */
553 sd_journal_set_data_threshold(j
, 4096);
562 SD_JOURNAL_FOREACH(j
)
563 print_entry(j
, n_found
++);
565 if (!arg_field
&& n_found
<= 0) {
566 log_notice("No coredumps found.");
574 static int save_core(sd_journal
*j
, int fd
, char **path
, bool *unlink_temp
) {
576 _cleanup_free_
char *filename
= NULL
;
580 assert((fd
>= 0) != !!path
);
581 assert(!!path
== !!unlink_temp
);
583 /* Prefer uncompressed file to journal (probably cached) to
584 * compressed file (probably uncached). */
585 r
= sd_journal_get_data(j
, "COREDUMP_FILENAME", (const void**) &data
, &len
);
586 if (r
< 0 && r
!= -ENOENT
)
587 log_warning_errno(r
, "Failed to retrieve COREDUMP_FILENAME: %m");
589 retrieve(data
, len
, "COREDUMP_FILENAME", &filename
);
591 if (filename
&& access(filename
, R_OK
) < 0) {
592 log_full(errno
== ENOENT
? LOG_DEBUG
: LOG_WARNING
,
593 "File %s is not readable: %m", filename
);
594 filename
= mfree(filename
);
597 if (filename
&& !endswith(filename
, ".xz") && !endswith(filename
, ".lz4")) {
605 _cleanup_close_
int fdt
= -1;
609 temp
= strdup("/var/tmp/coredump-XXXXXX");
613 fdt
= mkostemp_safe(temp
, O_WRONLY
|O_CLOEXEC
);
615 return log_error_errno(errno
, "Failed to create temporary file: %m");
616 log_debug("Created temporary file %s", temp
);
621 r
= sd_journal_get_data(j
, "COREDUMP", (const void**) &data
, &len
);
629 sz
= write(fdt
, data
, len
);
631 r
= log_error_errno(errno
,
632 "Failed to write temporary file: %m");
635 if (sz
!= (ssize_t
) len
) {
636 log_error("Short write to temporary file.");
640 } else if (filename
) {
641 #if defined(HAVE_XZ) || defined(HAVE_LZ4)
642 _cleanup_close_
int fdf
;
644 fdf
= open(filename
, O_RDONLY
| O_CLOEXEC
);
646 r
= log_error_errno(errno
,
647 "Failed to open %s: %m",
652 r
= decompress_stream(filename
, fdf
, fd
, -1);
654 log_error_errno(r
, "Failed to decompress %s: %m", filename
);
658 log_error("Cannot decompress file. Compiled without compression support.");
664 log_error("Cannot retrieve coredump from journal nor disk.");
666 log_error_errno(r
, "Failed to retrieve COREDUMP field: %m");
680 log_debug("Removed temporary file %s", temp
);
686 static int dump_core(sd_journal
* j
) {
695 print_info(arg_output
? stdout
: stderr
, j
, false);
697 if (on_tty() && !arg_output
) {
698 log_error("Refusing to dump core to tty.");
702 r
= save_core(j
, arg_output
? fileno(arg_output
) : STDOUT_FILENO
, NULL
, NULL
);
704 return log_error_errno(r
, "Coredump retrieval failed: %m");
706 r
= sd_journal_previous(j
);
708 log_warning("More than one entry matches, ignoring rest.");
713 static int run_gdb(sd_journal
*j
) {
714 _cleanup_free_
char *exe
= NULL
, *path
= NULL
;
715 bool unlink_path
= false;
728 print_info(stdout
, j
, false);
731 r
= sd_journal_get_data(j
, "COREDUMP_EXE", (const void**) &data
, &len
);
733 return log_error_errno(r
, "Failed to retrieve COREDUMP_EXE field: %m");
735 assert(len
> strlen("COREDUMP_EXE="));
736 data
+= strlen("COREDUMP_EXE=");
737 len
-= strlen("COREDUMP_EXE=");
739 exe
= strndup(data
, len
);
743 if (endswith(exe
, " (deleted)")) {
744 log_error("Binary already deleted.");
748 if (!path_is_absolute(exe
)) {
749 log_error("Binary is not an absolute path.");
753 r
= save_core(j
, -1, &path
, &unlink_path
);
755 return log_error_errno(r
, "Failed to retrieve core: %m");
759 r
= log_error_errno(errno
, "Failed to fork(): %m");
763 (void) reset_all_signal_handlers();
764 (void) reset_signal_mask();
766 execlp("gdb", "gdb", exe
, path
, NULL
);
768 log_error_errno(errno
, "Failed to invoke gdb: %m");
772 r
= wait_for_terminate(pid
, &st
);
774 log_error_errno(errno
, "Failed to wait for gdb: %m");
778 r
= st
.si_code
== CLD_EXITED
? st
.si_status
: 255;
782 log_debug("Removed temporary file %s", path
);
789 int main(int argc
, char *argv
[]) {
790 _cleanup_journal_close_ sd_journal
*j
= NULL
;
794 _cleanup_set_free_free_ Set
*matches
= NULL
;
796 setlocale(LC_ALL
, "");
797 log_parse_environment();
800 matches
= new_matches();
806 r
= parse_argv(argc
, argv
, matches
);
810 if (arg_action
== ACTION_NONE
)
816 r
= sd_journal_open_directory(&j
, arg_directory
, 0);
818 log_error_errno(r
, "Failed to open journals in directory: %s: %m", arg_directory
);
822 r
= sd_journal_open(&j
, SD_JOURNAL_LOCAL_ONLY
);
824 log_error_errno(r
, "Failed to open journal: %m");
829 /* We want full data, nothing truncated. */
830 sd_journal_set_data_threshold(j
, 0);
832 SET_FOREACH(match
, matches
, it
) {
833 r
= sd_journal_add_match(j
, match
, strlen(match
));
835 log_error_errno(r
, "Failed to add match '%s': %m",
841 if (_unlikely_(log_get_max_level() >= LOG_DEBUG
)) {
842 _cleanup_free_
char *filter
;
844 filter
= journal_make_match_string(j
);
845 log_debug("Journal filter: %s", filter
);
867 assert_not_reached("Shouldn't be here");
876 return r
>= 0 ? r
: EXIT_FAILURE
;