mirror of https://gitlab.com/nakst/essence
647 lines
21 KiB
C++
647 lines
21 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 Contiguous block reading in Scan and Enumerate.
|
|
// TODO Make GetDataBlock use (not yet implemented) system block cache.
|
|
|
|
#include <module.h>
|
|
|
|
struct SuperBlock {
|
|
uint32_t inodeCount;
|
|
uint32_t blockCount;
|
|
uint32_t reservedBlockCount;
|
|
uint32_t unallocatedBlockCount;
|
|
uint32_t unallocatedInodeCount;
|
|
|
|
uint32_t superBlockContainer;
|
|
uint32_t blockSizeExponent;
|
|
uint32_t fragmentSizeExponent;
|
|
|
|
uint32_t blocksPerBlockGroup;
|
|
uint32_t fragmentsPerBlockGroup;
|
|
uint32_t inodesPerBlockGroup;
|
|
|
|
uint32_t lastMountTime;
|
|
uint32_t lastWriteTime;
|
|
|
|
uint16_t mountsSinceLastCheck;
|
|
uint16_t mountsAllowedBetweenChecks;
|
|
|
|
uint16_t signature;
|
|
|
|
uint16_t state;
|
|
uint16_t errorHandling;
|
|
|
|
uint16_t minorVersion;
|
|
|
|
uint32_t lastCheckTime;
|
|
uint32_t intervalCheckTime;
|
|
|
|
uint32_t creatorID;
|
|
uint32_t majorVersion;
|
|
|
|
uint16_t superUserID;
|
|
uint16_t superGroupID;
|
|
|
|
uint32_t firstNonReservedInode;
|
|
uint16_t inodeStructureBytes;
|
|
uint16_t blockGroupOfSuperBlock;
|
|
|
|
uint32_t optionalFeatures;
|
|
uint32_t requiredFeatures;
|
|
uint32_t writeFeatures;
|
|
|
|
uint8_t fileSystemID[16];
|
|
uint8_t volumeName[16];
|
|
uint8_t lastMountPath[64];
|
|
|
|
uint32_t compressionAlgorithms;
|
|
|
|
uint8_t preallocateFileBlocks;
|
|
uint8_t preallocateDirectoryBlocks;
|
|
|
|
uint16_t _unused0;
|
|
|
|
uint8_t journalID[16];
|
|
uint32_t journalInode;
|
|
uint32_t journalDevice;
|
|
|
|
uint32_t orphanInodeList;
|
|
};
|
|
|
|
struct BlockGroupDescriptor {
|
|
uint32_t blockUsageBitmap;
|
|
uint32_t inodeUsageBitmap;
|
|
uint32_t inodeTable;
|
|
|
|
uint16_t unallocatedBlockCount;
|
|
uint16_t unallocatedInodeCount;
|
|
uint16_t directoryCount;
|
|
|
|
uint8_t _unused1[14];
|
|
};
|
|
|
|
struct Inode {
|
|
#define INODE_TYPE_DIRECTORY (0x4000)
|
|
#define INODE_TYPE_REGULAR (0x8000)
|
|
uint16_t type;
|
|
|
|
uint16_t userID;
|
|
uint32_t fileSizeLow;
|
|
|
|
uint32_t accessTime;
|
|
uint32_t creationTime;
|
|
uint32_t modificationTime;
|
|
uint32_t deletionTime;
|
|
|
|
uint16_t groupID;
|
|
uint16_t hardLinkCount;
|
|
uint32_t usedSectorCount;
|
|
uint32_t flags;
|
|
uint32_t _unused0;
|
|
|
|
uint32_t directBlockPointers[12];
|
|
uint32_t indirectBlockPointers[3];
|
|
|
|
uint32_t generation;
|
|
uint32_t extendedAttributeBlock;
|
|
uint32_t fileSizeHigh;
|
|
uint32_t fragmentBlock;
|
|
uint8_t osSpecific[12];
|
|
};
|
|
|
|
struct DirectoryEntry {
|
|
uint32_t inode;
|
|
uint16_t entrySize;
|
|
uint8_t nameLengthLow;
|
|
|
|
union {
|
|
uint8_t nameLengthHigh;
|
|
|
|
#define DIRENT_TYPE_REGULAR (1)
|
|
#define DIRENT_TYPE_DIRECTORY (2)
|
|
uint8_t type;
|
|
};
|
|
|
|
// Followed by name.
|
|
};
|
|
|
|
struct FSNode {
|
|
struct Volume *volume;
|
|
Inode inode;
|
|
};
|
|
|
|
struct Volume : KFileSystem {
|
|
SuperBlock superBlock;
|
|
BlockGroupDescriptor *blockGroupDescriptorTable;
|
|
size_t blockBytes;
|
|
};
|
|
|
|
static bool Mount(Volume *volume) {
|
|
#define MOUNT_FAILURE(message) do { KernelLog(LOG_ERROR, "Ext2", "mount failure", "Mount - " message); return false; } while (0)
|
|
|
|
// Load the superblock.
|
|
|
|
uint8_t *sectorBuffer = (uint8_t *) EsHeapAllocate(volume->block->information.sectorSize, false, K_FIXED);
|
|
|
|
if (!sectorBuffer) {
|
|
MOUNT_FAILURE("Could not allocate buffer.\n");
|
|
}
|
|
|
|
EsDefer(EsHeapFree(sectorBuffer, volume->block->information.sectorSize, K_FIXED));
|
|
|
|
{
|
|
if (ES_SUCCESS != volume->Access(1024, volume->block->information.sectorSize, K_ACCESS_READ, sectorBuffer, ES_FLAGS_DEFAULT)) {
|
|
MOUNT_FAILURE("Could not read boot sector.\n");
|
|
}
|
|
|
|
EsMemoryCopy(&volume->superBlock, sectorBuffer, sizeof(SuperBlock));
|
|
|
|
if (volume->superBlock.majorVersion < 1) {
|
|
MOUNT_FAILURE("Volumes below major version 1 not supprted.\n");
|
|
}
|
|
|
|
if (volume->superBlock.requiredFeatures != 2) {
|
|
MOUNT_FAILURE("Volume uses unsupported features that are required to read it.\n");
|
|
}
|
|
|
|
if (volume->superBlock.inodeStructureBytes < sizeof(Inode)) {
|
|
MOUNT_FAILURE("Inode structure size too small.\n");
|
|
}
|
|
|
|
volume->blockBytes = 1024 << volume->superBlock.blockSizeExponent;
|
|
|
|
if (volume->blockBytes < volume->block->information.sectorSize) {
|
|
MOUNT_FAILURE("Block size smaller than drive sector size.\n");
|
|
}
|
|
}
|
|
|
|
// Load the block group descriptor table.
|
|
|
|
{
|
|
uint32_t blockGroupCount = (volume->superBlock.blockCount + volume->superBlock.blocksPerBlockGroup - 1) / volume->superBlock.blocksPerBlockGroup;
|
|
uint32_t firstBlockContainingBlockGroupDescriptorTable = volume->blockBytes == 1024 ? 2 : 1;
|
|
uint32_t blockGroupDescriptorTableLengthInBlocks = (blockGroupCount * sizeof(BlockGroupDescriptor) + volume->blockBytes - 1) / volume->blockBytes;
|
|
|
|
volume->blockGroupDescriptorTable = (BlockGroupDescriptor *) EsHeapAllocate(blockGroupDescriptorTableLengthInBlocks * volume->blockBytes, false, K_FIXED);
|
|
|
|
if (!volume->blockGroupDescriptorTable) {
|
|
MOUNT_FAILURE("Could not allocate the block group descriptor table.\n");
|
|
}
|
|
|
|
if (ES_SUCCESS != volume->Access(firstBlockContainingBlockGroupDescriptorTable * volume->blockBytes,
|
|
blockGroupDescriptorTableLengthInBlocks * volume->blockBytes,
|
|
K_ACCESS_READ, volume->blockGroupDescriptorTable, ES_FLAGS_DEFAULT)) {
|
|
MOUNT_FAILURE("Could not read the block group descriptor table from the drive.\n");
|
|
}
|
|
}
|
|
|
|
// Load the root directory.
|
|
|
|
{
|
|
uint32_t inode = 2;
|
|
|
|
uint32_t blockGroup = (inode - 1) / volume->superBlock.inodesPerBlockGroup;
|
|
uint32_t indexInInodeTable = (inode - 1) % volume->superBlock.inodesPerBlockGroup;
|
|
uint32_t sectorInInodeTable = (indexInInodeTable * volume->superBlock.inodeStructureBytes) / volume->block->information.sectorSize;
|
|
uint32_t offsetInSector = (indexInInodeTable * volume->superBlock.inodeStructureBytes) % volume->block->information.sectorSize;
|
|
|
|
BlockGroupDescriptor *blockGroupDescriptor = volume->blockGroupDescriptorTable + blockGroup;
|
|
|
|
if (ES_SUCCESS != volume->Access(blockGroupDescriptor->inodeTable * volume->blockBytes
|
|
+ sectorInInodeTable * volume->block->information.sectorSize,
|
|
volume->block->information.sectorSize,
|
|
K_ACCESS_READ, sectorBuffer, ES_FLAGS_DEFAULT)) {
|
|
MOUNT_FAILURE("Could not read the inode table.\n");
|
|
}
|
|
|
|
volume->rootDirectory->driverNode = EsHeapAllocate(sizeof(FSNode), true, K_FIXED);
|
|
|
|
if (!volume->rootDirectory->driverNode) {
|
|
MOUNT_FAILURE("Could not allocate root node.\n");
|
|
}
|
|
|
|
FSNode *root = (FSNode *) volume->rootDirectory->driverNode;
|
|
root->volume = volume;
|
|
EsMemoryCopy(&root->inode, sectorBuffer + offsetInSector, sizeof(Inode));
|
|
|
|
volume->rootDirectoryInitialChildren = root->inode.fileSizeLow / sizeof(DirectoryEntry); // TODO This is a terrible upper-bound!
|
|
|
|
if ((root->inode.type & 0xF000) != INODE_TYPE_DIRECTORY) {
|
|
MOUNT_FAILURE("Root directory is not a directory.\n");
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static uint32_t GetDataBlock(Volume *volume, Inode *node, uint64_t blockIndex, uint8_t *blockBuffer) {
|
|
#define CHECK_BLOCK_INDEX() if (offset == 0 || offset / volume->block->information.sectorSize > volume->block->information.sectorCount) { \
|
|
KernelLog(LOG_ERROR, "Ext2", "invalid block index", "GetDataBlock - Block out of bounds.\n"); return 0; }
|
|
#define GET_DATA_BLOCK_ACCESS_FAILURE() do { KernelLog(LOG_ERROR, "Ext2", "block access failure", "GetDataBlock - Could not read block.\n"); return 0; } while (0)
|
|
|
|
size_t blockPointersPerBlock = volume->blockBytes / 4;
|
|
uint32_t *blockPointers = (uint32_t *) blockBuffer;
|
|
uint64_t offset;
|
|
|
|
if (blockIndex < 12) {
|
|
offset = node->directBlockPointers[blockIndex];
|
|
CHECK_BLOCK_INDEX();
|
|
return offset;
|
|
}
|
|
|
|
blockIndex -= 12;
|
|
|
|
if (blockIndex < blockPointersPerBlock) {
|
|
offset = node->indirectBlockPointers[0] * volume->blockBytes;
|
|
CHECK_BLOCK_INDEX();
|
|
if (ES_SUCCESS != volume->Access(offset, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT)) GET_DATA_BLOCK_ACCESS_FAILURE();
|
|
offset = blockPointers[blockIndex];
|
|
CHECK_BLOCK_INDEX();
|
|
return offset;
|
|
}
|
|
|
|
blockIndex -= blockPointersPerBlock;
|
|
|
|
if (blockIndex < blockPointersPerBlock * blockPointersPerBlock) {
|
|
offset = node->indirectBlockPointers[1] * volume->blockBytes;
|
|
CHECK_BLOCK_INDEX();
|
|
if (ES_SUCCESS != volume->Access(offset, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT)) GET_DATA_BLOCK_ACCESS_FAILURE();
|
|
offset = blockPointers[blockIndex / blockPointersPerBlock];
|
|
CHECK_BLOCK_INDEX();
|
|
if (ES_SUCCESS != volume->Access(offset, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT)) GET_DATA_BLOCK_ACCESS_FAILURE();
|
|
offset = blockPointers[blockIndex % blockPointersPerBlock];
|
|
CHECK_BLOCK_INDEX();
|
|
return offset;
|
|
}
|
|
|
|
blockIndex -= blockPointersPerBlock * blockPointersPerBlock;
|
|
|
|
if (blockIndex < blockPointersPerBlock * blockPointersPerBlock * blockPointersPerBlock) {
|
|
offset = node->indirectBlockPointers[2] * volume->blockBytes;
|
|
CHECK_BLOCK_INDEX();
|
|
if (ES_SUCCESS != volume->Access(offset, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT)) GET_DATA_BLOCK_ACCESS_FAILURE();
|
|
offset = blockPointers[blockIndex / blockPointersPerBlock / blockPointersPerBlock];
|
|
CHECK_BLOCK_INDEX();
|
|
if (ES_SUCCESS != volume->Access(offset, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT)) GET_DATA_BLOCK_ACCESS_FAILURE();
|
|
offset = blockPointers[(blockIndex / blockPointersPerBlock) % blockPointersPerBlock];
|
|
CHECK_BLOCK_INDEX();
|
|
if (ES_SUCCESS != volume->Access(offset, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT)) GET_DATA_BLOCK_ACCESS_FAILURE();
|
|
offset = blockPointers[blockIndex % blockPointersPerBlock];
|
|
CHECK_BLOCK_INDEX();
|
|
return offset;
|
|
}
|
|
|
|
KernelLog(LOG_ERROR, "Ext2", "invalid index in inode", "GetDataBlock - Index %d out of bounds.\n", blockIndex);
|
|
return 0;
|
|
}
|
|
|
|
static EsError Enumerate(KNode *node) {
|
|
#define ENUMERATE_FAILURE(message, error) do { KernelLog(LOG_ERROR, "Ext2", "enumerate failure", "Enumerate - " message); return error; } while (0)
|
|
|
|
FSNode *directory = (FSNode *) node->driverNode;
|
|
Volume *volume = directory->volume;
|
|
|
|
uint8_t *blockBuffer = (uint8_t *) EsHeapAllocate(volume->blockBytes, false, K_FIXED);
|
|
|
|
if (!blockBuffer) {
|
|
ENUMERATE_FAILURE("Could not allocate buffer.\n", ES_ERROR_INSUFFICIENT_RESOURCES);
|
|
}
|
|
|
|
EsDefer(EsHeapFree(blockBuffer, volume->blockBytes, K_FIXED));
|
|
|
|
uint32_t blocksInDirectory = directory->inode.fileSizeLow / volume->blockBytes;
|
|
|
|
for (uintptr_t i = 0; i < blocksInDirectory; i++) {
|
|
uint32_t block = GetDataBlock(volume, &directory->inode, i, blockBuffer);
|
|
|
|
if (!block) {
|
|
return ES_ERROR_HARDWARE_FAILURE;
|
|
}
|
|
|
|
EsError error = volume->Access((uint64_t) block * volume->blockBytes, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT);
|
|
if (error != ES_SUCCESS) ENUMERATE_FAILURE("Could not read block.\n", error);
|
|
|
|
uintptr_t positionInBlock = 0;
|
|
|
|
while (positionInBlock + sizeof(DirectoryEntry) < volume->blockBytes) {
|
|
DirectoryEntry *entry = (DirectoryEntry *) (blockBuffer + positionInBlock);
|
|
|
|
if (entry->entrySize > volume->blockBytes - positionInBlock
|
|
|| entry->nameLengthLow > volume->blockBytes - positionInBlock - sizeof(DirectoryEntry)) {
|
|
ENUMERATE_FAILURE("Invalid directory entry size.\n", ES_ERROR_CORRUPT_DATA);
|
|
}
|
|
|
|
KNodeMetadata metadata = {};
|
|
|
|
const char *name = (const char *) (blockBuffer + positionInBlock + sizeof(DirectoryEntry));
|
|
size_t nameBytes = entry->nameLengthLow;
|
|
|
|
metadata.type = entry->type == DIRENT_TYPE_DIRECTORY ? ES_NODE_DIRECTORY : entry->type == DIRENT_TYPE_REGULAR ? ES_NODE_FILE : ES_NODE_INVALID;
|
|
|
|
if (metadata.type == ES_NODE_DIRECTORY) {
|
|
metadata.directoryChildren = ES_DIRECTORY_CHILDREN_UNKNOWN;
|
|
}
|
|
|
|
if (metadata.type != ES_NODE_INVALID
|
|
&& !(nameBytes == 1 && name[0] == '.')
|
|
&& !(nameBytes == 2 && name[0] == '.' && name[1] == '.')) {
|
|
EsError error = FSDirectoryEntryFound(node, &metadata, &entry->inode, name, nameBytes, false);
|
|
|
|
if (error != ES_SUCCESS) {
|
|
return error;
|
|
}
|
|
}
|
|
|
|
positionInBlock += entry->entrySize;
|
|
}
|
|
}
|
|
|
|
return ES_SUCCESS;
|
|
}
|
|
|
|
static EsError Scan(const char *name, size_t nameBytes, KNode *_directory) {
|
|
#define SCAN_FAILURE(message, error) do { KernelLog(LOG_ERROR, "Ext2", "scan failure", "Scan - " message); return error; } while (0)
|
|
|
|
if (nameBytes == 2 && name[0] == '.' && name[1] == '.') return ES_ERROR_FILE_DOES_NOT_EXIST;
|
|
if (nameBytes == 1 && name[0] == '.') return ES_ERROR_FILE_DOES_NOT_EXIST;
|
|
|
|
FSNode *directory = (FSNode *) _directory->driverNode;
|
|
Volume *volume = directory->volume;
|
|
DirectoryEntry *entry = nullptr;
|
|
|
|
uint8_t *blockBuffer = (uint8_t *) EsHeapAllocate(volume->blockBytes, false, K_FIXED);
|
|
|
|
if (!blockBuffer) {
|
|
SCAN_FAILURE("Could not allocate buffer.\n", ES_ERROR_INSUFFICIENT_RESOURCES);
|
|
}
|
|
|
|
EsDefer(EsHeapFree(blockBuffer, volume->blockBytes, K_FIXED));
|
|
|
|
uint32_t blocksInDirectory = directory->inode.fileSizeLow / volume->blockBytes;
|
|
uint32_t inode = 0;
|
|
|
|
for (uintptr_t i = 0; i < blocksInDirectory; i++) {
|
|
uint32_t block = GetDataBlock(volume, &directory->inode, i, blockBuffer);
|
|
|
|
if (!block) {
|
|
return ES_ERROR_HARDWARE_FAILURE;
|
|
}
|
|
|
|
EsError error = volume->Access((uint64_t) block * volume->blockBytes, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT);
|
|
if (error != ES_SUCCESS) SCAN_FAILURE("Could not read block.\n", error);
|
|
|
|
uintptr_t positionInBlock = 0;
|
|
|
|
while (positionInBlock + sizeof(DirectoryEntry) < volume->blockBytes) {
|
|
entry = (DirectoryEntry *) (blockBuffer + positionInBlock);
|
|
|
|
if (entry->entrySize > volume->blockBytes - positionInBlock
|
|
|| entry->nameLengthLow > volume->blockBytes - positionInBlock - sizeof(DirectoryEntry)) {
|
|
SCAN_FAILURE("Invalid directory entry size.\n", ES_ERROR_CORRUPT_DATA);
|
|
}
|
|
|
|
if (entry->nameLengthLow == nameBytes && 0 == EsMemoryCompare(name, blockBuffer + positionInBlock + sizeof(DirectoryEntry), nameBytes)) {
|
|
inode = entry->inode;
|
|
goto foundInode;
|
|
}
|
|
|
|
positionInBlock += entry->entrySize;
|
|
}
|
|
}
|
|
|
|
return ES_ERROR_FILE_DOES_NOT_EXIST;
|
|
|
|
foundInode:;
|
|
|
|
if (inode >= volume->superBlock.inodeCount || inode == 0) {
|
|
SCAN_FAILURE("Invalid inode index.\n", ES_ERROR_CORRUPT_DATA);
|
|
}
|
|
|
|
KNodeMetadata metadata = {};
|
|
|
|
if (entry->type == DIRENT_TYPE_DIRECTORY) {
|
|
metadata.type = ES_NODE_DIRECTORY;
|
|
metadata.directoryChildren = ES_DIRECTORY_CHILDREN_UNKNOWN;
|
|
} else if (entry->type == DIRENT_TYPE_REGULAR) {
|
|
metadata.type = ES_NODE_FILE;
|
|
} else {
|
|
SCAN_FAILURE("Unsupported file type.\n", ES_ERROR_UNSUPPORTED);
|
|
}
|
|
|
|
return FSDirectoryEntryFound(_directory, &metadata, &inode, name, nameBytes, false);
|
|
}
|
|
|
|
static EsError Load(KNode *_directory, KNode *node, KNodeMetadata *metadata, const void *entryData) {
|
|
uint32_t inode = *(uint32_t *) entryData;
|
|
|
|
FSNode *directory = (FSNode *) _directory->driverNode;
|
|
Volume *volume = directory->volume;
|
|
|
|
uint8_t *blockBuffer = (uint8_t *) EsHeapAllocate(volume->blockBytes, false, K_FIXED);
|
|
|
|
if (!blockBuffer) {
|
|
return ES_ERROR_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
EsDefer(EsHeapFree(blockBuffer, volume->blockBytes, K_FIXED));
|
|
|
|
uint32_t blockGroup = (inode - 1) / volume->superBlock.inodesPerBlockGroup;
|
|
uint32_t indexInInodeTable = (inode - 1) % volume->superBlock.inodesPerBlockGroup;
|
|
uint32_t sectorInInodeTable = (indexInInodeTable * volume->superBlock.inodeStructureBytes) / volume->block->information.sectorSize;
|
|
uint32_t offsetInSector = (indexInInodeTable * volume->superBlock.inodeStructureBytes) % volume->block->information.sectorSize;
|
|
|
|
BlockGroupDescriptor *blockGroupDescriptor = volume->blockGroupDescriptorTable + blockGroup;
|
|
|
|
EsError error = volume->Access(blockGroupDescriptor->inodeTable * volume->blockBytes
|
|
+ sectorInInodeTable * volume->block->information.sectorSize,
|
|
volume->block->information.sectorSize,
|
|
K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT);
|
|
if (error != ES_SUCCESS) return error;
|
|
|
|
FSNode *data = (FSNode *) EsHeapAllocate(sizeof(FSNode), true, K_FIXED);
|
|
|
|
if (!data) {
|
|
return ES_ERROR_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
data->volume = volume;
|
|
node->driverNode = data;
|
|
|
|
EsMemoryCopy(&data->inode, blockBuffer + offsetInSector, sizeof(Inode));
|
|
|
|
if ((data->inode.type & 0xF000) == INODE_TYPE_DIRECTORY) {
|
|
if (metadata->type != ES_NODE_DIRECTORY) {
|
|
EsHeapFree(data, sizeof(FSNode), K_FIXED);
|
|
return ES_ERROR_CORRUPT_DATA;
|
|
}
|
|
|
|
metadata->directoryChildren = data->inode.fileSizeLow / sizeof(DirectoryEntry); // TODO This is a terrible upper-bound!
|
|
} else if ((data->inode.type & 0xF000) == INODE_TYPE_REGULAR) {
|
|
if (metadata->type != ES_NODE_FILE) {
|
|
EsHeapFree(data, sizeof(FSNode), K_FIXED);
|
|
return ES_ERROR_CORRUPT_DATA;
|
|
}
|
|
|
|
metadata->totalSize = (uint64_t) data->inode.fileSizeLow | ((uint64_t) data->inode.fileSizeHigh << 32);
|
|
} else {
|
|
if (metadata->type != ES_NODE_INVALID) {
|
|
EsHeapFree(data, sizeof(FSNode), K_FIXED);
|
|
return ES_ERROR_CORRUPT_DATA;
|
|
}
|
|
}
|
|
|
|
return ES_SUCCESS;
|
|
}
|
|
|
|
struct ReadDispatchGroup : KWorkGroup {
|
|
uint64_t extentIndex;
|
|
uint64_t extentCount;
|
|
uint8_t *extentBuffer;
|
|
Volume *volume;
|
|
|
|
void QueueExtent() {
|
|
if (!extentCount) return;
|
|
|
|
volume->Access(extentIndex * volume->blockBytes,
|
|
volume->blockBytes * extentCount, K_ACCESS_READ, extentBuffer, ES_FLAGS_DEFAULT, this);
|
|
}
|
|
|
|
void QueueBlock(Volume *_volume, uint64_t index, uint8_t *buffer) {
|
|
if (extentIndex + extentCount == index && extentCount
|
|
&& extentBuffer + extentCount * volume->blockBytes == buffer) {
|
|
extentCount++;
|
|
} else {
|
|
QueueExtent();
|
|
extentIndex = index;
|
|
extentCount = 1;
|
|
extentBuffer = buffer;
|
|
volume = _volume;
|
|
}
|
|
}
|
|
|
|
bool Read() {
|
|
QueueExtent();
|
|
return Wait();
|
|
}
|
|
};
|
|
|
|
static size_t Read(KNode *node, void *_buffer, EsFileOffset offset, EsFileOffset count) {
|
|
#define READ_FAILURE(message, error) do { KernelLog(LOG_ERROR, "Ext2", "read failure", "Read - " message); return error; } while (0)
|
|
|
|
FSNode *file = (FSNode *) node->driverNode;
|
|
Volume *volume = file->volume;
|
|
|
|
uint8_t *blockBuffer = (uint8_t *) EsHeapAllocate(volume->blockBytes, false, K_FIXED);
|
|
|
|
if (!blockBuffer) {
|
|
READ_FAILURE("Could not allocate sector buffer.\n", ES_ERROR_INSUFFICIENT_RESOURCES);
|
|
}
|
|
|
|
EsDefer(EsHeapFree(blockBuffer, volume->blockBytes, K_FIXED));
|
|
|
|
uint8_t *outputBuffer = (uint8_t *) _buffer;
|
|
uintptr_t outputPosition = 0;
|
|
|
|
uint32_t firstBlock = offset / volume->blockBytes,
|
|
lastBlock = (offset + count) / volume->blockBytes,
|
|
currentBlock = firstBlock;
|
|
|
|
ReadDispatchGroup dispatchGroup = {};
|
|
dispatchGroup.Initialise();
|
|
|
|
while (currentBlock <= lastBlock) {
|
|
uint32_t block = GetDataBlock(volume, &file->inode, currentBlock, blockBuffer);
|
|
|
|
if (!block) {
|
|
return false;
|
|
}
|
|
|
|
uintptr_t readStart = currentBlock == firstBlock ? (offset % volume->blockBytes) : 0;
|
|
uintptr_t readEnd = currentBlock == lastBlock ? ((offset + count) % volume->blockBytes) : volume->blockBytes;
|
|
|
|
bool readEntireBlock = readStart == 0 && readEnd == volume->blockBytes;
|
|
|
|
if (readEntireBlock) {
|
|
dispatchGroup.QueueBlock(volume, block, outputBuffer + outputPosition);
|
|
outputPosition += volume->blockBytes;
|
|
} else {
|
|
EsError error = volume->Access((uint64_t) block * volume->blockBytes, volume->blockBytes, K_ACCESS_READ, blockBuffer, ES_FLAGS_DEFAULT);
|
|
if (error != ES_SUCCESS) READ_FAILURE("Could not read blocks from drive.\n", error);
|
|
|
|
EsMemoryCopy(outputBuffer + outputPosition, blockBuffer + readStart, readEnd - readStart);
|
|
outputPosition += readEnd - readStart;
|
|
}
|
|
|
|
currentBlock++;
|
|
}
|
|
|
|
bool success = dispatchGroup.Read();
|
|
|
|
if (!success) {
|
|
READ_FAILURE("Could not read blocks from drive.\n", ES_ERROR_HARDWARE_FAILURE);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static void Close(KNode *node) {
|
|
EsHeapFree(node->driverNode, sizeof(FSNode), K_FIXED);
|
|
}
|
|
|
|
static void DeviceAttach(KDevice *parent) {
|
|
Volume *volume = (Volume *) KDeviceCreate("ext2", parent, sizeof(Volume));
|
|
|
|
if (!volume || !FSFileSystemInitialise(volume)) {
|
|
KernelLog(LOG_ERROR, "Ext2", "allocate error", "Could not initialise volume.\n");
|
|
return;
|
|
}
|
|
|
|
if (volume->block->information.sectorSize & 0x1FF) {
|
|
KernelLog(LOG_ERROR, "Ext2", "incorrect sector size", "Expected sector size to be a multiple of 512, but drive's sectors are %D.\n",
|
|
volume->block->information.sectorSize);
|
|
KDeviceDestroy(volume);
|
|
return;
|
|
}
|
|
|
|
if (!Mount(volume)) {
|
|
KernelLog(LOG_ERROR, "Ext2", "mount failure", "Could not mount Ext2 volume.\n");
|
|
EsHeapFree(volume->rootDirectory->driverNode, 0, K_FIXED);
|
|
EsHeapFree(volume->blockGroupDescriptorTable, 0, K_FIXED);
|
|
KDeviceDestroy(volume);
|
|
return;
|
|
}
|
|
|
|
volume->read = Read;
|
|
volume->scan = Scan;
|
|
volume->load = Load;
|
|
volume->enumerate = Enumerate;
|
|
volume->close = Close;
|
|
|
|
volume->spaceTotal = volume->superBlock.blockCount * volume->blockBytes;
|
|
volume->spaceUsed = (volume->superBlock.blockCount - volume->superBlock.unallocatedBlockCount) * volume->blockBytes;
|
|
|
|
volume->nameBytes = sizeof(volume->superBlock.volumeName);
|
|
if (volume->nameBytes > sizeof(volume->name)) volume->nameBytes = sizeof(volume->name);
|
|
EsMemoryCopy(volume->name, volume->superBlock.volumeName, volume->nameBytes);
|
|
|
|
for (uintptr_t i = 0; i < volume->nameBytes; i++) {
|
|
if (!volume->name[i]) {
|
|
volume->nameBytes = i;
|
|
}
|
|
}
|
|
|
|
volume->directoryEntryDataBytes = sizeof(uint32_t);
|
|
volume->nodeDataBytes = sizeof(FSNode);
|
|
|
|
KernelLog(LOG_INFO, "Ext2", "register file system", "Registering file system with name '%s'.\n",
|
|
volume->nameBytes, volume->name);
|
|
FSRegisterFileSystem(volume);
|
|
}
|
|
|
|
KDriver driverExt2 = {
|
|
.attach = DeviceAttach,
|
|
};
|