summaryrefslogtreecommitdiff
path: root/src/grp-journal/journalctl
diff options
context:
space:
mode:
Diffstat (limited to 'src/grp-journal/journalctl')
l---------src/grp-journal/journalctl/GNUmakefile1
-rw-r--r--src/grp-journal/journalctl/Makefile61
-rw-r--r--src/grp-journal/journalctl/journal-qrcode.c135
-rw-r--r--src/grp-journal/journalctl/journal-qrcode.h27
-rw-r--r--src/grp-journal/journalctl/journalctl.c2628
-rw-r--r--src/grp-journal/journalctl/journalctl.completion.bash123
-rw-r--r--src/grp-journal/journalctl/journalctl.completion.zsh123
-rw-r--r--src/grp-journal/journalctl/journalctl.xml929
-rw-r--r--src/grp-journal/journalctl/systemd-journal-catalog-update.service.in21
-rw-r--r--src/grp-journal/journalctl/systemd-journal-flush.service.in22
10 files changed, 4070 insertions, 0 deletions
diff --git a/src/grp-journal/journalctl/GNUmakefile b/src/grp-journal/journalctl/GNUmakefile
new file mode 120000
index 0000000000..95e5924740
--- /dev/null
+++ b/src/grp-journal/journalctl/GNUmakefile
@@ -0,0 +1 @@
+../../../GNUmakefile \ No newline at end of file
diff --git a/src/grp-journal/journalctl/Makefile b/src/grp-journal/journalctl/Makefile
new file mode 100644
index 0000000000..f8519405d2
--- /dev/null
+++ b/src/grp-journal/journalctl/Makefile
@@ -0,0 +1,61 @@
+# -*- Mode: makefile; indent-tabs-mode: t -*-
+#
+# This file is part of systemd.
+#
+# Copyright 2010-2012 Lennart Poettering
+# Copyright 2010-2012 Kay Sievers
+# Copyright 2013 Zbigniew Jędrzejewski-Szmek
+# Copyright 2013 David Strauss
+# Copyright 2016 Luke Shumaker
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# systemd is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with systemd; If not, see <http://www.gnu.org/licenses/>.
+include $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk
+include $(topsrcdir)/build-aux/Makefile.head.mk
+
+# using _CFLAGS = in the conditional below would suppress AM_CFLAGS
+journalctl_CFLAGS = \
+
+journalctl_SOURCES = \
+ src/journal/journalctl.c
+
+journalctl_LDADD = \
+ libsystemd-shared.la \
+ libudev-core.la
+
+ifneq ($(HAVE_QRENCODE),)
+journalctl_SOURCES += \
+ src/journal/journal-qrcode.c \
+ src/journal/journal-qrcode.h
+
+journalctl_CFLAGS += \
+ $(QRENCODE_CFLAGS)
+
+journalctl_LDADD += \
+ $(QRENCODE_LIBS)
+endif # HAVE_QRENCODE
+
+rootbin_PROGRAMS += \
+ journalctl
+
+nodist_systemunit_DATA += \
+ units/systemd-journal-flush.service \
+ units/systemd-journal-catalog-update.service
+SYSINIT_TARGET_WANTS += \
+ systemd-journal-flush.service \
+ systemd-journal-catalog-update.service
+EXTRA_DIST += \
+ units/systemd-journal-flush.service.in \
+ units/systemd-journal-catalog-update.service.in
+
+include $(topsrcdir)/build-aux/Makefile.tail.mk
diff --git a/src/grp-journal/journalctl/journal-qrcode.c b/src/grp-journal/journalctl/journal-qrcode.c
new file mode 100644
index 0000000000..e38730d65c
--- /dev/null
+++ b/src/grp-journal/journalctl/journal-qrcode.c
@@ -0,0 +1,135 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <errno.h>
+#include <qrencode.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "journal-qrcode.h"
+
+#define WHITE_ON_BLACK "\033[40;37;1m"
+#define NORMAL "\033[0m"
+
+static void print_border(FILE *output, unsigned width) {
+ unsigned x, y;
+
+ /* Four rows of border */
+ for (y = 0; y < 4; y += 2) {
+ fputs(WHITE_ON_BLACK, output);
+
+ for (x = 0; x < 4 + width + 4; x++)
+ fputs("\342\226\210", output);
+
+ fputs(NORMAL "\n", output);
+ }
+}
+
+int print_qr_code(
+ FILE *output,
+ const void *seed,
+ size_t seed_size,
+ uint64_t start,
+ uint64_t interval,
+ const char *hn,
+ sd_id128_t machine) {
+
+ FILE *f;
+ char *url = NULL;
+ size_t url_size = 0, i;
+ QRcode* qr;
+ unsigned x, y;
+
+ assert(seed);
+ assert(seed_size > 0);
+
+ f = open_memstream(&url, &url_size);
+ if (!f)
+ return -ENOMEM;
+
+ fputs("fss://", f);
+
+ for (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"?machine=" SD_ID128_FORMAT_STR,
+ start,
+ interval,
+ SD_ID128_FORMAT_VAL(machine));
+
+ if (hn)
+ fprintf(f, ";hostname=%s", hn);
+
+ if (ferror(f)) {
+ fclose(f);
+ free(url);
+ return -ENOMEM;
+ }
+
+ fclose(f);
+
+ qr = QRcode_encodeString(url, 0, QR_ECLEVEL_L, QR_MODE_8, 1);
+ free(url);
+
+ if (!qr)
+ return -ENOMEM;
+
+ print_border(output, qr->width);
+
+ for (y = 0; y < (unsigned) qr->width; y += 2) {
+ const uint8_t *row1, *row2;
+
+ row1 = qr->data + qr->width * y;
+ row2 = row1 + qr->width;
+
+ fputs(WHITE_ON_BLACK, output);
+ for (x = 0; x < 4; x++)
+ fputs("\342\226\210", output);
+
+ for (x = 0; x < (unsigned) qr->width; x ++) {
+ bool a, b;
+
+ a = row1[x] & 1;
+ b = (y+1) < (unsigned) qr->width ? (row2[x] & 1) : false;
+
+ if (a && b)
+ fputc(' ', output);
+ else if (a)
+ fputs("\342\226\204", output);
+ else if (b)
+ fputs("\342\226\200", output);
+ else
+ fputs("\342\226\210", output);
+ }
+
+ for (x = 0; x < 4; x++)
+ fputs("\342\226\210", output);
+ fputs(NORMAL "\n", output);
+ }
+
+ print_border(output, qr->width);
+
+ QRcode_free(qr);
+ return 0;
+}
diff --git a/src/grp-journal/journalctl/journal-qrcode.h b/src/grp-journal/journalctl/journal-qrcode.h
new file mode 100644
index 0000000000..34a779d5be
--- /dev/null
+++ b/src/grp-journal/journalctl/journal-qrcode.h
@@ -0,0 +1,27 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <stdio.h>
+
+#include <systemd/sd-id128.h>
+
+int print_qr_code(FILE *f, const void *seed, size_t seed_size, uint64_t start, uint64_t interval, const char *hn, sd_id128_t machine);
diff --git a/src/grp-journal/journalctl/journalctl.c b/src/grp-journal/journalctl/journalctl.c
new file mode 100644
index 0000000000..9e31283c0e
--- /dev/null
+++ b/src/grp-journal/journalctl/journalctl.c
@@ -0,0 +1,2628 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <getopt.h>
+#include <locale.h>
+#include <poll.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/inotify.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <linux/fs.h>
+
+#include <systemd/sd-bus.h>
+#include <systemd/sd-journal.h>
+
+#include "sd-bus/bus-error.h"
+#include "sd-bus/bus-util.h"
+#include "sd-journal/catalog.h"
+#include "sd-journal/fsprg.h"
+#include "sd-journal/journal-def.h"
+#include "sd-journal/journal-internal.h"
+#include "sd-journal/journal-vacuum.h"
+#include "sd-journal/journal-verify.h"
+#include "systemd-basic/alloc-util.h"
+#include "systemd-basic/chattr-util.h"
+#include "systemd-basic/fd-util.h"
+#include "systemd-basic/fileio.h"
+#include "systemd-basic/fs-util.h"
+#include "systemd-basic/glob-util.h"
+#include "systemd-basic/hostname-util.h"
+#include "systemd-basic/io-util.h"
+#include "systemd-basic/locale-util.h"
+#include "systemd-basic/log.h"
+#include "systemd-basic/mkdir.h"
+#include "systemd-basic/parse-util.h"
+#include "systemd-basic/path-util.h"
+#include "systemd-basic/rlimit-util.h"
+#include "systemd-basic/set.h"
+#include "systemd-basic/sigbus.h"
+#include "systemd-basic/strv.h"
+#include "systemd-basic/syslog-util.h"
+#include "systemd-basic/terminal-util.h"
+#include "systemd-basic/unit-name.h"
+#include "systemd-basic/user-util.h"
+#include "systemd-shared/acl-util.h"
+#include "systemd-shared/logs-show.h"
+#include "systemd-shared/pager.h"
+#include "systemd-shared/udev-util.h"
+#include "udev.h"
+
+#include "journal-qrcode.h"
+
+#define DEFAULT_FSS_INTERVAL_USEC (15*USEC_PER_MINUTE)
+
+enum {
+ /* Special values for arg_lines */
+ ARG_LINES_DEFAULT = -2,
+ ARG_LINES_ALL = -1,
+};
+
+static OutputMode arg_output = OUTPUT_SHORT;
+static bool arg_utc = false;
+static bool arg_pager_end = false;
+static bool arg_follow = false;
+static bool arg_full = true;
+static bool arg_all = false;
+static bool arg_no_pager = false;
+static int arg_lines = ARG_LINES_DEFAULT;
+static bool arg_no_tail = 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_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 const char *arg_verify_key = NULL;
+#ifdef HAVE_GCRYPT
+static usec_t arg_interval = DEFAULT_FSS_INTERVAL_USEC;
+static bool arg_force = false;
+#endif
+static usec_t arg_since, arg_until;
+static bool arg_since_set = false, arg_until_set = false;
+static char **arg_syslog_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 char *arg_root = NULL;
+static const char *arg_machine = NULL;
+static uint64_t arg_vacuum_size = 0;
+static uint64_t arg_vacuum_n_files = 0;
+static usec_t arg_vacuum_time = 0;
+
+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_SYNC,
+ ACTION_ROTATE,
+ ACTION_VACUUM,
+ ACTION_LIST_FIELDS,
+ ACTION_LIST_FIELD_NAMES,
+} arg_action = ACTION_SHOW;
+
+typedef struct BootId {
+ sd_id128_t id;
+ uint64_t first;
+ uint64_t last;
+ LIST_FIELDS(struct BootId, boot_list);
+} BootId;
+
+static int add_matches_for_device(sd_journal *j, const char *devpath) {
+ int r;
+ _cleanup_udev_unref_ struct udev *udev = NULL;
+ _cleanup_udev_device_unref_ struct udev_device *device = NULL;
+ struct udev_device *d = NULL;
+ struct stat st;
+
+ assert(j);
+ assert(devpath);
+
+ if (!path_startswith(devpath, "/dev/")) {
+ log_error("Devpath does not start with /dev/");
+ return -EINVAL;
+ }
+
+ udev = udev_new();
+ if (!udev)
+ return log_oom();
+
+ r = stat(devpath, &st);
+ if (r < 0)
+ log_error_errno(errno, "Couldn't stat file: %m");
+
+ d = device = udev_device_new_from_devnum(udev, S_ISBLK(st.st_mode) ? 'b' : 'c', st.st_rdev);
+ if (!device)
+ return log_error_errno(errno, "Failed to get udev device from devnum %u:%u: %m", major(st.st_rdev), minor(st.st_rdev));
+
+ while (d) {
+ _cleanup_free_ char *match = NULL;
+ const char *subsys, *sysname, *devnode;
+
+ subsys = udev_device_get_subsystem(d);
+ if (!subsys) {
+ d = udev_device_get_parent(d);
+ continue;
+ }
+
+ sysname = udev_device_get_sysname(d);
+ if (!sysname) {
+ d = udev_device_get_parent(d);
+ continue;
+ }
+
+ match = strjoin("_KERNEL_DEVICE=+", subsys, ":", sysname, NULL);
+ 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");
+
+ devnode = udev_device_get_devnode(d);
+ if (devnode) {
+ _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%u:%u", S_ISBLK(st.st_mode) ? 'b' : 'c', major(st.st_rdev), minor(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");
+ }
+
+ d = udev_device_get_parent(d);
+ }
+
+ 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_utc(buf, l, t);
+
+ 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;
+
+ if (strlen(x) >= 32) {
+ char *t;
+
+ t = strndupa(x, 32);
+ r = sd_id128_from_string(t, &id);
+ if (r >= 0)
+ x += 32;
+
+ if (*x != '-' && *x != '+' && *x != 0)
+ return -EINVAL;
+
+ if (*x != 0) {
+ r = safe_atoi(x, &off);
+ if (r < 0)
+ return r;
+ }
+ } else {
+ r = safe_atoi(x, &off);
+ if (r < 0)
+ return r;
+ }
+
+ if (boot_id)
+ *boot_id = id;
+
+ if (offset)
+ *offset = off;
+
+ return 0;
+}
+
+static void help(void) {
+
+ pager_open(arg_no_pager, arg_pager_end);
+
+ printf("%s [OPTIONS...] [MATCHES...]\n\n"
+ "Query the journal.\n\n"
+ "Options:\n"
+ " --system Show the system journal\n"
+ " --user Show the user journal for the current user\n"
+ " -M --machine=CONTAINER Operate on local container\n"
+ " -S --since=DATE Show entries not older than the specified date\n"
+ " -U --until=DATE Show entries not newer than the specified date\n"
+ " -c --cursor=CURSOR Show entries starting at the specified cursor\n"
+ " --after-cursor=CURSOR Show entries after the specified cursor\n"
+ " --show-cursor Print the cursor after all the entries\n"
+ " -b --boot[=ID] Show current boot or the specified boot\n"
+ " --list-boots Show terse information about recorded boots\n"
+ " -k --dmesg Show kernel message log from the current boot\n"
+ " -u --unit=UNIT Show logs from the specified unit\n"
+ " --user-unit=UNIT Show logs from the specified user unit\n"
+ " -t --identifier=STRING Show entries with the specified syslog identifier\n"
+ " -p --priority=RANGE Show entries with the specified priority\n"
+ " -e --pager-end Immediately jump to the end in the pager\n"
+ " -f --follow Follow the journal\n"
+ " -n --lines[=INTEGER] Number of journal entries to show\n"
+ " --no-tail Show all lines, even in follow mode\n"
+ " -r --reverse Show the newest entries first\n"
+ " -o --output=STRING Change journal output mode (short, short-precise,\n"
+ " short-iso, short-full, short-monotonic, short-unix,\n"
+ " verbose, export, json, json-pretty, json-sse, cat)\n"
+ " --utc Express time in Coordinated Universal Time (UTC)\n"
+ " -x --catalog Add message explanations where available\n"
+ " --no-full Ellipsize fields\n"
+ " -a --all Show all fields, including long and unprintable\n"
+ " -q --quiet Do not show info messages and privilege warning\n"
+ " --no-pager Do not pipe output into a pager\n"
+ " --no-hostname Suppress output of hostname field\n"
+ " -m --merge Show entries from all available journals\n"
+ " -D --directory=PATH Show journal files from directory\n"
+ " --file=PATH Show journal file\n"
+ " --root=ROOT Operate on files below a root directory\n"
+#ifdef HAVE_GCRYPT
+ " --interval=TIME Time interval for changing the FSS sealing key\n"
+ " --verify-key=KEY Specify FSS verification key\n"
+ " --force Override of the FSS key pair with --setup-keys\n"
+#endif
+ "\nCommands:\n"
+ " -h --help Show this help text\n"
+ " --version Show package version\n"
+ " -N --fields List all field names currently used\n"
+ " -F --field=FIELD List all values that a specified field takes\n"
+ " --disk-usage Show total disk usage of all journal files\n"
+ " --vacuum-size=BYTES Reduce disk usage below specified size\n"
+ " --vacuum-files=INT Leave only the specified number of journal files\n"
+ " --vacuum-time=TIME Remove journal files older than specified time\n"
+ " --verify Verify journal file consistency\n"
+ " --sync Synchronize unwritten journal messages to disk\n"
+ " --flush Flush all journal data from /run into /var\n"
+ " --rotate Request immediate rotation of the journal files\n"
+ " --header Show journal header information\n"
+ " --list-catalog Show all message IDs in the catalog\n"
+ " --dump-catalog Show entries in the message catalog\n"
+ " --update-catalog Update the message catalog database\n"
+ " --new-id128 Generate a new 128-bit ID\n"
+#ifdef HAVE_GCRYPT
+ " --setup-keys Generate a new FSS key pair\n"
+#endif
+ , program_invocation_short_name);
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_NO_PAGER,
+ ARG_NO_FULL,
+ ARG_NO_TAIL,
+ ARG_NEW_ID128,
+ ARG_THIS_BOOT,
+ ARG_LIST_BOOTS,
+ ARG_USER,
+ ARG_SYSTEM,
+ ARG_ROOT,
+ ARG_HEADER,
+ ARG_SETUP_KEYS,
+ ARG_FILE,
+ ARG_INTERVAL,
+ ARG_VERIFY,
+ ARG_VERIFY_KEY,
+ ARG_DISK_USAGE,
+ ARG_AFTER_CURSOR,
+ ARG_SHOW_CURSOR,
+ ARG_USER_UNIT,
+ ARG_LIST_CATALOG,
+ ARG_DUMP_CATALOG,
+ ARG_UPDATE_CATALOG,
+ ARG_FORCE,
+ ARG_UTC,
+ ARG_SYNC,
+ ARG_FLUSH,
+ ARG_ROTATE,
+ ARG_VACUUM_SIZE,
+ ARG_VACUUM_FILES,
+ ARG_VACUUM_TIME,
+ ARG_NO_HOSTNAME,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version" , no_argument, NULL, ARG_VERSION },
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER },
+ { "pager-end", no_argument, NULL, 'e' },
+ { "follow", no_argument, NULL, 'f' },
+ { "force", no_argument, NULL, ARG_FORCE },
+ { "output", required_argument, NULL, 'o' },
+ { "all", no_argument, NULL, 'a' },
+ { "full", no_argument, NULL, 'l' },
+ { "no-full", no_argument, NULL, ARG_NO_FULL },
+ { "lines", optional_argument, NULL, 'n' },
+ { "no-tail", no_argument, NULL, ARG_NO_TAIL },
+ { "new-id128", no_argument, NULL, ARG_NEW_ID128 },
+ { "quiet", no_argument, NULL, 'q' },
+ { "merge", no_argument, NULL, 'm' },
+ { "this-boot", no_argument, NULL, ARG_THIS_BOOT }, /* deprecated */
+ { "boot", optional_argument, NULL, 'b' },
+ { "list-boots", no_argument, NULL, ARG_LIST_BOOTS },
+ { "dmesg", no_argument, NULL, 'k' },
+ { "system", no_argument, NULL, ARG_SYSTEM },
+ { "user", no_argument, NULL, ARG_USER },
+ { "directory", required_argument, NULL, 'D' },
+ { "file", required_argument, NULL, ARG_FILE },
+ { "root", required_argument, NULL, ARG_ROOT },
+ { "header", no_argument, NULL, ARG_HEADER },
+ { "identifier", required_argument, NULL, 't' },
+ { "priority", required_argument, NULL, 'p' },
+ { "setup-keys", no_argument, NULL, ARG_SETUP_KEYS },
+ { "interval", required_argument, NULL, ARG_INTERVAL },
+ { "verify", no_argument, NULL, ARG_VERIFY },
+ { "verify-key", required_argument, NULL, ARG_VERIFY_KEY },
+ { "disk-usage", no_argument, NULL, ARG_DISK_USAGE },
+ { "cursor", required_argument, NULL, 'c' },
+ { "after-cursor", required_argument, NULL, ARG_AFTER_CURSOR },
+ { "show-cursor", no_argument, NULL, ARG_SHOW_CURSOR },
+ { "since", required_argument, NULL, 'S' },
+ { "until", required_argument, NULL, 'U' },
+ { "unit", required_argument, NULL, 'u' },
+ { "user-unit", required_argument, NULL, ARG_USER_UNIT },
+ { "field", required_argument, NULL, 'F' },
+ { "fields", no_argument, NULL, 'N' },
+ { "catalog", no_argument, NULL, 'x' },
+ { "list-catalog", no_argument, NULL, ARG_LIST_CATALOG },
+ { "dump-catalog", no_argument, NULL, ARG_DUMP_CATALOG },
+ { "update-catalog", no_argument, NULL, ARG_UPDATE_CATALOG },
+ { "reverse", no_argument, NULL, 'r' },
+ { "machine", required_argument, NULL, 'M' },
+ { "utc", no_argument, NULL, ARG_UTC },
+ { "flush", no_argument, NULL, ARG_FLUSH },
+ { "sync", no_argument, NULL, ARG_SYNC },
+ { "rotate", no_argument, NULL, ARG_ROTATE },
+ { "vacuum-size", required_argument, NULL, ARG_VACUUM_SIZE },
+ { "vacuum-files", required_argument, NULL, ARG_VACUUM_FILES },
+ { "vacuum-time", required_argument, NULL, ARG_VACUUM_TIME },
+ { "no-hostname", no_argument, NULL, ARG_NO_HOSTNAME },
+ {}
+ };
+
+ int c, r;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:c:S:U:t:u:NF:xrM:", options, NULL)) >= 0)
+
+ switch (c) {
+
+ case 'h':
+ help();
+ return 0;
+
+ case ARG_VERSION:
+ return version();
+
+ case ARG_NO_PAGER:
+ arg_no_pager = true;
+ break;
+
+ case 'e':
+ arg_pager_end = true;
+
+ if (arg_lines == ARG_LINES_DEFAULT)
+ arg_lines = 1000;
+
+ break;
+
+ case 'f':
+ arg_follow = true;
+ break;
+
+ case 'o':
+ arg_output = output_mode_from_string(optarg);
+ if (arg_output < 0) {
+ log_error("Unknown output format '%s'.", optarg);
+ return -EINVAL;
+ }
+
+ if (arg_output == OUTPUT_EXPORT ||
+ arg_output == OUTPUT_JSON ||
+ arg_output == OUTPUT_JSON_PRETTY ||
+ arg_output == OUTPUT_JSON_SSE ||
+ arg_output == OUTPUT_CAT)
+ arg_quiet = true;
+
+ break;
+
+ case 'l':
+ arg_full = true;
+ break;
+
+ case ARG_NO_FULL:
+ arg_full = false;
+ break;
+
+ case 'a':
+ arg_all = true;
+ break;
+
+ case 'n':
+ if (optarg) {
+ if (streq(optarg, "all"))
+ arg_lines = ARG_LINES_ALL;
+ else {
+ r = safe_atoi(optarg, &arg_lines);
+ if (r < 0 || arg_lines < 0) {
+ log_error("Failed to parse lines '%s'", optarg);
+ return -EINVAL;
+ }
+ }
+ } else {
+ arg_lines = 10;
+
+ /* Hmm, no argument? Maybe the next
+ * word on the command line is
+ * supposed to be the argument? Let's
+ * see if there is one, and is
+ * parsable. */
+ if (optind < argc) {
+ int n;
+ if (streq(argv[optind], "all")) {
+ arg_lines = ARG_LINES_ALL;
+ optind++;
+ } else if (safe_atoi(argv[optind], &n) >= 0 && n >= 0) {
+ arg_lines = n;
+ optind++;
+ }
+ }
+ }
+
+ break;
+
+ case ARG_NO_TAIL:
+ arg_no_tail = true;
+ break;
+
+ case ARG_NEW_ID128:
+ arg_action = ACTION_NEW_ID128;
+ break;
+
+ case 'q':
+ arg_quiet = true;
+ break;
+
+ case 'm':
+ arg_merge = true;
+ break;
+
+ case ARG_THIS_BOOT:
+ arg_boot = true;
+ break;
+
+ case 'b':
+ arg_boot = true;
+
+ if (optarg) {
+ r = parse_boot_descriptor(optarg, &arg_boot_id, &arg_boot_offset);
+ if (r < 0) {
+ log_error("Failed to parse boot descriptor '%s'", optarg);
+ return -EINVAL;
+ }
+ } else {
+
+ /* Hmm, no argument? Maybe the next
+ * word on the command line is
+ * supposed to be the argument? Let's
+ * see if there is one and is parsable
+ * as a boot descriptor... */
+
+ if (optind < argc &&
+ parse_boot_descriptor(argv[optind], &arg_boot_id, &arg_boot_offset) >= 0)
+ optind++;
+ }
+
+ break;
+
+ case ARG_LIST_BOOTS:
+ arg_action = ACTION_LIST_BOOTS;
+ break;
+
+ case 'k':
+ arg_boot = arg_dmesg = true;
+ break;
+
+ case ARG_SYSTEM:
+ arg_journal_type |= SD_JOURNAL_SYSTEM;
+ break;
+
+ case ARG_USER:
+ arg_journal_type |= SD_JOURNAL_CURRENT_USER;
+ break;
+
+ case 'M':
+ arg_machine = optarg;
+ break;
+
+ case 'D':
+ arg_directory = optarg;
+ break;
+
+ case ARG_FILE:
+ if (streq(optarg, "-"))
+ /* An undocumented feature: we can read journal files from STDIN. We don't document
+ * this though, since after all we only support this for mmap-able, seekable files, and
+ * not for example pipes which are probably the primary usecase for reading things from
+ * STDIN. To avoid confusion we hence don't document this feature. */
+ arg_file_stdin = true;
+ else {
+ r = glob_extend(&arg_file, optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add paths: %m");
+ }
+ break;
+
+ case ARG_ROOT:
+ r = parse_path_argument_and_warn(optarg, true, &arg_root);
+ if (r < 0)
+ return r;
+ break;
+
+ case 'c':
+ arg_cursor = optarg;
+ break;
+
+ case ARG_AFTER_CURSOR:
+ arg_after_cursor = optarg;
+ break;
+
+ case ARG_SHOW_CURSOR:
+ arg_show_cursor = true;
+ break;
+
+ case ARG_HEADER:
+ arg_action = ACTION_PRINT_HEADER;
+ break;
+
+ case ARG_VERIFY:
+ arg_action = ACTION_VERIFY;
+ break;
+
+ case ARG_DISK_USAGE:
+ arg_action = ACTION_DISK_USAGE;
+ break;
+
+ case ARG_VACUUM_SIZE:
+ r = parse_size(optarg, 1024, &arg_vacuum_size);
+ if (r < 0) {
+ log_error("Failed to parse vacuum size: %s", optarg);
+ return r;
+ }
+
+ arg_action = ACTION_VACUUM;
+ break;
+
+ case ARG_VACUUM_FILES:
+ r = safe_atou64(optarg, &arg_vacuum_n_files);
+ if (r < 0) {
+ log_error("Failed to parse vacuum files: %s", optarg);
+ return r;
+ }
+
+ arg_action = ACTION_VACUUM;
+ break;
+
+ case ARG_VACUUM_TIME:
+ r = parse_sec(optarg, &arg_vacuum_time);
+ if (r < 0) {
+ log_error("Failed to parse vacuum time: %s", optarg);
+ return r;
+ }
+
+ arg_action = ACTION_VACUUM;
+ break;
+
+#ifdef HAVE_GCRYPT
+ case ARG_FORCE:
+ arg_force = true;
+ break;
+
+ case ARG_SETUP_KEYS:
+ arg_action = ACTION_SETUP_KEYS;
+ break;
+
+
+ case ARG_VERIFY_KEY:
+ arg_action = ACTION_VERIFY;
+ arg_verify_key = optarg;
+ arg_merge = false;
+ break;
+
+ case ARG_INTERVAL:
+ r = parse_sec(optarg, &arg_interval);
+ if (r < 0 || arg_interval <= 0) {
+ log_error("Failed to parse sealing key change interval: %s", optarg);
+ return -EINVAL;
+ }
+ break;
+#else
+ case ARG_SETUP_KEYS:
+ case ARG_VERIFY_KEY:
+ case ARG_INTERVAL:
+ case ARG_FORCE:
+ log_error("Forward-secure sealing not available.");
+ return -EOPNOTSUPP;
+#endif
+
+ case 'p': {
+ const char *dots;
+
+ dots = strstr(optarg, "..");
+ if (dots) {
+ char *a;
+ int from, to, i;
+
+ /* a range */
+ a = strndup(optarg, dots - optarg);
+ if (!a)
+ return log_oom();
+
+ from = log_level_from_string(a);
+ to = log_level_from_string(dots + 2);
+ free(a);
+
+ if (from < 0 || to < 0) {
+ log_error("Failed to parse log level range %s", optarg);
+ return -EINVAL;
+ }
+
+ arg_priorities = 0;
+
+ if (from < to) {
+ for (i = from; i <= to; i++)
+ arg_priorities |= 1 << i;
+ } else {
+ for (i = to; i <= from; i++)
+ arg_priorities |= 1 << i;
+ }
+
+ } else {
+ int p, i;
+
+ p = log_level_from_string(optarg);
+ if (p < 0) {
+ log_error("Unknown log level %s", optarg);
+ return -EINVAL;
+ }
+
+ arg_priorities = 0;
+
+ for (i = 0; i <= p; i++)
+ arg_priorities |= 1 << i;
+ }
+
+ break;
+ }
+
+ case 'S':
+ r = parse_timestamp(optarg, &arg_since);
+ if (r < 0) {
+ log_error("Failed to parse timestamp: %s", optarg);
+ return -EINVAL;
+ }
+ arg_since_set = true;
+ break;
+
+ case 'U':
+ r = parse_timestamp(optarg, &arg_until);
+ if (r < 0) {
+ log_error("Failed to parse timestamp: %s", optarg);
+ return -EINVAL;
+ }
+ arg_until_set = true;
+ break;
+
+ case 't':
+ r = strv_extend(&arg_syslog_identifier, optarg);
+ if (r < 0)
+ return log_oom();
+ break;
+
+ case 'u':
+ r = strv_extend(&arg_system_units, optarg);
+ if (r < 0)
+ return log_oom();
+ break;
+
+ case ARG_USER_UNIT:
+ r = strv_extend(&arg_user_units, optarg);
+ if (r < 0)
+ return log_oom();
+ break;
+
+ case 'F':
+ arg_action = ACTION_LIST_FIELDS;
+ arg_field = optarg;
+ break;
+
+ case 'N':
+ arg_action = ACTION_LIST_FIELD_NAMES;
+ break;
+
+ case ARG_NO_HOSTNAME:
+ arg_no_hostname = true;
+ break;
+
+ case 'x':
+ arg_catalog = true;
+ break;
+
+ case ARG_LIST_CATALOG:
+ arg_action = ACTION_LIST_CATALOG;
+ break;
+
+ case ARG_DUMP_CATALOG:
+ arg_action = ACTION_DUMP_CATALOG;
+ break;
+
+ case ARG_UPDATE_CATALOG:
+ arg_action = ACTION_UPDATE_CATALOG;
+ break;
+
+ case 'r':
+ arg_reverse = true;
+ break;
+
+ case ARG_UTC:
+ arg_utc = true;
+ break;
+
+ case ARG_FLUSH:
+ arg_action = ACTION_FLUSH;
+ break;
+
+ case ARG_ROTATE:
+ arg_action = ACTION_ROTATE;
+ break;
+
+ case ARG_SYNC:
+ arg_action = ACTION_SYNC;
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached("Unhandled option");
+ }
+
+ if (arg_follow && !arg_no_tail && !arg_since && arg_lines == ARG_LINES_DEFAULT)
+ arg_lines = 10;
+
+ if (!!arg_directory + !!arg_file + !!arg_machine + !!arg_root > 1) {
+ log_error("Please specify at most one of -D/--directory=, --file=, -M/--machine=, --root.");
+ return -EINVAL;
+ }
+
+ if (arg_since_set && arg_until_set && arg_since > arg_until) {
+ log_error("--since= must be before --until=.");
+ return -EINVAL;
+ }
+
+ if (!!arg_cursor + !!arg_after_cursor + !!arg_since_set > 1) {
+ log_error("Please specify only one of --since=, --cursor=, and --after-cursor.");
+ return -EINVAL;
+ }
+
+ if (arg_follow && arg_reverse) {
+ log_error("Please specify either --reverse= or --follow=, not both.");
+ return -EINVAL;
+ }
+
+ if (!IN_SET(arg_action, ACTION_SHOW, ACTION_DUMP_CATALOG, ACTION_LIST_CATALOG) && optind < argc) {
+ log_error("Extraneous arguments starting with '%s'", argv[optind]);
+ return -EINVAL;
+ }
+
+ if ((arg_boot || arg_action == ACTION_LIST_BOOTS) && arg_merge) {
+ log_error("Using --boot or --list-boots with --merge is not supported.");
+ return -EINVAL;
+ }
+
+ if (!strv_isempty(arg_system_units) && (arg_journal_type == SD_JOURNAL_CURRENT_USER)) {
+
+ /* Specifying --user and --unit= at the same time makes no sense (as the former excludes the user
+ * journal, but the latter excludes the system journal, thus resulting in empty output). Let's be nice
+ * to users, and automatically turn --unit= into --user-unit= if combined with --user. */
+ r = strv_extend_strv(&arg_user_units, arg_system_units, true);
+ if (r < 0)
+ return -ENOMEM;
+
+ arg_system_units = strv_free(arg_system_units);
+ }
+
+ return 1;
+}
+
+static int generate_new_id128(void) {
+ sd_id128_t id;
+ int r;
+ unsigned i;
+
+ r = sd_id128_randomize(&id);
+ if (r < 0)
+ return log_error_errno(r, "Failed to generate ID: %m");
+
+ printf("As string:\n"
+ SD_ID128_FORMAT_STR "\n\n"
+ "As UUID:\n"
+ "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n\n"
+ "As macro:\n"
+ "#define MESSAGE_XYZ SD_ID128_MAKE(",
+ SD_ID128_FORMAT_VAL(id),
+ SD_ID128_FORMAT_VAL(id));
+ for (i = 0; i < 16; i++)
+ printf("%02x%s", id.bytes[i], i != 15 ? "," : "");
+ fputs(")\n\n", stdout);
+
+ printf("As Python constant:\n"
+ ">>> import uuid\n"
+ ">>> MESSAGE_XYZ = uuid.UUID('" SD_ID128_FORMAT_STR "')\n",
+ SD_ID128_FORMAT_VAL(id));
+
+ return 0;
+}
+
+static int add_matches(sd_journal *j, char **args) {
+ char **i;
+ 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, *t = NULL, *t2 = NULL, *interpreter = NULL;
+ const char *path;
+ struct stat st;
+
+ p = canonicalize_file_name(*i);
+ path = p ?: *i;
+
+ if (lstat(path, &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(path, &interpreter) > 0) {
+ _cleanup_free_ char *comm;
+
+ comm = strndup(basename(path), 15);
+ if (!comm)
+ return log_oom();
+
+ t = strappend("_COMM=", comm);
+ 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 = strappend("_EXE=", interpreter);
+ if (!t2)
+ return log_oom();
+ }
+ } else {
+ t = strappend("_EXE=", path);
+ 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, path);
+ if (r < 0)
+ return r;
+ } else {
+ log_error("File is neither a device node, nor regular file, nor executable: %s", *i);
+ return -EINVAL;
+ }
+
+ 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) {
+ log_error("\"+\" can only be used between terms");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void boot_id_free_all(BootId *l) {
+
+ while (l) {
+ BootId *i = l;
+ LIST_REMOVE(boot_list, l, i);
+ free(i);
+ }
+}
+
+static int discover_next_boot(sd_journal *j,
+ sd_id128_t previous_boot_id,
+ bool advance_older,
+ BootId **ret) {
+
+ _cleanup_free_ BootId *next_boot = NULL;
+ char match[9+32+1] = "_BOOT_ID=";
+ sd_id128_t boot_id;
+ int r;
+
+ assert(j);
+ assert(ret);
+
+ /* We expect the journal to be on the last position of a boot
+ * (in relation to the direction we are going), so that the next
+ * invocation of sd_journal_next/previous will be from a different
+ * boot. We then collect any information we desire and then jump
+ * to the last location of the new boot by using a _BOOT_ID match
+ * coming from the other journal direction. */
+
+ /* Make sure we aren't restricted by any _BOOT_ID matches, so that
+ * we can actually advance to a *different* boot. */
+ sd_journal_flush_matches(j);
+
+ do {
+ if (advance_older)
+ r = sd_journal_previous(j);
+ else
+ r = sd_journal_next(j);
+ if (r < 0)
+ return r;
+ else if (r == 0)
+ return 0; /* End of journal, yay. */
+
+ r = sd_journal_get_monotonic_usec(j, NULL, &boot_id);
+ if (r < 0)
+ return r;
+
+ /* We iterate through this in a loop, until the boot ID differs from the previous one. Note that
+ * normally, this will only require a single iteration, as we seeked to the last entry of the previous
+ * boot entry already. However, it might happen that the per-journal-field entry arrays are less
+ * complete than the main entry array, and hence might reference an entry that's not actually the last
+ * one of the boot ID as last one. Let's hence use the per-field array is initial seek position to
+ * speed things up, but let's not trust that it is complete, and hence, manually advance as
+ * necessary. */
+
+ } while (sd_id128_equal(boot_id, previous_boot_id));
+
+ next_boot = new0(BootId, 1);
+ if (!next_boot)
+ return -ENOMEM;
+
+ next_boot->id = boot_id;
+
+ r = sd_journal_get_realtime_usec(j, &next_boot->first);
+ if (r < 0)
+ return r;
+
+ /* Now seek to the last occurrence of this boot ID. */
+ sd_id128_to_string(next_boot->id, match + 9);
+ r = sd_journal_add_match(j, match, sizeof(match) - 1);
+ if (r < 0)
+ return r;
+
+ if (advance_older)
+ r = sd_journal_seek_head(j);
+ else
+ r = sd_journal_seek_tail(j);
+ if (r < 0)
+ return r;
+
+ if (advance_older)
+ r = sd_journal_next(j);
+ else
+ r = sd_journal_previous(j);
+ if (r < 0)
+ return r;
+ else if (r == 0) {
+ log_debug("Whoopsie! We found a boot ID but can't read its last entry.");
+ return -ENODATA; /* This shouldn't happen. We just came from this very boot ID. */
+ }
+
+ r = sd_journal_get_realtime_usec(j, &next_boot->last);
+ if (r < 0)
+ return r;
+
+ *ret = next_boot;
+ next_boot = NULL;
+
+ return 0;
+}
+
+static int get_boots(
+ sd_journal *j,
+ BootId **boots,
+ sd_id128_t *boot_id,
+ int offset) {
+
+ bool skip_once;
+ int r, count = 0;
+ BootId *head = NULL, *tail = NULL, *id;
+ const bool advance_older = boot_id && offset <= 0;
+ sd_id128_t previous_boot_id;
+
+ assert(j);
+
+ /* Adjust for the asymmetry that offset 0 is
+ * the last (and current) boot, while 1 is considered the
+ * (chronological) first boot in the journal. */
+ skip_once = boot_id && sd_id128_is_null(*boot_id) && offset <= 0;
+
+ /* Advance to the earliest/latest occurrence of our reference
+ * boot ID (taking our lookup direction into account), so that
+ * discover_next_boot() can do its job.
+ * If no reference is given, the journal head/tail will do,
+ * they're "virtual" boots after all. */
+ if (boot_id && !sd_id128_is_null(*boot_id)) {
+ char match[9+32+1] = "_BOOT_ID=";
+
+ sd_journal_flush_matches(j);
+
+ sd_id128_to_string(*boot_id, match + 9);
+ r = sd_journal_add_match(j, match, sizeof(match) - 1);
+ if (r < 0)
+ return r;
+
+ if (advance_older)
+ r = sd_journal_seek_head(j); /* seek to oldest */
+ else
+ r = sd_journal_seek_tail(j); /* seek to newest */
+ if (r < 0)
+ return r;
+
+ if (advance_older)
+ r = sd_journal_next(j); /* read the oldest entry */
+ else
+ r = sd_journal_previous(j); /* read the most recently added entry */
+ if (r < 0)
+ return r;
+ else if (r == 0)
+ goto finish;
+ else if (offset == 0) {
+ count = 1;
+ goto finish;
+ }
+
+ /* At this point the read pointer is positioned at the oldest/newest occurence of the reference boot
+ * ID. After flushing the matches, one more invocation of _previous()/_next() will hence place us at
+ * the following entry, which must then have an older/newer boot ID */
+ } else {
+
+ if (advance_older)
+ r = sd_journal_seek_tail(j); /* seek to newest */
+ else
+ r = sd_journal_seek_head(j); /* seek to oldest */
+ if (r < 0)
+ return r;
+
+ /* No sd_journal_next()/_previous() here.
+ *
+ * At this point the read pointer is positioned after the newest/before the oldest entry in the whole
+ * journal. The next invocation of _previous()/_next() will hence position us at the newest/oldest
+ * entry we have. */
+ }
+
+ previous_boot_id = SD_ID128_NULL;
+ for (;;) {
+ _cleanup_free_ BootId *current = NULL;
+
+ r = discover_next_boot(j, previous_boot_id, advance_older, &current);
+ if (r < 0) {
+ boot_id_free_all(head);
+ return r;
+ }
+
+ if (!current)
+ break;
+
+ previous_boot_id = current->id;
+
+ if (boot_id) {
+ if (!skip_once)
+ offset += advance_older ? 1 : -1;
+ skip_once = false;
+
+ if (offset == 0) {
+ count = 1;
+ *boot_id = current->id;
+ break;
+ }
+ } else {
+ LIST_FOREACH(boot_list, id, head) {
+ if (sd_id128_equal(id->id, current->id)) {
+ /* boot id already stored, something wrong with the journal files */
+ /* exiting as otherwise this problem would cause forever loop */
+ goto finish;
+ }
+ }
+ LIST_INSERT_AFTER(boot_list, head, tail, current);
+ tail = current;
+ current = NULL;
+ count++;
+ }
+ }
+
+finish:
+ if (boots)
+ *boots = head;
+
+ sd_journal_flush_matches(j);
+
+ return count;
+}
+
+static int list_boots(sd_journal *j) {
+ int w, i, count;
+ BootId *id, *all_ids;
+
+ assert(j);
+
+ count = get_boots(j, &all_ids, NULL, 0);
+ if (count < 0)
+ return log_error_errno(count, "Failed to determine boots: %m");
+ if (count == 0)
+ return count;
+
+ pager_open(arg_no_pager, arg_pager_end);
+
+ /* numbers are one less, but we need an extra char for the sign */
+ w = DECIMAL_STR_WIDTH(count - 1) + 1;
+
+ i = 0;
+ LIST_FOREACH(boot_list, id, all_ids) {
+ char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX];
+
+ printf("% *i " SD_ID128_FORMAT_STR " %s—%s\n",
+ w, i - count + 1,
+ SD_ID128_FORMAT_VAL(id->id),
+ format_timestamp_maybe_utc(a, sizeof(a), id->first),
+ format_timestamp_maybe_utc(b, sizeof(b), id->last));
+ i++;
+ }
+
+ boot_id_free_all(all_ids);
+
+ return 0;
+}
+
+static int add_boot(sd_journal *j) {
+ char match[9+32+1] = "_BOOT_ID=";
+ sd_id128_t boot_id;
+ 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);
+
+ boot_id = arg_boot_id;
+ r = get_boots(j, NULL, &boot_id, arg_boot_offset);
+ assert(r <= 1);
+ if (r <= 0) {
+ const char *reason = (r == 0) ? "No such boot ID in journal" : strerror(-r);
+
+ if (sd_id128_is_null(arg_boot_id))
+ log_error("Data from the specified boot (%+i) is not available: %s",
+ arg_boot_offset, reason);
+ else
+ log_error("Data from the specified boot ("SD_ID128_FORMAT_STR") is not available: %s",
+ SD_ID128_FORMAT_VAL(arg_boot_id), reason);
+
+ return r == 0 ? -ENODATA : r;
+ }
+
+ sd_id128_to_string(boot_id, match + 9);
+
+ r = sd_journal_add_match(j, match, sizeof(match) - 1);
+ 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;
+ const char *field;
+ 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 **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 -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 = 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;
+
+ r = unit_name_mangle(*i, UNIT_NAME_GLOB, &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;
+ 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++;
+ }
+ }
+
+ patterns = strv_free(patterns);
+
+ STRV_FOREACH(i, arg_user_units) {
+ _cleanup_free_ char *u = NULL;
+
+ r = unit_name_mangle(*i, UNIT_NAME_GLOB, &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;
+ 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)
+ 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_syslog_identifier(sd_journal *j) {
+ int r;
+ char **i;
+
+ assert(j);
+
+ STRV_FOREACH(i, arg_syslog_identifier) {
+ char *u;
+
+ u = strjoina("SYSLOG_IDENTIFIER=", *i);
+ 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 setup_keys(void) {
+#ifdef HAVE_GCRYPT
+ size_t mpk_size, seed_size, state_size, i;
+ uint8_t *mpk, *seed, *state;
+ int fd = -1, r;
+ 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)
+ 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) {
+ r = log_error_errno(errno, "unlink(\"%s\") failed: %m", p);
+ goto finish;
+ }
+ } else if (access(p, F_OK) >= 0) {
+ log_error("Sealing key file %s exists already. Use --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) {
+ r = log_error_errno(errno, "Failed to open /dev/random: %m");
+ goto finish;
+ }
+
+ log_info("Generating seed...");
+ r = loop_read_exact(fd, seed, seed_size, true);
+ if (r < 0) {
+ log_error_errno(r, "Failed to read random seed: %m");
+ 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);
+ if (fd < 0) {
+ r = log_error_errno(fd, "Failed to open %s: %m", k);
+ goto finish;
+ }
+
+ /* Enable secure remove, exclusion from dump, synchronous
+ * writing and in-place updating */
+ r = chattr_fd(fd, FS_SECRM_FL|FS_NODUMP_FL|FS_SYNC_FL|FS_NOCOW_FL, FS_SECRM_FL|FS_NODUMP_FL|FS_SYNC_FL|FS_NOCOW_FL);
+ if (r < 0)
+ log_warning_errno(r, "Failed to set file attributes: %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);
+
+ r = loop_write(fd, &h, sizeof(h), false);
+ if (r < 0) {
+ log_error_errno(r, "Failed to write header: %m");
+ goto finish;
+ }
+
+ r = loop_write(fd, state, state_size, false);
+ if (r < 0) {
+ log_error_errno(r, "Failed to write state: %m");
+ goto finish;
+ }
+
+ if (link(k, p) < 0) {
+ r = log_error_errno(errno, "Failed to link file: %m");
+ goto finish;
+ }
+
+ if (on_tty()) {
+ fprintf(stderr,
+ "\n"
+ "The new key pair has been generated. The %ssecret sealing key%s 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 %ssecret verification key%s. It should be stored\n"
+ "at a safe location and should not be saved locally on disk.\n"
+ "\n\t%s",
+ ansi_highlight(), ansi_normal(),
+ p,
+ ansi_highlight(), ansi_normal(),
+ ansi_highlight_red());
+ 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,
+ "%s\n"
+ "The sealing key is automatically changed every %s.\n",
+ ansi_normal(),
+ format_timespan(tsb, sizeof(tsb), arg_interval, 0));
+
+ hn = gethostname_malloc();
+
+ if (hn) {
+ hostname_cleanup(hn);
+ 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 -EOPNOTSUPP;
+#endif
+}
+
+static int verify(sd_journal *j) {
+ int r = 0;
+ Iterator i;
+ JournalFile *f;
+
+ assert(j);
+
+ log_show_color(true);
+
+ ORDERED_HASHMAP_FOREACH(f, j->files, i) {
+ int k;
+ usec_t first = 0, validated = 0, last = 0;
+
+#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_errno(k, "FAIL: %s (%m)", f->path);
+ 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_maybe_utc(a, sizeof(a), first),
+ format_timestamp_maybe_utc(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;
+}
+
+static int access_check_var_log_journal(sd_journal *j) {
+#ifdef HAVE_ACL
+ _cleanup_strv_free_ char **g = NULL;
+ const char* dir;
+#endif
+ int r;
+
+ assert(j);
+
+ if (arg_quiet)
+ return 0;
+
+ /* If we are root, we should have access, don't warn. */
+ if (getuid() == 0)
+ return 0;
+
+ /* If we are in the 'systemd-journal' group, we should have
+ * access too. */
+ r = in_group("systemd-journal");
+ if (r < 0)
+ return log_error_errno(r, "Failed to check if we are in the 'systemd-journal' group: %m");
+ if (r > 0)
+ return 0;
+
+#ifdef HAVE_ACL
+ if (laccess("/run/log/journal", F_OK) >= 0)
+ dir = "/run/log/journal";
+ else
+ dir = "/var/log/journal";
+
+ /* If we are in any of the groups listed in the journal ACLs,
+ * then all is good, too. Let's enumerate all groups from the
+ * default ACL of the directory, which generally should allow
+ * access to most journal files too. */
+ r = acl_search_groups(dir, &g);
+ if (r < 0)
+ return log_error_errno(r, "Failed to search journal ACL: %m");
+ if (r > 0)
+ return 0;
+
+ /* Print a pretty list, if there were ACLs set. */
+ if (!strv_isempty(g)) {
+ _cleanup_free_ char *s = NULL;
+
+ /* Thre are groups in the ACL, let's list them */
+ 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 groups '%s' can see all messages.\n"
+ " Pass -q to turn off this notice.", s);
+ return 1;
+ }
+#endif
+
+ /* If no ACLs were found, print a short version of the message. */
+ 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.");
+
+ return 1;
+}
+
+static int access_check(sd_journal *j) {
+ Iterator it;
+ void *code;
+ char *path;
+ int r = 0;
+
+ assert(j);
+
+ if (hashmap_isempty(j->errors)) {
+ if (ordered_hashmap_isempty(j->files))
+ log_notice("No journal files were found.");
+
+ return 0;
+ }
+
+ if (hashmap_contains(j->errors, INT_TO_PTR(-EACCES))) {
+ (void) access_check_var_log_journal(j);
+
+ if (ordered_hashmap_isempty(j->files))
+ r = log_error_errno(EACCES, "No journal files were opened due to insufficient permissions.");
+ }
+
+ HASHMAP_FOREACH_KEY(path, code, j->errors, it) {
+ int err;
+
+ err = abs(PTR_TO_INT(code));
+
+ switch (err) {
+ case EACCES:
+ continue;
+
+ case ENODATA:
+ log_warning_errno(err, "Journal file %s is truncated, ignoring file.", path);
+ break;
+
+ case EPROTONOSUPPORT:
+ log_warning_errno(err, "Journal file %s uses an unsupported feature, ignoring file.", path);
+ break;
+
+ case EBADMSG:
+ log_warning_errno(err, "Journal file %s corrupted, ignoring file.", path);
+ break;
+
+ default:
+ log_warning_errno(err, "An error was encountered while opening journal file or directory %s, ignoring file: %m", path);
+ break;
+ }
+ }
+
+ return r;
+}
+
+static int flush_to_var(void) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ _cleanup_close_ int watch_fd = -1;
+ int r;
+
+ if (arg_machine) {
+ log_error("--flush is not supported in conjunction with --machine=.");
+ return -EOPNOTSUPP;
+ }
+
+ /* Quick exit */
+ if (access("/run/systemd/journal/flushed", F_OK) >= 0)
+ return 0;
+
+ /* OK, let's actually do the full logic, send SIGUSR1 to the
+ * daemon and set up inotify to wait for the flushed file to appear */
+ r = bus_connect_system_systemd(&bus);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get D-Bus connection: %m");
+
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "KillUnit",
+ &error,
+ NULL,
+ "ssi", "systemd-journald.service", "main", SIGUSR1);
+ if (r < 0)
+ return log_error_errno(r, "Failed to kill journal service: %s", bus_error_message(&error, r));
+
+ mkdir_p("/run/systemd/journal", 0755);
+
+ watch_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
+ if (watch_fd < 0)
+ return log_error_errno(errno, "Failed to create inotify watch: %m");
+
+ r = inotify_add_watch(watch_fd, "/run/systemd/journal", IN_CREATE|IN_DONT_FOLLOW|IN_ONLYDIR);
+ if (r < 0)
+ return log_error_errno(errno, "Failed to watch journal directory: %m");
+
+ for (;;) {
+ if (access("/run/systemd/journal/flushed", F_OK) >= 0)
+ break;
+
+ if (errno != ENOENT)
+ return log_error_errno(errno, "Failed to check for existence of /run/systemd/journal/flushed: %m");
+
+ r = fd_wait_for_event(watch_fd, POLLIN, USEC_INFINITY);
+ if (r < 0)
+ return log_error_errno(r, "Failed to wait for event: %m");
+
+ r = flush_fd(watch_fd);
+ if (r < 0)
+ return log_error_errno(r, "Failed to flush inotify events: %m");
+ }
+
+ return 0;
+}
+
+static int send_signal_and_wait(int sig, const char *watch_path) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ _cleanup_close_ int watch_fd = -1;
+ usec_t start;
+ int r;
+
+ if (arg_machine) {
+ log_error("--sync and --rotate are not supported in conjunction with --machine=.");
+ return -EOPNOTSUPP;
+ }
+
+ start = now(CLOCK_MONOTONIC);
+
+ /* This call sends the specified signal to journald, and waits
+ * for acknowledgment by watching the mtime of the specified
+ * flag file. This is used to trigger syncing or rotation and
+ * then wait for the operation to complete. */
+
+ for (;;) {
+ usec_t tstamp;
+
+ /* See if a sync happened by now. */
+ r = read_timestamp_file(watch_path, &tstamp);
+ if (r < 0 && r != -ENOENT)
+ return log_error_errno(errno, "Failed to read %s: %m", watch_path);
+ if (r >= 0 && tstamp >= start)
+ return 0;
+
+ /* Let's ask for a sync, but only once. */
+ if (!bus) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+
+ r = bus_connect_system_systemd(&bus);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get D-Bus connection: %m");
+
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "KillUnit",
+ &error,
+ NULL,
+ "ssi", "systemd-journald.service", "main", sig);
+ if (r < 0)
+ return log_error_errno(r, "Failed to kill journal service: %s", bus_error_message(&error, r));
+
+ continue;
+ }
+
+ /* Let's install the inotify watch, if we didn't do that yet. */
+ if (watch_fd < 0) {
+
+ mkdir_p("/run/systemd/journal", 0755);
+
+ watch_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
+ if (watch_fd < 0)
+ return log_error_errno(errno, "Failed to create inotify watch: %m");
+
+ r = inotify_add_watch(watch_fd, "/run/systemd/journal", IN_MOVED_TO|IN_DONT_FOLLOW|IN_ONLYDIR);
+ if (r < 0)
+ return log_error_errno(errno, "Failed to watch journal directory: %m");
+
+ /* Recheck the flag file immediately, so that we don't miss any event since the last check. */
+ continue;
+ }
+
+ /* OK, all preparatory steps done, let's wait until
+ * inotify reports an event. */
+
+ r = fd_wait_for_event(watch_fd, POLLIN, USEC_INFINITY);
+ if (r < 0)
+ return log_error_errno(r, "Failed to wait for event: %m");
+
+ r = flush_fd(watch_fd);
+ if (r < 0)
+ return log_error_errno(r, "Failed to flush inotify events: %m");
+ }
+
+ return 0;
+}
+
+static int rotate(void) {
+ return send_signal_and_wait(SIGUSR2, "/run/systemd/journal/rotated");
+}
+
+static int sync_journal(void) {
+ return send_signal_and_wait(SIGRTMIN+1, "/run/systemd/journal/synced");
+}
+
+int main(int argc, char *argv[]) {
+ int r;
+ _cleanup_(sd_journal_closep) 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)
+ goto finish;
+
+ signal(SIGWINCH, columns_lines_cache_reset);
+ sigbus_install();
+
+ /* Increase max number of open files to 16K if we can, we
+ * might needs this when browsing journal files, which might
+ * be split up into many files. */
+ setrlimit_closest(RLIMIT_NOFILE, &RLIMIT_MAKE_CONST(16384));
+
+ switch (arg_action) {
+
+ case ACTION_NEW_ID128:
+ r = generate_new_id128();
+ goto finish;
+
+ case ACTION_SETUP_KEYS:
+ r = setup_keys();
+ goto finish;
+
+ case ACTION_LIST_CATALOG:
+ case ACTION_DUMP_CATALOG:
+ case ACTION_UPDATE_CATALOG: {
+ _cleanup_free_ char *database;
+
+ database = path_join(arg_root, CATALOG_DATABASE, NULL);
+ if (!database) {
+ r = log_oom();
+ goto finish;
+ }
+
+ if (arg_action == ACTION_UPDATE_CATALOG) {
+ r = catalog_update(database, arg_root, catalog_file_dirs);
+ if (r < 0)
+ log_error_errno(r, "Failed to list catalog: %m");
+ } else {
+ bool oneline = arg_action == ACTION_LIST_CATALOG;
+
+ pager_open(arg_no_pager, arg_pager_end);
+
+ if (optind < argc)
+ r = catalog_list_items(stdout, database, oneline, argv + optind);
+ else
+ r = catalog_list(stdout, database, oneline);
+ if (r < 0)
+ log_error_errno(r, "Failed to list catalog: %m");
+ }
+
+ goto finish;
+ }
+
+ case ACTION_FLUSH:
+ r = flush_to_var();
+ goto finish;
+
+ case ACTION_SYNC:
+ r = sync_journal();
+ goto finish;
+
+ case ACTION_ROTATE:
+ r = rotate();
+ goto finish;
+
+ case ACTION_SHOW:
+ case ACTION_PRINT_HEADER:
+ case ACTION_VERIFY:
+ case ACTION_DISK_USAGE:
+ case ACTION_LIST_BOOTS:
+ case ACTION_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("Unknown action");
+ }
+
+ if (arg_directory)
+ r = sd_journal_open_directory(&j, arg_directory, arg_journal_type);
+ else if (arg_root)
+ r = sd_journal_open_directory(&j, arg_root, arg_journal_type | SD_JOURNAL_OS_ROOT);
+ else if (arg_file_stdin) {
+ int ifd = STDIN_FILENO;
+ r = sd_journal_open_files_fd(&j, &ifd, 1, 0);
+ } else if (arg_file)
+ r = sd_journal_open_files(&j, (const char**) arg_file, 0);
+ else if (arg_machine) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ int fd;
+
+ if (geteuid() != 0) {
+ /* The file descriptor returned by OpenMachineRootDirectory() will be owned by users/groups of
+ * the container, thus we need root privileges to override them. */
+ log_error("Using the --machine= switch requires root privileges.");
+ r = -EPERM;
+ goto finish;
+ }
+
+ r = sd_bus_open_system(&bus);
+ if (r < 0) {
+ log_error_errno(r, "Failed to open system bus: %m");
+ goto finish;
+ }
+
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.machine1",
+ "/org/freedesktop/machine1",
+ "org.freedesktop.machine1.Manager",
+ "OpenMachineRootDirectory",
+ &error,
+ &reply,
+ "s", arg_machine);
+ if (r < 0) {
+ log_error_errno(r, "Failed to open root directory: %s", bus_error_message(&error, r));
+ goto finish;
+ }
+
+ r = sd_bus_message_read(reply, "h", &fd);
+ if (r < 0) {
+ bus_log_parse_error(r);
+ goto finish;
+ }
+
+ fd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
+ if (fd < 0) {
+ r = log_error_errno(errno, "Failed to duplicate file descriptor: %m");
+ goto finish;
+ }
+
+ r = sd_journal_open_directory_fd(&j, fd, SD_JOURNAL_OS_ROOT);
+ if (r < 0)
+ safe_close(fd);
+ } else
+ r = sd_journal_open(&j, !arg_merge*SD_JOURNAL_LOCAL_ONLY + arg_journal_type);
+ if (r < 0) {
+ log_error_errno(r, "Failed to open %s: %m", arg_directory ?: arg_file ? "files" : "journal");
+ goto finish;
+ }
+
+ r = access_check(j);
+ if (r < 0)
+ goto finish;
+
+ 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("Unexpected action.");
+
+ case ACTION_PRINT_HEADER:
+ journal_print_header(j);
+ r = 0;
+ goto finish;
+
+ case ACTION_VERIFY:
+ r = verify(j);
+ goto finish;
+
+ case ACTION_DISK_USAGE: {
+ uint64_t bytes = 0;
+ char sbytes[FORMAT_BYTES_MAX];
+
+ r = sd_journal_get_usage(j, &bytes);
+ if (r < 0)
+ goto finish;
+
+ printf("Archived and active journals take up %s in the file system.\n",
+ format_bytes(sbytes, sizeof(sbytes), bytes));
+ goto finish;
+ }
+
+ case ACTION_LIST_BOOTS:
+ r = list_boots(j);
+ goto finish;
+
+ case ACTION_VACUUM: {
+ Directory *d;
+ Iterator i;
+
+ HASHMAP_FOREACH(d, j->directories_by_path, i) {
+ int q;
+
+ if (d->is_root)
+ continue;
+
+ q = journal_directory_vacuum(d->path, arg_vacuum_size, arg_vacuum_n_files, arg_vacuum_time, NULL, true);
+ if (q < 0) {
+ log_error_errno(q, "Failed to vacuum %s: %m", d->path);
+ r = q;
+ }
+ }
+
+ goto finish;
+ }
+
+ case ACTION_LIST_FIELD_NAMES: {
+ const char *field;
+
+ SD_JOURNAL_FOREACH_FIELD(j, field) {
+ printf("%s\n", field);
+ n_shown++;
+ }
+
+ r = 0;
+ goto finish;
+ }
+
+ case ACTION_SHOW:
+ case ACTION_LIST_FIELDS:
+ break;
+
+ default:
+ assert_not_reached("Unknown action");
+ }
+
+ if (arg_boot_offset != 0 &&
+ sd_journal_has_runtime_files(j) > 0 &&
+ sd_journal_has_persistent_files(j) == 0) {
+ log_info("Specifying boot ID has no effect, no persistent journal was found");
+ r = 0;
+ goto finish;
+ }
+ /* 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)
+ goto finish;
+
+ r = add_dmesg(j);
+ if (r < 0)
+ goto finish;
+
+ r = add_units(j);
+ if (r < 0) {
+ log_error_errno(r, "Failed to add filter for units: %m");
+ goto finish;
+ }
+
+ r = add_syslog_identifier(j);
+ if (r < 0) {
+ log_error_errno(r, "Failed to add filter for syslog identifiers: %m");
+ goto finish;
+ }
+
+ r = add_priorities(j);
+ if (r < 0)
+ goto finish;
+
+ r = add_matches(j, argv + optind);
+ if (r < 0)
+ goto finish;
+
+ if (_unlikely_(log_get_max_level() >= LOG_DEBUG)) {
+ _cleanup_free_ char *filter;
+
+ filter = journal_make_match_string(j);
+ if (!filter)
+ return log_oom();
+
+ log_debug("Journal filter: %s", filter);
+ }
+
+ if (arg_action == ACTION_LIST_FIELDS) {
+ const void *data;
+ size_t size;
+
+ assert(arg_field);
+
+ r = sd_journal_set_data_threshold(j, 0);
+ if (r < 0) {
+ log_error_errno(r, "Failed to unset data size threshold: %m");
+ goto finish;
+ }
+
+ r = sd_journal_query_unique(j, arg_field);
+ if (r < 0) {
+ log_error_errno(r, "Failed to query unique data objects: %m");
+ goto finish;
+ }
+
+ 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++;
+ }
+
+ r = 0;
+ goto finish;
+ }
+
+ /* Opening the fd now means the first sd_journal_wait() will actually wait */
+ if (arg_follow) {
+ r = sd_journal_get_fd(j);
+ if (r == -EMEDIUMTYPE) {
+ log_error_errno(r, "The --follow switch is not supported in conjunction with reading from STDIN.");
+ goto finish;
+ }
+ if (r < 0) {
+ log_error_errno(r, "Failed to get journal fd: %m");
+ goto finish;
+ }
+ }
+
+ if (arg_cursor || arg_after_cursor) {
+ r = sd_journal_seek_cursor(j, arg_cursor ?: arg_after_cursor);
+ if (r < 0) {
+ log_error_errno(r, "Failed to seek to cursor: %m");
+ goto finish;
+ }
+
+ if (!arg_reverse)
+ r = sd_journal_next_skip(j, 1 + !!arg_after_cursor);
+ else
+ r = sd_journal_previous_skip(j, 1 + !!arg_after_cursor);
+
+ if (arg_after_cursor && r < 2) {
+ /* We couldn't find the next entry after the cursor. */
+ if (arg_follow)
+ need_seek = true;
+ else
+ arg_lines = 0;
+ }
+
+ } else if (arg_since_set && !arg_reverse) {
+ r = sd_journal_seek_realtime_usec(j, arg_since);
+ if (r < 0) {
+ log_error_errno(r, "Failed to seek to date: %m");
+ goto finish;
+ }
+ r = sd_journal_next(j);
+
+ } else if (arg_until_set && arg_reverse) {
+ r = sd_journal_seek_realtime_usec(j, arg_until);
+ if (r < 0) {
+ log_error_errno(r, "Failed to seek to date: %m");
+ goto finish;
+ }
+ r = sd_journal_previous(j);
+
+ } else if (arg_lines >= 0) {
+ r = sd_journal_seek_tail(j);
+ if (r < 0) {
+ log_error_errno(r, "Failed to seek to tail: %m");
+ goto finish;
+ }
+
+ r = sd_journal_previous_skip(j, arg_lines);
+
+ } else if (arg_reverse) {
+ r = sd_journal_seek_tail(j);
+ if (r < 0) {
+ log_error_errno(r, "Failed to seek to tail: %m");
+ goto finish;
+ }
+
+ r = sd_journal_previous(j);
+
+ } else {
+ r = sd_journal_seek_head(j);
+ if (r < 0) {
+ log_error_errno(r, "Failed to seek to head: %m");
+ goto finish;
+ }
+
+ r = sd_journal_next(j);
+ }
+
+ if (r < 0) {
+ log_error_errno(r, "Failed to iterate through journal: %m");
+ goto finish;
+ }
+ if (r == 0) {
+ if (arg_follow)
+ need_seek = true;
+ else {
+ if (!arg_quiet)
+ printf("-- No entries --\n");
+ goto finish;
+ }
+ }
+
+ if (!arg_follow)
+ pager_open(arg_no_pager, arg_pager_end);
+
+ if (!arg_quiet) {
+ 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) {
+ log_error_errno(r, "Failed to get cutoff: %m");
+ goto finish;
+ }
+
+ if (r > 0) {
+ if (arg_follow)
+ printf("-- Logs begin at %s. --\n",
+ format_timestamp_maybe_utc(start_buf, sizeof(start_buf), start));
+ else
+ printf("-- Logs begin at %s, end at %s. --\n",
+ format_timestamp_maybe_utc(start_buf, sizeof(start_buf), start),
+ format_timestamp_maybe_utc(end_buf, sizeof(end_buf), end));
+ }
+ }
+
+ for (;;) {
+ while (arg_lines < 0 || n_shown < arg_lines || (arg_follow && !first_line)) {
+ int flags;
+
+ if (need_seek) {
+ if (!arg_reverse)
+ r = sd_journal_next(j);
+ else
+ r = sd_journal_previous(j);
+ if (r < 0) {
+ log_error_errno(r, "Failed to iterate through journal: %m");
+ goto finish;
+ }
+ if (r == 0)
+ break;
+ }
+
+ if (arg_until_set && !arg_reverse) {
+ usec_t usec;
+
+ r = sd_journal_get_realtime_usec(j, &usec);
+ if (r < 0) {
+ log_error_errno(r, "Failed to determine timestamp: %m");
+ goto finish;
+ }
+ if (usec > arg_until)
+ goto finish;
+ }
+
+ if (arg_since_set && arg_reverse) {
+ usec_t usec;
+
+ r = sd_journal_get_realtime_usec(j, &usec);
+ if (r < 0) {
+ log_error_errno(r, "Failed to determine timestamp: %m");
+ goto finish;
+ }
+ if (usec < arg_since)
+ goto finish;
+ }
+
+ if (!arg_merge && !arg_quiet) {
+ sd_id128_t boot_id;
+
+ r = sd_journal_get_monotonic_usec(j, NULL, &boot_id);
+ if (r >= 0) {
+ if (previous_boot_id_valid &&
+ !sd_id128_equal(boot_id, previous_boot_id))
+ printf("%s-- Reboot --%s\n",
+ ansi_highlight(), ansi_normal());
+
+ previous_boot_id = boot_id;
+ previous_boot_id_valid = true;
+ }
+ }
+
+ flags =
+ arg_all * OUTPUT_SHOW_ALL |
+ arg_full * OUTPUT_FULL_WIDTH |
+ colors_enabled() * OUTPUT_COLOR |
+ arg_catalog * OUTPUT_CATALOG |
+ arg_utc * OUTPUT_UTC |
+ arg_no_hostname * OUTPUT_NO_HOSTNAME;
+
+ r = output_journal(stdout, j, arg_output, 0, flags, &ellipsized);
+ need_seek = true;
+ if (r == -EADDRNOTAVAIL)
+ break;
+ else if (r < 0 || ferror(stdout))
+ goto finish;
+
+ n_shown++;
+ }
+
+ if (!arg_follow) {
+ if (arg_show_cursor) {
+ _cleanup_free_ char *cursor = NULL;
+
+ r = sd_journal_get_cursor(j, &cursor);
+ if (r < 0 && r != -EADDRNOTAVAIL)
+ log_error_errno(r, "Failed to get cursor: %m");
+ else if (r >= 0)
+ printf("-- cursor: %s\n", cursor);
+ }
+
+ break;
+ }
+
+ r = sd_journal_wait(j, (uint64_t) -1);
+ if (r < 0) {
+ log_error_errno(r, "Couldn't wait for journal event: %m");
+ goto finish;
+ }
+
+ first_line = false;
+ }
+
+finish:
+ pager_close();
+
+ strv_free(arg_file);
+
+ strv_free(arg_syslog_identifier);
+ strv_free(arg_system_units);
+ strv_free(arg_user_units);
+
+ free(arg_root);
+
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/grp-journal/journalctl/journalctl.completion.bash b/src/grp-journal/journalctl/journalctl.completion.bash
new file mode 100644
index 0000000000..a999a10df1
--- /dev/null
+++ b/src/grp-journal/journalctl/journalctl.completion.bash
@@ -0,0 +1,123 @@
+# journalctl(1) completion -*- shell-script -*-
+#
+# This file is part of systemd.
+#
+# Copyright 2010 Ran Benita
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# systemd is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with systemd; If not, see <http://www.gnu.org/licenses/>.
+
+__contains_word () {
+ local w word=$1; shift
+ for w in "$@"; do
+ [[ $w = "$word" ]] && return
+ done
+}
+
+__get_machines() {
+ local a b
+ (machinectl list-images --no-legend --no-pager; machinectl list --no-legend --no-pager; echo ".host") | \
+ { while read a b; do echo " $a"; done; } | sort -u;
+}
+
+__syslog_priorities=(emerg alert crit err warning notice info debug)
+
+_journalctl() {
+ local field_vals= cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]}
+ local -A OPTS=(
+ [STANDALONE]='-a --all --full --system --user
+ --disk-usage -f --follow --header
+ -h --help -l --local --new-id128 -m --merge --no-pager
+ --no-tail -q --quiet --setup-keys --this-boot --verify
+ --version --list-catalog --update-catalog --list-boots
+ --show-cursor --dmesg -k --pager-end -e -r --reverse
+ --utc -x --catalog --no-full --force --dump-catalog
+ --flush --rotate --sync'
+ [ARG]='-b --boot --this-boot -D --directory --file -F --field
+ -M --machine -o --output -u --unit --user-unit -p --priority
+ --vacuum-size --vacuum-time'
+ [ARGUNKNOWN]='-c --cursor --interval -n --lines -S --since -U --until
+ --after-cursor --verify-key -t --identifier
+ --root'
+ )
+
+ if __contains_word "$prev" ${OPTS[ARG]} ${OPTS[ARGUNKNOWN]}; then
+ case $prev in
+ --boot|--this-boot|-b)
+ comps=$(journalctl -F '_BOOT_ID' 2>/dev/null)
+ ;;
+ --directory|-D)
+ comps=$(compgen -d -- "$cur")
+ compopt -o filenames
+ ;;
+ --file)
+ comps=$(compgen -f -- "$cur")
+ compopt -o filenames
+ ;;
+ --output|-o)
+ comps='short short-full short-iso short-precise short-monotonic short-unix verbose export json json-pretty json-sse cat'
+ ;;
+ --field|-F)
+ comps=$(journalctl --fields | sort 2>/dev/null)
+ ;;
+ --machine|-M)
+ comps=$( __get_machines )
+ ;;
+ --priority|-p)
+ comps=${__syslog_priorities[*]}
+ ;;
+ --unit|-u)
+ comps=$(journalctl -F '_SYSTEMD_UNIT' 2>/dev/null)
+ ;;
+ --user-unit)
+ comps=$(journalctl -F '_SYSTEMD_USER_UNIT' 2>/dev/null)
+ ;;
+ *)
+ return 0
+ ;;
+ esac
+ COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
+ return 0
+ fi
+
+ if [[ $cur = -* ]]; then
+ COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") )
+ return 0
+ elif [[ $cur = *=* ]]; then
+ mapfile -t field_vals < <(journalctl -F "${prev%=}" 2>/dev/null)
+ COMPREPLY=( $(compgen -W '${field_vals[*]}' -- "${cur#=}") )
+ elif [[ $cur = /dev* ]]; then
+ compopt -o filenames
+ COMPREPLY=( $(compgen -f -- "${cur}") )
+ elif [[ $cur = /* ]]; then
+ # Append /dev/ to the list of completions, so that
+ # after typing /<TAB><TAB> the user sees /dev/ as one
+ # of the alternatives. Later on the rule above will
+ # take care of showing device files in /dev/.
+ mapfile -t field_vals < <(journalctl -F "_EXE" 2>/dev/null; echo '/dev/')
+ COMPREPLY=( $(compgen -W '${field_vals[*]}' -- "${cur}") )
+ if [[ "${COMPREPLY[@]}" = '/dev/' ]]; then
+ compopt -o filenames
+ COMPREPLY=( $(compgen -f -- "${cur}") )
+ fi
+ elif [[ $prev = '=' ]]; then
+ mapfile -t field_vals < <(journalctl -F "${COMP_WORDS[COMP_CWORD-2]}" 2>/dev/null)
+ COMPREPLY=( $(compgen -W '${field_vals[*]}' -- "$cur") )
+ else
+ mapfile -t field_vals < <(journalctl --fields 2>/dev/null)
+ compopt -o nospace
+ COMPREPLY=( $(compgen -W '${field_vals[*]}' -S= -- "$cur") )
+ fi
+}
+
+complete -F _journalctl journalctl
diff --git a/src/grp-journal/journalctl/journalctl.completion.zsh b/src/grp-journal/journalctl/journalctl.completion.zsh
new file mode 100644
index 0000000000..ef67fcf2a0
--- /dev/null
+++ b/src/grp-journal/journalctl/journalctl.completion.zsh
@@ -0,0 +1,123 @@
+#compdef journalctl
+
+_list_fields() {
+ local -a journal_fields
+ journal_fields=(MESSAGE{,_ID} PRIORITY CODE_{FILE,LINE,FUNC}
+ ERRNO SYSLOG_{FACILITY,IDENTIFIER,PID}
+ _{P,U,G}ID _COMM _EXE _CMDLINE
+ _AUDIT_{SESSION,LOGINUID}
+ _SYSTEMD_{CGROUP,SESSION,UNIT,OWNER_UID}
+ _SYSTEMD_USER_UNIT USER_UNIT
+ _SELINUX_CONTEXT _SOURCE_REALTIME_TIMESTAMP
+ _{BOOT,MACHINE}_ID _HOSTNAME _TRANSPORT
+ _KERNEL_{DEVICE,SUBSYSTEM}
+ _UDEV_{SYSNAME,DEVNODE,DEVLINK}
+ __CURSOR __{REALTIME,MONOTONIC}_TIMESTAMP)
+ case $_jrnl_none in
+ yes) _values -s '=' 'possible fields' \
+ "${journal_fields[@]}:value:_journal_fields ${words[CURRENT]%%=*}" ;;
+ *) _describe 'possible fields' journal_fields ;;
+ esac
+}
+
+_journal_none() {
+ local -a _commands _files _jrnl_none
+ # Setting use-cache will slow this down considerably
+ _commands=( ${"$(_call_program commands "$service $_sys_service_mgr -F _EXE" 2>/dev/null)"} )
+ _jrnl_none='yes'
+ _alternative : \
+ 'files:/dev files:_files -W /dev -P /dev/' \
+ "commands:commands:($_commands[@])" \
+ 'fields:fields:_list_fields'
+}
+
+_journal_fields() {
+ local -a _fields cmd
+ cmd=("journalctl $_sys_service_mgr" "-F ${@[-1]}" "2>/dev/null" )
+ _fields=$(_call_program fields $cmd[@])
+ _fields=${_fields//'\'/'\\'}
+ _fields=${_fields//':'/'\:'}
+ _fields=( ${(f)_fields} )
+ typeset -U _fields
+ _describe 'possible values' _fields
+}
+
+_journal_boots() {
+ local -a _bootid _previousboots
+ _bootid=( ${(f)"$(_call_program bootid "$service -F _BOOT_ID")"} )
+ _previousboots=( -{1..${#_bootid}} )
+ _alternative : \
+ "offsets:boot offsets:compadd -a '_previousboots[1,-2]'" \
+ "bootid:boot ids:compadd -a _bootid"
+}
+
+# Build arguments for "journalctl" to be used in completion.
+# Use both --user and --system modes, they are not exclusive.
+local -a _modes; _modes=(--user --system)
+local -a _modes_with_arg; _modes_with_arg=(--directory -D --file -M --machine --root)
+typeset -a _sys_service_mgr
+local w k v i=0 n=$#words
+while (( i++ < n )); do
+ w=$words[$i]
+ if (( $_modes[(I)$w] )); then
+ _sys_service_mgr+=($w)
+ else
+ # Handle options with arguments. "--key=value" and "--key value".
+ k=${w%%=*}
+ if (( ${_modes_with_arg[(I)$k]} )); then
+ v=${w#*=}
+ if [[ "$k" != "$w" ]]; then
+ # "--key=value" style.
+ _sys_service_mgr+=($w)
+ else
+ # "--key value" style.
+ _sys_service_mgr+=($w ${words[((++i))]})
+ fi
+ fi
+ fi
+done
+_arguments -s \
+ {-h,--help}'[Show this help]' \
+ '--version[Show package version]' \
+ '--no-pager[Do not pipe output into a pager]' \
+ {-l,--full}'[Show long fields in full]' \
+ {-a,--all}'[Show all fields, including long and unprintable]' \
+ {-f,--follow}'[Follow journal]' \
+ {-e,--pager-end}'[Jump to the end of the journal in the pager]' \
+ {-n+,--lines=}'[Number of journal entries to show]:integer' \
+ '--no-tail[Show all lines, even in follow mode]' \
+ {-r,--reverse}'[Reverse output]' \
+ {-o+,--output=}'[Change journal output mode]:output modes:_sd_outputmodes' \
+ {-x,--catalog}'[Show explanatory texts with each log line]' \
+ {-q,--quiet}"[Don't show privilege warning]" \
+ {-m,--merge}'[Show entries from all available journals]' \
+ {-b+,--boot=}'[Show data only from the specified boot or offset]::boot id or offset:_journal_boots' \
+ '--list-boots[List boots ordered by time]' \
+ {-k,--dmesg}'[Show only kernel messages from the current boot]' \
+ {-u+,--unit=}'[Show data only from the specified unit]:units:_journal_fields _SYSTEMD_UNIT' \
+ '--user-unit=[Show data only from the specified user session unit]:units:_journal_fields USER_UNIT' \
+ {-p+,--priority=}'[Show only messages within the specified priority range]:priority:_journal_fields PRIORITY' \
+ {-t+,--identifier=}'[Show only messages with the specified syslog identifier]:identifier:_journal_fields SYSLOG_IDENTIFIER' \
+ {-c+,--cursor=}'[Start showing entries from the specified cursor]:cursors:_journal_fields __CURSORS' \
+ '--after-cursor=[Start showing entries from after the specified cursor]:cursors:_journal_fields __CURSORS' \
+ '--since=[Start showing entries on or newer than the specified date]:YYYY-MM-DD HH\:MM\:SS' \
+ '--until=[Stop showing entries on or older than the specified date]:YYYY-MM-DD HH\:MM\:SS' \
+ {-F,--field=}'[List all values a certain field takes]:Fields:_list_fields' \
+ '--system[Show system and kernel messages]' \
+ '--user[Show messages from user services]' \
+ '(--directory -D -M --machine --root --file)'{-M+,--machine=}'[Operate on local container]:machines:_sd_machines' \
+ '(--directory -D -M --machine --root --file)'{-D+,--directory=}'[Show journal files from directory]:directories:_directories' \
+ '(--directory -D -M --machine --root --file)''--root=[Operate on catalog hierarchy under specified directory]:directories:_directories' \
+ '(--directory -D -M --machine --root)--file=[Operate on specified journal files]:file:_files' \
+ '--new-id128[Generate a new 128 Bit ID]' \
+ '--header[Show journal header information]' \
+ '--disk-usage[Show total disk usage]' \
+ '--list-catalog[List messages in catalog]' \
+ '--dump-catalog[Dump messages in catalog]' \
+ '--update-catalog[Update binary catalog database]' \
+ '--setup-keys[Generate a new FSS key pair]' \
+ '--force[Force recreation of the FSS keys]' \
+ '--interval=[Time interval for changing the FSS sealing key]:time interval' \
+ '--verify[Verify journal file consistency]' \
+ '--verify-key=[Specify FSS verification key]:FSS key' \
+ '*::default: _journal_none'
diff --git a/src/grp-journal/journalctl/journalctl.xml b/src/grp-journal/journalctl/journalctl.xml
new file mode 100644
index 0000000000..63b4a267b8
--- /dev/null
+++ b/src/grp-journal/journalctl/journalctl.xml
@@ -0,0 +1,929 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+-->
+
+ <refentry id="journalctl"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+ <refentryinfo>
+ <title>journalctl</title>
+ <productname>systemd</productname>
+
+ <authorgroup>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Lennart</firstname>
+ <surname>Poettering</surname>
+ <email>lennart@poettering.net</email>
+ </author>
+ </authorgroup>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>journalctl</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>journalctl</refname>
+ <refpurpose>Query the systemd journal</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>journalctl</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ <arg choice="opt" rep="repeat">MATCHES</arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para><command>journalctl</command> may be used to query the
+ contents of the
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ journal as written by
+ <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
+
+ <para>If called without parameters, it will show the full
+ contents of the journal, starting with the oldest entry
+ collected.</para>
+
+ <para>If one or more match arguments are passed, the output is
+ filtered accordingly. A match is in the format
+ <literal>FIELD=VALUE</literal>,
+ e.g. <literal>_SYSTEMD_UNIT=httpd.service</literal>, referring
+ to the components of a structured journal entry. See
+ <citerefentry><refentrytitle>systemd.journal-fields</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+ for a list of well-known fields. If multiple matches are
+ specified matching different fields, the log entries are
+ filtered by both, i.e. the resulting output will show only
+ entries matching all the specified matches of this kind. If two
+ matches apply to the same field, then they are automatically
+ matched as alternatives, i.e. the resulting output will show
+ entries matching any of the specified matches for the same
+ field. Finally, the character <literal>+</literal> may appear
+ as a separate word between other terms on the command line. This
+ causes all matches before and after to be combined in a
+ disjunction (i.e. logical OR).</para>
+
+ <para>It is also possible to filter the entries by specifying an
+ absolute file path as an argument. The file path may be a file or
+ a symbolic link and the file must exist at the time of the query. If a
+ file path refers to an executable binary, an <literal>_EXE=</literal>
+ match for the canonicalized binary path is added to the query. If a
+ file path refers to an executable script, a <literal>_COMM=</literal>
+ match for the script name is added to the query. If a file path
+ refers to a device node, <literal>_KERNEL_DEVICE=</literal> matches for
+ the kernel name of the device and for each of its ancestor devices is
+ added to the query. Symbolic links are dereferenced, kernel names are
+ synthesized, and parent devices are identified from the environment at
+ the time of the query. In general, a device node is the best proxy for
+ an actual device, as log entries do not usually contain fields that
+ identify an actual device. For the resulting log entries to be correct
+ for the actual device, the relevant parts of the environment at the time
+ the entry was logged, in particular the actual device corresponding to
+ the device node, must have been the same as those at the time of the
+ query. Because device nodes generally change their corresponding devices
+ across reboots, specifying a device node path causes the resulting
+ entries to be restricted to those from the current boot.</para>
+
+ <para>Additional constraints may be added using options
+ <option>--boot</option>, <option>--unit=</option>, etc., to
+ further limit what entries will be shown (logical AND).</para>
+
+ <para>Output is interleaved from all accessible journal files,
+ whether they are rotated or currently being written, and
+ regardless of whether they belong to the system itself or are
+ accessible user journals.</para>
+
+ <para>The set of journal files which will be used can be
+ modified using the <option>--user</option>,
+ <option>--system</option>, <option>--directory</option>, and
+ <option>--file</option> options, see below.</para>
+
+ <para>All users are granted access to their private per-user
+ journals. However, by default, only root and users who are
+ members of a few special groups are granted access to the system
+ journal and the journals of other users. Members of the groups
+ <literal>systemd-journal</literal>, <literal>adm</literal>, and
+ <literal>wheel</literal> can read all journal files. Note
+ that the two latter groups traditionally have additional
+ privileges specified by the distribution. Members of the
+ <literal>wheel</literal> group can often perform administrative
+ tasks.</para>
+
+ <para>The output is paged through <command>less</command> by
+ default, and long lines are "truncated" to screen width. The
+ hidden part can be viewed by using the left-arrow and
+ right-arrow keys. Paging can be disabled; see the
+ <option>--no-pager</option> option and the "Environment" section
+ below.</para>
+
+ <para>When outputting to a tty, lines are colored according to
+ priority: lines of level ERROR and higher are colored red; lines
+ of level NOTICE and higher are highlighted; other lines are
+ displayed normally.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Options</title>
+
+ <para>The following options are understood:</para>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>--no-full</option></term>
+ <term><option>--full</option></term>
+ <term><option>-l</option></term>
+
+ <listitem><para>Ellipsize fields when they do not fit in
+ available columns. The default is to show full fields,
+ allowing them to wrap or be truncated by the pager, if one
+ is used.</para>
+
+ <para>The old options
+ <option>-l</option>/<option>--full</option> are not useful
+ anymore, except to undo <option>--no-full</option>.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-a</option></term>
+ <term><option>--all</option></term>
+
+ <listitem><para>Show all fields in full, even if they
+ include unprintable characters or are very
+ long.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-f</option></term>
+ <term><option>--follow</option></term>
+
+ <listitem><para>Show only the most recent journal entries,
+ and continuously print new entries as they are appended to
+ the journal.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-e</option></term>
+ <term><option>--pager-end</option></term>
+
+ <listitem><para>Immediately jump to the end of the journal
+ inside the implied pager tool. This implies
+ <option>-n1000</option> to guarantee that the pager will not
+ buffer logs of unbounded size. This may be overridden with
+ an explicit <option>-n</option> with some other numeric
+ value, while <option>-nall</option> will disable this cap.
+ Note that this option is only supported for the
+ <citerefentry project='man-pages'><refentrytitle>less</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ pager.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-n</option></term>
+ <term><option>--lines=</option></term>
+
+ <listitem><para>Show the most recent journal events and
+ limit the number of events shown. If
+ <option>--follow</option> is used, this option is
+ implied. The argument is a positive integer or
+ <literal>all</literal> to disable line limiting. The default
+ value is 10 if no argument is given.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--no-tail</option></term>
+
+ <listitem><para>Show all stored output lines, even in follow
+ mode. Undoes the effect of <option>--lines=</option>.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-r</option></term>
+ <term><option>--reverse</option></term>
+
+ <listitem><para>Reverse output so that the newest entries
+ are displayed first.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-o</option></term>
+ <term><option>--output=</option></term>
+
+ <listitem><para>Controls the formatting of the journal
+ entries that are shown. Takes one of the following
+ options:</para>
+ <variablelist>
+ <varlistentry>
+ <term>
+ <option>short</option>
+ </term>
+ <listitem>
+ <para>is the default and generates an output that is
+ mostly identical to the formatting of classic syslog
+ files, showing one line per journal entry.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>short-full</option>
+ </term>
+ <listitem>
+ <para>is very similar, but shows timestamps in the format the <option>--since=</option> and
+ <option>--until=</option> options accept. Unlike the timestamp information shown in
+ <option>short</option> output mode this mode includes weekday, year and timezone information in the
+ output, and is locale-independent.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>short-iso</option>
+ </term>
+ <listitem>
+ <para>is very similar, but shows ISO 8601 wallclock
+ timestamps.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>short-precise</option>
+ </term>
+ <listitem>
+ <para>is very similar, but shows timestamps with full
+ microsecond precision.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>short-monotonic</option>
+ </term>
+ <listitem>
+ <para>is very similar, but shows monotonic timestamps
+ instead of wallclock timestamps.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>short-unix</option>
+ </term>
+ <listitem>
+ <para>is very similar, but shows seconds passed since January 1st 1970 UTC instead of wallclock
+ timestamps ("UNIX time"). The time is shown with microsecond accuracy.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>verbose</option>
+ </term>
+ <listitem>
+ <para>shows the full-structured entry items with all
+ fields.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>export</option>
+ </term>
+ <listitem>
+ <para>serializes the journal into a binary (but mostly
+ text-based) stream suitable for backups and network
+ transfer (see
+ <ulink url="http://www.freedesktop.org/wiki/Software/systemd/export">Journal Export Format</ulink>
+ for more information).</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>json</option>
+ </term>
+ <listitem>
+ <para>formats entries as JSON data structures, one per
+ line (see
+ <ulink url="http://www.freedesktop.org/wiki/Software/systemd/json">Journal JSON Format</ulink>
+ for more information).</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>json-pretty</option>
+ </term>
+ <listitem>
+ <para>formats entries as JSON data structures, but
+ formats them in multiple lines in order to make them
+ more readable by humans.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>json-sse</option>
+ </term>
+ <listitem>
+ <para>formats entries as JSON data structures, but wraps
+ them in a format suitable for
+ <ulink url="https://developer.mozilla.org/en-US/docs/Server-sent_events/Using_server-sent_events">Server-Sent Events</ulink>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>cat</option>
+ </term>
+ <listitem>
+ <para>generates a very terse output, only showing the
+ actual message of each journal entry with no metadata,
+ not even a timestamp.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--utc</option></term>
+
+ <listitem><para>Express time in Coordinated Universal Time
+ (UTC).</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--no-hostname</option></term>
+
+ <listitem><para>Don't show the hostname field of log messages originating from the local host. This switch only
+ has an effect on the <option>short</option> family of output modes (see above).</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-x</option></term>
+ <term><option>--catalog</option></term>
+
+ <listitem><para>Augment log lines with explanation texts from
+ the message catalog. This will add explanatory help texts to
+ log messages in the output where this is available. These
+ short help texts will explain the context of an error or log
+ event, possible solutions, as well as pointers to support
+ forums, developer documentation, and any other relevant
+ manuals. Note that help texts are not available for all
+ messages, but only for selected ones. For more information on
+ the message catalog, please refer to the
+ <ulink url="http://www.freedesktop.org/wiki/Software/systemd/catalog">Message Catalog Developer Documentation</ulink>.</para>
+
+ <para>Note: when attaching <command>journalctl</command>
+ output to bug reports, please do <emphasis>not</emphasis> use
+ <option>-x</option>.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-q</option></term>
+ <term><option>--quiet</option></term>
+
+ <listitem><para>Suppresses all info messages
+ (i.e. "-- Logs begin at ...", "-- Reboot --"),
+ any warning messages regarding
+ inaccessible system journals when run as a normal
+ user.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-m</option></term>
+ <term><option>--merge</option></term>
+
+ <listitem><para>Show entries interleaved from all available
+ journals, including remote ones.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-b <optional><replaceable>ID</replaceable></optional><optional><replaceable>±offset</replaceable></optional></option></term>
+ <term><option>--boot=<optional><replaceable>ID</replaceable></optional><optional><replaceable>±offset</replaceable></optional></option></term>
+
+ <listitem><para>Show messages from a specific boot. This will
+ add a match for <literal>_BOOT_ID=</literal>.</para>
+
+ <para>The argument may be empty, in which case logs for the
+ current boot will be shown.</para>
+
+ <para>If the boot ID is omitted, a positive
+ <replaceable>offset</replaceable> will look up the boots
+ starting from the beginning of the journal, and an
+ equal-or-less-than zero <replaceable>offset</replaceable> will
+ look up boots starting from the end of the journal. Thus,
+ <constant>1</constant> means the first boot found in the
+ journal in chronological order, <constant>2</constant> the
+ second and so on; while <constant>-0</constant> is the last
+ boot, <constant>-1</constant> the boot before last, and so
+ on. An empty <replaceable>offset</replaceable> is equivalent
+ to specifying <constant>-0</constant>, except when the current
+ boot is not the last boot (e.g. because
+ <option>--directory</option> was specified to look at logs
+ from a different machine).</para>
+
+ <para>If the 32-character <replaceable>ID</replaceable> is
+ specified, it may optionally be followed by
+ <replaceable>offset</replaceable> which identifies the boot
+ relative to the one given by boot
+ <replaceable>ID</replaceable>. Negative values mean earlier
+ boots and positive values mean later boots. If
+ <replaceable>offset</replaceable> is not specified, a value of
+ zero is assumed, and the logs for the boot given by
+ <replaceable>ID</replaceable> are shown.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--list-boots</option></term>
+
+ <listitem><para>Show a tabular list of boot numbers (relative to
+ the current boot), their IDs, and the timestamps of the first
+ and last message pertaining to the boot.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-k</option></term>
+ <term><option>--dmesg</option></term>
+
+ <listitem><para>Show only kernel messages. This implies
+ <option>-b</option> and adds the match
+ <literal>_TRANSPORT=kernel</literal>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-t</option></term>
+ <term><option>--identifier=<replaceable>SYSLOG_IDENTIFIER</replaceable></option></term>
+
+ <listitem><para>Show messages for the specified syslog
+ identifier
+ <replaceable>SYSLOG_IDENTIFIER</replaceable>.</para>
+
+ <para>This parameter can be specified multiple
+ times.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-u</option></term>
+ <term><option>--unit=<replaceable>UNIT</replaceable>|<replaceable>PATTERN</replaceable></option></term>
+
+ <listitem><para>Show messages for the specified systemd unit
+ <replaceable>UNIT</replaceable> (such as a service unit), or
+ for any of the units matched by
+ <replaceable>PATTERN</replaceable>. If a pattern is
+ specified, a list of unit names found in the journal is
+ compared with the specified pattern and all that match are
+ used. For each unit name, a match is added for messages from
+ the unit
+ (<literal>_SYSTEMD_UNIT=<replaceable>UNIT</replaceable></literal>),
+ along with additional matches for messages from systemd and
+ messages about coredumps for the specified unit.</para>
+
+ <para>This parameter can be specified multiple times.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--user-unit=</option></term>
+
+ <listitem><para>Show messages for the specified user session
+ unit. This will add a match for messages from the unit
+ (<literal>_SYSTEMD_USER_UNIT=</literal> and
+ <literal>_UID=</literal>) and additional matches for messages
+ from session systemd and messages about coredumps for the
+ specified unit.</para>
+
+ <para>This parameter can be specified multiple times.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-p</option></term>
+ <term><option>--priority=</option></term>
+
+ <listitem><para>Filter output by message priorities or
+ priority ranges. Takes either a single numeric or textual log
+ level (i.e. between 0/<literal>emerg</literal> and
+ 7/<literal>debug</literal>), or a range of numeric/text log
+ levels in the form FROM..TO. The log levels are the usual
+ syslog log levels as documented in
+ <citerefentry project='man-pages'><refentrytitle>syslog</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+ i.e. <literal>emerg</literal> (0),
+ <literal>alert</literal> (1), <literal>crit</literal> (2),
+ <literal>err</literal> (3), <literal>warning</literal> (4),
+ <literal>notice</literal> (5), <literal>info</literal> (6),
+ <literal>debug</literal> (7). If a single log level is
+ specified, all messages with this log level or a lower (hence
+ more important) log level are shown. If a range is specified,
+ all messages within the range are shown, including both the
+ start and the end value of the range. This will add
+ <literal>PRIORITY=</literal> matches for the specified
+ priorities.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-c</option></term>
+ <term><option>--cursor=</option></term>
+
+ <listitem><para>Start showing entries from the location in the
+ journal specified by the passed cursor.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--after-cursor=</option></term>
+
+ <listitem><para>Start showing entries from the location in the
+ journal <emphasis>after</emphasis> the location specified by
+ the passed cursor. The cursor is shown when the
+ <option>--show-cursor</option> option is used.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--show-cursor</option></term>
+
+ <listitem><para>The cursor is shown after the last entry after
+ two dashes:</para>
+ <programlisting>-- cursor: s=0639...</programlisting>
+ <para>The format of the cursor is private
+ and subject to change.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-S</option></term>
+ <term><option>--since=</option></term>
+ <term><option>-U</option></term>
+ <term><option>--until=</option></term>
+
+ <listitem><para>Start showing entries on or newer than the specified date, or on or older than the specified
+ date, respectively. Date specifications should be of the format <literal>2012-10-30 18:17:16</literal>. If the
+ time part is omitted, <literal>00:00:00</literal> is assumed. If only the seconds component is omitted,
+ <literal>:00</literal> is assumed. If the date component is omitted, the current day is assumed. Alternatively
+ the strings <literal>yesterday</literal>, <literal>today</literal>, <literal>tomorrow</literal> are understood,
+ which refer to 00:00:00 of the day before the current day, the current day, or the day after the current day,
+ respectively. <literal>now</literal> refers to the current time. Finally, relative times may be specified,
+ prefixed with <literal>-</literal> or <literal>+</literal>, referring to times before or after the current
+ time, respectively. For complete time and date specification, see
+ <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry>. Note that
+ <option>--output=short-full</option> prints timestamps that follow precisely this format.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-F</option></term>
+ <term><option>--field=</option></term>
+
+ <listitem><para>Print all possible data values the specified
+ field can take in all entries of the journal.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-N</option></term>
+ <term><option>--fields</option></term>
+
+ <listitem><para>Print all field names currently used in all entries of the journal.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--system</option></term>
+ <term><option>--user</option></term>
+
+ <listitem><para>Show messages from system services and the
+ kernel (with <option>--system</option>). Show messages from
+ service of current user (with <option>--user</option>). If
+ neither is specified, show all messages that the user can see.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-M</option></term>
+ <term><option>--machine=</option></term>
+
+ <listitem><para>Show messages from a running, local
+ container. Specify a container name to connect to.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-D <replaceable>DIR</replaceable></option></term>
+ <term><option>--directory=<replaceable>DIR</replaceable></option></term>
+
+ <listitem><para>Takes a directory path as argument. If
+ specified, journalctl will operate on the specified journal
+ directory <replaceable>DIR</replaceable> instead of the
+ default runtime and system journal paths.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--file=<replaceable>GLOB</replaceable></option></term>
+
+ <listitem><para>Takes a file glob as an argument. If
+ specified, journalctl will operate on the specified journal
+ files matching <replaceable>GLOB</replaceable> instead of the
+ default runtime and system journal paths. May be specified
+ multiple times, in which case files will be suitably
+ interleaved.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--root=<replaceable>ROOT</replaceable></option></term>
+
+ <listitem><para>Takes a directory path as an argument. If
+ specified, journalctl will operate on journal directories and catalog file hierarchy
+ underneath the specified directory instead of the root
+ directory (e.g. <option>--update-catalog</option> will create
+ <filename><replaceable>ROOT</replaceable>/var/lib/systemd/catalog/database</filename>,
+ and journal files under <filename><replaceable>ROOT</replaceable>/run/journal</filename>
+ or <filename><replaceable>ROOT</replaceable>/var/log/journal</filename> will be displayed).
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--new-id128</option></term>
+
+ <listitem><para>Instead of showing journal contents, generate
+ a new 128-bit ID suitable for identifying messages. This is
+ intended for usage by developers who need a new identifier for
+ a new message they introduce and want to make
+ recognizable. This will print the new ID in three different
+ formats which can be copied into source code or similar.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--header</option></term>
+
+ <listitem><para>Instead of showing journal contents, show
+ internal header information of the journal fields
+ accessed.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--disk-usage</option></term>
+
+ <listitem><para>Shows the current disk usage of all journal
+ files. This shows the sum of the disk usage of all archived
+ and active journal files.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--vacuum-size=</option></term>
+ <term><option>--vacuum-time=</option></term>
+ <term><option>--vacuum-files=</option></term>
+
+ <listitem><para>Removes archived journal files until the disk
+ space they use falls below the specified size (specified with
+ the usual <literal>K</literal>, <literal>M</literal>,
+ <literal>G</literal> and <literal>T</literal> suffixes), or all
+ archived journal files contain no data older than the specified
+ timespan (specified with the usual <literal>s</literal>,
+ <literal>m</literal>, <literal>h</literal>,
+ <literal>days</literal>, <literal>months</literal>,
+ <literal>weeks</literal> and <literal>years</literal> suffixes),
+ or no more than the specified number of separate journal files
+ remain. Note that running <option>--vacuum-size=</option> has
+ only an indirect effect on the output shown by
+ <option>--disk-usage</option>, as the latter includes active
+ journal files, while the vacuuming operation only operates
+ on archived journal files. Similarly,
+ <option>--vacuum-files=</option> might not actually reduce the
+ number of journal files to below the specified number, as it
+ will not remove active journal
+ files. <option>--vacuum-size=</option>,
+ <option>--vacuum-time=</option> and
+ <option>--vacuum-files=</option> may be combined in a single
+ invocation to enforce any combination of a size, a time and a
+ number of files limit on the archived journal
+ files. Specifying any of these three parameters as zero is
+ equivalent to not enforcing the specific limit, and is thus
+ redundant.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--list-catalog
+ <optional><replaceable>128-bit-ID...</replaceable></optional>
+ </option></term>
+
+ <listitem><para>List the contents of the message catalog as a
+ table of message IDs, plus their short description strings.
+ </para>
+
+ <para>If any <replaceable>128-bit-ID</replaceable>s are
+ specified, only those entries are shown.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--dump-catalog
+ <optional><replaceable>128-bit-ID...</replaceable></optional>
+ </option></term>
+
+ <listitem><para>Show the contents of the message catalog, with
+ entries separated by a line consisting of two dashes and the
+ ID (the format is the same as <filename>.catalog</filename>
+ files).</para>
+
+ <para>If any <replaceable>128-bit-ID</replaceable>s are
+ specified, only those entries are shown.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--update-catalog</option></term>
+
+ <listitem><para>Update the message catalog index. This command
+ needs to be executed each time new catalog files are
+ installed, removed, or updated to rebuild the binary catalog
+ index.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--setup-keys</option></term>
+
+ <listitem><para>Instead of showing journal contents, generate
+ a new key pair for Forward Secure Sealing (FSS). This will
+ generate a sealing key and a verification key. The sealing key
+ is stored in the journal data directory and shall remain on
+ the host. The verification key should be stored
+ externally. Refer to the <option>Seal=</option> option in
+ <citerefentry><refentrytitle>journald.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for information on Forward Secure Sealing and for a link to a
+ refereed scholarly paper detailing the cryptographic theory it
+ is based on.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--force</option></term>
+
+ <listitem><para>When <option>--setup-keys</option> is passed
+ and Forward Secure Sealing (FSS) has already been configured,
+ recreate FSS keys.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--interval=</option></term>
+
+ <listitem><para>Specifies the change interval for the sealing
+ key when generating an FSS key pair with
+ <option>--setup-keys</option>. Shorter intervals increase CPU
+ consumption but shorten the time range of undetectable journal
+ alterations. Defaults to 15min.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--verify</option></term>
+
+ <listitem><para>Check the journal file for internal
+ consistency. If the file has been generated with FSS enabled and
+ the FSS verification key has been specified with
+ <option>--verify-key=</option>, authenticity of the journal file
+ is verified.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--verify-key=</option></term>
+
+ <listitem><para>Specifies the FSS verification key to use for
+ the <option>--verify</option> operation.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--sync</option></term>
+
+ <listitem><para>Asks the journal daemon to write all yet
+ unwritten journal data to the backing file system and
+ synchronize all journals. This call does not return until the
+ synchronization operation is complete. This command guarantees
+ that any log messages written before its invocation are safely
+ stored on disk at the time it returns.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--flush</option></term>
+
+ <listitem><para>Asks the journal daemon to flush any log data
+ stored in <filename>/run/log/journal</filename> into
+ <filename>/var/log/journal</filename>, if persistent storage
+ is enabled. This call does not return until the operation is
+ complete. Note that this call is idempotent: the data is only
+ flushed from <filename>/run/log/journal</filename> into
+ <filename>/var/log/journal</filename> once during system
+ runtime, and this command exits cleanly without executing any
+ operation if this has already happened. This command
+ effectively guarantees that all data is flushed to
+ <filename>/var/log/journal</filename> at the time it
+ returns.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--rotate</option></term>
+
+ <listitem><para>Asks the journal daemon to rotate journal
+ files. This call does not return until the rotation operation
+ is complete.</para></listitem>
+ </varlistentry>
+
+ <xi:include href="standard-options.xml" xpointer="help" />
+ <xi:include href="standard-options.xml" xpointer="version" />
+ <xi:include href="standard-options.xml" xpointer="no-pager" />
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Exit status</title>
+
+ <para>On success, 0 is returned; otherwise, a non-zero failure
+ code is returned.</para>
+ </refsect1>
+
+ <xi:include href="less-variables.xml" />
+
+ <refsect1>
+ <title>Examples</title>
+
+ <para>Without arguments, all collected logs are shown
+ unfiltered:</para>
+
+ <programlisting>journalctl</programlisting>
+
+ <para>With one match specified, all entries with a field matching
+ the expression are shown:</para>
+
+ <programlisting>journalctl _SYSTEMD_UNIT=avahi-daemon.service</programlisting>
+
+ <para>If two different fields are matched, only entries matching
+ both expressions at the same time are shown:</para>
+
+ <programlisting>journalctl _SYSTEMD_UNIT=avahi-daemon.service _PID=28097</programlisting>
+
+ <para>If two matches refer to the same field, all entries matching
+ either expression are shown:</para>
+
+ <programlisting>journalctl _SYSTEMD_UNIT=avahi-daemon.service _SYSTEMD_UNIT=dbus.service</programlisting>
+
+ <para>If the separator <literal>+</literal> is used, two
+ expressions may be combined in a logical OR. The following will
+ show all messages from the Avahi service process with the PID
+ 28097 plus all messages from the D-Bus service (from any of its
+ processes):</para>
+
+ <programlisting>journalctl _SYSTEMD_UNIT=avahi-daemon.service _PID=28097 + _SYSTEMD_UNIT=dbus.service</programlisting>
+
+ <para>Show all logs generated by the D-Bus executable:</para>
+
+ <programlisting>journalctl /usr/bin/dbus-daemon</programlisting>
+
+ <para>Show all kernel logs from previous boot:</para>
+
+ <programlisting>journalctl -k -b -1</programlisting>
+
+ <para>Show a live log display from a system service
+ <filename>apache.service</filename>:</para>
+
+ <programlisting>journalctl -f -u apache</programlisting>
+
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>coredumpctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.journal-fields</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>journald.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+ </para>
+ </refsect1>
+</refentry>
diff --git a/src/grp-journal/journalctl/systemd-journal-catalog-update.service.in b/src/grp-journal/journalctl/systemd-journal-catalog-update.service.in
new file mode 100644
index 0000000000..6370dd478f
--- /dev/null
+++ b/src/grp-journal/journalctl/systemd-journal-catalog-update.service.in
@@ -0,0 +1,21 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Rebuild Journal Catalog
+Documentation=man:systemd-journald.service(8) man:journald.conf(5)
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=local-fs.target
+Before=sysinit.target shutdown.target systemd-update-done.service
+ConditionNeedsUpdate=/etc
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@rootbindir@/journalctl --update-catalog
+TimeoutSec=90s
diff --git a/src/grp-journal/journalctl/systemd-journal-flush.service.in b/src/grp-journal/journalctl/systemd-journal-flush.service.in
new file mode 100644
index 0000000000..a0a2e3fdb4
--- /dev/null
+++ b/src/grp-journal/journalctl/systemd-journal-flush.service.in
@@ -0,0 +1,22 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Flush Journal to Persistent Storage
+Documentation=man:systemd-journald.service(8) man:journald.conf(5)
+DefaultDependencies=no
+Requires=systemd-journald.service
+After=systemd-journald.service
+After=systemd-remount-fs.service
+Before=systemd-user-sessions.service systemd-tmpfiles-setup.service
+RequiresMountsFor=/var/log/journal
+
+[Service]
+ExecStart=@rootbindir@/journalctl --flush
+Type=oneshot
+RemainAfterExit=yes
+TimeoutSec=90s