diff options
author | sin-ack <sin-ack@users.noreply.github.com> | 2021-05-12 19:17:51 +0000 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-05-12 22:38:20 +0200 |
commit | fe5ca6ca276ad452a16215031fb395b4f5bef048 (patch) | |
tree | 100ae8dbf8d2b1cee4125cf335017a021c63093f | |
parent | 2de11b0dc8bbaa0c264a7e6dbb32b5481a337fb8 (diff) | |
download | serenity-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.h | 28 | ||||
-rw-r--r-- | Kernel/API/InodeWatcherFlags.h | 18 | ||||
-rw-r--r-- | Kernel/API/Syscall.h | 304 | ||||
-rw-r--r-- | Kernel/CMakeLists.txt | 2 | ||||
-rw-r--r-- | Kernel/FileSystem/Ext2FileSystem.cpp | 14 | ||||
-rw-r--r-- | Kernel/FileSystem/File.h | 1 | ||||
-rw-r--r-- | Kernel/FileSystem/FileDescription.cpp | 21 | ||||
-rw-r--r-- | Kernel/FileSystem/FileDescription.h | 4 | ||||
-rw-r--r-- | Kernel/FileSystem/Inode.cpp | 38 | ||||
-rw-r--r-- | Kernel/FileSystem/Inode.h | 7 | ||||
-rw-r--r-- | Kernel/FileSystem/InodeWatcher.cpp | 140 | ||||
-rw-r--r-- | Kernel/FileSystem/InodeWatcher.h | 63 | ||||
-rw-r--r-- | Kernel/FileSystem/TmpFS.cpp | 7 | ||||
-rw-r--r-- | Kernel/Process.h | 4 | ||||
-rw-r--r-- | Kernel/Syscalls/inode_watcher.cpp | 88 | ||||
-rw-r--r-- | Kernel/Syscalls/watch_file.cpp | 44 |
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(¶ms, 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; -} - -} |