From e65583c3e9cf0e9c326bb2d414d40fa32f64f9c6 Mon Sep 17 00:00:00 2001 From: Emery Hemingway Date: Tue, 3 Nov 2020 15:28:56 +0100 Subject: [PATCH 1/3] cached_fs_rom: resolve symlinks Follow symlinks from ROM requests to files. This complicates the implementation but allows ROM requests to be redirected at both the label routing and the file-system layers. Redirecting ROMs with symlinks is useful for retrieving ROMs from deeply nested or otherwise excessively long file-system paths. --- repos/os/src/server/cached_fs_rom/main.cc | 472 +++++++++++++--------- 1 file changed, 281 insertions(+), 191 deletions(-) diff --git a/repos/os/src/server/cached_fs_rom/main.cc b/repos/os/src/server/cached_fs_rom/main.cc index 9e4e4d0eee..bac2c075a9 100755 --- a/repos/os/src/server/cached_fs_rom/main.cc +++ b/repos/os/src/server/cached_fs_rom/main.cc @@ -5,7 +5,7 @@ */ /* - * Copyright (C) 2018 Genode Labs GmbH + * Copyright (C) 2018-2020 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. @@ -26,32 +26,119 @@ /* local session-requests utility */ #include "session_requests.h" -/***************** - ** ROM service ** - *****************/ namespace Cached_fs_rom { using namespace Genode; - typedef Genode::Path Path; - typedef File_system::Session_client::Tx::Source Tx_source; - - struct Cached_rom; - typedef Genode::Id_space Cache_space; struct Transfer; typedef Genode::Id_space Transfer_space; + struct Cached_rom; + typedef Genode::Id_space Cache_space; + class Session_component; typedef Genode::Id_space Session_space; struct Main; + typedef Genode::Path Path; + typedef File_system::Session_client::Tx::Source Tx_source; typedef File_system::Session::Tx::Source::Packet_alloc_failed Packet_alloc_failed; - typedef File_system::File_handle File_handle; } +/** + * State of symlink resolution and file reading + */ +struct Cached_fs_rom::Transfer final +{ + Transfer_space &_transfers; + File_system::Session &_fs; + + Path const first_path; + Path final_path = first_path; + + Reconstructible _transfer_elem { + *this, _transfers, + Transfer_space::Id{_fs.node(first_path.string()).value} + }; + + File_system::Packet_descriptor _raw_pkt = _alloc_packet(); + File_system::Packet_guard _packet_guard { *_fs.tx(), _raw_pkt }; + + Attached_ram_dataspace ram_ds; + File_system::Status status { }; + + /* Id of the originating session request */ + Parent::Server::Id const request_id; + + File_system::seek_off_t seek = 0; + + bool diag; + bool completed = false; + + /** + * Allocate space in the File_system packet buffer + * + * \throw Packet_alloc_failed + */ + File_system::Packet_descriptor _alloc_packet() + { + if (!_fs.tx()->ready_to_submit()) + throw Packet_alloc_failed(); + + size_t chunk_size = max( + size_t(File_system::MAX_PATH_LEN), + _fs.tx()->bulk_buffer_size()/4); + return _fs.tx()->alloc_packet(chunk_size); + } + + File_system::Node_handle handle() const { + return File_system::Node_handle{ _transfer_elem->id().value }; } + + void replace_handle(File_system::Node_handle new_handle) + { + _fs.close(handle()); + _transfer_elem.destruct(); + _transfer_elem.construct( + *this, _transfers, Transfer_space::Id{new_handle.value}); + } + + Transfer(Genode::Env &env, + File_system::Session &fs, + Transfer_space &space, + Path path, + Parent::Server::Id &pid, + bool diag) + : _transfers(space) + , _fs(fs) + , first_path(path) + , ram_ds(env.ram(), env.rm(), 0) + , request_id(pid) + , diag(diag) + { } + + ~Transfer() { _fs.close(handle()); } + + bool matches(Path const &path) const { + return (first_path == path || final_path == path); } + + void submit_next_packet() + { + File_system::Packet_descriptor packet( + _raw_pkt, handle(), + File_system::Packet_descriptor::READ, + _raw_pkt.size(), seek); + _fs.tx()->submit_packet(packet); + } + +}; + + +/** + * A dataspace corresponding to a file-system path + */ struct Cached_fs_rom::Cached_rom final { Cached_rom(Cached_rom const &); @@ -60,20 +147,15 @@ struct Cached_fs_rom::Cached_rom final Genode::Env &env; Rm_connection &rm_connection; - size_t const file_size; - /** * Backing RAM dataspace - * - * This shall be valid even if the file is empty. */ - Attached_ram_dataspace ram_ds { - env.pd(), env.rm(), file_size ? file_size : 1 }; + Attached_ram_dataspace ram_ds { env.pd(), env.rm(), 0 }; /** * Read-only region map exposed as ROM module to the client */ - Region_map_client rm { rm_connection.create(ram_ds.size()) }; + Region_map_client rm_client; Region_map::Local_addr rm_attachment { }; Dataspace_capability rm_ds { }; @@ -81,48 +163,39 @@ struct Cached_fs_rom::Cached_rom final Cache_space::Element cache_elem; - Transfer *transfer = nullptr; - /** * Reference count of cache entry */ int _ref_count = 0; - Cached_rom(Cache_space &cache_space, - Env &env, - Rm_connection &rm, - Path const &file_path, - size_t size) - : - env(env), rm_connection(rm), file_size(size), - path(file_path), - cache_elem(*this, cache_space) + Cached_rom(Genode::Env &env, + Rm_connection &rm, + Cache_space &cache_space, + Transfer &transfer) + : env(env) + , rm_connection(rm) + , rm_client(rm.create(transfer.ram_ds.size())) + , path(transfer.final_path) + , cache_elem(*this, cache_space) { - if (size == 0) - complete(); + /* move dataspace from the transfer object */ + transfer.ram_ds.swap(ram_ds); + + /* attach dataspace read-only into region map */ + enum { OFFSET = 0, LOCAL_ADDR = false, EXEC = true, WRITE = false }; + rm_attachment = rm_client.attach( + ram_ds.cap(), ram_ds.size(), OFFSET, + LOCAL_ADDR, (addr_t)~0, EXEC, WRITE); + rm_ds = rm_client.dataspace(); } - /** - * Destructor - */ ~Cached_rom() { if (rm_attachment) - rm.detach(rm_attachment); + rm_client.detach(rm_attachment); } - bool completed() const { return rm_ds.valid(); } - bool unused() const { return (_ref_count < 1); } - - void complete() - { - /* attach dataspace read-only into region map */ - enum { OFFSET = 0, LOCAL_ADDR = false, EXEC = true, WRITE = false }; - rm_attachment = rm.attach( - ram_ds.cap(), ram_ds.size(), OFFSET, - LOCAL_ADDR, (addr_t)~0, EXEC, WRITE); - rm_ds = rm.dataspace(); - } + bool unused() const { return (_ref_count < 1); } /** * Return dataspace with content of file @@ -130,6 +203,9 @@ struct Cached_fs_rom::Cached_rom final Rom_dataspace_capability dataspace() const { return static_cap_cast(rm_ds); } + /** + * Guard for maintaining reference count + */ struct Guard { Cached_rom &_rom; @@ -142,97 +218,6 @@ struct Cached_fs_rom::Cached_rom final }; -struct Cached_fs_rom::Transfer final -{ - Cached_rom &_cached_rom; - Cached_rom::Guard _cache_guard { _cached_rom }; - - File_system::Session &_fs; - File_system::File_handle _handle; - - File_system::file_size_t const _size; - File_system::seek_off_t _seek = 0; - File_system::Packet_descriptor _raw_pkt = _alloc_packet(); - File_system::Packet_guard _packet_guard { *_fs.tx(), _raw_pkt }; - - Transfer_space::Element _transfer_elem; - - /** - * Allocate space in the File_system packet buffer - * - * \throw Packet_alloc_failed - */ - File_system::Packet_descriptor _alloc_packet() - { - if (!_fs.tx()->ready_to_submit()) - throw Packet_alloc_failed(); - - size_t chunk_size = min(_size, _fs.tx()->bulk_buffer_size()/4); - return _fs.tx()->alloc_packet(chunk_size); - } - - void _submit_next_packet() - { - using namespace File_system; - - File_system::Packet_descriptor - packet(_raw_pkt, _handle, - File_system::Packet_descriptor::READ, - _raw_pkt.size(), _seek); - - _fs.tx()->submit_packet(packet); - } - - public: - - /** - * Constructor - */ - Transfer(Transfer_space &space, - Cached_rom &rom, - File_system::Session &fs, - File_system::File_handle file_handle, - size_t file_size) - : - _cached_rom(rom), _fs(fs), - _handle(file_handle), _size(file_size), - _transfer_elem(*this, space, Transfer_space::Id{_handle.value}) - { - _cached_rom.transfer = this; - - _submit_next_packet(); - } - - Path const &path() const { return _cached_rom.path; } - - bool completed() const { return (_seek >= _size); } - - /** - * Called from the packet signal handler. - */ - void process_packet(File_system::Packet_descriptor const packet) - { - auto const pkt_seek = packet.position(); - - if (pkt_seek > _seek || _seek >= _size) { - error("bad packet seek position for ", path()); - error("packet seek is ", packet.position(), ", file seek is ", _seek, ", file size is ", _size); - _seek = _size; - } else { - size_t const n = min(packet.length(), _size - pkt_seek); - memcpy(_cached_rom.ram_ds.local_addr()+pkt_seek, - _fs.tx()->packet_content(packet), n); - _seek = pkt_seek+n; - } - - if (completed()) - _cached_rom.complete(); - else - _submit_next_packet(); - } -}; - - class Cached_fs_rom::Session_component final : public Rpc_object { private: @@ -240,17 +225,15 @@ class Cached_fs_rom::Session_component final : public Rpc_object Cached_rom &_cached_rom; Cached_rom::Guard _cache_guard { _cached_rom }; Session_space::Element _sessions_elem; - Session_label const _label; public: Session_component(Cached_rom &cached_rom, - Session_space &space, Session_space::Id id, - Session_label const &label) + Session_space &space, + Parent::Server::Id pid) : _cached_rom(cached_rom), - _sessions_elem(*this, space, id), - _label(label) + _sessions_elem(*this, space, Session_space::Id{pid.value}) { } @@ -273,8 +256,8 @@ struct Cached_fs_rom::Main final : Genode::Session_request_handler Rm_connection rm { env }; - Cache_space cache { }; Transfer_space transfers { }; + Cache_space cache { }; Session_space sessions { }; Heap heap { env.pd(), env.rm() }; @@ -302,36 +285,133 @@ struct Cached_fs_rom::Main final : Genode::Session_request_handler return (bool)discard; } - /** - * Open a file handle - */ - File_system::File_handle open(Path const &file_path) + void resolve(Transfer &transfer) { using namespace File_system; + transfer.status = fs.status(transfer.handle()); - Path dir_path(file_path); - dir_path.strip_last_element(); - Path file_name(file_path); - file_name.keep_only_last_element(); + if (transfer.status.directory()) { + error("cannot serve directory as ROM, \"", transfer.final_path, "\""); + throw Service_denied(); + } + Path dir_path(transfer.final_path); + dir_path.strip_last_element(); Dir_handle parent_handle = fs.dir(dir_path.base(), false); Handle_guard parent_guard(fs, parent_handle); - return fs.file( - parent_handle, file_name.base() + 1, - File_system::READ_ONLY, false); + if (transfer.status.symlink()) { + Symlink_handle link_handle = fs.symlink( + parent_handle, transfer.final_path.last_element(), false); + transfer.replace_handle(link_handle); + } else { + File_handle file_handle = fs.file( + parent_handle, transfer.final_path.last_element(), READ_ONLY, false); + transfer.replace_handle(file_handle); + + /* allocate a backing dataspace */ + size_t ds_size = + align_addr(max(transfer.status.size, 1U), 12); + + while (env.pd().avail_ram().value < ds_size + || env.pd().avail_caps().value < 8) { + /* drop unused cache entries */ + if (!cache_evict()) break; + } + + transfer.ram_ds.realloc(&env.ram(), ds_size); + } + + transfer.submit_next_packet(); } /** - * Open a file with some exception management + * Called from the packet signal handler. */ - File_system::File_handle try_open(Path const &file_path) + void process_packet(Transfer &transfer, + File_system::Packet_descriptor const packet) + { + if (transfer.status.symlink()) { + size_t const n = packet.length(); + if (n >= Path::capacity()) { + error("bad symlink read at ", transfer.final_path); + throw Service_denied(); + } + + char buf[Path::capacity()]; + copy_cstring(buf, fs.tx()->packet_content(packet), n); + if (*buf == '/') + transfer.final_path = Path(buf); + else + transfer.final_path.append_element(buf); + + if (transfer.diag) + log("following \"", transfer.first_path, "\" to \"", transfer.final_path, "\""); + + /* lookup the link target in the cache */ + cache.for_each([&] (Cached_rom &rom) { + if (!transfer.completed && rom.path == transfer.final_path) { + /* + * this session can be served but future requests to + * first_path must again traverse the symlinks to final_path + */ + Session_component *session = new (heap) + Session_component(rom, sessions, transfer.request_id); + if (transfer.diag) + log("deliver cached \"", transfer.final_path, "\""); + env.parent().deliver_session_cap( + transfer.request_id, env.ep().manage(*session)); + transfer.completed = true; + } + }); + if (transfer.completed) { + destroy(heap, &transfer); + } else { + transfer.replace_handle(fs.node(transfer.final_path.string())); + resolve(transfer); + } + } else { + auto const pkt_seek = packet.position(); + + if (pkt_seek > transfer.seek || transfer.seek >= transfer.status.size) { + error("unexpected packet seek position for ", transfer.final_path); + error("packet seek is ", packet.position(), ", file seek is ", transfer.seek, ", file size is ", transfer.status.size); + transfer.seek = transfer.status.size; + } else { + size_t const n = min(packet.length(), transfer.status.size - pkt_seek); + memcpy(transfer.ram_ds.local_addr()+pkt_seek, + fs.tx()->packet_content(packet), n); + transfer.seek = pkt_seek+n; + } + + if (transfer.seek < transfer.status.size) { + transfer.submit_next_packet(); + } else { + Cached_rom *rom = new (heap) + Cached_rom(env, rm, cache, transfer); + destroy(heap, &transfer); + Session_component *session = new (heap) + Session_component(*rom, sessions, transfer.request_id); + if (transfer.diag) + log("deliver fresh \"", transfer.final_path, "\""); + env.parent().deliver_session_cap( + transfer.request_id, env.ep().manage(*session)); + session_requests.schedule(); + } + } + } + + /** + * Attempt a procedure with exception management + */ + template + void try_transfer(Path const &file_path, PROC const &proc) { using namespace File_system; - try { return open(file_path); } + try { proc(); return; } catch (Lookup_failed) { error(file_path, " not found"); } catch (Invalid_handle) { error(file_path, ": invalid handle"); } - catch (Invalid_name) { error(file_path, ": invalid nme"); } + catch (Invalid_name) { error(file_path, ": invalid name"); } catch (Permission_denied) { error(file_path, ": permission denied"); } catch (...) { error(file_path, ": unhandled error"); } throw Service_denied(); @@ -364,55 +444,58 @@ struct Cached_fs_rom::Main final : Genode::Session_request_handler ** Find ROM in cache ** ***********************/ - Session_space::Id const id { pid.value }; - Session_label const label = label_from_args(args.string()); Path const path(label.last_element().string()); - Cached_rom *rom = nullptr; + bool diag = session_diag_from_args(args.string()).enabled; /* lookup the ROM in the cache */ + Cached_rom *rom = nullptr; cache.for_each([&] (Cached_rom &other) { if (!rom && other.path == path) rom = &other; }); - if (!rom) { - File_system::File_handle handle = try_open(path); - File_system::Handle_guard guard(fs, handle); - size_t file_size = fs.status(handle).size; - - while (env.pd().avail_ram().value < file_size || env.pd().avail_caps().value < 8) { - /* drop unused cache entries */ - if (!cache_evict()) break; - } - - rom = new (heap) Cached_rom(cache, env, rm, path, file_size); - } - - if (rom->completed()) { + if (rom) { /* Create new RPC object */ Session_component *session = new (heap) - Session_component(*rom, sessions, id, label); - if (session_diag_from_args(args.string()).enabled) - log("deliver ROM \"", label, "\""); + Session_component(*rom, sessions, pid); + if (diag) + log("deliver cached \"", path, "\" to \"", label, "\""); env.parent().deliver_session_cap(pid, env.ep().manage(*session)); - } else if (!rom->transfer) { - File_system::File_handle handle = try_open(path); + return; + } + + /* find matching transfer */ + bool pending = false; + transfers.for_each([&] (Transfer &other) { + /* symlink race? */ + pending |= other.matches(path); + }); + + if (pending) /* wait until transfer completes */ + return; + + if (diag) + log("lookup \"", path, "\" for \"", label, "\""); + /* initiate new transfer or throw Service_denied */ + try_transfer(path, [&] () { try { - new (heap) Transfer(transfers, *rom, fs, handle, rom->file_size); - } - catch (...) { - Genode::warning("defer transfer of ", rom->path); - fs.close(handle); - /* retry when next pending transfer completes */ + Transfer *transfer = new (heap) + Transfer(env, fs, transfers, path, pid, diag); + resolve(*transfer); + } catch (Packet_alloc_failed) { + /* temporary failure */ return; } - } + }); } + /** + * Destroy a closed session component + */ void handle_session_close(Parent::Server::Id pid) override { Session_space::Id id { pid.value }; @@ -425,6 +508,9 @@ struct Cached_fs_rom::Main final : Genode::Session_request_handler }); } + /** + * Handle pending READ packets + */ void handle_packets() { Tx_source &source = *fs.tx(); @@ -439,12 +525,16 @@ struct Cached_fs_rom::Main final : Genode::Session_request_handler transfers.apply( Transfer_space::Id{pkt.handle().value}, [&] (Transfer &transfer) { - transfer.process_packet(pkt); - if (transfer.completed()) { - session_requests.schedule(); + stray_pkt = false; + try { + try_transfer(transfer.final_path, [&] () { + process_packet(transfer, pkt); + }); + } catch (Service_denied) { + env.parent().session_response( + transfer.request_id, Parent::SERVICE_DENIED); destroy(heap, &transfer); } - stray_pkt = false; }); if (stray_pkt) -- 2.28.0 From 8e994fdb882071d224b1bb2b6efc711571b523f0 Mon Sep 17 00:00:00 2001 From: Emery Hemingway Date: Tue, 3 Nov 2020 17:37:50 +0100 Subject: [PATCH 2/3] cached_fs_rom: add directory session policy Support for directing ROM requests into directories by policy. --- repos/os/src/server/cached_fs_rom/README | 13 +++++++++++++ repos/os/src/server/cached_fs_rom/main.cc | 17 ++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 repos/os/src/server/cached_fs_rom/README diff --git a/repos/os/src/server/cached_fs_rom/README b/repos/os/src/server/cached_fs_rom/README new file mode 100644 index 0000000000..855c20ba7c --- /dev/null +++ b/repos/os/src/server/cached_fs_rom/README @@ -0,0 +1,13 @@ +The 'cached_fs_rom' server is an alternative to 'fs_rom' for serving ROMs +of fixed-content. The server will cache file content between sessions and +clients, however the server does not cache intermediate symlinks. + +Configuration +~~~~~~~~~~~~~ + +The only configuration option is the directory policy variable. + +! +! +! +! diff --git a/repos/os/src/server/cached_fs_rom/main.cc b/repos/os/src/server/cached_fs_rom/main.cc index bac2c075a9..8df3001111 100755 --- a/repos/os/src/server/cached_fs_rom/main.cc +++ b/repos/os/src/server/cached_fs_rom/main.cc @@ -13,6 +13,7 @@ /* Genode includes */ #include +#include #include #include #include @@ -267,9 +268,14 @@ struct Cached_fs_rom::Main final : Genode::Session_request_handler Session_requests_rom session_requests { env, *this }; + Attached_rom_dataspace config_rom { env, "config" }; + Io_signal_handler
packet_handler { env.ep(), *this, &Main::handle_packets }; + Signal_handler config_handler { + env.ep(), config_rom, &Attached_rom_dataspace::update }; + /** * Return true when a cache element is freed */ @@ -444,8 +450,17 @@ struct Cached_fs_rom::Main final : Genode::Session_request_handler ** Find ROM in cache ** ***********************/ + + Path path("/"); Session_label const label = label_from_args(args.string()); - Path const path(label.last_element().string()); + + try { + Session_policy policy(label, config_rom.xml()); + path.append(policy.attribute_value( + "directory", String("/")).string()); + } catch (Service_denied) { } + + path.append_element(label.last_element().string()); bool diag = session_diag_from_args(args.string()).enabled; -- 2.28.0 From c98ba6fd70938804833e5c65a18d01c60f5be17f Mon Sep 17 00:00:00 2001 From: Emery Hemingway Date: Thu, 5 Nov 2020 21:22:31 +0100 Subject: [PATCH 3/3] cached_fs_rom: do not apply directory for absolute ROMs If the ROM name starts with / then do not apply a directory policy. --- repos/os/src/server/cached_fs_rom/main.cc | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/repos/os/src/server/cached_fs_rom/main.cc b/repos/os/src/server/cached_fs_rom/main.cc index 8df3001111..4479b41e87 100755 --- a/repos/os/src/server/cached_fs_rom/main.cc +++ b/repos/os/src/server/cached_fs_rom/main.cc @@ -450,17 +450,18 @@ struct Cached_fs_rom::Main final : Genode::Session_request_handler ** Find ROM in cache ** ***********************/ + Path path; - Path path("/"); - Session_label const label = label_from_args(args.string()); + auto const label = label_from_args(args.string()); + auto const rom_name = label.last_element(); try { Session_policy policy(label, config_rom.xml()); - path.append(policy.attribute_value( - "directory", String("/")).string()); - } catch (Service_denied) { } - - path.append_element(label.last_element().string()); + auto dir = policy.attribute_value("directory", String("/")); + path = Path(rom_name.string(), dir.string()); + } catch (Service_denied) { + path = Path(rom_name.string()); + } bool diag = session_diag_from_args(args.string()).enabled; -- 2.28.0