From 3ff0efd62757a38d56c9f00100df7490bbd08f76 Mon Sep 17 00:00:00 2001 From: Sid Hussmann Date: Fri, 16 Oct 2020 19:29:21 +0200 Subject: [PATCH] vfs/pipe: add fifo feature to pipe plugin The vfs pipe plugin can now be used as named pipe which anables data transfer via file handles from one component to another. E.g. if one would like to send data from component A to stdin of a libc component B, one can do so by simply writing to that fifo file. Issue #3583 --- repos/gems/run/depot_autopilot.run | 1 + repos/gems/src/lib/vfs/pipe/README | 41 ++ repos/gems/src/lib/vfs/pipe/plugin.cc | 476 +++++++++++++----- .../recipes/pkg/test-libc_fifo_pipe/README | 1 + .../recipes/pkg/test-libc_fifo_pipe/archives | 7 + .../recipes/pkg/test-libc_fifo_pipe/hash | 1 + .../recipes/pkg/test-libc_fifo_pipe/runtime | 159 ++++++ .../src/test-libc_fifo_pipe/content.mk | 2 + .../recipes/src/test-libc_fifo_pipe/hash | 1 + .../recipes/src/test-libc_fifo_pipe/used_apis | 5 + .../libports/src/test/libc_fifo_pipe/main.cc | 324 ++++++++++++ .../src/test/libc_fifo_pipe/target.mk | 9 + repos/os/include/os/path.h | 2 +- 13 files changed, 903 insertions(+), 126 deletions(-) create mode 100644 repos/libports/recipes/pkg/test-libc_fifo_pipe/README create mode 100644 repos/libports/recipes/pkg/test-libc_fifo_pipe/archives create mode 100644 repos/libports/recipes/pkg/test-libc_fifo_pipe/hash create mode 100644 repos/libports/recipes/pkg/test-libc_fifo_pipe/runtime create mode 100644 repos/libports/recipes/src/test-libc_fifo_pipe/content.mk create mode 100644 repos/libports/recipes/src/test-libc_fifo_pipe/hash create mode 100644 repos/libports/recipes/src/test-libc_fifo_pipe/used_apis create mode 100644 repos/libports/src/test/libc_fifo_pipe/main.cc create mode 100644 repos/libports/src/test/libc_fifo_pipe/target.mk diff --git a/repos/gems/run/depot_autopilot.run b/repos/gems/run/depot_autopilot.run index fac2d814da..ad02980d8e 100644 --- a/repos/gems/run/depot_autopilot.run +++ b/repos/gems/run/depot_autopilot.run @@ -676,6 +676,7 @@ set default_test_pkgs { test-libc_connect_vfs_server_lxip test-libc_counter test-libc_execve + test-libc_fifo_pipe test-libc_fork test-libc_getenv test-libc_pipe diff --git a/repos/gems/src/lib/vfs/pipe/README b/repos/gems/src/lib/vfs/pipe/README index 95e40e6fc0..a3decd105c 100644 --- a/repos/gems/src/lib/vfs/pipe/README +++ b/repos/gems/src/lib/vfs/pipe/README @@ -10,3 +10,44 @@ The read and write capacity of a pipe may be queried by stat'ing the size of When all "in" and "out" handles on a pipe as well as the initial handle on "new" are closed, the pipe is destroyed. + +The pipe plugin can also be used as named pipe which enables data transfer via +file handles from one component to another. E.g. you can attach stdout of a libc +component to a named pipe and write data using standard fwrite() calls. + +Here a sample scenario where stdin of a component is attached to a named pipe: + +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! + +The fifo named 'upstream' represents a named pipe file which can be opened with +read-only or write-only flags. + +A remote component can now write to '/dev/pipe/upstream' so the data written is +then accessible on stdin within "my-libc-app". Once the remote component closes +the '/dev/pipe/upstream' file, an EOF is received on stdin of "my-libc-app". An +example can be found in in run/vfs_fifo_pipe.run. diff --git a/repos/gems/src/lib/vfs/pipe/plugin.cc b/repos/gems/src/lib/vfs/pipe/plugin.cc index 0592e3b065..5b08dcf73a 100644 --- a/repos/gems/src/lib/vfs/pipe/plugin.cc +++ b/repos/gems/src/lib/vfs/pipe/plugin.cc @@ -1,11 +1,13 @@ /* * \brief VFS pipe plugin * \author Emery Hemingway + * \author Sid Hussmann * \date 2019-05-29 */ /* * Copyright (C) 2019 Genode Labs GmbH + * Copyright (C) 2020 gapfruit AG * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU Affero General Public License version 3. @@ -21,7 +23,7 @@ namespace Vfs_pipe { 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; + typedef Genode::Path Path; enum { PIPE_BUF_SIZE = 8192U }; typedef Genode::Ring_buffer Pipe_buffer; @@ -32,12 +34,14 @@ namespace Vfs_pipe { typedef Genode::Registry::Element Pipe_handle_registry_element; typedef Genode::Registry Pipe_handle_registry; - class Pipe; + struct Pipe; typedef Genode::Id_space Pipe_space; struct New_pipe_handle; class File_system; + class Pipe_file_system; + class Fifo_file_system; } @@ -86,6 +90,7 @@ struct Vfs_pipe::Pipe Handle_fifo io_progress_waiters { }; Handle_fifo read_ready_waiters { }; unsigned num_writers = 0; + bool waiting_for_writers = true; Genode::Signal_context_capability ¬ify_sigh; @@ -95,7 +100,7 @@ struct Vfs_pipe::Pipe Genode::Signal_context_capability ¬ify_sigh) : alloc(alloc), space_elem(*this, space), notify_sigh(notify_sigh) { } - ~Pipe() { } + ~Pipe() = default; typedef Genode::String<8> Name; Name name() const @@ -142,15 +147,29 @@ struct Vfs_pipe::Pipe Genode::Allocator &alloc) { if (filename == "/in") { + + if (0 == num_writers) { + /* flush buffer */ + if (!buffer.empty()) { + Genode::warning("flushing non-empty buffer. capacity=", buffer.avail_capacity()); + } + buffer.reset(); + io_progress_waiters.dequeue_all([] (Handle_element /*&elem*/) { }); + } *handle = new (alloc) Pipe_handle(fs, alloc, Directory_service::OPEN_MODE_WRONLY, registry, *this); num_writers++; + waiting_for_writers = false; return Open_result::OPEN_OK; } if (filename == "/out") { *handle = new (alloc) Pipe_handle(fs, alloc, Directory_service::OPEN_MODE_RDONLY, registry, *this); + + if (0 == num_writers && buffer.empty()) { + waiting_for_writers = true; + } return Open_result::OPEN_OK; } @@ -212,7 +231,8 @@ struct Vfs_pipe::Pipe out_count = out; if (!out) { - if (num_writers == 0) + /* Send only EOF when at least one writer opened the pipe */ + if ((num_writers == 0) && !waiting_for_writers) return Read_result::READ_OK; /* EOF */ io_progress_waiters.enqueue(handle.io_progress_elem); @@ -294,7 +314,7 @@ struct Vfs_pipe::New_pipe_handle : Vfs::Vfs_handle class Vfs_pipe::File_system : public Vfs::File_system { - private: + protected: Pipe_space _pipe_space { }; @@ -311,10 +331,26 @@ class Vfs_pipe::File_system : public Vfs::File_system pipe.notify(); }); } + /* + * verifies if a path meets access control requirements + */ + virtual bool _valid_path(const char* cpath) const = 0; + + virtual bool _pipe_id(const char* cpath, Pipe_space::Id &id) const = 0; + + template + void _try_apply(Pipe_space::Id id, FN const &fn) + { + try { _pipe_space.apply(id, fn); } + catch (Pipe_space::Unknown_id) { } + } + public: File_system(Vfs::Env &env) - : _notify_handler(env.env().ep(), *this, &File_system::_notify_any) { } + : + _notify_handler(env.env().ep(), *this, &File_system::_notify_any) + { } const char* type() override { return "pipe"; } @@ -332,66 +368,75 @@ class Vfs_pipe::File_system : public Vfs::File_system 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; + if (mode & OPEN_MODE_CREATE) { + Genode::warning("cannot open fifo pipe with OPEN_MODE_CREATE"); + return OPEN_ERR_NO_PERM; } - path.strip_last_element(); - if (!path.has_single_element()) - return Open_result::OPEN_ERR_UNACCESSIBLE; + if (!((mode == Open_mode::OPEN_MODE_RDONLY) || + (mode == Open_mode::OPEN_MODE_WRONLY))) { + Genode::error("pipe only supports opening with WO or RO mode"); + return OPEN_ERR_NO_PERM; + } + if (!_valid_path(cpath)) + return OPEN_ERR_UNACCESSIBLE; + + Path const path { cpath }; + if (!path.has_single_element()) { + /* + * find out if the last element is "/in" or "/out" + * and enforce read/write policy + */ + Path io { cpath }; + io.keep_only_last_element(); + + if (io == "/in" && mode != Open_mode::OPEN_MODE_WRONLY) + return OPEN_ERR_NO_PERM; + if (io == "/out" && mode != Open_mode::OPEN_MODE_RDONLY) + return OPEN_ERR_NO_PERM; + } + + auto 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); + if (_pipe_id(cpath, id)) { + _try_apply(id, [&mode, &result, this, &handle, &alloc] (Pipe &pipe) { + auto const type { (mode == OPEN_MODE_RDONLY) ? "/out" : "/in" }; + result = pipe.open(*this, type, handle, alloc); }); } - catch (Pipe_space::Unknown_id) { } return result; } Opendir_result opendir(char const *cpath, bool create, - Vfs_handle **handle, - Allocator &alloc) override + Vfs_handle **handle, + Allocator &alloc) override { /* open dummy handles on directories */ + if (create) + return OPENDIR_ERR_PERMISSION_DENIED; - if (create) return OPENDIR_ERR_PERMISSION_DENIED; - Path path(cpath); - - if (path == "/") { + Path io { cpath }; + if (io == "/") { *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) { } + auto result { OPENDIR_ERR_PERMISSION_DENIED }; + /* create a path that matches with _pipe_id() */ + Path pseudo_path { cpath }; + io.keep_only_last_element(); + pseudo_path.append(io.string()); + Pipe_space::Id id { ~0UL }; + if (_pipe_id(pseudo_path.string(), id)) { + _try_apply(id, [&handle, &alloc, this, &result] (Pipe &/*pipe*/) { + *handle = new (alloc) + Vfs_handle(*this, *this, alloc, 0); + result = OPENDIR_OK; + }); } - return result; } @@ -419,31 +464,22 @@ class Vfs_pipe::File_system : public Vfs::File_system 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 (!_valid_path(cpath)) + return STAT_ERR_NO_ENTRY; + + Stat_result result { STAT_ERR_NO_ENTRY }; + Path const path { cpath }; 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) { + if (_pipe_id(cpath, id)) { + _try_apply(id, [&out, this, &result] (Pipe const &pipe) { out = Stat { - .size = 2, - .type = Node_type::DIRECTORY, - .rwx = Node_rwx::rwx(), + .size = file_size(0), + .type = Node_type::CONTINUOUS_FILE, + .rwx = Node_rwx::rw(), .inode = Genode::addr_t(&pipe), .device = Genode::addr_t(this), .modification_time = { } @@ -451,20 +487,15 @@ class Vfs_pipe::File_system : public Vfs::File_system 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; + /* find out if the last element is "/in" or "/out" */ + Path io { cpath }; + io.keep_only_last_element(); 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") { + if (_pipe_id(cpath, id)) { + _try_apply(id, [&io, &out, this, &result] (Pipe const &pipe) { + if (io == "/in") { out = Stat { .size = file_size(pipe.buffer.avail_capacity()), .type = Node_type::CONTINUOUS_FILE, @@ -475,7 +506,7 @@ class Vfs_pipe::File_system : public Vfs::File_system }; result = STAT_OK; } else - if (filename == "/out") { + if (io == "/out") { out = Stat { .size = file_size(PIPE_BUF_SIZE - pipe.buffer.avail_capacity()), @@ -489,7 +520,6 @@ class Vfs_pipe::File_system : public Vfs::File_system } }); } - catch (Pipe_space::Unknown_id) { } } return result; @@ -503,60 +533,28 @@ class Vfs_pipe::File_system : public Vfs::File_system 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; + Path const path { cpath }; + if (path == "/") + 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; - } + if (!_valid_path(cpath)) + return nullptr; + char const *result { 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; }); + if (_pipe_id(cpath, id)) { + _try_apply(id, [&result, &cpath] (Pipe &) { + result = cpath; + }); } - catch (Pipe_space::Unknown_id) { } return result; } + + /********************** ** File I/O service ** **********************/ @@ -606,14 +604,242 @@ class Vfs_pipe::File_system : public Vfs::File_system }; +class Vfs_pipe::Pipe_file_system : public Vfs_pipe::File_system +{ + protected: + + virtual bool _pipe_id(const char* cpath, Pipe_space::Id &id) const override + { + return 0 != ascii_to(cpath + 1, id.value); + } + + bool _valid_path(const char *cpath) const override + { + /* + * a valid pipe path is either + * "/pipe_number", + * "/pipe_number/in" + * or + * "/pipe_number/out" + */ + Path io { cpath }; + if (io.has_single_element()) + return true; + + io.keep_only_last_element(); + if ((io == "/in" || io == "/out")) + return true; + + return false; + } + + public: + + Pipe_file_system(Vfs::Env &env) + : + File_system(env) + { } + + Open_result open(const char *cpath, + unsigned mode, + Vfs::Vfs_handle **handle, + Genode::Allocator &alloc) override + { + Path const path { cpath }; + + if (path == "/new") { + if ((OPEN_MODE_ACCMODE & mode) == OPEN_MODE_WRONLY) + return OPEN_ERR_NO_PERM; + *handle = new (alloc) + New_pipe_handle(*this, alloc, mode, _pipe_space, _notify_cap); + return OPEN_OK; + } + + return File_system::open(cpath, mode, handle, alloc); + } + + Stat_result stat(const char *cpath, Stat &out) override + { + out = Stat { }; + Path const path { cpath }; + + if (path == "/new") { + out = Stat { + .size = 1, + .type = Node_type::TRANSACTIONAL_FILE, + .rwx = Node_rwx::ro(), + .inode = Genode::addr_t(this), + .device = Genode::addr_t(this), + .modification_time = { } + }; + return STAT_OK; + } + + return File_system::stat(cpath, out); + } + + bool directory(char const *cpath) override + { + Path const path { cpath }; + if (path == "/") return true; + if (path == "/new") return false; + + if (!path.has_single_element()) return false; + + bool result { false }; + Pipe_space::Id id { ~0UL }; + if (_pipe_id(cpath, id)) { + _try_apply(id, [&result] (Pipe &) { + result = true; + }); + } + + return result; + } + + const char* leaf_path(const char *cpath) override + { + Path path { cpath }; + if (path == "/new") + return cpath; + + return File_system::leaf_path(cpath); + } +}; + + +class Vfs_pipe::Fifo_file_system : public Vfs_pipe::File_system +{ + private: + + struct Fifo_item + { + Genode::Registry::Element _element; + Path const path; + Pipe_space::Id const id; + + Fifo_item(Genode::Registry ®istry, + Path const &path, Pipe_space::Id const &id) + : + _element(registry, *this), path(path), id(id) + { } + }; + + Vfs::Env &_env; + Genode::Registry _items { }; + + protected: + + bool _valid_path(const char *cpath) const override + { + /* + * either we have no access control (single file in path) + * or we need to verify access control + */ + Path io { cpath }; + if (io.has_single_element()) + return true; + + /* + * a valid access control path is either + * "/.pipename/in/in" + * or + * "/.pipename/out/out" + */ + if (io.base()[1] != '.') + return false; + + io.strip_last_element(); + if (io.has_single_element()) + return false; + + io.keep_only_last_element(); + if (!(io == "/in" || io == "/out")) + return false; + + Path io_file { cpath }; + io_file.keep_only_last_element(); + if (io_file == io) + return true; + + return false; + } + + virtual bool _pipe_id(const char* cpath, Pipe_space::Id &id) const override + { + Path path { cpath }; + if (!path.has_single_element()) { + /* remove /in/in or /out/out */ + path.strip_last_element(); + path.strip_last_element(); + /* remove the "." from /.pipe_name */ + if (strlen(path.base()) <= 2) + return false; + path = Path { path.base() + 2 }; + } + + bool result { false }; + _items.for_each([&path, &id, &result] (Fifo_item const &item) { + if (item.path == path) { + id = item.id; + result = true; + } + }); + return result; + } + + public: + + Fifo_file_system(Vfs::Env &env, Genode::Xml_node const &config) + : + File_system(env), _env(env) + { + config.for_each_sub_node("fifo", [&env, this] (Xml_node const &fifo) { + Path const path { fifo.attribute_value("name", String()) }; + + Pipe &pipe = *new (env.alloc()) + Pipe(env.alloc(), _pipe_space, _notify_cap); + new (env.alloc()) + Fifo_item(_items, path, pipe.space_elem.id()); + }); + } + + ~Fifo_file_system() + { + _items.for_each([this] (Fifo_item &item) { + destroy(_env.alloc(), &item); + }); + } + + bool directory(char const *cpath) override + { + Path const path { cpath }; + if (path == "/") return true; + if (_valid_path(cpath)) return false; + + Path io { cpath }; + io.keep_only_last_element(); + if (io == "/in") return true; + if (io == "/out") return true; + if (!path.has_single_element()) return false; + + return false; + } + +}; + + 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 + Vfs::File_system *create(Vfs::Env &env, Genode::Xml_node node) override { - return new (env.alloc()) - Vfs_pipe::File_system(env); + if (node.has_sub_node("fifo")) { + return new (env.alloc()) Vfs_pipe::Fifo_file_system(env, node); + } else { + return new (env.alloc()) Vfs_pipe::Pipe_file_system(env); + } } }; diff --git a/repos/libports/recipes/pkg/test-libc_fifo_pipe/README b/repos/libports/recipes/pkg/test-libc_fifo_pipe/README new file mode 100644 index 0000000000..1d64907bd8 --- /dev/null +++ b/repos/libports/recipes/pkg/test-libc_fifo_pipe/README @@ -0,0 +1 @@ +Test for using the libc with the fifo feature of the VFS pipe plugin. diff --git a/repos/libports/recipes/pkg/test-libc_fifo_pipe/archives b/repos/libports/recipes/pkg/test-libc_fifo_pipe/archives new file mode 100644 index 0000000000..906daefb4f --- /dev/null +++ b/repos/libports/recipes/pkg/test-libc_fifo_pipe/archives @@ -0,0 +1,7 @@ +_/src/fs_rom +_/src/init +_/src/libc +_/src/report_rom +_/src/test-libc_fifo_pipe +_/src/vfs +_/src/vfs_pipe diff --git a/repos/libports/recipes/pkg/test-libc_fifo_pipe/hash b/repos/libports/recipes/pkg/test-libc_fifo_pipe/hash new file mode 100644 index 0000000000..83a0d4868b --- /dev/null +++ b/repos/libports/recipes/pkg/test-libc_fifo_pipe/hash @@ -0,0 +1 @@ +2021-01-12 f2df8c7f21d8701352f73823355c70c515aa7857 diff --git a/repos/libports/recipes/pkg/test-libc_fifo_pipe/runtime b/repos/libports/recipes/pkg/test-libc_fifo_pipe/runtime new file mode 100644 index 0000000000..ed11bc4144 --- /dev/null +++ b/repos/libports/recipes/pkg/test-libc_fifo_pipe/runtime @@ -0,0 +1,159 @@ + + + + + --- test succeeded --- + Error: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/repos/libports/recipes/src/test-libc_fifo_pipe/content.mk b/repos/libports/recipes/src/test-libc_fifo_pipe/content.mk new file mode 100644 index 0000000000..8ac3f04cb2 --- /dev/null +++ b/repos/libports/recipes/src/test-libc_fifo_pipe/content.mk @@ -0,0 +1,2 @@ +SRC_DIR = src/test/libc_fifo_pipe +include $(GENODE_DIR)/repos/base/recipes/src/content.inc diff --git a/repos/libports/recipes/src/test-libc_fifo_pipe/hash b/repos/libports/recipes/src/test-libc_fifo_pipe/hash new file mode 100644 index 0000000000..3d650209df --- /dev/null +++ b/repos/libports/recipes/src/test-libc_fifo_pipe/hash @@ -0,0 +1 @@ +2021-01-12 ce7c08e54a4feeaead3b13ae08f4ce25b4be435c diff --git a/repos/libports/recipes/src/test-libc_fifo_pipe/used_apis b/repos/libports/recipes/src/test-libc_fifo_pipe/used_apis new file mode 100644 index 0000000000..6b3a6dfd4e --- /dev/null +++ b/repos/libports/recipes/src/test-libc_fifo_pipe/used_apis @@ -0,0 +1,5 @@ +base +libc +os +report_session +vfs diff --git a/repos/libports/src/test/libc_fifo_pipe/main.cc b/repos/libports/src/test/libc_fifo_pipe/main.cc new file mode 100644 index 0000000000..8b61a22941 --- /dev/null +++ b/repos/libports/src/test/libc_fifo_pipe/main.cc @@ -0,0 +1,324 @@ +/* + * \brief libc_fifo_pipe test + * \author Sid Hussmann + * \date 2019-12-12 + */ + +/* + * Copyright (C) 2018-2020 gapfruit AG + * 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. + */ + +/* Genode includes */ +#include +#include +#include +#include +#include +#include + + +/* libc includes */ +#include +#include +#include +#include +#include +#include + +namespace Test_fifo_pipe { + using namespace Genode; + class Main; + class Test; + class Echo; + static char const* TEST_DATA_FILENAME { "/ro/test-data.bin" }; + static char const* SEND_FILENAME { "/dev/send-pipe/in" }; + static char const* RECEIVE_FILENAME { "/dev/receive-pipe/out" }; + enum { BUF_SIZE = 4*1024 }; +} + +static size_t copy(int src, int dest) +{ + using namespace Genode; + size_t total = 0; + char buf[Test_fifo_pipe::BUF_SIZE] { }; + while (true) { + auto const nr = read(src, buf, sizeof(buf)); + if (nr == 0) { + break; + } + + if (nr < 0) { + int res = errno; + error((char const *)strerror(res)); + exit(res); + } + + auto remain = nr; + auto off = 0; + while (remain > 0) { + auto const nw = write(dest, buf+off, remain); + if (nw < 0 || nw > remain) { + int res = errno; + error((char const *)strerror(res)); + exit(res); + } + + remain -= nw; + off += nw; + total += nw; + } + } + return total; +} + +class Test_fifo_pipe::Test +{ + private: + + Env &_env; + Expanding_reporter _init_config { _env, "config", "init.config" }; + Attached_rom_dataspace _run_echo_template { _env, "init_template" }; + + pthread_attr_t _worker_settings; + pthread_t _sender_thread { 0 }; + pthread_t _receiver_thread { 0 }; + + static void *send_data(void*) + { + int send_file { open(SEND_FILENAME, O_WRONLY) }; + if (send_file < 0) { + error("Cannot open send file ", SEND_FILENAME); + exit(1); + } + int test_data_file { open(TEST_DATA_FILENAME, O_RDONLY) }; + if (test_data_file < 0) { + error("Cannot open test data file ", TEST_DATA_FILENAME); + exit(1); + } + auto const num { copy(test_data_file, send_file) }; + log("sent ", num, " bytes"); + close(send_file); + close(test_data_file); + pthread_exit(NULL); + return NULL; + } + + static void *handle_output_data(void*) + { + /* test values */ + char test_data[BUF_SIZE] { }; + char receive_buffer[BUF_SIZE] { }; + + int receive_file = open(RECEIVE_FILENAME, O_RDONLY); + if (receive_file < 0) { + error("Cannot open receive file ", RECEIVE_FILENAME); + exit(1); + } + int test_data_file = open(TEST_DATA_FILENAME, O_RDONLY); + if (test_data_file < 0) { + error("Cannot open test data file ", TEST_DATA_FILENAME); + exit(1); + } + + size_t total_received_bytes { 0 }; + while (true) { + /* read test data */ + auto const test_data_num = read(test_data_file, test_data, BUF_SIZE); + /* read piped data */ + auto const pipe_data_num = read(receive_file, receive_buffer, BUF_SIZE); + if (pipe_data_num > 0) { + /* compare piped data to test data */ + auto const diff_offset = Genode::memcmp(test_data, receive_buffer, test_data_num); + if (pipe_data_num != test_data_num || (0 != diff_offset)) { + error("writing to pipe failed. Data sent not equal data received. diff_offset=", diff_offset); + error("total_received_bytes=", total_received_bytes); + error("pipe_data_num=", pipe_data_num, " test_data_num=", test_data_num); + throw Exception(); + } + } + if (test_data_num < 0 || pipe_data_num < 0) { + int res = errno; + error((char const *)strerror(res)); + exit(res); + } + total_received_bytes += pipe_data_num; + if (test_data_num == 0) { + break; + } + if (pipe_data_num == 0) { + break; + } + } + log("received a total of ", total_received_bytes, " bytes"); + + close(test_data_file); + close(receive_file); + pthread_exit(NULL); + return NULL; + } + + void _write_init_config(Attached_rom_dataspace& rom, unsigned iteration) + { + _init_config.generate([&rom, iteration] (Xml_generator& xml) { + rom.xml().for_each_sub_node([&xml, iteration] (const Xml_node& node) { + if (node.type() != "start") { + node.with_raw_node([&xml] (char const *addr, const size_t size) { + xml.append(addr, size); + }); + } else { + auto const name { node.attribute_value("name", Genode::String<128> { }) }; + xml.node("start", [&xml, &node, &name, iteration] ( ) { + xml.attribute("name", name); + xml.attribute("version", iteration); + + node.with_raw_content([&xml] (char const *addr, size_t const size) { + xml.append(addr, size); + }); + }); + } + }); + }); + } + + public: + + Test(Env &env) : _env(env) + { + _run_echo_template.update(); + } + + ~Test() = default; + + void start_threads() + { + Libc::with_libc([this] () { + if (0 != pthread_attr_init(&_worker_settings)) { + error("error setting thread settings"); + exit(1); + } + if (0 != pthread_attr_setdetachstate(&_worker_settings, PTHREAD_CREATE_JOINABLE)) { + error("error setting thread settings"); + exit(1); + } + log("starting thread to send data to pipe"); + if (0 != pthread_create(&_sender_thread, &_worker_settings, send_data, nullptr)) { + error("error opening connection thread"); + exit(1); + } + log("starting thread to receive data from pipe"); + if (0 != pthread_create(&_receiver_thread, &_worker_settings, handle_output_data, nullptr)) { + error("error opening connection thread"); + exit(1); + } + }); + } + + void stop_threads() + { + Libc::with_libc([this] () { + log("joining sender thread "); + auto const ret_sender = pthread_join(_sender_thread, nullptr); + if (0 != ret_sender) { + warning("pthread_join unexpectedly returned " + "with ", ret_sender, " (errno=", errno, ")"); + } + log("joined sender thread"); + log("joining receiver thread "); + auto const ret_receiver = pthread_join(_receiver_thread, nullptr); + if (0 != ret_receiver) { + warning("pthread_join unexpectedly returned " + "with ", ret_receiver, " (errno=", errno, ")"); + } + log("joined receiver thread"); + pthread_attr_destroy(&_worker_settings); + }); + } + + void start_echo(unsigned iteration) + { + log("re-starting echo"); + _write_init_config(_run_echo_template, iteration); + } + + void access_control() + { + log("test access control"); + Libc::with_libc([this] () { + bool failed { false }; + int send_file { open(SEND_FILENAME, O_RDONLY) }; + if (send_file >= 0) { + error("should not have read access to ", SEND_FILENAME); + failed = true; + } + int receive_file { open(RECEIVE_FILENAME, O_WRONLY) }; + if (receive_file >= 0) { + error("should not have write access to ", RECEIVE_FILENAME); + failed = true; + } + if (failed) + exit(-1); + }); + } +}; + +class Test_fifo_pipe::Echo +{ + public: + + Echo() { } + + void run() const + { + Libc::with_libc([] () { + auto const num { copy(STDIN_FILENO, STDOUT_FILENO) }; + log("piped ", num, " bytes"); + }); + } + + ~Echo() + { + /* send EOF */ + Libc::with_libc([] () { close(STDOUT_FILENO); }); + } +}; + +class Test_fifo_pipe::Main +{ + private: + + Env &_env; + Attached_rom_dataspace _config { _env, "config" }; + + public: + + Main(Env &env) : _env(env) + { + /* the type attribute describes whether we are running as test or as echo */ + auto type = _config.xml().attribute_value("type", Genode::String<64> { }); + if (type == "echo") { + log("echo started"); + Echo echo; + echo.run(); + } else { + Test test(_env); + auto max_iterations { _config.xml().attribute_value("iterations", 1u) }; + log("test started with ", max_iterations, " iterations"); + for (unsigned i = 0; i < max_iterations; ++i) { + log("--- test iteration ", i, " started ---"); + test.start_echo(i); + test.start_threads(); + test.stop_threads(); + } + test.access_control(); + log("--- test succeeded ---"); + } + } + +}; + + +void Libc::Component::construct(Libc::Env& env) { static Test_fifo_pipe::Main main(env); } diff --git a/repos/libports/src/test/libc_fifo_pipe/target.mk b/repos/libports/src/test/libc_fifo_pipe/target.mk new file mode 100644 index 0000000000..92c5e3175b --- /dev/null +++ b/repos/libports/src/test/libc_fifo_pipe/target.mk @@ -0,0 +1,9 @@ +TARGET = test-libc_fifo_pipe + +LIBS := base +LIBS += libc +LIBS += vfs + +SRC_CC := main.cc + +CC_CXX_WARN_STRICT = diff --git a/repos/os/include/os/path.h b/repos/os/include/os/path.h index f27b917cb8..f89b715f29 100644 --- a/repos/os/include/os/path.h +++ b/repos/os/include/os/path.h @@ -326,7 +326,7 @@ class Genode::Path_base return strcmp(_path, other._path) != 0; } - char const *last_element() + char const *last_element() const { return last_element(_path)+1; }