From 09c1420a3279b058d3a732c21ae3c64e84b72e01 Mon Sep 17 00:00:00 2001 From: Maksymilian Wilczynski Date: Tue, 19 May 2026 21:50:55 +0200 Subject: [PATCH] add support for more than one volume --- OVMF/OVMF_VARS_4M_d.fd | Bin 540672 -> 540672 bytes build.bat | 11 +- code/drivers/fat32/fat32.c | 309 +++++++++++++++++++++++++------------ mkimage.bat | 4 +- readme.md | 12 +- 5 files changed, 230 insertions(+), 106 deletions(-) diff --git a/OVMF/OVMF_VARS_4M_d.fd b/OVMF/OVMF_VARS_4M_d.fd index 830a905c39233f36ff40d7eed3c5574d45a1ae1e..cd4f15c0aaa85cbbd9f2b3f847485bee292350ac 100644 GIT binary patch delta 89 zcmZo@P;3CAEsWnRC;J6CZMU#uyu`KnUG6ScMvKi8gSrJ6Eg_T@kn$9m{>_rniP0L! kzR1oU4I&HVGV!XuFZq?2R#7x_*+L@1aY(LP$XyDW)z|tnb+9tr( LCcwT-fWrU)cRCYS diff --git a/build.bat b/build.bat index c6cda6b..ca8f60d 100644 --- a/build.bat +++ b/build.bat @@ -183,7 +183,7 @@ link %LFLAGS% /SUBSYSTEM:NATIVE /ENTRY:main ^ /OUT:"bin\video.exe" /PDB:"bin\video.pdb" bin\win32.lib obj\libc.obj %SRC%\video\bink2w64.lib obj\video\*.obj || exit /b 1 :: ---- Mount disk image ---- -echo Mounting disk.vhd as Z: ... +echo Mounting disk.vhd... set DPS=%TEMP%\ntlite_mnt_%RANDOM%.txt ( echo select vdisk file="%VHD%" @@ -191,6 +191,9 @@ set DPS=%TEMP%\ntlite_mnt_%RANDOM%.txt echo select partition 1 echo format fs=fat32 quick echo assign letter=Z + echo select partition 2 + echo format fs=fat32 quick + echo assign letter=Y echo exit ) > "%DPS%" diskpart /s "%DPS%" @@ -202,10 +205,12 @@ if not exist "Z:\" ( exit /b 1 ) set DEST=Z: +set DEST2=Y: :: ---- Copy to ESP layout ---- echo Assembling ESP... mkdir "%DEST%\apps" 2>nul +mkdir "%DEST2%\apps" 2>nul mkdir "%DEST%\system" 2>nul mkdir "%DEST%\drivers" 2>nul mkdir "%DEST%\EFI\BOOT" 2>nul @@ -223,8 +228,8 @@ copy /y "bin\init.exe" "%DEST%\system\" copy /y "bin\shell_win32.exe" "%DEST%\apps\" copy /y "bin\shell_posix.exe" "%DEST%\apps\" copy /y "bin\shell_native.exe" "%DEST%\apps\" -copy /y "bin\async_apc.exe" "%DEST%\apps\" -copy /y "bin\async_cancel.exe" "%DEST%\apps\" +copy /y "bin\async_apc.exe" "%DEST2%\apps\" +copy /y "bin\async_cancel.exe" "%DEST2%\apps\" copy /y "bin\doom.exe" "%DEST%\apps\" copy /y "bin\video.exe" "%DEST%\apps\" copy /y "%SRC%\boot.ini" "%DEST%\" diff --git a/code/drivers/fat32/fat32.c b/code/drivers/fat32/fat32.c index 6c8f657..5837089 100644 --- a/code/drivers/fat32/fat32.c +++ b/code/drivers/fat32/fat32.c @@ -233,8 +233,9 @@ static void FlushFatCache(FAT32_VOL *v); // Driver state // --------------------------------------------------------------------------- struct FAT32_VOL_S { - uint8_t DriveIndex; - uint32_t PartitionLba; + uint8_t DriveIndex; // physical disk DEVICE_OBJECT index + uint64_t PartitionLba; // absolute LBA of the mounted volume + uint64_t PartitionSize; // sectors, best-effort bounds/documentation uint32_t BytesPerSector; uint32_t SectorsPerCluster; uint32_t BytesPerCluster; @@ -242,11 +243,10 @@ struct FAT32_VOL_S { uint32_t NumFats; uint32_t FatSize; // sectors per FAT copy uint32_t RootCluster; - uint32_t DataStart; // absolute LBA of cluster 2 + uint64_t DataStart; // absolute LBA of cluster 2 uint32_t TotalClusters; // usable data clusters uint32_t NextFreeHint; // fix #3: start cluster for next free-scan }; - typedef struct { uint32_t StartCluster; uint32_t FileSize; @@ -1479,66 +1479,174 @@ static void Fat32CloseDir(IO_FILESYSTEM *fs, void *dirCtx) { } // --------------------------------------------------------------------------- -// Partition scan (MBR + GPT) — unchanged +// Volume scan (MBR + GPT + superfloppy) // --------------------------------------------------------------------------- static const EFI_GUID GUID_EFI_SYSTEM = { 0xC12A7328, 0xF81F, 0x11D2, {0xBA,0x4B,0x00,0xA0,0xC9,0x3E,0xC9,0x3B} }; static const EFI_GUID GUID_MS_BASIC_DATA = { 0xEBD0A0A2, 0xB9E5, 0x4433, {0x87,0xC0,0x68,0xB6,0xB7,0x26,0x99,0xC7} }; static const EFI_GUID GUID_LINUX_BASIC_DATA = { 0x0FC63DAF, 0x8483, 0x4772, {0x8E,0x79,0x3D,0x69,0xD8,0x47,0x7D,0xE4} }; -static int IsFat32Vbr(FAT32_VOL *v, uint32_t lba) { +#define FAT32_MAX_PARTITIONS_PER_DISK 32 + +typedef struct FAT32_PARTITION_CANDIDATE { + uint64_t StartLba; + uint64_t SectorCount; +} FAT32_PARTITION_CANDIDATE; + +static uint32_t ReadLe32(const void* p) { + const uint8_t* b = (const uint8_t*)p; + return (uint32_t)b[0] + | ((uint32_t)b[1] << 8) + | ((uint32_t)b[2] << 16) + | ((uint32_t)b[3] << 24); +} + +static int IsFat32Vbr(FAT32_VOL *v, uint64_t lba) { uint8_t sector[512]; if (!KSUCCESS(ReadSectors(v, lba, 1, sector))) return 0; if (sector[510] != 0x55 || sector[511] != 0xAA) return 0; + BPB *bpb = (BPB *)sector; - return bpb->FatSize16 == 0 && bpb->RootEntryCount == 0 && bpb->FatSize32 != 0; + if (bpb->BytesPerSector != 512) return 0; // disk I/O layer is 512-byte sector based + if (bpb->SectorsPerCluster == 0) return 0; + if (bpb->NumFats == 0) return 0; + if (bpb->FatSize16 != 0) return 0; + if (bpb->RootEntryCount != 0) return 0; + if (bpb->FatSize32 == 0) return 0; + if (bpb->RootCluster < 2) return 0; + + uint32_t totalSectors = bpb->TotalSectors16 ? bpb->TotalSectors16 : bpb->TotalSectors32; + if (totalSectors == 0) return 0; + if (totalSectors <= (uint32_t)bpb->ReservedSectors + (uint32_t)bpb->NumFats * bpb->FatSize32) + return 0; + + return 1; } -static uint32_t FindFat32Partition(FAT32_VOL *v) { - uint8_t mbr[512]; - if (!KSUCCESS(ReadSectors(v, 0, 1, mbr))) return 0; - if (mbr[510] != 0x55 || mbr[511] != 0xAA) return 0; - - int isGpt = 0; - for (int i = 0; i < 4; i++) if (mbr[0x1BE + i*16 + 4] == 0xEE) { isGpt = 1; break; } - - if (isGpt) { - uint8_t gptSector[512]; - if (!KSUCCESS(ReadSectors(v, 1, 1, gptSector))) return 0; - GPT_HEADER *gpt = (GPT_HEADER *)gptSector; - if (memcmp(gpt->Signature, "EFI PART", 8) != 0) return 0; - - uint64_t entryLBA = gpt->PartitionEntryLBA; - uint32_t numEntries = gpt->NumberOfPartitionEntries; - uint32_t entrySize = gpt->SizeOfPartitionEntry; - if (entrySize < sizeof(GPT_ENTRY)) return 0; - - uint32_t eps = 512 / entrySize; uint8_t entrySector[512]; - for (uint32_t i = 0; i < numEntries; i++) { - if (i % eps == 0) - if (!KSUCCESS(ReadSectors(v, entryLBA + i/eps, 1, entrySector))) return 0; - GPT_ENTRY *entry = (GPT_ENTRY *)(entrySector + (i % eps) * entrySize); - static const EFI_GUID emptyGuid = {0}; - if (memcmp(&entry->PartitionTypeGUID, &emptyGuid, sizeof(EFI_GUID)) == 0) continue; - if (memcmp(&entry->PartitionTypeGUID, &GUID_EFI_SYSTEM, sizeof(EFI_GUID)) == 0 || - memcmp(&entry->PartitionTypeGUID, &GUID_MS_BASIC_DATA, sizeof(EFI_GUID)) == 0 || - memcmp(&entry->PartitionTypeGUID, &GUID_LINUX_BASIC_DATA, sizeof(EFI_GUID)) == 0) { - uint32_t lba = (uint32_t)entry->StartingLBA; - if (IsFat32Vbr(v, lba)) return lba; - } - } - return 0; - } - - for (int i = 0; i < 4; i++) { - uint8_t *entry = mbr + 0x1BE + i*16; - if (entry[4] == 0x0B || entry[4] == 0x0C) { - uint32_t lba = *(uint32_t *)(entry + 8); - if (IsFat32Vbr(v, lba)) return lba; - } +static int Fat32CandidateExists(FAT32_PARTITION_CANDIDATE* out, + uint32_t count, + uint64_t startLba) { + for (uint32_t i = 0; i < count; i++) { + if (out[i].StartLba == startLba) + return 1; } return 0; } +static void Fat32AddCandidate(FAT32_VOL* probe, + FAT32_PARTITION_CANDIDATE* out, + uint32_t* count, + uint32_t maxCount, + uint64_t startLba, + uint64_t sectorCount) { + if (!out || !count || *count >= maxCount) return; + if (Fat32CandidateExists(out, *count, startLba)) return; + + uint64_t diskSectors = IoDiskSectorCount(probe->DriveIndex); + if (diskSectors) { + if (startLba >= diskSectors) return; + if (sectorCount == 0 || startLba + sectorCount > diskSectors) + sectorCount = diskSectors - startLba; + } + + if (!IsFat32Vbr(probe, startLba)) return; + + out[*count].StartLba = startLba; + out[*count].SectorCount = sectorCount; + (*count)++; +} + +static uint32_t Fat32EnumerateDiskVolumes(uint8_t diskIndex, + FAT32_PARTITION_CANDIDATE* out, + uint32_t maxCount) { + if (!out || maxCount == 0) return 0; + + FAT32_VOL probe; + memset(&probe, 0, sizeof(probe)); + probe.DriveIndex = diskIndex; + + uint32_t count = 0; + uint8_t mbr[512]; + if (!KSUCCESS(ReadSectors(&probe, 0, 1, mbr))) + return 0; + + // A disk image may be a raw FAT32 volume with no partition table. + Fat32AddCandidate(&probe, out, &count, maxCount, 0, IoDiskSectorCount(diskIndex)); + + if (mbr[510] != 0x55 || mbr[511] != 0xAA) + return count; + + int isGpt = 0; + for (int i = 0; i < 4; i++) { + if (mbr[0x1BE + i * 16 + 4] == 0xEE) { + isGpt = 1; + break; + } + } + + if (isGpt) { + uint8_t gptSector[512]; + if (!KSUCCESS(ReadSectors(&probe, 1, 1, gptSector))) + return count; + + GPT_HEADER* gpt = (GPT_HEADER*)gptSector; + if (memcmp(gpt->Signature, "EFI PART", 8) != 0) + return count; + + uint64_t entryLBA = gpt->PartitionEntryLBA; + uint32_t numEntries = gpt->NumberOfPartitionEntries; + uint32_t entrySize = gpt->SizeOfPartitionEntry; + if (entrySize < sizeof(GPT_ENTRY) || entrySize > 512) + return count; + + uint32_t entriesPerSector = 512 / entrySize; + if (!entriesPerSector) + return count; + + uint8_t entrySector[512]; + uint64_t loadedSector = UINT64_MAX; + static const EFI_GUID emptyGuid = {0}; + + for (uint32_t i = 0; i < numEntries && count < maxCount; i++) { + uint64_t sectorLba = entryLBA + i / entriesPerSector; + if (sectorLba != loadedSector) { + if (!KSUCCESS(ReadSectors(&probe, sectorLba, 1, entrySector))) + return count; + loadedSector = sectorLba; + } + + GPT_ENTRY* entry = (GPT_ENTRY*)(entrySector + (i % entriesPerSector) * entrySize); + if (memcmp(&entry->PartitionTypeGUID, &emptyGuid, sizeof(EFI_GUID)) == 0) + continue; + + if (memcmp(&entry->PartitionTypeGUID, &GUID_EFI_SYSTEM, sizeof(EFI_GUID)) != 0 && + memcmp(&entry->PartitionTypeGUID, &GUID_MS_BASIC_DATA, sizeof(EFI_GUID)) != 0 && + memcmp(&entry->PartitionTypeGUID, &GUID_LINUX_BASIC_DATA, sizeof(EFI_GUID)) != 0) + continue; + + uint64_t start = entry->StartingLBA; + uint64_t end = entry->EndingLBA; + if (end < start) continue; + Fat32AddCandidate(&probe, out, &count, maxCount, start, end - start + 1); + } + + return count; + } + + for (int i = 0; i < 4 && count < maxCount; i++) { + uint8_t* entry = mbr + 0x1BE + i * 16; + uint8_t type = entry[4]; + uint32_t start = ReadLe32(entry + 8); + uint32_t size = ReadLe32(entry + 12); + if (!start || !size) continue; + + // FAT32 CHS/LBA plus EFI System Partition. The VBR probe is the + // authority, so accepting 0xEF here lets FAT32 ESPs mount too. + if (type == 0x0B || type == 0x0C || type == 0xEF) + Fat32AddCandidate(&probe, out, &count, maxCount, start, size); + } + + return count; +} // --------------------------------------------------------------------------- // Native async read/write @@ -1990,63 +2098,72 @@ KSTATUS DriverEntry(const HAL_API *hal) { uint32_t mounted = 0; KSTATUS lastStatus = STATUS_NOT_SUPPORTED; - for (uint8_t drive = 0; drive < FAT32_MAX_VOLUMES; drive++) { - if (!IoDiskDevicePresent(drive)) + for (uint8_t disk = 0; disk < FAT32_MAX_VOLUMES && mounted < FAT32_MAX_VOLUMES; disk++) { + if (!IoDiskDevicePresent(disk)) continue; - FAT32_VOL* v = &gVols[drive]; - memset(v, 0, sizeof(*v)); - v->DriveIndex = drive; - - uint32_t partLba = FindFat32Partition(v); - if (!partLba) { - DbgPrint("FAT32: drive %u: no FAT32 volume\n", drive); - continue; - } - v->PartitionLba = partLba; - - uint8_t sector[512]; - if (!KSUCCESS(ReadSectors(v, partLba, 1, sector))) { - lastStatus = STATUS_DEVICE_ERROR; - DbgPrint("FAT32: drive %u: failed to read VBR\n", drive); + FAT32_PARTITION_CANDIDATE parts[FAT32_MAX_PARTITIONS_PER_DISK]; + uint32_t partCount = Fat32EnumerateDiskVolumes(disk, parts, FAT32_MAX_PARTITIONS_PER_DISK); + if (!partCount) { + DbgPrint("FAT32: disk %u: no FAT32 volumes\n", disk); continue; } - BPB *bpb = (BPB *)sector; - if (bpb->FatSize16 != 0 || bpb->RootEntryCount != 0) { - lastStatus = STATUS_NOT_SUPPORTED; - DbgPrint("FAT32: drive %u: unsupported FAT variant\n", drive); - continue; - } + for (uint32_t part = 0; part < partCount && mounted < FAT32_MAX_VOLUMES; part++) { + uint8_t volumeIndex = (uint8_t)mounted; + FAT32_VOL* v = &gVols[volumeIndex]; + memset(v, 0, sizeof(*v)); + v->DriveIndex = disk; + v->PartitionLba = parts[part].StartLba; + v->PartitionSize = parts[part].SectorCount; - v->BytesPerSector = bpb->BytesPerSector; - v->SectorsPerCluster = bpb->SectorsPerCluster; - v->BytesPerCluster = bpb->BytesPerSector * bpb->SectorsPerCluster; - v->ReservedSectors = bpb->ReservedSectors; - v->NumFats = bpb->NumFats; - v->FatSize = bpb->FatSize32; - v->RootCluster = bpb->RootCluster; - v->DataStart = partLba + bpb->ReservedSectors + bpb->NumFats * bpb->FatSize32; - v->NextFreeHint = 2; + uint8_t sector[512]; + if (!KSUCCESS(ReadSectors(v, v->PartitionLba, 1, sector))) { + lastStatus = STATUS_DEVICE_ERROR; + DbgPrint("FAT32: disk %u part %u: failed to read VBR\n", disk, part); + continue; + } - uint32_t totalSectors = bpb->TotalSectors16 ? bpb->TotalSectors16 : bpb->TotalSectors32; - uint32_t dataSectors = totalSectors - bpb->ReservedSectors - bpb->NumFats * bpb->FatSize32; - v->TotalClusters = dataSectors / bpb->SectorsPerCluster; + BPB *bpb = (BPB *)sector; + if (!IsFat32Vbr(v, v->PartitionLba)) { + lastStatus = STATUS_NOT_SUPPORTED; + DbgPrint("FAT32: disk %u part %u: unsupported FAT variant\n", disk, part); + continue; + } - Fat32InitVtable(&gFs[drive], v); + v->BytesPerSector = bpb->BytesPerSector; + v->SectorsPerCluster = bpb->SectorsPerCluster; + v->BytesPerCluster = bpb->BytesPerSector * bpb->SectorsPerCluster; + v->ReservedSectors = bpb->ReservedSectors; + v->NumFats = bpb->NumFats; + v->FatSize = bpb->FatSize32; + v->RootCluster = bpb->RootCluster; + v->DataStart = v->PartitionLba + bpb->ReservedSectors + (uint64_t)bpb->NumFats * bpb->FatSize32; + v->NextFreeHint = 2; - IO_VOLUME_PARAMS volParams; - volParams.DiskIndex = v->DriveIndex; - volParams.PartitionLba = partLba; - volParams.PartitionSize = totalSectors; + uint32_t totalSectors = bpb->TotalSectors16 ? bpb->TotalSectors16 : bpb->TotalSectors32; + uint32_t dataSectors = totalSectors - bpb->ReservedSectors - bpb->NumFats * bpb->FatSize32; + v->TotalClusters = dataSectors / bpb->SectorsPerCluster; + if (v->PartitionSize == 0 || v->PartitionSize > totalSectors) + v->PartitionSize = totalSectors; - KSTATUS status = IoRegisterFilesystem(drive, &gFs[drive], &volParams); - if (KSUCCESS(status)) { - mounted++; - DbgPrint("FAT32: drive %u mounted (clusters=%u).\n", drive, v->TotalClusters); - } else { - lastStatus = status; - DbgPrint("FAT32: drive %u mount failed status=%d.\n", drive, status); + Fat32InitVtable(&gFs[volumeIndex], v); + + IO_VOLUME_PARAMS volParams; + volParams.DiskIndex = v->DriveIndex; + volParams.PartitionLba = v->PartitionLba; + volParams.PartitionSize = v->PartitionSize; + + KSTATUS status = IoRegisterFilesystem(volumeIndex, &gFs[volumeIndex], &volParams); + if (KSUCCESS(status)) { + mounted++; + DbgPrint("FAT32: disk %u part %u mounted as drive %u (lba=%llu clusters=%u).\n", + disk, part, volumeIndex, (unsigned long long)v->PartitionLba, v->TotalClusters); + } else { + lastStatus = status; + DbgPrint("FAT32: disk %u part %u mount as drive %u failed status=%d.\n", + disk, part, volumeIndex, status); + } } } diff --git a/mkimage.bat b/mkimage.bat index 686fd67..79e68a4 100644 --- a/mkimage.bat +++ b/mkimage.bat @@ -16,8 +16,10 @@ set DPS=%TEMP%\ntlite_mkimg_%RANDOM%.txt echo create vdisk file="%VHD%" maximum=256 type=fixed echo select vdisk file="%VHD%" echo attach vdisk + echo create partition primary size=150 + echo format fs=fat32 quick echo create partition primary - echo format fs=fat32 label=NTLITE quick + echo format fs=fat32 quick echo detach vdisk echo exit ) > "%DPS%" diff --git a/readme.md b/readme.md index be067e4..6252101 100644 --- a/readme.md +++ b/readme.md @@ -197,14 +197,14 @@ That sentence used to be science fiction. Now it is boring. Excellent. ```text "apps/shell.exe" -> current working directory -"0:/apps/shell.exe" -> drive 0 -"2:/data/config.ini" -> drive 2 -"C:\apps\doom.exe" -> drive 0, mapping is simple c->0, d->1, etc. -"/apps/video.exe" -> drive 0 -"/1/apps/video.exe" -> drive 1 +"0:/apps/shell.exe" -> volume 0 +"2:/data/config.ini" -> volume 2 +"C:\apps\doom.exe" -> volume 0, mapping is simple c->0, d->1, etc. +"/apps/video.exe" -> volume 0 +"/1/apps/video.exe" -> volume 1 ``` -Drives `0`-`7` are available. A drive only works if a filesystem is registered on it. The Win32 personality translates drive letters to drive numbers, while the POSIX personality maps drives onto a tree. +Volumes `0`-`7` are available. A volume only works if a filesystem is registered on it. The Win32 personality translates drive letters to volume numbers, while the POSIX personality maps volumes onto a tree. `0:/` is a mounted namespace, not a Windows path. It merely enjoys cosplay.