blob: 76e9ce45ee207f3a3315f198b38850ed94ac8f06 [file] [log] [blame]
/*
* Copyright (c) 2007 Travis Geiselbrecht
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <string.h>
#include <stdlib.h>
#include <debug.h>
#include <lib/fs/ext2.h>
#include "ext2_priv.h"
#define LOCAL_TRACE 0
int ext2_read_block(ext2_t *ext2, void *buf, blocknum_t bnum)
{
return bcache_read_block(ext2->cache, buf, bnum);
}
int ext2_get_block(ext2_t *ext2, void **ptr, blocknum_t bnum)
{
return bcache_get_block(ext2->cache, ptr, bnum);
}
int ext2_put_block(ext2_t *ext2, blocknum_t bnum)
{
return bcache_put_block(ext2->cache, bnum);
}
static int ext2_calculate_block_pointer_pos(ext2_t *ext2, blocknum_t block_to_find, uint32_t *level, uint32_t pos[])
{
uint32_t block_ptr_per_block, block_ptr_per_2nd_block;
// XXX optimize this
// See if it's in the direct blocks
if(block_to_find < EXT2_NDIR_BLOCKS) {
*level = 0;
pos[0] = block_to_find;
return 0;
}
block_ptr_per_block = EXT2_ADDR_PER_BLOCK(ext2->sb);
block_to_find -= EXT2_NDIR_BLOCKS;
// See if it's in the first indirect block
if(block_to_find < block_ptr_per_block) {
*level = 1;
pos[0] = EXT2_IND_BLOCK;
pos[1] = block_to_find;
return 0;
}
block_to_find -= block_ptr_per_block;
block_ptr_per_2nd_block = block_ptr_per_block * block_ptr_per_block;
// See if it's in the second indirect block
if(block_to_find < (block_ptr_per_2nd_block)) {
*level = 2;
pos[0] = EXT2_DIND_BLOCK;
pos[1] = block_to_find / block_ptr_per_block;
pos[2] = block_to_find % block_ptr_per_block;
return 0;
}
block_to_find -= block_ptr_per_2nd_block;
// See if it's in the third indirect block
if(block_to_find < (block_ptr_per_2nd_block * block_ptr_per_block)) {
*level = 3;
pos[0] = EXT2_TIND_BLOCK;
pos[1] = block_to_find / block_ptr_per_2nd_block;
pos[2] = (block_to_find % block_ptr_per_2nd_block) / block_ptr_per_block;
pos[3] = (block_to_find % block_ptr_per_2nd_block) % block_ptr_per_block;
return 0;
}
// The block requested must be too big.
return -1;
}
// This function returns a pointer to the cache block that corresponds to the indirect block pointer.
int ext2_get_indirect_block_pointer_cache_block(ext2_t *ext2, struct ext2_inode *inode, blocknum_t **cache_block, uint32_t level, uint32_t pos[], uint *block_loaded)
{
uint32_t current_level = 0;
uint current_block = 0, last_block;
blocknum_t *block = NULL;
int err;
if ((level > 3) || (level == 0)) {
err = -1;
goto error;
}
// Dig down into the indirect blocks. When done, current_block should point to the target.
while (current_level < level) {
if (current_level == 0) {
// read the direct block, simulates a prior loop
current_block = LE32(inode->i_block[pos[0]]);
}
if (current_block == 0) {
err = -1;
goto error;
}
last_block = current_block;
current_level++;
*block_loaded = current_block;
err = ext2_get_block(ext2, (void **)(void *)&block, current_block);
if (err < 0) {
goto error;
}
if (current_level < level) {
current_block = LE32(block[pos[current_level]]);
ext2_put_block(ext2, last_block);
}
}
*cache_block = block;
return 0;
error:
*cache_block = NULL;
*block_loaded = 0;
return err;
}
/* translate a file block to a physical block */
static blocknum_t file_block_to_fs_block(ext2_t *ext2, struct ext2_inode *inode, uint fileblock)
{
int err;
blocknum_t block;
LTRACEF("inode %p, fileblock %u\n", inode, fileblock);
uint32_t pos[4];
uint32_t level = 0;
ext2_calculate_block_pointer_pos(ext2, fileblock, &level, pos);
LTRACEF("level %d, pos 0x%x 0x%x 0x%x 0x%x\n", level, pos[0], pos[1], pos[2], pos[3]);
if (level == 0) {
/* direct block, just return it directly */
block = LE32(inode->i_block[fileblock]);
} else {
/* at least one level of indirection, get a pointer to the final indirect block table and dereference it */
blocknum_t *ind_table;
blocknum_t phys_block;
err = ext2_get_indirect_block_pointer_cache_block(ext2, inode, &ind_table, level, pos, &phys_block);
if (err < 0)
return 0;
/* dereference the final entry in the final table */
block = LE32(ind_table[pos[level]]);
LTRACEF("block %u, indirect_block %u\n", block, phys_block);
/* release the ref on the cache block */
ext2_put_block(ext2, phys_block);
}
LTRACEF("returning %u\n", block);
return block;
}
int ext2_read_inode(ext2_t *ext2, struct ext2_inode *inode, void *_buf, off_t offset, size_t len)
{
int err = 0;
int bytes_read = 0;
uint8_t *buf = _buf;
/* calculate the file size */
off_t file_size = ext2_file_len(ext2, inode);
LTRACEF("inode %p, offset %lld, len %zd, file_size %lld\n", inode, offset, len, file_size);
/* trim the read */
if (offset > file_size)
return 0;
if (offset + len >= file_size)
len = file_size - offset;
if (len == 0)
return 0;
/* calculate the starting file block */
uint file_block = offset / EXT2_BLOCK_SIZE(ext2->sb);
/* handle partial first block */
if ((offset % EXT2_BLOCK_SIZE(ext2->sb)) != 0) {
uint8_t temp[EXT2_BLOCK_SIZE(ext2->sb)];
/* calculate the block and read it */
blocknum_t phys_block = file_block_to_fs_block(ext2, inode, file_block);
if (phys_block == 0) {
memset(temp, 0, EXT2_BLOCK_SIZE(ext2->sb));
} else {
ext2_read_block(ext2, temp, phys_block);
}
/* copy out what we need */
size_t block_offset = offset % EXT2_BLOCK_SIZE(ext2->sb);
size_t tocopy = MIN(len, EXT2_BLOCK_SIZE(ext2->sb) - block_offset);
memcpy(buf, temp + block_offset, tocopy);
/* increment our stuff */
file_block++;
len -= tocopy;
bytes_read += tocopy;
buf += tocopy;
}
/* handle middle blocks */
while (len >= EXT2_BLOCK_SIZE(ext2->sb)) {
/* calculate the block and read it */
blocknum_t phys_block = file_block_to_fs_block(ext2, inode, file_block);
if (phys_block == 0) {
memset(buf, 0, EXT2_BLOCK_SIZE(ext2->sb));
} else {
ext2_read_block(ext2, buf, phys_block);
}
/* increment our stuff */
file_block++;
len -= EXT2_BLOCK_SIZE(ext2->sb);
bytes_read += EXT2_BLOCK_SIZE(ext2->sb);
buf += EXT2_BLOCK_SIZE(ext2->sb);
}
/* handle partial last block */
if (len > 0) {
uint8_t temp[EXT2_BLOCK_SIZE(ext2->sb)];
/* calculate the block and read it */
blocknum_t phys_block = file_block_to_fs_block(ext2, inode, file_block);
if (phys_block == 0) {
memset(temp, 0, EXT2_BLOCK_SIZE(ext2->sb));
} else {
ext2_read_block(ext2, temp, phys_block);
}
/* copy out what we need */
memcpy(buf, temp, len);
/* increment our stuff */
bytes_read += len;
}
LTRACEF("err %d, bytes_read %d\n", err, bytes_read);
return (err < 0) ? err : bytes_read;
}