diff --git a/Cargo.lock b/Cargo.lock index 21e7f7d..4f0a6c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,24 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "bitflags" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - [[package]] name = "itoa" version = "1.0.15" @@ -38,23 +20,11 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" -[[package]] -name = "nix" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" -dependencies = [ - "bitflags", - "cfg-if", - "cfg_aliases", - "libc", -] - [[package]] name = "oc2r-rust" version = "0.1.0" dependencies = [ - "nix", + "libc", "serde", "serde_json", ] diff --git a/Cargo.toml b/Cargo.toml index a896d3e..69d7c25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,4 @@ codegen-units = 1 [dependencies] serde = { version = "1", features = ["derive"]} serde_json = "1" -nix = { version = "0.30", features = ["term", "poll"]} +libc = "0.2" diff --git a/src/main.rs b/src/main.rs index e7b9dec..d1effc7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,8 @@ -use nix::errno::Errno; -use nix::poll::{PollFd, PollFlags, PollTimeout, poll}; -use nix::sys::termios; -use nix::sys::termios::SpecialCharacterIndices::{VMIN, VTIME}; -use nix::unistd::{read, write}; use serde::{Deserialize, Serialize}; use std::fs::OpenOptions; -use std::os::fd::{AsFd, BorrowedFd}; -use std::{io, result}; +use std::io; +use std::os::fd::{AsRawFd, RawFd}; +use std::result; // Requests ----------------------------------------------------------- #[derive(Serialize, Debug)] @@ -76,7 +72,6 @@ type Result = result::Result; #[derive(Debug)] enum Error { Io(io::Error), - Nix(Errno), Json(serde_json::Error), Framing, Protocol(&'static str), @@ -86,109 +81,193 @@ impl From for Error { Error::Io(e) } } -impl From for Error { - fn from(e: Errno) -> Self { - Error::Nix(e) - } -} impl From for Error { fn from(e: serde_json::Error) -> Self { Error::Json(e) } } -fn set_raw(fd: BorrowedFd<'_>) -> Result<()> { - let mut term = termios::tcgetattr(fd)?; - term.local_flags.remove( - termios::LocalFlags::ICANON | termios::LocalFlags::ECHO | termios::LocalFlags::ISIG, - ); - term.control_chars[VMIN as usize] = 1; - term.control_chars[VTIME as usize] = 0; - termios::tcsetattr(fd, termios::SetArg::TCSANOW, &term)?; +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Io(e) => write!(f, "io error: {e}"), + Error::Json(e) => write!(f, "json error: {e}"), + Error::Framing => f.write_str("invalid message framing"), + Error::Protocol(msg) => write!(f, "protocol error: {msg}"), + } + } +} + +impl std::error::Error for Error {} + +const DELIM: u8 = 0; + +fn set_raw(fd: RawFd) -> Result<()> { + unsafe { + let mut term = std::mem::zeroed::(); + if libc::tcgetattr(fd, &mut term) != 0 { + return Err(Error::Io(io::Error::last_os_error())); + } + libc::cfmakeraw(&mut term); + term.c_cc[libc::VMIN] = 1; + term.c_cc[libc::VTIME] = 0; + if libc::tcsetattr(fd, libc::TCSANOW, &term) != 0 { + return Err(Error::Io(io::Error::last_os_error())); + } + } Ok(()) } -fn flush(fd: BorrowedFd<'_>) -> Result<()> { - let mut p = [PollFd::new(fd, PollFlags::POLLIN)]; - let mut tmp = [0u8; 1024]; +fn flush(fd: RawFd) -> Result<()> { + println!("Flushing pending input…"); + let mut buf = [0u8; 1024]; + let mut total = 0usize; loop { - match poll(&mut p, 0u8)? { - 0 => return Ok(()), - _ => { - let _ = read(fd, &mut tmp)?; + let ready = loop { + let mut pfd = libc::pollfd { + fd, + events: libc::POLLIN, + revents: 0, + }; + let res = unsafe { libc::poll(&mut pfd, 1, 0) }; + if res < 0 { + let err = io::Error::last_os_error(); + if err.kind() == io::ErrorKind::Interrupted { + continue; + } + return Err(Error::Io(err)); + } + break res; + }; + + if ready == 0 { + if total == 0 { + println!("Flush complete; nothing pending."); + } else { + println!("Flush complete; drained {total} bytes."); + } + return Ok(()); + } + + loop { + let read = unsafe { libc::read(fd, buf.as_mut_ptr().cast(), buf.len()) }; + if read < 0 { + let err = io::Error::last_os_error(); + match err.kind() { + io::ErrorKind::Interrupted => continue, + io::ErrorKind::WouldBlock => break, + _ => return Err(Error::Io(err)), + } + } else if read == 0 { + break; + } else { + total += read as usize; + if read as usize != buf.len() { + break; + } } } } } #[inline] -fn write_all_fd(fd: BorrowedFd<'_>, mut buf: &[u8]) -> Result<()> { +fn write_all_fd(fd: RawFd, mut buf: &[u8]) -> Result<()> { while !buf.is_empty() { - match write(fd, buf) { - Ok(0) => return Err(Error::Framing), - Ok(n) => buf = &buf[n..], - Err(Errno::EINTR) => continue, - Err(e) => return Err(e.into()), - } - } - Ok(()) -} - -#[inline] -fn read_exact_fd(fd: BorrowedFd<'_>, mut buf: &mut [u8]) -> Result<()> { - while !buf.is_empty() { - match read(fd, buf) { - Ok(0) => return Err(Error::Framing), - Ok(n) => { - let tmp = buf; - buf = &mut tmp[n..]; + let written = unsafe { libc::write(fd, buf.as_ptr().cast(), buf.len()) }; + if written < 0 { + let err = io::Error::last_os_error(); + match err.kind() { + io::ErrorKind::Interrupted => continue, + io::ErrorKind::WouldBlock => continue, + _ => return Err(Error::Io(err)), } - Err(Errno::EINTR) | Err(Errno::EAGAIN) => continue, - Err(e) => return Err(e.into()), + } else if written == 0 { + return Err(Error::Framing); + } else { + buf = &buf[written as usize..]; } } Ok(()) } -#[inline] -fn write_len(fd: BorrowedFd<'_>, len: usize) -> Result<()> { - let l = u32::try_from(len).map_err(|_| Error::Framing)?; - write_all_fd(fd, &l.to_le_bytes()) -} - -#[inline] -fn read_len(fd: BorrowedFd<'_>) -> Result { - let mut b = [0u8; 4]; - read_exact_fd(fd, &mut b)?; - Ok(u32::from_le_bytes(b) as usize) -} - -fn write_message(fd: BorrowedFd<'_>, msg: &impl Serialize) -> Result<()> { +fn write_message(fd: RawFd, msg: &impl Serialize) -> Result<()> { let payload = serde_json::to_vec(msg)?; - write_len(fd, payload.len())?; + println!("Sending message: {}", String::from_utf8_lossy(&payload)); + write_all_fd(fd, &[DELIM])?; write_all_fd(fd, &payload)?; + write_all_fd(fd, &[DELIM])?; Ok(()) } -fn read_message(fd: BorrowedFd<'_>) -> Result { - let mut p = [PollFd::new(fd, PollFlags::POLLIN)]; - poll(&mut p, Option::::None)?; // infinite - let len = read_len(fd)?; - let mut buf = vec![0u8; len]; - read_exact_fd(fd, &mut buf)?; - Ok(serde_json::from_slice(&buf)?) +fn wait_for_readable(fd: RawFd) -> Result<()> { + loop { + let mut pfd = libc::pollfd { + fd, + events: libc::POLLIN, + revents: 0, + }; + let res = unsafe { libc::poll(&mut pfd, 1, -1) }; + if res < 0 { + let err = io::Error::last_os_error(); + if err.kind() == io::ErrorKind::Interrupted { + continue; + } + return Err(Error::Io(err)); + } + if res > 0 { + return Ok(()); + } + } +} + +fn read_message(fd: RawFd) -> Result { + println!("Waiting for response…"); + wait_for_readable(fd)?; + let mut buf = Vec::new(); + let mut byte = [0u8; 1]; + loop { + let read = unsafe { libc::read(fd, byte.as_mut_ptr().cast(), 1) }; + if read < 0 { + let err = io::Error::last_os_error(); + match err.kind() { + io::ErrorKind::Interrupted => continue, + io::ErrorKind::WouldBlock => { + wait_for_readable(fd)?; + continue; + } + _ => return Err(Error::Io(err)), + } + } else if read == 0 { + return Err(Error::Framing); + } else { + let value = byte[0]; + if value == DELIM { + if buf.is_empty() { + continue; + } + println!("Received message: {}", String::from_utf8_lossy(&buf)); + return Ok(serde_json::from_slice(&buf)?); + } else { + buf.push(value); + } + } + } } fn main() -> Result<()> { + println!("Opening /dev/hvc0…"); let file = OpenOptions::new() .read(true) .write(true) .open("/dev/hvc0")?; - let fd = file.as_fd(); + let fd = file.as_raw_fd(); + println!("Configuring raw console mode…"); set_raw(fd)?; + println!("Console is now in raw mode."); flush(fd)?; + println!("Requesting device list…"); write_message(fd, &RpcRequest::List)?; match read_message(fd)? { RpcResponse::List { data } => {