[+] Linux Watcher
[*] Expand watcher API -> Breaking NT [*] Reexpand loop queue API -> Breaking NT [*] Linux CPUInfo clean up [*] Bug fix: mkdir should set execute flag... because directories are special [*] Refactor: Cleanup base64 [*] Bug fix: UNIX path normalization [*] Bug fix: missing O_CREAT flag (au auto-creates) [*] Normalize line endings
This commit is contained in:
parent
cf219eabaa
commit
3defb1bb14
@ -20,16 +20,40 @@ namespace Aurora::IO::FS
|
||||
AuString path;
|
||||
};
|
||||
|
||||
AUE_DEFINE(EWatchEvent,
|
||||
(
|
||||
eSelfModify,
|
||||
eSelfDelete,
|
||||
eFileModify,
|
||||
eFileDelete,
|
||||
eFileCreate
|
||||
));
|
||||
|
||||
struct WatchRequest
|
||||
{
|
||||
WatchedFile watch;
|
||||
|
||||
// events are mere optimization hint. additional events may be provided, if available.
|
||||
AuList<EWatchEvent> events;
|
||||
};
|
||||
|
||||
struct WatchEvent
|
||||
{
|
||||
EWatchEvent event;
|
||||
WatchedFile watch;
|
||||
AuString file;
|
||||
};
|
||||
|
||||
struct IWatcher
|
||||
{
|
||||
virtual bool AddWatch(const WatchedFile &file) = 0;
|
||||
virtual bool AddWatch(const WatchRequest &file) = 0;
|
||||
|
||||
virtual bool RemoveByName(const AuString &path) = 0;
|
||||
virtual bool RemoveByPrivateContext(const AuSPtr<UserWatchData> &file) = 0;
|
||||
|
||||
virtual AuSPtr<Loop::ILoopSource> AsLoopSource() = 0;
|
||||
|
||||
virtual AuList<WatchedFile> QueryUpdates() = 0;
|
||||
virtual AuList<WatchEvent> QueryUpdates() = 0;
|
||||
};
|
||||
|
||||
AUKN_SHARED_API(NewWatcher, IWatcher);
|
||||
|
@ -50,6 +50,11 @@ namespace Aurora::Loop
|
||||
|
||||
/**
|
||||
* @brief Updates the OS watchdog list cache concept after Source[Remove/Add[WithTimeout]]
|
||||
* Commits may occur while another thread is waiting on the loop queue.
|
||||
* In those circumstances, they are internally preempted and rescheduled.
|
||||
* WaitAnd has undefined behaviour across MT commit. Linux *might* work.
|
||||
* NT wont until an APC-as-a-preempt-signal hack is implemented.
|
||||
* Rare edge case for all i care - it'll remain a blocking edge case for.now.
|
||||
* @return
|
||||
* @note thread safe
|
||||
*/
|
||||
@ -103,35 +108,48 @@ namespace Aurora::Loop
|
||||
|
||||
|
||||
/**
|
||||
* @brief Nonblocking wait-any for all objects in the loop queue
|
||||
* @warning (may yield to ILoopSourceSubscriber delegate on the current context)
|
||||
* @brief Nonblocking check for alert objects in the loop queue
|
||||
* @return
|
||||
* @note thread safe / nonblocking | can be called alongside any other function marked as such
|
||||
*/
|
||||
virtual bool IsSignaled() = 0;
|
||||
virtual bool IsSignaledPeek() = 0;
|
||||
|
||||
/**
|
||||
* @brief Nonblocking wait-any for all objects in the loop queue
|
||||
* @warning (may yield to ILoopSourceSubscriber delegate on the current context)
|
||||
* @warning (technically unsafe, use alloc-unsafe extended version to acknowledge unlocked sources)
|
||||
* @return
|
||||
* @note thread safe / nonblocking | can be called alongside any other function marked as such
|
||||
*/
|
||||
virtual AuUInt32 PumpNonblocking() = 0;
|
||||
|
||||
virtual AuList<AuSPtr<ILoopSource>> PumpNonblockingEx() = 0;
|
||||
|
||||
/**
|
||||
* @brief Waits on all the submitted loop sources until they are all complete or until the timeout has finished.
|
||||
* Note: the completion of another Wait[All/Any[Ex]] call may result in a
|
||||
* @param timeout timeout in MS, zero = indefinite
|
||||
* @return
|
||||
* @warning (may yield to ILoopSourceSubscriber delegate on the current context)
|
||||
* @warning (thread safety is limited blocking callers of the object)
|
||||
* @warning (thread safety might be limited to blocking callers of the object)
|
||||
*/
|
||||
virtual bool WaitAll (AuUInt32 timeout = 0) = 0;
|
||||
|
||||
/**
|
||||
* @brief Waits on all the loop sources until at least one is signaled.
|
||||
* Additional work may be scheduled on other threads.
|
||||
* @param timeout
|
||||
* @param timeout timeout in MS, zero = indefinite.
|
||||
* Use IsSignaledPump for nonblocking.
|
||||
* @return
|
||||
* @warning (may yield to ILoopSourceSubscriber delegate on the current context)
|
||||
* @warning (technically unsafe, use alloc-unsafe extended version to acknowledge unlocked sources)
|
||||
* @note thread safe | can be called alongside any other function marked as such
|
||||
*/
|
||||
virtual AuUInt32 WaitAny (AuUInt32 timeout = 0) = 0;
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* @param timeout
|
||||
* @brief Waits on all the loop sources until at least one is signaled.
|
||||
* @param timeout timeout in MS, zero = indefinite.
|
||||
* Use IsSignaledPump for nonblocking.
|
||||
* @return
|
||||
* @warning (may yield to ILoopSourceSubscriber delegate on the current context)
|
||||
* @note thread safe | can be called alongside any other function marked as such
|
||||
|
@ -238,7 +238,11 @@ namespace Aurora::Async
|
||||
{
|
||||
if (state->rateLimiter.CheckExchangePass())
|
||||
{
|
||||
bShouldTrySleepForKernel = asyncLoop->IsSignaled();
|
||||
#if defined(AURORA_PLATFORM_WIN32)
|
||||
bShouldTrySleepForKernel = asyncLoop->PumpNonblocking();
|
||||
#else
|
||||
bShouldTrySleepForKernel = asyncLoop->IsSignaledPeek();
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -256,18 +260,28 @@ namespace Aurora::Async
|
||||
{
|
||||
AuThreading::ContextYield();
|
||||
block = false;
|
||||
bShouldTrySleepForKernel = asyncLoop->IsSignaled();
|
||||
#if defined(AURORA_PLATFORM_WIN32)
|
||||
bShouldTrySleepForKernel = asyncLoop->PumpNonblocking();
|
||||
#else
|
||||
bShouldTrySleepForKernel = asyncLoop->IsSignaledPeek();
|
||||
#endif
|
||||
}
|
||||
else if (runMode == ERunMode::eEfficient)
|
||||
{
|
||||
bShouldTrySleepForKernel = block;
|
||||
if (!block)
|
||||
{
|
||||
bShouldTrySleepForKernel = asyncLoop->IsSignaled();
|
||||
bShouldTrySleepForKernel = asyncLoop->IsSignaledPeek();
|
||||
}
|
||||
}
|
||||
|
||||
if (bShouldTrySleepForKernel && asyncLoop->WaitAny(0))
|
||||
if (bShouldTrySleepForKernel
|
||||
// epoll and such like can be checked without read success. kevent works on availablity, not scheduling read like iosubmit, too.
|
||||
// allow windows to atomically pump instead of wasting time buffering the primitives state
|
||||
#if defined(AURORA_PLATFORM_WIN32)
|
||||
&& asyncLoop->WaitAny(0)
|
||||
#endif
|
||||
)
|
||||
{
|
||||
PollInternal(block);
|
||||
success = true;
|
||||
|
@ -111,25 +111,25 @@ namespace Aurora::HWInfo
|
||||
children.Add(CpuBitId(word));
|
||||
}
|
||||
|
||||
gCpuInfo.uCores++;
|
||||
gCpuInfo.coreTopology.push_back(children);
|
||||
gCpuInfo.threadTopology.push_back(children.lower);
|
||||
}
|
||||
|
||||
gCpuInfo.uThreads++;
|
||||
gCpuInfo.maskAllCores.Add(cpuId);
|
||||
}
|
||||
}
|
||||
|
||||
void SetCpuTopologyLinux()
|
||||
{
|
||||
gCpuInfo.uSocket = 1;
|
||||
gCpuInfo.uCores = 1;
|
||||
gCpuInfo.uThreads = get_nprocs();
|
||||
|
||||
gCpuInfo.bMaskMTHalf = true;
|
||||
|
||||
SetCaches();
|
||||
SetCpuA();
|
||||
|
||||
gCpuInfo.uCores = gCpuInfo.coreTopology.size();
|
||||
gCpuInfo.uSocket = AuMax(gCpuInfo.uSocket, AuUInt8(1));
|
||||
gCpuInfo.uCores = AuMax(gCpuInfo.uCores, AuUInt8(1));
|
||||
gCpuInfo.uThreads = gCpuInfo.uThreads ?
|
||||
gCpuInfo.uThreads :
|
||||
get_nprocs();
|
||||
}
|
||||
}
|
@ -25,7 +25,7 @@ namespace Aurora::IO::FS
|
||||
{
|
||||
bool _MkDir(const AuString &path)
|
||||
{
|
||||
return mkdir(path.c_str(), 0760) == 0;
|
||||
return mkdir(path.c_str(), 0775) == 0;
|
||||
}
|
||||
|
||||
AUKN_SYM bool FilesInDirectory(const AuString &string, AuList<AuString> &files)
|
||||
@ -56,7 +56,7 @@ namespace Aurora::IO::FS
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
AUKN_SYM bool ReadFile(const AuString &path, AuByteBuffer &buffer)
|
||||
|
@ -87,6 +87,10 @@ namespace Aurora::IO::FS
|
||||
{
|
||||
result += kDoublePathSplitter;
|
||||
}
|
||||
else if (buffer.size() && buffer[0] == kPathSplitter)
|
||||
{
|
||||
result += kPathSplitter;
|
||||
}
|
||||
|
||||
/**
|
||||
Technically, UTF-8 strings may contain the "NUL" byte.
|
||||
|
@ -90,6 +90,10 @@ namespace Aurora::IO::FS
|
||||
((!isFile) && (end)))
|
||||
{
|
||||
auto subpath = end ? cpath : AuString(cpath.begin(), cpath.begin() + i);
|
||||
if (subpath.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (DirExists(subpath))
|
||||
{
|
||||
|
@ -303,11 +303,12 @@ namespace Aurora::IO::FS
|
||||
}
|
||||
|
||||
auto fileHandle = open(pathex.c_str(),
|
||||
openMode == EFileOpenMode::eRead ? O_RDONLY : O_RDWR);
|
||||
openMode == EFileOpenMode::eRead ? O_RDONLY : (O_RDWR | O_CREAT),
|
||||
0644);
|
||||
|
||||
if (fileHandle < 0)
|
||||
{
|
||||
SysPushErrorIO("Couldn't open file: {}", path);
|
||||
SysPushErrorIO("Couldn't open file: {} ({}) {}", path, pathex, errno);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
384
Source/IO/FS/Watcher.Linux.cpp
Executable file
384
Source/IO/FS/Watcher.Linux.cpp
Executable file
@ -0,0 +1,384 @@
|
||||
/***
|
||||
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/Loop/LSHandle.hpp>
|
||||
|
||||
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<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 | IN_CLOSE_WRITE;
|
||||
}
|
||||
|
||||
//if (AuExists(request.events, EWatchEvent::eSelfDelete))
|
||||
{
|
||||
mask |= IN_DELETE_SELF | IN_MOVE_SELF;
|
||||
}
|
||||
|
||||
mask = IN_ALL_EVENTS;
|
||||
|
||||
int ret = inotify_add_watch(this->inotifyHandle_, cached.strNormalizedPath.c_str(), mask);
|
||||
if (ret == -1)
|
||||
{
|
||||
// TODO: push error
|
||||
return false;
|
||||
}
|
||||
|
||||
cached.watcherWd = ret;
|
||||
|
||||
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{};
|
||||
|
||||
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;
|
||||
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, event);
|
||||
}
|
||||
event.event = EWatchEvent::eFileCreate;
|
||||
}
|
||||
|
||||
if (header.mask & (IN_DELETE | IN_MOVED_FROM))
|
||||
{
|
||||
if (event.event != EWatchEvent::eEnumInvalid)
|
||||
{
|
||||
AuTryInsert(ret, event);
|
||||
}
|
||||
event.event = EWatchEvent::eFileDelete;
|
||||
}
|
||||
|
||||
if (header.mask & (IN_DELETE_SELF | IN_MOVE_SELF))
|
||||
{
|
||||
if (event.event != EWatchEvent::eEnumInvalid)
|
||||
{
|
||||
AuTryInsert(ret, event);
|
||||
}
|
||||
event.event = EWatchEvent::eSelfDelete;
|
||||
}
|
||||
|
||||
if (header.mask & (IN_MODIFY))
|
||||
{
|
||||
if (event.event != EWatchEvent::eEnumInvalid)
|
||||
{
|
||||
AuTryInsert(ret, event);
|
||||
}
|
||||
event.event = EWatchEvent::eFileModify;
|
||||
}
|
||||
|
||||
if (event.file.empty() || (bCrinkled) || (header.mask & IN_MOVE_SELF))
|
||||
{
|
||||
if (event.event != EWatchEvent::eEnumInvalid)
|
||||
{
|
||||
AuTryInsert(ret, event);
|
||||
}
|
||||
|
||||
if (header.mask & IN_ISDIR)
|
||||
{
|
||||
event.event = EWatchEvent::eSelfModify;
|
||||
}
|
||||
else
|
||||
{
|
||||
event.event = EWatchEvent::eFileModify;
|
||||
}
|
||||
}
|
||||
|
||||
if (event.event != EWatchEvent::eEnumInvalid)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
45
Source/IO/FS/Watcher.Linux.hpp
Executable file
45
Source/IO/FS/Watcher.Linux.hpp
Executable file
@ -0,0 +1,45 @@
|
||||
/***
|
||||
Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved.
|
||||
|
||||
File: Watcher.Linux.hpp
|
||||
Date: 2022-4-10
|
||||
Author: Reece
|
||||
***/
|
||||
#pragma once
|
||||
|
||||
namespace Aurora::IO::FS
|
||||
{
|
||||
struct LinuxWatcherHandle;
|
||||
|
||||
struct UnixCachedPath
|
||||
{
|
||||
AuString strNormalizedPath;
|
||||
AuString strTheCakeIsALie;
|
||||
AuSPtr<UserWatchData> userData;
|
||||
int watcherWd {-1};
|
||||
};
|
||||
|
||||
struct LinuxWatcher : IWatcher
|
||||
{
|
||||
~LinuxWatcher();
|
||||
|
||||
virtual bool AddWatch(const WatchRequest &file) override;
|
||||
|
||||
virtual bool RemoveByName(const AuString &path) override;
|
||||
virtual bool RemoveByPrivateContext(const AuSPtr<UserWatchData> &file) override;
|
||||
|
||||
virtual AuSPtr<Loop::ILoopSource> AsLoopSource() override;
|
||||
|
||||
virtual AuList<WatchEvent> QueryUpdates() override;
|
||||
|
||||
bool Init();
|
||||
void Deinit();
|
||||
|
||||
private:
|
||||
AuSPtr<LinuxWatcherHandle> loopSource_;
|
||||
AuThreadPrimitives::SpinLock spinlock_;
|
||||
AuList<UnixCachedPath> paths_;
|
||||
int inotifyHandle_ {-1};
|
||||
};
|
||||
|
||||
}
|
@ -4,6 +4,8 @@
|
||||
File: Watcher.NT.cpp
|
||||
Date: 2022-4-1
|
||||
Author: Reece
|
||||
Note: FindFirstChangeNotification locks the parent directory, every other library is garbage
|
||||
Aurora shall implement this sewage instead
|
||||
***/
|
||||
#include <Source/RuntimeInternal.hpp>
|
||||
#include "FS.hpp"
|
||||
@ -66,21 +68,30 @@ namespace Aurora::IO::FS
|
||||
AuString strTheCakeIsALie;
|
||||
AuSPtr<UserWatchData> userData;
|
||||
AuSPtr<NTWatchObject> watcher;
|
||||
bool bIsDirectory {};
|
||||
|
||||
AuList<AuTuple<AuString, bool, AuUInt64>> lastTick;
|
||||
|
||||
FILETIME lastFileTime {};
|
||||
|
||||
bool CheckRun();
|
||||
AuList<AuTuple<AuString, bool, AuUInt64>> GetCurrentState();
|
||||
|
||||
bool CheckDirDelta();
|
||||
void Warm();
|
||||
bool CheckRun(NTWatcher *parent);
|
||||
bool AddEvent(NTWatcher *parent, EWatchEvent type, const AuString &path);
|
||||
};
|
||||
|
||||
struct NTWatcher : IWatcher
|
||||
{
|
||||
virtual bool AddWatch(const WatchedFile &file) override;
|
||||
virtual bool WatchRequest(const WatchRequest &file) override;
|
||||
|
||||
virtual bool RemoveByName(const AuString &path) override;
|
||||
virtual bool RemoveByPrivateContext(const AuSPtr<UserWatchData> &file) override;
|
||||
|
||||
virtual AuSPtr<Loop::ILoopSource> AsLoopSource() override;
|
||||
|
||||
virtual AuList<WatchedFile> QueryUpdates() override;
|
||||
virtual AuList<WatchEvent> QueryUpdates() override;
|
||||
|
||||
bool Init();
|
||||
bool GoBrr();
|
||||
@ -94,7 +105,7 @@ namespace Aurora::IO::FS
|
||||
|
||||
public:
|
||||
|
||||
AuList<WatchedFile> triggered_;
|
||||
AuList<WatchEvent> triggered_;
|
||||
AuSPtr<NTEvent> ntEvent_;
|
||||
AuList<NTCachedPath> paths_;
|
||||
|
||||
@ -157,7 +168,69 @@ namespace Aurora::IO::FS
|
||||
|
||||
// NT Cached Path / File Watch Item class
|
||||
|
||||
bool NTCachedPath::CheckRun()
|
||||
void NTCachedPath::Warm()
|
||||
{
|
||||
HANDLE hFile;
|
||||
hFile = CreateFileW(AuLocale::ConvertFromUTF8(this->strNormalizedPath).c_str(),
|
||||
GENERIC_READ,
|
||||
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL | (this->bIsDirectory ? FILE_FLAG_BACKUP_SEMANTICS : 0),
|
||||
NULL);
|
||||
|
||||
if (hFile != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
if (!GetFileTime(hFile, NULL, NULL, &this->lastFileTime))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
CloseHandle(hFile);
|
||||
}
|
||||
|
||||
this->lastTick = GetCurrentState();
|
||||
}
|
||||
|
||||
AuList<AuTuple<AuString, bool, AuUInt64>> NTCachedPath::GetCurrentState()
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
bool NTCachedPath::CheckDirDelta()
|
||||
{
|
||||
auto next = GetCurrentState();
|
||||
auto currentState = next;
|
||||
auto oldState = AuExchange(this->lastTick, AuMove(next));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NTCachedPath::AddEvent(NTWatcher *parent, EWatchEvent type, const AuString &path)
|
||||
{
|
||||
AuCtorCode_t code;
|
||||
auto watchedFile = AuTryConstruct<WatchedFile>(code, this->userData, this->strTheCakeIsALie);
|
||||
if (!code)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
WatchEvent event;
|
||||
event.event = type;
|
||||
event.watch = AuMove(watchedFile);
|
||||
event.file = AuTryConstruct<AuString>(code, path);
|
||||
if (!code)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!AuTryInsert(parent->triggered_, AuMove(watchedFile)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NTCachedPath::CheckRun(NTWatcher *parent)
|
||||
{
|
||||
FILETIME curFileTime {};
|
||||
HANDLE hFile;
|
||||
@ -167,13 +240,23 @@ namespace Aurora::IO::FS
|
||||
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL | (AuIOFS::DirExists(this->strNormalizedPath) ? FILE_FLAG_BACKUP_SEMANTICS : 0),
|
||||
FILE_ATTRIBUTE_NORMAL | (this->bIsDirectory ? FILE_FLAG_BACKUP_SEMANTICS : 0),
|
||||
NULL);
|
||||
|
||||
if (hFile == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
if (GetLastError() == ERROR_FILE_NOT_FOUND)
|
||||
{
|
||||
// NOTE: we might want to add a check to prevent double dispatch
|
||||
return AddEvent(parent, EWatchEvent::eSelfDelete, this->strTheCakeIsALie);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool bDirChanged = CheckDirDelta();
|
||||
|
||||
if (!GetFileTime(hFile, NULL, NULL, &curFileTime))
|
||||
{
|
||||
@ -188,7 +271,13 @@ namespace Aurora::IO::FS
|
||||
|
||||
CloseHandle(hFile);
|
||||
|
||||
return ret;
|
||||
if (!ret)
|
||||
{
|
||||
return bDirChanged;
|
||||
}
|
||||
|
||||
// Send a self modified update once the timestamp changes
|
||||
return AddEvent(parent,this->bIsDirectory ? EWatchEvent::eSelfModify : EWatchEvent::eFileModify, this->strTheCakeIsALie) || bDirChanged;
|
||||
}
|
||||
|
||||
// Directory Watcher Object
|
||||
@ -277,26 +366,12 @@ namespace Aurora::IO::FS
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!filesWatched.CheckRun())
|
||||
if (!filesWatched.CheckRun(this->parent))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bAnyTriggered = true;
|
||||
|
||||
AuCtorCode_t code;
|
||||
auto watchedFile = AuTryConstruct<WatchedFile>(code, filesWatched.userData, filesWatched.strTheCakeIsALie);
|
||||
if (!code)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!AuTryInsert(this->parent->triggered_, AuMove(watchedFile)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bAnyTriggered = true;
|
||||
}
|
||||
|
||||
if (this->whoAsked_.Flags & REQUEST_OPLOCK_OUTPUT_FLAG_ACK_REQUIRED)
|
||||
@ -320,14 +395,15 @@ namespace Aurora::IO::FS
|
||||
|
||||
// NT Watcher - primary interface implementation
|
||||
|
||||
bool NTWatcher::AddWatch(const WatchedFile &file)
|
||||
bool NTWatcher::AddWatch(const WatchRequest &request)
|
||||
{
|
||||
AuCtorCode_t code;
|
||||
AuSPtr<NTWatchObject> watcher;
|
||||
NTCachedPath cached;
|
||||
|
||||
AU_LOCK_GUARD(this->spinlock_);
|
||||
auto &file = request.watch;
|
||||
|
||||
AU_LOCK_GUARD(this->spinlock_);
|
||||
|
||||
AuString translated;
|
||||
if (!AuIOFS::NormalizePath(translated, file.path))
|
||||
@ -350,17 +426,21 @@ namespace Aurora::IO::FS
|
||||
|
||||
cached.userData = file.userData;
|
||||
|
||||
|
||||
// Update the last edited timestamp as a frame of reference for fast compare of
|
||||
// directory entries upon lock breakage
|
||||
cached.CheckRun();
|
||||
|
||||
// Continue normalizing the parent path
|
||||
|
||||
if (AuIOFS::FileExists(translated))
|
||||
{
|
||||
AuIOFS::GetDirectoryFromPath(translated, translated);
|
||||
}
|
||||
else
|
||||
{
|
||||
// dammit, had to move checkrun
|
||||
cached.bIsDirectory = true;
|
||||
}
|
||||
|
||||
// Update the last edited timestamp as a frame of reference for fast compare of
|
||||
// directory entries upon lock breakage
|
||||
cached.Warm();
|
||||
|
||||
// Attempt to locate a watcher for the directoy
|
||||
|
||||
@ -387,7 +467,6 @@ namespace Aurora::IO::FS
|
||||
|
||||
item->parent = this;
|
||||
|
||||
|
||||
if (!item->Init(translated))
|
||||
{
|
||||
SysPushErrorGen();
|
||||
@ -497,9 +576,10 @@ namespace Aurora::IO::FS
|
||||
return AuStaticCast<Loop::ILSEvent>(this->ntEvent_);
|
||||
}
|
||||
|
||||
AuList<WatchedFile> NTWatcher::NTWatcher::QueryUpdates()
|
||||
AuList<WatchEvent> NTWatcher::NTWatcher::QueryUpdates()
|
||||
{
|
||||
AU_LOCK_GUARD(this->spinlock_);
|
||||
AsLoopSource().IsSignaled(); // nonblocking, wont latch
|
||||
return AuExchange(this->triggered_, {});
|
||||
}
|
||||
|
||||
|
@ -353,7 +353,7 @@ namespace Aurora::Loop
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoopQueue::IsSignaled()
|
||||
bool LoopQueue::IsSignaledPeek()
|
||||
{
|
||||
fd_set readSet;
|
||||
struct timeval tv {};
|
||||
@ -402,7 +402,6 @@ namespace Aurora::Loop
|
||||
// [==========] 1 test from 1 test suite ran. (11100 ms total)
|
||||
// ...and a turbojet
|
||||
|
||||
|
||||
//bool bTryAgain {};
|
||||
//DoTick(timeout, {}, &bTryAgain);
|
||||
// ...and + ~10ms latency
|
||||
@ -450,6 +449,35 @@ namespace Aurora::Loop
|
||||
return cTicked;
|
||||
}
|
||||
|
||||
AuUInt32 LoopQueue::PumpNonblocking()
|
||||
{
|
||||
AuUInt32 cTicked {};
|
||||
bool bTryAgain {};
|
||||
|
||||
do
|
||||
{
|
||||
bTryAgain = false;
|
||||
AuUInt32 ticked = DoTick(0, {}, &bTryAgain, true);
|
||||
cTicked += ticked;
|
||||
} while (bTryAgain);
|
||||
|
||||
return cTicked;
|
||||
}
|
||||
|
||||
AuList<AuSPtr<ILoopSource>> LoopQueue::PumpNonblockingEx()
|
||||
{
|
||||
AuList<AuSPtr<ILoopSource>> ret;
|
||||
bool bTryAgain {};
|
||||
|
||||
do
|
||||
{
|
||||
bTryAgain = false;
|
||||
AuUInt32 ticked = DoTick(0, &ret, &bTryAgain, true);
|
||||
} while (bTryAgain);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
AuList<AuSPtr<ILoopSource>> LoopQueue::WaitAnyEx(AuUInt32 timeoutIn)
|
||||
{
|
||||
AuList<AuSPtr<ILoopSource>> ret;
|
||||
@ -461,12 +489,10 @@ namespace Aurora::Loop
|
||||
}
|
||||
|
||||
bool bTryAgain {};
|
||||
AuUInt32 cTicked {};
|
||||
do
|
||||
{
|
||||
bTryAgain = false;
|
||||
AuUInt32 ticked = DoTick(timeout, &ret, &bTryAgain);
|
||||
cTicked += ticked;
|
||||
} while (bTryAgain);
|
||||
|
||||
return ret;
|
||||
@ -582,7 +608,7 @@ namespace Aurora::Loop
|
||||
}
|
||||
}
|
||||
|
||||
AuUInt32 LoopQueue::DoTick(AuUInt64 time, AuList<AuSPtr<ILoopSource>> *optOut, bool *tryAgain)
|
||||
AuUInt32 LoopQueue::DoTick(AuUInt64 time, AuList<AuSPtr<ILoopSource>> *optOut, bool *tryAgain, bool nonblock)
|
||||
{
|
||||
AuUInt32 bTicked {};
|
||||
AuUInt64 now {};
|
||||
@ -590,7 +616,6 @@ namespace Aurora::Loop
|
||||
|
||||
AU_LOCK_GUARD(this->sourceMutex_->AsReadable());
|
||||
|
||||
|
||||
for (const auto & source : this->sources_)
|
||||
{
|
||||
if (source->sourceExtended)
|
||||
@ -599,33 +624,39 @@ namespace Aurora::Loop
|
||||
}
|
||||
}
|
||||
|
||||
// epoll_pwait2 is fucking broken and the dipshits who wrote the test used relative values
|
||||
// syscall epoll_pwait2 is fucking broken and the dipshits who wrote the test used relative values
|
||||
//
|
||||
// Nothing I tried worked.
|
||||
//
|
||||
// Am I stupid? Probably, but...
|
||||
// (1) no one as far as i can tell has ever written anything using this api, per a github search
|
||||
// (1) no one as far as i can tell has ever written anything using this syscall, per a github search
|
||||
// (2) i found one reference that the that are the linux kernel developers used MONO time for this
|
||||
// one timespec API unlike everything else, using an abs value rel to that clock didn't change
|
||||
// one timespec API unlike everything else; using an abs value rel to that clock didn't change
|
||||
// anything.
|
||||
// (3) i found a test that would indicate its relative despite the fact UNIX/Linux sync APIs
|
||||
// tend to use abs time
|
||||
//
|
||||
// What does my experience working on xenus tell me?
|
||||
// Because the GOOOOGLERs in the form of linux kernel developers were faced with an issue that
|
||||
// Because the gOOOOglers in the form of linux kernel developers were faced with an issue that
|
||||
// couldn't be solved by involve copy/pasting memory map code, making a mess of public headers,
|
||||
// or taking credit for third party driver code as their own kernel code, indeed are to blame
|
||||
// for making my life miserable once again.
|
||||
// or taking credit for third party driver code as their own kernel code; they indeed are to
|
||||
// blame for making my life miserable once again. Something about this aint adding up.
|
||||
|
||||
|
||||
|
||||
|
||||
auto deltaMS = time ? AuMin(AuInt64(4), (AuInt64)time - (AuInt64)AuTime::CurrentClockMS()) : 0;
|
||||
if (deltaMS < 0) deltaMS = 0;
|
||||
AuInt64 deltaMS = 0;
|
||||
if (time)
|
||||
{
|
||||
deltaMS = AuMin(AuInt64(4), (AuInt64)time - (AuInt64)AuTime::CurrentClockMS());
|
||||
if (deltaMS < 0)
|
||||
{
|
||||
deltaMS = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
deltaMS = nonblock ? 0 : -1;
|
||||
}
|
||||
|
||||
int iEvents = epoll_wait(this->epollFd_, events, AuArraySize(events), deltaMS);
|
||||
|
||||
|
||||
if (iEvents == -1)
|
||||
{
|
||||
goto out;
|
||||
|
@ -85,9 +85,6 @@ namespace Aurora::Loop
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
bool IsValid();
|
||||
bool RemoveSourceNB(const AuSPtr<ILoopSource> &source);
|
||||
bool WaitAnyNBSpurious(AuUInt32 timeout, AuUInt32 &chuggerIndex, AuList<AuSPtr<ILoopSource>> *trigger, bool poll);
|
||||
|
@ -21,12 +21,14 @@ namespace Aurora::Loop
|
||||
|
||||
for (const auto i : handles)
|
||||
{
|
||||
if (i == -1) continue;
|
||||
FD_SET(i, &readSet);
|
||||
maxHandle = AuMax(maxHandle, i + 1);
|
||||
}
|
||||
|
||||
for (const auto i : handlesWrite)
|
||||
{
|
||||
if (i == -1) continue;
|
||||
FD_SET(i, &writeSet);
|
||||
maxHandle = AuMax(maxHandle, i + 1);
|
||||
}
|
||||
@ -67,13 +69,24 @@ namespace Aurora::Loop
|
||||
{
|
||||
fd_set readSet, writeSet;
|
||||
struct timeval tv {};
|
||||
int maxFd {};
|
||||
|
||||
FD_ZERO(&readSet);
|
||||
FD_ZERO(&writeSet);
|
||||
FD_SET(read, &readSet);
|
||||
FD_SET(write, &writeSet);
|
||||
|
||||
auto active = select(AuMax(read, write) + 1, read != -1 ? &readSet : NULL, write != -1 ? &writeSet : NULL, NULL, &tv);
|
||||
if (read != -1)
|
||||
{
|
||||
maxFd = read + 1;
|
||||
FD_SET(read, &readSet);
|
||||
}
|
||||
|
||||
if (write != -1)
|
||||
{
|
||||
FD_SET(write, &writeSet);
|
||||
maxFd = AuMax(maxFd, int(write) + 1);
|
||||
}
|
||||
|
||||
auto active = select(maxFd, read != -1 ? &readSet : NULL, write != -1 ? &writeSet : NULL, NULL, &tv);
|
||||
if (active == -1)
|
||||
{
|
||||
// todo push error
|
||||
|
@ -18,14 +18,14 @@ namespace Aurora::Parse
|
||||
if (!AuTryResize(decoded, length))
|
||||
{
|
||||
SysPushErrorMem();
|
||||
return {};
|
||||
return false;
|
||||
}
|
||||
|
||||
auto status = base32_decode(in.data(), in.size(), reinterpret_cast<unsigned char *>(&decoded[0]), &length, BASE32_RFC4648);
|
||||
if (!AuTryResize(decoded, length))
|
||||
{
|
||||
SysPushErrorMem();
|
||||
return {};
|
||||
return false;
|
||||
}
|
||||
|
||||
return status == CRYPT_OK;
|
||||
@ -38,14 +38,14 @@ namespace Aurora::Parse
|
||||
if (!AuTryResize(encoded, length))
|
||||
{
|
||||
SysPushErrorMem();
|
||||
return {};
|
||||
return false;
|
||||
}
|
||||
|
||||
auto status = base32_encode(reinterpret_cast<const unsigned char *>(buffer), length, &encoded[0], &outLength, BASE32_RFC4648);
|
||||
if (!AuTryResize(encoded, length))
|
||||
{
|
||||
SysPushErrorMem();
|
||||
return {};
|
||||
return false;
|
||||
}
|
||||
|
||||
return status == CRYPT_OK;
|
||||
|
@ -14,9 +14,12 @@ namespace Aurora::Parse
|
||||
AUKN_SYM bool Base64Decode(const AuString &in, AuByteBuffer &decoded, bool url)
|
||||
{
|
||||
unsigned long length = in.size();
|
||||
try
|
||||
|
||||
if (!AuTryResize(decoded, length))
|
||||
{
|
||||
decoded.resize(in.size());
|
||||
SysPushErrorMem();
|
||||
return false;
|
||||
}
|
||||
|
||||
int status;
|
||||
if (url)
|
||||
@ -28,23 +31,24 @@ namespace Aurora::Parse
|
||||
status = base64_decode(in.data(), in.size(), reinterpret_cast<unsigned char *>(&decoded[0]), &length);
|
||||
}
|
||||
|
||||
decoded.resize(length);
|
||||
return status == CRYPT_OK;
|
||||
}
|
||||
catch (...)
|
||||
if (!AuTryResize(decoded, length))
|
||||
{
|
||||
AuLogWarn("Decoding error: {}", in);
|
||||
Debug::PrintError();
|
||||
SysPushErrorMem();
|
||||
return false;
|
||||
}
|
||||
|
||||
return status == CRYPT_OK;
|
||||
}
|
||||
|
||||
AUKN_SYM bool Base64Encode(const Memory::MemoryViewRead &input, AuString &encoded, bool url)
|
||||
{
|
||||
unsigned long outLength = input.length + (input.length / 3.0) + 16;
|
||||
try
|
||||
|
||||
if (!AuTryResize(encoded, outLength))
|
||||
{
|
||||
encoded.resize(outLength);
|
||||
SysPushErrorMem();
|
||||
return false;
|
||||
}
|
||||
|
||||
int status;
|
||||
if (url)
|
||||
@ -56,14 +60,12 @@ namespace Aurora::Parse
|
||||
status = base64_encode(reinterpret_cast<const unsigned char*>(input.ptr), input.length, &encoded[0], &outLength);
|
||||
}
|
||||
|
||||
encoded.resize(outLength);
|
||||
return status == CRYPT_OK;
|
||||
}
|
||||
catch (...)
|
||||
if (!AuTryResize(encoded, outLength))
|
||||
{
|
||||
AuLogWarn("Encoding error");
|
||||
Debug::PrintError();
|
||||
SysPushErrorMem();
|
||||
return false;
|
||||
}
|
||||
|
||||
return status == CRYPT_OK;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user