Browse Source

[os2/process]: Implement process_exec

flysand7 10 months ago
parent
commit
d830069e5c
1 changed files with 90 additions and 0 deletions
  1. 90 0
      core/os/os2/process.odin

+ 90 - 0
core/os/os2/process.odin

@@ -1,6 +1,7 @@
 package os2
 
 import "base:runtime"
+import "core:strings"
 import "core:time"
 
 /*
@@ -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.
 */