Add #ifdef __cplusplus to all header files
[lwext4.git] / lwext4 / ext4_dir.c
index 81cde300e9e73af5528190b903402b8225853d35..61e6bdfa35ecc8fb430ee35692bfe5653ff5a96e 100644 (file)
@@ -42,6 +42,7 @@
 #include "ext4_config.h"
 #include "ext4_dir.h"
 #include "ext4_dir_idx.h"
+#include "ext4_crc32c.h"
 #include "ext4_inode.h"
 #include "ext4_fs.h"
 
 
 /****************************************************************************/
 
+/* Walk through a dirent block to find a checksum "dirent" at the tail */
+static struct ext4_dir_entry_tail *
+ext4_dir_get_tail(struct ext4_inode_ref *inode_ref,
+               struct ext4_dir_entry_ll *de)
+{
+       struct ext4_dir_entry_tail *t;
+       struct ext4_sblock *sb = &inode_ref->fs->sb;
+
+       t = EXT4_DIRENT_TAIL(de, ext4_sb_get_block_size(sb));
+
+       if (t->reserved_zero1 ||
+           to_le16(t->rec_len) != sizeof(struct ext4_dir_entry_tail) ||
+           t->reserved_zero2 ||
+           t->reserved_ft != EXT4_DIRENTRY_DIR_CSUM)
+               return NULL;
+
+       return t;
+}
+
+#if CONFIG_META_CSUM_ENABLE
+static uint32_t ext4_dir_checksum(struct ext4_inode_ref *inode_ref,
+                              struct ext4_dir_entry_ll *dirent, int size)
+{
+       uint32_t checksum;
+       struct ext4_sblock *sb = &inode_ref->fs->sb;
+       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 directory entries */
+       checksum = ext4_crc32c(checksum, dirent, size);
+       return checksum;
+}
+#else
+#define ext4_dir_checksum(...) 0
+#endif
+
+bool
+ext4_dir_checksum_verify(struct ext4_inode_ref *inode_ref,
+                        struct ext4_dir_entry_ll *dirent)
+{
+#ifdef CONFIG_META_CSUM_ENABLE
+       struct ext4_dir_entry_tail *t;
+       struct ext4_sblock *sb = &inode_ref->fs->sb;
+
+       /* Compute the checksum only if the filesystem supports it */
+       if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) {
+               t = ext4_dir_get_tail(inode_ref, dirent);
+               if (!t) {
+                       /* There is no space to hold the checksum */
+                       return false;
+               }
+
+               if (t->checksum != to_le32(ext4_dir_checksum(inode_ref, dirent,
+                                       (char *)t - (char *)dirent)))
+                       return false;
+
+       }
+#endif
+       return true;
+}
+
+/* checksumming functions */
+void initialize_dir_tail(struct ext4_dir_entry_tail *t)
+{
+       memset(t, 0, sizeof(struct ext4_dir_entry_tail));
+       t->rec_len = to_le16(sizeof(struct ext4_dir_entry_tail));
+       t->reserved_ft = EXT4_DIRENTRY_DIR_CSUM;
+}
+
+void ext4_dir_set_checksum(struct ext4_inode_ref *inode_ref,
+                          struct ext4_dir_entry_ll *dirent)
+{
+       struct ext4_dir_entry_tail *t;
+       struct ext4_sblock *sb = &inode_ref->fs->sb;
+
+       /* Compute the checksum only if the filesystem supports it */
+       if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) {
+               t = ext4_dir_get_tail(inode_ref, dirent);
+               if (!t) {
+                       /* There is no space to hold the checksum */
+                       return;
+               }
+
+               t->checksum = to_le32(ext4_dir_checksum(inode_ref, dirent,
+                                       (char *)t - (char *)dirent));
+       }
+}
+
 /**@brief Do some checks before returning iterator.
  * @param it Iterator to be checked
  * @param block_size Size of data block
  * @return Error code
  */
-static int ext4_dir_iterator_set(struct ext4_directory_iterator *it,
+static int ext4_dir_iterator_set(struct ext4_dir_iterator *it,
                                 uint32_t block_size)
 {
        it->current = NULL;
@@ -69,7 +167,7 @@ static int ext4_dir_iterator_set(struct ext4_directory_iterator *it,
        if (offset_in_block > block_size - 8)
                return EIO;
 
-       struct ext4_directory_entry_ll *entry =
+       struct ext4_dir_entry_ll *entry =
            (void *)(it->current_block.data + offset_in_block);
 
        /* Ensure that the whole entry does not overflow the block */
@@ -93,7 +191,7 @@ static int ext4_dir_iterator_set(struct ext4_directory_iterator *it,
  * @param pos Position of the next entry
  * @return Error code
  */
-static int ext4_dir_iterator_seek(struct ext4_directory_iterator *it,
+static int ext4_dir_iterator_seek(struct ext4_dir_iterator *it,
                                  uint64_t pos)
 {
        uint64_t size =
@@ -121,7 +219,7 @@ static int ext4_dir_iterator_seek(struct ext4_directory_iterator *it,
        /* Compute next block address */
        uint32_t block_size = ext4_sb_get_block_size(&it->inode_ref->fs->sb);
        uint64_t current_block_idx = it->current_offset / block_size;
-       uint64_t next_block_idx = pos / block_size;
+       uint32_t next_block_idx = pos / block_size;
 
        /*
         * If we don't have a block or are moving across block boundary,
@@ -138,9 +236,11 @@ static int ext4_dir_iterator_seek(struct ext4_directory_iterator *it,
                                return rc;
                }
 
-               uint32_t next_block_phys_idx;
+               ext4_fsblk_t next_block_phys_idx;
                int rc = ext4_fs_get_inode_data_block_index(
-                   it->inode_ref, next_block_idx, &next_block_phys_idx);
+                   it->inode_ref, next_block_idx,
+                   &next_block_phys_idx,
+                   false);
                if (rc != EOK)
                        return rc;
 
@@ -157,7 +257,7 @@ static int ext4_dir_iterator_seek(struct ext4_directory_iterator *it,
        return ext4_dir_iterator_set(it, block_size);
 }
 
-int ext4_dir_iterator_init(struct ext4_directory_iterator *it,
+int ext4_dir_iterator_init(struct ext4_dir_iterator *it,
                           struct ext4_inode_ref *inode_ref, uint64_t pos)
 {
        it->inode_ref = inode_ref;
@@ -168,7 +268,7 @@ int ext4_dir_iterator_init(struct ext4_directory_iterator *it,
        return ext4_dir_iterator_seek(it, pos);
 }
 
-int ext4_dir_iterator_next(struct ext4_directory_iterator *it)
+int ext4_dir_iterator_next(struct ext4_dir_iterator *it)
 {
        int r = EOK;
        uint16_t skip;
@@ -187,7 +287,7 @@ int ext4_dir_iterator_next(struct ext4_directory_iterator *it)
        return r;
 }
 
-int ext4_dir_iterator_fini(struct ext4_directory_iterator *it)
+int ext4_dir_iterator_fini(struct ext4_dir_iterator *it)
 {
        it->current = 0;
 
@@ -199,7 +299,7 @@ int ext4_dir_iterator_fini(struct ext4_directory_iterator *it)
 }
 
 void ext4_dir_write_entry(struct ext4_sblock *sb,
-                         struct ext4_directory_entry_ll *entry,
+                         struct ext4_dir_entry_ll *entry,
                          uint16_t entry_len, struct ext4_inode_ref *child,
                          const char *name, size_t name_len)
 {
@@ -209,16 +309,15 @@ void ext4_dir_write_entry(struct ext4_sblock *sb,
        /* Set type of entry */
        switch (ext4_inode_type(sb, child->inode)) {
        case EXT4_INODE_MODE_DIRECTORY:
-               ext4_dir_entry_ll_set_inode_type(sb, entry,
-                                                EXT4_DIRECTORY_FILETYPE_DIR);
+               ext4_dir_entry_ll_set_inode_type(sb, entry, EXT4_DIRENTRY_DIR);
                break;
        case EXT4_INODE_MODE_FILE:
-               ext4_dir_entry_ll_set_inode_type(
-                   sb, entry, EXT4_DIRECTORY_FILETYPE_REG_FILE);
+               ext4_dir_entry_ll_set_inode_type(sb, entry,
+                                                EXT4_DIRENTRY_REG_FILE);
                break;
        case EXT4_INODE_MODE_SOFTLINK:
-               ext4_dir_entry_ll_set_inode_type(
-                   sb, entry, EXT4_DIRECTORY_FILETYPE_SYMLINK);
+               ext4_dir_entry_ll_set_inode_type(sb, entry,
+                                                EXT4_DIRENTRY_SYMLINK);
                break;
        default:
                /* FIXME: right now we only support 3 inode type. */
@@ -241,8 +340,7 @@ int ext4_dir_add_entry(struct ext4_inode_ref *parent, const char *name,
 
 #if CONFIG_DIR_INDEX_ENABLE
        /* Index adding (if allowed) */
-       if ((ext4_sb_has_feature_compatible(&fs->sb,
-                                           EXT4_FEATURE_COMPAT_DIR_INDEX)) &&
+       if ((ext4_sb_feature_com(&fs->sb, EXT4_FCOM_DIR_INDEX)) &&
            (ext4_inode_has_flag(parent->inode, EXT4_INODE_FLAG_INDEX))) {
                int rc = ext4_dir_dx_add_entry(parent, child, name);
 
@@ -262,7 +360,7 @@ int ext4_dir_add_entry(struct ext4_inode_ref *parent, const char *name,
 
        /* Linear algorithm */
        uint32_t iblock = 0;
-       uint32_t fblock = 0;
+       ext4_fsblk_t fblock = 0;
        uint32_t block_size = ext4_sb_get_block_size(&fs->sb);
        uint32_t inode_size = ext4_inode_get_size(&fs->sb, parent->inode);
        uint32_t total_blocks = inode_size / block_size;
@@ -271,7 +369,9 @@ int ext4_dir_add_entry(struct ext4_inode_ref *parent, const char *name,
        bool success = false;
        for (iblock = 0; iblock < total_blocks; ++iblock) {
                int rc =
-                   ext4_fs_get_inode_data_block_index(parent, iblock, &fblock);
+                   ext4_fs_get_inode_data_block_index(parent,
+                                   iblock, &fblock,
+                                   false);
                if (rc != EOK)
                        return rc;
 
@@ -280,8 +380,20 @@ int ext4_dir_add_entry(struct ext4_inode_ref *parent, const char *name,
                if (rc != EOK)
                        return rc;
 
+               if (!ext4_dir_checksum_verify(
+                               parent,
+                               (struct ext4_dir_entry_ll *)
+                                       block.data)) {
+                       ext4_dbg(DEBUG_DIR,
+                                DBG_WARN "Leaf block checksum failed."
+                                "Inode: %" PRIu32", "
+                                "Block: %" PRIu32"\n",
+                                parent->index,
+                                iblock);
+               }
+
                /* If adding is successful, function can finish */
-               rc = ext4_dir_try_insert_entry(&fs->sb, &block, child, name,
+               rc = ext4_dir_try_insert_entry(&fs->sb, parent, &block, child, name,
                                               name_len);
                if (rc == EOK)
                        success = true;
@@ -305,33 +417,47 @@ int ext4_dir_add_entry(struct ext4_inode_ref *parent, const char *name,
        /* Load new block */
        struct ext4_block new_block;
 
-       rc = ext4_block_get(fs->bdev, &new_block, fblock);
+       rc = ext4_block_get_noread(fs->bdev, &new_block, fblock);
        if (rc != EOK)
                return rc;
 
        /* Fill block with zeroes */
        memset(new_block.data, 0, block_size);
-       struct ext4_directory_entry_ll *block_entry = (void *)new_block.data;
-       ext4_dir_write_entry(&fs->sb, block_entry, block_size, child, name,
-                            name_len);
+       struct ext4_dir_entry_ll *block_entry = (void *)new_block.data;
 
        /* Save new block */
+       if (ext4_sb_feature_ro_com(&fs->sb, EXT4_FRO_COM_METADATA_CSUM)) {
+               ext4_dir_write_entry(&fs->sb, block_entry,
+                               block_size - sizeof(struct ext4_dir_entry_tail),
+                               child,
+                               name, name_len);
+               initialize_dir_tail(EXT4_DIRENT_TAIL(new_block.data,
+                                       ext4_sb_get_block_size(&fs->sb)));
+       } else
+               ext4_dir_write_entry(&fs->sb, block_entry, block_size, child, name,
+                                    name_len);
+
+       ext4_dir_set_checksum(parent,
+                       (struct ext4_dir_entry_ll *)new_block.data);
        new_block.dirty = true;
        rc = ext4_block_set(fs->bdev, &new_block);
 
        return rc;
 }
 
-int ext4_dir_find_entry(struct ext4_directory_search_result *result,
+int ext4_dir_find_entry(struct ext4_dir_search_result *result,
                        struct ext4_inode_ref *parent, const char *name,
                        uint32_t name_len)
 {
        struct ext4_sblock *sb = &parent->fs->sb;
 
+       /* Entry clear */
+       result->block.lb_id = 0;
+       result->dentry = NULL;
+
 #if CONFIG_DIR_INDEX_ENABLE
        /* Index search */
-       if ((ext4_sb_has_feature_compatible(sb,
-                                           EXT4_FEATURE_COMPAT_DIR_INDEX)) &&
+       if ((ext4_sb_feature_com(sb, EXT4_FCOM_DIR_INDEX)) &&
            (ext4_inode_has_flag(parent->inode, EXT4_INODE_FLAG_INDEX))) {
                int rc = ext4_dir_dx_find_entry(result, parent, name_len, name);
 
@@ -352,7 +478,7 @@ int ext4_dir_find_entry(struct ext4_directory_search_result *result,
        /* Linear algorithm */
 
        uint32_t iblock;
-       uint32_t fblock;
+       ext4_fsblk_t fblock;
        uint32_t block_size = ext4_sb_get_block_size(sb);
        uint32_t inode_size = ext4_inode_get_size(sb, parent->inode);
        uint32_t total_blocks = inode_size / block_size;
@@ -361,7 +487,9 @@ int ext4_dir_find_entry(struct ext4_directory_search_result *result,
        for (iblock = 0; iblock < total_blocks; ++iblock) {
                /* Load block address */
                int rc =
-                   ext4_fs_get_inode_data_block_index(parent, iblock, &fblock);
+                   ext4_fs_get_inode_data_block_index(parent,
+                                   iblock, &fblock,
+                                   false);
                if (rc != EOK)
                        return rc;
 
@@ -371,8 +499,20 @@ int ext4_dir_find_entry(struct ext4_directory_search_result *result,
                if (rc != EOK)
                        return rc;
 
+               if (!ext4_dir_checksum_verify(
+                               parent,
+                               (struct ext4_dir_entry_ll *)
+                                       block.data)) {
+                       ext4_dbg(DEBUG_DIR,
+                                DBG_WARN "Leaf block checksum failed."
+                                "Inode: %" PRIu32", "
+                                "Block: %" PRIu32"\n",
+                                parent->index,
+                                iblock);
+               }
+
                /* Try to find entry in block */
-               struct ext4_directory_entry_ll *res_entry;
+               struct ext4_dir_entry_ll *res_entry;
                rc = ext4_dir_find_in_block(&block, sb, name_len, name,
                                            &res_entry);
                if (rc == EOK) {
@@ -388,11 +528,6 @@ int ext4_dir_find_entry(struct ext4_directory_search_result *result,
                        return rc;
        }
 
-       /* Entry was not found */
-
-       result->block.lb_id = 0;
-       result->dentry = NULL;
-
        return ENOENT;
 }
 
@@ -405,7 +540,7 @@ int ext4_dir_remove_entry(struct ext4_inode_ref *parent, const char *name,
                return ENOTDIR;
 
        /* Try to find entry */
-       struct ext4_directory_search_result result;
+       struct ext4_dir_search_result result;
        int rc = ext4_dir_find_entry(&result, parent, name, name_len);
        if (rc != EOK)
                return rc;
@@ -424,7 +559,7 @@ int ext4_dir_remove_entry(struct ext4_inode_ref *parent, const char *name,
                uint32_t offset = 0;
 
                /* Start from the first entry in block */
-               struct ext4_directory_entry_ll *tmp_dentry =
+               struct ext4_dir_entry_ll *tmp_dentry =
                    (void *)result.block.data;
                uint16_t tmp_dentry_length =
                    ext4_dir_entry_ll_get_entry_length(tmp_dentry);
@@ -447,12 +582,15 @@ int ext4_dir_remove_entry(struct ext4_inode_ref *parent, const char *name,
                    tmp_dentry, tmp_dentry_length + del_entry_length);
        }
 
+       ext4_dir_set_checksum(parent,
+                       (struct ext4_dir_entry_ll *)result.block.data);
        result.block.dirty = true;
 
        return ext4_dir_destroy_result(parent, &result);
 }
 
 int ext4_dir_try_insert_entry(struct ext4_sblock *sb,
+                             struct ext4_inode_ref *inode_ref,
                              struct ext4_block *target_block,
                              struct ext4_inode_ref *child, const char *name,
                              uint32_t name_len)
@@ -460,14 +598,14 @@ int ext4_dir_try_insert_entry(struct ext4_sblock *sb,
        /* Compute required length entry and align it to 4 bytes */
        uint32_t block_size = ext4_sb_get_block_size(sb);
        uint16_t required_len =
-           sizeof(struct ext4_fake_directory_entry) + name_len;
+           sizeof(struct ext4_fake_dir_entry) + name_len;
 
        if ((required_len % 4) != 0)
                required_len += 4 - (required_len % 4);
 
        /* Initialize pointers, stop means to upper bound */
-       struct ext4_directory_entry_ll *dentry = (void *)target_block->data;
-       struct ext4_directory_entry_ll *stop =
+       struct ext4_dir_entry_ll *dentry = (void *)target_block->data;
+       struct ext4_dir_entry_ll *stop =
            (void *)(target_block->data + block_size);
 
        /*
@@ -477,11 +615,17 @@ int ext4_dir_try_insert_entry(struct ext4_sblock *sb,
        while (dentry < stop) {
                uint32_t inode = ext4_dir_entry_ll_get_inode(dentry);
                uint16_t rec_len = ext4_dir_entry_ll_get_entry_length(dentry);
+               uint8_t inode_type = ext4_dir_entry_ll_get_inode_type(sb, dentry);
 
                /* If invalid and large enough entry, use it */
-               if ((inode == 0) && (rec_len >= required_len)) {
+               if ((inode == 0) &&
+                   (inode_type != EXT4_DIRENTRY_DIR_CSUM) &&
+                   (rec_len >= required_len)) {
                        ext4_dir_write_entry(sb, dentry, rec_len, child, name,
                                             name_len);
+                       ext4_dir_set_checksum(inode_ref,
+                                               (struct ext4_dir_entry_ll *)
+                                               target_block->data);
                        target_block->dirty = true;
 
                        return EOK;
@@ -493,7 +637,7 @@ int ext4_dir_try_insert_entry(struct ext4_sblock *sb,
                            ext4_dir_entry_ll_get_name_length(sb, dentry);
 
                        uint16_t used_space =
-                           sizeof(struct ext4_fake_directory_entry) +
+                           sizeof(struct ext4_fake_dir_entry) +
                            used_name_len;
 
                        if ((used_name_len % 4) != 0)
@@ -506,11 +650,14 @@ int ext4_dir_try_insert_entry(struct ext4_sblock *sb,
                                /* Cut tail of current entry */
                                ext4_dir_entry_ll_set_entry_length(dentry,
                                                                   used_space);
-                               struct ext4_directory_entry_ll *new_entry =
+                               struct ext4_dir_entry_ll *new_entry =
                                    (void *)((uint8_t *)dentry + used_space);
                                ext4_dir_write_entry(sb, new_entry, free_space,
                                                     child, name, name_len);
 
+                               ext4_dir_set_checksum(inode_ref,
+                                               (struct ext4_dir_entry_ll *)
+                                               target_block->data);
                                target_block->dirty = true;
                                return EOK;
                        }
@@ -526,11 +673,11 @@ int ext4_dir_try_insert_entry(struct ext4_sblock *sb,
 
 int ext4_dir_find_in_block(struct ext4_block *block, struct ext4_sblock *sb,
                           size_t name_len, const char *name,
-                          struct ext4_directory_entry_ll **res_entry)
+                          struct ext4_dir_entry_ll **res_entry)
 {
        /* Start from the first entry in block */
-       struct ext4_directory_entry_ll *dentry =
-           (struct ext4_directory_entry_ll *)block->data;
+       struct ext4_dir_entry_ll *dentry =
+           (struct ext4_dir_entry_ll *)block->data;
 
        /* Set upper bound for cycling */
        uint8_t *addr_limit = block->data + ext4_sb_get_block_size(sb);
@@ -563,7 +710,7 @@ int ext4_dir_find_in_block(struct ext4_block *block, struct ext4_sblock *sb,
                        return EINVAL;
 
                /* Jump to next entry */
-               dentry = (struct ext4_directory_entry_ll *)((uint8_t *)dentry +
+               dentry = (struct ext4_dir_entry_ll *)((uint8_t *)dentry +
                                                            dentry_len);
        }
 
@@ -572,7 +719,7 @@ int ext4_dir_find_in_block(struct ext4_block *block, struct ext4_sblock *sb,
 }
 
 int ext4_dir_destroy_result(struct ext4_inode_ref *parent,
-                           struct ext4_directory_search_result *result)
+                           struct ext4_dir_search_result *result)
 {
        if (result->block.lb_id)
                return ext4_block_set(parent->fs->bdev, &result->block);