2023-03-14 14:12:15

by Zhang Yi

[permalink] [raw]
Subject: [PATCH 0/4] e2fsprogs: journal cycled record transactions between each mount

From: Zhang Yi <[email protected]>

Hello!

This is the e2fsprogs part of the ext4 journal_cycle_record mount
option(corresponding kernel part is at [1]). It add a new parameter to
record the journal head of a clean filesystem, mke2fs initialize it to
the start of journal area, and e2fsck check and fix it if it's bad, and
also update it after recovering journal. Basic functional tests have
been passed.

Thanks,
Yi.

[1] https://lore.kernel.org/linux-ext4/[email protected]/T/#t

Zhang Yi (4):
lib/ext2fs: record and show journal head block
debugfs/e2fsck: update the journal head block after recovery
debugfs/e2fsck: check bad s_head block number
ext4.5: document the journal_cycle_record mount options

debugfs/journal.c | 10 +++++++++-
e2fsck/journal.c | 15 ++++++++++++++-
e2fsck/recovery.c | 21 +++++++++++++++++----
lib/e2p/ljs.c | 3 +++
lib/ext2fs/kernel-jbd.h | 6 ++++--
lib/ext2fs/mkjournal.c | 1 +
misc/ext4.5.in | 6 ++++++
7 files changed, 54 insertions(+), 8 deletions(-)

--
2.31.1



2023-03-14 14:12:17

by Zhang Yi

[permalink] [raw]
Subject: [PATCH 3/4] debugfs/e2fsck: check bad s_head block number

From: Zhang Yi <[email protected]>

Check s_head in the journal superblock and fix it if this value is out
of bounds.

Signed-off-by: Zhang Yi <[email protected]>
---
debugfs/journal.c | 5 +++++
e2fsck/journal.c | 9 +++++++++
2 files changed, 14 insertions(+)

diff --git a/debugfs/journal.c b/debugfs/journal.c
index 5bc7552d..1eef3bca 100644
--- a/debugfs/journal.c
+++ b/debugfs/journal.c
@@ -631,6 +631,11 @@ static errcode_t ext2fs_journal_load(journal_t *journal)
else if (ntohl(jsb->s_maxlen) > journal->j_total_len)
return EXT2_ET_CORRUPT_JOURNAL_SB;

+ if (jsb->s_head != 0 &&
+ (ntohl(jsb->s_head) < ntohl(jsb->s_first) ||
+ ntohl(jsb->s_head) >= journal->j_total_len))
+ return EXT2_ET_CORRUPT_JOURNAL_SB;
+
journal->j_tail_sequence = ntohl(jsb->s_sequence);
journal->j_transaction_sequence = journal->j_tail_sequence;
journal->j_tail = ntohl(jsb->s_start);
diff --git a/e2fsck/journal.c b/e2fsck/journal.c
index 8950446f..4b9f00ce 100644
--- a/e2fsck/journal.c
+++ b/e2fsck/journal.c
@@ -1374,6 +1374,15 @@ static errcode_t e2fsck_journal_load(journal_t *journal)
return EXT2_ET_CORRUPT_JOURNAL_SB;
}

+ if (jsb->s_head != 0 &&
+ (ntohl(jsb->s_head) < ntohl(jsb->s_first) ||
+ ntohl(jsb->s_head) >= journal->j_total_len)) {
+ com_err(ctx->program_name, EXT2_ET_CORRUPT_JOURNAL_SB,
+ _("%s, journal head out of bounds\n"),
+ ctx->device_name);
+ return EXT2_ET_CORRUPT_JOURNAL_SB;
+ }
+
journal->j_tail_sequence = ntohl(jsb->s_sequence);
journal->j_transaction_sequence = journal->j_tail_sequence;
journal->j_tail = ntohl(jsb->s_start);
--
2.31.1


2023-03-14 14:12:19

by Zhang Yi

[permalink] [raw]
Subject: [PATCH 1/4] lib/ext2fs: record and show journal head block

From: Zhang Yi <[email protected]>

Add a new parameter into on-disk journal head block to record the head
block number of a clean journal image. This is used to support
'journal_cycle_record' mount option in kernel, which will be continue to
record new journal transactions between each mount, instead of always
recording from the first block. Note that the s_head is only uptodate
while the image is clean, we still need to walk through to find the head
block if the journal is not empty.

Signed-off-by: Zhang Yi <[email protected]>
---
lib/e2p/ljs.c | 3 +++
lib/ext2fs/kernel-jbd.h | 6 ++++--
lib/ext2fs/mkjournal.c | 1 +
3 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/lib/e2p/ljs.c b/lib/e2p/ljs.c
index 59728198..769f635a 100644
--- a/lib/e2p/ljs.c
+++ b/lib/e2p/ljs.c
@@ -108,6 +108,9 @@ void e2p_list_journal_super(FILE *f, char *journal_sb_buf,
"Journal start: %u\n",
(unsigned int)ntohl(jsb->s_sequence),
(unsigned int)ntohl(jsb->s_start));
+ if (ntohl(jsb->s_start) == 0)
+ fprintf(f, "Journal head: %u\n",
+ (unsigned int)ntohl(jsb->s_head));
if (nr_users != 1)
fprintf(f, "Journal number of users: %u\n", nr_users);
if (jsb->s_feature_compat & e2p_be32(JBD2_FEATURE_COMPAT_CHECKSUM))
diff --git a/lib/ext2fs/kernel-jbd.h b/lib/ext2fs/kernel-jbd.h
index e5695006..1a7857c0 100644
--- a/lib/ext2fs/kernel-jbd.h
+++ b/lib/ext2fs/kernel-jbd.h
@@ -221,8 +221,10 @@ typedef struct journal_superblock_s
__u8 s_padding2[3];
/* 0x0054 */
__be32 s_num_fc_blks; /* Number of fast commit blocks */
-/* 0x0058 */
- __be32 s_padding[41];
+ __be32 s_head; /* blocknr of head of log, only uptodate
+ * while the filesystem is clean */
+/* 0x005C */
+ __be32 s_padding[40];
__be32 s_checksum; /* crc32c(superblock) */

/* 0x0100 */
diff --git a/lib/ext2fs/mkjournal.c b/lib/ext2fs/mkjournal.c
index 54772dd5..24245bb7 100644
--- a/lib/ext2fs/mkjournal.c
+++ b/lib/ext2fs/mkjournal.c
@@ -79,6 +79,7 @@ errcode_t ext2fs_create_journal_superblock2(ext2_filsys fs,
jsb->s_nr_users = 0;
jsb->s_first = htonl(ext2fs_journal_sb_start(fs->blocksize) + 1);
}
+ jsb->s_head = jsb->s_first;

*ret_jsb = (char *) jsb;
return 0;
--
2.31.1


2023-03-14 14:12:20

by Zhang Yi

[permalink] [raw]
Subject: [PATCH 2/4] debugfs/e2fsck: update the journal head block after recovery

From: Zhang Yi <[email protected]>

The s_head parameter is not uptodate if the journal is not empty, so
we need to update it after recovery. We also reset it to the journal
first block if something wrong.

Signed-off-by: Zhang Yi <[email protected]>
---
debugfs/journal.c | 5 ++++-
e2fsck/journal.c | 6 +++++-
e2fsck/recovery.c | 21 +++++++++++++++++----
3 files changed, 26 insertions(+), 6 deletions(-)

diff --git a/debugfs/journal.c b/debugfs/journal.c
index 5bac0d3b..5bc7552d 100644
--- a/debugfs/journal.c
+++ b/debugfs/journal.c
@@ -636,6 +636,7 @@ static errcode_t ext2fs_journal_load(journal_t *journal)
journal->j_tail = ntohl(jsb->s_start);
journal->j_first = ntohl(jsb->s_first);
journal->j_last = ntohl(jsb->s_maxlen);
+ journal->j_head = ntohl(jsb->s_head);

return 0;
}
@@ -650,8 +651,10 @@ static void ext2fs_journal_release(ext2_filsys fs, journal_t *journal,
else if (fs->flags & EXT2_FLAG_RW) {
jsb = journal->j_superblock;
jsb->s_sequence = htonl(journal->j_tail_sequence);
- if (reset)
+ if (reset) {
+ jsb->s_head = htonl(journal->j_head);
jsb->s_start = 0; /* this marks the journal as empty */
+ }
ext2fs_journal_sb_csum_set(journal, jsb);
mark_buffer_dirty(journal->j_sb_buffer);
}
diff --git a/e2fsck/journal.c b/e2fsck/journal.c
index c7868d89..8950446f 100644
--- a/e2fsck/journal.c
+++ b/e2fsck/journal.c
@@ -1378,6 +1378,7 @@ static errcode_t e2fsck_journal_load(journal_t *journal)
journal->j_transaction_sequence = journal->j_tail_sequence;
journal->j_tail = ntohl(jsb->s_start);
journal->j_first = ntohl(jsb->s_first);
+ journal->j_head = ntohl(jsb->s_head);
if (jbd2_has_feature_fast_commit(journal)) {
if (ntohl(jsb->s_maxlen) - jbd2_journal_get_num_fc_blks(jsb)
< JBD2_MIN_JOURNAL_BLOCKS) {
@@ -1426,6 +1427,7 @@ static void e2fsck_journal_reset_super(e2fsck_t ctx, journal_superblock_t *jsb,
jsb->s_blocksize = htonl(ctx->fs->blocksize);
jsb->s_maxlen = htonl(journal->j_total_len);
jsb->s_first = htonl(1);
+ jsb->s_head = jsb->s_first;

/* Initialize the journal sequence number so that there is "no"
* chance we will find old "valid" transactions in the journal.
@@ -1474,8 +1476,10 @@ static void e2fsck_journal_release(e2fsck_t ctx, journal_t *journal,
else if (!(ctx->options & E2F_OPT_READONLY)) {
jsb = journal->j_superblock;
jsb->s_sequence = htonl(journal->j_tail_sequence);
- if (reset)
+ if (reset) {
+ jsb->s_head = htonl(journal->j_head);
jsb->s_start = 0; /* this marks the journal as empty */
+ }
e2fsck_journal_sb_csum_set(journal, jsb);
mark_buffer_dirty(journal->j_sb_buffer);
}
diff --git a/e2fsck/recovery.c b/e2fsck/recovery.c
index 8ca35271..ea3a7646 100644
--- a/e2fsck/recovery.c
+++ b/e2fsck/recovery.c
@@ -29,6 +29,7 @@ struct recovery_info
{
tid_t start_transaction;
tid_t end_transaction;
+ unsigned long head_block;

int nr_replays;
int nr_revokes;
@@ -297,9 +298,10 @@ int jbd2_journal_recover(journal_t *journal)
*/

if (!sb->s_start) {
- jbd_debug(1, "No recovery required, last transaction %d\n",
- be32_to_cpu(sb->s_sequence));
+ jbd_debug(1, "No recovery required, last transaction %d, head block %u\n",
+ be32_to_cpu(sb->s_sequence), be32_to_cpu(sb->s_head));
journal->j_transaction_sequence = be32_to_cpu(sb->s_sequence) + 1;
+ journal->j_head = be32_to_cpu(sb->s_head);
return 0;
}

@@ -318,6 +320,9 @@ int jbd2_journal_recover(journal_t *journal)
/* Restart the log at the next transaction ID, thus invalidating
* any existing commit records in the log. */
journal->j_transaction_sequence = ++info.end_transaction;
+ journal->j_head = info.head_block;
+ jbd_debug(1, "JBD2: last transaction %d, head block %lu\n",
+ journal->j_transaction_sequence, journal->j_head);

jbd2_journal_clear_revoke(journal);
err2 = sync_blockdev(journal->j_fs_dev);
@@ -358,6 +363,7 @@ int jbd2_journal_skip_recovery(journal_t *journal)
if (err) {
printk(KERN_ERR "JBD2: error %d scanning journal\n", err);
++journal->j_transaction_sequence;
+ journal->j_head = journal->j_first;
} else {
#ifdef CONFIG_JBD2_DEBUG
int dropped = info.end_transaction -
@@ -367,6 +373,7 @@ int jbd2_journal_skip_recovery(journal_t *journal)
dropped, (dropped == 1) ? "" : "s");
#endif
journal->j_transaction_sequence = ++info.end_transaction;
+ journal->j_head = info.head_block;
}

journal->j_tail = 0;
@@ -456,7 +463,7 @@ static int do_one_pass(journal_t *journal,
struct recovery_info *info, enum passtype pass)
{
unsigned int first_commit_ID, next_commit_ID;
- unsigned long next_log_block;
+ unsigned long next_log_block, head_block;
int err, success = 0;
journal_superblock_t * sb;
journal_header_t * tmp;
@@ -479,6 +486,7 @@ static int do_one_pass(journal_t *journal,
sb = journal->j_superblock;
next_commit_ID = be32_to_cpu(sb->s_sequence);
next_log_block = be32_to_cpu(sb->s_start);
+ head_block = next_log_block;

first_commit_ID = next_commit_ID;
if (pass == PASS_SCAN)
@@ -804,6 +812,7 @@ static int do_one_pass(journal_t *journal,
if (commit_time < last_trans_commit_time)
goto ignore_crc_mismatch;
info->end_transaction = next_commit_ID;
+ info->head_block = head_block;

if (!jbd2_has_feature_async_commit(journal)) {
journal->j_failed_commit =
@@ -812,8 +821,10 @@ static int do_one_pass(journal_t *journal,
break;
}
}
- if (pass == PASS_SCAN)
+ if (pass == PASS_SCAN) {
last_trans_commit_time = commit_time;
+ head_block = next_log_block;
+ }
brelse(bh);
next_commit_ID++;
continue;
@@ -863,6 +874,8 @@ static int do_one_pass(journal_t *journal,
if (pass == PASS_SCAN) {
if (!info->end_transaction)
info->end_transaction = next_commit_ID;
+ if (!info->head_block)
+ info->head_block = head_block;
} else {
/* It's really bad news if different passes end up at
* different places (but possible due to IO errors). */
--
2.31.1


2023-03-14 14:12:21

by Zhang Yi

[permalink] [raw]
Subject: [PATCH 4/4] ext4.5: document the journal_cycle_record mount options

From: Zhang Yi <[email protected]>

Signed-off-by: Zhang Yi <[email protected]>
---
misc/ext4.5.in | 6 ++++++
1 file changed, 6 insertions(+)

diff --git a/misc/ext4.5.in b/misc/ext4.5.in
index c835a344..45392d1b 100644
--- a/misc/ext4.5.in
+++ b/misc/ext4.5.in
@@ -574,6 +574,12 @@ Commit block can be written to disk without waiting for descriptor blocks. If
enabled older kernels cannot mount the device.
This will enable 'journal_checksum' internally.
.TP
+.B journal_cycle_record
+The journal continue to record transactions in a new mount behind the previous
+mount, instead of always record from the start of the logging area. This will
+alow logdump code in debugfs to dump continuous transactions between each
+mount.
+.TP
.BR barrier=0 " / " barrier=1 " / " barrier " / " nobarrier
These mount options have the same effect as in ext3. The mount options
"barrier" and "nobarrier" are added for consistency with other ext4 mount
--
2.31.1