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