feat: MontaukOS installer app, toml config, app directory with manifests, and more

This commit is contained in:
2026-03-08 12:06:29 +01:00
parent 4d0177d55e
commit 1edbec3c66
60 changed files with 3045 additions and 268 deletions
+8
View File
@@ -76,4 +76,12 @@ namespace Montauk {
static int Sys_FDelete(const char* path) {
return Fs::Vfs::VfsDelete(path);
}
static int Sys_FMkdir(const char* path) {
return Fs::Vfs::VfsMkdir(path);
}
static int Sys_DriveList(int* outDrives, int maxEntries) {
return Fs::Vfs::VfsDriveList(outDrives, maxEntries);
}
};
+4
View File
@@ -157,6 +157,10 @@ namespace Montauk {
return (int64_t)Sys_FCreate((const char*)frame->arg1);
case SYS_FDELETE:
return (int64_t)Sys_FDelete((const char*)frame->arg1);
case SYS_FMKDIR:
return (int64_t)Sys_FMkdir((const char*)frame->arg1);
case SYS_DRIVELIST:
return (int64_t)Sys_DriveList((int*)frame->arg1, (int)frame->arg2);
case SYS_TERMSCALE:
return Sys_TermScale(frame->arg1, frame->arg2);
case SYS_RESOLVE:
+2
View File
@@ -91,6 +91,8 @@ namespace Montauk {
static constexpr uint64_t SYS_FWRITE = 41;
static constexpr uint64_t SYS_FCREATE = 42;
static constexpr uint64_t SYS_FDELETE = 77;
static constexpr uint64_t SYS_FMKDIR = 78;
static constexpr uint64_t SYS_DRIVELIST = 79;
/* Graphics.hpp */
static constexpr uint64_t SYS_TERMSCALE = 43;
+10
View File
@@ -483,6 +483,16 @@ namespace Drivers::Storage::Gpt {
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));
+178 -5
View File
@@ -50,6 +50,9 @@ namespace Fs::Fat32 {
// Location of the 32-byte SFN directory entry on disk
uint64_t sfnPartSector; // partition-relative sector
uint32_t sfnOffInSector; // byte offset within that sector
// Cached position for sequential access (avoids O(n²) chain walks)
uint32_t cachedClusterIdx; // cluster index in chain
uint32_t cachedCluster; // cluster number at that index
};
struct Fat32Instance {
@@ -805,6 +808,8 @@ namespace Fs::Fat32 {
self.files[i].isDirectory = (entry.attributes & ATTR_DIRECTORY) != 0;
self.files[i].sfnPartSector = entry.sfnPartSector;
self.files[i].sfnOffInSector = entry.sfnOffInSector;
self.files[i].cachedClusterIdx = 0;
self.files[i].cachedCluster = entry.firstCluster;
return i;
}
}
@@ -831,15 +836,23 @@ namespace Fs::Fat32 {
uint32_t startClusterIdx = (uint32_t)(offset / clusterSize);
uint32_t clusterOff = (uint32_t)(offset % clusterSize);
// Walk cluster chain to starting cluster
uint32_t cluster = file.firstCluster;
for (uint32_t i = 0; i < startClusterIdx; i++) {
// Walk cluster chain to starting cluster (use cache if possible)
uint32_t cluster;
uint32_t startFrom = 0;
if (file.cachedCluster >= 2 && file.cachedClusterIdx <= startClusterIdx) {
cluster = file.cachedCluster;
startFrom = file.cachedClusterIdx;
} else {
cluster = file.firstCluster;
}
for (uint32_t i = startFrom; i < startClusterIdx; i++) {
cluster = GetNextCluster(self, cluster);
if (IsEndOfChain(cluster)) return 0;
}
// Read data cluster by cluster
uint64_t bytesRead = 0;
uint32_t lastCluster = cluster;
while (bytesRead < size && !IsEndOfChain(cluster)) {
if (!ReadCluster(self, cluster)) break;
@@ -851,9 +864,17 @@ namespace Fs::Fat32 {
bytesRead += toRead;
clusterOff = 0; // subsequent clusters start from offset 0
lastCluster = cluster;
cluster = GetNextCluster(self, cluster);
}
// Update cache
if (bytesRead > 0 && lastCluster >= 2) {
uint32_t endClusterIdx = (uint32_t)((offset + bytesRead - 1) / clusterSize);
file.cachedClusterIdx = endClusterIdx;
file.cachedCluster = lastCluster;
}
return (int)bytesRead;
}
@@ -928,9 +949,18 @@ namespace Fs::Fat32 {
uint32_t clusterIdx = (uint32_t)(offset / clusterSize);
uint32_t clusterOff = (uint32_t)(offset % clusterSize);
uint32_t cluster = file.firstCluster;
// Use cached position if we can skip ahead
uint32_t cluster;
uint32_t prevCluster = 0;
for (uint32_t i = 0; i < clusterIdx; i++) {
uint32_t startIdx = 0;
if (file.cachedCluster >= 2 && file.cachedClusterIdx <= clusterIdx) {
cluster = file.cachedCluster;
startIdx = file.cachedClusterIdx;
} else {
cluster = file.firstCluster;
}
for (uint32_t i = startIdx; i < clusterIdx; i++) {
prevCluster = cluster;
uint32_t next = GetNextCluster(self, cluster);
if (IsEndOfChain(next)) {
@@ -977,6 +1007,13 @@ namespace Fs::Fat32 {
}
done:
// Update cached position for sequential access
if (bytesWritten > 0 && prevCluster >= 2) {
uint32_t endClusterIdx = (uint32_t)((offset + bytesWritten - 1) / clusterSize);
file.cachedClusterIdx = endClusterIdx;
file.cachedCluster = prevCluster;
}
// Update file size if we wrote past the end
uint64_t endPos = offset + bytesWritten;
if (endPos > file.fileSize) {
@@ -1038,6 +1075,8 @@ namespace Fs::Fat32 {
self.files[i].isDirectory = false;
self.files[i].sfnPartSector = existing.sfnPartSector;
self.files[i].sfnOffInSector = existing.sfnOffInSector;
self.files[i].cachedClusterIdx = 0;
self.files[i].cachedCluster = 0;
return i;
}
}
@@ -1124,6 +1163,8 @@ namespace Fs::Fat32 {
self.files[i].isDirectory = false;
self.files[i].sfnPartSector = sfnSector;
self.files[i].sfnOffInSector = sfnOff;
self.files[i].cachedClusterIdx = 0;
self.files[i].cachedCluster = 0;
return i;
}
}
@@ -1258,6 +1299,136 @@ namespace Fs::Fat32 {
return 0;
}
// =========================================================================
// Mkdir — create a directory
// =========================================================================
static int MkdirImpl(int inst, const char* path) {
if (inst < 0 || inst >= g_instanceCount || !g_instances[inst].active) return -1;
auto& self = g_instances[inst];
// Split path into parent directory and new dir name
char parentPath[MaxNameLen];
char dirName[MaxNameLen];
SplitPath(path, parentPath, MaxNameLen, dirName, MaxNameLen);
if (dirName[0] == '\0') return -1;
// Traverse to parent directory
ParsedEntry parentEntry;
if (!TraversePath(inst, parentPath, &parentEntry)) return -1;
if (!(parentEntry.attributes & ATTR_DIRECTORY)) return -1;
uint32_t parentCluster = parentEntry.firstCluster;
// If directory already exists, return success
ParsedEntry existing;
if (FindInDirectory(inst, parentCluster, dirName, &existing)) {
if (existing.attributes & ATTR_DIRECTORY) return 0;
return -1; // exists as a file
}
// Allocate a cluster for the new directory
uint32_t newCluster = AllocateCluster(self);
if (newCluster == 0) return -1;
// Initialize the new directory cluster with . and .. entries
memset(self.clusterBuf, 0, self.clusterSize);
// "." entry — points to itself
uint8_t* dot = self.clusterBuf;
memset(dot, ' ', 11);
dot[0] = '.';
dot[11] = ATTR_DIRECTORY;
uint16_t clHi = (uint16_t)(newCluster >> 16);
uint16_t clLo = (uint16_t)(newCluster & 0xFFFF);
memcpy(dot + 20, &clHi, 2);
memcpy(dot + 26, &clLo, 2);
// ".." entry — points to parent
uint8_t* dotdot = self.clusterBuf + 32;
memset(dotdot, ' ', 11);
dotdot[0] = '.'; dotdot[1] = '.';
dotdot[11] = ATTR_DIRECTORY;
uint32_t parentCl = (parentCluster == self.rootCluster) ? 0 : parentCluster;
clHi = (uint16_t)(parentCl >> 16);
clLo = (uint16_t)(parentCl & 0xFFFF);
memcpy(dotdot + 20, &clHi, 2);
memcpy(dotdot + 26, &clLo, 2);
if (!WriteClusterData(self, newCluster, self.clusterBuf)) return -1;
// Generate 8.3 short name for the directory
char shortName[11];
GenerateShortName(dirName, shortName);
MakeShortNameUnique(inst, parentCluster, shortName);
// Build LFN entries if needed
uint8_t lfnEntries[20 * 32];
int lfnCount = 0;
bool needsLfn = NeedsLfn(dirName, shortName);
if (needsLfn) {
uint8_t checksum = LfnChecksum(shortName);
lfnCount = BuildLfnEntries(dirName, checksum, lfnEntries);
}
int totalSlots = lfnCount + 1;
// Find free directory slots in parent
DirSlotPos pos = FindFreeDirSlots(inst, parentCluster, totalSlots);
if (!pos.found) return -1;
// Build all entries (LFN + SFN)
uint8_t allEntries[21 * 32];
if (lfnCount > 0) {
memcpy(allEntries, lfnEntries, lfnCount * 32);
}
// Build the SFN entry with ATTR_DIRECTORY
uint8_t* sfn = allEntries + lfnCount * 32;
memset(sfn, 0, 32);
memcpy(sfn, shortName, 11);
sfn[11] = ATTR_DIRECTORY;
clHi = (uint16_t)(newCluster >> 16);
clLo = (uint16_t)(newCluster & 0xFFFF);
memcpy(sfn + 20, &clHi, 2);
memcpy(sfn + 26, &clLo, 2);
// Directory size field is 0 per FAT spec
// Write entries to disk
uint64_t curSector = pos.partSector;
uint32_t curOff = pos.offsetInSec;
uint8_t sectorBuf[512];
if (!ReadPartSectors(self, curSector, 1, sectorBuf)) return -1;
int bytesTotal = totalSlots * 32;
int written = 0;
while (written < bytesTotal) {
int space = (int)self.bytesPerSector - (int)curOff;
int chunk = bytesTotal - written;
if (chunk > space) chunk = space;
memcpy(sectorBuf + curOff, allEntries + written, chunk);
written += chunk;
if (written < bytesTotal || chunk == space) {
if (!WritePartSectors(self, curSector, 1, sectorBuf)) return -1;
if (written < bytesTotal) {
curSector++;
curOff = 0;
if (!ReadPartSectors(self, curSector, 1, sectorBuf)) return -1;
}
} else {
if (!WritePartSectors(self, curSector, 1, sectorBuf)) return -1;
}
}
return 0;
}
// =========================================================================
// Template thunks — generate unique function pointers per instance
// =========================================================================
@@ -1271,6 +1442,7 @@ namespace Fs::Fat32 {
static int Write(int h, const uint8_t* b, uint64_t o, uint64_t s) { return WriteImpl(N, h, b, o, s); }
static int Create(const char* p) { return CreateImpl(N, p); }
static int Delete(const char* p) { return DeleteImpl(N, p); }
static int Mkdir(const char* p) { return MkdirImpl(N, p); }
};
template<int N>
@@ -1284,6 +1456,7 @@ namespace Fs::Fat32 {
Thunks<N>::Write,
Thunks<N>::Create,
Thunks<N>::Delete,
Thunks<N>::Mkdir,
};
}
+21
View File
@@ -176,6 +176,27 @@ namespace Fs::Vfs {
return driveTable[drive]->Delete(localPath);
}
int VfsMkdir(const char* path) {
int drive;
const char* localPath;
if (!ParsePath(path, drive, localPath)) return -1;
if (drive < 0 || drive >= MaxDrives || driveTable[drive] == nullptr) return -1;
if (driveTable[drive]->Mkdir == nullptr) return -1;
return driveTable[drive]->Mkdir(localPath);
}
int VfsDriveList(int* outDrives, int maxEntries) {
int count = 0;
for (int i = 0; i < MaxDrives && count < maxEntries; i++) {
if (driveTable[i] != nullptr) {
outDrives[count++] = i;
}
}
return count;
}
int VfsReadDir(const char* path, const char** outNames, int maxEntries) {
int drive;
const char* localPath;
+5
View File
@@ -22,6 +22,7 @@ namespace Fs::Vfs {
int (*Write)(int handle, const uint8_t* buffer, uint64_t offset, uint64_t size);
int (*Create)(const char* path);
int (*Delete)(const char* path);
int (*Mkdir)(const char* path);
};
void Initialize();
@@ -35,5 +36,9 @@ namespace Fs::Vfs {
uint64_t VfsGetSize(int handle);
void VfsClose(int handle);
int VfsReadDir(const char* path, const char** outNames, int maxEntries);
int VfsMkdir(const char* path);
// Returns number of registered drives, fills outDrives[] with their indices
int VfsDriveList(int* outDrives, int maxEntries);
}
+20 -14
View File
@@ -164,6 +164,7 @@ extern "C" void kmain() {
Efi::Init(ST, efi_memmap_request.response);
// Initialize ramdisk from Limine modules
bool hasRamdisk = false;
if (module_request.response != nullptr && module_request.response->module_count > 0) {
Kt::KernelLogStream(OK, "Modules") << "Found " << (uint64_t)module_request.response->module_count << " module(s)";
for (uint64_t i = 0; i < module_request.response->module_count; i++) {
@@ -177,30 +178,35 @@ extern "C" void kmain() {
modString[6] == 'k' && modString[7] == '\0') {
Kt::KernelLogStream(OK, "Modules") << "Ramdisk module at " << kcp::hex << (uint64_t)mod->address << kcp::dec << ", size=" << mod->size;
Fs::Ramdisk::Initialize(mod->address, mod->size);
hasRamdisk = true;
}
}
} else {
Kt::KernelLogStream(WARNING, "Modules") << "No modules loaded (ramdisk unavailable)";
}
// Initialize VFS and register ramdisk as drive 0
// Initialize VFS and register ramdisk as drive 0 only if present
Fs::Vfs::Initialize();
static Fs::Vfs::FsDriver ramdiskDriver = {
Fs::Ramdisk::Open,
Fs::Ramdisk::Read,
Fs::Ramdisk::GetSize,
Fs::Ramdisk::Close,
Fs::Ramdisk::ReadDir,
Fs::Ramdisk::Write,
Fs::Ramdisk::Create,
nullptr
};
Fs::Vfs::RegisterDrive(0, &ramdiskDriver);
if (hasRamdisk) {
static Fs::Vfs::FsDriver ramdiskDriver = {
Fs::Ramdisk::Open,
Fs::Ramdisk::Read,
Fs::Ramdisk::GetSize,
Fs::Ramdisk::Close,
Fs::Ramdisk::ReadDir,
Fs::Ramdisk::Write,
Fs::Ramdisk::Create,
nullptr,
nullptr
};
Fs::Vfs::RegisterDrive(0, &ramdiskDriver);
}
// Register filesystem probes and auto-mount partitions
// Register filesystem probes and auto-mount partitions.
// When no ramdisk, disk partitions start at drive 0 so init.elf is found there.
Fs::Fat32::RegisterProbe();
Fs::FsProbe::MountPartitions();
Fs::FsProbe::MountPartitions(hasRamdisk ? 1 : 0);
Hal::LoadTSS();
Montauk::InitializeSyscalls();
+5 -5
View File
@@ -8,11 +8,11 @@
namespace Net {
// QEMU user-mode networking defaults
static uint32_t g_ipAddress = Ipv4Addr(10, 0, 68, 99);
static uint32_t g_subnetMask = Ipv4Addr(255, 255, 255, 0);
static uint32_t g_gateway = Ipv4Addr(10, 0, 68, 1);
static uint32_t g_dnsServer = Ipv4Addr(10, 0, 68, 1);
// Unconfigured until usermode DHCP sets them
static uint32_t g_ipAddress = 0;
static uint32_t g_subnetMask = 0;
static uint32_t g_gateway = 0;
static uint32_t g_dnsServer = 0;
uint32_t GetIpAddress() { return g_ipAddress; }
void SetIpAddress(uint32_t ip) { g_ipAddress = ip; }