#include "filesystem.h" #include "thread_queue.h" #ifndef NOMINMAX #define NOMINMAX #endif #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include #include #include "win32_thread.cpp" void (*Error)(const char *, ...); void *VReserve(size_t size) { void *result = (uint8_t *)VirtualAlloc(0, size, MEM_RESERVE, PAGE_READWRITE); return result; } bool VCommit(void *p, size_t size) { void *result = VirtualAlloc(p, size, MEM_COMMIT, PAGE_READWRITE); return result ? true : false; } bool VRelease(void *p, size_t size) { BOOL result = VirtualFree(p, 0, MEM_RELEASE); return result ? true : false; } bool VDecommit(void *p, size_t size) { BOOL result = VirtualFree(p, size, MEM_DECOMMIT); return result ? true : false; } void InitOS(void (*error_proc)(const char *, ...)) { Error = error_proc; SetConsoleOutputCP(65001); SetConsoleCP(65001); } 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; 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; } bool IsValid(const FileIter &it) { return it.is_valid; } 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", FmtString(it->path), separator, FmtString(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); } FileIter IterateFiles(Allocator alo, String path) { FileIter it = {0}; it.allocator = alo; it.path = path; String modified_path = Format(it.allocator, "%.*s\\*", FmtString(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; } double get_time_in_micros(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; } 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; } 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; } String GetExeDir(Allocator allocator) { Scratch scratch((Arena *)allocator.object); String path = GetExePath(scratch); path = ChopLastSlash(path); path = Copy(allocator, path); return path; } 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; } 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); } 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; } 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; } bool IsAbsolute(String path) { bool result = path.len > 3 && IsAlphabetic(path.data[0]) && path.data[1] == ':' && (path.data[2] == '/' || path.data[2] == '\\'); return result; } 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; } 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; } } 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)); 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", FmtString(msg), FmtString(cmd), (char *)lpMsgBuf); } 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); } 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); } void CloseStdin(Process *process) { Win32Process *p = (Win32Process *)process->platform; CloseHandle(p->child_stdin_write); p->child_stdin_write = INVALID_HANDLE_VALUE; } 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", FmtString(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; } 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; } 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; } 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; }