The journal writes revoke records, but never set
JBD2_FEATURE_INCOMPAT_REVOKE in the journal superblock.  That flag is
incompatible on purpose: a journal implementation that does not know
revoke records must refuse to replay the log rather than replay it
wrongly.  A journal made by mke2fs has it, one made otherwise may not.
ext4 sets it when it loads a journal.  Set it too when the journal
starts, and flush it before the first commit.

The journal also writes 32-bit block tags without checksums, but never
looked at the journal superblock's features.  A journal with 64-bit
tags or checksums, which recent mke2fs versions make by default, got
records that e2fsck misreads.  journal_check, which replaces
journal_log_is_empty, now also reports such a journal: any compat,
ro_compat or incompat feature other than revoke, or a V1 superblock,
which has no feature fields.  A log that is not empty still keeps the
filesystem read-only, whatever its features, as only e2fsck can replay
it.  With an empty log, the filesystem is used without a journal, with
a warning, as ext2fs did before it had one, rather than kept
read-only: a root filesystem made that way must still boot.
---
 ext2fs/ext2fs.c      | 22 ++++++++++++++++++----
 ext2fs/jbd2_format.h | 15 +++++++++++++++
 ext2fs/journal.c     | 37 +++++++++++++++++++++++++++++++------
 ext2fs/journal.h     | 18 ++++++++++++++----
 4 files changed, 78 insertions(+), 14 deletions(-)

diff --git a/ext2fs/ext2fs.c b/ext2fs/ext2fs.c
index 71ce4a45b..7532f29ce 100644
--- a/ext2fs/ext2fs.c
+++ b/ext2fs/ext2fs.c
@@ -228,6 +228,12 @@ static int journal_needs_replay;
 /* diskfs_hard_readonly before journal_needs_replay was set, for a store
    that is read-only on its own.  */
 static int journal_saved_hard_readonly;
+/* Set while the journal uses a feature this implementation does not
+   write, such as 64-bit tags or checksums.  Its log is empty, so the
+   filesystem is used without a journal, as ext2fs did before it had
+   one, rather than kept read-only: a root filesystem made that way must
+   still boot.  */
+static int journal_unsupported;
 
 /* Look at the journal's log, when the journal is not running: at mount,
    and on each remount, which re-reads the superblock while the
@@ -236,18 +242,23 @@ void
 ext2_check_journal_log (void)
 {
   struct node *jnode = NULL;
-  int empty;
+  enum journal_state state = JOURNAL_NEEDS_REPLAY;
 
   if (!(sblock->s_feature_compat & EXT3_FEATURE_COMPAT_HAS_JOURNAL)
       || ext2_journal)
     return;
 
-  empty = (!diskfs_cached_lookup (sblock->s_journal_inum, &jnode)
-          && jnode && journal_log_is_empty (jnode));
+  if (!diskfs_cached_lookup (sblock->s_journal_inum, &jnode) && jnode)
+    state = journal_check (jnode);
   if (jnode)
     diskfs_nput (jnode);
 
-  if (!empty)
+  if (state == JOURNAL_UNSUPPORTED && !journal_unsupported)
+    ext2_warning ("the journal uses features ext2fs does not write, "
+                 "such as 64-bit blocks or checksums; journaling disabled");
+  journal_unsupported = state == JOURNAL_UNSUPPORTED;
+
+  if (state == JOURNAL_NEEDS_REPLAY)
     {
       if (!journal_needs_replay)
        {
@@ -270,6 +281,9 @@ ext2_start_journal (void)
 {
   struct node *jnode = NULL;
 
+  if (journal_unsupported)
+    return;
+
   if (diskfs_cached_lookup (sblock->s_journal_inum, &jnode) || !jnode)
     {
       ext2_warning ("cannot find the journal inode; journaling disabled");
diff --git a/ext2fs/jbd2_format.h b/ext2fs/jbd2_format.h
index dd9b15fb0..33284cf31 100644
--- a/ext2fs/jbd2_format.h
+++ b/ext2fs/jbd2_format.h
@@ -37,6 +37,21 @@
 
 #define JBD2_PACKED __attribute__((packed))
 
+/* Journal superblock features.  This journal writes 32-bit block tags
+   without checksums, and revoke records.  */
+#define JBD2_FEATURE_COMPAT_CHECKSUM     0x00000001    /* v1 commit checksums 
*/
+#define JBD2_FEATURE_INCOMPAT_REVOKE     0x00000001    /* Revoke blocks */
+#define JBD2_FEATURE_INCOMPAT_64BIT      0x00000002    /* 64-bit block tags */
+#define JBD2_FEATURE_INCOMPAT_ASYNC_COMMIT 0x00000004
+#define JBD2_FEATURE_INCOMPAT_CSUM_V2    0x00000008
+#define JBD2_FEATURE_INCOMPAT_CSUM_V3    0x00000010
+#define JBD2_FEATURE_INCOMPAT_FAST_COMMIT 0x00000020
+
+/* The features a journal may have for this implementation to write it.  */
+#define JBD2_KNOWN_COMPAT_FEATURES     0
+#define JBD2_KNOWN_ROCOMPAT_FEATURES   0
+#define JBD2_KNOWN_INCOMPAT_FEATURES   JBD2_FEATURE_INCOMPAT_REVOKE
+
 /**
  * The Journal Superblock (Version 2).
  * Lives at the very start of the journal partition (typically Inode 8).
diff --git a/ext2fs/journal.c b/ext2fs/journal.c
index f78674d39..3ebd6a249 100644
--- a/ext2fs/journal.c
+++ b/ext2fs/journal.c
@@ -2905,13 +2905,13 @@ kjournald_thread (void *arg)
   return NULL;
 }
 
-int
-journal_log_is_empty (struct node *journal_inode)
+enum journal_state
+journal_check (struct node *journal_inode)
 {
   journal_superblock_t *jsb = NULL;
+  enum journal_state state = JOURNAL_NEEDS_REPLAY;
   block_t phys = 0;
   size_t amount = 0;
-  int empty = 0;
   error_t err;
 
   err = ext2_getblk (journal_inode, 0, 0, &phys);
@@ -2930,13 +2930,24 @@ journal_log_is_empty (struct node *journal_inode)
           || (be32toh (jsb->s_header[1]) != JBD2_SUPERBLOCK_V1
               && be32toh (jsb->s_header[1]) != JBD2_SUPERBLOCK_V2))
     JRNL_LOG_WARN ("Bad journal superblock.");
+  else if (jsb->s_start != 0)
+    /* Whatever its features, only e2fsck can replay it.  */
+    state = JOURNAL_NEEDS_REPLAY;
+  else if (be32toh (jsb->s_header[1]) == JBD2_SUPERBLOCK_V1)
+    /* No feature fields, and so no revoke records.  */
+    state = JOURNAL_UNSUPPORTED;
+  else if ((be32toh (jsb->s_feature_compat) & ~JBD2_KNOWN_COMPAT_FEATURES)
+          || (be32toh (jsb->s_feature_ro_compat)
+              & ~JBD2_KNOWN_ROCOMPAT_FEATURES)
+          || (be32toh (jsb->s_feature_incompat)
+              & ~JBD2_KNOWN_INCOMPAT_FEATURES))
+    state = JOURNAL_UNSUPPORTED;
   else
-    /* s_start is 0 when the log is empty.  */
-    empty = jsb->s_start == 0;
+    state = JOURNAL_USABLE;
 
   if (jsb)
     munmap (jsb, amount);
-  return empty;
+  return state;
 }
 
 journal_t *
@@ -2971,6 +2982,20 @@ journal_create (struct node *journal_inode)
 
   if (journal_load_superblock (j) != 0)
     ext2_panic ("[JOURNAL] Failed to load superblock!");
+
+  /* The commits write revoke records, which a journal implementation
+     that does not know them must not replay.  journal_check has made
+     sure this is a V2 superblock.  */
+  journal_superblock_t *jsb = (journal_superblock_t *) j->j_sb_buffer;
+  if (!(be32toh (jsb->s_feature_incompat) & JBD2_FEATURE_INCOMPAT_REVOKE))
+    {
+      jsb->s_feature_incompat =
+       htobe32 (be32toh (jsb->s_feature_incompat)
+                | JBD2_FEATURE_INCOMPAT_REVOKE);
+      if (journal_write_block (j, 0, jsb))
+       ext2_panic ("[JOURNAL] Cannot write the journal superblock!");
+      flush_to_disk ();
+    }
   j->j_last_committed_tid = j->j_transaction_sequence - 1;
   j->j_released_tid = j->j_last_committed_tid;
   if (journal_map_init (&j->j_logged, 0))
diff --git a/ext2fs/journal.h b/ext2fs/journal.h
index 959f86455..a0e9c3d27 100644
--- a/ext2fs/journal.h
+++ b/ext2fs/journal.h
@@ -53,10 +53,20 @@ typedef struct journal journal_t;
 /* Initialize the journal subsystem using the inode provided (usually Inode 
8). */
 journal_t *journal_create (struct node *journal_inode);
 
-/* Returns 1 if the log of the journal in JOURNAL_INODE is empty on disk,
-   and 0 if it holds transactions that were never replayed, or if its
-   superblock cannot be read.  JOURNAL_INODE is locked.  */
-int journal_log_is_empty (struct node *journal_inode);
+/* What journal_check finds in a journal's superblock on disk.  */
+enum journal_state
+{
+  JOURNAL_USABLE,              /* Empty log, features this journal writes.  */
+  JOURNAL_NEEDS_REPLAY,                /* The log holds transactions that were 
never
+                                  replayed, or the superblock is unreadable.  
*/
+  JOURNAL_UNSUPPORTED          /* Empty log, but a feature this journal does
+                                  not write, such as 64-bit tags or
+                                  checksums.  */
+};
+
+/* Look at the superblock of the journal in JOURNAL_INODE on disk.
+   JOURNAL_INODE is locked.  */
+enum journal_state journal_check (struct node *journal_inode);
 
 /**
  * A block device filter layer for the VFS pager's write path.
-- 
2.56.0


Reply via email to