#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 os_core_backtrace_error_callback(void *data, const char *msg, int errnum) { Error("libbacktrace error: %s (errnum: %d)\n", msg, errnum); } int os_core_backtrace_print_callback(void *data, uintptr_t pc, const char *filename, int lineno, const char *function) { bool printed = false; if (filename != NULL) { char buffer[512]; 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 os_unix_crash_handler(int signal, siginfo_t* info, void* context) { backtrace_full(backtrace_state, 2, os_core_backtrace_print_callback, os_core_backtrace_error_callback, NULL); exit(1); } void os_unix_register_crash_handler(void) { backtrace_state = backtrace_create_state(NULL, 1, os_core_backtrace_error_callback, NULL); struct sigaction sa; sa.sa_sigaction = os_unix_crash_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART | SA_SIGINFO; sigaction(SIGSEGV, &sa, NULL); sigaction(SIGABRT, &sa, NULL); sigaction(SIGBUS, &sa, NULL); sigaction(SIGILL, &sa, NULL); sigaction(SIGFPE, &sa, NULL); } API void InitOS(void (*error_proc)(const char *, ...)) { Error = error_proc; os_unix_register_crash_handler(); } 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) { 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 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 *dir_char_ending = it->is_directory ? "/" : ""; const char *separator = it->path.data[it->path.len - 1] == '/' ? "" : "/"; it->relative_path = Format(it->allocator, "%S%s%s%s", it->path, separator, file->d_name, dir_char_ending); it->absolute_path = GetAbsolutePath(it->allocator, it->relative_path); if (it->is_directory) it->absolute_path = Format(it->allocator, "%S/", it->absolute_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; } struct UnixProcess { pid_t pid; int child_stdout_read; int stdin_write; }; API Array SplitCommand(Allocator allocator, String command_line) { Array cmd = {allocator}; String curr = {}; for (int i = 0; i < command_line.len; i += 1) { if (command_line.data[i] == ' ') { if (curr.len > 0) { Add(&cmd, Copy(allocator, curr).data); curr = {}; } continue; } if (curr.len == 0) { curr.data = command_line.data + i; } curr.len += 1; } if (curr.len > 0) { Add(&cmd, Copy(allocator, curr).data); } return cmd; } API Process SpawnProcess(String command_line, String working_dir, String write_stdin, Array enviroment) { Scratch scratch; const int PIPE_READ = 0; const int PIPE_WRITE = 1; bool error = false; char *buffer = AllocArray(scratch, char, 4096); chdir(working_dir.data); getcwd(buffer, 4096); defer { chdir(buffer); }; Process process = {}; UnixProcess *plat = (UnixProcess *)&process.platform; Array args = SplitCommand(scratch, command_line); Array env = {scratch}; For (enviroment) { Add(&env, Copy(scratch, it).data); } int stdout_desc[2] = {}; int stdin_desc[2] = {}; posix_spawn_file_actions_t actions = {}; if (posix_spawn_file_actions_init(&actions) != 0) { Error("Libc function failed: posix_spawn_file_actions_init, with error: %s", strerror(errno)); return process; } defer { posix_spawn_file_actions_destroy(&actions); }; if (pipe(stdout_desc) == -1) { Error("Libc function failed: pipe, with error: %s", strerror(errno)); return process; } defer { if (error) { close(stdout_desc[PIPE_READ]); close(stdout_desc[PIPE_WRITE]); } else { close(stdout_desc[PIPE_WRITE]); } }; if (pipe(stdin_desc) == -1) { Error("Libc function failed: pipe, with error: %s", strerror(errno)); return process; } defer { if (error) { close(stdin_desc[PIPE_READ]); close(stdin_desc[PIPE_WRITE]); } else { close(stdin_desc[PIPE_READ]); } }; error = posix_spawn_file_actions_addclose(&actions, stdout_desc[PIPE_READ]) != 0; if (error) { Error("Libc function failed: posix_spawn_file_actions_addclose, with error: %s", strerror(errno)); return process; } error = posix_spawn_file_actions_adddup2(&actions, stdout_desc[PIPE_WRITE], STDOUT_FILENO) != 0; if (error) { Error("Libc function failed: posix_spawn_file_actions_adddup2 STDOUT_FILENO, with error: %s", strerror(errno)); return process; } error = posix_spawn_file_actions_adddup2(&actions, stdout_desc[PIPE_WRITE], STDERR_FILENO) != 0; if (error) { Error("Libc function failed: posix_spawn_file_actions_adddup2 STDERR_FILENO, with error: %s", strerror(errno)); return process; } error = posix_spawn_file_actions_addclose(&actions, stdin_desc[PIPE_WRITE]) != 0; if (error) { Error("Libc function failed: posix_spawn_file_actions_addclose, with error: %s", strerror(errno)); return process; } error = posix_spawn_file_actions_adddup2(&actions, stdin_desc[PIPE_READ], STDIN_FILENO) != 0; if (error) { Error("Libc function failed: posix_spawn_file_actions_adddup2 STDIN_FILENO, with error: %s", strerror(errno)); return process; } pid_t process_pid = 0; error = posix_spawnp(&process_pid, args[0], &actions, NULL, args.data, env.data) != 0; if (error) { Error("Libc function failed: failed to create process\n, with error: %s", strerror(errno)); return process; } plat->child_stdout_read = stdout_desc[PIPE_READ]; plat->stdin_write = stdin_desc[PIPE_WRITE]; plat->pid = process_pid; if (write_stdin.len) { WriteStdin(&process, write_stdin); CloseStdin(&process); } process.id = process_pid; process.is_valid = true; return process; } API bool IsValid(Process *process) { UnixProcess *plat = (UnixProcess *)&process->platform; if (process->is_valid == false) { return false; } int status = 0; pollfd p = {}; p.fd = plat->child_stdout_read; p.events = POLLRDHUP | POLLERR | POLLHUP | POLLNVAL; int res = poll(&p, 1, 0); if (res > 0) { pid_t result = waitpid(plat->pid, &status, 0); process->exit_code = WEXITSTATUS(status); return false; } return true; } API void KillProcess(Process *process) { Assert(process->is_valid); UnixProcess *plat = (UnixProcess *)process->platform; kill(plat->pid, SIGKILL); process->exit_code = -1; } API String PollStdout(Allocator allocator, Process *process, bool force_read) { Assert(process->is_valid); UnixProcess *plat = (UnixProcess *)process->platform; String result = {}; result.data = AllocArray(allocator, char, 16 * 4096); pollfd p = {}; p.fd = plat->child_stdout_read; p.events = POLLIN; int res = poll(&p, 1, 0); if (res > 0 || force_read) { result.len = read(plat->child_stdout_read, result.data, 4 * 4096); } return result; } API void WriteStdin(Process *process, String string) { if (string.len == 0) return; Assert(process->is_valid); UnixProcess *plat = (UnixProcess *)process->platform; ssize_t size = write(plat->stdin_write, string.data, string.len); Assert(size == string.len); } API void CloseStdin(Process *process) { UnixProcess *plat = (UnixProcess *)process->platform; close(plat->stdin_write); } #elif OS_WINDOWS #ifndef NOMINMAX #define NOMINMAX #endif #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #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 *is_dir = it->is_directory ? "/" : ""; const char *separator = it->path.data[it->path.len - 1] == '/' ? "" : "/"; it->relative_path = Format(it->allocator, "%S%s%S%s", it->path, separator, it->filename, is_dir); it->absolute_path = GetAbsolutePath(it->allocator, it->relative_path); it->is_valid = true; if (it->is_directory) { Assert(it->relative_path.data[it->relative_path.len - 1] == '/'); Assert(it->absolute_path.data[it->absolute_path.len - 1] == '/'); } 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 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; } struct Win32Process { HANDLE handle; HANDLE child_stdout_read; HANDLE child_stdout_write; HANDLE child_stdin_read; HANDLE child_stdin_write; }; // static_assert(sizeof(Win32Process) < sizeof(Process::platform)); static void Win32ReportError(String msg, String cmd) { LPVOID lpMsgBuf; FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL); defer { LocalFree(lpMsgBuf); }; char *buff = (char *)lpMsgBuf; size_t buffLen = strlen((const char *)buff); if (buffLen > 0 && buff[buffLen - 1] == '\n') buff[buffLen - 1] = 0; Error("%S: %S! %s", msg, cmd, (char *)lpMsgBuf); } static void Win32CloseProcess(Process *process) { Win32Process *p = (Win32Process *)process->platform; if (p->handle != INVALID_HANDLE_VALUE) CloseHandle(p->handle); if (p->child_stdout_write != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdout_write); if (p->child_stdout_read != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdout_read); if (p->child_stdin_read != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdin_read); if (p->child_stdin_write != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdin_write); process->is_valid = false; } static void Win32ProcessError(Process *process, String msg, String cmd) { Win32ReportError(msg, cmd); Win32CloseProcess(process); } API void WriteStdin(Process *process, String string) { if (string.len == 0) return; Assert(process->is_valid); Win32Process *p = (Win32Process *)process->platform; Assert(p->child_stdin_write != INVALID_HANDLE_VALUE); DWORD written = 0; bool write_error = WriteFile(p->child_stdin_write, string.data, (DWORD)string.len, &written, NULL) == 0; Assert(write_error == false); Assert(written == string.len); } API void CloseStdin(Process *process) { Win32Process *p = (Win32Process *)process->platform; CloseHandle(p->child_stdin_write); p->child_stdin_write = INVALID_HANDLE_VALUE; } API Process SpawnProcess(String command_line, String working_dir, String write_stdin, Array enviroment) { Process process = {}; Win32Process *p = (Win32Process *)process.platform; Scratch scratch; command_line = Format(scratch, "/C %S", command_line); p->handle = INVALID_HANDLE_VALUE; p->child_stdout_write = INVALID_HANDLE_VALUE; p->child_stdout_read = INVALID_HANDLE_VALUE; p->child_stdin_read = INVALID_HANDLE_VALUE; p->child_stdin_write = INVALID_HANDLE_VALUE; SECURITY_ATTRIBUTES security_atrb = {}; security_atrb.nLength = sizeof(SECURITY_ATTRIBUTES); security_atrb.bInheritHandle = TRUE; if (!CreatePipe(&p->child_stdout_read, &p->child_stdout_write, &security_atrb, 0)) { Win32ProcessError(&process, "Failed to create process at create pipe stage", command_line); return process; } if (!SetHandleInformation(p->child_stdout_read, HANDLE_FLAG_INHERIT, 0)) { Win32ProcessError(&process, "Failed to create process at create pipe stage", command_line); return process; } if (!CreatePipe(&p->child_stdin_read, &p->child_stdin_write, &security_atrb, 0)) { Win32ProcessError(&process, "Failed to create process at create pipe stage", command_line); return process; } if (!SetHandleInformation(p->child_stdin_write, HANDLE_FLAG_INHERIT, 0)) { Win32ProcessError(&process, "Failed to create process at create pipe stage", command_line); return process; } STARTUPINFOW startup = {}; startup.cb = sizeof(STARTUPINFO); startup.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; startup.hStdInput = p->child_stdin_read; startup.hStdOutput = p->child_stdout_write; startup.hStdError = p->child_stdout_write; startup.wShowWindow = SW_HIDE; String16 cwd = ToString16(scratch, working_dir); String16 cmd = ToString16(scratch, command_line); char *env = NULL; if (enviroment.len) { Int size = GetSize(enviroment) + enviroment.len + 1; env = (char *)PushSize(scratch, size); Int i = 0; For(enviroment) { MemoryCopy(env + i, it.data, it.len); i += it.len; env[i++] = 0; } env[i++] = 0; } DWORD dwCreationFlags = 0; BOOL bInheritHandles = TRUE; PROCESS_INFORMATION info = {}; if (!CreateProcessW(L"c:\\windows\\system32\\cmd.exe", (wchar_t *)cmd.data, 0, 0, bInheritHandles, dwCreationFlags, env, (wchar_t *)cwd.data, &startup, &info)) { Win32ProcessError(&process, "failed to create process", command_line); return process; } // Close handles to the stdin and stdout pipes no longer needed by the child process. // If they are not explicitly closed, there is no way to recognize that the child process has ended. CloseHandle(info.hThread); CloseHandle(p->child_stdin_read); CloseHandle(p->child_stdout_write); p->child_stdin_read = INVALID_HANDLE_VALUE; p->child_stdout_write = INVALID_HANDLE_VALUE; p->handle = info.hProcess; process.is_valid = true; process.id = (int64_t)p->handle; if (write_stdin.len) { WriteStdin(&process, write_stdin); CloseStdin(&process); } return process; } API bool IsValid(Process *process) { if (process->is_valid == false) return false; Win32Process *p = (Win32Process *)process->platform; if (WaitForSingleObject(p->handle, 0) != WAIT_OBJECT_0) { return true; } DWORD exit_code; bool get_exit_code_failed = GetExitCodeProcess(p->handle, &exit_code) == 0; if (get_exit_code_failed) { exit_code = -1; } process->exit_code = (int)exit_code; Win32CloseProcess(process); return false; } API void KillProcess(Process *process) { Assert(process->is_valid); Win32Process *p = (Win32Process *)process->platform; bool terminate_process_error = TerminateProcess(p->handle, -1) == 0; if (terminate_process_error) { Assert(0); } Win32CloseProcess(process); process->exit_code = -1; } API String PollStdout(Allocator allocator, Process *process, bool force_read) { Assert(process->is_valid); Win32Process *p = (Win32Process *)process->platform; DWORD bytes_avail = 0; bool peek_error = PeekNamedPipe(p->child_stdout_read, NULL, 0, NULL, &bytes_avail, NULL) == 0; if (peek_error) { return {}; } else if (bytes_avail == 0) { return {}; } size_t buffer_size = ClampTop(bytes_avail, (DWORD)(4096 * 16)); char *buffer = AllocArray(allocator, char, buffer_size); DWORD bytes_read = 0; bool read_error = ReadFile(p->child_stdout_read, buffer, (DWORD)buffer_size, &bytes_read, 0) == 0; if (read_error) { Win32ReportError("Failed to read the stdout of child process", "ReadFile"); Dealloc(allocator, buffer); Win32CloseProcess(process); return {}; } String result = {buffer, bytes_read}; 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 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 *dir_char_ending = it->is_directory ? "/" : ""; const char *separator = it->path.data[it->path.len - 1] == '/' ? "" : "/"; it->relative_path = Format(it->allocator, "%.*s%s%s%s", FmtString(it->path), separator, file->d_name, dir_char_ending); it->absolute_path = GetAbsolutePath(it->allocator, it->relative_path); if (it->is_directory) it->absolute_path = Format(it->allocator, "%.*s/", FmtString(it->absolute_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; } API Array SplitCommand(Allocator allocator, String command_line) { Array cmd = {allocator}; String curr = {}; for (int i = 0; i < command_line.len; i += 1) { if (command_line.data[i] == ' ') { if (curr.len > 0) { Add(&cmd, Copy(allocator, curr).data); curr = {}; } continue; } if (curr.len == 0) { curr.data = command_line.data + i; } curr.len += 1; } if (curr.len > 0) { Add(&cmd, Copy(allocator, curr).data); } return cmd; } API Process SpawnProcess(String command_line, String working_dir, String write_stdin, Array enviroment) { return {}; } API bool IsValid(Process *process) { return false; } API void KillProcess(Process *process) { } API String PollStdout(Allocator allocator, Process *process, bool force_read) { return {}; } API void WriteStdin(Process *process, String string) { return; } API void CloseStdin(Process *process) { return; } #endif API double GetTimeSeconds() { return GetTimeMicros() / 1000000.0; }