blobsets/genode/src/blobsets_fs.nim

395 lines
12 KiB
Nim

import std/asyncdispatch
import std/tables, std/xmltree, std/strtabs, std/strutils, std/streams, std/xmlparser, std/random, std/times
import genode, genode/signals, genode/parents, genode/servers, genode/roms
import blobsets, blobsets/filestores, ./private/filesystemsession, ./private/store_reporter
const
currentPath = currentSourcePath.rsplit("/", 1)[0]
fsComponentH = currentPath & "/private/fs_component.h"
#[
const FsH = "<file_system_session/file_system_session.h>"
# Cannot throw C++ exceptions from within Libc::with_libc
proc raiseInvalidHandle() {.noreturn, header: FsH,
importcpp: "throw File_system::Invalid_handle()".}
proc raiseInvalidName() {.noreturn, header: FsH,
importcpp: "throw File_system::Invalid_name()".}
proc raiseLookupFailed() {.noreturn, header: FsH,
importcpp: "throw File_system::Lookup_failed()".}
proc raisePermissionDenied() {.noreturn, header: FsH,
importcpp: "throw File_system::Permission_denied()".}
template lookupAssert(cond: bool) =
if not cond: raiseLookupFailed()
]#
template assertInvalidHandle(cond: bool) =
if not cond: return (not Handle(0))
template permissionsAssert(cond: bool) =
if not cond: return (not Handle(0))
template validPathAssert(name: string) =
if name[name.low] != '/' or name[name.high] == '/':
return (not Handle(0))
template validNameAssert(name: string) =
if name.contains '/': return (not Handle(0))
type
FsCapability {.
importcpp: "File_system::Session_capability",
header: "<file_system_session/capability.h>".} = object
FsSessionComponentBase {.
importcpp: "File_system::SessionComponentBase", header: fsComponentH.} = object
FsSessionComponent = Constructible[FsSessionComponentBase]
Handle = culong
NodeKind = enum
nodeNode,
dirNode,
fileNode,
Node = object
path: string
case kind: NodeKind
of fileNode:
id: BlobId
size: BiggestInt
stream: BlobStream
else:
discard
SessionPtr = ptr SessionObj
SessionRef = ref SessionObj
SessionObj = object
sig: SignalHandler
cpp: FsSessionComponent
store: BlobStore
label: string
fsSetId: SetId
fsSet: BlobSet
next: Handle
nodes: Table[Handle, Node]
## Read files from the store into this buffer
rng: Rand
Session = ptr SessionObj | ref SessionObj | SessionObj
proc apply(s: Session; path: string; f: proc (id: BlobId; size: BiggestInt)) =
## Apply shortcut
apply(s.store, s.fsSet, path, f)
proc deliverSession*(parent: Parent; id: ServerId; cap: FsCapability) {.
importcpp: "#->deliver_session_cap(Genode::Parent::Server::Id{#}, #)".}
proc packetAvail(cpp: FsSessionComponent): bool {.
importcpp: "#->tx_sink()->packet_avail()".}
proc readyToAck(cpp: FsSessionComponent): bool {.
importcpp: "#->tx_sink()->ready_to_ack()".}
proc popRequest(cpp: FsSessionComponent): FsPacket {.
importcpp: "#->tx_sink()->get_packet()".}
proc packet_content(cpp: FsSessionComponent; pkt: FsPacket): pointer {.
importcpp: "#->tx_sink()->packet_content(@)".}
proc acknowledge(cpp: FsSessionComponent; pkt: FsPacket) {.
importcpp: "#->tx_sink()->acknowledge_packet(@)".}
proc manage(ep: Entrypoint; s: Session): FsCapability =
proc manage(ep: Entrypoint; cpp: FsSessionComponent): FsCapability {.
importcpp: "#.manage(*#)".}
result = ep.manage(s.cpp)
GC_ref(s)
proc dissolve(ep: Entrypoint; s: Session) =
proc dissolve(ep: Entrypoint; cpp: FsSessionComponent) {.
importcpp: "#.dissolve(*#)".}
dissolve s.sig
ep.dissolve(s.cpp)
destruct s.cpp
GC_unref(s)
proc nextId(s: Session): Handle =
result = s.next
inc s.next
proc inode(node: Node): culong = node.path.toKey.culong
## Dummy inode generator.
template fsRpc(session: SessionPtr; body: untyped) =
try: body
#except KeyError:
# raiseLookupFailed()
except:
echo "failed, ", getCurrentExceptionMsg()
proc nodeProc(session: pointer; path: cstring): Handle {.exportc.} =
let session = cast[SessionPtr](session)
fsRpc session:
result = session.nextId
if path == "/":
session.nodes[result] = Node(path: "/", kind: dirNode)
else:
var path = $path
validPathAssert path
path = path.strip(chars={'/'})
session.nodes[result] = Node(path: path, kind: nodeNode)
type Status {.importcpp: "File_system::Status", pure.} = object
size {.importcpp.}: culonglong
mode {.importcpp.}: cuint
inode {.importcpp.}: culong
proc statusProc(state: pointer; handle: Handle): Status {.exportc.} =
const
DirMode = 1 shl 14
FileMode = 1 shl 15
let session = cast[ptr SessionObj](state)
fsRpc session:
var st: Status
let node = session.nodes[handle]
st.inode = 0
st.mode = DirMode
if node.path == "?":
session.store.randomApply(session.fsSet, session.rng) do (id: BlobId; size: BiggestInt):
st.size = (culonglong)size
st.mode = FileMode
st.inode = node.inode
elif node.path != "/":
session.apply(node.path) do (id: BlobId; size: BiggestInt):
st.size = (culonglong)size
st.mode = FileMode
st.inode = node.inode
result = st
proc dirProc(state: pointer; path: cstring; create: cint): Handle {.exportc.} =
permissionsAssert(create == 0)
let session = cast[ptr SessionObj](state)
fsRpc session:
var path = $path
var n: Node
if path == "/":
n = Node(path: "", kind: dirNode)
else:
validPathAssert path
path = path.strip(chars={'/'})
if contains(session.store, session.fsSet, path):
return (not Handle(0))
n = Node(path: path, kind: dirNode)
result = session.nextId
session.nodes[result] = n
proc fileProc(state: pointer; dirH: Handle; name: cstring; mode: cuint; create: cint): Handle {.exportc.} =
permissionsAssert(create == 0)
let session = cast[ptr SessionObj](state)
fsRpc session:
let name = $name
validNameAssert name
var n: Node
let dir = session.nodes[dirH]
assertInvalidHandle(dir.kind == dirNode)
let path = if dir.path == "": name else: dir.path & "/" & name
var success = false
if path == "?":
var retry = 16
while not success and retry > 0:
# just open something
session.store.randomApply(session.fsSet, session.rng) do (id: BlobId; size: BiggestInt):
try:
let stream = session.store.openBlobStream(id, size, dataBlob)
n = Node(path: path, kind: fileNode, id: id, size: size, stream: stream)
success = true
except:
dec retry
else:
session.apply(path) do (id: BlobId; size: BiggestInt):
let stream = session.store.openBlobStream(id, size, dataBlob)
n = Node(path: path, kind: fileNode, id: id, size: size, stream: stream)
success = true
if not success:
result = (not Handle(0))
else:
result = session.nextId
session.nodes[result] = n
proc closeProc(state: pointer; h: Handle) {.exportc.} =
let session = cast[ptr SessionObj](state)
fsRpc session:
var n: Node
if session.nodes.take(h, n):
case n.kind
of fileNode:
close n.stream
else:
discard
proc unlinkProc(state: pointer; dirH: Handle; name: cstring) {.exportc.} =
discard
proc truncateProc(state: pointer; file: Handle, size: cuint) {.exportc.} =
discard
proc moveProc(state: pointer;
from_dir: Handle; from_name: cstring;
to_dir: Handle; to_name: cstring) {.exportc.} =
discard
proc processPacket(session: SessionRef; pkt: var FsPacket) =
## Process a File_system packet from the client.
if not session.nodes.hasKey(pkt.handle):
echo session.label, " sent packet with invalid handle"
else:
case pkt.operation
of READ:
let
node = session.nodes[pkt.handle]
pktBuf = session.cpp.packetContent pkt
# cast the pointer to an array pointer for indexing
case node.kind
of dirNode:
# pretend this is an empty directory
pkt.setLen 0
pkt.succeeded true
of fileNode:
node.stream.pos = pkt.position.int
let n = waitFor node.stream.read(pktBuf, pkt.len)
pkt.setLen n
pkt.succeeded true
else:
discard
of SYNC:
pkt.succeeded true
else:
discard
proc newSession(env: GenodeEnv; store: BlobStore; label: string; setId: SetId; fsSet: BlobSet; txBufSize: int): SessionRef =
proc construct(cpp: FsSessionComponent; env: GenodeEnv; txBufSize: int; state: SessionPtr; cap: SignalContextCapability) {.
importcpp.}
let session = SessionRef(
store: store,
label: label,
fsSetId: setId,
fsSet: fsSet,
nodes: initTable[Handle, Node](),
rng: initRand(rand(int.high))
)
session.sig = env.ep.newSignalHandler do ():
while session.cpp.packetAvail and session.cpp.readyToAck:
var pkt = session.cpp.popRequest
pkt.succeeded false # processPacket must affirm success
try: session.processPacket(pkt)
except: discard
session.cpp.acknowledge(pkt)
session.cpp.construct(env, txBufSize, session[].addr, session.sig.cap)
result = session
componentConstructHook = proc(env: GenodeEnv) =
var
policies = newSeq[XmlNode](8)
sessions = initTable[ServerId, SessionRef]()
let store = env.newStoreReporter(newFileStore("/store"))
# Use the file-system as the store backend, with a reporter wrapper
proc createSession(env: GenodeEnv; store: BlobStore; id: ServerId; label: string; setId: SetId; txBufSize: int) =
let
fsSet = waitFor store.load setId
session = env.newSession(store, label, setId, fsSet, txBufSize)
cap = env.ep.manage session
sessions[id] = session
echo setId.toHex, " served to ", label
env.parent.deliverSession(id, cap)
proc processSessions(rom: RomClient) =
update rom
var requests = initSessionRequestsParser(rom)
for id in requests.close:
if sessions.contains id:
let s = sessions[id]
env.ep.dissolve s
sessions.del id
env.parent.sessionResponseClose(id)
for id, label, args in requests.create "File_system":
let policy = policies.lookupPolicy label
doAssert(not sessions.contains(id), "session already exists for id")
doAssert(label != "")
if policy.isNil:
echo "no policy matched '", label, "'"
env.parent.sessionResponseDeny(id)
else:
var setId: SetId
let pAttrs = policy.attrs
if not pAttrs.isNil and pAttrs.contains "root":
try: setId = toSetId(pAttrs["root"])
except ValueError: discard
else:
for e in label.elements:
try:
setId = toSetId(e)
break
except ValueError: continue
if not setId.isNonZero:
try: setId = args.argString("root").toSetId
except: discard
if setId.isNonZero:
try:
let txBufSize = args.argInt "tx_buf_size"
env.createSession(store, id, label, setId, txBufSize.int)
except:
echo "failed to create session for '", label, "', ", getCurrentExceptionMsg()
env.parent.sessionResponseDeny(id)
else:
echo "no valid root policy for '", label, "'"
env.parent.sessionResponseDeny(id)
proc processConfig(rom: RomClient) {.gcsafe.} =
update rom
policies.setLen 0
let configXml = rom.xml
configXml.findAll("default-policy", policies)
if policies.len > 1:
echo "more than one '<default-policy/>' found, ignoring all"
policies.setLen 0
configXml.findAll("policy", policies)
for session in sessions.values:
# update root policies for active sessions
let policy = policies.lookupPolicy session.label
if not policy.isNil:
let pAttrs = policy.attrs
if not pAttrs.isNil and pAttrs.contains "root":
try:
let
idStr = pAttrs["root"]
policySetId = toSetId idStr
if session.fsSetId != policySetId:
let newSet = waitFor store.load policySetId
session.fsSet = newSet
session.fsSetId = policySetId
echo idStr, " is new root of ", session.label
except:
echo "failed to update policy for '",
session.label, "', ", getCurrentExceptionMsg()
let
sessionsRom = env.newRomHandler("session_requests", processSessions)
configRom = env.newRomHandler("config", processConfig)
process configRom
process sessionsRom
env.parent.announce "File_system"
randomize()
# initialize the RNG