diff options
| author | ngkaho1234 <ngkaho1234@gmail.com> | 2016-01-28 22:46:36 +0800 |
|---|---|---|
| committer | ngkaho1234 <ngkaho1234@gmail.com> | 2016-01-28 22:46:36 +0800 |
| commit | a45154a49b743eba4669442e6993c50583329d99 (patch) | |
| tree | 343ba15a0cdbbfa32b87f1ed1603ae7c8bfdfd65 /src | |
| parent | d3bb06fff7af3b2162633b4ab79f7f09b3fe3fdb (diff) | |
Reconstruct source directory tree.
Diffstat (limited to 'src')
| -rw-r--r-- | src/CMakeLists.txt | 10 | ||||
| -rw-r--r-- | src/ext4.c | 2935 | ||||
| -rw-r--r-- | src/ext4_balloc.c | 654 | ||||
| -rw-r--r-- | src/ext4_bcache.c | 317 | ||||
| -rw-r--r-- | src/ext4_bitmap.c | 156 | ||||
| -rw-r--r-- | src/ext4_block_group.c | 89 | ||||
| -rw-r--r-- | src/ext4_blockdev.c | 463 | ||||
| -rw-r--r-- | src/ext4_crc32.c | 182 | ||||
| -rw-r--r-- | src/ext4_debug.c | 64 | ||||
| -rw-r--r-- | src/ext4_dir.c | 690 | ||||
| -rw-r--r-- | src/ext4_dir_idx.c | 1439 | ||||
| -rw-r--r-- | src/ext4_extent.c | 1859 | ||||
| -rw-r--r-- | src/ext4_fs.c | 1713 | ||||
| -rw-r--r-- | src/ext4_hash.c | 322 | ||||
| -rw-r--r-- | src/ext4_ialloc.c | 365 | ||||
| -rw-r--r-- | src/ext4_inode.c | 372 | ||||
| -rw-r--r-- | src/ext4_journal.c | 2158 | ||||
| -rw-r--r-- | src/ext4_mbr.c | 129 | ||||
| -rw-r--r-- | src/ext4_mkfs.c | 774 | ||||
| -rw-r--r-- | src/ext4_super.c | 267 | ||||
| -rw-r--r-- | src/ext4_trans.c | 124 | ||||
| -rw-r--r-- | src/ext4_xattr.c | 942 |
22 files changed, 16024 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..78b233d --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,10 @@ + +#LIBRARY +include_directories(.) +aux_source_directory(. LWEXT4_SRC) +add_library(lwext4 ${LWEXT4_SRC}) +if (DEFINED SIZE) + add_custom_target(lib_size ALL DEPENDS lwext4 COMMAND ${SIZE} liblwext4.a) +else() + +endif() diff --git a/src/ext4.c b/src/ext4.c new file mode 100644 index 0000000..38ec6d4 --- /dev/null +++ b/src/ext4.c @@ -0,0 +1,2935 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4.h + * @brief Ext4 high level operations (file, directory, mountpoints...) + */ + +#include "ext4_config.h" +#include "ext4.h" +#include "ext4_blockdev.h" +#include "ext4_types.h" +#include "ext4_debug.h" +#include "ext4_errno.h" +#include "ext4_fs.h" +#include "ext4_dir.h" +#include "ext4_inode.h" +#include "ext4_super.h" +#include "ext4_dir_idx.h" +#include "ext4_xattr.h" +#include "ext4_journal.h" + + +#include <stdlib.h> +#include <string.h> + +/**@brief Mount point OS dependent lock*/ +#define EXT4_MP_LOCK(_m) \ + do { \ + if ((_m)->os_locks) \ + (_m)->os_locks->lock(); \ + } while (0) + +/**@brief Mount point OS dependent unlock*/ +#define EXT4_MP_UNLOCK(_m) \ + do { \ + if ((_m)->os_locks) \ + (_m)->os_locks->unlock(); \ + } while (0) + +/**@brief Mount point descriptor.*/ +struct ext4_mountpoint { + + /**@brief Mount done flag.*/ + bool mounted; + + /**@brief Mount point name (@ref ext4_mount)*/ + char name[32]; + + /**@brief OS dependent lock/unlock functions.*/ + const struct ext4_lock *os_locks; + + /**@brief Ext4 filesystem internals.*/ + struct ext4_fs fs; + + /**@brief Dynamic allocation cache flag.*/ + bool cache_dynamic; + + struct jbd_fs jbd_fs; + struct jbd_journal jbd_journal; +}; + +/**@brief Block devices descriptor.*/ +struct _ext4_devices { + + /**@brief Block device name (@ref ext4_device_register)*/ + char name[32]; + + /**@brief Block device handle.*/ + struct ext4_blockdev *bd; + + /**@brief Block cache handle.*/ + struct ext4_bcache *bc; +}; + +/**@brief Block devices.*/ +struct _ext4_devices _bdevices[CONFIG_EXT4_BLOCKDEVS_COUNT]; + +/**@brief Mountpoints.*/ +struct ext4_mountpoint _mp[CONFIG_EXT4_MOUNTPOINTS_COUNT]; + +int ext4_device_register(struct ext4_blockdev *bd, struct ext4_bcache *bc, + const char *dev_name) +{ + uint32_t i; + ext4_assert(bd && dev_name); + + for (i = 0; i < CONFIG_EXT4_BLOCKDEVS_COUNT; ++i) { + if (!_bdevices[i].bd) { + strcpy(_bdevices[i].name, dev_name); + _bdevices[i].bd = bd; + _bdevices[i].bc = bc; + return EOK; + } + + if (!strcmp(_bdevices[i].name, dev_name)) + return EOK; + } + return ENOSPC; +} + +/****************************************************************************/ + +static bool ext4_is_dots(const uint8_t *name, size_t name_size) +{ + if ((name_size == 1) && (name[0] == '.')) + return true; + + if ((name_size == 2) && (name[0] == '.') && (name[1] == '.')) + return true; + + return false; +} + +static int ext4_has_children(bool *has_children, struct ext4_inode_ref *enode) +{ + struct ext4_sblock *sb = &enode->fs->sb; + + /* Check if node is directory */ + if (!ext4_inode_is_type(sb, enode->inode, EXT4_INODE_MODE_DIRECTORY)) { + *has_children = false; + return EOK; + } + + struct ext4_dir_iter it; + int rc = ext4_dir_iterator_init(&it, enode, 0); + if (rc != EOK) + return rc; + + /* Find a non-empty directory entry */ + bool found = false; + while (it.curr != NULL) { + if (ext4_dir_en_get_inode(it.curr) != 0) { + uint16_t nsize; + nsize = ext4_dir_en_get_name_len(sb, it.curr); + if (!ext4_is_dots(it.curr->name, nsize)) { + found = true; + break; + } + } + + rc = ext4_dir_iterator_next(&it); + if (rc != EOK) { + ext4_dir_iterator_fini(&it); + return rc; + } + } + + rc = ext4_dir_iterator_fini(&it); + if (rc != EOK) + return rc; + + *has_children = found; + + return EOK; +} + +static int ext4_link(struct ext4_mountpoint *mp, struct ext4_inode_ref *parent, + struct ext4_inode_ref *ch, const char *n, + uint32_t len, bool rename) +{ + /* Check maximum name length */ + if (len > EXT4_DIRECTORY_FILENAME_LEN) + return EINVAL; + + /* Add entry to parent directory */ + int r = ext4_dir_add_entry(parent, n, len, ch); + if (r != EOK) + return r; + + /* Fill new dir -> add '.' and '..' entries. + * Also newly allocated inode should have 0 link count. + */ + + bool is_dir = ext4_inode_is_type(&mp->fs.sb, ch->inode, + EXT4_INODE_MODE_DIRECTORY); + if (is_dir && !rename) { + +#if CONFIG_DIR_INDEX_ENABLE + /* Initialize directory index if supported */ + if (ext4_sb_feature_com(&mp->fs.sb, EXT4_FCOM_DIR_INDEX)) { + r = ext4_dir_dx_init(ch, parent); + if (r != EOK) + return r; + + ext4_inode_set_flag(ch->inode, EXT4_INODE_FLAG_INDEX); + ch->dirty = true; + } else +#endif + { + r = ext4_dir_add_entry(ch, ".", strlen("."), ch); + if (r != EOK) { + ext4_dir_remove_entry(parent, n, strlen(n)); + return r; + } + + r = ext4_dir_add_entry(ch, "..", strlen(".."), parent); + if (r != EOK) { + ext4_dir_remove_entry(parent, n, strlen(n)); + ext4_dir_remove_entry(ch, ".", strlen(".")); + return r; + } + } + + /*New empty directory. Two links (. and ..) */ + ext4_inode_set_links_cnt(ch->inode, 2); + ext4_fs_inode_links_count_inc(parent); + ch->dirty = true; + parent->dirty = true; + return r; + } + /* + * In case we want to rename a directory, + * we reset the original '..' pointer. + */ + if (is_dir) { + bool idx; + idx = ext4_inode_has_flag(ch->inode, EXT4_INODE_FLAG_INDEX); + struct ext4_dir_search_result res; + if (!idx) { + r = ext4_dir_find_entry(&res, ch, "..", strlen("..")); + if (r != EOK) + return EIO; + + ext4_dir_en_set_inode(res.dentry, parent->index); + ext4_trans_set_block_dirty(res.block.buf); + r = ext4_dir_destroy_result(ch, &res); + if (r != EOK) + return r; + + } else { +#if CONFIG_DIR_INDEX_ENABLE + r = ext4_dir_dx_reset_parent_inode(ch, parent->index); + if (r != EOK) + return r; + +#endif + } + + ext4_fs_inode_links_count_inc(parent); + parent->dirty = true; + } + if (!rename) { + ext4_fs_inode_links_count_inc(ch); + ch->dirty = true; + } + + return r; +} + +static int ext4_unlink(struct ext4_mountpoint *mp, + struct ext4_inode_ref *parent, + struct ext4_inode_ref *child, const char *name, + uint32_t name_len) +{ + bool has_children; + int rc = ext4_has_children(&has_children, child); + if (rc != EOK) + return rc; + + /* Cannot unlink non-empty node */ + if (has_children) + return ENOTEMPTY; + + /* Remove entry from parent directory */ + rc = ext4_dir_remove_entry(parent, name, name_len); + if (rc != EOK) + return rc; + + bool is_dir = ext4_inode_is_type(&mp->fs.sb, child->inode, + EXT4_INODE_MODE_DIRECTORY); + + /* If directory - handle links from parent */ + if (is_dir) { + ext4_fs_inode_links_count_dec(parent); + parent->dirty = true; + } + + /* + * TODO: Update timestamps of the parent + * (when we have wall-clock time). + * + * ext4_inode_set_change_inode_time(parent->inode, (uint32_t) now); + * ext4_inode_set_modification_time(parent->inode, (uint32_t) now); + * parent->dirty = true; + */ + + /* + * TODO: Update timestamp for inode. + * + * ext4_inode_set_change_inode_time(child->inode, + * (uint32_t) now); + */ + if (ext4_inode_get_links_cnt(child->inode)) { + ext4_fs_inode_links_count_dec(child); + child->dirty = true; + } + + return EOK; +} + +/****************************************************************************/ + +int ext4_mount(const char *dev_name, const char *mount_point) +{ + ext4_assert(mount_point && dev_name); + int r; + int i; + + uint32_t bsize; + struct ext4_blockdev *bd = 0; + struct ext4_bcache *bc = 0; + struct ext4_mountpoint *mp = 0; + + if (mount_point[strlen(mount_point) - 1] != '/') + return ENOTSUP; + + for (i = 0; i < CONFIG_EXT4_BLOCKDEVS_COUNT; ++i) { + if (_bdevices[i].name) { + if (!strcmp(dev_name, _bdevices[i].name)) { + bd = _bdevices[i].bd; + bc = _bdevices[i].bc; + break; + } + } + } + + if (!bd) + return ENODEV; + + for (i = 0; i < CONFIG_EXT4_MOUNTPOINTS_COUNT; ++i) { + if (!_mp[i].mounted) { + strcpy(_mp[i].name, mount_point); + _mp[i].mounted = 1; + mp = &_mp[i]; + break; + } + + if (!strcmp(_mp[i].name, mount_point)) + return EOK; + } + + if (!mp) + return ENOMEM; + + r = ext4_block_init(bd); + if (r != EOK) + return r; + + r = ext4_fs_init(&mp->fs, bd); + if (r != EOK) { + ext4_block_fini(bd); + return r; + } + + bsize = ext4_sb_get_block_size(&mp->fs.sb); + ext4_block_set_lb_size(bd, bsize); + + mp->cache_dynamic = 0; + + if (!bc) { + /*Automatic block cache alloc.*/ + mp->cache_dynamic = 1; + bc = malloc(sizeof(struct ext4_bcache)); + + r = ext4_bcache_init_dynamic(bc, CONFIG_BLOCK_DEV_CACHE_SIZE, + bsize); + if (r != EOK) { + free(bc); + ext4_block_fini(bd); + return r; + } + } + + if (bsize != bc->itemsize) + return ENOTSUP; + + /*Bind block cache to block device*/ + r = ext4_block_bind_bcache(bd, bc); + if (r != EOK) { + ext4_bcache_cleanup(bc); + ext4_block_fini(bd); + if (mp->cache_dynamic) { + ext4_bcache_fini_dynamic(bc); + free(bc); + } + return r; + } + bd->fs = &mp->fs; + + return r; +} + + +int ext4_umount(const char *mount_point) +{ + int i; + int r; + struct ext4_mountpoint *mp = 0; + + for (i = 0; i < CONFIG_EXT4_MOUNTPOINTS_COUNT; ++i) { + if (!strcmp(_mp[i].name, mount_point)) { + mp = &_mp[i]; + break; + } + } + + if (!mp) + return ENODEV; + + r = ext4_fs_fini(&mp->fs); + if (r != EOK) + goto Finish; + + mp->mounted = 0; + + ext4_bcache_cleanup(mp->fs.bdev->bc); + if (mp->cache_dynamic) { + ext4_bcache_fini_dynamic(mp->fs.bdev->bc); + free(mp->fs.bdev->bc); + } + r = ext4_block_fini(mp->fs.bdev); +Finish: + mp->fs.bdev->fs = NULL; + return r; +} + +static struct ext4_mountpoint *ext4_get_mount(const char *path) +{ + int i; + for (i = 0; i < CONFIG_EXT4_MOUNTPOINTS_COUNT; ++i) { + + if (!_mp[i].mounted) + continue; + + if (!strncmp(_mp[i].name, path, strlen(_mp[i].name))) + return &_mp[i]; + } + return NULL; +} + +__unused +static int __ext4_journal_start(const char *mount_point) +{ + int r = EOK; + struct ext4_mountpoint *mp = ext4_get_mount(mount_point); + if (!mp) + return ENOENT; + + if (ext4_sb_feature_com(&mp->fs.sb, + EXT4_FCOM_HAS_JOURNAL)) { + r = jbd_get_fs(&mp->fs, &mp->jbd_fs); + if (r != EOK) + goto Finish; + + r = jbd_journal_start(&mp->jbd_fs, &mp->jbd_journal); + if (r != EOK) { + mp->jbd_fs.dirty = false; + jbd_put_fs(&mp->jbd_fs); + goto Finish; + } + mp->fs.jbd_fs = &mp->jbd_fs; + mp->fs.jbd_journal = &mp->jbd_journal; + } +Finish: + return r; +} + +__unused +static int __ext4_journal_stop(const char *mount_point) +{ + int r = EOK; + struct ext4_mountpoint *mp = ext4_get_mount(mount_point); + if (!mp) + return ENOENT; + + if (ext4_sb_feature_com(&mp->fs.sb, + EXT4_FCOM_HAS_JOURNAL)) { + r = jbd_journal_stop(&mp->jbd_journal); + if (r != EOK) { + mp->jbd_fs.dirty = false; + jbd_put_fs(&mp->jbd_fs); + mp->fs.jbd_journal = NULL; + mp->fs.jbd_fs = NULL; + goto Finish; + } + + r = jbd_put_fs(&mp->jbd_fs); + if (r != EOK) { + mp->fs.jbd_journal = NULL; + mp->fs.jbd_fs = NULL; + goto Finish; + } + + mp->fs.jbd_journal = NULL; + mp->fs.jbd_fs = NULL; + } +Finish: + return r; +} + +__unused +static int __ext4_recover(const char *mount_point) +{ + struct ext4_mountpoint *mp = ext4_get_mount(mount_point); + if (!mp) + return ENOENT; + + int r = ENOTSUP; + EXT4_MP_LOCK(mp); + if (ext4_sb_feature_com(&mp->fs.sb, EXT4_FCOM_HAS_JOURNAL)) { + struct jbd_fs *jbd_fs = calloc(1, sizeof(struct jbd_fs)); + if (!jbd_fs) { + r = ENOMEM; + goto Finish; + } + + + r = jbd_get_fs(&mp->fs, jbd_fs); + if (r != EOK) { + free(jbd_fs); + goto Finish; + } + + r = jbd_recover(jbd_fs); + jbd_put_fs(jbd_fs); + free(jbd_fs); + } + + +Finish: + EXT4_MP_UNLOCK(mp); + return r; +} + +__unused +static int __ext4_trans_start(struct ext4_mountpoint *mp) +{ + int r = EOK; + if (mp->fs.jbd_journal && !mp->fs.curr_trans) { + struct jbd_journal *journal = mp->fs.jbd_journal; + struct jbd_trans *trans; + trans = jbd_journal_new_trans(journal); + if (!trans) { + r = ENOMEM; + goto Finish; + } + mp->fs.curr_trans = trans; + } +Finish: + return r; +} + +__unused +static int __ext4_trans_stop(struct ext4_mountpoint *mp) +{ + int r = EOK; + if (mp->fs.jbd_journal && mp->fs.curr_trans) { + struct jbd_journal *journal = mp->fs.jbd_journal; + struct jbd_trans *trans = mp->fs.curr_trans; + r = jbd_journal_commit_trans(journal, trans); + mp->fs.curr_trans = NULL; + } + return r; +} + +__unused +static void __ext4_trans_abort(struct ext4_mountpoint *mp) +{ + if (mp->fs.jbd_journal && mp->fs.curr_trans) { + struct jbd_journal *journal = mp->fs.jbd_journal; + struct jbd_trans *trans = mp->fs.curr_trans; + jbd_journal_free_trans(journal, trans, true); + mp->fs.curr_trans = NULL; + } +} + +int ext4_journal_start(const char *mount_point __unused) +{ + int r = EOK; +#if CONFIG_JOURNALING_ENABLE + r = __ext4_journal_start(mount_point); +#endif + return r; +} + +int ext4_journal_stop(const char *mount_point __unused) +{ + int r = EOK; +#if CONFIG_JOURNALING_ENABLE + r = __ext4_journal_stop(mount_point); +#endif + return r; +} + +int ext4_recover(const char *mount_point __unused) +{ + int r = EOK; +#if CONFIG_JOURNALING_ENABLE + r = __ext4_recover(mount_point); +#endif + return r; +} + +static int ext4_trans_start(struct ext4_mountpoint *mp __unused) +{ + int r = EOK; +#if CONFIG_JOURNALING_ENABLE + r = __ext4_trans_start(mp); +#endif + return r; +} + +static int ext4_trans_stop(struct ext4_mountpoint *mp __unused) +{ + int r = EOK; +#if CONFIG_JOURNALING_ENABLE + r = __ext4_trans_stop(mp); +#endif + return r; +} + +static void ext4_trans_abort(struct ext4_mountpoint *mp __unused) +{ +#if CONFIG_JOURNALING_ENABLE + __ext4_trans_abort(mp); +#endif +} + + +int ext4_mount_point_stats(const char *mount_point, + struct ext4_mount_stats *stats) +{ + struct ext4_mountpoint *mp = ext4_get_mount(mount_point); + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + stats->inodes_count = ext4_get32(&mp->fs.sb, inodes_count); + stats->free_inodes_count = ext4_get32(&mp->fs.sb, free_inodes_count); + stats->blocks_count = ext4_sb_get_blocks_cnt(&mp->fs.sb); + stats->free_blocks_count = ext4_sb_get_free_blocks_cnt(&mp->fs.sb); + stats->block_size = ext4_sb_get_block_size(&mp->fs.sb); + + stats->block_group_count = ext4_block_group_cnt(&mp->fs.sb); + stats->blocks_per_group = ext4_get32(&mp->fs.sb, blocks_per_group); + stats->inodes_per_group = ext4_get32(&mp->fs.sb, inodes_per_group); + + memcpy(stats->volume_name, mp->fs.sb.volume_name, 16); + EXT4_MP_UNLOCK(mp); + + return EOK; +} + +int ext4_mount_setup_locks(const char *mount_point, + const struct ext4_lock *locks) +{ + uint32_t i; + struct ext4_mountpoint *mp = 0; + + for (i = 0; i < CONFIG_EXT4_MOUNTPOINTS_COUNT; ++i) { + if (!strcmp(_mp[i].name, mount_point)) { + mp = &_mp[i]; + break; + } + } + if (!mp) + return ENOENT; + + mp->os_locks = locks; + return EOK; +} + +/********************************FILE OPERATIONS*****************************/ + +static int ext4_path_check(const char *path, bool *is_goal) +{ + int i; + + for (i = 0; i < EXT4_DIRECTORY_FILENAME_LEN; ++i) { + + if (path[i] == '/') { + *is_goal = false; + return i; + } + + if (path[i] == 0) { + *is_goal = true; + return i; + } + } + + return 0; +} + +static bool ext4_parse_flags(const char *flags, uint32_t *file_flags) +{ + if (!flags) + return false; + + if (!strcmp(flags, "r") || !strcmp(flags, "rb")) { + *file_flags = O_RDONLY; + return true; + } + + if (!strcmp(flags, "w") || !strcmp(flags, "wb")) { + *file_flags = O_WRONLY | O_CREAT | O_TRUNC; + return true; + } + + if (!strcmp(flags, "a") || !strcmp(flags, "ab")) { + *file_flags = O_WRONLY | O_CREAT | O_APPEND; + return true; + } + + if (!strcmp(flags, "r+") || !strcmp(flags, "rb+") || + !strcmp(flags, "r+b")) { + *file_flags = O_RDWR; + return true; + } + + if (!strcmp(flags, "w+") || !strcmp(flags, "wb+") || + !strcmp(flags, "w+b")) { + *file_flags = O_RDWR | O_CREAT | O_TRUNC; + return true; + } + + if (!strcmp(flags, "a+") || !strcmp(flags, "ab+") || + !strcmp(flags, "a+b")) { + *file_flags = O_RDWR | O_CREAT | O_APPEND; + return true; + } + + return false; +} + +static int ext4_trunc_inode(struct ext4_mountpoint *mp, + uint32_t index, uint64_t new_size) +{ + int r = EOK; + struct ext4_fs *const fs = &mp->fs; + struct ext4_inode_ref inode_ref; + uint64_t inode_size; + bool has_trans = mp->fs.jbd_journal && mp->fs.curr_trans; + r = ext4_fs_get_inode_ref(fs, index, &inode_ref); + if (r != EOK) + return r; + + inode_size = ext4_inode_get_size(&fs->sb, inode_ref.inode); + ext4_fs_put_inode_ref(&inode_ref); + if (has_trans) + ext4_trans_stop(mp); + + while (inode_size > new_size + CONFIG_MAX_TRUNCATE_SIZE) { + + inode_size -= CONFIG_MAX_TRUNCATE_SIZE; + + ext4_trans_start(mp); + r = ext4_fs_get_inode_ref(fs, index, &inode_ref); + if (r != EOK) { + ext4_trans_abort(mp); + break; + } + r = ext4_fs_truncate_inode(&inode_ref, inode_size); + if (r != EOK) + ext4_fs_put_inode_ref(&inode_ref); + else + r = ext4_fs_put_inode_ref(&inode_ref); + + if (r != EOK) { + ext4_trans_abort(mp); + goto Finish; + } else + ext4_trans_stop(mp); + } + + if (inode_size > new_size) { + + inode_size = new_size; + + ext4_trans_start(mp); + r = ext4_fs_get_inode_ref(fs, index, &inode_ref); + if (r != EOK) { + ext4_trans_abort(mp); + goto Finish; + } + r = ext4_fs_truncate_inode(&inode_ref, inode_size); + if (r != EOK) + ext4_fs_put_inode_ref(&inode_ref); + else + r = ext4_fs_put_inode_ref(&inode_ref); + + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + } + +Finish: + + if (has_trans) + ext4_trans_start(mp); + + return r; +} + +static int ext4_trunc_dir(struct ext4_mountpoint *mp, + struct ext4_inode_ref *parent, + struct ext4_inode_ref *dir) +{ + int r = EOK; + bool is_dir = ext4_inode_is_type(&mp->fs.sb, dir->inode, + EXT4_INODE_MODE_DIRECTORY); + uint32_t block_size = ext4_sb_get_block_size(&mp->fs.sb); + if (!is_dir) + return EINVAL; + +#if CONFIG_DIR_INDEX_ENABLE + /* Initialize directory index if supported */ + if (ext4_sb_feature_com(&mp->fs.sb, EXT4_FCOM_DIR_INDEX)) { + r = ext4_dir_dx_init(dir, parent); + if (r != EOK) + return r; + + r = ext4_trunc_inode(mp, dir->index, + EXT4_DIR_DX_INIT_BCNT * block_size); + if (r != EOK) + return r; + } else +#endif + { + r = ext4_trunc_inode(mp, dir->index, block_size); + if (r != EOK) + return r; + } + + return ext4_fs_truncate_inode(dir, 0); +} + +/* + * NOTICE: if filetype is equal to EXT4_DIRENTRY_UNKNOWN, + * any filetype of the target dir entry will be accepted. + */ +static int ext4_generic_open2(ext4_file *f, const char *path, int flags, + int ftype, uint32_t *parent_inode, + uint32_t *name_off) +{ + bool is_goal = false; + uint32_t imode = EXT4_INODE_MODE_DIRECTORY; + uint32_t next_inode; + + int r; + int len; + struct ext4_mountpoint *mp = ext4_get_mount(path); + struct ext4_dir_search_result result; + struct ext4_inode_ref ref; + + f->mp = 0; + + if (!mp) + return ENOENT; + + struct ext4_fs *const fs = &mp->fs; + struct ext4_sblock *const sb = &mp->fs.sb; + + f->flags = flags; + + /*Skip mount point*/ + path += strlen(mp->name); + + if (name_off) + *name_off = strlen(mp->name); + + /*Load root*/ + r = ext4_fs_get_inode_ref(fs, EXT4_INODE_ROOT_INDEX, &ref); + if (r != EOK) + return r; + + if (parent_inode) + *parent_inode = ref.index; + + if (flags & O_CREAT) + ext4_trans_start(mp); + + len = ext4_path_check(path, &is_goal); + while (1) { + + len = ext4_path_check(path, &is_goal); + if (!len) { + /*If root open was request.*/ + if (ftype == EXT4_DE_DIR || ftype == EXT4_DE_UNKNOWN) + if (is_goal) + break; + + r = ENOENT; + break; + } + + r = ext4_dir_find_entry(&result, &ref, path, len); + if (r != EOK) { + + /*Destroy last result*/ + ext4_dir_destroy_result(&ref, &result); + if (r != ENOENT) + break; + + if (!(f->flags & O_CREAT)) + break; + + /*O_CREAT allows create new entry*/ + struct ext4_inode_ref child_ref; + r = ext4_fs_alloc_inode(fs, &child_ref, + is_goal ? ftype : EXT4_DE_DIR); + if (r != EOK) + break; + + + /*Link with root dir.*/ + r = ext4_link(mp, &ref, &child_ref, path, len, false); + if (r != EOK) { + /*Fail. Free new inode.*/ + ext4_fs_free_inode(&child_ref); + /*We do not want to write new inode. + But block has to be released.*/ + child_ref.dirty = false; + ext4_fs_put_inode_ref(&child_ref); + break; + } + + ext4_fs_put_inode_ref(&child_ref); + continue; + } + + if (parent_inode) + *parent_inode = ref.index; + + next_inode = ext4_dir_en_get_inode(result.dentry); + if (ext4_sb_feature_incom(sb, EXT4_FINCOM_FILETYPE)) { + uint8_t t; + t = ext4_dir_en_get_inode_type(sb, result.dentry); + imode = ext4_fs_correspond_inode_mode(t); + } else { + struct ext4_inode_ref child_ref; + r = ext4_fs_get_inode_ref(fs, next_inode, &child_ref); + if (r != EOK) + break; + + imode = ext4_inode_type(sb, child_ref.inode); + ext4_fs_put_inode_ref(&child_ref); + } + + r = ext4_dir_destroy_result(&ref, &result); + if (r != EOK) + break; + + /*If expected file error*/ + if (imode != EXT4_INODE_MODE_DIRECTORY && !is_goal) { + r = ENOENT; + break; + } + if (ftype != EXT4_DE_UNKNOWN) { + bool df = imode != ext4_fs_correspond_inode_mode(ftype); + if (df && is_goal) { + r = ENOENT; + break; + } + } + + r = ext4_fs_put_inode_ref(&ref); + if (r != EOK) + break; + + r = ext4_fs_get_inode_ref(fs, next_inode, &ref); + if (r != EOK) + break; + + if (is_goal) + break; + + path += len + 1; + + if (name_off) + *name_off += len + 1; + }; + + if (r != EOK) { + ext4_fs_put_inode_ref(&ref); + return r; + } + + if (is_goal) { + + if ((f->flags & O_TRUNC) && (imode == EXT4_INODE_MODE_FILE)) { + r = ext4_trunc_inode(mp, ref.index, 0); + if (r != EOK) { + ext4_fs_put_inode_ref(&ref); + return r; + } + } + + f->mp = mp; + f->fsize = ext4_inode_get_size(sb, ref.inode); + f->inode = ref.index; + f->fpos = 0; + + if (f->flags & O_APPEND) + f->fpos = f->fsize; + + } + + r = ext4_fs_put_inode_ref(&ref); + if (flags & O_CREAT) { + if (r == EOK) + ext4_trans_stop(mp); + else + ext4_trans_abort(mp); + + } + + return r; +} + +/****************************************************************************/ + +static int ext4_generic_open(ext4_file *f, const char *path, const char *flags, + bool file_expect, uint32_t *parent_inode, + uint32_t *name_off) +{ + uint32_t iflags; + int filetype; + if (ext4_parse_flags(flags, &iflags) == false) + return EINVAL; + + if (file_expect == true) + filetype = EXT4_DE_REG_FILE; + else + filetype = EXT4_DE_DIR; + + return ext4_generic_open2(f, path, iflags, filetype, parent_inode, + name_off); +} + +static int ext4_create_hardlink(const char *path, + struct ext4_inode_ref *child_ref, bool rename) +{ + bool is_goal = false; + uint32_t inode_mode = EXT4_INODE_MODE_DIRECTORY; + uint32_t next_inode; + + int r; + int len; + struct ext4_mountpoint *mp = ext4_get_mount(path); + struct ext4_dir_search_result result; + struct ext4_inode_ref ref; + + if (!mp) + return ENOENT; + + struct ext4_fs *const fs = &mp->fs; + struct ext4_sblock *const sb = &mp->fs.sb; + + /*Skip mount point*/ + path += strlen(mp->name); + + /*Load root*/ + r = ext4_fs_get_inode_ref(fs, EXT4_INODE_ROOT_INDEX, &ref); + if (r != EOK) + return r; + + len = ext4_path_check(path, &is_goal); + while (1) { + + len = ext4_path_check(path, &is_goal); + if (!len) { + /*If root open was request.*/ + r = is_goal ? EINVAL : ENOENT; + break; + } + + r = ext4_dir_find_entry(&result, &ref, path, len); + if (r != EOK) { + + /*Destroy last result*/ + ext4_dir_destroy_result(&ref, &result); + + if (r != ENOENT || !is_goal) + break; + + /*Link with root dir.*/ + r = ext4_link(mp, &ref, child_ref, path, len, rename); + break; + } else if (r == EOK && is_goal) { + /*Destroy last result*/ + ext4_dir_destroy_result(&ref, &result); + r = EEXIST; + break; + } + + next_inode = result.dentry->inode; + if (ext4_sb_feature_incom(sb, EXT4_FINCOM_FILETYPE)) { + uint8_t t; + t = ext4_dir_en_get_inode_type(sb, result.dentry); + inode_mode = ext4_fs_correspond_inode_mode(t); + } else { + struct ext4_inode_ref child_ref; + r = ext4_fs_get_inode_ref(fs, next_inode, &child_ref); + if (r != EOK) + break; + + inode_mode = ext4_inode_type(sb, child_ref.inode); + ext4_fs_put_inode_ref(&child_ref); + } + + r = ext4_dir_destroy_result(&ref, &result); + if (r != EOK) + break; + + if (inode_mode != EXT4_INODE_MODE_DIRECTORY) { + r = is_goal ? EEXIST : ENOENT; + break; + } + + r = ext4_fs_put_inode_ref(&ref); + if (r != EOK) + break; + + r = ext4_fs_get_inode_ref(fs, next_inode, &ref); + if (r != EOK) + break; + + if (is_goal) + break; + + path += len + 1; + }; + + if (r != EOK) { + ext4_fs_put_inode_ref(&ref); + return r; + } + + r = ext4_fs_put_inode_ref(&ref); + return r; +} + +static int ext4_remove_orig_reference(const char *path, uint32_t name_off, + struct ext4_inode_ref *parent_ref, + struct ext4_inode_ref *child_ref) +{ + bool is_goal; + int r; + int len; + struct ext4_mountpoint *mp = ext4_get_mount(path); + + if (!mp) + return ENOENT; + + /*Set path*/ + path += name_off; + + len = ext4_path_check(path, &is_goal); + + /* Remove entry from parent directory */ + r = ext4_dir_remove_entry(parent_ref, path, len); + if (r != EOK) + goto Finish; + + if (ext4_inode_is_type(&mp->fs.sb, child_ref->inode, + EXT4_INODE_MODE_DIRECTORY)) { + ext4_fs_inode_links_count_dec(parent_ref); + parent_ref->dirty = true; + } +Finish: + return r; +} + +int ext4_flink(const char *path, const char *hardlink_path) +{ + int r; + ext4_file f; + uint32_t name_off; + bool child_loaded = false; + uint32_t parent_inode, child_inode; + struct ext4_mountpoint *mp = ext4_get_mount(path); + struct ext4_mountpoint *target_mp = ext4_get_mount(hardlink_path); + struct ext4_inode_ref child_ref; + + if (!mp) + return ENOENT; + + /* Will that happen? Anyway return EINVAL for such case. */ + if (mp != target_mp) + return EINVAL; + + EXT4_MP_LOCK(mp); + ext4_trans_start(mp); + + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, + &parent_inode, &name_off); + if (r != EOK) + goto Finish; + + child_inode = f.inode; + ext4_fclose(&f); + + /*We have file to unlink. Load it.*/ + r = ext4_fs_get_inode_ref(&mp->fs, child_inode, &child_ref); + if (r != EOK) + goto Finish; + + child_loaded = true; + + /* Creating hardlink for directory is not allowed. */ + if (ext4_inode_is_type(&mp->fs.sb, child_ref.inode, + EXT4_INODE_MODE_DIRECTORY)) { + r = EINVAL; + goto Finish; + } + + r = ext4_create_hardlink(hardlink_path, &child_ref, false); + +Finish: + if (child_loaded) + ext4_fs_put_inode_ref(&child_ref); + + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + EXT4_MP_UNLOCK(mp); + return r; + +} + +int ext4_frename(const char *path, const char *new_path) +{ + int r; + ext4_file f; + uint32_t name_off; + bool parent_loaded = false, child_loaded = false; + uint32_t parent_inode, child_inode; + struct ext4_mountpoint *mp = ext4_get_mount(path); + struct ext4_inode_ref child_ref, parent_ref; + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + ext4_trans_start(mp); + + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, + &parent_inode, &name_off); + if (r != EOK) + goto Finish; + + child_inode = f.inode; + ext4_fclose(&f); + + /*Load parent*/ + r = ext4_fs_get_inode_ref(&mp->fs, parent_inode, &parent_ref); + if (r != EOK) + goto Finish; + + parent_loaded = true; + + /*We have file to unlink. Load it.*/ + r = ext4_fs_get_inode_ref(&mp->fs, child_inode, &child_ref); + if (r != EOK) + goto Finish; + + child_loaded = true; + + r = ext4_create_hardlink(new_path, &child_ref, true); + if (r != EOK) + goto Finish; + + r = ext4_remove_orig_reference(path, name_off, &parent_ref, &child_ref); + if (r != EOK) + goto Finish; + +Finish: + if (parent_loaded) + ext4_fs_put_inode_ref(&parent_ref); + + if (child_loaded) + ext4_fs_put_inode_ref(&child_ref); + + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + EXT4_MP_UNLOCK(mp); + return r; + +} + +/****************************************************************************/ + +int ext4_get_sblock(const char *mount_point, struct ext4_sblock **sb) +{ + struct ext4_mountpoint *mp = ext4_get_mount(mount_point); + + if (!mp) + return ENOENT; + + *sb = &mp->fs.sb; + return EOK; +} + +int ext4_cache_write_back(const char *path, bool on) +{ + struct ext4_mountpoint *mp = ext4_get_mount(path); + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + ext4_block_cache_write_back(mp->fs.bdev, on); + EXT4_MP_UNLOCK(mp); + return EOK; +} + +int ext4_fremove(const char *path) +{ + ext4_file f; + uint32_t parent_inode; + uint32_t name_off; + bool is_goal; + int r; + int len; + struct ext4_inode_ref child; + struct ext4_inode_ref parent; + struct ext4_mountpoint *mp = ext4_get_mount(path); + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + ext4_trans_start(mp); + + r = ext4_generic_open2(&f, path, O_RDWR, EXT4_DE_UNKNOWN, + &parent_inode, &name_off); + if (r != EOK) { + ext4_trans_abort(mp); + EXT4_MP_UNLOCK(mp); + return r; + } + + /*Load parent*/ + r = ext4_fs_get_inode_ref(&mp->fs, parent_inode, &parent); + if (r != EOK) { + ext4_trans_abort(mp); + EXT4_MP_UNLOCK(mp); + return r; + } + + /*We have file to delete. Load it.*/ + r = ext4_fs_get_inode_ref(&mp->fs, f.inode, &child); + if (r != EOK) { + ext4_fs_put_inode_ref(&parent); + ext4_trans_abort(mp); + EXT4_MP_UNLOCK(mp); + return r; + } + /* We do not allow opening files here. */ + if (ext4_inode_type(&mp->fs.sb, child.inode) == + EXT4_INODE_MODE_DIRECTORY) { + ext4_fs_put_inode_ref(&parent); + ext4_fs_put_inode_ref(&child); + ext4_trans_abort(mp); + EXT4_MP_UNLOCK(mp); + return r; + } + + /*Link count will be zero, the inode should be freed. */ + if (ext4_inode_get_links_cnt(child.inode) == 1) { + ext4_block_cache_write_back(mp->fs.bdev, 1); + r = ext4_trunc_inode(mp, child.index, 0); + if (r != EOK) { + ext4_fs_put_inode_ref(&parent); + ext4_fs_put_inode_ref(&child); + ext4_trans_abort(mp); + EXT4_MP_UNLOCK(mp); + return r; + } + ext4_block_cache_write_back(mp->fs.bdev, 0); + } + + /*Set path*/ + path += name_off; + + len = ext4_path_check(path, &is_goal); + + /*Unlink from parent*/ + r = ext4_unlink(mp, &parent, &child, path, len); + if (r != EOK) + goto Finish; + + /*Link count is zero, the inode should be freed. */ + if (!ext4_inode_get_links_cnt(child.inode)) { + ext4_inode_set_del_time(child.inode, -1L); + + r = ext4_fs_free_inode(&child); + if (r != EOK) + goto Finish; + } + +Finish: + ext4_fs_put_inode_ref(&child); + ext4_fs_put_inode_ref(&parent); + + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_fill_raw_inode(const char *path, uint32_t *ret_ino, + struct ext4_inode *inode) +{ + int r; + ext4_file f; + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + uint32_t ino; + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + + r = ext4_generic_open2(&f, path, O_RDONLY, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) { + EXT4_MP_UNLOCK(mp); + return r; + } + + ino = f.inode; + ext4_fclose(&f); + + /*Load parent*/ + r = ext4_fs_get_inode_ref(&mp->fs, ino, &inode_ref); + if (r != EOK) { + EXT4_MP_UNLOCK(mp); + return r; + } + + memcpy(inode, inode_ref.inode, sizeof(struct ext4_inode)); + ext4_fs_put_inode_ref(&inode_ref); + EXT4_MP_UNLOCK(mp); + + if (ret_ino) + *ret_ino = ino; + + return r; +} + +int ext4_fopen(ext4_file *f, const char *path, const char *flags) +{ + struct ext4_mountpoint *mp = ext4_get_mount(path); + int r; + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + + ext4_block_cache_write_back(mp->fs.bdev, 1); + r = ext4_generic_open(f, path, flags, true, 0, 0); + ext4_block_cache_write_back(mp->fs.bdev, 0); + + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_fopen2(ext4_file *f, const char *path, int flags) +{ + struct ext4_mountpoint *mp = ext4_get_mount(path); + int r; + int filetype; + + if (!mp) + return ENOENT; + + filetype = EXT4_DE_REG_FILE; + + EXT4_MP_LOCK(mp); + + ext4_block_cache_write_back(mp->fs.bdev, 1); + r = ext4_generic_open2(f, path, flags, filetype, NULL, NULL); + ext4_block_cache_write_back(mp->fs.bdev, 0); + + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_fclose(ext4_file *f) +{ + ext4_assert(f && f->mp); + + f->mp = 0; + f->flags = 0; + f->inode = 0; + f->fpos = f->fsize = 0; + + return EOK; +} + +static int ext4_ftruncate_no_lock(ext4_file *f, uint64_t size) +{ + struct ext4_inode_ref ref; + int r; + + + r = ext4_fs_get_inode_ref(&f->mp->fs, f->inode, &ref); + if (r != EOK) { + EXT4_MP_UNLOCK(f->mp); + return r; + } + + /*Sync file size*/ + f->fsize = ext4_inode_get_size(&f->mp->fs.sb, ref.inode); + if (f->fsize <= size) { + r = EOK; + goto Finish; + } + + /*Start write back cache mode.*/ + r = ext4_block_cache_write_back(f->mp->fs.bdev, 1); + if (r != EOK) + goto Finish; + + r = ext4_trunc_inode(f->mp, ref.index, size); + if (r != EOK) + goto Finish; + + f->fsize = size; + if (f->fpos > size) + f->fpos = size; + + /*Stop write back cache mode*/ + ext4_block_cache_write_back(f->mp->fs.bdev, 0); + + if (r != EOK) + goto Finish; + +Finish: + ext4_fs_put_inode_ref(&ref); + return r; + +} + +int ext4_ftruncate(ext4_file *f, uint64_t size) +{ + int r; + ext4_assert(f && f->mp); + + if (f->flags & O_RDONLY) + return EPERM; + + EXT4_MP_LOCK(f->mp); + + ext4_trans_start(f->mp); + r = ext4_ftruncate_no_lock(f, size); + if (r != EOK) + ext4_trans_abort(f->mp); + else + ext4_trans_stop(f->mp); + + EXT4_MP_UNLOCK(f->mp); + return r; +} + +int ext4_fread(ext4_file *f, void *buf, size_t size, size_t *rcnt) +{ + uint32_t unalg; + uint32_t iblock_idx; + uint32_t iblock_last; + uint32_t block_size; + + ext4_fsblk_t fblock; + ext4_fsblk_t fblock_start; + uint32_t fblock_count; + + uint8_t *u8_buf = buf; + int r; + struct ext4_inode_ref ref; + + ext4_assert(f && f->mp); + + if (f->flags & O_WRONLY) + return EPERM; + + if (!size) + return EOK; + + EXT4_MP_LOCK(f->mp); + + struct ext4_fs *const fs = &f->mp->fs; + struct ext4_sblock *const sb = &f->mp->fs.sb; + + if (rcnt) + *rcnt = 0; + + r = ext4_fs_get_inode_ref(fs, f->inode, &ref); + if (r != EOK) { + EXT4_MP_UNLOCK(f->mp); + return r; + } + + /*Sync file size*/ + f->fsize = ext4_inode_get_size(sb, ref.inode); + + block_size = ext4_sb_get_block_size(sb); + size = ((uint64_t)size > (f->fsize - f->fpos)) + ? ((size_t)(f->fsize - f->fpos)) : size; + + iblock_idx = (uint32_t)((f->fpos) / block_size); + iblock_last = (uint32_t)((f->fpos + size) / block_size); + unalg = (f->fpos) % block_size; + + /*If the size of symlink is smaller than 60 bytes*/ + bool softlink; + softlink = ext4_inode_is_type(sb, ref.inode, EXT4_INODE_MODE_SOFTLINK); + if (softlink && f->fsize < sizeof(ref.inode->blocks) + && !ext4_inode_get_blocks_count(sb, ref.inode)) { + + char *content = (char *)ref.inode->blocks; + if (f->fpos < f->fsize) { + size_t len = size; + if (unalg + size > (uint32_t)f->fsize) + len = (uint32_t)f->fsize - unalg; + memcpy(buf, content + unalg, len); + if (rcnt) + *rcnt = len; + + } + + r = EOK; + goto Finish; + } + + if (unalg) { + size_t len = size; + if (size > (block_size - unalg)) + len = block_size - unalg; + + r = ext4_fs_get_inode_dblk_idx(&ref, iblock_idx, &fblock, true); + if (r != EOK) + goto Finish; + + /* Do we get an unwritten range? */ + if (fblock != 0) { + uint64_t off = fblock * block_size + unalg; + r = ext4_block_readbytes(f->mp->fs.bdev, off, u8_buf, len); + if (r != EOK) + goto Finish; + + } else { + /* Yes, we do. */ + memset(u8_buf, 0, len); + } + + u8_buf += len; + size -= len; + f->fpos += len; + + if (rcnt) + *rcnt += len; + + iblock_idx++; + } + + fblock_start = 0; + fblock_count = 0; + while (size >= block_size) { + while (iblock_idx < iblock_last) { + r = ext4_fs_get_inode_dblk_idx(&ref, iblock_idx, + &fblock, true); + if (r != EOK) + goto Finish; + + iblock_idx++; + + if (!fblock_start) + fblock_start = fblock; + + if ((fblock_start + fblock_count) != fblock) + break; + + fblock_count++; + } + + r = ext4_blocks_get_direct(f->mp->fs.bdev, u8_buf, fblock_start, + fblock_count); + if (r != EOK) + goto Finish; + + size -= block_size * fblock_count; + u8_buf += block_size * fblock_count; + f->fpos += block_size * fblock_count; + + if (rcnt) + *rcnt += block_size * fblock_count; + + fblock_start = fblock; + fblock_count = 1; + } + + if (size) { + uint64_t off; + r = ext4_fs_get_inode_dblk_idx(&ref, iblock_idx, &fblock, true); + if (r != EOK) + goto Finish; + + off = fblock * block_size; + r = ext4_block_readbytes(f->mp->fs.bdev, off, u8_buf, size); + if (r != EOK) + goto Finish; + + f->fpos += size; + + if (rcnt) + *rcnt += size; + } + +Finish: + ext4_fs_put_inode_ref(&ref); + EXT4_MP_UNLOCK(f->mp); + return r; +} + +int ext4_fwrite(ext4_file *f, const void *buf, size_t size, size_t *wcnt) +{ + uint32_t unalg; + uint32_t iblk_idx; + uint32_t iblock_last; + uint32_t ifile_blocks; + uint32_t block_size; + + uint32_t fblock_count; + ext4_fsblk_t fblk; + ext4_fsblk_t fblock_start; + + struct ext4_inode_ref ref; + const uint8_t *u8_buf = buf; + int r, rr = EOK; + + ext4_assert(f && f->mp); + + if (f->flags & O_RDONLY) + return EPERM; + + if (!size) + return EOK; + + EXT4_MP_LOCK(f->mp); + ext4_trans_start(f->mp); + + struct ext4_fs *const fs = &f->mp->fs; + struct ext4_sblock *const sb = &f->mp->fs.sb; + + if (wcnt) + *wcnt = 0; + + r = ext4_fs_get_inode_ref(fs, f->inode, &ref); + if (r != EOK) { + ext4_trans_abort(f->mp); + EXT4_MP_UNLOCK(f->mp); + return r; + } + + /*Sync file size*/ + f->fsize = ext4_inode_get_size(sb, ref.inode); + block_size = ext4_sb_get_block_size(sb); + + iblock_last = (uint32_t)((f->fpos + size) / block_size); + iblk_idx = (uint32_t)(f->fpos / block_size); + ifile_blocks = (uint32_t)((f->fsize + block_size - 1) / block_size); + + unalg = (f->fpos) % block_size; + + if (unalg) { + size_t len = size; + uint64_t off; + if (size > (block_size - unalg)) + len = block_size - unalg; + + r = ext4_fs_init_inode_dblk_idx(&ref, iblk_idx, &fblk); + if (r != EOK) + goto Finish; + + off = fblk * block_size + unalg; + r = ext4_block_writebytes(f->mp->fs.bdev, off, u8_buf, len); + if (r != EOK) + goto Finish; + + u8_buf += len; + size -= len; + f->fpos += len; + + if (wcnt) + *wcnt += len; + + iblk_idx++; + } + + /*Start write back cache mode.*/ + r = ext4_block_cache_write_back(f->mp->fs.bdev, 1); + if (r != EOK) + goto Finish; + + fblock_start = 0; + fblock_count = 0; + while (size >= block_size) { + + while (iblk_idx < iblock_last) { + if (iblk_idx < ifile_blocks) { + r = ext4_fs_init_inode_dblk_idx(&ref, iblk_idx, + &fblk); + if (r != EOK) + goto Finish; + } else { + rr = ext4_fs_append_inode_dblk(&ref, &fblk, + &iblk_idx); + if (rr != EOK) { + /* Unable to append more blocks. But + * some block might be allocated already + * */ + break; + } + } + + iblk_idx++; + + if (!fblock_start) { + fblock_start = fblk; + } + + if ((fblock_start + fblock_count) != fblk) + break; + + fblock_count++; + } + + r = ext4_blocks_set_direct(f->mp->fs.bdev, u8_buf, fblock_start, + fblock_count); + if (r != EOK) + break; + + size -= block_size * fblock_count; + u8_buf += block_size * fblock_count; + f->fpos += block_size * fblock_count; + + if (wcnt) + *wcnt += block_size * fblock_count; + + fblock_start = fblk; + fblock_count = 1; + + if (rr != EOK) { + /*ext4_fs_append_inode_block has failed and no + * more blocks might be written. But node size + * should be updated.*/ + r = rr; + goto out_fsize; + } + } + + /*Stop write back cache mode*/ + ext4_block_cache_write_back(f->mp->fs.bdev, 0); + + if (r != EOK) + goto Finish; + + if (size) { + uint64_t off; + if (iblk_idx < ifile_blocks) { + r = ext4_fs_init_inode_dblk_idx(&ref, iblk_idx, &fblk); + if (r != EOK) + goto Finish; + } else { + r = ext4_fs_append_inode_dblk(&ref, &fblk, &iblk_idx); + if (r != EOK) + /*Node size sholud be updated.*/ + goto out_fsize; + } + + off = fblk * block_size; + r = ext4_block_writebytes(f->mp->fs.bdev, off, u8_buf, size); + if (r != EOK) + goto Finish; + + f->fpos += size; + + if (wcnt) + *wcnt += size; + } + +out_fsize: + if (f->fpos > f->fsize) { + f->fsize = f->fpos; + ext4_inode_set_size(ref.inode, f->fsize); + ref.dirty = true; + } + +Finish: + r = ext4_fs_put_inode_ref(&ref); + + if (r != EOK) + ext4_trans_abort(f->mp); + else + ext4_trans_stop(f->mp); + + EXT4_MP_UNLOCK(f->mp); + return r; +} + +int ext4_fseek(ext4_file *f, uint64_t offset, uint32_t origin) +{ + switch (origin) { + case SEEK_SET: + if (offset > f->fsize) + return EINVAL; + + f->fpos = offset; + return EOK; + case SEEK_CUR: + if ((offset + f->fpos) > f->fsize) + return EINVAL; + + f->fpos += offset; + return EOK; + case SEEK_END: + if (offset > f->fsize) + return EINVAL; + + f->fpos = f->fsize - offset; + return EOK; + } + return EINVAL; +} + +uint64_t ext4_ftell(ext4_file *f) +{ + return f->fpos; +} + +uint64_t ext4_fsize(ext4_file *f) +{ + return f->fsize; +} + +int ext4_chmod(const char *path, uint32_t mode) +{ + int r; + uint32_t ino, orig_mode; + ext4_file f; + struct ext4_sblock *sb; + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + ext4_trans_start(mp); + + r = ext4_generic_open2(&f, path, O_RDWR, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) { + ext4_trans_abort(mp); + EXT4_MP_UNLOCK(mp); + return r; + } + ino = f.inode; + sb = &mp->fs.sb; + ext4_fclose(&f); + r = ext4_fs_get_inode_ref(&mp->fs, ino, &inode_ref); + if (r != EOK) { + ext4_trans_abort(mp); + EXT4_MP_UNLOCK(mp); + return r; + } + + orig_mode = ext4_inode_get_mode(sb, inode_ref.inode); + orig_mode &= ~0xFFF; + orig_mode |= mode & 0xFFF; + ext4_inode_set_mode(sb, inode_ref.inode, orig_mode); + inode_ref.dirty = true; + + r = ext4_fs_put_inode_ref(&inode_ref); + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_chown(const char *path, uint32_t uid, uint32_t gid) +{ + int r; + ext4_file f; + uint32_t ino; + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + ext4_trans_start(mp); + + r = ext4_generic_open2(&f, path, O_RDWR, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) { + ext4_trans_abort(mp); + EXT4_MP_UNLOCK(mp); + return r; + } + ino = f.inode; + ext4_fclose(&f); + r = ext4_fs_get_inode_ref(&mp->fs, ino, &inode_ref); + if (r != EOK) { + ext4_trans_abort(mp); + EXT4_MP_UNLOCK(mp); + return r; + } + + ext4_inode_set_uid(inode_ref.inode, uid); + ext4_inode_set_gid(inode_ref.inode, gid); + inode_ref.dirty = true; + + r = ext4_fs_put_inode_ref(&inode_ref); + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_file_set_atime(const char *path, uint32_t atime) +{ + int r; + ext4_file f; + uint32_t ino; + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + ext4_trans_start(mp); + + r = ext4_generic_open2(&f, path, O_RDWR, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) { + ext4_trans_abort(mp); + EXT4_MP_UNLOCK(mp); + return r; + } + ino = f.inode; + ext4_fclose(&f); + r = ext4_fs_get_inode_ref(&mp->fs, ino, &inode_ref); + if (r != EOK) { + ext4_trans_abort(mp); + EXT4_MP_UNLOCK(mp); + return r; + } + + ext4_inode_set_access_time(inode_ref.inode, atime); + inode_ref.dirty = true; + + r = ext4_fs_put_inode_ref(&inode_ref); + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_file_set_mtime(const char *path, uint32_t mtime) +{ + int r; + ext4_file f; + uint32_t ino; + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + ext4_trans_start(mp); + + r = ext4_generic_open2(&f, path, O_RDWR, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) { + ext4_trans_abort(mp); + EXT4_MP_UNLOCK(mp); + return r; + } + ino = f.inode; + ext4_fclose(&f); + r = ext4_fs_get_inode_ref(&mp->fs, ino, &inode_ref); + if (r != EOK) { + ext4_trans_abort(mp); + EXT4_MP_UNLOCK(mp); + return r; + } + + ext4_inode_set_modif_time(inode_ref.inode, mtime); + inode_ref.dirty = true; + + r = ext4_fs_put_inode_ref(&inode_ref); + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_file_set_ctime(const char *path, uint32_t ctime) +{ + int r; + ext4_file f; + uint32_t ino; + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + ext4_trans_start(mp); + + r = ext4_generic_open2(&f, path, O_RDWR, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) { + ext4_trans_abort(mp); + EXT4_MP_UNLOCK(mp); + return r; + } + ino = f.inode; + ext4_fclose(&f); + r = ext4_fs_get_inode_ref(&mp->fs, ino, &inode_ref); + if (r != EOK) { + ext4_trans_abort(mp); + EXT4_MP_UNLOCK(mp); + return r; + } + + ext4_inode_set_change_inode_time(inode_ref.inode, ctime); + inode_ref.dirty = true; + + r = ext4_fs_put_inode_ref(&inode_ref); + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + EXT4_MP_UNLOCK(mp); + return r; +} + +static int ext4_fsymlink_set(ext4_file *f, const void *buf, uint32_t size) +{ + struct ext4_inode_ref ref; + uint32_t sblock; + ext4_fsblk_t fblock; + uint32_t block_size; + int r; + + ext4_assert(f && f->mp); + + if (!size) + return EOK; + + r = ext4_fs_get_inode_ref(&f->mp->fs, f->inode, &ref); + if (r != EOK) + return r; + + /*Sync file size*/ + block_size = ext4_sb_get_block_size(&f->mp->fs.sb); + if (size > block_size) { + r = EINVAL; + goto Finish; + } + r = ext4_ftruncate_no_lock(f, 0); + if (r != EOK) + goto Finish; + + /*Start write back cache mode.*/ + r = ext4_block_cache_write_back(f->mp->fs.bdev, 1); + if (r != EOK) + goto Finish; + + /*If the size of symlink is smaller than 60 bytes*/ + if (size < sizeof(ref.inode->blocks)) { + memset(ref.inode->blocks, 0, sizeof(ref.inode->blocks)); + memcpy(ref.inode->blocks, buf, size); + ext4_inode_clear_flag(ref.inode, EXT4_INODE_FLAG_EXTENTS); + } else { + ext4_fs_inode_blocks_init(&f->mp->fs, &ref); + r = ext4_fs_append_inode_dblk(&ref, &fblock, &sblock); + if (r != EOK) + goto Finish; + + r = ext4_block_writebytes(f->mp->fs.bdev, 0, buf, size); + if (r != EOK) + goto Finish; + + } + + /*Stop write back cache mode*/ + ext4_block_cache_write_back(f->mp->fs.bdev, 0); + + if (r != EOK) + goto Finish; + + ext4_inode_set_size(ref.inode, size); + ref.dirty = true; + + f->fsize = size; + if (f->fpos > size) + f->fpos = size; + +Finish: + ext4_fs_put_inode_ref(&ref); + return r; +} + +int ext4_fsymlink(const char *target, const char *path) +{ + struct ext4_mountpoint *mp = ext4_get_mount(path); + int r; + ext4_file f; + int filetype; + + if (!mp) + return ENOENT; + + filetype = EXT4_DE_SYMLINK; + + EXT4_MP_LOCK(mp); + ext4_trans_start(mp); + + ext4_block_cache_write_back(mp->fs.bdev, 1); + r = ext4_generic_open2(&f, path, O_RDWR|O_CREAT, filetype, NULL, NULL); + if (r == EOK) + r = ext4_fsymlink_set(&f, target, strlen(target)); + else + goto Finish; + + ext4_fclose(&f); + +Finish: + ext4_block_cache_write_back(mp->fs.bdev, 0); + + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_readlink(const char *path, char *buf, size_t bufsize, size_t *rcnt) +{ + struct ext4_mountpoint *mp = ext4_get_mount(path); + int r; + ext4_file f; + int filetype; + + if (!mp) + return ENOENT; + + if (!buf) + return EINVAL; + + filetype = EXT4_DE_SYMLINK; + + EXT4_MP_LOCK(mp); + ext4_block_cache_write_back(mp->fs.bdev, 1); + r = ext4_generic_open2(&f, path, O_RDONLY, filetype, NULL, NULL); + if (r == EOK) + r = ext4_fread(&f, buf, bufsize, rcnt); + else + goto Finish; + + ext4_fclose(&f); + +Finish: + ext4_block_cache_write_back(mp->fs.bdev, 0); + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_setxattr(const char *path, const char *name, size_t name_len, + const void *data, size_t data_size, bool replace) +{ + int r = EOK; + ext4_file f; + uint32_t inode; + uint8_t name_index; + const char *dissected_name = NULL; + size_t dissected_len = 0; + struct ext4_xattr_ref xattr_ref; + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + if (!mp) + return ENOENT; + + dissected_name = ext4_extract_xattr_name(name, name_len, + &name_index, &dissected_len); + if (!dissected_len) + return EINVAL; + + EXT4_MP_LOCK(mp); + ext4_trans_start(mp); + + r = ext4_generic_open2(&f, path, O_RDWR, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) + goto Finish; + inode = f.inode; + ext4_fclose(&f); + + r = ext4_fs_get_inode_ref(&mp->fs, inode, &inode_ref); + if (r != EOK) + goto Finish; + + r = ext4_fs_get_xattr_ref(&mp->fs, &inode_ref, &xattr_ref); + if (r != EOK) { + ext4_fs_put_inode_ref(&inode_ref); + goto Finish; + } + + r = ext4_fs_set_xattr(&xattr_ref, name_index, dissected_name, + dissected_len, data, data_size, replace); + + ext4_fs_put_xattr_ref(&xattr_ref); + ext4_fs_put_inode_ref(&inode_ref); +Finish: + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_getxattr(const char *path, const char *name, size_t name_len, + void *buf, size_t buf_size, size_t *data_size) +{ + int r = EOK; + ext4_file f; + uint32_t inode; + uint8_t name_index; + const char *dissected_name = NULL; + size_t dissected_len = 0; + struct ext4_xattr_ref xattr_ref; + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + if (!mp) + return ENOENT; + + dissected_name = ext4_extract_xattr_name(name, name_len, + &name_index, &dissected_len); + if (!dissected_len) + return EINVAL; + + EXT4_MP_LOCK(mp); + r = ext4_generic_open2(&f, path, O_RDWR, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) + goto Finish; + inode = f.inode; + ext4_fclose(&f); + + r = ext4_fs_get_inode_ref(&mp->fs, inode, &inode_ref); + if (r != EOK) + goto Finish; + + r = ext4_fs_get_xattr_ref(&mp->fs, &inode_ref, &xattr_ref); + if (r != EOK) { + ext4_fs_put_inode_ref(&inode_ref); + goto Finish; + } + + r = ext4_fs_get_xattr(&xattr_ref, name_index, dissected_name, + dissected_len, buf, buf_size, data_size); + + ext4_fs_put_xattr_ref(&xattr_ref); + ext4_fs_put_inode_ref(&inode_ref); +Finish: + EXT4_MP_UNLOCK(mp); + return r; +} + +struct ext4_listxattr_iterator { + char *list; + char *list_ptr; + size_t size; + size_t ret_size; + bool list_too_small; + bool get_required_size; +}; + +static int ext4_iterate_ea_list(struct ext4_xattr_ref *ref, + struct ext4_xattr_item *item) +{ + struct ext4_listxattr_iterator *lxi; + lxi = ref->iter_arg; + if (!lxi->get_required_size) { + size_t plen; + const char *prefix; + prefix = ext4_get_xattr_name_prefix(item->name_index, &plen); + if (lxi->ret_size + plen + item->name_len + 1 > lxi->size) { + lxi->list_too_small = 1; + return EXT4_XATTR_ITERATE_STOP; + } + if (prefix) { + memcpy(lxi->list_ptr, prefix, plen); + lxi->list_ptr += plen; + lxi->ret_size += plen; + } + memcpy(lxi->list_ptr, item->name, item->name_len); + lxi->list_ptr[item->name_len] = 0; + lxi->list_ptr += item->name_len + 1; + } + lxi->ret_size += item->name_len + 1; + return EXT4_XATTR_ITERATE_CONT; +} + +int ext4_listxattr(const char *path, char *list, size_t size, size_t *ret_size) +{ + int r = EOK; + ext4_file f; + uint32_t inode; + struct ext4_xattr_ref xattr_ref; + struct ext4_inode_ref inode_ref; + struct ext4_listxattr_iterator lxi; + struct ext4_mountpoint *mp = ext4_get_mount(path); + if (!mp) + return ENOENT; + + lxi.list = list; + lxi.list_ptr = list; + lxi.size = size; + lxi.ret_size = 0; + lxi.list_too_small = false; + lxi.get_required_size = (!size) ? true : false; + + EXT4_MP_LOCK(mp); + r = ext4_generic_open2(&f, path, O_RDWR, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) + goto Finish; + inode = f.inode; + ext4_fclose(&f); + + r = ext4_fs_get_inode_ref(&mp->fs, inode, &inode_ref); + if (r != EOK) + goto Finish; + + r = ext4_fs_get_xattr_ref(&mp->fs, &inode_ref, &xattr_ref); + if (r != EOK) { + ext4_fs_put_inode_ref(&inode_ref); + goto Finish; + } + + xattr_ref.iter_arg = &lxi; + ext4_fs_xattr_iterate(&xattr_ref, ext4_iterate_ea_list); + if (lxi.list_too_small) + r = ERANGE; + + if (r == EOK) { + if (ret_size) + *ret_size = lxi.ret_size; + + } + ext4_fs_put_xattr_ref(&xattr_ref); + ext4_fs_put_inode_ref(&inode_ref); +Finish: + EXT4_MP_UNLOCK(mp); + return r; + +} + +int ext4_removexattr(const char *path, const char *name, size_t name_len) +{ + int r = EOK; + ext4_file f; + uint32_t inode; + uint8_t name_index; + const char *dissected_name = NULL; + size_t dissected_len = 0; + struct ext4_xattr_ref xattr_ref; + struct ext4_inode_ref inode_ref; + struct ext4_mountpoint *mp = ext4_get_mount(path); + if (!mp) + return ENOENT; + + dissected_name = ext4_extract_xattr_name(name, name_len, + &name_index, &dissected_len); + if (!dissected_len) + return EINVAL; + + EXT4_MP_LOCK(mp); + ext4_trans_start(mp); + + r = ext4_generic_open2(&f, path, O_RDWR, EXT4_DE_UNKNOWN, NULL, NULL); + if (r != EOK) + goto Finish; + inode = f.inode; + ext4_fclose(&f); + + r = ext4_fs_get_inode_ref(&mp->fs, inode, &inode_ref); + if (r != EOK) + goto Finish; + + r = ext4_fs_get_xattr_ref(&mp->fs, &inode_ref, &xattr_ref); + if (r != EOK) { + ext4_fs_put_inode_ref(&inode_ref); + goto Finish; + } + + r = ext4_fs_remove_xattr(&xattr_ref, name_index, dissected_name, + dissected_len); + + ext4_fs_put_xattr_ref(&xattr_ref); + ext4_fs_put_inode_ref(&inode_ref); +Finish: + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + + EXT4_MP_UNLOCK(mp); + return r; + +} + +/*********************************DIRECTORY OPERATION************************/ + +int ext4_dir_rm(const char *path) +{ + int r; + int len; + ext4_file f; + + struct ext4_mountpoint *mp = ext4_get_mount(path); + struct ext4_inode_ref act; + struct ext4_inode_ref child; + struct ext4_dir_iter it; + + uint32_t name_off; + uint32_t inode_up; + uint32_t inode_current; + uint32_t depth = 1; + + bool has_children; + bool is_goal; + bool dir_end; + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + + struct ext4_fs *const fs = &mp->fs; + + /*Check if exist.*/ + r = ext4_generic_open(&f, path, "r", false, &inode_up, &name_off); + if (r != EOK) { + ext4_trans_abort(mp); + EXT4_MP_UNLOCK(mp); + return r; + } + + path += name_off; + len = ext4_path_check(path, &is_goal); + + inode_current = f.inode; + + ext4_block_cache_write_back(mp->fs.bdev, 1); + + do { + + uint64_t act_curr_pos = 0; + has_children = false; + dir_end = false; + + while (r == EOK && !has_children && !dir_end) { + + /*Load directory node.*/ + r = ext4_fs_get_inode_ref(fs, inode_current, &act); + if (r != EOK) { + break; + } + + /*Initialize iterator.*/ + r = ext4_dir_iterator_init(&it, &act, act_curr_pos); + if (r != EOK) { + ext4_fs_put_inode_ref(&act); + break; + } + + if (!it.curr) { + dir_end = true; + goto End; + } + + ext4_trans_start(mp); + + /*Get up directory inode when ".." entry*/ + if ((it.curr->name_len == 2) && + ext4_is_dots(it.curr->name, it.curr->name_len)) { + inode_up = ext4_dir_en_get_inode(it.curr); + } + + /*If directory or file entry, but not "." ".." entry*/ + if (!ext4_is_dots(it.curr->name, it.curr->name_len)) { + + /*Get child inode reference do unlink + * directory/file.*/ + uint32_t cinode; + uint32_t inode_type; + cinode = ext4_dir_en_get_inode(it.curr); + r = ext4_fs_get_inode_ref(fs, cinode, &child); + if (r != EOK) + goto End; + + /*If directory with no leaf children*/ + r = ext4_has_children(&has_children, &child); + if (r != EOK) { + ext4_fs_put_inode_ref(&child); + goto End; + } + + if (has_children) { + /*Has directory children. Go into this + * directory.*/ + inode_up = inode_current; + inode_current = cinode; + depth++; + ext4_fs_put_inode_ref(&child); + goto End; + } + inode_type = ext4_inode_type(&mp->fs.sb, + child.inode); + + /* Truncate */ + if (inode_type != EXT4_INODE_MODE_DIRECTORY) + r = ext4_trunc_inode(mp, child.index, 0); + else + r = ext4_trunc_dir(mp, &act, &child); + + if (r != EOK) { + ext4_fs_put_inode_ref(&child); + goto End; + } + + /*No children in child directory or file. Just + * unlink.*/ + r = ext4_unlink(f.mp, &act, &child, + (char *)it.curr->name, + it.curr->name_len); + if (r != EOK) { + ext4_fs_put_inode_ref(&child); + goto End; + } + + ext4_inode_set_del_time(child.inode, -1L); + ext4_inode_set_links_cnt(child.inode, 0); + child.dirty = true; + + r = ext4_fs_free_inode(&child); + if (r != EOK) { + ext4_fs_put_inode_ref(&child); + goto End; + } + + r = ext4_fs_put_inode_ref(&child); + if (r != EOK) + goto End; + + } + + r = ext4_dir_iterator_next(&it); + if (r != EOK) + goto End; + + act_curr_pos = it.curr_off; +End: + ext4_dir_iterator_fini(&it); + if (r == EOK) + r = ext4_fs_put_inode_ref(&act); + else + ext4_fs_put_inode_ref(&act); + + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + } + + if (dir_end) { + /*Directory iterator reached last entry*/ + depth--; + if (depth) + inode_current = inode_up; + + } + + if (r != EOK) + break; + + } while (depth); + + /*Last unlink*/ + if (r == EOK && !depth) { + /*Load parent.*/ + struct ext4_inode_ref parent; + r = ext4_fs_get_inode_ref(&f.mp->fs, inode_up, + &parent); + if (r != EOK) + goto Finish; + r = ext4_fs_get_inode_ref(&f.mp->fs, inode_current, + &act); + if (r != EOK) { + ext4_fs_put_inode_ref(&act); + goto Finish; + } + + ext4_trans_start(mp); + + /* In this place all directories should be + * unlinked. + * Last unlink from root of current directory*/ + r = ext4_unlink(f.mp, &parent, &act, + (char *)path, len); + if (r != EOK) { + ext4_fs_put_inode_ref(&parent); + ext4_fs_put_inode_ref(&act); + goto Finish; + } + + if (ext4_inode_get_links_cnt(act.inode) == 2) { + ext4_inode_set_del_time(act.inode, -1L); + ext4_inode_set_links_cnt(act.inode, 0); + act.dirty = true; + /*Truncate*/ + r = ext4_trunc_dir(mp, &parent, &act); + if (r != EOK) { + ext4_fs_put_inode_ref(&parent); + ext4_fs_put_inode_ref(&act); + goto Finish; + } + + r = ext4_fs_free_inode(&act); + if (r != EOK) { + ext4_fs_put_inode_ref(&parent); + ext4_fs_put_inode_ref(&act); + goto Finish; + } + } + + r = ext4_fs_put_inode_ref(&parent); + if (r != EOK) + goto Finish; + + r = ext4_fs_put_inode_ref(&act); + Finish: + if (r != EOK) + ext4_trans_abort(mp); + else + ext4_trans_stop(mp); + } + + ext4_block_cache_write_back(mp->fs.bdev, 0); + EXT4_MP_UNLOCK(mp); + + return r; +} + +int ext4_dir_mk(const char *path) +{ + int r; + ext4_file f; + + struct ext4_mountpoint *mp = ext4_get_mount(path); + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + ext4_trans_start(mp); + + /*Check if exist.*/ + r = ext4_generic_open(&f, path, "r", false, 0, 0); + if (r == EOK) { + /*Directory already created*/ + ext4_trans_stop(mp); + EXT4_MP_UNLOCK(mp); + return r; + } + + /*Create new dir*/ + r = ext4_generic_open(&f, path, "w", false, 0, 0); + if (r != EOK) { + ext4_trans_abort(mp); + EXT4_MP_UNLOCK(mp); + return r; + } + + ext4_trans_stop(mp); + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_dir_open(ext4_dir *d, const char *path) +{ + struct ext4_mountpoint *mp = ext4_get_mount(path); + int r; + + if (!mp) + return ENOENT; + + EXT4_MP_LOCK(mp); + r = ext4_generic_open(&d->f, path, "r", false, 0, 0); + d->next_off = 0; + EXT4_MP_UNLOCK(mp); + return r; +} + +int ext4_dir_close(ext4_dir *d) +{ + return ext4_fclose(&d->f); +} + +const ext4_direntry *ext4_dir_entry_next(ext4_dir *d) +{ +#define EXT4_DIR_ENTRY_OFFSET_TERM (uint64_t)(-1) + + int r; + ext4_direntry *de = 0; + struct ext4_inode_ref dir; + struct ext4_dir_iter it; + + EXT4_MP_LOCK(d->f.mp); + + if (d->next_off == EXT4_DIR_ENTRY_OFFSET_TERM) { + EXT4_MP_UNLOCK(d->f.mp); + return 0; + } + + r = ext4_fs_get_inode_ref(&d->f.mp->fs, d->f.inode, &dir); + if (r != EOK) { + goto Finish; + } + + r = ext4_dir_iterator_init(&it, &dir, d->next_off); + if (r != EOK) { + ext4_fs_put_inode_ref(&dir); + goto Finish; + } + + memcpy(&d->de, it.curr, sizeof(ext4_direntry)); + de = &d->de; + + ext4_dir_iterator_next(&it); + + d->next_off = it.curr ? it.curr_off : EXT4_DIR_ENTRY_OFFSET_TERM; + + ext4_dir_iterator_fini(&it); + ext4_fs_put_inode_ref(&dir); + +Finish: + EXT4_MP_UNLOCK(d->f.mp); + return de; +} + +void ext4_dir_entry_rewind(ext4_dir *d) +{ + d->next_off = 0; +} + +/** + * @} + */ diff --git a/src/ext4_balloc.c b/src/ext4_balloc.c new file mode 100644 index 0000000..2980e26 --- /dev/null +++ b/src/ext4_balloc.c @@ -0,0 +1,654 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_balloc.c + * @brief Physical block allocator. + */ + +#include "ext4_config.h" +#include "ext4_balloc.h" +#include "ext4_super.h" +#include "ext4_crc32.h" +#include "ext4_block_group.h" +#include "ext4_fs.h" +#include "ext4_bitmap.h" +#include "ext4_inode.h" + +/**@brief Compute number of block group from block address. + * @param sb superblock pointer. + * @param baddr Absolute address of block. + * @return Block group index + */ +uint32_t ext4_balloc_get_bgid_of_block(struct ext4_sblock *s, + uint64_t baddr) +{ + if (ext4_get32(s, first_data_block) && baddr) + baddr--; + + return (uint32_t)(baddr / ext4_get32(s, blocks_per_group)); +} + +/**@brief Compute the starting block address of a block group + * @param sb superblock pointer. + * @param bgid block group index + * @return Block address + */ +uint64_t ext4_balloc_get_block_of_bgid(struct ext4_sblock *s, + uint32_t bgid) +{ + uint64_t baddr = 0; + if (ext4_get32(s, first_data_block)) + baddr++; + + baddr += bgid * ext4_get32(s, blocks_per_group); + return baddr; +} + +#if CONFIG_META_CSUM_ENABLE +static uint32_t ext4_balloc_bitmap_csum(struct ext4_sblock *sb, + void *bitmap) +{ + uint32_t checksum = 0; + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + uint32_t blocks_per_group = ext4_get32(sb, blocks_per_group); + + /* First calculate crc32 checksum against fs uuid */ + checksum = ext4_crc32c(EXT4_CRC32_INIT, sb->uuid, + sizeof(sb->uuid)); + /* Then calculate crc32 checksum against block_group_desc */ + checksum = ext4_crc32c(checksum, bitmap, blocks_per_group / 8); + } + return checksum; +} +#else +#define ext4_balloc_bitmap_csum(...) 0 +#endif + +void ext4_balloc_set_bitmap_csum(struct ext4_sblock *sb, + struct ext4_bgroup *bg, + void *bitmap __unused) +{ + int desc_size = ext4_sb_get_desc_size(sb); + uint32_t checksum = ext4_balloc_bitmap_csum(sb, bitmap); + uint16_t lo_checksum = to_le16(checksum & 0xFFFF), + hi_checksum = to_le16(checksum >> 16); + + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + return; + + /* See if we need to assign a 32bit checksum */ + bg->block_bitmap_csum_lo = lo_checksum; + if (desc_size == EXT4_MAX_BLOCK_GROUP_DESCRIPTOR_SIZE) + bg->block_bitmap_csum_hi = hi_checksum; + +} + +#if CONFIG_META_CSUM_ENABLE +static bool +ext4_balloc_verify_bitmap_csum(struct ext4_sblock *sb, + struct ext4_bgroup *bg, + void *bitmap __unused) +{ + int desc_size = ext4_sb_get_desc_size(sb); + uint32_t checksum = ext4_balloc_bitmap_csum(sb, bitmap); + uint16_t lo_checksum = to_le16(checksum & 0xFFFF), + hi_checksum = to_le16(checksum >> 16); + + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + return true; + + if (bg->block_bitmap_csum_lo != lo_checksum) + return false; + + if (desc_size == EXT4_MAX_BLOCK_GROUP_DESCRIPTOR_SIZE) + if (bg->block_bitmap_csum_hi != hi_checksum) + return false; + + return true; +} +#else +#define ext4_balloc_verify_bitmap_csum(...) true +#endif + +int ext4_balloc_free_block(struct ext4_inode_ref *inode_ref, ext4_fsblk_t baddr) +{ + struct ext4_fs *fs = inode_ref->fs; + struct ext4_sblock *sb = &fs->sb; + + uint32_t bg_id = ext4_balloc_get_bgid_of_block(sb, baddr); + uint32_t index_in_group = ext4_fs_addr_to_idx_bg(sb, baddr); + + /* Load block group reference */ + struct ext4_block_group_ref bg_ref; + int rc = ext4_fs_get_block_group_ref(fs, bg_id, &bg_ref); + if (rc != EOK) + return rc; + + struct ext4_bgroup *bg = bg_ref.block_group; + + /* Load block with bitmap */ + ext4_fsblk_t bitmap_block_addr = + ext4_bg_get_block_bitmap(bg, sb); + + struct ext4_block bitmap_block; + + rc = ext4_trans_block_get(fs->bdev, &bitmap_block, bitmap_block_addr); + if (rc != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + if (!ext4_balloc_verify_bitmap_csum(sb, bg, bitmap_block.data)) { + ext4_dbg(DEBUG_BALLOC, + DBG_WARN "Bitmap checksum failed." + "Group: %" PRIu32"\n", + bg_ref.index); + } + + /* Modify bitmap */ + ext4_bmap_bit_clr(bitmap_block.data, index_in_group); + ext4_balloc_set_bitmap_csum(sb, bg, bitmap_block.data); + ext4_trans_set_block_dirty(bitmap_block.buf); + + /* Release block with bitmap */ + rc = ext4_block_set(fs->bdev, &bitmap_block); + if (rc != EOK) { + /* Error in saving bitmap */ + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + uint32_t block_size = ext4_sb_get_block_size(sb); + + /* Update superblock free blocks count */ + uint64_t sb_free_blocks = ext4_sb_get_free_blocks_cnt(sb); + sb_free_blocks++; + ext4_sb_set_free_blocks_cnt(sb, sb_free_blocks); + + /* Update inode blocks count */ + uint64_t ino_blocks = ext4_inode_get_blocks_count(sb, inode_ref->inode); + ino_blocks -= block_size / EXT4_INODE_BLOCK_SIZE; + ext4_inode_set_blocks_count(sb, inode_ref->inode, ino_blocks); + inode_ref->dirty = true; + + /* Update block group free blocks count */ + uint32_t free_blocks = ext4_bg_get_free_blocks_count(bg, sb); + free_blocks++; + ext4_bg_set_free_blocks_count(bg, sb, free_blocks); + + bg_ref.dirty = true; + + rc = ext4_trans_try_revoke_block(fs->bdev, baddr); + if (rc != EOK) { + bg_ref.dirty = false; + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + ext4_bcache_invalidate_lba(fs->bdev->bc, baddr, 1); + /* Release block group reference */ + return ext4_fs_put_block_group_ref(&bg_ref); +} + +int ext4_balloc_free_blocks(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t first, uint32_t count) +{ + int rc = EOK; + struct ext4_fs *fs = inode_ref->fs; + struct ext4_sblock *sb = &fs->sb; + + /* Compute indexes */ + uint32_t bg_first = ext4_balloc_get_bgid_of_block(sb, first); + + /* Compute indexes */ + uint32_t bg_last = ext4_balloc_get_bgid_of_block(sb, first + count - 1); + + if (!ext4_sb_feature_incom(sb, EXT4_FINCOM_FLEX_BG)) { + /*It is not possible without flex_bg that blocks are continuous + * and and last block belongs to other bg.*/ + if (bg_last != bg_first) { + ext4_dbg(DEBUG_BALLOC, DBG_WARN "FLEX_BG: disabled & " + "bg_last: %"PRIu32" bg_first: %"PRIu32"\n", + bg_last, bg_first); + } + } + + /* Load block group reference */ + struct ext4_block_group_ref bg_ref; + while (bg_first <= bg_last) { + + rc = ext4_fs_get_block_group_ref(fs, bg_first, &bg_ref); + if (rc != EOK) + return rc; + + struct ext4_bgroup *bg = bg_ref.block_group; + + uint32_t idx_in_bg_first; + idx_in_bg_first = ext4_fs_addr_to_idx_bg(sb, first); + + /* Load block with bitmap */ + ext4_fsblk_t bitmap_blk = ext4_bg_get_block_bitmap(bg, sb); + + struct ext4_block blk; + rc = ext4_trans_block_get(fs->bdev, &blk, bitmap_blk); + if (rc != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + if (!ext4_balloc_verify_bitmap_csum(sb, bg, blk.data)) { + ext4_dbg(DEBUG_BALLOC, + DBG_WARN "Bitmap checksum failed." + "Group: %" PRIu32"\n", + bg_ref.index); + } + uint32_t free_cnt; + free_cnt = ext4_sb_get_block_size(sb) * 8 - idx_in_bg_first; + + /*If last block, free only count blocks*/ + free_cnt = count > free_cnt ? free_cnt : count; + + /* Modify bitmap */ + ext4_bmap_bits_free(blk.data, idx_in_bg_first, free_cnt); + ext4_balloc_set_bitmap_csum(sb, bg, blk.data); + ext4_trans_set_block_dirty(blk.buf); + + count -= free_cnt; + first += free_cnt; + + /* Release block with bitmap */ + rc = ext4_block_set(fs->bdev, &blk); + if (rc != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + uint32_t block_size = ext4_sb_get_block_size(sb); + + /* Update superblock free blocks count */ + uint64_t sb_free_blocks = ext4_sb_get_free_blocks_cnt(sb); + sb_free_blocks += free_cnt; + ext4_sb_set_free_blocks_cnt(sb, sb_free_blocks); + + /* Update inode blocks count */ + uint64_t ino_blocks; + ino_blocks = ext4_inode_get_blocks_count(sb, inode_ref->inode); + ino_blocks -= free_cnt * (block_size / EXT4_INODE_BLOCK_SIZE); + ext4_inode_set_blocks_count(sb, inode_ref->inode, ino_blocks); + inode_ref->dirty = true; + + /* Update block group free blocks count */ + uint32_t free_blocks; + free_blocks = ext4_bg_get_free_blocks_count(bg, sb); + free_blocks += free_cnt; + ext4_bg_set_free_blocks_count(bg, sb, free_blocks); + bg_ref.dirty = true; + + /* Release block group reference */ + rc = ext4_fs_put_block_group_ref(&bg_ref); + if (rc != EOK) + break; + + bg_first++; + } + + uint32_t i; + for (i = 0;i < count;i++) { + rc = ext4_trans_try_revoke_block(fs->bdev, first + i); + if (rc != EOK) + return rc; + + } + + ext4_bcache_invalidate_lba(fs->bdev->bc, first, count); + /*All blocks should be released*/ + ext4_assert(count == 0); + return rc; +} + +int ext4_balloc_alloc_block(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t goal, + ext4_fsblk_t *fblock) +{ + ext4_fsblk_t alloc = 0; + ext4_fsblk_t bmp_blk_adr; + uint32_t rel_blk_idx = 0; + uint64_t free_blocks; + int r; + struct ext4_sblock *sb = &inode_ref->fs->sb; + + /* Load block group number for goal and relative index */ + uint32_t bg_id = ext4_balloc_get_bgid_of_block(sb, goal); + uint32_t idx_in_bg = ext4_fs_addr_to_idx_bg(sb, goal); + + struct ext4_block b; + struct ext4_block_group_ref bg_ref; + + /* Load block group reference */ + r = ext4_fs_get_block_group_ref(inode_ref->fs, bg_id, &bg_ref); + if (r != EOK) + return r; + + struct ext4_bgroup *bg = bg_ref.block_group; + + free_blocks = ext4_bg_get_free_blocks_count(bg_ref.block_group, sb); + if (free_blocks == 0) { + /* This group has no free blocks */ + goto goal_failed; + } + + /* Compute indexes */ + ext4_fsblk_t first_in_bg; + first_in_bg = ext4_balloc_get_block_of_bgid(sb, bg_ref.index); + + uint32_t first_in_bg_index; + first_in_bg_index = ext4_fs_addr_to_idx_bg(sb, first_in_bg); + + if (idx_in_bg < first_in_bg_index) + idx_in_bg = first_in_bg_index; + + /* Load block with bitmap */ + bmp_blk_adr = ext4_bg_get_block_bitmap(bg_ref.block_group, sb); + + r = ext4_trans_block_get(inode_ref->fs->bdev, &b, bmp_blk_adr); + if (r != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return r; + } + + if (!ext4_balloc_verify_bitmap_csum(sb, bg, b.data)) { + ext4_dbg(DEBUG_BALLOC, + DBG_WARN "Bitmap checksum failed." + "Group: %" PRIu32"\n", + bg_ref.index); + } + + /* Check if goal is free */ + if (ext4_bmap_is_bit_clr(b.data, idx_in_bg)) { + ext4_bmap_bit_set(b.data, idx_in_bg); + ext4_balloc_set_bitmap_csum(sb, bg_ref.block_group, + b.data); + ext4_trans_set_block_dirty(b.buf); + r = ext4_block_set(inode_ref->fs->bdev, &b); + if (r != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return r; + } + + alloc = ext4_fs_bg_idx_to_addr(sb, idx_in_bg, bg_id); + goto success; + } + + uint32_t blk_in_bg = ext4_blocks_in_group_cnt(sb, bg_id); + + uint32_t end_idx = (idx_in_bg + 63) & ~63; + if (end_idx > blk_in_bg) + end_idx = blk_in_bg; + + /* Try to find free block near to goal */ + uint32_t tmp_idx; + for (tmp_idx = idx_in_bg + 1; tmp_idx < end_idx; ++tmp_idx) { + if (ext4_bmap_is_bit_clr(b.data, tmp_idx)) { + ext4_bmap_bit_set(b.data, tmp_idx); + + ext4_balloc_set_bitmap_csum(sb, bg, b.data); + ext4_trans_set_block_dirty(b.buf); + r = ext4_block_set(inode_ref->fs->bdev, &b); + if (r != EOK) + return r; + + alloc = ext4_fs_bg_idx_to_addr(sb, tmp_idx, bg_id); + goto success; + } + } + + /* Find free bit in bitmap */ + r = ext4_bmap_bit_find_clr(b.data, idx_in_bg, blk_in_bg, &rel_blk_idx); + if (r == EOK) { + ext4_bmap_bit_set(b.data, rel_blk_idx); + ext4_balloc_set_bitmap_csum(sb, bg_ref.block_group, b.data); + ext4_trans_set_block_dirty(b.buf); + r = ext4_block_set(inode_ref->fs->bdev, &b); + if (r != EOK) + return r; + + alloc = ext4_fs_bg_idx_to_addr(sb, rel_blk_idx, bg_id); + goto success; + } + + /* No free block found yet */ + r = ext4_block_set(inode_ref->fs->bdev, &b); + if (r != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return r; + } + +goal_failed: + + r = ext4_fs_put_block_group_ref(&bg_ref); + if (r != EOK) + return r; + + /* Try other block groups */ + uint32_t block_group_count = ext4_block_group_cnt(sb); + uint32_t bgid = (bg_id + 1) % block_group_count; + uint32_t count = block_group_count; + + while (count > 0) { + r = ext4_fs_get_block_group_ref(inode_ref->fs, bgid, &bg_ref); + if (r != EOK) + return r; + + struct ext4_bgroup *bg = bg_ref.block_group; + free_blocks = ext4_bg_get_free_blocks_count(bg, sb); + if (free_blocks == 0) { + /* This group has no free blocks */ + goto next_group; + } + + /* Load block with bitmap */ + bmp_blk_adr = ext4_bg_get_block_bitmap(bg, sb); + r = ext4_trans_block_get(inode_ref->fs->bdev, &b, bmp_blk_adr); + if (r != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return r; + } + + if (!ext4_balloc_verify_bitmap_csum(sb, bg, b.data)) { + ext4_dbg(DEBUG_BALLOC, + DBG_WARN "Bitmap checksum failed." + "Group: %" PRIu32"\n", + bg_ref.index); + } + + /* Compute indexes */ + first_in_bg = ext4_balloc_get_block_of_bgid(sb, bgid); + idx_in_bg = ext4_fs_addr_to_idx_bg(sb, first_in_bg); + blk_in_bg = ext4_blocks_in_group_cnt(sb, bgid); + first_in_bg_index = ext4_fs_addr_to_idx_bg(sb, first_in_bg); + + if (idx_in_bg < first_in_bg_index) + idx_in_bg = first_in_bg_index; + + r = ext4_bmap_bit_find_clr(b.data, idx_in_bg, blk_in_bg, + &rel_blk_idx); + if (r == EOK) { + ext4_bmap_bit_set(b.data, rel_blk_idx); + ext4_balloc_set_bitmap_csum(sb, bg, b.data); + ext4_trans_set_block_dirty(b.buf); + r = ext4_block_set(inode_ref->fs->bdev, &b); + if (r != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return r; + } + + alloc = ext4_fs_bg_idx_to_addr(sb, rel_blk_idx, bgid); + goto success; + } + + r = ext4_block_set(inode_ref->fs->bdev, &b); + if (r != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return r; + } + + next_group: + r = ext4_fs_put_block_group_ref(&bg_ref); + if (r != EOK) { + return r; + } + + /* Goto next group */ + bgid = (bgid + 1) % block_group_count; + count--; + } + + return ENOSPC; + +success: + /* Empty command - because of syntax */ + ; + + uint32_t block_size = ext4_sb_get_block_size(sb); + + /* Update superblock free blocks count */ + uint64_t sb_free_blocks = ext4_sb_get_free_blocks_cnt(sb); + sb_free_blocks--; + ext4_sb_set_free_blocks_cnt(sb, sb_free_blocks); + + /* Update inode blocks (different block size!) count */ + uint64_t ino_blocks = ext4_inode_get_blocks_count(sb, inode_ref->inode); + ino_blocks += block_size / EXT4_INODE_BLOCK_SIZE; + ext4_inode_set_blocks_count(sb, inode_ref->inode, ino_blocks); + inode_ref->dirty = true; + + /* Update block group free blocks count */ + + uint32_t fb_cnt = ext4_bg_get_free_blocks_count(bg_ref.block_group, sb); + fb_cnt--; + ext4_bg_set_free_blocks_count(bg_ref.block_group, sb, fb_cnt); + + bg_ref.dirty = true; + r = ext4_fs_put_block_group_ref(&bg_ref); + + *fblock = alloc; + return r; +} + +int ext4_balloc_try_alloc_block(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t baddr, bool *free) +{ + int rc; + + struct ext4_fs *fs = inode_ref->fs; + struct ext4_sblock *sb = &fs->sb; + + /* Compute indexes */ + uint32_t block_group = ext4_balloc_get_bgid_of_block(sb, baddr); + uint32_t index_in_group = ext4_fs_addr_to_idx_bg(sb, baddr); + + /* Load block group reference */ + struct ext4_block_group_ref bg_ref; + rc = ext4_fs_get_block_group_ref(fs, block_group, &bg_ref); + if (rc != EOK) + return rc; + + /* Load block with bitmap */ + ext4_fsblk_t bmp_blk_addr; + bmp_blk_addr = ext4_bg_get_block_bitmap(bg_ref.block_group, sb); + + struct ext4_block b; + rc = ext4_trans_block_get(fs->bdev, &b, bmp_blk_addr); + if (rc != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + if (!ext4_balloc_verify_bitmap_csum(sb, bg_ref.block_group, b.data)) { + ext4_dbg(DEBUG_BALLOC, + DBG_WARN "Bitmap checksum failed." + "Group: %" PRIu32"\n", + bg_ref.index); + } + + /* Check if block is free */ + *free = ext4_bmap_is_bit_clr(b.data, index_in_group); + + /* Allocate block if possible */ + if (*free) { + ext4_bmap_bit_set(b.data, index_in_group); + ext4_balloc_set_bitmap_csum(sb, bg_ref.block_group, b.data); + ext4_trans_set_block_dirty(b.buf); + } + + /* Release block with bitmap */ + rc = ext4_block_set(fs->bdev, &b); + if (rc != EOK) { + /* Error in saving bitmap */ + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + /* If block is not free, return */ + if (!(*free)) + goto terminate; + + uint32_t block_size = ext4_sb_get_block_size(sb); + + /* Update superblock free blocks count */ + uint64_t sb_free_blocks = ext4_sb_get_free_blocks_cnt(sb); + sb_free_blocks--; + ext4_sb_set_free_blocks_cnt(sb, sb_free_blocks); + + /* Update inode blocks count */ + uint64_t ino_blocks = ext4_inode_get_blocks_count(sb, inode_ref->inode); + ino_blocks += block_size / EXT4_INODE_BLOCK_SIZE; + ext4_inode_set_blocks_count(sb, inode_ref->inode, ino_blocks); + inode_ref->dirty = true; + + /* Update block group free blocks count */ + uint32_t fb_cnt = ext4_bg_get_free_blocks_count(bg_ref.block_group, sb); + fb_cnt--; + ext4_bg_set_free_blocks_count(bg_ref.block_group, sb, fb_cnt); + + bg_ref.dirty = true; + +terminate: + return ext4_fs_put_block_group_ref(&bg_ref); +} + +/** + * @} + */ diff --git a/src/ext4_bcache.c b/src/ext4_bcache.c new file mode 100644 index 0000000..1a1766a --- /dev/null +++ b/src/ext4_bcache.c @@ -0,0 +1,317 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_bcache.c + * @brief Block cache allocator. + */ + +#include "ext4_config.h" +#include "ext4_bcache.h" +#include "ext4_blockdev.h" +#include "ext4_debug.h" +#include "ext4_errno.h" + +#include <string.h> +#include <stdlib.h> + +static int ext4_bcache_lba_compare(struct ext4_buf *a, struct ext4_buf *b) +{ + if (a->lba > b->lba) + return 1; + else if (a->lba < b->lba) + return -1; + return 0; +} + +static int ext4_bcache_lru_compare(struct ext4_buf *a, struct ext4_buf *b) +{ + if (a->lru_id > b->lru_id) + return 1; + else if (a->lru_id < b->lru_id) + return -1; + return 0; +} + +RB_GENERATE_INTERNAL(ext4_buf_lba, ext4_buf, lba_node, + ext4_bcache_lba_compare, static inline) +RB_GENERATE_INTERNAL(ext4_buf_lru, ext4_buf, lru_node, + ext4_bcache_lru_compare, static inline) + +int ext4_bcache_init_dynamic(struct ext4_bcache *bc, uint32_t cnt, + uint32_t itemsize) +{ + ext4_assert(bc && cnt && itemsize); + + memset(bc, 0, sizeof(struct ext4_bcache)); + + bc->cnt = cnt; + bc->itemsize = itemsize; + bc->ref_blocks = 0; + bc->max_ref_blocks = 0; + + return EOK; +} + +void ext4_bcache_cleanup(struct ext4_bcache *bc) +{ + struct ext4_buf *buf, *tmp; + RB_FOREACH_SAFE(buf, ext4_buf_lba, &bc->lba_root, tmp) { + ext4_block_flush_buf(bc->bdev, buf); + ext4_bcache_drop_buf(bc, buf); + } +} + +int ext4_bcache_fini_dynamic(struct ext4_bcache *bc) +{ + memset(bc, 0, sizeof(struct ext4_bcache)); + return EOK; +} + +/**@brief: + * + * This is ext4_bcache, the module handling basic buffer-cache stuff. + * + * Buffers in a bcache are sorted by their LBA and stored in a + * RB-Tree(lba_root). + * + * Bcache also maintains another RB-Tree(lru_root) right now, where + * buffers are sorted by their LRU id. + * + * A singly-linked list is used to track those dirty buffers which are + * ready to be flushed. (Those buffers which are dirty but also referenced + * are not considered ready to be flushed.) + * + * When a buffer is not referenced, it will be stored in both lba_root + * and lru_root, while it will only be stored in lba_root when it is + * referenced. + */ + +static struct ext4_buf * +ext4_buf_alloc(struct ext4_bcache *bc, uint64_t lba) +{ + void *data; + struct ext4_buf *buf; + data = malloc(bc->itemsize); + if (!data) + return NULL; + + buf = calloc(1, sizeof(struct ext4_buf)); + if (!buf) { + free(data); + return NULL; + } + + buf->lba = lba; + buf->data = data; + buf->bc = bc; + return buf; +} + +static void ext4_buf_free(struct ext4_buf *buf) +{ + free(buf->data); + free(buf); +} + +static struct ext4_buf * +ext4_buf_lookup(struct ext4_bcache *bc, uint64_t lba) +{ + struct ext4_buf tmp = { + .lba = lba + }; + + return RB_FIND(ext4_buf_lba, &bc->lba_root, &tmp); +} + +struct ext4_buf *ext4_buf_lowest_lru(struct ext4_bcache *bc) +{ + return RB_MIN(ext4_buf_lru, &bc->lru_root); +} + +void ext4_bcache_drop_buf(struct ext4_bcache *bc, struct ext4_buf *buf) +{ + /* Warn on dropping any referenced buffers.*/ + if (buf->refctr) { + ext4_dbg(DEBUG_BCACHE, DBG_WARN "Buffer is still referenced. " + "lba: %" PRIu64 ", refctr: %" PRIu32 "\n", + buf->lba, buf->refctr); + } else + RB_REMOVE(ext4_buf_lru, &bc->lru_root, buf); + + RB_REMOVE(ext4_buf_lba, &bc->lba_root, buf); + + /*Forcibly drop dirty buffer.*/ + if (ext4_bcache_test_flag(buf, BC_DIRTY)) + ext4_bcache_remove_dirty_node(bc, buf); + + ext4_buf_free(buf); + bc->ref_blocks--; +} + +void ext4_bcache_invalidate_lba(struct ext4_bcache *bc, + uint64_t from, + uint32_t cnt) +{ + uint64_t end = from + cnt - 1; + struct ext4_buf *tmp = ext4_buf_lookup(bc, from), *buf; + RB_FOREACH_FROM(buf, ext4_buf_lba, tmp) { + if (buf->lba > end) + break; + + /* Clear both dirty and up-to-date flags. */ + if (ext4_bcache_test_flag(buf, BC_DIRTY)) + ext4_bcache_remove_dirty_node(bc, buf); + + buf->end_write = NULL; + buf->end_write_arg = NULL; + ext4_bcache_clear_dirty(buf); + } +} + +struct ext4_buf * +ext4_bcache_find_get(struct ext4_bcache *bc, struct ext4_block *b, + uint64_t lba) +{ + struct ext4_buf *buf = ext4_buf_lookup(bc, lba); + if (buf) { + /* If buffer is not referenced. */ + if (!buf->refctr) { + /* Assign new value to LRU id and increment LRU counter + * by 1*/ + buf->lru_id = ++bc->lru_ctr; + RB_REMOVE(ext4_buf_lru, &bc->lru_root, buf); + if (ext4_bcache_test_flag(buf, BC_DIRTY)) + ext4_bcache_remove_dirty_node(bc, buf); + + } + + ext4_bcache_inc_ref(buf); + + b->lb_id = lba; + b->buf = buf; + b->data = buf->data; + } + return buf; +} + +int ext4_bcache_alloc(struct ext4_bcache *bc, struct ext4_block *b, + bool *is_new) +{ + /* Try to search the buffer with exaxt LBA. */ + struct ext4_buf *buf = ext4_bcache_find_get(bc, b, b->lb_id); + if (buf) { + *is_new = false; + return EOK; + } + + /* We need to allocate one buffer.*/ + buf = ext4_buf_alloc(bc, b->lb_id); + if (!buf) + return ENOMEM; + + RB_INSERT(ext4_buf_lba, &bc->lba_root, buf); + /* One more buffer in bcache now. :-) */ + bc->ref_blocks++; + + /*Calc ref blocks max depth*/ + if (bc->max_ref_blocks < bc->ref_blocks) + bc->max_ref_blocks = bc->ref_blocks; + + + ext4_bcache_inc_ref(buf); + /* Assign new value to LRU id and increment LRU counter + * by 1*/ + buf->lru_id = ++bc->lru_ctr; + + b->buf = buf; + b->data = buf->data; + + *is_new = true; + return EOK; +} + +int ext4_bcache_free(struct ext4_bcache *bc, struct ext4_block *b) +{ + struct ext4_buf *buf = b->buf; + + ext4_assert(bc && b); + + /*Check if valid.*/ + ext4_assert(b->lb_id); + + /*Block should have a valid pointer to ext4_buf.*/ + ext4_assert(buf); + + /*Check if someone don't try free unreferenced block cache.*/ + ext4_assert(buf->refctr); + + /*Just decrease reference counter*/ + ext4_bcache_dec_ref(buf); + + /* We are the last one touching this buffer, do the cleanups. */ + if (!buf->refctr) { + RB_INSERT(ext4_buf_lru, &bc->lru_root, buf); + /* This buffer is ready to be flushed. */ + if (ext4_bcache_test_flag(buf, BC_DIRTY) && + ext4_bcache_test_flag(buf, BC_UPTODATE)) { + if (bc->bdev->cache_write_back && + !ext4_bcache_test_flag(buf, BC_FLUSH) && + !ext4_bcache_test_flag(buf, BC_TMP)) + ext4_bcache_insert_dirty_node(bc, buf); + else { + ext4_block_flush_buf(bc->bdev, buf); + ext4_bcache_clear_flag(buf, BC_FLUSH); + } + } + + /* The buffer is invalidated...drop it. */ + if (!ext4_bcache_test_flag(buf, BC_UPTODATE) || + ext4_bcache_test_flag(buf, BC_TMP)) + ext4_bcache_drop_buf(bc, buf); + + } + + b->lb_id = 0; + b->data = 0; + + return EOK; +} + +bool ext4_bcache_is_full(struct ext4_bcache *bc) +{ + return (bc->cnt <= bc->ref_blocks); +} + + +/** + * @} + */ diff --git a/src/ext4_bitmap.c b/src/ext4_bitmap.c new file mode 100644 index 0000000..1320033 --- /dev/null +++ b/src/ext4_bitmap.c @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_bitmap.c + * @brief Block/inode bitmap allocator. + */ + +#include "ext4_config.h" +#include "ext4_bitmap.h" + +#include "ext4_errno.h" + +void ext4_bmap_bits_free(uint8_t *bmap, uint32_t sbit, uint32_t bcnt) +{ + uint32_t i = sbit; + + while (i & 7) { + + if (!bcnt) + return; + + ext4_bmap_bit_clr(bmap, i); + + bcnt--; + i++; + } + sbit = i; + bmap += (sbit >> 3); + + while (bcnt >= 32) { + *(uint32_t *)bmap = 0; + bmap += 4; + bcnt -= 32; + sbit += 32; + } + + while (bcnt >= 16) { + *(uint16_t *)bmap = 0; + bmap += 2; + bcnt -= 16; + sbit += 16; + } + + while (bcnt >= 8) { + *bmap = 0; + bmap += 1; + bcnt -= 8; + sbit += 8; + } + + for (i = 0; i < bcnt; ++i) { + ext4_bmap_bit_clr(bmap, i); + } +} + +int ext4_bmap_bit_find_clr(uint8_t *bmap, uint32_t sbit, uint32_t ebit, + uint32_t *bit_id) +{ + uint32_t i; + uint32_t bcnt = ebit - sbit; + + i = sbit; + + while (i & 7) { + + if (!bcnt) + return ENOSPC; + + if (ext4_bmap_is_bit_clr(bmap, i)) { + *bit_id = sbit; + return EOK; + } + + i++; + bcnt--; + } + + sbit = i; + bmap += (sbit >> 3); + + while (bcnt >= 32) { + if (*(uint32_t *)bmap != 0xFFFFFFFF) + goto finish_it; + + bmap += 4; + bcnt -= 32; + sbit += 32; + } + + while (bcnt >= 16) { + if (*(uint16_t *)bmap != 0xFFFF) + goto finish_it; + + bmap += 2; + bcnt -= 16; + sbit += 16; + } + +finish_it: + while (bcnt >= 8) { + if (*bmap != 0xFF) { + for (i = 0; i < 8; ++i) { + if (ext4_bmap_is_bit_clr(bmap, i)) { + *bit_id = sbit + i; + return EOK; + } + } + } + + bmap += 1; + bcnt -= 8; + sbit += 8; + } + + for (i = 0; i < bcnt; ++i) { + if (ext4_bmap_is_bit_clr(bmap, i)) { + *bit_id = sbit + i; + return EOK; + } + } + + return ENOSPC; +} + +/** + * @} + */ diff --git a/src/ext4_block_group.c b/src/ext4_block_group.c new file mode 100644 index 0000000..7f068b9 --- /dev/null +++ b/src/ext4_block_group.c @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_block_group.c + * @brief Block group function set. + */ + +#include "ext4_config.h" +#include "ext4_block_group.h" + +/**@brief CRC-16 look up table*/ +static uint16_t const crc16_tab[256] = { + 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, 0xC601, + 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, 0xCC01, 0x0CC0, + 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, 0x0A00, 0xCAC1, 0xCB81, + 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, 0xD801, 0x18C0, 0x1980, 0xD941, + 0x1B00, 0xDBC1, 0xDA81, 0x1A40, 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, + 0x1DC0, 0x1C80, 0xDC41, 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, + 0x1680, 0xD641, 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, + 0x1040, 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, + 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, 0x3C00, + 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, 0xFA01, 0x3AC0, + 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, 0x2800, 0xE8C1, 0xE981, + 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, 0xEE01, 0x2EC0, 0x2F80, 0xEF41, + 0x2D00, 0xEDC1, 0xEC81, 0x2C40, 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, + 0xE7C1, 0xE681, 0x2640, 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, + 0x2080, 0xE041, 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, + 0x6240, 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, + 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, 0xAA01, + 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, 0x7800, 0xB8C1, + 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, 0xBE01, 0x7EC0, 0x7F80, + 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, 0xB401, 0x74C0, 0x7580, 0xB541, + 0x7700, 0xB7C1, 0xB681, 0x7640, 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, + 0x71C0, 0x7080, 0xB041, 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, + 0x5280, 0x9241, 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, + 0x5440, 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, + 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, 0x8801, + 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, 0x4E00, 0x8EC1, + 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, 0x4400, 0x84C1, 0x8581, + 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, 0x8201, 0x42C0, 0x4380, 0x8341, + 0x4100, 0x81C1, 0x8081, 0x4040}; + +uint16_t ext4_bg_crc16(uint16_t crc, const uint8_t *buffer, size_t len) +{ + while (len--) + + crc = (((crc >> 8) & 0xffU) ^ + crc16_tab[(crc ^ *buffer++) & 0xffU]) & + 0x0000ffffU; + return crc; +} + +/** + * @} + */ diff --git a/src/ext4_blockdev.c b/src/ext4_blockdev.c new file mode 100644 index 0000000..23ad9ee --- /dev/null +++ b/src/ext4_blockdev.c @@ -0,0 +1,463 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_blockdev.c + * @brief Block device module. + */ + +#include "ext4_config.h" +#include "ext4_blockdev.h" +#include "ext4_errno.h" +#include "ext4_debug.h" + +#include <string.h> +#include <stdlib.h> + +static void ext4_bdif_lock(struct ext4_blockdev *bdev) +{ + if (!bdev->bdif->lock) + return; + + int r = bdev->bdif->lock(bdev); + ext4_assert(r == EOK); +} + +static void ext4_bdif_unlock(struct ext4_blockdev *bdev) +{ + if (!bdev->bdif->unlock) + return; + + int r = bdev->bdif->unlock(bdev); + ext4_assert(r == EOK); +} + +static int ext4_bdif_bread(struct ext4_blockdev *bdev, void *buf, + uint64_t blk_id, uint32_t blk_cnt) +{ + ext4_bdif_lock(bdev); + int r = bdev->bdif->bread(bdev, buf, blk_id, blk_cnt); + bdev->bdif->bread_ctr++; + ext4_bdif_unlock(bdev); + return r; +} + +static int ext4_bdif_bwrite(struct ext4_blockdev *bdev, const void *buf, + uint64_t blk_id, uint32_t blk_cnt) +{ + ext4_bdif_lock(bdev); + int r = bdev->bdif->bwrite(bdev, buf, blk_id, blk_cnt); + bdev->bdif->bwrite_ctr++; + ext4_bdif_unlock(bdev); + return r; +} + +int ext4_block_init(struct ext4_blockdev *bdev) +{ + int rc; + ext4_assert(bdev); + ext4_assert(bdev->bdif); + ext4_assert(bdev->bdif->open && + bdev->bdif->close && + bdev->bdif->bread && + bdev->bdif->bwrite); + + if (bdev->bdif->ph_refctr) { + bdev->bdif->ph_refctr++; + return EOK; + } + + /*Low level block init*/ + rc = bdev->bdif->open(bdev); + if (rc != EOK) + return rc; + + bdev->bdif->ph_refctr = 1; + return EOK; +} + +int ext4_block_bind_bcache(struct ext4_blockdev *bdev, struct ext4_bcache *bc) +{ + ext4_assert(bdev && bc); + bdev->bc = bc; + bc->bdev = bdev; + return EOK; +} + +void ext4_block_set_lb_size(struct ext4_blockdev *bdev, uint64_t lb_bsize) +{ + /*Logical block size has to be multiply of physical */ + ext4_assert(!(lb_bsize % bdev->bdif->ph_bsize)); + + bdev->lg_bsize = lb_bsize; + bdev->lg_bcnt = bdev->part_size / lb_bsize; +} + +int ext4_block_fini(struct ext4_blockdev *bdev) +{ + ext4_assert(bdev); + + if (!bdev->bdif->ph_refctr) + return EOK; + + bdev->bdif->ph_refctr--; + if (bdev->bdif->ph_refctr) + return EOK; + + /*Low level block fini*/ + return bdev->bdif->close(bdev); +} + +int ext4_block_flush_buf(struct ext4_blockdev *bdev, struct ext4_buf *buf) +{ + int r; + struct ext4_bcache *bc = bdev->bc; + + if (ext4_bcache_test_flag(buf, BC_DIRTY) && + ext4_bcache_test_flag(buf, BC_UPTODATE)) { + r = ext4_blocks_set_direct(bdev, buf->data, buf->lba, 1); + if (r) { + if (buf->end_write) { + bc->dont_shake = true; + buf->end_write(bc, buf, r, buf->end_write_arg); + bc->dont_shake = false; + } + + return r; + } + + ext4_bcache_remove_dirty_node(bc, buf); + ext4_bcache_clear_flag(buf, BC_DIRTY); + if (buf->end_write) { + bc->dont_shake = true; + buf->end_write(bc, buf, r, buf->end_write_arg); + bc->dont_shake = false; + } + } + return EOK; +} + +int ext4_block_flush_lba(struct ext4_blockdev *bdev, uint64_t lba) +{ + int r = EOK; + struct ext4_buf *buf; + struct ext4_block b; + buf = ext4_bcache_find_get(bdev->bc, &b, lba); + if (buf) { + r = ext4_block_flush_buf(bdev, buf); + ext4_bcache_free(bdev->bc, &b); + } + return r; +} + +int ext4_block_cache_shake(struct ext4_blockdev *bdev) +{ + int r = EOK; + struct ext4_buf *buf; + if (bdev->bc->dont_shake) + return EOK; + + while (!RB_EMPTY(&bdev->bc->lru_root) && + ext4_bcache_is_full(bdev->bc)) { + + buf = ext4_buf_lowest_lru(bdev->bc); + ext4_assert(buf); + if (ext4_bcache_test_flag(buf, BC_DIRTY)) { + r = ext4_block_flush_buf(bdev, buf); + if (r != EOK) + break; + + } + + ext4_bcache_drop_buf(bdev->bc, buf); + } + return r; +} + +int ext4_block_get_noread(struct ext4_blockdev *bdev, struct ext4_block *b, + uint64_t lba) +{ + bool is_new; + int r; + + ext4_assert(bdev && b); + + if (!bdev->bdif->ph_refctr) + return EIO; + + if (!(lba < bdev->lg_bcnt)) + return ERANGE; + + b->lb_id = lba; + + /*If cache is full we have to (flush and) drop it anyway :(*/ + r = ext4_block_cache_shake(bdev); + if (r != EOK) + return r; + + r = ext4_bcache_alloc(bdev->bc, b, &is_new); + if (r != EOK) + return r; + + if (!b->data) + return ENOMEM; + + return EOK; +} + +int ext4_block_get(struct ext4_blockdev *bdev, struct ext4_block *b, + uint64_t lba) +{ + int r = ext4_block_get_noread(bdev, b, lba); + if (r != EOK) + return r; + + if (ext4_bcache_test_flag(b->buf, BC_UPTODATE)) { + /* Data in the cache is up-to-date. + * Reading from physical device is not required */ + return EOK; + } + + r = ext4_blocks_get_direct(bdev, b->data, lba, 1); + if (r != EOK) { + ext4_bcache_free(bdev->bc, b); + b->lb_id = 0; + return r; + } + + /* Mark buffer up-to-date, since + * fresh data is read from physical device just now. */ + ext4_bcache_set_flag(b->buf, BC_UPTODATE); + return EOK; +} + +int ext4_block_set(struct ext4_blockdev *bdev, struct ext4_block *b) +{ + ext4_assert(bdev && b); + ext4_assert(b->buf); + + if (!bdev->bdif->ph_refctr) + return EIO; + + return ext4_bcache_free(bdev->bc, b); +} + +int ext4_blocks_get_direct(struct ext4_blockdev *bdev, void *buf, uint64_t lba, + uint32_t cnt) +{ + uint64_t pba; + uint32_t pb_cnt; + + ext4_assert(bdev && buf); + + pba = (lba * bdev->lg_bsize + bdev->part_offset) / bdev->bdif->ph_bsize; + pb_cnt = bdev->lg_bsize / bdev->bdif->ph_bsize; + + return ext4_bdif_bread(bdev, buf, pba, pb_cnt * cnt); +} + +int ext4_blocks_set_direct(struct ext4_blockdev *bdev, const void *buf, + uint64_t lba, uint32_t cnt) +{ + uint64_t pba; + uint32_t pb_cnt; + + ext4_assert(bdev && buf); + + pba = (lba * bdev->lg_bsize + bdev->part_offset) / bdev->bdif->ph_bsize; + pb_cnt = bdev->lg_bsize / bdev->bdif->ph_bsize; + + return ext4_bdif_bwrite(bdev, buf, pba, pb_cnt * cnt); +} + +int ext4_block_writebytes(struct ext4_blockdev *bdev, uint64_t off, + const void *buf, uint32_t len) +{ + uint64_t block_idx; + uint32_t blen; + uint32_t unalg; + int r = EOK; + + const uint8_t *p = (void *)buf; + + ext4_assert(bdev && buf); + + if (!bdev->bdif->ph_refctr) + return EIO; + + if (off + len > bdev->part_size) + return EINVAL; /*Ups. Out of range operation*/ + + block_idx = ((off + bdev->part_offset) / bdev->bdif->ph_bsize); + + /*OK lets deal with the first possible unaligned block*/ + unalg = (off & (bdev->bdif->ph_bsize - 1)); + if (unalg) { + + uint32_t wlen = (bdev->bdif->ph_bsize - unalg) > len + ? len + : (bdev->bdif->ph_bsize - unalg); + + r = ext4_bdif_bread(bdev, bdev->bdif->ph_bbuf, block_idx, 1); + if (r != EOK) + return r; + + memcpy(bdev->bdif->ph_bbuf + unalg, p, wlen); + r = ext4_bdif_bwrite(bdev, bdev->bdif->ph_bbuf, block_idx, 1); + if (r != EOK) + return r; + + p += wlen; + len -= wlen; + block_idx++; + } + + /*Aligned data*/ + blen = len / bdev->bdif->ph_bsize; + r = ext4_bdif_bwrite(bdev, p, block_idx, blen); + if (r != EOK) + return r; + + p += bdev->bdif->ph_bsize * blen; + len -= bdev->bdif->ph_bsize * blen; + + block_idx += blen; + + /*Rest of the data*/ + if (len) { + r = ext4_bdif_bread(bdev, bdev->bdif->ph_bbuf, block_idx, 1); + if (r != EOK) + return r; + + memcpy(bdev->bdif->ph_bbuf, p, len); + r = ext4_bdif_bwrite(bdev, bdev->bdif->ph_bbuf, block_idx, 1); + if (r != EOK) + return r; + } + + return r; +} + +int ext4_block_readbytes(struct ext4_blockdev *bdev, uint64_t off, void *buf, + uint32_t len) +{ + uint64_t block_idx; + uint32_t blen; + uint32_t unalg; + int r = EOK; + + uint8_t *p = (void *)buf; + + ext4_assert(bdev && buf); + + if (!bdev->bdif->ph_refctr) + return EIO; + + if (off + len > bdev->part_size) + return EINVAL; /*Ups. Out of range operation*/ + + block_idx = ((off + bdev->part_offset) / bdev->bdif->ph_bsize); + + /*OK lets deal with the first possible unaligned block*/ + unalg = (off & (bdev->bdif->ph_bsize - 1)); + if (unalg) { + + uint32_t rlen = (bdev->bdif->ph_bsize - unalg) > len + ? len + : (bdev->bdif->ph_bsize - unalg); + + r = ext4_bdif_bread(bdev, bdev->bdif->ph_bbuf, block_idx, 1); + if (r != EOK) + return r; + + memcpy(p, bdev->bdif->ph_bbuf + unalg, rlen); + + p += rlen; + len -= rlen; + block_idx++; + } + + /*Aligned data*/ + blen = len / bdev->bdif->ph_bsize; + + r = ext4_bdif_bread(bdev, p, block_idx, blen); + if (r != EOK) + return r; + + p += bdev->bdif->ph_bsize * blen; + len -= bdev->bdif->ph_bsize * blen; + + block_idx += blen; + + /*Rest of the data*/ + if (len) { + r = ext4_bdif_bread(bdev, bdev->bdif->ph_bbuf, block_idx, 1); + if (r != EOK) + return r; + + memcpy(p, bdev->bdif->ph_bbuf, len); + } + + return r; +} + +int ext4_block_cache_flush(struct ext4_blockdev *bdev) +{ + while (!SLIST_EMPTY(&bdev->bc->dirty_list)) { + int r; + struct ext4_buf *buf = SLIST_FIRST(&bdev->bc->dirty_list); + ext4_assert(buf); + r = ext4_block_flush_buf(bdev, buf); + if (r != EOK) + return r; + + } + return EOK; +} + +int ext4_block_cache_write_back(struct ext4_blockdev *bdev, uint8_t on_off) +{ + if (on_off) + bdev->cache_write_back++; + + if (!on_off && bdev->cache_write_back) + bdev->cache_write_back--; + + if (bdev->cache_write_back) + return EOK; + + /*Flush data in all delayed cache blocks*/ + return ext4_block_cache_flush(bdev); +} + +/** + * @} + */ diff --git a/src/ext4_crc32.c b/src/ext4_crc32.c new file mode 100644 index 0000000..a6ee6b0 --- /dev/null +++ b/src/ext4_crc32.c @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2014 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Based on FreeBSD. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_crc32c.c + * @brief Crc32c routine. Taken from FreeBSD kernel. + */ + +#include "ext4_config.h" +#include "ext4_crc32.h" + +static const uint32_t crc32_tab[] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; + +/* */ +/* CRC LOOKUP TABLE */ +/* ================ */ +/* The following CRC lookup table was generated automagically */ +/* by the Rocksoft^tm Model CRC Algorithm Table Generation */ +/* Program V1.0 using the following model parameters: */ +/* */ +/* Width : 4 bytes. */ +/* Poly : 0x1EDC6F41L */ +/* Reverse : TRUE. */ +/* */ +/* For more information on the Rocksoft^tm Model CRC Algorithm, */ +/* see the document titled "A Painless Guide to CRC Error */ +/* Detection Algorithms" by Ross Williams */ +/* (ross@guest.adelaide.edu.au.). This document is likely to be */ +/* in the FTP archive "ftp.adelaide.edu.au/pub/rocksoft". */ +/* */ +static const uint32_t crc32c_tab[256] = { + 0x00000000L, 0xF26B8303L, 0xE13B70F7L, 0x1350F3F4L, 0xC79A971FL, + 0x35F1141CL, 0x26A1E7E8L, 0xD4CA64EBL, 0x8AD958CFL, 0x78B2DBCCL, + 0x6BE22838L, 0x9989AB3BL, 0x4D43CFD0L, 0xBF284CD3L, 0xAC78BF27L, + 0x5E133C24L, 0x105EC76FL, 0xE235446CL, 0xF165B798L, 0x030E349BL, + 0xD7C45070L, 0x25AFD373L, 0x36FF2087L, 0xC494A384L, 0x9A879FA0L, + 0x68EC1CA3L, 0x7BBCEF57L, 0x89D76C54L, 0x5D1D08BFL, 0xAF768BBCL, + 0xBC267848L, 0x4E4DFB4BL, 0x20BD8EDEL, 0xD2D60DDDL, 0xC186FE29L, + 0x33ED7D2AL, 0xE72719C1L, 0x154C9AC2L, 0x061C6936L, 0xF477EA35L, + 0xAA64D611L, 0x580F5512L, 0x4B5FA6E6L, 0xB93425E5L, 0x6DFE410EL, + 0x9F95C20DL, 0x8CC531F9L, 0x7EAEB2FAL, 0x30E349B1L, 0xC288CAB2L, + 0xD1D83946L, 0x23B3BA45L, 0xF779DEAEL, 0x05125DADL, 0x1642AE59L, + 0xE4292D5AL, 0xBA3A117EL, 0x4851927DL, 0x5B016189L, 0xA96AE28AL, + 0x7DA08661L, 0x8FCB0562L, 0x9C9BF696L, 0x6EF07595L, 0x417B1DBCL, + 0xB3109EBFL, 0xA0406D4BL, 0x522BEE48L, 0x86E18AA3L, 0x748A09A0L, + 0x67DAFA54L, 0x95B17957L, 0xCBA24573L, 0x39C9C670L, 0x2A993584L, + 0xD8F2B687L, 0x0C38D26CL, 0xFE53516FL, 0xED03A29BL, 0x1F682198L, + 0x5125DAD3L, 0xA34E59D0L, 0xB01EAA24L, 0x42752927L, 0x96BF4DCCL, + 0x64D4CECFL, 0x77843D3BL, 0x85EFBE38L, 0xDBFC821CL, 0x2997011FL, + 0x3AC7F2EBL, 0xC8AC71E8L, 0x1C661503L, 0xEE0D9600L, 0xFD5D65F4L, + 0x0F36E6F7L, 0x61C69362L, 0x93AD1061L, 0x80FDE395L, 0x72966096L, + 0xA65C047DL, 0x5437877EL, 0x4767748AL, 0xB50CF789L, 0xEB1FCBADL, + 0x197448AEL, 0x0A24BB5AL, 0xF84F3859L, 0x2C855CB2L, 0xDEEEDFB1L, + 0xCDBE2C45L, 0x3FD5AF46L, 0x7198540DL, 0x83F3D70EL, 0x90A324FAL, + 0x62C8A7F9L, 0xB602C312L, 0x44694011L, 0x5739B3E5L, 0xA55230E6L, + 0xFB410CC2L, 0x092A8FC1L, 0x1A7A7C35L, 0xE811FF36L, 0x3CDB9BDDL, + 0xCEB018DEL, 0xDDE0EB2AL, 0x2F8B6829L, 0x82F63B78L, 0x709DB87BL, + 0x63CD4B8FL, 0x91A6C88CL, 0x456CAC67L, 0xB7072F64L, 0xA457DC90L, + 0x563C5F93L, 0x082F63B7L, 0xFA44E0B4L, 0xE9141340L, 0x1B7F9043L, + 0xCFB5F4A8L, 0x3DDE77ABL, 0x2E8E845FL, 0xDCE5075CL, 0x92A8FC17L, + 0x60C37F14L, 0x73938CE0L, 0x81F80FE3L, 0x55326B08L, 0xA759E80BL, + 0xB4091BFFL, 0x466298FCL, 0x1871A4D8L, 0xEA1A27DBL, 0xF94AD42FL, + 0x0B21572CL, 0xDFEB33C7L, 0x2D80B0C4L, 0x3ED04330L, 0xCCBBC033L, + 0xA24BB5A6L, 0x502036A5L, 0x4370C551L, 0xB11B4652L, 0x65D122B9L, + 0x97BAA1BAL, 0x84EA524EL, 0x7681D14DL, 0x2892ED69L, 0xDAF96E6AL, + 0xC9A99D9EL, 0x3BC21E9DL, 0xEF087A76L, 0x1D63F975L, 0x0E330A81L, + 0xFC588982L, 0xB21572C9L, 0x407EF1CAL, 0x532E023EL, 0xA145813DL, + 0x758FE5D6L, 0x87E466D5L, 0x94B49521L, 0x66DF1622L, 0x38CC2A06L, + 0xCAA7A905L, 0xD9F75AF1L, 0x2B9CD9F2L, 0xFF56BD19L, 0x0D3D3E1AL, + 0x1E6DCDEEL, 0xEC064EEDL, 0xC38D26C4L, 0x31E6A5C7L, 0x22B65633L, + 0xD0DDD530L, 0x0417B1DBL, 0xF67C32D8L, 0xE52CC12CL, 0x1747422FL, + 0x49547E0BL, 0xBB3FFD08L, 0xA86F0EFCL, 0x5A048DFFL, 0x8ECEE914L, + 0x7CA56A17L, 0x6FF599E3L, 0x9D9E1AE0L, 0xD3D3E1ABL, 0x21B862A8L, + 0x32E8915CL, 0xC083125FL, 0x144976B4L, 0xE622F5B7L, 0xF5720643L, + 0x07198540L, 0x590AB964L, 0xAB613A67L, 0xB831C993L, 0x4A5A4A90L, + 0x9E902E7BL, 0x6CFBAD78L, 0x7FAB5E8CL, 0x8DC0DD8FL, 0xE330A81AL, + 0x115B2B19L, 0x020BD8EDL, 0xF0605BEEL, 0x24AA3F05L, 0xD6C1BC06L, + 0xC5914FF2L, 0x37FACCF1L, 0x69E9F0D5L, 0x9B8273D6L, 0x88D28022L, + 0x7AB90321L, 0xAE7367CAL, 0x5C18E4C9L, 0x4F48173DL, 0xBD23943EL, + 0xF36E6F75L, 0x0105EC76L, 0x12551F82L, 0xE03E9C81L, 0x34F4F86AL, + 0xC69F7B69L, 0xD5CF889DL, 0x27A40B9EL, 0x79B737BAL, 0x8BDCB4B9L, + 0x988C474DL, 0x6AE7C44EL, 0xBE2DA0A5L, 0x4C4623A6L, 0x5F16D052L, + 0xAD7D5351L}; + +static inline uint32_t crc32(uint32_t crc, const void *buf, uint32_t size, + const uint32_t *tab) +{ + const uint8_t *p = (const uint8_t *)buf; + + while (size--) + crc = tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8); + + return (crc); +} + +uint32_t ext4_crc32(uint32_t crc, const void *buf, uint32_t size) +{ + return crc32(crc, buf, size, crc32_tab); +} + +uint32_t ext4_crc32c(uint32_t crc, const void *buf, uint32_t size) +{ + return crc32(crc, buf, size, crc32c_tab); +} + +/** + * @} + */ diff --git a/src/ext4_debug.c b/src/ext4_debug.c new file mode 100644 index 0000000..b38593b --- /dev/null +++ b/src/ext4_debug.c @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_debug.c + * @brief Debug printf and assert macros. + */ + +#include "ext4_config.h" +#include "ext4_debug.h" + +#include <stdarg.h> +#include <stdio.h> + +static uint32_t debug_mask; + +void ext4_dmask_set(uint32_t m) +{ + debug_mask |= m; +} + +void ext4_dmask_clr(uint32_t m) +{ + debug_mask &= ~m; +} + +uint32_t ext4_dmask_get(void) +{ + return debug_mask; +} + + + +/** + * @} + */ diff --git a/src/ext4_dir.c b/src/ext4_dir.c new file mode 100644 index 0000000..f26dc6d --- /dev/null +++ b/src/ext4_dir.c @@ -0,0 +1,690 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_dir.h + * @brief Directory handle procedures. + */ + +#include "ext4_config.h" +#include "ext4_dir.h" +#include "ext4_dir_idx.h" +#include "ext4_crc32.h" +#include "ext4_inode.h" +#include "ext4_fs.h" + +#include <string.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_en *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 || t->reserved_zero2) + return NULL; + if (to_le16(t->rec_len) != sizeof(struct ext4_dir_entry_tail)) + return NULL; + if (t->reserved_ft != EXT4_DIRENTRY_DIR_CSUM) + return NULL; + + return t; +} + +#if CONFIG_META_CSUM_ENABLE +static uint32_t ext4_dir_csum(struct ext4_inode_ref *inode_ref, + struct ext4_dir_en *dirent, int size) +{ + uint32_t csum; + 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 */ + csum = ext4_crc32c(EXT4_CRC32_INIT, sb->uuid, sizeof(sb->uuid)); + /* Then calculate crc32 checksum against inode number + * and inode generation */ + csum = ext4_crc32c(csum, &ino_index, sizeof(ino_index)); + csum = ext4_crc32c(csum, &ino_gen, sizeof(ino_gen)); + /* Finally calculate crc32 checksum against directory entries */ + csum = ext4_crc32c(csum, dirent, size); + return csum; +} +#else +#define ext4_dir_csum(...) 0 +#endif + +bool ext4_dir_csum_verify(struct ext4_inode_ref *inode_ref, + struct ext4_dir_en *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; + } + + ptrdiff_t __unused diff = (char *)t - (char *)dirent; + uint32_t csum = ext4_dir_csum(inode_ref, dirent, diff); + if (t->checksum != to_le32(csum)) + return false; + + } +#endif + return true; +} + +void ext4_dir_init_entry_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_csum(struct ext4_inode_ref *inode_ref, + struct ext4_dir_en *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; + } + + ptrdiff_t __unused diff = (char *)t - (char *)dirent; + uint32_t csum = ext4_dir_csum(inode_ref, dirent, diff); + t->checksum = to_le32(csum); + } +} + +/**@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_dir_iter *it, + uint32_t block_size) +{ + uint32_t off_in_block = it->curr_off % block_size; + struct ext4_sblock *sb = &it->inode_ref->fs->sb; + + it->curr = NULL; + + /* Ensure proper alignment */ + if ((off_in_block % 4) != 0) + return EIO; + + /* Ensure that the core of the entry does not overflow the block */ + if (off_in_block > block_size - 8) + return EIO; + + struct ext4_dir_en *en; + en = (void *)(it->curr_blk.data + off_in_block); + + /* Ensure that the whole entry does not overflow the block */ + uint16_t length = ext4_dir_en_get_entry_len(en); + if (off_in_block + length > block_size) + return EIO; + + /* Ensure the name length is not too large */ + if (ext4_dir_en_get_name_len(sb, en) > length - 8) + return EIO; + + /* Everything OK - "publish" the entry */ + it->curr = en; + return EOK; +} + +/**@brief Seek to next valid directory entry. + * Here can be jumped to the next data block. + * @param it Initialized iterator + * @param pos Position of the next entry + * @return Error code + */ +static int ext4_dir_iterator_seek(struct ext4_dir_iter *it, uint64_t pos) +{ + struct ext4_sblock *sb = &it->inode_ref->fs->sb; + struct ext4_inode *inode = it->inode_ref->inode; + struct ext4_blockdev *bdev = it->inode_ref->fs->bdev; + uint64_t size = ext4_inode_get_size(sb, inode); + int r; + + /* The iterator is not valid until we seek to the desired position */ + it->curr = NULL; + + /* Are we at the end? */ + if (pos >= size) { + if (it->curr_blk.lb_id) { + + r = ext4_block_set(bdev, &it->curr_blk); + it->curr_blk.lb_id = 0; + if (r != EOK) + return r; + } + + it->curr_off = pos; + return EOK; + } + + /* Compute next block address */ + uint32_t block_size = ext4_sb_get_block_size(sb); + uint64_t current_blk_idx = it->curr_off / block_size; + uint32_t next_blk_idx = (uint32_t)(pos / block_size); + + /* + * If we don't have a block or are moving across block boundary, + * we need to get another block + */ + if ((it->curr_blk.lb_id == 0) || + (current_blk_idx != next_blk_idx)) { + if (it->curr_blk.lb_id) { + r = ext4_block_set(bdev, &it->curr_blk); + it->curr_blk.lb_id = 0; + + if (r != EOK) + return r; + } + + ext4_fsblk_t next_blk; + r = ext4_fs_get_inode_dblk_idx(it->inode_ref, next_blk_idx, + &next_blk, false); + if (r != EOK) + return r; + + r = ext4_trans_block_get(bdev, &it->curr_blk, next_blk); + if (r != EOK) { + it->curr_blk.lb_id = 0; + return r; + } + } + + it->curr_off = pos; + return ext4_dir_iterator_set(it, block_size); +} + +int ext4_dir_iterator_init(struct ext4_dir_iter *it, + struct ext4_inode_ref *inode_ref, uint64_t pos) +{ + it->inode_ref = inode_ref; + it->curr = 0; + it->curr_off = 0; + it->curr_blk.lb_id = 0; + + return ext4_dir_iterator_seek(it, pos); +} + +int ext4_dir_iterator_next(struct ext4_dir_iter *it) +{ + int r = EOK; + uint16_t skip; + + while (r == EOK) { + skip = ext4_dir_en_get_entry_len(it->curr); + r = ext4_dir_iterator_seek(it, it->curr_off + skip); + + if (!it->curr) + break; + /*Skip NULL referenced entry*/ + if (ext4_dir_en_get_inode(it->curr) != 0) + break; + } + + return r; +} + +int ext4_dir_iterator_fini(struct ext4_dir_iter *it) +{ + it->curr = 0; + + if (it->curr_blk.lb_id) + return ext4_block_set(it->inode_ref->fs->bdev, &it->curr_blk); + + return EOK; +} + +void ext4_dir_write_entry(struct ext4_sblock *sb, struct ext4_dir_en *en, + uint16_t entry_len, struct ext4_inode_ref *child, + const char *name, size_t name_len) +{ + /* Check maximum entry length */ + ext4_assert(entry_len <= ext4_sb_get_block_size(sb)); + + /* Set type of entry */ + switch (ext4_inode_type(sb, child->inode)) { + case EXT4_INODE_MODE_DIRECTORY: + ext4_dir_en_set_inode_type(sb, en, EXT4_DE_DIR); + break; + case EXT4_INODE_MODE_FILE: + ext4_dir_en_set_inode_type(sb, en, EXT4_DE_REG_FILE); + break; + case EXT4_INODE_MODE_SOFTLINK: + ext4_dir_en_set_inode_type(sb, en, EXT4_DE_SYMLINK); + break; + default: + /* FIXME: right now we only support 3 inode type. */ + ext4_assert(0); + } + + /* Set basic attributes */ + ext4_dir_en_set_inode(en, child->index); + ext4_dir_en_set_entry_len(en, entry_len); + ext4_dir_en_set_name_len(sb, en, (uint16_t)name_len); + + /* Write name */ + memcpy(en->name, name, name_len); +} + +int ext4_dir_add_entry(struct ext4_inode_ref *parent, const char *name, + uint32_t name_len, struct ext4_inode_ref *child) +{ + int r; + struct ext4_fs *fs = parent->fs; + struct ext4_sblock *sb = &parent->fs->sb; + +#if CONFIG_DIR_INDEX_ENABLE + /* Index adding (if allowed) */ + if ((ext4_sb_feature_com(sb, EXT4_FCOM_DIR_INDEX)) && + (ext4_inode_has_flag(parent->inode, EXT4_INODE_FLAG_INDEX))) { + r = ext4_dir_dx_add_entry(parent, child, name); + + /* Check if index is not corrupted */ + if (r != EXT4_ERR_BAD_DX_DIR) { + if (r != EOK) + return r; + + return EOK; + } + + /* Needed to clear dir index flag if corrupted */ + ext4_inode_clear_flag(parent->inode, EXT4_INODE_FLAG_INDEX); + parent->dirty = true; + } +#endif + + /* Linear algorithm */ + uint32_t iblock = 0; + ext4_fsblk_t fblock = 0; + uint32_t block_size = ext4_sb_get_block_size(sb); + uint64_t inode_size = ext4_inode_get_size(sb, parent->inode); + uint32_t total_blocks = (uint32_t)(inode_size / block_size); + + /* Find block, where is space for new entry and try to add */ + bool success = false; + for (iblock = 0; iblock < total_blocks; ++iblock) { + r = ext4_fs_get_inode_dblk_idx(parent, iblock, &fblock, false); + if (r != EOK) + return r; + + struct ext4_block block; + r = ext4_trans_block_get(fs->bdev, &block, fblock); + if (r != EOK) + return r; + + if (!ext4_dir_csum_verify(parent, (void *)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 */ + r = ext4_dir_try_insert_entry(sb, parent, &block, child, + name, name_len); + if (r == EOK) + success = true; + + r = ext4_block_set(fs->bdev, &block); + if (r != EOK) + return r; + + if (success) + return EOK; + } + + /* No free block found - needed to allocate next data block */ + + iblock = 0; + fblock = 0; + r = ext4_fs_append_inode_dblk(parent, &fblock, &iblock); + if (r != EOK) + return r; + + /* Load new block */ + struct ext4_block b; + + r = ext4_trans_block_get_noread(fs->bdev, &b, fblock); + if (r != EOK) + return r; + + /* Fill block with zeroes */ + memset(b.data, 0, block_size); + struct ext4_dir_en *blk_en = (void *)b.data; + + /* Save new block */ + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + uint16_t el = block_size - sizeof(struct ext4_dir_entry_tail); + ext4_dir_write_entry(sb, blk_en, el, child, name, name_len); + ext4_dir_init_entry_tail(EXT4_DIRENT_TAIL(b.data, block_size)); + } else { + ext4_dir_write_entry(sb, blk_en, block_size, child, name, + name_len); + } + + ext4_dir_set_csum(parent, (void *)b.data); + ext4_trans_set_block_dirty(b.buf); + r = ext4_block_set(fs->bdev, &b); + + return r; +} + +int ext4_dir_find_entry(struct ext4_dir_search_result *result, + struct ext4_inode_ref *parent, const char *name, + uint32_t name_len) +{ + int r; + 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_feature_com(sb, EXT4_FCOM_DIR_INDEX)) && + (ext4_inode_has_flag(parent->inode, EXT4_INODE_FLAG_INDEX))) { + r = ext4_dir_dx_find_entry(result, parent, name_len, name); + /* Check if index is not corrupted */ + if (r != EXT4_ERR_BAD_DX_DIR) { + if (r != EOK) + return r; + + return EOK; + } + + /* Needed to clear dir index flag if corrupted */ + ext4_inode_clear_flag(parent->inode, EXT4_INODE_FLAG_INDEX); + parent->dirty = true; + } +#endif + + /* Linear algorithm */ + + uint32_t iblock; + ext4_fsblk_t fblock; + uint32_t block_size = ext4_sb_get_block_size(sb); + uint64_t inode_size = ext4_inode_get_size(sb, parent->inode); + uint32_t total_blocks = (uint32_t)(inode_size / block_size); + + /* Walk through all data blocks */ + for (iblock = 0; iblock < total_blocks; ++iblock) { + /* Load block address */ + r = ext4_fs_get_inode_dblk_idx(parent, iblock, &fblock, false); + if (r != EOK) + return r; + + /* Load data block */ + struct ext4_block b; + r = ext4_trans_block_get(parent->fs->bdev, &b, fblock); + if (r != EOK) + return r; + + if (!ext4_dir_csum_verify(parent, (void *)b.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_dir_en *res_entry; + r = ext4_dir_find_in_block(&b, sb, name_len, name, &res_entry); + if (r == EOK) { + result->block = b; + result->dentry = res_entry; + return EOK; + } + + /* Entry not found - put block and continue to the next block */ + + r = ext4_block_set(parent->fs->bdev, &b); + if (r != EOK) + return r; + } + + return ENOENT; +} + +int ext4_dir_remove_entry(struct ext4_inode_ref *parent, const char *name, + uint32_t name_len) +{ + struct ext4_sblock *sb = &parent->fs->sb; + /* Check if removing from directory */ + if (!ext4_inode_is_type(sb, parent->inode, EXT4_INODE_MODE_DIRECTORY)) + return ENOTDIR; + + /* Try to find entry */ + struct ext4_dir_search_result result; + int rc = ext4_dir_find_entry(&result, parent, name, name_len); + if (rc != EOK) + return rc; + + /* Invalidate entry */ + ext4_dir_en_set_inode(result.dentry, 0); + + /* Store entry position in block */ + uint32_t pos = (uint8_t *)result.dentry - result.block.data; + + /* + * If entry is not the first in block, it must be merged + * with previous entry + */ + if (pos != 0) { + uint32_t offset = 0; + + /* Start from the first entry in block */ + struct ext4_dir_en *tmp_de =(void *)result.block.data; + uint16_t de_len = ext4_dir_en_get_entry_len(tmp_de); + + /* Find direct predecessor of removed entry */ + while ((offset + de_len) < pos) { + offset += ext4_dir_en_get_entry_len(tmp_de); + tmp_de = (void *)(result.block.data + offset); + de_len = ext4_dir_en_get_entry_len(tmp_de); + } + + ext4_assert(de_len + offset == pos); + + /* Add to removed entry length to predecessor's length */ + uint16_t del_len; + del_len = ext4_dir_en_get_entry_len(result.dentry); + ext4_dir_en_set_entry_len(tmp_de, de_len + del_len); + } + + ext4_dir_set_csum(parent, + (struct ext4_dir_en *)result.block.data); + ext4_trans_set_block_dirty(result.block.buf); + + 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 *dst_blk, + struct ext4_inode_ref *child, const char *name, + uint32_t name_len) +{ + /* 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_dir_entry) + name_len; + + if ((required_len % 4) != 0) + required_len += 4 - (required_len % 4); + + /* Initialize pointers, stop means to upper bound */ + struct ext4_dir_en *start = (void *)dst_blk->data; + struct ext4_dir_en *stop = (void *)(dst_blk->data + block_size); + + /* + * Walk through the block and check for invalid entries + * or entries with free space for new entry + */ + while (start < stop) { + uint32_t inode = ext4_dir_en_get_inode(start); + uint16_t rec_len = ext4_dir_en_get_entry_len(start); + uint8_t itype = ext4_dir_en_get_inode_type(sb, start); + + /* If invalid and large enough entry, use it */ + if ((inode == 0) && (itype != EXT4_DIRENTRY_DIR_CSUM) && + (rec_len >= required_len)) { + ext4_dir_write_entry(sb, start, rec_len, child, name, + name_len); + ext4_dir_set_csum(inode_ref, (void *)dst_blk->data); + ext4_trans_set_block_dirty(dst_blk->buf); + + return EOK; + } + + /* Valid entry, try to split it */ + if (inode != 0) { + uint16_t used_len; + used_len = ext4_dir_en_get_name_len(sb, start); + + uint16_t sz; + sz = sizeof(struct ext4_fake_dir_entry) + used_len; + + if ((used_len % 4) != 0) + sz += 4 - (used_len % 4); + + uint16_t free_space = rec_len - sz; + + /* There is free space for new entry */ + if (free_space >= required_len) { + /* Cut tail of current entry */ + struct ext4_dir_en * new_entry; + new_entry = (void *)((uint8_t *)start + sz); + ext4_dir_en_set_entry_len(start, sz); + ext4_dir_write_entry(sb, new_entry, free_space, + child, name, name_len); + + ext4_dir_set_csum(inode_ref, + (void *)dst_blk->data); + ext4_trans_set_block_dirty(dst_blk->buf); + return EOK; + } + } + + /* Jump to the next entry */ + start = (void *)((uint8_t *)start + rec_len); + } + + /* No free space found for new entry */ + return ENOSPC; +} + +int ext4_dir_find_in_block(struct ext4_block *block, struct ext4_sblock *sb, + size_t name_len, const char *name, + struct ext4_dir_en **res_entry) +{ + /* Start from the first entry in block */ + struct ext4_dir_en *de = (struct ext4_dir_en *)block->data; + + /* Set upper bound for cycling */ + uint8_t *addr_limit = block->data + ext4_sb_get_block_size(sb); + + /* Walk through the block and check entries */ + while ((uint8_t *)de < addr_limit) { + /* Termination condition */ + if ((uint8_t *)de + name_len > addr_limit) + break; + + /* Valid entry - check it */ + if (ext4_dir_en_get_inode(de) != 0) { + /* For more efficient compare only lengths firstly*/ + uint16_t el = ext4_dir_en_get_name_len(sb, de); + if (el == name_len) { + /* Compare names */ + if (memcmp(name, de->name, name_len) == 0) { + *res_entry = de; + return EOK; + } + } + } + + uint16_t de_len = ext4_dir_en_get_entry_len(de); + + /* Corrupted entry */ + if (de_len == 0) + return EINVAL; + + /* Jump to next entry */ + de = (struct ext4_dir_en *)((uint8_t *)de + de_len); + } + + /* Entry not found */ + return ENOENT; +} + +int ext4_dir_destroy_result(struct ext4_inode_ref *parent, + struct ext4_dir_search_result *result) +{ + if (result->block.lb_id) + return ext4_block_set(parent->fs->bdev, &result->block); + + return EOK; +} + +/** + * @} + */ diff --git a/src/ext4_dir_idx.c b/src/ext4_dir_idx.c new file mode 100644 index 0000000..81da0de --- /dev/null +++ b/src/ext4_dir_idx.c @@ -0,0 +1,1439 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_dir_idx.c + * @brief Directory indexing procedures. + */ + +#include "ext4_config.h" +#include "ext4_dir_idx.h" +#include "ext4_dir.h" +#include "ext4_blockdev.h" +#include "ext4_fs.h" +#include "ext4_super.h" +#include "ext4_inode.h" +#include "ext4_crc32.h" +#include "ext4_hash.h" + +#include <string.h> +#include <stdlib.h> + +/**@brief Get hash version used in directory index. + * @param root_info Pointer to root info structure of index + * @return Hash algorithm version + */ +static inline uint8_t +ext4_dir_dx_rinfo_get_hash_version(struct ext4_dir_idx_rinfo *ri) +{ + return ri->hash_version; +} + +/**@brief Set hash version, that will be used in directory index. + * @param root_info Pointer to root info structure of index + * @param v Hash algorithm version + */ +static inline void +ext4_dir_dx_rinfo_set_hash_version(struct ext4_dir_idx_rinfo *ri, uint8_t v) +{ + ri->hash_version = v; +} + +/**@brief Get length of root_info structure in bytes. + * @param root_info Pointer to root info structure of index + * @return Length of the structure + */ +static inline uint8_t +ext4_dir_dx_rinfo_get_info_length(struct ext4_dir_idx_rinfo *ri) +{ + return ri->info_length; +} + +/**@brief Set length of root_info structure in bytes. + * @param root_info Pointer to root info structure of index + * @param info_length Length of the structure + */ +static inline void +ext4_dir_dx_root_info_set_info_length(struct ext4_dir_idx_rinfo *ri, + uint8_t len) +{ + ri->info_length = len; +} + +/**@brief Get number of indirect levels of HTree. + * @param root_info Pointer to root info structure of index + * @return Height of HTree (actually only 0 or 1) + */ +static inline uint8_t +ext4_dir_dx_rinfo_get_indirect_levels(struct ext4_dir_idx_rinfo *ri) +{ + return ri->indirect_levels; +} + +/**@brief Set number of indirect levels of HTree. + * @param root_info Pointer to root info structure of index + * @param lvl Height of HTree (actually only 0 or 1) + */ +static inline void +ext4_dir_dx_rinfo_set_indirect_levels(struct ext4_dir_idx_rinfo *ri, uint8_t l) +{ + ri->indirect_levels = l; +} + +/**@brief Get maximum number of index node entries. + * @param climit Pointer to counlimit structure + * @return Maximum of entries in node + */ +static inline uint16_t +ext4_dir_dx_climit_get_limit(struct ext4_dir_idx_climit *climit) +{ + return to_le16(climit->limit); +} + +/**@brief Set maximum number of index node entries. + * @param climit Pointer to counlimit structure + * @param limit Maximum of entries in node + */ +static inline void +ext4_dir_dx_climit_set_limit(struct ext4_dir_idx_climit *climit, uint16_t limit) +{ + climit->limit = to_le16(limit); +} + +/**@brief Get current number of index node entries. + * @param climit Pointer to counlimit structure + * @return Number of entries in node + */ +static inline uint16_t +ext4_dir_dx_climit_get_count(struct ext4_dir_idx_climit *climit) +{ + return to_le16(climit->count); +} + +/**@brief Set current number of index node entries. + * @param climit Pointer to counlimit structure + * @param count Number of entries in node + */ +static inline void +ext4_dir_dx_climit_set_count(struct ext4_dir_idx_climit *climit, uint16_t count) +{ + climit->count = to_le16(count); +} + +/**@brief Get hash value of index entry. + * @param entry Pointer to index entry + * @return Hash value + */ +static inline uint32_t +ext4_dir_dx_entry_get_hash(struct ext4_dir_idx_entry *entry) +{ + return to_le32(entry->hash); +} + +/**@brief Set hash value of index entry. + * @param entry Pointer to index entry + * @param hash Hash value + */ +static inline void +ext4_dir_dx_entry_set_hash(struct ext4_dir_idx_entry *entry, uint32_t hash) +{ + entry->hash = to_le32(hash); +} + +/**@brief Get block address where child node is located. + * @param entry Pointer to index entry + * @return Block address of child node + */ +static inline uint32_t +ext4_dir_dx_entry_get_block(struct ext4_dir_idx_entry *entry) +{ + return to_le32(entry->block); +} + +/**@brief Set block address where child node is located. + * @param entry Pointer to index entry + * @param block Block address of child node + */ +static inline void +ext4_dir_dx_entry_set_block(struct ext4_dir_idx_entry *entry, uint32_t block) +{ + entry->block = to_le32(block); +} + +/**@brief Sort entry item.*/ +struct ext4_dx_sort_entry { + uint32_t hash; + uint32_t rec_len; + void *dentry; +}; + +static int ext4_dir_dx_hash_string(struct ext4_hash_info *hinfo, int len, + const char *name) +{ + return ext2_htree_hash(name, len, hinfo->seed, hinfo->hash_version, + &hinfo->hash, &hinfo->minor_hash); +} + +#if CONFIG_META_CSUM_ENABLE +static uint32_t ext4_dir_dx_checksum(struct ext4_inode_ref *inode_ref, void *de, + int count_offset, int count, + struct ext4_dir_idx_tail *t) +{ + uint32_t orig_cum, csum = 0; + struct ext4_sblock *sb = &inode_ref->fs->sb; + int sz; + + /* Compute the checksum only if the filesystem supports it */ + 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; + ino_gen = to_le32(ext4_inode_get_generation(inode_ref->inode)); + + sz = count_offset + (count * sizeof(struct ext4_dir_idx_tail)); + orig_cum = t->checksum; + t->checksum = 0; + /* First calculate crc32 checksum against fs uuid */ + csum = ext4_crc32c(EXT4_CRC32_INIT, sb->uuid, sizeof(sb->uuid)); + /* Then calculate crc32 checksum against inode number + * and inode generation */ + csum = ext4_crc32c(csum, &ino_index, sizeof(ino_index)); + csum = ext4_crc32c(csum, &ino_gen, sizeof(ino_gen)); + /* After that calculate crc32 checksum against all the dx_entry */ + csum = ext4_crc32c(csum, de, sz); + /* Finally calculate crc32 checksum for dx_tail */ + csum = ext4_crc32c(csum, t, sizeof(struct ext4_dir_idx_tail)); + t->checksum = orig_cum; + } + return csum; +} + +static struct ext4_dir_idx_climit * +ext4_dir_dx_get_climit(struct ext4_inode_ref *inode_ref, + struct ext4_dir_en *dirent, int *offset) +{ + struct ext4_dir_en *dp; + struct ext4_dir_idx_root *root; + struct ext4_sblock *sb = &inode_ref->fs->sb; + uint32_t block_size = ext4_sb_get_block_size(sb); + uint16_t entry_len = ext4_dir_en_get_entry_len(dirent); + int count_offset; + + + if (entry_len == 12) { + root = (struct ext4_dir_idx_root *)dirent; + dp = (struct ext4_dir_en *)&root->dots[1]; + if (ext4_dir_en_get_entry_len(dp) != (block_size - 12)) + return NULL; + if (root->info.reserved_zero) + return NULL; + if (root->info.info_length != sizeof(struct ext4_dir_idx_rinfo)) + return NULL; + count_offset = 32; + } else if (entry_len == block_size) { + count_offset = 8; + } else { + return NULL; + } + + if (offset) + *offset = count_offset; + return (struct ext4_dir_idx_climit *)(((char *)dirent) + count_offset); +} + +/* + * BIG FAT NOTES: + * Currently we do not verify the checksum of HTree node. + */ +static bool ext4_dir_dx_csum_verify(struct ext4_inode_ref *inode_ref, + struct ext4_dir_en *de) +{ + struct ext4_sblock *sb = &inode_ref->fs->sb; + uint32_t block_size = ext4_sb_get_block_size(sb); + int coff, limit, cnt; + + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + struct ext4_dir_idx_climit *climit; + climit = ext4_dir_dx_get_climit(inode_ref, de, &coff); + if (!climit) { + /* Directory seems corrupted. */ + return true; + } + struct ext4_dir_idx_tail *t; + limit = ext4_dir_dx_climit_get_limit(climit); + cnt = ext4_dir_dx_climit_get_count(climit); + if (coff + (limit * sizeof(struct ext4_dir_idx_entry)) > + (block_size - sizeof(struct ext4_dir_idx_tail))) { + /* There is no space to hold the checksum */ + return true; + } + t = (void *)(((struct ext4_dir_idx_entry *)climit) + limit); + + uint32_t c; + c = to_le32(ext4_dir_dx_checksum(inode_ref, de, coff, cnt, t)); + if (t->checksum != c) + return false; + } + return true; +} + + +static void ext4_dir_set_dx_csum(struct ext4_inode_ref *inode_ref, + struct ext4_dir_en *dirent) +{ + int coff, limit, count; + struct ext4_sblock *sb = &inode_ref->fs->sb; + uint32_t block_size = ext4_sb_get_block_size(sb); + + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + struct ext4_dir_idx_climit *climit; + climit = ext4_dir_dx_get_climit(inode_ref, dirent, &coff); + if (!climit) { + /* Directory seems corrupted. */ + return; + } + struct ext4_dir_idx_tail *t; + limit = ext4_dir_dx_climit_get_limit(climit); + count = ext4_dir_dx_climit_get_count(climit); + if (coff + (limit * sizeof(struct ext4_dir_idx_entry)) > + (block_size - sizeof(struct ext4_dir_idx_tail))) { + /* There is no space to hold the checksum */ + return; + } + + t = (void *)(((struct ext4_dir_idx_entry *)climit) + limit); + t->checksum = to_le32(ext4_dir_dx_checksum(inode_ref, dirent, + coff, count, t)); + } +} +#else +#define ext4_dir_dx_csum_verify(...) true +#define ext4_dir_set_dx_csum(...) +#endif + +/****************************************************************************/ + +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 = 0; + bool need_append = + (ext4_inode_get_size(&dir->fs->sb, dir->inode) + < EXT4_DIR_DX_INIT_BCNT) + ? true : false; + struct ext4_sblock *sb = &dir->fs->sb; + uint32_t block_size = ext4_sb_get_block_size(&dir->fs->sb); + struct ext4_block block; + + int rc; + + if (!need_append) + rc = ext4_fs_init_inode_dblk_idx(dir, iblock, &fblock); + else + rc = ext4_fs_append_inode_dblk(dir, &fblock, &iblock); + + if (rc != EOK) + return rc; + + rc = ext4_trans_block_get_noread(dir->fs->bdev, &block, fblock); + if (rc != EOK) + return rc; + + /* Initialize pointers to data structures */ + struct ext4_dir_idx_root *root = (void *)block.data; + struct ext4_dir_idx_rinfo *info = &(root->info); + + memset(root, 0, sizeof(struct ext4_dir_idx_root)); + struct ext4_dir_en *de; + + /* Initialize dot entries */ + de = (struct ext4_dir_en *)root->dots; + ext4_dir_write_entry(sb, de, 12, dir, ".", strlen(".")); + + de = (struct ext4_dir_en *)(root->dots + 1); + uint16_t elen = block_size - 12; + ext4_dir_write_entry(sb, de, elen, parent, "..", strlen("..")); + + /* Initialize root info structure */ + uint8_t hash_version = ext4_get8(&dir->fs->sb, default_hash_version); + + ext4_dir_dx_rinfo_set_hash_version(info, hash_version); + ext4_dir_dx_rinfo_set_indirect_levels(info, 0); + ext4_dir_dx_root_info_set_info_length(info, 8); + + /* Set limit and current number of entries */ + struct ext4_dir_idx_climit *climit; + climit = (struct ext4_dir_idx_climit *)&root->en; + + ext4_dir_dx_climit_set_count(climit, 1); + + uint32_t entry_space; + entry_space = block_size - 2 * sizeof(struct ext4_dir_idx_dot_en) - + sizeof(struct ext4_dir_idx_rinfo); + + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + entry_space -= sizeof(struct ext4_dir_idx_tail); + + uint16_t root_limit = entry_space / sizeof(struct ext4_dir_idx_entry); + ext4_dir_dx_climit_set_limit(climit, root_limit); + + /* Append new block, where will be new entries inserted in the future */ + iblock++; + if (!need_append) + rc = ext4_fs_init_inode_dblk_idx(dir, iblock, &fblock); + else + rc = ext4_fs_append_inode_dblk(dir, &fblock, &iblock); + + if (rc != EOK) { + ext4_block_set(dir->fs->bdev, &block); + return rc; + } + + struct ext4_block new_block; + rc = ext4_trans_block_get_noread(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_dir_en *be = (void *)new_block.data; + + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + uint16_t len = block_size - sizeof(struct ext4_dir_entry_tail); + ext4_dir_en_set_entry_len(be, len); + ext4_dir_en_set_name_len(sb, be, 0); + ext4_dir_en_set_inode_type(sb, be, EXT4_DE_UNKNOWN); + ext4_dir_init_entry_tail(EXT4_DIRENT_TAIL(be, block_size)); + ext4_dir_set_csum(dir, be); + } else { + ext4_dir_en_set_entry_len(be, block_size); + } + + ext4_dir_en_set_inode(be, 0); + + ext4_trans_set_block_dirty(new_block.buf); + 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_dir_idx_entry *entry = root->en; + ext4_dir_dx_entry_set_block(entry, iblock); + + ext4_dir_set_dx_csum(dir, (struct ext4_dir_en *)block.data); + ext4_trans_set_block_dirty(block.buf); + + return ext4_block_set(dir->fs->bdev, &block); +} + +/**@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_dir_idx_root *root; + + root = (struct ext4_dir_idx_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_dir_idx_dot_en); + entry_space -= sizeof(struct ext4_dir_idx_rinfo); + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + entry_space -= sizeof(struct ext4_dir_idx_tail); + entry_space = entry_space / sizeof(struct ext4_dir_idx_entry); + + struct ext4_dir_idx_climit *climit = (void *)&root->en; + uint16_t limit = ext4_dir_dx_climit_get_limit(climit); + if (limit != entry_space) + return EXT4_ERR_BAD_DX_DIR; + + /* Check hash version and modify if necessary */ + hinfo->hash_version = ext4_dir_dx_rinfo_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; +} + +/**@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_dir_idx_block **dx_block, + struct ext4_dir_idx_block *dx_blocks) +{ + struct ext4_dir_idx_root *root; + struct ext4_dir_idx_entry *entries; + struct ext4_dir_idx_entry *p; + struct ext4_dir_idx_entry *q; + struct ext4_dir_idx_entry *m; + struct ext4_dir_idx_entry *at; + ext4_fsblk_t fblk; + uint32_t block_size; + uint16_t limit; + uint16_t entry_space; + uint8_t ind_level; + int r; + + struct ext4_dir_idx_block *tmp_dx_blk = dx_blocks; + struct ext4_block *tmp_blk = root_block; + struct ext4_sblock *sb = &inode_ref->fs->sb; + + block_size = ext4_sb_get_block_size(sb); + root = (struct ext4_dir_idx_root *)root_block->data; + entries = (struct ext4_dir_idx_entry *)&root->en; + limit = ext4_dir_dx_climit_get_limit((void *)entries); + ind_level = ext4_dir_dx_rinfo_get_indirect_levels(&root->info); + + /* Walk through the index tree */ + while (true) { + uint16_t cnt = ext4_dir_dx_climit_get_count((void *)entries); + if ((cnt == 0) || (cnt > limit)) + return EXT4_ERR_BAD_DX_DIR; + + /* Do binary search in every node */ + p = entries + 1; + q = entries + cnt - 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_blk->b, tmp_blk, sizeof(struct ext4_block)); + tmp_dx_blk->entries = entries; + tmp_dx_blk->position = at; + + /* Is algorithm in the leaf? */ + if (ind_level == 0) { + *dx_block = tmp_dx_blk; + return EOK; + } + + /* Goto child node */ + uint32_t n_blk = ext4_dir_dx_entry_get_block(at); + + ind_level--; + + r = ext4_fs_get_inode_dblk_idx(inode_ref, n_blk, &fblk, false); + if (r != EOK) + return r; + + r = ext4_trans_block_get(inode_ref->fs->bdev, tmp_blk, fblk); + if (r != EOK) + return r; + + entries = ((struct ext4_dir_idx_node *)tmp_blk->data)->entries; + limit = ext4_dir_dx_climit_get_limit((void *)entries); + + entry_space = block_size - sizeof(struct ext4_fake_dir_entry); + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + entry_space -= sizeof(struct ext4_dir_idx_tail); + + entry_space = entry_space / sizeof(struct ext4_dir_idx_entry); + + if (limit != entry_space) { + ext4_block_set(inode_ref->fs->bdev, tmp_blk); + return EXT4_ERR_BAD_DX_DIR; + } + + if (!ext4_dir_dx_csum_verify(inode_ref, (void *)tmp_blk->data)) { + ext4_dbg(DEBUG_DIR_IDX, + DBG_WARN "HTree checksum failed." + "Inode: %" PRIu32", " + "Block: %" PRIu32"\n", + inode_ref->index, + n_blk); + } + + ++tmp_dx_blk; + } + + /* Unreachable */ + return EOK; +} + +/**@brief Check if the the next block would be checked during entry search. + * @param inode_ref Directory i-node + * @param hash Hash value to check + * @param dx_block Current block + * @param dx_blocks Array with path from root to leaf node + * @return Standard Error code + */ +static int ext4_dir_dx_next_block(struct ext4_inode_ref *inode_ref, + uint32_t hash, + struct ext4_dir_idx_block *dx_block, + struct ext4_dir_idx_block *dx_blocks) +{ + int r; + uint32_t num_handles = 0; + ext4_fsblk_t blk_adr; + struct ext4_dir_idx_block *p = dx_block; + + /* Try to find data block with next bunch of entries */ + while (true) { + uint16_t cnt = ext4_dir_dx_climit_get_count((void *)p->entries); + + p->position++; + if (p->position < p->entries + cnt) + break; + + if (p == dx_blocks) + return EOK; + + num_handles++; + p--; + } + + /* Check hash collision (if not occurred - no next block cannot be + * used)*/ + uint32_t current_hash = ext4_dir_dx_entry_get_hash(p->position); + if ((hash & 1) == 0) { + if ((current_hash & ~1) != hash) + return 0; + } + + /* Fill new path */ + while (num_handles--) { + uint32_t blk = ext4_dir_dx_entry_get_block(p->position); + r = ext4_fs_get_inode_dblk_idx(inode_ref, blk, &blk_adr, false); + if (r != EOK) + return r; + + struct ext4_block b; + r = ext4_trans_block_get(inode_ref->fs->bdev, &b, blk_adr); + if (r != EOK) + return r; + + if (!ext4_dir_dx_csum_verify(inode_ref, (void *)b.data)) { + ext4_dbg(DEBUG_DIR_IDX, + DBG_WARN "HTree checksum failed." + "Inode: %" PRIu32", " + "Block: %" PRIu32"\n", + inode_ref->index, + blk); + } + + p++; + + /* Don't forget to put old block (prevent memory leak) */ + r = ext4_block_set(inode_ref->fs->bdev, &p->b); + if (r != EOK) + return r; + + memcpy(&p->b, &b, sizeof(b)); + p->entries = ((struct ext4_dir_idx_node *)b.data)->entries; + p->position = p->entries; + } + + return ENOENT; +} + +int ext4_dir_dx_find_entry(struct ext4_dir_search_result *result, + struct ext4_inode_ref *inode_ref, size_t name_len, + const char *name) +{ + /* Load direct block 0 (index root) */ + ext4_fsblk_t root_block_addr; + int rc2; + int rc; + rc = ext4_fs_get_inode_dblk_idx(inode_ref, 0, &root_block_addr, false); + if (rc != EOK) + return rc; + + struct ext4_fs *fs = inode_ref->fs; + + struct ext4_block root_block; + rc = ext4_trans_block_get(fs->bdev, &root_block, root_block_addr); + if (rc != EOK) + return rc; + + if (!ext4_dir_dx_csum_verify(inode_ref, (void *)root_block.data)) { + ext4_dbg(DEBUG_DIR_IDX, + DBG_WARN "HTree root checksum failed." + "Inode: %" PRIu32", " + "Block: %" PRIu32"\n", + inode_ref->index, + (uint32_t)0); + } + + /* Initialize hash info (compute hash value) */ + struct ext4_hash_info hinfo; + rc = ext4_dir_hinfo_init(&hinfo, &root_block, &fs->sb, name_len, name); + if (rc != EOK) { + ext4_block_set(fs->bdev, &root_block); + return EXT4_ERR_BAD_DX_DIR; + } + + /* + * Hardcoded number 2 means maximum height of index tree, + * specified in the Linux driver. + */ + struct ext4_dir_idx_block dx_blocks[2]; + struct ext4_dir_idx_block *dx_block; + struct ext4_dir_idx_block *tmp; + + rc = ext4_dir_dx_get_leaf(&hinfo, inode_ref, &root_block, &dx_block, + dx_blocks); + if (rc != EOK) { + ext4_block_set(fs->bdev, &root_block); + return EXT4_ERR_BAD_DX_DIR; + } + + do { + /* Load leaf block */ + uint32_t leaf_blk_idx; + ext4_fsblk_t leaf_block_addr; + struct ext4_block b; + + leaf_blk_idx = ext4_dir_dx_entry_get_block(dx_block->position); + rc = ext4_fs_get_inode_dblk_idx(inode_ref, leaf_blk_idx, + &leaf_block_addr, false); + if (rc != EOK) + goto cleanup; + + rc = ext4_trans_block_get(fs->bdev, &b, leaf_block_addr); + if (rc != EOK) + goto cleanup; + + if (!ext4_dir_csum_verify(inode_ref, (void *)b.data)) { + ext4_dbg(DEBUG_DIR_IDX, + DBG_WARN "HTree leaf block checksum failed." + "Inode: %" PRIu32", " + "Block: %" PRIu32"\n", + inode_ref->index, + leaf_blk_idx); + } + + /* Linear search inside block */ + struct ext4_dir_en *de; + rc = ext4_dir_find_in_block(&b, &fs->sb, name_len, name, &de); + + /* Found => return it */ + if (rc == EOK) { + result->block = b; + result->dentry = de; + goto cleanup; + } + + /* Not found, leave untouched */ + rc2 = ext4_block_set(fs->bdev, &b); + if (rc2 != EOK) + goto cleanup; + + if (rc != ENOENT) + goto cleanup; + + /* check if the next block could be checked */ + rc = ext4_dir_dx_next_block(inode_ref, hinfo.hash, dx_block, + &dx_blocks[0]); + if (rc < 0) + goto cleanup; + } while (rc == ENOENT); + + /* Entry not found */ + rc = ENOENT; + +cleanup: + /* The whole path must be released (preventing memory leak) */ + tmp = dx_blocks; + + while (tmp <= dx_block) { + rc2 = ext4_block_set(fs->bdev, &tmp->b); + if (rc == EOK && rc2 != EOK) + rc = rc2; + ++tmp; + } + + return rc; +} + +#if CONFIG_DIR_INDEX_COMB_SORT +#define SWAP_ENTRY(se1, se2) \ + do { \ + struct ext4_dx_sort_entry tmp = se1; \ + se1 = se2; \ + se2 = tmp; \ + \ +} while (0) + +static void comb_sort(struct ext4_dx_sort_entry *se, uint32_t count) +{ + struct ext4_dx_sort_entry *p, *q, *top = se + count - 1; + bool more; + /* Combsort */ + while (count > 2) { + count = (count * 10) / 13; + if (count - 9 < 2) + count = 11; + for (p = top, q = p - count; q >= se; p--, q--) + if (p->hash < q->hash) + SWAP_ENTRY(*p, *q); + } + /* Bubblesort */ + do { + more = 0; + q = top; + while (q-- > se) { + if (q[1].hash >= q[0].hash) + continue; + SWAP_ENTRY(*(q + 1), *q); + more = 1; + } + } while (more); +} +#else + +/**@brief Compare function used to pass in quicksort implementation. + * It can compare two entries by hash value. + * @param arg1 First entry + * @param arg2 Second entry + * @param dummy Unused parameter, can be NULL + * + * @return Classic compare result + * (0: equal, -1: arg1 < arg2, 1: arg1 > arg2) + */ +static int ext4_dir_dx_entry_comparator(const void *arg1, const void *arg2) +{ + struct ext4_dx_sort_entry *entry1 = (void *)arg1; + struct ext4_dx_sort_entry *entry2 = (void *)arg2; + + if (entry1->hash == entry2->hash) + return 0; + + if (entry1->hash < entry2->hash) + return -1; + else + return 1; +} +#endif + +/**@brief Insert new index entry to block. + * Note that space for new entry must be checked by caller. + * @param inode_ref Directory i-node + * @param index_block Block where to insert new entry + * @param hash Hash value covered by child node + * @param iblock Logical number of child block + * + */ +static void +ext4_dir_dx_insert_entry(struct ext4_inode_ref *inode_ref __unused, + struct ext4_dir_idx_block *index_block, + uint32_t hash, uint32_t iblock) +{ + struct ext4_dir_idx_entry *old_index_entry = index_block->position; + struct ext4_dir_idx_entry *new_index_entry = old_index_entry + 1; + struct ext4_dir_idx_climit *climit = (void *)index_block->entries; + struct ext4_dir_idx_entry *start_index = index_block->entries; + uint32_t count = ext4_dir_dx_climit_get_count(climit); + + size_t bytes; + bytes = (uint8_t *)(start_index + count) - (uint8_t *)(new_index_entry); + + memmove(new_index_entry + 1, new_index_entry, bytes); + + ext4_dir_dx_entry_set_block(new_index_entry, iblock); + ext4_dir_dx_entry_set_hash(new_index_entry, hash); + ext4_dir_dx_climit_set_count(climit, count + 1); + ext4_dir_set_dx_csum(inode_ref, (void *)index_block->b.data); + ext4_trans_set_block_dirty(index_block->b.buf); +} + +/**@brief Split directory entries to two parts preventing node overflow. + * @param inode_ref Directory i-node + * @param hinfo Hash info + * @param old_data_block Block with data to be split + * @param index_block Block where index entries are located + * @param new_data_block Output value for newly allocated data block + */ +static int ext4_dir_dx_split_data(struct ext4_inode_ref *inode_ref, + struct ext4_hash_info *hinfo, + struct ext4_block *old_data_block, + struct ext4_dir_idx_block *index_block, + struct ext4_block *new_data_block) +{ + int rc = EOK; + struct ext4_sblock *sb = &inode_ref->fs->sb; + uint32_t block_size = ext4_sb_get_block_size(&inode_ref->fs->sb); + + /* Allocate buffer for directory entries */ + uint8_t *entry_buffer = malloc(block_size); + if (entry_buffer == NULL) + return ENOMEM; + + /* dot entry has the smallest size available */ + uint32_t max_ecnt = block_size / sizeof(struct ext4_dir_idx_dot_en); + + /* Allocate sort entry */ + struct ext4_dx_sort_entry *sort; + + sort = malloc(max_ecnt * sizeof(struct ext4_dx_sort_entry)); + if (sort == NULL) { + free(entry_buffer); + return ENOMEM; + } + + uint32_t idx = 0; + uint32_t real_size = 0; + + /* Initialize hinfo */ + struct ext4_hash_info hinfo_tmp; + memcpy(&hinfo_tmp, hinfo, sizeof(struct ext4_hash_info)); + + /* Load all valid entries to the buffer */ + struct ext4_dir_en *de = (void *)old_data_block->data; + uint8_t *entry_buffer_ptr = entry_buffer; + while ((void *)de < (void *)(old_data_block->data + block_size)) { + /* Read only valid entries */ + if (ext4_dir_en_get_inode(de) && de->name_len) { + uint16_t len = ext4_dir_en_get_name_len(sb, de); + rc = ext4_dir_dx_hash_string(&hinfo_tmp, len, + (char *)de->name); + if (rc != EOK) { + free(sort); + free(entry_buffer); + return rc; + } + + uint32_t rec_len = 8 + len; + if ((rec_len % 4) != 0) + rec_len += 4 - (rec_len % 4); + + memcpy(entry_buffer_ptr, de, rec_len); + + sort[idx].dentry = entry_buffer_ptr; + sort[idx].rec_len = rec_len; + sort[idx].hash = hinfo_tmp.hash; + + entry_buffer_ptr += rec_len; + real_size += rec_len; + idx++; + } + + size_t elen = ext4_dir_en_get_entry_len(de); + de = (void *)((uint8_t *)de + elen); + } + +/* Sort all entries */ +#if CONFIG_DIR_INDEX_COMB_SORT + comb_sort(sort, idx); +#else + qsort(sort, idx, sizeof(struct ext4_dx_sort_entry), + ext4_dir_dx_entry_comparator); +#endif + /* Allocate new block for store the second part of entries */ + ext4_fsblk_t new_fblock; + uint32_t new_iblock; + rc = ext4_fs_append_inode_dblk(inode_ref, &new_fblock, &new_iblock); + if (rc != EOK) { + free(sort); + free(entry_buffer); + return rc; + } + + /* Load new block */ + struct ext4_block new_data_block_tmp; + rc = ext4_trans_block_get_noread(inode_ref->fs->bdev, &new_data_block_tmp, + new_fblock); + if (rc != EOK) { + free(sort); + free(entry_buffer); + return rc; + } + + /* + * Distribute entries to two blocks (by size) + * - compute the half + */ + uint32_t new_hash = 0; + uint32_t current_size = 0; + uint32_t mid = 0; + uint32_t i; + for (i = 0; i < idx; ++i) { + if ((current_size + sort[i].rec_len) > (block_size / 2)) { + new_hash = sort[i].hash; + mid = i; + break; + } + + current_size += sort[i].rec_len; + } + + /* Check hash collision */ + uint32_t continued = 0; + if (new_hash == sort[mid - 1].hash) + continued = 1; + + uint32_t off = 0; + void *ptr; + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + block_size -= sizeof(struct ext4_dir_entry_tail); + + /* First part - to the old block */ + for (i = 0; i < mid; ++i) { + ptr = old_data_block->data + off; + memcpy(ptr, sort[i].dentry, sort[i].rec_len); + + struct ext4_dir_en *t = ptr; + if (i < (mid - 1)) + ext4_dir_en_set_entry_len(t, sort[i].rec_len); + else + ext4_dir_en_set_entry_len(t, block_size - off); + + off += sort[i].rec_len; + } + + /* Second part - to the new block */ + off = 0; + for (i = mid; i < idx; ++i) { + ptr = new_data_block_tmp.data + off; + memcpy(ptr, sort[i].dentry, sort[i].rec_len); + + struct ext4_dir_en *t = ptr; + if (i < (idx - 1)) + ext4_dir_en_set_entry_len(t, sort[i].rec_len); + else + ext4_dir_en_set_entry_len(t, block_size - off); + + off += sort[i].rec_len; + } + + block_size = ext4_sb_get_block_size(&inode_ref->fs->sb); + + /* Do some steps to finish operation */ + sb = &inode_ref->fs->sb; + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + struct ext4_dir_entry_tail *t; + + t = EXT4_DIRENT_TAIL(old_data_block->data, block_size); + ext4_dir_init_entry_tail(t); + t = EXT4_DIRENT_TAIL(new_data_block_tmp.data, block_size); + ext4_dir_init_entry_tail(t); + } + ext4_dir_set_csum(inode_ref, (void *)old_data_block->data); + ext4_dir_set_csum(inode_ref, (void *)new_data_block_tmp.data); + ext4_trans_set_block_dirty(old_data_block->buf); + ext4_trans_set_block_dirty(new_data_block_tmp.buf); + + free(sort); + free(entry_buffer); + + ext4_dir_dx_insert_entry(inode_ref, index_block, new_hash + continued, + new_iblock); + + *new_data_block = new_data_block_tmp; + return EOK; +} + +/**@brief Split index node and maybe some parent nodes in the tree hierarchy. + * @param inode_ref Directory i-node + * @param dx_blocks Array with path from root to leaf node + * @param dx_block Leaf block to be split if needed + * @return Error code + */ +static int +ext4_dir_dx_split_index(struct ext4_inode_ref *ino_ref, + struct ext4_dir_idx_block *dx_blks, + struct ext4_dir_idx_block *dxb, + struct ext4_dir_idx_block **new_dx_block) +{ + struct ext4_sblock *sb = &ino_ref->fs->sb; + struct ext4_dir_idx_entry *e; + int r; + + uint32_t block_size = ext4_sb_get_block_size(&ino_ref->fs->sb); + uint32_t entry_space = block_size - sizeof(struct ext4_fake_dir_entry); + uint32_t node_limit = entry_space / sizeof(struct ext4_dir_idx_entry); + + bool meta_csum = ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM); + + if (dxb == dx_blks) + e = ((struct ext4_dir_idx_root *)dxb->b.data)->en; + else + e = ((struct ext4_dir_idx_node *)dxb->b.data)->entries; + + struct ext4_dir_idx_climit *climit = (struct ext4_dir_idx_climit *)e; + + uint16_t leaf_limit = ext4_dir_dx_climit_get_limit(climit); + uint16_t leaf_count = ext4_dir_dx_climit_get_count(climit); + + /* Check if is necessary to split index block */ + if (leaf_limit == leaf_count) { + struct ext4_dir_idx_entry *ren; + ptrdiff_t levels = dxb - dx_blks; + + ren = ((struct ext4_dir_idx_root *)dx_blks[0].b.data)->en; + struct ext4_dir_idx_climit *rclimit = (void *)ren; + uint16_t root_limit = ext4_dir_dx_climit_get_limit(rclimit); + uint16_t root_count = ext4_dir_dx_climit_get_count(rclimit); + + + /* Linux limitation */ + if ((levels > 0) && (root_limit == root_count)) + return ENOSPC; + + /* Add new block to directory */ + ext4_fsblk_t new_fblk; + uint32_t new_iblk; + r = ext4_fs_append_inode_dblk(ino_ref, &new_fblk, &new_iblk); + if (r != EOK) + return r; + + /* load new block */ + struct ext4_block b; + r = ext4_trans_block_get_noread(ino_ref->fs->bdev, &b, new_fblk); + if (r != EOK) + return r; + + struct ext4_dir_idx_node *new_node = (void *)b.data; + struct ext4_dir_idx_entry *new_en = new_node->entries; + + memset(&new_node->fake, 0, sizeof(struct ext4_fake_dir_entry)); + new_node->fake.entry_length = block_size; + + /* Split leaf node */ + if (levels > 0) { + uint32_t count_left = leaf_count / 2; + uint32_t count_right = leaf_count - count_left; + uint32_t hash_right; + size_t sz; + + struct ext4_dir_idx_climit *left_climit; + struct ext4_dir_idx_climit *right_climit; + + hash_right = ext4_dir_dx_entry_get_hash(e + count_left); + /* Copy data to new node */ + sz = count_right * sizeof(struct ext4_dir_idx_entry); + memcpy(new_en, e + count_left, sz); + + /* Initialize new node */ + left_climit = (struct ext4_dir_idx_climit *)e; + right_climit = (struct ext4_dir_idx_climit *)new_en; + + ext4_dir_dx_climit_set_count(left_climit, count_left); + ext4_dir_dx_climit_set_count(right_climit, count_right); + + if (meta_csum) + entry_space -= sizeof(struct ext4_dir_idx_tail); + + ext4_dir_dx_climit_set_limit(right_climit, node_limit); + + /* Which index block is target for new entry */ + uint32_t position_index = + (dxb->position - dxb->entries); + if (position_index >= count_left) { + ext4_dir_set_dx_csum( + ino_ref, + (struct ext4_dir_en *) + dxb->b.data); + ext4_trans_set_block_dirty(dxb->b.buf); + + struct ext4_block block_tmp = dxb->b; + + dxb->b = b; + + dxb->position = + new_en + position_index - count_left; + dxb->entries = new_en; + + b = block_tmp; + } + + /* Finally insert new entry */ + ext4_dir_dx_insert_entry(ino_ref, dx_blks, hash_right, + new_iblk); + ext4_dir_set_dx_csum(ino_ref, (void*)dx_blks[0].b.data); + ext4_dir_set_dx_csum(ino_ref, (void*)dx_blks[1].b.data); + ext4_trans_set_block_dirty(dx_blks[0].b.buf); + ext4_trans_set_block_dirty(dx_blks[1].b.buf); + + ext4_dir_set_dx_csum(ino_ref, (void *)b.data); + ext4_trans_set_block_dirty(b.buf); + return ext4_block_set(ino_ref->fs->bdev, &b); + } else { + size_t sz; + /* Copy data from root to child block */ + sz = leaf_count * sizeof(struct ext4_dir_idx_entry); + memcpy(new_en, e, sz); + + struct ext4_dir_idx_climit *new_climit = (void*)new_en; + if (meta_csum) + entry_space -= sizeof(struct ext4_dir_idx_tail); + + ext4_dir_dx_climit_set_limit(new_climit, node_limit); + + /* Set values in root node */ + struct ext4_dir_idx_climit *new_root_climit = (void *)e; + + ext4_dir_dx_climit_set_count(new_root_climit, 1); + ext4_dir_dx_entry_set_block(e, new_iblk); + + struct ext4_dir_idx_root *r = (void *)dx_blks[0].b.data; + r->info.indirect_levels = 1; + + /* Add new entry to the path */ + dxb = dx_blks + 1; + dxb->position = dx_blks->position - e + new_en; + dxb->entries = new_en; + dxb->b = b; + *new_dx_block = dxb; + + ext4_dir_set_dx_csum(ino_ref, (void*)dx_blks[0].b.data); + ext4_dir_set_dx_csum(ino_ref, (void*)dx_blks[1].b.data); + ext4_trans_set_block_dirty(dx_blks[0].b.buf); + ext4_trans_set_block_dirty(dx_blks[1].b.buf); + } + } + + return EOK; +} + +int ext4_dir_dx_add_entry(struct ext4_inode_ref *parent, + struct ext4_inode_ref *child, const char *name) +{ + int rc2 = EOK; + int r; + /* Get direct block 0 (index root) */ + ext4_fsblk_t rblock_addr; + r = ext4_fs_get_inode_dblk_idx(parent, 0, &rblock_addr, false); + if (r != EOK) + return r; + + struct ext4_fs *fs = parent->fs; + struct ext4_block root_blk; + + r = ext4_trans_block_get(fs->bdev, &root_blk, rblock_addr); + if (r != EOK) + return r; + + if (!ext4_dir_dx_csum_verify(parent, (void*)root_blk.data)) { + ext4_dbg(DEBUG_DIR_IDX, + DBG_WARN "HTree root checksum failed." + "Inode: %" PRIu32", " + "Block: %" PRIu32"\n", + parent->index, + (uint32_t)0); + } + + /* Initialize hinfo structure (mainly compute hash) */ + uint32_t name_len = strlen(name); + struct ext4_hash_info hinfo; + r = ext4_dir_hinfo_init(&hinfo, &root_blk, &fs->sb, name_len, name); + if (r != EOK) { + ext4_block_set(fs->bdev, &root_blk); + return EXT4_ERR_BAD_DX_DIR; + } + + /* + * Hardcoded number 2 means maximum height of index + * tree defined in Linux. + */ + struct ext4_dir_idx_block dx_blks[2]; + struct ext4_dir_idx_block *dx_blk; + struct ext4_dir_idx_block *dx_it; + + r = ext4_dir_dx_get_leaf(&hinfo, parent, &root_blk, &dx_blk, dx_blks); + if (r != EOK) { + r = EXT4_ERR_BAD_DX_DIR; + goto release_index; + } + + /* Try to insert to existing data block */ + uint32_t leaf_block_idx = ext4_dir_dx_entry_get_block(dx_blk->position); + ext4_fsblk_t leaf_block_addr; + r = ext4_fs_get_inode_dblk_idx(parent, leaf_block_idx, + &leaf_block_addr, false); + if (r != EOK) + goto release_index; + + /* + * Check if there is needed to split index node + * (and recursively also parent nodes) + */ + r = ext4_dir_dx_split_index(parent, dx_blks, dx_blk, &dx_blk); + if (r != EOK) + goto release_target_index; + + struct ext4_block target_block; + r = ext4_trans_block_get(fs->bdev, &target_block, leaf_block_addr); + if (r != EOK) + goto release_index; + + if (!ext4_dir_csum_verify(parent,(void *)target_block.data)) { + ext4_dbg(DEBUG_DIR_IDX, + DBG_WARN "HTree leaf block checksum failed." + "Inode: %" PRIu32", " + "Block: %" PRIu32"\n", + parent->index, + leaf_block_idx); + } + + /* Check if insert operation passed */ + r = ext4_dir_try_insert_entry(&fs->sb, parent, &target_block, child, + name, name_len); + if (r == EOK) + goto release_target_index; + + /* Split entries to two blocks (includes sorting by hash value) */ + struct ext4_block new_block; + r = ext4_dir_dx_split_data(parent, &hinfo, &target_block, dx_blk, + &new_block); + if (r != EOK) { + rc2 = r; + goto release_target_index; + } + + /* Where to save new entry */ + uint32_t blk_hash = ext4_dir_dx_entry_get_hash(dx_blk->position + 1); + if (hinfo.hash >= blk_hash) + r = ext4_dir_try_insert_entry(&fs->sb, parent, &new_block, + child, name, name_len); + else + r = ext4_dir_try_insert_entry(&fs->sb, parent, &target_block, + child, name, name_len); + + /* Cleanup */ + r = ext4_block_set(fs->bdev, &new_block); + if (r != EOK) + return r; + +/* Cleanup operations */ + +release_target_index: + rc2 = r; + + r = ext4_block_set(fs->bdev, &target_block); + if (r != EOK) + return r; + +release_index: + if (r != EOK) + rc2 = r; + + dx_it = dx_blks; + + while (dx_it <= dx_blk) { + r = ext4_block_set(fs->bdev, &dx_it->b); + if (r != EOK) + return r; + + dx_it++; + } + + return rc2; +} + +int ext4_dir_dx_reset_parent_inode(struct ext4_inode_ref *dir, + uint32_t parent_inode) +{ + /* Load block 0, where will be index root located */ + ext4_fsblk_t fblock; + int rc = ext4_fs_get_inode_dblk_idx(dir, 0, &fblock, false); + if (rc != EOK) + return rc; + + struct ext4_block block; + rc = ext4_trans_block_get(dir->fs->bdev, &block, fblock); + if (rc != EOK) + return rc; + + if (!ext4_dir_dx_csum_verify(dir, (void *)block.data)) { + ext4_dbg(DEBUG_DIR_IDX, + DBG_WARN "HTree root checksum failed." + "Inode: %" PRIu32", " + "Block: %" PRIu32"\n", + dir->index, + (uint32_t)0); + } + + /* Initialize pointers to data structures */ + struct ext4_dir_idx_root *root = (void *)block.data; + + /* Fill the inode field with a new parent ino. */ + ext4_dx_dot_en_set_inode(&root->dots[1], parent_inode); + + ext4_dir_set_dx_csum(dir, (void *)block.data); + ext4_trans_set_block_dirty(block.buf); + + return ext4_block_set(dir->fs->bdev, &block); +} + +/** + * @} + */ diff --git a/src/ext4_extent.c b/src/ext4_extent.c new file mode 100644 index 0000000..f53733d --- /dev/null +++ b/src/ext4_extent.c @@ -0,0 +1,1859 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * Copyright (c) 2015 Kaho Ng (ngkaho1234@gmail.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ext4_config.h" +#include "ext4_blockdev.h" +#include "ext4_fs.h" +#include "ext4_super.h" +#include "ext4_crc32.h" +#include "ext4_balloc.h" +#include "ext4_debug.h" + +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <stddef.h> + +#include "ext4_extent.h" + +/* + * used by extent splitting. + */ +#define EXT4_EXT_MARK_UNWRIT1 0x02 /* mark first half unwritten */ +#define EXT4_EXT_MARK_UNWRIT2 0x04 /* mark second half unwritten */ +#define EXT4_EXT_DATA_VALID1 0x08 /* first half contains valid data */ +#define EXT4_EXT_DATA_VALID2 0x10 /* second half contains valid data */ +#define EXT4_EXT_NO_COMBINE 0x20 /* do not combine two extents */ + +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)); +} + +static struct ext4_extent_header *ext_inode_hdr(struct ext4_inode *inode) +{ + return (struct ext4_extent_header *)inode->blocks; +} + +static struct ext4_extent_header *ext_block_hdr(struct ext4_block *block) +{ + return (struct ext4_extent_header *)block->data; +} + +static uint16_t ext_depth(struct ext4_inode *inode) +{ + return to_le16(ext_inode_hdr(inode)->depth); +} + +static uint16_t ext4_ext_get_actual_len(struct ext4_extent *ext) +{ + return (to_le16(ext->block_count) <= EXT_INIT_MAX_LEN + ? to_le16(ext->block_count) + : (to_le16(ext->block_count) - EXT_INIT_MAX_LEN)); +} + +static void ext4_ext_mark_initialized(struct ext4_extent *ext) +{ + ext->block_count = to_le16(ext4_ext_get_actual_len(ext)); +} + +static void ext4_ext_mark_unwritten(struct ext4_extent *ext) +{ + ext->block_count |= to_le16(EXT_INIT_MAX_LEN); +} + +static int ext4_ext_is_unwritten(struct ext4_extent *ext) +{ + /* Extent with ee_len of 0x8000 is treated as an initialized extent */ + return (to_le16(ext->block_count) > EXT_INIT_MAX_LEN); +} + +/* + * ext4_ext_pblock: + * combine low and high parts of physical block number into ext4_fsblk_t + */ +static ext4_fsblk_t ext4_ext_pblock(struct ext4_extent *ex) +{ + ext4_fsblk_t block; + + block = to_le32(ex->start_lo); + block |= ((ext4_fsblk_t)to_le16(ex->start_hi) << 31) << 1; + return block; +} + +/* + * ext4_idx_pblock: + * combine low and high parts of a leaf physical block number into ext4_fsblk_t + */ +static ext4_fsblk_t ext4_idx_pblock(struct ext4_extent_index *ix) +{ + ext4_fsblk_t block; + + block = to_le32(ix->leaf_lo); + block |= ((ext4_fsblk_t)to_le16(ix->leaf_hi) << 31) << 1; + return block; +} + +/* + * ext4_ext_store_pblock: + * stores a large physical block number into an extent struct, + * breaking it into parts + */ +static void ext4_ext_store_pblock(struct ext4_extent *ex, ext4_fsblk_t pb) +{ + ex->start_lo = to_le32((uint32_t)(pb & 0xffffffff)); + ex->start_hi = to_le16((uint16_t)((pb >> 32)) & 0xffff); +} + +/* + * ext4_idx_store_pblock: + * stores a large physical block number into an index struct, + * breaking it into parts + */ +static void ext4_idx_store_pblock(struct ext4_extent_index *ix, ext4_fsblk_t pb) +{ + ix->leaf_lo = to_le32((uint32_t)(pb & 0xffffffff)); + ix->leaf_hi = to_le16((uint16_t)((pb >> 32)) & 0xffff); +} + +static int ext4_allocate_single_block(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t goal, + ext4_fsblk_t *blockp) +{ + return ext4_balloc_alloc_block(inode_ref, goal, blockp); +} + +static ext4_fsblk_t ext4_new_meta_blocks(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t goal, + uint32_t flags __unused, + uint32_t *count, int *errp) +{ + ext4_fsblk_t block = 0; + + *errp = ext4_allocate_single_block(inode_ref, goal, &block); + if (count) + *count = 1; + return block; +} + +static void ext4_ext_free_blocks(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t block, uint32_t count, + uint32_t flags __unused) +{ + ext4_balloc_free_blocks(inode_ref, block, count); +} + +static uint16_t ext4_ext_space_block(struct ext4_inode_ref *inode_ref) +{ + uint16_t size; + uint32_t block_size = ext4_sb_get_block_size(&inode_ref->fs->sb); + + size = (block_size - sizeof(struct ext4_extent_header)) / + sizeof(struct ext4_extent); +#ifdef AGGRESSIVE_TEST + if (size > 6) + size = 6; +#endif + return size; +} + +static uint16_t ext4_ext_space_block_idx(struct ext4_inode_ref *inode_ref) +{ + uint16_t size; + uint32_t block_size = ext4_sb_get_block_size(&inode_ref->fs->sb); + + size = (block_size - sizeof(struct ext4_extent_header)) / + sizeof(struct ext4_extent_index); +#ifdef AGGRESSIVE_TEST + if (size > 5) + size = 5; +#endif + return size; +} + +static uint16_t ext4_ext_space_root(struct ext4_inode_ref *inode_ref) +{ + uint16_t size; + + size = sizeof(inode_ref->inode->blocks); + size -= sizeof(struct ext4_extent_header); + size /= sizeof(struct ext4_extent); +#ifdef AGGRESSIVE_TEST + if (size > 3) + size = 3; +#endif + return size; +} + +static uint16_t ext4_ext_space_root_idx(struct ext4_inode_ref *inode_ref) +{ + uint16_t size; + + size = sizeof(inode_ref->inode->blocks); + size -= sizeof(struct ext4_extent_header); + size /= sizeof(struct ext4_extent_index); +#ifdef AGGRESSIVE_TEST + if (size > 4) + size = 4; +#endif + return size; +} + +static uint16_t ext4_ext_max_entries(struct ext4_inode_ref *inode_ref, + uint32_t depth) +{ + uint16_t max; + + if (depth == ext_depth(inode_ref->inode)) { + if (depth == 0) + max = ext4_ext_space_root(inode_ref); + else + max = ext4_ext_space_root_idx(inode_ref); + } else { + if (depth == 0) + max = ext4_ext_space_block(inode_ref); + else + max = ext4_ext_space_block_idx(inode_ref); + } + + return max; +} + +static ext4_fsblk_t ext4_ext_find_goal(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, + ext4_lblk_t block) +{ + if (path) { + uint32_t depth = path->depth; + struct ext4_extent *ex; + + /* + * Try to predict block placement assuming that we are + * filling in a file which will eventually be + * non-sparse --- i.e., in the case of libbfd writing + * an ELF object sections out-of-order but in a way + * the eventually results in a contiguous object or + * executable file, or some database extending a table + * space file. However, this is actually somewhat + * non-ideal if we are writing a sparse file such as + * qemu or KVM writing a raw image file that is going + * to stay fairly sparse, since it will end up + * fragmenting the file system's free space. Maybe we + * should have some hueristics or some way to allow + * userspace to pass a hint to file system, + * especially if the latter case turns out to be + * common. + */ + ex = path[depth].extent; + if (ex) { + ext4_fsblk_t ext_pblk = ext4_ext_pblock(ex); + ext4_lblk_t ext_block = to_le32(ex->first_block); + + if (block > ext_block) + return ext_pblk + (block - ext_block); + else + return ext_pblk - (ext_block - block); + } + + /* it looks like index is empty; + * try to find starting block from index itself */ + if (path[depth].block.lb_id) + return path[depth].block.lb_id; + } + + /* OK. use inode's group */ + return ext4_fs_inode_to_goal_block(inode_ref); +} + +/* + * Allocation for a meta data block + */ +static ext4_fsblk_t ext4_ext_new_meta_block(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, + struct ext4_extent *ex, int *err, + uint32_t flags) +{ + ext4_fsblk_t goal, newblock; + + goal = ext4_ext_find_goal(inode_ref, path, to_le32(ex->first_block)); + newblock = ext4_new_meta_blocks(inode_ref, goal, flags, NULL, err); + return newblock; +} + +#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 __unused, + struct ext4_extent_header *eh) +{ + struct ext4_extent_tail *tail; + + tail = find_ext4_extent_tail(eh); + tail->et_checksum = to_le32(ext4_ext_block_csum(inode_ref, eh)); +} + +static int ext4_ext_dirty(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path) +{ + if (path->block.lb_id) + ext4_trans_set_block_dirty(path->block.buf); + else + inode_ref->dirty = true; + + return EOK; +} + +static void ext4_ext_drop_refs(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, bool keep_other) +{ + int32_t depth, i; + + if (!path) + return; + if (keep_other) + depth = 0; + else + depth = path->depth; + + for (i = 0; i <= depth; i++, path++) { + if (path->block.lb_id) { + if (ext4_bcache_test_flag(path->block.buf, BC_DIRTY)) + ext4_extent_block_csum_set(inode_ref, + path->header); + + ext4_block_set(inode_ref->fs->bdev, &path->block); + } + } +} + +/* + * Check that whether the basic information inside the extent header + * is correct or not. + */ +static int ext4_ext_check(struct ext4_inode_ref *inode_ref, + struct ext4_extent_header *eh, uint16_t depth, + ext4_fsblk_t pblk __unused) +{ + struct ext4_extent_tail *tail; + struct ext4_sblock *sb = &inode_ref->fs->sb; + const char *error_msg; + (void)error_msg; + + if (to_le16(eh->magic) != EXT4_EXTENT_MAGIC) { + error_msg = "invalid magic"; + goto corrupted; + } + if (to_le16(eh->depth) != depth) { + error_msg = "unexpected eh_depth"; + goto corrupted; + } + if (eh->max_entries_count == 0) { + error_msg = "invalid eh_max"; + goto corrupted; + } + if (to_le16(eh->entries_count) > to_le16(eh->max_entries_count)) { + error_msg = "invalid eh_entries"; + goto corrupted; + } + + tail = find_ext4_extent_tail(eh); + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + if (tail->et_checksum != to_le32(ext4_ext_block_csum(inode_ref, eh))) { + ext4_dbg(DEBUG_EXTENT, + DBG_WARN "Extent block checksum failed." + "Blocknr: %" PRIu64"\n", + pblk); + + } + } + + return EOK; + +corrupted: + ext4_dbg(DEBUG_EXTENT, "Bad extents B+ tree block: %s. " + "Blocknr: %" PRId64 "\n", + error_msg, pblk); + return EIO; +} + +static int read_extent_tree_block(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t pblk, int32_t depth, + struct ext4_block *bh, + uint32_t flags __unused) +{ + int err; + + err = ext4_trans_block_get(inode_ref->fs->bdev, bh, pblk); + if (err != EOK) + goto errout; + + err = ext4_ext_check(inode_ref, ext_block_hdr(bh), depth, pblk); + if (err != EOK) + goto errout; + + return EOK; +errout: + if (bh->lb_id) + ext4_block_set(inode_ref->fs->bdev, bh); + + return err; +} + +/* + * ext4_ext_binsearch_idx: + * binary search for the closest index of the given block + * the header must be checked before calling this + */ +static void ext4_ext_binsearch_idx(struct ext4_extent_path *path, + ext4_lblk_t block) +{ + struct ext4_extent_header *eh = path->header; + struct ext4_extent_index *r, *l, *m; + + l = EXT_FIRST_INDEX(eh) + 1; + r = EXT_LAST_INDEX(eh); + while (l <= r) { + m = l + (r - l) / 2; + if (block < to_le32(m->first_block)) + r = m - 1; + else + l = m + 1; + } + + path->index = l - 1; +} + +/* + * ext4_ext_binsearch: + * binary search for closest extent of the given block + * the header must be checked before calling this + */ +static void ext4_ext_binsearch(struct ext4_extent_path *path, ext4_lblk_t block) +{ + struct ext4_extent_header *eh = path->header; + struct ext4_extent *r, *l, *m; + + if (eh->entries_count == 0) { + /* + * this leaf is empty: + * we get such a leaf in split/add case + */ + return; + } + + l = EXT_FIRST_EXTENT(eh) + 1; + r = EXT_LAST_EXTENT(eh); + + while (l <= r) { + m = l + (r - l) / 2; + if (block < to_le32(m->first_block)) + r = m - 1; + else + l = m + 1; + } + + path->extent = l - 1; +} + +static int ext4_find_extent(struct ext4_inode_ref *inode_ref, ext4_lblk_t block, + struct ext4_extent_path **orig_path, uint32_t flags) +{ + struct ext4_extent_header *eh; + struct ext4_block bh = EXT4_BLOCK_ZERO(); + ext4_fsblk_t buf_block = 0; + struct ext4_extent_path *path = *orig_path; + int32_t depth, ppos = 0; + int32_t i; + int ret; + + eh = ext_inode_hdr(inode_ref->inode); + depth = ext_depth(inode_ref->inode); + + if (path) { + ext4_ext_drop_refs(inode_ref, path, 0); + if (depth > path[0].maxdepth) { + free(path); + *orig_path = path = NULL; + } + } + if (!path) { + int32_t path_depth = depth + 1; + /* account possible depth increase */ + path = calloc(1, sizeof(struct ext4_extent_path) * + (path_depth + 1)); + if (!path) + return ENOMEM; + path[0].maxdepth = path_depth; + } + path[0].header = eh; + path[0].block = bh; + + i = depth; + /* walk through the tree */ + while (i) { + ext4_ext_binsearch_idx(path + ppos, block); + path[ppos].p_block = ext4_idx_pblock(path[ppos].index); + path[ppos].depth = i; + path[ppos].extent = NULL; + buf_block = path[ppos].p_block; + + i--; + ppos++; + if (!path[ppos].block.lb_id || + path[ppos].block.lb_id != buf_block) { + ret = read_extent_tree_block(inode_ref, buf_block, i, + &bh, flags); + if (ret != EOK) { + goto err; + } + if (ppos > depth) { + ext4_block_set(inode_ref->fs->bdev, &bh); + ret = EIO; + goto err; + } + + eh = ext_block_hdr(&bh); + path[ppos].block = bh; + path[ppos].header = eh; + } + } + + path[ppos].depth = i; + path[ppos].extent = NULL; + path[ppos].index = NULL; + + /* find extent */ + ext4_ext_binsearch(path + ppos, block); + /* if not an empty leaf */ + if (path[ppos].extent) + path[ppos].p_block = ext4_ext_pblock(path[ppos].extent); + + *orig_path = path; + + ret = EOK; + return ret; + +err: + ext4_ext_drop_refs(inode_ref, path, 0); + free(path); + if (orig_path) + *orig_path = NULL; + return ret; +} + +static void ext4_ext_init_header(struct ext4_inode_ref *inode_ref, + struct ext4_extent_header *eh, int32_t depth) +{ + eh->entries_count = 0; + eh->max_entries_count = to_le16(ext4_ext_max_entries(inode_ref, depth)); + eh->magic = to_le16(EXT4_EXTENT_MAGIC); + eh->depth = depth; +} + +/* + * Be cautious, the buffer_head returned is not yet mark dirtied. */ +static int ext4_ext_split_node(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, int32_t at, + struct ext4_extent *newext, + ext4_fsblk_t *sibling, struct ext4_block *new_bh) +{ + int ret; + ext4_fsblk_t newblock; + struct ext4_block bh = EXT4_BLOCK_ZERO(); + int32_t depth = ext_depth(inode_ref->inode); + + ext4_assert(sibling); + + /* FIXME: currently we split at the point after the current extent. */ + newblock = ext4_ext_new_meta_block(inode_ref, path, newext, &ret, 0); + if (ret) + goto cleanup; + + /* For write access.# */ + ret = ext4_trans_block_get_noread(inode_ref->fs->bdev, &bh, newblock); + if (ret != EOK) + goto cleanup; + + if (at == depth) { + /* start copy from next extent */ + ptrdiff_t m = EXT_MAX_EXTENT(path[at].header) - path[at].extent; + struct ext4_extent_header *neh; + neh = ext_block_hdr(&bh); + ext4_ext_init_header(inode_ref, neh, 0); + if (m) { + struct ext4_extent *ex; + ex = EXT_FIRST_EXTENT(neh); + memmove(ex, path[at].extent + 1, + sizeof(struct ext4_extent) * m); + neh->entries_count = + to_le16(to_le16(neh->entries_count) + m); + path[at].header->entries_count = to_le16( + to_le16(path[at].header->entries_count) - m); + ret = ext4_ext_dirty(inode_ref, path + at); + if (ret) + goto cleanup; + } + } else { + ptrdiff_t m = EXT_MAX_INDEX(path[at].header) - path[at].index; + struct ext4_extent_header *neh; + neh = ext_block_hdr(&bh); + ext4_ext_init_header(inode_ref, neh, depth - at); + if (m) { + struct ext4_extent_index *ix; + ix = EXT_FIRST_INDEX(neh); + memmove(ix, path[at].index + 1, + sizeof(struct ext4_extent) * m); + neh->entries_count = + to_le16(to_le16(neh->entries_count) + m); + path[at].header->entries_count = to_le16( + to_le16(path[at].header->entries_count) - m); + ret = ext4_ext_dirty(inode_ref, path + at); + if (ret) + goto cleanup; + } + } +cleanup: + if (ret) { + if (bh.lb_id) { + ext4_block_set(inode_ref->fs->bdev, &bh); + } + if (newblock) + ext4_ext_free_blocks(inode_ref, newblock, 1, 0); + + newblock = 0; + } + *sibling = newblock; + *new_bh = bh; + return ret; +} + +static ext4_lblk_t ext4_ext_block_index(struct ext4_extent_header *eh) +{ + if (eh->depth) + return to_le32(EXT_FIRST_INDEX(eh)->first_block); + + return to_le32(EXT_FIRST_EXTENT(eh)->first_block); +} + +struct ext_split_trans { + ext4_fsblk_t ptr; + struct ext4_extent_path path; + int switch_to; +}; + +static int ext4_ext_insert_index(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, + int32_t at, + struct ext4_extent *newext, + ext4_lblk_t insert_index, + ext4_fsblk_t insert_block, + struct ext_split_trans *spt, + bool *need_grow) +{ + struct ext4_extent_index *ix; + struct ext4_extent_path *curp = path + at; + struct ext4_block bh = EXT4_BLOCK_ZERO(); + int32_t len; + int err; + struct ext4_extent_header *eh; + + *need_grow = false; + + if (curp->index && insert_index == to_le32(curp->index->first_block)) + return EIO; + + if (to_le16(curp->header->entries_count) == + to_le16(curp->header->max_entries_count)) { + if (at) { + struct ext4_extent_header *neh; + err = ext4_ext_split_node(inode_ref, path, at, newext, + &spt->ptr, &bh); + if (err != EOK) + goto out; + + neh = ext_block_hdr(&bh); + if (insert_index > to_le32(curp->index->first_block)) { + /* Make decision which node should be used to + * insert the index.*/ + if (to_le16(neh->entries_count) > + to_le16(curp->header->entries_count)) { + eh = curp->header; + /* insert after */ + ix = EXT_LAST_INDEX(eh) + 1; + } else { + eh = neh; + ix = EXT_FIRST_INDEX(eh); + } + } else { + eh = curp->header; + /* insert before */ + ix = EXT_LAST_INDEX(eh); + } + } else { + err = EOK; + *need_grow = true; + goto out; + } + } else { + eh = curp->header; + if (curp->index == NULL) { + ix = EXT_FIRST_INDEX(eh); + curp->index = ix; + } else if (insert_index > to_le32(curp->index->first_block)) { + /* insert after */ + ix = curp->index + 1; + } else { + /* insert before */ + ix = curp->index; + } + } + + len = EXT_LAST_INDEX(eh) - ix + 1; + ext4_assert(len >= 0); + if (len > 0) + memmove(ix + 1, ix, len * sizeof(struct ext4_extent_index)); + + if (ix > EXT_MAX_INDEX(eh)) { + err = EIO; + goto out; + } + + ix->first_block = to_le32(insert_index); + ext4_idx_store_pblock(ix, insert_block); + eh->entries_count = to_le16(to_le16(eh->entries_count) + 1); + + if (ix > EXT_LAST_INDEX(eh)) { + err = EIO; + goto out; + } + + if (eh == curp->header) + err = ext4_ext_dirty(inode_ref, curp); + else + err = EOK; + +out: + if (err != EOK || *need_grow) { + if (bh.lb_id) + ext4_block_set(inode_ref->fs->bdev, &bh); + + spt->ptr = 0; + } else if (bh.lb_id) { + /* If we got a sibling leaf. */ + ext4_extent_block_csum_set(inode_ref, ext_block_hdr(&bh)); + ext4_trans_set_block_dirty(bh.buf); + + spt->path.p_block = ext4_idx_pblock(ix); + spt->path.depth = to_le16(eh->depth); + spt->path.maxdepth = 0; + spt->path.extent = NULL; + spt->path.index = ix; + spt->path.header = eh; + spt->path.block = bh; + + /* + * If newext->ee_block can be included into the + * right sub-tree. + */ + if (to_le32(newext->first_block) >= + ext4_ext_block_index(ext_block_hdr(&bh))) + spt->switch_to = 1; + else { + curp->index = ix; + curp->p_block = ext4_idx_pblock(ix); + } + + } else { + spt->ptr = 0; + curp->index = ix; + curp->p_block = ext4_idx_pblock(ix); + } + return err; +} + +/* + * ext4_ext_correct_indexes: + * if leaf gets modified and modified extent is first in the leaf, + * then we have to correct all indexes above. + */ +static int ext4_ext_correct_indexes(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path) +{ + struct ext4_extent_header *eh; + int32_t depth = ext_depth(inode_ref->inode); + struct ext4_extent *ex; + uint32_t border; + int32_t k; + int err = EOK; + + eh = path[depth].header; + ex = path[depth].extent; + + if (ex == NULL || eh == NULL) + return EIO; + + if (depth == 0) { + /* there is no tree at all */ + return EOK; + } + + if (ex != EXT_FIRST_EXTENT(eh)) { + /* we correct tree if first leaf got modified only */ + return EOK; + } + + k = depth - 1; + border = path[depth].extent->first_block; + path[k].index->first_block = border; + err = ext4_ext_dirty(inode_ref, path + k); + if (err != EOK) + return err; + + while (k--) { + /* change all left-side indexes */ + if (path[k + 1].index != EXT_FIRST_INDEX(path[k + 1].header)) + break; + path[k].index->first_block = border; + err = ext4_ext_dirty(inode_ref, path + k); + if (err != EOK) + break; + } + + return err; +} + +static bool ext4_ext_can_prepend(struct ext4_extent *ex1, + struct ext4_extent *ex2) +{ + if (ext4_ext_pblock(ex2) + ext4_ext_get_actual_len(ex2) != + ext4_ext_pblock(ex1)) + return false; + +#ifdef AGGRESSIVE_TEST + if (ext4_ext_get_actual_len(ex1) + ext4_ext_get_actual_len(ex2) > 4) + return 0; +#else + if (ext4_ext_is_unwritten(ex1)) { + if (ext4_ext_get_actual_len(ex1) + + ext4_ext_get_actual_len(ex2) > + EXT_UNWRITTEN_MAX_LEN) + return false; + } else if (ext4_ext_get_actual_len(ex1) + ext4_ext_get_actual_len(ex2) > + EXT_INIT_MAX_LEN) + return false; +#endif + + if (to_le32(ex2->first_block) + ext4_ext_get_actual_len(ex2) != + to_le32(ex1->first_block)) + return false; + + return true; +} + +static bool ext4_ext_can_append(struct ext4_extent *ex1, + struct ext4_extent *ex2) +{ + if (ext4_ext_pblock(ex1) + ext4_ext_get_actual_len(ex1) != + ext4_ext_pblock(ex2)) + return false; + +#ifdef AGGRESSIVE_TEST + if (ext4_ext_get_actual_len(ex1) + ext4_ext_get_actual_len(ex2) > 4) + return 0; +#else + if (ext4_ext_is_unwritten(ex1)) { + if (ext4_ext_get_actual_len(ex1) + + ext4_ext_get_actual_len(ex2) > + EXT_UNWRITTEN_MAX_LEN) + return false; + } else if (ext4_ext_get_actual_len(ex1) + ext4_ext_get_actual_len(ex2) > + EXT_INIT_MAX_LEN) + return false; +#endif + + if (to_le32(ex1->first_block) + ext4_ext_get_actual_len(ex1) != + to_le32(ex2->first_block)) + return false; + + return true; +} + +static int ext4_ext_insert_leaf(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, + int32_t at, + struct ext4_extent *newext, + struct ext_split_trans *spt, + uint32_t flags, + bool *need_grow) +{ + struct ext4_extent_path *curp = path + at; + struct ext4_extent *ex = curp->extent; + struct ext4_block bh = EXT4_BLOCK_ZERO(); + int32_t len; + int err = EOK; + int unwritten; + struct ext4_extent_header *eh = NULL; + + *need_grow = false; + + if (curp->extent && + to_le32(newext->first_block) == to_le32(curp->extent->first_block)) + return EIO; + + if (!(flags & EXT4_EXT_NO_COMBINE)) { + if (curp->extent && ext4_ext_can_append(curp->extent, newext)) { + unwritten = ext4_ext_is_unwritten(curp->extent); + curp->extent->block_count = + to_le16(ext4_ext_get_actual_len(curp->extent) + + ext4_ext_get_actual_len(newext)); + if (unwritten) + ext4_ext_mark_unwritten(curp->extent); + err = ext4_ext_dirty(inode_ref, curp); + goto out; + } + + if (curp->extent && + ext4_ext_can_prepend(curp->extent, newext)) { + unwritten = ext4_ext_is_unwritten(curp->extent); + curp->extent->first_block = newext->first_block; + curp->extent->block_count = + to_le16(ext4_ext_get_actual_len(curp->extent) + + ext4_ext_get_actual_len(newext)); + if (unwritten) + ext4_ext_mark_unwritten(curp->extent); + err = ext4_ext_dirty(inode_ref, curp); + goto out; + } + } + + if (to_le16(curp->header->entries_count) == + to_le16(curp->header->max_entries_count)) { + if (at) { + struct ext4_extent_header *neh; + err = ext4_ext_split_node(inode_ref, path, at, newext, + &spt->ptr, &bh); + if (err != EOK) + goto out; + + neh = ext_block_hdr(&bh); + if (to_le32(newext->first_block) > + to_le32(curp->extent->first_block)) { + if (to_le16(neh->entries_count) > + to_le16(curp->header->entries_count)) { + eh = curp->header; + /* insert after */ + ex = EXT_LAST_EXTENT(eh) + 1; + } else { + eh = neh; + ex = EXT_FIRST_EXTENT(eh); + } + } else { + eh = curp->header; + /* insert before */ + ex = EXT_LAST_EXTENT(eh); + } + } else { + err = EOK; + *need_grow = true; + goto out; + } + } else { + eh = curp->header; + if (curp->extent == NULL) { + ex = EXT_FIRST_EXTENT(eh); + curp->extent = ex; + } else if (to_le32(newext->first_block) > + to_le32(curp->extent->first_block)) { + /* insert after */ + ex = curp->extent + 1; + } else { + /* insert before */ + ex = curp->extent; + } + } + + len = EXT_LAST_EXTENT(eh) - ex + 1; + ext4_assert(len >= 0); + if (len > 0) + memmove(ex + 1, ex, len * sizeof(struct ext4_extent)); + + if (ex > EXT_MAX_EXTENT(eh)) { + err = EIO; + goto out; + } + + ex->first_block = newext->first_block; + ex->block_count = newext->block_count; + ext4_ext_store_pblock(ex, ext4_ext_pblock(newext)); + eh->entries_count = to_le16(to_le16(eh->entries_count) + 1); + + if (ex > EXT_LAST_EXTENT(eh)) { + err = EIO; + goto out; + } + + if (eh == curp->header) { + err = ext4_ext_correct_indexes(inode_ref, path); + if (err != EOK) + goto out; + err = ext4_ext_dirty(inode_ref, curp); + } else + err = EOK; + +out: + if (err != EOK || *need_grow) { + if (bh.lb_id) + ext4_block_set(inode_ref->fs->bdev, &bh); + + spt->ptr = 0; + } else if (bh.lb_id) { + /* If we got a sibling leaf. */ + ext4_extent_block_csum_set(inode_ref, ext_block_hdr(&bh)); + ext4_trans_set_block_dirty(bh.buf); + + spt->path.p_block = ext4_ext_pblock(ex); + spt->path.depth = to_le16(eh->depth); + spt->path.maxdepth = 0; + spt->path.extent = ex; + spt->path.index = NULL; + spt->path.header = eh; + spt->path.block = bh; + + /* + * If newext->ee_block can be included into the + * right sub-tree. + */ + if (to_le32(newext->first_block) >= + ext4_ext_block_index(ext_block_hdr(&bh))) + spt->switch_to = 1; + else { + curp->extent = ex; + curp->p_block = ext4_ext_pblock(ex); + } + + } else { + spt->ptr = 0; + curp->extent = ex; + curp->p_block = ext4_ext_pblock(ex); + } + + return err; +} + +/* + * ext4_ext_grow_indepth: + * implements tree growing procedure: + * - allocates new block + * - moves top-level data (index block or leaf) into the new block + * - initializes new top-level, creating index that points to the + * just created block + */ +static int ext4_ext_grow_indepth(struct ext4_inode_ref *inode_ref, + uint32_t flags) +{ + struct ext4_extent_header *neh; + struct ext4_block bh = EXT4_BLOCK_ZERO(); + ext4_fsblk_t newblock, goal = 0; + int err = EOK; + + /* Try to prepend new index to old one */ + if (ext_depth(inode_ref->inode)) + goal = ext4_idx_pblock( + EXT_FIRST_INDEX(ext_inode_hdr(inode_ref->inode))); + else + goal = ext4_fs_inode_to_goal_block(inode_ref); + + newblock = ext4_new_meta_blocks(inode_ref, goal, flags, NULL, &err); + if (newblock == 0) + return err; + + /* # */ + err = ext4_trans_block_get_noread(inode_ref->fs->bdev, &bh, newblock); + if (err != EOK) { + ext4_ext_free_blocks(inode_ref, newblock, 1, 0); + return err; + } + + /* move top-level index/leaf into new block */ + memmove(bh.data, inode_ref->inode->blocks, + sizeof(inode_ref->inode->blocks)); + + /* set size of new block */ + neh = ext_block_hdr(&bh); + /* old root could have indexes or leaves + * so calculate e_max right way */ + if (ext_depth(inode_ref->inode)) + neh->max_entries_count = + to_le16(ext4_ext_space_block_idx(inode_ref)); + else + neh->max_entries_count = + to_le16(ext4_ext_space_block(inode_ref)); + + neh->magic = to_le16(EXT4_EXTENT_MAGIC); + ext4_extent_block_csum_set(inode_ref, neh); + + /* Update top-level index: num,max,pointer */ + neh = ext_inode_hdr(inode_ref->inode); + neh->entries_count = to_le16(1); + ext4_idx_store_pblock(EXT_FIRST_INDEX(neh), newblock); + if (neh->depth == 0) { + /* Root extent block becomes index block */ + neh->max_entries_count = + to_le16(ext4_ext_space_root_idx(inode_ref)); + EXT_FIRST_INDEX(neh) + ->first_block = EXT_FIRST_EXTENT(neh)->first_block; + } + neh->depth = to_le16(to_le16(neh->depth) + 1); + + ext4_trans_set_block_dirty(bh.buf); + inode_ref->dirty = true; + ext4_block_set(inode_ref->fs->bdev, &bh); + + return err; +} + +__unused static void print_path(struct ext4_extent_path *path) +{ + int32_t i = path->depth; + while (i >= 0) { + + ptrdiff_t a = + (path->extent) + ? (path->extent - EXT_FIRST_EXTENT(path->header)) + : 0; + ptrdiff_t b = + (path->index) + ? (path->index - EXT_FIRST_INDEX(path->header)) + : 0; + + (void)a; + (void)b; + ext4_dbg(DEBUG_EXTENT, + "depth %" PRId32 ", p_block: %" PRIu64 "," + "p_ext offset: %td, p_idx offset: %td\n", + i, path->p_block, a, b); + i--; + path++; + } +} + +static void ext4_ext_replace_path(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, + struct ext_split_trans *spt, + int32_t level) +{ + int32_t depth = ext_depth(inode_ref->inode); + int32_t i = depth - level; + ext4_ext_drop_refs(inode_ref, path + i, 1); + path[i] = spt[level].path; +} + +static int ext4_ext_insert_extent(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path **ppath, + struct ext4_extent *newext, uint32_t flags) +{ + int32_t i, depth, level; + int ret = EOK; + ext4_fsblk_t ptr = 0; + bool need_grow = false; + struct ext4_extent_path *path = *ppath; + struct ext_split_trans *spt = NULL; + struct ext_split_trans newblock; + + memset(&newblock, 0, sizeof(newblock)); + + depth = ext_depth(inode_ref->inode); + for (i = depth, level = 0; i >= 0; i--, level++) + if (EXT_HAS_FREE_INDEX(path + i)) + break; + + if (level) { + spt = calloc(1, sizeof(struct ext_split_trans) * (level)); + if (!spt) { + ret = ENOMEM; + goto out; + } + } + i = 0; +again: + depth = ext_depth(inode_ref->inode); + + do { + if (!i) { + ret = ext4_ext_insert_leaf(inode_ref, path, depth - i, + newext, &newblock, flags, + &need_grow); + } else { + ret = ext4_ext_insert_index( + inode_ref, path, depth - i, newext, + ext4_ext_block_index( + ext_block_hdr(&spt[i - 1].path.block)), + spt[i - 1].ptr, &newblock, + &need_grow); + } + ptr = newblock.ptr; + + if (ret != EOK) + goto out; + + else if (spt && ptr && !ret) { + /* Prepare for the next iteration after splitting. */ + spt[i] = newblock; + } + + i++; + } while (ptr != 0 && i <= depth); + + if (need_grow) { + ret = ext4_ext_grow_indepth(inode_ref, 0); + if (ret) + goto out; + ret = ext4_find_extent(inode_ref, to_le32(newext->first_block), + ppath, 0); + if (ret) + goto out; + i = depth; + path = *ppath; + goto again; + } +out: + if (ret) { + if (path) + ext4_ext_drop_refs(inode_ref, path, 0); + + while (--level >= 0 && spt) { + if (spt[level].ptr) { + ext4_ext_free_blocks(inode_ref, spt[level].ptr, + 1, 0); + ext4_ext_drop_refs(inode_ref, &spt[level].path, + 1); + } + } + } else { + while (--level >= 0 && spt) { + if (spt[level].switch_to) + ext4_ext_replace_path(inode_ref, path, spt, + level); + else if (spt[level].ptr) + ext4_ext_drop_refs(inode_ref, &spt[level].path, + 1); + } + } + if (spt) + free(spt); + + return ret; +} + +static void ext4_ext_remove_blocks(struct ext4_inode_ref *inode_ref, + struct ext4_extent *ex, ext4_lblk_t from, + ext4_lblk_t to) +{ + ext4_lblk_t len = to - from + 1; + ext4_lblk_t num; + ext4_fsblk_t start; + num = from - to_le32(ex->first_block); + start = ext4_ext_pblock(ex) + num; + ext4_dbg(DEBUG_EXTENT, + "Freeing %" PRIu32 " at %" PRIu64 ", %" PRIu32 "\n", from, + start, len); + + ext4_ext_free_blocks(inode_ref, start, len, 0); +} + +static int ext4_ext_remove_idx(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, int32_t depth) +{ + int err = EOK; + int32_t i = depth; + ext4_fsblk_t leaf; + + /* free index block */ + leaf = ext4_idx_pblock(path[i].index); + + if (path[i].index != EXT_LAST_INDEX(path[i].header)) { + ptrdiff_t len = EXT_LAST_INDEX(path[i].header) - path[i].index; + memmove(path[i].index, path[i].index + 1, + len * sizeof(struct ext4_extent_index)); + } + + path[i].header->entries_count = + to_le16(to_le16(path[i].header->entries_count) - 1); + err = ext4_ext_dirty(inode_ref, path + i); + if (err != EOK) + return err; + + ext4_dbg(DEBUG_EXTENT, "IDX: Freeing %" PRIu32 " at %" PRIu64 ", %d\n", + to_le32(path[i].index->first_block), leaf, 1); + ext4_ext_free_blocks(inode_ref, leaf, 1, 0); + + while (i > 0) { + if (path[i].index != EXT_FIRST_INDEX(path[i].header)) + break; + + path[i - 1].index->first_block = path[i].index->first_block; + err = ext4_ext_dirty(inode_ref, path + i - 1); + if (err != EOK) + break; + + i--; + } + return err; +} + +static int ext4_ext_remove_leaf(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path *path, ext4_lblk_t from, + ext4_lblk_t to) +{ + + int32_t depth = ext_depth(inode_ref->inode); + struct ext4_extent *ex = path[depth].extent; + struct ext4_extent *start_ex, *ex2 = NULL; + struct ext4_extent_header *eh = path[depth].header; + int32_t len; + int err = EOK; + uint16_t new_entries; + + start_ex = ex; + new_entries = to_le16(eh->entries_count); + while (ex <= EXT_LAST_EXTENT(path[depth].header) && + to_le32(ex->first_block) <= to) { + int32_t new_len = 0; + int unwritten; + ext4_lblk_t start, new_start; + ext4_fsblk_t newblock; + new_start = start = to_le32(ex->first_block); + len = ext4_ext_get_actual_len(ex); + newblock = ext4_ext_pblock(ex); + if (start < from) { + len -= from - start; + new_len = from - start; + start = from; + start_ex++; + } else { + if (start + len - 1 > to) { + len -= start + len - 1 - to; + new_len = start + len - 1 - to; + new_start = to + 1; + newblock += to + 1 - start; + ex2 = ex; + } + } + + ext4_ext_remove_blocks(inode_ref, ex, start, start + len - 1); + ex->first_block = to_le32(new_start); + if (!new_len) + new_entries--; + else { + unwritten = ext4_ext_is_unwritten(ex); + ex->block_count = to_le16(new_len); + ext4_ext_store_pblock(ex, newblock); + if (unwritten) + ext4_ext_mark_unwritten(ex); + } + + ex += 1; + } + + if (ex2 == NULL) + ex2 = ex; + + if (ex2 <= EXT_LAST_EXTENT(eh)) + memmove(start_ex, ex2, EXT_LAST_EXTENT(eh) - ex2 + 1); + + eh->entries_count = to_le16(new_entries); + ext4_ext_dirty(inode_ref, path + depth); + if (path[depth].extent == EXT_FIRST_EXTENT(eh) && eh->entries_count) + err = ext4_ext_correct_indexes(inode_ref, path); + + /* if this leaf is free, then we should + * remove it from index block above */ + if (err == EOK && eh->entries_count == 0 && path[depth].block.lb_id) + err = ext4_ext_remove_idx(inode_ref, path, depth - 1); + else if (depth > 0) + path[depth - 1].index++; + + return err; +} + +static bool ext4_ext_more_to_rm(struct ext4_extent_path *path, ext4_lblk_t to) +{ + if (!to_le16(path->header->entries_count)) + return false; + + if (path->index > EXT_LAST_INDEX(path->header)) + return false; + + if (to_le32(path->index->first_block) > to) + return false; + + return true; +} + +int ext4_extent_remove_space(struct ext4_inode_ref *inode_ref, ext4_lblk_t from, + ext4_lblk_t to) +{ + struct ext4_extent_path *path = NULL; + int ret = EOK; + int32_t depth = ext_depth(inode_ref->inode); + int32_t i; + + ret = ext4_find_extent(inode_ref, from, &path, 0); + if (ret) + goto out; + + if (!path[depth].extent) { + ret = EOK; + goto out; + } + + bool in_range = IN_RANGE(from, to_le32(path[depth].extent->first_block), + ext4_ext_get_actual_len(path[depth].extent)); + + if (!in_range) { + ret = EOK; + goto out; + } + + /* If we do remove_space inside the range of an extent */ + if ((to_le32(path[depth].extent->first_block) < from) && + (to < to_le32(path[depth].extent->first_block) + + ext4_ext_get_actual_len(path[depth].extent) - 1)) { + + struct ext4_extent *ex = path[depth].extent, newex; + int unwritten = ext4_ext_is_unwritten(ex); + ext4_lblk_t ee_block = to_le32(ex->first_block); + int32_t len = ext4_ext_get_actual_len(ex); + ext4_fsblk_t newblock = + to + 1 - ee_block + ext4_ext_pblock(ex); + + ex->block_count = to_le16(from - ee_block); + if (unwritten) + ext4_ext_mark_unwritten(ex); + + ext4_ext_dirty(inode_ref, path + depth); + + newex.first_block = to_le32(to + 1); + newex.block_count = to_le16(ee_block + len - 1 - to); + ext4_ext_store_pblock(&newex, newblock); + if (unwritten) + ext4_ext_mark_unwritten(&newex); + + ret = ext4_ext_insert_extent(inode_ref, &path, &newex, 0); + goto out; + } + + i = depth; + while (i >= 0) { + if (i == depth) { + struct ext4_extent_header *eh; + struct ext4_extent *first_ex, *last_ex; + ext4_lblk_t leaf_from, leaf_to; + eh = path[i].header; + ext4_assert(to_le16(eh->entries_count) > 0); + first_ex = EXT_FIRST_EXTENT(eh); + last_ex = EXT_LAST_EXTENT(eh); + leaf_from = to_le32(first_ex->first_block); + leaf_to = to_le32(last_ex->first_block) + + ext4_ext_get_actual_len(last_ex) - 1; + if (leaf_from < from) + leaf_from = from; + + if (leaf_to > to) + leaf_to = to; + + ext4_ext_remove_leaf(inode_ref, path, leaf_from, + leaf_to); + ext4_ext_drop_refs(inode_ref, path + i, 0); + i--; + continue; + } + + struct ext4_extent_header *eh; + eh = path[i].header; + if (ext4_ext_more_to_rm(path + i, to)) { + struct ext4_block bh = EXT4_BLOCK_ZERO(); + if (path[i + 1].block.lb_id) + ext4_ext_drop_refs(inode_ref, path + i + 1, 0); + + ret = read_extent_tree_block(inode_ref, + ext4_idx_pblock(path[i].index), + depth - i - 1, &bh, 0); + if (ret) + goto out; + + path[i].p_block = + ext4_idx_pblock(path[i].index); + path[i + 1].block = bh; + path[i + 1].header = ext_block_hdr(&bh); + path[i + 1].depth = depth - i - 1; + if (i + 1 == depth) + path[i + 1].extent = EXT_FIRST_EXTENT( + path[i + 1].header); + else + path[i + 1].index = + EXT_FIRST_INDEX(path[i + 1].header); + + i++; + } else { + if (i > 0) { + if (!eh->entries_count) + ret = ext4_ext_remove_idx(inode_ref, path, + i - 1); + else + path[i - 1].index++; + + } + + if (i) + ext4_block_set(inode_ref->fs->bdev, + &path[i].block); + + + i--; + } + + } + + /* TODO: flexible tree reduction should be here */ + if (path->header->entries_count == 0) { + /* + * truncate to zero freed all the tree, + * so we need to correct eh_depth + */ + ext_inode_hdr(inode_ref->inode)->depth = 0; + ext_inode_hdr(inode_ref->inode)->max_entries_count = + to_le16(ext4_ext_space_root(inode_ref)); + ret = ext4_ext_dirty(inode_ref, path); + } + +out: + ext4_ext_drop_refs(inode_ref, path, 0); + free(path); + path = NULL; + return ret; +} + +static int ext4_ext_split_extent_at(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path **ppath, + ext4_lblk_t split, uint32_t split_flag) +{ + struct ext4_extent *ex, newex; + ext4_fsblk_t newblock; + ext4_lblk_t ee_block; + int32_t ee_len; + int32_t depth = ext_depth(inode_ref->inode); + int err = EOK; + + ex = (*ppath)[depth].extent; + ee_block = to_le32(ex->first_block); + ee_len = ext4_ext_get_actual_len(ex); + newblock = split - ee_block + ext4_ext_pblock(ex); + + if (split == ee_block) { + /* + * case b: block @split is the block that the extent begins with + * then we just change the state of the extent, and splitting + * is not needed. + */ + if (split_flag & EXT4_EXT_MARK_UNWRIT2) + ext4_ext_mark_unwritten(ex); + else + ext4_ext_mark_initialized(ex); + + err = ext4_ext_dirty(inode_ref, *ppath + depth); + goto out; + } + + ex->block_count = to_le16(split - ee_block); + if (split_flag & EXT4_EXT_MARK_UNWRIT1) + ext4_ext_mark_unwritten(ex); + + err = ext4_ext_dirty(inode_ref, *ppath + depth); + if (err != EOK) + goto out; + + newex.first_block = to_le32(split); + newex.block_count = to_le16(ee_len - (split - ee_block)); + ext4_ext_store_pblock(&newex, newblock); + if (split_flag & EXT4_EXT_MARK_UNWRIT2) + ext4_ext_mark_unwritten(&newex); + err = ext4_ext_insert_extent(inode_ref, ppath, &newex, + EXT4_EXT_NO_COMBINE); + if (err != EOK) + goto restore_extent_len; + +out: + return err; +restore_extent_len: + ex->block_count = to_le16(ee_len); + err = ext4_ext_dirty(inode_ref, *ppath + depth); + return err; +} + +static int ext4_ext_convert_to_initialized(struct ext4_inode_ref *inode_ref, + struct ext4_extent_path **ppath, + ext4_lblk_t split, uint32_t blocks) +{ + int32_t depth = ext_depth(inode_ref->inode), err = EOK; + struct ext4_extent *ex = (*ppath)[depth].extent; + + ext4_assert(to_le32(ex->first_block) <= split); + + if (split + blocks == + to_le32(ex->first_block) + ext4_ext_get_actual_len(ex)) { + /* split and initialize right part */ + err = ext4_ext_split_extent_at(inode_ref, ppath, split, + EXT4_EXT_MARK_UNWRIT1); + } else if (to_le32(ex->first_block) == split) { + /* split and initialize left part */ + err = ext4_ext_split_extent_at(inode_ref, ppath, split + blocks, + EXT4_EXT_MARK_UNWRIT2); + } else { + /* split 1 extent to 3 and initialize the 2nd */ + err = ext4_ext_split_extent_at(inode_ref, ppath, split + blocks, + EXT4_EXT_MARK_UNWRIT1 | + EXT4_EXT_MARK_UNWRIT2); + if (!err) { + err = ext4_ext_split_extent_at(inode_ref, ppath, split, + EXT4_EXT_MARK_UNWRIT1); + } + } + + return err; +} + +static ext4_lblk_t ext4_ext_next_allocated_block(struct ext4_extent_path *path) +{ + int32_t depth; + + depth = path->depth; + + if (depth == 0 && path->extent == NULL) + return EXT_MAX_BLOCKS; + + while (depth >= 0) { + if (depth == path->depth) { + /* leaf */ + if (path[depth].extent && + path[depth].extent != + EXT_LAST_EXTENT(path[depth].header)) + return to_le32( + path[depth].extent[1].first_block); + } else { + /* index */ + if (path[depth].index != + EXT_LAST_INDEX(path[depth].header)) + return to_le32( + path[depth].index[1].first_block); + } + depth--; + } + + return EXT_MAX_BLOCKS; +} + +static int ext4_ext_zero_unwritten_range(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t block, + uint32_t blocks_count) +{ + int err = EOK; + uint32_t i; + uint32_t block_size = ext4_sb_get_block_size(&inode_ref->fs->sb); + for (i = 0; i < blocks_count; i++) { + struct ext4_block bh = EXT4_BLOCK_ZERO(); + err = ext4_trans_block_get_noread(inode_ref->fs->bdev, &bh, block + i); + if (err != EOK) + break; + + memset(bh.data, 0, block_size); + ext4_trans_set_block_dirty(bh.buf); + err = ext4_block_set(inode_ref->fs->bdev, &bh); + if (err != EOK) + break; + } + return err; +} + +int ext4_extent_get_blocks(struct ext4_inode_ref *inode_ref, ext4_lblk_t iblock, + uint32_t max_blocks, ext4_fsblk_t *result, bool create, + uint32_t *blocks_count) +{ + struct ext4_extent_path *path = NULL; + struct ext4_extent newex, *ex; + ext4_fsblk_t goal; + int err = EOK; + int32_t depth; + uint32_t allocated = 0; + ext4_lblk_t next; + ext4_fsblk_t newblock; + + if (result) + *result = 0; + + if (blocks_count) + *blocks_count = 0; + + /* find extent for this block */ + err = ext4_find_extent(inode_ref, iblock, &path, 0); + if (err != EOK) { + path = NULL; + goto out2; + } + + depth = ext_depth(inode_ref->inode); + + /* + * consistent leaf must not be empty + * this situations is possible, though, _during_ tree modification + * this is why assert can't be put in ext4_ext_find_extent() + */ + ex = path[depth].extent; + if (ex) { + ext4_lblk_t ee_block = to_le32(ex->first_block); + ext4_fsblk_t ee_start = ext4_ext_pblock(ex); + uint16_t ee_len = ext4_ext_get_actual_len(ex); + /* if found exent covers block, simple return it */ + if (IN_RANGE(iblock, ee_block, ee_len)) { + /* number of remain blocks in the extent */ + allocated = ee_len - (iblock - ee_block); + + if (!ext4_ext_is_unwritten(ex)) { + newblock = iblock - ee_block + ee_start; + goto out; + } + + if (!create) { + newblock = 0; + goto out; + } + + uint32_t zero_range; + zero_range = allocated; + if (zero_range > max_blocks) + zero_range = max_blocks; + + newblock = iblock - ee_block + ee_start; + err = ext4_ext_zero_unwritten_range(inode_ref, newblock, + zero_range); + if (err != EOK) + goto out2; + + err = ext4_ext_convert_to_initialized(inode_ref, &path, + iblock, zero_range); + if (err != EOK) + goto out2; + + goto out; + } + } + + /* + * requested block isn't allocated yet + * we couldn't try to create block if create flag is zero + */ + if (!create) { + goto out2; + } + + /* find next allocated block so that we know how many + * blocks we can allocate without ovelapping next extent */ + next = ext4_ext_next_allocated_block(path); + allocated = next - iblock; + if (allocated > max_blocks) + allocated = max_blocks; + + /* allocate new block */ + goal = ext4_ext_find_goal(inode_ref, path, iblock); + newblock = ext4_new_meta_blocks(inode_ref, goal, 0, &allocated, &err); + if (!newblock) + goto out2; + + /* try to insert new extent into found leaf and return */ + newex.first_block = to_le32(iblock); + ext4_ext_store_pblock(&newex, newblock); + newex.block_count = to_le16(allocated); + err = ext4_ext_insert_extent(inode_ref, &path, &newex, 0); + if (err != EOK) { + /* free data blocks we just allocated */ + ext4_ext_free_blocks(inode_ref, ext4_ext_pblock(&newex), + to_le16(newex.block_count), 0); + goto out2; + } + + /* previous routine could use block we allocated */ + newblock = ext4_ext_pblock(&newex); + +out: + if (allocated > max_blocks) + allocated = max_blocks; + + if (result) + *result = newblock; + + if (blocks_count) + *blocks_count = allocated; + +out2: + if (path) { + ext4_ext_drop_refs(inode_ref, path, 0); + free(path); + } + + return err; +} diff --git a/src/ext4_fs.c b/src/ext4_fs.c new file mode 100644 index 0000000..392728d --- /dev/null +++ b/src/ext4_fs.c @@ -0,0 +1,1713 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_fs.c + * @brief More complex filesystem functions. + */ + +#include "ext4_config.h" +#include "ext4_types.h" +#include "ext4_fs.h" +#include "ext4_errno.h" +#include "ext4_blockdev.h" +#include "ext4_super.h" +#include "ext4_crc32.h" +#include "ext4_debug.h" +#include "ext4_block_group.h" +#include "ext4_balloc.h" +#include "ext4_bitmap.h" +#include "ext4_inode.h" +#include "ext4_ialloc.h" +#include "ext4_extent.h" + +#include <string.h> + +int ext4_fs_init(struct ext4_fs *fs, struct ext4_blockdev *bdev) +{ + int r, i; + uint16_t tmp; + uint32_t bsize; + bool read_only = false; + + ext4_assert(fs && bdev); + + fs->bdev = bdev; + + r = ext4_sb_read(fs->bdev, &fs->sb); + if (r != EOK) + return r; + + if (!ext4_sb_check(&fs->sb)) + return ENOTSUP; + + bsize = ext4_sb_get_block_size(&fs->sb); + if (bsize > EXT4_MAX_BLOCK_SIZE) + return ENXIO; + + r = ext4_fs_check_features(fs, &read_only); + if (r != EOK) + return r; + + if (read_only) + return ENOTSUP; + + /* Compute limits for indirect block levels */ + uint32_t blocks_id = bsize / sizeof(uint32_t); + + fs->inode_block_limits[0] = EXT4_INODE_DIRECT_BLOCK_COUNT; + fs->inode_blocks_per_level[0] = 1; + + for (i = 1; i < 4; i++) { + fs->inode_blocks_per_level[i] = + fs->inode_blocks_per_level[i - 1] * blocks_id; + fs->inode_block_limits[i] = fs->inode_block_limits[i - 1] + + fs->inode_blocks_per_level[i]; + } + + /*Validate FS*/ + tmp = ext4_get16(&fs->sb, state); + if (tmp & EXT4_SUPERBLOCK_STATE_ERROR_FS) + ext4_dbg(DEBUG_FS, DBG_WARN + "last umount error: superblock fs_error flag\n"); + + + /* Mark system as mounted */ + ext4_set16(&fs->sb, state, EXT4_SUPERBLOCK_STATE_ERROR_FS); + r = ext4_sb_write(fs->bdev, &fs->sb); + if (r != EOK) + return r; + + /*Update mount count*/ + ext4_set16(&fs->sb, mount_count, ext4_get16(&fs->sb, mount_count) + 1); + + return r; +} + +int ext4_fs_fini(struct ext4_fs *fs) +{ + ext4_assert(fs); + + /*Set superblock state*/ + ext4_set16(&fs->sb, state, EXT4_SUPERBLOCK_STATE_VALID_FS); + + return ext4_sb_write(fs->bdev, &fs->sb); +} + +static void ext4_fs_debug_features_inc(uint32_t features_incompatible) +{ + if (features_incompatible & EXT4_FINCOM_COMPRESSION) + ext4_dbg(DEBUG_FS, DBG_NONE "compression\n"); + if (features_incompatible & EXT4_FINCOM_FILETYPE) + ext4_dbg(DEBUG_FS, DBG_NONE "filetype\n"); + if (features_incompatible & EXT4_FINCOM_RECOVER) + ext4_dbg(DEBUG_FS, DBG_NONE "recover\n"); + if (features_incompatible & EXT4_FINCOM_JOURNAL_DEV) + ext4_dbg(DEBUG_FS, DBG_NONE "journal_dev\n"); + if (features_incompatible & EXT4_FINCOM_META_BG) + ext4_dbg(DEBUG_FS, DBG_NONE "meta_bg\n"); + if (features_incompatible & EXT4_FINCOM_EXTENTS) + ext4_dbg(DEBUG_FS, DBG_NONE "extents\n"); + if (features_incompatible & EXT4_FINCOM_64BIT) + ext4_dbg(DEBUG_FS, DBG_NONE "64bit\n"); + if (features_incompatible & EXT4_FINCOM_MMP) + ext4_dbg(DEBUG_FS, DBG_NONE "mnp\n"); + if (features_incompatible & EXT4_FINCOM_FLEX_BG) + ext4_dbg(DEBUG_FS, DBG_NONE "flex_bg\n"); + if (features_incompatible & EXT4_FINCOM_EA_INODE) + ext4_dbg(DEBUG_FS, DBG_NONE "ea_inode\n"); + if (features_incompatible & EXT4_FINCOM_DIRDATA) + ext4_dbg(DEBUG_FS, DBG_NONE "dirdata\n"); + if (features_incompatible & EXT4_FINCOM_BG_USE_META_CSUM) + ext4_dbg(DEBUG_FS, DBG_NONE "meta_csum\n"); + if (features_incompatible & EXT4_FINCOM_LARGEDIR) + ext4_dbg(DEBUG_FS, DBG_NONE "largedir\n"); + if (features_incompatible & EXT4_FINCOM_INLINE_DATA) + ext4_dbg(DEBUG_FS, DBG_NONE "inline_data\n"); +} +static void ext4_fs_debug_features_comp(uint32_t features_compatible) +{ + if (features_compatible & EXT4_FCOM_DIR_PREALLOC) + ext4_dbg(DEBUG_FS, DBG_NONE "dir_prealloc\n"); + if (features_compatible & EXT4_FCOM_IMAGIC_INODES) + ext4_dbg(DEBUG_FS, DBG_NONE "imagic_inodes\n"); + if (features_compatible & EXT4_FCOM_HAS_JOURNAL) + ext4_dbg(DEBUG_FS, DBG_NONE "has_journal\n"); + if (features_compatible & EXT4_FCOM_EXT_ATTR) + ext4_dbg(DEBUG_FS, DBG_NONE "ext_attr\n"); + if (features_compatible & EXT4_FCOM_RESIZE_INODE) + ext4_dbg(DEBUG_FS, DBG_NONE "resize_inode\n"); + if (features_compatible & EXT4_FCOM_DIR_INDEX) + ext4_dbg(DEBUG_FS, DBG_NONE "dir_index\n"); +} + +static void ext4_fs_debug_features_ro(uint32_t features_ro) +{ + if (features_ro & EXT4_FRO_COM_SPARSE_SUPER) + ext4_dbg(DEBUG_FS, DBG_NONE "sparse_super\n"); + if (features_ro & EXT4_FRO_COM_LARGE_FILE) + ext4_dbg(DEBUG_FS, DBG_NONE "large_file\n"); + if (features_ro & EXT4_FRO_COM_BTREE_DIR) + ext4_dbg(DEBUG_FS, DBG_NONE "btree_dir\n"); + if (features_ro & EXT4_FRO_COM_HUGE_FILE) + ext4_dbg(DEBUG_FS, DBG_NONE "huge_file\n"); + if (features_ro & EXT4_FRO_COM_GDT_CSUM) + ext4_dbg(DEBUG_FS, DBG_NONE "gtd_csum\n"); + if (features_ro & EXT4_FRO_COM_DIR_NLINK) + ext4_dbg(DEBUG_FS, DBG_NONE "dir_nlink\n"); + if (features_ro & EXT4_FRO_COM_EXTRA_ISIZE) + ext4_dbg(DEBUG_FS, DBG_NONE "extra_isize\n"); + if (features_ro & EXT4_FRO_COM_QUOTA) + ext4_dbg(DEBUG_FS, DBG_NONE "quota\n"); + if (features_ro & EXT4_FRO_COM_BIGALLOC) + ext4_dbg(DEBUG_FS, DBG_NONE "bigalloc\n"); + if (features_ro & EXT4_FRO_COM_METADATA_CSUM) + ext4_dbg(DEBUG_FS, DBG_NONE "metadata_csum\n"); +} + +int ext4_fs_check_features(struct ext4_fs *fs, bool *read_only) +{ + ext4_assert(fs && read_only); + uint32_t v; + if (ext4_get32(&fs->sb, rev_level) == 0) { + *read_only = false; + return EOK; + } + + ext4_dbg(DEBUG_FS, DBG_INFO "sblock features_incompatible:\n"); + ext4_fs_debug_features_inc(ext4_get32(&fs->sb, features_incompatible)); + + ext4_dbg(DEBUG_FS, DBG_INFO "sblock features_compatible:\n"); + ext4_fs_debug_features_comp(ext4_get32(&fs->sb, features_compatible)); + + ext4_dbg(DEBUG_FS, DBG_INFO "sblock features_read_only:\n"); + ext4_fs_debug_features_ro(ext4_get32(&fs->sb, features_read_only)); + + /*Check features_incompatible*/ + v = (ext4_get32(&fs->sb, features_incompatible) & + (~CONFIG_SUPPORTED_FINCOM)); + if (v) { + ext4_dbg(DEBUG_FS, DBG_ERROR + "sblock has unsupported features incompatible:\n"); + ext4_fs_debug_features_inc(v); + return ENOTSUP; + } + + /*Check features_read_only*/ + v = ext4_get32(&fs->sb, features_read_only); + v &= ~CONFIG_SUPPORTED_FRO_COM; + if (v) { + ext4_dbg(DEBUG_FS, DBG_WARN + "sblock has unsupported features read only:\n"); + ext4_fs_debug_features_ro(v); + *read_only = true; + return EOK; + } + *read_only = false; + + return EOK; +} + +/**@brief Determine whether the block is inside the group. + * @param baddr block address + * @param bgid block group id + * @return Error code + */ +static bool ext4_block_in_group(struct ext4_sblock *s, ext4_fsblk_t baddr, + uint32_t bgid) +{ + uint32_t actual_bgid; + actual_bgid = ext4_balloc_get_bgid_of_block(s, baddr); + if (actual_bgid == bgid) + return true; + return false; +} + +/**@brief To avoid calling the atomic setbit hundreds or thousands of times, we only + * need to use it within a single byte (to ensure we get endianness right). + * We can use memset for the rest of the bitmap as there are no other users. + */ +static void ext4_fs_mark_bitmap_end(int start_bit, int end_bit, void *bitmap) +{ + int i; + + if (start_bit >= end_bit) + return; + + for (i = start_bit; (unsigned)i < ((start_bit + 7) & ~7UL); i++) + ext4_bmap_bit_set(bitmap, i); + + if (i < end_bit) + memset((char *)bitmap + (i >> 3), 0xff, (end_bit - i) >> 3); +} + +/**@brief Initialize block bitmap in block group. + * @param bg_ref Reference to block group + * @return Error code + */ +static int ext4_fs_init_block_bitmap(struct ext4_block_group_ref *bg_ref) +{ + struct ext4_sblock *sb = &bg_ref->fs->sb; + struct ext4_bgroup *bg = bg_ref->block_group; + int rc; + + uint32_t i, bit, bit_max; + uint32_t group_blocks; + uint16_t inode_size = ext4_get16(sb, inode_size); + uint32_t block_size = ext4_sb_get_block_size(sb); + uint32_t inodes_per_group = ext4_get32(sb, inodes_per_group); + + ext4_fsblk_t bmp_blk = ext4_bg_get_block_bitmap(bg, sb); + ext4_fsblk_t bmp_inode = ext4_bg_get_inode_bitmap(bg, sb); + ext4_fsblk_t inode_table = ext4_bg_get_inode_table_first_block(bg, sb); + ext4_fsblk_t first_bg = ext4_balloc_get_block_of_bgid(sb, bg_ref->index); + + uint32_t dsc_per_block = block_size / ext4_sb_get_desc_size(sb); + + bool flex_bg = ext4_sb_feature_incom(sb, EXT4_FINCOM_FLEX_BG); + bool meta_bg = ext4_sb_feature_incom(sb, EXT4_FINCOM_META_BG); + + uint32_t inode_table_bcnt = inodes_per_group * inode_size / block_size; + + struct ext4_block block_bitmap; + rc = ext4_trans_block_get_noread(bg_ref->fs->bdev, &block_bitmap, bmp_blk); + if (rc != EOK) + return rc; + + memset(block_bitmap.data, 0, block_size); + bit_max = ext4_sb_is_super_in_bg(sb, bg_ref->index); + + uint32_t count = ext4_sb_first_meta_bg(sb) * dsc_per_block; + if (!meta_bg || bg_ref->index < count) { + if (bit_max) { + bit_max += ext4_bg_num_gdb(sb, bg_ref->index); + bit_max += ext4_get16(sb, s_reserved_gdt_blocks); + } + } else { /* For META_BG_BLOCK_GROUPS */ + bit_max += ext4_bg_num_gdb(sb, bg_ref->index); + } + for (bit = 0; bit < bit_max; bit++) + ext4_bmap_bit_set(block_bitmap.data, bit); + + if (bg_ref->index == ext4_block_group_cnt(sb) - 1) { + /* + * Even though mke2fs always initialize first and last group + * if some other tool enabled the EXT4_BG_BLOCK_UNINIT we need + * to make sure we calculate the right free blocks + */ + + group_blocks = (uint32_t)(ext4_sb_get_blocks_cnt(sb) - + ext4_get32(sb, first_data_block) - + ext4_get32(sb, blocks_per_group) * + (ext4_block_group_cnt(sb) - 1)); + } else { + group_blocks = ext4_get32(sb, blocks_per_group); + } + + bool in_bg; + in_bg = ext4_block_in_group(sb, bmp_blk, bg_ref->index); + if (!flex_bg || in_bg) + ext4_bmap_bit_set(block_bitmap.data, + (uint32_t)(bmp_blk - first_bg)); + + in_bg = ext4_block_in_group(sb, bmp_inode, bg_ref->index); + if (!flex_bg || in_bg) + ext4_bmap_bit_set(block_bitmap.data, + (uint32_t)(bmp_inode - first_bg)); + + for (i = inode_table; i < inode_table + inode_table_bcnt; i++) { + in_bg = ext4_block_in_group(sb, i, bg_ref->index); + if (!flex_bg || in_bg) + ext4_bmap_bit_set(block_bitmap.data, + (uint32_t)(i - first_bg)); + } + /* + * Also if the number of blocks within the group is + * less than the blocksize * 8 ( which is the size + * of bitmap ), set rest of the block bitmap to 1 + */ + ext4_fs_mark_bitmap_end(group_blocks, block_size * 8, block_bitmap.data); + ext4_trans_set_block_dirty(block_bitmap.buf); + + ext4_balloc_set_bitmap_csum(sb, bg_ref->block_group, block_bitmap.data); + bg_ref->dirty = true; + + /* Save bitmap */ + return ext4_block_set(bg_ref->fs->bdev, &block_bitmap); +} + +/**@brief Initialize i-node bitmap in block group. + * @param bg_ref Reference to block group + * @return Error code + */ +static int ext4_fs_init_inode_bitmap(struct ext4_block_group_ref *bg_ref) +{ + int rc; + struct ext4_sblock *sb = &bg_ref->fs->sb; + struct ext4_bgroup *bg = bg_ref->block_group; + + /* Load bitmap */ + ext4_fsblk_t bitmap_block_addr = ext4_bg_get_inode_bitmap(bg, sb); + + struct ext4_block b; + rc = ext4_trans_block_get_noread(bg_ref->fs->bdev, &b, bitmap_block_addr); + if (rc != EOK) + return rc; + + /* Initialize all bitmap bits to zero */ + uint32_t block_size = ext4_sb_get_block_size(sb); + uint32_t inodes_per_group = ext4_get32(sb, inodes_per_group); + + memset(b.data, 0, (inodes_per_group + 7) / 8); + + uint32_t start_bit = inodes_per_group; + uint32_t end_bit = block_size * 8; + + uint32_t i; + for (i = start_bit; i < ((start_bit + 7) & ~7UL); i++) + ext4_bmap_bit_set(b.data, i); + + if (i < end_bit) + memset(b.data + (i >> 3), 0xff, (end_bit - i) >> 3); + + ext4_trans_set_block_dirty(b.buf); + + ext4_ialloc_set_bitmap_csum(sb, bg, b.data); + bg_ref->dirty = true; + + /* Save bitmap */ + return ext4_block_set(bg_ref->fs->bdev, &b); +} + +/**@brief Initialize i-node table in block group. + * @param bg_ref Reference to block group + * @return Error code + */ +static int ext4_fs_init_inode_table(struct ext4_block_group_ref *bg_ref) +{ + struct ext4_sblock *sb = &bg_ref->fs->sb; + struct ext4_bgroup *bg = bg_ref->block_group; + + uint32_t inode_size = ext4_get32(sb, inode_size); + uint32_t block_size = ext4_sb_get_block_size(sb); + uint32_t inodes_per_block = block_size / inode_size; + uint32_t inodes_in_group = ext4_inodes_in_group_cnt(sb, bg_ref->index); + uint32_t table_blocks = inodes_in_group / inodes_per_block; + ext4_fsblk_t fblock; + + if (inodes_in_group % inodes_per_block) + table_blocks++; + + /* Compute initialization bounds */ + ext4_fsblk_t first_block = ext4_bg_get_inode_table_first_block(bg, sb); + + ext4_fsblk_t last_block = first_block + table_blocks - 1; + + /* Initialization of all itable blocks */ + for (fblock = first_block; fblock <= last_block; ++fblock) { + struct ext4_block b; + int rc = ext4_trans_block_get_noread(bg_ref->fs->bdev, &b, fblock); + if (rc != EOK) + return rc; + + memset(b.data, 0, block_size); + ext4_trans_set_block_dirty(b.buf); + + ext4_block_set(bg_ref->fs->bdev, &b); + if (rc != EOK) + return rc; + } + + return EOK; +} + +static ext4_fsblk_t ext4_fs_get_descriptor_block(struct ext4_sblock *s, + uint32_t bgid, + uint32_t dsc_per_block) +{ + uint32_t first_meta_bg, dsc_id; + int has_super = 0; + dsc_id = bgid / dsc_per_block; + first_meta_bg = ext4_sb_first_meta_bg(s); + + bool meta_bg = ext4_sb_feature_incom(s, EXT4_FINCOM_META_BG); + + if (!meta_bg || dsc_id < first_meta_bg) + return ext4_get32(s, first_data_block) + dsc_id + 1; + + if (ext4_sb_is_super_in_bg(s, bgid)) + has_super = 1; + + return (has_super + ext4_fs_first_bg_block_no(s, bgid)); +} + +/**@brief Compute checksum of block group descriptor. + * @param sb Superblock + * @param bgid Index of block group in the filesystem + * @param bg Block group to compute checksum for + * @return Checksum value + */ +static uint16_t ext4_fs_bg_checksum(struct ext4_sblock *sb, uint32_t bgid, + struct ext4_bgroup *bg) +{ + /* If checksum not supported, 0 will be returned */ + uint16_t crc = 0; +#if CONFIG_META_CSUM_ENABLE + /* Compute the checksum only if the filesystem supports it */ + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + /* Use metadata_csum algorithm instead */ + uint32_t le32_bgid = to_le32(bgid); + uint32_t orig_checksum, checksum; + + /* Preparation: temporarily set bg checksum to 0 */ + orig_checksum = bg->checksum; + bg->checksum = 0; + + /* First calculate crc32 checksum against fs uuid */ + checksum = ext4_crc32c(EXT4_CRC32_INIT, sb->uuid, + sizeof(sb->uuid)); + /* Then calculate crc32 checksum against bgid */ + checksum = ext4_crc32c(checksum, &le32_bgid, sizeof(bgid)); + /* Finally calculate crc32 checksum against block_group_desc */ + checksum = ext4_crc32c(checksum, bg, ext4_sb_get_desc_size(sb)); + bg->checksum = orig_checksum; + + crc = checksum & 0xFFFF; + return crc; + } +#endif + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_GDT_CSUM)) { + uint8_t *base = (uint8_t *)bg; + uint8_t *checksum = (uint8_t *)&bg->checksum; + + uint32_t offset = (uint32_t)(checksum - base); + + /* Convert block group index to little endian */ + uint32_t group = to_le32(bgid); + + /* Initialization */ + crc = ext4_bg_crc16(~0, sb->uuid, sizeof(sb->uuid)); + + /* Include index of block group */ + crc = ext4_bg_crc16(crc, (uint8_t *)&group, sizeof(group)); + + /* Compute crc from the first part (stop before checksum field) + */ + crc = ext4_bg_crc16(crc, (uint8_t *)bg, offset); + + /* Skip checksum */ + offset += sizeof(bg->checksum); + + /* Checksum of the rest of block group descriptor */ + if ((ext4_sb_feature_incom(sb, EXT4_FINCOM_64BIT)) && + (offset < ext4_sb_get_desc_size(sb))) { + + const uint8_t *start = ((uint8_t *)bg) + offset; + size_t len = ext4_sb_get_desc_size(sb) - offset; + crc = ext4_bg_crc16(crc, start, len); + } + } + return crc; +} + +#if CONFIG_META_CSUM_ENABLE +static bool ext4_fs_verify_bg_csum(struct ext4_sblock *sb, + uint32_t bgid, + struct ext4_bgroup *bg) +{ + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + return true; + + return ext4_fs_bg_checksum(sb, bgid, bg) == to_le16(bg->checksum); +} +#else +#define ext4_fs_verify_bg_csum(...) true +#endif + +int ext4_fs_get_block_group_ref(struct ext4_fs *fs, uint32_t bgid, + struct ext4_block_group_ref *ref) +{ + /* Compute number of descriptors, that fits in one data block */ + uint32_t block_size = ext4_sb_get_block_size(&fs->sb); + uint32_t dsc_cnt = block_size / ext4_sb_get_desc_size(&fs->sb); + + /* Block group descriptor table starts at the next block after + * superblock */ + uint64_t block_id = ext4_fs_get_descriptor_block(&fs->sb, bgid, dsc_cnt); + + uint32_t offset = (bgid % dsc_cnt) * ext4_sb_get_desc_size(&fs->sb); + + int rc = ext4_trans_block_get(fs->bdev, &ref->block, block_id); + if (rc != EOK) + return rc; + + ref->block_group = (void *)(ref->block.data + offset); + ref->fs = fs; + ref->index = bgid; + ref->dirty = false; + struct ext4_bgroup *bg = ref->block_group; + + if (!ext4_fs_verify_bg_csum(&fs->sb, bgid, bg)) { + ext4_dbg(DEBUG_FS, + DBG_WARN "Block group descriptor checksum failed." + "Block group index: %" PRIu32"\n", + bgid); + } + + if (ext4_bg_has_flag(bg, EXT4_BLOCK_GROUP_BLOCK_UNINIT)) { + rc = ext4_fs_init_block_bitmap(ref); + if (rc != EOK) { + ext4_block_set(fs->bdev, &ref->block); + return rc; + } + ext4_bg_clear_flag(bg, EXT4_BLOCK_GROUP_BLOCK_UNINIT); + ref->dirty = true; + } + + if (ext4_bg_has_flag(bg, EXT4_BLOCK_GROUP_INODE_UNINIT)) { + rc = ext4_fs_init_inode_bitmap(ref); + if (rc != EOK) { + ext4_block_set(ref->fs->bdev, &ref->block); + return rc; + } + + ext4_bg_clear_flag(bg, EXT4_BLOCK_GROUP_INODE_UNINIT); + + if (!ext4_bg_has_flag(bg, EXT4_BLOCK_GROUP_ITABLE_ZEROED)) { + rc = ext4_fs_init_inode_table(ref); + if (rc != EOK) { + ext4_block_set(fs->bdev, &ref->block); + return rc; + } + + ext4_bg_set_flag(bg, EXT4_BLOCK_GROUP_ITABLE_ZEROED); + } + + ref->dirty = true; + } + + return EOK; +} + +int ext4_fs_put_block_group_ref(struct ext4_block_group_ref *ref) +{ + /* Check if reference modified */ + if (ref->dirty) { + /* Compute new checksum of block group */ + uint16_t cs; + cs = ext4_fs_bg_checksum(&ref->fs->sb, ref->index, + ref->block_group); + ref->block_group->checksum = to_le16(cs); + + /* Mark block dirty for writing changes to physical device */ + ext4_trans_set_block_dirty(ref->block.buf); + } + + /* Put back block, that contains block group descriptor */ + return ext4_block_set(ref->fs->bdev, &ref->block); +} + +#if CONFIG_META_CSUM_ENABLE +static uint32_t ext4_fs_inode_checksum(struct ext4_inode_ref *inode_ref) +{ + uint32_t checksum = 0; + struct ext4_sblock *sb = &inode_ref->fs->sb; + uint16_t inode_size = ext4_get16(sb, inode_size); + + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + uint32_t orig_checksum; + + uint32_t ino_index = to_le32(inode_ref->index); + uint32_t ino_gen = + to_le32(ext4_inode_get_generation(inode_ref->inode)); + + /* Preparation: temporarily set bg checksum to 0 */ + orig_checksum = ext4_inode_get_csum(sb, inode_ref->inode); + ext4_inode_set_csum(sb, inode_ref->inode, 0); + + /* 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 inode */ + checksum = ext4_crc32c(checksum, inode_ref->inode, inode_size); + ext4_inode_set_csum(sb, inode_ref->inode, orig_checksum); + + /* If inode size is not large enough to hold the + * upper 16bit of the checksum */ + if (inode_size == EXT4_GOOD_OLD_INODE_SIZE) + checksum &= 0xFFFF; + + } + return checksum; +} +#else +#define ext4_fs_inode_checksum(...) 0 +#endif + +static void ext4_fs_set_inode_checksum(struct ext4_inode_ref *inode_ref) +{ + struct ext4_sblock *sb = &inode_ref->fs->sb; + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + return; + + uint32_t csum = ext4_fs_inode_checksum(inode_ref); + ext4_inode_set_csum(sb, inode_ref->inode, csum); +} + +#if CONFIG_META_CSUM_ENABLE +static bool ext4_fs_verify_inode_csum(struct ext4_inode_ref *inode_ref) +{ + struct ext4_sblock *sb = &inode_ref->fs->sb; + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + return true; + + return ext4_inode_get_csum(sb, inode_ref->inode) == + ext4_fs_inode_checksum(inode_ref); +} +#else +#define ext4_fs_verify_inode_csum(...) true +#endif + +static int +__ext4_fs_get_inode_ref(struct ext4_fs *fs, uint32_t index, + struct ext4_inode_ref *ref, + bool initialized) +{ + /* Compute number of i-nodes, that fits in one data block */ + uint32_t inodes_per_group = ext4_get32(&fs->sb, inodes_per_group); + + /* + * Inode numbers are 1-based, but it is simpler to work with 0-based + * when computing indices + */ + index -= 1; + uint32_t block_group = index / inodes_per_group; + uint32_t offset_in_group = index % inodes_per_group; + + /* Load block group, where i-node is located */ + struct ext4_block_group_ref bg_ref; + + int rc = ext4_fs_get_block_group_ref(fs, block_group, &bg_ref); + if (rc != EOK) { + return rc; + } + + /* Load block address, where i-node table is located */ + ext4_fsblk_t inode_table_start = + ext4_bg_get_inode_table_first_block(bg_ref.block_group, &fs->sb); + + /* Put back block group reference (not needed more) */ + rc = ext4_fs_put_block_group_ref(&bg_ref); + if (rc != EOK) { + return rc; + } + + /* Compute position of i-node in the block group */ + uint16_t inode_size = ext4_get16(&fs->sb, inode_size); + uint32_t block_size = ext4_sb_get_block_size(&fs->sb); + uint32_t byte_offset_in_group = offset_in_group * inode_size; + + /* Compute block address */ + ext4_fsblk_t block_id = + inode_table_start + (byte_offset_in_group / block_size); + + rc = ext4_trans_block_get(fs->bdev, &ref->block, block_id); + if (rc != EOK) { + return rc; + } + + /* Compute position of i-node in the data block */ + uint32_t offset_in_block = byte_offset_in_group % block_size; + ref->inode = (struct ext4_inode *)(ref->block.data + offset_in_block); + + /* We need to store the original value of index in the reference */ + ref->index = index + 1; + ref->fs = fs; + ref->dirty = false; + + if (initialized && !ext4_fs_verify_inode_csum(ref)) { + ext4_dbg(DEBUG_FS, + DBG_WARN "Inode checksum failed." + "Inode: %" PRIu32"\n", + ref->index); + } + + return EOK; +} + +int ext4_fs_get_inode_ref(struct ext4_fs *fs, uint32_t index, + struct ext4_inode_ref *ref) +{ + return __ext4_fs_get_inode_ref(fs, index, ref, true); +} + +int ext4_fs_put_inode_ref(struct ext4_inode_ref *ref) +{ + /* Check if reference modified */ + if (ref->dirty) { + /* Mark block dirty for writing changes to physical device */ + ext4_fs_set_inode_checksum(ref); + ext4_trans_set_block_dirty(ref->block.buf); + } + + /* Put back block, that contains i-node */ + return ext4_block_set(ref->fs->bdev, &ref->block); +} + +void ext4_fs_inode_blocks_init(struct ext4_fs *fs, + struct ext4_inode_ref *inode_ref) +{ + int i; + struct ext4_inode *inode = inode_ref->inode; + + for (i = 0; i < EXT4_INODE_BLOCKS; i++) + inode->blocks[i] = 0; + + (void)fs; +#if CONFIG_EXTENT_ENABLE + /* Initialize extents if needed */ + if (ext4_sb_feature_incom(&fs->sb, EXT4_FINCOM_EXTENTS)) { + ext4_inode_set_flag(inode, EXT4_INODE_FLAG_EXTENTS); + + /* Initialize extent root header */ + ext4_extent_tree_init(inode_ref); + } +#endif +} + +uint32_t ext4_fs_correspond_inode_mode(int filetype) +{ + switch (filetype) { + case EXT4_DE_DIR: + return EXT4_INODE_MODE_DIRECTORY; + case EXT4_DE_REG_FILE: + return EXT4_INODE_MODE_FILE; + case EXT4_DE_SYMLINK: + return EXT4_INODE_MODE_SOFTLINK; + default: + /* FIXME: right now we only support 3 file type. */ + ext4_assert(0); + } + return 0; +} + +int ext4_fs_alloc_inode(struct ext4_fs *fs, struct ext4_inode_ref *inode_ref, + int filetype) +{ + /* Check if newly allocated i-node will be a directory */ + bool is_dir; + uint16_t inode_size = ext4_get16(&fs->sb, inode_size); + + is_dir = (filetype == EXT4_DE_DIR); + + /* Allocate inode by allocation algorithm */ + uint32_t index; + int rc = ext4_ialloc_alloc_inode(fs, &index, is_dir); + if (rc != EOK) + return rc; + + /* Load i-node from on-disk i-node table */ + rc = __ext4_fs_get_inode_ref(fs, index, inode_ref, false); + if (rc != EOK) { + ext4_ialloc_free_inode(fs, index, is_dir); + return rc; + } + + /* Initialize i-node */ + struct ext4_inode *inode = inode_ref->inode; + + uint32_t mode; + if (is_dir) { + /* + * Default directory permissions to be compatible with other + * systems + * 0777 (octal) == rwxrwxrwx + */ + + mode = 0777; + mode |= EXT4_INODE_MODE_DIRECTORY; + } else { + /* + * Default file permissions to be compatible with other systems + * 0666 (octal) == rw-rw-rw- + */ + + mode = 0666; + mode |= ext4_fs_correspond_inode_mode(filetype); + } + ext4_inode_set_mode(&fs->sb, inode, mode); + + ext4_inode_set_links_cnt(inode, 0); + ext4_inode_set_uid(inode, 0); + ext4_inode_set_gid(inode, 0); + ext4_inode_set_size(inode, 0); + ext4_inode_set_access_time(inode, 0); + ext4_inode_set_change_inode_time(inode, 0); + ext4_inode_set_modif_time(inode, 0); + ext4_inode_set_del_time(inode, 0); + ext4_inode_set_blocks_count(&fs->sb, inode, 0); + ext4_inode_set_flags(inode, 0); + ext4_inode_set_generation(inode, 0); + if (inode_size > EXT4_GOOD_OLD_INODE_SIZE) { + uint16_t off = offsetof(struct ext4_inode, extra_isize); + uint16_t size = sizeof(struct ext4_inode) - off; + ext4_inode_set_extra_isize(inode, size); + } + + /* Reset blocks array. For symbolic link inode, just + * fill in blocks with 0 */ + if (ext4_inode_is_type(&fs->sb, inode, EXT4_INODE_MODE_SOFTLINK)) { + for (int i = 0; i < EXT4_INODE_BLOCKS; i++) + inode->blocks[i] = 0; + + } else { + ext4_fs_inode_blocks_init(fs, inode_ref); + } + + inode_ref->dirty = true; + + return EOK; +} + +int ext4_fs_free_inode(struct ext4_inode_ref *inode_ref) +{ + struct ext4_fs *fs = inode_ref->fs; + uint32_t offset; + uint32_t suboff; + int rc; +#if CONFIG_EXTENT_ENABLE + /* For extents must be data block destroyed by other way */ + if ((ext4_sb_feature_incom(&fs->sb, EXT4_FINCOM_EXTENTS)) && + (ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS))) { + /* Data structures are released during truncate operation... */ + goto finish; + } +#endif + /* Release all indirect (no data) blocks */ + + /* 1) Single indirect */ + ext4_fsblk_t fblock = ext4_inode_get_indirect_block(inode_ref->inode, 0); + if (fblock != 0) { + int rc = ext4_balloc_free_block(inode_ref, fblock); + if (rc != EOK) + return rc; + + ext4_inode_set_indirect_block(inode_ref->inode, 0, 0); + } + + uint32_t block_size = ext4_sb_get_block_size(&fs->sb); + uint32_t count = block_size / sizeof(uint32_t); + + struct ext4_block block; + + /* 2) Double indirect */ + fblock = ext4_inode_get_indirect_block(inode_ref->inode, 1); + if (fblock != 0) { + int rc = ext4_trans_block_get(fs->bdev, &block, fblock); + if (rc != EOK) + return rc; + + ext4_fsblk_t ind_block; + for (offset = 0; offset < count; ++offset) { + ind_block = to_le32(((uint32_t *)block.data)[offset]); + + if (ind_block == 0) + continue; + rc = ext4_balloc_free_block(inode_ref, ind_block); + if (rc != EOK) { + ext4_block_set(fs->bdev, &block); + return rc; + } + + } + + ext4_block_set(fs->bdev, &block); + rc = ext4_balloc_free_block(inode_ref, fblock); + if (rc != EOK) + return rc; + + ext4_inode_set_indirect_block(inode_ref->inode, 1, 0); + } + + /* 3) Tripple indirect */ + struct ext4_block subblock; + fblock = ext4_inode_get_indirect_block(inode_ref->inode, 2); + if (fblock == 0) + goto finish; + rc = ext4_trans_block_get(fs->bdev, &block, fblock); + if (rc != EOK) + return rc; + + ext4_fsblk_t ind_block; + for (offset = 0; offset < count; ++offset) { + ind_block = to_le32(((uint32_t *)block.data)[offset]); + + if (ind_block == 0) + continue; + rc = ext4_trans_block_get(fs->bdev, &subblock, + ind_block); + if (rc != EOK) { + ext4_block_set(fs->bdev, &block); + return rc; + } + + ext4_fsblk_t ind_subblk; + for (suboff = 0; suboff < count; ++suboff) { + ind_subblk = to_le32(((uint32_t *)subblock.data)[suboff]); + + if (ind_subblk == 0) + continue; + rc = ext4_balloc_free_block(inode_ref, ind_subblk); + if (rc != EOK) { + ext4_block_set(fs->bdev, &subblock); + ext4_block_set(fs->bdev, &block); + return rc; + } + + } + + ext4_block_set(fs->bdev, &subblock); + + rc = ext4_balloc_free_block(inode_ref, + ind_block); + if (rc != EOK) { + ext4_block_set(fs->bdev, &block); + return rc; + } + + } + + ext4_block_set(fs->bdev, &block); + rc = ext4_balloc_free_block(inode_ref, fblock); + if (rc != EOK) + return rc; + + ext4_inode_set_indirect_block(inode_ref->inode, 2, 0); +finish: + /* Mark inode dirty for writing to the physical device */ + inode_ref->dirty = true; + + /* Free block with extended attributes if present */ + ext4_fsblk_t xattr_block = + ext4_inode_get_file_acl(inode_ref->inode, &fs->sb); + if (xattr_block) { + int rc = ext4_balloc_free_block(inode_ref, xattr_block); + if (rc != EOK) + return rc; + + ext4_inode_set_file_acl(inode_ref->inode, &fs->sb, 0); + } + + /* Free inode by allocator */ + if (ext4_inode_is_type(&fs->sb, inode_ref->inode, + EXT4_INODE_MODE_DIRECTORY)) + rc = ext4_ialloc_free_inode(fs, inode_ref->index, true); + else + rc = ext4_ialloc_free_inode(fs, inode_ref->index, false); + + return rc; +} + + +/**@brief Release data block from i-node + * @param inode_ref I-node to release block from + * @param iblock Logical block to be released + * @return Error code + */ +static int ext4_fs_release_inode_block(struct ext4_inode_ref *inode_ref, + uint32_t iblock) +{ + ext4_fsblk_t fblock; + + struct ext4_fs *fs = inode_ref->fs; + + /* Extents are handled otherwise = there is not support in this function + */ + ext4_assert(!( + ext4_sb_feature_incom(&fs->sb, EXT4_FINCOM_EXTENTS) && + (ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS)))); + + struct ext4_inode *inode = inode_ref->inode; + + /* Handle simple case when we are dealing with direct reference */ + if (iblock < EXT4_INODE_DIRECT_BLOCK_COUNT) { + fblock = ext4_inode_get_direct_block(inode, iblock); + + /* Sparse file */ + if (fblock == 0) + return EOK; + + ext4_inode_set_direct_block(inode, iblock, 0); + return ext4_balloc_free_block(inode_ref, fblock); + } + + /* Determine the indirection level needed to get the desired block */ + unsigned int level = 0; + unsigned int i; + for (i = 1; i < 4; i++) { + if (iblock < fs->inode_block_limits[i]) { + level = i; + break; + } + } + + if (level == 0) + return EIO; + + /* Compute offsets for the topmost level */ + uint32_t block_offset_in_level = + iblock - fs->inode_block_limits[level - 1]; + ext4_fsblk_t current_block = + ext4_inode_get_indirect_block(inode, level - 1); + uint32_t offset_in_block = + block_offset_in_level / fs->inode_blocks_per_level[level - 1]; + + /* + * Navigate through other levels, until we find the block number + * or find null reference meaning we are dealing with sparse file + */ + struct ext4_block block; + + while (level > 0) { + + /* Sparse check */ + if (current_block == 0) + return EOK; + + int rc = ext4_trans_block_get(fs->bdev, &block, current_block); + if (rc != EOK) + return rc; + + current_block = + to_le32(((uint32_t *)block.data)[offset_in_block]); + + /* Set zero if physical data block address found */ + if (level == 1) { + ((uint32_t *)block.data)[offset_in_block] = to_le32(0); + ext4_trans_set_block_dirty(block.buf); + } + + rc = ext4_block_set(fs->bdev, &block); + if (rc != EOK) + return rc; + + level--; + + /* + * If we are on the last level, break here as + * there is no next level to visit + */ + if (level == 0) + break; + + /* Visit the next level */ + block_offset_in_level %= fs->inode_blocks_per_level[level]; + offset_in_block = block_offset_in_level / + fs->inode_blocks_per_level[level - 1]; + } + + fblock = current_block; + if (fblock == 0) + return EOK; + + /* Physical block is not referenced, it can be released */ + return ext4_balloc_free_block(inode_ref, fblock); +} + +int ext4_fs_truncate_inode(struct ext4_inode_ref *inode_ref, uint64_t new_size) +{ + struct ext4_sblock *sb = &inode_ref->fs->sb; + uint32_t i; + int r; + + /* Check flags, if i-node can be truncated */ + if (!ext4_inode_can_truncate(sb, inode_ref->inode)) + return EINVAL; + + /* If sizes are equal, nothing has to be done. */ + uint64_t old_size = ext4_inode_get_size(sb, inode_ref->inode); + if (old_size == new_size) + return EOK; + + /* It's not supported to make the larger file by truncate operation */ + if (old_size < new_size) + return EINVAL; + + bool v; + v = ext4_inode_is_type(sb, inode_ref->inode, EXT4_INODE_MODE_SOFTLINK); + if (v && old_size < sizeof(inode_ref->inode->blocks) && + !ext4_inode_get_blocks_count(sb, inode_ref->inode)) { + char *content = (char *)inode_ref->inode->blocks + new_size; + memset(content, 0, + sizeof(inode_ref->inode->blocks) - (uint32_t)new_size); + ext4_inode_set_size(inode_ref->inode, new_size); + inode_ref->dirty = true; + + return EOK; + } + + /* Compute how many blocks will be released */ + uint32_t block_size = ext4_sb_get_block_size(sb); + uint32_t new_blocks_cnt = (uint32_t)((new_size + block_size - 1) / block_size); + uint32_t old_blocks_cnt = (uint32_t)((old_size + block_size - 1) / block_size); + uint32_t diff_blocks_cnt = old_blocks_cnt - new_blocks_cnt; +#if CONFIG_EXTENT_ENABLE + if ((ext4_sb_feature_incom(sb, EXT4_FINCOM_EXTENTS)) && + (ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS))) { + + /* Extents require special operation */ + if (diff_blocks_cnt) { + r = ext4_extent_remove_space(inode_ref, new_blocks_cnt, + EXT_MAX_BLOCKS); + if (r != EOK) + return r; + + } + } else +#endif + { + /* Release data blocks from the end of file */ + + /* Starting from 1 because of logical blocks are numbered from 0 + */ + for (i = 0; i < diff_blocks_cnt; ++i) { + r = ext4_fs_release_inode_block(inode_ref, + new_blocks_cnt + i); + if (r != EOK) + return r; + } + } + + /* Update i-node */ + ext4_inode_set_size(inode_ref->inode, new_size); + inode_ref->dirty = true; + + return EOK; +} + +/**@brief Compute 'goal' for inode index + * @param inode_ref Reference to inode, to allocate block for + * @return goal + */ +ext4_fsblk_t ext4_fs_inode_to_goal_block(struct ext4_inode_ref *inode_ref) +{ + uint32_t grp_inodes = ext4_get32(&inode_ref->fs->sb, inodes_per_group); + return (inode_ref->index - 1) / grp_inodes; +} + +/**@brief Compute 'goal' for allocation algorithm (For blockmap). + * @param inode_ref Reference to inode, to allocate block for + * @param goal + * @return error code + */ +int ext4_fs_indirect_find_goal(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t *goal) +{ + int r; + struct ext4_sblock *sb = &inode_ref->fs->sb; + *goal = 0; + + uint64_t inode_size = ext4_inode_get_size(sb, inode_ref->inode); + uint32_t block_size = ext4_sb_get_block_size(sb); + uint32_t iblock_cnt = (uint32_t)(inode_size / block_size); + + if (inode_size % block_size != 0) + iblock_cnt++; + + /* If inode has some blocks, get last block address + 1 */ + if (iblock_cnt > 0) { + r = ext4_fs_get_inode_dblk_idx(inode_ref, iblock_cnt - 1, + goal, false); + if (r != EOK) + return r; + + if (*goal != 0) { + (*goal)++; + return r; + } + + /* If goal == 0, sparse file -> continue */ + } + + /* Identify block group of inode */ + + uint32_t inodes_per_bg = ext4_get32(sb, inodes_per_group); + uint32_t block_group = (inode_ref->index - 1) / inodes_per_bg; + block_size = ext4_sb_get_block_size(sb); + + /* Load block group reference */ + struct ext4_block_group_ref bg_ref; + r = ext4_fs_get_block_group_ref(inode_ref->fs, block_group, &bg_ref); + if (r != EOK) + return r; + + struct ext4_bgroup *bg = bg_ref.block_group; + + /* Compute indexes */ + uint32_t bg_count = ext4_block_group_cnt(sb); + ext4_fsblk_t itab_first_block = ext4_bg_get_inode_table_first_block(bg, sb); + uint16_t itab_item_size = ext4_get16(sb, inode_size); + uint32_t itab_bytes; + + /* Check for last block group */ + if (block_group < bg_count - 1) { + itab_bytes = inodes_per_bg * itab_item_size; + } else { + /* Last block group could be smaller */ + uint32_t inodes_cnt = ext4_get32(sb, inodes_count); + + itab_bytes = (inodes_cnt - ((bg_count - 1) * inodes_per_bg)); + itab_bytes *= itab_item_size; + } + + ext4_fsblk_t inode_table_blocks = itab_bytes / block_size; + + if (itab_bytes % block_size) + inode_table_blocks++; + + *goal = itab_first_block + inode_table_blocks; + + return ext4_fs_put_block_group_ref(&bg_ref); +} + +static int ext4_fs_get_inode_dblk_idx_internal(struct ext4_inode_ref *inode_ref, + uint64_t iblock, ext4_fsblk_t *fblock, + bool extent_create, + bool support_unwritten __unused) +{ + struct ext4_fs *fs = inode_ref->fs; + + /* For empty file is situation simple */ + if (ext4_inode_get_size(&fs->sb, inode_ref->inode) == 0) { + *fblock = 0; + return EOK; + } + + ext4_fsblk_t current_block; + + (void)extent_create; +#if CONFIG_EXTENT_ENABLE + /* Handle i-node using extents */ + if ((ext4_sb_feature_incom(&fs->sb, EXT4_FINCOM_EXTENTS)) && + (ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS))) { + + ext4_fsblk_t current_fsblk; + int rc = ext4_extent_get_blocks(inode_ref, iblock, 1, + ¤t_fsblk, extent_create, NULL); + if (rc != EOK) + return rc; + + current_block = current_fsblk; + *fblock = current_block; + + ext4_assert(*fblock || support_unwritten); + return EOK; + } +#endif + + struct ext4_inode *inode = inode_ref->inode; + + /* Direct block are read directly from array in i-node structure */ + if (iblock < EXT4_INODE_DIRECT_BLOCK_COUNT) { + current_block = + ext4_inode_get_direct_block(inode, (uint32_t)iblock); + *fblock = current_block; + return EOK; + } + + /* Determine indirection level of the target block */ + unsigned int l = 0; + unsigned int i; + for (i = 1; i < 4; i++) { + if (iblock < fs->inode_block_limits[i]) { + l = i; + break; + } + } + + if (l == 0) + return EIO; + + /* Compute offsets for the topmost level */ + uint32_t blk_off_in_lvl = iblock - fs->inode_block_limits[l - 1]; + current_block = ext4_inode_get_indirect_block(inode, l - 1); + uint32_t off_in_blk = blk_off_in_lvl / fs->inode_blocks_per_level[l - 1]; + + /* Sparse file */ + if (current_block == 0) { + *fblock = 0; + return EOK; + } + + struct ext4_block block; + + /* + * Navigate through other levels, until we find the block number + * or find null reference meaning we are dealing with sparse file + */ + while (l > 0) { + /* Load indirect block */ + int rc = ext4_trans_block_get(fs->bdev, &block, current_block); + if (rc != EOK) + return rc; + + /* Read block address from indirect block */ + current_block = + to_le32(((uint32_t *)block.data)[off_in_blk]); + + /* Put back indirect block untouched */ + rc = ext4_block_set(fs->bdev, &block); + if (rc != EOK) + return rc; + + /* Check for sparse file */ + if (current_block == 0) { + *fblock = 0; + return EOK; + } + + /* Jump to the next level */ + l--; + + /* Termination condition - we have address of data block loaded + */ + if (l == 0) + break; + + /* Visit the next level */ + blk_off_in_lvl %= fs->inode_blocks_per_level[l]; + off_in_blk = blk_off_in_lvl / fs->inode_blocks_per_level[l - 1]; + } + + *fblock = current_block; + + return EOK; +} + + +int ext4_fs_get_inode_dblk_idx(struct ext4_inode_ref *inode_ref, + uint64_t iblock, ext4_fsblk_t *fblock, + bool support_unwritten) +{ + return ext4_fs_get_inode_dblk_idx_internal(inode_ref, iblock, fblock, + false, support_unwritten); +} + +int ext4_fs_init_inode_dblk_idx(struct ext4_inode_ref *inode_ref, + uint64_t iblock, ext4_fsblk_t *fblock) +{ + return ext4_fs_get_inode_dblk_idx_internal(inode_ref, iblock, fblock, + true, true); +} + +static int ext4_fs_set_inode_data_block_index(struct ext4_inode_ref *inode_ref, + uint32_t iblock, ext4_fsblk_t fblock) +{ + struct ext4_fs *fs = inode_ref->fs; + +#if CONFIG_EXTENT_ENABLE + /* Handle inode using extents */ + if ((ext4_sb_feature_incom(&fs->sb, EXT4_FINCOM_EXTENTS)) && + (ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS))) { + /* Not reachable */ + return ENOTSUP; + } +#endif + + /* Handle simple case when we are dealing with direct reference */ + if (iblock < EXT4_INODE_DIRECT_BLOCK_COUNT) { + ext4_inode_set_direct_block(inode_ref->inode, (uint32_t)iblock, + (uint32_t)fblock); + inode_ref->dirty = true; + + return EOK; + } + + /* Determine the indirection level needed to get the desired block */ + unsigned int l = 0; + unsigned int i; + for (i = 1; i < 4; i++) { + if (iblock < fs->inode_block_limits[i]) { + l = i; + break; + } + } + + if (l == 0) + return EIO; + + uint32_t block_size = ext4_sb_get_block_size(&fs->sb); + + /* Compute offsets for the topmost level */ + uint32_t blk_off_in_lvl = iblock - fs->inode_block_limits[l - 1]; + ext4_fsblk_t current_block = + ext4_inode_get_indirect_block(inode_ref->inode, l - 1); + uint32_t off_in_blk = blk_off_in_lvl / fs->inode_blocks_per_level[l - 1]; + + ext4_fsblk_t new_blk; + + struct ext4_block block; + struct ext4_block new_block; + + /* Is needed to allocate indirect block on the i-node level */ + if (current_block == 0) { + /* Allocate new indirect block */ + ext4_fsblk_t goal; + int rc = ext4_fs_indirect_find_goal(inode_ref, &goal); + if (rc != EOK) + return rc; + + rc = ext4_balloc_alloc_block(inode_ref, goal, &new_blk); + if (rc != EOK) + return rc; + + /* Update i-node */ + ext4_inode_set_indirect_block(inode_ref->inode, l - 1, + (uint32_t)new_blk); + inode_ref->dirty = true; + + /* Load newly allocated block */ + rc = ext4_trans_block_get_noread(fs->bdev, &new_block, new_blk); + if (rc != EOK) { + ext4_balloc_free_block(inode_ref, new_blk); + return rc; + } + + /* Initialize new block */ + memset(new_block.data, 0, block_size); + ext4_trans_set_block_dirty(new_block.buf); + + /* Put back the allocated block */ + rc = ext4_block_set(fs->bdev, &new_block); + if (rc != EOK) + return rc; + + current_block = new_blk; + } + + /* + * Navigate through other levels, until we find the block number + * or find null reference meaning we are dealing with sparse file + */ + while (l > 0) { + int rc = ext4_trans_block_get(fs->bdev, &block, current_block); + if (rc != EOK) + return rc; + + current_block = to_le32(((uint32_t *)block.data)[off_in_blk]); + if ((l > 1) && (current_block == 0)) { + ext4_fsblk_t goal; + rc = ext4_fs_indirect_find_goal(inode_ref, &goal); + if (rc != EOK) { + ext4_block_set(fs->bdev, &block); + return rc; + } + + /* Allocate new block */ + rc = + ext4_balloc_alloc_block(inode_ref, goal, &new_blk); + if (rc != EOK) { + ext4_block_set(fs->bdev, &block); + return rc; + } + + /* Load newly allocated block */ + rc = ext4_trans_block_get_noread(fs->bdev, &new_block, + new_blk); + + if (rc != EOK) { + ext4_block_set(fs->bdev, &block); + return rc; + } + + /* Initialize allocated block */ + memset(new_block.data, 0, block_size); + ext4_trans_set_block_dirty(new_block.buf); + + rc = ext4_block_set(fs->bdev, &new_block); + if (rc != EOK) { + ext4_block_set(fs->bdev, &block); + return rc; + } + + /* Write block address to the parent */ + uint32_t * p = (uint32_t * )block.data; + p[off_in_blk] = to_le32((uint32_t)new_blk); + ext4_trans_set_block_dirty(block.buf); + current_block = new_blk; + } + + /* Will be finished, write the fblock address */ + if (l == 1) { + uint32_t * p = (uint32_t * )block.data; + p[off_in_blk] = to_le32((uint32_t)fblock); + ext4_trans_set_block_dirty(block.buf); + } + + rc = ext4_block_set(fs->bdev, &block); + if (rc != EOK) + return rc; + + l--; + + /* + * If we are on the last level, break here as + * there is no next level to visit + */ + if (l == 0) + break; + + /* Visit the next level */ + blk_off_in_lvl %= fs->inode_blocks_per_level[l]; + off_in_blk = blk_off_in_lvl / fs->inode_blocks_per_level[l - 1]; + } + + return EOK; +} + + +int ext4_fs_append_inode_dblk(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t *fblock, uint32_t *iblock) +{ +#if CONFIG_EXTENT_ENABLE + /* Handle extents separately */ + if ((ext4_sb_feature_incom(&inode_ref->fs->sb, EXT4_FINCOM_EXTENTS)) && + (ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_EXTENTS))) { + int rc; + ext4_fsblk_t current_fsblk; + 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); + *iblock = (uint32_t)((inode_size + block_size - 1) / block_size); + + rc = ext4_extent_get_blocks(inode_ref, *iblock, 1, + ¤t_fsblk, true, NULL); + + *fblock = current_fsblk; + ext4_assert(*fblock); + + ext4_inode_set_size(inode_ref->inode, inode_size + block_size); + inode_ref->dirty = true; + + + return rc; + } +#endif + struct ext4_sblock *sb = &inode_ref->fs->sb; + + /* Compute next block index and allocate data block */ + uint64_t inode_size = ext4_inode_get_size(sb, inode_ref->inode); + uint32_t block_size = ext4_sb_get_block_size(sb); + + /* Align size i-node size */ + if ((inode_size % block_size) != 0) + inode_size += block_size - (inode_size % block_size); + + /* Logical blocks are numbered from 0 */ + uint32_t new_block_idx = (uint32_t)(inode_size / block_size); + + /* Allocate new physical block */ + ext4_fsblk_t goal, phys_block; + int rc = ext4_fs_indirect_find_goal(inode_ref, &goal); + if (rc != EOK) + return rc; + + rc = ext4_balloc_alloc_block(inode_ref, goal, &phys_block); + if (rc != EOK) + return rc; + + /* Add physical block address to the i-node */ + rc = ext4_fs_set_inode_data_block_index(inode_ref, new_block_idx, + phys_block); + if (rc != EOK) { + ext4_balloc_free_block(inode_ref, phys_block); + return rc; + } + + /* Update i-node */ + ext4_inode_set_size(inode_ref->inode, inode_size + block_size); + inode_ref->dirty = true; + + *fblock = phys_block; + *iblock = new_block_idx; + + return EOK; +} + +void ext4_fs_inode_links_count_inc(struct ext4_inode_ref *inode_ref) +{ + uint16_t link; + bool is_dx; + link = ext4_inode_get_links_cnt(inode_ref->inode); + link++; + ext4_inode_set_links_cnt(inode_ref->inode, link); + + is_dx = ext4_sb_feature_com(&inode_ref->fs->sb, EXT4_FCOM_DIR_INDEX) && + ext4_inode_has_flag(inode_ref->inode, EXT4_INODE_FLAG_INDEX); + + if (is_dx && link > 1) { + if (link >= EXT4_LINK_MAX || link == 2) { + ext4_inode_set_links_cnt(inode_ref->inode, 1); + + uint32_t v; + v = ext4_get32(&inode_ref->fs->sb, features_read_only); + v |= EXT4_FRO_COM_DIR_NLINK; + ext4_set32(&inode_ref->fs->sb, features_read_only, v); + } + } +} + +void ext4_fs_inode_links_count_dec(struct ext4_inode_ref *inode_ref) +{ + uint16_t links = ext4_inode_get_links_cnt(inode_ref->inode); + if (!ext4_inode_is_type(&inode_ref->fs->sb, inode_ref->inode, + EXT4_INODE_MODE_DIRECTORY)) { + if (links > 0) + ext4_inode_set_links_cnt(inode_ref->inode, links - 1); + return; + } + + if (links > 2) + ext4_inode_set_links_cnt(inode_ref->inode, links - 1); +} + +/** + * @} + */ diff --git a/src/ext4_hash.c b/src/ext4_hash.c new file mode 100644 index 0000000..4f97eac --- /dev/null +++ b/src/ext4_hash.c @@ -0,0 +1,322 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * FreeBSD: + * Copyright (c) 2010, 2013 Zheng Liu <lz@freebsd.org> + * Copyright (c) 2012, Vyacheslav Matyushin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/* + * The following notice applies to the code in ext2_half_md4(): + * + * Copyright (C) 1990-2, RSA Data Security, Inc. All rights reserved. + * + * License to copy and use this software is granted provided that it + * is identified as the "RSA Data Security, Inc. MD4 Message-Digest + * Algorithm" in all material mentioning or referencing this software + * or this function. + * + * License is also granted to make and use derivative works provided + * that such works are identified as "derived from the RSA Data + * Security, Inc. MD4 Message-Digest Algorithm" in all material + * mentioning or referencing the derived work. + * + * RSA Data Security, Inc. makes no representations concerning either + * the merchantability of this software or the suitability of this + * software for any particular purpose. It is provided "as is" + * without express or implied warranty of any kind. + * + * These notices must be retained in any copies of any part of this + * documentation and/or software. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_hash.c + * @brief Directory indexing hash functions. + */ + +#include "ext4_config.h" +#include "ext4_types.h" +#include "ext4_errno.h" + +#include <string.h> + +/* F, G, and H are MD4 functions */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) + +/* ROTATE_LEFT rotates x left n bits */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + +/* + * FF, GG, and HH are transformations for rounds 1, 2, and 3. + * Rotation is separated from addition to prevent recomputation. + */ +#define FF(a, b, c, d, x, s) \ + { \ + (a) += F((b), (c), (d)) + (x); \ + (a) = ROTATE_LEFT((a), (s)); \ + \ +} + +#define GG(a, b, c, d, x, s) \ + { \ + (a) += G((b), (c), (d)) + (x) + (uint32_t)0x5A827999; \ + (a) = ROTATE_LEFT((a), (s)); \ + \ +} + +#define HH(a, b, c, d, x, s) \ + { \ + (a) += H((b), (c), (d)) + (x) + (uint32_t)0x6ED9EBA1; \ + (a) = ROTATE_LEFT((a), (s)); \ + \ +} + +/* + * MD4 basic transformation. It transforms state based on block. + * + * This is a half md4 algorithm since Linux uses this algorithm for dir + * index. This function is derived from the RSA Data Security, Inc. MD4 + * Message-Digest Algorithm and was modified as necessary. + * + * The return value of this function is uint32_t in Linux, but actually we don't + * need to check this value, so in our version this function doesn't return any + * value. + */ +static void ext2_half_md4(uint32_t hash[4], uint32_t data[8]) +{ + uint32_t a = hash[0], b = hash[1], c = hash[2], d = hash[3]; + + /* Round 1 */ + FF(a, b, c, d, data[0], 3); + FF(d, a, b, c, data[1], 7); + FF(c, d, a, b, data[2], 11); + FF(b, c, d, a, data[3], 19); + FF(a, b, c, d, data[4], 3); + FF(d, a, b, c, data[5], 7); + FF(c, d, a, b, data[6], 11); + FF(b, c, d, a, data[7], 19); + + /* Round 2 */ + GG(a, b, c, d, data[1], 3); + GG(d, a, b, c, data[3], 5); + GG(c, d, a, b, data[5], 9); + GG(b, c, d, a, data[7], 13); + GG(a, b, c, d, data[0], 3); + GG(d, a, b, c, data[2], 5); + GG(c, d, a, b, data[4], 9); + GG(b, c, d, a, data[6], 13); + + /* Round 3 */ + HH(a, b, c, d, data[3], 3); + HH(d, a, b, c, data[7], 9); + HH(c, d, a, b, data[2], 11); + HH(b, c, d, a, data[6], 15); + HH(a, b, c, d, data[1], 3); + HH(d, a, b, c, data[5], 9); + HH(c, d, a, b, data[0], 11); + HH(b, c, d, a, data[4], 15); + + hash[0] += a; + hash[1] += b; + hash[2] += c; + hash[3] += d; +} + +/* + * Tiny Encryption Algorithm. + */ +static void ext2_tea(uint32_t hash[4], uint32_t data[8]) +{ + uint32_t tea_delta = 0x9E3779B9; + uint32_t sum; + uint32_t x = hash[0], y = hash[1]; + int n = 16; + int i = 1; + + while (n-- > 0) { + sum = i * tea_delta; + x += ((y << 4) + data[0]) ^ (y + sum) ^ ((y >> 5) + data[1]); + y += ((x << 4) + data[2]) ^ (x + sum) ^ ((x >> 5) + data[3]); + i++; + } + + hash[0] += x; + hash[1] += y; +} + +static uint32_t ext2_legacy_hash(const char *name, int len, int unsigned_char) +{ + uint32_t h0, h1 = 0x12A3FE2D, h2 = 0x37ABE8F9; + uint32_t multi = 0x6D22F5; + const unsigned char *uname = (const unsigned char *)name; + const signed char *sname = (const signed char *)name; + int val, i; + + for (i = 0; i < len; i++) { + if (unsigned_char) + val = (unsigned int)*uname++; + else + val = (int)*sname++; + + h0 = h2 + (h1 ^ (val * multi)); + if (h0 & 0x80000000) + h0 -= 0x7FFFFFFF; + h2 = h1; + h1 = h0; + } + + return (h1 << 1); +} + +static void ext2_prep_hashbuf(const char *src, uint32_t slen, uint32_t *dst, + int dlen, int unsigned_char) +{ + uint32_t padding = slen | (slen << 8) | (slen << 16) | (slen << 24); + uint32_t buf_val; + int len, i; + int buf_byte; + const unsigned char *ubuf = (const unsigned char *)src; + const signed char *sbuf = (const signed char *)src; + + if (slen > (uint32_t)dlen) + len = dlen; + else + len = slen; + + buf_val = padding; + + for (i = 0; i < len; i++) { + if (unsigned_char) + buf_byte = (unsigned int)ubuf[i]; + else + buf_byte = (int)sbuf[i]; + + if ((i % 4) == 0) + buf_val = padding; + + buf_val <<= 8; + buf_val += buf_byte; + + if ((i % 4) == 3) { + *dst++ = buf_val; + dlen -= sizeof(uint32_t); + buf_val = padding; + } + } + + dlen -= sizeof(uint32_t); + if (dlen >= 0) + *dst++ = buf_val; + + dlen -= sizeof(uint32_t); + while (dlen >= 0) { + *dst++ = padding; + dlen -= sizeof(uint32_t); + } +} + +int ext2_htree_hash(const char *name, int len, const uint32_t *hash_seed, + int hash_version, uint32_t *hash_major, + uint32_t *hash_minor) +{ + uint32_t hash[4]; + uint32_t data[8]; + uint32_t major = 0, minor = 0; + int unsigned_char = 0; + + if (!name || !hash_major) + return (-1); + + if (len < 1 || len > 255) + goto error; + + hash[0] = 0x67452301; + hash[1] = 0xEFCDAB89; + hash[2] = 0x98BADCFE; + hash[3] = 0x10325476; + + if (hash_seed) + memcpy(hash, hash_seed, sizeof(hash)); + + switch (hash_version) { + case EXT2_HTREE_TEA_UNSIGNED: + unsigned_char = 1; + case EXT2_HTREE_TEA: + while (len > 0) { + ext2_prep_hashbuf(name, len, data, 16, unsigned_char); + ext2_tea(hash, data); + len -= 16; + name += 16; + } + major = hash[0]; + minor = hash[1]; + break; + case EXT2_HTREE_LEGACY_UNSIGNED: + unsigned_char = 1; + case EXT2_HTREE_LEGACY: + major = ext2_legacy_hash(name, len, unsigned_char); + break; + case EXT2_HTREE_HALF_MD4_UNSIGNED: + unsigned_char = 1; + case EXT2_HTREE_HALF_MD4: + while (len > 0) { + ext2_prep_hashbuf(name, len, data, 32, unsigned_char); + ext2_half_md4(hash, data); + len -= 32; + name += 32; + } + major = hash[1]; + minor = hash[2]; + break; + default: + goto error; + } + + major &= ~1; + if (major == (EXT2_HTREE_EOF << 1)) + major = (EXT2_HTREE_EOF - 1) << 1; + *hash_major = major; + if (hash_minor) + *hash_minor = minor; + + return EOK; + +error: + *hash_major = 0; + if (hash_minor) + *hash_minor = 0; + return ENOTSUP; +} + +/** + * @} + */ diff --git a/src/ext4_ialloc.c b/src/ext4_ialloc.c new file mode 100644 index 0000000..bbdcd4d --- /dev/null +++ b/src/ext4_ialloc.c @@ -0,0 +1,365 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_ialloc.c + * @brief Inode allocation procedures. + */ + +#include "ext4_config.h" +#include "ext4_types.h" +#include "ext4_ialloc.h" +#include "ext4_super.h" +#include "ext4_crc32.h" +#include "ext4_fs.h" +#include "ext4_blockdev.h" +#include "ext4_block_group.h" +#include "ext4_bitmap.h" + +/**@brief Convert i-node number to relative index in block group. + * @param sb Superblock + * @param inode I-node number to be converted + * @return Index of the i-node in the block group + */ +static uint32_t ext4_ialloc_inode_to_bgidx(struct ext4_sblock *sb, + uint32_t inode) +{ + uint32_t inodes_per_group = ext4_get32(sb, inodes_per_group); + return (inode - 1) % inodes_per_group; +} + +/**@brief Convert relative index of i-node to absolute i-node number. + * @param sb Superblock + * @param index Index to be converted + * @return Absolute number of the i-node + * + */ +static uint32_t ext4_ialloc_bgidx_to_inode(struct ext4_sblock *sb, + uint32_t index, uint32_t bgid) +{ + uint32_t inodes_per_group = ext4_get32(sb, inodes_per_group); + return bgid * inodes_per_group + (index + 1); +} + +/**@brief Compute block group number from the i-node number. + * @param sb Superblock + * @param inode I-node number to be found the block group for + * @return Block group number computed from i-node number + */ +static uint32_t ext4_ialloc_get_bgid_of_inode(struct ext4_sblock *sb, + uint32_t inode) +{ + uint32_t inodes_per_group = ext4_get32(sb, inodes_per_group); + return (inode - 1) / inodes_per_group; +} + +#if CONFIG_META_CSUM_ENABLE +static uint32_t ext4_ialloc_bitmap_csum(struct ext4_sblock *sb, void *bitmap) +{ + uint32_t csum = 0; + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + uint32_t inodes_per_group = + ext4_get32(sb, inodes_per_group); + + /* First calculate crc32 checksum against fs uuid */ + csum = ext4_crc32c(EXT4_CRC32_INIT, sb->uuid, sizeof(sb->uuid)); + /* Then calculate crc32 checksum against inode bitmap */ + csum = ext4_crc32c(csum, bitmap, (inodes_per_group + 7) / 8); + } + return csum; +} +#else +#define ext4_ialloc_bitmap_csum(...) 0 +#endif + +void ext4_ialloc_set_bitmap_csum(struct ext4_sblock *sb, struct ext4_bgroup *bg, + void *bitmap __unused) +{ + int desc_size = ext4_sb_get_desc_size(sb); + uint32_t csum = ext4_ialloc_bitmap_csum(sb, bitmap); + uint16_t lo_csum = to_le16(csum & 0xFFFF), + hi_csum = to_le16(csum >> 16); + + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + return; + + /* See if we need to assign a 32bit checksum */ + bg->inode_bitmap_csum_lo = lo_csum; + if (desc_size == EXT4_MAX_BLOCK_GROUP_DESCRIPTOR_SIZE) + bg->inode_bitmap_csum_hi = hi_csum; + +} + +#if CONFIG_META_CSUM_ENABLE +static bool +ext4_ialloc_verify_bitmap_csum(struct ext4_sblock *sb, struct ext4_bgroup *bg, + void *bitmap __unused) +{ + + int desc_size = ext4_sb_get_desc_size(sb); + uint32_t csum = ext4_ialloc_bitmap_csum(sb, bitmap); + uint16_t lo_csum = to_le16(csum & 0xFFFF), + hi_csum = to_le16(csum >> 16); + + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + return true; + + if (bg->inode_bitmap_csum_lo != lo_csum) + return false; + + if (desc_size == EXT4_MAX_BLOCK_GROUP_DESCRIPTOR_SIZE) + if (bg->inode_bitmap_csum_hi != hi_csum) + return false; + + return true; +} +#else +#define ext4_ialloc_verify_bitmap_csum(...) true +#endif + +int ext4_ialloc_free_inode(struct ext4_fs *fs, uint32_t index, bool is_dir) +{ + struct ext4_sblock *sb = &fs->sb; + + /* Compute index of block group and load it */ + uint32_t block_group = ext4_ialloc_get_bgid_of_inode(sb, index); + + struct ext4_block_group_ref bg_ref; + int rc = ext4_fs_get_block_group_ref(fs, block_group, &bg_ref); + if (rc != EOK) + return rc; + + struct ext4_bgroup *bg = bg_ref.block_group; + + /* Load i-node bitmap */ + ext4_fsblk_t bitmap_block_addr = + ext4_bg_get_inode_bitmap(bg, sb); + + struct ext4_block b; + rc = ext4_trans_block_get(fs->bdev, &b, bitmap_block_addr); + if (rc != EOK) + return rc; + + if (!ext4_ialloc_verify_bitmap_csum(sb, bg, b.data)) { + ext4_dbg(DEBUG_IALLOC, + DBG_WARN "Bitmap checksum failed." + "Group: %" PRIu32"\n", + bg_ref.index); + } + + /* Free i-node in the bitmap */ + uint32_t index_in_group = ext4_ialloc_inode_to_bgidx(sb, index); + ext4_bmap_bit_clr(b.data, index_in_group); + ext4_ialloc_set_bitmap_csum(sb, bg, b.data); + ext4_trans_set_block_dirty(b.buf); + + /* Put back the block with bitmap */ + rc = ext4_block_set(fs->bdev, &b); + if (rc != EOK) { + /* Error in saving bitmap */ + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + /* If released i-node is a directory, decrement used directories count + */ + if (is_dir) { + uint32_t bg_used_dirs = ext4_bg_get_used_dirs_count(bg, sb); + bg_used_dirs--; + ext4_bg_set_used_dirs_count(bg, sb, bg_used_dirs); + } + + /* Update block group free inodes count */ + uint32_t free_inodes = ext4_bg_get_free_inodes_count(bg, sb); + free_inodes++; + ext4_bg_set_free_inodes_count(bg, sb, free_inodes); + + bg_ref.dirty = true; + + /* Put back the modified block group */ + rc = ext4_fs_put_block_group_ref(&bg_ref); + if (rc != EOK) + return rc; + + /* Update superblock free inodes count */ + ext4_set32(sb, free_inodes_count, + ext4_get32(sb, free_inodes_count) + 1); + + return EOK; +} + +int ext4_ialloc_alloc_inode(struct ext4_fs *fs, uint32_t *idx, bool is_dir) +{ + struct ext4_sblock *sb = &fs->sb; + + uint32_t bgid = fs->last_inode_bg_id; + uint32_t bg_count = ext4_block_group_cnt(sb); + uint32_t sb_free_inodes = ext4_get32(sb, free_inodes_count); + bool rewind = false; + + /* Try to find free i-node in all block groups */ + while (bgid <= bg_count) { + + if (bgid == bg_count) { + if (rewind) + break; + bg_count = fs->last_inode_bg_id; + bgid = 0; + rewind = true; + continue; + } + + /* Load block group to check */ + struct ext4_block_group_ref bg_ref; + int rc = ext4_fs_get_block_group_ref(fs, bgid, &bg_ref); + if (rc != EOK) + return rc; + + struct ext4_bgroup *bg = bg_ref.block_group; + + /* Read necessary values for algorithm */ + uint32_t free_inodes = ext4_bg_get_free_inodes_count(bg, sb); + uint32_t used_dirs = ext4_bg_get_used_dirs_count(bg, sb); + + /* Check if this block group is good candidate for allocation */ + if (free_inodes > 0) { + /* Load block with bitmap */ + ext4_fsblk_t bmp_blk_add = ext4_bg_get_inode_bitmap(bg, sb); + + struct ext4_block b; + rc = ext4_trans_block_get(fs->bdev, &b, bmp_blk_add); + if (rc != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + if (!ext4_ialloc_verify_bitmap_csum(sb, bg, b.data)) { + ext4_dbg(DEBUG_IALLOC, + DBG_WARN "Bitmap checksum failed." + "Group: %" PRIu32"\n", + bg_ref.index); + } + + /* Try to allocate i-node in the bitmap */ + uint32_t inodes_in_bg; + uint32_t idx_in_bg; + + inodes_in_bg = ext4_inodes_in_group_cnt(sb, bgid); + rc = ext4_bmap_bit_find_clr(b.data, 0, inodes_in_bg, + &idx_in_bg); + /* Block group has not any free i-node */ + if (rc == ENOSPC) { + rc = ext4_block_set(fs->bdev, &b); + if (rc != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + rc = ext4_fs_put_block_group_ref(&bg_ref); + if (rc != EOK) + return rc; + + continue; + } + + ext4_bmap_bit_set(b.data, idx_in_bg); + + /* Free i-node found, save the bitmap */ + ext4_ialloc_set_bitmap_csum(sb,bg, + b.data); + ext4_trans_set_block_dirty(b.buf); + + ext4_block_set(fs->bdev, &b); + if (rc != EOK) { + ext4_fs_put_block_group_ref(&bg_ref); + return rc; + } + + /* Modify filesystem counters */ + free_inodes--; + ext4_bg_set_free_inodes_count(bg, sb, free_inodes); + + /* Increment used directories counter */ + if (is_dir) { + used_dirs++; + ext4_bg_set_used_dirs_count(bg, sb, used_dirs); + } + + /* Decrease unused inodes count */ + uint32_t unused = + ext4_bg_get_itable_unused(bg, sb); + + uint32_t free = inodes_in_bg - unused; + + if (idx_in_bg >= free) { + unused = inodes_in_bg - (idx_in_bg + 1); + ext4_bg_set_itable_unused(bg, sb, unused); + } + + /* Save modified block group */ + bg_ref.dirty = true; + + rc = ext4_fs_put_block_group_ref(&bg_ref); + if (rc != EOK) + return rc; + + /* Update superblock */ + sb_free_inodes--; + ext4_set32(sb, free_inodes_count, sb_free_inodes); + + /* Compute the absolute i-nodex number */ + *idx = ext4_ialloc_bgidx_to_inode(sb, idx_in_bg, bgid); + + fs->last_inode_bg_id = bgid; + + return EOK; + } + + /* Block group not modified, put it and jump to the next block + * group */ + ext4_fs_put_block_group_ref(&bg_ref); + if (rc != EOK) + return rc; + + ++bgid; + } + + return ENOSPC; +} + +/** + * @} + */ diff --git a/src/ext4_inode.c b/src/ext4_inode.c new file mode 100644 index 0000000..9b6eb87 --- /dev/null +++ b/src/ext4_inode.c @@ -0,0 +1,372 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_inode.c + * @brief Inode handle functions + */ + +#include "ext4_config.h" +#include "ext4_types.h" +#include "ext4_inode.h" +#include "ext4_super.h" + +/**@brief Compute number of bits for block count. + * @param block_size Filesystem block_size + * @return Number of bits + */ +static uint32_t ext4_inode_block_bits_count(uint32_t block_size) +{ + uint32_t bits = 8; + uint32_t size = block_size; + + do { + bits++; + size = size >> 1; + } while (size > 256); + + return bits; +} + +uint32_t ext4_inode_get_mode(struct ext4_sblock *sb, struct ext4_inode *inode) +{ + uint32_t v = to_le16(inode->mode); + + if (ext4_get32(sb, creator_os) == EXT4_SUPERBLOCK_OS_HURD) { + v |= ((uint32_t)to_le16(inode->osd2.hurd2.mode_high)) << 16; + } + + return v; +} + +void ext4_inode_set_mode(struct ext4_sblock *sb, struct ext4_inode *inode, + uint32_t mode) +{ + inode->mode = to_le16((mode << 16) >> 16); + + if (ext4_get32(sb, creator_os) == EXT4_SUPERBLOCK_OS_HURD) + inode->osd2.hurd2.mode_high = to_le16(mode >> 16); +} + +uint32_t ext4_inode_get_uid(struct ext4_inode *inode) +{ + return to_le32(inode->uid); +} + +void ext4_inode_set_uid(struct ext4_inode *inode, uint32_t uid) +{ + inode->uid = to_le32(uid); +} + +uint64_t ext4_inode_get_size(struct ext4_sblock *sb, struct ext4_inode *inode) +{ + uint64_t v = to_le32(inode->size_lo); + + if ((ext4_get32(sb, rev_level) > 0) && + (ext4_inode_is_type(sb, inode, EXT4_INODE_MODE_FILE))) + v |= ((uint64_t)to_le32(inode->size_hi)) << 32; + + return v; +} + +void ext4_inode_set_size(struct ext4_inode *inode, uint64_t size) +{ + inode->size_lo = to_le32((size << 32) >> 32); + inode->size_hi = to_le32(size >> 32); +} + +uint32_t ext4_inode_get_csum(struct ext4_sblock *sb, struct ext4_inode *inode) +{ + uint16_t inode_size = ext4_get16(sb, inode_size); + uint32_t v = to_le16(inode->osd2.linux2.checksum_lo); + + if (inode_size > EXT4_GOOD_OLD_INODE_SIZE) + v |= ((uint32_t)to_le16(inode->checksum_hi)) << 16; + + return v; +} + +void ext4_inode_set_csum(struct ext4_sblock *sb, struct ext4_inode *inode, + uint32_t checksum) +{ + uint16_t inode_size = ext4_get16(sb, inode_size); + inode->osd2.linux2.checksum_lo = + to_le16((checksum << 16) >> 16); + + if (inode_size > EXT4_GOOD_OLD_INODE_SIZE) + inode->checksum_hi = to_le16(checksum >> 16); + +} + +uint32_t ext4_inode_get_access_time(struct ext4_inode *inode) +{ + return to_le32(inode->access_time); +} +void ext4_inode_set_access_time(struct ext4_inode *inode, uint32_t time) +{ + inode->access_time = to_le32(time); +} + +uint32_t ext4_inode_get_change_inode_time(struct ext4_inode *inode) +{ + return to_le32(inode->change_inode_time); +} +void ext4_inode_set_change_inode_time(struct ext4_inode *inode, uint32_t time) +{ + inode->change_inode_time = to_le32(time); +} + +uint32_t ext4_inode_get_modif_time(struct ext4_inode *inode) +{ + return to_le32(inode->modification_time); +} + +void ext4_inode_set_modif_time(struct ext4_inode *inode, uint32_t time) +{ + inode->modification_time = to_le32(time); +} + +uint32_t ext4_inode_get_del_time(struct ext4_inode *inode) +{ + return to_le32(inode->deletion_time); +} + +void ext4_inode_set_del_time(struct ext4_inode *inode, uint32_t time) +{ + inode->deletion_time = to_le32(time); +} + +uint32_t ext4_inode_get_gid(struct ext4_inode *inode) +{ + return to_le32(inode->gid); +} +void ext4_inode_set_gid(struct ext4_inode *inode, uint32_t gid) +{ + inode->gid = to_le32(gid); +} + +uint16_t ext4_inode_get_links_cnt(struct ext4_inode *inode) +{ + return to_le16(inode->links_count); +} +void ext4_inode_set_links_cnt(struct ext4_inode *inode, uint16_t cnt) +{ + inode->links_count = to_le16(cnt); +} + +uint64_t ext4_inode_get_blocks_count(struct ext4_sblock *sb, + struct ext4_inode *inode) +{ + uint64_t cnt = to_le32(inode->blocks_count_lo); + + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_HUGE_FILE)) { + + /* 48-bit field */ + cnt |= (uint64_t)to_le16(inode->osd2.linux2.blocks_high) << 32; + + if (ext4_inode_has_flag(inode, EXT4_INODE_FLAG_HUGE_FILE)) { + + uint32_t block_count = ext4_sb_get_block_size(sb); + uint32_t b = ext4_inode_block_bits_count(block_count); + return cnt << (b - 9); + } + } + + return cnt; +} + +int ext4_inode_set_blocks_count(struct ext4_sblock *sb, + struct ext4_inode *inode, uint64_t count) +{ + /* 32-bit maximum */ + uint64_t max = 0; + max = ~max >> 32; + + if (count <= max) { + inode->blocks_count_lo = to_le32((uint32_t)count); + inode->osd2.linux2.blocks_high = 0; + ext4_inode_clear_flag(inode, EXT4_INODE_FLAG_HUGE_FILE); + + return EOK; + } + + /* Check if there can be used huge files (many blocks) */ + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_HUGE_FILE)) + return EINVAL; + + /* 48-bit maximum */ + max = 0; + max = ~max >> 16; + + if (count <= max) { + inode->blocks_count_lo = to_le32((uint32_t)count); + inode->osd2.linux2.blocks_high = to_le16((uint16_t)(count >> 32)); + ext4_inode_clear_flag(inode, EXT4_INODE_FLAG_HUGE_FILE); + } else { + uint32_t block_count = ext4_sb_get_block_size(sb); + uint32_t block_bits =ext4_inode_block_bits_count(block_count); + + ext4_inode_set_flag(inode, EXT4_INODE_FLAG_HUGE_FILE); + count = count >> (block_bits - 9); + inode->blocks_count_lo = to_le32((uint32_t)count); + inode->osd2.linux2.blocks_high = to_le16((uint16_t)(count >> 32)); + } + + return EOK; +} + +uint32_t ext4_inode_get_flags(struct ext4_inode *inode) +{ + return to_le32(inode->flags); +} +void ext4_inode_set_flags(struct ext4_inode *inode, uint32_t flags) +{ + inode->flags = to_le32(flags); +} + +uint32_t ext4_inode_get_generation(struct ext4_inode *inode) +{ + return to_le32(inode->generation); +} +void ext4_inode_set_generation(struct ext4_inode *inode, uint32_t gen) +{ + inode->generation = to_le32(gen); +} + +uint16_t ext4_inode_get_extra_isize(struct ext4_inode *inode) +{ + return to_le16(inode->extra_isize); +} + +void ext4_inode_set_extra_isize(struct ext4_inode *inode, uint16_t size) +{ + inode->extra_isize = to_le16(size); +} + +uint64_t ext4_inode_get_file_acl(struct ext4_inode *inode, + struct ext4_sblock *sb) +{ + uint64_t v = to_le32(inode->file_acl_lo); + + if (ext4_get32(sb, creator_os) == EXT4_SUPERBLOCK_OS_LINUX) + v |= (uint32_t)to_le16(inode->osd2.linux2.file_acl_high) << 16; + + return v; +} + +void ext4_inode_set_file_acl(struct ext4_inode *inode, struct ext4_sblock *sb, + uint64_t acl) +{ + inode->file_acl_lo = to_le32((acl << 32) >> 32); + + if (ext4_get32(sb, creator_os) == EXT4_SUPERBLOCK_OS_LINUX) + inode->osd2.linux2.file_acl_high = to_le16((uint16_t)(acl >> 32)); +} + +uint32_t ext4_inode_get_direct_block(struct ext4_inode *inode, uint32_t idx) +{ + return to_le32(inode->blocks[idx]); +} +void ext4_inode_set_direct_block(struct ext4_inode *inode, uint32_t idx, + uint32_t block) +{ + inode->blocks[idx] = to_le32(block); +} + +uint32_t ext4_inode_get_indirect_block(struct ext4_inode *inode, uint32_t idx) +{ + return to_le32(inode->blocks[idx + EXT4_INODE_INDIRECT_BLOCK]); +} + +void ext4_inode_set_indirect_block(struct ext4_inode *inode, uint32_t idx, + uint32_t block) +{ + inode->blocks[idx + EXT4_INODE_INDIRECT_BLOCK] = to_le32(block); +} + +uint32_t ext4_inode_type(struct ext4_sblock *sb, struct ext4_inode *inode) +{ + return (ext4_inode_get_mode(sb, inode) & EXT4_INODE_MODE_TYPE_MASK); +} + +bool ext4_inode_is_type(struct ext4_sblock *sb, struct ext4_inode *inode, + uint32_t type) +{ + return ext4_inode_type(sb, inode) == type; +} + +bool ext4_inode_has_flag(struct ext4_inode *inode, uint32_t f) +{ + return ext4_inode_get_flags(inode) & f; +} + +void ext4_inode_clear_flag(struct ext4_inode *inode, uint32_t f) +{ + uint32_t flags = ext4_inode_get_flags(inode); + flags = flags & (~f); + ext4_inode_set_flags(inode, flags); +} + +void ext4_inode_set_flag(struct ext4_inode *inode, uint32_t f) +{ + uint32_t flags = ext4_inode_get_flags(inode); + flags = flags | f; + ext4_inode_set_flags(inode, flags); +} + +bool ext4_inode_can_truncate(struct ext4_sblock *sb, struct ext4_inode *inode) +{ + if ((ext4_inode_has_flag(inode, EXT4_INODE_FLAG_APPEND)) || + (ext4_inode_has_flag(inode, EXT4_INODE_FLAG_IMMUTABLE))) + return false; + + if ((ext4_inode_is_type(sb, inode, EXT4_INODE_MODE_FILE)) || + (ext4_inode_is_type(sb, inode, EXT4_INODE_MODE_DIRECTORY)) || + (ext4_inode_is_type(sb, inode, EXT4_INODE_MODE_SOFTLINK))) + return true; + + return false; +} + +struct ext4_extent_header * +ext4_inode_get_extent_header(struct ext4_inode *inode) +{ + return (struct ext4_extent_header *)inode->blocks; +} + +/** + * @} + */ diff --git a/src/ext4_journal.c b/src/ext4_journal.c new file mode 100644 index 0000000..73a84bf --- /dev/null +++ b/src/ext4_journal.c @@ -0,0 +1,2158 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * Copyright (c) 2015 Kaho Ng (ngkaho1234@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_journal.c + * @brief Journal handle functions + */ + +#include "ext4_config.h" +#include "ext4_types.h" +#include "ext4_fs.h" +#include "ext4_super.h" +#include "ext4_journal.h" +#include "ext4_errno.h" +#include "ext4_blockdev.h" +#include "ext4_crc32.h" +#include "ext4_debug.h" + +#include <string.h> +#include <stdlib.h> + +/**@brief Revoke entry during journal replay.*/ +struct revoke_entry { + /**@brief Block number not to be replayed.*/ + ext4_fsblk_t block; + + /**@brief For any transaction id smaller + * than trans_id, records of @block + * in those transactions should not + * be replayed.*/ + uint32_t trans_id; + + /**@brief Revoke tree node.*/ + RB_ENTRY(revoke_entry) revoke_node; +}; + +/**@brief Valid journal replay information.*/ +struct recover_info { + /**@brief Starting transaction id.*/ + uint32_t start_trans_id; + + /**@brief Ending transaction id.*/ + uint32_t last_trans_id; + + /**@brief Used as internal argument.*/ + uint32_t this_trans_id; + + /**@brief No of transactions went through.*/ + uint32_t trans_cnt; + + /**@brief RB-Tree storing revoke entries.*/ + RB_HEAD(jbd_revoke, revoke_entry) revoke_root; +}; + +/**@brief Journal replay internal arguments.*/ +struct replay_arg { + /**@brief Journal replay information.*/ + struct recover_info *info; + + /**@brief Current block we are on.*/ + uint32_t *this_block; + + /**@brief Current trans_id we are on.*/ + uint32_t this_trans_id; +}; + +static int +jbd_revoke_entry_cmp(struct revoke_entry *a, struct revoke_entry *b) +{ + if (a->block > b->block) + return 1; + else if (a->block < b->block) + return -1; + return 0; +} + +static int +jbd_block_rec_cmp(struct jbd_block_rec *a, struct jbd_block_rec *b) +{ + if (a->lba > b->lba) + return 1; + else if (a->lba < b->lba) + return -1; + return 0; +} + +RB_GENERATE_INTERNAL(jbd_revoke, revoke_entry, revoke_node, + jbd_revoke_entry_cmp, static inline) +RB_GENERATE_INTERNAL(jbd_block, jbd_block_rec, block_rec_node, + jbd_block_rec_cmp, static inline) + +#define jbd_alloc_revoke_entry() calloc(1, sizeof(struct revoke_entry)) +#define jbd_free_revoke_entry(addr) free(addr) + +static int jbd_has_csum(struct jbd_sb *jbd_sb) +{ + if (JBD_HAS_INCOMPAT_FEATURE(jbd_sb, JBD_FEATURE_INCOMPAT_CSUM_V2)) + return 2; + + if (JBD_HAS_INCOMPAT_FEATURE(jbd_sb, JBD_FEATURE_INCOMPAT_CSUM_V3)) + return 3; + + return 0; +} + +#if CONFIG_META_CSUM_ENABLE +static uint32_t jbd_sb_csum(struct jbd_sb *jbd_sb) +{ + uint32_t checksum = 0; + + if (jbd_has_csum(jbd_sb)) { + uint32_t orig_checksum = jbd_sb->checksum; + jbd_set32(jbd_sb, checksum, 0); + /* Calculate crc32c checksum against tho whole superblock */ + checksum = ext4_crc32c(EXT4_CRC32_INIT, jbd_sb, + JBD_SUPERBLOCK_SIZE); + jbd_sb->checksum = orig_checksum; + } + return checksum; +} +#else +#define jbd_sb_csum(...) 0 +#endif + +static void jbd_sb_csum_set(struct jbd_sb *jbd_sb) +{ + if (!jbd_has_csum(jbd_sb)) + return; + + jbd_set32(jbd_sb, checksum, jbd_sb_csum(jbd_sb)); +} + +#if CONFIG_META_CSUM_ENABLE +static bool +jbd_verify_sb_csum(struct jbd_sb *jbd_sb) +{ + if (!jbd_has_csum(jbd_sb)) + return true; + + return jbd_sb_csum(jbd_sb) == jbd_get32(jbd_sb, checksum); +} +#else +#define jbd_verify_sb_csum(...) true +#endif + +#if CONFIG_META_CSUM_ENABLE +static uint32_t jbd_meta_csum(struct jbd_fs *jbd_fs, + struct jbd_bhdr *bhdr) +{ + uint32_t checksum = 0; + + if (jbd_has_csum(&jbd_fs->sb)) { + uint32_t block_size = jbd_get32(&jbd_fs->sb, blocksize); + struct jbd_block_tail *tail = + (struct jbd_block_tail *)((char *)bhdr + block_size - + sizeof(struct jbd_block_tail)); + uint32_t orig_checksum = tail->checksum; + tail->checksum = 0; + + /* First calculate crc32c checksum against fs uuid */ + checksum = ext4_crc32c(EXT4_CRC32_INIT, jbd_fs->sb.uuid, + sizeof(jbd_fs->sb.uuid)); + /* Calculate crc32c checksum against tho whole block */ + checksum = ext4_crc32c(checksum, bhdr, + block_size); + tail->checksum = orig_checksum; + } + return checksum; +} +#else +#define jbd_meta_csum(...) 0 +#endif + +static void jbd_meta_csum_set(struct jbd_fs *jbd_fs, + struct jbd_bhdr *bhdr) +{ + uint32_t block_size = jbd_get32(&jbd_fs->sb, blocksize); + struct jbd_block_tail *tail = (struct jbd_block_tail *) + ((char *)bhdr + block_size - + sizeof(struct jbd_block_tail)); + if (!jbd_has_csum(&jbd_fs->sb)) + return; + + tail->checksum = to_be32(jbd_meta_csum(jbd_fs, bhdr)); +} + +#if CONFIG_META_CSUM_ENABLE +static bool +jbd_verify_meta_csum(struct jbd_fs *jbd_fs, + struct jbd_bhdr *bhdr) +{ + uint32_t block_size = jbd_get32(&jbd_fs->sb, blocksize); + struct jbd_block_tail *tail = (struct jbd_block_tail *) + ((char *)bhdr + block_size - + sizeof(struct jbd_block_tail)); + if (!jbd_has_csum(&jbd_fs->sb)) + return true; + + return jbd_meta_csum(jbd_fs, bhdr) == to_be32(tail->checksum); +} +#else +#define jbd_verify_meta_csum(...) true +#endif + +#if CONFIG_META_CSUM_ENABLE +static uint32_t jbd_commit_csum(struct jbd_fs *jbd_fs, + struct jbd_commit_header *header) +{ + uint32_t checksum = 0; + + if (jbd_has_csum(&jbd_fs->sb)) { + uint32_t orig_checksum_type = header->chksum_type, + orig_checksum_size = header->chksum_size, + orig_checksum = header->chksum[0]; + uint32_t block_size = jbd_get32(&jbd_fs->sb, blocksize); + header->chksum_type = 0; + header->chksum_size = 0; + header->chksum[0] = 0; + + /* First calculate crc32c checksum against fs uuid */ + checksum = ext4_crc32c(EXT4_CRC32_INIT, jbd_fs->sb.uuid, + sizeof(jbd_fs->sb.uuid)); + /* Calculate crc32c checksum against tho whole block */ + checksum = ext4_crc32c(checksum, header, + block_size); + + header->chksum_type = orig_checksum_type; + header->chksum_size = orig_checksum_size; + header->chksum[0] = orig_checksum; + } + return checksum; +} +#else +#define jbd_commit_csum(...) 0 +#endif + +static void jbd_commit_csum_set(struct jbd_fs *jbd_fs, + struct jbd_commit_header *header) +{ + if (!jbd_has_csum(&jbd_fs->sb)) + return; + + header->chksum_type = 0; + header->chksum_size = 0; + header->chksum[0] = jbd_commit_csum(jbd_fs, header); +} + +#if CONFIG_META_CSUM_ENABLE +static bool jbd_verify_commit_csum(struct jbd_fs *jbd_fs, + struct jbd_commit_header *header) +{ + if (!jbd_has_csum(&jbd_fs->sb)) + return true; + + return header->chksum[0] == to_be32(jbd_commit_csum(jbd_fs, + header)); +} +#else +#define jbd_verify_commit_csum(...) true +#endif + +#if CONFIG_META_CSUM_ENABLE +/* + * NOTE: We only make use of @csum parameter when + * JBD_FEATURE_COMPAT_CHECKSUM is enabled. + */ +static uint32_t jbd_block_csum(struct jbd_fs *jbd_fs, const void *buf, + uint32_t csum, + uint32_t sequence) +{ + uint32_t checksum = 0; + + if (jbd_has_csum(&jbd_fs->sb)) { + uint32_t block_size = jbd_get32(&jbd_fs->sb, blocksize); + /* First calculate crc32c checksum against fs uuid */ + checksum = ext4_crc32c(EXT4_CRC32_INIT, jbd_fs->sb.uuid, + sizeof(jbd_fs->sb.uuid)); + /* Then calculate crc32c checksum against sequence no. */ + checksum = ext4_crc32c(checksum, &sequence, + sizeof(uint32_t)); + /* Calculate crc32c checksum against tho whole block */ + checksum = ext4_crc32c(checksum, buf, + block_size); + } else if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_COMPAT_CHECKSUM)) { + uint32_t block_size = jbd_get32(&jbd_fs->sb, blocksize); + /* Calculate crc32c checksum against tho whole block */ + checksum = ext4_crc32(csum, buf, + block_size); + } + return checksum; +} +#else +#define jbd_block_csum(...) 0 +#endif + +static void jbd_block_tag_csum_set(struct jbd_fs *jbd_fs, void *__tag, + uint32_t checksum) +{ + int ver = jbd_has_csum(&jbd_fs->sb); + if (!ver) + return; + + if (ver == 2) { + struct jbd_block_tag *tag = __tag; + tag->checksum = (uint16_t)to_be32(checksum); + } else { + struct jbd_block_tag3 *tag = __tag; + tag->checksum = to_be32(checksum); + } +} + +/**@brief Write jbd superblock to disk. + * @param jbd_fs jbd filesystem + * @param s jbd superblock + * @return standard error code*/ +static int jbd_sb_write(struct jbd_fs *jbd_fs, struct jbd_sb *s) +{ + int rc; + struct ext4_fs *fs = jbd_fs->inode_ref.fs; + uint64_t offset; + ext4_fsblk_t fblock; + rc = jbd_inode_bmap(jbd_fs, 0, &fblock); + if (rc != EOK) + return rc; + + jbd_sb_csum_set(s); + offset = fblock * ext4_sb_get_block_size(&fs->sb); + return ext4_block_writebytes(fs->bdev, offset, s, + EXT4_SUPERBLOCK_SIZE); +} + +/**@brief Read jbd superblock from disk. + * @param jbd_fs jbd filesystem + * @param s jbd superblock + * @return standard error code*/ +static int jbd_sb_read(struct jbd_fs *jbd_fs, struct jbd_sb *s) +{ + int rc; + struct ext4_fs *fs = jbd_fs->inode_ref.fs; + uint64_t offset; + ext4_fsblk_t fblock; + rc = jbd_inode_bmap(jbd_fs, 0, &fblock); + if (rc != EOK) + return rc; + + offset = fblock * ext4_sb_get_block_size(&fs->sb); + return ext4_block_readbytes(fs->bdev, offset, s, + EXT4_SUPERBLOCK_SIZE); +} + +/**@brief Verify jbd superblock. + * @param sb jbd superblock + * @return true if jbd superblock is valid */ +static bool jbd_verify_sb(struct jbd_sb *sb) +{ + struct jbd_bhdr *header = &sb->header; + if (jbd_get32(header, magic) != JBD_MAGIC_NUMBER) + return false; + + if (jbd_get32(header, blocktype) != JBD_SUPERBLOCK && + jbd_get32(header, blocktype) != JBD_SUPERBLOCK_V2) + return false; + + return jbd_verify_sb_csum(sb); +} + +/**@brief Write back dirty jbd superblock to disk. + * @param jbd_fs jbd filesystem + * @return standard error code*/ +static int jbd_write_sb(struct jbd_fs *jbd_fs) +{ + int rc = EOK; + if (jbd_fs->dirty) { + rc = jbd_sb_write(jbd_fs, &jbd_fs->sb); + if (rc != EOK) + return rc; + + jbd_fs->dirty = false; + } + return rc; +} + +/**@brief Get reference to jbd filesystem. + * @param fs Filesystem to load journal of + * @param jbd_fs jbd filesystem + * @return standard error code*/ +int jbd_get_fs(struct ext4_fs *fs, + struct jbd_fs *jbd_fs) +{ + int rc; + uint32_t journal_ino; + + memset(jbd_fs, 0, sizeof(struct jbd_fs)); + /* See if there is journal inode on this filesystem.*/ + /* FIXME: detection on existance ofbkejournal bdev is + * missing.*/ + journal_ino = ext4_get32(&fs->sb, journal_inode_number); + + rc = ext4_fs_get_inode_ref(fs, + journal_ino, + &jbd_fs->inode_ref); + if (rc != EOK) { + memset(jbd_fs, 0, sizeof(struct jbd_fs)); + return rc; + } + rc = jbd_sb_read(jbd_fs, &jbd_fs->sb); + if (rc != EOK) { + memset(jbd_fs, 0, sizeof(struct jbd_fs)); + ext4_fs_put_inode_ref(&jbd_fs->inode_ref); + return rc; + } + if (!jbd_verify_sb(&jbd_fs->sb)) { + memset(jbd_fs, 0, sizeof(struct jbd_fs)); + ext4_fs_put_inode_ref(&jbd_fs->inode_ref); + rc = EIO; + } + + return rc; +} + +/**@brief Put reference of jbd filesystem. + * @param jbd_fs jbd filesystem + * @return standard error code*/ +int jbd_put_fs(struct jbd_fs *jbd_fs) +{ + int rc = EOK; + rc = jbd_write_sb(jbd_fs); + + ext4_fs_put_inode_ref(&jbd_fs->inode_ref); + return rc; +} + +/**@brief Data block lookup helper. + * @param jbd_fs jbd filesystem + * @param iblock block index + * @param fblock logical block address + * @return standard error code*/ +int jbd_inode_bmap(struct jbd_fs *jbd_fs, + ext4_lblk_t iblock, + ext4_fsblk_t *fblock) +{ + int rc = ext4_fs_get_inode_dblk_idx( + &jbd_fs->inode_ref, + iblock, + fblock, + false); + return rc; +} + +/**@brief jbd block get function (through cache). + * @param jbd_fs jbd filesystem + * @param block block descriptor + * @param fblock jbd logical block address + * @return standard error code*/ +static int jbd_block_get(struct jbd_fs *jbd_fs, + struct ext4_block *block, + ext4_fsblk_t fblock) +{ + /* TODO: journal device. */ + int rc; + ext4_lblk_t iblock = (ext4_lblk_t)fblock; + + /* Lookup the logical block address of + * fblock.*/ + rc = jbd_inode_bmap(jbd_fs, iblock, + &fblock); + if (rc != EOK) + return rc; + + struct ext4_blockdev *bdev = jbd_fs->inode_ref.fs->bdev; + rc = ext4_block_get(bdev, block, fblock); + + /* If succeeded, mark buffer as BC_FLUSH to indicate + * that data should be written to disk immediately.*/ + if (rc == EOK) { + ext4_bcache_set_flag(block->buf, BC_FLUSH); + /* As we don't want to occupy too much space + * in block cache, we set this buffer BC_TMP.*/ + ext4_bcache_set_flag(block->buf, BC_TMP); + } + + return rc; +} + +/**@brief jbd block get function (through cache, don't read). + * @param jbd_fs jbd filesystem + * @param block block descriptor + * @param fblock jbd logical block address + * @return standard error code*/ +static int jbd_block_get_noread(struct jbd_fs *jbd_fs, + struct ext4_block *block, + ext4_fsblk_t fblock) +{ + /* TODO: journal device. */ + int rc; + ext4_lblk_t iblock = (ext4_lblk_t)fblock; + rc = jbd_inode_bmap(jbd_fs, iblock, + &fblock); + if (rc != EOK) + return rc; + + struct ext4_blockdev *bdev = jbd_fs->inode_ref.fs->bdev; + rc = ext4_block_get_noread(bdev, block, fblock); + if (rc == EOK) + ext4_bcache_set_flag(block->buf, BC_FLUSH); + + return rc; +} + +/**@brief jbd block set procedure (through cache). + * @param jbd_fs jbd filesystem + * @param block block descriptor + * @return standard error code*/ +static int jbd_block_set(struct jbd_fs *jbd_fs, + struct ext4_block *block) +{ + return ext4_block_set(jbd_fs->inode_ref.fs->bdev, + block); +} + +/**@brief helper functions to calculate + * block tag size, not including UUID part. + * @param jbd_fs jbd filesystem + * @return tag size in bytes*/ +static int jbd_tag_bytes(struct jbd_fs *jbd_fs) +{ + int size; + + /* It is very easy to deal with the case which + * JBD_FEATURE_INCOMPAT_CSUM_V3 is enabled.*/ + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_CSUM_V3)) + return sizeof(struct jbd_block_tag3); + + size = sizeof(struct jbd_block_tag); + + /* If JBD_FEATURE_INCOMPAT_CSUM_V2 is enabled, + * add 2 bytes to size.*/ + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_CSUM_V2)) + size += sizeof(uint16_t); + + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_64BIT)) + return size; + + /* If block number is 4 bytes in size, + * minus 4 bytes from size */ + return size - sizeof(uint32_t); +} + +/**@brief Tag information. */ +struct tag_info { + /**@brief Tag size in bytes, including UUID part.*/ + int tag_bytes; + + /**@brief block number stored in this tag.*/ + ext4_fsblk_t block; + + /**@brief whether UUID part exists or not.*/ + bool uuid_exist; + + /**@brief UUID content if UUID part exists.*/ + uint8_t uuid[UUID_SIZE]; + + /**@brief Is this the last tag? */ + bool last_tag; + + /**@brief crc32c checksum. */ + uint32_t checksum; +}; + +/**@brief Extract information from a block tag. + * @param __tag pointer to the block tag + * @param tag_bytes block tag size of this jbd filesystem + * @param remaining size in buffer containing the block tag + * @param tag_info information of this tag. + * @return EOK when succeed, otherwise return EINVAL.*/ +static int +jbd_extract_block_tag(struct jbd_fs *jbd_fs, + void *__tag, + int tag_bytes, + int32_t remain_buf_size, + struct tag_info *tag_info) +{ + char *uuid_start; + tag_info->tag_bytes = tag_bytes; + tag_info->uuid_exist = false; + tag_info->last_tag = false; + + /* See whether it is possible to hold a valid block tag.*/ + if (remain_buf_size - tag_bytes < 0) + return EINVAL; + + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_CSUM_V3)) { + struct jbd_block_tag3 *tag = __tag; + tag_info->block = jbd_get32(tag, blocknr); + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_64BIT)) + tag_info->block |= + (uint64_t)jbd_get32(tag, blocknr_high) << 32; + + if (jbd_get32(tag, flags) & JBD_FLAG_ESCAPE) + tag_info->block = 0; + + if (!(jbd_get32(tag, flags) & JBD_FLAG_SAME_UUID)) { + /* See whether it is possible to hold UUID part.*/ + if (remain_buf_size - tag_bytes < UUID_SIZE) + return EINVAL; + + uuid_start = (char *)tag + tag_bytes; + tag_info->uuid_exist = true; + tag_info->tag_bytes += UUID_SIZE; + memcpy(tag_info->uuid, uuid_start, UUID_SIZE); + } + + if (jbd_get32(tag, flags) & JBD_FLAG_LAST_TAG) + tag_info->last_tag = true; + + } else { + struct jbd_block_tag *tag = __tag; + tag_info->block = jbd_get32(tag, blocknr); + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_64BIT)) + tag_info->block |= + (uint64_t)jbd_get32(tag, blocknr_high) << 32; + + if (jbd_get16(tag, flags) & JBD_FLAG_ESCAPE) + tag_info->block = 0; + + if (!(jbd_get16(tag, flags) & JBD_FLAG_SAME_UUID)) { + /* See whether it is possible to hold UUID part.*/ + if (remain_buf_size - tag_bytes < UUID_SIZE) + return EINVAL; + + uuid_start = (char *)tag + tag_bytes; + tag_info->uuid_exist = true; + tag_info->tag_bytes += UUID_SIZE; + memcpy(tag_info->uuid, uuid_start, UUID_SIZE); + } + + if (jbd_get16(tag, flags) & JBD_FLAG_LAST_TAG) + tag_info->last_tag = true; + + } + return EOK; +} + +/**@brief Write information to a block tag. + * @param __tag pointer to the block tag + * @param remaining size in buffer containing the block tag + * @param tag_info information of this tag. + * @return EOK when succeed, otherwise return EINVAL.*/ +static int +jbd_write_block_tag(struct jbd_fs *jbd_fs, + void *__tag, + int32_t remain_buf_size, + struct tag_info *tag_info) +{ + char *uuid_start; + int tag_bytes = jbd_tag_bytes(jbd_fs); + + tag_info->tag_bytes = tag_bytes; + + /* See whether it is possible to hold a valid block tag.*/ + if (remain_buf_size - tag_bytes < 0) + return EINVAL; + + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_CSUM_V3)) { + struct jbd_block_tag3 *tag = __tag; + memset(tag, 0, sizeof(struct jbd_block_tag3)); + jbd_set32(tag, blocknr, (uint32_t)tag_info->block); + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_64BIT)) + jbd_set32(tag, blocknr_high, tag_info->block >> 32); + + if (tag_info->uuid_exist) { + /* See whether it is possible to hold UUID part.*/ + if (remain_buf_size - tag_bytes < UUID_SIZE) + return EINVAL; + + uuid_start = (char *)tag + tag_bytes; + tag_info->tag_bytes += UUID_SIZE; + memcpy(uuid_start, tag_info->uuid, UUID_SIZE); + } else + jbd_set32(tag, flags, + jbd_get32(tag, flags) | JBD_FLAG_SAME_UUID); + + jbd_block_tag_csum_set(jbd_fs, __tag, tag_info->checksum); + + if (tag_info->last_tag) + jbd_set32(tag, flags, + jbd_get32(tag, flags) | JBD_FLAG_LAST_TAG); + + } else { + struct jbd_block_tag *tag = __tag; + memset(tag, 0, sizeof(struct jbd_block_tag)); + jbd_set32(tag, blocknr, (uint32_t)tag_info->block); + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_64BIT)) + jbd_set32(tag, blocknr_high, tag_info->block >> 32); + + if (tag_info->uuid_exist) { + /* See whether it is possible to hold UUID part.*/ + if (remain_buf_size - tag_bytes < UUID_SIZE) + return EINVAL; + + uuid_start = (char *)tag + tag_bytes; + tag_info->tag_bytes += UUID_SIZE; + memcpy(uuid_start, tag_info->uuid, UUID_SIZE); + } else + jbd_set16(tag, flags, + jbd_get16(tag, flags) | JBD_FLAG_SAME_UUID); + + jbd_block_tag_csum_set(jbd_fs, __tag, tag_info->checksum); + + if (tag_info->last_tag) + jbd_set16(tag, flags, + jbd_get16(tag, flags) | JBD_FLAG_LAST_TAG); + + } + return EOK; +} + +/**@brief Iterate all block tags in a block. + * @param jbd_fs jbd filesystem + * @param __tag_start pointer to the block + * @param tag_tbl_size size of the block + * @param func callback routine to indicate that + * a block tag is found + * @param arg additional argument to be passed to func */ +static void +jbd_iterate_block_table(struct jbd_fs *jbd_fs, + void *__tag_start, + int32_t tag_tbl_size, + void (*func)(struct jbd_fs * jbd_fs, + ext4_fsblk_t block, + uint8_t *uuid, + void *arg), + void *arg) +{ + char *tag_start, *tag_ptr; + int tag_bytes = jbd_tag_bytes(jbd_fs); + tag_start = __tag_start; + tag_ptr = tag_start; + + /* Cut off the size of block tail storing checksum. */ + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_CSUM_V2) || + JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_CSUM_V3)) + tag_tbl_size -= sizeof(struct jbd_block_tail); + + while (tag_tbl_size) { + struct tag_info tag_info; + int rc = jbd_extract_block_tag(jbd_fs, + tag_ptr, + tag_bytes, + tag_tbl_size, + &tag_info); + if (rc != EOK) + break; + + if (func) + func(jbd_fs, tag_info.block, tag_info.uuid, arg); + + /* Stop the iteration when we reach the last tag. */ + if (tag_info.last_tag) + break; + + tag_ptr += tag_info.tag_bytes; + tag_tbl_size -= tag_info.tag_bytes; + } +} + +static void jbd_display_block_tags(struct jbd_fs *jbd_fs, + ext4_fsblk_t block, + uint8_t *uuid, + void *arg) +{ + uint32_t *iblock = arg; + ext4_dbg(DEBUG_JBD, "Block in block_tag: %" PRIu64 "\n", block); + (*iblock)++; + (void)jbd_fs; + (void)uuid; + return; +} + +static struct revoke_entry * +jbd_revoke_entry_lookup(struct recover_info *info, ext4_fsblk_t block) +{ + struct revoke_entry tmp = { + .block = block + }; + + return RB_FIND(jbd_revoke, &info->revoke_root, &tmp); +} + +/**@brief Replay a block in a transaction. + * @param jbd_fs jbd filesystem + * @param block block address to be replayed.*/ +static void jbd_replay_block_tags(struct jbd_fs *jbd_fs, + ext4_fsblk_t block, + uint8_t *uuid __unused, + void *__arg) +{ + int r; + struct replay_arg *arg = __arg; + struct recover_info *info = arg->info; + uint32_t *this_block = arg->this_block; + struct revoke_entry *revoke_entry; + struct ext4_block journal_block, ext4_block; + struct ext4_fs *fs = jbd_fs->inode_ref.fs; + + (*this_block)++; + + /* We replay this block only if the current transaction id + * is equal or greater than that in revoke entry.*/ + revoke_entry = jbd_revoke_entry_lookup(info, block); + if (revoke_entry && + arg->this_trans_id < revoke_entry->trans_id) + return; + + ext4_dbg(DEBUG_JBD, + "Replaying block in block_tag: %" PRIu64 "\n", + block); + + r = jbd_block_get(jbd_fs, &journal_block, *this_block); + if (r != EOK) + return; + + /* We need special treatment for ext4 superblock. */ + if (block) { + r = ext4_block_get_noread(fs->bdev, &ext4_block, block); + if (r != EOK) { + jbd_block_set(jbd_fs, &journal_block); + return; + } + + memcpy(ext4_block.data, + journal_block.data, + jbd_get32(&jbd_fs->sb, blocksize)); + + ext4_bcache_set_dirty(ext4_block.buf); + ext4_block_set(fs->bdev, &ext4_block); + } else { + uint16_t mount_count, state; + mount_count = ext4_get16(&fs->sb, mount_count); + state = ext4_get16(&fs->sb, state); + + memcpy(&fs->sb, + journal_block.data + EXT4_SUPERBLOCK_OFFSET, + EXT4_SUPERBLOCK_SIZE); + + /* Mark system as mounted */ + ext4_set16(&fs->sb, state, state); + r = ext4_sb_write(fs->bdev, &fs->sb); + if (r != EOK) + return; + + /*Update mount count*/ + ext4_set16(&fs->sb, mount_count, mount_count); + } + + jbd_block_set(jbd_fs, &journal_block); + + return; +} + +/**@brief Add block address to revoke tree, along with + * its transaction id. + * @param info journal replay info + * @param block block address to be replayed.*/ +static void jbd_add_revoke_block_tags(struct recover_info *info, + ext4_fsblk_t block) +{ + struct revoke_entry *revoke_entry; + + ext4_dbg(DEBUG_JBD, "Add block %" PRIu64 " to revoke tree\n", block); + /* If the revoke entry with respect to the block address + * exists already, update its transaction id.*/ + revoke_entry = jbd_revoke_entry_lookup(info, block); + if (revoke_entry) { + revoke_entry->trans_id = info->this_trans_id; + return; + } + + revoke_entry = jbd_alloc_revoke_entry(); + ext4_assert(revoke_entry); + revoke_entry->block = block; + revoke_entry->trans_id = info->this_trans_id; + RB_INSERT(jbd_revoke, &info->revoke_root, revoke_entry); + + return; +} + +static void jbd_destroy_revoke_tree(struct recover_info *info) +{ + while (!RB_EMPTY(&info->revoke_root)) { + struct revoke_entry *revoke_entry = + RB_MIN(jbd_revoke, &info->revoke_root); + ext4_assert(revoke_entry); + RB_REMOVE(jbd_revoke, &info->revoke_root, revoke_entry); + jbd_free_revoke_entry(revoke_entry); + } +} + +/* Make sure we wrap around the log correctly! */ +#define wrap(sb, var) \ +do { \ + if (var >= jbd_get32((sb), maxlen)) \ + var -= (jbd_get32((sb), maxlen) - jbd_get32((sb), first)); \ +} while (0) + +#define ACTION_SCAN 0 +#define ACTION_REVOKE 1 +#define ACTION_RECOVER 2 + +/**@brief Add entries in a revoke block to revoke tree. + * @param jbd_fs jbd filesystem + * @param header revoke block header + * @param recover_info journal replay info*/ +static void jbd_build_revoke_tree(struct jbd_fs *jbd_fs, + struct jbd_bhdr *header, + struct recover_info *info) +{ + char *blocks_entry; + struct jbd_revoke_header *revoke_hdr = + (struct jbd_revoke_header *)header; + uint32_t i, nr_entries, record_len = 4; + + /* If we are working on a 64bit jbd filesystem, */ + if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb, + JBD_FEATURE_INCOMPAT_64BIT)) + record_len = 8; + + nr_entries = (jbd_get32(revoke_hdr, count) - + sizeof(struct jbd_revoke_header)) / + record_len; + + blocks_entry = (char *)(revoke_hdr + 1); + + for (i = 0;i < nr_entries;i++) { + if (record_len == 8) { + uint64_t *blocks = + (uint64_t *)blocks_entry; + jbd_add_revoke_block_tags(info, to_be64(*blocks)); + } else { + uint32_t *blocks = + (uint32_t *)blocks_entry; + jbd_add_revoke_block_tags(info, to_be32(*blocks)); + } + blocks_entry += record_len; + } +} + +static void jbd_debug_descriptor_block(struct jbd_fs *jbd_fs, + struct jbd_bhdr *header, + uint32_t *iblock) +{ + jbd_iterate_block_table(jbd_fs, + header + 1, + jbd_get32(&jbd_fs->sb, blocksize) - + sizeof(struct jbd_bhdr), + jbd_display_block_tags, + iblock); +} + +static void jbd_replay_descriptor_block(struct jbd_fs *jbd_fs, + struct jbd_bhdr *header, + struct replay_arg *arg) +{ + jbd_iterate_block_table(jbd_fs, + header + 1, + jbd_get32(&jbd_fs->sb, blocksize) - + sizeof(struct jbd_bhdr), + jbd_replay_block_tags, + arg); +} + +/**@brief The core routine of journal replay. + * @param jbd_fs jbd filesystem + * @param recover_info journal replay info + * @param action action needed to be taken + * @return standard error code*/ +static int jbd_iterate_log(struct jbd_fs *jbd_fs, + struct recover_info *info, + int action) +{ + int r = EOK; + bool log_end = false; + struct jbd_sb *sb = &jbd_fs->sb; + uint32_t start_trans_id, this_trans_id; + uint32_t start_block, this_block; + + /* We start iterating valid blocks in the whole journal.*/ + start_trans_id = this_trans_id = jbd_get32(sb, sequence); + start_block = this_block = jbd_get32(sb, start); + if (action == ACTION_SCAN) + info->trans_cnt = 0; + else if (!info->trans_cnt) + log_end = true; + + ext4_dbg(DEBUG_JBD, "Start of journal at trans id: %" PRIu32 "\n", + start_trans_id); + + while (!log_end) { + struct ext4_block block; + struct jbd_bhdr *header; + /* If we are not scanning for the last + * valid transaction in the journal, + * we will stop when we reach the end of + * the journal.*/ + if (action != ACTION_SCAN) + if (this_trans_id > info->last_trans_id) { + log_end = true; + continue; + } + + r = jbd_block_get(jbd_fs, &block, this_block); + if (r != EOK) + break; + + header = (struct jbd_bhdr *)block.data; + /* This block does not have a valid magic number, + * so we have reached the end of the journal.*/ + if (jbd_get32(header, magic) != JBD_MAGIC_NUMBER) { + jbd_block_set(jbd_fs, &block); + log_end = true; + continue; + } + + /* If the transaction id we found is not expected, + * we may have reached the end of the journal. + * + * If we are not scanning the journal, something + * bad might have taken place. :-( */ + if (jbd_get32(header, sequence) != this_trans_id) { + if (action != ACTION_SCAN) + r = EIO; + + jbd_block_set(jbd_fs, &block); + log_end = true; + continue; + } + + switch (jbd_get32(header, blocktype)) { + case JBD_DESCRIPTOR_BLOCK: + if (!jbd_verify_meta_csum(jbd_fs, header)) { + ext4_dbg(DEBUG_JBD, + DBG_WARN "Descriptor block checksum failed." + "Journal block: %" PRIu32"\n", + this_block); + log_end = true; + break; + } + ext4_dbg(DEBUG_JBD, "Descriptor block: %" PRIu32", " + "trans_id: %" PRIu32"\n", + this_block, this_trans_id); + if (action == ACTION_RECOVER) { + struct replay_arg replay_arg; + replay_arg.info = info; + replay_arg.this_block = &this_block; + replay_arg.this_trans_id = this_trans_id; + + jbd_replay_descriptor_block(jbd_fs, + header, &replay_arg); + } else + jbd_debug_descriptor_block(jbd_fs, + header, &this_block); + + break; + case JBD_COMMIT_BLOCK: + if (!jbd_verify_commit_csum(jbd_fs, + (struct jbd_commit_header *)header)) { + ext4_dbg(DEBUG_JBD, + DBG_WARN "Commit block checksum failed." + "Journal block: %" PRIu32"\n", + this_block); + log_end = true; + break; + } + ext4_dbg(DEBUG_JBD, "Commit block: %" PRIu32", " + "trans_id: %" PRIu32"\n", + this_block, this_trans_id); + /* This is the end of a transaction, + * we may now proceed to the next transaction. + */ + this_trans_id++; + info->trans_cnt++; + break; + case JBD_REVOKE_BLOCK: + if (!jbd_verify_meta_csum(jbd_fs, header)) { + ext4_dbg(DEBUG_JBD, + DBG_WARN "Revoke block checksum failed." + "Journal block: %" PRIu32"\n", + this_block); + log_end = true; + break; + } + ext4_dbg(DEBUG_JBD, "Revoke block: %" PRIu32", " + "trans_id: %" PRIu32"\n", + this_block, this_trans_id); + if (action == ACTION_REVOKE) { + info->this_trans_id = this_trans_id; + jbd_build_revoke_tree(jbd_fs, + header, info); + } + break; + default: + log_end = true; + break; + } + jbd_block_set(jbd_fs, &block); + this_block++; + wrap(sb, this_block); + if (this_block == start_block) + log_end = true; + + } + ext4_dbg(DEBUG_JBD, "End of journal.\n"); + if (r == EOK && action == ACTION_SCAN) { + /* We have finished scanning the journal. */ + info->start_trans_id = start_trans_id; + if (this_trans_id > start_trans_id) + info->last_trans_id = this_trans_id - 1; + else + info->last_trans_id = this_trans_id; + } + + return r; +} + +/**@brief Replay journal. + * @param jbd_fs jbd filesystem + * @return standard error code*/ +int jbd_recover(struct jbd_fs *jbd_fs) +{ + int r; + struct recover_info info; + struct jbd_sb *sb = &jbd_fs->sb; + if (!sb->start) + return EOK; + + RB_INIT(&info.revoke_root); + + r = jbd_iterate_log(jbd_fs, &info, ACTION_SCAN); + if (r != EOK) + return r; + + r = jbd_iterate_log(jbd_fs, &info, ACTION_REVOKE); + if (r != EOK) + return r; + + r = jbd_iterate_log(jbd_fs, &info, ACTION_RECOVER); + if (r == EOK) { + /* If we successfully replay the journal, + * clear EXT4_FINCOM_RECOVER flag on the + * ext4 superblock, and set the start of + * journal to 0.*/ + uint32_t features_incompatible = + ext4_get32(&jbd_fs->inode_ref.fs->sb, + features_incompatible); + jbd_set32(&jbd_fs->sb, start, 0); + features_incompatible &= ~EXT4_FINCOM_RECOVER; + ext4_set32(&jbd_fs->inode_ref.fs->sb, + features_incompatible, + features_incompatible); + jbd_fs->dirty = true; + r = ext4_sb_write(jbd_fs->inode_ref.fs->bdev, + &jbd_fs->inode_ref.fs->sb); + } + jbd_destroy_revoke_tree(&info); + return r; +} + +static void jbd_journal_write_sb(struct jbd_journal *journal) +{ + struct jbd_fs *jbd_fs = journal->jbd_fs; + jbd_set32(&jbd_fs->sb, start, journal->start); + jbd_set32(&jbd_fs->sb, sequence, journal->trans_id); + jbd_fs->dirty = true; +} + +/**@brief Start accessing the journal. + * @param jbd_fs jbd filesystem + * @param journal current journal session + * @return standard error code*/ +int jbd_journal_start(struct jbd_fs *jbd_fs, + struct jbd_journal *journal) +{ + int r; + uint32_t features_incompatible = + ext4_get32(&jbd_fs->inode_ref.fs->sb, + features_incompatible); + struct ext4_block block = EXT4_BLOCK_ZERO(); + features_incompatible |= EXT4_FINCOM_RECOVER; + ext4_set32(&jbd_fs->inode_ref.fs->sb, + features_incompatible, + features_incompatible); + r = ext4_sb_write(jbd_fs->inode_ref.fs->bdev, + &jbd_fs->inode_ref.fs->sb); + if (r != EOK) + return r; + + journal->first = jbd_get32(&jbd_fs->sb, first); + journal->start = journal->first; + journal->last = journal->first; + journal->trans_id = 1; + journal->alloc_trans_id = 1; + + journal->block_size = jbd_get32(&jbd_fs->sb, blocksize); + + r = jbd_block_get_noread(jbd_fs, + &block, + journal->start); + if (r != EOK) { + memset(journal, 0, sizeof(struct jbd_journal)); + return r; + } + memset(block.data, 0, journal->block_size); + ext4_bcache_set_dirty(block.buf); + r = jbd_block_set(jbd_fs, &block); + if (r != EOK) { + memset(journal, 0, sizeof(struct jbd_journal)); + return r; + } + + TAILQ_INIT(&journal->trans_queue); + TAILQ_INIT(&journal->cp_queue); + RB_INIT(&journal->block_rec_root); + journal->jbd_fs = jbd_fs; + jbd_journal_write_sb(journal); + return jbd_write_sb(jbd_fs); +} + +static void jbd_trans_end_write(struct ext4_bcache *bc __unused, + struct ext4_buf *buf __unused, + int res, + void *arg); + +static void jbd_journal_flush_trans(struct jbd_trans *trans) +{ + struct jbd_buf *jbd_buf, *tmp; + struct jbd_journal *journal = trans->journal; + struct ext4_fs *fs = journal->jbd_fs->inode_ref.fs; + void *tmp_data = malloc(journal->block_size); + ext4_assert(tmp_data); + + TAILQ_FOREACH_SAFE(jbd_buf, &trans->buf_queue, buf_node, + tmp) { + struct ext4_buf *buf = jbd_buf->block_rec->buf; + /* The buffer in memory is still dirty. */ + if (buf) { + if (jbd_buf->block_rec->trans != trans) { + int r; + struct ext4_block jbd_block = EXT4_BLOCK_ZERO(); + ext4_assert(ext4_block_get(fs->bdev, + &jbd_block, + jbd_buf->jbd_lba) == EOK); + memcpy(tmp_data, jbd_block.data, + journal->block_size); + ext4_block_set(fs->bdev, &jbd_block); + r = ext4_blocks_set_direct(fs->bdev, tmp_data, + buf->lba, 1); + jbd_trans_end_write(fs->bdev->bc, buf, r, jbd_buf); + } else + ext4_block_flush_buf(fs->bdev, buf); + + } + } + + free(tmp_data); +} + +static void +jbd_journal_skip_pure_revoke(struct jbd_journal *journal, + struct jbd_trans *trans) +{ + journal->start = trans->start_iblock + + trans->alloc_blocks; + wrap(&journal->jbd_fs->sb, journal->start); + journal->trans_id = trans->trans_id + 1; + jbd_journal_free_trans(journal, + trans, false); + jbd_journal_write_sb(journal); +} + +static void +jbd_journal_purge_cp_trans(struct jbd_journal *journal, + bool flush) +{ + struct jbd_trans *trans; + while ((trans = TAILQ_FIRST(&journal->cp_queue))) { + if (!trans->data_cnt) { + TAILQ_REMOVE(&journal->cp_queue, + trans, + trans_node); + jbd_journal_skip_pure_revoke(journal, trans); + } else { + if (trans->data_cnt == + trans->written_cnt) { + journal->start = + trans->start_iblock + + trans->alloc_blocks; + wrap(&journal->jbd_fs->sb, + journal->start); + journal->trans_id = + trans->trans_id + 1; + TAILQ_REMOVE(&journal->cp_queue, + trans, + trans_node); + jbd_journal_free_trans(journal, + trans, + false); + jbd_journal_write_sb(journal); + } else if (!flush) { + journal->start = + trans->start_iblock; + wrap(&journal->jbd_fs->sb, + journal->start); + journal->trans_id = + trans->trans_id; + jbd_journal_write_sb(journal); + break; + } else + jbd_journal_flush_trans(trans); + } + } +} + +/**@brief Stop accessing the journal. + * @param journal current journal session + * @return standard error code*/ +int jbd_journal_stop(struct jbd_journal *journal) +{ + int r; + struct jbd_fs *jbd_fs = journal->jbd_fs; + uint32_t features_incompatible; + + /* Make sure that journalled content have reached + * the disk.*/ + jbd_journal_purge_cp_trans(journal, true); + + /* There should be no block record in this journal + * session. */ + if (!RB_EMPTY(&journal->block_rec_root)) + ext4_dbg(DEBUG_JBD, + DBG_WARN "There are still block records " + "in this journal session!\n"); + + features_incompatible = + ext4_get32(&jbd_fs->inode_ref.fs->sb, + features_incompatible); + features_incompatible &= ~EXT4_FINCOM_RECOVER; + ext4_set32(&jbd_fs->inode_ref.fs->sb, + features_incompatible, + features_incompatible); + r = ext4_sb_write(jbd_fs->inode_ref.fs->bdev, + &jbd_fs->inode_ref.fs->sb); + if (r != EOK) + return r; + + journal->start = 0; + journal->trans_id = 0; + jbd_journal_write_sb(journal); + return jbd_write_sb(journal->jbd_fs); +} + +/**@brief Allocate a block in the journal. + * @param journal current journal session + * @param trans transaction + * @return allocated block address*/ +static uint32_t jbd_journal_alloc_block(struct jbd_journal *journal, + struct jbd_trans *trans) +{ + uint32_t start_block; + + start_block = journal->last++; + trans->alloc_blocks++; + wrap(&journal->jbd_fs->sb, journal->last); + + /* If there is no space left, flush all journalled + * blocks to disk first.*/ + if (journal->last == journal->start) + jbd_journal_purge_cp_trans(journal, true); + + return start_block; +} + +/**@brief Allocate a new transaction + * @param journal current journal session + * @return transaction allocated*/ +struct jbd_trans * +jbd_journal_new_trans(struct jbd_journal *journal) +{ + struct jbd_trans *trans = calloc(1, sizeof(struct jbd_trans)); + if (!trans) + return NULL; + + /* We will assign a trans_id to this transaction, + * once it has been committed.*/ + trans->journal = journal; + trans->data_csum = EXT4_CRC32_INIT; + trans->error = EOK; + TAILQ_INIT(&trans->buf_queue); + return trans; +} + +/**@brief gain access to it before making any modications. + * @param journal current journal session + * @param trans transaction + * @param block descriptor + * @return standard error code.*/ +int jbd_trans_get_access(struct jbd_journal *journal, + struct jbd_trans *trans, + struct ext4_block *block) +{ + int r = EOK; + struct ext4_fs *fs = journal->jbd_fs->inode_ref.fs; + struct jbd_buf *jbd_buf = block->buf->end_write_arg; + + /* If the buffer has already been modified, we should + * flush dirty data in this buffer to disk.*/ + if (ext4_bcache_test_flag(block->buf, BC_DIRTY) && + block->buf->end_write == jbd_trans_end_write) { + ext4_assert(jbd_buf); + if (jbd_buf->trans != trans) + r = ext4_block_flush_buf(fs->bdev, block->buf); + + } + return r; +} + +static struct jbd_block_rec * +jbd_trans_block_rec_lookup(struct jbd_journal *journal, + ext4_fsblk_t lba) +{ + struct jbd_block_rec tmp = { + .lba = lba + }; + + return RB_FIND(jbd_block, + &journal->block_rec_root, + &tmp); +} + +static void +jbd_trans_change_ownership(struct jbd_block_rec *block_rec, + struct jbd_trans *new_trans, + struct ext4_buf *new_buf) +{ + LIST_REMOVE(block_rec, tbrec_node); + /* Now this block record belongs to this transaction. */ + LIST_INSERT_HEAD(&new_trans->tbrec_list, block_rec, tbrec_node); + block_rec->trans = new_trans; + block_rec->buf = new_buf; +} + +static inline struct jbd_block_rec * +jbd_trans_insert_block_rec(struct jbd_trans *trans, + ext4_fsblk_t lba, + struct ext4_buf *buf) +{ + struct jbd_block_rec *block_rec; + block_rec = jbd_trans_block_rec_lookup(trans->journal, lba); + if (block_rec) { + jbd_trans_change_ownership(block_rec, trans, buf); + return block_rec; + } + block_rec = calloc(1, sizeof(struct jbd_block_rec)); + if (!block_rec) + return NULL; + + block_rec->lba = lba; + block_rec->buf = buf; + block_rec->trans = trans; + TAILQ_INIT(&block_rec->dirty_buf_queue); + LIST_INSERT_HEAD(&trans->tbrec_list, block_rec, tbrec_node); + RB_INSERT(jbd_block, &trans->journal->block_rec_root, block_rec); + return block_rec; +} + +static void +jbd_trans_finish_callback(struct jbd_journal *journal, + const struct jbd_trans *trans, + struct jbd_block_rec *block_rec, + bool abort) +{ + struct ext4_fs *fs = journal->jbd_fs->inode_ref.fs; + if (block_rec->trans != trans) + return; + + if (!abort) { + struct jbd_buf *jbd_buf, *tmp; + TAILQ_FOREACH_SAFE(jbd_buf, + &block_rec->dirty_buf_queue, + dirty_buf_node, + tmp) { + /* All we need is a fake ext4_buf. */ + struct ext4_buf buf; + + jbd_trans_end_write(fs->bdev->bc, + &buf, + EOK, + jbd_buf); + } + } else { + struct jbd_buf *jbd_buf; + struct ext4_block jbd_block = EXT4_BLOCK_ZERO(), + block = EXT4_BLOCK_ZERO(); + jbd_buf = TAILQ_LAST(&block_rec->dirty_buf_queue, + jbd_buf_dirty); + if (jbd_buf) { + ext4_assert(ext4_block_get(fs->bdev, + &jbd_block, + jbd_buf->jbd_lba) == EOK); + ext4_assert(ext4_block_get_noread(fs->bdev, + &block, + block_rec->lba) == EOK); + memcpy(block.data, jbd_block.data, + journal->block_size); + + jbd_trans_change_ownership(block_rec, + jbd_buf->trans, block.buf); + + block.buf->end_write = jbd_trans_end_write; + block.buf->end_write_arg = jbd_buf; + + ext4_bcache_set_flag(jbd_block.buf, BC_TMP); + ext4_bcache_set_dirty(block.buf); + + ext4_block_set(fs->bdev, &jbd_block); + ext4_block_set(fs->bdev, &block); + return; + } + } +} + +static inline void +jbd_trans_remove_block_rec(struct jbd_journal *journal, + struct jbd_block_rec *block_rec, + struct jbd_trans *trans) +{ + /* If this block record doesn't belong to this transaction, + * give up.*/ + if (block_rec->trans == trans) { + LIST_REMOVE(block_rec, tbrec_node); + RB_REMOVE(jbd_block, + &journal->block_rec_root, + block_rec); + free(block_rec); + } +} + +/**@brief Add block to a transaction and mark it dirty. + * @param trans transaction + * @param block block descriptor + * @return standard error code*/ +int jbd_trans_set_block_dirty(struct jbd_trans *trans, + struct ext4_block *block) +{ + struct jbd_buf *buf; + + struct jbd_block_rec *block_rec; + if (block->buf->end_write == jbd_trans_end_write) { + buf = block->buf->end_write_arg; + if (buf && buf->trans == trans) + return EOK; + } + buf = calloc(1, sizeof(struct jbd_buf)); + if (!buf) + return ENOMEM; + + if ((block_rec = jbd_trans_insert_block_rec(trans, + block->lb_id, + block->buf)) == NULL) { + free(buf); + return ENOMEM; + } + + TAILQ_INSERT_TAIL(&block_rec->dirty_buf_queue, + buf, + dirty_buf_node); + + buf->block_rec = block_rec; + buf->trans = trans; + buf->block = *block; + ext4_bcache_inc_ref(block->buf); + + /* If the content reach the disk, notify us + * so that we may do a checkpoint. */ + block->buf->end_write = jbd_trans_end_write; + block->buf->end_write_arg = buf; + + trans->data_cnt++; + TAILQ_INSERT_HEAD(&trans->buf_queue, buf, buf_node); + + ext4_bcache_set_dirty(block->buf); + return EOK; +} + +/**@brief Add block to be revoked to a transaction + * @param trans transaction + * @param lba logical block address + * @return standard error code*/ +int jbd_trans_revoke_block(struct jbd_trans *trans, + ext4_fsblk_t lba) +{ + struct jbd_revoke_rec *rec = + calloc(1, sizeof(struct jbd_revoke_rec)); + if (!rec) + return ENOMEM; + + rec->lba = lba; + LIST_INSERT_HEAD(&trans->revoke_list, rec, revoke_node); + return EOK; +} + +/**@brief Try to add block to be revoked to a transaction. + * If @lba still remains in an transaction on checkpoint + * queue, add @lba as a revoked block to the transaction. + * @param trans transaction + * @param lba logical block address + * @return standard error code*/ +int jbd_trans_try_revoke_block(struct jbd_trans *trans, + ext4_fsblk_t lba) +{ + int r = EOK; + struct jbd_journal *journal = trans->journal; + struct ext4_fs *fs = journal->jbd_fs->inode_ref.fs; + struct jbd_block_rec *block_rec = + jbd_trans_block_rec_lookup(journal, lba); + + /* Make sure we don't flush any buffers belong to this transaction. */ + if (block_rec && block_rec->trans != trans) { + /* If the buffer has not been flushed yet, flush it now. */ + if (block_rec->buf) { + r = ext4_block_flush_buf(fs->bdev, block_rec->buf); + if (r != EOK) + return r; + + } + + jbd_trans_revoke_block(trans, lba); + } + + return EOK; +} + +/**@brief Free a transaction + * @param journal current journal session + * @param trans transaction + * @param abort discard all the modifications on the block? + * @return standard error code*/ +void jbd_journal_free_trans(struct jbd_journal *journal, + struct jbd_trans *trans, + bool abort) +{ + struct jbd_buf *jbd_buf, *tmp; + struct jbd_revoke_rec *rec, *tmp2; + struct jbd_block_rec *block_rec, *tmp3; + struct ext4_fs *fs = journal->jbd_fs->inode_ref.fs; + TAILQ_FOREACH_SAFE(jbd_buf, &trans->buf_queue, buf_node, + tmp) { + block_rec = jbd_buf->block_rec; + if (abort) { + jbd_buf->block.buf->end_write = NULL; + jbd_buf->block.buf->end_write_arg = NULL; + ext4_bcache_clear_dirty(jbd_buf->block.buf); + ext4_block_set(fs->bdev, &jbd_buf->block); + } + + TAILQ_REMOVE(&jbd_buf->block_rec->dirty_buf_queue, + jbd_buf, + dirty_buf_node); + jbd_trans_finish_callback(journal, + trans, + block_rec, + abort); + TAILQ_REMOVE(&trans->buf_queue, jbd_buf, buf_node); + free(jbd_buf); + } + LIST_FOREACH_SAFE(rec, &trans->revoke_list, revoke_node, + tmp2) { + LIST_REMOVE(rec, revoke_node); + free(rec); + } + LIST_FOREACH_SAFE(block_rec, &trans->tbrec_list, tbrec_node, + tmp3) { + jbd_trans_remove_block_rec(journal, block_rec, trans); + } + + free(trans); +} + +/**@brief Write commit block for a transaction + * @param trans transaction + * @return standard error code*/ +static int jbd_trans_write_commit_block(struct jbd_trans *trans) +{ + int rc; + struct jbd_commit_header *header; + uint32_t commit_iblock = 0; + struct ext4_block commit_block; + struct jbd_journal *journal = trans->journal; + + commit_iblock = jbd_journal_alloc_block(journal, trans); + rc = jbd_block_get_noread(journal->jbd_fs, + &commit_block, commit_iblock); + if (rc != EOK) + return rc; + + header = (struct jbd_commit_header *)commit_block.data; + jbd_set32(&header->header, magic, JBD_MAGIC_NUMBER); + jbd_set32(&header->header, blocktype, JBD_COMMIT_BLOCK); + jbd_set32(&header->header, sequence, trans->trans_id); + + if (JBD_HAS_INCOMPAT_FEATURE(&journal->jbd_fs->sb, + JBD_FEATURE_COMPAT_CHECKSUM)) { + jbd_set32(header, chksum_type, JBD_CRC32_CHKSUM); + jbd_set32(header, chksum_size, JBD_CRC32_CHKSUM_SIZE); + jbd_set32(header, chksum[0], trans->data_csum); + } + jbd_commit_csum_set(journal->jbd_fs, header); + ext4_bcache_set_dirty(commit_block.buf); + rc = jbd_block_set(journal->jbd_fs, &commit_block); + if (rc != EOK) + return rc; + + return EOK; +} + +/**@brief Write descriptor block for a transaction + * @param journal current journal session + * @param trans transaction + * @return standard error code*/ +static int jbd_journal_prepare(struct jbd_journal *journal, + struct jbd_trans *trans) +{ + int rc = EOK, i = 0; + int32_t tag_tbl_size; + uint32_t desc_iblock = 0; + uint32_t data_iblock = 0; + char *tag_start = NULL, *tag_ptr = NULL; + struct jbd_buf *jbd_buf, *tmp; + struct ext4_block desc_block, data_block; + struct ext4_fs *fs = journal->jbd_fs->inode_ref.fs; + uint32_t checksum = EXT4_CRC32_INIT; + + /* Try to remove any non-dirty buffers from the tail of + * buf_queue. */ + TAILQ_FOREACH_REVERSE_SAFE(jbd_buf, &trans->buf_queue, + jbd_trans_buf, buf_node, tmp) { + /* We stop the iteration when we find a dirty buffer. */ + if (ext4_bcache_test_flag(jbd_buf->block.buf, + BC_DIRTY)) + break; + + TAILQ_REMOVE(&jbd_buf->block_rec->dirty_buf_queue, + jbd_buf, + dirty_buf_node); + + jbd_buf->block.buf->end_write = NULL; + jbd_buf->block.buf->end_write_arg = NULL; + jbd_trans_finish_callback(journal, + trans, + jbd_buf->block_rec, + true); + + /* The buffer has not been modified, just release + * that jbd_buf. */ + jbd_trans_remove_block_rec(journal, + jbd_buf->block_rec, trans); + trans->data_cnt--; + + ext4_block_set(fs->bdev, &jbd_buf->block); + TAILQ_REMOVE(&trans->buf_queue, jbd_buf, buf_node); + free(jbd_buf); + } + + TAILQ_FOREACH_SAFE(jbd_buf, &trans->buf_queue, buf_node, tmp) { + struct tag_info tag_info; + bool uuid_exist = false; + if (!ext4_bcache_test_flag(jbd_buf->block.buf, + BC_DIRTY)) { + TAILQ_REMOVE(&jbd_buf->block_rec->dirty_buf_queue, + jbd_buf, + dirty_buf_node); + + jbd_buf->block.buf->end_write = NULL; + jbd_buf->block.buf->end_write_arg = NULL; + jbd_trans_finish_callback(journal, + trans, + jbd_buf->block_rec, + true); + + /* The buffer has not been modified, just release + * that jbd_buf. */ + jbd_trans_remove_block_rec(journal, + jbd_buf->block_rec, trans); + trans->data_cnt--; + + ext4_block_set(fs->bdev, &jbd_buf->block); + TAILQ_REMOVE(&trans->buf_queue, jbd_buf, buf_node); + free(jbd_buf); + continue; + } + checksum = jbd_block_csum(journal->jbd_fs, + jbd_buf->block.data, + checksum, + trans->trans_id); +again: + if (!desc_iblock) { + struct jbd_bhdr *bhdr; + desc_iblock = jbd_journal_alloc_block(journal, trans); + rc = jbd_block_get_noread(journal->jbd_fs, + &desc_block, desc_iblock); + if (rc != EOK) + break; + + ext4_bcache_set_dirty(desc_block.buf); + + bhdr = (struct jbd_bhdr *)desc_block.data; + jbd_set32(bhdr, magic, JBD_MAGIC_NUMBER); + jbd_set32(bhdr, blocktype, JBD_DESCRIPTOR_BLOCK); + jbd_set32(bhdr, sequence, trans->trans_id); + + tag_start = (char *)(bhdr + 1); + tag_ptr = tag_start; + uuid_exist = true; + tag_tbl_size = journal->block_size - + sizeof(struct jbd_bhdr); + + if (jbd_has_csum(&journal->jbd_fs->sb)) + tag_tbl_size -= sizeof(struct jbd_block_tail); + + if (!trans->start_iblock) + trans->start_iblock = desc_iblock; + + } + tag_info.block = jbd_buf->block.lb_id; + tag_info.uuid_exist = uuid_exist; + if (i == trans->data_cnt - 1) + tag_info.last_tag = true; + else + tag_info.last_tag = false; + + tag_info.checksum = checksum; + + if (uuid_exist) + memcpy(tag_info.uuid, journal->jbd_fs->sb.uuid, + UUID_SIZE); + + rc = jbd_write_block_tag(journal->jbd_fs, + tag_ptr, + tag_tbl_size, + &tag_info); + if (rc != EOK) { + jbd_meta_csum_set(journal->jbd_fs, + (struct jbd_bhdr *)desc_block.data); + jbd_block_set(journal->jbd_fs, &desc_block); + desc_iblock = 0; + goto again; + } + + data_iblock = jbd_journal_alloc_block(journal, trans); + rc = jbd_block_get_noread(journal->jbd_fs, + &data_block, data_iblock); + if (rc != EOK) + break; + + ext4_bcache_set_dirty(data_block.buf); + + memcpy(data_block.data, jbd_buf->block.data, + journal->block_size); + jbd_buf->jbd_lba = data_block.lb_id; + + rc = jbd_block_set(journal->jbd_fs, &data_block); + if (rc != EOK) + break; + + tag_ptr += tag_info.tag_bytes; + tag_tbl_size -= tag_info.tag_bytes; + + i++; + } + if (rc == EOK && desc_iblock) { + jbd_meta_csum_set(journal->jbd_fs, + (struct jbd_bhdr *)desc_block.data); + trans->data_csum = checksum; + jbd_block_set(journal->jbd_fs, &desc_block); + } + + return rc; +} + +/**@brief Write revoke block for a transaction + * @param journal current journal session + * @param trans transaction + * @return standard error code*/ +static int +jbd_journal_prepare_revoke(struct jbd_journal *journal, + struct jbd_trans *trans) +{ + int rc = EOK, i = 0; + int32_t tag_tbl_size; + uint32_t desc_iblock = 0; + char *blocks_entry = NULL; + struct jbd_revoke_rec *rec, *tmp; + struct ext4_block desc_block; + struct jbd_revoke_header *header = NULL; + int32_t record_len = 4; + + if (JBD_HAS_INCOMPAT_FEATURE(&journal->jbd_fs->sb, + JBD_FEATURE_INCOMPAT_64BIT)) + record_len = 8; + + LIST_FOREACH_SAFE(rec, &trans->revoke_list, revoke_node, + tmp) { +again: + if (!desc_iblock) { + struct jbd_bhdr *bhdr; + desc_iblock = jbd_journal_alloc_block(journal, trans); + rc = jbd_block_get_noread(journal->jbd_fs, + &desc_block, desc_iblock); + if (rc != EOK) { + break; + } + + ext4_bcache_set_dirty(desc_block.buf); + + bhdr = (struct jbd_bhdr *)desc_block.data; + jbd_set32(bhdr, magic, JBD_MAGIC_NUMBER); + jbd_set32(bhdr, blocktype, JBD_REVOKE_BLOCK); + jbd_set32(bhdr, sequence, trans->trans_id); + + header = (struct jbd_revoke_header *)bhdr; + blocks_entry = (char *)(header + 1); + tag_tbl_size = journal->block_size - + sizeof(struct jbd_revoke_header); + + if (jbd_has_csum(&journal->jbd_fs->sb)) + tag_tbl_size -= sizeof(struct jbd_block_tail); + + if (!trans->start_iblock) + trans->start_iblock = desc_iblock; + + } + + if (tag_tbl_size < record_len) { + jbd_set32(header, count, + journal->block_size - tag_tbl_size); + jbd_meta_csum_set(journal->jbd_fs, + (struct jbd_bhdr *)desc_block.data); + jbd_block_set(journal->jbd_fs, &desc_block); + desc_iblock = 0; + header = NULL; + goto again; + } + if (record_len == 8) { + uint64_t *blocks = + (uint64_t *)blocks_entry; + *blocks = to_be64(rec->lba); + } else { + uint32_t *blocks = + (uint32_t *)blocks_entry; + *blocks = to_be32((uint32_t)rec->lba); + } + blocks_entry += record_len; + tag_tbl_size -= record_len; + + i++; + } + if (rc == EOK && desc_iblock) { + if (header != NULL) + jbd_set32(header, count, + journal->block_size - tag_tbl_size); + + jbd_meta_csum_set(journal->jbd_fs, + (struct jbd_bhdr *)desc_block.data); + jbd_block_set(journal->jbd_fs, &desc_block); + } + + return rc; +} + +/**@brief Put references of block descriptors in a transaction. + * @param journal current journal session + * @param trans transaction*/ +void jbd_journal_cp_trans(struct jbd_journal *journal, struct jbd_trans *trans) +{ + struct jbd_buf *jbd_buf, *tmp; + struct ext4_fs *fs = journal->jbd_fs->inode_ref.fs; + TAILQ_FOREACH_SAFE(jbd_buf, &trans->buf_queue, buf_node, + tmp) { + struct ext4_block block = jbd_buf->block; + ext4_block_set(fs->bdev, &block); + } +} + +/**@brief Update the start block of the journal when + * all the contents in a transaction reach the disk.*/ +static void jbd_trans_end_write(struct ext4_bcache *bc __unused, + struct ext4_buf *buf, + int res, + void *arg) +{ + struct jbd_buf *jbd_buf = arg; + struct jbd_trans *trans = jbd_buf->trans; + struct jbd_block_rec *block_rec = jbd_buf->block_rec; + struct jbd_journal *journal = trans->journal; + bool first_in_queue = + trans == TAILQ_FIRST(&journal->cp_queue); + if (res != EOK) + trans->error = res; + + TAILQ_REMOVE(&trans->buf_queue, jbd_buf, buf_node); + TAILQ_REMOVE(&block_rec->dirty_buf_queue, + jbd_buf, + dirty_buf_node); + + jbd_trans_finish_callback(journal, + trans, + jbd_buf->block_rec, + false); + if (block_rec->trans == trans) { + block_rec->buf = NULL; + /* Clear the end_write and end_write_arg fields. */ + buf->end_write = NULL; + buf->end_write_arg = NULL; + } + + free(jbd_buf); + + trans->written_cnt++; + if (trans->written_cnt == trans->data_cnt) { + /* If it is the first transaction on checkpoint queue, + * we will shift the start of the journal to the next + * transaction, and remove subsequent written + * transactions from checkpoint queue until we find + * an unwritten one. */ + if (first_in_queue) { + journal->start = trans->start_iblock + + trans->alloc_blocks; + wrap(&journal->jbd_fs->sb, journal->start); + journal->trans_id = trans->trans_id + 1; + TAILQ_REMOVE(&journal->cp_queue, trans, trans_node); + jbd_journal_free_trans(journal, trans, false); + + jbd_journal_purge_cp_trans(journal, false); + jbd_journal_write_sb(journal); + jbd_write_sb(journal->jbd_fs); + } + } +} + +/**@brief Commit a transaction to the journal immediately. + * @param journal current journal session + * @param trans transaction + * @return standard error code*/ +int jbd_journal_commit_trans(struct jbd_journal *journal, + struct jbd_trans *trans) +{ + int rc = EOK; + uint32_t last = journal->last; + + trans->trans_id = journal->alloc_trans_id; + rc = jbd_journal_prepare(journal, trans); + if (rc != EOK) + goto Finish; + + rc = jbd_journal_prepare_revoke(journal, trans); + if (rc != EOK) + goto Finish; + + if (TAILQ_EMPTY(&trans->buf_queue) && + LIST_EMPTY(&trans->revoke_list)) { + /* Since there are no entries in both buffer list + * and revoke entry list, we do not consider trans as + * complete transaction and just return EOK.*/ + jbd_journal_free_trans(journal, trans, false); + goto Finish; + } + + rc = jbd_trans_write_commit_block(trans); + if (rc != EOK) + goto Finish; + + journal->alloc_trans_id++; + if (TAILQ_EMPTY(&journal->cp_queue)) { + if (trans->data_cnt) { + journal->start = trans->start_iblock; + wrap(&journal->jbd_fs->sb, journal->start); + journal->trans_id = trans->trans_id; + jbd_journal_write_sb(journal); + jbd_write_sb(journal->jbd_fs); + TAILQ_INSERT_TAIL(&journal->cp_queue, trans, + trans_node); + jbd_journal_cp_trans(journal, trans); + } else { + journal->start = trans->start_iblock + + trans->alloc_blocks; + wrap(&journal->jbd_fs->sb, journal->start); + journal->trans_id = trans->trans_id + 1; + jbd_journal_write_sb(journal); + jbd_journal_free_trans(journal, trans, false); + } + } else { + TAILQ_INSERT_TAIL(&journal->cp_queue, trans, + trans_node); + if (trans->data_cnt) + jbd_journal_cp_trans(journal, trans); + + } +Finish: + if (rc != EOK) { + journal->last = last; + jbd_journal_free_trans(journal, trans, true); + } + return rc; +} + +/** + * @} + */ diff --git a/src/ext4_mbr.c b/src/ext4_mbr.c new file mode 100644 index 0000000..52df852 --- /dev/null +++ b/src/ext4_mbr.c @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_mbr.c + * @brief Master boot record parser + */ + +#include "ext4_config.h" +#include "ext4_types.h" +#include "ext4_debug.h" +#include "ext4_mbr.h" + +#include <inttypes.h> +#include <string.h> + +#define MBR_SIGNATURE 0xAA55 + +#pragma pack(push, 1) + +struct ext4_part_entry { + uint8_t status; + uint8_t chs1[3]; + uint8_t type; + uint8_t chs2[3]; + uint32_t first_lba; + uint32_t sectors; +}; + +struct ext4_mbr { + uint8_t bootstrap[446]; + struct ext4_part_entry part_entry[4]; + uint16_t signature; +}; + +#pragma pack(pop) + +int ext4_mbr_scan(struct ext4_blockdev *parent, struct ext4_mbr_bdevs *bdevs) +{ + int r; + size_t i; + + ext4_dbg(DEBUG_MBR, DBG_INFO "ext4_mbr_scan\n"); + memset(bdevs, 0, sizeof(struct ext4_mbr_bdevs)); + r = ext4_block_init(parent); + if (r != EOK) + return r; + + r = ext4_block_readbytes(parent, 0, parent->bdif->ph_bbuf, 512); + if (r != EOK) { + goto blockdev_fini; + } + + const struct ext4_mbr *mbr = (void *)parent->bdif->ph_bbuf; + + if (to_le16(mbr->signature) != MBR_SIGNATURE) { + ext4_dbg(DEBUG_MBR, DBG_ERROR "ext4_mbr_scan: unknown " + "signature: 0x%x\n", to_le16(mbr->signature)); + r = ENOENT; + goto blockdev_fini; + } + + /*Show bootstrap code*/ + ext4_dbg(DEBUG_MBR, "mbr_part: bootstrap:"); + for (i = 0; i < sizeof(mbr->bootstrap); ++i) { + if (!(i & 0xF)) + ext4_dbg(DEBUG_MBR | DEBUG_NOPREFIX, "\n"); + ext4_dbg(DEBUG_MBR | DEBUG_NOPREFIX, "%02x, ", mbr->bootstrap[i]); + } + + ext4_dbg(DEBUG_MBR | DEBUG_NOPREFIX, "\n\n"); + for (i = 0; i < 4; ++i) { + const struct ext4_part_entry *pe = &mbr->part_entry[i]; + ext4_dbg(DEBUG_MBR, "mbr_part: %d\n", (int)i); + ext4_dbg(DEBUG_MBR, "\tstatus: 0x%x\n", pe->status); + ext4_dbg(DEBUG_MBR, "\ttype 0x%x:\n", pe->type); + ext4_dbg(DEBUG_MBR, "\tfirst_lba: 0x%"PRIx32"\n", pe->first_lba); + ext4_dbg(DEBUG_MBR, "\tsectors: 0x%"PRIx32"\n", pe->sectors); + + if (!pe->sectors) + continue; /*Empty entry*/ + + if (pe->type != 0x83) + continue; /*Unsupported entry. 0x83 - linux native*/ + + bdevs->partitions[i].bdif = parent->bdif; + bdevs->partitions[i].part_offset = + (uint64_t)pe->first_lba * parent->bdif->ph_bsize; + bdevs->partitions[i].part_size = + (uint64_t)pe->sectors * parent->bdif->ph_bsize; + } + + blockdev_fini: + ext4_block_fini(parent); + return r; +} + +/** + * @} + */ + diff --git a/src/ext4_mkfs.c b/src/ext4_mkfs.c new file mode 100644 index 0000000..5712ef3 --- /dev/null +++ b/src/ext4_mkfs.c @@ -0,0 +1,774 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_mkfs.c + * @brief + */ + +#include "ext4_config.h" +#include "ext4_super.h" +#include "ext4_block_group.h" +#include "ext4_dir.h" +#include "ext4_dir_idx.h" +#include "ext4_fs.h" +#include "ext4_inode.h" +#include "ext4_debug.h" +#include "ext4_ialloc.h" +#include "ext4_mkfs.h" + +#include <inttypes.h> +#include <string.h> +#include <stdlib.h> + +#define DIV_ROUND_UP(x, y) (((x) + (y) - 1)/(y)) +#define EXT4_ALIGN(x, y) ((y) * DIV_ROUND_UP((x), (y))) + +struct fs_aux_info { + struct ext4_sblock *sb; + struct ext4_bgroup *bg_desc; + struct xattr_list_element *xattrs; + uint32_t first_data_block; + uint64_t len_blocks; + uint32_t inode_table_blocks; + uint32_t groups; + uint32_t bg_desc_blocks; + uint32_t default_i_flags; + uint32_t blocks_per_ind; + uint32_t blocks_per_dind; + uint32_t blocks_per_tind; +}; + +static inline int log_2(int j) +{ + int i; + + for (i = 0; j > 0; i++) + j >>= 1; + + return i - 1; +} + +static int sb2info(struct ext4_sblock *sb, struct ext4_mkfs_info *info) +{ + if (to_le16(sb->magic) != EXT4_SUPERBLOCK_MAGIC) + return EINVAL; + + info->block_size = 1024 << to_le32(sb->log_block_size); + info->blocks_per_group = to_le32(sb->blocks_per_group); + info->inodes_per_group = to_le32(sb->inodes_per_group); + info->inode_size = to_le16(sb->inode_size); + info->inodes = to_le32(sb->inodes_count); + info->feat_ro_compat = to_le32(sb->features_read_only); + info->feat_compat = to_le32(sb->features_compatible); + info->feat_incompat = to_le32(sb->features_incompatible); + info->bg_desc_reserve_blocks = to_le16(sb->s_reserved_gdt_blocks); + info->label = sb->volume_name; + info->len = (uint64_t)info->block_size * ext4_sb_get_blocks_cnt(sb); + info->dsc_size = to_le16(sb->desc_size); + + return EOK; +} + +static uint32_t compute_blocks_per_group(struct ext4_mkfs_info *info) +{ + return info->block_size * 8; +} + +static uint32_t compute_inodes(struct ext4_mkfs_info *info) +{ + return (uint32_t)DIV_ROUND_UP(info->len, info->block_size) / 4; +} + +static uint32_t compute_inodes_per_group(struct ext4_mkfs_info *info) +{ + uint32_t blocks = (uint32_t)DIV_ROUND_UP(info->len, info->block_size); + uint32_t block_groups = DIV_ROUND_UP(blocks, info->blocks_per_group); + uint32_t inodes = DIV_ROUND_UP(info->inodes, block_groups); + inodes = EXT4_ALIGN(inodes, (info->block_size / info->inode_size)); + + /* After properly rounding up the number of inodes/group, + * make sure to update the total inodes field in the info struct. + */ + info->inodes = inodes * block_groups; + + return inodes; +} + + +static uint32_t compute_journal_blocks(struct ext4_mkfs_info *info) +{ + uint32_t journal_blocks = (uint32_t)DIV_ROUND_UP(info->len, + info->block_size) / 64; + if (journal_blocks < 1024) + journal_blocks = 1024; + if (journal_blocks > 32768) + journal_blocks = 32768; + return journal_blocks; +} + +static bool has_superblock(struct ext4_mkfs_info *info, uint32_t bgid) +{ + if (!(info->feat_ro_compat & EXT4_FRO_COM_SPARSE_SUPER)) + return true; + + return ext4_sb_sparse(bgid); +} + +static int create_fs_aux_info(struct fs_aux_info *aux_info, + struct ext4_mkfs_info *info) +{ + aux_info->first_data_block = (info->block_size > 1024) ? 0 : 1; + aux_info->len_blocks = info->len / info->block_size; + aux_info->inode_table_blocks = DIV_ROUND_UP(info->inodes_per_group * + info->inode_size, info->block_size); + aux_info->groups = (uint32_t)DIV_ROUND_UP(aux_info->len_blocks - + aux_info->first_data_block, info->blocks_per_group); + aux_info->blocks_per_ind = info->block_size / sizeof(uint32_t); + aux_info->blocks_per_dind = + aux_info->blocks_per_ind * aux_info->blocks_per_ind; + aux_info->blocks_per_tind = + aux_info->blocks_per_dind * aux_info->blocks_per_dind; + + aux_info->bg_desc_blocks = + DIV_ROUND_UP(aux_info->groups * info->dsc_size, + info->block_size); + + aux_info->default_i_flags = EXT4_INODE_FLAG_NOATIME; + + uint32_t last_group_size = aux_info->len_blocks % info->blocks_per_group; + uint32_t last_header_size = 2 + aux_info->inode_table_blocks; + if (has_superblock(info, aux_info->groups - 1)) + last_header_size += 1 + aux_info->bg_desc_blocks + + info->bg_desc_reserve_blocks; + + if (last_group_size > 0 && last_group_size < last_header_size) { + aux_info->groups--; + aux_info->len_blocks -= last_group_size; + } + + aux_info->sb = calloc(1, EXT4_SUPERBLOCK_SIZE); + if (!aux_info->sb) + return ENOMEM; + + aux_info->bg_desc = calloc(aux_info->groups, sizeof(struct ext4_bgroup)); + if (!aux_info->bg_desc) + return ENOMEM; + + aux_info->xattrs = NULL; + + + ext4_dbg(DEBUG_MKFS, DBG_INFO "create_fs_aux_info\n"); + ext4_dbg(DEBUG_MKFS, DBG_NONE "first_data_block: %"PRIu32"\n", + aux_info->first_data_block); + ext4_dbg(DEBUG_MKFS, DBG_NONE "len_blocks: %"PRIu64"\n", + aux_info->len_blocks); + ext4_dbg(DEBUG_MKFS, DBG_NONE "inode_table_blocks: %"PRIu32"\n", + aux_info->inode_table_blocks); + ext4_dbg(DEBUG_MKFS, DBG_NONE "groups: %"PRIu32"\n", + aux_info->groups); + ext4_dbg(DEBUG_MKFS, DBG_NONE "bg_desc_blocks: %"PRIu32"\n", + aux_info->bg_desc_blocks); + ext4_dbg(DEBUG_MKFS, DBG_NONE "default_i_flags: %"PRIu32"\n", + aux_info->default_i_flags); + ext4_dbg(DEBUG_MKFS, DBG_NONE "blocks_per_ind: %"PRIu32"\n", + aux_info->blocks_per_ind); + ext4_dbg(DEBUG_MKFS, DBG_NONE "blocks_per_dind: %"PRIu32"\n", + aux_info->blocks_per_dind); + ext4_dbg(DEBUG_MKFS, DBG_NONE "blocks_per_tind: %"PRIu32"\n", + aux_info->blocks_per_tind); + + return EOK; +} + +static void release_fs_aux_info(struct fs_aux_info *aux_info) +{ + if (aux_info->sb) + free(aux_info->sb); + if (aux_info->bg_desc) + free(aux_info->bg_desc); +} + + +/* Fill in the superblock memory buffer based on the filesystem parameters */ +static void fill_in_sb(struct fs_aux_info *aux_info, struct ext4_mkfs_info *info) +{ + struct ext4_sblock *sb = aux_info->sb; + + sb->inodes_count = to_le32(info->inodes_per_group * aux_info->groups); + + ext4_sb_set_blocks_cnt(sb, aux_info->len_blocks); + ext4_sb_set_free_blocks_cnt(sb, aux_info->len_blocks); + sb->free_inodes_count = to_le32(info->inodes_per_group * aux_info->groups); + + sb->reserved_blocks_count_lo = to_le32(0); + sb->first_data_block = to_le32(aux_info->first_data_block); + sb->log_block_size = to_le32(log_2(info->block_size / 1024)); + sb->log_cluster_size = to_le32(log_2(info->block_size / 1024)); + sb->blocks_per_group = to_le32(info->blocks_per_group); + sb->frags_per_group = to_le32(info->blocks_per_group); + sb->inodes_per_group = to_le32(info->inodes_per_group); + sb->mount_time = to_le32(0); + sb->write_time = to_le32(0); + sb->mount_count = to_le16(0); + sb->max_mount_count = to_le16(0xFFFF); + sb->magic = to_le16(EXT4_SUPERBLOCK_MAGIC); + sb->state = to_le16(EXT4_SUPERBLOCK_STATE_VALID_FS); + sb->errors = to_le16(EXT4_SUPERBLOCK_ERRORS_RO); + sb->minor_rev_level = to_le16(0); + sb->last_check_time = to_le32(0); + sb->check_interval = to_le32(0); + sb->creator_os = to_le32(EXT4_SUPERBLOCK_OS_LINUX); + sb->rev_level = to_le32(1); + sb->def_resuid = to_le16(0); + sb->def_resgid = to_le16(0); + + sb->first_inode = to_le32(EXT4_GOOD_OLD_FIRST_INO); + sb->inode_size = to_le16(info->inode_size); + sb->block_group_index = to_le16(0); + + sb->features_compatible = to_le32(info->feat_compat); + sb->features_incompatible = to_le32(info->feat_incompat); + sb->features_read_only = to_le32(info->feat_ro_compat); + + memset(sb->uuid, 0, sizeof(sb->uuid)); + + memset(sb->volume_name, 0, sizeof(sb->volume_name)); + strncpy(sb->volume_name, info->label, sizeof(sb->volume_name)); + memset(sb->last_mounted, 0, sizeof(sb->last_mounted)); + + sb->algorithm_usage_bitmap = to_le32(0); + sb->s_prealloc_blocks = 0; + sb->s_prealloc_dir_blocks = 0; + sb->s_reserved_gdt_blocks = to_le16(info->bg_desc_reserve_blocks); + + if (info->feat_compat & EXT4_FCOM_HAS_JOURNAL) + sb->journal_inode_number = to_le32(EXT4_JOURNAL_INO); + sb->journal_dev = to_le32(0); + sb->last_orphan = to_le32(0); + sb->hash_seed[0] = to_le32(0x11111111); + sb->hash_seed[1] = to_le32(0x22222222); + sb->hash_seed[2] = to_le32(0x33333333); + sb->hash_seed[3] = to_le32(0x44444444); + sb->default_hash_version = EXT2_HTREE_HALF_MD4; + sb->checksum_type = 1; + sb->desc_size = to_le16(info->dsc_size); + sb->default_mount_opts = to_le32(0); + sb->first_meta_bg = to_le32(0); + sb->mkfs_time = to_le32(0); + + sb->reserved_blocks_count_hi = to_le32(0); + sb->min_extra_isize = to_le32(sizeof(struct ext4_inode) - + EXT4_GOOD_OLD_INODE_SIZE); + sb->want_extra_isize = to_le32(sizeof(struct ext4_inode) - + EXT4_GOOD_OLD_INODE_SIZE); + sb->flags = to_le32(EXT4_SUPERBLOCK_FLAGS_SIGNED_HASH); +} + +static void fill_bgroups(struct fs_aux_info *aux_info, + struct ext4_mkfs_info *info) +{ + uint32_t i; + + uint32_t bg_free_blk = 0; + uint64_t sb_free_blk = 0; + + for (i = 0; i < aux_info->groups; i++) { + + uint64_t bg_start_block = aux_info->first_data_block + + aux_info->first_data_block + i * info->blocks_per_group; + uint32_t blk_off = 0; + + bg_free_blk = info->blocks_per_group - + aux_info->inode_table_blocks; + + bg_free_blk -= 2; + blk_off += aux_info->bg_desc_blocks; + + if (i == (aux_info->groups - 1)) + bg_free_blk -= aux_info->first_data_block; + + if (has_superblock(info, i)) { + bg_start_block++; + blk_off += info->bg_desc_reserve_blocks; + bg_free_blk -= info->bg_desc_reserve_blocks + 1; + bg_free_blk -= aux_info->bg_desc_blocks; + } + + ext4_bg_set_block_bitmap(&aux_info->bg_desc[i], aux_info->sb, + bg_start_block + blk_off + 1); + + ext4_bg_set_inode_bitmap(&aux_info->bg_desc[i], aux_info->sb, + bg_start_block + blk_off + 2); + + ext4_bg_set_inode_table_first_block(&aux_info->bg_desc[i], + aux_info->sb, + bg_start_block + blk_off + 3); + + ext4_bg_set_free_blocks_count(&aux_info->bg_desc[i], + aux_info->sb, bg_free_blk); + + ext4_bg_set_free_inodes_count(&aux_info->bg_desc[i], + aux_info->sb, aux_info->sb->inodes_per_group); + + ext4_bg_set_used_dirs_count(&aux_info->bg_desc[i], aux_info->sb, + 0); + + ext4_bg_set_flag(&aux_info->bg_desc[i], + EXT4_BLOCK_GROUP_BLOCK_UNINIT | + EXT4_BLOCK_GROUP_INODE_UNINIT); + + sb_free_blk += bg_free_blk; + } + + ext4_sb_set_free_blocks_cnt(aux_info->sb, sb_free_blk); +} + + +static int write_bgroups(struct ext4_blockdev *bd, struct fs_aux_info *aux_info, + struct ext4_mkfs_info *info) +{ + int r; + uint32_t i; + struct ext4_block b; + for (i = 0; i < aux_info->groups; i++) { + uint64_t bg_start_block = aux_info->first_data_block + + + i * info->blocks_per_group; + uint32_t blk_off = 0; + + blk_off += aux_info->bg_desc_blocks; + if (has_superblock(info, i)) { + bg_start_block++; + blk_off += info->bg_desc_reserve_blocks; + } + + uint32_t block_size = ext4_sb_get_block_size(aux_info->sb); + uint32_t dsc_pos = 0; + uint32_t dsc_id = 0; + uint32_t dsc_size = ext4_sb_get_desc_size(aux_info->sb); + uint32_t dsc_blk_cnt = aux_info->bg_desc_blocks; + uint64_t dsc_blk = bg_start_block; + + while (dsc_blk_cnt--) { + r = ext4_block_get(bd, &b, dsc_blk++); + if (r != EOK) + return r; + + dsc_pos = 0; + while (dsc_pos + dsc_size <= block_size) { + memcpy(b.data + dsc_pos, + &aux_info->bg_desc[dsc_id], + dsc_size); + + dsc_pos += dsc_size; + dsc_id++; + + if (dsc_id == aux_info->groups) + break; + } + + ext4_bcache_set_dirty(b.buf); + r = ext4_block_set(bd, &b); + if (r != EOK) + return r; + + if (dsc_id == aux_info->groups) + break; + } + + r = ext4_block_get_noread(bd, &b, bg_start_block + blk_off + 1); + if (r != EOK) + return r; + memset(b.data, 0, block_size); + ext4_bcache_set_dirty(b.buf); + r = ext4_block_set(bd, &b); + if (r != EOK) + return r; + r = ext4_block_get_noread(bd, &b, bg_start_block + blk_off + 2); + if (r != EOK) + return r; + memset(b.data, 0, block_size); + ext4_bcache_set_dirty(b.buf); + r = ext4_block_set(bd, &b); + if (r != EOK) + return r; + } + + + return r; +} + +static int write_sblocks(struct ext4_blockdev *bd, struct fs_aux_info *aux_info, + struct ext4_mkfs_info *info) +{ + uint64_t offset; + uint32_t i; + int r; + + /* write out the backup superblocks */ + for (i = 1; i < aux_info->groups; i++) { + if (has_superblock(info, i)) { + offset = info->block_size * (aux_info->first_data_block + + i * info->blocks_per_group); + + aux_info->sb->block_group_index = to_le16(i); + r = ext4_block_writebytes(bd, offset, aux_info->sb, + EXT4_SUPERBLOCK_SIZE); + if (r != EOK) + return r; + } + } + + /* write out the primary superblock */ + aux_info->sb->block_group_index = to_le16(0); + return ext4_block_writebytes(bd, 1024, aux_info->sb, + EXT4_SUPERBLOCK_SIZE); +} + + +int ext4_mkfs_read_info(struct ext4_blockdev *bd, struct ext4_mkfs_info *info) +{ + int r; + struct ext4_sblock *sb = NULL; + r = ext4_block_init(bd); + if (r != EOK) + return r; + + sb = malloc(EXT4_SUPERBLOCK_SIZE); + if (!sb) + goto Finish; + + + r = ext4_sb_read(bd, sb); + if (r != EOK) + goto Finish; + + r = sb2info(sb, info); + +Finish: + if (sb) + free(sb); + ext4_block_fini(bd); + return r; +} + +static int mkfs_initial(struct ext4_blockdev *bd, struct ext4_mkfs_info *info) +{ + int r; + struct fs_aux_info aux_info; + memset(&aux_info, 0, sizeof(struct fs_aux_info)); + + r = create_fs_aux_info(&aux_info, info); + if (r != EOK) + goto Finish; + + fill_in_sb(&aux_info, info); + fill_bgroups(&aux_info, info); + + + r = write_bgroups(bd, &aux_info, info); + if (r != EOK) + goto Finish; + + r = write_sblocks(bd, &aux_info, info); + if (r != EOK) + goto Finish; + + Finish: + release_fs_aux_info(&aux_info); + return r; +} + +static int init_bgs(struct ext4_fs *fs) +{ + int r = EOK; + struct ext4_block_group_ref ref; + uint32_t i; + uint32_t bg_count = ext4_block_group_cnt(&fs->sb); + for (i = 0; i < bg_count; ++i) { + r = ext4_fs_get_block_group_ref(fs, i, &ref); + if (r != EOK) + break; + + r = ext4_fs_put_block_group_ref(&ref); + if (r != EOK) + break; + } + return r; +} + +static int alloc_inodes(struct ext4_fs *fs) +{ + int r = EOK; + int i; + struct ext4_inode_ref inode_ref; + for (i = 1; i < 12; ++i) { + int filetype = EXT4_DE_REG_FILE; + + switch (i) { + case EXT4_ROOT_INO: + case EXT4_GOOD_OLD_FIRST_INO: + filetype = EXT4_DE_DIR; + break; + } + + r = ext4_fs_alloc_inode(fs, &inode_ref, filetype); + if (r != EOK) + return r; + + ext4_inode_set_mode(&fs->sb, inode_ref.inode, 0); + ext4_fs_put_inode_ref(&inode_ref); + } + + return r; +} + +static int create_dirs(struct ext4_fs *fs) +{ + int r = EOK; + struct ext4_inode_ref root; + struct ext4_inode_ref child; + + r = ext4_fs_get_inode_ref(fs, EXT4_ROOT_INO, &root); + if (r != EOK) + return r; + + r = ext4_fs_get_inode_ref(fs, EXT4_GOOD_OLD_FIRST_INO, &child); + if (r != EOK) + return r; + + ext4_inode_set_mode(&fs->sb, child.inode, + EXT4_INODE_MODE_DIRECTORY | 0777); + + ext4_inode_set_mode(&fs->sb, root.inode, + EXT4_INODE_MODE_DIRECTORY | 0777); + +#if CONFIG_DIR_INDEX_ENABLE + /* Initialize directory index if supported */ + if (ext4_sb_feature_com(&fs->sb, EXT4_FCOM_DIR_INDEX)) { + r = ext4_dir_dx_init(&root, &root); + if (r != EOK) + return r; + + r = ext4_dir_dx_init(&child, &root); + if (r != EOK) + return r; + + ext4_inode_set_flag(root.inode, EXT4_INODE_FLAG_INDEX); + ext4_inode_set_flag(child.inode, EXT4_INODE_FLAG_INDEX); + } else +#endif + { + r = ext4_dir_add_entry(&root, ".", strlen("."), &root); + if (r != EOK) + return r; + + r = ext4_dir_add_entry(&root, "..", strlen(".."), &root); + if (r != EOK) + return r; + + r = ext4_dir_add_entry(&child, ".", strlen("."), &child); + if (r != EOK) + return r; + + r = ext4_dir_add_entry(&child, "..", strlen(".."), &root); + if (r != EOK) + return r; + } + + r = ext4_dir_add_entry(&root, "lost+found", strlen("lost+found"), &child); + if (r != EOK) + return r; + + ext4_inode_set_links_cnt(root.inode, 3); + ext4_inode_set_links_cnt(child.inode, 2); + + child.dirty = true; + root.dirty = true; + ext4_fs_put_inode_ref(&child); + ext4_fs_put_inode_ref(&root); + return r; +} + +int ext4_mkfs(struct ext4_fs *fs, struct ext4_blockdev *bd, + struct ext4_mkfs_info *info, int fs_type) +{ + int r; + + r = ext4_block_init(bd); + if (r != EOK) + return r; + + bd->fs = fs; + + if (info->len == 0) + info->len = bd->bdif->ph_bcnt * bd->bdif->ph_bsize; + + if (info->block_size == 0) + info->block_size = 4096; /*Set block size to default value*/ + + /* Round down the filesystem length to be a multiple of the block size */ + info->len &= ~((uint64_t)info->block_size - 1); + + if (info->journal_blocks == 0) + info->journal_blocks = compute_journal_blocks(info); + + if (info->blocks_per_group == 0) + info->blocks_per_group = compute_blocks_per_group(info); + + if (info->inodes == 0) + info->inodes = compute_inodes(info); + + if (info->inode_size == 0) + info->inode_size = 256; + + if (info->label == NULL) + info->label = ""; + + info->inodes_per_group = compute_inodes_per_group(info); + + switch (fs_type) { + case F_SET_EXT2: + info->feat_compat = EXT2_SUPPORTED_FCOM; + info->feat_ro_compat = EXT2_SUPPORTED_FRO_COM; + info->feat_incompat = EXT2_SUPPORTED_FINCOM; + break; + case F_SET_EXT3: + info->feat_compat = EXT3_SUPPORTED_FCOM; + info->feat_ro_compat = EXT3_SUPPORTED_FRO_COM; + info->feat_incompat = EXT3_SUPPORTED_FINCOM; + break; + case F_SET_EXT4: + info->feat_compat = EXT4_SUPPORTED_FCOM; + info->feat_ro_compat = EXT4_SUPPORTED_FRO_COM; + info->feat_incompat = EXT4_SUPPORTED_FINCOM; + break; + } + + /*TODO: handle this features*/ + info->feat_incompat &= ~EXT4_FINCOM_META_BG; + info->feat_incompat &= ~EXT4_FINCOM_FLEX_BG; + info->feat_ro_compat &= ~EXT4_FRO_COM_METADATA_CSUM; + + /*TODO: handle journal feature & inode*/ + if (info->journal == 0) + info->feat_compat |= 0; + + if (info->dsc_size == 0) { + + if (info->feat_incompat & EXT4_FINCOM_64BIT) + info->dsc_size = EXT4_MAX_BLOCK_GROUP_DESCRIPTOR_SIZE; + else + info->dsc_size = EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE; + } + + info->bg_desc_reserve_blocks = 0; + + ext4_dbg(DEBUG_MKFS, DBG_INFO "Creating filesystem with parameters:\n"); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Size: %"PRIu64"\n", info->len); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Block size: %"PRIu32"\n", + info->block_size); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Blocks per group: %"PRIu32"\n", + info->blocks_per_group); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Inodes per group: %"PRIu32"\n", + info->inodes_per_group); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Inode size: %"PRIu32"\n", + info->inode_size); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Inodes: %"PRIu32"\n", info->inodes); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Journal blocks: %"PRIu32"\n", + info->journal_blocks); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Features ro_compat: 0x%x\n", + info->feat_ro_compat); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Features compat: 0x%x\n", + info->feat_compat); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Features incompat: 0x%x\n", + info->feat_incompat); + ext4_dbg(DEBUG_MKFS, DBG_NONE "BG desc reserve: %"PRIu32"\n", + info->bg_desc_reserve_blocks); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Descriptor size: %"PRIu16"\n", + info->dsc_size); + ext4_dbg(DEBUG_MKFS, DBG_NONE "journal: %s\n", + info->journal ? "yes" : "no"); + ext4_dbg(DEBUG_MKFS, DBG_NONE "Label: %s\n", info->label); + + struct ext4_bcache bc; + memset(&bc, 0, sizeof(struct ext4_bcache)); + ext4_block_set_lb_size(bd, info->block_size); + r = ext4_bcache_init_dynamic(&bc, CONFIG_BLOCK_DEV_CACHE_SIZE, + info->block_size); + if (r != EOK) + goto block_fini; + + /*Bind block cache to block device*/ + r = ext4_block_bind_bcache(bd, &bc); + if (r != EOK) + goto cache_fini; + + r = ext4_block_cache_write_back(bd, 1); + if (r != EOK) + goto cache_fini; + + r = mkfs_initial(bd, info); + if (r != EOK) + goto cache_fini; + + r = ext4_fs_init(fs, bd); + if (r != EOK) + goto cache_fini; + + r = init_bgs(fs); + if (r != EOK) + goto fs_fini; + + r = alloc_inodes(fs); + if (r != EOK) + goto fs_fini; + + r = create_dirs(fs); + if (r != EOK) + goto fs_fini; + + fs_fini: + ext4_fs_fini(fs); + + cache_fini: + ext4_block_cache_write_back(bd, 0); + ext4_bcache_fini_dynamic(&bc); + + block_fini: + ext4_block_fini(bd); + + return r; +} + +/** + * @} + */ diff --git a/src/ext4_super.c b/src/ext4_super.c new file mode 100644 index 0000000..582d1fb --- /dev/null +++ b/src/ext4_super.c @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * + * + * HelenOS: + * Copyright (c) 2012 Martin Sucha + * Copyright (c) 2012 Frantisek Princ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_super.h + * @brief Superblock operations. + */ + +#include "ext4_config.h" +#include "ext4_super.h" +#include "ext4_crc32.h" + +uint32_t ext4_block_group_cnt(struct ext4_sblock *s) +{ + uint64_t blocks_count = ext4_sb_get_blocks_cnt(s); + uint32_t blocks_per_group = ext4_get32(s, blocks_per_group); + + uint32_t block_groups_count = (uint32_t)(blocks_count / blocks_per_group); + + if (blocks_count % blocks_per_group) + block_groups_count++; + + return block_groups_count; +} + +uint32_t ext4_blocks_in_group_cnt(struct ext4_sblock *s, uint32_t bgid) +{ + uint32_t block_group_count = ext4_block_group_cnt(s); + uint32_t blocks_per_group = ext4_get32(s, blocks_per_group); + uint64_t total_blocks = ext4_sb_get_blocks_cnt(s); + + if (bgid < block_group_count - 1) + return blocks_per_group; + + return (uint32_t)(total_blocks - ((block_group_count - 1) * blocks_per_group)); +} + +uint32_t ext4_inodes_in_group_cnt(struct ext4_sblock *s, uint32_t bgid) +{ + uint32_t block_group_count = ext4_block_group_cnt(s); + uint32_t inodes_per_group = ext4_get32(s, inodes_per_group); + uint32_t total_inodes = ext4_get32(s, inodes_count); + + if (bgid < block_group_count - 1) + return inodes_per_group; + + return (total_inodes - ((block_group_count - 1) * inodes_per_group)); +} + +#if CONFIG_META_CSUM_ENABLE +static uint32_t ext4_sb_csum(struct ext4_sblock *s) +{ + + return ext4_crc32c(EXT4_CRC32_INIT, s, + offsetof(struct ext4_sblock, checksum)); +} +#else +#define ext4_sb_csum(...) 0 +#endif + +static bool ext4_sb_verify_csum(struct ext4_sblock *s) +{ + if (!ext4_sb_feature_ro_com(s, EXT4_FRO_COM_METADATA_CSUM)) + return true; + + if (s->checksum_type != to_le32(EXT4_CHECKSUM_CRC32C)) + return false; + + return s->checksum == to_le32(ext4_sb_csum(s)); +} + +static void ext4_sb_set_csum(struct ext4_sblock *s) +{ + if (!ext4_sb_feature_ro_com(s, EXT4_FRO_COM_METADATA_CSUM)) + return; + + s->checksum = to_le32(ext4_sb_csum(s)); +} + +int ext4_sb_write(struct ext4_blockdev *bdev, struct ext4_sblock *s) +{ + ext4_sb_set_csum(s); + return ext4_block_writebytes(bdev, EXT4_SUPERBLOCK_OFFSET, s, + EXT4_SUPERBLOCK_SIZE); +} + +int ext4_sb_read(struct ext4_blockdev *bdev, struct ext4_sblock *s) +{ + return ext4_block_readbytes(bdev, EXT4_SUPERBLOCK_OFFSET, s, + EXT4_SUPERBLOCK_SIZE); +} + +bool ext4_sb_check(struct ext4_sblock *s) +{ + if (ext4_get16(s, magic) != EXT4_SUPERBLOCK_MAGIC) + return false; + + if (ext4_get32(s, inodes_count) == 0) + return false; + + if (ext4_sb_get_blocks_cnt(s) == 0) + return false; + + if (ext4_get32(s, blocks_per_group) == 0) + return false; + + if (ext4_get32(s, inodes_per_group) == 0) + return false; + + if (ext4_get16(s, inode_size) < 128) + return false; + + if (ext4_get32(s, first_inode) < 11) + return false; + + if (ext4_sb_get_desc_size(s) < EXT4_MIN_BLOCK_GROUP_DESCRIPTOR_SIZE) + return false; + + if (ext4_sb_get_desc_size(s) > EXT4_MAX_BLOCK_GROUP_DESCRIPTOR_SIZE) + return false; + + if (!ext4_sb_verify_csum(s)) + return false; + + return true; +} + +static inline int is_power_of(uint32_t a, uint32_t b) +{ + while (1) { + if (a < b) + return 0; + if (a == b) + return 1; + if ((a % b) != 0) + return 0; + a = a / b; + } +} + +bool ext4_sb_sparse(uint32_t group) +{ + if (group <= 1) + return 1; + + if (!(group & 1)) + return 0; + + return (is_power_of(group, 7) || is_power_of(group, 5) || + is_power_of(group, 3)); +} + +bool ext4_sb_is_super_in_bg(struct ext4_sblock *s, uint32_t group) +{ + if (ext4_sb_feature_ro_com(s, EXT4_FRO_COM_SPARSE_SUPER) && + !ext4_sb_sparse(group)) + return false; + return true; +} + +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; +} + +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); +} + +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; +} + +/** + * @} + */ diff --git a/src/ext4_trans.c b/src/ext4_trans.c new file mode 100644 index 0000000..9f32fb8 --- /dev/null +++ b/src/ext4_trans.c @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * Copyright (c) 2015 Kaho Ng (ngkaho1234@gmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_trans.c + * @brief Ext4 transaction buffer operations. + */ + +#include "ext4_config.h" +#include "ext4_types.h" +#include "ext4_journal.h" + +static int ext4_trans_get_write_access(struct ext4_fs *fs __unused, + struct ext4_block *block __unused) +{ + int r = EOK; +#if CONFIG_JOURNALING_ENABLE + if (fs->jbd_journal && fs->curr_trans) { + struct jbd_journal *journal = fs->jbd_journal; + struct jbd_trans *trans = fs->curr_trans; + r = jbd_trans_get_access(journal, trans, block); + } +#endif + return r; +} + +int ext4_trans_set_block_dirty(struct ext4_buf *buf) +{ + int r = EOK; +#if CONFIG_JOURNALING_ENABLE + struct ext4_fs *fs = buf->bc->bdev->fs; + struct ext4_block block = { + .lb_id = buf->lba, + .data = buf->data, + .buf = buf + }; + + if (fs->jbd_journal && fs->curr_trans) { + struct jbd_trans *trans = fs->curr_trans; + return jbd_trans_set_block_dirty(trans, &block); + } +#endif + ext4_bcache_set_dirty(buf); + return r; +} + +int ext4_trans_block_get_noread(struct ext4_blockdev *bdev, + struct ext4_block *b, + uint64_t lba) +{ + int r = ext4_block_get_noread(bdev, b, lba); + if (r != EOK) + return r; + + r = ext4_trans_get_write_access(bdev->fs, b); + if (r != EOK) + ext4_block_set(bdev, b); + + return r; +} + +int ext4_trans_block_get(struct ext4_blockdev *bdev, + struct ext4_block *b, + uint64_t lba) +{ + int r = ext4_block_get(bdev, b, lba); + if (r != EOK) + return r; + + r = ext4_trans_get_write_access(bdev->fs, b); + if (r != EOK) + ext4_block_set(bdev, b); + + return r; +} + +int ext4_trans_try_revoke_block(struct ext4_blockdev *bdev __unused, + uint64_t lba __unused) +{ + int r = EOK; +#if CONFIG_JOURNALING_ENABLE + struct ext4_fs *fs = bdev->fs; + if (fs->jbd_journal && fs->curr_trans) { + struct jbd_trans *trans = fs->curr_trans; + r = jbd_trans_try_revoke_block(trans, lba); + } else if (fs->jbd_journal) { + r = ext4_block_flush_lba(fs->bdev, lba); + } +#endif + return r; +} + +/** + * @} + */ diff --git a/src/ext4_xattr.c b/src/ext4_xattr.c new file mode 100644 index 0000000..54d457d --- /dev/null +++ b/src/ext4_xattr.c @@ -0,0 +1,942 @@ +/* + * Copyright (c) 2015 Grzegorz Kostka (kostka.grzegorz@gmail.com) + * Copyright (c) 2015 Kaho Ng (ngkaho1234@gmail.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** @addtogroup lwext4 + * @{ + */ +/** + * @file ext4_xattr.c + * @brief Extended Attribute manipulation. + */ + +#include "ext4_config.h" +#include "ext4_types.h" +#include "ext4_fs.h" +#include "ext4_errno.h" +#include "ext4_blockdev.h" +#include "ext4_super.h" +#include "ext4_crc32.h" +#include "ext4_debug.h" +#include "ext4_block_group.h" +#include "ext4_balloc.h" +#include "ext4_inode.h" + +#include <string.h> +#include <stdlib.h> + +/** + * @file ext4_xattr.c + * @brief Extended Attribute Manipulation + */ + +#define NAME_HASH_SHIFT 5 +#define VALUE_HASH_SHIFT 16 + +static inline void ext4_xattr_compute_hash(struct ext4_xattr_header *header, + struct ext4_xattr_entry *entry) +{ + uint32_t hash = 0; + char *name = EXT4_XATTR_NAME(entry); + int n; + + for (n = 0; n < entry->e_name_len; n++) { + hash = (hash << NAME_HASH_SHIFT) ^ + (hash >> (8 * sizeof(hash) - NAME_HASH_SHIFT)) ^ *name++; + } + + if (entry->e_value_block == 0 && entry->e_value_size != 0) { + uint32_t *value = + (uint32_t *)((char *)header + to_le16(entry->e_value_offs)); + for (n = (to_le32(entry->e_value_size) + EXT4_XATTR_ROUND) >> + EXT4_XATTR_PAD_BITS; + n; n--) { + hash = (hash << VALUE_HASH_SHIFT) ^ + (hash >> (8 * sizeof(hash) - VALUE_HASH_SHIFT)) ^ + to_le32(*value++); + } + } + entry->e_hash = to_le32(hash); +} + +#define BLOCK_HASH_SHIFT 16 + +/* + * ext4_xattr_rehash() + * + * Re-compute the extended attribute hash value after an entry has changed. + */ +static void ext4_xattr_rehash(struct ext4_xattr_header *header, + struct ext4_xattr_entry *entry) +{ + struct ext4_xattr_entry *here; + uint32_t hash = 0; + + ext4_xattr_compute_hash(header, entry); + here = EXT4_XATTR_ENTRY(header + 1); + while (!EXT4_XATTR_IS_LAST_ENTRY(here)) { + if (!here->e_hash) { + /* Block is not shared if an entry's hash value == 0 */ + hash = 0; + break; + } + hash = (hash << BLOCK_HASH_SHIFT) ^ + (hash >> (8 * sizeof(hash) - BLOCK_HASH_SHIFT)) ^ + to_le32(here->e_hash); + here = EXT4_XATTR_NEXT(here); + } + header->h_hash = to_le32(hash); +} + +#if CONFIG_META_CSUM_ENABLE +static uint32_t +ext4_xattr_block_checksum(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t blocknr, + struct ext4_xattr_header *header) +{ + uint32_t checksum = 0; + uint64_t le64_blocknr = blocknr; + struct ext4_sblock *sb = &inode_ref->fs->sb; + + if (ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) { + uint32_t orig_checksum; + + /* Preparation: temporarily set bg checksum to 0 */ + orig_checksum = header->h_checksum; + header->h_checksum = 0; + /* First calculate crc32 checksum against fs uuid */ + checksum = ext4_crc32c(EXT4_CRC32_INIT, sb->uuid, + sizeof(sb->uuid)); + /* Then calculate crc32 checksum block number */ + checksum = ext4_crc32c(checksum, &le64_blocknr, + sizeof(le64_blocknr)); + /* Finally calculate crc32 checksum against + * the entire xattr block */ + checksum = ext4_crc32c(checksum, header, + ext4_sb_get_block_size(sb)); + header->h_checksum = orig_checksum; + } + return checksum; +} +#else +#define ext4_xattr_block_checksum(...) 0 +#endif + +static void +ext4_xattr_set_block_checksum(struct ext4_inode_ref *inode_ref, + ext4_fsblk_t blocknr __unused, + struct ext4_xattr_header *header) +{ + struct ext4_sblock *sb = &inode_ref->fs->sb; + if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM)) + return; + + header->h_checksum = + ext4_xattr_block_checksum(inode_ref, blocknr, header); +} + +static int ext4_xattr_item_cmp(struct ext4_xattr_item *a, + struct ext4_xattr_item *b) +{ + int result; + if (a->in_inode && !b->in_inode) + return -1; + + if (!a->in_inode && b->in_inode) + return 1; + + result = a->name_index - b->name_index; + if (result) + return result; + + result = a->name_len - b->name_len; + if (result) + return result; + + return memcmp(a->name, b->name, a->name_len); +} + +RB_GENERATE_INTERNAL(ext4_xattr_tree, ext4_xattr_item, node, + ext4_xattr_item_cmp, static inline) + +static struct ext4_xattr_item * +ext4_xattr_item_alloc(uint8_t name_index, const char *name, size_t name_len) +{ + struct ext4_xattr_item *item; + item = malloc(sizeof(struct ext4_xattr_item) + name_len); + if (!item) + return NULL; + + item->name_index = name_index; + item->name = (char *)(item + 1); + item->name_len = name_len; + item->data = NULL; + item->data_size = 0; + + memset(&item->node, 0, sizeof(item->node)); + memcpy(item->name, name, name_len); + + if (name_index == EXT4_XATTR_INDEX_SYSTEM && + name_len == 4 && + !memcmp(name, "data", 4)) + item->in_inode = true; + else + item->in_inode = false; + + return item; +} + +static int ext4_xattr_item_alloc_data(struct ext4_xattr_item *item, + const void *orig_data, size_t data_size) +{ + void *data = NULL; + ext4_assert(!item->data); + data = malloc(data_size); + if (!data) + return ENOMEM; + + if (orig_data) + memcpy(data, orig_data, data_size); + + item->data = data; + item->data_size = data_size; + return EOK; +} + +static void ext4_xattr_item_free_data(struct ext4_xattr_item *item) +{ + ext4_assert(item->data); + free(item->data); + item->data = NULL; + item->data_size = 0; +} + +static int ext4_xattr_item_resize_data(struct ext4_xattr_item *item, + size_t new_data_size) +{ + if (new_data_size != item->data_size) { + void *new_data; + new_data = realloc(item->data, new_data_size); + if (!new_data) + return ENOMEM; + + item->data = new_data; + item->data_size = new_data_size; + } + return EOK; +} + +static void ext4_xattr_item_free(struct ext4_xattr_item *item) +{ + if (item->data) + ext4_xattr_item_free_data(item); + + free(item); +} + +static void *ext4_xattr_entry_data(struct ext4_xattr_ref *xattr_ref, + struct ext4_xattr_entry *entry, + bool in_inode) +{ + char *ret; + if (in_inode) { + struct ext4_xattr_ibody_header *header; + struct ext4_xattr_entry *first_entry; + int16_t inode_size = + ext4_get16(&xattr_ref->fs->sb, inode_size); + header = EXT4_XATTR_IHDR(xattr_ref->inode_ref->inode); + first_entry = EXT4_XATTR_IFIRST(header); + + ret = ((char *)first_entry + to_le16(entry->e_value_offs)); + if (ret + EXT4_XATTR_SIZE(to_le32(entry->e_value_size)) - + (char *)xattr_ref->inode_ref->inode > inode_size) + ret = NULL; + + return ret; + + } + int32_t block_size = ext4_sb_get_block_size(&xattr_ref->fs->sb); + ret = ((char *)xattr_ref->block.data + to_le16(entry->e_value_offs)); + if (ret + EXT4_XATTR_SIZE(to_le32(entry->e_value_size)) - + (char *)xattr_ref->block.data > block_size) + ret = NULL; + return ret; +} + +static int ext4_xattr_block_fetch(struct ext4_xattr_ref *xattr_ref) +{ + int ret = EOK; + size_t size_rem; + void *data; + struct ext4_xattr_entry *entry = NULL; + + ext4_assert(xattr_ref->block.data); + entry = EXT4_XATTR_BFIRST(&xattr_ref->block); + + size_rem = ext4_sb_get_block_size(&xattr_ref->fs->sb); + for (; size_rem > 0 && !EXT4_XATTR_IS_LAST_ENTRY(entry); + entry = EXT4_XATTR_NEXT(entry), + size_rem -= EXT4_XATTR_LEN(entry->e_name_len)) { + struct ext4_xattr_item *item; + char *e_name = EXT4_XATTR_NAME(entry); + + data = ext4_xattr_entry_data(xattr_ref, entry, false); + if (!data) { + ret = EIO; + goto Finish; + } + + item = ext4_xattr_item_alloc(entry->e_name_index, e_name, + (size_t)entry->e_name_len); + if (!item) { + ret = ENOMEM; + goto Finish; + } + if (ext4_xattr_item_alloc_data( + item, data, to_le32(entry->e_value_size)) != EOK) { + ext4_xattr_item_free(item); + ret = ENOMEM; + goto Finish; + } + RB_INSERT(ext4_xattr_tree, &xattr_ref->root, item); + xattr_ref->ea_size += EXT4_XATTR_SIZE(item->data_size) + + EXT4_XATTR_LEN(item->name_len); + } + +Finish: + return ret; +} + +static int ext4_xattr_inode_fetch(struct ext4_xattr_ref *xattr_ref) +{ + void *data; + size_t size_rem; + int ret = EOK; + struct ext4_xattr_ibody_header *header = NULL; + struct ext4_xattr_entry *entry = NULL; + uint16_t inode_size = ext4_get16(&xattr_ref->fs->sb, inode_size); + + header = EXT4_XATTR_IHDR(xattr_ref->inode_ref->inode); + entry = EXT4_XATTR_IFIRST(header); + + size_rem = inode_size - EXT4_GOOD_OLD_INODE_SIZE - + xattr_ref->inode_ref->inode->extra_isize; + for (; size_rem > 0 && !EXT4_XATTR_IS_LAST_ENTRY(entry); + entry = EXT4_XATTR_NEXT(entry), + size_rem -= EXT4_XATTR_LEN(entry->e_name_len)) { + struct ext4_xattr_item *item; + char *e_name = EXT4_XATTR_NAME(entry); + + data = ext4_xattr_entry_data(xattr_ref, entry, true); + if (!data) { + ret = EIO; + goto Finish; + } + + item = ext4_xattr_item_alloc(entry->e_name_index, e_name, + (size_t)entry->e_name_len); + if (!item) { + ret = ENOMEM; + goto Finish; + } + if (ext4_xattr_item_alloc_data( + item, data, to_le32(entry->e_value_size)) != EOK) { + ext4_xattr_item_free(item); + ret = ENOMEM; + goto Finish; + } + RB_INSERT(ext4_xattr_tree, &xattr_ref->root, item); + xattr_ref->ea_size += EXT4_XATTR_SIZE(item->data_size) + + EXT4_XATTR_LEN(item->name_len); + } + +Finish: + return ret; +} + +static size_t ext4_xattr_inode_space(struct ext4_xattr_ref *xattr_ref) +{ + uint16_t inode_size = ext4_get16(&xattr_ref->fs->sb, inode_size); + uint16_t size_rem = inode_size - EXT4_GOOD_OLD_INODE_SIZE - + xattr_ref->inode_ref->inode->extra_isize; + return size_rem; +} + +static size_t ext4_xattr_block_space(struct ext4_xattr_ref *xattr_ref) +{ + return ext4_sb_get_block_size(&xattr_ref->fs->sb); +} + +static int ext4_xattr_fetch(struct ext4_xattr_ref *xattr_ref) +{ + int ret = EOK; + uint16_t inode_size = ext4_get16(&xattr_ref->fs->sb, inode_size); + if (inode_size > EXT4_GOOD_OLD_INODE_SIZE) { + ret = ext4_xattr_inode_fetch(xattr_ref); + if (ret != EOK) + return ret; + } + + if (xattr_ref->block_loaded) + ret = ext4_xattr_block_fetch(xattr_ref); + + xattr_ref->dirty = false; + return ret; +} + +static struct ext4_xattr_item * +ext4_xattr_lookup_item(struct ext4_xattr_ref *xattr_ref, uint8_t name_index, + const char *name, size_t name_len) +{ + struct ext4_xattr_item tmp = { + .name_index = name_index, + .name = (char *)name, /*RB_FIND - won't touch this string*/ + .name_len = name_len, + }; + if (name_index == EXT4_XATTR_INDEX_SYSTEM && + name_len == 4 && + !memcmp(name, "data", 4)) + tmp.in_inode = true; + + return RB_FIND(ext4_xattr_tree, &xattr_ref->root, &tmp); +} + +static struct ext4_xattr_item * +ext4_xattr_insert_item(struct ext4_xattr_ref *xattr_ref, uint8_t name_index, + const char *name, size_t name_len, const void *data, + size_t data_size) +{ + struct ext4_xattr_item *item; + item = ext4_xattr_item_alloc(name_index, name, name_len); + if (!item) + return NULL; + + if ((xattr_ref->ea_size + EXT4_XATTR_SIZE(data_size) + + EXT4_XATTR_LEN(item->name_len) + > + ext4_xattr_inode_space(xattr_ref) - + sizeof(struct ext4_xattr_ibody_header)) + && + (xattr_ref->ea_size + EXT4_XATTR_SIZE(data_size) + + EXT4_XATTR_LEN(item->name_len) > + ext4_xattr_block_space(xattr_ref) - + sizeof(struct ext4_xattr_header))) { + ext4_xattr_item_free(item); + + return NULL; + } + if (ext4_xattr_item_alloc_data(item, data, data_size) != EOK) { + ext4_xattr_item_free(item); + return NULL; + } + RB_INSERT(ext4_xattr_tree, &xattr_ref->root, item); + xattr_ref->ea_size += + EXT4_XATTR_SIZE(item->data_size) + EXT4_XATTR_LEN(item->name_len); + xattr_ref->dirty = true; + return item; +} + +static int ext4_xattr_remove_item(struct ext4_xattr_ref *xattr_ref, + uint8_t name_index, const char *name, + size_t name_len) +{ + int ret = ENOENT; + struct ext4_xattr_item *item = + ext4_xattr_lookup_item(xattr_ref, name_index, name, name_len); + if (item) { + if (item == xattr_ref->iter_from) + xattr_ref->iter_from = + RB_NEXT(ext4_xattr_tree, &xattr_ref->root, item); + + xattr_ref->ea_size -= EXT4_XATTR_SIZE(item->data_size) + + EXT4_XATTR_LEN(item->name_len); + + RB_REMOVE(ext4_xattr_tree, &xattr_ref->root, item); + ext4_xattr_item_free(item); + xattr_ref->dirty = true; + ret = EOK; + } + return ret; +} + +static int ext4_xattr_resize_item(struct ext4_xattr_ref *xattr_ref, + struct ext4_xattr_item *item, + size_t new_data_size) +{ + int ret = EOK; + size_t old_data_size = item->data_size; + if ((xattr_ref->ea_size - EXT4_XATTR_SIZE(old_data_size) + + EXT4_XATTR_SIZE(new_data_size) + > + ext4_xattr_inode_space(xattr_ref) - + sizeof(struct ext4_xattr_ibody_header)) + && + (xattr_ref->ea_size - EXT4_XATTR_SIZE(old_data_size) + + EXT4_XATTR_SIZE(new_data_size) + > + ext4_xattr_block_space(xattr_ref) - + sizeof(struct ext4_xattr_header))) { + + return ENOSPC; + } + ret = ext4_xattr_item_resize_data(item, new_data_size); + if (ret != EOK) { + return ret; + } + xattr_ref->ea_size = + xattr_ref->ea_size - + EXT4_XATTR_SIZE(old_data_size) + + EXT4_XATTR_SIZE(new_data_size); + xattr_ref->dirty = true; + return ret; +} + +static void ext4_xattr_purge_items(struct ext4_xattr_ref *xattr_ref) +{ + struct ext4_xattr_item *item, *save_item; + RB_FOREACH_SAFE(item, ext4_xattr_tree, &xattr_ref->root, save_item) + { + RB_REMOVE(ext4_xattr_tree, &xattr_ref->root, item); + ext4_xattr_item_free(item); + } + xattr_ref->ea_size = 0; +} + +static int ext4_xattr_try_alloc_block(struct ext4_xattr_ref *xattr_ref) +{ + int ret = EOK; + + ext4_fsblk_t xattr_block = 0; + xattr_block = ext4_inode_get_file_acl(xattr_ref->inode_ref->inode, + &xattr_ref->fs->sb); + if (!xattr_block) { + ext4_fsblk_t goal = + ext4_fs_inode_to_goal_block(xattr_ref->inode_ref); + + ret = ext4_balloc_alloc_block(xattr_ref->inode_ref, + goal, + &xattr_block); + if (ret != EOK) + goto Finish; + + ret = ext4_trans_block_get(xattr_ref->fs->bdev, &xattr_ref->block, + xattr_block); + if (ret != EOK) { + ext4_balloc_free_block(xattr_ref->inode_ref, + xattr_block); + goto Finish; + } + + ext4_inode_set_file_acl(xattr_ref->inode_ref->inode, + &xattr_ref->fs->sb, xattr_block); + xattr_ref->inode_ref->dirty = true; + xattr_ref->block_loaded = true; + } + +Finish: + return ret; +} + +static void ext4_xattr_try_free_block(struct ext4_xattr_ref *xattr_ref) +{ + ext4_fsblk_t xattr_block; + xattr_block = ext4_inode_get_file_acl(xattr_ref->inode_ref->inode, + &xattr_ref->fs->sb); + ext4_inode_set_file_acl(xattr_ref->inode_ref->inode, &xattr_ref->fs->sb, + 0); + ext4_block_set(xattr_ref->fs->bdev, &xattr_ref->block); + ext4_balloc_free_block(xattr_ref->inode_ref, xattr_block); + xattr_ref->inode_ref->dirty = true; + xattr_ref->block_loaded = false; +} + +static void ext4_xattr_set_block_header(struct ext4_xattr_ref *xattr_ref) +{ + struct ext4_xattr_header *block_header = NULL; + block_header = EXT4_XATTR_BHDR(&xattr_ref->block); + + memset(block_header, 0, sizeof(struct ext4_xattr_header)); + block_header->h_magic = EXT4_XATTR_MAGIC; + block_header->h_refcount = to_le32(1); + block_header->h_blocks = to_le32(1); +} + +static void +ext4_xattr_set_inode_entry(struct ext4_xattr_item *item, + struct ext4_xattr_ibody_header *ibody_header, + struct ext4_xattr_entry *entry, void *ibody_data_ptr) +{ + entry->e_name_len = (uint8_t)item->name_len; + entry->e_name_index = item->name_index; + entry->e_value_offs = + (char *)ibody_data_ptr - (char *)EXT4_XATTR_IFIRST(ibody_header); + entry->e_value_block = 0; + entry->e_value_size = item->data_size; +} + +static void ext4_xattr_set_block_entry(struct ext4_xattr_item *item, + struct ext4_xattr_header *block_header, + struct ext4_xattr_entry *block_entry, + void *block_data_ptr) +{ + block_entry->e_name_len = (uint8_t)item->name_len; + block_entry->e_name_index = item->name_index; + block_entry->e_value_offs = + (char *)block_data_ptr - (char *)block_header; + block_entry->e_value_block = 0; + block_entry->e_value_size = item->data_size; +} + +static int ext4_xattr_write_to_disk(struct ext4_xattr_ref *xattr_ref) +{ + int ret = EOK; + bool block_modified = false; + void *ibody_data = NULL; + void *block_data = NULL; + struct ext4_xattr_item *item, *save_item; + size_t inode_size_rem, block_size_rem; + struct ext4_xattr_ibody_header *ibody_header = NULL; + struct ext4_xattr_header *block_header = NULL; + struct ext4_xattr_entry *entry = NULL; + struct ext4_xattr_entry *block_entry = NULL; + + inode_size_rem = ext4_xattr_inode_space(xattr_ref); + block_size_rem = ext4_xattr_block_space(xattr_ref); + if (inode_size_rem > sizeof(struct ext4_xattr_ibody_header)) { + ibody_header = EXT4_XATTR_IHDR(xattr_ref->inode_ref->inode); + entry = EXT4_XATTR_IFIRST(ibody_header); + } + + if (!xattr_ref->dirty) + goto Finish; + /* If there are enough spaces in the ibody EA table.*/ + if (inode_size_rem > sizeof(struct ext4_xattr_ibody_header)) { + memset(ibody_header, 0, inode_size_rem); + ibody_header->h_magic = EXT4_XATTR_MAGIC; + ibody_data = (char *)ibody_header + inode_size_rem; + inode_size_rem -= sizeof(struct ext4_xattr_ibody_header); + + xattr_ref->inode_ref->dirty = true; + } + /* If we need an extra block to hold the EA entries*/ + if (xattr_ref->ea_size > inode_size_rem) { + if (!xattr_ref->block_loaded) { + ret = ext4_xattr_try_alloc_block(xattr_ref); + if (ret != EOK) + goto Finish; + } + block_header = EXT4_XATTR_BHDR(&xattr_ref->block); + block_entry = EXT4_XATTR_BFIRST(&xattr_ref->block); + ext4_xattr_set_block_header(xattr_ref); + block_data = (char *)block_header + block_size_rem; + block_size_rem -= sizeof(struct ext4_xattr_header); + + ext4_trans_set_block_dirty(xattr_ref->block.buf); + } else { + /* We don't need an extra block.*/ + if (xattr_ref->block_loaded) { + block_header = EXT4_XATTR_BHDR(&xattr_ref->block); + block_header->h_refcount = + to_le32(to_le32(block_header->h_refcount) - 1); + if (!block_header->h_refcount) { + ext4_xattr_try_free_block(xattr_ref); + block_header = NULL; + } else { + block_entry = + EXT4_XATTR_BFIRST(&xattr_ref->block); + block_data = + (char *)block_header + block_size_rem; + block_size_rem -= + sizeof(struct ext4_xattr_header); + ext4_inode_set_file_acl( + xattr_ref->inode_ref->inode, + &xattr_ref->fs->sb, 0); + + xattr_ref->inode_ref->dirty = true; + ext4_trans_set_block_dirty(xattr_ref->block.buf); + } + } + } + RB_FOREACH_SAFE(item, ext4_xattr_tree, &xattr_ref->root, save_item) + { + if (EXT4_XATTR_SIZE(item->data_size) + + EXT4_XATTR_LEN(item->name_len) <= + inode_size_rem) { + ibody_data = (char *)ibody_data - + EXT4_XATTR_SIZE(item->data_size); + ext4_xattr_set_inode_entry(item, ibody_header, entry, + ibody_data); + memcpy(EXT4_XATTR_NAME(entry), item->name, + item->name_len); + memcpy(ibody_data, item->data, item->data_size); + entry = EXT4_XATTR_NEXT(entry); + inode_size_rem -= EXT4_XATTR_SIZE(item->data_size) + + EXT4_XATTR_LEN(item->name_len); + + xattr_ref->inode_ref->dirty = true; + continue; + } + if (EXT4_XATTR_SIZE(item->data_size) + + EXT4_XATTR_LEN(item->name_len) > + block_size_rem) { + ret = ENOSPC; + goto Finish; + } + block_data = + (char *)block_data - EXT4_XATTR_SIZE(item->data_size); + ext4_xattr_set_block_entry(item, block_header, block_entry, + block_data); + memcpy(EXT4_XATTR_NAME(block_entry), item->name, + item->name_len); + memcpy(block_data, item->data, item->data_size); + block_entry = EXT4_XATTR_NEXT(block_entry); + block_size_rem -= EXT4_XATTR_SIZE(item->data_size) + + EXT4_XATTR_LEN(item->name_len); + + block_modified = true; + } + xattr_ref->dirty = false; + if (block_modified) { + ext4_xattr_rehash(block_header, + EXT4_XATTR_BFIRST(&xattr_ref->block)); + ext4_xattr_set_block_checksum(xattr_ref->inode_ref, + xattr_ref->block.lb_id, + block_header); + ext4_trans_set_block_dirty(xattr_ref->block.buf); + } + +Finish: + return ret; +} + +void ext4_fs_xattr_iterate(struct ext4_xattr_ref *ref, + int (*iter)(struct ext4_xattr_ref *ref, + struct ext4_xattr_item *item)) +{ + struct ext4_xattr_item *item; + if (!ref->iter_from) + ref->iter_from = RB_MIN(ext4_xattr_tree, &ref->root); + + RB_FOREACH_FROM(item, ext4_xattr_tree, ref->iter_from) + { + int ret = EXT4_XATTR_ITERATE_CONT; + if (iter) + iter(ref, item); + + if (ret != EXT4_XATTR_ITERATE_CONT) { + if (ret == EXT4_XATTR_ITERATE_STOP) + ref->iter_from = NULL; + + break; + } + } +} + +void ext4_fs_xattr_iterate_reset(struct ext4_xattr_ref *ref) +{ + ref->iter_from = NULL; +} + +int ext4_fs_set_xattr(struct ext4_xattr_ref *ref, uint8_t name_index, + const char *name, size_t name_len, const void *data, + size_t data_size, bool replace) +{ + int ret = EOK; + struct ext4_xattr_item *item = + ext4_xattr_lookup_item(ref, name_index, name, name_len); + if (replace) { + if (!item) { + ret = ENODATA; + goto Finish; + } + if (item->data_size != data_size) + ret = ext4_xattr_resize_item(ref, item, data_size); + + if (ret != EOK) { + goto Finish; + } + memcpy(item->data, data, data_size); + } else { + if (item) { + ret = EEXIST; + goto Finish; + } + item = ext4_xattr_insert_item(ref, name_index, name, name_len, + data, data_size); + if (!item) + ret = ENOMEM; + } +Finish: + return ret; +} + +int ext4_fs_remove_xattr(struct ext4_xattr_ref *ref, uint8_t name_index, + const char *name, size_t name_len) +{ + return ext4_xattr_remove_item(ref, name_index, name, name_len); +} + +int ext4_fs_get_xattr(struct ext4_xattr_ref *ref, uint8_t name_index, + const char *name, size_t name_len, void *buf, + size_t buf_size, size_t *data_size) +{ + int ret = EOK; + size_t item_size = 0; + struct ext4_xattr_item *item = + ext4_xattr_lookup_item(ref, name_index, name, name_len); + + if (!item) { + ret = ENODATA; + goto Finish; + } + item_size = item->data_size; + if (buf_size > item_size) + buf_size = item_size; + + if (buf) + memcpy(buf, item->data, buf_size); + +Finish: + if (data_size) + *data_size = item_size; + + return ret; +} + +int ext4_fs_get_xattr_ref(struct ext4_fs *fs, struct ext4_inode_ref *inode_ref, + struct ext4_xattr_ref *ref) +{ + int rc; + ext4_fsblk_t xattr_block; + xattr_block = ext4_inode_get_file_acl(inode_ref->inode, &fs->sb); + RB_INIT(&ref->root); + ref->ea_size = 0; + ref->iter_from = NULL; + if (xattr_block) { + rc = ext4_trans_block_get(fs->bdev, &ref->block, xattr_block); + if (rc != EOK) + return EIO; + + ref->block_loaded = true; + } else + ref->block_loaded = false; + + ref->inode_ref = inode_ref; + ref->fs = fs; + + rc = ext4_xattr_fetch(ref); + if (rc != EOK) { + ext4_xattr_purge_items(ref); + if (xattr_block) + ext4_block_set(fs->bdev, &inode_ref->block); + + ref->block_loaded = false; + return rc; + } + return EOK; +} + +void ext4_fs_put_xattr_ref(struct ext4_xattr_ref *ref) +{ + ext4_xattr_write_to_disk(ref); + if (ref->block_loaded) { + ext4_block_set(ref->fs->bdev, &ref->block); + ref->block_loaded = false; + } + ext4_xattr_purge_items(ref); + ref->inode_ref = NULL; + ref->fs = NULL; +} + +struct xattr_prefix { + const char *prefix; + uint8_t name_index; +}; + +static const struct xattr_prefix prefix_tbl[] = { + {"user.", EXT4_XATTR_INDEX_USER}, + {"system.posix_acl_access", EXT4_XATTR_INDEX_POSIX_ACL_ACCESS}, + {"system.posix_acl_default", EXT4_XATTR_INDEX_POSIX_ACL_DEFAULT}, + {"trusted.", EXT4_XATTR_INDEX_TRUSTED}, + {"security.", EXT4_XATTR_INDEX_SECURITY}, + {"system.", EXT4_XATTR_INDEX_SYSTEM}, + {"system.richacl", EXT4_XATTR_INDEX_RICHACL}, + {NULL, 0}, +}; + +const char *ext4_extract_xattr_name(const char *full_name, size_t full_name_len, + uint8_t *name_index, size_t *name_len) +{ + int i; + ext4_assert(name_index); + if (!full_name_len) { + if (name_len) + *name_len = 0; + + return NULL; + } + + for (i = 0; prefix_tbl[i].prefix; i++) { + size_t prefix_len = strlen(prefix_tbl[i].prefix); + if (full_name_len >= prefix_len && + !memcmp(full_name, prefix_tbl[i].prefix, prefix_len)) { + *name_index = prefix_tbl[i].name_index; + if (name_len) + *name_len = full_name_len - prefix_len; + + return full_name + prefix_len; + } + } + if (name_len) + *name_len = 0; + + return NULL; +} + +const char *ext4_get_xattr_name_prefix(uint8_t name_index, + size_t *ret_prefix_len) +{ + int i; + + for (i = 0; prefix_tbl[i].prefix; i++) { + size_t prefix_len = strlen(prefix_tbl[i].prefix); + if (prefix_tbl[i].name_index == name_index) { + if (ret_prefix_len) + *ret_prefix_len = prefix_len; + + return prefix_tbl[i].prefix; + } + } + if (ret_prefix_len) + *ret_prefix_len = 0; + + return NULL; +} + +/** + * @} + */ |
