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 = "" # 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: "".} = 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 '' 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