//! Platform-specific process and filesystem operations. //! //! Centralizes OS-dependent behavior behind a clean boundary so core //! modules don't scatter `#[cfg]` branches through product logic. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ForegroundProcess { pub pid: u32, pub name: String, pub argv0: Option, pub argv: Option>, pub cmdline: Option, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct ForegroundJob { pub process_group_id: u32, pub processes: Vec, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Signal { Hangup, Terminate, Kill, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct ClipboardCommand { pub program: &'static str, pub args: &'static [&'static str], } #[derive(Debug, Clone, PartialEq, Eq)] pub struct ClipboardImage { pub bytes: Vec, pub extension: &'static str, } #[derive(Debug, PartialEq, Eq)] pub(crate) enum LimitedRead { Empty, Complete(Vec), Oversized, } pub(crate) fn read_limited_reader( mut reader: impl std::io::Read, max_bytes: usize, ) -> std::io::Result { let mut bytes = Vec::new(); let mut buffer = [0_u8; 8192]; while bytes.len() < max_bytes { let remaining = max_bytes - bytes.len(); let read_len = remaining.min(buffer.len()); let bytes_read = match reader.read(&mut buffer[..read_len]) { Ok(bytes_read) => bytes_read, Err(err) if err.kind() == std::io::ErrorKind::Interrupted => continue, Err(err) => return Err(err), }; if bytes_read == 0 { return if bytes.is_empty() { Ok(LimitedRead::Empty) } else { Ok(LimitedRead::Complete(bytes)) }; } bytes.extend_from_slice(&buffer[..bytes_read]); } let mut sentinel = [0_u8; 1]; loop { return match reader.read(&mut sentinel) { Ok(0) if bytes.is_empty() => Ok(LimitedRead::Empty), Ok(0) => Ok(LimitedRead::Complete(bytes)), Ok(_) => Ok(LimitedRead::Oversized), Err(err) if err.kind() == std::io::ErrorKind::Interrupted => continue, Err(err) => Err(err), }; } } #[cfg(target_os = "linux")] mod linux; #[cfg(target_os = "linux")] pub use linux::*; #[cfg(target_os = "macos")] mod macos; #[cfg(target_os = "macos")] pub use macos::*; #[cfg(not(any(target_os = "linux", target_os = "macos")))] mod fallback; #[cfg(not(any(target_os = "linux", target_os = "macos")))] pub use fallback::*; #[cfg(test)] mod tests { use super::*; #[test] fn read_limited_reader_returns_complete_data_under_limit() { let input = std::io::Cursor::new(b"image".to_vec()); assert_eq!( read_limited_reader(input, 16).expect("limited read"), LimitedRead::Complete(b"image".to_vec()) ); } #[test] fn read_limited_reader_returns_empty_for_empty_input() { let input = std::io::Cursor::new(Vec::::new()); assert_eq!( read_limited_reader(input, 16).expect("limited read"), LimitedRead::Empty ); } #[test] fn read_limited_reader_accepts_data_exactly_at_limit() { let input = std::io::Cursor::new(b"four".to_vec()); assert_eq!( read_limited_reader(input, 4).expect("limited read"), LimitedRead::Complete(b"four".to_vec()) ); } #[test] fn read_limited_reader_rejects_data_over_limit() { let input = std::io::Cursor::new(b"oversized".to_vec()); assert_eq!( read_limited_reader(input, 4).expect("limited read"), LimitedRead::Oversized ); } #[test] fn read_limited_reader_retries_interrupted_reads() { struct InterruptedOnce { interrupted: bool, inner: std::io::Cursor>, } impl std::io::Read for InterruptedOnce { fn read(&mut self, buffer: &mut [u8]) -> std::io::Result { if !self.interrupted { self.interrupted = true; return Err(std::io::ErrorKind::Interrupted.into()); } self.inner.read(buffer) } } let input = InterruptedOnce { interrupted: false, inner: std::io::Cursor::new(b"image".to_vec()), }; assert_eq!( read_limited_reader(input, 16).expect("limited read"), LimitedRead::Complete(b"image".to_vec()) ); } }