static struct ext4_directory_dx_countlimit * ext4_dir_dx_get_countlimit(struct ext4_inode_ref *inode_ref, struct ext4_directory_entry_ll *dirent, int *offset) { struct ext4_directory_entry_ll *dp; struct ext4_directory_dx_root *root; struct ext4_sblock *sb = &inode_ref->fs->sb; int count_offset; if (ext4_dir_entry_ll_get_entry_length(dirent) == ext4_sb_get_block_size(sb)) count_offset = 8; else if (ext4_dir_entry_ll_get_entry_length(dirent) == 12) { root = (struct ext4_directory_dx_root *)dirent; dp = (struct ext4_directory_entry_ll *)&root->dots[1]; if (ext4_dir_entry_ll_get_entry_length(dp) != ext4_sb_get_block_size(sb) - 12) return NULL; if (root->info.reserved_zero || root->info.info_length != sizeof(struct ext4_directory_dx_root_info)) return NULL; count_offset = 32; } else return NULL; if (offset) *offset = count_offset; return (struct ext4_directory_dx_countlimit *)(((char *)dirent) + count_offset); }
uint32_t ext4_num_base_meta_clusters(struct ext4_sblock *s, uint32_t block_group) { uint32_t num; uint32_t dsc_per_block = ext4_sb_get_block_size(s) / ext4_sb_get_desc_size(s); num = ext4_sb_is_super_in_bg(s, block_group); if (!ext4_sb_feature_incom(s, EXT4_FINCOM_META_BG) || block_group < ext4_sb_first_meta_bg(s) * dsc_per_block) { if (num) { num += ext4_bg_num_gdb(s, block_group); num += ext4_get16(s, s_reserved_gdt_blocks); } } else { num += ext4_bg_num_gdb(s, block_group); } uint32_t clustersize = 1024 << ext4_get32(s, log_cluster_size); uint32_t cluster_ratio = clustersize / ext4_sb_get_block_size(s); uint32_t v = (num + cluster_ratio - 1) >> ext4_get32(s, log_cluster_size); return v; }
static void ext4_dir_set_dx_checksum(struct ext4_inode_ref *inode_ref, struct ext4_directory_entry_ll *dirent) { int count_offset, limit, count; struct ext4_sblock *sb = &inode_ref->fs->sb; if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { struct ext4_directory_dx_countlimit *countlimit = ext4_dir_dx_get_countlimit(inode_ref, dirent, &count_offset); if (!countlimit) { /* Directory seems corrupted. */ return; } struct ext4_directory_dx_tail *t; limit = ext4_dir_dx_countlimit_get_limit(countlimit); count = ext4_dir_dx_countlimit_get_count(countlimit); if (count_offset + (limit * sizeof(struct ext4_directory_dx_entry)) > ext4_sb_get_block_size(sb) - sizeof(struct ext4_directory_dx_tail)) { /* There is no space to hold the checksum */ return; } t = (struct ext4_directory_dx_tail *) (((struct ext4_directory_dx_entry *)countlimit) + limit); t->checksum = to_le32(ext4_dir_dx_checksum(inode_ref, dirent, count_offset, count, t)); } }
/**@brief Initialize hash info structure necessary for index operations. * @param hinfo Pointer to hinfo to be initialized * @param root_block Root block (number 0) of index * @param sb Pointer to superblock * @param name_len Length of name to be computed hash value from * @param name Name to be computed hash value from * @return Standard error code */ static int ext4_dir_hinfo_init(struct ext4_hash_info *hinfo, struct ext4_block *root_block, struct ext4_sblock *sb, size_t name_len, const char *name) { struct ext4_directory_dx_root *root = (struct ext4_directory_dx_root *)root_block->data; if ((root->info.hash_version != EXT2_HTREE_LEGACY) && (root->info.hash_version != EXT2_HTREE_HALF_MD4) && (root->info.hash_version != EXT2_HTREE_TEA)) return EXT4_ERR_BAD_DX_DIR; /* Check unused flags */ if (root->info.unused_flags != 0) return EXT4_ERR_BAD_DX_DIR; /* Check indirect levels */ if (root->info.indirect_levels > 1) return EXT4_ERR_BAD_DX_DIR; /* Check if node limit is correct */ uint32_t block_size = ext4_sb_get_block_size(sb); uint32_t entry_space = block_size; entry_space -= 2 * sizeof(struct ext4_directory_dx_dot_entry); entry_space -= sizeof(struct ext4_directory_dx_root_info); if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) entry_space -= sizeof(struct ext4_directory_dx_tail); entry_space = entry_space / sizeof(struct ext4_directory_dx_entry); uint16_t limit = ext4_dir_dx_countlimit_get_limit( (struct ext4_directory_dx_countlimit *)&root->entries); if (limit != entry_space) return EXT4_ERR_BAD_DX_DIR; /* Check hash version and modify if necessary */ hinfo->hash_version = ext4_dir_dx_root_info_get_hash_version(&root->info); if ((hinfo->hash_version <= EXT2_HTREE_TEA) && (ext4_sb_check_flag(sb, EXT4_SUPERBLOCK_FLAGS_UNSIGNED_HASH))) { /* Use unsigned hash */ hinfo->hash_version += 3; } /* Load hash seed from superblock */ hinfo->seed = ext4_get8(sb, hash_seed); /* Compute hash value of name */ if (name) return ext4_dir_dx_hash_string(hinfo, name_len, name); return EOK; }
uint32_t ext4_bg_num_gdb(struct ext4_sblock *s, uint32_t group) { uint32_t dsc_per_block = ext4_sb_get_block_size(s) / ext4_sb_get_desc_size(s); uint32_t first_meta_bg = ext4_sb_first_meta_bg(s); uint32_t metagroup = group / dsc_per_block; if (!ext4_sb_feature_incom(s,EXT4_FINCOM_META_BG) || metagroup < first_meta_bg) return ext4_bg_num_gdb_nometa(s, group); return ext4_bg_num_gdb_meta(s, group); }
static uint32_t ext4_bg_num_gdb_meta(struct ext4_sblock *s, uint32_t group) { uint32_t dsc_per_block = ext4_sb_get_block_size(s) / ext4_sb_get_desc_size(s); uint32_t metagroup = group / dsc_per_block; uint32_t first = metagroup * dsc_per_block; uint32_t last = first + dsc_per_block - 1; if (group == first || group == first + 1 || group == last) return 1; return 0; }
static uint32_t ext4_bg_num_gdb_nometa(struct ext4_sblock *s, uint32_t group) { if (!ext4_sb_is_super_in_bg(s, group)) return 0; uint32_t dsc_per_block = ext4_sb_get_block_size(s) / ext4_sb_get_desc_size(s); uint32_t db_count = (ext4_block_group_cnt(s) + dsc_per_block - 1) / dsc_per_block; if (ext4_sb_feature_incom(s, EXT4_FINCOM_META_BG)) return ext4_sb_first_meta_bg(s); return db_count; }
/**@brief Walk through index tree and load leaf with corresponding hash value. * @param hinfo Initialized hash info structure * @param inode_ref Current i-node * @param root_block Root block (iblock 0), where is root node located * @param dx_block Pointer to leaf node in dx_blocks array * @param dx_blocks Array with the whole path from root to leaf * @return Standard error code */ static int ext4_dir_dx_get_leaf(struct ext4_hash_info *hinfo, struct ext4_inode_ref *inode_ref, struct ext4_block *root_block, struct ext4_directory_dx_block **dx_block, struct ext4_directory_dx_block *dx_blocks) { struct ext4_directory_dx_block *tmp_dx_block = dx_blocks; struct ext4_directory_dx_root *root = (struct ext4_directory_dx_root *)root_block->data; struct ext4_directory_dx_entry *entries = (struct ext4_directory_dx_entry *)&root->entries; uint16_t limit = ext4_dir_dx_countlimit_get_limit( (struct ext4_directory_dx_countlimit *)entries); uint8_t indirect_level = ext4_dir_dx_root_info_get_indirect_levels(&root->info); struct ext4_block *tmp_block = root_block; struct ext4_directory_dx_entry *p; struct ext4_directory_dx_entry *q; struct ext4_directory_dx_entry *m; struct ext4_directory_dx_entry *at; /* Walk through the index tree */ while (true) { uint16_t count = ext4_dir_dx_countlimit_get_count( (struct ext4_directory_dx_countlimit *)entries); if ((count == 0) || (count > limit)) return EXT4_ERR_BAD_DX_DIR; /* Do binary search in every node */ p = entries + 1; q = entries + count - 1; while (p <= q) { m = p + (q - p) / 2; if (ext4_dir_dx_entry_get_hash(m) > hinfo->hash) q = m - 1; else p = m + 1; } at = p - 1; /* Write results */ memcpy(&tmp_dx_block->block, tmp_block, sizeof(struct ext4_block)); tmp_dx_block->entries = entries; tmp_dx_block->position = at; /* Is algorithm in the leaf? */ if (indirect_level == 0) { *dx_block = tmp_dx_block; return EOK; } /* Goto child node */ uint32_t next_block = ext4_dir_dx_entry_get_block(at); indirect_level--; ext4_fsblk_t fblock; int rc = ext4_fs_get_inode_data_block_index( inode_ref, next_block, &fblock, false); if (rc != EOK) return rc; rc = ext4_block_get(inode_ref->fs->bdev, tmp_block, fblock); if (rc != EOK) return rc; entries = ((struct ext4_directory_dx_node *)tmp_block->data)->entries; limit = ext4_dir_dx_countlimit_get_limit( (struct ext4_directory_dx_countlimit *)entries); uint16_t entry_space = ext4_sb_get_block_size(&inode_ref->fs->sb) - sizeof(struct ext4_fake_directory_entry); if (ext4_sb_feature_ro_com(&inode_ref->fs->sb, EXT4_FRO_COM_METADATA_CSUM)) entry_space -= sizeof(struct ext4_directory_dx_tail); entry_space = entry_space / sizeof(struct ext4_directory_dx_entry); if (limit != entry_space) { ext4_block_set(inode_ref->fs->bdev, tmp_block); return EXT4_ERR_BAD_DX_DIR; } ++tmp_dx_block; } /* Unreachable */ return EOK; }
int ext4_dir_dx_init(struct ext4_inode_ref *dir, struct ext4_inode_ref *parent) { /* Load block 0, where will be index root located */ ext4_fsblk_t fblock; uint32_t iblock; struct ext4_sblock *sb = &dir->fs->sb; int rc = ext4_fs_append_inode_block(dir, &fblock, &iblock); if (rc != EOK) return rc; struct ext4_block block; rc = ext4_block_get(dir->fs->bdev, &block, fblock); if (rc != EOK) return rc; /* Initialize pointers to data structures */ struct ext4_directory_dx_root *root = (void *)block.data; struct ext4_directory_dx_root_info *info = &(root->info); /* Initialize dot entries */ ext4_dir_write_entry(&dir->fs->sb, (struct ext4_directory_entry_ll *)root->dots, 12, dir, ".", strlen(".")); ext4_dir_write_entry(&dir->fs->sb, (struct ext4_directory_entry_ll *)(root->dots + 1), ext4_sb_get_block_size(sb) - 12, parent, "..", strlen("..")); /* Initialize root info structure */ uint8_t hash_version = ext4_get8(&dir->fs->sb, default_hash_version); ext4_dir_dx_root_info_set_hash_version(info, hash_version); ext4_dir_dx_root_info_set_indirect_levels(info, 0); ext4_dir_dx_root_info_set_info_length(info, 8); /* Set limit and current number of entries */ struct ext4_directory_dx_countlimit *countlimit = (struct ext4_directory_dx_countlimit *)&root->entries; ext4_dir_dx_countlimit_set_count(countlimit, 1); uint32_t block_size = ext4_sb_get_block_size(&dir->fs->sb); uint32_t entry_space = block_size - 2 * sizeof(struct ext4_directory_dx_dot_entry) - sizeof(struct ext4_directory_dx_root_info); if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) entry_space -= sizeof(struct ext4_directory_dx_tail); uint16_t root_limit = entry_space / sizeof(struct ext4_directory_dx_entry); ext4_dir_dx_countlimit_set_limit(countlimit, root_limit); /* Append new block, where will be new entries inserted in the future */ rc = ext4_fs_append_inode_block(dir, &fblock, &iblock); if (rc != EOK) { ext4_block_set(dir->fs->bdev, &block); return rc; } struct ext4_block new_block; rc = ext4_block_get(dir->fs->bdev, &new_block, fblock); if (rc != EOK) { ext4_block_set(dir->fs->bdev, &block); return rc; } /* Fill the whole block with empty entry */ struct ext4_directory_entry_ll *block_entry = (void *)new_block.data; if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { ext4_dir_entry_ll_set_entry_length(block_entry, block_size - sizeof(struct ext4_directory_entry_tail)); ext4_dir_entry_ll_set_name_length(sb, block_entry, 0); ext4_dir_entry_ll_set_inode_type(sb, block_entry, EXT4_DIRENTRY_UNKNOWN); initialize_dir_tail(EXT4_DIRENT_TAIL(block_entry, ext4_sb_get_block_size(sb))); ext4_dir_set_checksum(dir, (struct ext4_directory_entry_ll *)new_block.data); } else { ext4_dir_entry_ll_set_entry_length(block_entry, block_size); } ext4_dir_entry_ll_set_inode(block_entry, 0); new_block.dirty = true; rc = ext4_block_set(dir->fs->bdev, &new_block); if (rc != EOK) { ext4_block_set(dir->fs->bdev, &block); return rc; } /* Connect new block to the only entry in index */ struct ext4_directory_dx_entry *entry = root->entries; ext4_dir_dx_entry_set_block(entry, iblock); ext4_dir_set_dx_checksum(dir, (struct ext4_directory_entry_ll *)block.data); block.dirty = true; return ext4_block_set(dir->fs->bdev, &block); }