mirror of
https://github.com/genodelabs/genode.git
synced 2024-12-19 05:37:54 +00:00
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
This commit is contained in:
parent
10605a6903
commit
3ff0efd627
@ -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
|
||||
|
@ -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:
|
||||
|
||||
! <start name="vfs">
|
||||
! <provides> <service name="File_system"/> </provides>
|
||||
! <resource name="RAM" quantum="4M"/>
|
||||
! <config>
|
||||
! <vfs>
|
||||
! <pipe>
|
||||
! <fifo name="upstream"/>
|
||||
! <fifo name="downstream"/>
|
||||
! </pipe>
|
||||
! </vfs>
|
||||
! <default-policy root="/" writeable="yes"/>
|
||||
! </config>
|
||||
! </start>
|
||||
!
|
||||
! <start name="my-libc-app">
|
||||
! <resource name="RAM" quantum="4M"/>
|
||||
! <config>
|
||||
! <vfs>
|
||||
! <dir name="dev">
|
||||
! <dir name="pipe"> <fs/> </dir>
|
||||
! <log/>
|
||||
! </dir>
|
||||
! </vfs>
|
||||
! <libc stdin="/dev/pipe/upstream" stdout="/dev/log" stderr="/dev/log"/>
|
||||
! </config>
|
||||
! </start>
|
||||
|
||||
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.
|
||||
|
@ -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<Vfs::MAX_PATH_LEN> Path;
|
||||
|
||||
enum { PIPE_BUF_SIZE = 8192U };
|
||||
typedef Genode::Ring_buffer<unsigned char, PIPE_BUF_SIZE+1> Pipe_buffer;
|
||||
@ -32,12 +34,14 @@ namespace Vfs_pipe {
|
||||
typedef Genode::Registry<Pipe_handle>::Element Pipe_handle_registry_element;
|
||||
typedef Genode::Registry<Pipe_handle> Pipe_handle_registry;
|
||||
|
||||
class Pipe;
|
||||
struct Pipe;
|
||||
typedef Genode::Id_space<Pipe> 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 <typename FN>
|
||||
void _try_apply(Pipe_space::Id id, FN const &fn)
|
||||
{
|
||||
try { _pipe_space.apply<Pipe &>(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<Pipe&>(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<Pipe&>(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<Pipe&>(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<Pipe&>(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<Pipe&>(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<Pipe&>(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<Fifo_item>::Element _element;
|
||||
Path const path;
|
||||
Pipe_space::Id const id;
|
||||
|
||||
Fifo_item(Genode::Registry<Fifo_item> ®istry,
|
||||
Path const &path, Pipe_space::Id const &id)
|
||||
:
|
||||
_element(registry, *this), path(path), id(id)
|
||||
{ }
|
||||
};
|
||||
|
||||
Vfs::Env &_env;
|
||||
Genode::Registry<Fifo_item> _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<MAX_PATH_LEN>()) };
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
1
repos/libports/recipes/pkg/test-libc_fifo_pipe/README
Normal file
1
repos/libports/recipes/pkg/test-libc_fifo_pipe/README
Normal file
@ -0,0 +1 @@
|
||||
Test for using the libc with the fifo feature of the VFS pipe plugin.
|
7
repos/libports/recipes/pkg/test-libc_fifo_pipe/archives
Normal file
7
repos/libports/recipes/pkg/test-libc_fifo_pipe/archives
Normal file
@ -0,0 +1,7 @@
|
||||
_/src/fs_rom
|
||||
_/src/init
|
||||
_/src/libc
|
||||
_/src/report_rom
|
||||
_/src/test-libc_fifo_pipe
|
||||
_/src/vfs
|
||||
_/src/vfs_pipe
|
1
repos/libports/recipes/pkg/test-libc_fifo_pipe/hash
Normal file
1
repos/libports/recipes/pkg/test-libc_fifo_pipe/hash
Normal file
@ -0,0 +1 @@
|
||||
2021-01-12 f2df8c7f21d8701352f73823355c70c515aa7857
|
159
repos/libports/recipes/pkg/test-libc_fifo_pipe/runtime
Normal file
159
repos/libports/recipes/pkg/test-libc_fifo_pipe/runtime
Normal file
@ -0,0 +1,159 @@
|
||||
<runtime ram="64M" caps="1000" binary="init">
|
||||
|
||||
<events>
|
||||
<timeout meaning="failed" sec="120" />
|
||||
<log meaning="succeeded">--- test succeeded ---</log>
|
||||
<log meaning="failed">Error: </log>
|
||||
</events>
|
||||
|
||||
<content>
|
||||
<rom label="fs_rom"/>
|
||||
<rom label="ld.lib.so"/>
|
||||
<rom label="libc.lib.so"/>
|
||||
<rom label="report_rom"/>
|
||||
<rom label="test-libc_fifo_pipe"/>
|
||||
<rom label="vfs"/>
|
||||
<rom label="vfs.lib.so"/>
|
||||
<rom label="vfs_pipe.lib.so"/>
|
||||
</content>
|
||||
|
||||
<config verbose="no">
|
||||
<parent-provides>
|
||||
<service name="CPU"/>
|
||||
<service name="LOG"/>
|
||||
<service name="PD"/>
|
||||
<service name="RM"/>
|
||||
<service name="ROM"/>
|
||||
<service name="Timer"/>
|
||||
</parent-provides>
|
||||
<default-route>
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</default-route>
|
||||
<default caps="128"/>
|
||||
|
||||
<start name="vfs">
|
||||
<provides>
|
||||
<service name="File_system"/>
|
||||
</provides>
|
||||
<resource name="RAM" quantum="4M"/>
|
||||
<config>
|
||||
<vfs>
|
||||
<pipe>
|
||||
<fifo name="upstream"/>
|
||||
<fifo name="downstream"/>
|
||||
</pipe>
|
||||
</vfs>
|
||||
<policy label="fifo-pipe-test -> in" root="/.upstream/in" writeable="yes" />
|
||||
<policy label="fifo-pipe-test -> out" root="/.downstream/out" writeable="no" />
|
||||
<default-policy root="/" writeable="yes"/>
|
||||
</config>
|
||||
</start>
|
||||
|
||||
<start name="ram_fs">
|
||||
<binary name="vfs"/>
|
||||
<resource name="RAM" quantum="2M"/>
|
||||
<provides>
|
||||
<service name="File_system"/>
|
||||
</provides>
|
||||
<config>
|
||||
<vfs>
|
||||
<inline name="init_template">
|
||||
<config verbose="no">
|
||||
<parent-provides>
|
||||
<service name="CPU"/>
|
||||
<service name="File_system"/>
|
||||
<service name="LOG"/>
|
||||
<service name="PD"/>
|
||||
<service name="RM"/>
|
||||
<service name="ROM"/>
|
||||
<service name="Timer"/>
|
||||
</parent-provides>
|
||||
<default caps="110"/>
|
||||
|
||||
<start name="echo">
|
||||
<binary name="test-libc_fifo_pipe"/>
|
||||
<resource name="RAM" quantum="3M"/>
|
||||
<config type="echo">
|
||||
<vfs>
|
||||
<dir name="dev">
|
||||
<fs label="downstream"/>
|
||||
<fs label="upstream"/>
|
||||
<log/>
|
||||
</dir>
|
||||
</vfs>
|
||||
<libc stdin="/dev/upstream" stdout="/dev/downstream" stderr="/dev/log"/>
|
||||
</config>
|
||||
<route>
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
|
||||
</config>
|
||||
</inline>
|
||||
</vfs>
|
||||
<policy label="fs_rom -> " root="/" writeable="no"/>
|
||||
</config>
|
||||
</start>
|
||||
|
||||
<start name="fs_rom">
|
||||
<resource name="RAM" quantum="2M"/>
|
||||
<provides>
|
||||
<service name="ROM"/>
|
||||
</provides>
|
||||
<route>
|
||||
<service name="File_system"> <child name="ram_fs"/> </service>
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
|
||||
<start name="report_rom">
|
||||
<resource name="RAM" quantum="2M"/>
|
||||
<provides>
|
||||
<service name="ROM"/>
|
||||
<service name="Report"/>
|
||||
</provides>
|
||||
<config verbose="no">
|
||||
<policy label="init.config" report="init.config"/>
|
||||
</config>
|
||||
</start>
|
||||
|
||||
<start name="fifo-pipe-test">
|
||||
<binary name="test-libc_fifo_pipe"/>
|
||||
<resource name="RAM" quantum="8M"/>
|
||||
<config iterations="15">
|
||||
<vfs>
|
||||
<dir name="ro">
|
||||
<rom name="test-data.bin"/>
|
||||
</dir>
|
||||
<dir name="dev">
|
||||
<dir name="send-pipe">
|
||||
<fs label="in"/>
|
||||
</dir>
|
||||
<dir name="receive-pipe">
|
||||
<fs label="out"/>
|
||||
</dir>
|
||||
<log/>
|
||||
</dir>
|
||||
</vfs>
|
||||
<libc stdout="/dev/log" stderr="/dev/log"/>
|
||||
</config>
|
||||
<route>
|
||||
<service name="File_system"> <child name="vfs"/> </service>
|
||||
<service name="Report" label="init.config"> <child name="report_rom" label="init.config"/> </service>
|
||||
<service name="ROM" label="init_template"> <child name="fs_rom" label="init_template"/> </service>
|
||||
<service name="ROM" label="test-data.bin"> <parent label="init"/> </service>
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
|
||||
<start name="init" caps="200">
|
||||
<resource name="RAM" quantum="16M"/>
|
||||
<route>
|
||||
<service name="ROM" label="config"> <child name="report_rom" label="init.config"/> </service>
|
||||
<service name="File_system"> <child name="vfs"/> </service>
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
|
||||
</config>
|
||||
</runtime>
|
@ -0,0 +1,2 @@
|
||||
SRC_DIR = src/test/libc_fifo_pipe
|
||||
include $(GENODE_DIR)/repos/base/recipes/src/content.inc
|
1
repos/libports/recipes/src/test-libc_fifo_pipe/hash
Normal file
1
repos/libports/recipes/src/test-libc_fifo_pipe/hash
Normal file
@ -0,0 +1 @@
|
||||
2021-01-12 ce7c08e54a4feeaead3b13ae08f4ce25b4be435c
|
5
repos/libports/recipes/src/test-libc_fifo_pipe/used_apis
Normal file
5
repos/libports/recipes/src/test-libc_fifo_pipe/used_apis
Normal file
@ -0,0 +1,5 @@
|
||||
base
|
||||
libc
|
||||
os
|
||||
report_session
|
||||
vfs
|
324
repos/libports/src/test/libc_fifo_pipe/main.cc
Normal file
324
repos/libports/src/test/libc_fifo_pipe/main.cc
Normal file
@ -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 <base/attached_rom_dataspace.h>
|
||||
#include <base/log.h>
|
||||
#include <libc/component.h>
|
||||
#include <os/reporter.h>
|
||||
#include <util/string.h>
|
||||
#include <util/xml_node.h>
|
||||
|
||||
|
||||
/* libc includes */
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
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); }
|
9
repos/libports/src/test/libc_fifo_pipe/target.mk
Normal file
9
repos/libports/src/test/libc_fifo_pipe/target.mk
Normal file
@ -0,0 +1,9 @@
|
||||
TARGET = test-libc_fifo_pipe
|
||||
|
||||
LIBS := base
|
||||
LIBS += libc
|
||||
LIBS += vfs
|
||||
|
||||
SRC_CC := main.cc
|
||||
|
||||
CC_CXX_WARN_STRICT =
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user