mirror of
https://github.com/Ed94/Odin.git
synced 2026-07-11 22:01:25 -07:00
[os2/process]: Implement process creation procedures
This commit is contained in:
+146
-19
@@ -5,6 +5,12 @@ import "core:time"
|
||||
import "base:runtime"
|
||||
import "core:strings"
|
||||
|
||||
/*
|
||||
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.
|
||||
|
||||
@@ -212,6 +218,145 @@ free_process_info :: proc(pi: Process_Info, allocator: runtime.Allocator) {
|
||||
delete(pi.cwd, 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 {
|
||||
pid: int,
|
||||
handle: uintptr,
|
||||
}
|
||||
|
||||
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`.
|
||||
|
||||
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.
|
||||
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,
|
||||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
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 {
|
||||
// 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,
|
||||
}
|
||||
|
||||
/*
|
||||
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, then close the handle.
|
||||
*/
|
||||
process_close :: proc(process: Process) -> (Error) {
|
||||
return _process_close(process)
|
||||
}
|
||||
|
||||
// Process_Attributes :: struct {
|
||||
// dir: string,
|
||||
// env: []string,
|
||||
@@ -225,27 +370,13 @@ free_process_info :: proc(pi: Process_Info, allocator: runtime.Allocator) {
|
||||
// None,
|
||||
// }
|
||||
|
||||
// Process_State :: struct {
|
||||
// pid: int,
|
||||
// exit_code: int,
|
||||
// exited: bool,
|
||||
// success: bool,
|
||||
// system_time: time.Duration,
|
||||
// user_time: time.Duration,
|
||||
// sys: rawptr,
|
||||
// }
|
||||
|
||||
|
||||
// Signal :: #type proc()
|
||||
|
||||
// Kill: Signal = nil
|
||||
// Interrupt: Signal = nil
|
||||
|
||||
|
||||
// find_process :: proc(pid: int) -> (^Process, Process_Error) {
|
||||
// return nil, .None
|
||||
// }
|
||||
|
||||
|
||||
// process_start :: proc(name: string, argv: []string, attr: ^Process_Attributes) -> (^Process, Process_Error) {
|
||||
// return nil, .None
|
||||
// }
|
||||
@@ -262,10 +393,6 @@ free_process_info :: proc(pi: Process_Info, allocator: runtime.Allocator) {
|
||||
// return .None
|
||||
// }
|
||||
|
||||
// process_wait :: proc(p: ^Process) -> (Process_State, Process_Error) {
|
||||
// return {}, .None
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ package os2
|
||||
|
||||
import "core:sys/windows"
|
||||
import "core:strings"
|
||||
import "core:time"
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
_Process_Handle :: windows.HANDLE
|
||||
@@ -213,7 +215,7 @@ _process_info :: proc(pid: int, selection: Process_Info_Fields, allocator: runti
|
||||
info.command_line = cmdline
|
||||
}
|
||||
if .Command_Args in selection {
|
||||
args, args_err := _parse_argv(raw_data(cmdline_w), allocator)
|
||||
args, args_err := _parse_command_line(raw_data(cmdline_w), allocator)
|
||||
if args_err != nil {
|
||||
err = args_err
|
||||
return
|
||||
@@ -323,7 +325,7 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime
|
||||
info.command_line = command_line
|
||||
}
|
||||
if .Command_Args in selection {
|
||||
args, args_err := _parse_argv(command_line_w, allocator)
|
||||
args, args_err := _parse_command_line(command_line_w, allocator)
|
||||
if args_err != nil {
|
||||
err = args_err
|
||||
return
|
||||
@@ -356,6 +358,121 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime
|
||||
return
|
||||
}
|
||||
|
||||
_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (Process, Error) {
|
||||
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(desc.stdout.impl.fd)
|
||||
}
|
||||
if desc.stderr != nil {
|
||||
stderr_handle = windows.HANDLE(desc.stderr.impl.fd)
|
||||
}
|
||||
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),
|
||||
nil,
|
||||
&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
|
||||
}
|
||||
|
||||
@(private)
|
||||
_filetime_to_duration :: proc(filetime: windows.FILETIME) -> time.Duration {
|
||||
ticks := u64(filetime.dwHighDateTime)<<32 | u64(filetime.dwLowDateTime)
|
||||
return time.Duration(ticks * 100)
|
||||
}
|
||||
|
||||
@(private)
|
||||
_get_process_user :: proc(process_handle: windows.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) {
|
||||
@@ -406,7 +523,7 @@ _get_process_user :: proc(process_handle: windows.HANDLE, allocator: runtime.All
|
||||
}
|
||||
|
||||
@(private)
|
||||
_parse_argv :: proc(cmd_line_w: [^]u16, allocator: runtime.Allocator) -> ([]string, Error) {
|
||||
_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 {
|
||||
@@ -430,6 +547,43 @@ _parse_argv :: proc(cmd_line_w: [^]u16, allocator: runtime.Allocator) -> ([]stri
|
||||
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
|
||||
@@ -470,6 +624,32 @@ _parse_environment_block :: proc(block: [^]u16, allocator: runtime.Allocator) ->
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user