ext4_journal: forcibly flush data to disk when stop journalling.
[lwext4.git] / lwext4 / ext4_extent.c
index abd4d8353e32f907f18a905f58a1f9657ffa03fb..d6f373bb0f5846afc3ed4cf1f539a3ecbcb8c7ba 100644 (file)
 #include "ext4_extent.h"
 #include "ext4_inode.h"
 #include "ext4_super.h"
+#include "ext4_crc32c.h"
 #include "ext4_blockdev.h"
 #include "ext4_balloc.h"
+#include "ext4_fs.h"
 
 #include <string.h>
 #include <stdlib.h>
 
 #if !CONFIG_EXTENT_FULL
 
+static struct ext4_extent_header *ext_inode_hdr(struct ext4_inode *inode)
+{
+       return (struct ext4_extent_header *)inode->blocks;
+}
+
+static uint16_t ext_depth(struct ext4_inode *inode)
+{
+       return to_le16(ext_inode_hdr(inode)->depth);
+}
+
+static struct ext4_extent_tail *
+find_ext4_extent_tail(struct ext4_extent_header *eh)
+{
+       return (struct ext4_extent_tail *)(((char *)eh) +
+                                          EXT4_EXTENT_TAIL_OFFSET(eh));
+}
+
+#if CONFIG_META_CSUM_ENABLE
+static uint32_t ext4_ext_block_csum(struct ext4_inode_ref *inode_ref,
+                                   struct ext4_extent_header *eh)
+{
+       uint32_t checksum = 0;
+       struct ext4_sblock *sb = &inode_ref->fs->sb;
+
+       if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) {
+               uint32_t ino_index = to_le32(inode_ref->index);
+               uint32_t ino_gen =
+                       to_le32(ext4_inode_get_generation(inode_ref->inode));
+               /* First calculate crc32 checksum against fs uuid */
+               checksum = ext4_crc32c(EXT4_CRC32_INIT, sb->uuid,
+                               sizeof(sb->uuid));
+               /* Then calculate crc32 checksum against inode number
+                * and inode generation */
+               checksum = ext4_crc32c(checksum, &ino_index,
+                                    sizeof(ino_index));
+               checksum = ext4_crc32c(checksum, &ino_gen,
+                                    sizeof(ino_gen));
+               /* Finally calculate crc32 checksum against
+                * the entire extent block up to the checksum field */
+               checksum = ext4_crc32c(checksum, eh,
+                               EXT4_EXTENT_TAIL_OFFSET(eh));
+       }
+       return checksum;
+}
+#else
+#define ext4_ext_block_csum(...) 0
+#endif
+
+static void ext4_extent_block_csum_set(struct ext4_inode_ref *inode_ref,
+                                      struct ext4_extent_header *eh)
+{
+       struct ext4_extent_tail *tail;
+       if (!ext4_sb_feature_ro_com(&inode_ref->fs->sb,
+                                   EXT4_FRO_COM_METADATA_CSUM))
+               return;
+
+       if (to_le16(eh->depth) < ext_depth(inode_ref->inode)) {
+               tail = find_ext4_extent_tail(eh);
+               tail->et_checksum = to_le32(ext4_ext_block_csum(inode_ref, eh));
+       }
+}
+
+#if CONFIG_META_CSUM_ENABLE
+static bool
+ext4_extent_verify_block_csum(struct ext4_inode_ref *inode_ref,
+                             struct ext4_block *block)
+{
+       struct ext4_extent_header *eh;
+       struct ext4_extent_tail *tail;
+       eh = (struct ext4_extent_header *)block->data;
+       if (!ext4_sb_feature_ro_com(&inode_ref->fs->sb,
+                                   EXT4_FRO_COM_METADATA_CSUM))
+               return true;
+
+       if (to_le16(eh->depth) < ext_depth(inode_ref->inode)) {
+               tail = find_ext4_extent_tail(eh);
+               return tail->et_checksum ==
+                       to_le32(ext4_ext_block_csum(inode_ref, eh));
+       }
+
+       return true;
+}
+#else
+#define ext4_extent_verify_block_csum(...) true
+#endif
+
 /**@brief Binary search in extent index node.
  * @param header Extent header of index node
  * @param index  Output value - found index will be set here
@@ -131,7 +219,7 @@ static void ext4_extent_binsearch(struct ext4_extent_header *header,
  * @return Error code*/
 static int
 ext4_extent_find_block(struct ext4_inode_ref *inode_ref, uint32_t iblock,
-                          uint32_t *fblock)
+                          ext4_fsblk_t *fblock)
 {
        int rc;
        /* Compute bound defined by i-node size */
@@ -172,6 +260,13 @@ ext4_extent_find_block(struct ext4_inode_ref *inode_ref, uint32_t iblock,
                int rc = ext4_block_get(inode_ref->fs->bdev, &block, child);
                if (rc != EOK)
                        return rc;
+               if (!ext4_extent_verify_block_csum(inode_ref,
+                                                  &block)) {
+                       ext4_dbg(DEBUG_EXTENT,
+                                DBG_WARN "Extent block checksum failed."
+                                "Blocknr: %" PRIu64"\n",
+                                child);
+               }
 
                header = (struct ext4_extent_header *)block.data;
        }
@@ -185,7 +280,7 @@ ext4_extent_find_block(struct ext4_inode_ref *inode_ref, uint32_t iblock,
                *fblock = 0;
        } else {
                /* Compute requested physical block address */
-               uint32_t phys_block;
+               ext4_fsblk_t phys_block;
                uint32_t first = ext4_extent_get_first_block(extent);
                phys_block = ext4_extent_get_start(extent) + iblock - first;
 
@@ -251,6 +346,14 @@ static int ext4_extent_find_extent(struct ext4_inode_ref *inode_ref,
                if (rc != EOK)
                        goto cleanup;
 
+               if (!ext4_extent_verify_block_csum(inode_ref,
+                                                  &block)) {
+                       ext4_dbg(DEBUG_EXTENT,
+                                DBG_WARN "Extent block checksum failed."
+                                "Blocknr: %" PRIu64"\n",
+                                fblock);
+               }
+
                pos++;
 
                eh = (struct ext4_extent_header *)block.data;
@@ -313,13 +416,21 @@ static int ext4_extent_release(struct ext4_inode_ref *inode_ref,
 static int ext4_extent_release_branch(struct ext4_inode_ref *inode_ref,
                                      struct ext4_extent_index *index)
 {
-       uint32_t fblock = ext4_extent_index_get_leaf(index);
+       ext4_fsblk_t fblock = ext4_extent_index_get_leaf(index);
        uint32_t i;
        struct ext4_block block;
        int rc = ext4_block_get(inode_ref->fs->bdev, &block, fblock);
        if (rc != EOK)
                return rc;
 
+       if (!ext4_extent_verify_block_csum(inode_ref,
+                               &block)) {
+               ext4_dbg(DEBUG_EXTENT,
+                        DBG_WARN "Extent block checksum failed."
+                        "Blocknr: %" PRIu64"\n",
+                        fblock);
+       }
+
        struct ext4_extent_header *header = (void *)block.data;
 
        if (ext4_extent_header_get_depth(header)) {
@@ -358,7 +469,7 @@ static int ext4_extent_release_branch(struct ext4_inode_ref *inode_ref,
 int ext4_extent_remove_space(struct ext4_inode_ref *inode_ref, ext4_lblk_t from,
                             ext4_lblk_t to)
 {
-       if (to != (ext4_lblk_t)-1)
+       if (to != EXT_MAX_BLOCKS)
                return ENOTSUP;
 
        /* Find the first extent to modify */
@@ -377,7 +488,7 @@ int ext4_extent_remove_space(struct ext4_inode_ref *inode_ref, ext4_lblk_t from,
 
        /* First extent maybe released partially */
        uint32_t first_iblock = ext4_extent_get_first_block(path_ptr->extent);
-       uint32_t first_fblock = ext4_extent_get_start(path_ptr->extent) +
+       ext4_fsblk_t first_fblock = ext4_extent_get_start(path_ptr->extent) +
                                from - first_iblock;
 
        uint16_t block_count = ext4_extent_get_block_count(path_ptr->extent);
@@ -422,7 +533,8 @@ int ext4_extent_remove_space(struct ext4_inode_ref *inode_ref, ext4_lblk_t from,
        }
 
        ext4_extent_header_set_entries_count(path_ptr->header, entries);
-       path_ptr->block.dirty = true;
+       ext4_extent_block_csum_set(inode_ref, path_ptr->header);
+       ext4_bcache_set_dirty(path_ptr->block.buf);
 
        /* If leaf node is empty, parent entry must be modified */
        bool remove_parent_record = false;
@@ -463,7 +575,8 @@ int ext4_extent_remove_space(struct ext4_inode_ref *inode_ref, ext4_lblk_t from,
                }
 
                ext4_extent_header_set_entries_count(path_ptr->header, entries);
-               path_ptr->block.dirty = true;
+               ext4_extent_block_csum_set(inode_ref, path_ptr->header);
+               ext4_bcache_set_dirty(path_ptr->block.buf);
 
                /* Free the node if it is empty */
                if ((entries == 0) && (path_ptr != path)) {
@@ -527,14 +640,18 @@ static int ext4_extent_append_extent(struct ext4_inode_ref *inode_ref,
 
                if (entries == limit) {
                        /* Full node - allocate block for new one */
-                       uint32_t fblock;
-                       int rc = ext4_balloc_alloc_block(inode_ref, &fblock);
+                       ext4_fsblk_t goal, fblock;
+                       int rc = ext4_fs_indirect_find_goal(inode_ref, &goal);
+                       if (rc != EOK)
+                               return rc;
+
+                       rc = ext4_balloc_alloc_block(inode_ref, goal, &fblock);
                        if (rc != EOK)
                                return rc;
 
                        struct ext4_block block;
                        rc =
-                           ext4_block_get(inode_ref->fs->bdev, &block, fblock);
+                           ext4_block_get_noread(inode_ref->fs->bdev, &block, fblock);
                        if (rc != EOK) {
                                ext4_balloc_free_block(inode_ref, fblock);
                                return rc;
@@ -586,7 +703,8 @@ static int ext4_extent_append_extent(struct ext4_inode_ref *inode_ref,
                                                     path_ptr->depth);
                        ext4_extent_header_set_generation(path_ptr->header, 0);
 
-                       path_ptr->block.dirty = true;
+                       ext4_extent_block_csum_set(inode_ref, path_ptr->header);
+                       ext4_bcache_set_dirty(path_ptr->block.buf);
 
                        /* Jump to the preceding item */
                        path_ptr--;
@@ -611,7 +729,8 @@ static int ext4_extent_append_extent(struct ext4_inode_ref *inode_ref,
 
                        ext4_extent_header_set_entries_count(path_ptr->header,
                                                             entries + 1);
-                       path_ptr->block.dirty = true;
+                       ext4_extent_block_csum_set(inode_ref, path_ptr->header);
+                       ext4_bcache_set_dirty(path_ptr->block.buf);
 
                        /* No more splitting needed */
                        return EOK;
@@ -626,13 +745,17 @@ static int ext4_extent_append_extent(struct ext4_inode_ref *inode_ref,
        uint16_t limit = ext4_extent_header_get_max_entries_count(path->header);
 
        if (entries == limit) {
-               uint32_t new_fblock;
-               int rc = ext4_balloc_alloc_block(inode_ref, &new_fblock);
+               ext4_fsblk_t goal, new_fblock;
+               int rc = ext4_fs_indirect_find_goal(inode_ref, &goal);
+               if (rc != EOK)
+                       return rc;
+
+               rc = ext4_balloc_alloc_block(inode_ref, goal, &new_fblock);
                if (rc != EOK)
                        return rc;
 
                struct ext4_block block;
-               rc = ext4_block_get(inode_ref->fs->bdev, &block, new_fblock);
+               rc = ext4_block_get_noread(inode_ref->fs->bdev, &block, new_fblock);
                if (rc != EOK)
                        return rc;
 
@@ -689,7 +812,8 @@ static int ext4_extent_append_extent(struct ext4_inode_ref *inode_ref,
                ext4_extent_header_set_max_entries_count(old_root->header,
                                                         limit);
 
-               old_root->block.dirty = true;
+               ext4_extent_block_csum_set(inode_ref, old_root->header);
+               ext4_bcache_set_dirty(old_root->block.buf);
 
                /* Re-initialize new root metadata */
                new_root->depth = root_depth + 1;
@@ -705,7 +829,9 @@ static int ext4_extent_append_extent(struct ext4_inode_ref *inode_ref,
                ext4_extent_index_set_first_block(new_root->index, 0);
                ext4_extent_index_set_leaf(new_root->index, new_fblock);
 
-               new_root->block.dirty = true;
+               /* Since new_root belongs to on-disk inode,
+                * we don't do checksum here */
+               ext4_bcache_set_dirty(new_root->block.buf);
        } else {
                if (path->depth) {
                        path->index =
@@ -720,7 +846,9 @@ static int ext4_extent_append_extent(struct ext4_inode_ref *inode_ref,
                }
 
                ext4_extent_header_set_entries_count(path->header, entries + 1);
-               path->block.dirty = true;
+               /* Since new_root belongs to on-disk inode,
+                * we don't do checksum here */
+               ext4_bcache_set_dirty(path->block.buf);
        }
 
        return EOK;
@@ -737,9 +865,10 @@ static int ext4_extent_append_extent(struct ext4_inode_ref *inode_ref,
  * @return Error code*/
 static int
 ext4_extent_append_block(struct ext4_inode_ref *inode_ref, uint32_t *iblock,
-                            uint32_t *fblock, bool update_size)
+                            ext4_fsblk_t *fblock, bool update_size)
 {
        uint16_t i;
+       ext4_fsblk_t goal;
        struct ext4_sblock *sb = &inode_ref->fs->sb;
        uint64_t inode_size = ext4_inode_get_size(sb, inode_ref->inode);
        uint32_t block_size = ext4_sb_get_block_size(sb);
@@ -771,12 +900,16 @@ ext4_extent_append_block(struct ext4_inode_ref *inode_ref, uint32_t *iblock,
        uint16_t block_count = ext4_extent_get_block_count(path_ptr->extent);
        uint16_t block_limit = (1 << 15);
 
-       uint32_t phys_block = 0;
+       ext4_fsblk_t phys_block = 0;
        if (block_count < block_limit) {
                /* There is space for new block in the extent */
                if (block_count == 0) {
+                       int rc = ext4_fs_indirect_find_goal(inode_ref, &goal);
+                       if (rc != EOK)
+                               goto finish;
+
                        /* Existing extent is empty */
-                       rc = ext4_balloc_alloc_block(inode_ref, &phys_block);
+                       rc = ext4_balloc_alloc_block(inode_ref, goal, &phys_block);
                        if (rc != EOK)
                                goto finish;
 
@@ -794,10 +927,16 @@ ext4_extent_append_block(struct ext4_inode_ref *inode_ref, uint32_t *iblock,
                                inode_ref->dirty = true;
                        }
 
-                       path_ptr->block.dirty = true;
+                       ext4_extent_block_csum_set(inode_ref, path_ptr->header);
+                       ext4_bcache_set_dirty(path_ptr->block.buf);
 
                        goto finish;
                } else {
+                       ext4_fsblk_t goal;
+                       int rc = ext4_fs_indirect_find_goal(inode_ref, &goal);
+                       if (rc != EOK)
+                               goto finish;
+
                        /* Existing extent contains some blocks */
                        phys_block = ext4_extent_get_start(path_ptr->extent);
                        phys_block +=
@@ -830,7 +969,8 @@ ext4_extent_append_block(struct ext4_inode_ref *inode_ref, uint32_t *iblock,
                                inode_ref->dirty = true;
                        }
 
-                       path_ptr->block.dirty = true;
+                       ext4_extent_block_csum_set(inode_ref, path_ptr->header);
+                       ext4_bcache_set_dirty(path_ptr->block.buf);
 
                        goto finish;
                }
@@ -840,8 +980,12 @@ append_extent:
        /* Append new extent to the tree */
        phys_block = 0;
 
+       rc = ext4_fs_indirect_find_goal(inode_ref, &goal);
+       if (rc != EOK)
+               goto finish;
+
        /* Allocate new data block */
-       rc = ext4_balloc_alloc_block(inode_ref, &phys_block);
+       rc = ext4_balloc_alloc_block(inode_ref, goal, &phys_block);
        if (rc != EOK)
                goto finish;
 
@@ -866,7 +1010,8 @@ append_extent:
                inode_ref->dirty = true;
        }
 
-       path_ptr->block.dirty = true;
+       ext4_extent_block_csum_set(inode_ref, path_ptr->header);
+       ext4_bcache_set_dirty(path_ptr->block.buf);
 
 finish:
        /* Set return values */
@@ -893,11 +1038,11 @@ finish:
 }
 
 int ext4_extent_get_blocks(struct ext4_inode_ref *inode_ref, ext4_fsblk_t iblock,
-                          uint32_t max_blocks, ext4_fsblk_t *result, bool create,
-                          uint32_t *blocks_count)
+                          ext4_lblk_t max_blocks, ext4_fsblk_t *result, bool create,
+                          ext4_lblk_t *blocks_count)
 {
        uint32_t iblk = iblock;
-       uint32_t fblk = 0;
+       ext4_fsblk_t fblk = 0;
        int r;
 
        if (blocks_count)