diff --git a/repos/base-linux/run/lx_fs_notify.run b/repos/base-linux/run/lx_fs_notify.run
new file mode 100644
index 0000000000..1944d26856
--- /dev/null
+++ b/repos/base-linux/run/lx_fs_notify.run
@@ -0,0 +1,436 @@
+#
+# \brief Test for using the lx_fs_notify plugin with the Linux file system
+# \author Pirmin Duss
+# \date 2019-12-05
+#
+
+assert_spec linux
+
+#
+# Build
+#
+
+create_boot_directory
+
+import_from_depot [depot_user]/src/[base_src]
+import_from_depot [depot_user]/src/chroot
+import_from_depot [depot_user]/src/fs_rom
+import_from_depot [depot_user]/src/init
+import_from_depot [depot_user]/src/libc
+import_from_depot [depot_user]/src/stdcxx
+import_from_depot [depot_user]/src/posix
+import_from_depot [depot_user]/src/vfs
+
+build {
+ server/lx_fs
+ test/lx_fs_notify/rom_log
+ test/lx_fs_notify/file_writer
+}
+
+#
+# init config
+#
+install_config {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
+
+#
+# configurations for the sub init
+#
+set init_run_fwrite_test {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
+
+set init_run_write_test {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
+
+set test_iterations 10
+set input_file_name "bin/lx_fs_notify/templates/infile.txt"
+set output_file_name "bin/lx_fs_notify/test/outfile.txt"
+
+#
+# print text in colors
+#
+proc color {foreground text} {
+ # tput is a little Unix utility that lets you use the termcap database
+ # *much* more easily...
+ return [exec tput setaf $foreground]$text[exec tput sgr0]
+}
+
+#
+# write the desired config for the sub init
+#
+proc write_init_config { config } {
+ set fd [open "bin/lx_fs_notify/init.config" "w"]
+ puts $fd $config
+ close $fd
+}
+
+#
+# clear the content of the init config
+#
+proc write_empty_init_config { } {
+ set fd [open "bin/lx_fs_notify/init.config" "w"]
+ puts $fd ""
+ close $fd
+}
+
+#
+# wait for an update to the test file and check
+#
+proc wait_file_changed { file_size additional_filter } {
+ global spawn_id
+
+ puts [color 3 "wait for file change with size=$file_size"]
+ set timeout 5
+ expect {
+ -i $spawn_id -re ".*init -> test-rom_log.*updated ROM content: size=$file_size.*\n" { }
+ timeout {
+ puts [color 1 "ERROR no file change or wrong file size reported. expected size was $file_size"]
+ exit -4
+ }
+ }
+
+ if { $additional_filter != {} } {
+ set wait_re ".*$additional_filter\n"
+ set timeout 3
+ expect {
+ -i $spawn_id -re $wait_re { }
+ timeout { }
+ }
+ }
+}
+
+#
+# wait for a defined time
+#
+proc wait_and_consume_log { delay_sec } {
+ global spawn_id
+
+ set timeout $delay_sec
+ expect {
+ -i $spawn_id -re { text that never is printed during the test } { }
+ timeout { }
+ }
+ after 10
+}
+
+#
+# create the input file for a test
+#
+proc create_test_file { input_file_name } {
+ exec seq -w [expr int(rand()*4000)+1] > $input_file_name
+ set file_size [exec stat -c%s $input_file_name]
+ return $file_size
+}
+
+#
+# create output file
+#
+proc create_output_file { output_file_name } {
+ exec seq -w [expr int(rand()*400)+1] > $output_file_name
+ set file_size [exec stat -c%s $output_file_name]
+ return $file_size
+}
+
+#
+# compute the size of the ROM from the file size
+#
+proc rom_size { file_size } {
+ if { [expr $file_size % 4096] == 0 } {
+ return $file_size
+ } else {
+ return [expr $file_size + (4096 - ($file_size % 4096))]
+ }
+}
+
+#
+# Create test-directory structure
+#
+exec rm -Rf bin/lx_fs_notify
+exec mkdir -p bin/lx_fs_notify/templates
+exec mkdir -p bin/lx_fs_notify/test
+exec mkdir -p bin/lx_fs_notify/mnt
+
+#
+# Boot modules
+#
+build_boot_image { lx_fs test-rom_log test-file_writer lx_fs_notify }
+
+#
+# build the test program for Linux
+# this wil bel located in /tmp/bin
+#
+exec make -C [genode_dir]/repos/os/src/test/lx_fs_notify/file_writer/
+
+#
+# Test cases
+#
+proc test_libc_fwrite_in_genode { test_iterations init_run_fwrite_test input_file_name output_file_name } {
+ puts [color 2 ">>> run libc fwrite test in Genode ($test_iterations iterations)"]
+ set size [create_output_file $output_file_name]
+ for { set it 0 } { $it < $test_iterations } { incr it } {
+ set size [create_test_file $input_file_name]
+ wait_and_consume_log 1
+ write_init_config $init_run_fwrite_test
+ wait_file_changed $size {child "test-file_writer" exited with exit value 0}
+ write_empty_init_config
+ }
+}
+
+proc test_libc_write_in_genode { test_iterations init_run_write_test input_file_name output_file_name } {
+ puts [color 2 ">>> run libc write test in Genode ($test_iterations iterations)"]
+ set size [create_output_file $output_file_name]
+ for { set it 0 } { $it < $test_iterations } { incr it } {
+ set size [expr max([create_test_file $input_file_name], $size)]
+ wait_and_consume_log 1
+ write_init_config $init_run_write_test
+ wait_file_changed $size {child "test-file_writer" exited with exit value 0}
+ write_empty_init_config
+ }
+}
+
+proc test_libc_fwrite_on_linux { test_iterations input_file_name output_file_name } {
+ puts [color 2 ">>> run libc fwrite test on Linux ($test_iterations iterations)"]
+ write_empty_init_config
+ create_output_file $output_file_name
+ for { set it 0 } { $it < $test_iterations } { incr it } {
+ set size [create_test_file $input_file_name]
+ wait_and_consume_log 1
+ exec /tmp/bin/file_writer --fwrite [run_dir]/genode/lx_fs_notify/templates/infile.txt [run_dir]/genode/lx_fs_notify/test/outfile.txt
+ wait_file_changed $size {}
+ }
+}
+
+proc test_libc_write_on_linux { test_iterations input_file_name output_file_name } {
+ puts [color 2 ">>> run libc write test on Linux ($test_iterations iterations)"]
+ write_empty_init_config
+ set size [create_output_file $output_file_name]
+ for { set it 0 } { $it < $test_iterations } { incr it } {
+ set size [expr max([create_test_file $input_file_name], $size)]
+ wait_and_consume_log 1
+ exec /tmp/bin/file_writer --write [run_dir]/genode/lx_fs_notify/templates/infile.txt [run_dir]/genode/lx_fs_notify/test/outfile.txt
+ wait_file_changed $size {}
+ }
+}
+
+proc test_tcl_file_copy { test_iterations input_file_name output_file_name} {
+ puts [color 2 ">>> run TCL 'file copy' test ($test_iterations iterations)"]
+ for { set it 0 } { $it < $test_iterations } { incr it } {
+ set size [create_test_file $input_file_name]
+ file copy -force $input_file_name $output_file_name
+ wait_file_changed $size {}
+ }
+}
+
+proc test_shell_cp { test_iterations input_file_name output_file_name } {
+ puts [color 2 ">>> run shell 'cp' test ($test_iterations iterations)"]
+ for { set it 0 } { $it < $test_iterations } { incr it } {
+ set size [create_test_file $input_file_name]
+ exec cp -f $input_file_name $output_file_name
+ wait_file_changed $size {}
+ }
+}
+
+proc test_shell_mv_overwrite { test_iterations input_file_name output_file_name } {
+ create_output_file $output_file_name
+ puts [color 2 ">>> run shell 'mv' overwrite test ($test_iterations iterations)"]
+ for { set it 0 } { $it < $test_iterations } { incr it } {
+ set size [create_test_file $input_file_name]
+ exec mv $input_file_name $output_file_name
+ wait_file_changed $size {}
+ }
+}
+
+proc test_shell_mv_rename { test_iterations } {
+ global output_file_name
+ puts [color 2 ">>> run 'mv' rename watched file test ($test_iterations iterations)"]
+ for { set it 0 } { $it < $test_iterations } { incr it } {
+ puts "create watched file"
+ set size [create_output_file $output_file_name]
+ wait_file_changed $size {}
+ puts "move watched file away"
+ exec mv $output_file_name $output_file_name.out
+ wait_file_changed 0 {}
+ }
+}
+
+proc test_shell_mv_move_other_dir { test_iterations } {
+ global input_file_name
+ global output_file_name
+ puts [color 2 ">>> run 'mv' move watched file to other directory test ($test_iterations iterations)"]
+ for { set it 0 } { $it < $test_iterations } { incr it } {
+ puts "create watched file"
+ set size [create_output_file $output_file_name]
+ wait_file_changed $size {}
+ puts "move watched file away"
+ exec mv $output_file_name $input_file_name
+ wait_file_changed 0 {}
+ }
+}
+
+proc test_shell_rm { test_iterations output_file_name } {
+ puts [color 2 ">>> run 'rm' remove watched file test ($test_iterations iterations)"]
+ for { set it 0 } { $it < $test_iterations } { incr it } {
+ puts "create watched file"
+ set size [create_output_file $output_file_name]
+ wait_file_changed $size {}
+ puts "remove watched file"
+ exec rm -f $output_file_name
+ wait_file_changed 0 {}
+ }
+}
+
+#
+# Execute test cases
+#
+# wait until the test program has started
+run_genode_until ".*wait for ROM update.*\n" 6
+set spawn_id [output_spawn_id]
+wait_and_consume_log 3
+
+test_libc_fwrite_in_genode $test_iterations $init_run_fwrite_test $input_file_name $output_file_name
+test_libc_write_in_genode $test_iterations $init_run_write_test $input_file_name $output_file_name
+test_libc_fwrite_on_linux $test_iterations $input_file_name $output_file_name
+test_libc_write_on_linux $test_iterations $input_file_name $output_file_name
+test_tcl_file_copy $test_iterations $input_file_name $output_file_name
+test_shell_cp $test_iterations $input_file_name $output_file_name
+test_shell_mv_overwrite $test_iterations $input_file_name $output_file_name
+test_shell_mv_rename $test_iterations
+test_shell_mv_move_other_dir $test_iterations
+test_shell_rm $test_iterations $output_file_name
+
+#
+# Cleanup test-directory structure
+#
+exec rm -Rf bin/lx_fs_notify
+
+# vi: set ft=tcl :
diff --git a/repos/os/src/server/lx_fs/README b/repos/os/src/server/lx_fs/README
index 7cce6be14a..b90cc200cc 100644
--- a/repos/os/src/server/lx_fs/README
+++ b/repos/os/src/server/lx_fs/README
@@ -32,13 +32,12 @@ optional 'writeable' attribute grants the permission to modify the file system.
Example
~~~~~~~
-To illustrate the use of lx_fs, refer to the 'base-linux/run/lx_fs.run'
-script.
+To illustrate the use of lx_fs, refer to the 'base-linux/run/lx_fs.run' or
+'base-linux/run/lx_fs_notify.run' scripts.
Notes
~~~~~
-If the Linux file system experiences changes from other processes
-'inotify' may help to keep the servers cache up-to-date. This is not
-implemented yet.
+'inotify' is used to track changes to the file system that are caused
+by non Genode components.
diff --git a/repos/os/src/server/lx_fs/fd_set.h b/repos/os/src/server/lx_fs/fd_set.h
new file mode 100644
index 0000000000..18f86a699e
--- /dev/null
+++ b/repos/os/src/server/lx_fs/fd_set.h
@@ -0,0 +1,49 @@
+/*
+ * \brief Fd_set utlity
+ * \author Stefan Thöni
+ * \author Pirmin Duss
+ * \date 2021-04-08
+ */
+
+/*
+ * Copyright (C) 2021 Genode Labs GmbH
+ * Copyright (C) 2021 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.
+ */
+
+#ifndef _FD_SET_H_
+#define _FD_SET_H_
+
+/* libc includes */
+#include
+
+
+namespace Lx_fs {
+ class Fd_set;
+}
+
+
+class Lx_fs::Fd_set
+{
+ private:
+
+ fd_set _fdset { };
+ int _nfds { };
+
+ public:
+
+ Fd_set(int fd0)
+ {
+ FD_ZERO(&_fdset);
+ FD_SET(fd0, &_fdset);
+ _nfds = fd0 + 1;
+ }
+
+ fd_set* fdset() { return &_fdset; }
+ int nfds() const { return _nfds; }
+ bool is_set(int fd) const { return FD_ISSET(fd, &_fdset); }
+};
+
+#endif /* _FD_SET_H_ */
diff --git a/repos/os/src/server/lx_fs/lx_util.cc b/repos/os/src/server/lx_fs/lx_util.cc
new file mode 100644
index 0000000000..bec2e8b2dc
--- /dev/null
+++ b/repos/os/src/server/lx_fs/lx_util.cc
@@ -0,0 +1,43 @@
+/*
+ * \brief Linux utilities
+ * \author Christian Helmuth
+ * \author Pirmin Duss
+ * \date 2013-11-11
+ */
+
+/*
+ * Copyright (C) 2013-2020 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.
+ */
+
+/* local includes */
+#include "lx_util.h"
+
+
+int File_system::access_mode(File_system::Mode const &mode)
+{
+ switch (mode) {
+ case STAT_ONLY:
+ case READ_ONLY: return O_RDONLY;
+ case WRITE_ONLY: return O_WRONLY;
+ case READ_WRITE: return O_RDWR;
+ }
+
+ return O_RDONLY;
+}
+
+
+Lx_fs::Path_string Lx_fs::absolute_root_dir(char const *root_path)
+{
+ char cwd[PATH_MAX];
+ char real_path[PATH_MAX];
+
+ getcwd(cwd, PATH_MAX);
+
+ realpath(Path_string { cwd, "/", root_path }.string(), real_path);
+
+ return Path_string { real_path };
+}
diff --git a/repos/os/src/server/lx_fs/lx_util.h b/repos/os/src/server/lx_fs/lx_util.h
index 8c63c218c5..174fa64307 100644
--- a/repos/os/src/server/lx_fs/lx_util.h
+++ b/repos/os/src/server/lx_fs/lx_util.h
@@ -49,34 +49,7 @@ namespace Lx_fs {
* session.
*
*/
- Path_string absolute_root_directory(char const *root_path);
+ Path_string absolute_root_dir(char const *root_path);
}
-
-int File_system::access_mode(File_system::Mode const &mode)
-{
- switch (mode) {
- case STAT_ONLY:
- case READ_ONLY: return O_RDONLY;
- case WRITE_ONLY: return O_WRONLY;
- case READ_WRITE: return O_RDWR;
- }
-
- return O_RDONLY;
-}
-
-
-Lx_fs::Path_string Lx_fs::absolute_root_directory(char const *root_path)
-{
- char cwd[PATH_MAX];
- char real_path[PATH_MAX];
-
- getcwd(cwd, PATH_MAX);
-
- realpath(Path_string { cwd, "/", root_path }.string(), real_path);
-
- return Path_string { real_path };
-}
-
-
#endif /* _LX_UTIL_H_ */
diff --git a/repos/os/src/server/lx_fs/main.cc b/repos/os/src/server/lx_fs/main.cc
index 320e036273..c0a656eeb1 100644
--- a/repos/os/src/server/lx_fs/main.cc
+++ b/repos/os/src/server/lx_fs/main.cc
@@ -28,8 +28,13 @@
/* local includes */
#include "directory.h"
-#include "node.h"
+#include "notifier.h"
#include "open_node.h"
+#include "watch.h"
+
+/* libc includes */
+#include
+#include
namespace Lx_fs {
@@ -85,7 +90,8 @@ class Lx_fs::Session_resources
class Lx_fs::Session_component : private Session_resources,
- public Session_rpc_object
+ public Session_rpc_object,
+ private Watch_node::Response_handler
{
private:
@@ -97,9 +103,8 @@ class Lx_fs::Session_component : private Session_resources,
Id_space _open_node_registry { };
bool _writable;
Absolute_path const _root_dir;
-
Signal_handler _process_packet_dispatcher;
-
+ Notifier &_notifier;
/******************************
** Packet-stream processing **
@@ -238,6 +243,16 @@ class Lx_fs::Session_component : private Session_resources,
}
}
+ /**
+ * Watch_node::Response_handler interface
+ */
+ void handle_watch_node_response(Lx_fs::Watch_node &node) override
+ {
+ using Fs_node = File_system::Open_node;
+ _process_packet_op(node.acked_packet(),
+ *(reinterpret_cast(node.open_node())));
+ }
+
public:
/**
@@ -248,7 +263,8 @@ class Lx_fs::Session_component : private Session_resources,
Genode::Cap_quota cap_quota,
size_t tx_buf_size,
char const *root_dir,
- bool writable)
+ bool writable,
+ Notifier ¬ifier)
:
Session_resources { env.pd(), env.rm(), ram_quota, cap_quota, tx_buf_size },
Session_rpc_object {_packet_ds.cap(), env.rm(), env.ep().rpc_ep() },
@@ -256,7 +272,8 @@ class Lx_fs::Session_component : private Session_resources,
_root { *new (&_alloc) Directory { _alloc, root_dir, false } },
_writable { writable },
_root_dir { root_dir },
- _process_packet_dispatcher { env.ep(), *this, &Session_component::_process_packets }
+ _process_packet_dispatcher { env.ep(), *this, &Session_component::_process_packets },
+ _notifier { notifier }
{
/*
* Register '_process_packets' dispatch function as signal
@@ -383,6 +400,24 @@ class Lx_fs::Session_component : private Session_resources,
return open_node->id();
}
+ Watch_handle watch(Path const &path) override
+ {
+ _assert_valid_path(path.string());
+
+ /* re-root the path */
+ Path_string watch_path { _root.path().string(), path.string() };
+
+ Watch_node *watch =
+ new (_alloc) Watch_node { _env, watch_path.string(), *this, _notifier };
+ Lx_fs::Open_node *open_watch =
+ new (_alloc) Lx_fs::Open_node(*watch, _open_node_registry);
+
+ Watch_handle handle { open_watch->id().value };
+ watch->open_node(open_watch);
+
+ return handle;
+ }
+
void close(Node_handle handle) override
{
auto close_fn = [&] (Open_node &open_node) {
@@ -520,6 +555,7 @@ class Lx_fs::Root : public Root_component
Genode::Env &_env;
Genode::Attached_rom_dataspace _config { _env, "config" };
+ Notifier _notifier { _env };
static inline bool writeable_from_args(char const *args)
{
@@ -603,8 +639,8 @@ class Lx_fs::Root : public Root_component
Genode::Ram_quota { ram_quota },
Genode::Cap_quota { cap_quota },
tx_buf_size,
- absolute_root_directory(root_dir).string(),
- writeable };
+ absolute_root_dir(root_dir).string(),
+ writeable, _notifier };
auto ram_used { _env.pd().used_ram().value - initial_ram_usage };
auto cap_used { _env.pd().used_caps().value - initial_cap_usage };
diff --git a/repos/os/src/server/lx_fs/notifier.cc b/repos/os/src/server/lx_fs/notifier.cc
new file mode 100644
index 0000000000..863e4b8f3b
--- /dev/null
+++ b/repos/os/src/server/lx_fs/notifier.cc
@@ -0,0 +1,427 @@
+/*
+ * \brief inotify handling for underlying file system.
+ * \author Pirmin Duss
+ * \date 2020-06-17
+ */
+
+/*
+ * Copyright (C) 2013-2021 Genode Labs GmbH
+ * Copyright (C) 2020-2021 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.
+ */
+
+/* Genode includes */
+#include "base/exception.h"
+#include
+#include "base/signal.h"
+#include "util/string.h"
+
+/* libc includes */
+#include
+#include
+#include
+#include /* strerror */
+#include
+#include
+#include
+#include
+#include
+
+/* local includes */
+#include "fd_set.h"
+#include "notifier.h"
+#include "watch.h"
+
+
+namespace Lx_fs
+{
+ enum {
+ STACK_SIZE = 8 * 1024,
+ EVENT_SIZE = (sizeof (struct inotify_event)),
+ EVENT_BUF_LEN = (1024 * (EVENT_SIZE + NAME_MAX + 1)),
+ PARALLEL_NOTIFICATIONS = 4,
+ INOTIFY_WATCH_MASK = IN_CLOSE_WRITE | /* writtable file was closed */
+ IN_MOVED_TO | /* file was moved to Y */
+ IN_MOVED_FROM | /* file was moved from X */
+ IN_CREATE | /* subfile was created */
+ IN_DELETE | /* subfile was deleted */
+ IN_IGNORED, /* file was ignored */
+ };
+
+ struct Libc_signal_thread;
+}
+
+#include
+#include
+#include
+
+/* do not leak internal function in to global namespace */
+namespace
+{
+ using namespace Lx_fs;
+
+ ::uint64_t timestamp_us()
+ {
+ struct timeval ts { 0, 0 };
+ gettimeofday(&ts, nullptr);
+
+ return (ts.tv_sec * 1'000'000) + ts.tv_usec;
+ }
+
+
+ template
+ T *remove_from_list(Genode::List &list, T *node, Allocator &alloc)
+ {
+ T *next { node->next() };
+ list.remove(node);
+ destroy(alloc, node);
+ return next;
+ }
+
+
+ bool is_dir(char const *path)
+ {
+ struct stat s;
+ int ret = lstat(path, &s);
+ if (ret == -1)
+ return false;
+
+ if (S_ISDIR(s.st_mode))
+ return true;
+
+ return false;
+ }
+
+
+ Path_string get_directory(Path_string const &path)
+ {
+ Path_string directory;
+ if (is_dir(path.string())) {
+ // make sure there is a '/' at the end of the path
+ if (path.string()[path.length() - 2] == '/')
+ directory = path;
+ else
+ directory = Path_string { path, '/' };
+ } else {
+ size_t pos = 0;
+ for (size_t i = 0; i < path.length() - 1; ++i) {
+ if (path.string()[i] == '/')
+ pos = i;
+ }
+ directory = Genode::Cstring { path.string(), pos + 1 };
+ }
+
+ return directory;
+ }
+
+
+ Path_string get_filename(Path_string const &path)
+ {
+ Path_string filename;
+ if (is_dir(path.string()))
+ return { };
+
+ size_t pos = 0;
+ for (size_t i = 0; i < path.length() - 1; ++i) {
+ if (path.string()[i] == '/')
+ pos = i;
+ }
+
+ /* if '/' is the last symbol we do not need a filename */
+ if (pos != path.length() - 2)
+ filename = Genode::Cstring { path.string() + pos + 1 };
+
+ return filename;
+ }
+
+} /* anonymous namespace */
+
+
+Lx_fs::Os_path::Os_path(const char *fullname)
+:
+ full_path { fullname },
+ directory { get_directory(full_path) },
+ filename { get_filename(full_path) }
+{ }
+
+
+bool Lx_fs::Notifier::_watched(char const *path) const
+{
+ for (Watches_list_element const *e = _watched_nodes.first(); e != nullptr; e = e->next()) {
+ if (e->path.full_path == path)
+ return true;
+ }
+
+ return false;
+}
+
+
+void Lx_fs::Notifier::_add_to_watched(char const *fullname)
+{
+ Os_path path { fullname };
+ for (Watches_list_element *e = _watched_nodes.first(); e != nullptr; e = e->next()) {
+ if (e->path.directory == path.directory) {
+ Watches_list_element *elem { new (_heap) Watches_list_element { e->watch_fd, path } };
+ _watched_nodes.insert(elem);
+ return;
+ }
+ }
+
+ auto watch_fd { inotify_add_watch(_fd, path.directory.string(), INOTIFY_WATCH_MASK) };
+
+ if (watch_fd > 0) {
+ Watches_list_element *elem { new (_heap) Watches_list_element { watch_fd, path } };
+ _watched_nodes.insert(elem);
+ }
+}
+
+
+int Lx_fs::Notifier::_add_node(char const *path, Watch_node &node)
+{
+ for (Watches_list_element *e = _watched_nodes.first(); e != nullptr; e = e->next()) {
+ if (e->path.full_path == path) {
+ Single_watch_list_element *c { new (_heap) Single_watch_list_element { node } };
+ e->add_node(c);
+ return e->watch_fd;
+ }
+ };
+
+ throw File_system::Lookup_failed { };
+}
+
+
+void Lx_fs::Notifier::_add_notify(Watch_node &node)
+{
+ Mutex::Guard guard { _notify_queue_mutex };
+ for (auto const *e = _notify_queue.first(); e != nullptr; e = e->next()) {
+ if (&e->node == &node) {
+ return;
+ }
+ }
+
+ auto *entry { new (_heap) Single_watch_list_element { node } };
+ _notify_queue.insert(entry);
+}
+
+
+void Lx_fs::Notifier::_process_notify()
+{
+ Mutex::Guard guard { _notify_queue_mutex };
+
+ /**
+ * limit amount of watch events sent at the same time,
+ * to prevent an overflow of the packet ack queue of the
+ * File_system session.
+ */
+ int cnt { 0 };
+ Single_watch_list_element *e { _notify_queue.first() };
+ for (; e != nullptr && cnt < PARALLEL_NOTIFICATIONS; ++cnt) {
+ e->node.notify_handler().local_submit();
+ e = remove_from_list(_notify_queue, e, _heap);
+ }
+}
+
+
+Lx_fs::Notifier::Notifier(Env &env)
+:
+ Thread { env, "inotify", STACK_SIZE },
+ _env { env }
+{
+ _fd = inotify_init();
+
+ if (0 > _fd)
+ throw Init_notify_failed { };
+
+ start();
+}
+
+
+Lx_fs::Notifier::~Notifier()
+{
+ /* do not notify the elements */
+ for (auto *e = _notify_queue.first(); e != nullptr; ) {
+ e = remove_from_list(_notify_queue, e, _heap);
+ }
+
+ for (auto *e = _watched_nodes.first(); e != nullptr; ) {
+ e = remove_from_list(_watched_nodes, e, _heap);
+ }
+
+ close(_fd);
+}
+
+
+Lx_fs::Notifier::Watches_list_element *Lx_fs::Notifier::_remove_node(Watches_list_element *node)
+{
+ int watch_fd { node->watch_fd };
+ Watches_list_element *next { remove_from_list(_watched_nodes, node, _heap) };
+ bool nodes_left { false };
+
+ Watches_list_element const *e { _watched_nodes.first() };
+ for (; e != nullptr && !nodes_left; e = e->next()) {
+ nodes_left = e->watch_fd == watch_fd;
+ }
+
+ if (!nodes_left) {
+ inotify_rm_watch(_fd, watch_fd);
+ }
+
+ return next;
+}
+
+
+void Lx_fs::Notifier::_handle_modify_file(inotify_event *event)
+{
+ for (Watches_list_element *e = _watched_nodes.first(); e != nullptr; e = e->next()) {
+ if (e->watch_fd == event->wd && (e->path.filename == event->name || e->path.is_dir())) {
+ e->notify_all([this] (Watch_node &node) {
+ _add_notify(node);
+ });
+ }
+ }
+}
+
+
+void Lx_fs::Notifier::_remove_empty_watches()
+{
+ for (Watches_list_element *e = _watched_nodes.first(); e != nullptr; ) {
+ if (e->empty()) {
+ e = _remove_node(e);
+ } else {
+ e = e->next();
+ }
+ }
+}
+
+
+void Lx_fs::Notifier::entry()
+{
+ enum {
+ SELECT_TIMEOUT_US = 5000, /* 5 milliseconds */
+ };
+
+ auto notify_ts { timestamp_us() };
+ bool pending_notify { false };
+ struct inotify_event *event { nullptr };
+ char buffer[EVENT_BUF_LEN] { 0 };
+ while (true) {
+
+ int num_select { 0 };
+ Fd_set fds { _fd };
+ struct timeval tv { .tv_sec = 0, .tv_usec = SELECT_TIMEOUT_US };
+
+ /*
+ * if no notifications are pending we wait until a inotify event occurs,
+ * otherwise we wait with a timeout on which we deliver some notifications
+ */
+ {
+ Mutex::Guard guard { _notify_queue_mutex };
+ pending_notify = _notify_queue.first() == nullptr;
+ }
+ if (pending_notify)
+ num_select = select(fds.nfds(), fds.fdset(), nullptr, nullptr, nullptr);
+ else
+ num_select = select(fds.nfds(), fds.fdset(), nullptr, nullptr, &tv);
+
+ /*
+ * select failed
+ */
+ if (num_select < 0) {
+ error("select on Linux event queue failed error=", Cstring { strerror(errno) });
+ continue;
+ }
+
+ /*
+ * select timed out, check if notifications are pending and send a bunch if neccessary
+ */
+ if (num_select == 0) {
+ _process_notify();
+ notify_ts = timestamp_us();
+ continue;
+ }
+
+ /*
+ * data is ready to be read from the inotify file descriptor
+ */
+ ssize_t const length { read(_fd, buffer, EVENT_BUF_LEN) };
+ ssize_t pos { 0 };
+
+ while (pos < length) {
+ event = reinterpret_cast(&buffer[pos]);
+
+ /*
+ * one of the registered events was triggered for
+ * one of the watched files/directories
+ */
+ if (event->mask & (INOTIFY_WATCH_MASK)) {
+ Mutex::Guard guard { _watched_nodes_mutex };
+ _handle_modify_file(event);
+ }
+
+ /* Linux kernel watch queue overflow */
+ else if (event->mask & IN_Q_OVERFLOW) {
+ error("Linux event queue overflow");
+ break;
+ }
+
+ pos += EVENT_SIZE + event->len;
+ }
+
+ /*
+ * Ensure that notifications are sent even when a lot of changes
+ * on the file system prevent the timout of select from triggering.
+ */
+ auto const delta { timestamp_us() - notify_ts };
+ if (delta > SELECT_TIMEOUT_US) {
+ _process_notify();
+ notify_ts = timestamp_us();
+ }
+ }
+}
+
+
+int Lx_fs::Notifier::add_watch(const char* path, Watch_node &node)
+{
+ {
+ Mutex::Guard guard { _watched_nodes_mutex };
+
+ if (!_watched(path)) {
+ _add_to_watched(path);
+ }
+ }
+
+ return _add_node(path, node);
+}
+
+
+void Lx_fs::Notifier::remove_watch(char const *path, Watch_node &node)
+{
+ {
+ Mutex::Guard guard { _notify_queue_mutex };
+ auto *e { _notify_queue.first() };
+ while (e != nullptr) {
+ if (&e->node == &node) {
+ auto *tmp { e };
+ e = e->next();
+ _notify_queue.remove(tmp);
+ destroy(_heap, tmp);
+ } else {
+ e = e->next();
+ }
+ }
+ }
+
+ {
+ Mutex::Guard guard { _watched_nodes_mutex };
+
+ for (Watches_list_element *e = _watched_nodes.first(); e != nullptr; e = e->next()) {
+ if (e->path.full_path == path) {
+ e->remove_node(_heap, node);
+ }
+ }
+
+ _remove_empty_watches();
+ }
+}
diff --git a/repos/os/src/server/lx_fs/notifier.h b/repos/os/src/server/lx_fs/notifier.h
new file mode 100644
index 0000000000..7329567768
--- /dev/null
+++ b/repos/os/src/server/lx_fs/notifier.h
@@ -0,0 +1,156 @@
+/*
+ * \brief inotify handling for underlying file system.
+ * \author Pirmin Duss
+ * \date 2020-06-17
+ */
+
+/*
+ * Copyright (C) 2020-2021 Genode Labs GmbH
+ * Copyright (C) 2020-2021 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.
+ */
+
+#ifndef _NOTIFIER_H_
+#define _NOTIFIER_H_
+
+/* Genode includes */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* libc includes */
+#include
+
+/* local includes */
+#include "lx_util.h"
+
+
+namespace Lx_fs
+{
+ using namespace Genode;
+
+ using Path_string = Genode::String;
+
+ class Init_notify_failed : public Genode::Exception { };
+
+ enum { MAX_PATH_SIZE = 1024 };
+ struct Os_path;
+
+ class Notifier;
+
+ /* forward decalaration */
+ class Watch_node;
+}
+
+
+/*
+ * full_path is always a concatenation of directory and filename
+ */
+struct Lx_fs::Os_path
+{
+ Path_string const full_path;
+ Path_string const directory; /* always ends with '/' */
+ Path_string const filename;
+
+ Os_path(char const *path);
+
+ bool is_dir() const { return filename.length() == 0; }
+
+};
+
+
+class Lx_fs::Notifier final : public Thread
+{
+ private:
+
+ struct Single_watch_list_element : public Genode::List::Element
+ {
+ Watch_node &node;
+
+ Single_watch_list_element(Watch_node &node) : node { node } {}
+ };
+
+ struct Watches_list_element : public Genode::List::Element
+ {
+ private:
+
+ List _nodes { };
+
+ public:
+
+ int const watch_fd;
+ Os_path const path;
+
+ Watches_list_element(int const watch_fd, Os_path const &path)
+ :
+ watch_fd { watch_fd }, path { path }
+ { }
+
+ ~Watches_list_element() = default;
+
+ bool empty() const { return _nodes.first() == nullptr; }
+
+ void add_node(Single_watch_list_element *cap_entry)
+ {
+ _nodes.insert(cap_entry);
+ }
+
+ template
+ void notify_all(FN const &fn)
+ {
+ for (Single_watch_list_element *e=_nodes.first(); e!=nullptr; e=e->next()) {
+ fn(e->node);
+ }
+ }
+
+ void remove_node(Allocator &alloc, Watch_node &node)
+ {
+ for (Single_watch_list_element *e=_nodes.first(); e!=nullptr; e=e->next()) {
+ if (&e->node == &node) {
+ _nodes.remove(e);
+ destroy(alloc, e);
+ return;
+ }
+ }
+ }
+ };
+
+ Env &_env;
+ Heap _heap { _env.ram(), _env.rm() };
+ int _fd { -1 };
+ List _watched_nodes { };
+ Mutex _watched_nodes_mutex { };
+ List _notify_queue { };
+ Mutex _notify_queue_mutex { };
+
+ void entry() override;
+
+ void _add_notify(Watch_node &node);
+ void _process_notify();
+ void _handle_removed_file(inotify_event *event);
+ void _handle_modify_file(inotify_event *event);
+ void _remove_empty_watches();
+ void _print_watches_list();
+
+ bool _watched(char const *path) const;
+ void _add_to_watched(char const *path);
+ int _add_node(char const *path, Watch_node &node);
+ Watches_list_element *_remove_node(Watches_list_element *node);
+
+ public:
+
+ Notifier(Env &env);
+ ~Notifier();
+
+ int add_watch(const char *path, Watch_node& node);
+ void remove_watch(char const *path, Watch_node &node);
+};
+
+#endif /* _NOTIFIER_H_ */
diff --git a/repos/os/src/server/lx_fs/target.mk b/repos/os/src/server/lx_fs/target.mk
index 1fb4a91ebb..8ccef42b0a 100644
--- a/repos/os/src/server/lx_fs/target.mk
+++ b/repos/os/src/server/lx_fs/target.mk
@@ -1,6 +1,6 @@
TARGET = lx_fs
REQUIRES = linux
-SRC_CC = main.cc
+SRC_CC = main.cc notifier.cc lx_util.cc watch.cc
LIBS = lx_hybrid
INC_DIR += $(PRG_DIR)
diff --git a/repos/os/src/server/lx_fs/watch.cc b/repos/os/src/server/lx_fs/watch.cc
new file mode 100644
index 0000000000..09052f9afa
--- /dev/null
+++ b/repos/os/src/server/lx_fs/watch.cc
@@ -0,0 +1,65 @@
+/*
+ * \brief File-system node
+ * \author Pirmin Duss
+ * \date 2020-06-17
+ */
+
+/*
+ * Copyright (C) 2013-2020 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.
+ */
+
+
+/* local includes */
+#include "watch.h"
+#include "notifier.h"
+
+
+unsigned long Lx_fs::Watch_node::_inode(char const *path)
+{
+ struct stat s { };
+ int ret { lstat(path, &s) };
+ if (ret == -1)
+ throw Lookup_failed();
+
+ return s.st_ino;
+}
+
+
+Lx_fs::Watch_node::Watch_node(Env &env,
+ char const *path,
+ Response_handler &response_handler,
+ Notifier ¬ifier)
+:
+ Node { _inode(path) },
+ _env { env },
+ _response_handler { response_handler },
+ _notifier { notifier }
+{
+ name(path);
+
+ if (notifier.add_watch(path, *this) < 0) {
+ throw Lookup_failed { };
+ }
+}
+
+Lx_fs::Watch_node::~Watch_node()
+{
+ _notifier.remove_watch(name(), *this);
+}
+
+
+void Lx_fs::Watch_node::_handle_notify()
+{
+ mark_as_updated();
+ _acked_packet = Packet_descriptor { Packet_descriptor { },
+ Node_handle { _open_node->id().value },
+ Packet_descriptor::CONTENT_CHANGED,
+ 0, 0 };
+ _acked_packet.succeeded(true);
+
+ _response_handler.handle_watch_node_response(*this);
+}
diff --git a/repos/os/src/server/lx_fs/watch.h b/repos/os/src/server/lx_fs/watch.h
new file mode 100644
index 0000000000..7202849638
--- /dev/null
+++ b/repos/os/src/server/lx_fs/watch.h
@@ -0,0 +1,95 @@
+/*
+ * \brief File-system node for watched files/directories
+ * \author Pirmin Duss
+ * \date 2020-06-17
+ */
+
+/*
+ * Copyright (C) 2013-2020 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.
+ */
+
+#ifndef _WATCH_H_
+#define _WATCH_H_
+
+/* Genode includes */
+#include
+#include
+#include
+#include
+
+/* local includes */
+#include "node.h"
+#include "open_node.h"
+
+
+namespace Lx_fs {
+
+ using namespace File_system;
+
+ /* forward declaration */
+ class Notifier;
+
+ class Watch_node;
+}
+
+
+class Lx_fs::Watch_node final : public Lx_fs::Node
+{
+ public:
+
+ using Packet_descriptor = File_system::Packet_descriptor;
+ using Fs_open_node = File_system::Open_node;
+
+ struct Response_handler : Genode::Interface
+ {
+ virtual void handle_watch_node_response(Watch_node &) = 0;
+ };
+
+ private:
+
+ using Signal_handler = Genode::Signal_handler;
+
+ /*
+ * Noncopyable
+ */
+ Watch_node(Watch_node const &) = delete;
+ Watch_node &operator = (Watch_node const &) = delete;
+
+ Genode::Env &_env;
+ Response_handler &_response_handler;
+ Notifier &_notifier;
+ Signal_handler _notify_handler { _env.ep(), *this, &Watch_node::_handle_notify };
+ Packet_descriptor _acked_packet { };
+ Fs_open_node *_open_node { nullptr };
+
+ void _handle_notify();
+
+ unsigned long _inode(char const *path);
+
+ public:
+
+ Watch_node(Genode::Env &env,
+ char const *path,
+ Response_handler &response_handler,
+ Notifier ¬ifier);
+
+ ~Watch_node();
+
+ Signal_handler ¬ify_handler() { return _notify_handler; }
+
+ void update_modification_time(Timestamp const) override { }
+ size_t read(char *, size_t, seek_off_t) override { return 0; }
+ size_t write(char const *, size_t, seek_off_t) override { return 0; }
+ Status status() override { return Status { }; }
+
+ Packet_descriptor &acked_packet() { return _acked_packet; }
+
+ void open_node(Fs_open_node *open_node) { _open_node = open_node; }
+ Fs_open_node *open_node() { return _open_node; }
+};
+
+#endif /* _WATCH_H_ */
diff --git a/repos/os/src/test/lx_fs_notify/file_writer/Makefile b/repos/os/src/test/lx_fs_notify/file_writer/Makefile
new file mode 100644
index 0000000000..5917d135d8
--- /dev/null
+++ b/repos/os/src/test/lx_fs_notify/file_writer/Makefile
@@ -0,0 +1,11 @@
+
+SRC_C = main.cc
+C_WARNINGS = -Wall -Werror
+BUILD_DIR = /tmp/bin
+
+all:
+ mkdir -p $(BUILD_DIR)
+ g++ $(C_WARNINGS) $(SRC_C) -o $(BUILD_DIR)/file_writer
+
+clean:
+ rm -Rf bin
\ No newline at end of file
diff --git a/repos/os/src/test/lx_fs_notify/file_writer/main.cc b/repos/os/src/test/lx_fs_notify/file_writer/main.cc
new file mode 100644
index 0000000000..c30180efed
--- /dev/null
+++ b/repos/os/src/test/lx_fs_notify/file_writer/main.cc
@@ -0,0 +1,118 @@
+/*
+ * \brief Test component for the watch feature of the `lx_fs` server.
+ * \author Pirmin Duss
+ * \date 2020-06-17
+ */
+
+/*
+ * Copyright (C) 2013-2020 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.
+ */
+
+
+/* libc includes */
+#include
+#include
+#include
+#include
+#include
+
+
+/* stdcxx includes */
+#include
+#include
+#include
+#include
+
+
+void print_usage(const char *prg)
+{
+ std::cerr << "Usage: " << prg << " <--fwrite|--write> \n\n";
+ std::cerr << " --fwrite use fopen/fwrite/fclose functions\n";
+ std::cerr << " --write use open/write/close functions\n";
+ std::cerr << " input_file_name name of the input file to write.\n";
+ std::cerr << " output_file_name name of the file to write to.\n";
+ std::cerr << "\n" << std::endl;
+
+ exit(-1);
+}
+
+
+void use_fwrite(std::string const &data, char const *out_file_name)
+{
+ FILE *out_fp { fopen(out_file_name, "w") };
+ if (out_fp == nullptr) {
+ exit(-88);
+ }
+
+ auto to_write { data.size() };
+ size_t pos { 0 };
+
+ while (to_write > 0) {
+
+ auto written { fwrite(data.c_str()+pos, sizeof(char), to_write, out_fp) };
+
+ to_write -= written;
+ pos += written;
+ }
+
+ fclose(out_fp);
+}
+
+
+void use_write(std::string const &data, char const *out_file_name)
+{
+ auto fd { open(out_file_name, O_WRONLY) };
+ if (fd < 0) {
+ exit(-99);
+ }
+
+ auto to_write { data.size() };
+ size_t pos { 0 };
+
+ while (to_write > 0) {
+
+ auto written { write(fd, data.c_str()+pos, to_write) };
+
+ if (written < 0) {
+ exit(-66);
+ }
+
+ to_write -= written;
+ pos += written;
+ }
+
+ close(fd);
+}
+
+
+std::string read_input(char const *file_name)
+{
+ std::ifstream in_stream { file_name };
+
+ return std::string(std::istreambuf_iterator(in_stream),
+ std::istreambuf_iterator());
+}
+
+
+int main(int argc, char *argv[])
+{
+ if (argc < 4) {
+ print_usage(argv[0]);
+ }
+
+ std::string const data { read_input(argv[2]) };
+
+ if (strcmp(argv[1], "--fwrite") == 0) {
+ use_fwrite(data, argv[3]);
+ } else if (strcmp(argv[1], "--write") == 0) {
+ use_write(data, argv[3]);
+ } else {
+ print_usage(argv[0]);
+ }
+
+ return 0;
+}
diff --git a/repos/os/src/test/lx_fs_notify/file_writer/target.mk b/repos/os/src/test/lx_fs_notify/file_writer/target.mk
new file mode 100644
index 0000000000..b28f5b81ff
--- /dev/null
+++ b/repos/os/src/test/lx_fs_notify/file_writer/target.mk
@@ -0,0 +1,8 @@
+TARGET := test-file_writer
+
+LIBS := base
+LIBS += libc
+LIBS += posix
+LIBS += stdcxx
+
+SRC_CC := main.cc
\ No newline at end of file
diff --git a/repos/os/src/test/lx_fs_notify/rom_log/main.cc b/repos/os/src/test/lx_fs_notify/rom_log/main.cc
new file mode 100644
index 0000000000..bce9e1a729
--- /dev/null
+++ b/repos/os/src/test/lx_fs_notify/rom_log/main.cc
@@ -0,0 +1,55 @@
+/*
+ * \brief Test component for the watch feature of the `lx_fs` server.
+ * \author Pirmin Duss
+ * \date 2020-06-17
+ */
+
+/*
+ * Copyright (C) 2013-2020 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.
+ */
+
+
+#include
+#include
+
+
+namespace Test_lx_fs_notify
+{
+ using namespace Genode;
+
+ class Main;
+};
+
+
+class Test_lx_fs_notify::Main
+{
+ private:
+
+ Env& _env;
+ Signal_handler _update_handler { _env.ep(), *this, &Main::_update };
+ Attached_rom_dataspace _test_rom { _env, "outfile.txt" };
+
+ void _update()
+ {
+ _test_rom.update();
+ log("updated ROM content: size=", strlen(_test_rom.local_addr()));
+ }
+
+ public:
+
+ Main(Env& env) : _env { env }
+ {
+ _test_rom.sigh(_update_handler);
+ log("wait for ROM update");
+ }
+};
+
+
+void Component::construct(Genode::Env& env)
+{
+ static Test_lx_fs_notify::Main main { env };
+}
diff --git a/repos/os/src/test/lx_fs_notify/rom_log/target.mk b/repos/os/src/test/lx_fs_notify/rom_log/target.mk
new file mode 100644
index 0000000000..1ca7421ae3
--- /dev/null
+++ b/repos/os/src/test/lx_fs_notify/rom_log/target.mk
@@ -0,0 +1,5 @@
+TARGET := test-rom_log
+
+SRC_CC := main.cc
+
+LIBS := base