1724 lines
68 KiB
C
1724 lines
68 KiB
C
// Native read-only NTFS filesystem driver for NTLite.
|
|
// Portions are based on ntfs-efi by Mark Harmstone (2023), GPLv2-or-later.
|
|
// The port removes UEFI protocol/header dependencies and targets NTLite's IO_FILESYSTEM ABI.
|
|
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <ntlite/ddi/io_driver.h>
|
|
#include <ntlite/ddk/ddk.h>
|
|
#include <ntlite/ddk/file.h>
|
|
#include <ntlite/ddk/memory.h>
|
|
#include <ntlite/ddk/debug.h>
|
|
#include <ntlite/ddk/list.h>
|
|
#include <ntlite/ddk/sync.h>
|
|
|
|
#define NTFS_MAX_VOLUMES 8u
|
|
#define NTFS_MAX_PARTITIONS_PER_DISK 32u
|
|
#define NTFS_SECTOR_SIZE 512u
|
|
#define NTFS_FS_NAME "NTFS "
|
|
#define NTFS_UPCASE_TABLE_CHARS 0x10000u
|
|
#define NTFS_UPCASE_TABLE_BYTES (NTFS_UPCASE_TABLE_CHARS * sizeof(uint16_t))
|
|
|
|
#ifndef NTFS_TRACE_ENABLED
|
|
#define NTFS_TRACE_ENABLED 0
|
|
#endif
|
|
|
|
#if NTFS_TRACE_ENABLED
|
|
#define NtfsTrace DbgPrint
|
|
#else
|
|
#define NtfsTrace(...) ((void)0)
|
|
#endif
|
|
|
|
#define NTFS_MFT_INODE 0ull
|
|
#define NTFS_ROOT_DIR_INODE 5ull
|
|
#define NTFS_UPCASE_INODE 10ull
|
|
|
|
#define NTFS_FILE_SIGNATURE 0x454c4946u /* FILE */
|
|
#define NTFS_INDEX_SIGNATURE 0x58444e49u /* INDX */
|
|
|
|
#define NTFS_ATTR_STANDARD_INFORMATION 0x10u
|
|
#define NTFS_ATTR_ATTRIBUTE_LIST 0x20u
|
|
#define NTFS_ATTR_FILE_NAME 0x30u
|
|
#define NTFS_ATTR_DATA 0x80u
|
|
#define NTFS_ATTR_INDEX_ROOT 0x90u
|
|
#define NTFS_ATTR_INDEX_ALLOCATION 0xA0u
|
|
#define NTFS_ATTR_END 0xffffffffu
|
|
|
|
#define NTFS_FORM_RESIDENT 0u
|
|
#define NTFS_FORM_NONRESIDENT 1u
|
|
|
|
#define NTFS_ATTRIBUTE_FLAG_COMPRESSION_MASK 0x00ffu
|
|
#define NTFS_ATTRIBUTE_FLAG_ENCRYPTED 0x4000u
|
|
|
|
#define NTFS_FILE_ATTRIBUTE_READONLY 0x00000001u
|
|
#define NTFS_FILE_ATTRIBUTE_HIDDEN 0x00000002u
|
|
#define NTFS_FILE_ATTRIBUTE_SYSTEM 0x00000004u
|
|
#define NTFS_FILE_ATTRIBUTE_DIRECTORY 0x00000010u
|
|
#define NTFS_FILE_ATTRIBUTE_ARCHIVE 0x00000020u
|
|
#define NTFS_FILE_ATTRIBUTE_DIRECTORY_MFT 0x10000000u
|
|
|
|
#define NTFS_INDEX_ENTRY_SUBNODE 1u
|
|
#define NTFS_INDEX_ENTRY_LAST 2u
|
|
|
|
#define NTFS_FILE_NAME_POSIX 0u
|
|
#define NTFS_FILE_NAME_WINDOWS 1u
|
|
#define NTFS_FILE_NAME_DOS 2u
|
|
#define NTFS_FILE_NAME_WINDOWS_AND_DOS 3u
|
|
|
|
#pragma pack(push, 1)
|
|
|
|
typedef struct NTFS_BOOT_SECTOR {
|
|
uint8_t Jmp[3];
|
|
uint8_t FsName[8];
|
|
uint16_t BytesPerSector;
|
|
uint8_t SectorsPerCluster;
|
|
uint16_t ReservedSectors;
|
|
uint8_t Unused1[5];
|
|
uint8_t Media;
|
|
uint8_t Unused2[2];
|
|
uint16_t SectorsPerTrack;
|
|
uint16_t Heads;
|
|
uint32_t HiddenSectors;
|
|
uint32_t Unused3;
|
|
uint32_t Unknown;
|
|
uint64_t TotalSectors;
|
|
uint64_t MFT;
|
|
uint64_t MFTMirr;
|
|
int8_t ClustersPerMFTRecord;
|
|
uint8_t Padding1[3];
|
|
int8_t ClustersPerIndexRecord;
|
|
uint8_t Padding2[3];
|
|
uint64_t SerialNumber;
|
|
uint32_t Checksum;
|
|
} NTFS_BOOT_SECTOR;
|
|
|
|
typedef struct NTFS_MULTI_SECTOR_HEADER {
|
|
uint32_t Signature;
|
|
uint16_t UpdateSequenceArrayOffset;
|
|
uint16_t UpdateSequenceArraySize;
|
|
} NTFS_MULTI_SECTOR_HEADER;
|
|
|
|
typedef struct NTFS_FILE_RECORD_SEGMENT_HEADER {
|
|
NTFS_MULTI_SECTOR_HEADER MultiSectorHeader;
|
|
uint64_t LogFileSequenceNumber;
|
|
uint16_t SequenceNumber;
|
|
uint16_t HardLinkCount;
|
|
uint16_t FirstAttributeOffset;
|
|
uint16_t Flags;
|
|
uint32_t EntryUsedSize;
|
|
uint32_t EntryAllocatedSize;
|
|
uint64_t BaseFileRecordSegment;
|
|
uint16_t NextAttributeID;
|
|
} NTFS_FILE_RECORD_SEGMENT_HEADER;
|
|
|
|
typedef struct NTFS_ATTRIBUTE_RECORD_HEADER {
|
|
uint32_t TypeCode;
|
|
uint16_t RecordLength;
|
|
uint16_t RecordLengthHigh;
|
|
uint8_t FormCode;
|
|
uint8_t NameLength;
|
|
uint16_t NameOffset;
|
|
uint16_t Flags;
|
|
uint16_t Instance;
|
|
union {
|
|
struct {
|
|
uint32_t ValueLength;
|
|
uint16_t ValueOffset;
|
|
uint8_t Reserved[2];
|
|
} Resident;
|
|
struct {
|
|
uint64_t LowestVcn;
|
|
uint64_t HighestVcn;
|
|
uint16_t MappingPairsOffset;
|
|
uint16_t CompressionUnit;
|
|
uint32_t Padding;
|
|
uint64_t AllocatedLength;
|
|
uint64_t FileSize;
|
|
uint64_t ValidDataLength;
|
|
uint64_t TotalAllocated;
|
|
} Nonresident;
|
|
} Form;
|
|
} NTFS_ATTRIBUTE_RECORD_HEADER;
|
|
|
|
typedef struct NTFS_STANDARD_INFORMATION {
|
|
int64_t CreationTime;
|
|
int64_t LastAccessTime;
|
|
int64_t LastWriteTime;
|
|
int64_t ChangeTime;
|
|
uint32_t FileAttributes;
|
|
uint32_t MaximumVersions;
|
|
uint32_t VersionNumber;
|
|
uint32_t ClassId;
|
|
uint32_t OwnerId;
|
|
uint32_t SecurityId;
|
|
uint64_t QuotaCharged;
|
|
uint64_t USN;
|
|
} NTFS_STANDARD_INFORMATION;
|
|
|
|
typedef struct NTFS_INDEX_NODE_HEADER {
|
|
uint32_t FirstEntry;
|
|
uint32_t TotalSize;
|
|
uint32_t AllocatedSize;
|
|
uint32_t Flags;
|
|
} NTFS_INDEX_NODE_HEADER;
|
|
|
|
typedef struct NTFS_INDEX_ENTRY {
|
|
uint64_t FileReference;
|
|
uint16_t EntryLength;
|
|
uint16_t StreamLength;
|
|
uint32_t Flags;
|
|
} NTFS_INDEX_ENTRY;
|
|
|
|
typedef struct NTFS_INDEX_ROOT {
|
|
uint32_t AttributeType;
|
|
uint32_t CollationRule;
|
|
uint32_t BytesPerIndexRecord;
|
|
uint8_t ClustersPerIndexRecord;
|
|
uint8_t Padding[3];
|
|
NTFS_INDEX_NODE_HEADER NodeHeader;
|
|
} NTFS_INDEX_ROOT;
|
|
|
|
typedef struct NTFS_INDEX_RECORD {
|
|
NTFS_MULTI_SECTOR_HEADER MultiSectorHeader;
|
|
uint64_t SequenceNumber;
|
|
uint64_t Vcn;
|
|
NTFS_INDEX_NODE_HEADER Header;
|
|
uint16_t UpdateSequence;
|
|
} NTFS_INDEX_RECORD;
|
|
|
|
typedef struct NTFS_FILE_NAME_ATTR {
|
|
uint64_t Parent;
|
|
int64_t CreationTime;
|
|
int64_t LastAccessTime;
|
|
int64_t LastWriteTime;
|
|
int64_t ChangeTime;
|
|
uint64_t AllocationSize;
|
|
uint64_t EndOfFile;
|
|
uint32_t FileAttributes;
|
|
uint32_t EaSize;
|
|
uint8_t FileNameLength;
|
|
uint8_t Namespace;
|
|
uint16_t FileName[1];
|
|
} NTFS_FILE_NAME_ATTR;
|
|
|
|
typedef struct NTFS_GPT_GUID {
|
|
uint32_t Data1;
|
|
uint16_t Data2;
|
|
uint16_t Data3;
|
|
uint8_t Data4[8];
|
|
} NTFS_GPT_GUID;
|
|
|
|
typedef struct NTFS_GPT_HEADER {
|
|
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;
|
|
NTFS_GPT_GUID DiskGUID;
|
|
uint64_t PartitionEntryLBA;
|
|
uint32_t NumberOfPartitionEntries;
|
|
uint32_t SizeOfPartitionEntry;
|
|
uint32_t PartitionEntryArrayCRC32;
|
|
} NTFS_GPT_HEADER;
|
|
|
|
typedef struct NTFS_GPT_ENTRY {
|
|
NTFS_GPT_GUID PartitionTypeGUID;
|
|
NTFS_GPT_GUID UniquePartitionGUID;
|
|
uint64_t StartingLBA;
|
|
uint64_t EndingLBA;
|
|
uint64_t Attributes;
|
|
uint16_t PartitionName[36];
|
|
} NTFS_GPT_ENTRY;
|
|
|
|
typedef struct NTFS_MBR_PARTITION {
|
|
uint8_t BootIndicator;
|
|
uint8_t StartingChs[3];
|
|
uint8_t OsType;
|
|
uint8_t EndingChs[3];
|
|
uint32_t StartingLba;
|
|
uint32_t SizeInLba;
|
|
} NTFS_MBR_PARTITION;
|
|
|
|
#pragma pack(pop)
|
|
|
|
typedef struct NTFS_RUN {
|
|
LIST_ENTRY Link;
|
|
uint64_t Vcn;
|
|
uint64_t Lcn;
|
|
uint64_t Length;
|
|
bool Sparse;
|
|
} NTFS_RUN;
|
|
|
|
typedef struct NTFS_VOLUME {
|
|
uint8_t DriveIndex;
|
|
uint8_t VolumeIndex;
|
|
uint64_t PartitionLba;
|
|
uint64_t PartitionSize;
|
|
uint64_t SerialNumber;
|
|
uint32_t BytesPerSector;
|
|
uint32_t SectorsPerCluster;
|
|
uint32_t BytesPerCluster;
|
|
uint64_t TotalSectors;
|
|
uint64_t MftStartCluster;
|
|
uint64_t FileRecordSize;
|
|
LIST_ENTRY MftRuns;
|
|
uint16_t* Upcase;
|
|
} NTFS_VOLUME;
|
|
|
|
typedef struct NTFS_FILE {
|
|
NTFS_VOLUME* Vol;
|
|
uint64_t Inode;
|
|
uint64_t Size;
|
|
uint64_t ValidDataLength;
|
|
uint64_t PhysicalSize;
|
|
uint32_t Attributes;
|
|
bool IsDirectory;
|
|
NTFS_STANDARD_INFORMATION StandardInfo;
|
|
LIST_ENTRY DataRuns;
|
|
LIST_ENTRY IndexRuns;
|
|
uint8_t* ResidentData;
|
|
size_t ResidentDataSize;
|
|
NTFS_INDEX_ROOT* IndexRoot;
|
|
size_t IndexRootSize;
|
|
} NTFS_FILE;
|
|
|
|
typedef struct NTFS_DIRENT_CACHE {
|
|
uint64_t Inode;
|
|
uint64_t FileSize;
|
|
uint64_t AllocSize;
|
|
uint32_t Attributes;
|
|
int64_t CreationTime;
|
|
int64_t LastAccessTime;
|
|
int64_t LastWriteTime;
|
|
char Name[SYS_DIR_NAME_MAX];
|
|
} NTFS_DIRENT_CACHE;
|
|
|
|
typedef struct NTFS_DIR_CTX {
|
|
NTFS_DIRENT_CACHE* Entries;
|
|
uint32_t Count;
|
|
uint32_t Capacity;
|
|
uint32_t Position;
|
|
} NTFS_DIR_CTX;
|
|
|
|
typedef struct NTFS_PARTITION_CANDIDATE {
|
|
uint64_t StartLba;
|
|
uint64_t SectorCount;
|
|
} NTFS_PARTITION_CANDIDATE;
|
|
|
|
typedef struct NTFS_ATTR_VIEW {
|
|
const NTFS_ATTRIBUTE_RECORD_HEADER* Attr;
|
|
const uint8_t* ResidentData;
|
|
size_t ResidentSize;
|
|
} NTFS_ATTR_VIEW;
|
|
|
|
typedef struct NTFS_FIND_CTX {
|
|
NTFS_VOLUME* Vol;
|
|
const uint16_t* Name;
|
|
size_t NameLength;
|
|
uint64_t Inode;
|
|
bool Found;
|
|
} NTFS_FIND_CTX;
|
|
|
|
typedef struct NTFS_COLLECT_CTX {
|
|
NTFS_VOLUME* Vol;
|
|
NTFS_DIR_CTX* Dir;
|
|
KSTATUS Status;
|
|
bool HideNtfsMetadata;
|
|
} NTFS_COLLECT_CTX;
|
|
|
|
static NTFS_VOLUME gVolumes[NTFS_MAX_VOLUMES];
|
|
static IO_FILESYSTEM gFilesystems[NTFS_MAX_VOLUMES];
|
|
static bool gVolumeMounted[NTFS_MAX_VOLUMES];
|
|
static bool gVolumeBusy[NTFS_MAX_VOLUMES];
|
|
static bool gDiskScanned[NTFS_MAX_VOLUMES];
|
|
static SPINLOCK gNtfsMountLock = SPINLOCK_INIT;
|
|
static uint32_t gMountedCount;
|
|
|
|
static uint64_t NtfsAlignUp(uint64_t value, uint64_t alignment) {
|
|
if (alignment == 0) return value;
|
|
uint64_t rem = value % alignment;
|
|
return rem ? value + (alignment - rem) : value;
|
|
}
|
|
|
|
static bool NtfsIsPowerOfTwo32(uint32_t value) {
|
|
return value != 0 && (value & (value - 1u)) == 0;
|
|
}
|
|
|
|
static uint64_t NtfsFileReferenceNumber(uint64_t reference) {
|
|
return reference & 0x0000ffffffffffffull;
|
|
}
|
|
|
|
static void NtfsInitRunList(LIST_ENTRY* list) {
|
|
LIST_INIT(list);
|
|
}
|
|
|
|
static void NtfsFreeRuns(LIST_ENTRY* list) {
|
|
while (!LIST_IS_EMPTY(list)) {
|
|
LIST_ENTRY* node = ListPopHead(list);
|
|
NTFS_RUN* run = CONTAINING_RECORD(node, NTFS_RUN, Link);
|
|
MmFreePool(run);
|
|
}
|
|
}
|
|
|
|
static void NtfsDebugDumpRuns(const char* tag, const LIST_ENTRY* runs) {
|
|
if (!tag || !runs) return;
|
|
uint32_t i = 0;
|
|
for (LIST_ENTRY* node = runs->Fwd; node != runs && i < 16u; node = node->Fwd, i++) {
|
|
NTFS_RUN* run = CONTAINING_RECORD(node, NTFS_RUN, Link);
|
|
NtfsTrace("NTFS: %s run[%u]: vcn=%llu lcn=%llu len=%llu sparse=%u\n",
|
|
tag, i,
|
|
(unsigned long long)run->Vcn,
|
|
(unsigned long long)run->Lcn,
|
|
(unsigned long long)run->Length,
|
|
run->Sparse ? 1u : 0u);
|
|
}
|
|
if (i == 0) NtfsTrace("NTFS: %s runlist empty\n", tag);
|
|
}
|
|
|
|
static KSTATUS NtfsReadSectors(NTFS_VOLUME* vol, uint64_t lba, uint32_t count, void* buffer) {
|
|
if (!vol || !buffer || count == 0) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (vol->PartitionSize && lba >= vol->PartitionSize) return NTLSTATUS_DEVICE_ERROR;
|
|
if (vol->PartitionSize && (uint64_t)count > vol->PartitionSize - lba) return NTLSTATUS_DEVICE_ERROR;
|
|
return IoReadDiskSectors(vol->DriveIndex, vol->PartitionLba + lba, count, buffer);
|
|
}
|
|
|
|
static KSTATUS NtfsReadBytesDirect(NTFS_VOLUME* vol, uint64_t byteOffset, uint64_t byteCount, void* buffer) {
|
|
if (!vol || !buffer) return NTLSTATUS_INVALID_PARAMETER;
|
|
if ((byteOffset % vol->BytesPerSector) != 0 || (byteCount % vol->BytesPerSector) != 0)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
uint64_t lba = byteOffset / vol->BytesPerSector;
|
|
uint64_t sectors = byteCount / vol->BytesPerSector;
|
|
if (sectors > 0xffffffffull) return NTLSTATUS_INVALID_PARAMETER;
|
|
return NtfsReadSectors(vol, lba, (uint32_t)sectors, buffer);
|
|
}
|
|
|
|
static KSTATUS NtfsProcessFixups(NTFS_MULTI_SECTOR_HEADER* header, uint64_t length, uint32_t sectorSize) {
|
|
if (!header || sectorSize == 0 || length < sectorSize) return NTLSTATUS_INVALID_PARAMETER;
|
|
uint64_t sectors = length / sectorSize;
|
|
if (header->UpdateSequenceArraySize < sectors + 1) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (header->UpdateSequenceArrayOffset + (uint64_t)header->UpdateSequenceArraySize * sizeof(uint16_t) > length)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
uint16_t* seq = (uint16_t*)((uint8_t*)header + header->UpdateSequenceArrayOffset);
|
|
uint8_t* ptr = (uint8_t*)header + sectorSize - sizeof(uint16_t);
|
|
for (uint64_t i = 0; i < sectors; i++) {
|
|
if (*(uint16_t*)ptr != seq[0]) return NTLSTATUS_CHECKSUM_MISMATCH;
|
|
*(uint16_t*)ptr = seq[i + 1u];
|
|
ptr += sectorSize;
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
static int64_t NtfsReadSignedLe(const uint8_t* p, uint8_t bytes) {
|
|
uint64_t value = 0;
|
|
for (uint8_t i = 0; i < bytes; i++) value |= ((uint64_t)p[i]) << (8u * i);
|
|
if (bytes && (p[bytes - 1u] & 0x80u)) {
|
|
for (uint8_t i = bytes; i < 8u; i++) value |= 0xffull << (8u * i);
|
|
}
|
|
return (int64_t)value;
|
|
}
|
|
|
|
static uint32_t NtfsAttributeRecordLength(const NTFS_ATTRIBUTE_RECORD_HEADER* attr) {
|
|
if (!attr) return 0;
|
|
return (uint32_t)attr->RecordLength | ((uint32_t)attr->RecordLengthHigh << 16);
|
|
}
|
|
|
|
static uint32_t NtfsAttributeMinimumLength(uint8_t formCode) {
|
|
if (formCode == NTFS_FORM_RESIDENT) {
|
|
return (uint32_t)(offsetof(NTFS_ATTRIBUTE_RECORD_HEADER, Form.Resident.Reserved) +
|
|
sizeof(((NTFS_ATTRIBUTE_RECORD_HEADER*)0)->Form.Resident.Reserved));
|
|
}
|
|
if (formCode == NTFS_FORM_NONRESIDENT) {
|
|
return (uint32_t)(offsetof(NTFS_ATTRIBUTE_RECORD_HEADER, Form.Nonresident.ValidDataLength) +
|
|
sizeof(((NTFS_ATTRIBUTE_RECORD_HEADER*)0)->Form.Nonresident.ValidDataLength));
|
|
}
|
|
return (uint32_t)offsetof(NTFS_ATTRIBUTE_RECORD_HEADER, Form);
|
|
}
|
|
|
|
static KSTATUS NtfsReadMappings(NTFS_VOLUME* vol, const NTFS_ATTRIBUTE_RECORD_HEADER* attr, LIST_ENTRY* runs) {
|
|
if (!vol || !attr || !runs) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (attr->FormCode != NTFS_FORM_NONRESIDENT) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (attr->Flags & NTFS_ATTRIBUTE_FLAG_ENCRYPTED) return NTLSTATUS_NOT_SUPPORTED;
|
|
if (attr->Flags & NTFS_ATTRIBUTE_FLAG_COMPRESSION_MASK) return NTLSTATUS_NOT_SUPPORTED;
|
|
uint32_t attrLen = NtfsAttributeRecordLength(attr);
|
|
if (attrLen < NtfsAttributeMinimumLength(NTFS_FORM_NONRESIDENT)) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (attr->Form.Nonresident.MappingPairsOffset >= attrLen) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
uint64_t limitBytes = attr->Form.Nonresident.AllocatedLength;
|
|
if (limitBytes == 0) limitBytes = attr->Form.Nonresident.FileSize;
|
|
if (limitBytes == 0) limitBytes = attr->Form.Nonresident.ValidDataLength;
|
|
uint64_t maxCluster = NtfsAlignUp(limitBytes, vol->BytesPerCluster) / vol->BytesPerCluster;
|
|
if (maxCluster == 0) return NTLSTATUS_SUCCESS;
|
|
|
|
const uint8_t* stream = (const uint8_t*)attr + attr->Form.Nonresident.MappingPairsOffset;
|
|
const uint8_t* end = (const uint8_t*)attr + attrLen;
|
|
uint64_t nextVcn = attr->Form.Nonresident.LowestVcn;
|
|
int64_t currentLcn = 0;
|
|
|
|
while (stream < end && *stream != 0) {
|
|
uint8_t header = *stream++;
|
|
uint8_t lenBytes = header & 0x0fu;
|
|
uint8_t lcnBytes = header >> 4;
|
|
if (lenBytes == 0 || lenBytes > 8 || lcnBytes > 8) return NTLSTATUS_INVALID_PARAMETER;
|
|
if ((size_t)(end - stream) < (size_t)lenBytes + (size_t)lcnBytes) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
int64_t runClustersSigned = NtfsReadSignedLe(stream, lenBytes);
|
|
stream += lenBytes;
|
|
if (runClustersSigned <= 0) return NTLSTATUS_INVALID_PARAMETER;
|
|
uint64_t currentVcn = nextVcn;
|
|
nextVcn += (uint64_t)runClustersSigned;
|
|
if (nextVcn > maxCluster) nextVcn = maxCluster;
|
|
|
|
NTFS_RUN* run = (NTFS_RUN*)MmAllocatePool(sizeof(NTFS_RUN));
|
|
if (!run) return NTLSTATUS_NO_MEMORY;
|
|
memset(run, 0, sizeof(*run));
|
|
run->Vcn = currentVcn;
|
|
run->Length = nextVcn - currentVcn;
|
|
if (lcnBytes == 0) {
|
|
run->Sparse = true;
|
|
run->Lcn = 0;
|
|
} else {
|
|
currentLcn += NtfsReadSignedLe(stream, lcnBytes);
|
|
stream += lcnBytes;
|
|
if (currentLcn < 0) { MmFreePool(run); return NTLSTATUS_INVALID_PARAMETER; }
|
|
run->Sparse = false;
|
|
run->Lcn = (uint64_t)currentLcn;
|
|
}
|
|
ListInsertTail(runs, &run->Link);
|
|
if (nextVcn >= maxCluster) break;
|
|
}
|
|
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
static KSTATUS NtfsReadFromRuns(NTFS_VOLUME* vol, const LIST_ENTRY* runs, uint64_t offset, void* buffer, uint64_t size) {
|
|
if (!vol || !runs || (!buffer && size)) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (size == 0) return NTLSTATUS_SUCCESS;
|
|
|
|
uint8_t* out = (uint8_t*)buffer;
|
|
uint64_t clusterSize = vol->BytesPerCluster;
|
|
uint64_t vcn = offset / clusterSize;
|
|
uint64_t endOffset = offset + size;
|
|
uint64_t lastVcn = NtfsAlignUp(endOffset, clusterSize) / clusterSize;
|
|
|
|
for (LIST_ENTRY* node = runs->Fwd; node != runs && size; node = node->Fwd) {
|
|
NTFS_RUN* run = CONTAINING_RECORD(node, NTFS_RUN, Link);
|
|
if (run->Vcn >= lastVcn || run->Vcn + run->Length <= vcn) continue;
|
|
|
|
uint64_t runByteStart = run->Vcn * clusterSize;
|
|
uint64_t runByteEnd = (run->Vcn + run->Length) * clusterSize;
|
|
if (offset < runByteStart) {
|
|
uint64_t gap = runByteStart - offset;
|
|
if (gap > size) gap = size;
|
|
memset(out, 0, (size_t)gap);
|
|
out += gap;
|
|
offset += gap;
|
|
size -= gap;
|
|
vcn = offset / clusterSize;
|
|
if (!size) break;
|
|
}
|
|
|
|
uint64_t chunkStart = offset > runByteStart ? offset : runByteStart;
|
|
uint64_t chunkEnd = endOffset < runByteEnd ? endOffset : runByteEnd;
|
|
if (chunkEnd <= chunkStart) continue;
|
|
uint64_t chunkSize = chunkEnd - chunkStart;
|
|
uint64_t mappingOffset = chunkStart - runByteStart;
|
|
|
|
if (run->Sparse) {
|
|
memset(out, 0, (size_t)chunkSize);
|
|
} else {
|
|
uint64_t byteOnVolume = run->Lcn * clusterSize + mappingOffset;
|
|
KSTATUS s = NtfsReadBytesDirect(vol, byteOnVolume, chunkSize, out);
|
|
if (!KSUCCESS(s)) return s;
|
|
}
|
|
|
|
out += chunkSize;
|
|
offset += chunkSize;
|
|
size -= chunkSize;
|
|
vcn = offset / clusterSize;
|
|
}
|
|
|
|
if (size) memset(out, 0, (size_t)size);
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
static bool NtfsAttrNameEqualsAscii(const NTFS_ATTRIBUTE_RECORD_HEADER* attr, const char* ascii) {
|
|
if (!attr || !ascii) return false;
|
|
size_t len = strlen(ascii);
|
|
if (attr->NameLength != len) return false;
|
|
if (attr->NameLength == 0) return len == 0;
|
|
uint32_t attrLen = NtfsAttributeRecordLength(attr);
|
|
uint32_t nameBytes = (uint32_t)attr->NameLength * sizeof(uint16_t);
|
|
if (attr->NameOffset < offsetof(NTFS_ATTRIBUTE_RECORD_HEADER, Form) ||
|
|
attr->NameOffset + nameBytes > attrLen) {
|
|
return false;
|
|
}
|
|
const uint16_t* name = (const uint16_t*)((const uint8_t*)attr + attr->NameOffset);
|
|
for (size_t i = 0; i < len; i++) {
|
|
if (name[i] != (uint16_t)(uint8_t)ascii[i]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static KSTATUS NtfsForEachRecordAttribute(NTFS_VOLUME* vol,
|
|
uint64_t inode,
|
|
const NTFS_FILE_RECORD_SEGMENT_HEADER* record,
|
|
bool (*callback)(const NTFS_ATTR_VIEW* view, void* ctx),
|
|
void* ctx) {
|
|
if (!vol || !record || !callback) return NTLSTATUS_INVALID_PARAMETER;
|
|
uint32_t offset = record->FirstAttributeOffset;
|
|
uint32_t recordBytes = record->EntryUsedSize ? record->EntryUsedSize : record->EntryAllocatedSize;
|
|
if (recordBytes == 0 || recordBytes > vol->FileRecordSize) recordBytes = (uint32_t)vol->FileRecordSize;
|
|
|
|
if (offset >= recordBytes || offset < sizeof(NTFS_FILE_RECORD_SEGMENT_HEADER)) {
|
|
NtfsTrace("NTFS: attrs: inode=%llu bad firstAttr=%u recordBytes=%u fileRecordSize=%llu\n",
|
|
(unsigned long long)inode, offset, recordBytes, (unsigned long long)vol->FileRecordSize);
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
while (offset + offsetof(NTFS_ATTRIBUTE_RECORD_HEADER, Form) <= recordBytes) {
|
|
const NTFS_ATTRIBUTE_RECORD_HEADER* attr = (const NTFS_ATTRIBUTE_RECORD_HEADER*)((const uint8_t*)record + offset);
|
|
if (attr->TypeCode == NTFS_ATTR_END) break;
|
|
uint32_t attrLen = NtfsAttributeRecordLength(attr);
|
|
uint32_t minAttrLen = NtfsAttributeMinimumLength(attr->FormCode);
|
|
if (attrLen < minAttrLen || attrLen == 0) {
|
|
NtfsTrace("NTFS: attrs: inode=%llu bad attr off=%u type=0x%x len=%u form=%u min=%u\n",
|
|
(unsigned long long)inode, offset, attr->TypeCode, attrLen, attr->FormCode, minAttrLen);
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
if (offset + attrLen > recordBytes) {
|
|
NtfsTrace("NTFS: attrs: inode=%llu attr overruns off=%u type=0x%x len=%u recordBytes=%u used=%u alloc=%u\n",
|
|
(unsigned long long)inode, offset, attr->TypeCode, attrLen, recordBytes,
|
|
record->EntryUsedSize, record->EntryAllocatedSize);
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
NTFS_ATTR_VIEW view;
|
|
memset(&view, 0, sizeof(view));
|
|
view.Attr = attr;
|
|
if (attr->FormCode == NTFS_FORM_RESIDENT) {
|
|
if ((uint32_t)attr->Form.Resident.ValueOffset < minAttrLen ||
|
|
(uint32_t)attr->Form.Resident.ValueOffset + attr->Form.Resident.ValueLength > attrLen) {
|
|
NtfsTrace("NTFS: attrs: inode=%llu resident bounds off=%u type=0x%x attrLen=%u valueOff=%u valueLen=%u\n",
|
|
(unsigned long long)inode, offset, attr->TypeCode, attrLen,
|
|
attr->Form.Resident.ValueOffset, attr->Form.Resident.ValueLength);
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
view.ResidentData = (const uint8_t*)attr + attr->Form.Resident.ValueOffset;
|
|
view.ResidentSize = attr->Form.Resident.ValueLength;
|
|
} else if (attr->FormCode != NTFS_FORM_NONRESIDENT) {
|
|
NtfsTrace("NTFS: attrs: inode=%llu unsupported form=%u off=%u type=0x%x\n",
|
|
(unsigned long long)inode, attr->FormCode, offset, attr->TypeCode);
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (!callback(&view, ctx)) break;
|
|
offset += attrLen;
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
static KSTATUS NtfsValidateFileRecord(NTFS_VOLUME* vol, uint64_t inode, void* record, const char* source) {
|
|
NTFS_FILE_RECORD_SEGMENT_HEADER* fr = (NTFS_FILE_RECORD_SEGMENT_HEADER*)record;
|
|
if (fr->MultiSectorHeader.Signature != NTFS_FILE_SIGNATURE) {
|
|
const uint8_t* b = (const uint8_t*)record;
|
|
NtfsTrace("NTFS: record: inode=%llu %s bad signature=0x%x bytes=%02x %02x %02x %02x mftOff=%llu\n",
|
|
(unsigned long long)inode, source ? source : "?", fr->MultiSectorHeader.Signature,
|
|
b[0], b[1], b[2], b[3], (unsigned long long)(inode * vol->FileRecordSize));
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
KSTATUS s = NtfsProcessFixups(&fr->MultiSectorHeader, vol->FileRecordSize, vol->BytesPerSector);
|
|
if (!KSUCCESS(s)) {
|
|
NtfsTrace("NTFS: record: inode=%llu %s fixup failed st=0x%x sig=0x%x usaOff=%u usaCount=%u recordSize=%llu sector=%u\n",
|
|
(unsigned long long)inode, source ? source : "?", s, fr->MultiSectorHeader.Signature,
|
|
fr->MultiSectorHeader.UpdateSequenceArrayOffset,
|
|
fr->MultiSectorHeader.UpdateSequenceArraySize,
|
|
(unsigned long long)vol->FileRecordSize, vol->BytesPerSector);
|
|
return s;
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
static KSTATUS NtfsReadRecord(NTFS_VOLUME* vol, uint64_t inode, void* record) {
|
|
if (!vol || !record) return NTLSTATUS_INVALID_PARAMETER;
|
|
uint64_t mftOffset = inode * vol->FileRecordSize;
|
|
KSTATUS s = NtfsReadFromRuns(vol, &vol->MftRuns, mftOffset, record, vol->FileRecordSize);
|
|
if (!KSUCCESS(s)) {
|
|
NtfsTrace("NTFS: record: inode=%llu runlist read failed st=0x%x mftOff=%llu size=%llu\n",
|
|
(unsigned long long)inode, s, (unsigned long long)mftOffset,
|
|
(unsigned long long)vol->FileRecordSize);
|
|
NtfsDebugDumpRuns("mft", &vol->MftRuns);
|
|
return s;
|
|
}
|
|
|
|
s = NtfsValidateFileRecord(vol, inode, record, "runlist");
|
|
if (KSUCCESS(s)) return s;
|
|
|
|
/* Diagnostic and conservative rescue path: on a normal NTFS volume, early MFT records
|
|
are contiguous from the boot-sector $MFT LCN. If the decoded runlist gives garbage,
|
|
try that direct address and log whether it works. This also keeps the system usable
|
|
while we narrow the runlist bug down. */
|
|
uint8_t* direct = (uint8_t*)MmAllocatePool((size_t)vol->FileRecordSize);
|
|
if (!direct) return s;
|
|
uint64_t directByte = vol->MftStartCluster * vol->BytesPerCluster + mftOffset;
|
|
KSTATUS ds = NtfsReadBytesDirect(vol, directByte, vol->FileRecordSize, direct);
|
|
if (KSUCCESS(ds)) ds = NtfsValidateFileRecord(vol, inode, direct, "boot-mft-direct");
|
|
else NtfsTrace("NTFS: record: inode=%llu direct read failed st=0x%x byte=%llu\n",
|
|
(unsigned long long)inode, ds, (unsigned long long)directByte);
|
|
if (KSUCCESS(ds)) {
|
|
memcpy(record, direct, (size_t)vol->FileRecordSize);
|
|
NtfsTrace("NTFS: record: inode=%llu used boot-sector $MFT direct fallback; decoded MFT runlist is suspect\n",
|
|
(unsigned long long)inode);
|
|
NtfsDebugDumpRuns("mft", &vol->MftRuns);
|
|
MmFreePool(direct);
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
MmFreePool(direct);
|
|
return s;
|
|
}
|
|
|
|
static void NtfsInitFile(NTFS_FILE* file, NTFS_VOLUME* vol, uint64_t inode) {
|
|
memset(file, 0, sizeof(*file));
|
|
file->Vol = vol;
|
|
file->Inode = inode;
|
|
NtfsInitRunList(&file->DataRuns);
|
|
NtfsInitRunList(&file->IndexRuns);
|
|
}
|
|
|
|
static void NtfsCleanupFile(NTFS_FILE* file) {
|
|
if (!file) return;
|
|
NtfsFreeRuns(&file->DataRuns);
|
|
NtfsFreeRuns(&file->IndexRuns);
|
|
if (file->ResidentData) MmFreePool(file->ResidentData);
|
|
if (file->IndexRoot) MmFreePool(file->IndexRoot);
|
|
memset(file, 0, sizeof(*file));
|
|
}
|
|
|
|
typedef struct NTFS_LOAD_FILE_CTX {
|
|
NTFS_FILE* File;
|
|
KSTATUS Status;
|
|
} NTFS_LOAD_FILE_CTX;
|
|
|
|
static bool NtfsLoadFileAttributeCallback(const NTFS_ATTR_VIEW* view, void* rawCtx) {
|
|
NTFS_LOAD_FILE_CTX* ctx = (NTFS_LOAD_FILE_CTX*)rawCtx;
|
|
NTFS_FILE* file = ctx->File;
|
|
const NTFS_ATTRIBUTE_RECORD_HEADER* attr = view->Attr;
|
|
|
|
switch (attr->TypeCode) {
|
|
case NTFS_ATTR_STANDARD_INFORMATION: {
|
|
if (attr->FormCode == NTFS_FORM_RESIDENT && view->ResidentData) {
|
|
size_t toCopy = view->ResidentSize < sizeof(file->StandardInfo) ? view->ResidentSize : sizeof(file->StandardInfo);
|
|
memcpy(&file->StandardInfo, view->ResidentData, toCopy);
|
|
file->Attributes = file->StandardInfo.FileAttributes;
|
|
}
|
|
break;
|
|
}
|
|
case NTFS_ATTR_FILE_NAME: {
|
|
if (attr->FormCode == NTFS_FORM_RESIDENT && view->ResidentSize >= offsetof(NTFS_FILE_NAME_ATTR, FileName)) {
|
|
const NTFS_FILE_NAME_ATTR* fn = (const NTFS_FILE_NAME_ATTR*)view->ResidentData;
|
|
file->Attributes = fn->FileAttributes;
|
|
file->IsDirectory = (fn->FileAttributes & NTFS_FILE_ATTRIBUTE_DIRECTORY_MFT) != 0 ||
|
|
(fn->FileAttributes & NTFS_FILE_ATTRIBUTE_DIRECTORY) != 0;
|
|
if (file->Size == 0) file->Size = fn->EndOfFile;
|
|
if (file->PhysicalSize == 0) file->PhysicalSize = fn->AllocationSize;
|
|
}
|
|
break;
|
|
}
|
|
case NTFS_ATTR_DATA: {
|
|
if (attr->NameLength != 0) break;
|
|
if (attr->FormCode == NTFS_FORM_RESIDENT) {
|
|
file->Size = view->ResidentSize;
|
|
file->ValidDataLength = view->ResidentSize;
|
|
file->PhysicalSize = view->ResidentSize;
|
|
if (view->ResidentSize) {
|
|
file->ResidentData = (uint8_t*)MmAllocatePool(view->ResidentSize);
|
|
if (!file->ResidentData) { ctx->Status = NTLSTATUS_NO_MEMORY; return false; }
|
|
memcpy(file->ResidentData, view->ResidentData, view->ResidentSize);
|
|
file->ResidentDataSize = view->ResidentSize;
|
|
}
|
|
} else if (attr->FormCode == NTFS_FORM_NONRESIDENT) {
|
|
file->Size = attr->Form.Nonresident.FileSize;
|
|
file->ValidDataLength = attr->Form.Nonresident.ValidDataLength;
|
|
file->PhysicalSize = attr->Form.Nonresident.AllocatedLength;
|
|
ctx->Status = NtfsReadMappings(file->Vol, attr, &file->DataRuns);
|
|
if (!KSUCCESS(ctx->Status)) return false;
|
|
}
|
|
break;
|
|
}
|
|
case NTFS_ATTR_INDEX_ROOT: {
|
|
if (NtfsAttrNameEqualsAscii(attr, "$I30") && attr->FormCode == NTFS_FORM_RESIDENT && view->ResidentSize) {
|
|
file->IndexRoot = (NTFS_INDEX_ROOT*)MmAllocatePool(view->ResidentSize);
|
|
if (!file->IndexRoot) { ctx->Status = NTLSTATUS_NO_MEMORY; return false; }
|
|
memcpy(file->IndexRoot, view->ResidentData, view->ResidentSize);
|
|
file->IndexRootSize = view->ResidentSize;
|
|
file->IsDirectory = true;
|
|
}
|
|
break;
|
|
}
|
|
case NTFS_ATTR_INDEX_ALLOCATION: {
|
|
if (NtfsAttrNameEqualsAscii(attr, "$I30") && attr->FormCode == NTFS_FORM_NONRESIDENT) {
|
|
ctx->Status = NtfsReadMappings(file->Vol, attr, &file->IndexRuns);
|
|
if (!KSUCCESS(ctx->Status)) return false;
|
|
}
|
|
break;
|
|
}
|
|
case NTFS_ATTR_ATTRIBUTE_LIST:
|
|
/* Complex attribute-list continuations are not expanded in this first native port. */
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static KSTATUS NtfsLoadFile(NTFS_VOLUME* vol, uint64_t inode, NTFS_FILE* file) {
|
|
if (!vol || !file) return NTLSTATUS_INVALID_PARAMETER;
|
|
NtfsInitFile(file, vol, inode);
|
|
void* record = MmAllocatePool((size_t)vol->FileRecordSize);
|
|
if (!record) return NTLSTATUS_NO_MEMORY;
|
|
KSTATUS s = NtfsReadRecord(vol, inode, record);
|
|
if (!KSUCCESS(s)) { MmFreePool(record); NtfsCleanupFile(file); return s; }
|
|
|
|
NTFS_LOAD_FILE_CTX ctx;
|
|
ctx.File = file;
|
|
ctx.Status = NTLSTATUS_SUCCESS;
|
|
s = NtfsForEachRecordAttribute(vol, inode, (const NTFS_FILE_RECORD_SEGMENT_HEADER*)record,
|
|
NtfsLoadFileAttributeCallback, &ctx);
|
|
MmFreePool(record);
|
|
if (!KSUCCESS(s)) { NtfsCleanupFile(file); return s; }
|
|
if (!KSUCCESS(ctx.Status)) { NtfsCleanupFile(file); return ctx.Status; }
|
|
if (file->ValidDataLength == 0 && file->ResidentDataSize) file->ValidDataLength = file->ResidentDataSize;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
static KSTATUS NtfsReadFileData(NTFS_FILE* file, uint64_t offset, void* buffer, size_t size, size_t* bytesRead) {
|
|
if (bytesRead) *bytesRead = 0;
|
|
if (!file || !buffer || !bytesRead) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (file->IsDirectory) return NTLSTATUS_ACCESS_DENIED;
|
|
if (offset >= file->Size || size == 0) return NTLSTATUS_SUCCESS;
|
|
|
|
uint64_t remaining = file->Size - offset;
|
|
if ((uint64_t)size > remaining) size = (size_t)remaining;
|
|
|
|
if (file->ResidentData) {
|
|
if (offset >= file->ResidentDataSize) {
|
|
memset(buffer, 0, size);
|
|
} else {
|
|
size_t avail = file->ResidentDataSize - (size_t)offset;
|
|
size_t copy = size < avail ? size : avail;
|
|
memcpy(buffer, file->ResidentData + offset, copy);
|
|
if (copy < size) memset((uint8_t*)buffer + copy, 0, size - copy);
|
|
}
|
|
*bytesRead = size;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
uint64_t validEnd = offset + size;
|
|
if (validEnd > file->ValidDataLength) validEnd = file->ValidDataLength;
|
|
if (offset >= file->ValidDataLength) {
|
|
memset(buffer, 0, size);
|
|
*bytesRead = size;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
uint64_t sectorSize = file->Vol->BytesPerSector;
|
|
uint64_t startAligned = offset & ~(sectorSize - 1u);
|
|
uint64_t endAligned = NtfsAlignUp(validEnd, sectorSize);
|
|
uint8_t* tmp = NULL;
|
|
uint8_t* ioBuffer = (uint8_t*)buffer;
|
|
uint64_t ioSize = endAligned - startAligned;
|
|
|
|
if (startAligned != offset || endAligned != validEnd) {
|
|
tmp = (uint8_t*)MmAllocatePool((size_t)ioSize);
|
|
if (!tmp) return NTLSTATUS_NO_MEMORY;
|
|
ioBuffer = tmp;
|
|
}
|
|
|
|
KSTATUS s = NtfsReadFromRuns(file->Vol, &file->DataRuns, startAligned, ioBuffer, ioSize);
|
|
if (!KSUCCESS(s)) { if (tmp) MmFreePool(tmp); return s; }
|
|
|
|
if (tmp) {
|
|
memcpy(buffer, tmp + (offset - startAligned), (size_t)(validEnd - offset));
|
|
MmFreePool(tmp);
|
|
}
|
|
if (validEnd < offset + size) {
|
|
memset((uint8_t*)buffer + (validEnd - offset), 0, (size_t)(offset + size - validEnd));
|
|
}
|
|
*bytesRead = size;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
static uint16_t NtfsUpcaseChar(NTFS_VOLUME* vol, uint16_t c) {
|
|
if (vol && vol->Upcase) return vol->Upcase[c];
|
|
if (c >= (uint16_t)'a' && c <= (uint16_t)'z') return (uint16_t)(c - 32u);
|
|
return c;
|
|
}
|
|
|
|
static int NtfsCompareUtf16Name(NTFS_VOLUME* vol, const uint16_t* a, size_t alen, const uint16_t* b, size_t blen) {
|
|
size_t n = alen < blen ? alen : blen;
|
|
for (size_t i = 0; i < n; i++) {
|
|
uint16_t ca = NtfsUpcaseChar(vol, a[i]);
|
|
uint16_t cb = NtfsUpcaseChar(vol, b[i]);
|
|
if (ca < cb) return -1;
|
|
if (ca > cb) return 1;
|
|
}
|
|
if (alen < blen) return -1;
|
|
if (alen > blen) return 1;
|
|
return 0;
|
|
}
|
|
|
|
static size_t NtfsUtf16ToAscii(const uint16_t* src, size_t len, char* dst, size_t dstSize) {
|
|
if (!dst || dstSize == 0) return 0;
|
|
size_t out = 0;
|
|
for (size_t i = 0; i < len && out + 1 < dstSize; i++) {
|
|
uint16_t c = src[i];
|
|
dst[out++] = (c >= 0x20u && c < 0x7fu) ? (char)c : '?';
|
|
}
|
|
dst[out] = '\0';
|
|
return out;
|
|
}
|
|
|
|
static void NtfsWinTimeToSystemTime(int64_t win, SYS_SYSTEM_TIME* out) {
|
|
memset(out, 0, sizeof(*out));
|
|
if (win <= 0) return;
|
|
|
|
uint64_t unixSeconds = (uint64_t)(win / 10000000ll);
|
|
if (unixSeconds >= 11644473600ull) unixSeconds -= 11644473600ull;
|
|
else unixSeconds = 0;
|
|
out->EpochSeconds = unixSeconds;
|
|
out->Nanosecond = (uint32_t)((win % 10000000ll) * 100ll);
|
|
out->Flags = SYS_TIME_FLAG_VALID;
|
|
out->TimeZoneMinutes = SYS_TIME_UNSPECIFIED_TZ;
|
|
|
|
uint64_t days = unixSeconds / 86400ull;
|
|
uint32_t rem = (uint32_t)(unixSeconds % 86400ull);
|
|
out->Hour = (uint8_t)(rem / 3600u);
|
|
out->Minute = (uint8_t)((rem % 3600u) / 60u);
|
|
out->Second = (uint8_t)(rem % 60u);
|
|
out->DayOfWeek = (uint8_t)((days + 4u) % 7u);
|
|
|
|
int64_t z = (int64_t)days + 719468ll;
|
|
int64_t era = (z >= 0 ? z : z - 146096ll) / 146097ll;
|
|
uint32_t doe = (uint32_t)(z - era * 146097ll);
|
|
uint32_t yoe = (doe - doe / 1460u + doe / 36524u - doe / 146096u) / 365u;
|
|
int64_t y = (int64_t)yoe + era * 400ll;
|
|
uint32_t doy = doe - (365u * yoe + yoe / 4u - yoe / 100u);
|
|
uint32_t mp = (5u * doy + 2u) / 153u;
|
|
uint32_t d = doy - (153u * mp + 2u) / 5u + 1u;
|
|
uint32_t m = mp + (mp < 10u ? 3u : (uint32_t)-9);
|
|
y += (m <= 2u);
|
|
out->Year = (uint16_t)y;
|
|
out->Month = (uint8_t)m;
|
|
out->Day = (uint8_t)d;
|
|
}
|
|
|
|
static uint32_t NtfsSysAttrsFromNtfs(uint32_t attr, bool isDir) {
|
|
uint32_t out = 0;
|
|
if (isDir || (attr & NTFS_FILE_ATTRIBUTE_DIRECTORY) || (attr & NTFS_FILE_ATTRIBUTE_DIRECTORY_MFT)) out |= SYS_FILE_ATTR_DIRECTORY;
|
|
if (attr & NTFS_FILE_ATTRIBUTE_READONLY) out |= SYS_FILE_ATTR_READ_ONLY;
|
|
if (attr & NTFS_FILE_ATTRIBUTE_HIDDEN) out |= SYS_FILE_ATTR_HIDDEN;
|
|
if (attr & NTFS_FILE_ATTRIBUTE_SYSTEM) out |= SYS_FILE_ATTR_SYSTEM;
|
|
if (attr & NTFS_FILE_ATTRIBUTE_ARCHIVE) out |= SYS_FILE_ATTR_ARCHIVE;
|
|
return out;
|
|
}
|
|
|
|
static void NtfsFillStat(NTFS_FILE* file, SYS_FILE_STAT* out) {
|
|
memset(out, 0, sizeof(*out));
|
|
out->Size = sizeof(*out);
|
|
out->Type = file->IsDirectory ? SYS_FILE_TYPE_DIRECTORY : SYS_FILE_TYPE_REGULAR;
|
|
out->Attributes = NtfsSysAttrsFromNtfs(file->Attributes, file->IsDirectory);
|
|
out->LinkCount = 1;
|
|
out->FileSize = file->Size;
|
|
out->VolumeId = file->Vol->SerialNumber;
|
|
out->FileId = file->Inode;
|
|
NtfsWinTimeToSystemTime(file->StandardInfo.CreationTime, &out->CreationTime);
|
|
NtfsWinTimeToSystemTime(file->StandardInfo.LastAccessTime, &out->LastAccessTime);
|
|
NtfsWinTimeToSystemTime(file->StandardInfo.LastWriteTime, &out->LastWriteTime);
|
|
NtfsWinTimeToSystemTime(file->StandardInfo.ChangeTime, &out->ChangeTime);
|
|
}
|
|
|
|
typedef bool (*NTFS_INDEX_ENTRY_CALLBACK)(const NTFS_FILE_NAME_ATTR* fn, uint64_t inode, void* ctx);
|
|
|
|
static KSTATUS NtfsWalkIndexBuffer(NTFS_VOLUME* vol,
|
|
NTFS_INDEX_ROOT* root,
|
|
const LIST_ENTRY* indexRuns,
|
|
const NTFS_INDEX_NODE_HEADER* nodeHeader,
|
|
const uint8_t* nodeBase,
|
|
size_t nodeBytes,
|
|
NTFS_INDEX_ENTRY_CALLBACK callback,
|
|
void* ctx,
|
|
uint32_t depth) {
|
|
if (!vol || !root || !nodeHeader || !nodeBase || !callback) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (depth > 16u) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (nodeHeader->FirstEntry > nodeBytes) {
|
|
NtfsTrace("NTFS: index walk: firstEntry=%u nodeBytes=%llu depth=%u\n",
|
|
nodeHeader->FirstEntry, (unsigned long long)nodeBytes, depth);
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
uint32_t offset = nodeHeader->FirstEntry;
|
|
uint32_t limit = nodeHeader->TotalSize;
|
|
if (limit == 0 || limit > nodeBytes) limit = (uint32_t)nodeBytes;
|
|
|
|
while (offset + sizeof(NTFS_INDEX_ENTRY) <= limit) {
|
|
const NTFS_INDEX_ENTRY* ent = (const NTFS_INDEX_ENTRY*)(nodeBase + offset);
|
|
if (ent->EntryLength < sizeof(NTFS_INDEX_ENTRY) || offset + ent->EntryLength > limit) {
|
|
NtfsTrace("NTFS: index walk: bad entry off=%u len=%u limit=%u flags=0x%x depth=%u\n",
|
|
offset, ent->EntryLength, limit, ent->Flags, depth);
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (!(ent->Flags & NTFS_INDEX_ENTRY_LAST) && ent->StreamLength >= offsetof(NTFS_FILE_NAME_ATTR, FileName)) {
|
|
const NTFS_FILE_NAME_ATTR* fn = (const NTFS_FILE_NAME_ATTR*)((const uint8_t*)ent + sizeof(NTFS_INDEX_ENTRY));
|
|
size_t needed = offsetof(NTFS_FILE_NAME_ATTR, FileName) + (size_t)fn->FileNameLength * sizeof(uint16_t);
|
|
if (needed <= ent->StreamLength && fn->Namespace != NTFS_FILE_NAME_DOS) {
|
|
if (!callback(fn, NtfsFileReferenceNumber(ent->FileReference), ctx)) return NTLSTATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
if (ent->Flags & NTFS_INDEX_ENTRY_SUBNODE) {
|
|
if (ent->EntryLength < sizeof(NTFS_INDEX_ENTRY) + sizeof(uint64_t)) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (!indexRuns || LIST_IS_EMPTY(indexRuns)) {
|
|
NtfsTrace("NTFS: index walk: subnode present but no $INDEX_ALLOCATION runs depth=%u off=%u\n",
|
|
depth, offset);
|
|
return NTLSTATUS_NOT_SUPPORTED;
|
|
}
|
|
uint64_t childVcn = *(const uint64_t*)((const uint8_t*)ent + ent->EntryLength - sizeof(uint64_t));
|
|
uint64_t childOffset;
|
|
if (root->BytesPerIndexRecord < vol->BytesPerCluster) childOffset = childVcn * vol->BytesPerSector;
|
|
else childOffset = childVcn * (uint64_t)vol->BytesPerCluster;
|
|
|
|
uint8_t* record = (uint8_t*)MmAllocatePool(root->BytesPerIndexRecord);
|
|
if (!record) return NTLSTATUS_NO_MEMORY;
|
|
KSTATUS s = NtfsReadFromRuns(vol, indexRuns, childOffset, record, root->BytesPerIndexRecord);
|
|
if (KSUCCESS(s)) {
|
|
NTFS_INDEX_RECORD* ir = (NTFS_INDEX_RECORD*)record;
|
|
if (ir->MultiSectorHeader.Signature != NTFS_INDEX_SIGNATURE) {
|
|
NtfsTrace("NTFS: index walk: child VCN=%llu offset=%llu signature=0x%x, expected INDX\n",
|
|
(unsigned long long)childVcn,
|
|
(unsigned long long)childOffset,
|
|
ir->MultiSectorHeader.Signature);
|
|
s = NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
else s = NtfsProcessFixups(&ir->MultiSectorHeader, root->BytesPerIndexRecord, vol->BytesPerSector);
|
|
if (KSUCCESS(s)) {
|
|
const uint8_t* recNodeBase = (const uint8_t*)&ir->Header;
|
|
size_t recNodeBytes = root->BytesPerIndexRecord - offsetof(NTFS_INDEX_RECORD, Header);
|
|
s = NtfsWalkIndexBuffer(vol, root, indexRuns, &ir->Header, recNodeBase, recNodeBytes, callback, ctx, depth + 1u);
|
|
}
|
|
}
|
|
MmFreePool(record);
|
|
if (!KSUCCESS(s)) {
|
|
NtfsTrace("NTFS: index walk: child VCN=%llu failed st=0x%x depth=%u\n",
|
|
(unsigned long long)childVcn, s, depth);
|
|
return s;
|
|
}
|
|
}
|
|
|
|
if (ent->Flags & NTFS_INDEX_ENTRY_LAST) break;
|
|
offset += ent->EntryLength;
|
|
}
|
|
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
static KSTATUS NtfsWalkDirectory(NTFS_FILE* dir, NTFS_INDEX_ENTRY_CALLBACK callback, void* ctx) {
|
|
if (!dir || !dir->IsDirectory || !dir->IndexRoot || !callback) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (dir->IndexRootSize < sizeof(NTFS_INDEX_ROOT)) return NTLSTATUS_INVALID_PARAMETER;
|
|
const uint8_t* nodeBase = (const uint8_t*)&dir->IndexRoot->NodeHeader;
|
|
size_t nodeBytes = dir->IndexRootSize - offsetof(NTFS_INDEX_ROOT, NodeHeader);
|
|
return NtfsWalkIndexBuffer(dir->Vol, dir->IndexRoot, &dir->IndexRuns,
|
|
&dir->IndexRoot->NodeHeader, nodeBase, nodeBytes,
|
|
callback, ctx, 0u);
|
|
}
|
|
|
|
static bool NtfsFindEntryCallback(const NTFS_FILE_NAME_ATTR* fn, uint64_t inode, void* rawCtx) {
|
|
NTFS_FIND_CTX* ctx = (NTFS_FIND_CTX*)rawCtx;
|
|
if (NtfsCompareUtf16Name(ctx->Vol, ctx->Name, ctx->NameLength, fn->FileName, fn->FileNameLength) == 0) {
|
|
ctx->Inode = inode;
|
|
ctx->Found = true;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static KSTATUS NtfsFindInDirectory(NTFS_VOLUME* vol, uint64_t dirInode, const uint16_t* name, size_t nameLength, uint64_t* outInode) {
|
|
if (!vol || !name || !nameLength || !outInode) return NTLSTATUS_INVALID_PARAMETER;
|
|
NTFS_FILE dir;
|
|
KSTATUS s = NtfsLoadFile(vol, dirInode, &dir);
|
|
if (!KSUCCESS(s)) return s;
|
|
if (!dir.IsDirectory) { NtfsCleanupFile(&dir); return NTLSTATUS_NOT_FOUND; }
|
|
|
|
NTFS_FIND_CTX ctx;
|
|
memset(&ctx, 0, sizeof(ctx));
|
|
ctx.Vol = vol;
|
|
ctx.Name = name;
|
|
ctx.NameLength = nameLength;
|
|
s = NtfsWalkDirectory(&dir, NtfsFindEntryCallback, &ctx);
|
|
NtfsCleanupFile(&dir);
|
|
if (!KSUCCESS(s)) return s;
|
|
if (!ctx.Found) return NTLSTATUS_NOT_FOUND;
|
|
*outInode = ctx.Inode;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
static KSTATUS NtfsLookupPath(NTFS_VOLUME* vol, SYS_STRING_VIEW path, uint64_t* outInode) {
|
|
if (!vol || !outInode || !SysSvIsValid(path)) return NTLSTATUS_INVALID_PARAMETER;
|
|
uint64_t inode = NTFS_ROOT_DIR_INODE;
|
|
size_t pos = 0;
|
|
|
|
while (pos < path.Length && (path.Data[pos] == '\\' || path.Data[pos] == '/')) pos++;
|
|
if (pos >= path.Length) { *outInode = inode; return NTLSTATUS_SUCCESS; }
|
|
|
|
while (pos < path.Length) {
|
|
while (pos < path.Length && (path.Data[pos] == '\\' || path.Data[pos] == '/')) pos++;
|
|
if (pos >= path.Length) break;
|
|
size_t start = pos;
|
|
while (pos < path.Length && path.Data[pos] != '\\' && path.Data[pos] != '/') pos++;
|
|
size_t len = pos - start;
|
|
if (len == 0) continue;
|
|
if (len == 1 && path.Data[start] == '.') continue;
|
|
if (len == 2 && path.Data[start] == '.' && path.Data[start + 1u] == '.') return NTLSTATUS_INVALID_PARAMETER;
|
|
if (len > 255u) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
uint16_t segment[260];
|
|
for (size_t i = 0; i < len; i++) segment[i] = (uint16_t)(uint8_t)path.Data[start + i];
|
|
KSTATUS s = NtfsFindInDirectory(vol, inode, segment, len, &inode);
|
|
if (!KSUCCESS(s)) return s;
|
|
}
|
|
|
|
*outInode = inode;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
static bool NtfsShouldHideDirectoryEntry(const NTFS_FILE_NAME_ATTR* fn, bool hideNtfsMetadata) {
|
|
if (!fn) return true;
|
|
if (fn->FileNameLength == 0) return true;
|
|
if (fn->FileNameLength == 1 && fn->FileName[0] == (uint16_t)'.') return true;
|
|
|
|
/* The NTFS root directory exposes metadata files such as $MFT, $Bitmap,
|
|
$LogFile, and $Extend. They are filesystem implementation details, not
|
|
normal user directory entries, so keep them out of directory listings.
|
|
Lookup still sees the raw index when the driver needs metadata internally. */
|
|
if (hideNtfsMetadata && fn->FileName[0] == (uint16_t)'$') return true;
|
|
return false;
|
|
}
|
|
|
|
static bool NtfsCollectEntryCallback(const NTFS_FILE_NAME_ATTR* fn, uint64_t inode, void* rawCtx) {
|
|
NTFS_COLLECT_CTX* ctx = (NTFS_COLLECT_CTX*)rawCtx;
|
|
NTFS_DIR_CTX* dir = ctx->Dir;
|
|
if (NtfsShouldHideDirectoryEntry(fn, ctx->HideNtfsMetadata)) return true;
|
|
if (dir->Count == dir->Capacity) {
|
|
uint32_t newCap = dir->Capacity ? dir->Capacity * 2u : 32u;
|
|
if (newCap < dir->Capacity) { ctx->Status = NTLSTATUS_NO_MEMORY; return false; }
|
|
NTFS_DIRENT_CACHE* newer = (NTFS_DIRENT_CACHE*)MmReallocatePool(dir->Entries, (size_t)newCap * sizeof(NTFS_DIRENT_CACHE));
|
|
if (!newer) { ctx->Status = NTLSTATUS_NO_MEMORY; return false; }
|
|
dir->Entries = newer;
|
|
dir->Capacity = newCap;
|
|
}
|
|
|
|
NTFS_DIRENT_CACHE* e = &dir->Entries[dir->Count++];
|
|
memset(e, 0, sizeof(*e));
|
|
e->Inode = inode;
|
|
e->FileSize = fn->EndOfFile;
|
|
e->AllocSize = fn->AllocationSize;
|
|
e->Attributes = fn->FileAttributes;
|
|
e->CreationTime = fn->CreationTime;
|
|
e->LastAccessTime = fn->LastAccessTime;
|
|
e->LastWriteTime = fn->LastWriteTime;
|
|
NtfsUtf16ToAscii(fn->FileName, fn->FileNameLength, e->Name, sizeof(e->Name));
|
|
ctx->Status = NTLSTATUS_SUCCESS;
|
|
return true;
|
|
}
|
|
|
|
static KSTATUS NtfsOpen(IO_FILESYSTEM* fs, SYS_STRING_VIEW path, void** fileCtx) {
|
|
if (!fs || !fileCtx || !SysSvIsValid(path)) return NTLSTATUS_INVALID_PARAMETER;
|
|
NTFS_VOLUME* vol = (NTFS_VOLUME*)fs->DriverCtx;
|
|
if (!vol) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
uint64_t inode;
|
|
KSTATUS s = NtfsLookupPath(vol, path, &inode);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
NTFS_FILE* file = (NTFS_FILE*)MmAllocatePool(sizeof(NTFS_FILE));
|
|
if (!file) return NTLSTATUS_NO_MEMORY;
|
|
s = NtfsLoadFile(vol, inode, file);
|
|
if (!KSUCCESS(s)) { MmFreePool(file); return s; }
|
|
*fileCtx = file;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
static void NtfsClose(IO_FILESYSTEM* fs, void* fileCtx) {
|
|
(void)fs;
|
|
NTFS_FILE* file = (NTFS_FILE*)fileCtx;
|
|
if (!file) return;
|
|
NtfsCleanupFile(file);
|
|
MmFreePool(file);
|
|
}
|
|
|
|
static KSTATUS NtfsRead(IO_FILESYSTEM* fs, void* fileCtx, uint64_t offset, void* buf, size_t size, size_t* bytesRead) {
|
|
(void)fs;
|
|
return NtfsReadFileData((NTFS_FILE*)fileCtx, offset, buf, size, bytesRead);
|
|
}
|
|
|
|
static KSTATUS NtfsWrite(IO_FILESYSTEM* fs, void* fileCtx, uint64_t offset, const void* buf, size_t size, size_t* bytesWritten) {
|
|
(void)fs; (void)fileCtx; (void)offset; (void)buf; (void)size;
|
|
if (bytesWritten) *bytesWritten = 0;
|
|
return NTLSTATUS_NOT_SUPPORTED;
|
|
}
|
|
|
|
static KSTATUS NtfsCreate(IO_FILESYSTEM* fs, SYS_STRING_VIEW path, void** fileCtx) {
|
|
(void)fs; (void)path; (void)fileCtx;
|
|
return NTLSTATUS_NOT_SUPPORTED;
|
|
}
|
|
|
|
static KSTATUS NtfsDelete(IO_FILESYSTEM* fs, SYS_STRING_VIEW path) {
|
|
(void)fs; (void)path;
|
|
return NTLSTATUS_NOT_SUPPORTED;
|
|
}
|
|
|
|
static KSTATUS NtfsTruncate(IO_FILESYSTEM* fs, void* fileCtx, uint64_t newSize, uint32_t fileFlags) {
|
|
(void)fs; (void)fileCtx; (void)newSize; (void)fileFlags;
|
|
return NTLSTATUS_NOT_SUPPORTED;
|
|
}
|
|
|
|
static uint64_t NtfsGetSize(IO_FILESYSTEM* fs, void* fileCtx) {
|
|
(void)fs;
|
|
NTFS_FILE* file = (NTFS_FILE*)fileCtx;
|
|
return file ? file->Size : 0;
|
|
}
|
|
|
|
static KSTATUS NtfsQueryInfo(IO_FILESYSTEM* fs, void* fileCtx, SYS_FILE_STAT* out) {
|
|
(void)fs;
|
|
if (!fileCtx || !out) return NTLSTATUS_INVALID_PARAMETER;
|
|
NtfsFillStat((NTFS_FILE*)fileCtx, out);
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
static KSTATUS NtfsStat(IO_FILESYSTEM* fs, SYS_STRING_VIEW path, SYS_FILE_STAT* out) {
|
|
if (!fs || !out || !SysSvIsValid(path)) return NTLSTATUS_INVALID_PARAMETER;
|
|
void* ctx = NULL;
|
|
KSTATUS s = NtfsOpen(fs, path, &ctx);
|
|
if (!KSUCCESS(s)) return s;
|
|
s = NtfsQueryInfo(fs, ctx, out);
|
|
NtfsClose(fs, ctx);
|
|
return s;
|
|
}
|
|
|
|
static KSTATUS NtfsQueryIdentity(IO_FILESYSTEM* fs, void* fileCtx, IO_FILE_IDENTITY* out) {
|
|
(void)fs;
|
|
if (!fileCtx || !out) return NTLSTATUS_INVALID_PARAMETER;
|
|
NTFS_FILE* file = (NTFS_FILE*)fileCtx;
|
|
out->VolumeId = file->Vol->SerialNumber;
|
|
out->FileId = file->Inode;
|
|
out->Generation = (uint64_t)file->StandardInfo.LastWriteTime ^ ((uint64_t)file->StandardInfo.ChangeTime << 1u);
|
|
out->Size = file->Size;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
static KSTATUS NtfsOpenDir(IO_FILESYSTEM* fs, SYS_STRING_VIEW dirPath, void** dirCtx) {
|
|
if (!fs || !dirCtx || !SysSvIsValid(dirPath)) return NTLSTATUS_INVALID_PARAMETER;
|
|
NTFS_VOLUME* vol = (NTFS_VOLUME*)fs->DriverCtx;
|
|
if (!vol) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
uint64_t inode;
|
|
KSTATUS s = NtfsLookupPath(vol, dirPath, &inode);
|
|
if (!KSUCCESS(s)) {
|
|
NtfsTrace("NTFS: opendir: lookup failed drive=%u pathLen=%llu st=0x%x\n",
|
|
vol->VolumeIndex, (unsigned long long)dirPath.Length, s);
|
|
return s;
|
|
}
|
|
|
|
NTFS_FILE dir;
|
|
s = NtfsLoadFile(vol, inode, &dir);
|
|
if (!KSUCCESS(s)) {
|
|
NtfsTrace("NTFS: opendir: load inode=%llu failed st=0x%x\n",
|
|
(unsigned long long)inode, s);
|
|
return s;
|
|
}
|
|
if (!dir.IsDirectory) {
|
|
NtfsTrace("NTFS: opendir: inode=%llu is not directory attr=0x%x indexRoot=0x%016llX\n",
|
|
(unsigned long long)inode, dir.Attributes, dir.IndexRoot);
|
|
NtfsCleanupFile(&dir);
|
|
return NTLSTATUS_NOT_FOUND;
|
|
}
|
|
if (!dir.IndexRoot) {
|
|
NtfsTrace("NTFS: opendir: inode=%llu has no $I30 $INDEX_ROOT\n",
|
|
(unsigned long long)inode);
|
|
} else {
|
|
NtfsTrace("NTFS: opendir: inode=%llu indexRootSize=%llu indexRecordBytes=%u indexRunsEmpty=%u\n",
|
|
(unsigned long long)inode,
|
|
(unsigned long long)dir.IndexRootSize,
|
|
dir.IndexRoot->BytesPerIndexRecord,
|
|
LIST_IS_EMPTY(&dir.IndexRuns) ? 1u : 0u);
|
|
}
|
|
|
|
NTFS_DIR_CTX* ctx = (NTFS_DIR_CTX*)MmAllocatePool(sizeof(NTFS_DIR_CTX));
|
|
if (!ctx) { NtfsCleanupFile(&dir); return NTLSTATUS_NO_MEMORY; }
|
|
memset(ctx, 0, sizeof(*ctx));
|
|
|
|
NTFS_COLLECT_CTX collect;
|
|
memset(&collect, 0, sizeof(collect));
|
|
collect.Vol = vol;
|
|
collect.Dir = ctx;
|
|
collect.Status = NTLSTATUS_SUCCESS;
|
|
collect.HideNtfsMetadata = (inode == NTFS_ROOT_DIR_INODE);
|
|
s = NtfsWalkDirectory(&dir, NtfsCollectEntryCallback, &collect);
|
|
NtfsCleanupFile(&dir);
|
|
if (!KSUCCESS(s)) {
|
|
NtfsTrace("NTFS: opendir: walk inode=%llu failed st=0x%x collected=%u\n",
|
|
(unsigned long long)inode, s, ctx->Count);
|
|
if (ctx->Entries) MmFreePool(ctx->Entries);
|
|
MmFreePool(ctx);
|
|
return s;
|
|
}
|
|
if (!KSUCCESS(collect.Status)) {
|
|
NtfsTrace("NTFS: opendir: collect inode=%llu failed st=0x%x collected=%u\n",
|
|
(unsigned long long)inode, collect.Status, ctx->Count);
|
|
if (ctx->Entries) MmFreePool(ctx->Entries);
|
|
MmFreePool(ctx);
|
|
return collect.Status;
|
|
}
|
|
|
|
NtfsTrace("NTFS: opendir: inode=%llu OK entries=%u\n",
|
|
(unsigned long long)inode, ctx->Count);
|
|
|
|
*dirCtx = ctx;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
static KSTATUS NtfsReadDir(IO_FILESYSTEM* fs, void* dirCtx, KDIR_ENTRY* out) {
|
|
(void)fs;
|
|
if (!dirCtx || !out) return NTLSTATUS_INVALID_PARAMETER;
|
|
NTFS_DIR_CTX* ctx = (NTFS_DIR_CTX*)dirCtx;
|
|
if (ctx->Position >= ctx->Count) return NTLSTATUS_NO_MORE_FILES;
|
|
NTFS_DIRENT_CACHE* e = &ctx->Entries[ctx->Position++];
|
|
memset(out, 0, sizeof(*out));
|
|
strncpy(out->Name, e->Name, sizeof(out->Name) - 1u);
|
|
out->Attr = (uint8_t)NtfsSysAttrsFromNtfs(e->Attributes, (e->Attributes & NTFS_FILE_ATTRIBUTE_DIRECTORY_MFT) != 0);
|
|
out->FileSize = e->FileSize > 0xffffffffull ? 0xffffffffu : (uint32_t)e->FileSize;
|
|
NtfsWinTimeToSystemTime(e->CreationTime, &out->CreateTime);
|
|
NtfsWinTimeToSystemTime(e->LastAccessTime, &out->LastAccessTime);
|
|
NtfsWinTimeToSystemTime(e->LastWriteTime, &out->LastWriteTime);
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
static void NtfsCloseDir(IO_FILESYSTEM* fs, void* dirCtx) {
|
|
(void)fs;
|
|
NTFS_DIR_CTX* ctx = (NTFS_DIR_CTX*)dirCtx;
|
|
if (!ctx) return;
|
|
if (ctx->Entries) MmFreePool(ctx->Entries);
|
|
MmFreePool(ctx);
|
|
}
|
|
|
|
static void NtfsQueryStats(IO_FILESYSTEM* fs, IO_FILESYSTEM_STATS* out) {
|
|
(void)fs;
|
|
if (out) memset(out, 0, sizeof(*out));
|
|
}
|
|
|
|
static void NtfsInitVtable(IO_FILESYSTEM* fs, NTFS_VOLUME* vol) {
|
|
memset(fs, 0, sizeof(*fs));
|
|
fs->Open = NtfsOpen;
|
|
fs->Read = NtfsRead;
|
|
fs->Write = NtfsWrite;
|
|
fs->Create = NtfsCreate;
|
|
fs->Delete = NtfsDelete;
|
|
fs->Truncate = NtfsTruncate;
|
|
fs->GetSize = NtfsGetSize;
|
|
fs->QueryInfo = NtfsQueryInfo;
|
|
fs->Stat = NtfsStat;
|
|
fs->QueryIdentity = NtfsQueryIdentity;
|
|
fs->Close = NtfsClose;
|
|
fs->QueryStats = NtfsQueryStats;
|
|
fs->DriverCtx = vol;
|
|
fs->OpenDir = NtfsOpenDir;
|
|
fs->ReadDir = NtfsReadDir;
|
|
fs->CloseDir = NtfsCloseDir;
|
|
}
|
|
|
|
static bool NtfsGuidIsZero(const NTFS_GPT_GUID* guid) {
|
|
static const NTFS_GPT_GUID zeroGuid = {0};
|
|
return memcmp(guid, &zeroGuid, sizeof(*guid)) == 0;
|
|
}
|
|
|
|
static bool NtfsIsValidBootSector(const NTFS_BOOT_SECTOR* bs) {
|
|
if (!bs) return false;
|
|
if (memcmp(bs->FsName, NTFS_FS_NAME, 8) != 0) return false;
|
|
if (!NtfsIsPowerOfTwo32(bs->BytesPerSector)) return false;
|
|
if (bs->BytesPerSector != NTFS_SECTOR_SIZE) return false;
|
|
if (!NtfsIsPowerOfTwo32(bs->SectorsPerCluster)) return false;
|
|
if (bs->TotalSectors == 0 || bs->MFT == 0) return false;
|
|
return true;
|
|
}
|
|
|
|
static uint64_t NtfsDecodeRecordSize(int8_t clusters, uint32_t bytesPerCluster) {
|
|
if (clusters > 0) return (uint64_t)(uint8_t)clusters * bytesPerCluster;
|
|
if (clusters < 0 && clusters > -64) return 1ull << (uint8_t)(-clusters);
|
|
return 0;
|
|
}
|
|
|
|
typedef struct NTFS_MFT_CTX {
|
|
NTFS_VOLUME* Vol;
|
|
KSTATUS Status;
|
|
bool Found;
|
|
} NTFS_MFT_CTX;
|
|
|
|
static bool NtfsMftAttributeCallback(const NTFS_ATTR_VIEW* view, void* rawCtx) {
|
|
NTFS_MFT_CTX* ctx = (NTFS_MFT_CTX*)rawCtx;
|
|
const NTFS_ATTRIBUTE_RECORD_HEADER* attr = view->Attr;
|
|
if (attr->TypeCode == NTFS_ATTR_DATA && attr->NameLength == 0 && attr->FormCode == NTFS_FORM_NONRESIDENT) {
|
|
ctx->Status = NtfsReadMappings(ctx->Vol, attr, &ctx->Vol->MftRuns);
|
|
ctx->Found = KSUCCESS(ctx->Status);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static KSTATUS NtfsReadMftRuns(NTFS_VOLUME* vol) {
|
|
void* record = MmAllocatePool((size_t)vol->FileRecordSize);
|
|
if (!record) return NTLSTATUS_NO_MEMORY;
|
|
uint64_t mftByte = vol->MftStartCluster * vol->BytesPerCluster;
|
|
KSTATUS s = NtfsReadBytesDirect(vol, mftByte, vol->FileRecordSize, record);
|
|
if (KSUCCESS(s)) {
|
|
NTFS_FILE_RECORD_SEGMENT_HEADER* fr = (NTFS_FILE_RECORD_SEGMENT_HEADER*)record;
|
|
if (fr->MultiSectorHeader.Signature != NTFS_FILE_SIGNATURE) s = NTLSTATUS_INVALID_PARAMETER;
|
|
else s = NtfsProcessFixups(&fr->MultiSectorHeader, vol->FileRecordSize, vol->BytesPerSector);
|
|
}
|
|
if (!KSUCCESS(s)) { MmFreePool(record); return s; }
|
|
|
|
NTFS_MFT_CTX ctx;
|
|
ctx.Vol = vol;
|
|
ctx.Status = NTLSTATUS_NOT_FOUND;
|
|
ctx.Found = false;
|
|
s = NtfsForEachRecordAttribute(vol, NTFS_MFT_INODE, (const NTFS_FILE_RECORD_SEGMENT_HEADER*)record,
|
|
NtfsMftAttributeCallback, &ctx);
|
|
MmFreePool(record);
|
|
if (!KSUCCESS(s)) return s;
|
|
if (!ctx.Found) return NTLSTATUS_NOT_FOUND;
|
|
if (KSUCCESS(ctx.Status)) NtfsDebugDumpRuns("mft", &vol->MftRuns);
|
|
return ctx.Status;
|
|
}
|
|
|
|
static KSTATUS NtfsInitUpcaseIdentity(NTFS_VOLUME* vol) {
|
|
if (!vol) return NTLSTATUS_INVALID_PARAMETER;
|
|
vol->Upcase = (uint16_t*)MmAllocatePool(NTFS_UPCASE_TABLE_BYTES);
|
|
if (!vol->Upcase) return NTLSTATUS_NO_MEMORY;
|
|
for (uint32_t i = 0; i < NTFS_UPCASE_TABLE_CHARS; i++) vol->Upcase[i] = (uint16_t)i;
|
|
for (uint16_t c = (uint16_t)'a'; c <= (uint16_t)'z'; c++) vol->Upcase[c] = (uint16_t)(c - 32u);
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
static void NtfsCleanupVolume(NTFS_VOLUME* vol) {
|
|
if (!vol) return;
|
|
if (vol->MftRuns.Fwd) NtfsFreeRuns(&vol->MftRuns);
|
|
if (vol->Upcase) {
|
|
MmFreePool(vol->Upcase);
|
|
vol->Upcase = (uint16_t*)0;
|
|
}
|
|
}
|
|
|
|
static void NtfsTryLoadUpcase(NTFS_VOLUME* vol) {
|
|
NTFS_FILE upcase;
|
|
if (!KSUCCESS(NtfsLoadFile(vol, NTFS_UPCASE_INODE, &upcase))) return;
|
|
if (vol->Upcase && upcase.Size >= NTFS_UPCASE_TABLE_BYTES) {
|
|
size_t br = 0;
|
|
(void)NtfsReadFileData(&upcase, 0, vol->Upcase, NTFS_UPCASE_TABLE_BYTES, &br);
|
|
}
|
|
NtfsCleanupFile(&upcase);
|
|
}
|
|
|
|
static KSTATUS NtfsProbeAndPrepareVolume(NTFS_VOLUME* vol, uint8_t disk, uint64_t startLba, uint64_t sectors) {
|
|
if (!vol) return NTLSTATUS_INVALID_PARAMETER;
|
|
memset(vol, 0, sizeof(*vol));
|
|
vol->DriveIndex = disk;
|
|
vol->PartitionLba = startLba;
|
|
vol->PartitionSize = sectors;
|
|
vol->BytesPerSector = NTFS_SECTOR_SIZE;
|
|
NtfsInitRunList(&vol->MftRuns);
|
|
|
|
uint8_t sector[NTFS_SECTOR_SIZE];
|
|
memset(sector, 0, sizeof(sector));
|
|
KSTATUS s = NtfsReadSectors(vol, 0, 1, sector);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
const NTFS_BOOT_SECTOR* bs = (const NTFS_BOOT_SECTOR*)sector;
|
|
if (!NtfsIsValidBootSector(bs)) return NTLSTATUS_NOT_SUPPORTED;
|
|
|
|
vol->BytesPerSector = bs->BytesPerSector;
|
|
vol->SectorsPerCluster = bs->SectorsPerCluster;
|
|
vol->BytesPerCluster = vol->BytesPerSector * vol->SectorsPerCluster;
|
|
vol->TotalSectors = bs->TotalSectors;
|
|
vol->PartitionSize = sectors ? sectors : bs->TotalSectors;
|
|
if (vol->PartitionSize > bs->TotalSectors) vol->PartitionSize = bs->TotalSectors;
|
|
vol->SerialNumber = bs->SerialNumber;
|
|
vol->MftStartCluster = bs->MFT;
|
|
vol->FileRecordSize = NtfsDecodeRecordSize(bs->ClustersPerMFTRecord, vol->BytesPerCluster);
|
|
if (vol->FileRecordSize == 0 || vol->FileRecordSize > 65536ull || (vol->FileRecordSize % vol->BytesPerSector) != 0)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
s = NtfsInitUpcaseIdentity(vol);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
s = NtfsReadMftRuns(vol);
|
|
if (!KSUCCESS(s)) { NtfsCleanupVolume(vol); return s; }
|
|
NtfsTryLoadUpcase(vol);
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
static uint32_t NtfsEnumerateDiskPartitions(uint8_t disk, NTFS_PARTITION_CANDIDATE* parts, uint32_t maxParts, bool* mediaReadable) {
|
|
if (mediaReadable) *mediaReadable = false;
|
|
if (!parts || maxParts == 0) return 0;
|
|
uint8_t sector[NTFS_SECTOR_SIZE];
|
|
NTFS_VOLUME raw;
|
|
memset(&raw, 0, sizeof(raw));
|
|
raw.DriveIndex = disk;
|
|
raw.PartitionLba = 0;
|
|
raw.PartitionSize = IoDiskSectorCount(disk);
|
|
raw.BytesPerSector = NTFS_SECTOR_SIZE;
|
|
NtfsInitRunList(&raw.MftRuns);
|
|
|
|
if (!KSUCCESS(NtfsReadSectors(&raw, 0, 1, sector))) return 0;
|
|
if (mediaReadable) *mediaReadable = true;
|
|
|
|
uint32_t count = 0;
|
|
NTFS_BOOT_SECTOR* vbr = (NTFS_BOOT_SECTOR*)sector;
|
|
if (NtfsIsValidBootSector(vbr)) {
|
|
parts[count].StartLba = 0;
|
|
parts[count].SectorCount = vbr->TotalSectors;
|
|
count++;
|
|
if (count >= maxParts) return count;
|
|
}
|
|
|
|
if (sector[510] != 0x55u || sector[511] != 0xaau) return count;
|
|
|
|
NTFS_MBR_PARTITION* mbr = (NTFS_MBR_PARTITION*)(sector + 446u);
|
|
bool protectiveMbr = false;
|
|
for (uint32_t i = 0; i < 4u; i++) if (mbr[i].OsType == 0xeeu) protectiveMbr = true;
|
|
|
|
if (protectiveMbr) {
|
|
uint8_t gptSector[NTFS_SECTOR_SIZE];
|
|
if (!KSUCCESS(NtfsReadSectors(&raw, 1, 1, gptSector))) return count;
|
|
NTFS_GPT_HEADER* hdr = (NTFS_GPT_HEADER*)gptSector;
|
|
if (memcmp(hdr->Signature, "EFI PART", 8) != 0 || hdr->SizeOfPartitionEntry < sizeof(NTFS_GPT_ENTRY)) return count;
|
|
uint32_t entries = hdr->NumberOfPartitionEntries;
|
|
if (entries > 256u) entries = 256u;
|
|
uint32_t entrySize = hdr->SizeOfPartitionEntry;
|
|
uint8_t entrySector[NTFS_SECTOR_SIZE];
|
|
for (uint32_t i = 0; i < entries && count < maxParts; i++) {
|
|
uint64_t byteOff = (uint64_t)i * entrySize;
|
|
uint64_t lba = hdr->PartitionEntryLBA + byteOff / NTFS_SECTOR_SIZE;
|
|
uint32_t inSector = (uint32_t)(byteOff % NTFS_SECTOR_SIZE);
|
|
if (inSector + sizeof(NTFS_GPT_ENTRY) > NTFS_SECTOR_SIZE) continue;
|
|
if (!KSUCCESS(NtfsReadSectors(&raw, lba, 1, entrySector))) break;
|
|
NTFS_GPT_ENTRY* e = (NTFS_GPT_ENTRY*)(entrySector + inSector);
|
|
if (NtfsGuidIsZero(&e->PartitionTypeGUID)) continue;
|
|
if (e->EndingLBA < e->StartingLBA) continue;
|
|
parts[count].StartLba = e->StartingLBA;
|
|
parts[count].SectorCount = e->EndingLBA - e->StartingLBA + 1ull;
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < 4u && count < maxParts; i++) {
|
|
if (mbr[i].OsType == 0 || mbr[i].SizeInLba == 0) continue;
|
|
parts[count].StartLba = mbr[i].StartingLba;
|
|
parts[count].SectorCount = mbr[i].SizeInLba;
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static int NtfsReserveFreeVolumeIndex(void) {
|
|
uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock);
|
|
for (uint8_t i = 0; i < NTFS_MAX_VOLUMES; i++) {
|
|
if (!gVolumeMounted[i] && !gVolumeBusy[i] && !IoFilesystemPresent(i)) {
|
|
gVolumeBusy[i] = true;
|
|
KeReleaseSpinlock(&gNtfsMountLock, flags);
|
|
return (int)i;
|
|
}
|
|
}
|
|
KeReleaseSpinlock(&gNtfsMountLock, flags);
|
|
return -1;
|
|
}
|
|
|
|
static void NtfsReleaseReservedVolumeIndex(uint8_t volumeIndex) {
|
|
if (volumeIndex >= NTFS_MAX_VOLUMES) return;
|
|
uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock);
|
|
gVolumeBusy[volumeIndex] = false;
|
|
KeReleaseSpinlock(&gNtfsMountLock, flags);
|
|
}
|
|
|
|
static bool NtfsBeginDiskScan(uint8_t disk) {
|
|
if (disk >= NTFS_MAX_VOLUMES) return false;
|
|
uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock);
|
|
if (gDiskScanned[disk]) {
|
|
KeReleaseSpinlock(&gNtfsMountLock, flags);
|
|
return false;
|
|
}
|
|
gDiskScanned[disk] = true;
|
|
KeReleaseSpinlock(&gNtfsMountLock, flags);
|
|
return true;
|
|
}
|
|
|
|
static void NtfsEndDiskScan(uint8_t disk, bool completed) {
|
|
if (disk >= NTFS_MAX_VOLUMES) return;
|
|
if (completed) return;
|
|
uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock);
|
|
gDiskScanned[disk] = false;
|
|
KeReleaseSpinlock(&gNtfsMountLock, flags);
|
|
}
|
|
|
|
static uint32_t NtfsTryMountDisk(uint8_t disk) {
|
|
if (disk >= NTFS_MAX_VOLUMES) return 0;
|
|
if (!IoDiskDevicePresent(disk)) return 0;
|
|
if (!NtfsBeginDiskScan(disk)) return 0;
|
|
|
|
bool scanCompleted = false;
|
|
bool mediaReadable = false;
|
|
NTFS_PARTITION_CANDIDATE parts[NTFS_MAX_PARTITIONS_PER_DISK];
|
|
uint32_t partCount = NtfsEnumerateDiskPartitions(disk, parts, NTFS_MAX_PARTITIONS_PER_DISK, &mediaReadable);
|
|
if (!mediaReadable) {
|
|
DbgPrint("NTFS: disk %u: media probe deferred; sector 0 unreadable\n", disk);
|
|
NtfsEndDiskScan(disk, false);
|
|
return 0;
|
|
}
|
|
scanCompleted = true;
|
|
|
|
uint32_t mounted = 0;
|
|
for (uint32_t p = 0; p < partCount; p++) {
|
|
int freeVolume = NtfsReserveFreeVolumeIndex();
|
|
if (freeVolume < 0) break;
|
|
uint8_t volumeIndex = (uint8_t)freeVolume;
|
|
KSTATUS s = NtfsProbeAndPrepareVolume(&gVolumes[volumeIndex], disk, parts[p].StartLba, parts[p].SectorCount);
|
|
if (!KSUCCESS(s)) {
|
|
NtfsCleanupVolume(&gVolumes[volumeIndex]);
|
|
memset(&gVolumes[volumeIndex], 0, sizeof(gVolumes[volumeIndex]));
|
|
NtfsReleaseReservedVolumeIndex(volumeIndex);
|
|
continue;
|
|
}
|
|
gVolumes[volumeIndex].VolumeIndex = volumeIndex;
|
|
|
|
NtfsInitVtable(&gFilesystems[volumeIndex], &gVolumes[volumeIndex]);
|
|
IO_VOLUME_PARAMS params;
|
|
params.DiskIndex = disk;
|
|
params.PartitionLba = parts[p].StartLba;
|
|
params.PartitionSize = gVolumes[volumeIndex].PartitionSize;
|
|
s = IoRegisterFilesystem(volumeIndex, &gFilesystems[volumeIndex], ¶ms);
|
|
if (KSUCCESS(s)) {
|
|
uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock);
|
|
gVolumeMounted[volumeIndex] = true;
|
|
gVolumeBusy[volumeIndex] = false;
|
|
gMountedCount++;
|
|
KeReleaseSpinlock(&gNtfsMountLock, flags);
|
|
mounted++;
|
|
DbgPrint("NTFS: disk %u part %u mounted as drive %u (lba=%llu mft_record=%llu).\n",
|
|
disk, p, volumeIndex,
|
|
(unsigned long long)parts[p].StartLba,
|
|
(unsigned long long)gVolumes[volumeIndex].FileRecordSize);
|
|
} else {
|
|
NtfsCleanupVolume(&gVolumes[volumeIndex]);
|
|
memset(&gVolumes[volumeIndex], 0, sizeof(gVolumes[volumeIndex]));
|
|
memset(&gFilesystems[volumeIndex], 0, sizeof(gFilesystems[volumeIndex]));
|
|
NtfsReleaseReservedVolumeIndex(volumeIndex);
|
|
DbgPrint("NTFS: disk %u part %u mount failed status=0x%x.\n", disk, p, s);
|
|
}
|
|
}
|
|
NtfsEndDiskScan(disk, scanCompleted);
|
|
if (partCount && !mounted && gMountedCount >= NTFS_MAX_VOLUMES)
|
|
DbgPrint("NTFS: disk %u: volume table full\n", disk);
|
|
return mounted;
|
|
}
|
|
|
|
static void NtfsUnmountDisk(uint8_t diskIndex, IO_DEVICE_CHANGE change) {
|
|
uint32_t removed = 0;
|
|
for (uint8_t i = 0; i < NTFS_MAX_VOLUMES; i++) {
|
|
if (!gVolumeMounted[i] || gVolumes[i].DriveIndex != diskIndex) continue;
|
|
(void)IoUnregisterFilesystem(i);
|
|
NtfsCleanupVolume(&gVolumes[i]);
|
|
memset(&gVolumes[i], 0, sizeof(gVolumes[i]));
|
|
memset(&gFilesystems[i], 0, sizeof(gFilesystems[i]));
|
|
uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock);
|
|
gVolumeMounted[i] = false;
|
|
gVolumeBusy[i] = false;
|
|
if (gMountedCount) gMountedCount--;
|
|
KeReleaseSpinlock(&gNtfsMountLock, flags);
|
|
removed++;
|
|
}
|
|
if (diskIndex < NTFS_MAX_VOLUMES) {
|
|
uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock);
|
|
gDiskScanned[diskIndex] = false;
|
|
KeReleaseSpinlock(&gNtfsMountLock, flags);
|
|
}
|
|
if (removed) {
|
|
DbgPrint("NTFS: disk %u %s; unmounted %u volume(s); total=%u\n",
|
|
diskIndex,
|
|
change == IoDeviceSurpriseRemoved ? "surprise removed" : "removed",
|
|
removed,
|
|
gMountedCount);
|
|
}
|
|
}
|
|
|
|
static void NtfsDiskArrival(uint8_t driveIndex, DEVICE_OBJECT* device, void* context) {
|
|
(void)device;
|
|
(void)context;
|
|
uint32_t mounted = NtfsTryMountDisk(driveIndex);
|
|
if (mounted) DbgPrint("NTFS: hotplug scan mounted %u new volume(s); total=%u\n", mounted, gMountedCount);
|
|
}
|
|
|
|
static void NtfsDiskChange(uint8_t driveIndex, DEVICE_OBJECT* device, IO_DEVICE_CHANGE change, void* context) {
|
|
(void)device;
|
|
(void)context;
|
|
switch (change) {
|
|
case IoDeviceArrived:
|
|
NtfsDiskArrival(driveIndex, device, context);
|
|
break;
|
|
case IoDeviceSurpriseRemoved:
|
|
case IoDeviceRemoved:
|
|
NtfsUnmountDisk(driveIndex, change);
|
|
break;
|
|
case IoDeviceQueryRemove:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) {
|
|
DbgPrint("NTFS: Init\n");
|
|
if (!ddk) return NTLSTATUS_INVALID_PARAMETER;
|
|
(void)init;
|
|
memset(gVolumes, 0, sizeof(gVolumes));
|
|
memset(gFilesystems, 0, sizeof(gFilesystems));
|
|
memset(gVolumeMounted, 0, sizeof(gVolumeMounted));
|
|
memset(gVolumeBusy, 0, sizeof(gVolumeBusy));
|
|
memset(gDiskScanned, 0, sizeof(gDiskScanned));
|
|
gMountedCount = 0;
|
|
|
|
KSTATUS cbStatus = IoRegisterDiskChangeCallback(NtfsDiskChange, (void*)0);
|
|
bool callbackRegistered = KSUCCESS(cbStatus);
|
|
if (!callbackRegistered)
|
|
DbgPrint("NTFS: disk-change callback registration failed st=0x%x\n", cbStatus);
|
|
|
|
KSTATUS enumStatus = IoEnumerateDiskDevices(NtfsDiskArrival, (void*)0);
|
|
if (!KSUCCESS(enumStatus))
|
|
DbgPrint("NTFS: initial disk enumeration failed st=0x%x\n", enumStatus);
|
|
|
|
if (!gMountedCount && !callbackRegistered) {
|
|
DbgPrint("NTFS: no mountable NTFS volumes found and disk-change callback unavailable.\n");
|
|
return cbStatus;
|
|
}
|
|
|
|
if (!gMountedCount)
|
|
DbgPrint("NTFS: no mountable NTFS volumes found; driver resident for disk changes.\n");
|
|
|
|
DbgPrint("NTFS: Init complete; mounted %u volume(s).\n", gMountedCount);
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|