533 lines
12 KiB
C
533 lines
12 KiB
C
/**
|
|
* @file os_api.c
|
|
* @brief Operating system abstraction API.
|
|
*
|
|
* The OS API implements an overridable interface for implementing functions
|
|
* that are operating system specific, in addition to a number of hooks which
|
|
* allow for customization by the user, like logging.
|
|
*/
|
|
|
|
#include "private_api.h"
|
|
#include <ctype.h>
|
|
#include <time.h>
|
|
|
|
void ecs_os_api_impl(ecs_os_api_t *api);
|
|
|
|
static bool ecs_os_api_initialized = false;
|
|
static bool ecs_os_api_initializing = false;
|
|
static int ecs_os_api_init_count = 0;
|
|
|
|
ecs_os_api_t ecs_os_api = {
|
|
.flags_ = EcsOsApiHighResolutionTimer | EcsOsApiLogWithColors,
|
|
.log_level_ = -1 /* Disable tracing by default, but log warnings/errors */
|
|
};
|
|
|
|
int64_t ecs_os_api_malloc_count = 0;
|
|
int64_t ecs_os_api_realloc_count = 0;
|
|
int64_t ecs_os_api_calloc_count = 0;
|
|
int64_t ecs_os_api_free_count = 0;
|
|
|
|
void ecs_os_set_api(
|
|
ecs_os_api_t *os_api)
|
|
{
|
|
if (!ecs_os_api_initialized) {
|
|
ecs_os_api = *os_api;
|
|
ecs_os_api_initialized = true;
|
|
}
|
|
}
|
|
|
|
ecs_os_api_t ecs_os_get_api(void) {
|
|
return ecs_os_api;
|
|
}
|
|
|
|
void ecs_os_init(void)
|
|
{
|
|
if (!ecs_os_api_initialized) {
|
|
ecs_os_set_api_defaults();
|
|
}
|
|
|
|
if (!(ecs_os_api_init_count ++)) {
|
|
if (ecs_os_api.init_) {
|
|
ecs_os_api.init_();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ecs_os_fini(void) {
|
|
if (!--ecs_os_api_init_count) {
|
|
if (ecs_os_api.fini_) {
|
|
ecs_os_api.fini_();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Assume every non-glibc Linux target has no execinfo.
|
|
This mainly fixes musl support, as musl doesn't define any preprocessor macro specifying its presence. */
|
|
#if defined(ECS_TARGET_LINUX) && !defined(__GLIBC__)
|
|
#define HAVE_EXECINFO 0
|
|
#elif !defined(ECS_TARGET_WINDOWS) && !defined(ECS_TARGET_EM) && !defined(ECS_TARGET_ANDROID)
|
|
#define HAVE_EXECINFO 1
|
|
#else
|
|
#define HAVE_EXECINFO 0
|
|
#endif
|
|
|
|
#if HAVE_EXECINFO
|
|
#include <execinfo.h>
|
|
#define ECS_BT_BUF_SIZE 100
|
|
|
|
void flecs_dump_backtrace(
|
|
void *stream)
|
|
{
|
|
int nptrs;
|
|
void *buffer[ECS_BT_BUF_SIZE];
|
|
char **strings;
|
|
|
|
nptrs = backtrace(buffer, ECS_BT_BUF_SIZE);
|
|
|
|
strings = backtrace_symbols(buffer, nptrs);
|
|
if (strings == NULL) {
|
|
return;
|
|
}
|
|
|
|
for (int j = 1; j < nptrs; j++) {
|
|
fprintf(stream, "%s\n", strings[j]);
|
|
}
|
|
|
|
free(strings);
|
|
}
|
|
#else
|
|
void flecs_dump_backtrace(
|
|
void *stream)
|
|
{
|
|
(void)stream;
|
|
}
|
|
#endif
|
|
#undef HAVE_EXECINFO_H
|
|
|
|
static
|
|
void flecs_log_msg(
|
|
int32_t level,
|
|
const char *file,
|
|
int32_t line,
|
|
const char *msg)
|
|
{
|
|
FILE *stream;
|
|
if (level >= 0) {
|
|
stream = stdout;
|
|
} else {
|
|
stream = stderr;
|
|
}
|
|
|
|
bool use_colors = ecs_os_api.flags_ & EcsOsApiLogWithColors;
|
|
bool timestamp = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp;
|
|
bool deltatime = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta;
|
|
|
|
time_t now = 0;
|
|
|
|
if (deltatime) {
|
|
now = time(NULL);
|
|
int64_t delta = 0;
|
|
if (ecs_os_api.log_last_timestamp_) {
|
|
delta = now - ecs_os_api.log_last_timestamp_;
|
|
}
|
|
ecs_os_api.log_last_timestamp_ = (int64_t)now;
|
|
|
|
if (delta) {
|
|
if (delta < 10) {
|
|
fputs(" ", stream);
|
|
}
|
|
if (delta < 100) {
|
|
fputs(" ", stream);
|
|
}
|
|
char time_buf[20];
|
|
ecs_os_sprintf(time_buf, "%u", (uint32_t)delta);
|
|
fputs("+", stream);
|
|
fputs(time_buf, stream);
|
|
fputs(" ", stream);
|
|
} else {
|
|
fputs(" ", stream);
|
|
}
|
|
}
|
|
|
|
if (timestamp) {
|
|
if (!now) {
|
|
now = time(NULL);
|
|
}
|
|
char time_buf[20];
|
|
ecs_os_sprintf(time_buf, "%u", (uint32_t)now);
|
|
fputs(time_buf, stream);
|
|
fputs(" ", stream);
|
|
}
|
|
|
|
if (level >= 4) {
|
|
if (use_colors) fputs(ECS_NORMAL, stream);
|
|
fputs("jrnl", stream);
|
|
} else if (level >= 0) {
|
|
if (level == 0) {
|
|
if (use_colors) fputs(ECS_MAGENTA, stream);
|
|
} else {
|
|
if (use_colors) fputs(ECS_GREY, stream);
|
|
}
|
|
fputs("info", stream);
|
|
} else if (level == -2) {
|
|
if (use_colors) fputs(ECS_YELLOW, stream);
|
|
fputs("warning", stream);
|
|
} else if (level == -3) {
|
|
if (use_colors) fputs(ECS_RED, stream);
|
|
fputs("error", stream);
|
|
} else if (level == -4) {
|
|
if (use_colors) fputs(ECS_RED, stream);
|
|
fputs("fatal", stream);
|
|
}
|
|
|
|
if (use_colors) fputs(ECS_NORMAL, stream);
|
|
fputs(": ", stream);
|
|
|
|
if (level >= 0) {
|
|
if (ecs_os_api.log_indent_) {
|
|
char indent[32];
|
|
int i, indent_count = ecs_os_api.log_indent_;
|
|
if (indent_count > 15) indent_count = 15;
|
|
|
|
for (i = 0; i < indent_count; i ++) {
|
|
indent[i * 2] = '|';
|
|
indent[i * 2 + 1] = ' ';
|
|
}
|
|
|
|
if (ecs_os_api.log_indent_ != indent_count) {
|
|
indent[i * 2 - 2] = '+';
|
|
}
|
|
|
|
indent[i * 2] = '\0';
|
|
|
|
fputs(indent, stream);
|
|
}
|
|
}
|
|
|
|
if (level < 0) {
|
|
if (file) {
|
|
const char *file_ptr = strrchr(file, '/');
|
|
if (!file_ptr) {
|
|
file_ptr = strrchr(file, '\\');
|
|
}
|
|
|
|
if (file_ptr) {
|
|
file = file_ptr + 1;
|
|
}
|
|
|
|
fputs(file, stream);
|
|
fputs(": ", stream);
|
|
}
|
|
|
|
if (line) {
|
|
fprintf(stream, "%d: ", line);
|
|
}
|
|
}
|
|
|
|
fputs(msg, stream);
|
|
|
|
fputs("\n", stream);
|
|
|
|
if (level == -4) {
|
|
flecs_dump_backtrace(stream);
|
|
}
|
|
}
|
|
|
|
void ecs_os_dbg(
|
|
const char *file,
|
|
int32_t line,
|
|
const char *msg)
|
|
{
|
|
if (ecs_os_api.log_) {
|
|
ecs_os_api.log_(1, file, line, msg);
|
|
}
|
|
}
|
|
|
|
void ecs_os_trace(
|
|
const char *file,
|
|
int32_t line,
|
|
const char *msg)
|
|
{
|
|
if (ecs_os_api.log_) {
|
|
ecs_os_api.log_(0, file, line, msg);
|
|
}
|
|
}
|
|
|
|
void ecs_os_warn(
|
|
const char *file,
|
|
int32_t line,
|
|
const char *msg)
|
|
{
|
|
if (ecs_os_api.log_) {
|
|
ecs_os_api.log_(-2, file, line, msg);
|
|
}
|
|
}
|
|
|
|
void ecs_os_err(
|
|
const char *file,
|
|
int32_t line,
|
|
const char *msg)
|
|
{
|
|
if (ecs_os_api.log_) {
|
|
ecs_os_api.log_(-3, file, line, msg);
|
|
}
|
|
}
|
|
|
|
void ecs_os_fatal(
|
|
const char *file,
|
|
int32_t line,
|
|
const char *msg)
|
|
{
|
|
if (ecs_os_api.log_) {
|
|
ecs_os_api.log_(-4, file, line, msg);
|
|
}
|
|
}
|
|
|
|
static
|
|
void ecs_os_gettime(ecs_time_t *time) {
|
|
ecs_assert(ecs_os_has_time() == true, ECS_MISSING_OS_API, NULL);
|
|
|
|
uint64_t now = ecs_os_now();
|
|
uint64_t sec = now / 1000000000;
|
|
|
|
assert(sec < UINT32_MAX);
|
|
assert((now - sec * 1000000000) < UINT32_MAX);
|
|
|
|
time->sec = (uint32_t)sec;
|
|
time->nanosec = (uint32_t)(now - sec * 1000000000);
|
|
}
|
|
|
|
static
|
|
void* ecs_os_api_malloc(ecs_size_t size) {
|
|
ecs_os_linc(&ecs_os_api_malloc_count);
|
|
ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL);
|
|
return malloc((size_t)size);
|
|
}
|
|
|
|
static
|
|
void* ecs_os_api_calloc(ecs_size_t size) {
|
|
ecs_os_linc(&ecs_os_api_calloc_count);
|
|
ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL);
|
|
return calloc(1, (size_t)size);
|
|
}
|
|
|
|
static
|
|
void* ecs_os_api_realloc(void *ptr, ecs_size_t size) {
|
|
ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL);
|
|
|
|
if (ptr) {
|
|
ecs_os_linc(&ecs_os_api_realloc_count);
|
|
} else {
|
|
/* If not actually reallocing, treat as malloc */
|
|
ecs_os_linc(&ecs_os_api_malloc_count);
|
|
}
|
|
|
|
return realloc(ptr, (size_t)size);
|
|
}
|
|
|
|
static
|
|
void ecs_os_api_free(void *ptr) {
|
|
if (ptr) {
|
|
ecs_os_linc(&ecs_os_api_free_count);
|
|
}
|
|
free(ptr);
|
|
}
|
|
|
|
static
|
|
char* ecs_os_api_strdup(const char *str) {
|
|
if (str) {
|
|
int len = ecs_os_strlen(str);
|
|
char *result = ecs_os_malloc(len + 1);
|
|
ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL);
|
|
ecs_os_strcpy(result, str);
|
|
return result;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
void ecs_os_strset(char **str, const char *value) {
|
|
char *old = str[0];
|
|
str[0] = ecs_os_strdup(value);
|
|
ecs_os_free(old);
|
|
}
|
|
|
|
/* Replace dots with underscores */
|
|
static
|
|
char *module_file_base(const char *module, char sep) {
|
|
char *base = ecs_os_strdup(module);
|
|
ecs_size_t i, len = ecs_os_strlen(base);
|
|
for (i = 0; i < len; i ++) {
|
|
if (base[i] == '.') {
|
|
base[i] = sep;
|
|
}
|
|
}
|
|
|
|
return base;
|
|
}
|
|
|
|
static
|
|
char* ecs_os_api_module_to_dl(const char *module) {
|
|
ecs_strbuf_t lib = ECS_STRBUF_INIT;
|
|
|
|
/* Best guess, use module name with underscores + OS library extension */
|
|
char *file_base = module_file_base(module, '_');
|
|
|
|
# if defined(ECS_TARGET_LINUX) || defined(ECS_TARGET_FREEBSD)
|
|
ecs_strbuf_appendlit(&lib, "lib");
|
|
ecs_strbuf_appendstr(&lib, file_base);
|
|
ecs_strbuf_appendlit(&lib, ".so");
|
|
# elif defined(ECS_TARGET_DARWIN)
|
|
ecs_strbuf_appendlit(&lib, "lib");
|
|
ecs_strbuf_appendstr(&lib, file_base);
|
|
ecs_strbuf_appendlit(&lib, ".dylib");
|
|
# elif defined(ECS_TARGET_WINDOWS)
|
|
ecs_strbuf_appendstr(&lib, file_base);
|
|
ecs_strbuf_appendlit(&lib, ".dll");
|
|
# endif
|
|
|
|
ecs_os_free(file_base);
|
|
|
|
return ecs_strbuf_get(&lib);
|
|
}
|
|
|
|
static
|
|
char* ecs_os_api_module_to_etc(const char *module) {
|
|
ecs_strbuf_t lib = ECS_STRBUF_INIT;
|
|
|
|
/* Best guess, use module name with dashes + /etc */
|
|
char *file_base = module_file_base(module, '-');
|
|
|
|
ecs_strbuf_appendstr(&lib, file_base);
|
|
ecs_strbuf_appendlit(&lib, "/etc");
|
|
|
|
ecs_os_free(file_base);
|
|
|
|
return ecs_strbuf_get(&lib);
|
|
}
|
|
|
|
void ecs_os_set_api_defaults(void)
|
|
{
|
|
/* Don't overwrite if already initialized */
|
|
if (ecs_os_api_initialized != 0) {
|
|
return;
|
|
}
|
|
|
|
if (ecs_os_api_initializing != 0) {
|
|
return;
|
|
}
|
|
|
|
ecs_os_api_initializing = true;
|
|
|
|
/* Memory management */
|
|
ecs_os_api.malloc_ = ecs_os_api_malloc;
|
|
ecs_os_api.free_ = ecs_os_api_free;
|
|
ecs_os_api.realloc_ = ecs_os_api_realloc;
|
|
ecs_os_api.calloc_ = ecs_os_api_calloc;
|
|
|
|
/* Strings */
|
|
ecs_os_api.strdup_ = ecs_os_api_strdup;
|
|
|
|
/* Time */
|
|
ecs_os_api.get_time_ = ecs_os_gettime;
|
|
|
|
/* Logging */
|
|
ecs_os_api.log_ = flecs_log_msg;
|
|
|
|
/* Modules */
|
|
if (!ecs_os_api.module_to_dl_) {
|
|
ecs_os_api.module_to_dl_ = ecs_os_api_module_to_dl;
|
|
}
|
|
|
|
if (!ecs_os_api.module_to_etc_) {
|
|
ecs_os_api.module_to_etc_ = ecs_os_api_module_to_etc;
|
|
}
|
|
|
|
ecs_os_api.abort_ = abort;
|
|
|
|
# ifdef FLECS_OS_API_IMPL
|
|
/* Initialize defaults to OS API IMPL addon, but still allow for overriding
|
|
* by the application */
|
|
ecs_set_os_api_impl();
|
|
ecs_os_api_initialized = false;
|
|
# endif
|
|
|
|
ecs_os_api_initializing = false;
|
|
}
|
|
|
|
bool ecs_os_has_heap(void) {
|
|
return
|
|
(ecs_os_api.malloc_ != NULL) &&
|
|
(ecs_os_api.calloc_ != NULL) &&
|
|
(ecs_os_api.realloc_ != NULL) &&
|
|
(ecs_os_api.free_ != NULL);
|
|
}
|
|
|
|
bool ecs_os_has_threading(void) {
|
|
return
|
|
(ecs_os_api.mutex_new_ != NULL) &&
|
|
(ecs_os_api.mutex_free_ != NULL) &&
|
|
(ecs_os_api.mutex_lock_ != NULL) &&
|
|
(ecs_os_api.mutex_unlock_ != NULL) &&
|
|
(ecs_os_api.cond_new_ != NULL) &&
|
|
(ecs_os_api.cond_free_ != NULL) &&
|
|
(ecs_os_api.cond_wait_ != NULL) &&
|
|
(ecs_os_api.cond_signal_ != NULL) &&
|
|
(ecs_os_api.cond_broadcast_ != NULL) &&
|
|
(ecs_os_api.thread_new_ != NULL) &&
|
|
(ecs_os_api.thread_join_ != NULL) &&
|
|
(ecs_os_api.thread_self_ != NULL);
|
|
}
|
|
|
|
bool ecs_os_has_task_support(void) {
|
|
return
|
|
(ecs_os_api.mutex_new_ != NULL) &&
|
|
(ecs_os_api.mutex_free_ != NULL) &&
|
|
(ecs_os_api.mutex_lock_ != NULL) &&
|
|
(ecs_os_api.mutex_unlock_ != NULL) &&
|
|
(ecs_os_api.cond_new_ != NULL) &&
|
|
(ecs_os_api.cond_free_ != NULL) &&
|
|
(ecs_os_api.cond_wait_ != NULL) &&
|
|
(ecs_os_api.cond_signal_ != NULL) &&
|
|
(ecs_os_api.cond_broadcast_ != NULL) &&
|
|
(ecs_os_api.task_new_ != NULL) &&
|
|
(ecs_os_api.task_join_ != NULL);
|
|
}
|
|
|
|
bool ecs_os_has_time(void) {
|
|
return
|
|
(ecs_os_api.get_time_ != NULL) &&
|
|
(ecs_os_api.sleep_ != NULL) &&
|
|
(ecs_os_api.now_ != NULL);
|
|
}
|
|
|
|
bool ecs_os_has_logging(void) {
|
|
return (ecs_os_api.log_ != NULL);
|
|
}
|
|
|
|
bool ecs_os_has_dl(void) {
|
|
return
|
|
(ecs_os_api.dlopen_ != NULL) &&
|
|
(ecs_os_api.dlproc_ != NULL) &&
|
|
(ecs_os_api.dlclose_ != NULL);
|
|
}
|
|
|
|
bool ecs_os_has_modules(void) {
|
|
return
|
|
(ecs_os_api.module_to_dl_ != NULL) &&
|
|
(ecs_os_api.module_to_etc_ != NULL);
|
|
}
|
|
|
|
#if defined(ECS_TARGET_WINDOWS)
|
|
static char error_str[255];
|
|
#endif
|
|
|
|
const char* ecs_os_strerror(int err) {
|
|
# if defined(ECS_TARGET_WINDOWS)
|
|
strerror_s(error_str, 255, err);
|
|
return error_str;
|
|
# else
|
|
return strerror(err);
|
|
# endif
|
|
}
|