Programming Language

Nocter

A self-contained systems language built around simplicity, encapsulation, and foolproof design.

/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)
}