Files
text_editor/src/basic/basic_os.cpp
2026-02-07 11:08:29 +01:00

750 lines
21 KiB
C++

typedef void OSErrorReport(const char *, ...);
#if OS_POSIX
#include <dirent.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <linux/limits.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <spawn.h>
#include <poll.h>
#include <execinfo.h>
#include <backtrace.h>
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 <windows.h>
#undef Yield
#include <stdio.h>
#include <intrin.h>
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 <dirent.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <spawn.h>
#include <poll.h>
#define PATH_MAX 1024
#include <emscripten.h>
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;
}