portable_pty/
serial.rs

1//! This module implements a serial port based tty.
2//! This is a bit different from the other implementations in that
3//! we cannot explicitly spawn a process into the serial connection,
4//! so we can only use a `CommandBuilder::new_default_prog` with the
5//! `openpty` method.
6//! On most (all?) systems, attempting to open multiple instances of
7//! the same serial port will fail.
8use crate::{
9  Child, ChildKiller, CommandBuilder, ExitStatus, MasterPty, PtyPair, PtySize,
10  PtySystem, SlavePty,
11};
12use anyhow::{ensure, Context};
13use filedescriptor::FileDescriptor;
14use serial::{
15  BaudRate, CharSize, FlowControl, Parity, PortSettings, SerialPort, StopBits,
16  SystemPort,
17};
18use std::ffi::{OsStr, OsString};
19use std::io::{Read, Result as IoResult, Write};
20use std::sync::{Arc, Mutex};
21use std::time::Duration;
22
23type Handle = Arc<Mutex<SystemPort>>;
24
25pub struct SerialTty {
26  port: OsString,
27  baud: BaudRate,
28  char_size: CharSize,
29  parity: Parity,
30  stop_bits: StopBits,
31  flow_control: FlowControl,
32}
33
34impl SerialTty {
35  pub fn new<T: AsRef<OsStr> + ?Sized>(port: &T) -> Self {
36    Self {
37      port: port.as_ref().to_owned(),
38      baud: BaudRate::Baud9600,
39      char_size: CharSize::Bits8,
40      parity: Parity::ParityNone,
41      stop_bits: StopBits::Stop1,
42      flow_control: FlowControl::FlowSoftware,
43    }
44  }
45
46  pub fn set_baud_rate(&mut self, baud: BaudRate) {
47    self.baud = baud;
48  }
49
50  pub fn set_char_size(&mut self, char_size: CharSize) {
51    self.char_size = char_size;
52  }
53
54  pub fn set_parity(&mut self, parity: Parity) {
55    self.parity = parity;
56  }
57
58  pub fn set_stop_bits(&mut self, stop_bits: StopBits) {
59    self.stop_bits = stop_bits;
60  }
61
62  pub fn set_flow_control(&mut self, flow_control: FlowControl) {
63    self.flow_control = flow_control;
64  }
65}
66
67impl PtySystem for SerialTty {
68  fn openpty(&self, _size: PtySize) -> anyhow::Result<PtyPair> {
69    let mut port = serial::open(&self.port)
70      .with_context(|| format!("openpty on serial port {:?}", self.port))?;
71
72    let settings = PortSettings {
73      baud_rate: self.baud,
74      char_size: self.char_size,
75      parity: self.parity,
76      stop_bits: self.stop_bits,
77      flow_control: self.flow_control,
78    };
79    log::debug!("serial settings: {:#?}", settings);
80    port.configure(&settings)?;
81
82    // The timeout needs to be rather short because, at least on Windows,
83    // a read with a long timeout will block a concurrent write from
84    // happening.  In wezterm we tend to have a thread looping on read
85    // while writes happen occasionally from the gui thread, and if we
86    // make this timeout too long we can block the gui thread.
87    port.set_timeout(Duration::from_millis(50))?;
88
89    let port: Handle = Arc::new(Mutex::new(port));
90
91    Ok(PtyPair {
92      slave: Box::new(Slave {
93        port: Arc::clone(&port),
94      }),
95      master: Box::new(Master { port }),
96    })
97  }
98}
99
100struct Slave {
101  port: Handle,
102}
103
104impl SlavePty for Slave {
105  fn spawn_command(
106    &self,
107    cmd: CommandBuilder,
108  ) -> anyhow::Result<Box<dyn Child + Send + Sync>> {
109    ensure!(
110      cmd.is_default_prog(),
111      "can only use default prog commands with serial tty implementations"
112    );
113    Ok(Box::new(SerialChild {
114      port: Arc::clone(&self.port),
115    }))
116  }
117}
118
119/// There isn't really a child process on the end of the serial connection,
120/// so all of the Child trait impls are NOP
121struct SerialChild {
122  port: Handle,
123}
124
125// An anemic impl of Debug to satisfy some indirect trait bounds
126impl std::fmt::Debug for SerialChild {
127  fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
128    fmt.debug_struct("SerialChild").finish()
129  }
130}
131
132impl Child for SerialChild {
133  fn try_wait(&mut self) -> IoResult<Option<ExitStatus>> {
134    Ok(None)
135  }
136
137  fn wait(&mut self) -> IoResult<ExitStatus> {
138    // There isn't really a child process to wait for,
139    // as the serial connection never really "dies",
140    // however, for something like a USB serial port,
141    // if it is unplugged then it logically is terminated.
142    // We read the CD (carrier detect) signal periodically
143    // to see if the device has gone away: we actually discard
144    // the CD value itself and just look for an error state.
145    // We could potentially also decide to call CD==false the
146    // same thing as the "child" completing.
147    loop {
148      std::thread::sleep(Duration::from_secs(5));
149
150      let mut port = self.port.lock().unwrap();
151      if let Err(err) = port.read_cd() {
152        log::error!("Error reading carrier detect: {:#}", err);
153        return Ok(ExitStatus::with_exit_code(1));
154      }
155    }
156  }
157
158  fn process_id(&self) -> Option<u32> {
159    None
160  }
161
162  #[cfg(windows)]
163  fn as_raw_handle(&self) -> Option<std::os::windows::io::RawHandle> {
164    None
165  }
166}
167
168impl ChildKiller for SerialChild {
169  fn kill(&mut self) -> IoResult<()> {
170    Ok(())
171  }
172
173  fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync> {
174    Box::new(SerialChildKiller)
175  }
176}
177
178#[derive(Debug)]
179struct SerialChildKiller;
180
181impl ChildKiller for SerialChildKiller {
182  fn kill(&mut self) -> IoResult<()> {
183    Ok(())
184  }
185
186  fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync> {
187    Box::new(SerialChildKiller)
188  }
189}
190
191struct Master {
192  port: Handle,
193}
194
195impl Write for Master {
196  fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
197    self.port.lock().unwrap().write(buf)
198  }
199
200  fn flush(&mut self) -> Result<(), std::io::Error> {
201    self.port.lock().unwrap().flush()
202  }
203}
204
205impl MasterPty for Master {
206  fn resize(&self, _size: PtySize) -> anyhow::Result<()> {
207    // Serial ports have no concept of size
208    Ok(())
209  }
210
211  fn get_size(&self) -> anyhow::Result<PtySize> {
212    // Serial ports have no concept of size
213    Ok(PtySize::default())
214  }
215
216  fn try_clone_reader(&self) -> anyhow::Result<Box<dyn std::io::Read + Send>> {
217    // We rely on the fact that SystemPort implements the traits
218    // that expose the underlying file descriptor, and that direct
219    // reads from that return the raw data that we want
220    let fd = FileDescriptor::dup(&*self.port.lock().unwrap())?;
221    Ok(Box::new(Reader { fd }))
222  }
223
224  fn try_clone_writer(&self) -> anyhow::Result<Box<dyn std::io::Write + Send>> {
225    let port = Arc::clone(&self.port);
226    Ok(Box::new(Master { port }))
227  }
228
229  #[cfg(unix)]
230  fn process_group_leader(&self) -> Option<libc::pid_t> {
231    // N/A: there is no local process
232    None
233  }
234}
235
236struct Reader {
237  fd: FileDescriptor,
238}
239
240impl Read for Reader {
241  fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
242    // On windows, this self.fd.read will block for up to the time we set
243    // as the timeout when we set up the port, but on unix it will
244    // never block.
245    loop {
246      #[cfg(unix)]
247      {
248        use filedescriptor::{poll, pollfd, AsRawSocketDescriptor, POLLIN};
249        // The serial crate puts the serial port in non-blocking mode,
250        // so we must explicitly poll for ourselves here to avoid a
251        // busy loop.
252        let mut poll_array = [pollfd {
253          fd: self.fd.as_socket_descriptor(),
254          events: POLLIN,
255          revents: 0,
256        }];
257        let _ = poll(&mut poll_array, None);
258      }
259
260      match self.fd.read(buf) {
261        Ok(0) => {
262          if cfg!(windows) {
263            // Read timeout with no data available yet;
264            // loop and try again.
265            continue;
266          }
267          return Err(std::io::Error::new(
268            std::io::ErrorKind::UnexpectedEof,
269            "EOF on serial port",
270          ));
271        }
272        Ok(size) => {
273          return Ok(size);
274        }
275        Err(e) => {
276          if e.kind() == std::io::ErrorKind::WouldBlock {
277            continue;
278          }
279          log::error!("serial read error: {}", e);
280          return Err(e);
281        }
282      }
283    }
284  }
285}