// Copyright Epic Games, Inc. All Rights Reserved. #include "ZenServerState.h" #include "HAL/FileManager.h" #include "HAL/PlatformProcess.h" #include "Logging/LogMacros.h" #include "Misc/ScopeExit.h" #include "Misc/Paths.h" #include "Misc/PathViews.h" #include "Serialization/CompactBinaryValidation.h" #if PLATFORM_WINDOWS # include "Windows/AllowWindowsPlatformTypes.h" # include # include # include # include "Windows/HideWindowsPlatformTypes.h" #endif #if PLATFORM_UNIX || PLATFORM_MAC # include # include # include # include # include #endif DEFINE_LOG_CATEGORY_STATIC(LogZenServiceState, Log, All); #if PLATFORM_UNIX static char GetPidStatus(int Pid) { TAnsiStringBuilder<128> StatPath; StatPath.Appendf("/proc/%d/stat", Pid); FILE* StatFile = fopen(*StatPath, "r"); if (StatFile) { char Buffer[5120]; int Size = fread(Buffer, 1, 5120 - 1, StatFile); fclose(StatFile); if (Size > 0) { Buffer[Size] = 0; char* ScanPtr = strrchr(Buffer, ')'); if (ScanPtr && ScanPtr[1] != '\0') { ScanPtr += 2; char State = *ScanPtr; return State; } } } return 0; } static bool IsZombieProcess(int pid) { char Status = GetPidStatus(pid); if (Status == 'Z' || Status == 0) { return true; } return false; } #endif // ZEN_PLATFORM_LINUX #if PLATFORM_MAC static bool IsZombieProcess(int pid) { struct kinfo_proc Info; int Mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid }; size_t InfoSize = sizeof Info; int Res = sysctl(Mib, 4, &Info, &InfoSize, NULL, 0); if (Res != 0) { return false; } if (Info.kp_proc.p_stat == SZOMB) { // Zombie process return true; } return false; } #endif // PLATFORM_MAC // Native functions to interact with a process using a process id // We don't use UE's own OpenProcess as they try to open processes with PROCESS_ALL_ACCESS bool ZenServerState::IsProcessRunning(uint32 Pid) { if (Pid == 0) { return false; } #if PLATFORM_WINDOWS HANDLE Handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, (DWORD)Pid); if (!Handle) { DWORD Error = GetLastError(); if (Error == ERROR_INVALID_PARAMETER) { return false; } else if (Error == ERROR_ACCESS_DENIED) { UE_LOG(LogZenServiceState, Warning, TEXT("No access to open running process %d: %d, assuming it is running"), Pid, Error); return true; } UE_LOG(LogZenServiceState, Warning, TEXT("Failed to open running process %d: %d, assuming it is not running"), Pid, Error); return false; } ON_SCOPE_EXIT{ CloseHandle(Handle); }; DWORD ExitCode = 0; if (GetExitCodeProcess(Handle, &ExitCode) == 0) { DWORD Error = GetLastError(); UE_LOG(LogZenServiceState, Warning, TEXT("Failed to get running process exit code %d: %d, assuming it is still running"), Pid, Error); return true; } else if (ExitCode == STILL_ACTIVE) { return true; } return false; #elif PLATFORM_UNIX || PLATFORM_MAC int Res = kill(pid_t(Pid), 0); if (Res == 0) { if (IsZombieProcess(Pid)) { return false; } return true; } int Error = errno; if (Error == EPERM) { UE_LOG(LogZenServiceState, Warning, TEXT("No permission to signal running process %d: %d, assuming it is running"), Pid, Error); return true; } else if (Error == ESRCH) { return false; } UE_LOG(LogZenServiceState, Warning, TEXT("Failed to signal running process %d: %d, assuming it is running"), Pid, Error); return true; #endif } bool ZenServerState::Terminate(uint32 Pid) { #if PLATFORM_WINDOWS HANDLE Handle = OpenProcess(PROCESS_TERMINATE | SYNCHRONIZE, 0, (DWORD)Pid); if (Handle == NULL) { DWORD Error = GetLastError(); if (Error != ERROR_INVALID_PARAMETER) { UE_LOG(LogZenServiceState, Warning, TEXT("Failed to open running process for terminate %d: %d"), Pid, Error); return false; } return true; } ON_SCOPE_EXIT{ CloseHandle(Handle); }; BOOL bTerminated = TerminateProcess(Handle, 0); if (!bTerminated) { DWORD Error = GetLastError(); UE_LOG(LogZenServiceState, Warning, TEXT("Failed to terminate running process %d: %d"), Pid, Error); return false; } DWORD WaitResult = WaitForSingleObject(Handle, 15000); BOOL bSuccess = (WaitResult == WAIT_OBJECT_0) || (WaitResult == WAIT_ABANDONED_0); if (!bSuccess) { if (WaitResult == WAIT_FAILED) { DWORD Error = GetLastError(); UE_LOG(LogZenServiceState, Warning, TEXT("Failed to wait for terminated process %d: %d"), Pid, Error); } return false; } #elif PLATFORM_UNIX || PLATFORM_MAC int Res = kill(pid_t(Pid), SIGKILL); if (Res != 0) { int LastError = errno; if (LastError != ESRCH) { UE_LOG(LogZenServiceState, Warning, TEXT("Failed to terminate running process %d: %d"), Pid, LastError); return false; } } #endif return true; } bool ZenServerState::FindRunningProcessId(const TCHAR* ExecutablePath, uint32* OutPid) { FString NormalizedExecutablePath(ExecutablePath); FPaths::NormalizeFilename(NormalizedExecutablePath); FPlatformProcess::FProcEnumerator ProcIter; while (ProcIter.MoveNext()) { FPlatformProcess::FProcEnumInfo ProcInfo = ProcIter.GetCurrent(); FString Candidate = ProcInfo.GetFullPath(); FPaths::NormalizeFilename(Candidate); if (Candidate == NormalizedExecutablePath) { if (OutPid) { *OutPid = ProcInfo.GetPID(); } return true; } } return false; } ZenServerState::ZenServerState(bool ReadOnly) : m_hMapFile(nullptr) , m_Data(nullptr) { size_t MapSize = m_MaxEntryCount * sizeof(ZenServerEntry); #if PLATFORM_WINDOWS DWORD DesiredAccess = ReadOnly ? FILE_MAP_READ : (FILE_MAP_READ | FILE_MAP_WRITE); HANDLE hMap = OpenFileMapping(DesiredAccess, 0, L"Global\\ZenMap"); if (hMap == NULL) { hMap = OpenFileMapping(DesiredAccess, 0, L"Local\\ZenMap"); } if (hMap == NULL) { return; } void* pBuf = MapViewOfFile(hMap, // handle to map object DesiredAccess, // read permission 0, // offset high 0, // offset low MapSize); if (pBuf == NULL) { CloseHandle(hMap); return; } #elif PLATFORM_UNIX || PLATFORM_MAC int OFlag = ReadOnly ? (O_RDONLY | O_CLOEXEC) : (O_RDWR | O_CREAT | O_CLOEXEC); int Fd = shm_open("/UnrealEngineZen", OFlag, 0666); if (Fd < 0) { return; } void* hMap = (void*)intptr_t(Fd); int Prot = ReadOnly ? PROT_READ : (PROT_WRITE | PROT_READ); void* pBuf = mmap(nullptr, MapSize, Prot, MAP_SHARED, Fd, 0); if (pBuf == MAP_FAILED) { close(Fd); return; } #endif #if PLATFORM_WINDOWS || PLATFORM_UNIX || PLATFORM_MAC m_hMapFile = hMap; m_Data = reinterpret_cast(pBuf); #endif m_IsReadOnly = ReadOnly; } ZenServerState::~ZenServerState() { #if PLATFORM_WINDOWS if (m_Data) { UnmapViewOfFile(m_Data); } if (m_hMapFile) { CloseHandle(m_hMapFile); } #elif PLATFORM_UNIX || PLATFORM_MAC if (m_Data != nullptr) { munmap((void*)m_Data, m_MaxEntryCount * sizeof(ZenServerEntry)); } int Fd = int(intptr_t(m_hMapFile)); close(Fd); #endif m_hMapFile = nullptr; m_Data = nullptr; } const ZenServerState::ZenServerEntry* ZenServerState::LookupByDesiredListenPortInternal(int Port) const { if (m_Data == nullptr) { return nullptr; } for (int i = 0; i < m_MaxEntryCount; ++i) { if (m_Data[i].DesiredListenPort.load(std::memory_order_relaxed) == Port) { const ZenServerState::ZenServerEntry* Entry = &m_Data[i]; if (IsProcessRunning((uint32)Entry->Pid.load(std::memory_order_relaxed))) { return Entry; } } } return nullptr; } const ZenServerState::ZenServerEntry* ZenServerState::LookupByDesiredListenPort(int Port) const { return LookupByDesiredListenPortInternal(Port); } ZenServerState::ZenServerEntry* ZenServerState::LookupByDesiredListenPort(int Port) { check(!m_IsReadOnly); return const_cast(LookupByDesiredListenPortInternal(Port)); } const ZenServerState::ZenServerEntry* ZenServerState::LookupByEffectiveListenPortInternal(int Port) const { if (m_Data == nullptr) { return nullptr; } for (int i = 0; i < m_MaxEntryCount; ++i) { const ZenServerState::ZenServerEntry* Entry = &m_Data[i]; if (Entry->EffectiveListenPort.load(std::memory_order_relaxed) == Port) { if (IsProcessRunning((uint32)Entry->Pid.load(std::memory_order_relaxed))) { return Entry; } } } return nullptr; } const ZenServerState::ZenServerEntry* ZenServerState::LookupByEffectiveListenPort(int Port) const { return LookupByEffectiveListenPortInternal(Port); } ZenServerState::ZenServerEntry* ZenServerState::LookupByEffectiveListenPort(int Port) { check(!m_IsReadOnly); return const_cast(LookupByEffectiveListenPortInternal(Port)); } const ZenServerState::ZenServerEntry* ZenServerState::LookupByPid(uint32 Pid) const { if (m_Data == nullptr) { return nullptr; } for (int i = 0; i < m_MaxEntryCount; ++i) { const ZenServerState::ZenServerEntry* Entry = &m_Data[i]; if (m_Data[i].Pid.load(std::memory_order_relaxed) == Pid) { if (IsProcessRunning(Pid)) { return Entry; } } } return nullptr; } bool ZenServerState::ZenServerEntry::AddSponsorProcess(uint32 PidToAdd) { uint32_t ServerPid = Pid.load(); // Sponsor processes are checked every second, so 2 second wait time should be enough const FTimespan MaximumWaitForPickup = FTimespan::FromSeconds(2); auto WaitForPickup = [&](uint32_t AddedSlotIndex) { FDateTime StartedWaitingForPickup = FDateTime::UtcNow(); while (SponsorPids[AddedSlotIndex] == PidToAdd) { FTimespan WaitForPickup = FDateTime::UtcNow() - StartedWaitingForPickup; if (WaitForPickup > MaximumWaitForPickup) { SponsorPids[AddedSlotIndex].compare_exchange_strong(PidToAdd, 0); return false; } if (!IsProcessRunning(ServerPid)) { SponsorPids[AddedSlotIndex].compare_exchange_strong(PidToAdd, 0); return false; } FPlatformProcess::Sleep(0.1f); } return true; }; for (uint32_t SponsorIndex = 0; SponsorIndex < 8; SponsorIndex++) { if (SponsorPids[SponsorIndex].load(std::memory_order_relaxed) == PidToAdd) { return WaitForPickup(SponsorIndex); } uint32_t Expected = 0; if (SponsorPids[SponsorIndex].compare_exchange_strong(Expected, PidToAdd)) { return WaitForPickup(SponsorIndex); } } return false; } ZenSharedEvent::ZenSharedEvent(FStringView EventName) : m_EventName(EventName) { check(m_EventName.Len() > 0); } ZenSharedEvent::~ZenSharedEvent() { Close(); } bool ZenSharedEvent::Create() { #if PLATFORM_WINDOWS check(m_EventHandle == NULL); FString FullEventName = GetFullEventName(); m_EventHandle = (void*)CreateEventW(nullptr, true, false, *FullEventName); if (m_EventHandle == NULL) { DWORD LastError = GetLastError(); UE_LOG(LogZenServiceState, Warning, TEXT("Failed creating named event '%s' (err: %d)"), *FullEventName, LastError); return false; } return true; #elif PLATFORM_UNIX || PLATFORM_MAC check(m_Fd == -1); check(m_Semaphore == -1); FAnsiString EventPath = GetEventPath(); // Create a file to back the semaphore m_Fd = open(*EventPath, O_RDWR | O_CREAT | O_CLOEXEC, 0666); if (m_Fd < 0) { int LastError = errno; UE_LOG(LogZenServiceState, Warning, TEXT("Failed to create named event '%hs' (err: %d)"), *EventPath, LastError); return false; } fchmod(m_Fd, 0666); // Use the file path to generate an IPC key key_t IpcKey = ftok(*EventPath, 1); if (IpcKey < 0) { int LastError = errno; close(m_Fd); m_Fd = -1; UE_LOG(LogZenServiceState, Warning, TEXT("Failed to create an SysV IPC key for named event '%hs' (err: %d)"), *EventPath, LastError); return false; } // Use the key to create/open the semaphore m_Semaphore = semget(IpcKey, 1, 0600 | IPC_CREAT); if (m_Semaphore < 0) { int LastError = errno; close(m_Fd); m_Fd = -1; UE_LOG(LogZenServiceState, Warning, TEXT("Failed creating an SysV semaphore for named event '%hs' (err: %d)"), *EventPath, LastError); return false; } // Atomically claim ownership of the semaphore's key. The owner initializes // the semaphore to 1 so we can use the wait-for-zero op as that does not // modify the semaphore's value on a successful wait. int LockResult = flock(m_Fd, LOCK_EX | LOCK_NB); if (LockResult == 0) { // This isn't thread safe really. Another thread could open the same // semaphore and successfully wait on it in the period of time where // this comment is but before the semaphore's initialised. semctl(m_Semaphore, 0, SETVAL, 1); } return true; #endif } bool ZenSharedEvent::Exists() const { #if PLATFORM_WINDOWS FString FullEventName = GetFullEventName(); HANDLE EventHandle = OpenEventW(READ_CONTROL, false, *FullEventName); if (EventHandle == NULL) { DWORD LastError = GetLastError(); if (LastError != ERROR_FILE_NOT_FOUND) { UE_LOG(LogZenServiceState, Warning, TEXT("Failed checking existance of named event '%s' (err: %d)"), *FullEventName, LastError); } return false; } else { CloseHandle(EventHandle); return false; } #elif PLATFORM_UNIX || PLATFORM_MAC FAnsiString EventPath = GetEventPath(); key_t IpcKey = ftok(*EventPath, 1); if (IpcKey < 0) { int LastError = errno; UE_LOG(LogZenServiceState, Warning, TEXT("Failed to create an SysV IPC key for named event '%hs' (err: %d)"), *EventPath, LastError); return false; } int Semaphore = semget(IpcKey, 1, 0400); if (Semaphore < 0) { int LastError = errno; if (LastError != ENOENT) { UE_LOG(LogZenServiceState, Warning, TEXT("Failed checking named event '%hs' (err: %d)"), *EventPath, LastError); } return false; } return true; #endif } bool ZenSharedEvent::Open() { #if PLATFORM_WINDOWS check(m_EventHandle == NULL); FString FullEventName = GetFullEventName(); m_EventHandle = (void*)OpenEventW(EVENT_MODIFY_STATE, false, *FullEventName); if (m_EventHandle == NULL) { DWORD LastError = GetLastError(); if (LastError != ERROR_FILE_NOT_FOUND) { UE_LOG(LogZenServiceState, Warning, TEXT("Failed opening named event '%s' (err: %d)"), *FullEventName, LastError); } return false; } return true; #elif PLATFORM_UNIX || PLATFORM_MAC check(m_Fd == -1); check(m_Semaphore == -1); FAnsiString EventPath = GetEventPath(); key_t IpcKey = ftok(*EventPath, 1); if (IpcKey < 0) { int LastError = errno; UE_LOG(LogZenServiceState, Warning, TEXT("Failed to create an SysV IPC key for named event '%hs' (err: %d)"), *EventPath, LastError); return false; } m_Semaphore = semget(IpcKey, 1, 0600); if (m_Semaphore < 0) { int LastError = errno; if (LastError != ENOENT) { UE_LOG(LogZenServiceState, Warning, TEXT("Failed opening named event '%hs' (err: %d)"), *EventPath, LastError); } return false; } return true; #endif } bool ZenSharedEvent::Wait(int TimeoutMs) { #if PLATFORM_WINDOWS check(m_EventHandle != NULL); const DWORD Timeout = (TimeoutMs < 0) ? INFINITE : TimeoutMs; DWORD Result = WaitForSingleObject((HANDLE)m_EventHandle, Timeout); if (Result == WAIT_FAILED) { DWORD LastError = GetLastError(); UE_LOG(LogZenServiceState, Warning, TEXT("Failed waiting for named event '%s' (err: %d)"), *m_EventName, LastError); return false; } return (Result == WAIT_OBJECT_0) || (Result == WAIT_ABANDONED_0); #elif PLATFORM_UNIX || PLATFORM_MAC check(m_Semaphore != -1); int Result; struct sembuf SemOp = {}; if (TimeoutMs < 0) { Result = semop(m_Semaphore, &SemOp, 1); if (Result != 0) { int LastError = errno; UE_LOG(LogZenServiceState, Warning, TEXT("Failed waiting for named event '%s' (err: %d)"), *m_EventName, LastError); return false; } return true; } #if PLATFORM_UNIX const int TimeoutSec = TimeoutMs / 1000; struct timespec TimeoutValue = { .tv_sec = TimeoutSec, .tv_nsec = (TimeoutMs - (TimeoutSec * 1000)) * 1000000, }; Result = semtimedop(m_Semaphore, &SemOp, 1, &TimeoutValue); if (Result == 0) { return true; } int LastError = errno; if (LastError != EAGAIN) { UE_LOG(LogZenServiceState, Warning, TEXT("Failed waiting for named event '%s' (err: %d)"), *m_EventName, LastError); } return false; #elif PLATFORM_MAC const unsigned int SleepTimeMs = 10u; SemOp.sem_flg = IPC_NOWAIT; do { Result = semop(m_Semaphore, &SemOp, 1); if (Result == 0) { return true; } else { int LastError = errno; if (errno != EAGAIN) { UE_LOG(LogZenServiceState, Warning, TEXT("Failed waiting for named event '%s' (err: %d)"), *m_EventName, LastError); break; } } usleep(SleepTimeMs * 1000u); TimeoutMs -= SleepTimeMs; } while (TimeoutMs > 0); return false; #endif // PLATFORM_MAC #endif } bool ZenSharedEvent::Set() { #if PLATFORM_WINDOWS check(m_EventHandle != nullptr); if (SetEvent((HANDLE)m_EventHandle)) { return true; } else { DWORD LastError = GetLastError(); UE_LOG(LogZenServiceState, Warning, TEXT("Failed signalling named event '%s' (err: %d)"), *m_EventName, LastError); return false; } #elif PLATFORM_UNIX || PLATFORM_MAC check(m_Semaphore != -1); if (semctl(m_Semaphore, 0, SETVAL, 0) != -1) { return true; } else { int LastError = errno; UE_LOG(LogZenServiceState, Warning, TEXT("Failed signalling named event '%s' (err: %d)"), *m_EventName, LastError); return false; } #endif } void ZenSharedEvent::Close() { #if PLATFORM_WINDOWS if (m_EventHandle != NULL) { CloseHandle((HANDLE)m_EventHandle); } m_EventHandle = NULL; #elif PLATFORM_UNIX || PLATFORM_MAC if (m_Fd != -1) { if (flock(m_Fd, LOCK_EX | LOCK_NB) == 0) { unlink(*m_EventPath); flock(m_Fd, LOCK_UN | LOCK_NB); close(m_Fd); semctl(m_Semaphore, 0, IPC_RMID); } } m_Fd = -1; m_Semaphore = -1; #endif } #if PLATFORM_WINDOWS FString ZenSharedEvent::GetFullEventName() const { TStringBuilder<128> Name; Name << "Local\\"; Name << m_EventName; return *Name; } #elif PLATFORM_UNIX || PLATFORM_MAC FAnsiString ZenSharedEvent::GetEventPath() const { TAnsiStringBuilder<128> Name; Name << "/tmp/"; Name << m_EventName; return *Name; } #endif FString ZenSharedEvent::GetShutdownEventName(uint16 EffectiveListenPort) { return *WriteToWideString<64>(WIDETEXT("Zen_"), EffectiveListenPort, WIDETEXT("_Shutdown")); } FString ZenSharedEvent::GetStartupEventName() { return (*WriteToWideString<64>(WIDETEXT("Zen_"), FPlatformProcess::GetCurrentProcessId(), WIDETEXT("_Startup"))); } static ZenLockFileData ReadLockData(FUniqueBuffer&& FileBytes) { if (ValidateCompactBinary(FileBytes, ECbValidateMode::Default) == ECbValidateError::None) { FCbObject LockObject(FileBytes.MoveToShared()); int32 ProcessId = LockObject["pid"].AsInt32(); FCbObjectId SessionId = LockObject["session_id"].AsObjectId(); int32 EffectivePort = LockObject["port"].AsInt32(); bool IsReady = LockObject["ready"].AsBool(); FUtf8StringView DataDir = LockObject["data"].AsString(); FUtf8StringView ExecutablePath = LockObject["executable"].AsString(); bool IsValid = ProcessId > 0 && EffectivePort > 0 && EffectivePort <= 0xffff; return ZenLockFileData{ .ProcessId = static_cast(ProcessId), .SessionId = SessionId, .EffectivePort = static_cast(EffectivePort), .IsReady = IsReady, .DataDir = FString(DataDir), .ExecutablePath = FString(ExecutablePath), .IsValid = IsValid }; } return ZenLockFileData{}; } bool ZenLockFileData::IsLockFileLocked(const TCHAR* FileName, bool bAttemptCleanUp) { #if PLATFORM_WINDOWS if (bAttemptCleanUp) { IFileManager::Get().Delete(FileName, false, false, true); } return IFileManager::Get().FileExists(FileName); #elif PLATFORM_UNIX || PLATFORM_MAC TAnsiStringBuilder<256> LockFilePath; LockFilePath << FileName; if (bAttemptCleanUp) { int32 Fd = open(LockFilePath.ToString(), O_RDONLY | O_CLOEXEC); if (Fd < 0) { int LastError = errno; if (LastError == ENOENT) { return false; } UE_LOG(LogZenServiceState, Warning, TEXT("Failed opening lock file '%hs' (err: %d)"), *LockFilePath, LastError); return true; } int32 LockRet = flock(Fd, LOCK_EX | LOCK_NB); if (LockRet < 0) { int LastError = errno; close(Fd); if (LastError != EWOULDBLOCK) { UE_LOG(LogZenServiceState, Warning, TEXT("Failed locking lock file '%hs' (err: %d)"), *LockFilePath, LastError); } return true; } unlink(LockFilePath.ToString()); flock(Fd, LOCK_UN); close(Fd); } struct stat Stat; int Res = stat(LockFilePath.ToString(), &Stat); if (Res == 0) { return true; } int LastError = errno; if (LastError == ENOENT) { return false; } UE_LOG(LogZenServiceState, Warning, TEXT("Failed checking stat of '%hs' (err: %d)"), *LockFilePath, LastError); return true; #endif } ZenLockFileData ZenLockFileData::ReadCbLockFile(const TCHAR* FileName) { #if PLATFORM_WINDOWS // Windows specific lock reading path // Uses share flags that are unique to windows to allow us to read file contents while the file may be open for write AND delete by another process (zenserver). uint32 Access = GENERIC_READ; uint32 WinFlags = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; uint32 Create = OPEN_EXISTING; TStringBuilder FullFileNameBuilder; FPathViews::ToAbsolutePath(FileName, FullFileNameBuilder); for (TCHAR& Char : MakeArrayView(FullFileNameBuilder)) { if (Char == TEXT('/')) { Char = TEXT('\\'); } } if (FullFileNameBuilder.Len() >= MAX_PATH) { FullFileNameBuilder.Prepend(TEXTVIEW("\\\\?\\")); } HANDLE Handle = CreateFileW(*FullFileNameBuilder, Access, WinFlags, NULL, Create, FILE_ATTRIBUTE_NORMAL, NULL); if (Handle != INVALID_HANDLE_VALUE) { ON_SCOPE_EXIT{ CloseHandle(Handle); }; LARGE_INTEGER LI; if (GetFileSizeEx(Handle, &LI)) { checkf(LI.QuadPart == LI.u.LowPart, TEXT("Lock file exceeds supported 2GB limit.")); int32 FileSize32 = LI.u.LowPart; if (FileSize32 != 0) { FUniqueBuffer FileBytes = FUniqueBuffer::Alloc(FileSize32); DWORD ReadBytes = 0; if (ReadFile(Handle, FileBytes.GetData(), FileSize32, &ReadBytes, NULL) && (ReadBytes == FileSize32)) { return ReadLockData(std::move(FileBytes)); } } } } return {}; #elif PLATFORM_UNIX || PLATFORM_MAC TAnsiStringBuilder<256> LockFilePath; LockFilePath << FileName; int32 Fd = open(LockFilePath.ToString(), O_RDONLY | O_CLOEXEC); if (Fd != -1) { ON_SCOPE_EXIT{ close(Fd); }; struct stat Stat; int Res = fstat(Fd, &Stat); if (Res == 0) { uint64 FileSize = uint64(Stat.st_size); checkf(FileSize < 2u * 1024u * 1024u * 1024u, TEXT("Lock file exceeds supported 2GB limit.")); if (FileSize != 0) { FUniqueBuffer FileBytes = FUniqueBuffer::Alloc(FileSize); if (read(Fd, FileBytes.GetData(), FileSize) == FileSize) { return ReadLockData(std::move(FileBytes)); } } } } return {}; #endif }