HTTP store server for Genode

This commit is contained in:
Ehmry - 2019-01-27 16:19:01 +01:00
parent 801d7c5ee0
commit a10a7a4e50
16 changed files with 35 additions and 1475 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
/blobset
/tests/test_set
/tests/test_http
/genode/bin/blobsets_http

View File

@ -0,0 +1,24 @@
# Package
version = "0.1.0"
author = "Emery Hemingway"
description = "Blobset components for Genode"
license = "AGPLv3"
srcDir = "src"
binDir = "bin"
bin = @[
#"dagfs_fs",
#"blobset_fs_store",
#"dagfs_server",
#"dagfs_tcp_store"
#"blobset_rom"
"blobsets_http"
]
backend = "cpp"
# Dependencies
requires "nim >= 0.19.0", "blobsets", "genode"
task genode, "Build for Genode":
exec "nimble build --os:genode -d:posix"

View File

@ -1,23 +0,0 @@
# Package
version = "0.1.2"
author = "Emery Hemingway"
description = "Dagfs TCP server"
license = "GPLv3"
srcDir = "src"
binDir = "bin"
bin = @[
"dagfs_fs",
"dagfs_fs_store",
"dagfs_rom",
"dagfs_server",
"dagfs_tcp_store"
]
backend = "cpp"
# Dependencies
requires "nim >= 0.18.1", "dagfs", "genode"
task genode, "Build for Genode":
exec "nimble build --os:genode -d:posix"

View File

@ -1,20 +0,0 @@
#
# \brief File-system backed Dagfs server
# \author Emery Hemingway
# \date 2017-11-04
#
#
# Copyright (C) 2017 - 2018 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.
#
import std/streams, std/strutils, genode,
dagfs, dagfs/stores, ./dagfs_client
componentConstructHook = proc (env: GenodeEnv) =
let
store = newFileStore("/") ## Storage backend for sessions
backend = env.newDagfsBackend(store)

View File

@ -1,186 +0,0 @@
import xmltree, streams, strtabs, strutils, xmlparser, tables, cbor,
genode, genode/parents, genode/servers, genode/roms,
dagfs, dagfs/stores, ./dagfs_client, dagfs/fsnodes
const
currentPath = currentSourcePath.rsplit("/", 1)[0]
header = currentPath & "/rom_component.h"
{.passC: "-I" & currentPath.}
type
RomSessionCapability {.
importcpp: "Genode::Rom_session_capability",
header: "<rom_session/capability.h>".} = object
RomSessionComponentBase {.importcpp, header: header.} = object
impl {.importcpp.}: pointer
RomSessionComponent = Constructible[RomSessionComponentBase]
Session = ref object
env: GenodeEnv
cpp: RomSessionComponent
ds: DataspaceCapability
proc isValid(cap: RomSessionCapability): bool {.importcpp: "#.valid()".}
proc deliverSession*(parent: Parent; id: ServerId; cap: RomSessionCapability) {.
importcpp: "#->deliver_session_cap(Genode::Parent::Server::Id{#}, #)".}
proc newSession(env: GenodeEnv; ds: DataspaceCapability): Session =
proc construct(cpp: RomSessionComponent; ds: DataspaceCapability) {.importcpp.}
new result
result.env = env
result.cpp.construct(ds)
result.ds = ds
proc manage(ep: Entrypoint; s: Session): RomSessionCapability =
proc manage(ep: Entrypoint; cpp: RomSessionComponent): RomSessionCapability {.
importcpp: "#.manage(*#)".}
result = ep.manage(s.cpp)
GC_ref s
proc dissolve(s: Session) =
proc dissolve(ep: Entrypoint; cpp: RomSessionComponent) {.
importcpp: "#.dissolve(*#)".}
let
ep = s.env.ep
pd = s.env.pd
ep.dissolve(s.cpp)
destruct s.cpp
pd.freeDataspace s.ds
GC_unref s
proc readFile(store: DagfsStore; s: Stream; file: FsNode) =
var chunk = ""
if file.isRaw:
store.get(file.cid, chunk)
assert(file.cid.verify chunk)
s.write chunk
else:
var n = 0
for i in 0..file.links.high:
store.get(file.links[i].cid, chunk)
assert(file.links[i].cid.verify chunk)
doAssert(n+chunk.len <= file.size)
s.write chunk
n.inc chunk.len
doAssert(n == file.size)
componentConstructHook = proc(env: GenodeEnv) =
var
store = env.newDagfsFrontend()
policies = newSeq[XmlNode](8)
sessions = initTable[ServerId, Session]()
proc readDataspace(label: string; rootCid: Cid): DataspaceCapability =
let
name = label.lastLabelElement
root = store.openDir(rootCid)
file = store.walk(root, name)
if file.isNil:
echo name, " not in root ", rootCid, " for '", label, "'"
else:
let
pd = env.pd
romDs = pd.allocDataspace file.size.int
dsFact = env.rm.newDataspaceStreamFactory(romDs)
romS = dsFact.newStream()
try: store.readFile(romS, file)
except:
close romS
pd.freeDataspace romDs
raise getCurrentException()
close romS
close dsFact
result = romDs
proc createSessionNoTry(id: ServerId; label: string; rootCid: Cid): RomSessionCapability =
let romDs = readDataspace(label, rootCid)
if romDs.isValid:
let session = env.newSession(romDs)
sessions[id] = session
result = env.ep.manage(session)
proc createSession(env: GenodeEnv; id: ServerId; label: string; rootCid: Cid) =
var cap = RomSessionCapability()
try: cap = createSessionNoTry(id, label, rootCid)
except MissingChunk:
let e = (MissingChunk)getCurrentException()
echo "cannot resolve '", label, "', ", e.cid, " is missing"
except:
echo "unhandled exception while resolving '", label, "', ",
getCurrentExceptionMsg()
discard
if cap.isValid:
echo "deliver ROM to ", label
env.parent.deliverSession id, cap
else:
echo "deny ROM to ", label
let parent = env.parent
parent.sessionResponseDeny(id)
proc processConfig(rom: RomClient) =
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)
proc processSessions(rom: RomClient) {.gcsafe.} =
update rom
var requests = initSessionRequestsParser(rom)
for id in requests.close:
if sessions.contains id:
let s = sessions[id]
dissolve(s)
sessions.del id
env.parent.sessionResponseClose(id)
for id, label, args in requests.create "ROM":
try:
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 rootCid = initCid()
let pAttrs = policy.attrs
if not pAttrs.isNil and pAttrs.contains "root":
try: rootCid = parseCid(pAttrs["root"])
except ValueError: discard
else:
for e in label.elements:
try:
rootCid = parseCid e
break
except ValueError: continue
if rootCid.isValid:
createSession(env, id, label, rootCid)
else:
echo "no valid root policy for '", label, "'"
env.parent.sessionResponseDeny(id)
except:
echo "failed to create session for '", label, "', ", getCurrentExceptionMsg()
env.parent.sessionResponseDeny(id)
# All sessions have been instantiated and requests fired off,
# now return to the entrypoint and dispatch packet signals,
# which will work the chain of futures and callbacks until
# `createSession` completes and capabilities are delivered
let
configRom = env.newRomHandler(
"config", processConfig)
sessionsRom = env.newRomHandler(
"session_requests", processSessions)
process configRom
process sessionsRom
env.parent.announce "ROM"

View File

@ -1,33 +0,0 @@
/*
* \brief C++ base of Dagfs client
* \author Emery Hemingway
* \date 2017-11-08
*/
/*
* 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.
*/
#ifndef _DAGFS_CLIENT_H_
#define _DAGFS_CLIENT_H_
/* Genode includes */
#include "../../include/dagfs_session/connection.h"
#include <base/heap.h>
struct DagfsClientBase
{
Genode::Heap heap;
Genode::Allocator_avl tx_packet_alloc { &heap };
Dagfs::Connection conn;
DagfsClientBase(Genode::Env *env, char const *label, Genode::size_t tx_buf_size)
: heap(env->pd(), env->rm()),
conn(*env, tx_packet_alloc, label, tx_buf_size)
{ }
};
#endif /* _DAGFS_CLIENT_H_ */

View File

@ -1,215 +0,0 @@
when not defined(genode):
{.error: "Genode only Dagfs client".}
import cbor, std/tables, std/strutils
import genode, genode/signals
import dagfs, dagfs/stores, ./dagfs_session
const
currentPath = currentSourcePath.rsplit("/", 1)[0]
dagfsClientH = currentPath & "/dagfs_client.h"
{.passC: "-I" & currentPath & "/../../include".}
type
DagfsClientBase {.importcpp, header: dagfsClientH.} = object
DagfsClientCpp = Constructible[DagfsClientBase]
proc construct(cpp: DagfsClientCpp; env: GenodeEnv; label: cstring; txBufSize: int) {.
importcpp.}
proc bulk_buffer_size(cpp: DagfsClientCpp): csize {.
importcpp: "#->conn.source().bulk_buffer_size()".}
proc sigh_ack_avail(cpp: DagfsClientCpp; sig: SignalContextCapability) {.
importcpp: "#->conn.channel().sigh_ack_avail(@)".}
proc ready_to_submit(cpp: DagfsClientCpp): bool {.
importcpp: "#->conn.source().ready_to_submit()".}
proc ready_to_ack(cpp: DagfsClientCpp): bool {.
importcpp: "#->conn.source().ready_to_ack()".}
proc ack_avail(cpp: DagfsClientCpp): bool {.
importcpp: "#->conn.source().ack_avail()".}
proc alloc_packet(cpp: DagfsClientCpp; size = MaxPacketSize): DagfsPacket {.
importcpp: "#->conn.source().alloc_packet(@)".}
proc packet_content(cpp: DagfsClientCpp; pkt: DagfsPacket): pointer {.
importcpp: "#->conn.source().packet_content(@)".}
proc submit_packet(cpp: DagfsClientCpp; pkt: DagfsPacket) {.
importcpp: "#->conn.source().submit_packet(@)".}
proc submit_packet(cpp: DagfsClientCpp; pkt: DagfsPacket; cid: cstring; op: DagfsOpcode) {.
importcpp: "#->conn.source().submit_packet(Dagfs::Packet(#, (char const *)#, #))".}
proc get_acked_packet(cpp: DagfsClientCpp): DagfsPacket {.
importcpp: "#->conn.source().get_acked_packet()".}
proc release_packet(cpp: DagfsClientCpp; pkt: DagfsPacket) {.
importcpp: "#->conn.source().release_packet(@)".}
type
DagfsFrontend* = ref DagfsFrontendObj
DagfsFrontendObj = object of DagfsStoreObj
## Dagfs session client consuming a store.
cpp: DagfsClientCpp
proc fendClose(s: DagfsStore) =
var fend = DagfsFrontend(s)
destruct fend.cpp
proc fendPutBuffer(s: DagfsStore; buf: pointer; len: Natural): Cid =
## Put block to Dagfs server, blocks for two packet round-trip.
var fend = DagfsFrontend(s)
var
pktCid = dagHash(buf, len)
if pktCid == zeroChunk:
return pktCid
assert(fend.cpp.readyToSubmit, "Dagfs client packet queue congested")
var pkt = fend.cpp.allocPacket(len)
let pktBuf = fend.cpp.packetContent pkt
defer: fend.cpp.releasePacket pkt
assert(not pktBuf.isNil, "allocated packet has nil content")
assert(len <= pkt.size)
pkt.setLen len
copyMem(pktBuf, buf, len)
assert(fend.cpp.readyToSubmit, "Dagfs client packet queue congested")
fend.cpp.submitPacket(pkt, pktCid.toHex, PUT)
let ack = fend.cpp.getAckedPacket()
doAssert(ack.error == OK)
result = ack.cid()
assert(result.isValid, "server returned a packet with and invalid CID")
proc fendPut(s: DagfsStore; blk: string): Cid =
## Put block to Dagfs server, blocks for two packet round-trip.
let fend = DagfsFrontend(s)
var
blk = blk
pktCid = dagHash blk
if pktCid == zeroChunk:
return pktCid
assert(fend.cpp.readyToSubmit, "Dagfs client packet queue congested")
var pkt = fend.cpp.allocPacket(blk.len)
let pktBuf = fend.cpp.packetContent pkt
defer: fend.cpp.releasePacket pkt
assert(not pktBuf.isNil, "allocated packet has nil content")
assert(blk.len <= pkt.size)
pkt.setLen blk.len
copyMem(pktBuf, blk[0].addr, blk.len)
assert(fend.cpp.readyToSubmit, "Dagfs client packet queue congested")
fend.cpp.submitPacket(pkt, pktCid.toHex, PUT)
let ack = fend.cpp.getAckedPacket()
doAssert(ack.error == OK)
result = ack.cid()
assert(result.isValid, "server returned a packet with and invalid CID")
proc fendGetBuffer(s: DagfsStore; cid: Cid; buf: pointer; len: Natural): int =
## Get from Dagfs server, blocks for packet round-trip.
let fend = DagfsFrontend(s)
assert(fend.cpp.readyToSubmit, "Dagfs client packet queue congested")
let pkt = fend.cpp.allocPacket len
fend.cpp.submitPacket(pkt, cid.toHex, GET)
let ack = fend.cpp.getAckedPacket
doAssert(ack.cid == cid)
if ack.error == OK:
let pktBuf = fend.cpp.packetContent ack
assert(not pktBuf.isNil, "ack packet has nil content")
assert(ack.len <= len)
assert(0 < ack.len)
result = ack.len
copyMem(buf, pktBuf, result)
if pkt.size > 0:
fend.cpp.releasePacket pkt
# free the original packet that was allocated
case ack.error:
of OK: discard
of MISSING:
raiseMissing cid
else:
raise newException(CatchableError, "Dagfs packet error " & $ack.error)
proc fendGet(s: DagfsStore; cid: Cid; result: var string) =
## Get from Dagfs server, blocks for packet round-trip.
let fend = DagfsFrontend(s)
assert(fend.cpp.readyToSubmit, "Dagfs client packet queue congested")
let pkt = fend.cpp.allocPacket()
defer: fend.cpp.releasePacket pkt
fend.cpp.submitPacket(pkt, cid.toHex, GET)
let ack = fend.cpp.getAckedPacket()
doAssert(ack.cid == cid)
case ack.error:
of OK:
let ackBuf = fend.cpp.packetContent ack
assert(not ackBuf.isNil)
assert(0 < ack.len, "server return zero length packet")
result.setLen ack.len
copyMem(result[0].addr, ackBuf, result.len)
assert(cid.verify(result), "Dagfs client packet failed verification")
of MISSING:
raiseMissing cid
else:
raise newException(CatchableError, "Dagfs packet error " & $ack.error)
const
defaultBufferSize* = maxChunkSize * 4
proc newDagfsFrontend*(env: GenodeEnv; label = ""; bufferSize = defaultBufferSize): DagfsFrontend =
## Open a new frontend client connection.
## Blocks retrieved by `get` are not verified.
new result
construct(result.cpp, env, label, bufferSize)
result.closeImpl = fendClose
result.putBufferImpl = fendPutBuffer
result.putImpl = fendPut
result.getBufferImpl = fendGetBuffer
result.getImpl = fendGet
type
DagfsBackend* = ref DagfsBackendObj
DagfsBackendObj = object
## Dagfs session client providing a store.
cpp: DagfsClientCpp
store: DagfsStore
sigh: SignalHandler
const zeroHex = zeroChunk.toHex
proc newDagfsBackend*(env: GenodeEnv; store: DagfsStore; label = ""; bufferSize = defaultBufferSize): DagfsBackend =
## Open a new backend client connection.
doAssert(bufferSize > maxChunkSize, "Dagfs backend session buffer is too small")
let bend = DagfsBackend(store: store)
construct(bend.cpp, env, label, bufferSize)
bend.sigh = env.ep.newSignalHandler do ():
while bend.cpp.ackAvail:
var pkt = bend.cpp.getAckedPacket()
let
buf = bend.cpp.packetContent(pkt)
cid = pkt.cid
case pkt.op
of GET:
pkt.setOp(PUT)
try:
let n = store.getBuffer(cid, buf, pkt.size)
pkt.setLen(n)
bend.cpp.submitPacket(pkt)
except MissingChunk:
pkt.setError(MISSING)
bend.cpp.submitPacket(pkt)
of PUT:
let putCid = store.putBuffer(buf, pkt.len)
doAssert(putCid == cid, $putCid & " PUT CID mismatch with server")
bend.cpp.submitPacket(pkt, putCid.toHex, Idle)
else:
echo "unhandled packet from server"
bend.cpp.submitPacket(pkt, zeroHex, Idle)
bend.cpp.sighAckAvail(bend.sigh.cap)
for _ in 1..(bend.cpp.bulkBufferSize div maxChunkSize):
let pkt = bend.cpp.allocPacket(maxChunkSize)
assert(bend.cpp.readyToSubmit)
bend.cpp.submitPacket(pkt, zeroHex, IDLE)
bend

View File

@ -1,424 +0,0 @@
import std/tables, std/xmltree, std/strtabs, std/strutils, std/streams, std/xmlparser
import genode, genode/signals, genode/parents, genode/servers, genode/roms
import dagfs, dagfs/stores, dagfs/fsnodes, ./dagfs_client, ./filesystemsession
const
currentPath = currentSourcePath.rsplit("/", 1)[0]
fsComponentH = currentPath & "/fs_component.h"
const FsH = "<file_system_session/file_system_session.h>"
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: "<file_system_session/capability.h>".} = object
FsSessionComponentBase {.
importcpp: "File_system::SessionComponentBase", header: fsComponentH.} = object
FsSessionComponent = Constructible[FsSessionComponentBase]
Handle = culong
NodeKind = enum
nodeNode,
dirNode,
fileNode,
cidNode
Node = object
ufs: FsNode
kind: NodeKind
SessionPtr = ptr SessionObj
SessionRef = ref SessionObj
SessionObj = object
sig: SignalHandler
cpp: FsSessionComponent
store: DagfsFrontend
label: string
rootDir: FsNode
next: Handle
nodes: Table[Handle, Node]
cache: string
## Read files from the store into this buffer
cacheCid: Cid
## CID of the cache contents
Session = ptr SessionObj | ref SessionObj | SessionObj
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(cid: Cid): culong = hash(cid).culong
## Convert a CID to a inode with the same hash
## algorithm used to store CIDs in tables.
template fsRpc(session: SessionPtr; body: untyped) =
try: body
except MissingChunk:
let e = (MissingChunk)getCurrentException()
echo "Synchronous RPC failure, missing object ", e.cid
raiseLookupFailed()
except:
echo "failed, ", getCurrentExceptionMsg()
raisePermissionDenied()
proc nodeProc(session: pointer; path: cstring): Handle {.exportc.} =
let session = cast[SessionPtr](session)
fsRpc session:
var n: Node
if path == "/":
n = Node(ufs: session.rootDir, kind: nodeNode)
else:
var path = $path
validPathAssert path
if path.endsWith("/.cid"):
path.setLen(path.len - "/.cid".len)
let ufs = session.store.walk(session.rootDir, path)
if ufs.isNil:
raiseLookupFailed()
n = Node(ufs: ufs, kind: cidNode)
else:
let ufs = session.store.walk(session.rootDir, path)
if ufs.isNil:
raiseLookupFailed()
n = Node(ufs: ufs, kind: nodeNode)
result = session.nextId
session.nodes[result] = n
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:
let node = session.nodes[handle]
result.inode = node.ufs.cid.inode
if node.ufs.isDir:
if node.kind == cidNode:
result.size = 0
result.mode = FileMode
else:
result.size = (culonglong)node.ufs.size * fsDirentSize().BiggestInt
result.mode = DirMode
else:
result.size = node.ufs.size.culonglong
result.mode = FileMode
proc dirProc(state: pointer; path: cstring; create: cint): Handle {.exportc.} =
permissionsAssert(create == 0)
let session = cast[ptr SessionObj](state)
fsRpc session:
let path = $path
var n: Node
if path == "/":
n = Node(ufs: session.rootDir, kind: dirNode)
else:
validPathAssert path
let ufs = session.store.walk(session.rootDir, path)
if ufs.isNil:
raiseLookupFailed()
if not ufs.isDir:
raiseLookupFailed()
n = Node(ufs: ufs, 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 name == ".cid":
n = Node(
ufs: dir.ufs,
kind: cidNode)
else:
let ufs = dir.ufs[name]
lookupAssert(not ufs.isNil and ufs.isFile)
n = Node(
ufs: ufs,
kind: fileNode)
result = session.nextId
session.nodes[result] = n
proc closeProc(state: pointer; h: Handle) {.exportc.} =
let session = cast[ptr SessionObj](state)
fsRpc session:
session.nodes.del h
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:
if pkt.operation == 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 fileNode:
if node.ufs.isRaw:
if session.cacheCid != node.ufs.cid:
session.store.get(node.ufs.cid, session.cache)
session.cacheCid = node.ufs.cid
if pkt.position < session.cache.len:
let
pos = pkt.position.int
n = min(pkt.len, session.cache.len - pos)
copyMem(pktBuf, session.cache[pos].addr, n)
pkt.setLen n
pkt.succeeded true
else:
var
pktPos = pkt.position
remain = pkt.len
filePos: int64
count: int
for i in 0..node.ufs.links.high:
let linkSize = node.ufs.links[i].size
if (pktPos >= filePos) and (pktPos < filePos+linkSize):
if session.cacheCid != node.ufs.links[i].cid:
session.store.get(node.ufs.links[i].cid, session.cache)
session.cacheCid = node.ufs.links[i].cid
let
off = (int)pktPos - filePos
n = min(remain, session.cache.len - off)
copyMem(pktBuf[count].addr, session.cache[off].addr, n)
pktPos.inc n
count.inc n
remain.dec n
if remain == 0:
break
filePos.inc linkSize
pkt.setLen count
pkt.succeeded true
of dirNode:
if pkt.len >= fsDirentSize():
let i = pkt.position().int div fsDirentSize().int
var (name, u) = node.ufs[i]
if not u.isNil and name != "":
let dirent = cast[ptr FsDirent](pktBuf)
zeroMem(dirent, fsDirentSize())
dirent.inode = u.cid.inode
dirent.kind = if u.isFile: TYPE_FILE else: TYPE_DIRECTORY
copyMem(dirent.name, name[0].addr, min(name.len, MAX_NAME_LEN-1))
pkt.setLen fsDirentSize()
pkt.succeeded true
else:
pkt.setLen 0
of cidNode:
var s = node.ufs.cid.toHex()
let pos = pkt.position.int
if pos < s.len:
let n = min(s.len - pos, pkt.len)
copyMem(pktBuf, s[pos].addr, n)
pkt.setLen n
pkt.succeeded true
else:
pkt.setLen 0
else:
echo "ignoring ", pkt.operation, " packet from ", session.label
proc newSession(env: GenodeEnv; store: DagfsFrontend; label: string; root: FsNode; txBufSize: int): SessionRef =
proc construct(cpp: FsSessionComponent; env: GenodeEnv; txBufSize: int; state: SessionPtr; cap: SignalContextCapability) {.
importcpp.}
let session = new SessionRef
session.store = store
session.label = label
session.rootDir = root
session.nodes = initTable[Handle, Node]()
session.cache = ""
# Buffer for reading file data.
session.cacheCid = initCid()
# Last block that was read into the cache buffer.
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.newDagfsFrontend()
## The Dagfs session client backing File_system sessions.
proc createSession(env: GenodeEnv; store: DagfsFrontend; id: ServerId; label, rootPath: string; rootCid: Cid; txBufSize: int) =
var ufsRoot: FsNode
try: ufsRoot = store.openDir(rootCid)
except: ufsRoot = nil
if not ufsRoot.isNil and not(rootPath == "/" or rootPath == ""):
try: ufsRoot = store.walk(ufsRoot, rootPath)
except: ufsRoot = nil
# Can't use 'if' in 'try' here.
if not ufsRoot.isNil:
let session = env.newSession(store, label, ufsRoot, txBufSize)
sessions[id] = session
let cap = env.ep.manage session
echo rootCid, " served to ", label
env.parent.deliverSession(id, cap)
else:
echo "failed to create session for '", label, "', ",
getCurrentExceptionMsg()
env.parent.sessionResponseDeny(id)
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 rootCid = initCid()
let pAttrs = policy.attrs
if not pAttrs.isNil and pAttrs.contains "root":
try: rootCid = parseCid(pAttrs["root"])
except ValueError: discard
else:
for e in label.elements:
try:
rootCid = parseCid e
break
except ValueError: continue
if rootCid.isValid:
try:
let
rootPath = args.argString "root"
txBufSize = args.argInt "tx_buf_size"
env.createSession(store, id, label, rootPath, rootCid, 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
policyCidStr = pAttrs["root"]
policyCid = parseCid policyCidStr
if session.rootDir.cid != policyCid:
session.rootDir = store.openDir policyCid
echo policyCid, " 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"

View File

@ -1,56 +0,0 @@
/*
* \brief Dagfs C++ session component
* \author Emery Hemingway
* \date 2017-11-07
*/
/*
* 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.
*/
#ifndef _INCLUDE__NIM__DAGFS_SERVER_H_
#define _INCLUDE__NIM__DAGFS_SERVER_H_
#include <dagfs_session/rpc_object.h>
#include <base/heap.h>
#include <base/attached_ram_dataspace.h>
struct Communication_buffer
{
Genode::Attached_ram_dataspace _tx_ds;
Communication_buffer(Genode::Pd_session &pd,
Genode::Region_map &rm,
Genode::size_t tx_buf_size)
: _tx_ds(pd, rm, tx_buf_size) { }
};
struct DagfsSessionComponentBase : Communication_buffer,
Dagfs::Session_rpc_object
{
static Genode::size_t tx_buf_size(char const *args)
{
Genode::size_t const buf_size = Genode::Arg_string::find_arg(
args, "tx_buf_size").aligned_size();
if (!buf_size)
throw Genode::Service_denied();
return buf_size;
}
DagfsSessionComponentBase(Genode::Env *env, char const *args)
:
Communication_buffer(env->pd(), env->rm(), tx_buf_size(args)),
Session_rpc_object(env->rm(), env->ep().rpc_ep(), _tx_ds.cap())
{ }
void packetHandler(Genode::Signal_context_capability cap)
{
_tx.sigh_ready_to_ack(cap);
_tx.sigh_packet_avail(cap);
}
};
#endif /* _INCLUDE__NIM__DAGFS_SERVER_H_ */

View File

@ -1,278 +0,0 @@
#
# \brief Dagfs routing server
# \author Emery Hemingway
# \date 2017-11-11
#
#
# Copyright (C) 2017-2018 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.
#
import std/strtabs, std/tables, std/xmltree, std/strutils, std/deques
import dagfs, dagfs/stores, ./dagfs_session,
genode, genode/signals, genode/servers, genode/parents, genode/roms
const
currentPath = currentSourcePath.rsplit("/", 1)[0]
dagfsserverH = currentPath & "/dagfs_server.h"
{.passC: "-I" & currentPath & "/../../include".}
type
DagfsSessionComponentBase {.importcpp, header: dagfsserverH.} = object
SessionCpp = Constructible[DagfsSessionComponentBase]
proc construct(cpp: SessionCpp; env: GenodeEnv; args: cstring) {.importcpp.}
proc packetHandler(cpp: SessionCpp; cap: SignalContextCapability) {.
importcpp: "#->packetHandler(@)".}
proc packetContent(cpp: SessionCpp; pkt: DagfsPacket): pointer {.
importcpp: "#->sink().packet_content(@)".}
proc packetAvail(cpp: SessionCpp): bool {.
importcpp: "#->sink().packet_avail()".}
proc readyToAck(cpp: SessionCpp): bool {.
importcpp: "#->sink().ready_to_ack()".}
proc peekPacket(cpp: SessionCpp): DagfsPacket {.
importcpp: "#->sink().peek_packet()".}
proc getPacket(cpp: SessionCpp): DagfsPacket {.
importcpp: "#->sink().get_packet()".}
proc acknowledgePacket(cpp: SessionCpp; pkt: DagfsPacket) {.
importcpp: "#->sink().acknowledge_packet(@)".}
proc acknowledgePacket(cpp: SessionCpp; pkt: DagfsPacket; cid: cstring; op: DagfsOpcode) {.
importcpp: "#->sink().acknowledge_packet(Dagfs::Packet(#, (char const *)#, #))".}
template acknowledgePacket(cpp: SessionCpp; pkt: DagfsPacket; cid: Cid; op: DagfsOpcode) =
acknowledgePacket(cpp, pkt, cid.toHex, op)
type
Session = ref SessionObj
SessionObj = object of RootObj
cpp: SessionCpp
sig: SignalHandler
label: string
Frontend = ref object of SessionObj
discard
Backend = ref object of SessionObj
idle: Deque[DagfsPacket]
prio: int
Frontends = OrderedTableRef[ServerId, Frontend]
Backends = OrderedTableRef[ServerId, Backend]
proc `$`(s: Session): string = s.label
proc submitGet*(bend: Backend; cid: Cid): bool =
if 0 < bend.idle.len:
let pkt = bend.idle.popFirst()
bend.cpp.acknowledgePacket(pkt, cid, GET)
result = true
proc submitPut*(bend: Backend; cid: Cid; buf: pointer; len: int): bool =
if 0 < bend.idle.len:
var pkt = bend.idle.popFirst()
copyMem(bend.cpp.packetContent(pkt), buf, len)
pkt.setLen(len)
bend.cpp.acknowledgePacket(pkt, cid, PUT)
result = true
proc isPending(fend: Frontend; cid: Cid): bool =
if fend.cpp.packetAvail and fend.cpp.readyToAck:
result = (cid == fend.cpp.peekPacket.cid)
proc isPending(fend: Session; cid: Cid; op: DagfsOpcode): bool =
if fend.cpp.packetAvail and fend.cpp.readyToAck:
let pkt = fend.cpp.peekPacket()
result = (pkt.op == op and cid == pkt.cid)
proc processPacket(backends: Backends; fend: Frontend): bool =
if backends.len < 1:
echo "cannot service frontend client, no backends connected"
var pkt = fend.cpp.getPacket
pkt.setError MISSING
fend.cpp.acknowledgePacket(pkt)
return true
let
pkt = fend.cpp.peekPacket
cid = pkt.cid
op = pkt.op
case op
of GET:
for bend in backends.values:
if bend.submitGet(cid):
break
of PUT:
let
buf = fend.cpp.packetContent(pkt)
len = pkt.len
for bend in backends.values:
if bend.submitPut(cid, buf, len):
break
else:
var ack = fend.cpp.getPacket()
ack.setError ERROR
fend.cpp.acknowledgePacket(ack)
result = true
proc processPacket(frontends: Frontends; bend: Backend): bool =
let
pkt = bend.cpp.getPacket
cid = pkt.cid
op = pkt.op
case op
of PUT:
for fend in frontends.values:
if fend.isPending(cid, GET):
var ack = fend.cpp.getPacket
ack.setError(pkt.error)
if ack.size < pkt.len:
ack.setError(OVERSIZE)
fend.cpp.acknowledgePacket(ack)
else:
ack.setLen(pkt.len)
copyMem(fend.cpp.packetContent(ack), bend.cpp.packetContent(pkt), ack.len)
fend.cpp.acknowledgePacket(ack, cid, PUT)
of IDLE:
for fend in frontends.values:
if fend.isPending(cid, PUT):
fend.cpp.acknowledgePacket(fend.cpp.getPacket, cid, IDLE)
else:
echo "invalid ", op, " packet from backend ", bend.label
bend.idle.addLast pkt
true
proc newFrontend(env: GenodeEnv; backends: Backends; args, label: string): Frontend =
let fend = Frontend(label: label)
fend.cpp.construct(env, args)
fend.sig = env.ep.newSignalHandler do ():
while fend.cpp.packetAvail and fend.cpp.readyToAck:
if not backends.processPacket(fend): break
fend.cpp.packetHandler(fend.sig.cap)
fend
proc newBackend(env: GenodeEnv; frontends: Frontends; args: string; prio: int; label: string): Backend =
let bend = Backend(
label: label,
idle: initDeque[DagfsPacket](),
prio: prio)
bend.cpp.construct(env, args)
bend.sig = env.ep.newSignalHandler do ():
assert(bend.cpp.packetAvail, $bend & " signaled but no packet avail")
assert(bend.cpp.readyToAck, $bend & " signaled but not ready to ack")
while bend.cpp.packetAvail and bend.cpp.readyToAck:
if not frontends.processPacket(bend): break
bend.cpp.packetHandler(bend.sig.cap)
bend
proc manage(ep: Entrypoint; s: Session): DagfsSessionCapability =
## Manage a session from the default entrypoint.
proc manage(ep: Entrypoint; cpp: SessionCpp): DagfsSessionCapability {.
importcpp: "#.manage(*#)".}
result = ep.manage(s.cpp)
GC_ref s
proc dissolve(ep: Entrypoint; s: Session) =
## Dissolve a session from the entrypoint so that it can be freed.
proc dissolve(ep: Entrypoint; cpp: SessionCpp) {.
importcpp: "#.dissolve(*#)".}
ep.dissolve(s.cpp)
destruct(s.cpp)
dissolve(s.sig)
GC_unref s
componentConstructHook = proc(env: GenodeEnv) =
var
policies = newSeq[XmlNode]()
backends = newOrderedTable[ServerId, Backend]()
frontends = newOrderedTable[ServerId, Frontend]()
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)
proc createSessions(rom: RomClient): bool =
## Return true on progress
var requests = initSessionRequestsParser(rom)
for id, label, args in requests.create "Dagfs":
let policy = policies.lookupPolicy label
if policy.isNil:
echo "no policy matched '", label, "'"
env.parent.sessionResponseDeny(id)
else:
var session: Session
let role = policy.attr("role")
case role
of "frontend":
if frontends.contains id:
continue
if backends.len < 1:
echo "defering ", label, " until backends are available"
continue
let fend = newFrontend(env, backends, args, label)
frontends[id] = fend
session = fend
of "backend":
if backends.contains id:
continue
var prio = 1
try: prio = policy.attr("prio").parseInt
except: discard
let bend = newBackend(env, frontends, args, prio, label)
backends[id] = bend
backends.sort(proc (x, y: (ServerId, Backend)): int =
x[1].prio - y[1].prio)
session = bend
else:
echo "invalid role for policy ", policy
env.parent.sessionResponseDeny(id)
continue
let cap = env.ep.manage(session)
proc deliverSession(env: GenodeEnv; id: ServerId; cap: DagfsSessionCapability) {.
importcpp: "#->parent().deliver_session_cap(Genode::Parent::Server::Id{#}, #)".}
env.deliverSession(id, cap)
echo "session opened for ", label
result = true
proc processSessions(rom: RomClient) =
update rom
var requests = initSessionRequestsParser(rom)
for id in requests.close:
var s: Session
if frontends.contains id:
s = frontends[id]
frontends.del id
elif backends.contains id:
s = backends[id]
backends.del id
env.ep.dissolve s
env.parent.sessionResponseClose(id)
while rom.createSessions():
update rom
# Process the session requests repeatedly to ensure
# that frontend sessions waiting for backends are
# processed regardless to the order of requests.
let
sessionsRom = env.newRomHandler("session_requests", processSessions)
configRom = env.newRomHandler("config", processConfig)
process configRom
process sessionsRom

View File

@ -1,52 +0,0 @@
#
# \brief Dagfs session definitions
# \author Emery Hemingway
# \date 2017-11-11
#
#
# 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.
#
import dagfs
const MaxPacketSize* = 1 shl 18;
type
DagfsSessionCapability* {.final, pure,
importcpp: "Dagfs::Session_capability",
header: "<dagfs_session/capability.h>".} = object
DagfsPacket* {.
importcpp: "Dagfs::Packet",
header: "<dagfs_session/dagfs_session.h>".} = object
DagfsOpcode* {.importcpp: "Dagfs::Packet::Opcode".} = enum
PUT, GET, IDLE, INVALID
DagfsError* {.importcpp: "Dagfs::Packet::Error".} = enum
OK, MISSING, OVERSIZE, FULL, ERROR
proc size*(pkt: DagfsPacket): csize {.importcpp.}
## Physical packet size.
proc cidStr(p: DagfsPacket): cstring {.importcpp: "#.cid().string()".}
proc cid*(p: DagfsPacket): Cid = parseCid $p.cidStr
proc setCid*(p: var DagfsPacket; cid: cstring) {.importcpp: "#.cid(@)".}
proc setCid*(p: var DagfsPacket; cid: Cid) = p.setCid(cid.toHex())
proc op*(pkt: DagfsPacket): DagfsOpcode {.importcpp.}
proc setOp*(p: var DagfsPacket; op: DagfsOpcode) {.importcpp: "op".}
proc len*(pkt: DagfsPacket): csize {.importcpp: "length".}
## Logical packet length.
proc setLen*(pkt: var DagfsPacket; len: int) {.importcpp: "length".}
## Set logical packet length.
proc error*(pkt: DagfsPacket): DagfsError {.importcpp.}
proc setError*(pkt: var DagfsPacket; err: DagfsError) {.importcpp: "error".}

View File

@ -1,16 +0,0 @@
when not defined(genode):
{.error: "Genode only module".}
import std/asyncdispatch
import ./dagfs_client, dagfs/tcp
when not defined(genode):
{.error: "Genode only server".}
componentConstructHook = proc (env: GenodeEnv) =
echo "--- Dagfs TCP server ---"
let
store = env.newDagfsFrontend()
server = newTcpServer store
waitFor server.serve()
quit "--- Dagfs TCP server died ---"

View File

@ -1,45 +0,0 @@
#
# \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 = "<file_system_session/file_system_session.h>"
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

View File

@ -1,93 +0,0 @@
/*
* \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 <file_system_session/rpc_object.h>
#include <root/component.h>
#include <libc/component.h>
#include <base/heap.h>
typedef unsigned long Handle;
Handle nodeProc(void *state, char *path);
Handle dirProc(void *state, char *