422 lines
12 KiB
C++
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));
|
|
}
|