mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 14:02:46 +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/StringBuilder.h> | ||||
| #include <Kernel/FileSystem/Inode.h> | ||||
| #include <Kernel/FileSystem/InodeWatcher.h> | ||||
| #include <Kernel/Net/LocalSocket.h> | ||||
| #include <Kernel/VM/VMObject.h> | ||||
| 
 | ||||
|  | @ -131,3 +132,33 @@ bool Inode::unbind_socket() | |||
|     m_socket = nullptr; | ||||
|     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> | ||||
| 
 | ||||
| class FileDescription; | ||||
| class InodeWatcher; | ||||
| class LocalSocket; | ||||
| class VMObject; | ||||
| 
 | ||||
| class Inode : public RefCounted<Inode> { | ||||
| class Inode : public RefCounted<Inode>, public Weakable<Inode> { | ||||
|     friend class VFS; | ||||
|     friend class FS; | ||||
| 
 | ||||
|  | @ -73,9 +74,12 @@ public: | |||
| 
 | ||||
|     static void sync(); | ||||
| 
 | ||||
|     void register_watcher(Badge<InodeWatcher>, InodeWatcher&); | ||||
|     void unregister_watcher(Badge<InodeWatcher>, 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<VMObject> m_vmo; | ||||
|     RefPtr<LocalSocket> m_socket; | ||||
|     HashTable<InodeWatcher*> m_watchers; | ||||
|     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/DiskPartition.o \
 | ||||
|     Devices/MBRPartitionTable.o \
 | ||||
|     FileSystem/InodeWatcher.o \
 | ||||
|     FileSystem/FileSystem.o \
 | ||||
|     FileSystem/DiskBackedFileSystem.o \
 | ||||
|     FileSystem/Ext2FileSystem.o \
 | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ | |||
| #include <Kernel/FileSystem/Custody.h> | ||||
| #include <Kernel/FileSystem/FIFO.h> | ||||
| #include <Kernel/FileSystem/FileDescription.h> | ||||
| #include <Kernel/FileSystem/InodeWatcher.h> | ||||
| #include <Kernel/FileSystem/SharedMemory.h> | ||||
| #include <Kernel/FileSystem/VirtualFileSystem.h> | ||||
| #include <Kernel/IO.h> | ||||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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(); | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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 { | ||||
| 
 | ||||
|  |  | |||
|  | @ -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); | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
							
								
								
									
										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
	
	 Andreas Kling
						Andreas Kling