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>
This commit is contained in:
sin-ack 2021-05-12 19:17:51 +00:00 committed by Andreas Kling
parent 2de11b0dc8
commit fe5ca6ca27
16 changed files with 521 additions and 262 deletions

View file

@ -1,21 +1,37 @@
/* /*
* Copyright (c) 2020, the SerenityOS developers. * Copyright (c) 2020-2021, the SerenityOS developers.
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#pragma once #pragma once
#include <AK/EnumBits.h>
#include <AK/Types.h> #include <AK/Types.h>
#ifdef KERNEL
# include <LibC/limits.h>
#else
# include <limits.h>
#endif
struct [[gnu::packed]] InodeWatcherEvent { struct [[gnu::packed]] InodeWatcherEvent {
enum class Type { enum class Type : u32 {
Invalid = 0, Invalid = 0,
Modified, MetadataModified = 1 << 0,
ChildAdded, ContentModified = 1 << 1,
ChildRemoved, Deleted = 1 << 2,
ChildCreated = 1 << 3,
ChildDeleted = 1 << 4,
}; };
int watch_descriptor { 0 };
Type type { Type::Invalid }; 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;

View file

@ -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);

View file

@ -27,154 +27,156 @@ typedef u32 socklen_t;
namespace Kernel { namespace Kernel {
#define ENUMERATE_SYSCALLS(S) \ #define ENUMERATE_SYSCALLS(S) \
S(yield) \ S(yield) \
S(open) \ S(open) \
S(close) \ S(close) \
S(read) \ S(read) \
S(lseek) \ S(lseek) \
S(kill) \ S(kill) \
S(getuid) \ S(getuid) \
S(exit) \ S(exit) \
S(geteuid) \ S(geteuid) \
S(getegid) \ S(getegid) \
S(getgid) \ S(getgid) \
S(getpid) \ S(getpid) \
S(getppid) \ S(getppid) \
S(getresuid) \ S(getresuid) \
S(getresgid) \ S(getresgid) \
S(waitid) \ S(waitid) \
S(mmap) \ S(mmap) \
S(munmap) \ S(munmap) \
S(get_dir_entries) \ S(get_dir_entries) \
S(getcwd) \ S(getcwd) \
S(gettimeofday) \ S(gettimeofday) \
S(gethostname) \ S(gethostname) \
S(sethostname) \ S(sethostname) \
S(chdir) \ S(chdir) \
S(uname) \ S(uname) \
S(set_mmap_name) \ S(set_mmap_name) \
S(readlink) \ S(readlink) \
S(write) \ S(write) \
S(ttyname) \ S(ttyname) \
S(stat) \ S(stat) \
S(getsid) \ S(getsid) \
S(setsid) \ S(setsid) \
S(getpgid) \ S(getpgid) \
S(setpgid) \ S(setpgid) \
S(getpgrp) \ S(getpgrp) \
S(fork) \ S(fork) \
S(execve) \ S(execve) \
S(dup2) \ S(dup2) \
S(sigaction) \ S(sigaction) \
S(umask) \ S(umask) \
S(getgroups) \ S(getgroups) \
S(setgroups) \ S(setgroups) \
S(sigreturn) \ S(sigreturn) \
S(sigprocmask) \ S(sigprocmask) \
S(sigpending) \ S(sigpending) \
S(pipe) \ S(pipe) \
S(killpg) \ S(killpg) \
S(seteuid) \ S(seteuid) \
S(setegid) \ S(setegid) \
S(setuid) \ S(setuid) \
S(setgid) \ S(setgid) \
S(setreuid) \ S(setreuid) \
S(setresuid) \ S(setresuid) \
S(setresgid) \ S(setresgid) \
S(alarm) \ S(alarm) \
S(fstat) \ S(fstat) \
S(access) \ S(access) \
S(fcntl) \ S(fcntl) \
S(ioctl) \ S(ioctl) \
S(mkdir) \ S(mkdir) \
S(times) \ S(times) \
S(utime) \ S(utime) \
S(sync) \ S(sync) \
S(ptsname) \ S(ptsname) \
S(select) \ S(select) \
S(unlink) \ S(unlink) \
S(poll) \ S(poll) \
S(rmdir) \ S(rmdir) \
S(chmod) \ S(chmod) \
S(socket) \ S(socket) \
S(bind) \ S(bind) \
S(accept) \ S(accept) \
S(listen) \ S(listen) \
S(connect) \ S(connect) \
S(link) \ S(link) \
S(chown) \ S(chown) \
S(fchmod) \ S(fchmod) \
S(symlink) \ S(symlink) \
S(sendmsg) \ S(sendmsg) \
S(recvmsg) \ S(recvmsg) \
S(getsockopt) \ S(getsockopt) \
S(setsockopt) \ S(setsockopt) \
S(create_thread) \ S(create_thread) \
S(gettid) \ S(gettid) \
S(donate) \ S(donate) \
S(rename) \ S(rename) \
S(ftruncate) \ S(ftruncate) \
S(exit_thread) \ S(exit_thread) \
S(mknod) \ S(mknod) \
S(writev) \ S(writev) \
S(beep) \ S(beep) \
S(getsockname) \ S(getsockname) \
S(getpeername) \ S(getpeername) \
S(socketpair) \ S(socketpair) \
S(sched_setparam) \ S(sched_setparam) \
S(sched_getparam) \ S(sched_getparam) \
S(fchown) \ S(fchown) \
S(halt) \ S(halt) \
S(reboot) \ S(reboot) \
S(mount) \ S(mount) \
S(umount) \ S(umount) \
S(dump_backtrace) \ S(dump_backtrace) \
S(dbgputch) \ S(dbgputch) \
S(dbgputstr) \ S(dbgputstr) \
S(watch_file) \ S(create_inode_watcher) \
S(mprotect) \ S(inode_watcher_add_watch) \
S(realpath) \ S(inode_watcher_remove_watch) \
S(get_process_name) \ S(mprotect) \
S(fchdir) \ S(realpath) \
S(getrandom) \ S(get_process_name) \
S(getkeymap) \ S(fchdir) \
S(setkeymap) \ S(getrandom) \
S(clock_gettime) \ S(getkeymap) \
S(clock_settime) \ S(setkeymap) \
S(clock_nanosleep) \ S(clock_gettime) \
S(join_thread) \ S(clock_settime) \
S(module_load) \ S(clock_nanosleep) \
S(module_unload) \ S(join_thread) \
S(detach_thread) \ S(module_load) \
S(set_thread_name) \ S(module_unload) \
S(get_thread_name) \ S(detach_thread) \
S(madvise) \ S(set_thread_name) \
S(purge) \ S(get_thread_name) \
S(profiling_enable) \ S(madvise) \
S(profiling_disable) \ S(purge) \
S(profiling_free_buffer) \ S(profiling_enable) \
S(futex) \ S(profiling_disable) \
S(chroot) \ S(profiling_free_buffer) \
S(pledge) \ S(futex) \
S(unveil) \ S(chroot) \
S(perf_event) \ S(pledge) \
S(shutdown) \ S(unveil) \
S(get_stack_bounds) \ S(perf_event) \
S(ptrace) \ S(shutdown) \
S(sendfd) \ S(get_stack_bounds) \
S(recvfd) \ S(ptrace) \
S(sysconf) \ S(sendfd) \
S(set_process_name) \ S(recvfd) \
S(disown) \ S(sysconf) \
S(adjtime) \ S(set_process_name) \
S(allocate_tls) \ S(disown) \
S(prctl) \ S(adjtime) \
S(mremap) \ S(allocate_tls) \
S(set_coredump_metadata) \ S(prctl) \
S(anon_create) \ S(mremap) \
S(msyscall) \ S(set_coredump_metadata) \
S(readv) \ S(anon_create) \
S(msyscall) \
S(readv) \
S(emuctl) S(emuctl)
namespace Syscall { namespace Syscall {
@ -442,6 +444,12 @@ struct SC_set_coredump_metadata_params {
StringArgument value; StringArgument value;
}; };
struct SC_inode_watcher_add_watch_params {
int fd;
StringArgument user_path;
u32 event_mask;
};
void initialize(); void initialize();
int sync(); int sync();

View file

@ -198,7 +198,7 @@ set(KERNEL_SOURCES
Syscalls/unveil.cpp Syscalls/unveil.cpp
Syscalls/utime.cpp Syscalls/utime.cpp
Syscalls/waitid.cpp Syscalls/waitid.cpp
Syscalls/watch_file.cpp Syscalls/inode_watcher.cpp
Syscalls/write.cpp Syscalls/write.cpp
TTY/MasterPTY.cpp TTY/MasterPTY.cpp
TTY/PTYMultiplexer.cpp TTY/PTYMultiplexer.cpp

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
* *
* SPDX-License-Identifier: BSD-2-Clause * 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; 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()); 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; return nwritten;
} }
@ -1208,7 +1211,7 @@ KResult Ext2FSInode::add_child(Inode& child, const StringView& name, mode_t mode
return result; return result;
m_lookup_cache.set(name, child.index()); m_lookup_cache.set(name, child.index());
did_add_child(child.identifier()); did_add_child(child.identifier(), name);
return KSuccess; return KSuccess;
} }
@ -1245,7 +1248,7 @@ KResult Ext2FSInode::remove_child(const StringView& name)
if (result.is_error()) if (result.is_error())
return result; return result;
did_remove_child(child_id); did_remove_child(child_id, name);
return KSuccess; return KSuccess;
} }
@ -1674,10 +1677,15 @@ KResult Ext2FSInode::decrement_link_count()
if (fs().is_readonly()) if (fs().is_readonly())
return EROFS; return EROFS;
VERIFY(m_raw_inode.i_links_count); VERIFY(m_raw_inode.i_links_count);
--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) if (ref_count() == 1 && m_raw_inode.i_links_count == 0)
fs().uncache_inode(index()); fs().uncache_inode(index());
set_metadata_dirty(true);
return KSuccess; return KSuccess;
} }

View file

@ -109,6 +109,7 @@ public:
virtual bool is_block_device() const { return false; } virtual bool is_block_device() const { return false; }
virtual bool is_character_device() const { return false; } virtual bool is_character_device() const { return false; }
virtual bool is_socket() 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; } virtual FileBlockCondition& block_condition() { return m_block_condition; }

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -13,6 +14,7 @@
#include <Kernel/FileSystem/FileDescription.h> #include <Kernel/FileSystem/FileDescription.h>
#include <Kernel/FileSystem/FileSystem.h> #include <Kernel/FileSystem/FileSystem.h>
#include <Kernel/FileSystem/InodeFile.h> #include <Kernel/FileSystem/InodeFile.h>
#include <Kernel/FileSystem/InodeWatcher.h>
#include <Kernel/Net/Socket.h> #include <Kernel/Net/Socket.h>
#include <Kernel/Process.h> #include <Kernel/Process.h>
#include <Kernel/TTY/MasterPTY.h> #include <Kernel/TTY/MasterPTY.h>
@ -295,6 +297,25 @@ TTY* FileDescription::tty()
return static_cast<TTY*>(m_file.ptr()); 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 bool FileDescription::is_master_pty() const
{ {
return m_file->is_master_pty(); return m_file->is_master_pty();

View file

@ -77,6 +77,10 @@ public:
const TTY* tty() const; const TTY* tty() const;
TTY* tty(); TTY* tty();
bool is_inode_watcher() const;
const InodeWatcher* inode_watcher() const;
InodeWatcher* inode_watcher();
bool is_master_pty() const; bool is_master_pty() const;
const MasterPTY* master_pty() const; const MasterPTY* master_pty() const;
MasterPTY* master_pty(); MasterPTY* master_pty();

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -109,6 +110,10 @@ Inode::~Inode()
{ {
ScopedSpinLock all_inodes_lock(s_all_inodes_lock); ScopedSpinLock all_inodes_lock(s_all_inodes_lock);
all_with_lock().remove(this); all_with_lock().remove(this);
for (auto& watcher : m_watchers) {
watcher->unregister_by_inode({}, identifier());
}
} }
void Inode::will_be_destroyed() 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. // 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. // We don't always end up on this particular code path, for instance when writing to an ext2fs file.
for (auto& watcher : m_watchers) { 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); Locker locker(m_lock);
for (auto& watcher : m_watchers) { for (auto& watcher : m_watchers) {
watcher->notify_child_added({}, child_id); watcher->notify_inode_event({}, identifier(), InodeWatcherEvent::Type::ChildCreated, name);
} }
} }
void Inode::did_remove_child(const InodeIdentifier& child_id) 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); Locker locker(m_lock);
for (auto& watcher : m_watchers) { for (auto& watcher : m_watchers) {
watcher->notify_child_removed({}, child_id); watcher->notify_inode_event({}, identifier(), InodeWatcherEvent::Type::ContentModified);
}
}
void Inode::did_delete_self()
{
Locker locker(m_lock);
for (auto& watcher : m_watchers) {
watcher->notify_inode_event({}, identifier(), InodeWatcherEvent::Type::Deleted);
} }
} }

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -111,8 +112,10 @@ protected:
void set_metadata_dirty(bool); void set_metadata_dirty(bool);
KResult prepare_to_write_data(); KResult prepare_to_write_data();
void did_add_child(const InodeIdentifier&); void did_add_child(InodeIdentifier const& child_id, String const& name);
void did_remove_child(const InodeIdentifier&); void did_remove_child(InodeIdentifier const& child_id, String const& name);
void did_modify_contents();
void did_delete_self();
mutable Lock m_lock { "Inode" }; mutable Lock m_lock { "Inode" };

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -7,53 +8,59 @@
#include <AK/Memory.h> #include <AK/Memory.h>
#include <Kernel/FileSystem/Inode.h> #include <Kernel/FileSystem/Inode.h>
#include <Kernel/FileSystem/InodeWatcher.h> #include <Kernel/FileSystem/InodeWatcher.h>
#include <Kernel/Process.h>
namespace Kernel { namespace Kernel {
NonnullRefPtr<InodeWatcher> InodeWatcher::create(Inode& inode) NonnullRefPtr<InodeWatcher> InodeWatcher::create()
{ {
return adopt_ref(*new InodeWatcher(inode)); return adopt_ref(*new InodeWatcher);
}
InodeWatcher::InodeWatcher(Inode& inode)
: m_inode(inode)
{
inode.register_watcher({}, *this);
} }
InodeWatcher::~InodeWatcher() InodeWatcher::~InodeWatcher()
{ {
if (auto inode = m_inode.strong_ref()) (void)close();
inode->unregister_watcher({}, *this);
} }
bool InodeWatcher::can_read(const FileDescription&, size_t) const bool InodeWatcher::can_read(const FileDescription&, size_t) const
{ {
return !m_queue.is_empty() || !m_inode; Locker locker(m_lock);
} return !m_queue.is_empty();
bool InodeWatcher::can_write(const FileDescription&, size_t) const
{
return true;
} }
KResultOr<size_t> InodeWatcher::read(FileDescription&, u64, UserOrKernelBuffer& buffer, size_t buffer_size) KResultOr<size_t> InodeWatcher::read(FileDescription&, u64, UserOrKernelBuffer& buffer, size_t buffer_size)
{ {
Locker locker(m_lock); Locker locker(m_lock);
VERIFY(!m_queue.is_empty() || !m_inode); if (m_queue.is_empty())
// can_read will catch the blocking case.
if (!m_inode) return EAGAIN;
return 0;
auto event = m_queue.dequeue(); auto event = m_queue.dequeue();
if (buffer_size < sizeof(InodeWatcherEvent)) size_t name_length = event.path.length() + 1;
return buffer_size; size_t bytes_to_write = sizeof(InodeWatcherEvent);
if (!event.path.is_null())
bytes_to_write += name_length;
size_t bytes_to_write = min(buffer_size, sizeof(event)); if (buffer_size < bytes_to_write)
return EINVAL;
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; return (ssize_t)data_bytes;
}); });
if (nwritten < 0) if (nwritten < 0)
@ -62,37 +69,94 @@ KResultOr<size_t> InodeWatcher::read(FileDescription&, u64, UserOrKernelBuffer&
return bytes_to_write; 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 String InodeWatcher::absolute_path(const FileDescription&) const
{ {
if (auto inode = m_inode.strong_ref()) return String::formatted("InodeWatcher:({})", m_wd_to_watches.size());
return String::formatted("InodeWatcher:{}", inode->identifier().to_string());
return "InodeWatcher:(gone)";
} }
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); 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(); 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); 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); 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);
} }
} }

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -7,38 +8,76 @@
#pragma once #pragma once
#include <AK/Badge.h> #include <AK/Badge.h>
#include <AK/Checked.h>
#include <AK/CircularQueue.h> #include <AK/CircularQueue.h>
#include <AK/WeakPtr.h> #include <AK/HashMap.h>
#include <AK/NonnullOwnPtr.h>
#include <Kernel/API/InodeWatcherEvent.h> #include <Kernel/API/InodeWatcherEvent.h>
#include <Kernel/FileSystem/File.h> #include <Kernel/FileSystem/File.h>
#include <Kernel/Lock.h>
namespace Kernel { namespace Kernel {
class Inode; 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 { class InodeWatcher final : public File {
public: public:
static NonnullRefPtr<InodeWatcher> create(Inode&); static NonnullRefPtr<InodeWatcher> create();
virtual ~InodeWatcher() override; virtual ~InodeWatcher() override;
virtual bool can_read(const FileDescription&, size_t) const 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> 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 String absolute_path(const FileDescription&) const override;
virtual const char* class_name() const override { return "InodeWatcher"; }; virtual const char* class_name() const override { return "InodeWatcher"; };
virtual bool is_inode_watcher() const override { return true; }
void notify_inode_event(Badge<Inode>, InodeWatcherEvent::Type); void notify_inode_event(Badge<Inode>, InodeIdentifier, InodeWatcherEvent::Type, String const& name = {});
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: private:
explicit InodeWatcher(Inode&); explicit InodeWatcher() { }
Lock m_lock; mutable Lock m_lock;
WeakPtr<Inode> m_inode;
CircularQueue<InodeWatcherEvent, 32> m_queue; struct Event {
int wd { 0 };
InodeWatcherEvent::Type type { InodeWatcherEvent::Type::Invalid };
String path;
};
CircularQueue<Event, 32> m_queue;
Checked<int> m_wd_counter { 1 };
// 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;
}; };
} }

View file

@ -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? if (!buffer.read(m_content->data() + offset, size)) // TODO: partial reads?
return EFAULT; return EFAULT;
did_modify_contents();
return size; return size;
} }
@ -284,7 +286,7 @@ KResult TmpFSInode::add_child(Inode& child, const StringView& name, mode_t)
return ENAMETOOLONG; return ENAMETOOLONG;
m_children.set(name, { name, static_cast<TmpFSInode&>(child) }); m_children.set(name, { name, static_cast<TmpFSInode&>(child) });
did_add_child(child.identifier()); did_add_child(child.identifier(), name);
return KSuccess; return KSuccess;
} }
@ -300,8 +302,9 @@ KResult TmpFSInode::remove_child(const StringView& name)
if (it == m_children.end()) if (it == m_children.end())
return ENOENT; return ENOENT;
auto child_id = it->value.inode->identifier(); auto child_id = it->value.inode->identifier();
it->value.inode->did_delete_self();
m_children.remove(it); m_children.remove(it);
did_remove_child(child_id); did_remove_child(child_id, name);
return KSuccess; return KSuccess;
} }

View file

@ -251,7 +251,9 @@ public:
KResultOr<int> sys$beep(); KResultOr<int> sys$beep();
KResultOr<int> sys$get_process_name(Userspace<char*> buffer, size_t buffer_size); 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$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$dbgputch(u8);
KResultOr<int> sys$dbgputstr(Userspace<const u8*>, int length); KResultOr<int> sys$dbgputstr(Userspace<const u8*>, int length);
KResultOr<int> sys$dump_backtrace(); KResultOr<int> sys$dump_backtrace();

View file

@ -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;
}
}

View file

@ -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;
}
}