mirror of https://gitlab.com/nakst/essence
1272 lines
40 KiB
C++
1272 lines
40 KiB
C++
// This file is part of the Essence operating system.
|
|
// It is released under the terms of the MIT license -- see LICENSE.md.
|
|
// Written by: nakst.
|
|
|
|
// TODO Validation of all fields.
|
|
// TODO Update to the latest file subsystem changes.
|
|
// - Set spaceUsed and spaceTotal.
|
|
|
|
#include <module.h>
|
|
|
|
struct BootSector {
|
|
uint8_t jump[3];
|
|
char signature[8];
|
|
uint16_t bytesPerSector;
|
|
uint8_t sectorsPerCluster;
|
|
uint16_t reservedSectors;
|
|
uint8_t unused0[3];
|
|
uint16_t unused1;
|
|
uint8_t media;
|
|
uint16_t unused2;
|
|
uint16_t sectorsPerTrack;
|
|
uint16_t headsPerCylinder;
|
|
uint32_t hiddenSectors;
|
|
uint32_t unused3;
|
|
uint32_t unused4;
|
|
uint64_t totalSectors;
|
|
uint64_t mftStart;
|
|
uint64_t mftMirrorStart;
|
|
int8_t clustersPerFileRecord;
|
|
uint8_t unused5[3];
|
|
int8_t clustersPerIndexBlock;
|
|
uint8_t unused6[3];
|
|
uint64_t serialNumber;
|
|
uint32_t checksum;
|
|
uint8_t bootloader[426];
|
|
uint16_t bootSignature;
|
|
} ES_STRUCT_PACKED;
|
|
|
|
struct FileRecordHeader {
|
|
uint32_t magic;
|
|
uint16_t updateSequenceOffset;
|
|
uint16_t updateSequenceSize;
|
|
uint64_t logSequence;
|
|
uint16_t sequenceNumber;
|
|
uint16_t hardLinkCount;
|
|
uint16_t firstAttributeOffset;
|
|
uint16_t flags;
|
|
uint32_t usedSize;
|
|
uint32_t allocatedSize;
|
|
uint64_t fileReference;
|
|
uint16_t nextAttributeID;
|
|
uint16_t unused;
|
|
uint32_t recordNumber;
|
|
} ES_STRUCT_PACKED;
|
|
|
|
struct AttributeHeader {
|
|
uint32_t attributeType;
|
|
uint32_t length;
|
|
uint8_t nonResident;
|
|
uint8_t nameLength;
|
|
uint16_t nameOffset;
|
|
uint16_t flags;
|
|
uint16_t attributeID;
|
|
} ES_STRUCT_PACKED;
|
|
|
|
struct ResidentAttributeHeader : AttributeHeader {
|
|
uint32_t attributeLength;
|
|
uint16_t attributeOffset;
|
|
uint8_t indexed;
|
|
uint8_t unused;
|
|
} ES_STRUCT_PACKED;
|
|
|
|
struct FileNameAttributeHeader {
|
|
uint64_t parentRecordNumber : 48;
|
|
uint64_t parentSequenceNumber : 16;
|
|
uint64_t creationTime;
|
|
uint64_t modificationTime;
|
|
uint64_t metadataModificationTime;
|
|
uint64_t readTime;
|
|
uint64_t allocatedSize;
|
|
uint64_t realSize;
|
|
uint32_t flags;
|
|
uint32_t reparse;
|
|
uint8_t fileNameLength;
|
|
uint8_t namespaceType;
|
|
} ES_STRUCT_PACKED;
|
|
|
|
struct NonResidentAttributeHeader : AttributeHeader {
|
|
uint64_t firstCluster;
|
|
uint64_t lastCluster;
|
|
uint16_t dataRunsOffset;
|
|
uint16_t compressionUnit;
|
|
uint32_t unused;
|
|
uint64_t attributeAllocated;
|
|
uint64_t attributeSize;
|
|
uint64_t streamDataSize;
|
|
} ES_STRUCT_PACKED;
|
|
|
|
struct RunHeader {
|
|
uint8_t lengthFieldBytes : 4;
|
|
uint8_t offsetFieldBytes : 4;
|
|
} ES_STRUCT_PACKED;
|
|
|
|
struct IndexBlockHeader {
|
|
uint32_t magic;
|
|
uint16_t updateSequenceOffset;
|
|
uint16_t updateSequenceSize;
|
|
uint64_t logSequence;
|
|
uint64_t selfVirtualCluster;
|
|
uint32_t firstEntryOffset;
|
|
uint32_t totalEntrySize;
|
|
uint32_t allocatedSize;
|
|
uint32_t flags;
|
|
} ES_STRUCT_PACKED;
|
|
|
|
struct IndexRootHeader {
|
|
uint32_t attributeType;
|
|
uint32_t collationRule;
|
|
uint32_t bytesPerIndexRecord;
|
|
uint8_t clustersPerIndexRecord;
|
|
uint8_t unused[3];
|
|
uint32_t firstEntryOffset;
|
|
uint32_t totalEntrySize;
|
|
uint32_t allocatedSize;
|
|
uint32_t flags;
|
|
} ES_STRUCT_PACKED;
|
|
|
|
struct IndexEntryHeader {
|
|
uint64_t fileRecordNumber : 48;
|
|
uint64_t fileSequenceNumber : 16;
|
|
uint16_t indexEntryLength;
|
|
uint16_t streamLength;
|
|
uint32_t flags;
|
|
} ES_STRUCT_PACKED;
|
|
|
|
// TODO Support other MFT file record sizes.
|
|
#define MFT_FILE_SIZE (1024)
|
|
|
|
struct FSNode {
|
|
struct Volume *volume;
|
|
|
|
union {
|
|
uint8_t fileRecord[MFT_FILE_SIZE];
|
|
FileRecordHeader header;
|
|
};
|
|
};
|
|
|
|
struct Volume : KDevice {
|
|
KFileSystem *fileSystem;
|
|
KNode *root;
|
|
BootSector bootSector;
|
|
|
|
size_t clusterBytes,
|
|
mftRecordBytes,
|
|
indexBlockBytes,
|
|
clusterCount,
|
|
sectorBytes;
|
|
|
|
FSNode mftNode, volumeNode;
|
|
|
|
uint16_t *upCaseLookup[256];
|
|
};
|
|
|
|
static uint16_t ToUpper(Volume *volume, uint16_t character) {
|
|
uint16_t *table = volume->upCaseLookup[character >> 8];
|
|
return table ? table[character & 0xFF] : character;
|
|
}
|
|
|
|
static uint64_t GetDataRunLength(RunHeader *dataRun) {
|
|
uint64_t length = 0;
|
|
|
|
for (int i = 0; i < dataRun->lengthFieldBytes; i++) {
|
|
length |= (uint64_t) (((uint8_t *) dataRun)[1 + i]) << (i * 8);
|
|
}
|
|
|
|
return length;
|
|
}
|
|
|
|
static uint64_t GetDataRunOffset(RunHeader *dataRun) {
|
|
uint64_t offset = 0;
|
|
|
|
for (int i = 0; i < dataRun->offsetFieldBytes; i++) {
|
|
offset |= (uint64_t) (((uint8_t *) dataRun)[1 + dataRun->lengthFieldBytes + i]) << (i * 8);
|
|
}
|
|
|
|
if (offset & ((uint64_t) 1 << (dataRun->offsetFieldBytes * 8 - 1))) {
|
|
for (int i = dataRun->offsetFieldBytes; i < 8; i++) {
|
|
offset |= (uint64_t) 0xFF << (i * 8);
|
|
}
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
static bool ApplyFixup(Volume *volume, uint8_t *buffer, uint16_t fixupOffset, uint16_t fixupSize, size_t bufferBytes) {
|
|
#define FIXUP_FAILURE(message) do { KernelLog(LOG_ERROR, "NTFS", "fixup failure", "ApplyFixup - " message); return false; } while (0)
|
|
|
|
// TODO Is the aliasing done by this function optimizations-safe?
|
|
|
|
size_t sectorCount = bufferBytes / volume->sectorBytes;
|
|
|
|
if ((fixupOffset & 1) || (fixupOffset + fixupSize > bufferBytes) || ((size_t) (fixupSize - 1) != sectorCount)) {
|
|
FIXUP_FAILURE("Invalid offset/size.\n");
|
|
}
|
|
|
|
uint16_t *fixupArray = (uint16_t *) (buffer + fixupOffset);
|
|
|
|
for (uintptr_t i = 0; i < sectorCount; i++) {
|
|
uint16_t *position = (uint16_t *) (buffer + (i + 1) * volume->sectorBytes - 2);
|
|
|
|
if (*position != fixupArray[0]) {
|
|
FIXUP_FAILURE("Incorrect sequence number.\n");
|
|
}
|
|
|
|
*position = fixupArray[i + 1];
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ValidateFileRecord(Volume *volume, FSNode *node, bool isDirectory) {
|
|
#define VALIDATE_FILE_FAILURE(message) do { KernelLog(LOG_ERROR, "NTFS", "validate file record failure", "ValidateFileRecord - " message); return false; } while (0)
|
|
|
|
FileRecordHeader *header = &node->header;
|
|
|
|
if (header->magic != 0x454C4946) {
|
|
VALIDATE_FILE_FAILURE("Invalid magic number.\n");
|
|
}
|
|
|
|
if (!ApplyFixup(volume, node->fileRecord, header->updateSequenceOffset, header->updateSequenceSize, MFT_FILE_SIZE)) {
|
|
return false;
|
|
}
|
|
|
|
if (~header->flags & (1 << 0)) {
|
|
VALIDATE_FILE_FAILURE("Record not in use.\n");
|
|
}
|
|
|
|
if (((header->flags & (1 << 1)) ? true : false) != isDirectory) {
|
|
VALIDATE_FILE_FAILURE("Incorrect isDirectory flag.\n");
|
|
}
|
|
|
|
if (header->allocatedSize != MFT_FILE_SIZE || header->usedSize > MFT_FILE_SIZE) {
|
|
VALIDATE_FILE_FAILURE("Invalid file header allocated/used size.\n");
|
|
}
|
|
|
|
if (header->firstAttributeOffset > MFT_FILE_SIZE - sizeof(AttributeHeader)) {
|
|
VALIDATE_FILE_FAILURE("Invalid first attribute offset.\n");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static AttributeHeader *FindAttribute(Volume *, FSNode *node, uint8_t type, AttributeHeader *startingFrom = nullptr, bool optional = false) {
|
|
#define FIND_ATTRIBUTE_FAILURE(message) do { KernelLog(LOG_ERROR, "NTFS", "find attribute failure", "FindAttribute - " message); return nullptr; } while (0)
|
|
|
|
AttributeHeader *attribute = startingFrom ?: (AttributeHeader *) (node->fileRecord + node->header.firstAttributeOffset);
|
|
bool skip = startingFrom ? true : false;
|
|
|
|
while (true) {
|
|
if (attribute->attributeType == type && !skip) {
|
|
return attribute;
|
|
} else if (attribute->attributeType == 0xFFFFFFFF) {
|
|
if (optional) return nullptr;
|
|
FIND_ATTRIBUTE_FAILURE("Could not find attribute.\n");
|
|
}
|
|
|
|
skip = false;
|
|
|
|
if ((size_t) ((uint8_t *) attribute - node->fileRecord + attribute->length) > MFT_FILE_SIZE - sizeof(AttributeHeader)
|
|
|| attribute->length < sizeof(AttributeHeader)) {
|
|
FIND_ATTRIBUTE_FAILURE("Invalid attribute length.\n");
|
|
}
|
|
|
|
attribute = (AttributeHeader *) ((uint8_t *) attribute + attribute->length);
|
|
}
|
|
}
|
|
|
|
static bool ReadFileCluster(Volume *volume, FSNode *node, uint64_t startCluster, void *_buffer) {
|
|
#define READ_FILE_CLUSTERS_FAILURE(message) do { KernelLog(LOG_ERROR, "NTFS", "read file clusters failure", "ReadFileClusters - " message); return false; } while (0)
|
|
|
|
uint8_t *buffer = (uint8_t *) _buffer;
|
|
|
|
// Find the data attribute.
|
|
|
|
NonResidentAttributeHeader *dataHeader = (NonResidentAttributeHeader *) FindAttribute(volume, node, 0x80);
|
|
|
|
if (!dataHeader) {
|
|
// TODO Support $ATTRIBUTE_LIST.
|
|
READ_FILE_CLUSTERS_FAILURE("Could not find data attribute.\n");
|
|
}
|
|
|
|
if (dataHeader->length < sizeof(NonResidentAttributeHeader)) {
|
|
READ_FILE_CLUSTERS_FAILURE("Data attribute is too short.\n");
|
|
}
|
|
|
|
if (!dataHeader->nonResident) {
|
|
// TODO Resident data attribute.
|
|
READ_FILE_CLUSTERS_FAILURE("Data attribute is resident.\n");
|
|
}
|
|
|
|
if (dataHeader->flags) {
|
|
READ_FILE_CLUSTERS_FAILURE("Data attribute has unsupported flags.\n");
|
|
}
|
|
|
|
// Find the data run containing the requested clusters.
|
|
|
|
RunHeader *dataRun = (RunHeader *) ((uint8_t *) dataHeader + dataHeader->dataRunsOffset);
|
|
uint64_t clusterNumber = 0;
|
|
bool foundStart = false;
|
|
uint64_t clusterCount = 1;
|
|
|
|
KWorkGroup dispatchGroup = {};
|
|
dispatchGroup.Initialise();
|
|
|
|
while (((uint8_t *) dataRun - (uint8_t *) dataHeader) < dataHeader->length && dataRun->lengthFieldBytes) {
|
|
uint64_t length = GetDataRunLength(dataRun), offset = GetDataRunOffset(dataRun);
|
|
|
|
clusterNumber += offset;
|
|
dataRun = (RunHeader *) ((uint8_t *) dataRun + 1 + dataRun->lengthFieldBytes + dataRun->offsetFieldBytes);
|
|
|
|
if (!foundStart) {
|
|
if (length <= startCluster) {
|
|
startCluster -= length;
|
|
continue;
|
|
} else {
|
|
foundStart = true;
|
|
}
|
|
}
|
|
|
|
if (foundStart) {
|
|
uint64_t firstClusterToRead = clusterNumber + startCluster;
|
|
uint64_t clustersToRead = 1;
|
|
startCluster = 0;
|
|
|
|
// Read the clusters.
|
|
|
|
volume->fileSystem->Access(firstClusterToRead * volume->clusterBytes,
|
|
clustersToRead * volume->clusterBytes, K_ACCESS_READ,
|
|
buffer, ES_FLAGS_DEFAULT, &dispatchGroup);
|
|
|
|
clusterCount -= clustersToRead;
|
|
buffer += clustersToRead * volume->clusterBytes;
|
|
|
|
if (!clusterCount) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (clusterCount) {
|
|
READ_FILE_CLUSTERS_FAILURE("Trying to read past the end of the data runs.\n");
|
|
}
|
|
|
|
// Wait for outstanding IO operations to complete.
|
|
|
|
bool success = dispatchGroup.Wait();
|
|
|
|
if (!success) {
|
|
READ_FILE_CLUSTERS_FAILURE("Could not read clusters from block device.\n");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ReadFileRecord(KNode *kNode, FSNode *node, uint64_t index, EsNodeType nodeType, void *clusterBuffer) {
|
|
#define READ_FILE_RECORD_FAILURE(message) do { KernelLog(LOG_ERROR, "NTFS", "read file record failure", "ReadFileRecord - " message); return false; } while (0)
|
|
|
|
Volume *volume = node->volume;
|
|
|
|
// Work out the cluster containing the file record.
|
|
|
|
uint64_t cluster = index * MFT_FILE_SIZE / volume->clusterBytes;
|
|
uint64_t offsetInCluster = index * MFT_FILE_SIZE % volume->clusterBytes;
|
|
|
|
// Read this cluster.
|
|
|
|
if (!ReadFileCluster(volume, &volume->mftNode, cluster, clusterBuffer)) {
|
|
READ_FILE_RECORD_FAILURE("Could not read the file record from the MFT.\n");
|
|
}
|
|
|
|
// Copy into the node.
|
|
|
|
EsMemoryCopy(node->fileRecord, (uint8_t *) clusterBuffer + offsetInCluster, MFT_FILE_SIZE);
|
|
|
|
if (!ValidateFileRecord(volume, node, nodeType == ES_NODE_DIRECTORY)) {
|
|
return false;
|
|
}
|
|
|
|
// Store information about the node.
|
|
|
|
if (!kNode) {
|
|
return true;
|
|
}
|
|
|
|
if (nodeType == ES_NODE_FILE) {
|
|
AttributeHeader *dataHeader = (AttributeHeader *) FindAttribute(volume, node, 0x80);
|
|
|
|
if (!dataHeader) {
|
|
READ_FILE_RECORD_FAILURE("File did not have a data attribute.\n");
|
|
}
|
|
|
|
if (dataHeader->nonResident) {
|
|
kNode->data.fileSize = ((NonResidentAttributeHeader *) dataHeader)->attributeSize;
|
|
} else {
|
|
kNode->data.fileSize = ((ResidentAttributeHeader *) dataHeader)->attributeLength;
|
|
}
|
|
|
|
kNode->data.type = ES_NODE_FILE;
|
|
} else if (nodeType == ES_NODE_DIRECTORY) {
|
|
NonResidentAttributeHeader *indexAllocationHeader = (NonResidentAttributeHeader *) FindAttribute(volume, node, 0xA0, nullptr, true);
|
|
|
|
if (!indexAllocationHeader || indexAllocationHeader->flags || !indexAllocationHeader->nonResident) {
|
|
kNode->data.directoryChildren = MFT_FILE_SIZE / 16;
|
|
} else {
|
|
// TODO This is a terrible upper estimate! Find a better one?
|
|
kNode->data.directoryChildren = indexAllocationHeader->attributeSize / 16;
|
|
}
|
|
|
|
kNode->data.type = ES_NODE_DIRECTORY;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool Mount(Volume *volume) {
|
|
#define MOUNT_FAILURE(message) do { KernelLog(LOG_ERROR, "NTFS", "mount failure", "Mount - " message); return false; } while (0)
|
|
|
|
// Load the boot sector.
|
|
|
|
{
|
|
void *buffer = EsHeapAllocate(volume->fileSystem->block->sectorSize, false, K_FIXED);
|
|
|
|
if (!buffer) {
|
|
MOUNT_FAILURE("Could not allocate buffer.\n");
|
|
}
|
|
|
|
EsDefer(EsHeapFree(buffer, volume->fileSystem->block->sectorSize, K_FIXED));
|
|
|
|
if (!volume->fileSystem->Access(0, volume->fileSystem->block->sectorSize, K_ACCESS_READ, buffer, ES_FLAGS_DEFAULT)) {
|
|
MOUNT_FAILURE("Could not read boot sector.\n");
|
|
}
|
|
|
|
EsMemoryCopy(&volume->bootSector, buffer, sizeof(BootSector));
|
|
|
|
volume->sectorBytes = volume->bootSector.bytesPerSector;
|
|
volume->clusterBytes = volume->bootSector.sectorsPerCluster * volume->bootSector.bytesPerSector;
|
|
volume->mftRecordBytes = volume->bootSector.clustersPerFileRecord >= 0
|
|
? volume->bootSector.clustersPerFileRecord * volume->clusterBytes : (1 << -volume->bootSector.clustersPerFileRecord);
|
|
volume->indexBlockBytes = volume->bootSector.clustersPerIndexBlock >= 0
|
|
? volume->bootSector.clustersPerIndexBlock * volume->clusterBytes : (1 << -volume->bootSector.clustersPerIndexBlock);
|
|
volume->clusterCount = volume->fileSystem->block->sectorCount / volume->bootSector.sectorsPerCluster;
|
|
|
|
if (volume->bootSector.bytesPerSector != volume->fileSystem->block->sectorSize) MOUNT_FAILURE("Invalid bytes per sector.\n");
|
|
if ((volume->bootSector.sectorsPerCluster - 1) & volume->bootSector.sectorsPerCluster) MOUNT_FAILURE("Invalid sectors per cluster.\n");
|
|
if (volume->mftRecordBytes != MFT_FILE_SIZE) MOUNT_FAILURE("Unsupported MFT record size.\n");
|
|
if (volume->mftRecordBytes > volume->clusterBytes) MOUNT_FAILURE("MFT record size bigger than cluster size.\n");
|
|
if (volume->indexBlockBytes % volume->clusterBytes) MOUNT_FAILURE("Index block size is not a multiple of the cluster size.\n");
|
|
if (volume->bootSector.mftStart >= volume->clusterCount) MOUNT_FAILURE("MFT starts past end of volume.\n");
|
|
}
|
|
|
|
void *buffer = EsHeapAllocate(volume->clusterBytes, false, K_FIXED);
|
|
|
|
if (!buffer) {
|
|
MOUNT_FAILURE("Could not allocate buffer.\n");
|
|
}
|
|
|
|
EsDefer(EsHeapFree(buffer, volume->clusterBytes, K_FIXED));
|
|
|
|
// Load the MFT.
|
|
|
|
{
|
|
if (!volume->fileSystem->Access(volume->bootSector.mftStart * volume->clusterBytes,
|
|
volume->clusterBytes, K_ACCESS_READ, buffer, ES_FLAGS_DEFAULT)) {
|
|
MOUNT_FAILURE("Could not read MFT file.\n");
|
|
}
|
|
|
|
EsMemoryCopy(volume->mftNode.fileRecord, buffer, MFT_FILE_SIZE);
|
|
|
|
if (!ValidateFileRecord(volume, &volume->mftNode, false)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Load the volume node.
|
|
|
|
{
|
|
volume->volumeNode.volume = volume;
|
|
|
|
if (!ReadFileRecord(nullptr, &volume->volumeNode, 3, ES_NODE_FILE, buffer)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Load the root directory.
|
|
|
|
{
|
|
volume->root = FSNodeAllocate();
|
|
|
|
if (!volume->root) {
|
|
MOUNT_FAILURE("Could not allocate root node.\n");
|
|
}
|
|
|
|
volume->root->driverNode = EsHeapAllocate(sizeof(FSNode), true, K_FIXED);
|
|
|
|
if (!volume->root->driverNode) {
|
|
MOUNT_FAILURE("Could not allocate root node.\n");
|
|
}
|
|
|
|
FSNode *root = (FSNode *) volume->root->driverNode;
|
|
root->volume = volume;
|
|
|
|
if (!ReadFileRecord(volume->root, root, 5, ES_NODE_DIRECTORY, buffer)) {
|
|
return false;
|
|
}
|
|
|
|
if (!FSRegisterNewNode(volume->root, nullptr, ES_NODE_DIRECTORY, EsLiteral("$Root"))) {
|
|
MOUNT_FAILURE("Could not register root directory node.\n");
|
|
}
|
|
}
|
|
|
|
// Load $UpCase.
|
|
|
|
{
|
|
FSNode *upCase = (FSNode *) EsHeapAllocate(sizeof(FSNode), true, K_FIXED);
|
|
|
|
if (!upCase) {
|
|
MOUNT_FAILURE("Could not allocate upCase node.\n");
|
|
}
|
|
|
|
EsDefer(EsHeapFree(upCase, sizeof(FSNode), K_FIXED));
|
|
|
|
upCase->volume = volume;
|
|
|
|
if (!ReadFileRecord(nullptr, upCase, 10, ES_NODE_FILE, buffer)) {
|
|
return false;
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < 131072 / volume->clusterBytes; i++) {
|
|
if (!ReadFileCluster(volume, upCase, i, buffer)) {
|
|
return false;
|
|
}
|
|
|
|
for (uint16_t j = 0, codepoint = i * volume->clusterBytes / 2; j < volume->clusterBytes / 2; j++, codepoint++) {
|
|
uint16_t upperCase = ((uint16_t *) buffer)[j];
|
|
if (codepoint == upperCase) continue;
|
|
|
|
uint8_t msb = codepoint >> 8, lsb = codepoint & 0xFF;
|
|
|
|
if (!volume->upCaseLookup[msb]) {
|
|
volume->upCaseLookup[msb] = (uint16_t *) EsHeapAllocate(512, false, K_FIXED);
|
|
|
|
if (!volume->upCaseLookup[msb]) {
|
|
MOUNT_FAILURE("Could not allocate upCase table.\n");
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < 256; i++) {
|
|
volume->upCaseLookup[msb][i] = (msb << 8) | i;
|
|
}
|
|
}
|
|
|
|
volume->upCaseLookup[msb][lsb] = upperCase;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#define BAD_ATTRIBUTE_FAILURE(message) do { KernelLog(LOG_ERROR, "NTFS", "bad attribute failure", "GetIndexAllocationHeader - " message); return nullptr; } while (0)
|
|
|
|
static NonResidentAttributeHeader *GetIndexAllocationHeader(Volume *volume, FSNode *directory) {
|
|
NonResidentAttributeHeader *indexAllocationHeader = (NonResidentAttributeHeader *) FindAttribute(volume, directory, 0xA0);
|
|
|
|
if (!indexAllocationHeader) {
|
|
// TODO Support $ATTRIBUTE_LIST.
|
|
BAD_ATTRIBUTE_FAILURE("Could not find index allocation attribute.\n");
|
|
}
|
|
|
|
if (indexAllocationHeader->length < sizeof(NonResidentAttributeHeader)) {
|
|
BAD_ATTRIBUTE_FAILURE("Index allocation attribute is too short.\n");
|
|
}
|
|
|
|
if (!indexAllocationHeader->nonResident) {
|
|
BAD_ATTRIBUTE_FAILURE("Index allocation attribute is resident.\n");
|
|
}
|
|
|
|
if (indexAllocationHeader->flags) {
|
|
BAD_ATTRIBUTE_FAILURE("Index allocation attribute has unsupported flags.\n");
|
|
}
|
|
|
|
return indexAllocationHeader;
|
|
}
|
|
|
|
#define ENUMERATE_FAILURE(message) do { KernelLog(LOG_ERROR, "NTFS", "enumerate failure", "Enumerate - " message); return ES_ERROR_UNKNOWN; } while (0)
|
|
|
|
static EsError EnumerateIndexEntry(IndexEntryHeader *entry, uint8_t *bufferLimit, KNode *directory) {
|
|
if (entry->indexEntryLength < sizeof(IndexEntryHeader)) {
|
|
ENUMERATE_FAILURE("Invalid index entry length.\n");
|
|
}
|
|
|
|
if (entry->flags & (1 << 1)) {
|
|
return ES_SUCCESS;
|
|
}
|
|
|
|
FileNameAttributeHeader *fileNameAttribute = (FileNameAttributeHeader *) (entry + 1);
|
|
|
|
if ((uint8_t *) fileNameAttribute + fileNameAttribute->fileNameLength * 2 > bufferLimit) {
|
|
ENUMERATE_FAILURE("File name attribute has length too large.\n");
|
|
}
|
|
|
|
if ((fileNameAttribute->namespaceType == 1 || fileNameAttribute->namespaceType == 3) && entry->fileRecordNumber >= 24) {
|
|
KNodeMetadata metadata = {};
|
|
|
|
metadata.type = (fileNameAttribute->flags & 0x10000000) ? ES_NODE_DIRECTORY : ES_NODE_FILE;
|
|
|
|
if (metadata.type == ES_NODE_FILE) {
|
|
metadata.totalSize = fileNameAttribute->realSize;
|
|
} else {
|
|
metadata.directoryChildren = ES_DIRECTORY_CHILDREN_UNKNOWN;
|
|
}
|
|
|
|
uintptr_t namePosition = 0;
|
|
uint16_t *fileName = (uint16_t *) (fileNameAttribute + 1);
|
|
size_t inputCharactersRemaining = fileNameAttribute->fileNameLength;
|
|
char childName[ES_MAX_DIRECTORY_CHILD_NAME_LENGTH];
|
|
size_t childNameBytes = 0;
|
|
|
|
while (inputCharactersRemaining) {
|
|
uint32_t c = *fileName;
|
|
inputCharactersRemaining--;
|
|
fileName++;
|
|
|
|
if (c >= 0xD800 && c < 0xDC00 && inputCharactersRemaining) {
|
|
uint32_t c2 = *fileName;
|
|
|
|
if (c2 >= 0xDC00 && c2 < 0xE000) {
|
|
inputCharactersRemaining--;
|
|
fileName++;
|
|
|
|
c = ((c - 0xD800) << 10) + (c2 - 0xDC00) + 0x10000;
|
|
}
|
|
}
|
|
|
|
size_t encodedBytes = utf8_encode(c, nullptr);
|
|
|
|
if (namePosition + encodedBytes <= ES_MAX_DIRECTORY_CHILD_NAME_LENGTH) {
|
|
utf8_encode(c, childName + namePosition);
|
|
namePosition += encodedBytes;
|
|
childNameBytes += encodedBytes;
|
|
} else {
|
|
// Name too long.
|
|
// TODO Now what?
|
|
}
|
|
}
|
|
|
|
EsError error = FSDirectoryEntryFound(directory, &metadata,
|
|
&reference, sizeof(DirectoryEntryReference),
|
|
childName, childNameBytes, false);
|
|
|
|
if (error != ES_SUCCESS) {
|
|
return error;
|
|
}
|
|
}
|
|
|
|
return ES_SUCCESS;
|
|
}
|
|
|
|
static EsError EnumerateIndexNode(Volume *volume, uint8_t *buffer, uint64_t clustersRead, KNode *directory) {
|
|
for (uintptr_t indexBlock = 0; indexBlock < volume->clusterBytes * clustersRead / volume->indexBlockBytes; indexBlock++) {
|
|
uint8_t *blockStart = buffer + indexBlock * volume->indexBlockBytes;
|
|
|
|
IndexBlockHeader *header = (IndexBlockHeader *) blockStart;
|
|
|
|
if (header->magic != 0x58444E49) {
|
|
ENUMERATE_FAILURE("Index block has incorrect magic number.\n");
|
|
}
|
|
|
|
if (!ApplyFixup(volume, blockStart, header->updateSequenceOffset, header->updateSequenceSize, volume->indexBlockBytes)) {
|
|
return ES_ERROR_CORRUPT_DATA;
|
|
}
|
|
|
|
if (header->firstEntryOffset > volume->indexBlockBytes || header->firstEntryOffset + header->totalEntrySize > volume->indexBlockBytes) {
|
|
ENUMERATE_FAILURE("Index block has incorrect entries offset/size.\n");
|
|
}
|
|
|
|
uint8_t *entriesPosition = blockStart + header->firstEntryOffset + 0x18;
|
|
uint8_t *entriesEnd = entriesPosition + header->totalEntrySize;
|
|
|
|
while (entriesPosition + sizeof(IndexEntryHeader) <= entriesEnd) {
|
|
IndexEntryHeader *entry = (IndexEntryHeader *) entriesPosition;
|
|
EsError error = EnumerateIndexEntry(entry, blockStart + header->firstEntryOffset + header->totalEntrySize, directory);
|
|
if (error != ES_SUCCESS) return error;
|
|
if (entry->flags & (1 << 1)) break;
|
|
entriesPosition += entry->indexEntryLength;
|
|
}
|
|
}
|
|
|
|
return ES_SUCCESS;
|
|
}
|
|
|
|
static EsError Enumerate(KNode *node) {
|
|
FSNode *directory = (FSNode *) node->driverNode;
|
|
Volume *volume = directory->volume;
|
|
|
|
size_t bufferSize = volume->indexBlockBytes > volume->clusterBytes ? volume->indexBlockBytes : volume->clusterBytes;
|
|
size_t clustersPerBuffer = bufferSize / volume->clusterBytes;
|
|
|
|
uint8_t *buffer = (uint8_t *) EsHeapAllocate(bufferSize, false, K_FIXED);
|
|
|
|
if (!buffer) {
|
|
ENUMERATE_FAILURE("Could not allocate buffer.\n");
|
|
}
|
|
|
|
EsDefer(EsHeapFree(buffer, bufferSize, K_FIXED));
|
|
|
|
// Parse entries in the index root.
|
|
|
|
ResidentAttributeHeader *indexRootHeader = (ResidentAttributeHeader *) FindAttribute(volume, directory, 0x90);
|
|
|
|
if (!indexRootHeader || indexRootHeader->nonResident || indexRootHeader->attributeLength < sizeof(IndexRootHeader)
|
|
|| indexRootHeader->attributeOffset > MFT_FILE_SIZE - ((uint8_t *) indexRootHeader - directory->fileRecord)
|
|
|| indexRootHeader->attributeOffset + indexRootHeader->attributeLength > MFT_FILE_SIZE - ((uint8_t *) indexRootHeader - directory->fileRecord)) {
|
|
ENUMERATE_FAILURE("Invalid index root attribute.\n");
|
|
}
|
|
|
|
{
|
|
IndexRootHeader *header = (IndexRootHeader *) ((uint8_t *) indexRootHeader + indexRootHeader->attributeOffset);
|
|
|
|
if (header->firstEntryOffset > indexRootHeader->attributeLength
|
|
|| header->totalEntrySize > indexRootHeader->attributeLength - header->firstEntryOffset) {
|
|
ENUMERATE_FAILURE("Index root has incorrect entries offset/size.\n");
|
|
}
|
|
|
|
uint8_t *entriesPosition = (uint8_t *) header + header->firstEntryOffset + 0x10;
|
|
uint8_t *entriesEnd = entriesPosition + header->totalEntrySize;
|
|
|
|
while (entriesPosition + sizeof(IndexEntryHeader) <= entriesEnd) {
|
|
IndexEntryHeader *entry = (IndexEntryHeader *) entriesPosition;
|
|
EnumerateIndexEntry(entry, entriesEnd, node);
|
|
if (entry->flags & (1 << 1)) break;
|
|
entriesPosition += entry->indexEntryLength;
|
|
}
|
|
}
|
|
|
|
if (!FindAttribute(volume, directory, 0xA0, nullptr, true)) {
|
|
return true;
|
|
}
|
|
|
|
// Get the index allocation attribute.
|
|
|
|
NonResidentAttributeHeader *indexAllocationHeader = GetIndexAllocationHeader(volume, directory);
|
|
|
|
if (!indexAllocationHeader) {
|
|
return false;
|
|
}
|
|
|
|
// For every data run...
|
|
|
|
RunHeader *dataRun = (RunHeader *) ((uint8_t *) indexAllocationHeader + indexAllocationHeader->dataRunsOffset);
|
|
uint64_t clusterNumber = 0;
|
|
uintptr_t clustersInBuffer = 0;
|
|
|
|
while (((uint8_t *) dataRun - (uint8_t *) indexAllocationHeader) < indexAllocationHeader->length && dataRun->lengthFieldBytes) {
|
|
uint64_t length = GetDataRunLength(dataRun), offset = GetDataRunOffset(dataRun);
|
|
|
|
clusterNumber += offset;
|
|
dataRun = (RunHeader *) ((uint8_t *) dataRun + 1 + dataRun->lengthFieldBytes + dataRun->offsetFieldBytes);
|
|
|
|
uint64_t positionInRun = 0;
|
|
|
|
while (positionInRun != length) {
|
|
size_t clustersToRead = (clustersPerBuffer - clustersInBuffer > length - positionInRun)
|
|
? (length - positionInRun) : (clustersPerBuffer - clustersInBuffer);
|
|
|
|
volume->fileSystem->Access((clusterNumber + positionInRun) * volume->clusterBytes,
|
|
clustersToRead * volume->clusterBytes, K_ACCESS_READ,
|
|
buffer + clustersInBuffer * volume->clusterBytes, ES_FLAGS_DEFAULT);
|
|
|
|
clustersInBuffer += clustersToRead;
|
|
positionInRun += clustersToRead;
|
|
|
|
if (clustersInBuffer == clustersPerBuffer) {
|
|
EsError error = EnumerateIndexNode(volume, buffer, clustersPerBuffer, node);
|
|
if (error != ES_SUCCESS) return error;
|
|
clustersInBuffer = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ES_SUCCESS;
|
|
}
|
|
|
|
static KNode *Scan(const char *_name, size_t _nameBytes, KNode *_directory) {
|
|
#define SCAN_FAILURE(message) do { KernelLog(LOG_ERROR, "NTFS", "scan failure", "Scan - " message); return nullptr; } while (0)
|
|
|
|
uint16_t *searchName = (uint16_t *) EsHeapAllocate(256 * sizeof(uint16_t), false, K_FIXED);
|
|
size_t searchNameCharacters = 0;
|
|
|
|
if (!searchName) {
|
|
SCAN_FAILURE("Could not allocate buffer for search name.\n");
|
|
}
|
|
|
|
EsDefer(EsHeapFree(searchName, 256 * sizeof(uint16_t), K_FIXED));
|
|
|
|
const char *_name2 = _name;
|
|
size_t _nameBytes2 = _nameBytes;
|
|
|
|
while (_nameBytes) {
|
|
size_t lengthBytes = utf8_length_char(_name);
|
|
if (_nameBytes < lengthBytes) {
|
|
SCAN_FAILURE("Invalid search name.\n");
|
|
}
|
|
|
|
uint32_t value = utf8_value(_name);
|
|
|
|
if (value <= 0xFFFF) {
|
|
if (searchNameCharacters >= 255) return nullptr;
|
|
searchName[searchNameCharacters++] = value;
|
|
} else {
|
|
if (searchNameCharacters >= 254) return nullptr;
|
|
value -= 0x10000;
|
|
searchName[searchNameCharacters++] = 0xD800 | ((value >> 10) & 0x3FF);
|
|
searchName[searchNameCharacters++] = 0xDC00 | ((value >> 0) & 0x3FF);
|
|
}
|
|
|
|
_name += lengthBytes;
|
|
_nameBytes -= lengthBytes;
|
|
}
|
|
|
|
FSNode *directory = (FSNode *) _directory->driverNode;
|
|
Volume *volume = directory->volume;
|
|
|
|
uint8_t *buffer = (uint8_t *) EsHeapAllocate(volume->indexBlockBytes, false, K_FIXED);
|
|
|
|
if (!buffer) {
|
|
SCAN_FAILURE("Could not allocate buffer for index block.\n");
|
|
}
|
|
|
|
EsDefer(EsHeapFree(buffer, volume->indexBlockBytes, K_FIXED));
|
|
|
|
bool hasIndexAllocation = FindAttribute(volume, directory, 0xA0, nullptr, true) ? true : false;
|
|
NonResidentAttributeHeader *indexAllocationHeader = hasIndexAllocation ? GetIndexAllocationHeader(volume, directory) : nullptr;
|
|
ResidentAttributeHeader *indexRootHeader = (ResidentAttributeHeader *) FindAttribute(volume, directory, 0x90);
|
|
|
|
if ((!indexAllocationHeader && hasIndexAllocation) || !indexRootHeader) {
|
|
SCAN_FAILURE("Could not find necessary attributes.\n");
|
|
}
|
|
|
|
if (indexRootHeader->nonResident || indexRootHeader->attributeLength < sizeof(IndexRootHeader)
|
|
|| indexRootHeader->attributeOffset > MFT_FILE_SIZE - ((uint8_t *) indexRootHeader - directory->fileRecord)
|
|
|| indexRootHeader->attributeOffset + indexRootHeader->attributeLength > MFT_FILE_SIZE - ((uint8_t *) indexRootHeader - directory->fileRecord)) {
|
|
SCAN_FAILURE("Invalid index root attribute.\n");
|
|
}
|
|
|
|
IndexRootHeader *indexRoot = (IndexRootHeader *) ((uint8_t *) indexRootHeader + indexRootHeader->attributeOffset);
|
|
|
|
if (indexRoot->firstEntryOffset > indexRootHeader->attributeLength
|
|
|| indexRoot->firstEntryOffset + indexRoot->totalEntrySize > indexRootHeader->attributeLength
|
|
|| indexRoot->totalEntrySize < sizeof(IndexEntryHeader)
|
|
|| indexRoot->attributeType != 0x30 /* filenames */
|
|
|| indexRoot->collationRule != 1 /* Unicode strings */) {
|
|
SCAN_FAILURE("Invalid index root attribute (2).\n");
|
|
}
|
|
|
|
uint8_t *entriesPosition = (uint8_t *) indexRoot + indexRoot->firstEntryOffset + 0x10;
|
|
uint8_t *entriesEnd = entriesPosition + indexRoot->totalEntrySize;
|
|
bool hasSubNodes = indexRoot->flags & (1 << 0);
|
|
int depth = 0;
|
|
|
|
while (entriesPosition + sizeof(IndexEntryHeader) <= entriesEnd) {
|
|
IndexEntryHeader *entry = (IndexEntryHeader *) entriesPosition;
|
|
|
|
if (entry->streamLength > entry->indexEntryLength
|
|
|| entriesPosition + entry->indexEntryLength > entriesEnd) {
|
|
SCAN_FAILURE("Invalid index entry.\n");
|
|
}
|
|
|
|
bool descend = false;
|
|
|
|
if (entry->flags & (1 << 1)) {
|
|
// Last entry in the node.
|
|
descend = true;
|
|
} else {
|
|
if (entry->streamLength < sizeof(FileNameAttributeHeader)) {
|
|
SCAN_FAILURE("Index entry stream too short.\n");
|
|
}
|
|
|
|
FileNameAttributeHeader *fileNameHeader = (FileNameAttributeHeader *) (entry + 1);
|
|
|
|
if (fileNameHeader->fileNameLength * 2 > entry->streamLength - sizeof(FileNameAttributeHeader)) {
|
|
SCAN_FAILURE("Index entry stream too short (2).\n");
|
|
}
|
|
|
|
if (fileNameHeader->namespaceType == 1 || fileNameHeader->namespaceType == 3) {
|
|
uint16_t *fileName = (uint16_t *) (fileNameHeader + 1);
|
|
size_t fileNameCharacters = fileNameHeader->fileNameLength;
|
|
|
|
uintptr_t index = 0;
|
|
bool match = true;
|
|
|
|
while (index != fileNameCharacters || index != searchNameCharacters) {
|
|
if (index == fileNameCharacters) {
|
|
match = false;
|
|
break;
|
|
} else if (index == searchNameCharacters) {
|
|
descend = true;
|
|
match = false;
|
|
break;
|
|
}
|
|
|
|
uint16_t c1 = ToUpper(volume, fileName[index]), c2 = ToUpper(volume, searchName[index]);
|
|
|
|
if (c1 != c2) {
|
|
if (c1 > c2) {
|
|
descend = true;
|
|
}
|
|
|
|
match = false;
|
|
break;
|
|
}
|
|
|
|
index++;
|
|
}
|
|
|
|
if (match) {
|
|
// Found a match; open the node.
|
|
|
|
KNode *child = FSNodeAllocate();
|
|
|
|
if (!child) {
|
|
SCAN_FAILURE("Could not allocate node.\n");
|
|
}
|
|
|
|
child->driverNode = EsHeapAllocate(sizeof(FSNode), true, K_FIXED);
|
|
|
|
if (!child->driverNode) {
|
|
EsHeapFree(child, 0, K_FIXED);
|
|
SCAN_FAILURE("Could not allocate node.\n");
|
|
}
|
|
|
|
FSNode *node = (FSNode *) child->driverNode;
|
|
node->volume = volume;
|
|
|
|
if (!ReadFileRecord(child, node, entry->fileRecordNumber,
|
|
(fileNameHeader->flags & 0x10000000) ? ES_NODE_DIRECTORY : ES_NODE_FILE,
|
|
buffer)) {
|
|
EsHeapFree(node, sizeof(FSNode), K_FIXED);
|
|
EsHeapFree(child, 0, K_FIXED);
|
|
return nullptr;
|
|
}
|
|
|
|
if (!FSRegisterNewNode(child, _directory, child->data.type, _name2, _nameBytes2)) { // TODO Case-insensitivity support in the VFS.
|
|
EsHeapFree(node, sizeof(FSNode), K_FIXED);
|
|
EsHeapFree(child, 0, K_FIXED);
|
|
SCAN_FAILURE("Could not register root directory node.\n");
|
|
}
|
|
|
|
return child;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (descend) {
|
|
if (!hasIndexAllocation) {
|
|
SCAN_FAILURE("Cannot descend in index without allocation attribute.\n");
|
|
}
|
|
|
|
if (!hasSubNodes || (~entry->flags & (1 << 0))) {
|
|
// Could not find the entry.
|
|
return nullptr;
|
|
}
|
|
|
|
uint64_t nextVCN = *(uint64_t *) (entriesPosition + entry->indexEntryLength - sizeof(uint64_t));
|
|
|
|
// Descend to the next index level.
|
|
|
|
if (++depth > 16) {
|
|
SCAN_FAILURE("Directory filename index too deep.\n");
|
|
}
|
|
|
|
RunHeader *dataRun = (RunHeader *) ((uint8_t *) indexAllocationHeader + indexAllocationHeader->dataRunsOffset);
|
|
|
|
// Load the next index block.
|
|
|
|
uint64_t clusterNumber = 0;
|
|
uint64_t positionInAllocation = 0;
|
|
bool foundStart = false;
|
|
|
|
uintptr_t positionInBuffer = 0;
|
|
size_t clustersToRead = volume->indexBlockBytes / volume->clusterBytes; // TODO Assumes a cluster fits in an index block.
|
|
|
|
while (((uint8_t *) dataRun - (uint8_t *) indexAllocationHeader) < indexAllocationHeader->length && dataRun->lengthFieldBytes) {
|
|
uint64_t length = GetDataRunLength(dataRun), offset = GetDataRunOffset(dataRun);
|
|
|
|
clusterNumber += offset;
|
|
|
|
uintptr_t clusterIndex = clusterNumber;
|
|
|
|
if (nextVCN >= positionInAllocation && nextVCN < positionInAllocation + length) {
|
|
foundStart = true;
|
|
clusterIndex += nextVCN - positionInAllocation;
|
|
length -= nextVCN - positionInAllocation;
|
|
}
|
|
|
|
if (foundStart) {
|
|
size_t read = length > (clustersToRead - positionInBuffer) ? (clustersToRead - positionInBuffer) : length;
|
|
|
|
if (!volume->fileSystem->Access(clusterIndex * volume->clusterBytes,
|
|
read * volume->clusterBytes, K_ACCESS_READ,
|
|
buffer + positionInBuffer * volume->clusterBytes, ES_FLAGS_DEFAULT)) {
|
|
SCAN_FAILURE("Could not read the clusters containing VCN to descend to.\n");
|
|
}
|
|
|
|
positionInBuffer += read;
|
|
|
|
if (positionInBuffer == clustersToRead) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
positionInAllocation += length;
|
|
dataRun = (RunHeader *) ((uint8_t *) dataRun + 1 + dataRun->lengthFieldBytes + dataRun->offsetFieldBytes);
|
|
}
|
|
|
|
if (positionInBuffer != clustersToRead) {
|
|
SCAN_FAILURE("Could not find all the cluster containing VCN to descend to.\n");
|
|
}
|
|
|
|
IndexBlockHeader *header = (IndexBlockHeader *) buffer;
|
|
|
|
if (header->magic != 0x58444E49) {
|
|
SCAN_FAILURE("Index block has incorrect magic number.\n");
|
|
}
|
|
|
|
if (!ApplyFixup(volume, buffer, header->updateSequenceOffset, header->updateSequenceSize, volume->indexBlockBytes)) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (header->firstEntryOffset > volume->indexBlockBytes
|
|
|| header->totalEntrySize > volume->indexBlockBytes - header->firstEntryOffset) {
|
|
SCAN_FAILURE("Index block has incorrect entries offset/size.\n");
|
|
}
|
|
|
|
entriesPosition = (uint8_t *) header + header->firstEntryOffset + 0x18;
|
|
entriesEnd = entriesPosition + header->totalEntrySize;
|
|
hasSubNodes = header->flags & (1 << 0);
|
|
} else {
|
|
entriesPosition += entry->indexEntryLength;
|
|
}
|
|
}
|
|
|
|
SCAN_FAILURE("The last entry in an index node did not have the last entry flag set.\n");
|
|
}
|
|
|
|
static size_t Read(KNode *node, void *_buffer, EsFileOffset offset, EsFileOffset count) {
|
|
#define READ_FAILURE(message) do { KernelLog(LOG_ERROR, "NTFS", "read failure", "Read - " message); return ES_ERROR_UNKNOWN; } while (0)
|
|
|
|
FSNode *file = (FSNode *) node->driverNode;
|
|
Volume *volume = file->volume;
|
|
|
|
uint8_t *clusterBuffer = (uint8_t *) EsHeapAllocate(volume->clusterBytes * 2, false, K_FIXED);
|
|
|
|
if (!clusterBuffer) {
|
|
READ_FAILURE("Could not allocate cluster buffer.\n");
|
|
}
|
|
|
|
EsDefer(EsHeapFree(clusterBuffer, volume->clusterBytes * 2, K_FIXED));
|
|
|
|
uint8_t *clusterBuffer2 = clusterBuffer + volume->clusterBytes;
|
|
uint8_t *outputBuffer = (uint8_t *) _buffer;
|
|
|
|
// Find the data attribute.
|
|
|
|
AttributeHeader *_dataHeader = (AttributeHeader *) FindAttribute(volume, file, 0x80);
|
|
|
|
if (!_dataHeader) {
|
|
// TODO Support $ATTRIBUTE_LIST.
|
|
READ_FAILURE("Could not find data attribute.\n");
|
|
}
|
|
|
|
if (!_dataHeader->nonResident) {
|
|
// Resident data attribute.
|
|
|
|
if (_dataHeader->length < sizeof(ResidentAttributeHeader)) {
|
|
READ_FAILURE("Data attribute is too short.\n");
|
|
}
|
|
|
|
ResidentAttributeHeader *residentDataHeader = (ResidentAttributeHeader *) _dataHeader;
|
|
|
|
if (residentDataHeader->attributeOffset > MFT_FILE_SIZE - ((uint8_t *) residentDataHeader - file->fileRecord)
|
|
|| residentDataHeader->attributeLength > MFT_FILE_SIZE - ((uint8_t *) residentDataHeader - file->fileRecord)
|
|
- residentDataHeader->attributeOffset) {
|
|
READ_FAILURE("Data attribute offset/length invalid.\n");
|
|
}
|
|
|
|
// Copy data into the buffer.
|
|
|
|
uint8_t *residentData = (uint8_t *) residentDataHeader + residentDataHeader->attributeOffset;
|
|
|
|
if (offset > residentDataHeader->attributeLength || count > residentDataHeader->attributeLength - offset) {
|
|
READ_FAILURE("Data attribute offset/length invalid (2).\n");
|
|
}
|
|
|
|
EsMemoryCopy(outputBuffer, residentData + offset, count);
|
|
return true;
|
|
}
|
|
|
|
NonResidentAttributeHeader *dataHeader = (NonResidentAttributeHeader *) _dataHeader;
|
|
|
|
if (dataHeader->length < sizeof(NonResidentAttributeHeader)) {
|
|
READ_FAILURE("Data attribute is too short.\n");
|
|
}
|
|
|
|
if (dataHeader->flags) {
|
|
READ_FAILURE("Data attribute has unsupported flags.\n");
|
|
}
|
|
|
|
// Find the data run containing the requested clusters.
|
|
|
|
RunHeader *dataRun = (RunHeader *) ((uint8_t *) dataHeader + dataHeader->dataRunsOffset);
|
|
uint64_t clusterNumber = 0;
|
|
bool foundStart = false;
|
|
|
|
uint64_t startCluster = offset / volume->clusterBytes;
|
|
uint64_t endCluster = (offset + count) / volume->clusterBytes;
|
|
uint64_t startOffset = offset - startCluster * volume->clusterBytes;
|
|
uint64_t endOffset = (offset + count) - endCluster * volume->clusterBytes;
|
|
|
|
uint64_t clusterPosition = 0;
|
|
uint8_t *buffer = outputBuffer;
|
|
|
|
KWorkGroup dispatchGroup = {};
|
|
dispatchGroup.Initialise();
|
|
|
|
while (((uint8_t *) dataRun - (uint8_t *) dataHeader) < dataHeader->length && dataRun->lengthFieldBytes) {
|
|
uint64_t length = GetDataRunLength(dataRun), offset = GetDataRunOffset(dataRun);
|
|
|
|
clusterNumber += offset;
|
|
dataRun = (RunHeader *) ((uint8_t *) dataRun + 1 + dataRun->lengthFieldBytes + dataRun->offsetFieldBytes);
|
|
|
|
uint64_t runOffset = 0;
|
|
|
|
if (!foundStart) {
|
|
if (length <= startCluster - clusterPosition) {
|
|
clusterPosition += length;
|
|
continue;
|
|
} else {
|
|
runOffset = startCluster - clusterPosition;
|
|
clusterPosition = startCluster;
|
|
foundStart = true;
|
|
}
|
|
}
|
|
|
|
if (!foundStart) {
|
|
continue;
|
|
}
|
|
|
|
while (runOffset < length && clusterPosition <= endCluster) {
|
|
uint64_t firstClusterToRead = clusterNumber + runOffset;
|
|
uint64_t clustersToRead = (endCluster - clusterPosition) > (length - runOffset)
|
|
? (length - runOffset) : (endCluster - clusterPosition);
|
|
|
|
if (clusterPosition == startCluster || clusterPosition == endCluster) {
|
|
clustersToRead = 1;
|
|
}
|
|
|
|
// Read the clusters.
|
|
|
|
if (clusterPosition == startCluster) {
|
|
volume->fileSystem->Access(firstClusterToRead * volume->clusterBytes,
|
|
clustersToRead * volume->clusterBytes, K_ACCESS_READ,
|
|
clusterBuffer, ES_FLAGS_DEFAULT, &dispatchGroup);
|
|
buffer += volume->clusterBytes - startOffset;
|
|
} else if (clusterPosition == endCluster) {
|
|
volume->fileSystem->Access(firstClusterToRead * volume->clusterBytes,
|
|
clustersToRead * volume->clusterBytes, K_ACCESS_READ,
|
|
clusterBuffer2, ES_FLAGS_DEFAULT, &dispatchGroup);
|
|
} else {
|
|
volume->fileSystem->Access(firstClusterToRead * volume->clusterBytes,
|
|
clustersToRead * volume->clusterBytes, K_ACCESS_READ,
|
|
buffer, ES_FLAGS_DEFAULT, &dispatchGroup);
|
|
buffer += clustersToRead * volume->clusterBytes;
|
|
}
|
|
|
|
clusterPosition += clustersToRead;
|
|
runOffset += clustersToRead;
|
|
}
|
|
}
|
|
|
|
if (clusterPosition <= endCluster) {
|
|
READ_FAILURE("Trying to read past the end of the data runs.\n");
|
|
}
|
|
|
|
// Wait for outstanding IO operations to complete.
|
|
|
|
bool success = dispatchGroup.Wait();
|
|
|
|
if (!success) {
|
|
READ_FAILURE("Could not read clusters from block device.\n");
|
|
}
|
|
|
|
// Copy first and last clusters into the buffer.
|
|
|
|
if (startCluster == endCluster) {
|
|
EsMemoryCopy(outputBuffer, clusterBuffer + startOffset, count);
|
|
} else {
|
|
EsMemoryCopy(outputBuffer, clusterBuffer + startOffset, volume->clusterBytes - startOffset);
|
|
EsMemoryCopy(outputBuffer + count - endOffset, clusterBuffer2, endOffset);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void Close(KNode *node) {
|
|
EsHeapFree(node->driverNode, sizeof(FSNode), K_FIXED);
|
|
}
|
|
|
|
static void DeviceAttach(KDevice *parent) {
|
|
Volume *volume = (Volume *) KDeviceCreate("NTFS", parent, sizeof(Volume), ES_DEVICE_FILE_SYSTEM);
|
|
|
|
if (!volume) {
|
|
KernelLog(LOG_ERROR, "NTFS", "allocate error", "EntryNTFS - Could not allocate Volume structure.\n");
|
|
return;
|
|
}
|
|
|
|
volume->fileSystem = (KFileSystem *) parent;
|
|
|
|
if (!Mount(volume)) {
|
|
KernelLog(LOG_ERROR, "NTFS", "mount failure", "EntryNTFS - Could not mount NTFS volume.\n");
|
|
|
|
if (volume->root) EsHeapFree(volume->root->driverNode, 0, K_FIXED);
|
|
EsHeapFree(volume->root, 0, K_FIXED);
|
|
|
|
for (uintptr_t i = 0; i < 256; i++) {
|
|
if (volume->upCaseLookup[i]) {
|
|
EsHeapFree(volume->upCaseLookup[i], 512, K_FIXED);
|
|
}
|
|
}
|
|
|
|
KDeviceDestroy(volume);
|
|
return;
|
|
}
|
|
|
|
{
|
|
ResidentAttributeHeader *volumeNameHeader = (ResidentAttributeHeader *) FindAttribute(volume, &volume->volumeNode, 0x60);
|
|
|
|
if (volumeNameHeader && !volumeNameHeader->nonResident
|
|
&& (volumeNameHeader->attributeOffset + volumeNameHeader->attributeLength
|
|
+ ((uint8_t *) volumeNameHeader - volume->volumeNode.fileRecord) <= MFT_FILE_SIZE)) {
|
|
volume->fileSystem->nameBytes = volumeNameHeader->attributeLength;
|
|
if (volume->fileSystem->nameBytes > sizeof(volume->fileSystem->name)) volume->fileSystem->nameBytes = sizeof(volume->fileSystem->name);
|
|
EsMemoryCopy(volume->fileSystem->name, (uint8_t *) volumeNameHeader + volumeNameHeader->attributeOffset, volume->fileSystem->nameBytes);
|
|
}
|
|
}
|
|
|
|
volume->fileSystem->read = Read;
|
|
volume->fileSystem->scan = Scan;
|
|
volume->fileSystem->load = Load;
|
|
volume->fileSystem->enumerate = Enumerate;
|
|
volume->fileSystem->close = Close;
|
|
|
|
EsMemoryCopy(&volume->fileSystem->identifier, &volume->bootSector.serialNumber, sizeof(volume->bootSector.serialNumber));
|
|
|
|
KernelLog(LOG_INFO, "NTFS", "register file system", "EntryNTFS - Registering file system with name '%s'.\n",
|
|
volume->fileSystem->nameBytes, volume->fileSystem->name);
|
|
FSRegisterFileSystem(volume->fileSystem);
|
|
}
|
|
|
|
KDriver driverNTFS = {
|
|
.attach = DeviceAttach;
|
|
};
|