Skip to content

Commit

Permalink
[os2/process]: Implement process_exec
Browse files Browse the repository at this point in the history
  • Loading branch information
flysand7 committed Sep 28, 2024
1 parent 7deb28c commit d830069
Showing 1 changed file with 90 additions and 0 deletions.
90 changes: 90 additions & 0 deletions core/os/os2/process.odin
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package os2

import "base:runtime"
import "core:strings"
import "core:time"

/*
Expand Down Expand Up @@ -347,6 +348,95 @@ 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.
Use this function when the target process doesn't require any input from stdin,
in order to complete.
This procedure does not free `stdout` and `stderr` slices before an error is
returned. Make sure to call `delete` on these slices.
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_exec :: proc(
desc: Process_Desc,
allocator: runtime.Allocator,
loc := #caller_location,
) -> (
state: Process_State,
stdout: []u8,
stderr: []u8,
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(stdout_w)
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_builder := strings.builder_make(allocator) or_return
stderr_builder := strings.builder_make(allocator) or_return
read_data: for {
buf: [1024]u8
n: int
has_data: bool
hangup := false
has_data, err = pipe_has_data(stdout_r)
if has_data {
n, err = read(stdout_r, buf[:])
strings.write_bytes(&stdout_builder, buf[:n])
}
switch err {
case nil: // nothing
case .Broken_Pipe:
hangup = true
case:
return
}
has_data, err = pipe_has_data(stderr_r)
if has_data {
n, err = read(stderr_r, buf[:])
strings.write_bytes(&stderr_builder, buf[:n])
}
switch err {
case nil: // nothing
case .Broken_Pipe:
hangup = true
case:
return
}
if hangup {
break read_data
}
}
err = nil
stdout = transmute([]u8) strings.to_string(stdout_builder)
stderr = transmute([]u8) strings.to_string(stderr_builder)
state = process_wait(process) or_return
return
}

/*
The state of the process after it has finished execution.
*/
Expand Down

0 comments on commit d830069

Please sign in to comment.