Modules/Ext2 - Experimental write support coming along
[tpg/acess2.git] / KernelLand / Modules / Filesystems / Ext2 / ext2.c
1 /*\r
2  * Acess2 Ext2 Driver\r
3  * - By John Hodge (thePowersGang)\r
4  *\r
5  * ext2.c\r
6  * - Driver core\r
7  */\r
8 #define DEBUG   0\r
9 #define VERSION VER2(0,90)\r
10 #include "ext2_common.h"\r
11 #include <modules.h>\r
12 \r
13 #define MIN_BLOCKS_PER_GROUP    2\r
14 #define MAX_BLOCK_LOG_SIZE      10      // 1024 << 10 = 1MiB\r
15 \r
16 // === IMPORTS ===\r
17 extern tVFS_NodeType    gExt2_DirType;\r
18 \r
19 // === PROTOTYPES ===\r
20  int    Ext2_Install(char **Arguments);\r
21  int    Ext2_Cleanup(void);\r
22 // - Interface Functions\r
23  int    Ext2_Detect(int FD);\r
24 tVFS_Node       *Ext2_InitDevice(const char *Device, const char **Options);\r
25 void    Ext2_Unmount(tVFS_Node *Node);\r
26 void    Ext2_CloseFile(tVFS_Node *Node);\r
27 // - Internal Helpers\r
28  int    Ext2_int_GetInode(tVFS_Node *Node, tExt2_Inode *Inode);\r
29 Uint64  Ext2_int_GetBlockAddr(tExt2_Disk *Disk, Uint32 *Blocks, int BlockNum);\r
30 Uint32  Ext2_int_AllocateInode(tExt2_Disk *Disk, Uint32 Parent);\r
31 void    Ext2_int_DereferenceInode(tExt2_Disk *Disk, Uint32 Inode);\r
32 void    Ext2_int_UpdateSuperblock(tExt2_Disk *Disk);\r
33 \r
34 // === SEMI-GLOBALS ===\r
35 MODULE_DEFINE(0, VERSION, FS_Ext2, Ext2_Install, Ext2_Cleanup);\r
36 tExt2_Disk      gExt2_disks[6];\r
37  int    giExt2_count = 0;\r
38 tVFS_Driver     gExt2_FSInfo = {\r
39         .Name = "ext2",\r
40         .Detect = Ext2_Detect,\r
41         .InitDevice = Ext2_InitDevice,\r
42         .Unmount = Ext2_Unmount,\r
43         .GetNodeFromINode = NULL\r
44         };\r
45 \r
46 // === CODE ===\r
47 /**\r
48  * \fn int Ext2_Install(char **Arguments)\r
49  * \brief Install the Ext2 Filesystem Driver\r
50  */\r
51 int Ext2_Install(char **Arguments)\r
52 {\r
53         VFS_AddDriver( &gExt2_FSInfo );\r
54         return MODULE_ERR_OK;\r
55 }\r
56 \r
57 /**\r
58  * \brief Clean up driver state before unload\r
59  */\r
60 int Ext2_Cleanup(void)\r
61 {\r
62         return 0;\r
63 }\r
64 \r
65 /**\r
66  * Detect if a volume is Ext2 formatted\r
67  */\r
68 int Ext2_Detect(int FD)\r
69 {\r
70         tExt2_SuperBlock        sb;\r
71         size_t  len;\r
72         \r
73         len = VFS_ReadAt(FD, 1024, 1024, &sb);\r
74 \r
75         if( len != 1024 ) {\r
76                 Log_Debug("Ext2", "_Detect: Read failed? (0x%x != 1024)", len);\r
77                 return 0;\r
78         }\r
79         \r
80         switch(sb.s_magic)\r
81         {\r
82         case 0xEF53:\r
83                 return 2;\r
84         default:\r
85                 Log_Debug("Ext2", "_Detect: s_magic = 0x%x", sb.s_magic);\r
86                 return 0;\r
87         }\r
88 }\r
89 \r
90 /**\r
91  \brief Initializes a device to be read by by the driver\r
92  \param Device  String - Device to read from\r
93  \param Options NULL Terminated array of option strings\r
94  \return Root Node\r
95 */\r
96 tVFS_Node *Ext2_InitDevice(const char *Device, const char **Options)\r
97 {\r
98         tExt2_Disk      *disk;\r
99          int    fd;\r
100          int    groupCount;\r
101         tExt2_SuperBlock        sb;\r
102         tExt2_Inode     inode;\r
103         \r
104         ENTER("sDevice pOptions", Device, Options);\r
105         \r
106         // Open Disk\r
107         fd = VFS_Open(Device, VFS_OPENFLAG_READ|VFS_OPENFLAG_WRITE);            //Open Device\r
108         if(fd == -1) {\r
109                 Log_Warning("EXT2", "Unable to open '%s'", Device);\r
110                 LEAVE('n');\r
111                 return NULL;\r
112         }\r
113         \r
114         // Read Superblock at offset 1024\r
115         VFS_ReadAt(fd, 1024, 1024, &sb);        // Read Superblock\r
116         \r
117         // Sanity Check Magic value\r
118         if(sb.s_magic != 0xEF53) {\r
119                 Log_Warning("EXT2", "Volume '%s' is not an EXT2 volume (0x%x != 0xEF53)",\r
120                         Device, sb.s_magic);\r
121                 VFS_Close(fd);\r
122                 LEAVE('n');\r
123                 return NULL;\r
124         }\r
125 \r
126         if( sb.s_blocks_per_group < MIN_BLOCKS_PER_GROUP ) {\r
127                 Log_Warning("Ext2", "Blocks per group is too small (%i < %i)",\r
128                         sb.s_blocks_per_group, MIN_BLOCKS_PER_GROUP);\r
129                 goto _error;\r
130         }       \r
131 \r
132         // Get Group count\r
133         groupCount = DivUp(sb.s_blocks_count, sb.s_blocks_per_group);\r
134         LOG("groupCount = %i", groupCount);\r
135         \r
136         // Allocate Disk Information\r
137         disk = malloc(sizeof(tExt2_Disk) + sizeof(tExt2_Group)*groupCount);\r
138         if(!disk) {\r
139                 Log_Warning("EXT2", "Unable to allocate disk structure");\r
140                 VFS_Close(fd);\r
141                 LEAVE('n');\r
142                 return NULL;\r
143         }\r
144         disk->FD = fd;\r
145         memcpy(&disk->SuperBlock, &sb, 1024);\r
146         disk->GroupCount = groupCount;\r
147         \r
148         // Get an inode cache handle\r
149         disk->CacheID = Inode_GetHandle();\r
150         \r
151         // Get Block Size\r
152         if( sb.s_log_block_size > MAX_BLOCK_LOG_SIZE ) {\r
153                 Log_Warning("Ext2", "Block size (log2) too large (%i > %i)",\r
154                         sb.s_log_block_size, MAX_BLOCK_LOG_SIZE);\r
155                 goto _error;\r
156         }\r
157         disk->BlockSize = 1024 << sb.s_log_block_size;\r
158         LOG("Disk->BlockSie = 0x%x (1024 << %i)", disk->BlockSize, sb.s_log_block_size);\r
159         \r
160         // Read Group Information\r
161         LOG("sb,s_first_data_block = %x", sb.s_first_data_block);\r
162         VFS_ReadAt(\r
163                 disk->FD,\r
164                 sb.s_first_data_block * disk->BlockSize + 1024,\r
165                 sizeof(tExt2_Group)*groupCount,\r
166                 disk->Groups\r
167                 );\r
168         \r
169         LOG("Block Group 0");\r
170         LOG(".bg_block_bitmap = 0x%x", disk->Groups[0].bg_block_bitmap);\r
171         LOG(".bg_inode_bitmap = 0x%x", disk->Groups[0].bg_inode_bitmap);\r
172         LOG(".bg_inode_table = 0x%x", disk->Groups[0].bg_inode_table);\r
173         LOG("Block Group 1");\r
174         LOG(".bg_block_bitmap = 0x%x", disk->Groups[1].bg_block_bitmap);\r
175         LOG(".bg_inode_bitmap = 0x%x", disk->Groups[1].bg_inode_bitmap);\r
176         LOG(".bg_inode_table = 0x%x", disk->Groups[1].bg_inode_table);\r
177         \r
178         // Get root Inode\r
179         Ext2_int_ReadInode(disk, 2, &inode);\r
180         \r
181         // Create Root Node\r
182         memset(&disk->RootNode, 0, sizeof(tVFS_Node));\r
183         disk->RootNode.Inode = 2;       // Root inode ID\r
184         disk->RootNode.ImplPtr = disk;  // Save disk pointer\r
185         disk->RootNode.Size = -1;       // Fill in later (on readdir)\r
186         disk->RootNode.Flags = VFS_FFLAG_DIRECTORY;\r
187 \r
188         disk->RootNode.Type = &gExt2_DirType;\r
189         \r
190         // Complete root node\r
191         disk->RootNode.UID = inode.i_uid;\r
192         disk->RootNode.GID = inode.i_gid;\r
193         disk->RootNode.NumACLs = 1;\r
194         disk->RootNode.ACLs = &gVFS_ACL_EveryoneRW;\r
195         \r
196         #if DEBUG\r
197         LOG("inode.i_size = 0x%x", inode.i_size);\r
198         LOG("inode.i_block[0] = 0x%x", inode.i_block[0]);\r
199         #endif\r
200         \r
201         LEAVE('p', &disk->RootNode);\r
202         return &disk->RootNode;\r
203 _error:\r
204         free(disk);\r
205         VFS_Close(fd);\r
206         LEAVE('n');\r
207         return NULL;\r
208 }\r
209 \r
210 /**\r
211  * \fn void Ext2_Unmount(tVFS_Node *Node)\r
212  * \brief Close a mounted device\r
213  */\r
214 void Ext2_Unmount(tVFS_Node *Node)\r
215 {\r
216         tExt2_Disk      *disk = Node->ImplPtr;\r
217         \r
218         VFS_Close( disk->FD );\r
219         Inode_ClearCache( disk->CacheID );\r
220         memset(disk, 0, sizeof(tExt2_Disk)+disk->GroupCount*sizeof(tExt2_Group));\r
221         free(disk);\r
222 }\r
223 \r
224 /**\r
225  * \fn void Ext2_CloseFile(tVFS_Node *Node)\r
226  * \brief Close a file (Remove it from the cache)\r
227  */\r
228 void Ext2_CloseFile(tVFS_Node *Node)\r
229 {\r
230         tExt2_Disk      *disk = Node->ImplPtr;\r
231         ENTER("pNode", Node);\r
232 \r
233         if( Mutex_Acquire(&Node->Lock) != 0 )\r
234         {\r
235                 LEAVE('-');\r
236                 return ;\r
237         }\r
238 \r
239         if( Node->Flags & VFS_FFLAG_DIRTY )\r
240         {\r
241                 // Commit changes\r
242                 Log_Warning("Ext2", "TODO: Commit node changes");\r
243         }\r
244 \r
245         int was_not_referenced = (Node->ImplInt == 0);\r
246         tVFS_ACL        *acls = Node->ACLs;\r
247         if( Inode_UncacheNode(disk->CacheID, Node->Inode) == 1 )\r
248         {\r
249                 if( was_not_referenced )\r
250                 {\r
251                         LOG("Removng inode");\r
252                         // Remove inode\r
253                         Log_Warning("Ext2", "TODO: Remove inode when not referenced");\r
254                 }\r
255                 if( acls != &gVFS_ACL_EveryoneRW ) {\r
256                         free(acls);\r
257                 }\r
258                 LOG("Node cleaned");\r
259         }\r
260         else {\r
261                 LOG("Still referenced, releasing lock");\r
262                 Mutex_Release(&Node->Lock);\r
263         }\r
264         LEAVE('-');\r
265         return ;\r
266 }\r
267 \r
268 //==================================\r
269 //=       INTERNAL FUNCTIONS       =\r
270 //==================================\r
271 /**\r
272  * \fn int Ext2_int_ReadInode(tExt2_Disk *Disk, Uint InodeId, tExt2_Inode *Inode)\r
273  * \brief Read an inode into memory\r
274  */\r
275 int Ext2_int_ReadInode(tExt2_Disk *Disk, Uint32 InodeId, tExt2_Inode *Inode)\r
276 {\r
277          int    group, subId;\r
278         \r
279         ENTER("pDisk iInodeId pInode", Disk, InodeId, Inode);\r
280         \r
281         if(InodeId == 0)        return 0;\r
282         \r
283         InodeId --;     // Inodes are numbered starting at 1\r
284         \r
285         group = InodeId / Disk->SuperBlock.s_inodes_per_group;\r
286         subId = InodeId % Disk->SuperBlock.s_inodes_per_group;\r
287         \r
288         LOG("group=%i, subId = %i", group, subId);\r
289         \r
290         // Read Inode\r
291         VFS_ReadAt(Disk->FD,\r
292                 Disk->Groups[group].bg_inode_table * Disk->BlockSize + sizeof(tExt2_Inode)*subId,\r
293                 sizeof(tExt2_Inode),\r
294                 Inode);\r
295         \r
296         LEAVE('i', 1);\r
297         return 1;\r
298 }\r
299 \r
300 /**\r
301  * \brief Write a modified inode out to disk\r
302  */\r
303 int Ext2_int_WriteInode(tExt2_Disk *Disk, Uint32 InodeId, tExt2_Inode *Inode)\r
304 {\r
305          int    group, subId;\r
306         ENTER("pDisk iInodeId pInode", Disk, InodeId, Inode);\r
307         \r
308         if(InodeId == 0) {\r
309                 LEAVE('i', 0);\r
310                 return 0;\r
311         }\r
312         \r
313         InodeId --;     // Inodes are numbered starting at 1\r
314         \r
315         group = InodeId / Disk->SuperBlock.s_inodes_per_group;\r
316         subId = InodeId % Disk->SuperBlock.s_inodes_per_group;\r
317         \r
318         LOG("group=%i, subId = %i", group, subId);\r
319         \r
320         // Write Inode\r
321         VFS_WriteAt(Disk->FD,\r
322                 Disk->Groups[group].bg_inode_table * Disk->BlockSize + sizeof(tExt2_Inode)*subId,\r
323                 sizeof(tExt2_Inode),\r
324                 Inode\r
325                 );\r
326         \r
327         LEAVE('i', 1);\r
328         return 1;\r
329 }\r
330 \r
331 /**\r
332  * \fn Uint64 Ext2_int_GetBlockAddr(tExt2_Disk *Disk, Uint32 *Blocks, int BlockNum)\r
333  * \brief Get the address of a block from an inode's list\r
334  * \param Disk  Disk information structure\r
335  * \param Blocks        Pointer to an inode's block list\r
336  * \param BlockNum      Block index in list\r
337  */\r
338 Uint64 Ext2_int_GetBlockAddr(tExt2_Disk *Disk, Uint32 *Blocks, int BlockNum)\r
339 {\r
340         Uint32  *iBlocks;\r
341          int    dwPerBlock = Disk->BlockSize / 4;\r
342         \r
343         // Direct Blocks\r
344         if(BlockNum < 12)\r
345                 return (Uint64)Blocks[BlockNum] * Disk->BlockSize;\r
346         \r
347         // Single Indirect Blocks\r
348         iBlocks = malloc( Disk->BlockSize );\r
349         VFS_ReadAt(Disk->FD, (Uint64)Blocks[12]*Disk->BlockSize, Disk->BlockSize, iBlocks);\r
350         \r
351         BlockNum -= 12;\r
352         if(BlockNum < dwPerBlock)\r
353         {\r
354                 BlockNum = iBlocks[BlockNum];\r
355                 free(iBlocks);\r
356                 return (Uint64)BlockNum * Disk->BlockSize;\r
357         }\r
358         \r
359         BlockNum -= dwPerBlock;\r
360         // Double Indirect Blocks\r
361         if(BlockNum < dwPerBlock*dwPerBlock)\r
362         {\r
363                 VFS_ReadAt(Disk->FD, (Uint64)Blocks[13]*Disk->BlockSize, Disk->BlockSize, iBlocks);\r
364                 VFS_ReadAt(Disk->FD, (Uint64)iBlocks[BlockNum/dwPerBlock]*Disk->BlockSize, Disk->BlockSize, iBlocks);\r
365                 BlockNum = iBlocks[BlockNum%dwPerBlock];\r
366                 free(iBlocks);\r
367                 return (Uint64)BlockNum * Disk->BlockSize;\r
368         }\r
369         \r
370         BlockNum -= dwPerBlock*dwPerBlock;\r
371         // Triple Indirect Blocks\r
372         VFS_ReadAt(Disk->FD, (Uint64)Blocks[14]*Disk->BlockSize, Disk->BlockSize, iBlocks);\r
373         VFS_ReadAt(Disk->FD, (Uint64)iBlocks[BlockNum/(dwPerBlock*dwPerBlock)]*Disk->BlockSize, Disk->BlockSize, iBlocks);\r
374         VFS_ReadAt(Disk->FD, (Uint64)iBlocks[(BlockNum/dwPerBlock)%dwPerBlock]*Disk->BlockSize, Disk->BlockSize, iBlocks);\r
375         BlockNum = iBlocks[BlockNum%dwPerBlock];\r
376         free(iBlocks);\r
377         return (Uint64)BlockNum * Disk->BlockSize;\r
378 }\r
379 \r
380 /**\r
381  * \fn Uint32 Ext2_int_AllocateInode(tExt2_Disk *Disk, Uint32 Parent)\r
382  * \brief Allocate an inode (from the current group preferably)\r
383  * \param Disk  EXT2 Disk Information Structure\r
384  * \param Parent        Inode ID of the parent (used to locate the child nearby)\r
385  */\r
386 Uint32 Ext2_int_AllocateInode(tExt2_Disk *Disk, Uint32 Parent)\r
387 {\r
388         Uint    start_group = (Parent - 1) / Disk->SuperBlock.s_inodes_per_group;\r
389         Uint    group = start_group;\r
390 \r
391         if( Disk->SuperBlock.s_free_inodes_count == 0 ) \r
392         {\r
393                 Log_Notice("Ext2", "Ext2_int_AllocateInode - Out of inodes on %p", Disk);\r
394                 return 0;\r
395         }\r
396 \r
397         while( group < Disk->GroupCount && Disk->Groups[group].bg_free_inodes_count == 0 )\r
398                 group ++;\r
399         if( group == Disk->GroupCount )\r
400         {\r
401                 group = 0;\r
402                 while( group < start_group && Disk->Groups[group].bg_free_inodes_count == 0 )\r
403                         group ++;\r
404         }\r
405         \r
406         if( Disk->Groups[group].bg_free_inodes_count == 0 )\r
407         {\r
408                 Log_Notice("Ext2", "Ext2_int_AllocateInode - Out of inodes on %p, but superblock says some free", Disk);\r
409                 return 0;\r
410         }\r
411 \r
412         // Load bitmap for group\r
413         //  (s_inodes_per_group / 8) bytes worth\r
414         // - Allocate a buffer the size of a sector/block\r
415         // - Read in part of the bitmap\r
416         // - Search for a free inode\r
417         tExt2_Group     *bg = &Disk->Groups[group];\r
418          int    ofs = 0;\r
419         do {\r
420                 const int sector_size = 512;\r
421                 Uint8 buf[sector_size];\r
422                 VFS_ReadAt(Disk->FD, Disk->BlockSize*bg->bg_inode_bitmap+ofs, sector_size, buf);\r
423 \r
424                 int byte, bit;\r
425                 for( byte = 0; byte < sector_size && buf[byte] != 0xFF; byte ++ )\r
426                         ;\r
427                 if( byte < sector_size )\r
428                 {\r
429                         for( bit = 0; bit < 8 && buf[byte] & (1 << bit); bit ++)\r
430                                 ;\r
431                         ASSERT(bit != 8);\r
432                         buf[byte] |= 1 << bit;\r
433                         VFS_WriteAt(Disk->FD, Disk->BlockSize*bg->bg_inode_bitmap+ofs, sector_size, buf);\r
434 \r
435                         bg->bg_free_inodes_count --;\r
436                         Disk->SuperBlock.s_free_inodes_count --;\r
437 \r
438                         Uint32  ret = group * Disk->SuperBlock.s_inodes_per_group + byte * 8 + bit + 1;\r
439                         Log_Debug("Ext2", "Ext2_int_AllocateInode - Allocated 0x%x", ret);\r
440                         return ret;\r
441                 }\r
442 \r
443                 ofs += sector_size;\r
444         } while(ofs < Disk->SuperBlock.s_inodes_per_group / 8);\r
445 \r
446         Log_Notice("Ext2", "Ext2_int_AllocateInode - Out of inodes in group %p:%i but header reported free",\r
447                 Disk, group);\r
448 \r
449         return 0;\r
450 }\r
451 \r
452 /**\r
453  * \brief Reduce the reference count on an inode\r
454  */\r
455 void Ext2_int_DereferenceInode(tExt2_Disk *Disk, Uint32 Inode)\r
456 {\r
457         Log_Warning("Ext2", "TODO: Impliment Ext2_int_DereferenceInode");\r
458 }\r
459 \r
460 /**\r
461  * \fn void Ext2_int_UpdateSuperblock(tExt2_Disk *Disk)\r
462  * \brief Updates the superblock\r
463  */\r
464 void Ext2_int_UpdateSuperblock(tExt2_Disk *Disk)\r
465 {\r
466          int    bpg = Disk->SuperBlock.s_blocks_per_group;\r
467          int    ngrp = Disk->SuperBlock.s_blocks_count / bpg;\r
468          int    i;\r
469          \r
470         // Update Primary\r
471         VFS_WriteAt(Disk->FD, 1024, 1024, &Disk->SuperBlock);\r
472         \r
473         // Secondaries\r
474         // at Block Group 1, 3^n, 5^n, 7^n\r
475         \r
476         // 1\r
477         if(ngrp <= 1)   return;\r
478         VFS_WriteAt(Disk->FD, 1*bpg*Disk->BlockSize, 1024, &Disk->SuperBlock);\r
479         \r
480         #define INT_MAX (((long long int)1<<(sizeof(int)*8))-1)\r
481         \r
482         // Powers of 3\r
483         for( i = 3; i < ngrp && i < INT_MAX/3; i *= 3 )\r
484                 VFS_WriteAt(Disk->FD, i*bpg*Disk->BlockSize, 1024, &Disk->SuperBlock);\r
485         \r
486         // Powers of 5\r
487         for( i = 5; i < ngrp && i < INT_MAX/5; i *= 5 )\r
488                 VFS_WriteAt(Disk->FD, i*bpg*Disk->BlockSize, 1024, &Disk->SuperBlock);\r
489         \r
490         // Powers of 7\r
491         for( i = 7; i < ngrp && i < INT_MAX/7; i *= 7 )\r
492                 VFS_WriteAt(Disk->FD, i*bpg*Disk->BlockSize, 1024, &Disk->SuperBlock);\r
493 }\r

UCC git Repository :: git.ucc.asn.au