journalctl: don't trust the per-field entry tables when looking for boot IDs
authorLennart Poettering <lennart@poettering.net>
Mon, 25 Apr 2016 16:08:42 +0000 (18:08 +0200)
committerLennart Poettering <lennart@poettering.net>
Mon, 25 Apr 2016 16:08:42 +0000 (18:08 +0200)
When appending to a journal file, journald will:

a) first, append the actual entry to the end of the journal file
b) second, add an offset reference to it to the global entry array stored at
   the beginning of the file
c) third, add offset references to it to the per-field entry array stored at
   various places of the file

The global entry array, maintained by b) is used when iterating through the
journal without matches applied.

The per-field entry array maintained by c) is used when iterating through the
journal with a match for that specific field applied.

In the wild, there are journal files where a) and b) were completed, but c)
was not before the files were abandoned. This means, that in some cases log
entries are at the end of these files that appear in the global entry array,
but not in the per-field entry array of the _BOOT_ID= field. Now, the
"journalctl --list-boots" command alternatingly uses the global entry array
and the per-field entry array of the _BOOT_ID= field. It seeks to the last
entry of a specific _BOOT_ID=field by having the right match installed, and
then jumps to the next following entry with no match installed anymore, under
the assumption this would bring it to the next boot ID. However, if the
per-field entry wasn't written fully, it might actually turn out that the
global entry array might know one more entry with the same _BOOT_ID, thus
resulting in a indefinite loop around the same _BOOT_ID.

This patch fixes that, by updating the boot search logic to always continue
reading entries until the boot ID actually changed from the previous. Thus, the
per-field entry array is used as quick jump index (i.e. as an optimization),
but not trusted otherwise.  Only the global entry array is trusted.

This replaces PR #1904, which is actually very similar to this one. However,
this one actually reads the boot ID directly from the entry header, and doesn't
try to read it at all until the read pointer is actually really located on the
first item to read.

Fixes: #617

Replaces: #1904

src/journal/journalctl.c

index 97310e2..4b2736c 100644 (file)
@@ -988,17 +988,18 @@ static void boot_id_free_all(BootId *l) {
         }
 }
 
-static int discover_next_boot(
-                sd_journal *j,
-                BootId **boot,
-                bool advance_older) {
+static int discover_next_boot(sd_journal *j,
+                sd_id128_t previous_boot_id,
+                bool advance_older,
+                BootId **ret) {
 
-        int r;
-        char match[9+32+1] = "_BOOT_ID=";
         _cleanup_free_ BootId *next_boot = NULL;
+        char match[9+32+1] = "_BOOT_ID=";
+        sd_id128_t boot_id;
+        int r;
 
         assert(j);
-        assert(boot);
+        assert(ret);
 
         /* We expect the journal to be on the last position of a boot
          * (in relation to the direction we are going), so that the next
@@ -1011,22 +1012,35 @@ static int discover_next_boot(
          * we can actually advance to a *different* boot. */
         sd_journal_flush_matches(j);
 
-        if (advance_older)
-                r = sd_journal_previous(j);
-        else
-                r = sd_journal_next(j);
-        if (r < 0)
-                return r;
-        else if (r == 0)
-                return 0; /* End of journal, yay. */
+        do {
+                if (advance_older)
+                        r = sd_journal_previous(j);
+                else
+                        r = sd_journal_next(j);
+                if (r < 0)
+                        return r;
+                else if (r == 0)
+                        return 0; /* End of journal, yay. */
+
+                r = sd_journal_get_monotonic_usec(j, NULL, &boot_id);
+                if (r < 0)
+                        return r;
+
+                /* We iterate through this in a loop, until the boot ID differs from the previous one. Note that
+                 * normally, this will only require a single iteration, as we seeked to the last entry of the previous
+                 * boot entry already. However, it might happen that the per-journal-field entry arrays are less
+                 * complete than the main entry array, and hence might reference an entry that's not actually the last
+                 * one of the boot ID as last one. Let's hence use the per-field array is initial seek position to
+                 * speed things up, but let's not trust that it is complete, and hence, manually advance as
+                 * necessary. */
+
+        } while (sd_id128_equal(boot_id, previous_boot_id));
 
         next_boot = new0(BootId, 1);
         if (!next_boot)
                 return -ENOMEM;
 
-        r = sd_journal_get_monotonic_usec(j, NULL, &next_boot->id);
-        if (r < 0)
-                return r;
+        next_boot->id = boot_id;
 
         r = sd_journal_get_realtime_usec(j, &next_boot->first);
         if (r < 0)
@@ -1058,7 +1072,7 @@ static int discover_next_boot(
         if (r < 0)
                 return r;
 
-        *boot = next_boot;
+        *ret = next_boot;
         next_boot = NULL;
 
         return 0;
@@ -1074,6 +1088,7 @@ static int get_boots(
         int r, count = 0;
         BootId *head = NULL, *tail = NULL;
         const bool advance_older = query_ref_boot && ref_boot_offset <= 0;
+        sd_id128_t previous_boot_id;
 
         assert(j);
 
@@ -1136,10 +1151,11 @@ static int get_boots(
                  * entry we have. */
         }
 
+        previous_boot_id = SD_ID128_NULL;
         for (;;) {
                 _cleanup_free_ BootId *current = NULL;
 
-                r = discover_next_boot(j, &current, advance_older);
+                r = discover_next_boot(j, previous_boot_id, advance_older, &current);
                 if (r < 0) {
                         boot_id_free_all(head);
                         return r;
@@ -1148,6 +1164,8 @@ static int get_boots(
                 if (!current)
                         break;
 
+                previous_boot_id = current->id;
+
                 if (query_ref_boot) {
                         if (!skip_once)
                                 ref_boot_offset += advance_older ? 1 : -1;