/*** Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: Watcher.Linux.cpp Date: 2022-4-10 Author: Reece ***/ #include #include "FS.hpp" #include "Watcher.Linux.hpp" #include #include namespace Aurora::IO::FS { struct LinuxWatcherHandle : public Loop::LSHandle { LinuxWatcherHandle(int handle) : Loop::LSHandle(AuUInt(handle)) {} virtual Loop::ELoopSource GetType() override; }; Loop::ELoopSource LinuxWatcherHandle::GetType() { return Loop::ELoopSource::eSourceFileWatcher; } LinuxWatcher::~LinuxWatcher() { Deinit(); } bool LinuxWatcher::AddWatch(const WatchRequest &request) { AuCtorCode_t code; bool bIsDirectory {}; AuUInt32 mask {}; UnixCachedPath cached; auto &file = request.watch; AU_LOCK_GUARD(this->spinlock_); if (this->inotifyHandle_ == -1) { SysPushErrorUninitialized(); return false; } AuString translated; if (!AuIOFS::NormalizePath(translated, file.path)) { SysPushErrorIO(); translated = file.path; } // Create the NT path in the midst of path normalization cached.strNormalizedPath = AuTryConstruct(code, translated); if (!code) { SysPushErrorMem(); return false; } cached.strTheCakeIsALie = AuTryConstruct(code, file.path); if (!code) { SysPushErrorMem(); return false; } cached.userData = file.userData; if (AuIOFS::DirExists(translated)) { bIsDirectory = true; } mask |= IN_ATTRIB; if (bIsDirectory) { mask |= IN_MOVE_SELF; if (AuExists(request.events, EWatchEvent::eFileCreate)) { mask |= IN_CREATE | IN_MOVED_TO /*may as well. easter egg.*/; } if (AuExists(request.events, EWatchEvent::eFileDelete)) { mask |= IN_DELETE | IN_MOVED_FROM; } } if (AuExists(request.events, EWatchEvent::eFileModify) || !bIsDirectory) { mask |= IN_MODIFY; } //if (AuExists(request.events, EWatchEvent::eSelfDelete)) { mask |= IN_DELETE_SELF | IN_MOVE_SELF; } int ret = inotify_add_watch(this->inotifyHandle_, cached.strNormalizedPath.c_str(), mask); if (ret == -1) { // TODO: push error return false; } cached.watcherWd = ret; cached.bIsDir = bIsDirectory; if (!AuTryInsert(this->paths_, cached)) { SysPushErrorMem(); auto ree = inotify_rm_watch(this->inotifyHandle_, ret); if (ree == -1) { SysPushErrorGeneric(); } return false; } return true; } bool LinuxWatcher::RemoveByName(const AuString &path) { AU_LOCK_GUARD(this->spinlock_); AuString strNormalized; if (!AuIOFS::NormalizePath(strNormalized, path)) { return false; } return AuRemoveIf(this->paths_, [&](const UnixCachedPath &object) -> bool { if ((strNormalized == object.strNormalizedPath) || (strNormalized.empty() && object.strNormalizedPath == path)) { auto ree = inotify_rm_watch(this->inotifyHandle_, object.watcherWd); if (ree == -1) { SysPushErrorGeneric(); } return true; } return false; }); } bool LinuxWatcher::RemoveByPrivateContext(const AuSPtr &file) { AU_LOCK_GUARD(this->spinlock_); return AuRemoveIf(this->paths_, [&](const UnixCachedPath &object) -> bool { if (file != object.userData) { return false; } auto ree = inotify_rm_watch(this->inotifyHandle_, object.watcherWd); if (ree == -1) { SysPushErrorGeneric(); } return true; }); } AuSPtr LinuxWatcher::AsLoopSource() { AU_LOCK_GUARD(this->spinlock_); return this->loopSource_; } AuList LinuxWatcher::QueryUpdates() { AU_LOCK_GUARD(this->spinlock_); bool bSuccess {}; AuList ret; if (this->inotifyHandle_ == -1) { SysPushErrorUninitialized(); return ret; } while (true) { char buffer[4096]; int length = read(this->inotifyHandle_, &buffer, sizeof(buffer)); int index {}; if (length <= -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { return ret; } else { SysPushErrorGeneric(); return ret; } } while (index < length) { WatchEvent event; auto & header = *(inotify_event *)(buffer + index); index += sizeof(struct inotify_event) + header.len; #if 0 printf("watcher event: %i %i %i\n", index, header.wd, header.mask); #endif if (!AuTryResize(event.file, header.len)) { SysPushErrorMem("Out of memory -> can't consume body. Will misalign stream... Deinitializing..."); Deinit(); return ret; } AuMemcpy(event.file.data(), header.name, header.len); bool bFound {}, bCrinkled{}, bIsDir{}; for (auto & path : this->paths_) { if (path.watcherWd != header.wd) { continue; } event.watch = WatchedFile {path.userData, path.strTheCakeIsALie}; bCrinkled = path.strNormalizedPath == event.file; bFound = true; bIsDir = path.bIsDir; break; } if (!bFound) { SysPushErrorGeneric("Couldn't find inotify wd for event"); continue; } if (event.file.empty()) { event.file = event.watch.path; // TODO: alloc } event.event = EWatchEvent::eEnumInvalid; if (header.mask & (IN_CREATE | IN_MOVED_TO)) { if (event.event != EWatchEvent::eEnumInvalid) { AuTryInsert(ret, AuConstReference(event)); } event.event = EWatchEvent::eFileCreate; } else if (header.mask & (IN_DELETE | IN_MOVED_FROM)) { if (event.event != EWatchEvent::eEnumInvalid) { AuTryInsert(ret, AuConstReference(event)); } event.event = EWatchEvent::eFileDelete; } else if (header.mask & (IN_DELETE_SELF | IN_MOVE_SELF)) { if (event.event != EWatchEvent::eEnumInvalid) { AuTryInsert(ret, AuConstReference(event)); } event.event = EWatchEvent::eSelfDelete; } else if (header.mask & (IN_MODIFY)) { if (event.event != EWatchEvent::eEnumInvalid) { AuTryInsert(ret, AuConstReference(event)); } event.event = EWatchEvent::eFileModify; } if (event.file.empty() || (bCrinkled) || (header.mask & IN_MOVE_SELF)) { if (event.event != EWatchEvent::eEnumInvalid) { AuTryInsert(ret, AuConstReference(event)); } if (header.mask & IN_ISDIR) { event.event = EWatchEvent::eSelfModify; } else { event.event = EWatchEvent::eFileModify; } } if (event.event != EWatchEvent::eEnumInvalid) { AuTryInsert(ret, AuConstReference(event)); } if (bIsDir) { // The following helps us match the results of the NT impl if ((event.event == EWatchEvent::eFileCreate) || (event.event == EWatchEvent::eFileDelete)|| (event.event == EWatchEvent::eFileModify)) { event.event = EWatchEvent::eSelfModify; event.file = event.watch.path; // TODO: alloc AuTryInsert(ret, event); } } } } return ret; } bool LinuxWatcher::Init() { this->inotifyHandle_ = ::inotify_init1(IN_NONBLOCK); if (this->inotifyHandle_ == -1) { return false; } this->loopSource_ = AuMakeShared(this->inotifyHandle_); return bool(this->loopSource_); } void LinuxWatcher::Deinit() { auto paths = AuExchange(this->paths_, {}); for (auto handle : paths) { if (handle.watcherWd != -1) { auto ree = inotify_rm_watch(this->inotifyHandle_, handle.watcherWd); if (ree == -1) { SysPushErrorGeneric(); } } } if ((this->inotifyHandle_ != 0) && (this->inotifyHandle_ != -1)) { ::close(AuExchange(this->inotifyHandle_, -1)); } } AUKN_SYM IWatcher *NewWatcherNew() { auto watcher = _new LinuxWatcher(); if (!watcher) { return {}; } if (!watcher->Init()) { delete watcher; return {}; } return watcher; } AUKN_SYM void NewWatcherRelease(IWatcher *watcher) { AuSafeDelete(watcher); } }