mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 09:28:11 +00:00
Kernel: Add a mechanism for listening for changes to an inode.
The syscall is quite simple: int watch_file(const char* path, int path_length); It returns a file descriptor referring to a "InodeWatcher" object in the kernel. It becomes readable whenever something changes about the inode. Currently this is implemented by hooking the "metadata dirty bit" in Inode which isn't perfect, but it's a start. :^)
This commit is contained in:
parent
a9adf4c95b
commit
c8e2bb5605
12 changed files with 200 additions and 3 deletions
|
@ -1,6 +1,7 @@
|
||||||
#include <AK/NonnullRefPtrVector.h>
|
#include <AK/NonnullRefPtrVector.h>
|
||||||
#include <AK/StringBuilder.h>
|
#include <AK/StringBuilder.h>
|
||||||
#include <Kernel/FileSystem/Inode.h>
|
#include <Kernel/FileSystem/Inode.h>
|
||||||
|
#include <Kernel/FileSystem/InodeWatcher.h>
|
||||||
#include <Kernel/Net/LocalSocket.h>
|
#include <Kernel/Net/LocalSocket.h>
|
||||||
#include <Kernel/VM/VMObject.h>
|
#include <Kernel/VM/VMObject.h>
|
||||||
|
|
||||||
|
@ -131,3 +132,33 @@ bool Inode::unbind_socket()
|
||||||
m_socket = nullptr;
|
m_socket = nullptr;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Inode::register_watcher(Badge<InodeWatcher>, InodeWatcher& watcher)
|
||||||
|
{
|
||||||
|
LOCKER(m_lock);
|
||||||
|
ASSERT(!m_watchers.contains(&watcher));
|
||||||
|
m_watchers.set(&watcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Inode::unregister_watcher(Badge<InodeWatcher>, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -11,10 +11,11 @@
|
||||||
#include <Kernel/Lock.h>
|
#include <Kernel/Lock.h>
|
||||||
|
|
||||||
class FileDescription;
|
class FileDescription;
|
||||||
|
class InodeWatcher;
|
||||||
class LocalSocket;
|
class LocalSocket;
|
||||||
class VMObject;
|
class VMObject;
|
||||||
|
|
||||||
class Inode : public RefCounted<Inode> {
|
class Inode : public RefCounted<Inode>, public Weakable<Inode> {
|
||||||
friend class VFS;
|
friend class VFS;
|
||||||
friend class FS;
|
friend class FS;
|
||||||
|
|
||||||
|
@ -73,9 +74,12 @@ public:
|
||||||
|
|
||||||
static void sync();
|
static void sync();
|
||||||
|
|
||||||
|
void register_watcher(Badge<InodeWatcher>, InodeWatcher&);
|
||||||
|
void unregister_watcher(Badge<InodeWatcher>, InodeWatcher&);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Inode(FS& fs, unsigned index);
|
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_contents_changed(off_t, ssize_t, const u8*);
|
||||||
void inode_size_changed(size_t old_size, size_t new_size);
|
void inode_size_changed(size_t old_size, size_t new_size);
|
||||||
|
|
||||||
|
@ -86,5 +90,6 @@ private:
|
||||||
unsigned m_index { 0 };
|
unsigned m_index { 0 };
|
||||||
WeakPtr<VMObject> m_vmo;
|
WeakPtr<VMObject> m_vmo;
|
||||||
RefPtr<LocalSocket> m_socket;
|
RefPtr<LocalSocket> m_socket;
|
||||||
|
HashTable<InodeWatcher*> m_watchers;
|
||||||
bool m_metadata_dirty { false };
|
bool m_metadata_dirty { false };
|
||||||
};
|
};
|
||||||
|
|
60
Kernel/FileSystem/InodeWatcher.cpp
Normal file
60
Kernel/FileSystem/InodeWatcher.cpp
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
#include <Kernel/FileSystem/Inode.h>
|
||||||
|
#include <Kernel/FileSystem/InodeWatcher.h>
|
||||||
|
|
||||||
|
NonnullRefPtr<InodeWatcher> 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<Inode> 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<Inode>, Event::Type event_type)
|
||||||
|
{
|
||||||
|
m_queue.enqueue({ event_type });
|
||||||
|
}
|
38
Kernel/FileSystem/InodeWatcher.h
Normal file
38
Kernel/FileSystem/InodeWatcher.h
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/Badge.h>
|
||||||
|
#include <AK/CircularQueue.h>
|
||||||
|
#include <AK/WeakPtr.h>
|
||||||
|
#include <Kernel/FileSystem/File.h>
|
||||||
|
|
||||||
|
class Inode;
|
||||||
|
|
||||||
|
class InodeWatcher final : public File {
|
||||||
|
public:
|
||||||
|
static NonnullRefPtr<InodeWatcher> 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<Inode>, Event::Type);
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit InodeWatcher(Inode&);
|
||||||
|
|
||||||
|
WeakPtr<Inode> m_inode;
|
||||||
|
CircularQueue<Event, 32> m_queue;
|
||||||
|
};
|
|
@ -72,6 +72,7 @@ VFS_OBJS = \
|
||||||
Devices/DebugLogDevice.o \
|
Devices/DebugLogDevice.o \
|
||||||
Devices/DiskPartition.o \
|
Devices/DiskPartition.o \
|
||||||
Devices/MBRPartitionTable.o \
|
Devices/MBRPartitionTable.o \
|
||||||
|
FileSystem/InodeWatcher.o \
|
||||||
FileSystem/FileSystem.o \
|
FileSystem/FileSystem.o \
|
||||||
FileSystem/DiskBackedFileSystem.o \
|
FileSystem/DiskBackedFileSystem.o \
|
||||||
FileSystem/Ext2FileSystem.o \
|
FileSystem/Ext2FileSystem.o \
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include <Kernel/FileSystem/Custody.h>
|
#include <Kernel/FileSystem/Custody.h>
|
||||||
#include <Kernel/FileSystem/FIFO.h>
|
#include <Kernel/FileSystem/FIFO.h>
|
||||||
#include <Kernel/FileSystem/FileDescription.h>
|
#include <Kernel/FileSystem/FileDescription.h>
|
||||||
|
#include <Kernel/FileSystem/InodeWatcher.h>
|
||||||
#include <Kernel/FileSystem/SharedMemory.h>
|
#include <Kernel/FileSystem/SharedMemory.h>
|
||||||
#include <Kernel/FileSystem/VirtualFileSystem.h>
|
#include <Kernel/FileSystem/VirtualFileSystem.h>
|
||||||
#include <Kernel/IO.h>
|
#include <Kernel/IO.h>
|
||||||
|
@ -2638,6 +2639,26 @@ int Process::sys$ftruncate(int fd, off_t length)
|
||||||
return description->truncate(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)
|
int Process::sys$systrace(pid_t pid)
|
||||||
{
|
{
|
||||||
InterruptDisabler disabler;
|
InterruptDisabler disabler;
|
||||||
|
|
|
@ -104,6 +104,7 @@ public:
|
||||||
void die();
|
void die();
|
||||||
void finalize();
|
void finalize();
|
||||||
|
|
||||||
|
int sys$watch_file(const char* path, int path_length);
|
||||||
int sys$dbgputch(u8);
|
int sys$dbgputch(u8);
|
||||||
int sys$dbgputstr(const u8*, int length);
|
int sys$dbgputstr(const u8*, int length);
|
||||||
int sys$dump_backtrace();
|
int sys$dump_backtrace();
|
||||||
|
|
|
@ -297,6 +297,8 @@ static u32 handle(RegisterDump& regs, u32 function, u32 arg1, u32 arg2, u32 arg3
|
||||||
}
|
}
|
||||||
case Syscall::SC_dump_backtrace:
|
case Syscall::SC_dump_backtrace:
|
||||||
return current->process().sys$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:
|
default:
|
||||||
kprintf("<%u> int0x82: Unknown function %u requested {%x, %x, %x}\n", current->process().pid(), function, arg1, arg2, arg3);
|
kprintf("<%u> int0x82: Unknown function %u requested {%x, %x, %x}\n", current->process().pid(), function, arg1, arg2, arg3);
|
||||||
return -ENOSYS;
|
return -ENOSYS;
|
||||||
|
|
|
@ -118,7 +118,8 @@ struct timeval;
|
||||||
__ENUMERATE_SYSCALL(reboot) \
|
__ENUMERATE_SYSCALL(reboot) \
|
||||||
__ENUMERATE_SYSCALL(dump_backtrace) \
|
__ENUMERATE_SYSCALL(dump_backtrace) \
|
||||||
__ENUMERATE_SYSCALL(dbgputch) \
|
__ENUMERATE_SYSCALL(dbgputch) \
|
||||||
__ENUMERATE_SYSCALL(dbgputstr)
|
__ENUMERATE_SYSCALL(dbgputstr) \
|
||||||
|
__ENUMERATE_SYSCALL(watch_file)
|
||||||
|
|
||||||
namespace Syscall {
|
namespace Syscall {
|
||||||
|
|
||||||
|
|
|
@ -14,4 +14,11 @@ int fcntl(int fd, int cmd, ...)
|
||||||
int rc = syscall(SC_fcntl, fd, cmd, extra_arg);
|
int rc = syscall(SC_fcntl, fd, cmd, extra_arg);
|
||||||
__RETURN_WITH_ERRNO(rc, rc, -1);
|
__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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,7 @@ __BEGIN_DECLS
|
||||||
#define S_IRWXO (S_IRWXG >> 3)
|
#define S_IRWXO (S_IRWXG >> 3)
|
||||||
|
|
||||||
int fcntl(int fd, int cmd, ...);
|
int fcntl(int fd, int cmd, ...);
|
||||||
|
int watch_file(const char* path, int path_length);
|
||||||
|
|
||||||
#define F_RDLCK 0
|
#define F_RDLCK 0
|
||||||
#define F_WRLCK 1
|
#define F_WRLCK 1
|
||||||
|
|
29
Userland/mon.cpp
Normal file
29
Userland/mon.cpp
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#include <AK/LogStream.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue