[PATCH] fs: ext4: Add metadata checksums support

Fredrik Hallenberg megahallon at gmail.com
Fri Feb 12 17:57:47 CET 2021


Support crc32c checksums in ext4 filesystems with metadata_csum flag
active. This includes superblock, inodes, inode and block group tables,
directory blocks and journal.

Signed-off-by: Fredrik Hallenberg <megahallon at gmail.com>
---
 fs/ext4/Kconfig                   |   1 +
 fs/ext4/ext4_common.c             |  91 ++++++++++-------
 fs/ext4/ext4_common.h             |   7 +-
 fs/ext4/ext4_journal.c            | 135 ++++++++++++++++++++-----
 fs/ext4/ext4_journal.h            |  40 +++++++-
 fs/ext4/ext4_write.c              | 162 ++++++++++++++++++++++++++++--
 include/ext4fs.h                  |   7 ++
 include/ext_common.h              |  77 +++++++++++++-
 test/py/tests/test_env.py         |   3 -
 test/py/tests/test_fs/conftest.py |   4 -
 10 files changed, 441 insertions(+), 86 deletions(-)

diff --git a/fs/ext4/Kconfig b/fs/ext4/Kconfig
index 1a913d2b6d..da033428b2 100644
--- a/fs/ext4/Kconfig
+++ b/fs/ext4/Kconfig
@@ -8,6 +8,7 @@ config FS_EXT4
 config EXT4_WRITE
 	bool "Enable ext4 filesystem write support"
 	depends on FS_EXT4
+	select CRC32C
 	help
 	  This provides support for creating and writing new files to an
 	  existing ext4 filesystem partition.
diff --git a/fs/ext4/ext4_common.c b/fs/ext4/ext4_common.c
index c52cc400e1..6e23e47c97 100644
--- a/fs/ext4/ext4_common.c
+++ b/fs/ext4/ext4_common.c
@@ -45,6 +45,8 @@ __le32 *ext4fs_indir3_block;
 int ext4fs_indir3_size;
 int ext4fs_indir3_blkno = -1;
 struct ext2_inode *g_parent_inode;
+int g_parent_inode_no;
+
 static int symlinknest;
 
 #if defined(CONFIG_EXT4_WRITE)
@@ -416,32 +418,6 @@ void ext4fs_reset_inode_bmap(int inode_no, unsigned char *buffer, int index)
 		*ptr = *ptr & ~(operand);
 }
 
-uint16_t ext4fs_checksum_update(uint32_t i)
-{
-	struct ext2_block_group *desc;
-	struct ext_filesystem *fs = get_fs();
-	uint16_t crc = 0;
-	__le32 le32_i = cpu_to_le32(i);
-
-	desc = ext4fs_get_group_descriptor(fs, i);
-	if (le32_to_cpu(fs->sb->feature_ro_compat) & EXT4_FEATURE_RO_COMPAT_GDT_CSUM) {
-		int offset = offsetof(struct ext2_block_group, bg_checksum);
-
-		crc = ext2fs_crc16(~0, fs->sb->unique_id,
-				   sizeof(fs->sb->unique_id));
-		crc = ext2fs_crc16(crc, &le32_i, sizeof(le32_i));
-		crc = ext2fs_crc16(crc, desc, offset);
-		offset += sizeof(desc->bg_checksum);	/* skip checksum */
-		assert(offset == sizeof(*desc));
-		if (offset < fs->gdsize) {
-			crc = ext2fs_crc16(crc, (__u8 *)desc + offset,
-					   fs->gdsize - offset);
-		}
-	}
-
-	return crc;
-}
-
 static int check_void_in_dentry(struct ext2_dirent *dir, char *filename)
 {
 	int dentry_length;
@@ -472,6 +448,33 @@ static int check_void_in_dentry(struct ext2_dirent *dir, char *filename)
 	return 0;
 }
 
+static void ext4fs_set_dirent_tail_csum(struct ext_filesystem *fs, uint8_t *block)
+{
+	struct ext4_dir_entry_tail *tail;
+	uint32_t crc32;
+	uint32_t inode_seed;
+	__le32 inum = cpu_to_le32(g_parent_inode_no);
+	__le32 gen = g_parent_inode->generation;
+	int size;
+
+	if (!(le32_to_cpu(fs->sb->feature_ro_compat) & EXT4_FEATURE_RO_COMPAT_METADATA_CSUM))
+		return;
+
+	tail = (struct ext4_dir_entry_tail *)(block + fs->blksz
+					      - sizeof(struct ext4_dir_entry_tail));
+	if (tail->det_reserved_ft != 0xde) {
+		printf("Bad dirent tail\n");
+		return;
+	}
+
+	inode_seed = ext4_csum(fs->csum_seed, (uint8_t *)&inum, sizeof(inum));
+	inode_seed = ext4_csum(inode_seed, (uint8_t *)&gen, sizeof(gen));
+
+	size = (uint8_t *)tail - block;
+	crc32 = ext4_csum(inode_seed, block, size);
+	tail->det_checksum = cpu_to_le32(crc32);
+}
+
 int ext4fs_update_parent_dentry(char *filename, int file_type)
 {
 	unsigned int *zero_buffer = NULL;
@@ -492,6 +495,10 @@ int ext4fs_update_parent_dentry(char *filename, int file_type)
 	uint32_t new_size;
 	uint32_t new_blockcnt;
 	uint32_t directory_blocks;
+	struct ext4_dir_entry_tail *tail;
+	int has_metadata_chksum = le32_to_cpu(fs->sb->feature_ro_compat) &
+		EXT4_FEATURE_RO_COMPAT_METADATA_CSUM ? 1 : 0;
+	uint32_t max_size = fs->blksz;
 
 	zero_buffer = zalloc(fs->blksz);
 	if (!zero_buffer) {
@@ -529,12 +536,20 @@ restart_read:
 	dir = (struct ext2_dirent *)root_first_block_buffer;
 	totalbytes = 0;
 
+	if (has_metadata_chksum) {
+		tail = (struct ext4_dir_entry_tail *)(root_first_block_buffer + fs->blksz
+						      - sizeof(struct ext4_dir_entry_tail));
+		if (tail->det_reserved_ft != 0xde)
+			printf("Bad dirent tail\n");
+		max_size -= sizeof(struct ext4_dir_entry_tail);
+	}
+
 	while (le16_to_cpu(dir->direntlen) > 0) {
 		unsigned short used_len = ROUND(dir->namelen +
 		    sizeof(struct ext2_dirent), 4);
 
 		/* last entry of block */
-		if (fs->blksz - totalbytes == le16_to_cpu(dir->direntlen)) {
+		if (max_size - totalbytes == le16_to_cpu(dir->direntlen)) {
 
 			/* check if new entry fits */
 			if ((used_len + new_entry_byte_reqd) <=
@@ -608,7 +623,7 @@ restart_read:
 	if (sizeof_void_space)
 		dir->direntlen = cpu_to_le16(sizeof_void_space);
 	else
-		dir->direntlen = cpu_to_le16(fs->blksz - totalbytes);
+		dir->direntlen = cpu_to_le16(max_size - totalbytes);
 
 	dir->namelen = strlen(filename);
 	dir->filetype = file_type;
@@ -616,7 +631,8 @@ restart_read:
 	temp_dir = temp_dir + sizeof(struct ext2_dirent);
 	memcpy(temp_dir, filename, strlen(filename));
 
-	/* update or write  the 1st block of root inode */
+	/* update or write the 1st block of root inode */
+	ext4fs_set_dirent_tail_csum(fs, (uint8_t *)root_first_block_buffer);
 	if (ext4fs_put_metadata(root_first_block_buffer,
 				first_block_no_of_root))
 		goto fail;
@@ -925,6 +941,7 @@ static int unlink_filename(char *filename, unsigned int blknr)
 			/* invalidate dir entry */
 			dir->inode = 0;
 		}
+		ext4fs_set_dirent_tail_csum(fs, (uint8_t *)block_buffer);
 		if (ext4fs_put_metadata(block_buffer, blknr))
 			goto fail;
 		ret = inodeno;
@@ -1101,6 +1118,8 @@ int ext4fs_get_new_inode_no(void)
 		goto fail;
 	int has_gdt_chksum = le32_to_cpu(fs->sb->feature_ro_compat) &
 		EXT4_FEATURE_RO_COMPAT_GDT_CSUM ? 1 : 0;
+	int has_metadata_chksum = le32_to_cpu(fs->sb->feature_ro_compat) &
+		EXT4_FEATURE_RO_COMPAT_METADATA_CSUM ? 1 : 0;
 
 	if (fs->first_pass_ibmap == 0) {
 		for (i = 0; i < fs->no_blkgrp; i++) {
@@ -1112,7 +1131,7 @@ int ext4fs_get_new_inode_no(void)
 				uint16_t bg_flags = ext4fs_bg_get_flags(bgd);
 				uint64_t i_bitmap_blk =
 					ext4fs_bg_get_inode_id(bgd, fs);
-				if (has_gdt_chksum)
+				if (has_gdt_chksum || has_metadata_chksum)
 					bgd->bg_itable_unused = free_inodes;
 				if (bg_flags & EXT4_BG_INODE_UNINIT) {
 					put_ext4(i_bitmap_blk * fs->blksz,
@@ -1131,7 +1150,7 @@ int ext4fs_get_new_inode_no(void)
 							(i * inodes_per_grp);
 				fs->first_pass_ibmap++;
 				ext4fs_bg_free_inodes_dec(bgd, fs);
-				if (has_gdt_chksum)
+				if (has_gdt_chksum || has_metadata_chksum)
 					ext4fs_bg_itable_unused_dec(bgd, fs);
 				ext4fs_sb_free_inodes_dec(fs->sb);
 				status = ext4fs_devread(i_bitmap_blk *
@@ -1187,7 +1206,7 @@ restart:
 			prev_inode_bitmap_index = ibmap_idx;
 		}
 		ext4fs_bg_free_inodes_dec(bgd, fs);
-		if (has_gdt_chksum)
+		if (has_gdt_chksum || has_metadata_chksum)
 			bgd->bg_itable_unused = bgd->free_inodes;
 		ext4fs_sb_free_inodes_dec(fs->sb);
 		goto success;
@@ -1598,7 +1617,7 @@ int ext4fs_read_inode(struct ext2_data *data, int ino, struct ext2_inode *inode)
 	struct ext2_sblock *sblock = &data->sblock;
 	struct ext_filesystem *fs = get_fs();
 	int log2blksz = get_fs()->dev_desc->log2blksz;
-	int inodes_per_block, status;
+	int inodes_per_block, status, size;
 	long int blkno;
 	unsigned int blkoff;
 
@@ -1633,9 +1652,9 @@ int ext4fs_read_inode(struct ext2_data *data, int ino, struct ext2_inode *inode)
 	free(blkgrp);
 
 	/* Read the inode. */
+	size = min(sizeof(struct ext2_inode), fs->inodesz);
 	status = ext4fs_devread((lbaint_t)blkno << (LOG2_BLOCK_SIZE(data) -
-				log2blksz), blkoff,
-				sizeof(struct ext2_inode), (char *)inode);
+				log2blksz), blkoff, size, (char *)inode);
 	if (status == 0)
 		return 0;
 
@@ -2368,7 +2387,7 @@ int ext4fs_mount(unsigned part_length)
 	struct ext2_data *data;
 	int status;
 	struct ext_filesystem *fs = get_fs();
-	data = zalloc(SUPERBLOCK_SIZE);
+	data = zalloc(sizeof(struct ext2_data));
 	if (!data)
 		return 0;
 
diff --git a/fs/ext4/ext4_common.h b/fs/ext4/ext4_common.h
index beaee9c80b..afcd9a072d 100644
--- a/fs/ext4/ext4_common.h
+++ b/fs/ext4/ext4_common.h
@@ -41,6 +41,8 @@
 #define SUPERBLOCK_SIZE	1024
 #define F_FILE			1
 
+#define EXT4_OLD_INODE_SIZE 128
+
 static inline void *zalloc(size_t size)
 {
 	void *p = memalign(ARCH_DMA_MINALIGN, size);
@@ -59,7 +61,6 @@ int ext4fs_iterate_dir(struct ext2fs_node *dir, char *name,
 
 #if defined(CONFIG_EXT4_WRITE)
 uint32_t ext4fs_div_roundup(uint32_t size, uint32_t n);
-uint16_t ext4fs_checksum_update(unsigned int i);
 int ext4fs_get_parent_inode_num(const char *dirname, char *dname, int flags);
 int ext4fs_update_parent_dentry(char *filename, int file_type);
 uint32_t ext4fs_get_new_blk_no(void);
@@ -86,5 +87,9 @@ uint64_t ext4fs_sb_get_free_blocks(const struct ext2_sblock *sb);
 void ext4fs_sb_set_free_blocks(struct ext2_sblock *sb, uint64_t free_blocks);
 uint32_t ext4fs_bg_get_free_blocks(const struct ext2_block_group *bg,
 	const struct ext_filesystem *fs);
+void ext4fs_set_superblock_csum(struct ext2_sblock *sb);
+uint32_t ext4_csum(uint32_t crc, const uint8_t *data, unsigned int length);
+void ext4fs_set_journal_superblock_csum(struct journal_superblock_t *sb);
+
 #endif
 #endif
diff --git a/fs/ext4/ext4_journal.c b/fs/ext4/ext4_journal.c
index 1a340b4764..aace78a795 100644
--- a/fs/ext4/ext4_journal.c
+++ b/fs/ext4/ext4_journal.c
@@ -184,6 +184,7 @@ int ext4fs_log_journal(char *journal_buffer, uint32_t blknr)
 int ext4fs_put_metadata(char *metadata_buffer, uint32_t blknr)
 {
 	struct ext_filesystem *fs = get_fs();
+
 	if (!metadata_buffer) {
 		printf("Invalid input arguments %s\n", __func__);
 		return -EINVAL;
@@ -336,6 +337,7 @@ void recover_transaction(int prev_desc_logical_no)
 	int ofs, flags;
 	int i;
 	struct ext3_journal_block_tag *tag;
+	int tag_size = fs->journal_tag_size;
 	char *temp_buff = zalloc(fs->blksz);
 	char *metadata_buff = zalloc(fs->blksz);
 	if (!temp_buff || !metadata_buff)
@@ -353,12 +355,14 @@ void recover_transaction(int prev_desc_logical_no)
 
 	do {
 		tag = (struct ext3_journal_block_tag *)(p_jdb + ofs);
-		ofs += sizeof(struct ext3_journal_block_tag);
+		ofs += tag_size;
 
 		if (ofs > fs->blksz)
 			break;
 
-		flags = be32_to_cpu(tag->flags);
+		flags = be16_to_cpu(tag->flags);
+
+		/* skip uuid */
 		if (!(flags & EXT3_JOURNAL_FLAG_SAME_UUID))
 			ofs += 16;
 
@@ -388,6 +392,15 @@ void print_jrnl_status(int recovery_flag)
 		printf("Journal Scan Completed\n");
 }
 
+void ext4fs_set_journal_superblock_csum(struct journal_superblock_t *sb)
+{
+	uint32_t csum;
+
+	sb->s_checksum = 0;
+	csum = ext4_csum(~0, (uint8_t *)sb, sizeof(struct journal_superblock_t));
+	sb->s_checksum = cpu_to_be32(csum);
+}
+
 int ext4fs_check_journal_state(int recovery_flag)
 {
 	int i;
@@ -405,9 +418,7 @@ int ext4fs_check_journal_state(int recovery_flag)
 	char *temp_buff = NULL;
 	char *temp_buff1 = NULL;
 	struct ext_filesystem *fs = get_fs();
-
-	if (le32_to_cpu(fs->sb->feature_ro_compat) & EXT4_FEATURE_RO_COMPAT_METADATA_CSUM)
-		return 0;
+	int tag_size = sizeof(struct ext3_journal_block_tag3);
 
 	temp_buff = zalloc(fs->blksz);
 	if (!temp_buff)
@@ -425,6 +436,11 @@ int ext4fs_check_journal_state(int recovery_flag)
 		       temp_buff);
 	jsb = (struct journal_superblock_t *) temp_buff;
 
+	if (be32_to_cpu(jsb->s_header.h_magic) != EXT3_JOURNAL_MAGIC_NUMBER) {
+		printf("Bad ext4 journal magic number\n");
+		return -ENODEV;
+	}
+
 	if (le32_to_cpu(fs->sb->feature_incompat) & EXT3_FEATURE_INCOMPAT_RECOVER) {
 		if (recovery_flag == RECOVER)
 			printf("Recovery required\n");
@@ -437,10 +453,23 @@ int ext4fs_check_journal_state(int recovery_flag)
 	if (be32_to_cpu(jsb->s_start) == 0)
 		goto end;
 
-	if (!(jsb->s_feature_compat &
-				cpu_to_be32(JBD2_FEATURE_COMPAT_CHECKSUM)))
-		jsb->s_feature_compat |=
-				cpu_to_be32(JBD2_FEATURE_COMPAT_CHECKSUM);
+	fs->journal_csum_seed = ext4_csum(~0, jsb->s_uuid, sizeof(jsb->s_uuid));
+
+	if (be32_to_cpu(jsb->s_feature_incompat) & JBD2_FEATURE_INCOMPAT_CSUM_V3) {
+		fs->journal_tag_size = sizeof(struct ext3_journal_block_tag3);
+		fs->journal_csum_version = 3;
+	} else {
+		fs->journal_tag_size = sizeof(struct ext3_journal_block_tag);
+		fs->journal_csum_version = 0;
+		if (be32_to_cpu(jsb->s_feature_compat) & JBD2_FEATURE_COMPAT_CHECKSUM)
+			fs->journal_csum_version = 1;
+		if (be32_to_cpu(jsb->s_feature_incompat) & JBD2_FEATURE_INCOMPAT_CSUM_V2) {
+			fs->journal_tag_size += sizeof(uint16_t);
+			fs->journal_csum_version = 2;
+		}
+		if (!(be32_to_cpu(jsb->s_feature_incompat) & JBD2_FEATURE_INCOMPAT_64BIT))
+			fs->journal_tag_size -= sizeof(uint32_t);
+	}
 
 	i = be32_to_cpu(jsb->s_first);
 	while (1) {
@@ -468,10 +497,11 @@ int ext4fs_check_journal_state(int recovery_flag)
 			do {
 				tag = (struct ext3_journal_block_tag *)
 				    (p_jdb + ofs);
-				ofs += sizeof(struct ext3_journal_block_tag);
+				ofs += tag_size;
 				if (ofs > fs->blksz)
 					break;
-				flags = be32_to_cpu(tag->flags);
+				flags = be16_to_cpu(tag->flags);
+				/* skip uuid */
 				if (!(flags & EXT3_JOURNAL_FLAG_SAME_UUID))
 					ofs += 16;
 				i++;
@@ -532,16 +562,15 @@ end:
 		fs->sb->feature_incompat = cpu_to_le32(new_feature_incompat);
 
 		/* Update the super block */
-		put_ext4((uint64_t) (SUPERBLOCK_SIZE),
-			 (struct ext2_sblock *)fs->sb,
-			 (uint32_t) SUPERBLOCK_SIZE);
+		ext4fs_set_superblock_csum(fs->sb);
+		put_ext4(SUPERBLOCK_SIZE, fs->sb, SUPERBLOCK_SIZE);
 		ext4_read_superblock((char *)fs->sb);
 
 		blknr = read_allocated_block(&inode_journal,
 					 EXT2_JOURNAL_SUPERBLOCK, NULL);
+		ext4fs_set_journal_superblock_csum(jsb);
 		put_ext4((uint64_t) ((uint64_t)blknr * (uint64_t)fs->blksz),
-			 (struct journal_superblock_t *)temp_buff,
-			 (uint32_t) fs->blksz);
+			 jsb, (uint32_t)fs->blksz);
 		ext4fs_free_revoke_blks();
 	}
 	free(temp_buff);
@@ -550,18 +579,58 @@ end:
 	return 0;
 }
 
+static void descriptor_block_csum_set(const uint8_t *block)
+{
+	struct ext_filesystem *fs = get_fs();
+	struct ext3_journal_block_tail *tail;
+	__u32 csum;
+
+	if (fs->journal_csum_version < 2)
+		return;
+
+	tail = (struct ext3_journal_block_tail *)(block + fs->blksz -
+						  sizeof(struct ext3_journal_block_tail));
+	tail->t_checksum = 0;
+	csum = ext4_csum(fs->journal_csum_seed, block, fs->blksz);
+	tail->t_checksum = cpu_to_be32(csum);
+}
+
+static void block_tag_csum_set(struct ext3_journal_block_tag3 *tag, const uint8_t *data,
+			       uint32_t sequence)
+{
+	struct ext_filesystem *fs = get_fs();
+	struct ext3_journal_block_tag *tag2 = (struct ext3_journal_block_tag *)tag;
+	uint32_t crc32;
+	__be32 seq;
+
+	if (fs->journal_csum_version < 2)
+		return;
+
+	seq = cpu_to_be32(sequence);
+	crc32 = ext4_csum(fs->journal_csum_seed, (uint8_t *)&seq, sizeof(seq));
+	crc32 = ext4_csum(crc32, data, fs->blksz);
+
+	if (fs->journal_csum_version == 3)
+		tag->checksum = cpu_to_be32(crc32);
+	else
+		tag2->checksum = cpu_to_be16(crc32 & 0xffff);
+}
+
 static void update_descriptor_block(long int blknr)
 {
 	int i;
 	long int jsb_blknr;
 	struct journal_header_t jdb;
-	struct ext3_journal_block_tag tag;
+	struct ext3_journal_block_tag3 tag;
 	struct ext2_inode inode_journal;
 	struct journal_superblock_t *jsb = NULL;
 	char *buf = NULL;
 	char *temp = NULL;
 	struct ext_filesystem *fs = get_fs();
 	char *temp_buff = zalloc(fs->blksz);
+	int tag_size = fs->journal_tag_size;
+	uint32_t sequence;
+
 	if (!temp_buff)
 		return;
 
@@ -575,6 +644,9 @@ static void update_descriptor_block(long int blknr)
 	jdb.h_blocktype = cpu_to_be32(EXT3_JOURNAL_DESCRIPTOR_BLOCK);
 	jdb.h_magic = cpu_to_be32(EXT3_JOURNAL_MAGIC_NUMBER);
 	jdb.h_sequence = jsb->s_sequence;
+
+	sequence = be32_to_cpu(jsb->s_sequence);
+
 	buf = zalloc(fs->blksz);
 	if (!buf) {
 		free(temp_buff);
@@ -589,16 +661,19 @@ static void update_descriptor_block(long int blknr)
 			break;
 
 		tag.block = cpu_to_be32(journal_ptr[i]->blknr);
-		tag.flags = cpu_to_be32(EXT3_JOURNAL_FLAG_SAME_UUID);
-		memcpy(temp, &tag, sizeof(struct ext3_journal_block_tag));
-		temp = temp + sizeof(struct ext3_journal_block_tag);
+		tag.flags = cpu_to_be16(EXT3_JOURNAL_FLAG_SAME_UUID);
+		block_tag_csum_set(&tag, (uint8_t *)journal_ptr[i]->buf, sequence);
+		memcpy(temp, &tag, tag_size);
+		temp = temp + tag_size;
 	}
 
 	tag.block = cpu_to_be32(journal_ptr[--i]->blknr);
-	tag.flags = cpu_to_be32(EXT3_JOURNAL_FLAG_LAST_TAG);
-	memcpy(temp - sizeof(struct ext3_journal_block_tag), &tag,
-	       sizeof(struct ext3_journal_block_tag));
-	put_ext4((uint64_t) ((uint64_t)blknr * (uint64_t)fs->blksz), buf, (uint32_t) fs->blksz);
+	tag.flags = cpu_to_be16(EXT3_JOURNAL_FLAG_LAST_TAG);
+	block_tag_csum_set(&tag, (uint8_t *)journal_ptr[i]->buf, sequence);
+	memcpy(temp - tag_size, &tag, tag_size);
+
+	descriptor_block_csum_set((uint8_t *)buf);
+	put_ext4((uint64_t)((uint64_t)blknr * (uint64_t)fs->blksz), buf, fs->blksz);
 
 	free(temp_buff);
 	free(buf);
@@ -606,7 +681,7 @@ static void update_descriptor_block(long int blknr)
 
 static void update_commit_block(long int blknr)
 {
-	struct journal_header_t jdb;
+	struct ext3_journal_commit_header jdb;
 	struct ext_filesystem *fs = get_fs();
 	char *buf = NULL;
 	struct ext2_inode inode_journal;
@@ -632,7 +707,15 @@ static void update_commit_block(long int blknr)
 		free(temp_buff);
 		return;
 	}
-	memcpy(buf, &jdb, sizeof(struct journal_header_t));
+
+	if (fs->journal_csum_version > 1) {
+		uint32_t csum = ext4_csum(fs->journal_csum_seed, (uint8_t *)temp_buff, fs->blksz);
+
+		jdb.h_chksum[0] = cpu_to_be32(csum);
+	}
+
+	memcpy(buf, &jdb, sizeof(struct ext3_journal_commit_header));
+
 	put_ext4((uint64_t) ((uint64_t)blknr * (uint64_t)fs->blksz), buf, (uint32_t) fs->blksz);
 
 	free(temp_buff);
diff --git a/fs/ext4/ext4_journal.h b/fs/ext4/ext4_journal.h
index 43fb8e7664..9bdbf66971 100644
--- a/fs/ext4/ext4_journal.h
+++ b/fs/ext4/ext4_journal.h
@@ -22,7 +22,6 @@
 #define EXT2_JOURNAL_INO		8	/* Journal inode */
 #define EXT2_JOURNAL_SUPERBLOCK	0	/* Journal  Superblock number */
 
-#define JBD2_FEATURE_COMPAT_CHECKSUM	0x00000001
 #define EXT3_JOURNAL_MAGIC_NUMBER	0xc03b3998U
 #define TRANSACTION_RUNNING		1
 #define TRANSACTION_COMPLETE		0
@@ -37,6 +36,14 @@
 #define EXT3_JOURNAL_FLAG_DELETED	4
 #define EXT3_JOURNAL_FLAG_LAST_TAG	8
 
+#define JBD2_FEATURE_COMPAT_CHECKSUM		0x00000001
+
+#define JBD2_FEATURE_INCOMPAT_REVOKE		0x00000001
+#define JBD2_FEATURE_INCOMPAT_64BIT		0x00000002
+#define JBD2_FEATURE_INCOMPAT_ASYNC_COMMIT	0x00000004
+#define JBD2_FEATURE_INCOMPAT_CSUM_V2		0x00000008
+#define JBD2_FEATURE_INCOMPAT_CSUM_V3		0x00000010
+
 /* Maximum entries in 1 journal transaction */
 #define MAX_JOURNAL_ENTRIES 100
 struct journal_log {
@@ -90,16 +97,45 @@ struct journal_superblock_t {
 	__be32 s_max_trans_data;	/* Limit of data blocks per trans. */
 
 	/* 0x0050 */
-	__be32 s_padding[44];
+	__u8 s_checksum_type;	/* checksum type */
+	__u8 s_padding2[3];
+	__u32 s_padding[42];
+	__be32 s_checksum;	/* crc32c(superblock) */
 
 	/* 0x0100 */
 	__u8 s_users[16 * 48];	/* ids of all fs'es sharing the log */
 	/* 0x0400 */
 } ;
 
+struct ext3_journal_commit_header {
+	__be32 h_magic;
+	__be32 h_blocktype;
+	__be32 h_sequence;
+	unsigned char h_chksum_type;
+	unsigned char h_chksum_size;
+	unsigned char h_padding[2];
+	__be32 h_chksum[8];
+	__be64 h_commit_sec;
+	__be32 h_commit_nsec;
+};
+
 struct ext3_journal_block_tag {
+	__be32 block;
+	__be16 checksum;	/* truncated crc32c(uuid+seq+block) */
+	__be16 flags;
+	__be32 blocknr_high;	/* only used when INCOMPAT_64BIT is set */
+};
+
+/* Use if FEATURE_INCOMPAT_CSUM_V3 is set */
+struct ext3_journal_block_tag3 {
 	__be32 block;
 	__be32 flags;
+	__be32 blocknr_high;
+	__be32 checksum;	/* crc32c(uuid+seq+block) */
+};
+
+struct ext3_journal_block_tail {
+	__be32 t_checksum;	/* crc32c(uuid+descr_block) */
 };
 
 struct journal_revoke_header_t {
diff --git a/fs/ext4/ext4_write.c b/fs/ext4/ext4_write.c
index f22af45d1b..fc248c0bc2 100644
--- a/fs/ext4/ext4_write.c
+++ b/fs/ext4/ext4_write.c
@@ -30,6 +30,7 @@
 #include <linux/stat.h>
 #include <div64.h>
 #include "ext4_common.h"
+#include <u-boot/crc.h>
 
 static inline void ext4fs_sb_free_inodes_inc(struct ext2_sblock *sb)
 {
@@ -67,6 +68,139 @@ static inline void ext4fs_bg_free_blocks_inc
 		bg->free_blocks_high = cpu_to_le16(free_blocks >> 16);
 }
 
+uint32_t ext4_csum(uint32_t crc, const uint8_t *data, unsigned int length)
+{
+	static uint32_t table[256];
+	static int init;
+
+	if (!init) {
+		crc32c_init(table, 0x82f63b78);
+		init = 1;
+	}
+
+	return crc32c_cal(crc, (const char *)data, length, table);
+}
+
+void ext4fs_set_superblock_csum(struct ext2_sblock *sb)
+{
+	int offset;
+
+	if (!(le32_to_cpu(sb->feature_ro_compat) & EXT4_FEATURE_RO_COMPAT_METADATA_CSUM))
+		return;
+
+	offset = offsetof(struct ext2_sblock, checksum);
+	sb->checksum = cpu_to_le32(ext4_csum(~0, (uint8_t *)sb, offset));
+}
+
+static uint32_t ext4_inode_csum(struct ext2_inode *inode, unsigned int inode_no)
+{
+	struct ext_filesystem *fs = get_fs();
+	uint32_t crc32;
+	uint16_t dummy_csum = 0;
+	unsigned int dummy_size = sizeof(dummy_csum);
+	int offset = offsetof(struct ext2_inode, checksum_lo);
+	__le32 inum = cpu_to_le32(inode_no);
+	__le32 gen = inode->generation;
+	uint32_t inode_seed;
+
+	inode_seed = ext4_csum(fs->csum_seed, (uint8_t *)&inum, sizeof(inum));
+	inode_seed = ext4_csum(inode_seed, (uint8_t *)&gen, sizeof(gen));
+
+	crc32 = ext4_csum(inode_seed, (uint8_t *)inode, offset);
+	crc32 = ext4_csum(crc32, (uint8_t *)&dummy_csum, dummy_size);
+	offset += dummy_size;
+	crc32 = ext4_csum(crc32, (uint8_t *)inode + offset, EXT4_OLD_INODE_SIZE - offset);
+
+	if (fs->inodesz > EXT4_OLD_INODE_SIZE) {
+		uint16_t extra_size;
+
+		offset = offsetof(struct ext2_inode, checksum_hi);
+		crc32 = ext4_csum(crc32, (uint8_t *)inode + EXT4_OLD_INODE_SIZE,
+				  offset - EXT4_OLD_INODE_SIZE);
+		extra_size = le16_to_cpu(inode->extra_isize);
+		if (extra_size + EXT4_OLD_INODE_SIZE >=
+		    offsetof(struct ext2_inode, checksum_hi) + sizeof(inode->checksum_hi)) {
+			crc32 = ext4_csum(crc32, (uint8_t *)&dummy_csum, dummy_size);
+			offset += dummy_size;
+		}
+		crc32 = ext4_csum(crc32, (uint8_t *)inode + offset, fs->inodesz - offset);
+	}
+
+	return crc32;
+}
+
+static void ext4fs_set_inode_csum(struct ext2_inode *inode, unsigned int inode_no)
+
+{
+	struct ext_filesystem *fs = get_fs();
+	uint32_t crc32;
+
+	if (!(le32_to_cpu(fs->sb->feature_ro_compat) & EXT4_FEATURE_RO_COMPAT_METADATA_CSUM))
+		return;
+
+	crc32 = ext4_inode_csum(inode, inode_no);
+	inode->checksum_lo = cpu_to_le16(crc32 & 0xffff);
+
+	if (fs->inodesz > EXT4_OLD_INODE_SIZE) {
+		uint16_t extra_size = le16_to_cpu(inode->extra_isize);
+
+		if (extra_size + EXT4_OLD_INODE_SIZE >=
+		    offsetof(struct ext2_inode, checksum_hi) + sizeof(inode->checksum_hi))
+			inode->checksum_hi = cpu_to_le16(crc32 >> 16);
+	}
+}
+
+static void ext4fs_set_group_descriptor_csum(uint32_t i)
+{
+	struct ext_filesystem *fs = get_fs();
+	struct ext2_block_group *desc = ext4fs_get_group_descriptor(fs, i);
+	__le32 le32_i = cpu_to_le32(i);
+	int offset = offsetof(struct ext2_block_group, bg_checksum);
+	uint16_t crc = 0;
+
+	if (le32_to_cpu(fs->sb->feature_ro_compat) & EXT4_FEATURE_RO_COMPAT_METADATA_CSUM) {
+		uint32_t crc32;
+		uint16_t dummy_csum = 0;
+		int sz;
+
+		/* inode bitmap */
+		sz = fs->sb->inodes_per_group / 8;
+		crc32 = ext4_csum(fs->csum_seed, fs->inode_bmaps[i], sz);
+		desc->bg_inode_id_csum = cpu_to_le16(crc32 & 0xFFFF);
+		if (fs->gdsize >= EXT4_BG_INODE_BITMAP_CSUM_HI_END)
+			desc->bg_inode_id_csum_high = cpu_to_le16(crc32 >> 16);
+
+		/* block bitmap */
+		sz = fs->sb->fragments_per_group / 8;
+		crc32 = ext4_csum(fs->csum_seed, fs->blk_bmaps[i], sz);
+		desc->bg_block_id_csum = cpu_to_le16(crc32 & 0xFFFF);
+		if (fs->gdsize >= EXT4_BG_BLOCK_BITMAP_CSUM_HI_END)
+			desc->bg_block_id_csum_high = cpu_to_le16(crc32 >> 16);
+
+		crc32 = ext4_csum(fs->csum_seed, (uint8_t *)&le32_i, sizeof(le32_i));
+		crc32 = ext4_csum(crc32, (uint8_t *)desc, offset);
+		crc32 = ext4_csum(crc32, (uint8_t *)&dummy_csum, sizeof(dummy_csum));
+		offset += sizeof(dummy_csum);
+		if (offset < fs->gdsize) {
+			crc32 = ext4_csum(crc32, (uint8_t *)desc + offset,
+					  fs->gdsize - offset);
+		}
+		crc = crc32 & 0xffff;
+	} else if (le32_to_cpu(fs->sb->feature_ro_compat) & EXT4_FEATURE_RO_COMPAT_GDT_CSUM) {
+		crc = ext2fs_crc16(~0, fs->sb->unique_id,
+				   sizeof(fs->sb->unique_id));
+		crc = ext2fs_crc16(crc, &le32_i, sizeof(le32_i));
+		crc = ext2fs_crc16(crc, desc, offset);
+		offset += sizeof(desc->bg_checksum);	/* skip checksum */
+		assert(offset == sizeof(*desc));
+		if (offset < fs->gdsize) {
+			crc = ext2fs_crc16(crc, (__u8 *)desc + offset,
+					   fs->gdsize - offset);
+		}
+	}
+	desc->bg_checksum = cpu_to_le16(crc);
+}
+
 static void ext4fs_update(void)
 {
 	short i;
@@ -74,14 +208,14 @@ static void ext4fs_update(void)
 	struct ext_filesystem *fs = get_fs();
 	struct ext2_block_group *bgd = NULL;
 
-	/* update  super block */
-	put_ext4((uint64_t)(SUPERBLOCK_SIZE),
-		 (struct ext2_sblock *)fs->sb, (uint32_t)SUPERBLOCK_SIZE);
+	/* update super block */
+	ext4fs_set_superblock_csum(fs->sb);
+	put_ext4(SUPERBLOCK_SIZE, fs->sb, SUPERBLOCK_SIZE);
 
 	/* update block bitmaps */
 	for (i = 0; i < fs->no_blkgrp; i++) {
 		bgd = ext4fs_get_group_descriptor(fs, i);
-		bgd->bg_checksum = cpu_to_le16(ext4fs_checksum_update(i));
+		ext4fs_set_group_descriptor_csum(i);
 		uint64_t b_bitmap_blk = ext4fs_bg_get_block_id(bgd, fs);
 		put_ext4(b_bitmap_blk * fs->blksz,
 			 fs->blk_bmaps[i], fs->blksz);
@@ -613,6 +747,11 @@ int ext4fs_init(void)
 	if (!ext4_read_superblock((char *)fs->sb))
 		goto fail;
 
+	if (le32_to_cpu(fs->sb->feature_incompat) & EXT4_FEATURE_INCOMPAT_CSUM_SEED)
+		fs->csum_seed = le32_to_cpu(fs->sb->checksum_seed);
+	else if (le32_to_cpu(fs->sb->feature_ro_compat) & EXT4_FEATURE_RO_COMPAT_METADATA_CSUM)
+		fs->csum_seed = ext4_csum(~0, fs->sb->unique_id, sizeof(fs->sb->unique_id));
+
 	/* init journal */
 	if (ext4fs_init_journal())
 		goto fail;
@@ -713,6 +852,7 @@ void ext4fs_deinit(void)
 			       temp_buff);
 		jsb = (struct journal_superblock_t *)temp_buff;
 		jsb->s_start = 0;
+		ext4fs_set_journal_superblock_csum(jsb);
 		put_ext4((uint64_t) ((uint64_t)blknr * (uint64_t)fs->blksz),
 			 (struct journal_superblock_t *)temp_buff, fs->blksz);
 		free(temp_buff);
@@ -724,8 +864,8 @@ void ext4fs_deinit(void)
 	new_feature_incompat = le32_to_cpu(fs->sb->feature_incompat);
 	new_feature_incompat &= ~EXT3_FEATURE_INCOMPAT_RECOVER;
 	fs->sb->feature_incompat = cpu_to_le32(new_feature_incompat);
-	put_ext4((uint64_t)(SUPERBLOCK_SIZE),
-		 (struct ext2_sblock *)fs->sb, (uint32_t)SUPERBLOCK_SIZE);
+	ext4fs_set_superblock_csum(fs->sb);
+	put_ext4(SUPERBLOCK_SIZE, fs->sb, SUPERBLOCK_SIZE);
 	free(fs->sb);
 	fs->sb = NULL;
 
@@ -881,17 +1021,15 @@ int ext4fs_write(const char *fname, const char *buffer,
 		return -1;
 	}
 
-	if (le32_to_cpu(fs->sb->feature_ro_compat) & EXT4_FEATURE_RO_COMPAT_METADATA_CSUM) {
-		printf("Unsupported feature metadata_csum found, not writing.\n");
-		return -1;
-	}
-
 	inodes_per_block = fs->blksz / fs->inodesz;
 	parent_inodeno = ext4fs_get_parent_inode_num(fname, filename, F_FILE);
 	if (parent_inodeno == -1)
 		goto fail;
 	if (ext4fs_iget(parent_inodeno, g_parent_inode))
 		goto fail;
+
+	g_parent_inode_no = parent_inodeno;
+
 	/* do not mess up a directory using hash trees */
 	if (le32_to_cpu(g_parent_inode->flags) & EXT4_INDEX_FL) {
 		printf("hash tree directory\n");
@@ -963,6 +1101,8 @@ int ext4fs_write(const char *fname, const char *buffer,
 	file_inode->blockcnt = cpu_to_le32((blks_reqd_for_file * fs->blksz) >>
 					   LOG2_SECTOR_SIZE);
 
+	ext4fs_set_inode_csum(file_inode, inodeno);
+
 	temp_ptr = zalloc(fs->blksz);
 	if (!temp_ptr)
 		goto fail;
diff --git a/include/ext4fs.h b/include/ext4fs.h
index cb5d9cc0a5..5485388166 100644
--- a/include/ext4fs.h
+++ b/include/ext4fs.h
@@ -37,6 +37,7 @@ struct disk_partition;
 #define EXT4_FEATURE_RO_COMPAT_METADATA_CSUM 0x0400
 #define EXT4_FEATURE_INCOMPAT_EXTENTS	0x0040
 #define EXT4_FEATURE_INCOMPAT_64BIT	0x0080
+#define EXT4_FEATURE_INCOMPAT_CSUM_SEED	0x2000
 #define EXT4_INDIRECT_BLOCKS		12
 
 #define EXT4_BG_INODE_UNINIT		0x0001
@@ -117,6 +118,11 @@ struct ext_filesystem {
 
 	/* Block Device Descriptor */
 	struct blk_desc *dev_desc;
+
+	uint32_t csum_seed;
+	uint32_t journal_csum_seed;
+	uint32_t journal_tag_size;
+	int journal_csum_version;
 };
 
 struct ext_block_cache {
@@ -130,6 +136,7 @@ extern struct ext2fs_node *ext4fs_file;
 
 #if defined(CONFIG_EXT4_WRITE)
 extern struct ext2_inode *g_parent_inode;
+extern int g_parent_inode_no;
 extern int gd_index;
 extern int gindex;
 
diff --git a/include/ext_common.h b/include/ext_common.h
index bc3324172a..c024b33e56 100644
--- a/include/ext_common.h
+++ b/include/ext_common.h
@@ -97,7 +97,7 @@ struct ext2_sblock {
 	__le32 feature_compatibility;
 	__le32 feature_incompat;
 	__le32 feature_ro_compat;
-	__le32 unique_id[4];
+	uint8_t unique_id[16];
 	char volume_name[16];
 	char last_mounted_on[64];
 	__le32 compression_info;
@@ -116,6 +116,8 @@ struct ext2_sblock {
 	__le32 first_meta_block_group;
 	__le32 mkfs_time;
 	__le32 journal_blocks[17];
+
+	/* 64 bit support */
 	__le32 total_blocks_high;
 	__le32 reserved_blocks_high;
 	__le32 free_blocks_high;
@@ -128,6 +130,43 @@ struct ext2_sblock {
 	__le32 raid_stripe_width;
 	uint8_t log2_groups_per_flex;
 	uint8_t checksum_type;
+	uint8_t encryption_level;
+	uint8_t reserved_pad;
+	__le64 kbytes_written;
+	__le32 snapshot_inum;
+	__le32 snapshot_id;
+	__le64 snapshot_r_blocks_count;
+	__le32 snapshot_list;
+	__le32 error_count;
+	__le32 first_error_time;
+	__le32 first_error_ino;
+	__le64 first_error_block;
+	uint8_t first_error_func[32];
+	__le32 first_error_line;
+	__le32 last_error_time;
+	__le32 last_error_ino;
+	__le32 last_error_line;
+	__le64 last_error_block;
+	uint8_t last_error_func[32];
+	uint8_t mount_opts[64];
+	__le32 usr_quota_inum;
+	__le32 grp_quota_inum;
+	__le32 overhead_clusters;
+	__le32 backup_bgs[2];
+	uint8_t encrypt_algos[4];
+	uint8_t encrypt_pw_salt[16];
+	__le32 lpf_ino;
+	__le32 prj_quota_inum;
+	__le32 checksum_seed;
+	uint8_t wtime_hi;
+	uint8_t mtime_hi;
+	uint8_t mkfs_time_hi;
+	uint8_t lastcheck_hi;
+	uint8_t first_error_time_hi;
+	uint8_t last_error_time_hi;
+	uint8_t pad[2];
+	__le32 reserved[96];
+	__le32 checksum;
 };
 
 struct ext2_block_group {
@@ -157,6 +196,13 @@ struct ext2_block_group {
 	__le32 bg_reserved;
 };
 
+#define EXT4_BG_INODE_BITMAP_CSUM_HI_END	\
+	(offsetof(struct ext2_block_group, bg_inode_id_csum_high) + \
+	 sizeof(__le16))
+#define EXT4_BG_BLOCK_BITMAP_CSUM_HI_END	\
+	(offsetof(struct ext2_block_group, bg_block_id_csum_high) + \
+	 sizeof(__le16))
+
 /* The ext2 inode. */
 struct ext2_inode {
 	__le16 mode;
@@ -181,11 +227,28 @@ struct ext2_inode {
 		char symlink[60];
 		char inline_data[60];
 	} b;
-	__le32 version;
+	__le32 generation;
 	__le32 acl;
 	__le32 size_high;	/* previously dir_acl, but never used */
 	__le32 fragment_addr;
-	__le32 osd2[3];
+
+	__le16 blocks_high; /* were l_i_reserved1 */
+	__le16 file_acl_high;
+	__le16 uid_high;
+	__le16 gid_high;
+	__le16 checksum_lo; /* crc32c(uuid+inum+inode) LE */
+	__le16 reserved;
+
+	/* optional part */
+	__le16 extra_isize;
+	__le16 checksum_hi; /* crc32c(uuid+inum+inode) BE */
+	__le32 ctime_extra;
+	__le32 mtime_extra;
+	__le32 atime_extra;
+	__le32 crtime;
+	__le32 crtime_extra;
+	__le32 version_hi;
+	__le32 projid;
 };
 
 /* The header of an ext2 directory entry. */
@@ -196,6 +259,14 @@ struct ext2_dirent {
 	__u8 filetype;
 };
 
+struct ext4_dir_entry_tail {
+	__le32	det_reserved_zero1;	/* Pretend to be unused */
+	__le16	det_rec_len;		/* 12 */
+	__u8	det_reserved_zero2;	/* Zero name length */
+	__u8	det_reserved_ft;	/* 0xDE, fake file type */
+	__le32	det_checksum;		/* crc32c(uuid+inum+dirblock) */
+};
+
 struct ext2fs_node {
 	struct ext2_data *data;
 	struct ext2_inode inode;
diff --git a/test/py/tests/test_env.py b/test/py/tests/test_env.py
index 940279651d..847b4fbcdd 100644
--- a/test/py/tests/test_env.py
+++ b/test/py/tests/test_env.py
@@ -417,9 +417,6 @@ def mk_env_ext4(state_test_env):
         try:
             u_boot_utils.run_and_log(c, 'dd if=/dev/zero of=%s bs=1M count=16' % persistent)
             u_boot_utils.run_and_log(c, 'mkfs.ext4 %s' % persistent)
-            sb_content = u_boot_utils.run_and_log(c, 'tune2fs -l %s' % persistent)
-            if 'metadata_csum' in sb_content:
-                u_boot_utils.run_and_log(c, 'tune2fs -O ^metadata_csum %s' % persistent)
         except CalledProcessError:
             call('rm -f %s' % persistent, shell=True)
             raise
diff --git a/test/py/tests/test_fs/conftest.py b/test/py/tests/test_fs/conftest.py
index ec70e8c4ef..272cbb639c 100644
--- a/test/py/tests/test_fs/conftest.py
+++ b/test/py/tests/test_fs/conftest.py
@@ -165,10 +165,6 @@ def mk_fs(config, fs_type, size, id):
             % (fs_img, count), shell=True)
         check_call('mkfs.%s %s %s'
             % (fs_lnxtype, mkfs_opt, fs_img), shell=True)
-        if fs_type == 'ext4':
-            sb_content = check_output('tune2fs -l %s' % fs_img, shell=True).decode()
-            if 'metadata_csum' in sb_content:
-                check_call('tune2fs -O ^metadata_csum %s' % fs_img, shell=True)
         return fs_img
     except CalledProcessError:
         call('rm -f %s' % fs_img, shell=True)
-- 
2.27.0



More information about the U-Boot mailing list