Files
nt-lite/code/drivers/fat32/fat32.c
T

1233 lines
43 KiB
C

// fat32.c - FAT32 filesystem driver
// Read + write support: file data, cluster allocation, directory entries.
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <io_driver.h>
#include <hal_api.h>
#include <io_file.h>
#include <mm_virt.h>
#include <ke_debug.h>
// ---------------------------------------------------------------------------
// 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;
}