typedef void OSErrorReport(const char *, ...); #if OS_POSIX #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include API void (*Error)(const char *, ...); struct backtrace_state *backtrace_state = NULL; void BacktraceOnError(void *data, const char *msg, int errnum) { Unused(data); Error("libbacktrace error: %s (errnum: %d)\n", msg, errnum); } int BacktraceOnPrint(void *data, uintptr_t pc, const char *filename, int lineno, const char *function) { Unused(data); Unused(pc); bool printed = false; if (filename != NULL) { char buffer[1024]; char *f = realpath(filename, buffer); printf("%s:%d:1: ", f, lineno); printed = true; } if (function != NULL) { printf("%s", function); printed = true; } if (printed) { printf("\n"); } return 0; } void CrashHandler(int signal, siginfo_t* info, void* context) { Unused(signal); Unused(info); Unused(context); backtrace_full(backtrace_state, 2, BacktraceOnPrint, BacktraceOnError, NULL); exit(1); } void RegisterCrashHandler(void) { backtrace_state = backtrace_create_state(NULL, 1, BacktraceOnError, NULL); struct sigaction sa; sa.sa_sigaction = CrashHandler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_SIGINFO | SA_NODEFER | SA_RESETHAND; // Important flags! sigaction(SIGSEGV, &sa, NULL); // Segmentation fault sigaction(SIGABRT, &sa, NULL); // Abort sigaction(SIGBUS, &sa, NULL); // Bus error sigaction(SIGILL, &sa, NULL); // Illegal instruction sigaction(SIGFPE, &sa, NULL); // Floating point exception sigaction(SIGTRAP, &sa, NULL); // Breakpoint/trap sigaction(SIGSYS, &sa, NULL); // Bad system call sigaction(SIGXCPU, &sa, NULL); // CPU time limit exceeded sigaction(SIGXFSZ, &sa, NULL); // File size limit exceeded sigaction(SIGTERM, &sa, NULL); // Termination request (optional) } API void InitOS(void (*error_proc)(const char *, ...)) { Error = error_proc; RegisterCrashHandler(); } API String ReadFile(Allocator al, String path) { Scratch scratch(al); String null_term = Copy(scratch, path); String result = {}; FILE *f = fopen(null_term.data, "rb"); if (f) { fseek(f, 0, SEEK_END); result.len = ftell(f); fseek(f, 0, SEEK_SET); result.data = (char *)AllocSize(al, result.len + 1); fread(result.data, result.len, 1, f); result.data[result.len] = 0; fclose(f); } return result; } API bool WriteFile(String path, String data) { Scratch scratch; String null_term = Copy(scratch, path); bool result = false; FILE *f = fopen((const char *)null_term.data, "w"); if (f) { size_t written = fwrite(data.data, 1, data.len, f); if (SizeToInt(written) == data.len) { result = true; } fclose(f); } return result; } API bool DeleteFile(String path) { Scratch scratch; String null_term = Copy(scratch, path); int result = unlink(null_term.data); return result == 0; } API MakeDirResult MakeDir(String path) { Scratch scratch; String null_term = Copy(scratch, path); int error = mkdir(null_term.data, 0755); MakeDirResult result = MakeDirResult_Success; if (error != 0) { result = MakeDirResult_ErrorOther; if (errno == EEXIST) result = MakeDirResult_Exists; } return result; } API int64_t GetFileModTime(String path) { Scratch scratch; String null_term = Copy(scratch, path); struct stat attrib = {}; stat(null_term.data, &attrib); struct timespec ts = attrib.st_mtim; int64_t result = (((int64_t)ts.tv_sec) * 1000000ll) + ((int64_t)ts.tv_nsec) / 1000ll; return result; } API String GetAbsolutePath(Allocator al, String path) { Scratch scratch(al); String null_term = Copy(scratch, path); char *buffer = AllocArray(al, char, PATH_MAX); realpath(null_term.data, buffer); String result = buffer; return result; } API bool FileExists(String path) { Scratch scratch; String null_term = Copy(scratch, path); bool result = false; if (access((char *)null_term.data, F_OK) == 0) { result = true; } return result; } API bool IsDir(String path) { Scratch scratch; String null_term = Copy(scratch, path); struct stat s; if (stat(null_term.data, &s) != 0) return false; return S_ISDIR(s.st_mode); } API bool IsFile(String path) { Scratch scratch; String null_term = Copy(scratch, path); struct stat s; if (stat(null_term.data, &s) != 0) return false; return S_ISREG(s.st_mode); } API bool IsAbsolute(String path) { bool result = path.len && path.data[0] == '/'; return result; } API String GetWorkingDir(Allocator al) { char *buffer = AllocArray(al, char, PATH_MAX); char *cwd = getcwd(buffer, PATH_MAX); return cwd; } String GetExePath(Allocator al) { char *buffer = AllocArray(al, char, PATH_MAX); readlink("/proc/self/exe", buffer, PATH_MAX); return buffer; } API String GetExeDir(Allocator al) { Scratch scratch(al); String exe_path = GetExePath(scratch); String dir = ChopLastSlash(exe_path); String result = Copy(al, dir); return result; } API uint64_t GetTimeNanos(void) { struct timespec spec; clock_gettime(CLOCK_MONOTONIC, &spec); uint64_t ts = ((uint64_t)spec.tv_sec * 1000000000ull) + (uint64_t)spec.tv_nsec; return ts; } API double GetTimeMicros(void) { struct timespec spec; clock_gettime(CLOCK_MONOTONIC, &spec); return (((double)spec.tv_sec) * 1000000) + (((double)spec.tv_nsec) / 1000); } API bool IsValid(const FileIter &it) { return it.is_valid; } API void Advance(FileIter *it) { struct dirent *file = NULL; while ((file = readdir((DIR *)it->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->filename = Copy(it->allocator, file->d_name); const char *separator = it->path.data[it->path.len - 1] == '/' ? "" : "/"; it->relative_path = Format(it->allocator, "%S%s%s", it->path, separator, file->d_name); it->absolute_path = GetAbsolutePath(it->allocator, it->relative_path); it->is_valid = true; return; } it->is_valid = false; closedir((DIR *)it->dir); } API FileIter IterateFiles(Allocator alo, String path) { FileIter it = {}; it.allocator = alo; it.path = path; Scratch scratch(alo); String null_term = Copy(scratch, path); it.dir = (void *)opendir((char *)null_term.data); if (it.dir) { Advance(&it); } return it; } #elif OS_WINDOWS #ifndef NOMINMAX #define NOMINMAX #endif #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #undef Yield #include #include API void (*Error)(const char *, ...); API void InitOS(void (*error_proc)(const char *, ...)) { Error = error_proc; SetConsoleOutputCP(65001); SetConsoleCP(65001); } API String ReadFile(Allocator arena, String path) { bool success = false; String result = {}; Scratch scratch(arena); String16 string16 = ToString16(scratch, path); HANDLE handle = CreateFileW((wchar_t *)string16.data, 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 = (int64_t)file_size.QuadPart; result.data = (char *)AllocSize(arena, result.len + 1); DWORD read; if (ReadFile(handle, result.data, (DWORD)result.len, &read, NULL)) { // @todo: can only read 32 byte size files? if (read == result.len) { success = true; result.data[result.len] = 0; } } } } CloseHandle(handle); } if (!success) { Dealloc(arena, result.data); result = {}; } return result; } typedef struct Win32_FileIter { HANDLE handle; WIN32_FIND_DATAW data; } Win32_FileIter; API String GetAbsolutePath(Allocator arena, String relative) { Scratch scratch(arena); String16 wpath = ToString16(scratch, relative); wchar_t *wpath_abs = AllocArray(scratch, wchar_t, 4096); DWORD written = GetFullPathNameW((wchar_t *)wpath.data, 4096, wpath_abs, 0); if (written == 0) return {}; String path = ToString(arena, {(char16_t *)wpath_abs, written}); NormalizePathInPlace(path); return path; } API bool IsValid(const FileIter &it) { return it.is_valid; } API void Advance(FileIter *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->filename = ToString(it->allocator, (char16_t *)data->cFileName, WideLength((char16_t *)data->cFileName)); const char *separator = it->path.data[it->path.len - 1] == '/' ? "" : "/"; it->relative_path = Format(it->allocator, "%S%s%S", it->path, separator, it->filename); it->absolute_path = GetAbsolutePath(it->allocator, it->relative_path); it->is_valid = true; return; } it->is_valid = false; DWORD error = GetLastError(); Assert(error == ERROR_NO_MORE_FILES); FindClose(it->w32->handle); } API FileIter IterateFiles(Allocator alo, String path) { FileIter it = {0}; it.allocator = alo; it.path = path; String modified_path = Format(it.allocator, "%S\\*", path); String16 modified_path16 = ToString16(it.allocator, modified_path); it.w32 = AllocType(it.allocator, Win32_FileIter); it.w32->handle = FindFirstFileW((wchar_t *)modified_path16.data, &it.w32->data); if (it.w32->handle == INVALID_HANDLE_VALUE) { it.is_valid = false; return it; } Advance(&it); return it; } API uint64_t GetTimeNanos(void) { static double invfreq; if (!invfreq) { LARGE_INTEGER frequency; QueryPerformanceFrequency(&frequency); invfreq = 1000000000.0 / frequency.QuadPart; } LARGE_INTEGER counter; QueryPerformanceCounter(&counter); uint64_t ts = (uint64_t)((double)counter.QuadPart * invfreq); return ts; } API double GetTimeMicros(void) { static double invfreq; if (!invfreq) { LARGE_INTEGER frequency; QueryPerformanceFrequency(&frequency); invfreq = 1000000.0 / frequency.QuadPart; } LARGE_INTEGER counter; QueryPerformanceCounter(&counter); return counter.QuadPart * invfreq; } API bool WriteFile(String path, String data) { bool result = false; Scratch scratch; String16 wpath = ToString16(scratch, path); DWORD access = GENERIC_WRITE; DWORD creation_disposition = CREATE_ALWAYS; HANDLE handle = CreateFileW((wchar_t *)wpath.data, access, 0, NULL, creation_disposition, FILE_ATTRIBUTE_NORMAL, NULL); if (handle != INVALID_HANDLE_VALUE) { DWORD bytes_written = 0; Assert(data.len == (DWORD)data.len); // @Todo: can only read 32 byte size files? BOOL error = WriteFile(handle, data.data, (DWORD)data.len, &bytes_written, NULL); if (error == TRUE) { if (bytes_written == data.len) { result = true; } } CloseHandle(handle); } return result; } API String GetExePath(Allocator allocator) { Scratch scratch(allocator); wchar_t *wbuffer = AllocArray(scratch, wchar_t, 4096); DWORD wsize = GetModuleFileNameW(0, wbuffer, 4096); Assert(wsize != 0); String path = ToString(allocator, (char16_t *)wbuffer, wsize); NormalizePathInPlace(path); return path; } API String GetExeDir(Allocator allocator) { Scratch scratch(allocator); String path = GetExePath(scratch); path = ChopLastSlash(path); path = Copy(allocator, path); return path; } API bool FileExists(String path) { Scratch scratch; String16 wbuff = ToString16(scratch, path); DWORD attribs = GetFileAttributesW((wchar_t *)wbuff.data); bool result = attribs == INVALID_FILE_ATTRIBUTES ? false : true; return result; } API bool IsDir(String path) { Scratch scratch; String16 wbuff = ToString16(scratch, path); DWORD dwAttrib = GetFileAttributesW((wchar_t *)wbuff.data); return dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY); } API bool IsFile(String path) { Scratch scratch; String16 wbuff = ToString16(scratch, path); DWORD dwAttrib = GetFileAttributesW((wchar_t *)wbuff.data); bool is_file = (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) == 0; return dwAttrib != INVALID_FILE_ATTRIBUTES && is_file; } API String GetWorkingDir(Allocator arena) { Scratch scratch(arena); wchar_t *wbuffer = AllocArray(scratch, wchar_t, 4096); DWORD wsize = GetCurrentDirectoryW(4096, wbuffer); Assert(wsize != 0); wbuffer[wsize] = 0; String path = ToString(arena, (char16_t *)wbuffer, wsize); NormalizePathInPlace(path); return path; } API bool IsAbsolute(String path) { bool result = path.len > 3 && IsAlphabetic(path.data[0]) && path.data[1] == ':' && (path.data[2] == '/' || path.data[2] == '\\'); return result; } API bool DeleteFile(String path) { Scratch scratch; String16 wpath = ToString16(scratch, path); BOOL success = DeleteFileW((wchar_t *)wpath.data); bool result = true; if (success == 0) result = false; return result; } API int64_t GetFileModTime(String file) { Scratch scratch; String16 string16 = ToString16(scratch, file); WIN32_FIND_DATAW data; HANDLE handle = FindFirstFileW((wchar_t *)string16.data, &data); if (handle != INVALID_HANDLE_VALUE) { FindClose(handle); FILETIME time = data.ftLastWriteTime; int64_t result = (int64_t)time.dwHighDateTime << 32 | time.dwLowDateTime; return result; } else { return -1; } } API MakeDirResult MakeDir(String path) { Scratch scratch; MakeDirResult result = MakeDirResult_Success; String16 string16 = ToString16(scratch, path); BOOL success = CreateDirectoryW((wchar_t *)string16.data, NULL); if (success == 0) { DWORD error = GetLastError(); if (error == ERROR_ALREADY_EXISTS) { result = MakeDirResult_Exists; } else if (error == ERROR_PATH_NOT_FOUND) { result = MakeDirResult_NotFound; } else { result = MakeDirResult_ErrorOther; } } return result; } #elif OS_WASM #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define PATH_MAX 1024 #include API void (*Error)(const char *, ...); API void InitOS(void (*error_proc)(const char *, ...)) { Error = error_proc; } API String ReadFile(Allocator al, String path) { Scratch scratch(al); String null_term = Copy(scratch, path); String result = {}; FILE *f = fopen(null_term.data, "rb"); if (f) { fseek(f, 0, SEEK_END); result.len = ftell(f); fseek(f, 0, SEEK_SET); result.data = (char *)AllocSize(al, result.len + 1); fread(result.data, result.len, 1, f); result.data[result.len] = 0; fclose(f); } return result; } API bool WriteFile(String path, String data) { Scratch scratch; String null_term = Copy(scratch, path); bool result = false; FILE *f = fopen((const char *)null_term.data, "w"); if (f) { size_t written = fwrite(data.data, 1, data.len, f); if (written == data.len) { result = true; } fclose(f); } return result; } API bool DeleteFile(String path) { Scratch scratch; String null_term = Copy(scratch, path); int result = unlink(null_term.data); return result == 0; } API MakeDirResult MakeDir(String path) { Scratch scratch; String null_term = Copy(scratch, path); int error = mkdir(null_term.data, 0755); MakeDirResult result = MakeDirResult_Success; if (error != 0) { result = MakeDirResult_ErrorOther; if (errno == EEXIST) result = MakeDirResult_Exists; } return result; } API int64_t GetFileModTime(String path) { Scratch scratch; String null_term = Copy(scratch, path); struct stat attrib = {}; stat(null_term.data, &attrib); struct timespec ts = attrib.st_mtim; int64_t result = (((int64_t)ts.tv_sec) * 1000000ll) + ((int64_t)ts.tv_nsec) / 1000ll; return result; } API String GetAbsolutePath(Allocator al, String path) { return path; } API bool FileExists(String path) { Scratch scratch; String null_term = Copy(scratch, path); bool result = false; if (access((char *)null_term.data, F_OK) == 0) { result = true; } return result; } API bool IsDir(String path) { Scratch scratch; String null_term = Copy(scratch, path); struct stat s; if (stat(null_term.data, &s) != 0) return false; return S_ISDIR(s.st_mode); } API bool IsFile(String path) { Scratch scratch; String null_term = Copy(scratch, path); struct stat s; if (stat(null_term.data, &s) != 0) return false; return S_ISREG(s.st_mode); } API bool IsAbsolute(String path) { bool result = path.len && path.data[0] == '/'; return result; } API String GetWorkingDir(Allocator al) { return Copy(al, "/workingdir"); } API String GetExePath(Allocator al) { return Copy(al, "/text_editor"); } API String GetExeDir(Allocator al) { Scratch scratch(al); String exe_path = GetExePath(scratch); String dir = ChopLastSlash(exe_path); String result = Copy(al, dir); return result; } API uint64_t GetTimeNanos(void) { struct timespec spec; clock_gettime(CLOCK_MONOTONIC, &spec); uint64_t ts = ((uint64_t)spec.tv_sec * 1000000000ull) + (uint64_t)spec.tv_nsec; return ts; } API double GetTimeMicros(void) { struct timespec spec; clock_gettime(CLOCK_MONOTONIC, &spec); return (((double)spec.tv_sec) * 1000000) + (((double)spec.tv_nsec) / 1000); } API bool IsValid(const FileIter &it) { return it.is_valid; } API void Advance(FileIter *it) { struct dirent *file = NULL; while ((file = readdir((DIR *)it->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->filename = Copy(it->allocator, file->d_name); const char *separator = it->path.data[it->path.len - 1] == '/' ? "" : "/"; it->relative_path = Format(it->allocator, "%S%s%s", it->path, separator, file->d_name); it->absolute_path = GetAbsolutePath(it->allocator, it->relative_path); it->is_valid = true; return; } it->is_valid = false; closedir((DIR *)it->dir); } API FileIter IterateFiles(Allocator alo, String path) { FileIter it = {}; it.allocator = alo; it.path = path; Scratch scratch(alo); String null_term = Copy(scratch, path); it.dir = (void *)opendir((char *)null_term.data); if (it.dir) { Advance(&it); } return it; } #endif API double GetTimeSeconds() { return GetTimeMicros() / 1000000.0; } API String WriteTempFile(Allocator allocator, String data) { Scratch scratch(allocator); #if OS_WINDOWS int buffer_len = MAX_PATH+1; char16_t *buffer = AllocArray(scratch, char16_t, buffer_len); Assert(sizeof(char16_t) == sizeof(wchar_t)); DWORD result = GetTempPath2W(buffer_len, (LPWSTR)buffer); Assert(result != 0); String16 temp16 = {buffer, result}; NormalizePathInPlace(temp16); String temp_directory = ToString(allocator, temp16); #else String temp_directory = "/tmp"; #endif String temp_filename = Format(allocator, "%S/temp%llu", temp_directory, GetTimeNanos()); bool done = WriteFile(temp_filename, data); Assert(done); return temp_filename; }