Programming Language

Nocter

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

/development/std/process/command_io_session_darwin.nct

command_io_session_darwin.nct

//! Owning Darwin stdin/stdout/stderr command-I/O session.
//!
//! One session owns all configured pipes, direction liveness, bounded fair progress, capture
//! buffers, and cleanup. Raw poll/read/write facts remain in `command_io_darwin.nct`.

see ./command_io_darwin.nct
see ./index.nct
see ./pipe_darwin.nct

use /mem.RawBuffer
use /mem
use /vec.Vec

#target: "arm64-darwin"
enum CommandInputChannel {
    inherited
    configured(state: CommandInputState)
}

#target: "arm64-darwin"
struct CommandInputState {
    pipe: OwnedPipe
    offset: usize
}

#target: "arm64-darwin"
struct CommandCaptureState {
    stdout_pipe: OwnedPipe
    stderr_pipe: OwnedPipe
    stdout: Vec<u8>
    stderr: Vec<u8>
    scratch: RawBuffer
}

#target: "arm64-darwin"
enum CommandCaptureChannels {
    inherited
    configured(state: CommandCaptureState)
}

#target: "arm64-darwin"
struct CommandIoSession {
    input: CommandInputChannel
    captures: CommandCaptureChannels
}

#target: "arm64-darwin"
enum CommandIoPreparationAttempt {
    ready(session: CommandIoSession)
    input_failed(errno: i32)
    capture_failed(errno: i32)
}

#target: "arm64-darwin"
enum InputChannelPreparationAttempt {
    ready(channel: CommandInputChannel)
    failed(errno: i32)
}

#target: "arm64-darwin"
enum CaptureChannelsPreparationAttempt {
    ready(channels: CommandCaptureChannels)
    failed(errno: i32)
}

#target: "arm64-darwin"
enum CapturedCommandOutput {
    inherited
    captured(stdout: Vec<u8>, stderr: Vec<u8>)
}

#target: "arm64-darwin"
struct CommandIoResult {
    output: CapturedCommandOutput
    input_failed: bool
    capture_failed: bool
}

#target: "arm64-darwin"
enum StreamDrainAttempt {
    progress
    interrupted
    failed(errno: i32)
    malformed
}

#target: "arm64-darwin"
const COMMAND_IO_CHUNK_BYTES: usize = 8192

#target: "arm64-darwin"
func create_input_channel(configured: bool): InputChannelPreparationAttempt {
    if !configured {
        return InputChannelPreparationAttempt.ready(CommandInputChannel.inherited)
    }
    match create_cloexec_pipe() {
        PipeAttempt.failed(errno) {
            return InputChannelPreparationAttempt.failed(errno)
        }
        PipeAttempt.ready(pipe) {
            match configure_pipe_writer_for_async_io(&pipe) {
                PipeConfigurationAttempt.ready {
                    return InputChannelPreparationAttempt.ready(
                        CommandInputChannel.configured(CommandInputState {
                            pipe: move pipe,
                            offset: 0,
                        }),
                    )
                }
                PipeConfigurationAttempt.failed(errno) {
                    drop pipe
                    return InputChannelPreparationAttempt.failed(errno)
                }
            }
        }
    }
}

#target: "arm64-darwin"
func create_capture_channels(configured: bool): CaptureChannelsPreparationAttempt {
    if !configured {
        return CaptureChannelsPreparationAttempt.ready(CommandCaptureChannels.inherited)
    }
    match create_cloexec_pipe() {
        PipeAttempt.failed(errno) {
            return CaptureChannelsPreparationAttempt.failed(errno)
        }
        PipeAttempt.ready(stdout_pipe) {
            match create_cloexec_pipe() {
                PipeAttempt.failed(errno) {
                    drop stdout_pipe
                    return CaptureChannelsPreparationAttempt.failed(errno)
                }
                PipeAttempt.ready(stderr_pipe) {
                    var allocator = mem.current_allocator()
                    return CaptureChannelsPreparationAttempt.ready(
                        CommandCaptureChannels.configured(CommandCaptureState {
                            stdout_pipe: move stdout_pipe,
                            stderr_pipe: move stderr_pipe,
                            stdout: Vec.empty(),
                            stderr: Vec.empty(),
                            scratch: allocator.alloc(COMMAND_IO_CHUNK_BYTES, 1),
                        }),
                    )
                }
            }
        }
    }
}

#target: "arm64-darwin"
func create_command_io_session(
    has_input: bool,
    captures_output: bool,
): CommandIoPreparationAttempt {
    match create_input_channel(has_input) {
        InputChannelPreparationAttempt.failed(errno) {
            return CommandIoPreparationAttempt.input_failed(errno)
        }
        InputChannelPreparationAttempt.ready(input) {
            match create_capture_channels(captures_output) {
                CaptureChannelsPreparationAttempt.failed(errno) {
                    drop input
                    return CommandIoPreparationAttempt.capture_failed(errno)
                }
                CaptureChannelsPreparationAttempt.ready(captures) {
                    return CommandIoPreparationAttempt.ready(CommandIoSession {
                        input: move input,
                        captures: move captures,
                    })
                }
            }
        }
    }
}

#target: "arm64-darwin"
noalloc func input_channel_parent_active(channel: &CommandInputChannel): bool {
    match channel {
        CommandInputChannel.inherited { return false }
        CommandInputChannel.configured(state) { return pipe_write_is_open(&state.pipe) }
    }
}

#target: "arm64-darwin"
noalloc func input_channel_parent_fd(channel: &CommandInputChannel): usize {
    match channel {
        CommandInputChannel.inherited { return 0 }
        CommandInputChannel.configured(state) { return pipe_write_fd(&state.pipe) }
    }
}

#target: "arm64-darwin"
noalloc func stdout_channel_parent_active(channels: &CommandCaptureChannels): bool {
    match channels {
        CommandCaptureChannels.inherited { return false }
        CommandCaptureChannels.configured(state) {
            return pipe_read_is_open(&state.stdout_pipe)
        }
    }
}

#target: "arm64-darwin"
noalloc func stdout_channel_parent_fd(channels: &CommandCaptureChannels): usize {
    match channels {
        CommandCaptureChannels.inherited { return 0 }
        CommandCaptureChannels.configured(state) {
            return pipe_read_fd(&state.stdout_pipe)
        }
    }
}

#target: "arm64-darwin"
noalloc func stderr_channel_parent_active(channels: &CommandCaptureChannels): bool {
    match channels {
        CommandCaptureChannels.inherited { return false }
        CommandCaptureChannels.configured(state) {
            return pipe_read_is_open(&state.stderr_pipe)
        }
    }
}

#target: "arm64-darwin"
noalloc func stderr_channel_parent_fd(channels: &CommandCaptureChannels): usize {
    match channels {
        CommandCaptureChannels.inherited { return 0 }
        CommandCaptureChannels.configured(state) {
            return pipe_read_fd(&state.stderr_pipe)
        }
    }
}

#target: "arm64-darwin"
noalloc func command_io_is_active(session: &CommandIoSession): bool {
    return input_channel_parent_active(&session.input)
        || stdout_channel_parent_active(&session.captures)
        || stderr_channel_parent_active(&session.captures)
}

#target: "arm64-darwin"
noalloc func close_input_channel_parent(channel: &+CommandInputChannel): void {
    match channel {
        CommandInputChannel.inherited {}
        CommandInputChannel.configured(state) { close_pipe_write(&+state.pipe) }
    }
    return
}

#target: "arm64-darwin"
noalloc func close_capture_channels_parent(channels: &+CommandCaptureChannels): void {
    match channels {
        CommandCaptureChannels.inherited {}
        CommandCaptureChannels.configured(state) {
            close_pipe_read(&+state.stdout_pipe)
            close_pipe_read(&+state.stderr_pipe)
        }
    }
    return
}

#target: "arm64-darwin"
noalloc func prepare_parent_command_io(session: &+CommandIoSession): void {
    match &+session.input {
        CommandInputChannel.inherited {}
        CommandInputChannel.configured(state) { close_pipe_read(&+state.pipe) }
    }
    match &+session.captures {
        CommandCaptureChannels.inherited {}
        CommandCaptureChannels.configured(state) {
            close_pipe_write(&+state.stdout_pipe)
            close_pipe_write(&+state.stderr_pipe)
        }
    }
    return
}

#target: "arm64-darwin"
func append_command_io_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 write_ready_command_input(
    channel: &+CommandInputChannel,
    input: &[u8]?,
): StreamDrainAttempt {
    let input_bytes = input otherwise { return StreamDrainAttempt.malformed }
    match channel {
        CommandInputChannel.inherited { return StreamDrainAttempt.malformed }
        CommandInputChannel.configured(state) {
            let position = state.offset
            match write_command_input_once(pipe_write_fd(&state.pipe), input_bytes, position) {
                CommandIoWriteAttempt.bytes(count) {
                    let next = position + count
                    state.offset = next
                    if next == input_bytes.len() { close_pipe_write(&+state.pipe) }
                    return StreamDrainAttempt.progress
                }
                CommandIoWriteAttempt.broken_pipe {
                    close_pipe_write(&+state.pipe)
                    return StreamDrainAttempt.progress
                }
                CommandIoWriteAttempt.would_block { return StreamDrainAttempt.interrupted }
                CommandIoWriteAttempt.interrupted { return StreamDrainAttempt.interrupted }
                CommandIoWriteAttempt.failed(errno) { return StreamDrainAttempt.failed(errno) }
                CommandIoWriteAttempt.malformed { return StreamDrainAttempt.malformed }
            }
        }
    }
}

#target: "arm64-darwin"
func drain_ready_stdout(channels: &+CommandCaptureChannels): StreamDrainAttempt {
    match channels {
        CommandCaptureChannels.inherited { return StreamDrainAttempt.malformed }
        CommandCaptureChannels.configured(state) {
            match read_command_io_once(
                pipe_read_fd(&state.stdout_pipe),
                state.scratch.bytes_mut(),
            ) {
                CommandIoReadAttempt.bytes(count) {
                    append_command_io_bytes(&+state.stdout, &state.scratch, count)
                    return StreamDrainAttempt.progress
                }
                CommandIoReadAttempt.end {
                    close_pipe_read(&+state.stdout_pipe)
                    return StreamDrainAttempt.progress
                }
                CommandIoReadAttempt.interrupted { return StreamDrainAttempt.interrupted }
                CommandIoReadAttempt.failed(errno) { return StreamDrainAttempt.failed(errno) }
                CommandIoReadAttempt.malformed { return StreamDrainAttempt.malformed }
            }
        }
    }
}

#target: "arm64-darwin"
func drain_ready_stderr(channels: &+CommandCaptureChannels): StreamDrainAttempt {
    match channels {
        CommandCaptureChannels.inherited { return StreamDrainAttempt.malformed }
        CommandCaptureChannels.configured(state) {
            match read_command_io_once(
                pipe_read_fd(&state.stderr_pipe),
                state.scratch.bytes_mut(),
            ) {
                CommandIoReadAttempt.bytes(count) {
                    append_command_io_bytes(&+state.stderr, &state.scratch, count)
                    return StreamDrainAttempt.progress
                }
                CommandIoReadAttempt.end {
                    close_pipe_read(&+state.stderr_pipe)
                    return StreamDrainAttempt.progress
                }
                CommandIoReadAttempt.interrupted { return StreamDrainAttempt.interrupted }
                CommandIoReadAttempt.failed(errno) { return StreamDrainAttempt.failed(errno) }
                CommandIoReadAttempt.malformed { return StreamDrainAttempt.malformed }
            }
        }
    }
}

#target: "arm64-darwin"
func finish_command_io(
    session: CommandIoSession,
    input_failed: bool,
    capture_failed: bool,
): CommandIoResult {
    var source = move session
    close_input_channel_parent(&+source.input)
    match move source.captures {
        CommandCaptureChannels.inherited {
            return CommandIoResult {
                output: CapturedCommandOutput.inherited,
                input_failed: input_failed,
                capture_failed: capture_failed,
            }
        }
        CommandCaptureChannels.configured(state) {
            return CommandIoResult {
                output: CapturedCommandOutput.captured(move state.stdout, move state.stderr),
                input_failed: input_failed,
                capture_failed: capture_failed,
            }
        }
    }
}

#target: "arm64-darwin"
func drain_command_io(session: CommandIoSession, input: &[u8]?): CommandIoResult {
    var state = move session
    var input_failed = false
    var capture_failed = false
    prepare_parent_command_io(&+state)
    if input_channel_parent_active(&state.input) {
        let input_bytes = input otherwise {
            input_failed = true
            close_input_channel_parent(&+state.input)
            return drain_command_io_after_input_validation(
                move state,
                input,
                input_failed,
                capture_failed,
            )
        }
        if input_bytes.len() == 0 { close_input_channel_parent(&+state.input) }
    }
    return drain_command_io_after_input_validation(
        move state,
        input,
        input_failed,
        capture_failed,
    )
}

#target: "arm64-darwin"
func drain_command_io_after_input_validation(
    session: CommandIoSession,
    input: &[u8]?,
    prior_input_failure: bool,
    prior_capture_failure: bool,
): CommandIoResult {
    var state = move session
    var input_failed = prior_input_failure
    var capture_failed = prior_capture_failure
    while command_io_is_active(&state) {
        match poll_command_io_once(
            input_channel_parent_fd(&state.input),
            input_channel_parent_active(&state.input),
            stdout_channel_parent_fd(&state.captures),
            stdout_channel_parent_active(&state.captures),
            stderr_channel_parent_fd(&state.captures),
            stderr_channel_parent_active(&state.captures),
        ) {
            CommandIoPollAttempt.interrupted {}
            CommandIoPollAttempt.failed(_) {
                if input_channel_parent_active(&state.input) { input_failed = true }
                if stdout_channel_parent_active(&state.captures)
                    || stderr_channel_parent_active(&state.captures) {
                    capture_failed = true
                }
                close_input_channel_parent(&+state.input)
                close_capture_channels_parent(&+state.captures)
            }
            CommandIoPollAttempt.malformed {
                if input_channel_parent_active(&state.input) { input_failed = true }
                if stdout_channel_parent_active(&state.captures)
                    || stderr_channel_parent_active(&state.captures) {
                    capture_failed = true
                }
                close_input_channel_parent(&+state.input)
                close_capture_channels_parent(&+state.captures)
            }
            CommandIoPollAttempt.ready(input_events, stdout_events, stderr_events) {
                if input_events.invalid {
                    input_failed = true
                    close_input_channel_parent(&+state.input)
                } else if input_channel_parent_active(&state.input)
                    && (input_events.writable || input_events.error || input_events.hangup) {
                    match write_ready_command_input(&+state.input, input) {
                        StreamDrainAttempt.progress {}
                        StreamDrainAttempt.interrupted {}
                        StreamDrainAttempt.failed(_) {
                            input_failed = true
                            close_input_channel_parent(&+state.input)
                        }
                        StreamDrainAttempt.malformed {
                            input_failed = true
                            close_input_channel_parent(&+state.input)
                        }
                    }
                }
                if stdout_events.invalid || stderr_events.invalid {
                    capture_failed = true
                    close_capture_channels_parent(&+state.captures)
                } else {
                    if stdout_channel_parent_active(&state.captures)
                        && (stdout_events.readable || stdout_events.error || stdout_events.hangup) {
                        match drain_ready_stdout(&+state.captures) {
                            StreamDrainAttempt.progress {}
                            StreamDrainAttempt.interrupted {}
                            StreamDrainAttempt.failed(_) {
                                capture_failed = true
                                close_capture_channels_parent(&+state.captures)
                            }
                            StreamDrainAttempt.malformed {
                                capture_failed = true
                                close_capture_channels_parent(&+state.captures)
                            }
                        }
                    }
                    if stderr_channel_parent_active(&state.captures)
                        && (stderr_events.readable || stderr_events.error || stderr_events.hangup) {
                        match drain_ready_stderr(&+state.captures) {
                            StreamDrainAttempt.progress {}
                            StreamDrainAttempt.interrupted {}
                            StreamDrainAttempt.failed(_) {
                                capture_failed = true
                                close_capture_channels_parent(&+state.captures)
                            }
                            StreamDrainAttempt.malformed {
                                capture_failed = true
                                close_capture_channels_parent(&+state.captures)
                            }
                        }
                    }
                }
            }
        }
    }
    return finish_command_io(move state, input_failed, capture_failed)
}