#include "os.h" #include "core/core_string16.h" #include "core/core_platform.h" #include "core/core_log.h" /////////////////////////////////// #if PLATFORM_WINDOWS #define NOMINMAX #define WIN32_LEAN_AND_MEAN #include #include #include #include /////////////////////////////// fn os_date_t os_local_time(void) { SYSTEMTIME lt; GetLocalTime(<); os_date_t result = {0}; result.ms = lt.wMilliseconds; result.sec = lt.wSecond; result.min = lt.wMinute; result.hour = lt.wHour; result.day = lt.wDay; result.month = lt.wMonth; result.year = lt.wYear; return result; } fn os_date_t os_universal_time(void) { SYSTEMTIME lt; GetSystemTime(<); os_date_t result = {0}; result.ms = lt.wMilliseconds; result.sec = lt.wSecond; result.min = lt.wMinute; result.hour = lt.wHour; result.day = lt.wDay; result.month = lt.wMonth; result.year = lt.wYear; return result; } static int64_t win32_counts_per_second; fn f64 os_seconds(void) { if (win32_counts_per_second == 0) { LARGE_INTEGER freq; QueryPerformanceFrequency(&freq); win32_counts_per_second = freq.QuadPart; } LARGE_INTEGER time; QueryPerformanceCounter(&time); f64 result = (f64)time.QuadPart / (f64)win32_counts_per_second; return result; } // @todo: revise these conversions fn f64 os_get_microseconds(void) { f64 secs = os_seconds(); f64 result = secs * 1000000.0; return result; } fn f64 os_milliseconds(void) { f64 secs = os_seconds(); f64 result = secs * 1000.0; return result; } fn void os_sleep(u32 milliseconds) { Sleep(milliseconds); } /////////////////////////////// // files fn b32 os_copy(s8_t from, s8_t to, b32 overwrite) { BOOL fail_if_exists = true; if (overwrite) fail_if_exists = false; ma_temp_t scratch = ma_begin_scratch(); s16_t from16 = s16_from_s8(scratch.arena, from); s16_t to16 = s16_from_s8(scratch.arena, to); BOOL success = CopyFileW(from16.str, to16.str, fail_if_exists); ma_end_scratch(scratch); return success ? true : false; } fn b32 os_delete(s8_t path) { ma_temp_t scratch = ma_begin_scratch(); s16_t path16 = s16_from_s8(scratch.arena, path); BOOL success = DeleteFileW(path16.str); ma_end_scratch(scratch); return success ? true : false; } fn os_mkdir_t os_mkdir(s8_t path) { os_mkdir_t result = os_mkdir_success; ma_temp_t scratch = ma_begin_scratch(); s16_t path16 = s16_from_s8(scratch.arena, path); BOOL success = CreateDirectoryW(path16.str, NULL); if (success == 0) { DWORD error = GetLastError(); if (error == ERROR_ALREADY_EXISTS) result = os_mkdir_file_exists; else if (error == ERROR_PATH_NOT_FOUND) result = os_mkdir_path_not_found; else result = os_mkdir_other_error; } ma_end_scratch(scratch); return result; } fn os_write_t os_write(s8_t path, s8_t content) { os_write_t result = os_write_other_error; ma_temp_t scratch = ma_begin_scratch(); s16_t path16 = s16_from_s8(scratch.arena, path); HANDLE handle = CreateFileW(path16.str, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (handle != INVALID_HANDLE_VALUE) { assert_expr(content.len == (DWORD)content.len); DWORD bytes_written = 0; BOOL error = WriteFile(handle, content.str, (DWORD)content.len, &bytes_written, NULL); if (error == TRUE) { if (bytes_written == content.len) { result = os_write_success; } } CloseHandle(handle); } else result = os_write_path_not_found; ma_end_scratch(scratch); return result; } // @todo: improve to return error codes fn s8_t os_read(ma_arena_t *arena, s8_t path) { s8_t result = {0}; ma_temp_t scratch = ma_begin_scratch1(arena); s16_t path16 = s16_from_s8(scratch.arena, path); HANDLE handle = CreateFileW(path16.str, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (handle != INVALID_HANDLE_VALUE) { LARGE_INTEGER file_size; if (GetFileSizeEx(handle, &file_size)) { if (file_size.QuadPart != 0) { result.len = file_size.QuadPart; result.str = ma_push_array(arena, char, result.len + 1); assert(result.len == (i64)(DWORD)result.len); DWORD read; if (ReadFile(handle, result.str, (DWORD)result.len, &read, NULL)) { // @todo: can only read 32 byte size files? if (read == result.len) { result.str[result.len] = 0; } } } } CloseHandle(handle); } ma_end_scratch(scratch); return result; } typedef struct w32_file_iter_t w32_file_iter_t; struct w32_file_iter_t { HANDLE handle; WIN32_FIND_DATAW data; }; fn void os_advance(os_iter_t *it) { while (FindNextFileW(it->w32->handle, &it->w32->data) != 0) { WIN32_FIND_DATAW *data = &it->w32->data; // Skip '.' and '..' if (data->cFileName[0] == '.' && data->cFileName[1] == '.' && data->cFileName[2] == 0) continue; if (data->cFileName[0] == '.' && data->cFileName[1] == 0) continue; it->is_directory = data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; it->name = s8_from_s16(it->arena, s16_make(data->cFileName, str16_len(data->cFileName))); char *separator = it->path.str[it->path.len - 1] == '/' ? "" : "/"; it->rel = s8_printf(it->arena, "%S%s%S", it->path, separator, it->name); it->abs = os_abs(it->arena, it->rel); it->is_valid = true; s8_normalize_path_unsafe(it->rel); s8_normalize_path_unsafe(it->abs); return; } it->is_valid = false; DWORD error = GetLastError(); assert(error == ERROR_NO_MORE_FILES); FindClose(it->w32->handle); } fn os_iter_t *os_iter(ma_arena_t *arena, s8_t path) { os_iter_t *it = ma_push_type(arena, os_iter_t); it->w32 = ma_push_type(arena, w32_file_iter_t); it->arena = arena; it->path = path; it->is_valid = true; ma_temp_t scratch = ma_begin_scratch1(arena); s8_t mod_path = s8_printf(scratch.arena, "%S\\*", path); s16_t mod_path16 = s16_from_s8(scratch.arena, mod_path); it->w32->handle = FindFirstFileW(mod_path16.str, &it->w32->data); if (it->w32->handle == INVALID_HANDLE_VALUE) { it->is_valid = false; } if (it->is_valid) { assert(it->w32->data.cFileName[0] == '.' && it->w32->data.cFileName[1] == 0); os_advance(it); } ma_end_scratch(scratch); return it; } fn i64 os_mod_time(s8_t file) { WIN32_FIND_DATAW data; ma_temp_t scratch = ma_begin_scratch(); s16_t wpath = s16_from_s8(scratch.arena, file); HANDLE handle = FindFirstFileW(wpath.str, &data); i64 result = -1; if (handle != INVALID_HANDLE_VALUE) { FindClose(handle); FILETIME time = data.ftLastWriteTime; result = (i64)time.dwHighDateTime << 32 | time.dwLowDateTime; } ma_end_scratch(scratch); return result; } fn s8_t os_abs(ma_arena_t *arena, s8_t rel) { const int buffer_size = 2048; ma_temp_t scratch = ma_begin_scratch1(arena); s16_t rel16 = s16_from_s8(scratch.arena, rel); wchar_t *buffer = ma_push_array(scratch.arena, wchar_t, buffer_size); DWORD written = GetFullPathNameW(rel16.str, buffer_size, buffer, 0); assert(written != 0); assert((i64)written < (i64)buffer_size); s8_t result = s8_from_s16(arena, s16_make(buffer, written)); ma_end_scratch(scratch); return result; } fn s8_t os_exe(ma_arena_t *arena) { const int buffer_size = 2048; ma_temp_t scratch = ma_begin_scratch1(arena); wchar_t *buffer = ma_push_array(scratch.arena, wchar_t, buffer_size); DWORD wsize = GetModuleFileNameW(0, buffer, buffer_size); assert(wsize != 0); s8_t result = s8_from_s16(arena, s16_make(buffer, wsize)); s8_normalize_path_unsafe(result); ma_end_scratch(scratch); return result; } fn s8_t os_exe_dir(ma_arena_t *arena) { ma_temp_t scratch = ma_begin_scratch1(arena); s8_t exe = os_exe(scratch.arena); s8_t path = s8_chop_last_slash(exe); s8_t result = s8_printf(arena, "%S", path); ma_end_scratch(scratch); return result; } fn b32 os_is_dir(s8_t path) { ma_temp_t scratch = ma_begin_scratch(); s16_t path16 = s16_from_s8(scratch.arena, path); DWORD dwAttrib = GetFileAttributesW(path16.str); ma_end_scratch(scratch); return dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY); } fn b32 os_is_file(s8_t path) { ma_temp_t scratch = ma_begin_scratch(); s16_t path16 = s16_from_s8(scratch.arena, path); DWORD dwAttrib = GetFileAttributesW(path16.str); ma_end_scratch(scratch); b32 is_file = (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) == 0; return dwAttrib != INVALID_FILE_ATTRIBUTES && is_file; } fn b32 os_is_abs(s8_t path) { b32 result = path.len > 3 && char_is_alphabetic(path.str[0]) && path.str[1] == ':' && path.str[2] == '/'; return result; } fn b32 os_exists(s8_t path) { ma_temp_t scratch = ma_begin_scratch(); s16_t path16 = s16_from_s8(scratch.arena, path); DWORD attribs = GetFileAttributesW(path16.str); ma_end_scratch(scratch); return attribs == INVALID_FILE_ATTRIBUTES ? false : true; } fn s8_t os_cwd(ma_arena_t *arena) { ma_temp_t scratch = ma_begin_scratch1(arena); const u32 buffer_size = 1024; wchar_t *buffer = ma_push_array(scratch.arena, wchar_t, buffer_size); DWORD wsize = GetCurrentDirectoryW(buffer_size, buffer); assert(wsize != 0); assert(wsize <= buffer_size); s8_t path = s8_from_s16(scratch.arena, s16_make(buffer, wsize)); s8_normalize_path_unsafe(path); s8_t result = s8_printf(arena, "%S", path); ma_end_scratch(scratch); return result; } fn void os_set_cwd(s8_t path) { ma_temp_t scratch = ma_begin_scratch(); s16_t path16 = s16_from_s8(scratch.arena, path); SetCurrentDirectoryW(path16.str); ma_end_scratch(scratch); } /////////////////////////////// // threading typedef struct w32_thread_t w32_thread_t; struct w32_thread_t { HANDLE handle; DWORD id; }; fn os_thread_t os_thread_create(ma_arena_t *arena, os_thread_fn_t *func, void *user_data) { assert(func); LPSECURITY_ATTRIBUTES security_attribs = NULL; SIZE_T stack_size = 0; DWORD creation_flags = 0; DWORD thread_id = 0; HANDLE thread_handle = CreateThread(security_attribs, stack_size, (LPTHREAD_START_ROUTINE)func, user_data, creation_flags, &thread_id); if (thread_handle == NULL) { debugf("%s.CreatedThread failed: %d", __FUNCTION__, GetLastError()); return (os_thread_t){.exited = true}; } os_thread_t result = {0}; result.w32 = ma_push_type(arena, w32_thread_t); result.w32->handle = thread_handle; result.w32->id = thread_id; return result; } fn i32 os_thread_join(os_thread_t *thread) { DWORD return_value_indicates_event = WaitForSingleObject(thread->w32->handle, INFINITE); if (return_value_indicates_event != WAIT_OBJECT_0) { debugf("%s.WaitForSingleObject failed: %d", __FUNCTION__, GetLastError()); } DWORD exit_code = 0; BOOL if_fails_then_zero = GetExitCodeThread(thread->w32->handle, &exit_code); if (if_fails_then_zero == 0) { debugf("%s.GetExitCodeThread failed: %d", __FUNCTION__, GetLastError()); } else { thread->exit_code = exit_code; } CloseHandle(thread->w32->handle); thread->exited = true; return thread->exit_code; } // @todo: // This will probably create 16 arenas or more XD // fn i32 os__thread_log(void *data) { // os_core_init(); // debugf("testing"); // return 0; // } // fn void os_test_threads(void) { // os_thread_t threads[16]; // ma_temp_t scratch = ma_begin_scratch(); // for (int i = 0; i < lengthof(threads); i += 1) { // threads[i] = os_thread_create(scratch.arena, os__thread_log, NULL); // } // for (int i = 0; i < lengthof(threads); i += 1) { // os_thread_join(&threads[i]); // } // ma_end_scratch(scratch); // } fn s8_t os_appdata(ma_arena_t *arena, s8_t name) { assert(name.len != 0); assert(name.str); assert(arena); wchar_t *out_path = NULL; HRESULT hr = SHGetKnownFolderPath(&FOLDERID_RoamingAppData, KF_FLAG_CREATE, NULL, &out_path); if (!SUCCEEDED(hr)) { debugf("%s.SHGetKnownFolderPath failed with hr: %d", __FUNCTION__, hr); return s8_null; } s16_t appdata_path = s16_from_str16((u16 *)out_path); ma_temp_t scratch = ma_begin_scratch1(arena); s8_t tmp = s8_from_s16(scratch.arena, appdata_path); s8_normalize_path_unsafe(tmp); s8_t path = s8_printf(arena, "%S/%S", tmp, name); os_mkdir_t mkdir_status = os_mkdir(path); if (mkdir_status != os_mkdir_success && mkdir_status != os_mkdir_file_exists) { debugf("%s.os_mkdir failed with status: %d, for: %S", __FUNCTION__, mkdir_status, path); path = s8_null; } CoTaskMemFree(out_path); ma_end_scratch(scratch); return path; } /////////////////////////////////// #elif PLATFORM_POSIX /////////////////////////////////// #include #include #include #include fn os_date_t os_local_time(void) { os_date_t result = {0}; time_t t = time(NULL); struct tm *lt = localtime(&t); result.sec = lt->tm_sec; result.min = lt->tm_min; result.hour = lt->tm_hour; result.day = lt->tm_mday; result.month = lt->tm_mon; result.year = lt->tm_year; return result; } fn os_date_t os_universal_time(void) { os_date_t result = {0}; time_t t = time(NULL); struct tm u = {0}; gmtime_r(&t, &u); result.sec = u.tm_sec; result.min = u.tm_min; result.hour = u.tm_hour; result.day = u.tm_mday; result.month = u.tm_mon; result.year = u.tm_year; return result; } fn f64 os_get_microseconds(void) { struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); u64 result = t.tv_sec*million(1) + (t.tv_nsec/thousand(1)); return (f64)result; } fn f64 os_get_milliseconds(void) { u64 micros = os_get_microseconds(); f64 result = (f64)micros / 1000.0; return result; } fn s8_t os_exe(ma_arena_t *arena) { const i32 buffer_size = 2048; ma_temp_t temp = ma_begin_scratch1(arena); char *buffer = ma_push_array(arena, char, buffer_size); ssize_t size = readlink("/proc/self/exe", buffer, buffer_size); assert(size < buffer_size); // @todo:? assert(size != -1); s8_t string = s8_copy(arena, s8_make(buffer, size)); ma_end_scratch(temp); return string; } fn s8_t os_exe_dir(ma_arena_t *arena) { ma_temp_t temp = ma_begin_scratch1(arena); s8_t exe = os_exe(temp.arena); s8_t dir = s8_chop_last_slash(exe); s8_t result = s8_copy(arena, dir); ma_end_scratch(temp); return result; } fn s8_t os_cwd(ma_arena_t *arena) { const i32 buffer_size = 2048; ma_temp_t temp = ma_begin_scratch1(arena); char *buffer = ma_push_array(arena, char, buffer_size); char *ok = getcwd(buffer, buffer_size); assert(ok); s8_t result = s8_copy(arena, s8_from_char(buffer)); ma_end_scratch(temp); return result; } fn void os_set_cwd(s8_t path) { ma_temp_t temp = ma_begin_scratch(); chdir(s8_copy(temp.arena, path).str); ma_end_scratch(temp); } fn b32 os_exists(s8_t path) { ma_temp_t temp = ma_begin_scratch(); s8_t copy = s8_copy(temp.arena, path); int ok = access(copy.str, F_OK); ma_end_scratch(temp); return ok == 0 ? true : false; } fn b32 os_is_dir(s8_t path) { b32 result = false; ma_temp_t temp = ma_begin_scratch(); s8_t copy = s8_copy(temp.arena, path); DIR *dir = opendir(copy.str); if (dir) { result = true; closedir(dir); } ma_end_scratch(temp); return result; } fn b32 os_is_file(s8_t path) { ma_temp_t temp = ma_begin_scratch(); s8_t copy = s8_copy(temp.arena, path); struct stat path_stat; stat(copy.str, &path_stat); ma_end_scratch(temp); b32 result = S_ISREG(path_stat.st_mode); return result; } fn b32 os_is_abs(s8_t path) { b32 result = path.len && path.str[0] == '/'; return result; } fn s8_t os_abs(ma_arena_t *arena, s8_t path) { ma_temp_t temp = ma_begin_scratch1(arena); s8_t null_term_path = s8_copy(temp.arena, path); char buffer[PATH_MAX]; char *resolved_path = realpath(null_term_path.str, buffer); s8_t result = s8_copy(arena, s8_from_char(resolved_path)); ma_end_scratch(temp); return result; } fn s8_t os_read(ma_arena_t *arena, s8_t path) { s8_t result = {0}; ma_temp_t temp = ma_begin_scratch1(arena); s8_t copy_path = s8_copy(temp.arena, path); FILE *fd = fopen(copy_path.str, "rb"); if (fd) { fseek(fd, 0, SEEK_END); long size = ftell(fd); fseek(fd, 0, SEEK_SET); char *string = ma_push_array(arena, char, size + 1); fread(string, size, 1, fd); fclose(fd); result = s8_make(string, size); result.str[result.len] = 0; } ma_end_scratch(temp); return result; } fn os_write_t os_write(s8_t path, s8_t content) { os_write_t result = os_write_path_not_found; ma_temp_t scratch = ma_begin_scratch(); s8_t copy_path = s8_copy(scratch.arena, path); FILE *fd = fopen(copy_path.str, "w"); if (fd) { size_t size_written = fwrite(content.str, 1, content.len, fd); assert((int64_t)size_written == content.len); fclose(fd); result = os_write_success; } ma_end_scratch(scratch); return result; } fn b32 os_copy(s8_t src_path, s8_t dst_path, b32 overwrite) { if (overwrite == false && os_exists(dst_path)) { return 0; } ma_temp_t temp = ma_begin_scratch(); s8_t src_data = os_read(temp.arena, src_path); os_write_t write_result = os_write(dst_path, src_data); b32 result = write_result == os_write_success; ma_end_scratch(temp); return result; } fn b32 os_delete(s8_t path) { ma_temp_t scratch = ma_begin_scratch(); s8_t copy_path = s8_copy(scratch.arena, path); int rc = unlink(copy_path.str); ma_end_scratch(scratch); return (b32)(rc == 0); } fn void os_advance(os_iter_t *it) { struct dirent *file = NULL; while ((file = readdir((DIR *)it->unix_dir)) != NULL) { if (file->d_name[0] == '.' && file->d_name[1] == '.' && file->d_name[2] == 0) { continue; } if (file->d_name[0] == '.' && file->d_name[1] == 0) { continue; } it->is_directory = file->d_type == DT_DIR; it->name = s8_copy(it->arena, s8_from_char(file->d_name)); const char *separator = it->path.str[it->path.len - 1] == '/' ? "" : "/"; it->rel = s8_printf(it->arena, "%S%s%s", it->path, separator, file->d_name); it->abs = os_abs(it->arena, it->rel); it->is_valid = true; return; } it->is_valid = false; closedir((DIR *)it->unix_dir); } fn os_iter_t *os_iter(ma_arena_t *arena, s8_t path) { os_iter_t *it = ma_push_type(arena, os_iter_t); it->arena = arena; it->path = s8_copy(arena, path); it->unix_dir = opendir(s8_copy(arena, it->path).str); if (it->unix_dir) { it->is_valid = true; os_advance(it); } return it; } /////////////////////////////////// #endif // PLATFORM_POSIX /////////////////////////////////// fn s8_t os_format_date(ma_arena_t *arena, os_date_t date) { s8_t result = s8_printf(arena, "%04u-%02u-%02u %02u:%02u:%02u.%03u", date.year, date.month, date.day, date.hour, date.min, date.sec, date.ms); return result; } #if PLATFORM_WINDOWS || PLATFORM_POSIX fn int os_systemf(const char *string, ...) { ma_temp_t scratch = ma_begin_scratch(); S8_FMT(scratch.arena, string, result); int error_code = system(result.str); ma_end_scratch(scratch); return error_code; } #endif