2024-01-31 12:49:23 +00:00
////////////////////////////////////////////////////////////////////////////////////////////////////
//
/ / $ $ $ $ $ $ \ $ $ $ $ $ $ \ $ $ $ $ $ $ $ \ $ $ $ $ $ $ \ $ $ $ $ $ $ \ $ $ $ $ $ $ \ $ $ \ $ $ \
// $$ __$$\ $$ __$$\ $$ __$$\ $$ __$$\ $$ __$$\ \_$$ _|$$ | $$ |
// $$ / $$ |$$ / \__| $$ | $$ |$$ / $$ |$$ / \__| $$ | \$$\ $$ |
// $$ | $$ |\$$$$$$\ $$$$$$$ |$$ | $$ |\$$$$$$\ $$ | \$$$$ /
// $$ | $$ | \____$$\ $$ ____/ $$ | $$ | \____$$\ $$ | $$ $$<
/ / $ $ | $ $ | $ $ \ $ $ | $ $ | $ $ | $ $ | $ $ \ $ $ | $ $ | $ $ / \ $ $ \
// $$$$$$ |\$$$$$$ | $$ | $$$$$$ |\$$$$$$ |$$$$$$\ $$ / $$ |
// \______/ \______/ \__| \______/ \______/ \______|\__| \__|
//
// dqn_os_posix.cpp -- Posix implementation of the OS layer
//
////////////////////////////////////////////////////////////////////////////////////////////////////
// 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 | Dqn_OSMemPage_Guard ) ) {
result = PROT_NONE ;
} else {
if ( protect & Dqn_OSMemPage_Read )
result = PROT_READ ;
if ( protect & Dqn_OSMemPage_Write )
result = PROT_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 ) ;
if ( commit = = Dqn_OSMemCommit_Yes )
os_page_flags | = ( PROT_READ | PROT_WRITE ) ;
void * result = mmap ( nullptr , size , os_page_flags , MAP_PRIVATE | MAP_ANONYMOUS , - 1 , 0 ) ;
if ( result = = MAP_FAILED )
result = nullptr ;
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 = mprotect ( ptr , size , os_page_flags ) = = 0 ;
return result ;
}
DQN_API void Dqn_OS_MemDecommit ( void * ptr , Dqn_usize size )
{
mprotect ( ptr , size , PROT_NONE ) ;
madvise ( ptr , size , MADV_FREE ) ;
}
DQN_API void Dqn_OS_MemRelease ( void * ptr , Dqn_usize size )
{
munmap ( ptr , size ) ;
}
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 ) ;
int result = mprotect ( ptr , size , os_page_flags ) ;
DQN_ASSERTF ( result = = 0 , " mprotect failed (%d) " , errno ) ;
return result ;
}
// NOTE: [$DATE] Date //////////////////////////////////////////////////////////////////////////////
DQN_API Dqn_OSDateTime Dqn_OS_DateLocalTimeNow ( )
{
Dqn_OSDateTime result = { } ;
struct timespec ts ;
clock_gettime ( CLOCK_REALTIME , & ts ) ;
// NOTE: localtime_r is used because it is thread safe
// See: https://linux.die.net/man/3/localtime
// According to POSIX.1-2004, localtime() is required to behave as though
// tzset(3) was called, while localtime_r() does not have this requirement.
// For portable code tzset(3) should be called before localtime_r().
for ( static bool once = true ; once ; once = false )
tzset ( ) ;
struct tm time = { } ;
localtime_r ( & ts . tv_sec , & time ) ;
result . hour = time . tm_hour ;
result . minutes = time . tm_min ;
result . seconds = time . tm_sec ;
result . day = DQN_CAST ( uint8_t ) time . tm_mday ;
result . month = DQN_CAST ( uint8_t ) time . tm_mon + 1 ;
result . year = 1900 + DQN_CAST ( int16_t ) time . tm_year ;
return result ;
}
DQN_API uint64_t Dqn_OS_DateUnixTime ( )
{
uint64_t result = time ( nullptr ) ;
return result ;
}
DQN_API bool Dqn_OS_SecureRNGBytes ( void * buffer , uint32_t size )
{
2024-01-31 13:03:06 +00:00
# if defined(DQN_PLATFORM_EMSCRIPTEN)
( void ) buffer ; ( void ) size ;
return false ;
# else
2024-01-31 12:49:23 +00:00
if ( ! buffer | | size < 0 )
return false ;
if ( size = = 0 )
return true ;
DQN_ASSERTF ( size < = 32 ,
" We can increase this by chunking the buffer and filling 32 bytes at a time. *Nix guarantees 32 "
" bytes can always be fulfilled by this system at a time " ) ;
// TODO(doyle): https://github.com/jedisct1/libsodium/blob/master/src/libsodium/randombytes/sysrandom/randombytes_sysrandom.c
// TODO(doyle): https://man7.org/linux/man-pages/man2/getrandom.2.html
uint32_t read_bytes = 0 ;
do {
read_bytes = getrandom ( buffer , size , 0 ) ; // NOTE: EINTR can not be triggered if size <= 32 bytes
} while ( read_bytes ! = size | | errno = = EAGAIN ) ;
return true ;
2024-01-31 13:03:06 +00:00
# endif
2024-01-31 12:49:23 +00:00
}
DQN_API Dqn_Str8 Dqn_OS_EXEPath ( Dqn_Arena * arena )
{
Dqn_Str8 result = { } ;
if ( ! arena )
return result ;
int required_size_wo_null_terminator = 0 ;
for ( int try_size = 128 ; ; try_size * = 2 ) {
auto scoped_arena = Dqn_ArenaTempMemScope ( arena ) ;
char * try_buf = Dqn_Arena_NewArray ( arena , char , try_size , Dqn_ZeroMem_No ) ;
int bytes_written = readlink ( " /proc/self/exe " , try_buf , try_size ) ;
if ( bytes_written = = - 1 ) {
// Failed, we're unable to determine the executable directory
break ;
} else if ( bytes_written = = try_size ) {
// Try again, if returned size was equal- we may of prematurely
// truncated according to the man pages
continue ;
} else {
// readlink will give us the path to the executable. Once we
// determine the correct buffer size required to get the full file
// path, we do some post-processing on said string and extract just
// the directory.
// TODO(dqn): It'd be nice if there's some way of keeping this
// try_buf around, memcopy the byte and trash the try_buf from the
// arena. Instead we just get the size and redo the call one last
// time after this "calculate" step.
DQN_ASSERTF ( bytes_written < try_size , " bytes_written can never be greater than the try size, function writes at most try_size " ) ;
required_size_wo_null_terminator = bytes_written ;
break ;
}
}
if ( required_size_wo_null_terminator ) {
Dqn_ArenaTempMem temp_mem = Dqn_Arena_TempMemBegin ( arena ) ;
char * exe_path = Dqn_Arena_NewArray ( arena , char , required_size_wo_null_terminator + 1 , Dqn_ZeroMem_No ) ;
exe_path [ required_size_wo_null_terminator ] = 0 ;
int bytes_written = readlink ( " /proc/self/exe " , exe_path , required_size_wo_null_terminator ) ;
if ( bytes_written = = - 1 ) {
// Note that if read-link fails again can be because there's
// a potential race condition here, our exe or directory could have
// been deleted since the last call, so we need to be careful.
Dqn_Arena_TempMemEnd ( temp_mem ) ;
} else {
result = Dqn_Str8_Init ( exe_path , required_size_wo_null_terminator ) ;
}
}
return result ;
}
DQN_API uint64_t Dqn_OS_PerfCounterFrequency ( )
{
// NOTE: On Linux we use clock_gettime(CLOCK_MONOTONIC_RAW) which
// increments at nanosecond granularity.
uint64_t result = 1'000'000'000 ;
return result ;
}
DQN_API uint64_t Dqn_OS_PerfCounterNow ( )
{
struct timespec ts ;
clock_gettime ( CLOCK_MONOTONIC_RAW , & ts ) ;
uint64_t result = DQN_CAST ( uint64_t ) ts . tv_sec * 1'000'000'000 + DQN_CAST ( uint64_t ) ts . tv_nsec ;
return result ;
}
# if !defined(DQN_NO_OS_FILE_API)
DQN_API Dqn_OSPathInfo Dqn_OS_PathInfo ( Dqn_Str8 path )
{
Dqn_OSPathInfo result = { } ;
if ( ! Dqn_Str8_HasData ( path ) )
return result ;
struct stat file_stat ;
if ( lstat ( path . data , & file_stat ) ! = - 1 ) {
result . exists = true ;
result . size = file_stat . st_size ;
result . last_access_time_in_s = file_stat . st_atime ;
result . last_write_time_in_s = file_stat . st_mtime ;
// TODO(dqn): Seems linux does not support creation time via stat. We
// shoddily deal with this.
result . create_time_in_s = DQN_MIN ( result . last_access_time_in_s , result . last_write_time_in_s ) ;
}
return result ;
}
2024-02-11 07:23:13 +00:00
DQN_API bool Dqn_OS_PathDelete ( Dqn_Str8 path )
{
bool result = false ;
if ( Dqn_Str8_HasData ( path ) )
result = remove ( path . data ) = = 0 ;
return result ;
}
2024-01-31 12:49:23 +00:00
DQN_API bool Dqn_OS_FileExists ( Dqn_Str8 path )
{
bool result = false ;
if ( ! Dqn_Str8_HasData ( path ) )
return result ;
struct stat stat_result ;
if ( lstat ( path . data , & stat_result ) ! = - 1 )
result = S_ISREG ( stat_result . st_mode ) | | S_ISLNK ( stat_result . st_mode ) ;
return result ;
}
2024-02-11 07:23:13 +00:00
DQN_API bool Dqn_OS_CopyFile ( Dqn_Str8 src , Dqn_Str8 dest , bool overwrite , Dqn_ErrorSink * error )
2024-01-31 12:49:23 +00:00
{
bool result = false ;
# if defined(DQN_PLATFORM_EMSCRIPTEN)
2024-02-11 07:23:13 +00:00
Dqn_ErrorSink_MakeF ( error , 1 , " Unsupported on Emscripten because of their VFS model " ) ;
2024-01-31 12:49:23 +00:00
# else
2024-02-11 07:23:13 +00:00
int src_fd = open ( src . data , O_RDONLY ) ;
if ( src_fd = = - 1 ) {
int error_code = errno ;
Dqn_ErrorSink_MakeF ( error ,
error_code ,
" Failed to open file '%.*s' for copying: (%d) %s " ,
DQN_STR_FMT ( src ) ,
error_code ,
strerror ( error_code ) ) ;
return result ;
2024-01-31 12:49:23 +00:00
}
2024-02-11 07:23:13 +00:00
DQN_DEFER {
2024-01-31 12:49:23 +00:00
close ( src_fd ) ;
2024-02-11 07:23:13 +00:00
} ;
2024-01-31 12:49:23 +00:00
2024-02-11 07:23:13 +00:00
int dest_fd = open ( dest . data , O_WRONLY | O_CREAT | ( overwrite ? O_TRUNC : 0 ) ) ;
if ( dest_fd = = - 1 ) {
int error_code = errno ;
Dqn_ErrorSink_MakeF ( error ,
error_code ,
" Failed to open file destination '%.*s' for copying to: (%d) %s " ,
DQN_STR_FMT ( src ) ,
error_code ,
strerror ( error_code ) ) ;
return result ;
}
DQN_DEFER {
2024-01-31 12:49:23 +00:00
close ( dest_fd ) ;
2024-02-11 07:23:13 +00:00
} ;
struct stat stat_existing ;
int fstat_result = fstat ( src_fd , & stat_existing ) ;
if ( fstat_result = = - 1 ) {
int error_code = errno ;
Dqn_ErrorSink_MakeF ( error ,
error_code ,
" Failed to query file size of '%.*s' for copying: (%d) %s " ,
DQN_STR_FMT ( src ) ,
error_code ,
strerror ( error_code ) ) ;
return result ;
}
ssize_t bytes_written = sendfile64 ( dest_fd , src_fd , 0 , stat_existing . st_size ) ;
result = ( bytes_written = = stat_existing . st_size ) ;
if ( ! result ) {
int error_code = errno ;
Dqn_Scratch scratch = Dqn_Scratch_Get ( nullptr ) ;
Dqn_Str8 file_size_str8 = Dqn_U64ToByteSizeStr8 ( scratch . arena , stat_existing . st_size , Dqn_U64ByteSizeType_Auto ) ;
Dqn_Str8 bytes_written_str8 = Dqn_U64ToByteSizeStr8 ( scratch . arena , bytes_written , Dqn_U64ByteSizeType_Auto ) ;
Dqn_ErrorSink_MakeF ( error ,
error_code ,
" Failed to copy file '%.*s' to '%.*s', we copied %.*s but the file size is %.*s: (%d) %s " ,
DQN_STR_FMT ( src ) ,
DQN_STR_FMT ( dest ) ,
DQN_STR_FMT ( bytes_written_str8 ) ,
DQN_STR_FMT ( file_size_str8 ) ,
error_code ,
strerror ( error_code ) ) ;
}
2024-01-31 12:49:23 +00:00
# endif
return result ;
}
2024-02-11 07:23:13 +00:00
DQN_API bool Dqn_OS_MoveFile ( Dqn_Str8 src , Dqn_Str8 dest , bool overwrite , Dqn_ErrorSink * error )
2024-01-31 12:49:23 +00:00
{
// See: https://github.com/gingerBill/gb/blob/master/gb.h
bool result = false ;
bool file_moved = true ;
if ( link ( src . data , dest . data ) = = - 1 ) {
// NOTE: Link can fail if we're trying to link across different volumes
// so we fall back to a binary directory.
2024-02-11 07:23:13 +00:00
file_moved | = Dqn_OS_CopyFile ( src , dest , overwrite , error ) ;
2024-01-31 12:49:23 +00:00
}
2024-02-11 07:23:13 +00:00
if ( file_moved ) {
int unlink_result = unlink ( src . data ) ;
if ( unlink_result = = - 1 ) {
int error_code = errno ;
Dqn_ErrorSink_MakeF ( error ,
error_code ,
" File '%.*s' was moved but failed to be unlinked from old location: (%d) %s " ,
DQN_STR_FMT ( src ) ,
error_code ,
strerror ( error_code ) ) ;
}
}
2024-01-31 12:49:23 +00:00
return result ;
}
DQN_API bool Dqn_OS_DirExists ( Dqn_Str8 path )
{
bool result = false ;
if ( ! Dqn_Str8_HasData ( path ) )
return result ;
struct stat stat_result ;
if ( lstat ( path . data , & stat_result ) ! = - 1 )
result = S_ISDIR ( stat_result . st_mode ) ;
return result ;
}
2024-02-11 07:23:13 +00:00
DQN_API bool Dqn_OS_MakeDir ( Dqn_Str8 path )
2024-01-31 12:49:23 +00:00
{
Dqn_Scratch scratch = Dqn_Scratch_Get ( nullptr ) ;
bool result = true ;
// TODO(doyle): Implement this without using the path indexes, it's not
// necessary. See Windows implementation.
Dqn_usize path_indexes_size = 0 ;
uint16_t path_indexes [ 64 ] = { } ;
Dqn_Str8 copy = Dqn_Str8_Copy ( scratch . arena , path ) ;
for ( Dqn_usize index = copy . size - 1 ; index < copy . size ; index - - ) {
bool first_char = index = = ( copy . size - 1 ) ;
char ch = copy . data [ index ] ;
if ( ch = = ' / ' | | first_char ) {
char temp = copy . data [ index ] ;
if ( ! first_char )
copy . data [ index ] = 0 ; // Temporarily null terminate it
bool is_file = Dqn_OS_FileExists ( copy ) ;
if ( ! first_char )
copy . data [ index ] = temp ; // Undo null termination
if ( is_file ) {
// NOTE: There's something that exists in at this path, but
// it's not a directory. This request to make a directory is
// invalid.
return false ;
} else {
if ( Dqn_OS_DirExists ( copy ) ) {
// NOTE: We found a directory, we can stop here and start
// building up all the directories that didn't exist up to
// this point.
break ;
} else {
// NOTE: There's nothing that exists at this path, we can
// create a directory here
path_indexes [ path_indexes_size + + ] = DQN_CAST ( uint16_t ) index ;
}
}
}
}
for ( Dqn_usize index = path_indexes_size - 1 ; result & & index < path_indexes_size ; index - - ) {
uint16_t path_index = path_indexes [ index ] ;
char temp = copy . data [ path_index ] ;
if ( index ! = 0 ) copy . data [ path_index ] = 0 ;
result | = mkdir ( copy . data , 0774 ) = = 0 ;
if ( index ! = 0 ) copy . data [ path_index ] = temp ;
}
return result ;
}
// NOTE: R/W Stream API ////////////////////////////////////////////////////////////////////////////
2024-02-11 07:23:13 +00:00
DQN_API Dqn_OSFile Dqn_OS_FileOpen ( Dqn_Str8 path , Dqn_OSFileOpen open_mode , uint32_t access , Dqn_ErrorSink * error )
2024-01-31 12:49:23 +00:00
{
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 ;
}
if ( access & Dqn_OSFileAccess_Execute ) {
2024-02-11 07:23:13 +00:00
result . error = true ;
Dqn_ErrorSink_MakeF ( error , 1 , " Failed to open file '%.*s': File access flag 'execute' is not supported " , DQN_STR_FMT ( path ) ) ;
2024-01-31 12:49:23 +00:00
DQN_INVALID_CODE_PATH ; // TODO: Not supported via fopen
return result ;
}
// NOTE: fopen interface is not as expressive as the Win32
// We will fopen the file beforehand to setup the state/check for validity
2024-02-11 07:23:13 +00:00
// before closing and reopening it with the correct request access
2024-01-31 12:49:23 +00:00
// permissions.
{
FILE * handle = nullptr ;
switch ( open_mode ) {
case Dqn_OSFileOpen_CreateAlways : handle = fopen ( path . data , " w " ) ; break ;
case Dqn_OSFileOpen_OpenIfExist : handle = fopen ( path . data , " r " ) ; break ;
case Dqn_OSFileOpen_OpenAlways : handle = fopen ( path . data , " a " ) ; break ;
default : DQN_INVALID_CODE_PATH ; break ;
}
2024-02-11 07:23:13 +00:00
if ( ! handle ) { // TODO(doyle): FileOpen flag to string
result . error = true ;
Dqn_ErrorSink_MakeF ( error , 1 , " Failed to open file '%.*s': File could not be opened in requested mode 'Dqn_OSFileOpen' flag %d " , DQN_STR_FMT ( path ) , open_mode ) ;
2024-01-31 12:49:23 +00:00
return result ;
}
fclose ( handle ) ;
}
char const * fopen_mode = nullptr ;
if ( access & Dqn_OSFileAccess_AppendOnly ) {
fopen_mode = " a+ " ;
} else if ( access & Dqn_OSFileAccess_Write ) {
fopen_mode = " w+ " ;
} else if ( access & Dqn_OSFileAccess_Read ) {
fopen_mode = " r+ " ;
}
FILE * handle = fopen ( path . data , fopen_mode ) ;
if ( ! handle ) {
2024-02-11 07:23:13 +00:00
result . error = true ;
Dqn_ErrorSink_MakeF ( error , 1 , " Failed to open file '%.*s': File could not be opened with requested access mode 'Dqn_OSFileAccess' %d " , DQN_STR_FMT ( path ) , fopen_mode ) ;
2024-01-31 12:49:23 +00:00
return result ;
}
result . handle = handle ;
return result ;
}
2024-02-11 07:23:13 +00:00
DQN_API bool Dqn_OS_FileRead ( Dqn_OSFile * file , void * buffer , Dqn_usize size , Dqn_ErrorSink * error )
2024-01-31 12:49:23 +00:00
{
2024-02-11 07:23:13 +00:00
if ( ! file | | ! file - > handle | | file - > error | | ! buffer | | size < = 0 )
return false ;
if ( fread ( buffer , size , 1 , DQN_CAST ( FILE * ) file - > handle ) ! = 1 ) {
Dqn_Scratch scratch = Dqn_Scratch_Get ( nullptr ) ;
Dqn_Str8 buffer_size_str8 = Dqn_U64ToByteSizeStr8 ( scratch . arena , size , Dqn_U64ByteSizeType_Auto ) ;
Dqn_ErrorSink_MakeF ( error , 1 , " Failed to read %.*s from file " , DQN_STR_FMT ( buffer_size_str8 ) ) ;
return false ;
}
return true ;
}
DQN_API bool Dqn_OS_FileWritePtr ( Dqn_OSFile * file , void const * buffer , Dqn_usize size , Dqn_ErrorSink * error )
{
if ( ! file | | ! file - > handle | | file - > error | | ! buffer | | size < = 0 )
2024-01-31 12:49:23 +00:00
return false ;
bool result = fwrite ( buffer , DQN_CAST ( Dqn_usize ) size , 1 /*count*/ , DQN_CAST ( FILE * ) file - > handle ) = = 1 /*count*/ ;
2024-02-11 07:23:13 +00:00
if ( ! result ) {
Dqn_Scratch scratch = Dqn_Scratch_Get ( nullptr ) ;
Dqn_Str8 buffer_size_str8 = Dqn_U64ToByteSizeStr8 ( scratch . arena , size , Dqn_U64ByteSizeType_Auto ) ;
Dqn_ErrorSink_MakeF ( error , 1 , " Failed to write buffer (%s) to file handle " , DQN_STR_FMT ( buffer_size_str8 ) ) ;
}
2024-01-31 12:49:23 +00:00
return result ;
}
2024-02-11 07:23:13 +00:00
DQN_API void Dqn_OS_FileClose ( Dqn_OSFile * file )
2024-01-31 12:49:23 +00:00
{
2024-02-11 07:23:13 +00:00
if ( ! file | | ! file - > handle | | file - > error )
2024-01-31 12:49:23 +00:00
return ;
fclose ( DQN_CAST ( FILE * ) file - > handle ) ;
* file = { } ;
}
# endif // !defined(DQN_NO_OS_FILE_API)
// NOTE: [$EXEC] Dqn_OSExec ////////////////////////////////////////////////////////////////////////
DQN_API void Dqn_OS_Exit ( uint32_t exit_code )
{
exit ( 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 ;
}
# if defined(DQN_PLATFORM_EMSCRIPTEN)
DQN_ASSERTF ( false , " Unsupported operation " ) ;
# else
static_assert ( sizeof ( pid_t ) < = sizeof ( handle . process ) , " We store the PID opaquely in a register sized pointer " ) ;
pid_t process = { } ;
DQN_MEMCPY ( & process , & handle . process , sizeof ( process ) ) ;
for ( ; ; ) {
int status = 0 ;
if ( waitpid ( process , & status , 0 ) < 0 ) {
result . os_error_code = errno ;
break ;
}
if ( WIFEXITED ( status ) ) {
result . exit_code = WEXITSTATUS ( status ) ;
break ;
}
if ( WIFSIGNALED ( status ) ) {
result . os_error_code = WTERMSIG ( status ) ;
break ;
}
}
# endif
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 ) ;
// TODO: This API will need to switch to an array of strings for unix
pid_t child_pid = fork ( ) ;
if ( child_pid < 0 ) {
result . os_error_code = errno ;
return result ;
}
if ( child_pid = = 0 ) {
// NOTE: Convert the command into something suitable for execvp
char * * argv = Dqn_Arena_NewArray ( scratch . arena , char * , cmd_line . size + 1 /*null*/ , Dqn_ZeroMem_Yes ) ;
if ( ! argv ) {
result . exit_code = - 1 ;
return result ;
}
for ( Dqn_usize arg_index = 0 ; arg_index < cmd_line . size ; arg_index + + ) {
Dqn_Str8 arg = cmd_line . data [ arg_index ] ;
argv [ arg_index ] = Dqn_Str8_Copy ( scratch . arena , arg ) . data ; // NOTE: Copy string to guarantee it is null-terminated
}
// NOTE: Change the working directory if there is one
char * prev_working_dir = nullptr ;
DQN_DEFER {
if ( ! prev_working_dir )
return ;
if ( result . os_error_code = = 0 )
chdir ( prev_working_dir ) ;
free ( prev_working_dir ) ;
} ;
if ( working_dir . size ) {
prev_working_dir = get_current_dir_name ( ) ;
if ( chdir ( working_dir . data ) = = - 1 ) {
result . os_error_code = errno ;
return result ;
}
}
// NOTE: Execute the command. We reuse argv because the first arg, the
// binary to execute is guaranteed to be null-terminated.
if ( execvp ( argv [ 0 ] , argv ) < 0 ) {
result . os_error_code = errno ;
return result ;
}
}
DQN_MEMCPY ( & result . process , & child_pid , sizeof ( child_pid ) ) ;
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 = { } ;
int pshared = 0 ; // Share the semaphore across all threads in the process
if ( sem_init ( & result . posix_handle , pshared , initial_count ) = = 0 )
result . posix_init = true ;
return result ;
}
DQN_API bool Dqn_OS_SemaphoreIsValid ( Dqn_OSSemaphore * semaphore )
{
bool result = false ;
if ( semaphore ) {
result = semaphore - > posix_init ;
}
return result ;
}
DQN_API void Dqn_OS_SemaphoreDeinit ( Dqn_OSSemaphore * semaphore )
{
if ( ! Dqn_OS_SemaphoreIsValid ( semaphore ) )
return ;
// TODO(doyle): Error handling?
if ( semaphore - > posix_init )
sem_destroy ( & semaphore - > posix_handle ) ;
* semaphore = { } ;
}
// NOTE: These functions don't need semaphore to be passed by pointer, **BUT**
// the POSIX implementation disallows copies of sem_t. In particular:
//
// Source: The Open Group Base Specifications Issue 7, 2018 edition
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_09_09
//
// 2.9.9 Synchronization Object Copies and Alternative Mappings
//
// For barriers, condition variables, mutexes, and read-write locks, [TSH]
// [Option Start] if the process-shared attribute is set to
// PTHREAD_PROCESS_PRIVATE, [Option End] only the synchronization object at the
// address used to initialize it can be used for performing synchronization. The
// effect of referring to another mapping of the same object when locking,
// unlocking, or destroying the object is undefined. [...] The effect of
// referring to a copy of the object when locking, unlocking, or destroying it
// is undefined.
DQN_API void Dqn_OS_SemaphoreIncrement ( Dqn_OSSemaphore * semaphore , uint32_t amount )
{
if ( ! Dqn_OS_SemaphoreIsValid ( semaphore ) )
return ;
# if defined(DQN_OS_WIN32)
sem_post_multiple ( & semaphore - > posix_handle , amount ) ; // mingw extension
# else
DQN_FOR_UINDEX ( index , amount )
sem_post ( & semaphore - > posix_handle ) ;
# endif // !defined(DQN_OS_WIN32)
}
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 ( timeout_ms = = DQN_OS_SEMAPHORE_INFINITE_TIMEOUT ) {
int wait_result = 0 ;
do {
wait_result = sem_wait ( & semaphore - > posix_handle ) ;
} while ( wait_result = = - 1 & & errno = = EINTR ) ;
if ( wait_result = = 0 )
result = Dqn_OSSemaphoreWaitResult_Success ;
} else {
struct timespec abs_timeout = { } ;
abs_timeout . tv_sec = timeout_ms / 1000 ;
abs_timeout . tv_nsec = ( timeout_ms % 1000 ) * 1'000'000 ;
if ( sem_timedwait ( & semaphore - > posix_handle , & abs_timeout ) = = 0 ) {
result = Dqn_OSSemaphoreWaitResult_Success ;
} else {
if ( errno = = ETIMEDOUT )
result = Dqn_OSSemaphoreWaitResult_Timeout ;
}
}
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 = { } ;
if ( pthread_mutexattr_init ( & result . posix_attribs ) ! = 0 )
return result ;
if ( pthread_mutex_init ( & result . posix_handle , & result . posix_attribs ) ! = 0 )
return result ;
return result ;
}
DQN_API void Dqn_OS_MutexDeinit ( Dqn_OSMutex * mutex )
{
if ( ! mutex )
return ;
pthread_mutexattr_destroy ( & mutex - > posix_attribs ) ;
pthread_mutex_destroy ( & mutex - > posix_handle ) ;
}
DQN_API void Dqn_OS_MutexLock ( Dqn_OSMutex * mutex )
{
if ( ! mutex )
return ;
pthread_mutex_lock ( & mutex - > posix_handle ) ;
}
DQN_API void Dqn_OS_MutexUnlock ( Dqn_OSMutex * mutex )
{
if ( ! mutex )
return ;
pthread_mutex_unlock ( & mutex - > posix_handle ) ;
}
// NOTE: [$THRD] Dqn_OSThread /////////////////////////////////////////////////////////////////////
static void * 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 nullptr ;
}
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
// NOTE: pthread_t is essentially the thread ID. In Windows, the handle and
// the ID are different things. For pthreads then we just duplicate the
// thread ID to both variables
pthread_t p_thread = { } ;
static_assert ( sizeof ( p_thread ) < = sizeof ( thread - > handle ) ,
" We store the thread handle opaquely in our abstraction, "
" there must be enough bytes to store pthread's structure " ) ;
static_assert ( sizeof ( p_thread ) < = sizeof ( thread - > thread_id ) ,
" We store the thread handle opaquely in our abstraction, "
" there must be enough bytes to store pthread's structure " ) ;
pthread_attr_t attribs = { } ;
pthread_attr_init ( & attribs ) ;
result = pthread_create ( & p_thread , & attribs , Dqn_OS_ThreadFunc_ , thread ) = = 0 ;
pthread_attr_destroy ( & attribs ) ;
if ( result ) {
DQN_MEMCPY ( & thread - > handle , & p_thread , sizeof ( p_thread ) ) ;
DQN_MEMCPY ( & thread - > thread_id , & p_thread , sizeof ( p_thread ) ) ;
}
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 ;
pthread_t thread_id = { } ;
DQN_MEMCPY ( & thread_id , & thread - > thread_id , sizeof ( thread_id ) ) ;
void * return_val = nullptr ;
pthread_join ( thread_id , & return_val ) ;
thread - > handle = { } ;
thread - > thread_id = { } ;
}
DQN_API uint32_t Dqn_OS_ThreadID ( )
{
pid_t result = gettid ( ) ;
DQN_ASSERT ( gettid ( ) > = 0 ) ;
return DQN_CAST ( uint32_t ) result ;
}
# endif // !defined(DQN_NO_THREAD)
// NOTE: [$HTTP] Dqn_OSHttp ////////////////////////////////////////////////////////////////////////
#if 0 // TODO(doyle): Implement websockets for Windows and Emscripten
static EM_BOOL EMWebSocketOnOpenCallback ( int type , const EmscriptenWebSocketOpenEvent * event , void * user_context )
{
( void ) user_context ;
( void ) type ;
( void ) event ;
// EMSCRIPTEN_RESULT result = emscripten_websocket_send_utf8_text(event->socket, R"({"jsonrpc":"2.0","id":1,"method": "eth_subscribe","params":["newHeads"]})");
// if (result)
// Dqn_Log_InfoF("Failed to emscripten_websocket_send_utf8_text(): %d\n", result);
return EM_TRUE ;
}
static EM_BOOL EMWebSocketOnMsgCallback ( int type , const EmscriptenWebSocketMessageEvent * event __attribute__ ( ( nonnull ) ) , void * user_context )
{
( void ) type ;
( void ) user_context ;
( void ) event ;
if ( event - > isText ) {
Dqn_Log_InfoF ( " Received: %.*s " , event - > numBytes , event - > data ) ;
} else {
Dqn_Log_InfoF ( " Received: %d bytes " , event - > numBytes ) ;
}
return EM_TRUE ;
}
static EM_BOOL EMWebSocketOnErrorCallback ( int type , const EmscriptenWebSocketErrorEvent * event , void * user_context )
{
( void ) user_context ;
( void ) type ;
( void ) event ;
return EM_TRUE ;
}
static EM_BOOL EMWebSocketOnCloseCallback ( int type , const EmscriptenWebSocketCloseEvent * event , void * user_context )
{
( void ) user_context ;
( void ) type ;
( void ) event ;
return EM_TRUE ;
}
# endif
# if defined(DQN_PLATFORM_EMSCRIPTEN)
static void Dqn_OS_HttpRequestEMFetchOnSuccessCallback ( emscripten_fetch_t * fetch )
{
Dqn_OSHttpResponse * response = DQN_CAST ( Dqn_OSHttpResponse * ) fetch - > userData ;
if ( ! DQN_CHECK ( response ) )
return ;
response - > http_status = DQN_CAST ( uint32_t ) fetch - > status ;
response - > body = Dqn_Str8_Alloc ( response - > arena , fetch - > numBytes , Dqn_ZeroMem_No ) ;
if ( response - > body . data )
DQN_MEMCPY ( response - > body . data , fetch - > data , fetch - > numBytes ) ;
Dqn_OS_SemaphoreIncrement ( & response - > on_complete_semaphore , 1 ) ;
Dqn_Atomic_AddU32 ( & response - > done , 1 ) ;
}
static void Dqn_OS_HttpRequestEMFetchOnErrorCallback ( emscripten_fetch_t * fetch )
{
Dqn_OSHttpResponse * response = DQN_CAST ( Dqn_OSHttpResponse * ) fetch - > userData ;
if ( ! DQN_CHECK ( response ) )
return ;
response - > http_status = DQN_CAST ( uint32_t ) fetch - > status ;
response - > body = Dqn_Str8_Alloc ( response - > arena , fetch - > numBytes , Dqn_ZeroMem_No ) ;
if ( response - > body . size )
DQN_MEMCPY ( response - > body . data , fetch - > data , fetch - > numBytes ) ;
Dqn_OS_SemaphoreIncrement ( & response - > on_complete_semaphore , 1 ) ;
Dqn_Atomic_AddU32 ( & response - > done , 1 ) ;
}
# endif
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 ;
# if defined(DQN_PLATFORM_EMSCRIPTEN)
emscripten_fetch_attr_t fetch_attribs = { } ;
emscripten_fetch_attr_init ( & fetch_attribs ) ;
if ( method . size > = sizeof ( fetch_attribs . requestMethod ) ) {
response - > error_msg = Dqn_Str8_InitF ( arena ,
" Request method in EM has a size limit of 31 characters, method was '%.*s' which is %zu characters long " ,
DQN_STR_FMT ( method ) ,
method . size ) ;
DQN_CHECKF ( method . size < sizeof ( fetch_attribs . requestMethod ) , " %.*s " , DQN_STR_FMT ( response - > error_msg ) ) ;
response - > error_code = DQN_CAST ( uint32_t ) - 1 ;
Dqn_Atomic_AddU32 ( & response - > done , 1 ) ;
return ;
}
DQN_MEMCPY ( fetch_attribs . requestMethod , method . data , method . size ) ;
fetch_attribs . requestData = body . data ;
fetch_attribs . requestDataSize = body . size ;
fetch_attribs . attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY ;
fetch_attribs . onsuccess = Dqn_OS_HttpRequestEMFetchOnSuccessCallback ;
fetch_attribs . onerror = Dqn_OS_HttpRequestEMFetchOnErrorCallback ;
fetch_attribs . userData = response ;
Dqn_Str8 url = Dqn_Str8_InitF ( scratch_arena , " %.*s%.*s " , DQN_STR_FMT ( host ) , DQN_STR_FMT ( path ) ) ;
Dqn_Log_InfoF ( " Initiating HTTP '%s' request to '%.*s' with payload '%.*s' " , fetch_attribs . requestMethod , DQN_STR_FMT ( url ) , DQN_STR_FMT ( body ) ) ;
response - > on_complete_semaphore = Dqn_OS_SemaphoreInit ( 0 ) ;
response - > em_handle = emscripten_fetch ( & fetch_attribs , url . data ) ;
# else // #elif defined(DQN_OS_WIN32)
DQN_INVALID_CODE_PATHF ( " Unimplemented function " ) ;
# endif
}
DQN_API void Dqn_OS_HttpRequestFree ( Dqn_OSHttpResponse * response )
{
// NOTE: Cleanup
# if defined(DQN_PLATFORM_EMSCRIPTEN)
if ( response - > em_handle ) {
emscripten_fetch_close ( response - > em_handle ) ;
response - > em_handle = nullptr ;
}
# endif // #elif defined(DQN_OS_WIN32)
Dqn_Arena_Deinit ( & response - > tmp_arena ) ;
if ( Dqn_OS_SemaphoreIsValid ( & response - > on_complete_semaphore ) )
Dqn_OS_SemaphoreDeinit ( & response - > on_complete_semaphore ) ;
* response = { } ;
}