3 * - By John Hodge (thePowersGang)
\r
9 #define VERSION VER2(0,90)
\r
10 #include "ext2_common.h"
\r
11 #include <modules.h>
\r
13 #define MIN_BLOCKS_PER_GROUP 2
\r
14 #define MAX_BLOCK_LOG_SIZE 10 // 1024 << 10 = 1MiB
\r
16 // === PROTOTYPES ===
\r
17 int Ext2_Install(char **Arguments);
\r
18 int Ext2_Cleanup(void);
\r
19 // - Interface Functions
\r
20 int Ext2_Detect(int FD);
\r
21 tVFS_Node *Ext2_InitDevice(const char *Device, const char **Options);
\r
22 void Ext2_Unmount(tVFS_Node *Node);
\r
23 void Ext2_CloseFile(tVFS_Node *Node);
\r
24 tVFS_Node *Ext2_GetNodeFromINode(tVFS_Node *RootNode, Uint64 Inode);
\r
25 // - Internal Helpers
\r
26 int Ext2_int_GetInode(tVFS_Node *Node, tExt2_Inode *Inode);
\r
27 void Ext2_int_DumpInode(tExt2_Disk *Disk, Uint32 InodeID, tExt2_Inode *Inode);
\r
28 Uint64 Ext2_int_GetBlockAddr(tExt2_Disk *Disk, Uint32 *Blocks, int BlockNum);
\r
29 Uint32 Ext2_int_AllocateInode(tExt2_Disk *Disk, Uint32 Parent);
\r
30 void Ext2_int_DereferenceInode(tExt2_Disk *Disk, Uint32 Inode);
\r
31 void Ext2_int_UpdateSuperblock(tExt2_Disk *Disk);
\r
33 // === SEMI-GLOBALS ===
\r
34 MODULE_DEFINE(0, VERSION, FS_Ext2, Ext2_Install, Ext2_Cleanup);
\r
35 tExt2_Disk gExt2_disks[6];
\r
36 int giExt2_count = 0;
\r
37 tVFS_Driver gExt2_FSInfo = {
\r
39 .Detect = Ext2_Detect,
\r
40 .InitDevice = Ext2_InitDevice,
\r
41 .Unmount = Ext2_Unmount,
\r
42 .GetNodeFromINode = Ext2_GetNodeFromINode
\r
47 * \fn int Ext2_Install(char **Arguments)
\r
48 * \brief Install the Ext2 Filesystem Driver
\r
50 int Ext2_Install(char **Arguments)
\r
52 VFS_AddDriver( &gExt2_FSInfo );
\r
53 return MODULE_ERR_OK;
\r
57 * \brief Clean up driver state before unload
\r
59 int Ext2_Cleanup(void)
\r
65 * Detect if a volume is Ext2 formatted
\r
67 int Ext2_Detect(int FD)
\r
69 tExt2_SuperBlock sb;
\r
72 len = VFS_ReadAt(FD, 1024, 1024, &sb);
\r
75 Log_Debug("Ext2", "_Detect: Read failed? (0x%x != 1024)", len);
\r
84 Log_Debug("Ext2", "_Detect: s_magic = 0x%x", sb.s_magic);
\r
90 \brief Initializes a device to be read by by the driver
\r
91 \param Device String - Device to read from
\r
92 \param Options NULL Terminated array of option strings
\r
95 tVFS_Node *Ext2_InitDevice(const char *Device, const char **Options)
\r
97 tExt2_Disk *disk = NULL;
\r
100 tExt2_SuperBlock sb;
\r
102 ENTER("sDevice pOptions", Device, Options);
\r
105 fd = VFS_Open(Device, VFS_OPENFLAG_READ|VFS_OPENFLAG_WRITE); //Open Device
\r
107 Log_Warning("EXT2", "Unable to open '%s'", Device);
\r
112 // Read Superblock at offset 1024
\r
113 VFS_ReadAt(fd, 1024, 1024, &sb); // Read Superblock
\r
115 // Sanity Check Magic value
\r
116 if(sb.s_magic != 0xEF53) {
\r
117 Log_Warning("EXT2", "Volume '%s' is not an EXT2 volume (0x%x != 0xEF53)",
\r
118 Device, sb.s_magic);
\r
122 if( sb.s_blocks_per_group < MIN_BLOCKS_PER_GROUP ) {
\r
123 Log_Warning("Ext2", "Blocks per group is too small (%i < %i)",
\r
124 sb.s_blocks_per_group, MIN_BLOCKS_PER_GROUP);
\r
129 groupCount = DivUp(sb.s_blocks_count, sb.s_blocks_per_group);
\r
130 LOG("groupCount = %i", groupCount);
\r
132 // Allocate Disk Information
\r
133 disk = malloc(sizeof(tExt2_Disk) + sizeof(tExt2_Group)*groupCount);
\r
135 Log_Warning("EXT2", "Unable to allocate disk structure");
\r
139 memcpy(&disk->SuperBlock, &sb, 1024);
\r
140 disk->GroupCount = groupCount;
\r
142 // Get an inode cache handle
\r
143 disk->CacheID = Inode_GetHandle(NULL);
\r
146 if( sb.s_log_block_size > MAX_BLOCK_LOG_SIZE ) {
\r
147 Log_Warning("Ext2", "Block size (log2) too large (%i > %i)",
\r
148 sb.s_log_block_size, MAX_BLOCK_LOG_SIZE);
\r
151 disk->BlockSize = 1024 << sb.s_log_block_size;
\r
152 LOG("Disk->BlockSie = 0x%x (1024 << %i)", disk->BlockSize, sb.s_log_block_size);
\r
154 // Read Group Information
\r
155 LOG("sb,s_first_data_block = %x", sb.s_first_data_block);
\r
158 sb.s_first_data_block * disk->BlockSize + 1024,
\r
159 sizeof(tExt2_Group)*groupCount,
\r
163 LOG("Block Group 0");
\r
164 LOG(".bg_block_bitmap = 0x%x", disk->Groups[0].bg_block_bitmap);
\r
165 LOG(".bg_inode_bitmap = 0x%x", disk->Groups[0].bg_inode_bitmap);
\r
166 LOG(".bg_inode_table = 0x%x", disk->Groups[0].bg_inode_table);
\r
167 LOG("Block Group 1");
\r
168 LOG(".bg_block_bitmap = 0x%x", disk->Groups[1].bg_block_bitmap);
\r
169 LOG(".bg_inode_bitmap = 0x%x", disk->Groups[1].bg_inode_bitmap);
\r
170 LOG(".bg_inode_table = 0x%x", disk->Groups[1].bg_inode_table);
\r
173 Ext2_int_ReadInode(disk, 2, &disk->RootInode);
\r
175 // Create Root Node
\r
176 memset(&disk->RootNode, 0, sizeof(tVFS_Node));
\r
177 disk->RootNode.Inode = 2; // Root inode ID
\r
178 disk->RootNode.ImplPtr = disk; // Save disk pointer
\r
179 disk->RootNode.Size = -1; // Fill in later (on readdir)
\r
180 disk->RootNode.Flags = VFS_FFLAG_DIRECTORY;
\r
182 disk->RootNode.Type = &gExt2_DirType;
\r
184 // Complete root node
\r
185 disk->RootNode.UID = disk->RootInode.i_uid;
\r
186 disk->RootNode.GID = disk->RootInode.i_gid;
\r
187 disk->RootNode.NumACLs = 1;
\r
188 disk->RootNode.ACLs = &gVFS_ACL_EveryoneRW;
\r
191 LOG("inode.i_size = 0x%x", disk->RootInode.i_size);
\r
192 LOG("inode.i_block[0] = 0x%x", disk->RootInode.i_block[0]);
\r
195 LEAVE('p', &disk->RootNode);
\r
196 return &disk->RootNode;
\r
206 * \fn void Ext2_Unmount(tVFS_Node *Node)
\r
207 * \brief Close a mounted device
\r
209 void Ext2_Unmount(tVFS_Node *Node)
\r
211 tExt2_Disk *disk = Node->ImplPtr;
\r
213 VFS_Close( disk->FD );
\r
214 Inode_ClearCache( disk->CacheID );
\r
215 memset(disk, 0, sizeof(tExt2_Disk)+disk->GroupCount*sizeof(tExt2_Group));
\r
220 * \fn void Ext2_CloseFile(tVFS_Node *Node)
\r
221 * \brief Close a file (Remove it from the cache)
\r
223 void Ext2_CloseFile(tVFS_Node *Node)
\r
225 tExt2_Disk *disk = Node->ImplPtr;
\r
226 ENTER("pNode", Node);
\r
228 if( Mutex_Acquire(&Node->Lock) != 0 )
\r
234 if( Node->Flags & VFS_FFLAG_DIRTY )
\r
237 Ext2_int_WritebackNode(disk, Node);
\r
238 Node->Flags &= ~VFS_FFLAG_DIRTY;
\r
241 int was_not_referenced = (Node->ImplInt == 0);
\r
242 tVFS_ACL *acls = Node->ACLs;
\r
243 if( Inode_UncacheNode(disk->CacheID, Node->Inode) == 1 )
\r
245 if( was_not_referenced )
\r
247 LOG("Removng inode");
\r
249 Log_Warning("Ext2", "TODO: Remove inode when not referenced (%x)", (Uint32)Node->Inode);
\r
251 if( acls != &gVFS_ACL_EveryoneRW ) {
\r
254 LOG("Node cleaned");
\r
257 LOG("Still referenced, releasing lock");
\r
258 Mutex_Release(&Node->Lock);
\r
264 tVFS_Node *Ext2_GetNodeFromINode(tVFS_Node *RootNode, Uint64 Inode)
\r
266 return Ext2_int_CreateNode(RootNode->ImplPtr, Inode);
\r
269 //==================================
\r
270 //= INTERNAL FUNCTIONS =
\r
271 //==================================
\r
273 * \fn int Ext2_int_ReadInode(tExt2_Disk *Disk, Uint InodeId, tExt2_Inode *Inode)
\r
274 * \brief Read an inode into memory
\r
276 int Ext2_int_ReadInode(tExt2_Disk *Disk, Uint32 InodeId, tExt2_Inode *Inode)
\r
280 ENTER("pDisk iInodeId pInode", Disk, InodeId, Inode);
\r
282 if(InodeId == 0) return 0;
\r
284 InodeId --; // Inodes are numbered starting at 1
\r
286 group = InodeId / Disk->SuperBlock.s_inodes_per_group;
\r
287 subId = InodeId % Disk->SuperBlock.s_inodes_per_group;
\r
289 LOG("group=%i, subId = %i", group, subId);
\r
292 VFS_ReadAt(Disk->FD,
\r
293 Disk->Groups[group].bg_inode_table * Disk->BlockSize + sizeof(tExt2_Inode)*subId,
\r
294 sizeof(tExt2_Inode),
\r
302 * \brief Write a modified inode out to disk
\r
304 int Ext2_int_WriteInode(tExt2_Disk *Disk, Uint32 InodeId, tExt2_Inode *Inode)
\r
307 ENTER("pDisk iInodeId pInode", Disk, InodeId, Inode);
\r
314 Ext2_int_DumpInode(Disk, InodeId, Inode);
\r
316 InodeId --; // Inodes are numbered starting at 1
\r
318 group = InodeId / Disk->SuperBlock.s_inodes_per_group;
\r
319 subId = InodeId % Disk->SuperBlock.s_inodes_per_group;
\r
321 LOG("group=%i, subId = %i", group, subId);
\r
324 VFS_WriteAt(Disk->FD,
\r
325 Disk->Groups[group].bg_inode_table * Disk->BlockSize + sizeof(tExt2_Inode)*subId,
\r
326 sizeof(tExt2_Inode),
\r
335 * \fn vfs_node *Ext2_int_CreateNode(tExt2_Disk *Disk, Uint InodeID)
\r
336 * \brief Create a new VFS Node
\r
338 tVFS_Node *Ext2_int_CreateNode(tExt2_Disk *Disk, Uint InodeID)
\r
344 tVFS_Node *node = &data.retNode;
\r
345 tExt2_Inode *in = &data.inode;
\r
347 if( !Ext2_int_ReadInode(Disk, InodeID, &data.inode) )
\r
350 if( (node = Inode_GetCache(Disk->CacheID, InodeID)) )
\r
352 node = &data.retNode;
\r
354 memset(node, 0, sizeof(*node));
\r
357 node->Inode = InodeID;
\r
358 node->ImplPtr = Disk;
\r
359 node->ImplInt = in->i_links_count;
\r
360 if( in->i_links_count == 0 ) {
\r
361 Log_Notice("Ext2", "Inode %p:%x is not referenced, bug?", Disk, InodeID);
\r
365 node->Size = in->i_size;
\r
367 // Set Access Permissions
\r
368 node->UID = in->i_uid;
\r
369 node->GID = in->i_gid;
\r
371 node->ACLs = VFS_UnixToAcessACL(in->i_mode & 0777, in->i_uid, in->i_gid);
\r
373 // Set Function Pointers
\r
374 node->Type = &gExt2_FileType;
\r
376 switch(in->i_mode & EXT2_S_IFMT)
\r
380 node->Flags = VFS_FFLAG_SYMLINK;
\r
385 node->Size |= (Uint64)in->i_dir_acl << 32;
\r
389 node->Type = &gExt2_DirType;
\r
390 node->Flags = VFS_FFLAG_DIRECTORY;
\r
391 node->Data = calloc( sizeof(Uint16), DivUp(node->Size, Disk->BlockSize) );
\r
393 // Unknown, Write protect it to be safe
\r
395 node->Flags = VFS_FFLAG_READONLY;
\r
400 node->ATime = in->i_atime * 1000;
\r
401 node->MTime = in->i_mtime * 1000;
\r
402 node->CTime = in->i_ctime * 1000;
\r
404 // Save in node cache and return saved node
\r
405 return Inode_CacheNodeEx(Disk->CacheID, &data.retNode, sizeof(data));
\r
408 int Ext2_int_WritebackNode(tExt2_Disk *Disk, tVFS_Node *Node)
\r
410 tExt2_Inode *inode = (void*)(Node+1);
\r
412 if( Disk != Node->ImplPtr ) {
\r
413 Log_Error("Ext2", "Ext2_int_WritebackNode - Disk != Node->ImplPtr");
\r
417 if( Node->Flags & VFS_FFLAG_SYMLINK ) {
\r
418 inode->i_mode = EXT2_S_IFLNK;
\r
420 else if( Node->Flags & VFS_FFLAG_DIRECTORY ) {
\r
421 inode->i_mode = EXT2_S_IFDIR;
\r
423 else if( Node->Flags & VFS_FFLAG_READONLY ) {
\r
424 Log_Notice("Ext2", "Not writing back readonly inode %p:%x", Disk, Node->Inode);
\r
428 inode->i_mode = EXT2_S_IFREG;
\r
429 inode->i_dir_acl = Node->Size >> 32;
\r
432 inode->i_size = Node->Size & 0xFFFFFFFF;
\r
433 inode->i_links_count = Node->ImplInt;
\r
435 inode->i_uid = Node->UID;
\r
436 inode->i_gid = Node->GID;
\r
438 inode->i_atime = Node->ATime / 1000;
\r
439 inode->i_mtime = Node->MTime / 1000;
\r
440 inode->i_ctime = Node->CTime / 1000;
\r
442 // TODO: Compact ACLs into unix mode
\r
443 Log_Warning("Ext2", "TODO: Support converting Acess ACLs into unix modes");
\r
444 inode->i_mode |= 777;
\r
446 Ext2_int_WriteInode(Disk, Node->Inode, inode);
\r
451 void Ext2_int_DumpInode(tExt2_Disk *Disk, Uint32 InodeID, tExt2_Inode *Inode)
\r
453 LOG("%p[Inode %i] = {", Disk, InodeID);
\r
454 LOG(" .i_mode = 0%04o", Inode->i_mode);
\r
455 LOG(" .i_uid:i_gid = %i:%i", Inode->i_uid, Inode->i_gid);
\r
456 LOG(" .i_size = 0x%x", Inode->i_size);
\r
457 LOG(" .i_block[0:3] = {0x%x,0x%x,0x%x,0x%x}",
\r
458 Inode->i_block[0], Inode->i_block[1], Inode->i_block[2], Inode->i_block[3]);
\r
459 LOG(" .i_block[4:7] = {0x%x,0x%x,0x%x,0x%x}",
\r
460 Inode->i_block[4], Inode->i_block[5], Inode->i_block[6], Inode->i_block[7]);
\r
461 LOG(" .i_block[8:11] = {0x%x,0x%x,0x%x,0x%x}",
\r
462 Inode->i_block[8], Inode->i_block[6], Inode->i_block[10], Inode->i_block[11]);
\r
463 LOG(" .i_block[12:14] = {0x%x,0x%x,0x%x}",
\r
464 Inode->i_block[12], Inode->i_block[13], Inode->i_block[14]);
\r
469 * \fn Uint64 Ext2_int_GetBlockAddr(tExt2_Disk *Disk, Uint32 *Blocks, int BlockNum)
\r
470 * \brief Get the address of a block from an inode's list
\r
471 * \param Disk Disk information structure
\r
472 * \param Blocks Pointer to an inode's block list
\r
473 * \param BlockNum Block index in list
\r
475 Uint64 Ext2_int_GetBlockAddr(tExt2_Disk *Disk, Uint32 *Blocks, int BlockNum)
\r
478 int dwPerBlock = Disk->BlockSize / 4;
\r
482 return (Uint64)Blocks[BlockNum] * Disk->BlockSize;
\r
484 // Single Indirect Blocks
\r
485 iBlocks = malloc( Disk->BlockSize );
\r
486 VFS_ReadAt(Disk->FD, (Uint64)Blocks[12]*Disk->BlockSize, Disk->BlockSize, iBlocks);
\r
489 if(BlockNum < dwPerBlock)
\r
491 BlockNum = iBlocks[BlockNum];
\r
493 return (Uint64)BlockNum * Disk->BlockSize;
\r
496 BlockNum -= dwPerBlock;
\r
497 // Double Indirect Blocks
\r
498 if(BlockNum < dwPerBlock*dwPerBlock)
\r
500 VFS_ReadAt(Disk->FD, (Uint64)Blocks[13]*Disk->BlockSize, Disk->BlockSize, iBlocks);
\r
501 VFS_ReadAt(Disk->FD, (Uint64)iBlocks[BlockNum/dwPerBlock]*Disk->BlockSize, Disk->BlockSize, iBlocks);
\r
502 BlockNum = iBlocks[BlockNum%dwPerBlock];
\r
504 return (Uint64)BlockNum * Disk->BlockSize;
\r
507 BlockNum -= dwPerBlock*dwPerBlock;
\r
508 // Triple Indirect Blocks
\r
509 VFS_ReadAt(Disk->FD, (Uint64)Blocks[14]*Disk->BlockSize, Disk->BlockSize, iBlocks);
\r
510 VFS_ReadAt(Disk->FD, (Uint64)iBlocks[BlockNum/(dwPerBlock*dwPerBlock)]*Disk->BlockSize, Disk->BlockSize, iBlocks);
\r
511 VFS_ReadAt(Disk->FD, (Uint64)iBlocks[(BlockNum/dwPerBlock)%dwPerBlock]*Disk->BlockSize, Disk->BlockSize, iBlocks);
\r
512 BlockNum = iBlocks[BlockNum%dwPerBlock];
\r
514 return (Uint64)BlockNum * Disk->BlockSize;
\r
518 * \fn Uint32 Ext2_int_AllocateInode(tExt2_Disk *Disk, Uint32 Parent)
\r
519 * \brief Allocate an inode (from the current group preferably)
\r
520 * \param Disk EXT2 Disk Information Structure
\r
521 * \param Parent Inode ID of the parent (used to locate the child nearby)
\r
523 Uint32 Ext2_int_AllocateInode(tExt2_Disk *Disk, Uint32 Parent)
\r
525 Uint start_group = (Parent - 1) / Disk->SuperBlock.s_inodes_per_group;
\r
526 Uint group = start_group;
\r
528 if( Disk->SuperBlock.s_free_inodes_count == 0 )
\r
530 Log_Notice("Ext2", "Ext2_int_AllocateInode - Out of inodes on %p", Disk);
\r
534 while( group < Disk->GroupCount && Disk->Groups[group].bg_free_inodes_count == 0 )
\r
536 if( group == Disk->GroupCount )
\r
539 while( group < start_group && Disk->Groups[group].bg_free_inodes_count == 0 )
\r
543 if( Disk->Groups[group].bg_free_inodes_count == 0 )
\r
545 Log_Notice("Ext2", "Ext2_int_AllocateInode - Out of inodes on %p, but superblock says some free", Disk);
\r
549 // Load bitmap for group
\r
550 // (s_inodes_per_group / 8) bytes worth
\r
551 // - Allocate a buffer the size of a sector/block
\r
552 // - Read in part of the bitmap
\r
553 // - Search for a free inode
\r
554 tExt2_Group *bg = &Disk->Groups[group];
\r
557 const int sector_size = 512;
\r
558 Uint8 buf[sector_size];
\r
559 VFS_ReadAt(Disk->FD, Disk->BlockSize*bg->bg_inode_bitmap+ofs, sector_size, buf);
\r
562 for( byte = 0; byte < sector_size && buf[byte] == 0xFF; byte ++ )
\r
564 if( byte < sector_size )
\r
566 for( bit = 0; bit < 8 && buf[byte] & (1 << bit); bit ++)
\r
569 buf[byte] |= 1 << bit;
\r
570 VFS_WriteAt(Disk->FD, Disk->BlockSize*bg->bg_inode_bitmap+ofs, sector_size, buf);
\r
572 bg->bg_free_inodes_count --;
\r
573 Disk->SuperBlock.s_free_inodes_count --;
\r
575 Uint32 ret = group * Disk->SuperBlock.s_inodes_per_group + byte * 8 + bit + 1;
\r
576 Log_Debug("Ext2", "Ext2_int_AllocateInode - Allocated 0x%x", ret);
\r
580 ofs += sector_size;
\r
581 } while(ofs < Disk->SuperBlock.s_inodes_per_group / 8);
\r
583 Log_Notice("Ext2", "Ext2_int_AllocateInode - Out of inodes in group %p:%i but header reported free",
\r
590 * \brief Reduce the reference count on an inode
\r
592 void Ext2_int_DereferenceInode(tExt2_Disk *Disk, Uint32 Inode)
\r
594 Log_Warning("Ext2", "TODO: Impliment Ext2_int_DereferenceInode");
\r
598 * \fn void Ext2_int_UpdateSuperblock(tExt2_Disk *Disk)
\r
599 * \brief Updates the superblock
\r
601 void Ext2_int_UpdateSuperblock(tExt2_Disk *Disk)
\r
603 int bpg = Disk->SuperBlock.s_blocks_per_group;
\r
604 int ngrp = Disk->SuperBlock.s_blocks_count / bpg;
\r
608 VFS_WriteAt(Disk->FD, 1024, 1024, &Disk->SuperBlock);
\r
610 // - Update block groups while we're at it
\r
613 Disk->SuperBlock.s_first_data_block * Disk->BlockSize + 1024,
\r
614 sizeof(tExt2_Group)*Disk->GroupCount,
\r
619 // at Block Group 1, 3^n, 5^n, 7^n
\r
622 if(ngrp <= 1) return;
\r
623 VFS_WriteAt(Disk->FD, 1*bpg*Disk->BlockSize, 1024, &Disk->SuperBlock);
\r
625 #define INT_MAX (((long long int)1<<(sizeof(int)*8))-1)
\r
628 for( i = 3; i < ngrp && i < INT_MAX/3; i *= 3 )
\r
629 VFS_WriteAt(Disk->FD, i*bpg*Disk->BlockSize, 1024, &Disk->SuperBlock);
\r
632 for( i = 5; i < ngrp && i < INT_MAX/5; i *= 5 )
\r
633 VFS_WriteAt(Disk->FD, i*bpg*Disk->BlockSize, 1024, &Disk->SuperBlock);
\r
636 for( i = 7; i < ngrp && i < INT_MAX/7; i *= 7 )
\r
637 VFS_WriteAt(Disk->FD, i*bpg*Disk->BlockSize, 1024, &Disk->SuperBlock);
\r