+/*
+ * Acess2 FAT12/16/32 Driver
+ * - By John Hodge (thePowersGang)
+ *
+ * dir.c
+ * - Directory access/manipulation code
+ */
+#define DEBUG 1
+#include <acess.h>
+#include <vfs.h>
+#include "common.h"
+
+// === PROTOTYPES ===
+void FAT_int_ProperFilename(char *dest, const char *src);
+char *FAT_int_CreateName(fat_filetable *ft, const Uint16 *LongFileName);
+ int FAT_int_ConvertUTF16_to_UTF8(Uint8 *Dest, const Uint16 *Source);
+ int FAT_int_ConvertUTF8_to_UTF16(Uint16 *Dest, const Uint8 *Source);
+
+ int FAT_int_GetEntryByName(tVFS_Node *DirNode, const char *Name, fat_filetable *Entry);
+ int FAT_int_GetEntryByCluster(tVFS_Node *DirNode, Uint32 Cluster, fat_filetable *Entry);
+ int FAT_int_ReadDirSector(tVFS_Node *Node, int Sector, fat_filetable *Buffer);
+#if SUPPORT_WRITE
+ int FAT_int_WriteDirEntry(tVFS_Node *Node, int ID, fat_filetable *Entry);
+#endif
+#if USE_LFN
+Uint16 *FAT_int_GetLFN(tVFS_Node *Node, int ID);
+void FAT_int_DelLFN(tVFS_Node *Node, int ID);
+#endif
+char *FAT_ReadDir(tVFS_Node *Node, int ID);
+tVFS_Node *FAT_FindDir(tVFS_Node *Node, const char *Name);
+tVFS_Node *FAT_GetNodeFromINode(tVFS_Node *Root, Uint64 Inode);
+#if SUPPORT_WRITE
+ int FAT_Mknod(tVFS_Node *Node, const char *Name, Uint Flags);
+ int FAT_int_IsValid83Filename(const char *Name);
+ int FAT_Link(tVFS_Node *DirNode, const char *NewName, tVFS_Node *Node);
+ int FAT_Relink(tVFS_Node *node, const char *OldName, const char *NewName);
+#endif
+
+// === CODE ===
+
+/**
+ * \brief Converts a FAT directory entry name into a proper filename
+ * \param dest Destination array (must be at least 13 bytes in size)
+ * \param src 8.3 filename (concatenated, e.g 'FILE1 TXT')
+ */
+void FAT_int_ProperFilename(char *dest, const char *src)
+{
+ int inpos, outpos;
+
+ // Name
+ outpos = 0;
+ for( inpos = 0; inpos < 8; inpos++ ) {
+ if(src[inpos] == ' ') break;
+ dest[outpos++] = src[inpos];
+ }
+ inpos = 8;
+ // Check for empty extensions
+ if(src[8] != ' ')
+ {
+ dest[outpos++] = '.';
+ for( ; inpos < 11; inpos++) {
+ if(src[inpos] == ' ') break;
+ dest[outpos++] = src[inpos];
+ }
+ }
+ dest[outpos++] = '\0';
+
+ //LOG("dest='%s'", dest);
+}
+
+/**
+ * \fn char *FAT_int_CreateName(fat_filetable *ft, Uint8 *LongFileName)
+ * \brief Converts either a LFN or a 8.3 Name into a proper name
+ * \param ft Pointer to the file's entry in the parent directory
+ * \param LongFileName Long file name pointer
+ * \return Filename as a heap string
+ */
+char *FAT_int_CreateName(fat_filetable *ft, const Uint16 *LongFileName)
+{
+ char *ret;
+ ENTER("pft sLongFileName", ft, LongFileName);
+ //Log_Debug("FAT", "FAT_int_CreateName(ft=%p, LongFileName=%p'%s')", ft, LongFileName);
+ #if USE_LFN
+ if(LongFileName && LongFileName[0] != 0)
+ {
+ int len = FAT_int_ConvertUTF16_to_UTF8(NULL, LongFileName);
+ ret = malloc( len + 1 );
+ FAT_int_ConvertUTF16_to_UTF8((Uint8*)ret, LongFileName);
+ }
+ else
+ {
+ #endif
+ ret = (char*) malloc(13);
+ if( !ret ) {
+ Log_Warning("FAT", "FAT_int_CreateName: malloc(13) failed");
+ return NULL;
+ }
+ FAT_int_ProperFilename(ret, ft->name);
+ #if USE_LFN
+ }
+ #endif
+ LEAVE('s', ret);
+ return ret;
+}
+
+#if USE_LFN
+int FAT_int_CompareUTF16_UTF8(const Uint16 *Str16, const char *Str8)
+{
+ int pos16 = 0, pos8 = 0;
+ const Uint8 *str8 = (const Uint8 *)Str8;
+
+ while( Str16[pos16] && str8[pos8] )
+ {
+ Uint32 cp8, cp16;
+ if( Str16[pos16] & 0x8000 ) {
+ // Do something!
+ }
+ else {
+ cp16 = Str16[pos16];
+ pos16 ++;
+ }
+ pos8 += ReadUTF8(str8 + pos8, &cp8);
+
+ if(cp16 == cp8) continue ;
+
+ if(cp16 < cp8)
+ return -1;
+ else
+ return 1;
+ }
+ if(Str16[pos16] == str8[pos8])
+ return 0;
+ if(Str16[pos16] < str8[pos8])
+ return -1;
+ else
+ return 1;
+}
+
+int FAT_int_ConvertUTF16_to_UTF8(Uint8 *Dest, const Uint16 *Source)
+{
+ int len = 0;
+ for( ; *Source; Source ++ )
+ {
+ // TODO: Decode/Reencode
+ if( Dest )
+ Dest[len] = *Source;
+ len += 1;
+ }
+ if( Dest )
+ Dest[len] = 0;
+ return len;
+}
+
+int FAT_int_ConvertUTF8_to_UTF16(Uint16 *Dest, const Uint8 *Source)
+{
+ int len = 0;
+ for( ; *Source; Source ++ )
+ {
+ Uint32 cp;
+ int cpl;
+
+ cpl = ReadUTF8(Source, &cp);
+ if(cp < 0x8000) {
+ if( Dest )
+ Dest[len] = cp;
+ len ++;
+ }
+ else {
+ // TODO!
+ }
+ Source += cpl;
+ }
+ Dest[len] = 0;
+ return len;
+}
+
+int FAT_int_ParseLFN(const fat_filetable *Entry, Uint16 *Buffer)
+{
+ const fat_longfilename *lfnInfo;
+ int ofs;
+
+ lfnInfo = (const void*)Entry;
+
+ if(lfnInfo->id & 0x40) {
+ memset(Buffer, 0, 256*2);
+ }
+ ofs = (lfnInfo->id & 0x3F) * 13 - 1;
+ if( ofs >= 255 )
+ return -1;
+
+ Buffer[ofs--] = lfnInfo->name3[1]; Buffer[ofs--] = lfnInfo->name3[0];
+ Buffer[ofs--] = lfnInfo->name2[5]; Buffer[ofs--] = lfnInfo->name2[4];
+ Buffer[ofs--] = lfnInfo->name2[3]; Buffer[ofs--] = lfnInfo->name2[2];
+ Buffer[ofs--] = lfnInfo->name2[1]; Buffer[ofs--] = lfnInfo->name2[0];
+ Buffer[ofs--] = lfnInfo->name1[4]; Buffer[ofs--] = lfnInfo->name1[3];
+ Buffer[ofs--] = lfnInfo->name1[2]; Buffer[ofs--] = lfnInfo->name1[1];
+ Buffer[ofs--] = lfnInfo->name1[0];
+
+ if((lfnInfo->id&0x3F) == 1)
+ return 1;
+ return 0;
+}
+#endif
+
+int FAT_int_GetEntryByName(tVFS_Node *DirNode, const char *Name, fat_filetable *Entry)
+{
+ fat_filetable fileinfo[16];
+ char tmpName[13];
+ #if USE_LFN
+ Uint16 lfn[256];
+ int lfnId = -1;
+ #endif
+
+ for( int i = 0; ; i++ )
+ {
+ if((i & 0xF) == 0) {
+ if(FAT_int_ReadDirSector(DirNode, i/16, fileinfo))
+ {
+ LEAVE('i', -1);
+ return -1;
+ }
+ }
+
+ //Check if the files are free
+ if(fileinfo[i&0xF].name[0] == '\0') break; // End of List marker
+ if(fileinfo[i&0xF].name[0] == '\xE5') continue; // Free entry
+
+
+ #if USE_LFN
+ // Long File Name Entry
+ if(fileinfo[i & 0xF].attrib == ATTR_LFN)
+ {
+ if( FAT_int_ParseLFN(&fileinfo[i&0xF], lfn) )
+ lfnId = i+1;
+ continue ;
+ }
+ // Remove LFN if it does not apply
+ if(lfnId != i) lfn[0] = 0;
+ #else
+ if(fileinfo[i&0xF].attrib == ATTR_LFN) continue;
+ #endif
+
+ // Get Real Filename
+ FAT_int_ProperFilename(tmpName, fileinfo[i&0xF].name);
+// LOG("tmpName = '%s'", tmpName);
+// #if DEBUG
+// Debug_HexDump("FAT tmpName", tmpName, strlen(tmpName));
+// #endif
+/*
+ #if DEBUG && USE_LFN
+ if(lfnId == i)
+ {
+ Uint8 lfntmp[256*3+1];
+ FAT_int_ConvertUTF16_to_UTF8(lfntmp, lfn);
+ LOG("lfntmp = '%s'", lfntmp);
+ }
+ #endif
+*/
+
+ // Only the long name is case sensitive, 8.3 is not
+ #if USE_LFN
+ if(strucmp(tmpName, Name) == 0 || FAT_int_CompareUTF16_UTF8(lfn, Name) == 0)
+ #else
+ if(strucmp(tmpName, Name) == 0)
+ #endif
+ {
+ memcpy(Entry, fileinfo + (i&0xF), sizeof(*Entry));
+ LOG("Found %s at %i", Name, i);
+ LEAVE('i', i);
+ return i;
+ }
+ }
+
+ LEAVE('i', -1);
+ return -1;
+}
+
+int FAT_int_GetEntryByCluster(tVFS_Node *DirNode, Uint32 Cluster, fat_filetable *Entry)
+{
+ int ents_per_sector = 512 / sizeof(fat_filetable);
+ fat_filetable fileinfo[ents_per_sector];
+ int i, sector;
+
+ Mutex_Acquire(&DirNode->Lock);
+ sector = 0;
+ for( i = 0; ; i ++ )
+ {
+ if( i == 0 || i == ents_per_sector )
+ {
+ if(FAT_int_ReadDirSector(DirNode, sector, fileinfo))
+ {
+ LOG("ReadDirSector failed");
+ break ;
+ }
+ i = 0;
+ sector ++;
+ }
+
+ // Check for free/end of list
+ if(fileinfo[i].name[0] == '\0') break; // End of List marker
+ if(fileinfo[i].name[0] == '\xE5') continue; // Free entry
+
+ if(fileinfo[i].attrib == ATTR_LFN) continue;
+
+ LOG("fileinfo[i].cluster = %x %04x", fileinfo[i].clusterHi, fileinfo[i].cluster);
+ #if DEBUG
+ {
+ char tmpName[13];
+ FAT_int_ProperFilename(tmpName, fileinfo[i].name);
+ LOG("tmpName = '%s'", tmpName);
+ }
+ #endif
+
+
+ if(fileinfo[i].cluster != (Cluster & 0xFFFF)) continue;
+ if(fileinfo[i].clusterHi != ((Cluster >> 16) & 0xFFFF)) continue;
+
+ memcpy(Entry, &fileinfo[i], sizeof(*Entry));
+ Mutex_Release(&DirNode->Lock);
+ return i;
+ }
+
+ Mutex_Release(&DirNode->Lock);
+ return -1;
+}
+
+/*
+ * ====================
+ * Directory IO
+ * ====================
+ */
+
+/**
+ * \brief Reads a sector from the disk
+ * \param Node Directory node to read
+ * \param Sector Sector number in the directory to read
+ * \param Buffer Destination buffer for the read data
+ */
+int FAT_int_ReadDirSector(tVFS_Node *Node, int Sector, fat_filetable *Buffer)
+{
+ Uint64 addr;
+ tFAT_VolInfo *disk = Node->ImplPtr;
+
+ ENTER("pNode iSector pEntry", Node, Sector, Buffer);
+
+ // Parse address
+ if(FAT_int_GetAddress(Node, Sector * 512, &addr, NULL))
+ {
+ LEAVE('i', 1);
+ return 1;
+ }
+
+ LOG("addr = 0x%llx", addr);
+ // Read Sector
+ if(VFS_ReadAt(disk->fileHandle, addr, 512, Buffer) != 512)
+ {
+ LEAVE('i', 1);
+ return 1;
+ }
+
+ LEAVE('i', 0);
+ return 0;
+}
+
+#if SUPPORT_WRITE
+/**
+ * \brief Writes an entry to the disk
+ * \todo Support expanding a directory
+ * \param Node Directory node
+ * \param ID ID of entry to update
+ * \param Entry Entry data
+ * \return Zero on success, non-zero on error
+ */
+int FAT_int_WriteDirEntry(tVFS_Node *Node, int ID, fat_filetable *Entry)
+{
+ Uint64 addr = 0;
+ int tmp;
+ Uint32 cluster = 0;
+ tFAT_VolInfo *disk = Node->ImplPtr;
+
+ ENTER("pNode iID pEntry", Node, ID, Entry);
+
+ tmp = FAT_int_GetAddress(Node, ID * sizeof(fat_filetable), &addr, &cluster);
+ if( tmp )
+ {
+ //TODO: Allocate a cluster
+ cluster = FAT_int_AllocateCluster(Node->ImplPtr, cluster);
+ if(cluster == -1) {
+ Log_Warning("FAT", "Unable to allocate an other cluster for %p", Node);
+ LEAVE('i', 1);
+ return 1;
+ }
+ FAT_int_GetAddress(Node, ID * sizeof(fat_filetable), &addr, &cluster);
+ }
+
+
+ LOG("addr = 0x%llx", addr);
+
+ // Read Sector
+ VFS_WriteAt(disk->fileHandle, addr, sizeof(fat_filetable), Entry); // Read Dir Data
+
+ LEAVE('i', 0);
+ return 0;
+}
+#endif
+
+#if USE_LFN
+/**
+ * \fn Uint16 *FAT_int_GetLFN(tVFS_Node *node)
+ * \brief Return pointer to LFN cache entry
+ * \param Node Directory node
+ * \param ID ID of the short name
+ * \return Pointer to the LFN cache entry
+ */
+Uint16 *FAT_int_GetLFN(tVFS_Node *Node, int ID)
+{
+ tFAT_LFNCache *cache;
+ int i, firstFree;
+
+ Mutex_Acquire( &Node->Lock );
+
+ // TODO: Thread Safety (Lock things)
+ cache = Node->Data;
+
+ // Create a cache if it isn't there
+ if(!cache) {
+ cache = Node->Data = malloc( sizeof(tFAT_LFNCache) + sizeof(tFAT_LFNCacheEnt) );
+ cache->NumEntries = 1;
+ cache->Entries[0].ID = ID;
+ cache->Entries[0].Data[0] = 0;
+ Mutex_Release( &Node->Lock );
+ //Log_Debug("FAT", "Return = %p (new)", cache->Entries[0].Data);
+ return cache->Entries[0].Data;
+ }
+
+ // Scan for this entry
+ firstFree = -1;
+ for( i = 0; i < cache->NumEntries; i++ )
+ {
+ if( cache->Entries[i].ID == ID ) {
+ Mutex_Release( &Node->Lock );
+ //Log_Debug("FAT", "Return = %p (match)", cache->Entries[i].Data);
+ return cache->Entries[i].Data;
+ }
+ if( cache->Entries[i].ID == -1 && firstFree == -1 )
+ firstFree = i;
+ }
+
+ if(firstFree == -1) {
+ // Use `i` for temp length
+ i = sizeof(tFAT_LFNCache) + (cache->NumEntries+1)*sizeof(tFAT_LFNCacheEnt);
+ Node->Data = realloc( Node->Data, i );
+ if( !Node->Data ) {
+ Log_Error("FAT", "realloc() fail, unable to allocate %i for LFN cache", i);
+ Mutex_Release( &Node->Lock );
+ return NULL;
+ }
+ //Log_Debug("FAT", "Realloc (%i)\n", i);
+ cache = Node->Data;
+ i = cache->NumEntries;
+ cache->NumEntries ++;
+ }
+ else {
+ i = firstFree;
+ }
+
+ // Create new entry
+ cache->Entries[ i ].ID = ID;
+ cache->Entries[ i ].Data[0] = '\0';
+
+ Mutex_Release( &Node->Lock );
+ //Log_Debug("FAT", "Return = %p (firstFree, i = %i)", cache->Entries[i].Data, i);
+ return cache->Entries[ i ].Data;
+}
+
+/**
+ * \fn void FAT_int_DelLFN(tVFS_Node *node)
+ * \brief Delete a LFN cache entry
+ * \param Node Directory node
+ * \param ID File Entry ID
+ */
+void FAT_int_DelLFN(tVFS_Node *Node, int ID)
+{
+ tFAT_LFNCache *cache = Node->Data;
+ int i;
+
+ // Fast return
+ if(!cache) return;
+
+ // Scan for a current entry
+ for( i = 0; i < cache->NumEntries; i++ )
+ {
+ if( cache->Entries[i].ID == ID )
+ cache->Entries[i].ID = -1;
+ }
+ return ;
+}
+#endif
+
+/**
+ * \fn char *FAT_ReadDir(tVFS_Node *Node, int ID)
+ * \param Node Node structure of directory
+ * \param ID Directory position
+ * \return Filename as a heap string, NULL or VFS_SKIP
+ */
+char *FAT_ReadDir(tVFS_Node *Node, int ID)
+{
+ fat_filetable fileinfo[16]; // sizeof(fat_filetable)=32, so 16 per sector
+ int a;
+ char *ret;
+ #if USE_LFN
+ Uint16 *lfn = NULL;
+ #endif
+
+ ENTER("pNode iID", Node, ID);
+
+ if(FAT_int_ReadDirSector(Node, ID/16, fileinfo))
+ {
+ LOG("End of chain, end of dir");
+ LEAVE('n');
+ return NULL;
+ }
+
+ // Offset in sector
+ a = ID % 16;
+
+ LOG("fileinfo[%i].name[0] = 0x%x", a, (Uint8)fileinfo[a].name[0]);
+
+ // Check if this is the last entry
+ if( fileinfo[a].name[0] == '\0' ) {
+ Node->Size = ID;
+ LOG("End of list");
+ LEAVE('n');
+ return NULL; // break
+ }
+
+ // Check for empty entry
+ if( (Uint8)fileinfo[a].name[0] == 0xE5 ) {
+ LOG("Empty Entry");
+ #if 0 // Stop on empty entry?
+ LEAVE('n');
+ return NULL; // Stop
+ #else
+ LEAVE('p', VFS_SKIP);
+ return VFS_SKIP; // Skip
+ #endif
+ }
+
+ #if USE_LFN
+ // Get Long File Name Cache
+ if(fileinfo[a].attrib == ATTR_LFN)
+ {
+ fat_longfilename *lfnInfo;
+
+ lfnInfo = (fat_longfilename *) &fileinfo[a];
+
+ // Get cache for corresponding file
+ // > ID + Index gets the corresponding short node
+ lfn = FAT_int_GetLFN( Node, ID + (lfnInfo->id & 0x3F) );
+
+ a = FAT_int_ParseLFN(&fileinfo[a], lfn);
+ if( a < 0 ) {
+ LOG("Invalid LFN, error");
+ LEAVE('n');
+ return NULL;
+ }
+
+// LOG("lfn = '%s'", lfn);
+ //Log_Debug("FAT", "lfn = '%s'", lfn);
+ LEAVE('p', VFS_SKIP);
+ return VFS_SKIP;
+ }
+ #endif
+
+ // Check if it is a volume entry
+ if(fileinfo[a].attrib & 0x08) {
+ LEAVE('p', VFS_SKIP);
+ return VFS_SKIP;
+ }
+ // Ignore .
+ if(fileinfo[a].name[0] == '.' && fileinfo[a].name[1] == ' ') {
+ LEAVE('p', VFS_SKIP);
+ return VFS_SKIP;
+ }
+ // and ..
+ if(fileinfo[a].name[0] == '.' && fileinfo[a].name[1] == '.' && fileinfo[a].name[2] == ' ') {
+ LEAVE('p', VFS_SKIP);
+ return VFS_SKIP;
+ }
+
+ LOG("name='%c%c%c%c%c%c%c%c.%c%c%c'",
+ fileinfo[a].name[0], fileinfo[a].name[1], fileinfo[a].name[2], fileinfo[a].name[3],
+ fileinfo[a].name[4], fileinfo[a].name[5], fileinfo[a].name[6], fileinfo[a].name[7],
+ fileinfo[a].name[8], fileinfo[a].name[9], fileinfo[a].name[10] );
+
+ #if USE_LFN
+ lfn = FAT_int_GetLFN(Node, ID);
+ //Log_Debug("FAT", "lfn = %p'%s'", lfn, lfn);
+ ret = FAT_int_CreateName(&fileinfo[a], lfn);
+ #else
+ ret = FAT_int_CreateName(&fileinfo[a], NULL);
+ #endif
+
+ LEAVE('s', ret);
+ return ret;
+}
+
+/**
+ * \fn tVFS_Node *FAT_FindDir(tVFS_Node *node, char *name)
+ * \brief Finds an entry in the current directory
+ */
+tVFS_Node *FAT_FindDir(tVFS_Node *Node, const char *Name)
+{
+ fat_filetable fileent;
+
+ ENTER("pNode sname", Node, Name);
+
+ // Fast Returns
+ if(!Name || Name[0] == '\0') {
+ LEAVE('n');
+ return NULL;
+ }
+
+ if( FAT_int_GetEntryByName(Node, Name, &fileent) == -1 ) {
+ LEAVE('n');
+ return NULL;
+ }
+
+
+ tVFS_Node *ret = FAT_int_CreateNode(Node, &fileent);
+ LOG("Found %s as %p", Name, ret);
+ LEAVE('p', ret);
+ return ret;
+}
+
+tVFS_Node *FAT_GetNodeFromINode(tVFS_Node *Root, Uint64 Inode)
+{
+ tFAT_VolInfo *disk = Root->ImplPtr;
+ tVFS_Node *dirnode, *ret;
+ fat_filetable ft;
+
+ ENTER("pRoot XInode", Root, Inode);
+
+ ret = FAT_int_GetNode(disk, Inode & 0xFFFFFFFF);
+ if( ret ) {
+ if( (ret->Inode >> 32) != 0 ) {
+ LOG("Node in cache, quick return");
+ return ret;
+ }
+ else {
+ LOG("Node cached, but incomplete");
+ // Fall on through
+ }
+ ret = NULL;
+ }
+
+ dirnode = FAT_int_CreateIncompleteDirNode(disk, Inode >> 32);
+
+ int id = FAT_int_GetEntryByCluster(dirnode, Inode & 0xFFFFFFFF, &ft);
+ if( id != -1 ) {
+ ret = FAT_int_CreateNode(dirnode, &ft);
+ }
+
+ dirnode->Type->Close(dirnode);
+
+ LEAVE('p', ret);
+ return ret;
+}
+
+#if SUPPORT_WRITE
+/**
+ * \brief Create a new node
+ */
+int FAT_Mknod(tVFS_Node *DirNode, const char *Name, Uint Flags)
+{
+ tFAT_VolInfo *disk = DirNode->ImplPtr;
+ int rv;
+ fat_filetable ft;
+ memset(&ft, 0, sizeof(ft));
+
+ // Allocate a cluster
+ Uint32 cluster = FAT_int_AllocateCluster(disk, -1);
+ LOG("Cluster 0x%07x allocated", cluster);
+
+ // Create a temporary file table entry for an empty node
+ ft.cluster = cluster & 0xFFFF;
+ ft.clusterHi = cluster >> 16;
+ ft.size = 0;
+ if( Flags & VFS_FFLAG_DIRECTORY )
+ ft.attrib = ATTR_DIRECTORY;
+ else
+ ft.attrib = 0;
+
+ tVFS_Node *newnode = FAT_int_CreateNode(DirNode, &ft);
+ if( !newnode ) {
+ return -1;
+ }
+ LOG("newnode = %p", newnode);
+
+ // Call link
+ if( (rv = FAT_Link(DirNode, Name, newnode)) ) {
+ newnode->Flags |= FAT_FLAG_DELETE;
+ }
+ LOG("rv = %i", rv);
+ FAT_CloseFile(newnode);
+ return rv;
+}
+
+/**
+ * \brief Internal - Checks if a character is valid in an 8.3 filename
+ */
+static inline int is_valid_83_char(char ch)
+{
+ if( '0' <= ch && ch <= '9' )
+ return 1;
+ if( 'A' <= ch && ch <= 'Z' )
+ return 1;
+ return 0;
+}
+
+/**
+ * \brief Internal - Determines if a filename is a valid 8.3 filename
+ */
+int FAT_int_IsValid83Filename(const char *Name)
+{
+ int i, j;
+ // Check filename portion
+ for( i = 0; Name[i] && i < 8; i ++ )
+ {
+ if( Name[i] == '.' )
+ break;
+ if( !is_valid_83_char(Name[i]) )
+ return 0;
+ }
+ // If the next char is not \0 or '.', it's not valid
+ if( Name[i] && Name[i++] != '.' )
+ return 0;
+
+ // Check the extension portion
+ for( j = 0; Name[i+j] && j < 3; j ++ )
+ {
+ if( !is_valid_83_char(Name[i+j]) )
+ return 0;
+ }
+
+ // After the extension must be the end
+ if( !Name[i+j] )
+ return 0;
+
+ return 1;
+}
+
+/**
+ * \brief Create a new name for a file
+ * \note Since FAT doesn't support reference counting, this will cause double-references if
+ * a file is hardlinked and not unlinked
+ */
+int FAT_Link(tVFS_Node *DirNode, const char *NewName, tVFS_Node *NewNode)
+{
+ Uint16 lfn[256];
+ fat_filetable ft;
+ int nLFNEnt = 0;
+
+ // -- Create filetable entry --
+ int bNeedsLFN = !FAT_int_IsValid83Filename(NewName);
+ if( bNeedsLFN )
+ {
+ int lfnlen = FAT_int_ConvertUTF8_to_UTF16(lfn, (const Uint8*)NewName);
+ nLFNEnt = DivUp(lfnlen, 13);
+
+ // Create mangled filetable entry
+ // - Requires checking for duplicates
+ Log_Warning("FAT", "FAT_Link - LFN Mangling unimplimented");
+ }
+ else
+ {
+ // Create pure filetable entry
+ Log_Warning("FAT", "FAT_Link - Filename translation unimplimented");
+ }
+
+ ft.size = NewNode->Size;
+
+ // -- Add entry to the directory --
+ Mutex_Acquire( &DirNode->Lock );
+
+ // Locate a range of nLFNEnt + 1 free entries
+ // - If there are none, defragment the directory?
+ // - Else, expand the directory
+ // - and if that fails, return an error
+ Log_Warning("FAT", "FAT_Link - Free entry scanning unimplimented");
+
+ Mutex_Release( &DirNode->Lock );
+ return ENOTIMPL;
+}
+
+/**
+ * \fn int FAT_Relink(tVFS_Node *Node, char *OldName, char *NewName)
+ * \brief Rename / Delete a file
+ */
+int FAT_Unlink(tVFS_Node *Node, const char *OldName)
+{
+ tVFS_Node *child;
+ fat_filetable ft;
+
+ Mutex_Acquire(&Node->Lock);
+
+ int id = FAT_int_GetEntryByName(Node, OldName, &ft);
+ if(id == -1) {
+ Mutex_Release(&Node->Lock);
+ return ENOTFOUND;
+ }
+
+ child = FAT_int_CreateNode(Node->ImplPtr, &ft);
+ if( !child ) {
+ Mutex_Release(&Node->Lock);
+ return EINVAL;
+ }
+ child->ImplInt |= FAT_FLAG_DELETE; // Mark for deletion on close
+
+ // TODO: If it has a LFN, remove that too
+
+ // Delete from the directory
+ ft.name[0] = '\xE9';
+ FAT_int_WriteDirEntry(Node, id, &ft);
+
+ // Close child
+ child->Type->Close( child );
+ Mutex_Release( &Node->Lock );
+ return EOK;
+}
+#endif