aboutsummaryrefslogtreecommitdiff
path: root/core/os/process.odin
diff options
context:
space:
mode:
Diffstat (limited to 'core/os/process.odin')
-rw-r--r--core/os/process.odin548
1 files changed, 548 insertions, 0 deletions
diff --git a/core/os/process.odin b/core/os/process.odin
new file mode 100644
index 000000000..9cdaf0457
--- /dev/null
+++ b/core/os/process.odin
@@ -0,0 +1,548 @@
+package os
+
+import "base:runtime"
+
+import "core:time"
+
+/*
+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")
+internal_args_to_free: []string
+
+@(private="file")
+get_args :: proc "contextless" () -> []string {
+ context = runtime.default_context()
+ result := make([]string, len(runtime.args__), heap_allocator())
+ for rt_arg, i in runtime.args__ {
+ result[i] = string(rt_arg)
+ }
+ internal_args_to_free = result
+ return result
+}
+
+@(fini, private="file")
+delete_args :: proc "contextless" () {
+ if internal_args_to_free != nil {
+ context = runtime.default_context()
+ delete(internal_args_to_free, heap_allocator())
+ }
+}
+
+/*
+Exit the current process.
+*/
+exit :: proc "contextless" (code: int) -> ! {
+ runtime.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.
+*/
+@(require_results)
+get_uid :: proc() -> int {
+ 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.
+*/
+@(require_results)
+get_euid :: proc() -> int {
+ 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.
+*/
+@(require_results)
+get_gid :: proc() -> int {
+ 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.
+*/
+@(require_results)
+get_egid :: proc() -> int {
+ return _get_egid()
+}
+
+/*
+Obtain the ID of the current process.
+*/
+@(require_results)
+get_pid :: proc() -> int {
+ 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.
+*/
+@(require_results)
+get_ppid :: proc() -> int {
+ return _get_ppid()
+}
+
+/*
+Obtain the current thread id
+*/
+@(require_results)
+get_current_thread_id :: proc "contextless" () -> int {
+ return _get_current_thread_id()
+}
+
+/*
+Return the number of cores
+*/
+get_processor_core_count :: proc() -> int {
+ return _get_processor_core_count()
+}
+
+/*
+Obtain ID's of all processes running in the system.
+*/
+@(require_results)
+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,
+}
+
+ALL_INFO :: Process_Info_Fields{.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. The
+`free_process_info` procedure needs to be called, even if this procedure
+returned an error, as some of the fields may have been allocated.
+
+**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 procedure.
+*/
+@(require_results)
+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. The
+`free_process_info` procedure needs to be called, even if this procedure
+returned an error, as some of the fields may have been allocated.
+
+**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 procedure.
+*/
+@(require_results)
+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 procedure. The
+`free_process_info` procedure needs to be called, even if this procedure
+returned an error, as some of the fields may have been allocated.
+
+**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 procedure.
+*/
+@(require_results)
+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)
+ for a in pi.command_args {
+ delete(a, allocator)
+ }
+ delete(pi.command_args, allocator)
+ for s in pi.environment {
+ delete(s, allocator)
+ }
+ delete(pi.environment, allocator)
+ delete(pi.working_dir, allocator)
+ delete(pi.username, 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.
+*/
+@(require_results)
+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 {
+ // 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.
+ // NOTE(laytan): maybe should be `Maybe([]string)` so you can do `nil` == current env, empty == empty/no env.
+ 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 in an unpredictable manner. In case multiple threads change
+handle inheritance properties, make sure to serialize all those calls.
+*/
+@(require_results)
+process_start :: proc(desc: Process_Desc) -> (Process, Error) {
+ return _process_start(desc)
+}
+
+/*
+Execute the process and capture stdout and stderr streams.
+
+This procedure creates a new process, with a given command and environment
+strings as parameters, and waits until the process finishes execution. While
+the process is running, this procedure accumulates the output of its stdout
+and stderr streams and returns byte slices containing the captured data from
+the streams.
+
+This procedure expects that `stdout` and `stderr` fields of the `desc` parameter
+are left at default, i.e. a `nil` value. You can not capture stdout/stderr and
+redirect it to a file at the same time.
+
+This procedure does not free `stdout` and `stderr` slices before an error is
+returned. Make sure to call `delete` on these slices.
+*/
+@(require_results)
+process_exec :: proc(
+ desc: Process_Desc,
+ allocator: runtime.Allocator,
+ loc := #caller_location,
+) -> (
+ state: Process_State,
+ stdout: []byte,
+ stderr: []byte,
+ err: Error,
+) {
+ assert(desc.stdout == nil, "Cannot redirect stdout when it's being captured", loc)
+ assert(desc.stderr == nil, "Cannot redirect stderr when it's being captured", loc)
+
+ stdout_r, stdout_w := pipe() or_return
+ defer close(stdout_r)
+ stderr_r, stderr_w := pipe() or_return
+ defer close(stderr_r)
+
+ process: Process
+ {
+ // NOTE(flysand): Make sure the write-ends are closed, regardless
+ // of the outcome. This makes read-ends readable on our side.
+ defer close(stdout_w)
+ defer close(stderr_w)
+ desc := desc
+ desc.stdout = stdout_w
+ desc.stderr = stderr_w
+ process = process_start(desc) or_return
+ }
+
+ {
+ stdout_b: [dynamic]byte
+ stdout_b.allocator = allocator
+
+ stderr_b: [dynamic]byte
+ stderr_b.allocator = allocator
+
+ buf: [1024]u8 = ---
+
+ stdout_done, stderr_done, has_data: bool
+ for err == nil && (!stdout_done || !stderr_done) {
+ n := 0
+
+ if !stdout_done {
+ has_data, err = pipe_has_data(stdout_r)
+ if has_data {
+ n, err = read(stdout_r, buf[:])
+ }
+
+ switch err {
+ case nil:
+ _, err = append(&stdout_b, ..buf[:n])
+ case .EOF, .Broken_Pipe:
+ stdout_done = true
+ err = nil
+ }
+ }
+
+ if err == nil && !stderr_done {
+ n = 0
+ has_data, err = pipe_has_data(stderr_r)
+ if has_data {
+ n, err = read(stderr_r, buf[:])
+ }
+
+ switch err {
+ case nil:
+ _, err = append(&stderr_b, ..buf[:n])
+ case .EOF, .Broken_Pipe:
+ stderr_done = true
+ err = nil
+ }
+ }
+ }
+
+ stdout = stdout_b[:]
+ stderr = stderr_b[:]
+ }
+
+ if err != nil {
+ state, _ = process_wait(process, timeout=0)
+ if !state.exited {
+ _ = process_kill(process)
+ state, _ = process_wait(process)
+ }
+ return
+ }
+
+ state, err = process_wait(process)
+ return
+}
+
+/*
+ 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.
+*/
+@(require_results)
+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.
+*/
+@(require_results)
+process_close :: proc(process: Process) -> (Error) {
+ return _process_close(process)
+}
+
+/*
+Terminate a process.
+
+This procedure terminates a process, specified by it's handle, `process`.
+*/
+@(require_results)
+process_kill :: proc(process: Process) -> (Error) {
+ return _process_kill(process)
+}