summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorngkaho1234 <ngkaho1234@gmail.com>2016-01-28 22:46:36 +0800
committerngkaho1234 <ngkaho1234@gmail.com>2016-01-28 22:46:36 +0800
commita45154a49b743eba4669442e6993c50583329d99 (patch)
tree343ba15a0cdbbfa32b87f1ed1603ae7c8bfdfd65 /src
parentd3bb06fff7af3b2162633b4ab79f7f09b3fe3fdb (diff)
Reconstruct source directory tree.
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt10
-rw-r--r--src/ext4.c2935
-rw-r--r--src/ext4_balloc.c654
-rw-r--r--src/ext4_bcache.c317
-rw-r--r--src/ext4_bitmap.c156
-rw-r--r--src/ext4_block_group.c89
-rw-r--r--src/ext4_blockdev.c463
-rw-r--r--src/ext4_crc32.c182
-rw-r--r--src/ext4_debug.c64
-rw-r--r--src/ext4_dir.c690
-rw-r--r--src/ext4_dir_idx.c1439
-rw-r--r--src/ext4_extent.c1859
-rw-r--r--src/ext4_fs.c1713
-rw-r--r--src/ext4_hash.c322
-rw-r--r--src/ext4_ialloc.c365
-rw-r--r--src/ext4_inode.c372
-rw-r--r--src/ext4_journal.c2158
-rw-r--r--src/ext4_mbr.c129
-rw-r--r--src/ext4_mkfs.c774
-rw-r--r--src/ext4_super.c267
-rw-r--r--src/ext4_trans.c124
-rw-r--r--src/ext4_xattr.c942
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,
+ &current_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,
+ &current_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;
+}
+
+/**
+ * @}
+ */