Files
metadesk/code/os/linux/os_linux.c
T
ed c8cb9f3995 prepping to collapse Arena receiving functions to those that receive AllocatorInfo
perf impact of the indirection should be minimal, reduces code duplication.

Ideally we'd use gencpp to just have the user specify what they want, then modify which proc is avail by doing the refactors required for using either
However, it can't process execution bodies yet or expressions so thats not possible.

The other way is to just utilize _Generic to generalize the codepath so that it collapses neater but that leads to a ton of implementation getting lifted to preprocessing... so no.
2025-02-09 11:51:50 -05:00

1106 lines
28 KiB
C

#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_alloc(ainfo); {
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