// fat32.c - FAT32 filesystem driver // Read + write support: file data, cluster allocation, directory entries. #include #include #include #include #include #include #include #include // --------------------------------------------------------------------------- // On-disk structures // --------------------------------------------------------------------------- #pragma pack(push, 1) typedef struct { uint8_t Jump[3]; char OemId[8]; uint16_t BytesPerSector; uint8_t SectorsPerCluster; uint16_t ReservedSectors; uint8_t NumFats; uint16_t RootEntryCount; uint16_t TotalSectors16; uint8_t MediaType; uint16_t FatSize16; uint16_t SectorsPerTrack; uint16_t NumHeads; uint32_t HiddenSectors; uint32_t TotalSectors32; uint32_t FatSize32; uint16_t ExtFlags; uint16_t FsVersion; uint32_t RootCluster; uint16_t FsInfoSector; uint16_t BackupBootSector; uint8_t Reserved[12]; uint8_t DriveNumber; uint8_t Reserved1; uint8_t BootSignature; uint32_t VolumeId; char VolumeLabel[11]; char FsType[8]; } BPB; typedef struct { char Name[8]; char Ext[3]; uint8_t Attr; uint8_t Reserved; uint8_t CrtTimeTenth; uint16_t CrtTime; uint16_t CrtDate; uint16_t LastAccDate; uint16_t FirstClusterHi; uint16_t WrtTime; uint16_t WrtDate; uint16_t FirstClusterLo; uint32_t FileSize; } DIRENT; typedef struct { uint8_t Order; uint16_t Name1[5]; uint8_t Attr; uint8_t Type; uint8_t Checksum; uint16_t Name2[6]; uint16_t FirstCluster; uint16_t Name3[2]; } LFN_ENTRY; typedef struct { uint32_t Data1; uint16_t Data2; uint16_t Data3; uint8_t Data4[8]; } EFI_GUID; typedef struct { char Signature[8]; uint32_t Revision; uint32_t HeaderSize; uint32_t HeaderCRC32; uint32_t Reserved; uint64_t MyLBA; uint64_t AlternateLBA; uint64_t FirstUsableLBA; uint64_t LastUsableLBA; EFI_GUID DiskGUID; uint64_t PartitionEntryLBA; uint32_t NumberOfPartitionEntries; uint32_t SizeOfPartitionEntry; uint32_t PartitionEntryArrayCRC32; } GPT_HEADER; typedef struct { EFI_GUID PartitionTypeGUID; EFI_GUID UniquePartitionGUID; uint64_t StartingLBA; uint64_t EndingLBA; uint64_t Attributes; uint16_t PartitionName[36]; } GPT_ENTRY; #pragma pack(pop) #define ATTR_DIRECTORY 0x10 #define ATTR_ARCHIVE 0x20 #define ATTR_LFN 0x0F #define FAT32_EOC 0x0FFFFFF8u #define FAT32_EOC_MARK 0x0FFFFFFFu #define FAT32_FREE 0x00000000u // --------------------------------------------------------------------------- // Driver state // --------------------------------------------------------------------------- typedef struct { uint8_t DriveIndex; uint32_t PartitionLba; uint32_t BytesPerSector; uint32_t SectorsPerCluster; uint32_t BytesPerCluster; uint32_t ReservedSectors; uint32_t NumFats; uint32_t FatSize; // sectors per FAT copy uint32_t RootCluster; uint32_t DataStart; // absolute LBA of cluster 2 uint32_t TotalClusters; // usable data clusters const HAL_API* Hal; } FAT32_VOL; typedef struct { uint32_t StartCluster; uint32_t FileSize; FAT32_VOL* Vol; // Location of this file's DIRENT on disk (for in-place updates). uint64_t DirentLba; // LBA of the sector containing the DIRENT uint32_t DirentOffset; // byte offset of the DIRENT within that sector } FAT32_FILE; // --------------------------------------------------------------------------- // Low-level sector I/O // --------------------------------------------------------------------------- static KSTATUS ReadSectors(FAT32_VOL* v, uint64_t lba, uint32_t count, void* buf) { if (!v || !v->Hal || !v->Hal->Disk.ReadSectors) return STATUS_INVALID_PARAMETER; return v->Hal->Disk.ReadSectors(v->DriveIndex, lba, count, buf); } static KSTATUS WriteSectors(FAT32_VOL* v, uint64_t lba, uint32_t count, const void* buf) { if (!v || !v->Hal || !v->Hal->Disk.WriteSectors) return STATUS_INVALID_PARAMETER; return v->Hal->Disk.WriteSectors(v->DriveIndex, lba, count, buf); } static uint64_t ClusterToLba(FAT32_VOL* v, uint32_t cluster) { return v->DataStart + (uint64_t)(cluster - 2) * v->SectorsPerCluster; } // --------------------------------------------------------------------------- // FAT read / write // --------------------------------------------------------------------------- // Returns the FAT value for 'cluster', masked to 28 bits. Returns 0 on error. static uint32_t FatEntry(FAT32_VOL* v, uint32_t cluster) { uint32_t fatOffset = cluster * 4; uint64_t sector = v->PartitionLba + v->ReservedSectors + fatOffset / v->BytesPerSector; uint32_t offInSec = fatOffset % v->BytesPerSector; uint8_t* sec = (uint8_t*)MmAllocatePool(v->BytesPerSector); if (!sec) return 0; if (!KSUCCESS(ReadSectors(v, sector, 1, sec))) { MmFreePool(sec); return 0; } uint32_t val = *(uint32_t*)(sec + offInSec) & 0x0FFFFFFF; MmFreePool(sec); return val; } // Write 'value' (28-bit) into every FAT copy for 'cluster'. // Preserves the upper nibble of the existing entry as required by the spec. static KSTATUS WriteFatEntry(FAT32_VOL* v, uint32_t cluster, uint32_t value) { uint32_t fatOffset = cluster * 4; uint32_t sectorIdx = fatOffset / v->BytesPerSector; uint32_t offInSec = fatOffset % v->BytesPerSector; uint8_t* sec = (uint8_t*)MmAllocatePool(v->BytesPerSector); if (!sec) return STATUS_NO_MEMORY; for (uint32_t f = 0; f < v->NumFats; f++) { uint64_t lba = (uint64_t)v->PartitionLba + v->ReservedSectors + (uint64_t)f * v->FatSize + sectorIdx; KSTATUS s = ReadSectors(v, lba, 1, sec); if (!KSUCCESS(s)) { MmFreePool(sec); return s; } // Upper nibble is reserved; preserve it. uint32_t existing = *(uint32_t*)(sec + offInSec); *(uint32_t*)(sec + offInSec) = (existing & 0xF0000000u) | (value & 0x0FFFFFFFu); s = WriteSectors(v, lba, 1, sec); if (!KSUCCESS(s)) { MmFreePool(sec); return s; } } MmFreePool(sec); return STATUS_SUCCESS; } // --------------------------------------------------------------------------- // Cluster allocation // --------------------------------------------------------------------------- // Scan FAT for a free cluster, mark it EOC, zero its data, and return it. // Returns 0 if the disk is full or an error occurs. static uint32_t AllocCluster(FAT32_VOL* v) { uint8_t* sec = (uint8_t*)MmAllocatePool(v->BytesPerSector); if (!sec) return 0; uint32_t entriesPerSector = v->BytesPerSector / 4; uint64_t fatStart = (uint64_t)v->PartitionLba + v->ReservedSectors; uint32_t found = 0; for (uint32_t s = 0; s < v->FatSize && !found; s++) { if (!KSUCCESS(ReadSectors(v, fatStart + s, 1, sec))) break; for (uint32_t i = 0; i < entriesPerSector; i++) { uint32_t cluster = s * entriesPerSector + i; if (cluster < 2) continue; if (v->TotalClusters && cluster >= v->TotalClusters + 2) goto done; uint32_t val = *(uint32_t*)(sec + i * 4) & 0x0FFFFFFF; if (val == FAT32_FREE) { found = cluster; break; } } } done: MmFreePool(sec); if (!found) return 0; // Mark the cluster as end-of-chain. if (!KSUCCESS(WriteFatEntry(v, found, FAT32_EOC_MARK))) return 0; // Zero the cluster data (mandatory for correctness in directories). uint8_t* zero = (uint8_t*)MmAllocatePool(v->BytesPerCluster); if (zero) { memset(zero, 0, v->BytesPerCluster); WriteSectors(v, ClusterToLba(v, found), v->SectorsPerCluster, zero); MmFreePool(zero); } return found; } // Allocate a new cluster and link it after 'lastCluster'. Returns 0 on failure. static uint32_t AppendCluster(FAT32_VOL* v, uint32_t lastCluster) { uint32_t newCluster = AllocCluster(v); if (!newCluster) return 0; if (!KSUCCESS(WriteFatEntry(v, lastCluster, newCluster))) return 0; return newCluster; } // Walk the chain starting at 'start'; return the last cluster and optionally // the total cluster count. static uint32_t GetChainEnd(FAT32_VOL* v, uint32_t start, uint32_t* outCount) { uint32_t cluster = start; uint32_t prev = start; uint32_t count = 0; while (cluster >= 2 && cluster < FAT32_EOC) { prev = cluster; count++; cluster = FatEntry(v, cluster); } if (outCount) *outCount = count; return prev; } // Free every cluster in the chain starting at 'start'. static void FreeClusterChain(FAT32_VOL* v, uint32_t start) { uint32_t cluster = start; while (cluster >= 2 && cluster < FAT32_EOC) { uint32_t next = FatEntry(v, cluster); WriteFatEntry(v, cluster, FAT32_FREE); cluster = next; } } // --------------------------------------------------------------------------- // Directory entry (DIRENT) on-disk update helpers // --------------------------------------------------------------------------- static KSTATUS DirentUpdateSize(FAT32_VOL* v, uint64_t lba, uint32_t offset, uint32_t size) { uint8_t* sec = (uint8_t*)MmAllocatePool(v->BytesPerSector); if (!sec) return STATUS_NO_MEMORY; KSTATUS s = ReadSectors(v, lba, 1, sec); if (KSUCCESS(s)) { ((DIRENT*)(sec + offset))->FileSize = size; s = WriteSectors(v, lba, 1, sec); } MmFreePool(sec); return s; } static KSTATUS DirentUpdateFirstCluster(FAT32_VOL* v, uint64_t lba, uint32_t offset, uint32_t cluster) { uint8_t* sec = (uint8_t*)MmAllocatePool(v->BytesPerSector); if (!sec) return STATUS_NO_MEMORY; KSTATUS s = ReadSectors(v, lba, 1, sec); if (KSUCCESS(s)) { DIRENT* d = (DIRENT*)(sec + offset); d->FirstClusterHi = (uint16_t)(cluster >> 16); d->FirstClusterLo = (uint16_t)(cluster & 0xFFFF); s = WriteSectors(v, lba, 1, sec); } MmFreePool(sec); return s; } // --------------------------------------------------------------------------- // 8.3 name matching / assembly // --------------------------------------------------------------------------- static int MatchName83(const char* dirName, const char* dirExt, const char* want) { char buf[13]; int n = 0; for (int i = 0; i < 8 && dirName[i] != ' '; i++) buf[n++] = (char)(dirName[i] >= 'A' && dirName[i] <= 'Z' ? dirName[i] + 32 : dirName[i]); if (dirExt[0] != ' ') { buf[n++] = '.'; for (int i = 0; i < 3 && dirExt[i] != ' '; i++) buf[n++] = (char)(dirExt[i] >= 'A' && dirExt[i] <= 'Z' ? dirExt[i] + 32 : dirExt[i]); } buf[n] = '\0'; const char* p = buf; while (*want && *p) { char a = *p++, b = *want++; if (a >= 'A' && a <= 'Z') a += 32; if (b >= 'A' && b <= 'Z') b += 32; if (a != b) return 0; } return *want == '\0' && *p == '\0'; } // Generate a space-padded, uppercased 8.3 name from a plain filename. static void Make83Name(const char* src, char name8[8], char ext3[3]) { memset(name8, ' ', 8); memset(ext3, ' ', 3); const char* dot = NULL; for (const char* p = src; *p; p++) if (*p == '.') dot = p; int ni = 0; const char* end = dot ? dot : src + strlen(src); for (const char* p = src; p < end && ni < 8; p++) { char c = *p; if (c >= 'a' && c <= 'z') c -= 32; name8[ni++] = c; } if (dot) { int ei = 0; for (const char* p = dot + 1; *p && ei < 3; p++) { char c = *p; if (c >= 'a' && c <= 'z') c -= 32; ext3[ei++] = c; } } } // --------------------------------------------------------------------------- // LFN assembly / matching // --------------------------------------------------------------------------- #define MAX_LFN 256 static void LfnAppend(char* lfn, const LFN_ENTRY* e) { int seq = (e->Order & 0x3F) - 1; if (seq < 0 || seq >= (MAX_LFN / 13)) return; int pos = seq * 13; uint16_t chars[13]; int idx = 0; for (int i = 0; i < 5; i++) chars[idx++] = e->Name1[i]; for (int i = 0; i < 6; i++) chars[idx++] = e->Name2[i]; for (int i = 0; i < 2; i++) chars[idx++] = e->Name3[i]; for (int i = 0; i < 13 && pos < MAX_LFN - 1; i++, pos++) { uint16_t c = chars[i]; if (c == 0x0000 || c == 0xFFFF) { lfn[pos] = '\0'; break; } lfn[pos] = (char)(c & 0x7F); } } static int LfnMatch(const char* lfn, const char* want) { while (*lfn && *want) { char a = *lfn++, b = *want++; if (a >= 'A' && a <= 'Z') a += 32; if (b >= 'A' && b <= 'Z') b += 32; if (a != b) return 0; } return *lfn == '\0' && *want == '\0'; } // --------------------------------------------------------------------------- // Directory search // Passes back the on-disk location of the matched DIRENT when outDirentLba // and outDirentOffset are non-NULL. // --------------------------------------------------------------------------- static int SearchDir(FAT32_VOL* v, uint32_t dirCluster, const char* name, uint32_t* outCluster, uint32_t* outSize, uint8_t* outAttr, uint64_t* outDirentLba, // nullable uint32_t* outDirentOffset) // nullable { uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster); if (!clusterBuf) return 0; char lfn[MAX_LFN]; int hasLfn = 0; memset(lfn, 0, sizeof(lfn)); uint32_t cluster = dirCluster; while (cluster >= 2 && cluster < FAT32_EOC) { uint64_t clusterLba = ClusterToLba(v, cluster); if (!KSUCCESS(ReadSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf))) break; uint32_t entries = v->BytesPerCluster / sizeof(DIRENT); DIRENT* dir = (DIRENT*)clusterBuf; for (uint32_t i = 0; i < entries; i++) { if (dir[i].Name[0] == 0x00) goto done; if ((uint8_t)dir[i].Name[0] == 0xE5) { hasLfn = 0; memset(lfn, 0, sizeof(lfn)); continue; } if (dir[i].Attr == ATTR_LFN) { LfnAppend(lfn, (LFN_ENTRY*)&dir[i]); hasLfn = 1; continue; } int match = 0; if (hasLfn && lfn[0]) match = LfnMatch(lfn, name); if (!match) match = MatchName83(dir[i].Name, dir[i].Ext, name); hasLfn = 0; memset(lfn, 0, sizeof(lfn)); if (match) { *outCluster = ((uint32_t)dir[i].FirstClusterHi << 16) | dir[i].FirstClusterLo; *outSize = dir[i].FileSize; *outAttr = dir[i].Attr; // Compute the exact on-disk location of this DIRENT. if (outDirentLba || outDirentOffset) { uint64_t entryByte = (uint64_t)i * sizeof(DIRENT); if (outDirentLba) *outDirentLba = clusterLba + entryByte / v->BytesPerSector; if (outDirentOffset) *outDirentOffset = (uint32_t)(entryByte % v->BytesPerSector); } MmFreePool(clusterBuf); return 1; } } cluster = FatEntry(v, cluster); } done: MmFreePool(clusterBuf); return 0; } // --------------------------------------------------------------------------- // Path traversal (with optional DIRENT-location output) // --------------------------------------------------------------------------- static KSTATUS TraverseEx(FAT32_VOL* v, const char* path, uint32_t* outCluster, uint32_t* outSize, uint64_t* outDirentLba, // nullable uint32_t* outDirentOffset) // nullable { uint32_t cluster = v->RootCluster; uint32_t size = 0; uint8_t attr = ATTR_DIRECTORY; // Sentinel values: root directory has no DIRENT of its own. if (outDirentLba) *outDirentLba = 0; if (outDirentOffset) *outDirentOffset = 0; char component[256]; const char* p = path; while (*p) { while (*p == '\\' || *p == '/') p++; if (!*p) break; int len = 0; while (*p && *p != '\\' && *p != '/' && len < 255) component[len++] = *p++; component[len] = '\0'; if (!len) break; if (!(attr & ATTR_DIRECTORY)) return STATUS_NOT_FOUND; uint32_t childCluster, childSize; uint8_t childAttr; uint64_t childLba = 0; uint32_t childOffset = 0; if (!SearchDir(v, cluster, component, &childCluster, &childSize, &childAttr, &childLba, &childOffset)) return STATUS_NOT_FOUND; cluster = childCluster; size = childSize; attr = childAttr; if (outDirentLba) *outDirentLba = childLba; if (outDirentOffset) *outDirentOffset = childOffset; } *outCluster = cluster; *outSize = size; return STATUS_SUCCESS; } static KSTATUS Traverse(FAT32_VOL* v, const char* path, uint32_t* outCluster, uint32_t* outSize) { return TraverseEx(v, path, outCluster, outSize, NULL, NULL); } // --------------------------------------------------------------------------- // Path helpers for Fat32Create // --------------------------------------------------------------------------- // Split "/dir/sub/file.txt" into parent="/dir/sub" and name="file.txt". static void SplitPath(const char* path, char parent[256], char name[256]) { const char* last = NULL; for (const char* p = path; *p; p++) if (*p == '\\' || *p == '/') last = p; if (!last) { parent[0] = '\0'; strncpy(name, path, 255); name[255] = '\0'; } else { size_t len = (size_t)(last - path); if (len > 255) len = 255; memcpy(parent, path, len); parent[len] = '\0'; strncpy(name, last + 1, 255); name[255] = '\0'; } } // --------------------------------------------------------------------------- // Find a free DIRENT slot in the directory starting at 'dirCluster'. // Extends the directory with a new cluster if no slot is available. // Returns 1 on success and fills *outLba / *outOffset. // --------------------------------------------------------------------------- static int FindFreeDirent(FAT32_VOL* v, uint32_t dirCluster, uint64_t* outLba, uint32_t* outOffset) { uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster); if (!clusterBuf) return 0; uint32_t cluster = dirCluster; uint32_t prevCluster = dirCluster; while (cluster >= 2 && cluster < FAT32_EOC) { uint64_t clusterLba = ClusterToLba(v, cluster); if (!KSUCCESS(ReadSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf))) { MmFreePool(clusterBuf); return 0; } uint32_t entries = v->BytesPerCluster / sizeof(DIRENT); DIRENT* dir = (DIRENT*)clusterBuf; for (uint32_t i = 0; i < entries; i++) { uint8_t first = (uint8_t)dir[i].Name[0]; if (first == 0xE5 || first == 0x00) { uint64_t entryByte = (uint64_t)i * sizeof(DIRENT); *outLba = clusterLba + entryByte / v->BytesPerSector; *outOffset = (uint32_t)(entryByte % v->BytesPerSector); MmFreePool(clusterBuf); return 1; } } prevCluster = cluster; cluster = FatEntry(v, cluster); } // No free slot — extend the directory with one more cluster. uint32_t newCluster = AppendCluster(v, prevCluster); if (newCluster) { *outLba = ClusterToLba(v, newCluster); *outOffset = 0; MmFreePool(clusterBuf); return 1; } MmFreePool(clusterBuf); return 0; } // --------------------------------------------------------------------------- // IO_FILESYSTEM vtable — read path (unchanged logic, updated signatures) // --------------------------------------------------------------------------- static KSTATUS Fat32Open(IO_FILESYSTEM* fs, const char* path, void** fileCtx) { FAT32_VOL* v = (FAT32_VOL*)fs->DriverCtx; uint32_t cluster, size; uint64_t direntLba; uint32_t direntOffset; KSTATUS s = TraverseEx(v, path, &cluster, &size, &direntLba, &direntOffset); if (!KSUCCESS(s)) return s; FAT32_FILE* f = (FAT32_FILE*)MmAllocatePool(sizeof(FAT32_FILE)); if (!f) return STATUS_NO_MEMORY; f->StartCluster = cluster; f->FileSize = size; f->Vol = v; f->DirentLba = direntLba; f->DirentOffset = direntOffset; *fileCtx = f; return STATUS_SUCCESS; } static KSTATUS Fat32Read(IO_FILESYSTEM* fs, void* fileCtx, uint64_t offset, void* buf, size_t size, size_t* bytesRead) { (void)fs; FAT32_FILE* f = (FAT32_FILE*)fileCtx; FAT32_VOL* v = f->Vol; if (offset >= f->FileSize) { *bytesRead = 0; return STATUS_SUCCESS; } if (offset + size > f->FileSize) size = (size_t)(f->FileSize - offset); uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster); if (!clusterBuf) return STATUS_NO_MEMORY; size_t totalRead = 0; uint8_t* out = (uint8_t*)buf; uint32_t skipClusters = (uint32_t)(offset / v->BytesPerCluster); uint32_t offsetInCluster = (uint32_t)(offset % v->BytesPerCluster); uint32_t cluster = f->StartCluster; for (uint32_t i = 0; i < skipClusters && cluster < FAT32_EOC; i++) cluster = FatEntry(v, cluster); while (size > 0 && cluster >= 2 && cluster < FAT32_EOC) { if (!KSUCCESS(ReadSectors(v, ClusterToLba(v, cluster), v->SectorsPerCluster, clusterBuf))) break; size_t avail = v->BytesPerCluster - offsetInCluster; size_t copy = avail < size ? avail : size; memcpy(out, clusterBuf + offsetInCluster, copy); out += copy; totalRead += copy; size -= copy; offsetInCluster = 0; cluster = FatEntry(v, cluster); } MmFreePool(clusterBuf); *bytesRead = totalRead; return STATUS_SUCCESS; } // --------------------------------------------------------------------------- // IO_FILESYSTEM vtable — write path (new) // --------------------------------------------------------------------------- // Write up to 'size' bytes at 'offset' in the file, extending it as needed. // Performs a read-modify-write for any partial cluster at the head or tail. static KSTATUS Fat32Write(IO_FILESYSTEM* fs, void* fileCtx, uint64_t offset, const void* buf, size_t size, size_t* bytesWritten) { (void)fs; if (!size) { *bytesWritten = 0; return STATUS_SUCCESS; } FAT32_FILE* f = (FAT32_FILE*)fileCtx; FAT32_VOL* v = f->Vol; const uint8_t* src = (const uint8_t*)buf; // Ensure the file has at least one cluster. if (f->StartCluster < 2) { uint32_t first = AllocCluster(v); if (!first) { *bytesWritten = 0; return STATUS_NO_MEMORY; } f->StartCluster = first; DirentUpdateFirstCluster(v, f->DirentLba, f->DirentOffset, first); } // Walk the cluster chain to the cluster that contains 'offset', // allocating new clusters along the way if required. uint32_t clusterIndex = (uint32_t)(offset / v->BytesPerCluster); uint32_t offsetInCluster = (uint32_t)(offset % v->BytesPerCluster); uint32_t cluster = f->StartCluster; for (uint32_t i = 0; i < clusterIndex; i++) { uint32_t next = FatEntry(v, cluster); if (next < 2 || next >= FAT32_EOC) { next = AppendCluster(v, cluster); if (!next) { *bytesWritten = 0; return STATUS_NO_MEMORY; } } cluster = next; } // Write loop — one cluster per iteration. uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster); if (!clusterBuf) { *bytesWritten = 0; return STATUS_NO_MEMORY; } size_t written = 0; size_t remaining = size; while (remaining > 0 && cluster >= 2) { // Read first when this is a partial-cluster write (head or tail). int partial = (offsetInCluster > 0) || (remaining < v->BytesPerCluster); if (partial) { if (!KSUCCESS(ReadSectors(v, ClusterToLba(v, cluster), v->SectorsPerCluster, clusterBuf))) { break; } } size_t avail = v->BytesPerCluster - offsetInCluster; size_t copy = avail < remaining ? avail : remaining; memcpy(clusterBuf + offsetInCluster, src, copy); if (!KSUCCESS(WriteSectors(v, ClusterToLba(v, cluster), v->SectorsPerCluster, clusterBuf))) { break; } src += copy; written += copy; remaining -= copy; offsetInCluster = 0; if (remaining > 0) { uint32_t next = FatEntry(v, cluster); if (next < 2 || next >= FAT32_EOC) { next = AppendCluster(v, cluster); if (!next) break; // disk full } cluster = next; } } MmFreePool(clusterBuf); // Update file size in the directory entry if we extended the file. uint64_t newEnd = offset + written; if (newEnd > f->FileSize) { f->FileSize = (uint32_t)newEnd; DirentUpdateSize(v, f->DirentLba, f->DirentOffset, f->FileSize); } *bytesWritten = written; return (written == size) ? STATUS_SUCCESS : STATUS_DEVICE_ERROR; } // Create a new file at 'path'. The parent directory must already exist. // Returns an open FAT32_FILE context on success. static KSTATUS Fat32Create(IO_FILESYSTEM* fs, const char* path, void** fileCtx) { FAT32_VOL* v = (FAT32_VOL*)fs->DriverCtx; char parentPath[256], filename[256]; SplitPath(path, parentPath, filename); if (!filename[0]) return STATUS_INVALID_PARAMETER; // Resolve the parent directory. uint32_t parentCluster, parentSize; KSTATUS s = Traverse(v, parentPath, &parentCluster, &parentSize); if (!KSUCCESS(s)) return s; // Reject if a file with that name already exists. { uint32_t existCluster, existSize; uint8_t existAttr; if (SearchDir(v, parentCluster, filename, &existCluster, &existSize, &existAttr, NULL, NULL)) return STATUS_OBJECT_NAME_COLLISION; } // Find (or create) a free slot in the parent directory. uint64_t direntLba; uint32_t direntOffset; if (!FindFreeDirent(v, parentCluster, &direntLba, &direntOffset)) return STATUS_NO_MEMORY; // Build and write the 8.3 directory entry. uint8_t* sec = (uint8_t*)MmAllocatePool(v->BytesPerSector); if (!sec) return STATUS_NO_MEMORY; s = ReadSectors(v, direntLba, 1, sec); if (!KSUCCESS(s)) { MmFreePool(sec); return s; } DIRENT* d = (DIRENT*)(sec + direntOffset); memset(d, 0, sizeof(DIRENT)); Make83Name(filename, d->Name, d->Ext); d->Attr = ATTR_ARCHIVE; d->FirstClusterHi = 0; d->FirstClusterLo = 0; d->FileSize = 0; s = WriteSectors(v, direntLba, 1, sec); MmFreePool(sec); if (!KSUCCESS(s)) return s; // Allocate and return a file context. FAT32_FILE* f = (FAT32_FILE*)MmAllocatePool(sizeof(FAT32_FILE)); if (!f) return STATUS_NO_MEMORY; f->StartCluster = 0; f->FileSize = 0; f->Vol = v; f->DirentLba = direntLba; f->DirentOffset = direntOffset; *fileCtx = f; return STATUS_SUCCESS; } // Truncate or extend the file to exactly 'newSize' bytes. static KSTATUS Fat32Truncate(IO_FILESYSTEM* fs, void* fileCtx, uint64_t newSize) { (void)fs; FAT32_FILE* f = (FAT32_FILE*)fileCtx; FAT32_VOL* v = f->Vol; if ((uint32_t)newSize == f->FileSize) return STATUS_SUCCESS; if (newSize == 0) { if (f->StartCluster >= 2) { FreeClusterChain(v, f->StartCluster); f->StartCluster = 0; DirentUpdateFirstCluster(v, f->DirentLba, f->DirentOffset, 0); } f->FileSize = 0; return DirentUpdateSize(v, f->DirentLba, f->DirentOffset, 0); } uint32_t newClusters = (uint32_t) ((newSize + v->BytesPerCluster - 1) / v->BytesPerCluster); if (newSize < f->FileSize) { // Shorten: walk to the new last cluster, then free the tail. if (f->StartCluster < 2) goto update_size; // nothing to free uint32_t cluster = f->StartCluster; uint32_t lastGood = cluster; for (uint32_t i = 1; i < newClusters && cluster < FAT32_EOC; i++) { lastGood = cluster; cluster = FatEntry(v, cluster); } // lastGood is now the new end-of-chain. uint32_t tail = FatEntry(v, lastGood); WriteFatEntry(v, lastGood, FAT32_EOC_MARK); FreeClusterChain(v, tail); } else { // Extend: allocate the extra clusters (AllocCluster zeroes them). uint32_t curClusters = f->FileSize ? (f->FileSize + v->BytesPerCluster - 1) / v->BytesPerCluster : 0; uint32_t lastCluster = 0; if (f->StartCluster < 2) { // Empty file — allocate the first cluster. uint32_t first = AllocCluster(v); if (!first) return STATUS_NO_MEMORY; f->StartCluster = first; DirentUpdateFirstCluster(v, f->DirentLba, f->DirentOffset, first); lastCluster = first; curClusters = 1; } else { lastCluster = GetChainEnd(v, f->StartCluster, NULL); } for (uint32_t i = curClusters; i < newClusters; i++) { uint32_t next = AppendCluster(v, lastCluster); if (!next) return STATUS_NO_MEMORY; lastCluster = next; } } update_size: f->FileSize = (uint32_t)newSize; return DirentUpdateSize(v, f->DirentLba, f->DirentOffset, (uint32_t)newSize); } // Delete the file at 'path': marks its DIRENT as deleted and frees its clusters. static KSTATUS Fat32Delete(IO_FILESYSTEM* fs, const char* path) { FAT32_VOL* v = (FAT32_VOL*)fs->DriverCtx; uint32_t cluster, size; uint64_t direntLba; uint32_t direntOffset; KSTATUS s = TraverseEx(v, path, &cluster, &size, &direntLba, &direntOffset); if (!KSUCCESS(s)) return s; if (!direntLba) return STATUS_ACCESS_DENIED; // root directory // Mark the DIRENT as deleted (0xE5). uint8_t* sec = (uint8_t*)MmAllocatePool(v->BytesPerSector); if (!sec) return STATUS_NO_MEMORY; s = ReadSectors(v, direntLba, 1, sec); if (KSUCCESS(s)) { sec[direntOffset] = (uint8_t)0xE5; s = WriteSectors(v, direntLba, 1, sec); } MmFreePool(sec); if (!KSUCCESS(s)) return s; // Release the cluster chain. if (cluster >= 2) FreeClusterChain(v, cluster); return STATUS_SUCCESS; } static uint64_t Fat32GetSize(IO_FILESYSTEM* fs, void* fileCtx) { (void)fs; return ((FAT32_FILE*)fileCtx)->FileSize; } static void Fat32Close(IO_FILESYSTEM* fs, void* fileCtx) { (void)fs; MmFreePool(fileCtx); } // --------------------------------------------------------------------------- // Directory enumeration // --------------------------------------------------------------------------- typedef struct { FAT32_VOL* Vol; uint32_t DirCluster; // start cluster of the directory uint32_t CurCluster; // cluster currently being scanned uint32_t EntryIndex; // entry index within the current cluster char LfnBuf[MAX_LFN]; // accumulated LFN for the next entry int HasLfn; } FAT32_DIR_CTX; static KSTATUS Fat32OpenDir(IO_FILESYSTEM* fs, const char* dirPath, void** dirCtx) { FAT32_VOL* v = (FAT32_VOL*)fs->DriverCtx; uint32_t cluster, size; KSTATUS s; if (!dirPath || dirPath[0] == '\0') { // Empty path = root directory cluster = v->RootCluster; } else { s = Traverse(v, dirPath, &cluster, &size); if (!KSUCCESS(s)) return s; // The path must refer to a directory (size==0 for dirs in FAT32) // Traverse doesn't give us the attr here, but root-style open is fine; // if the caller passes a file path we'll just enumerate its "cluster" // harmlessly and get zero results. } FAT32_DIR_CTX* ctx = (FAT32_DIR_CTX*)MmAllocatePool(sizeof(FAT32_DIR_CTX)); if (!ctx) return STATUS_NO_MEMORY; ctx->Vol = v; ctx->DirCluster = cluster; ctx->CurCluster = cluster; ctx->EntryIndex = 0; ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); *dirCtx = ctx; return STATUS_SUCCESS; } static KSTATUS Fat32ReadDir(IO_FILESYSTEM* fs, void* dirCtx, KDIR_ENTRY* out) { (void)fs; FAT32_DIR_CTX* ctx = (FAT32_DIR_CTX*)dirCtx; FAT32_VOL* v = ctx->Vol; uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster); if (!clusterBuf) return STATUS_NO_MEMORY; uint32_t entriesPerCluster = v->BytesPerCluster / sizeof(DIRENT); while (ctx->CurCluster >= 2 && ctx->CurCluster < FAT32_EOC) { uint64_t clusterLba = ClusterToLba(v, ctx->CurCluster); if (!KSUCCESS(ReadSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf))) break; DIRENT* dir = (DIRENT*)clusterBuf; while (ctx->EntryIndex < entriesPerCluster) { DIRENT* e = &dir[ctx->EntryIndex]; ctx->EntryIndex++; // End-of-directory marker if (e->Name[0] == 0x00) { MmFreePool(clusterBuf); ctx->CurCluster = FAT32_EOC; // mark exhausted return STATUS_NO_MORE_FILES; } // Deleted entry if ((uint8_t)e->Name[0] == 0xE5) { ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); continue; } // LFN entry if (e->Attr == ATTR_LFN) { LfnAppend(ctx->LfnBuf, (LFN_ENTRY*)e); ctx->HasLfn = 1; continue; } // Skip volume label (attr 0x08) if (e->Attr & 0x08 && !(e->Attr & ATTR_DIRECTORY)) { ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); continue; } // Build display name: prefer LFN, fall back to 8.3 if (ctx->HasLfn && ctx->LfnBuf[0]) { size_t len = strlen(ctx->LfnBuf); if (len >= KDIR_NAME_MAX) len = KDIR_NAME_MAX - 1; memcpy(out->Name, ctx->LfnBuf, len); out->Name[len] = '\0'; } else { // Reconstruct 8.3 name char name83[13]; int n = 0; for (int i = 0; i < 8 && e->Name[i] != ' '; i++) name83[n++] = (char)(e->Name[i] >= 'A' && e->Name[i] <= 'Z' ? e->Name[i] + 32 : e->Name[i]); if (e->Ext[0] != ' ') { name83[n++] = '.'; for (int i = 0; i < 3 && e->Ext[i] != ' '; i++) name83[n++] = (char)(e->Ext[i] >= 'A' && e->Ext[i] <= 'Z' ? e->Ext[i] + 32 : e->Ext[i]); } name83[n] = '\0'; size_t len = (size_t)n; if (len >= KDIR_NAME_MAX) len = KDIR_NAME_MAX - 1; memcpy(out->Name, name83, len); out->Name[len] = '\0'; } out->Attr = e->Attr; out->FileSize = e->FileSize; ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); MmFreePool(clusterBuf); return STATUS_SUCCESS; } // Move to next cluster ctx->CurCluster = FatEntry(v, ctx->CurCluster); ctx->EntryIndex = 0; ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); } MmFreePool(clusterBuf); return STATUS_NO_MORE_FILES; } static void Fat32CloseDir(IO_FILESYSTEM* fs, void* dirCtx) { (void)fs; MmFreePool(dirCtx); } // --------------------------------------------------------------------------- // Partition scan (MBR + GPT) — unchanged // --------------------------------------------------------------------------- static const EFI_GUID GUID_EFI_SYSTEM = { 0xC12A7328, 0xF81F, 0x11D2, {0xBA,0x4B,0x00,0xA0,0xC9,0x3E,0xC9,0x3B} }; static const EFI_GUID GUID_MS_BASIC_DATA = { 0xEBD0A0A2, 0xB9E5, 0x4433, {0x87,0xC0,0x68,0xB6,0xB7,0x26,0x99,0xC7} }; static const EFI_GUID GUID_LINUX_BASIC_DATA = { 0x0FC63DAF, 0x8483, 0x4772, {0x8E,0x79,0x3D,0x69,0xD8,0x47,0x7D,0xE4} }; static int IsFat32Vbr(FAT32_VOL* v, uint32_t lba) { uint8_t sector[512]; if (!KSUCCESS(ReadSectors(v, lba, 1, sector))) return 0; if (sector[510] != 0x55 || sector[511] != 0xAA) return 0; BPB* bpb = (BPB*)sector; if (bpb->FatSize16 != 0 || bpb->RootEntryCount != 0) return 0; if (bpb->FatSize32 == 0) return 0; return 1; } static uint32_t FindFat32Partition(FAT32_VOL* v) { uint8_t mbr[512]; if (!KSUCCESS(ReadSectors(v, 0, 1, mbr))) return 0; if (mbr[510] != 0x55 || mbr[511] != 0xAA) return 0; int isGpt = 0; for (int i = 0; i < 4; i++) if (mbr[0x1BE + i * 16 + 4] == 0xEE) { isGpt = 1; break; } if (isGpt) { uint8_t gptSector[512]; if (!KSUCCESS(ReadSectors(v, 1, 1, gptSector))) return 0; GPT_HEADER* gpt = (GPT_HEADER*)gptSector; if (memcmp(gpt->Signature, "EFI PART", 8) != 0) return 0; uint64_t entryLBA = gpt->PartitionEntryLBA; uint32_t numEntries = gpt->NumberOfPartitionEntries; uint32_t entrySize = gpt->SizeOfPartitionEntry; if (entrySize < sizeof(GPT_ENTRY)) return 0; uint32_t entriesPerSector = 512 / entrySize; uint8_t entrySector[512]; for (uint32_t i = 0; i < numEntries; i++) { if (i % entriesPerSector == 0) { if (!KSUCCESS(ReadSectors(v, entryLBA + i / entriesPerSector, 1, entrySector))) return 0; } GPT_ENTRY* entry = (GPT_ENTRY*) (entrySector + (i % entriesPerSector) * entrySize); static const EFI_GUID emptyGuid = {0}; if (memcmp(&entry->PartitionTypeGUID, &emptyGuid, sizeof(EFI_GUID)) == 0) continue; if (memcmp(&entry->PartitionTypeGUID, &GUID_EFI_SYSTEM, sizeof(EFI_GUID)) == 0 || memcmp(&entry->PartitionTypeGUID, &GUID_MS_BASIC_DATA, sizeof(EFI_GUID)) == 0 || memcmp(&entry->PartitionTypeGUID, &GUID_LINUX_BASIC_DATA,sizeof(EFI_GUID)) == 0) { uint32_t lba = (uint32_t)entry->StartingLBA; if (IsFat32Vbr(v, lba)) return lba; } } return 0; } for (int i = 0; i < 4; i++) { uint8_t* entry = mbr + 0x1BE + i * 16; uint8_t type = entry[4]; if (type == 0x0B || type == 0x0C) { uint32_t lba = *(uint32_t*)(entry + 8); if (IsFat32Vbr(v, lba)) return lba; } } return 0; } // --------------------------------------------------------------------------- // DriverEntry // --------------------------------------------------------------------------- static FAT32_VOL gVol; static IO_FILESYSTEM gFs; KSTATUS DriverEntry(const HAL_API* hal) { DbgPrint("FAT32: Init\n"); if (!hal) return STATUS_INVALID_PARAMETER; gVol.Hal = hal; gVol.DriveIndex = 0; uint32_t partLba = FindFat32Partition(&gVol); if (!partLba) { DbgPrint("FAT32: No FAT32 volume found.\n"); return STATUS_NOT_SUPPORTED; } gVol.PartitionLba = partLba; uint8_t sector[512]; if (!KSUCCESS(ReadSectors(&gVol, partLba, 1, sector))) return STATUS_DEVICE_ERROR; BPB* bpb = (BPB*)sector; if (bpb->FatSize16 != 0 || bpb->RootEntryCount != 0) return STATUS_NOT_SUPPORTED; gVol.BytesPerSector = bpb->BytesPerSector; gVol.SectorsPerCluster = bpb->SectorsPerCluster; gVol.BytesPerCluster = bpb->BytesPerSector * bpb->SectorsPerCluster; gVol.ReservedSectors = bpb->ReservedSectors; gVol.NumFats = bpb->NumFats; gVol.FatSize = bpb->FatSize32; gVol.RootCluster = bpb->RootCluster; gVol.DataStart = partLba + bpb->ReservedSectors + bpb->NumFats * bpb->FatSize32; uint32_t totalSectors = bpb->TotalSectors16 ? bpb->TotalSectors16 : bpb->TotalSectors32; uint32_t dataSectors = totalSectors - bpb->ReservedSectors - bpb->NumFats * bpb->FatSize32; gVol.TotalClusters = dataSectors / bpb->SectorsPerCluster; gFs.Open = Fat32Open; gFs.Read = Fat32Read; gFs.Write = Fat32Write; gFs.Create = Fat32Create; gFs.Delete = Fat32Delete; gFs.Truncate = Fat32Truncate; gFs.GetSize = Fat32GetSize; gFs.Close = Fat32Close; gFs.DriverCtx = &gVol; gFs.OpenDir = Fat32OpenDir; gFs.ReadDir = Fat32ReadDir; gFs.CloseDir = Fat32CloseDir; IO_VOLUME_PARAMS volParams; volParams.DiskIndex = gVol.DriveIndex; volParams.PartitionLba = partLba; volParams.PartitionSize = totalSectors; KSTATUS status = IoRegisterFilesystem(gVol.DriveIndex, &gFs, &volParams); DbgPrint("FAT32: Init complete (status=%d, clusters=%u).\n", status, gVol.TotalClusters); return STATUS_SUCCESS; }