340 lines
10 KiB
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))
|