#include "logs-show.h"
#include "main-func.h"
#include "memory-util.h"
+#include "memstream-util.h"
#include "missing_sched.h"
#include "mkdir.h"
#include "mount-util.h"
#include "rlimit-util.h"
#include "set.h"
#include "sigbus.h"
+#include "signal-util.h"
#include "static-destruct.h"
#include "stdio-util.h"
#include "string-table.h"
sd_id128_t machine,
bool full,
char **ret_url) {
- _cleanup_free_ char *url = NULL;
- _cleanup_fclose_ FILE *f = NULL;
- size_t url_size = 0;
- int r;
+
+ _cleanup_(memstream_done) MemStream m = {};
+ FILE *f;
assert(seed);
assert(seed_size > 0);
- f = open_memstream_unlocked(&url, &url_size);
+ f = memstream_init(&m);
if (!f)
return -ENOMEM;
fprintf(f, ";hostname=%s", hn);
}
- r = fflush_and_check(f);
- if (r < 0)
- return r;
-
- f = safe_fclose(f);
-
- if (!url)
- return -ENOMEM;
-
- *ret_url = TAKE_PTR(url);
- return 0;
+ return memstream_finalize(&m, ret_url, NULL);
}
#endif
return 0;
}
-static int wait_for_change(sd_journal *j, int poll_fd) {
- struct pollfd pollfds[] = {
- { .fd = poll_fd, .events = POLLIN },
- { .fd = STDOUT_FILENO },
- };
- usec_t timeout;
+static int update_cursor(sd_journal *j) {
+ _cleanup_free_ char *cursor = NULL;
int r;
assert(j);
- assert(poll_fd >= 0);
- /* Much like sd_journal_wait() but also keeps an eye on STDOUT, and exits as soon as we see a POLLHUP on that,
- * i.e. when it is closed. */
+ if (!arg_show_cursor && !arg_cursor_file)
+ return 0;
- r = sd_journal_get_timeout(j, &timeout);
+ r = sd_journal_get_cursor(j, &cursor);
+ if (r == -EADDRNOTAVAIL)
+ return 0;
if (r < 0)
- return log_error_errno(r, "Failed to determine journal waiting time: %m");
+ return log_error_errno(r, "Failed to get cursor: %m");
- r = ppoll_usec(pollfds, ELEMENTSOF(pollfds), timeout);
- if (r == -EINTR)
- return 0;
+ 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;
+}
+
+typedef struct Context {
+ sd_journal *journal;
+ 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 int show(Context *c) {
+ sd_journal *j;
+ int r, n_shown = 0;
+
+ assert(c);
+
+ j = ASSERT_PTR(c->journal);
+
+ while (arg_lines < 0 || n_shown < arg_lines || arg_follow) {
+ int flags;
+ 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)) {
+ /* If --lines= is set, we usually rely on the n_shown to tell us
+ * when to stop. However, if --since= 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;
+ }
+ }
+
+ flags =
+ arg_all * OUTPUT_SHOW_ALL |
+ arg_full * OUTPUT_FULL_WIDTH |
+ colors_enabled() * OUTPUT_COLOR |
+ arg_catalog * OUTPUT_CATALOG |
+ arg_utc * OUTPUT_UTC |
+ arg_no_hostname * OUTPUT_NO_HOSTNAME;
+
+ 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 log_error_errno(r, "Couldn't wait for journal event: %m");
+ 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;
- if (pollfds[1].revents & (POLLHUP|POLLERR)) /* STDOUT has been closed? */
- return log_debug_errno(SYNTHETIC_ERRNO(ECANCELED),
- "Standard output has been closed.");
+ 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);
+ }
- r = sd_journal_process(j);
+ 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 process journal events: %m");
+ 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 < 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 run(int argc, char *argv[]) {
+ bool need_seek = false, since_seeked = false, use_cursor = false, after_cursor = false;
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
_cleanup_(umount_and_freep) char *mounted_dir = NULL;
- bool previous_boot_id_valid = false, first_line = true, ellipsized = false, need_seek = false, since_seeked = false;
- bool use_cursor = false, after_cursor = false;
_cleanup_(sd_journal_closep) sd_journal *j = NULL;
- sd_id128_t previous_boot_id = SD_ID128_NULL, previous_boot_id_output = SD_ID128_NULL;
- dual_timestamp previous_ts_output = DUAL_TIMESTAMP_NULL;
_cleanup_close_ int machine_fd = -EBADF;
- int n_shown = 0, r, poll_fd = -EBADF;
+ int n_shown, r, poll_fd = -EBADF;
setlocale(LC_ALL, "");
log_setup();
}
}
- for (;;) {
- while (arg_lines < 0 || n_shown < arg_lines || (arg_follow && !first_line)) {
- int flags;
- size_t highlight[2] = {};
-
- if (need_seek) {
- 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_set && !arg_reverse && (arg_lines < 0 || arg_since_set)) {
- /* If --lines= is set, we usually rely on the n_shown to tell us
- * when to stop. However, if --since= 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 || !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");
- since_seeked = true;
-
- need_seek = true;
- continue;
- }
- 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 (previous_boot_id_valid &&
- !sd_id128_equal(boot_id, previous_boot_id))
- printf("%s-- Boot "SD_ID128_FORMAT_STR" --%s\n",
- ansi_highlight(), SD_ID128_FORMAT_VAL(boot_id), ansi_normal());
-
- previous_boot_id = boot_id;
- 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) {
- 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) {
- need_seek = true;
- continue;
- }
- }
+ Context c = {
+ .journal = j,
+ .need_seek = need_seek,
+ .since_seeked = since_seeked,
+ };
- flags =
- arg_all * OUTPUT_SHOW_ALL |
- arg_full * OUTPUT_FULL_WIDTH |
- colors_enabled() * OUTPUT_COLOR |
- arg_catalog * OUTPUT_CATALOG |
- arg_utc * OUTPUT_UTC |
- arg_no_hostname * OUTPUT_NO_HOSTNAME;
-
- r = show_journal_entry(stdout, j, arg_output, 0, flags,
- arg_output_fields, highlight, &ellipsized,
- &previous_ts_output, &previous_boot_id_output);
- need_seek = true;
- if (r == -EADDRNOTAVAIL)
- break;
- if (r < 0)
- return r;
+ if (arg_follow) {
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ int sig;
- n_shown++;
+ assert(poll_fd >= 0);
- /* 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");
- }
- }
+ r = setup_event(&c, poll_fd, &e);
+ if (r < 0)
+ return r;
- if (!arg_follow) {
- if (n_shown == 0 && !arg_quiet)
- printf("-- No entries --\n");
- break;
- }
+ r = sd_event_loop(e);
+ if (r < 0)
+ return r;
+ sig = r;
- fflush(stdout);
+ /* unref signal event sources. */
+ e = sd_event_unref(e);
- r = wait_for_change(j, poll_fd);
+ r = update_cursor(j);
if (r < 0)
return r;
- first_line = false;
+ /* re-send the original signal. */
+ assert(SIGNAL_VALID(sig));
+ if (raise(sig) < 0)
+ log_error("Failed to raise the original signal SIG%s, ignoring: %m", signal_to_string(sig));
+
+ return 0;
}
- if (arg_show_cursor || arg_cursor_file) {
- _cleanup_free_ char *cursor = NULL;
+ r = show(&c);
+ if (r < 0)
+ return r;
+ n_shown = r;
- r = sd_journal_get_cursor(j, &cursor);
- if (r < 0 && r != -EADDRNOTAVAIL)
- return log_error_errno(r, "Failed to get cursor: %m");
- if (r >= 0) {
- if (arg_show_cursor)
- printf("-- cursor: %s\n", cursor);
+ if (n_shown == 0 && !arg_quiet)
+ printf("-- No entries --\n");
- 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);
- }
- }
- }
+ r = update_cursor(j);
+ if (r < 0)
+ return r;
if (arg_compiled_pattern && n_shown == 0)
/* --grep was used, no error was thrown, but the pattern didn't