/* * Filesystem.hpp * SYS_OPEN, SYS_READ, SYS_GETSIZE, SYS_CLOSE, SYS_READDIR, * SYS_FWRITE, SYS_FCREATE syscalls * Copyright (c) 2026 Daniel Hammer */ #pragma once #include #include #include #include #include #include #include #include #include #include #include "Path.hpp" #include namespace montauk::abi { // Writes refused here return SystemIntegrityError: the path is protected // by Fs/ProtectedPaths, not merely gated on an ordinary capability. static bool CanModifyFilePath(const char* resolved) { uint64_t required = Fs::RequiredFileWriteCapability(resolved); return required == 0 || Sched::HasCapability(required); } // Drivers fail a create under a missing parent with a bare -1; report it // as NoSuchPath so callers can tell it apart (Rust's create_dir_all only // creates the missing ancestors when mkdir says NotFound). static bool ParentDirExists(const char* resolved) { char parent[256]; int len = Lib::strlen(resolved); if (len >= (int)sizeof(parent)) return false; memcpy(parent, resolved, len + 1); while (len > 0 && parent[len - 1] == '/') len--; while (len > 0 && parent[len - 1] != '/') len--; while (len > 0 && parent[len - 1] == '/') len--; if (len == 0 || parent[len - 1] == ':') return true; // drive root parent[len] = '\0'; Fs::Vfs::StatInfo info; return Fs::Vfs::VfsStat(parent, info) == 0 && info.isDir; } static int Sys_Open(const char* path) { char resolved[256]; if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return InvalidArgument; return Ipc::OpenFileHandle(resolved); } static int Sys_Read(int handle, uint8_t* buffer, uint64_t offset, uint64_t size) { return Ipc::FileReadHandle(handle, buffer, offset, size); } static uint64_t Sys_GetSize(int handle) { return Ipc::FileGetSizeHandle(handle); } static void Sys_Close(int handle) { Ipc::CloseHandle(handle); } static int Sys_ReadDirAt(const char* path, const char** outNames, int maxEntries, int startIndex); static int Sys_ReadDir(const char* path, const char** outNames, int maxEntries) { return Sys_ReadDirAt(path, outNames, maxEntries, 0); } static int Sys_ReadDirAt(const char* path, const char** outNames, int maxEntries, int startIndex) { char resolved[256]; if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return InvalidArgument; if (startIndex < 0) return InvalidArgument; // Get entries from VFS into a kernel-local array const char* kernelNames[256]; int max = maxEntries; if (max > 256) max = 256; int count = Fs::Vfs::VfsReadDirAt(resolved, kernelNames, max, startIndex); if (count <= 0) return count; // Allocate a user-accessible page for string data via process heap auto* proc = Sched::GetCurrentProcessPtr(); if (proc == nullptr) return GenericFailure; // Use a rotating ring of scratch pages below the heap instead of // extending the heap high-water mark on every call. This keeps repeated // directory scans from consuming user heap address space while still // allowing nested callers to hold multiple readdir results at once. uint32_t slot = proc->readdirCursor % Sched::UserReadDirSlots; proc->readdirCursor = (slot + 1) % Sched::UserReadDirSlots; uint64_t userVa = Sched::UserReadDirBase + (uint64_t)slot * 0x1000ULL; uint64_t physAddr = Memory::VMM::Paging::GetPhysAddr(proc->pml4Phys, userVa); uint8_t* pageBuf = nullptr; if (physAddr == 0) { void* page = Memory::g_pfa->AllocateZeroed(); if (page == nullptr) return OutOfMemory; physAddr = Memory::SubHHDM((uint64_t)page); if (!Memory::VMM::Paging::MapUserIn(proc->pml4Phys, physAddr, userVa)) { Memory::g_pfa->Free(page); return OutOfMemory; } pageBuf = (uint8_t*)page; } else { pageBuf = (uint8_t*)Memory::HHDM(physAddr); memset(pageBuf, 0, 0x1000); } // Copy strings into the user page and write pointers to outNames uint64_t offset = 0; int copied = 0; for (int i = 0; i < count; i++) { int len = Lib::strlen(kernelNames[i]) + 1; if (offset + len > 0x1000) break; memcpy(pageBuf + offset, kernelNames[i], len); outNames[i] = (const char*)(userVa + offset); offset += len; copied++; } return copied; } static int Sys_FWrite(int handle, const uint8_t* data, uint64_t offset, uint64_t size) { return Ipc::FileWriteHandle(handle, data, offset, size); } static int Sys_FCreate(const char* path) { char resolved[256]; if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return InvalidArgument; if (!CanModifyFilePath(resolved)) return SystemIntegrityError; if (!ParentDirExists(resolved)) return NoSuchPath; return Ipc::CreateFileHandle(resolved); } static int Sys_FDelete(const char* path) { char resolved[256]; if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return InvalidArgument; if (!CanModifyFilePath(resolved)) return SystemIntegrityError; return Fs::Vfs::VfsDelete(resolved); } static int Sys_Stat(const char* path, FileStat* out) { if (out == nullptr) return InvalidArgument; char resolved[256]; if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return InvalidArgument; Fs::Vfs::StatInfo info; if (Fs::Vfs::VfsStat(resolved, info) != 0) return NoSuchPath; out->size = info.size; out->mtime = info.mtime; out->ctime = info.ctime; out->atime = info.atime; out->mode = info.mode; out->isDir = info.isDir ? 1u : 0u; return Success; } static int Sys_Utime(const char* path, int64_t atime, int64_t mtime, bool useCurrent) { char resolved[256]; if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return InvalidArgument; // Timestamps are file state like any other: a protected path must not // be mutable through a side door that skips the write check. if (!CanModifyFilePath(resolved)) return SystemIntegrityError; if (useCurrent) { int64_t now = Timekeeping::GetUnixTimestamp(); atime = now; mtime = now; } // VfsSetTimes has its own codes: -1 not found, -2 unsupported by the // filesystem, -3 timestamp out of range, -4 I/O error. switch (Fs::Vfs::VfsSetTimes(resolved, atime, mtime)) { case 0: return Success; case -2: return UnsupportedOp; case -3: return InvalidArgument; case -4: return IoFailure; default: return NoSuchPath; } } static int Sys_FMkdir(const char* path) { char resolved[256]; if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return InvalidArgument; if (!CanModifyFilePath(resolved)) return SystemIntegrityError; if (!ParentDirExists(resolved)) return NoSuchPath; return Fs::Vfs::VfsMkdir(resolved); } static int Sys_FRename(const char* oldPath, const char* newPath) { char resolvedOld[256]; char resolvedNew[256]; if (!ResolveProcessPath(oldPath, resolvedOld, sizeof(resolvedOld))) return InvalidArgument; if (!ResolveProcessPath(newPath, resolvedNew, sizeof(resolvedNew))) return InvalidArgument; if (!CanModifyFilePath(resolvedOld) || !CanModifyFilePath(resolvedNew)) return SystemIntegrityError; return Fs::Vfs::VfsRename(resolvedOld, resolvedNew); } static int Sys_DriveList(int* outDrives, int maxEntries) { return Fs::Vfs::VfsDriveList(outDrives, maxEntries); } static int Sys_DriveLabel(int driveNumber, char* outLabel, int maxLen) { return Fs::Vfs::VfsDriveLabel(driveNumber, outLabel, maxLen); } static int Sys_DriveKind(int driveNumber) { int blockDev = Fs::FsProbe::GetBlockDeviceForDrive(driveNumber); if (blockDev < 0) return 0; auto* dev = Drivers::Storage::GetBlockDevice(blockDev); return dev ? (int)dev->Kind : 0; } };