Genode File-system server
Repurpose the File_system server from Dagfs.
This commit is contained in:
parent
a10a7a4e50
commit
319432aad9
|
@ -1,4 +1,4 @@
|
|||
/blobset
|
||||
/tests/test_set
|
||||
/tests/test_http
|
||||
/genode/bin/blobsets_http
|
||||
/genode/bin/
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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 = "<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,
|
||||
|
||||
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 '<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 = 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"
|
|
@ -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 = "<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
|
|
@ -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 <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 *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();
|
||||
}
|
||||
};
|
|
@ -155,7 +155,7 @@ func isNonZero*(bh: BlobId): bool =
|
|||
{.deprecated: [isValid: isNonZero].}
|
||||
|
||||
type
|
||||
Key = int64
|
||||
Key* = int64
|
||||
|
||||
const
|
||||
keyBits = sizeof(Key) shl 3
|
||||
|
|
|
@ -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: "",
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue