/* * \brief Terminal file system * \author Christian Prochaska * \author Norman Feske * \author Christian Helmuth * \date 2012-05-23 */ /* * Copyright (C) 2012-2019 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. */ #ifndef _INCLUDE__VFS__TERMINAL_FILE_SYSTEM_H_ #define _INCLUDE__VFS__TERMINAL_FILE_SYSTEM_H_ #include #include #include #include #include #include namespace Vfs { class Terminal_file_system; } struct Vfs::Terminal_file_system { typedef String<64> Name; struct Local_factory; struct Data_file_system; struct Compound_file_system; }; /** * File system node for processing the terminal data read/write streams */ class Vfs::Terminal_file_system::Data_file_system : public Single_file_system { public: /** * Interface for propagating user interrupts (control-c) */ struct Interrupt_handler : Interface { virtual void handle_interrupt() = 0; }; private: Name const _name; Genode::Entrypoint &_ep; Terminal::Connection &_terminal; Interrupt_handler &_interrupt_handler; enum { READ_BUFFER_SIZE = 4000 }; typedef Genode::Ring_buffer Read_buffer; Read_buffer _read_buffer { }; static void _fetch_data_from_terminal(Terminal::Connection &terminal, Read_buffer &read_buffer, Interrupt_handler &interrupt_handler) { while (terminal.avail()) { /* * Copy new data into read buffer, detect user-interrupt * characters (control-c) */ unsigned const buf_size = read_buffer.avail_capacity(); if (buf_size == 0) break; char buf[buf_size]; unsigned const received = terminal.read(buf, buf_size); for (unsigned i = 0; i < received; i++) { char const c = buf[i]; enum { INTERRUPT = 3 }; if (c == INTERRUPT) { interrupt_handler.handle_interrupt(); } else { read_buffer.add(c); } } } } struct Terminal_vfs_handle : Single_vfs_handle { Terminal::Connection &_terminal; Read_buffer &_read_buffer; Interrupt_handler &_interrupt_handler; bool notifying = false; bool blocked = false; Terminal_vfs_handle(Terminal::Connection &terminal, Read_buffer &read_buffer, Interrupt_handler &interrupt_handler, Directory_service &ds, File_io_service &fs, Genode::Allocator &alloc, int flags) : Single_vfs_handle(ds, fs, alloc, flags), _terminal(terminal), _read_buffer(read_buffer), _interrupt_handler(interrupt_handler) { } bool read_ready() override { return !_read_buffer.empty(); } Read_result read(char *dst, file_size count, file_size &out_count) override { if (_read_buffer.empty()) _fetch_data_from_terminal(_terminal, _read_buffer, _interrupt_handler); if (_read_buffer.empty()) { blocked = true; return READ_QUEUED; } unsigned consumed = 0; for (; consumed < count && !_read_buffer.empty(); consumed++) dst[consumed] = _read_buffer.get(); out_count = consumed; return READ_OK; } Write_result write(char const *src, file_size count, file_size &out_count) override { out_count = _terminal.write(src, count); return WRITE_OK; } }; typedef Genode::Registered Registered_handle; typedef Genode::Registry Handle_registry; Handle_registry _handle_registry { }; Genode::Io_signal_handler _read_avail_handler { _ep, *this, &Data_file_system::_handle_read_avail }; void _handle_read_avail() { /* * Fetch as much data from the terminal as possible to detect * user-interrupt characters (control-c), even before the VFS * client attempts to read from the terminal. * * Note that a user interrupt that follows a large chunk of data * (exceeding the capacity of the read buffer) cannot be detected * without reading the data first. In the case where the VFS client * never reads data (e.g., it just blocks for a timeout), * consecutive user interrupts will never be delivered once such a * situation occurs. This can be provoked by pasting a large amount * of text into the terminal. */ _fetch_data_from_terminal(_terminal, _read_buffer, _interrupt_handler); _handle_registry.for_each([this] (Registered_handle &handle) { if (handle.blocked) { handle.blocked = false; handle.io_progress_response(); } if (handle.notifying) { handle.notifying = false; handle.read_ready_response(); } }); } public: Data_file_system(Genode::Entrypoint &ep, Terminal::Connection &terminal, Name const &name, Interrupt_handler &interrupt_handler) : Single_file_system(Node_type::TRANSACTIONAL_FILE, name.string(), Node_rwx::rw(), Genode::Xml_node("")), _name(name), _ep(ep), _terminal(terminal), _interrupt_handler(interrupt_handler) { /* register for read-avail notification */ _terminal.read_avail_sigh(_read_avail_handler); } static const char *name() { return "data"; } char const *type() override { return "data"; } Open_result open(char const *path, unsigned flags, Vfs_handle **out_handle, Allocator &alloc) override { if (!_single_file(path)) return OPEN_ERR_UNACCESSIBLE; try { *out_handle = new (alloc) Registered_handle(_handle_registry, _terminal, _read_buffer, _interrupt_handler, *this, *this, alloc, flags); return OPEN_OK; } catch (Genode::Out_of_ram) { return OPEN_ERR_OUT_OF_RAM; } catch (Genode::Out_of_caps) { return OPEN_ERR_OUT_OF_CAPS; } } /******************************** ** File I/O service interface ** ********************************/ bool notify_read_ready(Vfs_handle *vfs_handle) override { Terminal_vfs_handle *handle = static_cast(vfs_handle); if (!handle) return false; handle->notifying = true; return true; } Ftruncate_result ftruncate(Vfs_handle *, file_size) override { return FTRUNCATE_OK; } bool check_unblock(Vfs_handle *, bool rd, bool wr, bool) override { return ((rd && _terminal.avail()) || wr); } }; struct Vfs::Terminal_file_system::Local_factory : File_system_factory, Data_file_system::Interrupt_handler { typedef Genode::String<64> Label; Label const _label; Name const _name; Genode::Env &_env; Terminal::Connection _terminal { _env, _label.string() }; Data_file_system _data_fs { _env.ep(), _terminal, _name, *this }; struct Info { Terminal::Session::Size size; void print(Genode::Output &out) const { char buf[128] { }; Genode::Xml_generator xml(buf, sizeof(buf), "terminal", [&] () { xml.attribute("rows", size.lines()); xml.attribute("columns", size.columns()); }); Genode::print(out, Genode::Cstring(buf)); } }; /** * Number of occurred user interrupts (control-c) */ unsigned _interrupts = 0; Readonly_value_file_system _info_fs { "info", Info{} }; Readonly_value_file_system _rows_fs { "rows", 0 }; Readonly_value_file_system _columns_fs { "columns", 0 }; Readonly_value_file_system _interrupts_fs { "interrupts", _interrupts }; Genode::Io_signal_handler _size_changed_handler { _env.ep(), *this, &Local_factory::_handle_size_changed }; void _handle_size_changed() { Info const info { .size = _terminal.size() }; _info_fs .value(info); _rows_fs .value(info.size.lines()); _columns_fs.value(info.size.columns()); } /** * Interrupt_handler interface */ void handle_interrupt() override { _interrupts++; _interrupts_fs.value(_interrupts); } static Name name(Xml_node config) { return config.attribute_value("name", Name("terminal")); } Local_factory(Vfs::Env &env, Xml_node config) : _label(config.attribute_value("label", Label(""))), _name(name(config)), _env(env.env()) { _terminal.size_changed_sigh(_size_changed_handler); _handle_size_changed(); } Vfs::File_system *create(Vfs::Env&, Xml_node node) override { if (node.has_type("data")) return &_data_fs; if (node.has_type("info")) return &_info_fs; if (node.has_type("rows")) return &_rows_fs; if (node.has_type("columns")) return &_columns_fs; if (node.has_type("interrupts")) return &_interrupts_fs; return nullptr; } }; class Vfs::Terminal_file_system::Compound_file_system : private Local_factory, public Vfs::Dir_file_system { private: typedef Terminal_file_system::Name Name; typedef String<200> Config; static Config _config(Name const &name) { char buf[Config::capacity()] { }; /* * By not using the node type "dir", we operate the * 'Dir_file_system' in root mode, allowing multiple sibling nodes * to be present at the mount point. */ Genode::Xml_generator xml(buf, sizeof(buf), "compound", [&] () { xml.node("data", [&] () { xml.attribute("name", name); }); xml.node("dir", [&] () { xml.attribute("name", Name(".", name)); xml.node("info", [&] () {}); xml.node("rows", [&] () {}); xml.node("columns", [&] () {}); xml.node("interrupts", [&] () {}); }); }); return Config(Genode::Cstring(buf)); } public: Compound_file_system(Vfs::Env &vfs_env, Genode::Xml_node node) : Local_factory(vfs_env, node), Vfs::Dir_file_system(vfs_env, Xml_node(_config(Local_factory::name(node)).string()), *this) { } static const char *name() { return "terminal"; } char const *type() override { return name(); } }; #endif /* _INCLUDE__VFS__TERMINAL_FILE_SYSTEM_H_ */