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