AuroraRuntime/Source/IO/FS/Watcher.Linux.cpp

394 lines
11 KiB
C++

/***
Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: Watcher.Linux.cpp
Date: 2022-4-10
Author: Reece
***/
#include <Source/RuntimeInternal.hpp>
#include "FS.hpp"
#include "Watcher.Linux.hpp"
#include <sys/inotify.h>
#include <Source/IO/Loop/LSHandle.hpp>
namespace Aurora::IO::FS
{
struct LinuxWatcherHandle : AuLoop::LSHandle
{
LinuxWatcherHandle(int handle) : AuLoop::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<AuString>(code, translated);
if (!code)
{
SysPushErrorMem();
return false;
}
cached.strTheCakeIsALie = AuTryConstruct<AuString>(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<UserWatchData> &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<Loop::ILoopSource> LinuxWatcher::AsLoopSource()
{
AU_LOCK_GUARD(this->spinlock_);
return this->loopSource_;
}
AuList<WatchEvent> LinuxWatcher::QueryUpdates()
{
AU_LOCK_GUARD(this->spinlock_);
bool bSuccess {};
AuList<WatchEvent> 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<LinuxWatcherHandle>(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<LinuxWatcher *>(watcher);
}
}