X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fjournal%2Fjournalctl.c;h=34017484be49fb67a980c166da353a7f035b32b5;hp=5c21ab0adffd7dbeaf50041da32787b26b3ff797;hb=c3eba2ab4e70fc7aed902b1d5b1fcb978ef98dfa;hpb=6c7be122acd666d4e93541179d89747aa12efb67 diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 5c21ab0ad..34017484b 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -29,7 +29,12 @@ #include #include #include +#include #include +#include +#include +#include +#include #include @@ -44,6 +49,7 @@ #include "journal-def.h" #include "journal-verify.h" #include "journal-authenticate.h" +#include "journal-qrcode.h" #include "fsprg.h" #define DEFAULT_FSS_INTERVAL_USEC (15*USEC_PER_MINUTE) @@ -52,22 +58,28 @@ static OutputMode arg_output = OUTPUT_SHORT; static bool arg_follow = false; static bool arg_show_all = false; static bool arg_no_pager = false; -static int arg_lines = -1; +static unsigned arg_lines = 0; static bool arg_no_tail = false; static bool arg_quiet = false; -static bool arg_local = false; +static bool arg_merge = false; static bool arg_this_boot = false; +static const char *arg_cursor = NULL; static const char *arg_directory = 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; +#endif +static usec_t arg_since, arg_until; +static bool arg_since_set = false, arg_until_set = false; static enum { ACTION_SHOW, ACTION_NEW_ID128, ACTION_PRINT_HEADER, ACTION_SETUP_KEYS, - ACTION_VERIFY + ACTION_VERIFY, + ACTION_DISK_USAGE, } arg_action = ACTION_SHOW; static int help(void) { @@ -78,24 +90,30 @@ static int help(void) { " --version Show package version\n" " --no-pager Do not pipe output into a pager\n" " -a --all Show all fields, including long and unprintable\n" + " -c --cursor=CURSOR Start showing entries from specified cursor\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" " -f --follow Follow journal\n" - " -n --lines=INTEGER Journal entries to show\n" + " -n --lines[=INTEGER] Number of journal entries to show\n" " --no-tail Show all lines, even in follow mode\n" " -o --output=STRING Change journal output mode (short, short-monotonic,\n" - " verbose, export, json, cat)\n" + " verbose, export, json, json-pretty, json-sse, cat)\n" " -q --quiet Don't show privilege warning\n" - " -l --local Only local entries\n" + " -m --merge Show entries from all available journals\n" " -b --this-boot Show data only from current boot\n" " -D --directory=PATH Show journal files from directory\n" " -p --priority=RANGE Show only messages within the specified priority range\n\n" "Commands:\n" " --new-id128 Generate a new 128 Bit ID\n" " --header Show journal header information\n" + " --disk-usage Show total disk usage\n" +#ifdef HAVE_GCRYPT " --setup-keys Generate new FSS key pair\n" " --interval=TIME Time interval for changing the FSS sealing key\n" " --verify Verify journal file consistency\n" - " --verify-key=KEY Specify FSS verification key\n", - program_invocation_short_name); + " --verify-key=KEY Specify FSS verification key\n" +#endif + , program_invocation_short_name); return 0; } @@ -111,7 +129,10 @@ static int parse_argv(int argc, char *argv[]) { ARG_SETUP_KEYS, ARG_INTERVAL, ARG_VERIFY, - ARG_VERIFY_KEY + ARG_VERIFY_KEY, + ARG_DISK_USAGE, + ARG_SINCE, + ARG_UNTIL }; static const struct option options[] = { @@ -121,11 +142,11 @@ static int parse_argv(int argc, char *argv[]) { { "follow", no_argument, NULL, 'f' }, { "output", required_argument, NULL, 'o' }, { "all", no_argument, NULL, 'a' }, - { "lines", required_argument, NULL, 'n' }, + { "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' }, - { "local", no_argument, NULL, 'l' }, + { "merge", no_argument, NULL, 'm' }, { "this-boot", no_argument, NULL, 'b' }, { "directory", required_argument, NULL, 'D' }, { "header", no_argument, NULL, ARG_HEADER }, @@ -134,6 +155,10 @@ static int parse_argv(int argc, char *argv[]) { { "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' }, + { "since", required_argument, NULL, ARG_SINCE }, + { "until", required_argument, NULL, ARG_UNTIL }, { NULL, 0, NULL, 0 } }; @@ -142,7 +167,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hfo:an:qlbD:p:", options, NULL)) >= 0) { + while ((c = getopt_long(argc, argv, "hfo:an::qmbD:p:c:", options, NULL)) >= 0) { switch (c) { @@ -162,10 +187,11 @@ static int parse_argv(int argc, char *argv[]) { case 'f': arg_follow = true; + signal(SIGWINCH, columns_cache_reset); break; case 'o': - arg_output = output_mode_from_string(optarg); + arg_output = output_mode_from_string(optarg); if (arg_output < 0) { log_error("Unknown output '%s'.", optarg); return -EINVAL; @@ -178,11 +204,15 @@ static int parse_argv(int argc, char *argv[]) { break; case 'n': - r = safe_atoi(optarg, &arg_lines); - if (r < 0 || arg_lines < 0) { - log_error("Failed to parse lines '%s'", optarg); - return -EINVAL; - } + if (optarg) { + r = safe_atou(optarg, &arg_lines); + if (r < 0 || arg_lines <= 0) { + log_error("Failed to parse lines '%s'", optarg); + return -EINVAL; + } + } else + arg_lines = 10; + break; case ARG_NO_TAIL: @@ -197,8 +227,8 @@ static int parse_argv(int argc, char *argv[]) { arg_quiet = true; break; - case 'l': - arg_local = true; + case 'm': + arg_merge = true; break; case 'b': @@ -209,21 +239,32 @@ static int parse_argv(int argc, char *argv[]) { arg_directory = optarg; break; + case 'c': + arg_cursor = optarg; + 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; + +#ifdef HAVE_GCRYPT case ARG_SETUP_KEYS: arg_action = ACTION_SETUP_KEYS; break; - case ARG_VERIFY: - arg_action = ACTION_VERIFY; - break; case ARG_VERIFY_KEY: arg_action = ACTION_VERIFY; arg_verify_key = optarg; + arg_merge = false; break; case ARG_INTERVAL: @@ -233,6 +274,13 @@ static int parse_argv(int argc, char *argv[]) { return -EINVAL; } break; +#else + case ARG_SETUP_KEYS: + case ARG_VERIFY_KEY: + case ARG_INTERVAL: + log_error("Forward-secure sealing not available."); + return -ENOTSUP; +#endif case 'p': { const char *dots; @@ -284,6 +332,24 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_SINCE: + 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 ARG_UNTIL: + 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 '?': return -EINVAL; @@ -293,9 +359,19 @@ static int parse_argv(int argc, char *argv[]) { } } - if (arg_follow && !arg_no_tail && arg_lines < 0) + if (arg_follow && !arg_no_tail && arg_lines <= 0) arg_lines = 10; + if (arg_since_set && arg_until_set && arg_since_set > arg_until_set) { + log_error("--since= must be before --until=."); + return -EINVAL; + } + + if (arg_cursor && arg_since_set) { + log_error("Please specify either --since= or --cursor=, not both."); + return -EINVAL; + } + return 1; } @@ -453,7 +529,7 @@ static int setup_keys(void) { size_t mpk_size, seed_size, state_size, i; uint8_t *mpk, *seed, *state; ssize_t l; - int fd = -1, r; + int fd = -1, r, attr = 0; sd_id128_t machine, boot; char *p = NULL, *k = NULL; struct FSSHeader h; @@ -476,7 +552,7 @@ static int setup_keys(void) { return log_oom(); if (access(p, F_OK) >= 0) { - log_error("Evolving key file %s exists already.", p); + log_error("Sealing key file %s exists already.", p); r = -EEXIST; goto finish; } @@ -530,6 +606,16 @@ static int setup_keys(void) { goto finish; } + /* Enable secure remove, exclusion from dump, synchronous + * writing and in-place updating */ + if (ioctl(fd, FS_IOC_GETFLAGS, &attr) < 0) + log_warning("FS_IOC_GETFLAGS failed: %m"); + + attr |= FS_SECRM_FL|FS_NODUMP_FL|FS_SYNC_FL|FS_NOCOW_FL; + + if (ioctl(fd, FS_IOC_SETFLAGS, &attr) < 0) + log_warning("FS_IOC_SETFLAGS failed: %m"); + zero(h); memcpy(h.signature, "KSHHRHLP", 8); h.machine_id = machine; @@ -564,7 +650,8 @@ static int setup_keys(void) { fprintf(stderr, "\n" "The new key pair has been generated. The " ANSI_HIGHLIGHT_ON "secret sealing key" ANSI_HIGHLIGHT_OFF " has been written to\n" - "the following local file. It should not be used on multiple hosts.\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" @@ -582,12 +669,31 @@ static int setup_keys(void) { printf("/%llx-%llx\n", (unsigned long long) n, (unsigned long long) arg_interval); if (isatty(STDOUT_FILENO)) { - char tsb[FORMAT_TIMESPAN_MAX]; + char tsb[FORMAT_TIMESPAN_MAX], *hn; fprintf(stderr, ANSI_HIGHLIGHT_OFF "\n" "The sealing key is automatically changed every %s.\n", format_timespan(tsb, sizeof(tsb), arg_interval)); + + 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 */ + setlocale(LC_CTYPE, ""); + + if (streq_ptr(nl_langinfo(CODESET), "UTF-8")) { + 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; @@ -605,7 +711,8 @@ finish: return r; #else - log_error("Forward-secure journal verification not available."); + log_error("Forward-secure sealing not available."); + return -ENOTSUP; #endif } @@ -616,16 +723,18 @@ static int verify(sd_journal *j) { assert(j); + log_show_color(true); + HASHMAP_FOREACH(f, j->files, i) { int k; - usec_t from, to, total; + usec_t first, validated, last; #ifdef HAVE_GCRYPT - if (!arg_verify_key && journal_file_fss_enabled(f)) - log_warning("Journal file %s has sealing enabled but verification key has not been passed using --verify-key=.", f->path); + 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, &from, &to, &total); + 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; @@ -636,11 +745,18 @@ static int verify(sd_journal *j) { char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX], c[FORMAT_TIMESPAN_MAX]; log_info("PASS: %s", f->path); - if (journal_file_fss_enabled(f)) - log_info("=> Validated from %s to %s, %s missing", - format_timestamp(a, sizeof(a), from), - format_timestamp(b, sizeof(b), to), - format_timespan(c, sizeof(c), total > to ? total - to : 0)); + if (arg_verify_key && JOURNAL_HEADER_SEALED(f->header)) { + if (validated > 0) { + log_info("=> Validated from %s to %s, final %s entries not sealed.", + format_timestamp(a, sizeof(a), first), + format_timestamp(b, sizeof(b), validated), + format_timespan(c, sizeof(c), last > validated ? last - validated : 0)); + } else if (last > 0) + log_info("=> No sealing yet, %s of entries not sealed.", + format_timespan(c, sizeof(c), last - first)); + else + log_info("=> No sealing yet, no entries in file."); + } } } @@ -650,11 +766,11 @@ static int verify(sd_journal *j) { int main(int argc, char *argv[]) { int r; sd_journal *j = NULL; - unsigned line = 0; bool need_seek = false; sd_id128_t previous_boot_id; bool previous_boot_id_valid = false; bool have_pager; + unsigned n_shown = 0; log_parse_environment(); log_open(); @@ -676,7 +792,7 @@ int main(int argc, char *argv[]) { if (arg_directory) r = sd_journal_open_directory(&j, arg_directory, 0); else - r = sd_journal_open(&j, arg_local ? SD_JOURNAL_LOCAL_ONLY : 0); + r = sd_journal_open(&j, arg_merge ? 0 : SD_JOURNAL_LOCAL_ONLY); if (r < 0) { log_error("Failed to open journal: %s", strerror(-r)); @@ -694,9 +810,34 @@ int main(int argc, char *argv[]) { goto finish; } + if (arg_action == ACTION_DISK_USAGE) { + uint64_t bytes; + char sbytes[FORMAT_BYTES_MAX]; + + r = sd_journal_get_usage(j, &bytes); + if (r < 0) + goto finish; + + printf("Journals take up %s on disk.\n", format_bytes(sbytes, sizeof(sbytes), bytes)); + r = 0; + goto finish; + } + #ifdef HAVE_ACL + if (access("/var/log/journal", F_OK) < 0 && geteuid() != 0 && in_group("adm") <= 0) { + log_error("Unprivileged users can't see messages unless persistent log storage is enabled. Users in the group 'adm' can always see messages."); + r = -EACCES; + goto finish; + } + if (!arg_quiet && geteuid() != 0 && in_group("adm") <= 0) - log_warning("Showing user generated messages only. Users in the group 'adm' can see all messages. Pass -q to turn this message off."); + log_warning("Showing user generated messages only. Users in the group 'adm' can see all messages. Pass -q to turn this notice off."); +#else + if (geteuid() != 0 && in_group("adm") <= 0) { + log_error("No access to messages. Only users in the group 'adm' can see messages."); + r = -EACCES; + goto finish; + } #endif r = add_this_boot(j); @@ -711,27 +852,24 @@ int main(int argc, char *argv[]) { if (r < 0) goto finish; - 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 (arg_cursor) { + r = sd_journal_seek_cursor(j, arg_cursor); if (r < 0) { - log_error("Failed to get cutoff: %s", strerror(-r)); + log_error("Failed to seek to cursor: %s", strerror(-r)); goto finish; } - if (r > 0) { - if (arg_follow) - printf("Logs begin at %s.\n", format_timestamp(start_buf, sizeof(start_buf), start)); - else - printf("Logs begin at %s, end at %s.\n", - format_timestamp(start_buf, sizeof(start_buf), start), - format_timestamp(end_buf, sizeof(end_buf), end)); + r = sd_journal_next(j); + + } else if (arg_since_set) { + r = sd_journal_seek_realtime_usec(j, arg_since); + if (r < 0) { + log_error("Failed to seek to date: %s", strerror(-r)); + goto finish; } - } + r = sd_journal_next(j); - if (arg_lines >= 0) { + } else if (arg_lines > 0) { r = sd_journal_seek_tail(j); if (r < 0) { log_error("Failed to seek to tail: %s", strerror(-r)); @@ -739,6 +877,7 @@ int main(int argc, char *argv[]) { } r = sd_journal_previous_skip(j, arg_lines); + } else { r = sd_journal_seek_head(j); if (r < 0) { @@ -757,18 +896,29 @@ int main(int argc, char *argv[]) { on_tty(); have_pager = !arg_no_pager && !arg_follow && pager_open(); - if (arg_output == OUTPUT_JSON) { - fputc('[', stdout); - fflush(stdout); + 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("Failed to get cutoff: %s", strerror(-r)); + goto finish; + } + + if (r > 0) { + if (arg_follow) + printf("---- Logs begin at %s.\n", format_timestamp(start_buf, sizeof(start_buf), start)); + else + printf("---- Logs begin at %s, end at %s.\n", + format_timestamp(start_buf, sizeof(start_buf), start), + format_timestamp(end_buf, sizeof(end_buf), end)); + } } for (;;) { - for (;;) { - sd_id128_t boot_id; - int flags = - arg_show_all * OUTPUT_SHOW_ALL | - have_pager * OUTPUT_FULL_WIDTH | - on_tty() * OUTPUT_COLOR; + while (arg_lines == 0 || arg_follow || n_shown < arg_lines) { + int flags; if (need_seek) { r = sd_journal_next(j); @@ -781,23 +931,41 @@ int main(int argc, char *argv[]) { if (r == 0) break; - 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"); + if (arg_until_set) { + usec_t usec; + + r = sd_journal_get_realtime_usec(j, &usec); + if (r < 0) { + log_error("Failed to determine timestamp: %s", strerror(-r)); + goto finish; + } + } + + if (!arg_merge) { + 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(ANSI_HIGHLIGHT_ON "---- Reboot ----" ANSI_HIGHLIGHT_OFF "\n"); - previous_boot_id = boot_id; - previous_boot_id_valid = true; + previous_boot_id = boot_id; + previous_boot_id_valid = true; + } } - line ++; + flags = + arg_show_all * OUTPUT_SHOW_ALL | + have_pager * OUTPUT_FULL_WIDTH | + on_tty() * OUTPUT_COLOR; - r = output_journal(j, arg_output, line, 0, flags); + r = output_journal(stdout, j, arg_output, 0, flags); if (r < 0) goto finish; need_seek = true; + n_shown++; } if (!arg_follow) @@ -805,14 +973,11 @@ int main(int argc, char *argv[]) { r = sd_journal_wait(j, (uint64_t) -1); if (r < 0) { - log_error("Couldn't wait for log event: %s", strerror(-r)); + log_error("Couldn't wait for journal event: %s", strerror(-r)); goto finish; } } - if (arg_output == OUTPUT_JSON) - fputs("\n]\n", stdout); - finish: if (j) sd_journal_close(j);