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 const char *arg_directory
= NULL
;
53 static int arg_no_pager
= false;
54 static int arg_no_legend
= false;
55 static int arg_one
= false;
56 static FILE* arg_output
= NULL
;
58 static Set
*new_matches(void) {
69 tmp
= strdup("MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1");
76 r
= set_consume(set
, tmp
);
78 log_error_errno(r
, "failed to add to set: %m");
86 static int add_match(Set
*set
, const char *match
) {
91 _cleanup_free_
char *p
= NULL
;
93 if (strchr(match
, '='))
95 else if (strchr(match
, '/')) {
96 p
= path_make_absolute_cwd(match
);
101 prefix
= "COREDUMP_EXE=";
103 else if (safe_atou(match
, &pid
) == 0)
104 prefix
= "COREDUMP_PID=";
106 prefix
= "COREDUMP_COMM=";
108 pattern
= strjoin(prefix
, match
, NULL
);
112 log_debug("Adding pattern: %s", pattern
);
113 r
= set_consume(set
, pattern
);
115 log_error_errno(r
, "Failed to add pattern: %m");
121 return log_error_errno(r
, "Failed to add match: %m");
124 static void help(void) {
125 printf("%s [OPTIONS...]\n\n"
126 "List or retrieve coredumps from the journal.\n\n"
128 " -h --help Show this help\n"
129 " --version Print version string\n"
130 " --no-pager Do not pipe output into a pager\n"
131 " --no-legend Do not print the column headers.\n"
132 " -1 Show information about most recent entry only\n"
133 " -F --field=FIELD List all values a certain field takes\n"
134 " -o --output=FILE Write output to FILE\n\n"
135 " -D --directory=DIR Use journal files from directory\n\n"
138 " list [MATCHES...] List available coredumps (default)\n"
139 " info [MATCHES...] Show detailed information about one or more coredumps\n"
140 " dump [MATCHES...] Print first matching coredump to stdout\n"
141 " gdb [MATCHES...] Start gdb for the first matching coredump\n"
142 , program_invocation_short_name
);
145 static int parse_argv(int argc
, char *argv
[], Set
*matches
) {
154 static const struct option options
[] = {
155 { "help", no_argument
, NULL
, 'h' },
156 { "version" , no_argument
, NULL
, ARG_VERSION
},
157 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
158 { "no-legend", no_argument
, NULL
, ARG_NO_LEGEND
},
159 { "output", required_argument
, NULL
, 'o' },
160 { "field", required_argument
, NULL
, 'F' },
161 { "directory", required_argument
, NULL
, 'D' },
168 while ((c
= getopt_long(argc
, argv
, "ho:F:1D:", options
, NULL
)) >= 0)
172 arg_action
= ACTION_NONE
;
177 arg_action
= ACTION_NONE
;
178 puts(PACKAGE_STRING
);
179 puts(SYSTEMD_FEATURES
);
187 arg_no_legend
= true;
192 log_error("cannot set output more than once");
196 arg_output
= fopen(optarg
, "we");
198 return log_error_errno(errno
, "writing to '%s': %m", optarg
);
204 log_error("cannot use --field/-F more than once");
215 arg_directory
= optarg
;
222 assert_not_reached("Unhandled option");
226 const char *cmd
= argv
[optind
++];
227 if (streq(cmd
, "list"))
228 arg_action
= ACTION_LIST
;
229 else if (streq(cmd
, "dump"))
230 arg_action
= ACTION_DUMP
;
231 else if (streq(cmd
, "gdb"))
232 arg_action
= ACTION_GDB
;
233 else if (streq(cmd
, "info"))
234 arg_action
= ACTION_INFO
;
236 log_error("Unknown action '%s'", cmd
);
241 if (arg_field
&& arg_action
!= ACTION_LIST
) {
242 log_error("Option --field/-F only makes sense with list");
246 while (optind
< argc
) {
247 r
= add_match(matches
, argv
[optind
]);
256 static int retrieve(const void *data
,
264 ident
= strlen(name
) + 1; /* name + "=" */
269 if (memcmp(data
, name
, ident
- 1) != 0)
272 if (((const char*) data
)[ident
- 1] != '=')
275 v
= strndup((const char*)data
+ ident
, len
- ident
);
285 static void print_field(FILE* file
, sd_journal
*j
) {
286 _cleanup_free_
char *value
= NULL
;
295 SD_JOURNAL_FOREACH_DATA(j
, d
, l
)
296 retrieve(d
, l
, arg_field
, &value
);
299 fprintf(file
, "%s\n", value
);
302 static int print_list(FILE* file
, sd_journal
*j
, int had_legend
) {
304 *pid
= NULL
, *uid
= NULL
, *gid
= NULL
,
305 *sgnl
= NULL
, *exe
= NULL
, *comm
= NULL
, *cmdline
= NULL
,
310 char buf
[FORMAT_TIMESTAMP_MAX
];
317 SD_JOURNAL_FOREACH_DATA(j
, d
, l
) {
318 retrieve(d
, l
, "COREDUMP_PID", &pid
);
319 retrieve(d
, l
, "COREDUMP_UID", &uid
);
320 retrieve(d
, l
, "COREDUMP_GID", &gid
);
321 retrieve(d
, l
, "COREDUMP_SIGNAL", &sgnl
);
322 retrieve(d
, l
, "COREDUMP_EXE", &exe
);
323 retrieve(d
, l
, "COREDUMP_COMM", &comm
);
324 retrieve(d
, l
, "COREDUMP_CMDLINE", &cmdline
);
325 retrieve(d
, l
, "COREDUMP_FILENAME", &filename
);
328 if (!pid
&& !uid
&& !gid
&& !sgnl
&& !exe
&& !comm
&& !cmdline
&& !filename
) {
329 log_warning("Empty coredump log entry");
333 r
= sd_journal_get_realtime_usec(j
, &t
);
335 return log_error_errno(r
, "Failed to get realtime timestamp: %m");
337 format_timestamp(buf
, sizeof(buf
), t
);
338 present
= filename
&& access(filename
, F_OK
) == 0;
340 if (!had_legend
&& !arg_no_legend
)
341 fprintf(file
, "%-*s %*s %*s %*s %*s %*s %s\n",
342 FORMAT_TIMESTAMP_WIDTH
, "TIME",
350 fprintf(file
, "%-*s %*s %*s %*s %*s %*s %s\n",
351 FORMAT_TIMESTAMP_WIDTH
, buf
,
356 1, present
? "*" : "",
357 strna(exe
?: (comm
?: cmdline
)));
362 static int print_info(FILE *file
, sd_journal
*j
, bool need_space
) {
364 *pid
= NULL
, *uid
= NULL
, *gid
= NULL
,
365 *sgnl
= NULL
, *exe
= NULL
, *comm
= NULL
, *cmdline
= NULL
,
366 *unit
= NULL
, *user_unit
= NULL
, *session
= NULL
,
367 *boot_id
= NULL
, *machine_id
= NULL
, *hostname
= NULL
,
368 *slice
= NULL
, *cgroup
= NULL
, *owner_uid
= NULL
,
369 *message
= NULL
, *timestamp
= NULL
, *filename
= NULL
;
377 SD_JOURNAL_FOREACH_DATA(j
, d
, l
) {
378 retrieve(d
, l
, "COREDUMP_PID", &pid
);
379 retrieve(d
, l
, "COREDUMP_UID", &uid
);
380 retrieve(d
, l
, "COREDUMP_GID", &gid
);
381 retrieve(d
, l
, "COREDUMP_SIGNAL", &sgnl
);
382 retrieve(d
, l
, "COREDUMP_EXE", &exe
);
383 retrieve(d
, l
, "COREDUMP_COMM", &comm
);
384 retrieve(d
, l
, "COREDUMP_CMDLINE", &cmdline
);
385 retrieve(d
, l
, "COREDUMP_UNIT", &unit
);
386 retrieve(d
, l
, "COREDUMP_USER_UNIT", &user_unit
);
387 retrieve(d
, l
, "COREDUMP_SESSION", &session
);
388 retrieve(d
, l
, "COREDUMP_OWNER_UID", &owner_uid
);
389 retrieve(d
, l
, "COREDUMP_SLICE", &slice
);
390 retrieve(d
, l
, "COREDUMP_CGROUP", &cgroup
);
391 retrieve(d
, l
, "COREDUMP_TIMESTAMP", ×tamp
);
392 retrieve(d
, l
, "COREDUMP_FILENAME", &filename
);
393 retrieve(d
, l
, "_BOOT_ID", &boot_id
);
394 retrieve(d
, l
, "_MACHINE_ID", &machine_id
);
395 retrieve(d
, l
, "_HOSTNAME", &hostname
);
396 retrieve(d
, l
, "MESSAGE", &message
);
404 " PID: %s%s%s (%s)\n",
405 ansi_highlight(), strna(pid
), ansi_highlight_off(), comm
);
409 ansi_highlight(), strna(pid
), ansi_highlight_off());
414 if (parse_uid(uid
, &n
) >= 0) {
415 _cleanup_free_
char *u
= NULL
;
431 if (parse_gid(gid
, &n
) >= 0) {
432 _cleanup_free_
char *g
= NULL
;
448 if (safe_atoi(sgnl
, &sig
) >= 0)
449 fprintf(file
, " Signal: %s (%s)\n", sgnl
, signal_to_string(sig
));
451 fprintf(file
, " Signal: %s\n", sgnl
);
457 r
= safe_atou64(timestamp
, &u
);
459 char absolute
[FORMAT_TIMESTAMP_MAX
], relative
[FORMAT_TIMESPAN_MAX
];
462 " Timestamp: %s (%s)\n",
463 format_timestamp(absolute
, sizeof(absolute
), u
),
464 format_timestamp_relative(relative
, sizeof(relative
), u
));
467 fprintf(file
, " Timestamp: %s\n", timestamp
);
471 fprintf(file
, " Command Line: %s\n", cmdline
);
473 fprintf(file
, " Executable: %s%s%s\n", ansi_highlight(), exe
, ansi_highlight_off());
475 fprintf(file
, " Control Group: %s\n", cgroup
);
477 fprintf(file
, " Unit: %s\n", unit
);
479 fprintf(file
, " User Unit: %s\n", unit
);
481 fprintf(file
, " Slice: %s\n", slice
);
483 fprintf(file
, " Session: %s\n", session
);
487 if (parse_uid(owner_uid
, &n
) >= 0) {
488 _cleanup_free_
char *u
= NULL
;
492 " Owner UID: %s (%s)\n",
501 fprintf(file
, " Boot ID: %s\n", boot_id
);
503 fprintf(file
, " Machine ID: %s\n", machine_id
);
505 fprintf(file
, " Hostname: %s\n", hostname
);
507 if (filename
&& access(filename
, F_OK
) == 0)
508 fprintf(file
, " Coredump: %s\n", filename
);
511 _cleanup_free_
char *m
= NULL
;
513 m
= strreplace(message
, "\n", "\n ");
515 fprintf(file
, " Message: %s\n", strstrip(m
?: message
));
521 static int focus(sd_journal
*j
) {
524 r
= sd_journal_seek_tail(j
);
526 r
= sd_journal_previous(j
);
528 return log_error_errno(r
, "Failed to search journal: %m");
530 log_error("No match found.");
536 static void print_entry(sd_journal
*j
, unsigned n_found
) {
539 if (arg_action
== ACTION_INFO
)
540 print_info(stdout
, j
, n_found
);
542 print_field(stdout
, j
);
544 print_list(stdout
, j
, n_found
);
547 static int dump_list(sd_journal
*j
) {
548 unsigned n_found
= 0;
553 /* The coredumps are likely to compressed, and for just
554 * listing them we don't need to decompress them, so let's
555 * pick a fairly low data threshold here */
556 sd_journal_set_data_threshold(j
, 4096);
565 SD_JOURNAL_FOREACH(j
)
566 print_entry(j
, n_found
++);
568 if (!arg_field
&& n_found
<= 0) {
569 log_notice("No coredumps found.");
577 static int save_core(sd_journal
*j
, int fd
, char **path
, bool *unlink_temp
) {
579 _cleanup_free_
char *filename
= NULL
;
583 assert((fd
>= 0) != !!path
);
584 assert(!!path
== !!unlink_temp
);
586 /* Prefer uncompressed file to journal (probably cached) to
587 * compressed file (probably uncached). */
588 r
= sd_journal_get_data(j
, "COREDUMP_FILENAME", (const void**) &data
, &len
);
589 if (r
< 0 && r
!= -ENOENT
)
590 log_warning_errno(r
, "Failed to retrieve COREDUMP_FILENAME: %m");
592 retrieve(data
, len
, "COREDUMP_FILENAME", &filename
);
594 if (filename
&& access(filename
, R_OK
) < 0) {
595 log_full(errno
== ENOENT
? LOG_DEBUG
: LOG_WARNING
,
596 "File %s is not readable: %m", filename
);
597 filename
= mfree(filename
);
600 if (filename
&& !endswith(filename
, ".xz") && !endswith(filename
, ".lz4")) {
608 _cleanup_close_
int fdt
= -1;
612 temp
= strdup("/var/tmp/coredump-XXXXXX");
616 fdt
= mkostemp_safe(temp
, O_WRONLY
|O_CLOEXEC
);
618 return log_error_errno(errno
, "Failed to create temporary file: %m");
619 log_debug("Created temporary file %s", temp
);
624 r
= sd_journal_get_data(j
, "COREDUMP", (const void**) &data
, &len
);
632 sz
= write(fdt
, data
, len
);
634 log_error_errno(errno
, "Failed to write temporary file: %m");
638 if (sz
!= (ssize_t
) len
) {
639 log_error("Short write to temporary file.");
643 } else if (filename
) {
644 #if defined(HAVE_XZ) || defined(HAVE_LZ4)
645 _cleanup_close_
int fdf
;
647 fdf
= open(filename
, O_RDONLY
| O_CLOEXEC
);
649 log_error_errno(errno
, "Failed to open %s: %m", filename
);
654 r
= decompress_stream(filename
, fdf
, fd
, -1);
656 log_error_errno(r
, "Failed to decompress %s: %m", filename
);
660 log_error("Cannot decompress file. Compiled without compression support.");
666 log_error("Cannot retrieve coredump from journal nor disk.");
668 log_error_errno(r
, "Failed to retrieve COREDUMP field: %m");
682 log_debug("Removed temporary file %s", temp
);
688 static int dump_core(sd_journal
* j
) {
697 print_info(arg_output
? stdout
: stderr
, j
, false);
699 if (on_tty() && !arg_output
) {
700 log_error("Refusing to dump core to tty.");
704 r
= save_core(j
, arg_output
? fileno(arg_output
) : STDOUT_FILENO
, NULL
, NULL
);
706 return log_error_errno(r
, "Coredump retrieval failed: %m");
708 r
= sd_journal_previous(j
);
710 log_warning("More than one entry matches, ignoring rest.");
715 static int run_gdb(sd_journal
*j
) {
716 _cleanup_free_
char *exe
= NULL
, *path
= NULL
;
717 bool unlink_path
= false;
730 print_info(stdout
, j
, false);
733 r
= sd_journal_get_data(j
, "COREDUMP_EXE", (const void**) &data
, &len
);
735 return log_error_errno(r
, "Failed to retrieve COREDUMP_EXE field: %m");
737 assert(len
> strlen("COREDUMP_EXE="));
738 data
+= strlen("COREDUMP_EXE=");
739 len
-= strlen("COREDUMP_EXE=");
741 exe
= strndup(data
, len
);
745 if (endswith(exe
, " (deleted)")) {
746 log_error("Binary already deleted.");
750 if (!path_is_absolute(exe
)) {
751 log_error("Binary is not an absolute path.");
755 r
= save_core(j
, -1, &path
, &unlink_path
);
757 return log_error_errno(r
, "Failed to retrieve core: %m");
761 log_error_errno(errno
, "Failed to fork(): %m");
766 (void) reset_all_signal_handlers();
767 (void) reset_signal_mask();
769 execlp("gdb", "gdb", exe
, path
, NULL
);
771 log_error_errno(errno
, "Failed to invoke gdb: %m");
775 r
= wait_for_terminate(pid
, &st
);
777 log_error_errno(errno
, "Failed to wait for gdb: %m");
781 r
= st
.si_code
== CLD_EXITED
? st
.si_status
: 255;
785 log_debug("Removed temporary file %s", path
);
792 int main(int argc
, char *argv
[]) {
793 _cleanup_journal_close_ sd_journal
*j
= NULL
;
797 _cleanup_set_free_free_ Set
*matches
= NULL
;
799 setlocale(LC_ALL
, "");
800 log_parse_environment();
803 matches
= new_matches();
809 r
= parse_argv(argc
, argv
, matches
);
813 if (arg_action
== ACTION_NONE
)
819 r
= sd_journal_open_directory(&j
, arg_directory
, 0);
821 log_error_errno(r
, "Failed to open journals in directory: %s: %m", arg_directory
);
825 r
= sd_journal_open(&j
, SD_JOURNAL_LOCAL_ONLY
);
827 log_error_errno(r
, "Failed to open journal: %m");
832 /* We want full data, nothing truncated. */
833 sd_journal_set_data_threshold(j
, 0);
835 SET_FOREACH(match
, matches
, it
) {
836 r
= sd_journal_add_match(j
, match
, strlen(match
));
838 log_error_errno(r
, "Failed to add match '%s': %m",
844 if (_unlikely_(log_get_max_level() >= LOG_DEBUG
)) {
845 _cleanup_free_
char *filter
;
847 filter
= journal_make_match_string(j
);
848 log_debug("Journal filter: %s", filter
);
870 assert_not_reached("Shouldn't be here");
879 return r
>= 0 ? r
: EXIT_FAILURE
;