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, PtySystem,
10    SlavePty,
11};
12use anyhow::{ensure, Context};
13use filedescriptor::FileDescriptor;
14use serial2::{CharSize, FlowControl, Parity, SerialPort, StopBits};
15use std::cell::RefCell;
16use std::ffi::{OsStr, OsString};
17use std::io::{Read, Result as IoResult, Write};
18#[cfg(unix)]
19use std::path::PathBuf;
20use std::sync::Arc;
21use std::time::Duration;
22
23type Handle = Arc<SerialPort>;
24
25pub struct SerialTty {
26    port: OsString,
27    baud: u32,
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: 9600,
39            char_size: CharSize::Bits8,
40            parity: Parity::None,
41            stop_bits: StopBits::One,
42            flow_control: FlowControl::XonXoff,
43        }
44    }
45
46    pub fn set_baud_rate(&mut self, baud: u32) {
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 = SerialPort::open(&self.port, self.baud)
70            .with_context(|| format!("openpty on serial port {:?}", self.port))?;
71
72        let mut settings = port.get_configuration()?;
73        settings.set_raw();
74        settings.set_baud_rate(self.baud)?;
75        settings.set_char_size(self.char_size);
76        settings.set_flow_control(self.flow_control);
77        settings.set_parity(self.parity);
78        settings.set_stop_bits(self.stop_bits);
79        log::debug!("serial settings: {:#?}", port.get_configuration());
80        port.set_configuration(&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_read_timeout(Duration::from_millis(50))?;
88        port.set_write_timeout(Duration::from_millis(50))?;
89
90        let port: Handle = Arc::new(port);
91
92        Ok(PtyPair {
93            slave: Box::new(Slave {
94                port: Arc::clone(&port),
95            }),
96            master: Box::new(Master {
97                port,
98                took_writer: RefCell::new(false),
99            }),
100        })
101    }
102}
103
104struct Slave {
105    port: Handle,
106}
107
108impl SlavePty for Slave {
109    fn spawn_command(&self, cmd: CommandBuilder) -> anyhow::Result<Box<dyn Child + Send + Sync>> {
110        ensure!(
111            cmd.is_default_prog(),
112            "can only use default prog commands with serial tty implementations"
113        );
114        Ok(Box::new(SerialChild {
115            port: Arc::clone(&self.port),
116        }))
117    }
118}
119
120/// There isn't really a child process on the end of the serial connection,
121/// so all of the Child trait impls are NOP
122struct SerialChild {
123    port: Handle,
124}
125
126// An anemic impl of Debug to satisfy some indirect trait bounds
127impl std::fmt::Debug for SerialChild {
128    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
129        fmt.debug_struct("SerialChild").finish()
130    }
131}
132
133impl Child for SerialChild {
134    fn try_wait(&mut self) -> IoResult<Option<ExitStatus>> {
135        Ok(None)
136    }
137
138    fn wait(&mut self) -> IoResult<ExitStatus> {
139        // There isn't really a child process to wait for,
140        // as the serial connection never really "dies",
141        // however, for something like a USB serial port,
142        // if it is unplugged then it logically is terminated.
143        // We read the CD (carrier detect) signal periodically
144        // to see if the device has gone away: we actually discard
145        // the CD value itself and just look for an error state.
146        // We could potentially also decide to call CD==false the
147        // same thing as the "child" completing.
148        loop {
149            std::thread::sleep(Duration::from_secs(5));
150
151            let port = &self.port;
152            if let Err(err) = port.read_cd() {
153                log::error!("Error reading carrier detect: {:#}", err);
154                return Ok(ExitStatus::with_exit_code(1));
155            }
156        }
157    }
158
159    fn process_id(&self) -> Option<u32> {
160        None
161    }
162
163    #[cfg(windows)]
164    fn as_raw_handle(&self) -> Option<std::os::windows::io::RawHandle> {
165        None
166    }
167}
168
169impl ChildKiller for SerialChild {
170    fn kill(&mut self) -> IoResult<()> {
171        Ok(())
172    }
173
174    fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync> {
175        Box::new(SerialChildKiller)
176    }
177}
178
179#[derive(Debug)]
180struct SerialChildKiller;
181
182impl ChildKiller for SerialChildKiller {
183    fn kill(&mut self) -> IoResult<()> {
184        Ok(())
185    }
186
187    fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync> {
188        Box::new(SerialChildKiller)
189    }
190}
191
192struct Master {
193    port: Handle,
194    took_writer: RefCell<bool>,
195}
196
197struct MasterWriter {
198    port: Handle,
199}
200
201impl Write for MasterWriter {
202    fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
203        self.port.write(buf)
204    }
205
206    fn flush(&mut self) -> Result<(), std::io::Error> {
207        self.port.flush()
208    }
209}
210
211impl MasterPty for Master {
212    fn resize(&self, _size: PtySize) -> anyhow::Result<()> {
213        // Serial ports have no concept of size
214        Ok(())
215    }
216
217    fn get_size(&self) -> anyhow::Result<PtySize> {
218        // Serial ports have no concept of size
219        Ok(PtySize::default())
220    }
221
222    fn try_clone_reader(&self) -> anyhow::Result<Box<dyn std::io::Read + Send>> {
223        // We rely on the fact that SystemPort implements the traits
224        // that expose the underlying file descriptor, and that direct
225        // reads from that return the raw data that we want
226        let fd = FileDescriptor::dup(&*self.port)?;
227        Ok(Box::new(Reader { fd }))
228    }
229
230    fn take_writer(&self) -> anyhow::Result<Box<dyn std::io::Write + Send>> {
231        if *self.took_writer.borrow() {
232            anyhow::bail!("cannot take writer more than once");
233        }
234        *self.took_writer.borrow_mut() = true;
235        let port = Arc::clone(&self.port);
236        Ok(Box::new(MasterWriter { port }))
237    }
238
239    #[cfg(unix)]
240    fn process_group_leader(&self) -> Option<libc::pid_t> {
241        // N/A: there is no local process
242        None
243    }
244
245    #[cfg(unix)]
246    fn as_raw_fd(&self) -> Option<crate::unix::RawFd> {
247        None
248    }
249
250    #[cfg(unix)]
251    fn tty_name(&self) -> Option<PathBuf> {
252        None
253    }
254}
255
256struct Reader {
257    fd: FileDescriptor,
258}
259
260impl Read for Reader {
261    fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
262        // On windows, this self.fd.read will block for up to the time we set
263        // as the timeout when we set up the port, but on unix it will
264        // never block.
265        loop {
266            #[cfg(unix)]
267            {
268                use filedescriptor::{poll, pollfd, AsRawSocketDescriptor, POLLIN};
269                // The serial crate puts the serial port in non-blocking mode,
270                // so we must explicitly poll for ourselves here to avoid a
271                // busy loop.
272                let mut poll_array = [pollfd {
273                    fd: self.fd.as_socket_descriptor(),
274                    events: POLLIN,
275                    revents: 0,
276                }];
277                let _ = poll(&mut poll_array, None);
278            }
279
280            match self.fd.read(buf) {
281                Ok(0) => {
282                    if cfg!(windows) {
283                        // Read timeout with no data available yet;
284                        // loop and try again.
285                        continue;
286                    }
287                    return Err(std::io::Error::new(
288                        std::io::ErrorKind::UnexpectedEof,
289                        "EOF on serial port",
290                    ));
291                }
292                Ok(size) => {
293                    return Ok(size);
294                }
295                Err(e) => {
296                    if e.kind() == std::io::ErrorKind::WouldBlock {
297                        continue;
298                    }
299                    log::error!("serial read error: {}", e);
300                    return Err(e);
301                }
302            }
303        }
304    }
305}