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