299 lines
11 KiB
C
299 lines
11 KiB
C
// FAT mount discovery, BPB validation, and driver lifetime.
|
|
|
|
#include "fat_internal.h"
|
|
|
|
// FAT BPB validation.
|
|
typedef struct FAT_BPB_LAYOUT {
|
|
FAT_VARIANT Variant;
|
|
uint32_t TotalSectors;
|
|
uint32_t FatSize;
|
|
uint32_t RootDirSectors;
|
|
uint32_t DataSectors;
|
|
uint32_t TotalClusters;
|
|
} FAT_BPB_LAYOUT;
|
|
|
|
static int FatComputeBpbLayout(const BPB* bpb, FAT_BPB_LAYOUT* out) {
|
|
if (!bpb || !out) return 0;
|
|
memset(out, 0, sizeof(*out));
|
|
|
|
if (bpb->BytesPerSector != 512) return 0;
|
|
if (bpb->SectorsPerCluster == 0) return 0;
|
|
if (bpb->NumFats == 0) return 0;
|
|
if (bpb->ReservedSectors == 0) return 0;
|
|
|
|
uint32_t totalSectors = bpb->TotalSectors16 ? bpb->TotalSectors16 : bpb->TotalSectors32;
|
|
uint32_t fatSize = bpb->FatSize16 ? bpb->FatSize16 : bpb->FatSize32;
|
|
if (!totalSectors || !fatSize) return 0;
|
|
|
|
uint32_t rootDirSectors = ((uint32_t)bpb->RootEntryCount * sizeof(DIRENT) +
|
|
(uint32_t)bpb->BytesPerSector - 1u) /
|
|
(uint32_t)bpb->BytesPerSector;
|
|
uint32_t metadataSectors = (uint32_t)bpb->ReservedSectors +
|
|
(uint32_t)bpb->NumFats * fatSize +
|
|
rootDirSectors;
|
|
if (totalSectors <= metadataSectors) return 0;
|
|
|
|
uint32_t dataSectors = totalSectors - metadataSectors;
|
|
uint32_t totalClusters = dataSectors / bpb->SectorsPerCluster;
|
|
FAT_VARIANT variant;
|
|
if (totalClusters < 4085u)
|
|
variant = FatVariant12;
|
|
else if (totalClusters < 65525u)
|
|
variant = FatVariant16;
|
|
else
|
|
variant = FatVariant32;
|
|
|
|
if (variant == FatVariant16) {
|
|
if (bpb->FatSize16 == 0 || bpb->RootEntryCount == 0) return 0;
|
|
} else if (variant == FatVariant32) {
|
|
if (bpb->FatSize16 != 0 || bpb->RootEntryCount != 0) return 0;
|
|
if (bpb->FatSize32 == 0 || bpb->RootCluster < 2) return 0;
|
|
}
|
|
|
|
out->Variant = variant;
|
|
out->TotalSectors = totalSectors;
|
|
out->FatSize = fatSize;
|
|
out->RootDirSectors = rootDirSectors;
|
|
out->DataSectors = dataSectors;
|
|
out->TotalClusters = totalClusters;
|
|
return 1;
|
|
}
|
|
|
|
|
|
// Driver entry and dynamic partition-backed mount instances.
|
|
static LIST_ENTRY gFatMounts;
|
|
static uint32_t gMountedCount;
|
|
static MUTEX gFatMountMutex;
|
|
static SPINLOCK gFatMountMutexInitLock = SPINLOCK_INIT;
|
|
static int gFatMountMutexReady;
|
|
|
|
// Marks only this FAT mount's cache state unusable when its backing volume dies.
|
|
void FatVolumeDead(IO_VOLUME_PROVIDER* provider, IO_VOLUME_REMOVAL_REASON reason) {
|
|
(void)reason;
|
|
if (!provider || !provider->Context)
|
|
return;
|
|
FatCacheInvalidateVolume((FAT_VOL*)provider->Context);
|
|
}
|
|
|
|
// Releases one dynamically allocated FAT mount after every volume reference has drained.
|
|
void FatDestroyVolumeProvider(IO_VOLUME_PROVIDER* provider) {
|
|
if (!provider || !provider->Context)
|
|
return;
|
|
FAT_VOL* volume = (FAT_VOL*)provider->Context;
|
|
FatCacheInvalidateVolume(volume);
|
|
memset(volume, 0, sizeof(*volume));
|
|
MmFreePool(volume);
|
|
}
|
|
|
|
// Initializes the mount-list mutex without allocating kernel objects.
|
|
static void FatMountEnsureMutex(void) {
|
|
if (gFatMountMutexReady)
|
|
return;
|
|
|
|
uintptr_t flags = KeAcquireSpinlock(&gFatMountMutexInitLock);
|
|
if (!gFatMountMutexReady) {
|
|
memset(&gFatMountMutex, 0, sizeof(gFatMountMutex));
|
|
ObInitializeObjectHeader(&gFatMountMutex.Header, ObjectTypeMutex);
|
|
gFatMountMutex.Lock = (SPINLOCK)SPINLOCK_INIT;
|
|
gFatMountMutex.Owner = (struct THREAD*)0;
|
|
LIST_INIT(&gFatMountMutex.WaitList);
|
|
LIST_INIT(&gFatMountMutex.OwnerLink);
|
|
gFatMountMutex.DonationClass = KeSchedulingNormal;
|
|
gFatMountMutex.DonationPriority = PriorityLow;
|
|
gFatMountMutex.HasDonation = 0;
|
|
gFatMountMutex.Abandoned = 0;
|
|
gFatMountMutexReady = 1;
|
|
}
|
|
KeReleaseSpinlock(&gFatMountMutexInitLock, flags);
|
|
}
|
|
|
|
// Returns one active FAT mount for an exact partition extent while the mount mutex is held.
|
|
static FAT_VOL* FatFindMountLocked(const IO_PARTITION_INFO* partition) {
|
|
if (!partition)
|
|
return (FAT_VOL*)0;
|
|
for (LIST_ENTRY* entry = gFatMounts.Fwd; entry != &gFatMounts; entry = entry->Fwd) {
|
|
FAT_VOL* volume = CONTAINING_RECORD(entry, FAT_VOL, MountLink);
|
|
if (volume->DriveIndex == partition->DiskIndex &&
|
|
volume->PartitionLba == partition->StartLba) {
|
|
return volume;
|
|
}
|
|
}
|
|
return (FAT_VOL*)0;
|
|
}
|
|
|
|
// Probes one I/O-core partition extent and mounts it when its VBR describes supported FAT16/FAT32.
|
|
static bool FatTryMountPartition(const IO_PARTITION_INFO* partition) {
|
|
if (!partition || partition->SectorCount == 0 || !IoDiskDevicePresent(partition->DiskIndex))
|
|
return false;
|
|
|
|
FatMountEnsureMutex();
|
|
if (!KE_MUTEX_ACQUIRE_SUCCEEDED(KeAcquireMutex(&gFatMountMutex, KE_WAIT_INFINITE)))
|
|
return false;
|
|
|
|
if (FatFindMountLocked(partition)) {
|
|
KeReleaseMutex(&gFatMountMutex);
|
|
return false;
|
|
}
|
|
|
|
FAT_VOL* v = (FAT_VOL*)MmAllocatePool(sizeof(FAT_VOL));
|
|
if (!v) {
|
|
KeReleaseMutex(&gFatMountMutex);
|
|
return false;
|
|
}
|
|
memset(v, 0, sizeof(*v));
|
|
LIST_INIT(&v->MountLink);
|
|
v->DriveIndex = partition->DiskIndex;
|
|
v->PartitionLba = partition->StartLba;
|
|
v->PartitionSize = partition->SectorCount;
|
|
|
|
uint8_t sector[512];
|
|
if (!KSUCCESS(FatReadSectors(v, v->PartitionLba, 1, sector))) {
|
|
MmFreePool(v);
|
|
KeReleaseMutex(&gFatMountMutex);
|
|
return false;
|
|
}
|
|
|
|
BPB* bpb = (BPB*)sector;
|
|
FAT_BPB_LAYOUT layout;
|
|
if (!FatComputeBpbLayout(bpb, &layout) ||
|
|
(layout.Variant != FatVariant16 && layout.Variant != FatVariant32)) {
|
|
MmFreePool(v);
|
|
KeReleaseMutex(&gFatMountMutex);
|
|
return false;
|
|
}
|
|
|
|
v->Variant = layout.Variant;
|
|
v->BytesPerSector = bpb->BytesPerSector;
|
|
v->SectorsPerCluster = bpb->SectorsPerCluster;
|
|
v->BytesPerCluster = bpb->BytesPerSector * bpb->SectorsPerCluster;
|
|
v->ReservedSectors = bpb->ReservedSectors;
|
|
v->NumFats = bpb->NumFats;
|
|
v->FatSize = layout.FatSize;
|
|
v->RootEntryCount = bpb->RootEntryCount;
|
|
v->RootDirSectors = layout.RootDirSectors;
|
|
v->RootDirStart = v->PartitionLba + bpb->ReservedSectors +
|
|
(uint64_t)bpb->NumFats * layout.FatSize;
|
|
v->RootCluster = v->Variant == FatVariant16 ? FAT_FIXED_ROOT_CLUSTER : bpb->RootCluster;
|
|
v->DataStart = v->RootDirStart + layout.RootDirSectors;
|
|
v->NextFreeHint = 2;
|
|
v->TotalClusters = layout.TotalClusters;
|
|
if (v->PartitionSize > layout.TotalSectors)
|
|
v->PartitionSize = layout.TotalSectors;
|
|
|
|
FatDebugPrint("FAT: disk=%u lba=%llu %s layout fat=%u root-sectors=%u data=%u clusters=%u\n",
|
|
v->DriveIndex, (unsigned long long)v->PartitionLba,
|
|
FatVariantName(v->Variant), v->FatSize,
|
|
v->RootDirSectors, layout.DataSectors, v->TotalClusters);
|
|
|
|
IO_VOLUME_PROVIDER provider;
|
|
FatInitProvider(&provider, v);
|
|
IO_VOLUME_SOURCE volParams;
|
|
volParams.Type = IoVolumeSourcePartition;
|
|
volParams.DiskIndex = v->DriveIndex;
|
|
memset(volParams.Reserved, 0, sizeof(volParams.Reserved));
|
|
volParams.StartLba = v->PartitionLba;
|
|
volParams.SectorCount = v->PartitionSize;
|
|
|
|
IO_VOLUME* volume = (IO_VOLUME*)0;
|
|
KSTATUS status = IoCreateVolume(&provider, &volParams, &volume);
|
|
if (!KSUCCESS(status)) {
|
|
if (volume) {
|
|
(void)IoRemoveVolume(volume, IoVolumeUnmounted);
|
|
IoReleaseVolume(volume);
|
|
} else {
|
|
MmFreePool(v);
|
|
}
|
|
DbgPrint("FAT: disk=%u lba=%llu mount failed status=0x%x\n",
|
|
partition->DiskIndex, (unsigned long long)partition->StartLba, status);
|
|
KeReleaseMutex(&gFatMountMutex);
|
|
return false;
|
|
}
|
|
|
|
// Retain the IoCreateVolume caller reference while this provider remains on the active mount list.
|
|
v->Volume = volume;
|
|
ListInsertTail(&gFatMounts, &v->MountLink);
|
|
gMountedCount++;
|
|
uint32_t instanceId = IoGetVolumeInstanceId(volume);
|
|
uint32_t mountedCount = gMountedCount;
|
|
|
|
DbgPrint("FAT: disk=%u lba=%llu %s mounted volume=%u; total=%u\n",
|
|
v->DriveIndex, (unsigned long long)v->PartitionLba,
|
|
FatVariantName(v->Variant), instanceId, mountedCount);
|
|
KeReleaseMutex(&gFatMountMutex);
|
|
return true;
|
|
}
|
|
|
|
// Unmounts one exact FAT provider instance when its backing partition disappears.
|
|
static void FatUnmountPartition(const IO_PARTITION_INFO* partition, IO_PARTITION_CHANGE change) {
|
|
if (!partition)
|
|
return;
|
|
|
|
FatMountEnsureMutex();
|
|
if (!KE_MUTEX_ACQUIRE_SUCCEEDED(KeAcquireMutex(&gFatMountMutex, KE_WAIT_INFINITE)))
|
|
return;
|
|
|
|
FAT_VOL* v = FatFindMountLocked(partition);
|
|
if (!v) {
|
|
KeReleaseMutex(&gFatMountMutex);
|
|
return;
|
|
}
|
|
|
|
IO_VOLUME* volume = v->Volume;
|
|
uint32_t instanceId = IoGetVolumeInstanceId(volume);
|
|
ListRemove(&v->MountLink);
|
|
v->Volume = (IO_VOLUME*)0;
|
|
if (gMountedCount)
|
|
gMountedCount--;
|
|
uint32_t mountedCount = gMountedCount;
|
|
KeReleaseMutex(&gFatMountMutex);
|
|
|
|
(void)IoRemoveVolume(volume, IoVolumeBackingRemoved);
|
|
IoReleaseVolume(volume); // Drop the active mount-list reference.
|
|
DbgPrint("FAT: disk=%u lba=%llu %s; unmounted volume=%u; total=%u\n",
|
|
partition->DiskIndex, (unsigned long long)partition->StartLba,
|
|
change == IoPartitionSurpriseRemoved ? "surprise removed" : "removed",
|
|
instanceId, mountedCount);
|
|
}
|
|
|
|
// Receives immutable partition extents from the I/O core; FAT never parses partition tables.
|
|
static void FatPartitionChange(const IO_PARTITION_INFO* partition,
|
|
IO_PARTITION_CHANGE change,
|
|
void* context) {
|
|
(void)context;
|
|
switch (change) {
|
|
case IoPartitionArrived:
|
|
(void)FatTryMountPartition(partition);
|
|
break;
|
|
case IoPartitionSurpriseRemoved:
|
|
case IoPartitionRemoved:
|
|
FatUnmountPartition(partition, change);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Initializes FAT and subscribes to the authoritative kernel partition stream.
|
|
KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) {
|
|
DbgPrint("FAT: Init\n");
|
|
if (!ddk)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
(void)init;
|
|
|
|
FatCacheInvalidate();
|
|
LIST_INIT(&gFatMounts);
|
|
gMountedCount = 0;
|
|
FatMountEnsureMutex();
|
|
|
|
KSTATUS status = IoRegisterPartitionChangeCallback(FatPartitionChange, NULL);
|
|
if (!KSUCCESS(status)) {
|
|
DbgPrint("FAT: partition callback registration failed status=0x%x\n", status);
|
|
return status;
|
|
}
|
|
|
|
if (!gMountedCount)
|
|
DbgPrint("FAT: no mountable FAT partitions found; driver resident for partition changes.\n");
|
|
DbgPrint("FAT: Init complete; mounted %u volume(s).\n", gMountedCount);
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|