rumpk/core/fs/sfs.nim

340 lines
10 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 File System (SFS)
# Markus Maiwald (Architect) | Voxis Forge (AI)
#
# Rumpk Phase 23: The Sovereign Filesystem (SFS) v2
# Features: Multi-Sector Files (Linked List), Block Alloc Map (BAM)
#
# DOCTRINE(SPEC-021):
# This file currently implements the "Physics-Logic Hybrid" for Bootstrapping.
# In Phase 37, this will be deprecated in favor of:
# - L0: LittleFS (Atomic Physics)
# - L1: SFS Overlay Daemon (Sovereign Logic in Userland)
proc kprintln(s: cstring) {.importc, cdecl.}
proc kprint(s: cstring) {.importc, cdecl.}
proc kprint_hex(n: uint64) {.importc, cdecl.}
# =========================================================
# SFS Configurations
# =========================================================
const SFS_MAGIC* = 0x31534653'u32
const
SEC_SB = 0
SEC_BAM = 1
SEC_DIR = 2
# Linked List Payload: 508 bytes data + 4 bytes next_sector
CHUNK_SIZE = 508
EOF_MARKER = 0xFFFFFFFF'u32
type
Superblock* = object
magic*: uint32
disk_size*: uint32
DirEntry* = object
filename*: array[32, char]
start_sector*: uint32
size_bytes*: uint32
reserved*: array[24, byte]
var sfs_mounted: bool = false
var io_buffer: array[512, byte]
proc virtio_blk_read(sector: uint64, buf: pointer) {.importc, cdecl.}
proc virtio_blk_write(sector: uint64, buf: pointer) {.importc, cdecl.}
# =========================================================
# Helpers
# =========================================================
# Removed sfs_set_bam (unused)
proc sfs_alloc_sector(): uint32 =
# Simple allocator: Scan BAM for first 0 bit
virtio_blk_read(SEC_BAM, addr io_buffer[0])
for i in 0..<512:
if io_buffer[i] != 0xFF:
# Found a byte with free space
for b in 0..7:
if (io_buffer[i] and byte(1 shl b)) == 0:
# Found free bit
let sec = uint32(i * 8 + b)
# Mark applied in sfs_set_bam but for efficiency do it here/flush
io_buffer[i] = io_buffer[i] or byte(1 shl b)
virtio_blk_write(SEC_BAM, addr io_buffer[0])
return sec
return 0 # Error / Full
# =========================================================
# SFS API
# =========================================================
proc sfs_is_mounted*(): bool = sfs_mounted
proc sfs_format*() =
kprintln("[SFS] Formatting disk...")
# 1. Clear IO Buffer
for i in 0..511: io_buffer[i] = 0
# 2. Setup Superblock
io_buffer[0] = byte('S')
io_buffer[1] = byte('F')
io_buffer[2] = byte('S')
io_buffer[3] = byte('2')
# Disk size placeholder (32MB = 65536 sectors)
io_buffer[4] = 0x00; io_buffer[5] = 0x00; io_buffer[6] = 0x01; io_buffer[7] = 0x00
virtio_blk_write(SEC_SB, addr io_buffer[0])
# 3. Clear BAM
for i in 0..511: io_buffer[i] = 0
# Mark sectors 0, 1, 2 as used
io_buffer[0] = 0x07
virtio_blk_write(SEC_BAM, addr io_buffer[0])
# 4. Clear Directory
for i in 0..511: io_buffer[i] = 0
virtio_blk_write(SEC_DIR, addr io_buffer[0])
kprintln("[SFS] Format Complete.")
proc sfs_mount*() =
kprintln("[SFS] Mounting System v2...")
# 1. Read Sector 0 (Superblock)
virtio_blk_read(SEC_SB, addr io_buffer[0])
# 2. Check Magic (SFS2)
if io_buffer[0] == byte('S') and io_buffer[1] == byte('F') and
io_buffer[2] == byte('S') and io_buffer[3] == byte('2'):
kprintln("[SFS] Mount SUCCESS. Version 2 (Linked Chain).")
sfs_mounted = true
elif io_buffer[0] == 0 and io_buffer[1] == 0:
kprintln("[SFS] Fresh disk detected.")
sfs_format()
sfs_mounted = true
else:
kprint("[SFS] Mount FAILED. Invalid Magic/Ver. Found: ")
kprint_hex(cast[uint64](io_buffer[0]))
kprintln("")
proc sfs_list*() =
if not sfs_mounted: return
virtio_blk_read(SEC_DIR, addr io_buffer[0])
kprintln("[SFS] Files:")
var offset = 0
while offset < 512:
if io_buffer[offset] != 0:
var name: string = ""
for i in 0..31:
let c = char(io_buffer[offset+i])
if c == '\0': break
name.add(c)
kprint(" - ")
kprintln(cstring(name))
offset += 64
proc sfs_get_files*(): string =
var res = ""
if not sfs_mounted: return res
virtio_blk_read(SEC_DIR, addr io_buffer[0])
for offset in countup(0, 511, 64):
if io_buffer[offset] != 0:
var name = ""
for i in 0..31:
let c = char(io_buffer[offset+i])
if c == '\0': break
name.add(c)
res.add(name)
res.add("\n")
return res
proc sfs_write_file*(name: cstring, data: cstring, data_len: int) {.exportc, cdecl.} =
if not sfs_mounted: return
virtio_blk_read(SEC_DIR, addr io_buffer[0])
var dir_offset = -1
var file_exists = false
# 1. Find File or Free Slot
for offset in countup(0, 511, 64):
if io_buffer[offset] != 0:
var entry_name = ""
for i in 0..31:
if io_buffer[offset+i] == 0: break
entry_name.add(char(io_buffer[offset+i]))
if entry_name == $name:
dir_offset = offset
file_exists = true
# For existing files, efficient rewrite logic is complex (reuse chain vs new).
# V2 Simplification: Just create NEW chain, orphan old one (leak) for now.
# Future: Walk old chain and free in BAM.
break
elif dir_offset == -1:
dir_offset = offset
if dir_offset == -1:
kprintln("[SFS] Error: Directory Full.")
return
# 2. Chunk and Write Data
var remaining = data_len
var data_ptr = 0
var first_sector = 0'u32
var current_sector = 0'u32
# For the first chunk
current_sector = sfs_alloc_sector()
if current_sector == 0:
kprintln("[SFS] Error: Disk Full.")
return
first_sector = current_sector
while remaining > 0:
var sector_buf: array[512, byte]
# Fill Data
let chunk_size = if remaining > CHUNK_SIZE: CHUNK_SIZE else: remaining
for i in 0..<chunk_size:
sector_buf[i] = byte(data[data_ptr + i])
remaining -= chunk_size
data_ptr += chunk_size
# Determine Next Sector
var next_sector = EOF_MARKER
if remaining > 0:
next_sector = sfs_alloc_sector()
if next_sector == 0:
next_sector = EOF_MARKER # Disk full, truncated
remaining = 0
# Write Next Pointer
sector_buf[508] = byte(next_sector and 0xFF)
sector_buf[509] = byte((next_sector shr 8) and 0xFF)
sector_buf[510] = byte((next_sector shr 16) and 0xFF)
sector_buf[511] = byte((next_sector shr 24) and 0xFF)
# Flush Sector
virtio_blk_write(uint64(current_sector), addr sector_buf[0])
current_sector = next_sector
if current_sector == EOF_MARKER: break
# 3. Update Directory Entry
# Need to read Dir again as buffer was used for BAM/Data
virtio_blk_read(SEC_DIR, addr io_buffer[0])
let n_str = $name
for i in 0..31:
if i < n_str.len: io_buffer[dir_offset+i] = byte(n_str[i])
else: io_buffer[dir_offset+i] = 0
io_buffer[dir_offset+32] = byte(first_sector and 0xFF)
io_buffer[dir_offset+33] = byte((first_sector shr 8) and 0xFF)
io_buffer[dir_offset+34] = byte((first_sector shr 16) and 0xFF)
io_buffer[dir_offset+35] = byte((first_sector shr 24) and 0xFF)
let sz = uint32(data_len)
io_buffer[dir_offset+36] = byte(sz and 0xFF)
io_buffer[dir_offset+37] = byte((sz shr 8) and 0xFF)
io_buffer[dir_offset+38] = byte((sz shr 16) and 0xFF)
io_buffer[dir_offset+39] = byte((sz shr 24) and 0xFF)
virtio_blk_write(SEC_DIR, addr io_buffer[0])
kprintln("[SFS] Multi-Sector Write Complete.")
proc sfs_read_file*(name: cstring, dest: pointer, max_len: int): int {.exportc, cdecl.} =
if not sfs_mounted: return -1
virtio_blk_read(SEC_DIR, addr io_buffer[0])
var start_sector = 0'u32
var file_size = 0'u32
var found = false
for offset in countup(0, 511, 64):
if io_buffer[offset] != 0:
var entry_name = ""
for i in 0..31:
if io_buffer[offset+i] == 0: break
entry_name.add(char(io_buffer[offset+i]))
if entry_name == $name:
start_sector = uint32(io_buffer[offset+32]) or
(uint32(io_buffer[offset+33]) shl 8) or
(uint32(io_buffer[offset+34]) shl 16) or
(uint32(io_buffer[offset+35]) shl 24)
file_size = uint32(io_buffer[offset+36]) or
(uint32(io_buffer[offset+37]) shl 8) or
(uint32(io_buffer[offset+38]) shl 16) or
(uint32(io_buffer[offset+39]) shl 24)
found = true
break
if not found: return -1
# Read Chain
var current_sector = start_sector
var dest_addr = cast[int](dest)
var remaining = int(file_size)
if remaining > max_len: remaining = max_len
var total_read = 0
while remaining > 0 and current_sector != EOF_MARKER and current_sector != 0:
var sector_buf: array[512, byte]
virtio_blk_read(uint64(current_sector), addr sector_buf[0])
# Extract Payload
let payload_size = min(remaining, CHUNK_SIZE)
# Be careful not to overflow dest buffer if payload_size > remaining (handled by min)
copyMem(cast[pointer](dest_addr), addr sector_buf[0], payload_size)
dest_addr += payload_size
remaining -= payload_size
total_read += payload_size
# Next Sector
current_sector = uint32(sector_buf[508]) or
(uint32(sector_buf[509]) shl 8) or
(uint32(sector_buf[510]) shl 16) or
(uint32(sector_buf[511]) shl 24)
return total_read
proc vfs_register_sfs(name: string, size: uint64) {.importc, cdecl.}
proc sfs_sync_vfs*() =
if not sfs_mounted: return
virtio_blk_read(SEC_DIR, addr io_buffer[0])
for offset in countup(0, 511, 64):
if io_buffer[offset] != 0:
var name = ""
for i in 0..31:
let c = char(io_buffer[offset+i])
if c == '\0': break
name.add(c)
let f_size = uint32(io_buffer[offset+36]) or
(uint32(io_buffer[offset+37]) shl 8) or
(uint32(io_buffer[offset+38]) shl 16) or
(uint32(io_buffer[offset+39]) shl 24)
vfs_register_sfs(name, uint64(f_size))