static bool arg_no_tail = false;
static bool arg_quiet = false;
static bool arg_merge = false;
-static bool arg_this_boot = false;
+static bool arg_boot_id = false;
+static char *arg_boot_id_descriptor = NULL;
static bool arg_dmesg = false;
static const char *arg_cursor = NULL;
static const char *arg_directory = NULL;
+static char **arg_file = NULL;
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 const char *arg_field = NULL;
static bool arg_catalog = false;
static bool arg_reverse = false;
+static int arg_journal_type = 0;
static const char *arg_root = NULL;
static enum {
ACTION_UPDATE_CATALOG
} arg_action = ACTION_SHOW;
+typedef struct boot_id_t {
+ sd_id128_t id;
+ uint64_t timestamp;
+} boot_id_t;
+
static int help(void) {
printf("%s [OPTIONS...] [MATCHES...]\n\n"
"Query the journal.\n\n"
"Flags:\n"
+ " --system Show only the system journal\n"
+ " --user Show only the user journal for current user\n"
" --since=DATE Start showing entries newer or of the specified date\n"
" --until=DATE Stop showing entries older or of the specified date\n"
" -c --cursor=CURSOR Start showing entries from specified cursor\n"
- " -b --this-boot Show data only from current boot\n"
- " -k --dmesg Show kmsg log from current boot\n"
+ " -b --boot[=ID] Show data only from ID or current boot if unspecified\n"
+ " -k --dmesg Show kernel message log from current boot\n"
" -u --unit=UNIT Show data only from the specified unit\n"
" --user-unit=UNIT Show data only from the specified user session unit\n"
" -p --priority=RANGE Show only messages within the specified priority range\n"
" -o --output=STRING Change journal output mode (short, short-monotonic,\n"
" verbose, export, json, json-pretty, json-sse, cat)\n"
" -x --catalog Add message explanations where available\n"
- " --full Do not ellipsize fields\n"
+ " -l --full Do not ellipsize fields\n"
" -a --all Show all fields, including long and unprintable\n"
" -q --quiet Don't show privilege warning\n"
" --no-pager Do not pipe output into a pager\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 catalog files underneath the root ROOT\n"
#ifdef HAVE_GCRYPT
" --interval=TIME Time interval for changing the FSS sealing key\n"
" --update-catalog Update the message catalog database\n"
#ifdef HAVE_GCRYPT
" --setup-keys Generate new FSS key pair\n"
+ " --force Force overriding new FSS key pair with --setup-keys\n"
" --verify Verify journal file consistency\n"
#endif
, program_invocation_short_name);
ARG_NO_PAGER,
ARG_NO_TAIL,
ARG_NEW_ID128,
+ ARG_USER,
+ ARG_SYSTEM,
ARG_ROOT,
ARG_HEADER,
- ARG_FULL,
ARG_SETUP_KEYS,
+ ARG_FILE,
ARG_INTERVAL,
ARG_VERIFY,
ARG_VERIFY_KEY,
ARG_USER_UNIT,
ARG_LIST_CATALOG,
ARG_DUMP_CATALOG,
- ARG_UPDATE_CATALOG
+ ARG_UPDATE_CATALOG,
+ ARG_FORCE,
};
static const struct option options[] = {
{ "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, ARG_FULL },
+ { "full", no_argument, NULL, 'l' },
{ "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, 'b' },
+ { "boot", optional_argument, NULL, 'b' },
+ { "this-boot", optional_argument, NULL, 'b' }, /* deprecated */
{ "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 },
{ "priority", required_argument, NULL, 'p' },
assert(argc >= 0);
assert(argv);
- while ((c = getopt_long(argc, argv, "hefo:an::qmbkD:p:c:u:F:xr", options, NULL)) >= 0) {
+ while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:c:u:F:xr", options, NULL)) >= 0) {
switch (c) {
break;
- case ARG_FULL:
+ case 'l':
arg_full = true;
break;
break;
case 'b':
- arg_this_boot = true;
+ if (optarg)
+ arg_boot_id_descriptor = optarg;
+ else if (optind < argc && argv[optind][0] != '-') {
+ arg_boot_id_descriptor = argv[optind];
+ optind++;
+ }
+ arg_boot_id = true;
break;
case 'k':
- arg_this_boot = arg_dmesg = true;
+ arg_boot_id = 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 'D':
arg_directory = optarg;
break;
+ case ARG_FILE:
+ r = glob_extend(&arg_file, optarg);
+ if (r < 0) {
+ log_error("Failed to add paths: %s", strerror(-r));
+ return r;
+ };
+ break;
+
case ARG_ROOT:
arg_root = optarg;
break;
break;
#ifdef HAVE_GCRYPT
+ case ARG_FORCE:
+ arg_force = true;
+ break;
+
case ARG_SETUP_KEYS:
arg_action = ACTION_SETUP_KEYS;
break;
case ARG_SETUP_KEYS:
case ARG_VERIFY_KEY:
case ARG_INTERVAL:
+ case ARG_FORCE:
log_error("Forward-secure sealing not available.");
return -ENOTSUP;
#endif
if (arg_follow && !arg_no_tail && arg_lines < 0)
arg_lines = 10;
+ if (arg_directory && arg_file) {
+ log_error("Please specify either -D/--directory= or --file=, not both.");
+ return -EINVAL;
+ }
+
if (arg_since_set && arg_until_set && arg_since > arg_until) {
log_error("--since= must be before --until=.");
return -EINVAL;
return 0;
}
-static int add_this_boot(sd_journal *j) {
- if (!arg_this_boot)
+static int boot_id_cmp(const void *a, const void *b) {
+ uint64_t _a, _b;
+
+ _a = ((const boot_id_t *)a)->timestamp;
+ _b = ((const boot_id_t *)b)->timestamp;
+
+ return _a < _b ? -1 : (_a > _b ? 1 : 0);
+}
+
+static int get_relative_boot_id(sd_journal *j, sd_id128_t *boot_id, int relative) {
+ int r;
+ const void *data;
+ unsigned int id_count = 0;
+ size_t length, allocated = 0;
+ boot_id_t ref_boot_id, *id;
+ _cleanup_free_ boot_id_t *all_ids = NULL;
+ bool find_first_boot = false, ref_boot_found = false;
+
+ assert(j);
+ assert(boot_id);
+
+ if (relative == 0)
return 0;
- return add_match_this_boot(j);
+ if (sd_id128_equal(*boot_id, SD_ID128_NULL) && relative > 0) {
+ find_first_boot = true;
+ relative--;
+ }
+
+ r = sd_journal_query_unique(j, "_BOOT_ID");
+ if (r < 0)
+ return r;
+
+ SD_JOURNAL_FOREACH_UNIQUE(j, data, length) {
+ if (length < strlen("_BOOT_ID="))
+ continue;
+
+ if (!GREEDY_REALLOC(all_ids, allocated, id_count + 1))
+ return log_oom();
+
+ id = &all_ids[id_count];
+
+ r = sd_id128_from_string(((const char *)data) + strlen("_BOOT_ID="), &id->id);
+ if (r < 0) {
+ continue;
+ }
+
+ sd_journal_flush_matches(j);
+ r = sd_journal_add_match(j, data, length);
+ if (r < 0)
+ continue;
+
+ r = sd_journal_seek_head(j);
+ if (r < 0)
+ continue;
+
+ r = sd_journal_next(j);
+ if (r <= 0)
+ continue;
+
+ r = sd_journal_get_realtime_usec(j, &id->timestamp);
+ if (r < 0)
+ continue;
+
+ if (!find_first_boot && sd_id128_equal(id->id, *boot_id)) {
+ ref_boot_id = *id;
+ ref_boot_found = true;
+ }
+
+ id_count++;
+ }
+
+ *boot_id = SD_ID128_NULL;
+ sd_journal_flush_matches(j);
+
+ if (id_count == 0 || (!find_first_boot && !ref_boot_found))
+ return 0;
+
+ qsort(all_ids, id_count, sizeof(boot_id_t), boot_id_cmp);
+ if (find_first_boot)
+ id = all_ids;
+ else
+ id = bsearch(&ref_boot_id, all_ids, id_count, sizeof(boot_id_t), boot_id_cmp);
+
+ if (!id || (relative < 0 && ((id - all_ids) + relative) < 0) ||
+ (relative >= 0 && (unsigned long)((id - all_ids) + relative) >= id_count))
+ return 0;
+
+ id += relative;
+ *boot_id = id->id;
+ return 0;
+}
+
+static int add_boot(sd_journal *j) {
+ char match[9+32+1] = "_BOOT_ID=";
+ char *marker;
+ sd_id128_t boot_id;
+ int r, relative = 0;
+
+ assert(j);
+
+ if (!arg_boot_id)
+ return 0;
+
+ if (arg_boot_id_descriptor) {
+ marker = strchr(arg_boot_id_descriptor, ':');
+ if (marker) {
+ *marker = '\0';
+ marker++;
+
+ if (*marker == '\0')
+ relative = -1;
+ else {
+ r = safe_atoi(marker, &relative);
+ if (r < 0) {
+ log_error("Failed to parse relative boot ID number '%s'", marker);
+ return -EINVAL;
+ }
+ }
+ }
+ }
+
+ if (isempty(arg_boot_id_descriptor)) {
+ if (relative > 0) {
+ /* We cannot look into the future. Instead, we look
+ * into the past (starting from first boot). The ID
+ * will be looked up later */
+ boot_id = SD_ID128_NULL;
+ } else {
+ r = sd_id128_get_boot(&boot_id);
+ if (r < 0) {
+ log_error("Failed to get boot ID: %s", strerror(-r));
+ return r;
+ }
+ }
+ } else {
+ r = sd_id128_from_string(arg_boot_id_descriptor, &boot_id);
+ if (r < 0) {
+ log_error("Failed to parse boot ID: %s", strerror(-r));
+ return r;
+ }
+ }
+
+ r = get_relative_boot_id(j, &boot_id, relative);
+ if (r < 0) {
+ log_error("Failed to look up boot ID: %s", strerror(-r));
+ return r;
+ } else if (sd_id128_equal(boot_id, SD_ID128_NULL)) {
+ log_error("Failed to find boot ID");
+ return -1;
+ }
+
+ sd_id128_to_string(boot_id, match + 9);
+ r = sd_journal_add_match(j, match, strlen(match));
+ if (r < 0) {
+ log_error("Failed to add match: %s", strerror(-r));
+ return r;
+ }
+
+ r = sd_journal_add_conjunction(j);
+ if (r < 0)
+ return r;
+
+ return 0;
}
static int add_dmesg(sd_journal *j) {
char *p = NULL, *k = NULL;
struct FSSHeader h;
uint64_t n;
+ struct stat st;
+
+ r = stat("/var/log/journal", &st);
+ if (r < 0 && errno != ENOENT && errno != ENOTDIR) {
+ log_error("stat(\"%s\") failed: %m", "/var/log/journal");
+ return -errno;
+ }
+
+ if (r < 0 || !S_ISDIR(st.st_mode)) {
+ log_error("%s is not a directory, must be using persistent logging for FSS.",
+ "/var/log/journal");
+ return r < 0 ? -errno : -ENOTDIR;
+ }
r = sd_id128_get_machine(&machine);
if (r < 0) {
return log_oom();
if (access(p, F_OK) >= 0) {
- log_error("Sealing key file %s exists already.", p);
- r = -EEXIST;
- goto finish;
+ if (arg_force) {
+ r = unlink(p);
+ if (r < 0) {
+ log_error("unlink(\"%s\") failed: %m", p);
+ r = -errno;
+ goto finish;
+ }
+ } else {
+ log_error("Sealing key file %s exists already. (--force to recreate)", p);
+ r = -EEXIST;
+ goto finish;
+ }
}
if (asprintf(&k, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss.tmp.XXXXXX",
}
if (arg_directory)
- r = sd_journal_open_directory(&j, arg_directory, 0);
+ r = sd_journal_open_directory(&j, arg_directory, arg_journal_type);
+ else if (arg_file)
+ r = sd_journal_open_files(&j, (const char**) arg_file, 0);
else
- r = sd_journal_open(&j, arg_merge ? 0 : SD_JOURNAL_LOCAL_ONLY);
+ r = sd_journal_open(&j, !arg_merge*SD_JOURNAL_LOCAL_ONLY + arg_journal_type);
if (r < 0) {
- log_error("Failed to open journal: %s", strerror(-r));
+ log_error("Failed to open %s: %s",
+ arg_directory ? arg_directory : arg_file ? "files" : "journal",
+ strerror(-r));
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
- r = add_this_boot(j);
+ /* add_boot() must be called first!
+ * It may need to seek the journal to find parent boot IDs. */
+ r = add_boot(j);
if (r < 0)
return EXIT_FAILURE;
if (r < 0)
return EXIT_FAILURE;
- /* Opening the fd now means the first sd_journal_wait() will actually wait */
- r = sd_journal_get_fd(j);
- if (r < 0)
- return EXIT_FAILURE;
+ log_debug("Journal filter: %s", j->level0 ? journal_make_match_string(j) : "none");
if (arg_field) {
const void *data;
return EXIT_SUCCESS;
}
+ /* Opening the fd now means the first sd_journal_wait() will actually wait */
+ if (arg_follow) {
+ r = sd_journal_get_fd(j);
+ if (r < 0)
+ return EXIT_FAILURE;
+ }
+
if (arg_cursor) {
r = sd_journal_seek_cursor(j, arg_cursor);
if (r < 0) {
if (!arg_merge) {
sd_id128_t boot_id;
+ const char *color_on = on_tty() ? ANSI_HIGHLIGHT_ON : "",
+ *color_off = on_tty() ? ANSI_HIGHLIGHT_OFF : "";
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(ANSI_HIGHLIGHT_ON "-- Reboot --" ANSI_HIGHLIGHT_OFF "\n");
+ printf("%s-- Reboot --%s\n", color_on, color_off);
previous_boot_id = boot_id;
previous_boot_id_valid = true;