/* * \brief VFS pipe plugin * \author Emery Hemingway * \date 2019-05-29 */ /* * Copyright (C) 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. */ #include #include #include #include namespace Vfs_pipe { using namespace Vfs; typedef Vfs::Directory_service::Open_result Open_result; typedef Vfs::File_io_service::Write_result Write_result; typedef Vfs::File_io_service::Read_result Read_result; typedef Genode::Path<32> Path; enum { PIPE_BUF_SIZE = 8192U }; typedef Genode::Ring_buffer Pipe_buffer; struct Pipe_handle; typedef Genode::Fifo_element Handle_element; typedef Genode::Fifo Handle_fifo; typedef Genode::Registry::Element Pipe_handle_registry_element; typedef Genode::Registry Pipe_handle_registry; class Pipe; typedef Genode::Id_space Pipe_space; struct New_pipe_handle; class File_system; } struct Vfs_pipe::Pipe_handle : Vfs::Vfs_handle, private Pipe_handle_registry_element { Pipe &pipe; Handle_element io_progress_elem { *this }; Handle_element read_ready_elem { *this }; bool const writer; Pipe_handle(Vfs::File_system &fs, Genode::Allocator &alloc, unsigned flags, Pipe_handle_registry ®istry, Pipe &p) : Vfs::Vfs_handle(fs, fs, alloc, flags), Pipe_handle_registry_element(registry, *this), pipe(p), writer(flags == Directory_service::OPEN_MODE_WRONLY) { } virtual ~Pipe_handle(); Write_result write(const char *buf, file_size count, file_size &out_count); Read_result read(char *buf, file_size count, file_size &out_count); bool read_ready(); bool notify_read_ready(); }; struct Vfs_pipe::Pipe { Genode::Allocator &alloc; Pipe_space::Element space_elem; Pipe_buffer buffer { }; Pipe_handle_registry registry { }; Handle_fifo io_progress_waiters { }; Handle_fifo read_ready_waiters { }; unsigned num_writers = 0; Genode::Signal_context_capability ¬ify_sigh; bool new_handle_active { true }; Pipe(Genode::Allocator &alloc, Pipe_space &space, Genode::Signal_context_capability ¬ify_sigh) : alloc(alloc), space_elem(*this, space), notify_sigh(notify_sigh) { } ~Pipe() { } typedef Genode::String<8> Name; Name name() const { return Name(space_elem.id().value); } /** * Check if pipe is referenced, if not, destroy */ void cleanup() { bool alive = new_handle_active; if (!alive) registry.for_each([&alive] (Pipe_handle&) { alive = true; }); if (!alive) destroy(alloc, this); } /** * Remove "/new" handle reference */ void remove_new_handle() { new_handle_active = false; } /** * Detach a handle */ void remove(Pipe_handle &handle) { if (handle.io_progress_elem.enqueued()) io_progress_waiters.remove(handle.io_progress_elem); if (handle.read_ready_elem.enqueued()) read_ready_waiters.remove(handle.read_ready_elem); } /** * Open a write or read handle */ Open_result open(Vfs::File_system &fs, Path const &filename, Vfs::Vfs_handle **handle, Genode::Allocator &alloc) { if (filename == "/in") { *handle = new (alloc) Pipe_handle(fs, alloc, Directory_service::OPEN_MODE_WRONLY, registry, *this); num_writers++; return Open_result::OPEN_OK; } if (filename == "/out") { *handle = new (alloc) Pipe_handle(fs, alloc, Directory_service::OPEN_MODE_RDONLY, registry, *this); return Open_result::OPEN_OK; } return Open_result::OPEN_ERR_UNACCESSIBLE; } /** * Use a signal as a hack to defer notifications * until the "io_progress_handler". */ void submit_signal() { Genode::Signal_transmitter(notify_sigh).submit(); } /** * Notify handles waiting for activity */ void notify() { io_progress_waiters.dequeue_all([] (Handle_element &elem) { elem.object().io_progress_response(); }); read_ready_waiters.dequeue_all([] (Handle_element &elem) { elem.object().read_ready_response(); }); } Write_result write(Pipe_handle &handle, const char *buf, file_size count, file_size &out_count) { file_size out = 0; bool notify = buffer.empty(); while (out < count && 0 < buffer.avail_capacity()) { buffer.add(*(buf++)); ++out; } out_count = out; if (out < count) io_progress_waiters.enqueue(handle.io_progress_elem); if (notify) submit_signal(); return Write_result::WRITE_OK; } Read_result read(Pipe_handle &handle, char *buf, file_size count, file_size &out_count) { bool notify = buffer.avail_capacity() == 0; file_size out = 0; while (out < count && !buffer.empty()) { *(buf++) = buffer.get(); ++out; } out_count = out; if (!out) { if (num_writers == 0) return Read_result::READ_OK; /* EOF */ io_progress_waiters.enqueue(handle.io_progress_elem); return Read_result::READ_QUEUED; } if (notify) submit_signal(); return Read_result::READ_OK; } }; Vfs_pipe::Pipe_handle::~Pipe_handle() { pipe.remove(*this); } Vfs_pipe::Write_result Vfs_pipe::Pipe_handle::write(const char *buf, file_size count, file_size &out_count) { return Pipe_handle::pipe.write(*this, buf, count, out_count); } Vfs_pipe::Read_result Vfs_pipe::Pipe_handle::read(char *buf, file_size count, file_size &out_count) { return Pipe_handle::pipe.read(*this, buf, count, out_count); } bool Vfs_pipe::Pipe_handle::read_ready() { return !pipe.buffer.empty(); } bool Vfs_pipe::Pipe_handle::notify_read_ready() { if (!read_ready_elem.enqueued()) pipe.read_ready_waiters.enqueue(read_ready_elem); return true; } struct Vfs_pipe::New_pipe_handle : Vfs::Vfs_handle { Pipe &pipe; New_pipe_handle(Vfs::File_system &fs, Genode::Allocator &alloc, unsigned flags, Pipe_space &pipe_space, Genode::Signal_context_capability ¬ify_sigh) : Vfs::Vfs_handle(fs, fs, alloc, flags), pipe(*(new (alloc) Pipe(alloc, pipe_space, notify_sigh))) { } ~New_pipe_handle() { pipe.remove_new_handle(); } Read_result read(char *buf, file_size count, file_size &out_count) { auto name = pipe.name(); if (name.length() < count) { memcpy(buf, name.string(), name.length()); out_count = name.length(); return Read_result::READ_OK; } return Read_result::READ_ERR_INVALID; } }; class Vfs_pipe::File_system : public Vfs::File_system { private: Pipe_space _pipe_space { }; /* * XXX: a hack to defer cross-thread notifications at * the libc until the io_progress handler */ Genode::Io_signal_handler _notify_handler; Genode::Signal_context_capability _notify_cap { _notify_handler }; void _notify_any() { _pipe_space.for_each([] (Pipe &pipe) { pipe.notify(); }); } public: File_system(Vfs::Env &env) : _notify_handler(env.env().ep(), *this, &File_system::_notify_any) { } const char* type() override { return "pipe"; } /*********************** ** Directory service ** ***********************/ Genode::Dataspace_capability dataspace(char const*) override { return Genode::Dataspace_capability(); } void release(char const*, Dataspace_capability) override { } Open_result open(const char *cpath, unsigned mode, Vfs::Vfs_handle **handle, Genode::Allocator &alloc) override { Path path(cpath); if (path == "/new") { if ((Directory_service::OPEN_MODE_ACCMODE & mode) == Directory_service::OPEN_MODE_WRONLY) return Open_result::OPEN_ERR_NO_PERM; *handle = new (alloc) New_pipe_handle(*this, alloc, mode, _pipe_space, _notify_cap); return Open_result::OPEN_OK; } path.strip_last_element(); if (!path.has_single_element()) return Open_result::OPEN_ERR_UNACCESSIBLE; Pipe_space::Id id { ~0UL }; if (!ascii_to(path.last_element(), id.value)) return Open_result::OPEN_ERR_UNACCESSIBLE; Open_result result = Open_result::OPEN_ERR_UNACCESSIBLE; try { _pipe_space.apply(id, [&] (Pipe &pipe) { Path filename(cpath); filename.keep_only_last_element(); result = pipe.open(*this, filename, handle, alloc); }); } catch (Pipe_space::Unknown_id) { } return result; } Opendir_result opendir(char const *cpath, bool create, Vfs_handle **handle, Allocator &alloc) override { /* open dummy handles on directories */ if (create) return OPENDIR_ERR_PERMISSION_DENIED; Path path(cpath); if (path == "/") { *handle = new (alloc) Vfs_handle(*this, *this, alloc, 0); return OPENDIR_OK; } Opendir_result result { OPENDIR_ERR_LOOKUP_FAILED }; if (path.has_single_element()) { Pipe_space::Id id { ~0UL }; if (ascii_to(path.last_element(), id.value)) try { _pipe_space.apply(id, [&] (Pipe&) { *handle = new (alloc) Vfs_handle(*this, *this, alloc, 0); result = OPENDIR_OK; }); } catch (Pipe_space::Unknown_id) { } } return result; } void close(Vfs::Vfs_handle *vfs_handle) override { Pipe *pipe = nullptr; if (Pipe_handle *handle = dynamic_cast(vfs_handle)) { pipe = &handle->pipe; if (handle->writer) pipe->num_writers--; } else if (New_pipe_handle *handle = dynamic_cast(vfs_handle)) pipe = &handle->pipe; destroy(vfs_handle->alloc(), vfs_handle); if (pipe) pipe->cleanup(); } Stat_result stat(const char *cpath, Stat &out) override { Stat_result result { STAT_ERR_NO_ENTRY }; Path path(cpath); out = Stat { }; if (path == "/new") { out = Stat { .size = 1, .type = Node_type::TRANSACTIONAL_FILE, .rwx = Node_rwx::wo(), .inode = Genode::addr_t(this), .device = Genode::addr_t(this), .modification_time = { } }; return STAT_OK; } if (path.has_single_element()) { Pipe_space::Id id { ~0UL }; if (ascii_to(path.last_element(), id.value)) try { _pipe_space.apply(id, [&] (Pipe &pipe) { out = Stat { .size = 2, .type = Node_type::DIRECTORY, .rwx = Node_rwx::rwx(), .inode = Genode::addr_t(&pipe), .device = Genode::addr_t(this), .modification_time = { } }; result = STAT_OK; }); } catch (Pipe_space::Unknown_id) { } } else { /* maybe this is /N/in or /N/out */ path.strip_last_element(); if (!path.has_single_element()) /* too many directory levels */ return result; Pipe_space::Id id { ~0UL }; if (ascii_to(path.last_element(), id.value)) try { _pipe_space.apply(id, [&] (Pipe &pipe) { Path filename(cpath); filename.keep_only_last_element(); if (filename == "/in") { out = Stat { .size = file_size(pipe.buffer.avail_capacity()), .type = Node_type::CONTINUOUS_FILE, .rwx = Node_rwx::wo(), .inode = Genode::addr_t(&pipe) + 1, .device = Genode::addr_t(this), .modification_time = { } }; result = STAT_OK; } else if (filename == "/out") { out = Stat { .size = file_size(PIPE_BUF_SIZE - pipe.buffer.avail_capacity()), .type = Node_type::CONTINUOUS_FILE, .rwx = Node_rwx::ro(), .inode = Genode::addr_t(&pipe) + 2, .device = Genode::addr_t(this), .modification_time = { } }; result = STAT_OK; } }); } catch (Pipe_space::Unknown_id) { } } return result; } Unlink_result unlink(const char*) override { return UNLINK_ERR_NO_ENTRY; } Rename_result rename(const char*, const char*) override { return RENAME_ERR_NO_ENTRY; } file_size num_dirent(char const *) override { return 0; } bool directory(char const *cpath) override { Path path(cpath); if (path == "/") return true; if (!path.has_single_element()) return Open_result::OPEN_ERR_UNACCESSIBLE; Pipe_space::Id id { ~0UL }; if (!ascii_to(path.last_element(), id.value)) return false; bool result = false; try { _pipe_space.apply(id, [&] (Pipe &) { result = true; }); } catch (Pipe_space::Unknown_id) { } return result; } const char* leaf_path(const char *cpath) override { Path path(cpath); if (path == "/") return cpath; if (path == "/new") return cpath; char const *result = nullptr; if (!path.has_single_element()) { /* maybe this is /N/in or /N/out */ path.strip_last_element(); if (!path.has_single_element()) /* too many directory levels */ return nullptr; Path filename(cpath); filename.keep_only_last_element(); if (filename != "/in" && filename != "/out") /* not a pipe file */ return nullptr; } Pipe_space::Id id { ~0UL }; if (ascii_to(path.last_element(), id.value)) try { /* check if the pipe directory exists */ _pipe_space.apply(id, [&] (Pipe &) { result = cpath; }); } catch (Pipe_space::Unknown_id) { } return result; } /********************** ** File I/O service ** **********************/ Write_result write(Vfs_handle *vfs_handle, const char *src, file_size count, file_size &out_count) override { if (Pipe_handle *handle = dynamic_cast(vfs_handle)) return handle->write(src, count, out_count); return WRITE_ERR_INVALID; } Read_result complete_read(Vfs_handle *vfs_handle, char *dst, file_size count, file_size &out_count) override { if (Pipe_handle *handle = dynamic_cast(vfs_handle)) return handle->read(dst, count, out_count); if (New_pipe_handle *handle = dynamic_cast(vfs_handle)) return handle->read(dst, count, out_count); return READ_ERR_INVALID; } bool read_ready(Vfs_handle *vfs_handle) override { if (Pipe_handle *handle = dynamic_cast(vfs_handle)) return handle->read_ready(); return true; } bool notify_read_ready(Vfs_handle *vfs_handle) override { if (Pipe_handle *handle = dynamic_cast(vfs_handle)) return handle->notify_read_ready(); return false; } Ftruncate_result ftruncate(Vfs_handle*, file_size) override { return FTRUNCATE_ERR_NO_PERM; } Sync_result complete_sync(Vfs_handle*) override { return SYNC_OK; } }; extern "C" Vfs::File_system_factory *vfs_file_system_factory(void) { struct Factory : Vfs::File_system_factory { Vfs::File_system *create(Vfs::Env &env, Genode::Xml_node) override { return new (env.alloc()) Vfs_pipe::File_system(env); } }; static Factory f; return &f; }