1297 lines
52 KiB
C++
1297 lines
52 KiB
C++
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$\ $$\ $$\ $$$$$$\ $$$$$$\
|
|
// $$ __$$\ $$ __$$\ $$ | $\ $$ |\_$$ _|$$$\ $$ |$$ ___$$\ $$ __$$\
|
|
// $$ / $$ |$$ / \__| $$ |$$$\ $$ | $$ | $$$$\ $$ |\_/ $$ |\__/ $$ |
|
|
// $$ | $$ |\$$$$$$\ $$ $$ $$\$$ | $$ | $$ $$\$$ | $$$$$ / $$$$$$ |
|
|
// $$ | $$ | \____$$\ $$$$ _$$$$ | $$ | $$ \$$$$ | \___$$\ $$ ____/
|
|
// $$ | $$ |$$\ $$ | $$$ / \$$$ | $$ | $$ |\$$$ |$$\ $$ |$$ |
|
|
// $$$$$$ |\$$$$$$ | $$ / \$$ |$$$$$$\ $$ | \$$ |\$$$$$$ |$$$$$$$$\
|
|
// \______/ \______/ \__/ \__|\______|\__| \__| \______/ \________|
|
|
//
|
|
// dqn_os_win32.cpp
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// NOTE: [$VMEM] Dqn_OSMem //////////////////////////////////////////////////////////////////////////
|
|
static uint32_t Dqn_OS_MemConvertPageToOSFlags_(uint32_t protect)
|
|
{
|
|
DQN_ASSERT((protect & ~Dqn_OSMemPage_All) == 0);
|
|
DQN_ASSERT(protect != 0);
|
|
uint32_t result = 0;
|
|
|
|
if (protect & Dqn_OSMemPage_NoAccess) {
|
|
result = PAGE_NOACCESS;
|
|
} else {
|
|
if (protect & Dqn_OSMemPage_ReadWrite) {
|
|
result = PAGE_READWRITE;
|
|
} else if (protect & Dqn_OSMemPage_Read) {
|
|
result = PAGE_READONLY;
|
|
} else if (protect & Dqn_OSMemPage_Write) {
|
|
Dqn_Log_WarningF("Windows does not support write-only pages, granting read+write access");
|
|
result = PAGE_READWRITE;
|
|
}
|
|
}
|
|
|
|
if (protect & Dqn_OSMemPage_Guard)
|
|
result |= PAGE_GUARD;
|
|
|
|
DQN_ASSERTF(result != PAGE_GUARD, "Page guard is a modifier, you must also specify a page permission like read or/and write");
|
|
return result;
|
|
}
|
|
|
|
DQN_API void *Dqn_OS_MemReserve(Dqn_usize size, Dqn_OSMemCommit commit, uint32_t page_flags)
|
|
{
|
|
unsigned long os_page_flags = Dqn_OS_MemConvertPageToOSFlags_(page_flags);
|
|
unsigned long flags = MEM_RESERVE | (commit == Dqn_OSMemCommit_Yes ? MEM_COMMIT : 0);
|
|
void *result = VirtualAlloc(nullptr, size, flags, os_page_flags);
|
|
return result;
|
|
}
|
|
|
|
DQN_API bool Dqn_OS_MemCommit(void *ptr, Dqn_usize size, uint32_t page_flags)
|
|
{
|
|
bool result = false;
|
|
if (!ptr || size == 0)
|
|
return false;
|
|
unsigned long os_page_flags = Dqn_OS_MemConvertPageToOSFlags_(page_flags);
|
|
result = VirtualAlloc(ptr, size, MEM_COMMIT, os_page_flags) != nullptr;
|
|
return result;
|
|
}
|
|
|
|
DQN_API void Dqn_OS_MemDecommit(void *ptr, Dqn_usize size)
|
|
{
|
|
// NOTE: This is a decommit call, which is explicitly saying to free the
|
|
// pages but not the address space, you would use OS_MemRelease to release
|
|
// everything.
|
|
DQN_MSVC_WARNING_PUSH
|
|
DQN_MSVC_WARNING_DISABLE(6250) // Calling 'VirtualFree' without the MEM_RELEASE flag might free memory but not address descriptors (VADs). This causes address space leaks.
|
|
VirtualFree(ptr, size, MEM_DECOMMIT);
|
|
DQN_MSVC_WARNING_POP
|
|
}
|
|
|
|
DQN_API void Dqn_OS_MemRelease(void *ptr, Dqn_usize size)
|
|
{
|
|
(void)size;
|
|
VirtualFree(ptr, 0, MEM_RELEASE);
|
|
}
|
|
|
|
DQN_API int Dqn_OS_MemProtect(void *ptr, Dqn_usize size, uint32_t page_flags)
|
|
{
|
|
if (!ptr || size == 0)
|
|
return 0;
|
|
|
|
static Dqn_Str8 const ALIGNMENT_ERROR_MSG =
|
|
DQN_STR8("Page protection requires pointers to be page aligned because we "
|
|
"can only guard memory at a multiple of the page boundary.");
|
|
DQN_ASSERTF(Dqn_IsPowerOfTwoAligned(DQN_CAST(uintptr_t)ptr, g_dqn_library->os_page_size), "%s", ALIGNMENT_ERROR_MSG.data);
|
|
DQN_ASSERTF(Dqn_IsPowerOfTwoAligned(size, g_dqn_library->os_page_size), "%s", ALIGNMENT_ERROR_MSG.data);
|
|
|
|
unsigned long os_page_flags = Dqn_OS_MemConvertPageToOSFlags_(page_flags);
|
|
unsigned long prev_flags = 0;
|
|
int result = VirtualProtect(ptr, size, os_page_flags, &prev_flags);
|
|
|
|
(void)prev_flags;
|
|
if (result == 0)
|
|
DQN_ASSERTF(result, "VirtualProtect failed");
|
|
return result;
|
|
}
|
|
|
|
// NOTE: [$DATE] Date //////////////////////////////////////////////////////////////////////////////
|
|
DQN_API Dqn_OSDateTime Dqn_OS_DateLocalTimeNow()
|
|
{
|
|
SYSTEMTIME sys_time;
|
|
GetLocalTime(&sys_time);
|
|
|
|
Dqn_OSDateTime result = {};
|
|
result.hour = DQN_CAST(uint8_t) sys_time.wHour;
|
|
result.minutes = DQN_CAST(uint8_t) sys_time.wMinute;
|
|
result.seconds = DQN_CAST(uint8_t) sys_time.wSecond;
|
|
result.day = DQN_CAST(uint8_t) sys_time.wDay;
|
|
result.month = DQN_CAST(uint8_t) sys_time.wMonth;
|
|
result.year = DQN_CAST(int16_t) sys_time.wYear;
|
|
return result;
|
|
}
|
|
|
|
DQN_API uint64_t Dqn_OS_DateUnixTime()
|
|
{
|
|
const uint64_t UNIX_TIME_START = 0x019DB1DED53E8000; // January 1, 1970 (start of Unix epoch) in "ticks"
|
|
const uint64_t TICKS_PER_SECOND = 10'000'000; // Filetime returned is in intervals of 100 nanoseconds
|
|
|
|
FILETIME file_time;
|
|
GetSystemTimeAsFileTime(&file_time);
|
|
|
|
LARGE_INTEGER date_time;
|
|
date_time.u.LowPart = file_time.dwLowDateTime;
|
|
date_time.u.HighPart = file_time.dwHighDateTime;
|
|
uint64_t result = (date_time.QuadPart - UNIX_TIME_START) / TICKS_PER_SECOND;
|
|
return result;
|
|
}
|
|
|
|
DQN_API bool Dqn_OS_SecureRNGBytes(void *buffer, uint32_t size)
|
|
{
|
|
if (!buffer || size < 0)
|
|
return false;
|
|
|
|
if (size == 0)
|
|
return true;
|
|
|
|
bool init = true;
|
|
Dqn_TicketMutex_Begin(&g_dqn_library->win32_bcrypt_rng_mutex);
|
|
if (!g_dqn_library->win32_bcrypt_rng_handle)
|
|
{
|
|
wchar_t const BCRYPT_ALGORITHM[] = L"RNG";
|
|
long /*NTSTATUS*/ init_status = BCryptOpenAlgorithmProvider(&g_dqn_library->win32_bcrypt_rng_handle, BCRYPT_ALGORITHM, nullptr /*implementation*/, 0 /*flags*/);
|
|
if (!g_dqn_library->win32_bcrypt_rng_handle || init_status != 0)
|
|
{
|
|
Dqn_Log_ErrorF("Failed to initialise random number generator, error: %d", init_status);
|
|
init = false;
|
|
}
|
|
}
|
|
Dqn_TicketMutex_End(&g_dqn_library->win32_bcrypt_rng_mutex);
|
|
|
|
if (!init)
|
|
return false;
|
|
|
|
long gen_status = BCryptGenRandom(g_dqn_library->win32_bcrypt_rng_handle, DQN_CAST(unsigned char *)buffer, size, 0 /*flags*/);
|
|
if (gen_status != 0)
|
|
{
|
|
Dqn_Log_ErrorF("Failed to generate random bytes: %d", gen_status);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
DQN_API Dqn_Str8 Dqn_OS_EXEPath(Dqn_Arena *arena)
|
|
{
|
|
Dqn_Str8 result = {};
|
|
if (!arena)
|
|
return result;
|
|
Dqn_Scratch scratch = Dqn_Scratch_Get(arena);
|
|
Dqn_Str16 exe_dir16 = Dqn_Win_EXEPathW(scratch.arena);
|
|
result = Dqn_Win_Str16ToStr8(arena, exe_dir16);
|
|
return result;
|
|
}
|
|
|
|
DQN_API void Dqn_OS_SleepMs(Dqn_uint milliseconds)
|
|
{
|
|
Sleep(milliseconds);
|
|
}
|
|
|
|
DQN_API uint64_t Dqn_OS_PerfCounterFrequency()
|
|
{
|
|
uint64_t result = g_dqn_library->win32_qpc_frequency.QuadPart;
|
|
DQN_ASSERTF(result, "Initialise the library with Dqn_Library_Init() to get a valid QPC frequency value");
|
|
return result;
|
|
}
|
|
|
|
DQN_API uint64_t Dqn_OS_PerfCounterNow()
|
|
{
|
|
LARGE_INTEGER integer = {};
|
|
QueryPerformanceCounter(&integer);
|
|
uint64_t result = integer.QuadPart;
|
|
return result;
|
|
}
|
|
|
|
#if !defined(DQN_NO_OS_FILE_API)
|
|
static uint64_t Dqn_Win_FileTimeToSeconds_(FILETIME const *time)
|
|
{
|
|
ULARGE_INTEGER time_large_int = {};
|
|
time_large_int.u.LowPart = time->dwLowDateTime;
|
|
time_large_int.u.HighPart = time->dwHighDateTime;
|
|
uint64_t result = (time_large_int.QuadPart / 10000000ULL) - 11644473600ULL;
|
|
return result;
|
|
}
|
|
|
|
DQN_API Dqn_OSPathInfo Dqn_OS_PathInfo(Dqn_Str8 path)
|
|
{
|
|
Dqn_OSPathInfo result = {};
|
|
if (!Dqn_Str8_HasData(path))
|
|
return result;
|
|
|
|
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
|
|
Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path);
|
|
|
|
WIN32_FILE_ATTRIBUTE_DATA attrib_data = {};
|
|
if (!GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data))
|
|
return result;
|
|
|
|
result.exists = true;
|
|
result.create_time_in_s = Dqn_Win_FileTimeToSeconds_(&attrib_data.ftCreationTime);
|
|
result.last_access_time_in_s = Dqn_Win_FileTimeToSeconds_(&attrib_data.ftLastAccessTime);
|
|
result.last_write_time_in_s = Dqn_Win_FileTimeToSeconds_(&attrib_data.ftLastWriteTime);
|
|
|
|
LARGE_INTEGER large_int = {};
|
|
large_int.u.HighPart = DQN_CAST(int32_t)attrib_data.nFileSizeHigh;
|
|
large_int.u.LowPart = attrib_data.nFileSizeLow;
|
|
result.size = (uint64_t)large_int.QuadPart;
|
|
|
|
if (attrib_data.dwFileAttributes != INVALID_FILE_ATTRIBUTES) {
|
|
if (attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
result.type = Dqn_OSPathInfoType_Directory;
|
|
else
|
|
result.type = Dqn_OSPathInfoType_File;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
DQN_API bool Dqn_OS_FileExists(Dqn_Str8 path)
|
|
{
|
|
bool result = false;
|
|
if (!Dqn_Str8_HasData(path))
|
|
return result;
|
|
|
|
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
|
|
Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path);
|
|
if (path16.size) {
|
|
WIN32_FILE_ATTRIBUTE_DATA attrib_data = {};
|
|
if (GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data)) {
|
|
result = (attrib_data.dwFileAttributes != INVALID_FILE_ATTRIBUTES) &&
|
|
!(attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DQN_API bool Dqn_OS_FileCopy(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite, Dqn_ErrorSink *error)
|
|
{
|
|
bool result = false;
|
|
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
|
|
Dqn_Str16 src16 = Dqn_Win_Str8ToStr16(scratch.arena, src);
|
|
Dqn_Str16 dest16 = Dqn_Win_Str8ToStr16(scratch.arena, dest);
|
|
|
|
int fail_if_exists = overwrite == false;
|
|
result = CopyFileW(src16.data, dest16.data, fail_if_exists) != 0;
|
|
|
|
if (!result) {
|
|
Dqn_WinError win_error = Dqn_Win_LastError(scratch.arena);
|
|
Dqn_ErrorSink_MakeF(error,
|
|
win_error.code,
|
|
"Failed to copy file '%.*s' to '%.*s': (%u) %.*s",
|
|
DQN_STR_FMT(src),
|
|
DQN_STR_FMT(dest),
|
|
win_error.code,
|
|
DQN_STR_FMT(win_error.msg));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DQN_API bool Dqn_OS_FileMove(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite, Dqn_ErrorSink *error)
|
|
{
|
|
bool result = false;
|
|
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
|
|
Dqn_Str16 src16 = Dqn_Win_Str8ToStr16(scratch.arena, src);
|
|
Dqn_Str16 dest16 = Dqn_Win_Str8ToStr16(scratch.arena, dest);
|
|
|
|
unsigned long flags = MOVEFILE_COPY_ALLOWED;
|
|
if (overwrite) {
|
|
flags |= MOVEFILE_REPLACE_EXISTING;
|
|
}
|
|
|
|
result = MoveFileExW(src16.data, dest16.data, flags) != 0;
|
|
if (!result) {
|
|
Dqn_WinError win_error = Dqn_Win_LastError(scratch.arena);
|
|
Dqn_ErrorSink_MakeF(error,
|
|
win_error.code,
|
|
"Failed to move file '%.*s' to '%.*s': (%u) %.*s",
|
|
DQN_STR_FMT(src),
|
|
DQN_STR_FMT(dest),
|
|
win_error.code,
|
|
DQN_STR_FMT(win_error.msg));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
DQN_API bool Dqn_OS_DirExists(Dqn_Str8 path)
|
|
{
|
|
bool result = false;
|
|
if (!Dqn_Str8_HasData(path))
|
|
return result;
|
|
|
|
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
|
|
Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path);
|
|
if (path16.size) {
|
|
WIN32_FILE_ATTRIBUTE_DATA attrib_data = {};
|
|
if (GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data)) {
|
|
result = (attrib_data.dwFileAttributes != INVALID_FILE_ATTRIBUTES) &&
|
|
(attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
DQN_API bool Dqn_OS_DirMake(Dqn_Str8 path)
|
|
{
|
|
bool result = true;
|
|
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
|
|
Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path);
|
|
|
|
// NOTE: Go back from the end of the string to all the directories in the
|
|
// string, and try to create them. Since Win32 API cannot create
|
|
// intermediate directories that don't exist in a path we need to go back
|
|
// and record all the directories until we encounter one that exists.
|
|
//
|
|
// From that point onwards go forwards and make all the directories
|
|
// inbetween by null-terminating the string temporarily, creating the
|
|
// directory and so forth until we reach the end.
|
|
//
|
|
// If we find a file at some point in the path we fail out because the
|
|
// series of directories can not be made if a file exists with the same
|
|
// name.
|
|
for (Dqn_usize index = 0; index < path16.size; index++) {
|
|
bool first_char = index == (path16.size - 1);
|
|
wchar_t ch = path16.data[index];
|
|
if (ch == '/' || ch == '\\' || first_char) {
|
|
wchar_t temp = path16.data[index];
|
|
if (!first_char)
|
|
path16.data[index] = 0; // Temporarily null terminate it
|
|
|
|
WIN32_FILE_ATTRIBUTE_DATA attrib_data = {};
|
|
bool successful = GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data); // Check
|
|
|
|
if (successful) {
|
|
if (attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
|
// NOTE: The directory exists, continue iterating the path
|
|
} else {
|
|
// NOTE: There's some kind of file that exists at the path
|
|
// but it's not a directory. This request to make a
|
|
// directory is invalid.
|
|
return false;
|
|
}
|
|
} else {
|
|
// NOTE: There's nothing that exists at this path, we can create
|
|
// a directory here
|
|
result |= (CreateDirectoryW(path16.data, nullptr) == 0);
|
|
}
|
|
|
|
if (!first_char)
|
|
path16.data[index] = temp; // Undo null termination
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DQN_API bool Dqn_OS_PathDelete(Dqn_Str8 path)
|
|
{
|
|
bool result = false;
|
|
if (!Dqn_Str8_HasData(path))
|
|
return result;
|
|
|
|
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
|
|
Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path);
|
|
if (path16.size) {
|
|
result = DeleteFileW(path16.data);
|
|
if (!result)
|
|
result = RemoveDirectoryW(path16.data);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// NOTE: R/W Stream API ////////////////////////////////////////////////////////////////////////////
|
|
DQN_API Dqn_OSFile Dqn_OS_OpenFile(Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint32_t access, Dqn_ErrorSink *error)
|
|
{
|
|
Dqn_OSFile result = {};
|
|
if (!Dqn_Str8_HasData(path) || path.size <= 0)
|
|
return result;
|
|
|
|
if ((access & ~Dqn_OSFileAccess_All) || ((access & Dqn_OSFileAccess_All) == 0)) {
|
|
DQN_INVALID_CODE_PATH;
|
|
return result;
|
|
}
|
|
|
|
unsigned long create_flag = 0;
|
|
switch (open_mode) {
|
|
case Dqn_OSFileOpen_CreateAlways: create_flag = CREATE_ALWAYS; break;
|
|
case Dqn_OSFileOpen_OpenIfExist: create_flag = OPEN_EXISTING; break;
|
|
case Dqn_OSFileOpen_OpenAlways: create_flag = OPEN_ALWAYS; break;
|
|
default: DQN_INVALID_CODE_PATH; return result;
|
|
}
|
|
|
|
unsigned long access_mode = 0;
|
|
if (access & Dqn_OSFileAccess_AppendOnly) {
|
|
DQN_ASSERTF((access & ~Dqn_OSFileAccess_AppendOnly) == 0,
|
|
"Append can only be applied exclusively to the file, other access modes not permitted");
|
|
access_mode = FILE_APPEND_DATA;
|
|
} else {
|
|
if (access & Dqn_OSFileAccess_Read)
|
|
access_mode |= GENERIC_READ;
|
|
if (access & Dqn_OSFileAccess_Write)
|
|
access_mode |= GENERIC_WRITE;
|
|
if (access & Dqn_OSFileAccess_Execute)
|
|
access_mode |= GENERIC_EXECUTE;
|
|
}
|
|
|
|
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
|
|
Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path);
|
|
void *handle = CreateFileW(/*LPCWSTR lpFileName*/ path16.data,
|
|
/*DWORD dwDesiredAccess*/ access_mode,
|
|
/*DWORD dwShareMode*/ 0,
|
|
/*LPSECURITY_ATTRIBUTES lpSecurityAttributes*/ nullptr,
|
|
/*DWORD dwCreationDisposition*/ create_flag,
|
|
/*DWORD dwFlagsAndAttributes*/ FILE_ATTRIBUTE_NORMAL,
|
|
/*HANDLE hTemplateFile*/ nullptr);
|
|
|
|
if (handle == INVALID_HANDLE_VALUE) {
|
|
Dqn_WinError win_error = Dqn_Win_LastError(scratch.arena);
|
|
result.error = true;
|
|
Dqn_ErrorSink_MakeF(error, win_error.code, "Failed to open file at '%.*s': '%.*s'", DQN_STR_FMT(path), DQN_STR_FMT(win_error.msg));
|
|
return result;
|
|
}
|
|
|
|
result.handle = handle;
|
|
return result;
|
|
}
|
|
|
|
DQN_API bool Dqn_OS_WriteFileBuffer(Dqn_OSFile *file, void const *buffer, Dqn_usize size, Dqn_ErrorSink *error)
|
|
{
|
|
if (!file || !file->handle || file->error || !buffer || size <= 0)
|
|
return false;
|
|
|
|
bool result = true;
|
|
char const *end = DQN_CAST(char *) buffer + size;
|
|
for (char const *ptr = DQN_CAST(char const *) buffer; result && ptr != end;) {
|
|
unsigned long write_size = DQN_CAST(unsigned long)DQN_MIN((unsigned long)-1, end - ptr);
|
|
unsigned long bytes_written = 0;
|
|
result = WriteFile(file->handle, ptr, write_size, &bytes_written, nullptr /*lpOverlapped*/) != 0;
|
|
ptr += bytes_written;
|
|
}
|
|
|
|
if (!result) {
|
|
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
|
|
Dqn_WinError win_error = Dqn_Win_LastError(scratch.arena);
|
|
Dqn_Str8 buffer_size_str8 = Dqn_U64ToByteSizeStr8(scratch.arena, size, Dqn_U64ByteSizeType_Auto);
|
|
Dqn_ErrorSink_MakeF(error, win_error.code, "Failed to write buffer (%.*s) to file handle: %.*s", DQN_STR_FMT(buffer_size_str8), DQN_STR_FMT(win_error.msg));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DQN_API void Dqn_OS_CloseFile(Dqn_OSFile *file)
|
|
{
|
|
if (!file || !file->handle || file->error)
|
|
return;
|
|
CloseHandle(file->handle);
|
|
*file = {};
|
|
}
|
|
|
|
// NOTE: R/W Entire File ///////////////////////////////////////////////////////////////////////////
|
|
DQN_API Dqn_Str8 Dqn_OS_ReadAll(Dqn_Str8 path, Dqn_Arena *arena, Dqn_ErrorSink *error)
|
|
{
|
|
Dqn_Str8 result = {};
|
|
if (!arena)
|
|
return result;
|
|
|
|
Dqn_ArenaTempMemScope temp_mem = Dqn_ArenaTempMemScope(arena);
|
|
// NOTE: Convert to UTF16 //////////////////////////////////////////////////////////////////////
|
|
Dqn_Scratch scratch = Dqn_Scratch_Get(arena);
|
|
Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path);
|
|
|
|
// NOTE: Get the file handle ///////////////////////////////////////////////////////////////////
|
|
void *file_handle = CreateFileW(/*LPCWSTR lpFileName*/ path16.data,
|
|
/*DWORD dwDesiredAccess*/ GENERIC_READ,
|
|
/*DWORD dwShareMode*/ FILE_SHARE_READ,
|
|
/*LPSECURITY_ATTRIBUTES lpSecurityAttributes*/ nullptr,
|
|
/*DWORD dwCreationDisposition*/ OPEN_EXISTING,
|
|
/*DWORD dwFlagsAndAttributes*/ FILE_ATTRIBUTE_READONLY,
|
|
/*HANDLE hTemplateFile*/ nullptr);
|
|
if (file_handle == INVALID_HANDLE_VALUE) {
|
|
Dqn_WinError win_error = Dqn_Win_LastError(scratch.arena);
|
|
Dqn_ErrorSink_MakeF(error, win_error.code, "Failed to open file at '%.*s' for reading: %.*s", DQN_STR_FMT(path), DQN_STR_FMT(win_error.msg));
|
|
return result;
|
|
}
|
|
DQN_DEFER { CloseHandle(file_handle); };
|
|
|
|
// NOTE: Query the file size ///////////////////////////////////////////////////////////////////
|
|
LARGE_INTEGER win_file_size;
|
|
if (!GetFileSizeEx(file_handle, &win_file_size)) {
|
|
Dqn_WinError win_error = Dqn_Win_LastError(scratch.arena);
|
|
Dqn_ErrorSink_MakeF(error, win_error.code, "Failed to query file size for reading '%.*s': %.*s", DQN_STR_FMT(path), DQN_STR_FMT(win_error.msg));
|
|
return result;
|
|
}
|
|
|
|
unsigned long const bytes_desired = DQN_CAST(unsigned long)win_file_size.QuadPart;
|
|
if (!DQN_CHECK(bytes_desired == win_file_size.QuadPart)) {
|
|
Dqn_ErrorSink_MakeF(error, 1 /*error_code*/, "Current implementation doesn't support reading >4GiB file at '%.*s', implement Win32 overlapped IO", DQN_STR_FMT(path));
|
|
return result;
|
|
}
|
|
|
|
// NOTE: Read the file from disk ///////////////////////////////////////////////////////////////
|
|
Dqn_Str8 buffer = Dqn_Str8_Alloc(arena, bytes_desired, Dqn_ZeroMem_No);
|
|
unsigned long bytes_read = 0;
|
|
unsigned long read_result = ReadFile(/*HANDLE hFile*/ file_handle,
|
|
/*LPVOID lpBuffer*/ buffer.data,
|
|
/*DWORD nNumberOfBytesToRead*/ bytes_desired,
|
|
/*LPDWORD lpNumberOfByesRead*/ &bytes_read,
|
|
/*LPOVERLAPPED lpOverlapped*/ nullptr);
|
|
|
|
if (read_result == 0) {
|
|
Dqn_WinError win_error = Dqn_Win_LastError(scratch.arena);
|
|
Dqn_ErrorSink_MakeF(error, win_error.code, "Failed to read bytes from file '%.*s': (%u) %.*s", DQN_STR_FMT(path), win_error.code, DQN_STR_FMT(win_error.msg));
|
|
return result;
|
|
}
|
|
|
|
if (bytes_read != bytes_desired) {
|
|
Dqn_WinError win_error = Dqn_Win_LastError(scratch.arena);
|
|
Dqn_ErrorSink_MakeF(
|
|
error,
|
|
win_error.code,
|
|
"Failed to read the desired number of bytes from file '%.*s', we read %uB but we expected %uB: (%u) %.*s",
|
|
DQN_STR_FMT(path),
|
|
bytes_read,
|
|
bytes_desired,
|
|
win_error.code,
|
|
DQN_STR_FMT(win_error.msg));
|
|
return result;
|
|
}
|
|
|
|
buffer.data[bytes_desired] = 0;
|
|
result = buffer;
|
|
temp_mem.mem = {};
|
|
return result;
|
|
}
|
|
#endif // !defined(DQN_NO_OS_FILE_API)
|
|
|
|
// NOTE: [$EXEC] Dqn_OSExec ////////////////////////////////////////////////////////////////////////
|
|
DQN_API void Dqn_OS_Exit(uint32_t exit_code)
|
|
{
|
|
ExitProcess(exit_code);
|
|
}
|
|
|
|
DQN_API Dqn_OSExecResult Dqn_OS_ExecWait(Dqn_OSExecAsyncHandle handle)
|
|
{
|
|
Dqn_OSExecResult result = {};
|
|
if (!handle.process || handle.os_error_code) {
|
|
result.os_error_code = handle.os_error_code;
|
|
return result;
|
|
}
|
|
|
|
if (handle.exit_code) {
|
|
result.exit_code = handle.exit_code;
|
|
return result;
|
|
}
|
|
|
|
DWORD exec_result = WaitForSingleObject(handle.process, INFINITE);
|
|
if (exec_result == WAIT_FAILED) {
|
|
result.os_error_code = GetLastError();
|
|
return result;
|
|
}
|
|
|
|
DWORD exit_status;
|
|
if (!GetExitCodeProcess(handle.process, &exit_status)) {
|
|
result.os_error_code = GetLastError();
|
|
return result;
|
|
}
|
|
|
|
result.exit_code = exit_status;
|
|
CloseHandle(handle.process);
|
|
return result;
|
|
}
|
|
|
|
DQN_API Dqn_OSExecAsyncHandle Dqn_OS_ExecAsync(Dqn_Slice<Dqn_Str8> cmd_line, Dqn_Str8 working_dir)
|
|
{
|
|
Dqn_OSExecAsyncHandle result = {};
|
|
if (cmd_line.size == 0)
|
|
return result;
|
|
|
|
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
|
|
Dqn_Str8 cmd_rendered = Dqn_Slice_Str8Render(scratch.arena, cmd_line, DQN_STR8(" "));
|
|
Dqn_Str16 cmd16 = Dqn_Win_Str8ToStr16(scratch.arena, cmd_rendered);
|
|
Dqn_Str16 working_dir16 = Dqn_Win_Str8ToStr16(scratch.arena, working_dir);
|
|
|
|
PROCESS_INFORMATION proc_info = {};
|
|
STARTUPINFOW startup_info = {};
|
|
startup_info.cb = sizeof(STARTUPINFOW);
|
|
startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
|
|
startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
|
|
startup_info.dwFlags |= STARTF_USESTDHANDLES;
|
|
BOOL create_result = CreateProcessW(nullptr, cmd16.data, nullptr, nullptr, true, 0, nullptr, working_dir16.data, &startup_info, &proc_info);
|
|
if (!create_result) {
|
|
result.os_error_code = GetLastError();
|
|
return result;
|
|
}
|
|
|
|
CloseHandle(proc_info.hThread);
|
|
result.process = proc_info.hProcess;
|
|
return result;
|
|
}
|
|
|
|
#if !defined(DQN_NO_SEMAPHORE)
|
|
// NOTE: [$SEMA] Dqn_OSSemaphore ///////////////////////////////////////////////////////////////////
|
|
DQN_API Dqn_OSSemaphore Dqn_OS_SemaphoreInit(uint32_t initial_count)
|
|
{
|
|
Dqn_OSSemaphore result = {};
|
|
SECURITY_ATTRIBUTES security_attribs = {};
|
|
result.win32_handle = CreateSemaphoreA(&security_attribs, initial_count, INT32_MAX, nullptr /*name*/);
|
|
return result;
|
|
}
|
|
|
|
DQN_API bool Dqn_OS_SemaphoreIsValid(Dqn_OSSemaphore *semaphore)
|
|
{
|
|
bool result = false;
|
|
if (semaphore) {
|
|
result = semaphore->win32_handle;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DQN_API void Dqn_OS_SemaphoreDeinit(Dqn_OSSemaphore *semaphore)
|
|
{
|
|
if (!Dqn_OS_SemaphoreIsValid(semaphore))
|
|
return;
|
|
CloseHandle(semaphore->win32_handle);
|
|
*semaphore = {};
|
|
}
|
|
|
|
DQN_API void Dqn_OS_SemaphoreIncrement(Dqn_OSSemaphore *semaphore, uint32_t amount)
|
|
{
|
|
if (!Dqn_OS_SemaphoreIsValid(semaphore))
|
|
return;
|
|
LONG prev_count = 0;
|
|
ReleaseSemaphore(DQN_CAST(HANDLE *)semaphore->win32_handle, amount, &prev_count);
|
|
}
|
|
|
|
DQN_API Dqn_OSSemaphoreWaitResult Dqn_OS_SemaphoreWait(Dqn_OSSemaphore *semaphore, uint32_t timeout_ms)
|
|
{
|
|
Dqn_OSSemaphoreWaitResult result = {};
|
|
if (!Dqn_OS_SemaphoreIsValid(semaphore))
|
|
return result;
|
|
|
|
if (!semaphore->win32_handle)
|
|
return result;
|
|
|
|
DWORD wait_result = WaitForSingleObject(semaphore->win32_handle, timeout_ms == DQN_OS_SEMAPHORE_INFINITE_TIMEOUT ? INFINITE : timeout_ms);
|
|
if (wait_result == WAIT_TIMEOUT)
|
|
result = Dqn_OSSemaphoreWaitResult_Timeout;
|
|
else if (wait_result == WAIT_OBJECT_0)
|
|
result = Dqn_OSSemaphoreWaitResult_Success;
|
|
return result;
|
|
}
|
|
#endif // !defined(DQN_NO_SEMAPHORE)
|
|
|
|
#if !defined(DQN_NO_THREAD)
|
|
// NOTE: [$MUTX] Dqn_OSMutex ///////////////////////////////////////////////////////////////////////
|
|
DQN_API Dqn_OSMutex Dqn_OS_MutexInit()
|
|
{
|
|
Dqn_OSMutex result = {};
|
|
|
|
CRITICAL_SECTION crit_section = {};
|
|
InitializeCriticalSection(&crit_section);
|
|
|
|
static_assert(sizeof(CRITICAL_SECTION) <= sizeof(result.win32_handle), "Insufficient bytes to store Win32 mutex opaquely in our abstracted Dqn_OSMutex");
|
|
DQN_MEMCPY(result.win32_handle, &crit_section, sizeof(crit_section));
|
|
return result;
|
|
}
|
|
|
|
DQN_API void Dqn_OS_MutexDeinit(Dqn_OSMutex *mutex)
|
|
{
|
|
if (!mutex)
|
|
return;
|
|
CRITICAL_SECTION *crit_section = DQN_CAST(CRITICAL_SECTION *)mutex->win32_handle;
|
|
DeleteCriticalSection(crit_section);
|
|
DQN_MEMSET(mutex->win32_handle, 0, DQN_ARRAY_UCOUNT(mutex->win32_handle));
|
|
}
|
|
|
|
DQN_API void Dqn_OS_MutexLock(Dqn_OSMutex *mutex)
|
|
{
|
|
if (!mutex)
|
|
return;
|
|
CRITICAL_SECTION *crit_section = DQN_CAST(CRITICAL_SECTION *)mutex->win32_handle;
|
|
EnterCriticalSection(crit_section);
|
|
}
|
|
|
|
DQN_API void Dqn_OS_MutexUnlock(Dqn_OSMutex *mutex)
|
|
{
|
|
if (!mutex)
|
|
return;
|
|
CRITICAL_SECTION *crit_section = DQN_CAST(CRITICAL_SECTION *)mutex->win32_handle;
|
|
LeaveCriticalSection(crit_section);
|
|
}
|
|
|
|
// NOTE: [$THRD] Dqn_OSThread /////////////////////////////////////////////////////////////////////
|
|
static DWORD __stdcall Dqn_OS_ThreadFunc_(void *user_context)
|
|
{
|
|
Dqn_OSThread *thread = DQN_CAST(Dqn_OSThread *)user_context;
|
|
Dqn_OS_SemaphoreWait(&thread->init_semaphore, DQN_OS_SEMAPHORE_INFINITE_TIMEOUT);
|
|
thread->func(thread);
|
|
return 0;
|
|
}
|
|
|
|
DQN_API bool Dqn_OS_ThreadInit(Dqn_OSThread *thread, Dqn_OSThreadFunc *func, void *user_context)
|
|
{
|
|
bool result = false;
|
|
if (!thread)
|
|
return result;
|
|
|
|
thread->func = func;
|
|
thread->user_context = user_context;
|
|
thread->init_semaphore = Dqn_OS_SemaphoreInit(0 /*initial_count*/);
|
|
|
|
// TODO(doyle): Check if semaphore is valid
|
|
DWORD thread_id = 0;
|
|
SECURITY_ATTRIBUTES security_attribs = {};
|
|
thread->handle = CreateThread(&security_attribs,
|
|
0 /*stack_size*/,
|
|
Dqn_OS_ThreadFunc_,
|
|
thread,
|
|
0 /*creation_flags*/,
|
|
&thread_id);
|
|
|
|
result = thread->handle != INVALID_HANDLE_VALUE;
|
|
if (result) {
|
|
thread->thread_id = thread_id;
|
|
}
|
|
|
|
if (result) {
|
|
Dqn_OS_SemaphoreIncrement(&thread->init_semaphore, 1);
|
|
} else {
|
|
Dqn_OS_SemaphoreDeinit(&thread->init_semaphore);
|
|
*thread = {};
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
DQN_API void Dqn_OS_ThreadDeinit(Dqn_OSThread *thread)
|
|
{
|
|
if (!thread || !thread->handle)
|
|
return;
|
|
|
|
WaitForSingleObject(thread->handle, INFINITE);
|
|
CloseHandle(thread->handle);
|
|
thread->handle = INVALID_HANDLE_VALUE;
|
|
thread->thread_id = {};
|
|
}
|
|
|
|
DQN_API uint32_t Dqn_OS_ThreadID()
|
|
{
|
|
unsigned long result = GetCurrentThreadId();
|
|
return result;
|
|
}
|
|
#endif // !defined(DQN_NO_THREAD)
|
|
|
|
// NOTE: [$HTTP] Dqn_OSHttp ////////////////////////////////////////////////////////////////////////
|
|
void Dqn_OS_HttpRequestWin32Callback(HINTERNET session, DWORD *dwContext, DWORD dwInternetStatus, VOID *lpvStatusInformation, DWORD dwStatusInformationLength)
|
|
{
|
|
(void)session;
|
|
(void)dwStatusInformationLength;
|
|
|
|
Dqn_OSHttpResponse *response = DQN_CAST(Dqn_OSHttpResponse *)dwContext;
|
|
HINTERNET request = DQN_CAST(HINTERNET)response->win32_request_handle;
|
|
Dqn_WinError error = {};
|
|
DWORD const READ_BUFFER_SIZE = DQN_MEGABYTES(1);
|
|
|
|
if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_RESOLVING_NAME) {
|
|
} else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_NAME_RESOLVED) {
|
|
} else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER) {
|
|
} else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER) {
|
|
} else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_SENDING_REQUEST) {
|
|
} else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_REQUEST_SENT) {
|
|
} else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE) {
|
|
} else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED) {
|
|
} else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION) {
|
|
} else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED) {
|
|
} else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED) {
|
|
} else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HANDLE_CREATED) {
|
|
} else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING) {
|
|
} else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_DETECTING_PROXY) {
|
|
} else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_REDIRECT) {
|
|
} else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE) {
|
|
} else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_SECURE_FAILURE) {
|
|
} else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE) {
|
|
DWORD status = 0;
|
|
DWORD status_size = sizeof(status_size);
|
|
if (WinHttpQueryHeaders(request,
|
|
WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
|
|
WINHTTP_HEADER_NAME_BY_INDEX,
|
|
&status,
|
|
&status_size,
|
|
WINHTTP_NO_HEADER_INDEX)) {
|
|
response->http_status = DQN_CAST(uint16_t)status;
|
|
|
|
// NOTE: You can normally call into WinHttpQueryDataAvailable which means the kernel
|
|
// will buffer the response into a single buffer and return us the full size of the
|
|
// request.
|
|
//
|
|
// or
|
|
//
|
|
// You may call WinHttpReadData directly to write the memory into our buffer directly.
|
|
// This is advantageous to avoid a copy from the kernel buffer into our buffer. If the
|
|
// end user application knows the typical payload size then they can optimise for this
|
|
// to prevent unnecessary allocation on the user side.
|
|
void *buffer = Dqn_Arena_Alloc(response->builder.arena, READ_BUFFER_SIZE, 1 /*align*/, Dqn_ZeroMem_No);
|
|
if (!WinHttpReadData(request, buffer, READ_BUFFER_SIZE, nullptr))
|
|
error = Dqn_Win_LastError(&response->tmp_arena);
|
|
} else {
|
|
error = Dqn_Win_LastError(&response->tmp_arena);
|
|
}
|
|
} else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE) {
|
|
} else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_READ_COMPLETE) {
|
|
DWORD bytes_read = dwStatusInformationLength;
|
|
if (bytes_read) {
|
|
Dqn_Str8 prev_buffer = Dqn_Str8_Init(DQN_CAST(char *) lpvStatusInformation, bytes_read);
|
|
Dqn_Str8Builder_AppendRef(&response->builder, prev_buffer);
|
|
|
|
void *buffer = Dqn_Arena_Alloc(response->builder.arena, READ_BUFFER_SIZE, 1 /*align*/, Dqn_ZeroMem_No);
|
|
if (!WinHttpReadData(request, buffer, READ_BUFFER_SIZE, nullptr))
|
|
error = Dqn_Win_LastError(&response->tmp_arena);
|
|
}
|
|
} else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE) {
|
|
} else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_REQUEST_ERROR) {
|
|
WINHTTP_ASYNC_RESULT *async_result = DQN_CAST(WINHTTP_ASYNC_RESULT *)lpvStatusInformation;
|
|
error = Dqn_Win_ErrorCodeToMsg(&response->tmp_arena, DQN_CAST(uint32_t)async_result->dwError);
|
|
} else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE) {
|
|
if (!WinHttpReceiveResponse(request, 0))
|
|
error = Dqn_Win_LastError(&response->tmp_arena);
|
|
}
|
|
|
|
// NOTE: If the request handle is missing, then, the response has been freed.
|
|
// MSDN says that this callback can still be called after closing the handle
|
|
// and trigger the WINHTTP_CALLBACK_STATUS_REQUEST_ERROR.
|
|
if (response->win32_request_handle) {
|
|
bool read_complete = dwInternetStatus == WINHTTP_CALLBACK_STATUS_READ_COMPLETE && dwStatusInformationLength == 0;
|
|
if (read_complete) {
|
|
response->body = Dqn_Str8Builder_Build(&response->builder, response->arena);
|
|
}
|
|
|
|
if (read_complete || dwInternetStatus == WINHTTP_CALLBACK_STATUS_REQUEST_ERROR || error.code) {
|
|
Dqn_OS_SemaphoreIncrement(&response->on_complete_semaphore, 1);
|
|
Dqn_Atomic_AddU32(&response->done, 1);
|
|
}
|
|
|
|
if (error.code) {
|
|
response->error_code = error.code;
|
|
response->error_msg = error.msg;
|
|
}
|
|
}
|
|
}
|
|
|
|
DQN_API void Dqn_OS_HttpRequestAsync(Dqn_OSHttpResponse *response,
|
|
Dqn_Arena *arena,
|
|
Dqn_Str8 host,
|
|
Dqn_Str8 path,
|
|
Dqn_OSHttpRequestSecure secure,
|
|
Dqn_Str8 method,
|
|
Dqn_Str8 body,
|
|
Dqn_Str8 headers)
|
|
{
|
|
if (!response || !arena)
|
|
return;
|
|
|
|
response->arena = arena;
|
|
response->builder.arena = response->scratch_arena ? response->scratch_arena : &response->tmp_arena;
|
|
|
|
Dqn_Arena *scratch_arena = response->scratch_arena;
|
|
Dqn_Scratch scratch_ = Dqn_Scratch_Get(arena);
|
|
if (!scratch_arena) {
|
|
scratch_arena = scratch_.arena;
|
|
}
|
|
|
|
Dqn_WinError error = {};
|
|
DQN_DEFER {
|
|
response->error_msg = error.msg;
|
|
response->error_code = error.code;
|
|
if (error.code) {
|
|
// NOTE: 'Wait' handles failures gracefully, skipping the wait and
|
|
// cleans up the request
|
|
Dqn_OS_HttpRequestWait(response);
|
|
Dqn_Atomic_AddU32(&response->done, 1);
|
|
}
|
|
};
|
|
|
|
response->win32_request_session = WinHttpOpen(nullptr /*user agent*/, WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC);
|
|
if (!response->win32_request_session) {
|
|
error = Dqn_Win_LastError(&response->tmp_arena);
|
|
return;
|
|
}
|
|
|
|
DWORD callback_flags = WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE |
|
|
WINHTTP_CALLBACK_STATUS_READ_COMPLETE |
|
|
WINHTTP_CALLBACK_STATUS_REQUEST_ERROR |
|
|
WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE;
|
|
if (WinHttpSetStatusCallback(response->win32_request_session,
|
|
DQN_CAST(WINHTTP_STATUS_CALLBACK)Dqn_OS_HttpRequestWin32Callback,
|
|
callback_flags,
|
|
DQN_CAST(DWORD_PTR)nullptr /*dwReserved*/) == WINHTTP_INVALID_STATUS_CALLBACK) {
|
|
error = Dqn_Win_LastError(&response->tmp_arena);
|
|
return;
|
|
}
|
|
|
|
Dqn_Str16 host16 = Dqn_Win_Str8ToStr16(scratch_arena, host);
|
|
response->win32_request_connection = WinHttpConnect(response->win32_request_session, host16.data, secure ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT, 0 /*reserved*/);
|
|
if (!response->win32_request_connection) {
|
|
error = Dqn_Win_LastError(&response->tmp_arena);
|
|
return;
|
|
}
|
|
|
|
Dqn_Str16 method16 = Dqn_Win_Str8ToStr16(scratch_arena, method);
|
|
Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch_arena, path);
|
|
response->win32_request_handle = WinHttpOpenRequest(response->win32_request_connection,
|
|
method16.data,
|
|
path16.data,
|
|
nullptr /*version*/,
|
|
nullptr /*referrer*/,
|
|
nullptr /*accept types*/,
|
|
secure ? WINHTTP_FLAG_SECURE : 0);
|
|
if (!response->win32_request_handle) {
|
|
error = Dqn_Win_LastError(&response->tmp_arena);
|
|
return;
|
|
}
|
|
|
|
Dqn_Str16 headers16 = Dqn_Win_Str8ToStr16(scratch_arena, headers);
|
|
response->on_complete_semaphore = Dqn_OS_SemaphoreInit(0);
|
|
if (!WinHttpSendRequest(response->win32_request_handle,
|
|
headers16.data,
|
|
DQN_CAST(DWORD)headers16.size,
|
|
body.data /*optional data*/,
|
|
DQN_CAST(DWORD) body.size /*optional length*/,
|
|
DQN_CAST(DWORD) body.size /*total content length*/,
|
|
DQN_CAST(DWORD_PTR)response)) {
|
|
error = Dqn_Win_LastError(&response->tmp_arena);
|
|
return;
|
|
}
|
|
}
|
|
|
|
DQN_API void Dqn_OS_HttpRequestFree(Dqn_OSHttpResponse *response)
|
|
{
|
|
// NOTE: Cleanup
|
|
// NOTE: These calls are synchronous even when the HTTP request is async.
|
|
WinHttpCloseHandle(response->win32_request_handle);
|
|
WinHttpCloseHandle(response->win32_request_connection);
|
|
WinHttpCloseHandle(response->win32_request_session);
|
|
|
|
response->win32_request_session = nullptr;
|
|
response->win32_request_connection = nullptr;
|
|
response->win32_request_handle = nullptr;
|
|
Dqn_Arena_Deinit(&response->tmp_arena);
|
|
if (Dqn_OS_SemaphoreIsValid(&response->on_complete_semaphore))
|
|
Dqn_OS_SemaphoreDeinit(&response->on_complete_semaphore);
|
|
|
|
*response = {};
|
|
}
|
|
|
|
// NOTE: [$WIND] Dqn_Win ///////////////////////////////////////////////////////////////////////////
|
|
DQN_API Dqn_WinError Dqn_Win_ErrorCodeToMsg(Dqn_Arena *arena, uint32_t error_code)
|
|
{
|
|
Dqn_WinError result = {};
|
|
result.code = error_code;
|
|
if (!arena)
|
|
return result;
|
|
|
|
DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
|
|
void *module_to_get_errors_from = nullptr;
|
|
|
|
if (result.code >= 12000 && result.code <= 12175) {
|
|
flags |= FORMAT_MESSAGE_FROM_HMODULE;
|
|
module_to_get_errors_from = GetModuleHandleA("winhttp.dll");
|
|
}
|
|
|
|
wchar_t *error16 = nullptr;
|
|
DWORD size = FormatMessageW(/*DWORD dwFlags */ flags | FORMAT_MESSAGE_ALLOCATE_BUFFER,
|
|
/*LPCVOID lpSource */ module_to_get_errors_from,
|
|
/*DWORD dwMessageId */ result.code,
|
|
/*DWORD dwLanguageId*/ 0,
|
|
/*LPWSTR lpBuffer */ (LPWSTR)&error16,
|
|
/*DWORD nSize */ 0,
|
|
/*va_list *Arguments */ nullptr);
|
|
if (size)
|
|
result.msg = Dqn_Win_Str16ToStr8(arena, {error16, size});
|
|
if (error16)
|
|
LocalFree(error16);
|
|
|
|
return result;
|
|
}
|
|
|
|
DQN_API Dqn_WinError Dqn_Win_LastError(Dqn_Arena *arena)
|
|
{
|
|
Dqn_WinError result = Dqn_Win_ErrorCodeToMsg(arena, GetLastError());
|
|
return result;
|
|
}
|
|
|
|
DQN_API void Dqn_Win_MakeProcessDPIAware()
|
|
{
|
|
typedef bool SetProcessDpiAwareProc(void);
|
|
typedef bool SetProcessDpiAwarenessProc(DPI_AWARENESS);
|
|
typedef bool SetProcessDpiAwarenessContextProc(void * /*DPI_AWARENESS_CONTEXT*/);
|
|
|
|
// NOTE(doyle): Taken from cmuratori/refterm snippet on DPI awareness. It
|
|
// appears we can make this robust by just loading user32.dll and using
|
|
// GetProcAddress on the DPI function. If it's not there, we're on an old
|
|
// version of windows, so we can call an older version of the API.
|
|
void *lib_handle = LoadLibraryA("user32.dll");
|
|
if (!lib_handle)
|
|
return;
|
|
|
|
if (auto *set_process_dpi_awareness_context = DQN_CAST(SetProcessDpiAwarenessContextProc *)GetProcAddress(DQN_CAST(HMODULE)lib_handle, "SetProcessDpiAwarenessContext")) {
|
|
set_process_dpi_awareness_context(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
|
} else if (auto *set_process_dpi_awareness = DQN_CAST(SetProcessDpiAwarenessProc *)GetProcAddress(DQN_CAST(HMODULE)lib_handle, "SetProcessDpiAwareness")) {
|
|
set_process_dpi_awareness(DPI_AWARENESS_PER_MONITOR_AWARE);
|
|
} else if (auto *set_process_dpi_aware = DQN_CAST(SetProcessDpiAwareProc *)GetProcAddress(DQN_CAST(HMODULE)lib_handle, "SetProcessDpiAware")) {
|
|
set_process_dpi_aware();
|
|
}
|
|
}
|
|
|
|
// NOTE: Windows UTF8 to Str16 //////////////////////////////////////////////
|
|
DQN_API Dqn_Str16 Dqn_Win_Str8ToStr16(Dqn_Arena *arena, Dqn_Str8 src)
|
|
{
|
|
Dqn_Str16 result = {};
|
|
if (!arena || !Dqn_Str8_HasData(src))
|
|
return result;
|
|
|
|
int required_size = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DQN_CAST(int)src.size, nullptr /*dest*/, 0 /*dest size*/);
|
|
if (required_size <= 0)
|
|
return result;
|
|
|
|
wchar_t *buffer = Dqn_Arena_NewArray(arena, wchar_t, required_size + 1, Dqn_ZeroMem_No);
|
|
if (!buffer)
|
|
return result;
|
|
|
|
int chars_written = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DQN_CAST(int)src.size, buffer, required_size);
|
|
if (DQN_CHECK(chars_written == required_size)) {
|
|
result.data = buffer;
|
|
result.size = chars_written;
|
|
result.data[result.size] = 0;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DQN_API int Dqn_Win_Str8ToStr16Buffer(Dqn_Str8 src, wchar_t *dest, int dest_size)
|
|
{
|
|
int result = 0;
|
|
if (!Dqn_Str8_HasData(src))
|
|
return result;
|
|
|
|
result = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DQN_CAST(int)src.size, nullptr /*dest*/, 0 /*dest size*/);
|
|
if (result <= 0 || result > dest_size || !dest)
|
|
return result;
|
|
|
|
result = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DQN_CAST(int)src.size, dest, DQN_CAST(int)dest_size);
|
|
dest[DQN_MIN(result, dest_size - 1)] = 0;
|
|
return result;
|
|
}
|
|
|
|
// NOTE: Windows Str16 To UTF8 //////////////////////////////////////////////////////////////////
|
|
DQN_API int Dqn_Win_Str16ToStr8Buffer(Dqn_Str16 src, char *dest, int dest_size)
|
|
{
|
|
int result = 0;
|
|
if (!Dqn_Str16_HasData(src))
|
|
return result;
|
|
|
|
int src_size = Dqn_Safe_SaturateCastISizeToInt(src.size);
|
|
if (src_size <= 0)
|
|
return result;
|
|
|
|
result = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, nullptr /*dest*/, 0 /*dest size*/, nullptr, nullptr);
|
|
if (result <= 0 || result > dest_size || !dest)
|
|
return result;
|
|
|
|
result = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, dest, DQN_CAST(int)dest_size, nullptr, nullptr);
|
|
dest[DQN_MIN(result, dest_size - 1)] = 0;
|
|
return result;
|
|
}
|
|
|
|
DQN_API Dqn_Str8 Dqn_Win_Str16ToStr8(Dqn_Arena *arena, Dqn_Str16 src)
|
|
{
|
|
Dqn_Str8 result = {};
|
|
if (!arena || !Dqn_Str16_HasData(src))
|
|
return result;
|
|
|
|
int src_size = Dqn_Safe_SaturateCastISizeToInt(src.size);
|
|
if (src_size <= 0)
|
|
return result;
|
|
|
|
int required_size = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, nullptr /*dest*/, 0 /*dest size*/, nullptr, nullptr);
|
|
if (required_size <= 0)
|
|
return result;
|
|
|
|
// NOTE: Str8 allocate ensures there's one extra byte for
|
|
// null-termination already so no-need to +1 the required size
|
|
Dqn_ArenaTempMemScope temp_mem = Dqn_ArenaTempMemScope(arena);
|
|
Dqn_Str8 buffer = Dqn_Str8_Alloc(arena, required_size, Dqn_ZeroMem_No);
|
|
if (!Dqn_Str8_HasData(buffer))
|
|
return result;
|
|
|
|
int chars_written = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, buffer.data, DQN_CAST(int)buffer.size, nullptr, nullptr);
|
|
if (DQN_CHECK(chars_written == required_size)) {
|
|
result = buffer;
|
|
result.data[result.size] = 0;
|
|
temp_mem.mem = {};
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// NOTE: Windows Executable Directory //////////////////////////////////////////
|
|
DQN_API Dqn_Str16 Dqn_Win_EXEPathW(Dqn_Arena *arena)
|
|
{
|
|
Dqn_Scratch scratch = Dqn_Scratch_Get(arena);
|
|
Dqn_Str16 result = {};
|
|
Dqn_usize module_size = 0;
|
|
wchar_t *module_path = nullptr;
|
|
do {
|
|
module_size += 256;
|
|
module_path = Dqn_Arena_NewArray(scratch.arena, wchar_t, module_size, Dqn_ZeroMem_No);
|
|
if (!module_path)
|
|
return result;
|
|
module_size = DQN_CAST(Dqn_usize)GetModuleFileNameW(nullptr /*module*/, module_path, DQN_CAST(int)module_size);
|
|
} while (GetLastError() == ERROR_INSUFFICIENT_BUFFER);
|
|
|
|
Dqn_usize index_of_last_slash = 0;
|
|
for (Dqn_usize index = module_size - 1; !index_of_last_slash && index < module_size; index--)
|
|
index_of_last_slash = module_path[index] == '\\' ? index : 0;
|
|
|
|
result.data = Dqn_Arena_NewArray(arena, wchar_t, module_size + 1, Dqn_ZeroMem_No);
|
|
result.size = module_size;
|
|
DQN_MEMCPY(result.data, module_path, sizeof(wchar_t) * result.size);
|
|
result.data[result.size] = 0;
|
|
return result;
|
|
}
|
|
|
|
DQN_API Dqn_Str16 Dqn_Win_EXEDirW(Dqn_Arena *arena)
|
|
{
|
|
// TODO(doyle): Implement a Dqn_Str16_BinarySearchReverse
|
|
Dqn_Scratch scratch = Dqn_Scratch_Get(arena);
|
|
Dqn_Str16 result = {};
|
|
Dqn_usize module_size = 0;
|
|
wchar_t *module_path = nullptr;
|
|
do {
|
|
module_size += 256;
|
|
module_path = Dqn_Arena_NewArray(scratch.arena, wchar_t, module_size, Dqn_ZeroMem_No);
|
|
if (!module_path)
|
|
return result;
|
|
module_size = DQN_CAST(Dqn_usize)GetModuleFileNameW(nullptr /*module*/, module_path, DQN_CAST(int)module_size);
|
|
} while (GetLastError() == ERROR_INSUFFICIENT_BUFFER);
|
|
|
|
Dqn_usize index_of_last_slash = 0;
|
|
for (Dqn_usize index = module_size - 1; !index_of_last_slash && index < module_size; index--)
|
|
index_of_last_slash = module_path[index] == '\\' ? index : 0;
|
|
|
|
result.data = Dqn_Arena_NewArray(arena, wchar_t, index_of_last_slash + 1, Dqn_ZeroMem_No);
|
|
result.size = index_of_last_slash;
|
|
DQN_MEMCPY(result.data, module_path, sizeof(wchar_t) * result.size);
|
|
result.data[result.size] = 0;
|
|
return result;
|
|
}
|
|
|
|
DQN_API Dqn_Str8 Dqn_Win_WorkingDir(Dqn_Arena *arena, Dqn_Str8 suffix)
|
|
{
|
|
Dqn_Scratch scratch = Dqn_Scratch_Get(arena);
|
|
Dqn_Str16 suffix16 = Dqn_Win_Str8ToStr16(scratch.arena, suffix);
|
|
Dqn_Str16 dir16 = Dqn_Win_WorkingDirW(scratch.arena, suffix16);
|
|
Dqn_Str8 result = Dqn_Win_Str16ToStr8(arena, dir16);
|
|
return result;
|
|
}
|
|
|
|
DQN_API Dqn_Str16 Dqn_Win_WorkingDirW(Dqn_Arena *arena, Dqn_Str16 suffix)
|
|
{
|
|
DQN_ASSERT(suffix.size >= 0);
|
|
Dqn_Str16 result = {};
|
|
|
|
// NOTE: required_size is the size required *including* the null-terminator
|
|
Dqn_Scratch scratch = Dqn_Scratch_Get(arena);
|
|
unsigned long required_size = GetCurrentDirectoryW(0, nullptr);
|
|
unsigned long desired_size = required_size + DQN_CAST(unsigned long) suffix.size;
|
|
|
|
wchar_t *scratch_w_path = Dqn_Arena_NewArray(scratch.arena, wchar_t, desired_size, Dqn_ZeroMem_No);
|
|
if (!scratch_w_path)
|
|
return result;
|
|
|
|
unsigned long bytes_written_wo_null_terminator = GetCurrentDirectoryW(desired_size, scratch_w_path);
|
|
if ((bytes_written_wo_null_terminator + 1) != required_size) {
|
|
// TODO(dqn): Error
|
|
return result;
|
|
}
|
|
|
|
wchar_t *w_path = Dqn_Arena_NewArray(arena, wchar_t, desired_size, Dqn_ZeroMem_No);
|
|
if (!w_path)
|
|
return result;
|
|
|
|
if (suffix.size) {
|
|
DQN_MEMCPY(w_path, scratch_w_path, sizeof(*scratch_w_path) * bytes_written_wo_null_terminator);
|
|
DQN_MEMCPY(w_path + bytes_written_wo_null_terminator, suffix.data, sizeof(suffix.data[0]) * suffix.size);
|
|
w_path[desired_size] = 0;
|
|
}
|
|
|
|
result = Dqn_Str16{w_path, DQN_CAST(Dqn_usize)(desired_size - 1)};
|
|
return result;
|
|
}
|
|
|
|
DQN_API bool Dqn_Win_FolderWIterate(Dqn_Str16 path, Dqn_Win_FolderIteratorW *it)
|
|
{
|
|
WIN32_FIND_DATAW find_data = {};
|
|
if (it->handle) {
|
|
if (FindNextFileW(it->handle, &find_data) == 0)
|
|
return false;
|
|
} else {
|
|
it->handle = FindFirstFileExW(path.data, /*LPCWSTR lpFileName,*/
|
|
FindExInfoStandard, /*FINDEX_INFO_LEVELS fInfoLevelId,*/
|
|
&find_data, /*LPVOID lpFindFileData,*/
|
|
FindExSearchNameMatch, /*FINDEX_SEARCH_OPS fSearchOp,*/
|
|
nullptr, /*LPVOID lpSearchFilter,*/
|
|
FIND_FIRST_EX_LARGE_FETCH /*unsigned long dwAdditionalFlags)*/);
|
|
|
|
if (it->handle == INVALID_HANDLE_VALUE)
|
|
return false;
|
|
}
|
|
|
|
it->file_name_buf[0] = 0;
|
|
it->file_name = Dqn_Str16{it->file_name_buf, 0};
|
|
|
|
do {
|
|
if (find_data.cFileName[0] == '.' || (find_data.cFileName[0] == '.' && find_data.cFileName[1] == '.'))
|
|
continue;
|
|
|
|
it->file_name.size = Dqn_CStr16_Size(find_data.cFileName);
|
|
DQN_ASSERT(it->file_name.size < (DQN_ARRAY_UCOUNT(it->file_name_buf) - 1));
|
|
DQN_MEMCPY(it->file_name.data, find_data.cFileName, it->file_name.size * sizeof(wchar_t));
|
|
it->file_name_buf[it->file_name.size] = 0;
|
|
break;
|
|
} while (FindNextFileW(it->handle, &find_data) != 0);
|
|
|
|
return it->file_name.size > 0;
|
|
}
|
|
|
|
DQN_API bool Dqn_Win_FolderIterate(Dqn_Str8 path, Dqn_Win_FolderIterator *it)
|
|
{
|
|
if (!Dqn_Str8_HasData(path) || !it || path.size <= 0)
|
|
return false;
|
|
|
|
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
|
|
Dqn_Win_FolderIteratorW wide_it = {};
|
|
Dqn_Str16 path16 = {};
|
|
if (it->handle) {
|
|
wide_it.handle = it->handle;
|
|
} else {
|
|
bool needs_asterisks = Dqn_Str8_EndsWith(path, DQN_STR8("\\")) ||
|
|
Dqn_Str8_EndsWith(path, DQN_STR8("/"));
|
|
bool has_glob = Dqn_Str8_EndsWith(path, DQN_STR8("\\*")) ||
|
|
Dqn_Str8_EndsWith(path, DQN_STR8("/*"));
|
|
|
|
Dqn_Str8 adjusted_path = path;
|
|
if (!has_glob) {
|
|
// NOTE: We are missing the glob for enumerating the files, we will
|
|
// add those characters in this branch, so overwrite the null
|
|
// character, add the glob and re-null terminate the buffer.
|
|
if (needs_asterisks)
|
|
adjusted_path = Dqn_OS_PathConvertF(scratch.arena, "%.*s*", DQN_STR_FMT(path));
|
|
else
|
|
adjusted_path = Dqn_OS_PathConvertF(scratch.arena, "%.*s/*", DQN_STR_FMT(path));
|
|
}
|
|
|
|
path16 = Dqn_Win_Str8ToStr16(scratch.arena, adjusted_path);
|
|
if (path16.size <= 0) // Conversion error
|
|
return false;
|
|
}
|
|
|
|
bool result = Dqn_Win_FolderWIterate(path16, &wide_it);
|
|
it->handle = wide_it.handle;
|
|
if (result) {
|
|
int size = Dqn_Win_Str16ToStr8Buffer(wide_it.file_name, it->file_name_buf, DQN_ARRAY_UCOUNT(it->file_name_buf));
|
|
it->file_name = Dqn_Str8_Init(it->file_name_buf, size);
|
|
}
|
|
|
|
return result;
|
|
}
|