[+] 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:
Reece Wilson 2022-04-10 16:40:49 +01:00
parent cf219eabaa
commit 3defb1bb14
19 changed files with 1041 additions and 424 deletions

View File

@ -1,146 +1,146 @@
#pragma once
namespace Aurora::Async
{
#pragma region EASE_OF_READING
struct BasicWorkStdFunc : IWorkItemHandler
{
AuFunction<void()> callback;
AuFunction<void()> shutdown; // error
BasicWorkStdFunc(AuFunction<void()> &&callback, AuFunction<void()> &&shutdown) : callback(std::move(callback)), shutdown(std::move(shutdown))
{}
BasicWorkStdFunc(AuFunction<void()> &&callback) : callback(std::move(callback))
{}
BasicWorkStdFunc(const AuFunction<void()> &callback) : callback(callback)
{}
BasicWorkStdFunc(const AuFunction<void()> &callback, const AuFunction<void()> &shutdown) : callback(callback), shutdown(shutdown)
{}
private:
#if !defined(_CPPSHARP)
void DispatchFrame(ProcessInfo &info) override
{
try
{
callback();
}
catch (...)
{
Debug::PrintError();
}
}
void Shutdown() override
{
try
{
if (shutdown)
{
shutdown();
}
}
catch (...)
{
Debug::PrintError();
}
}
#endif
};
/// @hideinitializer
template<typename Frame_t = AuFunction<void()>, typename Cleanup_t = AuFunction<void()>>
struct WorkItemCallable : IWorkItemHandler
{
Frame_t frame;
Cleanup_t cleanup;
private:
void DispatchFrame(ProcessInfo &info) override
{
if constexpr (AuIsBaseOfTemplate<AuFunction, Frame_t>::value)
{
if (!frame)
{
info.type = IWorkItemHandler::EProcessNext::eFinished;
return;
}
}
frame();
info.type = IWorkItemHandler::EProcessNext::eFinished;
}
void Shutdown() override
{
if constexpr (AuIsBaseOfTemplate<AuFunction, Cleanup_t>::value)
{
if (!cleanup)
{
return;
}
}
cleanup();
}
};
#define ASYNC_ERROR(exp) { if constexpr (AuIsSame_v<T, bool>) { SysPushErrorGen(exp); return {}; } else { throw AuString(exp); } }
#define ASYNC_FINISH { if constexpr (AuIsSame_v<T, bool>) { return true; } }
template<typename T = void, typename... Args, AU_TEMPLATE_ENABLE_WHEN(AuIsSame_v<T, bool> || AuIsVoid_v<T>)>
static AuFunction<T(Args&&...)> TranslateAsyncFunctionToDispatcherWithThread(WorkerId_t id, AuFunction<void(Args...)> func)
{
if (!func) return {};
return [=](Args&&... in) -> T
{
auto work = AuMakeShared<BasicWorkStdFunc>([=]() -> void {
func(in...);
});
if (!work) ASYNC_ERROR("can't dispatch async call. out of memory");
auto workItem = NewWorkItem(id, work);
if (!workItem) ASYNC_ERROR("can't dispatch async call. out of memory");
workItem->Dispatch();
ASYNC_FINISH;
};
}
/// Async app only
template<typename T = void, typename... Args, AU_TEMPLATE_ENABLE_WHEN(AuIsSame_v<T, bool> || AuIsVoid_v<T>)>
static AuFunction<T(Args&&...)> TranslateAsyncFunctionToDispatcher(AuFunction<void(Args...)> func)
{
return TranslateAsyncFunctionToDispatcherWithThread(GetAsyncApp()->GetCurrentThread(), func);
}
/// Async app only
template<typename B = void, typename T, typename... Args, AU_TEMPLATE_ENABLE_WHEN(AuIsSame_v<T, bool> || AuIsVoid_v<T>)>
static AuFunction<T(AuFunction<void(const B&)>, Args...)> TranslateAsyncReturnableFunctionToDispatcherWithThread(WorkerId_t id, AuFunction<B(Args...)> func)
{
return [=](AuFunction<T(const B&)> callback, Args... in) -> T
{
auto work = AuMakeShared<WorkPairImpl<AVoid, B>>();
if (!work) ASYNC_ERROR("can't dispatch async call; out of memory");
work.task.onProcess = [=](const AVoid &) -> B
{
if (!func) return B{};
return func(in...);
};
work.callback.onSuccess = [=](const AVoid &, const B &ret)
{
callback(ret);
};
auto workItem = NewWorkItem(id, work);
if (!workItem) ASYNC_ERROR("can't dispatch async call; out of memory");
workItem->Dispatch();
ASYNC_FINISH;
};
}
#undef ASYNC_ERROR
#undef ASYNC_FINISH
#pragma endregion EASE_OF_READING
#pragma once
namespace Aurora::Async
{
#pragma region EASE_OF_READING
struct BasicWorkStdFunc : IWorkItemHandler
{
AuFunction<void()> callback;
AuFunction<void()> shutdown; // error
BasicWorkStdFunc(AuFunction<void()> &&callback, AuFunction<void()> &&shutdown) : callback(std::move(callback)), shutdown(std::move(shutdown))
{}
BasicWorkStdFunc(AuFunction<void()> &&callback) : callback(std::move(callback))
{}
BasicWorkStdFunc(const AuFunction<void()> &callback) : callback(callback)
{}
BasicWorkStdFunc(const AuFunction<void()> &callback, const AuFunction<void()> &shutdown) : callback(callback), shutdown(shutdown)
{}
private:
#if !defined(_CPPSHARP)
void DispatchFrame(ProcessInfo &info) override
{
try
{
callback();
}
catch (...)
{
Debug::PrintError();
}
}
void Shutdown() override
{
try
{
if (shutdown)
{
shutdown();
}
}
catch (...)
{
Debug::PrintError();
}
}
#endif
};
/// @hideinitializer
template<typename Frame_t = AuFunction<void()>, typename Cleanup_t = AuFunction<void()>>
struct WorkItemCallable : IWorkItemHandler
{
Frame_t frame;
Cleanup_t cleanup;
private:
void DispatchFrame(ProcessInfo &info) override
{
if constexpr (AuIsBaseOfTemplate<AuFunction, Frame_t>::value)
{
if (!frame)
{
info.type = IWorkItemHandler::EProcessNext::eFinished;
return;
}
}
frame();
info.type = IWorkItemHandler::EProcessNext::eFinished;
}
void Shutdown() override
{
if constexpr (AuIsBaseOfTemplate<AuFunction, Cleanup_t>::value)
{
if (!cleanup)
{
return;
}
}
cleanup();
}
};
#define ASYNC_ERROR(exp) { if constexpr (AuIsSame_v<T, bool>) { SysPushErrorGen(exp); return {}; } else { throw AuString(exp); } }
#define ASYNC_FINISH { if constexpr (AuIsSame_v<T, bool>) { return true; } }
template<typename T = void, typename... Args, AU_TEMPLATE_ENABLE_WHEN(AuIsSame_v<T, bool> || AuIsVoid_v<T>)>
static AuFunction<T(Args&&...)> TranslateAsyncFunctionToDispatcherWithThread(WorkerId_t id, AuFunction<void(Args...)> func)
{
if (!func) return {};
return [=](Args&&... in) -> T
{
auto work = AuMakeShared<BasicWorkStdFunc>([=]() -> void {
func(in...);
});
if (!work) ASYNC_ERROR("can't dispatch async call. out of memory");
auto workItem = NewWorkItem(id, work);
if (!workItem) ASYNC_ERROR("can't dispatch async call. out of memory");
workItem->Dispatch();
ASYNC_FINISH;
};
}
/// Async app only
template<typename T = void, typename... Args, AU_TEMPLATE_ENABLE_WHEN(AuIsSame_v<T, bool> || AuIsVoid_v<T>)>
static AuFunction<T(Args&&...)> TranslateAsyncFunctionToDispatcher(AuFunction<void(Args...)> func)
{
return TranslateAsyncFunctionToDispatcherWithThread(GetAsyncApp()->GetCurrentThread(), func);
}
/// Async app only
template<typename B = void, typename T, typename... Args, AU_TEMPLATE_ENABLE_WHEN(AuIsSame_v<T, bool> || AuIsVoid_v<T>)>
static AuFunction<T(AuFunction<void(const B&)>, Args...)> TranslateAsyncReturnableFunctionToDispatcherWithThread(WorkerId_t id, AuFunction<B(Args...)> func)
{
return [=](AuFunction<T(const B&)> callback, Args... in) -> T
{
auto work = AuMakeShared<WorkPairImpl<AVoid, B>>();
if (!work) ASYNC_ERROR("can't dispatch async call; out of memory");
work.task.onProcess = [=](const AVoid &) -> B
{
if (!func) return B{};
return func(in...);
};
work.callback.onSuccess = [=](const AVoid &, const B &ret)
{
callback(ret);
};
auto workItem = NewWorkItem(id, work);
if (!workItem) ASYNC_ERROR("can't dispatch async call; out of memory");
workItem->Dispatch();
ASYNC_FINISH;
};
}
#undef ASYNC_ERROR
#undef ASYNC_FINISH
#pragma endregion EASE_OF_READING
}

View File

@ -19,17 +19,41 @@ namespace Aurora::IO::FS
AuSPtr<UserWatchData> userData;
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);

View File

@ -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

View File

@ -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;

View File

@ -76,9 +76,9 @@ namespace Aurora::HWInfo
bool bIsHyperThreaded {};
for (auto &[threadId, coreStr] : cpuThreads)
{
auto cpuId = CpuBitId(threadId);
auto cpuId = CpuBitId(threadId);
auto coreID = ReadUInt(kBasePath + coreStr + "/topology/core_id");
auto coreID = ReadUInt(kBasePath + coreStr + "/topology/core_id");
auto cpuList = ReadString(kBasePath + coreStr +"/topology/core_cpus_list");
auto isHVCore = AuStringContains(cpuList, ",");
@ -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();
}
}

View File

@ -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)

View File

@ -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.

View File

@ -90,7 +90,11 @@ namespace Aurora::IO::FS
((!isFile) && (end)))
{
auto subpath = end ? cpath : AuString(cpath.begin(), cpath.begin() + i);
if (subpath.empty())
{
continue;
}
if (DirExists(subpath))
{
continue;

View File

@ -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
View 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
View 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};
};
}

View File

@ -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,14 +240,24 @@ 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)
{
return false;
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))
{
CloseHandle(hFile);
@ -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
@ -196,7 +285,7 @@ namespace Aurora::IO::FS
bool NTWatchObject::Init(const AuString &usrStr)
{
AuCtorCode_t code;
this->strBaseDir = AuTryConstruct<AuString>(code, usrStr);
if (!code)
{
@ -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_, {});
}

View File

@ -1,119 +1,119 @@
/***
Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: LSEvent.Linux.cpp
Date: 2022-4-4
Author: Reece
***/
#include <Source/RuntimeInternal.hpp>
#include "LSEvent.hpp"
#include "LSFromFdNonblocking.hpp"
namespace Aurora::Loop
{
LSEvent::LSEvent(bool triggered, bool atomicRelease, bool permitMultipleTriggers) : atomicRelease_(atomicRelease)
{
Init(triggered);
}
LSEvent::~LSEvent()
{
if ((this->handle != 0) &&
(this->handle != -1))
{
close(this->handle);
}
}
bool LSEvent::OnTrigger(AuUInt handle)
{
AuUInt64 oldSemaphoreValue {};
if (!this->atomicRelease_)
{
return true;
}
return read(this->handle, &oldSemaphoreValue, sizeof(oldSemaphoreValue)) == 8;
}
void LSEvent::Init(bool init)
{
handle = eventfd(init ? 1 : 0, EFD_NONBLOCK);
}
bool LSEvent::Set()
{
AuUInt64 plsNoOverflow {1};
if (write(this->handle, &plsNoOverflow, sizeof(plsNoOverflow)) != 8)
{
// todo push error
return false;
}
return true;
}
bool LSEvent::Reset()
{
AuUInt64 oldSemaphoreValue {0};
// RETURN VALUE IS USELESS - Failure is to be expected
read(this->handle, &oldSemaphoreValue, sizeof(oldSemaphoreValue));
return true;
}
bool LSEvent::IsSignaled()
{
return IsSignaledFromNonblockingImpl(this, this, &LSEvent::IsSignaledNonblocking);
}
bool LSEvent::IsSignaledNonblocking()
{
if (!this->atomicRelease_)
{
fd_set set;
struct timeval tv {};
FD_ZERO(&set);
FD_SET(this->handle, &set);
auto active = select(this->handle + 1, &set, NULL, NULL, &tv);
if (active == -1)
{
// todo push error
return false;
}
return active == 1;
}
else
{
AuUInt64 oldSemaphoreValue {};
return read(this->handle, &oldSemaphoreValue, sizeof(oldSemaphoreValue)) == 8;
}
}
ELoopSource LSEvent::GetType()
{
return ELoopSource::eSourceEvent;
}
AUKN_SYM AuSPtr<ILSEvent> NewLSEvent(bool triggered, bool atomicRelease, bool permitMultipleTriggers)
{
auto event = AuMakeShared<LSEvent>(triggered, atomicRelease, permitMultipleTriggers);
if (!event)
{
return {};
}
if (!event->HasValidHandle())
{
return {};
}
return event;
}
/***
Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: LSEvent.Linux.cpp
Date: 2022-4-4
Author: Reece
***/
#include <Source/RuntimeInternal.hpp>
#include "LSEvent.hpp"
#include "LSFromFdNonblocking.hpp"
namespace Aurora::Loop
{
LSEvent::LSEvent(bool triggered, bool atomicRelease, bool permitMultipleTriggers) : atomicRelease_(atomicRelease)
{
Init(triggered);
}
LSEvent::~LSEvent()
{
if ((this->handle != 0) &&
(this->handle != -1))
{
close(this->handle);
}
}
bool LSEvent::OnTrigger(AuUInt handle)
{
AuUInt64 oldSemaphoreValue {};
if (!this->atomicRelease_)
{
return true;
}
return read(this->handle, &oldSemaphoreValue, sizeof(oldSemaphoreValue)) == 8;
}
void LSEvent::Init(bool init)
{
handle = eventfd(init ? 1 : 0, EFD_NONBLOCK);
}
bool LSEvent::Set()
{
AuUInt64 plsNoOverflow {1};
if (write(this->handle, &plsNoOverflow, sizeof(plsNoOverflow)) != 8)
{
// todo push error
return false;
}
return true;
}
bool LSEvent::Reset()
{
AuUInt64 oldSemaphoreValue {0};
// RETURN VALUE IS USELESS - Failure is to be expected
read(this->handle, &oldSemaphoreValue, sizeof(oldSemaphoreValue));
return true;
}
bool LSEvent::IsSignaled()
{
return IsSignaledFromNonblockingImpl(this, this, &LSEvent::IsSignaledNonblocking);
}
bool LSEvent::IsSignaledNonblocking()
{
if (!this->atomicRelease_)
{
fd_set set;
struct timeval tv {};
FD_ZERO(&set);
FD_SET(this->handle, &set);
auto active = select(this->handle + 1, &set, NULL, NULL, &tv);
if (active == -1)
{
// todo push error
return false;
}
return active == 1;
}
else
{
AuUInt64 oldSemaphoreValue {};
return read(this->handle, &oldSemaphoreValue, sizeof(oldSemaphoreValue)) == 8;
}
}
ELoopSource LSEvent::GetType()
{
return ELoopSource::eSourceEvent;
}
AUKN_SYM AuSPtr<ILSEvent> NewLSEvent(bool triggered, bool atomicRelease, bool permitMultipleTriggers)
{
auto event = AuMakeShared<LSEvent>(triggered, atomicRelease, permitMultipleTriggers);
if (!event)
{
return {};
}
if (!event->HasValidHandle())
{
return {};
}
return event;
}
}

View File

@ -1,31 +1,31 @@
/***
Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: LSEvent.Linux.hpp
Date: 2022-4-4
Author: Reece
***/
#pragma once
#include "LSHandle.hpp"
namespace Aurora::Loop
{
struct LSEvent : public ILSEvent, public LSHandle
{
LSEvent(bool triggered, bool atomicRelease, bool permitMultipleTriggers);
~LSEvent();
bool Set() override;
bool Reset() override;
virtual bool OnTrigger(AuUInt handle) override;
bool IsSignaled() override;
virtual ELoopSource GetType() override;
private:
void Init(bool init);
bool IsSignaledNonblocking();
bool atomicRelease_;
};
/***
Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: LSEvent.Linux.hpp
Date: 2022-4-4
Author: Reece
***/
#pragma once
#include "LSHandle.hpp"
namespace Aurora::Loop
{
struct LSEvent : public ILSEvent, public LSHandle
{
LSEvent(bool triggered, bool atomicRelease, bool permitMultipleTriggers);
~LSEvent();
bool Set() override;
bool Reset() override;
virtual bool OnTrigger(AuUInt handle) override;
bool IsSignaled() override;
virtual ELoopSource GetType() override;
private:
void Init(bool init);
bool IsSignaledNonblocking();
bool atomicRelease_;
};
}

View File

@ -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
// anything.
// 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;

View File

@ -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);

View File

@ -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);
if (read != -1)
{
maxFd = read + 1;
FD_SET(read, &readSet);
}
auto active = select(AuMax(read, write) + 1, read != -1 ? &readSet : NULL, write != -1 ? &writeSet : NULL, NULL, &tv);
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

View File

@ -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;

View File

@ -14,56 +14,58 @@ 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());
int status;
if (url)
{
status = base64url_decode(in.data(), in.size(), reinterpret_cast<unsigned char *>(&decoded[0]), &length);
}
else
{
status = base64_decode(in.data(), in.size(), reinterpret_cast<unsigned char *>(&decoded[0]), &length);
}
decoded.resize(length);
return status == CRYPT_OK;
}
catch (...)
{
AuLogWarn("Decoding error: {}", in);
Debug::PrintError();
SysPushErrorMem();
return false;
}
int status;
if (url)
{
status = base64url_decode(in.data(), in.size(), reinterpret_cast<unsigned char *>(&decoded[0]), &length);
}
else
{
status = base64_decode(in.data(), in.size(), reinterpret_cast<unsigned char *>(&decoded[0]), &length);
}
if (!AuTryResize(decoded, length))
{
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
{
encoded.resize(outLength);
int status;
if (url)
{
status = base64url_encode(reinterpret_cast<const unsigned char*>(input.ptr), input.length, &encoded[0], &outLength);
}
else
{
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;
}
int status;
if (url)
{
status = base64url_encode(reinterpret_cast<const unsigned char*>(input.ptr), input.length, &encoded[0], &outLength);
}
else
{
status = base64_encode(reinterpret_cast<const unsigned char*>(input.ptr), input.length, &encoded[0], &outLength);
}
if (!AuTryResize(encoded, outLength))
{
SysPushErrorMem();
return false;
}
return status == CRYPT_OK;
}
}