1172 lines
34 KiB
C++
1172 lines
34 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "UbaFile.h"
|
|
#include "UbaDirectoryIterator.h"
|
|
#include "UbaEvent.h"
|
|
#include "UbaPathUtils.h"
|
|
#include "UbaProcessStats.h"
|
|
#include <algorithm>
|
|
|
|
#if PLATFORM_WINDOWS
|
|
#include <io.h>
|
|
#include <psapi.h>
|
|
#include <WinCon.h>
|
|
#include <RestartManager.h>
|
|
#pragma comment(lib, "Rstrtmgr.lib")
|
|
struct FILE_NETWORK_OPEN_INFORMATION { LARGE_INTEGER CreationTime; LARGE_INTEGER LastAccessTime; LARGE_INTEGER LastWriteTime; LARGE_INTEGER ChangeTime; LARGE_INTEGER AllocationSize; LARGE_INTEGER EndOfFile; ULONG FileAttributes; };
|
|
extern "C" NTSTATUS NTAPI NtQueryInformationFile(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation, ULONG Length, FILE_INFORMATION_CLASS FileInformationClass);
|
|
extern "C" NTSTATUS NTAPI NtQueryFullAttributesFile(const OBJECT_ATTRIBUTES *attr, FILE_NETWORK_OPEN_INFORMATION *info);
|
|
#else
|
|
#include <dlfcn.h>
|
|
#include <execinfo.h>
|
|
#include <sys/time.h>
|
|
#endif
|
|
|
|
#if PLATFORM_MAC
|
|
#include <mach-o/dyld.h>
|
|
#include <copyfile.h>
|
|
#endif
|
|
|
|
namespace uba
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
void GetProcessHoldingFile(StringBufferBase& out, const tchar* fileName)
|
|
{
|
|
DWORD dwSession;
|
|
WCHAR szSessionKey[CCH_RM_SESSION_KEY + 1] = { 0 };
|
|
DWORD dwError = RmStartSession(&dwSession, 0, szSessionKey);
|
|
if (dwError != ERROR_SUCCESS)
|
|
return;
|
|
auto sg = MakeGuard([dwSession]() { RmEndSession(dwSession); });
|
|
dwError = RmRegisterResources(dwSession, 1, &fileName, 0, NULL, 0, NULL);
|
|
if (dwError != ERROR_SUCCESS)
|
|
return;
|
|
|
|
DWORD dwReason;
|
|
UINT nProcInfoNeeded;
|
|
UINT nProcInfo = 10;
|
|
RM_PROCESS_INFO rgpi[10] = {};
|
|
dwError = RmGetList(dwSession, &nProcInfoNeeded, &nProcInfo, rgpi, &dwReason);
|
|
if (dwError != ERROR_SUCCESS)
|
|
return;
|
|
|
|
for (u32 i = 0; i < nProcInfo; i++)
|
|
{
|
|
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, rgpi[i].Process.dwProcessId);
|
|
if (!hProcess)
|
|
continue;
|
|
auto pg = MakeGuard([hProcess]() { CloseHandle(hProcess); });
|
|
|
|
FILETIME ftCreate, ftExit, ftKernel, ftUser;
|
|
if (!GetProcessTimes(hProcess, &ftCreate, &ftExit, &ftKernel, &ftUser))
|
|
continue;
|
|
if (CompareFileTime(&rgpi[i].Process.ProcessStartTime, &ftCreate) != 0)
|
|
continue;
|
|
WCHAR sz[MaxPath];
|
|
DWORD cch = MaxPath;
|
|
if (!QueryFullProcessImageNameW(hProcess, 0, sz, &cch))
|
|
continue;
|
|
if (cch <= MaxPath)
|
|
out.Appendf(TC(" - %s"), sz); // Using Appendf to have capacity check
|
|
}
|
|
}
|
|
|
|
HANDLE asHANDLE(FileHandle fh)
|
|
{
|
|
return (HANDLE)(fh == InvalidFileHandle ? InvalidFileHandle : (fh & FileHandleFlagMask));
|
|
}
|
|
|
|
#define PREFIX_FILENAME(fileName, prefix) \
|
|
UBA_ASSERT(TStrlen(fileName) < MaxPath); \
|
|
StringBuffer<MaxPath> STRING_JOIN(longName, __LINE__); \
|
|
if (IsAbsolutePath(fileName) && !IsUncPath(fileName)) \
|
|
{ \
|
|
auto& lsb = STRING_JOIN(longName, __LINE__); \
|
|
lsb.Append(TC(prefix)); \
|
|
FixPath(fileName, nullptr, 0, lsb); \
|
|
fileName = lsb.data; \
|
|
}
|
|
#define MAKE_LONG_FILENAME(fileName) PREFIX_FILENAME(fileName, "\\\\?\\")
|
|
#define MAKE_NT_FILENAME(fileName) PREFIX_FILENAME(fileName, "\\??\\")
|
|
|
|
#else
|
|
int asFileDescriptor(FileHandle fh)
|
|
{
|
|
if (fh == InvalidFileHandle)
|
|
return (int)fh;
|
|
return (int)(fh & FileHandleFlagMask);
|
|
}
|
|
#endif
|
|
|
|
|
|
bool ReadFile(Logger& logger, const tchar* fileName, FileHandle fileHandle, void* b, u64 bufferLen)
|
|
{
|
|
auto& stats = KernelStats::GetCurrent();
|
|
ExtendedTimerScope ts(stats.readFile);
|
|
u8* buffer = (u8*)b;
|
|
u64 readLeft = bufferLen;
|
|
u64 firstZeroReadTime = 0;
|
|
|
|
while (readLeft)
|
|
{
|
|
u32 toRead = u32(Min(readLeft, u64(~u32(0)) - 1));
|
|
#if PLATFORM_WINDOWS
|
|
DWORD wasRead = 0;
|
|
if (!::ReadFile(asHANDLE(fileHandle), buffer, toRead, &wasRead, NULL))
|
|
if (GetLastError() != ERROR_IO_PENDING)
|
|
return logger.Error(TC("ERROR reading %llu bytes from file %s (error: %s)"), toRead, fileName, LastErrorToText().data);
|
|
#else
|
|
ssize_t wasRead = read(asFileDescriptor(fileHandle), buffer, toRead);
|
|
if (wasRead == -1)
|
|
{
|
|
UBA_ASSERTF(false, TC("ERROR ReadFile error handling not implemented (Trying to read %u bytes from fd %llu)"), toRead, fileHandle);
|
|
return false;
|
|
}
|
|
#endif
|
|
if (wasRead == 0)
|
|
{
|
|
if (firstZeroReadTime == 0)
|
|
firstZeroReadTime = GetTime();
|
|
else if (TimeToMs(GetTime() - firstZeroReadTime) > 3*1000)
|
|
return logger.Error(TC("ERROR reading file %s trying to read %u bytes from offset %llu but ReadFile returns 0 bytes read.. Is the file big enough?"), fileName, toRead, bufferLen - readLeft);
|
|
}
|
|
|
|
readLeft -= wasRead;
|
|
buffer += wasRead;
|
|
}
|
|
|
|
stats.readFile.bytes += bufferLen;
|
|
return true;
|
|
}
|
|
|
|
UBA_NOINLINE bool ReportOpenFileError(Logger& logger, const tchar* fileName, u32 lastError) // To prevent stack usage
|
|
{
|
|
StringBuffer<4096> additionalInfo;
|
|
#if PLATFORM_WINDOWS
|
|
if (lastError == ERROR_SHARING_VIOLATION)
|
|
GetProcessHoldingFile(additionalInfo, fileName);
|
|
#endif
|
|
return logger.Error(TC("ERROR opening file %s for read (%s%s)"), fileName, LastErrorToText(lastError).data, additionalInfo.data);
|
|
}
|
|
|
|
bool OpenFileSequentialRead(Logger& logger, const tchar* fileName, FileHandle& outHandle, bool fileNotFoundIsError, bool overlapped)
|
|
{
|
|
u32 dwFlagsAndAttributes = DefaultAttributes();
|
|
#if PLATFORM_WINDOWS
|
|
dwFlagsAndAttributes |= (overlapped ? FILE_FLAG_OVERLAPPED : FILE_FLAG_SEQUENTIAL_SCAN);
|
|
MAKE_LONG_FILENAME(fileName);
|
|
#endif
|
|
|
|
outHandle = uba::CreateFileW(fileName, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, OPEN_EXISTING, dwFlagsAndAttributes);
|
|
if (outHandle != InvalidFileHandle)
|
|
return true;
|
|
u32 lastError = GetLastError();
|
|
if (lastError == ERROR_FILE_NOT_FOUND || lastError == ERROR_PATH_NOT_FOUND)
|
|
return !fileNotFoundIsError;
|
|
if (lastError == ERROR_ACCESS_DENIED)
|
|
{
|
|
// Remotes can ask for files via storage (SessionServer::HandleMessage->StoreCasFile->StorageImpl::StoreCasFile->this) and sometimes they exist as folders (but should be treated as not existing)
|
|
u32 attr = GetFileAttributesW(fileName);
|
|
if (attr != INVALID_FILE_ATTRIBUTES)
|
|
if (IsDirectory(attr))
|
|
return false;
|
|
}
|
|
return ReportOpenFileError(logger, fileName, lastError);
|
|
}
|
|
|
|
bool GetFileBasicInformationByHandle(FileBasicInformation& out, Logger& logger, const tchar* fileName, FileHandle hFile, bool errorOnFail)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
ExtendedTimerScope ts(KernelStats::GetCurrent().getFileInfo);
|
|
|
|
if (IsRunningWine())
|
|
return GetFileBasicInformation(out, logger, fileName, errorOnFail);
|
|
IO_STATUS_BLOCK b;
|
|
FILE_NETWORK_OPEN_INFORMATION info;
|
|
NTSTATUS res = NtQueryInformationFile(asHANDLE(hFile), &b, &info, sizeof(info), (FILE_INFORMATION_CLASS)34); // FileNetworkOpenInformation
|
|
if (res != STATUS_SUCCESS)
|
|
return errorOnFail ? logger.Error(TC("GetFileBasicInformationByHandle (NtQueryInformationFile) failed on %s (0x%x)"), fileName, res) : false;
|
|
out.attributes = info.FileAttributes;
|
|
out.lastWriteTime = info.LastWriteTime.QuadPart;
|
|
out.size = info.EndOfFile.QuadPart;
|
|
return true;
|
|
#else
|
|
FileInformation info;
|
|
if (!GetFileInformationByHandle(info, logger, fileName, hFile))
|
|
return false;
|
|
out.attributes = info.attributes;
|
|
out.lastWriteTime = info.lastWriteTime;
|
|
out.size = info.size;
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
bool GetFileBasicInformation(FileBasicInformation& out, Logger& logger, const tchar* fileName, bool errorOnFail)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
ExtendedTimerScope ts(KernelStats::GetCurrent().getFileInfo);
|
|
FILE_NETWORK_OPEN_INFORMATION info;
|
|
MAKE_NT_FILENAME(fileName);
|
|
UNICODE_STRING us;
|
|
us.Length = USHORT(TStrlen(fileName)*sizeof(tchar));
|
|
us.MaximumLength = us.Length;
|
|
us.Buffer = (tchar*)fileName;
|
|
OBJECT_ATTRIBUTES oa;
|
|
InitializeObjectAttributes(&oa, &us, OBJ_CASE_INSENSITIVE, NULL, NULL);
|
|
NTSTATUS res = NtQueryFullAttributesFile(&oa, &info);
|
|
if (res != STATUS_SUCCESS)
|
|
return errorOnFail ? logger.Error(TC("GetFileBasicInformationByHandle (NtQueryFullAttributesFile) failed on %s (0x%x)"), fileName, res) : false;
|
|
out.attributes = info.FileAttributes;
|
|
out.lastWriteTime = info.LastWriteTime.QuadPart;
|
|
out.size = info.EndOfFile.QuadPart;
|
|
return true;
|
|
#else
|
|
FileInformation info;
|
|
if (!GetFileInformation(info, logger, fileName))
|
|
return false;
|
|
out.attributes = info.attributes;
|
|
out.lastWriteTime = info.lastWriteTime;
|
|
out.size = info.size;
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
bool GetFileInformationByHandle(FileInformation& out, Logger& logger, const tchar* fileName, FileHandle hFile)
|
|
{
|
|
ExtendedTimerScope ts(KernelStats::GetCurrent().getFileInfo);
|
|
#if PLATFORM_WINDOWS
|
|
BY_HANDLE_FILE_INFORMATION info;
|
|
if (!::GetFileInformationByHandle(asHANDLE(hFile), &info))
|
|
return logger.Error(TC("GetFileInformationByHandle failed on %s (%s)"), fileName, LastErrorToText().data);
|
|
out.attributes = info.dwFileAttributes;
|
|
out.volumeSerialNumber = info.dwVolumeSerialNumber;
|
|
out.lastWriteTime = (u64&)info.ftLastWriteTime;
|
|
out.size = u64(ToLargeInteger(info.nFileSizeHigh, info.nFileSizeLow).QuadPart);
|
|
out.index = u64(ToLargeInteger(info.nFileIndexHigh, info.nFileIndexLow).QuadPart);
|
|
return true;
|
|
#else
|
|
struct stat attr;
|
|
int res = fstat(asFileDescriptor(hFile), &attr);
|
|
if (res != 0)
|
|
return logger.Error(TC("GetFileInformationByHandle (fstat) failed on %s (%s)"), fileName, strerror(errno));
|
|
out.lastWriteTime = FromTimeSpec(attr.st_mtimespec);
|
|
out.attributes = attr.st_mode;
|
|
out.volumeSerialNumber = attr.st_dev;
|
|
out.index = attr.st_ino;
|
|
out.size = attr.st_size;
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
bool GetFileInformation(FileInformation& out, Logger& logger, const tchar* fileName)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
MAKE_LONG_FILENAME(fileName);
|
|
FileHandle h = uba::CreateFileW(fileName, 0, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS);
|
|
if (h == InvalidFileHandle)
|
|
return false;// logger.Error(TC("GetFileInformation: CreateFile failed for file %s (%s)"), fileName, LastErrorToText().data);
|
|
auto handleGuard = MakeGuard([&]() { uba::CloseFile(fileName, h); });
|
|
if (!GetFileInformationByHandle(out, logger, fileName, h))
|
|
return false;// logger.Error(TC("Failed to get file information for %s while checking file added for write. This should not happen! (%s)"), fileName, LastErrorToText().data);
|
|
return true;
|
|
#else
|
|
struct stat attr;
|
|
int res = stat(fileName, &attr);
|
|
if (res != 0)
|
|
{
|
|
if (errno != ENOENT)
|
|
logger.Warning(TC("GetFileInformation: stat failed for file %s and this error handling not implemented (%s)"), fileName, strerror(errno));
|
|
SetLastError(ERROR_FILE_NOT_FOUND);
|
|
return false;
|
|
}
|
|
out.lastWriteTime = FromTimeSpec(attr.st_mtimespec);
|
|
out.attributes = attr.st_mode;
|
|
out.volumeSerialNumber = attr.st_dev;
|
|
out.index = attr.st_ino;
|
|
out.size = attr.st_size;
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
bool FileExists(Logger& logger, const tchar* fileName, u64* outSize, u32* outAttributes, u64* lastWriteTime)
|
|
{
|
|
ExtendedTimerScope ts(KernelStats::GetCurrent().getFileInfo);
|
|
#if PLATFORM_WINDOWS
|
|
MAKE_LONG_FILENAME(fileName);
|
|
WIN32_FILE_ATTRIBUTE_DATA data;
|
|
if (!::GetFileAttributesExW(fileName, GetFileExInfoStandard, &data))
|
|
{
|
|
DWORD lastError = GetLastError();
|
|
if (lastError != ERROR_FILE_NOT_FOUND && lastError != ERROR_PATH_NOT_FOUND)
|
|
logger.Error(TC("GetFileAttributesW failed on %s (%s)"), fileName, LastErrorToText(lastError).data);
|
|
return false;
|
|
}
|
|
|
|
if (outSize)
|
|
{
|
|
LARGE_INTEGER li;
|
|
li.HighPart = (LONG)data.nFileSizeHigh;
|
|
li.LowPart = data.nFileSizeLow;
|
|
*outSize = u64(li.QuadPart);
|
|
}
|
|
if (outAttributes)
|
|
*outAttributes = data.dwFileAttributes;
|
|
|
|
if (lastWriteTime)
|
|
*lastWriteTime = (u64&)data.ftLastWriteTime;
|
|
|
|
return true;
|
|
#else
|
|
struct stat attr;
|
|
if (stat(fileName, &attr) == -1)
|
|
{
|
|
if (errno == ENOENT)
|
|
{
|
|
SetLastError(ERROR_FILE_NOT_FOUND);
|
|
return false;
|
|
}
|
|
if (errno == ENOTDIR)
|
|
{
|
|
SetLastError(ERROR_PATH_NOT_FOUND);
|
|
return false;
|
|
}
|
|
UBA_ASSERTF(false, TC("FileExists error handling for %u is not implemented (%s)"), errno, strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
if (outSize)
|
|
*outSize = attr.st_size;
|
|
if (outAttributes)
|
|
*outAttributes = attr.st_mode;
|
|
if (lastWriteTime)
|
|
*lastWriteTime = FromTimeSpec(attr.st_mtimespec);
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
bool SetFilePointer(Logger& logger, const tchar* fileName, FileHandle handle, u64 position)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
if (!::SetFilePointerEx(asHANDLE(handle), ToLargeInteger(position), NULL, FILE_BEGIN))
|
|
return logger.Error(TC("SetFilePointerEx failed on %s (%s)"), fileName, LastErrorToText().data);
|
|
return true;
|
|
#else
|
|
if (lseek(asFileDescriptor(handle), position, SEEK_SET) != position)
|
|
return logger.Error("lseek to %llu failed for %s: %s", position, fileName, strerror(errno));
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
bool SetEndOfFile(Logger& logger, const tchar* fileName, FileHandle handle, u64 size)
|
|
{
|
|
ExtendedTimerScope ts(KernelStats::GetCurrent().setFileInfo);
|
|
#if PLATFORM_WINDOWS
|
|
FILE_END_OF_FILE_INFO info;
|
|
info.EndOfFile = ToLargeInteger(size);
|
|
if (!::SetFileInformationByHandle(asHANDLE(handle), FileEndOfFileInfo, &info, sizeof(info)))
|
|
return logger.Error(TC("SetFileInformationByHandle failed on %s (%s)"), fileName, LastErrorToText().data);
|
|
return true;
|
|
#else
|
|
UBA_ASSERTF(false, TC("SetEndOfFile not implemented"));
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool GetDirectoryOfCurrentModule(Logger& logger, StringBufferBase& out)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
HMODULE hm = NULL;
|
|
if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCWSTR)&GetDirectoryOfCurrentModule, &hm))
|
|
return logger.Error(TC("GetModuleHandleEx failed (%s)"), LastErrorToText().data);
|
|
u32 len = GetModuleFileNameW(hm, out.data + out.count, out.capacity - out.count);
|
|
if (!len)
|
|
return logger.Error(TC("GetModuleFileNameW failed (%s)"), LastErrorToText().data);
|
|
out.count += len;
|
|
UBA_ASSERTF(GetLastError() == ERROR_SUCCESS, TC("GetModuleFileNameW failed (%s)"), LastErrorToText().data);
|
|
const tchar* lastSlash = out.Last('\\');
|
|
out.Resize(lastSlash - out.data);
|
|
return true;
|
|
|
|
#else
|
|
// Can be a shared library, not only executable.. so can't use /proc/self/exe
|
|
Dl_info info;
|
|
if (!dladdr((void*)&GetDirectoryOfCurrentModule, &info))
|
|
return logger.Error("dladdr failed to get info for address to GetDirectoryOfCurrentModule");
|
|
out.count = GetFullPathNameW(info.dli_fname, out.capacity, out.data, nullptr);
|
|
if (!out.count)
|
|
return logger.Error("GetFullPathNameW failed to return full name for %s", info.dli_fname);
|
|
char* lastSlash = strrchr(out.data, '/');
|
|
out.Resize(lastSlash - out.data);
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
bool DeleteAllFiles(Logger& logger, const tchar* dir, bool deleteDir, u32* count)
|
|
{
|
|
bool success = true;
|
|
bool traverseRes = TraverseDir(logger, ToView(dir),
|
|
[&](const DirectoryEntry& e)
|
|
{
|
|
StringBuffer<> fullPath(dir);
|
|
fullPath.EnsureEndsWithSlash().Append(e.name);
|
|
|
|
if (IsReadOnly(e.attributes))
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
SetFileAttributesW(fullPath.data, e.attributes & ~FILE_ATTRIBUTE_READONLY);
|
|
#else
|
|
UBA_ASSERT(false);
|
|
#endif
|
|
}
|
|
|
|
if (IsDirectory(e.attributes))
|
|
{
|
|
if (!DeleteAllFiles(logger, fullPath.data, true, count))
|
|
success = false;
|
|
}
|
|
else
|
|
{
|
|
if (!DeleteFileW(fullPath.data))
|
|
{
|
|
logger.Warning(TC("Failed to delete file %s (%s)"), fullPath.data, LastErrorToText().data);
|
|
success = false;
|
|
}
|
|
else if (count)
|
|
++(*count);
|
|
}
|
|
});
|
|
|
|
if (!traverseRes || !success)
|
|
return false;
|
|
|
|
if (!deleteDir)
|
|
return true;
|
|
|
|
if (RemoveDirectoryW(dir))
|
|
return true;
|
|
|
|
u32 lastError = GetLastError();
|
|
|
|
if (lastError == ERROR_FILE_NOT_FOUND || lastError == ERROR_PATH_NOT_FOUND)
|
|
return true;
|
|
|
|
logger.Warning(TC("Failed to delete directory %s (%s)"), dir, LastErrorToText(lastError).data);
|
|
return false;
|
|
}
|
|
|
|
bool SearchPathForFile(Logger& logger, StringBufferBase& out, const tchar* file, StringView workingDir, StringView applicationDir)
|
|
{
|
|
UBA_ASSERT(!IsAbsolutePath(file));
|
|
|
|
StringBuffer<> fullPath;
|
|
|
|
auto TestFileExists = [&](const tchar* extraInfo)
|
|
{
|
|
if (GetFileAttributesW(fullPath.data) != INVALID_FILE_ATTRIBUTES)
|
|
{
|
|
FixPath(fullPath.data, nullptr, 0, out);
|
|
return true;
|
|
}
|
|
u32 lastError = GetLastError();
|
|
if (lastError != ERROR_FILE_NOT_FOUND && lastError != ERROR_PATH_NOT_FOUND)
|
|
logger.Warning(TC("SearchPathForFile tried to find the file %s%s but got error when getting attributes (%s)"), fullPath.data, extraInfo, LastErrorToText(lastError).data);
|
|
return false;
|
|
};
|
|
|
|
if (applicationDir.count)
|
|
{
|
|
fullPath.Append(applicationDir).EnsureEndsWithSlash().Append(file);
|
|
if (TestFileExists(TC("")))
|
|
return true;
|
|
}
|
|
|
|
if (workingDir.count)
|
|
{
|
|
fullPath.Clear().Append(workingDir).EnsureEndsWithSlash().Append(file);
|
|
if (TestFileExists(TC("")))
|
|
return true;
|
|
}
|
|
|
|
char varSeparator = IsWindows ? ';' : ':';
|
|
tchar buff[32 * 1024] = { 0 };
|
|
u32 len = GetEnvironmentVariableW(TC("PATH"), buff, sizeof_array(buff));
|
|
if (!len || len == sizeof_array(buff))
|
|
return logger.Error(TC("Failed to get PATH environment variable"));
|
|
if (len >= sizeof_array(buff))
|
|
return logger.Error(TC("Failed to get PATH variable, buffer too small (need %u)"), len);
|
|
|
|
tchar* lastStart = buff;
|
|
tchar* it = buff;
|
|
bool end = false;
|
|
while (!end)
|
|
{
|
|
end = *it == 0;
|
|
if (*it != varSeparator && !end)
|
|
{
|
|
++it;
|
|
continue;
|
|
}
|
|
|
|
*it = 0;
|
|
|
|
fullPath.Clear().Append(lastStart);
|
|
if (*lastStart)
|
|
fullPath.EnsureEndsWithSlash();
|
|
fullPath.Append(file);
|
|
|
|
if (TestFileExists(TC(" using PATH environment variable")))
|
|
return true;
|
|
|
|
lastStart = ++it;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
FileHandle CreateFileW(const tchar* fileName, u32 desiredAccess, u32 shareMode, u32 createDisp, u32 flagsAndAttributes)
|
|
{
|
|
ExtendedTimerScope ts(KernelStats::GetCurrent().createFile);
|
|
#if PLATFORM_WINDOWS
|
|
MAKE_LONG_FILENAME(fileName);
|
|
return (FileHandle)(u64)::CreateFileW(fileName, desiredAccess, shareMode, NULL, createDisp, flagsAndAttributes, NULL);
|
|
#else
|
|
int flags = O_CLOEXEC;
|
|
if (createDisp == CREATE_ALWAYS)
|
|
flags |= O_CREAT | O_TRUNC;
|
|
else if (createDisp == OPEN_EXISTING)
|
|
flags = O_NONBLOCK;
|
|
else
|
|
UBA_ASSERTF(false, TC("CreateFileW create disposition %u not supported"), createDisp);
|
|
|
|
if ((desiredAccess & (GENERIC_WRITE | GENERIC_READ)) == (GENERIC_WRITE | GENERIC_READ))
|
|
flags |= O_RDWR;
|
|
else if (desiredAccess & GENERIC_WRITE)
|
|
flags |= O_WRONLY;
|
|
else if (desiredAccess & GENERIC_READ)
|
|
flags |= O_RDONLY;
|
|
else if (!desiredAccess)
|
|
flags = O_RDONLY;
|
|
else
|
|
UBA_ASSERTF(false, TC("CreateFileW desired access %u not supported"), desiredAccess);
|
|
int mode = flagsAndAttributes;
|
|
int fd = open(fileName, flags, mode);
|
|
if (fd != -1)
|
|
{
|
|
struct stat attr;
|
|
int res = fstat(fd, &attr);
|
|
if (res != 0)
|
|
{
|
|
UBA_ASSERTF(false, TC("CreateFileW (fstat) error handling for %u (%s) not implemented"), errno, strerror(errno));
|
|
}
|
|
if (S_ISREG(attr.st_mode))
|
|
return (FileHandle)fd;
|
|
close(fd);
|
|
SetLastError(ERROR_ACCESS_DENIED);
|
|
return InvalidFileHandle;
|
|
}
|
|
|
|
if (errno == ENOENT)
|
|
{
|
|
SetLastError(ERROR_FILE_NOT_FOUND);
|
|
return InvalidFileHandle;
|
|
}
|
|
|
|
if (errno == ENOTDIR)
|
|
{
|
|
SetLastError(ERROR_PATH_NOT_FOUND);
|
|
return InvalidFileHandle;
|
|
}
|
|
|
|
if (errno == EACCES)
|
|
{
|
|
SetLastError(ERROR_ACCESS_DENIED);
|
|
return InvalidFileHandle;
|
|
}
|
|
|
|
UBA_ASSERTF(false, TC("CreateFileW failed for %s - Error handling for %u (%s) not implemented"), fileName, errno, strerror(errno));
|
|
return InvalidFileHandle;
|
|
#endif
|
|
}
|
|
|
|
bool CloseFile(const tchar* fileName, FileHandle h)
|
|
{
|
|
ExtendedTimerScope ts(KernelStats::GetCurrent().closeFile);
|
|
if (h == InvalidFileHandle)
|
|
return true;
|
|
|
|
#if PLATFORM_WINDOWS
|
|
return ::CloseHandle(asHANDLE(h));
|
|
#else
|
|
if (close(asFileDescriptor(h)) == 0)
|
|
return true;
|
|
UBA_ASSERTF(false, TC("CloseFile error handling not implemented while failing to close %s (%s)"), fileName, strerror(errno));
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool CreateDirectoryW(const tchar* pathName)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
MAKE_LONG_FILENAME(pathName);
|
|
return ::CreateDirectoryW(pathName, NULL);
|
|
#else
|
|
if (mkdir(pathName, 0777) == 0)
|
|
{
|
|
SetLastError(ERROR_SUCCESS);
|
|
return true;
|
|
}
|
|
if (errno == EEXIST)
|
|
{
|
|
SetLastError(ERROR_ALREADY_EXISTS);
|
|
return false;
|
|
}
|
|
if (errno == ENOENT || errno == ENOTDIR)
|
|
{
|
|
SetLastError(ERROR_PATH_NOT_FOUND);
|
|
return false;
|
|
}
|
|
UBA_ASSERTF(false, TC("CreateDirectoryW failed creating %s - error handling %i not handled (%s)"), pathName, errno, strerror(errno));
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool RemoveDirectoryW(const tchar* pathName)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
MAKE_LONG_FILENAME(pathName);
|
|
if (::RemoveDirectoryW(pathName))
|
|
return true;
|
|
return false;
|
|
#else
|
|
int res = rmdir(pathName);
|
|
if (res == 0)
|
|
return true;
|
|
if (errno == ENOENT)
|
|
{
|
|
SetLastError(ERROR_FILE_NOT_FOUND);
|
|
return false;
|
|
}
|
|
UBA_ASSERTF(false, TC("RemoveDirectoryW error handling not implemented (%s): %s"), strerror(errno), pathName);
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool DeleteFileW(const tchar* fileName)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
MAKE_LONG_FILENAME(fileName);
|
|
if (::DeleteFileW(fileName))
|
|
return true;
|
|
return false;
|
|
#else
|
|
int res = remove(fileName);
|
|
if (res == 0)
|
|
return true;
|
|
if (errno == ENOENT)
|
|
{
|
|
SetLastError(ERROR_FILE_NOT_FOUND);
|
|
return false;
|
|
}
|
|
if (errno == EPERM)
|
|
{
|
|
SetLastError(ERROR_ACCESS_DENIED);
|
|
return false;
|
|
}
|
|
|
|
UBA_ASSERTF(false, TC("DeleteFileW failed on %s - Error handling not implemented (%s)"), fileName, strerror(errno));
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool CopyFileW(const tchar* existingFileName, const tchar* newFileName, bool bFailIfExists)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
MAKE_LONG_FILENAME(existingFileName);
|
|
MAKE_LONG_FILENAME(newFileName);
|
|
return ::CopyFileW(existingFileName, newFileName, bFailIfExists);
|
|
#elif PLATFORM_MAC
|
|
if (copyfile(existingFileName, newFileName, 0, COPYFILE_ALL) == 0)
|
|
return true;
|
|
UBA_ASSERTF(false, TC("CopyFileW failed on %s - Error handling not implemented (%s)"), existingFileName, strerror(errno));
|
|
return false;
|
|
#else
|
|
|
|
UBA_ASSERTF(false, TC("CopyFileW not implemented (From %s to %s)"), existingFileName, newFileName);
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
u32 GetLongPathNameW(const tchar* lpszShortPath, tchar* lpszLongPath, u32 cchBuffer)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
return ::GetLongPathNameW(lpszShortPath, lpszLongPath, cchBuffer);
|
|
#else
|
|
UBA_ASSERTF(false, TC("GetLongPathNameW not implemented"));
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool GetFileLastWriteTime(u64& outTime, FileHandle hFile)
|
|
{
|
|
ExtendedTimerScope ts(KernelStats::GetCurrent().getFileTime);
|
|
#if PLATFORM_WINDOWS
|
|
FILETIME lastWriteTime;
|
|
auto res = ::GetFileTime(asHANDLE(hFile), NULL, NULL, &lastWriteTime);
|
|
outTime = (u64&)lastWriteTime;
|
|
return res;
|
|
#else
|
|
struct stat attr;
|
|
int res = fstat(asFileDescriptor(hFile), &attr);
|
|
if (res != 0)
|
|
{
|
|
UBA_ASSERTF(errno == ENOENT, TC("GetFileLastWriteTime (fstat) error handling not implemented: %s"), strerror(errno));
|
|
SetLastError(ERROR_FILE_NOT_FOUND);
|
|
return false;
|
|
}
|
|
outTime = FromTimeSpec(attr.st_mtimespec);
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
bool SetFileLastWriteTime(FileHandle fileHandle, u64 writeTime)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
return SetFileTime(asHANDLE(fileHandle), (FILETIME*)&writeTime, NULL, (FILETIME*)&writeTime);
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
bool MoveFileExW(const tchar* existingFileName, const tchar* newFileName, u32 dwFlags)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
MAKE_LONG_FILENAME(existingFileName);
|
|
MAKE_LONG_FILENAME(newFileName);
|
|
return ::MoveFileExW(existingFileName, newFileName, dwFlags);
|
|
#else
|
|
int res = rename(existingFileName, newFileName);
|
|
if (res == 0)
|
|
{
|
|
SetLastError(ERROR_SUCCESS);
|
|
return true;
|
|
}
|
|
UBA_ASSERTF(false, TC("MoveFileExW error handling not implemented"));
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool GetFileSizeEx(u64& outFileSize, FileHandle hFile)
|
|
{
|
|
ExtendedTimerScope ts(KernelStats::GetCurrent().getFileInfo);
|
|
#if PLATFORM_WINDOWS
|
|
LARGE_INTEGER lpFileSize;
|
|
if (!::GetFileSizeEx(asHANDLE(hFile), &lpFileSize))
|
|
return false;
|
|
outFileSize = u64(lpFileSize.QuadPart);
|
|
return true;
|
|
#else
|
|
struct stat attr;
|
|
int res = fstat(asFileDescriptor(hFile), &attr);
|
|
if (res == 0)
|
|
{
|
|
outFileSize = attr.st_size;
|
|
return true;
|
|
}
|
|
|
|
UBA_ASSERTF(false, TC("GetFileSizeEx error handling not implemented"));
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
u32 GetFileAttributesW(const tchar* fileName)
|
|
{
|
|
ExtendedTimerScope ts(KernelStats::GetCurrent().getFileInfo);
|
|
#if PLATFORM_WINDOWS
|
|
MAKE_LONG_FILENAME(fileName);
|
|
return ::GetFileAttributesW(fileName);
|
|
#else
|
|
struct stat attr;
|
|
if (stat(fileName, &attr) == -1)
|
|
{
|
|
if (errno == ENOENT)
|
|
{
|
|
SetLastError(ERROR_FILE_NOT_FOUND);
|
|
return INVALID_FILE_ATTRIBUTES;
|
|
}
|
|
if (errno == ENOTDIR)
|
|
{
|
|
SetLastError(ERROR_DIRECTORY);
|
|
return INVALID_FILE_ATTRIBUTES;
|
|
}
|
|
UBA_ASSERTF(false, TC("GetFileAttributesW error handling not implemented %s (%s)"), fileName, strerror(errno));
|
|
return INVALID_FILE_ATTRIBUTES;
|
|
}
|
|
return attr.st_mode;
|
|
#endif
|
|
}
|
|
|
|
bool IsReadOnly(u32 attributes)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
return (attributes & FILE_ATTRIBUTE_READONLY) != 0;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
u32 DefaultAttributes(bool execute)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
return FILE_ATTRIBUTE_NORMAL;
|
|
#else
|
|
return S_IRUSR | S_IWUSR | (execute ? S_IXUSR : 0) | S_IRGRP | S_IROTH;
|
|
#endif
|
|
}
|
|
|
|
bool CreateHardLinkW(const tchar* newFileName, const tchar* existingFileName)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
MAKE_LONG_FILENAME(newFileName);
|
|
MAKE_LONG_FILENAME(existingFileName);
|
|
return ::CreateHardLinkW(newFileName, existingFileName, NULL);
|
|
#else
|
|
int res = link(existingFileName, newFileName); // We need to use links in order for explicit dynamic library dependencies to be found at the same path.
|
|
//int res = symlink(existingFileName, newFileName);
|
|
if (res == 0)
|
|
return true;
|
|
|
|
#if PLATFORM_MAC
|
|
if (errno == EPERM) // Because of System Integrity Protection we might not be allowed to link this file, fallback to copy
|
|
return false;
|
|
#endif
|
|
if (errno == EEXIST)
|
|
{
|
|
SetLastError(ERROR_ALREADY_EXISTS);
|
|
return false;
|
|
}
|
|
UBA_ASSERTF(false, TC("CreateHardLinkW %s to %s error handling not implemented (%s)"), existingFileName, newFileName, strerror(errno));
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
u32 GetFullPathNameW(const tchar* fileName, u32 nBufferLength, tchar* lpBuffer, tchar** lpFilePart)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
return ::GetFullPathNameW(fileName, nBufferLength, lpBuffer, lpFilePart);
|
|
#else
|
|
char source[1024];
|
|
size_t sourceStart = 0;
|
|
size_t fileNameStart = 0;
|
|
|
|
if (fileName[0] == '~' && fileName[1] == '/')
|
|
{
|
|
strcpy(source, getenv("HOME"));
|
|
sourceStart = strlen(source);
|
|
source[sourceStart++] = '/';
|
|
fileNameStart = 2;
|
|
TSprintf_s(source + sourceStart, 1024 - sourceStart, "%s", fileName + fileNameStart);
|
|
return strlen(strcpy(lpBuffer, source));
|
|
}
|
|
|
|
#if 0
|
|
char fullPath[1024];
|
|
if (!realpath(fileName, fullPath))
|
|
{
|
|
UBA_ASSERTF(false, TC("realpath error handling not implemented for path %s (%s)"), fileName, strerror(errno));
|
|
return 0;
|
|
}
|
|
u32 len = TStrlen(fullPath);
|
|
UBA_ASSERT(len < sizeof_array(fullPath));
|
|
#elif 0
|
|
const char* fullPath = canonicalize_file_name(fileName);
|
|
u32 len = TStrlen(fullPath);
|
|
#else
|
|
char cwd[PATH_MAX];
|
|
if (!getcwd(cwd, PATH_MAX))
|
|
{
|
|
UBA_ASSERT(false);
|
|
return 0;
|
|
}
|
|
u32 cwdlen = u32(strlen(cwd));
|
|
cwd[cwdlen++] = '/';
|
|
cwd[cwdlen] = 0;
|
|
|
|
char fullPath[1024];
|
|
u32 len;
|
|
if (!FixPath2(fileName, cwd, cwdlen, fullPath, sizeof(fullPath), &len))
|
|
return 0;
|
|
#endif
|
|
UBA_ASSERT(len < nBufferLength);
|
|
memcpy(lpBuffer, fullPath, len + 1);
|
|
return len;
|
|
#endif
|
|
}
|
|
|
|
bool SearchPathW(const tchar* a, const tchar* b, const tchar* c, u32 d, tchar* e, tchar** f)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
return ::SearchPathW(a, b, c, d, e, f);
|
|
#else
|
|
UBA_ASSERTF(false, TC("SearchPathW not implemented"));
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
u64 GetSystemTimeAsFileTime()
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
FILETIME temp;
|
|
::GetSystemTimeAsFileTime(&temp); // Contains a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC).
|
|
return u64(ToLargeInteger(temp.dwHighDateTime, temp.dwLowDateTime).QuadPart);
|
|
#else
|
|
timeval tv;
|
|
gettimeofday(&tv, NULL);
|
|
return u64(tv.tv_sec) * 10'000'000ull + u64(tv.tv_usec)*10ull;
|
|
#endif
|
|
}
|
|
|
|
u64 GetFileTimeAsSeconds(u64 fileTime)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
return TimeToMs(fileTime)/1000;
|
|
#else
|
|
return fileTime / 10'000'000ull;
|
|
#endif
|
|
}
|
|
|
|
u64 GetFileTimeAsTime(u64 fileTime)
|
|
{
|
|
return MsToTime(GetFileTimeAsSeconds(fileTime)*1000);
|
|
}
|
|
|
|
u64 GetSecondsAsFileTime(u64 seconds)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
return MsToTime(seconds*1000);
|
|
#else
|
|
return seconds * 10'000'000ull;
|
|
#endif
|
|
}
|
|
|
|
bool GetCurrentDirectoryW(StringBufferBase& out)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
u32 res = ::GetCurrentDirectoryW(out.capacity, out.data);
|
|
if (!res || res > out.capacity)
|
|
{
|
|
UBA_ASSERT(false);
|
|
return false;
|
|
}
|
|
out.count = res;
|
|
return true;
|
|
#else
|
|
if (!getcwd(out.data, out.capacity))
|
|
{
|
|
UBA_ASSERT(false);
|
|
return false;
|
|
}
|
|
out.count = strlen(out.data);
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
bool DirectoryCache::CreateDirectory(Logger& logger, const tchar* dir)
|
|
{
|
|
u64 dirLen = TStrlen(dir);
|
|
if (dir[dirLen - 1] == PathSeparator)
|
|
--dirLen;
|
|
TString key(dir, dir + dirLen);
|
|
dir = key.c_str();
|
|
|
|
SCOPED_FUTEX(m_createdDirsLock, lock);
|
|
CreatedDir& cd = m_createdDirs.try_emplace(key).first->second;
|
|
lock.Leave();
|
|
SCOPED_FUTEX(cd.lock, dirLock);
|
|
if (cd.handled)
|
|
return true;
|
|
cd.handled = true;
|
|
if (uba::CreateDirectoryW(dir))
|
|
return true;
|
|
u32 lastError = GetLastError();
|
|
if (lastError == ERROR_ALREADY_EXISTS)
|
|
return true;
|
|
if (lastError != ERROR_PATH_NOT_FOUND)
|
|
return logger.Error(TC("Failed to create directory %s (%s)"), dir, LastErrorToText(lastError).data);
|
|
|
|
tchar temp[512];
|
|
const tchar* lastSep = TStrrchr(dir, PathSeparator);
|
|
u64 pos = u64(lastSep - dir);
|
|
memcpy(temp, dir, pos * sizeof(tchar));
|
|
temp[pos] = 0;
|
|
if (pos == 2 && temp[1] == ':')
|
|
return false;
|
|
if (!CreateDirectory(logger, temp))
|
|
return false;
|
|
if (!uba::CreateDirectoryW(dir))
|
|
return logger.Error(TC("Failed to create directory %s (%s)"), dir, LastErrorToText().data);
|
|
return true;
|
|
}
|
|
|
|
void DirectoryCache::Clear()
|
|
{
|
|
SCOPED_FUTEX(m_createdDirsLock, lock);
|
|
m_createdDirs.clear();
|
|
}
|
|
|
|
bool GetAlternativeUbaPath(Logger& logger, StringBufferBase& out, StringView firstPath, bool isWindowsArm)
|
|
{
|
|
out.Append(firstPath);
|
|
const tchar* engineDir = IsWindows ? TC("\\Engine\\") : TC("/Engine/");
|
|
const tchar* engineDirPos;
|
|
if (!out.Contains(engineDir, true, &engineDirPos))
|
|
return false;//logger.Error(TC("Failed to find Engine dir in %s"), out.data);
|
|
#if PLATFORM_WINDOWS
|
|
const tchar* platformStr = TC("Win64");
|
|
#elif PLATFORM_LINUX
|
|
const tchar* platformStr = TC("Linux");
|
|
#else
|
|
const tchar* platformStr = TC("Mac");
|
|
#endif
|
|
out.Resize(engineDirPos - out.data + 8).Append(TCV("Binaries")).Append(PathSeparator).Append(platformStr).Append(PathSeparator).Append("UnrealBuildAccelerator").Append(PathSeparator);
|
|
if constexpr (IsWindows)
|
|
out.Append(isWindowsArm ? TC("arm64") : TC("x64")).Append(PathSeparator);
|
|
return true;
|
|
};
|
|
|
|
VolumeCache::~VolumeCache()
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
for (auto& volume : volumes)
|
|
CloseHandle(asHANDLE(volume.handle));
|
|
#endif
|
|
}
|
|
|
|
bool VolumeCache::Init(Logger& logger)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
tchar volumeName[MAX_PATH] = {0};
|
|
HANDLE hFind = FindFirstVolumeW(volumeName, sizeof_array(volumeName));
|
|
|
|
if (hFind == INVALID_HANDLE_VALUE)
|
|
return logger.Error(TC("FindFirstVolume failed (%s)"), LastErrorToText().data);
|
|
|
|
do
|
|
{
|
|
DWORD serialNumber = 0;
|
|
if (!GetVolumeInformationW(volumeName, nullptr, 0, &serialNumber, nullptr, nullptr, nullptr, 0))
|
|
continue;
|
|
Volume& volume = volumes.emplace_back();
|
|
volume.serialNumber = serialNumber;
|
|
|
|
|
|
tchar driveLetters[MAX_PATH] = {0};
|
|
DWORD cchReturnLength = 0;
|
|
if (GetVolumePathNamesForVolumeNameW(volumeName, driveLetters, sizeof_array(driveLetters), &cchReturnLength))
|
|
{
|
|
StringBuffer<128> drives;
|
|
for (tchar* p = driveLetters; *p; p += TStrlen(p) + 1)
|
|
drives.Append(p);
|
|
volume.drives = drives.data;
|
|
}
|
|
|
|
u32 volumeNameLen = TStrlen(volumeName);
|
|
if (volumeName[volumeNameLen - 1] == PathSeparator)
|
|
volumeName[volumeNameLen - 1] = 0;
|
|
|
|
volume.handle = (FileHandle)(u64)::CreateFileW(volumeName, 0, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
|
|
}
|
|
while (FindNextVolumeW(hFind, volumeName, sizeof_array(volumeName)));
|
|
|
|
FindVolumeClose(hFind);
|
|
|
|
std::sort(volumes.begin(), volumes.end(), [](const Volume& a, const Volume& b)
|
|
{
|
|
bool aempt = a.drives.empty();
|
|
bool bempt = b.drives.empty();
|
|
if (aempt != bempt)
|
|
return bempt;
|
|
if (!aempt)
|
|
return a.drives < b.drives;
|
|
return a.serialNumber < b.serialNumber;
|
|
});
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
u32 VolumeCache::GetSerialIndex(u32 volumeSerial)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
u32 index = 1;
|
|
for (auto& volume : volumes)
|
|
{
|
|
if (volume.serialNumber == volumeSerial)
|
|
return index;
|
|
++index;
|
|
}
|
|
if (!volumeSerial)
|
|
return index;
|
|
#endif
|
|
return volumeSerial;
|
|
}
|
|
|
|
void VolumeCache::Write(BinaryWriter& writer)
|
|
{
|
|
writer.WriteU16(u16(volumes.size()));
|
|
for (auto& volume : volumes)
|
|
writer.WriteU32(volume.serialNumber);
|
|
}
|
|
|
|
void VolumeCache::Read(BinaryReader& reader)
|
|
{
|
|
u32 count = reader.ReadU16();
|
|
volumes.resize(count);
|
|
for (u32 i=0; i!=count;++i)
|
|
volumes[i].serialNumber = reader.ReadU32();
|
|
}
|
|
|
|
bool VolumeCache::Volume::UpdateStats(u8& outBusyPercent, u32& outReadCount, u64& outReadBytes, u32& outWriteCount, u64& outWriteBytes)
|
|
{
|
|
outBusyPercent = 0;
|
|
outReadCount = 0;
|
|
outReadBytes = 0;
|
|
outWriteCount = 0;
|
|
outWriteBytes = 0;
|
|
|
|
#if PLATFORM_WINDOWS
|
|
DISK_PERFORMANCE perf = {};
|
|
DWORD bytesReturned;
|
|
if (!::DeviceIoControl(asHANDLE(handle), IOCTL_DISK_PERFORMANCE, NULL, 0, &perf, sizeof(perf), &bytesReturned, NULL))
|
|
{
|
|
::CloseHandle(asHANDLE(handle));
|
|
handle = InvalidFileHandle;
|
|
return false;
|
|
}
|
|
|
|
u64 queryTime = perf.QueryTime.QuadPart;
|
|
u64 idleTime = perf.IdleTime.QuadPart;
|
|
u32 readCount = perf.ReadCount;
|
|
u32 writeCount = perf.WriteCount;
|
|
u64 readBytes = perf.BytesRead.QuadPart;
|
|
u64 writeBytes = perf.BytesWritten.QuadPart;
|
|
|
|
if (prevQueryTime)
|
|
{
|
|
double busyPercent = 100.0 - 100.0*(double(idleTime - prevIdleTime) / double(queryTime - prevQueryTime));
|
|
busyPercent = Max(0.0, Min(100.0, busyPercent));
|
|
outBusyPercent = (u8)busyPercent;
|
|
outReadCount = readCount - prevReadCount;
|
|
outReadBytes = readBytes - prevReadBytes;
|
|
outWriteCount = writeCount - prevWriteCount;
|
|
outWriteBytes = writeBytes - prevWriteBytes;
|
|
}
|
|
prevQueryTime = queryTime;
|
|
prevIdleTime = idleTime;
|
|
|
|
prevReadCount = readCount;
|
|
prevReadBytes = readBytes;
|
|
prevWriteCount = writeCount;
|
|
prevWriteBytes = writeBytes;
|
|
#endif
|
|
return true;
|
|
}
|
|
}
|