rumpk/core/fs/vfs.nim

228 lines
7.4 KiB
Nim

# SPDX-License-Identifier: LSL-1.0
# Copyright (c) 2026 Markus Maiwald
# Stewardship: Self Sovereign Society Foundation
#
# This file is part of the Nexus Sovereign Core.
# See legal/LICENSE_SOVEREIGN.md for license terms.
## Rumpk Layer 1: Sovereign VFS (The Loom)
##
## Freestanding implementation (No OS module dependencies).
## Uses fixed-size arrays for descriptors to ensure deterministic latency.
import tar, sfs, lfs_bridge
type
VFSMode = enum
MODE_TAR, MODE_SFS, MODE_RAM, MODE_TTY, MODE_LFS
MountPoint = object
prefix: array[32, char]
mode: VFSMode
FileHandle = object
path: array[64, char]
offset: uint64
mode: VFSMode
active: bool
lfs_handle: int32 ## LFS file handle (-1 = not open)
const MAX_MOUNTS = 8
const MAX_FDS = 32
var mnt_table: array[MAX_MOUNTS, MountPoint]
var mnt_count: int = 0
var fd_table: array[MAX_FDS, FileHandle]
# Helper: manual string compare
proc vfs_starts_with(s, prefix: cstring): bool =
let ps = cast[ptr UncheckedArray[char]](s)
let pp = cast[ptr UncheckedArray[char]](prefix)
var i = 0
while pp[i] != '\0':
if ps[i] != pp[i]: return false
i += 1
return true
proc vfs_streq(s1, s2: cstring): bool =
let p1 = cast[ptr UncheckedArray[char]](s1)
let p2 = cast[ptr UncheckedArray[char]](s2)
var i = 0
while true:
if p1[i] != p2[i]: return false
if p1[i] == '\0': return true
i += 1
proc vfs_add_mount(prefix: cstring, mode: VFSMode) =
if mnt_count >= MAX_MOUNTS: return
let p = cast[ptr UncheckedArray[char]](prefix)
var i = 0
while p[i] != '\0' and i < 31:
mnt_table[mnt_count].prefix[i] = p[i]
i += 1
mnt_table[mnt_count].prefix[i] = '\0'
mnt_table[mnt_count].mode = mode
mnt_count += 1
proc kprintln(s: cstring) {.importc, cdecl.}
proc vfs_mount_init*() =
# Mount LittleFS for /nexus (persistent sovereign storage)
if lfs_bridge.lfs_mount_fs():
vfs_add_mount("/nexus", MODE_LFS)
else:
# Fallback to SFS if LittleFS mount fails (no block device?)
kprintln("[VFS] LFS mount failed, falling back to SFS for /nexus")
vfs_add_mount("/nexus", MODE_SFS)
vfs_add_mount("/sysro", MODE_TAR)
vfs_add_mount("/state", MODE_RAM)
vfs_add_mount("/dev/tty", MODE_TTY)
vfs_add_mount("/Bus/Console/tty0", MODE_TTY)
proc resolve_path(path: cstring): (VFSMode, int) =
for i in 0..<mnt_count:
let prefix = cast[cstring](addr mnt_table[i].prefix[0])
if vfs_starts_with(path, prefix):
var len = 0
while mnt_table[i].prefix[len] != '\0': len += 1
return (mnt_table[i].mode, len)
return (MODE_TAR, 0)
proc ion_vfs_open*(path: cstring, flags: int32): int32 {.exportc, cdecl.} =
let (mode, prefix_len) = resolve_path(path)
# Delegate internal open
let sub_path = cast[cstring](cast[uint64](path) + uint64(prefix_len))
var internal_fd: int32 = -1
# Map VFS flags to LFS flags for MODE_LFS
var lfs_h: int32 = -1
case mode:
of MODE_TAR, MODE_RAM: internal_fd = tar.vfs_open(sub_path, flags)
of MODE_SFS: internal_fd = 0 # Shim
of MODE_LFS:
# Convert POSIX-ish flags to LFS flags
var lfs_flags = lfs_bridge.LFS_O_RDONLY
if (flags and 3) == 1: lfs_flags = lfs_bridge.LFS_O_WRONLY
elif (flags and 3) == 2: lfs_flags = lfs_bridge.LFS_O_RDWR
if (flags and 0x40) != 0: lfs_flags = lfs_flags or lfs_bridge.LFS_O_CREAT # O_CREAT
if (flags and 0x200) != 0: lfs_flags = lfs_flags or lfs_bridge.LFS_O_TRUNC # O_TRUNC
if (flags and 0x400) != 0: lfs_flags = lfs_flags or lfs_bridge.LFS_O_APPEND # O_APPEND
lfs_h = lfs_bridge.lfs_open_file(sub_path, lfs_flags)
if lfs_h >= 0: internal_fd = 0
of MODE_TTY: internal_fd = 1 # Shim
if internal_fd >= 0:
for i in 0..<MAX_FDS:
if not fd_table[i].active:
fd_table[i].active = true
fd_table[i].mode = mode
fd_table[i].offset = 0
fd_table[i].lfs_handle = lfs_h
let p = cast[ptr UncheckedArray[char]](sub_path)
var j = 0
while p[j] != '\0' and j < 63:
fd_table[i].path[j] = p[j]
j += 1
fd_table[i].path[j] = '\0'
return int32(i + 3) # FDs start at 3
# No free slot — close LFS handle if we opened one
if lfs_h >= 0: discard lfs_bridge.lfs_close_file(lfs_h)
return -1
proc ion_vfs_read*(fd: int32, buf: pointer, count: uint64): int64 {.exportc, cdecl.} =
let idx = int(fd - 3)
if idx < 0 or idx >= MAX_FDS or not fd_table[idx].active: return -1
let fh = addr fd_table[idx]
case fh.mode:
of MODE_TTY: return -2
of MODE_TAR, MODE_RAM:
let path = cast[cstring](addr fh.path[0])
let n = tar.vfs_read_at(path, buf, count, fh.offset)
if n > 0: fh.offset += uint64(n)
return n
of MODE_SFS:
let path = cast[cstring](addr fh.path[0])
var temp: array[256, byte] # Small shim
let n = sfs.sfs_read_file(path, addr temp[0], 256)
if n <= 0: return -1
let avail = uint64(n) - fh.offset
let actual = if count < avail: count else: avail
if actual > 0:
copyMem(buf, addr temp[int(fh.offset)], int(actual))
fh.offset += actual
return int64(actual)
return 0
of MODE_LFS:
if fh.lfs_handle < 0: return -1
let n = lfs_bridge.lfs_read_file(fh.lfs_handle, buf, uint32(count))
if n > 0: fh.offset += uint64(n)
return int64(n)
proc ion_vfs_write*(fd: int32, buf: pointer, count: uint64): int64 {.exportc, cdecl.} =
let idx = int(fd - 3)
if idx < 0 or idx >= MAX_FDS or not fd_table[idx].active: return -1
let fh = addr fd_table[idx]
case fh.mode:
of MODE_TTY: return -2
of MODE_TAR, MODE_RAM:
let path = cast[cstring](addr fh.path[0])
let n = tar.vfs_write_at(path, buf, count, fh.offset)
if n > 0: fh.offset += uint64(n)
return n
of MODE_SFS:
let path = cast[cstring](addr fh.path[0])
sfs.sfs_write_file(path, buf, int(count))
return int64(count)
of MODE_LFS:
if fh.lfs_handle < 0: return -1
let n = lfs_bridge.lfs_write_file(fh.lfs_handle, buf, uint32(count))
if n > 0: fh.offset += uint64(n)
return int64(n)
proc ion_vfs_close*(fd: int32): int32 {.exportc, cdecl.} =
let idx = int(fd - 3)
if idx >= 0 and idx < MAX_FDS:
if fd_table[idx].mode == MODE_LFS and fd_table[idx].lfs_handle >= 0:
discard lfs_bridge.lfs_close_file(fd_table[idx].lfs_handle)
fd_table[idx].lfs_handle = -1
fd_table[idx].active = false
return 0
return -1
proc ion_vfs_dup*(fd: int32, min_fd: int32 = 0): int32 {.exportc, cdecl.} =
let idx = int(fd - 3)
if idx < 0 or idx >= MAX_FDS or not fd_table[idx].active: return -1
# F_DUPFD needs to find first fd >= min_fd
let start_idx = if min_fd > 3: int(min_fd - 3) else: 0
for i in start_idx..<MAX_FDS:
if not fd_table[i].active:
fd_table[i] = fd_table[idx]
return int32(i + 3)
return -1
proc ion_vfs_dup2*(old_fd: int32, new_fd: int32): int32 {.exportc, cdecl.} =
let old_idx = int(old_fd - 3)
let new_idx = int(new_fd - 3)
if old_idx < 0 or old_idx >= MAX_FDS or not fd_table[old_idx].active: return -1
if new_idx < 0 or new_idx >= MAX_FDS: return -1
if old_idx == new_idx: return new_fd
fd_table[new_idx] = fd_table[old_idx]
fd_table[new_idx].active = true
return new_fd
proc ion_vfs_list*(buf: pointer, max_len: uint64): int64 {.exportc, cdecl.} =
# Hardcoded baseline for now to avoid string/os dependency
let msg = "/nexus\n/sysro\n/state\n"
let n = if uint64(msg.len) < max_len: uint64(msg.len) else: max_len
if n > 0: copyMem(buf, unsafeAddr msg[0], int(n))
return int64(n)