Usermode/libc - Fix strchr and strrchr behavior
[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   0
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, Uint Flags);
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
22 // === GLOBALS ===
23 tVFS_NodeType   gExt2_DirType = {
24         .TypeName = "ext2-dir",
25         .ReadDir = Ext2_ReadDir,
26         .FindDir = Ext2_FindDir,
27         .MkNod = Ext2_MkNod,
28         .Unlink = Ext2_Unlink,
29         .Link = Ext2_Link,
30         .Close = Ext2_CloseFile
31         };
32 tVFS_NodeType   gExt2_FileType = {
33         .TypeName = "ext2-file",
34         .Read = Ext2_Read,
35         .Write = Ext2_Write,
36         .Close = Ext2_CloseFile
37         };
38
39 // === CODE ===
40 /**
41  * \brief Reads a directory entry
42  * \param Node  Directory node
43  * \param Pos   Position of desired element
44  */
45 int Ext2_ReadDir(tVFS_Node *Node, int Pos, char Dest[FILENAME_MAX])
46 {
47         tExt2_Inode     *inode = (void*)(Node+1);
48         tExt2_DirEnt    dirent;
49         Uint64  Base;   // Block's Base Address
50          int    block = 0;
51         Uint    ofs = 0;
52          int    entNum = 0;
53         tExt2_Disk      *disk = Node->ImplPtr;
54         Uint    size;
55         
56         ENTER("pNode iPos", Node, Pos);
57         
58         // Read directory's inode
59         size = inode->i_size;
60         
61         LOG("inode={.i_block[0]= 0x%x, .i_size=0x%x}", inode->i_block[0], inode->i_size);
62         
63         // Find Entry
64         // Get First Block
65         // - Do this ourselves as it is a simple operation
66         Base = inode->i_block[0] * disk->BlockSize;
67         // Scan directory
68         while(Pos -- && size > 0 && size <= inode->i_size)
69         {
70                 VFS_ReadAt( disk->FD, Base+ofs, sizeof(tExt2_DirEnt), &dirent);
71                 
72                 if( dirent.rec_len == 0 ) {
73                         size = 0;
74                         break;
75                 }
76                 
77                 ofs += dirent.rec_len;
78                 size -= dirent.rec_len;
79                 entNum ++;
80                 
81                 if(ofs >= disk->BlockSize) {
82                         block ++;
83                         if( ofs > disk->BlockSize ) {
84                                 Log_Warning("EXT2", "Directory Entry %i of inode %i extends over a block boundary, ignoring",
85                                         entNum-1, Node->Inode);
86                         }
87                         ofs = 0;
88                         Base = Ext2_int_GetBlockAddr( disk, inode->i_block, block );
89                         if( Base == 0 ) {
90                                 size = 0;
91                                 break;
92                         }
93                 }
94         }
95         
96         // Check for the end of the list
97         if(size <= 0 || size > inode->i_size) {
98                 LEAVE('i', -ENOENT);
99                 return -ENOENT;
100         }
101         
102         // Read Entry
103         VFS_ReadAt( disk->FD, Base+ofs, sizeof(tExt2_DirEnt), &dirent );
104         LOG("dirent={.rec_len=%i,.inode=0x%x,.name_len=%i}",
105                 dirent.rec_len, dirent.inode, dirent.name_len);
106         dirent.name[ dirent.name_len ] = '\0';  // Cap off string
107         
108         if( dirent.name_len == 0 ) {
109                 LEAVE('i', 1);
110                 return 1;
111         }
112         
113         // Ignore . and .. (these are done in the VFS)
114         if( (dirent.name[0] == '.' && dirent.name[1] == '\0')
115         ||  (dirent.name[0] == '.' && dirent.name[1] == '.' && dirent.name[2]=='\0')) {
116                 LEAVE('i', 1);
117                 return 1;       // Skip
118         }
119         
120         LOG("Name '%s'", dirent.name);
121         strncpy(Dest, dirent.name, FILENAME_MAX);
122         LEAVE('i', 0);
123         return 0;
124 }
125
126 /**
127  * \brief Gets information about a file
128  * \param Node  Parent Node
129  * \param Filename      Name of wanted file
130  * \return VFS Node of file
131  */
132 tVFS_Node *Ext2_FindDir(tVFS_Node *Node, const char *Filename, Uint Flags)
133 {
134         tExt2_Disk      *disk = Node->ImplPtr;
135         tExt2_Inode     *inode = (void*)(Node+1);
136         tExt2_DirEnt    dirent;
137         Uint64  Base;   // Block's Base Address
138          int    block = 0;
139         Uint    ofs = 0;
140          int    entNum = 0;
141         Uint    size;
142          int    filenameLen = strlen(Filename);
143         
144         // Read directory's inode
145         size = inode->i_size;
146         
147         // Get First Block
148         // - Do this ourselves as it is a simple operation
149         Base = inode->i_block[0] * disk->BlockSize;
150         // Find File
151         while(size > 0)
152         {
153                 VFS_ReadAt( disk->FD, Base+ofs, sizeof(tExt2_DirEnt), &dirent);
154                 // If it matches, create a node and return it
155                 if(dirent.name_len == filenameLen && strncmp(dirent.name, Filename, filenameLen) == 0)
156                         return Ext2_int_CreateNode( disk, dirent.inode );
157                 if( dirent.rec_len == 0 )
158                         break;
159                 // Increment pointers
160                 ofs += dirent.rec_len;
161                 size -= dirent.rec_len;
162                 entNum ++;
163                 
164                 // Check for end of block
165                 if(ofs >= disk->BlockSize) {
166                         block ++;
167                         if( ofs > disk->BlockSize ) {
168                                 Log_Warning("EXT2", "Directory Entry %i of inode %i extends over a block boundary, ignoring",
169                                         entNum-1, Node->Inode);
170                         }
171                         ofs = 0;
172                         Base = Ext2_int_GetBlockAddr( disk, inode->i_block, block );
173                         if( Base == 0 )
174                                 break;
175                 }
176         }
177         
178         return NULL;
179 }
180
181 /**
182  * \fn int Ext2_MkNod(tVFS_Node *Parent, const char *Name, Uint Flags)
183  * \brief Create a new node
184  */
185 tVFS_Node *Ext2_MkNod(tVFS_Node *Parent, const char *Name, Uint Flags)
186 {
187         ENTER("pParent sName xFlags", Parent, Name, Flags);
188         
189         Uint64 inodeNum = Ext2_int_AllocateInode(Parent->ImplPtr, Parent->Inode);
190         if( inodeNum == 0 ) {
191                 LOG("Inode allocation failed");
192                 LEAVE_RET('n', NULL);
193         }
194         tVFS_Node *child = Ext2_int_CreateNode(Parent->ImplPtr, inodeNum);
195         if( !child ) {
196                 Ext2_int_DereferenceInode(Parent->ImplPtr, inodeNum);
197                 Log_Warning("Ext2", "Ext2_MkNod - Node creation failed");
198                 LEAVE_RET('n', NULL);
199         }
200
201         child->Flags = Flags & (VFS_FFLAG_DIRECTORY|VFS_FFLAG_SYMLINK|VFS_FFLAG_READONLY);
202         child->UID = Threads_GetUID();
203         child->GID = Threads_GetGID();
204         child->CTime =
205                 child->MTime =
206                 child->ATime =
207                 now();
208         child->ImplInt = 0;     // ImplInt is the link count
209         // TODO: Set up ACLs
210
211         int rv = Ext2_Link(Parent, Name, child);
212         if( rv ) {
213                 Ext2_CloseFile(child);
214                 return NULL;
215         }
216         LEAVE_RET('p', child);
217 }
218
219 /**
220  * \brief Rename a file
221  * \param Node  This (directory) node
222  * \param OldName       Old name of file
223  * \param NewName       New name for file
224  * \return Boolean Failure - See ::tVFS_Node.Unlink for info
225  */
226 int Ext2_Unlink(tVFS_Node *Node, const char *OldName)
227 {
228         Log_Warning("Ext2", "TODO: Impliment Ext2_Unlink");
229         return 1;
230 }
231
232 /**
233  * \brief Links an existing node to a new name
234  * \param Parent        Parent (directory) node
235  * \param Name  New name for the node
236  * \param Node  Node to link
237  * \return Boolean Failure - See ::tVFS_Node.Link for info
238  */
239 int Ext2_Link(tVFS_Node *Node, const char *Name, tVFS_Node *Child)
240 {       
241         tExt2_Disk      *disk = Node->ImplPtr;
242         tExt2_Inode     *inode = (void*)(Node+1);
243         tExt2_DirEnt    *dirent;
244         tExt2_DirEnt    newEntry;
245         Uint64  base;   // Block's Base Address
246          int    block = 0, ofs = 0;
247         Uint    size;
248          int    bestMatch = -1;
249          int    bestSize=0, bestBlock=0, bestOfs=0, bestNeedsSplit=0;
250          int    nEntries;
251
252         ENTER("pNode sName pChild",
253                 Node, Name, Child);
254         
255         void *blockData = malloc(disk->BlockSize);
256         
257         // Create a stub entry
258         newEntry.inode = Child->Inode;
259         newEntry.name_len = strlen(Name);
260         newEntry.rec_len = ((newEntry.name_len+3)&~3) + EXT2_DIRENT_SIZE;
261         newEntry.type = inode->i_mode >> 12;
262         memcpy(newEntry.name, Name, newEntry.name_len);
263         
264         // Read directory's inode
265         size = inode->i_size;
266         
267         // Get a lock on the inode
268         //Ext2_int_LockInode(disk, Node->Inode);
269         Mutex_Acquire(&Node->Lock);
270
271 //      if( !Node->Data ) {
272 //      }
273
274         // Get First Block
275         // - Do this ourselves as it is a simple operation
276         base = inode->i_block[0] * disk->BlockSize;
277         VFS_ReadAt( disk->FD, base, disk->BlockSize, blockData );
278         block = 0;
279         nEntries = 0;
280         // Find File
281         while(size > 0)
282         {
283                 dirent = blockData + ofs;
284                 // Sanity Check the entry
285                 if(ofs + dirent->rec_len > disk->BlockSize) {
286                         Log_Warning("EXT2",
287                                 "Directory entry %i of inode 0x%x extends over a block boundary",
288                                 nEntries, (Uint)Node->Inode);
289                 }
290                 else
291                 {
292                         LOG("Entry %i: %x %i bytes", nEntries, dirent->type, dirent->rec_len);
293                         // Free entry
294                         if(dirent->type == 0)
295                         {
296                                 if( dirent->rec_len >= newEntry.rec_len
297                                  && (bestMatch == -1 || bestSize > dirent->rec_len) )
298                                 {
299                                         bestMatch = nEntries;
300                                         bestSize = dirent->rec_len;
301                                         bestBlock = block;
302                                         bestOfs = ofs;
303                                         bestNeedsSplit = 0;
304                                 }
305                         }
306                         // Non free - check name to avoid duplicates
307                         else
308                         {
309                                 LOG(" name='%.*s'", dirent->name_len, dirent->name);
310                                 if(strncmp(Name, dirent->name, dirent->name_len) == 0) {
311                                         //Ext2_int_UnlockInode(disk, Node->Inode);
312                                         goto _err;
313                                 }
314                                 
315                                  int    spare_space = dirent->rec_len - (dirent->name_len + EXT2_DIRENT_SIZE);
316                                 if( spare_space > newEntry.rec_len
317                                  && (bestMatch == -1 || bestSize > spare_space) )
318                                 {
319                                         bestMatch = nEntries;
320                                         bestSize = spare_space;
321                                         bestBlock = block;
322                                         bestOfs = ofs;
323                                         bestNeedsSplit = 1;
324                                 }
325                         }
326                 }
327                 
328                 // Increment the pointer
329                 nEntries ++;
330                 ofs += dirent->rec_len;
331                 size -= dirent->rec_len;
332                 if( size > 0 && ofs >= disk->BlockSize ) {
333                         // Read the next block if needed
334                 //      BLOCK_DIR_OFS(Node->Data, block) = nEntries;
335                         block ++;
336                         ofs = 0;
337                         base = Ext2_int_GetBlockAddr(disk, inode->i_block, block);
338                         VFS_ReadAt( disk->FD, base, disk->BlockSize, blockData );
339                 }
340         }
341         
342         LOG("bestMatch = %i", bestMatch);
343         // If EOF was reached with no space, check if we can fit one on the end
344         if( bestMatch < 0 && ofs + newEntry.rec_len < disk->BlockSize ) {
345                 Node->Size += newEntry.rec_len;
346                 Node->Flags |= VFS_FFLAG_DIRTY;
347                 bestBlock = block;
348                 bestOfs = ofs;
349                 bestSize = newEntry.rec_len;
350                 bestNeedsSplit = 0;
351         }
352         // Check if a free slot was found
353         if( bestMatch >= 0 )
354         {
355                 // Read-Modify-Write
356                 base = Ext2_int_GetBlockAddr(disk, inode->i_block, bestBlock);
357                 VFS_ReadAt( disk->FD, base, disk->BlockSize, blockData );
358                 dirent = blockData + bestOfs;
359                 // Shorten a pre-existing entry
360                 if(bestNeedsSplit)
361                 {
362                         dirent->rec_len = EXT2_DIRENT_SIZE + dirent->name_len;
363                         bestOfs += dirent->rec_len;
364                         //bestSize -= dirent->rec_len; // (not needed, bestSize is the spare space after)
365                         dirent = blockData + bestOfs;
366                 }
367                 // Insert new file entry
368                 memcpy(dirent, &newEntry, newEntry.rec_len);
369                 // Create a new blank entry
370                 if( bestSize != newEntry.rec_len )
371                 {
372                         bestOfs += newEntry.rec_len;
373                         dirent = blockData + bestOfs;
374
375                         dirent->rec_len = bestSize - newEntry.rec_len;                  
376                         dirent->type = 0;
377                 }
378                 // Save changes
379                 VFS_WriteAt( disk->FD, base, disk->BlockSize, blockData );
380         }
381         else {
382                 // Allocate block, Write
383                 Uint32 newblock = Ext2_int_AllocateBlock(disk, base / disk->BlockSize);
384                 Ext2_int_AppendBlock(Node, inode, newblock);
385                 base = newblock * disk->BlockSize;
386                 Node->Size += newEntry.rec_len;
387                 Node->Flags |= VFS_FFLAG_DIRTY;
388                 memcpy(blockData, &newEntry, newEntry.rec_len);
389                 memset(blockData + newEntry.rec_len, 0, disk->BlockSize - newEntry.rec_len);
390                 VFS_WriteAt( disk->FD, base, disk->BlockSize, blockData );
391         }
392
393         Child->ImplInt ++;
394         Child->Flags |= VFS_FFLAG_DIRTY;
395
396         //Ext2_int_UnlockInode(disk, Node->Inode);
397         free(blockData);
398         Mutex_Release(&Node->Lock);
399         LEAVE('i', 0);
400         return 0;
401 _err:
402         free(blockData);
403         Mutex_Release(&Node->Lock);
404         LEAVE('i', 1);
405         return 1;       // ERR_???
406 }
407

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