Misc Changes, Added Logging Subsystem, Fixes to InitRD, Working on RTL8139 driver
[tpg/acess2.git] / Modules / Filesystems / FAT / fat.c
1 /*\r
2  * Acess 2\r
3  * FAT12/16/32 Driver Version (Incl LFN)\r
4  */\r
5 #define DEBUG   0\r
6 #define VERBOSE 1\r
7 \r
8 #define CACHE_FAT       1       //!< Caches the FAT in memory\r
9 #define USE_LFN         1       //!< Enables the use of Long File Names\r
10 \r
11 #include <acess.h>\r
12 #include <modules.h>\r
13 #include <vfs.h>\r
14 #include "fs_fat.h"\r
15 \r
16 \r
17 // === TYPES ===\r
18 #if USE_LFN\r
19 typedef struct s_lfncache\r
20 {\r
21         Uint    Inode;\r
22         tFAT_VolInfo    *Disk;\r
23          int    id;\r
24         char    Name[256];\r
25         struct s_lfncache       *Next;\r
26 }       t_lfncache;\r
27 #endif\r
28 \r
29 // === PROTOTYPES ===\r
30  int    FAT_Install(char **Arguments);\r
31 tVFS_Node       *FAT_InitDevice(char *device, char **options);\r
32 void    FAT_Unmount(tVFS_Node *Node);\r
33 \r
34 Uint32  FAT_int_GetFatValue(tFAT_VolInfo *Disk, Uint32 Cluster);\r
35 Uint32  FAT_int_AllocateCluster(tFAT_VolInfo *Disk, Uint32 Previous);\r
36 \r
37 void    FAT_int_ReadCluster(tFAT_VolInfo *Disk, Uint32 Cluster, int Length, void *Buffer);\r
38 void    FAT_int_WriteCluster(tFAT_VolInfo *Disk, Uint32 Cluster, void *Buffer);\r
39 \r
40 Uint64  FAT_Read(tVFS_Node *Node, Uint64 Offset, Uint64 Length, void *Buffer);\r
41 Uint64  FAT_Write(tVFS_Node *Node, Uint64 Offset, Uint64 Length, void *Buffer);\r
42 char    *FAT_ReadDir(tVFS_Node *Node, int ID);\r
43 tVFS_Node       *FAT_FindDir(tVFS_Node *Node, char *Name);\r
44  int    FAT_Mknod(tVFS_Node *Node, char *Name, Uint Flags);\r
45  int    FAT_Relink(tVFS_Node *node, char *OldName, char *NewName);\r
46 void    FAT_CloseFile(tVFS_Node *node);\r
47 \r
48 // === SEMI-GLOBALS ===\r
49 MODULE_DEFINE(0, (0<<8)|50 /*v0.50*/, VFAT, FAT_Install, NULL, NULL);\r
50 tFAT_VolInfo    gFAT_Disks[8];\r
51  int    giFAT_PartCount = 0;\r
52 #if USE_LFN\r
53 t_lfncache      *fat_lfncache;\r
54 #endif\r
55 tVFS_Driver     gFAT_FSInfo = {"fat", 0, FAT_InitDevice, FAT_Unmount, NULL};\r
56 \r
57 // === CODE ===\r
58 /**\r
59  * \fn int FAT_Install(char **Arguments)\r
60  * \brief \r
61  */\r
62 int FAT_Install(char **Arguments)\r
63 {\r
64         VFS_AddDriver( &gFAT_FSInfo );\r
65         return MODULE_ERR_OK;\r
66 }\r
67 \r
68 /**\r
69  * \fn tVFS_Node *FAT_InitDevice(char *Device, char **Options)\r
70  * \brief Reads the boot sector of a disk and prepares the structures for it\r
71  */\r
72 tVFS_Node *FAT_InitDevice(char *Device, char **Options)\r
73 {\r
74         fat_bootsect *bs;\r
75          int    i;\r
76         Uint32  FATSz, RootDirSectors, TotSec;\r
77         tVFS_Node       *node = NULL;\r
78         tFAT_VolInfo    *diskInfo = &gFAT_Disks[giFAT_PartCount];\r
79         \r
80         // Temporary Pointer\r
81         bs = &diskInfo->bootsect;\r
82         \r
83         //Open device and read boot sector\r
84         diskInfo->fileHandle = VFS_Open(Device, VFS_OPENFLAG_READ|VFS_OPENFLAG_WRITE);\r
85         if(diskInfo->fileHandle == -1) {\r
86                 Warning("FAT_InitDisk - Unable to open device '%s'", Device);\r
87                 return NULL;\r
88         }\r
89         \r
90         VFS_ReadAt(diskInfo->fileHandle, 0, 512, bs);\r
91         \r
92         if(bs->bps == 0 || bs->spc == 0) {\r
93                 Warning("FAT_InitDisk - Error in FAT Boot Sector\n");\r
94                 return NULL;\r
95         }\r
96         \r
97         // FAT Type Determining\r
98         // - From Microsoft FAT Specifcation\r
99         RootDirSectors = ((bs->files_in_root*32) + (bs->bps - 1)) / bs->bps;\r
100         \r
101         if(bs->fatSz16 != 0)            FATSz = bs->fatSz16;\r
102         else                                    FATSz = bs->spec.fat32.fatSz32;\r
103         \r
104         if(bs->totalSect16 != 0)                TotSec = bs->totalSect16;\r
105         else                                            TotSec = bs->totalSect32;\r
106         \r
107         diskInfo->ClusterCount = (TotSec - (bs->resvSectCount + (bs->fatCount * FATSz) + RootDirSectors)) / bs->spc;\r
108         \r
109         if(diskInfo->ClusterCount < 4085)\r
110                 diskInfo->type = FAT12;\r
111         else if(diskInfo->ClusterCount < 65525)\r
112                 diskInfo->type = FAT16;\r
113         else\r
114                 diskInfo->type = FAT32;\r
115         \r
116         #if VERBOSE\r
117         {\r
118                 char    *sFatType, *sSize;\r
119                 Uint    iSize = diskInfo->ClusterCount * bs->spc * bs->bps / 1024;\r
120                 \r
121                 switch(diskInfo->type)\r
122                 {\r
123                 case FAT12:     sFatType = "FAT12";     break;\r
124                 case FAT16:     sFatType = "FAT16";     break;\r
125                 case FAT32:     sFatType = "FAT32";     break;\r
126                 default:        sFatType = "UNKNOWN";   break;\r
127                 }\r
128                 if(iSize <= 2*1024) {\r
129                         sSize = "KiB";\r
130                 }\r
131                 else if(iSize <= 2*1024*1024) {\r
132                         sSize = "MiB";\r
133                         iSize >>= 10;\r
134                 }\r
135                 else {\r
136                         sSize = "GiB";\r
137                         iSize >>= 20;\r
138                 }\r
139                 Log("[FAT ] '%s' %s, %i %s", Device, sFatType, iSize, sSize);\r
140         }\r
141         #endif\r
142         \r
143         // Get Name\r
144         if(diskInfo->type == FAT32) {\r
145                 for(i=0;i<11;i++)\r
146                         diskInfo->name[i] = (bs->spec.fat32.label[i] == ' ' ? '\0' : bs->spec.fat32.label[i]);\r
147         }\r
148         else {\r
149                 for(i=0;i<11;i++)\r
150                         diskInfo->name[i] = (bs->spec.fat16.label[i] == ' ' ? '\0' : bs->spec.fat16.label[i]);\r
151         }\r
152         diskInfo->name[11] = '\0';\r
153         \r
154         // Compute Root directory offset\r
155         if(diskInfo->type == FAT32)\r
156                 diskInfo->rootOffset = bs->spec.fat32.rootClust;\r
157         else\r
158                 diskInfo->rootOffset = (FATSz * bs->fatCount) / bs->spc;\r
159         \r
160         diskInfo->firstDataSect = bs->resvSectCount + (bs->fatCount * FATSz) + RootDirSectors;\r
161         \r
162         //Allow for Caching the FAT\r
163         #if CACHE_FAT\r
164         {\r
165         Uint32  Ofs;\r
166         diskInfo->FATCache = (Uint32*)malloc(sizeof(Uint32)*diskInfo->ClusterCount);\r
167         if(diskInfo->FATCache == NULL) {\r
168                 Warning("FAT_InitDisk - Heap Exhausted\n");\r
169                 return NULL;\r
170         }\r
171         Ofs = bs->resvSectCount*512;\r
172         if(diskInfo->type == FAT12)\r
173         {\r
174                 Uint32  val;\r
175                  int    j;\r
176                 char    buf[1536];\r
177                 for(i = 0; i < diskInfo->ClusterCount/2; i++) {\r
178                         j = i & 511;    //%512\r
179                         if( j == 0 ) {\r
180                                 VFS_ReadAt(diskInfo->fileHandle, Ofs, 3*512, buf);\r
181                                 Ofs += 3*512;\r
182                         }\r
183                         val = *((int*)(buf+j*3));\r
184                         diskInfo->FATCache[i*2] = val & 0xFFF;\r
185                         diskInfo->FATCache[i*2+1] = (val>>12) & 0xFFF;\r
186                 }\r
187         }\r
188         else if(diskInfo->type == FAT16)\r
189         {\r
190                 Uint16  buf[256];\r
191                 for(i=0;i<diskInfo->ClusterCount;i++) {\r
192                         if( (i & 255) == 0 ) {\r
193                                 VFS_ReadAt(diskInfo->fileHandle, Ofs, 512, buf);\r
194                                 Ofs += 512;\r
195                         }\r
196                         diskInfo->FATCache[i] = buf[i&255];\r
197                 }\r
198         }\r
199         else if(diskInfo->type == FAT32)\r
200         {\r
201                 Uint32  buf[128];\r
202                 for(i=0;i<diskInfo->ClusterCount;i++) {\r
203                         if( (i & 127) == 0 ) {\r
204                                 VFS_ReadAt(diskInfo->fileHandle, Ofs, 512, buf);\r
205                                 Ofs += 512;\r
206                         }\r
207                         diskInfo->FATCache[i] = buf[i&127];\r
208                 }\r
209         }\r
210         LOG("FAT Fully Cached");\r
211         }\r
212         #endif /*CACHE_FAT*/\r
213         \r
214         diskInfo->BytesPerCluster = bs->spc * bs->bps;\r
215         \r
216         // Initalise inode cache for filesystem\r
217         diskInfo->inodeHandle = Inode_GetHandle();\r
218         LOG("Inode Cache handle is %i", diskInfo->inodeHandle);\r
219         \r
220         // == VFS Interface\r
221         node = &diskInfo->rootNode;\r
222         node->Size = bs->files_in_root;\r
223         node->Inode = diskInfo->rootOffset;     // 0:31 - Cluster, 32:63 - Parent Directory Cluster\r
224         node->ImplPtr = diskInfo;       // Disk info pointer\r
225         node->ImplInt = 0;      // 0:15 - Directory Index, 16: Dirty Flag\r
226         \r
227         node->ReferenceCount = 1;\r
228         \r
229         node->UID = 0;  node->GID = 0;\r
230         node->NumACLs = 1;\r
231         node->ACLs = &gVFS_ACL_EveryoneRWX;\r
232         node->Flags = VFS_FFLAG_DIRECTORY;\r
233         node->CTime = node->MTime = node->ATime = now();\r
234         \r
235         node->Read = node->Write = NULL;\r
236         node->ReadDir = FAT_ReadDir;\r
237         node->FindDir = FAT_FindDir;\r
238         node->Relink = FAT_Relink;\r
239         node->MkNod = FAT_Mknod;\r
240         //node->Close = FAT_Unmount;\r
241         \r
242         giFAT_PartCount ++;\r
243         return node;\r
244 }\r
245 \r
246 /**\r
247  * \brief Closes a mount and marks it as free\r
248  * \param Node  Mount Root\r
249  * \r
250  * \todo Remove FAT Cache\r
251  * \todo Clear LFN Cache\r
252  * \todo Check that all files are closed and flushed\r
253  */\r
254 void FAT_Unmount(tVFS_Node *Node)\r
255 {\r
256         tFAT_VolInfo    *disk = Node->ImplPtr;\r
257         \r
258         // Close Disk Handle\r
259         VFS_Close( disk->fileHandle );\r
260         // Clear Node Cache\r
261         Inode_ClearCache(disk->inodeHandle);\r
262         // Mark as unused\r
263         disk->fileHandle = -2;\r
264         return;\r
265 }\r
266 \r
267 /*\r
268  * === FILE IO ===\r
269  */\r
270 /**\r
271  * \fn Uint32 FAT_int_GetFatValue(tFAT_VolInfo *Disk, Uint32 cluster)\r
272  * \brief Fetches a value from the FAT\r
273  */\r
274 Uint32 FAT_int_GetFatValue(tFAT_VolInfo *Disk, Uint32 cluster)\r
275 {\r
276         Uint32  val = 0;\r
277         #if !CACHE_FAT\r
278         Uint32  ofs = Disk->bootsect.resvSectCount*512;\r
279         #endif\r
280         ENTER("pDisk xCluster", Disk, cluster);\r
281         LOCK( &Disk->lFAT );\r
282         #if CACHE_FAT\r
283         val = Disk->FATCache[cluster];\r
284         if(Disk->type == FAT12 && val == EOC_FAT12)     val = -1;\r
285         if(Disk->type == FAT16 && val == EOC_FAT16)     val = -1;\r
286         if(Disk->type == FAT32 && val == EOC_FAT32)     val = -1;\r
287         #else\r
288         if(Disk->type == FAT12) {\r
289                 VFS_ReadAt(Disk->fileHandle, ofs+(cluster>>1)*3, 3, &val);\r
290                 val = (cluster&1 ? val&0xFFF : val>>12);\r
291                 if(val == EOC_FAT12)    val = -1;\r
292         } else if(Disk->type == FAT16) {\r
293                 VFS_ReadAt(Disk->fileHandle, ofs+cluster*2, 2, &val);\r
294                 if(val == EOC_FAT16)    val = -1;\r
295         } else {\r
296                 VFS_ReadAt(Disk->fileHandle, ofs+cluster*4, 4, &val);\r
297                 if(val == EOC_FAT32)    val = -1;\r
298         }\r
299         #endif /*CACHE_FAT*/\r
300         RELEASE( &Disk->lFAT );\r
301         LEAVE('x', val);\r
302         return val;\r
303 }\r
304 \r
305 /**\r
306  * \brief Allocate a new cluster\r
307  */\r
308 Uint32 FAT_int_AllocateCluster(tFAT_VolInfo *Disk, Uint32 Previous)\r
309 {\r
310         Uint32  ret = Previous;\r
311         #if CACHE_FAT\r
312         Uint32  eoc;\r
313         \r
314         LOCK(Disk->lFAT);\r
315         for(ret = Previous; ret < Disk->ClusterCount; ret++)\r
316         {\r
317                 if(Disk->FATCache[ret] == 0)\r
318                         goto append;\r
319         }\r
320         for(ret = 0; ret < Previous; ret++)\r
321         {\r
322                 if(Disk->FATCache[ret] == 0)\r
323                         goto append;\r
324         }\r
325         \r
326         RELEASE(Disk->lFAT);\r
327         return 0;\r
328         \r
329 append:\r
330         switch(Disk->type)\r
331         {\r
332         case FAT12:     eoc = EOC_FAT12;        break;\r
333         case FAT16:     eoc = EOC_FAT16;        break;\r
334         case FAT32:     eoc = EOC_FAT32;        break;\r
335         default:        return 0;\r
336         }\r
337         \r
338         Disk->FATCache[ret] = eoc;\r
339         Disk->FATCache[Previous] = ret;\r
340         \r
341         RELEASE(Disk->lFAT);\r
342         return ret;\r
343         #else\r
344         Uint32  val;\r
345         //Uint8 buf[512];\r
346         Warning("[FAT  ] TODO: Implement cluster allocation with non cached FAT");\r
347         return 0;\r
348         \r
349         if(Disk->type == FAT12) {\r
350                 VFS_ReadAt(Disk->fileHandle, ofs+(Previous>>1)*3, 3, &val);\r
351                 if( Previous & 1 ) {\r
352                         val &= 0xFFF000;\r
353                         val |= ret;\r
354                 }\r
355                 else {\r
356                         val &= 0xFFF;\r
357                         val |= ret<<12;\r
358                 }\r
359                 VFS_WriteAt(Disk->fileHandle, ofs+(Previous>>1)*3, 3, &val);\r
360                 \r
361                 VFS_ReadAt(Disk->fileHandle, ofs+(Cluster>>1)*3, 3, &val);\r
362                 if( Cluster & 1 ) {\r
363                         val &= 0xFFF000;\r
364                         val |= eoc;\r
365                 }\r
366                 else {\r
367                         val &= 0x000FFF;\r
368                         val |= eoc<<12;\r
369                 }\r
370                 VFS_WriteAt(Disk->fileHandle, ofs+(Cluster>>1)*3, 3, &val);\r
371         } else if(Disk->type == FAT16) {\r
372                 VFS_ReadAt(Disk->fileHandle, ofs+Previous*2, 2, &ret);\r
373                 VFS_ReadAt(Disk->fileHandle, ofs+Cluster*2, 2, &eoc);\r
374         } else {\r
375                 VFS_ReadAt(Disk->fileHandle, ofs+Previous*4, 4, &ret);\r
376                 VFS_ReadAt(Disk->fileHandle, ofs+Cluster*4, 4, &eoc);\r
377         }\r
378         return ret;\r
379         #endif\r
380 }\r
381 \r
382 /**\r
383  * \brief Read a cluster\r
384  */\r
385 void FAT_int_ReadCluster(tFAT_VolInfo *Disk, Uint32 Cluster, int Length, void *Buffer)\r
386 {\r
387         ENTER("pDisk xCluster iLength pBuffer", Disk, Cluster, Length, Buffer);\r
388         //Log("Cluster = %i (0x%x)", Cluster, Cluster);\r
389         VFS_ReadAt(\r
390                 Disk->fileHandle,\r
391                 (Disk->firstDataSect + (Cluster-2)*Disk->bootsect.spc )\r
392                         * Disk->bootsect.bps,\r
393                 Length,\r
394                 Buffer\r
395                 );\r
396         LEAVE('-');\r
397 }\r
398 \r
399 /**\r
400  * \brief Write a cluster to disk\r
401  */\r
402 void FAT_int_WriteCluster(tFAT_VolInfo *Disk, Uint32 Cluster, void *Buffer)\r
403 {\r
404         ENTER("pDisk xCluster pBuffer", Disk, Cluster, Buffer);\r
405         VFS_ReadAt(\r
406                 Disk->fileHandle,\r
407                 (Disk->firstDataSect + (Cluster-2)*Disk->bootsect.spc )\r
408                         * Disk->bootsect.bps,\r
409                 Disk->BytesPerCluster,\r
410                 Buffer\r
411                 );\r
412         LEAVE('-');\r
413 }\r
414 \r
415 /**\r
416  * \fn Uint64 FAT_Read(tVFS_Node *node, Uint64 offset, Uint64 length, void *buffer)\r
417  * \brief Reads data from a specified file\r
418  */\r
419 Uint64 FAT_Read(tVFS_Node *Node, Uint64 offset, Uint64 length, void *buffer)\r
420 {\r
421          int    preSkip, count;\r
422          int    i, cluster, pos;\r
423          int    bpc;\r
424         void    *tmpBuf;\r
425         tFAT_VolInfo    *disk = Node->ImplPtr;\r
426         \r
427         ENTER("pNode Xoffset Xlength pbuffer", Node, offset, length, buffer);\r
428         \r
429         // Calculate and Allocate Bytes Per Cluster\r
430         bpc = disk->BytesPerCluster;\r
431         tmpBuf = (void*) malloc(bpc);\r
432         if( !tmpBuf )   return 0;\r
433         \r
434         // Cluster is stored in Inode Field\r
435         cluster = Node->Inode & 0xFFFFFFFF;\r
436         \r
437         // Sanity Check offset\r
438         if(offset > Node->Size) {\r
439                 //LOG("Reading past EOF (%i > %i)", offset, node->Size);\r
440                 LEAVE('i', 0);\r
441                 return 0;\r
442         }\r
443         // Clamp Size\r
444         if(offset + length > Node->Size) {\r
445                 //LOG("Reading past EOF (%lli + %lli > %lli), clamped to %lli",\r
446                 //      offset, length, node->Size, node->Size - offset);\r
447                 length = Node->Size - offset;\r
448         }\r
449         \r
450         // Single Cluster including offset\r
451         if(length + offset < bpc)\r
452         {\r
453                 FAT_int_ReadCluster(disk, cluster, bpc, tmpBuf);\r
454                 memcpy( buffer, (void*)( tmpBuf + offset%bpc ), length );\r
455                 free(tmpBuf);\r
456                 LEAVE('i', 1);\r
457                 return length;\r
458         }\r
459         \r
460         preSkip = offset / bpc;\r
461         \r
462         //Skip previous clusters\r
463         for(i=preSkip;i--;)     {\r
464                 cluster = FAT_int_GetFatValue(disk, cluster);\r
465                 if(cluster == -1) {\r
466                         Warning("FAT_Read - Offset is past end of cluster chain mark");\r
467                         LEAVE('i', 0);\r
468                         return 0;\r
469                 }\r
470         }\r
471         \r
472         // Get Count of Clusters to read\r
473         count = ((offset%bpc+length) / bpc) + 1;\r
474         \r
475         // Get buffer Position after 1st cluster\r
476         pos = bpc - offset%bpc;\r
477         \r
478         // Read 1st Cluster\r
479         FAT_int_ReadCluster(disk, cluster, bpc, tmpBuf);\r
480         memcpy(\r
481                 buffer,\r
482                 (void*)( tmpBuf + (bpc-pos) ),\r
483                 (pos < length ? pos : length)\r
484                 );\r
485         \r
486         if (count == 1) {\r
487                 free(tmpBuf);\r
488                 LEAVE('i', 1);\r
489                 return length;\r
490         }\r
491         \r
492         cluster = FAT_int_GetFatValue(disk, cluster);\r
493         \r
494         #if DEBUG\r
495         LOG("pos=%i\n", pos);\r
496         LOG("Reading the rest of the clusters\n");\r
497         #endif\r
498         \r
499         \r
500         //Read the rest of the cluster data\r
501         for( i = 1; i < count-1; i++ )\r
502         {\r
503                 FAT_int_ReadCluster(disk, cluster, bpc, tmpBuf);\r
504                 memcpy((void*)(buffer+pos), tmpBuf, bpc);\r
505                 pos += bpc;\r
506                 cluster = FAT_int_GetFatValue(disk, cluster);\r
507                 if(cluster == -1) {\r
508                         Warning("FAT_Read - Read past End of Cluster Chain");\r
509                         free(tmpBuf);\r
510                         LEAVE('i', 0);\r
511                         return 0;\r
512                 }\r
513         }\r
514         \r
515         FAT_int_ReadCluster(disk, cluster, bpc, tmpBuf);\r
516         memcpy((void*)(buffer+pos), tmpBuf, length-pos);\r
517         \r
518         #if DEBUG\r
519         LOG("Free tmpBuf(0x%x) and Return\n", tmpBuf);\r
520         #endif\r
521         \r
522         free(tmpBuf);\r
523         LEAVE('X', length);\r
524         return length;\r
525 }\r
526 \r
527 /**\r
528  * \brief Write to a file\r
529  * \param Node  File Node\r
530  * \param Offset        Offset within file\r
531  * \param Length        Size of data to write\r
532  * \param Buffer        Data source\r
533  */\r
534 Uint64 FAT_Write(tVFS_Node *Node, Uint64 Offset, Uint64 Length, void *Buffer)\r
535 {\r
536         tFAT_VolInfo    *disk = Node->ImplPtr;\r
537         void    *tmpBuf;\r
538          int    remLength = Length;\r
539         Uint32  cluster, tmpCluster;\r
540          int    bNewCluster = 0;\r
541         \r
542         if(Offset > Node->Size) return 0;\r
543         \r
544         // Seek Clusters\r
545         cluster = Node->Inode & 0xFFFFFFFF;\r
546         while( Offset > disk->BytesPerCluster )\r
547         {\r
548                 cluster = FAT_int_GetFatValue( disk, cluster );\r
549                 if(cluster == -1) {\r
550                         Warning("[FAT  ] EOC Unexpectedly Reached");\r
551                         return 0;\r
552                 }\r
553                 Offset -= disk->BytesPerCluster;\r
554         }\r
555         if( Offset == disk->BytesPerCluster )\r
556         {\r
557                 Uint32  tmp = FAT_int_AllocateCluster(disk, cluster);\r
558                 if(!tmp)        return 0;\r
559                 cluster = tmp;\r
560                 Offset -= disk->BytesPerCluster;\r
561         }\r
562         \r
563         if( Offset + Length < disk->BytesPerCluster )\r
564         {\r
565                 tmpBuf = malloc( disk->BytesPerCluster );\r
566                 \r
567                 // Read-Modify-Write\r
568                 FAT_int_ReadCluster( disk, cluster, disk->BytesPerCluster, tmpBuf );\r
569                 memcpy( tmpBuf + Offset, Buffer, Length );\r
570                 FAT_int_WriteCluster( disk, cluster, tmpBuf );\r
571                 \r
572                 free(tmpBuf);\r
573                 return Length;\r
574         }\r
575         \r
576         // Clean up changes within a cluster\r
577         if( Offset )\r
578         {\r
579                 tmpBuf = malloc( disk->BytesPerCluster );\r
580                 \r
581                 // Read-Modify-Write\r
582                 FAT_int_ReadCluster( disk, cluster, disk->BytesPerCluster, tmpBuf );\r
583                 memcpy( tmpBuf + Offset, Buffer, disk->BytesPerCluster - Offset );\r
584                 FAT_int_WriteCluster( disk, cluster, tmpBuf );\r
585                 \r
586                 free(tmpBuf);\r
587                 \r
588                 remLength -= disk->BytesPerCluster - Offset;\r
589                 Buffer += disk->BytesPerCluster - Offset;\r
590                 \r
591                 // Get next cluster (allocating if needed)\r
592                 tmpCluster = FAT_int_GetFatValue(disk, cluster);\r
593                 if(tmpCluster == -1) {\r
594                         tmpCluster = FAT_int_AllocateCluster(disk, cluster);\r
595                         if( tmpCluster == 0 ) {\r
596                                 return Length - remLength;\r
597                         }\r
598                 }\r
599                 cluster = tmpCluster;\r
600         }\r
601         \r
602         while( remLength > disk->BytesPerCluster )\r
603         {\r
604                 FAT_int_WriteCluster( disk, cluster, Buffer );\r
605                 Buffer += disk->BytesPerCluster;\r
606                 \r
607                 // Get next cluster (allocating if needed)\r
608                 tmpCluster = FAT_int_GetFatValue(disk, cluster);\r
609                 if(tmpCluster == -1) {\r
610                         bNewCluster = 1;\r
611                         tmpCluster = FAT_int_AllocateCluster(disk, cluster);\r
612                         if( tmpCluster == 0 ) {\r
613                                 return Length - remLength;\r
614                         }\r
615                 }\r
616                 cluster = tmpCluster;\r
617         }\r
618         \r
619         // Finish off\r
620         tmpBuf = malloc( disk->BytesPerCluster );\r
621         if( bNewCluster )\r
622                 memset(tmpBuf, 0, disk->BytesPerCluster);\r
623         else\r
624                 FAT_int_ReadCluster( disk, cluster, disk->BytesPerCluster, tmpBuf );\r
625         memcpy( tmpBuf, Buffer, remLength );\r
626         FAT_int_WriteCluster( disk, cluster, tmpBuf );\r
627         free( tmpBuf );\r
628         \r
629         return Length;\r
630 }\r
631 \r
632 /**\r
633  * \fn void FAT_int_ProperFilename(char *dest, char *src)\r
634  * \brief Converts a FAT directory entry name into a proper filename\r
635  */\r
636 void FAT_int_ProperFilename(char *dest, char *src)\r
637 {\r
638          int    a, b;\r
639         \r
640         for( a = 0; a < 8; a++) {\r
641                 if(src[a] == ' ')       break;\r
642                 dest[a] = src[a];\r
643         }\r
644         b = a;\r
645         a = 8;\r
646         if(src[8] != ' ')\r
647                 dest[b++] = '.';\r
648         for( ; a < 11; a++, b++)        {\r
649                 if(src[a] == ' ')       break;\r
650                 dest[b] = src[a];\r
651         }\r
652         dest[b] = '\0';\r
653         #if DEBUG\r
654         //LOG("dest='%s'", dest);\r
655         #endif\r
656 }\r
657 \r
658 /**\r
659  * \fn char *FAT_int_CreateName(tVFS_Node *parent, fat_filetable *ft, char *LongFileName)\r
660  * \brief Converts either a LFN or a 8.3 Name into a proper name\r
661  */\r
662 char *FAT_int_CreateName(tVFS_Node *parent, fat_filetable *ft, char *LongFileName)\r
663 {\r
664         char    *ret;\r
665          int    len;\r
666         #if USE_LFN\r
667         if(LongFileName && LongFileName[0] != '\0')\r
668         {       \r
669                 len = strlen(LongFileName);\r
670                 ret = malloc(len+1);\r
671                 strcpy(ret, LongFileName);\r
672         }\r
673         else\r
674         {\r
675         #endif\r
676                 ret = (char*) malloc(13);\r
677                 memset(ret, 13, '\0');\r
678                 FAT_int_ProperFilename(ret, ft->name);\r
679         #if USE_LFN\r
680         }\r
681         #endif\r
682         return ret;\r
683 }\r
684 \r
685 /**\r
686  * \fn tVFS_Node *FAT_int_CreateNode(tVFS_Node *parent, fat_filetable *ft, char *LongFileName)\r
687  * \brief Creates a tVFS_Node structure for a given file entry\r
688  */\r
689 tVFS_Node *FAT_int_CreateNode(tVFS_Node *parent, fat_filetable *ft, char *LongFileName)\r
690 {\r
691         tVFS_Node       node = {0};\r
692         tVFS_Node       *ret;\r
693         tFAT_VolInfo    *disk = parent->ImplPtr;\r
694         \r
695         ENTER("pParent pFT sLongFileName", parent, ft, LongFileName);\r
696         \r
697         // Set Other Data\r
698         node.Inode = ft->cluster | (ft->clusterHi<<16);\r
699         node.Size = ft->size;\r
700         LOG("ft->size = %i", ft->size);\r
701         node.ImplPtr = parent->ImplPtr;\r
702         node.UID = 0;   node.GID = 0;\r
703         node.NumACLs = 1;\r
704         \r
705         node.Flags = 0;\r
706         if(ft->attrib & ATTR_DIRECTORY) node.Flags |= VFS_FFLAG_DIRECTORY;\r
707         if(ft->attrib & ATTR_READONLY) {\r
708                 node.Flags |= VFS_FFLAG_READONLY;\r
709                 node.ACLs = &gVFS_ACL_EveryoneRX;       // R-XR-XR-X\r
710         }\r
711         else {\r
712                 node.ACLs = &gVFS_ACL_EveryoneRWX;      // RWXRWXRWX\r
713         }\r
714         \r
715         node.ATime = timestamp(0,0,0,\r
716                         ((ft->adate&0x1F)-1),   //Days\r
717                         ((ft->adate&0x1E0)-1),          //Months\r
718                         1980+((ft->adate&0xFF00)>>8));  //Years\r
719         \r
720         node.CTime = ft->ctimems * 10;  //Miliseconds\r
721         node.CTime += timestamp(\r
722                         (ft->ctime&0x1F)<<1,    //Seconds\r
723                         ((ft->ctime&0x3F0)>>5), //Minutes\r
724                         ((ft->ctime&0xF800)>>11),       //Hours\r
725                         ((ft->cdate&0x1F)-1),           //Days\r
726                         ((ft->cdate&0x1E0)-1),          //Months\r
727                         1980+((ft->cdate&0xFF00)>>8));  //Years\r
728                         \r
729         node.MTime = timestamp(\r
730                         (ft->mtime&0x1F)<<1,    //Seconds\r
731                         ((ft->mtime&0x3F0)>>5), //Minuites\r
732                         ((ft->mtime&0xF800)>>11),       //Hours\r
733                         ((ft->mdate&0x1F)-1),           //Days\r
734                         ((ft->mdate&0x1E0)-1),          //Months\r
735                         1980+((ft->mdate&0xFF00)>>8));  //Years\r
736         \r
737         if(node.Flags & VFS_FFLAG_DIRECTORY) {\r
738                 node.ReadDir = FAT_ReadDir;\r
739                 node.FindDir = FAT_FindDir;\r
740                 node.MkNod = FAT_Mknod;\r
741                 node.Size = -1;\r
742         } else {\r
743                 node.Read = FAT_Read;\r
744                 node.Write = FAT_Write;\r
745         }\r
746         node.Close = FAT_CloseFile;\r
747         node.Relink = FAT_Relink;\r
748         \r
749         ret = Inode_CacheNode(disk->inodeHandle, &node);\r
750         LEAVE('p', ret);\r
751         return ret;\r
752 }\r
753 \r
754 #if USE_LFN\r
755 /**\r
756  \fn char *FAT_int_GetLFN(tVFS_Node *node)\r
757  \brief Return pointer to LFN cache entry\r
758  */\r
759 char *FAT_int_GetLFN(tVFS_Node *node)\r
760 {\r
761         t_lfncache      *tmp;\r
762         tmp = fat_lfncache;\r
763         while(tmp)\r
764         {\r
765                 if(tmp->Inode == node->Inode && tmp->Disk == node->ImplPtr)\r
766                         return tmp->Name;\r
767                 tmp = tmp->Next;\r
768         }\r
769         tmp = malloc(sizeof(t_lfncache));\r
770         tmp->Inode = node->Inode;\r
771         tmp->Disk = node->ImplPtr;\r
772         memset(tmp->Name, 0, 256);\r
773         \r
774         tmp->Next = fat_lfncache;\r
775         fat_lfncache = tmp;\r
776         \r
777         return tmp->Name;\r
778 }\r
779 \r
780 /**\r
781  \fn void FAT_int_DelLFN(tVFS_Node *node)\r
782  \brief Delete a LFN cache entry\r
783 */\r
784 void FAT_int_DelLFN(tVFS_Node *node)\r
785 {\r
786         t_lfncache      *tmp;\r
787         \r
788         if(!fat_lfncache)       return;\r
789         \r
790         if(!fat_lfncache->Next)\r
791         {\r
792                 tmp = fat_lfncache;\r
793                 fat_lfncache = tmp->Next;\r
794                 free(tmp);\r
795                 return;\r
796         }\r
797         tmp = fat_lfncache;\r
798         while(tmp && tmp->Next)\r
799         {\r
800                 if(tmp->Inode == node->Inode && tmp->Disk == node->ImplPtr)\r
801                 {\r
802                         free(tmp->Next);\r
803                         tmp->Next = tmp->Next->Next;\r
804                         return;\r
805                 }\r
806                 tmp = tmp->Next;\r
807         }\r
808 }\r
809 #endif\r
810 \r
811 /**\r
812  \fn char *FAT_ReadDir(tVFS_Node *Node, int ID)\r
813  \param Node    Node structure of directory\r
814  \param ID      Directory position\r
815 **/\r
816 char *FAT_ReadDir(tVFS_Node *Node, int ID)\r
817 {\r
818         fat_filetable   fileinfo[16];   //Sizeof=32, 16 per sector\r
819          int    a=0;\r
820         tFAT_VolInfo    *disk = Node->ImplPtr;\r
821         Uint32  cluster, offset;\r
822          int    preSkip;\r
823         char    *ret;\r
824         #if USE_LFN\r
825         char    *lfn = NULL;\r
826         #endif\r
827         \r
828         ENTER("pNode iID", Node, ID);\r
829         \r
830         // Get Byte Offset and skip\r
831         offset = ID * sizeof(fat_filetable);\r
832         preSkip = offset / (512 * disk->bootsect.spc);\r
833         LOG("disk->bootsect.spc = %i", disk->bootsect.spc);\r
834         LOG("Node->size = %i", Node->Size);\r
835         cluster = Node->Inode & 0xFFFFFFFF;     // Cluster ID\r
836         \r
837         // Do Cluster Skip\r
838         // - Pre FAT32 had a reserved area for the root.\r
839         if( disk->type == FAT32 || cluster != disk->rootOffset )\r
840         {\r
841                 //Skip previous clusters\r
842                 for(a=preSkip;a--;)     {\r
843                         cluster = FAT_int_GetFatValue(disk, cluster);\r
844                         // Check for end of cluster chain\r
845                         if(cluster == -1) {     LEAVE('n');     return NULL;}\r
846                 }\r
847         }\r
848         \r
849         // Bounds Checking (Used to spot heap overflows)\r
850         if(cluster > disk->ClusterCount + 2)\r
851         {\r
852                 Warning("FAT_ReadDir - Cluster ID is over cluster count (0x%x>0x%x)",\r
853                         cluster, disk->ClusterCount+2);\r
854                 LEAVE('n');\r
855                 return NULL;\r
856         }\r
857         \r
858         LOG("cluster=0x%x, ID=%i", cluster, ID);\r
859         \r
860         // Compute Offsets\r
861         // - Pre FAT32 cluster base (in sectors)\r
862         if( cluster == disk->rootOffset && disk->type != FAT32 )\r
863                 offset = disk->bootsect.resvSectCount + cluster*disk->bootsect.spc;\r
864         else\r
865         {       // FAT32 cluster base (in sectors)\r
866                 offset = disk->firstDataSect;\r
867                 offset += (cluster - 2) * disk->bootsect.spc;\r
868         }\r
869         // Sector in cluster\r
870         if(disk->bootsect.spc != 1)\r
871                 offset += (ID / 16) % disk->bootsect.spc;\r
872         // Offset in sector\r
873         a = ID % 16;\r
874 \r
875         LOG("offset=%i, a=%i", offset, a);\r
876         \r
877         // Read Sector\r
878         VFS_ReadAt(disk->fileHandle, offset*512, 512, fileinfo);        // Read Dir Data\r
879         \r
880         LOG("name[0] = 0x%x", (Uint8)fileinfo[a].name[0]);\r
881         //Check if this is the last entry\r
882         if( fileinfo[a].name[0] == '\0' ) {\r
883                 Node->Size = ID;\r
884                 LOG("End of list");\r
885                 LEAVE('n');\r
886                 return NULL;    // break\r
887         }\r
888         \r
889         // Check for empty entry\r
890         if( (Uint8)fileinfo[a].name[0] == 0xE5 ) {\r
891                 LOG("Empty Entry");\r
892                 LEAVE('p', VFS_SKIP);\r
893                 return VFS_SKIP;        // Skip\r
894         }\r
895         \r
896         #if USE_LFN\r
897         // Get Long File Name Cache\r
898         lfn = FAT_int_GetLFN(Node);\r
899         if(fileinfo[a].attrib == ATTR_LFN)\r
900         {\r
901                 fat_longfilename        *lfnInfo;\r
902                  int    len;\r
903                 \r
904                 lfnInfo = (fat_longfilename *) &fileinfo[a];\r
905                 if(lfnInfo->id & 0x40)  memset(lfn, 0, 256);\r
906                 // Get the current length\r
907                 len = strlen(lfn);\r
908                 \r
909                 // Sanity Check (FAT implementations should not allow >255 bytes)\r
910                 if(len + 13 > 255)      return VFS_SKIP;\r
911                 // Rebase all bytes\r
912                 for(a=len+1;a--;)       lfn[a+13] = lfn[a];\r
913                 \r
914                 // Append new bytes\r
915                 lfn[ 0] = lfnInfo->name1[0];    lfn[ 1] = lfnInfo->name1[1];\r
916                 lfn[ 2] = lfnInfo->name1[2];    lfn[ 3] = lfnInfo->name1[3];\r
917                 lfn[ 4] = lfnInfo->name1[4];    \r
918                 lfn[ 5] = lfnInfo->name2[0];    lfn[ 6] = lfnInfo->name2[1];\r
919                 lfn[ 7] = lfnInfo->name2[2];    lfn[ 8] = lfnInfo->name2[3];\r
920                 lfn[ 9] = lfnInfo->name2[4];    lfn[10] = lfnInfo->name2[5];\r
921                 lfn[11] = lfnInfo->name3[0];    lfn[12] = lfnInfo->name3[1];\r
922                 LEAVE('p', VFS_SKIP);\r
923                 return VFS_SKIP;\r
924         }\r
925         #endif\r
926         \r
927         //Check if it is a volume entry\r
928         if(fileinfo[a].attrib & 0x08) {\r
929                 LEAVE('p', VFS_SKIP);\r
930                 return VFS_SKIP;\r
931         }\r
932         // Ignore . and ..\r
933         if(fileinfo[a].name[0] == '.') {\r
934                 LEAVE('p', VFS_SKIP);\r
935                 return VFS_SKIP;\r
936         }       \r
937         \r
938         LOG("name='%c%c%c%c%c%c%c%c.%c%c%c'\n",\r
939                 fileinfo[a].name[0], fileinfo[a].name[1], fileinfo[a].name[2], fileinfo[a].name[3],\r
940                 fileinfo[a].name[4], fileinfo[a].name[5], fileinfo[a].name[6], fileinfo[a].name[7],\r
941                 fileinfo[a].name[8], fileinfo[a].name[9], fileinfo[a].name[10] );\r
942         \r
943         #if USE_LFN\r
944         //node = FAT_int_CreateNode(Node, &fileinfo[a], lfn);\r
945         ret = FAT_int_CreateName(Node, &fileinfo[a], lfn);\r
946         lfn[0] = '\0';\r
947         #else\r
948         //node = FAT_int_CreateNode(Node, &fileinfo[a], NULL);\r
949         ret = FAT_int_CreateName(Node, &fileinfo[a], NULL);\r
950         #endif\r
951         \r
952         LEAVE('s', ret);\r
953         return ret;\r
954 }\r
955 \r
956 /**\r
957  * \fn tVFS_Node *FAT_FindDir(tVFS_Node *node, char *name)\r
958  * \brief Finds an entry in the current directory\r
959  */\r
960 tVFS_Node *FAT_FindDir(tVFS_Node *Node, char *name)\r
961 {\r
962         fat_filetable   fileinfo[16];\r
963         char    tmpName[11];\r
964         #if USE_LFN\r
965         fat_longfilename        *lfnInfo;\r
966         char    *lfn = NULL;\r
967          int    lfnPos=255, lfnId = -1;\r
968         #endif\r
969          int    i=0;\r
970         tVFS_Node       *tmpNode;\r
971         Uint64  diskOffset;\r
972         tFAT_VolInfo    *disk = Node->ImplPtr;\r
973         Uint32  dirCluster;\r
974         Uint32  cluster;\r
975         \r
976         ENTER("pNode sname", Node, name);\r
977         \r
978         // Fast Returns\r
979         if(!name || name[0] == '\0') {\r
980                 LEAVE('n');\r
981                 return NULL;\r
982         }\r
983         \r
984         #if USE_LFN\r
985         lfn = FAT_int_GetLFN(Node);\r
986         #endif\r
987         \r
988         dirCluster = Node->Inode & 0xFFFFFFFF;\r
989         // Seek to Directory\r
990         if( dirCluster == disk->rootOffset && disk->type != FAT32 )\r
991                 diskOffset = (disk->bootsect.resvSectCount+dirCluster*disk->bootsect.spc) << 9;\r
992         else\r
993                 diskOffset = (disk->firstDataSect+(dirCluster-2)*disk->bootsect.spc) << 9;\r
994         \r
995         for(;;i++)\r
996         {\r
997                 // Load sector\r
998                 if((i & 0xF) == 0) {\r
999                         //Log("FAT_FindDir: diskOffset = 0x%x", diskOffset);\r
1000                         VFS_ReadAt(disk->fileHandle, diskOffset, 512, fileinfo);\r
1001                         diskOffset += 512;\r
1002                 }\r
1003                 \r
1004                 //Check if the files are free\r
1005                 if(fileinfo[i&0xF].name[0] == '\0')     break;          //Free and last\r
1006                 if(fileinfo[i&0xF].name[0] == '\xE5')   goto loadCluster;       //Free\r
1007                 \r
1008                 \r
1009                 #if USE_LFN\r
1010                 // Long File Name Entry\r
1011                 if(fileinfo[i&0xF].attrib == ATTR_LFN)\r
1012                 {\r
1013                         lfnInfo = (fat_longfilename *) &fileinfo[i&0xF];\r
1014                         if(lfnInfo->id & 0x40) {\r
1015                                 memset(lfn, 0, 256);\r
1016                                 lfnPos = 255;\r
1017                         }\r
1018                         lfn[lfnPos--] = lfnInfo->name3[1];      lfn[lfnPos--] = lfnInfo->name3[0];\r
1019                         lfn[lfnPos--] = lfnInfo->name2[5];      lfn[lfnPos--] = lfnInfo->name2[4];\r
1020                         lfn[lfnPos--] = lfnInfo->name2[3];      lfn[lfnPos--] = lfnInfo->name2[2];\r
1021                         lfn[lfnPos--] = lfnInfo->name2[1];      lfn[lfnPos--] = lfnInfo->name2[0];\r
1022                         lfn[lfnPos--] = lfnInfo->name1[4];      lfn[lfnPos--] = lfnInfo->name1[3];\r
1023                         lfn[lfnPos--] = lfnInfo->name1[2];      lfn[lfnPos--] = lfnInfo->name1[1];\r
1024                         lfn[lfnPos--] = lfnInfo->name1[0];\r
1025                         if((lfnInfo->id&0x3F) == 1)\r
1026                         {\r
1027                                 memcpy(lfn, lfn+lfnPos+1, 256-lfnPos);\r
1028                                 lfnId = i+1;\r
1029                         }\r
1030                 }\r
1031                 else\r
1032                 {\r
1033                         // Remove LFN if it does not apply\r
1034                         if(lfnId != i)  lfn[0] = '\0';\r
1035                 #endif\r
1036                         // Get Real Filename\r
1037                         FAT_int_ProperFilename(tmpName, fileinfo[i&0xF].name);\r
1038                 \r
1039                         LOG("tmpName = '%s'", tmpName);\r
1040                 \r
1041                         //Only Long name is case sensitive, 8.3 is not\r
1042                         #if USE_LFN\r
1043                         if(strucmp(tmpName, name) == 0 || strcmp(lfn, name) == 0) {\r
1044                         #else\r
1045                         if(strucmp(tmpName, name) == 0) {\r
1046                         #endif\r
1047                                 cluster = fileinfo[i&0xF].cluster | (fileinfo[i&0xF].clusterHi << 16);\r
1048                                 tmpNode = Inode_GetCache(disk->inodeHandle, cluster);\r
1049                                 if(tmpNode == NULL)     // Node is not cached\r
1050                                 {\r
1051                                         #if USE_LFN\r
1052                                         tmpNode = FAT_int_CreateNode(Node, &fileinfo[i&0xF], lfn);\r
1053                                         #else\r
1054                                         tmpNode = FAT_int_CreateNode(Node, &fileinfo[i&0xF], NULL);\r
1055                                         #endif\r
1056                                 }\r
1057                                 #if USE_LFN\r
1058                                 lfn[0] = '\0';\r
1059                                 #endif\r
1060                                 LEAVE('p', tmpNode);\r
1061                                 return tmpNode;\r
1062                         }\r
1063                 #if USE_LFN\r
1064                 }\r
1065                 #endif\r
1066                 \r
1067         loadCluster:\r
1068                 //Load Next cluster?\r
1069                 if( ((i+1) >> 4) % disk->bootsect.spc == 0 && ((i+1) & 0xF) == 0)\r
1070                 {\r
1071                         if( dirCluster == disk->rootOffset && disk->type != FAT32 )\r
1072                                 continue;\r
1073                         dirCluster = FAT_int_GetFatValue(disk, dirCluster);\r
1074                         if(dirCluster == -1)    break;\r
1075                         diskOffset = (disk->firstDataSect+(dirCluster-2)*disk->bootsect.spc)*512;\r
1076                 }\r
1077         }\r
1078         \r
1079         LEAVE('n');\r
1080         return NULL;\r
1081 }\r
1082 \r
1083 /**\r
1084  * \fn int FAT_Mknod(tVFS_Node *Node, char *Name, Uint Flags)\r
1085  * \brief Create a new node\r
1086  */\r
1087 int FAT_Mknod(tVFS_Node *Node, char *Name, Uint Flags)\r
1088 {\r
1089         return 0;\r
1090 }\r
1091 \r
1092 /**\r
1093  * \fn int FAT_Relink(tVFS_Node *Node, char *OldName, char *NewName)\r
1094  * \brief Rename / Delete a file\r
1095  */\r
1096 int FAT_Relink(tVFS_Node *Node, char *OldName, char *NewName)\r
1097 {\r
1098         return 0;\r
1099 }\r
1100 \r
1101 /**\r
1102  * \fn void FAT_CloseFile(tVFS_Node *Node)\r
1103  * \brief Close an open file\r
1104  */\r
1105 void FAT_CloseFile(tVFS_Node *Node)\r
1106 {\r
1107         tFAT_VolInfo    *disk = Node->ImplPtr;\r
1108         if(Node == NULL)        return ;\r
1109         \r
1110         Inode_UncacheNode(disk->inodeHandle, Node->Inode);\r
1111         #if USE_LFN\r
1112         // If node has been uncached and is a directory, delete the LFN cache\r
1113         if(     !Inode_GetCache(disk->inodeHandle, Node->Inode) && Node->Flags & VFS_FFLAG_DIRECTORY)\r
1114                 FAT_int_DelLFN(Node);\r
1115         else    // Get Cache references the node, so dereference it\r
1116                 Inode_UncacheNode(disk->inodeHandle, Node->Inode);\r
1117         #endif\r
1118         return ;\r
1119 }\r

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