From 267817c2c50b92995cc50d6192f67a54fc62b793 Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Sat, 12 Jan 2013 21:27:57 +0100 Subject: [PATCH] Add new 'fs_rom' service The 'fs_rom' service provides files stored on a file system as ROM modules via the ROM-session interface. Fixes #606 --- doc/components.txt | 4 + os/src/server/fs_rom/README | 16 ++ os/src/server/fs_rom/main.cc | 470 +++++++++++++++++++++++++++++++++ os/src/server/fs_rom/target.mk | 3 + 4 files changed, 493 insertions(+) create mode 100644 os/src/server/fs_rom/README create mode 100755 os/src/server/fs_rom/main.cc create mode 100755 os/src/server/fs_rom/target.mk diff --git a/doc/components.txt b/doc/components.txt index 909b146e4..2f868be13 100644 --- a/doc/components.txt +++ b/doc/components.txt @@ -292,6 +292,10 @@ Separate components: Provides each file contained in a tar file obtained via Genode's ROM session as separate ROM session. +:'os/src/server/fs_rom': + Provides files stored in a file system as individual ROM sessions. Please + refer to 'os/src/server/fs_rom' for more information. + :'os/src/server/iso9660': Provides each file of an ISO9660 file system accessed via a block session as separate ROM session. diff --git a/os/src/server/fs_rom/README b/os/src/server/fs_rom/README new file mode 100644 index 000000000..386200475 --- /dev/null +++ b/os/src/server/fs_rom/README @@ -0,0 +1,16 @@ +The 'fs_rom' server translates the 'File_system' session interface to the +'ROM' session' interface. Each request for a ROM file is handled by looking +up an equally named file on the file system. If no such file could be found, +the server watches the file system for the creation of the corresponding file. +Furthermore, the server reflects file changes as signals to the ROM session. + +Limitations +----------- + +* Symbolic links are not handled +* The server needs to allocate RAM for each requested file. The RAM is always + allocated from the RAM session of the server. The RAM quota consumed by the + server depends on the client requests and the size of the requested files. + Therefore, one instance of the server should not be used by untrusted clients + and critical clients at the same time. In such situations, multiple instances + of the server could be used. diff --git a/os/src/server/fs_rom/main.cc b/os/src/server/fs_rom/main.cc new file mode 100755 index 000000000..c2cc3d846 --- /dev/null +++ b/os/src/server/fs_rom/main.cc @@ -0,0 +1,470 @@ +/* + * \brief Service that provides files of a file system as ROM sessions + * \author Norman Feske + * \date 2013-01-11 + */ + +/* + * Copyright (C) 2013 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. + */ + +/* Genode includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************* + ** Utilities for accessing the file system ** + *********************************************/ + +/* + * XXX The following generic utilities should be moved to a public place. + * They are based on those found in the 'libc_fs' plugin. We should + * unify them. + */ + +namespace File_system { + + /** + * Collect pending packet acknowledgements, freeing the space occupied + * by the packet in the bulk buffer + * + * This function should be called prior enqueing new packets into the + * packet stream to free up space in the bulk buffer. + */ + static void collect_acknowledgements(Session::Tx::Source &source) + { + while (source.ack_avail()) + source.release_packet(source.get_acked_packet()); + } + + + /** + * Read file content + */ + static inline size_t read(Session &fs, File_handle const &file_handle, + void *dst, size_t count, off_t seek_offset = 0) + { + Session::Tx::Source &source = *fs.tx(); + + size_t const max_packet_size = source.bulk_buffer_size() / 2; + + size_t remaining_count = count; + + while (remaining_count) { + + collect_acknowledgements(source); + + size_t const curr_packet_size = min(remaining_count, max_packet_size); + + Packet_descriptor + packet(source.alloc_packet(curr_packet_size), + 0, + file_handle, + File_system::Packet_descriptor::READ, + curr_packet_size, + seek_offset); + + /* pass packet to server side */ + source.submit_packet(packet); + + packet = source.get_acked_packet(); + + size_t const read_num_bytes = min(packet.length(), curr_packet_size); + + /* copy-out payload into destination buffer */ + memcpy(dst, source.packet_content(packet), read_num_bytes); + + source.release_packet(packet); + + /* prepare next iteration */ + seek_offset += read_num_bytes; + dst = (void *)((Genode::addr_t)dst + read_num_bytes); + remaining_count -= read_num_bytes; + + /* + * If we received less bytes than requested, we reached the end + * of the file. + */ + if (read_num_bytes < curr_packet_size) + break; + } + + return count - remaining_count; + } + + + struct Handle_guard + { + private: + + Session &_session; + Node_handle _handle; + + public: + + Handle_guard(Session &session, Node_handle handle) + : _session(session), _handle(handle) { } + + ~Handle_guard() { _session.close(_handle); } + }; +} + + +/***************** + ** ROM service ** + *****************/ + +/** + * A 'Rom_session_component' exports a single file of the file system + */ +class Rom_session_component : public Genode::Rpc_object +{ + private: + + File_system::Session &_fs; + + enum { PATH_MAX_LEN = 512 }; + typedef Genode::Path Path; + + /** + * Name of requested file, interpreted at path into the file system + */ + Path const _file_path; + + /** + * Handle of associated file + */ + File_system::File_handle _file_handle; + + /** + * Handle of currently watched compound directory + * + * The compund directory is watched only if the requested file could + * not be looked up. + */ + File_system::Node_handle _compound_dir_handle; + + /** + * Dataspace exposed as ROM module to the client + */ + Genode::Ram_dataspace_capability _file_ds; + + /** + * Handler for ROM file changes + */ + Genode::Lock _sigh_lock; + Genode::Signal_context_capability _sigh; + + /** + * Dispatcher that is called each time when the requested file is not + * yet available and the compound directory changes + * + * The change of the compound directory bears the chance that the + * requested file re-appears. So we inform the client about a ROM + * module change and thereby give it a chance to call 'dataspace()' in + * response. + */ + Genode::Signal_dispatcher _dir_change_dispatcher; + + /** + * Signal-handling function called by the main thread the compound + * directory changed. + * + * Note that this function is not executed in the context of the RPC + * entrypoint. Therefore, the access to '_sigh' is synchronized with + * the 'sigh()' function using '_sigh_lock'. + */ + void _dir_changed(unsigned) + { + Genode::Lock::Guard guard(_sigh_lock); + + PINF("detected directory change"); + if (_sigh.valid()) + Genode::Signal_transmitter(_sigh).submit(); + } + + /** + * Open compound directory of specified file + * + * \param walk_up If set to true, the function tries to walk up the + * hierarchy towards the root and returns the first + * existing directory on the way. If set to false, the + * function returns the immediate compound directory. + */ + static File_system::Dir_handle _open_compound_dir(File_system::Session &fs, + Path const &path, + bool walk_up) + { + using namespace File_system; + + Genode::Path dir_path(path.base()); + + while (!path.equals("/")) { + + dir_path.strip_last_element(); + + try { return fs.dir(dir_path.base(), false); } + + catch (Invalid_handle) { PERR("Invalid_handle"); } + catch (Invalid_name) { PERR("Invalid_name"); } + catch (Lookup_failed) { PERR("Lookup_failed"); } + catch (Permission_denied) { PERR("Permission_denied"); } + catch (Name_too_long) { PERR("Name_too_long"); } + catch (No_space) { PERR("No_space"); } + + /* + * If the directory could not be opened, walk up the hierarchy + * towards the root and try again. + */ + if (!walk_up) break; + } + return Dir_handle(); /* invalid */ + } + + /** + * Open file with specified name at the file system + */ + static File_system::File_handle _open_file(File_system::Session &fs, + Path const &path) + { + using namespace File_system; + + File_system::File_handle file_handle; + + try { + + Dir_handle dir = _open_compound_dir(fs, path, false); + Handle_guard guard(fs, dir); + + /* open file */ + Genode::Path file_name(path.base()); + file_name.keep_only_last_element(); + file_handle = fs.file(dir, file_name.base() + 1, + File_system::READ_ONLY, false); + } + catch (Invalid_handle) { PERR("Invalid_handle"); } + catch (Invalid_name) { PERR("Invalid_name"); } + catch (Lookup_failed) { PERR("Lookup_failed"); } + + return file_handle; + } + + void _register_for_compound_dir_changes() + { + /* forget about the previously watched compound directory */ + if (_compound_dir_handle.valid()) + _fs.close(_compound_dir_handle); + + _compound_dir_handle = _open_compound_dir(_fs, _file_path, true); + + /* register for changes in compound directory */ + if (_compound_dir_handle.valid()) + _fs.sigh(_compound_dir_handle, _dir_change_dispatcher); + else + PWRN("could not track compound dir, giving up"); + } + + /** + * Initialize '_file_ds' dataspace with file content + */ + void _update_dataspace() + { + using namespace File_system; + + /* + * On each repeated call of this function, the dataspace is + * replaced with a new one that contains the most current file + * content. + */ + if (_file_ds.valid()) { + env()->ram_session()->free(_file_ds); + _file_ds = Ram_dataspace_capability(); + } + + /* close and then re-open the file */ + if (_file_handle.valid()) + _fs.close(_file_handle); + + _file_handle = _open_file(_fs, _file_path); + + /* + * If we got the file, we can stop paying attention to the + * compound directory. + */ + if (_file_handle.valid() && _compound_dir_handle.valid()) + _fs.close(_compound_dir_handle); + + /* register for file changes */ + if (_sigh.valid() && _file_handle.valid()) + _fs.sigh(_file_handle, _sigh); + + size_t const file_size = _file_handle.valid() + ? _fs.status(_file_handle).size : 0; + + /* allocate new RAM dataspace according to file size */ + if (file_size > 0) { + try { + _file_ds = env()->ram_session()->alloc(file_size); } + catch (...) { + PERR("couldn't allocate memory for file, empty result\n"); + _file_ds = Ram_dataspace_capability(); + return; + } + } + + if (!_file_ds.valid()) { + _register_for_compound_dir_changes(); + return; + } + + /* map dataspace locally */ + void * const dst_addr = env()->rm_session()->attach(_file_ds); + + /* read content from file */ + read(_fs, _file_handle, dst_addr, file_size); + + /* unmap dataspace */ + env()->rm_session()->detach(dst_addr); + } + + 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(File_system::Session &fs, const char *file_path, + Genode::Signal_receiver &sig_reg) + : + _fs(fs), _file_path(file_path), _file_handle(_open_file(_fs, _file_path)), + _dir_change_dispatcher(sig_reg, *this, &Rom_session_component::_dir_changed) + { + if (!_file_handle.valid()) + _register_for_compound_dir_changes(); + } + + /** + * Destructor + */ + ~Rom_session_component() + { + /* close re-open the file */ + if (_file_handle.valid()) + _fs.close(_file_handle); + + if (_compound_dir_handle.valid()) + _fs.close(_compound_dir_handle); + + /* close file */ + Genode::env()->ram_session()->free(_file_ds); + } + + /** + * Return dataspace with up-to-date content of file + */ + Genode::Rom_dataspace_capability dataspace() + { + _update_dataspace(); + Genode::Dataspace_capability ds = _file_ds; + return Genode::static_cap_cast(ds); + } + + void sigh(Genode::Signal_context_capability sigh) + { + Genode::Lock::Guard guard(_sigh_lock); + _sigh = sigh; + if (_file_handle.valid()) + _fs.sigh(_file_handle, _sigh); + } +}; + + +class Rom_root : public Genode::Root_component +{ + private: + + File_system::Session &_fs; + Genode::Signal_receiver &_sig_rec; + + Rom_session_component *_create_session(const char *args) + { + enum { FILENAME_MAX_LEN = 128 }; + char filename[FILENAME_MAX_LEN]; + Genode::Arg_string::find_arg(args, "filename") + .string(filename, sizeof(filename), ""); + + PINF("connection for file '%s' requested\n", filename); + + /* create new session for the requested file */ + return new (md_alloc()) Rom_session_component(_fs, filename, _sig_rec); + } + + public: + + /** + * Constructor + * + * \param entrypoint entrypoint to be used for ROM sessions + * \param md_alloc meta-data allocator used for ROM sessions + * \param fs file-system session + */ + Rom_root(Genode::Rpc_entrypoint &entrypoint, + Genode::Allocator &md_alloc, + File_system::Session &fs, + Genode::Signal_receiver &sig_rec) + : + Genode::Root_component(&entrypoint, &md_alloc), + _fs(fs), _sig_rec(sig_rec) + { } +}; + + +int main(void) +{ + using namespace Genode; + + /* open file-system session */ + static Genode::Allocator_avl fs_tx_block_alloc(env()->heap()); + static File_system::Connection fs(fs_tx_block_alloc); + + /* connection to capability service needed to create capabilities */ + static Cap_connection cap; + + /* creation of the entrypoint and the root interface */ + static Sliced_heap sliced_heap(env()->ram_session(), + env()->rm_session()); + + /* receiver of directory-change signals */ + static Signal_receiver sig_rec; + + enum { STACK_SIZE = 8*1024 }; + static Rpc_entrypoint ep(&cap, STACK_SIZE, "fs_rom_ep"); + static Rom_root rom_root(ep, sliced_heap, fs, sig_rec); + + /* announce server*/ + env()->parent()->announce(ep.manage(&rom_root)); + + /* process incoming signals */ + for (;;) { + Signal s = sig_rec.wait_for_signal(); + static_cast(s.context())->dispatch(s.num()); + } + return 0; +} diff --git a/os/src/server/fs_rom/target.mk b/os/src/server/fs_rom/target.mk new file mode 100755 index 000000000..47f2a31de --- /dev/null +++ b/os/src/server/fs_rom/target.mk @@ -0,0 +1,3 @@ +TARGET = fs_rom +SRC_CC = main.cc +LIBS = cxx env server signal