libretro-genode/src/fs_block/component.cc

422 lines
12 KiB
C++

/*
* \brief Serve blocks from a file system session
* \author Emery Hemingway
* \date 2015-09-25
*/
/*
* Copyright (C) 2015-2018 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#include <file_system_session/connection.h>
#include <file_system/util.h>
#include <block_session/rpc_object.h>
#include <block/driver.h>
#include <os/path.h>
#include <os/session_policy.h>
#include <root/component.h>
#include <base/attached_rom_dataspace.h>
#include <base/attached_ram_dataspace.h>
#include <base/allocator_avl.h>
#include <base/heap.h>
#include <base/component.h>
namespace Fs_block {
using namespace Genode;
using namespace Block;
struct Block_buffer;
class Session_component;
class Root_component;
typedef Genode::Path<File_system::MAX_PATH_LEN> Path;
typedef File_system::file_size_t file_size_t;
}
struct Fs_block::Block_buffer
{
Attached_ram_dataspace buffer_ds;
Block_buffer(Genode::Env &env, size_t buffer_size)
: buffer_ds(env.pd(), env.rm(), buffer_size) { }
};
class Fs_block::Session_component final : private Block_buffer,
public Block::Session_rpc_object
{
private:
enum { PENDING_QUEUE_COUNT = File_system::Session::TX_QUEUE_SIZE };
Block::Packet_descriptor _pending[PENDING_QUEUE_COUNT];
Genode::Heap &_heap;
Genode::Allocator_avl _fs_tx_alloc { &_heap };
File_system::Connection<void> _fs;
Signal_handler<Session_component> _fs_handler;
Signal_handler<Session_component> _blk_handler;
File_system::File_handle _handle { ~0U };
Genode::size_t const _blk_size;
Block::sector_t _blk_count = 0;
Block::Session::Operations _ops { };
Genode::size_t _pending_count = 0; /* pending index */
bool _pending_sync = false;
void _process_blk_pkt(Block::Packet_descriptor const blk_packet)
{
File_system::Session::Tx::Source &source = *_fs.tx();
Block::Session::Tx::Sink &sink = *tx_sink();
File_system::seek_off_t start = blk_packet.block_number() * _blk_size;
size_t const len = blk_packet.block_count() * _blk_size;
File_system::Packet_descriptor::Opcode op;
switch (blk_packet.operation()) {
case Block::Packet_descriptor::Opcode::READ:
op = File_system::Packet_descriptor::Opcode::READ; break;
case Block::Packet_descriptor::Opcode::WRITE:
op = File_system::Packet_descriptor::Opcode::WRITE; break;
default:
throw ~0;
}
File_system::Packet_descriptor fs_packet(
source.alloc_packet(len), _handle, op,
len, File_system::seek_off_t(start));
if (op == File_system::Packet_descriptor::WRITE)
memcpy(source.packet_content(fs_packet),
sink.packet_content(blk_packet),
fs_packet.length());
source.submit_packet(fs_packet);
}
void _process_blk()
{
File_system::Session::Tx::Source &source = *_fs.tx();
Block::Session::Tx::Sink &sink = *tx_sink();
while (sink.packet_avail()
&& source.ready_to_submit()
&& _pending_count < PENDING_QUEUE_COUNT)
{
Block::Packet_descriptor pkt = sink.get_packet();
if (pkt.size() == 0 || pkt.operation() > Block::Packet_descriptor::Opcode::WRITE)
{
warning("refusing invalid Block packet");
sink.acknowledge_packet(pkt);
} else {
pkt.succeeded(false);
for (int i = 0; i < PENDING_QUEUE_COUNT; ++i) {
if (_pending[i].size() == 0) {
_pending[i] = pkt;
break;
}
}
++_pending_count;
_process_blk_pkt(pkt);
}
}
}
void _process_fs_packet()
{
File_system::Session::Tx::Source &source = *_fs.tx();
Block::Session::Tx::Sink &sink = *tx_sink();
File_system::Packet_descriptor fs_packet = source.get_acked_packet();
Block::Packet_descriptor::Opcode op;
switch (fs_packet.operation()) {
case File_system::Packet_descriptor::Opcode::READ:
op = Block::Packet_descriptor::Opcode::READ; break;
case File_system::Packet_descriptor::Opcode::WRITE:
op = Block::Packet_descriptor::Opcode::WRITE; break;
case File_system::Packet_descriptor::Opcode::SYNC:
_pending_sync = false;
default:
source.release_packet(fs_packet);
return;
}
Block::sector_t const blk_num = fs_packet.position() / _blk_size;
if (_pending_count < 1)
error("got a packet but nothing pending");
for (int i = 0; i < PENDING_QUEUE_COUNT; ++i) {
/* process the first queue item with the same offset */
if (_pending[i].operation() == op &&
_pending[i].block_number() == blk_num)
{
size_t byte_count = min(
_pending[i].size(), fs_packet.length());
size_t blk_count = min(
_pending[i].block_count(), byte_count / _blk_size);
/* create a new packet with the length from the FS */
Block::Packet_descriptor ack_pkt(
_pending[i], op, blk_num, blk_count);
ack_pkt.succeeded(fs_packet.succeeded());
/* wipe the queue entry */
_pending[i] = Block::Packet_descriptor();
--_pending_count;
if (op == Block::Packet_descriptor::Opcode::READ) {
/* zero any trailing content (last block of file) */
if (byte_count < ack_pkt.size()) {
memset(sink.packet_content(ack_pkt),
0x00, ack_pkt.size());
}
memcpy(sink.packet_content(ack_pkt),
source.packet_content(fs_packet),
byte_count);
}
/* free packets */
sink.acknowledge_packet(ack_pkt);
source.release_packet(fs_packet);
break;
}
}
}
void _process_fs()
{
Block::Session::Tx::Sink &sink = *tx_sink();
File_system::Session::Tx::Source &source = *_fs.tx();
while (source.ack_avail() && sink.ready_to_ack())
_process_fs_packet();
}
Path _file_root(char const *file_path)
{
Path r(file_path);
r.strip_last_element();
return r;
}
public:
/**
* Constructor
*/
Session_component(Genode::Env &env,
Genode::Heap &heap,
size_t tx_buf_size,
file_size_t device_size,
size_t block_size,
char const *file_path,
bool writeable)
:
Block_buffer(env, tx_buf_size),
Session_rpc_object(env.rm(), Block_buffer::buffer_ds.cap(),
env.ep().rpc_ep()),
_heap(heap),
_fs(env, _fs_tx_alloc, "", _file_root(file_path).string(),
writeable, tx_buf_size),
_fs_handler( env.ep(), *this, &Session_component::_process_fs),
_blk_handler(env.ep(), *this, &Session_component::_process_blk),
_blk_size(block_size)
{
using namespace File_system;
/* the File_system session is rooted at the parent directory of the file */
Path file_name(file_path);
file_name.keep_only_last_element();
char const *name = file_name.string()+1;
try {
Dir_handle dir = _fs.dir("/", false);
Handle_guard guard(_fs, dir);
if (writeable) {
try {
_ops.set_operation(Block::Packet_descriptor::READ);
_ops.set_operation(Block::Packet_descriptor::WRITE);
_handle = _fs.file(dir, name, READ_WRITE, false);
} catch (Lookup_failed) {
if (device_size == 0) throw;
_handle = _fs.file(dir, name, READ_WRITE, true);
} catch (Permission_denied) {
try {
_ops.set_operation(Block::Packet_descriptor::READ);
_handle = _fs.file(dir, name, READ_ONLY, false);
} catch (Permission_denied) {
/* not likely, but still supported */
_ops.set_operation(Block::Packet_descriptor::WRITE);
_handle = _fs.file(dir, name, WRITE_ONLY, false);
}
}
} else {
_ops.set_operation(Block::Packet_descriptor::READ);
_handle = _fs.file(dir, name, READ_ONLY, false);
}
} catch (...) {
error("failed to open ", file_path);
throw;
}
File_system::Status st = _fs.status(_handle);
if (device_size != 0 && st.size != device_size) {
if (!writeable) {
Genode::error("cannot resize read-only file ", file_path);
throw Service_denied();
}
try { _fs.truncate(_handle, device_size); }
catch (...) {
Genode::error("failed to resize ", file_path, " to ", Genode::Number_of_bytes(device_size));
throw;
}
st = _fs.status(_handle);
}
_blk_count = st.size / block_size;
if (st.size % block_size) /* round up */
++_blk_count;
/* register signal handlers */
_fs.sigh_ack_avail(_fs_handler);
_tx.sigh_packet_avail(_blk_handler);
for (int i = 0; i < PENDING_QUEUE_COUNT; ++i)
_pending[i] = Block::Packet_descriptor();
}
/*****************************
** Block session interface **
*****************************/
void info(sector_t *blk_count,
Genode::size_t *blk_size,
Operations *ops)
{
*blk_count = _blk_count;
*blk_size = _blk_size;
*ops = _ops;
}
void sync() override
{
if (_fs.tx()->ready_to_submit()) {
_fs.tx()->submit_packet(File_system::Packet_descriptor(
File_system::Packet_descriptor(), _handle,
File_system::Packet_descriptor::SYNC, 0, 0));
_pending_sync = true;
} else {
error("cannot sync, File_system submit queue is full");
}
}
};
class Fs_block::Root_component final :
public Genode::Root_component<Fs_block::Session_component, Genode::Single_client>
{
private:
Genode::Env &_env;
Genode::Heap &_heap;
Attached_rom_dataspace _config_rom { _env, "config" };
static
file_size_t _device_size(Xml_node const &config)
{
Genode::Number_of_bytes zero { 0 };
return config.attribute_value("device_size", zero);
}
protected:
Session_component *_create_session(char const *args) override
{
Session_label const label = label_from_args(args);
long block_size = 512;
bool writeable = false;
_config_rom.update();
String<File_system::MAX_PATH_LEN> path;
Session_policy const policy(label, _config_rom.xml());
policy.attribute("file").value(&path);
file_size_t device_size = _device_size(policy);
block_size = policy.attribute_value("block_size", block_size);
writeable = policy.attribute_value("writeable", false);
/*
* Check that there is sufficient quota, but the client is not
* required to pay for the entire cost of the session for two
* reasons.
*
* First, we only serve a single client at a time so a denial of
* service is not issue. If the quota is deficient the session
* is denied and the parent is not consulted.
*
* Second, we maintain a packet buffer of equal size with the
* backend that we do not expect the client to account for.
* This simplifies the implementation because any packet
* allocation at the client can be matched at the backend.
*/
size_t ram_quota =
Arg_string::find_arg(args, "ram_quota" ).aligned_size();
size_t tx_buf_size =
Arg_string::find_arg(args, "tx_buf_size").aligned_size();
if (!tx_buf_size) {
error("invalid buffer size");
throw Service_denied();
}
size_t avail = ram_quota + _env.pd().avail_ram().value;
size_t session_size = max((size_t)4096, sizeof(Session_component));
if ((avail < session_size) ||
(tx_buf_size*2 > avail - session_size))
{
error("insufficient 'ram_quota', got %zd, need %zd",
ram_quota, (tx_buf_size*2 + session_size) - avail);
throw Service_denied();
}
return new (_heap)
Session_component(_env, _heap, tx_buf_size,\
device_size, block_size,
path.string(), writeable);
}
public:
/**
* Constructor
*/
Root_component(Genode::Env &env, Genode::Heap &heap)
:
Genode::Root_component<Fs_block::Session_component, Genode::Single_client>(
env.ep(), heap),
_env(env), _heap(heap)
{ }
};
void Component::construct(Genode::Env &env)
{
static Genode::Heap heap(env.pd(), env.rm());
static Fs_block::Root_component root(env, heap);
env.parent().announce(env.ep().manage(root));
}