essence-os/drivers/iso9660.cpp

545 lines
16 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.
#include <module.h>
#define SECTOR_SIZE (2048)
struct LBE16 {
#ifdef __BIG_ENDIAN__
uint16_t _u, x;
#else
uint16_t x, _u;
#endif
};
struct LBE32 {
#ifdef __BIG_ENDIAN__
uint32_t _u, x;
#else
uint32_t x, _u;
#endif
};
struct DateTime {
char year[4];
char month[2];
char day[2];
char hour[2];
char minute[2];
char second[2];
char centiseconds[2];
int8_t timeZoneOffset;
} ES_STRUCT_PACKED;
struct DateTime2 {
uint8_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
int8_t timeZoneOffset;
} ES_STRUCT_PACKED;
struct DirectoryRecord {
uint8_t length;
uint8_t extendedAttributeLength;
LBE32 extentStart;
LBE32 extentSize;
DateTime2 recordingTime;
uint8_t flags;
uint8_t interleavedUnitSize;
uint8_t interleavedGapSize;
LBE16 volumeSequenceNumber;
uint8_t fileNameBytes;
} ES_STRUCT_PACKED;
struct PrimaryDescriptor {
uint8_t typeCode;
char signature[5];
uint8_t version;
uint8_t _unused0;
char systemIdentifier[32];
char volumeIdentifier[32];
uint8_t _unused1[8];
LBE32 volumeSize;
uint8_t _unused2[32];
LBE16 volumeSetSize;
LBE16 volumeSequenceNumber;
LBE16 logicalBlockSize;
LBE32 pathTableSize;
uint32_t pathTableLittle;
uint32_t optionalPathTableLittle;
uint32_t pathTableBig;
uint32_t optionalPathTableBig;
DirectoryRecord rootDirectory;
char rootDirectoryName;
char volumeSetIdentifier[128];
char publisherIdentifier[128];
char dataPreparerIdentifier[128];
char applicationIdentifier[128];
char copyrightFileIdentifier[38];
char abstractFileIdentifier[36];
char bibliographicFileIdentifier[37];
DateTime volumeCreationTime;
DateTime volumeModificationTime;
DateTime volumeExpirationTime;
DateTime volumeEffectiveTime;
uint8_t fileStructureVersion;
uint8_t _unused3;
char applicationSpecific[512];
uint8_t _unused4[653];
} ES_STRUCT_PACKED;
struct DirectoryRecordReference {
uint32_t sector, offset;
};
struct FSNode {
struct Volume *volume;
DirectoryRecord record;
};
struct Volume : KFileSystem {
PrimaryDescriptor primaryDescriptor;
};
static EsError ScanInternal(const char *name, size_t nameBytes, KNode *_directory, DirectoryRecord *_record = nullptr);
static bool Mount(Volume *volume) {
#define MOUNT_FAILURE(message) do { KernelLog(LOG_ERROR, "ISO9660", "mount failure", "Mount - " message); return false; } while (0)
uintptr_t descriptorIndex = 0;
while (true) {
if (ES_SUCCESS != volume->Access(32768 + SECTOR_SIZE * descriptorIndex, SECTOR_SIZE, K_ACCESS_READ, &volume->primaryDescriptor, ES_FLAGS_DEFAULT)) {
MOUNT_FAILURE("Could not access descriptor list.\n");
}
if (0 != EsMemoryCompare(volume->primaryDescriptor.signature, "CD001", 5)) {
MOUNT_FAILURE("Invalid descriptor signature.\n");
}
if (volume->primaryDescriptor.typeCode == 1) {
break;
}
if (volume->primaryDescriptor.typeCode == 0xFF) {
MOUNT_FAILURE("Could not find primary descriptor in descriptor list.\n");
}
if (++descriptorIndex > 16) {
MOUNT_FAILURE("Could not find end of descriptor list.\n");
}
}
if (volume->primaryDescriptor.version != 1 || volume->primaryDescriptor.fileStructureVersion != 1) {
MOUNT_FAILURE("Unsupported file system version.\n");
}
if (volume->primaryDescriptor.logicalBlockSize.x != SECTOR_SIZE) {
MOUNT_FAILURE("Unsupported block size.\n");
}
{
FSNode *root = (FSNode *) EsHeapAllocate(sizeof(FSNode), true, K_FIXED);
if (!root) {
MOUNT_FAILURE("Could not allocate root node.\n");
}
volume->rootDirectory->driverNode = root;
volume->rootDirectoryInitialChildren = volume->primaryDescriptor.rootDirectory.extentSize.x / sizeof(DirectoryRecord);
root->volume = volume;
root->record = volume->primaryDescriptor.rootDirectory;
}
{
// Is this the boot disc?
EsUniqueIdentifier identifier = KGetBootIdentifier();
if (0 != EsMemoryCompare("Essence::", volume->primaryDescriptor.applicationSpecific, 9)) {
goto notBoot;
}
if (EsMemoryCompare(&identifier, volume->primaryDescriptor.applicationSpecific + 9, 16)) {
goto notBoot;
}
DirectoryRecord record = {};
ScanInternal(EsLiteral("ESSENCE.DAT;1"), volume->rootDirectory, &record);
record.extentSize.x = (record.extentSize.x + SECTOR_SIZE - 1) / SECTOR_SIZE;
if (!record.length || record.extentStart.x >= volume->block->information.sectorCount
|| record.extentSize.x >= volume->block->information.sectorCount - record.extentStart.x) {
goto notBoot;
}
// Load the first sector to look at the MBR.
uint8_t *firstSector = (uint8_t *) EsHeapAllocate(SECTOR_SIZE, false, K_FIXED);
if (!firstSector) {
KernelLog(LOG_ERROR, "ISO9660", "allocation failure", "Could not allocate sector buffer to check MBR.\n");
goto notBoot;
}
EsDefer(EsHeapFree(firstSector, SECTOR_SIZE, K_FIXED));
if (ES_SUCCESS != volume->Access(record.extentStart.x * SECTOR_SIZE, SECTOR_SIZE, K_ACCESS_READ, firstSector, ES_FLAGS_DEFAULT)) {
goto notBoot;
}
uint32_t sectorOffset = ((uint32_t) firstSector[0x1BE + 8] << 0) + ((uint32_t) firstSector[0x1BE + 9] << 8)
+ ((uint32_t) firstSector[0x1BE + 10] << 16) + ((uint32_t) firstSector[0x1BE + 11] << 24);
sectorOffset /= (SECTOR_SIZE / 512); // Convert to disc sectors.
if (sectorOffset >= record.extentSize.x) {
goto notBoot;
}
record.extentStart.x += sectorOffset;
record.extentSize.x -= sectorOffset;
KernelLog(LOG_INFO, "ISO9660", "found boot disc", "Found boot disc. Image at %d/%d.\n",
record.extentStart.x, record.extentSize.x);
FSPartitionDeviceCreate(volume->block, record.extentStart.x, record.extentSize.x, ES_FLAGS_DEFAULT, EsLiteral("CD-ROM boot partition"));
}
notBoot:;
return true;
}
static size_t Read(KNode *node, void *_buffer, EsFileOffset offset, EsFileOffset count) {
#define READ_FAILURE(message, error) do { KernelLog(LOG_ERROR, "ISO9660", "read failure", "Read - " message); return error; } while (0)
FSNode *file = (FSNode *) node->driverNode;
Volume *volume = file->volume;
uint8_t *sectorBuffer = (uint8_t *) EsHeapAllocate(SECTOR_SIZE, false, K_FIXED);
if (!sectorBuffer) {
READ_FAILURE("Could not allocate sector buffer.\n", ES_ERROR_INSUFFICIENT_RESOURCES);
}
EsDefer(EsHeapFree(sectorBuffer, SECTOR_SIZE, K_FIXED));
uint8_t *outputBuffer = (uint8_t *) _buffer;
uint64_t firstSector = offset / SECTOR_SIZE;
uint32_t lba = file->record.extentStart.x + firstSector;
offset %= SECTOR_SIZE;
while (count) {
if (offset || count < SECTOR_SIZE) {
EsError error = volume->Access(lba * SECTOR_SIZE, SECTOR_SIZE, K_ACCESS_READ, sectorBuffer, ES_FLAGS_DEFAULT);
if (error != ES_SUCCESS) READ_FAILURE("Could not read file sector.\n", error);
uint64_t bytesToRead = (count > SECTOR_SIZE - offset) ? (SECTOR_SIZE - offset) : count;
EsMemoryCopy(outputBuffer, sectorBuffer + offset, bytesToRead);
lba++, count -= bytesToRead, offset = 0, outputBuffer += bytesToRead;
} else {
uint64_t sectorsToRead = count / SECTOR_SIZE;
EsError error = volume->Access(lba * SECTOR_SIZE, sectorsToRead * SECTOR_SIZE, K_ACCESS_READ, outputBuffer, ES_FLAGS_DEFAULT);
if (error != ES_SUCCESS) READ_FAILURE("Could not read file sectors.\n", error);
lba += sectorsToRead, count -= SECTOR_SIZE * sectorsToRead, outputBuffer += SECTOR_SIZE * sectorsToRead;
}
}
return true;
}
static EsError Enumerate(KNode *node) {
#define ENUMERATE_FAILURE(message, error) do { KernelLog(LOG_ERROR, "ISO9660", "enumerate failure", "Enumerate - " message); return error; } while (0)
FSNode *directory = (FSNode *) node->driverNode;
Volume *volume = directory->volume;
// TODO Load multiple sectors at once?
uint8_t *sectorBuffer = (uint8_t *) EsHeapAllocate(SECTOR_SIZE, false, K_FIXED);
if (!sectorBuffer) {
ENUMERATE_FAILURE("Could not allocate sector buffer.\n", ES_ERROR_INSUFFICIENT_RESOURCES);
}
EsDefer(EsHeapFree(sectorBuffer, SECTOR_SIZE, K_FIXED));
uint32_t currentSector = directory->record.extentStart.x;
uint32_t remainingBytes = directory->record.extentSize.x;
while (remainingBytes) {
EsError accessResult = volume->Access(currentSector * SECTOR_SIZE, SECTOR_SIZE, K_ACCESS_READ, (uint8_t *) sectorBuffer, ES_FLAGS_DEFAULT);
if (accessResult != ES_SUCCESS) {
ENUMERATE_FAILURE("Could not read sector.\n", accessResult);
}
uintptr_t positionInSector = 0;
while (positionInSector < SECTOR_SIZE && positionInSector < remainingBytes) {
DirectoryRecord *record = (DirectoryRecord *) (sectorBuffer + positionInSector);
if (!record->length) {
break;
}
if (positionInSector + record->length > SECTOR_SIZE || record->length < sizeof(DirectoryRecord)) {
ENUMERATE_FAILURE("Invalid directory record.\n", ES_ERROR_CORRUPT_DATA);
}
if (record->fileNameBytes <= 2) {
goto nextEntry;
}
{
KNodeMetadata metadata = {};
size_t nameBytes = record->fileNameBytes;
for (uintptr_t i = 0; i < record->fileNameBytes; i++) {
if (((char *) (record + 1))[i] == ';') {
nameBytes = i;
break;
}
}
metadata.type = (record->flags & (1 << 1)) ? ES_NODE_DIRECTORY : ES_NODE_FILE;
if (metadata.type == ES_NODE_DIRECTORY) {
metadata.directoryChildren = ES_DIRECTORY_CHILDREN_UNKNOWN;
metadata.totalSize = 0;
} else if (metadata.type == ES_NODE_FILE) {
metadata.totalSize = record->extentSize.x;
}
DirectoryRecordReference reference = {};
reference.sector = currentSector;
reference.offset = positionInSector;
EsError error = FSDirectoryEntryFound(node, &metadata, &reference,
(const char *) (record + 1), nameBytes, false);
if (error != ES_SUCCESS) {
return error;
}
}
nextEntry:;
positionInSector += record->length;
}
if (remainingBytes < SECTOR_SIZE) {
remainingBytes = 0;
} else {
remainingBytes -= SECTOR_SIZE;
}
}
return ES_SUCCESS;
}
static EsError ScanInternal(const char *name, size_t nameBytes, KNode *_directory, DirectoryRecord *_record) {
#define SCAN_FAILURE(message, error) do { KernelLog(LOG_ERROR, "ISO9660", "scan failure", "Scan - " message); return error; } while (0)
// Check for invalid characters.
for (uintptr_t i = 0; i < nameBytes; i++) {
bool validCharacter = name[i] == '.' || name[i] == ';' || name[i] == '_'
|| (name[i] >= 'A' && name[i] <= 'Z')
|| (name[i] >= '0' && name[i] <= '9');
if (!validCharacter) {
return ES_ERROR_FILE_DOES_NOT_EXIST;
}
}
FSNode *directory = (FSNode *) _directory->driverNode;
Volume *volume = directory->volume;
// TODO Load multiple sectors at once?
uint8_t *sectorBuffer = (uint8_t *) EsHeapAllocate(SECTOR_SIZE, false, K_FIXED);
if (!sectorBuffer) {
SCAN_FAILURE("Could not allocate sector buffer.\n", ES_ERROR_INSUFFICIENT_RESOURCES);
}
EsDefer(EsHeapFree(sectorBuffer, SECTOR_SIZE, K_FIXED));
uint32_t currentSector = directory->record.extentStart.x;
uint32_t remainingBytes = directory->record.extentSize.x;
while (remainingBytes) {
EsError accessResult = volume->Access(currentSector * SECTOR_SIZE, SECTOR_SIZE, K_ACCESS_READ, (uint8_t *) sectorBuffer, ES_FLAGS_DEFAULT);
if (accessResult != ES_SUCCESS) {
SCAN_FAILURE("Could not read sector.\n", accessResult);
}
uintptr_t positionInSector = 0;
while (positionInSector < SECTOR_SIZE && positionInSector < remainingBytes) {
DirectoryRecord *record = (DirectoryRecord *) (sectorBuffer + positionInSector);
if (!record->length) {
break;
}
if (positionInSector + record->length > SECTOR_SIZE || record->length < sizeof(DirectoryRecord)) {
SCAN_FAILURE("Invalid directory record.\n", ES_ERROR_CORRUPT_DATA);
}
if (record->fileNameBytes <= 2) {
goto nextEntry;
}
if (!((nameBytes == record->fileNameBytes && 0 == EsMemoryCompare(record + 1, name, nameBytes))
|| (nameBytes + 2 == record->fileNameBytes && 0 == EsMemoryCompare(record + 1, name, nameBytes)
&& 0 == EsMemoryCompare((char *) (record + 1) + record->fileNameBytes - 2, ";1", 2)))) {
goto nextEntry;
}
if (_record) {
EsMemoryCopy(_record, record, sizeof(DirectoryRecord));
return ES_SUCCESS;
}
{
KNodeMetadata metadata = {};
metadata.type = (record->flags & (1 << 1)) ? ES_NODE_DIRECTORY : ES_NODE_FILE;
if (metadata.type == ES_NODE_DIRECTORY) {
metadata.directoryChildren = ES_DIRECTORY_CHILDREN_UNKNOWN;
metadata.totalSize = 0;
} else if (metadata.type == ES_NODE_FILE) {
metadata.totalSize = record->extentSize.x;
}
DirectoryRecordReference reference = {};
reference.sector = currentSector;
reference.offset = positionInSector;
return FSDirectoryEntryFound(_directory, &metadata, &reference,
name, nameBytes, false);
}
nextEntry:;
positionInSector += record->length;
}
if (remainingBytes < SECTOR_SIZE) {
remainingBytes = 0;
} else {
remainingBytes -= SECTOR_SIZE;
}
}
return ES_ERROR_FILE_DOES_NOT_EXIST;
}
static EsError Scan(const char *name, size_t nameBytes, KNode *_directory) {
return ScanInternal(name, nameBytes, _directory);
}
static EsError Load(KNode *_directory, KNode *_node, KNodeMetadata *, const void *entryData) {
DirectoryRecordReference reference = *(DirectoryRecordReference *) entryData;
FSNode *directory = (FSNode *) _directory->driverNode;
Volume *volume = directory->volume;
uint8_t *sectorBuffer = (uint8_t *) EsHeapAllocate(SECTOR_SIZE, false, K_FIXED);
if (!sectorBuffer) {
return ES_ERROR_INSUFFICIENT_RESOURCES;
}
EsDefer(EsHeapFree(sectorBuffer, SECTOR_SIZE, K_FIXED));
EsError error = volume->Access(reference.sector * SECTOR_SIZE, SECTOR_SIZE, K_ACCESS_READ, (uint8_t *) sectorBuffer, 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;
DirectoryRecord *record = (DirectoryRecord *) (sectorBuffer + reference.offset);
EsMemoryCopy(&data->record, record, sizeof(DirectoryRecord));
return ES_SUCCESS;
}
static void Close(KNode *node) {
EsHeapFree(node->driverNode, sizeof(FSNode), K_FIXED);
}
static void DeviceAttach(KDevice *parent) {
Volume *volume = (Volume *) KDeviceCreate("ISO9660", parent, sizeof(Volume));
if (!volume || !FSFileSystemInitialise(volume)) {
KernelLog(LOG_ERROR, "ISO9660", "allocate error", "DeviceAttach - Could not initialise volume.\n");
return;
}
if (volume->block->information.sectorSize != SECTOR_SIZE) {
KernelLog(LOG_ERROR, "ISO9660", "incorrect sector size", "DeviceAttach - Expected 2KB sectors, but drive's sectors are %D.\n",
volume->block->information.sectorSize);
KDeviceDestroy(volume);
return;
}
if (!Mount(volume)) {
KernelLog(LOG_ERROR, "ISO9660", "mount failure", "DeviceAttach - Could not mount ISO9660 volume.\n");
KDeviceDestroy(volume);
return;
}
volume->read = Read;
volume->scan = Scan;
volume->load = Load;
volume->enumerate = Enumerate;
volume->close = Close;
volume->spaceUsed = volume->primaryDescriptor.volumeSize.x * volume->primaryDescriptor.logicalBlockSize.x;
volume->spaceTotal = volume->spaceUsed;
uint64_t crc64 = CalculateCRC64(&volume->primaryDescriptor, sizeof(PrimaryDescriptor), 0);
EsMemoryCopy(&volume->identifier, &crc64, sizeof(crc64));
volume->nameBytes = sizeof(volume->primaryDescriptor.volumeIdentifier);
if (volume->nameBytes > sizeof(volume->name)) volume->nameBytes = sizeof(volume->name);
EsMemoryCopy(volume->name, volume->primaryDescriptor.volumeIdentifier, volume->nameBytes);
for (intptr_t i = volume->nameBytes - 1; i >= 0; i--) {
if (volume->name[i] == ' ') {
volume->nameBytes--;
} else {
break;
}
}
volume->directoryEntryDataBytes = sizeof(DirectoryRecordReference);
volume->nodeDataBytes = sizeof(FSNode);
KernelLog(LOG_INFO, "ISO9660", "register file system", "DeviceAttach - Registering file system with name '%s'.\n",
volume->nameBytes, volume->name);
FSRegisterFileSystem(volume);
}
KDriver driverISO9660 = {
.attach = DeviceAttach,
};