Genode File-system server

Repurpose the File_system server from Dagfs.
This commit is contained in:
Ehmry - 2019-01-27 22:26:32 +01:00
parent a10a7a4e50
commit 319432aad9
7 changed files with 517 additions and 11 deletions

2
.gitignore vendored
View File

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

View File

@ -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"

367
genode/src/blobsets_fs.nim Normal file
View File

@ -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"

View File

@ -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

93
genode/src/fs_component.h Normal file
View File

@ -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();
}
};

View File

@ -155,7 +155,7 @@ func isNonZero*(bh: BlobId): bool =
{.deprecated: [isValid: isNonZero].}
type
Key = int64
Key* = int64
const
keyBits = sizeof(Key) shl 3

View File

@ -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: "",
)