750 lines
21 KiB
C++
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;
|
|
}
|