hos/kernel/fs/ext2.c

457 lines
15 KiB
C

// ext2.c
// Author: Josh Holtrop
// Date: 08/22/04
// Modified: 12/24/04
#include "hos_defines.h"
#include "fs/devices.h"
#include "kout.h"
#include "ext2.h"
#include "mm/vmm.h"
#include "fs/vfs.h"
#include "lang/lang.h"
#include "functions.h"
/* Turning an inode number into a (group, inode_index) pair:
* group = (inode - 1) / s_inodes_per_group
* index = (inode - 1) % s_inodes_per_group
*/
vfs_fs_t ext2_driver = {ext2_mount_super, ext2_umount_super, ext2_stat,
ext2__open_dir, ext2__read_dir, ext2__close_dir,
ext2__open_file, ext2__read_file, ext2__close_file,
ext2__open_block_file, ext2__read_block_file, ext2__close_block_file};
// initialize the filesystem driver
int ext2_init(int fsID)
{
vfs_fs_t *fs;
if (( fs = New(vfs_fs_t) )) // give the VFS system our FS structure
{
*fs = ext2_driver;
vfs_register_fs(fsID, fs);
return 0;
}
return -1;
}
// mount the superblock of the filesystem and return a pointer to it, if valid
void *ext2_mount_super(major_t major, minor_t minor)
{
ext2_super_block_t *super = kmalloc(1024);
block_read(major, minor, 2, 2, super);
if (super->s_magic != EXT2_MAGIC) // not an ext2 filesystem
{
kfree(super);
return NULL;
}
return super;
}
// called when we are unmounting this filesystem mount
int ext2_umount_super(vfs_mount_t *mount)
{
return kfree(mount->super); // free memory that the superblock was taking
}
// stat a file, return a structure of info about it
int ext2_stat(vfs_mount_t *mount, char *file, vfs_stat_t *stat)
{
u32_t inode_number = ext2_get_inode_number(mount, file);
if (!inode_number)
return -1;
ext2_inode_t inode = ext2_get_inode(mount, inode_number);
switch(inode.i_mode & EXT2_I_MODE_TYPE_MASK)
{
case EXT2_I_MODE_FIFO: stat->type = VFS_FT_FIFO; break;
case EXT2_I_MODE_CHAR: stat->type = VFS_FT_CHAR; break;
case EXT2_I_MODE_DIR: stat->type = VFS_FT_DIR; break;
case EXT2_I_MODE_BLOCK: stat->type = VFS_FT_BLOCK; break;
case EXT2_I_MODE_FILE: stat->type = VFS_FT_FILE; break;
case EXT2_I_MODE_SYM: stat->type = VFS_FT_SYMLINK; break;
case EXT2_I_MODE_SOCK: stat->type = VFS_FT_SOCK; break;
default: stat->type = VFS_FT_UNKNOWN; break;
}
stat->size = inode.i_size;
stat->inode = inode_number;
stat->permissions = inode.i_mode & EXT2_I_MODE_ATTR_MASK;
stat->uid = inode.i_uid;
stat->gid = inode.i_gid;
stat->atime = inode.i_atime;
stat->mtime = inode.i_mtime;
stat->ctime = inode.i_ctime;
stat->links = inode.i_links_count;
return 0;
}
// returns the inode number that the given file name points to
u32_t ext2_get_inode_number(vfs_mount_t *mount, char *file)
{
if (file[0] != '/')
return 0;
if (strlen(file) == 1)
return 2; // root inode number
char *fil = kmalloc(strlen(file));
strcpy(fil, file + 1);
int subs = str_split(fil, '/'); // how many levels are there to do lookups on?
u32_t on_dir = 2; // start on the root directory
char *lookupName = fil;
ext2_dir_entry_t dentry;
while (subs--)
{
if (ext2_dir_lookup(mount, on_dir, lookupName, &dentry))
{
kfree(fil);
return 0; // entry not found
}
on_dir = dentry.inode;
lookupName = str_advance(lookupName);
}
kfree(fil);
return on_dir;
}
// lookup a file name in a directory and store the directory entry for it
int ext2_dir_lookup(vfs_mount_t *mount, u32_t dir_inode, char *fileName, ext2_dir_entry_t *direntry)
{
ext2_open_dir_t *dir = ext2_open_dir(mount, dir_inode);
if (!dir)
return -1; // bad directory inode number
ext2_dir_entry_t dentry;
while (!ext2_dir_read_entry(mount, dir, &dentry))
{
char *dentryName = kcalloc(1, dentry.name_length + 1);
memcpy(dentryName, dentry.name, dentry.name_length);
int res = strcmp(fileName, dentryName);
kfree(dentryName);
if (!res)
{
*direntry = dentry;
ext2_close_dir(mount, dir);
return 0;
}
}
ext2_close_dir(mount, dir);
return -2;
}
// open a directory by inode number for reading
ext2_open_dir_t *ext2_open_dir(vfs_mount_t *mount, u32_t inode_number)
{
ext2_open_dir_t *open_dir = New(ext2_open_dir_t);
ext2_open_inode_t *open_inode = ext2_open_inode(mount, inode_number);
if (!open_inode || ((open_inode->inode.i_mode & EXT2_I_MODE_TYPE_MASK) != EXT2_I_MODE_DIR))
{
kfree(open_dir);
return NULL;
}
open_dir->open_inode = open_inode;
open_dir->position = 0;
return open_dir;
}
int ext2_dir_read_entry(vfs_mount_t *mount, ext2_open_dir_t *open_dir, ext2_dir_entry_t *dentry)
{
ext2_super_block_t *super = mount->super;
if (open_dir->position >= open_dir->open_inode->inode.i_size)
return -1; // EOF
u32_t dir_block = open_dir->position >> (10 + super->s_log_block_size);
char *block = kmalloc(2048 << super->s_log_block_size);
ext2_inode_seek(mount, open_dir->open_inode, dir_block);
if (ext2_read_inode_block(mount, open_dir->open_inode, block))
ext2_read_inode_block(mount, open_dir->open_inode, block + (1024 << super->s_log_block_size));
ext2_dir_entry_t *dir_entry = (ext2_dir_entry_t *)(block + open_dir->position % (1024 << super->s_log_block_size));
if (!dir_entry->inode)
{
kfree(block);
return -2; // EOF
}
memcpy(dentry, dir_entry, min(dir_entry->length, sizeof(ext2_dir_entry_t)));
open_dir->position += dir_entry->length;
kfree(block);
return 0;
}
int ext2_close_dir(vfs_mount_t *mount, ext2_open_dir_t *open_dir)
{
ext2_close_inode(mount, open_dir->open_inode);
kfree(open_dir);
return 0;
}
// open an inode for reading
ext2_open_inode_t *ext2_open_inode(vfs_mount_t *mount, u32_t inode_number)
{
if (ext2_inode_status(mount, inode_number) != 1)
return NULL;
ext2_inode_t inode = ext2_get_inode(mount, inode_number);
ext2_open_inode_t *open_inode = New(ext2_open_inode_t);
open_inode->inode = inode;
open_inode->block = 0;
open_inode->block_pointers = NULL;
open_inode->block_pointers_start = 0;
mount->refs++;
return open_inode;
}
// seek to a certain block of an open inode
int ext2_inode_seek(vfs_mount_t *mount, ext2_open_inode_t *open_inode, u32_t block_number)
{
ext2_super_block_t *super = mount->super;
if (open_inode->inode.i_size <= (block_number << (10 + super->s_log_block_size)))
return -1; // at or past EOF
open_inode->block = block_number;
return 0;
}
// returns number of bytes read
int ext2_read_inode_block(vfs_mount_t *mount, ext2_open_inode_t *open_inode, void *block)
{
ext2_super_block_t *super = mount->super;
if (open_inode->inode.i_size <= (open_inode->block << (10 + super->s_log_block_size)))
return 0; // at or past EOF
u32_t leftover_bytes = open_inode->inode.i_size - (open_inode->block << (10 + super->s_log_block_size));
u32_t block_number = ext2_block_number(mount, open_inode);
block_read(mount->major, mount->minor, ext2_FSToDiskBlock(block_number, super),
2 << super->s_log_block_size, block);
open_inode->block++;
return min(leftover_bytes, 1024 << super->s_log_block_size);
}
// close an open inode
int ext2_close_inode(vfs_mount_t *mount, ext2_open_inode_t *open_inode)
{
mount->refs--;
if (open_inode->block_pointers)
kfree(open_inode->block_pointers); // free the block pointers cache
kfree(open_inode);
return 0;
}
// check the status of an inode
// -1: invalid inode number
// 0: free inode
// 1: allocated inode
int ext2_inode_status(vfs_mount_t *mount, u32_t inode_number)
{
ext2_super_block_t *super = mount->super;
if (inode_number < 1 || inode_number > super->s_inodes_count) // inode number invalid
return -1;
inode_number--; // turn inode_number into a 0-based index
u32_t group = inode_number / super->s_inodes_per_group;
u32_t index = inode_number % super->s_inodes_per_group;
u32_t inode_bitmap_block = ext2_get_group_desc(mount, group).bg_inode_bitmap + (index >> (13 + super->s_log_block_size));
u32_t bitmap_index = index % (8192 << super->s_log_block_size);
u8_t *inode_bitmap = kmalloc(1024 << super->s_log_block_size);
block_read(mount->major, mount->minor,
ext2_FSToDiskBlock(inode_bitmap_block, super), 2 << super->s_log_block_size, inode_bitmap);
int inode_status = (inode_bitmap[bitmap_index >> 3] >> (bitmap_index & 0x7)) & 1;
kfree(inode_bitmap);
return inode_status;
}
// check the status of a block
// -1: invalid block number
// 0: free block
// 1: allocated block
int ext2_block_status(vfs_mount_t *mount, u32_t block_number)
{
ext2_super_block_t *super = mount->super;
if (block_number < super->s_first_data_block || block_number > super->s_blocks_count) // block number invalid
return -1;
block_number -= super->s_first_data_block;
u32_t group = block_number / super->s_blocks_per_group;
u32_t index = block_number % super->s_blocks_per_group;
u32_t block_bitmap_block = ext2_get_group_desc(mount, group).bg_block_bitmap + (index >> (13 + super->s_log_block_size));
u32_t bitmap_index = index % (8192 << super->s_log_block_size);
u8_t *block_bitmap = kmalloc(1024 << super->s_log_block_size);
block_read(mount->major, mount->minor,
ext2_FSToDiskBlock(block_bitmap_block, super), 2 << super->s_log_block_size, block_bitmap);
int block_status = (block_bitmap[bitmap_index >> 3] >> (bitmap_index & 0x7)) & 1;
kfree(block_bitmap);
return block_status;
}
// transform open_inode->block (a relative block number) to an absolute block number for the filesystem
u32_t ext2_block_number(vfs_mount_t *mount, ext2_open_inode_t *open_inode)
{
if (open_inode->block < 12)
return open_inode->inode.i_block[open_inode->block];
ext2_super_block_t *super = mount->super;
int pointersPerBlock = 256 << super->s_log_block_size;
if (open_inode->block_pointers && // there is a block pointers cache block allocated
(open_inode->block >= open_inode->block_pointers_start) && // and the block number is in it
(open_inode->block < (open_inode->block_pointers_start + pointersPerBlock)))
return open_inode->block_pointers[open_inode->block - open_inode->block_pointers_start];
u32_t rel_block = open_inode->block - 12;
if (!open_inode->block_pointers)
open_inode->block_pointers = kmalloc(pointersPerBlock << 2);
if (rel_block < pointersPerBlock) // indirect block in i_block[12]
{
block_read(mount->major, mount->minor, ext2_FSToDiskBlock(open_inode->inode.i_block[12], super),
2 << super->s_log_block_size, open_inode->block_pointers);
open_inode->block_pointers_start = 12;
return open_inode->block_pointers[rel_block];
}
rel_block -= pointersPerBlock;
if (rel_block < (pointersPerBlock * pointersPerBlock)) // double-indirect block in i_block[13]
{
block_read(mount->major, mount->minor, ext2_FSToDiskBlock(open_inode->inode.i_block[13], super),
2 << super->s_log_block_size, open_inode->block_pointers);
u32_t real_block = open_inode->block_pointers[rel_block / pointersPerBlock];
block_read(mount->major, mount->minor, ext2_FSToDiskBlock(real_block, super),
2 << super->s_log_block_size, open_inode->block_pointers);
open_inode->block_pointers_start = 12 + pointersPerBlock + rel_block - (rel_block % pointersPerBlock);
return open_inode->block_pointers[rel_block % pointersPerBlock];
}
// this code shouldn't run unless we are dealing with a 65+mb file ...
rel_block -= pointersPerBlock * pointersPerBlock;
block_read(mount->major, mount->minor, ext2_FSToDiskBlock(open_inode->inode.i_block[14], super),
2 << super->s_log_block_size, open_inode->block_pointers);
u32_t index_1 = rel_block / (pointersPerBlock * pointersPerBlock);
u32_t leftover_1 = rel_block % (pointersPerBlock * pointersPerBlock);
u32_t block_1 = open_inode->block_pointers[index_1];
block_read(mount->major, mount->minor, ext2_FSToDiskBlock(block_1, super),
2 << super->s_log_block_size, open_inode->block_pointers);
u32_t index_2 = leftover_1 / pointersPerBlock;
u32_t leftover_2 = leftover_1 % pointersPerBlock;
u32_t block_2 = open_inode->block_pointers[index_2];
block_read(mount->major, mount->minor, ext2_FSToDiskBlock(block_2, super),
2 << super->s_log_block_size, open_inode->block_pointers);
open_inode->block_pointers_start = 12 + (pointersPerBlock + 1) * pointersPerBlock + rel_block - (rel_block % pointersPerBlock);
return open_inode->block_pointers[leftover_2];
}
// read the inode structure from the device and return it
ext2_inode_t ext2_get_inode(vfs_mount_t *mount, u32_t inode)
{
ext2_super_block_t *super = mount->super;
inode--; // turn inode into a 0-based index
u32_t group = inode / super->s_inodes_per_group;
u32_t index = inode % super->s_inodes_per_group;
u32_t inodeAddr = (ext2_get_group_desc(mount, group).bg_inode_table <<
(10 + super->s_log_block_size)) + (index << 7);
void *block = kmalloc(512);
block_read(mount->major, mount->minor, inodeAddr >> 9, 1, block);
ext2_inode_t in = *(ext2_inode_t *)(block + (inodeAddr & 0x1FF));
kfree(block);
return in;
}
// read the group descriptor structure from the device and return it
ext2_group_desc_t ext2_get_group_desc(vfs_mount_t *mount, u32_t group)
{
ext2_super_block_t *super = mount->super;
u32_t groupDescAddr = ((1 + super->s_first_data_block) << (10 + super->s_log_block_size)) + (group << 5);
void *block = kmalloc(512);
block_read(mount->major, mount->minor, groupDescAddr >> 9, 1, block);
ext2_group_desc_t gd = *(ext2_group_desc_t *)(block + (groupDescAddr & 0x1FF));
kfree(block);
return gd;
}
int ext2__open_dir(vfs_mount_t *mount, char *file, vfs_open_file_t *dir)
{
u32_t dir_inode = ext2_get_inode_number(mount, file);
if (!dir_inode)
return -1;
ext2_open_dir_t *open_dir = ext2_open_dir(mount, dir_inode);
if (!open_dir)
return -2;
dir->fs_data = open_dir;
return 0;
}
int ext2__read_dir(vfs_mount_t *mount, vfs_open_file_t *dir, vfs_dir_entry_t *dentry)
{
ext2_dir_entry_t t_dentry;
int status = ext2_dir_read_entry(mount, dir->fs_data, &t_dentry);
if (status)
return status;
memcpy(dentry->name, t_dentry.name, t_dentry.name_length);
dentry->name[t_dentry.name_length] = 0;
return 0;
}
int ext2__close_dir(vfs_mount_t *mount, vfs_open_file_t *dir)
{
return ext2_close_dir(mount, dir->fs_data);
}
int ext2__open_file(vfs_mount_t *mount, char *file, vfs_open_file_t *open_file)
{
}
int ext2__read_file(vfs_mount_t *mount, vfs_open_file_t *open_file)
{
}
int ext2__close_file(vfs_mount_t *mount, vfs_open_file_t *open_file)
{
}
int ext2__open_block_file(vfs_mount_t *mount, char *file, vfs_open_file_t *open_file)
{
u32_t file_inode = ext2_get_inode_number(mount, file);
if (!file_inode)
return -1;
ext2_open_inode_t *open_inode = ext2_open_inode(mount, file_inode);
if (!open_inode)
return -2;
ext2__open_block_file_t *open_block_file = New(ext2__open_block_file_t);
open_block_file->open_inode = open_inode;
open_block_file->block = 0;
open_file->fs_data = open_block_file;
return 0;
}
// returns the number of bytes read
int ext2__read_block_file(vfs_mount_t *mount, vfs_open_file_t *open_file, void *buffer)
{
ext2_super_block_t *super = mount->super;
ext2__open_block_file_t *open_block_file = open_file->fs_data;
if (ext2_inode_seek(mount, open_block_file->open_inode, ext2_diskToFSBlock(open_block_file->block, super)))
return 0; // EOF
u8_t *block = kmalloc(1024 << super->s_log_block_size);
u32_t file_position_read = (1024 << super->s_log_block_size) * open_block_file->open_inode->block;
u32_t file_position_want = open_block_file->block << 9;
u32_t data_offset = file_position_want - file_position_read;
int bytes_read = ext2_read_inode_block(mount, open_block_file->open_inode, block);
if (bytes_read <= data_offset)
{
kfree(block);
return 0; // EOF
}
memcpy(buffer, block + data_offset, 512);
kfree(block);
return min(512, bytes_read - data_offset);
}
int ext2__close_block_file(vfs_mount_t *mount, vfs_open_file_t *open_file)
{
ext2__open_block_file_t *open_block_file = open_file->fs_data;
ext2_close_inode(mount, open_block_file->open_inode);
kfree(open_block_file);
return 0;
}