221 lines
8.6 KiB
C++
221 lines
8.6 KiB
C++
/*
|
|
* 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 <Fs/Vfs.hpp>
|
|
#include <Fs/FsProbe.hpp>
|
|
#include <Drivers/Storage/BlockDevice.hpp>
|
|
#include <Sched/Scheduler.hpp>
|
|
#include <Memory/PageFrameAllocator.hpp>
|
|
#include <Memory/HHDM.hpp>
|
|
#include <Memory/Paging.hpp>
|
|
#include <Libraries/Memory.hpp>
|
|
#include <Ipc/Ipc.hpp>
|
|
#include <Timekeeping/Time.hpp>
|
|
#include "Path.hpp"
|
|
#include <Fs/ProtectedPaths.hpp>
|
|
|
|
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;
|
|
}
|
|
};
|