2022-04-10 15:40:49 +00:00
|
|
|
/***
|
|
|
|
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>
|
|
|
|
|
2022-06-12 13:53:35 +00:00
|
|
|
#include <Source/IO/Loop/LSHandle.hpp>
|
2022-04-10 15:40:49 +00:00
|
|
|
|
|
|
|
namespace Aurora::IO::FS
|
|
|
|
{
|
2022-06-11 23:52:46 +00:00
|
|
|
struct LinuxWatcherHandle : AuLoop::LSHandle
|
2022-04-10 15:40:49 +00:00
|
|
|
{
|
2022-06-11 23:52:46 +00:00
|
|
|
LinuxWatcherHandle(int handle) : AuLoop::LSHandle(AuUInt(handle))
|
2022-04-10 15:40:49 +00:00
|
|
|
{}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2022-04-10 15:51:25 +00:00
|
|
|
mask |= IN_MODIFY;
|
2022-04-10 15:40:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//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;
|
2022-04-13 08:57:28 +00:00
|
|
|
cached.bIsDir = bIsDirectory;
|
2022-04-10 15:40:49 +00:00
|
|
|
|
|
|
|
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);
|
2022-04-13 08:57:28 +00:00
|
|
|
bool bFound {}, bCrinkled{}, bIsDir{};
|
2022-04-10 15:40:49 +00:00
|
|
|
|
|
|
|
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;
|
2022-04-13 08:57:28 +00:00
|
|
|
bIsDir = path.bIsDir;
|
2022-04-10 15:40:49 +00:00
|
|
|
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)
|
|
|
|
{
|
2022-04-13 08:57:28 +00:00
|
|
|
AuTryInsert(ret, AuConstReference(event));
|
2022-04-10 15:40:49 +00:00
|
|
|
}
|
|
|
|
event.event = EWatchEvent::eFileCreate;
|
|
|
|
}
|
2022-04-13 08:57:28 +00:00
|
|
|
else if (header.mask & (IN_DELETE | IN_MOVED_FROM))
|
2022-04-10 15:40:49 +00:00
|
|
|
{
|
|
|
|
if (event.event != EWatchEvent::eEnumInvalid)
|
|
|
|
{
|
2022-04-13 08:57:28 +00:00
|
|
|
AuTryInsert(ret, AuConstReference(event));
|
2022-04-10 15:40:49 +00:00
|
|
|
}
|
|
|
|
event.event = EWatchEvent::eFileDelete;
|
|
|
|
}
|
2022-04-13 08:57:28 +00:00
|
|
|
else if (header.mask & (IN_DELETE_SELF | IN_MOVE_SELF))
|
2022-04-10 15:40:49 +00:00
|
|
|
{
|
|
|
|
if (event.event != EWatchEvent::eEnumInvalid)
|
|
|
|
{
|
2022-04-13 08:57:28 +00:00
|
|
|
AuTryInsert(ret, AuConstReference(event));
|
2022-04-10 15:40:49 +00:00
|
|
|
}
|
|
|
|
event.event = EWatchEvent::eSelfDelete;
|
|
|
|
}
|
2022-04-13 08:57:28 +00:00
|
|
|
else if (header.mask & (IN_MODIFY))
|
2022-04-10 15:40:49 +00:00
|
|
|
{
|
|
|
|
if (event.event != EWatchEvent::eEnumInvalid)
|
|
|
|
{
|
2022-04-13 08:57:28 +00:00
|
|
|
AuTryInsert(ret, AuConstReference(event));
|
2022-04-10 15:40:49 +00:00
|
|
|
}
|
|
|
|
event.event = EWatchEvent::eFileModify;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (event.file.empty() || (bCrinkled) || (header.mask & IN_MOVE_SELF))
|
|
|
|
{
|
|
|
|
if (event.event != EWatchEvent::eEnumInvalid)
|
|
|
|
{
|
2022-04-13 08:57:28 +00:00
|
|
|
AuTryInsert(ret, AuConstReference(event));
|
2022-04-10 15:40:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (header.mask & IN_ISDIR)
|
|
|
|
{
|
|
|
|
event.event = EWatchEvent::eSelfModify;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
event.event = EWatchEvent::eFileModify;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (event.event != EWatchEvent::eEnumInvalid)
|
|
|
|
{
|
2022-04-13 08:57:28 +00:00
|
|
|
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);
|
|
|
|
}
|
2022-04-10 15:40:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|