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

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