add support for more than one volume

This commit is contained in:
maksw committed 2026-05-19 21:52:07 +02:00
1 parent 529f816166
commit 09c1420a32
5 files changed
+191 -67

No files matched your search

Binary file not shown.
+8 -3
View File
@@ -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%\"
+174 -57
View File
@@ -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,67 +1479,175 @@ 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) {
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(v, 0, 1, mbr))) return 0;
if (mbr[510] != 0x55 || mbr[511] != 0xAA) return 0;
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; }
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;
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)) return 0;
if (entrySize < sizeof(GPT_ENTRY) || entrySize > 512)
return count;
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);
uint32_t entriesPerSector = 512 / entrySize;
if (!entriesPerSector)
return count;
uint8_t entrySector[512];
uint64_t loadedSector = UINT64_MAX;
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 (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;
}
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;
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;
}
return 0;
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,32 +2098,36 @@ 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);
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;
}
v->PartitionLba = partLba;
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;
uint8_t sector[512];
if (!KSUCCESS(ReadSectors(v, partLba, 1, sector))) {
if (!KSUCCESS(ReadSectors(v, v->PartitionLba, 1, sector))) {
lastStatus = STATUS_DEVICE_ERROR;
DbgPrint("FAT32: drive %u: failed to read VBR\n", drive);
DbgPrint("FAT32: disk %u part %u: failed to read VBR\n", disk, part);
continue;
}
BPB *bpb = (BPB *)sector;
if (bpb->FatSize16 != 0 || bpb->RootEntryCount != 0) {
if (!IsFat32Vbr(v, v->PartitionLba)) {
lastStatus = STATUS_NOT_SUPPORTED;
DbgPrint("FAT32: drive %u: unsupported FAT variant\n", drive);
DbgPrint("FAT32: disk %u part %u: unsupported FAT variant\n", disk, part);
continue;
}
@@ -2026,27 +2138,32 @@ KSTATUS DriverEntry(const HAL_API *hal) {
v->NumFats = bpb->NumFats;
v->FatSize = bpb->FatSize32;
v->RootCluster = bpb->RootCluster;
v->DataStart = partLba + bpb->ReservedSectors + bpb->NumFats * bpb->FatSize32;
v->DataStart = v->PartitionLba + bpb->ReservedSectors + (uint64_t)bpb->NumFats * bpb->FatSize32;
v->NextFreeHint = 2;
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;
Fat32InitVtable(&gFs[drive], v);
Fat32InitVtable(&gFs[volumeIndex], v);
IO_VOLUME_PARAMS volParams;
volParams.DiskIndex = v->DriveIndex;
volParams.PartitionLba = partLba;
volParams.PartitionSize = totalSectors;
volParams.PartitionLba = v->PartitionLba;
volParams.PartitionSize = v->PartitionSize;
KSTATUS status = IoRegisterFilesystem(drive, &gFs[drive], &volParams);
KSTATUS status = IoRegisterFilesystem(volumeIndex, &gFs[volumeIndex], &volParams);
if (KSUCCESS(status)) {
mounted++;
DbgPrint("FAT32: drive %u mounted (clusters=%u).\n", drive, v->TotalClusters);
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: drive %u mount failed status=%d.\n", drive, status);
DbgPrint("FAT32: disk %u part %u mount as drive %u failed status=%d.\n",
disk, part, volumeIndex, status);
}
}
}
+3 -1
View File
@@ -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%"
+6 -6
View File
@@ -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.