]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
journalctl: split journalctl.c into small pieces
authorYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 20 Mar 2024 18:50:09 +0000 (03:50 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Tue, 26 Mar 2024 13:24:24 +0000 (22:24 +0900)
And introduces per-action functions.
No functional change, just refactoring.

17 files changed:
src/journal/journalctl-authenticate.c [new file with mode: 0644]
src/journal/journalctl-authenticate.h [new file with mode: 0644]
src/journal/journalctl-catalog.c [new file with mode: 0644]
src/journal/journalctl-catalog.h [new file with mode: 0644]
src/journal/journalctl-filter.c [new file with mode: 0644]
src/journal/journalctl-filter.h [new file with mode: 0644]
src/journal/journalctl-misc.c [new file with mode: 0644]
src/journal/journalctl-misc.h [new file with mode: 0644]
src/journal/journalctl-show.c [new file with mode: 0644]
src/journal/journalctl-show.h [new file with mode: 0644]
src/journal/journalctl-util.c [new file with mode: 0644]
src/journal/journalctl-util.h [new file with mode: 0644]
src/journal/journalctl-varlink.c [new file with mode: 0644]
src/journal/journalctl-varlink.h [new file with mode: 0644]
src/journal/journalctl.c
src/journal/journalctl.h [new file with mode: 0644]
src/journal/meson.build

diff --git a/src/journal/journalctl-authenticate.c b/src/journal/journalctl-authenticate.c
new file mode 100644 (file)
index 0000000..79f09b1
--- /dev/null
@@ -0,0 +1,217 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "chattr-util.h"
+#include "errno-util.h"
+#include "fd-util.h"
+#include "fs-util.h"
+#include "fsprg.h"
+#include "hostname-util.h"
+#include "io-util.h"
+#include "journal-authenticate.h"
+#include "journalctl.h"
+#include "journalctl-authenticate.h"
+#include "memstream-util.h"
+#include "qrcode-util.h"
+#include "random-util.h"
+#include "terminal-util.h"
+#include "tmpfile-util.h"
+
+static int format_journal_url(
+                const void *seed,
+                size_t seed_size,
+                uint64_t start,
+                uint64_t interval,
+                const char *hn,
+                sd_id128_t machine,
+                bool full,
+                char **ret_url) {
+
+        _cleanup_(memstream_done) MemStream m = {};
+        FILE *f;
+
+        assert(seed);
+        assert(seed_size > 0);
+
+        f = memstream_init(&m);
+        if (!f)
+                return -ENOMEM;
+
+        if (full)
+                fputs("fss://", f);
+
+        for (size_t i = 0; i < seed_size; i++) {
+                if (i > 0 && i % 3 == 0)
+                        fputc('-', f);
+                fprintf(f, "%02x", ((uint8_t*) seed)[i]);
+        }
+
+        fprintf(f, "/%"PRIx64"-%"PRIx64, start, interval);
+
+        if (full) {
+                fprintf(f, "?machine=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(machine));
+                if (hn)
+                        fprintf(f, ";hostname=%s", hn);
+        }
+
+        return memstream_finalize(&m, ret_url, NULL);
+}
+
+int action_setup_keys(void) {
+        size_t mpk_size, seed_size, state_size;
+        _cleanup_(unlink_and_freep) char *k = NULL;
+        _cleanup_free_ char *p = NULL;
+        uint8_t *mpk, *seed, *state;
+        _cleanup_close_ int fd = -EBADF;
+        sd_id128_t machine, boot;
+        struct stat st;
+        uint64_t n;
+        int r;
+
+        assert(arg_action == ACTION_SETUP_KEYS);
+
+        r = stat("/var/log/journal", &st);
+        if (r < 0 && !IN_SET(errno, ENOENT, ENOTDIR))
+                return log_error_errno(errno, "stat(\"%s\") failed: %m", "/var/log/journal");
+
+        if (r < 0 || !S_ISDIR(st.st_mode)) {
+                log_error("%s is not a directory, must be using persistent logging for FSS.",
+                          "/var/log/journal");
+                return r < 0 ? -errno : -ENOTDIR;
+        }
+
+        r = sd_id128_get_machine(&machine);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get machine ID: %m");
+
+        r = sd_id128_get_boot(&boot);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get boot ID: %m");
+
+        if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss",
+                     SD_ID128_FORMAT_VAL(machine)) < 0)
+                return log_oom();
+
+        if (arg_force) {
+                r = unlink(p);
+                if (r < 0 && errno != ENOENT)
+                        return log_error_errno(errno, "unlink(\"%s\") failed: %m", p);
+        } else if (access(p, F_OK) >= 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
+                                       "Sealing key file %s exists already. Use --force to recreate.", p);
+
+        if (asprintf(&k, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss.tmp.XXXXXX",
+                     SD_ID128_FORMAT_VAL(machine)) < 0)
+                return log_oom();
+
+        mpk_size = FSPRG_mskinbytes(FSPRG_RECOMMENDED_SECPAR);
+        mpk = alloca_safe(mpk_size);
+
+        seed_size = FSPRG_RECOMMENDED_SEEDLEN;
+        seed = alloca_safe(seed_size);
+
+        state_size = FSPRG_stateinbytes(FSPRG_RECOMMENDED_SECPAR);
+        state = alloca_safe(state_size);
+
+        log_info("Generating seed...");
+        r = crypto_random_bytes(seed, seed_size);
+        if (r < 0)
+                return log_error_errno(r, "Failed to acquire random seed: %m");
+
+        log_info("Generating key pair...");
+        FSPRG_GenMK(NULL, mpk, seed, seed_size, FSPRG_RECOMMENDED_SECPAR);
+
+        log_info("Generating sealing key...");
+        FSPRG_GenState0(state, mpk, seed, seed_size);
+
+        assert(arg_interval > 0);
+
+        n = now(CLOCK_REALTIME);
+        n /= arg_interval;
+
+        safe_close(fd);
+        fd = mkostemp_safe(k);
+        if (fd < 0)
+                return log_error_errno(fd, "Failed to open %s: %m", k);
+
+        r = chattr_secret(fd, CHATTR_WARN_UNSUPPORTED_FLAGS);
+        if (r < 0)
+                log_full_errno(ERRNO_IS_NOT_SUPPORTED(r) ? LOG_DEBUG : LOG_WARNING,
+                               r, "Failed to set file attributes on '%s', ignoring: %m", k);
+
+        struct FSSHeader h = {
+                .signature = { 'K', 'S', 'H', 'H', 'R', 'H', 'L', 'P' },
+                .machine_id = machine,
+                .boot_id = boot,
+                .header_size = htole64(sizeof(h)),
+                .start_usec = htole64(n * arg_interval),
+                .interval_usec = htole64(arg_interval),
+                .fsprg_secpar = htole16(FSPRG_RECOMMENDED_SECPAR),
+                .fsprg_state_size = htole64(state_size),
+        };
+
+        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);
+        if (r < 0)
+                return log_error_errno(r, "Failed to write state: %m");
+
+        if (rename(k, p) < 0)
+                return log_error_errno(errno, "Failed to link file: %m");
+
+        k = mfree(k);
+
+        _cleanup_free_ char *hn = NULL, *key = NULL;
+
+        r = format_journal_url(seed, seed_size, n, arg_interval, hn, machine, false, &key);
+        if (r < 0)
+                return r;
+
+        if (on_tty()) {
+                hn = gethostname_malloc();
+                if (hn)
+                        hostname_cleanup(hn);
+
+                fprintf(stderr,
+                        "\nNew keys have been generated for host %s%s" SD_ID128_FORMAT_STR ".\n"
+                        "\n"
+                        "The %ssecret sealing key%s has been written to the following local file.\n"
+                        "This key file is automatically updated when the sealing key is advanced.\n"
+                        "It should not be used on multiple hosts.\n"
+                        "\n"
+                        "\t%s\n"
+                        "\n"
+                        "The sealing key is automatically changed every %s.\n"
+                        "\n"
+                        "Please write down the following %ssecret verification key%s. It should be stored\n"
+                        "in a safe location and should not be saved locally on disk.\n"
+                        "\n\t%s",
+                        strempty(hn), hn ? "/" : "",
+                        SD_ID128_FORMAT_VAL(machine),
+                        ansi_highlight(), ansi_normal(),
+                        p,
+                        FORMAT_TIMESPAN(arg_interval, 0),
+                        ansi_highlight(), ansi_normal(),
+                        ansi_highlight_red());
+                fflush(stderr);
+        }
+
+        puts(key);
+
+        if (on_tty()) {
+                fprintf(stderr, "%s", ansi_normal());
+#if HAVE_QRENCODE
+                _cleanup_free_ char *url = NULL;
+                r = format_journal_url(seed, seed_size, n, arg_interval, hn, machine, true, &url);
+                if (r < 0)
+                        return r;
+
+                (void) print_qrcode(stderr,
+                                    "To transfer the verification key to your phone scan the QR code below",
+                                    url);
+#endif
+        }
+
+        return 0;
+}
diff --git a/src/journal/journalctl-authenticate.h b/src/journal/journalctl-authenticate.h
new file mode 100644 (file)
index 0000000..2a8ebd5
--- /dev/null
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#if HAVE_GCRYPT
+
+int action_setup_keys(void);
+
+#else
+
+#include "log.h"
+
+static inline int action_setup_keys(void) {
+        return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Forward-secure sealing not available.");
+}
+
+#endif
diff --git a/src/journal/journalctl-catalog.c b/src/journal/journalctl-catalog.c
new file mode 100644 (file)
index 0000000..116e152
--- /dev/null
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "catalog.h"
+#include "journalctl.h"
+#include "journalctl-catalog.h"
+#include "path-util.h"
+
+int action_update_catalog(void) {
+        _cleanup_free_ char *database = NULL;
+        const char *e;
+        int r;
+
+        assert(arg_action == ACTION_UPDATE_CATALOG);
+
+        database = path_join(arg_root, secure_getenv("SYSTEMD_CATALOG") ?: CATALOG_DATABASE);
+        if (!database)
+                return log_oom();
+
+        e = secure_getenv("SYSTEMD_CATALOG_SOURCES");
+        r = catalog_update(database,
+                           arg_root,
+                           e ? STRV_MAKE_CONST(e) : catalog_file_dirs);
+        if (r < 0)
+                return log_error_errno(r, "Failed to update catalog: %m");
+
+        return 0;
+}
+
+int action_list_catalog(char **items) {
+        _cleanup_free_ char *database = NULL;
+        int r;
+
+        assert(IN_SET(arg_action, ACTION_LIST_CATALOG, ACTION_DUMP_CATALOG));
+
+        database = path_join(arg_root, secure_getenv("SYSTEMD_CATALOG") ?: CATALOG_DATABASE);
+        if (!database)
+                return log_oom();
+
+        bool oneline = arg_action == ACTION_LIST_CATALOG;
+
+        pager_open(arg_pager_flags);
+
+        if (items)
+                r = catalog_list_items(stdout, database, oneline, items);
+        else
+                r = catalog_list(stdout, database, oneline);
+        if (r < 0)
+                return log_error_errno(r, "Failed to list catalog: %m");
+
+        return 0;
+}
diff --git a/src/journal/journalctl-catalog.h b/src/journal/journalctl-catalog.h
new file mode 100644 (file)
index 0000000..d52bdd4
--- /dev/null
@@ -0,0 +1,5 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+int action_update_catalog(void);
+int action_list_catalog(char **items);
diff --git a/src/journal/journalctl-filter.c b/src/journal/journalctl-filter.c
new file mode 100644 (file)
index 0000000..9c0e422
--- /dev/null
@@ -0,0 +1,543 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-device.h"
+
+#include "chase.h"
+#include "devnum-util.h"
+#include "fileio.h"
+#include "glob-util.h"
+#include "journal-internal.h"
+#include "journalctl.h"
+#include "journalctl-filter.h"
+#include "logs-show.h"
+#include "missing_sched.h"
+#include "nulstr-util.h"
+#include "path-util.h"
+#include "unit-name.h"
+
+static int add_boot(sd_journal *j) {
+        int r;
+
+        assert(j);
+
+        if (!arg_boot)
+                return 0;
+
+        /* Take a shortcut and use the current boot_id, which we can do very quickly.
+         * We can do this only when we logs are coming from the current machine,
+         * so take the slow path if log location is specified. */
+        if (arg_boot_offset == 0 && sd_id128_is_null(arg_boot_id) &&
+            !arg_directory && !arg_file && !arg_root)
+                return add_match_this_boot(j, arg_machine);
+
+        if (sd_id128_is_null(arg_boot_id)) {
+                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);
+                if (r == 0)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENODATA),
+                                               "No journal boot entry found from the specified boot offset (%+i).",
+                                               arg_boot_offset);
+        } else {
+                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));
+                if (r == 0)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENODATA),
+                                               "No journal boot entry found from the specified boot ID (%s).",
+                                               SD_ID128_TO_STRING(arg_boot_id));
+        }
+
+        r = add_match_boot_id(j, arg_boot_id);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add match: %m");
+
+        r = sd_journal_add_conjunction(j);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add conjunction: %m");
+
+        return 0;
+}
+
+static int add_dmesg(sd_journal *j) {
+        int r;
+
+        assert(j);
+
+        if (!arg_dmesg)
+                return 0;
+
+        r = sd_journal_add_match(j, "_TRANSPORT=kernel",
+                                 STRLEN("_TRANSPORT=kernel"));
+        if (r < 0)
+                return log_error_errno(r, "Failed to add match: %m");
+
+        r = sd_journal_add_conjunction(j);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add conjunction: %m");
+
+        return 0;
+}
+
+static int get_possible_units(
+                sd_journal *j,
+                const char *fields,
+                char **patterns,
+                Set **units) {
+
+        _cleanup_set_free_free_ Set *found = NULL;
+        int r;
+
+        found = set_new(&string_hash_ops);
+        if (!found)
+                return -ENOMEM;
+
+        NULSTR_FOREACH(field, fields) {
+                const void *data;
+                size_t size;
+
+                r = sd_journal_query_unique(j, field);
+                if (r < 0)
+                        return r;
+
+                SD_JOURNAL_FOREACH_UNIQUE(j, data, size) {
+                        char *eq;
+                        size_t prefix;
+                        _cleanup_free_ char *u = NULL;
+
+                        eq = memchr(data, '=', size);
+                        if (eq)
+                                prefix = eq - (char*) data + 1;
+                        else
+                                prefix = 0;
+
+                        u = strndup((char*) data + prefix, size - prefix);
+                        if (!u)
+                                return -ENOMEM;
+
+                        STRV_FOREACH(pattern, patterns)
+                                if (fnmatch(*pattern, u, FNM_NOESCAPE) == 0) {
+                                        log_debug("Matched %s with pattern %s=%s", u, field, *pattern);
+
+                                        r = set_consume(found, u);
+                                        u = NULL;
+                                        if (r < 0 && r != -EEXIST)
+                                                return r;
+
+                                        break;
+                                }
+                }
+        }
+
+        *units = TAKE_PTR(found);
+
+        return 0;
+}
+
+/* This list is supposed to return the superset of unit names
+ * possibly matched by rules added with add_matches_for_unit... */
+#define SYSTEM_UNITS                 \
+        "_SYSTEMD_UNIT\0"            \
+        "COREDUMP_UNIT\0"            \
+        "UNIT\0"                     \
+        "OBJECT_SYSTEMD_UNIT\0"      \
+        "_SYSTEMD_SLICE\0"
+
+/* ... and add_matches_for_user_unit */
+#define USER_UNITS                   \
+        "_SYSTEMD_USER_UNIT\0"       \
+        "USER_UNIT\0"                \
+        "COREDUMP_USER_UNIT\0"       \
+        "OBJECT_SYSTEMD_USER_UNIT\0" \
+        "_SYSTEMD_USER_SLICE\0"
+
+static int add_units(sd_journal *j) {
+        _cleanup_strv_free_ char **patterns = NULL;
+        int r, count = 0;
+
+        assert(j);
+
+        STRV_FOREACH(i, arg_system_units) {
+                _cleanup_free_ char *u = NULL;
+
+                r = unit_name_mangle(*i, UNIT_NAME_MANGLE_GLOB | (arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN), &u);
+                if (r < 0)
+                        return r;
+
+                if (string_is_glob(u)) {
+                        r = strv_push(&patterns, u);
+                        if (r < 0)
+                                return r;
+                        u = NULL;
+                } else {
+                        r = add_matches_for_unit(j, u);
+                        if (r < 0)
+                                return r;
+                        r = sd_journal_add_disjunction(j);
+                        if (r < 0)
+                                return r;
+                        count++;
+                }
+        }
+
+        if (!strv_isempty(patterns)) {
+                _cleanup_set_free_free_ Set *units = NULL;
+                char *u;
+
+                r = get_possible_units(j, SYSTEM_UNITS, patterns, &units);
+                if (r < 0)
+                        return r;
+
+                SET_FOREACH(u, units) {
+                        r = add_matches_for_unit(j, u);
+                        if (r < 0)
+                                return r;
+                        r = sd_journal_add_disjunction(j);
+                        if (r < 0)
+                                return r;
+                        count++;
+                }
+        }
+
+        patterns = strv_free(patterns);
+
+        STRV_FOREACH(i, arg_user_units) {
+                _cleanup_free_ char *u = NULL;
+
+                r = unit_name_mangle(*i, UNIT_NAME_MANGLE_GLOB | (arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN), &u);
+                if (r < 0)
+                        return r;
+
+                if (string_is_glob(u)) {
+                        r = strv_push(&patterns, u);
+                        if (r < 0)
+                                return r;
+                        u = NULL;
+                } else {
+                        r = add_matches_for_user_unit(j, u, getuid());
+                        if (r < 0)
+                                return r;
+                        r = sd_journal_add_disjunction(j);
+                        if (r < 0)
+                                return r;
+                        count++;
+                }
+        }
+
+        if (!strv_isempty(patterns)) {
+                _cleanup_set_free_free_ Set *units = NULL;
+                char *u;
+
+                r = get_possible_units(j, USER_UNITS, patterns, &units);
+                if (r < 0)
+                        return r;
+
+                SET_FOREACH(u, units) {
+                        r = add_matches_for_user_unit(j, u, getuid());
+                        if (r < 0)
+                                return r;
+                        r = sd_journal_add_disjunction(j);
+                        if (r < 0)
+                                return r;
+                        count++;
+                }
+        }
+
+        /* Complain if the user request matches but nothing whatsoever was
+         * found, since otherwise everything would be matched. */
+        if (!(strv_isempty(arg_system_units) && strv_isempty(arg_user_units)) && count == 0)
+                return -ENODATA;
+
+        r = sd_journal_add_conjunction(j);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int add_syslog_identifier(sd_journal *j) {
+        int r;
+
+        assert(j);
+
+        STRV_FOREACH(i, arg_syslog_identifier) {
+                _cleanup_free_ char *u = NULL;
+
+                u = strjoin("SYSLOG_IDENTIFIER=", *i);
+                if (!u)
+                        return -ENOMEM;
+                r = sd_journal_add_match(j, u, 0);
+                if (r < 0)
+                        return r;
+                r = sd_journal_add_disjunction(j);
+                if (r < 0)
+                        return r;
+        }
+
+        r = sd_journal_add_conjunction(j);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int add_exclude_identifier(sd_journal *j) {
+        _cleanup_set_free_ Set *excludes = NULL;
+        int r;
+
+        assert(j);
+
+        r = set_put_strdupv(&excludes, arg_exclude_identifier);
+        if (r < 0)
+                return r;
+
+        return set_free_and_replace(j->exclude_syslog_identifiers, excludes);
+}
+
+static int add_priorities(sd_journal *j) {
+        char match[] = "PRIORITY=0";
+        int i, r;
+
+        assert(j);
+
+        if (arg_priorities == 0xFF)
+                return 0;
+
+        for (i = LOG_EMERG; i <= LOG_DEBUG; i++)
+                if (arg_priorities & (1 << i)) {
+                        match[sizeof(match)-2] = '0' + i;
+
+                        r = sd_journal_add_match(j, match, strlen(match));
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to add match: %m");
+                }
+
+        r = sd_journal_add_conjunction(j);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add conjunction: %m");
+
+        return 0;
+}
+
+static int add_facilities(sd_journal *j) {
+        void *p;
+        int r;
+
+        SET_FOREACH(p, arg_facilities) {
+                char match[STRLEN("SYSLOG_FACILITY=") + DECIMAL_STR_MAX(int)];
+
+                xsprintf(match, "SYSLOG_FACILITY=%d", PTR_TO_INT(p));
+
+                r = sd_journal_add_match(j, match, strlen(match));
+                if (r < 0)
+                        return log_error_errno(r, "Failed to add match: %m");
+        }
+
+        return 0;
+}
+
+static int add_matches_for_device(sd_journal *j, const char *devpath) {
+        _cleanup_(sd_device_unrefp) sd_device *device = NULL;
+        sd_device *d = NULL;
+        struct stat st;
+        int r;
+
+        assert(j);
+        assert(devpath);
+
+        if (!path_startswith(devpath, "/dev/"))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "Devpath does not start with /dev/");
+
+        if (stat(devpath, &st) < 0)
+                return log_error_errno(errno, "Couldn't stat file: %m");
+
+        r = sd_device_new_from_stat_rdev(&device, &st);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get device from devnum " DEVNUM_FORMAT_STR ": %m", DEVNUM_FORMAT_VAL(st.st_rdev));
+
+        for (d = device; d; ) {
+                _cleanup_free_ char *match = NULL;
+                const char *subsys, *sysname, *devnode;
+                sd_device *parent;
+
+                r = sd_device_get_subsystem(d, &subsys);
+                if (r < 0)
+                        goto get_parent;
+
+                r = sd_device_get_sysname(d, &sysname);
+                if (r < 0)
+                        goto get_parent;
+
+                match = strjoin("_KERNEL_DEVICE=+", subsys, ":", sysname);
+                if (!match)
+                        return log_oom();
+
+                r = sd_journal_add_match(j, match, 0);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to add match: %m");
+
+                if (sd_device_get_devname(d, &devnode) >= 0) {
+                        _cleanup_free_ char *match1 = NULL;
+
+                        r = stat(devnode, &st);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to stat() device node \"%s\": %m", devnode);
+
+                        r = asprintf(&match1, "_KERNEL_DEVICE=%c" DEVNUM_FORMAT_STR, S_ISBLK(st.st_mode) ? 'b' : 'c', DEVNUM_FORMAT_VAL(st.st_rdev));
+                        if (r < 0)
+                                return log_oom();
+
+                        r = sd_journal_add_match(j, match1, 0);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to add match: %m");
+                }
+
+get_parent:
+                if (sd_device_get_parent(d, &parent) < 0)
+                        break;
+
+                d = parent;
+        }
+
+        r = add_match_this_boot(j, arg_machine);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add match for the current boot: %m");
+
+        return 0;
+}
+
+static int add_matches(sd_journal *j, char **args) {
+        bool have_term = false;
+
+        assert(j);
+
+        STRV_FOREACH(i, args) {
+                int r;
+
+                if (streq(*i, "+")) {
+                        if (!have_term)
+                                break;
+                        r = sd_journal_add_disjunction(j);
+                        have_term = false;
+
+                } else if (path_is_absolute(*i)) {
+                        _cleanup_free_ char *p = NULL, *t = NULL, *t2 = NULL, *interpreter = NULL;
+                        struct stat st;
+
+                        r = chase(*i, NULL, CHASE_TRAIL_SLASH, &p, NULL);
+                        if (r < 0)
+                                return log_error_errno(r, "Couldn't canonicalize path: %m");
+
+                        if (lstat(p, &st) < 0)
+                                return log_error_errno(errno, "Couldn't stat file: %m");
+
+                        if (S_ISREG(st.st_mode) && (0111 & st.st_mode)) {
+                                if (executable_is_script(p, &interpreter) > 0) {
+                                        _cleanup_free_ char *comm = NULL;
+
+                                        r = path_extract_filename(p, &comm);
+                                        if (r < 0)
+                                                return log_error_errno(r, "Failed to extract filename of '%s': %m", p);
+
+                                        t = strjoin("_COMM=", strshorten(comm, TASK_COMM_LEN-1));
+                                        if (!t)
+                                                return log_oom();
+
+                                        /* Append _EXE only if the interpreter is not a link.
+                                           Otherwise, it might be outdated often. */
+                                        if (lstat(interpreter, &st) == 0 && !S_ISLNK(st.st_mode)) {
+                                                t2 = strjoin("_EXE=", interpreter);
+                                                if (!t2)
+                                                        return log_oom();
+                                        }
+                                } else {
+                                        t = strjoin("_EXE=", p);
+                                        if (!t)
+                                                return log_oom();
+                                }
+
+                                r = sd_journal_add_match(j, t, 0);
+
+                                if (r >=0 && t2)
+                                        r = sd_journal_add_match(j, t2, 0);
+
+                        } else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) {
+                                r = add_matches_for_device(j, p);
+                                if (r < 0)
+                                        return r;
+                        } else
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                       "File is neither a device node, nor regular file, nor executable: %s",
+                                                       *i);
+
+                        have_term = true;
+                } else {
+                        r = sd_journal_add_match(j, *i, 0);
+                        have_term = true;
+                }
+
+                if (r < 0)
+                        return log_error_errno(r, "Failed to add match '%s': %m", *i);
+        }
+
+        if (!strv_isempty(args) && !have_term)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "\"+\" can only be used between terms");
+
+        return 0;
+}
+
+int add_filters(sd_journal *j, char **matches) {
+        int r;
+
+        assert(j);
+
+        /* add_boot() must be called first!
+         * It may need to seek the journal to find parent boot IDs. */
+        r = add_boot(j);
+        if (r < 0)
+                return r;
+
+        r = add_dmesg(j);
+        if (r < 0)
+                return r;
+
+        r = add_units(j);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add filter for units: %m");
+
+        r = add_syslog_identifier(j);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add filter for syslog identifiers: %m");
+
+        r = add_exclude_identifier(j);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add exclude filter for syslog identifiers: %m");
+
+        r = add_priorities(j);
+        if (r < 0)
+                return r;
+
+        r = add_facilities(j);
+        if (r < 0)
+                return r;
+
+        r = add_matches(j, matches);
+        if (r < 0)
+                return r;
+
+        if (DEBUG_LOGGING) {
+                _cleanup_free_ char *filter = NULL;
+
+                filter = journal_make_match_string(j);
+                if (!filter)
+                        return log_oom();
+
+                log_debug("Journal filter: %s", filter);
+        }
+
+        return 0;
+}
diff --git a/src/journal/journalctl-filter.h b/src/journal/journalctl-filter.h
new file mode 100644 (file)
index 0000000..c752c0c
--- /dev/null
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-journal.h"
+
+int add_filters(sd_journal *j, char **matches);
diff --git a/src/journal/journalctl-misc.c b/src/journal/journalctl-misc.c
new file mode 100644 (file)
index 0000000..c7a49ee
--- /dev/null
@@ -0,0 +1,269 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "dirent-util.h"
+#include "fd-util.h"
+#include "format-table.h"
+#include "format-util.h"
+#include "journal-internal.h"
+#include "journal-verify.h"
+#include "journalctl.h"
+#include "journalctl-misc.h"
+#include "journalctl-util.h"
+#include "logs-show.h"
+#include "syslog-util.h"
+
+int action_print_header(void) {
+        _cleanup_(sd_journal_closep) sd_journal *j = NULL;
+        int r;
+
+        assert(arg_action == ACTION_PRINT_HEADER);
+
+        r = acquire_journal(&j);
+        if (r < 0)
+                return r;
+
+        journal_print_header(j);
+        return 0;
+}
+
+int action_verify(void) {
+        _cleanup_(sd_journal_closep) sd_journal *j = NULL;
+        int r;
+
+        assert(arg_action == ACTION_VERIFY);
+
+        r = acquire_journal(&j);
+        if (r < 0)
+                return r;
+
+        log_show_color(true);
+
+        JournalFile *f;
+        ORDERED_HASHMAP_FOREACH(f, j->files) {
+                int k;
+                usec_t first = 0, validated = 0, last = 0;
+
+#if HAVE_GCRYPT
+                if (!arg_verify_key && JOURNAL_HEADER_SEALED(f->header))
+                        log_notice("Journal file %s has sealing enabled but verification key has not been passed using --verify-key=.", f->path);
+#endif
+
+                k = journal_file_verify(f, arg_verify_key, &first, &validated, &last, /* show_progress = */ !arg_quiet);
+                if (k == -EINVAL)
+                        /* If the key was invalid give up right-away. */
+                        return k;
+                if (k < 0)
+                        r = log_warning_errno(k, "FAIL: %s (%m)", f->path);
+                else {
+                        char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX];
+                        log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, "PASS: %s", f->path);
+
+                        if (arg_verify_key && JOURNAL_HEADER_SEALED(f->header)) {
+                                if (validated > 0) {
+                                        log_full(arg_quiet ? LOG_DEBUG : LOG_INFO,
+                                                 "=> Validated from %s to %s, final %s entries not sealed.",
+                                                 format_timestamp_maybe_utc(a, sizeof(a), first),
+                                                 format_timestamp_maybe_utc(b, sizeof(b), validated),
+                                                 FORMAT_TIMESPAN(last > validated ? last - validated : 0, 0));
+                                } else if (last > 0)
+                                        log_full(arg_quiet ? LOG_DEBUG : LOG_INFO,
+                                                 "=> No sealing yet, %s of entries not sealed.",
+                                                 FORMAT_TIMESPAN(last - first, 0));
+                                else
+                                        log_full(arg_quiet ? LOG_DEBUG : LOG_INFO,
+                                                 "=> No sealing yet, no entries in file.");
+                        }
+                }
+        }
+
+        return r;
+}
+
+int action_disk_usage(void) {
+        _cleanup_(sd_journal_closep) sd_journal *j = NULL;
+        uint64_t bytes = 0;
+        int r;
+
+        assert(arg_action == ACTION_DISK_USAGE);
+
+        r = acquire_journal(&j);
+        if (r < 0)
+                return r;
+
+        r = sd_journal_get_usage(j, &bytes);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get disk usage: %m");
+
+        printf("Archived and active journals take up %s in the file system.\n", FORMAT_BYTES(bytes));
+        return 0;
+}
+
+int action_list_boots(void) {
+        _cleanup_(sd_journal_closep) sd_journal *j = NULL;
+        _cleanup_(table_unrefp) Table *table = NULL;
+        _cleanup_free_ BootId *boots = NULL;
+        size_t n_boots;
+        int r;
+
+        assert(arg_action == ACTION_LIST_BOOTS);
+
+        r = acquire_journal(&j);
+        if (r < 0)
+                return r;
+
+        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 0;
+
+        table = table_new("idx", "boot id", "first entry", "last entry");
+        if (!table)
+                return log_oom();
+
+        if (arg_full)
+                table_set_width(table, 0);
+
+        r = table_set_json_field_name(table, 0, "index");
+        if (r < 0)
+                return log_error_errno(r, "Failed to set JSON field name of column 0: %m");
+
+        (void) table_set_sort(table, (size_t) 0);
+        (void) table_set_reverse(table, 0, arg_reverse);
+
+        FOREACH_ARRAY(i, boots, n_boots) {
+                r = table_add_many(table,
+                                   TABLE_INT, (int)(i - boots) - (int) n_boots + 1,
+                                   TABLE_SET_ALIGN_PERCENT, 100,
+                                   TABLE_ID128, i->id,
+                                   TABLE_TIMESTAMP, i->first_usec,
+                                   TABLE_TIMESTAMP, i->last_usec);
+                if (r < 0)
+                        return table_log_add_error(r);
+        }
+
+        r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, !arg_quiet);
+        if (r < 0)
+                return table_log_print_error(r);
+
+        return 0;
+}
+
+int action_list_fields(void) {
+        _cleanup_(sd_journal_closep) sd_journal *j = NULL;
+        int r, n_shown = 0;
+
+        assert(arg_action == ACTION_LIST_FIELDS);
+        assert(arg_field);
+
+        r = acquire_journal(&j);
+        if (r < 0)
+                return r;
+
+        if (!journal_boot_has_effect(j))
+                return 0;
+
+        r = sd_journal_set_data_threshold(j, 0);
+        if (r < 0)
+                return log_error_errno(r, "Failed to unset data size threshold: %m");
+
+        r = sd_journal_query_unique(j, arg_field);
+        if (r < 0)
+                return log_error_errno(r, "Failed to query unique data objects: %m");
+
+        const void *data;
+        size_t size;
+        SD_JOURNAL_FOREACH_UNIQUE(j, data, size) {
+                const void *eq;
+
+                if (arg_lines >= 0 && n_shown >= arg_lines)
+                        break;
+
+                eq = memchr(data, '=', size);
+                if (eq)
+                        printf("%.*s\n", (int) (size - ((const uint8_t*) eq - (const uint8_t*) data + 1)), (const char*) eq + 1);
+                else
+                        printf("%.*s\n", (int) size, (const char*) data);
+
+                n_shown++;
+        }
+
+        return 0;
+}
+
+int action_list_field_names(void) {
+        _cleanup_(sd_journal_closep) sd_journal *j = NULL;
+        int r;
+
+        assert(arg_action == ACTION_LIST_FIELD_NAMES);
+
+        r = acquire_journal(&j);
+        if (r < 0)
+                return r;
+
+        const char *field;
+        SD_JOURNAL_FOREACH_FIELD(j, field)
+                printf("%s\n", field);
+
+        return 0;
+}
+
+int action_list_namespaces(void) {
+        _cleanup_(table_unrefp) Table *table = NULL;
+        sd_id128_t machine;
+        char machine_id[SD_ID128_STRING_MAX];
+        int r;
+
+        assert(arg_action == ACTION_LIST_NAMESPACES);
+
+        r = sd_id128_get_machine(&machine);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get machine ID: %m");
+
+        sd_id128_to_string(machine, machine_id);
+
+        table = table_new("namespace");
+        if (!table)
+                return log_oom();
+
+        (void) table_set_sort(table, (size_t) 0);
+
+        FOREACH_STRING(dir, "/var/log/journal", "/run/log/journal") {
+                _cleanup_free_ char *path = NULL;
+                _cleanup_closedir_ DIR *dirp = NULL;
+
+                path = path_join(arg_root, dir);
+                if (!path)
+                        return log_oom();
+
+                dirp = opendir(path);
+                if (!dirp) {
+                        log_debug_errno(errno, "Failed to open directory %s, ignoring: %m", path);
+                        continue;
+                }
+
+                FOREACH_DIRENT(de, dirp, return log_error_errno(errno, "Failed to iterate through %s: %m", path)) {
+                        char *dot;
+
+                        if (!startswith(de->d_name, machine_id))
+                                continue;
+
+                        dot = strchr(de->d_name, '.');
+                        if (!dot)
+                                continue;
+
+                        if (!log_namespace_name_valid(dot + 1))
+                                continue;
+
+                        r = table_add_cell(table, NULL, TABLE_STRING, dot + 1);
+                        if (r < 0)
+                                return table_log_add_error(r);
+                }
+        }
+
+        r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, !arg_quiet);
+        if (r < 0)
+                return table_log_print_error(r);
+
+        return 0;
+}
diff --git a/src/journal/journalctl-misc.h b/src/journal/journalctl-misc.h
new file mode 100644 (file)
index 0000000..70f851b
--- /dev/null
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdbool.h>
+
+int action_print_header(void);
+int action_verify(void);
+int action_disk_usage(void);
+int action_list_boots(void);
+int action_list_fields(void);
+int action_list_field_names(void);
+int action_list_namespaces(void);
diff --git a/src/journal/journalctl-show.c b/src/journal/journalctl-show.c
new file mode 100644 (file)
index 0000000..54c68c2
--- /dev/null
@@ -0,0 +1,466 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#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 1,024 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 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) {
+        bool need_seek = false, since_seeked = false, after_cursor = false;
+        _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;
+
+        assert(arg_action == ACTION_SHOW);
+
+        (void) signal(SIGWINCH, columns_lines_cache_reset);
+
+        r = acquire_journal(&j);
+        if (r < 0)
+                return r;
+
+        if (!journal_boot_has_effect(j))
+                return arg_compiled_pattern ? -ENOENT : 0;
+
+        r = add_filters(j, matches);
+        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(j);
+                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_cursor || arg_after_cursor || arg_cursor_file) {
+                cursor = arg_cursor ?: arg_after_cursor;
+
+                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;
+                        }
+                } 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");
+
+                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) {
+                        /* We couldn't find the next entry after the cursor. */
+                        if (arg_follow)
+                                need_seek = true;
+                        else
+                                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");
+                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)
+                need_seek = true;
+
+        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(j, &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));
+                }
+        }
+
+        Context c = {
+                .journal = j,
+                .has_cursor = cursor,
+                .need_seek = need_seek,
+                .since_seeked = since_seeked,
+        };
+
+        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(j);
+                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(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
+                 * 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;
+}
diff --git a/src/journal/journalctl-show.h b/src/journal/journalctl-show.h
new file mode 100644 (file)
index 0000000..83f3bdd
--- /dev/null
@@ -0,0 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+int action_show(char **matches);
diff --git a/src/journal/journalctl-util.c b/src/journal/journalctl-util.c
new file mode 100644 (file)
index 0000000..f8f5ac6
--- /dev/null
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "journal-util.h"
+#include "journalctl.h"
+#include "journalctl-util.h"
+#include "rlimit-util.h"
+#include "sigbus.h"
+#include "terminal-util.h"
+
+char* format_timestamp_maybe_utc(char *buf, size_t l, usec_t t) {
+        assert(buf);
+
+        if (arg_utc)
+                return format_timestamp_style(buf, l, t, TIMESTAMP_UTC);
+
+        return format_timestamp(buf, l, t);
+}
+
+int acquire_journal(sd_journal **ret) {
+        _cleanup_(sd_journal_closep) sd_journal *j = NULL;
+        int r;
+
+        assert(ret);
+
+        /* Increase max number of open files if we can, we might needs this when browsing journal files, which might be
+         * split up into many files. */
+        (void) rlimit_nofile_bump(HIGH_RLIMIT_NOFILE);
+
+        sigbus_install();
+
+        if (arg_directory)
+                r = sd_journal_open_directory(&j, arg_directory, arg_journal_type | arg_journal_additional_open_flags);
+        else if (arg_root)
+                r = sd_journal_open_directory(&j, arg_root, arg_journal_type | arg_journal_additional_open_flags | SD_JOURNAL_OS_ROOT);
+        else if (arg_file_stdin)
+                r = sd_journal_open_files_fd(&j, (int[]) { STDIN_FILENO }, 1, arg_journal_additional_open_flags);
+        else if (arg_file)
+                r = sd_journal_open_files(&j, (const char**) arg_file, arg_journal_additional_open_flags);
+        else if (arg_machine)
+                r = journal_open_machine(&j, arg_machine, arg_journal_additional_open_flags);
+        else
+                r = sd_journal_open_namespace(
+                                &j,
+                                arg_namespace,
+                                (arg_merge ? 0 : SD_JOURNAL_LOCAL_ONLY) |
+                                arg_namespace_flags | arg_journal_type | arg_journal_additional_open_flags);
+        if (r < 0)
+                return log_error_errno(r, "Failed to open %s: %m", arg_directory ?: arg_file ? "files" : "journal");
+
+        r = journal_access_check_and_warn(j, arg_quiet,
+                                          !(arg_journal_type == SD_JOURNAL_CURRENT_USER || arg_user_units));
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(j);
+        return 0;
+}
+
+bool journal_boot_has_effect(sd_journal *j) {
+        assert(j);
+
+        if (arg_boot_offset != 0 &&
+            sd_journal_has_runtime_files(j) > 0 &&
+            sd_journal_has_persistent_files(j) == 0) {
+                log_info("Specifying boot ID or boot offset has no effect, no persistent journal was found.");
+                return false;
+        }
+
+        return true;
+}
diff --git a/src/journal/journalctl-util.h b/src/journal/journalctl-util.h
new file mode 100644 (file)
index 0000000..38f634f
--- /dev/null
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-journal.h"
+
+#include "time-util.h"
+
+char* format_timestamp_maybe_utc(char *buf, size_t l, usec_t t);
+int acquire_journal(sd_journal **ret);
+bool journal_boot_has_effect(sd_journal *j);
diff --git a/src/journal/journalctl-varlink.c b/src/journal/journalctl-varlink.c
new file mode 100644 (file)
index 0000000..89aed05
--- /dev/null
@@ -0,0 +1,142 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "errno-util.h"
+#include "journal-internal.h"
+#include "journal-vacuum.h"
+#include "journalctl.h"
+#include "journalctl-util.h"
+#include "journalctl-varlink.h"
+#include "varlink.h"
+
+static int varlink_connect_journal(Varlink **ret) {
+        _cleanup_(varlink_flush_close_unrefp) Varlink *vl = NULL;
+        const char *address;
+        int r;
+
+        assert(ret);
+
+        address = arg_namespace ?
+                  strjoina("/run/systemd/journal.", arg_namespace, "/io.systemd.journal") :
+                  "/run/systemd/journal/io.systemd.journal";
+
+        r = varlink_connect_address(&vl, address);
+        if (r < 0)
+                return r;
+
+        (void) varlink_set_description(vl, "journal");
+        (void) varlink_set_relative_timeout(vl, USEC_INFINITY);
+
+        *ret = TAKE_PTR(vl);
+        return 0;
+}
+
+int action_flush_to_var(void) {
+        _cleanup_(varlink_flush_close_unrefp) Varlink *link = NULL;
+        int r;
+
+        assert(arg_action == ACTION_FLUSH);
+
+        if (arg_machine || arg_namespace)
+                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                                       "--flush is not supported in conjunction with %s.",
+                                       arg_machine ? "--machine=" : "--namespace=");
+
+        if (access("/run/systemd/journal/flushed", F_OK) >= 0)
+                return 0; /* Already flushed, no need to contact journald */
+        if (errno != ENOENT)
+                return log_error_errno(errno, "Unable to check for existence of /run/systemd/journal/flushed: %m");
+
+        r = varlink_connect_journal(&link);
+        if (r < 0)
+                return log_error_errno(r, "Failed to connect to Varlink socket: %m");
+
+        return varlink_call_and_log(link, "io.systemd.Journal.FlushToVar", /* parameters= */ NULL, /* ret_parameters= */ NULL);
+}
+
+int action_relinquish_var(void) {
+        _cleanup_(varlink_flush_close_unrefp) Varlink *link = NULL;
+        int r;
+
+        assert(arg_action == ACTION_RELINQUISH_VAR);
+
+        if (arg_machine || arg_namespace)
+                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                                       "--(smart-)relinquish-var is not supported in conjunction with %s.",
+                                       arg_machine ? "--machine=" : "--namespace=");
+
+        r = varlink_connect_journal(&link);
+        if (r < 0)
+                return log_error_errno(r, "Failed to connect to Varlink socket: %m");
+
+        return varlink_call_and_log(link, "io.systemd.Journal.RelinquishVar", /* parameters= */ NULL, /* ret_parameters= */ NULL);
+}
+
+int action_rotate(void) {
+        _cleanup_(varlink_flush_close_unrefp) Varlink *link = NULL;
+        int r;
+
+        assert(IN_SET(arg_action, ACTION_ROTATE, ACTION_ROTATE_AND_VACUUM));
+
+        if (arg_machine)
+                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                                       "--rotate is not supported in conjunction with --machine=.");
+
+        r = varlink_connect_journal(&link);
+        if (r < 0)
+                return log_error_errno(r, "Failed to connect to Varlink socket: %m");
+
+        return varlink_call_and_log(link, "io.systemd.Journal.Rotate", /* parameters= */ NULL, /* ret_parameters= */ NULL);
+}
+
+int action_vacuum(void) {
+        _cleanup_(sd_journal_closep) sd_journal *j = NULL;
+        Directory *d;
+        int r, ret = 0;
+
+        assert(IN_SET(arg_action, ACTION_VACUUM, ACTION_ROTATE_AND_VACUUM));
+
+        r = acquire_journal(&j);
+        if (r < 0)
+                return r;
+
+        HASHMAP_FOREACH(d, j->directories_by_path) {
+                r = journal_directory_vacuum(d->path, arg_vacuum_size, arg_vacuum_n_files, arg_vacuum_time, NULL, !arg_quiet);
+                if (r < 0)
+                        RET_GATHER(ret, log_error_errno(r, "Failed to vacuum %s: %m", d->path));
+        }
+
+        return ret;
+}
+
+int action_rotate_and_vacuum(void) {
+        int r;
+
+        assert(arg_action == ACTION_ROTATE_AND_VACUUM);
+
+        r = action_rotate();
+        if (r < 0)
+                return r;
+
+        return action_vacuum();
+}
+
+int action_sync(void) {
+        _cleanup_(varlink_flush_close_unrefp) Varlink *link = NULL;
+        int r;
+
+        assert(arg_action == ACTION_SYNC);
+
+        if (arg_machine)
+                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                                       "--sync is not supported in conjunction with --machine=.");
+
+        r = varlink_connect_journal(&link);
+        if (ERRNO_IS_NEG_DISCONNECT(r) && arg_namespace)
+                /* If the namespaced sd-journald instance was shut down due to inactivity, it should already
+                 * be synchronized */
+                return 0;
+        if (r < 0)
+                return log_error_errno(r, "Failed to connect to Varlink socket: %m");
+
+        return varlink_call_and_log(link, "io.systemd.Journal.Synchronize", /* parameters= */ NULL, /* ret_parameters= */ NULL);
+}
diff --git a/src/journal/journalctl-varlink.h b/src/journal/journalctl-varlink.h
new file mode 100644 (file)
index 0000000..e10983a
--- /dev/null
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+int action_flush_to_var(void);
+int action_relinquish_var(void);
+int action_rotate(void);
+int action_vacuum(void);
+int action_rotate_and_vacuum(void);
+int action_sync(void);
index 2d44e95e290926ac8bfa205a40aaab1cd13a45d0..bbc6aa6dacb9660feeb1af73a1009642fcba0f97 100644 (file)
@@ -1,86 +1,29 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
-#include <errno.h>
-#include <fcntl.h>
-#include <fnmatch.h>
 #include <getopt.h>
-#include <linux/fs.h>
-#include <signal.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <sys/inotify.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#include "sd-bus.h"
-#include "sd-device.h"
+
 #include "sd-journal.h"
 
-#include "acl-util.h"
-#include "alloc-util.h"
 #include "build.h"
-#include "bus-error.h"
-#include "bus-locator.h"
-#include "bus-util.h"
-#include "catalog.h"
-#include "chase.h"
-#include "chattr-util.h"
-#include "constants.h"
-#include "devnum-util.h"
-#include "dirent-util.h"
-#include "dissect-image.h"
-#include "fd-util.h"
-#include "fileio.h"
-#include "format-table.h"
-#include "format-util.h"
-#include "fs-util.h"
-#include "fsprg.h"
 #include "glob-util.h"
-#include "hostname-util.h"
 #include "id128-print.h"
-#include "io-util.h"
-#include "journal-def.h"
-#include "journal-internal.h"
-#include "journal-util.h"
-#include "journal-vacuum.h"
-#include "journal-verify.h"
+#include "journalctl.h"
+#include "journalctl-authenticate.h"
+#include "journalctl-catalog.h"
+#include "journalctl-misc.h"
+#include "journalctl-show.h"
+#include "journalctl-varlink.h"
 #include "locale-util.h"
-#include "log.h"
-#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 "mountpoint-util.h"
-#include "nulstr-util.h"
-#include "pager.h"
 #include "parse-argument.h"
-#include "parse-util.h"
-#include "path-util.h"
-#include "pcre2-util.h"
 #include "pretty-print.h"
-#include "qrcode-util.h"
-#include "random-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"
-#include "strv.h"
 #include "syslog-util.h"
-#include "terminal-util.h"
-#include "tmpfile-util.h"
-#include "unit-name.h"
-#include "user-util.h"
-#include "varlink.h"
 
 #define DEFAULT_FSS_INTERVAL_USEC (15*USEC_PER_MINUTE)
-#define PROCESS_INOTIFY_INTERVAL 1024   /* Every 1,024 messages processed */
 
 enum {
         /* Special values for arg_lines */
@@ -88,62 +31,65 @@ enum {
         ARG_LINES_ALL = -1,
 };
 
-static OutputMode arg_output = OUTPUT_SHORT;
-static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
-static bool arg_utc = false;
-static bool arg_follow = false;
-static bool arg_full = true;
-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;
-static bool arg_merge = false;
-static bool arg_boot = false;
-static sd_id128_t arg_boot_id = {};
-static int arg_boot_offset = 0;
-static bool arg_dmesg = false;
-static bool arg_no_hostname = false;
-static const char *arg_cursor = NULL;
-static const char *arg_cursor_file = NULL;
-static const char *arg_after_cursor = NULL;
-static bool arg_show_cursor = false;
-static const char *arg_directory = NULL;
-static char **arg_file = NULL;
-static bool arg_file_stdin = false;
-static int arg_priorities = 0xFF;
-static Set *arg_facilities = NULL;
-static char *arg_verify_key = NULL;
+JournalctlAction arg_action = ACTION_SHOW;
+OutputMode arg_output = OUTPUT_SHORT;
+JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
+PagerFlags arg_pager_flags = 0;
+bool arg_utc = false;
+bool arg_follow = false;
+bool arg_full = true;
+bool arg_all = false;
+int arg_lines = ARG_LINES_DEFAULT;
+bool arg_lines_oldest = false;
+bool arg_no_tail = false;
+bool arg_truncate_newline = false;
+bool arg_quiet = false;
+bool arg_merge = false;
+bool arg_boot = false;
+sd_id128_t arg_boot_id = {};
+int arg_boot_offset = 0;
+bool arg_dmesg = false;
+bool arg_no_hostname = false;
+const char *arg_cursor = NULL;
+const char *arg_cursor_file = NULL;
+const char *arg_after_cursor = NULL;
+bool arg_show_cursor = false;
+const char *arg_directory = NULL;
+char **arg_file = NULL;
+bool arg_file_stdin = false;
+int arg_priorities = 0xFF;
+Set *arg_facilities = NULL;
+char *arg_verify_key = NULL;
 #if HAVE_GCRYPT
-static usec_t arg_interval = DEFAULT_FSS_INTERVAL_USEC;
-static bool arg_force = false;
+usec_t arg_interval = DEFAULT_FSS_INTERVAL_USEC;
+bool arg_force = false;
 #endif
-static usec_t arg_since = 0, arg_until = 0;
-static bool arg_since_set = false, arg_until_set = false;
-static char **arg_syslog_identifier = NULL;
-static char **arg_exclude_identifier = NULL;
-static char **arg_system_units = NULL;
-static char **arg_user_units = NULL;
-static const char *arg_field = NULL;
-static bool arg_catalog = false;
-static bool arg_reverse = false;
-static int arg_journal_type = 0;
-static int arg_journal_additional_open_flags = 0;
-static int arg_namespace_flags = 0;
-static char *arg_root = NULL;
-static char *arg_image = NULL;
-static const char *arg_machine = NULL;
-static const char *arg_namespace = NULL;
-static uint64_t arg_vacuum_size = 0;
-static uint64_t arg_vacuum_n_files = 0;
-static usec_t arg_vacuum_time = 0;
-static Set *arg_output_fields = NULL;
-static const char *arg_pattern = NULL;
-static pcre2_code *arg_compiled_pattern = NULL;
-static PatternCompileCase arg_case = PATTERN_COMPILE_CASE_AUTO;
-ImagePolicy *arg_image_policy = NULL;
+usec_t arg_since = 0;
+usec_t arg_until = 0;
+bool arg_since_set = false;
+bool arg_until_set = false;
+char **arg_syslog_identifier = NULL;
+char **arg_exclude_identifier = NULL;
+char **arg_system_units = NULL;
+char **arg_user_units = NULL;
+const char *arg_field = NULL;
+bool arg_catalog = false;
+bool arg_reverse = false;
+int arg_journal_type = 0;
+int arg_journal_additional_open_flags = 0;
+int arg_namespace_flags = 0;
+char *arg_root = NULL;
+char *arg_image = NULL;
+const char *arg_machine = NULL;
+const char *arg_namespace = NULL;
+uint64_t arg_vacuum_size = 0;
+uint64_t arg_vacuum_n_files = 0;
+usec_t arg_vacuum_time = 0;
+Set *arg_output_fields = NULL;
+const char *arg_pattern = NULL;
+pcre2_code *arg_compiled_pattern = NULL;
+PatternCompileCase arg_case = PATTERN_COMPILE_CASE_AUTO;
+static ImagePolicy *arg_image_policy = NULL;
 
 STATIC_DESTRUCTOR_REGISTER(arg_file, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_facilities, set_freep);
@@ -158,107 +104,6 @@ STATIC_DESTRUCTOR_REGISTER(arg_output_fields, set_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_compiled_pattern, pattern_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
 
-static enum {
-        ACTION_SHOW,
-        ACTION_NEW_ID128,
-        ACTION_PRINT_HEADER,
-        ACTION_SETUP_KEYS,
-        ACTION_VERIFY,
-        ACTION_DISK_USAGE,
-        ACTION_LIST_CATALOG,
-        ACTION_DUMP_CATALOG,
-        ACTION_UPDATE_CATALOG,
-        ACTION_LIST_BOOTS,
-        ACTION_FLUSH,
-        ACTION_RELINQUISH_VAR,
-        ACTION_SYNC,
-        ACTION_ROTATE,
-        ACTION_VACUUM,
-        ACTION_ROTATE_AND_VACUUM,
-        ACTION_LIST_FIELDS,
-        ACTION_LIST_FIELD_NAMES,
-        ACTION_LIST_NAMESPACES,
-} arg_action = ACTION_SHOW;
-
-static int add_matches_for_device(sd_journal *j, const char *devpath) {
-        _cleanup_(sd_device_unrefp) sd_device *device = NULL;
-        sd_device *d = NULL;
-        struct stat st;
-        int r;
-
-        assert(j);
-        assert(devpath);
-
-        if (!path_startswith(devpath, "/dev/"))
-                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                       "Devpath does not start with /dev/");
-
-        if (stat(devpath, &st) < 0)
-                return log_error_errno(errno, "Couldn't stat file: %m");
-
-        r = sd_device_new_from_stat_rdev(&device, &st);
-        if (r < 0)
-                return log_error_errno(r, "Failed to get device from devnum " DEVNUM_FORMAT_STR ": %m", DEVNUM_FORMAT_VAL(st.st_rdev));
-
-        for (d = device; d; ) {
-                _cleanup_free_ char *match = NULL;
-                const char *subsys, *sysname, *devnode;
-                sd_device *parent;
-
-                r = sd_device_get_subsystem(d, &subsys);
-                if (r < 0)
-                        goto get_parent;
-
-                r = sd_device_get_sysname(d, &sysname);
-                if (r < 0)
-                        goto get_parent;
-
-                match = strjoin("_KERNEL_DEVICE=+", subsys, ":", sysname);
-                if (!match)
-                        return log_oom();
-
-                r = sd_journal_add_match(j, match, 0);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to add match: %m");
-
-                if (sd_device_get_devname(d, &devnode) >= 0) {
-                        _cleanup_free_ char *match1 = NULL;
-
-                        r = stat(devnode, &st);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to stat() device node \"%s\": %m", devnode);
-
-                        r = asprintf(&match1, "_KERNEL_DEVICE=%c" DEVNUM_FORMAT_STR, S_ISBLK(st.st_mode) ? 'b' : 'c', DEVNUM_FORMAT_VAL(st.st_rdev));
-                        if (r < 0)
-                                return log_oom();
-
-                        r = sd_journal_add_match(j, match1, 0);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to add match: %m");
-                }
-
-get_parent:
-                if (sd_device_get_parent(d, &parent) < 0)
-                        break;
-
-                d = parent;
-        }
-
-        r = add_match_this_boot(j, arg_machine);
-        if (r < 0)
-                return log_error_errno(r, "Failed to add match for the current boot: %m");
-
-        return 0;
-}
-
-static char *format_timestamp_maybe_utc(char *buf, size_t l, usec_t t) {
-
-        if (arg_utc)
-                return format_timestamp_style(buf, l, t, TIMESTAMP_UTC);
-
-        return format_timestamp(buf, l, t);
-}
-
 static int parse_boot_descriptor(const char *x, sd_id128_t *boot_id, int *offset) {
         sd_id128_t id = SD_ID128_NULL;
         int off = 0, r;
@@ -333,10 +178,6 @@ default_noarg:
         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:");
@@ -1160,1598 +1001,103 @@ static int parse_argv(int argc, char *argv[]) {
         return 1;
 }
 
-static int add_matches(sd_journal *j, char **args) {
-        bool have_term = false;
-
-        assert(j);
-
-        STRV_FOREACH(i, args) {
-                int r;
-
-                if (streq(*i, "+")) {
-                        if (!have_term)
-                                break;
-                        r = sd_journal_add_disjunction(j);
-                        have_term = false;
-
-                } else if (path_is_absolute(*i)) {
-                        _cleanup_free_ char *p = NULL, *t = NULL, *t2 = NULL, *interpreter = NULL;
-                        struct stat st;
-
-                        r = chase(*i, NULL, CHASE_TRAIL_SLASH, &p, NULL);
-                        if (r < 0)
-                                return log_error_errno(r, "Couldn't canonicalize path: %m");
-
-                        if (lstat(p, &st) < 0)
-                                return log_error_errno(errno, "Couldn't stat file: %m");
-
-                        if (S_ISREG(st.st_mode) && (0111 & st.st_mode)) {
-                                if (executable_is_script(p, &interpreter) > 0) {
-                                        _cleanup_free_ char *comm = NULL;
-
-                                        r = path_extract_filename(p, &comm);
-                                        if (r < 0)
-                                                return log_error_errno(r, "Failed to extract filename of '%s': %m", p);
-
-                                        t = strjoin("_COMM=", strshorten(comm, TASK_COMM_LEN-1));
-                                        if (!t)
-                                                return log_oom();
-
-                                        /* Append _EXE only if the interpreter is not a link.
-                                           Otherwise, it might be outdated often. */
-                                        if (lstat(interpreter, &st) == 0 && !S_ISLNK(st.st_mode)) {
-                                                t2 = strjoin("_EXE=", interpreter);
-                                                if (!t2)
-                                                        return log_oom();
-                                        }
-                                } else {
-                                        t = strjoin("_EXE=", p);
-                                        if (!t)
-                                                return log_oom();
-                                }
-
-                                r = sd_journal_add_match(j, t, 0);
-
-                                if (r >=0 && t2)
-                                        r = sd_journal_add_match(j, t2, 0);
-
-                        } else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) {
-                                r = add_matches_for_device(j, p);
-                                if (r < 0)
-                                        return r;
-                        } else
-                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                                       "File is neither a device node, nor regular file, nor executable: %s",
-                                                       *i);
-
-                        have_term = true;
-                } else {
-                        r = sd_journal_add_match(j, *i, 0);
-                        have_term = true;
-                }
-
-                if (r < 0)
-                        return log_error_errno(r, "Failed to add match '%s': %m", *i);
-        }
-
-        if (!strv_isempty(args) && !have_term)
-                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                       "\"+\" can only be used between terms");
-
-        return 0;
-}
-
-static int list_namespaces(const char *root) {
-        _cleanup_(table_unrefp) Table *table = NULL;
-        sd_id128_t machine;
-        char machine_id[SD_ID128_STRING_MAX];
-        int r;
-
-        r = sd_id128_get_machine(&machine);
-        if (r < 0)
-                return log_error_errno(r, "Failed to get machine ID: %m");
-
-        sd_id128_to_string(machine, machine_id);
-
-        table = table_new("namespace");
-        if (!table)
-                return log_oom();
-
-        (void) table_set_sort(table, (size_t) 0);
-
-        FOREACH_STRING(dir, "/var/log/journal", "/run/log/journal") {
-                _cleanup_free_ char *path = NULL;
-                _cleanup_closedir_ DIR *dirp = NULL;
-
-                path = path_join(root, dir);
-                if (!path)
-                        return log_oom();
-
-                dirp = opendir(path);
-                if (!dirp) {
-                        log_debug_errno(errno, "Failed to open directory %s, ignoring: %m", path);
-                        continue;
-                }
-
-                FOREACH_DIRENT(de, dirp, return log_error_errno(errno, "Failed to iterate through %s: %m", path)) {
-                        char *dot;
-
-                        if (!startswith(de->d_name, machine_id))
-                                continue;
-
-                        dot = strchr(de->d_name, '.');
-                        if (!dot)
-                                continue;
-
-                        if (!log_namespace_name_valid(dot + 1))
-                                continue;
-
-                        r = table_add_cell(table, NULL, TABLE_STRING, dot + 1);
-                        if (r < 0)
-                                return table_log_add_error(r);
-                }
-        }
-
-        r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, !arg_quiet);
-        if (r < 0)
-                return table_log_print_error(r);
-
-        return 0;
-}
-
-static int list_boots(sd_journal *j) {
-        _cleanup_(table_unrefp) Table *table = NULL;
-        _cleanup_free_ BootId *boots = NULL;
-        size_t n_boots;
-        int r;
-
-        assert(j);
-
-        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 0;
-
-        table = table_new("idx", "boot id", "first entry", "last entry");
-        if (!table)
-                return log_oom();
-
-        if (arg_full)
-                table_set_width(table, 0);
-
-        r = table_set_json_field_name(table, 0, "index");
-        if (r < 0)
-                return log_error_errno(r, "Failed to set JSON field name of column 0: %m");
-
-        (void) table_set_sort(table, (size_t) 0);
-        (void) table_set_reverse(table, 0, arg_reverse);
-
-        FOREACH_ARRAY(i, boots, n_boots) {
-                r = table_add_many(table,
-                                   TABLE_INT, (int)(i - boots) - (int) n_boots + 1,
-                                   TABLE_SET_ALIGN_PERCENT, 100,
-                                   TABLE_ID128, i->id,
-                                   TABLE_TIMESTAMP, i->first_usec,
-                                   TABLE_TIMESTAMP, i->last_usec);
-                if (r < 0)
-                        return table_log_add_error(r);
-        }
-
-        r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, !arg_quiet);
-        if (r < 0)
-                return table_log_print_error(r);
-
-        return 0;
-}
-
-static int add_boot(sd_journal *j) {
-        int r;
-
-        assert(j);
-
-        if (!arg_boot)
-                return 0;
-
-        /* Take a shortcut and use the current boot_id, which we can do very quickly.
-         * We can do this only when we logs are coming from the current machine,
-         * so take the slow path if log location is specified. */
-        if (arg_boot_offset == 0 && sd_id128_is_null(arg_boot_id) &&
-            !arg_directory && !arg_file && !arg_root)
-                return add_match_this_boot(j, arg_machine);
-
-        if (sd_id128_is_null(arg_boot_id)) {
-                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);
-                if (r == 0)
-                        return log_error_errno(SYNTHETIC_ERRNO(ENODATA),
-                                               "No journal boot entry found from the specified boot offset (%+i).",
-                                               arg_boot_offset);
-        } else {
-                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));
-                if (r == 0)
-                        return log_error_errno(SYNTHETIC_ERRNO(ENODATA),
-                                               "No journal boot entry found from the specified boot ID (%s).",
-                                               SD_ID128_TO_STRING(arg_boot_id));
-        }
-
-        r = add_match_boot_id(j, arg_boot_id);
-        if (r < 0)
-                return log_error_errno(r, "Failed to add match: %m");
-
-        r = sd_journal_add_conjunction(j);
-        if (r < 0)
-                return log_error_errno(r, "Failed to add conjunction: %m");
-
-        return 0;
-}
-
-static int add_dmesg(sd_journal *j) {
-        int r;
-        assert(j);
-
-        if (!arg_dmesg)
-                return 0;
-
-        r = sd_journal_add_match(j, "_TRANSPORT=kernel",
-                                 STRLEN("_TRANSPORT=kernel"));
-        if (r < 0)
-                return log_error_errno(r, "Failed to add match: %m");
-
-        r = sd_journal_add_conjunction(j);
-        if (r < 0)
-                return log_error_errno(r, "Failed to add conjunction: %m");
-
-        return 0;
-}
-
-static int get_possible_units(
-                sd_journal *j,
-                const char *fields,
-                char **patterns,
-                Set **units) {
-
-        _cleanup_set_free_free_ Set *found = NULL;
+static int run(int argc, char *argv[]) {
+        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
+        _cleanup_(umount_and_freep) char *mounted_dir = NULL;
         int r;
 
-        found = set_new(&string_hash_ops);
-        if (!found)
-                return -ENOMEM;
-
-        NULSTR_FOREACH(field, fields) {
-                const void *data;
-                size_t size;
-
-                r = sd_journal_query_unique(j, field);
-                if (r < 0)
-                        return r;
-
-                SD_JOURNAL_FOREACH_UNIQUE(j, data, size) {
-                        char *eq;
-                        size_t prefix;
-                        _cleanup_free_ char *u = NULL;
-
-                        eq = memchr(data, '=', size);
-                        if (eq)
-                                prefix = eq - (char*) data + 1;
-                        else
-                                prefix = 0;
-
-                        u = strndup((char*) data + prefix, size - prefix);
-                        if (!u)
-                                return -ENOMEM;
-
-                        STRV_FOREACH(pattern, patterns)
-                                if (fnmatch(*pattern, u, FNM_NOESCAPE) == 0) {
-                                        log_debug("Matched %s with pattern %s=%s", u, field, *pattern);
-
-                                        r = set_consume(found, u);
-                                        u = NULL;
-                                        if (r < 0 && r != -EEXIST)
-                                                return r;
-
-                                        break;
-                                }
-                }
-        }
-
-        *units = TAKE_PTR(found);
-
-        return 0;
-}
+        setlocale(LC_ALL, "");
+        log_setup();
 
-/* This list is supposed to return the superset of unit names
- * possibly matched by rules added with add_matches_for_unit... */
-#define SYSTEM_UNITS                 \
-        "_SYSTEMD_UNIT\0"            \
-        "COREDUMP_UNIT\0"            \
-        "UNIT\0"                     \
-        "OBJECT_SYSTEMD_UNIT\0"      \
-        "_SYSTEMD_SLICE\0"
-
-/* ... and add_matches_for_user_unit */
-#define USER_UNITS                   \
-        "_SYSTEMD_USER_UNIT\0"       \
-        "USER_UNIT\0"                \
-        "COREDUMP_USER_UNIT\0"       \
-        "OBJECT_SYSTEMD_USER_UNIT\0" \
-        "_SYSTEMD_USER_SLICE\0"
-
-static int add_units(sd_journal *j) {
-        _cleanup_strv_free_ char **patterns = NULL;
-        int r, count = 0;
-
-        assert(j);
-
-        STRV_FOREACH(i, arg_system_units) {
-                _cleanup_free_ char *u = NULL;
-
-                r = unit_name_mangle(*i, UNIT_NAME_MANGLE_GLOB | (arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN), &u);
-                if (r < 0)
-                        return r;
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                return r;
 
-                if (string_is_glob(u)) {
-                        r = strv_push(&patterns, u);
-                        if (r < 0)
-                                return r;
-                        u = NULL;
-                } else {
-                        r = add_matches_for_unit(j, u);
-                        if (r < 0)
-                                return r;
-                        r = sd_journal_add_disjunction(j);
-                        if (r < 0)
-                                return r;
-                        count++;
-                }
-        }
+        char **args = strv_skip(argv, optind);
 
-        if (!strv_isempty(patterns)) {
-                _cleanup_set_free_free_ Set *units = NULL;
-                char *u;
+        if (arg_image) {
+                assert(!arg_root);
 
-                r = get_possible_units(j, SYSTEM_UNITS, patterns, &units);
+                r = mount_image_privately_interactively(
+                                arg_image,
+                                arg_image_policy,
+                                DISSECT_IMAGE_GENERIC_ROOT |
+                                DISSECT_IMAGE_REQUIRE_ROOT |
+                                DISSECT_IMAGE_VALIDATE_OS |
+                                DISSECT_IMAGE_RELAX_VAR_CHECK |
+                                (arg_action == ACTION_UPDATE_CATALOG ? DISSECT_IMAGE_FSCK|DISSECT_IMAGE_GROWFS : DISSECT_IMAGE_READ_ONLY) |
+                                DISSECT_IMAGE_ALLOW_USERSPACE_VERITY,
+                                &mounted_dir,
+                                /* ret_dir_fd= */ NULL,
+                                &loop_device);
                 if (r < 0)
                         return r;
 
-                SET_FOREACH(u, units) {
-                        r = add_matches_for_unit(j, u);
-                        if (r < 0)
-                                return r;
-                        r = sd_journal_add_disjunction(j);
-                        if (r < 0)
-                                return r;
-                        count++;
-                }
+                arg_root = strdup(mounted_dir);
+                if (!arg_root)
+                        return log_oom();
         }
 
-        patterns = strv_free(patterns);
+        switch (arg_action) {
 
-        STRV_FOREACH(i, arg_user_units) {
-                _cleanup_free_ char *u = NULL;
+        case ACTION_SHOW:
+                return action_show(args);
 
-                r = unit_name_mangle(*i, UNIT_NAME_MANGLE_GLOB | (arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN), &u);
-                if (r < 0)
-                        return r;
+        case ACTION_NEW_ID128:
+                return id128_print_new(ID128_PRINT_PRETTY);
 
-                if (string_is_glob(u)) {
-                        r = strv_push(&patterns, u);
-                        if (r < 0)
-                                return r;
-                        u = NULL;
-                } else {
-                        r = add_matches_for_user_unit(j, u, getuid());
-                        if (r < 0)
-                                return r;
-                        r = sd_journal_add_disjunction(j);
-                        if (r < 0)
-                                return r;
-                        count++;
-                }
-        }
+        case ACTION_SETUP_KEYS:
+                return action_setup_keys();
 
-        if (!strv_isempty(patterns)) {
-                _cleanup_set_free_free_ Set *units = NULL;
-                char *u;
+        case ACTION_LIST_CATALOG:
+        case ACTION_DUMP_CATALOG:
+                return action_list_catalog(args);
 
-                r = get_possible_units(j, USER_UNITS, patterns, &units);
-                if (r < 0)
-                        return r;
+        case ACTION_UPDATE_CATALOG:
+                return action_update_catalog();
 
-                SET_FOREACH(u, units) {
-                        r = add_matches_for_user_unit(j, u, getuid());
-                        if (r < 0)
-                                return r;
-                        r = sd_journal_add_disjunction(j);
-                        if (r < 0)
-                                return r;
-                        count++;
-                }
-        }
+        case ACTION_PRINT_HEADER:
+                return action_print_header();
 
-        /* Complain if the user request matches but nothing whatsoever was
-         * found, since otherwise everything would be matched. */
-        if (!(strv_isempty(arg_system_units) && strv_isempty(arg_user_units)) && count == 0)
-                return -ENODATA;
+        case ACTION_VERIFY:
+                return action_verify();
 
-        r = sd_journal_add_conjunction(j);
-        if (r < 0)
-                return r;
+        case ACTION_DISK_USAGE:
+                return action_disk_usage();
 
-        return 0;
-}
+        case ACTION_LIST_BOOTS:
+                return action_list_boots();
 
-static int add_priorities(sd_journal *j) {
-        char match[] = "PRIORITY=0";
-        int i, r;
-        assert(j);
+        case ACTION_LIST_FIELDS:
+                return action_list_fields();
 
-        if (arg_priorities == 0xFF)
-                return 0;
+        case ACTION_LIST_FIELD_NAMES:
+                return action_list_field_names();
 
-        for (i = LOG_EMERG; i <= LOG_DEBUG; i++)
-                if (arg_priorities & (1 << i)) {
-                        match[sizeof(match)-2] = '0' + i;
+        case ACTION_LIST_NAMESPACES:
+                return action_list_namespaces();
 
-                        r = sd_journal_add_match(j, match, strlen(match));
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to add match: %m");
-                }
+        case ACTION_FLUSH:
+                return action_flush_to_var();
 
-        r = sd_journal_add_conjunction(j);
-        if (r < 0)
-                return log_error_errno(r, "Failed to add conjunction: %m");
+        case ACTION_RELINQUISH_VAR:
+                return action_relinquish_var();
 
-        return 0;
-}
+        case ACTION_SYNC:
+                return action_sync();
 
-static int add_facilities(sd_journal *j) {
-        void *p;
-        int r;
+        case ACTION_ROTATE:
+                return action_rotate();
 
-        SET_FOREACH(p, arg_facilities) {
-                char match[STRLEN("SYSLOG_FACILITY=") + DECIMAL_STR_MAX(int)];
+        case ACTION_VACUUM:
+                return action_vacuum();
 
-                xsprintf(match, "SYSLOG_FACILITY=%d", PTR_TO_INT(p));
+        case ACTION_ROTATE_AND_VACUUM:
+                return action_rotate_and_vacuum();
 
-                r = sd_journal_add_match(j, match, strlen(match));
-                if (r < 0)
-                        return log_error_errno(r, "Failed to add match: %m");
+        default:
+                assert_not_reached();
         }
-
-        return 0;
-}
-
-static int add_syslog_identifier(sd_journal *j) {
-        int r;
-
-        assert(j);
-
-        STRV_FOREACH(i, arg_syslog_identifier) {
-                _cleanup_free_ char *u = NULL;
-
-                u = strjoin("SYSLOG_IDENTIFIER=", *i);
-                if (!u)
-                        return -ENOMEM;
-                r = sd_journal_add_match(j, u, 0);
-                if (r < 0)
-                        return r;
-                r = sd_journal_add_disjunction(j);
-                if (r < 0)
-                        return r;
-        }
-
-        r = sd_journal_add_conjunction(j);
-        if (r < 0)
-                return r;
-
-        return 0;
-}
-
-static int add_exclude_identifier(sd_journal *j) {
-        _cleanup_set_free_ Set *excludes = NULL;
-        int r;
-
-        assert(j);
-
-        r = set_put_strdupv(&excludes, arg_exclude_identifier);
-        if (r < 0)
-                return r;
-
-        return set_free_and_replace(j->exclude_syslog_identifiers, excludes);
-}
-
-#if HAVE_GCRYPT
-static int format_journal_url(
-                const void *seed,
-                size_t seed_size,
-                uint64_t start,
-                uint64_t interval,
-                const char *hn,
-                sd_id128_t machine,
-                bool full,
-                char **ret_url) {
-
-        _cleanup_(memstream_done) MemStream m = {};
-        FILE *f;
-
-        assert(seed);
-        assert(seed_size > 0);
-
-        f = memstream_init(&m);
-        if (!f)
-                return -ENOMEM;
-
-        if (full)
-                fputs("fss://", f);
-
-        for (size_t i = 0; i < seed_size; i++) {
-                if (i > 0 && i % 3 == 0)
-                        fputc('-', f);
-                fprintf(f, "%02x", ((uint8_t*) seed)[i]);
-        }
-
-        fprintf(f, "/%"PRIx64"-%"PRIx64, start, interval);
-
-        if (full) {
-                fprintf(f, "?machine=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(machine));
-                if (hn)
-                        fprintf(f, ";hostname=%s", hn);
-        }
-
-        return memstream_finalize(&m, ret_url, NULL);
-}
-#endif
-
-static int setup_keys(void) {
-#if HAVE_GCRYPT
-        size_t mpk_size, seed_size, state_size;
-        _cleanup_(unlink_and_freep) char *k = NULL;
-        _cleanup_free_ char *p = NULL;
-        uint8_t *mpk, *seed, *state;
-        _cleanup_close_ int fd = -EBADF;
-        sd_id128_t machine, boot;
-        struct stat st;
-        uint64_t n;
-        int r;
-
-        r = stat("/var/log/journal", &st);
-        if (r < 0 && !IN_SET(errno, ENOENT, ENOTDIR))
-                return log_error_errno(errno, "stat(\"%s\") failed: %m", "/var/log/journal");
-
-        if (r < 0 || !S_ISDIR(st.st_mode)) {
-                log_error("%s is not a directory, must be using persistent logging for FSS.",
-                          "/var/log/journal");
-                return r < 0 ? -errno : -ENOTDIR;
-        }
-
-        r = sd_id128_get_machine(&machine);
-        if (r < 0)
-                return log_error_errno(r, "Failed to get machine ID: %m");
-
-        r = sd_id128_get_boot(&boot);
-        if (r < 0)
-                return log_error_errno(r, "Failed to get boot ID: %m");
-
-        if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss",
-                     SD_ID128_FORMAT_VAL(machine)) < 0)
-                return log_oom();
-
-        if (arg_force) {
-                r = unlink(p);
-                if (r < 0 && errno != ENOENT)
-                        return log_error_errno(errno, "unlink(\"%s\") failed: %m", p);
-        } else if (access(p, F_OK) >= 0)
-                return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
-                                       "Sealing key file %s exists already. Use --force to recreate.", p);
-
-        if (asprintf(&k, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss.tmp.XXXXXX",
-                     SD_ID128_FORMAT_VAL(machine)) < 0)
-                return log_oom();
-
-        mpk_size = FSPRG_mskinbytes(FSPRG_RECOMMENDED_SECPAR);
-        mpk = alloca_safe(mpk_size);
-
-        seed_size = FSPRG_RECOMMENDED_SEEDLEN;
-        seed = alloca_safe(seed_size);
-
-        state_size = FSPRG_stateinbytes(FSPRG_RECOMMENDED_SECPAR);
-        state = alloca_safe(state_size);
-
-        log_info("Generating seed...");
-        r = crypto_random_bytes(seed, seed_size);
-        if (r < 0)
-                return log_error_errno(r, "Failed to acquire random seed: %m");
-
-        log_info("Generating key pair...");
-        FSPRG_GenMK(NULL, mpk, seed, seed_size, FSPRG_RECOMMENDED_SECPAR);
-
-        log_info("Generating sealing key...");
-        FSPRG_GenState0(state, mpk, seed, seed_size);
-
-        assert(arg_interval > 0);
-
-        n = now(CLOCK_REALTIME);
-        n /= arg_interval;
-
-        safe_close(fd);
-        fd = mkostemp_safe(k);
-        if (fd < 0)
-                return log_error_errno(fd, "Failed to open %s: %m", k);
-
-        r = chattr_secret(fd, CHATTR_WARN_UNSUPPORTED_FLAGS);
-        if (r < 0)
-                log_full_errno(ERRNO_IS_NOT_SUPPORTED(r) ? LOG_DEBUG : LOG_WARNING,
-                               r, "Failed to set file attributes on '%s', ignoring: %m", k);
-
-        struct FSSHeader h = {
-                .signature = { 'K', 'S', 'H', 'H', 'R', 'H', 'L', 'P' },
-                .machine_id = machine,
-                .boot_id = boot,
-                .header_size = htole64(sizeof(h)),
-                .start_usec = htole64(n * arg_interval),
-                .interval_usec = htole64(arg_interval),
-                .fsprg_secpar = htole16(FSPRG_RECOMMENDED_SECPAR),
-                .fsprg_state_size = htole64(state_size),
-        };
-
-        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);
-        if (r < 0)
-                return log_error_errno(r, "Failed to write state: %m");
-
-        if (rename(k, p) < 0)
-                return log_error_errno(errno, "Failed to link file: %m");
-
-        k = mfree(k);
-
-        _cleanup_free_ char *hn = NULL, *key = NULL;
-
-        r = format_journal_url(seed, seed_size, n, arg_interval, hn, machine, false, &key);
-        if (r < 0)
-                return r;
-
-        if (on_tty()) {
-                hn = gethostname_malloc();
-                if (hn)
-                        hostname_cleanup(hn);
-
-                fprintf(stderr,
-                        "\nNew keys have been generated for host %s%s" SD_ID128_FORMAT_STR ".\n"
-                        "\n"
-                        "The %ssecret sealing key%s has been written to the following local file.\n"
-                        "This key file is automatically updated when the sealing key is advanced.\n"
-                        "It should not be used on multiple hosts.\n"
-                        "\n"
-                        "\t%s\n"
-                        "\n"
-                        "The sealing key is automatically changed every %s.\n"
-                        "\n"
-                        "Please write down the following %ssecret verification key%s. It should be stored\n"
-                        "in a safe location and should not be saved locally on disk.\n"
-                        "\n\t%s",
-                        strempty(hn), hn ? "/" : "",
-                        SD_ID128_FORMAT_VAL(machine),
-                        ansi_highlight(), ansi_normal(),
-                        p,
-                        FORMAT_TIMESPAN(arg_interval, 0),
-                        ansi_highlight(), ansi_normal(),
-                        ansi_highlight_red());
-                fflush(stderr);
-        }
-
-        puts(key);
-
-        if (on_tty()) {
-                fprintf(stderr, "%s", ansi_normal());
-#if HAVE_QRENCODE
-                _cleanup_free_ char *url = NULL;
-                r = format_journal_url(seed, seed_size, n, arg_interval, hn, machine, true, &url);
-                if (r < 0)
-                        return r;
-
-                (void) print_qrcode(stderr,
-                                    "To transfer the verification key to your phone scan the QR code below",
-                                    url);
-#endif
-        }
-
-        return 0;
-#else
-        return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
-                               "Forward-secure sealing not available.");
-#endif
-}
-
-static int verify(sd_journal *j, bool verbose) {
-        int r = 0;
-        JournalFile *f;
-
-        assert(j);
-
-        log_show_color(true);
-
-        ORDERED_HASHMAP_FOREACH(f, j->files) {
-                int k;
-                usec_t first = 0, validated = 0, last = 0;
-
-#if HAVE_GCRYPT
-                if (!arg_verify_key && JOURNAL_HEADER_SEALED(f->header))
-                        log_notice("Journal file %s has sealing enabled but verification key has not been passed using --verify-key=.", f->path);
-#endif
-
-                k = journal_file_verify(f, arg_verify_key, &first, &validated, &last, verbose);
-                if (k == -EINVAL)
-                        /* If the key was invalid give up right-away. */
-                        return k;
-                else if (k < 0)
-                        r = log_warning_errno(k, "FAIL: %s (%m)", f->path);
-                else {
-                        char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX];
-                        log_full(verbose ? LOG_INFO : LOG_DEBUG, "PASS: %s", f->path);
-
-                        if (arg_verify_key && JOURNAL_HEADER_SEALED(f->header)) {
-                                if (validated > 0) {
-                                        log_full(verbose ? LOG_INFO : LOG_DEBUG,
-                                                 "=> Validated from %s to %s, final %s entries not sealed.",
-                                                 format_timestamp_maybe_utc(a, sizeof(a), first),
-                                                 format_timestamp_maybe_utc(b, sizeof(b), validated),
-                                                 FORMAT_TIMESPAN(last > validated ? last - validated : 0, 0));
-                                } else if (last > 0)
-                                        log_full(verbose ? LOG_INFO : LOG_DEBUG,
-                                                 "=> No sealing yet, %s of entries not sealed.",
-                                                 FORMAT_TIMESPAN(last - first, 0));
-                                else
-                                        log_full(verbose ? LOG_INFO : LOG_DEBUG,
-                                                 "=> No sealing yet, no entries in file.");
-                        }
-                }
-        }
-
-        return r;
-}
-
-static int varlink_connect_journal(Varlink **ret_link) {
-        const char *address;
-        int r;
-
-        address = arg_namespace ?
-                  strjoina("/run/systemd/journal.", arg_namespace, "/io.systemd.journal") :
-                  "/run/systemd/journal/io.systemd.journal";
-
-        r = varlink_connect_address(ret_link, address);
-        if (r < 0)
-                return r;
-
-        (void) varlink_set_description(*ret_link, "journal");
-        (void) varlink_set_relative_timeout(*ret_link, USEC_INFINITY);
-
-        return 0;
-}
-
-static int flush_to_var(void) {
-        _cleanup_(varlink_flush_close_unrefp) Varlink *link = NULL;
-        int r;
-
-        if (arg_machine || arg_namespace)
-                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
-                                       "--flush is not supported in conjunction with %s.",
-                                       arg_machine ? "--machine=" : "--namespace=");
-
-        if (access("/run/systemd/journal/flushed", F_OK) >= 0)
-                return 0; /* Already flushed, no need to contact journald */
-        if (errno != ENOENT)
-                return log_error_errno(errno, "Unable to check for existence of /run/systemd/journal/flushed: %m");
-
-        r = varlink_connect_journal(&link);
-        if (r < 0)
-                return log_error_errno(r, "Failed to connect to Varlink socket: %m");
-
-        return varlink_call_and_log(link, "io.systemd.Journal.FlushToVar", /* parameters= */ NULL, /* ret_parameters= */ NULL);
-}
-
-static int relinquish_var(void) {
-        _cleanup_(varlink_flush_close_unrefp) Varlink *link = NULL;
-        int r;
-
-        if (arg_machine || arg_namespace)
-                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
-                                       "--(smart-)relinquish-var is not supported in conjunction with %s.",
-                                       arg_machine ? "--machine=" : "--namespace=");
-
-        r = varlink_connect_journal(&link);
-        if (r < 0)
-                return log_error_errno(r, "Failed to connect to Varlink socket: %m");
-
-        return varlink_call_and_log(link, "io.systemd.Journal.RelinquishVar", /* parameters= */ NULL, /* ret_parameters= */ NULL);
-}
-
-static int rotate(void) {
-        _cleanup_(varlink_flush_close_unrefp) Varlink *link = NULL;
-        int r;
-
-        if (arg_machine)
-                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
-                                       "--rotate is not supported in conjunction with --machine=.");
-
-        r = varlink_connect_journal(&link);
-        if (r < 0)
-                return log_error_errno(r, "Failed to connect to Varlink socket: %m");
-
-        return varlink_call_and_log(link, "io.systemd.Journal.Rotate", /* parameters= */ NULL, /* ret_parameters= */ NULL);
-}
-
-static int sync_journal(void) {
-        _cleanup_(varlink_flush_close_unrefp) Varlink *link = NULL;
-        int r;
-
-        if (arg_machine)
-                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
-                                       "--sync is not supported in conjunction with --machine=.");
-
-        r = varlink_connect_journal(&link);
-        if (ERRNO_IS_NEG_DISCONNECT(r) && arg_namespace)
-                /* If the namespaced sd-journald instance was shut down due to inactivity, it should already
-                 * be synchronized */
-                return 0;
-        if (r < 0)
-                return log_error_errno(r, "Failed to connect to Varlink socket: %m");
-
-        return varlink_call_and_log(link, "io.systemd.Journal.Synchronize", /* parameters= */ NULL, /* ret_parameters= */ NULL);
-}
-
-static int action_list_fields(sd_journal *j) {
-        const void *data;
-        size_t size;
-        int r, n_shown = 0;
-
-        assert(arg_field);
-
-        r = sd_journal_set_data_threshold(j, 0);
-        if (r < 0)
-                return log_error_errno(r, "Failed to unset data size threshold: %m");
-
-        r = sd_journal_query_unique(j, arg_field);
-        if (r < 0)
-                return log_error_errno(r, "Failed to query unique data objects: %m");
-
-        SD_JOURNAL_FOREACH_UNIQUE(j, data, size) {
-                const void *eq;
-
-                if (arg_lines >= 0 && n_shown >= arg_lines)
-                        break;
-
-                eq = memchr(data, '=', size);
-                if (eq)
-                        printf("%.*s\n", (int) (size - ((const uint8_t*) eq - (const uint8_t*) data + 1)), (const char*) eq + 1);
-                else
-                        printf("%.*s\n", (int) size, (const char*) data);
-
-                n_shown++;
-        }
-
-        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;
-}
-
-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 int show(Context *c) {
-        sd_journal *j;
-        int r, n_shown = 0;
-
-        assert(c);
-
-        j = ASSERT_PTR(c->journal);
-
-        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 run(int argc, char *argv[]) {
-        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, "");
-        log_setup();
-
-        /* Increase max number of open files if we can, we might needs this when browsing journal files, which might be
-         * split up into many files. */
-        (void) rlimit_nofile_bump(HIGH_RLIMIT_NOFILE);
-
-        r = parse_argv(argc, argv);
-        if (r <= 0)
-                return r;
-
-        if (arg_image) {
-                assert(!arg_root);
-
-                r = mount_image_privately_interactively(
-                                arg_image,
-                                arg_image_policy,
-                                DISSECT_IMAGE_GENERIC_ROOT |
-                                DISSECT_IMAGE_REQUIRE_ROOT |
-                                DISSECT_IMAGE_VALIDATE_OS |
-                                DISSECT_IMAGE_RELAX_VAR_CHECK |
-                                (arg_action == ACTION_UPDATE_CATALOG ? DISSECT_IMAGE_FSCK|DISSECT_IMAGE_GROWFS : DISSECT_IMAGE_READ_ONLY) |
-                                DISSECT_IMAGE_ALLOW_USERSPACE_VERITY,
-                                &mounted_dir,
-                                /* ret_dir_fd= */ NULL,
-                                &loop_device);
-                if (r < 0)
-                        return r;
-
-                arg_root = strdup(mounted_dir);
-                if (!arg_root)
-                        return log_oom();
-        }
-
-        signal(SIGWINCH, columns_lines_cache_reset);
-        sigbus_install();
-
-        switch (arg_action) {
-
-        case ACTION_NEW_ID128:
-                return id128_print_new(ID128_PRINT_PRETTY);
-
-        case ACTION_SETUP_KEYS:
-                return setup_keys();
-
-        case ACTION_LIST_CATALOG:
-        case ACTION_DUMP_CATALOG:
-        case ACTION_UPDATE_CATALOG: {
-                _cleanup_free_ char *database = NULL;
-
-                database = path_join(arg_root, secure_getenv("SYSTEMD_CATALOG") ?: CATALOG_DATABASE);
-                if (!database)
-                        return log_oom();
-
-                if (arg_action == ACTION_UPDATE_CATALOG) {
-                        const char *e;
-
-                        e = secure_getenv("SYSTEMD_CATALOG_SOURCES");
-
-                        r = catalog_update(
-                                        database,
-                                        arg_root,
-                                        e ? (const char* const*) STRV_MAKE(e) : catalog_file_dirs);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to list catalog: %m");
-                } else {
-                        bool oneline = arg_action == ACTION_LIST_CATALOG;
-
-                        pager_open(arg_pager_flags);
-
-                        if (optind < argc)
-                                r = catalog_list_items(stdout, database, oneline, argv + optind);
-                        else
-                                r = catalog_list(stdout, database, oneline);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to list catalog: %m");
-                }
-
-                return 0;
-        }
-
-        case ACTION_FLUSH:
-                return flush_to_var();
-
-        case ACTION_RELINQUISH_VAR:
-                return relinquish_var();
-
-        case ACTION_SYNC:
-                return sync_journal();
-
-        case ACTION_ROTATE:
-                return rotate();
-
-        case ACTION_LIST_NAMESPACES:
-                return list_namespaces(arg_root);
-
-        case ACTION_SHOW:
-        case ACTION_PRINT_HEADER:
-        case ACTION_VERIFY:
-        case ACTION_DISK_USAGE:
-        case ACTION_LIST_BOOTS:
-        case ACTION_VACUUM:
-        case ACTION_ROTATE_AND_VACUUM:
-        case ACTION_LIST_FIELDS:
-        case ACTION_LIST_FIELD_NAMES:
-                /* These ones require access to the journal files, continue below. */
-                break;
-
-        default:
-                assert_not_reached();
-        }
-
-        if (arg_directory)
-                r = sd_journal_open_directory(&j, arg_directory, arg_journal_type | arg_journal_additional_open_flags);
-        else if (arg_root)
-                r = sd_journal_open_directory(&j, arg_root, arg_journal_type | arg_journal_additional_open_flags | SD_JOURNAL_OS_ROOT);
-        else if (arg_file_stdin)
-                r = sd_journal_open_files_fd(&j, (int[]) { STDIN_FILENO }, 1, arg_journal_additional_open_flags);
-        else if (arg_file)
-                r = sd_journal_open_files(&j, (const char**) arg_file, arg_journal_additional_open_flags);
-        else if (arg_machine)
-                r = journal_open_machine(&j, arg_machine, arg_journal_additional_open_flags);
-        else
-                r = sd_journal_open_namespace(
-                                &j,
-                                arg_namespace,
-                                (arg_merge ? 0 : SD_JOURNAL_LOCAL_ONLY) |
-                                arg_namespace_flags | arg_journal_type | arg_journal_additional_open_flags);
-        if (r < 0)
-                return log_error_errno(r, "Failed to open %s: %m", arg_directory ?: arg_file ? "files" : "journal");
-
-        r = journal_access_check_and_warn(j, arg_quiet,
-                                          !(arg_journal_type == SD_JOURNAL_CURRENT_USER || arg_user_units));
-        if (r < 0)
-                return r;
-
-        switch (arg_action) {
-
-        case ACTION_NEW_ID128:
-        case ACTION_SETUP_KEYS:
-        case ACTION_LIST_CATALOG:
-        case ACTION_DUMP_CATALOG:
-        case ACTION_UPDATE_CATALOG:
-        case ACTION_FLUSH:
-        case ACTION_SYNC:
-        case ACTION_ROTATE:
-                assert_not_reached();
-
-        case ACTION_PRINT_HEADER:
-                journal_print_header(j);
-                return 0;
-
-        case ACTION_VERIFY:
-                return verify(j, !arg_quiet);
-
-        case ACTION_DISK_USAGE: {
-                uint64_t bytes = 0;
-
-                r = sd_journal_get_usage(j, &bytes);
-                if (r < 0)
-                        return r;
-
-                printf("Archived and active journals take up %s in the file system.\n",
-                       FORMAT_BYTES(bytes));
-
-                return 0;
-        }
-
-        case ACTION_LIST_BOOTS:
-                return list_boots(j);
-
-        case ACTION_ROTATE_AND_VACUUM:
-
-                r = rotate();
-                if (r < 0)
-                        return r;
-
-                _fallthrough_;
-
-        case ACTION_VACUUM: {
-                Directory *d;
-                int ret = 0;
-
-                HASHMAP_FOREACH(d, j->directories_by_path) {
-                        r = journal_directory_vacuum(d->path, arg_vacuum_size, arg_vacuum_n_files, arg_vacuum_time, NULL, !arg_quiet);
-                        if (r < 0) {
-                                log_error_errno(r, "Failed to vacuum %s: %m", d->path);
-                                if (ret >= 0)
-                                        ret = r;
-                        }
-                }
-
-                return ret;
-        }
-
-        case ACTION_LIST_FIELD_NAMES: {
-                const char *field;
-
-                SD_JOURNAL_FOREACH_FIELD(j, field)
-                        printf("%s\n", field);
-
-                return 0;
-        }
-
-        case ACTION_SHOW:
-        case ACTION_LIST_FIELDS:
-                break;
-
-        default:
-                assert_not_reached();
-        }
-
-        if (arg_boot_offset != 0 &&
-            sd_journal_has_runtime_files(j) > 0 &&
-            sd_journal_has_persistent_files(j) == 0) {
-                log_info("Specifying boot ID or boot offset has no effect, no persistent journal was found.");
-
-                if (arg_action == ACTION_SHOW && arg_compiled_pattern)
-                        return -ENOENT;
-
-                return 0;
-        }
-        /* add_boot() must be called first!
-         * It may need to seek the journal to find parent boot IDs. */
-        r = add_boot(j);
-        if (r < 0)
-                return r;
-
-        r = add_dmesg(j);
-        if (r < 0)
-                return r;
-
-        r = add_units(j);
-        if (r < 0)
-                return log_error_errno(r, "Failed to add filter for units: %m");
-
-        r = add_syslog_identifier(j);
-        if (r < 0)
-                return log_error_errno(r, "Failed to add filter for syslog identifiers: %m");
-
-        r = add_exclude_identifier(j);
-        if (r < 0)
-                return log_error_errno(r, "Failed to add exclude filter for syslog identifiers: %m");
-
-        r = add_priorities(j);
-        if (r < 0)
-                return r;
-
-        r = add_facilities(j);
-        if (r < 0)
-                return r;
-
-        r = add_matches(j, argv + optind);
-        if (r < 0)
-                return r;
-
-        if (DEBUG_LOGGING) {
-                _cleanup_free_ char *filter = NULL;
-
-                filter = journal_make_match_string(j);
-                if (!filter)
-                        return log_oom();
-
-                log_debug("Journal filter: %s", filter);
-        }
-
-        if (arg_action == ACTION_LIST_FIELDS)
-                return action_list_fields(j);
-
-        /* Opening the fd now means the first sd_journal_wait() will actually wait */
-        if (arg_follow) {
-                poll_fd = sd_journal_get_fd(j);
-                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_cursor || arg_after_cursor || arg_cursor_file) {
-                cursor = arg_cursor ?: arg_after_cursor;
-
-                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;
-                        }
-                } 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");
-
-                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) {
-                        /* We couldn't find the next entry after the cursor. */
-                        if (arg_follow)
-                                need_seek = true;
-                        else
-                                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");
-                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)
-                need_seek = true;
-
-        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(j, &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));
-                }
-        }
-
-        Context c = {
-                .journal = j,
-                .has_cursor = cursor,
-                .need_seek = need_seek,
-                .since_seeked = since_seeked,
-        };
-
-        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(j);
-                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(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
-                 * 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;
 }
 
 DEFINE_MAIN_FUNCTION_WITH_POSITIVE_SIGNAL(run);
diff --git a/src/journal/journalctl.h b/src/journal/journalctl.h
new file mode 100644 (file)
index 0000000..c6993a2
--- /dev/null
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include "sd-id128.h"
+
+#include "json.h"
+#include "output-mode.h"
+#include "pager.h"
+#include "pcre2-util.h"
+#include "set.h"
+#include "time-util.h"
+
+typedef enum JournalctlAction {
+        ACTION_SHOW,
+        ACTION_NEW_ID128,
+        ACTION_SETUP_KEYS,
+        ACTION_LIST_CATALOG,
+        ACTION_DUMP_CATALOG,
+        ACTION_UPDATE_CATALOG,
+        ACTION_PRINT_HEADER,
+        ACTION_VERIFY,
+        ACTION_DISK_USAGE,
+        ACTION_LIST_BOOTS,
+        ACTION_LIST_FIELDS,
+        ACTION_LIST_FIELD_NAMES,
+        ACTION_LIST_NAMESPACES,
+        ACTION_FLUSH,
+        ACTION_RELINQUISH_VAR,
+        ACTION_SYNC,
+        ACTION_ROTATE,
+        ACTION_VACUUM,
+        ACTION_ROTATE_AND_VACUUM,
+} JournalctlAction;
+
+extern JournalctlAction arg_action;
+extern OutputMode arg_output;
+extern JsonFormatFlags arg_json_format_flags;
+extern PagerFlags arg_pager_flags;
+extern bool arg_utc;
+extern bool arg_follow;
+extern bool arg_full;
+extern bool arg_all;
+extern int arg_lines;
+extern bool arg_lines_oldest;
+extern bool arg_no_tail;
+extern bool arg_truncate_newline;
+extern bool arg_quiet;
+extern bool arg_merge;
+extern bool arg_boot;
+extern sd_id128_t arg_boot_id;
+extern int arg_boot_offset;
+extern bool arg_dmesg;
+extern bool arg_no_hostname;
+extern const char *arg_cursor;
+extern const char *arg_cursor_file;
+extern const char *arg_after_cursor;
+extern bool arg_show_cursor;
+extern const char *arg_directory;
+extern char **arg_file;
+extern bool arg_file_stdin;
+extern int arg_priorities;
+extern Set *arg_facilities;
+extern char *arg_verify_key;
+#if HAVE_GCRYPT
+extern usec_t arg_interval;
+extern bool arg_force;
+#endif
+extern usec_t arg_since;
+extern usec_t arg_until;
+extern bool arg_since_set;
+extern bool arg_until_set;
+extern char **arg_syslog_identifier;
+extern char **arg_exclude_identifier;
+extern char **arg_system_units;
+extern char **arg_user_units;
+extern const char *arg_field;
+extern bool arg_catalog;
+extern bool arg_reverse;
+extern int arg_journal_type;
+extern int arg_journal_additional_open_flags;
+extern int arg_namespace_flags;
+extern char *arg_root;
+extern char *arg_image;
+extern const char *arg_machine;
+extern const char *arg_namespace;
+extern uint64_t arg_vacuum_size;
+extern uint64_t arg_vacuum_n_files;
+extern usec_t arg_vacuum_time;
+extern Set *arg_output_fields;
+extern const char *arg_pattern;
+extern pcre2_code *arg_compiled_pattern;
+extern PatternCompileCase arg_case;
+
+static inline bool arg_lines_needs_seek_end(void) {
+        return arg_lines >= 0 && !arg_lines_oldest;
+}
index 1811feb02b8bd6a1623fda4789622d2a6c23600f..11bb6fa1f223ff32d9fbd83686c92ca144e68ce2 100644 (file)
@@ -29,6 +29,20 @@ libjournal_core = static_library(
                        userspace],
         build_by_default : false)
 
+journalctl_sources = files(
+        'journalctl.c',
+        'journalctl-catalog.c',
+        'journalctl-filter.c',
+        'journalctl-misc.c',
+        'journalctl-show.c',
+        'journalctl-util.c',
+        'journalctl-varlink.c',
+)
+
+if conf.get('HAVE_GCRYPT') == 1
+        journalctl_sources += files('journalctl-authenticate.c')
+endif
+
 if get_option('link-journalctl-shared')
         journalctl_link_with = [libshared]
 else
@@ -91,7 +105,7 @@ executables += [
         executable_template + {
                 'name' : 'journalctl',
                 'public' : true,
-                'sources' : files('journalctl.c'),
+                'sources' : journalctl_sources,
                 'link_with' : journalctl_link_with,
                 'dependencies' : [
                         libdl,