diff --git a/hyclone_server/fs/hostfs.cpp b/hyclone_server/fs/hostfs.cpp index 2636a08..26de03e 100644 --- a/hyclone_server/fs/hostfs.cpp +++ b/hyclone_server/fs/hostfs.cpp @@ -208,4 +208,26 @@ status_t HostfsDevice::TransformDirent(const std::filesystem::path& path, haiku_ } return dirent.d_reclen; +} + +status_t HostfsDevice::AddMonitor(haiku_ino_t node) +{ + auto& vfsService = System::GetInstance().GetVfsService(); + std::string pathStr; + + if (!vfsService.GetEntryRef(EntryRef(_info.dev, node), pathStr)) + { + return B_ENTRY_NOT_FOUND; + } + + std::filesystem::path path(pathStr); + auto relativePath = path.lexically_relative(_root); + auto hostPath = _hostRoot / relativePath; + + return server_add_native_monitor(hostPath, _info.dev, node); +} + +status_t HostfsDevice::RemoveMonitor(haiku_ino_t node) +{ + return server_remove_native_monitor(_info.dev, node); } \ No newline at end of file diff --git a/hyclone_server/fs/hostfs.h b/hyclone_server/fs/hostfs.h index 446861e..cb4c35d 100644 --- a/hyclone_server/fs/hostfs.h +++ b/hyclone_server/fs/hostfs.h @@ -24,6 +24,9 @@ class HostfsDevice : public VfsDevice, private std::enable_shared_from_thismonitor + && listener->monitor->node != (haiku_ino_t)-1) + { + vfsService.RemoveMonitor(listener->monitor->device, listener->monitor->node); + } + } +} + IoContext& IoContext::operator=(const IoContext& other) { if (this == &other) @@ -27,11 +43,25 @@ size_t IoContext::AddMonitor(const std::shared_ptr& listener) { _monitors.push_back(listener); listener->context_link = --_monitors.end(); + if (listener && listener->monitor + && listener->monitor->node != (haiku_ino_t)-1) + { + auto& vfsService = System::GetInstance().GetVfsService(); + auto lock = vfsService.Lock(); + vfsService.AddMonitor(listener->monitor->device, listener->monitor->node); + } return _monitors.size(); } size_t IoContext::RemoveMonitor(const std::shared_ptr& listener) { _monitors.erase(listener->context_link); + if (listener && listener->monitor + && listener->monitor->node != (haiku_ino_t)-1) + { + auto& vfsService = System::GetInstance().GetVfsService(); + auto lock = vfsService.Lock(); + vfsService.RemoveMonitor(listener->monitor->device, listener->monitor->node); + } return _monitors.size(); } \ No newline at end of file diff --git a/hyclone_server/io_context.h b/hyclone_server/io_context.h index b239021..bbdcee1 100644 --- a/hyclone_server/io_context.h +++ b/hyclone_server/io_context.h @@ -15,9 +15,9 @@ class IoContext std::list> _monitors; public: IoContext() = default; - ~IoContext() = default; IoContext(const IoContext& other); IoContext& operator=(const IoContext& other); + ~IoContext(); unsigned int MaxMonitors() const { return _maxMonitors; } unsigned int NumMonitors() const; diff --git a/hyclone_server/server_native.h b/hyclone_server/server_native.h index 402be82..39cc0c8 100644 --- a/hyclone_server/server_native.h +++ b/hyclone_server/server_native.h @@ -5,6 +5,7 @@ #include #include +#include "BeDefs.h" #include "haiku_team.h" #include "haiku_thread.h" @@ -28,6 +29,9 @@ void server_fill_fs_info(const std::filesystem::path& path, haiku_fs_info* info) status_t server_read_stat(const std::filesystem::path& path, haiku_stat& st); status_t server_write_stat(const std::filesystem::path& path, const haiku_stat& stat, int statMask); +status_t server_add_native_monitor(const std::filesystem::path& hostPath, haiku_dev_t device, haiku_ino_t node); +status_t server_remove_native_monitor(haiku_dev_t device, haiku_ino_t node); + void server_exit_thread(); #endif // __SERVER_NATIVE_H__ \ No newline at end of file diff --git a/hyclone_server/server_nodemonitor.cpp b/hyclone_server/server_nodemonitor.cpp index 0ea4d02..d6bedb0 100644 --- a/hyclone_server/server_nodemonitor.cpp +++ b/hyclone_server/server_nodemonitor.cpp @@ -6,16 +6,6 @@ const std::string NodeMonitorService::kNodeMonitorServiceName = "node monitor"; -typedef std::list> MonitorListenerList; - -struct node_monitor -{ - // node_monitor* hash_link; - haiku_dev_t device; - haiku_ino_t node; - MonitorListenerList listeners; -}; - struct interested_monitor_listener_list { MonitorListenerList::iterator iterator; @@ -374,6 +364,129 @@ status_t NodeMonitorService::_SendNotificationMessage(KMessage& message, return B_OK; } +/*! \brief Notifies all interested listeners that an entry has been created + or removed. + \param opcode \c B_ENTRY_CREATED or \c B_ENTRY_REMOVED. + \param device The ID of the mounted FS, the entry lives/lived in. + \param directory The entry's parent directory ID. + \param name The entry's name. + \param node The ID of the node the entry refers/referred to. + \return + - \c B_OK, if everything went fine, + - another error code otherwise. +*/ +status_t NodeMonitorService::NotifyEntryCreatedOrRemoved( + int32 opcode, haiku_dev_t device, + haiku_ino_t directory, const char* name, haiku_ino_t node) +{ + if (!name) + return B_BAD_VALUE; + + std::unique_lock locker(_recursiveLock); + + // get the lists of all interested listeners + interested_monitor_listener_list interestedListeners[3]; + int32 interestedListenerCount = 0; + // ... for the volume + _GetInterestedVolumeListeners(device, B_WATCH_NAME, + interestedListeners, interestedListenerCount); + // ... for the node + if (opcode != B_ENTRY_CREATED) + { + _GetInterestedMonitorListeners(device, node, B_WATCH_NAME, + interestedListeners, interestedListenerCount); + } + // ... for the directory + _GetInterestedMonitorListeners(device, directory, B_WATCH_DIRECTORY, + interestedListeners, interestedListenerCount); + + if (interestedListenerCount == 0) + return B_OK; + + // there are interested listeners: construct the message and send it + char messageBuffer[1024]; + KMessage message; + message.SetTo(messageBuffer, sizeof(messageBuffer), B_NODE_MONITOR); + message.AddInt32("opcode", opcode); + message.AddInt32("device", device); + message.AddInt64("directory", directory); + message.AddInt64("node", node); + message.AddString("name", name); // for "removed" Haiku only + + return _SendNotificationMessage(message, interestedListeners, + interestedListenerCount); +} + +inline status_t +NodeMonitorService::NotifyEntryMoved( + haiku_dev_t device, haiku_ino_t fromDirectory, + const char* fromName, haiku_ino_t toDirectory, const char* toName, + haiku_ino_t node) +{ + if (!fromName || !toName) + return B_BAD_VALUE; + + // If node is a mount point, we need to resolve it to the mounted + // volume's root node. + haiku_dev_t nodeDevice = device; + { + auto& vfsService = System::GetInstance().GetVfsService(); + auto lock = vfsService.Lock(); + std::string path; + if (vfsService.GetEntryRef(EntryRef(device, node), path)) + { + auto nodeVfsDevice = vfsService.GetDevice(path).lock(); + + if (nodeVfsDevice) + { + nodeDevice = nodeVfsDevice->GetId(); + node = nodeVfsDevice->GetInfo().root; + } + } + } + // vfs_resolve_vnode_to_covering_vnode(device, node, &nodeDevice, &node); + + std::unique_lock locker(_recursiveLock); + + // get the lists of all interested listeners + interested_monitor_listener_list interestedListeners[4]; + int32 interestedListenerCount = 0; + // ... for the volume + _GetInterestedVolumeListeners(device, B_WATCH_NAME, + interestedListeners, interestedListenerCount); + // ... for the node + _GetInterestedMonitorListeners(nodeDevice, node, B_WATCH_NAME, + interestedListeners, interestedListenerCount); + // ... for the source directory + _GetInterestedMonitorListeners(device, fromDirectory, B_WATCH_DIRECTORY, + interestedListeners, interestedListenerCount); + // ... for the target directory + if (toDirectory != fromDirectory) + { + _GetInterestedMonitorListeners(device, toDirectory, B_WATCH_DIRECTORY, + interestedListeners, interestedListenerCount); + } + + if (interestedListenerCount == 0) + return B_OK; + + // there are interested listeners: construct the message and send it + char messageBuffer[1024]; + KMessage message; + message.SetTo(messageBuffer, sizeof(messageBuffer), B_NODE_MONITOR); + message.AddInt32("opcode", B_ENTRY_MOVED); + message.AddInt32("device", device); + message.AddInt64("from directory", fromDirectory); + message.AddInt64("to directory", toDirectory); + message.AddInt32("node device", nodeDevice); // Haiku only + message.AddInt64("node", node); + message.AddString("from name", fromName); // Haiku only + message.AddString("name", toName); + + return _SendNotificationMessage(message, interestedListeners, + interestedListenerCount); +} + status_t NodeMonitorService::NotifyUnmount(haiku_dev_t device) { std::unique_lock locker(_recursiveLock); @@ -529,7 +642,7 @@ status_t NodeMonitorService::UpdateUserListener(const std::shared_ptr std::shared_ptr monitor; status_t status = _GetMonitor(context, device, node, true, &monitor, - (flags & B_WATCH_VOLUME) != 0); + (flags & B_WATCH_VOLUME) != 0); if (status < B_OK) return status; diff --git a/hyclone_server/server_nodemonitor.h b/hyclone_server/server_nodemonitor.h index d498147..bba4f7d 100644 --- a/hyclone_server/server_nodemonitor.h +++ b/hyclone_server/server_nodemonitor.h @@ -10,7 +10,7 @@ #include "BeDefs.h" #include "server_notifications.h" -struct node_monitor; +struct monitor_listener; struct interested_monitor_listener_list; class KMessage; @@ -19,6 +19,15 @@ class IoContext; class UserNodeListener; +typedef std::list> MonitorListenerList; + +struct node_monitor +{ + haiku_dev_t device; + haiku_ino_t node; + MonitorListenerList listeners; +}; + struct monitor_listener { std::list>::iterator context_link; @@ -101,14 +110,14 @@ class NodeMonitorService : public NotificationService status_t InitCheck(); status_t NotifyEntryCreatedOrRemoved(int32 opcode, haiku_dev_t device, - haiku_ino_t directory, const std::string& name, haiku_ino_t node); + haiku_ino_t directory, const char* name, haiku_ino_t node); status_t NotifyEntryMoved(haiku_dev_t device, haiku_ino_t fromDirectory, - const std::string& fromName, haiku_ino_t toDirectory, const std::string& toName, + const char* fromName, haiku_ino_t toDirectory, const char* toName, haiku_ino_t node); status_t NotifyStatChanged(haiku_dev_t device, haiku_ino_t directory, haiku_ino_t node, uint32 statFields); status_t NotifyAttributeChanged(haiku_dev_t device, haiku_ino_t directory, - haiku_ino_t node, const std::string& attribute, int32 cause); + haiku_ino_t node, const char* attribute, int32 cause); status_t NotifyUnmount(haiku_dev_t device); status_t NotifyMount(haiku_dev_t device, haiku_dev_t parentDevice, haiku_ino_t parentDirectory); diff --git a/hyclone_server/server_vfs.cpp b/hyclone_server/server_vfs.cpp index c320686..8ae94b7 100644 --- a/hyclone_server/server_vfs.cpp +++ b/hyclone_server/server_vfs.cpp @@ -28,6 +28,12 @@ size_t VfsService::RegisterEntryRef(const EntryRef& ref, std::string&& path) return _entryRefs.size(); } +size_t VfsService::UnregisterEntryRef(const EntryRef& ref) +{ + _entryRefs.erase(ref); + return _entryRefs.size(); +} + bool VfsService::GetEntryRef(const EntryRef& ref, std::string& path) const { auto it = _entryRefs.find(ref); @@ -40,6 +46,19 @@ bool VfsService::GetEntryRef(const EntryRef& ref, std::string& path) const return false; } +bool VfsService::SearchEntryRef(const std::string& path, EntryRef& ref) const +{ + for (const auto& [entryRef, entryPath] : _entryRefs) + { + if (entryPath == path) + { + ref = entryRef; + return true; + } + } + return false; +} + size_t VfsService::RegisterDevice(const std::shared_ptr& device) { const auto& path = device->GetRoot(); @@ -154,6 +173,11 @@ status_t VfsService::Unmount(const std::filesystem::path& path, uint32 flags) haiku_dev_t dev = it->second->GetInfo().dev; _devices.Remove(dev); _deviceReferences.erase(dev); + for (auto& monitor : _monitors[dev]) + { + it->second->RemoveMonitor(monitor); + } + _monitors.erase(dev); std::vector toRemove; for (auto refIt = _entryRefs.begin(); refIt != _entryRefs.end(); ++refIt) { @@ -318,4 +342,44 @@ status_t VfsService::Ioctl(const std::filesystem::path& path, unsigned int cmd, { return device->Ioctl(path, cmd, addr, buffer, size); }); +} + +status_t VfsService::AddMonitor(haiku_dev_t device, haiku_ino_t node) +{ + auto vfsDevice = _devices.Get(device); + if (!vfsDevice) + { + return B_ENTRY_NOT_FOUND; + } + if (_monitors[device].contains(node)) + { + return B_OK; + } + status_t status = vfsDevice->AddMonitor(node); + if (status != B_OK) + { + return status; + } + _monitors[device].insert(node); + return B_OK; +} + +status_t VfsService::RemoveMonitor(haiku_dev_t device, haiku_ino_t node) +{ + auto vfsDevice = _devices.Get(device); + if (!vfsDevice) + { + return B_ENTRY_NOT_FOUND; + } + if (!_monitors[device].contains(node)) + { + return B_OK; + } + status_t status = vfsDevice->RemoveMonitor(node); + if (status != B_OK) + { + return status; + } + _monitors[device].erase(node); + return B_OK; } \ No newline at end of file diff --git a/hyclone_server/server_vfs.h b/hyclone_server/server_vfs.h index 88296db..f94063a 100644 --- a/hyclone_server/server_vfs.h +++ b/hyclone_server/server_vfs.h @@ -8,6 +8,7 @@ #include #include #include +#include #include "BeDefs.h" #include "entry_ref.h" @@ -51,6 +52,9 @@ class VfsDevice virtual status_t Ioctl(const std::filesystem::path& path, unsigned int cmd, void* addr, void* buffer, size_t size) { return B_BAD_VALUE; } + virtual status_t AddMonitor(haiku_ino_t node) { return B_UNSUPPORTED; } + virtual status_t RemoveMonitor(haiku_ino_t node) { return B_UNSUPPORTED; } + const haiku_fs_info& GetInfo() const { return _info; } haiku_fs_info& GetInfo() { return _info; } const std::filesystem::path& GetRoot() const { return _root; } @@ -75,6 +79,7 @@ class VfsService IdMap, haiku_dev_t> _devices; std::unordered_map, PathHash> _deviceMounts; std::unordered_map _deviceReferences; + std::unordered_map> _monitors; using mounter_t = status_t(*)(const std::filesystem::path& path, const std::filesystem::path& device, uint32 mountFlags, const std::string& args, std::shared_ptr& output); std::unordered_map _mounters; @@ -146,7 +151,9 @@ class VfsService size_t RegisterEntryRef(const EntryRef& ref, const std::string& path); size_t RegisterEntryRef(const EntryRef& ref, std::string&& path); + size_t UnregisterEntryRef(const EntryRef& ref); bool GetEntryRef(const EntryRef& ref, std::string& path) const; + bool SearchEntryRef(const std::string& path, EntryRef& ref) const; size_t RegisterDevice(const std::shared_ptr& device); std::weak_ptr GetDevice(int id); @@ -176,6 +183,9 @@ class VfsService status_t RemoveAttr(const std::filesystem::path& path, const std::string& name); status_t Ioctl(const std::filesystem::path& path, unsigned int cmd, void* addr, void* buffer, size_t size); + status_t AddMonitor(haiku_dev_t device, haiku_ino_t node); + status_t RemoveMonitor(haiku_dev_t device, haiku_ino_t node); + std::unique_lock Lock() { return std::unique_lock(_lock); } }; diff --git a/hyclone_server/sys/linux/server_inotify.cpp b/hyclone_server/sys/linux/server_inotify.cpp new file mode 100644 index 0000000..2e44112 --- /dev/null +++ b/hyclone_server/sys/linux/server_inotify.cpp @@ -0,0 +1,203 @@ +#include +#include +#include +#include +#include + +#include "BeDefs.h" +#include "entry_ref.h" +#include "haiku_errors.h" +#include "server_errno.h" +#include "server_native.h" +#include "server_nodemonitor.h" +#include "server_vfs.h" +#include "system.h" + +static int sInotifyFd = -1; +static std::once_flag sInotifyInitFlag; +static status_t sInotifyStatus = B_NO_INIT; +static std::thread sInotifyThread; +static std::mutex sInotifyMutex; +static std::unordered_map> sInotifyListeners; +static std::unordered_map sInotifyWatches; + +static void server_init_inotify(); +static void server_inotify_thread_main(); + +status_t server_add_native_monitor(const std::filesystem::path& hostPath, haiku_dev_t device, haiku_ino_t node) +{ + std::call_once(sInotifyInitFlag, server_init_inotify); + + if (sInotifyStatus != B_OK) + { + return sInotifyStatus; + } + + int wd = inotify_add_watch(sInotifyFd, hostPath.c_str(), IN_DELETE | IN_CREATE | IN_MOVED_FROM | IN_MOVED_TO); + if (wd < 0) + { + return LinuxToB(errno); + } + + { + auto lock = std::unique_lock(sInotifyMutex); + assert(!sInotifyListeners[wd].contains(EntryRef(device, node))); + sInotifyListeners[wd].insert(EntryRef(device, node)); + sInotifyWatches[EntryRef(device, node)] = wd; + } + + return B_OK; +} + +status_t server_remove_native_monitor(haiku_dev_t device, haiku_ino_t node) +{ + if (sInotifyStatus != B_OK) + { + return sInotifyStatus; + } + + { + auto lock = std::unique_lock(sInotifyMutex); + if (!sInotifyWatches.contains(EntryRef(device, node))) + { + return B_BAD_VALUE; + } + int wd = sInotifyWatches[EntryRef(device, node)]; + sInotifyWatches.erase(EntryRef(device, node)); + sInotifyListeners[wd].erase(EntryRef(device, node)); + if (sInotifyListeners[wd].empty()) + { + inotify_rm_watch(sInotifyFd, wd); + sInotifyListeners.erase(wd); + } + } + + return B_OK; +} + +void server_init_inotify() +{ + sInotifyFd = inotify_init(); + if (sInotifyFd < 0) + { + sInotifyStatus = LinuxToB(errno); + } + + sInotifyThread = std::thread(server_inotify_thread_main); + sInotifyStatus = B_OK; +} + +void server_inotify_thread_main() +{ + uint8_t buffer[std::max((size_t)4096, sizeof(inotify_event) + NAME_MAX + 1)]; + auto& system = System::GetInstance(); + auto& nodeMonitorService = system.GetNodeMonitorService(); + auto& vfsService = system.GetVfsService(); + while (!system.IsShuttingDown()) + { + int length = read(sInotifyFd, buffer, sizeof(buffer)); + if (length < 0) + { + sInotifyStatus = LinuxToB(errno); + break; + } + + for (uint8_t* ptr = buffer; ptr < buffer + length;) + { + const inotify_event& event = *(inotify_event*)ptr; + ptr += sizeof(inotify_event) + sizeof(inotify_event) + event.len; + + std::vector refs; + + { + auto lock = std::unique_lock(sInotifyMutex); + if (!sInotifyListeners.contains(event.wd)) + { + continue; + } + refs.insert(refs.end(), sInotifyListeners[event.wd].begin(), sInotifyListeners[event.wd].end()); + } + + if (event.mask & (IN_DELETE | IN_CREATE | IN_MOVED_FROM | IN_MOVED_TO)) + { + std::vector nodes; + nodes.resize(refs.size()); + + { + auto lock = vfsService.Lock(); + std::string path; + + status_t status; + + for (size_t i = 0; i < refs.size(); i++) + { + nodes[i] = (haiku_ino_t)-1; + if (!vfsService.GetEntryRef(refs[i], path)) + { + continue; + } + + auto nodePath = (std::filesystem::path(path) / event.name).lexically_normal(); + + if (event.mask & (IN_CREATE | IN_MOVED_TO)) + { + haiku_stat stat; + status = vfsService.ReadStat(nodePath, stat, false); + + if (status != B_OK) + { + continue; + } + + nodes[i] = stat.st_ino; + + vfsService.RegisterEntryRef(EntryRef(stat.st_dev, stat.st_ino), nodePath); + } + else // if (event.mask & (IN_DELETE | IN_MOVED_FROM)) + { + // We cannot read the stat of the node that was deleted or moved. + // However, we can check the VFS service cache for any registered + // entry refs for that path. + EntryRef ref; + if (!vfsService.SearchEntryRef(nodePath, ref)) + { + continue; + } + + nodes[i] = ref.GetInode(); + + vfsService.UnregisterEntryRef(ref); + } + } + } + + for (size_t i = 0; i < refs.size(); ++i) + { + if (nodes[i] == (haiku_ino_t)-1) + { + continue; + } + + // We're translating IN_MOVED_FROM and IN_MOVED_TO to B_ENTRY_REMOVED and + // B_ENTRY_CREATED, respectively, instead of B_ENTRY_MOVED because: + // - B_ENTRY_MOVED requires information about both the old and new paths, + // but Linux does not provide a way to get both. + // - It is not guaranteed that paths involved in IN_MOVED_FROM and IN_MOVED_TO + // will be on the same emulated device. + + if (event.mask & (IN_DELETE | IN_MOVED_FROM)) + { + nodeMonitorService.NotifyEntryCreatedOrRemoved(B_ENTRY_REMOVED, + refs[i].GetDevice(), refs[i].GetInode(), event.name, nodes[i]); + } + + if (event.mask & (IN_CREATE | IN_MOVED_TO)) + { + nodeMonitorService.NotifyEntryCreatedOrRemoved(B_ENTRY_CREATED, + refs[i].GetDevice(), refs[i].GetInode(), event.name, nodes[i]); + } + } + } + } + } +} \ No newline at end of file