|  | /* | 
|  | * Copyright (c) 2023 Antmicro <www.antmicro.com> | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #include <zephyr/kernel.h> | 
|  | #include <zephyr/init.h> | 
|  | #include <zephyr/fs/fs.h> | 
|  | #include <zephyr/logging/log.h> | 
|  | #include <zephyr/sys/util.h> | 
|  | #include <zephyr/sys/byteorder.h> | 
|  |  | 
|  | #include <stdint.h> | 
|  |  | 
|  | #include "ext2.h" | 
|  | #include "ext2_struct.h" | 
|  | #include "ext2_impl.h" | 
|  | #include "ext2_diskops.h" | 
|  | #include "ext2_bitmap.h" | 
|  |  | 
|  | LOG_MODULE_DECLARE(ext2); | 
|  |  | 
|  | /* Static declarations */ | 
|  | static int get_level_offsets(struct ext2_data *fs, uint32_t block, uint32_t offsets[4]); | 
|  | static inline uint32_t get_ngroups(struct ext2_data *fs); | 
|  |  | 
|  | #define MAX_OFFSETS_SIZE 4 | 
|  | /* Array of zeros to be used in inode block calculation */ | 
|  | static const uint32_t zero_offsets[MAX_OFFSETS_SIZE]; | 
|  |  | 
|  | static void fill_sblock(struct ext2_superblock *sb, struct ext2_disk_superblock *disk_sb) | 
|  | { | 
|  | sb->s_inodes_count      = sys_le32_to_cpu(disk_sb->s_inodes_count); | 
|  | sb->s_blocks_count      = sys_le32_to_cpu(disk_sb->s_blocks_count); | 
|  | sb->s_free_blocks_count = sys_le32_to_cpu(disk_sb->s_free_blocks_count); | 
|  | sb->s_free_inodes_count = sys_le32_to_cpu(disk_sb->s_free_inodes_count); | 
|  | sb->s_first_data_block  = sys_le32_to_cpu(disk_sb->s_first_data_block); | 
|  | sb->s_log_block_size    = sys_le32_to_cpu(disk_sb->s_log_block_size); | 
|  | sb->s_log_frag_size     = sys_le32_to_cpu(disk_sb->s_log_frag_size); | 
|  | sb->s_blocks_per_group  = sys_le32_to_cpu(disk_sb->s_blocks_per_group); | 
|  | sb->s_frags_per_group   = sys_le32_to_cpu(disk_sb->s_frags_per_group); | 
|  | sb->s_inodes_per_group  = sys_le32_to_cpu(disk_sb->s_inodes_per_group); | 
|  | sb->s_mnt_count         = sys_le16_to_cpu(disk_sb->s_mnt_count); | 
|  | sb->s_max_mnt_count     = sys_le16_to_cpu(disk_sb->s_max_mnt_count); | 
|  | sb->s_magic             = sys_le16_to_cpu(disk_sb->s_magic); | 
|  | sb->s_state             = sys_le16_to_cpu(disk_sb->s_state); | 
|  | sb->s_errors            = sys_le16_to_cpu(disk_sb->s_errors); | 
|  | sb->s_creator_os        = sys_le32_to_cpu(disk_sb->s_creator_os); | 
|  | sb->s_rev_level         = sys_le32_to_cpu(disk_sb->s_rev_level); | 
|  | sb->s_first_ino         = sys_le32_to_cpu(disk_sb->s_first_ino); | 
|  | sb->s_inode_size        = sys_le16_to_cpu(disk_sb->s_inode_size); | 
|  | sb->s_block_group_nr    = sys_le16_to_cpu(disk_sb->s_block_group_nr); | 
|  | sb->s_feature_compat    = sys_le32_to_cpu(disk_sb->s_feature_compat); | 
|  | sb->s_feature_incompat  = sys_le32_to_cpu(disk_sb->s_feature_incompat); | 
|  | sb->s_feature_ro_compat = sys_le32_to_cpu(disk_sb->s_feature_ro_compat); | 
|  | } | 
|  |  | 
|  | static void fill_disk_sblock(struct ext2_disk_superblock *disk_sb, struct ext2_superblock *sb) | 
|  | { | 
|  | disk_sb->s_inodes_count      = sys_cpu_to_le32(sb->s_inodes_count); | 
|  | disk_sb->s_blocks_count      = sys_cpu_to_le32(sb->s_blocks_count); | 
|  | disk_sb->s_free_blocks_count = sys_cpu_to_le32(sb->s_free_blocks_count); | 
|  | disk_sb->s_free_inodes_count = sys_cpu_to_le32(sb->s_free_inodes_count); | 
|  | disk_sb->s_first_data_block  = sys_cpu_to_le32(sb->s_first_data_block); | 
|  | disk_sb->s_log_block_size    = sys_cpu_to_le32(sb->s_log_block_size); | 
|  | disk_sb->s_log_frag_size     = sys_cpu_to_le32(sb->s_log_frag_size); | 
|  | disk_sb->s_blocks_per_group  = sys_cpu_to_le32(sb->s_blocks_per_group); | 
|  | disk_sb->s_frags_per_group   = sys_cpu_to_le32(sb->s_frags_per_group); | 
|  | disk_sb->s_inodes_per_group  = sys_cpu_to_le32(sb->s_inodes_per_group); | 
|  | disk_sb->s_mnt_count         = sys_cpu_to_le16(sb->s_mnt_count); | 
|  | disk_sb->s_max_mnt_count     = sys_cpu_to_le16(sb->s_max_mnt_count); | 
|  | disk_sb->s_magic             = sys_cpu_to_le16(sb->s_magic); | 
|  | disk_sb->s_state             = sys_cpu_to_le16(sb->s_state); | 
|  | disk_sb->s_errors            = sys_cpu_to_le16(sb->s_errors); | 
|  | disk_sb->s_creator_os        = sys_cpu_to_le32(sb->s_creator_os); | 
|  | disk_sb->s_rev_level         = sys_cpu_to_le32(sb->s_rev_level); | 
|  | disk_sb->s_first_ino         = sys_cpu_to_le32(sb->s_first_ino); | 
|  | disk_sb->s_inode_size        = sys_cpu_to_le16(sb->s_inode_size); | 
|  | disk_sb->s_block_group_nr    = sys_cpu_to_le16(sb->s_block_group_nr); | 
|  | disk_sb->s_feature_compat    = sys_cpu_to_le32(sb->s_feature_compat); | 
|  | disk_sb->s_feature_incompat  = sys_cpu_to_le32(sb->s_feature_incompat); | 
|  | disk_sb->s_feature_ro_compat = sys_cpu_to_le32(sb->s_feature_ro_compat); | 
|  | } | 
|  |  | 
|  | static void fill_bgroup(struct ext2_bgroup *bg, struct ext2_disk_bgroup *disk_bg) | 
|  | { | 
|  | bg->bg_block_bitmap      = sys_le32_to_cpu(disk_bg->bg_block_bitmap); | 
|  | bg->bg_inode_bitmap      = sys_le32_to_cpu(disk_bg->bg_inode_bitmap); | 
|  | bg->bg_inode_table       = sys_le32_to_cpu(disk_bg->bg_inode_table); | 
|  | bg->bg_free_blocks_count = sys_le16_to_cpu(disk_bg->bg_free_blocks_count); | 
|  | bg->bg_free_inodes_count = sys_le16_to_cpu(disk_bg->bg_free_inodes_count); | 
|  | bg->bg_used_dirs_count   = sys_le16_to_cpu(disk_bg->bg_used_dirs_count); | 
|  | } | 
|  |  | 
|  | static void fill_disk_bgroup(struct ext2_disk_bgroup *disk_bg, struct ext2_bgroup *bg) | 
|  | { | 
|  | disk_bg->bg_block_bitmap      = sys_cpu_to_le32(bg->bg_block_bitmap); | 
|  | disk_bg->bg_inode_bitmap      = sys_cpu_to_le32(bg->bg_inode_bitmap); | 
|  | disk_bg->bg_inode_table       = sys_cpu_to_le32(bg->bg_inode_table); | 
|  | disk_bg->bg_free_blocks_count = sys_cpu_to_le16(bg->bg_free_blocks_count); | 
|  | disk_bg->bg_free_inodes_count = sys_cpu_to_le16(bg->bg_free_inodes_count); | 
|  | disk_bg->bg_used_dirs_count   = sys_cpu_to_le16(bg->bg_used_dirs_count); | 
|  | } | 
|  |  | 
|  | static void fill_inode(struct ext2_inode *inode, struct ext2_disk_inode *dino) | 
|  | { | 
|  | inode->i_mode        = sys_le16_to_cpu(dino->i_mode); | 
|  | inode->i_size        = sys_le32_to_cpu(dino->i_size); | 
|  | inode->i_links_count = sys_le16_to_cpu(dino->i_links_count); | 
|  | inode->i_blocks      = sys_le32_to_cpu(dino->i_blocks); | 
|  | for (int i = 0; i < EXT2_INODE_BLOCKS; i++) { | 
|  | inode->i_block[i] = sys_le32_to_cpu(dino->i_block[i]); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void fill_disk_inode(struct ext2_disk_inode *dino, struct ext2_inode *inode) | 
|  | { | 
|  | dino->i_mode        = sys_cpu_to_le16(inode->i_mode); | 
|  | dino->i_size        = sys_cpu_to_le32(inode->i_size); | 
|  | dino->i_links_count = sys_cpu_to_le16(inode->i_links_count); | 
|  | dino->i_blocks      = sys_cpu_to_le32(inode->i_blocks); | 
|  | for (int i = 0; i < EXT2_INODE_BLOCKS; i++) { | 
|  | dino->i_block[i] = sys_cpu_to_le32(inode->i_block[i]); | 
|  | } | 
|  | } | 
|  |  | 
|  | struct ext2_direntry *ext2_fetch_direntry(struct ext2_disk_direntry *disk_de) | 
|  | { | 
|  |  | 
|  | if (disk_de->de_name_len > EXT2_MAX_FILE_NAME) { | 
|  | return NULL; | 
|  | } | 
|  | uint32_t prog_rec_len = sizeof(struct ext2_direntry) + disk_de->de_name_len; | 
|  | struct ext2_direntry *de = k_heap_alloc(&direntry_heap, prog_rec_len, K_FOREVER); | 
|  |  | 
|  | __ASSERT(de != NULL, "allocated direntry can't be NULL"); | 
|  |  | 
|  | de->de_inode     = sys_le32_to_cpu(disk_de->de_inode); | 
|  | de->de_rec_len   = sys_le16_to_cpu(disk_de->de_rec_len); | 
|  | de->de_name_len  = disk_de->de_name_len; | 
|  | de->de_file_type = disk_de->de_file_type; | 
|  | memcpy(de->de_name, disk_de->de_name, de->de_name_len); | 
|  | return de; | 
|  | } | 
|  |  | 
|  | void ext2_write_direntry(struct ext2_disk_direntry *disk_de, struct ext2_direntry *de) | 
|  | { | 
|  | disk_de->de_inode     = sys_le32_to_cpu(de->de_inode); | 
|  | disk_de->de_rec_len   = sys_le16_to_cpu(de->de_rec_len); | 
|  | disk_de->de_name_len  = de->de_name_len; | 
|  | disk_de->de_file_type = de->de_file_type; | 
|  | memcpy(disk_de->de_name, de->de_name, de->de_name_len); | 
|  | } | 
|  |  | 
|  | uint32_t ext2_get_disk_direntry_inode(struct ext2_disk_direntry *de) | 
|  | { | 
|  | return sys_le32_to_cpu(de->de_inode); | 
|  | } | 
|  |  | 
|  | uint32_t ext2_get_disk_direntry_reclen(struct ext2_disk_direntry *de) | 
|  | { | 
|  | return sys_le16_to_cpu(de->de_rec_len); | 
|  | } | 
|  |  | 
|  | uint8_t ext2_get_disk_direntry_namelen(struct ext2_disk_direntry *de) | 
|  | { | 
|  | return de->de_name_len; | 
|  | } | 
|  |  | 
|  | uint8_t ext2_get_disk_direntry_type(struct ext2_disk_direntry *de) | 
|  | { | 
|  | return de->de_file_type; | 
|  | } | 
|  |  | 
|  | void ext2_set_disk_direntry_inode(struct ext2_disk_direntry *de, uint32_t inode) | 
|  | { | 
|  | de->de_inode = sys_cpu_to_le32(inode); | 
|  | } | 
|  |  | 
|  | void ext2_set_disk_direntry_reclen(struct ext2_disk_direntry *de, uint16_t reclen) | 
|  | { | 
|  | de->de_rec_len = sys_cpu_to_le16(reclen); | 
|  | } | 
|  |  | 
|  | void ext2_set_disk_direntry_namelen(struct ext2_disk_direntry *de, uint8_t namelen) | 
|  | { | 
|  | de->de_name_len = namelen; | 
|  | } | 
|  |  | 
|  | void ext2_set_disk_direntry_type(struct ext2_disk_direntry *de, uint8_t type) | 
|  | { | 
|  | de->de_file_type = type; | 
|  | } | 
|  |  | 
|  | void ext2_set_disk_direntry_name(struct ext2_disk_direntry *de, const char *name, size_t len) | 
|  | { | 
|  | memcpy(de->de_name, name, len); | 
|  | } | 
|  |  | 
|  | int ext2_fetch_superblock(struct ext2_data *fs) | 
|  | { | 
|  | struct ext2_block *b; | 
|  | uint32_t sblock_offset; | 
|  |  | 
|  | if (fs->block_size == 1024) { | 
|  | sblock_offset = 0; | 
|  | b = ext2_get_block(fs, 1); | 
|  | } else { | 
|  | sblock_offset = 1024; | 
|  | b = ext2_get_block(fs, 0); | 
|  | } | 
|  | if (b == NULL) { | 
|  | return -ENOENT; | 
|  | } | 
|  |  | 
|  | struct ext2_disk_superblock *disk_sb = | 
|  | (struct ext2_disk_superblock *)(b->data + sblock_offset); | 
|  |  | 
|  | fill_sblock(&fs->sblock, disk_sb); | 
|  |  | 
|  | ext2_drop_block(b); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static inline uint32_t get_ngroups(struct ext2_data *fs) | 
|  | { | 
|  | uint32_t ngroups = | 
|  | fs->sblock.s_blocks_count / fs->sblock.s_blocks_per_group; | 
|  |  | 
|  | if (fs->sblock.s_blocks_count % fs->sblock.s_blocks_per_group != 0) { | 
|  | /* there is one more group if the last group is incomplete */ | 
|  | ngroups += 1; | 
|  | } | 
|  | return ngroups; | 
|  | } | 
|  |  | 
|  | int ext2_fetch_block_group(struct ext2_data *fs, uint32_t group) | 
|  | { | 
|  | struct ext2_bgroup *bg = &fs->bgroup; | 
|  |  | 
|  | /* Check if block group is cached */ | 
|  | if (group == bg->num) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | uint32_t ngroups = get_ngroups(fs); | 
|  |  | 
|  | LOG_DBG("ngroups:%d", ngroups); | 
|  | LOG_DBG("cur_group:%d fetch_group:%d", bg->num, group); | 
|  |  | 
|  | if (group > ngroups) { | 
|  | return -ERANGE; | 
|  | } | 
|  |  | 
|  | uint32_t groups_per_block = fs->block_size / sizeof(struct ext2_disk_bgroup); | 
|  | uint32_t block = group / groups_per_block; | 
|  | uint32_t offset = group % groups_per_block; | 
|  | uint32_t global_block = fs->sblock.s_first_data_block + 1 + block; | 
|  |  | 
|  | struct ext2_block *b = ext2_get_block(fs, global_block); | 
|  |  | 
|  | if (b == NULL) { | 
|  | return -ENOENT; | 
|  | } | 
|  |  | 
|  | struct ext2_disk_bgroup *disk_bg = ((struct ext2_disk_bgroup *)b->data) + offset; | 
|  |  | 
|  | fill_bgroup(bg, disk_bg); | 
|  |  | 
|  | /* Drop unused block */ | 
|  | ext2_drop_block(b); | 
|  |  | 
|  | /* Invalidate previously fetched blocks */ | 
|  | ext2_drop_block(bg->inode_table); | 
|  | ext2_drop_block(bg->inode_bitmap); | 
|  | ext2_drop_block(bg->block_bitmap); | 
|  | bg->inode_table = bg->inode_bitmap = bg->block_bitmap = NULL; | 
|  |  | 
|  | bg->fs = fs; | 
|  | bg->num = group; | 
|  |  | 
|  | LOG_DBG("[BG:%d] itable:%d free_blk:%d free_ino:%d useddirs:%d bbitmap:%d ibitmap:%d", | 
|  | group, bg->bg_inode_table, | 
|  | bg->bg_free_blocks_count, | 
|  | bg->bg_free_inodes_count, | 
|  | bg->bg_used_dirs_count, | 
|  | bg->bg_block_bitmap, | 
|  | bg->bg_inode_bitmap); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int ext2_fetch_bg_itable(struct ext2_bgroup *bg, uint32_t block) | 
|  | { | 
|  | if (bg->inode_table && bg->inode_table_block == block) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | struct ext2_data *fs = bg->fs; | 
|  | uint32_t global_block = bg->bg_inode_table + block; | 
|  |  | 
|  | ext2_drop_block(bg->inode_table); | 
|  | bg->inode_table = ext2_get_block(fs, global_block); | 
|  | if (bg->inode_table == NULL) { | 
|  | return -ENOENT; | 
|  | } | 
|  |  | 
|  | bg->inode_table_block = block; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int ext2_fetch_bg_ibitmap(struct ext2_bgroup *bg) | 
|  | { | 
|  | if (bg->inode_bitmap) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | struct ext2_data *fs = bg->fs; | 
|  | uint32_t global_block = bg->bg_inode_bitmap; | 
|  |  | 
|  | bg->inode_bitmap = ext2_get_block(fs, global_block); | 
|  | if (bg->inode_bitmap == NULL) { | 
|  | return -ENOENT; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int ext2_fetch_bg_bbitmap(struct ext2_bgroup *bg) | 
|  | { | 
|  | if (bg->block_bitmap) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | struct ext2_data *fs = bg->fs; | 
|  | uint32_t global_block = bg->bg_block_bitmap; | 
|  |  | 
|  | bg->block_bitmap = ext2_get_block(fs, global_block); | 
|  | if (bg->block_bitmap == NULL) { | 
|  | return -ENOENT; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Fetch block group and inode table of given inode. | 
|  | * | 
|  | * @return Offset of inode in currently fetched inode table block. | 
|  | */ | 
|  | static int32_t get_itable_entry(struct ext2_data *fs, uint32_t ino) | 
|  | { | 
|  | int rc; | 
|  | uint32_t ino_group = (ino - 1) / fs->sblock.s_inodes_per_group; | 
|  | uint32_t ino_index = (ino - 1) % fs->sblock.s_inodes_per_group; | 
|  |  | 
|  | LOG_DBG("ino_group:%d ino_index:%d", ino_group, ino_index); | 
|  |  | 
|  | rc = ext2_fetch_block_group(fs, ino_group); | 
|  | if (rc < 0) { | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | uint32_t inode_size = fs->sblock.s_inode_size; | 
|  | uint32_t inodes_per_block = fs->block_size / inode_size; | 
|  |  | 
|  | uint32_t block_index  = ino_index / inodes_per_block; | 
|  | uint32_t block_offset = ino_index % inodes_per_block; | 
|  |  | 
|  | LOG_DBG("block_index:%d block_offset:%d", block_index, block_offset); | 
|  |  | 
|  | rc = ext2_fetch_bg_itable(&fs->bgroup, block_index); | 
|  | if (rc < 0) { | 
|  | return rc; | 
|  | } | 
|  | return block_offset; | 
|  | } | 
|  |  | 
|  | int ext2_fetch_inode(struct ext2_data *fs, uint32_t ino, struct ext2_inode *inode) | 
|  | { | 
|  |  | 
|  | int32_t itable_offset = get_itable_entry(fs, ino); | 
|  |  | 
|  | LOG_DBG("fetch inode: %d", ino); | 
|  |  | 
|  | if (itable_offset < 0) { | 
|  | return itable_offset; | 
|  | } | 
|  |  | 
|  | struct ext2_disk_inode *dino = &BGROUP_INODE_TABLE(&fs->bgroup)[itable_offset]; | 
|  |  | 
|  | fill_inode(inode, dino); | 
|  |  | 
|  | /* Copy needed data into inode structure */ | 
|  | inode->i_fs = fs; | 
|  | inode->flags = 0; | 
|  | inode->i_id = ino; | 
|  |  | 
|  | LOG_DBG("mode:%d size:%d links:%d", dino->i_mode, dino->i_size, dino->i_links_count); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * @param try_current -- if true then check if searched offset matches offset of currently fetched | 
|  | *        block on that level. If they match then it is the block we are looking for. | 
|  | */ | 
|  | static int fetch_level_blocks(struct ext2_inode *inode, uint32_t offsets[4], int lvl, int max_lvl, | 
|  | bool try_current) | 
|  | { | 
|  | uint32_t block; | 
|  | bool already_fetched = try_current && (offsets[lvl] == inode->offsets[lvl]); | 
|  |  | 
|  | /* all needed blocks fetched */ | 
|  | if (lvl > max_lvl) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* If already fetched block matches desired one we can use it and move to the next level. */ | 
|  | if (!already_fetched) { | 
|  | /* Fetched block on current level was wrong. | 
|  | * We can't use fetched blocks on this and next levels. | 
|  | */ | 
|  | try_current = false; | 
|  |  | 
|  | ext2_drop_block(inode->blocks[lvl]); | 
|  |  | 
|  | if (lvl == 0) { | 
|  | block = inode->i_block[offsets[0]]; | 
|  | } else { | 
|  | uint32_t *list = (uint32_t *)inode->blocks[lvl - 1]->data; | 
|  |  | 
|  | block = sys_le32_to_cpu(list[offsets[lvl]]); | 
|  | } | 
|  |  | 
|  | if (block == 0) { | 
|  | inode->blocks[lvl] = ext2_get_empty_block(inode->i_fs); | 
|  | } else { | 
|  | inode->blocks[lvl] = ext2_get_block(inode->i_fs, block); | 
|  | } | 
|  |  | 
|  | if (inode->blocks[lvl] == NULL) { | 
|  | return -ENOENT; | 
|  | } | 
|  | LOG_DBG("[fetch] lvl:%d off:%d num:%d", lvl, offsets[lvl], block); | 
|  | } | 
|  | return fetch_level_blocks(inode, offsets, lvl + 1, max_lvl, try_current); | 
|  | } | 
|  |  | 
|  | int ext2_fetch_inode_block(struct ext2_inode *inode, uint32_t block) | 
|  | { | 
|  | /* Check if correct inode block is cached. */ | 
|  | if (inode->flags & INODE_FETCHED_BLOCK && inode->block_num == block) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | LOG_DBG("inode:%d cur_blk:%d fetch_blk:%d", inode->i_id, inode->block_num, block); | 
|  |  | 
|  | struct ext2_data *fs = inode->i_fs; | 
|  | int max_lvl, ret; | 
|  | uint32_t offsets[MAX_OFFSETS_SIZE]; | 
|  | bool try_current = inode->flags & INODE_FETCHED_BLOCK; | 
|  |  | 
|  | max_lvl = get_level_offsets(fs, block, offsets); | 
|  |  | 
|  | ret = fetch_level_blocks(inode, offsets, 0, max_lvl, try_current); | 
|  | if (ret < 0) { | 
|  | ext2_inode_drop_blocks(inode); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | memcpy(inode->offsets, offsets, MAX_OFFSETS_SIZE * sizeof(uint32_t)); | 
|  | inode->block_lvl = max_lvl; | 
|  | inode->block_num = block; | 
|  | inode->flags |= INODE_FETCHED_BLOCK; | 
|  |  | 
|  | LOG_DBG("[ino:%d fetch]\t Lvl:%d {%d, %d, %d, %d}", inode->i_id, inode->block_lvl, | 
|  | inode->offsets[0], inode->offsets[1], inode->offsets[2], inode->offsets[3]); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static bool all_zero(const uint32_t *offsets, int lvl) | 
|  | { | 
|  | for (int i = 0; i < lvl; ++i) { | 
|  | if (offsets[i] > 0) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief delete blocks from one described with offsets array | 
|  | * | 
|  | * NOTE: To use this function safely drop all fetched inode blocks | 
|  | * | 
|  | * @retval >=0 Number of removed blocks (only the blocks with actual inode data) | 
|  | * @retval <0 Error | 
|  | */ | 
|  | static int64_t delete_blocks(struct ext2_data *fs, uint32_t block_num, int lvl, | 
|  | const uint32_t *offsets) | 
|  | { | 
|  | __ASSERT(block_num != 0, "Can't delete zero block"); | 
|  | __ASSERT(lvl >= 0 && lvl < MAX_OFFSETS_SIZE, | 
|  | "Expected 0 <= lvl < %d (got: lvl=%d)", lvl, MAX_OFFSETS_SIZE); | 
|  |  | 
|  | int ret; | 
|  | int64_t removed = 0, rem; | 
|  | uint32_t *list, start_blk; | 
|  | struct ext2_block *list_block = NULL; | 
|  | bool remove_current = false; | 
|  | bool block_dirty = false; | 
|  |  | 
|  | if (lvl == 0) { | 
|  | /* If we got here we will remove this block | 
|  | * and it is also a block with actual inode data, hence we count it. | 
|  | */ | 
|  | remove_current = true; | 
|  | removed++; | 
|  | } else { | 
|  | /* Current block holds a list of blocks. */ | 
|  | list_block = ext2_get_block(fs, block_num); | 
|  |  | 
|  | if (list_block == NULL) { | 
|  | return -ENOENT; | 
|  | } | 
|  | list = (uint32_t *)list_block->data; | 
|  |  | 
|  | if (all_zero(offsets, lvl)) { | 
|  | /* We remove all blocks that are referenced by current block, hence current | 
|  | * block isn't needed anymore. | 
|  | */ | 
|  | remove_current = true; | 
|  | start_blk = 0; | 
|  |  | 
|  | } else if (lvl == 1) { | 
|  | /* We are on one before last layer of inode block table. The next layer are | 
|  | * single blocks, hence we will just remove them. | 
|  | * We can just set start_blk here and remove blocks in loop at the end of | 
|  | * this function. | 
|  | */ | 
|  | start_blk = offsets[0]; | 
|  |  | 
|  | } else { | 
|  | uint32_t block_num2 = sys_le32_to_cpu(list[offsets[0]]); | 
|  |  | 
|  | /* We don't remove all blocks referenced by current block. We have to use | 
|  | * offsets to decide which part of next block we want to remove. | 
|  | */ | 
|  | if (block_num2 == 0) { | 
|  | LOG_ERR("Inode block that references other blocks must be nonzero"); | 
|  | fs->flags |= EXT2_DATA_FLAGS_ERR; | 
|  | removed = -EINVAL; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | /* We will start removing whole blocks from next block on this level */ | 
|  | start_blk = offsets[0] + 1; | 
|  |  | 
|  | /* Remove desired part of lower level block. */ | 
|  | rem = delete_blocks(fs, block_num2, lvl - 1, &offsets[1]); | 
|  | if (rem < 0) { | 
|  | removed = rem; | 
|  | goto out; | 
|  | } | 
|  | removed += rem; | 
|  | } | 
|  |  | 
|  | /* Iterate over blocks that will be entirely deleted */ | 
|  | for (uint32_t i = start_blk; i < fs->block_size / EXT2_BLOCK_NUM_SIZE; ++i) { | 
|  | uint32_t block_num2 = sys_le32_to_cpu(list[i]); | 
|  |  | 
|  | if (block_num2 == 0) { | 
|  | continue; | 
|  | } | 
|  | rem = delete_blocks(fs, block_num2, lvl - 1, zero_offsets); | 
|  | if (rem < 0) { | 
|  | removed = rem; | 
|  | goto out; | 
|  | } | 
|  | removed += rem; | 
|  | list[i] = 0; | 
|  | block_dirty = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (remove_current) { | 
|  | LOG_DBG("free block %d (lvl %d)", block_num, lvl); | 
|  |  | 
|  | /* If we remove current block, we don't have to write it's updated content. */ | 
|  | if (list_block) { | 
|  | block_dirty = false; | 
|  | } | 
|  |  | 
|  | ret = ext2_free_block(fs, block_num); | 
|  | if (ret < 0) { | 
|  | removed = ret; | 
|  | } | 
|  | } | 
|  | out: | 
|  | if (removed >= 0 && list_block && block_dirty) { | 
|  | ret = ext2_write_block(fs, list_block); | 
|  | if (ret < 0) { | 
|  | removed = ret; | 
|  | } | 
|  | } | 
|  | ext2_drop_block(list_block); | 
|  |  | 
|  | /* On error removed will contain negative error code */ | 
|  | return removed; | 
|  | } | 
|  |  | 
|  | static int get_level_offsets(struct ext2_data *fs, uint32_t block, uint32_t offsets[4]) | 
|  | { | 
|  | const uint32_t B = fs->block_size / EXT2_BLOCK_NUM_SIZE; | 
|  | const uint32_t lvl0_blks = EXT2_INODE_BLOCK_1LVL; | 
|  | const uint32_t lvl1_blks = B; | 
|  | const uint32_t lvl2_blks = B * B; | 
|  | const uint32_t lvl3_blks = B * B * B; | 
|  |  | 
|  | /* Level 0 */ | 
|  | if (block < lvl0_blks) { | 
|  | offsets[0] = block; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Level 1 */ | 
|  | block -= lvl0_blks; | 
|  | if (block < lvl1_blks) { | 
|  | offsets[0] = EXT2_INODE_BLOCK_1LVL; | 
|  | offsets[1] = block; | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | /* Level 2 */ | 
|  | block -= lvl1_blks; | 
|  | if (block < lvl2_blks) { | 
|  | offsets[0] = EXT2_INODE_BLOCK_2LVL; | 
|  | offsets[1] = block / B; | 
|  | offsets[2] = block % B; | 
|  | return 2; | 
|  | } | 
|  |  | 
|  | /* Level 3 */ | 
|  | if (block < lvl3_blks) { | 
|  | block -= lvl2_blks; | 
|  | offsets[0] = EXT2_INODE_BLOCK_3LVL; | 
|  | offsets[1] = block / (B * B); | 
|  | offsets[2] = (block % (B * B)) / B; | 
|  | offsets[3] = (block % (B * B)) % B; | 
|  | return 3; | 
|  | } | 
|  | /* Block number is too large */ | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | static int block0_level(uint32_t block) | 
|  | { | 
|  | if (block >= EXT2_INODE_BLOCK_1LVL) { | 
|  | return block - EXT2_INODE_BLOCK_1LVL + 1; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int64_t ext2_inode_remove_blocks(struct ext2_inode *inode, uint32_t first) | 
|  | { | 
|  | uint32_t start; | 
|  | int max_lvl; | 
|  | int64_t ret, removed = 0; | 
|  | uint32_t offsets[4]; | 
|  | struct ext2_data *fs = inode->i_fs; | 
|  |  | 
|  | max_lvl = get_level_offsets(inode->i_fs, first, offsets); | 
|  |  | 
|  | if (all_zero(&offsets[1], max_lvl)) { | 
|  | /* The first block to remove is either: | 
|  | *  - one of the first 12 blocks in the indode | 
|  | *  - the first referenced block in the indirect block list; | 
|  | *    we remove also the indirect block | 
|  | */ | 
|  | start = offsets[0]; | 
|  | } else { | 
|  | /* There will be some blocks referenced from first affected block hence we can't | 
|  | * remove it. | 
|  | */ | 
|  | if (inode->i_block[offsets[0]] == 0) { | 
|  | LOG_ERR("Inode block that references other blocks must be nonzero"); | 
|  | fs->flags |= EXT2_DATA_FLAGS_ERR; | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | start = offsets[0] + 1; | 
|  | ret = delete_blocks(inode->i_fs, inode->i_block[offsets[0]], | 
|  | block0_level(offsets[0]), &offsets[1]); | 
|  | if (ret < 0) { | 
|  | return ret; | 
|  | } | 
|  | removed += ret; | 
|  | } | 
|  |  | 
|  | for (uint32_t i = start; i < EXT2_INODE_BLOCKS; i++) { | 
|  | if (inode->i_block[i] == 0) { | 
|  | continue; | 
|  | } | 
|  | ret = delete_blocks(inode->i_fs, inode->i_block[i], block0_level(i), | 
|  | zero_offsets); | 
|  | if (ret < 0) { | 
|  | return ret; | 
|  | } | 
|  | removed += ret; | 
|  | inode->i_block[i] = 0; | 
|  | } | 
|  | return removed; | 
|  | } | 
|  | static int alloc_level_blocks(struct ext2_inode *inode) | 
|  | { | 
|  | int ret = 0; | 
|  | uint32_t *block; | 
|  | bool allocated = false; | 
|  | struct ext2_data *fs = inode->i_fs; | 
|  |  | 
|  | for (int lvl = 0; lvl <= inode->block_lvl; ++lvl) { | 
|  | if (lvl == 0) { | 
|  | block = &inode->i_block[inode->offsets[lvl]]; | 
|  | } else { | 
|  | block = &((uint32_t *)inode->blocks[lvl - 1]->data)[inode->offsets[lvl]]; | 
|  | *block = sys_le32_to_cpu(*block); | 
|  | } | 
|  |  | 
|  | if (*block == 0) { | 
|  | ret = ext2_assign_block_num(fs, inode->blocks[lvl]); | 
|  | if (ret < 0) { | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* Update block from higher level. */ | 
|  | *block = sys_cpu_to_le32(inode->blocks[lvl]->num); | 
|  | if (lvl > 0) { | 
|  | ret = ext2_write_block(fs, inode->blocks[lvl-1]); | 
|  | if (ret < 0) { | 
|  | return ret; | 
|  | } | 
|  | } | 
|  | allocated = true; | 
|  | /* Allocating block on that level implies that blocks on lower levels will | 
|  | * be allocated too hence we can set allocated here. | 
|  | */ | 
|  | LOG_DBG("Alloc lvl:%d (num: %d) %s", lvl, *block, | 
|  | lvl == inode->block_lvl ? "data" : "indirect"); | 
|  | } | 
|  | } | 
|  | if (allocated) { | 
|  | /* Update number of reserved blocks. | 
|  | * (We are always counting 512 size blocks.) | 
|  | */ | 
|  | inode->i_blocks += fs->block_size / 512; | 
|  | ret = ext2_commit_inode(inode); | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int ext2_commit_superblock(struct ext2_data *fs) | 
|  | { | 
|  | int ret; | 
|  | struct ext2_block *b; | 
|  | uint32_t sblock_offset; | 
|  |  | 
|  | if (fs->block_size == 1024) { | 
|  | sblock_offset = 0; | 
|  | b = ext2_get_block(fs, 1); | 
|  | } else { | 
|  | sblock_offset = 1024; | 
|  | b = ext2_get_block(fs, 0); | 
|  | } | 
|  | if (b == NULL) { | 
|  | return -ENOENT; | 
|  | } | 
|  |  | 
|  | struct ext2_disk_superblock *disk_sb = | 
|  | (struct ext2_disk_superblock *)(b->data + sblock_offset); | 
|  |  | 
|  | fill_disk_sblock(disk_sb, &fs->sblock); | 
|  |  | 
|  | ret = ext2_write_block(fs, b); | 
|  | if (ret < 0) { | 
|  | return ret; | 
|  | } | 
|  | ext2_drop_block(b); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int ext2_commit_bg(struct ext2_data *fs) | 
|  | { | 
|  | int ret; | 
|  | struct ext2_bgroup *bg = &fs->bgroup; | 
|  |  | 
|  | uint32_t groups_per_block = fs->block_size / sizeof(struct ext2_disk_bgroup); | 
|  | uint32_t block = bg->num / groups_per_block; | 
|  | uint32_t offset = bg->num % groups_per_block; | 
|  | uint32_t global_block = fs->sblock.s_first_data_block + 1 + block; | 
|  |  | 
|  | struct ext2_block *b = ext2_get_block(fs, global_block); | 
|  |  | 
|  | if (b == NULL) { | 
|  | return -ENOENT; | 
|  | } | 
|  |  | 
|  | struct ext2_disk_bgroup *disk_bg = ((struct ext2_disk_bgroup *)b->data) + offset; | 
|  |  | 
|  | fill_disk_bgroup(disk_bg, bg); | 
|  |  | 
|  | ret = ext2_write_block(fs, b); | 
|  | if (ret < 0) { | 
|  | return ret; | 
|  | } | 
|  | ext2_drop_block(b); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int ext2_commit_inode(struct ext2_inode *inode) | 
|  | { | 
|  | struct ext2_data *fs = inode->i_fs; | 
|  |  | 
|  | int32_t itable_offset = get_itable_entry(fs, inode->i_id); | 
|  |  | 
|  | if (itable_offset < 0) { | 
|  | return itable_offset; | 
|  | } | 
|  |  | 
|  | /* get pointer to proper inode in fetched block */ | 
|  | struct ext2_disk_inode *dino = &BGROUP_INODE_TABLE(&fs->bgroup)[itable_offset]; | 
|  |  | 
|  | /* fill dinode */ | 
|  | fill_disk_inode(dino, inode); | 
|  |  | 
|  | return ext2_write_block(fs, fs->bgroup.inode_table); | 
|  | } | 
|  |  | 
|  | int ext2_commit_inode_block(struct ext2_inode *inode) | 
|  | { | 
|  | if (!(inode->flags & INODE_FETCHED_BLOCK)) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | int ret; | 
|  |  | 
|  | LOG_DBG("inode:%d current_blk:%d", inode->i_id, inode->block_num); | 
|  |  | 
|  | ret = alloc_level_blocks(inode); | 
|  | if (ret < 0) { | 
|  | return ret; | 
|  | } | 
|  | ret = ext2_write_block(inode->i_fs, inode_current_block(inode)); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int ext2_clear_inode(struct ext2_data *fs, uint32_t ino) | 
|  | { | 
|  | int ret; | 
|  | int32_t itable_offset = get_itable_entry(fs, ino); | 
|  |  | 
|  | if (itable_offset < 0) { | 
|  | return itable_offset; | 
|  | } | 
|  |  | 
|  | memset(&BGROUP_INODE_TABLE(&fs->bgroup)[itable_offset], 0, sizeof(struct ext2_disk_inode)); | 
|  | ret = ext2_write_block(fs, fs->bgroup.inode_table); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int64_t ext2_alloc_block(struct ext2_data *fs) | 
|  | { | 
|  | int rc, bitmap_slot; | 
|  | uint32_t group = 0, set; | 
|  | int32_t total; | 
|  |  | 
|  | rc = ext2_fetch_block_group(fs, group); | 
|  | if (rc < 0) { | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | LOG_DBG("Free blocks: %d", fs->bgroup.bg_free_blocks_count); | 
|  | while ((rc >= 0) && (fs->bgroup.bg_free_blocks_count == 0)) { | 
|  | group++; | 
|  | rc = ext2_fetch_block_group(fs, group); | 
|  | if (rc == -ERANGE) { | 
|  | /* reached last group */ | 
|  | return -ENOSPC; | 
|  | } | 
|  | } | 
|  | if (rc < 0) { | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | rc = ext2_fetch_bg_bbitmap(&fs->bgroup); | 
|  | if (rc < 0) { | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | bitmap_slot = ext2_bitmap_find_free(BGROUP_BLOCK_BITMAP(&fs->bgroup), fs->block_size); | 
|  | if (bitmap_slot < 0) { | 
|  | LOG_WRN("Cannot find free block in group %d (rc: %d)", group, bitmap_slot); | 
|  | return bitmap_slot; | 
|  | } | 
|  |  | 
|  | /* In bitmap blocks are counted from s_first_data_block hence we have to add this offset. */ | 
|  | total = group * fs->sblock.s_blocks_per_group + bitmap_slot + fs->sblock.s_first_data_block; | 
|  |  | 
|  | LOG_DBG("Found free block %d in group %d (total: %d)", bitmap_slot, group, total); | 
|  |  | 
|  | rc = ext2_bitmap_set(BGROUP_BLOCK_BITMAP(&fs->bgroup), bitmap_slot, fs->block_size); | 
|  | if (rc < 0) { | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | fs->bgroup.bg_free_blocks_count -= 1; | 
|  | fs->sblock.s_free_blocks_count -= 1; | 
|  |  | 
|  | set = ext2_bitmap_count_set(BGROUP_BLOCK_BITMAP(&fs->bgroup), fs->sblock.s_blocks_count); | 
|  |  | 
|  | if (set != (fs->sblock.s_blocks_count - fs->sblock.s_free_blocks_count)) { | 
|  | error_behavior(fs, "Wrong number of used blocks in superblock and bitmap"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | rc = ext2_commit_superblock(fs); | 
|  | if (rc < 0) { | 
|  | LOG_DBG("super block write returned: %d", rc); | 
|  | return -EIO; | 
|  | } | 
|  | rc = ext2_commit_bg(fs); | 
|  | if (rc < 0) { | 
|  | LOG_DBG("block group write returned: %d", rc); | 
|  | return -EIO; | 
|  | } | 
|  | rc = ext2_write_block(fs, fs->bgroup.block_bitmap); | 
|  | if (rc < 0) { | 
|  | LOG_DBG("block bitmap write returned: %d", rc); | 
|  | return -EIO; | 
|  | } | 
|  | return total; | 
|  | } | 
|  |  | 
|  | static int check_zero_inode(struct ext2_data *fs, uint32_t ino) | 
|  | { | 
|  | int32_t itable_offset = get_itable_entry(fs, ino); | 
|  |  | 
|  | if (itable_offset < 0) { | 
|  | return itable_offset; | 
|  | } | 
|  |  | 
|  | uint8_t *bytes = (uint8_t *)&BGROUP_INODE_TABLE(&fs->bgroup)[itable_offset]; | 
|  |  | 
|  | for (int i = 0; i < sizeof(struct ext2_disk_inode); ++i) { | 
|  | if (bytes[i] != 0) { | 
|  | return -EINVAL; | 
|  | } | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int32_t ext2_alloc_inode(struct ext2_data *fs) | 
|  | { | 
|  | int rc, r; | 
|  | uint32_t group = 0, set; | 
|  | int32_t global_idx; | 
|  |  | 
|  | rc = ext2_fetch_block_group(fs, group); | 
|  |  | 
|  | while (fs->bgroup.bg_free_inodes_count == 0 && rc >= 0) { | 
|  | group++; | 
|  | rc = ext2_fetch_block_group(fs, group); | 
|  | if (rc == -ERANGE) { | 
|  | /* reached last group */ | 
|  | return -ENOSPC; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (rc < 0) { | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | LOG_DBG("Free inodes (bg): %d", fs->bgroup.bg_free_inodes_count); | 
|  | LOG_DBG("Free inodes (sb): %d", fs->sblock.s_free_inodes_count); | 
|  |  | 
|  | rc = ext2_fetch_bg_ibitmap(&fs->bgroup); | 
|  | if (rc < 0) { | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | r = ext2_bitmap_find_free(BGROUP_INODE_BITMAP(&fs->bgroup), fs->block_size); | 
|  | if (r < 0) { | 
|  | LOG_DBG("Cannot find free inode in group %d (rc: %d)", group, r); | 
|  | return r; | 
|  | } | 
|  |  | 
|  | /* Add 1 because inodes are counted from 1 not 0. */ | 
|  | global_idx = group * fs->sblock.s_inodes_per_group + r + 1; | 
|  |  | 
|  | /* Inode table entry for found inode must be cleared. */ | 
|  | if (check_zero_inode(fs, global_idx) != 0) { | 
|  | error_behavior(fs,  "Inode is not cleared in inode table!"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | LOG_DBG("Found free inode %d in group %d (global_idx: %d)", r, group, global_idx); | 
|  |  | 
|  | rc = ext2_bitmap_set(BGROUP_INODE_BITMAP(&fs->bgroup), r, fs->block_size); | 
|  | if (rc < 0) { | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | fs->bgroup.bg_free_inodes_count -= 1; | 
|  | fs->sblock.s_free_inodes_count -= 1; | 
|  |  | 
|  | set = ext2_bitmap_count_set(BGROUP_INODE_BITMAP(&fs->bgroup), fs->sblock.s_inodes_count); | 
|  |  | 
|  | if (set != fs->sblock.s_inodes_count - fs->sblock.s_free_inodes_count) { | 
|  | error_behavior(fs, "Wrong number of used inodes in superblock and bitmap"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | rc = ext2_commit_superblock(fs); | 
|  | if (rc < 0) { | 
|  | LOG_DBG("super block write returned: %d", rc); | 
|  | return -EIO; | 
|  | } | 
|  | rc = ext2_commit_bg(fs); | 
|  | if (rc < 0) { | 
|  | LOG_DBG("block group write returned: %d", rc); | 
|  | return -EIO; | 
|  | } | 
|  | rc = ext2_write_block(fs, fs->bgroup.inode_bitmap); | 
|  | if (rc < 0) { | 
|  | LOG_DBG("block bitmap write returned: %d", rc); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | LOG_DBG("Free inodes (bg): %d", fs->bgroup.bg_free_inodes_count); | 
|  | LOG_DBG("Free inodes (sb): %d", fs->sblock.s_free_inodes_count); | 
|  |  | 
|  | return global_idx; | 
|  | } | 
|  |  | 
|  | int ext2_free_block(struct ext2_data *fs, uint32_t block) | 
|  | { | 
|  | LOG_DBG("Free block %d", block); | 
|  |  | 
|  | /* Block bitmaps tracks blocks starting from s_first_data_block. */ | 
|  | block -= fs->sblock.s_first_data_block; | 
|  |  | 
|  | int rc; | 
|  | uint32_t group = block / fs->sblock.s_blocks_per_group; | 
|  | uint32_t off = block % fs->sblock.s_blocks_per_group; | 
|  | uint32_t set; | 
|  |  | 
|  | rc = ext2_fetch_block_group(fs, group); | 
|  | if (rc < 0) { | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | rc = ext2_fetch_bg_bbitmap(&fs->bgroup); | 
|  | if (rc < 0) { | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | rc = ext2_bitmap_unset(BGROUP_BLOCK_BITMAP(&fs->bgroup), off, fs->block_size); | 
|  | if (rc < 0) { | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | fs->bgroup.bg_free_blocks_count += 1; | 
|  | fs->sblock.s_free_blocks_count += 1; | 
|  |  | 
|  | set = ext2_bitmap_count_set(BGROUP_BLOCK_BITMAP(&fs->bgroup), fs->sblock.s_blocks_count); | 
|  |  | 
|  | if (set != fs->sblock.s_blocks_count - fs->sblock.s_free_blocks_count) { | 
|  | error_behavior(fs, "Wrong number of used blocks in superblock and bitmap"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | rc = ext2_commit_superblock(fs); | 
|  | if (rc < 0) { | 
|  | LOG_DBG("super block write returned: %d", rc); | 
|  | return -EIO; | 
|  | } | 
|  | rc = ext2_commit_bg(fs); | 
|  | if (rc < 0) { | 
|  | LOG_DBG("block group write returned: %d", rc); | 
|  | return -EIO; | 
|  | } | 
|  | rc = ext2_write_block(fs, fs->bgroup.block_bitmap); | 
|  | if (rc < 0) { | 
|  | LOG_DBG("block bitmap write returned: %d", rc); | 
|  | return -EIO; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int ext2_free_inode(struct ext2_data *fs, uint32_t ino, bool directory) | 
|  | { | 
|  | LOG_DBG("Free inode %d", ino); | 
|  |  | 
|  | int rc; | 
|  | uint32_t group = (ino - 1) / fs->sblock.s_inodes_per_group; | 
|  | uint32_t bitmap_off = (ino - 1) % fs->sblock.s_inodes_per_group; | 
|  | uint32_t set; | 
|  |  | 
|  | rc = ext2_fetch_block_group(fs, group); | 
|  | if (rc < 0) { | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | rc = ext2_fetch_bg_ibitmap(&fs->bgroup); | 
|  | if (rc < 0) { | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | rc = ext2_bitmap_unset(BGROUP_INODE_BITMAP(&fs->bgroup), bitmap_off, fs->block_size); | 
|  | if (rc < 0) { | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | rc = ext2_clear_inode(fs, ino); | 
|  | if (rc < 0) { | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | fs->bgroup.bg_free_inodes_count += 1; | 
|  | fs->sblock.s_free_inodes_count += 1; | 
|  |  | 
|  | if (directory) { | 
|  | fs->bgroup.bg_used_dirs_count -= 1; | 
|  | } | 
|  |  | 
|  | set = ext2_bitmap_count_set(BGROUP_INODE_BITMAP(&fs->bgroup), fs->sblock.s_inodes_count); | 
|  |  | 
|  | if (set != fs->sblock.s_inodes_count - fs->sblock.s_free_inodes_count) { | 
|  | error_behavior(fs, "Wrong number of used inodes in superblock and bitmap"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | LOG_INF("Inode %d is free", ino); | 
|  |  | 
|  | rc = ext2_commit_superblock(fs); | 
|  | if (rc < 0) { | 
|  | LOG_DBG("super block write returned: %d", rc); | 
|  | return -EIO; | 
|  | } | 
|  | rc = ext2_commit_bg(fs); | 
|  | if (rc < 0) { | 
|  | LOG_DBG("block group write returned: %d", rc); | 
|  | return -EIO; | 
|  | } | 
|  | rc = ext2_write_block(fs, fs->bgroup.inode_bitmap); | 
|  | if (rc < 0) { | 
|  | LOG_DBG("block bitmap write returned: %d", rc); | 
|  | return -EIO; | 
|  | } | 
|  | rc = fs->backend_ops->sync(fs); | 
|  | if (rc < 0) { | 
|  | return -EIO; | 
|  | } | 
|  | return 0; | 
|  | } |