Browse Source

Merge pull request #3310 from flysand7/core-process

Addition of `core:os2/process` api.
gingerBill 1 year ago
parent
commit
affe8f7144
2 changed files with 1279 additions and 44 deletions
  1. 335 44
      core/os/os2/process.odin
  2. 944 0
      core/os/os2/process_windows.odin

+ 335 - 44
core/os/os2/process.odin

@@ -3,100 +3,391 @@ package os2
 import "core:sync"
 import "core:time"
 import "base:runtime"
+import "core:strings"
 
-args: []string
+/*
+	In procedures that explicitly state this as one of the allowed values,
+	specifies an infinite timeout.
+*/
+TIMEOUT_INFINITE :: time.MIN_DURATION // Note(flysand): Any negative duration will be treated as infinity
 
+/*
+	Arguments to the current process.
+*/
+args := get_args()
+
+@(private="file")
+get_args :: proc() -> []string {
+	result := make([]string, len(runtime.args__), heap_allocator())
+	for rt_arg, i in runtime.args__ {
+		result[i] = cast(string) rt_arg
+	}
+	return result[:]
+}
+
+/*
+	Exit the current process.
+*/
 exit :: proc "contextless" (code: int) -> ! {
-	runtime.trap()
+	_exit(code)
 }
 
+/*
+	Obtain the UID of the current process.
+
+	**Note(windows)**: Windows doesn't follow the posix permissions model, so
+	the function simply returns -1.
+*/
 get_uid :: proc() -> int {
-	return -1
+	return _get_uid()
 }
 
+/*
+	Obtain the effective UID of the current process.
+
+	The effective UID is typically the same as the UID of the process. In case
+	the process was run by a user with elevated permissions, the process may
+	lower the privilege to perform some tasks without privilege. In these cases
+	the real UID of the process and the effective UID are different.
+	
+	**Note(windows)**: Windows doesn't follow the posix permissions model, so
+	the function simply returns -1.
+*/
 get_euid :: proc() -> int {
-	return -1
+	return _get_euid()
 }
 
+/*
+	Obtain the GID of the current process.
+	
+	**Note(windows)**: Windows doesn't follow the posix permissions model, so
+	the function simply returns -1.
+*/
 get_gid :: proc() -> int {
-	return -1
+	return _get_gid()
 }
 
+/*
+	Obtain the effective GID of the current process.
+	
+	The effective GID is typically the same as the GID of the process. In case
+	the process was run by a user with elevated permissions, the process may
+	lower the privilege to perform some tasks without privilege. In these cases
+	the real GID of the process and the effective GID are different.
+
+	**Note(windows)**: Windows doesn't follow the posix permissions model, so
+	the function simply returns -1.
+*/
 get_egid :: proc() -> int {
-	return -1
+	return _get_egid()
 }
 
+/*
+	Obtain the ID of the current process.
+*/
 get_pid :: proc() -> int {
-	return -1
+	return _get_pid()
 }
 
+/*
+	Obtain the ID of the parent process.
+
+	**Note(windows)**: Windows does not mantain strong relationships between
+	parent and child processes. This function returns the ID of the process
+	that has created the current process. In case the parent has died, the ID
+	returned by this function can identify a non-existent or a different
+	process.
+*/
 get_ppid :: proc() -> int {
-	return -1
+	return _get_ppid()
 }
 
+/*
+	Obtain ID's of all processes running in the system.
+*/
+process_list :: proc(allocator: runtime.Allocator) -> ([]int, Error) {
+	return _process_list(allocator)
+}
 
-Process :: struct {
-	pid:          int,
-	handle:       uintptr,
-	is_done:      b32,
-	signal_mutex: sync.RW_Mutex,
+/*
+	Bit set specifying which fields of the `Process_Info` struct need to be
+	obtained by the `process_info()` procedure. Each bit corresponds to a
+	field in the `Process_Info` struct.
+*/
+Process_Info_Fields :: bit_set[Process_Info_Field]
+Process_Info_Field :: enum {
+	Executable_Path,
+	PPid,
+	Priority,
+	Command_Line,
+	Command_Args,
+	Environment,
+	Username,
+	Working_Dir,
+}
+
+/*
+	Contains information about the process as obtained by the `process_info()`
+	procedure.
+*/
+Process_Info :: struct {
+	// The information about a process the struct contains. `pid` is always
+	// stored, no matter what.
+	fields: Process_Info_Fields,
+	// The ID of the process.
+	pid: int,
+	// The ID of the parent process.
+	ppid: int,
+	// The process priority.
+	priority: int,
+	// The path to the executable, which the process runs.
+	executable_path: string,
+	// The command line supplied to the process.
+	command_line: string,
+	// The arguments supplied to the process.
+	command_args: []string,
+	// The environment of the process.
+	environment: []string,
+	// The username of the user who started the process.
+	username: string,
+	// The current working directory of the process.
+	working_dir: string,
 }
 
+/*
+	Obtain information about a process.
 
-Process_Attributes :: struct {
-	dir: string,
-	env: []string,
-	files: []^File,
-	sys: ^Process_Attributes_OS_Specific,
+	This procedure obtains an information, specified by `selection` parameter of
+	a process given by `pid`.
+	
+	Use `free_process_info` to free the memory allocated by this procedure. In
+	case the function returns an error all temporary allocations would be freed
+	and as such, calling `free_process_info()` is not needed.
+
+	**Note**: The resulting information may or may contain the fields specified
+	by the `selection` parameter. Always check whether the returned
+	`Process_Info` struct has the required fields before checking the error code
+	returned by this function.
+*/
+process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) {
+	return _process_info_by_pid(pid, selection, allocator)
 }
 
-Process_Attributes_OS_Specific :: struct{}
+/*
+	Obtain information about a process.
+
+	This procedure obtains information, specified by `selection` parameter
+	about a process that has been opened by the application, specified in
+	the `process` parameter.
+
+	Use `free_process_info` to free the memory allocated by this procedure. In
+	case the function returns an error, all temporary allocations would be freed
+	and as such, calling `free_process_info` is not needed.
 
-Process_Error :: enum {
-	None,
+	**Note**: The resulting information may or may contain the fields specified
+	by the `selection` parameter. Always check whether the returned
+	`Process_Info` struct has the required fields before checking the error code
+	returned by this function.
+*/
+process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) {
+	return _process_info_by_handle(process, selection, allocator)
 }
 
-Process_State :: struct {
-	pid:         int,
-	exit_code:   int,
-	exited:      bool,
-	success:     bool,
-	system_time: time.Duration,
-	user_time:   time.Duration,
-	sys:         rawptr,
+/*
+	Obtain information about the current process.
+
+	This procedure obtains the information, specified by `selection` parameter
+	about the currently running process.
+
+	Use `free_process_info` to free the memory allocated by this function. In
+	case this function returns an error, all temporary allocations would be
+	freed and as such calling `free_process_info()` is not needed.
+
+	**Note**: The resulting information may or may contain the fields specified
+	by the `selection` parameter. Always check whether the returned
+	`Process_Info` struct has the required fields before checking the error code
+	returned by this function.
+*/
+current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) {
+	return _current_process_info(selection, allocator)
 }
 
-Signal :: #type proc()
+/*
+	Obtain information about the specified process.
+*/
+process_info :: proc {
+	process_info_by_pid,
+	process_info_by_handle,
+	current_process_info,
+}
+
+/*
+	Free the information about the process.
+
+	This procedure frees the memory occupied by process info using the provided
+	allocator. The allocator needs to be the same allocator that was supplied
+	to the `process_info` function.
+*/
+free_process_info :: proc(pi: Process_Info, allocator: runtime.Allocator) {
+	delete(pi.executable_path, allocator)
+	delete(pi.command_line, allocator)
+	delete(pi.command_args, allocator)
+	for s in pi.environment {
+		delete(s, allocator)
+	}
+	delete(pi.environment, allocator)
+	delete(pi.working_dir, allocator)
+}
+
+/*
+	Represents a process handle.
 
-Kill:      Signal = nil
-Interrupt: Signal = nil
+	When a process dies, the OS is free to re-use the pid of that process. The
+	`Process` struct represents a handle to the process that will refer to a
+	specific process, even after it has died.
 
+	**Note(linux)**: The `handle` will be referring to pidfd.
+*/
+Process :: struct {
+	pid: int,
+	handle: uintptr,
+}
 
-find_process :: proc(pid: int) -> (^Process, Process_Error) {
-	return nil, .None
+Process_Open_Flags :: bit_set[Process_Open_Flag]
+Process_Open_Flag :: enum {
+	// Request for reading from the virtual memory of another process.
+	Mem_Read,
+	// Request for writing to the virtual memory of another process.
+	Mem_Write,
 }
 
+/*
+	Open a process handle using it's pid.
+
+	This procedure obtains a process handle of a process specified by `pid`.
+	This procedure can be subject to race conditions. See the description of
+	`Process`.
 
-process_start :: proc(name: string, argv: []string, attr: ^Process_Attributes) -> (^Process, Process_Error) {
-	return nil, .None
+	Use `process_close()` function to close the process handle.
+*/
+process_open :: proc(pid: int, flags := Process_Open_Flags {}) -> (Process, Error) {
+	return _process_open(pid, flags)
 }
 
-process_release :: proc(p: ^Process) -> Process_Error {
-	return .None
+/*
+	The description of how a process should be created.
+*/
+Process_Desc :: struct {
+	// OS-specific attributes.
+	sys_attr: _Sys_Process_Attributes,
+	// The working directory of the process. If the string has length 0, the
+	// working directory is assumed to be the current working directory of the
+	// current process.
+	working_dir: string,
+	// The command to run. Each element of the slice is a separate argument to
+	// the process. The first element of the slice would be the executable.
+	command: []string,
+	// A slice of strings, each having the format `KEY=VALUE` representing the
+	// full environment that the child process will receive.
+	// In case this slice is `nil`, the current process' environment is used.
+	env: []string,
+	// The `stderr` handle to give to the child process. It can be either a file
+	// or a writeable end of a pipe. Passing `nil` will shut down the process'
+	// stderr output.
+	stderr: ^File,
+	// The `stdout` handle to give to the child process. It can be either a file
+	// or a writeabe end of a pipe. Passing a `nil` will shut down the process'
+	// stdout output.
+	stdout: ^File,
+	// The `stdin` handle to give to the child process. It can either be a file
+	// or a readable end of a pipe. Passing a `nil` will shut down the process'
+	// input.
+	stdin: ^File,
 }
 
-process_kill :: proc(p: ^Process) -> Process_Error {
-	return .None
+/*
+	Create a new process and obtain its handle.
+
+	This procedure creates a new process, with a given command and environment
+	strings as parameters. Use `environ()` to inherit the environment of the
+	current process.
+
+	The `desc` parameter specifies the description of how the process should
+	be created. It contains information such as the command line, the
+	environment of the process, the starting directory and many other options.
+	Most of the fields in the struct can be set to `nil` or an empty value.
+	
+	Use `process_close` to close the handle to the process. Note, that this
+	is not the same as terminating the process. One can terminate the process
+	and not close the handle, in which case the handle would be leaked. In case
+	the function returns an error, an invalid handle is returned.
+
+	This procedure is not thread-safe. It may alter the inheritance properties
+	of file handles in an unpredictable manner. In case multiple threads change
+	handle inheritance properties, make sure to serialize all those calls.
+*/
+process_start :: proc(desc := Process_Desc {}) -> (Process, Error) {
+	return _process_start(desc)
 }
 
-process_signal :: proc(p: ^Process, sig: Signal) -> Process_Error {
-	return .None
+/*
+	The state of the process after it has finished execution.
+*/
+Process_State :: struct {
+	// The ID of the process.
+	pid: int,
+	// Specifies whether the process has terminated or is still running.
+	exited: bool,
+	// The exit code of the process, if it has exited.
+	// Will also store the number of the exception or signal that has crashed the
+	// process.
+	exit_code: int,
+	// Specifies whether the termination of the process was successfull or not,
+	// i.e. whether it has crashed or not.
+	// **Note(windows)**: On windows `true` is always returned, as there is no
+	// reliable way to obtain information about whether the process has crashed.
+	success: bool,
+	// The time the process has spend executing in kernel time.
+	system_time: time.Duration,
+	// The time the process has spend executing in userspace.
+	user_time: time.Duration,
 }
 
-process_wait :: proc(p: ^Process) -> (Process_State, Process_Error) {
-	return {}, .None
+/*
+	Wait for a process event.
+
+	This procedure blocks the execution until the process has exited or the
+	timeout (if specified) has reached zero. If the timeout is `TIMEOUT_INFINITE`,
+	no timeout restriction is imposed and the procedure can block indefinately.
+
+	If the timeout has expired, the `General_Error.Timeout` is returned as
+	the error.
+
+	If an error is returned for any other reason, other than timeout, the
+	process state is considered undetermined.
+*/
+process_wait :: proc(process: Process, timeout := TIMEOUT_INFINITE) -> (Process_State, Error) {
+	return _process_wait(process, timeout)
 }
 
+/*
+	Close the handle to a process.
 
+	This procedure closes the handle associated with a process. It **does not**
+	terminate a process, in case it was running. In case a termination is
+	desired, kill the process first, wait for the process to finish,
+	then close the handle.
+*/
+process_close :: proc(process: Process) -> (Error) {
+	return _process_close(process)
+}
 
+/*
+	Terminate a process.
 
+	This procedure terminates a process, specified by it's handle, `process`.
+
+*/
+process_kill :: proc(process: Process) -> (Error) {
+	return _process_kill(process)
+}

+ 944 - 0
core/os/os2/process_windows.odin

@@ -0,0 +1,944 @@
+//+build windows
+package os2
+
+import "core:sys/windows"
+import "core:strings"
+import "core:time"
+
+import "base:runtime"
+
+_Process_Handle :: windows.HANDLE
+
+_exit :: proc "contextless" (code: int) -> ! {
+	windows.ExitProcess(u32(code))
+}
+
+_get_uid :: proc() -> int {
+	return -1
+}
+
+_get_euid :: proc() -> int {
+	return -1
+}
+
+_get_gid :: proc() -> int {
+	return -1
+}
+
+_get_egid :: proc() -> int {
+	return -1
+}
+
+_get_pid :: proc() -> int {
+	return cast(int) windows.GetCurrentProcessId()
+}
+
+_get_ppid :: proc() -> int {
+	our_pid := windows.GetCurrentProcessId()
+	snap := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0)
+	if snap == windows.INVALID_HANDLE_VALUE {
+		return -1
+	}
+	defer windows.CloseHandle(snap)
+	entry := windows.PROCESSENTRY32W { dwSize = size_of(windows.PROCESSENTRY32W) }
+	status := windows.Process32FirstW(snap, &entry)
+	for status {
+		if entry.th32ProcessID == our_pid {
+			return cast(int) entry.th32ParentProcessID
+		}
+		status = windows.Process32NextW(snap, &entry)
+	}
+	return -1
+}
+
+_process_list :: proc(allocator: runtime.Allocator) -> ([]int, Error) {
+	pid_list := make([dynamic]int, allocator)
+	snap := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0)
+	if snap == windows.INVALID_HANDLE_VALUE {
+		return pid_list[:], _get_platform_error()
+	}
+	entry := windows.PROCESSENTRY32W { dwSize = size_of(windows.PROCESSENTRY32W) }
+	status := windows.Process32FirstW(snap, &entry)
+	for status {
+		append(&pid_list, cast(int) entry.th32ProcessID)
+		status = windows.Process32NextW(snap, &entry)
+	}
+	return pid_list[:], nil
+}
+
+_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
+	info.pid = pid
+	defer if err != nil {
+		free_process_info(info, allocator)
+	}
+	need_snapprocess := \
+		.PPid in selection ||
+		.Priority in selection
+	need_snapmodule := \
+		.Executable_Path in selection
+	need_peb := \
+		.Command_Line in selection ||
+		.Environment in selection ||
+		.Working_Dir in selection
+	need_process_handle := need_peb || .Username in selection
+	// Data obtained from process snapshots
+	if need_snapprocess {
+		entry, entry_err := _process_entry_by_pid(info.pid)
+		if entry_err != nil {
+			err = General_Error.Not_Exist
+			return
+		}
+		if .PPid in selection {
+			info.fields |= {.PPid}
+			info.ppid = int(entry.th32ParentProcessID)
+		}
+		if .Priority in selection {
+			info.fields |= {.Priority}
+			info.priority = int(entry.pcPriClassBase)
+		}
+	}
+	if need_snapmodule {
+		exe_path, exe_path_err := _process_exe_by_pid(pid, allocator)
+		if exe_path_err != nil {
+			err = exe_path_err
+			return
+		}
+		info.fields |= {.Executable_Path}
+		info.executable_path = exe_path
+	}
+	ph := windows.INVALID_HANDLE_VALUE
+	if need_process_handle {
+		ph = windows.OpenProcess(
+			windows.PROCESS_QUERY_LIMITED_INFORMATION | windows.PROCESS_VM_READ,
+			false,
+			u32(pid),
+		)
+		if ph == windows.INVALID_HANDLE_VALUE {
+			err = _get_platform_error()
+			return
+		}
+	}
+	defer if ph != windows.INVALID_HANDLE_VALUE {
+		windows.CloseHandle(ph)
+	}
+	if need_peb {
+		// TODO(flysand): This was not tested with WOW64 or 32-bit processes,
+		// might need to be revised later when issues occur.
+		ntdll_lib := windows.LoadLibraryW(windows.L("ntdll.dll"))
+		if ntdll_lib == nil {
+			err = _get_platform_error()
+			return
+		}
+		defer windows.FreeLibrary(ntdll_lib)
+		NtQueryInformationProcess := cast(NtQueryInformationProcess_T) windows.GetProcAddress(ntdll_lib, "NtQueryInformationProcess")
+		if NtQueryInformationProcess == nil {
+			err = _get_platform_error()
+			return
+		}
+		process_info_size: u32 = ---
+		process_info: PROCESS_BASIC_INFORMATION = ---
+		status := NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size)
+		if status != 0 {
+			// TODO(flysand): There's probably a mismatch between NTSTATUS and
+			// windows userland error codes, I haven't checked.
+			err = Platform_Error(status)
+			return
+		}
+		if process_info.PebBaseAddress == nil {
+			// Not sure what the error is
+			err = General_Error.Unsupported
+			return
+		}
+		process_peb: PEB = ---
+		bytes_read: uint = ---
+		read_struct :: proc(h: windows.HANDLE, addr: rawptr, dest: ^$T, br: ^uint) -> windows.BOOL {
+			return windows.ReadProcessMemory(h, addr, dest, size_of(T), br)
+		}
+		read_slice :: proc(h: windows.HANDLE, addr: rawptr, dest: []$T, br: ^uint) -> windows.BOOL {
+			return windows.ReadProcessMemory(h, addr, raw_data(dest), len(dest)*size_of(T), br)
+		}
+		if !read_struct(ph, process_info.PebBaseAddress, &process_peb, &bytes_read) {
+			err = _get_platform_error()
+			return
+		}
+		process_params: RTL_USER_PROCESS_PARAMETERS = ---
+		if !read_struct(ph, process_peb.ProcessParameters, &process_params, &bytes_read) {
+			err = _get_platform_error()
+			return
+		}
+		if .Command_Line in selection || .Command_Args in selection {
+			TEMP_ALLOCATOR_GUARD()
+			cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator())
+			if !read_slice(ph, process_params.CommandLine.Buffer, cmdline_w, &bytes_read) {
+				err = _get_platform_error()
+				return
+			}
+			if .Command_Line in selection {
+				cmdline, cmdline_err := windows.utf16_to_utf8(cmdline_w, allocator)
+				if cmdline_err != nil {
+					err = cmdline_err
+					return
+				}
+				info.fields |= {.Command_Line}
+				info.command_line = cmdline
+			}
+			if .Command_Args in selection {
+				args, args_err := _parse_command_line(raw_data(cmdline_w), allocator)
+				if args_err != nil {
+					err = args_err
+					return
+				}
+				info.fields += {.Command_Args}
+				info.command_args = args
+			}
+		}
+		if .Environment in selection {
+			TEMP_ALLOCATOR_GUARD()
+			env_len := process_params.EnvironmentSize / 2
+			envs_w := make([]u16, env_len, temp_allocator())
+			if !read_slice(ph, process_params.Environment, envs_w, &bytes_read) {
+				err = _get_platform_error()
+				return
+			}
+			envs, envs_err := _parse_environment_block(raw_data(envs_w), allocator)
+			if envs_err != nil {
+				err = envs_err
+				return
+			}
+			info.fields |= {.Environment}
+			info.environment = envs
+		}
+		if .Working_Dir in selection {
+			TEMP_ALLOCATOR_GUARD()
+			cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator())
+			if !read_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w, &bytes_read) {
+				err = _get_platform_error()
+				return
+			}
+			cwd, cwd_err := windows.utf16_to_utf8(cwd_w, allocator)
+			if cwd_err != nil {
+				err = cwd_err
+				return
+			}
+			info.fields |= {.Working_Dir}
+			info.working_dir = cwd
+		}
+	}
+	if .Username in selection {
+		username, username_err := _get_process_user(ph, allocator)
+		if username_err != nil {
+			err = username_err
+			return
+		}
+		info.fields |= {.Username}
+		info.username = username
+	}
+	err = nil
+	return
+}
+
+_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
+	pid := process.pid
+	info.pid = pid
+	defer if err != nil {
+		free_process_info(info, allocator)
+	}
+	need_snapprocess := \
+		.PPid in selection ||
+		.Priority in selection
+	need_snapmodule := \
+		.Executable_Path in selection
+	need_peb := \
+		.Command_Line in selection ||
+		.Environment in selection ||
+		.Working_Dir in selection
+	// Data obtained from process snapshots
+	if need_snapprocess {
+		entry, entry_err := _process_entry_by_pid(info.pid)
+		if entry_err != nil {
+			err = General_Error.Not_Exist
+			return
+		}
+		if .PPid in selection {
+			info.fields |= {.PPid}
+			info.ppid = int(entry.th32ParentProcessID)
+		}
+		if .Priority in selection {
+			info.fields |= {.Priority}
+			info.priority = int(entry.pcPriClassBase)
+		}
+	}
+	if need_snapmodule {
+		exe_path, exe_path_err := _process_exe_by_pid(pid, allocator)
+		if exe_path_err != nil {
+			err = exe_path_err
+			return
+		}
+		info.fields |= {.Executable_Path}
+		info.executable_path = exe_path
+	}
+	ph := cast(windows.HANDLE) process.handle
+	if need_peb {
+		ntdll_lib := windows.LoadLibraryW(windows.L("ntdll.dll"))
+		if ntdll_lib == nil {
+			err = _get_platform_error()
+			return
+		}
+		defer windows.FreeLibrary(ntdll_lib)
+		NtQueryInformationProcess := cast(NtQueryInformationProcess_T) windows.GetProcAddress(ntdll_lib, "NtQueryInformationProcess")
+		if NtQueryInformationProcess == nil {
+			err = _get_platform_error()
+			return
+		}
+		process_info_size: u32 = ---
+		process_info: PROCESS_BASIC_INFORMATION = ---
+		status := NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size)
+		if status != 0 {
+			// TODO(flysand): There's probably a mismatch between NTSTATUS and
+			// windows userland error codes, I haven't checked.
+			err = Platform_Error(status)
+			return
+		}
+		if process_info.PebBaseAddress == nil {
+			// Not sure what the error is
+			err = General_Error.Unsupported
+			return
+		}
+		process_peb: PEB = ---
+		bytes_read: uint = ---
+		read_struct :: proc(h: windows.HANDLE, addr: rawptr, dest: ^$T, br: ^uint) -> windows.BOOL {
+			return windows.ReadProcessMemory(h, addr, dest, size_of(T), br)
+		}
+		read_slice :: proc(h: windows.HANDLE, addr: rawptr, dest: []$T, br: ^uint) -> windows.BOOL {
+			return windows.ReadProcessMemory(h, addr, raw_data(dest), len(dest)*size_of(T), br)
+		}
+		if !read_struct(ph, process_info.PebBaseAddress, &process_peb, &bytes_read) {
+			err = _get_platform_error()
+			return
+		}
+		process_params: RTL_USER_PROCESS_PARAMETERS = ---
+		if !read_struct(ph, process_peb.ProcessParameters, &process_params, &bytes_read) {
+			err = _get_platform_error()
+			return
+		}
+		if .Command_Line in selection || .Command_Args in selection {
+			TEMP_ALLOCATOR_GUARD()
+			cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator())
+			if !read_slice(ph, process_params.CommandLine.Buffer, cmdline_w, &bytes_read) {
+				err = _get_platform_error()
+				return
+			}
+			if .Command_Line in selection {
+				cmdline, cmdline_err := windows.utf16_to_utf8(cmdline_w, allocator)
+				if cmdline_err != nil {
+					err = cmdline_err
+					return
+				}
+				info.fields |= {.Command_Line}
+				info.command_line = cmdline
+			}
+			if .Command_Args in selection {
+				args, args_err := _parse_command_line(raw_data(cmdline_w), allocator)
+				if args_err != nil {
+					err = args_err
+					return
+				}
+				info.fields += {.Command_Args}
+				info.command_args = args
+			}
+		}
+		if .Environment in selection {
+			TEMP_ALLOCATOR_GUARD()
+			env_len := process_params.EnvironmentSize / 2
+			envs_w := make([]u16, env_len, temp_allocator())
+			if !read_slice(ph, process_params.Environment, envs_w, &bytes_read) {
+				err = _get_platform_error()
+				return
+			}
+			envs, envs_err := _parse_environment_block(raw_data(envs_w), allocator)
+			if envs_err != nil {
+				err = envs_err
+				return
+			}
+			info.fields |= {.Environment}
+			info.environment = envs
+		}
+		if .Working_Dir in selection {
+			TEMP_ALLOCATOR_GUARD()
+			cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator())
+			if !read_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w, &bytes_read) {
+				err = _get_platform_error()
+				return
+			}
+			cwd, cwd_err := windows.utf16_to_utf8(cwd_w, allocator)
+			if cwd_err != nil {
+				err = cwd_err
+				return
+			}
+			info.fields |= {.Working_Dir}
+			info.working_dir = cwd
+		}
+	}
+	if .Username in selection {
+		username, username_err := _get_process_user(ph, allocator)
+		if username_err != nil {
+			err = username_err
+			return
+		}
+		info.fields |= {.Username}
+		info.username = username
+	}
+	err = nil
+	return
+}
+
+_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
+	info.pid = cast(int) windows.GetCurrentProcessId()
+	defer if err != nil {
+		free_process_info(info, allocator)
+	}
+	need_snapprocess := .PPid in selection || .Priority in selection
+	if need_snapprocess {
+		entry, entry_err := _process_entry_by_pid(info.pid)
+		if entry_err != nil {
+			err = General_Error.Not_Exist
+			return
+		}
+		if .PPid in selection {
+			info.fields += {.PPid}
+			info.ppid = int(entry.th32ProcessID)
+		}
+		if .Priority in selection {
+			info.fields += {.Priority}
+			info.priority = int(entry.pcPriClassBase)
+		}
+	}
+	if .Executable_Path in selection {
+		exe_filename_w: [256]u16
+		path_len := windows.GetModuleFileNameW(nil, raw_data(exe_filename_w[:]), len(exe_filename_w))
+		exe_filename, exe_filename_err := windows.utf16_to_utf8(exe_filename_w[:path_len], allocator)
+		if exe_filename_err != nil {
+			err = exe_filename_err
+			return
+		}
+		info.fields += {.Executable_Path}
+		info.executable_path = exe_filename
+	}
+	if .Command_Line in selection  || .Command_Args in selection {
+		command_line_w := windows.GetCommandLineW()
+		if .Command_Line in selection {
+			command_line, command_line_err := windows.wstring_to_utf8(command_line_w, -1, allocator)
+			if command_line_err != nil {
+				err = command_line_err
+				return
+			}
+			info.fields += {.Command_Line}
+			info.command_line = command_line
+		}
+		if .Command_Args in selection {
+			args, args_err := _parse_command_line(command_line_w, allocator)
+			if args_err != nil {
+				err = args_err
+				return
+			}
+			info.fields += {.Command_Args}
+			info.command_args = args
+		}
+	}
+	if .Environment in selection {
+		env_block := windows.GetEnvironmentStringsW()
+		envs, envs_err := _parse_environment_block(env_block, allocator)
+		if envs_err != nil {
+			err = envs_err
+			return
+		}
+		info.fields += {.Environment}
+		info.environment = envs
+	}
+	if .Username in selection {
+		process_handle := windows.GetCurrentProcess()
+		username, username_err := _get_process_user(process_handle, allocator)
+		if username_err != nil {
+			err = username_err
+			return
+		}
+		info.fields += {.Username}
+		info.username = username
+	}
+	if .Working_Dir in selection {
+		// TODO(flysand): Implement this by reading PEB
+		err = .Mode_Not_Implemented
+		return
+	}
+	err = nil
+	return
+}
+
+_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (Process, Error) {
+	// Note(flysand): The handle will be used for querying information so we
+	// take the necessary permissions right away.
+	dwDesiredAccess := windows.PROCESS_QUERY_LIMITED_INFORMATION | windows.SYNCHRONIZE
+	if .Mem_Read in flags {
+		dwDesiredAccess |= windows.PROCESS_VM_READ
+	}
+	if .Mem_Write in flags {
+		dwDesiredAccess |= windows.PROCESS_VM_WRITE
+	}
+	handle := windows.OpenProcess(
+		dwDesiredAccess,
+		false,
+		u32(pid),
+	)
+	if handle == windows.INVALID_HANDLE_VALUE {
+		return {}, _get_platform_error()
+	}
+	return Process {
+		pid = pid,
+		handle = cast(uintptr) handle,
+	}, nil
+}
+
+_Sys_Process_Attributes :: struct {}
+
+_process_start :: proc(desc: Process_Desc) -> (Process, Error) {
+	TEMP_ALLOCATOR_GUARD()
+	command_line := _build_command_line(desc.command, temp_allocator())
+	command_line_w := windows.utf8_to_wstring(command_line, temp_allocator())
+	environment := desc.env
+	if desc.env == nil {
+		environment = environ(temp_allocator())
+	}
+	environment_block := _build_environment_block(environment, temp_allocator())
+	environment_block_w := windows.utf8_to_utf16(environment_block, temp_allocator())
+	stderr_handle := windows.GetStdHandle(windows.STD_ERROR_HANDLE)
+	stdout_handle := windows.GetStdHandle(windows.STD_OUTPUT_HANDLE)
+	stdin_handle := windows.GetStdHandle(windows.STD_INPUT_HANDLE)
+	if desc.stdout != nil {
+		stdout_handle = windows.HANDLE((^File_Impl)(desc.stdout.impl).fd)
+	}
+	if desc.stderr != nil {
+		stderr_handle = windows.HANDLE((^File_Impl)(desc.stderr.impl).fd)
+	}
+	if desc.stdin != nil {
+		stdin_handle = windows.HANDLE((^File_Impl)(desc.stderr.impl).fd)
+	}
+	working_dir_w := windows.wstring(nil)
+	if len(desc.working_dir) > 0 {
+		working_dir_w = windows.utf8_to_wstring(desc.working_dir, temp_allocator())
+	}
+	process_info: windows.PROCESS_INFORMATION = ---
+	process_ok := windows.CreateProcessW(
+		nil,
+		command_line_w,
+		nil,
+		nil,
+		true,
+		windows.CREATE_UNICODE_ENVIRONMENT|windows.NORMAL_PRIORITY_CLASS,
+		raw_data(environment_block_w),
+		working_dir_w,
+		&windows.STARTUPINFOW {
+			cb = size_of(windows.STARTUPINFOW),
+			hStdError = stderr_handle,
+			hStdOutput = stdout_handle,
+			hStdInput = stdin_handle,
+			dwFlags = windows.STARTF_USESTDHANDLES,
+		},
+		&process_info,
+	)
+	if !process_ok {
+		return {}, _get_platform_error()
+	}
+	return Process {
+		pid = cast(int) process_info.dwProcessId,
+		handle = cast(uintptr) process_info.hProcess,
+	}, nil
+}
+
+_process_wait :: proc(process: Process, timeout: time.Duration) -> (Process_State, Error) {
+	handle := windows.HANDLE(process.handle)
+	timeout_ms := u32(timeout / time.Millisecond) if timeout > 0 else windows.INFINITE
+	wait_result := windows.WaitForSingleObject(handle, timeout_ms)
+	switch wait_result {
+	case windows.WAIT_OBJECT_0:
+		exit_code: u32 = ---
+		if !windows.GetExitCodeProcess(handle, &exit_code) {
+			return {}, _get_platform_error()
+		}
+		time_created: windows.FILETIME = ---
+		time_exited: windows.FILETIME = ---
+		time_kernel: windows.FILETIME = ---
+		time_user: windows.FILETIME = ---
+		if !windows.GetProcessTimes(handle, &time_created, &time_exited, &time_kernel, &time_user) {
+			return {}, _get_platform_error()
+		}
+		return Process_State {
+			exit_code = cast(int) exit_code,
+			exited = true,
+			pid = process.pid,
+			success = true,
+			system_time = _filetime_to_duration(time_kernel),
+			user_time = _filetime_to_duration(time_user),
+		}, nil
+	case windows.WAIT_TIMEOUT:
+		return {}, General_Error.Timeout
+	case:
+		return {}, _get_platform_error()
+	}
+}
+
+_process_close :: proc(process: Process) -> (Error) {
+	if !windows.CloseHandle(cast(windows.HANDLE) process.handle) {
+		return _get_platform_error()
+	}
+	return nil
+}
+
+_process_kill :: proc(process: Process) -> (Error) {
+	// Note(flysand): This is different than what the task manager's "kill process"
+	// functionality does, as we don't try to send WM_CLOSE message first. This
+	// is quite a rough way to kill the process, which should be consistent with
+	// linux. The error code 9 is to mimic SIGKILL event.
+	if !windows.TerminateProcess(windows.HANDLE(process.handle), 9) {
+		return _get_platform_error()
+	}
+	return nil
+}
+
+@(private)
+_filetime_to_duration :: proc(filetime: windows.FILETIME) -> time.Duration {
+	ticks := u64(filetime.dwHighDateTime)<<32 | u64(filetime.dwLowDateTime)
+	return time.Duration(ticks * 100)
+}
+
+@(private)
+_process_entry_by_pid :: proc(pid: int) -> (windows.PROCESSENTRY32W, Error) {
+	snap := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0)
+	if snap == windows.INVALID_HANDLE_VALUE {
+		return {}, _get_platform_error()
+	}
+	defer windows.CloseHandle(snap)
+	entry := windows.PROCESSENTRY32W { dwSize = size_of(windows.PROCESSENTRY32W) }
+	status := windows.Process32FirstW(snap, &entry)
+	found := false
+	for status {
+		if u32(pid) == entry.th32ProcessID {
+			found = true
+			break
+		}
+		status = windows.Process32NextW(snap, &entry)
+	}
+	if !found {
+		return {}, General_Error.Not_Exist
+	}
+	return entry, nil
+}
+
+// Note(flysand): Not sure which way it's better to get the executable path:
+// via toolhelp snapshots or by reading other process' PEB memory. I have
+// a slight suspicion that if both exe path and command line are desired,
+// it's faster to just read both from PEB, but maybe the toolhelp snapshots
+// are just better...?
+@(private)
+_process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (string, Error) {
+	snap := windows.CreateToolhelp32Snapshot(
+		windows.TH32CS_SNAPMODULE|windows.TH32CS_SNAPMODULE32,
+		u32(pid),
+	)
+	if snap == windows.INVALID_HANDLE_VALUE {
+		return "", _get_platform_error()
+	}
+	defer windows.CloseHandle(snap)
+	entry := windows.MODULEENTRY32W { dwSize = size_of(windows.MODULEENTRY32W) }
+	status := windows.Module32FirstW(snap, &entry)
+	if !status {
+		return "", _get_platform_error()
+	}
+	exe_path, err := windows.wstring_to_utf8(raw_data(entry.szExePath[:]), -1,  allocator)
+	if err != nil {
+		return "", err
+	}
+	return exe_path, nil
+}
+
+@(private)
+_get_process_user :: proc(process_handle: windows.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) {
+	TEMP_ALLOCATOR_GUARD()
+	token_handle: windows.HANDLE = ---
+	if !windows.OpenProcessToken(process_handle, windows.TOKEN_QUERY, &token_handle) {
+		err = _get_platform_error()
+		return
+	}
+	token_user_size: u32 = ---
+	if !windows.GetTokenInformation(token_handle, .TokenUser, nil, 0, &token_user_size) {
+		// Note(flysand): Make sure the buffer too small error comes out, and not any other error
+		err = _get_platform_error()
+		if v, ok := err.(Platform_Error); !ok || int(v) != 0x7a {
+			return
+		}
+	}
+	token_user := cast(^windows.TOKEN_USER) raw_data(make([]u8, token_user_size, temp_allocator()))
+	if !windows.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) {
+		err = _get_platform_error()
+		return
+	}
+	sid_type: windows.SID_NAME_USE = ---
+	username_w: [256]u16 = ---
+	domain_w: [256]u16 = ---
+	username_chrs: u32 = 256
+	domain_chrs: u32 = 256
+	if !windows.LookupAccountSidW(nil, token_user.User.Sid, &username_w[0], &username_chrs, &domain_w[0], &domain_chrs, &sid_type) {
+		err = _get_platform_error()
+		return
+	}
+	username, username_err := windows.utf16_to_utf8(username_w[:username_chrs], temp_allocator())
+	if username_err != nil {
+		err = username_err
+		return
+	}
+	domain, domain_err := windows.utf16_to_utf8(domain_w[:domain_chrs], temp_allocator())
+	if domain_err != nil {
+		err = domain_err
+		return
+	}
+	full_name, full_name_err := strings.concatenate([]string {domain, "\\", username}, allocator)
+	if full_name_err != nil {
+		err = full_name_err
+		return
+	}
+	return full_name, nil
+}
+
+@(private)
+_parse_command_line :: proc(cmd_line_w: [^]u16, allocator: runtime.Allocator) -> ([]string, Error) {
+	argc: i32 = ---
+	argv_w := windows.CommandLineToArgvW(cmd_line_w, &argc)
+	if argv_w == nil {
+		return nil, _get_platform_error()
+	}
+	argv, argv_err := make([]string, argc, allocator)
+	if argv_err != nil {
+		return nil, argv_err
+	}
+	for arg_w, i in argv_w[:argc] {
+		arg, arg_err := windows.wstring_to_utf8(arg_w, -1, allocator)
+		if arg_err != nil {
+			for s in argv[:i] {
+				delete(s, allocator)
+			}
+			delete(argv, allocator)
+			return nil, arg_err
+		}
+		argv[i] = arg
+	}
+	return argv, nil
+}
+
+@(private)
+_build_command_line :: proc(command: []string, allocator: runtime.Allocator) -> string {
+	_write_byte_n_times :: #force_inline proc(builder: ^strings.Builder, b: byte, n: int) {
+		for _ in 0 ..< n {
+			strings.write_byte(builder, b)
+		}
+	}
+	builder := strings.builder_make(allocator)
+	for arg, i in command {
+		if i != 0 {
+			strings.write_byte(&builder, ' ')
+		}
+		j := 0
+		strings.write_byte(&builder, '"')
+		for j < len(arg) {
+			backslashes := 0
+			for j < len(arg) && arg[j] == '\\' {
+				backslashes += 1
+				j += 1
+			}
+			if j == len(arg) {
+				_write_byte_n_times(&builder, '\\', 2*backslashes)
+				break
+			} else if arg[j] == '"' {
+				_write_byte_n_times(&builder, '\\', 2*backslashes+1)
+				strings.write_byte(&builder, '"')
+			} else {
+				_write_byte_n_times(&builder, '\\', backslashes)
+				strings.write_byte(&builder, arg[j])
+			}
+			j += 1
+		}
+		strings.write_byte(&builder, '"')
+	}
+	return strings.to_string(builder)
+}
+
+@(private)
+_parse_environment_block :: proc(block: [^]u16, allocator: runtime.Allocator) -> ([]string, Error) {
+	zt_count := 0
+	for idx := 0; true; {
+		if block[idx] == 0x0000 {
+			zt_count += 1
+			if block[idx+1] == 0x0000 {
+				zt_count += 1
+				break
+			}
+		}
+		idx += 1
+	}
+	// Note(flysand): Each string in the environment block is terminated
+	// by a NUL character. In addition, the environment block itself is
+	// terminated by a NUL character. So the number of strings in the
+	// environment block is the number of NUL character minus the
+	// block terminator.
+	env_count := zt_count - 1
+	envs := make([]string, env_count, allocator)
+	env_idx := 0
+	last_idx := 0
+	idx := 0
+	for block[idx] != 0x0000 {
+		for block[idx] != 0x0000 {
+			idx += 1
+		}
+		env_w := block[last_idx:idx]
+		env, env_err := windows.utf16_to_utf8(env_w, allocator)
+		if env_err != nil {
+			return nil, env_err
+		}
+		envs[env_idx] = env
+		env_idx += 1
+		idx += 1
+		last_idx = idx
+	}
+	return envs, nil
+}
+
+@(private)
+_build_environment_block :: proc(environment: []string, allocator: runtime.Allocator) -> string {
+	builder := strings.builder_make(allocator)
+	#reverse for kv, cur_idx in environment {
+		eq_idx := strings.index_byte(kv, '=')
+		assert(eq_idx != -1, "Malformed environment string. Expected '=' to separate keys and values")
+		key := kv[:eq_idx]
+		already_handled := false
+		for old_kv in environment[cur_idx+1:] {
+			old_key := old_kv[:strings.index_byte(old_kv, '=')]
+			if key == old_key {
+				already_handled = true
+				break
+			}
+		}
+		if already_handled {
+			continue
+		}
+		strings.write_bytes(&builder, transmute([]byte) kv)
+		strings.write_byte(&builder, 0)
+	}
+	// Note(flysand): In addition to the NUL-terminator for each string, the
+	// environment block itself is NUL-terminated.
+	strings.write_byte(&builder, 0)
+	return strings.to_string(builder)
+}
+
+@(private="file")
+PROCESSINFOCLASS :: enum i32 {
+    ProcessBasicInformation = 0,
+	ProcessDebugPort = 7,
+    ProcessWow64Information = 26,
+	ProcessImageFileName = 27,
+	ProcessBreakOnTermination = 29,
+	ProcessTelemetryIdInformation = 64,
+	ProcessSubsystemInformation = 75,
+}
+
+@(private="file")
+NtQueryInformationProcess_T :: #type proc (
+    ProcessHandle: windows.HANDLE,
+    ProcessInformationClass: PROCESSINFOCLASS,
+    ProcessInformation: rawptr,
+    ProcessInformationLength: u32,
+    ReturnLength: ^u32,
+) -> u32
+
+@(private="file")
+PROCESS_BASIC_INFORMATION :: struct {
+    _: rawptr,
+    PebBaseAddress: ^PEB,
+    _: [2]rawptr,
+    UniqueProcessId: ^u32,
+    _: rawptr,
+}
+
+@(private="file")
+PEB :: struct {
+    _: [2]u8,
+    BeingDebugged: u8,
+    _: [1]u8,
+    _: [2]rawptr,
+    Ldr: ^PEB_LDR_DATA,
+    ProcessParameters: ^RTL_USER_PROCESS_PARAMETERS,
+    _: [104]u8,
+    _: [52]rawptr,
+    PostProcessInitRoutine: #type proc "stdcall" (),
+    _: [128]u8,
+    _: [1]rawptr,
+    SessionId: u32,
+}
+
+@(private="file")
+PEB_LDR_DATA :: struct {
+    _: [8]u8,
+    _: [3]rawptr,
+    InMemoryOrderModuleList: LIST_ENTRY,
+}
+
+@(private="file")
+RTL_USER_PROCESS_PARAMETERS :: struct {
+	MaximumLength: u32,
+	Length: u32,
+	Flags: u32,
+	DebugFlags: u32,
+	ConsoleHandle: rawptr,
+	ConsoleFlags: u32,
+	StdInputHandle: rawptr,
+	StdOutputHandle: rawptr,
+	StdErrorHandle: rawptr,
+	CurrentDirectoryPath: UNICODE_STRING,
+	CurrentDirectoryHandle: rawptr,
+	DllPath: UNICODE_STRING,
+    ImagePathName: UNICODE_STRING,
+    CommandLine: UNICODE_STRING,
+	Environment: rawptr,
+	StartingPositionLeft: u32,
+	StartingPositionTop: u32,
+	Width: u32,
+	Height: u32,
+	CharWidth: u32,
+	CharHeight: u32,
+	ConsoleTextAttributes: u32,
+	WindowFlags: u32,
+	ShowWindowFlags: u32,
+	WindowTitle: UNICODE_STRING,
+	DesktopName: UNICODE_STRING,
+	ShellInfo: UNICODE_STRING,
+	RuntimeData: UNICODE_STRING,
+	DLCurrentDirectory: [32]RTL_DRIVE_LETTER_CURDIR,
+	EnvironmentSize: u32,
+}
+
+RTL_DRIVE_LETTER_CURDIR :: struct {
+	Flags: u16,
+	Length: u16,
+	TimeStamp: u32,
+	DosPath: UNICODE_STRING,
+}
+
+@(private="file")
+UNICODE_STRING :: struct {
+    Length: u16,
+    MaximumLength: u16,
+    Buffer: [^]u16,
+}
+
+@(private="file")
+LIST_ENTRY :: struct {
+	Flink: ^LIST_ENTRY,
+	Blink: ^LIST_ENTRY,
+}