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

Jan Janssen medhefgo at web.de
Thu May 30 08:24:44 PDT 2013


The format to specify the boot ID is inspired by git's ^n syntax
and it even allows to look into the future.

Unfortunately, to get a chronological list of boot IDs, we
need search through the journal. sd_journal_enumerate_unique()
doesn't help us here, because order of returned values is undefined.

To make things less painful, an initial search for the reference
boot ID is performed, which will either quickly fail so we don't have
to needlessly walk the full journal or give us a cursor from which
to start the slow lookup process.
The lookup process itself has to walk all entries because we can't
just single out some MESSAGE_ID that could get rotated away. But
that shouldn't be a problem for the most common use case of
just going back/forth a few boot IDs.
---
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               |  34 +++++---
 shell-completion/bash/journalctl |   8 +-
 src/journal/journalctl.c         | 164 +++++++++++++++++++++++++++++++++++----
 4 files changed, 179 insertions(+), 28 deletions(-)

diff --git a/TODO b/TODO
index f8a1b1b..7dd9376 100644
--- a/TODO
+++ b/TODO
@@ -267,7 +267,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..1690e00 100644
--- a/man/journalctl.xml
+++ b/man/journalctl.xml
@@ -312,23 +312,33 @@
                         </varlistentry>
 
                         <varlistentry>
-                                <term><option>-b</option></term>
-                                <term><option>--this-boot</option></term>
+                                <term><option>-b <optional><replaceable>ID</replaceable></optional></option></term>
+                                <term><option>--boot=<optional><replaceable>ID</replaceable></optional></option></term>
+                                <term><option>--this-boot=<optional><replaceable>ID</replaceable></optional></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>
+                                <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
+                                optionally followed by the ancestry identifier
+                                <literal>^n</literal>, which identifies the
+                                chronologically nth previous boot ID. Supplying
+                                a negative value will look for the chronologically
+                                next boot ID. <literal>n</literal> may be ommitted,
+                                in which case 1 is assumed. A value of 0 is
+                                equivalent to the current boot ID. If the ancestry
+                                indentifier is supplied, the boot ID itself may be
+                                ommited and the current boot is assumed.</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 +676,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 2e672fa..ee6db4a 100644
--- a/src/journal/journalctl.c
+++ b/src/journal/journalctl.c
@@ -70,7 +70,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 +109,9 @@ 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"
+               "     --this-boot         Alias for --boot (deprecated)\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'              },
                 { "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,20 +599,143 @@ static int add_matches(sd_journal *j, char **args) {
         return 0;
 }
 
-static int add_this_boot(sd_journal *j) {
+static int get_ancestor_boot_id(sd_journal *j, sd_id128_t *boot_id, int degree)
+{
+    int r;
+    sd_id128_t last_id;
+    bool boot_id_found = false;
+    char match[9+32+1] = "_BOOT_ID=";
+    _cleanup_free_ char *cursor = NULL;
+
+    assert(j);
+    assert(boot_id);
+
+    if (degree == 0)
+        return 0;
+
+    /* We could add a match for MESSAGE_ID=SD_MESSAGE_JOURNAL_START, or
+     * something equivalent to reduce the amount of entries to iterate through.
+     * Unfortunately, those specific messages could be rotated away.
+     * To speed things up a little we do an initial search for the reference
+     * boot ID and flush the matches. We still have to search through all other
+     * entries preceding/following that entry until we find our target.
+     */
+
+    sd_id128_to_string(*boot_id, match + 9);
+    r = sd_journal_add_match(j, match, strlen(match));
+    if (r < 0)
+        return r;
+
+    if (degree > 0)
+        r = sd_journal_seek_tail(j);
+    else
+        r = sd_journal_seek_head(j);
+    if (r < 0)
+        return r;
+
+    if (degree > 0)
+        r = sd_journal_previous(j);
+    else
+        r = sd_journal_next(j);
+    if (r < 0)
+        return r;
+
+    r = sd_journal_get_cursor(j, &cursor);
+    if (r < 0)
+        return r;
+
+    sd_journal_flush_matches(j);
+    r = sd_journal_seek_cursor(j, cursor);
+    if (r < 0)
+        return r;
+
+    for (;;) {
+        sd_id128_t id;
+
+        if (degree > 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)
+            degree += degree > 0 ? -1 : 1;
+
+        if (degree == 0) {
+            *boot_id = id;
+            break;
+        }
+
+        last_id = id;
+    }
+
+    if (!boot_id_found || degree != 0)
+        *boot_id = SD_ID128_NULL;
+
+    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;
+        int r, ancestor = 0;
 
         assert(j);
 
-        if (!arg_this_boot)
-                return 0;
+        if (!arg_boot_id)
+            return 0;
 
-        r = sd_id128_get_boot(&boot_id);
-        if (r < 0) {
-                log_error("Failed to get boot id: %s", strerror(-r));
+        if (arg_boot_id_descriptor) {
+            marker = strchr(arg_boot_id_descriptor, '^');
+            if (marker) {
+                *marker = '\0';
+                marker++;
+
+                if (*marker == '\0')
+                    ancestor = 1;
+                else {
+                    r = safe_atoi(marker, &ancestor);
+                    if (r < 0) {
+                        log_error("Failed to parse ancestry specifier '%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_ancestor_boot_id(j, &boot_id, ancestor);
+        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 ancestor of boot ID");
+            return -1;
         }
 
         sd_id128_to_string(boot_id, match + 9);
@@ -1152,7 +1284,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