2024-04-18 22:59:11 +10:00
# pragma once
# include "dqn.h"
2024-08-01 13:34:36 +10:00
/*
2024-01-31 23:49:23 +11:00
////////////////////////////////////////////////////////////////////////////////////////////////////
//
/ / $ $ $ $ $ $ \ $ $ $ $ $ $ \ $ $ \ $ $ \ $ $ $ $ $ $ \ $ $ \ $ $ \ $ $ $ $ $ $ \ $ $ $ $ $ $ \
/ / $ $ __ $ $ \ $ $ __ $ $ \ $ $ | $ \ $ $ | \ _ $ $ _ | $ $ $ \ $ $ | $ $ ___ $ $ \ $ $ __ $ $ \
// $$ / $$ |$$ / \__| $$ |$$$\ $$ | $$ | $$$$\ $$ |\_/ $$ |\__/ $$ |
// $$ | $$ |\$$$$$$\ $$ $$ $$\$$ | $$ | $$ $$\$$ | $$$$$ / $$$$$$ |
// $$ | $$ | \____$$\ $$$$ _$$$$ | $$ | $$ \$$$$ | \___$$\ $$ ____/
// $$ | $$ |$$\ $$ | $$$ / \$$$ | $$ | $$ |\$$$ |$$\ $$ |$$ |
/ / $ $ $ $ $ $ | \ $ $ $ $ $ $ | $ $ / \ $ $ | $ $ $ $ $ $ \ $ $ | \ $ $ | \ $ $ $ $ $ $ | $ $ $ $ $ $ $ $ \
// \______/ \______/ \__/ \__|\______|\__| \__| \______/ \________|
//
// dqn_os_win32.cpp
//
////////////////////////////////////////////////////////////////////////////////////////////////////
2024-08-01 13:34:36 +10:00
*/
2024-01-31 23:49:23 +11:00
2025-02-14 00:27:42 +11:00
// NOTE: [$VMEM] DN_OSMem /////////////////////////////////////////////////////////////////////////
static uint32_t DN_OS_MemConvertPageToOSFlags_ ( uint32_t protect )
2024-01-31 23:49:23 +11:00
{
2025-02-14 00:27:42 +11:00
DN_ASSERT ( ( protect & ~ DN_OSMemPage_All ) = = 0 ) ;
DN_ASSERT ( protect ! = 0 ) ;
2024-01-31 23:49:23 +11:00
uint32_t result = 0 ;
2025-02-14 00:27:42 +11:00
if ( protect & DN_OSMemPage_NoAccess ) {
2024-01-31 23:49:23 +11:00
result = PAGE_NOACCESS ;
} else {
2025-02-14 00:27:42 +11:00
if ( protect & DN_OSMemPage_ReadWrite ) {
2024-01-31 23:49:23 +11:00
result = PAGE_READWRITE ;
2025-02-14 00:27:42 +11:00
} else if ( protect & DN_OSMemPage_Read ) {
2024-01-31 23:49:23 +11:00
result = PAGE_READONLY ;
2025-02-14 00:27:42 +11:00
} else if ( protect & DN_OSMemPage_Write ) {
DN_Log_WarningF ( " Windows does not support write-only pages, granting read+write access " ) ;
2024-01-31 23:49:23 +11:00
result = PAGE_READWRITE ;
}
}
2025-02-14 00:27:42 +11:00
if ( protect & DN_OSMemPage_Guard )
2024-01-31 23:49:23 +11:00
result | = PAGE_GUARD ;
2025-02-14 00:27:42 +11:00
DN_ASSERTF ( result ! = PAGE_GUARD , " Page guard is a modifier, you must also specify a page permission like read or/and write " ) ;
2024-01-31 23:49:23 +11:00
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API void * DN_OS_MemReserve ( DN_USize size , DN_OSMemCommit commit , uint32_t page_flags )
2024-01-31 23:49:23 +11:00
{
2025-02-14 00:27:42 +11:00
unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_ ( page_flags ) ;
unsigned long flags = MEM_RESERVE | ( commit = = DN_OSMemCommit_Yes ? MEM_COMMIT : 0 ) ;
2024-01-31 23:49:23 +11:00
void * result = VirtualAlloc ( nullptr , size , flags , os_page_flags ) ;
2025-02-14 00:27:42 +11:00
DN_Atomic_AddU64 ( & g_dn_core - > mem_allocs_total , DN_CAST ( bool ) ( flags & MEM_COMMIT ) ) ;
DN_Atomic_AddU64 ( & g_dn_core - > mem_allocs_frame , DN_CAST ( bool ) ( flags & MEM_COMMIT ) ) ;
2024-01-31 23:49:23 +11:00
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API bool DN_OS_MemCommit ( void * ptr , DN_USize size , uint32_t page_flags )
2024-01-31 23:49:23 +11:00
{
bool result = false ;
if ( ! ptr | | size = = 0 )
return false ;
2025-02-14 00:27:42 +11:00
unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_ ( page_flags ) ;
2024-01-31 23:49:23 +11:00
result = VirtualAlloc ( ptr , size , MEM_COMMIT , os_page_flags ) ! = nullptr ;
2025-02-14 00:27:42 +11:00
DN_Atomic_AddU64 ( & g_dn_core - > mem_allocs_total , 1 ) ;
DN_Atomic_AddU64 ( & g_dn_core - > mem_allocs_frame , 1 ) ;
2024-01-31 23:49:23 +11:00
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API void DN_OS_MemDecommit ( void * ptr , DN_USize size )
2024-01-31 23:49:23 +11:00
{
// 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.
2025-02-14 00:27:42 +11:00
DN_MSVC_WARNING_PUSH
DN_MSVC_WARNING_DISABLE ( 6250 ) // Calling 'VirtualFree' without the MEM_RELEASE flag might free memory but not address descriptors (VADs). This causes address space leaks.
2024-01-31 23:49:23 +11:00
VirtualFree ( ptr , size , MEM_DECOMMIT ) ;
2025-02-14 00:27:42 +11:00
DN_MSVC_WARNING_POP
2024-01-31 23:49:23 +11:00
}
2025-02-14 00:27:42 +11:00
DN_API void DN_OS_MemRelease ( void * ptr , DN_USize size )
2024-01-31 23:49:23 +11:00
{
( void ) size ;
VirtualFree ( ptr , 0 , MEM_RELEASE ) ;
}
2025-02-14 00:27:42 +11:00
DN_API int DN_OS_MemProtect ( void * ptr , DN_USize size , uint32_t page_flags )
2024-01-31 23:49:23 +11:00
{
if ( ! ptr | | size = = 0 )
return 0 ;
2025-02-14 00:27:42 +11:00
static DN_Str8 const ALIGNMENT_ERROR_MSG =
DN_STR8 ( " Page protection requires pointers to be page aligned because we "
2024-01-31 23:49:23 +11:00
" can only guard memory at a multiple of the page boundary. " ) ;
2025-02-14 00:27:42 +11:00
DN_ASSERTF ( DN_IsPowerOfTwoAligned ( DN_CAST ( uintptr_t ) ptr , g_dn_core - > os_page_size ) , " %s " , ALIGNMENT_ERROR_MSG . data ) ;
DN_ASSERTF ( DN_IsPowerOfTwoAligned ( size , g_dn_core - > os_page_size ) , " %s " , ALIGNMENT_ERROR_MSG . data ) ;
2024-01-31 23:49:23 +11:00
2025-02-14 00:27:42 +11:00
unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_ ( page_flags ) ;
2024-01-31 23:49:23 +11:00
unsigned long prev_flags = 0 ;
int result = VirtualProtect ( ptr , size , os_page_flags , & prev_flags ) ;
( void ) prev_flags ;
if ( result = = 0 )
2025-02-14 00:27:42 +11:00
DN_ASSERTF ( result , " VirtualProtect failed " ) ;
2024-01-31 23:49:23 +11:00
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API void * DN_OS_MemAlloc ( DN_USize size , DN_ZeroMem zero_mem )
{
uint32_t flags = zero_mem = = DN_ZeroMem_Yes ? HEAP_ZERO_MEMORY : 0 ;
DN_ASSERT ( size < = DN_CAST ( DWORD ) ( - 1 ) ) ;
void * result = HeapAlloc ( GetProcessHeap ( ) , flags , DN_CAST ( DWORD ) size ) ;
return result ;
}
DN_API void DN_OS_MemDealloc ( void * ptr )
{
HeapFree ( GetProcessHeap ( ) , 0 , ptr ) ;
}
2024-01-31 23:49:23 +11:00
// NOTE: [$DATE] Date //////////////////////////////////////////////////////////////////////////////
2025-02-14 00:27:42 +11:00
DN_API DN_OSDateTime DN_OS_DateLocalTimeNow ( )
2024-01-31 23:49:23 +11:00
{
SYSTEMTIME sys_time ;
GetLocalTime ( & sys_time ) ;
2025-02-14 00:27:42 +11:00
DN_OSDateTime result = { } ;
result . hour = DN_CAST ( uint8_t ) sys_time . wHour ;
result . minutes = DN_CAST ( uint8_t ) sys_time . wMinute ;
result . seconds = DN_CAST ( uint8_t ) sys_time . wSecond ;
result . day = DN_CAST ( uint8_t ) sys_time . wDay ;
result . month = DN_CAST ( uint8_t ) sys_time . wMonth ;
result . year = DN_CAST ( int16_t ) sys_time . wYear ;
2024-01-31 23:49:23 +11:00
return result ;
}
2025-02-14 00:27:42 +11:00
const uint64_t DN_OS_WIN32_UNIX_TIME_START = 0x019DB1DED53E8000 ; // January 1, 1970 (start of Unix epoch) in "ticks"
const uint64_t DN_OS_WIN32_FILE_TIME_TICKS_PER_SECOND = 10'000'000 ; // Filetime returned is in intervals of 100 nanoseconds
2024-02-25 22:37:14 +11:00
2025-02-14 00:27:42 +11:00
DN_API uint64_t DN_OS_DateUnixTimeNs ( )
2024-01-31 23:49:23 +11:00
{
FILETIME file_time ;
GetSystemTimeAsFileTime ( & file_time ) ;
2025-02-14 00:27:42 +11:00
// NOTE: Filetime returned is in intervals of 100 nanoeseconds so we
// multiply by 100 to get nanoseconds.
LARGE_INTEGER date_time ;
date_time . u . LowPart = file_time . dwLowDateTime ;
date_time . u . HighPart = file_time . dwHighDateTime ;
uint64_t result = ( date_time . QuadPart - DN_OS_WIN32_UNIX_TIME_START ) * 100 ;
return result ;
}
static SYSTEMTIME DN_OS_DateToSystemTime_ ( DN_OSDateTime date )
{
SYSTEMTIME result = { } ;
result . wYear = date . year ;
result . wMonth = date . month ;
result . wDay = date . day ;
result . wHour = date . hour ;
result . wMinute = date . minutes ;
result . wSecond = date . seconds ;
return result ;
}
static uint64_t DN_OS_SystemTimeToUnixTimeS_ ( SYSTEMTIME * sys_time )
{
FILETIME file_time = { } ;
SystemTimeToFileTime ( sys_time , & file_time ) ;
2024-01-31 23:49:23 +11:00
LARGE_INTEGER date_time ;
date_time . u . LowPart = file_time . dwLowDateTime ;
date_time . u . HighPart = file_time . dwHighDateTime ;
2025-02-14 00:27:42 +11:00
uint64_t result = ( date_time . QuadPart - DN_OS_WIN32_UNIX_TIME_START ) / DN_OS_WIN32_FILE_TIME_TICKS_PER_SECOND ;
return result ;
}
DN_API uint64_t DN_OS_DateLocalToUnixTimeS ( DN_OSDateTime date )
{
SYSTEMTIME local_time = DN_OS_DateToSystemTime_ ( date ) ;
SYSTEMTIME sys_time = { } ;
TzSpecificLocalTimeToSystemTime ( nullptr , & local_time , & sys_time ) ;
uint64_t result = DN_OS_SystemTimeToUnixTimeS_ ( & sys_time ) ;
2024-02-25 22:37:14 +11:00
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API uint64_t DN_OS_DateToUnixTimeS ( DN_OSDateTime date )
{
DN_ASSERT ( DN_OS_DateIsValid ( date ) ) ;
SYSTEMTIME sys_time = DN_OS_DateToSystemTime_ ( date ) ;
uint64_t result = DN_OS_SystemTimeToUnixTimeS_ ( & sys_time ) ;
return result ;
}
DN_API DN_OSDateTime DN_OS_DateUnixTimeSToDate ( uint64_t time )
2024-04-18 22:59:11 +10:00
{
// NOTE: Windows epoch time starts from Jan 1, 1601 and counts in
// 100-nanoseconds intervals.
//
// See: https://devblogs.microsoft.com/oldnewthing/20090306-00/?p=18913
uint64_t win32_time = 116'444'736'000'000'000 + ( time * 10'000'000 ) ;
SYSTEMTIME sys_time = { } ;
FILETIME file_time = { } ;
file_time . dwLowDateTime = ( DWORD ) win32_time ;
file_time . dwHighDateTime = win32_time > > 32 ;
FileTimeToSystemTime ( & file_time , & sys_time ) ;
2025-02-14 00:27:42 +11:00
DN_OSDateTime result = { } ;
result . year = DN_CAST ( uint16_t ) sys_time . wYear ;
result . month = DN_CAST ( uint8_t ) sys_time . wMonth ;
result . day = DN_CAST ( uint8_t ) sys_time . wDay ;
result . hour = DN_CAST ( uint8_t ) sys_time . wHour ;
result . minutes = DN_CAST ( uint8_t ) sys_time . wMinute ;
result . seconds = DN_CAST ( uint8_t ) sys_time . wSecond ;
2024-01-31 23:49:23 +11:00
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API bool DN_OS_SecureRNGBytes ( void * buffer , uint32_t size )
2024-01-31 23:49:23 +11:00
{
if ( ! buffer | | size < 0 )
return false ;
if ( size = = 0 )
return true ;
bool init = true ;
2025-02-14 00:27:42 +11:00
DN_TicketMutex_Begin ( & g_dn_core - > win32_bcrypt_rng_mutex ) ;
if ( ! g_dn_core - > win32_bcrypt_rng_handle )
2024-01-31 23:49:23 +11:00
{
wchar_t const BCRYPT_ALGORITHM [ ] = L " RNG " ;
2025-02-14 00:27:42 +11:00
long /*NTSTATUS*/ init_status = BCryptOpenAlgorithmProvider ( & g_dn_core - > win32_bcrypt_rng_handle , BCRYPT_ALGORITHM , nullptr /*implementation*/ , 0 /*flags*/ ) ;
if ( ! g_dn_core - > win32_bcrypt_rng_handle | | init_status ! = 0 )
2024-01-31 23:49:23 +11:00
{
2025-02-14 00:27:42 +11:00
DN_Log_ErrorF ( " Failed to initialise random number generator, error: %d " , init_status ) ;
2024-01-31 23:49:23 +11:00
init = false ;
}
}
2025-02-14 00:27:42 +11:00
DN_TicketMutex_End ( & g_dn_core - > win32_bcrypt_rng_mutex ) ;
2024-01-31 23:49:23 +11:00
if ( ! init )
return false ;
2025-02-14 00:27:42 +11:00
long gen_status = BCryptGenRandom ( g_dn_core - > win32_bcrypt_rng_handle , DN_CAST ( unsigned char * ) buffer , size , 0 /*flags*/ ) ;
2024-01-31 23:49:23 +11:00
if ( gen_status ! = 0 )
{
2025-02-14 00:27:42 +11:00
DN_Log_ErrorF ( " Failed to generate random bytes: %d " , gen_status ) ;
2024-01-31 23:49:23 +11:00
return false ;
}
return true ;
}
2025-02-14 00:27:42 +11:00
DN_API bool DN_OS_SetEnvVar ( DN_Str8 name , DN_Str8 value )
{
DN_TLSTMem tmem = DN_TLS_PushTMem ( nullptr ) ;
DN_Str16 name16 = DN_Win_Str8ToStr16 ( tmem . arena , name ) ;
DN_Str16 value16 = DN_Win_Str8ToStr16 ( tmem . arena , value ) ;
bool result = SetEnvironmentVariableW ( name16 . data , value16 . data ) ! = 0 ;
return result ;
}
DN_API DN_Str8 DN_OS_EXEPath ( DN_Arena * arena )
2024-01-31 23:49:23 +11:00
{
2025-02-14 00:27:42 +11:00
DN_Str8 result = { } ;
2024-01-31 23:49:23 +11:00
if ( ! arena )
return result ;
2025-02-14 00:27:42 +11:00
DN_TLSTMem tmem = DN_TLS_TMem ( arena ) ;
DN_Str16 exe_dir16 = DN_Win_EXEPathW ( tmem . arena ) ;
result = DN_Win_Str16ToStr8 ( arena , exe_dir16 ) ;
2024-01-31 23:49:23 +11:00
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API void DN_OS_SleepMs ( DN_UInt milliseconds )
2024-01-31 23:49:23 +11:00
{
Sleep ( milliseconds ) ;
}
2025-02-14 00:27:42 +11:00
DN_API uint64_t DN_OS_PerfCounterFrequency ( )
2024-01-31 23:49:23 +11:00
{
2025-02-14 00:27:42 +11:00
uint64_t result = g_dn_core - > win32_qpc_frequency . QuadPart ;
DN_ASSERTF ( result , " Initialise the library with DN_Library_Init() to get a valid QPC frequency value " ) ;
2024-01-31 23:49:23 +11:00
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API uint64_t DN_OS_PerfCounterNow ( )
2024-01-31 23:49:23 +11:00
{
LARGE_INTEGER integer = { } ;
QueryPerformanceCounter ( & integer ) ;
uint64_t result = integer . QuadPart ;
return result ;
}
2025-02-14 00:27:42 +11:00
# if !defined(DN_NO_OS_FILE_API)
static uint64_t DN_Win_FileTimeToSeconds_ ( FILETIME const * time )
2024-01-31 23:49:23 +11:00
{
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 ;
}
2025-02-14 00:27:42 +11:00
DN_API DN_OSPathInfo DN_OS_PathInfo ( DN_Str8 path )
2024-01-31 23:49:23 +11:00
{
2025-02-14 00:27:42 +11:00
DN_OSPathInfo result = { } ;
if ( ! DN_Str8_HasData ( path ) )
2024-01-31 23:49:23 +11:00
return result ;
2025-02-14 00:27:42 +11:00
DN_TLSTMem tmem = DN_TLS_TMem ( nullptr ) ;
DN_Str16 path16 = DN_Win_Str8ToStr16 ( tmem . arena , path ) ;
2024-01-31 23:49:23 +11:00
WIN32_FILE_ATTRIBUTE_DATA attrib_data = { } ;
if ( ! GetFileAttributesExW ( path16 . data , GetFileExInfoStandard , & attrib_data ) )
return result ;
result . exists = true ;
2025-02-14 00:27:42 +11:00
result . create_time_in_s = DN_Win_FileTimeToSeconds_ ( & attrib_data . ftCreationTime ) ;
result . last_access_time_in_s = DN_Win_FileTimeToSeconds_ ( & attrib_data . ftLastAccessTime ) ;
result . last_write_time_in_s = DN_Win_FileTimeToSeconds_ ( & attrib_data . ftLastWriteTime ) ;
2024-01-31 23:49:23 +11:00
LARGE_INTEGER large_int = { } ;
2025-02-14 00:27:42 +11:00
large_int . u . HighPart = DN_CAST ( int32_t ) attrib_data . nFileSizeHigh ;
2024-01-31 23:49:23 +11:00
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 )
2025-02-14 00:27:42 +11:00
result . type = DN_OSPathInfoType_Directory ;
2024-01-31 23:49:23 +11:00
else
2025-02-14 00:27:42 +11:00
result . type = DN_OSPathInfoType_File ;
2024-01-31 23:49:23 +11:00
}
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API bool DN_OS_PathDelete ( DN_Str8 path )
2024-02-11 18:23:13 +11:00
{
bool result = false ;
2025-02-14 00:27:42 +11:00
if ( ! DN_Str8_HasData ( path ) )
2024-02-11 18:23:13 +11:00
return result ;
2025-02-14 00:27:42 +11:00
DN_TLSTMem tmem = DN_TLS_TMem ( nullptr ) ;
DN_Str16 path16 = DN_Win_Str8ToStr16 ( tmem . arena , path ) ;
2024-02-11 18:23:13 +11:00
if ( path16 . size ) {
result = DeleteFileW ( path16 . data ) ;
if ( ! result )
result = RemoveDirectoryW ( path16 . data ) ;
}
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API bool DN_OS_FileExists ( DN_Str8 path )
2024-01-31 23:49:23 +11:00
{
bool result = false ;
2025-02-14 00:27:42 +11:00
if ( ! DN_Str8_HasData ( path ) )
2024-01-31 23:49:23 +11:00
return result ;
2025-02-14 00:27:42 +11:00
DN_TLSTMem tmem = DN_TLS_TMem ( nullptr ) ;
DN_Str16 path16 = DN_Win_Str8ToStr16 ( tmem . arena , path ) ;
2024-01-31 23:49:23 +11:00
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 ;
}
2025-02-14 00:27:42 +11:00
DN_API bool DN_OS_CopyFile ( DN_Str8 src , DN_Str8 dest , bool overwrite , DN_ErrSink * err )
2024-01-31 23:49:23 +11:00
{
bool result = false ;
2025-02-14 00:27:42 +11:00
DN_TLSTMem tmem = DN_TLS_TMem ( nullptr ) ;
DN_Str16 src16 = DN_Win_Str8ToStr16 ( tmem . arena , src ) ;
DN_Str16 dest16 = DN_Win_Str8ToStr16 ( tmem . arena , dest ) ;
2024-01-31 23:49:23 +11:00
int fail_if_exists = overwrite = = false ;
2024-02-11 16:14:00 +11:00
result = CopyFileW ( src16 . data , dest16 . data , fail_if_exists ) ! = 0 ;
2024-01-31 23:49:23 +11:00
if ( ! result ) {
2025-02-14 00:27:42 +11:00
DN_WinError win_error = DN_Win_LastError ( tmem . arena ) ;
DN_ErrSink_AppendF ( err ,
2024-02-11 16:14:00 +11:00
win_error . code ,
" Failed to copy file '%.*s' to '%.*s': (%u) %.*s " ,
2025-02-14 00:27:42 +11:00
DN_STR_FMT ( src ) ,
DN_STR_FMT ( dest ) ,
2024-02-11 16:14:00 +11:00
win_error . code ,
2025-02-14 00:27:42 +11:00
DN_STR_FMT ( win_error . msg ) ) ;
2024-01-31 23:49:23 +11:00
}
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API bool DN_OS_MoveFile ( DN_Str8 src , DN_Str8 dest , bool overwrite , DN_ErrSink * err )
2024-01-31 23:49:23 +11:00
{
bool result = false ;
2025-02-14 00:27:42 +11:00
DN_TLSTMem tmem = DN_TLS_TMem ( nullptr ) ;
DN_Str16 src16 = DN_Win_Str8ToStr16 ( tmem . arena , src ) ;
DN_Str16 dest16 = DN_Win_Str8ToStr16 ( tmem . arena , dest ) ;
2024-01-31 23:49:23 +11:00
unsigned long flags = MOVEFILE_COPY_ALLOWED ;
if ( overwrite ) {
flags | = MOVEFILE_REPLACE_EXISTING ;
}
result = MoveFileExW ( src16 . data , dest16 . data , flags ) ! = 0 ;
if ( ! result ) {
2025-02-14 00:27:42 +11:00
DN_WinError win_error = DN_Win_LastError ( tmem . arena ) ;
DN_ErrSink_AppendF ( err ,
2024-02-11 16:14:00 +11:00
win_error . code ,
" Failed to move file '%.*s' to '%.*s': (%u) %.*s " ,
2025-02-14 00:27:42 +11:00
DN_STR_FMT ( src ) ,
DN_STR_FMT ( dest ) ,
2024-02-11 16:14:00 +11:00
win_error . code ,
2025-02-14 00:27:42 +11:00
DN_STR_FMT ( win_error . msg ) ) ;
2024-01-31 23:49:23 +11:00
}
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API bool DN_OS_MakeDir ( DN_Str8 path )
2024-01-31 23:49:23 +11:00
{
2024-08-01 13:34:36 +10:00
bool result = true ;
2025-02-14 00:27:42 +11:00
DN_TLSTMem tmem = DN_TLS_TMem ( nullptr ) ;
DN_Str16 path16 = DN_Win_Str8ToStr16 ( tmem . arena , path ) ;
2024-01-31 23:49:23 +11:00
// 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.
2025-02-14 00:27:42 +11:00
for ( DN_USize index = 0 ; index < path16 . size ; index + + ) {
2024-01-31 23:49:23 +11:00
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 ;
}
2024-02-11 18:23:13 +11:00
2025-02-14 00:27:42 +11:00
DN_API bool DN_OS_DirExists ( DN_Str8 path )
2024-01-31 23:49:23 +11:00
{
bool result = false ;
2025-02-14 00:27:42 +11:00
if ( ! DN_Str8_HasData ( path ) )
2024-01-31 23:49:23 +11:00
return result ;
2025-02-14 00:27:42 +11:00
DN_TLSTMem tmem = DN_TLS_TMem ( nullptr ) ;
DN_Str16 path16 = DN_Win_Str8ToStr16 ( tmem . arena , path ) ;
2024-01-31 23:49:23 +11:00
if ( path16 . size ) {
2024-02-11 18:23:13 +11:00
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 ) ;
}
2024-01-31 23:49:23 +11:00
}
2024-02-11 18:23:13 +11:00
2024-01-31 23:49:23 +11:00
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API bool DN_OS_DirIterate ( DN_Str8 path , DN_OSDirIterator * it )
{
if ( ! DN_Str8_HasData ( path ) | | ! it | | path . size < = 0 )
return false ;
DN_TLSTMem tmem = DN_TLS_TMem ( nullptr ) ;
DN_Win_FolderIteratorW wide_it = { } ;
DN_Str16 path16 = { } ;
if ( it - > handle ) {
wide_it . handle = it - > handle ;
} else {
bool needs_asterisks = DN_Str8_EndsWith ( path , DN_STR8 ( " \\ " ) ) | |
DN_Str8_EndsWith ( path , DN_STR8 ( " / " ) ) ;
bool has_glob = DN_Str8_EndsWith ( path , DN_STR8 ( " \\ * " ) ) | |
DN_Str8_EndsWith ( path , DN_STR8 ( " /* " ) ) ;
DN_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 = DN_OS_PathF ( tmem . arena , " %.*s* " , DN_STR_FMT ( path ) ) ;
else
adjusted_path = DN_OS_PathF ( tmem . arena , " %.*s/* " , DN_STR_FMT ( path ) ) ;
}
path16 = DN_Win_Str8ToStr16 ( tmem . arena , adjusted_path ) ;
if ( path16 . size < = 0 ) // Conversion error
return false ;
}
bool result = DN_Win_DirWIterate ( path16 , & wide_it ) ;
it - > handle = wide_it . handle ;
if ( result ) {
int size = DN_Win_Str16ToStr8Buffer ( wide_it . file_name , it - > buffer , DN_ARRAY_UCOUNT ( it - > buffer ) ) ;
it - > file_name = DN_Str8_Init ( it - > buffer , size ) ;
}
return result ;
}
2024-01-31 23:49:23 +11:00
// NOTE: R/W Stream API ////////////////////////////////////////////////////////////////////////////
2025-02-14 00:27:42 +11:00
DN_API DN_OSFile DN_OS_FileOpen ( DN_Str8 path , DN_OSFileOpen open_mode , uint32_t access , DN_ErrSink * err )
2024-01-31 23:49:23 +11:00
{
2025-02-14 00:27:42 +11:00
DN_OSFile result = { } ;
if ( ! DN_Str8_HasData ( path ) | | path . size < = 0 )
2024-01-31 23:49:23 +11:00
return result ;
2025-02-14 00:27:42 +11:00
if ( ( access & ~ DN_OSFileAccess_All ) | | ( ( access & DN_OSFileAccess_All ) = = 0 ) ) {
DN_INVALID_CODE_PATH ;
2024-01-31 23:49:23 +11:00
return result ;
}
unsigned long create_flag = 0 ;
switch ( open_mode ) {
2025-02-14 00:27:42 +11:00
case DN_OSFileOpen_CreateAlways : create_flag = CREATE_ALWAYS ; break ;
case DN_OSFileOpen_OpenIfExist : create_flag = OPEN_EXISTING ; break ;
case DN_OSFileOpen_OpenAlways : create_flag = OPEN_ALWAYS ; break ;
default : DN_INVALID_CODE_PATH ; return result ;
2024-01-31 23:49:23 +11:00
}
unsigned long access_mode = 0 ;
2025-02-14 00:27:42 +11:00
if ( access & DN_OSFileAccess_AppendOnly ) {
DN_ASSERTF ( ( access & ~ DN_OSFileAccess_AppendOnly ) = = 0 ,
2024-01-31 23:49:23 +11:00
" Append can only be applied exclusively to the file, other access modes not permitted " ) ;
access_mode = FILE_APPEND_DATA ;
} else {
2025-02-14 00:27:42 +11:00
if ( access & DN_OSFileAccess_Read )
2024-01-31 23:49:23 +11:00
access_mode | = GENERIC_READ ;
2025-02-14 00:27:42 +11:00
if ( access & DN_OSFileAccess_Write )
2024-01-31 23:49:23 +11:00
access_mode | = GENERIC_WRITE ;
2025-02-14 00:27:42 +11:00
if ( access & DN_OSFileAccess_Execute )
2024-01-31 23:49:23 +11:00
access_mode | = GENERIC_EXECUTE ;
}
2025-02-14 00:27:42 +11:00
DN_TLSTMem tmem = DN_TLS_TMem ( nullptr ) ;
DN_Str16 path16 = DN_Win_Str8ToStr16 ( tmem . arena , path ) ;
2024-01-31 23:49:23 +11:00
void * handle = CreateFileW ( /*LPCWSTR lpFileName*/ path16 . data ,
/*DWORD dwDesiredAccess*/ access_mode ,
2025-02-14 00:27:42 +11:00
/*DWORD dwShareMode*/ FILE_SHARE_READ | FILE_SHARE_WRITE ,
2024-01-31 23:49:23 +11:00
/*LPSECURITY_ATTRIBUTES lpSecurityAttributes*/ nullptr ,
/*DWORD dwCreationDisposition*/ create_flag ,
/*DWORD dwFlagsAndAttributes*/ FILE_ATTRIBUTE_NORMAL ,
/*HANDLE hTemplateFile*/ nullptr ) ;
if ( handle = = INVALID_HANDLE_VALUE ) {
2025-02-14 00:27:42 +11:00
DN_WinError win_error = DN_Win_LastError ( tmem . arena ) ;
2024-02-11 16:14:00 +11:00
result . error = true ;
2025-02-14 00:27:42 +11:00
DN_ErrSink_AppendF ( err , win_error . code , " Failed to open file at '%.*s': '%.*s' " , DN_STR_FMT ( path ) , DN_STR_FMT ( win_error . msg ) ) ;
2024-01-31 23:49:23 +11:00
return result ;
}
result . handle = handle ;
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API bool DN_OS_FileRead ( DN_OSFile * file , void * buffer , DN_USize size , DN_ErrSink * err )
2024-02-11 18:23:13 +11:00
{
if ( ! file | | ! file - > handle | | file - > error | | ! buffer | | size < = 0 )
return false ;
2025-02-14 00:27:42 +11:00
DN_TLSTMem tmem = DN_TLS_TMem ( nullptr ) ;
if ( ! DN_CHECK ( size < = ( unsigned long ) - 1 ) ) {
DN_Str8 buffer_size_str8 = DN_U64ToByteSizeStr8 ( tmem . arena , size , DN_U64ByteSizeType_Auto ) ;
DN_ErrSink_AppendF (
err ,
2024-02-11 18:23:13 +11:00
1 /*error_code*/ ,
" Current implementation doesn't support reading >4GiB file (requested %.*s), implement Win32 overlapped IO " ,
2025-02-14 00:27:42 +11:00
DN_STR_FMT ( buffer_size_str8 ) ) ;
2024-02-11 18:23:13 +11:00
return false ;
}
unsigned long bytes_read = 0 ;
unsigned long read_result = ReadFile ( /*HANDLE hFile*/ file - > handle ,
/*LPVOID lpBuffer*/ buffer ,
2025-02-14 00:27:42 +11:00
/*DWORD nNumberOfBytesToRead*/ DN_CAST ( unsigned long ) size ,
2024-02-11 18:23:13 +11:00
/*LPDWORD lpNumberOfByesRead*/ & bytes_read ,
/*LPOVERLAPPED lpOverlapped*/ nullptr ) ;
if ( read_result = = 0 ) {
2025-02-14 00:27:42 +11:00
DN_WinError win_error = DN_Win_LastError ( tmem . arena ) ;
DN_ErrSink_AppendF ( err , win_error . code , " Failed to read data from file: (%u) %.*s " , win_error . code , DN_STR_FMT ( win_error . msg ) ) ;
2024-02-11 18:23:13 +11:00
return false ;
}
if ( bytes_read ! = size ) {
2025-02-14 00:27:42 +11:00
DN_WinError win_error = DN_Win_LastError ( tmem . arena ) ;
DN_ErrSink_AppendF (
err ,
2024-02-11 18:23:13 +11:00
win_error . code ,
" Failed to read the desired number of bytes from file, we read %uB but we expected %uB: (%u) %.*s " ,
bytes_read ,
2025-02-14 00:27:42 +11:00
DN_CAST ( unsigned long ) size ,
2024-02-11 18:23:13 +11:00
win_error . code ,
2025-02-14 00:27:42 +11:00
DN_STR_FMT ( win_error . msg ) ) ;
2024-02-11 18:23:13 +11:00
return false ;
}
return true ;
}
2025-02-14 00:27:42 +11:00
DN_API bool DN_OS_FileWritePtr ( DN_OSFile * file , void const * buffer , DN_USize size , DN_ErrSink * err )
2024-01-31 23:49:23 +11:00
{
2024-02-11 16:14:00 +11:00
if ( ! file | | ! file - > handle | | file - > error | | ! buffer | | size < = 0 )
2024-01-31 23:49:23 +11:00
return false ;
bool result = true ;
2025-02-14 00:27:42 +11:00
char const * end = DN_CAST ( char * ) buffer + size ;
for ( char const * ptr = DN_CAST ( char const * ) buffer ; result & & ptr ! = end ; ) {
unsigned long write_size = DN_CAST ( unsigned long ) DN_MIN ( ( unsigned long ) - 1 , end - ptr ) ;
2024-01-31 23:49:23 +11:00
unsigned long bytes_written = 0 ;
result = WriteFile ( file - > handle , ptr , write_size , & bytes_written , nullptr /*lpOverlapped*/ ) ! = 0 ;
ptr + = bytes_written ;
}
if ( ! result ) {
2025-02-14 00:27:42 +11:00
DN_TLSTMem tmem = DN_TLS_TMem ( nullptr ) ;
DN_WinError win_error = DN_Win_LastError ( tmem . arena ) ;
DN_Str8 buffer_size_str8 = DN_U64ToByteSizeStr8 ( tmem . arena , size , DN_U64ByteSizeType_Auto ) ;
DN_ErrSink_AppendF ( err , win_error . code , " Failed to write buffer (%.*s) to file handle: %.*s " , DN_STR_FMT ( buffer_size_str8 ) , DN_STR_FMT ( win_error . msg ) ) ;
2024-01-31 23:49:23 +11:00
}
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API bool DN_OS_FileFlush ( DN_OSFile * file , DN_ErrSink * err )
{
if ( ! file | | ! file - > handle | | file - > error )
return false ;
BOOL result = FlushFileBuffers ( DN_CAST ( HANDLE ) file - > handle ) ;
if ( ! result ) {
DN_TLSTMem tmem = DN_TLS_TMem ( nullptr ) ;
DN_WinError win_error = DN_Win_LastError ( tmem . arena ) ;
DN_ErrSink_AppendF ( err , win_error . code , " Failed to flush file buffer to disk: %.*s " , DN_STR_FMT ( win_error . msg ) ) ;
}
return DN_CAST ( bool ) result ;
}
DN_API void DN_OS_FileClose ( DN_OSFile * file )
2024-01-31 23:49:23 +11:00
{
2024-02-11 16:14:00 +11:00
if ( ! file | | ! file - > handle | | file - > error )
2024-01-31 23:49:23 +11:00
return ;
CloseHandle ( file - > handle ) ;
* file = { } ;
}
2025-02-14 00:27:42 +11:00
# endif // !defined(DN_NO_OS_FILE_API)
2024-01-31 23:49:23 +11:00
2025-02-14 00:27:42 +11:00
// NOTE: [$EXEC] DN_OSExec ////////////////////////////////////////////////////////////////////////
DN_API void DN_OS_Exit ( int32_t exit_code )
2024-01-31 23:49:23 +11:00
{
2025-02-14 00:27:42 +11:00
ExitProcess ( DN_CAST ( UINT ) exit_code ) ;
2024-01-31 23:49:23 +11:00
}
2025-02-14 00:27:42 +11:00
DN_API DN_OSExecResult DN_OS_ExecPump ( DN_OSExecAsyncHandle handle ,
char * stdout_buffer ,
size_t * stdout_size ,
char * stderr_buffer ,
size_t * stderr_size ,
uint32_t timeout_ms ,
DN_ErrSink * err )
2024-01-31 23:49:23 +11:00
{
2025-02-14 00:27:42 +11:00
DN_OSExecResult result = { } ;
size_t stdout_buffer_size = 0 ;
size_t stderr_buffer_size = 0 ;
if ( stdout_size ) {
stdout_buffer_size = * stdout_size ;
* stdout_size = 0 ;
}
if ( stderr_size ) {
stderr_buffer_size = * stderr_size ;
* stderr_size = 0 ;
}
2024-03-25 13:14:05 +11:00
if ( ! handle . process | | handle . os_error_code | | handle . exit_code ) {
if ( handle . os_error_code )
result . os_error_code = handle . os_error_code ;
else
result . exit_code = handle . exit_code ;
2024-01-31 23:49:23 +11:00
2025-02-14 00:27:42 +11:00
DN_ASSERT ( ! handle . stdout_read ) ;
DN_ASSERT ( ! handle . stdout_write ) ;
DN_ASSERT ( ! handle . stderr_read ) ;
DN_ASSERT ( ! handle . stderr_write ) ;
DN_ASSERT ( ! handle . process ) ;
2024-01-31 23:49:23 +11:00
return result ;
}
2025-02-14 00:27:42 +11:00
DN_TLSTMem tmem = DN_TLS_TMem ( nullptr ) ;
DWORD stdout_bytes_available = 0 ;
DWORD stderr_bytes_available = 0 ;
PeekNamedPipe ( handle . stdout_read , nullptr , 0 , nullptr , & stdout_bytes_available , nullptr ) ;
PeekNamedPipe ( handle . stderr_read , nullptr , 0 , nullptr , & stderr_bytes_available , nullptr ) ;
2024-03-25 13:14:05 +11:00
2025-02-14 00:27:42 +11:00
DWORD exec_result = WAIT_TIMEOUT ;
if ( stdout_bytes_available = = 0 & & stderr_bytes_available = = 0 )
exec_result = WaitForSingleObject ( handle . process , timeout_ms ) ;
2024-08-01 13:34:36 +10:00
2025-02-14 00:27:42 +11:00
if ( exec_result = = WAIT_FAILED ) {
DN_WinError win_error = DN_Win_LastError ( tmem . arena ) ;
result . os_error_code = win_error . code ;
DN_ErrSink_AppendF ( err , result . os_error_code , " Executed command failed to terminate: %.*s " , DN_STR_FMT ( win_error . msg ) ) ;
} else {
if ( DN_CHECK ( exec_result = = WAIT_TIMEOUT | | exec_result = = WAIT_OBJECT_0 ) ) {
2024-08-01 13:34:36 +10:00
// NOTE: Read stdout from process //////////////////////////////////////////////////////
2025-02-14 00:27:42 +11:00
// If the pipes are full, the process will block. We periodically
// flush the pipes to make sure this doesn't happen
char sink [ DN_KILOBYTES ( 8 ) ] ;
stdout_bytes_available = 0 ;
2024-08-01 13:34:36 +10:00
if ( PeekNamedPipe ( handle . stdout_read , nullptr , 0 , nullptr , & stdout_bytes_available , nullptr ) ) {
if ( stdout_bytes_available ) {
2025-02-14 00:27:42 +11:00
DWORD bytes_read = 0 ;
char * dest_buffer = handle . stdout_write & & stdout_buffer ? stdout_buffer : sink ;
size_t dest_size = handle . stdout_write & & stdout_buffer ? stdout_buffer_size : DN_ARRAY_UCOUNT ( sink ) ;
BOOL success = ReadFile ( handle . stdout_read , dest_buffer , DN_CAST ( DWORD ) dest_size , & bytes_read , NULL ) ;
( void ) success ; // TODO:
if ( stdout_size )
* stdout_size = bytes_read ;
2024-08-01 13:34:36 +10:00
}
}
// NOTE: Read stderr from process //////////////////////////////////////////////////////
2025-02-14 00:27:42 +11:00
stderr_bytes_available = 0 ;
2024-08-01 13:34:36 +10:00
if ( PeekNamedPipe ( handle . stderr_read , nullptr , 0 , nullptr , & stderr_bytes_available , nullptr ) ) {
if ( stderr_bytes_available ) {
2025-02-14 00:27:42 +11:00
char * dest_buffer = handle . stderr_write & & stderr_buffer ? stderr_buffer : sink ;
size_t dest_size = handle . stderr_write & & stderr_buffer ? stderr_buffer_size : DN_ARRAY_UCOUNT ( sink ) ;
DWORD bytes_read = 0 ;
BOOL success = ReadFile ( handle . stderr_read , dest_buffer , DN_CAST ( DWORD ) dest_size , & bytes_read , NULL ) ;
( void ) success ; // TODO:
if ( stderr_size )
* stderr_size = bytes_read ;
2024-08-01 13:34:36 +10:00
}
}
}
}
2025-02-14 00:27:42 +11:00
result . finished = exec_result = = WAIT_OBJECT_0 | | exec_result = = WAIT_FAILED ;
if ( exec_result = = WAIT_OBJECT_0 ) {
2024-03-25 13:14:05 +11:00
DWORD exit_status ;
if ( GetExitCodeProcess ( handle . process , & exit_status ) ) {
result . exit_code = exit_status ;
} else {
2025-02-14 00:27:42 +11:00
DN_WinError win_error = DN_Win_LastError ( tmem . arena ) ;
2024-03-25 13:14:05 +11:00
result . os_error_code = win_error . code ;
2025-02-14 00:27:42 +11:00
DN_ErrSink_AppendF ( err ,
2024-03-25 13:14:05 +11:00
result . os_error_code ,
" Failed to retrieve command exit code: %.*s " ,
2025-02-14 00:27:42 +11:00
DN_STR_FMT ( win_error . msg ) ) ;
2024-03-25 13:14:05 +11:00
}
2025-02-14 00:27:42 +11:00
// NOTE: Cleanup ///////////////////////////////////////////////////////////////////////////////
CloseHandle ( handle . stdout_write ) ;
CloseHandle ( handle . stderr_write ) ;
CloseHandle ( handle . stdout_read ) ;
CloseHandle ( handle . stderr_read ) ;
CloseHandle ( handle . process ) ;
}
result . stdout_text = DN_Str8_Init ( stdout_buffer , stdout_size ? * stdout_size : 0 ) ;
result . stderr_text = DN_Str8_Init ( stderr_buffer , stderr_size ? * stderr_size : 0 ) ;
return result ;
}
DN_API DN_OSExecResult DN_OS_ExecWait ( DN_OSExecAsyncHandle handle , DN_Arena * arena , DN_ErrSink * err )
{
DN_OSExecResult result = { } ;
if ( ! handle . process | | handle . os_error_code | | handle . exit_code ) {
result . finished = true ;
if ( handle . os_error_code )
result . os_error_code = handle . os_error_code ;
else
result . exit_code = handle . exit_code ;
DN_ASSERT ( ! handle . stdout_read ) ;
DN_ASSERT ( ! handle . stdout_write ) ;
DN_ASSERT ( ! handle . stderr_read ) ;
DN_ASSERT ( ! handle . stderr_write ) ;
DN_ASSERT ( ! handle . process ) ;
return result ;
}
DN_TLSTMem tmem = DN_TLS_TMem ( arena ) ;
DN_Str8Builder stdout_builder = { } ;
DN_Str8Builder stderr_builder = { } ;
if ( arena ) {
stdout_builder . arena = tmem . arena ;
stderr_builder . arena = tmem . arena ;
}
uint32_t const SLOW_WAIT_TIME_MS = 100 ;
uint32_t const FAST_WAIT_TIME_MS = 20 ;
uint32_t wait_ms = FAST_WAIT_TIME_MS ;
while ( ! result . finished ) {
size_t stdout_size = DN_KILOBYTES ( 8 ) ;
size_t stderr_size = DN_KILOBYTES ( 8 ) ;
char * stdout_buffer = DN_Arena_NewArray ( tmem . arena , char , stdout_size , DN_ZeroMem_No ) ;
char * stderr_buffer = DN_Arena_NewArray ( tmem . arena , char , stderr_size , DN_ZeroMem_No ) ;
result = DN_OS_ExecPump ( handle , stdout_buffer , & stdout_size , stderr_buffer , & stderr_size , wait_ms , err ) ;
DN_Str8Builder_AppendCopy ( & stdout_builder , result . stdout_text ) ;
DN_Str8Builder_AppendCopy ( & stderr_builder , result . stderr_text ) ;
wait_ms = ( DN_Str8_HasData ( result . stdout_text ) | | DN_Str8_HasData ( result . stderr_text ) ) ? FAST_WAIT_TIME_MS : SLOW_WAIT_TIME_MS ;
2024-01-31 23:49:23 +11:00
}
2025-02-14 00:27:42 +11:00
// NOTE: Get stdout/stderr. If no arena is passed this is a no-op //////////////////////////////
result . stdout_text = DN_Str8Builder_Build ( & stdout_builder , arena ) ;
result . stderr_text = DN_Str8Builder_Build ( & stderr_builder , arena ) ;
2024-01-31 23:49:23 +11:00
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync ( DN_Slice < DN_Str8 > cmd_line , DN_OSExecArgs * args , DN_ErrSink * err )
2024-01-31 23:49:23 +11:00
{
2024-03-25 13:14:05 +11:00
// NOTE: Pre-amble /////////////////////////////////////////////////////////////////////////////
2025-02-14 00:27:42 +11:00
DN_OSExecAsyncHandle result = { } ;
2024-01-31 23:49:23 +11:00
if ( cmd_line . size = = 0 )
return result ;
2025-02-14 00:27:42 +11:00
DN_TLSTMem tmem = DN_TLS_TMem ( nullptr ) ;
DN_Str8 cmd_rendered = DN_Slice_Str8Render ( tmem . arena , cmd_line , DN_STR8 ( " " ) ) ;
DN_Str16 cmd16 = DN_Win_Str8ToStr16 ( tmem . arena , cmd_rendered ) ;
DN_Str16 working_dir16 = DN_Win_Str8ToStr16 ( tmem . arena , args - > working_dir ) ;
DN_Str8Builder env_builder = DN_Str8Builder_Init_TLS ( ) ;
DN_Str8Builder_AppendArrayRef ( & env_builder , args - > environment . data , args - > environment . size ) ;
if ( env_builder . string_size )
DN_Str8Builder_AppendRef ( & env_builder , DN_STR8 ( " \0 " ) ) ;
DN_Str8 env_block8 = DN_Str8Builder_BuildDelimited_TLS ( & env_builder , DN_STR8 ( " \0 " ) ) ;
DN_Str16 env_block16 = { } ;
if ( env_block8 . size )
env_block16 = DN_Win_Str8ToStr16 ( tmem . arena , env_block8 ) ;
2024-01-31 23:49:23 +11:00
2024-03-25 13:14:05 +11:00
// NOTE: Stdout/err security attributes ////////////////////////////////////////////////////////
SECURITY_ATTRIBUTES save_std_security_attribs = { } ;
save_std_security_attribs . nLength = sizeof ( save_std_security_attribs ) ;
save_std_security_attribs . bInheritHandle = true ;
// NOTE: Redirect stdout ///////////////////////////////////////////////////////////////////////
HANDLE stdout_read = { } ;
HANDLE stdout_write = { } ;
2025-02-14 00:27:42 +11:00
DN_DEFER {
2024-03-25 16:11:57 +11:00
if ( result . os_error_code | | result . exit_code ) {
CloseHandle ( stdout_read ) ;
CloseHandle ( stdout_write ) ;
}
} ;
2025-02-14 00:27:42 +11:00
if ( DN_Bit_IsSet ( args - > flags , DN_OSExecFlags_SaveStdout ) ) {
2024-03-25 13:14:05 +11:00
if ( ! CreatePipe ( & stdout_read , & stdout_write , & save_std_security_attribs , /*nSize*/ 0 ) ) {
2025-02-14 00:27:42 +11:00
DN_WinError win_error = DN_Win_LastError ( tmem . arena ) ;
2024-03-25 13:14:05 +11:00
result . os_error_code = win_error . code ;
2025-02-14 00:27:42 +11:00
DN_ErrSink_AppendF (
err ,
2024-03-25 13:14:05 +11:00
result . os_error_code ,
" Failed to create stdout pipe to redirect the output of the command '%.*s': %.*s " ,
2025-02-14 00:27:42 +11:00
DN_STR_FMT ( cmd_rendered ) ,
DN_STR_FMT ( win_error . msg ) ) ;
2024-03-25 13:14:05 +11:00
return result ;
}
if ( ! SetHandleInformation ( stdout_read , HANDLE_FLAG_INHERIT , 0 ) ) {
2025-02-14 00:27:42 +11:00
DN_WinError win_error = DN_Win_LastError ( tmem . arena ) ;
2024-03-25 13:14:05 +11:00
result . os_error_code = win_error . code ;
2025-02-14 00:27:42 +11:00
DN_ErrSink_AppendF ( err ,
2024-03-25 13:14:05 +11:00
result . os_error_code ,
" Failed to make stdout 'read' pipe non-inheritable when trying to "
" execute command '%.*s': %.*s " ,
2025-02-14 00:27:42 +11:00
DN_STR_FMT ( cmd_rendered ) ,
DN_STR_FMT ( win_error . msg ) ) ;
2024-03-25 13:14:05 +11:00
return result ;
}
}
// NOTE: Redirect stderr ///////////////////////////////////////////////////////////////////////
HANDLE stderr_read = { } ;
HANDLE stderr_write = { } ;
2025-02-14 00:27:42 +11:00
DN_DEFER {
2024-03-25 16:11:57 +11:00
if ( result . os_error_code | | result . exit_code ) {
CloseHandle ( stderr_read ) ;
CloseHandle ( stderr_write ) ;
}
} ;
2025-02-14 00:27:42 +11:00
if ( DN_Bit_IsSet ( args - > flags , DN_OSExecFlags_SaveStderr ) ) {
if ( DN_Bit_IsSet ( args - > flags , DN_OSExecFlags_MergeStderrToStdout ) ) {
2024-03-25 13:14:05 +11:00
stderr_read = stdout_read ;
stderr_write = stdout_write ;
} else {
if ( ! CreatePipe ( & stderr_read , & stderr_write , & save_std_security_attribs , /*nSize*/ 0 ) ) {
2025-02-14 00:27:42 +11:00
DN_WinError win_error = DN_Win_LastError ( tmem . arena ) ;
2024-03-25 13:14:05 +11:00
result . os_error_code = win_error . code ;
2025-02-14 00:27:42 +11:00
DN_ErrSink_AppendF (
err ,
2024-03-25 13:14:05 +11:00
result . os_error_code ,
" Failed to create stderr pipe to redirect the output of the command '%.*s': %.*s " ,
2025-02-14 00:27:42 +11:00
DN_STR_FMT ( cmd_rendered ) ,
DN_STR_FMT ( win_error . msg ) ) ;
2024-03-25 13:14:05 +11:00
return result ;
}
if ( ! SetHandleInformation ( stderr_read , HANDLE_FLAG_INHERIT , 0 ) ) {
2025-02-14 00:27:42 +11:00
DN_WinError win_error = DN_Win_LastError ( tmem . arena ) ;
2024-03-25 13:14:05 +11:00
result . os_error_code = win_error . code ;
2025-02-14 00:27:42 +11:00
DN_ErrSink_AppendF ( err ,
2024-03-25 13:14:05 +11:00
result . os_error_code ,
" Failed to make stderr 'read' pipe non-inheritable when trying to "
" execute command '%.*s': %.*s " ,
2025-02-14 00:27:42 +11:00
DN_STR_FMT ( cmd_rendered ) ,
DN_STR_FMT ( win_error . msg ) ) ;
2024-03-25 13:14:05 +11:00
return result ;
}
}
}
// NOTE: Execute command ///////////////////////////////////////////////////////////////////////
2024-01-31 23:49:23 +11:00
PROCESS_INFORMATION proc_info = { } ;
STARTUPINFOW startup_info = { } ;
startup_info . cb = sizeof ( STARTUPINFOW ) ;
2024-03-25 13:14:05 +11:00
startup_info . hStdError = stderr_write ? stderr_write : GetStdHandle ( STD_ERROR_HANDLE ) ;
startup_info . hStdOutput = stdout_write ? stdout_write : GetStdHandle ( STD_OUTPUT_HANDLE ) ;
2024-01-31 23:49:23 +11:00
startup_info . hStdInput = GetStdHandle ( STD_INPUT_HANDLE ) ;
startup_info . dwFlags | = STARTF_USESTDHANDLES ;
2025-02-14 00:27:42 +11:00
BOOL create_result = CreateProcessW ( nullptr ,
cmd16 . data ,
nullptr ,
nullptr ,
true ,
CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT ,
env_block16 . data ,
working_dir16 . data ,
& startup_info ,
& proc_info ) ;
2024-01-31 23:49:23 +11:00
if ( ! create_result ) {
2025-02-14 00:27:42 +11:00
DN_WinError win_error = DN_Win_LastError ( tmem . arena ) ;
2024-03-25 13:14:05 +11:00
result . os_error_code = win_error . code ;
2025-02-14 00:27:42 +11:00
DN_ErrSink_AppendF ( err ,
2024-03-25 13:14:05 +11:00
result . os_error_code ,
" Failed to execute command '%.*s': %.*s " ,
2025-02-14 00:27:42 +11:00
DN_STR_FMT ( cmd_rendered ) ,
DN_STR_FMT ( win_error . msg ) ) ;
2024-01-31 23:49:23 +11:00
return result ;
}
2024-03-25 13:14:05 +11:00
// NOTE: Post-amble ////////////////////////////////////////////////////////////////////////////
2024-01-31 23:49:23 +11:00
CloseHandle ( proc_info . hThread ) ;
2024-03-25 13:14:05 +11:00
result . process = proc_info . hProcess ;
result . stdout_read = stdout_read ;
result . stdout_write = stdout_write ;
2025-02-14 00:27:42 +11:00
if ( DN_Bit_IsSet ( args - > flags , DN_OSExecFlags_SaveStderr ) & & DN_Bit_IsNotSet ( args - > flags , DN_OSExecFlags_MergeStderrToStdout ) ) {
2024-03-25 13:14:05 +11:00
result . stderr_read = stderr_read ;
result . stderr_write = stderr_write ;
}
2025-02-14 00:27:42 +11:00
result . exec_flags = args - > flags ;
2024-01-31 23:49:23 +11:00
return result ;
}
2025-02-14 00:27:42 +11:00
# if !defined(DN_NO_SEMAPHORE)
// NOTE: [$SEMA] DN_OSSemaphore ///////////////////////////////////////////////////////////////////
DN_API DN_OSSemaphore DN_OS_SemaphoreInit ( uint32_t initial_count )
2024-01-31 23:49:23 +11:00
{
2025-02-14 00:27:42 +11:00
DN_OSSemaphore result = { } ;
2024-01-31 23:49:23 +11:00
SECURITY_ATTRIBUTES security_attribs = { } ;
result . win32_handle = CreateSemaphoreA ( & security_attribs , initial_count , INT32_MAX , nullptr /*name*/ ) ;
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API bool DN_OS_SemaphoreIsValid ( DN_OSSemaphore * semaphore )
2024-01-31 23:49:23 +11:00
{
bool result = false ;
if ( semaphore ) {
result = semaphore - > win32_handle ;
}
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API void DN_OS_SemaphoreDeinit ( DN_OSSemaphore * semaphore )
2024-01-31 23:49:23 +11:00
{
2025-02-14 00:27:42 +11:00
if ( ! DN_OS_SemaphoreIsValid ( semaphore ) )
2024-01-31 23:49:23 +11:00
return ;
CloseHandle ( semaphore - > win32_handle ) ;
* semaphore = { } ;
}
2025-02-14 00:27:42 +11:00
DN_API void DN_OS_SemaphoreIncrement ( DN_OSSemaphore * semaphore , uint32_t amount )
2024-01-31 23:49:23 +11:00
{
2025-02-14 00:27:42 +11:00
if ( ! DN_OS_SemaphoreIsValid ( semaphore ) )
2024-01-31 23:49:23 +11:00
return ;
LONG prev_count = 0 ;
2025-02-14 00:27:42 +11:00
ReleaseSemaphore ( DN_CAST ( HANDLE * ) semaphore - > win32_handle , amount , & prev_count ) ;
2024-01-31 23:49:23 +11:00
}
2025-02-14 00:27:42 +11:00
DN_API DN_OSSemaphoreWaitResult DN_OS_SemaphoreWait ( DN_OSSemaphore * semaphore , uint32_t timeout_ms )
2024-01-31 23:49:23 +11:00
{
2025-02-14 00:27:42 +11:00
DN_OSSemaphoreWaitResult result = { } ;
if ( ! DN_OS_SemaphoreIsValid ( semaphore ) )
2024-01-31 23:49:23 +11:00
return result ;
if ( ! semaphore - > win32_handle )
return result ;
2025-02-14 00:27:42 +11:00
DWORD wait_result = WaitForSingleObject ( semaphore - > win32_handle , timeout_ms = = DN_OS_SEMAPHORE_INFINITE_TIMEOUT ? INFINITE : timeout_ms ) ;
2024-01-31 23:49:23 +11:00
if ( wait_result = = WAIT_TIMEOUT )
2025-02-14 00:27:42 +11:00
result = DN_OSSemaphoreWaitResult_Timeout ;
2024-01-31 23:49:23 +11:00
else if ( wait_result = = WAIT_OBJECT_0 )
2025-02-14 00:27:42 +11:00
result = DN_OSSemaphoreWaitResult_Success ;
2024-01-31 23:49:23 +11:00
return result ;
}
2025-02-14 00:27:42 +11:00
# endif // !defined(DN_NO_SEMAPHORE)
2024-01-31 23:49:23 +11:00
2025-02-14 00:27:42 +11:00
# if !defined(DN_NO_THREAD)
// NOTE: [$MUTX] DN_OSMutex ///////////////////////////////////////////////////////////////////////
DN_API DN_OSMutex DN_OS_MutexInit ( )
2024-01-31 23:49:23 +11:00
{
2025-02-14 00:27:42 +11:00
DN_OSMutex result = { } ;
2024-01-31 23:49:23 +11:00
CRITICAL_SECTION crit_section = { } ;
InitializeCriticalSection ( & crit_section ) ;
2025-02-14 00:27:42 +11:00
static_assert ( sizeof ( CRITICAL_SECTION ) < = sizeof ( result . win32_handle ) , " Insufficient bytes to store Win32 mutex opaquely in our abstracted DN_OSMutex " ) ;
DN_MEMCPY ( result . win32_handle , & crit_section , sizeof ( crit_section ) ) ;
2024-01-31 23:49:23 +11:00
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API void DN_OS_MutexDeinit ( DN_OSMutex * mutex )
2024-01-31 23:49:23 +11:00
{
if ( ! mutex )
return ;
2025-02-14 00:27:42 +11:00
CRITICAL_SECTION * crit_section = DN_CAST ( CRITICAL_SECTION * ) mutex - > win32_handle ;
2024-01-31 23:49:23 +11:00
DeleteCriticalSection ( crit_section ) ;
2025-02-14 00:27:42 +11:00
DN_MEMSET ( mutex - > win32_handle , 0 , DN_ARRAY_UCOUNT ( mutex - > win32_handle ) ) ;
2024-01-31 23:49:23 +11:00
}
2025-02-14 00:27:42 +11:00
DN_API void DN_OS_MutexLock ( DN_OSMutex * mutex )
2024-01-31 23:49:23 +11:00
{
if ( ! mutex )
return ;
2025-02-14 00:27:42 +11:00
CRITICAL_SECTION * crit_section = DN_CAST ( CRITICAL_SECTION * ) mutex - > win32_handle ;
2024-01-31 23:49:23 +11:00
EnterCriticalSection ( crit_section ) ;
}
2025-02-14 00:27:42 +11:00
DN_API void DN_OS_MutexUnlock ( DN_OSMutex * mutex )
2024-01-31 23:49:23 +11:00
{
if ( ! mutex )
return ;
2025-02-14 00:27:42 +11:00
CRITICAL_SECTION * crit_section = DN_CAST ( CRITICAL_SECTION * ) mutex - > win32_handle ;
2024-01-31 23:49:23 +11:00
LeaveCriticalSection ( crit_section ) ;
}
2025-02-14 00:27:42 +11:00
// NOTE: [$THRD] DN_OSThread /////////////////////////////////////////////////////////////////////
static DWORD __stdcall DN_OS_ThreadFunc_ ( void * user_context )
2024-01-31 23:49:23 +11:00
{
2025-02-14 00:27:42 +11:00
DN_OS_ThreadExecute_ ( user_context ) ;
2024-01-31 23:49:23 +11:00
return 0 ;
}
2025-02-14 00:27:42 +11:00
DN_API bool DN_OS_ThreadInit ( DN_OSThread * thread , DN_OSThreadFunc * func , void * user_context )
2024-01-31 23:49:23 +11:00
{
bool result = false ;
if ( ! thread )
return result ;
thread - > func = func ;
thread - > user_context = user_context ;
2025-02-14 00:27:42 +11:00
thread - > init_semaphore = DN_OS_SemaphoreInit ( 0 /*initial_count*/ ) ;
2024-01-31 23:49:23 +11:00
// TODO(doyle): Check if semaphore is valid
DWORD thread_id = 0 ;
SECURITY_ATTRIBUTES security_attribs = { } ;
thread - > handle = CreateThread ( & security_attribs ,
0 /*stack_size*/ ,
2025-02-14 00:27:42 +11:00
DN_OS_ThreadFunc_ ,
2024-01-31 23:49:23 +11:00
thread ,
0 /*creation_flags*/ ,
& thread_id ) ;
result = thread - > handle ! = INVALID_HANDLE_VALUE ;
if ( result ) {
thread - > thread_id = thread_id ;
}
2025-02-14 00:27:42 +11:00
// NOTE: Ensure that thread_id is set before 'thread->func' is called.
2024-01-31 23:49:23 +11:00
if ( result ) {
2025-02-14 00:27:42 +11:00
DN_OS_SemaphoreIncrement ( & thread - > init_semaphore , 1 ) ;
2024-01-31 23:49:23 +11:00
} else {
2025-02-14 00:27:42 +11:00
DN_OS_SemaphoreDeinit ( & thread - > init_semaphore ) ;
2024-01-31 23:49:23 +11:00
* thread = { } ;
}
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API void DN_OS_ThreadDeinit ( DN_OSThread * thread )
2024-01-31 23:49:23 +11:00
{
if ( ! thread | | ! thread - > handle )
return ;
WaitForSingleObject ( thread - > handle , INFINITE ) ;
CloseHandle ( thread - > handle ) ;
thread - > handle = INVALID_HANDLE_VALUE ;
thread - > thread_id = { } ;
2025-02-14 00:27:42 +11:00
DN_TLS_Deinit ( & thread - > tls ) ;
2024-01-31 23:49:23 +11:00
}
2025-02-14 00:27:42 +11:00
DN_API uint32_t DN_OS_ThreadID ( )
2024-01-31 23:49:23 +11:00
{
unsigned long result = GetCurrentThreadId ( ) ;
return result ;
}
2025-02-14 00:27:42 +11:00
typedef HRESULT DN_WinSetThreadDescriptionFunc ( HANDLE hThread , PWSTR const lpThreadDescription ) ;
static DN_WinSetThreadDescriptionFunc * g_dn_win_set_thread_description = nullptr ;
DN_API void DN_Win_ThreadSetName ( DN_Str8 name )
{
DN_TLS * tls = DN_TLS_Get ( ) ;
DN_ArenaTempMem tmem = DN_Arena_TempMemBegin ( tls - > arenas + DN_TLSArena_TMem0 ) ;
// NOTE: SetThreadDescription is only available in
// Windows Server 2016, Windows 10 LTSB 2016 and Windows 10 version 1607
//
// See: https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreaddescription
if ( g_dn_win_set_thread_description ) {
DN_Str16 name16 = DN_Win_Str8ToStr16 ( tmem . arena , name ) ;
g_dn_win_set_thread_description ( GetCurrentThread ( ) , ( WCHAR * ) name16 . data ) ;
DN_Arena_TempMemEnd ( tmem ) ;
return ;
}
// NOTE: Fallback to throw-exception method to set thread name
# pragma pack(push, 8)
struct DN_WinThreadNameInfo {
uint32_t dwType ;
char * szName ;
uint32_t dwThreadID ;
uint32_t dwFlags ;
} ;
# pragma pack(pop)
DN_Str8 copy = DN_Str8_Copy ( tmem . arena , name ) ;
DN_WinThreadNameInfo info = { } ;
info . dwType = 0x1000 ;
info . szName = ( char * ) copy . data ;
info . dwThreadID = DN_OS_ThreadID ( ) ;
__try {
RaiseException ( 0x406D1388 , 0 , sizeof ( info ) / sizeof ( void * ) , ( const ULONG_PTR * ) & info ) ;
} __except ( EXCEPTION_EXECUTE_HANDLER ) {
}
DN_Arena_TempMemEnd ( tmem ) ;
}
# endif // !defined(DN_NO_THREAD)
// NOTE: [$HTTP] DN_OSHttp ////////////////////////////////////////////////////////////////////////
void DN_OS_HttpRequestWin32Callback ( HINTERNET session , DWORD * dwContext , DWORD dwInternetStatus , VOID * lpvStatusInformation , DWORD dwStatusInformationLength )
2024-01-31 23:49:23 +11:00
{
( void ) session ;
( void ) dwStatusInformationLength ;
2025-02-14 00:27:42 +11:00
DN_OSHttpResponse * response = DN_CAST ( DN_OSHttpResponse * ) dwContext ;
HINTERNET request = DN_CAST ( HINTERNET ) response - > win32_request_handle ;
DN_WinError error = { } ;
DWORD const READ_BUFFER_SIZE = DN_MEGABYTES ( 1 ) ;
2024-01-31 23:49:23 +11:00
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 ) ) {
2025-02-14 00:27:42 +11:00
response - > http_status = DN_CAST ( uint16_t ) status ;
2024-01-31 23:49:23 +11:00
// 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.
2025-02-14 00:27:42 +11:00
void * buffer = DN_Arena_Alloc ( response - > builder . arena , READ_BUFFER_SIZE , 1 /*align*/ , DN_ZeroMem_No ) ;
2024-01-31 23:49:23 +11:00
if ( ! WinHttpReadData ( request , buffer , READ_BUFFER_SIZE , nullptr ) )
2025-02-14 00:27:42 +11:00
error = DN_Win_LastError ( & response - > tmp_arena ) ;
2024-01-31 23:49:23 +11:00
} else {
2025-02-14 00:27:42 +11:00
error = DN_Win_LastError ( & response - > tmp_arena ) ;
2024-01-31 23:49:23 +11:00
}
} else if ( dwInternetStatus = = WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE ) {
} else if ( dwInternetStatus = = WINHTTP_CALLBACK_STATUS_READ_COMPLETE ) {
DWORD bytes_read = dwStatusInformationLength ;
if ( bytes_read ) {
2025-02-14 00:27:42 +11:00
DN_Str8 prev_buffer = DN_Str8_Init ( DN_CAST ( char * ) lpvStatusInformation , bytes_read ) ;
DN_Str8Builder_AppendRef ( & response - > builder , prev_buffer ) ;
2024-01-31 23:49:23 +11:00
2025-02-14 00:27:42 +11:00
void * buffer = DN_Arena_Alloc ( response - > builder . arena , READ_BUFFER_SIZE , 1 /*align*/ , DN_ZeroMem_No ) ;
2024-01-31 23:49:23 +11:00
if ( ! WinHttpReadData ( request , buffer , READ_BUFFER_SIZE , nullptr ) )
2025-02-14 00:27:42 +11:00
error = DN_Win_LastError ( & response - > tmp_arena ) ;
2024-01-31 23:49:23 +11:00
}
} else if ( dwInternetStatus = = WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE ) {
} else if ( dwInternetStatus = = WINHTTP_CALLBACK_STATUS_REQUEST_ERROR ) {
2025-02-14 00:27:42 +11:00
WINHTTP_ASYNC_RESULT * async_result = DN_CAST ( WINHTTP_ASYNC_RESULT * ) lpvStatusInformation ;
error = DN_Win_ErrorCodeToMsg ( & response - > tmp_arena , DN_CAST ( uint32_t ) async_result - > dwError ) ;
2024-01-31 23:49:23 +11:00
} else if ( dwInternetStatus = = WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE ) {
if ( ! WinHttpReceiveResponse ( request , 0 ) )
2025-02-14 00:27:42 +11:00
error = DN_Win_LastError ( & response - > tmp_arena ) ;
2024-01-31 23:49:23 +11:00
}
// 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.
2025-02-14 00:27:42 +11:00
if ( request ) {
2024-01-31 23:49:23 +11:00
bool read_complete = dwInternetStatus = = WINHTTP_CALLBACK_STATUS_READ_COMPLETE & & dwStatusInformationLength = = 0 ;
if ( read_complete ) {
2025-02-14 00:27:42 +11:00
response - > body = DN_Str8Builder_Build ( & response - > builder , response - > arena ) ;
2024-01-31 23:49:23 +11:00
}
if ( read_complete | | dwInternetStatus = = WINHTTP_CALLBACK_STATUS_REQUEST_ERROR | | error . code ) {
2025-02-14 00:27:42 +11:00
DN_OS_SemaphoreIncrement ( & response - > on_complete_semaphore , 1 ) ;
DN_Atomic_AddU32 ( & response - > done , 1 ) ;
2024-01-31 23:49:23 +11:00
}
if ( error . code ) {
response - > error_code = error . code ;
response - > error_msg = error . msg ;
}
}
}
2025-02-14 00:27:42 +11:00
DN_API void DN_OS_HttpRequestAsync ( DN_OSHttpResponse * response ,
DN_Arena * arena ,
DN_Str8 host ,
DN_Str8 path ,
DN_OSHttpRequestSecure secure ,
DN_Str8 method ,
DN_Str8 body ,
DN_Str8 headers )
2024-01-31 23:49:23 +11:00
{
if ( ! response | | ! arena )
return ;
response - > arena = arena ;
2024-08-01 13:34:36 +10:00
response - > builder . arena = response - > tmem_arena ? response - > tmem_arena : & response - > tmp_arena ;
2024-01-31 23:49:23 +11:00
2025-02-14 00:27:42 +11:00
DN_Arena * tmem_arena = response - > tmem_arena ;
DN_TLSTMem tmem_ = DN_TLS_TMem ( arena ) ;
if ( ! tmem_arena ) {
tmem_arena = tmem_ . arena ;
2024-01-31 23:49:23 +11:00
}
2025-02-14 00:27:42 +11:00
DN_WinError error = { } ;
DN_DEFER {
2024-01-31 23:49:23 +11:00
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
2025-02-14 00:27:42 +11:00
DN_OS_HttpRequestWait ( response ) ;
DN_Atomic_AddU32 ( & response - > done , 1 ) ;
2024-01-31 23:49:23 +11:00
}
} ;
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 ) {
2025-02-14 00:27:42 +11:00
error = DN_Win_LastError ( & response - > tmp_arena ) ;
2024-01-31 23:49:23 +11:00
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 ,
2025-02-14 00:27:42 +11:00
DN_CAST ( WINHTTP_STATUS_CALLBACK ) DN_OS_HttpRequestWin32Callback ,
2024-01-31 23:49:23 +11:00
callback_flags ,
2025-02-14 00:27:42 +11:00
DN_CAST ( DWORD_PTR ) nullptr /*dwReserved*/ ) = = WINHTTP_INVALID_STATUS_CALLBACK ) {
error = DN_Win_LastError ( & response - > tmp_arena ) ;
2024-01-31 23:49:23 +11:00
return ;
}
2025-02-14 00:27:42 +11:00
DN_Str16 host16 = DN_Win_Str8ToStr16 ( tmem_arena , host ) ;
2024-01-31 23:49:23 +11:00
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 ) {
2025-02-14 00:27:42 +11:00
error = DN_Win_LastError ( & response - > tmp_arena ) ;
2024-01-31 23:49:23 +11:00
return ;
}
2025-02-14 00:27:42 +11:00
DN_Str16 method16 = DN_Win_Str8ToStr16 ( tmem_arena , method ) ;
DN_Str16 path16 = DN_Win_Str8ToStr16 ( tmem_arena , path ) ;
2024-01-31 23:49:23 +11:00
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 ) {
2025-02-14 00:27:42 +11:00
error = DN_Win_LastError ( & response - > tmp_arena ) ;
2024-01-31 23:49:23 +11:00
return ;
}
2025-02-14 00:27:42 +11:00
DN_Str16 headers16 = DN_Win_Str8ToStr16 ( tmem_arena , headers ) ;
response - > on_complete_semaphore = DN_OS_SemaphoreInit ( 0 ) ;
2024-01-31 23:49:23 +11:00
if ( ! WinHttpSendRequest ( response - > win32_request_handle ,
headers16 . data ,
2025-02-14 00:27:42 +11:00
DN_CAST ( DWORD ) headers16 . size ,
2024-01-31 23:49:23 +11:00
body . data /*optional data*/ ,
2025-02-14 00:27:42 +11:00
DN_CAST ( DWORD ) body . size /*optional length*/ ,
DN_CAST ( DWORD ) body . size /*total content length*/ ,
DN_CAST ( DWORD_PTR ) response ) ) {
error = DN_Win_LastError ( & response - > tmp_arena ) ;
2024-01-31 23:49:23 +11:00
return ;
}
}
2025-02-14 00:27:42 +11:00
DN_API void DN_OS_HttpRequestFree ( DN_OSHttpResponse * response )
2024-01-31 23:49:23 +11:00
{
// 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 ;
2025-02-14 00:27:42 +11:00
DN_Arena_Deinit ( & response - > tmp_arena ) ;
if ( DN_OS_SemaphoreIsValid ( & response - > on_complete_semaphore ) )
DN_OS_SemaphoreDeinit ( & response - > on_complete_semaphore ) ;
2024-01-31 23:49:23 +11:00
* response = { } ;
}
2025-02-14 00:27:42 +11:00
// NOTE: [$WIND] DN_Win ///////////////////////////////////////////////////////////////////////////
DN_API DN_Str16 DN_Win_ErrorCodeToMsg16Alloc ( uint32_t error_code )
2024-01-31 23:49:23 +11:00
{
DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS ;
void * module_to_get_errors_from = nullptr ;
2025-02-14 00:27:42 +11:00
if ( error_code > = 12000 & & error_code < = 12175 ) {
2024-01-31 23:49:23 +11:00
flags | = FORMAT_MESSAGE_FROM_HMODULE ;
module_to_get_errors_from = GetModuleHandleA ( " winhttp.dll " ) ;
}
2025-02-14 00:27:42 +11:00
wchar_t * result16 = nullptr ;
2024-01-31 23:49:23 +11:00
DWORD size = FormatMessageW ( /*DWORD dwFlags */ flags | FORMAT_MESSAGE_ALLOCATE_BUFFER ,
/*LPCVOID lpSource */ module_to_get_errors_from ,
2025-02-14 00:27:42 +11:00
/*DWORD dwMessageId */ error_code ,
2024-01-31 23:49:23 +11:00
/*DWORD dwLanguageId*/ 0 ,
2025-02-14 00:27:42 +11:00
/*LPWSTR lpBuffer */ ( LPWSTR ) & result16 ,
2024-01-31 23:49:23 +11:00
/*DWORD nSize */ 0 ,
/*va_list *Arguments */ nullptr ) ;
2025-02-14 00:27:42 +11:00
DN_Str16 result = { result16 , size } ;
return result ;
}
DN_API DN_WinError DN_Win_ErrorCodeToMsgAlloc ( uint32_t error_code )
{
DN_WinError result = { } ;
result . code = error_code ;
DN_Str16 error16 = DN_Win_ErrorCodeToMsg16Alloc ( error_code ) ;
if ( error16 . size )
result . msg = DN_Win_Str16ToStr8Alloc ( error16 ) ;
if ( error16 . data )
LocalFree ( error16 . data ) ;
return result ;
}
DN_API DN_WinError DN_Win_ErrorCodeToMsg ( DN_Arena * arena , uint32_t error_code )
{
DN_WinError result = { } ;
result . code = error_code ;
if ( arena ) {
DN_Str16 error16 = DN_Win_ErrorCodeToMsg16Alloc ( error_code ) ;
if ( error16 . size )
result . msg = DN_Win_Str16ToStr8 ( arena , error16 ) ;
if ( error16 . data )
LocalFree ( error16 . data ) ;
}
return result ;
}
DN_API DN_WinError DN_Win_LastError ( DN_Arena * arena )
{
DN_WinError result = DN_Win_ErrorCodeToMsg ( arena , GetLastError ( ) ) ;
2024-01-31 23:49:23 +11:00
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API DN_WinError DN_Win_LastErrorAlloc ( )
2024-01-31 23:49:23 +11:00
{
2025-02-14 00:27:42 +11:00
DN_WinError result = DN_Win_ErrorCodeToMsgAlloc ( GetLastError ( ) ) ;
2024-01-31 23:49:23 +11:00
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API void DN_Win_MakeProcessDPIAware ( )
2024-01-31 23:49:23 +11:00
{
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 ;
2025-02-14 00:27:42 +11:00
if ( auto * set_process_dpi_awareness_context = DN_CAST ( SetProcessDpiAwarenessContextProc * ) GetProcAddress ( DN_CAST ( HMODULE ) lib_handle , " SetProcessDpiAwarenessContext " ) ) {
2024-01-31 23:49:23 +11:00
set_process_dpi_awareness_context ( DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ) ;
2025-02-14 00:27:42 +11:00
} else if ( auto * set_process_dpi_awareness = DN_CAST ( SetProcessDpiAwarenessProc * ) GetProcAddress ( DN_CAST ( HMODULE ) lib_handle , " SetProcessDpiAwareness " ) ) {
2024-01-31 23:49:23 +11:00
set_process_dpi_awareness ( DPI_AWARENESS_PER_MONITOR_AWARE ) ;
2025-02-14 00:27:42 +11:00
} else if ( auto * set_process_dpi_aware = DN_CAST ( SetProcessDpiAwareProc * ) GetProcAddress ( DN_CAST ( HMODULE ) lib_handle , " SetProcessDpiAware " ) ) {
2024-01-31 23:49:23 +11:00
set_process_dpi_aware ( ) ;
}
}
// NOTE: Windows UTF8 to Str16 //////////////////////////////////////////////
2025-02-14 00:27:42 +11:00
DN_API DN_Str16 DN_Win_Str8ToStr16 ( DN_Arena * arena , DN_Str8 src )
2024-01-31 23:49:23 +11:00
{
2025-02-14 00:27:42 +11:00
DN_Str16 result = { } ;
if ( ! arena | | ! DN_Str8_HasData ( src ) )
2024-01-31 23:49:23 +11:00
return result ;
2025-02-14 00:27:42 +11:00
int required_size = MultiByteToWideChar ( CP_UTF8 , 0 /*dwFlags*/ , src . data , DN_CAST ( int ) src . size , nullptr /*dest*/ , 0 /*dest size*/ ) ;
2024-01-31 23:49:23 +11:00
if ( required_size < = 0 )
return result ;
2025-02-14 00:27:42 +11:00
wchar_t * buffer = DN_Arena_NewArray ( arena , wchar_t , required_size + 1 , DN_ZeroMem_No ) ;
2024-01-31 23:49:23 +11:00
if ( ! buffer )
return result ;
2025-02-14 00:27:42 +11:00
int chars_written = MultiByteToWideChar ( CP_UTF8 , 0 /*dwFlags*/ , src . data , DN_CAST ( int ) src . size , buffer , required_size ) ;
if ( DN_CHECK ( chars_written = = required_size ) ) {
2024-01-31 23:49:23 +11:00
result . data = buffer ;
result . size = chars_written ;
result . data [ result . size ] = 0 ;
}
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API int DN_Win_Str8ToStr16Buffer ( DN_Str8 src , wchar_t * dest , int dest_size )
2024-01-31 23:49:23 +11:00
{
int result = 0 ;
2025-02-14 00:27:42 +11:00
if ( ! DN_Str8_HasData ( src ) )
2024-01-31 23:49:23 +11:00
return result ;
2025-02-14 00:27:42 +11:00
result = MultiByteToWideChar ( CP_UTF8 , 0 /*dwFlags*/ , src . data , DN_CAST ( int ) src . size , nullptr /*dest*/ , 0 /*dest size*/ ) ;
2024-01-31 23:49:23 +11:00
if ( result < = 0 | | result > dest_size | | ! dest )
return result ;
2025-02-14 00:27:42 +11:00
result = MultiByteToWideChar ( CP_UTF8 , 0 /*dwFlags*/ , src . data , DN_CAST ( int ) src . size , dest , DN_CAST ( int ) dest_size ) ;
dest [ DN_MIN ( result , dest_size - 1 ) ] = 0 ;
2024-01-31 23:49:23 +11:00
return result ;
}
// NOTE: Windows Str16 To UTF8 //////////////////////////////////////////////////////////////////
2025-02-14 00:27:42 +11:00
DN_API int DN_Win_Str16ToStr8Buffer ( DN_Str16 src , char * dest , int dest_size )
2024-01-31 23:49:23 +11:00
{
int result = 0 ;
2025-02-14 00:27:42 +11:00
if ( ! DN_Str16_HasData ( src ) )
2024-01-31 23:49:23 +11:00
return result ;
2025-02-14 00:27:42 +11:00
int src_size = DN_Safe_SaturateCastISizeToInt ( src . size ) ;
2024-01-31 23:49:23 +11:00
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 ;
2025-02-14 00:27:42 +11:00
result = WideCharToMultiByte ( CP_UTF8 , 0 /*dwFlags*/ , src . data , src_size , dest , DN_CAST ( int ) dest_size , nullptr , nullptr ) ;
dest [ DN_MIN ( result , dest_size - 1 ) ] = 0 ;
return result ;
}
DN_API DN_Str8 DN_Win_Str16ToStr8Alloc ( DN_Str16 src )
{
DN_Str8 result = { } ;
if ( ! DN_Str16_HasData ( src ) )
return result ;
int src_size = DN_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 ;
DN_Str8 buffer = { } ;
buffer . data = DN_CAST ( char * ) DN_OS_MemAlloc ( required_size + 1 , DN_ZeroMem_No ) ;
if ( buffer . data ) {
buffer . size = required_size ;
int chars_written = WideCharToMultiByte ( CP_UTF8 ,
0 /*dwFlags*/ ,
src . data ,
src_size ,
buffer . data ,
DN_CAST ( int ) buffer . size ,
nullptr ,
nullptr ) ;
if ( DN_CHECK ( chars_written = = required_size ) ) {
result = buffer ;
result . data [ result . size ] = 0 ;
}
}
2024-01-31 23:49:23 +11:00
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API DN_Str8 DN_Win_Str16ToStr8 ( DN_Arena * arena , DN_Str16 src )
2024-01-31 23:49:23 +11:00
{
2025-02-14 00:27:42 +11:00
DN_Str8 result = { } ;
if ( ! arena | | ! DN_Str16_HasData ( src ) )
2024-01-31 23:49:23 +11:00
return result ;
2025-02-14 00:27:42 +11:00
int src_size = DN_Safe_SaturateCastISizeToInt ( src . size ) ;
2024-01-31 23:49:23 +11:00
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
2025-02-14 00:27:42 +11:00
DN_ArenaTempMemScope temp_mem = DN_ArenaTempMemScope ( arena ) ;
DN_Str8 buffer = DN_Str8_Alloc ( arena , required_size , DN_ZeroMem_No ) ;
if ( ! DN_Str8_HasData ( buffer ) )
2024-01-31 23:49:23 +11:00
return result ;
2025-02-14 00:27:42 +11:00
int chars_written = WideCharToMultiByte ( CP_UTF8 , 0 /*dwFlags*/ , src . data , src_size , buffer . data , DN_CAST ( int ) buffer . size , nullptr , nullptr ) ;
if ( DN_CHECK ( chars_written = = required_size ) ) {
2024-01-31 23:49:23 +11:00
result = buffer ;
result . data [ result . size ] = 0 ;
temp_mem . mem = { } ;
}
return result ;
}
// NOTE: Windows Executable Directory //////////////////////////////////////////
2025-02-14 00:27:42 +11:00
DN_API DN_Str16 DN_Win_EXEPathW ( DN_Arena * arena )
2024-01-31 23:49:23 +11:00
{
2025-02-14 00:27:42 +11:00
DN_TLSTMem tmem = DN_TLS_TMem ( arena ) ;
DN_Str16 result = { } ;
DN_USize module_size = 0 ;
2024-01-31 23:49:23 +11:00
wchar_t * module_path = nullptr ;
do {
module_size + = 256 ;
2025-02-14 00:27:42 +11:00
module_path = DN_Arena_NewArray ( tmem . arena , wchar_t , module_size , DN_ZeroMem_No ) ;
2024-01-31 23:49:23 +11:00
if ( ! module_path )
return result ;
2025-02-14 00:27:42 +11:00
module_size = DN_CAST ( DN_USize ) GetModuleFileNameW ( nullptr /*module*/ , module_path , DN_CAST ( int ) module_size ) ;
2024-01-31 23:49:23 +11:00
} while ( GetLastError ( ) = = ERROR_INSUFFICIENT_BUFFER ) ;
2025-02-14 00:27:42 +11:00
DN_USize index_of_last_slash = 0 ;
for ( DN_USize index = module_size - 1 ; ! index_of_last_slash & & index < module_size ; index - - )
2024-01-31 23:49:23 +11:00
index_of_last_slash = module_path [ index ] = = ' \\ ' ? index : 0 ;
2025-02-14 00:27:42 +11:00
result . data = DN_Arena_NewArray ( arena , wchar_t , module_size + 1 , DN_ZeroMem_No ) ;
2024-01-31 23:49:23 +11:00
result . size = module_size ;
2025-02-14 00:27:42 +11:00
DN_MEMCPY ( result . data , module_path , sizeof ( wchar_t ) * result . size ) ;
2024-01-31 23:49:23 +11:00
result . data [ result . size ] = 0 ;
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API DN_Str16 DN_Win_EXEDirW ( DN_Arena * arena )
2024-01-31 23:49:23 +11:00
{
2025-02-14 00:27:42 +11:00
// TODO(doyle): Implement a DN_Str16_BinarySearchReverse
DN_TLSTMem tmem = DN_TLS_TMem ( arena ) ;
DN_Str16 result = { } ;
DN_USize module_size = 0 ;
2024-01-31 23:49:23 +11:00
wchar_t * module_path = nullptr ;
do {
module_size + = 256 ;
2025-02-14 00:27:42 +11:00
module_path = DN_Arena_NewArray ( tmem . arena , wchar_t , module_size , DN_ZeroMem_No ) ;
2024-01-31 23:49:23 +11:00
if ( ! module_path )
return result ;
2025-02-14 00:27:42 +11:00
module_size = DN_CAST ( DN_USize ) GetModuleFileNameW ( nullptr /*module*/ , module_path , DN_CAST ( int ) module_size ) ;
2024-01-31 23:49:23 +11:00
} while ( GetLastError ( ) = = ERROR_INSUFFICIENT_BUFFER ) ;
2025-02-14 00:27:42 +11:00
DN_USize index_of_last_slash = 0 ;
for ( DN_USize index = module_size - 1 ; ! index_of_last_slash & & index < module_size ; index - - )
2024-01-31 23:49:23 +11:00
index_of_last_slash = module_path [ index ] = = ' \\ ' ? index : 0 ;
2025-02-14 00:27:42 +11:00
result . data = DN_Arena_NewArray ( arena , wchar_t , index_of_last_slash + 1 , DN_ZeroMem_No ) ;
2024-01-31 23:49:23 +11:00
result . size = index_of_last_slash ;
2025-02-14 00:27:42 +11:00
DN_MEMCPY ( result . data , module_path , sizeof ( wchar_t ) * result . size ) ;
2024-01-31 23:49:23 +11:00
result . data [ result . size ] = 0 ;
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API DN_Str8 DN_Win_WorkingDir ( DN_Arena * arena , DN_Str8 suffix )
2024-01-31 23:49:23 +11:00
{
2025-02-14 00:27:42 +11:00
DN_TLSTMem tmem = DN_TLS_TMem ( arena ) ;
DN_Str16 suffix16 = DN_Win_Str8ToStr16 ( tmem . arena , suffix ) ;
DN_Str16 dir16 = DN_Win_WorkingDirW ( tmem . arena , suffix16 ) ;
DN_Str8 result = DN_Win_Str16ToStr8 ( arena , dir16 ) ;
2024-01-31 23:49:23 +11:00
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API DN_Str16 DN_Win_WorkingDirW ( DN_Arena * arena , DN_Str16 suffix )
2024-01-31 23:49:23 +11:00
{
2025-02-14 00:27:42 +11:00
DN_ASSERT ( suffix . size > = 0 ) ;
DN_Str16 result = { } ;
2024-01-31 23:49:23 +11:00
// NOTE: required_size is the size required *including* the null-terminator
2025-02-14 00:27:42 +11:00
DN_TLSTMem tmem = DN_TLS_TMem ( arena ) ;
2024-01-31 23:49:23 +11:00
unsigned long required_size = GetCurrentDirectoryW ( 0 , nullptr ) ;
2025-02-14 00:27:42 +11:00
unsigned long desired_size = required_size + DN_CAST ( unsigned long ) suffix . size ;
2024-01-31 23:49:23 +11:00
2025-02-14 00:27:42 +11:00
wchar_t * tmem_w_path = DN_Arena_NewArray ( tmem . arena , wchar_t , desired_size , DN_ZeroMem_No ) ;
if ( ! tmem_w_path )
2024-01-31 23:49:23 +11:00
return result ;
2025-02-14 00:27:42 +11:00
unsigned long bytes_written_wo_null_terminator = GetCurrentDirectoryW ( desired_size , tmem_w_path ) ;
2024-01-31 23:49:23 +11:00
if ( ( bytes_written_wo_null_terminator + 1 ) ! = required_size ) {
2025-02-14 00:27:42 +11:00
// TODO(dn): Error
2024-01-31 23:49:23 +11:00
return result ;
}
2025-02-14 00:27:42 +11:00
wchar_t * w_path = DN_Arena_NewArray ( arena , wchar_t , desired_size , DN_ZeroMem_No ) ;
2024-01-31 23:49:23 +11:00
if ( ! w_path )
return result ;
if ( suffix . size ) {
2025-02-14 00:27:42 +11:00
DN_MEMCPY ( w_path , tmem_w_path , sizeof ( * tmem_w_path ) * bytes_written_wo_null_terminator ) ;
DN_MEMCPY ( w_path + bytes_written_wo_null_terminator , suffix . data , sizeof ( suffix . data [ 0 ] ) * suffix . size ) ;
2024-01-31 23:49:23 +11:00
w_path [ desired_size ] = 0 ;
}
2025-02-14 00:27:42 +11:00
result = DN_Str16 { w_path , DN_CAST ( DN_USize ) ( desired_size - 1 ) } ;
2024-01-31 23:49:23 +11:00
return result ;
}
2025-02-14 00:27:42 +11:00
DN_API bool DN_Win_DirWIterate ( DN_Str16 path , DN_Win_FolderIteratorW * it )
2024-01-31 23:49:23 +11:00
{
WIN32_FIND_DATAW find_data = { } ;
if ( it - > handle ) {
2025-02-14 00:27:42 +11:00
if ( FindNextFileW ( it - > handle , & find_data ) = = 0 ) {
FindClose ( it - > handle ) ;
2024-01-31 23:49:23 +11:00
return false ;
2025-02-14 00:27:42 +11:00
}
2024-01-31 23:49:23 +11:00
} 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 ;
2025-02-14 00:27:42 +11:00
it - > file_name = DN_Str16 { it - > file_name_buf , 0 } ;
2024-01-31 23:49:23 +11:00
do {
if ( find_data . cFileName [ 0 ] = = ' . ' | | ( find_data . cFileName [ 0 ] = = ' . ' & & find_data . cFileName [ 1 ] = = ' . ' ) )
continue ;
2025-02-14 00:27:42 +11:00
it - > file_name . size = DN_CStr16_Size ( find_data . cFileName ) ;
DN_ASSERT ( it - > file_name . size < ( DN_ARRAY_UCOUNT ( it - > file_name_buf ) - 1 ) ) ;
DN_MEMCPY ( it - > file_name . data , find_data . cFileName , it - > file_name . size * sizeof ( wchar_t ) ) ;
2024-01-31 23:49:23 +11:00
it - > file_name_buf [ it - > file_name . size ] = 0 ;
break ;
} while ( FindNextFileW ( it - > handle , & find_data ) ! = 0 ) ;
2025-02-14 00:27:42 +11:00
bool result = it - > file_name . size > 0 ;
if ( ! result )
FindClose ( it - > handle ) ;
2024-01-31 23:49:23 +11:00
return result ;
}