[systemd-devel] [PATCH v3] journalctl: Add support for showing messages from a previous boot

Jan Janssen medhefgo at web.de
Wed Jun 5 05:32:35 PDT 2013


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 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 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               |  58 +++++++++---
 shell-completion/bash/journalctl |   8 +-
 src/journal/journalctl.c         | 186 ++++++++++++++++++++++++++++++++++++---
 4 files changed, 228 insertions(+), 25 deletions(-)

diff --git a/TODO b/TODO
index ecc5748..2b2aafc 100644
--- a/TODO
+++ b/TODO
@@ -259,7 +259,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 d9ca0a6..6cbeb22 100644
--- a/man/journalctl.xml
+++ b/man/journalctl.xml
@@ -312,23 +312,55 @@
                         </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 specified
+                                boot <replaceable>ID</replaceable>. 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 will look for a past boot and a positive
+                                value for a future boot. The boot IDs are
+                                searched for in chronological order.</para>
+
+                                <para>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>. The boot ID may
+                                be omitted if <literal>:</literal> is provided,
+                                which will assume the current boot ID as the
+                                reference.</para>
+
+                                <para>For example, if <literal>962e0810b0c44735a6a70e7132996502</literal>
+                                were the ID of the current boot, the following
+                                are all equivalent:
+                                <option>962e0810b0c44735a6a70e7132996502</option>,
+                                <option>962e0810-b0c4-4735-a6a7-0e7132996502</option>,
+                                <option>:0</option>,
+                                <option>962e0810b0c44735a6a70e7132996502:0</option>,
+                                <option>962e0810-b0c4-4735-a6a7-0e7132996502:0</option>.
+                                Additionally, if <literal>04089164-6fb3-4826-a7d1-207b11a02169</literal>
+                                were the previous boot ID the following are equivalent:
+                                <option>040891646fb34826a7d1207b11a02169</option>,
+                                <option>04089164-6fb3-4826-a7d1-207b11a02169</option>,
+                                <option>:-1</option>, <option>:</option>,
+                                <option>962e0810b0c44735a6a70e7132996502:-1</option>,
+                                <option>962e0810-b0c4-4735-a6a7-0e7132996502:-1</option>.
+                                </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>
 
@@ -666,6 +698,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 19362ae..2c6ced9 100644
--- a/shell-completion/bash/journalctl
+++ b/shell-completion/bash/journalctl
@@ -38,17 +38,21 @@ _journalctl() {
         local field_vals= cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]}
         local -A OPTS=(
                 [STANDALONE]='-a --all --full
-                              -b --this-boot --disk-usage -f --follow --header
+                              --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 eb79c4d..91003de 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;
@@ -108,8 +110,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"
@@ -188,7 +190,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'              },
                 { "directory",    required_argument, NULL, 'D'              },
                 { "root",         required_argument, NULL, ARG_ROOT         },
@@ -218,7 +221,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) {
 
@@ -317,11 +320,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 'D':
@@ -590,11 +599,164 @@ 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;
+        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;
 
-        return add_match_this_boot(j);
+        /* 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. */
+
+        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;
+
+        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 (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;
+
+        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)) {
+                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) {
@@ -1129,7 +1291,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



More information about the systemd-devel mailing list