diff --git a/Kernel/FileSystem/Inode.cpp b/Kernel/FileSystem/Inode.cpp index 9c953ec78a0..ae9a77a7fd7 100644 --- a/Kernel/FileSystem/Inode.cpp +++ b/Kernel/FileSystem/Inode.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -131,3 +132,33 @@ bool Inode::unbind_socket() m_socket = nullptr; return true; } + +void Inode::register_watcher(Badge, InodeWatcher& watcher) +{ + LOCKER(m_lock); + ASSERT(!m_watchers.contains(&watcher)); + m_watchers.set(&watcher); +} + +void Inode::unregister_watcher(Badge, InodeWatcher& watcher) +{ + LOCKER(m_lock); + ASSERT(m_watchers.contains(&watcher)); + m_watchers.remove(&watcher); +} + +void Inode::set_metadata_dirty(bool metadata_dirty) +{ + if (m_metadata_dirty == metadata_dirty) + return; + + m_metadata_dirty = metadata_dirty; + if (m_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. + LOCKER(m_lock); + for (auto& watcher : m_watchers) { + watcher->notify_inode_event({}, InodeWatcher::Event::Type::Modified); + } + } +} diff --git a/Kernel/FileSystem/Inode.h b/Kernel/FileSystem/Inode.h index 1caa8c0dbea..797ec80c1f4 100644 --- a/Kernel/FileSystem/Inode.h +++ b/Kernel/FileSystem/Inode.h @@ -11,10 +11,11 @@ #include class FileDescription; +class InodeWatcher; class LocalSocket; class VMObject; -class Inode : public RefCounted { +class Inode : public RefCounted, public Weakable { friend class VFS; friend class FS; @@ -73,9 +74,12 @@ public: static void sync(); + void register_watcher(Badge, InodeWatcher&); + void unregister_watcher(Badge, InodeWatcher&); + protected: Inode(FS& fs, unsigned index); - void set_metadata_dirty(bool b) { m_metadata_dirty = b; } + void set_metadata_dirty(bool); void inode_contents_changed(off_t, ssize_t, const u8*); void inode_size_changed(size_t old_size, size_t new_size); @@ -86,5 +90,6 @@ private: unsigned m_index { 0 }; WeakPtr m_vmo; RefPtr m_socket; + HashTable m_watchers; bool m_metadata_dirty { false }; }; diff --git a/Kernel/FileSystem/InodeWatcher.cpp b/Kernel/FileSystem/InodeWatcher.cpp new file mode 100644 index 00000000000..9a5e3840959 --- /dev/null +++ b/Kernel/FileSystem/InodeWatcher.cpp @@ -0,0 +1,60 @@ +#include +#include + +NonnullRefPtr InodeWatcher::create(Inode& inode) +{ + return adopt(*new InodeWatcher(inode)); +} + +InodeWatcher::InodeWatcher(Inode& inode) + : m_inode(inode.make_weak_ptr()) +{ + inode.register_watcher({}, *this); +} + +InodeWatcher::~InodeWatcher() +{ + if (RefPtr safe_inode = m_inode.ptr()) + safe_inode->unregister_watcher({}, *this); +} + +bool InodeWatcher::can_read(FileDescription&) const +{ + return !m_queue.is_empty() || !m_inode; +} + +bool InodeWatcher::can_write(FileDescription&) const +{ + return true; +} + +ssize_t InodeWatcher::read(FileDescription&, u8* buffer, ssize_t buffer_size) +{ + ASSERT(!m_queue.is_empty() || !m_inode); + + if (!m_inode) + return 0; + + // FIXME: What should we do if the output buffer is too small? + ASSERT(buffer_size >= (int)sizeof(Event)); + auto event = m_queue.dequeue(); + memcpy(buffer, &event, sizeof(event)); + return sizeof(event); +} + +ssize_t InodeWatcher::write(FileDescription&, const u8*, ssize_t) +{ + return -EIO; +} + +String InodeWatcher::absolute_path(const FileDescription&) const +{ + if (!m_inode) + return "InodeWatcher:(gone)"; + return String::format("InodeWatcher:%s", m_inode->identifier().to_string().characters()); +} + +void InodeWatcher::notify_inode_event(Badge, Event::Type event_type) +{ + m_queue.enqueue({ event_type }); +} diff --git a/Kernel/FileSystem/InodeWatcher.h b/Kernel/FileSystem/InodeWatcher.h new file mode 100644 index 00000000000..e4091429f24 --- /dev/null +++ b/Kernel/FileSystem/InodeWatcher.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include +#include + +class Inode; + +class InodeWatcher final : public File { +public: + static NonnullRefPtr create(Inode&); + virtual ~InodeWatcher() override; + + struct Event { + enum class Type { + Invalid = 0, + Modified, + }; + + Type type { Type::Invalid }; + }; + + virtual bool can_read(FileDescription&) const override; + virtual bool can_write(FileDescription&) const override; + virtual ssize_t read(FileDescription&, u8*, ssize_t) override; + virtual ssize_t write(FileDescription&, const u8*, ssize_t) override; + virtual String absolute_path(const FileDescription&) const override; + virtual const char* class_name() const override { return "InodeWatcher"; }; + + void notify_inode_event(Badge, Event::Type); + +private: + explicit InodeWatcher(Inode&); + + WeakPtr m_inode; + CircularQueue m_queue; +}; diff --git a/Kernel/Makefile b/Kernel/Makefile index a508a8cc24f..62f8b1276b9 100644 --- a/Kernel/Makefile +++ b/Kernel/Makefile @@ -72,6 +72,7 @@ VFS_OBJS = \ Devices/DebugLogDevice.o \ Devices/DiskPartition.o \ Devices/MBRPartitionTable.o \ + FileSystem/InodeWatcher.o \ FileSystem/FileSystem.o \ FileSystem/DiskBackedFileSystem.o \ FileSystem/Ext2FileSystem.o \ diff --git a/Kernel/Process.cpp b/Kernel/Process.cpp index e32d6b50cdc..b0720f928ec 100644 --- a/Kernel/Process.cpp +++ b/Kernel/Process.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -2638,6 +2639,26 @@ int Process::sys$ftruncate(int fd, off_t length) return description->truncate(length); } +int Process::sys$watch_file(const char* path, int path_length) +{ + if (!validate_read(path, path_length)) + return -EFAULT; + + auto custody_or_error = VFS::the().resolve_path({ path, path_length }, current_directory()); + if (custody_or_error.is_error()) + return custody_or_error.error(); + + auto& custody = custody_or_error.value(); + auto& inode = custody->inode(); + + int fd = alloc_fd(); + if (fd < 0) + return fd; + + m_fds[fd].set(FileDescription::create(*InodeWatcher::create(inode))); + return fd; +} + int Process::sys$systrace(pid_t pid) { InterruptDisabler disabler; diff --git a/Kernel/Process.h b/Kernel/Process.h index f9dd19c3c98..4e9a09d9730 100644 --- a/Kernel/Process.h +++ b/Kernel/Process.h @@ -104,6 +104,7 @@ public: void die(); void finalize(); + int sys$watch_file(const char* path, int path_length); int sys$dbgputch(u8); int sys$dbgputstr(const u8*, int length); int sys$dump_backtrace(); diff --git a/Kernel/Syscall.cpp b/Kernel/Syscall.cpp index 9334c326812..43db79393eb 100644 --- a/Kernel/Syscall.cpp +++ b/Kernel/Syscall.cpp @@ -297,6 +297,8 @@ static u32 handle(RegisterDump& regs, u32 function, u32 arg1, u32 arg2, u32 arg3 } case Syscall::SC_dump_backtrace: return current->process().sys$dump_backtrace(); + case Syscall::SC_watch_file: + return current->process().sys$watch_file((const char*)arg1, (int)arg2); default: kprintf("<%u> int0x82: Unknown function %u requested {%x, %x, %x}\n", current->process().pid(), function, arg1, arg2, arg3); return -ENOSYS; diff --git a/Kernel/Syscall.h b/Kernel/Syscall.h index 4a365530d4d..b45343964fa 100644 --- a/Kernel/Syscall.h +++ b/Kernel/Syscall.h @@ -118,7 +118,8 @@ struct timeval; __ENUMERATE_SYSCALL(reboot) \ __ENUMERATE_SYSCALL(dump_backtrace) \ __ENUMERATE_SYSCALL(dbgputch) \ - __ENUMERATE_SYSCALL(dbgputstr) + __ENUMERATE_SYSCALL(dbgputstr) \ + __ENUMERATE_SYSCALL(watch_file) namespace Syscall { diff --git a/Libraries/LibC/fcntl.cpp b/Libraries/LibC/fcntl.cpp index 582c3397109..4ee34c6dac6 100644 --- a/Libraries/LibC/fcntl.cpp +++ b/Libraries/LibC/fcntl.cpp @@ -14,4 +14,11 @@ int fcntl(int fd, int cmd, ...) int rc = syscall(SC_fcntl, fd, cmd, extra_arg); __RETURN_WITH_ERRNO(rc, rc, -1); } + +int watch_file(const char* path, int path_length) +{ + int rc = syscall(SC_watch_file, path, path_length); + __RETURN_WITH_ERRNO(rc, rc, -1); +} + } diff --git a/Libraries/LibC/fcntl.h b/Libraries/LibC/fcntl.h index 78aac1abdff..7c886f9f7e8 100644 --- a/Libraries/LibC/fcntl.h +++ b/Libraries/LibC/fcntl.h @@ -54,6 +54,7 @@ __BEGIN_DECLS #define S_IRWXO (S_IRWXG >> 3) int fcntl(int fd, int cmd, ...); +int watch_file(const char* path, int path_length); #define F_RDLCK 0 #define F_WRLCK 1 diff --git a/Userland/mon.cpp b/Userland/mon.cpp new file mode 100644 index 00000000000..c8980ca3885 --- /dev/null +++ b/Userland/mon.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + const char* path = argc > 1 ? argv[1] : "."; + int watch_fd = watch_file(path, strlen(path)); + if (watch_fd < 0) { + perror("Unable to watch"); + return 1; + } + for (;;) { + char buffer[256]; + int rc = read(watch_fd, buffer, sizeof(buffer)); + if (rc < 0) { + perror("read"); + return 1; + } + if (rc == 0) { + printf("End-of-file.\n"); + return 0; + } + printf("Something changed about '%s'\n", path); + } + return 0; +}