/* * \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 #include #include #include #include #include #include #include #include #include #include #include namespace Fs_block { using namespace Genode; using namespace Block; struct Block_buffer; class Session_component; class Root_component; typedef Genode::Path 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 _fs; Signal_handler _fs_handler; Signal_handler _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 { 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 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( 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)); }