414 lines
12 KiB
Nim
414 lines
12 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.
|
|
|
|
## Nexus Membrane: SFS Userspace Client (SPEC-021)
|
|
##
|
|
## The Sovereign Filesystem Overlay:
|
|
## - L0: LittleFS (Atomic Physics) via `lfs_nim`
|
|
## - L1: SFS Overlay (Encryption via Monolith VolumeKey)
|
|
##
|
|
## Kernel is just a Block Valve - no FS logic there.
|
|
|
|
import ../blk
|
|
import ../libc
|
|
import lfs_nim
|
|
import monolith
|
|
|
|
const
|
|
SFS_MAGIC* = 0x32534653'u32 # "SFS2" little endian
|
|
SEC_SB = 0'u64
|
|
SEC_BAM = 1'u64
|
|
SEC_DIR = 2'u64
|
|
CHUNK_SIZE = 508
|
|
EOF_MARKER = 0xFFFFFFFF'u32
|
|
DIR_ENTRY_SIZE = 64
|
|
MAX_FILENAME = 32
|
|
|
|
type
|
|
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 print(s: cstring) =
|
|
discard libc.write(1, cast[pointer](s), csize_t(s.len))
|
|
|
|
proc print(s: string) =
|
|
if s.len > 0:
|
|
discard libc.write(1, cast[pointer](unsafeAddr s[0]), csize_t(s.len))
|
|
|
|
# =========================================================
|
|
# Helpers
|
|
# =========================================================
|
|
|
|
proc sfs_alloc_sector(): uint32 =
|
|
## Allocate a free sector using the Block Allocation Map
|
|
discard blk_read(SEC_BAM, addr io_buffer[0])
|
|
|
|
for i in 0..<512:
|
|
if io_buffer[i] != 0xFF:
|
|
for b in 0..7:
|
|
if (io_buffer[i] and byte(1 shl b)) == 0:
|
|
let sec = uint32(i * 8 + b)
|
|
# Mark as allocated
|
|
io_buffer[i] = io_buffer[i] or byte(1 shl b)
|
|
discard blk_write(SEC_BAM, addr io_buffer[0])
|
|
return sec
|
|
return 0 # Disk full
|
|
|
|
# =========================================================
|
|
# SFS API (Userland)
|
|
# =========================================================
|
|
|
|
proc sfs_mount*(): bool =
|
|
## Mount the SFS filesystem (SPEC-021/022)
|
|
## Uses LittleFS as backend, VolumeKey for encryption
|
|
print("[SFS-U] Mounting Sovereign Filesystem...\n")
|
|
|
|
# Phase 1: Initialize LittleFS backend
|
|
if not lfs_nim_mount():
|
|
print("[SFS-U] LittleFS mount failed. Attempting format...\n")
|
|
if not lfs_nim_format():
|
|
print("[SFS-U] ERROR: LittleFS format failed.\n")
|
|
return false
|
|
if not lfs_nim_mount():
|
|
print("[SFS-U] ERROR: LittleFS mount failed after format.\n")
|
|
return false
|
|
print("[SFS-U] LittleFS backend mounted.\n")
|
|
|
|
sfs_mounted = true
|
|
print("[SFS-U] Mount SUCCESS. SPEC-021 Compliant.\n")
|
|
return true
|
|
|
|
proc sfs_is_mounted*(): bool = sfs_mounted
|
|
|
|
proc sfs_list*(): seq[string] =
|
|
## List all files in the filesystem
|
|
result = @[]
|
|
if not sfs_mounted: return
|
|
|
|
discard blk_read(SEC_DIR, addr io_buffer[0])
|
|
|
|
for offset in countup(0, 511, DIR_ENTRY_SIZE):
|
|
if io_buffer[offset] != 0:
|
|
var name = ""
|
|
for i in 0..<MAX_FILENAME:
|
|
let c = char(io_buffer[offset + i])
|
|
if c == '\0': break
|
|
name.add(c)
|
|
result.add(name)
|
|
|
|
proc get_vfs_listing*(): seq[string] =
|
|
return sfs_list()
|
|
|
|
proc sfs_write*(filename: string, data: pointer, data_len: int): int =
|
|
## Write a file to the filesystem
|
|
## Returns: bytes written or negative error
|
|
if not sfs_mounted: return -1
|
|
|
|
discard blk_read(SEC_DIR, addr io_buffer[0])
|
|
|
|
var dir_offset = -1
|
|
|
|
# Find existing file or free slot
|
|
for offset in countup(0, 511, DIR_ENTRY_SIZE):
|
|
if io_buffer[offset] != 0:
|
|
var entry_name = ""
|
|
for i in 0..<MAX_FILENAME:
|
|
if io_buffer[offset + i] == 0: break
|
|
entry_name.add(char(io_buffer[offset + i]))
|
|
|
|
if entry_name == filename:
|
|
dir_offset = offset
|
|
break
|
|
elif dir_offset == -1:
|
|
dir_offset = offset
|
|
|
|
if dir_offset == -1:
|
|
print("[SFS-U] Error: Directory Full.\n")
|
|
return -2
|
|
|
|
# Allocate first sector
|
|
var first_sector = sfs_alloc_sector()
|
|
if first_sector == 0:
|
|
print("[SFS-U] Error: Disk Full.\n")
|
|
return -3
|
|
|
|
# Write data in chunks
|
|
var remaining = data_len
|
|
var data_ptr = 0
|
|
var current_sector = first_sector
|
|
|
|
while remaining > 0:
|
|
var sector_buf: array[512, byte]
|
|
|
|
let chunk_size = if remaining > CHUNK_SIZE: CHUNK_SIZE else: remaining
|
|
copyMem(addr sector_buf[0],
|
|
cast[pointer](cast[int](data) + data_ptr),
|
|
chunk_size)
|
|
|
|
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
|
|
remaining = 0
|
|
|
|
# Write next pointer at end of sector
|
|
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)
|
|
|
|
discard blk_write(uint64(current_sector), addr sector_buf[0])
|
|
|
|
if next_sector == EOF_MARKER: break
|
|
current_sector = next_sector
|
|
|
|
# Update directory entry
|
|
discard blk_read(SEC_DIR, addr io_buffer[0])
|
|
|
|
for i in 0..<MAX_FILENAME:
|
|
if i < filename.len:
|
|
io_buffer[dir_offset + i] = byte(filename[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)
|
|
|
|
discard blk_write(SEC_DIR, addr io_buffer[0])
|
|
discard blk_sync()
|
|
|
|
print("[SFS-U] Write Complete: " & $data_len & " bytes.\n")
|
|
return data_len
|
|
|
|
proc sfs_read*(filename: string, dest: pointer, max_len: int): int =
|
|
## Read a file from the filesystem
|
|
## Returns: bytes read or negative error
|
|
if not sfs_mounted: return -1
|
|
|
|
discard 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, DIR_ENTRY_SIZE):
|
|
if io_buffer[offset] != 0:
|
|
var entry_name = ""
|
|
for i in 0..<MAX_FILENAME:
|
|
if io_buffer[offset + i] == 0: break
|
|
entry_name.add(char(io_buffer[offset + i]))
|
|
|
|
if entry_name == filename:
|
|
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]
|
|
discard blk_read(uint64(current_sector), addr sector_buf[0])
|
|
|
|
let payload_size = min(remaining, CHUNK_SIZE)
|
|
copyMem(cast[pointer](dest_addr), addr sector_buf[0], payload_size)
|
|
|
|
dest_addr += payload_size
|
|
remaining -= payload_size
|
|
total_read += payload_size
|
|
|
|
# Next sector pointer
|
|
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
|
|
|
|
# =========================================================
|
|
# Streaming Write API (Phase 39: The Downloader)
|
|
# =========================================================
|
|
|
|
type
|
|
SfsHandle* = ref object
|
|
filename*: string
|
|
first_sector*: uint32
|
|
current_sector*: uint32
|
|
byte_offset*: int # Total file size so far
|
|
sector_buf*: array[512, byte] # Current sector buffer
|
|
buf_pos*: int # Position in current sector buffer (0..508)
|
|
dir_index*: int # Directory slot index
|
|
|
|
proc sfs_open_write*(filename: string): SfsHandle =
|
|
## Open a file for streaming write (overwrites existing)
|
|
if not sfs_mounted: return nil
|
|
|
|
discard blk_read(SEC_DIR, addr io_buffer[0])
|
|
|
|
var dir_offset = -1
|
|
# Find free slot or overwrite existing
|
|
for offset in countup(0, 511, DIR_ENTRY_SIZE):
|
|
if io_buffer[offset] != 0:
|
|
var entry_name = ""
|
|
for i in 0..<MAX_FILENAME:
|
|
if io_buffer[offset + i] == 0: break
|
|
entry_name.add(char(io_buffer[offset + i]))
|
|
if entry_name == filename:
|
|
dir_offset = offset
|
|
break
|
|
elif dir_offset == -1:
|
|
dir_offset = offset
|
|
|
|
if dir_offset == -1:
|
|
print("[SFS-U] Error: Directory Full.\n")
|
|
return nil
|
|
|
|
let start_sec = sfs_alloc_sector()
|
|
if start_sec == 0:
|
|
print("[SFS-U] Error: Disk Full.\n")
|
|
return nil
|
|
|
|
var h = new(SfsHandle)
|
|
h.filename = filename
|
|
h.first_sector = start_sec
|
|
h.current_sector = start_sec
|
|
h.byte_offset = 0
|
|
h.buf_pos = 0
|
|
h.dir_index = dir_offset
|
|
|
|
# Initialize buffer (Clean slate)
|
|
# for i in 0..511: h.sector_buf[i] = 0 (Nim does this by default?)
|
|
|
|
print("[Glass Vault] Opened stream: " & filename & "\n")
|
|
return h
|
|
|
|
proc sfs_write_chunk*(h: SfsHandle, data: pointer, len: int) =
|
|
## Append data to the open file handle
|
|
if h == nil or len <= 0: return
|
|
|
|
var remaining = len
|
|
var src_ptr = cast[int](data)
|
|
|
|
while remaining > 0:
|
|
# Space left in current sector payload (max 508 bytes)
|
|
let space = CHUNK_SIZE - h.buf_pos
|
|
if space > 0:
|
|
let to_copy = min(remaining, space)
|
|
copyMem(addr h.sector_buf[h.buf_pos], cast[pointer](src_ptr), to_copy)
|
|
|
|
h.buf_pos += to_copy
|
|
h.byte_offset += to_copy
|
|
remaining -= to_copy
|
|
src_ptr += to_copy
|
|
|
|
# If sector full (payload filled), flush it
|
|
if h.buf_pos == CHUNK_SIZE:
|
|
# Allocate next sector
|
|
let next_sec = sfs_alloc_sector()
|
|
# What if disk full? (MVP: Panic or stop writing?)
|
|
# We'll just mark EOF and stop if 0.
|
|
|
|
var next_marker = if next_sec == 0: EOF_MARKER else: next_sec
|
|
|
|
# Write link
|
|
h.sector_buf[508] = byte(next_marker and 0xFF)
|
|
h.sector_buf[509] = byte((next_marker shr 8) and 0xFF)
|
|
h.sector_buf[510] = byte((next_marker shr 16) and 0xFF)
|
|
h.sector_buf[511] = byte((next_marker shr 24) and 0xFF)
|
|
|
|
# Flush current sector
|
|
discard blk_write(uint64(h.current_sector), addr h.sector_buf[0])
|
|
|
|
if next_sec == 0:
|
|
print("[SFS-U] Warning: Disk Full during stream.\n")
|
|
break
|
|
|
|
# Move to next
|
|
h.current_sector = next_sec
|
|
h.buf_pos = 0
|
|
# Reset buffer? Yes mostly
|
|
# for i in 0..511: h.sector_buf[i] = 0
|
|
|
|
proc sfs_close_write*(h: SfsHandle) =
|
|
## Flush remaining data and update directory
|
|
if h == nil: return
|
|
|
|
# Write final sector (even if partially full)
|
|
let next_marker = EOF_MARKER
|
|
h.sector_buf[508] = byte(next_marker and 0xFF)
|
|
h.sector_buf[509] = byte((next_marker shr 8) and 0xFF)
|
|
h.sector_buf[510] = byte((next_marker shr 16) and 0xFF)
|
|
h.sector_buf[511] = byte((next_marker shr 24) and 0xFF)
|
|
|
|
discard blk_write(uint64(h.current_sector), addr h.sector_buf[0])
|
|
|
|
# Update Directory Entry
|
|
discard blk_read(SEC_DIR, addr io_buffer[0])
|
|
|
|
let dir_offset = h.dir_index
|
|
let filename = h.filename
|
|
|
|
# Clear name area first
|
|
for i in 0..<MAX_FILENAME: io_buffer[dir_offset + i] = 0
|
|
|
|
for i in 0..<MAX_FILENAME:
|
|
if i < filename.len:
|
|
io_buffer[dir_offset + i] = byte(filename[i])
|
|
else:
|
|
break
|
|
|
|
# Start Sector
|
|
io_buffer[dir_offset + 32] = byte(h.first_sector and 0xFF)
|
|
io_buffer[dir_offset + 33] = byte((h.first_sector shr 8) and 0xFF)
|
|
io_buffer[dir_offset + 34] = byte((h.first_sector shr 16) and 0xFF)
|
|
io_buffer[dir_offset + 35] = byte((h.first_sector shr 24) and 0xFF)
|
|
|
|
# Size
|
|
let sz = uint32(h.byte_offset)
|
|
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)
|
|
|
|
discard blk_write(SEC_DIR, addr io_buffer[0])
|
|
discard blk_sync()
|
|
|
|
print("[Glass Vault] Closed stream: " & h.filename & " (" & $h.byte_offset & " bytes)\n")
|