488 lines
16 KiB
C++
488 lines
16 KiB
C++
#include "filesystem.h"
|
|
#include "thread_queue.h"
|
|
|
|
#ifndef NOMINMAX
|
|
#define NOMINMAX
|
|
#endif
|
|
#ifndef WIN32_LEAN_AND_MEAN
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#endif
|
|
#include <windows.h>
|
|
#include <stdio.h>
|
|
#include <intrin.h>
|
|
|
|
#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<String> 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;
|
|
}
|