diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 03:50:40 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 03:50:40 +0000 |
commit | fc53809803cd2bc2434e312b19a18fa36776da12 (patch) | |
tree | b4b43bd6538f51965ce32856e9c053d0f90919c8 /src/journal/journalctl-show.c | |
parent | Adding upstream version 255.5. (diff) | |
download | systemd-fc53809803cd2bc2434e312b19a18fa36776da12.tar.xz systemd-fc53809803cd2bc2434e312b19a18fa36776da12.zip |
Adding upstream version 256.upstream/256
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/journal/journalctl-show.c')
-rw-r--r-- | src/journal/journalctl-show.c | 477 |
1 files changed, 477 insertions, 0 deletions
diff --git a/src/journal/journalctl-show.c b/src/journal/journalctl-show.c new file mode 100644 index 0000000..e8ffc72 --- /dev/null +++ b/src/journal/journalctl-show.c @@ -0,0 +1,477 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <unistd.h> + +#include "sd-event.h" + +#include "fileio.h" +#include "journalctl.h" +#include "journalctl-filter.h" +#include "journalctl-show.h" +#include "journalctl-util.h" +#include "logs-show.h" +#include "terminal-util.h" + +#define PROCESS_INOTIFY_INTERVAL 1024 /* Every 1024 messages processed */ + +typedef struct Context { + sd_journal *journal; + bool has_cursor; + bool need_seek; + bool since_seeked; + bool ellipsized; + bool previous_boot_id_valid; + sd_id128_t previous_boot_id; + sd_id128_t previous_boot_id_output; + dual_timestamp previous_ts_output; +} Context; + +static void context_done(Context *c) { + assert(c); + + sd_journal_close(c->journal); +} + +static int seek_journal(Context *c) { + sd_journal *j = ASSERT_PTR(ASSERT_PTR(c)->journal); + _cleanup_free_ char *cursor_from_file = NULL; + const char *cursor = NULL; + bool after_cursor = false; + int r; + + if (arg_cursor || arg_after_cursor) { + assert(!!arg_cursor != !!arg_after_cursor); + + cursor = arg_cursor ?: arg_after_cursor; + after_cursor = arg_after_cursor; + + } else if (arg_cursor_file) { + r = read_one_line_file(arg_cursor_file, &cursor_from_file); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to read cursor file %s: %m", arg_cursor_file); + if (r > 0) { + cursor = cursor_from_file; + after_cursor = true; + } + } + + if (cursor) { + c->has_cursor = true; + + r = sd_journal_seek_cursor(j, cursor); + if (r < 0) + return log_error_errno(r, "Failed to seek to cursor: %m"); + + r = sd_journal_step_one(j, !arg_reverse); + if (r < 0) + return log_error_errno(r, "Failed to iterate through journal: %m"); + + if (after_cursor && r > 0) { + /* With --after-cursor=/--cursor-file= we want to skip the first entry only if it's + * the entry the cursor is pointing at, otherwise, if some journal filters are used, + * we might skip the first entry of the filter match, which leads to unexpectedly + * missing journal entries. */ + int k; + + k = sd_journal_test_cursor(j, cursor); + if (k < 0) + return log_error_errno(k, "Failed to test cursor against current entry: %m"); + if (k > 0) + /* Current entry matches the one our cursor is pointing at, so let's try + * to advance the next entry. */ + r = sd_journal_step_one(j, !arg_reverse); + } + + if (r == 0 && !arg_follow) + /* We couldn't find the next entry after the cursor. */ + arg_lines = 0; + + } else if (arg_reverse || arg_lines_needs_seek_end()) { + /* If --reverse and/or --lines=N are specified, things get a little tricky. First we seek to + * the place of --until if specified, otherwise seek to tail. Then, if --reverse is + * specified, we search backwards and let the output counter in show() handle --lines for us. + * If --reverse is unspecified, we just jump backwards arg_lines and search afterwards from + * there. */ + + if (arg_until_set) { + r = sd_journal_seek_realtime_usec(j, arg_until); + if (r < 0) + return log_error_errno(r, "Failed to seek to date: %m"); + } else { + r = sd_journal_seek_tail(j); + if (r < 0) + return log_error_errno(r, "Failed to seek to tail: %m"); + } + + if (arg_reverse) + r = sd_journal_previous(j); + else /* arg_lines_needs_seek_end */ + r = sd_journal_previous_skip(j, arg_lines); + + } else if (arg_since_set) { + /* This is placed after arg_reverse and arg_lines. If --since is used without + * both, we seek to the place of --since and search afterwards from there. + * If used with --reverse or --lines, we seek to the tail first and check if + * the entry is within the range of --since later. */ + + r = sd_journal_seek_realtime_usec(j, arg_since); + if (r < 0) + return log_error_errno(r, "Failed to seek to date: %m"); + c->since_seeked = true; + + r = sd_journal_next(j); + + } else { + r = sd_journal_seek_head(j); + if (r < 0) + return log_error_errno(r, "Failed to seek to head: %m"); + + r = sd_journal_next(j); + } + if (r < 0) + return log_error_errno(r, "Failed to iterate through journal: %m"); + if (r == 0) + c->need_seek = true; + + return 0; +} + +static int show(Context *c) { + sd_journal *j = ASSERT_PTR(ASSERT_PTR(c)->journal); + int r, n_shown = 0; + + OutputFlags flags = + arg_all * OUTPUT_SHOW_ALL | + arg_full * OUTPUT_FULL_WIDTH | + colors_enabled() * OUTPUT_COLOR | + arg_catalog * OUTPUT_CATALOG | + arg_utc * OUTPUT_UTC | + arg_truncate_newline * OUTPUT_TRUNCATE_NEWLINE | + arg_no_hostname * OUTPUT_NO_HOSTNAME; + + while (arg_lines < 0 || n_shown < arg_lines || arg_follow) { + size_t highlight[2] = {}; + + if (c->need_seek) { + r = sd_journal_step_one(j, !arg_reverse); + if (r < 0) + return log_error_errno(r, "Failed to iterate through journal: %m"); + if (r == 0) + break; + } + + if (arg_until_set && !arg_reverse && (arg_lines < 0 || arg_since_set || c->has_cursor)) { + /* If --lines= is set, we usually rely on the n_shown to tell us when to stop. + * However, if --since= or one of the cursor argument is set too, we may end up + * having less than --lines= to output. In this case let's also check if the entry + * is in range. */ + + usec_t usec; + + r = sd_journal_get_realtime_usec(j, &usec); + if (r < 0) + return log_error_errno(r, "Failed to determine timestamp: %m"); + if (usec > arg_until) + break; + } + + if (arg_since_set && (arg_reverse || !c->since_seeked)) { + usec_t usec; + + r = sd_journal_get_realtime_usec(j, &usec); + if (r < 0) + return log_error_errno(r, "Failed to determine timestamp: %m"); + + if (usec < arg_since) { + if (arg_reverse) + break; /* Reached the earliest entry */ + + /* arg_lines >= 0 (!since_seeked): + * We jumped arg_lines back and it seems to be too much */ + r = sd_journal_seek_realtime_usec(j, arg_since); + if (r < 0) + return log_error_errno(r, "Failed to seek to date: %m"); + c->since_seeked = true; + + c->need_seek = true; + continue; + } + c->since_seeked = true; /* We're surely within the range of --since now */ + } + + if (!arg_merge && !arg_quiet) { + sd_id128_t boot_id; + + r = sd_journal_get_monotonic_usec(j, NULL, &boot_id); + if (r >= 0) { + if (c->previous_boot_id_valid && + !sd_id128_equal(boot_id, c->previous_boot_id)) + printf("%s-- Boot "SD_ID128_FORMAT_STR" --%s\n", + ansi_highlight(), SD_ID128_FORMAT_VAL(boot_id), ansi_normal()); + + c->previous_boot_id = boot_id; + c->previous_boot_id_valid = true; + } + } + + if (arg_compiled_pattern) { + const void *message; + size_t len; + + r = sd_journal_get_data(j, "MESSAGE", &message, &len); + if (r < 0) { + if (r == -ENOENT) { + c->need_seek = true; + continue; + } + + return log_error_errno(r, "Failed to get MESSAGE field: %m"); + } + + assert_se(message = startswith(message, "MESSAGE=")); + + r = pattern_matches_and_log(arg_compiled_pattern, message, + len - strlen("MESSAGE="), highlight); + if (r < 0) + return r; + if (r == 0) { + c->need_seek = true; + continue; + } + } + + r = show_journal_entry(stdout, j, arg_output, 0, flags, + arg_output_fields, highlight, &c->ellipsized, + &c->previous_ts_output, &c->previous_boot_id_output); + c->need_seek = true; + if (r == -EADDRNOTAVAIL) + break; + if (r < 0) + return r; + + n_shown++; + + /* If journalctl take a long time to process messages, and during that time journal file + * rotation occurs, a journalctl client will keep those rotated files open until it calls + * sd_journal_process(), which typically happens as a result of calling sd_journal_wait() below + * in the "following" case. By periodically calling sd_journal_process() during the processing + * loop we shrink the window of time a client instance has open file descriptors for rotated + * (deleted) journal files. */ + if ((n_shown % PROCESS_INOTIFY_INTERVAL) == 0) { + r = sd_journal_process(j); + if (r < 0) + return log_error_errno(r, "Failed to process inotify events: %m"); + } + } + + return n_shown; +} + +static int show_and_fflush(Context *c, sd_event_source *s) { + int r; + + assert(c); + assert(s); + + r = show(c); + if (r < 0) + return sd_event_exit(sd_event_source_get_event(s), r); + + fflush(stdout); + return 0; +} + +static int on_journal_event(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + Context *c = ASSERT_PTR(userdata); + int r; + + assert(s); + + r = sd_journal_process(c->journal); + if (r < 0) { + log_error_errno(r, "Failed to process journal events: %m"); + return sd_event_exit(sd_event_source_get_event(s), r); + } + + return show_and_fflush(c, s); +} + +static int on_first_event(sd_event_source *s, void *userdata) { + return show_and_fflush(userdata, s); +} + +static int on_signal(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + assert(s); + assert(si); + assert(IN_SET(si->ssi_signo, SIGTERM, SIGINT)); + + return sd_event_exit(sd_event_source_get_event(s), si->ssi_signo); +} + +static int setup_event(Context *c, int fd, sd_event **ret) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + int r; + + assert(arg_follow); + assert(c); + assert(fd >= 0); + assert(ret); + + r = sd_event_default(&e); + if (r < 0) + return log_error_errno(r, "Failed to allocate sd_event object: %m"); + + (void) sd_event_add_signal(e, NULL, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, on_signal, NULL); + (void) sd_event_add_signal(e, NULL, SIGINT | SD_EVENT_SIGNAL_PROCMASK, on_signal, NULL); + + r = sd_event_add_io(e, NULL, fd, EPOLLIN, &on_journal_event, c); + if (r < 0) + return log_error_errno(r, "Failed to add io event source for journal: %m"); + + /* Also keeps an eye on STDOUT, and exits as soon as we see a POLLHUP on that, i.e. when it is closed. */ + r = sd_event_add_io(e, NULL, STDOUT_FILENO, EPOLLHUP|EPOLLERR, NULL, INT_TO_PTR(-ECANCELED)); + if (r == -EPERM) + /* Installing an epoll watch on a regular file doesn't work and fails with EPERM. Which is + * totally OK, handle it gracefully. epoll_ctl() documents EPERM as the error returned when + * the specified fd doesn't support epoll, hence it's safe to check for that. */ + log_debug_errno(r, "Unable to install EPOLLHUP watch on stderr, not watching for hangups."); + else if (r < 0) + return log_error_errno(r, "Failed to add io event source for stdout: %m"); + + if (arg_lines != 0 || arg_since_set) { + r = sd_event_add_defer(e, NULL, on_first_event, c); + if (r < 0) + return log_error_errno(r, "Failed to add defer event source: %m"); + } + + *ret = TAKE_PTR(e); + return 0; +} + +static int update_cursor(sd_journal *j) { + _cleanup_free_ char *cursor = NULL; + int r; + + assert(j); + + if (!arg_show_cursor && !arg_cursor_file) + return 0; + + r = sd_journal_get_cursor(j, &cursor); + if (r == -EADDRNOTAVAIL) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to get cursor: %m"); + + if (arg_show_cursor) + printf("-- cursor: %s\n", cursor); + + if (arg_cursor_file) { + r = write_string_file(arg_cursor_file, cursor, WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_ATOMIC); + if (r < 0) + return log_error_errno(r, "Failed to write new cursor to %s: %m", arg_cursor_file); + } + + return 0; +} + +int action_show(char **matches) { + _cleanup_(context_done) Context c = {}; + int n_shown, r, poll_fd = -EBADF; + + assert(arg_action == ACTION_SHOW); + + (void) signal(SIGWINCH, columns_lines_cache_reset); + + r = acquire_journal(&c.journal); + if (r < 0) + return r; + + if (!journal_boot_has_effect(c.journal)) + return arg_compiled_pattern ? -ENOENT : 0; + + r = add_filters(c.journal, matches); + if (r < 0) + return r; + + r = seek_journal(&c); + if (r < 0) + return r; + + /* Opening the fd now means the first sd_journal_wait() will actually wait */ + if (arg_follow) { + poll_fd = sd_journal_get_fd(c.journal); + if (poll_fd == -EMFILE) { + log_warning_errno(poll_fd, "Insufficient watch descriptors available. Reverting to -n."); + arg_follow = false; + } else if (poll_fd == -EMEDIUMTYPE) + return log_error_errno(poll_fd, "The --follow switch is not supported in conjunction with reading from STDIN."); + else if (poll_fd < 0) + return log_error_errno(poll_fd, "Failed to get journal fd: %m"); + } + + if (!arg_follow) + pager_open(arg_pager_flags); + + if (!arg_quiet && (arg_lines != 0 || arg_follow) && DEBUG_LOGGING) { + usec_t start, end; + char start_buf[FORMAT_TIMESTAMP_MAX], end_buf[FORMAT_TIMESTAMP_MAX]; + + r = sd_journal_get_cutoff_realtime_usec(c.journal, &start, &end); + if (r < 0) + return log_error_errno(r, "Failed to get cutoff: %m"); + if (r > 0) { + if (arg_follow) + printf("-- Journal begins at %s. --\n", + format_timestamp_maybe_utc(start_buf, sizeof(start_buf), start)); + else + printf("-- Journal begins at %s, ends at %s. --\n", + format_timestamp_maybe_utc(start_buf, sizeof(start_buf), start), + format_timestamp_maybe_utc(end_buf, sizeof(end_buf), end)); + } + } + + if (arg_follow) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + int sig; + + assert(poll_fd >= 0); + + r = setup_event(&c, poll_fd, &e); + if (r < 0) + return r; + + r = sd_event_loop(e); + if (r < 0) + return r; + sig = r; + + r = update_cursor(c.journal); + if (r < 0) + return r; + + /* re-send the original signal. */ + return sig; + } + + r = show(&c); + if (r < 0) + return r; + n_shown = r; + + if (n_shown == 0 && !arg_quiet) + printf("-- No entries --\n"); + + r = update_cursor(c.journal); + if (r < 0) + return r; + + if (arg_compiled_pattern && n_shown == 0) + /* --grep was used, no error was thrown, but the pattern didn't + * match anything. Let's mimic grep's behavior here and return + * a non-zero exit code, so journalctl --grep can be used + * in scripts and such */ + return -ENOENT; + + return 0; +} |