diff options
| author | Per Larsson <[email protected]> | 2021-12-14 12:34:47 +0100 |
|---|---|---|
| committer | Per Larsson <[email protected]> | 2021-12-14 12:34:47 +0100 |
| commit | b6c6568e1618f10d2160d836b65e35586e3c740f (patch) | |
| tree | f6a929cf918850bbba87d0ee67cd3482b2d50e24 /zencore | |
| parent | Fixed bug in z$ service returning partial cache records and enable small obje... (diff) | |
| parent | Partial revert b363c5b (diff) | |
| download | zen-b6c6568e1618f10d2160d836b65e35586e3c740f.tar.xz zen-b6c6568e1618f10d2160d836b65e35586e3c740f.zip | |
Merged main.
Diffstat (limited to 'zencore')
25 files changed, 1515 insertions, 223 deletions
diff --git a/zencore/blake3.cpp b/zencore/blake3.cpp index 8f8952271..b2019d4e2 100644 --- a/zencore/blake3.cpp +++ b/zencore/blake3.cpp @@ -8,7 +8,9 @@ #include <zencore/zencore.h> #include "../thirdparty/BLAKE3/c/blake3.h" -#pragma comment(lib, "blake3.lib") +#if ZEN_PLATFORM_WINDOWS +# pragma comment(lib, "blake3.lib") +#endif #include <string.h> diff --git a/zencore/compactbinary.cpp b/zencore/compactbinary.cpp index c6bf38b04..494b45581 100644 --- a/zencore/compactbinary.cpp +++ b/zencore/compactbinary.cpp @@ -18,6 +18,8 @@ #if ZEN_PLATFORM_WINDOWS # include <zencore/windows.h> +#else +# include <time.h> #endif #if ZEN_WITH_TESTS @@ -46,15 +48,31 @@ IsLeapYear(int Year) return false; } +static constexpr uint64_t +GetPlatformToDateTimeBiasInSeconds() +{ +#if ZEN_PLATFORM_WINDOWS + const uint64_t PlatformEpochYear = 1601; +#else + const uint64_t PlatformEpochYear = 1970; +#endif + const uint64_t DateTimeEpochYear = 1; + return uint64_t(double(PlatformEpochYear - DateTimeEpochYear) * 365.2425) * 86400; +} + DateTime DateTime::Now() { + static const uint64_t EpochBias = GetPlatformToDateTimeBiasInSeconds(); + static const uint64_t SecsTo100nsTicks = int64_t(10e9 / 100); + #if ZEN_PLATFORM_WINDOWS FILETIME SysTime; GetSystemTimeAsFileTime(&SysTime); - return DateTime{504911232000000000ull + (uint64_t(SysTime.dwHighDateTime) << 32) | SysTime.dwLowDateTime}; + return DateTime{(EpochBias * SecsTo100nsTicks) + (uint64_t(SysTime.dwHighDateTime) << 32) | SysTime.dwLowDateTime}; #else -# error Needs implementation + int64_t SecondsSinceUnixEpoch = time(nullptr); + return DateTime{(EpochBias + SecondsSinceUnixEpoch) * SecsTo100nsTicks}; #endif } @@ -82,88 +100,6 @@ DateTime::Set(int Year, int Month, int Day, int Hour, int Minute, int Second, in Second * TimeSpan::TicksPerSecond + MilliSecond * TimeSpan::TicksPerMillisecond; } -void -TimeSpan::Set(int Days, int Hours, int Minutes, int Seconds, int FractionNano) -{ - int64_t TotalTicks = 0; - - TotalTicks += Days * TicksPerDay; - TotalTicks += Hours * TicksPerHour; - TotalTicks += Minutes * TicksPerMinute; - TotalTicks += Seconds * TicksPerSecond; - TotalTicks += FractionNano / NanosecondsPerTick; - - Ticks = TotalTicks; -} - -std::string -TimeSpan::ToString(const char* Format) const -{ - using namespace fmt::literals; - - StringBuilder<128> Result; - - Result.Append((Ticks < 0) ? '-' : '+'); - - while (*Format != '\0') - { - if ((*Format == '%') && (*++Format != '\0')) - { - switch (*Format) - { - case 'd': - Result.Append("{}"_format(GetDays())); - break; - case 'D': - Result.Append("{:08}"_format(GetDays())); - break; - case 'h': - Result.Append("{:02}"_format(GetHours())); - break; - case 'm': - Result.Append("{:02}"_format(GetMinutes())); - break; - case 's': - Result.Append("{:02}"_format(GetSeconds())); - break; - case 'f': - Result.Append("{:03}"_format(GetFractionMilli())); - break; - case 'u': - Result.Append("{:06}"_format(GetFractionMicro())); - break; - case 't': - Result.Append("{:07}"_format(GetFractionTicks())); - break; - case 'n': - Result.Append("{:09}"_format(GetFractionNano())); - break; - default: - Result.Append(*Format); - } - } - else - { - Result.Append(*Format); - } - - ++Format; - } - - return Result.ToString(); -} - -std::string -TimeSpan::ToString() const -{ - if (GetDays() == 0) - { - return ToString("%h:%m:%s.%f"); - } - - return ToString("%d.%h:%m:%s.%f"); -} - int DateTime::GetYear() const { @@ -329,6 +265,88 @@ DateTime::ToIso8601() const return ToString("%Y-%m-%dT%H:%M:%S.%sZ"); } +void +TimeSpan::Set(int Days, int Hours, int Minutes, int Seconds, int FractionNano) +{ + int64_t TotalTicks = 0; + + TotalTicks += Days * TicksPerDay; + TotalTicks += Hours * TicksPerHour; + TotalTicks += Minutes * TicksPerMinute; + TotalTicks += Seconds * TicksPerSecond; + TotalTicks += FractionNano / NanosecondsPerTick; + + Ticks = TotalTicks; +} + +std::string +TimeSpan::ToString(const char* Format) const +{ + using namespace fmt::literals; + + StringBuilder<128> Result; + + Result.Append((int64_t(Ticks) < 0) ? '-' : '+'); + + while (*Format != '\0') + { + if ((*Format == '%') && (*++Format != '\0')) + { + switch (*Format) + { + case 'd': + Result.Append("{}"_format(GetDays())); + break; + case 'D': + Result.Append("{:08}"_format(GetDays())); + break; + case 'h': + Result.Append("{:02}"_format(GetHours())); + break; + case 'm': + Result.Append("{:02}"_format(GetMinutes())); + break; + case 's': + Result.Append("{:02}"_format(GetSeconds())); + break; + case 'f': + Result.Append("{:03}"_format(GetFractionMilli())); + break; + case 'u': + Result.Append("{:06}"_format(GetFractionMicro())); + break; + case 't': + Result.Append("{:07}"_format(GetFractionTicks())); + break; + case 'n': + Result.Append("{:09}"_format(GetFractionNano())); + break; + default: + Result.Append(*Format); + } + } + else + { + Result.Append(*Format); + } + + ++Format; + } + + return Result.ToString(); +} + +std::string +TimeSpan::ToString() const +{ + if (GetDays() == 0) + { + return ToString("%h:%m:%s.%f"); + } + + return ToString("%d.%h:%m:%s.%f"); +} + StringBuilderBase& Guid::ToString(StringBuilderBase& Sb) const { diff --git a/zencore/compactbinarybuilder.cpp b/zencore/compactbinarybuilder.cpp index fa5b6a69b..5111504e1 100644 --- a/zencore/compactbinarybuilder.cpp +++ b/zencore/compactbinarybuilder.cpp @@ -985,7 +985,12 @@ TEST_CASE("usonbuilder.string") SUBCASE("Non-ASCII String") { +# if ZEN_SIZEOF_WCHAR_T == 2 wchar_t Value[2] = {0xd83d, 0xde00}; +# else + wchar_t Value[1] = {0x1f600}; +# endif + Writer.AddString("\xf0\x9f\x98\x80"sv); Writer.AddString(std::wstring_view(Value, ZEN_ARRAY_COUNT(Value))); CbFieldIterator Fields = Writer.Save(); diff --git a/zencore/filesystem.cpp b/zencore/filesystem.cpp index 53e6f1f38..f850f1a80 100644 --- a/zencore/filesystem.cpp +++ b/zencore/filesystem.cpp @@ -174,7 +174,8 @@ CleanDirectory(const wchar_t* DirPath) bool CreateDirectories(const std::filesystem::path& Dir) { - return std::filesystem::create_directories(Dir); + std::error_code ErrorCode; + return std::filesystem::create_directories(Dir, ErrorCode); } bool @@ -244,6 +245,7 @@ SupportsBlockRefCounting(std::filesystem::path Path) return true; #else + ZEN_UNUSED(Path); return false; #endif // ZEN_PLATFORM_WINDOWS } @@ -406,16 +408,47 @@ CloneFile(std::filesystem::path FromPath, std::filesystem::path ToPath) const bool AllOk = (TRUE == SetFileInformationByHandle(TargetFile, FileDispositionInfo, &FileDisposition, sizeof FileDisposition)); return AllOk; -#else +#elif ZEN_PLATFORM_LINUX +# if 0 + struct ScopedFd + { + ~ScopedFd() { close(Fd); } + int Fd; + }; + + // The 'from' file + int FromFd = open(FromPath.c_str(), O_RDONLY|O_CLOEXEC); + if (FromFd < 0) + { + return false; + } + ScopedFd $From = { FromFd }; + + // The 'to' file + int ToFd = open(ToPath.c_str(), O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0666); + if (ToFd < 0) + { + return false; + } + ScopedFd $To = { FromFd }; + + ioctl(ToFd, FICLONE, FromFd); + + return false; +# endif // 0 + ZEN_UNUSED(FromPath, ToPath); ZEN_ERROR("CloneFile() is not implemented on this platform"); return false; +#elif ZEN_PLATFORM_MAC + /* clonefile() syscall if APFS */ +# error not implemented + return false; #endif // ZEN_PLATFORM_WINDOWS } bool CopyFile(std::filesystem::path FromPath, std::filesystem::path ToPath, const CopyFileOptions& Options) { -#if ZEN_PLATFORM_WINDOWS bool Success = false; if (Options.EnableClone) @@ -433,6 +466,7 @@ CopyFile(std::filesystem::path FromPath, std::filesystem::path ToPath, const Cop return false; } +#if ZEN_PLATFORM_WINDOWS BOOL CancelFlag = FALSE; Success = !!::CopyFileExW(FromPath.c_str(), ToPath.c_str(), @@ -440,17 +474,58 @@ CopyFile(std::filesystem::path FromPath, std::filesystem::path ToPath, const Cop /* lpData */ nullptr, &CancelFlag, /* dwCopyFlags */ 0); +#else + using namespace fmt::literals; - if (!Success) + struct ScopedFd + { + ~ScopedFd() { close(Fd); } + int Fd; + }; + + // From file + int FromFd = open(FromPath.c_str(), O_RDONLY | O_CLOEXEC); + if (FromFd < 0) { - throw std::system_error(std::error_code(::GetLastError(), std::system_category()), "file copy failed"); + ThrowLastError("failed to open file {}"_format(FromPath)); } + ScopedFd $From = {FromFd}; - return Success; -#else - ZEN_ERROR("CopyFile() is not implemented on this platform"); - return false; + // To file + int ToFd = open(ToPath.c_str(), O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0644); + if (ToFd < 0) + { + ThrowLastError("failed to create file {}"_format(ToPath)); + } + ScopedFd $To = {ToFd}; + + // Copy impl + static const size_t BufferSize = 64 << 10; + void* Buffer = malloc(BufferSize); + while (true) + { + int BytesRead = read(FromFd, Buffer, BufferSize); + if (BytesRead <= 0) + { + Success = (BytesRead == 0); + break; + } + + if (write(ToFd, Buffer, BytesRead) != BufferSize) + { + Success = false; + break; + } + } + free(Buffer); #endif // ZEN_PLATFORM_WINDOWS + + if (!Success) + { + ThrowLastError("file copy failed"sv); + } + + return true; } void @@ -474,11 +549,12 @@ WriteFile(std::filesystem::path Path, const IoBuffer* const* Data, size_t Buffer } #else - int Fd = open(Path.c_str(), O_WRONLY); + int OpenFlags = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC; + int Fd = open(Path.c_str(), OpenFlags, 0666); if (Fd < 0) { zen::CreateDirectories(Path.parent_path()); - Fd = open(Path.c_str(), O_WRONLY); + Fd = open(Path.c_str(), OpenFlags, 0666); } if (Fd < 0) @@ -505,7 +581,7 @@ WriteFile(std::filesystem::path Path, const IoBuffer* const* Data, size_t Buffer ThrowSystemException(hRes, "File write failed for '{}'"_format(Path).c_str()); } #else - if (write(Fd, DataPtr, WriteSize) != WriteSize) + if (write(Fd, DataPtr, WriteSize) != int64_t(WriteSize)) { ThrowLastError("File write failed for '{}'"_format(Path)); } @@ -569,10 +645,12 @@ ReadFile(std::filesystem::path Path) FileSizeBytes = FileSize.EndOfFile.QuadPart; Handle = FromFile.Detach(); #else - int Fd = open(Path.c_str(), O_RDONLY); + int Fd = open(Path.c_str(), O_RDONLY | O_CLOEXEC); if (Fd < 0) { - return FileContents{.ErrorCode = std::error_code(zen::GetLastError(), std::system_category())}; + FileContents Ret; + Ret.ErrorCode = std::error_code(zen::GetLastError(), std::system_category()); + return Ret; } static_assert(sizeof(decltype(stat::st_size)) == sizeof(uint64_t), "fstat() doesn't support large files"); @@ -616,16 +694,57 @@ ScanFile(std::filesystem::path Path, const uint64_t ChunkSize, std::function<voi ProcessFunc(ReadBuffer.data(), dwBytesRead); } +#else + int Fd = open(Path.c_str(), O_RDONLY | O_CLOEXEC); + if (Fd < 0) + { + return false; + } + + bool Success = true; + + void* Buffer = malloc(ChunkSize); + while (true) + { + int BytesRead = read(Fd, Buffer, ChunkSize); + if (BytesRead < 0) + { + Success = false; + break; + } + + if (BytesRead == 0) + { + break; + } + + ProcessFunc(Buffer, BytesRead); + } + + free(Buffer); + close(Fd); + + if (!Success) + { + ThrowLastError("file scan failed"); + } +#endif // ZEN_PLATFORM_WINDOWS return true; +} + +void +PathToUtf8(const std::filesystem::path& Path, StringBuilderBase& Out) +{ +#if ZEN_PLATFORM_WINDOWS + WideToUtf8(Path.native().c_str(), Out); #else - ZEN_ERROR("ScanFile() is not implemented on this platform"); - return false; -#endif // ZEN_PLATFORM_WINDOWS + Out << Path.c_str(); +#endif } std::string -ToUtf8(const std::filesystem::path& Path) +PathToUtf8(const std::filesystem::path& Path) { #if ZEN_PLATFORM_WINDOWS return WideToUtf8(Path.native().c_str()); @@ -750,7 +869,7 @@ FileSystemTraversal::TraverseFileSystem(const std::filesystem::path& RootDir, Tr ThrowLastError("Failed to open directory for traversal: {}"_format(RootDir.c_str())); } - for (struct dirent* Entry; Entry = readdir(Dir);) + for (struct dirent* Entry; (Entry = readdir(Dir));) { const char* FileName = Entry->d_name; @@ -806,14 +925,15 @@ PathFromHandle(void* NativeHandle) return FullPath; #elif ZEN_PLATFORM_LINUX - char Buffer[256]; - sprintf(Buffer, "/proc/%d/fd/%d", getpid(), int(uintptr_t(NativeHandle))); - ssize_t BytesRead = readlink(Buffer, Buffer, sizeof(Buffer) - 1); + char Link[256]; + char Path[64]; + sprintf(Path, "/proc/self/fd/%d", int(uintptr_t(NativeHandle))); + ssize_t BytesRead = readlink(Path, Link, sizeof(Link) - 1); if (BytesRead <= 0) return std::filesystem::path(); - Buffer[BytesRead] = '\0'; - return Buffer; + Link[BytesRead] = '\0'; + return Link; #else # error Unimplemented platform #endif // ZEN_PLATFORM_WINDOWS @@ -828,14 +948,13 @@ GetRunningExecutablePath() return {std::wstring_view(ExePath, PathLength)}; #elif ZEN_PLATFORM_LINUX - char Buffer[256]; - sprintf(Buffer, "/proc/%d/exe", getpid()); - ssize_t BytesRead = readlink(Buffer, Buffer, sizeof(Buffer) - 1); + char Link[256]; + ssize_t BytesRead = readlink("/proc/self/exe", Link, sizeof(Link) - 1); if (BytesRead < 0) return {}; - Buffer[BytesRead] = '\0'; - return Buffer; + Link[BytesRead] = '\0'; + return Link; #else # error Unimplemented platform #endif // ZEN_PLATFORM_WINDOWS @@ -859,7 +978,7 @@ TEST_CASE("filesystem") // GetExePath -- this is not a great test as it's so dependent on where the this code gets linked in path BinPath = GetRunningExecutablePath(); - const bool ExpectedExe = ToUtf8(BinPath.stem().native()).ends_with("-test"sv) || BinPath.stem() == "zenserver"; + const bool ExpectedExe = PathToUtf8(BinPath.stem().native()).ends_with("-test"sv) || BinPath.stem() == "zenserver"; CHECK(ExpectedExe); CHECK(is_regular_file(BinPath)); @@ -869,7 +988,7 @@ TEST_CASE("filesystem") Handle = CreateFileW(BinPath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr); CHECK(Handle != INVALID_HANDLE_VALUE); # else - int Fd = open(BinPath.c_str(), O_RDONLY); + int Fd = open(BinPath.c_str(), O_RDONLY | O_CLOEXEC); CHECK(Fd >= 0); Handle = (void*)uintptr_t(Fd); # endif @@ -900,6 +1019,87 @@ TEST_CASE("filesystem") FileSystemTraversal().TraverseFileSystem(BinPath.parent_path().parent_path(), Visitor); CHECK(Visitor.bFoundExpected); + + // Scan/read file + FileContents BinRead = ReadFile(BinPath); + std::vector<uint8_t> BinScan; + ScanFile(BinPath, 16 << 10, [&](const void* Data, size_t Size) { + const auto* Ptr = (uint8_t*)Data; + BinScan.insert(BinScan.end(), Ptr, Ptr + Size); + }); + CHECK_EQ(BinRead.Data.size(), 1); + CHECK_EQ(BinScan.size(), BinRead.Data[0].GetSize()); +} + +TEST_CASE("WriteFile") +{ + std::filesystem::path TempFile = GetRunningExecutablePath().parent_path(); + TempFile /= "write_file_test"; + + uint64_t Magics[] = { + 0x0'a9e'a9e'a9e'a9e'a9e, + 0x0'493'493'493'493'493, + }; + + struct + { + const void* Data; + size_t Size; + } MagicTests[] = { + { + Magics, + sizeof(Magics), + }, + { + Magics + 1, + sizeof(Magics[0]), + }, + }; + for (auto& MagicTest : MagicTests) + { + WriteFile(TempFile, IoBuffer(IoBuffer::Wrap, MagicTest.Data, MagicTest.Size)); + + FileContents MagicsReadback = ReadFile(TempFile); + CHECK_EQ(MagicsReadback.Data.size(), 1); + CHECK_EQ(MagicsReadback.Data[0].GetSize(), MagicTest.Size); + CHECK_EQ(memcmp(MagicTest.Data, MagicsReadback.Data[0].Data(), MagicTest.Size), 0); + } + + std::filesystem::remove(TempFile); +} + +TEST_CASE("PathBuilder") +{ +# if ZEN_PLATFORM_WINDOWS + const char* foo_bar = "/foo\\bar"; +# else + const char* foo_bar = "/foo/bar"; +# endif + + ExtendablePathBuilder<32> Path; + for (const char* Prefix : {"/foo", "/foo/"}) + { + Path.Reset(); + Path.Append(Prefix); + Path /= "bar"; + CHECK(Path.ToPath() == foo_bar); + } + + using fspath = std::filesystem::path; + + Path.Reset(); + Path.Append(fspath("/foo/")); + Path /= (fspath("bar")); + CHECK(Path.ToPath() == foo_bar); + +# if ZEN_PLATFORM_WINDOWS + Path.Reset(); + Path.Append(fspath(L"/\u0119oo/")); + Path /= L"bar"; + printf("%ls\n", Path.ToPath().c_str()); + CHECK(Path.ToView() == L"/\u0119oo/bar"); + CHECK(Path.ToPath() == L"\\\u0119oo\\bar"); +# endif } #endif diff --git a/zencore/include/zencore/blockingqueue.h b/zencore/include/zencore/blockingqueue.h index 277095689..f92df5a54 100644 --- a/zencore/include/zencore/blockingqueue.h +++ b/zencore/include/zencore/blockingqueue.h @@ -3,6 +3,7 @@ #pragma once #include <atomic> +#include <condition_variable> #include <deque> #include <mutex> diff --git a/zencore/include/zencore/compactbinary.h b/zencore/include/zencore/compactbinary.h index 06331c510..66fa3065d 100644 --- a/zencore/include/zencore/compactbinary.h +++ b/zencore/include/zencore/compactbinary.h @@ -63,7 +63,7 @@ public: private: void Set(int Year, int Month, int Day, int Hours, int Minutes, int Seconds, int MilliSecond); - uint64_t Ticks; + uint64_t Ticks; // 1 tick == 0.1us == 100ns, epoch == Jan 1st 0001 }; class TimeSpan diff --git a/zencore/include/zencore/filesystem.h b/zencore/include/zencore/filesystem.h index 155291e67..16c16fc07 100644 --- a/zencore/include/zencore/filesystem.h +++ b/zencore/include/zencore/filesystem.h @@ -5,6 +5,7 @@ #include "zencore.h" #include <zencore/iobuffer.h> +#include <zencore/string.h> #include <filesystem> #include <functional> @@ -55,7 +56,74 @@ struct CopyFileOptions ZENCORE_API bool CopyFile(std::filesystem::path FromPath, std::filesystem::path ToPath, const CopyFileOptions& Options); ZENCORE_API bool SupportsBlockRefCounting(std::filesystem::path Path); -ZENCORE_API std::string ToUtf8(const std::filesystem::path& Path); +ZENCORE_API void PathToUtf8(const std::filesystem::path& Path, StringBuilderBase& Out); +ZENCORE_API std::string PathToUtf8(const std::filesystem::path& Path); + +extern template class StringBuilderImpl<std::filesystem::path::value_type>; + +/** + * Helper class for building paths. Backed by a string builder. + * + */ +class PathBuilderBase : public StringBuilderImpl<std::filesystem::path::value_type> +{ +private: + using Super = StringBuilderImpl<std::filesystem::path::value_type>; + +protected: + using CharType = std::filesystem::path::value_type; + using ViewType = std::basic_string_view<CharType>; + +public: + void Append(const std::filesystem::path& Rhs) { Super::Append(Rhs.c_str()); } + void operator/=(const std::filesystem::path& Rhs) { this->operator/=(Rhs.c_str()); }; + void operator/=(const CharType* Rhs) + { + AppendSeparator(); + Super::Append(Rhs); + } + operator ViewType() const { return ToView(); } + std::basic_string_view<CharType> ToView() const { return std::basic_string_view<CharType>(Data(), Size()); } + std::filesystem::path ToPath() const { return std::filesystem::path(ToView()); } + + std::string ToUtf8() const + { +#if ZEN_PLATFORM_WINDOWS + return WideToUtf8(ToView()); +#else + return std::string(ToView()); +#endif + } + + void AppendSeparator() + { + if (ToView().ends_with(std::filesystem::path::preferred_separator) +#if ZEN_PLATFORM_WINDOWS + || ToView().ends_with('/') +#endif + ) + return; + + Super::Append(std::filesystem::path::preferred_separator); + } +}; + +template<size_t N> +class PathBuilder : public PathBuilderBase +{ +public: + PathBuilder() { Init(m_Buffer, N); } + +private: + PathBuilderBase::CharType m_Buffer[N]; +}; + +template<size_t N> +class ExtendablePathBuilder : public PathBuilder<N> +{ +public: + ExtendablePathBuilder() { this->m_IsExtendable = true; } +}; struct DiskSpace { @@ -85,7 +153,8 @@ class FileSystemTraversal public: struct TreeVisitor { - using path_view = std::basic_string_view<std::filesystem::path::value_type>; + using path_view = std::basic_string_view<std::filesystem::path::value_type>; + using path_string = std::filesystem::path::string_type; virtual void VisitFile(const std::filesystem::path& Parent, const path_view& File, uint64_t FileSize) = 0; diff --git a/zencore/include/zencore/intmath.h b/zencore/include/zencore/intmath.h index 7619e1950..0d0ceff16 100644 --- a/zencore/include/zencore/intmath.h +++ b/zencore/include/zencore/intmath.h @@ -107,7 +107,7 @@ FloorLog2(uint32_t Value) static inline uint32_t CountLeadingZeros(uint32_t Value) { - unsigned long Log2; + unsigned long Log2 = 0; _BitScanReverse64(&Log2, (uint64_t(Value) << 1) | 1); return 32 - Log2; } @@ -115,7 +115,7 @@ CountLeadingZeros(uint32_t Value) static inline uint64_t FloorLog2_64(uint64_t Value) { - unsigned long Log2; + unsigned long Log2 = 0; long Mask = -long(_BitScanReverse64(&Log2, Value) != 0); return Log2 & Mask; } @@ -123,7 +123,7 @@ FloorLog2_64(uint64_t Value) static inline uint64_t CountLeadingZeros64(uint64_t Value) { - unsigned long Log2; + unsigned long Log2 = 0; long Mask = -long(_BitScanReverse64(&Log2, Value) != 0); return ((63 - Log2) & Mask) | (64 & ~Mask); } diff --git a/zencore/include/zencore/iobuffer.h b/zencore/include/zencore/iobuffer.h index 012e9a9df..816179a0a 100644 --- a/zencore/include/zencore/iobuffer.h +++ b/zencore/include/zencore/iobuffer.h @@ -383,10 +383,8 @@ private: class IoBufferBuilder { - using path_char_t = std::filesystem::path::value_type; - public: - ZENCORE_API static IoBuffer MakeFromFile(const path_char_t* FileName, uint64_t Offset = 0, uint64_t Size = ~0ull); + ZENCORE_API static IoBuffer MakeFromFile(const std::filesystem::path& FileName, uint64_t Offset = 0, uint64_t Size = ~0ull); ZENCORE_API static IoBuffer MakeFromTemporaryFile(const std::filesystem::path& FileName); ZENCORE_API static IoBuffer MakeFromFileHandle(void* FileHandle, uint64_t Offset = 0, uint64_t Size = ~0ull); ZENCORE_API static IoBuffer ReadFromFileMaybe(IoBuffer& InBuffer); diff --git a/zencore/include/zencore/logging.h b/zencore/include/zencore/logging.h index 0b080cb9d..468e5d6e2 100644 --- a/zencore/include/zencore/logging.h +++ b/zencore/include/zencore/logging.h @@ -82,9 +82,9 @@ using zen::Log; Log().critical(fmtstr##sv, ##__VA_ARGS__); \ } while (false) -#define ZEN_CONSOLE(fmtstr, ...) \ - do \ - { \ - using namespace std::literals; \ - ConsoleLog().info(fmtstr##sv, __VA_ARGS__); \ +#define ZEN_CONSOLE(fmtstr, ...) \ + do \ + { \ + using namespace std::literals; \ + ConsoleLog().info(fmtstr##sv, ##__VA_ARGS__); \ } while (false) diff --git a/zencore/include/zencore/refcount.h b/zencore/include/zencore/refcount.h index 7167ab3b5..92ebebcfe 100644 --- a/zencore/include/zencore/refcount.h +++ b/zencore/include/zencore/refcount.h @@ -159,7 +159,7 @@ public: private: T* m_Ref = nullptr; - template<class T> + template<class U> friend class Ref; }; diff --git a/zencore/include/zencore/string.h b/zencore/include/zencore/string.h index 031ea2c1d..848310aa3 100644 --- a/zencore/include/zencore/string.h +++ b/zencore/include/zencore/string.h @@ -219,6 +219,19 @@ public: return AppendRange(String.data(), String.data() + String.size()); } + inline StringBuilderImpl& AppendBool(bool v) + { + // This is a method instead of a << operator overload as the latter can + // easily get called with non-bool types like pointers. It is a very + // subtle behaviour that can cause bugs. + using namespace std::literals; + if (v) + { + return AppendAscii("true"sv); + } + return AppendAscii("false"sv); + } + inline void RemoveSuffix(uint32_t Count) { ZEN_ASSERT(Count <= Size()); @@ -291,15 +304,6 @@ public: inline StringBuilderImpl& operator<<(const char* str) { return AppendAscii(str); } inline StringBuilderImpl& operator<<(const std::string_view str) { return AppendAscii(str); } inline StringBuilderImpl& operator<<(const std::u8string_view str) { return AppendAscii(str); } - inline StringBuilderImpl& operator<<(bool v) - { - using namespace std::literals; - if (v) - { - return AppendAscii("true"sv); - } - return AppendAscii("false"sv); - } protected: inline void Init(C* Base, size_t Capacity) @@ -424,7 +428,7 @@ public: inline std::wstring_view ToView() const { return std::wstring_view{Data(), Size()}; } inline std::wstring ToString() const { return std::wstring{Data(), Size()}; } - inline StringBuilderImpl& operator<<(const std::u16string_view str) { return Append((const wchar_t*)str.data(), str.size()); } + inline StringBuilderImpl& operator<<(const std::wstring_view str) { return Append((const wchar_t*)str.data(), str.size()); } inline StringBuilderImpl& operator<<(const wchar_t* str) { return Append(str); } using StringBuilderImpl:: operator<<; }; @@ -460,7 +464,6 @@ std::wstring Utf8ToWide(const std::string_view& wstr); void WideToUtf8(const wchar_t* wstr, StringBuilderBase& out); std::string WideToUtf8(const wchar_t* wstr); -void WideToUtf8(const std::u16string_view& wstr, StringBuilderBase& out); void WideToUtf8(const std::wstring_view& wstr, StringBuilderBase& out); std::string WideToUtf8(const std::wstring_view Wstr); @@ -697,6 +700,19 @@ ForEachStrTok(const std::string_view& Str, char Delim, Fn&& Func) ////////////////////////////////////////////////////////////////////////// +inline int32_t +StrCaseCompare(const char* Lhs, const char* Rhs, int64_t Length = -1) +{ + // A helper for cross-platform case-insensitive string comparison. +#if ZEN_PLATFORM_WINDOWS + return (Length < 0) ? _stricmp(Lhs, Rhs) : _strnicmp(Lhs, Rhs, size_t(Length)); +#else + return (Length < 0) ? strcasecmp(Lhs, Rhs) : strncasecmp(Lhs, Rhs, size_t(Length)); +#endif +} + +////////////////////////////////////////////////////////////////////////// + /** * ASCII character bitset useful for fast and readable parsing * diff --git a/zencore/include/zencore/thread.h b/zencore/include/zencore/thread.h index 9fc4c87a2..16d0e9dee 100644 --- a/zencore/include/zencore/thread.h +++ b/zencore/include/zencore/thread.h @@ -6,6 +6,7 @@ #include <shared_mutex> +#include <filesystem> #include <string_view> #include <vector> @@ -102,11 +103,30 @@ protected: /** Basic abstraction of an IPC mechanism (aka 'binary semaphore') */ -class NamedEvent : public Event +class NamedEvent { public: + NamedEvent() = default; ZENCORE_API explicit NamedEvent(std::string_view EventName); - ZENCORE_API explicit NamedEvent(std::u8string_view EventName); + ZENCORE_API ~NamedEvent(); + ZENCORE_API void Close(); + ZENCORE_API void Set(); + ZENCORE_API bool Wait(int TimeoutMs = -1); + + NamedEvent(NamedEvent&& Rhs) noexcept : m_EventHandle(Rhs.m_EventHandle) { Rhs.m_EventHandle = nullptr; } + + inline NamedEvent& operator=(NamedEvent&& Rhs) noexcept + { + std::swap(m_EventHandle, Rhs.m_EventHandle); + return *this; + } + +protected: + void* m_EventHandle = nullptr; + +private: + NamedEvent(const NamedEvent& Rhs) = delete; + NamedEvent& operator=(const NamedEvent& Rhs) = delete; }; /** Basic abstraction of a named (system wide) mutex primitive @@ -143,13 +163,38 @@ public: ZENCORE_API bool Wait(int TimeoutMs = -1); ZENCORE_API void Terminate(int ExitCode); ZENCORE_API void Reset(); - inline [[nodiscard]] int Pid() const { return m_Pid; } + [[nodiscard]] inline int Pid() const { return m_Pid; } private: void* m_ProcessHandle = nullptr; int m_Pid = 0; }; +/** Basic process creation + */ +struct CreateProcOptions +{ + enum + { + Flag_NewConsole = 1 << 0, + Flag_Elevated = 1 << 1, + Flag_Unelevated = 1 << 2, + }; + + const std::filesystem::path* WorkingDirectory = nullptr; + uint32_t Flags = 0; +}; + +#if ZEN_PLATFORM_WINDOWS +using CreateProcResult = void*; // handle to the process +#else +using CreateProcResult = int32_t; // pid +#endif + +ZENCORE_API CreateProcResult CreateProc(const std::filesystem::path& Executable, + std::string_view CommandLine, // should also include arg[0] (executable name) + const CreateProcOptions& Options = {}); + /** Process monitor - monitors a list of running processes via polling Intended to be used to monitor a set of "sponsor" processes, where @@ -168,12 +213,15 @@ public: ZENCORE_API bool IsActive() const; private: - mutable RwLock m_Lock; - std::vector<void*> m_ProcessHandles; + using HandleType = void*; + + mutable RwLock m_Lock; + std::vector<HandleType> m_ProcessHandles; }; ZENCORE_API bool IsProcessRunning(int pid); ZENCORE_API int GetCurrentProcessId(); +ZENCORE_API int GetCurrentThreadId(); ZENCORE_API void Sleep(int ms); diff --git a/zencore/include/zencore/trace.h b/zencore/include/zencore/trace.h new file mode 100644 index 000000000..a2e69a145 --- /dev/null +++ b/zencore/include/zencore/trace.h @@ -0,0 +1,51 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include <zencore/zencore.h> + +#if ZEN_WITH_TRACE + +# define __UNREAL__ 0 +# define IS_MONOLITHIC 1 +# define PLATFORM_WINDOWS ZEN_PLATFORM_WINDOWS +# define PLATFORM_UNIX ZEN_PLATFORM_LINUX +# define PLATFORM_APPLE ZEN_PLATFORM_MAC +# define PLATFORM_ANDROID 0 +# define PLATFORM_HOLOLENS 0 +# define UE_BUILD_TEST 0 +# define UE_BUILD_SHIPPING 0 + +ZEN_THIRD_PARTY_INCLUDES_START +# if !defined(TRACE_IMPLEMENT) +# define TRACE_IMPLEMENT 0 +# endif +# include <trace.h> +# undef TRACE_IMPLEMENT + +ZEN_THIRD_PARTY_INCLUDES_END +# undef __UNREAL__ +# undef IS_MONOLITHIC +# undef PLATFORM_WINDOWS +# undef PLATFORM_UNIX +# undef PLATFORM_APPLE +# undef PLATFORM_ANDROID +# undef PLATFORM_HOLOLENS +# undef UE_BUILD_TEST +# undef UE_BUILD_SHIPPING + +# define ZEN_TRACE_CPU(x) TRACE_CPU_SCOPE(x) + +enum class TraceType +{ + File, + Network, +}; + +void TraceInit(const char* HostOrPath, TraceType Type); + +#else + +# define ZEN_TRACE_CPU(x) + +#endif // ZEN_WITH_TRACE diff --git a/zencore/include/zencore/uid.h b/zencore/include/zencore/uid.h index f4e9ab65a..d25aa8059 100644 --- a/zencore/include/zencore/uid.h +++ b/zencore/include/zencore/uid.h @@ -74,7 +74,7 @@ struct Oid size_t operator()(const Oid& id) const { const size_t seed = id.OidBits[0]; - return (seed << 6) + (seed >> 2) + 0x9e3779b9 + uint64_t(id.OidBits[1]) | (uint64_t(id.OidBits[2]) << 32); + return ((seed << 6) + (seed >> 2) + 0x9e3779b9 + uint64_t(id.OidBits[1])) | (uint64_t(id.OidBits[2]) << 32); } }; diff --git a/zencore/include/zencore/varint.h b/zencore/include/zencore/varint.h index 0c40dd66b..d7f2ebfb7 100644 --- a/zencore/include/zencore/varint.h +++ b/zencore/include/zencore/varint.h @@ -2,6 +2,8 @@ #include "intmath.h" +#include <algorithm> + namespace zen { // Variable-Length Integer Encoding diff --git a/zencore/include/zencore/windows.h b/zencore/include/zencore/windows.h index 68138566b..117c5eff1 100644 --- a/zencore/include/zencore/windows.h +++ b/zencore/include/zencore/windows.h @@ -10,7 +10,12 @@ struct IUnknown; // Workaround for "combaseapi.h(229): error C2187: syntax erro #ifndef NOMINMAX # define NOMINMAX // We don't want your min/max macros #endif -#define WIN32_LEAN_AND_MEAN +#ifndef NOGDI +# define NOGDI // We don't want your GetObject define +#endif +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif #include <windows.h> #undef GetObject diff --git a/zencore/include/zencore/zencore.h b/zencore/include/zencore/zencore.h index 6b9a0f658..3352af5d2 100644 --- a/zencore/include/zencore/zencore.h +++ b/zencore/include/zencore/zencore.h @@ -16,7 +16,7 @@ #define ZEN_PLATFORM_WINDOWS 0 #define ZEN_PLATFORM_LINUX 0 -#define ZEN_PLATFORM_MACOS 0 +#define ZEN_PLATFORM_MAC 0 #ifdef _WIN32 # undef ZEN_PLATFORM_WINDOWS @@ -25,8 +25,20 @@ # undef ZEN_PLATFORM_LINUX # define ZEN_PLATFORM_LINUX 1 #elif defined(__APPLE__) -# undef ZEN_PLATFORM_MACOS -# define ZEN_PLATFORM_MACOS 1 +# undef ZEN_PLATFORM_MAC +# define ZEN_PLATFORM_MAC 1 +#endif + +#if ZEN_PLATFORM_WINDOWS +# if !defined(NOMINMAX) +# define NOMINMAX // stops Windows.h from defining 'min/max' macros +# endif +# if !defined(NOGDI) +# define NOGDI +# endif +# if !defined(WIN32_LEAN_AND_MEAN) +# define WIN32_LEAN_AND_MEAN // cut-down what Windows.h defines +# endif #endif ////////////////////////////////////////////////////////////////////////// @@ -68,20 +80,45 @@ #ifndef ZEN_THIRD_PARTY_INCLUDES_START # if ZEN_COMPILER_MSC -# define ZEN_THIRD_PARTY_INCLUDES_START __pragma(warning(push)) __pragma(warning(disable : 4668 4127)) -# else -# define ZEN_THIRD_PARTY_INCLUDES_START +# define ZEN_THIRD_PARTY_INCLUDES_START \ + __pragma(warning(push)) __pragma(warning(disable : 4668)) /* use of undefined preprocessor macro */ \ + __pragma(warning(disable : 4267)) /* '=': conversion from 'size_t' to 'US' */ \ + __pragma(warning(disable : 4127)) +# elif ZEN_COMPILER_CLANG +# define ZEN_THIRD_PARTY_INCLUDES_START \ + _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wundef\"") \ + _Pragma("clang diagnostic ignored \"-Wunused-parameter\"") _Pragma("clang diagnostic ignored \"-Wunused-variable\"") +# elif ZEN_COMPILER_GCC +# define ZEN_THIRD_PARTY_INCLUDES_START \ + _Pragma("GCC diagnostic push") /* NB. ignoring -Wundef doesn't work with GCC */ \ + _Pragma("GCC diagnostic ignored \"-Wunused-parameter\"") _Pragma("GCC diagnostic ignored \"-Wunused-variable\"") # endif #endif #ifndef ZEN_THIRD_PARTY_INCLUDES_END # if ZEN_COMPILER_MSC # define ZEN_THIRD_PARTY_INCLUDES_END __pragma(warning(pop)) -# else -# define ZEN_THIRD_PARTY_INCLUDES_END +# elif ZEN_COMPILER_CLANG +# define ZEN_THIRD_PARTY_INCLUDES_END _Pragma("clang diagnostic pop") +# elif ZEN_COMPILER_GCC +# define ZEN_THIRD_PARTY_INCLUDES_END _Pragma("GCC diagnostic pop") # endif #endif +#if ZEN_COMPILER_MSC +# define ZEN_DEBUG_BREAK() \ + do \ + { \ + __debugbreak(); \ + } while (0) +#else +# define ZEN_DEBUG_BREAK() \ + do \ + { \ + __builtin_trap(); \ + } while (0) +#endif + ////////////////////////////////////////////////////////////////////////// // Architecture // @@ -112,6 +149,13 @@ #define ZEN_PLATFORM_SUPPORTS_UNALIGNED_LOADS 1 +#if defined(__SIZEOF_WCHAR_T__) && __SIZEOF_WCHAR_T__ == 4 +# define ZEN_SIZEOF_WCHAR_T 4 +#else +static_assert(sizeof(wchar_t) == 2, "wchar_t is expected to be two bytes in size"); +# define ZEN_SIZEOF_WCHAR_T 2 +#endif + ////////////////////////////////////////////////////////////////////////// // Assert // @@ -206,6 +250,12 @@ char (&ZenArrayCountHelper(const T (&)[N]))[N + 1]; # define ZEN_NOINLINE __attribute__((noinline)) #endif +#if ZEN_PLATFORM_WINDOWS +# define ZEN_EXE_SUFFIX_LITERAL ".exe" +#else +# define ZEN_EXE_SUFFIX_LITERAL +#endif + #define ZEN_UNUSED(...) ((void)__VA_ARGS__) #define ZEN_NOT_IMPLEMENTED(...) ZEN_ASSERT(false, __VA_ARGS__) #define ZENCORE_API // Placeholder to allow DLL configs in the future (maybe) @@ -233,7 +283,12 @@ ZENCORE_API void zencore_forcelinktests(); #if ZEN_COMPILER_MSC # define ZEN_DISABLE_OPTIMIZATION_ACTUAL __pragma(optimize("", off)) # define ZEN_ENABLE_OPTIMIZATION_ACTUAL __pragma(optimize("", on)) -#else +#elif ZEN_COMPILER_GCC +# define ZEN_DISABLE_OPTIMIZATION_ACTUAL _Pragma("GCC push_options") _Pragma("GCC optimize (\"O0\")") +# define ZEN_ENABLE_OPTIMIZATION_ACTUAL _Pragma("GCC pop_options") +#elif ZEN_COMPILER_CLANG +# define ZEN_DISABLE_OPTIMIZATION_ACTUAL _Pragma("clang optimize off") +# define ZEN_ENABLE_OPTIMIZATION_ACTUAL _Pragma("clang optimize on") #endif // Set up optimization control macros, now that we have both the build settings and the platform macros @@ -249,4 +304,10 @@ ZENCORE_API void zencore_forcelinktests(); ////////////////////////////////////////////////////////////////////////// +#ifndef ZEN_WITH_TRACE +# define ZEN_WITH_TRACE 0 +#endif + +////////////////////////////////////////////////////////////////////////// + using ThreadId_t = uint32_t; diff --git a/zencore/iobuffer.cpp b/zencore/iobuffer.cpp index 979772d5e..30865068a 100644 --- a/zencore/iobuffer.cpp +++ b/zencore/iobuffer.cpp @@ -246,6 +246,7 @@ IoBufferExtendedCore::Materialize() const if (m_Flags.load(std::memory_order_relaxed) & kIsMaterialized) return; + void* NewMmapHandle; uint32_t NewFlags = kIsMaterialized; const uint64_t MapOffset = m_FileOffset & ~0xffffull; @@ -253,12 +254,12 @@ IoBufferExtendedCore::Materialize() const const uint64_t MapSize = m_DataBytes + MappedOffsetDisplacement; #if ZEN_PLATFORM_WINDOWS - void* NewMmapHandle = CreateFileMapping(m_FileHandle, - /* lpFileMappingAttributes */ nullptr, - /* flProtect */ PAGE_READONLY, - /* dwMaximumSizeLow */ 0, - /* dwMaximumSizeHigh */ 0, - /* lpName */ nullptr); + NewMmapHandle = CreateFileMapping(m_FileHandle, + /* lpFileMappingAttributes */ nullptr, + /* flProtect */ PAGE_READONLY, + /* dwMaximumSizeLow */ 0, + /* dwMaximumSizeHigh */ 0, + /* lpName */ nullptr); if (NewMmapHandle == nullptr) { @@ -398,18 +399,22 @@ IoBufferBuilder::ReadFromFileMaybe(IoBuffer& InBuffer) DWORD dwNumberOfBytesRead = 0; BOOL Success = ::ReadFile(FileRef.FileHandle, OutBuffer.MutableData(), DWORD(NumberOfBytesToRead), &dwNumberOfBytesRead, &Ovl); +#else + int Fd = int(intptr_t(FileRef.FileHandle)); + int Result = pread(Fd, OutBuffer.MutableData(), size_t(FileRef.FileChunkSize), off_t(FileRef.FileChunkOffset)); + bool Success = (Result < 0); + + uint32_t dwNumberOfBytesRead = uint32_t(Result); +#endif if (!Success) { ThrowLastError("ReadFile failed in IoBufferBuilder::ReadFromFileMaybe"); } - ZEN_ASSERT(dwNumberOfBytesRead == NumberOfBytesToRead); + ZEN_ASSERT(dwNumberOfBytesRead == FileRef.FileChunkSize); return OutBuffer; -#else -# error Needs implementation -#endif } else { @@ -424,14 +429,14 @@ IoBufferBuilder::MakeFromFileHandle(void* FileHandle, uint64_t Offset, uint64_t } IoBuffer -IoBufferBuilder::MakeFromFile(const path_char_t* FileName, uint64_t Offset, uint64_t Size) +IoBufferBuilder::MakeFromFile(const std::filesystem::path& FileName, uint64_t Offset, uint64_t Size) { uint64_t FileSize; #if ZEN_PLATFORM_WINDOWS CAtlFile DataFile; - HRESULT hRes = DataFile.Create(FileName, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING); + HRESULT hRes = DataFile.Create(FileName.c_str(), GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING); if (FAILED(hRes)) { @@ -440,7 +445,7 @@ IoBufferBuilder::MakeFromFile(const path_char_t* FileName, uint64_t Offset, uint DataFile.GetSize((ULONGLONG&)FileSize); #else - int Fd = open(FileName, O_RDONLY); + int Fd = open(FileName.c_str(), O_RDONLY); if (Fd < 0) { return {}; diff --git a/zencore/memory.cpp b/zencore/memory.cpp index c94829276..4b51ef9a2 100644 --- a/zencore/memory.cpp +++ b/zencore/memory.cpp @@ -3,6 +3,7 @@ #include <zencore/intmath.h> #include <zencore/memory.h> #include <zencore/testing.h> +#include <zencore/zencore.h> #ifdef ZEN_PLATFORM_WINDOWS # include <malloc.h> diff --git a/zencore/stats.cpp b/zencore/stats.cpp index 0c0647999..1bb6f6de0 100644 --- a/zencore/stats.cpp +++ b/zencore/stats.cpp @@ -227,7 +227,7 @@ UniformSample::Snapshot() const uint64_t ValuesSize = Size(); std::vector<double> Values(ValuesSize); - for (int i = 0; i < ValuesSize; ++i) + for (int i = 0, n = int(ValuesSize); i < n; ++i) { Values[i] = double(m_Values[i]); } diff --git a/zencore/string.cpp b/zencore/string.cpp index b7670617f..ad6ee78fc 100644 --- a/zencore/string.cpp +++ b/zencore/string.cpp @@ -30,6 +30,17 @@ utf16to8_impl(u16bit_iterator StartIt, u16bit_iterator EndIt, ::zen::StringBuild } } +template<typename u32bit_iterator> +void +utf32to8_impl(u32bit_iterator StartIt, u32bit_iterator EndIt, ::zen::StringBuilderBase& OutString) +{ + for (; StartIt != EndIt; ++StartIt) + { + wchar_t cp = *StartIt; + OutString.AppendCodepoint(cp); + } +} + ////////////////////////////////////////////////////////////////////////// namespace zen { @@ -76,7 +87,7 @@ FilepathFindExtension(const std::string_view& Path, const char* ExtensionToMatch // Look for extension introducer ('.') - for (size_t i = PathLen - 1; i >= 0; --i) + for (int64_t i = PathLen - 1; i >= 0; --i) { if (Path[i] == '.') return Path.data() + i; @@ -164,26 +175,24 @@ Utf8ToWide(const std::u8string_view& Str8, WideStringBuilderBase& OutString) void WideToUtf8(const wchar_t* Wstr, StringBuilderBase& OutString) { - WideToUtf8(std::u16string_view{(char16_t*)Wstr}, OutString); + WideToUtf8(std::wstring_view{Wstr}, OutString); } void WideToUtf8(const std::wstring_view& Wstr, StringBuilderBase& OutString) { - WideToUtf8(std::u16string_view{(char16_t*)Wstr.data(), Wstr.size()}, OutString); -} - -void -WideToUtf8(const std::u16string_view& Wstr, StringBuilderBase& OutString) -{ +#if ZEN_SIZEOF_WCHAR_T == 2 utf16to8_impl(begin(Wstr), end(Wstr), OutString); +#else + utf32to8_impl(begin(Wstr), end(Wstr), OutString); +#endif } std::string WideToUtf8(const wchar_t* Wstr) { ExtendableStringBuilder<128> String; - WideToUtf8(std::u16string_view{(char16_t*)Wstr}, String); + WideToUtf8(std::wstring_view{Wstr}, String); return String.c_str(); } @@ -192,7 +201,7 @@ std::string WideToUtf8(const std::wstring_view Wstr) { ExtendableStringBuilder<128> String; - WideToUtf8(std::u16string_view{(char16_t*)Wstr.data(), Wstr.size()}, String); + WideToUtf8(std::wstring_view{Wstr.data(), Wstr.size()}, String); return String.c_str(); } @@ -942,6 +951,18 @@ TEST_CASE("string") CHECK_EQ(ToLower("TE%St"sv), "te%st"sv); } + SUBCASE("StrCaseCompare") + { + CHECK(StrCaseCompare("foo", "FoO") == 0); + CHECK(StrCaseCompare("Bar", "bAs") < 0); + CHECK(StrCaseCompare("bAr", "Bas") < 0); + CHECK(StrCaseCompare("BBr", "Bar") > 0); + CHECK(StrCaseCompare("Bbr", "BAr") > 0); + CHECK(StrCaseCompare("foo", "FoO", 3) == 0); + CHECK(StrCaseCompare("Bar", "bAs", 3) < 0); + CHECK(StrCaseCompare("BBr", "Bar", 2) > 0); + } + SUBCASE("ForEachStrTok") { const auto Tokens = "here,is,my,different,tokens"sv; diff --git a/zencore/thread.cpp b/zencore/thread.cpp index da711fe89..6d17e6968 100644 --- a/zencore/thread.cpp +++ b/zencore/thread.cpp @@ -3,14 +3,32 @@ #include <zencore/thread.h> #include <zencore/except.h> +#include <zencore/filesystem.h> +#include <zencore/scopeguard.h> #include <zencore/string.h> +#include <zencore/testing.h> #if ZEN_PLATFORM_WINDOWS +# include <shellapi.h> +# include <Shlobj.h> # include <zencore/windows.h> #elif ZEN_PLATFORM_LINUX +# include <chrono> +# include <condition_variable> +# include <mutex> + +# include <fcntl.h> +# include <mqueue.h> +# include <pthread.h> +# include <signal.h> +# include <sys/file.h> +# include <sys/wait.h> +# include <time.h> # include <unistd.h> #endif +#include <thread> + ZEN_THIRD_PARTY_INCLUDES_START #include <fmt/format.h> ZEN_THIRD_PARTY_INCLUDES_END @@ -69,6 +87,8 @@ SetCurrentThreadName([[maybe_unused]] std::string_view ThreadName) std::string ThreadNameZ{ThreadName}; SetNameInternal(GetCurrentThreadId(), ThreadNameZ.c_str()); #else + std::string ThreadNameZ{ThreadName}; + pthread_setname_np(pthread_self(), ThreadNameZ.c_str()); #endif } // namespace zen @@ -98,40 +118,80 @@ RwLock::ReleaseExclusive() ////////////////////////////////////////////////////////////////////////// -#if ZEN_PLATFORM_WINDOWS +#if !ZEN_PLATFORM_WINDOWS +struct EventInner +{ + std::mutex Mutex; + std::condition_variable CondVar; + bool volatile bSet = false; +}; +#endif // !ZEN_PLATFORM_WINDOWS Event::Event() { - m_EventHandle = CreateEvent(nullptr, true, false, nullptr); + bool bManualReset = true; + bool bInitialState = false; + +#if ZEN_PLATFORM_WINDOWS + m_EventHandle = CreateEvent(nullptr, bManualReset, bInitialState, nullptr); +#else + ZEN_UNUSED(bManualReset); + auto* Inner = new EventInner(); + Inner->bSet = bInitialState; + m_EventHandle = Inner; +#endif } Event::~Event() { - CloseHandle(m_EventHandle); + Close(); } void Event::Set() { +#if ZEN_PLATFORM_WINDOWS SetEvent(m_EventHandle); +#else + auto* Inner = (EventInner*)m_EventHandle; + { + std::unique_lock Lock(Inner->Mutex); + Inner->bSet = true; + } + Inner->CondVar.notify_all(); +#endif } void Event::Reset() { +#if ZEN_PLATFORM_WINDOWS ResetEvent(m_EventHandle); +#else + auto* Inner = (EventInner*)m_EventHandle; + { + std::unique_lock Lock(Inner->Mutex); + Inner->bSet = false; + } +#endif } void Event::Close() { +#if ZEN_PLATFORM_WINDOWS CloseHandle(m_EventHandle); +#else + auto* Inner = (EventInner*)m_EventHandle; + delete Inner; +#endif m_EventHandle = nullptr; } bool Event::Wait(int TimeoutMs) { +#if ZEN_PLATFORM_WINDOWS using namespace std::literals; const DWORD Timeout = (TimeoutMs < 0) ? INFINITE : TimeoutMs; @@ -144,12 +204,48 @@ Event::Wait(int TimeoutMs) } return (Result == WAIT_OBJECT_0); +#else + auto* Inner = (EventInner*)m_EventHandle; + + if (TimeoutMs >= 0) + { + std::unique_lock Lock(Inner->Mutex); + + if (Inner->bSet) + { + return true; + } + + return Inner->CondVar.wait_for(Lock, std::chrono::milliseconds(TimeoutMs), [&] { return Inner->bSet; }); + } + + std::unique_lock Lock(Inner->Mutex); + + if (!Inner->bSet) + { + Inner->CondVar.wait(Lock, [&] { return Inner->bSet; }); + } + + return true; +#endif } ////////////////////////////////////////////////////////////////////////// -NamedEvent::NamedEvent(std::u8string_view EventName) : Event(nullptr) +#if ZEN_PLATFORM_LINUX +static bool +IsMessageQueueEmpty(int Fd) { + // Check if there is already a message in the queue. + mq_attr Attributes = {O_NONBLOCK, 1, 1, 0}; + mq_getattr(Fd, &Attributes); + return (Attributes.mq_curmsgs == 0); +} +#endif // ZEN_PLATFORM_LINUX + +NamedEvent::NamedEvent(std::string_view EventName) +{ +#if ZEN_PLATFORM_WINDOWS using namespace std::literals; ExtendableStringBuilder<64> Name; @@ -157,30 +253,151 @@ NamedEvent::NamedEvent(std::u8string_view EventName) : Event(nullptr) Name << EventName; m_EventHandle = CreateEventA(nullptr, true, false, Name.c_str()); +#elif ZEN_PLATFORM_LINUX + ExtendableStringBuilder<64> Name; + Name << "/"; + Name << EventName; + + mq_attr Attributes = { + 0, // flags + 1, // max message count + 1, // max message size + 0, // current messages + }; + + int Inner = mq_open(Name.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0644, &Attributes); + if (Inner < 0) + { + ThrowLastError("Failed to get message queue from mq_open()"); + } + + int LockResult = flock(Inner, LOCK_EX | LOCK_NB); + if (LockResult == 0) + { + // This is really thread safe as the message queue could be set between + // getting the exclusive lock and checking the queue. But for the our + // simple synchronising of process, this should be okay. + while (!IsMessageQueueEmpty(Inner)) + { + char Sink; + mq_receive(Inner, &Sink, sizeof(Sink), nullptr); + } + } + + m_EventHandle = (void*)intptr_t(Inner); +#else +# error Implement NamedEvent for this platform +#endif } -NamedEvent::NamedEvent(std::string_view EventName) : Event(nullptr) +NamedEvent::~NamedEvent() { - using namespace std::literals; + Close(); +} - ExtendableStringBuilder<64> Name; - Name << "Local\\"sv; - Name << EventName; +void +NamedEvent::Close() +{ + if (m_EventHandle == nullptr) + { + return; + } - m_EventHandle = CreateEventA(nullptr, true, false, Name.c_str()); +#if ZEN_PLATFORM_WINDOWS + CloseHandle(m_EventHandle); +#elif ZEN_PLATFORM_LINUX + int Inner = int(intptr_t(m_EventHandle)); + + if (flock(Inner, LOCK_EX | LOCK_NB) == 0) + { + flock(Inner, LOCK_UN | LOCK_NB); + std::filesystem::path Name = PathFromHandle((void*)(intptr_t(Inner))); + mq_unlink(Name.c_str()); + } + + close(Inner); +#endif + + m_EventHandle = nullptr; +} + +void +NamedEvent::Set() +{ +#if ZEN_PLATFORM_WINDOWS + SetEvent(m_EventHandle); +#elif ZEN_PLATFORM_LINUX + int Inner = int(intptr_t(m_EventHandle)); + + if (!IsMessageQueueEmpty(Inner)) + { + return; + } + + char Message = 0x49; + if (mq_send(Inner, &Message, sizeof(Message), 0) != 0) + { + ThrowLastError("Unable to send set message to queue"); + } +#endif } +bool +NamedEvent::Wait(int TimeoutMs) +{ +#if ZEN_PLATFORM_WINDOWS + const DWORD Timeout = (TimeoutMs < 0) ? INFINITE : TimeoutMs; + + DWORD Result = WaitForSingleObject(m_EventHandle, Timeout); + + if (Result == WAIT_FAILED) + { + using namespace std::literals; + zen::ThrowLastError("Event wait failed"sv); + } + + return (Result == WAIT_OBJECT_0); +#elif ZEN_PLATFORM_LINUX + int Inner = int(intptr_t(m_EventHandle)); + + if (!IsMessageQueueEmpty(Inner)) + { + return true; + } + + struct timeval TimeoutValue = { + .tv_sec = 0, + .tv_usec = TimeoutMs << 10, + }; + struct timeval* TimeoutPtr = (TimeoutMs < 0) ? nullptr : &TimeoutValue; + + fd_set FdSet; + FD_ZERO(&FdSet); + FD_SET(Inner, &FdSet); + return select(Inner + 1, &FdSet, nullptr, nullptr, TimeoutPtr) > 0; +#endif +} + +////////////////////////////////////////////////////////////////////////// + NamedMutex::~NamedMutex() { +#if ZEN_PLATFORM_WINDOWS if (m_MutexHandle) { CloseHandle(m_MutexHandle); } +#else + int Inner = int(intptr_t(m_MutexHandle)); + flock(Inner, LOCK_UN); + close(Inner); +#endif } bool NamedMutex::Create(std::string_view MutexName) { +#if ZEN_PLATFORM_WINDOWS ZEN_ASSERT(m_MutexHandle == nullptr); using namespace std::literals; @@ -192,11 +409,32 @@ NamedMutex::Create(std::string_view MutexName) m_MutexHandle = CreateMutexA(nullptr, /* InitialOwner */ TRUE, Name.c_str()); return !!m_MutexHandle; +#else + ExtendableStringBuilder<64> Name; + Name << "/tmp/" << MutexName; + + int Inner = open(Name.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0644); + if (Inner < 0) + { + return false; + } + + if (flock(Inner, LOCK_EX) != 0) + { + close(Inner); + Inner = 0; + return false; + } + + m_MutexHandle = (void*)(intptr_t(Inner)); + return true; +#endif // ZEN_PLATFORM_WINDOWS } bool NamedMutex::Exists(std::string_view MutexName) { +#if ZEN_PLATFORM_WINDOWS using namespace std::literals; ExtendableStringBuilder<64> Name; @@ -213,24 +451,49 @@ NamedMutex::Exists(std::string_view MutexName) CloseHandle(MutexHandle); return true; -} +#else + ExtendableStringBuilder<64> Name; + Name << "/tmp/" << MutexName; -#endif // ZEN_PLATFORM_WINDOWS + bool bExists = false; + int Fd = open(Name.c_str(), O_RDWR, 0644); + if (Fd >= 0) + { + if (flock(Fd, LOCK_EX | LOCK_NB) == 0) + { + flock(Fd, LOCK_UN | LOCK_NB); + } + else + { + bExists = true; + } + close(Fd); + } -#if ZEN_PLATFORM_WINDOWS + return bExists; +#endif // ZEN_PLATFORM_WINDOWS +} ////////////////////////////////////////////////////////////////////////// ProcessHandle::ProcessHandle() = default; +#if ZEN_PLATFORM_WINDOWS void ProcessHandle::Initialize(void* ProcessHandle) { ZEN_ASSERT(m_ProcessHandle == nullptr); + + if (ProcessHandle == INVALID_HANDLE_VALUE) + { + ProcessHandle = nullptr; + } + // TODO: perform some debug verification here to verify it's a valid handle? m_ProcessHandle = ProcessHandle; m_Pid = GetProcessId(m_ProcessHandle); } +#endif // ZEN_PLATFORM_WINDOWS ProcessHandle::~ProcessHandle() { @@ -241,12 +504,21 @@ void ProcessHandle::Initialize(int Pid) { ZEN_ASSERT(m_ProcessHandle == nullptr); - m_ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Pid); - using namespace fmt::literals; +#if ZEN_PLATFORM_WINDOWS + m_ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Pid); +#elif ZEN_PLATFORM_LINUX + if (Pid > 0) + { + m_ProcessHandle = (void*)(intptr_t(Pid)); + } +#else +# error Check process control on this platform +#endif if (!m_ProcessHandle) { + using namespace fmt::literals; ThrowLastError("ProcessHandle::Initialize(pid: {}) failed"_format(Pid)); } @@ -256,29 +528,51 @@ ProcessHandle::Initialize(int Pid) bool ProcessHandle::IsRunning() const { + bool bActive = false; + +#if ZEN_PLATFORM_WINDOWS DWORD ExitCode = 0; GetExitCodeProcess(m_ProcessHandle, &ExitCode); + bActive = (ExitCode == STILL_ACTIVE); +#elif ZEN_PLATFORM_LINUX + StringBuilder<64> ProcPath; + ProcPath << "/proc/" << m_Pid; + bActive = (access(ProcPath.c_str(), F_OK) != 0); +#else +# error Check process control on this platform +#endif - return ExitCode == STILL_ACTIVE; + return bActive; } bool ProcessHandle::IsValid() const { - return (m_ProcessHandle != nullptr) && (m_ProcessHandle != INVALID_HANDLE_VALUE); + return (m_ProcessHandle != nullptr); } void ProcessHandle::Terminate(int ExitCode) { - if (IsRunning()) + if (!IsRunning()) { - TerminateProcess(m_ProcessHandle, ExitCode); + return; } + bool bSuccess = false; + +#if ZEN_PLATFORM_WINDOWS + TerminateProcess(m_ProcessHandle, ExitCode); DWORD WaitResult = WaitForSingleObject(m_ProcessHandle, INFINITE); + bSuccess = (WaitResult != WAIT_OBJECT_0); +#elif ZEN_PLATFORM_LINUX + ZEN_UNUSED(ExitCode); + bSuccess = (kill(m_Pid, SIGKILL) == 0); +#else +# error Check kill() on this platform +#endif - if (WaitResult != WAIT_OBJECT_0) + if (!bSuccess) { // What might go wrong here, and what is meaningful to act on? } @@ -289,14 +583,20 @@ ProcessHandle::Reset() { if (IsValid()) { +#if ZEN_PLATFORM_WINDOWS CloseHandle(m_ProcessHandle); +#endif m_ProcessHandle = nullptr; + m_Pid = 0; } } bool ProcessHandle::Wait(int TimeoutMs) { + using namespace std::literals; + +#if ZEN_PLATFORM_WINDOWS const DWORD Timeout = (TimeoutMs < 0) ? INFINITE : TimeoutMs; const DWORD WaitResult = WaitForSingleObject(m_ProcessHandle, Timeout); @@ -310,19 +610,301 @@ ProcessHandle::Wait(int TimeoutMs) return false; case WAIT_FAILED: - // What might go wrong here, and what is meaningful to act on? - using namespace std::literals; - ThrowLastError("Process::Wait failed"sv); + break; } +#elif ZEN_PLATFORM_LINUX + const int SleepMs = 20; + timespec SleepTime = {0, SleepMs * 1000 * 1000}; + for (int i = 0;; i += SleepMs) + { + int WaitState = 0; + waitpid(m_Pid, &WaitState, WNOHANG | WCONTINUED | WUNTRACED); - return false; -} + if (kill(m_Pid, 0) < 0) + { + if (zen::GetLastError() == ESRCH) + { + return true; + } + break; + } -#endif // ZEN_PLATFORM_WINDOWS + if (TimeoutMs >= 0 && i >= TimeoutMs) + { + return false; + } + + nanosleep(&SleepTime, nullptr); + } +#else +# error Check kill() on this platform +#endif + + // What might go wrong here, and what is meaningful to act on? + ThrowLastError("Process::Wait failed"sv); +} ////////////////////////////////////////////////////////////////////////// +#if !ZEN_PLATFORM_WINDOWS || ZEN_WITH_TESTS +static void +BuildArgV(std::vector<char*>& Out, char* CommandLine) +{ + char* Cursor = CommandLine; + while (true) + { + // Skip leading whitespace + for (; *Cursor == ' '; ++Cursor) + ; + + // Check for nullp terminator + if (*Cursor == '\0') + { + break; + } + + Out.push_back(Cursor); + + // Extract word + int QuoteCount = 0; + do + { + QuoteCount += (*Cursor == '\"'); + if (*Cursor == ' ' && !(QuoteCount & 1)) + { + break; + } + ++Cursor; + } while (*Cursor != '\0'); + + if (*Cursor == '\0') + { + break; + } + + *Cursor = '\0'; + ++Cursor; + } +} +#endif // !WINDOWS || TESTS + +#if ZEN_PLATFORM_WINDOWS +static CreateProcResult +CreateProcNormal(const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options) +{ + PROCESS_INFORMATION ProcessInfo{}; + STARTUPINFO StartupInfo{.cb = sizeof(STARTUPINFO)}; + + const bool InheritHandles = false; + void* Environment = nullptr; + LPSECURITY_ATTRIBUTES ProcessAttributes = nullptr; + LPSECURITY_ATTRIBUTES ThreadAttributes = nullptr; + + DWORD CreationFlags = 0; + if (Options.Flags & CreateProcOptions::Flag_NewConsole) + { + CreationFlags |= CREATE_NEW_CONSOLE; + } + + const wchar_t* WorkingDir = nullptr; + if (Options.WorkingDirectory != nullptr) + { + WorkingDir = Options.WorkingDirectory->c_str(); + } + + ExtendableWideStringBuilder<256> CommandLineZ; + CommandLineZ << CommandLine; + + BOOL Success = CreateProcessW(Executable.c_str(), + CommandLineZ.Data(), + ProcessAttributes, + ThreadAttributes, + InheritHandles, + CreationFlags, + Environment, + WorkingDir, + &StartupInfo, + &ProcessInfo); + + if (!Success) + { + return nullptr; + } + + CloseHandle(ProcessInfo.hThread); + return ProcessInfo.hProcess; +} + +static CreateProcResult +CreateProcUnelevated(const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options) +{ + /* Launches a binary with the shell as its parent. The shell (such as + Explorer) should be an unelevated process. */ + + // No sense in using this route if we are not elevated in the first place + if (IsUserAnAdmin() == FALSE) + { + return CreateProcNormal(Executable, CommandLine, Options); + } + + // Get the users' shell process and open it for process creation + HWND ShellWnd = GetShellWindow(); + if (ShellWnd == nullptr) + { + return nullptr; + } + + DWORD ShellPid; + GetWindowThreadProcessId(ShellWnd, &ShellPid); + + HANDLE Process = OpenProcess(PROCESS_CREATE_PROCESS, FALSE, ShellPid); + if (Process == nullptr) + { + return nullptr; + } + auto $0 = MakeGuard([&] { CloseHandle(Process); }); + + // Creating a process as a child of another process is done by setting a + // thread-attribute list on the startup info passed to CreateProcess() + SIZE_T AttrListSize; + InitializeProcThreadAttributeList(nullptr, 1, 0, &AttrListSize); + + auto AttrList = (PPROC_THREAD_ATTRIBUTE_LIST)malloc(AttrListSize); + auto $1 = MakeGuard([&] { free(AttrList); }); + + if (!InitializeProcThreadAttributeList(AttrList, 1, 0, &AttrListSize)) + { + return nullptr; + } + + BOOL bOk = + UpdateProcThreadAttribute(AttrList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, (HANDLE*)&Process, sizeof(Process), nullptr, nullptr); + if (!bOk) + { + return nullptr; + } + + // By this point we know we are an elevated process. It is not allowed to + // create a process as a child of another unelevated process that share our + // elevated console window if we have one. So we'll need to create a new one. + uint32_t CreateProcFlags = EXTENDED_STARTUPINFO_PRESENT; + if (GetConsoleWindow() != nullptr) + { + CreateProcFlags |= CREATE_NEW_CONSOLE; + } + else + { + CreateProcFlags |= DETACHED_PROCESS; + } + + // Everything is set up now so we can proceed and launch the process + STARTUPINFOEXW StartupInfo = { + .StartupInfo = {.cb = sizeof(STARTUPINFOEXW)}, + .lpAttributeList = AttrList, + }; + PROCESS_INFORMATION ProcessInfo = {}; + + if (Options.Flags & CreateProcOptions::Flag_NewConsole) + { + CreateProcFlags |= CREATE_NEW_CONSOLE; + } + + ExtendableWideStringBuilder<256> CommandLineZ; + CommandLineZ << CommandLine; + + bOk = CreateProcessW(Executable.c_str(), + CommandLineZ.Data(), + nullptr, + nullptr, + FALSE, + CreateProcFlags, + nullptr, + nullptr, + &StartupInfo.StartupInfo, + &ProcessInfo); + if (bOk == FALSE) + { + return nullptr; + } + + CloseHandle(ProcessInfo.hThread); + return ProcessInfo.hProcess; +} + +static CreateProcResult +CreateProcElevated(const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options) +{ + ExtendableWideStringBuilder<256> CommandLineZ; + CommandLineZ << CommandLine; + + SHELLEXECUTEINFO ShellExecuteInfo; + ZeroMemory(&ShellExecuteInfo, sizeof(ShellExecuteInfo)); + ShellExecuteInfo.cbSize = sizeof(ShellExecuteInfo); + ShellExecuteInfo.fMask = SEE_MASK_UNICODE | SEE_MASK_NOCLOSEPROCESS; + ShellExecuteInfo.lpFile = Executable.c_str(); + ShellExecuteInfo.lpVerb = TEXT("runas"); + ShellExecuteInfo.nShow = SW_SHOW; + ShellExecuteInfo.lpParameters = CommandLineZ.c_str(); + + if (Options.WorkingDirectory != nullptr) + { + ShellExecuteInfo.lpDirectory = Options.WorkingDirectory->c_str(); + } + + if (::ShellExecuteEx(&ShellExecuteInfo)) + { + return ShellExecuteInfo.hProcess; + } + + return nullptr; +} +#endif // ZEN_PLATFORM_WINDOWS + +CreateProcResult +CreateProc(const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options) +{ #if ZEN_PLATFORM_WINDOWS + if (Options.Flags & CreateProcOptions::Flag_Unelevated) + { + return CreateProcUnelevated(Executable, CommandLine, Options); + } + + if (Options.Flags & CreateProcOptions::Flag_Elevated) + { + return CreateProcElevated(Executable, CommandLine, Options); + } + + return CreateProcNormal(Executable, CommandLine, Options); +#else + std::vector<char*> ArgV; + std::string CommandLineZ(CommandLine); + BuildArgV(ArgV, CommandLineZ.data()); + ArgV.push_back(nullptr); + + int ChildPid = fork(); + if (ChildPid < 0) + { + ThrowLastError("Failed to fork a new child process"); + } + else if (ChildPid == 0) + { + if (Options.WorkingDirectory != nullptr) + { + int Result = chdir(Options.WorkingDirectory->c_str()); + ZEN_UNUSED(Result); + } + + if (execv(Executable.c_str(), ArgV.data()) < 0) + { + ThrowLastError("Failed to exec() a new process image"); + } + } + + return ChildPid; +#endif +} + +////////////////////////////////////////////////////////////////////////// ProcessMonitor::ProcessMonitor() { @@ -332,9 +914,11 @@ ProcessMonitor::~ProcessMonitor() { RwLock::ExclusiveLockScope _(m_Lock); - for (HANDLE& Proc : m_ProcessHandles) + for (HandleType& Proc : m_ProcessHandles) { +#if ZEN_PLATFORM_WINDOWS CloseHandle(Proc); +#endif Proc = 0; } } @@ -346,24 +930,34 @@ ProcessMonitor::IsRunning() bool FoundOne = false; - for (HANDLE& Proc : m_ProcessHandles) + for (HandleType& Proc : m_ProcessHandles) { + bool ProcIsActive; + +#if ZEN_PLATFORM_WINDOWS DWORD ExitCode = 0; GetExitCodeProcess(Proc, &ExitCode); - if (ExitCode != STILL_ACTIVE) + ProcIsActive = (ExitCode == STILL_ACTIVE); + if (!ProcIsActive) { CloseHandle(Proc); - Proc = 0; } - else +#else + int Pid = int(intptr_t(Proc)); + ProcIsActive = IsProcessRunning(Pid); +#endif + + if (!ProcIsActive) { - // Still alive - FoundOne = true; + Proc = 0; } + + // Still alive + FoundOne |= ProcIsActive; } - std::erase_if(m_ProcessHandles, [](HANDLE Handle) { return Handle == 0; }); + std::erase_if(m_ProcessHandles, [](HandleType Handle) { return Handle == 0; }); return FoundOne; } @@ -371,7 +965,13 @@ ProcessMonitor::IsRunning() void ProcessMonitor::AddPid(int Pid) { - HANDLE ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Pid); + HandleType ProcessHandle; + +#if ZEN_PLATFORM_WINDOWS + ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Pid); +#else + ProcessHandle = HandleType(intptr_t(Pid)); +#endif if (ProcessHandle) { @@ -387,8 +987,6 @@ ProcessMonitor::IsActive() const return m_ProcessHandles.empty() == false; } -#endif // ZEN_PLATFORM_WINDOWS - ////////////////////////////////////////////////////////////////////////// bool @@ -418,7 +1016,9 @@ IsProcessRunning(int pid) return true; #else - ZEN_NOT_IMPLEMENTED(); + char Buffer[64]; + sprintf(Buffer, "/proc/%d", pid); + return access(Buffer, F_OK) == 0; #endif } @@ -428,7 +1028,17 @@ GetCurrentProcessId() #if ZEN_PLATFORM_WINDOWS return ::GetCurrentProcessId(); #else - return getpid(); + return int(getpid()); +#endif +} + +int +GetCurrentThreadId() +{ +#if ZEN_PLATFORM_WINDOWS + return ::GetCurrentThreadId(); +#else + return int(gettid()); #endif } @@ -447,9 +1057,124 @@ Sleep(int ms) // Testing related code follows... // +#if ZEN_WITH_TESTS + void thread_forcelink() { } +TEST_CASE("Thread") +{ + int Pid = GetCurrentProcessId(); + CHECK(Pid > 0); + CHECK(IsProcessRunning(Pid)); +} + +TEST_CASE("BuildArgV") +{ + const char* Words[] = {"one", "two", "three", "four", "five"}; + struct + { + int WordCount; + const char* Input; + } Cases[] = { + {0, ""}, + {0, " "}, + {1, "one"}, + {1, " one"}, + {1, "one "}, + {2, "one two"}, + {2, " one two"}, + {2, "one two "}, + {2, " one two"}, + {2, "one two "}, + {2, "one two "}, + {3, "one two three"}, + {3, "\"one\" two \"three\""}, + {5, "one two three four five"}, + }; + + for (const auto& Case : Cases) + { + std::vector<char*> OutArgs; + StringBuilder<64> Mutable; + Mutable << Case.Input; + BuildArgV(OutArgs, Mutable.Data()); + + CHECK_EQ(OutArgs.size(), Case.WordCount); + + for (int i = 0, n = int(OutArgs.size()); i < n; ++i) + { + const char* Truth = Words[i]; + size_t TruthLen = strlen(Truth); + + const char* Candidate = OutArgs[i]; + bool bQuoted = (Candidate[0] == '\"'); + Candidate += bQuoted; + + CHECK(strncmp(Truth, Candidate, TruthLen) == 0); + + if (bQuoted) + { + CHECK_EQ(Candidate[TruthLen], '\"'); + } + } + } +} + +TEST_CASE("NamedEvent") +{ + std::string Name = "zencore_test_event"; + NamedEvent TestEvent(Name); + + // Timeout test + for (uint32_t i = 0; i < 8; ++i) + { + bool bEventSet = TestEvent.Wait(100); + CHECK(!bEventSet); + } + + // Thread check + std::thread Waiter = std::thread([Name]() { + NamedEvent ReadyEvent(Name + "_ready"); + ReadyEvent.Set(); + + NamedEvent TestEvent(Name); + TestEvent.Wait(1000); + }); + + NamedEvent ReadyEvent(Name + "_ready"); + ReadyEvent.Wait(); + + zen::Sleep(500); + TestEvent.Set(); + + Waiter.join(); + + // Manual reset property + for (uint32_t i = 0; i < 8; ++i) + { + bool bEventSet = TestEvent.Wait(100); + CHECK(bEventSet); + } +} + +TEST_CASE("NamedMutex") +{ + static const char* Name = "zen_test_mutex"; + + CHECK(!NamedMutex::Exists(Name)); + + { + NamedMutex TestMutex; + CHECK(TestMutex.Create(Name)); + CHECK(NamedMutex::Exists(Name)); + } + + CHECK(!NamedMutex::Exists(Name)); +} + +#endif // ZEN_WITH_TESTS + } // namespace zen diff --git a/zencore/trace.cpp b/zencore/trace.cpp new file mode 100644 index 000000000..f6a303960 --- /dev/null +++ b/zencore/trace.cpp @@ -0,0 +1,33 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#if ZEN_WITH_TRACE + +# include <zencore/zencore.h> + +# define TRACE_IMPLEMENT 1 +# include <zencore/trace.h> +//#undef TRACE_IMPLEMENT + +void +TraceInit(const char* HostOrPath, TraceType Type) +{ + switch (Type) + { + case TraceType::Network: + trace::SendTo(HostOrPath); + break; + + case TraceType::File: + trace::WriteTo(HostOrPath); + break; + } + + trace::FInitializeDesc Desc = { + .bUseImportantCache = false, + }; + trace::Initialize(Desc); + + trace::ToggleChannel("cpu", true); +} + +#endif // ZEN_WITH_TRACE diff --git a/zencore/xmake.lua b/zencore/xmake.lua index d26a9f922..738fe5796 100644 --- a/zencore/xmake.lua +++ b/zencore/xmake.lua @@ -2,8 +2,18 @@ target('zencore') set_kind("static") add_files("**.cpp") add_includedirs("include", {public=true}) - add_includedirs("..\\thirdparty\\utfcpp\\source") - add_linkdirs("$(projectdir)/thirdparty/BLAKE3/lib/Win64", "$(projectdir)/thirdparty/Oodle/lib/Win64") + add_includedirs("$(projectdir)/thirdparty/utfcpp/source") + if is_os("windows") then + add_linkdirs("$(projectdir)/thirdparty/BLAKE3/lib/Win64") + add_linkdirs("$(projectdir)/thirdparty/Oodle/lib/Win64") + elseif is_os("linux") then + add_linkdirs("$(projectdir)/thirdparty/BLAKE3/lib/Linux_x64") + add_linkdirs("$(projectdir)/thirdparty/Oodle/lib/Linux_x64") + add_links("blake3") + add_links("oo2corelinux64") + add_syslinks("pthread") + end + add_options("zentrace") add_packages( "vcpkg::spdlog", "vcpkg::fmt", @@ -14,5 +24,26 @@ target('zencore') "vcpkg::cpr", "vcpkg::curl", -- required by cpr "vcpkg::zlib", -- required by curl + "vcpkg::openssl", -- required by curl "vcpkg::xxhash", "vcpkg::gsl-lite") + + if is_plat("linux") then + -- The 'vcpkg::openssl' package is two libraries; ssl and crypto, with + -- ssl being dependent on symbols in crypto. When GCC-like linkers read + -- object files from their command line, those object files only resolve + -- symbols of objects previously encountered. Thus crypto must appear + -- after ssl so it can fill out ssl's unresolved symbol table. Xmake's + -- vcpkg support is basic and works by parsing .list files. Openssl's + -- archives are listed alphabetically causing crypto to be _before_ ssl + -- and resulting in link errors. The links are restated here to force + -- xmake to use the correct order, and "syslinks" is used to force the + -- arguments to the end of the line (otherwise they can appear before + -- curl and cause more errors). + add_syslinks("crypto") + add_syslinks("dl") + end + + if is_plat("linux") then + add_syslinks("rt") + end |