genode/repos/os/src/server/fs_rom/main.cc
Norman Feske 37be984d34 fs_rom: workaround for possible livelock
Thanks to Alexander Boettcher for investigating!
2018-06-12 12:11:50 +02:00

506 lines
13 KiB
C++
Executable File

/*
* \brief Service that provides files of a file system as ROM sessions
* \author Norman Feske
* \author Emery Hemingway
* \date 2013-01-11
*/
/*
* Copyright (C) 2013-2018 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 <rom_session/rom_session.h>
#include <file_system_session/connection.h>
#include <file_system/util.h>
#include <os/path.h>
#include <base/attached_ram_dataspace.h>
#include <root/component.h>
#include <base/component.h>
#include <base/session_label.h>
#include <util/arg_string.h>
#include <base/heap.h>
/*****************
** ROM service **
*****************/
namespace Fs_rom {
using namespace Genode;
class Rom_session_component;
class Rom_root;
typedef Id_space<Rom_session_component> Sessions;
typedef File_system::Session_client::Tx::Source Tx_source;
}
/**
* A 'Rom_session_component' exports a single file of the file system
*/
class Fs_rom::Rom_session_component : public Rpc_object<Rom_session>
{
private:
friend class List<Rom_session_component>;
friend class Packet_handler;
Env &_env;
Sessions &_sessions;
File_system::Session &_fs;
Constructible<Sessions::Element> _watch_elem { };
enum { PATH_MAX_LEN = 512 };
typedef Genode::Path<PATH_MAX_LEN> Path;
/**
* Name of requested file, interpreted at path into the file system
*/
Path const _file_path;
/**
* Wandering notification handle
*/
Constructible<File_system::Watch_handle> _watch_handle { };
/**
* Handle of associated file opened during read loop
*/
File_system::File_handle _file_handle { ~0UL };
/**
* Size of current version of the file
*/
File_system::file_size_t _file_size = 0;
/**
* Read offset of the file
*/
File_system::seek_off_t _file_seek = 0;
/**
* Dataspace exposed as ROM module to the client
*/
Attached_ram_dataspace _file_ds;
/**
* Signal destination for ROM file changes
*/
Signal_context_capability _sigh { };
/*
* Version number used to track the need for ROM update notifications
*/
struct Version { unsigned value; };
Version _curr_version { 0 };
Version _handed_out_version { 0 };
/**
* Track if the session file or a directory is being watched
*/
bool _watching_file = false;
/*
* Exception
*/
struct Watch_failed { };
/**
* Watch the session ROM file or some parent directory
*/
void _open_watch_handle()
{
using namespace File_system;
_close_watch_handle();
Path watch_path(_file_path);
/* track if we can open the file or resort to a parent */
bool at_the_file = true;
try {
while (true) {
try {
_watch_handle.construct(_fs.watch(watch_path.base()));
_watch_elem.construct(
*this, _sessions, Sessions::Id{_watch_handle->value});
_watching_file = at_the_file;
return;
}
catch (File_system::Lookup_failed) { }
catch (File_system::Unavailable) { }
if (watch_path == "/")
throw Watch_failed();
watch_path.strip_last_element();
/* keep looping, but only directories will be opened */
at_the_file = false;
}
} catch (File_system::Out_of_ram) {
error("not enough RAM to watch '", watch_path, "'");
} catch (File_system::Out_of_caps) {
error("not enough caps to watch '", watch_path, "'");
}
throw Watch_failed();
}
void _close_watch_handle()
{
if (_watch_handle.constructed()) {
_watch_elem.destruct();
_fs.close(*_watch_handle);
_watch_handle.destruct();
}
_watching_file = false;
}
enum { UPDATE_OR_REPLACE = false, UPDATE_ONLY = true };
/**
* Fill dataspace with file content, return true if the
* current dataspace is reused.
*/
bool _read_dataspace(bool update_only)
{
using namespace File_system;
Genode::Path<PATH_MAX_LEN> dir_path(_file_path);
dir_path.strip_last_element();
Genode::Path<PATH_MAX_LEN> file_name(_file_path);
file_name.keep_only_last_element();
Dir_handle parent_handle = _fs.dir(dir_path.base(), false);
Handle_guard parent_guard(_fs, parent_handle);
/* the file handle is opened here... */
_file_handle = _fs.file(
parent_handle, file_name.base() + 1,
File_system::READ_ONLY, false);
Handle_guard file_guard(_fs, _file_handle);
Sessions::Element read_elem(
*this, _sessions, Sessions::Id{_file_handle.value});
/* ...but only for the lifetime of this procedure */
_file_seek = 0;
_file_size = _fs.status(_file_handle).size;
if (_file_size > _file_ds.size()) {
/* allocate new RAM dataspace according to file size */
if (update_only)
return false;
try {
_file_ds.realloc(&_env.ram(), _file_size);
} catch (...) {
error("failed to allocate memory for ", _file_path);
return false;
}
} else {
memset(_file_ds.local_addr<char>(), 0x00, _file_ds.size());
}
/* omit read if file is empty */
if (_file_size == 0)
return false;
/* read content from file */
Tx_source &source = *_fs.tx();
while (_file_seek < _file_size) {
/* if we cannot submit then process acknowledgements */
while (!source.ready_to_submit())
_env.ep().wait_and_dispatch_one_io_signal();
size_t chunk_size = min(_file_size - _file_seek,
source.bulk_buffer_size() / 2);
File_system::Packet_descriptor
packet(source.alloc_packet(chunk_size), _file_handle,
File_system::Packet_descriptor::READ,
chunk_size, _file_seek);
source.submit_packet(packet);
/*
* Process the global signal handler until we got a response
* for the read request (indicated by a change of the seek
* position).
*/
size_t const orig_file_seek = _file_seek;
while (_file_seek == orig_file_seek)
_env.ep().wait_and_dispatch_one_io_signal();
}
_handed_out_version = _curr_version;
return true;
}
bool _try_read_dataspace(bool update_only)
{
using namespace File_system;
try { _open_watch_handle(); }
catch (Watch_failed) { }
try { return _read_dataspace(update_only); }
catch (Lookup_failed) { log(_file_path, " ROM file is missing"); }
catch (Invalid_handle) { error(_file_path, ": invalid handle"); }
catch (Invalid_name) { error(_file_path, ": invalid name"); }
catch (Permission_denied) { error(_file_path, ": permission denied"); }
catch (...) { error(_file_path, ": unhandled error"); };
return false;
}
void _notify_client_about_new_version()
{
using namespace File_system;
if (_sigh.valid() && _curr_version.value != _handed_out_version.value) {
/* notify if the file exists and is not empty */
try {
Node_handle file = _fs.node(_file_path.base());
Handle_guard g(_fs, file);
_file_size = _fs.status(file).size;
if (_file_size > 0) {
/* assume a transition between versions */
Signal_transmitter(_sigh).submit();
}
}
/* notify if the file is removed */
catch (File_system::Lookup_failed) {
if (_file_size > 0) {
memset(_file_ds.local_addr<char>(), 0x00, _file_size);
_file_size = 0;
Signal_transmitter(_sigh).submit();
}
}
catch (...) { }
}
}
public:
/**
* Constructor
*
* \param fs file-system session to read the file from
* \param filename requested file name
* \param sig_rec signal receiver used to get notified about changes
* within the compound directory (in the case when
* the requested file could not be found at session-
* creation time)
*/
Rom_session_component(Env &env,
Sessions &sessions,
File_system::Session &fs,
const char *file_path)
:
_env(env), _sessions(sessions), _fs(fs),
_file_path(file_path),
_file_ds(env.ram(), env.rm(), 0) /* realloc later */
{
try { _open_watch_handle(); }
catch (Watch_failed) { }
}
/**
* Destructor
*/
~Rom_session_component()
{
_close_watch_handle();
}
/**
* Return dataspace with up-to-date content of file
*/
Rom_dataspace_capability dataspace()
{
using namespace File_system;
_try_read_dataspace(UPDATE_OR_REPLACE);
/* always serve a valid, even empty, dataspace */
if (_file_ds.size() < 1) {
_file_ds.realloc(&_env.ram(), 1);
}
Dataspace_capability ds = _file_ds.cap();
return static_cap_cast<Rom_dataspace>(ds);
}
void sigh(Signal_context_capability sigh)
{
_sigh = sigh;
if (_sigh.valid()) {
try { _open_watch_handle(); }
catch (Watch_failed) { }
}
_notify_client_about_new_version();
}
/**
* Update the current dataspace content
*/
bool update() override {
return _try_read_dataspace(UPDATE_ONLY); }
/**
* Called by the packet signal handler.
*/
void process_packet(File_system::Packet_descriptor const packet)
{
switch (packet.operation()) {
case File_system::Packet_descriptor::CONTENT_CHANGED:
if (!(packet.handle() == *_watch_handle))
return;
if (!_watching_file) {
/* try and get closer to the file */
_open_watch_handle();
}
if (_watching_file) {
/* notify the client of the change */
_curr_version = Version { _curr_version.value + 1 };
_notify_client_about_new_version();
}
return;
case File_system::Packet_descriptor::READ: {
if (!(packet.handle() == _file_handle))
return;
if (packet.position() > _file_seek || _file_seek >= _file_size) {
error("bad packet seek position");
_file_ds.realloc(&_env.ram(), 0);
_file_seek = 0;
return;
}
size_t const n = min(packet.length(), _file_size - _file_seek);
memcpy(_file_ds.local_addr<char>()+_file_seek,
_fs.tx()->packet_content(packet), n);
_file_seek += n;
return;
}
case File_system::Packet_descriptor::WRITE:
warning("discarding strange WRITE acknowledgement");
return;
case File_system::Packet_descriptor::SYNC:
warning("discarding strange SYNC acknowledgement");
return;
case File_system::Packet_descriptor::READ_READY:
warning("discarding strange READ_READY acknowledgement");
return;
}
}
};
class Fs_rom::Rom_root : public Root_component<Fs_rom::Rom_session_component>
{
private:
Env &_env;
Heap _heap { _env.ram(), _env.rm() };
Sessions _sessions { };
Allocator_avl _fs_tx_block_alloc { &_heap };
/* open file-system session */
File_system::Connection _fs { _env, _fs_tx_block_alloc };
Io_signal_handler<Rom_root> _packet_handler {
_env.ep(), *this, &Rom_root::_handle_packets };
void _handle_packets()
{
Tx_source &source = *_fs.tx();
while (source.ack_avail()) {
File_system::Packet_descriptor pkt = source.get_acked_packet();
/* sessions are indexed in space by watch and read handles */
auto const apply_fn = [pkt] (Rom_session_component &session) {
session.process_packet(pkt); };
try { _sessions.apply<Rom_session_component&>(
Sessions::Id{pkt.handle().value}, apply_fn); }
/* packet handle closed while packet in flight */
catch (Sessions::Unknown_id) { }
source.release_packet(pkt);
}
}
Rom_session_component *_create_session(const char *args) override
{
Session_label const label = label_from_args(args);
Session_label const module_name = label.last_element();
/* create new session for the requested file */
return new (md_alloc())
Rom_session_component(_env, _sessions, _fs, module_name.string());
}
public:
/**
* Constructor
*
* \param env Component environment
* \param md_alloc meta-data allocator used for ROM sessions
*/
Rom_root(Env &env,
Allocator &md_alloc)
:
Root_component<Rom_session_component>(env.ep(), md_alloc),
_env(env)
{
/* Process CONTENT_CHANGED acknowledgement packets at the entrypoint */
_fs.sigh_ack_avail(_packet_handler);
env.parent().announce(env.ep().manage(*this));
}
};
void Component::construct(Genode::Env &env)
{
/*
* Print message to force the creation of the env LOG session, which would
* otherwise be created on the first (error) message. The latter becomes a
* problem when using the fs_rom as a provider for the env ROM sessions
* (like depot_rom in the sculpt scenario). Here, an error message printed
* in the synchronously called 'Rom_session::dataspace' RPC function may
* create a livelock on the attempt to obtain the LOG session.
*
* XXX This workaround can be removed with the eager creation of the
* the env log session.
*/
Genode::log("--- fs_rom ---");
static Genode::Sliced_heap sliced_heap(env.ram(), env.rm());
static Fs_rom::Rom_root inst(env, sliced_heap);
}