Kernel - VFS API Update - ReadDir caller provided buffer
[tpg/acess2.git] / KernelLand / Modules / Filesystems / Ext2 / dir.c
1 /*
2  * Acess2 Ext2 Driver
3  * - By John Hodge (thePowersGang)
4  *
5  * dir.c
6  * - Directory Handling
7  */
8 #define DEBUG   1
9 #define VERBOSE 0
10 #include "ext2_common.h"
11
12 // === MACROS ===
13 #define BLOCK_DIR_OFS(_data, _block)    (((Uint16*)(_data))[(_block)])
14
15 // === PROTOTYPES ===
16  int    Ext2_ReadDir(tVFS_Node *Node, int Pos, char Dest[FILENAME_MAX]);
17 tVFS_Node       *Ext2_FindDir(tVFS_Node *Node, const char *FileName);
18 tVFS_Node       *Ext2_MkNod(tVFS_Node *Node, const char *Name, Uint Flags);
19  int    Ext2_Unlink(tVFS_Node *Node, const char *OldName);
20  int    Ext2_Link(tVFS_Node *Parent, const char *Name, tVFS_Node *Node);
21 // --- Helpers ---
22 tVFS_Node       *Ext2_int_CreateNode(tExt2_Disk *Disk, Uint InodeId);
23
24 // === GLOBALS ===
25 tVFS_NodeType   gExt2_DirType = {
26         .TypeName = "ext2-dir",
27         .ReadDir = Ext2_ReadDir,
28         .FindDir = Ext2_FindDir,
29         .MkNod = Ext2_MkNod,
30         .Unlink = Ext2_Unlink,
31         .Link = Ext2_Link,
32         .Close = Ext2_CloseFile
33         };
34 tVFS_NodeType   gExt2_FileType = {
35         .TypeName = "ext2-file",
36         .Read = Ext2_Read,
37         .Write = Ext2_Write,
38         .Close = Ext2_CloseFile
39         };
40
41 // === CODE ===
42 /**
43  * \brief Reads a directory entry
44  * \param Node  Directory node
45  * \param Pos   Position of desired element
46  */
47 int Ext2_ReadDir(tVFS_Node *Node, int Pos, char Dest[FILENAME_MAX])
48 {
49         tExt2_Inode     inode;
50         tExt2_DirEnt    dirent;
51         Uint64  Base;   // Block's Base Address
52          int    block = 0;
53         Uint    ofs = 0;
54          int    entNum = 0;
55         tExt2_Disk      *disk = Node->ImplPtr;
56         Uint    size;
57         
58         ENTER("pNode iPos", Node, Pos);
59         
60         // Read directory's inode
61         Ext2_int_ReadInode(disk, Node->Inode, &inode);
62         size = inode.i_size;
63         
64         LOG("inode={.i_block[0]= 0x%x, .i_size=0x%x}", inode.i_block[0], inode.i_size);
65         
66         // Find Entry
67         // Get First Block
68         // - Do this ourselves as it is a simple operation
69         Base = inode.i_block[0] * disk->BlockSize;
70         // Scan directory
71         while(Pos -- && size > 0 && size <= inode.i_size)
72         {
73                 VFS_ReadAt( disk->FD, Base+ofs, sizeof(tExt2_DirEnt), &dirent);
74                 ofs += dirent.rec_len;
75                 size -= dirent.rec_len;
76                 entNum ++;
77                 
78                 if(ofs >= disk->BlockSize) {
79                         block ++;
80                         if( ofs > disk->BlockSize ) {
81                                 Log_Warning("EXT2", "Directory Entry %i of inode %i extends over a block boundary, ignoring",
82                                         entNum-1, Node->Inode);
83                         }
84                         ofs = 0;
85                         Base = Ext2_int_GetBlockAddr( disk, inode.i_block, block );
86                         if( Base == 0 ) {
87                                 size = 0;
88                                 break;
89                         }
90                 }
91         }
92         
93         // Check for the end of the list
94         if(size <= 0 || size > inode.i_size) {
95                 LEAVE('i', -ENOENT);
96                 return -ENOENT;
97         }
98         
99         // Read Entry
100         VFS_ReadAt( disk->FD, Base+ofs, sizeof(tExt2_DirEnt), &dirent );
101         LOG("dirent={.rec_len=%i,.inode=0x%x,.name_len=%i}",
102                 dirent.rec_len, dirent.inode, dirent.name_len);
103         dirent.name[ dirent.name_len ] = '\0';  // Cap off string
104         
105         if( dirent.name_len == 0 ) {
106                 LEAVE('i', 1);
107                 return 1;
108         }
109         
110         // Ignore . and .. (these are done in the VFS)
111         if( (dirent.name[0] == '.' && dirent.name[1] == '\0')
112         ||  (dirent.name[0] == '.' && dirent.name[1] == '.' && dirent.name[2]=='\0')) {
113                 LEAVE('i', 1);
114                 return 1;       // Skip
115         }
116         
117         LOG("Name '%s'", dirent.name);
118         strncpy(Dest, dirent.name, FILENAME_MAX);
119         LEAVE('i', 0);
120         return 0;
121 }
122
123 /**
124  * \brief Gets information about a file
125  * \param Node  Parent Node
126  * \param Filename      Name of wanted file
127  * \return VFS Node of file
128  */
129 tVFS_Node *Ext2_FindDir(tVFS_Node *Node, const char *Filename)
130 {
131         tExt2_Disk      *disk = Node->ImplPtr;
132         tExt2_Inode     inode;
133         tExt2_DirEnt    dirent;
134         Uint64  Base;   // Block's Base Address
135          int    block = 0;
136         Uint    ofs = 0;
137          int    entNum = 0;
138         Uint    size;
139          int    filenameLen = strlen(Filename);
140         
141         // Read directory's inode
142         Ext2_int_ReadInode(disk, Node->Inode, &inode);
143         size = inode.i_size;
144         
145         // Get First Block
146         // - Do this ourselves as it is a simple operation
147         Base = inode.i_block[0] * disk->BlockSize;
148         // Find File
149         while(size > 0)
150         {
151                 VFS_ReadAt( disk->FD, Base+ofs, sizeof(tExt2_DirEnt), &dirent);
152                 // TODO: Possible overrun if name_len == 255?
153                 dirent.name[ dirent.name_len ] = '\0';  // Cap off string
154                 // If it matches, create a node and return it
155                 if(dirent.name_len == filenameLen && strcmp(dirent.name, Filename) == 0)
156                         return Ext2_int_CreateNode( disk, dirent.inode );
157                 // Increment pointers
158                 ofs += dirent.rec_len;
159                 size -= dirent.rec_len;
160                 entNum ++;
161                 
162                 // Check for end of block
163                 if(ofs >= disk->BlockSize) {
164                         block ++;
165                         if( ofs > disk->BlockSize ) {
166                                 Log_Warning("EXT2", "Directory Entry %i of inode %i extends over a block boundary, ignoring",
167                                         entNum-1, Node->Inode);
168                         }
169                         ofs = 0;
170                         Base = Ext2_int_GetBlockAddr( disk, inode.i_block, block );
171                 }
172         }
173         
174         return NULL;
175 }
176
177 /**
178  * \fn int Ext2_MkNod(tVFS_Node *Parent, const char *Name, Uint Flags)
179  * \brief Create a new node
180  */
181 tVFS_Node *Ext2_MkNod(tVFS_Node *Parent, const char *Name, Uint Flags)
182 {
183         ENTER("pParent sName xFlags", Parent, Name, Flags);
184         
185         Uint64 inodeNum = Ext2_int_AllocateInode(Parent->ImplPtr, Parent->Inode);
186         if( inodeNum == 0 ) {
187                 LOG("Inode allocation failed");
188                 LEAVE_RET('n', NULL);
189         }
190         tVFS_Node *child = Ext2_int_CreateNode(Parent->ImplPtr, inodeNum);
191         if( !child ) {
192                 Ext2_int_DereferenceInode(Parent->ImplPtr, inodeNum);
193                 Log_Warning("Ext2", "Ext2_MkNod - Node creation failed");
194                 LEAVE_RET('n', NULL);
195         }
196
197         child->Flags = Flags & (VFS_FFLAG_DIRECTORY|VFS_FFLAG_SYMLINK|VFS_FFLAG_READONLY);
198         child->UID = Threads_GetUID();
199         child->GID = Threads_GetGID();
200         child->CTime =
201                 child->MTime =
202                 child->ATime =
203                 now();
204         child->ImplInt = 0;     // ImplInt is the link count
205         // TODO: Set up ACLs
206
207         int rv = Ext2_Link(Parent, Name, child);
208         if( rv ) {
209                 Ext2_CloseFile(child);
210                 return NULL;
211         }
212         LEAVE_RET('p', child);
213 }
214
215 /**
216  * \brief Rename a file
217  * \param Node  This (directory) node
218  * \param OldName       Old name of file
219  * \param NewName       New name for file
220  * \return Boolean Failure - See ::tVFS_Node.Unlink for info
221  */
222 int Ext2_Unlink(tVFS_Node *Node, const char *OldName)
223 {
224         Log_Warning("Ext2", "TODO: Impliment Ext2_Unlink");
225         return 1;
226 }
227
228 /**
229  * \brief Links an existing node to a new name
230  * \param Parent        Parent (directory) node
231  * \param Name  New name for the node
232  * \param Node  Node to link
233  * \return Boolean Failure - See ::tVFS_Node.Link for info
234  */
235 int Ext2_Link(tVFS_Node *Node, const char *Name, tVFS_Node *Child)
236 {       
237         #if 1
238         tExt2_Disk      *disk = Node->ImplPtr;
239         tExt2_Inode     inode;
240         tExt2_DirEnt    *dirent;
241         tExt2_DirEnt    newEntry;
242         Uint64  base;   // Block's Base Address
243          int    block = 0, ofs = 0;
244         Uint    size;
245         void    *blockData;
246          int    bestMatch = -1;
247          int    bestSize=0, bestBlock=0, bestOfs=0, bestNeedsSplit=0;
248          int    nEntries;
249
250         ENTER("pNode sName pChild",
251                 Node, Name, Child);
252         
253         blockData = malloc(disk->BlockSize);
254         
255         // Read child inode (get's the file type)
256         Ext2_int_ReadInode(disk, Child->Inode, &inode);
257         
258         // Create a stub entry
259         newEntry.inode = Child->Inode;
260         newEntry.name_len = strlen(Name);
261         newEntry.rec_len = ((newEntry.name_len+3)&~3) + EXT2_DIRENT_SIZE;
262         newEntry.type = inode.i_mode >> 12;
263         memcpy(newEntry.name, Name, newEntry.name_len);
264         
265         // Read directory's inode
266         Ext2_int_ReadInode(disk, Node->Inode, &inode);
267         size = inode.i_size;
268         
269         // Get a lock on the inode
270         //Ext2_int_LockInode(disk, Node->Inode);
271         Mutex_Acquire(&Node->Lock);
272
273 //      if( !Node->Data ) {
274 //      }
275
276         // Get First Block
277         // - Do this ourselves as it is a simple operation
278         base = inode.i_block[0] * disk->BlockSize;
279         VFS_ReadAt( disk->FD, base, disk->BlockSize, blockData );
280         block = 0;
281         nEntries = 0;
282         // Find File
283         while(size > 0)
284         {
285                 dirent = blockData + ofs;
286                 // Sanity Check the entry
287                 if(ofs + dirent->rec_len > disk->BlockSize) {
288                         Log_Warning("EXT2",
289                                 "Directory entry %i of inode 0x%x extends over a block boundary",
290                                 nEntries, (Uint)Node->Inode);
291                 }
292                 else
293                 {
294                         LOG("Entry %i: %x %i bytes", nEntries, dirent->type, dirent->rec_len);
295                         // Free entry
296                         if(dirent->type == 0)
297                         {
298                                 if( dirent->rec_len >= newEntry.rec_len
299                                  && (bestMatch == -1 || bestSize > dirent->rec_len) )
300                                 {
301                                         bestMatch = nEntries;
302                                         bestSize = dirent->rec_len;
303                                         bestBlock = block;
304                                         bestOfs = ofs;
305                                         bestNeedsSplit = 0;
306                                 }
307                         }
308                         // Non free - check name to avoid duplicates
309                         else
310                         {
311                                 LOG(" name='%.*s'", dirent->name_len, dirent->name);
312                                 if(strncmp(Name, dirent->name, dirent->name_len) == 0) {
313                                         //Ext2_int_UnlockInode(disk, Node->Inode);
314                                         Mutex_Release(&Node->Lock);
315                                         LEAVE('i', 1);
316                                         return 1;       // ERR_???
317                                 }
318                                 
319                                  int    spare_space = dirent->rec_len - (dirent->name_len + EXT2_DIRENT_SIZE);
320                                 if( spare_space > newEntry.rec_len
321                                  && (bestMatch == -1 || bestSize > spare_space) )
322                                 {
323                                         bestMatch = nEntries;
324                                         bestSize = spare_space;
325                                         bestBlock = block;
326                                         bestOfs = ofs;
327                                         bestNeedsSplit = 1;
328                                 }
329                         }
330                 }
331                 
332                 // Increment the pointer
333                 nEntries ++;
334                 ofs += dirent->rec_len;
335                 size -= dirent->rec_len;
336                 if( size > 0 && ofs >= disk->BlockSize ) {
337                         // Read the next block if needed
338                 //      BLOCK_DIR_OFS(Node->Data, block) = nEntries;
339                         block ++;
340                         ofs = 0;
341                         base = Ext2_int_GetBlockAddr(disk, inode.i_block, block);
342                         VFS_ReadAt( disk->FD, base, disk->BlockSize, blockData );
343                 }
344         }
345         
346         LOG("bestMatch = %i", bestMatch);
347         // If EOF was reached with no space, check if we can fit one on the end
348         if( bestMatch < 0 && ofs + newEntry.rec_len < disk->BlockSize ) {
349                 Node->Size += newEntry.rec_len;
350                 Node->Flags |= VFS_FFLAG_DIRTY;
351                 bestBlock = block;
352                 bestOfs = ofs;
353                 bestSize = newEntry.rec_len;
354                 bestNeedsSplit = 0;
355         }
356         // Check if a free slot was found
357         if( bestMatch >= 0 )
358         {
359                 // Read-Modify-Write
360                 base = Ext2_int_GetBlockAddr(disk, inode.i_block, bestBlock);
361                 VFS_ReadAt( disk->FD, base, disk->BlockSize, blockData );
362                 dirent = blockData + bestOfs;
363                 // Shorten a pre-existing entry
364                 if(bestNeedsSplit)
365                 {
366                         dirent->rec_len = EXT2_DIRENT_SIZE + dirent->name_len;
367                         bestOfs += dirent->rec_len;
368                         //bestSize -= dirent->rec_len; // (not needed, bestSize is the spare space after)
369                         dirent = blockData + bestOfs;
370                 }
371                 // Insert new file entry
372                 memcpy(dirent, &newEntry, newEntry.rec_len);
373                 // Create a new blank entry
374                 if( bestSize != newEntry.rec_len )
375                 {
376                         bestOfs += newEntry.rec_len;
377                         dirent = blockData + bestOfs;
378
379                         dirent->rec_len = bestSize - newEntry.rec_len;                  
380                         dirent->type = 0;
381                 }
382                 // Save changes
383                 VFS_WriteAt( disk->FD, base, disk->BlockSize, blockData );
384         }
385         else {
386                 // Allocate block, Write
387                 Uint32 newblock = Ext2_int_AllocateBlock(disk, base / disk->BlockSize);
388                 Ext2_int_AppendBlock(disk, &inode, newblock);
389                 base = newblock * disk->BlockSize;
390                 Node->Size += newEntry.rec_len;
391                 Node->Flags |= VFS_FFLAG_DIRTY;
392                 memcpy(blockData, &newEntry, newEntry.rec_len);
393                 memset(blockData + newEntry.rec_len, 0, disk->BlockSize - newEntry.rec_len);
394                 VFS_WriteAt( disk->FD, base, disk->BlockSize, blockData );
395         }
396
397         Child->ImplInt ++;
398         Child->Flags |= VFS_FFLAG_DIRTY;
399
400         //Ext2_int_UnlockInode(disk, Node->Inode);
401         Mutex_Release(&Node->Lock);
402         LEAVE('i', 0);
403         return 0;
404         #else
405         Log_Warning("Ext2", "TODO: Impliment Ext2_Link");
406         return 1;
407         #endif
408 }
409
410 // ---- INTERNAL FUNCTIONS ----
411 /**
412  * \fn vfs_node *Ext2_int_CreateNode(tExt2_Disk *Disk, Uint InodeID)
413  * \brief Create a new VFS Node
414  */
415 tVFS_Node *Ext2_int_CreateNode(tExt2_Disk *Disk, Uint InodeID)
416 {
417         tExt2_Inode     inode;
418         tVFS_Node       retNode;
419         tVFS_Node       *tmpNode;
420         
421         if( !Ext2_int_ReadInode(Disk, InodeID, &inode) )
422                 return NULL;
423         
424         if( (tmpNode = Inode_GetCache(Disk->CacheID, InodeID)) )
425                 return tmpNode;
426
427         memset(&retNode, 0, sizeof(retNode));   
428         
429         // Set identifiers
430         retNode.Inode = InodeID;
431         retNode.ImplPtr = Disk;
432         retNode.ImplInt = inode.i_links_count;
433         
434         // Set file length
435         retNode.Size = inode.i_size;
436         
437         // Set Access Permissions
438         retNode.UID = inode.i_uid;
439         retNode.GID = inode.i_gid;
440         retNode.NumACLs = 3;
441         retNode.ACLs = VFS_UnixToAcessACL(inode.i_mode & 0777, inode.i_uid, inode.i_gid);
442         
443         //  Set Function Pointers
444         retNode.Type = &gExt2_FileType;
445         
446         switch(inode.i_mode & EXT2_S_IFMT)
447         {
448         // Symbolic Link
449         case EXT2_S_IFLNK:
450                 retNode.Flags = VFS_FFLAG_SYMLINK;
451                 break;
452         // Regular File
453         case EXT2_S_IFREG:
454                 retNode.Flags = 0;
455                 retNode.Size |= (Uint64)inode.i_dir_acl << 32;
456                 break;
457         // Directory
458         case EXT2_S_IFDIR:
459                 retNode.Type = &gExt2_DirType;
460                 retNode.Flags = VFS_FFLAG_DIRECTORY;
461                 retNode.Data = calloc( sizeof(Uint16), DivUp(retNode.Size, Disk->BlockSize) );
462                 break;
463         // Unknown, Write protect it to be safe 
464         default:
465                 retNode.Flags = VFS_FFLAG_READONLY;
466                 break;
467         }
468         
469         // Set Timestamps
470         retNode.ATime = inode.i_atime * 1000;
471         retNode.MTime = inode.i_mtime * 1000;
472         retNode.CTime = inode.i_ctime * 1000;
473         
474         // Save in node cache and return saved node
475         return Inode_CacheNode(Disk->CacheID, &retNode);
476 }
477
478 int Ext2_int_WritebackNode(tExt2_Disk *Disk, tVFS_Node *Node)
479 {
480         Log_Warning("Ext2","TODO: Impliment Ext2_int_WritebackNode");
481         return 0;
482 }
483

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