use crate::{
Child, ChildKiller, CommandBuilder, ExitStatus, MasterPty, PtyPair, PtySize,
PtySystem, SlavePty,
};
use anyhow::{ensure, Context};
use filedescriptor::FileDescriptor;
use serial::{
BaudRate, CharSize, FlowControl, Parity, PortSettings, SerialPort, StopBits,
SystemPort,
};
use std::ffi::{OsStr, OsString};
use std::io::{Read, Result as IoResult, Write};
use std::sync::{Arc, Mutex};
use std::time::Duration;
type Handle = Arc<Mutex<SystemPort>>;
pub struct SerialTty {
port: OsString,
baud: BaudRate,
char_size: CharSize,
parity: Parity,
stop_bits: StopBits,
flow_control: FlowControl,
}
impl SerialTty {
pub fn new<T: AsRef<OsStr> + ?Sized>(port: &T) -> Self {
Self {
port: port.as_ref().to_owned(),
baud: BaudRate::Baud9600,
char_size: CharSize::Bits8,
parity: Parity::ParityNone,
stop_bits: StopBits::Stop1,
flow_control: FlowControl::FlowSoftware,
}
}
pub fn set_baud_rate(&mut self, baud: BaudRate) {
self.baud = baud;
}
pub fn set_char_size(&mut self, char_size: CharSize) {
self.char_size = char_size;
}
pub fn set_parity(&mut self, parity: Parity) {
self.parity = parity;
}
pub fn set_stop_bits(&mut self, stop_bits: StopBits) {
self.stop_bits = stop_bits;
}
pub fn set_flow_control(&mut self, flow_control: FlowControl) {
self.flow_control = flow_control;
}
}
impl PtySystem for SerialTty {
fn openpty(&self, _size: PtySize) -> anyhow::Result<PtyPair> {
let mut port = serial::open(&self.port)
.with_context(|| format!("openpty on serial port {:?}", self.port))?;
let settings = PortSettings {
baud_rate: self.baud,
char_size: self.char_size,
parity: self.parity,
stop_bits: self.stop_bits,
flow_control: self.flow_control,
};
log::debug!("serial settings: {:#?}", settings);
port.configure(&settings)?;
port.set_timeout(Duration::from_millis(50))?;
let port: Handle = Arc::new(Mutex::new(port));
Ok(PtyPair {
slave: Box::new(Slave {
port: Arc::clone(&port),
}),
master: Box::new(Master { port }),
})
}
}
struct Slave {
port: Handle,
}
impl SlavePty for Slave {
fn spawn_command(
&self,
cmd: CommandBuilder,
) -> anyhow::Result<Box<dyn Child + Send + Sync>> {
ensure!(
cmd.is_default_prog(),
"can only use default prog commands with serial tty implementations"
);
Ok(Box::new(SerialChild {
port: Arc::clone(&self.port),
}))
}
}
struct SerialChild {
port: Handle,
}
impl std::fmt::Debug for SerialChild {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
fmt.debug_struct("SerialChild").finish()
}
}
impl Child for SerialChild {
fn try_wait(&mut self) -> IoResult<Option<ExitStatus>> {
Ok(None)
}
fn wait(&mut self) -> IoResult<ExitStatus> {
loop {
std::thread::sleep(Duration::from_secs(5));
let mut port = self.port.lock().unwrap();
if let Err(err) = port.read_cd() {
log::error!("Error reading carrier detect: {:#}", err);
return Ok(ExitStatus::with_exit_code(1));
}
}
}
fn process_id(&self) -> Option<u32> {
None
}
#[cfg(windows)]
fn as_raw_handle(&self) -> Option<std::os::windows::io::RawHandle> {
None
}
}
impl ChildKiller for SerialChild {
fn kill(&mut self) -> IoResult<()> {
Ok(())
}
fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync> {
Box::new(SerialChildKiller)
}
}
#[derive(Debug)]
struct SerialChildKiller;
impl ChildKiller for SerialChildKiller {
fn kill(&mut self) -> IoResult<()> {
Ok(())
}
fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync> {
Box::new(SerialChildKiller)
}
}
struct Master {
port: Handle,
}
impl Write for Master {
fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
self.port.lock().unwrap().write(buf)
}
fn flush(&mut self) -> Result<(), std::io::Error> {
self.port.lock().unwrap().flush()
}
}
impl MasterPty for Master {
fn resize(&self, _size: PtySize) -> anyhow::Result<()> {
Ok(())
}
fn get_size(&self) -> anyhow::Result<PtySize> {
Ok(PtySize::default())
}
fn try_clone_reader(&self) -> anyhow::Result<Box<dyn std::io::Read + Send>> {
let fd = FileDescriptor::dup(&*self.port.lock().unwrap())?;
Ok(Box::new(Reader { fd }))
}
fn try_clone_writer(&self) -> anyhow::Result<Box<dyn std::io::Write + Send>> {
let port = Arc::clone(&self.port);
Ok(Box::new(Master { port }))
}
#[cfg(unix)]
fn process_group_leader(&self) -> Option<libc::pid_t> {
None
}
}
struct Reader {
fd: FileDescriptor,
}
impl Read for Reader {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
loop {
#[cfg(unix)]
{
use filedescriptor::{poll, pollfd, AsRawSocketDescriptor, POLLIN};
let mut poll_array = [pollfd {
fd: self.fd.as_socket_descriptor(),
events: POLLIN,
revents: 0,
}];
let _ = poll(&mut poll_array, None);
}
match self.fd.read(buf) {
Ok(0) => {
if cfg!(windows) {
continue;
}
return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"EOF on serial port",
));
}
Ok(size) => {
return Ok(size);
}
Err(e) => {
if e.kind() == std::io::ErrorKind::WouldBlock {
continue;
}
log::error!("serial read error: {}", e);
return Err(e);
}
}
}
}
}