//! Bounded borrowed-buffer I/O loops. No descriptor or buffer ownership moves. use turkos_os_syscall_abi::syscall_abi::Errno; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Fault { Interrupted, Kernel(Errno), Wire } pub trait Writer { fn write_once(&mut self, bytes: &[u8]) -> Result; } pub trait Reader { fn read_once(&mut self, bytes: &mut [u8]) -> Result; } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Outcome { pub bytes: usize, pub calls: usize, pub interruptions: usize } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { BudgetExceeded(Outcome), Stalled(Outcome), EndOfInput(Outcome), InvalidCount(Outcome), Kernel(Errno, Outcome), Wire(Outcome), } /// Every attempt, including EINTR and a zero result, consumes one call. /// A second zero result without intervening byte progress is rejected. pub fn write_all(writer: &mut W, bytes: &[u8], max_calls: usize) -> Result { let mut state = Outcome { bytes: 0, calls: 0, interruptions: 0 }; let mut consecutive_zero = 0; while state.bytes < bytes.len() { if state.calls == max_calls { return Err(Error::BudgetExceeded(state)); } let remaining = &bytes[state.bytes..]; state.calls += 1; match writer.write_once(remaining) { Ok(0) => { consecutive_zero += 1; if consecutive_zero == 2 { return Err(Error::Stalled(state)); } } Ok(count) if count <= remaining.len() => { state.bytes += count; consecutive_zero = 0; } Ok(_) => return Err(Error::InvalidCount(state)), Err(Fault::Interrupted) => state.interruptions += 1, Err(Fault::Kernel(error)) => return Err(Error::Kernel(error, state)), Err(Fault::Wire) => return Err(Error::Wire(state)), } } Ok(state) } /// A zero read means EOF. Short reads and EINTR have distinct transitions. pub fn read_exact(reader: &mut R, bytes: &mut [u8], max_calls: usize) -> Result { let mut state = Outcome { bytes: 0, calls: 0, interruptions: 0 }; while state.bytes < bytes.len() { if state.calls == max_calls { return Err(Error::BudgetExceeded(state)); } let remaining = &mut bytes[state.bytes..]; state.calls += 1; match reader.read_once(remaining) { Ok(0) => return Err(Error::EndOfInput(state)), Ok(count) if count <= remaining.len() => state.bytes += count, Ok(_) => return Err(Error::InvalidCount(state)), Err(Fault::Interrupted) => state.interruptions += 1, Err(Fault::Kernel(error)) => return Err(Error::Kernel(error, state)), Err(Fault::Wire) => return Err(Error::Wire(state)), } } Ok(state) } #[cfg(all(target_arch = "aarch64", target_os = "none"))] pub struct NativeWriter { fd: u32 } #[cfg(all(target_arch = "aarch64", target_os = "none"))] impl NativeWriter { pub const fn new(fd: u32) -> Self { Self { fd } } } #[cfg(all(target_arch = "aarch64", target_os = "none"))] impl Writer for NativeWriter { fn write_once(&mut self, bytes: &[u8]) -> Result { use turkos_os_syscall_abi::syscall_abi::{AbiResult, CALL_WRITE, ABI_VERSION, MAX_WRITE}; if bytes.len() > MAX_WRITE { return Err(Fault::Kernel(Errno::Invalid)); } let mut result = self.fd as u64; unsafe { core::arch::asm!("svc #0", inout("x0") result, in("x1") bytes.as_ptr() as u64, in("x2") bytes.len() as u64, in("x7") ABI_VERSION, in("x8") CALL_WRITE, options(nostack)); } match AbiResult::decode(result) { Ok(AbiResult::Ok(value)) => Ok(value.value() as usize), Ok(AbiResult::Err(error)) => Err(Fault::Kernel(error)), Err(_) => Err(Fault::Wire), } } }