896 lines
33 KiB
C++
896 lines
33 KiB
C++
/*
|
|
* Gpt.cpp
|
|
* GUID Partition Table (GPT) parser
|
|
* Copyright (c) 2026 Daniel Hammer
|
|
*/
|
|
|
|
#include "Gpt.hpp"
|
|
#include "BlockDevice.hpp"
|
|
#include <Terminal/Terminal.hpp>
|
|
#include <CppLib/Stream.hpp>
|
|
#include <Libraries/Memory.hpp>
|
|
#include <Memory/PageFrameAllocator.hpp>
|
|
|
|
using namespace Kt;
|
|
|
|
namespace Drivers::Storage::Gpt {
|
|
|
|
// -------------------------------------------------------------------------
|
|
// State
|
|
// -------------------------------------------------------------------------
|
|
|
|
static PartitionInfo g_partitions[MaxPartitions] = {};
|
|
static int g_partitionCount = 0;
|
|
|
|
// -------------------------------------------------------------------------
|
|
// CRC32 (ISO 3309 / UEFI spec, polynomial 0xEDB88320)
|
|
// -------------------------------------------------------------------------
|
|
|
|
static uint32_t Crc32(const void* data, uint32_t length) {
|
|
const uint8_t* buf = (const uint8_t*)data;
|
|
uint32_t crc = 0xFFFFFFFF;
|
|
|
|
for (uint32_t i = 0; i < length; i++) {
|
|
crc ^= buf[i];
|
|
for (int bit = 0; bit < 8; bit++) {
|
|
if (crc & 1)
|
|
crc = (crc >> 1) ^ 0xEDB88320;
|
|
else
|
|
crc >>= 1;
|
|
}
|
|
}
|
|
|
|
return ~crc;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// GUID helpers
|
|
// -------------------------------------------------------------------------
|
|
|
|
static bool GuidIsZero(const Guid& g) {
|
|
return g.Data1 == 0 && g.Data2 == 0 && g.Data3 == 0 &&
|
|
g.Data4[0] == 0 && g.Data4[1] == 0 && g.Data4[2] == 0 &&
|
|
g.Data4[3] == 0 && g.Data4[4] == 0 && g.Data4[5] == 0 &&
|
|
g.Data4[6] == 0 && g.Data4[7] == 0;
|
|
}
|
|
|
|
static bool GuidEquals(const Guid& a, const Guid& b) {
|
|
return a.Data1 == b.Data1 && a.Data2 == b.Data2 && a.Data3 == b.Data3 &&
|
|
a.Data4[0] == b.Data4[0] && a.Data4[1] == b.Data4[1] &&
|
|
a.Data4[2] == b.Data4[2] && a.Data4[3] == b.Data4[3] &&
|
|
a.Data4[4] == b.Data4[4] && a.Data4[5] == b.Data4[5] &&
|
|
a.Data4[6] == b.Data4[6] && a.Data4[7] == b.Data4[7];
|
|
}
|
|
|
|
static int FindExistingPartition(int blockDevIndex, uint64_t startLba, uint64_t endLba) {
|
|
for (int i = 0; i < g_partitionCount; i++) {
|
|
if (g_partitions[i].BlockDevIndex == blockDevIndex &&
|
|
g_partitions[i].StartLba == startLba &&
|
|
g_partitions[i].EndLba == endLba) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// UTF-16LE to ASCII narrowing
|
|
// -------------------------------------------------------------------------
|
|
|
|
static void Utf16ToAscii(const uint16_t* src, int srcLen, char* dst, int dstMax) {
|
|
int j = 0;
|
|
for (int i = 0; i < srcLen && j < dstMax - 1; i++) {
|
|
uint16_t c = src[i];
|
|
if (c == 0) break;
|
|
dst[j++] = (c < 128) ? (char)c : '?';
|
|
}
|
|
dst[j] = '\0';
|
|
}
|
|
|
|
static void CopyAsciiName(const char* src, char* dst, int dstMax) {
|
|
int i = 0;
|
|
for (; src && src[i] && i < dstMax - 1; i++) {
|
|
dst[i] = src[i];
|
|
}
|
|
dst[i] = '\0';
|
|
}
|
|
|
|
static bool IsExtendedMbrType(uint8_t type) {
|
|
return type == 0x05 || type == 0x0F || type == 0x85;
|
|
}
|
|
|
|
static Guid GuidForMbrType(uint8_t type) {
|
|
switch (type) {
|
|
case 0x82:
|
|
return GUID_LINUX_SWAP;
|
|
case 0x83:
|
|
return GUID_LINUX_FS;
|
|
default:
|
|
return GUID_BASIC_DATA;
|
|
}
|
|
}
|
|
|
|
static int AppendMbrPartition(int blockDevIndex, uint64_t diskSectorCount,
|
|
uint64_t startLba, uint64_t sectorCount,
|
|
uint8_t type, const char* name) {
|
|
if (sectorCount == 0 || g_partitionCount >= MaxPartitions) return 0;
|
|
if (startLba >= diskSectorCount || sectorCount > diskSectorCount - startLba) return 0;
|
|
|
|
uint64_t endLba = startLba + sectorCount - 1;
|
|
if (endLba < startLba) return 0;
|
|
if (FindExistingPartition(blockDevIndex, startLba, endLba) >= 0) return 0;
|
|
|
|
PartitionInfo& part = g_partitions[g_partitionCount];
|
|
part.BlockDevIndex = blockDevIndex;
|
|
part.StartLba = startLba;
|
|
part.EndLba = endLba;
|
|
part.SectorCount = sectorCount;
|
|
part.TypeGuid = GuidForMbrType(type);
|
|
part.UniqueGuid = GUID_UNUSED;
|
|
part.Attributes = 0;
|
|
CopyAsciiName(name, part.Name, 72);
|
|
|
|
g_partitionCount++;
|
|
return 1;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Validate protective MBR
|
|
// -------------------------------------------------------------------------
|
|
|
|
static bool ValidateProtectiveMbr(const uint8_t* sector0) {
|
|
const ProtectiveMbr* mbr = (const ProtectiveMbr*)sector0;
|
|
|
|
if (mbr->Signature != 0xAA55) return false;
|
|
|
|
// At least one partition entry should have type 0xEE (GPT protective)
|
|
for (int i = 0; i < 4; i++) {
|
|
if (mbr->Partitions[i].Type == 0xEE) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Validate GPT header
|
|
// -------------------------------------------------------------------------
|
|
|
|
static bool ValidateHeader(GptHeader* hdr, uint64_t expectedLba) {
|
|
if (hdr->Signature != GPT_HEADER_SIGNATURE) {
|
|
KernelLogStream(ERROR, "GPT") << "Invalid signature";
|
|
return false;
|
|
}
|
|
|
|
if (hdr->Revision < GPT_HEADER_REVISION) {
|
|
KernelLogStream(ERROR, "GPT") << "Unsupported revision";
|
|
return false;
|
|
}
|
|
|
|
if (hdr->HeaderSize < 92 || hdr->HeaderSize > 512) {
|
|
KernelLogStream(ERROR, "GPT") << "Invalid header size: " << (uint64_t)hdr->HeaderSize;
|
|
return false;
|
|
}
|
|
|
|
if (hdr->MyLba != expectedLba) {
|
|
KernelLogStream(ERROR, "GPT") << "MyLBA mismatch: expected "
|
|
<< expectedLba << ", got " << hdr->MyLba;
|
|
return false;
|
|
}
|
|
|
|
// Verify header CRC32
|
|
uint32_t savedCrc = hdr->HeaderCrc32;
|
|
hdr->HeaderCrc32 = 0;
|
|
uint32_t computed = Crc32(hdr, hdr->HeaderSize);
|
|
hdr->HeaderCrc32 = savedCrc;
|
|
|
|
if (computed != savedCrc) {
|
|
KernelLogStream(ERROR, "GPT") << "Header CRC32 mismatch: expected "
|
|
<< base::hex << (uint64_t)savedCrc << ", computed " << (uint64_t)computed;
|
|
return false;
|
|
}
|
|
|
|
if (hdr->SizeOfPartitionEntry < 128) {
|
|
KernelLogStream(ERROR, "GPT") << "Partition entry size too small: "
|
|
<< (uint64_t)hdr->SizeOfPartitionEntry;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Validate partition entry array CRC32
|
|
// -------------------------------------------------------------------------
|
|
|
|
static bool ValidatePartitionArrayCrc(const BlockDevice* dev, const GptHeader* hdr) {
|
|
uint32_t totalBytes = hdr->NumberOfPartitionEntries * hdr->SizeOfPartitionEntry;
|
|
uint32_t totalSectors = (totalBytes + 511) / 512;
|
|
|
|
// Read all partition entry sectors into a temporary buffer
|
|
// Max 128 entries * 128 bytes = 16384 bytes = 32 sectors = 4 pages
|
|
int pagesNeeded = (totalBytes + 0xFFF) / 0x1000;
|
|
if (pagesNeeded > 8) {
|
|
KernelLogStream(ERROR, "GPT") << "Partition array too large";
|
|
return false;
|
|
}
|
|
|
|
void* buf;
|
|
if (pagesNeeded == 1) {
|
|
buf = Memory::g_pfa->AllocateZeroed();
|
|
} else {
|
|
buf = Memory::g_pfa->ReallocConsecutive(nullptr, pagesNeeded);
|
|
memset(buf, 0, pagesNeeded * 0x1000);
|
|
}
|
|
|
|
// Read in chunks of 128 sectors max
|
|
uint8_t* dst = (uint8_t*)buf;
|
|
uint64_t lba = hdr->PartitionEntryLba;
|
|
uint32_t remaining = totalSectors;
|
|
|
|
while (remaining > 0) {
|
|
uint32_t chunk = remaining > 128 ? 128 : remaining;
|
|
if (!dev->ReadSectors(dev->Ctx, lba, chunk, dst)) {
|
|
KernelLogStream(ERROR, "GPT") << "Failed to read partition entries at LBA " << lba;
|
|
Memory::g_pfa->Free(buf, pagesNeeded);
|
|
return false;
|
|
}
|
|
dst += chunk * 512;
|
|
lba += chunk;
|
|
remaining -= chunk;
|
|
}
|
|
|
|
uint32_t computed = Crc32(buf, totalBytes);
|
|
Memory::g_pfa->Free(buf, pagesNeeded);
|
|
|
|
if (computed != hdr->PartitionEntryArrayCrc32) {
|
|
KernelLogStream(ERROR, "GPT") << "Partition array CRC32 mismatch: expected "
|
|
<< base::hex << (uint64_t)hdr->PartitionEntryArrayCrc32
|
|
<< ", computed " << (uint64_t)computed;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Parse partition entries
|
|
// -------------------------------------------------------------------------
|
|
|
|
static int ParsePartitions(const BlockDevice* dev, const GptHeader* hdr, int blockDevIndex) {
|
|
uint32_t totalBytes = hdr->NumberOfPartitionEntries * hdr->SizeOfPartitionEntry;
|
|
uint32_t totalSectors = (totalBytes + 511) / 512;
|
|
|
|
int pagesNeeded = (totalBytes + 0xFFF) / 0x1000;
|
|
if (pagesNeeded > 8) return 0;
|
|
|
|
void* buf;
|
|
if (pagesNeeded == 1) {
|
|
buf = Memory::g_pfa->AllocateZeroed();
|
|
} else {
|
|
buf = Memory::g_pfa->ReallocConsecutive(nullptr, pagesNeeded);
|
|
memset(buf, 0, pagesNeeded * 0x1000);
|
|
}
|
|
|
|
uint8_t* dst = (uint8_t*)buf;
|
|
uint64_t lba = hdr->PartitionEntryLba;
|
|
uint32_t remaining = totalSectors;
|
|
|
|
while (remaining > 0) {
|
|
uint32_t chunk = remaining > 128 ? 128 : remaining;
|
|
if (!dev->ReadSectors(dev->Ctx, lba, chunk, dst)) {
|
|
Memory::g_pfa->Free(buf, pagesNeeded);
|
|
return 0;
|
|
}
|
|
dst += chunk * 512;
|
|
lba += chunk;
|
|
remaining -= chunk;
|
|
}
|
|
|
|
int found = 0;
|
|
|
|
for (uint32_t i = 0; i < hdr->NumberOfPartitionEntries && g_partitionCount < MaxPartitions; i++) {
|
|
const GptPartitionEntry* entry = (const GptPartitionEntry*)
|
|
((uint8_t*)buf + i * hdr->SizeOfPartitionEntry);
|
|
|
|
if (GuidIsZero(entry->TypeGuid)) continue;
|
|
if (entry->StartingLba == 0 || entry->EndingLba == 0) continue;
|
|
if (entry->StartingLba > entry->EndingLba) continue;
|
|
if (FindExistingPartition(blockDevIndex, entry->StartingLba, entry->EndingLba) >= 0) continue;
|
|
|
|
PartitionInfo& part = g_partitions[g_partitionCount];
|
|
part.BlockDevIndex = blockDevIndex;
|
|
part.StartLba = entry->StartingLba;
|
|
part.EndLba = entry->EndingLba;
|
|
part.SectorCount = entry->EndingLba - entry->StartingLba + 1;
|
|
part.TypeGuid = entry->TypeGuid;
|
|
part.UniqueGuid = entry->UniqueGuid;
|
|
part.Attributes = entry->Attributes;
|
|
uint16_t nameCopy[36];
|
|
memcpy(nameCopy, entry->Name, sizeof(nameCopy));
|
|
Utf16ToAscii(nameCopy, 36, part.Name, 72);
|
|
|
|
g_partitionCount++;
|
|
found++;
|
|
}
|
|
|
|
Memory::g_pfa->Free(buf, pagesNeeded);
|
|
return found;
|
|
}
|
|
|
|
static int ParseExtendedMbr(const BlockDevice* dev, int blockDevIndex,
|
|
uint64_t extendedBase, int& logicalNumber) {
|
|
int found = 0;
|
|
uint64_t ebrLba = extendedBase;
|
|
|
|
for (int guard = 0; guard < 32 && g_partitionCount < MaxPartitions; guard++) {
|
|
uint8_t ebr[512];
|
|
if (!dev->ReadSectors(dev->Ctx, ebrLba, 1, ebr)) break;
|
|
|
|
const ProtectiveMbr* mbr = (const ProtectiveMbr*)ebr;
|
|
if (mbr->Signature != 0xAA55) break;
|
|
|
|
const MbrPartitionEntry& logical = mbr->Partitions[0];
|
|
if (logical.Type != 0 && logical.SectorCount != 0 &&
|
|
!IsExtendedMbrType(logical.Type)) {
|
|
char name[16] = {};
|
|
name[0] = 'M'; name[1] = 'B'; name[2] = 'R';
|
|
int n = logicalNumber++;
|
|
name[3] = (char)('0' + ((n / 10) % 10));
|
|
name[4] = (char)('0' + (n % 10));
|
|
name[5] = '\0';
|
|
|
|
found += AppendMbrPartition(blockDevIndex,
|
|
dev->SectorCount,
|
|
ebrLba + logical.LbaFirst,
|
|
logical.SectorCount,
|
|
logical.Type,
|
|
name);
|
|
}
|
|
|
|
const MbrPartitionEntry& next = mbr->Partitions[1];
|
|
if (next.Type == 0 || next.SectorCount == 0 || !IsExtendedMbrType(next.Type)) {
|
|
break;
|
|
}
|
|
|
|
ebrLba = extendedBase + next.LbaFirst;
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
static int ParseMbrPartitions(const BlockDevice* dev, const uint8_t* sector0,
|
|
int blockDevIndex) {
|
|
const ProtectiveMbr* mbr = (const ProtectiveMbr*)sector0;
|
|
if (mbr->Signature != 0xAA55) {
|
|
KernelLogStream(INFO, "MBR") << "No partition table on device "
|
|
<< blockDevIndex << " (LBA 0 signature absent)";
|
|
return 0;
|
|
}
|
|
|
|
int found = 0;
|
|
int logicalNumber = 5;
|
|
|
|
for (int i = 0; i < 4 && g_partitionCount < MaxPartitions; i++) {
|
|
const MbrPartitionEntry& entry = mbr->Partitions[i];
|
|
if (entry.Type == 0 || entry.SectorCount == 0) continue;
|
|
|
|
if (IsExtendedMbrType(entry.Type)) {
|
|
found += ParseExtendedMbr(dev, blockDevIndex, entry.LbaFirst, logicalNumber);
|
|
continue;
|
|
}
|
|
|
|
char name[8] = {};
|
|
name[0] = 'M'; name[1] = 'B'; name[2] = 'R';
|
|
name[3] = (char)('1' + i);
|
|
name[4] = '\0';
|
|
|
|
found += AppendMbrPartition(blockDevIndex, dev->SectorCount, entry.LbaFirst,
|
|
entry.SectorCount, entry.Type, name);
|
|
}
|
|
|
|
if (found > 0) {
|
|
KernelLogStream(OK, "MBR") << "Found " << found
|
|
<< " partition(s) on device " << blockDevIndex;
|
|
} else {
|
|
KernelLogStream(INFO, "MBR") << "Device " << blockDevIndex
|
|
<< " has MBR signature but no partition entries";
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Public API
|
|
// -------------------------------------------------------------------------
|
|
|
|
const char* GetTypeName(const Guid& typeGuid) {
|
|
if (GuidEquals(typeGuid, GUID_EFI_SYSTEM)) return "EFI System";
|
|
if (GuidEquals(typeGuid, GUID_BASIC_DATA)) return "Basic Data";
|
|
if (GuidEquals(typeGuid, GUID_LINUX_FS)) return "Linux Filesystem";
|
|
if (GuidEquals(typeGuid, GUID_LINUX_SWAP)) return "Linux Swap";
|
|
return "Unknown";
|
|
}
|
|
|
|
int ProbeDevice(int blockDevIndex) {
|
|
const BlockDevice* dev = GetBlockDevice(blockDevIndex);
|
|
if (!dev) return 0;
|
|
|
|
// Need at least 34 sectors (MBR + GPT header + 32 sectors of entries)
|
|
if (dev->SectorCount < 34) return 0;
|
|
|
|
// Read LBA 0 (protective MBR) and LBA 1 (GPT header) — 2 sectors
|
|
uint8_t sectorBuf[1024];
|
|
|
|
if (!dev->ReadSectors(dev->Ctx, 0, 2, sectorBuf)) {
|
|
KernelLogStream(ERROR, "GPT") << "Failed to read LBA 0-1 from device "
|
|
<< blockDevIndex;
|
|
return 0;
|
|
}
|
|
|
|
// Validate protective MBR. If this is a legacy-partitioned disk instead
|
|
// of GPT, keep using the same partition registry for MBR entries.
|
|
if (!ValidateProtectiveMbr(sectorBuf)) {
|
|
return ParseMbrPartitions(dev, sectorBuf, blockDevIndex);
|
|
}
|
|
|
|
// Validate primary GPT header (LBA 1)
|
|
GptHeader* hdr = (GptHeader*)(sectorBuf + 512);
|
|
|
|
if (!ValidateHeader(hdr, 1)) {
|
|
KernelLogStream(WARNING, "GPT") << "Primary header invalid on device " << blockDevIndex
|
|
<< ", trying backup...";
|
|
|
|
// Try backup header at last LBA
|
|
uint64_t lastLba = dev->SectorCount - 1;
|
|
if (!dev->ReadSectors(dev->Ctx, lastLba, 1, sectorBuf + 512)) {
|
|
KernelLogStream(ERROR, "GPT") << "Failed to read backup header";
|
|
return 0;
|
|
}
|
|
|
|
if (!ValidateHeader(hdr, lastLba)) {
|
|
KernelLogStream(ERROR, "GPT") << "Backup header also invalid";
|
|
return 0;
|
|
}
|
|
|
|
KernelLogStream(OK, "GPT") << "Using backup GPT header";
|
|
}
|
|
|
|
// Validate partition entry array CRC
|
|
if (!ValidatePartitionArrayCrc(dev, hdr)) {
|
|
return 0;
|
|
}
|
|
|
|
KernelLogStream(OK, "GPT") << "Valid GPT on device " << blockDevIndex
|
|
<< " (" << dev->Model << "): "
|
|
<< (uint64_t)hdr->NumberOfPartitionEntries << " entry slots, "
|
|
<< (uint64_t)hdr->SizeOfPartitionEntry << " bytes each";
|
|
|
|
// Parse partition entries
|
|
int found = ParsePartitions(dev, hdr, blockDevIndex);
|
|
|
|
// Log discovered partitions
|
|
for (int i = g_partitionCount - found; i < g_partitionCount; i++) {
|
|
const PartitionInfo& p = g_partitions[i];
|
|
uint64_t sizeMB = (p.SectorCount * dev->SectorSize) / (1024 * 1024);
|
|
uint64_t sizeGB = sizeMB / 1024;
|
|
|
|
if (sizeGB > 0) {
|
|
KernelLogStream(OK, "GPT") << " Partition " << (i - (g_partitionCount - found))
|
|
<< ": " << (p.Name[0] ? p.Name : "(unnamed)")
|
|
<< " [" << GetTypeName(p.TypeGuid) << "] "
|
|
<< sizeGB << " GiB"
|
|
<< " (LBA " << p.StartLba << "-" << p.EndLba << ")";
|
|
} else {
|
|
KernelLogStream(OK, "GPT") << " Partition " << (i - (g_partitionCount - found))
|
|
<< ": " << (p.Name[0] ? p.Name : "(unnamed)")
|
|
<< " [" << GetTypeName(p.TypeGuid) << "] "
|
|
<< sizeMB << " MiB"
|
|
<< " (LBA " << p.StartLba << "-" << p.EndLba << ")";
|
|
}
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
void ProbeAll() {
|
|
int devCount = GetBlockDeviceCount();
|
|
if (devCount == 0) return;
|
|
|
|
KernelLogStream(INFO, "GPT") << "Probing " << devCount << " block device(s) for GPT...";
|
|
|
|
int totalPartitions = 0;
|
|
for (int i = 0; i < devCount; i++) {
|
|
totalPartitions += ProbeDevice(i);
|
|
}
|
|
|
|
if (totalPartitions > 0) {
|
|
KernelLogStream(OK, "GPT") << "Found " << totalPartitions << " partition(s) total";
|
|
} else {
|
|
KernelLogStream(INFO, "GPT") << "No GPT partitions found";
|
|
}
|
|
}
|
|
|
|
int GetPartitionCount() {
|
|
return g_partitionCount;
|
|
}
|
|
|
|
const PartitionInfo* GetPartition(int index) {
|
|
if (index < 0 || index >= g_partitionCount) return nullptr;
|
|
return &g_partitions[index];
|
|
}
|
|
|
|
int RemovePartitionsForBlockDevice(int blockDevIndex) {
|
|
int removed = 0;
|
|
int dst = 0;
|
|
|
|
for (int src = 0; src < g_partitionCount; src++) {
|
|
if (g_partitions[src].BlockDevIndex == blockDevIndex) {
|
|
removed++;
|
|
continue;
|
|
}
|
|
|
|
if (dst != src) {
|
|
g_partitions[dst] = g_partitions[src];
|
|
}
|
|
dst++;
|
|
}
|
|
|
|
for (int i = dst; i < g_partitionCount; i++) {
|
|
g_partitions[i] = {};
|
|
}
|
|
|
|
g_partitionCount = dst;
|
|
|
|
if (removed > 0) {
|
|
KernelLogStream(INFO, "GPT") << "Removed " << removed
|
|
<< " partition record(s) for device " << blockDevIndex;
|
|
}
|
|
|
|
return removed;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// ASCII to UTF-16LE for partition names
|
|
// -------------------------------------------------------------------------
|
|
|
|
static void AsciiToUtf16(const char* src, uint16_t* dst, int maxChars) {
|
|
int i = 0;
|
|
for (; i < maxChars - 1 && src[i]; i++) {
|
|
dst[i] = (uint16_t)(uint8_t)src[i];
|
|
}
|
|
for (; i < maxChars; i++) {
|
|
dst[i] = 0;
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Simple GUID generation from RDTSC + mixing
|
|
// -------------------------------------------------------------------------
|
|
|
|
static uint64_t SimpleRand64() {
|
|
uint32_t lo, hi;
|
|
asm volatile ("rdtsc" : "=a"(lo), "=d"(hi));
|
|
uint64_t val = ((uint64_t)hi << 32) | lo;
|
|
// xorshift64
|
|
val ^= val << 13;
|
|
val ^= val >> 7;
|
|
val ^= val << 17;
|
|
return val;
|
|
}
|
|
|
|
static Guid GenerateGuid() {
|
|
uint64_t a = SimpleRand64();
|
|
uint64_t b = SimpleRand64();
|
|
Guid g;
|
|
memcpy(&g, &a, 8);
|
|
memcpy(((uint8_t*)&g) + 8, &b, 8);
|
|
// Set version 4 (random) and variant 1
|
|
g.Data3 = (g.Data3 & 0x0FFF) | 0x4000;
|
|
g.Data4[0] = (g.Data4[0] & 0x3F) | 0x80;
|
|
return g;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Write helpers
|
|
// -------------------------------------------------------------------------
|
|
|
|
static bool WriteSector(const BlockDevice* dev, uint64_t lba, const void* buf) {
|
|
return dev->WriteSectors(dev->Ctx, lba, 1, buf);
|
|
}
|
|
|
|
// Rebuild and write the partition entry array + both GPT headers.
|
|
// entries is the full 128-entry array in memory.
|
|
static bool WriteGptStructures(const BlockDevice* dev, GptHeader* primary,
|
|
uint8_t* entryArray, uint32_t entryArrayBytes) {
|
|
uint32_t entryArraySectors = (entryArrayBytes + 511) / 512;
|
|
|
|
// Compute partition entry array CRC
|
|
uint32_t entryCrc = Crc32(entryArray, entryArrayBytes);
|
|
primary->PartitionEntryArrayCrc32 = entryCrc;
|
|
|
|
// Write primary entry array (starts at LBA 2)
|
|
for (uint32_t s = 0; s < entryArraySectors; s++) {
|
|
if (!dev->WriteSectors(dev->Ctx, primary->PartitionEntryLba + s, 1,
|
|
entryArray + s * 512))
|
|
return false;
|
|
}
|
|
|
|
// Write primary header (LBA 1)
|
|
primary->HeaderCrc32 = 0;
|
|
primary->HeaderCrc32 = Crc32(primary, primary->HeaderSize);
|
|
|
|
uint8_t hdrSector[512];
|
|
memset(hdrSector, 0, 512);
|
|
memcpy(hdrSector, primary, primary->HeaderSize);
|
|
if (!WriteSector(dev, 1, hdrSector)) return false;
|
|
|
|
// Build and write backup header at last LBA
|
|
GptHeader backup = *primary;
|
|
backup.MyLba = primary->AlternateLba;
|
|
backup.AlternateLba = primary->MyLba;
|
|
// Backup partition entries are right before the backup header
|
|
backup.PartitionEntryLba = backup.MyLba - entryArraySectors;
|
|
|
|
// Write backup entry array
|
|
for (uint32_t s = 0; s < entryArraySectors; s++) {
|
|
if (!dev->WriteSectors(dev->Ctx, backup.PartitionEntryLba + s, 1,
|
|
entryArray + s * 512))
|
|
return false;
|
|
}
|
|
|
|
// Write backup header CRC
|
|
backup.HeaderCrc32 = 0;
|
|
backup.HeaderCrc32 = Crc32(&backup, backup.HeaderSize);
|
|
memset(hdrSector, 0, 512);
|
|
memcpy(hdrSector, &backup, backup.HeaderSize);
|
|
if (!WriteSector(dev, backup.MyLba, hdrSector)) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// InitializeGpt
|
|
// -------------------------------------------------------------------------
|
|
|
|
int InitializeGpt(int blockDevIndex) {
|
|
const BlockDevice* dev = GetBlockDevice(blockDevIndex);
|
|
if (!dev) return -1;
|
|
if (dev->SectorCount < 68) return -1; // minimum for GPT
|
|
|
|
// Remove stale in-memory partitions for this device
|
|
int dst = 0;
|
|
for (int src = 0; src < g_partitionCount; src++) {
|
|
if (g_partitions[src].BlockDevIndex != blockDevIndex) {
|
|
if (dst != src) g_partitions[dst] = g_partitions[src];
|
|
dst++;
|
|
}
|
|
}
|
|
g_partitionCount = dst;
|
|
|
|
// Write protective MBR
|
|
ProtectiveMbr mbr;
|
|
memset(&mbr, 0, sizeof(mbr));
|
|
mbr.Signature = 0xAA55;
|
|
mbr.Partitions[0].Status = 0x00;
|
|
mbr.Partitions[0].Type = 0xEE;
|
|
mbr.Partitions[0].LbaFirst = 1;
|
|
uint64_t mbrSectors = dev->SectorCount - 1;
|
|
mbr.Partitions[0].SectorCount = (mbrSectors > 0xFFFFFFFF) ? 0xFFFFFFFF : (uint32_t)mbrSectors;
|
|
|
|
if (!WriteSector(dev, 0, &mbr)) {
|
|
KernelLogStream(ERROR, "GPT") << "Failed to write protective MBR";
|
|
return -1;
|
|
}
|
|
|
|
// Build primary GPT header
|
|
uint32_t numEntries = 128;
|
|
uint32_t entrySize = 128;
|
|
uint32_t entryArrayBytes = numEntries * entrySize;
|
|
uint32_t entryArraySectors = (entryArrayBytes + 511) / 512; // 32 sectors
|
|
|
|
GptHeader hdr;
|
|
memset(&hdr, 0, sizeof(hdr));
|
|
hdr.Signature = GPT_HEADER_SIGNATURE;
|
|
hdr.Revision = GPT_HEADER_REVISION;
|
|
hdr.HeaderSize = 92;
|
|
hdr.MyLba = 1;
|
|
hdr.AlternateLba = dev->SectorCount - 1;
|
|
hdr.FirstUsableLba = 2 + entryArraySectors; // after primary entries
|
|
hdr.LastUsableLba = dev->SectorCount - 2 - entryArraySectors; // before backup entries
|
|
hdr.DiskGuid = GenerateGuid();
|
|
hdr.PartitionEntryLba = 2;
|
|
hdr.NumberOfPartitionEntries = numEntries;
|
|
hdr.SizeOfPartitionEntry = entrySize;
|
|
|
|
// Empty partition entry array
|
|
uint8_t* entryArray = (uint8_t*)Memory::g_pfa->ReallocConsecutive(
|
|
nullptr, (entryArrayBytes + 0xFFF) / 0x1000);
|
|
memset(entryArray, 0, entryArrayBytes);
|
|
|
|
bool ok = WriteGptStructures(dev, &hdr, entryArray, entryArrayBytes);
|
|
Memory::g_pfa->Free(entryArray, (entryArrayBytes + 0xFFF) / 0x1000);
|
|
|
|
if (!ok) {
|
|
KernelLogStream(ERROR, "GPT") << "Failed to write GPT structures";
|
|
return -1;
|
|
}
|
|
|
|
KernelLogStream(OK, "GPT") << "Initialized GPT on device " << blockDevIndex
|
|
<< " (usable LBA " << hdr.FirstUsableLba << "-" << hdr.LastUsableLba << ")";
|
|
|
|
return 0;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// AddPartition
|
|
// -------------------------------------------------------------------------
|
|
|
|
int AddPartition(int blockDevIndex, uint64_t startLba, uint64_t endLba,
|
|
const Guid& typeGuid, const char* name) {
|
|
const BlockDevice* dev = GetBlockDevice(blockDevIndex);
|
|
if (!dev) return -1;
|
|
|
|
// Read existing primary GPT header
|
|
uint8_t hdrBuf[512];
|
|
if (!dev->ReadSectors(dev->Ctx, 1, 1, hdrBuf)) return -1;
|
|
|
|
GptHeader* hdr = (GptHeader*)hdrBuf;
|
|
if (!ValidateHeader(hdr, 1)) {
|
|
KernelLogStream(ERROR, "GPT") << "No valid GPT on device " << blockDevIndex;
|
|
return -1;
|
|
}
|
|
|
|
// Read partition entry array
|
|
uint32_t entryArrayBytes = hdr->NumberOfPartitionEntries * hdr->SizeOfPartitionEntry;
|
|
uint32_t pages = (entryArrayBytes + 0xFFF) / 0x1000;
|
|
uint8_t* entryArray = (uint8_t*)Memory::g_pfa->ReallocConsecutive(nullptr, pages);
|
|
memset(entryArray, 0, pages * 0x1000);
|
|
|
|
uint32_t entryArraySectors = (entryArrayBytes + 511) / 512;
|
|
for (uint32_t s = 0; s < entryArraySectors; s++) {
|
|
if (!dev->ReadSectors(dev->Ctx, hdr->PartitionEntryLba + s, 1, entryArray + s * 512)) {
|
|
Memory::g_pfa->Free(entryArray, pages);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// Find a free entry slot
|
|
int freeSlot = -1;
|
|
for (uint32_t i = 0; i < hdr->NumberOfPartitionEntries; i++) {
|
|
GptPartitionEntry* e = (GptPartitionEntry*)(entryArray + i * hdr->SizeOfPartitionEntry);
|
|
if (GuidIsZero(e->TypeGuid)) {
|
|
freeSlot = (int)i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (freeSlot < 0) {
|
|
KernelLogStream(ERROR, "GPT") << "No free partition entry slots";
|
|
Memory::g_pfa->Free(entryArray, pages);
|
|
return -1;
|
|
}
|
|
|
|
// Auto-fill: find largest free region if startLba/endLba are both 0
|
|
if (startLba == 0 && endLba == 0) {
|
|
// Collect used ranges
|
|
struct Range { uint64_t start; uint64_t end; };
|
|
Range used[MaxPartitions];
|
|
int usedCount = 0;
|
|
|
|
for (uint32_t i = 0; i < hdr->NumberOfPartitionEntries && usedCount < MaxPartitions; i++) {
|
|
GptPartitionEntry* e = (GptPartitionEntry*)(entryArray + i * hdr->SizeOfPartitionEntry);
|
|
if (!GuidIsZero(e->TypeGuid)) {
|
|
used[usedCount].start = e->StartingLba;
|
|
used[usedCount].end = e->EndingLba;
|
|
usedCount++;
|
|
}
|
|
}
|
|
|
|
// Simple bubble sort by start LBA
|
|
for (int i = 0; i < usedCount - 1; i++) {
|
|
for (int j = i + 1; j < usedCount; j++) {
|
|
if (used[j].start < used[i].start) {
|
|
Range tmp = used[i]; used[i] = used[j]; used[j] = tmp;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find largest gap
|
|
uint64_t bestStart = 0, bestEnd = 0, bestSize = 0;
|
|
|
|
// Gap before first partition
|
|
uint64_t gapStart = hdr->FirstUsableLba;
|
|
uint64_t gapEnd = (usedCount > 0) ? used[0].start - 1 : hdr->LastUsableLba;
|
|
if (gapEnd >= gapStart && gapEnd - gapStart + 1 > bestSize) {
|
|
bestStart = gapStart; bestEnd = gapEnd;
|
|
bestSize = gapEnd - gapStart + 1;
|
|
}
|
|
|
|
// Gaps between partitions
|
|
for (int i = 0; i < usedCount - 1; i++) {
|
|
gapStart = used[i].end + 1;
|
|
gapEnd = used[i + 1].start - 1;
|
|
if (gapEnd >= gapStart && gapEnd - gapStart + 1 > bestSize) {
|
|
bestStart = gapStart; bestEnd = gapEnd;
|
|
bestSize = gapEnd - gapStart + 1;
|
|
}
|
|
}
|
|
|
|
// Gap after last partition
|
|
if (usedCount > 0) {
|
|
gapStart = used[usedCount - 1].end + 1;
|
|
gapEnd = hdr->LastUsableLba;
|
|
if (gapEnd >= gapStart && gapEnd - gapStart + 1 > bestSize) {
|
|
bestStart = gapStart; bestEnd = gapEnd;
|
|
bestSize = gapEnd - gapStart + 1;
|
|
}
|
|
}
|
|
|
|
if (bestSize == 0) {
|
|
KernelLogStream(ERROR, "GPT") << "No free space for new partition";
|
|
Memory::g_pfa->Free(entryArray, pages);
|
|
return -1;
|
|
}
|
|
|
|
startLba = bestStart;
|
|
endLba = bestEnd;
|
|
}
|
|
|
|
// Validate range
|
|
if (startLba < hdr->FirstUsableLba || endLba > hdr->LastUsableLba || startLba > endLba) {
|
|
KernelLogStream(ERROR, "GPT") << "Invalid partition range";
|
|
Memory::g_pfa->Free(entryArray, pages);
|
|
return -1;
|
|
}
|
|
|
|
// Fill the entry
|
|
GptPartitionEntry* newEntry = (GptPartitionEntry*)(entryArray + freeSlot * hdr->SizeOfPartitionEntry);
|
|
newEntry->TypeGuid = typeGuid;
|
|
newEntry->UniqueGuid = GenerateGuid();
|
|
newEntry->StartingLba = startLba;
|
|
newEntry->EndingLba = endLba;
|
|
newEntry->Attributes = 0;
|
|
AsciiToUtf16(name ? name : "", newEntry->Name, 36);
|
|
|
|
// Write updated GPT structures
|
|
bool ok = WriteGptStructures(dev, hdr, entryArray, entryArrayBytes);
|
|
Memory::g_pfa->Free(entryArray, pages);
|
|
|
|
if (!ok) {
|
|
KernelLogStream(ERROR, "GPT") << "Failed to write updated GPT";
|
|
return -1;
|
|
}
|
|
|
|
// Add to in-memory partition table
|
|
if (g_partitionCount < MaxPartitions) {
|
|
PartitionInfo& part = g_partitions[g_partitionCount];
|
|
part.BlockDevIndex = blockDevIndex;
|
|
part.StartLba = startLba;
|
|
part.EndLba = endLba;
|
|
part.SectorCount = endLba - startLba + 1;
|
|
part.TypeGuid = typeGuid;
|
|
part.UniqueGuid = newEntry->UniqueGuid;
|
|
part.Attributes = 0;
|
|
if (name) {
|
|
int i = 0;
|
|
for (; i < 71 && name[i]; i++) part.Name[i] = name[i];
|
|
part.Name[i] = '\0';
|
|
} else {
|
|
part.Name[0] = '\0';
|
|
}
|
|
|
|
int idx = g_partitionCount++;
|
|
|
|
KernelLogStream(OK, "GPT") << "Added partition " << freeSlot
|
|
<< " [" << GetTypeName(typeGuid) << "] LBA "
|
|
<< startLba << "-" << endLba;
|
|
|
|
return idx;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
};
|