mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-05 23:28:48 +00:00
Merge pull request #3310 from flysand7/core-process
Addition of `core:os2/process` api.
This commit is contained in:
+341
-50
@@ -3,100 +3,391 @@ package os2
|
|||||||
import "core:sync"
|
import "core:sync"
|
||||||
import "core:time"
|
import "core:time"
|
||||||
import "base:runtime"
|
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) -> ! {
|
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 {
|
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 {
|
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 {
|
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 {
|
get_egid :: proc() -> int {
|
||||||
return -1
|
return _get_egid()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Obtain the ID of the current process.
|
||||||
|
*/
|
||||||
get_pid :: proc() -> int {
|
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 {
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
|
||||||
|
**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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
|
||||||
|
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 {
|
Process :: struct {
|
||||||
pid: int,
|
pid: int,
|
||||||
handle: uintptr,
|
handle: uintptr,
|
||||||
is_done: b32,
|
|
||||||
signal_mutex: sync.RW_Mutex,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
Process_Attributes :: struct {
|
/*
|
||||||
dir: string,
|
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`.
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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,
|
env: []string,
|
||||||
files: []^File,
|
// The `stderr` handle to give to the child process. It can be either a file
|
||||||
sys: ^Process_Attributes_OS_Specific,
|
// 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_Attributes_OS_Specific :: struct{}
|
/*
|
||||||
|
Create a new process and obtain its handle.
|
||||||
|
|
||||||
Process_Error :: enum {
|
This procedure creates a new process, with a given command and environment
|
||||||
None,
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
The state of the process after it has finished execution.
|
||||||
|
*/
|
||||||
Process_State :: struct {
|
Process_State :: struct {
|
||||||
pid: int,
|
// The ID of the process.
|
||||||
exit_code: int,
|
pid: int,
|
||||||
exited: bool,
|
// Specifies whether the process has terminated or is still running.
|
||||||
success: bool,
|
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,
|
system_time: time.Duration,
|
||||||
user_time: time.Duration,
|
// The time the process has spend executing in userspace.
|
||||||
sys: rawptr,
|
user_time: time.Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
Signal :: #type proc()
|
/*
|
||||||
|
Wait for a process event.
|
||||||
|
|
||||||
Kill: Signal = nil
|
This procedure blocks the execution until the process has exited or the
|
||||||
Interrupt: Signal = nil
|
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.
|
||||||
|
|
||||||
find_process :: proc(pid: int) -> (^Process, Process_Error) {
|
If an error is returned for any other reason, other than timeout, the
|
||||||
return nil, .None
|
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.
|
||||||
|
|
||||||
process_start :: proc(name: string, argv: []string, attr: ^Process_Attributes) -> (^Process, Process_Error) {
|
This procedure closes the handle associated with a process. It **does not**
|
||||||
return nil, .None
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
process_release :: proc(p: ^Process) -> Process_Error {
|
/*
|
||||||
return .None
|
Terminate a process.
|
||||||
|
|
||||||
|
This procedure terminates a process, specified by it's handle, `process`.
|
||||||
|
|
||||||
|
*/
|
||||||
|
process_kill :: proc(process: Process) -> (Error) {
|
||||||
|
return _process_kill(process)
|
||||||
}
|
}
|
||||||
|
|
||||||
process_kill :: proc(p: ^Process) -> Process_Error {
|
|
||||||
return .None
|
|
||||||
}
|
|
||||||
|
|
||||||
process_signal :: proc(p: ^Process, sig: Signal) -> Process_Error {
|
|
||||||
return .None
|
|
||||||
}
|
|
||||||
|
|
||||||
process_wait :: proc(p: ^Process) -> (Process_State, Process_Error) {
|
|
||||||
return {}, .None
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user