mirror of
https://github.com/genodelabs/genode.git
synced 2025-05-31 06:31:10 +00:00
lx_fs: enable watch
Enable watching files via the inotify interface of the Linux Kernel. Delivery of watches to components is staggered in order to prevent an overflow of the ACK queue in cases when a lot of changes are made to the file system from the Linux side. Fixes #4070
This commit is contained in:
parent
37f1873f2e
commit
7db6f457d4
436
repos/base-linux/run/lx_fs_notify.run
Normal file
436
repos/base-linux/run/lx_fs_notify.run
Normal file
@ -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 {
|
||||
<config>
|
||||
<parent-provides>
|
||||
<service name="CPU"/>
|
||||
<service name="LOG"/>
|
||||
<service name="PD"/>
|
||||
<service name="RM"/>
|
||||
<service name="ROM"/>
|
||||
</parent-provides>
|
||||
<default-route>
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</default-route>
|
||||
<default caps="100"/>
|
||||
|
||||
<start name="lx_fs" caps="200" ld="no">
|
||||
<resource name="RAM" quantum="4M"/>
|
||||
<provides> <service name="File_system"/> </provides>
|
||||
<config>
|
||||
<policy label_prefix="fs_rom_config" root="/lx_fs_notify" writeable="no"/>
|
||||
<policy label_prefix="fs_rom_test" root="/lx_fs_notify/test" writeable="no"/>
|
||||
<policy label_suffix="templates" root="/lx_fs_notify/templates" writeable="yes"/>
|
||||
<policy label_suffix="test" root="/lx_fs_notify/test" writeable="yes"/>
|
||||
</config>
|
||||
</start>
|
||||
|
||||
<start name="fs_rom_config">
|
||||
<binary name="fs_rom"/>
|
||||
<resource name="RAM" quantum="8M"/>
|
||||
<provides> <service name="ROM"/> </provides>
|
||||
<route>
|
||||
<service name="File_system"> <child name="lx_fs"/> </service>
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
|
||||
<start name="fs_rom_test">
|
||||
<binary name="fs_rom"/>
|
||||
<resource name="RAM" quantum="40M"/>
|
||||
<provides> <service name="ROM"/> </provides>
|
||||
<route>
|
||||
<service name="File_system"> <child name="lx_fs"/> </service>
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
|
||||
<start name="test-rom_log">
|
||||
<resource name="RAM" quantum="8M"/>
|
||||
<route>
|
||||
<service name="ROM" label="outfile.txt"> <child name="fs_rom_test"/> </service>
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
|
||||
<start name="init" caps="4000">
|
||||
<resource name="RAM" quantum="300M"/>
|
||||
<route>
|
||||
<service name="File_system"> <child name="lx_fs"/> </service>
|
||||
<service name="ROM" label="config"> <child name="fs_rom_config" label="init.config"/> </service>
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
|
||||
</config>
|
||||
}
|
||||
|
||||
#
|
||||
# configurations for the sub init
|
||||
#
|
||||
set init_run_fwrite_test {
|
||||
<config>
|
||||
<parent-provides>
|
||||
<service name="CPU"/>
|
||||
<service name="LOG"/>
|
||||
<service name="PD"/>
|
||||
<service name="RM"/>
|
||||
<service name="ROM"/>
|
||||
<service name="Timer"/>
|
||||
<service name="File_system"/>
|
||||
</parent-provides>
|
||||
<default-route>
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</default-route>
|
||||
<default caps="100"/>
|
||||
|
||||
<start name="test-file_writer" caps="300" version="1">
|
||||
<resource name="RAM" quantum="16M"/>
|
||||
<config>
|
||||
<libc stdout="/dev/log" stderr="/dev/log"/>
|
||||
<vfs>
|
||||
<dir name="dev">
|
||||
<log/>
|
||||
<null/>
|
||||
</dir>
|
||||
<dir name="templates"> <fs label="templates"/> </dir>
|
||||
<dir name="test"> <fs label="test"/> </dir>
|
||||
</vfs>
|
||||
<arg value="test-file_writer"/>
|
||||
<arg value="--fwrite"/>
|
||||
<arg value="templates/infile.txt"/>
|
||||
<arg value="test/outfile.txt"/>
|
||||
</config>
|
||||
</start>
|
||||
</config>
|
||||
}
|
||||
|
||||
set init_run_write_test {
|
||||
<config>
|
||||
<parent-provides>
|
||||
<service name="CPU"/>
|
||||
<service name="LOG"/>
|
||||
<service name="PD"/>
|
||||
<service name="RM"/>
|
||||
<service name="ROM"/>
|
||||
<service name="Timer"/>
|
||||
<service name="File_system"/>
|
||||
</parent-provides>
|
||||
<default-route>
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</default-route>
|
||||
<default caps="100"/>
|
||||
|
||||
<start name="test-file_writer" caps="300" version="2">
|
||||
<resource name="RAM" quantum="16M"/>
|
||||
<config>
|
||||
<libc stdout="/dev/log" stderr="/dev/log"/>
|
||||
<vfs>
|
||||
<dir name="dev">
|
||||
<log/>
|
||||
<null/>
|
||||
</dir>
|
||||
<dir name="templates"> <fs label="templates"/> </dir>
|
||||
<dir name="test"> <fs label="test"/> </dir>
|
||||
</vfs>
|
||||
<arg value="test-file_writer"/>
|
||||
<arg value="--write"/>
|
||||
<arg value="templates/infile.txt"/>
|
||||
<arg value="test/outfile.txt"/>
|
||||
</config>
|
||||
</start>
|
||||
</config>
|
||||
}
|
||||
|
||||
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 :
|
@ -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.
|
||||
|
49
repos/os/src/server/lx_fs/fd_set.h
Normal file
49
repos/os/src/server/lx_fs/fd_set.h
Normal file
@ -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 <sys/select.h>
|
||||
|
||||
|
||||
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_ */
|
43
repos/os/src/server/lx_fs/lx_util.cc
Normal file
43
repos/os/src/server/lx_fs/lx_util.cc
Normal file
@ -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 };
|
||||
}
|
@ -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_ */
|
||||
|
@ -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 <signal.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
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<File_system::Node> _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<Lx_fs::Node>;
|
||||
_process_packet_op(node.acked_packet(),
|
||||
*(reinterpret_cast<Fs_node*>(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<Watch_node> *open_watch =
|
||||
new (_alloc) Lx_fs::Open_node<Watch_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<Session_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<Session_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 };
|
||||
|
427
repos/os/src/server/lx_fs/notifier.cc
Normal file
427
repos/os/src/server/lx_fs/notifier.cc
Normal file
@ -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 <base/log.h>
|
||||
#include "base/signal.h"
|
||||
#include "util/string.h"
|
||||
|
||||
/* libc includes */
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h> /* strerror */
|
||||
#include <sys/inotify.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* 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 <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
/* 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 <typename T, typename Allocator>
|
||||
T *remove_from_list(Genode::List<T> &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<struct inotify_event*>(&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();
|
||||
}
|
||||
}
|
156
repos/os/src/server/lx_fs/notifier.h
Normal file
156
repos/os/src/server/lx_fs/notifier.h
Normal file
@ -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 <base/exception.h>
|
||||
#include <base/heap.h>
|
||||
#include <base/mutex.h>
|
||||
#include <base/signal.h>
|
||||
#include <base/thread.h>
|
||||
#include <file_system/listener.h>
|
||||
#include <os/path.h>
|
||||
#include <util/list.h>
|
||||
|
||||
/* libc includes */
|
||||
#include <sys/inotify.h>
|
||||
|
||||
/* local includes */
|
||||
#include "lx_util.h"
|
||||
|
||||
|
||||
namespace Lx_fs
|
||||
{
|
||||
using namespace Genode;
|
||||
|
||||
using Path_string = Genode::String<File_system::MAX_PATH_LEN>;
|
||||
|
||||
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<Single_watch_list_element>::Element
|
||||
{
|
||||
Watch_node &node;
|
||||
|
||||
Single_watch_list_element(Watch_node &node) : node { node } {}
|
||||
};
|
||||
|
||||
struct Watches_list_element : public Genode::List<Watches_list_element>::Element
|
||||
{
|
||||
private:
|
||||
|
||||
List<Single_watch_list_element> _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 <typename FN>
|
||||
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<Watches_list_element> _watched_nodes { };
|
||||
Mutex _watched_nodes_mutex { };
|
||||
List<Single_watch_list_element> _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_ */
|
@ -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)
|
||||
|
65
repos/os/src/server/lx_fs/watch.cc
Normal file
65
repos/os/src/server/lx_fs/watch.cc
Normal file
@ -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);
|
||||
}
|
95
repos/os/src/server/lx_fs/watch.h
Normal file
95
repos/os/src/server/lx_fs/watch.h
Normal file
@ -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 <base/id_space.h>
|
||||
#include <file_system/node.h>
|
||||
#include <file_system/open_node.h>
|
||||
#include <os/path.h>
|
||||
|
||||
/* 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<Lx_fs::Watch_node>;
|
||||
|
||||
struct Response_handler : Genode::Interface
|
||||
{
|
||||
virtual void handle_watch_node_response(Watch_node &) = 0;
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
using Signal_handler = Genode::Signal_handler<Watch_node>;
|
||||
|
||||
/*
|
||||
* 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_ */
|
11
repos/os/src/test/lx_fs_notify/file_writer/Makefile
Normal file
11
repos/os/src/test/lx_fs_notify/file_writer/Makefile
Normal file
@ -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
|
118
repos/os/src/test/lx_fs_notify/file_writer/main.cc
Normal file
118
repos/os/src/test/lx_fs_notify/file_writer/main.cc
Normal file
@ -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 <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
||||
/* stdcxx includes */
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <streambuf>
|
||||
#include <iostream>
|
||||
|
||||
|
||||
void print_usage(const char *prg)
|
||||
{
|
||||
std::cerr << "Usage: " << prg << " <--fwrite|--write> <input_file_name> <output_file_name>\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<char>(in_stream),
|
||||
std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
8
repos/os/src/test/lx_fs_notify/file_writer/target.mk
Normal file
8
repos/os/src/test/lx_fs_notify/file_writer/target.mk
Normal file
@ -0,0 +1,8 @@
|
||||
TARGET := test-file_writer
|
||||
|
||||
LIBS := base
|
||||
LIBS += libc
|
||||
LIBS += posix
|
||||
LIBS += stdcxx
|
||||
|
||||
SRC_CC := main.cc
|
55
repos/os/src/test/lx_fs_notify/rom_log/main.cc
Normal file
55
repos/os/src/test/lx_fs_notify/rom_log/main.cc
Normal file
@ -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 <base/component.h>
|
||||
#include <base/attached_rom_dataspace.h>
|
||||
|
||||
|
||||
namespace Test_lx_fs_notify
|
||||
{
|
||||
using namespace Genode;
|
||||
|
||||
class Main;
|
||||
};
|
||||
|
||||
|
||||
class Test_lx_fs_notify::Main
|
||||
{
|
||||
private:
|
||||
|
||||
Env& _env;
|
||||
Signal_handler<Main> _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<const char>()));
|
||||
}
|
||||
|
||||
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 };
|
||||
}
|
5
repos/os/src/test/lx_fs_notify/rom_log/target.mk
Normal file
5
repos/os/src/test/lx_fs_notify/rom_log/target.mk
Normal file
@ -0,0 +1,5 @@
|
||||
TARGET := test-rom_log
|
||||
|
||||
SRC_CC := main.cc
|
||||
|
||||
LIBS := base
|
Loading…
x
Reference in New Issue
Block a user