+ r = sd_journal_get_realtime_usec(j, &id->first);
+ if (r < 0)
+ return r;
+
+ if (sd_id128_equal(id->id, *boot_id))
+ ref_boot_id = *id;
+
+ count++;
+ flush:
+ sd_journal_flush_matches(j);
+ }
+
+ qsort_safe(all_ids, count, sizeof(boot_id_t), boot_id_cmp);
+
+ if (sd_id128_equal(*boot_id, SD_ID128_NULL)) {
+ if (relative > (int) count || relative <= -(int)count)
+ return -EADDRNOTAVAIL;
+
+ *boot_id = all_ids[(relative <= 0)*count + relative - 1].id;
+ } else {
+ id = bsearch(&ref_boot_id, all_ids, count, sizeof(boot_id_t), boot_id_cmp);
+
+ if (!id ||
+ relative <= 0 ? (id - all_ids) + relative < 0 :
+ (id - all_ids) + relative >= (int) count)
+ return -EADDRNOTAVAIL;
+
+ *boot_id = (id + relative)->id;
+ }
+
+ return 0;
+}
+
+static int add_boot(sd_journal *j) {
+ char match[9+32+1] = "_BOOT_ID=";
+ int r;
+
+ assert(j);
+
+ if (!arg_boot)
+ return 0;
+
+ if (arg_boot_offset == 0 && sd_id128_equal(arg_boot_id, SD_ID128_NULL))
+ return add_match_this_boot(j, arg_machine);
+
+ r = get_relative_boot_id(j, &arg_boot_id, arg_boot_offset);
+ if (r < 0) {
+ if (sd_id128_equal(arg_boot_id, SD_ID128_NULL))
+ log_error("Failed to look up boot %+i: %s", arg_boot_offset, strerror(-r));
+ else
+ log_error("Failed to look up boot ID "SD_ID128_FORMAT_STR"%+i: %s",
+ SD_ID128_FORMAT_VAL(arg_boot_id), arg_boot_offset, strerror(-r));
+ return r;
+ }
+
+ sd_id128_to_string(arg_boot_id, match + 9);
+
+ r = sd_journal_add_match(j, match, sizeof(match) - 1);
+ if (r < 0) {
+ log_error("Failed to add match: %s", strerror(-r));
+ return r;
+ }
+
+ r = sd_journal_add_conjunction(j);
+ if (r < 0)
+ return r;
+
+ 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) {
+ log_error("Failed to add match: %s", strerror(-r));
+ return r;
+ }
+
+ r = sd_journal_add_conjunction(j);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int get_possible_units(sd_journal *j,
+ const char *fields,
+ char **patterns,
+ Set **units) {
+ _cleanup_set_free_free_ Set *found;
+ const char *field;
+ int r;
+
+ found = set_new(string_hash_func, string_compare_func);
+ if (!found)
+ return log_oom();
+
+ 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 **pattern, *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 log_oom();
+
+ 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 = found;
+ found = NULL;
+ 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"
+
+static int add_units(sd_journal *j) {
+ _cleanup_strv_free_ char **patterns = NULL;
+ int r, count = 0;
+ char **i;
+
+ assert(j);
+
+ STRV_FOREACH(i, arg_system_units) {
+ _cleanup_free_ char *u = NULL;
+
+ u = unit_name_mangle(*i, MANGLE_GLOB);
+ if (!u)
+ return log_oom();
+
+ 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;
+ Iterator it;
+ char *u;
+
+ r = get_possible_units(j, SYSTEM_UNITS, patterns, &units);
+ if (r < 0)
+ return r;
+
+ SET_FOREACH(u, units, it) {
+ r = add_matches_for_unit(j, u);
+ if (r < 0)
+ return r;
+ r = sd_journal_add_disjunction(j);
+ if (r < 0)
+ return r;
+ count ++;
+ }
+ }
+
+ strv_free(patterns);
+ patterns = NULL;
+
+ STRV_FOREACH(i, arg_user_units) {
+ _cleanup_free_ char *u = NULL;
+
+ u = unit_name_mangle(*i, MANGLE_GLOB);
+ if (!u)
+ return log_oom();
+
+ 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;
+ Iterator it;
+ char *u;
+
+ r = get_possible_units(j, USER_UNITS, patterns, &units);
+ if (r < 0)
+ return r;
+
+ SET_FOREACH(u, units, it) {
+ 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_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) {
+ log_error("Failed to add match: %s", strerror(-r));
+ return r;
+ }
+ }
+
+ r = sd_journal_add_conjunction(j);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int setup_keys(void) {
+#ifdef HAVE_GCRYPT
+ size_t mpk_size, seed_size, state_size, i;
+ uint8_t *mpk, *seed, *state;
+ ssize_t l;
+ int fd = -1, r, attr = 0;
+ sd_id128_t machine, boot;
+ char *p = NULL, *k = NULL;
+ struct FSSHeader h;
+ uint64_t n;
+ struct stat st;
+
+ r = stat("/var/log/journal", &st);
+ if (r < 0 && errno != ENOENT && errno != ENOTDIR) {
+ log_error("stat(\"%s\") failed: %m", "/var/log/journal");
+ return -errno;
+ }
+
+ 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) {
+ log_error("Failed to get machine ID: %s", strerror(-r));
+ return r;
+ }
+
+ r = sd_id128_get_boot(&boot);
+ if (r < 0) {
+ log_error("Failed to get boot ID: %s", strerror(-r));
+ return r;
+ }
+
+ if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss",
+ SD_ID128_FORMAT_VAL(machine)) < 0)
+ return log_oom();
+
+ if (access(p, F_OK) >= 0) {
+ if (arg_force) {
+ r = unlink(p);
+ if (r < 0) {
+ log_error("unlink(\"%s\") failed: %m", p);
+ r = -errno;
+ goto finish;
+ }
+ } else {
+ log_error("Sealing key file %s exists already. (--force to recreate)", p);
+ r = -EEXIST;
+ goto finish;
+ }
+ }
+
+ if (asprintf(&k, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss.tmp.XXXXXX",
+ SD_ID128_FORMAT_VAL(machine)) < 0) {
+ r = log_oom();
+ goto finish;
+ }
+
+ mpk_size = FSPRG_mskinbytes(FSPRG_RECOMMENDED_SECPAR);
+ mpk = alloca(mpk_size);
+
+ seed_size = FSPRG_RECOMMENDED_SEEDLEN;
+ seed = alloca(seed_size);
+
+ state_size = FSPRG_stateinbytes(FSPRG_RECOMMENDED_SECPAR);
+ state = alloca(state_size);
+
+ fd = open("/dev/random", O_RDONLY|O_CLOEXEC|O_NOCTTY);
+ if (fd < 0) {
+ log_error("Failed to open /dev/random: %m");
+ r = -errno;
+ goto finish;
+ }
+
+ log_info("Generating seed...");
+ l = loop_read(fd, seed, seed_size, true);
+ if (l < 0 || (size_t) l != seed_size) {
+ log_error("Failed to read random seed: %s", strerror(EIO));
+ r = -EIO;
+ goto finish;
+ }
+
+ 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, O_WRONLY|O_CLOEXEC);
+ if (fd < 0) {
+ log_error("Failed to open %s: %m", k);
+ r = -errno;
+ goto finish;
+ }
+
+ /* Enable secure remove, exclusion from dump, synchronous
+ * writing and in-place updating */
+ if (ioctl(fd, FS_IOC_GETFLAGS, &attr) < 0)
+ log_warning("FS_IOC_GETFLAGS failed: %m");
+
+ attr |= FS_SECRM_FL|FS_NODUMP_FL|FS_SYNC_FL|FS_NOCOW_FL;
+
+ if (ioctl(fd, FS_IOC_SETFLAGS, &attr) < 0)
+ log_warning("FS_IOC_SETFLAGS failed: %m");
+
+ zero(h);
+ memcpy(h.signature, "KSHHRHLP", 8);
+ h.machine_id = machine;
+ h.boot_id = boot;
+ h.header_size = htole64(sizeof(h));
+ h.start_usec = htole64(n * arg_interval);
+ h.interval_usec = htole64(arg_interval);
+ h.fsprg_secpar = htole16(FSPRG_RECOMMENDED_SECPAR);
+ h.fsprg_state_size = htole64(state_size);
+
+ l = loop_write(fd, &h, sizeof(h), false);
+ if (l < 0 || (size_t) l != sizeof(h)) {
+ log_error("Failed to write header: %s", strerror(EIO));
+ r = -EIO;
+ goto finish;
+ }
+
+ l = loop_write(fd, state, state_size, false);
+ if (l < 0 || (size_t) l != state_size) {
+ log_error("Failed to write state: %s", strerror(EIO));
+ r = -EIO;
+ goto finish;
+ }
+
+ if (link(k, p) < 0) {
+ log_error("Failed to link file: %m");
+ r = -errno;
+ goto finish;
+ }
+
+ if (on_tty()) {
+ fprintf(stderr,
+ "\n"
+ "The new key pair has been generated. The " ANSI_HIGHLIGHT_ON "secret sealing key" ANSI_HIGHLIGHT_OFF " has been written to\n"
+ "the following local file. This key file is automatically updated when the\n"
+ "sealing key is advanced. It should not be used on multiple hosts.\n"
+ "\n"
+ "\t%s\n"
+ "\n"
+ "Please write down the following " ANSI_HIGHLIGHT_ON "secret verification key" ANSI_HIGHLIGHT_OFF ". It should be stored\n"
+ "at a safe location and should not be saved locally on disk.\n"
+ "\n\t" ANSI_HIGHLIGHT_RED_ON, p);
+ fflush(stderr);
+ }
+ for (i = 0; i < seed_size; i++) {
+ if (i > 0 && i % 3 == 0)
+ putchar('-');
+ printf("%02x", ((uint8_t*) seed)[i]);
+ }
+
+ printf("/%llx-%llx\n", (unsigned long long) n, (unsigned long long) arg_interval);
+
+ if (on_tty()) {
+ char tsb[FORMAT_TIMESPAN_MAX], *hn;
+
+ fprintf(stderr,
+ ANSI_HIGHLIGHT_OFF "\n"
+ "The sealing key is automatically changed every %s.\n",
+ format_timespan(tsb, sizeof(tsb), arg_interval, 0));
+
+ hn = gethostname_malloc();
+
+ if (hn) {
+ hostname_cleanup(hn, false);
+ fprintf(stderr, "\nThe keys have been generated for host %s/" SD_ID128_FORMAT_STR ".\n", hn, SD_ID128_FORMAT_VAL(machine));
+ } else
+ fprintf(stderr, "\nThe keys have been generated for host " SD_ID128_FORMAT_STR ".\n", SD_ID128_FORMAT_VAL(machine));
+
+#ifdef HAVE_QRENCODE
+ /* If this is not an UTF-8 system don't print any QR codes */
+ if (is_locale_utf8()) {
+ fputs("\nTo transfer the verification key to your phone please scan the QR code below:\n\n", stderr);
+ print_qr_code(stderr, seed, seed_size, n, arg_interval, hn, machine);
+ }
+#endif
+ free(hn);
+ }
+
+ r = 0;
+
+finish:
+ safe_close(fd);
+
+ if (k) {
+ unlink(k);
+ free(k);
+ }
+
+ free(p);
+
+ return r;
+#else
+ log_error("Forward-secure sealing not available.");
+ return -ENOTSUP;
+#endif
+}
+
+static int verify(sd_journal *j) {
+ int r = 0;
+ Iterator i;
+ JournalFile *f;
+
+ assert(j);
+
+ log_show_color(true);
+
+ HASHMAP_FOREACH(f, j->files, i) {
+ int k;
+ usec_t first, validated, last;
+
+#ifdef 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, true);
+ if (k == -EINVAL) {
+ /* If the key was invalid give up right-away. */
+ return k;
+ } else if (k < 0) {
+ log_warning("FAIL: %s (%s)", f->path, strerror(-k));
+ r = k;
+ } else {
+ char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX], c[FORMAT_TIMESPAN_MAX];
+ log_info("PASS: %s", f->path);
+
+ if (arg_verify_key && JOURNAL_HEADER_SEALED(f->header)) {
+ if (validated > 0) {
+ log_info("=> Validated from %s to %s, final %s entries not sealed.",
+ format_timestamp(a, sizeof(a), first),
+ format_timestamp(b, sizeof(b), validated),
+ format_timespan(c, sizeof(c), last > validated ? last - validated : 0, 0));
+ } else if (last > 0)
+ log_info("=> No sealing yet, %s of entries not sealed.",
+ format_timespan(c, sizeof(c), last - first, 0));
+ else
+ log_info("=> No sealing yet, no entries in file.");
+ }
+ }
+ }
+
+ return r;
+}
+
+#ifdef HAVE_ACL
+static int access_check_var_log_journal(sd_journal *j) {
+ _cleanup_strv_free_ char **g = NULL;
+ bool have_access;
+ int r;
+
+ assert(j);
+
+ have_access = in_group("systemd-journal") > 0;
+
+ if (!have_access) {
+ /* Let's enumerate all groups from the default ACL of
+ * the directory, which generally should allow access
+ * to most journal files too */
+ r = search_acl_groups(&g, "/var/log/journal/", &have_access);
+ if (r < 0)
+ return r;
+ }
+
+ if (!have_access) {
+
+ if (strv_isempty(g))
+ log_notice("Hint: You are currently not seeing messages from other users and the system.\n"
+ " Users in the 'systemd-journal' group can see all messages. Pass -q to\n"
+ " turn off this notice.");
+ else {
+ _cleanup_free_ char *s = NULL;
+
+ r = strv_extend(&g, "systemd-journal");
+ if (r < 0)
+ return log_oom();
+
+ strv_sort(g);
+ strv_uniq(g);
+
+ s = strv_join(g, "', '");
+ if (!s)
+ return log_oom();
+
+ log_notice("Hint: You are currently not seeing messages from other users and the system.\n"
+ " Users in the groups '%s' can see all messages.\n"
+ " Pass -q to turn off this notice.", s);
+ }
+ }
+
+ return 0;
+}
+#endif
+
+static int access_check(sd_journal *j) {
+ Iterator it;
+ void *code;
+ int r = 0;
+
+ assert(j);
+
+ if (set_isempty(j->errors)) {
+ if (hashmap_isempty(j->files))
+ log_notice("No journal files were found.");
+ return 0;
+ }
+
+ if (set_contains(j->errors, INT_TO_PTR(-EACCES))) {
+#ifdef HAVE_ACL
+ /* If /var/log/journal doesn't even exist,
+ * unprivileged users have no access at all */
+ if (access("/var/log/journal", F_OK) < 0 &&
+ geteuid() != 0 &&
+ in_group("systemd-journal") <= 0) {
+ log_error("Unprivileged users cannot access messages, unless persistent log storage is\n"
+ "enabled. Users in the 'systemd-journal' group may always access messages.");
+ return -EACCES;
+ }
+
+ /* If /var/log/journal exists, try to pring a nice
+ notice if the user lacks access to it */
+ if (!arg_quiet && geteuid() != 0) {
+ r = access_check_var_log_journal(j);
+ if (r < 0)
+ return r;
+ }
+#else
+ if (geteuid() != 0 && in_group("systemd-journal") <= 0) {
+ log_error("Unprivileged users cannot access messages. Users in the 'systemd-journal' group\n"
+ "group may access messages.");
+ return -EACCES;
+ }
+#endif
+
+ if (hashmap_isempty(j->files)) {
+ log_error("No journal files were opened due to insufficient permissions.");
+ r = -EACCES;
+ }
+ }
+
+ SET_FOREACH(code, j->errors, it) {
+ int err;
+
+ err = -PTR_TO_INT(code);
+ assert(err > 0);
+
+ if (err != EACCES)
+ log_warning("Error was encountered while opening journal files: %s",
+ strerror(err));
+ }
+
+ return r;
+}
+
+int main(int argc, char *argv[]) {
+ int r;
+ _cleanup_journal_close_ sd_journal *j = NULL;
+ bool need_seek = false;
+ sd_id128_t previous_boot_id;
+ bool previous_boot_id_valid = false, first_line = true;
+ int n_shown = 0;
+ bool ellipsized = false;
+
+ setlocale(LC_ALL, "");
+ log_parse_environment();
+ log_open();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)