diff --git a/gems/src/server/file_terminal/README b/gems/src/server/file_terminal/README new file mode 100644 index 000000000..bd7d9bd81 --- /dev/null +++ b/gems/src/server/file_terminal/README @@ -0,0 +1,9 @@ +File terminal is a service that provides Genode's Terminal_session interface +for a given file via a File_system_session. + +! +! +! h +! + +To keep things simple a client can only open one file at the moment. diff --git a/gems/src/server/file_terminal/main.cc b/gems/src/server/file_terminal/main.cc new file mode 100644 index 000000000..4167afb64 --- /dev/null +++ b/gems/src/server/file_terminal/main.cc @@ -0,0 +1,291 @@ +/* + * \brief Service providing the 'Terminal_session' interface for a file + * \author Josef Soentgen + * \date 2013-10-08 + */ + +/* + * 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 + +/* libc includes */ +#include +#include +#include +#include +#include + + +static bool const verbose = false; + +#define PDBGV(...) if (verbose) PDBG(__VA_ARGS__) + + +class Open_file +{ + private: + + /** + * File descriptor for open file + */ + int _fd; + + /** + * Signal handler to be informed about the established connection + */ + Genode::Signal_context_capability _connected_sigh; + + /** + * Signal handler to be informed about data available to read + */ + Genode::Signal_context_capability _read_avail_sigh; + + /** + * Buffer for incoming data + * + * This buffer is used for synchronizing the reception of data in the + * main thread ('watch_sockets_for_incoming_data') and the entrypoint + * thread ('read'). The main thread fills the buffer if its not already + * occupied and the entrypoint thread consumes the buffer. When the + * buffer becomes occupied, the corresponding socket gets removed from + * the 'rfds' set of 'select()' until a read request from the terminal + * client comes in. + */ + enum { READ_BUF_SIZE = 4096 }; + char _read_buf[READ_BUF_SIZE]; + Genode::size_t _read_buf_bytes_used; + + public: + + Open_file(const char *filename) : _fd(-1) + { + PDBGV("open '%s'", filename); + _fd = ::open(filename, O_CREAT|O_RDWR); + if (_fd == -1) + ::perror("open"); + } + + ~Open_file() { close(_fd); } + + /** + * Return file descriptor of the open file + */ + int fd() const { return _fd; } + + /** + * Register signal handler to be notified once we openend the file + */ + void connected_sigh(Genode::Signal_context_capability sigh) + { + _connected_sigh = sigh; + + /* notify the client immediatly if we have already openend the file */ + if (file_opened() && _connected_sigh.valid()) + Genode::Signal_transmitter(_connected_sigh).submit(); + } + + /** + * Register signal handler to be notified when data is available for + * reading + */ + void read_avail_sigh(Genode::Signal_context_capability sigh) + { + _read_avail_sigh = sigh; + + /* if read data is available right now, deliver signal immediately */ + if (!read_buffer_empty() && _read_avail_sigh.valid()) + Genode::Signal_transmitter(_read_avail_sigh).submit(); + } + + /** + * Return true if the file was successfully openend + */ + bool file_opened() const { return _fd != -1; } + + /** + * Fetch data from socket into internal read buffer + */ + void fill_read_buffer_and_notify_client() + { + if (_read_buf_bytes_used) { + PWRN("read buffer already in use"); + return; + } + + /* read from file */ + _read_buf_bytes_used = ::read(_fd, _read_buf, sizeof(_read_buf)); + + /* notify client about bytes available for reading */ + if (_read_avail_sigh.valid()) + Genode::Signal_transmitter(_read_avail_sigh).submit(); + } + + /** + * Flush internal read buffer into destination buffer + */ + Genode::size_t flush_read_buffer(char *dst, Genode::size_t dst_len) + { + Genode::size_t num_bytes = Genode::min(dst_len, _read_buf_bytes_used); + Genode::memcpy(dst, _read_buf, num_bytes); + _read_buf_bytes_used = 0; + return num_bytes; + } + + /** + * Return true if the internal read buffer is ready to receive data + */ + bool read_buffer_empty() const { return _read_buf_bytes_used == 0; } +}; + + +namespace Terminal { + + class Session_component : public Genode::Rpc_object, + public Open_file + { + private: + + Genode::Attached_ram_dataspace _io_buffer; + + public: + + Session_component(Genode::size_t io_buffer_size, const char *filename) + : + Open_file(filename), + _io_buffer(Genode::env()->ram_session(), io_buffer_size) + { } + + /******************************** + ** Terminal session interface ** + ********************************/ + + Size size() { return Size(0, 0); } + + bool avail() + { + return !read_buffer_empty(); + } + + Genode::size_t _read(Genode::size_t dst_len) + { + Genode::size_t num_bytes = + flush_read_buffer(_io_buffer.local_addr(), + Genode::min(_io_buffer.size(), dst_len)); + + return num_bytes; + } + + void _write(Genode::size_t num_bytes) + { + /* sanitize argument */ + num_bytes = Genode::min(num_bytes, _io_buffer.size()); + + /* write data to descriptor */ + if (::write(fd(), _io_buffer.local_addr(), num_bytes) < 0) + PERR("write error, dropping data"); + } + + Genode::Dataspace_capability _dataspace() + { + return _io_buffer.cap(); + } + + void read_avail_sigh(Genode::Signal_context_capability sigh) + { + Open_file::read_avail_sigh(sigh); + } + + void connected_sigh(Genode::Signal_context_capability sigh) + { + Open_file::connected_sigh(sigh); + } + + Genode::size_t read(void *buf, Genode::size_t) { return 0; } + Genode::size_t write(void const *buf, Genode::size_t) { return 0; } + }; + + + class Root_component : public Genode::Root_component + { + protected: + + Session_component *_create_session(const char *args) + { + Genode::size_t io_buffer_size = 4096; + + try { + Genode::Session_label label(args); + Genode::Session_policy policy(label); + + char filename[256]; + policy.attribute("filename").value(filename, sizeof(filename)); + + if (policy.has_attribute("io_buffer_size")) + policy.attribute("io_buffer_size").value(&io_buffer_size); + + return new (md_alloc()) + Session_component(io_buffer_size, filename); + + } catch (Genode::Xml_node::Nonexistent_attribute) { + PERR("Missing \"filename\" attribute in policy definition"); + throw Genode::Root::Unavailable(); + } catch (Genode::Session_policy::No_policy_defined) { + PERR("Invalid session request, no matching policy"); + throw Genode::Root::Unavailable(); + } + } + + public: + + /** + * Constructor + */ + Root_component(Genode::Rpc_entrypoint *ep, + Genode::Allocator *md_alloc) + : + Genode::Root_component(ep, md_alloc) + { } + }; +} + + +int main() +{ + using namespace Genode; + + Genode::printf("--- file terminal started ---\n"); + + /** + * The stack needs to be that large because certain functions + * in the libc (e.g. mktime(3)) require a huge stack. + */ + enum { STACK_SIZE = 16*sizeof(addr_t)*1024 }; + static Cap_connection cap; + static Rpc_entrypoint ep(&cap, STACK_SIZE, "terminal_ep"); + + static Sliced_heap sliced_heap(env()->ram_session(), env()->rm_session()); + + /* create root interface for service */ + static Terminal::Root_component root(&ep, &sliced_heap); + + /* announce service at our parent */ + env()->parent()->announce(ep.manage(&root)); + + Genode::sleep_forever(); + + return 0; +} diff --git a/gems/src/server/file_terminal/target.mk b/gems/src/server/file_terminal/target.mk new file mode 100644 index 000000000..65eeea7a4 --- /dev/null +++ b/gems/src/server/file_terminal/target.mk @@ -0,0 +1,3 @@ +TARGET = file_terminal +SRC_CC = main.cc +LIBS = libc libc_fs libc_log