u-boot/fs/ext4/ext4_journal.c
Marek Szyprowski bd8fbd8a13 ext4: add support for filesystems without JOURNAL
JOURNAL is optional for EXT4 (and EXT3) filesystems, so add support for
skipping it. This fixes corrupting EXT4 volumes without JOURNAL after
using uboot's 'ext4write' command.

Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com>
Reviewed-by: Lukasz Majewski <lukma@denx.de>
2019-07-18 11:31:24 -04:00

666 lines
17 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* (C) Copyright 2011 - 2012 Samsung Electronics
* EXT4 filesystem implementation in Uboot by
* Uma Shankar <uma.shankar@samsung.com>
* Manjunatha C Achar <a.manjunatha@samsung.com>
*
* Journal data structures and headers for Journaling feature of ext4
* have been referred from JBD2 (Journaling Block device 2)
* implementation in Linux Kernel.
* Written by Stephen C. Tweedie <sct@redhat.com>
*
* Copyright 1998-2000 Red Hat, Inc --- All Rights Reserved
*/
#include <common.h>
#include <ext4fs.h>
#include <malloc.h>
#include <ext_common.h>
#include "ext4_common.h"
static struct revoke_blk_list *revk_blk_list;
static struct revoke_blk_list *prev_node;
static int first_node = true;
int gindex;
int gd_index;
int jrnl_blk_idx;
struct journal_log *journal_ptr[MAX_JOURNAL_ENTRIES];
struct dirty_blocks *dirty_block_ptr[MAX_JOURNAL_ENTRIES];
int ext4fs_init_journal(void)
{
int i;
char *temp = NULL;
struct ext_filesystem *fs = get_fs();
/* init globals */
revk_blk_list = NULL;
prev_node = NULL;
gindex = 0;
gd_index = 0;
jrnl_blk_idx = 1;
for (i = 0; i < MAX_JOURNAL_ENTRIES; i++) {
journal_ptr[i] = zalloc(sizeof(struct journal_log));
if (!journal_ptr[i])
goto fail;
dirty_block_ptr[i] = zalloc(sizeof(struct dirty_blocks));
if (!dirty_block_ptr[i])
goto fail;
journal_ptr[i]->buf = NULL;
journal_ptr[i]->blknr = -1;
dirty_block_ptr[i]->buf = NULL;
dirty_block_ptr[i]->blknr = -1;
}
if (fs->blksz == 4096) {
temp = zalloc(fs->blksz);
if (!temp)
goto fail;
journal_ptr[gindex]->buf = zalloc(fs->blksz);
if (!journal_ptr[gindex]->buf)
goto fail;
ext4fs_devread(0, 0, fs->blksz, temp);
memcpy(temp + SUPERBLOCK_SIZE, fs->sb, SUPERBLOCK_SIZE);
memcpy(journal_ptr[gindex]->buf, temp, fs->blksz);
journal_ptr[gindex++]->blknr = 0;
free(temp);
} else {
journal_ptr[gindex]->buf = zalloc(fs->blksz);
if (!journal_ptr[gindex]->buf)
goto fail;
memcpy(journal_ptr[gindex]->buf, fs->sb, SUPERBLOCK_SIZE);
journal_ptr[gindex++]->blknr = 1;
}
/* Check the file system state using journal super block */
if (ext4fs_check_journal_state(SCAN))
goto fail;
/* Check the file system state using journal super block */
if (ext4fs_check_journal_state(RECOVER))
goto fail;
return 0;
fail:
return -1;
}
void ext4fs_dump_metadata(void)
{
struct ext_filesystem *fs = get_fs();
int i;
for (i = 0; i < MAX_JOURNAL_ENTRIES; i++) {
if (dirty_block_ptr[i]->blknr == -1)
break;
put_ext4((uint64_t) ((uint64_t)dirty_block_ptr[i]->blknr *
(uint64_t)fs->blksz), dirty_block_ptr[i]->buf,
fs->blksz);
}
}
void ext4fs_free_journal(void)
{
int i;
for (i = 0; i < MAX_JOURNAL_ENTRIES; i++) {
if (dirty_block_ptr[i]->blknr == -1)
break;
if (dirty_block_ptr[i]->buf)
free(dirty_block_ptr[i]->buf);
}
for (i = 0; i < MAX_JOURNAL_ENTRIES; i++) {
if (journal_ptr[i]->blknr == -1)
break;
if (journal_ptr[i]->buf)
free(journal_ptr[i]->buf);
}
for (i = 0; i < MAX_JOURNAL_ENTRIES; i++) {
if (journal_ptr[i])
free(journal_ptr[i]);
if (dirty_block_ptr[i])
free(dirty_block_ptr[i]);
}
gindex = 0;
gd_index = 0;
jrnl_blk_idx = 1;
}
int ext4fs_log_gdt(char *gd_table)
{
struct ext_filesystem *fs = get_fs();
short i;
long int var = fs->gdtable_blkno;
for (i = 0; i < fs->no_blk_pergdt; i++) {
journal_ptr[gindex]->buf = zalloc(fs->blksz);
if (!journal_ptr[gindex]->buf)
return -ENOMEM;
memcpy(journal_ptr[gindex]->buf, gd_table, fs->blksz);
gd_table += fs->blksz;
journal_ptr[gindex++]->blknr = var++;
}
return 0;
}
/*
* This function stores the backup copy of meta data in RAM
* journal_buffer -- Buffer containing meta data
* blknr -- Block number on disk of the meta data buffer
*/
int ext4fs_log_journal(char *journal_buffer, uint32_t blknr)
{
struct ext_filesystem *fs = get_fs();
short i;
if (!journal_buffer) {
printf("Invalid input arguments %s\n", __func__);
return -EINVAL;
}
for (i = 0; i < MAX_JOURNAL_ENTRIES; i++) {
if (journal_ptr[i]->blknr == -1)
break;
if (journal_ptr[i]->blknr == blknr)
return 0;
}
journal_ptr[gindex]->buf = zalloc(fs->blksz);
if (!journal_ptr[gindex]->buf)
return -ENOMEM;
memcpy(journal_ptr[gindex]->buf, journal_buffer, fs->blksz);
journal_ptr[gindex++]->blknr = blknr;
return 0;
}
/*
* This function stores the modified meta data in RAM
* metadata_buffer -- Buffer containing meta data
* blknr -- Block number on disk of the meta data buffer
*/
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;
}
if (dirty_block_ptr[gd_index]->buf)
assert(dirty_block_ptr[gd_index]->blknr == blknr);
else
dirty_block_ptr[gd_index]->buf = zalloc(fs->blksz);
if (!dirty_block_ptr[gd_index]->buf)
return -ENOMEM;
memcpy(dirty_block_ptr[gd_index]->buf, metadata_buffer, fs->blksz);
dirty_block_ptr[gd_index++]->blknr = blknr;
return 0;
}
void print_revoke_blks(char *revk_blk)
{
int offset;
int max;
long int blocknr;
struct journal_revoke_header_t *header;
if (revk_blk == NULL)
return;
header = (struct journal_revoke_header_t *) revk_blk;
offset = sizeof(struct journal_revoke_header_t);
max = be32_to_cpu(header->r_count);
printf("total bytes %d\n", max);
while (offset < max) {
blocknr = be32_to_cpu(*((__be32 *)(revk_blk + offset)));
printf("revoke blknr is %ld\n", blocknr);
offset += 4;
}
}
static struct revoke_blk_list *_get_node(void)
{
struct revoke_blk_list *tmp_node;
tmp_node = zalloc(sizeof(struct revoke_blk_list));
if (tmp_node == NULL)
return NULL;
tmp_node->content = NULL;
tmp_node->next = NULL;
return tmp_node;
}
void ext4fs_push_revoke_blk(char *buffer)
{
struct revoke_blk_list *node = NULL;
struct ext_filesystem *fs = get_fs();
if (buffer == NULL) {
printf("buffer ptr is NULL\n");
return;
}
node = _get_node();
if (!node) {
printf("_get_node: malloc failed\n");
return;
}
node->content = zalloc(fs->blksz);
if (node->content == NULL)
return;
memcpy(node->content, buffer, fs->blksz);
if (first_node == true) {
revk_blk_list = node;
prev_node = node;
first_node = false;
} else {
prev_node->next = node;
prev_node = node;
}
}
void ext4fs_free_revoke_blks(void)
{
struct revoke_blk_list *tmp_node = revk_blk_list;
struct revoke_blk_list *next_node = NULL;
while (tmp_node != NULL) {
if (tmp_node->content)
free(tmp_node->content);
tmp_node = tmp_node->next;
}
tmp_node = revk_blk_list;
while (tmp_node != NULL) {
next_node = tmp_node->next;
free(tmp_node);
tmp_node = next_node;
}
revk_blk_list = NULL;
prev_node = NULL;
first_node = true;
}
int check_blknr_for_revoke(long int blknr, int sequence_no)
{
struct journal_revoke_header_t *header;
int offset;
int max;
long int blocknr;
char *revk_blk;
struct revoke_blk_list *tmp_revk_node = revk_blk_list;
while (tmp_revk_node != NULL) {
revk_blk = tmp_revk_node->content;
header = (struct journal_revoke_header_t *) revk_blk;
if (sequence_no < be32_to_cpu(header->r_header.h_sequence)) {
offset = sizeof(struct journal_revoke_header_t);
max = be32_to_cpu(header->r_count);
while (offset < max) {
blocknr = be32_to_cpu(*((__be32 *)
(revk_blk + offset)));
if (blocknr == blknr)
goto found;
offset += 4;
}
}
tmp_revk_node = tmp_revk_node->next;
}
return -1;
found:
return 0;
}
/*
* This function parses the journal blocks and replays the
* suceessful transactions. A transaction is successfull
* if commit block is found for a descriptor block
* The tags in descriptor block contain the disk block
* numbers of the metadata to be replayed
*/
void recover_transaction(int prev_desc_logical_no)
{
struct ext2_inode inode_journal;
struct ext_filesystem *fs = get_fs();
struct journal_header_t *jdb;
long int blknr;
char *p_jdb;
int ofs, flags;
int i;
struct ext3_journal_block_tag *tag;
char *temp_buff = zalloc(fs->blksz);
char *metadata_buff = zalloc(fs->blksz);
if (!temp_buff || !metadata_buff)
goto fail;
i = prev_desc_logical_no;
ext4fs_read_inode(ext4fs_root, EXT2_JOURNAL_INO,
(struct ext2_inode *)&inode_journal);
blknr = read_allocated_block((struct ext2_inode *)
&inode_journal, i, NULL);
ext4fs_devread((lbaint_t)blknr * fs->sect_perblk, 0, fs->blksz,
temp_buff);
p_jdb = (char *)temp_buff;
jdb = (struct journal_header_t *) temp_buff;
ofs = sizeof(struct journal_header_t);
do {
tag = (struct ext3_journal_block_tag *)(p_jdb + ofs);
ofs += sizeof(struct ext3_journal_block_tag);
if (ofs > fs->blksz)
break;
flags = be32_to_cpu(tag->flags);
if (!(flags & EXT3_JOURNAL_FLAG_SAME_UUID))
ofs += 16;
i++;
debug("\t\ttag %u\n", be32_to_cpu(tag->block));
if (revk_blk_list != NULL) {
if (check_blknr_for_revoke(be32_to_cpu(tag->block),
be32_to_cpu(jdb->h_sequence)) == 0)
continue;
}
blknr = read_allocated_block(&inode_journal, i, NULL);
ext4fs_devread((lbaint_t)blknr * fs->sect_perblk, 0,
fs->blksz, metadata_buff);
put_ext4((uint64_t)((uint64_t)be32_to_cpu(tag->block) * (uint64_t)fs->blksz),
metadata_buff, (uint32_t) fs->blksz);
} while (!(flags & EXT3_JOURNAL_FLAG_LAST_TAG));
fail:
free(temp_buff);
free(metadata_buff);
}
void print_jrnl_status(int recovery_flag)
{
if (recovery_flag == RECOVER)
printf("Journal Recovery Completed\n");
else
printf("Journal Scan Completed\n");
}
int ext4fs_check_journal_state(int recovery_flag)
{
int i;
int DB_FOUND = NO;
long int blknr;
int transaction_state = TRANSACTION_COMPLETE;
int prev_desc_logical_no = 0;
int curr_desc_logical_no = 0;
int ofs, flags;
struct ext2_inode inode_journal;
struct journal_superblock_t *jsb = NULL;
struct journal_header_t *jdb = NULL;
char *p_jdb = NULL;
struct ext3_journal_block_tag *tag = NULL;
char *temp_buff = NULL;
char *temp_buff1 = NULL;
struct ext_filesystem *fs = get_fs();
temp_buff = zalloc(fs->blksz);
if (!temp_buff)
return -ENOMEM;
temp_buff1 = zalloc(fs->blksz);
if (!temp_buff1) {
free(temp_buff);
return -ENOMEM;
}
ext4fs_read_inode(ext4fs_root, EXT2_JOURNAL_INO, &inode_journal);
blknr = read_allocated_block(&inode_journal, EXT2_JOURNAL_SUPERBLOCK,
NULL);
ext4fs_devread((lbaint_t)blknr * fs->sect_perblk, 0, fs->blksz,
temp_buff);
jsb = (struct journal_superblock_t *) temp_buff;
if (le32_to_cpu(fs->sb->feature_incompat) & EXT3_FEATURE_INCOMPAT_RECOVER) {
if (recovery_flag == RECOVER)
printf("Recovery required\n");
} else {
if (recovery_flag == RECOVER)
printf("File System is consistent\n");
goto end;
}
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);
i = be32_to_cpu(jsb->s_first);
while (1) {
blknr = read_allocated_block(&inode_journal, i, NULL);
memset(temp_buff1, '\0', fs->blksz);
ext4fs_devread((lbaint_t)blknr * fs->sect_perblk,
0, fs->blksz, temp_buff1);
jdb = (struct journal_header_t *) temp_buff1;
if (be32_to_cpu(jdb->h_blocktype) ==
EXT3_JOURNAL_DESCRIPTOR_BLOCK) {
if (be32_to_cpu(jdb->h_sequence) !=
be32_to_cpu(jsb->s_sequence)) {
print_jrnl_status(recovery_flag);
break;
}
curr_desc_logical_no = i;
if (transaction_state == TRANSACTION_COMPLETE)
transaction_state = TRANSACTION_RUNNING;
else
return -1;
p_jdb = (char *)temp_buff1;
ofs = sizeof(struct journal_header_t);
do {
tag = (struct ext3_journal_block_tag *)
(p_jdb + ofs);
ofs += sizeof(struct ext3_journal_block_tag);
if (ofs > fs->blksz)
break;
flags = be32_to_cpu(tag->flags);
if (!(flags & EXT3_JOURNAL_FLAG_SAME_UUID))
ofs += 16;
i++;
debug("\t\ttag %u\n", be32_to_cpu(tag->block));
} while (!(flags & EXT3_JOURNAL_FLAG_LAST_TAG));
i++;
DB_FOUND = YES;
} else if (be32_to_cpu(jdb->h_blocktype) ==
EXT3_JOURNAL_COMMIT_BLOCK) {
if (be32_to_cpu(jdb->h_sequence) !=
be32_to_cpu(jsb->s_sequence)) {
print_jrnl_status(recovery_flag);
break;
}
if (transaction_state == TRANSACTION_RUNNING ||
(DB_FOUND == NO)) {
transaction_state = TRANSACTION_COMPLETE;
i++;
jsb->s_sequence =
cpu_to_be32(be32_to_cpu(
jsb->s_sequence) + 1);
}
prev_desc_logical_no = curr_desc_logical_no;
if ((recovery_flag == RECOVER) && (DB_FOUND == YES))
recover_transaction(prev_desc_logical_no);
DB_FOUND = NO;
} else if (be32_to_cpu(jdb->h_blocktype) ==
EXT3_JOURNAL_REVOKE_BLOCK) {
if (be32_to_cpu(jdb->h_sequence) !=
be32_to_cpu(jsb->s_sequence)) {
print_jrnl_status(recovery_flag);
break;
}
if (recovery_flag == SCAN)
ext4fs_push_revoke_blk((char *)jdb);
i++;
} else {
debug("Else Case\n");
if (be32_to_cpu(jdb->h_sequence) !=
be32_to_cpu(jsb->s_sequence)) {
print_jrnl_status(recovery_flag);
break;
}
}
}
end:
if (recovery_flag == RECOVER) {
uint32_t new_feature_incompat;
jsb->s_start = cpu_to_be32(1);
jsb->s_sequence = cpu_to_be32(be32_to_cpu(jsb->s_sequence) + 1);
/* get the superblock */
ext4_read_superblock((char *)fs->sb);
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);
/* Update the super block */
put_ext4((uint64_t) (SUPERBLOCK_SIZE),
(struct ext2_sblock *)fs->sb,
(uint32_t) SUPERBLOCK_SIZE);
ext4_read_superblock((char *)fs->sb);
blknr = read_allocated_block(&inode_journal,
EXT2_JOURNAL_SUPERBLOCK, NULL);
put_ext4((uint64_t) ((uint64_t)blknr * (uint64_t)fs->blksz),
(struct journal_superblock_t *)temp_buff,
(uint32_t) fs->blksz);
ext4fs_free_revoke_blks();
}
free(temp_buff);
free(temp_buff1);
return 0;
}
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 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);
if (!temp_buff)
return;
ext4fs_read_inode(ext4fs_root, EXT2_JOURNAL_INO, &inode_journal);
jsb_blknr = read_allocated_block(&inode_journal,
EXT2_JOURNAL_SUPERBLOCK, NULL);
ext4fs_devread((lbaint_t)jsb_blknr * fs->sect_perblk, 0, fs->blksz,
temp_buff);
jsb = (struct journal_superblock_t *) temp_buff;
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;
buf = zalloc(fs->blksz);
if (!buf) {
free(temp_buff);
return;
}
temp = buf;
memcpy(buf, &jdb, sizeof(struct journal_header_t));
temp += sizeof(struct journal_header_t);
for (i = 0; i < MAX_JOURNAL_ENTRIES; i++) {
if (journal_ptr[i]->blknr == -1)
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.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);
free(temp_buff);
free(buf);
}
static void update_commit_block(long int blknr)
{
struct journal_header_t jdb;
struct ext_filesystem *fs = get_fs();
char *buf = NULL;
struct ext2_inode inode_journal;
struct journal_superblock_t *jsb;
long int jsb_blknr;
char *temp_buff = zalloc(fs->blksz);
if (!temp_buff)
return;
ext4fs_read_inode(ext4fs_root, EXT2_JOURNAL_INO,
&inode_journal);
jsb_blknr = read_allocated_block(&inode_journal,
EXT2_JOURNAL_SUPERBLOCK, NULL);
ext4fs_devread((lbaint_t)jsb_blknr * fs->sect_perblk, 0, fs->blksz,
temp_buff);
jsb = (struct journal_superblock_t *) temp_buff;
jdb.h_blocktype = cpu_to_be32(EXT3_JOURNAL_COMMIT_BLOCK);
jdb.h_magic = cpu_to_be32(EXT3_JOURNAL_MAGIC_NUMBER);
jdb.h_sequence = jsb->s_sequence;
buf = zalloc(fs->blksz);
if (!buf) {
free(temp_buff);
return;
}
memcpy(buf, &jdb, sizeof(struct journal_header_t));
put_ext4((uint64_t) ((uint64_t)blknr * (uint64_t)fs->blksz), buf, (uint32_t) fs->blksz);
free(temp_buff);
free(buf);
}
void ext4fs_update_journal(void)
{
struct ext2_inode inode_journal;
struct ext_filesystem *fs = get_fs();
long int blknr;
int i;
if (!(fs->sb->feature_compatibility & EXT4_FEATURE_COMPAT_HAS_JOURNAL))
return;
ext4fs_read_inode(ext4fs_root, EXT2_JOURNAL_INO, &inode_journal);
blknr = read_allocated_block(&inode_journal, jrnl_blk_idx++, NULL);
update_descriptor_block(blknr);
for (i = 0; i < MAX_JOURNAL_ENTRIES; i++) {
if (journal_ptr[i]->blknr == -1)
break;
blknr = read_allocated_block(&inode_journal, jrnl_blk_idx++,
NULL);
put_ext4((uint64_t) ((uint64_t)blknr * (uint64_t)fs->blksz),
journal_ptr[i]->buf, fs->blksz);
}
blknr = read_allocated_block(&inode_journal, jrnl_blk_idx++, NULL);
update_commit_block(blknr);
printf("update journal finished\n");
}