2 * Acess2 FAT12/16/32 Driver
3 * - By John Hodge (thePowersGang)
6 * - Directory access/manipulation code
14 void FAT_int_ProperFilename(char *dest, const char *src);
15 char *FAT_int_CreateName(fat_filetable *ft, const Uint16 *LongFileName);
16 int FAT_int_ConvertUTF16_to_UTF8(Uint8 *Dest, const Uint16 *Source);
17 int FAT_int_ConvertUTF8_to_UTF16(Uint16 *Dest, const Uint8 *Source);
19 int FAT_int_GetEntryByName(tVFS_Node *DirNode, const char *Name, fat_filetable *Entry);
20 int FAT_int_GetEntryByCluster(tVFS_Node *DirNode, Uint32 Cluster, fat_filetable *Entry);
21 int FAT_int_ReadDirSector(tVFS_Node *Node, int Sector, fat_filetable *Buffer);
23 int FAT_int_WriteDirEntry(tVFS_Node *Node, int ID, fat_filetable *Entry);
26 Uint16 *FAT_int_GetLFN(tVFS_Node *Node, int ID);
27 void FAT_int_DelLFN(tVFS_Node *Node, int ID);
29 char *FAT_ReadDir(tVFS_Node *Node, int ID);
30 tVFS_Node *FAT_FindDir(tVFS_Node *Node, const char *Name);
31 tVFS_Node *FAT_GetNodeFromINode(tVFS_Node *Root, Uint64 Inode);
33 int FAT_Mknod(tVFS_Node *Node, const char *Name, Uint Flags);
34 int FAT_int_IsValid83Filename(const char *Name);
35 int FAT_Link(tVFS_Node *DirNode, const char *NewName, tVFS_Node *Node);
36 int FAT_Relink(tVFS_Node *node, const char *OldName, const char *NewName);
42 * \brief Converts a FAT directory entry name into a proper filename
43 * \param dest Destination array (must be at least 13 bytes in size)
44 * \param src 8.3 filename (concatenated, e.g 'FILE1 TXT')
46 void FAT_int_ProperFilename(char *dest, const char *src)
52 for( inpos = 0; inpos < 8; inpos++ ) {
53 if(src[inpos] == ' ') break;
54 dest[outpos++] = src[inpos];
57 // Check for empty extensions
61 for( ; inpos < 11; inpos++) {
62 if(src[inpos] == ' ') break;
63 dest[outpos++] = src[inpos];
66 dest[outpos++] = '\0';
68 //LOG("dest='%s'", dest);
72 * \fn char *FAT_int_CreateName(fat_filetable *ft, Uint8 *LongFileName)
73 * \brief Converts either a LFN or a 8.3 Name into a proper name
74 * \param ft Pointer to the file's entry in the parent directory
75 * \param LongFileName Long file name pointer
76 * \return Filename as a heap string
78 char *FAT_int_CreateName(fat_filetable *ft, const Uint16 *LongFileName)
81 ENTER("pft sLongFileName", ft, LongFileName);
82 //Log_Debug("FAT", "FAT_int_CreateName(ft=%p, LongFileName=%p'%s')", ft, LongFileName);
84 if(LongFileName && LongFileName[0] != 0)
86 int len = FAT_int_ConvertUTF16_to_UTF8(NULL, LongFileName);
87 ret = malloc( len + 1 );
88 FAT_int_ConvertUTF16_to_UTF8((Uint8*)ret, LongFileName);
93 ret = (char*) malloc(13);
95 Log_Warning("FAT", "FAT_int_CreateName: malloc(13) failed");
98 FAT_int_ProperFilename(ret, ft->name);
107 int FAT_int_CompareUTF16_UTF8(const Uint16 *Str16, const char *Str8)
109 int pos16 = 0, pos8 = 0;
110 const Uint8 *str8 = (const Uint8 *)Str8;
112 while( Str16[pos16] && str8[pos8] )
115 if( Str16[pos16] & 0x8000 ) {
122 pos8 += ReadUTF8(str8 + pos8, &cp8);
124 if(cp16 == cp8) continue ;
131 if(Str16[pos16] == str8[pos8])
133 if(Str16[pos16] < str8[pos8])
139 int FAT_int_ConvertUTF16_to_UTF8(Uint8 *Dest, const Uint16 *Source)
142 for( ; *Source; Source ++ )
144 // TODO: Decode/Reencode
154 int FAT_int_ConvertUTF8_to_UTF16(Uint16 *Dest, const Uint8 *Source)
157 for( ; *Source; Source ++ )
162 cpl = ReadUTF8(Source, &cp);
177 int FAT_int_ParseLFN(const fat_filetable *Entry, Uint16 *Buffer)
179 const fat_longfilename *lfnInfo;
182 lfnInfo = (const void*)Entry;
184 if(lfnInfo->id & 0x40) {
185 memset(Buffer, 0, 256*2);
187 ofs = (lfnInfo->id & 0x3F) * 13 - 1;
191 Buffer[ofs--] = lfnInfo->name3[1]; Buffer[ofs--] = lfnInfo->name3[0];
192 Buffer[ofs--] = lfnInfo->name2[5]; Buffer[ofs--] = lfnInfo->name2[4];
193 Buffer[ofs--] = lfnInfo->name2[3]; Buffer[ofs--] = lfnInfo->name2[2];
194 Buffer[ofs--] = lfnInfo->name2[1]; Buffer[ofs--] = lfnInfo->name2[0];
195 Buffer[ofs--] = lfnInfo->name1[4]; Buffer[ofs--] = lfnInfo->name1[3];
196 Buffer[ofs--] = lfnInfo->name1[2]; Buffer[ofs--] = lfnInfo->name1[1];
197 Buffer[ofs--] = lfnInfo->name1[0];
199 if((lfnInfo->id&0x3F) == 1)
205 int FAT_int_GetEntryByName(tVFS_Node *DirNode, const char *Name, fat_filetable *Entry)
207 fat_filetable fileinfo[16];
214 for( int i = 0; ; i++ )
217 if(FAT_int_ReadDirSector(DirNode, i/16, fileinfo))
224 //Check if the files are free
225 if(fileinfo[i&0xF].name[0] == '\0') break; // End of List marker
226 if(fileinfo[i&0xF].name[0] == '\xE5') continue; // Free entry
230 // Long File Name Entry
231 if(fileinfo[i & 0xF].attrib == ATTR_LFN)
233 if( FAT_int_ParseLFN(&fileinfo[i&0xF], lfn) )
237 // Remove LFN if it does not apply
238 if(lfnId != i) lfn[0] = 0;
240 if(fileinfo[i&0xF].attrib == ATTR_LFN) continue;
244 FAT_int_ProperFilename(tmpName, fileinfo[i&0xF].name);
245 // LOG("tmpName = '%s'", tmpName);
247 // Debug_HexDump("FAT tmpName", tmpName, strlen(tmpName));
253 Uint8 lfntmp[256*3+1];
254 FAT_int_ConvertUTF16_to_UTF8(lfntmp, lfn);
255 LOG("lfntmp = '%s'", lfntmp);
260 // Only the long name is case sensitive, 8.3 is not
262 if(strucmp(tmpName, Name) == 0 || FAT_int_CompareUTF16_UTF8(lfn, Name) == 0)
264 if(strucmp(tmpName, Name) == 0)
267 memcpy(Entry, fileinfo + (i&0xF), sizeof(*Entry));
268 LOG("Found %s at %i", Name, i);
278 int FAT_int_GetEntryByCluster(tVFS_Node *DirNode, Uint32 Cluster, fat_filetable *Entry)
280 int ents_per_sector = 512 / sizeof(fat_filetable);
281 fat_filetable fileinfo[ents_per_sector];
284 Mutex_Acquire(&DirNode->Lock);
288 if( i == 0 || i == ents_per_sector )
290 if(FAT_int_ReadDirSector(DirNode, sector, fileinfo))
292 LOG("ReadDirSector failed");
299 // Check for free/end of list
300 if(fileinfo[i].name[0] == '\0') break; // End of List marker
301 if(fileinfo[i].name[0] == '\xE5') continue; // Free entry
303 if(fileinfo[i].attrib == ATTR_LFN) continue;
305 LOG("fileinfo[i].cluster = %x %04x", fileinfo[i].clusterHi, fileinfo[i].cluster);
309 FAT_int_ProperFilename(tmpName, fileinfo[i].name);
310 LOG("tmpName = '%s'", tmpName);
315 if(fileinfo[i].cluster != (Cluster & 0xFFFF)) continue;
316 if(fileinfo[i].clusterHi != ((Cluster >> 16) & 0xFFFF)) continue;
318 memcpy(Entry, &fileinfo[i], sizeof(*Entry));
319 Mutex_Release(&DirNode->Lock);
323 Mutex_Release(&DirNode->Lock);
328 * ====================
330 * ====================
334 * \brief Reads a sector from the disk
335 * \param Node Directory node to read
336 * \param Sector Sector number in the directory to read
337 * \param Buffer Destination buffer for the read data
339 int FAT_int_ReadDirSector(tVFS_Node *Node, int Sector, fat_filetable *Buffer)
342 tFAT_VolInfo *disk = Node->ImplPtr;
344 ENTER("pNode iSector pEntry", Node, Sector, Buffer);
347 if(FAT_int_GetAddress(Node, Sector * 512, &addr, NULL))
353 LOG("addr = 0x%llx", addr);
355 if(VFS_ReadAt(disk->fileHandle, addr, 512, Buffer) != 512)
367 * \brief Writes an entry to the disk
368 * \todo Support expanding a directory
369 * \param Node Directory node
370 * \param ID ID of entry to update
371 * \param Entry Entry data
372 * \return Zero on success, non-zero on error
374 int FAT_int_WriteDirEntry(tVFS_Node *Node, int ID, fat_filetable *Entry)
379 tFAT_VolInfo *disk = Node->ImplPtr;
381 ENTER("pNode iID pEntry", Node, ID, Entry);
383 tmp = FAT_int_GetAddress(Node, ID * sizeof(fat_filetable), &addr, &cluster);
386 //TODO: Allocate a cluster
387 cluster = FAT_int_AllocateCluster(Node->ImplPtr, cluster);
389 Log_Warning("FAT", "Unable to allocate an other cluster for %p", Node);
393 FAT_int_GetAddress(Node, ID * sizeof(fat_filetable), &addr, &cluster);
397 LOG("addr = 0x%llx", addr);
400 VFS_WriteAt(disk->fileHandle, addr, sizeof(fat_filetable), Entry); // Read Dir Data
409 * \fn Uint16 *FAT_int_GetLFN(tVFS_Node *node)
410 * \brief Return pointer to LFN cache entry
411 * \param Node Directory node
412 * \param ID ID of the short name
413 * \return Pointer to the LFN cache entry
415 Uint16 *FAT_int_GetLFN(tVFS_Node *Node, int ID)
417 tFAT_LFNCache *cache;
420 Mutex_Acquire( &Node->Lock );
422 // TODO: Thread Safety (Lock things)
425 // Create a cache if it isn't there
427 cache = Node->Data = malloc( sizeof(tFAT_LFNCache) + sizeof(tFAT_LFNCacheEnt) );
428 cache->NumEntries = 1;
429 cache->Entries[0].ID = ID;
430 cache->Entries[0].Data[0] = 0;
431 Mutex_Release( &Node->Lock );
432 //Log_Debug("FAT", "Return = %p (new)", cache->Entries[0].Data);
433 return cache->Entries[0].Data;
436 // Scan for this entry
438 for( i = 0; i < cache->NumEntries; i++ )
440 if( cache->Entries[i].ID == ID ) {
441 Mutex_Release( &Node->Lock );
442 //Log_Debug("FAT", "Return = %p (match)", cache->Entries[i].Data);
443 return cache->Entries[i].Data;
445 if( cache->Entries[i].ID == -1 && firstFree == -1 )
449 if(firstFree == -1) {
450 // Use `i` for temp length
451 i = sizeof(tFAT_LFNCache) + (cache->NumEntries+1)*sizeof(tFAT_LFNCacheEnt);
452 Node->Data = realloc( Node->Data, i );
454 Log_Error("FAT", "realloc() fail, unable to allocate %i for LFN cache", i);
455 Mutex_Release( &Node->Lock );
458 //Log_Debug("FAT", "Realloc (%i)\n", i);
460 i = cache->NumEntries;
461 cache->NumEntries ++;
468 cache->Entries[ i ].ID = ID;
469 cache->Entries[ i ].Data[0] = '\0';
471 Mutex_Release( &Node->Lock );
472 //Log_Debug("FAT", "Return = %p (firstFree, i = %i)", cache->Entries[i].Data, i);
473 return cache->Entries[ i ].Data;
477 * \fn void FAT_int_DelLFN(tVFS_Node *node)
478 * \brief Delete a LFN cache entry
479 * \param Node Directory node
480 * \param ID File Entry ID
482 void FAT_int_DelLFN(tVFS_Node *Node, int ID)
484 tFAT_LFNCache *cache = Node->Data;
490 // Scan for a current entry
491 for( i = 0; i < cache->NumEntries; i++ )
493 if( cache->Entries[i].ID == ID )
494 cache->Entries[i].ID = -1;
501 * \fn char *FAT_ReadDir(tVFS_Node *Node, int ID)
502 * \param Node Node structure of directory
503 * \param ID Directory position
504 * \return Filename as a heap string, NULL or VFS_SKIP
506 char *FAT_ReadDir(tVFS_Node *Node, int ID)
508 fat_filetable fileinfo[16]; // sizeof(fat_filetable)=32, so 16 per sector
515 ENTER("pNode iID", Node, ID);
517 if(FAT_int_ReadDirSector(Node, ID/16, fileinfo))
519 LOG("End of chain, end of dir");
527 LOG("fileinfo[%i].name[0] = 0x%x", a, (Uint8)fileinfo[a].name[0]);
529 // Check if this is the last entry
530 if( fileinfo[a].name[0] == '\0' ) {
534 return NULL; // break
537 // Check for empty entry
538 if( (Uint8)fileinfo[a].name[0] == 0xE5 ) {
540 #if 0 // Stop on empty entry?
544 LEAVE('p', VFS_SKIP);
545 return VFS_SKIP; // Skip
550 // Get Long File Name Cache
551 if(fileinfo[a].attrib == ATTR_LFN)
553 fat_longfilename *lfnInfo;
555 lfnInfo = (fat_longfilename *) &fileinfo[a];
557 // Get cache for corresponding file
558 // > ID + Index gets the corresponding short node
559 lfn = FAT_int_GetLFN( Node, ID + (lfnInfo->id & 0x3F) );
561 a = FAT_int_ParseLFN(&fileinfo[a], lfn);
563 LOG("Invalid LFN, error");
568 // LOG("lfn = '%s'", lfn);
569 //Log_Debug("FAT", "lfn = '%s'", lfn);
570 LEAVE('p', VFS_SKIP);
575 // Check if it is a volume entry
576 if(fileinfo[a].attrib & 0x08) {
577 LEAVE('p', VFS_SKIP);
581 if(fileinfo[a].name[0] == '.' && fileinfo[a].name[1] == ' ') {
582 LEAVE('p', VFS_SKIP);
586 if(fileinfo[a].name[0] == '.' && fileinfo[a].name[1] == '.' && fileinfo[a].name[2] == ' ') {
587 LEAVE('p', VFS_SKIP);
591 LOG("name='%c%c%c%c%c%c%c%c.%c%c%c'",
592 fileinfo[a].name[0], fileinfo[a].name[1], fileinfo[a].name[2], fileinfo[a].name[3],
593 fileinfo[a].name[4], fileinfo[a].name[5], fileinfo[a].name[6], fileinfo[a].name[7],
594 fileinfo[a].name[8], fileinfo[a].name[9], fileinfo[a].name[10] );
597 lfn = FAT_int_GetLFN(Node, ID);
598 //Log_Debug("FAT", "lfn = %p'%s'", lfn, lfn);
599 ret = FAT_int_CreateName(&fileinfo[a], lfn);
601 ret = FAT_int_CreateName(&fileinfo[a], NULL);
609 * \fn tVFS_Node *FAT_FindDir(tVFS_Node *node, char *name)
610 * \brief Finds an entry in the current directory
612 tVFS_Node *FAT_FindDir(tVFS_Node *Node, const char *Name)
614 fat_filetable fileent;
616 ENTER("pNode sname", Node, Name);
619 if(!Name || Name[0] == '\0') {
624 if( FAT_int_GetEntryByName(Node, Name, &fileent) == -1 ) {
630 tVFS_Node *ret = FAT_int_CreateNode(Node, &fileent);
631 LOG("Found %s as %p", Name, ret);
636 tVFS_Node *FAT_GetNodeFromINode(tVFS_Node *Root, Uint64 Inode)
638 tFAT_VolInfo *disk = Root->ImplPtr;
639 tVFS_Node *dirnode, *ret;
642 ENTER("pRoot XInode", Root, Inode);
644 ret = FAT_int_GetNode(disk, Inode & 0xFFFFFFFF);
646 if( (ret->Inode >> 32) != 0 ) {
647 LOG("Node in cache, quick return");
651 LOG("Node cached, but incomplete");
657 dirnode = FAT_int_CreateIncompleteDirNode(disk, Inode >> 32);
659 int id = FAT_int_GetEntryByCluster(dirnode, Inode & 0xFFFFFFFF, &ft);
661 ret = FAT_int_CreateNode(dirnode, &ft);
664 dirnode->Type->Close(dirnode);
672 * \brief Create a new node
674 int FAT_Mknod(tVFS_Node *DirNode, const char *Name, Uint Flags)
676 tFAT_VolInfo *disk = DirNode->ImplPtr;
679 memset(&ft, 0, sizeof(ft));
681 // Allocate a cluster
682 Uint32 cluster = FAT_int_AllocateCluster(disk, -1);
683 LOG("Cluster 0x%07x allocated", cluster);
685 // Create a temporary file table entry for an empty node
686 ft.cluster = cluster & 0xFFFF;
687 ft.clusterHi = cluster >> 16;
689 if( Flags & VFS_FFLAG_DIRECTORY )
690 ft.attrib = ATTR_DIRECTORY;
694 tVFS_Node *newnode = FAT_int_CreateNode(DirNode, &ft);
698 LOG("newnode = %p", newnode);
701 if( (rv = FAT_Link(DirNode, Name, newnode)) ) {
702 newnode->Flags |= FAT_FLAG_DELETE;
705 FAT_CloseFile(newnode);
710 * \brief Internal - Checks if a character is valid in an 8.3 filename
712 static inline int is_valid_83_char(char ch)
714 if( '0' <= ch && ch <= '9' )
716 if( 'A' <= ch && ch <= 'Z' )
722 * \brief Internal - Determines if a filename is a valid 8.3 filename
724 int FAT_int_IsValid83Filename(const char *Name)
727 // Check filename portion
728 for( i = 0; Name[i] && i < 8; i ++ )
732 if( !is_valid_83_char(Name[i]) )
735 // If the next char is not \0 or '.', it's not valid
736 if( Name[i] && Name[i++] != '.' )
739 // Check the extension portion
740 for( j = 0; Name[i+j] && j < 3; j ++ )
742 if( !is_valid_83_char(Name[i+j]) )
746 // After the extension must be the end
754 * \brief Create a new name for a file
755 * \note Since FAT doesn't support reference counting, this will cause double-references if
756 * a file is hardlinked and not unlinked
758 int FAT_Link(tVFS_Node *DirNode, const char *NewName, tVFS_Node *NewNode)
764 // -- Create filetable entry --
765 int bNeedsLFN = !FAT_int_IsValid83Filename(NewName);
768 int lfnlen = FAT_int_ConvertUTF8_to_UTF16(lfn, (const Uint8*)NewName);
769 nLFNEnt = DivUp(lfnlen, 13);
771 // Create mangled filetable entry
772 // - Requires checking for duplicates
773 Log_Warning("FAT", "FAT_Link - LFN Mangling unimplimented");
777 // Create pure filetable entry
778 Log_Warning("FAT", "FAT_Link - Filename translation unimplimented");
781 ft.size = NewNode->Size;
783 // -- Add entry to the directory --
784 Mutex_Acquire( &DirNode->Lock );
786 // Locate a range of nLFNEnt + 1 free entries
787 // - If there are none, defragment the directory?
788 // - Else, expand the directory
789 // - and if that fails, return an error
790 Log_Warning("FAT", "FAT_Link - Free entry scanning unimplimented");
792 Mutex_Release( &DirNode->Lock );
797 * \fn int FAT_Relink(tVFS_Node *Node, char *OldName, char *NewName)
798 * \brief Rename / Delete a file
800 int FAT_Unlink(tVFS_Node *Node, const char *OldName)
805 Mutex_Acquire(&Node->Lock);
807 int id = FAT_int_GetEntryByName(Node, OldName, &ft);
809 Mutex_Release(&Node->Lock);
813 child = FAT_int_CreateNode(Node->ImplPtr, &ft);
815 Mutex_Release(&Node->Lock);
818 child->ImplInt |= FAT_FLAG_DELETE; // Mark for deletion on close
820 // TODO: If it has a LFN, remove that too
822 // Delete from the directory
824 FAT_int_WriteDirEntry(Node, id, &ft);
827 child->Type->Close( child );
828 Mutex_Release( &Node->Lock );