summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsin-ack <sin-ack@users.noreply.github.com>2021-05-12 19:17:51 +0000
committerAndreas Kling <kling@serenityos.org>2021-05-12 22:38:20 +0200
commitfe5ca6ca276ad452a16215031fb395b4f5bef048 (patch)
tree100ae8dbf8d2b1cee4125cf335017a021c63093f
parent2de11b0dc8bbaa0c264a7e6dbb32b5481a337fb8 (diff)
downloadserenity-fe5ca6ca276ad452a16215031fb395b4f5bef048.zip
Kernel: Implement multi-watch InodeWatcher :^)
This patch modifies InodeWatcher to switch to a one watcher, multiple watches architecture. The following changes have been made: - The watch_file syscall is removed, and in its place the create_iwatcher, iwatcher_add_watch and iwatcher_remove_watch calls have been added. - InodeWatcher now holds multiple WatchDescriptions for each file that is being watched. - The InodeWatcher file descriptor can be read from to receive events on all watched files. Co-authored-by: Gunnar Beutner <gunnar@beutner.name>
-rw-r--r--Kernel/API/InodeWatcherEvent.h28
-rw-r--r--Kernel/API/InodeWatcherFlags.h18
-rw-r--r--Kernel/API/Syscall.h304
-rw-r--r--Kernel/CMakeLists.txt2
-rw-r--r--Kernel/FileSystem/Ext2FileSystem.cpp14
-rw-r--r--Kernel/FileSystem/File.h1
-rw-r--r--Kernel/FileSystem/FileDescription.cpp21
-rw-r--r--Kernel/FileSystem/FileDescription.h4
-rw-r--r--Kernel/FileSystem/Inode.cpp38
-rw-r--r--Kernel/FileSystem/Inode.h7
-rw-r--r--Kernel/FileSystem/InodeWatcher.cpp140
-rw-r--r--Kernel/FileSystem/InodeWatcher.h63
-rw-r--r--Kernel/FileSystem/TmpFS.cpp7
-rw-r--r--Kernel/Process.h4
-rw-r--r--Kernel/Syscalls/inode_watcher.cpp88
-rw-r--r--Kernel/Syscalls/watch_file.cpp44
16 files changed, 521 insertions, 262 deletions
diff --git a/Kernel/API/InodeWatcherEvent.h b/Kernel/API/InodeWatcherEvent.h
index db26588700..9a2ce68152 100644
--- a/Kernel/API/InodeWatcherEvent.h
+++ b/Kernel/API/InodeWatcherEvent.h
@@ -1,21 +1,37 @@
/*
- * Copyright (c) 2020, the SerenityOS developers.
+ * Copyright (c) 2020-2021, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
+#include <AK/EnumBits.h>
#include <AK/Types.h>
+#ifdef KERNEL
+# include <LibC/limits.h>
+#else
+# include <limits.h>
+#endif
+
struct [[gnu::packed]] InodeWatcherEvent {
- enum class Type {
+ enum class Type : u32 {
Invalid = 0,
- Modified,
- ChildAdded,
- ChildRemoved,
+ MetadataModified = 1 << 0,
+ ContentModified = 1 << 1,
+ Deleted = 1 << 2,
+ ChildCreated = 1 << 3,
+ ChildDeleted = 1 << 4,
};
+ int watch_descriptor { 0 };
Type type { Type::Invalid };
- unsigned inode_index { 0 };
+ size_t name_length { 0 };
+ // This is a VLA which is written during the read() from the descriptor.
+ const char name[];
};
+
+AK_ENUM_BITWISE_OPERATORS(InodeWatcherEvent::Type);
+
+constexpr unsigned MAXIMUM_EVENT_SIZE = sizeof(InodeWatcherEvent) + NAME_MAX + 1;
diff --git a/Kernel/API/InodeWatcherFlags.h b/Kernel/API/InodeWatcherFlags.h
new file mode 100644
index 0000000000..c9cdf42b1f
--- /dev/null
+++ b/Kernel/API/InodeWatcherFlags.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/EnumBits.h>
+#include <AK/Types.h>
+
+enum class InodeWatcherFlags : u32 {
+ None = 0,
+ Nonblock = 1 << 0,
+ CloseOnExec = 1 << 1,
+};
+
+AK_ENUM_BITWISE_OPERATORS(InodeWatcherFlags);
diff --git a/Kernel/API/Syscall.h b/Kernel/API/Syscall.h
index e55d4fb739..57a36cd0e3 100644
--- a/Kernel/API/Syscall.h
+++ b/Kernel/API/Syscall.h
@@ -27,154 +27,156 @@ typedef u32 socklen_t;
namespace Kernel {
-#define ENUMERATE_SYSCALLS(S) \
- S(yield) \
- S(open) \
- S(close) \
- S(read) \
- S(lseek) \
- S(kill) \
- S(getuid) \
- S(exit) \
- S(geteuid) \
- S(getegid) \
- S(getgid) \
- S(getpid) \
- S(getppid) \
- S(getresuid) \
- S(getresgid) \
- S(waitid) \
- S(mmap) \
- S(munmap) \
- S(get_dir_entries) \
- S(getcwd) \
- S(gettimeofday) \
- S(gethostname) \
- S(sethostname) \
- S(chdir) \
- S(uname) \
- S(set_mmap_name) \
- S(readlink) \
- S(write) \
- S(ttyname) \
- S(stat) \
- S(getsid) \
- S(setsid) \
- S(getpgid) \
- S(setpgid) \
- S(getpgrp) \
- S(fork) \
- S(execve) \
- S(dup2) \
- S(sigaction) \
- S(umask) \
- S(getgroups) \
- S(setgroups) \
- S(sigreturn) \
- S(sigprocmask) \
- S(sigpending) \
- S(pipe) \
- S(killpg) \
- S(seteuid) \
- S(setegid) \
- S(setuid) \
- S(setgid) \
- S(setreuid) \
- S(setresuid) \
- S(setresgid) \
- S(alarm) \
- S(fstat) \
- S(access) \
- S(fcntl) \
- S(ioctl) \
- S(mkdir) \
- S(times) \
- S(utime) \
- S(sync) \
- S(ptsname) \
- S(select) \
- S(unlink) \
- S(poll) \
- S(rmdir) \
- S(chmod) \
- S(socket) \
- S(bind) \
- S(accept) \
- S(listen) \
- S(connect) \
- S(link) \
- S(chown) \
- S(fchmod) \
- S(symlink) \
- S(sendmsg) \
- S(recvmsg) \
- S(getsockopt) \
- S(setsockopt) \
- S(create_thread) \
- S(gettid) \
- S(donate) \
- S(rename) \
- S(ftruncate) \
- S(exit_thread) \
- S(mknod) \
- S(writev) \
- S(beep) \
- S(getsockname) \
- S(getpeername) \
- S(socketpair) \
- S(sched_setparam) \
- S(sched_getparam) \
- S(fchown) \
- S(halt) \
- S(reboot) \
- S(mount) \
- S(umount) \
- S(dump_backtrace) \
- S(dbgputch) \
- S(dbgputstr) \
- S(watch_file) \
- S(mprotect) \
- S(realpath) \
- S(get_process_name) \
- S(fchdir) \
- S(getrandom) \
- S(getkeymap) \
- S(setkeymap) \
- S(clock_gettime) \
- S(clock_settime) \
- S(clock_nanosleep) \
- S(join_thread) \
- S(module_load) \
- S(module_unload) \
- S(detach_thread) \
- S(set_thread_name) \
- S(get_thread_name) \
- S(madvise) \
- S(purge) \
- S(profiling_enable) \
- S(profiling_disable) \
- S(profiling_free_buffer) \
- S(futex) \
- S(chroot) \
- S(pledge) \
- S(unveil) \
- S(perf_event) \
- S(shutdown) \
- S(get_stack_bounds) \
- S(ptrace) \
- S(sendfd) \
- S(recvfd) \
- S(sysconf) \
- S(set_process_name) \
- S(disown) \
- S(adjtime) \
- S(allocate_tls) \
- S(prctl) \
- S(mremap) \
- S(set_coredump_metadata) \
- S(anon_create) \
- S(msyscall) \
- S(readv) \
+#define ENUMERATE_SYSCALLS(S) \
+ S(yield) \
+ S(open) \
+ S(close) \
+ S(read) \
+ S(lseek) \
+ S(kill) \
+ S(getuid) \
+ S(exit) \
+ S(geteuid) \
+ S(getegid) \
+ S(getgid) \
+ S(getpid) \
+ S(getppid) \
+ S(getresuid) \
+ S(getresgid) \
+ S(waitid) \
+ S(mmap) \
+ S(munmap) \
+ S(get_dir_entries) \
+ S(getcwd) \
+ S(gettimeofday) \
+ S(gethostname) \
+ S(sethostname) \
+ S(chdir) \
+ S(uname) \
+ S(set_mmap_name) \
+ S(readlink) \
+ S(write) \
+ S(ttyname) \
+ S(stat) \
+ S(getsid) \
+ S(setsid) \
+ S(getpgid) \
+ S(setpgid) \
+ S(getpgrp) \
+ S(fork) \
+ S(execve) \
+ S(dup2) \
+ S(sigaction) \
+ S(umask) \
+ S(getgroups) \
+ S(setgroups) \
+ S(sigreturn) \
+ S(sigprocmask) \
+ S(sigpending) \
+ S(pipe) \
+ S(killpg) \
+ S(seteuid) \
+ S(setegid) \
+ S(setuid) \
+ S(setgid) \
+ S(setreuid) \
+ S(setresuid) \
+ S(setresgid) \
+ S(alarm) \
+ S(fstat) \
+ S(access) \
+ S(fcntl) \
+ S(ioctl) \
+ S(mkdir) \
+ S(times) \
+ S(utime) \
+ S(sync) \
+ S(ptsname) \
+ S(select) \
+ S(unlink) \
+ S(poll) \
+ S(rmdir) \
+ S(chmod) \
+ S(socket) \
+ S(bind) \
+ S(accept) \
+ S(listen) \
+ S(connect) \
+ S(link) \
+ S(chown) \
+ S(fchmod) \
+ S(symlink) \
+ S(sendmsg) \
+ S(recvmsg) \
+ S(getsockopt) \
+ S(setsockopt) \
+ S(create_thread) \
+ S(gettid) \
+ S(donate) \
+ S(rename) \
+ S(ftruncate) \
+ S(exit_thread) \
+ S(mknod) \
+ S(writev) \
+ S(beep) \
+ S(getsockname) \
+ S(getpeername) \
+ S(socketpair) \
+ S(sched_setparam) \
+ S(sched_getparam) \
+ S(fchown) \
+ S(halt) \
+ S(reboot) \
+ S(mount) \
+ S(umount) \
+ S(dump_backtrace) \
+ S(dbgputch) \
+ S(dbgputstr) \
+ S(create_inode_watcher) \
+ S(inode_watcher_add_watch) \
+ S(inode_watcher_remove_watch) \
+ S(mprotect) \
+ S(realpath) \
+ S(get_process_name) \
+ S(fchdir) \
+ S(getrandom) \
+ S(getkeymap) \
+ S(setkeymap) \
+ S(clock_gettime) \
+ S(clock_settime) \
+ S(clock_nanosleep) \
+ S(join_thread) \
+ S(module_load) \
+ S(module_unload) \
+ S(detach_thread) \
+ S(set_thread_name) \
+ S(get_thread_name) \
+ S(madvise) \
+ S(purge) \
+ S(profiling_enable) \
+ S(profiling_disable) \
+ S(profiling_free_buffer) \
+ S(futex) \
+ S(chroot) \
+ S(pledge) \
+ S(unveil) \
+ S(perf_event) \
+ S(shutdown) \
+ S(get_stack_bounds) \
+ S(ptrace) \
+ S(sendfd) \
+ S(recvfd) \
+ S(sysconf) \
+ S(set_process_name) \
+ S(disown) \
+ S(adjtime) \
+ S(allocate_tls) \
+ S(prctl) \
+ S(mremap) \
+ S(set_coredump_metadata) \
+ S(anon_create) \
+ S(msyscall) \
+ S(readv) \
S(emuctl)
namespace Syscall {
@@ -442,6 +444,12 @@ struct SC_set_coredump_metadata_params {
StringArgument value;
};
+struct SC_inode_watcher_add_watch_params {
+ int fd;
+ StringArgument user_path;
+ u32 event_mask;
+};
+
void initialize();
int sync();
diff --git a/Kernel/CMakeLists.txt b/Kernel/CMakeLists.txt
index c9576d472e..5d904c1439 100644
--- a/Kernel/CMakeLists.txt
+++ b/Kernel/CMakeLists.txt
@@ -198,7 +198,7 @@ set(KERNEL_SOURCES
Syscalls/unveil.cpp
Syscalls/utime.cpp
Syscalls/waitid.cpp
- Syscalls/watch_file.cpp
+ Syscalls/inode_watcher.cpp
Syscalls/write.cpp
TTY/MasterPTY.cpp
TTY/PTYMultiplexer.cpp
diff --git a/Kernel/FileSystem/Ext2FileSystem.cpp b/Kernel/FileSystem/Ext2FileSystem.cpp
index a43dbfcb2e..30e59c2806 100644
--- a/Kernel/FileSystem/Ext2FileSystem.cpp
+++ b/Kernel/FileSystem/Ext2FileSystem.cpp
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -1033,6 +1034,8 @@ KResultOr<ssize_t> Ext2FSInode::write_bytes(off_t offset, ssize_t count, const U
nwritten += num_bytes_to_copy;
}
+ did_modify_contents();
+
dbgln_if(EXT2_VERY_DEBUG, "Ext2FSInode[{}]::write_bytes(): After write, i_size={}, i_blocks={} ({} blocks in list)", identifier(), size(), m_raw_inode.i_blocks, m_block_list.size());
return nwritten;
}
@@ -1208,7 +1211,7 @@ KResult Ext2FSInode::add_child(Inode& child, const StringView& name, mode_t mode
return result;
m_lookup_cache.set(name, child.index());
- did_add_child(child.identifier());
+ did_add_child(child.identifier(), name);
return KSuccess;
}
@@ -1245,7 +1248,7 @@ KResult Ext2FSInode::remove_child(const StringView& name)
if (result.is_error())
return result;
- did_remove_child(child_id);
+ did_remove_child(child_id, name);
return KSuccess;
}
@@ -1674,10 +1677,15 @@ KResult Ext2FSInode::decrement_link_count()
if (fs().is_readonly())
return EROFS;
VERIFY(m_raw_inode.i_links_count);
+
--m_raw_inode.i_links_count;
+ set_metadata_dirty(true);
+ if (m_raw_inode.i_links_count == 0)
+ did_delete_self();
+
if (ref_count() == 1 && m_raw_inode.i_links_count == 0)
fs().uncache_inode(index());
- set_metadata_dirty(true);
+
return KSuccess;
}
diff --git a/Kernel/FileSystem/File.h b/Kernel/FileSystem/File.h
index fade38d7a7..32de5c75a3 100644
--- a/Kernel/FileSystem/File.h
+++ b/Kernel/FileSystem/File.h
@@ -109,6 +109,7 @@ public:
virtual bool is_block_device() const { return false; }
virtual bool is_character_device() const { return false; }
virtual bool is_socket() const { return false; }
+ virtual bool is_inode_watcher() const { return false; }
virtual FileBlockCondition& block_condition() { return m_block_condition; }
diff --git a/Kernel/FileSystem/FileDescription.cpp b/Kernel/FileSystem/FileDescription.cpp
index 077de0f25c..8af6efb8a0 100644
--- a/Kernel/FileSystem/FileDescription.cpp
+++ b/Kernel/FileSystem/FileDescription.cpp
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -13,6 +14,7 @@
#include <Kernel/FileSystem/FileDescription.h>
#include <Kernel/FileSystem/FileSystem.h>
#include <Kernel/FileSystem/InodeFile.h>
+#include <Kernel/FileSystem/InodeWatcher.h>
#include <Kernel/Net/Socket.h>
#include <Kernel/Process.h>
#include <Kernel/TTY/MasterPTY.h>
@@ -295,6 +297,25 @@ TTY* FileDescription::tty()
return static_cast<TTY*>(m_file.ptr());
}
+bool FileDescription::is_inode_watcher() const
+{
+ return m_file->is_inode_watcher();
+}
+
+const InodeWatcher* FileDescription::inode_watcher() const
+{
+ if (!is_inode_watcher())
+ return nullptr;
+ return static_cast<const InodeWatcher*>(m_file.ptr());
+}
+
+InodeWatcher* FileDescription::inode_watcher()
+{
+ if (!is_inode_watcher())
+ return nullptr;
+ return static_cast<InodeWatcher*>(m_file.ptr());
+}
+
bool FileDescription::is_master_pty() const
{
return m_file->is_master_pty();
diff --git a/Kernel/FileSystem/FileDescription.h b/Kernel/FileSystem/FileDescription.h
index 06e03e6171..47efdec69c 100644
--- a/Kernel/FileSystem/FileDescription.h
+++ b/Kernel/FileSystem/FileDescription.h
@@ -77,6 +77,10 @@ public:
const TTY* tty() const;
TTY* tty();
+ bool is_inode_watcher() const;
+ const InodeWatcher* inode_watcher() const;
+ InodeWatcher* inode_watcher();
+
bool is_master_pty() const;
const MasterPTY* master_pty() const;
MasterPTY* master_pty();
diff --git a/Kernel/FileSystem/Inode.cpp b/Kernel/FileSystem/Inode.cpp
index 2adef0aa6e..e1fcee8eb2 100644
--- a/Kernel/FileSystem/Inode.cpp
+++ b/Kernel/FileSystem/Inode.cpp
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -109,6 +110,10 @@ Inode::~Inode()
{
ScopedSpinLock all_inodes_lock(s_all_inodes_lock);
all_with_lock().remove(this);
+
+ for (auto& watcher : m_watchers) {
+ watcher->unregister_by_inode({}, identifier());
+ }
}
void Inode::will_be_destroyed()
@@ -211,24 +216,47 @@ void Inode::set_metadata_dirty(bool metadata_dirty)
// FIXME: Maybe we should hook into modification events somewhere else, I'm not sure where.
// We don't always end up on this particular code path, for instance when writing to an ext2fs file.
for (auto& watcher : m_watchers) {
- watcher->notify_inode_event({}, InodeWatcherEvent::Type::Modified);
+ watcher->notify_inode_event({}, identifier(), InodeWatcherEvent::Type::MetadataModified);
}
}
}
-void Inode::did_add_child(const InodeIdentifier& child_id)
+void Inode::did_add_child(InodeIdentifier const&, String const& name)
+{
+ Locker locker(m_lock);
+
+ for (auto& watcher : m_watchers) {
+ watcher->notify_inode_event({}, identifier(), InodeWatcherEvent::Type::ChildCreated, name);
+ }
+}
+
+void Inode::did_remove_child(InodeIdentifier const&, String const& name)
+{
+ Locker locker(m_lock);
+
+ if (name == "." || name == "..") {
+ // These are just aliases and are not interesting to userspace.
+ return;
+ }
+
+ for (auto& watcher : m_watchers) {
+ watcher->notify_inode_event({}, identifier(), InodeWatcherEvent::Type::ChildDeleted, name);
+ }
+}
+
+void Inode::did_modify_contents()
{
Locker locker(m_lock);
for (auto& watcher : m_watchers) {
- watcher->notify_child_added({}, child_id);
+ watcher->notify_inode_event({}, identifier(), InodeWatcherEvent::Type::ContentModified);
}
}
-void Inode::did_remove_child(const InodeIdentifier& child_id)
+void Inode::did_delete_self()
{
Locker locker(m_lock);
for (auto& watcher : m_watchers) {
- watcher->notify_child_removed({}, child_id);
+ watcher->notify_inode_event({}, identifier(), InodeWatcherEvent::Type::Deleted);
}
}
diff --git a/Kernel/FileSystem/Inode.h b/Kernel/FileSystem/Inode.h
index 6eb78be34d..3b027402e6 100644
--- a/Kernel/FileSystem/Inode.h
+++ b/Kernel/FileSystem/Inode.h
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -111,8 +112,10 @@ protected:
void set_metadata_dirty(bool);
KResult prepare_to_write_data();
- void did_add_child(const InodeIdentifier&);
- void did_remove_child(const InodeIdentifier&);
+ void did_add_child(InodeIdentifier const& child_id, String const& name);
+ void did_remove_child(InodeIdentifier const& child_id, String const& name);
+ void did_modify_contents();
+ void did_delete_self();
mutable Lock m_lock { "Inode" };
diff --git a/Kernel/FileSystem/InodeWatcher.cpp b/Kernel/FileSystem/InodeWatcher.cpp
index 06b208c471..ea66d7cc3a 100644
--- a/Kernel/FileSystem/InodeWatcher.cpp
+++ b/Kernel/FileSystem/InodeWatcher.cpp
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -7,53 +8,59 @@
#include <AK/Memory.h>
#include <Kernel/FileSystem/Inode.h>
#include <Kernel/FileSystem/InodeWatcher.h>
+#include <Kernel/Process.h>
namespace Kernel {
-NonnullRefPtr<InodeWatcher> InodeWatcher::create(Inode& inode)
+NonnullRefPtr<InodeWatcher> InodeWatcher::create()
{
- return adopt_ref(*new InodeWatcher(inode));
-}
-
-InodeWatcher::InodeWatcher(Inode& inode)
- : m_inode(inode)
-{
- inode.register_watcher({}, *this);
+ return adopt_ref(*new InodeWatcher);
}
InodeWatcher::~InodeWatcher()
{
- if (auto inode = m_inode.strong_ref())
- inode->unregister_watcher({}, *this);
+ (void)close();
}
bool InodeWatcher::can_read(const FileDescription&, size_t) const
{
- return !m_queue.is_empty() || !m_inode;
-}
-
-bool InodeWatcher::can_write(const FileDescription&, size_t) const
-{
- return true;
+ Locker locker(m_lock);
+ return !m_queue.is_empty();
}
KResultOr<size_t> InodeWatcher::read(FileDescription&, u64, UserOrKernelBuffer& buffer, size_t buffer_size)
{
Locker locker(m_lock);
- VERIFY(!m_queue.is_empty() || !m_inode);
-
- if (!m_inode)
- return 0;
+ if (m_queue.is_empty())
+ // can_read will catch the blocking case.
+ return EAGAIN;
auto event = m_queue.dequeue();
- if (buffer_size < sizeof(InodeWatcherEvent))
- return buffer_size;
+ size_t name_length = event.path.length() + 1;
+ size_t bytes_to_write = sizeof(InodeWatcherEvent);
+ if (!event.path.is_null())
+ bytes_to_write += name_length;
+
+ if (buffer_size < bytes_to_write)
+ return EINVAL;
- size_t bytes_to_write = min(buffer_size, sizeof(event));
+ ssize_t nwritten = buffer.write_buffered<MAXIMUM_EVENT_SIZE>(bytes_to_write, [&](u8* data, size_t data_bytes) {
+ size_t offset = 0;
+
+ memcpy(data + offset, &event.wd, sizeof(InodeWatcherEvent::watch_descriptor));
+ offset += sizeof(InodeWatcherEvent::watch_descriptor);
+ memcpy(data + offset, &event.type, sizeof(InodeWatcherEvent::type));
+ offset += sizeof(InodeWatcherEvent::type);
+
+ if (!event.path.is_null()) {
+ memcpy(data + offset, &name_length, sizeof(InodeWatcherEvent::name_length));
+ offset += sizeof(InodeWatcherEvent::name_length);
+ memcpy(data + offset, event.path.characters(), name_length);
+ } else {
+ memset(data + offset, 0, sizeof(InodeWatcherEvent::name_length));
+ }
- ssize_t nwritten = buffer.write_buffered<sizeof(event)>(bytes_to_write, [&](u8* data, size_t data_bytes) {
- memcpy(data, &event, bytes_to_write);
return (ssize_t)data_bytes;
});
if (nwritten < 0)
@@ -62,37 +69,94 @@ KResultOr<size_t> InodeWatcher::read(FileDescription&, u64, UserOrKernelBuffer&
return bytes_to_write;
}
-KResultOr<size_t> InodeWatcher::write(FileDescription&, u64, const UserOrKernelBuffer&, size_t)
+KResult InodeWatcher::close()
{
- return EIO;
+ Locker locker(m_lock);
+
+ for (auto& entry : m_wd_to_watches) {
+ auto& inode = const_cast<Inode&>(entry.value->inode);
+ inode.unregister_watcher({}, *this);
+ }
+
+ m_wd_to_watches.clear();
+ m_inode_to_watches.clear();
+ return KSuccess;
}
String InodeWatcher::absolute_path(const FileDescription&) const
{
- if (auto inode = m_inode.strong_ref())
- return String::formatted("InodeWatcher:{}", inode->identifier().to_string());
- return "InodeWatcher:(gone)";
+ return String::formatted("InodeWatcher:({})", m_wd_to_watches.size());
}
-void InodeWatcher::notify_inode_event(Badge<Inode>, InodeWatcherEvent::Type event_type)
+void InodeWatcher::notify_inode_event(Badge<Inode>, InodeIdentifier inode_id, InodeWatcherEvent::Type event_type, String const& name)
{
Locker locker(m_lock);
- m_queue.enqueue({ event_type });
+
+ auto it = m_inode_to_watches.find(inode_id);
+ if (it == m_inode_to_watches.end())
+ return;
+
+ auto& watcher = *it->value;
+ if (!(watcher.event_mask & static_cast<unsigned>(event_type)))
+ return;
+
+ m_queue.enqueue({ watcher.wd, event_type, name });
evaluate_block_conditions();
}
-void InodeWatcher::notify_child_added(Badge<Inode>, const InodeIdentifier& child_id)
+KResultOr<int> InodeWatcher::register_inode(Inode& inode, unsigned event_mask)
{
Locker locker(m_lock);
- m_queue.enqueue({ InodeWatcherEvent::Type::ChildAdded, child_id.index().value() });
- evaluate_block_conditions();
+
+ if (m_inode_to_watches.find(inode.identifier()) != m_inode_to_watches.end())
+ return EEXIST;
+
+ int wd;
+ do {
+ wd = m_wd_counter.value();
+
+ m_wd_counter++;
+ if (m_wd_counter.has_overflow())
+ m_wd_counter = 1;
+ } while (m_wd_to_watches.find(wd) != m_wd_to_watches.end());
+
+ auto description = WatchDescription::create(wd, inode, event_mask);
+ m_inode_to_watches.set(inode.identifier(), description.ptr());
+ m_wd_to_watches.set(wd, move(description));
+
+ inode.register_watcher({}, *this);
+ return wd;
}
-void InodeWatcher::notify_child_removed(Badge<Inode>, const InodeIdentifier& child_id)
+KResult InodeWatcher::unregister_by_wd(int wd)
{
Locker locker(m_lock);
- m_queue.enqueue({ InodeWatcherEvent::Type::ChildRemoved, child_id.index().value() });
- evaluate_block_conditions();
+
+ auto it = m_wd_to_watches.find(wd);
+ if (it == m_wd_to_watches.end())
+ return ENOENT;
+
+ auto& inode = it->value->inode;
+ inode.unregister_watcher({}, *this);
+
+ m_inode_to_watches.remove(inode.identifier());
+ m_wd_to_watches.remove(it);
+
+ return KSuccess;
+}
+
+void InodeWatcher::unregister_by_inode(Badge<Inode>, InodeIdentifier identifier)
+{
+ Locker locker(m_lock);
+
+ auto it = m_inode_to_watches.find(identifier);
+ if (it == m_inode_to_watches.end())
+ return;
+
+ // NOTE: no need to call unregister_watcher here, the Inode calls us.
+
+ m_inode_to_watches.remove(identifier);
+ m_wd_to_watches.remove(it->value->wd);
}
}
diff --git a/Kernel/FileSystem/InodeWatcher.h b/Kernel/FileSystem/InodeWatcher.h
index 1c55104a8f..f429e17ab4 100644
--- a/Kernel/FileSystem/InodeWatcher.h
+++ b/Kernel/FileSystem/InodeWatcher.h
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -7,38 +8,76 @@
#pragma once
#include <AK/Badge.h>
+#include <AK/Checked.h>
#include <AK/CircularQueue.h>
-#include <AK/WeakPtr.h>
+#include <AK/HashMap.h>
+#include <AK/NonnullOwnPtr.h>
#include <Kernel/API/InodeWatcherEvent.h>
#include <Kernel/FileSystem/File.h>
-#include <Kernel/Lock.h>
namespace Kernel {
class Inode;
+// A specific description of a watch.
+struct WatchDescription {
+ int wd;
+ Inode& inode;
+ unsigned event_mask;
+
+ static NonnullOwnPtr<WatchDescription> create(int wd, Inode& inode, unsigned event_mask)
+ {
+ return adopt_own(*new WatchDescription(wd, inode, event_mask));
+ }
+
+private:
+ WatchDescription(int wd, Inode& inode, unsigned event_mask)
+ : wd(wd)
+ , inode(inode)
+ , event_mask(event_mask)
+ {
+ }
+};
+
class InodeWatcher final : public File {
public:
- static NonnullRefPtr<InodeWatcher> create(Inode&);
+ static NonnullRefPtr<InodeWatcher> create();
virtual ~InodeWatcher() override;
virtual bool can_read(const FileDescription&, size_t) const override;
- virtual bool can_write(const FileDescription&, size_t) const override;
virtual KResultOr<size_t> read(FileDescription&, u64, UserOrKernelBuffer&, size_t) override;
- virtual KResultOr<size_t> write(FileDescription&, u64, const UserOrKernelBuffer&, size_t) override;
+ // Can't write to an inode watcher.
+ virtual bool can_write(const FileDescription&, size_t) const override { return true; }
+ virtual KResultOr<size_t> write(FileDescription&, u64, const UserOrKernelBuffer&, size_t) override { return EIO; }
+ virtual KResult close() override;
+
virtual String absolute_path(const FileDescription&) const override;
virtual const char* class_name() const override { return "InodeWatcher"; };
+ virtual bool is_inode_watcher() const override { return true; }
+
+ void notify_inode_event(Badge<Inode>, InodeIdentifier, InodeWatcherEvent::Type, String const& name = {});
- void notify_inode_event(Badge<Inode>, InodeWatcherEvent::Type);
- void notify_child_added(Badge<Inode>, const InodeIdentifier& child_id);
- void notify_child_removed(Badge<Inode>, const InodeIdentifier& child_id);
+ KResultOr<int> register_inode(Inode&, unsigned event_mask);
+ KResult unregister_by_wd(int);
+ void unregister_by_inode(Badge<Inode>, InodeIdentifier);
private:
- explicit InodeWatcher(Inode&);
+ explicit InodeWatcher() { }
+
+ mutable Lock m_lock;
+
+ struct Event {
+ int wd { 0 };
+ InodeWatcherEvent::Type type { InodeWatcherEvent::Type::Invalid };
+ String path;
+ };
+ CircularQueue<Event, 32> m_queue;
+ Checked<int> m_wd_counter { 1 };
- Lock m_lock;
- WeakPtr<Inode> m_inode;
- CircularQueue<InodeWatcherEvent, 32> m_queue;
+ // NOTE: These two hashmaps provide two different ways of reaching the same
+ // watch description, so they will overlap.
+ HashMap<int, NonnullOwnPtr<WatchDescription>> m_wd_to_watches;
+ HashMap<InodeIdentifier, WatchDescription*> m_inode_to_watches;
};
}
diff --git a/Kernel/FileSystem/TmpFS.cpp b/Kernel/FileSystem/TmpFS.cpp
index 541a1ad5a7..e56e310207 100644
--- a/Kernel/FileSystem/TmpFS.cpp
+++ b/Kernel/FileSystem/TmpFS.cpp
@@ -188,6 +188,8 @@ KResultOr<ssize_t> TmpFSInode::write_bytes(off_t offset, ssize_t size, const Use
if (!buffer.read(m_content->data() + offset, size)) // TODO: partial reads?
return EFAULT;
+
+ did_modify_contents();
return size;
}
@@ -284,7 +286,7 @@ KResult TmpFSInode::add_child(Inode& child, const StringView& name, mode_t)
return ENAMETOOLONG;
m_children.set(name, { name, static_cast<TmpFSInode&>(child) });
- did_add_child(child.identifier());
+ did_add_child(child.identifier(), name);
return KSuccess;
}
@@ -300,8 +302,9 @@ KResult TmpFSInode::remove_child(const StringView& name)
if (it == m_children.end())
return ENOENT;
auto child_id = it->value.inode->identifier();
+ it->value.inode->did_delete_self();
m_children.remove(it);
- did_remove_child(child_id);
+ did_remove_child(child_id, name);
return KSuccess;
}
diff --git a/Kernel/Process.h b/Kernel/Process.h
index 054bcc1621..59ffec5e84 100644
--- a/Kernel/Process.h
+++ b/Kernel/Process.h
@@ -251,7 +251,9 @@ public:
KResultOr<int> sys$beep();
KResultOr<int> sys$get_process_name(Userspace<char*> buffer, size_t buffer_size);
KResultOr<int> sys$set_process_name(Userspace<const char*> user_name, size_t user_name_length);
- KResultOr<int> sys$watch_file(Userspace<const char*> path, size_t path_length);
+ KResultOr<int> sys$create_inode_watcher(u32 flags);
+ KResultOr<int> sys$inode_watcher_add_watch(Userspace<const Syscall::SC_inode_watcher_add_watch_params*> user_params);
+ KResultOr<int> sys$inode_watcher_remove_watch(int fd, int wd);
KResultOr<int> sys$dbgputch(u8);
KResultOr<int> sys$dbgputstr(Userspace<const u8*>, int length);
KResultOr<int> sys$dump_backtrace();
diff --git a/Kernel/Syscalls/inode_watcher.cpp b/Kernel/Syscalls/inode_watcher.cpp
new file mode 100644
index 0000000000..fc9e878acb
--- /dev/null
+++ b/Kernel/Syscalls/inode_watcher.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <Kernel/API/InodeWatcherFlags.h>
+#include <Kernel/FileSystem/Custody.h>
+#include <Kernel/FileSystem/FileDescription.h>
+#include <Kernel/FileSystem/InodeWatcher.h>
+#include <Kernel/Process.h>
+
+namespace Kernel {
+
+KResultOr<int> Process::sys$create_inode_watcher(u32 flags)
+{
+ REQUIRE_PROMISE(rpath);
+
+ int fd = alloc_fd();
+ if (fd < 0)
+ return fd;
+
+ auto description_or_error = FileDescription::create(*InodeWatcher::create());
+ if (description_or_error.is_error())
+ return description_or_error.error();
+
+ m_fds[fd].set(description_or_error.release_value());
+ m_fds[fd].description()->set_readable(true);
+
+ if (flags & static_cast<unsigned>(InodeWatcherFlags::Nonblock))
+ m_fds[fd].description()->set_blocking(false);
+ if (flags & static_cast<unsigned>(InodeWatcherFlags::CloseOnExec))
+ m_fds[fd].set_flags(m_fds[fd].flags() | FD_CLOEXEC);
+
+ return fd;
+}
+
+KResultOr<int> Process::sys$inode_watcher_add_watch(Userspace<const Syscall::SC_inode_watcher_add_watch_params*> user_params)
+{
+ REQUIRE_PROMISE(rpath);
+
+ Syscall::SC_inode_watcher_add_watch_params params;
+ if (!copy_from_user(&params, user_params))
+ return EFAULT;
+
+ auto description = file_description(params.fd);
+ if (!description)
+ return EBADF;
+ if (!description->is_inode_watcher())
+ return EBADF;
+ auto inode_watcher = description->inode_watcher();
+
+ auto path = get_syscall_path_argument(params.user_path.characters, params.user_path.length);
+ if (path.is_error())
+ return path.error();
+
+ auto custody_or_error = VFS::the().resolve_path(path.value(), current_directory());
+ if (custody_or_error.is_error())
+ return custody_or_error.error();
+
+ auto& custody = custody_or_error.value();
+ if (!custody->inode().fs().supports_watchers())
+ return ENOTSUP;
+
+ auto wd_or_error = inode_watcher->register_inode(custody->inode(), params.event_mask);
+ if (wd_or_error.is_error())
+ return wd_or_error.error();
+ return wd_or_error.value();
+}
+
+KResultOr<int> Process::sys$inode_watcher_remove_watch(int fd, int wd)
+{
+ auto description = file_description(fd);
+ if (!description)
+ return EBADF;
+ if (!description->is_inode_watcher())
+ return EBADF;
+ auto inode_watcher = description->inode_watcher();
+
+ auto result = inode_watcher->unregister_by_wd(wd);
+ if (result.is_error())
+ return result;
+
+ return 0;
+}
+
+}
diff --git a/Kernel/Syscalls/watch_file.cpp b/Kernel/Syscalls/watch_file.cpp
deleted file mode 100644
index 9ccce9ef54..0000000000
--- a/Kernel/Syscalls/watch_file.cpp
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
-
-#include <Kernel/FileSystem/Custody.h>
-#include <Kernel/FileSystem/FileDescription.h>
-#include <Kernel/FileSystem/InodeWatcher.h>
-#include <Kernel/Process.h>
-
-namespace Kernel {
-
-KResultOr<int> Process::sys$watch_file(Userspace<const char*> user_path, size_t path_length)
-{
- REQUIRE_PROMISE(rpath);
- auto path = get_syscall_path_argument(user_path, path_length);
- if (path.is_error())
- return path.error();
-
- auto custody_or_error = VFS::the().resolve_path(path.value(), current_directory());
- if (custody_or_error.is_error())
- return custody_or_error.error();
-
- auto& custody = custody_or_error.value();
- auto& inode = custody->inode();
-
- if (!inode.fs().supports_watchers())
- return ENOTSUP;
-
- int fd = alloc_fd();
- if (fd < 0)
- return fd;
-
- auto description = FileDescription::create(*InodeWatcher::create(inode));
- if (description.is_error())
- return description.error();
-
- m_fds[fd].set(description.release_value());
- m_fds[fd].description()->set_readable(true);
- return fd;
-}
-
-}