+/* SPDX-License-Identifier: LGPL-2.1+ */
/***
This file is part of systemd.
Copyright 2012 Zbigniew Jędrzejewski-Szmek
-
- systemd is free software; you can redistribute it and/or modify it
- under the terms of the GNU Lesser General Public License as published by
- the Free Software Foundation; either version 2.1 of the License, or
- (at your option) any later version.
-
- systemd is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License
- along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <fcntl.h>
#include "fileio.h"
#include "fs-util.h"
#include "journal-internal.h"
+#include "journal-util.h"
#include "log.h"
#include "macro.h"
#include "pager.h"
#include "terminal-util.h"
#include "user-util.h"
#include "util.h"
+#include "verbs.h"
-static enum {
- ACTION_NONE,
- ACTION_INFO,
- ACTION_LIST,
- ACTION_DUMP,
- ACTION_GDB,
-} arg_action = ACTION_LIST;
+#define SHORT_BUS_CALL_TIMEOUT_USEC (3 * USEC_PER_SEC)
+
+static usec_t arg_since = USEC_INFINITY, arg_until = USEC_INFINITY;
static const char* arg_field = NULL;
static const char *arg_directory = NULL;
static bool arg_no_pager = false;
static int arg_one = false;
static FILE* arg_output = NULL;
static bool arg_reverse = false;
-static char** arg_matches = NULL;
+static bool arg_quiet = false;
static int add_match(sd_journal *j, const char *match) {
_cleanup_free_ char *p = NULL;
- char *pattern = NULL;
- const char* prefix;
+ const char* prefix, *pattern;
pid_t pid;
int r;
else
prefix = "COREDUMP_COMM=";
- pattern = strjoin(prefix, match);
- if (!pattern)
- return log_oom();
-
+ pattern = strjoina(prefix, match);
log_debug("Adding match: %s", pattern);
r = sd_journal_add_match(j, pattern, 0);
if (r < 0)
return log_error_errno(r, "Failed to add match \"%s\": %m", match);
+
return 0;
}
-static int add_matches(sd_journal *j) {
+static int add_matches(sd_journal *j, char **matches) {
char **match;
int r;
if (r < 0)
return log_error_errno(r, "Failed to add match \"%s\": %m", "MESSAGE_ID=" SD_MESSAGE_BACKTRACE_STR);
- STRV_FOREACH(match, arg_matches) {
+ STRV_FOREACH(match, matches) {
r = add_match(j, *match);
if (r < 0)
return r;
return 0;
}
-static void help(void) {
+static int acquire_journal(sd_journal **ret, char **matches) {
+ _cleanup_(sd_journal_closep) sd_journal *j = NULL;
+ int r;
+
+ assert(ret);
+
+ if (arg_directory) {
+ r = sd_journal_open_directory(&j, arg_directory, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to open journals in directory: %s: %m", arg_directory);
+ } else {
+ r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
+ if (r < 0)
+ return log_error_errno(r, "Failed to open journal: %m");
+ }
+
+ r = journal_access_check_and_warn(j, arg_quiet, true);
+ if (r < 0)
+ return r;
+
+ r = add_matches(j, matches);
+ if (r < 0)
+ return r;
+
+ if (DEBUG_LOGGING) {
+ _cleanup_free_ char *filter;
+
+ filter = journal_make_match_string(j);
+ log_debug("Journal filter: %s", filter);
+ }
+
+ *ret = TAKE_PTR(j);
+
+ return 0;
+}
+
+static int help(void) {
printf("%s [OPTIONS...]\n\n"
"List or retrieve coredumps from the journal.\n\n"
"Flags:\n"
" --no-pager Do not pipe output into a pager\n"
" --no-legend Do not print the column headers.\n"
" -1 Show information about most recent entry only\n"
+ " -S --since=DATE Only print coredumps since the date\n"
+ " -U --until=DATE Only print coredumps until the date\n"
" -r --reverse Show the newest entries first\n"
" -F --field=FIELD List all values a certain field takes\n"
" -o --output=FILE Write output to FILE\n"
" -D --directory=DIR Use journal files from directory\n\n"
-
+ " -q --quiet Do not show info messages and privilege warning\n"
"Commands:\n"
" list [MATCHES...] List available coredumps (default)\n"
" info [MATCHES...] Show detailed information about one or more coredumps\n"
" dump [MATCHES...] Print first matching coredump to stdout\n"
" gdb [MATCHES...] Start gdb for the first matching coredump\n"
, program_invocation_short_name);
+
+ return 0;
}
static int parse_argv(int argc, char *argv[]) {
ARG_NO_LEGEND,
};
- int c;
+ int c, r;
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "field", required_argument, NULL, 'F' },
{ "directory", required_argument, NULL, 'D' },
{ "reverse", no_argument, NULL, 'r' },
+ { "since", required_argument, NULL, 'S' },
+ { "until", required_argument, NULL, 'U' },
+ { "quiet", no_argument, NULL, 'q' },
{}
};
assert(argc >= 0);
assert(argv);
- while ((c = getopt_long(argc, argv, "ho:F:1D:r", options, NULL)) >= 0)
+ while ((c = getopt_long(argc, argv, "ho:F:1D:rS:U:q", options, NULL)) >= 0)
switch(c) {
-
case 'h':
- arg_action = ACTION_NONE;
- help();
- return 0;
+ return help();
case ARG_VERSION:
- arg_action = ACTION_NONE;
return version();
case ARG_NO_PAGER:
break;
+ case 'S':
+ r = parse_timestamp(optarg, &arg_since);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse timestamp: %s", optarg);
+ break;
+
+ case 'U':
+ r = parse_timestamp(optarg, &arg_until);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse timestamp: %s", optarg);
+ break;
+
case 'F':
if (arg_field) {
log_error("cannot use --field/-F more than once");
arg_reverse = true;
break;
+ case 'q':
+ arg_quiet = true;
+ break;
+
case '?':
return -EINVAL;
assert_not_reached("Unhandled option");
}
- if (optind < argc) {
- const char *cmd = argv[optind++];
- if (streq(cmd, "list"))
- arg_action = ACTION_LIST;
- else if (streq(cmd, "dump"))
- arg_action = ACTION_DUMP;
- else if (streq(cmd, "gdb"))
- arg_action = ACTION_GDB;
- else if (streq(cmd, "info"))
- arg_action = ACTION_INFO;
- else {
- log_error("Unknown action '%s'", cmd);
- return -EINVAL;
- }
- }
-
- if (arg_field && arg_action != ACTION_LIST) {
- log_error("Option --field/-F only makes sense with list");
+ if (arg_since != USEC_INFINITY && arg_until != USEC_INFINITY &&
+ arg_since > arg_until) {
+ log_error("--since= must be before --until=.");
return -EINVAL;
}
- if (optind < argc)
- arg_matches = argv + optind;
-
- return 0;
+ return 1;
}
static int retrieve(const void *data,
_cleanup_free_ char
*mid = NULL, *pid = NULL, *uid = NULL, *gid = NULL,
*sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL,
- *filename = NULL, *coredump = NULL;
+ *filename = NULL, *truncated = NULL, *coredump = NULL;
const void *d;
size_t l;
usec_t t;
RETRIEVE(d, l, "COREDUMP_COMM", comm);
RETRIEVE(d, l, "COREDUMP_CMDLINE", cmdline);
RETRIEVE(d, l, "COREDUMP_FILENAME", filename);
+ RETRIEVE(d, l, "COREDUMP_TRUNCATED", truncated);
RETRIEVE(d, l, "COREDUMP", coredump);
}
format_timestamp(buf, sizeof(buf), t);
if (!had_legend && !arg_no_legend)
- fprintf(file, "%-*s %*s %*s %*s %*s %*s %s\n",
+ fprintf(file, "%-*s %*s %*s %*s %*s %-*s %s\n",
FORMAT_TIMESTAMP_WIDTH, "TIME",
6, "PID",
5, "UID",
5, "GID",
3, "SIG",
- 8, "COREFILE",
+ 9, "COREFILE",
"EXE");
normal_coredump = streq_ptr(mid, SD_MESSAGE_COREDUMP_STR);
else
present = "-";
+ if (STR_IN_SET(present, "present", "journal") && truncated && parse_boolean(truncated) > 0)
+ present = "truncated";
+
fprintf(file, "%-*s %*s %*s %*s %*s %-*s %s\n",
FORMAT_TIMESTAMP_WIDTH, buf,
6, strna(pid),
5, strna(uid),
5, strna(gid),
3, normal_coredump ? strna(sgnl) : "-",
- 8, present,
+ 9, present,
strna(exe ?: (comm ?: cmdline)));
return 0;
*boot_id = NULL, *machine_id = NULL, *hostname = NULL,
*slice = NULL, *cgroup = NULL, *owner_uid = NULL,
*message = NULL, *timestamp = NULL, *filename = NULL,
- *coredump = NULL;
+ *truncated = NULL, *coredump = NULL;
const void *d;
size_t l;
bool normal_coredump;
RETRIEVE(d, l, "COREDUMP_CGROUP", cgroup);
RETRIEVE(d, l, "COREDUMP_TIMESTAMP", timestamp);
RETRIEVE(d, l, "COREDUMP_FILENAME", filename);
+ RETRIEVE(d, l, "COREDUMP_TRUNCATED", truncated);
RETRIEVE(d, l, "COREDUMP", coredump);
RETRIEVE(d, l, "_BOOT_ID", boot_id);
RETRIEVE(d, l, "_MACHINE_ID", machine_id);
if (hostname)
fprintf(file, " Hostname: %s\n", hostname);
- if (filename)
- fprintf(file, " Storage: %s%s\n", filename,
- access(filename, R_OK) < 0 ? " (inaccessible)" : "");
+ if (filename) {
+ bool inacc, trunc;
+
+ inacc = access(filename, R_OK) < 0;
+ trunc = truncated && parse_boolean(truncated) > 0;
+
+ if (inacc || trunc)
+ fprintf(file, " Storage: %s%s (%s%s%s)%s\n",
+ ansi_highlight_red(),
+ filename,
+ inacc ? "inaccessible" : "",
+ inacc && trunc ? ", " : "",
+ trunc ? "truncated" : "",
+ ansi_normal());
+ else
+ fprintf(file, " Storage: %s\n", filename);
+ }
+
else if (coredump)
fprintf(file, " Storage: journal\n");
else
return r;
}
-static int print_entry(sd_journal *j, unsigned n_found) {
+static int print_entry(sd_journal *j, unsigned n_found, bool verb_is_info) {
assert(j);
- if (arg_action == ACTION_INFO)
+ if (verb_is_info)
return print_info(stdout, j, n_found);
else if (arg_field)
return print_field(stdout, j);
return print_list(stdout, j, n_found);
}
-static int dump_list(sd_journal *j) {
+static int dump_list(int argc, char **argv, void *userdata) {
+ _cleanup_(sd_journal_closep) sd_journal *j = NULL;
unsigned n_found = 0;
+ bool verb_is_info;
int r;
- assert(j);
+ verb_is_info = (argc >= 1 && streq(argv[0], "info"));
+
+ r = acquire_journal(&j, argv + 1);
+ if (r < 0)
+ return r;
+
+ (void) pager_open(arg_no_pager, false);
/* The coredumps are likely to compressed, and for just
* listing them we don't need to decompress them, so let's
if (r < 0)
return r;
- return print_entry(j, 0);
+ return print_entry(j, 0, verb_is_info);
} else {
- if (!arg_reverse) {
- SD_JOURNAL_FOREACH(j) {
- r = print_entry(j, n_found++);
+ if (arg_since != USEC_INFINITY && !arg_reverse)
+ r = sd_journal_seek_realtime_usec(j, arg_since);
+ else if (arg_until != USEC_INFINITY && arg_reverse)
+ r = sd_journal_seek_realtime_usec(j, arg_until);
+ else if (arg_reverse)
+ r = sd_journal_seek_tail(j);
+ else
+ r = sd_journal_seek_head(j);
+ if (r < 0)
+ return log_error_errno(r, "Failed to seek to date: %m");
+
+ for (;;) {
+ if (!arg_reverse)
+ r = sd_journal_next(j);
+ else
+ r = sd_journal_previous(j);
+
+ if (r < 0)
+ return log_error_errno(r, "Failed to iterate through journal: %m");
+
+ if (r == 0)
+ break;
+
+ if (arg_until != USEC_INFINITY && !arg_reverse) {
+ usec_t usec;
+
+ r = sd_journal_get_realtime_usec(j, &usec);
if (r < 0)
- return r;
+ return log_error_errno(r, "Failed to determine timestamp: %m");
+ if (usec > arg_until)
+ continue;
}
- } else {
- SD_JOURNAL_FOREACH_BACKWARDS(j) {
- r = print_entry(j, n_found++);
+
+ if (arg_since != USEC_INFINITY && arg_reverse) {
+ usec_t usec;
+
+ r = sd_journal_get_realtime_usec(j, &usec);
if (r < 0)
- return r;
+ return log_error_errno(r, "Failed to determine timestamp: %m");
+ if (usec < arg_since)
+ continue;
}
+
+ r = print_entry(j, n_found++, verb_is_info);
+ if (r < 0)
+ return r;
}
if (!arg_field && n_found <= 0) {
- log_notice("No coredumps found.");
+ if (!arg_quiet)
+ log_notice("No coredumps found.");
return -ESRCH;
}
}
return log_error_errno(errno, "File \"%s\" is not readable: %m", filename);
if (path && !endswith(filename, ".xz") && !endswith(filename, ".lz4")) {
- *path = filename;
- filename = NULL;
+ *path = TAKE_PTR(filename);
return 0;
}
}
if (filename) {
-#if defined(HAVE_XZ) || defined(HAVE_LZ4)
+#if HAVE_XZ || HAVE_LZ4
_cleanup_close_ int fdf;
fdf = open(filename, O_RDONLY | O_CLOEXEC);
return r;
}
-static int dump_core(sd_journal* j) {
+static int dump_core(int argc, char **argv, void *userdata) {
+ _cleanup_(sd_journal_closep) sd_journal *j = NULL;
int r;
- assert(j);
+ if (arg_field) {
+ log_error("Option --field/-F only makes sense with list");
+ return -EINVAL;
+ }
+
+ r = acquire_journal(&j, argv + 1);
+ if (r < 0)
+ return r;
r = focus(j);
if (r < 0)
return r;
r = sd_journal_previous(j);
- if (r > 0)
- log_warning("More than one entry matches, ignoring rest.");
+ if (r > 0 && !arg_quiet)
+ log_notice("More than one entry matches, ignoring rest.");
return 0;
}
-static int run_gdb(sd_journal *j) {
+static int run_gdb(int argc, char **argv, void *userdata) {
+ _cleanup_(sd_journal_closep) sd_journal *j = NULL;
_cleanup_free_ char *exe = NULL, *path = NULL;
bool unlink_path = false;
const char *data;
- siginfo_t st;
size_t len;
pid_t pid;
int r;
- assert(j);
+ if (arg_field) {
+ log_error("Option --field/-F only makes sense with list");
+ return -EINVAL;
+ }
+
+ r = acquire_journal(&j, argv + 1);
+ if (r < 0)
+ return r;
r = focus(j);
if (r < 0)
if (r < 0)
return log_error_errno(r, "Failed to retrieve COREDUMP_EXE field: %m");
- assert(len > strlen("COREDUMP_EXE="));
- data += strlen("COREDUMP_EXE=");
- len -= strlen("COREDUMP_EXE=");
+ assert(len > STRLEN("COREDUMP_EXE="));
+ data += STRLEN("COREDUMP_EXE=");
+ len -= STRLEN("COREDUMP_EXE=");
exe = strndup(data, len);
if (!exe)
/* Don't interfere with gdb and its handling of SIGINT. */
(void) ignore_signals(SIGINT, -1);
- pid = fork();
- if (pid < 0) {
- r = log_error_errno(errno, "Failed to fork(): %m");
+ r = safe_fork("(gdb)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_CLOSE_ALL_FDS|FORK_LOG, &pid);
+ if (r < 0)
goto finish;
- }
- if (pid == 0) {
- (void) reset_all_signal_handlers();
- (void) reset_signal_mask();
-
+ if (r == 0) {
execlp("gdb", "gdb", exe, path, NULL);
-
+ log_open();
log_error_errno(errno, "Failed to invoke gdb: %m");
- _exit(1);
- }
-
- r = wait_for_terminate(pid, &st);
- if (r < 0) {
- log_error_errno(r, "Failed to wait for gdb: %m");
- goto finish;
+ _exit(EXIT_FAILURE);
}
- r = st.si_code == CLD_EXITED ? st.si_status : 255;
+ r = wait_for_terminate_and_check("gdb", pid, WAIT_LOG_ABNORMAL);
finish:
(void) default_signals(SIGINT, -1);
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
int c = 0, r;
- const char *state;
+ const char *id, *state, *substate;
+
+ if (arg_quiet)
+ return false;
r = sd_bus_default_system(&bus);
if (r < 0)
if (r < 0)
return bus_log_create_error(r);
- r = sd_bus_call(bus, m, 0, &error, &reply);
+ r = sd_bus_call(bus, m, SHORT_BUS_CALL_TIMEOUT_USEC, &error, &reply);
if (r < 0)
return log_error_errno(r, "Failed to check if any systemd-coredump@.service units are running: %s",
bus_error_message(&error, r));
while ((r = sd_bus_message_read(
reply, "(ssssssouso)",
- NULL, NULL, NULL, &state, NULL,
- NULL, NULL, NULL, NULL, NULL)) > 0)
- if (!STR_IN_SET(state, "dead", "failed"))
- c++;
-
+ &id, NULL, NULL, &state, &substate,
+ NULL, NULL, NULL, NULL, NULL)) > 0) {
+ bool found = !STR_IN_SET(state, "inactive", "dead", "failed");
+ log_debug("Unit %s is %s/%s, %scounting it.", id, state, substate, found ? "" : "not ");
+ c += found;
+ }
if (r < 0)
return bus_log_parse_error(r);
return c;
}
+static int coredumpctl_main(int argc, char *argv[]) {
+
+ static const Verb verbs[] = {
+ { "list", VERB_ANY, VERB_ANY, VERB_DEFAULT, dump_list },
+ { "info", VERB_ANY, VERB_ANY, 0, dump_list },
+ { "dump", VERB_ANY, VERB_ANY, 0, dump_core },
+ { "gdb", VERB_ANY, VERB_ANY, 0, run_gdb },
+ {}
+ };
+
+ return dispatch_verb(argc, argv, verbs, NULL);
+}
+
int main(int argc, char *argv[]) {
- _cleanup_(sd_journal_closep) sd_journal*j = NULL;
- int r = 0, units_active;
+ int r, units_active;
setlocale(LC_ALL, "");
log_parse_environment();
log_open();
r = parse_argv(argc, argv);
- if (r < 0)
- goto end;
-
- if (arg_action == ACTION_NONE)
+ if (r <= 0)
goto end;
sigbus_install();
- if (arg_directory) {
- r = sd_journal_open_directory(&j, arg_directory, 0);
- if (r < 0) {
- log_error_errno(r, "Failed to open journals in directory: %s: %m", arg_directory);
- goto end;
- }
- } else {
- r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
- if (r < 0) {
- log_error_errno(r, "Failed to open journal: %m");
- goto end;
- }
- }
-
- r = add_matches(j);
- if (r < 0)
- goto end;
-
- if (_unlikely_(log_get_max_level() >= LOG_DEBUG)) {
- _cleanup_free_ char *filter;
-
- filter = journal_make_match_string(j);
- log_debug("Journal filter: %s", filter);
- }
-
units_active = check_units_active(); /* error is treated the same as 0 */
- switch(arg_action) {
-
- case ACTION_LIST:
- case ACTION_INFO:
- pager_open(arg_no_pager, false);
- r = dump_list(j);
- break;
-
- case ACTION_DUMP:
- r = dump_core(j);
- break;
-
- case ACTION_GDB:
- r = run_gdb(j);
- break;
-
- default:
- assert_not_reached("Shouldn't be here");
- }
+ r = coredumpctl_main(argc, argv);
if (units_active > 0)
printf("%s-- Notice: %d systemd-coredump@.service %s, output may be incomplete.%s\n",
end:
pager_close();
- if (arg_output)
- fclose(arg_output);
+ safe_fclose(arg_output);
return r >= 0 ? r : EXIT_FAILURE;
}