static bool arg_all = false;
static PagerFlags arg_pager_flags = 0;
static int arg_lines = ARG_LINES_DEFAULT;
+static bool arg_lines_oldest = false;
static bool arg_no_tail = false;
static bool arg_truncate_newline = false;
static bool arg_quiet = false;
ACTION_LIST_FIELD_NAMES,
} arg_action = ACTION_SHOW;
-typedef struct BootId {
- sd_id128_t id;
- usec_t first_usec;
- usec_t last_usec;
-} BootId;
-
static int add_matches_for_device(sd_journal *j, const char *devpath) {
_cleanup_(sd_device_unrefp) sd_device *device = NULL;
sd_device *d = NULL;
return 1;
}
+static int parse_lines(const char *arg, bool graceful) {
+ const char *l;
+ int n, r;
+
+ assert(arg || graceful);
+
+ if (!arg)
+ goto default_noarg;
+
+ if (streq(arg, "all")) {
+ arg_lines = ARG_LINES_ALL;
+ return 1;
+ }
+
+ l = startswith(arg, "+");
+
+ r = safe_atoi(l ?: arg, &n);
+ if (r < 0 || n < 0) {
+ if (graceful)
+ goto default_noarg;
+
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse --lines='%s'.", arg);
+ }
+
+ arg_lines = n;
+ arg_lines_oldest = l;
+
+ return 1;
+
+default_noarg:
+ arg_lines = 10;
+ arg_lines_oldest = false;
+ return 0;
+}
+
+static bool arg_lines_needs_seek_end(void) {
+ return arg_lines >= 0 && !arg_lines_oldest;
+}
+
static int help_facilities(void) {
if (!arg_quiet)
puts("Available facilities:");
" -M --machine=CONTAINER Operate on local container\n"
" -m --merge Show entries from all available journals\n"
" -D --directory=PATH Show journal files from directory\n"
- " --file=PATH Show journal file\n"
+ " -i --file=PATH Show journal file\n"
" --root=PATH Operate on an alternate filesystem root\n"
" --image=PATH Operate on disk image as filesystem root\n"
" --image-policy=POLICY Specify disk image dissection policy\n"
" json, json-pretty, json-sse, json-seq, cat,\n"
" with-unit)\n"
" --output-fields=LIST Select fields to print in verbose/export/json modes\n"
- " -n --lines[=INTEGER] Number of journal entries to show\n"
+ " -n --lines[=[+]INTEGER] Number of journal entries to show\n"
" -r --reverse Show the newest entries first\n"
" --show-cursor Print the cursor after all the entries\n"
" --utc Express time in Coordinated Universal Time (UTC)\n"
ARG_HEADER,
ARG_FACILITY,
ARG_SETUP_KEYS,
- ARG_FILE,
ARG_INTERVAL,
ARG_VERIFY,
ARG_VERIFY_KEY,
{ "system", no_argument, NULL, ARG_SYSTEM },
{ "user", no_argument, NULL, ARG_USER },
{ "directory", required_argument, NULL, 'D' },
- { "file", required_argument, NULL, ARG_FILE },
+ { "file", required_argument, NULL, 'i' },
{ "root", required_argument, NULL, ARG_ROOT },
{ "image", required_argument, NULL, ARG_IMAGE },
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
assert(argc >= 0);
assert(argv);
- while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:g:c:S:U:t:u:NF:xrM:", options, NULL)) >= 0)
+ while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:g:c:S:U:t:u:NF:xrM:i:", options, NULL)) >= 0)
switch (c) {
break;
case 'n':
- if (optarg) {
- if (streq(optarg, "all"))
- arg_lines = ARG_LINES_ALL;
- else {
- r = safe_atoi(optarg, &arg_lines);
- if (r < 0 || arg_lines < 0)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse lines '%s'", optarg);
- }
- } else {
- arg_lines = 10;
-
- /* Hmm, no argument? Maybe the next
- * word on the command line is
- * supposed to be the argument? Let's
- * see if there is one, and is
- * parsable. */
- if (optind < argc) {
- int n;
- if (streq(argv[optind], "all")) {
- arg_lines = ARG_LINES_ALL;
- optind++;
- } else if (safe_atoi(argv[optind], &n) >= 0 && n >= 0) {
- arg_lines = n;
- optind++;
- }
- }
- }
+ r = parse_lines(optarg ?: argv[optind], !optarg);
+ if (r < 0)
+ return r;
+ if (r > 0 && !optarg)
+ optind++;
break;
arg_directory = optarg;
break;
- case ARG_FILE:
+ case 'i':
if (streq(optarg, "-"))
/* An undocumented feature: we can read journal files from STDIN. We don't document
* this though, since after all we only support this for mmap-able, seekable files, and
- * not for example pipes which are probably the primary usecase for reading things from
+ * not for example pipes which are probably the primary use case for reading things from
* STDIN. To avoid confusion we hence don't document this feature. */
arg_file_stdin = true;
else {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--since= must be before --until=.");
- if (!!arg_cursor + !!arg_after_cursor + !!arg_since_set > 1)
+ if (!!arg_cursor + !!arg_after_cursor + !!arg_cursor_file + !!arg_since_set > 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Please specify only one of --since=, --cursor=, and --after-cursor=.");
+ "Please specify only one of --since=, --cursor=, --cursor-file=, and --after-cursor=.");
if (arg_follow && arg_reverse)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Please specify either --reverse= or --follow=, not both.");
+ "Please specify either --reverse or --follow, not both.");
+
+ if (arg_lines >= 0 && arg_lines_oldest && (arg_reverse || arg_follow))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--lines=+N is unsupported when --reverse or --follow is specified.");
if (!IN_SET(arg_action, ACTION_SHOW, ACTION_DUMP_CATALOG, ACTION_LIST_CATALOG) && optind < argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
if (r < 0)
return r;
- /* When --grep is used along with --lines, we don't know how many lines we can print.
- * So we search backwards and count until enough lines have been printed or we hit the head.
+ /* When --grep is used along with --lines without '+', i.e. when we start from the end of the
+ * journal, we don't know how many lines we can print. So we search backwards and count until
+ * enough lines have been printed or we hit the head.
* An exception is that --follow might set arg_lines, so let's not imply --reverse
* if that is specified. */
- if (arg_lines >= 0 && !arg_follow)
+ if (arg_lines_needs_seek_end() && !arg_follow)
arg_reverse = true;
}
return 0;
}
-static int discover_next_boot(
- sd_journal *j,
- sd_id128_t previous_boot_id,
- bool advance_older,
- BootId *ret) {
-
- BootId boot;
- int r;
-
- assert(j);
- assert(ret);
-
- /* We expect the journal to be on the last position of a boot
- * (in relation to the direction we are going), so that the next
- * invocation of sd_journal_next/previous will be from a different
- * boot. We then collect any information we desire and then jump
- * to the last location of the new boot by using a _BOOT_ID match
- * coming from the other journal direction. */
-
- /* Make sure we aren't restricted by any _BOOT_ID matches, so that
- * we can actually advance to a *different* boot. */
- sd_journal_flush_matches(j);
-
- do {
- if (advance_older)
- r = sd_journal_previous(j);
- else
- r = sd_journal_next(j);
- if (r < 0)
- return r;
- else if (r == 0) {
- *ret = (BootId) {};
- return 0; /* End of journal, yay. */
- }
-
- r = sd_journal_get_monotonic_usec(j, NULL, &boot.id);
- if (r < 0)
- return r;
-
- /* We iterate through this in a loop, until the boot ID differs from the previous one. Note that
- * normally, this will only require a single iteration, as we moved to the last entry of the previous
- * boot entry already. However, it might happen that the per-journal-field entry arrays are less
- * complete than the main entry array, and hence might reference an entry that's not actually the last
- * one of the boot ID as last one. Let's hence use the per-field array is initial seek position to
- * speed things up, but let's not trust that it is complete, and hence, manually advance as
- * necessary. */
-
- } while (sd_id128_equal(boot.id, previous_boot_id));
-
- r = sd_journal_get_realtime_usec(j, &boot.first_usec);
- if (r < 0)
- return r;
-
- /* Now seek to the last occurrence of this boot ID. */
- r = add_match_boot_id(j, boot.id);
- if (r < 0)
- return r;
-
- if (advance_older)
- r = sd_journal_seek_head(j);
- else
- r = sd_journal_seek_tail(j);
- if (r < 0)
- return r;
-
- if (advance_older)
- r = sd_journal_next(j);
- else
- r = sd_journal_previous(j);
- if (r < 0)
- return r;
- else if (r == 0)
- return log_debug_errno(SYNTHETIC_ERRNO(ENODATA),
- "Whoopsie! We found a boot ID but can't read its last entry."); /* This shouldn't happen. We just came from this very boot ID. */
-
- r = sd_journal_get_realtime_usec(j, &boot.last_usec);
- if (r < 0)
- return r;
-
- sd_journal_flush_matches(j);
- *ret = boot;
- return 1;
-}
-
-static int find_boot_by_id(sd_journal *j) {
- int r;
-
- assert(j);
-
- sd_journal_flush_matches(j);
-
- r = add_match_boot_id(j, arg_boot_id);
- if (r < 0)
- return r;
-
- r = sd_journal_seek_head(j); /* seek to oldest */
- if (r < 0)
- return r;
-
- r = sd_journal_next(j); /* read the oldest entry */
- if (r < 0)
- return r;
-
- /* At this point the read pointer is positioned at the oldest occurrence of the reference boot ID.
- * After flushing the matches, one more invocation of _previous() will hence place us at the
- * following entry, which must then have an older boot ID */
-
- sd_journal_flush_matches(j);
- return r > 0;
-}
-
-static int find_boot_by_offset(sd_journal *j) {
- bool advance_older, skip_once;
- int r;
-
- /* Adjust for the asymmetry that offset 0 is the last (and current) boot, while 1 is considered the
- * (chronological) first boot in the journal. */
- advance_older = skip_once = arg_boot_offset <= 0;
-
- if (advance_older)
- r = sd_journal_seek_tail(j); /* seek to newest */
- else
- r = sd_journal_seek_head(j); /* seek to oldest */
- if (r < 0)
- return r;
-
- /* No sd_journal_next()/_previous() here.
- *
- * At this point the read pointer is positioned after the newest/before the oldest entry in the whole
- * journal. The next invocation of _previous()/_next() will hence position us at the newest/oldest
- * entry we have. */
-
- int offset = arg_boot_offset;
- sd_id128_t previous_boot_id = SD_ID128_NULL;
- for (;;) {
- BootId boot;
-
- r = discover_next_boot(j, previous_boot_id, advance_older, &boot);
- if (r <= 0)
- return r;
-
- previous_boot_id = boot.id;
-
- if (!skip_once)
- offset += advance_older ? 1 : -1;
- skip_once = false;
-
- if (offset == 0) {
- arg_boot_id = boot.id;
- return true;
- }
- }
-}
-
-static int get_boots(sd_journal *j, BootId **ret_boots, size_t *ret_n_boots) {
- _cleanup_free_ BootId *boots = NULL;
- size_t n_boots = 0;
- int r;
-
- assert(j);
- assert(ret_boots);
- assert(ret_n_boots);
-
- r = sd_journal_seek_head(j); /* seek to oldest */
- if (r < 0)
- return r;
-
- /* No sd_journal_next()/_previous() here.
- *
- * At this point the read pointer is positioned before the oldest entry in the whole journal. The
- * next invocation of _next() will hence position us at the oldest entry we have. */
-
- sd_id128_t previous_boot_id = SD_ID128_NULL;
- for (;;) {
- BootId boot;
-
- r = discover_next_boot(j, previous_boot_id, /* advance_older = */ false, &boot);
- if (r < 0)
- return r;
- if (r == 0)
- break;
-
- previous_boot_id = boot.id;
-
- FOREACH_ARRAY(i, boots, n_boots)
- if (sd_id128_equal(i->id, boot.id))
- /* The boot id is already stored, something wrong with the journal files.
- * Exiting as otherwise this problem would cause an infinite loop. */
- break;
-
- if (!GREEDY_REALLOC(boots, n_boots + 1))
- return -ENOMEM;
-
- boots[n_boots++] = boot;
- }
-
- *ret_boots = TAKE_PTR(boots);
- *ret_n_boots = n_boots;
- return n_boots > 0;
-}
-
static int list_boots(sd_journal *j) {
_cleanup_(table_unrefp) Table *table = NULL;
_cleanup_free_ BootId *boots = NULL;
assert(j);
- r = get_boots(j, &boots, &n_boots);
+ r = journal_get_boots(j, &boots, &n_boots);
if (r < 0)
return log_error_errno(r, "Failed to determine boots: %m");
if (r == 0)
return add_match_this_boot(j, arg_machine);
if (sd_id128_is_null(arg_boot_id)) {
- r = find_boot_by_offset(j);
+ r = journal_find_boot_by_offset(j, arg_boot_offset, &arg_boot_id);
if (r < 0)
return log_error_errno(r, "Failed to find journal entry from the specified boot offset (%+i): %m",
arg_boot_offset);
"No journal boot entry found from the specified boot offset (%+i).",
arg_boot_offset);
} else {
- r = find_boot_by_id(j);
+ r = journal_find_boot_by_id(j, arg_boot_id);
if (r < 0)
return log_error_errno(r, "Failed to find journal entry from the specified boot ID (%s): %m",
SD_ID128_TO_STRING(arg_boot_id));
.fsprg_state_size = htole64(state_size),
};
- r = loop_write(fd, &h, sizeof(h), false);
+ r = loop_write(fd, &h, sizeof(h));
if (r < 0)
return log_error_errno(r, "Failed to write header: %m");
- r = loop_write(fd, state, state_size, false);
+ r = loop_write(fd, state, state_size);
if (r < 0)
return log_error_errno(r, "Failed to write state: %m");
static int setup_event(Context *c, int fd, sd_event **ret) {
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
- struct stat st;
int r;
assert(arg_follow);
if (r < 0)
return log_error_errno(r, "Failed to add io event source for journal: %m");
- if (fstat(STDOUT_FILENO, &st) < 0)
- return log_error_errno(r, "Failed to stat stdout: %m");
-
- if (IN_SET(st.st_mode & S_IFMT, S_IFCHR, S_IFIFO, S_IFSOCK)) {
- /* 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");
- }
+ /* 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);
}
static int run(int argc, char *argv[]) {
- bool need_seek = false, since_seeked = false, use_cursor = false, after_cursor = false;
+ bool need_seek = false, since_seeked = false, after_cursor = false;
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
_cleanup_(umount_and_freep) char *mounted_dir = NULL;
_cleanup_(sd_journal_closep) sd_journal *j = NULL;
+ _cleanup_free_ char *cursor_from_file = NULL;
+ const char *cursor = NULL;
int n_shown, r, poll_fd = -EBADF;
setlocale(LC_ALL, "");
}
if (arg_cursor || arg_after_cursor || arg_cursor_file) {
- _cleanup_free_ char *cursor_from_file = NULL;
- const char *cursor = arg_cursor ?: arg_after_cursor;
+ cursor = arg_cursor ?: arg_after_cursor;
if (arg_cursor_file) {
r = read_one_line_file(arg_cursor_file, &cursor_from_file);
}
} else
after_cursor = arg_after_cursor;
+ }
- if (cursor) {
- r = sd_journal_seek_cursor(j, cursor);
- if (r < 0)
- return log_error_errno(r, "Failed to seek to cursor: %m");
+ if (cursor) {
+ r = sd_journal_seek_cursor(j, cursor);
+ if (r < 0)
+ return log_error_errno(r, "Failed to seek to cursor: %m");
- use_cursor = true;
+ 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 (use_cursor) {
- if (!arg_reverse)
- r = sd_journal_next_skip(j, 1 + after_cursor);
- else
- r = sd_journal_previous_skip(j, 1 + after_cursor);
- if (after_cursor && r < 2) {
+ if (r == 0) {
/* We couldn't find the next entry after the cursor. */
if (arg_follow)
need_seek = true;
else
arg_lines = 0;
}
-
- } else if (arg_until_set && (arg_reverse || arg_lines >= 0)) {
- /* If both --until and any of --reverse and --lines is specified, things get
+ } else if (arg_until_set && (arg_reverse || arg_lines_needs_seek_end())) {
+ /* If both --until and any of --reverse and --lines=N is specified, things get
* a little tricky. We seek to the place of --until first. If only --reverse or
* --reverse and --lines is specified, we search backwards and let the output
* counter handle --lines for us. If only --lines is used, we just jump backwards
if (arg_reverse)
r = sd_journal_previous(j);
- else /* arg_lines >= 0 */
+ else /* arg_lines_needs_seek_end */
r = sd_journal_previous_skip(j, arg_lines);
} else if (arg_reverse) {
r = sd_journal_previous(j);
- } else if (arg_lines >= 0) {
+ } else if (arg_lines_needs_seek_end()) {
r = sd_journal_seek_tail(j);
if (r < 0)
return log_error_errno(r, "Failed to seek to tail: %m");