#ifdef INTELLISENSE_DIRECTIVES # include "os_linux.h" # include "os/os.h" #endif // Copyright (c) 2024 Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) //////////////////////////////// //~ rjf: Globals MD_API_C global OS_LNX_State os_lnx_state = {0}; MD_API_C thread_static OS_LNX_SafeCallChain* os_lnx_safe_call_chain = 0; //////////////////////////////// //~ rjf: Entities OS_LNX_Entity* os_lnx_entity_alloc(OS_LNX_EntityKind kind) { OS_LNX_Entity* entity = 0; defer_loop(pthread_mutex_lock (&os_lnx_state.entity_mutex), pthread_mutex_unlock(&os_lnx_state.entity_mutex)) { entity = os_lnx_state.entity_free; if (entity) { sll_stack_pop(os_lnx_state.entity_free); } else { entity = push_array_no_zero(os_lnx_state.entity_arena, OS_LNX_Entity, 1); } } memory_zero_struct(entity); entity->kind = kind; return entity; } void os_lnx_entity_release(OS_LNX_Entity *entity) { defer_loop(pthread_mutex_lock(&os_lnx_state.entity_mutex), pthread_mutex_unlock(&os_lnx_state.entity_mutex)) { sll_stack_push(os_lnx_state.entity_free, entity); } } //////////////////////////////// //~ rjf: Thread Entry Point void* os_lnx_thread_entry_point(void *ptr) { OS_LNX_Entity* entity = (OS_LNX_Entity *)ptr; OS_ThreadFunctionType* func = entity->thread.func; void* thread_ptr = entity->thread.ptr; TCTX tctx_; tctx_init_and_equip(&tctx_); func(thread_ptr); tctx_release(); return 0; } //////////////////////////////// //~ rjf: @os_hooks System/Process Info (Implemented Per-OS) OS_SystemInfo* os_get_system_info(void) { return &os_lnx_state.system_info; } OS_ProcessInfo* os_get_process_info(void) { return &os_lnx_state.process_info; } //////////////////////////////// //~ rjf: @os_hooks Thread Info (Implemented Per-OS) void os_set_thread_name(String8 name) { TempArena scratch = scratch_begin(0, 0); { String8 name_copy = push_str8_copy(scratch.arena, name); pthread_t current_thread = pthread_self(); pthread_setname_np(current_thread, (char *)name_copy.str); } scratch_end(scratch); } //////////////////////////////// //~ rjf: @os_hooks Aborting (Implemented Per-OS) void os_abort(S32 exit_code) { exit(exit_code); } //////////////////////////////// //~ rjf: @os_hooks File System (Implemented Per-OS) //- rjf: files OS_Handle os_file_open(OS_AccessFlags flags, String8 path) { TempArena scratch = scratch_begin(0, 0); { String8 path_copy = push_str8_copy(scratch.arena, path); int lnx_flags = 0; if (flags & (OS_AccessFlag_Read | OS_AccessFlag_Write)) { lnx_flags = O_RDWR; } else if (flags & OS_AccessFlag_Write) { lnx_flags = O_WRONLY; } else if (flags & OS_AccessFlag_Read) { lnx_flags = O_RDONLY; } if (flags & OS_AccessFlag_Append) { lnx_flags |= O_APPEND; } int fd = open((char *)path_copy.str, lnx_flags); OS_Handle handle = {0}; if (fd != -1) { handle.u64[0] = fd; } } scratch_end(scratch); return handle; } void os_file_close(OS_Handle file) { if (os_handle_match(file, os_handle_zero())) { return; } int fd = (int)file.u64[0]; close(fd); } U64 os_file_read(OS_Handle file, Rng1U64 rng, void* out_data) { if (os_handle_match(file, os_handle_zero())) { return 0; } int fd = (int)file.u64[0]; if (rng.min != 0) { lseek(fd, rng.min, SEEK_SET); } U64 total_num_bytes_to_read = dim_1u64(rng); U64 total_num_bytes_read = 0; U64 total_num_bytes_left_to_read = total_num_bytes_to_read; for (;total_num_bytes_left_to_read > 0;) { int read_result = read(fd, (U8 *)out_data + total_num_bytes_read, total_num_bytes_left_to_read); if (read_result >= 0) { total_num_bytes_read += read_result; total_num_bytes_left_to_read -= read_result; } else if (errno != EINTR) { break; } } return total_num_bytes_read; } U64 os_file_write(OS_Handle file, Rng1U64 rng, void *data) { if(os_handle_match(file, os_handle_zero())) { return 0; } int fd = (int)file.u64[0]; if (rng.min != 0) { lseek(fd, rng.min, SEEK_SET); } U64 total_num_bytes_to_write = dim_1u64(rng); U64 total_num_bytes_written = 0; U64 total_num_bytes_left_to_write = total_num_bytes_to_write; for (;total_num_bytes_left_to_write > 0;) { int write_result = write(fd, (U8*)data + total_num_bytes_written, total_num_bytes_left_to_write); if (write_result >= 0) { total_num_bytes_written += write_result; total_num_bytes_left_to_write -= write_result; } else if (errno != EINTR) { break; } } return total_num_bytes_written; } B32 os_file_set_times(OS_Handle file, DateTime date_time) { if(os_handle_match(file, os_handle_zero())) { return 0; } int fd = (int)file.u64[0]; timespec time = os_lnx_timespec_from_date_time(date_time); timespec times[2] = {time, time}; int futimens_result = futimens(fd, times); B32 good = (futimens_result != -1); return good; } FileProperties os_properties_from_file(OS_Handle file) { if(os_handle_match(file, os_handle_zero())) { return (FileProperties){0}; } int fd = (int)file.u64[0]; struct stat fd_stat = {0}; int fstat_result = fstat(fd, &fd_stat); FileProperties props = {0}; if (fstat_result != -1) { props = os_lnx_file_properties_from_stat(&fd_stat); } return props; } OS_FileID os_id_from_file(OS_Handle file) { if (os_handle_match(file, os_handle_zero())) { return (OS_FileID){0}; } int fd = (int)file.u64[0]; struct stat fd_stat = {0}; int fstat_result = fstat(fd, &fd_stat); OS_FileID id = {0}; if(fstat_result != -1) { id.v[0] = fd_stat.st_dev; id.v[1] = fd_stat.st_ino; } return id; } B32 os_delete_file_at_path(String8 path) { TempArena scratch = scratch_begin(0, 0); B32 result = 0; String8 path_copy = push_str8_copy(scratch.arena, path); if (remove((char*)path_copy.str) != -1) { result = 1; } scratch_end(scratch); return result; } B32 os_copy_file_path(String8 dst, String8 src) { B32 result = 0; OS_Handle src_h = os_file_open(OS_AccessFlag_Read, src); OS_Handle dst_h = os_file_open(OS_AccessFlag_Write, dst); if ( !os_handle_match(src_h, os_handle_zero()) && !os_handle_match(dst_h, os_handle_zero()) ) { FileProperties src_props = os_properties_from_file(src_h); U64 size = src_props.size; U64 total_bytes_copied = 0; U64 bytes_left_to_copy = size; for (;bytes_left_to_copy > 0;) { TempArena scratch = scratch_begin(0, 0); U64 buffer_size = Min(bytes_left_to_copy, MB(8)); U8 *buffer = push_array_no_zero(scratch.arena, U8, buffer_size); U64 bytes_read = os_file_read (src_h, r1u64(total_bytes_copied, total_bytes_copied+buffer_size), buffer); U64 bytes_written = os_file_write(dst_h, r1u64(total_bytes_copied, total_bytes_copied+bytes_read), buffer); U64 bytes_copied = Min(bytes_read, bytes_written); bytes_left_to_copy -= bytes_copied; total_bytes_copied += bytes_copied; scratch_end(scratch); if(bytes_copied == 0) { break; } } } os_file_close(src_h); os_file_close(dst_h); return result; } String8 os_full_path_from_path(Arena* arena, String8 path) { char buffer[PATH_MAX] = {0}; TempArena scratch = scratch_begin(&arena, 1); { String8 path_copy = push_str8_copy(scratch.arena, path); realpath((char *)path_copy.str, buffer); } scratch_end(scratch); String8 result = push_str8_copy(arena, str8_cstring(buffer)); return result; } String8 os_full_path_from_path_alloc(AllocatorInfo ainfo, String8 path) { char buffer[PATH_MAX] = {0}; TempArena scratch = scratch_begin(0, 0); { String8 path_copy = push_str8_copy(scratch.arena, path); realpath((char *)path_copy.str, buffer); } scratch_end(scratch); String8 result = alloc_str8_copy(ainfo, str8_cstring(buffer)); return result; } B32 os_file_path_exists(String8 path) { B32 result = 0; TempArena scratch = scratch_begin(0, 0); String8 path_copy = push_str8_copy(scratch.arena, path); int access_result = access((char *)path_copy.str, F_OK); if (access_result == 0) { result = 1; } scratch_end(scratch); return result; } internal FileProperties os_properties_from_file_path(String8 path) { FileProperties props = {0}; TempArena scratch = scratch_begin(0, 0); String8 path_copy = push_str8_copy(scratch.arena, path); struct stat f_stat = {0}; int stat_result = stat((char *)path_copy.str, &f_stat); if (stat_result != -1) { props = os_lnx_file_properties_from_stat(&f_stat); } scratch_end(scratch); return props; } //- rjf: file maps OS_Handle os_file_map_open(OS_AccessFlags flags, OS_Handle file) { OS_Handle map = file; return map; } void os_file_map_close(OS_Handle map) { // NOTE(rjf): nothing to do; `map` handles are the same as `file` handles in // the linux implementation (on Windows they require separate handles) } void* os_file_map_view_open(OS_Handle map, OS_AccessFlags flags, Rng1U64 range) { if (os_handle_match(map, os_handle_zero())) { return 0; } int prot_flags = 0; if (flags & OS_AccessFlag_Write) { prot_flags |= PROT_WRITE; } if (flags & OS_AccessFlag_Read) { prot_flags |= PROT_READ; } int fd = (int)map.u64[0]; int map_flags = MAP_PRIVATE; void* base = mmap(0, dim_1u64(range), prot_flags, map_flags, fd, range.min); return base; } void os_file_map_view_close(OS_Handle map, void* ptr, Rng1U64 range) { munmap(ptr, dim_1u64(range)); } //- rjf: directory iteration OS_FileIter* os_file_iter_begin(Arena* arena, String8 path, OS_FileIterFlags flags) { OS_FileIter* base_iter = push_array(arena, OS_FileIter, 1); base_iter->flags = flags; OS_LNX_FileIter* iter = (OS_LNX_FileIter*)base_iter->memory; { String8 path_copy = push_str8_copy(arena, path); iter->dir = opendir((char*)path_copy.str); iter->path = path_copy; } return base_iter; } B32 os_file_iter_next(Arena* arena, OS_FileIter* iter, OS_FileInfo* info_out) { B32 good = 0; OS_LNX_FileIter* lnx_iter = (OS_LNX_FileIter*)iter->memory; for(;;) { // rjf: get next entry lnx_iter->dp = readdir(lnx_iter->dir); good = (lnx_iter->dp != 0); // rjf: unpack entry info struct stat st = {0}; int stat_result = 0; if(good) { TempArena scratch = scratch_begin(&arena, 1); String8 full_path = push_str8f(scratch.arena, "%S/%s", lnx_iter->path, lnx_iter->dp->d_name); stat_result = stat((char *)full_path.str, &st); scratch_end(scratch); } // rjf: determine if filtered B32 filtered = 0; if(good) { filtered = ((st.st_mode == S_IFDIR && iter->flags & OS_FileIterFlag_SkipFolders) || (st.st_mode == S_IFREG && iter->flags & OS_FileIterFlag_SkipFiles) || (lnx_iter->dp->d_name[0] == '.' && lnx_iter->dp->d_name[1] == 0) || (lnx_iter->dp->d_name[0] == '.' && lnx_iter->dp->d_name[1] == '.' && lnx_iter->dp->d_name[2] == 0)); } // rjf: output & exit, if good & unfiltered if (good && !filtered) { info_out->name = push_str8_copy(arena, str8_cstring(lnx_iter->dp->d_name)); if (stat_result != -1) { info_out->props = os_lnx_file_properties_from_stat(&st); } break; } // rjf: exit if not good if (!good) { break; } } return good; } void os_file_iter_end(OS_FileIter* iter) { OS_LNX_FileIter* lnx_iter = (OS_LNX_FileIter*)iter->memory; closedir(lnx_iter->dir); } //- rjf: directory creation B32 os_make_directory(String8 path) { B32 result = 0; TempArena scratch = scratch_begin(0, 0); String8 path_copy = push_str8_copy(scratch.arena, path); if (mkdir((char*)path_copy.str, 0777) != -1) { result = 1; } scratch_end(scratch); return result; } //////////////////////////////// //~ rjf: @os_hooks Shared Memory (Implemented Per-OS) OS_Handle os_shared_memory_alloc(U64 size, String8 name) { TempArena scratch = scratch_begin(0, 0); String8 name_copy = push_str8_copy(scratch.arena, name); int id = shm_open((char *)name_copy.str, O_RDWR, 0); ftruncate(id, size); scratch_end(scratch); OS_Handle result = {(U64)id}; return result; } OS_Handle os_shared_memory_open(String8 name) { TempArena scratch = scratch_begin(0, 0); String8 name_copy = push_str8_copy(scratch.arena, name); int id = shm_open((char *)name_copy.str, O_RDWR, 0); scratch_end(scratch); OS_Handle result = {(U64)id}; return result; } void os_shared_memory_close(OS_Handle handle) { if (os_handle_match(handle, os_handle_zero())) { return; } int id = (int)handle.u64[0]; close(id); } void* os_shared_memory_view_open(OS_Handle handle, Rng1U64 range) { if (os_handle_match(handle, os_handle_zero())) { return 0; } int id = (int)handle.u64[0]; void* base = mmap(0, dim_1u64(range), PROT_READ|PROT_WRITE, MAP_SHARED, id, range.min); return base; } void os_shared_memory_view_close(OS_Handle handle, void* ptr, Rng1U64 range) { if (os_handle_match(handle, os_handle_zero())) { return; } munmap(ptr, dim_1u64(range)); } //////////////////////////////// //~ rjf: @os_hooks Time (Implemented Per-OS) U64 os_now_microseconds(void) { struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); U64 result = t.tv_sec * million(1) + (t.tv_nsec / thousand(1)); return result; } U32 os_now_unix(void) { time_t t = time(0); return (U32)t; } DateTime os_now_universal_time(void) { time_t t = 0; time(&t); struct tm universal_tm = {0}; gmtime_r(&t, &universal_tm); DateTime result = os_lnx_date_time_from_tm(universal_tm, 0); return result; } DateTime os_universal_time_from_local(DateTime* date_time) { // rjf: local DateTime -> universal time_t tm local_tm = os_lnx_tm_from_date_time(*date_time); local_tm.tm_isdst = -1; time_t universal_t = mktime(&local_tm); // rjf: universal time_t -> DateTime tm universal_tm = {0}; gmtime_r(&universal_t, &universal_tm); DateTime result = os_lnx_date_time_from_tm(universal_tm, 0); return result; } DateTime os_local_time_from_universal(DateTime* date_time) { // rjf: universal DateTime -> local time_t tm universal_tm = os_lnx_tm_from_date_time(*date_time); universal_tm.tm_isdst = -1; time_t universal_t = timegm(&universal_tm); tm local_tm = {0}; localtime_r(&universal_t, &local_tm); // rjf: local tm -> DateTime DateTime result = os_lnx_date_time_from_tm(local_tm, 0); return result; } void os_sleep_milliseconds(U32 msec) { usleep(msec * thousand(1)); } //////////////////////////////// //~ rjf: @os_hooks Child Processes (Implemented Per-OS) OS_Handle os_process_launch(OS_ProcessLaunchParams* params) { NotImplemented; } B32 os_process_join(OS_Handle handle, U64 endt_us) { NotImplemented; } void os_process_detach(OS_Handle handle) { NotImplemented; } //////////////////////////////// //~ rjf: @os_hooks Threads (Implemented Per-OS) OS_Handle os_thread_launch(OS_ThreadFunctionType* func, void* ptr, void* params) { OS_LNX_Entity* entity = os_lnx_entity_alloc(OS_LNX_EntityKind_Thread); entity->thread.func = func; entity->thread.ptr = ptr; { pthread_attr_t attr; pthread_attr_init(&attr); int pthread_result = pthread_create(&entity->thread.handle, &attr, os_lnx_thread_entry_point, entity); pthread_attr_destroy(&attr); if (pthread_result == -1) { os_lnx_entity_release(entity); entity = 0; } } OS_Handle handle = {(U64)entity}; return handle; } B32 os_thread_join(OS_Handle handle, U64 endt_us) { if (os_handle_match(handle, os_handle_zero())) { return 0; } OS_LNX_Entity* entity = (OS_LNX_Entity*)handle.u64[0]; int join_result = pthread_join(entity->thread.handle, 0); B32 result = (join_result == 0); os_lnx_entity_release(entity); return result; } void os_thread_detach(OS_Handle handle) { if(os_handle_match(handle, os_handle_zero())) { return; } OS_LNX_Entity* entity = (OS_LNX_Entity*)handle.u64[0]; os_lnx_entity_release(entity); } //////////////////////////////// //~ rjf: @os_hooks Synchronization Primitives (Implemented Per-OS) //- rjf: mutexes OS_Handle os_mutex_alloc(void) { OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_Mutex); pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); int init_result = pthread_mutex_init(&entity->mutex_handle, &attr); pthread_mutexattr_destroy(&attr); if (init_result == -1) { os_lnx_entity_release(entity); entity = 0; } OS_Handle handle = {(U64)entity}; return handle; } void os_mutex_release(OS_Handle mutex) { if (os_handle_match(mutex, os_handle_zero())) { return; } OS_LNX_Entity* entity = (OS_LNX_Entity *)mutex.u64[0]; pthread_mutex_destroy(&entity->mutex_handle); os_lnx_entity_release(entity); } void os_mutex_take(OS_Handle mutex) { if (os_handle_match(mutex, os_handle_zero())) { return; } OS_LNX_Entity* entity = (OS_LNX_Entity *)mutex.u64[0]; pthread_mutex_lock(&entity->mutex_handle); } void os_mutex_drop(OS_Handle mutex) { if (os_handle_match(mutex, os_handle_zero())) { return; } OS_LNX_Entity* entity = (OS_LNX_Entity *)mutex.u64[0]; pthread_mutex_unlock(&entity->mutex_handle); } //- rjf: reader/writer mutexes OS_Handle os_rw_mutex_alloc(void) { OS_LNX_Entity* entity = os_lnx_entity_alloc(OS_LNX_EntityKind_RWMutex); int init_result = pthread_rwlock_init(&entity->rwmutex_handle, 0); if (init_result == -1) { os_lnx_entity_release(entity); entity = 0; } OS_Handle handle = {(U64)entity}; return handle; } void os_rw_mutex_release(OS_Handle rw_mutex) { if (os_handle_match(rw_mutex, os_handle_zero())) { return; } OS_LNX_Entity* entity = (OS_LNX_Entity*)rw_mutex.u64[0]; pthread_rwlock_destroy(&entity->rwmutex_handle); os_lnx_entity_release(entity); } void os_rw_mutex_take_r(OS_Handle rw_mutex) { if (os_handle_match(rw_mutex, os_handle_zero())) { return; } OS_LNX_Entity* entity = (OS_LNX_Entity*)rw_mutex.u64[0]; pthread_rwlock_rdlock(&entity->rwmutex_handle); } void os_rw_mutex_drop_r(OS_Handle rw_mutex) { if (os_handle_match(rw_mutex, os_handle_zero())) { return; } OS_LNX_Entity* entity = (OS_LNX_Entity*)rw_mutex.u64[0]; pthread_rwlock_unlock(&entity->rwmutex_handle); } void os_rw_mutex_take_w(OS_Handle rw_mutex) { if (os_handle_match(rw_mutex, os_handle_zero())) { return; } OS_LNX_Entity* entity = (OS_LNX_Entity*)rw_mutex.u64[0]; pthread_rwlock_wrlock(&entity->rwmutex_handle); } void os_rw_mutex_drop_w(OS_Handle rw_mutex) { if (os_handle_match(rw_mutex, os_handle_zero())) { return; } OS_LNX_Entity* entity = (OS_LNX_Entity*)rw_mutex.u64[0]; pthread_rwlock_unlock(&entity->rwmutex_handle); } //- rjf: condition variables OS_Handle os_condition_variable_alloc(void) { OS_LNX_Entity* entity = os_lnx_entity_alloc(OS_LNX_EntityKind_ConditionVariable); int init_result = pthread_cond_init(&entity->cv.cond_handle, 0); if (init_result == -1) { os_lnx_entity_release(entity); entity = 0; } int init2_result = 0; if (entity) { init2_result = pthread_mutex_init(&entity->cv.rwlock_mutex_handle, 0); } if (init2_result == -1) { pthread_cond_destroy(&entity->cv.cond_handle); os_lnx_entity_release(entity); entity = 0; } OS_Handle handle = {(U64)entity}; return handle; } void os_condition_variable_release(OS_Handle cv) { if (os_handle_match(cv, os_handle_zero())) { return; } OS_LNX_Entity* entity = (OS_LNX_Entity *)cv.u64[0]; pthread_cond_destroy(&entity->cv.cond_handle); pthread_mutex_destroy(&entity->cv.rwlock_mutex_handle); os_lnx_entity_release(entity); } B32 os_condition_variable_wait(OS_Handle cv, OS_Handle mutex, U64 endt_us) { if (os_handle_match(cv, os_handle_zero())) { return 0; } if (os_handle_match(mutex, os_handle_zero())) { return 0; } OS_LNX_Entity* cv_entity = (OS_LNX_Entity*)cv.u64[0]; OS_LNX_Entity* mutex_entity = (OS_LNX_Entity*)mutex.u64[0]; struct timespec endt_timespec; endt_timespec.tv_sec = endt_us / million(1); endt_timespec.tv_nsec = thousand(1) * (endt_us - (endt_us / million(1)) * million(1)); int wait_result = pthread_cond_timedwait(&cv_entity->cv.cond_handle, &mutex_entity->mutex_handle, &endt_timespec); B32 result = (wait_result != ETIMEDOUT); return result; } B32 os_condition_variable_wait_rw_r(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) { // TODO(rjf): because pthread does not supply cv/rw natively, I had to hack // this together, but this would probably just be a lot better if we just // implemented the primitives ourselves with e.g. futexes // if(os_handle_match(cv, os_handle_zero())) { return 0; } if(os_handle_match(mutex_rw, os_handle_zero())) { return 0; } OS_LNX_Entity* cv_entity = (OS_LNX_Entity*)cv.u64[0]; OS_LNX_Entity* rw_mutex_entity = (OS_LNX_Entity*)mutex_rw.u64[0]; struct timespec endt_timespec; endt_timespec.tv_sec = endt_us / million(1); endt_timespec.tv_nsec = thousand(1) * (endt_us - (endt_us / million(1)) * million(1)); B32 result = 0; for(;;) { pthread_mutex_lock(&cv_entity->cv.rwlock_mutex_handle); int wait_result = pthread_cond_timedwait(&cv_entity->cv.cond_handle, &cv_entity->cv.rwlock_mutex_handle, &endt_timespec); if (wait_result != ETIMEDOUT) { pthread_rwlock_rdlock(&rw_mutex_entity->rwmutex_handle); pthread_mutex_unlock(&cv_entity->cv.rwlock_mutex_handle); result = 1; break; } pthread_mutex_unlock(&cv_entity->cv.rwlock_mutex_handle); if (wait_result == ETIMEDOUT) { break; } } return result; } B32 os_condition_variable_wait_rw_w(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) { // TODO(rjf): because pthread does not supply cv/rw natively, I had to hack // this together, but this would probably just be a lot better if we just // implemented the primitives ourselves with e.g. futexes // if (os_handle_match(cv, os_handle_zero())) { return 0; } if (os_handle_match(mutex_rw, os_handle_zero())) { return 0; } OS_LNX_Entity* cv_entity = (OS_LNX_Entity*)cv.u64[0]; OS_LNX_Entity* rw_mutex_entity = (OS_LNX_Entity*)mutex_rw.u64[0]; struct timespec endt_timespec; endt_timespec.tv_sec = endt_us / million(1); endt_timespec.tv_nsec = thousand(1) * (endt_us - (endt_us / million(1)) * million(1)); B32 result = 0; for(;;) { pthread_mutex_lock(&cv_entity->cv.rwlock_mutex_handle); int wait_result = pthread_cond_timedwait(&cv_entity->cv.cond_handle, &cv_entity->cv.rwlock_mutex_handle, &endt_timespec); if (wait_result != ETIMEDOUT) { pthread_rwlock_wrlock(&rw_mutex_entity->rwmutex_handle); pthread_mutex_unlock(&cv_entity->cv.rwlock_mutex_handle); result = 1; break; } pthread_mutex_unlock(&cv_entity->cv.rwlock_mutex_handle); if (wait_result == ETIMEDOUT) { break; } } return result; } void os_condition_variable_signal(OS_Handle cv) { if (os_handle_match(cv, os_handle_zero())) { return; } OS_LNX_Entity* cv_entity = (OS_LNX_Entity *)cv.u64[0]; pthread_cond_signal(&cv_entity->cv.cond_handle); } void os_condition_variable_broadcast(OS_Handle cv) { if (os_handle_match(cv, os_handle_zero())) { return; } OS_LNX_Entity* cv_entity = (OS_LNX_Entity *)cv.u64[0]; pthread_cond_broadcast(&cv_entity->cv.cond_handle); } //- rjf: cross-process semaphores OS_Handle os_semaphore_alloc(U32 initial_count, U32 max_count, String8 name) { NotImplemented; } void os_semaphore_release(OS_Handle semaphore) { NotImplemented; } OS_Handle os_semaphore_open(String8 name) { NotImplemented; } void os_semaphore_close(OS_Handle semaphore) { NotImplemented; } B32 os_semaphore_take(OS_Handle semaphore, U64 endt_us) { NotImplemented; } void os_semaphore_drop(OS_Handle semaphore) { NotImplemented; } //////////////////////////////// //~ rjf: @os_hooks Dynamically-Loaded Libraries (Implemented Per-OS) OS_Handle os_library_open(String8 path) { TempArena scratch = scratch_begin(0, 0); char* path_cstr = (char *)push_str8_copy(scratch.arena, path).str; void* so = dlopen(path_cstr, RTLD_LAZY); OS_Handle lib = { (U64)so }; scratch_end(scratch); return lib; } VoidProc* os_library_load_proc(OS_Handle lib, String8 name) { TempArena scratch = scratch_begin(0, 0); void* so = (void*)lib.u64; char* name_cstr = (char*)push_str8_copy(scratch.arena, name).str; VoidProc* proc = (VoidProc*)dlsym(so, name_cstr); scratch_end(scratch); return proc; } void os_library_close(OS_Handle lib) { void* so = (void*)lib.u64; dlclose(so); } //////////////////////////////// //~ rjf: @os_hooks Safe Calls (Implemented Per-OS) void os_safe_call(OS_ThreadFunctionType* func, OS_ThreadFunctionType* fail_handler, void* ptr) { // rjf: push handler to chain OS_LNX_SafeCallChain chain = {0}; SLLStackPush(os_lnx_safe_call_chain, &chain); chain.fail_handler = fail_handler; chain.ptr = ptr; // rjf: set up sig handler info struct sigaction new_act = {0}; new_act.sa_handler = os_lnx_safe_call_sig_handler; int signals_to_handle[] = { SIGILL, SIGFPE, SIGSEGV, SIGBUS, SIGTRAP, }; struct sigaction og_act[array_count(signals_to_handle)] = {0}; // rjf: attach handler info for all signals for(U32 i = 0; i < array_count(signals_to_handle); i += 1) { sigaction(signals_to_handle[i], &new_act, &og_act[i]); } // rjf: call function func(ptr); // rjf: reset handler info for all signals for (U32 i = 0; i < array_count(signals_to_handle); i += 1) { sigaction(signals_to_handle[i], &og_act[i], 0); } } //////////////////////////////// //~ rjf: @os_hooks GUIDs (Implemented Per-OS) OS_Guid os_make_guid(void) { U8 random_bytes[16] = {0}; md_static_assert(sizeof(random_bytes) == sizeof(OS_Guid), os_lnx_guid_size_check); getrandom(random_bytes, sizeof(random_bytes), 0); OS_Guid guid = {0}; memory_copy(&guid, random_bytes, sizeof(random_bytes)); guid.data3 &= 0x0fff; guid.data3 |= (4 << 12); guid.data4[0] &= 0x3f; guid.data4[0] |= 0x80; return guid; } //////////////////////////////// //~ rjf: @os_hooks Entry Points (Implemented Per-OS) #if BUILD_ENTRY_DEFINING_UNIT || 1 int main(int argc, char **argv) { //- rjf: set up OS layer { //- rjf: get statically-allocated system/process info { OS_SystemInfo *info = &os_lnx_state.system_info; info->logical_processor_count = (U32)get_nprocs(); info->page_size = (U64)getpagesize(); info->large_page_size = MB(2); info->allocation_granularity = info->page_size; } { OS_ProcessInfo *info = &os_lnx_state.process_info; info->pid = (U32)getpid(); } //- rjf: set up thread context local_persist TCTX tctx; tctx_init_and_equip(&tctx); //- rjf: set up dynamically allocated state os_lnx_state.arena = arena_alloc(); os_lnx_state.entity_arena = arena_alloc(); pthread_mutex_init(&os_lnx_state.entity_mutex, 0); //- rjf: grab dynamically allocated system info { TempArena scratch = scratch_begin(0, 0); OS_SystemInfo* info = &os_lnx_state.system_info; // rjf: get machine name B32 got_final_result = 0; U8* buffer = 0; int size = 0; for (S64 cap = 4096, r = 0; r < 4; cap *= 2, r += 1) { scratch_end(scratch); buffer = push_array_no_zero(scratch.arena, U8, cap); size = gethostname((char*)buffer, cap); if (size < cap) { got_final_result = 1; break; } } // rjf: save name to info if (got_final_result && size > 0) { info->machine_name.size = size; info->machine_name.str = push_array_no_zero(os_lnx_state.arena, U8, info->machine_name.size + 1); MemoryCopy(info->machine_name.str, buffer, info->machine_name.size); info->machine_name.str[info->machine_name.size] = 0; } scratch_end(scratch); } //- rjf: grab dynamically allocated process info { TempArena scratch = scratch_begin(0, 0); OS_ProcessInfo* info = &os_lnx_state.process_info; // rjf: grab binary path { // rjf: get self string B32 got_final_result = 0; U8* buffer = 0; int size = 0; for (S64 cap = PATH_MAX, r = 0; r < 4; cap *= 2, r += 1) { scratch_end(scratch); buffer = push_array_no_zero(scratch.arena, U8, cap); size = readlink("/proc/self/exe", (char*)buffer, cap); if (size < cap) { got_final_result = 1; break; } } // rjf: save if (got_final_result && size > 0) { String8 full_name = str8(buffer, size); String8 name_chopped = str8_chop_last_slash(full_name); info->binary_path = push_str8_copy(os_lnx_state.arena, name_chopped); } } // rjf: grab initial directory { info->initial_path = os_get_current_path(os_lnx_state.arena); } // rjf: grab home directory { char *home = getenv("HOME"); info->user_program_data_path = str8_cstring(home); } scratch_end(scratch); } } //- rjf: call into "real" entry point main_thread_base_entry_point(entry_point, argv, (U64)argc); } // BUILD_ENTRY_POINT #endif