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; }