HelenOS sources
This source file includes following definitions.
- ext4_extent_get_first_block
- ext4_extent_set_first_block
- ext4_extent_get_block_count
- ext4_extent_set_block_count
- ext4_extent_get_start
- ext4_extent_set_start
- ext4_extent_index_get_first_block
- ext4_extent_index_set_first_block
- ext4_extent_index_get_leaf
- ext4_extent_index_set_leaf
- ext4_extent_header_get_magic
- ext4_extent_header_set_magic
- ext4_extent_header_get_entries_count
- ext4_extent_header_set_entries_count
- ext4_extent_header_get_max_entries_count
- ext4_extent_header_set_max_entries_count
- ext4_extent_header_get_depth
- ext4_extent_header_set_depth
- ext4_extent_header_get_generation
- ext4_extent_header_set_generation
- ext4_extent_binsearch_idx
- ext4_extent_binsearch
- ext4_extent_find_block
- ext4_extent_find_extent
- ext4_extent_release
- ext4_extent_release_branch
- ext4_extent_release_blocks_from
- ext4_extent_append_extent
- ext4_extent_append_block
#include <byteorder.h>
#include <errno.h>
#include <mem.h>
#include <stdlib.h>
#include "ext4/balloc.h"
#include "ext4/extent.h"
#include "ext4/inode.h"
#include "ext4/superblock.h"
uint32_t ext4_extent_get_first_block(ext4_extent_t *extent)
{
return uint32_t_le2host(extent->first_block);
}
void ext4_extent_set_first_block(ext4_extent_t *extent, uint32_t iblock)
{
extent->first_block = host2uint32_t_le(iblock);
}
uint16_t ext4_extent_get_block_count(ext4_extent_t *extent)
{
return uint16_t_le2host(extent->block_count);
}
void ext4_extent_set_block_count(ext4_extent_t *extent, uint16_t count)
{
extent->block_count = host2uint16_t_le(count);
}
uint64_t ext4_extent_get_start(ext4_extent_t *extent)
{
return ((uint64_t)uint16_t_le2host(extent->start_hi)) << 32 |
((uint64_t)uint32_t_le2host(extent->start_lo));
}
void ext4_extent_set_start(ext4_extent_t *extent, uint64_t fblock)
{
extent->start_lo = host2uint32_t_le((fblock << 32) >> 32);
extent->start_hi = host2uint16_t_le((uint16_t)(fblock >> 32));
}
uint32_t ext4_extent_index_get_first_block(ext4_extent_index_t *index)
{
return uint32_t_le2host(index->first_block);
}
void ext4_extent_index_set_first_block(ext4_extent_index_t *index,
uint32_t iblock)
{
index->first_block = host2uint32_t_le(iblock);
}
uint64_t ext4_extent_index_get_leaf(ext4_extent_index_t *index)
{
return ((uint64_t) uint16_t_le2host(index->leaf_hi)) << 32 |
((uint64_t)uint32_t_le2host(index->leaf_lo));
}
void ext4_extent_index_set_leaf(ext4_extent_index_t *index, uint64_t fblock)
{
index->leaf_lo = host2uint32_t_le((fblock << 32) >> 32);
index->leaf_hi = host2uint16_t_le((uint16_t) (fblock >> 32));
}
uint16_t ext4_extent_header_get_magic(ext4_extent_header_t *header)
{
return uint16_t_le2host(header->magic);
}
void ext4_extent_header_set_magic(ext4_extent_header_t *header, uint16_t magic)
{
header->magic = host2uint16_t_le(magic);
}
uint16_t ext4_extent_header_get_entries_count(ext4_extent_header_t *header)
{
return uint16_t_le2host(header->entries_count);
}
void ext4_extent_header_set_entries_count(ext4_extent_header_t *header,
uint16_t count)
{
header->entries_count = host2uint16_t_le(count);
}
uint16_t ext4_extent_header_get_max_entries_count(ext4_extent_header_t *header)
{
return uint16_t_le2host(header->max_entries_count);
}
void ext4_extent_header_set_max_entries_count(ext4_extent_header_t *header,
uint16_t max_count)
{
header->max_entries_count = host2uint16_t_le(max_count);
}
uint16_t ext4_extent_header_get_depth(ext4_extent_header_t *header)
{
return uint16_t_le2host(header->depth);
}
void ext4_extent_header_set_depth(ext4_extent_header_t *header, uint16_t depth)
{
header->depth = host2uint16_t_le(depth);
}
uint32_t ext4_extent_header_get_generation(ext4_extent_header_t *header)
{
return uint32_t_le2host(header->generation);
}
void ext4_extent_header_set_generation(ext4_extent_header_t *header,
uint32_t generation)
{
header->generation = host2uint32_t_le(generation);
}
static void ext4_extent_binsearch_idx(ext4_extent_header_t *header,
ext4_extent_index_t **index, uint32_t iblock)
{
ext4_extent_index_t *r;
ext4_extent_index_t *l;
ext4_extent_index_t *m;
uint16_t entries_count =
ext4_extent_header_get_entries_count(header);
l = EXT4_EXTENT_FIRST_INDEX(header) + 1;
r = EXT4_EXTENT_FIRST_INDEX(header) + entries_count - 1;
while (l <= r) {
m = l + (r - l) / 2;
uint32_t first_block = ext4_extent_index_get_first_block(m);
if (iblock < first_block)
r = m - 1;
else
l = m + 1;
}
*index = l - 1;
}
static void ext4_extent_binsearch(ext4_extent_header_t *header,
ext4_extent_t **extent, uint32_t iblock)
{
ext4_extent_t *r;
ext4_extent_t *l;
ext4_extent_t *m;
uint16_t entries_count =
ext4_extent_header_get_entries_count(header);
if (entries_count == 0) {
*extent = NULL;
return;
}
l = EXT4_EXTENT_FIRST(header) + 1;
r = EXT4_EXTENT_FIRST(header) + entries_count - 1;
while (l <= r) {
m = l + (r - l) / 2;
uint32_t first_block = ext4_extent_get_first_block(m);
if (iblock < first_block)
r = m - 1;
else
l = m + 1;
}
*extent = l - 1;
}
errno_t ext4_extent_find_block(ext4_inode_ref_t *inode_ref, uint32_t iblock,
uint32_t *fblock)
{
errno_t rc = EOK;
uint64_t inode_size =
ext4_inode_get_size(inode_ref->fs->superblock, inode_ref->inode);
uint32_t block_size =
ext4_superblock_get_block_size(inode_ref->fs->superblock);
uint32_t last_idx = (inode_size - 1) / block_size;
if (iblock > last_idx) {
*fblock = 0;
return EOK;
}
block_t *block = NULL;
ext4_extent_header_t *header =
ext4_inode_get_extent_header(inode_ref->inode);
while (ext4_extent_header_get_depth(header) != 0) {
ext4_extent_index_t *index;
ext4_extent_binsearch_idx(header, &index, iblock);
uint64_t child = ext4_extent_index_get_leaf(index);
if (block != NULL) {
rc = block_put(block);
if (rc != EOK)
return rc;
}
rc = block_get(&block, inode_ref->fs->device, child,
BLOCK_FLAGS_NONE);
if (rc != EOK)
return rc;
header = (ext4_extent_header_t *)block->data;
}
ext4_extent_t *extent = NULL;
ext4_extent_binsearch(header, &extent, iblock);
if (extent == NULL) {
*fblock = 0;
} else {
uint32_t phys_block;
uint32_t first = ext4_extent_get_first_block(extent);
phys_block = ext4_extent_get_start(extent) + iblock - first;
*fblock = phys_block;
}
if (block != NULL)
rc = block_put(block);
return rc;
}
static errno_t ext4_extent_find_extent(ext4_inode_ref_t *inode_ref, uint32_t iblock,
ext4_extent_path_t **ret_path)
{
ext4_extent_header_t *eh =
ext4_inode_get_extent_header(inode_ref->inode);
uint16_t depth = ext4_extent_header_get_depth(eh);
ext4_extent_path_t *tmp_path;
tmp_path = malloc(sizeof(ext4_extent_path_t) * (depth + 2));
if (tmp_path == NULL)
return ENOMEM;
tmp_path[0].block = inode_ref->block;
tmp_path[0].header = eh;
uint16_t pos = 0;
errno_t rc;
errno_t rc2;
while (ext4_extent_header_get_depth(eh) != 0) {
ext4_extent_binsearch_idx(tmp_path[pos].header,
&tmp_path[pos].index, iblock);
tmp_path[pos].depth = depth;
tmp_path[pos].extent = NULL;
assert(tmp_path[pos].index != NULL);
uint64_t fblock = ext4_extent_index_get_leaf(tmp_path[pos].index);
block_t *block;
rc = block_get(&block, inode_ref->fs->device, fblock,
BLOCK_FLAGS_NONE);
if (rc != EOK)
goto cleanup;
pos++;
eh = (ext4_extent_header_t *)block->data;
tmp_path[pos].block = block;
tmp_path[pos].header = eh;
}
tmp_path[pos].depth = 0;
tmp_path[pos].extent = NULL;
tmp_path[pos].index = NULL;
ext4_extent_binsearch(tmp_path[pos].header, &tmp_path[pos].extent, iblock);
*ret_path = tmp_path;
return EOK;
cleanup:
rc2 = EOK;
for (uint16_t i = 1; i < tmp_path->depth; ++i) {
if (tmp_path[i].block) {
rc2 = block_put(tmp_path[i].block);
if (rc == EOK && rc2 != EOK)
rc = rc2;
}
}
free(tmp_path);
return rc;
}
static errno_t ext4_extent_release(ext4_inode_ref_t *inode_ref,
ext4_extent_t *extent)
{
uint64_t start = ext4_extent_get_start(extent);
uint16_t block_count = ext4_extent_get_block_count(extent);
return ext4_balloc_free_blocks(inode_ref, start, block_count);
}
static errno_t ext4_extent_release_branch(ext4_inode_ref_t *inode_ref,
ext4_extent_index_t *index)
{
uint32_t fblock = ext4_extent_index_get_leaf(index);
block_t *block;
errno_t rc = block_get(&block, inode_ref->fs->device, fblock, BLOCK_FLAGS_NONE);
if (rc != EOK)
return rc;
ext4_extent_header_t *header = block->data;
if (ext4_extent_header_get_depth(header)) {
ext4_extent_index_t *idx = EXT4_EXTENT_FIRST_INDEX(header);
for (uint32_t i = 0;
i < ext4_extent_header_get_entries_count(header);
++i, ++idx) {
rc = ext4_extent_release_branch(inode_ref, idx);
if (rc != EOK)
return rc;
}
} else {
ext4_extent_t *ext = EXT4_EXTENT_FIRST(header);
for (uint32_t i = 0;
i < ext4_extent_header_get_entries_count(header);
++i, ++ext) {
rc = ext4_extent_release(inode_ref, ext);
if (rc != EOK)
return rc;
}
}
rc = block_put(block);
if (rc != EOK)
return rc;
return ext4_balloc_free_block(inode_ref, fblock);
}
errno_t ext4_extent_release_blocks_from(ext4_inode_ref_t *inode_ref,
uint32_t iblock_from)
{
ext4_extent_path_t *path;
errno_t rc2;
errno_t rc = ext4_extent_find_extent(inode_ref, iblock_from, &path);
if (rc != EOK)
return rc;
ext4_extent_path_t *path_ptr = path;
while (path_ptr->depth != 0)
path_ptr++;
assert(path_ptr->extent != NULL);
uint32_t first_iblock =
ext4_extent_get_first_block(path_ptr->extent);
uint32_t first_fblock =
ext4_extent_get_start(path_ptr->extent) + iblock_from - first_iblock;
uint16_t block_count = ext4_extent_get_block_count(path_ptr->extent);
uint16_t delete_count = block_count -
(first_fblock - ext4_extent_get_start(path_ptr->extent));
rc = ext4_balloc_free_blocks(inode_ref, first_fblock, delete_count);
if (rc != EOK)
goto cleanup;
block_count -= delete_count;
ext4_extent_set_block_count(path_ptr->extent, block_count);
uint16_t entries =
ext4_extent_header_get_entries_count(path_ptr->header);
ext4_extent_t *tmp_ext = path_ptr->extent + 1;
ext4_extent_t *stop_ext = EXT4_EXTENT_FIRST(path_ptr->header) + entries;
if (block_count == 0)
entries--;
while (tmp_ext < stop_ext) {
first_fblock = ext4_extent_get_start(tmp_ext);
delete_count = ext4_extent_get_block_count(tmp_ext);
rc = ext4_balloc_free_blocks(inode_ref, first_fblock, delete_count);
if (rc != EOK)
goto cleanup;
entries--;
tmp_ext++;
}
ext4_extent_header_set_entries_count(path_ptr->header, entries);
path_ptr->block->dirty = true;
bool remove_parent_record = false;
if ((path_ptr != path) && (entries == 0)) {
rc = ext4_balloc_free_block(inode_ref, path_ptr->block->lba);
if (rc != EOK)
goto cleanup;
remove_parent_record = true;
}
--path_ptr;
while (path_ptr >= path) {
entries = ext4_extent_header_get_entries_count(path_ptr->header);
ext4_extent_index_t *index = path_ptr->index + 1;
ext4_extent_index_t *stop =
EXT4_EXTENT_FIRST_INDEX(path_ptr->header) + entries;
if (remove_parent_record)
entries--;
while (index < stop) {
rc = ext4_extent_release_branch(inode_ref, index);
if (rc != EOK)
goto cleanup;
++index;
--entries;
}
ext4_extent_header_set_entries_count(path_ptr->header, entries);
path_ptr->block->dirty = true;
if ((entries == 0) && (path_ptr != path)) {
rc = ext4_balloc_free_block(inode_ref, path_ptr->block->lba);
if (rc != EOK)
goto cleanup;
remove_parent_record = true;
} else
remove_parent_record = false;
--path_ptr;
}
cleanup:
rc2 = EOK;
for (uint16_t i = 1; i <= path->depth; ++i) {
if (path[i].block) {
rc2 = block_put(path[i].block);
if (rc == EOK && rc2 != EOK)
rc = rc2;
}
}
free(path);
return rc;
}
static errno_t ext4_extent_append_extent(ext4_inode_ref_t *inode_ref,
ext4_extent_path_t *path, uint32_t iblock)
{
ext4_extent_path_t *path_ptr = path + path->depth;
uint32_t block_size =
ext4_superblock_get_block_size(inode_ref->fs->superblock);
while (path_ptr > path) {
uint16_t entries =
ext4_extent_header_get_entries_count(path_ptr->header);
uint16_t limit =
ext4_extent_header_get_max_entries_count(path_ptr->header);
if (entries == limit) {
uint32_t fblock;
errno_t rc = ext4_balloc_alloc_block(inode_ref, &fblock);
if (rc != EOK)
return rc;
block_t *block;
rc = block_get(&block, inode_ref->fs->device, fblock,
BLOCK_FLAGS_NOREAD);
if (rc != EOK) {
ext4_balloc_free_block(inode_ref, fblock);
return rc;
}
rc = block_put(path_ptr->block);
if (rc != EOK) {
ext4_balloc_free_block(inode_ref, fblock);
block_put(block);
return rc;
}
memset(block->data, 0, block_size);
path_ptr->block = block;
path_ptr->header = block->data;
if (path_ptr->depth) {
path_ptr->index = EXT4_EXTENT_FIRST_INDEX(path_ptr->header);
ext4_extent_index_set_first_block(path_ptr->index, iblock);
ext4_extent_index_set_leaf(path_ptr->index, (path_ptr + 1)->block->lba);
limit = (block_size - sizeof(ext4_extent_header_t)) /
sizeof(ext4_extent_index_t);
} else {
path_ptr->extent = EXT4_EXTENT_FIRST(path_ptr->header);
ext4_extent_set_first_block(path_ptr->extent, iblock);
limit = (block_size - sizeof(ext4_extent_header_t)) /
sizeof(ext4_extent_t);
}
ext4_extent_header_set_entries_count(path_ptr->header, 1);
ext4_extent_header_set_max_entries_count(path_ptr->header, limit);
ext4_extent_header_set_magic(path_ptr->header, EXT4_EXTENT_MAGIC);
ext4_extent_header_set_depth(path_ptr->header, path_ptr->depth);
ext4_extent_header_set_generation(path_ptr->header, 0);
path_ptr->block->dirty = true;
path_ptr--;
} else {
if (path_ptr->depth) {
path_ptr->index = EXT4_EXTENT_FIRST_INDEX(path_ptr->header) + entries;
ext4_extent_index_set_first_block(path_ptr->index, iblock);
ext4_extent_index_set_leaf(path_ptr->index, (path_ptr + 1)->block->lba);
} else {
path_ptr->extent = EXT4_EXTENT_FIRST(path_ptr->header) + entries;
ext4_extent_set_first_block(path_ptr->extent, iblock);
}
ext4_extent_header_set_entries_count(path_ptr->header, entries + 1);
path_ptr->block->dirty = true;
return EOK;
}
}
assert(path_ptr == path);
uint16_t entries = ext4_extent_header_get_entries_count(path->header);
uint16_t limit = ext4_extent_header_get_max_entries_count(path->header);
if (entries == limit) {
uint32_t new_fblock;
errno_t rc = ext4_balloc_alloc_block(inode_ref, &new_fblock);
if (rc != EOK)
return rc;
block_t *block;
rc = block_get(&block, inode_ref->fs->device, new_fblock,
BLOCK_FLAGS_NOREAD);
if (rc != EOK)
return rc;
memset(block->data, 0, block_size);
memcpy(block->data, inode_ref->inode->blocks,
EXT4_INODE_BLOCKS * sizeof(uint32_t));
block_t *root_block = path->block;
uint16_t root_depth = path->depth;
ext4_extent_header_t *root_header = path->header;
ext4_extent_path_t *new_root = path;
ext4_extent_path_t *old_root = path + 1;
size_t nbytes = sizeof(ext4_extent_path_t) * (path->depth + 1);
memmove(old_root, new_root, nbytes);
memset(new_root, 0, sizeof(ext4_extent_path_t));
old_root->block = block;
old_root->header = (ext4_extent_header_t *)block->data;
if (old_root->depth) {
limit = (block_size - sizeof(ext4_extent_header_t)) /
sizeof(ext4_extent_index_t);
old_root->index = EXT4_EXTENT_FIRST_INDEX(old_root->header) + entries;
ext4_extent_index_set_first_block(old_root->index, iblock);
ext4_extent_index_set_leaf(old_root->index, (old_root + 1)->block->lba);
old_root->extent = NULL;
} else {
limit = (block_size - sizeof(ext4_extent_header_t)) /
sizeof(ext4_extent_t);
old_root->extent = EXT4_EXTENT_FIRST(old_root->header) + entries;
ext4_extent_set_first_block(old_root->extent, iblock);
old_root->index = NULL;
}
ext4_extent_header_set_entries_count(old_root->header, entries + 1);
ext4_extent_header_set_max_entries_count(old_root->header, limit);
old_root->block->dirty = true;
new_root->depth = root_depth + 1;
new_root->block = root_block;
new_root->header = root_header;
new_root->extent = NULL;
new_root->index = EXT4_EXTENT_FIRST_INDEX(new_root->header);
ext4_extent_header_set_depth(new_root->header, new_root->depth);
ext4_extent_header_set_entries_count(new_root->header, 1);
ext4_extent_index_set_first_block(new_root->index, 0);
ext4_extent_index_set_leaf(new_root->index, new_fblock);
new_root->block->dirty = true;
} else {
if (path->depth) {
path->index = EXT4_EXTENT_FIRST_INDEX(path->header) + entries;
ext4_extent_index_set_first_block(path->index, iblock);
ext4_extent_index_set_leaf(path->index, (path + 1)->block->lba);
} else {
path->extent = EXT4_EXTENT_FIRST(path->header) + entries;
ext4_extent_set_first_block(path->extent, iblock);
}
ext4_extent_header_set_entries_count(path->header, entries + 1);
path->block->dirty = true;
}
return EOK;
}
errno_t ext4_extent_append_block(ext4_inode_ref_t *inode_ref, uint32_t *iblock,
uint32_t *fblock, bool update_size)
{
ext4_superblock_t *sb = inode_ref->fs->superblock;
uint64_t inode_size = ext4_inode_get_size(sb, inode_ref->inode);
uint32_t block_size = ext4_superblock_get_block_size(sb);
uint32_t new_block_idx = 0;
if (inode_size > 0) {
if ((inode_size % block_size) != 0)
inode_size += block_size - (inode_size % block_size);
new_block_idx = inode_size / block_size;
}
ext4_extent_path_t *path;
errno_t rc2;
errno_t rc = ext4_extent_find_extent(inode_ref, new_block_idx, &path);
if (rc != EOK)
return rc;
ext4_extent_path_t *path_ptr = path;
while (path_ptr->depth != 0)
path_ptr++;
if (path_ptr->extent == NULL)
goto append_extent;
uint16_t block_count = ext4_extent_get_block_count(path_ptr->extent);
uint16_t block_limit = (1 << 15);
uint32_t phys_block = 0;
if (block_count < block_limit) {
if (block_count == 0) {
rc = ext4_balloc_alloc_block(inode_ref, &phys_block);
if (rc != EOK)
goto finish;
ext4_extent_set_first_block(path_ptr->extent, new_block_idx);
ext4_extent_set_start(path_ptr->extent, phys_block);
ext4_extent_set_block_count(path_ptr->extent, 1);
if (update_size) {
ext4_inode_set_size(inode_ref->inode, inode_size + block_size);
inode_ref->dirty = true;
}
path_ptr->block->dirty = true;
goto finish;
} else {
phys_block = ext4_extent_get_start(path_ptr->extent);
phys_block += ext4_extent_get_block_count(path_ptr->extent);
bool free;
rc = ext4_balloc_try_alloc_block(inode_ref, phys_block, &free);
if (rc != EOK)
goto finish;
if (!free) {
goto append_extent;
}
ext4_extent_set_block_count(path_ptr->extent, block_count + 1);
if (update_size) {
ext4_inode_set_size(inode_ref->inode, inode_size + block_size);
inode_ref->dirty = true;
}
path_ptr->block->dirty = true;
goto finish;
}
}
append_extent:
phys_block = 0;
rc = ext4_balloc_alloc_block(inode_ref, &phys_block);
if (rc != EOK)
goto finish;
rc = ext4_extent_append_extent(inode_ref, path, new_block_idx);
if (rc != EOK) {
ext4_balloc_free_block(inode_ref, phys_block);
goto finish;
}
uint32_t tree_depth = ext4_extent_header_get_depth(path->header);
path_ptr = path + tree_depth;
ext4_extent_set_block_count(path_ptr->extent, 1);
ext4_extent_set_first_block(path_ptr->extent, new_block_idx);
ext4_extent_set_start(path_ptr->extent, phys_block);
if (update_size) {
ext4_inode_set_size(inode_ref->inode, inode_size + block_size);
inode_ref->dirty = true;
}
path_ptr->block->dirty = true;
finish:
rc2 = EOK;
*iblock = new_block_idx;
*fblock = phys_block;
for (uint16_t i = 1; i <= path->depth; ++i) {
if (path[i].block) {
rc2 = block_put(path[i].block);
if (rc == EOK && rc2 != EOK)
rc = rc2;
}
}
free(path);
return rc;
}
HelenOS homepage, sources at GitHub