From 319432aad99f6b2f5443ef5d3a141ec1b7710063 Mon Sep 17 00:00:00 2001 From: Emery Hemingway Date: Sun, 27 Jan 2019 22:26:32 +0100 Subject: [PATCH] Genode File-system server Repurpose the File_system server from Dagfs. --- .gitignore | 2 +- genode/blobsets_genode.nimble | 6 +- genode/src/blobsets_fs.nim | 367 +++++++++++++++++++++++++++++++ genode/src/filesystemsession.nim | 45 ++++ genode/src/fs_component.h | 93 ++++++++ src/blobsets.nim | 2 +- src/blobsets/filestores.nim | 13 +- 7 files changed, 517 insertions(+), 11 deletions(-) create mode 100644 genode/src/blobsets_fs.nim create mode 100644 genode/src/filesystemsession.nim create mode 100644 genode/src/fs_component.h diff --git a/.gitignore b/.gitignore index 6f487be..8cd3b1c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ /blobset /tests/test_set /tests/test_http -/genode/bin/blobsets_http +/genode/bin/ diff --git a/genode/blobsets_genode.nimble b/genode/blobsets_genode.nimble index bd0c24b..1635da8 100644 --- a/genode/blobsets_genode.nimble +++ b/genode/blobsets_genode.nimble @@ -7,12 +7,12 @@ license = "AGPLv3" srcDir = "src" binDir = "bin" bin = @[ - #"dagfs_fs", #"blobset_fs_store", #"dagfs_server", #"dagfs_tcp_store" - #"blobset_rom" - "blobsets_http" + "blobsets_fs", + "blobsets_http", + "blobsets_rom", ] backend = "cpp" diff --git a/genode/src/blobsets_fs.nim b/genode/src/blobsets_fs.nim new file mode 100644 index 0000000..fc18b66 --- /dev/null +++ b/genode/src/blobsets_fs.nim @@ -0,0 +1,367 @@ +import std/tables, std/xmltree, std/strtabs, std/strutils, std/streams, std/xmlparser + +import genode, genode/signals, genode/parents, genode/servers, genode/roms + +import blobsets, blobsets/filestores, ./filesystemsession + +const + currentPath = currentSourcePath.rsplit("/", 1)[0] + fsComponentH = currentPath & "/fs_component.h" + +const FsH = "" + +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 permissionsAssert(cond: bool) = + if not cond: raisePermissionDenied() + +template lookupAssert(cond: bool) = + if not cond: raiseLookupFailed() + +template validPathAssert(name: string) = + if name[name.low] != '/' or name[name.high] == '/': raiseLookupFailed() + +template validNameAssert(name: string) = + if name.contains '/': raiseInvalidName() + +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] + cache: string + ## Read files from the store into this buffer + cacheCid: BlobId + ## CID of the cache contents + + 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() + raisePermissionDenied() + +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 = node.inode + st.mode = DirMode + if node.path != "": + session.apply(node.path) do (id: BlobId; size: BiggestInt): + st.size = (culonglong)size + st.mode = FileMode + 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 session.fsSet.contains path: + raiseLookupFailed() + 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] + if dir.kind != dirNode: + raiseInvalidHandle() + let path = if dir.path == "": name else: dir.path & "/" & name + var success = false + 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: + raiseLookupFailed() + result = session.nextId + session.nodes[result] = n + +proc closeProc(state: pointer; h: Handle) {.exportc.} = + let session = cast[ptr SessionObj](state) + fsRpc session: + let n = session.nodes[h] + session.nodes.del h + case n.kind + of fileNode: + close n.stream + else: + discard + +proc unlinkProc(state: pointer; dirH: Handle; name: cstring) {.exportc.} = + raisePermissionDenied() + +proc truncateProc(state: pointer; file: Handle, size: cuint) {.exportc.} = + raisePermissionDenied() + +proc moveProc(state: pointer; + from_dir: Handle; from_name: cstring; + to_dir: Handle; to_name: cstring) {.exportc.} = + raisePermissionDenied() + +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 = cast[ptr array[maxChunkSize, char]](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 = 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](), + cache: "", + # Buffer for reading file data. + ) + 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 = newFileStore("/store") + # Use the file-system as the store backend + + proc createSession(env: GenodeEnv; store: BlobStore; id: ServerId; label: string; setId: SetId; txBufSize: int) = + let + fsSet = store.loadSet(setId) + session = env.newSession(store, label, setId, fsSet, txBufSize) + cap = env.ep.manage session + sessions[id] = session + echo setId, " 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 setId.isValid: + try: + let + #rootPath = args.argString "root" + 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 = store.loadSet 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" diff --git a/genode/src/filesystemsession.nim b/genode/src/filesystemsession.nim new file mode 100644 index 0000000..a25c737 --- /dev/null +++ b/genode/src/filesystemsession.nim @@ -0,0 +1,45 @@ +# +# \brief Nim File_system session support +# \author Emery Hemingway +# \date 2017-12-05 +# + +# +# Copyright (C) 2017 Genode Labs GmbH +# +# This file is part of the Genode OS framework, which is distributed +# under the terms of the GNU Affero General Public License version 3. +# + +const FsH = "" + +type + FsPacket* {. + header: FsH, importcpp: "File_system::Packet_descriptor".} = object + + Operation* {. + header: FsH, importcpp: "File_system::Packet_descriptor::Opcode".} = enum + READ, WRITE, CONTENT_CHANGED, READ_READY, SYNC + +proc handle*(pkt: FsPacket): culong {.importcpp: "#.handle().value".} +proc operation*(pkt: FsPacket): Operation {.importcpp.} +proc position*(pkt: FsPacket): BiggestInt {.importcpp.} +proc len*(pkt: FsPacket): int {.importcpp: "length".} +proc setLen*(pkt: FsPacket; n: int) {.importcpp: "length".} +proc succeeded*(pkt: FsPacket): bool {.importcpp.} +proc succeeded*(pkt: FsPacket, b: bool) {.importcpp.} + +type + FsDirentType* {.importcpp: "File_system::Directory_entry::Type".} = enum + TYPE_FILE, TYPE_DIRECTORY, TYPE_SYMLINK + + FsDirent* {. + header: FsH, importcpp: "File_system::Directory_entry", final, pure.} = object + inode* {.importcpp.}: culong + kind* {.importcpp: "type".}: FsDirentType + name* {.importcpp.}: cstring + +proc fsDirentSize*(): cint {. + importcpp: "sizeof(File_system::Directory_entry)".} + +var MAX_NAME_LEN* {.importcpp:"File_system::MAX_NAME_LEN", noDecl.}: cint diff --git a/genode/src/fs_component.h b/genode/src/fs_component.h new file mode 100644 index 0000000..d185232 --- /dev/null +++ b/genode/src/fs_component.h @@ -0,0 +1,93 @@ +/* + * \brief C++ File_system session component for Nim + * \author Emery Hemingway + * \date 2017-12-02 + */ + +/* + * Copyright (C) 2017 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +/* Genode includes */ +#include +#include +#include +#include + +typedef unsigned long Handle; + +Handle nodeProc(void *state, char *path); +Handle dirProc(void *state, char *path, int create); +Handle fileProc(void *state, Handle dir, char *name, unsigned int mode, int create); +File_system::Status statusProc(void *state, Handle handle); +void closeProc(void *state, Handle handle); +void unlinkProc(void *state, Handle dir, char *name); +void truncateProc(void *state, int file, File_system::file_size_t size); +void moveProc(void *state, + Handle from_dir, char *from_name, + Handle to_dir, char *to_name); + +namespace File_system { struct SessionComponentBase; } + +struct File_system::SessionComponentBase : File_system::Session_rpc_object +{ + void *state; + + SessionComponentBase(Genode::Env *env, + size_t tx_buf_size, + void *state, + Genode::Signal_context_capability cap) + : Session_rpc_object(env->pd().alloc(tx_buf_size), env->rm(), env->ep().rpc_ep()), + state(state) + { + _tx.sigh_packet_avail(cap); + _tx.sigh_ready_to_ack(cap); + } + + + /*********************************** + ** File_system session interface ** + ***********************************/ + + Node_handle node(File_system::Path const &path) override { + return Node_handle{ nodeProc(state, path.string()) }; } + + Dir_handle dir(File_system::Path const &path, bool create) override { + return Dir_handle{ dirProc(state, path.string(), create) }; } + + File_handle file(Dir_handle dir, Name const &name, Mode mode, bool create) override { + return File_handle{ fileProc(state, dir.value, name.string(), unsigned(mode), create) }; } + + Symlink_handle symlink(Dir_handle, Name const &name, bool create) override { + throw Lookup_failed(); } + + void close(Node_handle handle) override { + closeProc(state, handle.value); } + + Status status(Node_handle handle) override { + return statusProc(state, handle.value); } + + void control(Node_handle h, Control) override { } + + void unlink(Dir_handle dir, Name const &name) override + { + if (!unlinkProc) + throw Permission_denied(); + } + + void truncate(File_handle, File_system::file_size_t size) override + { + if (!truncateProc) + throw Permission_denied(); + } + + void move(Dir_handle, Name const &from, + Dir_handle, Name const &to) override + { + if (!moveProc) + throw Permission_denied(); + } +}; diff --git a/src/blobsets.nim b/src/blobsets.nim index 1dce480..52d2a94 100644 --- a/src/blobsets.nim +++ b/src/blobsets.nim @@ -155,7 +155,7 @@ func isNonZero*(bh: BlobId): bool = {.deprecated: [isValid: isNonZero].} type - Key = int64 + Key* = int64 const keyBits = sizeof(Key) shl 3 diff --git a/src/blobsets/filestores.nim b/src/blobsets/filestores.nim index 2beb70f..97fd8b0 100644 --- a/src/blobsets/filestores.nim +++ b/src/blobsets/filestores.nim @@ -121,14 +121,15 @@ proc fsOpenIngestStream(s: BlobStore; size: BiggestInt; kind: BlobKind): IngestS stream.leaves = newSeq[BlobId]() stream -proc newFileStore*(root: string): FileStore = +proc newFileStore*(root: string): BlobStore = ## Create a new store object backed by a file-system. try: createDir(root / $dataBlob) createDir(root / $metaBlob) except: discard - new result - result.openBlobStreamImpl = fsOpenBlobStream - result.openIngestStreamImpl = fsOpenIngestStream - result.root = root - result.buf = "" + FileStore( + openBlobStreamImpl: fsOpenBlobStream, + openIngestStreamImpl: fsOpenIngestStream, + root: root, + buf: "", + )