/development/std/process/capture_session_darwin.nct
capture_session_darwin.nct
//! Owning Darwin two-stream capture session.
//!
//! The session is the sole owner of capture buffers, the reusable bounded read buffer, stream
//! liveness, fair drain order, and cleanup after readiness or read failure. Raw poll and read facts
//! remain owned by `capture_darwin.nct`.
see ./capture_darwin.nct
see ./index.nct
see ./pipe_darwin.nct
use /mem.RawBuffer
use /mem
use /vec.Vec
#target: "arm64-darwin"
struct CaptureSession {
stdout_pipe: OwnedPipe
stderr_pipe: OwnedPipe
stdout: Vec<u8>
stderr: Vec<u8>
scratch: RawBuffer
}
#target: "arm64-darwin"
enum CaptureSessionAttempt {
complete(stdout: Vec<u8>, stderr: Vec<u8>)
failed(errno: i32)
malformed
}
#target: "arm64-darwin"
enum StreamDrainAttempt {
progress
interrupted
failed(errno: i32)
malformed
}
#target: "arm64-darwin"
enum CapturePreparationAttempt {
ready(session: CaptureSession)
failed(errno: i32)
}
#target: "arm64-darwin"
const CAPTURE_CHUNK_BYTES: usize = 8192
#target: "arm64-darwin"
func create_capture_session(): CapturePreparationAttempt {
match create_cloexec_pipe() {
PipeAttempt.failed(errno) { return CapturePreparationAttempt.failed(errno) }
PipeAttempt.ready(stdout_pipe) {
match create_cloexec_pipe() {
PipeAttempt.failed(errno) {
drop stdout_pipe
return CapturePreparationAttempt.failed(errno)
}
PipeAttempt.ready(stderr_pipe) {
return CapturePreparationAttempt.ready(prepare_capture_session(
move stdout_pipe,
move stderr_pipe,
))
}
}
}
}
}
#target: "arm64-darwin"
func prepare_capture_session(
stdout_pipe: OwnedPipe,
stderr_pipe: OwnedPipe,
): CaptureSession {
var allocator = mem.current_allocator()
return CaptureSession {
stdout_pipe: move stdout_pipe,
stderr_pipe: move stderr_pipe,
stdout: Vec.empty(),
stderr: Vec.empty(),
scratch: allocator.alloc(CAPTURE_CHUNK_BYTES, 1),
}
}
#target: "arm64-darwin"
func append_capture_bytes(
output: &+Vec<u8>,
scratch: &RawBuffer,
count: usize,
): void {
let bytes = scratch.bytes()
var index: usize = 0
while index < count {
output.push(bytes[index])
index += 1
}
return
}
#target: "arm64-darwin"
func drain_ready_stream(
pipe: &+OwnedPipe,
output: &+Vec<u8>,
scratch: &+RawBuffer,
): StreamDrainAttempt {
match read_capture_once(pipe_read_fd(pipe), scratch.bytes_mut()) {
CaptureReadAttempt.bytes(count) {
append_capture_bytes(output, scratch, count)
return StreamDrainAttempt.progress
}
CaptureReadAttempt.end {
close_pipe_read(pipe)
return StreamDrainAttempt.progress
}
CaptureReadAttempt.interrupted { return StreamDrainAttempt.interrupted }
CaptureReadAttempt.failed(errno) { return StreamDrainAttempt.failed(errno) }
CaptureReadAttempt.malformed { return StreamDrainAttempt.malformed }
}
}
#target: "arm64-darwin"
noalloc func close_capture_reads(session: &+CaptureSession): void {
close_pipe_read(&+session.stdout_pipe)
close_pipe_read(&+session.stderr_pipe)
return
}
#target: "arm64-darwin"
noalloc func close_capture_writes(session: &+CaptureSession): void {
close_pipe_write(&+session.stdout_pipe)
close_pipe_write(&+session.stderr_pipe)
return
}
#target: "arm64-darwin"
noalloc func close_all_capture_ends(session: &+CaptureSession): void {
close_capture_reads(session)
close_capture_writes(session)
return
}
#target: "arm64-darwin"
func capture_session_failed(session: &+CaptureSession, errno: i32): CaptureSessionAttempt {
close_capture_reads(session)
return CaptureSessionAttempt.failed(errno)
}
#target: "arm64-darwin"
func capture_session_malformed(session: &+CaptureSession): CaptureSessionAttempt {
close_capture_reads(session)
return CaptureSessionAttempt.malformed
}
#target: "arm64-darwin"
func drain_captured_streams(session: CaptureSession): CaptureSessionAttempt {
var capture = move session
close_capture_writes(&+capture)
while pipe_read_is_open(&capture.stdout_pipe) || pipe_read_is_open(&capture.stderr_pipe) {
match poll_captures_once(
pipe_read_fd(&capture.stdout_pipe),
pipe_read_is_open(&capture.stdout_pipe),
pipe_read_fd(&capture.stderr_pipe),
pipe_read_is_open(&capture.stderr_pipe),
) {
CapturePollAttempt.interrupted {}
CapturePollAttempt.failed(errno) {
return capture_session_failed(&+capture, errno)
}
CapturePollAttempt.malformed {
return capture_session_malformed(&+capture)
}
CapturePollAttempt.ready(stdout_events, stderr_events) {
if stdout_events.invalid || stderr_events.invalid {
return capture_session_malformed(&+capture)
}
if stdout_events.readable || stdout_events.error || stdout_events.hangup {
match drain_ready_stream(
&+capture.stdout_pipe,
&+capture.stdout,
&+capture.scratch,
) {
StreamDrainAttempt.progress {}
StreamDrainAttempt.interrupted {}
StreamDrainAttempt.failed(errno) {
return capture_session_failed(&+capture, errno)
}
StreamDrainAttempt.malformed {
return capture_session_malformed(&+capture)
}
}
}
if stderr_events.readable || stderr_events.error || stderr_events.hangup {
match drain_ready_stream(
&+capture.stderr_pipe,
&+capture.stderr,
&+capture.scratch,
) {
StreamDrainAttempt.progress {}
StreamDrainAttempt.interrupted {}
StreamDrainAttempt.failed(errno) {
return capture_session_failed(&+capture, errno)
}
StreamDrainAttempt.malformed {
return capture_session_malformed(&+capture)
}
}
}
}
}
}
return CaptureSessionAttempt.complete(move capture.stdout, move capture.stderr)
}