diff options
Diffstat (limited to 'src/grp-journal/journalctl')
l--------- | src/grp-journal/journalctl/GNUmakefile | 1 | ||||
-rw-r--r-- | src/grp-journal/journalctl/Makefile | 61 | ||||
-rw-r--r-- | src/grp-journal/journalctl/journal-qrcode.c | 135 | ||||
-rw-r--r-- | src/grp-journal/journalctl/journal-qrcode.h | 27 | ||||
-rw-r--r-- | src/grp-journal/journalctl/journalctl.c | 2628 | ||||
-rw-r--r-- | src/grp-journal/journalctl/journalctl.completion.bash | 123 | ||||
-rw-r--r-- | src/grp-journal/journalctl/journalctl.completion.zsh | 123 | ||||
-rw-r--r-- | src/grp-journal/journalctl/journalctl.xml | 929 | ||||
-rw-r--r-- | src/grp-journal/journalctl/systemd-journal-catalog-update.service.in | 21 | ||||
-rw-r--r-- | src/grp-journal/journalctl/systemd-journal-flush.service.in | 22 |
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, ¤t); + 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 |