mirror of
https://github.com/Ed94/metadesk.git
synced 2026-06-13 07:52:22 -07:00
3e064f6443
Eventually a heavy reduction of base & OS should be provided via libgen or done here to reduce the overall surface area of the library for the user. Base and os were dragged from raddbg and the original metadesk didn't need as many definitions.
1093 lines
30 KiB
C
1093 lines
30 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 md_global MD_OS_LNX_State md_os_lnx_state = {0};
|
|
MD_API_C md_thread_static MD_OS_LNX_SafeCallChain* md_os_lnx_safe_call_chain = 0;
|
|
|
|
////////////////////////////////
|
|
//~ rjf: Entities
|
|
|
|
MD_OS_LNX_Entity*
|
|
md_os_lnx_entity_alloc(MD_OS_LNX_EntityKind kind)
|
|
{
|
|
MD_OS_LNX_Entity* entity = 0;
|
|
md_defer_loop(pthread_mutex_lock (&md_os_lnx_state.entity_mutex), pthread_mutex_unlock(&md_os_lnx_state.entity_mutex))
|
|
{
|
|
entity = md_os_lnx_state.entity_free;
|
|
if (entity) {
|
|
md_sll_stack_pop(md_os_lnx_state.entity_free);
|
|
}
|
|
else {
|
|
entity = md_push_array__no_zero(md_os_lnx_state.entity_arena, MD_OS_LNX_Entity, 1);
|
|
}
|
|
}
|
|
md_memory_zero_struct(entity);
|
|
entity->kind = kind;
|
|
return entity;
|
|
}
|
|
|
|
void
|
|
md_os_lnx_entity_release(MD_OS_LNX_Entity *entity) {
|
|
md_defer_loop(pthread_mutex_lock(&md_os_lnx_state.entity_mutex), pthread_mutex_unlock(&md_os_lnx_state.entity_mutex))
|
|
{
|
|
md_sll_stack_push(md_os_lnx_state.entity_free, entity);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: Thread Entry Point
|
|
|
|
void*
|
|
md_os_lnx_thread_entry_point(void *ptr)
|
|
{
|
|
MD_OS_LNX_Entity* entity = (MD_OS_LNX_Entity *)ptr;
|
|
MD_OS_ThreadFunctionType* func = entity->thread.func;
|
|
void* thread_ptr = entity->thread.ptr;
|
|
|
|
MD_TCTX md_tctx_;
|
|
md_tctx_init_and_equip(&md_tctx_);
|
|
func(thread_ptr);
|
|
md_tctx_release();
|
|
return 0;
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: @md_os_hooks System/Process Info (Implemented Per-OS)
|
|
|
|
MD_OS_SystemInfo*
|
|
md_os_get_system_info(void) {
|
|
return &md_os_lnx_state.system_info;
|
|
}
|
|
|
|
MD_OS_ProcessInfo*
|
|
md_os_get_process_info(void) {
|
|
return &md_os_lnx_state.process_info;
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: @md_os_hooks Thread Info (Implemented Per-OS)
|
|
|
|
void
|
|
md_os_set_thread_name(MD_String8 name)
|
|
{
|
|
MD_TempArena scratch = md_scratch_begin(0, 0);
|
|
{
|
|
MD_String8 name_copy = md_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: @md_os_hooks Aborting (Implemented Per-OS)
|
|
|
|
void md_os_abort(MD_S32 exit_code) { exit(exit_code); }
|
|
|
|
////////////////////////////////
|
|
//~ rjf: @md_os_hooks File System (Implemented Per-OS)
|
|
|
|
//- rjf: files
|
|
|
|
MD_OS_Handle
|
|
md_os_file_open(MD_OS_AccessFlags flags, MD_String8 path)
|
|
{
|
|
MD_TempArena scratch = md_scratch_begin(0, 0);
|
|
{
|
|
MD_String8 path_copy = md_push_str8_copy(scratch.arena, path);
|
|
int lnx_flags = 0;
|
|
if (flags & (MD_OS_AccessFlag_Read | MD_OS_AccessFlag_Write)) { lnx_flags = O_RDWR; }
|
|
else if (flags & MD_OS_AccessFlag_Write) { lnx_flags = O_WRONLY; }
|
|
else if (flags & MD_OS_AccessFlag_Read) { lnx_flags = O_RDONLY; }
|
|
if (flags & MD_OS_AccessFlag_Append) { lnx_flags |= O_APPEND; }
|
|
|
|
int fd = open((char *)path_copy.str, lnx_flags);
|
|
MD_OS_Handle handle = {0};
|
|
if (fd != -1) {
|
|
handle.u64[0] = fd;
|
|
}
|
|
}
|
|
scratch_end(scratch);
|
|
return handle;
|
|
}
|
|
|
|
void
|
|
md_os_file_close(MD_OS_Handle file) {
|
|
if (md_os_handle_match(file, md_os_handle_zero())) { return; }
|
|
int fd = (int)file.u64[0];
|
|
close(fd);
|
|
}
|
|
|
|
MD_U64
|
|
md_os_file_read(MD_OS_Handle file, MD_Rng1U64 rng, void* out_data)
|
|
{
|
|
if (md_os_handle_match(file, md_os_handle_zero())) { return 0; }
|
|
|
|
int fd = (int)file.u64[0];
|
|
if (rng.md_min != 0) {
|
|
lseek(fd, rng.md_min, SEEK_SET);
|
|
}
|
|
|
|
MD_U64 total_num_bytes_to_read = md_dim_1u64(rng);
|
|
MD_U64 total_num_bytes_read = 0;
|
|
MD_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, (MD_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;
|
|
}
|
|
|
|
MD_U64
|
|
md_os_file_write(MD_OS_Handle file, MD_Rng1U64 rng, void *data)
|
|
{
|
|
if (md_os_handle_match(file, md_os_handle_zero())) { return 0; }
|
|
|
|
int fd = (int)file.u64[0];
|
|
if (rng.md_min != 0) {
|
|
lseek(fd, rng.md_min, SEEK_SET);
|
|
}
|
|
|
|
MD_U64 total_num_bytes_to_write = md_dim_1u64(rng);
|
|
MD_U64 total_num_bytes_written = 0;
|
|
MD_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, (MD_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;
|
|
}
|
|
|
|
MD_B32
|
|
md_os_file_set_times(MD_OS_Handle file, MD_DateTime date_time)
|
|
{
|
|
if (md_os_handle_match(file, md_os_handle_zero())) { return 0; }
|
|
int fd = (int)file.u64[0];
|
|
|
|
timespec time = md_os_lnx_timespec_from_date_time(date_time);
|
|
timespec times[2] = {time, time};
|
|
int futimens_result = futimens(fd, times);
|
|
MD_B32 good = (futimens_result != -1);
|
|
return good;
|
|
}
|
|
|
|
MD_FileProperties
|
|
md_os_properties_from_file(MD_OS_Handle file)
|
|
{
|
|
if (md_os_handle_match(file, md_os_handle_zero())) { return (MD_FileProperties){0}; }
|
|
|
|
int fd = (int)file.u64[0];
|
|
struct stat fd_stat = {0};
|
|
int fstat_result = fstat(fd, &fd_stat);
|
|
MD_FileProperties props = {0};
|
|
if (fstat_result != -1) {
|
|
props = md_os_lnx_file_properties_from_stat(&fd_stat);
|
|
}
|
|
return props;
|
|
}
|
|
|
|
MD_OS_FileID
|
|
md_os_id_from_file(MD_OS_Handle file)
|
|
{
|
|
if (md_os_handle_match(file, md_os_handle_zero())) { return (MD_OS_FileID){0}; }
|
|
int fd = (int)file.u64[0];
|
|
struct stat fd_stat = {0};
|
|
int fstat_result = fstat(fd, &fd_stat);
|
|
MD_OS_FileID id = {0};
|
|
if (fstat_result != -1) {
|
|
id.v[0] = fd_stat.st_dev;
|
|
id.v[1] = fd_stat.st_ino;
|
|
}
|
|
return id;
|
|
}
|
|
|
|
MD_B32
|
|
md_os_delete_file_at_path(MD_String8 path)
|
|
{
|
|
MD_TempArena scratch = md_scratch_begin(0, 0);
|
|
MD_B32 result = 0;
|
|
MD_String8 path_copy = md_push_str8_copy(scratch.arena, path);
|
|
if (remove((char*)path_copy.str) != -1) {
|
|
result = 1;
|
|
}
|
|
scratch_end(scratch);
|
|
return result;
|
|
}
|
|
|
|
MD_B32
|
|
md_os_copy_file_path(MD_String8 dst, MD_String8 src)
|
|
{
|
|
MD_B32 result = 0;
|
|
MD_OS_Handle src_h = md_os_file_open(MD_OS_AccessFlag_Read, src);
|
|
MD_OS_Handle dst_h = md_os_file_open(MD_OS_AccessFlag_Write, dst);
|
|
if ( !md_os_handle_match(src_h, md_os_handle_zero()) &&
|
|
!md_os_handle_match(dst_h, md_os_handle_zero()) )
|
|
{
|
|
MD_FileProperties src_props = md_os_properties_from_file(src_h);
|
|
|
|
MD_U64 size = src_props.size;
|
|
MD_U64 total_bytes_copied = 0;
|
|
MD_U64 bytes_left_to_copy = size;
|
|
for (;bytes_left_to_copy > 0;)
|
|
{
|
|
MD_TempArena scratch = md_scratch_begin(0, 0);
|
|
|
|
MD_U64 buffer_size = Min(bytes_left_to_copy, MD_MB(8));
|
|
MD_U8 *buffer = md_push_array__no_zero(scratch.arena, MD_U8, buffer_size);
|
|
MD_U64 bytes_read = md_os_file_read (src_h, md_r1u64(total_bytes_copied, total_bytes_copied+buffer_size), buffer);
|
|
MD_U64 bytes_written = md_os_file_write(dst_h, md_r1u64(total_bytes_copied, total_bytes_copied+bytes_read), buffer);
|
|
MD_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;
|
|
}
|
|
}
|
|
}
|
|
md_os_file_close(src_h);
|
|
md_os_file_close(dst_h);
|
|
return result;
|
|
}
|
|
|
|
MD_String8
|
|
md_os_full_path_from_path__ainfo(MD_AllocatorInfo ainfo, MD_String8 path)
|
|
{
|
|
char buffer[PATH_MAX] = {0};
|
|
MD_TempArena scratch = md_scratch_begin(ainfo); {
|
|
MD_String8 path_copy = md_push_str8_copy(scratch.arena, path);
|
|
realpath((char *)path_copy.str, buffer);
|
|
}
|
|
scratch_end(scratch);
|
|
MD_String8 result = md_alloc_str8_copy(ainfo, md_str8_cstring(buffer));
|
|
return result;
|
|
}
|
|
|
|
MD_B32
|
|
md_os_file_path_exists(MD_String8 path)
|
|
{
|
|
MD_B32 result = 0;
|
|
MD_TempArena scratch = md_scratch_begin(0, 0);
|
|
MD_String8 path_copy = md_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;
|
|
}
|
|
|
|
md_internal MD_FileProperties
|
|
md_os_properties_from_file_path(MD_String8 path)
|
|
{
|
|
MD_FileProperties props = {0};
|
|
MD_TempArena scratch = md_scratch_begin(0, 0);
|
|
MD_String8 path_copy = md_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 = md_os_lnx_file_properties_from_stat(&f_stat);
|
|
}
|
|
scratch_end(scratch);
|
|
return props;
|
|
}
|
|
|
|
//- rjf: file maps
|
|
|
|
MD_OS_Handle
|
|
md_os_file_map_open(MD_OS_AccessFlags flags, MD_OS_Handle file) {
|
|
MD_OS_Handle map = file;
|
|
return map;
|
|
}
|
|
|
|
void
|
|
md_os_file_map_close(MD_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*
|
|
md_os_file_map_view_open(MD_OS_Handle map, MD_OS_AccessFlags flags, MD_Rng1U64 range)
|
|
{
|
|
if (md_os_handle_match(map, md_os_handle_zero())) { return 0; }
|
|
int prot_flags = 0;
|
|
if (flags & MD_OS_AccessFlag_Write) { prot_flags |= PROT_WRITE; }
|
|
if (flags & MD_OS_AccessFlag_Read) { prot_flags |= PROT_READ; }
|
|
int fd = (int)map.u64[0];
|
|
int map_flags = MAP_PRIVATE;
|
|
void* base = mmap(0, md_dim_1u64(range), prot_flags, map_flags, fd, range.md_min);
|
|
return base;
|
|
}
|
|
|
|
void
|
|
md_os_file_map_view_close(MD_OS_Handle map, void* ptr, MD_Rng1U64 range) {
|
|
munmap(ptr, md_dim_1u64(range));
|
|
}
|
|
|
|
//- rjf: directory iteration
|
|
|
|
MD_OS_FileIter*
|
|
md_os_file_iter_begin__ainfo(MD_AllocatorInfo ainfo, MD_String8 path, MD_OS_FileIterFlags flags)
|
|
{
|
|
MD_OS_FileIter*
|
|
base_iter = md_alloc_array(ainfo, MD_OS_FileIter, 1);
|
|
base_iter->flags = flags;
|
|
|
|
MD_OS_LNX_FileIter* iter = (MD_OS_LNX_FileIter*)base_iter->memory;
|
|
{
|
|
MD_String8 path_copy = md_str8_copy(arena, path);
|
|
iter->dir = opendir((char*)path_copy.str);
|
|
iter->path = path_copy;
|
|
}
|
|
return base_iter;
|
|
}
|
|
|
|
MD_B32
|
|
md_os_file_iter_next(MD_Arena* arena, MD_OS_FileIter* iter, MD_OS_FileInfo* info_out)
|
|
{
|
|
MD_B32 good = 0;
|
|
MD_OS_LNX_FileIter* lnx_iter = (MD_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)
|
|
{
|
|
MD_TempArena scratch = md_scratch_begin(&arena, 1);
|
|
MD_String8 full_path = md_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
|
|
MD_B32 filtered = 0;
|
|
if(good)
|
|
{
|
|
filtered = ((st.st_mode == S_IFDIR && iter->flags & MD_OS_FileIterFlag_SkipFolders) ||
|
|
(st.st_mode == S_IFREG && iter->flags & MD_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 = md_str8_copy(arena, md_str8_cstring(lnx_iter->dp->d_name));
|
|
if (stat_result != -1) {
|
|
info_out->props = md_os_lnx_file_properties_from_stat(&st);
|
|
}
|
|
break;
|
|
}
|
|
|
|
// rjf: exit if not good
|
|
if (!good)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
return good;
|
|
}
|
|
|
|
void
|
|
md_os_file_iter_end(MD_OS_FileIter* iter) {
|
|
MD_OS_LNX_FileIter* lnx_iter = (MD_OS_LNX_FileIter*)iter->memory;
|
|
closedir(lnx_iter->dir);
|
|
}
|
|
|
|
//- rjf: directory creation
|
|
|
|
MD_B32
|
|
md_os_make_directory(MD_String8 path)
|
|
{
|
|
MD_B32 result = 0;
|
|
MD_TempArena scratch = md_scratch_begin(0, 0);
|
|
MD_String8 path_copy = md_push_str8_copy(scratch.arena, path);
|
|
if (mkdir((char*)path_copy.str, 0777) != -1) {
|
|
result = 1;
|
|
}
|
|
scratch_end(scratch);
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: @md_os_hooks Shared Memory (Implemented Per-OS)
|
|
|
|
MD_OS_Handle
|
|
md_os_shared_memory_alloc(MD_U64 size, MD_String8 name)
|
|
{
|
|
MD_TempArena scratch = md_scratch_begin(0, 0);
|
|
MD_String8 name_copy = md_push_str8_copy(scratch.arena, name);
|
|
int id = shm_open((char *)name_copy.str, O_RDWR, 0);
|
|
ftruncate(id, size);
|
|
scratch_end(scratch);
|
|
MD_OS_Handle result = {(MD_U64)id};
|
|
return result;
|
|
}
|
|
|
|
MD_OS_Handle
|
|
md_os_shared_memory_open(MD_String8 name)
|
|
{
|
|
MD_TempArena scratch = md_scratch_begin(0, 0);
|
|
MD_String8 name_copy = md_push_str8_copy(scratch.arena, name);
|
|
int id = shm_open((char *)name_copy.str, O_RDWR, 0);
|
|
scratch_end(scratch);
|
|
MD_OS_Handle result = {(MD_U64)id};
|
|
return result;
|
|
}
|
|
|
|
void
|
|
md_os_shared_memory_close(MD_OS_Handle handle) {
|
|
if (md_os_handle_match(handle, md_os_handle_zero())) { return; }
|
|
int id = (int)handle.u64[0];
|
|
close(id);
|
|
}
|
|
|
|
void*
|
|
md_os_shared_memory_view_open(MD_OS_Handle handle, MD_Rng1U64 range) {
|
|
if (md_os_handle_match(handle, md_os_handle_zero())) { return 0; }
|
|
int id = (int)handle.u64[0];
|
|
void* base = mmap(0, md_dim_1u64(range), PROT_READ|PROT_WRITE, MAP_SHARED, id, range.md_min);
|
|
return base;
|
|
}
|
|
|
|
void
|
|
md_os_shared_memory_view_close(MD_OS_Handle handle, void* ptr, MD_Rng1U64 range) {
|
|
if (md_os_handle_match(handle, md_os_handle_zero())) { return; }
|
|
munmap(ptr, md_dim_1u64(range));
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: @md_os_hooks Time (Implemented Per-OS)
|
|
|
|
MD_U64
|
|
md_os_now_microseconds(void) {
|
|
struct timespec t;
|
|
clock_gettime(CLOCK_MONOTONIC, &t);
|
|
MD_U64 result = t.tv_sec * md_million(1) + (t.tv_nsec / md_thousand(1));
|
|
return result;
|
|
}
|
|
|
|
MD_U32
|
|
md_os_now_unix(void) {
|
|
time_t t = time(0);
|
|
return (MD_U32)t;
|
|
}
|
|
|
|
MD_DateTime
|
|
md_os_now_universal_time(void)
|
|
{
|
|
time_t t = 0;
|
|
time(&t);
|
|
struct tm universal_tm = {0};
|
|
gmtime_r(&t, &universal_tm);
|
|
MD_DateTime result = md_os_lnx_date_time_from_tm(universal_tm, 0);
|
|
return result;
|
|
}
|
|
|
|
MD_DateTime
|
|
md_os_universal_time_from_local(MD_DateTime* date_time)
|
|
{
|
|
// rjf: local MD_DateTime -> universal time_t
|
|
tm local_tm = md_os_lnx_tm_from_date_time(*date_time);
|
|
local_tm.tm_isdst = -1;
|
|
time_t universal_t = mktime(&local_tm);
|
|
|
|
// rjf: universal time_t -> MD_DateTime
|
|
tm universal_tm = {0};
|
|
gmtime_r(&universal_t, &universal_tm);
|
|
MD_DateTime result = md_os_lnx_date_time_from_tm(universal_tm, 0);
|
|
return result;
|
|
}
|
|
|
|
MD_DateTime
|
|
md_os_local_time_from_universal(MD_DateTime* date_time)
|
|
{
|
|
// rjf: universal MD_DateTime -> local time_t
|
|
tm universal_tm = md_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 -> MD_DateTime
|
|
MD_DateTime result = md_os_lnx_date_time_from_tm(local_tm, 0);
|
|
return result;
|
|
}
|
|
|
|
void
|
|
md_os_sleep_milliseconds(MD_U32 msec) {
|
|
usleep(msec * md_thousand(1));
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: @md_os_hooks Child Processes (Implemented Per-OS)
|
|
|
|
MD_OS_Handle
|
|
md_os_process_launch(MD_OS_ProcessLaunchParams* params)
|
|
{
|
|
NotImplemented;
|
|
}
|
|
|
|
MD_B32
|
|
md_os_process_join(MD_OS_Handle handle, MD_U64 endt_us)
|
|
{
|
|
NotImplemented;
|
|
}
|
|
|
|
void
|
|
md_os_process_detach(MD_OS_Handle handle)
|
|
{
|
|
NotImplemented;
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: @md_os_hooks Threads (Implemented Per-OS)
|
|
|
|
MD_OS_Handle
|
|
md_os_thread_launch(MD_OS_ThreadFunctionType* func, void* ptr, void* params)
|
|
{
|
|
MD_OS_LNX_Entity* entity = md_os_lnx_entity_alloc(MD_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, md_os_lnx_thread_entry_point, entity);
|
|
pthread_attr_destroy(&attr);
|
|
if (pthread_result == -1)
|
|
{
|
|
md_os_lnx_entity_release(entity);
|
|
entity = 0;
|
|
}
|
|
}
|
|
MD_OS_Handle handle = {(MD_U64)entity};
|
|
return handle;
|
|
}
|
|
|
|
MD_B32
|
|
md_os_thread_join(MD_OS_Handle handle, MD_U64 endt_us)
|
|
{
|
|
if (md_os_handle_match(handle, md_os_handle_zero())) { return 0; }
|
|
MD_OS_LNX_Entity* entity = (MD_OS_LNX_Entity*)handle.u64[0];
|
|
int join_result = pthread_join(entity->thread.handle, 0);
|
|
MD_B32 result = (join_result == 0);
|
|
md_os_lnx_entity_release(entity);
|
|
return result;
|
|
}
|
|
|
|
void
|
|
md_os_thread_detach(MD_OS_Handle handle) {
|
|
if(md_os_handle_match(handle, md_os_handle_zero())) { return; }
|
|
MD_OS_LNX_Entity* entity = (MD_OS_LNX_Entity*)handle.u64[0];
|
|
md_os_lnx_entity_release(entity);
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: @md_os_hooks Synchronization Primitives (Implemented Per-OS)
|
|
|
|
//- rjf: mutexes
|
|
|
|
MD_OS_Handle
|
|
md_os_mutex_alloc(void)
|
|
{
|
|
MD_OS_LNX_Entity *entity = md_os_lnx_entity_alloc(MD_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) {
|
|
md_os_lnx_entity_release(entity);
|
|
entity = 0;
|
|
}
|
|
MD_OS_Handle handle = {(MD_U64)entity};
|
|
return handle;
|
|
}
|
|
|
|
void
|
|
md_os_mutex_release(MD_OS_Handle mutex) {
|
|
if (md_os_handle_match(mutex, md_os_handle_zero())) { return; }
|
|
MD_OS_LNX_Entity* entity = (MD_OS_LNX_Entity *)mutex.u64[0];
|
|
pthread_mutex_destroy(&entity->mutex_handle);
|
|
md_os_lnx_entity_release(entity);
|
|
}
|
|
|
|
void
|
|
md_os_mutex_take(MD_OS_Handle mutex) {
|
|
if (md_os_handle_match(mutex, md_os_handle_zero())) { return; }
|
|
MD_OS_LNX_Entity* entity = (MD_OS_LNX_Entity *)mutex.u64[0];
|
|
pthread_mutex_lock(&entity->mutex_handle);
|
|
}
|
|
|
|
void
|
|
md_os_mutex_drop(MD_OS_Handle mutex) {
|
|
if (md_os_handle_match(mutex, md_os_handle_zero())) { return; }
|
|
MD_OS_LNX_Entity* entity = (MD_OS_LNX_Entity *)mutex.u64[0];
|
|
pthread_mutex_unlock(&entity->mutex_handle);
|
|
}
|
|
|
|
//- rjf: reader/writer mutexes
|
|
|
|
MD_OS_Handle
|
|
md_os_rw_mutex_alloc(void)
|
|
{
|
|
MD_OS_LNX_Entity* entity = md_os_lnx_entity_alloc(MD_OS_LNX_EntityKind_RWMutex);
|
|
int init_result = pthread_rwlock_init(&entity->rwmutex_handle, 0);
|
|
if (init_result == -1) {
|
|
md_os_lnx_entity_release(entity);
|
|
entity = 0;
|
|
}
|
|
MD_OS_Handle handle = {(MD_U64)entity};
|
|
return handle;
|
|
}
|
|
|
|
void
|
|
md_os_rw_mutex_release(MD_OS_Handle rw_mutex) {
|
|
if (md_os_handle_match(rw_mutex, md_os_handle_zero())) { return; }
|
|
MD_OS_LNX_Entity* entity = (MD_OS_LNX_Entity*)rw_mutex.u64[0];
|
|
pthread_rwlock_destroy(&entity->rwmutex_handle);
|
|
md_os_lnx_entity_release(entity);
|
|
}
|
|
|
|
void
|
|
md_os_rw_mutex_take_r(MD_OS_Handle rw_mutex) {
|
|
if (md_os_handle_match(rw_mutex, md_os_handle_zero())) { return; }
|
|
MD_OS_LNX_Entity* entity = (MD_OS_LNX_Entity*)rw_mutex.u64[0];
|
|
pthread_rwlock_rdlock(&entity->rwmutex_handle);
|
|
}
|
|
|
|
void
|
|
md_os_rw_mutex_drop_r(MD_OS_Handle rw_mutex) {
|
|
if (md_os_handle_match(rw_mutex, md_os_handle_zero())) { return; }
|
|
MD_OS_LNX_Entity* entity = (MD_OS_LNX_Entity*)rw_mutex.u64[0];
|
|
pthread_rwlock_unlock(&entity->rwmutex_handle);
|
|
}
|
|
|
|
void
|
|
md_os_rw_mutex_take_w(MD_OS_Handle rw_mutex) {
|
|
if (md_os_handle_match(rw_mutex, md_os_handle_zero())) { return; }
|
|
MD_OS_LNX_Entity* entity = (MD_OS_LNX_Entity*)rw_mutex.u64[0];
|
|
pthread_rwlock_wrlock(&entity->rwmutex_handle);
|
|
}
|
|
|
|
void
|
|
md_os_rw_mutex_drop_w(MD_OS_Handle rw_mutex) {
|
|
if (md_os_handle_match(rw_mutex, md_os_handle_zero())) { return; }
|
|
MD_OS_LNX_Entity* entity = (MD_OS_LNX_Entity*)rw_mutex.u64[0];
|
|
pthread_rwlock_unlock(&entity->rwmutex_handle);
|
|
}
|
|
|
|
//- rjf: condition variables
|
|
|
|
MD_OS_Handle
|
|
md_os_condition_variable_alloc(void)
|
|
{
|
|
MD_OS_LNX_Entity* entity = md_os_lnx_entity_alloc(MD_OS_LNX_EntityKind_ConditionVariable);
|
|
int init_result = pthread_cond_init(&entity->cv.cond_handle, 0);
|
|
if (init_result == -1) {
|
|
md_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);
|
|
md_os_lnx_entity_release(entity);
|
|
entity = 0;
|
|
}
|
|
MD_OS_Handle handle = {(MD_U64)entity};
|
|
return handle;
|
|
}
|
|
|
|
void
|
|
md_os_condition_variable_release(MD_OS_Handle cv) {
|
|
if (md_os_handle_match(cv, md_os_handle_zero())) { return; }
|
|
MD_OS_LNX_Entity* entity = (MD_OS_LNX_Entity *)cv.u64[0];
|
|
pthread_cond_destroy(&entity->cv.cond_handle);
|
|
pthread_mutex_destroy(&entity->cv.rwlock_mutex_handle);
|
|
md_os_lnx_entity_release(entity);
|
|
}
|
|
|
|
MD_B32
|
|
md_os_condition_variable_wait(MD_OS_Handle cv, MD_OS_Handle mutex, MD_U64 endt_us)
|
|
{
|
|
if (md_os_handle_match(cv, md_os_handle_zero())) { return 0; }
|
|
if (md_os_handle_match(mutex, md_os_handle_zero())) { return 0; }
|
|
|
|
MD_OS_LNX_Entity* cv_entity = (MD_OS_LNX_Entity*)cv.u64[0];
|
|
MD_OS_LNX_Entity* mutex_entity = (MD_OS_LNX_Entity*)mutex.u64[0];
|
|
|
|
struct timespec endt_timespec;
|
|
endt_timespec.tv_sec = endt_us / md_million(1);
|
|
endt_timespec.tv_nsec = md_thousand(1) * (endt_us - (endt_us / md_million(1)) * md_million(1));
|
|
|
|
int wait_result = pthread_cond_timedwait(&cv_entity->cv.cond_handle, &mutex_entity->mutex_handle, &endt_timespec);
|
|
MD_B32 result = (wait_result != ETIMEDOUT);
|
|
return result;
|
|
}
|
|
|
|
MD_B32
|
|
md_os_condition_variable_wait_rw_r(MD_OS_Handle cv, MD_OS_Handle mutex_rw, MD_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 (md_os_handle_match(cv, md_os_handle_zero())) { return 0; }
|
|
if (md_os_handle_match(mutex_rw, md_os_handle_zero())) { return 0; }
|
|
|
|
MD_OS_LNX_Entity* cv_entity = (MD_OS_LNX_Entity*)cv.u64[0];
|
|
MD_OS_LNX_Entity* rw_mutex_entity = (MD_OS_LNX_Entity*)mutex_rw.u64[0];
|
|
|
|
struct timespec endt_timespec;
|
|
endt_timespec.tv_sec = endt_us / md_million(1);
|
|
endt_timespec.tv_nsec = md_thousand(1) * (endt_us - (endt_us / md_million(1)) * md_million(1));
|
|
|
|
MD_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;
|
|
}
|
|
|
|
MD_B32
|
|
md_os_condition_variable_wait_rw_w(MD_OS_Handle cv, MD_OS_Handle mutex_rw, MD_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 (md_os_handle_match(cv, md_os_handle_zero())) { return 0; }
|
|
if (md_os_handle_match(mutex_rw, md_os_handle_zero())) { return 0; }
|
|
|
|
MD_OS_LNX_Entity* cv_entity = (MD_OS_LNX_Entity*)cv.u64[0];
|
|
MD_OS_LNX_Entity* rw_mutex_entity = (MD_OS_LNX_Entity*)mutex_rw.u64[0];
|
|
|
|
struct timespec endt_timespec;
|
|
endt_timespec.tv_sec = endt_us / md_million(1);
|
|
endt_timespec.tv_nsec = md_thousand(1) * (endt_us - (endt_us / md_million(1)) * md_million(1));
|
|
|
|
MD_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
|
|
md_os_condition_variable_signal(MD_OS_Handle cv) {
|
|
if (md_os_handle_match(cv, md_os_handle_zero())) { return; }
|
|
MD_OS_LNX_Entity* cv_entity = (MD_OS_LNX_Entity *)cv.u64[0];
|
|
pthread_cond_signal(&cv_entity->cv.cond_handle);
|
|
}
|
|
|
|
void
|
|
md_os_condition_variable_broadcast(MD_OS_Handle cv) {
|
|
if (md_os_handle_match(cv, md_os_handle_zero())) { return; }
|
|
MD_OS_LNX_Entity* cv_entity = (MD_OS_LNX_Entity *)cv.u64[0];
|
|
pthread_cond_broadcast(&cv_entity->cv.cond_handle);
|
|
}
|
|
|
|
//- rjf: cross-process semaphores
|
|
|
|
MD_OS_Handle
|
|
md_os_semaphore_alloc(MD_U32 initial_count, MD_U32 max_count, MD_String8 name)
|
|
{
|
|
NotImplemented;
|
|
}
|
|
|
|
void
|
|
md_os_semaphore_release(MD_OS_Handle semaphore)
|
|
{
|
|
NotImplemented;
|
|
}
|
|
|
|
MD_OS_Handle
|
|
md_os_semaphore_open(MD_String8 name)
|
|
{
|
|
NotImplemented;
|
|
}
|
|
|
|
void
|
|
md_os_semaphore_close(MD_OS_Handle semaphore)
|
|
{
|
|
NotImplemented;
|
|
}
|
|
|
|
MD_B32
|
|
md_os_semaphore_take(MD_OS_Handle semaphore, MD_U64 endt_us)
|
|
{
|
|
NotImplemented;
|
|
}
|
|
|
|
void
|
|
md_os_semaphore_drop(MD_OS_Handle semaphore)
|
|
{
|
|
NotImplemented;
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: @md_os_hooks Dynamically-Loaded Libraries (Implemented Per-OS)
|
|
|
|
MD_OS_Handle
|
|
md_os_library_open(MD_String8 path) {
|
|
MD_TempArena scratch = md_scratch_begin(0, 0);
|
|
char* path_cstr = (char *)md_push_str8_copy(scratch.arena, path).str;
|
|
void* so = dlopen(path_cstr, RTLD_LAZY);
|
|
MD_OS_Handle lib = { (MD_U64)so };
|
|
scratch_end(scratch);
|
|
return lib;
|
|
}
|
|
|
|
MD_VoidProc*
|
|
md_os_library_load_proc(MD_OS_Handle lib, MD_String8 name) {
|
|
MD_TempArena scratch = md_scratch_begin(0, 0);
|
|
void* so = (void*)lib.u64;
|
|
char* name_cstr = (char*)md_push_str8_copy(scratch.arena, name).str;
|
|
MD_VoidProc* proc = (MD_VoidProc*)dlsym(so, name_cstr);
|
|
scratch_end(scratch);
|
|
return proc;
|
|
}
|
|
|
|
void
|
|
md_os_library_close(MD_OS_Handle lib) {
|
|
void* so = (void*)lib.u64;
|
|
dlclose(so);
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: @md_os_hooks Safe Calls (Implemented Per-OS)
|
|
|
|
void
|
|
md_os_safe_call(MD_OS_ThreadFunctionType* func, MD_OS_ThreadFunctionType* fail_handler, void* ptr)
|
|
{
|
|
// rjf: push handler to chain
|
|
MD_OS_LNX_SafeCallChain chain = {0};
|
|
sll_stack_push(md_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 = md_os_lnx_safe_call_sig_handler;
|
|
int signals_to_handle[] = {
|
|
SIGILL, SIGFPE, SIGSEGV, SIGBUS, SIGTRAP,
|
|
};
|
|
struct sigaction og_act[md_array_count(signals_to_handle)] = {0};
|
|
|
|
// rjf: attach handler info for all signals
|
|
for(MD_U32 i = 0; i < md_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 (MD_U32 i = 0; i < md_array_count(signals_to_handle); i += 1) {
|
|
sigaction(signals_to_handle[i], &og_act[i], 0);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: @md_os_hooks GUIDs (Implemented Per-OS)
|
|
|
|
MD_OS_Guid
|
|
md_os_make_guid(void)
|
|
{
|
|
MD_U8 random_bytes[16] = {0};
|
|
md_static_assert(sizeof(random_bytes) == sizeof(MD_OS_Guid), md_os_lnx_guid_size_check);
|
|
getrandom(random_bytes, sizeof(random_bytes), 0);
|
|
|
|
MD_OS_Guid guid = {0};
|
|
md_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: @md_os_hooks Entry Points (Implemented Per-OS)
|
|
|
|
#if MD_BUILD_ENTRY_DEFINING_UNIT || 1
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
//- rjf: set up OS layer
|
|
{
|
|
//- rjf: get statically-allocated system/process info
|
|
{
|
|
MD_OS_SystemInfo *info = &md_os_lnx_state.system_info;
|
|
info->logical_processor_count = (MD_U32)get_nprocs();
|
|
info->page_size = (MD_U64)getpagesize();
|
|
info->large_page_size = MD_MB(2);
|
|
info->allocation_granularity = info->page_size;
|
|
}
|
|
{
|
|
MD_OS_ProcessInfo *info = &md_os_lnx_state.process_info;
|
|
info->pid = (MD_U32)getpid();
|
|
}
|
|
|
|
//- rjf: set up thread context
|
|
md_local_persist MD_TCTX md_tctx;
|
|
md_tctx_init_and_equip(&md_tctx);
|
|
|
|
//- rjf: set up dynamically allocated state
|
|
md_os_lnx_state.arena = md_arena_alloc();
|
|
md_os_lnx_state.entity_arena = md_arena_alloc();
|
|
pthread_mutex_init(&md_os_lnx_state.entity_mutex, 0);
|
|
|
|
//- rjf: grab dynamically allocated system info
|
|
{
|
|
MD_TempArena scratch = md_scratch_begin(0, 0);
|
|
MD_OS_SystemInfo* info = &md_os_lnx_state.system_info;
|
|
|
|
// rjf: get machine name
|
|
MD_B32 got_final_result = 0;
|
|
MD_U8* buffer = 0;
|
|
int size = 0;
|
|
for (MD_S64 cap = 4096, r = 0; r < 4; cap *= 2, r += 1)
|
|
{
|
|
scratch_end(scratch);
|
|
buffer = md_push_array__no_zero(scratch.arena, MD_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 = md_push_array__no_zero(md_os_lnx_state.arena, MD_U8, info->machine_name.size + 1);
|
|
memory_copy(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
|
|
{
|
|
MD_TempArena scratch = md_scratch_begin(0, 0);
|
|
MD_OS_ProcessInfo* info = &md_os_lnx_state.process_info;
|
|
|
|
// rjf: grab binary path
|
|
{
|
|
// rjf: get self string
|
|
MD_B32 got_final_result = 0;
|
|
MD_U8* buffer = 0;
|
|
int size = 0;
|
|
for (MD_S64 cap = PATH_MAX, r = 0; r < 4; cap *= 2, r += 1)
|
|
{
|
|
scratch_end(scratch);
|
|
buffer = md_push_array__no_zero(scratch.arena, MD_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)
|
|
{
|
|
MD_String8 full_name = md_str8(buffer, size);
|
|
MD_String8 name_chopped = md_str8_chop_last_slash(full_name);
|
|
info->binary_path = md_push_str8_copy(md_os_lnx_state.arena, name_chopped);
|
|
}
|
|
}
|
|
|
|
// rjf: grab initial directory
|
|
{
|
|
info->initial_path = md_os_get_current_path(md_os_lnx_state.arena);
|
|
}
|
|
|
|
// rjf: grab home directory
|
|
{
|
|
char *home = getenv("HOME");
|
|
info->user_program_data_path = md_str8_cstring(home);
|
|
}
|
|
|
|
scratch_end(scratch);
|
|
}
|
|
}
|
|
|
|
//- rjf: call into "real" entry point
|
|
md_main_thread_base_entry_point(md_entry_point, argv, (MD_U64)argc);
|
|
}
|
|
|
|
// MD_BUILD_ENTRY_POINT
|
|
#endif
|