Files
2026-10-27 09:28:31 +01:00

638 lines
25 KiB
C

// Mounted-volume core. This file owns volume identity, provider lifetime, and dead-volume dispatch semantics.
#include <stdbool.h>
#include <string.h>
#include <kernel/io_volume.h>
#include <kernel/ke_id.h>
#include <kernel/ke_debug.h>
#include <kernel/ob_object.h>
#include <kernel/mm_file_cache.h>
#include <kernel/mm_virt.h>
#include <kernel/ke_event.h>
#include <kernel/ke_sched.h>
static KE_ID_ALLOCATOR gVolumeIds = KE_ID_ALLOCATOR_INIT(1);
static SPINLOCK gVolumeRegistryLock = SPINLOCK_INIT;
static LIST_ENTRY gVolumeRegistry;
static bool gVolumeRegistryInitialized;
static SPINLOCK gVolumeStatsLock = SPINLOCK_INIT;
static IO_VOLUME_STATS gVolumeStats;
static IO_VOLUME_MANAGER_STATS gVolumeManagerStats;
static EVENT* gVolumeChangeEvent;
static SPINLOCK gVolumeChangeLock = SPINLOCK_INIT;
static uint64_t gVolumeGeneration;
static IO_VOLUME_MANAGER gVolumeManagerObject;
static bool gVolumeManagerObjectInitialized;
static void KiIoVolumeAddStat(uint64_t* field, int64_t delta);
// Initializes the dynamic mounted-volume registry on first use.
static void KiIoEnsureVolumeRegistry(void) {
uintptr_t flags = KeAcquireSpinlock(&gVolumeRegistryLock);
if (!gVolumeRegistryInitialized) {
LIST_INIT(&gVolumeRegistry);
gVolumeRegistryInitialized = 1;
}
KeReleaseSpinlock(&gVolumeRegistryLock, flags);
}
// Publishes one mounted-volume registry change to discovery waiters.
static void KiIoNotifyVolumeChange(void) {
uintptr_t flags = KeAcquireSpinlock(&gVolumeChangeLock);
gVolumeGeneration++;
KeReleaseSpinlock(&gVolumeChangeLock, flags);
if (gVolumeChangeEvent)
KeSignalEvent(gVolumeChangeEvent);
}
// Initializes the waitable mounted-volume registry notification before providers start.
KSTATUS IoInitializeVolumeManager(void) {
KiIoEnsureVolumeRegistry();
if (!gVolumeManagerObjectInitialized) {
ObInitializeObjectHeader(&gVolumeManagerObject.Header, ObjectTypeVolumeManager);
gVolumeManagerObjectInitialized = 1;
}
if (gVolumeChangeEvent)
return NTLSTATUS_SUCCESS;
return KeEventCreate(EventAutoReset, 0, &gVolumeChangeEvent);
}
// References the persistent manager object used to delegate mounted-volume control-plane authority.
KSTATUS IoReferenceVolumeManager(IO_VOLUME_MANAGER** managerOut) {
if (!managerOut)
return NTLSTATUS_INVALID_PARAMETER;
*managerOut = (IO_VOLUME_MANAGER*)0;
if (!gVolumeManagerObjectInitialized || !ObTryReferenceObject(&gVolumeManagerObject.Header))
return NTLSTATUS_OBJECT_DEAD;
*managerOut = &gVolumeManagerObject;
return NTLSTATUS_SUCCESS;
}
// Captures one stable value snapshot. A generation change during capture causes a retry instead of returning stale metadata as current.
KSTATUS IoSnapshotVolumes(IO_VOLUME_SNAPSHOT_ENTRY** entriesOut, uint32_t* countOut, uint64_t* generationOut) {
if (!entriesOut || !countOut || !generationOut)
return NTLSTATUS_INVALID_PARAMETER;
*entriesOut = (IO_VOLUME_SNAPSHOT_ENTRY*)0;
*countOut = 0;
*generationOut = 0;
KiIoEnsureVolumeRegistry();
for (;;) {
uint64_t generationBefore = IoGetVolumeGeneration();
uintptr_t flags = KeAcquireSpinlock(&gVolumeRegistryLock);
size_t count = 0;
LIST_ENTRY* head = &gVolumeRegistry;
for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd)
count++;
KeReleaseSpinlock(&gVolumeRegistryLock, flags);
if (count > UINT32_MAX)
return NTLSTATUS_LIMIT_REACHED;
IO_VOLUME_SNAPSHOT_ENTRY* entries = (IO_VOLUME_SNAPSHOT_ENTRY*)0;
if (count) {
entries = (IO_VOLUME_SNAPSHOT_ENTRY*)MmAllocatePool(count * sizeof(*entries));
if (!entries)
return NTLSTATUS_NO_MEMORY;
memset(entries, 0, count * sizeof(*entries));
}
size_t used = 0;
bool overflowed = false;
flags = KeAcquireSpinlock(&gVolumeRegistryLock);
for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd) {
if (used >= count) {
overflowed = true;
break;
}
IO_VOLUME* volume = CONTAINING_RECORD(node, IO_VOLUME, RegistryLink);
entries[used].InstanceId = volume->InstanceId;
used++;
}
KeReleaseSpinlock(&gVolumeRegistryLock, flags);
uint64_t generationAfter = IoGetVolumeGeneration();
if (!overflowed && generationBefore == generationAfter) {
*entriesOut = entries;
*countOut = (uint32_t)used;
*generationOut = generationAfter;
KiIoVolumeAddStat(&gVolumeManagerStats.Snapshots, 1);
return NTLSTATUS_SUCCESS;
}
if (entries)
MmFreePool(entries);
}
}
// References one currently mounted volume by its runtime instance identity.
KSTATUS IoReferenceVolumeByInstanceId(uint32_t instanceId, IO_VOLUME** volumeOut) {
if (!instanceId || !volumeOut)
return NTLSTATUS_INVALID_PARAMETER;
*volumeOut = (IO_VOLUME*)0;
KiIoEnsureVolumeRegistry();
uintptr_t flags = KeAcquireSpinlock(&gVolumeRegistryLock);
LIST_ENTRY* head = &gVolumeRegistry;
for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd) {
IO_VOLUME* volume = CONTAINING_RECORD(node, IO_VOLUME, RegistryLink);
if (volume->InstanceId != instanceId)
continue;
if (!ObTryReferenceObject(&volume->Header)) {
KeReleaseSpinlock(&gVolumeRegistryLock, flags);
return NTLSTATUS_OBJECT_DEAD;
}
KeReleaseSpinlock(&gVolumeRegistryLock, flags);
if (!IoVolumeIsMounted(volume)) {
ObDereferenceObject(&volume->Header);
return NTLSTATUS_OBJECT_DEAD;
}
*volumeOut = volume;
KiIoVolumeAddStat(&gVolumeManagerStats.VolumeOpens, 1);
return NTLSTATUS_SUCCESS;
}
KeReleaseSpinlock(&gVolumeRegistryLock, flags);
return NTLSTATUS_NOT_FOUND;
}
// Returns the current dynamic mounted-volume registry generation.
uint64_t IoGetVolumeGeneration(void) {
uintptr_t flags = KeAcquireSpinlock(&gVolumeChangeLock);
uint64_t generation = gVolumeGeneration;
KeReleaseSpinlock(&gVolumeChangeLock, flags);
return generation;
}
// Waits until the mounted-volume registry changes or the timeout expires.
KSTATUS IoWaitForVolumeChange(uint64_t observedGeneration, uint64_t timeoutMilliseconds) {
KiIoVolumeAddStat(&gVolumeManagerStats.Waits, 1);
if (IoGetVolumeGeneration() != observedGeneration) {
KiIoVolumeAddStat(&gVolumeManagerStats.Wakeups, 1);
return NTLSTATUS_SUCCESS;
}
if (!gVolumeChangeEvent)
return NTLSTATUS_OBJECT_DEAD;
EVENT* events[1] = { gVolumeChangeEvent };
uint32_t selected = 0;
KSTATUS status = KeWaitForMultipleEvents(events, 1, 0, timeoutMilliseconds, &selected);
if (KSUCCESS(status))
KiIoVolumeAddStat(&gVolumeManagerStats.Wakeups, 1);
return status;
}
// Adds one value to a mounted-volume statistic.
static void KiIoVolumeAddStat(uint64_t* field, int64_t delta) {
uintptr_t flags = KeAcquireSpinlock(&gVolumeStatsLock);
if (delta >= 0)
*field += (uint64_t)delta;
else {
uint64_t amount = (uint64_t)(-delta);
*field = *field > amount ? *field - amount : 0;
}
KeReleaseSpinlock(&gVolumeStatsLock, flags);
}
void IoQueryVolumeStats(IO_VOLUME_STATS* out) {
if (!out) return;
uintptr_t flags = KeAcquireSpinlock(&gVolumeStatsLock);
*out = gVolumeStats;
KeReleaseSpinlock(&gVolumeStatsLock, flags);
}
void IoQueryVolumeManagerStats(IO_VOLUME_MANAGER_STATS* out) {
if (!out) return;
uintptr_t flags = KeAcquireSpinlock(&gVolumeStatsLock);
*out = gVolumeManagerStats;
KeReleaseSpinlock(&gVolumeStatsLock, flags);
}
// References the unique mounted volume backed by one exact disk extent start.
KSTATUS IoReferenceVolumeByBacking(uint8_t diskIndex, uint64_t startLba, IO_VOLUME** volumeOut) {
if (!volumeOut)
return NTLSTATUS_INVALID_PARAMETER;
*volumeOut = (IO_VOLUME*)0;
KiIoEnsureVolumeRegistry();
IO_VOLUME* match = (IO_VOLUME*)0;
uint32_t matches = 0;
uintptr_t flags = KeAcquireSpinlock(&gVolumeRegistryLock);
LIST_ENTRY* head = &gVolumeRegistry;
for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd) {
IO_VOLUME* volume = CONTAINING_RECORD(node, IO_VOLUME, RegistryLink);
if (volume->Source.DiskIndex != diskIndex || volume->Source.StartLba != startLba)
continue;
matches++;
if (!match && ObTryReferenceObject(&volume->Header))
match = volume;
}
KeReleaseSpinlock(&gVolumeRegistryLock, flags);
if (matches == 0)
return NTLSTATUS_NOT_FOUND;
if (matches != 1) {
if (match)
ObDereferenceObject(&match->Header);
return NTLSTATUS_OBJECT_NAME_COLLISION;
}
if (!match)
return NTLSTATUS_OBJECT_DEAD;
if (!IoVolumeIsMounted(match)) {
ObDereferenceObject(&match->Header);
return NTLSTATUS_OBJECT_DEAD;
}
*volumeOut = match;
return NTLSTATUS_SUCCESS;
}
// Returns whether the volume still accepts provider operations.
bool IoVolumeIsMounted(const IO_VOLUME* volume) {
if (!volume)
return false;
IO_VOLUME* mutableVolume = (IO_VOLUME*)volume;
uintptr_t flags = KeAcquireSpinlock(&mutableVolume->Lock);
bool mounted = mutableVolume->State == IoVolumeMounted;
KeReleaseSpinlock(&mutableVolume->Lock, flags);
return mounted;
}
// Acquires one object reference only while the mounted-volume state remains live.
bool IoVolumeTryReferenceMounted(IO_VOLUME* volume) {
if (!volume || !ObTryReferenceObject(&volume->Header))
return false;
if (IoVolumeIsMounted(volume))
return true;
ObDereferenceObject(&volume->Header);
return false;
}
// Destroys provider state only after namespace, file, directory, and transient references drain.
static void KiIoVolumeDestroy(OBJECT* object) {
IO_VOLUME* volume = CONTAINING_RECORD(object, IO_VOLUME, Header);
if (volume->Provider.Destroy)
volume->Provider.Destroy(&volume->Provider);
if (volume->BackingDevice) {
IoDereferenceDeviceObject(volume->BackingDevice);
volume->BackingDevice = (DEVICE_OBJECT*)0;
}
if (volume->InstanceId)
KiIoVolumeAddStat(&gVolumeStats.ProviderDestroys, 1);
}
// Creates a dynamic mounted volume and inserts it into the mounted-volume registry.
__declspec(dllexport) KSTATUS IoCreateVolume(const IO_VOLUME_PROVIDER* provider,
const IO_VOLUME_SOURCE* source,
IO_VOLUME** volumeOut) {
if (!provider || !source || !volumeOut || !provider->Open || !provider->Read ||
!provider->GetSize || !provider->Close || source->SectorCount == 0 ||
(source->Type != IoVolumeSourcePartition && source->Type != IoVolumeSourceBlockDevice)) {
return NTLSTATUS_INVALID_PARAMETER;
}
if (source->Type == IoVolumeSourceBlockDevice && source->StartLba != 0)
return NTLSTATUS_INVALID_PARAMETER;
*volumeOut = (IO_VOLUME*)0;
DEVICE_OBJECT* backingDevice = IoReferenceDiskDevice(source->DiskIndex);
if (!backingDevice)
return NTLSTATUS_NOT_FOUND;
uint64_t diskSectors = backingDevice->u.Disk.SectorCount;
if (source->StartLba >= diskSectors || source->SectorCount > diskSectors - source->StartLba ||
(source->Type == IoVolumeSourceBlockDevice && source->SectorCount != diskSectors)) {
IoDereferenceDeviceObject(backingDevice);
return NTLSTATUS_INVALID_PARAMETER;
}
IO_VOLUME* volume = (IO_VOLUME*)ObAllocateObject(ObjectTypeVolume, sizeof(IO_VOLUME));
if (!volume) {
IoDereferenceDeviceObject(backingDevice);
return NTLSTATUS_NO_MEMORY;
}
uint32_t instanceId = 0;
KSTATUS status = KeAllocateId32(&gVolumeIds, &instanceId);
if (!KSUCCESS(status)) {
IoDereferenceDeviceObject(backingDevice);
ObDereferenceObject(&volume->Header);
return status;
}
volume->Header.Destroy = KiIoVolumeDestroy;
volume->Lock = (SPINLOCK)SPINLOCK_INIT;
volume->InstanceId = instanceId;
volume->State = IoVolumeMounted;
volume->RemovalReason = 0;
volume->Source = *source;
volume->BackingDevice = backingDevice;
volume->Provider = *provider;
LIST_INIT(&volume->RegistryLink);
KiIoEnsureVolumeRegistry();
uintptr_t flags = KeAcquireSpinlock(&gVolumeRegistryLock);
ListInsertTail(&gVolumeRegistry, &volume->RegistryLink);
volume->RegistryLinked = 1;
KeReleaseSpinlock(&gVolumeRegistryLock, flags);
// The registry owns the allocation reference; the creator receives an independent reference.
ObReferenceObject(&volume->Header);
*volumeOut = volume;
KiIoVolumeAddStat(&gVolumeStats.CreatedVolumes, 1);
KiIoVolumeAddStat(&gVolumeStats.MountedVolumes, 1);
KiIoNotifyVolumeChange();
DbgPrint("io: volume %u mounted disk=%u start=%llu sectors=%llu\n",
instanceId,
source->DiskIndex,
(unsigned long long)source->StartLba,
(unsigned long long)source->SectorCount);
return NTLSTATUS_SUCCESS;
}
// Marks a mounted volume terminally dead while retaining provider state until references drain.
__declspec(dllexport) KSTATUS IoRemoveVolume(IO_VOLUME* volume, IO_VOLUME_REMOVAL_REASON reason) {
if (!volume || reason < IoVolumeUnmounted || reason > IoVolumeProviderDead)
return NTLSTATUS_INVALID_PARAMETER;
uintptr_t flags = KeAcquireSpinlock(&volume->Lock);
if (volume->State != IoVolumeMounted) {
KeReleaseSpinlock(&volume->Lock, flags);
return NTLSTATUS_OBJECT_DEAD;
}
volume->State = IoVolumeDead;
volume->RemovalReason = (uint32_t)reason;
KeReleaseSpinlock(&volume->Lock, flags);
// No future provider operation can start after this point. The callback may cancel
// provider-specific work, but Context remains valid until KiIoVolumeDestroy.
if (volume->Provider.VolumeDead)
volume->Provider.VolumeDead(&volume->Provider, reason);
MmFileCacheDiscardVolume(volume);
bool dropRegistryReference = false;
flags = KeAcquireSpinlock(&gVolumeRegistryLock);
if (volume->RegistryLinked) {
ListRemove(&volume->RegistryLink);
volume->RegistryLinked = 0;
dropRegistryReference = true;
}
KeReleaseSpinlock(&gVolumeRegistryLock, flags);
KiIoVolumeAddStat(&gVolumeStats.MountedVolumes, -1);
KiIoVolumeAddStat(&gVolumeStats.RemovedVolumes, 1);
KiIoNotifyVolumeChange();
const char* reasonName = reason == IoVolumeUnmounted ? "unmounted" :
reason == IoVolumeBackingRemoved ? "backing-removed" : "provider-dead";
DbgPrint("io: volume %u removed reason=%s\n", volume->InstanceId, reasonName);
if (dropRegistryReference)
ObDereferenceObject(&volume->Header);
return NTLSTATUS_SUCCESS;
}
// Releases a creator-owned mounted-volume reference.
__declspec(dllexport) void IoReleaseVolume(IO_VOLUME* volume) {
if (volume)
ObDereferenceObject(&volume->Header);
}
// Returns the monotonic identity of a mounted-volume instance.
__declspec(dllexport) uint32_t IoGetVolumeInstanceId(const IO_VOLUME* volume) {
return volume ? volume->InstanceId : 0;
}
// Returns the immutable block extent backing this mounted-volume instance.
__declspec(dllexport) KSTATUS IoGetVolumeSource(const IO_VOLUME* volume, IO_VOLUME_SOURCE* sourceOut) {
if (!volume || !sourceOut)
return NTLSTATUS_INVALID_PARAMETER;
*sourceOut = volume->Source;
return NTLSTATUS_SUCCESS;
}
// Returns whether native provider async read is available while the volume remains mounted.
bool IoVolumeSupportsReadAsync(const IO_VOLUME* volume) {
return volume && IoVolumeIsMounted(volume) && volume->Provider.ReadAsync != (void*)0;
}
// Returns whether native provider async write is available while the volume remains mounted.
bool IoVolumeSupportsWriteAsync(const IO_VOLUME* volume) {
return volume && IoVolumeIsMounted(volume) && volume->Provider.WriteAsync != (void*)0;
}
// Validates mounted state before entering provider code.
static KSTATUS KiIoVolumeCheckMounted(IO_VOLUME* volume) {
if (IoVolumeIsMounted(volume))
return NTLSTATUS_SUCCESS;
KiIoVolumeAddStat(&gVolumeStats.DeadIoRejects, 1);
return NTLSTATUS_OBJECT_DEAD;
}
KSTATUS IoVolumeOpen(IO_VOLUME* volume, SYS_STRING_VIEW path, void** fileCtx) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
return volume->Provider.Open(&volume->Provider, path, fileCtx);
}
KSTATUS IoVolumeRead(IO_VOLUME* volume, void* fileCtx, uint64_t offset,
void* buffer, size_t size, size_t* bytesRead) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.Read) return NTLSTATUS_NOT_SUPPORTED;
return volume->Provider.Read(&volume->Provider, fileCtx, offset, buffer, size, bytesRead);
}
KSTATUS IoVolumeWrite(IO_VOLUME* volume, void* fileCtx, uint64_t offset,
const void* buffer, size_t size, size_t* bytesWritten) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.Write) return NTLSTATUS_NOT_SUPPORTED;
return volume->Provider.Write(&volume->Provider, fileCtx, offset, buffer, size, bytesWritten);
}
KSTATUS IoVolumeReadAsync(IO_VOLUME* volume, void* fileCtx, uint64_t offset,
void* buffer, size_t size, IO_FS_ASYNC_COMPLETE complete,
void* completeContext) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.ReadAsync) return NTLSTATUS_NOT_SUPPORTED;
return volume->Provider.ReadAsync(&volume->Provider, fileCtx, offset, buffer, size, complete, completeContext);
}
KSTATUS IoVolumeWriteAsync(IO_VOLUME* volume, void* fileCtx, uint64_t offset,
const void* buffer, size_t size, IO_FS_ASYNC_COMPLETE complete,
void* completeContext) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.WriteAsync) return NTLSTATUS_NOT_SUPPORTED;
return volume->Provider.WriteAsync(&volume->Provider, fileCtx, offset, buffer, size, complete, completeContext);
}
KSTATUS IoVolumeCreate(IO_VOLUME* volume, SYS_STRING_VIEW path, void** fileCtx) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.Create) return NTLSTATUS_NOT_SUPPORTED;
return volume->Provider.Create(&volume->Provider, path, fileCtx);
}
KSTATUS IoVolumeDelete(IO_VOLUME* volume, SYS_STRING_VIEW path) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.Delete) return NTLSTATUS_NOT_SUPPORTED;
return volume->Provider.Delete(&volume->Provider, path);
}
KSTATUS IoVolumeRename(IO_VOLUME* volume, SYS_STRING_VIEW oldPath, SYS_STRING_VIEW newPath) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.Rename) return NTLSTATUS_NOT_SUPPORTED;
return volume->Provider.Rename(&volume->Provider, oldPath, newPath);
}
KSTATUS IoVolumeCreateDir(IO_VOLUME* volume, SYS_STRING_VIEW path) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.CreateDir) return NTLSTATUS_NOT_SUPPORTED;
return volume->Provider.CreateDir(&volume->Provider, path);
}
KSTATUS IoVolumeRemoveDir(IO_VOLUME* volume, SYS_STRING_VIEW path) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.RemoveDir) return NTLSTATUS_NOT_SUPPORTED;
return volume->Provider.RemoveDir(&volume->Provider, path);
}
KSTATUS IoVolumeTruncate(IO_VOLUME* volume, void* fileCtx, uint64_t newSize, uint32_t fileFlags) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.Truncate) return NTLSTATUS_NOT_SUPPORTED;
return volume->Provider.Truncate(&volume->Provider, fileCtx, newSize, fileFlags);
}
KSTATUS IoVolumeGetSize(IO_VOLUME* volume, void* fileCtx, uint64_t* sizeOut) {
if (!sizeOut) return NTLSTATUS_INVALID_PARAMETER;
*sizeOut = 0;
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.GetSize) return NTLSTATUS_NOT_SUPPORTED;
*sizeOut = volume->Provider.GetSize(&volume->Provider, fileCtx);
return NTLSTATUS_SUCCESS;
}
KSTATUS IoVolumeQueryInfo(IO_VOLUME* volume, void* fileCtx, SYS_FILE_STAT* out) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.QueryInfo) return NTLSTATUS_NOT_SUPPORTED;
s = volume->Provider.QueryInfo(&volume->Provider, fileCtx, out);
if (KSUCCESS(s) && out)
out->VolumeId = volume->InstanceId;
return s;
}
KSTATUS IoVolumeStat(IO_VOLUME* volume, SYS_STRING_VIEW path, SYS_FILE_STAT* out) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.Stat) return NTLSTATUS_NOT_SUPPORTED;
s = volume->Provider.Stat(&volume->Provider, path, out);
if (KSUCCESS(s) && out)
out->VolumeId = volume->InstanceId;
return s;
}
KSTATUS IoVolumeQueryIdentity(IO_VOLUME* volume, void* fileCtx, IO_FILE_IDENTITY* out) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!out || !volume->Provider.QueryIdentity) return NTLSTATUS_NOT_SUPPORTED;
s = volume->Provider.QueryIdentity(&volume->Provider, fileCtx, out);
if (KSUCCESS(s))
out->VolumeId = volume->InstanceId;
return s;
}
KSTATUS IoVolumeFlush(IO_VOLUME* volume, void* fileCtx) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.Flush) return NTLSTATUS_SUCCESS;
return volume->Provider.Flush(&volume->Provider, fileCtx);
}
void IoVolumeClose(IO_VOLUME* volume, void* fileCtx) {
if (volume && volume->Provider.Close && fileCtx)
volume->Provider.Close(&volume->Provider, fileCtx);
}
void IoVolumeQueryStats(IO_VOLUME* volume, IO_FILESYSTEM_STATS* out) {
if (!out) return;
memset(out, 0, sizeof(*out));
if (!volume || !IoVolumeIsMounted(volume) || !volume->Provider.QueryStats)
return;
volume->Provider.QueryStats(&volume->Provider, out);
}
// Aggregates filesystem traversal statistics across every dynamic mounted volume.
__declspec(dllexport) void IoQueryFilesystemStats(IO_FILESYSTEM_STATS* out) {
if (!out) return;
memset(out, 0, sizeof(*out));
KiIoEnsureVolumeRegistry();
uintptr_t flags = KeAcquireSpinlock(&gVolumeRegistryLock);
size_t count = 0;
LIST_ENTRY* head = &gVolumeRegistry;
for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd)
count++;
KeReleaseSpinlock(&gVolumeRegistryLock, flags);
if (!count) return;
IO_VOLUME** snapshot = (IO_VOLUME**)MmAllocatePool(count * sizeof(IO_VOLUME*));
if (!snapshot) return;
memset(snapshot, 0, count * sizeof(IO_VOLUME*));
size_t used = 0;
flags = KeAcquireSpinlock(&gVolumeRegistryLock);
for (LIST_ENTRY* node = head->Fwd; node != head && used < count; node = node->Fwd) {
IO_VOLUME* volume = CONTAINING_RECORD(node, IO_VOLUME, RegistryLink);
if (ObTryReferenceObject(&volume->Header))
snapshot[used++] = volume;
}
KeReleaseSpinlock(&gVolumeRegistryLock, flags);
for (size_t i = 0; i < used; ++i) {
IO_FILESYSTEM_STATS one;
IoVolumeQueryStats(snapshot[i], &one);
ObDereferenceObject(&snapshot[i]->Header);
out->PathLookups += one.PathLookups;
out->PathCacheHits += one.PathCacheHits;
out->PathCacheMisses += one.PathCacheMisses;
out->PathCacheStores += one.PathCacheStores;
out->PathCacheInvalidations += one.PathCacheInvalidations;
if (one.PathCacheGeneration > out->PathCacheGeneration)
out->PathCacheGeneration = one.PathCacheGeneration;
out->DirectorySearches += one.DirectorySearches;
out->DirectoryBlocksRead += one.DirectoryBlocksRead;
out->DirectoryEntriesScanned += one.DirectoryEntriesScanned;
out->DataReadRuns += one.DataReadRuns;
out->DataReadSectors += one.DataReadSectors;
out->DataReadRunMerges += one.DataReadRunMerges;
if (one.MaxDataReadSpanSectors > out->MaxDataReadSpanSectors)
out->MaxDataReadSpanSectors = one.MaxDataReadSpanSectors;
}
MmFreePool(snapshot);
}
KSTATUS IoVolumeOpenDir(IO_VOLUME* volume, SYS_STRING_VIEW path, void** dirCtx) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.OpenDir) return NTLSTATUS_NOT_SUPPORTED;
return volume->Provider.OpenDir(&volume->Provider, path, dirCtx);
}
KSTATUS IoVolumeReadDir(IO_VOLUME* volume, void* dirCtx, KDIR_ENTRY* out) {
KSTATUS s = KiIoVolumeCheckMounted(volume);
if (!KSUCCESS(s)) return s;
if (!volume->Provider.ReadDir) return NTLSTATUS_NOT_SUPPORTED;
return volume->Provider.ReadDir(&volume->Provider, dirCtx, out);
}
void IoVolumeCloseDir(IO_VOLUME* volume, void* dirCtx) {
if (volume && volume->Provider.CloseDir && dirCtx)
volume->Provider.CloseDir(&volume->Provider, dirCtx);
}