270 lines
7.5 KiB
C
270 lines
7.5 KiB
C
/*
|
|
* 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;
|
|
}
|
|
|