// ext2.cpp // ext2 filesystem driver for HOS // Author: Josh Holtrop // Date: 05/10/05 // Modified: 05/10/05 /* example ext2 filesystem block group structure containing superblock and group descriptors: * block offset description #blocks * ------------ ----------- ------- * 0 superblock 1 * 1 group descriptors gd_blocks = ceil(ceil[s_blocks_count / s_blocks_per_group] / (1024 >> [5 - s_log_block_size])) * gd_blocks+1 block bitmap block 1 * gd_blocks+2 inode bitmap block 1 * gd_blocks+3 inode table itblocks = ceil[s_inodes_per_group / (1024 >> [7 - s_log_block_size])] * gd_blocks+3+itblocks data blocks s_blocks_per_group - gd_blocks - itblocks - 3 */ extern "C" { #include "display/kout.h" #include "mm/vmm.h" #include "lang/lang.h" } #include "ext2.h" #include "Ext2OpenDirectory.h" #include "Ext2OpenFile.h" /* initialize the ext2 filesystem driver */ int ext2_init() { vfs_register("ext2", ext2__mount_func); return 0; } /* a function to mount an ext2 filesystem and return a pointer to it */ FileSystem *ext2__mount_func(device_t dev) { ext2_super_block_t *super = (ext2_super_block_t *) New(ext2_super_block_t); if ( (block_read(DEV_MAJOR(dev), DEV_MINOR(dev), 2, 2, super) < 1024) || (super->s_magic != EXT2_MAGIC) ) { kfree(super); return NULL; } Ext2fs *fs = new Ext2fs(super, dev); kfree(super); return fs; } /* constructor for an actual ext2 filesystem object */ Ext2fs::Ext2fs(ext2_super_block_t *super, device_t dev) { myDevice = dev; memcpy(&mySuper, super, sizeof(ext2_super_block_t)); mySuperDirty = 0; myNumGroups = /* ceil[blocks_count / blocks_per_group] */ (mySuper.s_blocks_count + mySuper.s_blocks_per_group - 1) / mySuper.s_blocks_per_group; myGroupDescriptorsPerBlock = 1024 >> (5 - mySuper.s_log_block_size); myGroupDescriptorBlocks = /* ceil[num_groups / groupDescriptorsPerBlock] */ (myNumGroups + myGroupDescriptorsPerBlock - 1) / myGroupDescriptorsPerBlock; myInodesPerBlock = 1024 >> (7 - mySuper.s_log_block_size); myInodeTableBlocks = /* ceil[inodes_per_group / inodePerBlock] */ (mySuper.s_inodes_per_group + myInodesPerBlock - 1) / myInodesPerBlock; /* test/debug code */ OpenDirectory *od = openDirectory(2, VFS_MODE_WRITE); kprintf("TEST: %d\n", od->unlink("link")); kprintf("TEST: %d\n", od->create("test", VFS_FT_FILE, 0640, 0)); kprintf("TEST: %d\n", od->create("Block File baby", VFS_FT_BLOCK, 0666, 5 << 8 | 3)); kprintf("TEST: %d\n", od->create("Character-File", VFS_FT_CHAR, 0644, 130 << 8 | 42)); delete od; } /* this destructor destroys an ext2 filesystem that was mounted */ Ext2fs::~Ext2fs() { if (mySuperDirty) block_write(DEV_MAJOR(myDevice), DEV_MINOR(myDevice), 2, 2, &mySuper); } /* return the total number of 512-blocks on the filesystem */ u32_t Ext2fs::totalBlocks() { return mySuper.s_blocks_count >> (mySuper.s_log_block_size + 1); } /* return the number of free 512-blocks on the filesystem */ u32_t Ext2fs::freeBlocks() { return mySuper.s_free_blocks_count >> (mySuper.s_log_block_size + 1); } /* return the total number of inodes on the filesystem */ u32_t Ext2fs::totalInodes() { return mySuper.s_inodes_count; } /* return the free number of inodes on the filesystem */ u32_t Ext2fs::freeInodes() { return mySuper.s_free_inodes_count; } /* ext2 filesystems always have a root-directory inode number of 2 */ u32_t Ext2fs::getRootInodeNumber() { return 2; } /* open a directory */ OpenDirectory *Ext2fs::openDirectory(u32_t inum, int mode) { ext2_inode_t inode; readInode(inum, &inode); if ((inode.i_mode & EXT2_I_MODE_TYPE_MASK) == EXT2_I_MODE_DIR) return new Ext2OpenDirectory(this, inum, mode); return NULL; } /* stat an inode */ int Ext2fs::stat(u32_t inum, vfs_stat_t *buf) { ext2_inode_t inode; if (readInode(inum, &inode)) return -1; buf->dev = 0; switch(inode.i_mode & EXT2_I_MODE_TYPE_MASK) { case EXT2_I_MODE_FIFO: buf->type = VFS_FT_FIFO; break; case EXT2_I_MODE_CHAR: buf->type = VFS_FT_CHAR; buf->dev = inode.i_block[0]; break; case EXT2_I_MODE_DIR: buf->type = VFS_FT_DIR; break; case EXT2_I_MODE_BLOCK: buf->type = VFS_FT_BLOCK; buf->dev = inode.i_block[0]; break; case EXT2_I_MODE_FILE: buf->type = VFS_FT_FILE; break; case EXT2_I_MODE_SYM: buf->type = VFS_FT_SYMLINK; break; case EXT2_I_MODE_SOCK: buf->type = VFS_FT_SOCK; break; default: buf->type = VFS_FT_UNKNOWN; } buf->size = inode.i_size; buf->inode = inum; buf->permissions = inode.i_mode & EXT2_I_MODE_ATTR_MASK; buf->uid = inode.i_uid; buf->gid = inode.i_gid; buf->atime = inode.i_atime; buf->mtime = inode.i_mtime; buf->ctime = inode.i_ctime; buf->links = inode.i_links_count; return 0; } /* dereference a symbolink link */ int Ext2fs::link_deref(u32_t inum, char *buf) { ext2_inode_t inode; if (readInode(inum, &inode)) return -1; /* Couldn't read inode */ if ((inode.i_mode & EXT2_I_MODE_TYPE_MASK) != EXT2_I_MODE_SYM) return -2; if (inode.i_size > VFS_MAX_PATH_LENGTH) return -3; if (inode.i_size < 61) { memcpy(buf, inode.i_block, inode.i_size); buf[inode.i_size] = 0; return 0; } char *block = new char[1024 << mySuper.s_log_block_size]; readBlock(inode.i_block[0], block); memcpy(buf, block, inode.i_size); buf[inode.i_size] = 0; delete[] block; return 0; } /*************************** internal functions ***************************/ /* read an inode from the disk into memory */ int Ext2fs::readInode(u32_t inum, ext2_inode_t *buf) { if (inodeStatus(inum) != 1) return -1; inum--; /* turn inode number into a 0-based index */ u32_t group = inum / mySuper.s_inodes_per_group; u32_t index = inum % mySuper.s_inodes_per_group; ext2_group_desc_t group_desc; readGroupDescriptor(group, &group_desc); ext2_inode_t *inode_block = new ext2_inode_t[8 << mySuper.s_log_block_size]; readBlock(group_desc.bg_inode_table + (index >> (3 + mySuper.s_log_block_size)), inode_block); u32_t inode_block_index = index % (8 << mySuper.s_log_block_size); memcpy(buf, inode_block + inode_block_index, sizeof(ext2_inode_t)); delete[] inode_block; return 0; } /* write an inode from memory onto the disk */ int Ext2fs::writeInode(u32_t inum, ext2_inode_t *buf) { if (inodeStatus(inum) != 1) return -1; inum--; /* turn inode number into a 0-based index */ u32_t group = inum / mySuper.s_inodes_per_group; u32_t index = inum % mySuper.s_inodes_per_group; ext2_group_desc_t group_desc; readGroupDescriptor(group, &group_desc); ext2_inode_t *inode_block = new ext2_inode_t[8 << mySuper.s_log_block_size]; u32_t block_num = group_desc.bg_inode_table + (index >> (3 + mySuper.s_log_block_size)); readBlock(block_num, inode_block); u32_t inode_block_index = index % (8 << mySuper.s_log_block_size); memcpy(inode_block + inode_block_index, buf, sizeof(ext2_inode_t)); writeBlock(block_num, inode_block); delete[] inode_block; return 0; } /* read the group descriptor for a given group number */ int Ext2fs::readGroupDescriptor(u32_t group, ext2_group_desc_t *buf) { if (group >= myNumGroups) return -1; ext2_group_desc_t *gd_block = new ext2_group_desc_t[myGroupDescriptorsPerBlock]; readBlock(mySuper.s_first_data_block + 1 + (group / myGroupDescriptorsPerBlock), gd_block); memcpy(buf, gd_block + (group % myGroupDescriptorsPerBlock), sizeof(ext2_group_desc_t)); delete[] gd_block; return 0; } /* write the group descriptor for a given group number */ int Ext2fs::writeGroupDescriptor(u32_t group, ext2_group_desc_t *buf) { if (group >= myNumGroups) return -1; ext2_group_desc_t *gd_block = new ext2_group_desc_t[myGroupDescriptorsPerBlock]; u32_t block_num = mySuper.s_first_data_block + 1 + (group / myGroupDescriptorsPerBlock); readBlock(block_num, gd_block); memcpy(gd_block + (group % myGroupDescriptorsPerBlock), buf, sizeof(ext2_group_desc_t)); writeBlock(block_num, gd_block); delete[] gd_block; return 0; } /* read a data block from the block device */ int Ext2fs::readBlock(u32_t blockNum, void *buf) { return block_read(DEV_MAJOR(myDevice), DEV_MINOR(myDevice), blockNum << (mySuper.s_log_block_size + 1), 2 << mySuper.s_log_block_size, buf); } /* write a data block to the block device */ int Ext2fs::writeBlock(u32_t blockNum, void *buf) { return block_write(DEV_MAJOR(myDevice), DEV_MINOR(myDevice), blockNum << (mySuper.s_log_block_size + 1), 2 << mySuper.s_log_block_size, buf); } /* check the status of an inode (1-based inode number) * -1: invalid inode number * 0: free inode * 1: allocated (used) inode */ int Ext2fs::inodeStatus(u32_t inum) { if (inum < 1 || inum > mySuper.s_inodes_count) return -1; /* invalid inode number */ inum--; /* turn inode number into a 0-based index */ u32_t group = inum / mySuper.s_inodes_per_group; u32_t index = inum % mySuper.s_inodes_per_group; ext2_group_desc_t group_desc; readGroupDescriptor(group, &group_desc); u8_t *bitmap_block = new u8_t[1024 << mySuper.s_log_block_size]; readBlock(group_desc.bg_inode_bitmap + (index >> (13 + mySuper.s_log_block_size)), bitmap_block); u32_t bitmap_index = index % (8192 << mySuper.s_log_block_size); int inode_status = (bitmap_block[bitmap_index >> 3] >> (bitmap_index & 0x7)) & 1; delete[] bitmap_block; return inode_status; } /* check the status of a block * -1: invalid block number * 0: free block * 1: allocated (used) block */ int Ext2fs::blockStatus(u32_t bnum) { if (bnum < mySuper.s_first_data_block) return 1; if (bnum >= mySuper.s_blocks_count) return -1; /* invalid inode number */ bnum -= mySuper.s_first_data_block; u32_t group = bnum / mySuper.s_blocks_per_group; u32_t index = bnum % mySuper.s_blocks_per_group; ext2_group_desc_t group_desc; readGroupDescriptor(group, &group_desc); u8_t *bitmap_block = new u8_t[1024 << mySuper.s_log_block_size]; readBlock(group_desc.bg_block_bitmap + (index >> (13 + mySuper.s_log_block_size)), bitmap_block); u32_t bitmap_index = index % (8192 << mySuper.s_log_block_size); int block_status = (bitmap_block[bitmap_index >> 3] >> (bitmap_index & 0x7)) & 1; delete[] bitmap_block; return block_status; } /* allocate an inode, return number (0 on failure) */ u32_t Ext2fs::allocInode() { ext2_group_desc_t gd; for (unsigned int bg = 0; bg < myNumGroups; bg++) { readGroupDescriptor(bg, &gd); if (gd.bg_free_inodes_count) { u32_t index = allocFromBitmap(gd.bg_inode_bitmap, mySuper.s_inodes_per_group); if (index == 0xFFFFFFFF) return 0; gd.bg_free_inodes_count--; writeGroupDescriptor(bg, &gd); mySuper.s_free_inodes_count--; mySuperDirty = 1; return (bg * mySuper.s_inodes_per_group) + index + 1; } } return 0; } /* allocate a block, return number (0 on failure) */ u32_t Ext2fs::allocBlock() { ext2_group_desc_t gd; for (unsigned int bg = 0; bg < myNumGroups; bg++) { readGroupDescriptor(bg, &gd); if (gd.bg_free_blocks_count) { u32_t index = allocFromBitmap(gd.bg_block_bitmap, mySuper.s_blocks_per_group); if (index == 0xFFFFFFFF) return 0; gd.bg_free_blocks_count--; writeGroupDescriptor(bg, &gd); mySuper.s_free_blocks_count--; mySuperDirty = 1; return (bg * mySuper.s_blocks_per_group) + index + mySuper.s_first_data_block; } } return 0; } /* free an inode, return error code */ int Ext2fs::freeInode(u32_t inum) { if (inum < 11 || inum > mySuper.s_inodes_count) return -1; if (inodeStatus(inum) != 1) return -2; inum--; /* now 0-based */ ext2_group_desc_t gd; u32_t group_num = inum / mySuper.s_inodes_per_group; readGroupDescriptor(group_num, &gd); freeFromBitmap(gd.bg_inode_bitmap, inum % mySuper.s_inodes_per_group); gd.bg_free_inodes_count++; mySuper.s_free_inodes_count++; mySuperDirty = 1; writeGroupDescriptor(group_num, &gd); return 0; } /* free a block, return error code */ int Ext2fs::freeBlock(u32_t bnum) { if (bnum < mySuper.s_first_data_block || bnum >= mySuper.s_blocks_count) return -1; if (blockStatus(bnum) != 1) return -2; bnum -= mySuper.s_first_data_block; /* now 0-based */ ext2_group_desc_t gd; u32_t group_num = bnum / mySuper.s_blocks_per_group; readGroupDescriptor(group_num, &gd); freeFromBitmap(gd.bg_block_bitmap, bnum % mySuper.s_blocks_per_group); gd.bg_free_blocks_count++; mySuper.s_free_blocks_count++; mySuperDirty = 1; writeGroupDescriptor(group_num, &gd); return 0; } /* allocate a node from a bitmap, return the index, 0xFFFFFFFF on failure */ u32_t Ext2fs::allocFromBitmap(u32_t blockNum, u32_t maxBits) { u8_t *block = new u8_t[1024 << mySuper.s_log_block_size]; u8_t *blockPtr = block; u32_t maxBytes = (maxBits + 7) >> 3; readBlock(blockNum, block); for (u32_t idx = 0; idx < maxBytes; idx++) { if (*blockPtr != 0xFF) { for (u32_t bit = 0; bit < 8; bit++) { if ( !((*blockPtr) & (1 << bit)) ) { u32_t nodeIndex = (idx << 3) + bit; if (nodeIndex < maxBits) { *blockPtr |= (1 << bit); writeBlock(blockNum, block); return nodeIndex; } return 0xFFFFFFFF; } } break; } blockPtr++; } delete[] block; return 0xFFFFFFFF; } /* free a node from a bitmap */ void Ext2fs::freeFromBitmap(u32_t blockNum, u32_t node) { u8_t *block = new u8_t[1024 << mySuper.s_log_block_size]; readBlock(blockNum, block); u8_t *blockPtr = block + (node >> 3); *blockPtr &= ((1 << (node & 0x7)) ^ 0xFF); writeBlock(blockNum, block); delete[] block; } /* this function is called when a file is opened for writing and truncates * the inode. the function notifies any file handles open on this inode that * the inode was truncated */ void Ext2fs::notifyTruncatedFiles(u32_t inum) { // TODO: notify open files } /* unlink an inode: * decrement i_links_count * if it is zero, free the inode * if not, write the inode back */ void Ext2fs::unlink(u32_t inum) { ext2_inode_t inode; readInode(inum, &inode); inode.i_links_count--; if (inode.i_links_count) { writeInode(inum, &inode); return; } freeBlocksFromInode(inum); freeInode(inum); } int Ext2fs::attemptRemoveDir(u32_t inum) { vfs_dir_entry_t dentry, dentry2; OpenDirectory *od = openDirectory(inum, VFS_MODE_READ); if (od->read(&dentry)) { delete od; return -1; } if (od->read(&dentry2)) { delete od; return -1; } if (!od->read(&dentry)) { delete od; return -1; } delete od; unlink(dentry.inode); unlink(dentry2.inode); } void Ext2fs::freeBlocksFromInode(u32_t inum) { }