Unfortunately, to get a chronological list of boot IDs, we need to search through the journal. sd_journal_enumerate_unique() doesn't help us here, because the order of returned values is undefined.
An initial search for the reference boot ID is performed. We then start a search filtering by SD_MESSAGE_JOURNAL_START. This message ID should come up in every journal and is therefore a good start to reduce the amount of messages the lookup process has to walk through to find the previous/next boot IDs. Note that this or any other message ID could get rotated away, so lookup is not guaranteed to be precise. This should only affect old (and uninteresting) journal entries, though. --- Changes in v4: - search for the nth boot starting from the beginning of the journal if only ":n" with positive n is provided - further improvemed wording in the man page Changes in v3: - do filter by MESSAGE_ID and simply declare the cases where we skip boot IDs not a problem - --this-boot not documented anymore - usage of ":" instead of "^" to define relative IDs - improved wording in the man page - indentation fixes Changes in v2: - prevent unnecessary strdup by changing the argv value in place - speed up the lookup by doing an initial search for the boot ID TODO | 1 - man/journalctl.xml | 54 ++++++++--- shell-completion/bash/journalctl | 11 ++- src/journal/journalctl.c | 205 ++++++++++++++++++++++++++++++++++++--- 4 files changed, 244 insertions(+), 27 deletions(-) diff --git a/TODO b/TODO index df3725f..95580ad 100644 --- a/TODO +++ b/TODO @@ -274,7 +274,6 @@ Features: - journal-send.c, log.c: when the log socket is clogged, and we drop, count this and write a message about this when it gets unclogged again. - journal: find a way to allow dropping history early, based on priority, other rules - journal: When used on NFS, check payload hashes - - Introduce journalctl -b <nr> to show journal messages of a previous boot - journald: check whether it is OK if the client can still modify delivered journal entries - journal live copy, based on libneon (client) and libmicrohttpd (server) - journald: add kernel cmdline option to disable ratelimiting for debug purposes diff --git a/man/journalctl.xml b/man/journalctl.xml index f399868..a8af46f 100644 --- a/man/journalctl.xml +++ b/man/journalctl.xml @@ -312,23 +312,51 @@ </varlistentry> <varlistentry> - <term><option>-b</option></term> - <term><option>--this-boot</option></term> - - <listitem><para>Show data only from - current boot. This will add a match - for <literal>_BOOT_ID=</literal> for - the current boot ID of the - kernel.</para></listitem> + <term><option>-b <optional><replaceable>ID</replaceable></optional></option></term> + <term><option>--boot=<optional><replaceable>ID</replaceable></optional></option></term> + + <listitem><para>Show messages from the specified + boot <replaceable>ID</replaceable> or from + current boot if no <replaceable>ID</replaceable> + is given. This will add a match for + <literal>_BOOT_ID=</literal>.</para> + + <para>The argument is a 128 bit ID given in + short or UUID form and optionally followed by + <literal>:n</literal> which identifies the nth + boot relative to the boot ID given to the left + of <literal>:</literal>. Supplying a negative + value for n will look for a past boot and a + positive value for a future boot. The boot IDs + are searched for in chronological order. If no + number is provided after <literal>:</literal>, + <literal>-1</literal> is assumed. A value of 0 + is valid and equivalent to omitting + <literal>:0</literal>.</para> + + <para>Alternatively, the argument may constist + only of <literal>:n</literal>. In this case, a + positive value will look up the nth boot + starting from the beginning of the jouranl. A + negative value will look up a previous boot + starting from the current boot. <literal>:0</literal> + will look for the current boot ID. Thus, + <literal>:1</literal> is the first boot found in + the journal, <literal>:2</literal> the second + and so on; while <literal>:-1</literal> is the + previous boot, <literal>:-2</literal> the boot + before that and so on. Omitting a value after + <literal>:</literal> will look for the previous + boot.</para></listitem> </varlistentry> <varlistentry> <term><option>-k</option></term> <term><option>--dmesg</option></term> - <listitem><para>Show kernel messages from - current boot. This implies <option>-b</option> - and adds the match <literal>_TRANSPORT=kernel</literal>. + <listitem><para>Show only kernel messages. This + implies <option>-b</option> and adds the match + <literal>_TRANSPORT=kernel</literal>. </para></listitem> </varlistentry> @@ -694,6 +722,10 @@ <programlisting>journalctl /dev/sda</programlisting> + <para>Show all kernel logs from last boot:</para> + + <programlisting>journalctl -k -b :</programlisting> + </refsect1> <refsect1> diff --git a/shell-completion/bash/journalctl b/shell-completion/bash/journalctl index 5ab59c9..29bf6bc 100644 --- a/shell-completion/bash/journalctl +++ b/shell-completion/bash/journalctl @@ -37,19 +37,22 @@ __journal_fields=(MESSAGE{,_ID} PRIORITY CODE_{FILE,LINE,FUNC} _journalctl() { local field_vals= cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} local -A OPTS=( - [STANDALONE]='-a --all --full - --system --user - -b --this-boot --disk-usage -f --follow --header + [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' - [ARG]='-D --directory -F --field -o --output -u --unit --user-unit' + [ARG]='-b --boot --this-boot -D --directory -F --field + -o --output -u --unit --user-unit' [ARGUNKNOWN]='-c --cursor --interval -n --lines -p --priority --since --until --verify-key' ) 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 diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 1a441dd..a2aa71a 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -40,6 +40,7 @@ #endif #include <systemd/sd-journal.h> +#include <systemd/sd-messages.h> #include "log.h" #include "logs-show.h" @@ -70,7 +71,8 @@ static int arg_lines = -1; 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; @@ -112,8 +114,8 @@ static int help(void) { " --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" @@ -196,7 +198,8 @@ static int parse_argv(int argc, char *argv[]) { { "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 }, @@ -229,7 +232,7 @@ static int parse_argv(int argc, char *argv[]) { 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:an::qmb::kD:p:c:u:F:xr", options, NULL)) >= 0) { switch (c) { @@ -328,11 +331,17 @@ static int parse_argv(int argc, char *argv[]) { 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: @@ -622,11 +631,183 @@ static int add_matches(sd_journal *j, char **args) { return 0; } -static int add_this_boot(sd_journal *j) { - if (!arg_this_boot) +static int get_relative_boot_id(sd_journal *j, sd_id128_t *boot_id, int relative) +{ + int r; + sd_id128_t last_id; + bool boot_id_found = false, find_first_boot = false; + char boot_match[9+32+1] = "_BOOT_ID="; + char journal_start_match[11+32+1] = "MESSAGE_ID="; + _cleanup_free_ char *cursor = NULL; + + assert(j); + assert(boot_id); + + if (relative == 0) + return 0; + + /* We first search for the reference boot ID to get a cursor. Then + * we use a match for SD_MESSAGE_JOURNAL_START to reduce the amount of + * messages to walk through to find our target boot ID. */ + + find_first_boot = sd_id128_equal(*boot_id, SD_ID128_NULL); + if (!find_first_boot) { + sd_id128_to_string(*boot_id, boot_match + 9); + r = sd_journal_add_match(j, boot_match, strlen(boot_match)); + if (r < 0) + return r; + + if (relative < 0) + r = sd_journal_seek_tail(j); + else + r = sd_journal_seek_head(j); + if (r < 0) + return r; + + if (relative < 0) + r = sd_journal_previous(j); + else + r = sd_journal_next(j); + if (r < 0) + return r; + else if (r == 0) { + *boot_id = SD_ID128_NULL; + return 0; + } + + r = sd_journal_get_cursor(j, &cursor); + if (r < 0) + return r; + } + + sd_journal_flush_matches(j); + sd_id128_to_string(SD_MESSAGE_JOURNAL_START, journal_start_match + 11); + r = sd_journal_add_match(j, journal_start_match, strlen(journal_start_match)); + if (r < 0) + return r; + + if (find_first_boot) + r = sd_journal_seek_head(j); + else + r = sd_journal_seek_cursor(j, cursor); + if (r < 0) + return r; + + for (;;) { + sd_id128_t id; + + if (relative < 0) + r = sd_journal_previous(j); + else + r = sd_journal_next(j); + if (r < 0) + return r; + else if (r == 0) + break; + + r = sd_journal_get_monotonic_usec(j, NULL, &id); + if (r < 0) + return r; + + if (find_first_boot) { + *boot_id = id; + relative -= 1; + find_first_boot = false; + } + + if (sd_id128_equal(last_id, id)) + continue; + else if (sd_id128_equal(*boot_id, id)) + boot_id_found = true; + else if (boot_id_found) + relative += relative < 0 ? 1 : -1; + + if (relative == 0) { + *boot_id = id; + break; + } + + last_id = id; + } + + if (!boot_id_found || relative != 0) + *boot_id = SD_ID128_NULL; + + sd_journal_flush_matches(j); + 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; - return add_match_this_boot(j); + 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) { @@ -1165,7 +1346,9 @@ int main(int argc, char *argv[]) { 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; -- 1.8.3.1 _______________________________________________ systemd-devel mailing list systemd-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/systemd-devel